linkup

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

commit a286b18428747eafbb6923b86fe5f25c3f70f7bf
Author: Louis Burda <quent.burda@gmail.com>
Date:   Thu, 20 Oct 2022 01:16:22 +0200

Initial prototype

Diffstat:
A.gitignore | 1+
AMakefile | 14++++++++++++++
Alinkup.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 168 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +linkup diff --git a/Makefile b/Makefile @@ -0,0 +1,14 @@ +all: linkup + +clean: + rm -f linkup + +linkup: linkup.c + +install: linkup + install -m755 linkup -T "$(DESTDIR)$(PREFIX)$(BINDIR)/linkup" + +uninstall: + rm -f "$(DESTDIR)$(PREFIX)$(BINDIR)/linkup" + +.PHONY: all clean install uninstall diff --git a/linkup.c b/linkup.c @@ -0,0 +1,153 @@ +#include <linux/limits.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <unistd.h> +#include <err.h> +#include <errno.h> +#include <string.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> + +static const char usage[] = "linkup [-h] [-x FILE..] SOURCE DEST"; + +static bool force; + +bool +exists(int dirfd, const char *path) +{ + struct stat attr; + + return !fstatat(dirfd, path, &attr, AT_SYMLINK_NOFOLLOW); +} + +void +removeall(int pfd, const char *path) +{ + 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"); + } + + 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))) + removeall(fd, ent->d_name); + closedir(dir); + } + + unlinkat(pfd, path, AT_REMOVEDIR); +} + +int +main(int argc, const char **argv) +{ + char spbuf[PATH_MAX], dpbuf[PATH_MAX]; + char spath[PATH_MAX], dpath[PATH_MAX]; + struct dirent *ent; + struct stat attr; + const char *sarg, *darg; + const char **arg; + DIR *sdir, *ddir; + const char **ignored; + int ignorecnt; + int ignorecap; + int i; + + ignored = NULL; + ignorecap = 0; + ignorecnt = 0; + + force = false; + sarg = darg = NULL; + for (arg = &argv[1]; *arg; arg++) { + if (!strcmp(*arg, "-x")) { + if (ignorecnt == ignorecap) { + ignorecap = (ignorecap + 1) * 2; + ignored = realloc(ignored, + ignorecap * sizeof(const char *)); + } + ignored[ignorecnt] = *arg; + ignorecnt++; + } else if (!strcmp(*arg, "-f")) { + force = true; + } else if (!sarg) { + sarg = *arg; + } else if (!darg) { + darg = *arg; + } else { + printf("Usage: %s\n", usage); + return 0; + } + } + + if (!sarg || !darg) { + fprintf(stderr, "Usage: %s\n", usage); + return 1; + } + + if (!realpath(sarg, spath)) + err(1, "realpath src"); + + if (!realpath(darg, dpath)) + err(1, "realpath dst"); + + sdir = opendir(spath); + if (!sdir) err(1, "opendir src"); + + ddir = opendir(dpath); + if (!ddir) err(1, "opendir dst"); + + while ((ent = readdir(sdir))) { + /* skip ignored files */ + for (i = 0; i < ignorecnt; i++) { + if (!strcmp(ignored[i], ent->d_name)) + break; + } + if (i != ignorecnt) continue; + + /* skip default directories */ + if (!strcmp(ent->d_name, ".")) + continue; + if (!strcmp(ent->d_name, "..")) + continue; + + /* 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); + } + removeall(dirfd(ddir), 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"); + } + + /* clean dangling symlinks */ + 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; + if (fstatat(dirfd(ddir), ent->d_name, &attr, 0) < 0) + unlinkat(dirfd(ddir), ent->d_name, 0); + } + + free(ignored); +}