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 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;
/*
@ -69,17 +70,20 @@ 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();
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));
}
@ -338,11 +342,17 @@ linkit(const char *source, const char *target, bool isdir)
}
static void
usage(void)
link_usage(void)
{
(void)fprintf(stderr, "%s\n%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");
(void)fprintf(stderr, "usage: link source_file target_file\n");
exit(1);
}
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);
}

View File

@ -214,6 +214,61 @@ sw_flag_body()
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_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_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
}