/* * Copyright (c) 2013, 2018, 2020 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * ln.c * Create a hard or symbolic link. */ #include #include #include #include #include #include #include #include #include #include static bool lnat(const char* source, int source_dirfd, const char* source_basename, const char* target, int target_dirfd, const char* target_basename, bool force, bool symbolic, bool physical, bool no_dereference, bool no_target_directory, bool verbose) { for ( int attempt = 0; true; attempt++ ) { if ( (symbolic ? symlinkat(source, target_dirfd, target_basename) : linkat(source_dirfd, source_basename, target_dirfd, target_basename, physical ? 0 : AT_SYMLINK_FOLLOW)) < 0 ) { int error = errno; if ( !attempt && error == EEXIST && !no_target_directory ) { struct stat target_st; if ( !fstatat(target_dirfd, target_basename, &target_st, no_dereference ? AT_SYMLINK_NOFOLLOW : 0) && S_ISDIR(target_st.st_mode) ) { int new_target_dirfd = openat(target_dirfd, target_basename, O_RDONLY | O_DIRECTORY); if ( new_target_dirfd < 0 ) { warn("%s", target); return false; } char* new_target; if ( asprintf(&new_target, "%s/%s", target, source_basename) < 0 ) err(1, "malloc"); const char* new_target_basename = source_basename; bool result = lnat(source, source_dirfd, source_basename, new_target, new_target_dirfd, new_target_basename, force, symbolic, physical, no_dereference, no_target_directory, verbose); free(new_target); close(new_target_dirfd); return result; } } if ( !attempt && error == EEXIST && force ) { if ( !symbolic && !strcmp(source_basename, target_basename) ) { struct stat source_dirst, target_dirst; fstat(source_dirfd, &source_dirst); fstat(target_dirfd, &target_dirst); if ( source_dirst.st_dev == target_dirst.st_dev && source_dirst.st_ino == target_dirst.st_ino ) { warnx("'%s' and '%s' are the same file", source, target); return false; } } if ( unlinkat(target_dirfd, target_basename, 0) < 0 ) { warn("unlink: %s", target); return false; } continue; } errno = error; warn("%s: %s -> %s", symbolic ? "symlink" : "link", source, target); return false; } if ( verbose ) printf("`%s' => `%s'\n", source, target); return true; } } // Retains the trailing slashes unlike basename(3) so ln foo/ bar/ fails. static const char* basename_with_slashes(const char* path) { size_t offset = strlen(path); while ( offset && path[offset - 1] == '/' ) offset--; while ( offset && path[offset - 1] != '/' ) offset--; return path + offset; } static bool ln(const char* source, const char* target, bool force, bool symbolic, bool physical, bool no_dereference, bool no_target_directory, bool verbose) { char* source_dup = strdup(source); if ( !source_dup ) err(1, "malloc"); const char* source_basename = basename_with_slashes(source); char* source_dirname = dirname(source_dup); int source_dirfd = symbolic ? AT_FDCWD : open(source_dirname, O_RDONLY | O_DIRECTORY); if ( !symbolic && source_dirfd < 0 ) { warn("%s", source); free(source_dup); return false; } char* target_dup = strdup(target); if ( !target_dup ) err(1, "malloc"); const char* target_basename = basename_with_slashes(target); char* target_dirname = dirname(target_dup); int target_dirfd = open(target_dirname, O_RDONLY | O_DIRECTORY); if ( target_dirfd < 0 ) { warn("%s", target); if ( symbolic ) close(source_dirfd); free(source_dup); free(target_dup); return false; } bool result = lnat(source, source_dirfd, source_basename, target, target_dirfd, target_basename, force, symbolic, physical, no_dereference, no_target_directory, verbose); close(source_dirfd); close(target_dirfd); free(source_dup); free(target_dup); return result; } static bool ln_into_directory(const char* source, const char* target, bool force, bool symbolic, bool physical, bool no_dereference, bool verbose) { char* source_copy = strdup(source); if ( !source_copy ) err(1, "malloc"); const char* base_name = basename(source_copy); size_t source_length = strlen(source); bool has_slash = source_length && source[source_length - 1] == '/'; char* new_target; if ( asprintf(&new_target, "%s%s%s", target, has_slash ? "" : "/", base_name) < 0 ) err(1, "malloc"); free(source_copy); bool ret = ln(source, new_target, force, symbolic, physical, no_dereference, true, verbose); free(new_target); return ret; } static void compact_arguments(int* argc, char*** argv) { for ( int i = 0; i < *argc; i++ ) { while ( i < *argc && !(*argv)[i] ) { for ( int n = i; n < *argc; n++ ) (*argv)[n] = (*argv)[n+1]; (*argc)--; } } } int main(int argc, char* argv[]) { bool force = false; bool symbolic = false; bool physical = true; bool no_dereference = false; bool no_target_directory = false; bool verbose = false; for ( int i = 1; i < argc; i++ ) { const char* arg = argv[i]; if ( arg[0] != '-' || !arg[1] ) continue; argv[i] = NULL; if ( !strcmp(arg, "--") ) break; if ( arg[1] != '-' ) { char c; while ( (c = *++arg) ) switch ( c ) { case 'f': force = true; break; case 'h': no_dereference = true; break; case 'L': physical = false; break; case 'n': no_dereference = true; break; case 'P': physical = true; break; case 's': symbolic = true; break; case 'T': no_target_directory = true; break; case 'v': verbose = true; break; default: errx(1, "unknown option -- '%c'", c); } } else if ( !strcmp(arg, "--force") ) force = true; else if ( !strcmp(arg, "--logical") ) physical = false; else if ( !strcmp(arg, "--physical") ) physical = true; else if ( !strcmp(arg, "--symbolic") ) symbolic = true; else if ( !strcmp(arg, "--verbose") ) verbose = true; else errx(1, "unknown option: %s", arg); } compact_arguments(&argc, &argv); if ( no_target_directory && argc != 3 ) errx(1, "unexpected extra operand"); if ( argc == 2 ) return ln_into_directory(argv[1], ".", force, symbolic, physical, no_dereference, verbose) ? 0 : 1; if ( argc == 3 ) return ln(argv[1], argv[2], force, symbolic, physical, no_dereference, no_target_directory, verbose) ? 0 : 1; const char* target = argv[argc - 1]; bool success = true; for ( int i = 1; i < argc - 1; i++ ) { const char* source = argv[i]; if ( !ln_into_directory(source, target, force, symbolic, physical, no_dereference, verbose) ) success = false; } return success ? 0 : 1; }