diff options
Diffstat (limited to 'bin/ln')
-rw-r--r-- | bin/ln/Makefile | 3 | ||||
-rw-r--r-- | bin/ln/Makefile.depend | 2 | ||||
-rw-r--r-- | bin/ln/ln.1 | 3 | ||||
-rw-r--r-- | bin/ln/ln.c | 154 | ||||
-rw-r--r-- | bin/ln/symlink.7 | 6 | ||||
-rw-r--r-- | bin/ln/tests/Makefile | 2 | ||||
-rw-r--r-- | bin/ln/tests/Makefile.depend | 1 | ||||
-rw-r--r-- | bin/ln/tests/ln_test.sh | 155 |
8 files changed, 191 insertions, 135 deletions
diff --git a/bin/ln/Makefile b/bin/ln/Makefile index dfabafa09a5c..0220ac3ba459 100644 --- a/bin/ln/Makefile +++ b/bin/ln/Makefile @@ -1,6 +1,3 @@ -# @(#)Makefile 8.2 (Berkeley) 5/31/93 -# $FreeBSD$ - .include <src.opts.mk> PACKAGE=runtime diff --git a/bin/ln/Makefile.depend b/bin/ln/Makefile.depend index 6cfaab1c3644..6ef78fac5cbf 100644 --- a/bin/ln/Makefile.depend +++ b/bin/ln/Makefile.depend @@ -1,8 +1,6 @@ -# $FreeBSD$ # Autogenerated - do NOT edit! DIRDEPS = \ - gnu/lib/csu \ include \ include/xlocale \ lib/${CSU_DIR} \ diff --git a/bin/ln/ln.1 b/bin/ln/ln.1 index 9f8819e0f95a..43522e0c1cb8 100644 --- a/bin/ln/ln.1 +++ b/bin/ln/ln.1 @@ -29,9 +29,6 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" @(#)ln.1 8.2 (Berkeley) 12/30/93 -.\" $FreeBSD$ -.\" .Dd June 12, 2017 .Dt LN 1 .Os diff --git a/bin/ln/ln.c b/bin/ln/ln.c index e37dee361b19..3055c7563cca 100644 --- a/bin/ln/ln.c +++ b/bin/ln/ln.c @@ -29,20 +29,6 @@ * SUCH DAMAGE. */ -#if 0 -#ifndef lint -static char const copyright[] = -"@(#) Copyright (c) 1987, 1993, 1994\n\ - The Regents of the University of California. All rights reserved.\n"; -#endif /* not lint */ - -#ifndef lint -static char sccsid[] = "@(#)ln.c 8.2 (Berkeley) 3/31/94"; -#endif /* not lint */ -#endif -#include <sys/cdefs.h> -__FBSDID("$FreeBSD$"); - #include <sys/param.h> #include <sys/stat.h> @@ -51,30 +37,32 @@ __FBSDID("$FreeBSD$"); #include <fcntl.h> #include <libgen.h> #include <limits.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> -static int fflag; /* Unlink existing files. */ -static int Fflag; /* Remove empty directories also. */ -static int hflag; /* Check new name for symlink first. */ -static int iflag; /* Interactive mode. */ -static int Pflag; /* Create hard links to symlinks. */ -static int sflag; /* Symbolic, not hard, link. */ -static int vflag; /* Verbose output. */ -static int wflag; /* Warn if symlink target does not +static bool fflag; /* Unlink existing files. */ +static bool Fflag; /* Remove empty directories also. */ +static bool hflag; /* Check new name for symlink first. */ +static bool iflag; /* Interactive mode. */ +static bool Pflag; /* Create hard links to symlinks. */ +static bool sflag; /* Symbolic, not hard, link. */ +static bool vflag; /* Verbose output. */ +static bool wflag; /* Warn if symlink target does not * exist, and -f is not enabled. */ static char linkch; -static int linkit(const char *, const char *, int); -static void usage(void); +static int linkit(const char *, const char *, bool); +static void link_usage(void) __dead2; +static void usage(void) __dead2; int main(int argc, char *argv[]) { struct stat sb; - char *p, *targetdir; + char *targetdir; int ch, exitval; /* @@ -82,52 +70,55 @@ main(int argc, char *argv[]) * "link", for which the functionality provided is greatly * simplified. */ - if ((p = strrchr(argv[0], '/')) == NULL) - p = argv[0]; - else - ++p; - if (strcmp(p, "link") == 0) { + if (strcmp(getprogname(), "link") == 0) { while (getopt(argc, argv, "") != -1) - usage(); + link_usage(); argc -= optind; argv += optind; if (argc != 2) - usage(); - exit(linkit(argv[0], argv[1], 0)); + link_usage(); + if (lstat(argv[1], &sb) == 0) + errc(1, EEXIST, "%s", argv[1]); + /* + * We could simply call link(2) here, but linkit() + * performs additional checks and gives better + * diagnostics. + */ + exit(linkit(argv[0], argv[1], false)); } while ((ch = getopt(argc, argv, "FLPfhinsvw")) != -1) switch (ch) { case 'F': - Fflag = 1; + Fflag = true; break; case 'L': - Pflag = 0; + Pflag = false; break; case 'P': - Pflag = 1; + Pflag = true; break; case 'f': - fflag = 1; - iflag = 0; - wflag = 0; + fflag = true; + iflag = false; + wflag = false; break; case 'h': case 'n': - hflag = 1; + hflag = true; break; case 'i': - iflag = 1; - fflag = 0; + iflag = true; + fflag = false; break; case 's': - sflag = 1; + sflag = true; break; case 'v': - vflag = 1; + vflag = true; break; case 'w': - wflag = 1; + wflag = true; break; case '?': default: @@ -138,21 +129,21 @@ main(int argc, char *argv[]) argc -= optind; linkch = sflag ? '-' : '='; - if (sflag == 0) - Fflag = 0; - if (Fflag == 1 && iflag == 0) { - fflag = 1; - wflag = 0; /* Implied when fflag != 0 */ + if (!sflag) + Fflag = false; + if (Fflag && !iflag) { + fflag = true; + wflag = false; /* Implied when fflag is true */ } - switch(argc) { + switch (argc) { case 0: usage(); /* NOTREACHED */ case 1: /* ln source */ - exit(linkit(argv[0], ".", 1)); + exit(linkit(argv[0], ".", true)); case 2: /* ln source target */ - exit(linkit(argv[0], argv[1], 0)); + exit(linkit(argv[0], argv[1], false)); default: ; } @@ -171,7 +162,7 @@ main(int argc, char *argv[]) if (!S_ISDIR(sb.st_mode)) usage(); for (exitval = 0; *argv != targetdir; ++argv) - exitval |= linkit(*argv, targetdir, 1); + exitval |= linkit(*argv, targetdir, true); exit(exitval); } @@ -221,15 +212,23 @@ samedirent(const char *path1, const char *path2) return sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino; } +/* + * Create a link to source. If target is a directory (and some additional + * conditions apply, see comments within) the link will be created within + * target and have the basename of source. Otherwise, the link will be + * named target. If isdir is true, target has already been determined to + * be a directory; otherwise, we will check, if needed. + */ static int -linkit(const char *source, const char *target, int isdir) +linkit(const char *source, const char *target, bool isdir) { - struct stat sb; - const char *p; - int ch, exists, first; char path[PATH_MAX]; char wbuf[PATH_MAX]; char bbuf[PATH_MAX]; + struct stat sb; + const char *p; + int ch, first; + bool append, exists; if (!sflag) { /* If source doesn't exist, quit now. */ @@ -246,14 +245,27 @@ linkit(const char *source, const char *target, int isdir) } /* - * If the target is a directory (and not a symlink if hflag), - * append the source's name, unless Fflag is set. + * Append a slash and the source's basename if: + * - the target is "." or ends in "/" or "/.", or + * - the target is a directory (and not a symlink if hflag) and + * Fflag is not set */ - if (!Fflag && (isdir || - (lstat(target, &sb) == 0 && S_ISDIR(sb.st_mode)) || - (!hflag && stat(target, &sb) == 0 && S_ISDIR(sb.st_mode)))) { + if ((p = strrchr(target, '/')) == NULL) + p = target; + else + p++; + append = false; + if (p[0] == '\0' || (p[0] == '.' && p[1] == '\0')) { + append = true; + } else if (!Fflag) { + if (isdir || (lstat(target, &sb) == 0 && S_ISDIR(sb.st_mode)) || + (!hflag && stat(target, &sb) == 0 && S_ISDIR(sb.st_mode))) { + append = true; + } + } + if (append) { if (strlcpy(bbuf, source, sizeof(bbuf)) >= sizeof(bbuf) || - (p = basename(bbuf)) == NULL || + (p = basename(bbuf)) == NULL /* can't happen */ || snprintf(path, sizeof(path), "%s/%s", target, p) >= (ssize_t)sizeof(path)) { errno = ENAMETOOLONG; @@ -292,7 +304,7 @@ linkit(const char *source, const char *target, int isdir) /* * If the file exists, first check it is not the same directory entry. */ - exists = !lstat(target, &sb); + exists = lstat(target, &sb) == 0; if (exists) { if (!sflag && samedirent(source, target)) { warnx("%s and %s are the same directory entry", @@ -350,11 +362,17 @@ linkit(const char *source, const char *target, int isdir) } static void +link_usage(void) +{ + (void)fprintf(stderr, "usage: link source_file target_file\n"); + exit(1); +} + +static void usage(void) { - (void)fprintf(stderr, "%s\n%s\n%s\n", + (void)fprintf(stderr, "%s\n%s\n", "usage: ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file [target_file]", - " ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file ... target_dir", - " link source_file target_file"); + " ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file ... target_dir"); exit(1); } diff --git a/bin/ln/symlink.7 b/bin/ln/symlink.7 index b3488ea37ae1..28d9908f2053 100644 --- a/bin/ln/symlink.7 +++ b/bin/ln/symlink.7 @@ -26,10 +26,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" @(#)symlink.7 8.3 (Berkeley) 3/31/94 -.\" $FreeBSD$ -.\" -.Dd February 16, 2015 +.Dd August 11, 2024 .Dt SYMLINK 7 .Os .Sh NAME @@ -147,6 +144,7 @@ unless given the .Dv AT_SYMLINK_NOFOLLOW flag: .Xr chflagsat 2 , +.Xr faccessat 2 , .Xr fchmodat 2 , .Xr fchownat 2 , .Xr fstatat 2 diff --git a/bin/ln/tests/Makefile b/bin/ln/tests/Makefile index 7ac99a947560..13ae71b73143 100644 --- a/bin/ln/tests/Makefile +++ b/bin/ln/tests/Makefile @@ -1,5 +1,3 @@ -# $FreeBSD$ - ATF_TESTS_SH+= ln_test .include <bsd.test.mk> diff --git a/bin/ln/tests/Makefile.depend b/bin/ln/tests/Makefile.depend index f80275d86ab1..11aba52f82cf 100644 --- a/bin/ln/tests/Makefile.depend +++ b/bin/ln/tests/Makefile.depend @@ -1,4 +1,3 @@ -# $FreeBSD$ # Autogenerated - do NOT edit! DIRDEPS = \ diff --git a/bin/ln/tests/ln_test.sh b/bin/ln/tests/ln_test.sh index e1f5b47c262e..78b4074aea18 100644 --- a/bin/ln/tests/ln_test.sh +++ b/bin/ln/tests/ln_test.sh @@ -1,4 +1,6 @@ # +# SPDX-License-Identifier: BSD-2-Clause +# # Copyright 2017 Shivansh Rai # All rights reserved. # @@ -23,14 +25,15 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $FreeBSD$ -# -set_umask() +atf_check_same_file() +{ + atf_check_equal "$(stat -f %d,%i "$1")" "$(stat -f %d,%i "$2")" +} + +atf_check_symlink_to() { - if ! umask 022; then - atf_fail "setting umask failed" - fi + atf_check -o inline:"$1\n" readlink "$2" } atf_test_case L_flag @@ -40,18 +43,13 @@ L_flag_head() "symbolic link, '-L' option creates a hard" \ "link to the target of the symbolic link" } - L_flag_body() { - set_umask atf_check touch A atf_check ln -s A B atf_check ln -L B C - stat_A=$(stat -f %i A) - stat_C=$(stat -f %i C) - atf_check_equal "$stat_A" "$stat_C" - atf_check -o inline:'Symbolic Link\n' stat -f %SHT B - atf_check -o inline:'A\n' readlink B + atf_check_same_file A C + atf_check_symlink_to A B } atf_test_case P_flag @@ -61,16 +59,12 @@ P_flag_head() "symbolic link, '-P' option creates a hard " \ "link to the symbolic link itself" } - P_flag_body() { - set_umask atf_check touch A atf_check ln -s A B atf_check ln -P B C - stat_B=$(stat -f %i B) - stat_C=$(stat -f %i C) - atf_check_equal "$stat_B" "$stat_C" + atf_check_same_file B C } atf_test_case f_flag @@ -79,15 +73,11 @@ f_flag_head() atf_set "descr" "Verify that if the target file already exists, " \ "'-f' option unlinks it so that link may occur" } - f_flag_body() { - set_umask atf_check touch A B atf_check ln -f A B - stat_A=$(stat -f %i A) - stat_B=$(stat -f %i B) - atf_check_equal "$stat_A" "$stat_B" + atf_check_same_file A B } atf_test_case target_exists_hard @@ -96,13 +86,11 @@ target_exists_hard_head() atf_set "descr" "Verify whether creating a hard link fails if the " \ "target file already exists" } - target_exists_hard_body() { - set_umask atf_check touch A B atf_check -s exit:1 -e inline:'ln: B: File exists\n' \ - ln A B + ln A B } atf_test_case target_exists_symbolic @@ -111,13 +99,11 @@ target_exists_symbolic_head() atf_set "descr" "Verify whether creating a symbolic link fails if " \ "the target file already exists" } - target_exists_symbolic_body() { - set_umask atf_check touch A B atf_check -s exit:1 -e inline:'ln: B: File exists\n' \ - ln -s A B + ln -s A B } atf_test_case shf_flag_dir @@ -125,13 +111,12 @@ shf_flag_dir_head() { atf_set "descr" "Verify that if the target directory is a symbolic " \ "link, '-shf' option prevents following the link" } - shf_flag_dir_body() { atf_check mkdir -m 0777 A B atf_check ln -s A C atf_check ln -shf B C - atf_check -o inline:'Symbolic Link\n' stat -f %SHT C + atf_check test -L C atf_check -o inline:'B\n' readlink C } @@ -140,14 +125,12 @@ snf_flag_dir_head() { atf_set "descr" "Verify that if the target directory is a symbolic " \ "link, '-snf' option prevents following the link" } - snf_flag_dir_body() { atf_check mkdir -m 0777 A B atf_check ln -s A C atf_check ln -snf B C - atf_check -o inline:'Symbolic Link\n' stat -f %SHT C - atf_check -o inline:'B\n' readlink C + atf_check_symlink_to B C } atf_test_case sF_flag @@ -157,13 +140,11 @@ sF_flag_head() "and is a directory, then '-sF' option removes " \ "it so that the link may occur" } - sF_flag_body() { atf_check mkdir A B atf_check ln -sF A B - atf_check -o inline:'Symbolic Link\n' stat -f %SHT B - atf_check -o inline:'A\n' readlink B + atf_check_symlink_to A B } atf_test_case sf_flag @@ -173,14 +154,31 @@ sf_flag_head() "'-sf' option unlinks it and creates a symbolic link " \ "to the source file" } - sf_flag_body() { - set_umask atf_check touch A B atf_check ln -sf A B - atf_check -o inline:'Symbolic Link\n' stat -f %SHT B - atf_check -o inline:'A\n' readlink B + atf_check_symlink_to A B +} + +atf_test_case sfF_flag +sfF_flag_head() +{ + atf_set "descr" "Verify that if the target file already exists " \ + "and is a symlink, then '-sfF' option removes " \ + "it so that the link may occur" +} +sfF_flag_body() +{ + atf_check mkdir A B C D D/A + atf_check ln -sF A C + atf_check_symlink_to A C + atf_check ln -sfF B C + atf_check_symlink_to B C + atf_check ln -sfF A D/ + atf_check_symlink_to A D/A + atf_check ln -sfF ../A . + atf_check_symlink_to ../A A } atf_test_case s_flag @@ -188,14 +186,11 @@ s_flag_head() { atf_set "descr" "Verify that '-s' option creates a symbolic link" } - s_flag_body() { - set_umask atf_check touch A atf_check ln -s A B - atf_check -o inline:'Symbolic Link\n' stat -f %SHT B - atf_check -o inline:'A\n' readlink B + atf_check_symlink_to A B } atf_test_case s_flag_broken @@ -204,12 +199,10 @@ s_flag_broken_head() atf_set "descr" "Verify that if the source file does not exists, '-s' " \ "option creates a broken symbolic link to the source file" } - s_flag_broken_body() { atf_check ln -s A B - atf_check -o inline:'Symbolic Link\n' stat -f %SHT B - atf_check -o inline:'A\n' readlink B + atf_check_symlink_to A B } atf_test_case sw_flag @@ -218,13 +211,66 @@ sw_flag_head() atf_set "descr" "Verify that '-sw' option produces a warning if the " \ "source of a symbolic link does not currently exist" } - sw_flag_body() { atf_check -s exit:0 -e inline:'ln: warning: A: No such file or directory\n' \ - ln -sw A B - atf_check -o inline:'Symbolic Link\n' stat -f %SHT B - atf_check -o inline:'A\n' readlink B + ln -sw A B + atf_check_symlink_to A B +} + +atf_test_case link_argc +link_argc_head() { + atf_set "descr" "Verify that link(1) requires exactly two arguments" +} +link_argc_body() { + atf_check -s exit:1 -e match:"usage: link" \ + link foo + atf_check -s exit:1 -e match:"No such file" \ + link foo bar + atf_check -s exit:1 -e match:"No such file" \ + link -- foo bar + atf_check -s exit:1 -e match:"usage: link" \ + link foo bar baz +} + +atf_test_case link_basic +link_basic_head() { + atf_set "descr" "Verify that link(1) creates a link" +} +link_basic_body() { + touch foo + atf_check link foo bar + atf_check_same_file foo bar + rm bar + ln -s foo bar + atf_check link bar baz + atf_check_same_file foo baz +} + +atf_test_case link_eexist +link_eexist_head() { + atf_set "descr" "Verify that link(1) fails if the target exists" +} +link_eexist_body() { + touch foo bar + atf_check -s exit:1 -e match:"bar.*exists" \ + link foo bar + ln -s non-existent baz + atf_check -s exit:1 -e match:"baz.*exists" \ + link foo baz +} + +atf_test_case link_eisdir +link_eisdir_head() { + atf_set "descr" "Verify that link(1) fails if the source is a directory" +} +link_eisdir_body() { + mkdir foo + atf_check -s exit:1 -e match:"foo.*directory" \ + link foo bar + ln -s foo bar + atf_check -s exit:1 -e match:"bar.*directory" \ + link bar baz } atf_init_test_cases() @@ -238,7 +284,12 @@ atf_init_test_cases() atf_add_test_case snf_flag_dir atf_add_test_case sF_flag atf_add_test_case sf_flag + atf_add_test_case sfF_flag atf_add_test_case s_flag atf_add_test_case s_flag_broken atf_add_test_case sw_flag + atf_add_test_case link_argc + atf_add_test_case link_basic + atf_add_test_case link_eexist + atf_add_test_case link_eisdir } |