linkup

Simple symlink farmer
git clone https://git.sinitax.com/sinitax/linkup
Log | Files | Refs | LICENSE | sfeed.txt

commit 02748101b3fb3a8a8aaaacc5c4e1e62dd0ea45fc
parent 47bfb593e8f3d808340954ffe5352d21b5f76307
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sat, 22 Oct 2022 15:11:52 +0200

Fix resolving of dangling symbolic links

Diffstat:
Mlinkup.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
1 file changed, 63 insertions(+), 31 deletions(-)

diff --git a/linkup.c b/linkup.c @@ -27,37 +27,66 @@ exists(int dirfd, const char *path) } void -remove_all(int pfd, const char *path) +normalize(const char *src, char *dst) { - struct dirent *ent; - struct stat attr; - DIR *dir; - int fd; - - if (fstatat(pfd, path, &attr, AT_SYMLINK_NOFOLLOW) < 0) { - if (errno == ENOENT) return; - err(1, "fstatat"); + const char *sep, *tok; + char *end; + int len; + + /* only for absolute paths */ + + end = dst; + tok = src; + *end = '\0'; + while (1) { + sep = strchr(tok, '/'); + len = sep ? sep - tok : strlen(tok); + if (!len || !strncmp(tok, ".", len)) + goto next; + if (!strncmp(tok, "..", len)) { + end = strrchr(dst, '/'); + if (!end) end = dst; + *end = '\0'; + } else { + *end++ = '/'; + strncpy(end, tok, len); + end += len; + *end = '\0'; + } +next: + tok = sep + 1; + if (!sep) break; } +} - if ((attr.st_mode & S_IFMT) == S_IFDIR) { - fd = openat(pfd, path, 0); - dir = fdopendir(fd); - if (!dir) err(1, "opendir"); - while ((ent = readdir(dir))) - remove_all(fd, ent->d_name); - closedir(dir); +void +realparent(int dirfd, const char *dirpath, const char *file, char *parent) +{ + char tmpbuf[PATH_MAX]; + char *sep; + + if (readlinkat(dirfd, file, tmpbuf, PATH_MAX) < 0) + err(1, "readlink"); + + if (tmpbuf[0] != '/') { /* relative path */ + strncpy(parent, tmpbuf, PATH_MAX); + snprintf(tmpbuf, PATH_MAX, "%s/%s", dirpath, parent); } - unlinkat(pfd, path, AT_REMOVEDIR); + normalize(tmpbuf, parent); + if ((sep = strrchr(parent, '/'))) + *sep = '\0'; } void do_install(const char *spath, const char *dpath) { char spbuf[PATH_MAX], dpbuf[PATH_MAX]; + char tmpbuf[PATH_MAX]; struct dirent *ent; struct stat attr; DIR *sdir, *ddir; + char *sep; int i; sdir = opendir(spath); @@ -80,17 +109,19 @@ do_install(const char *spath, const char *dpath) if (!strcmp(ent->d_name, "..")) continue; + snprintf(spbuf, PATH_MAX, "%s/%s", spath, ent->d_name); + snprintf(dpbuf, PATH_MAX, "%s/%s", dpath, ent->d_name); + /* ensure link destination does not exist */ if (!fstatat(dirfd(ddir), ent->d_name, &attr, AT_SYMLINK_NOFOLLOW)) { if ((attr.st_mode & S_IFMT) != S_IFLNK && !force) errx(1, "dst %s already exists\n", ent->d_name); - remove_all(dirfd(ddir), ent->d_name); + if (remove(dpbuf)) + err(1, "remove dst %s", ent->d_name); } /* create link */ - snprintf(spbuf, PATH_MAX, "%s/%s", spath, ent->d_name); - snprintf(dpbuf, PATH_MAX, "%s/%s", dpath, ent->d_name); if (symlink(spbuf, dpbuf) < 0) err(1, "link"); } @@ -99,12 +130,13 @@ do_install(const char *spath, const char *dpath) while ((ent = readdir(ddir))) { if (ent->d_type != DT_LNK) continue; - snprintf(dpbuf, PATH_MAX, "%s/%s", dpath, ent->d_name); - if (readlink(dpbuf, dpbuf, PATH_MAX) < 0) - err(1, "readlink"); - realpath(dpbuf, dpbuf); - if (strncmp(dpbuf, spath, strlen(spath))) - break; + + /* must point into src */ + realparent(dirfd(ddir), dpath, ent->d_name, dpbuf); + if (strcmp(dpbuf, spath)) + continue; + + /* with missing destination */ if (fstatat(dirfd(ddir), ent->d_name, &attr, 0) < 0) unlinkat(dirfd(ddir), ent->d_name, 0); } @@ -118,6 +150,7 @@ do_uninstall(const char *spath, const char *dpath) { struct dirent *ent; char dpbuf[PATH_MAX]; + char *sep; DIR *ddir; ddir = opendir(dpath); @@ -126,11 +159,10 @@ do_uninstall(const char *spath, const char *dpath) while ((ent = readdir(ddir))) { if (ent->d_type != DT_LNK) continue; - snprintf(dpbuf, PATH_MAX, "%s/%s", dpath, ent->d_name); - if (readlink(dpbuf, dpbuf, PATH_MAX) < 0) - err(1, "readlink"); - realpath(dpbuf, dpbuf); - if (!strncmp(dpbuf, spath, strlen(spath))) + + realparent(dirfd(ddir), dpath, ent->d_name, dpbuf); + + if (!strcmp(dpbuf, spath)) unlinkat(dirfd(ddir), ent->d_name, 0); }