/* rename.c -- file rename tool Copyright 1998,1999 Xuming 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 #include #include #include #include #include #include #if HAVE_UNISTD_H #include #include #endif #if STDC_HEADERS #include #else #ifndef HAVE_STRCHR #define strchr index #define strrchr rindex #endif char *strchr(), *strrchr(); #endif #if HAVE_DIRENT_H #include #define NAMLEN(dirent) strlen((dirent)->d_name) #else #define dirent direct #define NAMLEN(dirent) (dirent)->d_namlen #if HAVE_SYS_NDIR_H #include #endif #if HAVE_SYS_DIR_H #include #endif #if HAVE_NDIR_H #include #endif #endif #if HAVE_REGEX_H #include #elif #include "regex.h" #endif #if HAVE_GETOPT_H #include #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 .\n"; char *version = "rename tool version %s\n"; if (mode) printf(version, VERSION); else printf(help); }