logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/
commit: 3222081dd068d490c610c2f79797d6f53374a862
parent 08147ec21ddca45950d806e72ebfba30f44dc3c3
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Sat, 13 Dec 2025 16:29:53 +0100

cmd/ln: Don't skip directory check prior to unlinking with -T

Done because unlink() returning EISDIR is a Linux extension,
and EPERM (BSD, POSIX.1-2024) is too generic an error.

Smells like a TOCTOU trap to me but it is what it is…

Diffstat:

Mcmd/ln.c10++++++++--
Mtest-cmd/ln.sh4++--
2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/cmd/ln.c b/cmd/ln.c @@ -68,7 +68,7 @@ do_link(char *src, char *dest, int destfd) return -1; } - if(destfd < 0 && !opt_T) + if(destfd < 0) { destfd = open(dest, open_dest_flags); if(destfd < 0) @@ -95,8 +95,14 @@ do_link(char *src, char *dest, int destfd) } int dirfd = AT_FDCWD; - if(S_ISDIR(dest_stat.st_mode) && !opt_T) + if(S_ISDIR(dest_stat.st_mode)) { + if(opt_T) + { + fprintf(stderr, "ln: error: Option -T passed but target '%s' is a directory\n", dest); + return -1; + } + dirfd = destfd; } else diff --git a/test-cmd/ln.sh b/test-cmd/ln.sh @@ -126,7 +126,7 @@ t_cmd Ts:cleanup '' rm Ts t_cmd Ts.d:mkdir '' mkdir -p Ts.d t_args --exit=1 Ts.d "ln: error: Destination 'Ts.d' already exists " -Ts / Ts.d -t_args --exit=1 Ts.d:f "ln: error: Failed unlinking destination 'Ts.d': Is a directory +t_args --exit=1 Ts.d:f "ln: error: Option -T passed but target 'Ts.d' is a directory " -Tsf / Ts.d t_cmd Ts.d:rmdir '' rm -r Ts.d @@ -139,6 +139,6 @@ t_cmd T.d:mkdir '' mkdir -p T.d t_args --exit=1 T.d "ln: error: Destination 'T.d' already exists " -T "$target" T.d t_cmd '' '' test -d T.d -t_args --exit=1 T.d:f "ln: error: Failed unlinking destination 'T.d': Is a directory +t_args --exit=1 T.d:f "ln: error: Option -T passed but target 'T.d' is a directory " -Tf "$target" T.d t_cmd T.d:rmdir '' rm -r T.d