coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] rename: move command from util-linux to coreutils


From: Sami Kerola
Subject: Re: [PATCH] rename: move command from util-linux to coreutils
Date: Tue, 24 Dec 2013 22:29:40 +0000

Seasons greetings,

This is a resubmission[1][2] of the rename(1), with attempt to move it
from util-linux package to coreutils.  Various compiler warnings are
removed, make syntax-check passes, --test option is renamed to --dry-run,
file list can be wrote to stdin but only if --files0-from is in use.
AFAIK most of the issues mentioned earlier are sorted, and it is either
time to get more advice what could be improved, or final reject for the
proposal to move the command to this project.

[1] http://lists.gnu.org/archive/html/coreutils/2012-06/threads.html#00021
[2] http://lists.gnu.org/archive/html/coreutils/2012-07/threads.html#00014

On 24 December 2013 22:28, Sami Kerola <address@hidden> wrote:
> The implementation is completely rewrote without chaning existing
> command line syntax.  This implementation adds --exec option, which
> will allow use of a string manipulation command, such as 'tr' or 'sed'
> to determine target names or rename operations.
>
> * AUTHORS: Add my name.
> * NEWS: Mention the new program.
> * README: Reference the new program.
> * build-aux/gen-lists-of-programs.sh: Update.
> * doc/coreutils.texi: Document the new command.
> * man/.gitignore: Ignore the new man page.
> * man/local.mk: Reference the new man page.
> * man/rename.x: A new template.
> * po/POTFILES.in: Add new c file.
> * scripts/git-hooks/commit-msg: Allow rename: commit prefix.
> * src/.gitignore: Ignore the new binary.
> * src/local.mk: Reference the new command.
> * src/rename.c: New file.
> * tests/local.mk: Reference the new tests.
> * tests/misc/rename-exec: Test new command exec option.
> * tests/misc/rename-foobar: Basic test for the new command.
> ---
>  AUTHORS                            |   1 +
>  NEWS                               |   5 +
>  README                             |   8 +-
>  build-aux/gen-lists-of-programs.sh |   1 +
>  doc/coreutils.texi                 | 117 ++++++-
>  man/.gitignore                     |   1 +
>  man/local.mk                       |   1 +
>  man/rename.x                       |  10 +
>  po/POTFILES.in                     |   1 +
>  scripts/git-hooks/commit-msg       |  13 +-
>  src/.gitignore                     |   1 +
>  src/local.mk                       |   3 +
>  src/rename.c                       | 659 
> +++++++++++++++++++++++++++++++++++++
>  tests/local.mk                     |   2 +
>  tests/misc/rename-exec.sh          |  30 ++
>  tests/misc/rename-foo2bar.sh       |  30 ++
>  16 files changed, 872 insertions(+), 11 deletions(-)
>  create mode 100644 man/rename.x
>  create mode 100644 src/rename.c
>  create mode 100755 tests/misc/rename-exec.sh
>  create mode 100755 tests/misc/rename-foo2bar.sh
>
> diff --git a/AUTHORS b/AUTHORS
> index df21e90..e2ae037 100644
> --- a/AUTHORS
> +++ b/AUTHORS
> @@ -64,6 +64,7 @@ ptx: François Pinard
>  pwd: Jim Meyering
>  readlink: Dmitry V. Levin
>  realpath: Pádraig Brady
> +rename: Sami Kerola
>  rm: Paul Rubin, David MacKenzie, Richard M. Stallman, Jim Meyering
>  rmdir: David MacKenzie
>  runcon: Russell Coker
> diff --git a/NEWS b/NEWS
> index cbac480..212b697 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -2,6 +2,11 @@ GNU coreutils NEWS                                    -*- 
> outline -*-
>
>  * Noteworthy changes in release ?.? (????-??-??) [?]
>
> +** New programs
> +
> +  rename: Rename multiple files using a pattern.  This command will replace
> +  an utility with same name in util-linux package.
> +
>
>  * Noteworthy changes in release 8.22 (2013-12-13) [stable]
>
> diff --git a/README b/README
> index b81897e..4f65819 100644
> --- a/README
> +++ b/README
> @@ -12,10 +12,10 @@ The programs that can be built with this package are:
>    factor false fmt fold groups head hostid hostname id install join kill
>    link ln logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup
>    nproc numfmt od paste pathchk pinky pr printenv printf ptx pwd readlink
> -  realpath rm rmdir runcon seq sha1sum sha224sum sha256sum sha384sum 
> sha512sum
> -  shred shuf sleep sort split stat stdbuf stty sum sync tac tail tee test
> -  timeout touch tr true truncate tsort tty uname unexpand uniq unlink
> -  uptime users vdir wc who whoami yes
> +  realpath rename rm rmdir runcon seq sha1sum sha224sum sha256sum
> +  sha384sum sha512sum shred shuf sleep sort split stat stdbuf stty sum
> +  sync tac tail tee test timeout touch tr true truncate tsort tty uname
> +  unexpand uniq unlink uptime users vdir wc who whoami yes
>
>  See the file NEWS for a list of major changes in the current release.
>
> diff --git a/build-aux/gen-lists-of-programs.sh 
> b/build-aux/gen-lists-of-programs.sh
> index bf63ee3..2962a82 100755
> --- a/build-aux/gen-lists-of-programs.sh
> +++ b/build-aux/gen-lists-of-programs.sh
> @@ -96,6 +96,7 @@ normal_progs='
>      pwd
>      readlink
>      realpath
> +    rename
>      rm
>      rmdir
>      runcon
> diff --git a/doc/coreutils.texi b/doc/coreutils.texi
> index 9c2b79c..27a3602 100644
> --- a/doc/coreutils.texi
> +++ b/doc/coreutils.texi
> @@ -96,6 +96,7 @@
>  * pwd: (coreutils)pwd invocation.               Print working directory.
>  * readlink: (coreutils)readlink invocation.     Print referent of a symlink.
>  * realpath: (coreutils)readpath invocation.     Print resolved file names.
> +* rename: (coreutils)rename invocation.         Rename multiple files.
>  * rm: (coreutils)rm invocation.                 Remove files.
>  * rmdir: (coreutils)rmdir invocation.           Remove empty directories.
>  * runcon: (coreutils)runcon invocation.         Run in specified SELinux CTX.
> @@ -195,7 +196,7 @@ Free Documentation License''.
>  * Printing text::                echo printf yes
>  * Conditions::                   false true test expr
>  * Redirection::                  tee
> -* File name manipulation::       dirname basename pathchk mktemp realpath
> +* File name manipulation::       dirname basename pathchk rename mktemp 
> realpath
>  * Working context::              pwd stty printenv tty
>  * User information::             id logname whoami groups users who
>  * System context::               date arch nproc uname hostname hostid uptime
> @@ -382,6 +383,7 @@ File name manipulation
>  * basename invocation::          Strip directory and suffix from a file name
>  * dirname invocation::           Strip last file name component
>  * pathchk invocation::           Check file name validity and portability
> +* rename invocation::            Rename multiple files using a pattern
>  * mktemp invocation::            Create temporary file or directory
>  * realpath invocation::          Print resolved file names
>
> @@ -13116,6 +13118,7 @@ This section describes commands that manipulate file 
> names.
>  * basename invocation::         Strip directory and suffix from a file name.
>  * dirname invocation::          Strip last file name component.
>  * pathchk invocation::          Check file name validity and portability.
> +* rename invocation::           Rename multiple files using a pattern.
>  * mktemp invocation::           Create temporary file or directory.
>  * realpath invocation::         Print resolved file names.
>  @end menu
> @@ -13340,6 +13343,118 @@ Exit status:
>  1 otherwise.
>  @end display
>
> +
> +@node rename invocation
> +@section @command{rename}: Rename multiple files using a pattern
> +
> +@pindex rename
> +@cindex rename multiple files
> +
> +@command{rename} moves multiple files using a pattern. Synopsis:
> +
> +@example
> +rename [@var{option}] @var{expression} @var{replacement} @var{file...}
> +rename [@var{option}] --files0-from=FILE  @var{--exec} @var{command}
> +find . -print0 \
> +  | rename [@var{option}] @var{expression} @var{replacement}
> +find . -print0 \
> +  | rename [@var{option}] @var{--exec} @var{command}
> +@end example
> +
> +The @command{rename} command does multiple @var{file} renames same way as
> +@command{mv} would do individually. The @command{rename} will not rename
> +directory names, nor move files out of directory in which they are.
> +
> +The program accepts the following options.  Also see @ref{Common options}.
> +
> +@table @samp
> +
> +@filesZeroFromOption{rename}
> +
> +
> +@item -e @var{command -o --option}
> +@itemx --exec @var{command -o --option}
> +@opindex -e
> +@opindex --exec
> +@cindex external execution
> +Use @command{command -o --option} to manipulate file name string.
> +@smallexample
> +# Show how ba would be removed from bash
> +find /bin/ba* -print0 | rename -t -e sed 's/bash/sh/'
> +@end smallexample
> +
> +
> +@item -R
> +@itemx -r
> +@itemx --recursive
> +@opindex -R
> +@opindex -r
> +@opindex --recursive
> +@cindex recursive renaming
> +Rename files recursively.
> +
> +@item -x
> +@itemx --one-file-system
> +@opindex -x
> +@opindex --one-file-system
> +@cindex file systems, omitting renaming to different
> +Skip subdirectories that are on different file systems from the one that
> +the rename started on.
> +
> +@optBackup
> +
> +@optBackupSuffix
> +
> +@item -f
> +@itemx --force
> +@opindex -f
> +@opindex --force
> +@cindex prompts, omitting
> +Do not prompt the user before removing a destination file.
> +@mvOptsIfn
> +
> +@item -i
> +@itemx --interactive
> +@opindex -i
> +@opindex --interactive
> +@cindex prompts, forcing
> +Prompt whether to overwrite each existing destination file, regardless
> +of its permissions.
> +If the response is not affirmative, the file is skipped.
> +@mvOptsIfn
> +
> +@item -n
> +@itemx --no-clobber
> +@opindex -n
> +@opindex --no-clobber
> +@cindex prompts, omitting
> +Do not overwrite an existing file.
> +@mvOptsIfn
> +This option is mutually exclusive with @option{-b} or @option{--backup} 
> option.
> +
> +@item -t
> +@itemx --test
> +@opindex -t
> +@opindex --test
> +Show printout how @command{rename} would affect without performing any 
> changes.
> +
> +@item -v
> +@itemx --verbose
> +@opindex -v
> +@opindex --verbose
> +Print the name of each file before moving it.
> +
> +@end table
> +
> +@cindex exit status of @command{rename}
> +Exit status:
> +
> +@display
> +0 all files where renamed succesfully
> +1 otherwise.
> +@end display
> +
> +
>  @node mktemp invocation
>  @section @command{mktemp}: Create temporary file or directory
>
> diff --git a/man/.gitignore b/man/.gitignore
> index aef4002..b2993c5 100644
> --- a/man/.gitignore
> +++ b/man/.gitignore
> @@ -61,6 +61,7 @@ ptx.1
>  pwd.1
>  readlink.1
>  realpath.1
> +rename.1
>  rm.1
>  rmdir.1
>  seq.1
> diff --git a/man/local.mk b/man/local.mk
> index 45dbcb9..c228c5a 100644
> --- a/man/local.mk
> +++ b/man/local.mk
> @@ -127,6 +127,7 @@ man/ptx.1:       src/ptx
>  man/pwd.1:       src/pwd
>  man/readlink.1:  src/readlink
>  man/realpath.1:  src/realpath
> +man/rename.1:    src/rename
>  man/rm.1:        src/rm
>  man/rmdir.1:     src/rmdir
>  man/runcon.1:    src/runcon
> diff --git a/man/rename.x b/man/rename.x
> new file mode 100644
> index 0000000..f44af45
> --- /dev/null
> +++ b/man/rename.x
> @@ -0,0 +1,10 @@
> +[NAME]
> +rename \- rename multiple files using a pattern
> +[DESCRIPTION]
> +.\" Add any additional description here
> +[EXAMPLES]
> +.B rename .htm .html *.htm
> +.br
> +This will move all files ending .htm to end with .html in current directory.
> +[SEE ALSO]
> +rename(2)
> diff --git a/po/POTFILES.in b/po/POTFILES.in
> index 5c219d9..a009f50 100644
> --- a/po/POTFILES.in
> +++ b/po/POTFILES.in
> @@ -106,6 +106,7 @@ src/readlink.c
>  src/realpath.c
>  src/relpath.c
>  src/remove.c
> +src/rename.c
>  src/rm.c
>  src/rmdir.c
>  src/runcon.c
> diff --git a/scripts/git-hooks/commit-msg b/scripts/git-hooks/commit-msg
> index 559a0ea..0bb5d1a 100755
> --- a/scripts/git-hooks/commit-msg
> +++ b/scripts/git-hooks/commit-msg
> @@ -17,12 +17,13 @@ my @valid = qw(
>      arch base64 basename cat chcon chgrp chmod chown chroot cksum comm
>      cp csplit cut date dd df dir dircolors dirname du echo env expand
>      expr factor false fmt fold groups head hostid hostname id install
> -    join kill link ln logname ls md5sum mkdir mkfifo mknod mktemp
> -    mv nice nl nohup nproc numfmt od paste pathchk pinky pr printenv printf
> -    ptx pwd readlink realpath rm rmdir runcon seq sha1sum sha224sum sha256sum
> -    sha384sum sha512sum shred shuf sleep sort split stat stdbuf stty
> -    sum sync tac tail tee test timeout touch tr true truncate tsort
> -    tty uname unexpand uniq unlink uptime users vdir wc who whoami yes
> +    join kill link ln logname ls md5sum mkdir mkfifo mknod mktemp mv
> +    nice nl nohup nproc numfmt od paste pathchk pinky pr printenv
> +    printf ptx pwd readlink realpath rename rm rmdir runcon seq sha1sum
> +    sha224sum sha256sum sha384sum sha512sum shred shuf sleep sort split
> +    stat stdbuf stty sum sync tac tail tee test timeout touch tr true
> +    truncate tsort tty uname unexpand uniq unlink uptime users vdir wc
> +    who whoami yes
>
>      copy gnulib tests maint doc build scripts
>      );
> diff --git a/src/.gitignore b/src/.gitignore
> index 25573df..43895af 100644
> --- a/src/.gitignore
> +++ b/src/.gitignore
> @@ -71,6 +71,7 @@ ptx
>  pwd
>  readlink
>  realpath
> +rename
>  rm
>  rmdir
>  runcon
> diff --git a/src/local.mk b/src/local.mk
> index fe80f41..4f92d61 100644
> --- a/src/local.mk
> +++ b/src/local.mk
> @@ -136,6 +136,7 @@ src_link_LDADD = $(LDADD)
>  src_ln_LDADD = $(LDADD)
>  src_logname_LDADD = $(LDADD)
>  src_ls_LDADD = $(LDADD)
> +src_rename_LDADD = $(LDADD)
>
>  # This must *not* depend on anything in lib/, since it is used to generate
>  # src/primes.h.  If it depended on libcoreutils.a, that would pull all 
> lib/*.c
> @@ -213,6 +214,7 @@ src_vdir_LDADD = $(src_ls_LDADD)
>  src_cp_LDADD += $(copy_ldadd)
>  src_ginstall_LDADD += $(copy_ldadd)
>  src_mv_LDADD += $(copy_ldadd)
> +src_rename_LDADD += $(copy_ldadd)
>
>  src_mv_LDADD += $(remove_ldadd)
>  src_rm_LDADD += $(remove_ldadd)
> @@ -356,6 +358,7 @@ src_realpath_SOURCES = src/realpath.c src/relpath.c 
> src/relpath.h
>  src_timeout_SOURCES = src/timeout.c src/operand2sig.c
>
>  src_mv_SOURCES = src/mv.c src/remove.c $(copy_sources) $(selinux_sources)
> +src_rename_SOURCES = src/rename.c $(copy_sources)
>  src_rm_SOURCES = src/rm.c src/remove.c
>
>  src_mkdir_SOURCES = src/mkdir.c src/prog-fprintf.c $(selinux_sources)
> diff --git a/src/rename.c b/src/rename.c
> new file mode 100644
> index 0000000..e800f43
> --- /dev/null
> +++ b/src/rename.c
> @@ -0,0 +1,659 @@
> +/* rename -- rename multiple files using a pattern
> +   Copyright (C) 2013- Free Software Foundation, Inc.
> +
> +   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 Software Foundation, either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* This command supports the same syntax as util-linux rename, which was
> +   written by Andries Brouwer at Jan 1st, 2000.  The command appeared to
> +   coreutils as the util-linux package is less obvious package to ship a
> +   command such as this.
> +
> +   The coreutils implementation was done by Sami Kerola.  */
> +
> +#include <config.h>
> +
> +#include <assert.h>             /* for assert */
> +#include <getopt.h>             /* for no_argument, optind, optarg, etc */
> +#include <libintl.h>            /* for bindtextdomain, textdomain */
> +#include <selinux/selinux.h>    /* for is_selinux_enabled */
> +#include <stdio.h>              /* for NULL, stdout, fprintf, etc */
> +#include <sys/param.h>          /* for MIN */
> +#include <sys/wait.h>           /* for waitpid */
> +
> +#include "argv-iter.h"          /* for argv_iter_init_argv, etc */
> +#include "backupfile.h"         /* for xget_version, etc */
> +#include "full-write.h"         /* for full_write */
> +#include "copy.h"               /* for cp_options, etc */
> +#include "cp-hash.h"            /* for hash_init */
> +#include "error.h"              /* for error */
> +#include "fts_.h"               /* for FTS, FTSENT, FTS_PHYSICAL, etc */
> +#include "physmem.h"            /* for physmem_available */
> +#include "quotearg.h"           /* for quotearg_colon */
> +#include "quote.h"              /* for quote */
> +#include "readtokens0.h"        /* for Tokens, readtokens0, etc */
> +#include "system.h"             /* for _, STREQ, etc */
> +#include "xfts.h"               /* for xfts_open */
> +
> +/* The official name of this program (e.g., no `g' prefix).  */
> +#define PROGRAM_NAME "rename"
> +
> +#define AUTHORS \
> +  proper_name ("Sami Kerola")
> +
> +#if RENAME_DEBUG
> +#define FTS_CROSS_CHECK(Fts) fts_cross_check (Fts)
> +#else
> +#define FTS_CROSS_CHECK(Fts)
> +#endif
> +
> +/* True if we have ever read the standard input. */
> +static bool have_read_stdin;
> +
> +/* If true change the modes of directories recursively. */
> +static bool recurse;
> +
> +/* If true only print out what a rename would do.  */
> +static bool test_only = false;
> +
> +/* If set tradional string + replacement is wanted */
> +static char *original_str;
> +static char *replacement;
> +
> +/* The exec is using these.  */
> +static char *cmd_to_run = NULL;
> +static char **cmd_args = NULL;
> +
> +static void
> +cp_option_init (struct cp_options *x)
> +{
> +  bool selinux_enabled = (0 < is_selinux_enabled ());
> +
> +  cp_options_default (x);
> +  x->copy_as_regular = false;
> +  x->reflink_mode = REFLINK_NEVER;
> +  x->dereference = DEREF_NEVER;
> +  x->unlink_dest_before_opening = false;
> +  x->unlink_dest_after_failed_open = false;
> +  x->hard_link = false;
> +  x->interactive = I_UNSPECIFIED;
> +  x->move_mode = true;
> +  x->one_file_system = false;
> +  x->preserve_ownership = true;
> +  x->preserve_links = true;
> +  x->preserve_mode = true;
> +  x->preserve_timestamps = true;
> +  x->preserve_security_context = selinux_enabled;
> +  x->reduce_diagnostics = false;
> +  x->data_copy_required = true;
> +  x->require_preserve = false;
> +  x->require_preserve_context = false;
> +  x->preserve_xattr = true;
> +  x->require_preserve_xattr = false;
> +  x->recursive = false;
> +  x->sparse_mode = SPARSE_AUTO;
> +  x->symbolic_link = false;
> +  x->set_mode = false;
> +  x->mode = 0;
> +  x->stdin_tty = isatty (STDIN_FILENO);
> +
> +  x->open_dangling_dest_symlink = false;
> +  x->update = false;
> +  x->verbose = false;
> +  x->dest_info = NULL;
> +  x->src_info = NULL;
> +}
> +
> +/* Change first occurence of 'substr' to be 'replacement' in
> +   'filename'.  This function is used when classical rename is been
> +   queried.  */
> +static char *
> +construct_dest_name (const char *filename, const char *substr, const char 
> *repl)
> +{
> +  char *tok = NULL;
> +  char *newstr = NULL;
> +  char *basename;
> +
> +  /* Search if file name has path.  */
> +  basename = strrchr (filename, '/');
> +  if (basename == NULL)
> +    tok = strstr (filename, substr);
> +  else
> +    tok = strstr (basename, substr);
> +  if (tok == NULL)
> +    return xstrdup (filename);
> +
> +  newstr = xmalloc (strlen (filename) - strlen (substr) + strlen (repl) + 1);
> +
> +  /* Construct new file name.  */
> +  if (newstr == NULL)
> +    return NULL;
> +  memcpy (newstr, filename, tok - filename);
> +  memcpy (newstr + (tok - filename), repl, strlen (repl));
> +  memcpy (newstr + (tok - filename) + strlen (repl),
> +          tok + strlen (substr),
> +          strlen (filename) - strlen (substr) - (tok - filename));
> +  memset (newstr + strlen (filename) - strlen (substr) + strlen (repl), 0, 
> 1);
> +  return newstr;
> +}
> +
> +/* Child will perform filename altering.  */
> +static char *
> +request_child (const char *filename)
> +{
> +  pid_t exec_pid;
> +  FILE *mother_file = NULL;
> +  int child2mother_pipefd[2];
> +  int mother2child_pipefd[2];
> +  int status = EXIT_SUCCESS;
> +  char *ret = NULL;
> +  char *basename;
> +  size_t skip = 0;
> +
> +  signal (SIGPIPE, SIG_IGN);
> +  errno = 0;
> +  if (pipe (mother2child_pipefd) != 0)
> +    error (EXIT_FAILURE, errno, _("failed to create pipe"));
> +  if (pipe (child2mother_pipefd) != 0)
> +    error (EXIT_FAILURE, errno, _("failed to create pipe"));
> +
> +  exec_pid = fork ();
> +  if (0 < exec_pid)
> +    {
> +      /* Mother.  */
> +      if (close (mother2child_pipefd[0]) != 0)
> +        error (EXIT_FAILURE, errno, _("failed to close pipe"));
> +      if (close (child2mother_pipefd[1]) != 0)
> +        error (EXIT_FAILURE, errno, _("failed to close pipe"));
> +      mother_file = fdopen (child2mother_pipefd[0], "r");
> +    }
> +  if (0 == exec_pid)
> +    {
> +      /* Child.  */
> +      if (close (STDIN_FILENO) != 0)
> +        error (EXIT_FAILURE, errno, _("read error"));
> +      if (close (STDOUT_FILENO) != 0)
> +        error (EXIT_FAILURE, errno, _("write error"));
> +      if (close (mother2child_pipefd[1]) != 0)
> +        error (EXIT_FAILURE, errno, _("failed to close pipe"));
> +      if (close (child2mother_pipefd[0]) != 0)
> +        error (EXIT_FAILURE, errno, _("failed to close pipe"));
> +
> +      /* Associate input and output with a pipe.  */
> +      if (dup2 (mother2child_pipefd[0], STDIN_FILENO) == -1)
> +        error (EXIT_FAILURE, errno, _("moving input pipe"));
> +      if (dup2 (child2mother_pipefd[1], STDOUT_FILENO) == -1)
> +        error (EXIT_FAILURE, errno, _("moving output pipe"));
> +
> +      if (execvp (cmd_to_run, cmd_args) != 0)
> +        error (0, errno, _("executing file %s failed"), quote (cmd_to_run));
> +      /* Never reached after successful execvp.  */
> +      exit (EXIT_FAILURE);
> +    }
> +
> +  /* Search if file name has path.  */
> +  basename = strrchr (filename, '/');
> +  if (basename != NULL)
> +    skip = strlen (filename) - strlen (basename);
> +  /* Communicate with the child.  */
> +  if (0 < skip)
> +    {
> +      if (full_write
> +          (mother2child_pipefd[1], basename,
> +           strlen (basename)) != (strlen (basename)) && errno != EPIPE)
> +        {
> +          error (EXIT_FAILURE, errno, _("write error"));
> +        }
> +    }
> +  else
> +    {
> +      if (full_write
> +          (mother2child_pipefd[1], filename,
> +           strlen (filename)) != (strlen (filename)) && errno != EPIPE)
> +        {
> +          error (EXIT_FAILURE, errno, _("write error"));
> +        }
> +    }
> +  if (close (mother2child_pipefd[1]) != 0)
> +    error (EXIT_FAILURE, errno, _("failed to close pipe"));
> +  /* Child working, prepare memory for new file name.  */
> +  ret = xmalloc ((size_t) PATH_MAX);
> +  memcpy (ret, filename, skip);
> +  /* Read what child had to tell.  */
> +  if (fgets (ret + skip, PATH_MAX - skip, mother_file) == NULL)
> +    error (EXIT_FAILURE, errno, _("reading executable conversion failed"));
> +  if (waitpid (exec_pid, &status, 0) == -1 && errno != ECHILD)
> +    error (EXIT_FAILURE, errno, _("waiting for child process"));
> +  /* Child is terminated.  */
> +  if (mother_file != NULL)
> +    if (fclose (mother_file) != 0)
> +      error (EXIT_FAILURE, errno, _("read error"));
> +  if (WEXITSTATUS (status) != EXIT_SUCCESS)
> +    {
> +      exit (EXIT_FAILURE);
> +    }
> +  return ret;
> +}
> +
> +/* Request subroutine about how file name should be affected, and
> +   perform rename file operation.  */
> +static bool
> +rename_entity (FTSENT *ent, struct cp_options *x)
> +{
> +  char *destination;
> +  bool copy_into_self;
> +  bool rename_succeeded;
> +  bool ok = true;
> +
> +  /* Ask for new name. */
> +  if (cmd_to_run != NULL)
> +    {
> +      destination = request_child (ent->fts_path);
> +    }
> +  else
> +    {
> +      destination =
> +        construct_dest_name (ent->fts_path, original_str, replacement);
> +    }
> +
> +  /* Perform move operation. */
> +  if (test_only == true)
> +    printf ("%s -> %s\n", ent->fts_path, destination);
> +  else
> +    ok &=
> +      copy (ent->fts_path, destination, false, x, &copy_into_self,
> +            &rename_succeeded);
> +
> +  if (ok)
> +    {
> +      if (copy_into_self)
> +        {
> +          ok = false;
> +        }
> +    }
> +  free (destination);
> +  return ok;
> +}
> +
> +/* Read entities from directory, and request renaming.  */
> +static bool
> +prepare_rename (char **files, int bit_flags, struct cp_options *x)
> +{
> +  bool ok = true;
> +
> +  if (files != NULL)
> +    {
> +      FTS *fts = xfts_open (files, bit_flags, NULL);
> +
> +      while (1)
> +        {
> +          FTSENT *ent;
> +
> +          ent = fts_read (fts);
> +          if (ent == NULL)
> +            {
> +              if (errno != 0)
> +                {
> +                  error (0, errno, _("fts_read failed: %s"),
> +                         quotearg_colon (fts->fts_path));
> +                  ok = false;
> +                }
> +              break;
> +            }
> +          FTS_CROSS_CHECK (fts);
> +          if (!recurse)
> +            fts_set (fts, ent, FTS_SKIP);
> +          ok &= rename_entity (ent, x);
> +        }
> +
> +      if (fts_close (fts) != 0)
> +        {
> +          error (0, errno, _("fts_close failed"));
> +          ok = false;
> +        }
> +    }
> +
> +  return ok;
> +}
> +
> +void
> +usage (int status)
> +{
> +  if (status != EXIT_SUCCESS)
> +    emit_try_help ();
> +  else
> +    {
> +      printf (_("\
> +Usage: %s [OPTION]... EXPRESSION REPLACEMENT FILE...\n\
> +  or:  %s [OPTION] -0 FILE -e COMMAND [OPTION]\n\
> +"), program_name, program_name);
> +      fputs (_("\
> +Rename lots of files in directory.\n\
> +\n\
> +"), stdout);
> +      fputs (_("\
> +  -0, --files0-from=FILE  read rename entries from NUL-terminated file\n\
> +  -e, --exec=COMMAND      manipulate file names by using a command\n\
> +  -r, -R, --recursive     rename in directories recursively\n\
> +  -x, --one-file-system   skip directories on different file systems\n\
> +      --backup[=CONTROL]  make a backup of each existing destination file\n\
> +  -b                      like --backup but does not accept an argument\n\
> +  -S, --suffix=SUFFIX     override the usual backup suffix\n\
> +  -f, --force             do not prompt before overwriting\n\
> +  -i, --interactive       prompt before overwrite\n\
> +  -n, --no-clobber        do not overwrite an existing file\n\
> +If you specify more than one of -f, -i, -n, only the final one takes 
> effect.\n\
> +      --dry-run           show a dry-run print out\n\
> +  -v, --verbose           explain what is being done\n\
> +"), stdout);
> +      fputs (HELP_OPTION_DESCRIPTION, stdout);
> +      fputs (VERSION_OPTION_DESCRIPTION, stdout);
> +      /* FIXME: IMHO the messges bellow should be BACKUP_OPTION_DESCRIPTION
> +         similar to help and version.  */
> +      fputs (_("\
> +\n\
> +The backup suffix is `~', unless set with --suffix or 
> SIMPLE_BACKUP_SUFFIX.\n\
> +The version control method may be selected via the --backup option or 
> through\n\
> +the VERSION_CONTROL environment variable.  Here are the values:\n\
> +\n\
> +"), stdout);
> +      fputs (_("\
> +  none, off       never make backups (even if --backup is given)\n\
> +  numbered, t     make numbered backups\n\
> +  existing, nil   numbered if numbered backups exist, simple otherwise\n\
> +  simple, never   always make simple backups\n\
> +"), stdout);
> +      emit_ancillary_info ();
> +    }
> +  exit (status);
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  bool make_backups = false;
> +  int i, c;
> +  char *backup_suffix_string;
> +  char *version_control_string = NULL;
> +  struct cp_options x;
> +  char **files;
> +  static char const *files_from = NULL;
> +  bool ok;
> +  struct Tokens tok;
> +
> +  /* Bit flags that control how fts works.  */
> +  int bit_flags = FTS_NOSTAT;
> +  bit_flags |= FTS_PHYSICAL;
> +
> +  bool read_tokens = false;
> +  struct argv_iterator *ai;
> +
> +  /* for long options with no corresponding short option, use enum */
> +  enum
> +  {
> +    DRY_RUN_OPTION = CHAR_MAX + 1
> +  };
> +
> +  static const struct option longopts[] = {
> +    {"files0-from", optional_argument, NULL, '0'},
> +    {"backup", optional_argument, NULL, 'b'},
> +    {"dereference-args", no_argument, NULL, 'D'},
> +    {"exec", required_argument, NULL, 'e'},
> +    {"force", no_argument, NULL, 'f'},
> +    {"interactive", no_argument, NULL, 'i'},
> +    {"dereference", no_argument, NULL, 'L'},
> +    {"no-clobber", no_argument, NULL, 'n'},
> +    {"recursive", no_argument, NULL, 'r'},
> +    {"suffix", required_argument, NULL, 'S'},
> +    {"dry-run", no_argument, NULL, DRY_RUN_OPTION},
> +    {"verbose", no_argument, NULL, 'v'},
> +    {GETOPT_HELP_OPTION_DECL},
> +    {GETOPT_VERSION_OPTION_DECL},
> +    {NULL, 0, NULL, 0}
> +  };
> +
> +  initialize_main (&argc, &argv);
> +  set_program_name (argv[0]);
> +  setlocale (LC_ALL, "");
> +  bindtextdomain (PACKAGE, LOCALEDIR);
> +  textdomain (PACKAGE);
> +
> +  atexit (close_stdin);
> +
> +  cp_option_init (&x);
> +  recurse = false;
> +
> +  /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless
> +     we'll actually use backup_suffix_string.  */
> +  backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
> +
> +  while ((c =
> +          getopt_long (argc, argv, "0::b::e:finrS:v", longopts, NULL)) != -1)
> +    {
> +      switch (c)
> +        {
> +        case '0':
> +          if (files_from != NULL)
> +            error (0, 0, _("warning: input file %s will not be read"),
> +                   quote (files_from));
> +          if (optarg)
> +            files_from = optarg;
> +          else
> +            files_from = "-";
> +          break;
> +        case 'b':
> +          make_backups = true;
> +          if (optarg)
> +            version_control_string = optarg;
> +          break;
> +        case 'e':
> +          cmd_to_run = optarg;
> +          cmd_args = (argv + optind) - 1;
> +          optind = argc;
> +          if (files_from == NULL)
> +            files_from = "-";
> +          goto options_done;
> +        case 'f':
> +          x.interactive = I_ALWAYS_YES;
> +          break;
> +        case 'i':
> +          x.interactive = I_ASK_USER;
> +          break;
> +        case 'n':
> +          x.interactive = I_ALWAYS_NO;
> +          break;
> +        case 'r':
> +        case 'R':
> +          recurse = true;
> +          break;
> +        case 'v':
> +          x.verbose = true;
> +          break;
> +        case 'S':
> +          make_backups = true;
> +          backup_suffix_string = optarg;
> +          break;
> +        case DRY_RUN_OPTION:
> +          test_only = true;
> +          break;
> +
> +          case_GETOPT_HELP_CHAR;
> +          case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
> +        default:
> +          usage (EXIT_FAILURE);
> +        }
> +    }
> +options_done:
> +
> +  /* Check usage of backup options */
> +  if (make_backups && x.interactive == I_ALWAYS_NO)
> +    {
> +      error (0, 0,
> +             _("options --backup and --no-clobber are mutually exclusive"));
> +      usage (EXIT_FAILURE);
> +    }
> +
> +  if (backup_suffix_string)
> +    simple_backup_suffix = xstrdup (backup_suffix_string);
> +
> +  x.backup_type = (make_backups
> +                   ? xget_version (_("backup type"),
> +                                   version_control_string) : no_backups);
> +
> +  /* Command expects
> +     ++ match and replacement pair
> +     ++ or exec option
> +     accoppanied with file list
> +     ++ as command line argument(s)
> +     ++ or read from --files0-from=parameter
> +     ++ or read from stdin */
> +  if (optind < argc - 1)
> +    {
> +      original_str = argv[optind];
> +      replacement = argv[optind + 1];
> +      optind += 2;
> +    }
> +  if (original_str == NULL && cmd_to_run == NULL)
> +    {
> +      error (0, 0, _("not enough arguments"));
> +      usage (EXIT_FAILURE);
> +    }
> +
> +  hash_init ();
> +  if (files_from)
> +    {
> +      FILE *stream;
> +      /* When using --files0-from=F, you may not specify any files
> +         on the command-line.  */
> +      if (optind < argc)
> +        {
> +          error (0, 0, _("extra operand %s"), quote (argv[optind]));
> +          fprintf (stderr, "%s\n",
> +                   _("file operands cannot be combined with --files0-from"));
> +          usage (EXIT_FAILURE);
> +        }
> +
> +      if (STREQ (files_from, "-"))
> +        stream = stdin;
> +      else
> +        {
> +          stream = fopen (files_from, "r");
> +          if (stream == NULL)
> +            error (EXIT_FAILURE, errno, _("cannot open %s for reading"),
> +                   quote (files_from));
> +        }
> +
> +      /* Read the file list into RAM if we can detect its size and that
> +         size is reasonable.  Otherwise, we'll read a name at a time.  */
> +      struct stat st;
> +      if (fstat (fileno (stream), &st) == 0 && S_ISREG (st.st_mode)
> +          && st.st_size <= MIN (10 * 1024 * 1024, physmem_available () / 2))
> +        {
> +          read_tokens = true;
> +          readtokens0_init (&tok);
> +          if (!readtokens0 (stream, &tok) || fclose (stream) != 0)
> +            error (EXIT_FAILURE, 0, _("cannot read file names from %s"),
> +                   quote (files_from));
> +          files = tok.tok;
> +          ai = argv_iter_init_argv (files);
> +        }
> +      else
> +        {
> +          files = NULL;
> +          ai = argv_iter_init_stream (stream);
> +        }
> +    }
> +  else
> +    {
> +      static char *stdin_only[] = { NULL };
> +      files = (optind < argc ? argv + optind : stdin_only);
> +      ai = argv_iter_init_argv (files);
> +    }
> +
> +  if (!ai)
> +    xalloc_die ();
> +
> +  /* prepare_rename will not find cycles on its own, so ask fts_read
> +     to check for them accurately.  */
> +  bit_flags |= FTS_TIGHT_CYCLE_CHECK;
> +
> +  ok = true;
> +  for (i = 0; /* empty */ ; i++)
> +    {
> +      bool skip_file = false;
> +      enum argv_iter_err ai_err;
> +      char *input_path = argv_iter (ai, &ai_err);
> +      if (!input_path)
> +        {
> +          switch (ai_err)
> +            {
> +            case AI_ERR_EOF:
> +              goto argv_iter_done;
> +            case AI_ERR_READ:
> +              error (0, errno, _("%s: read error"),
> +                     quotearg_colon (files_from));
> +              ok = false;
> +              goto argv_iter_done;
> +            case AI_ERR_MEM:
> +              xalloc_die ();
> +            case AI_ERR_OK:
> +            default:
> +              assert (!"unexpected error code from argv_iter");
> +            }
> +        }
> +
> +      if (!input_path[0])
> +        {
> +          /* Diagnose a zero-length file name.  When it's one
> +             among many, knowing the record number may help.  */
> +          if (files_from == NULL)
> +            error (0, 0, "%s", _("invalid zero-length file name"));
> +          else
> +            {
> +              /* Using the standard `filename:line-number:' prefix here is
> +                 not totally appropriate, since NUL is the separator, not NL,
> +                 but it might be better than nothing.  */
> +              unsigned long int file_number = argv_iter_n_args (ai);
> +              error (0, 0, "%s:%lu: %s",
> +                     quotearg_colon (files_from), file_number,
> +                     _("invalid zero-length file name"));
> +            }
> +          skip_file = true;
> +        }
> +
> +      if (skip_file)
> +        ok = false;
> +      else
> +        {
> +          static char *temp_argv[] = { NULL, NULL };
> +          temp_argv[0] = input_path;
> +          ok &= prepare_rename (temp_argv, bit_flags, &x);
> +        }
> +    }
> +argv_iter_done:
> +
> +  /* No command line arguments.  Read from stdin.  However, no
> +     arguments with --files0-from is reason to skip this. */
> +  if (ok && !files_from && argv_iter_n_args (ai) == 0)
> +    ok &= prepare_rename (NULL, bit_flags, &x);
> +
> +  if (read_tokens)
> +    readtokens0_free (&tok);
> +
> +  argv_iter_free (ai);
> +
> +  if (have_read_stdin && close (STDIN_FILENO) != 0)
> +    error (EXIT_FAILURE, errno, "-");
> +
> +  exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
> +}
> diff --git a/tests/local.mk b/tests/local.mk
> index 9a54e7b..184a3ee 100644
> --- a/tests/local.mk
> +++ b/tests/local.mk
> @@ -301,6 +301,8 @@ all_tests =                                 \
>    tests/misc/readlink-fp-loop.sh               \
>    tests/misc/readlink-root.sh                  \
>    tests/misc/realpath.sh                       \
> +  tests/misc/rename-exec.sh                    \
> +  tests/misc/rename-foo2bar.sh                 \
>    tests/misc/runcon-no-reorder.sh              \
>    tests/misc/sha1sum.pl                                \
>    tests/misc/sha1sum-vec.pl                    \
> diff --git a/tests/misc/rename-exec.sh b/tests/misc/rename-exec.sh
> new file mode 100755
> index 0000000..4e892d0
> --- /dev/null
> +++ b/tests/misc/rename-exec.sh
> @@ -0,0 +1,30 @@
> +#!/bin/sh
> +# test rename basic functionality
> +
> +# Copyright (C) 2013 Free Software Foundation, Inc.
> +
> +# 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 Software Foundation, either version 3 of the License, or
> +# (at your option) any later version.
> +
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +. "${srcdir=.}/tests/init.sh"; path_prepend_ ../src
> +print_ver_ rename
> +
> +for i in foo1 foo2 ; do
> +  echo a > $i || framework_failure_
> +done
> +
> +find foo? -print0 | rename --exec tr a-z A-Z 2>/dev/null || fail=1
> +test -f FOO1 || fail=1
> +test -f FOO2 || fail=1
> +
> +Exit $fail
> diff --git a/tests/misc/rename-foo2bar.sh b/tests/misc/rename-foo2bar.sh
> new file mode 100755
> index 0000000..6463dfc
> --- /dev/null
> +++ b/tests/misc/rename-foo2bar.sh
> @@ -0,0 +1,30 @@
> +#!/bin/sh
> +# test rename basic functionality
> +
> +# Copyright (C) 2013 Free Software Foundation, Inc.
> +
> +# 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 Software Foundation, either version 3 of the License, or
> +# (at your option) any later version.
> +
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +. "${srcdir=.}/tests/init.sh"; path_prepend_ ../src
> +print_ver_ rename
> +
> +for i in foo1 foo2 ; do
> +  echo a > $i || framework_failure_
> +done
> +
> +rename foo bar foo? 2>/dev/null || fail=1
> +test -f bar1 || fail=1
> +test -f bar2 || fail=1
> +
> +Exit $fail
> --
> 1.8.5.2
>



-- 
Sami Kerola
http://www.iki.fi/kerolasa/



reply via email to

[Prev in Thread] Current Thread [Next in Thread]