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

src/fstree.c


#define _POSIX_C_SOURCE 200809L
#define _NETBSD_SOURCE 1
 
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
 
#include "mpx.h"
 
void
copyfile(const char* src, const char* dst)
{
	static char buf[MAXFILE];
	long n;
 
	n = readfile(src, buf, (long)sizeof(buf));
	if(n == -2)
	{
		die("port file too large to stage (increase MAXFILE)");
	}
	if(n < 0)
	{
		die("failed reading port file");
	}
	if(writefile(dst, buf, n) < 0)
	{
		die("failed writing staged file");
	}
}
 
void
copytree(const char* src, const char* dst)
{
	DIR* d;
	struct dirent* e;
	char s[MAXPATH];
	char t[MAXPATH];
	struct stat st;
 
	if(mkdir(dst, 0755) != 0)
	{
		die("failed creating stage dir");
	}
	d = opendir(src);
	if(d == NULL)
	{
		die("failed reading port dir");
	}
	e = readdir(d);
	while(e != NULL)
	{
		if(strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0)
		{
			if(pathjoin(s, sizeof(s), src, e->d_name) == 0
			    || pathjoin(t, sizeof(t), dst, e->d_name) == 0)
			{
				die("path too long");
			}
			if(lstat(s, &st) != 0)
			{
				die("failed reading port file");
			}
			if(S_ISDIR(st.st_mode))
			{
				copytree(s, t);
			}
			else
			{
				copyfile(s, t);
			}
		}
		e = readdir(d);
	}
	closedir(d);
}
 
void
rmtree(const char* path)
{
	DIR* d;
	struct dirent* e;
	char sub[MAXPATH];
	struct stat st;
 
	d = opendir(path);
	if(d == NULL)
	{
		unlink(path);
		return;
	}
	e = readdir(d);
	while(e != NULL)
	{
		if(strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0
		    && pathjoin(sub, sizeof(sub), path, e->d_name) != 0)
		{
			if(lstat(sub, &st) == 0)
			{
				if(S_ISDIR(st.st_mode))
				{
					rmtree(sub);
				}
				else
				{
					unlink(sub);
				}
			}
		}
		e = readdir(d);
	}
	closedir(d);
	rmdir(path);
}
 
void
walktree(const char* base, const char* rel, FILE* out, dev_t basedev,
    int has_exclude, dev_t excldev, ino_t exclino)
{
	DIR* d;
	struct dirent* e;
	char full[MAXPATH];
	char nrel[MAXPATH];
	struct stat st;
 
	if(pathjoin(full, sizeof(full), base, rel) == 0)
	{
		die("path too long");
	}
	d = opendir(full);
	if(d == NULL)
	{
		return;
	}
	e = readdir(d);
	while(e != NULL)
	{
		if(strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0)
		{
			/* unlike rmtree's best-effort cleanup (a leftover file from a
			 * too-long path is a small, tolerable harm next to blocking
			 * the whole removal), a path this snapshot silently drops is
			 * a path that then silently doesn't exist as far as `mp
			 * files`/install-conflict-detection are concerned -- die
			 * loudly instead, matching copytree's own die-on-overflow,
			 * rather than let the manifest quietly go out of sync with
			 * what's actually on disk. */
			{
				size_t dn = strlen(e->d_name);
 
				if(rel[0] != '\0')
				{
					if(pathjoin(nrel, sizeof(nrel), rel, e->d_name) == 0)
					{
						die("path too long");
					}
				}
				else
				{
					if(dn > sizeof(nrel) - 1)
					{
						die("path too long");
					}
					memcpy(nrel, e->d_name, dn);
					nrel[dn] = '\0';
				}
				if(pathjoin(full, sizeof(full), base, nrel) == 0)
				{
					die("path too long");
				}
			}
			if(lstat(full, &st) == 0)
			{
				if(S_ISDIR(st.st_mode))
				{
					/* don't cross into a separate filesystem while
					 * snapshotting an install prefix -- same boundary
					 * "find -xdev" stops at, and the reason a bind-
					 * mounted /proc (or /sys, /dev, or any other bind
					 * mount) inside a chrooted build doesn't get its
					 * entire, constantly-changing content recorded as
					 * some package's own installed files. compared by
					 * device+inode, not the path string: a prefix of
					 * "/" makes pathjoin's own "a" + "/" + "b" produce
					 * a redundant leading "//" that a literal path
					 * comparison would have to know to tolerate. */
					if(st.st_dev != basedev)
					{
						/* different filesystem: don't descend */
					}
					/* the configured WD (staging area) itself, if it
					 * happens to live INSIDE the prefix being
					 * snapshotted (true whenever MP_PREFIX is "/", never
					 * true for a normal disjoint MP_PREFIX/WD pair) --
					 * every package's own git checkout/build churn under
					 * there is staging state, never that package's own
					 * installed output, and walking it wholesale is both
					 * enormous and wrong. same device+inode comparison,
					 * for the same reason as above. */
					else if(has_exclude && st.st_dev == excldev && st.st_ino == exclino)
					{
						/* this directory IS the configured WD: skip it */
					}
					else
					{
						/* a directory's own EXISTENCE, not its mtime --
						 * same reasoning as the symlink case just below,
						 * and for the identical class of false positive:
						 * a directory's mtime bumps every time ANYTHING
						 * inside it is added or removed, so an mtime-based
						 * entry here would flag a heavily-shared parent
						 * (share/man/man1, say) as "changed" on every
						 * single unrelated package's own install, the
						 * moment it drops one more file into that same
						 * directory. a constant marker means a directory
						 * only ever registers as "changed" the ONE time
						 * it's a genuinely new key (didn't exist in the
						 * "before" snapshot at all) -- exactly "this
						 * install created this directory for the first
						 * time," never "something else also living here
						 * changed." real motivating gap: an install's own
						 * empty leftover directory (nothing else under it
						 * ever created a FILE there, so nothing ever
						 * implicitly captured it) silently outlived
						 * "mp remove" forever, with zero tracking of it
						 * ever having existed at all. */
						fprintf(out, "%s\tdir\n", nrel);
						walktree(base, nrel, out, basedev, has_exclude, excldev, exclino);
					}
				}
				else if(S_ISLNK(st.st_mode))
				{
					/* symlink target, not mtime: installing any
					 * shared library via libtool runs libtool's
					 * "--finish" step, which recreates EVERY
					 * versioned .so symlink already in that libdir
					 * (not just its own) as a normal side effect --
					 * same target, fresh mtime. mtime-based diffing
					 * flags that harmless recreation as a changed
					 * file, producing a false "conflicts with"
					 * against whichever package actually owns the
					 * symlink. a symlink's content is its target, so
					 * compare that instead; falls back to mtime only
					 * if readlink itself fails (e.g. a race), rather
					 * than silently dropping the entry. */
					char target[MAXPATH];
					ssize_t tn = readlink(full, target, sizeof(target) - 1);
 
					if(tn < 0)
					{
						fprintf(out, "%s\t%ld.%09ld\n", nrel,
						    (long)st.st_mtim.tv_sec, (long)st.st_mtim.tv_nsec);
					}
					else
					{
						target[tn] = '\0';
						fprintf(out, "%s\tlink:%s\n", nrel, target);
					}
				}
				else
				{
					fprintf(out, "%s\t%ld.%09ld\n", nrel,
					    (long)st.st_mtim.tv_sec, (long)st.st_mtim.tv_nsec);
				}
			}
		}
		e = readdir(d);
	}
	closedir(d);
}
powered by btf.