logo

utils-std

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

Dcmd/mv.197-------------------------------------------------------------------------------
Dcmd/mv.c322-------------------------------------------------------------------------------
Mcoreutils.txt2+-
Mlsb_commands.txt2+-
Mposix_utilities.txt2+-
Dtest-cmd/mv.t140-------------------------------------------------------------------------------
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 . - .