Commit | Line | Data |
---|---|---|
7f9ef9cc | 1 | /*- |
5fa310b5 | 2 | * Copyright (c) 1990, 1993, 1994 |
51d86845 | 3 | * The Regents of the University of California. All rights reserved. |
7f9ef9cc KB |
4 | * |
5 | * %sccs.include.redist.c% | |
d1e4967d | 6 | */ |
ae80f4a2 | 7 | |
7f9ef9cc | 8 | #ifndef lint |
51d86845 | 9 | static char copyright[] = |
5fa310b5 | 10 | "@(#) Copyright (c) 1990, 1993, 1994\n\ |
51d86845 | 11 | The Regents of the University of California. All rights reserved.\n"; |
7f9ef9cc | 12 | #endif /* not lint */ |
d1e4967d | 13 | |
7f9ef9cc | 14 | #ifndef lint |
815897e8 | 15 | static char sccsid[] = "@(#)rm.c 8.8 (Berkeley) %G%"; |
7f9ef9cc | 16 | #endif /* not lint */ |
d1e4967d | 17 | |
7f9ef9cc KB |
18 | #include <sys/types.h> |
19 | #include <sys/stat.h> | |
411a0820 KB |
20 | |
21 | #include <err.h> | |
706c0358 | 22 | #include <errno.h> |
60f2fa5e | 23 | #include <fcntl.h> |
7f9ef9cc | 24 | #include <fts.h> |
7f9ef9cc | 25 | #include <stdio.h> |
7f9ef9cc | 26 | #include <stdlib.h> |
411a0820 KB |
27 | #include <string.h> |
28 | #include <unistd.h> | |
29 | ||
88edbcd4 | 30 | int dflag, eval, fflag, iflag, Pflag, Wflag, stdin_ok; |
ae80f4a2 | 31 | |
411a0820 KB |
32 | int check __P((char *, char *, struct stat *)); |
33 | void checkdot __P((char **)); | |
60f2fa5e KB |
34 | void rm_file __P((char **)); |
35 | void rm_overwrite __P((char *, struct stat *)); | |
36 | void rm_tree __P((char **)); | |
411a0820 | 37 | void usage __P((void)); |
7f9ef9cc KB |
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 | */ | |
411a0820 | 46 | int |
ae80f4a2 | 47 | main(argc, argv) |
8161a03a | 48 | int argc; |
411a0820 | 49 | char *argv[]; |
ae80f4a2 | 50 | { |
7f9ef9cc | 51 | int ch, rflag; |
ae80f4a2 | 52 | |
60f2fa5e | 53 | Pflag = rflag = 0; |
815897e8 | 54 | while ((ch = getopt(argc, argv, "dfiPRrW")) != -1) |
7f9ef9cc KB |
55 | switch(ch) { |
56 | case 'd': | |
57 | dflag = 1; | |
58 | break; | |
8161a03a | 59 | case 'f': |
7f9ef9cc KB |
60 | fflag = 1; |
61 | iflag = 0; | |
d1e4967d | 62 | break; |
8161a03a | 63 | case 'i': |
7f9ef9cc KB |
64 | fflag = 0; |
65 | iflag = 1; | |
8161a03a | 66 | break; |
60f2fa5e KB |
67 | case 'P': |
68 | Pflag = 1; | |
69 | break; | |
8161a03a | 70 | case 'R': |
60f2fa5e | 71 | case 'r': /* Compatibility. */ |
7f9ef9cc | 72 | rflag = 1; |
8161a03a | 73 | break; |
88edbcd4 JSP |
74 | case 'W': |
75 | Wflag = 1; | |
76 | break; | |
8161a03a KB |
77 | case '?': |
78 | default: | |
79 | usage(); | |
80 | } | |
7f9ef9cc KB |
81 | argc -= optind; |
82 | argv += optind; | |
ca02483b | 83 | |
7f9ef9cc | 84 | if (argc < 1) |
8161a03a | 85 | usage(); |
ae80f4a2 | 86 | |
7f9ef9cc | 87 | checkdot(argv); |
d1e4967d | 88 | |
815897e8 KB |
89 | if (*argv) { |
90 | stdin_ok = isatty(STDIN_FILENO); | |
91 | ||
92 | if (rflag) | |
93 | rm_tree(argv); | |
94 | else | |
95 | rm_file(argv); | |
96 | } | |
7f9ef9cc | 97 | |
411a0820 | 98 | exit (eval); |
7f9ef9cc KB |
99 | } |
100 | ||
411a0820 | 101 | void |
60f2fa5e | 102 | rm_tree(argv) |
7f9ef9cc KB |
103 | char **argv; |
104 | { | |
706c0358 JSP |
105 | FTS *fts; |
106 | FTSENT *p; | |
107 | int needstat; | |
88edbcd4 | 108 | int flags; |
7f9ef9cc KB |
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 | ||
88edbcd4 JSP |
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))) | |
411a0820 | 128 | err(1, NULL); |
5fa310b5 | 129 | while ((p = fts_read(fts)) != NULL) { |
411a0820 | 130 | switch (p->fts_info) { |
7f9ef9cc | 131 | case FTS_DNR: |
5fa310b5 KB |
132 | if (!fflag || p->fts_errno != ENOENT) { |
133 | warnx("%s: %s", | |
134 | p->fts_path, strerror(p->fts_errno)); | |
d6a1ced2 KB |
135 | eval = 1; |
136 | } | |
137 | continue; | |
7f9ef9cc | 138 | case FTS_ERR: |
5fa310b5 | 139 | errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno)); |
7f9ef9cc | 140 | case FTS_NS: |
411a0820 KB |
141 | /* |
142 | * FTS_NS: assume that if can't stat the file, it | |
143 | * can't be unlinked. | |
144 | */ | |
7f9ef9cc | 145 | if (!needstat) |
d1e4967d | 146 | break; |
5fa310b5 KB |
147 | if (!fflag || p->fts_errno != ENOENT) { |
148 | warnx("%s: %s", | |
149 | p->fts_path, strerror(p->fts_errno)); | |
411a0820 KB |
150 | eval = 1; |
151 | } | |
7f9ef9cc | 152 | continue; |
7f9ef9cc | 153 | case FTS_D: |
411a0820 | 154 | /* Pre-order: give user chance to skip. */ |
815897e8 | 155 | if (!fflag && !check(p->fts_path, p->fts_accpath, |
834beb60 | 156 | p->fts_statp)) { |
7f9ef9cc KB |
157 | (void)fts_set(fts, p, FTS_SKIP); |
158 | p->fts_number = SKIPPED; | |
d1e4967d | 159 | } |
7f9ef9cc | 160 | continue; |
7f9ef9cc | 161 | case FTS_DP: |
411a0820 | 162 | /* Post-order: see if user skipped. */ |
7f9ef9cc KB |
163 | if (p->fts_number == SKIPPED) |
164 | continue; | |
165 | break; | |
815897e8 KB |
166 | default: |
167 | if (!fflag && | |
168 | !check(p->fts_path, p->fts_accpath, p->fts_statp)) | |
169 | continue; | |
d1e4967d | 170 | } |
7f9ef9cc KB |
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 | */ | |
88edbcd4 JSP |
177 | switch (p->fts_info) { |
178 | case FTS_DP: | |
179 | case FTS_DNR: | |
815897e8 | 180 | if (!rmdir(p->fts_accpath) || fflag && errno == ENOENT) |
7f9ef9cc | 181 | continue; |
88edbcd4 JSP |
182 | break; |
183 | ||
184 | case FTS_W: | |
607ed6e9 | 185 | if (!undelete(p->fts_accpath) || |
88edbcd4 JSP |
186 | fflag && errno == ENOENT) |
187 | continue; | |
188 | break; | |
189 | ||
190 | default: | |
60f2fa5e KB |
191 | if (Pflag) |
192 | rm_overwrite(p->fts_accpath, NULL); | |
193 | if (!unlink(p->fts_accpath) || fflag && errno == ENOENT) | |
194 | continue; | |
195 | } | |
411a0820 KB |
196 | warn("%s", p->fts_path); |
197 | eval = 1; | |
ae80f4a2 | 198 | } |
5fa310b5 KB |
199 | if (errno) |
200 | err(1, "fts_read"); | |
7f9ef9cc KB |
201 | } |
202 | ||
411a0820 | 203 | void |
60f2fa5e | 204 | rm_file(argv) |
7f9ef9cc KB |
205 | char **argv; |
206 | { | |
7f9ef9cc | 207 | struct stat sb; |
815897e8 | 208 | int rval; |
60f2fa5e | 209 | char *f; |
ae80f4a2 | 210 | |
7f9ef9cc KB |
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 | */ | |
5fa310b5 | 215 | while ((f = *argv++) != NULL) { |
7f9ef9cc KB |
216 | /* Assume if can't stat the file, can't unlink it. */ |
217 | if (lstat(f, &sb)) { | |
88edbcd4 JSP |
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; | |
411a0820 | 226 | } |
88edbcd4 JSP |
227 | } else if (Wflag) { |
228 | warnx("%s: %s", f, strerror(EEXIST)); | |
229 | eval = 1; | |
7f9ef9cc | 230 | continue; |
ae80f4a2 | 231 | } |
88edbcd4 | 232 | |
815897e8 | 233 | if (S_ISDIR(sb.st_mode) && !dflag) { |
411a0820 KB |
234 | warnx("%s: is a directory", f); |
235 | eval = 1; | |
7f9ef9cc | 236 | continue; |
b2704a87 | 237 | } |
88edbcd4 | 238 | if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb)) |
7f9ef9cc | 239 | continue; |
88edbcd4 | 240 | if (S_ISWHT(sb.st_mode)) |
607ed6e9 | 241 | rval = undelete(f); |
88edbcd4 | 242 | else if (S_ISDIR(sb.st_mode)) |
60f2fa5e KB |
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)) { | |
411a0820 KB |
250 | warn("%s", f); |
251 | eval = 1; | |
252 | } | |
ae80f4a2 BJ |
253 | } |
254 | } | |
255 | ||
60f2fa5e KB |
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 | |
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 | ||
411a0820 | 311 | int |
7f9ef9cc KB |
312 | check(path, name, sp) |
313 | char *path, *name; | |
314 | struct stat *sp; | |
ae80f4a2 | 315 | { |
706c0358 | 316 | int ch, first; |
411a0820 | 317 | char modep[15]; |
ae80f4a2 | 318 | |
7f9ef9cc KB |
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 | |
91c2220f KB |
326 | * because their permissions are meaningless. Check stdin_ok |
327 | * first because we may not have stat'ed the file. | |
7f9ef9cc | 328 | */ |
91c2220f | 329 | if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK)) |
411a0820 | 330 | return (1); |
7f9ef9cc KB |
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(); | |
411a0820 | 342 | return (first == 'y'); |
d1e4967d RC |
343 | } |
344 | ||
7f9ef9cc | 345 | #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2])) |
411a0820 | 346 | void |
7f9ef9cc KB |
347 | checkdot(argv) |
348 | char **argv; | |
349 | { | |
706c0358 | 350 | char *p, **save, **t; |
7f9ef9cc KB |
351 | int complained; |
352 | ||
353 | complained = 0; | |
354 | for (t = argv; *t;) { | |
5fa310b5 | 355 | if ((p = strrchr(*t, '/')) != NULL) |
7f9ef9cc KB |
356 | ++p; |
357 | else | |
358 | p = *t; | |
359 | if (ISDOT(p)) { | |
360 | if (!complained++) | |
411a0820 KB |
361 | warnx("\".\" and \"..\" may not be removed"); |
362 | eval = 1; | |
815897e8 KB |
363 | for (save = t; (t[0] = t[1]) != NULL; ++t) |
364 | continue; | |
7f9ef9cc KB |
365 | t = save; |
366 | } else | |
367 | ++t; | |
368 | } | |
369 | } | |
370 | ||
411a0820 | 371 | void |
8161a03a KB |
372 | usage() |
373 | { | |
706c0358 | 374 | |
815897e8 | 375 | (void)fprintf(stderr, "usage: rm [-dfiPRrW] file ...\n"); |
8161a03a KB |
376 | exit(1); |
377 | } |