| git.druid.rocks | index | druid520 | mp | src/ | 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);
}