logo

utils-std

Collection of commonly available Unix tools
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:

Mcmd/ln.113++++++++-----
Mcmd/ln.c43+++++++++++++++++++++++++++----------------
Mtest-cmd/ln.t16++++++++++++++++
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 . .