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:
M | linkup.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);
}