ln.c (4518B)
- // 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 _POSIX_C_SOURCE 200809L
- #include "../lib/bitmasks.h"
- // NetBSD (9.3 and 10) hides symlink behind _XOPEN_SOURCE / _NETBSD_SOURCE
- #ifdef __NetBSD__
- #define _XOPEN_SOURCE 700
- #endif
- #include <assert.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>
- #include <unistd.h> // getopt, symlink, link
- static bool opt_s = false, force = false;
- static int link_flags = 0;
- static int open_dir_flags = O_RDONLY | O_DIRECTORY;
- static int
- do_link(char *src, char *dest)
- {
- assert(errno == 0);
- 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;
- }
- }
- // Fallback
- assert(errno == EEXIST);
- errno = 0;
- int dirfd = open(dest, open_dir_flags);
- if(dirfd < 0)
- {
- assert(errno != 0);
- // ENOTDIR: Found but not a directory
- // ELOOP: POSIX return code when O_NOFOLLOW encounters a symbolic link
- // EMLINK: Same as ELOOP but FreeBSD *sigh*
- if(errno == ENOTDIR || errno == ELOOP || errno == EMLINK)
- {
- if(!force)
- {
- fprintf(stderr, "ln: error: Destination '%s' already exists\n", dest);
- return -1;
- }
- errno = 0;
- dirfd = AT_FDCWD;
- if(unlink(dest) < 0)
- {
- fprintf(stderr,
- "ln: error: Failed removing already existing destination '%s': %s\n",
- dest,
- strerror(errno));
- return -1;
- }
- }
- }
- if(errno != 0)
- {
- fprintf(stderr,
- "ln: error: Failed opening destination as directory '%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(dirfd == AT_FDCWD) return 0;
- if(close(dirfd) != 0)
- {
- fprintf(stderr, "ln: error: Failed closing directory '%s': %s\n", dest, strerror(errno));
- return -1;
- }
- return 0;
- }
- static void
- usage(void)
- {
- fprintf(stderr, "\
- Usage: ln [-fv] [-L|-P] source... target\n\
- ln -s [-fv] reference... target\n\
- ");
- }
- int
- main(int argc, char *argv[])
- {
- bool verbose = false;
- int c = -1;
- while((c = getopt(argc, argv, ":fnsLPv")) != -1)
- {
- switch(c)
- {
- case 'f':
- force = true;
- break;
- case 'n':
- FIELD_SET(open_dir_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 '?':
- fprintf(stderr, "ln: error: Unknown option '-%c'\n", optopt);
- usage();
- break;
- }
- }
- assert(errno == 0);
- argc -= optind;
- argv += optind;
- if(argc <= 1)
- {
- fprintf(stderr, "ln: error: Not enough operands, %d given, expect >= 2\n", argc);
- return 1;
- }
- else if(argc == 2)
- {
- struct stat dest_status;
- int ret_stat = fstatat(AT_FDCWD, argv[1], &dest_status, AT_SYMLINK_NOFOLLOW);
- if(argc == 2 && (errno == ENOENT || (ret_stat == 0 && !S_ISDIR(dest_status.st_mode))))
- {
- errno = 0;
- int ret = do_link(argv[0], argv[1]);
- return ret < 0 ? 1 : 0;
- }
- errno = 0;
- }
- char *dest = argv[argc - 1];
- char target[PATH_MAX] = "";
- for(int i = 0; i < argc - 1; i++)
- {
- char *src = argv[i];
- char *src_basename = basename(src);
- if(snprintf(target, PATH_MAX, "%s/%s", dest, src_basename) < 0)
- {
- fprintf(stderr, "ln: error: Failed joining destination '%s' and target '%s'\n", dest, src);
- return 1;
- }
- if(do_link(src, target) < 0) return 1;
- if(verbose) printf("'%s' -> '%s'\n", src, dest);
- }
- return 0;
- }