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