logo

utils-std

Collection of commonly available Unix tools
commit: 25b4d174ea465c365fd5356d7a73ef49fe72dc24
parent 4ad6ddd0e1587ec03e5c11fac8dce09e92ab2b79
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Sun,  2 Jun 2024 08:20:53 +0200

cmd/mv: Align overwriting to POSIX

Diffstat:

Mcmd/mv.c55+++++++++++++++++++++++++++++++++----------------------
Mtest-cmd/mv.t48++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 79 insertions(+), 24 deletions(-)

diff --git a/cmd/mv.c b/cmd/mv.c @@ -31,6 +31,8 @@ char *argv0 = "mv"; bool no_clob = false, force = false, interact = false, verbose = false; +static int stdin_tty = 0; + struct named_fd { int fd; @@ -70,20 +72,23 @@ do_renameat(const char *restrict src, struct named_fd destdir, const char *restr return -1; } - int ret = faccessat(destdir.fd, dest, F_OK, 0); - if(ret == 0) + errno = 0; + + struct stat dest_status; + int ret = fstatat(destdir.fd, dest, &dest_status, 0); + if(ret < 0 && errno != ENOENT) { - struct stat dest_status; - if(fstatat(destdir.fd, dest, &dest_status, 0) < 0) - { - fprintf(stderr, - "mv: Failed getting status for destination file '%s/%s': %s\n", - destdir.name, - dest, - strerror(errno)); - return -1; - } + fprintf(stderr, + "mv: Failed getting status for destination file '%s/%s': %s\n", + destdir.name, + dest, + strerror(errno)); + return -1; + } + errno = 0; + if(ret == 0) + { struct stat src_status; if(fstatat(AT_FDCWD, src, &src_status, 0) < 0) { @@ -109,25 +114,28 @@ do_renameat(const char *restrict src, struct named_fd destdir, const char *restr if(!force) { - if(interact || isatty(STDIN_FILENO)) + if(interact) { - errno = 0; if(!consentf( "mv: Destination file '%s/%s' already exists, overwrite? [yN] ", destdir.name, dest)) return 0; } - else + else if(stdin_tty) { - errno = 0; - fprintf(stderr, "mv: Destination file '%s/%s' already exists.\n", destdir.name, dest); - return 0; + if(faccessat(destdir.fd, dest, W_OK, 0) == 0) + { + if(!consentf("mv: No write permissions for destination file '%s/%s', overwrite? [yN] ", + destdir.name, + dest)) + return 0; + } + else + { + errno = 0; + } } } } - else - { - errno = 0; - } assert(errno == 0); if(renameat(AT_FDCWD, src, destdir.fd, dest) < 0) @@ -257,6 +265,9 @@ main(int argc, char *argv[]) consent_init(); + stdin_tty = isatty(STDIN_FILENO); + if(!stdin_tty) errno = 0; + if(destdir.fd == AT_FDCWD) { if(argc <= 1) diff --git a/test-cmd/mv.t b/test-cmd/mv.t @@ -9,12 +9,12 @@ POSIX, non-directory target with a trailing slash is an error $ touch nondir file $ mv file nondir/ - mv: Failed opening destination directory 'nondir/': Not a directory + mv: Failed getting status for destination file './nondir/': Not a directory [1] $ test -e file $ test -e nondir $ mv enoent nondir/ - mv: Failed moving 'enoent' to './nondir/': No such file or directory + mv: Failed getting status for destination file './nondir/': Not a directory [1] $ rm nondir file @@ -81,6 +81,50 @@ POSIX mv(1) step 2, same file $ test -e same-s $ rm same Same same-s +Where destination is an existing directory + $ mkdir destdir + $ touch foo + $ mv foo destdir + mv: Destination file './destdir' already exists. + [1] + $ test -f foo + $ test -d destdir + $ test ! -e destdir/foo + $ rm -r destdir + + $ mkdir destdir_trail/ + $ touch foo_trail + $ mv foo_trail destdir_trail/ + mv: Destination file './destdir_trail/' already exists. + [1] + $ test -f foo_trail + $ test -d destdir_trail + $ test ! -e destdir_trail/foo_trail + $ rm -r destdir_trail + $ rm foo_trail + +Where destination is an existing file + $ touch foo_file destfile + $ mv foo_file destfile + $ test ! -e foo_file + $ test -f destfile + $ rm destfile + + $ touch foo_file_i destfile_i + $ printf 'y\n' | mv -i foo_file_i destfile_i + mv: Destination file './destfile_i' already exists, overwrite? [yN] y + $ test ! -e foo_file_i + $ test -f destfile_i + $ rm destfile_i + + $ touch foo_file_trail destfile_trail + $ mv foo_file_trail destfile_trail/ + mv: Failed getting status for destination file './destfile_trail/': Not a directory + [1] + $ test -f foo_file_trail + $ test -f destfile_trail + $ rm foo_file_trail destfile_trail + Verbose (non-standard) $ touch foo $ mv -v foo bar