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