ln.c (5727B)
- // utils-std: Collection of commonly available Unix tools
- // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- #define _GNU_SOURCE // O_PATH with glibc
- #define _DEFAULT_SOURCE // due to O_PATH
- // Don't define _POSIX_C_SOURCE otherwise FreeBSD hides O_PATH
- #include "../lib/bitmasks.h"
- #include "../libutils/getopt_nolong.h"
- #include <errno.h>
- #include <fcntl.h> // linkat, AT_SYMLINK_FOLLOW
- #include <libgen.h> // basename
- #include <limits.h> // PATH_MAX
- #include <stdbool.h>
- #include <stdio.h> // fprintf
- #include <string.h> // strerror
- #include <sys/stat.h> // fstat
- #include <unistd.h> // getopt, symlink, link
- const char *argv0 = "ln";
- static bool opt_s = false, force = false;
- static int link_flags = 0;
- static int open_target_flags = O_RDONLY | O_PATH;
- static int open_dest_flags = O_RDONLY | O_PATH | O_NOFOLLOW;
- static struct stat dest_stat;
- static int
- do_link(char *src, char *dest, int destfd)
- {
- if(opt_s)
- {
- if(symlinkat(src, AT_FDCWD, dest) == 0) return 0;
- if(errno != EEXIST)
- {
- fprintf(stderr, "ln: error: Failed creating symlink '%s': %s\n", dest, strerror(errno));
- return -1;
- }
- }
- else
- {
- if(linkat(AT_FDCWD, src, AT_FDCWD, dest, link_flags) == 0) return 0;
- if(errno != EEXIST)
- {
- fprintf(stderr,
- "ln: error: Failed creating hard link from '%s' to '%s': %s\n",
- src,
- dest,
- strerror(errno));
- return -1;
- }
- }
- if(strcmp(src, dest) == 0)
- {
- fprintf(stderr, "ln: error: Path '%s' passed to both source and destination\n", src);
- return 1;
- }
- errno = 0;
- if(!force)
- {
- fprintf(stderr, "ln: error: Destination '%s' already exists\n", dest);
- return -1;
- }
- if(destfd < 0)
- {
- destfd = open(dest, open_dest_flags);
- if(destfd < 0)
- {
- if(errno != ENOENT)
- {
- fprintf(stderr,
- "ln: error: Failed opening destination as path '%s': %s\n",
- dest,
- strerror(errno));
- return -1;
- }
- }
- else if(fstat(destfd, &dest_stat) < 0)
- {
- fprintf(stderr,
- "ln: error: Failed getting status for destination '%s': %s\n",
- dest,
- strerror(errno));
- return -1;
- }
- }
- int dirfd = AT_FDCWD;
- if(S_ISDIR(dest_stat.st_mode))
- {
- dirfd = destfd;
- }
- else
- {
- /* check if symbolic/hard link refers to same file */
- struct stat src_stat;
- if(stat(src, &src_stat) < 0)
- {
- if(errno != ENOENT)
- {
- fprintf(
- stderr, "ln: error: Failed getting status for source '%s': %s\n", src, strerror(errno));
- return -1;
- }
- }
- else if(src_stat.st_dev == dest_stat.st_dev && src_stat.st_ino == dest_stat.st_ino)
- {
- fprintf(stderr,
- "ln: error: Source '%s' and destination '%s' refer to the same file\n",
- src,
- dest);
- return -1;
- }
- if(unlink(dest) < 0)
- {
- fprintf(stderr, "ln: error: Failed unlinking destination '%s': %s\n", dest, strerror(errno));
- return -1;
- }
- }
- if(opt_s)
- {
- if(symlinkat(src, dirfd, dest) == 0) goto cleanup;
- fprintf(stderr, "ln: error: Failed creating symlink '%s': %s\n", dest, strerror(errno));
- return -1;
- }
- else
- {
- if(linkat(AT_FDCWD, src, dirfd, dest, link_flags) == 0) goto cleanup;
- fprintf(stderr,
- "ln: error: Failed creating hard link from '%s' to '%s': %s\n",
- src,
- dest,
- strerror(errno));
- return -1;
- }
- cleanup:
- if(destfd < 0) return 0;
- if(close(destfd) != 0)
- {
- fprintf(stderr,
- "ln: error: Failed closing destination directory '%s': %s\n",
- dest,
- strerror(errno));
- return -1;
- }
- return 0;
- }
- static void
- usage(void)
- {
- fprintf(stderr, "\
- Usage: ln [-fnv] [-L|-P] source... target\n\
- ln -s [-fnv] reference... target\n\
- ");
- }
- int
- main(int argc, char *argv[])
- {
- bool verbose = false;
- for(int c = -1; (c = getopt_nolong(argc, argv, ":fnsLPv")) != -1;)
- {
- switch(c)
- {
- case 'f':
- force = true;
- break;
- case 'n':
- FIELD_SET(open_target_flags, O_NOFOLLOW);
- break;
- case 's':
- opt_s = true;
- break;
- case 'L':
- FIELD_SET(link_flags, AT_SYMLINK_FOLLOW);
- break;
- case 'P':
- FIELD_CLR(link_flags, AT_SYMLINK_FOLLOW);
- break;
- case 'v':
- verbose = true;
- break;
- case '?':
- GETOPT_UNKNOWN_OPT
- usage();
- return 1;
- }
- }
- argc -= optind;
- argv += optind;
- char *target = argv[argc - 1];
- char dest[PATH_MAX] = "";
- if(argc <= 0)
- {
- fprintf(stderr, "ln: error: Not enough operands, %d given, expect >= 1\n", argc);
- return 1;
- }
- else if(argc == 1)
- {
- target = (char *)".";
- argc++;
- }
- else if(argc == 2)
- {
- int targetfd = open(target, open_target_flags);
- if(targetfd >= 0)
- {
- if(fstat(targetfd, &dest_stat) < 0)
- {
- fprintf(stderr,
- "ln: error: Failed getting status for target '%s': %s\n",
- dest,
- strerror(errno));
- return -1;
- }
- if(!S_ISDIR(dest_stat.st_mode))
- {
- int ret = do_link(argv[0], argv[1], targetfd);
- return ret < 0 ? 1 : 0;
- }
- if(close(targetfd) < 0)
- {
- fprintf(stderr,
- "ln: error: Failed closing target directory '%s': %s\n",
- target,
- strerror(errno));
- return -1;
- }
- }
- else
- {
- int ret = do_link(argv[0], argv[1], -1);
- return ret < 0 ? 1 : 0;
- }
- }
- for(int i = 0; i < argc - 1; i++)
- {
- char *src = argv[i];
- char *src_basename = basename(src);
- if(snprintf(dest, PATH_MAX, "%s/%s", target, src_basename) < 0)
- {
- fprintf(stderr,
- "ln: error: Failed joining target '%s' and source basename '%s'\n",
- target,
- src_basename);
- return 1;
- }
- if(do_link(src, dest, -1) < 0) return 1;
- if(verbose) printf("'%s' -> '%s'\n", src, dest);
- }
- return 0;
- }