do not edit — generated by btf.
git.druid.rocksindexdruid520mpsrc/merge.c

src/merge.c


#define _POSIX_C_SOURCE 200809L
#define _NETBSD_SOURCE 1
 
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
 
#include "mpx.h"
 
/* creates every ancestor directory of path (path itself is a file, not
 * a directory to create) -- same "walk each '/' boundary" approach as a
 * shell's own mkdir -p, tolerating EEXIST (the common case: most of a
 * package's directory tree already exists from an earlier file in the
 * same manifest, or from another package entirely). anything else
 * (ENOTDIR -- a path component already exists as a plain file/symlink,
 * genuine corruption) is fatal: silently continuing would merge this
 * file somewhere other than where its manifest says it belongs. */
static void
mkdirp(const char* path)
{
	char buf[MAXPATH];
	size_t n = strlen(path);
	size_t i;
 
	if(n >= sizeof(buf))
	{
		die("path too long");
		return;
	}
	memcpy(buf, path, n + 1);
	for(i = 1; i < n; i = i + 1)
	{
		if(buf[i] == '/')
		{
			buf[i] = '\0';
			if(mkdir(buf, 0755) != 0 && errno != EEXIST)
			{
				die("failed creating destination directory");
				return;
			}
			buf[i] = '/';
		}
	}
}
 
/* atomically replaces dst with a symlink to whatever src points at:
 * built as a fresh symlink at dst+".mp-merge-tmp" then rename()d over
 * dst, same reasoning as the regular-file case in merge_one below --
 * dst is never briefly missing partway through.
 *
 * an ABSOLUTE target rooted at srcprefix is rewritten to the same
 * relative path under dstprefix instead of copied verbatim -- a port's
 * own install phase routinely bakes its OWN $MP_PREFIX into a symlink
 * it creates (musl's own install.sh -D -l does exactly this for
 * ld-musl-*.so.1 -> libc.so), and src-prefix is a throwaway build
 * location for both mp.sysroot and "mp reinstall --inplace": copying
 * that target as-is leaves a symlink pointing at a path that is about
 * to stop existing the moment the ephemeral/sysroot build is cleaned
 * up. hit for real during this feature's own use: an --inplace musl
 * rebuild left /lib/ld-musl-x86_64.so.1 pointing at
 * /usr/mp/.inplace/musl/root/lib/libc.so, breaking every dynamically
 * linked binary on the live root (including the shell) the moment that
 * scratch directory was removed. a target that ISN'T rooted at
 * srcprefix (relative, or absolute but pointing somewhere else
 * entirely) is left exactly as-is. */
static void
merge_symlink(const char* srcprefix, const char* dstprefix, const char* src, const char* dst)
{
	char target[MAXPATH];
	char final[MAXPATH];
	char tmp[MAXPATH];
	size_t off = 0;
	size_t srclen = strlen(srcprefix);
	ssize_t tn = readlink(src, target, sizeof(target) - 1);
 
	if(tn < 0)
	{
		die("failed reading source symlink");
		return;
	}
	target[tn] = '\0';
	if(strncmp(target, srcprefix, srclen) == 0
	    && (target[srclen] == '/' || target[srclen] == '\0'))
	{
		size_t foff = 0;
 
		if(bufcat(final, sizeof(final), &foff, dstprefix) == 0
		    || bufcat(final, sizeof(final), &foff, target + srclen) == 0)
		{
			die("path too long");
			return;
		}
	}
	else
	{
		if(strlen(target) >= sizeof(final))
		{
			die("path too long");
			return;
		}
		memcpy(final, target, (size_t)tn + 1);
	}
	if(bufcat(tmp, sizeof(tmp), &off, dst) == 0
	    || bufcat(tmp, sizeof(tmp), &off, ".mp-merge-tmp") == 0)
	{
		die("path too long");
		return;
	}
	unlink(tmp);
	if(symlink(final, tmp) != 0)
	{
		die("failed creating replacement symlink");
		return;
	}
	if(rename(tmp, dst) != 0)
	{
		die("failed replacing destination symlink");
		return;
	}
}
 
/* atomically replaces dst with src's content and permission bits: full
 * content is streamed straight from src's fd to a temp file in dst's
 * own directory (never loaded whole into memory the way readfile/
 * writefile's MAXFILE cap would require -- a real installed binary,
 * unlike a port script or config file, routinely exceeds 1MB), then
 * rename()d over dst so dst is never briefly truncated or missing. */
static void
merge_regular(const char* src, const char* dst, mode_t mode)
{
	static char buf[65536];
	char tmp[MAXPATH];
	size_t off = 0;
	int sfd;
	int dfd;
	ssize_t n;
 
	if(bufcat(tmp, sizeof(tmp), &off, dst) == 0
	    || bufcat(tmp, sizeof(tmp), &off, ".mp-merge-tmp") == 0)
	{
		die("path too long");
		return;
	}
	sfd = open(src, O_RDONLY);
	if(sfd < 0)
	{
		die("failed opening source file");
		return;
	}
	unlink(tmp);
	dfd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0600);
	if(dfd < 0)
	{
		/* die() exits the whole process in practice, so this close()
		 * never actually runs -- kept anyway (and checked by every
		 * return below that follows an fd open) so this file is
		 * correct plain C89 even without relying on that: no
		 * noreturn hint on die() (a GNU/Clang extension mpx doesn't
		 * use, see mpx.h's own comment) means the compiler must treat
		 * every "return;" here as a real path or the leak. */
		close(sfd);
		die("failed creating replacement file");
		return;
	}
	n = read(sfd, buf, sizeof(buf));
	while(n > 0)
	{
		ssize_t off2 = 0;
 
		while(off2 < n)
		{
			ssize_t w = write(dfd, buf + off2, (size_t)(n - off2));
 
			if(w <= 0)
			{
				close(sfd);
				close(dfd);
				die("failed writing replacement file");
				return;
			}
			off2 = off2 + w;
		}
		n = read(sfd, buf, sizeof(buf));
	}
	if(n < 0 || fchmod(dfd, mode & 07777) != 0)
	{
		close(sfd);
		close(dfd);
		die("failed finishing replacement file");
		return;
	}
	close(sfd);
	close(dfd);
	if(rename(tmp, dst) != 0)
	{
		die("failed replacing destination file");
		return;
	}
}
 
/* one manifest entry: rel is INSTPREFIX-relative, exactly as written by
 * mplib::db::write_manifest (and read back by read_lines) -- no leading
 * slash, no trailing newline. */
static void
merge_one(const char* srcprefix, const char* dstprefix, const char* rel)
{
	char src[MAXPATH];
	char dst[MAXPATH];
	struct stat st;
 
	if(!pathfits(srcprefix, rel) || !pathfits(dstprefix, rel)
	    || pathjoin(src, sizeof(src), srcprefix, rel) == 0
	    || pathjoin(dst, sizeof(dst), dstprefix, rel) == 0)
	{
		die("path too long");
		return;
	}
	if(lstat(src, &st) != 0)
	{
		die("manifest lists a file the source tree no longer has");
		return;
	}
	mkdirp(dst);
	if(S_ISLNK(st.st_mode))
	{
		merge_symlink(srcprefix, dstprefix, src, dst);
	}
	else if(S_ISREG(st.st_mode))
	{
		merge_regular(src, dst, st.st_mode);
	}
	else if(S_ISDIR(st.st_mode))
	{
		/* fstree.c's own "path\tdir" snapshot marker (added this
		 * session, tracking a directory's own existence -- e.g. an
		 * otherwise-empty directory a package's install: phase
		 * creates) means a manifest CAN legitimately list a directory
		 * now, not just regular files and symlinks -- this comment's
		 * own claim otherwise was true when merge.c was first written,
		 * not anymore. mkdirp above only ever creates rel's ANCESTORS
		 * (by design, see its own comment: "path itself is a file, not
		 * a directory"), so the leaf itself still needs creating here.
		 * EEXIST tolerated same as mkdirp's own: a directory shared
		 * with another already-merged package (or one this same
		 * package already created via an earlier manifest entry) is
		 * the common case, not corruption. */
		if(mkdir(dst, st.st_mode) != 0 && errno != EEXIST)
		{
			die("failed creating destination directory");
			return;
		}
	}
	else
	{
		/* walktree (the only writer of any manifest) never records
		 * anything but regular files, symlinks, and (as of the fix
		 * above) directories -- anything else here means the manifest
		 * and the tree it describes have already diverged. */
		die("manifest entry is neither a regular file, symlink, nor directory");
		return;
	}
}
 
/* mpx merge <src-prefix> <dst-prefix> <manifest-file>
 *
 * folds one package's (or, called once per line-group by a caller that
 * concatenates several, a whole sysroot's) installed files from a
 * built-elsewhere tree onto the live one: every path the manifest lists
 * is copied from src-prefix into dst-prefix, overwriting whatever was
 * there, atomically per file/symlink (see merge_regular/merge_symlink).
 * never deletes anything -- a path dst-prefix has that the manifest
 * doesn't mention is simply left alone, on purpose (see mp.sysroot's
 * own comment for why: this is an overlay, not a sync).
 *
 * this is the primitive mp.sysroot and "mp reinstall --inplace" both
 * build on to avoid the del.sh-then-add.sh window a normal reinstall
 * goes through -- the new tree is fully built (into a sysroot, or an
 * ephemeral stand-in for one) before a single live file changes, and
 * every file that does change is replaced in one rename(), never
 * deleted-then-recreated. */
void
cmd_merge(int argc, char** argv)
{
	static char manifest[MAXFILE];
	long n;
	long start;
	long i;
 
	if(argc != 5)
	{
		badusage(argv[0], "merge <src-prefix> <dst-prefix> <manifest-file>");
		return;
	}
	n = readfile(argv[4], manifest, (long)sizeof(manifest));
	if(n == -2)
	{
		die("manifest too large to merge (increase MAXFILE)");
		return;
	}
	if(n < 0)
	{
		die("failed reading manifest");
		return;
	}
	start = 0;
	for(i = 0; i < n; i = i + 1)
	{
		if(manifest[i] == '\n')
		{
			if(i > start)
			{
				char rel[MAXPATH];
				long len = i - start;
 
				if((size_t)len >= sizeof(rel))
				{
					die("manifest entry too long");
					return;
				}
				memcpy(rel, manifest + start, (size_t)len);
				rel[len] = '\0';
				merge_one(argv[2], argv[3], rel);
			}
			start = i + 1;
		}
	}
}
powered by btf.