xref: /original-bsd/bin/rm/rm.c (revision 00695d63)
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
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
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
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
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
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
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
372 usage()
373 {
374 
375 	(void)fprintf(stderr, "usage: rm [-dfiPRrW] file ...\n");
376 	exit(1);
377 }
378