]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/rename.c
rename: add option --interactive to ask before overwriting
[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 #include <string.h>
18 #include <stdlib.h>
19 #include <errno.h>
20 #include <getopt.h>
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25
26 #include "nls.h"
27 #include "xalloc.h"
28 #include "c.h"
29 #include "closestream.h"
30 #include "rpmatch.h"
31
32 #define RENAME_EXIT_SOMEOK 2
33 #define RENAME_EXIT_NOTHING 4
34 #define RENAME_EXIT_UNEXPLAINED 64
35
36 static int string_replace(char *from, char *to, char *s, char *orig, char **newname)
37 {
38 char *p, *q, *where;
39
40 where = strstr(s, from);
41 if (where == NULL)
42 return 1;
43 p = orig;
44 *newname = xmalloc(strlen(orig) + strlen(to) + 1);
45 q = *newname;
46 while (p < where)
47 *q++ = *p++;
48 p = to;
49 while (*p)
50 *q++ = *p++;
51 p = where + strlen(from);
52 while (*p)
53 *q++ = *p++;
54 *q = 0;
55 return 0;
56 }
57
58 static int ask(char *name)
59 {
60 int c;
61 char buf[2];
62 printf(_("%s: overwrite `%s'? "), program_invocation_short_name, name);
63 fflush(stdout);
64 if ((c = fgetc(stdin)) == EOF) {
65 buf[0] = '\0';
66 clearerr(stdin); errno = 0;
67 printf("\n");
68 }
69 else {
70 buf[0] = c; buf[1] = '\0';
71 if (c != '\n')
72 while ((c = fgetc(stdin)) != '\n' && c != EOF);
73 }
74 if (rpmatch(buf) == RPMATCH_YES)
75 return 0;
76 else
77 return 1;
78 }
79
80 static int do_symlink(char *from, char *to, char *s, int verbose, int noact,
81 int nooverwrite, int interactive)
82 {
83 char *newname = NULL, *target = NULL;
84 int ret = 1;
85 struct stat sb;
86
87 if ( faccessat(AT_FDCWD, s, F_OK, AT_SYMLINK_NOFOLLOW) != 0 &&
88 errno != EINVAL )
89 /* Skip if AT_SYMLINK_NOFOLLOW is not supported; lstat() below will
90 detect the access error */
91 {
92 warn(_("%s: not accessible"), s);
93 return 2;
94 }
95
96 if (lstat(s, &sb) == -1) {
97 warn(_("stat of %s failed"), s);
98 return 2;
99 }
100 if (!S_ISLNK(sb.st_mode)) {
101 warnx(_("%s: not a symbolic link"), s);
102 return 2;
103 }
104 target = xmalloc(sb.st_size + 1);
105 if (readlink(s, target, sb.st_size + 1) < 0) {
106 warn(_("%s: readlink failed"), s);
107 free(target);
108 return 2;
109 }
110 target[sb.st_size] = '\0';
111 if (string_replace(from, to, target, target, &newname))
112 ret = 0;
113
114 if (ret == 1 && (nooverwrite || interactive) && lstat(newname, &sb) != 0)
115 nooverwrite = interactive = 0;
116
117 if ( ret == 1 &&
118 (nooverwrite || (interactive && (noact || ask(newname) != 0))) )
119 {
120 if (verbose)
121 printf(_("Skipping existing link: `%s' -> `%s'\n"), s, target);
122 ret = 0;
123 }
124
125 if (ret == 1) {
126 if (!noact && 0 > unlink(s)) {
127 warn(_("%s: unlink failed"), s);
128 ret = 2;
129 } else if (!noact && symlink(newname, s) != 0) {
130 warn(_("%s: symlinking to %s failed"), s, newname);
131 ret = 2;
132 }
133 }
134 if (verbose && (noact || ret == 1))
135 if (verbose)
136 printf("%s: `%s' -> `%s'\n", s, target, newname);
137 free(newname);
138 free(target);
139 return ret;
140 }
141
142 static int do_file(char *from, char *to, char *s, int verbose, int noact,
143 int nooverwrite, int interactive)
144 {
145 char *newname = NULL, *file=NULL;
146 int ret = 1;
147
148 if (access(s, F_OK) != 0) {
149 warn(_("%s: not accessible"), s);
150 return 2;
151 }
152
153 if (strchr(from, '/') == NULL && strchr(to, '/') == NULL)
154 file = strrchr(s, '/');
155 if (file == NULL)
156 file = s;
157 if (string_replace(from, to, file, s, &newname))
158 return 0;
159
160 if ((nooverwrite || interactive) && access(newname, F_OK) != 0)
161 nooverwrite = interactive = 0;
162
163 if (nooverwrite || (interactive && (noact || ask(newname) != 0))) {
164 if (verbose)
165 printf(_("Skipping existing file: `%s'\n"), newname);
166 ret = 0;
167 }
168 else if (!noact && rename(s, newname) != 0) {
169 warn(_("%s: rename to %s failed"), s, newname);
170 ret = 2;
171 }
172 if (verbose && (noact || ret == 1))
173 printf("`%s' -> `%s'\n", s, newname);
174 free(newname);
175 return ret;
176 }
177
178 static void __attribute__((__noreturn__)) usage(void)
179 {
180 FILE *out = stdout;
181 fputs(USAGE_HEADER, out);
182 fprintf(out,
183 _(" %s [options] <expression> <replacement> <file>...\n"),
184 program_invocation_short_name);
185
186 fputs(USAGE_SEPARATOR, out);
187 fputs(_("Rename files.\n"), out);
188
189 fputs(USAGE_OPTIONS, out);
190 fputs(_(" -v, --verbose explain what is being done\n"), out);
191 fputs(_(" -s, --symlink act on the target of symlinks\n"), out);
192 fputs(_(" -n, --no-act do not make any changes\n"), out);
193 fputs(_(" -o, --no-overwrite don't overwrite existing files\n"), out);
194 fputs(_(" -i, --interactive prompt before overwrite\n"), out);
195 fputs(USAGE_SEPARATOR, out);
196 printf(USAGE_HELP_OPTIONS(21));
197 printf(USAGE_MAN_TAIL("rename(1)"));
198 exit(EXIT_SUCCESS);
199 }
200
201 int main(int argc, char **argv)
202 {
203 char *from, *to;
204 int i, c, ret = 0, verbose = 0, noact = 0, nooverwrite = 0, interactive = 0;
205 int (*do_rename)(char *from, char *to, char *s, int verbose, int noact,
206 int nooverwrite, int interactive) = do_file;
207
208 static const struct option longopts[] = {
209 {"verbose", no_argument, NULL, 'v'},
210 {"version", no_argument, NULL, 'V'},
211 {"help", no_argument, NULL, 'h'},
212 {"no-act", no_argument, NULL, 'n'},
213 {"no-overwrite", no_argument, NULL, 'o'},
214 {"interactive", no_argument, NULL, 'i'},
215 {"symlink", no_argument, NULL, 's'},
216 {NULL, 0, NULL, 0}
217 };
218
219 setlocale(LC_ALL, "");
220 bindtextdomain(PACKAGE, LOCALEDIR);
221 textdomain(PACKAGE);
222 atexit(close_stdout);
223
224 while ((c = getopt_long(argc, argv, "vsVhnoi", longopts, NULL)) != -1)
225 switch (c) {
226 case 'n':
227 noact = 1;
228 break;
229 case 'v':
230 verbose = 1;
231 break;
232 case 'o':
233 nooverwrite = 1;
234 interactive = 0;
235 break;
236 case 'i':
237 interactive = 1;
238 nooverwrite = 0;
239 break;
240 case 's':
241 do_rename = do_symlink;
242 break;
243 case 'V':
244 printf(UTIL_LINUX_VERSION);
245 return EXIT_SUCCESS;
246 case 'h':
247 usage();
248 default:
249 errtryhelp(EXIT_FAILURE);
250 }
251
252 argc -= optind;
253 argv += optind;
254
255 if (argc < 3) {
256 warnx(_("not enough arguments"));
257 errtryhelp(EXIT_FAILURE);
258 }
259
260 from = argv[0];
261 to = argv[1];
262
263 if (!strcmp(from, to))
264 return RENAME_EXIT_NOTHING;
265
266 for (i = 2; i < argc; i++)
267 ret |= do_rename(from, to, argv[i], verbose, noact, nooverwrite, interactive);
268
269 switch (ret) {
270 case 0:
271 return RENAME_EXIT_NOTHING;
272 case 1:
273 return EXIT_SUCCESS;
274 case 2:
275 return EXIT_FAILURE;
276 case 3:
277 return RENAME_EXIT_SOMEOK;
278 default:
279 return RENAME_EXIT_UNEXPLAINED;
280 }
281 }