commit: 30a23b46897839a4be659fff24370384a599b657
parent 726b168d8e6fa197f3acbb93330d54d0ddfb284e
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Sat, 8 Jun 2024 07:37:34 +0200
cmd/ln: Align with POSIX on existing target directory
Diffstat:
3 files changed, 51 insertions(+), 21 deletions(-)
diff --git a/cmd/ln.1 b/cmd/ln.1
@@ -26,17 +26,20 @@ for each given
.Ar source
or
.Ar reference .
-When multiple
+.Pp
+When
+.Ar target
+is an existing directory or multiple
.Ar source
or
.Ar reference
are given,
-.Ar target
-is expected to be a directory which
.Nm
-will create links into.
+will create links into
+.Ar target .
.Pp
-Should be noted that
+Should be noted that unlike commands like
+.Xr cp 1 ,
.Ar reference
is kept as is and therefore is always relative to
.Ar target
diff --git a/cmd/ln.c b/cmd/ln.c
@@ -171,30 +171,41 @@ main(int argc, char *argv[])
argc -= optind;
argv += optind;
- if(argc == 2)
+ if(argc <= 1)
{
- if(do_link(argv[0], argv[1]) < 0) return 1;
+ fprintf(stderr, "ln: Not enough operands, %d given, expect >= 2\n", argc);
+ return 1;
}
- else
+ else if(argc == 2)
{
- char *dest = argv[argc - 1];
- char target[PATH_MAX] = "";
-
- for(int i = 0; i < argc - 1; i++)
+ 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))))
{
- char *src = argv[i];
+ int ret = do_link(argv[0], argv[1]);
- char *src_sep = strrchr(src, '/');
- char *src_basename = src_sep != NULL ? src_sep + 1 : src;
+ return ret < 0 ? 1 : 0;
+ }
+ errno = 0;
+ }
- if(snprintf(target, PATH_MAX, "%s/%s", dest, src_basename) < 0)
- {
- fprintf(stderr, "ln: Failed joining destination '%s' and target '%s'\n", dest, src);
- return 1;
- }
+ char *dest = argv[argc - 1];
+ char target[PATH_MAX] = "";
- if(do_link(src, target) < 0) return 1;
+ for(int i = 0; i < argc - 1; i++)
+ {
+ char *src = argv[i];
+
+ char *src_sep = strrchr(src, '/');
+ char *src_basename = src_sep != NULL ? src_sep + 1 : src;
+
+ if(snprintf(target, PATH_MAX, "%s/%s", dest, src_basename) < 0)
+ {
+ fprintf(stderr, "ln: Failed joining destination '%s' and target '%s'\n", dest, src);
+ return 1;
}
+
+ if(do_link(src, target) < 0) return 1;
}
return 0;
diff --git a/test-cmd/ln.t b/test-cmd/ln.t
@@ -74,5 +74,21 @@
//example.org
$ rm -r n_directory n_dir_symlink
+ $ mkdir e_target_dir
+ $ ln -s e_ref_dir e_target_dir
+ $ test ! -e e_ref_dir
+ $ test ! -L e_target_dir
+ $ test -d e_target_dir
+ $ test -L e_target_dir/e_ref_dir
+ $ echo mere copy > e_src_dir
+ $ test -f e_src_dir
+ $ ln e_src_dir e_target_dir
+ $ echo hardlink > e_src_dir
+ $ test -d e_target_dir
+ $ test -f e_target_dir/e_src_dir
+ $ cat e_target_dir/e_src_dir
+ hardlink
+ $ rm -r e_target_dir e_src_dir
+
$ find .
.