commit: 2eecdbaad34600bb6004ba646fd530121d174cda
parent 0e443fbe5c17bc13110e2ef07f649c52d47ece02
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Thu, 11 Jul 2024 02:15:05 +0200
cmd/mv: cleanout for now, misses directory handling
Diffstat:
6 files changed, 3 insertions(+), 562 deletions(-)
diff --git a/cmd/mv.1 b/cmd/mv.1
@@ -1,97 +0,0 @@
-.\" utils-std: Collection of commonly available Unix tools
-.\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
-.\" SPDX-License-Identifier: MPL-2.0
-.Dd 2024-05-10
-.Dt MV 1
-.Os
-.Sh NAME
-.Nm mv
-.Nd move and rename files
-.Sh SYNOPSIS
-.Nm
-.Op Fl f Ns | Ns Fl i Ns | Ns Fl n
-.Op Fl v
-.Ar source
-.Ar destfile
-.Nm
-.Op Fl f Ns | Ns Fl i Ns | Ns Fl n
-.Op Fl v
-.Ar source...
-.Ar destdir
-.Nm
-.Op Fl f Ns | Ns Fl i Ns | Ns Fl n
-.Op Fl v
-.Fl t Ar destdir
-.Ar source...
-.Sh DESCRIPTION
-In the first form,
-.Nm
-moves each given
-.Ar source
-to
-.Ar destfile .
-This form is assumed when
-.Ar destfile
-does not refers to an existing directory, or a symlink pointing to one.
-Additionally, in this case a trailing slash and
-.Ar source
-not referring to a directory results in an error.
-.Pp
-In the second and third form,
-.Nm
-moves each given
-.Ar source
-into
-.Ar destdir
-with appending the
-.Ar source
-basename to
-.Ar destdir
-to create the full destination path.
-.Sh OPTIONS
-.Bl -tag -width _f
-.It Fl f
-Force, do not ask before overwriting to the destination path.
-Overrides previously set
-.Fl i
-and
-.Fl n
-options.
-.It Fl i
-Interactive, causes
-.Nm
-to ask before overwriting a file.
-Overrides previously set
-.Fl f
-and
-.Fl n
-options.
-.It Fl n
-No-clobber, never overwrite.
-Overrides previously set
-.Fl f
-and
-.Fl i
-options.
-.It Fl t Ar destdir
-Set the destination directory.
-.It Fl v
-Verbose, write which action has been done.
-.El
-.Sh STANDARDS
-The
-.Nm
-utility is expected to be
-.St -p1003.2
-compatible.
-The
-.Fl n ,
-.Fl t Ar destdir
-and
-.Fl v
-options are extensions.
-.Sh HISTORY
-A
-.Nm
-command appeared in
-.At v1 .
diff --git a/cmd/mv.c b/cmd/mv.c
@@ -1,322 +0,0 @@
-// utils-std: Collection of commonly available Unix tools
-// SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
-// SPDX-License-Identifier: MPL-2.0
-
-#define _POSIX_C_SOURCE 200809L
-#define _GNU_SOURCE // copy_file_range
-#define _FILE_OFFSET_BITS 64
-
-#include "../lib/consent.h"
-#include "../lib/fs.h"
-
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h> // open
-#include <libgen.h> // basename
-#include <limits.h> // PATH_MAX
-#include <locale.h> // setlocale
-#include <stdbool.h>
-#include <stdint.h> // SIZE_MAX
-#include <stdio.h> // fprintf, rename
-#include <string.h> // strcmp
-#include <sys/stat.h> // stat, S_ISDIR
-#include <unistd.h> // getopt
-
-// Workaround against GNU glibc
-// https://sourceware.org/bugzilla/show_bug.cgi?id=18228
-#if defined(__linux__) && !defined(O_SEARCH)
-// As defined in musl
-#define O_SEARCH O_PATH
-#endif
-
-char *argv0 = "mv";
-
-bool no_clob = false, force = false, interact = false, verbose = false;
-
-static int stdin_tty = 0;
-
-struct named_fd
-{
- int fd;
- char *name;
-};
-
-static int
-copy_unlink(const char *restrict src, const char *restrict dest)
-{
- int in = open(src, O_RDONLY | O_NOCTTY);
- if(in < 0)
- {
- fprintf(stderr, "mv: Failed opening '%s': %s\n", src, strerror(errno));
- errno = 0;
- return -1;
- }
-
- int out = open(dest, O_WRONLY | O_CREAT | O_NOCTTY);
- if(out < 0)
- {
- fprintf(stderr, "mv: Failed opening '%s': %s\n", dest, strerror(errno));
- errno = 0;
- return -1;
- }
-
- if(auto_file_copy(in, out, SIZE_MAX, 0) < 0) return -1;
-
- return unlink(src);
-}
-
-static int
-do_renameat(const char *restrict src, struct named_fd destdir, const char *restrict dest)
-{
- if(destdir.fd == AT_FDCWD && strcmp(src, dest) == 0)
- {
- fprintf(stderr, "mv: Error, passed to both source and destination: '%s'\n", src);
- return -1;
- }
-
- errno = 0;
-
- struct stat dest_status;
- int ret = fstatat(destdir.fd, dest, &dest_status, 0);
- if(ret < 0 && errno != ENOENT)
- {
- 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)
- {
- fprintf(stderr, "mv: Failed getting status for source file '%s': %s\n", src, strerror(errno));
- return -1;
- }
-
- if(dest_status.st_ino == src_status.st_ino && dest_status.st_dev == src_status.st_dev) return 0;
-
- if(no_clob)
- {
- fprintf(stderr, "mv: Destination file '%s/%s' already exists\n", destdir.name, dest);
- return -1;
- }
-
- if(!force)
- {
- if(interact)
- {
- if(!consentf(
- "mv: Destination file '%s/%s' already exists, overwrite? [yN] ", destdir.name, dest))
- return 0;
- }
- else if(stdin_tty)
- {
- 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;
- }
- }
- }
- }
-
- assert(errno == 0);
- if(renameat(AT_FDCWD, src, destdir.fd, dest) < 0)
- {
- switch(errno)
- {
- case EXDEV:
- errno = 0;
- if(copy_unlink(src, dest) < 0) return -1;
- break;
- case EISDIR:
- case ENOTDIR:
- if(destdir.fd != AT_FDCWD)
- {
- fprintf(stderr, "mv: Failed moving '%s' into '%s': %s\n", src, dest, strerror(errno));
- return -1;
- }
-
- int tmp_destdir = open(dest, O_SEARCH | O_DIRECTORY);
- if(tmp_destdir < 0)
- {
- fprintf(
- stderr, "mv: Failed opening destination directory '%s': %s\n", dest, strerror(errno));
- return -1;
- }
-
- if(renameat(AT_FDCWD, src, tmp_destdir, src) < 0)
- {
- fprintf(
- stderr, "mv: Failed moving '%s' into directory '%s': %s\n", src, dest, strerror(errno));
- return -1;
- }
-
- if(close(tmp_destdir) < 0)
- {
- fprintf(stderr, "mv: Failed closing directory '%s': %s\n", dest, strerror(errno));
- return -1;
- }
- break;
- default:
- fprintf(stderr,
- "mv: Failed moving '%s' to '%s/%s': %s\n",
- src,
- destdir.name,
- dest,
- strerror(errno));
- return -1;
- }
- }
-
- if(verbose) fprintf(stderr, "mv: renamed '%s' -> '%s/%s'\n", src, destdir.name, dest);
-
- return 0;
-}
-
-static void
-usage()
-{
- fprintf(stderr, "Usage: mv [-f|-i|-n] [-v] source dest\n");
- fprintf(stderr, " mv [-f|-i|-n] [-v] source... destdir\n");
- fprintf(stderr, " mv [-f|-i|-n] [-v] -t destdir source...\n");
-}
-
-int
-main(int argc, char *argv[])
-{
- struct named_fd destdir = {
- .fd = AT_FDCWD,
- .name = ".",
- };
-
- int c = -1;
- while((c = getopt(argc, argv, ":fint:v")) != -1)
- {
- switch(c)
- {
- case 'f':
- force = true;
- interact = false;
- no_clob = false;
- break;
- case 'i':
- force = false;
- interact = true;
- no_clob = false;
- break;
- case 'n':
- force = false;
- interact = false;
- no_clob = true;
- break;
- case 't':
- destdir.name = optarg;
- destdir.fd = open(optarg, O_SEARCH | O_DIRECTORY);
-
- if(destdir.fd < 0)
- {
- fprintf(
- stderr, "mv: Failed opening destination directory '%s': %s\n", optarg, strerror(errno));
- return 1;
- }
- break;
- case 'v':
- verbose = true;
- break;
- case ':':
- fprintf(stderr, "mv: Error: Missing operand for option: '-%c'\n", optopt);
- usage();
- return 1;
- case '?':
- fprintf(stderr, "mv: Error: Unrecognised option: '-%c'\n", optopt);
- usage();
- return 1;
- }
- }
-
- argc -= optind;
- argv += optind;
-
- errno = 0;
- setlocale(LC_ALL, "");
- if(errno != 0)
- {
- fprintf(stderr, "%s: Warning: Failed to initialize locales: %s\n", argv0, strerror(errno));
- errno = 0;
- }
-
- consent_init();
-
- stdin_tty = isatty(STDIN_FILENO);
- if(!stdin_tty) errno = 0;
-
- if(destdir.fd == AT_FDCWD)
- {
- if(argc <= 1)
- {
- fprintf(stderr, "mv: Not enough operands, %d given, expect >= 2\n", argc);
- return 1;
- }
-
- struct stat dest_status;
- int ret_stat = fstatat(destdir.fd, argv[1], &dest_status, 0);
- if(argc == 2 && (errno == ENOENT || (ret_stat == 0 && !S_ISDIR(dest_status.st_mode))))
- {
- int ret = do_renameat(argv[0], destdir, argv[1]);
-
- consent_finish();
- return ret < 0 ? 1 : 0;
- }
- errno = 0;
-
- argc--;
- destdir.name = argv[argc];
- destdir.fd = open(destdir.name, O_SEARCH | O_DIRECTORY);
- if(destdir.fd < 0)
- {
- fprintf(stderr,
- "mv: Failed opening destination directory '%s': %s\n",
- destdir.name,
- strerror(errno));
-
- consent_finish();
- return 1;
- }
- }
-
- for(int i = 0; i < argc; i++)
- {
- char arg[PATH_MAX] = "";
- strcpy(arg, argv[i]);
-
- char *filename = basename(arg);
-
- if(do_renameat(argv[i], destdir, filename) < 0)
- {
- consent_finish();
- return 1;
- }
- }
-
- consent_finish();
-
- if(close(destdir.fd) < 0)
- {
- fprintf(stderr, "mv: Failed closing directory '%s': %s\n", destdir.name, strerror(errno));
- return 1;
- }
-
- return 0;
-}
diff --git a/coreutils.txt b/coreutils.txt
@@ -47,7 +47,7 @@ mkdir: Done
mkfifo: Done
mknod: Done
mktemp: Todo
-mv: Done
+mv
nice: Done
nl: No, use sed
nohup: Done
diff --git a/lsb_commands.txt b/lsb_commands.txt
@@ -84,7 +84,7 @@ mktemp: Todo
more: No
mount: out of scope
msgfmt: out of scope
-mv: Done
+mv
newgrp: out of scope
nice: Done
nl: No, use sed
diff --git a/posix_utilities.txt b/posix_utilities.txt
@@ -83,7 +83,7 @@ mesg: no, external (linked to wall and write)
mkdir: done
mkfifo: done
more: no
-mv: done
+mv
newgrp
nice: done
nl
diff --git a/test-cmd/mv.t b/test-cmd/mv.t
@@ -1,140 +0,0 @@
-#!/usr/bin/env cram
-# SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
-# SPDX-License-Identifier: MPL-2.0
-
- $ export PATH="$TESTDIR/../cmd:$PATH"
-
- $ test "$(command -v mv)" = "$TESTDIR/../cmd/mv"
-
-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
- [1]
- $ test -e file
- $ test -e nondir
- $ mv enoent nondir/
- mv: Failed opening destination directory 'nondir/': Not a directory
- [1]
- $ rm nondir file
-
-POSIX mv(1) step 1a, no -f option, no -i option
- $ mkdir -m -w 1a_no-f_no-write
- $ touch src
- $ printf '\n' | mv src 1a_no-f_no-write/dest
- mv: Failed moving 'src' to './1a_no-f_no-write/dest': Permission denied
- [1]
- $ test -e src
- $ printf 'n\n' | mv src 1a_no-f_no-write/dest
- mv: Failed moving 'src' to './1a_no-f_no-write/dest': Permission denied
- [1]
- $ test -e src
- $ printf 'y\n' | mv src 1a_no-f_no-write/dest
- mv: Failed moving 'src' to './1a_no-f_no-write/dest': Permission denied
- [1]
- $ test ! -e src
- [1]
- $ rm -fr 1a_no-f_no-write
-
-POSIX mv(1) step 1b, no -f option, -i passed
- $ mkdir 1a_no-f_write
- $ touch src 1a_no-f_write/dest
- $ printf '\n' | mv -i src 1a_no-f_write/dest
- mv: Destination file './1a_no-f_write/dest' already exists, overwrite? [yN]
- mv: Got empty response, considering it false
- $ test -e src
- $ test -d 1a_no-f_write
- $ printf 'n\n' | mv -i src 1a_no-f_write/dest
- mv: Destination file './1a_no-f_write/dest' already exists, overwrite? [yN] n
- $ test -f src
- $ test -d 1a_no-f_write
- $ printf 'y\n' | mv -i src 1a_no-f_write/dest
- mv: Destination file './1a_no-f_write/dest' already exists, overwrite? [yN] y
- $ test ! -e src
- $ test -d 1a_no-f_write
- $ rm -fr 1a_no-f_write
-
-POSIX mv(1) step 2, same file
- $ touch same
- $ mv same same
- mv: Error, passed to both source and destination: 'same'
- [1]
- $ test -e same
- $ ln same Same
- $ mv same Same
- $ test -e same
- $ test -e Same
- $ ln -s same same-s
- $ mv same same-s
- $ test -e same
- $ test -e Same
- $ test -e same-s
- $ mv Same same-s
- $ test -e same
- $ test -e Same
- $ test -e same-s
- $ rm same Same same-s
-
-Where destination is an existing directory
- $ mkdir destdir
- $ touch foo
- $ mv foo destdir
- $ test ! -e foo
- $ test -d destdir
- $ test -f destdir/foo
- $ rm -r destdir
-
- $ mkdir destdir_trail/
- $ touch foo_trail
- $ mv foo_trail destdir_trail/
- $ test ! -e foo_trail
- $ test -d destdir_trail
- $ test -f destdir_trail/foo_trail
- $ rm -r destdir_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 opening destination directory '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
- mv: renamed 'foo' -> './bar'
- $ test ! -e foo
- $ test -e bar
- $ rm bar
-
-Last component used for destination filename
- $ mkdir -p src_last/dir dest_last
- $ touch src_last/dir/file
- $ mv src_last/dir/file dest_last/
- $ test -f dest_last/file
- $ test ! -e dest_last/dir/file
- $ test ! -e src_last/dir/file
- $ mv src_last/dir dest_last
- $ test -d dest_last/dir
- $ test ! -e src_last/dir
- $ test -f dest_last/file
- $ rm -r src_last dest_last
-
-No files should be left
- $ find .
- .