rm.c revision 1.35 1 /* $NetBSD: rm.c,v 1.35 2003/08/04 22:31:25 jschauma Exp $ */
2
3 /*-
4 * Copyright (c) 1990, 1993, 1994, 2003
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #include <sys/cdefs.h>
37 #ifndef lint
38 __COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\n\
39 The Regents of the University of California. All rights reserved.\n");
40 #endif /* not lint */
41
42 #ifndef lint
43 #if 0
44 static char sccsid[] = "@(#)rm.c 8.8 (Berkeley) 4/27/95";
45 #else
46 __RCSID("$NetBSD: rm.c,v 1.35 2003/08/04 22:31:25 jschauma Exp $");
47 #endif
48 #endif /* not lint */
49
50 #include <sys/param.h>
51 #include <sys/stat.h>
52 #include <sys/types.h>
53
54 #include <err.h>
55 #include <errno.h>
56 #include <fcntl.h>
57 #include <fts.h>
58 #include <grp.h>
59 #include <locale.h>
60 #include <pwd.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <unistd.h>
65 #include <vis.h>
66
67 int dflag, eval, fflag, iflag, Pflag, stdin_ok, stdout_ok, vflag, Wflag;
68
69 int check(char *, char *, struct stat *);
70 void checkdot(char **);
71 char *printescaped(const char *);
72 void rm_file(char **);
73 void rm_overwrite(char *, struct stat *);
74 void rm_tree(char **);
75 void usage(void);
76 int main(int, char *[]);
77
78 /*
79 * For the sake of the `-f' flag, check whether an error number indicates the
80 * failure of an operation due to an non-existent file, either per se (ENOENT)
81 * or because its filename argument was illegal (ENAMETOOLONG, ENOTDIR).
82 */
83 #define NONEXISTENT(x) \
84 ((x) == ENOENT || (x) == ENAMETOOLONG || (x) == ENOTDIR)
85
86 /*
87 * rm --
88 * This rm is different from historic rm's, but is expected to match
89 * POSIX 1003.2 behavior. The most visible difference is that -f
90 * has two specific effects now, ignore non-existent files and force
91 * file removal.
92 */
93 int
94 main(int argc, char *argv[])
95 {
96 int ch, rflag;
97
98 setprogname(argv[0]);
99 (void)setlocale(LC_ALL, "");
100
101 Pflag = rflag = 0;
102 while ((ch = getopt(argc, argv, "dfiPRrvW")) != -1)
103 switch (ch) {
104 case 'd':
105 dflag = 1;
106 break;
107 case 'f':
108 fflag = 1;
109 iflag = 0;
110 break;
111 case 'i':
112 fflag = 0;
113 iflag = 1;
114 break;
115 case 'P':
116 Pflag = 1;
117 break;
118 case 'R':
119 case 'r': /* Compatibility. */
120 rflag = 1;
121 break;
122 case 'v':
123 vflag = 1;
124 break;
125 case 'W':
126 Wflag = 1;
127 break;
128 case '?':
129 default:
130 usage();
131 }
132 argc -= optind;
133 argv += optind;
134
135 if (argc < 1)
136 usage();
137
138 checkdot(argv);
139
140 if (*argv) {
141 stdin_ok = isatty(STDIN_FILENO);
142 stdout_ok = isatty(STDOUT_FILENO);
143
144 if (rflag)
145 rm_tree(argv);
146 else
147 rm_file(argv);
148 }
149
150 exit(eval);
151 /* NOTREACHED */
152 }
153
154 void
155 rm_tree(char **argv)
156 {
157 FTS *fts;
158 FTSENT *p;
159 int flags, needstat, rval;
160 char *fn;
161
162 /*
163 * Remove a file hierarchy. If forcing removal (-f), or interactive
164 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
165 */
166 needstat = !fflag && !iflag && stdin_ok;
167
168 /*
169 * If the -i option is specified, the user can skip on the pre-order
170 * visit. The fts_number field flags skipped directories.
171 */
172 #define SKIPPED 1
173
174 flags = FTS_PHYSICAL;
175 if (!needstat)
176 flags |= FTS_NOSTAT;
177 if (Wflag)
178 flags |= FTS_WHITEOUT;
179 if (!(fts = fts_open(argv, flags,
180 (int (*)(const FTSENT **, const FTSENT **))NULL)))
181 err(1, NULL);
182 while ((p = fts_read(fts)) != NULL) {
183
184 switch (p->fts_info) {
185 case FTS_DNR:
186 if (!fflag || p->fts_errno != ENOENT) {
187 fn = printescaped(p->fts_path);
188 warnx("%s: %s", fn, strerror(p->fts_errno));
189 free(fn);
190 eval = 1;
191 }
192 continue;
193 case FTS_ERR:
194 errx(EXIT_FAILURE, "%s: %s", printescaped(p->fts_path),
195 strerror(p->fts_errno));
196 /* NOTREACHED */
197 case FTS_NS:
198 /*
199 * FTS_NS: assume that if can't stat the file, it
200 * can't be unlinked.
201 */
202 if (fflag && NONEXISTENT(p->fts_errno))
203 continue;
204 if (needstat) {
205 fn = printescaped(p->fts_path);
206 warnx("%s: %s", fn, strerror(p->fts_errno));
207 free(fn);
208 eval = 1;
209 continue;
210 }
211 break;
212 case FTS_D:
213 /* Pre-order: give user chance to skip. */
214 if (!fflag && !check(p->fts_path, p->fts_accpath,
215 p->fts_statp)) {
216 (void)fts_set(fts, p, FTS_SKIP);
217 p->fts_number = SKIPPED;
218 }
219 continue;
220 case FTS_DP:
221 /* Post-order: see if user skipped. */
222 if (p->fts_number == SKIPPED)
223 continue;
224 break;
225 default:
226 if (!fflag &&
227 !check(p->fts_path, p->fts_accpath, p->fts_statp))
228 continue;
229 }
230
231 rval = 0;
232 /*
233 * If we can't read or search the directory, may still be
234 * able to remove it. Don't print out the un{read,search}able
235 * message unless the remove fails.
236 */
237 switch (p->fts_info) {
238 case FTS_DP:
239 case FTS_DNR:
240 rval = rmdir(p->fts_accpath);
241 if (rval != 0 && fflag && errno == ENOENT)
242 continue;
243 break;
244
245 case FTS_W:
246 rval = undelete(p->fts_accpath);
247 if (rval != 0 && fflag && errno == ENOENT)
248 continue;
249 break;
250
251 default:
252 if (Pflag)
253 rm_overwrite(p->fts_accpath, NULL);
254 rval = unlink(p->fts_accpath);
255 if (rval != 0 && fflag && NONEXISTENT(errno))
256 continue;
257 break;
258 }
259 if (rval != 0) {
260 fn = printescaped(p->fts_path);
261 warn("%s", fn);
262 free(fn);
263 eval = 1;
264 } else if (vflag) {
265 fn = printescaped(p->fts_path);
266 (void)printf("%s\n", fn);
267 free(fn);
268 }
269 }
270 if (errno)
271 err(1, "fts_read");
272 }
273
274 void
275 rm_file(char **argv)
276 {
277 struct stat sb;
278 int rval;
279 char *f, *fn;
280
281 /*
282 * Remove a file. POSIX 1003.2 states that, by default, attempting
283 * to remove a directory is an error, so must always stat the file.
284 */
285 while ((f = *argv++) != NULL) {
286 /* Assume if can't stat the file, can't unlink it. */
287 if (lstat(f, &sb)) {
288 if (Wflag) {
289 sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
290 } else {
291 if (!fflag || !NONEXISTENT(errno)) {
292 fn = printescaped(f);
293 warn("%s", fn);
294 free(fn);
295 eval = 1;
296 }
297 continue;
298 }
299 } else if (Wflag) {
300 fn = printescaped(f);
301 warnx("%s: %s", fn, strerror(EEXIST));
302 free(fn);
303 eval = 1;
304 continue;
305 }
306
307 if (S_ISDIR(sb.st_mode) && !dflag) {
308 fn = printescaped(f);
309 warnx("%s: is a directory", fn);
310 free(fn);
311 eval = 1;
312 continue;
313 }
314 if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
315 continue;
316 if (S_ISWHT(sb.st_mode))
317 rval = undelete(f);
318 else if (S_ISDIR(sb.st_mode))
319 rval = rmdir(f);
320 else {
321 if (Pflag)
322 rm_overwrite(f, &sb);
323 rval = unlink(f);
324 }
325 if (rval && (!fflag || !NONEXISTENT(errno))) {
326 fn = printescaped(f);
327 warn("%s", fn);
328 free(fn);
329 eval = 1;
330 }
331 if (vflag && rval == 0) {
332 fn = printescaped(f);
333 (void)printf("%s\n", fn);
334 free(fn);
335 }
336 }
337 }
338
339 /*
340 * rm_overwrite --
341 * Overwrite the file 3 times with varying bit patterns.
342 *
343 * XXX
344 * This is a cheap way to *really* delete files. Note that only regular
345 * files are deleted, directories (and therefore names) will remain.
346 * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
347 * System V file system). In a logging file system, you'll have to have
348 * kernel support.
349 */
350 void
351 rm_overwrite(char *file, struct stat *sbp)
352 {
353 struct stat sb;
354 off_t len;
355 int fd, wlen;
356 char buf[8 * 1024];
357 char *fn;
358
359 fd = -1;
360 if (sbp == NULL) {
361 if (lstat(file, &sb))
362 goto err;
363 sbp = &sb;
364 }
365 if (!S_ISREG(sbp->st_mode))
366 return;
367 if ((fd = open(file, O_WRONLY, 0)) == -1)
368 goto err;
369
370 #define PASS(byte) do { \
371 memset(buf, byte, sizeof(buf)); \
372 for (len = sbp->st_size; len > 0; len -= wlen) { \
373 wlen = len < sizeof(buf) ? len : sizeof(buf); \
374 if (write(fd, buf, wlen) != wlen) \
375 goto err; \
376 } \
377 } while (/* CONSTCOND */ 0)
378 PASS(0xff);
379 if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
380 goto err;
381 PASS(0x00);
382 if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
383 goto err;
384 PASS(0xff);
385 if (!fsync(fd) && !close(fd))
386 return;
387
388 err: eval = 1;
389 fn = printescaped(file);
390 warn("%s", fn);
391 free(fn);
392 }
393
394 int
395 check(char *path, char *name, struct stat *sp)
396 {
397 int ch, first;
398 char modep[15];
399 char *fn;
400
401 /* Check -i first. */
402 if (iflag) {
403 fn = printescaped(path);
404 (void)fprintf(stderr, "remove '%s'? ", fn);
405 free(fn);
406 } else {
407 /*
408 * If it's not a symbolic link and it's unwritable and we're
409 * talking to a terminal, ask. Symbolic links are excluded
410 * because their permissions are meaningless. Check stdin_ok
411 * first because we may not have stat'ed the file.
412 */
413 if (!stdin_ok || S_ISLNK(sp->st_mode) ||
414 !(access(name, W_OK) && (errno != ETXTBSY)))
415 return (1);
416 strmode(sp->st_mode, modep);
417 fn = printescaped(path);
418 (void)fprintf(stderr, "override %s%s%s/%s for '%s'? ",
419 modep + 1, modep[9] == ' ' ? "" : " ",
420 user_from_uid(sp->st_uid, 0),
421 group_from_gid(sp->st_gid, 0), fn);
422 free(fn);
423 }
424 (void)fflush(stderr);
425
426 first = ch = getchar();
427 while (ch != '\n' && ch != EOF)
428 ch = getchar();
429 return (first == 'y' || first == 'Y');
430 }
431
432 /*
433 * POSIX.2 requires that if "." or ".." are specified as the basename
434 * portion of an operand, a diagnostic message be written to standard
435 * error and nothing more be done with such operands.
436 *
437 * Since POSIX.2 defines basename as the final portion of a path after
438 * trailing slashes have been removed, we'll remove them here.
439 */
440 #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
441 void
442 checkdot(char **argv)
443 {
444 char *p, **save, **t;
445 int complained;
446
447 complained = 0;
448 for (t = argv; *t;) {
449 /* strip trailing slashes */
450 p = strrchr(*t, '\0');
451 while (--p > *t && *p == '/')
452 *p = '\0';
453
454 /* extract basename */
455 if ((p = strrchr(*t, '/')) != NULL)
456 ++p;
457 else
458 p = *t;
459
460 if (ISDOT(p)) {
461 if (!complained++)
462 warnx("\".\" and \"..\" may not be removed");
463 eval = 1;
464 for (save = t; (t[0] = t[1]) != NULL; ++t)
465 continue;
466 t = save;
467 } else
468 ++t;
469 }
470 }
471
472 char *
473 printescaped(const char *src)
474 {
475 size_t len;
476 char *retval;
477
478 len = strlen(src);
479 if (len != 0 && SIZE_T_MAX/len <= 4) {
480 errx(EXIT_FAILURE, "%s: name too long", src);
481 /* NOTREACHED */
482 }
483
484 retval = (char *)malloc(4*len+1);
485 if (retval != NULL) {
486 if (stdout_ok)
487 (void)strvis(retval, src, VIS_NL | VIS_CSTYLE);
488 else
489 (void)strcpy(retval, src);
490 return retval;
491 } else
492 errx(EXIT_FAILURE, "out of memory!");
493 /* NOTREACHED */
494 }
495
496 void
497 usage(void)
498 {
499
500 (void)fprintf(stderr, "usage: %s [-f|-i] [-dPRrvW] file ...\n",
501 getprogname());
502 exit(1);
503 /* NOTREACHED */
504 }
505