]>
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> |
5a2a8177 | 31 | #include <unistd.h> |
f43bdeda | 32 | #include <termios.h> |
5a2a8177 JM |
33 | #include <sys/types.h> |
34 | #include <sys/stat.h> | |
87f3feac | 35 | |
eb63b9b8 | 36 | #include "nls.h" |
87f3feac | 37 | #include "xalloc.h" |
d200a926 | 38 | #include "c.h" |
c05a80ca | 39 | #include "closestream.h" |
96209c98 | 40 | #include "optutils.h" |
eb4aea8a | 41 | #include "rpmatch.h" |
eb63b9b8 | 42 | |
d6cf9e16 SK |
43 | #define RENAME_EXIT_SOMEOK 2 |
44 | #define RENAME_EXIT_NOTHING 4 | |
45 | #define RENAME_EXIT_UNEXPLAINED 64 | |
46 | ||
f43bdeda | 47 | static int tty_cbreak = 0; |
a2b23d3a TL |
48 | static int all = 0; |
49 | static int last = 0; | |
f43bdeda | 50 | |
fa7abf33 PH |
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) | |
d200a926 | 73 | { |
5651128a | 74 | char *p, *q, *where; |
96209c98 | 75 | size_t count = 0, fromlen = strlen(from); |
5651128a | 76 | |
fa7abf33 | 77 | p = where = find_initial_replace(from, to, orig); |
5651128a SK |
78 | if (where == NULL) |
79 | return 1; | |
a2b23d3a | 80 | count++; |
2285629d PH |
81 | while ((all || last) && p && *p) { |
82 | p = strstr(p + (last ? 1 : max(fromlen, (size_t) 1)), from); | |
a2b23d3a TL |
83 | if (p) { |
84 | if (all) | |
85 | count++; | |
86 | if (last) | |
87 | where = p; | |
88 | } | |
89 | } | |
5651128a | 90 | p = orig; |
a2b23d3a | 91 | *newname = xmalloc(strlen(orig) - count * fromlen + count * strlen(to) + 1); |
5651128a | 92 | q = *newname; |
b0e402c3 | 93 | while (count--) { |
a2b23d3a TL |
94 | while (p < where) |
95 | *q++ = *p++; | |
96 | p = to; | |
97 | while (*p) | |
98 | *q++ = *p++; | |
2285629d PH |
99 | if (fromlen > 0) { |
100 | p = where + fromlen; | |
101 | where = strstr(p, from); | |
102 | } else { | |
103 | p = where; | |
104 | where += 1; | |
105 | } | |
a2b23d3a | 106 | } |
eb63b9b8 KZ |
107 | while (*p) |
108 | *q++ = *p++; | |
22853e4a | 109 | *q = 0; |
5651128a SK |
110 | return 0; |
111 | } | |
eb63b9b8 | 112 | |
eb4aea8a M |
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) { | |
0cb2c653 | 120 | buf[0] = 'n'; |
0cb2c653 | 121 | printf("n\n"); |
eb4aea8a M |
122 | } |
123 | else { | |
0cb2c653 | 124 | buf[0] = c; |
33785dd3 | 125 | if (c != '\n' && tty_cbreak) { |
0dba6b6f | 126 | #ifdef HAVE___FPURGE |
33785dd3 M |
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); | |
0dba6b6f | 133 | #endif |
f43bdeda | 134 | printf("\n"); |
33785dd3 | 135 | } |
f43bdeda | 136 | else if (c != '\n') |
eb4aea8a M |
137 | while ((c = fgetc(stdin)) != '\n' && c != EOF); |
138 | } | |
0cb2c653 | 139 | buf[1] = '\0'; |
eb4aea8a M |
140 | if (rpmatch(buf) == RPMATCH_YES) |
141 | return 0; | |
042f62df RP |
142 | |
143 | return 1; | |
eb4aea8a M |
144 | } |
145 | ||
146 | static int do_symlink(char *from, char *to, char *s, int verbose, int noact, | |
147 | int nooverwrite, int interactive) | |
5651128a SK |
148 | { |
149 | char *newname = NULL, *target = NULL; | |
150 | int ret = 1; | |
538f010c | 151 | ssize_t ssz; |
5651128a SK |
152 | struct stat sb; |
153 | ||
826538bf M |
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 | { | |
5454df9c M |
159 | warn(_("%s: not accessible"), s); |
160 | return 2; | |
161 | } | |
162 | ||
5651128a | 163 | if (lstat(s, &sb) == -1) { |
fc14ceba | 164 | warn(_("stat of %s failed"), s); |
5651128a SK |
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); | |
538f010c KZ |
172 | |
173 | ssz = readlink(s, target, sb.st_size + 1); | |
174 | if (ssz < 0) { | |
5651128a SK |
175 | warn(_("%s: readlink failed"), s); |
176 | free(target); | |
177 | return 2; | |
178 | } | |
538f010c KZ |
179 | target[ssz] = '\0'; |
180 | ||
fa7abf33 | 181 | if (string_replace(from, to, target, &newname) != 0) |
5651128a | 182 | ret = 0; |
fabb9067 | 183 | |
eb4aea8a M |
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 | { | |
fabb9067 | 190 | if (verbose) |
b98ab303 | 191 | printf(_("Skipping existing link: `%s' -> `%s'\n"), s, target); |
fabb9067 DG |
192 | ret = 0; |
193 | } | |
194 | ||
195 | if (ret == 1) { | |
196 | if (!noact && 0 > unlink(s)) { | |
197 | warn(_("%s: unlink failed"), s); | |
198 | ret = 2; | |
7ac394f8 M |
199 | } |
200 | else if (!noact && symlink(newname, s) != 0) { | |
fabb9067 DG |
201 | warn(_("%s: symlinking to %s failed"), s, newname); |
202 | ret = 2; | |
203 | } | |
5a2a8177 | 204 | } |
990bf1f0 | 205 | if (verbose && (noact || ret == 1)) |
7ac394f8 | 206 | printf("%s: `%s' -> `%s'\n", s, target, newname); |
d200a926 | 207 | free(newname); |
23b4715b | 208 | free(target); |
d6cf9e16 | 209 | return ret; |
eb63b9b8 KZ |
210 | } |
211 | ||
eb4aea8a M |
212 | static int do_file(char *from, char *to, char *s, int verbose, int noact, |
213 | int nooverwrite, int interactive) | |
5651128a | 214 | { |
fa7abf33 | 215 | char *newname = NULL; |
5651128a | 216 | int ret = 1; |
477239ce | 217 | struct stat sb; |
5651128a | 218 | |
477239ce MFO |
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 | { | |
5454df9c M |
224 | warn(_("%s: not accessible"), s); |
225 | return 2; | |
226 | } | |
227 | ||
477239ce MFO |
228 | if (lstat(s, &sb) == -1) { |
229 | warn(_("stat of %s failed"), s); | |
230 | return 2; | |
231 | } | |
fa7abf33 | 232 | if (string_replace(from, to, s, &newname) != 0) |
5651128a | 233 | return 0; |
5454df9c | 234 | |
eb4aea8a M |
235 | if ((nooverwrite || interactive) && access(newname, F_OK) != 0) |
236 | nooverwrite = interactive = 0; | |
237 | ||
238 | if (nooverwrite || (interactive && (noact || ask(newname) != 0))) { | |
6277e231 M |
239 | if (verbose) |
240 | printf(_("Skipping existing file: `%s'\n"), newname); | |
fabb9067 DG |
241 | ret = 0; |
242 | } | |
990bf1f0 | 243 | else if (!noact && rename(s, newname) != 0) { |
5651128a SK |
244 | warn(_("%s: rename to %s failed"), s, newname); |
245 | ret = 2; | |
246 | } | |
990bf1f0 | 247 | if (verbose && (noact || ret == 1)) |
5651128a SK |
248 | printf("`%s' -> `%s'\n", s, newname); |
249 | free(newname); | |
250 | return ret; | |
251 | } | |
252 | ||
6e1eda6f | 253 | static void __attribute__((__noreturn__)) usage(void) |
d200a926 | 254 | { |
6e1eda6f | 255 | FILE *out = stdout; |
540dfebe | 256 | fputs(USAGE_HEADER, out); |
d200a926 | 257 | fprintf(out, |
09af3db4 | 258 | _(" %s [options] <expression> <replacement> <file>...\n"), |
d200a926 | 259 | program_invocation_short_name); |
451dbcfa BS |
260 | |
261 | fputs(USAGE_SEPARATOR, out); | |
262 | fputs(_("Rename files.\n"), out); | |
263 | ||
540dfebe | 264 | fputs(USAGE_OPTIONS, out); |
9a838c3c DG |
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); | |
a2b23d3a TL |
268 | fputs(_(" -a, --all replace all occurrences\n"), out); |
269 | fputs(_(" -l, --last replace only the last occurrence\n"), out); | |
9a838c3c | 270 | fputs(_(" -o, --no-overwrite don't overwrite existing files\n"), out); |
eb4aea8a | 271 | fputs(_(" -i, --interactive prompt before overwrite\n"), out); |
540dfebe | 272 | fputs(USAGE_SEPARATOR, out); |
bad4c729 MY |
273 | fprintf(out, USAGE_HELP_OPTIONS(21)); |
274 | fprintf(out, USAGE_MAN_TAIL("rename(1)")); | |
6e1eda6f | 275 | exit(EXIT_SUCCESS); |
d200a926 | 276 | } |
eb63b9b8 | 277 | |
d200a926 SK |
278 | int main(int argc, char **argv) |
279 | { | |
280 | char *from, *to; | |
eb4aea8a | 281 | int i, c, ret = 0, verbose = 0, noact = 0, nooverwrite = 0, interactive = 0; |
f43bdeda | 282 | struct termios tio; |
eb4aea8a M |
283 | int (*do_rename)(char *from, char *to, char *s, int verbose, int noact, |
284 | int nooverwrite, int interactive) = do_file; | |
d200a926 SK |
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'}, | |
a2b23d3a TL |
290 | {"all", no_argument, NULL, 'a'}, |
291 | {"last", no_argument, NULL, 'l'}, | |
990bf1f0 | 292 | {"no-act", no_argument, NULL, 'n'}, |
9a838c3c | 293 | {"no-overwrite", no_argument, NULL, 'o'}, |
eb4aea8a | 294 | {"interactive", no_argument, NULL, 'i'}, |
5a2a8177 | 295 | {"symlink", no_argument, NULL, 's'}, |
d200a926 SK |
296 | {NULL, 0, NULL, 0} |
297 | }; | |
96209c98 TL |
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; | |
eb63b9b8 KZ |
304 | |
305 | setlocale(LC_ALL, ""); | |
306 | bindtextdomain(PACKAGE, LOCALEDIR); | |
307 | textdomain(PACKAGE); | |
2c308875 | 308 | close_stdout_atexit(); |
eb63b9b8 | 309 | |
96209c98 TL |
310 | while ((c = getopt_long(argc, argv, "vsVhnaloi", longopts, NULL)) != -1) { |
311 | err_exclusive_options(c, longopts, excl, excl_st); | |
d200a926 | 312 | switch (c) { |
990bf1f0 AR |
313 | case 'n': |
314 | noact = 1; | |
0849ff36 | 315 | break; |
a2b23d3a TL |
316 | case 'a': |
317 | all = 1; | |
a2b23d3a TL |
318 | break; |
319 | case 'l': | |
320 | last = 1; | |
a2b23d3a | 321 | break; |
8c1ce08d SK |
322 | case 'v': |
323 | verbose = 1; | |
990bf1f0 | 324 | break; |
0849ff36 M |
325 | case 'o': |
326 | nooverwrite = 1; | |
eb4aea8a M |
327 | break; |
328 | case 'i': | |
329 | interactive = 1; | |
0849ff36 | 330 | break; |
5a2a8177 | 331 | case 's': |
5651128a | 332 | do_rename = do_symlink; |
5a2a8177 | 333 | break; |
2c308875 | 334 | |
d200a926 | 335 | case 'V': |
2c308875 | 336 | print_version(EXIT_SUCCESS); |
d200a926 | 337 | case 'h': |
6e1eda6f | 338 | usage(); |
d200a926 | 339 | default: |
677ec86c | 340 | errtryhelp(EXIT_FAILURE); |
eb63b9b8 | 341 | } |
96209c98 | 342 | } |
d200a926 SK |
343 | |
344 | argc -= optind; | |
345 | argv += optind; | |
eb63b9b8 KZ |
346 | |
347 | if (argc < 3) { | |
8c219bf4 | 348 | warnx(_("not enough arguments")); |
6e1eda6f | 349 | errtryhelp(EXIT_FAILURE); |
eb63b9b8 KZ |
350 | } |
351 | ||
d200a926 SK |
352 | from = argv[0]; |
353 | to = argv[1]; | |
354 | ||
9f430c8a SK |
355 | if (!strcmp(from, to)) |
356 | return RENAME_EXIT_NOTHING; | |
357 | ||
f43bdeda M |
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 | ||
d200a926 | 366 | for (i = 2; i < argc; i++) |
eb4aea8a | 367 | ret |= do_rename(from, to, argv[i], verbose, noact, nooverwrite, interactive); |
d6cf9e16 SK |
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 | } | |
eb63b9b8 | 381 | } |