From f3d1b9d0e4a6ecd93f42a0cf529673d0cef25e23 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Fri, 7 Mar 2014 00:07:23 +0100 Subject: [PATCH] Improve mv(1). --- utils/mv.cpp | 233 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 226 insertions(+), 7 deletions(-) diff --git a/utils/mv.cpp b/utils/mv.cpp index caed6b14..b5ec3895 100644 --- a/utils/mv.cpp +++ b/utils/mv.cpp @@ -1,6 +1,6 @@ /******************************************************************************* - Copyright(C) Jonas 'Sortie' Termansen 2013. + Copyright(C) Jonas 'Sortie' Termansen 2013, 2014. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free @@ -20,18 +20,237 @@ *******************************************************************************/ +#include + #include #include +#include +#include #include +#include +#include +#include + +char* basename_dup(const char* path) +{ + char* copy = strdup(path); + if ( !copy ) + return (char*) NULL; + char* result = strdup(basename(copy)); + free(copy); + if ( !result ) + return (char*) NULL; + return result; +} + +const int FLAG_VERBOSE = 1 << 0; +const int FLAG_ASK_OVERWRITE = 1 << 1; + +bool mv(int src_dirfd, const char* src_rel, const char* src_path, + int dst_dirfd, const char* dst_rel, const char* dst_path, + int flags) +{ + if ( flags & FLAG_VERBOSE ) + printf("`%s' -> `%s'\n", src_path, dst_path); + if ( flags & FLAG_ASK_OVERWRITE ) + { + // TODO: Ask for confirmation if needed. + } + if ( renameat(src_dirfd, src_rel, dst_dirfd, dst_rel) < 0 ) + { + error(0, errno, "renaming `%s' to `%s'", src_path, dst_path); + return false; + } + return true; +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +static void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... [-T] SOURCE DEST\n", argv0); + fprintf(fp, " or: %s [OPTION]... SOURCE... DIRECTORY\n", argv0); + fprintf(fp, " or: %s [OPTION]... -t DIRECTORY SOURCE...\n", argv0); +} + +static void version(FILE* fp, const char* argv0) +{ + fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR); + fprintf(fp, "License GPLv3+: GNU GPL version 3 or later .\n"); + fprintf(fp, "This is free software: you are free to change and redistribute it.\n"); + fprintf(fp, "There is NO WARRANTY, to the extent permitted by law.\n"); +} int main(int argc, char* argv[]) { - if ( argc != 3 ) + int flags = 0; + bool flag_no_target_directory = false; + const char* target_directory = NULL; + + const char* argv0 = argv[0]; + for ( int i = 1; i < argc; i++ ) { - printf("Usage: %s \n", argv[0]); - return 0; + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + case 'f': flags &= ~FLAG_ASK_OVERWRITE; break; +#if 0 + case 'i': flags |= FLAG_ASK_OVERWRITE; break; + case 'n': flags |= FLAG_NO_CLOBBER; break; +#endif + case 't': + if ( *(arg + 1) ) + target_directory = arg + 1; + else if ( i + 1 == argc ) + { + error(0, 0, "option requires an argument -- '%c'", c); + fprintf(stderr, "Try '%s --help' for more information\n", argv0); + exit(1); + } + else + { + target_directory = argv[i+1]; + argv[++i] = NULL; + } + arg = "t"; + break; + case 'T': flag_no_target_directory = true; break; +#if 0 + case 'u': flags |= FLAG_UPDATE; break; +#endif + case 'v': flags |= FLAG_VERBOSE; break; + default: + fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--target-directory") ) + { + if ( i + 1 == argc ) + { + error(0, 0, "option '--target-directory 'requires an argument"); + fprintf(stderr, "Try '%s --help' for more information\n", argv0); + exit(1); + } + target_directory = argv[++i]; + argv[i] = NULL; + } + else if ( !strncmp(arg, "--target-directory=", strlen("--target-directory=")) ) + target_directory = arg + strlen("--target-directory="); + else if ( !strcmp(arg, "--no-target-directory") ) + flag_no_target_directory = true; + else if ( !strcmp(arg, "--help") ) + help(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--version") ) + version(stdout, argv0), exit(0); + else + { + fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); + help(stderr, argv0); + exit(1); + } } - if ( rename(argv[1], argv[2]) != 0 ) - error(1, errno, "rename `%s' to `%s'", argv[1], argv[2]); - return 0; + + if ( target_directory && flag_no_target_directory ) + error(1, 0, "cannot combine --target-directory (-t) and --no-target-directory (-T)"); + + compact_arguments(&argc, &argv); + + if ( argc < 2 ) + error(1, 0, "missing file operand"); + + if ( !target_directory && !flag_no_target_directory && argc == 3 ) + { + const char* dst = argv[2]; + struct stat dst_st; + if ( stat(dst, &dst_st) < 0 ) + flag_no_target_directory = true; + else if ( !S_ISDIR(dst_st.st_mode) ) + flag_no_target_directory = true; + } + + if ( flag_no_target_directory ) + { + const char* src = argv[1]; + if ( argc < 3 ) + error(1, 0, "missing destination file operand after `%s'", src); + const char* dst = argv[2]; + if ( 3 < argc ) + error(1, 0, "extra operand `%s'", argv[3]); + return mv(AT_FDCWD, src, src, + AT_FDCWD, dst, dst, + flags) ? 0 : 1; + } + + if ( !target_directory ) + { + target_directory = argv[--argc]; + argv[argc] = NULL; + } + + int dst_dirfd = open(target_directory, O_RDONLY | O_DIRECTORY); + if ( dst_dirfd < 0 ) + error(1, errno, "`%s'", target_directory); + + if ( argc < 2 ) + error(1, 0, "missing file operand"); + + const char* join = "/"; + if ( strlen(target_directory) && + target_directory[strlen(target_directory)-1] == '/' ) + join = ""; + + bool success = true; + for ( int i = 1; i < argc; i++ ) + { + const char* src = argv[i]; + int src_dirfd = AT_FDCWD; + const char* src_rel = src; + const char* src_path = src; + char* dst_rel = basename_dup(src_path); + if ( !dst_rel ) + { + error(0, errno, "strdup"); + success = false; + continue; + } + char* dst_path = NULL; + asprintf(&dst_path, "%s%s%s", target_directory, join, dst_rel); + if ( !dst_path ) + { + error(0, errno, "asprintf"); + free(dst_rel); + success = false; + continue; + } + if ( !mv(src_dirfd, src_rel, src_path, + dst_dirfd, dst_rel, dst_path, + flags) ) + success = false; + free(dst_path); + free(dst_rel); + } + + close(dst_dirfd); + + return success ? 0 : 1; }