rm.c revision 1.17 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 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 /*static char sccsid[] = "from: @(#)rm.c 8.7 (Berkeley) 10/18/94";*/
42 static char *rcsid = "$Id: rm.c,v 1.17 1994/12/28 01:37:50 mycroft Exp $";
43 #endif /* not lint */
44
45 #include <sys/types.h>
46 #include <sys/stat.h>
47
48 #include <locale.h>
49 #include <err.h>
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <fts.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57
58 int dflag, eval, fflag, iflag, Pflag, Wflag, stdin_ok;
59
60 int check __P((char *, char *, struct stat *));
61 void checkdot __P((char **));
62 void rm_file __P((char **));
63 void rm_overwrite __P((char *, struct stat *));
64 void rm_tree __P((char **));
65 void usage __P((void));
66
67 /*
68 * rm --
69 * This rm is different from historic rm's, but is expected to match
70 * POSIX 1003.2 behavior. The most visible difference is that -f
71 * has two specific effects now, ignore non-existent files and force
72 * file removal.
73 */
74 int
75 main(argc, argv)
76 int argc;
77 char *argv[];
78 {
79 int ch, rflag;
80
81 setlocale(LC_ALL, "");
82
83 Pflag = rflag = 0;
84 while ((ch = getopt(argc, argv, "dfiPRrW")) != -1)
85 switch(ch) {
86 case 'd':
87 dflag = 1;
88 break;
89 case 'f':
90 fflag = 1;
91 iflag = 0;
92 break;
93 case 'i':
94 fflag = 0;
95 iflag = 1;
96 break;
97 case 'P':
98 Pflag = 1;
99 break;
100 case 'R':
101 case 'r': /* Compatibility. */
102 rflag = 1;
103 break;
104 case 'W':
105 Wflag = 1;
106 break;
107 case '?':
108 default:
109 usage();
110 }
111 argc -= optind;
112 argv += optind;
113
114 if (argc < 1)
115 usage();
116
117 checkdot(argv);
118
119 if (*argv) {
120 stdin_ok = isatty(STDIN_FILENO);
121
122 if (rflag)
123 rm_tree(argv);
124 else
125 rm_file(argv);
126 }
127
128 exit (eval);
129 }
130
131 void
132 rm_tree(argv)
133 char **argv;
134 {
135 FTS *fts;
136 FTSENT *p;
137 int needstat;
138 int flags;
139
140 /*
141 * Remove a file hierarchy. If forcing removal (-f), or interactive
142 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
143 */
144 needstat = !fflag && !iflag && stdin_ok;
145
146 /*
147 * If the -i option is specified, the user can skip on the pre-order
148 * visit. The fts_number field flags skipped directories.
149 */
150 #define SKIPPED 1
151
152 flags = FTS_PHYSICAL;
153 if (!needstat)
154 flags |= FTS_NOSTAT;
155 if (Wflag)
156 flags |= FTS_WHITEOUT;
157 if (!(fts = fts_open(argv, flags, (int (*)())NULL)))
158 err(1, NULL);
159 while ((p = fts_read(fts)) != NULL) {
160 switch (p->fts_info) {
161 case FTS_DNR:
162 if (!fflag || p->fts_errno != ENOENT) {
163 warnx("%s: %s",
164 p->fts_path, strerror(p->fts_errno));
165 eval = 1;
166 }
167 continue;
168 case FTS_ERR:
169 errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
170 case FTS_NS:
171 /*
172 * FTS_NS: assume that if can't stat the file, it
173 * can't be unlinked.
174 */
175 if (!needstat)
176 break;
177 if (!fflag || p->fts_errno != ENOENT) {
178 warnx("%s: %s",
179 p->fts_path, strerror(p->fts_errno));
180 eval = 1;
181 }
182 continue;
183 case FTS_D:
184 /* Pre-order: give user chance to skip. */
185 if (!fflag && !check(p->fts_path, p->fts_accpath,
186 p->fts_statp)) {
187 (void)fts_set(fts, p, FTS_SKIP);
188 p->fts_number = SKIPPED;
189 }
190 continue;
191 case FTS_DP:
192 /* Post-order: see if user skipped. */
193 if (p->fts_number == SKIPPED)
194 continue;
195 break;
196 default:
197 if (!fflag &&
198 !check(p->fts_path, p->fts_accpath, p->fts_statp))
199 continue;
200 }
201
202 /*
203 * If we can't read or search the directory, may still be
204 * able to remove it. Don't print out the un{read,search}able
205 * message unless the remove fails.
206 */
207 switch (p->fts_info) {
208 case FTS_DP:
209 case FTS_DNR:
210 if (!rmdir(p->fts_accpath) || fflag && errno == ENOENT)
211 continue;
212 break;
213
214 case FTS_W:
215 if (!undelete(p->fts_accpath) ||
216 fflag && errno == ENOENT)
217 continue;
218 break;
219
220 default:
221 if (Pflag)
222 rm_overwrite(p->fts_accpath, NULL);
223 if (!unlink(p->fts_accpath) || fflag && errno == ENOENT)
224 continue;
225 }
226 warn("%s", p->fts_path);
227 eval = 1;
228 }
229 if (errno)
230 err(1, "fts_read");
231 }
232
233 void
234 rm_file(argv)
235 char **argv;
236 {
237 struct stat sb;
238 int rval;
239 char *f;
240
241 /*
242 * Remove a file. POSIX 1003.2 states that, by default, attempting
243 * to remove a directory is an error, so must always stat the file.
244 */
245 while ((f = *argv++) != NULL) {
246 /* Assume if can't stat the file, can't unlink it. */
247 if (lstat(f, &sb)) {
248 if (Wflag) {
249 sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
250 } else {
251 if (!fflag || errno != ENOENT) {
252 warn("%s", f);
253 eval = 1;
254 }
255 continue;
256 }
257 } else if (Wflag) {
258 warnx("%s: %s", f, strerror(EEXIST));
259 eval = 1;
260 continue;
261 }
262
263 if (S_ISDIR(sb.st_mode) && !dflag) {
264 warnx("%s: is a directory", f);
265 eval = 1;
266 continue;
267 }
268 if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
269 continue;
270 if (S_ISWHT(sb.st_mode))
271 rval = undelete(f);
272 else if (S_ISDIR(sb.st_mode))
273 rval = rmdir(f);
274 else {
275 if (Pflag)
276 rm_overwrite(f, &sb);
277 rval = unlink(f);
278 }
279 if (rval && (!fflag || errno != ENOENT)) {
280 warn("%s", f);
281 eval = 1;
282 }
283 }
284 }
285
286 /*
287 * rm_overwrite --
288 * Overwrite the file 3 times with varying bit patterns.
289 *
290 * XXX
291 * This is a cheap way to *really* delete files. Note that only regular
292 * files are deleted, directories (and therefore names) will remain.
293 * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
294 * System V file system). In a logging file system, you'll have to have
295 * kernel support.
296 */
297 void
298 rm_overwrite(file, sbp)
299 char *file;
300 struct stat *sbp;
301 {
302 struct stat sb;
303 off_t len;
304 int fd, wlen;
305 char buf[8 * 1024];
306
307 fd = -1;
308 if (sbp == NULL) {
309 if (lstat(file, &sb))
310 goto err;
311 sbp = &sb;
312 }
313 if (!S_ISREG(sbp->st_mode))
314 return;
315 if ((fd = open(file, O_WRONLY, 0)) == -1)
316 goto err;
317
318 #define PASS(byte) { \
319 memset(buf, byte, sizeof(buf)); \
320 for (len = sbp->st_size; len > 0; len -= wlen) { \
321 wlen = len < sizeof(buf) ? len : sizeof(buf); \
322 if (write(fd, buf, wlen) != wlen) \
323 goto err; \
324 } \
325 }
326 PASS(0xff);
327 if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
328 goto err;
329 PASS(0x00);
330 if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
331 goto err;
332 PASS(0xff);
333 if (!fsync(fd) && !close(fd))
334 return;
335
336 err: eval = 1;
337 warn("%s", file);
338 }
339
340
341 int
342 check(path, name, sp)
343 char *path, *name;
344 struct stat *sp;
345 {
346 int ch, first;
347 char modep[15];
348
349 /* Check -i first. */
350 if (iflag)
351 (void)fprintf(stderr, "remove %s? ", path);
352 else {
353 /*
354 * If it's not a symbolic link and it's unwritable and we're
355 * talking to a terminal, ask. Symbolic links are excluded
356 * because their permissions are meaningless. Check stdin_ok
357 * first because we may not have stat'ed the file.
358 */
359 if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK))
360 return (1);
361 strmode(sp->st_mode, modep);
362 (void)fprintf(stderr, "override %s%s%s/%s for %s? ",
363 modep + 1, modep[9] == ' ' ? "" : " ",
364 user_from_uid(sp->st_uid, 0),
365 group_from_gid(sp->st_gid, 0), path);
366 }
367 (void)fflush(stderr);
368
369 first = ch = getchar();
370 while (ch != '\n' && ch != EOF)
371 ch = getchar();
372 return (first == 'y' || first == 'Y');
373 }
374
375 /*
376 * POSIX.2 requires that if "." or ".." are specified as the basename
377 * portion of an operand, a diagnostic message be written to standard
378 * error and nothing more be done with such operands.
379 *
380 * Since POSIX.2 defines basename as the final portion of a path after
381 * trailing slashes have been removed, we'll remove them here.
382 */
383 #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2]))
384 void
385 checkdot(argv)
386 char **argv;
387 {
388 char *p, **save, **t;
389 int complained;
390
391 complained = 0;
392 for (t = argv; *t;) {
393 /* strip trailing slashes */
394 p = strrchr (*t, '\0');
395 while (--p > *t && *p == '/')
396 *p = '\0';
397
398 /* extract basename */
399 if ((p = strrchr(*t, '/')) != NULL)
400 ++p;
401 else
402 p = *t;
403
404 if (ISDOT(p)) {
405 if (!complained++)
406 warnx("\".\" and \"..\" may not be removed");
407 eval = 1;
408 for (save = t; (t[0] = t[1]) != NULL; ++t)
409 continue;
410 t = save;
411 } else
412 ++t;
413 }
414 }
415
416 void
417 usage()
418 {
419
420 (void)fprintf(stderr, "usage: rm [-dfiPRrW] file ...\n");
421 exit(1);
422 }
423