linkup

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

commit e6d25d99f2fd2c1e13b06c7fa71784fa1bca93a4
parent 0c59ffc0c205b45ee88b8a0b1840b8831e51e6ae
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sun, 18 Jun 2023 04:17:21 +0200

Refactor for readability and fix warnings

Diffstat:
MMakefile | 2++
Mlinkup.c | 233+++++++++++++++++++++++++++++++++++++++++--------------------------------------
2 files changed, 123 insertions(+), 112 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,6 +1,8 @@ PREFIX ?= /usr/local BINDIR ?= /bin +CFLAGS = -Wunused-variable -Wunused-function -Wuninitialized -Wconversion + all: linkup clean: diff --git a/linkup.c b/linkup.c @@ -3,27 +3,37 @@ #include <fcntl.h> #include <dirent.h> #include <unistd.h> -#include <err.h> #include <errno.h> #include <string.h> +#include <stdarg.h> #include <stdbool.h> -#include <stdlib.h> #include <stdio.h> +#include <stdlib.h> -static const char usage[] = "linkup [-h] [-x FILE..] SOURCE DEST"; - -static bool force; +static bool force = true; +static bool install = true; -static const char **ignored; -static int ignorecnt; -static int ignorecap; +static const char **ignored = NULL; +static size_t ignorecnt = 0; +static size_t ignorecap = 0; -bool -exists(int dirfd, const char *path) +static void +die(const char *fmt, ...) { - struct stat attr; + va_list ap; + + va_start(ap, fmt); + fputs("linkup: ", stderr); + vfprintf(stderr, fmt, ap); + if (*fmt && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + va_end(ap); - return !fstatat(dirfd, path, &attr, AT_SYMLINK_NOFOLLOW); + exit(1); } void @@ -31,40 +41,41 @@ normalize(const char *src, char *dst) { const char *sep, *tok; char *end; - int len; + size_t len; end = dst; - tok = src; *end = '\0'; - do { + + tok = src; + while (tok) { 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'; + len = sep ? (size_t) (sep - tok) : strlen(tok); + if (len && strncmp(tok, ".", len)) { + 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; - } while (sep); + tok = sep ? sep + 1 : NULL; + } } -void + +static void realparent(int dirfd, const char *dirpath, const char *file, char *parent) { - char tmpbuf[PATH_MAX+1]; + char tmpbuf[PATH_MAX]; ssize_t len; char *sep; - if ((len = readlinkat(dirfd, file, tmpbuf, PATH_MAX)) == -1) - err(1, "readlink"); + if ((len = readlinkat(dirfd, file, tmpbuf, PATH_MAX-1)) == -1) + die("readlink '%s/%s':", dirpath, file); tmpbuf[len] = '\0'; if (tmpbuf[0] != '/') { /* relative path */ @@ -77,147 +88,145 @@ realparent(int dirfd, const char *dirpath, const char *file, char *parent) *sep = '\0'; } -void -do_install(const char *spath, const char *dpath) +static void +ignore(const char *arg) { - char spbuf[PATH_MAX], dpbuf[PATH_MAX]; + if (ignorecnt == ignorecap) { + ignorecap = (ignorecap + 1) * 2; + ignored = realloc(ignored, ignorecap * sizeof(const char *)); + } + ignored[ignorecnt] = arg; + ignorecnt++; +} + +static void +do_install(const char *src_path, const char *dst_path) +{ + char src_fpath[PATH_MAX], dst_fpath[PATH_MAX]; struct dirent *ent; struct stat attr; - DIR *sdir, *ddir; - char *sep; + DIR *src_dir, *dst_dir; int i; - sdir = opendir(spath); - if (!sdir) err(1, "opendir src"); + src_dir = opendir(src_path); + if (!src_dir) die("opendir '%s'", src_path); - ddir = opendir(dpath); - if (!ddir) err(1, "opendir dst"); + dst_dir = opendir(dst_path); + if (!dst_dir) die("opendir '%s'", dst_path); + + while ((ent = readdir(src_dir))) { + if (!strcmp(ent->d_name, ".")) + continue; + if (!strcmp(ent->d_name, "..")) + continue; - 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; - - snprintf(spbuf, PATH_MAX, "%s/%s", spath, ent->d_name); - snprintf(dpbuf, PATH_MAX, "%s/%s", dpath, ent->d_name); + snprintf(src_fpath, PATH_MAX, "%s/%s", src_path, ent->d_name); + snprintf(dst_fpath, PATH_MAX, "%s/%s", dst_path, ent->d_name); /* ensure link destination does not exist */ - if (!fstatat(dirfd(ddir), ent->d_name, + if (!fstatat(dirfd(dst_dir), ent->d_name, &attr, AT_SYMLINK_NOFOLLOW)) { if ((attr.st_mode & S_IFMT) != S_IFLNK && !force) - errx(1, "dst %s already exists", ent->d_name); - if (remove(dpbuf)) - err(1, "remove dst %s", ent->d_name); + die("dst '%s' already exists", dst_fpath); + if (remove(dst_fpath)) + die("remove '%s' failed:", dst_path); } - /* create link */ - if (symlink(spbuf, dpbuf) < 0) - err(1, "link"); + if (symlink(src_fpath, dst_fpath) < 0) + die("linking '%s' failed:", dst_fpath); } - /* clean dangling symlinks */ - while ((ent = readdir(ddir))) { + /* cleanup dangling symlinks */ + while ((ent = readdir(dst_dir))) { if (ent->d_type != DT_LNK) continue; /* must point into src */ - realparent(dirfd(ddir), dpath, ent->d_name, dpbuf); - if (strcmp(dpbuf, spath)) + realparent(dirfd(dst_dir), dst_path, ent->d_name, dst_fpath); + if (strcmp(dst_fpath, src_path)) continue; /* with missing destination */ - if (fstatat(dirfd(ddir), ent->d_name, &attr, 0) < 0) - unlinkat(dirfd(ddir), ent->d_name, 0); + if (faccessat(dirfd(dst_dir), ent->d_name, F_OK, 0)) + unlinkat(dirfd(dst_dir), ent->d_name, 0); } - closedir(ddir); - closedir(sdir); + closedir(dst_dir); + closedir(src_dir); } -void -do_uninstall(const char *spath, const char *dpath) +static void +do_uninstall(const char *src_path, const char *dst_path) { struct dirent *ent; - char dpbuf[PATH_MAX]; - char *sep; - DIR *ddir; + char dst_fpath[PATH_MAX]; + DIR *dst_dir; - ddir = opendir(dpath); - if (!ddir) err(1, "opendir dst"); + dst_dir = opendir(dst_path); + if (!dst_dir) die("opendir '%s':", dst_path); - while ((ent = readdir(ddir))) { + while ((ent = readdir(dst_dir))) { if (ent->d_type != DT_LNK) continue; - realparent(dirfd(ddir), dpath, ent->d_name, dpbuf); - if (!strcmp(dpbuf, spath)) - unlinkat(dirfd(ddir), ent->d_name, 0); + /* must point into src */ + realparent(dirfd(dst_dir), dst_path, ent->d_name, dst_fpath); + if (strcmp(dst_fpath, src_path)) + continue; + + if (unlinkat(dirfd(dst_dir), ent->d_name, 0)) + die("failed to remove '%s/%s':", dst_path, ent->d_name); } - closedir(ddir); + closedir(dst_dir); } int main(int argc, const char **argv) { - char spath[PATH_MAX], dpath[PATH_MAX]; - const char *sarg, *darg; + char src_path[PATH_MAX], dst_path[PATH_MAX]; + const char *src_arg, *dst_arg; const char **arg; - bool install; - int i; - - ignored = NULL; - ignorecap = 0; - ignorecnt = 0; - force = false; - install = true; - sarg = darg = NULL; + src_arg = dst_arg = 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++; + ignore(*arg); } else if (!strcmp(*arg, "-u")) { install = false; } else if (!strcmp(*arg, "-f")) { force = true; - } else if (!sarg) { - sarg = *arg; - } else if (!darg) { - darg = *arg; + } else if (!src_arg) { + src_arg = *arg; + } else if (!dst_arg) { + dst_arg = *arg; } else { - printf("Usage: %s\n", usage); - return 0; + break; } } - if (!sarg || !darg) { - fprintf(stderr, "Usage: %s\n", usage); + if (!src_arg || !dst_arg || *arg) { + fprintf(stderr, "Usage: linkup [-h] [-x FILE..] SRC DST\n"); return 1; } - if (!realpath(sarg, spath)) - err(1, "realpath src"); + if (!realpath(src_arg, src_path)) + die("realpath '%s':", src_path); - if (!realpath(darg, dpath)) - err(1, "realpath dst"); + if (!realpath(dst_arg, dst_path)) + die("realpath '%s':", dst_path); - if (install) do_install(spath, dpath); - else do_uninstall(spath, dpath); + if (install) { + do_install(src_path, dst_path); + } else { + do_uninstall(src_path, dst_path); + } free(ignored); }