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