#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <libgen.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>
static const char *progname = NULL;
static void
usage(const char *fmt, ...)
{
if (fmt != NULL) {
va_list ap;
va_start(ap, fmt);
vwarnx(fmt, ap);
va_end(ap);
}
(void) fprintf(stderr,
"Usage: %s [opts] -d <destdir> <input(s)>\n\n"
"Options:\n"
"\t-n\tdry run\n",
progname);
}
typedef struct manlink_iter {
FILE *mi_fp;
size_t mi_cap;
char *mi_line;
char *mi_tok_saveptr;
char *mi_target;
} manlink_iter_t;
typedef struct manlink_iter_result {
const char *mir_name;
const char *mir_target;
} manlink_iter_res_t;
static bool
valid_name(const char *name)
{
for (char c; (c = *name) != '\0'; name++) {
if (c == '/') {
return (false);
}
if (c == '#') {
return (false);
}
if (!isalnum(c) && !ispunct(c)) {
return (false);
}
}
return (true);
}
static bool
valid_target(const char *target)
{
for (char c; (c = *target) != '\0'; target++) {
if (isalnum(c)) {
continue;
}
switch (c) {
case '.':
case '_':
case '-':
case '/':
break;
default:
return (false);
}
}
return (true);
}
static void
link_iter_init(FILE *ifp, manlink_iter_t *itr)
{
itr->mi_fp = ifp;
itr->mi_cap = 0;
itr->mi_line = NULL;
itr->mi_target = NULL;
}
static void
link_iter_fini(manlink_iter_t *itr)
{
(void) fclose(itr->mi_fp);
free(itr->mi_line);
free(itr->mi_target);
}
static bool
link_iter_next(manlink_iter_t *itr, const char **namep, const char **targetp)
{
ssize_t len;
while ((len = getline(&itr->mi_line, &itr->mi_cap, itr->mi_fp)) >= 1) {
char *line = itr->mi_line;
if (line[len - 1] == '\n') {
line[len - 1] = '\0';
}
if (*line == '\0' || *line == '#') {
continue;
} else if (*line == '\t') {
const char *name = line + 1;
if (!valid_name(name)) {
err(EXIT_FAILURE,
"Invalid link name: \"%s\"", name);
} else if (itr->mi_target == NULL) {
err(EXIT_FAILURE,
"Link without preceding target");
} else {
*namep = name;
*targetp = itr->mi_target;
return (true);
}
} else {
if (!valid_target(line)) {
errx(EXIT_FAILURE,
"Invalid link target \"%s\"", line);
} else {
free(itr->mi_target);
itr->mi_target = strdup(line);
continue;
}
}
}
return (false);
}
static void
do_links(const char *dest_dir, const char *input_file, bool dry_run)
{
int dfd = open(dest_dir, O_DIRECTORY | O_RDONLY, 0);
if (dfd < 0) {
err(EXIT_FAILURE, "Could not open destination dir %s",
dest_dir);
}
FILE *ifp = fopen(input_file, "r");
if (ifp == NULL) {
err(EXIT_FAILURE, "Could not open input file %s", input_file);
}
manlink_iter_t iter;
link_iter_init(ifp, &iter);
const char *name, *target;
while (link_iter_next(&iter, &name, &target)) {
struct stat st;
const int res = fstatat(dfd, name, &st, AT_SYMLINK_NOFOLLOW);
if (res == 0) {
if (S_ISLNK(st.st_mode)) {
char buf[MAXPATHLEN];
buf[0] = '\0';
ssize_t len = readlinkat(dfd, name, buf,
sizeof (buf));
if (len > 0) {
buf[MIN(len, sizeof (buf) - 1)] = '\0';
}
if (strncmp(buf, target, sizeof (buf)) == 0) {
continue;
}
}
(void) printf("unlink %s/%s\n", dest_dir, name);
if (!dry_run && unlinkat(dfd, name, 0) != 0) {
err(EXIT_FAILURE,
"Could not unlink conflicting file %s/%s",
dest_dir, name);
}
} else if (errno != ENOENT) {
err(EXIT_FAILURE, "stat() failure for link %s/%s",
dest_dir, name);
}
(void) printf("link %s/%s -> %s\n", dest_dir, name, target);
if (!dry_run && symlinkat(target, dfd, name) != 0) {
err(EXIT_FAILURE, "failure to create link at %s/%s",
dest_dir, name);
}
}
link_iter_fini(&iter);
}
int
main(int argc, char *argv[])
{
char *dest_dir = NULL;
bool do_dry_run = false;
progname = basename(argv[0]);
int c;
while ((c = getopt(argc, argv, "nd:")) != -1) {
switch (c) {
case 'd':
dest_dir = optarg;
break;
case 'n':
do_dry_run = true;
break;
case '?':
usage("unknown option: -%c", optopt);
exit(EXIT_FAILURE);
}
}
argc -= optind;
argv += optind;
if (argc < 1) {
usage("input file(s)");
exit(EXIT_FAILURE);
}
for (uint_t i = 0; i < (uint_t)argc; i++) {
do_links(dest_dir, argv[i], do_dry_run);
}
return (EXIT_SUCCESS);
}