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:
M | cmd/mv.c | 55 | +++++++++++++++++++++++++++++++++---------------------- |
M | test-cmd/mv.t | 48 | ++++++++++++++++++++++++++++++++++++++++++++++-- |
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