/*
    rename.c -- file rename tool

    Copyright 1998,1999  Xuming <xuming@bigfoot.com>

    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 2, 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, the file COPYING in this directory, for
    more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <ctype.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <termios.h>
#include <sys/stat.h>

#if HAVE_UNISTD_H
  #include <sys/types.h>
  #include <unistd.h>
#endif

#if STDC_HEADERS
  #include <string.h>
#else
  #ifndef HAVE_STRCHR
    #define strchr index
    #define strrchr rindex
  #endif
  char *strchr(), *strrchr();
#endif

#if HAVE_DIRENT_H
  #include <dirent.h>
  #define NAMLEN(dirent) strlen((dirent)->d_name)
#else
  #define dirent direct
  #define NAMLEN(dirent) (dirent)->d_namlen
  #if HAVE_SYS_NDIR_H
    #include <sys/ndir.h>
  #endif
  #if HAVE_SYS_DIR_H
    #include <sys/dir.h>
  #endif
  #if HAVE_NDIR_H
    #include <ndir.h>
  #endif
#endif

#if HAVE_REGEX_H
  #include <regex.h>
#elif
  #include "regex.h"
#endif

#if HAVE_GETOPT_H
  #include <getopt.h>
  #ifndef HAVE_GETOPT_LONG
    #error No getopt_long function
  #endif
#else
  #error No getopt functions
#endif
  
#include "rename.h"
  
int 	prompt = 0;	/* 0: ask; 1, No to all; other: Yes to all */
int 	oper = 0;	/* the operation: lowcase, upcase, substitute */
int	attr = 0;	/* some attributes of the operation */


regex_t preg[1];	/* see manpage regex(7) */
char	*patnbuf;
char	*pattern;	/* memeory for storing fixed pattern */
int 	pnlen;
char	*subst;
int	stlen;		/* length of substitute and pattern string */

struct passwd *pwd = NULL;

char	cwd[SVRBUF];	/* current working directory */


void recursive(char *path);
int  change_name(char *oldname);
int  match_regexpr(char *str, int n);
int  match_pattern(char *str, int n);
int  match_backward(char *str, int n);
int  setpattern(char *arg);
int  do_rename(char *old, char *new);
void sigbreak(int sig);
void usage(int mode);

char* (*StrStr)(const char *, const char *);
int   (*StrnCmp)(const char *, const char *, size_t);

int main(int argc, char **argv)
{
    int 	recurs = 0, c;
    struct	stat	fs;
    char	*sopt = "hVtluRvs:o:";
    struct	option	lopt[] = {
	{ "lowcase",	0, NULL, 'l' },
	{ "upcase", 	0, NULL, 'u' },
	{ "recursive", 	0, NULL, 'R' },
	{ "owner",      1, NULL, 'o' },
	{ "verbose", 	0, NULL, 'v' },
	{ "test", 	0, NULL, 't' },
	{ "help", 	0, NULL, 'h' },
	{ "version", 	0, NULL, 'V' },
	{ "yes",  	0, NULL, 2 },
	{ "no", 	0, NULL, 1 },
	{ NULL, 0, NULL, 0 }
    };

    StrStr  = strstr;
    StrnCmp = strncmp;
    
    while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != EOF)  {
	switch (c) {
	case 'h':
	    usage(0);
	    return 0;
	    
	case 'V':
	    usage(1);
	    return 0;

	case 1:
	case 2:
	    prompt = c;
	    break;
	    
	case 'l':
	    oper = ACT_LOWCASE;
	    break;
	    
	case 'u':
	    oper = ACT_UPCASE;
	    break;
	    
	case 's':
	    if (setpattern(optarg))  
		return -1;
	    break;
	    
	case 'o':
	    if (geteuid() != 0)  {
		printf("Only super user can change file's owner!\n");
		return 1;
	    }			
	    pwd = getpwnam(optarg);
	    if (pwd == NULL) {
		printf("No this user in current system [%s]\n", optarg);
		return 1;
	    } else
		attr |= MOD_OWNER;
	    break;
	    
	case 't':
	    attr |= MOD_VERBO | MOD_TEST;
	    break;
	    
	case 'v':
	    attr |= MOD_VERBO;
	    break;
	    
	case 'R':
	    recurs = 1;
	    break;
	}
    }
    
    if ((oper == ACT_DEFT) && (attr & MOD_OWNER))
	oper = ACT_OWNER;
	
    if (oper == ACT_DEFT)  {
	    
 	/* just simplely change one filename to another */

	if ((optind + 2) > argc)  {
	    printf("%s: missing file arguments.\n", argv[0]);
	    return 1;
	} else if ((optind + 2) < argc)  {	
	    printf("%s: too many arguments.\n", argv[0]);
	    return 2;
	} else if (strcmp(argv[optind], argv[optind+1])) 
	    do_rename(argv[optind], argv[optind+1]);
	
	return 0;
    }

    if (optind >= argc)  {
	printf("%s: missing file arguments.\n", argv[0]);
	return 1;
    }
	
    if (getcwd(cwd, SVRBUF) == NULL)  {
	printf("Out of path!\n");
	return -1;
    }
    
    signal(SIGINT, sigbreak);
    signal(SIGHUP, sigbreak);
    signal(SIGQUIT, sigbreak);
    signal(SIGTERM, sigbreak);

    for ( ; optind < argc; optind++)  {

	if (recurs)  {
	    if (lstat(argv[optind], &fs) < 0)  {
		perror("lstat");
		continue;
	    }
	    if (S_ISDIR(fs.st_mode))  {
		recursive(argv[optind]);
		chdir(cwd);
	    }
	}
	change_name(argv[optind]);
    }
    
    if (oper == ACT_REG)  
	regfree(preg);
    
/*    chdir(cwd);*/
    return 0;
}


void recursive(char *path)
{
    DIR 	*dir;
    struct	stat	fs;
    struct	dirent	*de;

    if (attr & MOD_VERBO)  
	printf("Entering directory [%s]\n", path);
    
    if (chdir(path) < 0)  {
	perror("chdir");
	return;
    }
    
    if ((dir = opendir(".")) == NULL)  {
	perror("opendir");
	return;
    }
    while((de = readdir(dir)) != NULL)  {
	    
    	if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))  
	    continue;
    	if (lstat(de->d_name, &fs) < 0)  
	    continue; 	/* maybe permission denied */
	
    	if (S_ISDIR(fs.st_mode))  
	    recursive(de->d_name);
    	change_name(de->d_name);
    }
    closedir(dir);
    
    chdir("..");
    if (attr & MOD_VERBO)  
	printf("Leaving directory [%s]\n", path);
}


/* according to the defined ruler, this routine spawns a new filename 
 * and changes old one to this one. */

int change_name(char *oldname)
{
    char	*p, new[SVRBUF];
    int		n;
    
    strncpy(new, oldname, SVRBUF);
    new[SVRBUF-1]='\0';

    /* index the actual filename, not the full path */
    
    if ((p = strrchr(new, '/')) == NULL)  
	p = new;  
    else  
	p++;
    
    if (!strcmp(p, ".") || !strcmp(p, ".."))  
	return 1;
    
    n = new + SVRBUF - p;
    
    switch (oper)  {
    case ACT_LOWCASE:
        while (*p)  {
	    *p = tolower(*p);
    	    p++;
    	}
	break;

    case ACT_UPCASE:
        while (*p)  {
    	    *p = toupper(*p);
    	    p++;
    	}
	break;

    case ACT_SUFFIX:
	n -= strlen(p) - pnlen;
	p += strlen(p) - pnlen;
	if (!StrnCmp(p, pattern, pnlen))
	{
	    strncpy(p, subst, n);
	    p[n-1]='\0';
	}
	break;
	
    case ACT_REG:
        match_regexpr(p, n);
	break;
	
    case ACT_SUBT:
	match_pattern(p, n);
	break;

    case ACT_BACKWD:
	match_backward(p, n);
	break;

    case ACT_OWNER:
	if (attr & MOD_TEST)
	    break;
	if (chown(new, pwd->pw_uid, pwd->pw_gid) < 0)
	    perror("chown");
	break;
    }
    
    if (!strcmp(oldname, new))  
	return 1;
    
    return do_rename(oldname, new);
}


/* to match a null-terminated string against the precompiled pattern buffer.
   When successed, it substitutes matches with the second parameter so
   the original string with enough buffer will be modified.
   Note: precompiled pattern buffer be set to globel.
   If no matches in the string, return 0.
*/

int  match_regexpr(char *str, int n)
{
    int		rs = 0;
    char	tmp[SVRBUF];
    regmatch_t	pmatch[1];

    while (!regexec(preg, str, 1, pmatch, 0))  {
	strncpy(tmp, str + pmatch->rm_eo, SVRBUF);
	tmp[SVRBUF-1]='\0';
	strncpy(str + pmatch->rm_so, subst, n - pmatch->rm_so);
	str[pmatch->rm_so+(n-pmatch->rm_so)-1]='\0';
	n -= strlen(str);
	str += strlen(str);
	strncat(str, tmp, n);
	rs++;
	if ((attr & MOD_REPT) == 0)  
	    break;
    }
    return rs;
}


int  match_pattern(char *str, int n)
{
    char  tmp[SVRBUF], *p;
    int   rs = 0;

    while ((p = StrStr(str, pattern)) != NULL)  {
	n -= p - str;
	strncpy(tmp, p + pnlen, SVRBUF);
	tmp[SVRBUF-1]='\0';
	strncpy(p, subst, n);
	if (n) p[n-1]='\0';

	p += stlen;
	n -= stlen;
	strncpy(p, tmp, n);
	if (n) p[n-1]='\0';

	str = p;

	rs++;
	if ((attr & MOD_REPT) == 0)  
	    break;
    }
    return rs;
}


int match_backward(char *str, int n)
{
    char  tmp[SVRBUF], *p;
    int   rs, room, l;

    /* deal some special situation */
    
    rs = strlen(str) - pnlen;
    if (rs < 0)  
	return 0;	/* the pattern is longer than dest. string */
    
    if (rs == 0)  
    {
	if (StrStr(str, pattern))  
	{
	    l = (stlen < n) ? stlen : n ;
	    strncpy(str, subst, l);
	    if ( l ) str[l-1]='\0';
	    return 1;
	}
	return 0;
    }
    
    room = n - rs;
    for (p = str + rs, rs = 0; p >= str; p--, room++)  {
	    
	if (!StrnCmp(p, pattern, pnlen))  
	{	
	    strncpy(tmp, p + pnlen, SVRBUF);
	    tmp[SVRBUF-1]='\0';
	    strncpy(p, subst, room);
	    if ( room ) p[room-1]='\0';
	    l = room - stlen;
	    strncpy(p + stlen, tmp, l);
	    if ( l ) p[stlen+l-1]='\0';
	    rs++;
	    if ((attr & MOD_REPT) == 0)  
		break;
	}
    }
    return rs;
}    
    

int setpattern(char *arg)
{
    char *idx[4], *p; 
    int  cflags = 0;

    patnbuf = dup_str(arg);
    fixtoken(patnbuf, idx, 4, "/");
    
    pattern = idx[1];
    if (pattern == NULL)  
	return -1;
    else 
	pnlen = strlen(pattern);
    
    subst = idx[2];
    if (subst == NULL)
	return -1;
    else
	stlen = strlen(subst);
    
    oper = ACT_SUBT;
    for (p = idx[3]; p && *p; p++)  {
	switch (*p)  {
	case 'g':
	case 'G':
	    attr |= MOD_REPT;
	    break;
	
	case 'b':
	case 'B':
	    oper = ACT_BACKWD;
	    break;

	case 's':
	case 'S':
	    oper = ACT_SUFFIX;
	    break;

	case 'i':
	case 'I':
	    attr |= MOD_ICASE;
	    StrnCmp = strncasecmp;
	    StrStr = strcasestr;
	    cflags |= REG_ICASE;
	    break;
	
	case 'r':
	case 'R':
	    oper = ACT_REG;
	    break;

	case 'e':
	case 'E':
	    oper = ACT_REG;
	    cflags |= REG_EXTENDED;
	    break;
	}
    }

    if (oper == ACT_REG)  {
	if (regcomp(preg, pattern, cflags))  {
	    printf("Compiling regular expression failed. [%s]\n", pattern);
	    return -2;
	}
    }
    
/*    if (attr & MOD_TEST) 
	printf("pattern: %s subst: %s refs: %s\n", pattern, subst, idx[2]);*/
    
    return 0;
}


/* returns: 0 rename ok, skip or test. others means work failed */

int do_rename(char *oldp, char *newp)
{
    struct stat	fs;
    char *p, *new, buf[64];
    int  i, rs = 0;

    if (!stat(newp, &fs) && S_ISDIR(fs.st_mode))  {
	/* the destination is directory, which means we must move the 
	 * original file into this directory, just like mv(1) does */
	new = alloca(strlen(oldp) + strlen(newp) + 5);
	strcpy(new, newp);
	for (i = strlen(new) - 1; i >= 0; i--) {
	    if (new[i] != '/')
		break;
	}
	new[i+1] = '/';
	new[i+2] = '\0';
	strcat(new, oldp);
    } else {
	new = alloca(strlen(newp) + 1);
	strcpy(new, newp);
    }	
    
    if (attr & MOD_VERBO)  
	printf("rename %-20s => %-20s : ", oldp, new);
    
    if (!stat(new, &fs))  {
	/* the target file has existed already */

	if (prompt == 1)  {
	    goto skip;
	    
	} else if (prompt == 0)  {
	    fprintf(stderr, "overwrite %s? (Yes, No, All, nO_to_all) ", new);
	    tcflush(0, TCIFLUSH);
	    read(0, buf, 64);
	    p = skip_space(buf);
	    
	    switch (*p)  {
	    case 'a':
	    case 'A':
		prompt = 2;

	    case 'y':
	    case 'Y':
		break;
		    
	    case 'o':
	    case 'O':
		prompt = 1;
		
	    case 'n':
	    case 'N':
	    default:
		goto skip;
	    }
	}
    }
    
    if (attr & MOD_TEST) {
	if (attr & MOD_VERBO)
	    printf("tested\n");
    } else {
	rs = rename(oldp, new);
	if (rs < 0) 
	    perror("rename");
	else if (attr & MOD_VERBO)
	    printf("ok\n");
	
	if (attr & MOD_OWNER)
	    chown(new, pwd->pw_uid, pwd->pw_gid);
    }

    return rs;

skip:
    if (attr & MOD_VERBO) 
	printf("skiped\n");
    return rs;
}

	
void sigbreak(int sig)
{

    if (oper == ACT_REG)  
	regfree(preg);
    
    chdir(cwd);
    exit(sig);
}
    
    
void usage(int mode)
{

    char *help = "\
Usage: rename SOURCE DEST\n\
   or: rename [OPTION] file ...\n\
Rename SOURCE to DEST, or substitute characters match the specified pattern\n\
in the filename.\n\
\n\
  -l, --lowcase            lowcase the file names\n\
  -u, --upcase             upcase the file names\n\
  -s/PATTERN/STRING[/sw]   replace matching PATTERN with STRING, [sw] is\n\
                           [g] replace all occurrences in the filename\n\
                           [i] ignore case when searching\n\
                           [b] backward searching and replacing\n\
                           [s] change file's suffix name\n\
                           [r] PATTERN is regular expression\n\
                           [e] PATTERN is extended regular expression\n\
  -R, --recursive          operate on files and directories recursively\n\
  -o, --owner  OWNER       change file's owner (superuser only)\n\
  -v, --verbose            display verbose information\n\
  -t, --test               test only\n\
  -h, --help               display this help and exit\n\
  -V, --version            output version information and exit\n\
      --yes  --no          force to choose YES or NO when target exists\n\
\n\
See man page regex(7) for detail information about extended regular expression.\n\
\n\
Report bugs to <xuming@bigfoot.com>.\n";

    char *version = "rename tool version %s\n";

    if (mode)  
	printf(version, VERSION);  
    else  
	printf(help);
}



syntax highlighted by Code2HTML, v. 0.9.1