]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/rename.c
build-sys: release++ (v2.38.1)
[thirdparty/util-linux.git] / 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 static int string_replace(char *from, char *to, char *s, char *orig, char **newname)
52 {
53 char *p, *q, *where;
54 size_t count = 0, fromlen = strlen(from);
55
56 p = where = strstr(s, from);
57 if (where == NULL)
58 return 1;
59 count++;
60 while ((all || last) && p) {
61 p = strstr(p + (last ? 1 : fromlen), from);
62 if (p) {
63 if (all)
64 count++;
65 if (last)
66 where = p;
67 }
68 }
69 p = orig;
70 *newname = xmalloc(strlen(orig) - count * fromlen + count * strlen(to) + 1);
71 q = *newname;
72 while (count--) {
73 while (p < where)
74 *q++ = *p++;
75 p = to;
76 while (*p)
77 *q++ = *p++;
78 p = where + fromlen;
79 where = strstr(p, from);
80 }
81 while (*p)
82 *q++ = *p++;
83 *q = 0;
84 return 0;
85 }
86
87 static int ask(char *name)
88 {
89 int c;
90 char buf[2];
91 printf(_("%s: overwrite `%s'? "), program_invocation_short_name, name);
92 fflush(stdout);
93 if ((c = fgetc(stdin)) == EOF) {
94 buf[0] = 'n';
95 printf("n\n");
96 }
97 else {
98 buf[0] = c;
99 if (c != '\n' && tty_cbreak) {
100 #ifdef HAVE___FPURGE
101 /* Possibly purge a multi-byte character; or do a
102 required purge of the rest of the line (including
103 the newline) if the tty has been put back in
104 canonical mode (for example by a shell after a
105 SIGTSTP signal). */
106 __fpurge(stdin);
107 #endif
108 printf("\n");
109 }
110 else if (c != '\n')
111 while ((c = fgetc(stdin)) != '\n' && c != EOF);
112 }
113 buf[1] = '\0';
114 if (rpmatch(buf) == RPMATCH_YES)
115 return 0;
116
117 return 1;
118 }
119
120 static int do_symlink(char *from, char *to, char *s, int verbose, int noact,
121 int nooverwrite, int interactive)
122 {
123 char *newname = NULL, *target = NULL;
124 int ret = 1;
125 ssize_t ssz;
126 struct stat sb;
127
128 if ( faccessat(AT_FDCWD, s, F_OK, AT_SYMLINK_NOFOLLOW) != 0 &&
129 errno != EINVAL )
130 /* Skip if AT_SYMLINK_NOFOLLOW is not supported; lstat() below will
131 detect the access error */
132 {
133 warn(_("%s: not accessible"), s);
134 return 2;
135 }
136
137 if (lstat(s, &sb) == -1) {
138 warn(_("stat of %s failed"), s);
139 return 2;
140 }
141 if (!S_ISLNK(sb.st_mode)) {
142 warnx(_("%s: not a symbolic link"), s);
143 return 2;
144 }
145 target = xmalloc(sb.st_size + 1);
146
147 ssz = readlink(s, target, sb.st_size + 1);
148 if (ssz < 0) {
149 warn(_("%s: readlink failed"), s);
150 free(target);
151 return 2;
152 }
153 target[ssz] = '\0';
154
155 if (string_replace(from, to, target, target, &newname) != 0)
156 ret = 0;
157
158 if (ret == 1 && (nooverwrite || interactive) && lstat(newname, &sb) != 0)
159 nooverwrite = interactive = 0;
160
161 if ( ret == 1 &&
162 (nooverwrite || (interactive && (noact || ask(newname) != 0))) )
163 {
164 if (verbose)
165 printf(_("Skipping existing link: `%s' -> `%s'\n"), s, target);
166 ret = 0;
167 }
168
169 if (ret == 1) {
170 if (!noact && 0 > unlink(s)) {
171 warn(_("%s: unlink failed"), s);
172 ret = 2;
173 }
174 else if (!noact && symlink(newname, s) != 0) {
175 warn(_("%s: symlinking to %s failed"), s, newname);
176 ret = 2;
177 }
178 }
179 if (verbose && (noact || ret == 1))
180 printf("%s: `%s' -> `%s'\n", s, target, newname);
181 free(newname);
182 free(target);
183 return ret;
184 }
185
186 static int do_file(char *from, char *to, char *s, int verbose, int noact,
187 int nooverwrite, int interactive)
188 {
189 char *newname = NULL, *file=NULL;
190 int ret = 1;
191 struct stat sb;
192
193 if ( faccessat(AT_FDCWD, s, F_OK, AT_SYMLINK_NOFOLLOW) != 0 &&
194 errno != EINVAL )
195 /* Skip if AT_SYMLINK_NOFOLLOW is not supported; lstat() below will
196 detect the access error */
197 {
198 warn(_("%s: not accessible"), s);
199 return 2;
200 }
201
202 if (lstat(s, &sb) == -1) {
203 warn(_("stat of %s failed"), s);
204 return 2;
205 }
206 if (strchr(from, '/') == NULL && strchr(to, '/') == NULL)
207 file = strrchr(s, '/');
208 if (file == NULL)
209 file = s;
210 if (string_replace(from, to, file, s, &newname) != 0)
211 return 0;
212
213 if ((nooverwrite || interactive) && access(newname, F_OK) != 0)
214 nooverwrite = interactive = 0;
215
216 if (nooverwrite || (interactive && (noact || ask(newname) != 0))) {
217 if (verbose)
218 printf(_("Skipping existing file: `%s'\n"), newname);
219 ret = 0;
220 }
221 else if (!noact && rename(s, newname) != 0) {
222 warn(_("%s: rename to %s failed"), s, newname);
223 ret = 2;
224 }
225 if (verbose && (noact || ret == 1))
226 printf("`%s' -> `%s'\n", s, newname);
227 free(newname);
228 return ret;
229 }
230
231 static void __attribute__((__noreturn__)) usage(void)
232 {
233 FILE *out = stdout;
234 fputs(USAGE_HEADER, out);
235 fprintf(out,
236 _(" %s [options] <expression> <replacement> <file>...\n"),
237 program_invocation_short_name);
238
239 fputs(USAGE_SEPARATOR, out);
240 fputs(_("Rename files.\n"), out);
241
242 fputs(USAGE_OPTIONS, out);
243 fputs(_(" -v, --verbose explain what is being done\n"), out);
244 fputs(_(" -s, --symlink act on the target of symlinks\n"), out);
245 fputs(_(" -n, --no-act do not make any changes\n"), out);
246 fputs(_(" -a, --all replace all occurrences\n"), out);
247 fputs(_(" -l, --last replace only the last occurrence\n"), out);
248 fputs(_(" -o, --no-overwrite don't overwrite existing files\n"), out);
249 fputs(_(" -i, --interactive prompt before overwrite\n"), out);
250 fputs(USAGE_SEPARATOR, out);
251 printf(USAGE_HELP_OPTIONS(21));
252 printf(USAGE_MAN_TAIL("rename(1)"));
253 exit(EXIT_SUCCESS);
254 }
255
256 int main(int argc, char **argv)
257 {
258 char *from, *to;
259 int i, c, ret = 0, verbose = 0, noact = 0, nooverwrite = 0, interactive = 0;
260 struct termios tio;
261 int (*do_rename)(char *from, char *to, char *s, int verbose, int noact,
262 int nooverwrite, int interactive) = do_file;
263
264 static const struct option longopts[] = {
265 {"verbose", no_argument, NULL, 'v'},
266 {"version", no_argument, NULL, 'V'},
267 {"help", no_argument, NULL, 'h'},
268 {"all", no_argument, NULL, 'a'},
269 {"last", no_argument, NULL, 'l'},
270 {"no-act", no_argument, NULL, 'n'},
271 {"no-overwrite", no_argument, NULL, 'o'},
272 {"interactive", no_argument, NULL, 'i'},
273 {"symlink", no_argument, NULL, 's'},
274 {NULL, 0, NULL, 0}
275 };
276 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
277 { 'a','l' },
278 { 'i','o' },
279 { 0 }
280 };
281 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
282
283 setlocale(LC_ALL, "");
284 bindtextdomain(PACKAGE, LOCALEDIR);
285 textdomain(PACKAGE);
286 close_stdout_atexit();
287
288 while ((c = getopt_long(argc, argv, "vsVhnaloi", longopts, NULL)) != -1) {
289 err_exclusive_options(c, longopts, excl, excl_st);
290 switch (c) {
291 case 'n':
292 noact = 1;
293 break;
294 case 'a':
295 all = 1;
296 break;
297 case 'l':
298 last = 1;
299 break;
300 case 'v':
301 verbose = 1;
302 break;
303 case 'o':
304 nooverwrite = 1;
305 break;
306 case 'i':
307 interactive = 1;
308 break;
309 case 's':
310 do_rename = do_symlink;
311 break;
312
313 case 'V':
314 print_version(EXIT_SUCCESS);
315 case 'h':
316 usage();
317 default:
318 errtryhelp(EXIT_FAILURE);
319 }
320 }
321
322 argc -= optind;
323 argv += optind;
324
325 if (argc < 3) {
326 warnx(_("not enough arguments"));
327 errtryhelp(EXIT_FAILURE);
328 }
329
330 from = argv[0];
331 to = argv[1];
332
333 if (!strcmp(from, to))
334 return RENAME_EXIT_NOTHING;
335
336 tty_cbreak = 0;
337 if (interactive && isatty(STDIN_FILENO) != 0) {
338 if (tcgetattr(STDIN_FILENO, &tio) != 0)
339 warn(_("failed to get terminal attributes"));
340 else if (!(tio.c_lflag & ICANON) && tio.c_cc[VMIN] == 1)
341 tty_cbreak = 1;
342 }
343
344 for (i = 2; i < argc; i++)
345 ret |= do_rename(from, to, argv[i], verbose, noact, nooverwrite, interactive);
346
347 switch (ret) {
348 case 0:
349 return RENAME_EXIT_NOTHING;
350 case 1:
351 return EXIT_SUCCESS;
352 case 2:
353 return EXIT_FAILURE;
354 case 3:
355 return RENAME_EXIT_SOMEOK;
356 default:
357 return RENAME_EXIT_UNEXPLAINED;
358 }
359 }