(root)/
util-linux-2.39/
misc-utils/
rename.c
       1  /*
       2   * rename.c - aeb 2000-01-01
       3   *
       4  --------------------------------------------------------------
       5  #!/bin/sh
       6  if [ $# -le 2 ]; then echo call: rename from to files; exit; fi
       7  FROM="$1"
       8  TO="$2"
       9  shift
      10  shift
      11  for i in $@; do N=`echo "$i" | sed "s/$FROM/$TO/g"`; mv "$i" "$N"; done
      12  --------------------------------------------------------------
      13   * This shell script will do renames of files, but may fail
      14   * in cases involving special characters. Here a C version.
      15   */
      16  #include <stdio.h>
      17  #ifdef HAVE_STDIO_EXT_H
      18  #	include <stdio_ext.h>
      19  #endif
      20  #ifndef HAVE___FPURGE
      21  # ifdef HAVE_FPURGE
      22  #	define HAVE___FPURGE 1
      23  #	define __fpurge fpurge
      24  # endif
      25  #endif
      26  #include <string.h>
      27  #include <stdlib.h>
      28  #include <errno.h>
      29  #include <getopt.h>
      30  #include <fcntl.h>
      31  #include <unistd.h>
      32  #include <termios.h>
      33  #include <sys/types.h>
      34  #include <sys/stat.h>
      35  
      36  #include "nls.h"
      37  #include "xalloc.h"
      38  #include "c.h"
      39  #include "closestream.h"
      40  #include "optutils.h"
      41  #include "rpmatch.h"
      42  
      43  #define RENAME_EXIT_SOMEOK	2
      44  #define RENAME_EXIT_NOTHING	4
      45  #define RENAME_EXIT_UNEXPLAINED	64
      46  
      47  static int tty_cbreak = 0;
      48  static int all = 0;
      49  static int last = 0;
      50  
      51  /* Find the first place in `orig` where we'll perform a replacement. NULL if
      52     there are no replacements to do. */
      53  static char *find_initial_replace(char *from, char *to, char *orig)
      54  {
      55  	char *search_start = orig;
      56  
      57  	if (strchr(from, '/') == NULL && strchr(to, '/') == NULL) {
      58  		/* We only want to search in the final path component. Don't
      59  		   include the final '/' in that component; if `from` is empty,
      60  		   we want it to first match after the '/', not before. */
      61  		search_start = strrchr(orig, '/');
      62  
      63  		if (search_start == NULL)
      64  			search_start = orig;
      65  		else
      66  			search_start++;
      67  	}
      68  
      69  	return strstr(search_start, from);
      70  }
      71  
      72  static int string_replace(char *from, char *to, char *orig, char **newname)
      73  {
      74  	char *p, *q, *where;
      75  	size_t count = 0, fromlen = strlen(from);
      76  
      77  	p = where = find_initial_replace(from, to, orig);
      78  	if (where == NULL)
      79  		return 1;
      80  	count++;
      81  	while ((all || last) && p && *p) {
      82  		p = strstr(p + (last ? 1 : max(fromlen, (size_t) 1)), from);
      83  		if (p) {
      84  			if (all)
      85  				count++;
      86  			if (last)
      87  				where = p;
      88  		}
      89  	}
      90  	p = orig;
      91  	*newname = xmalloc(strlen(orig) - count * fromlen + count * strlen(to) + 1);
      92  	q = *newname;
      93  	while (count--) {
      94  		while (p < where)
      95  			*q++ = *p++;
      96  		p = to;
      97  		while (*p)
      98  			*q++ = *p++;
      99  		if (fromlen > 0) {
     100  			p = where + fromlen;
     101  			where = strstr(p, from);
     102  		} else {
     103  			p = where;
     104  			where += 1;
     105  		}
     106  	}
     107  	while (*p)
     108  		*q++ = *p++;
     109  	*q = 0;
     110  	return 0;
     111  }
     112  
     113  static int ask(char *name)
     114  {
     115  	int c;
     116  	char buf[2];
     117  	printf(_("%s: overwrite `%s'? "), program_invocation_short_name, name);
     118  	fflush(stdout);
     119  	if ((c = fgetc(stdin)) == EOF) {
     120  		buf[0] = 'n';
     121  		printf("n\n");
     122  	}
     123  	else {
     124  		buf[0] = c;
     125  		if (c != '\n' && tty_cbreak) {
     126  #ifdef HAVE___FPURGE
     127  			/* Possibly purge a multi-byte character; or do a
     128  			   required purge of the rest of the line (including
     129  			   the newline) if the tty has been put back in
     130  			   canonical mode (for example by a shell after a
     131  			   SIGTSTP signal). */
     132  			__fpurge(stdin);
     133  #endif
     134  			printf("\n");
     135  		}
     136  		else if (c != '\n')
     137  			while ((c = fgetc(stdin)) != '\n' && c != EOF);
     138  	}
     139  	buf[1] = '\0';
     140  	if (rpmatch(buf) == RPMATCH_YES)
     141  		return 0;
     142  
     143  	return 1;
     144  }
     145  
     146  static int do_symlink(char *from, char *to, char *s, int verbose, int noact,
     147                        int nooverwrite, int interactive)
     148  {
     149  	char *newname = NULL, *target = NULL;
     150  	int ret = 1;
     151  	ssize_t ssz;
     152  	struct stat sb;
     153  
     154  	if ( faccessat(AT_FDCWD, s, F_OK, AT_SYMLINK_NOFOLLOW) != 0 &&
     155  	     errno != EINVAL )
     156  	   /* Skip if AT_SYMLINK_NOFOLLOW is not supported; lstat() below will
     157  	      detect the access error */
     158  	{
     159  		warn(_("%s: not accessible"), s);
     160  		return 2;
     161  	}
     162  
     163  	if (lstat(s, &sb) == -1) {
     164  		warn(_("stat of %s failed"), s);
     165  		return 2;
     166  	}
     167  	if (!S_ISLNK(sb.st_mode)) {
     168  		warnx(_("%s: not a symbolic link"), s);
     169  		return 2;
     170  	}
     171  	target = xmalloc(sb.st_size + 1);
     172  
     173  	ssz = readlink(s, target, sb.st_size + 1);
     174  	if (ssz < 0) {
     175  		warn(_("%s: readlink failed"), s);
     176  		free(target);
     177  		return 2;
     178  	}
     179  	target[ssz] = '\0';
     180  
     181  	if (string_replace(from, to, target, &newname) != 0)
     182  		ret = 0;
     183  
     184  	if (ret == 1 && (nooverwrite || interactive) && lstat(newname, &sb) != 0)
     185  		nooverwrite = interactive = 0;
     186  
     187  	if ( ret == 1 &&
     188  	     (nooverwrite || (interactive && (noact || ask(newname) != 0))) )
     189  	{
     190  		if (verbose)
     191  			printf(_("Skipping existing link: `%s' -> `%s'\n"), s, target);
     192  		ret = 0;
     193  	}
     194  
     195  	if (ret == 1) {
     196  		if (!noact && 0 > unlink(s)) {
     197  			warn(_("%s: unlink failed"), s);
     198  			ret = 2;
     199  		}
     200  		else if (!noact && symlink(newname, s) != 0) {
     201  			warn(_("%s: symlinking to %s failed"), s, newname);
     202  			ret = 2;
     203  		}
     204  	}
     205  	if (verbose && (noact || ret == 1))
     206  		printf("%s: `%s' -> `%s'\n", s, target, newname);
     207  	free(newname);
     208  	free(target);
     209  	return ret;
     210  }
     211  
     212  static int do_file(char *from, char *to, char *s, int verbose, int noact,
     213                     int nooverwrite, int interactive)
     214  {
     215  	char *newname = NULL;
     216  	int ret = 1;
     217  	struct stat sb;
     218  
     219  	if ( faccessat(AT_FDCWD, s, F_OK, AT_SYMLINK_NOFOLLOW) != 0 &&
     220  	     errno != EINVAL )
     221  	   /* Skip if AT_SYMLINK_NOFOLLOW is not supported; lstat() below will
     222  	      detect the access error */
     223  	{
     224  		warn(_("%s: not accessible"), s);
     225  		return 2;
     226  	}
     227  
     228  	if (lstat(s, &sb) == -1) {
     229  		warn(_("stat of %s failed"), s);
     230  		return 2;
     231  	}
     232  	if (string_replace(from, to, s, &newname) != 0)
     233  		return 0;
     234  
     235  	if ((nooverwrite || interactive) && access(newname, F_OK) != 0)
     236  		nooverwrite = interactive = 0;
     237  
     238  	if (nooverwrite || (interactive && (noact || ask(newname) != 0))) {
     239  		if (verbose)
     240  			printf(_("Skipping existing file: `%s'\n"), newname);
     241  		ret = 0;
     242  	}
     243  	else if (!noact && rename(s, newname) != 0) {
     244  		warn(_("%s: rename to %s failed"), s, newname);
     245  		ret = 2;
     246  	}
     247  	if (verbose && (noact || ret == 1))
     248  		printf("`%s' -> `%s'\n", s, newname);
     249  	free(newname);
     250  	return ret;
     251  }
     252  
     253  static void __attribute__((__noreturn__)) usage(void)
     254  {
     255  	FILE *out = stdout;
     256  	fputs(USAGE_HEADER, out);
     257  	fprintf(out,
     258  	      _(" %s [options] <expression> <replacement> <file>...\n"),
     259  		program_invocation_short_name);
     260  
     261  	fputs(USAGE_SEPARATOR, out);
     262  	fputs(_("Rename files.\n"), out);
     263  
     264  	fputs(USAGE_OPTIONS, out);
     265  	fputs(_(" -v, --verbose       explain what is being done\n"), out);
     266  	fputs(_(" -s, --symlink       act on the target of symlinks\n"), out);
     267  	fputs(_(" -n, --no-act        do not make any changes\n"), out);
     268  	fputs(_(" -a, --all           replace all occurrences\n"), out);
     269  	fputs(_(" -l, --last          replace only the last occurrence\n"), out);
     270  	fputs(_(" -o, --no-overwrite  don't overwrite existing files\n"), out);
     271  	fputs(_(" -i, --interactive   prompt before overwrite\n"), out);
     272  	fputs(USAGE_SEPARATOR, out);
     273  	printf(USAGE_HELP_OPTIONS(21));
     274  	printf(USAGE_MAN_TAIL("rename(1)"));
     275  	exit(EXIT_SUCCESS);
     276  }
     277  
     278  int main(int argc, char **argv)
     279  {
     280  	char *from, *to;
     281  	int i, c, ret = 0, verbose = 0, noact = 0, nooverwrite = 0, interactive = 0;
     282  	struct termios tio;
     283  	int (*do_rename)(char *from, char *to, char *s, int verbose, int noact,
     284  	                 int nooverwrite, int interactive) = do_file;
     285  
     286  	static const struct option longopts[] = {
     287  		{"verbose", no_argument, NULL, 'v'},
     288  		{"version", no_argument, NULL, 'V'},
     289  		{"help", no_argument, NULL, 'h'},
     290  		{"all", no_argument, NULL, 'a'},
     291  		{"last", no_argument, NULL, 'l'},
     292  		{"no-act", no_argument, NULL, 'n'},
     293  		{"no-overwrite", no_argument, NULL, 'o'},
     294  		{"interactive", no_argument, NULL, 'i'},
     295  		{"symlink", no_argument, NULL, 's'},
     296  		{NULL, 0, NULL, 0}
     297  	};
     298  	static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
     299  		{ 'a','l' },
     300  		{ 'i','o' },
     301  		{ 0 }
     302  	};
     303  	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
     304  
     305  	setlocale(LC_ALL, "");
     306  	bindtextdomain(PACKAGE, LOCALEDIR);
     307  	textdomain(PACKAGE);
     308  	close_stdout_atexit();
     309  
     310  	while ((c = getopt_long(argc, argv, "vsVhnaloi", longopts, NULL)) != -1) {
     311  		err_exclusive_options(c, longopts, excl, excl_st);
     312  		switch (c) {
     313  		case 'n':
     314  			noact = 1;
     315  			break;
     316  		case 'a':
     317  			all = 1;
     318  			break;
     319  		case 'l':
     320  			last = 1;
     321  			break;
     322  		case 'v':
     323  			verbose = 1;
     324  			break;
     325  		case 'o':
     326  			nooverwrite = 1;
     327  			break;
     328  		case 'i':
     329  			interactive = 1;
     330  			break;
     331  		case 's':
     332  			do_rename = do_symlink;
     333  			break;
     334  
     335  		case 'V':
     336  			print_version(EXIT_SUCCESS);
     337  		case 'h':
     338  			usage();
     339  		default:
     340  			errtryhelp(EXIT_FAILURE);
     341  		}
     342  	}
     343  
     344  	argc -= optind;
     345  	argv += optind;
     346  
     347  	if (argc < 3) {
     348  		warnx(_("not enough arguments"));
     349  		errtryhelp(EXIT_FAILURE);
     350  	}
     351  
     352  	from = argv[0];
     353  	to = argv[1];
     354  
     355  	if (!strcmp(from, to))
     356  		return RENAME_EXIT_NOTHING;
     357  
     358  	tty_cbreak = 0;
     359  	if (interactive && isatty(STDIN_FILENO) != 0) {
     360  		if (tcgetattr(STDIN_FILENO, &tio) != 0)
     361  			warn(_("failed to get terminal attributes"));
     362  		else if (!(tio.c_lflag & ICANON) && tio.c_cc[VMIN] == 1)
     363  			tty_cbreak = 1;
     364  	}
     365  
     366  	for (i = 2; i < argc; i++)
     367  		ret |= do_rename(from, to, argv[i], verbose, noact, nooverwrite, interactive);
     368  
     369  	switch (ret) {
     370  	case 0:
     371  		return RENAME_EXIT_NOTHING;
     372  	case 1:
     373  		return EXIT_SUCCESS;
     374  	case 2:
     375  		return EXIT_FAILURE;
     376  	case 3:
     377  		return RENAME_EXIT_SOMEOK;
     378  	default:
     379  		return RENAME_EXIT_UNEXPLAINED;
     380  	}
     381  }