rm.c revision 124041
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#if 0
35#ifndef lint
36static const char copyright[] =
37"@(#) Copyright (c) 1990, 1993, 1994\n\
38	The Regents of the University of California.  All rights reserved.\n";
39#endif /* not lint */
40
41#ifndef lint
42static char sccsid[] = "@(#)rm.c	8.5 (Berkeley) 4/18/94";
43#endif /* not lint */
44#endif
45#include <sys/cdefs.h>
46__FBSDID("$FreeBSD: head/bin/rm/rm.c 124041 2004-01-01 10:26:43Z kuriyama $");
47
48#include <sys/stat.h>
49#include <sys/param.h>
50#include <sys/mount.h>
51
52#include <err.h>
53#include <errno.h>
54#include <fcntl.h>
55#include <fts.h>
56#include <grp.h>
57#include <pwd.h>
58#include <stdio.h>
59#include <stdlib.h>
60#include <string.h>
61#include <sysexits.h>
62#include <unistd.h>
63
64int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
65uid_t uid;
66
67int	check(char *, char *, struct stat *);
68void	checkdot(char **);
69void	rm_file(char **);
70int	rm_overwrite(char *, struct stat *);
71void	rm_tree(char **);
72void	usage(void);
73
74/*
75 * rm --
76 *	This rm is different from historic rm's, but is expected to match
77 *	POSIX 1003.2 behavior.  The most visible difference is that -f
78 *	has two specific effects now, ignore non-existent files and force
79 * 	file removal.
80 */
81int
82main(int argc, char *argv[])
83{
84	int ch, rflag;
85	char *p;
86
87	/*
88	 * Test for the special case where the utility is called as
89	 * "unlink", for which the functionality provided is greatly
90	 * simplified.
91	 */
92	if ((p = rindex(argv[0], '/')) == NULL)
93		p = argv[0];
94	else
95		++p;
96	if (strcmp(p, "unlink") == 0) {
97		while (getopt(argc, argv, "") != -1)
98			usage();
99		argc -= optind;
100		argv += optind;
101		if (argc != 1)
102			usage();
103		rm_file(&argv[0]);
104		exit(eval);
105	}
106
107	Pflag = rflag = 0;
108	while ((ch = getopt(argc, argv, "dfiPRrvW")) != -1)
109		switch(ch) {
110		case 'd':
111			dflag = 1;
112			break;
113		case 'f':
114			fflag = 1;
115			iflag = 0;
116			break;
117		case 'i':
118			fflag = 0;
119			iflag = 1;
120			break;
121		case 'P':
122			Pflag = 1;
123			break;
124		case 'R':
125		case 'r':			/* Compatibility. */
126			rflag = 1;
127			break;
128		case 'v':
129			vflag = 1;
130			break;
131		case 'W':
132			Wflag = 1;
133			break;
134		default:
135			usage();
136		}
137	argc -= optind;
138	argv += optind;
139
140	if (argc < 1) {
141		if (fflag)
142			return (0);
143		usage();
144	}
145
146	checkdot(argv);
147	uid = geteuid();
148
149	if (*argv) {
150		stdin_ok = isatty(STDIN_FILENO);
151
152		if (rflag)
153			rm_tree(argv);
154		else
155			rm_file(argv);
156	}
157
158	exit (eval);
159}
160
161void
162rm_tree(char **argv)
163{
164	FTS *fts;
165	FTSENT *p;
166	int needstat;
167	int flags;
168	int rval;
169
170	/*
171	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
172	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
173	 */
174	needstat = !uid || (!fflag && !iflag && stdin_ok);
175
176	/*
177	 * If the -i option is specified, the user can skip on the pre-order
178	 * visit.  The fts_number field flags skipped directories.
179	 */
180#define	SKIPPED	1
181
182	flags = FTS_PHYSICAL;
183	if (!needstat)
184		flags |= FTS_NOSTAT;
185	if (Wflag)
186		flags |= FTS_WHITEOUT;
187	if (!(fts = fts_open(argv, flags, NULL)))
188		err(1, "fts_open");
189	while ((p = fts_read(fts)) != NULL) {
190		switch (p->fts_info) {
191		case FTS_DNR:
192			if (!fflag || p->fts_errno != ENOENT) {
193				warnx("%s: %s",
194				    p->fts_path, strerror(p->fts_errno));
195				eval = 1;
196			}
197			continue;
198		case FTS_ERR:
199			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
200		case FTS_NS:
201			/*
202			 * Assume that since fts_read() couldn't stat the
203			 * file, it can't be unlinked.
204			 */
205			if (!needstat)
206				break;
207			if (!fflag || p->fts_errno != ENOENT) {
208				warnx("%s: %s",
209				    p->fts_path, strerror(p->fts_errno));
210				eval = 1;
211			}
212			continue;
213		case FTS_D:
214			/* Pre-order: give user chance to skip. */
215			if (!fflag && !check(p->fts_path, p->fts_accpath,
216			    p->fts_statp)) {
217				(void)fts_set(fts, p, FTS_SKIP);
218				p->fts_number = SKIPPED;
219			}
220			else if (!uid &&
221				 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
222				 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
223				 chflags(p->fts_accpath,
224					 p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
225				goto err;
226			continue;
227		case FTS_DP:
228			/* Post-order: see if user skipped. */
229			if (p->fts_number == SKIPPED)
230				continue;
231			break;
232		default:
233			if (!fflag &&
234			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
235				continue;
236		}
237
238		rval = 0;
239		if (!uid &&
240		    (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
241		    !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
242			rval = chflags(p->fts_accpath,
243				       p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
244		if (rval == 0) {
245			/*
246			 * If we can't read or search the directory, may still be
247			 * able to remove it.  Don't print out the un{read,search}able
248			 * message unless the remove fails.
249			 */
250			switch (p->fts_info) {
251			case FTS_DP:
252			case FTS_DNR:
253				rval = rmdir(p->fts_accpath);
254				if (rval == 0 || (fflag && errno == ENOENT)) {
255					if (rval == 0 && vflag)
256						(void)printf("%s\n",
257						    p->fts_path);
258					continue;
259				}
260				break;
261
262			case FTS_W:
263				rval = undelete(p->fts_accpath);
264				if (rval == 0 && (fflag && errno == ENOENT)) {
265					if (vflag)
266						(void)printf("%s\n",
267						    p->fts_path);
268					continue;
269				}
270				break;
271
272			case FTS_NS:
273				/*
274				 * Assume that since fts_read() couldn't stat
275				 * the file, it can't be unlinked.
276				 */
277				if (fflag)
278					continue;
279				/* FALLTHROUGH */
280			default:
281				if (Pflag)
282					if (!rm_overwrite(p->fts_accpath, NULL))
283						continue;
284				rval = unlink(p->fts_accpath);
285				if (rval == 0 || (fflag && errno == ENOENT)) {
286					if (rval == 0 && vflag)
287						(void)printf("%s\n",
288						    p->fts_path);
289					continue;
290				}
291			}
292		}
293err:
294		warn("%s", p->fts_path);
295		eval = 1;
296	}
297	if (errno)
298		err(1, "fts_read");
299}
300
301void
302rm_file(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					if (!rm_overwrite(f, &sb))
350						continue;
351				rval = unlink(f);
352			}
353		}
354		if (rval && (!fflag || errno != ENOENT)) {
355			warn("%s", f);
356			eval = 1;
357		}
358		if (vflag && rval == 0)
359			(void)printf("%s\n", f);
360	}
361}
362
363/*
364 * rm_overwrite --
365 *	Overwrite the file 3 times with varying bit patterns.
366 *
367 * XXX
368 * This is a cheap way to *really* delete files.  Note that only regular
369 * files are deleted, directories (and therefore names) will remain.
370 * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
371 * System V file system).  In a logging file system, you'll have to have
372 * kernel support.
373 */
374int
375rm_overwrite(char *file, struct stat *sbp)
376{
377	struct stat sb;
378	struct statfs fsb;
379	off_t len;
380	int bsize, fd, wlen;
381	char *buf = NULL;
382
383	fd = -1;
384	if (sbp == NULL) {
385		if (lstat(file, &sb))
386			goto err;
387		sbp = &sb;
388	}
389	if (!S_ISREG(sbp->st_mode))
390		return (1);
391	if ((fd = open(file, O_WRONLY, 0)) == -1)
392		goto err;
393	if (fstatfs(fd, &fsb) == -1)
394		goto err;
395	bsize = MAX(fsb.f_iosize, 1024);
396	if ((buf = malloc(bsize)) == NULL)
397		err(1, "%s: malloc", file);
398
399#define	PASS(byte) {							\
400	memset(buf, byte, bsize);					\
401	for (len = sbp->st_size; len > 0; len -= wlen) {		\
402		wlen = len < bsize ? len : bsize;			\
403		if (write(fd, buf, wlen) != wlen)			\
404			goto err;					\
405	}								\
406}
407	PASS(0xff);
408	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
409		goto err;
410	PASS(0x00);
411	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
412		goto err;
413	PASS(0xff);
414	if (!fsync(fd) && !close(fd)) {
415		free(buf);
416		return (1);
417	}
418
419err:	eval = 1;
420	if (buf)
421		free(buf);
422	if (fd != -1)
423		close(fd);
424	warn("%s", file);
425	return (0);
426}
427
428
429int
430check(char *path, char *name, 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		 * Also skip this check if the -P option was specified because
445		 * we will not be able to overwrite file contents and will
446		 * barf later.
447		 */
448		if (!stdin_ok || S_ISLNK(sp->st_mode) || Pflag ||
449		    (!access(name, W_OK) &&
450		    !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
451		    (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
452			return (1);
453		strmode(sp->st_mode, modep);
454		if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
455			err(1, "fflagstostr");
456		(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
457		    modep + 1, modep[9] == ' ' ? "" : " ",
458		    user_from_uid(sp->st_uid, 0),
459		    group_from_gid(sp->st_gid, 0),
460		    *flagsp ? flagsp : "", *flagsp ? " " : "",
461		    path);
462		free(flagsp);
463	}
464	(void)fflush(stderr);
465
466	first = ch = getchar();
467	while (ch != '\n' && ch != EOF)
468		ch = getchar();
469	return (first == 'y' || first == 'Y');
470}
471
472#define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
473void
474checkdot(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
497void
498usage(void)
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}
506