ln: Improve link(1) variant of ln(1).

* Give link(1) its own usage message.
* Use getprogname(3) instead of rolling our own.
* Verify that the target file does not already exist.
* Add tests specific to link(1).

MFC after:	3 days
Sponsored by:	Klara, Inc.
Reviewed by:	allanjude
Differential Revision:	https://reviews.freebsd.org/D44635
This commit is contained in:
Dag-Erling Smørgrav 2024-04-04 16:14:50 +02:00
parent 8311bc5f17
commit bee7cf9e97
2 changed files with 85 additions and 16 deletions

View File

@ -55,13 +55,14 @@ static bool wflag; /* Warn if symlink target does not
static char linkch; static char linkch;
static int linkit(const char *, const char *, bool); static int linkit(const char *, const char *, bool);
static void link_usage(void) __dead2;
static void usage(void) __dead2; static void usage(void) __dead2;
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
struct stat sb; struct stat sb;
char *p, *targetdir; char *targetdir;
int ch, exitval; int ch, exitval;
/* /*
@ -69,17 +70,20 @@ main(int argc, char *argv[])
* "link", for which the functionality provided is greatly * "link", for which the functionality provided is greatly
* simplified. * simplified.
*/ */
if ((p = strrchr(argv[0], '/')) == NULL) if (strcmp(getprogname(), "link") == 0) {
p = argv[0];
else
++p;
if (strcmp(p, "link") == 0) {
while (getopt(argc, argv, "") != -1) while (getopt(argc, argv, "") != -1)
usage(); link_usage();
argc -= optind; argc -= optind;
argv += optind; argv += optind;
if (argc != 2) if (argc != 2)
usage(); 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)); exit(linkit(argv[0], argv[1], false));
} }
@ -338,11 +342,17 @@ linkit(const char *source, const char *target, bool isdir)
} }
static void static void
usage(void) link_usage(void)
{ {
(void)fprintf(stderr, "%s\n%s\n%s\n", (void)fprintf(stderr, "usage: link source_file target_file\n");
"usage: ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file [target_file]", exit(1);
" ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file ... target_dir", }
" link source_file target_file");
static void
usage(void)
{
(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");
exit(1); exit(1);
} }

View File

@ -90,7 +90,7 @@ target_exists_hard_body()
{ {
atf_check touch A B atf_check touch A B
atf_check -s exit:1 -e inline:'ln: B: File exists\n' \ atf_check -s exit:1 -e inline:'ln: B: File exists\n' \
ln A B ln A B
} }
atf_test_case target_exists_symbolic atf_test_case target_exists_symbolic
@ -103,7 +103,7 @@ target_exists_symbolic_body()
{ {
atf_check touch A B atf_check touch A B
atf_check -s exit:1 -e inline:'ln: B: File exists\n' \ 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 atf_test_case shf_flag_dir
@ -210,10 +210,65 @@ sw_flag_head()
sw_flag_body() sw_flag_body()
{ {
atf_check -s exit:0 -e inline:'ln: warning: A: No such file or directory\n' \ atf_check -s exit:0 -e inline:'ln: warning: A: No such file or directory\n' \
ln -sw A B ln -sw A B
atf_check_symlink_to 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() atf_init_test_cases()
{ {
atf_add_test_case L_flag atf_add_test_case L_flag
@ -229,4 +284,8 @@ atf_init_test_cases()
atf_add_test_case s_flag atf_add_test_case s_flag
atf_add_test_case s_flag_broken atf_add_test_case s_flag_broken
atf_add_test_case sw_flag 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
} }