xref: /csrg-svn/bin/rm/rm.c (revision 67575)
145769Sbostic /*-
266605Sbostic  * Copyright (c) 1990, 1993, 1994
361008Sbostic  *	The Regents of the University of California.  All rights reserved.
445769Sbostic  *
545769Sbostic  * %sccs.include.redist.c%
618277Sralph  */
718277Sralph 
845769Sbostic #ifndef lint
961008Sbostic static char copyright[] =
1066605Sbostic "@(#) Copyright (c) 1990, 1993, 1994\n\
1161008Sbostic 	The Regents of the University of California.  All rights reserved.\n";
1245769Sbostic #endif /* not lint */
1345769Sbostic 
1445769Sbostic #ifndef lint
15*67575Spendry static char sccsid[] = "@(#)rm.c	8.6 (Berkeley) 07/28/94";
1645769Sbostic #endif /* not lint */
1745769Sbostic 
1845769Sbostic #include <sys/types.h>
1945769Sbostic #include <sys/stat.h>
2059515Sbostic 
2159515Sbostic #include <err.h>
2266561Spendry #include <errno.h>
2366860Sbostic #include <fcntl.h>
2445769Sbostic #include <fts.h>
251083Sbill #include <stdio.h>
2659515Sbostic #include <stdlib.h>
2745769Sbostic #include <string.h>
2859515Sbostic #include <unistd.h>
291083Sbill 
30*67575Spendry int dflag, eval, fflag, iflag, Pflag, Wflag, stdin_ok;
311083Sbill 
3259515Sbostic int	check __P((char *, char *, struct stat *));
3359515Sbostic void	checkdot __P((char **));
3466860Sbostic void	rm_file __P((char **));
3566860Sbostic void	rm_overwrite __P((char *, struct stat *));
3666860Sbostic void	rm_tree __P((char **));
3759515Sbostic void	usage __P((void));
3859515Sbostic 
3945769Sbostic /*
4045769Sbostic  * rm --
4145769Sbostic  *	This rm is different from historic rm's, but is expected to match
4245769Sbostic  *	POSIX 1003.2 behavior.  The most visible difference is that -f
4345769Sbostic  *	has two specific effects now, ignore non-existent files and force
4445769Sbostic  * 	file removal.
4545769Sbostic  */
4659515Sbostic int
471083Sbill main(argc, argv)
4834044Sbostic 	int argc;
4959515Sbostic 	char *argv[];
501083Sbill {
5145769Sbostic 	int ch, rflag;
521083Sbill 
5366860Sbostic 	Pflag = rflag = 0;
54*67575Spendry 	while ((ch = getopt(argc, argv, "dfiPRrW")) != EOF)
5545769Sbostic 		switch(ch) {
5645769Sbostic 		case 'd':
5745769Sbostic 			dflag = 1;
5845769Sbostic 			break;
5934044Sbostic 		case 'f':
6045769Sbostic 			fflag = 1;
6145769Sbostic 			iflag = 0;
6218277Sralph 			break;
6334044Sbostic 		case 'i':
6445769Sbostic 			fflag = 0;
6545769Sbostic 			iflag = 1;
6634044Sbostic 			break;
6766860Sbostic 		case 'P':
6866860Sbostic 			Pflag = 1;
6966860Sbostic 			break;
7034044Sbostic 		case 'R':
7166860Sbostic 		case 'r':			/* Compatibility. */
7245769Sbostic 			rflag = 1;
7334044Sbostic 			break;
74*67575Spendry 		case 'W':
75*67575Spendry 			Wflag = 1;
76*67575Spendry 			break;
7734044Sbostic 		case '?':
7834044Sbostic 		default:
7934044Sbostic 			usage();
8034044Sbostic 		}
8145769Sbostic 	argc -= optind;
8245769Sbostic 	argv += optind;
831932Sroot 
8445769Sbostic 	if (argc < 1)
8534044Sbostic 		usage();
8645769Sbostic 
8745769Sbostic 	checkdot(argv);
8845769Sbostic 	if (!*argv)
8959515Sbostic 		exit (eval);
9045769Sbostic 
9145769Sbostic 	stdin_ok = isatty(STDIN_FILENO);
9245769Sbostic 
9345769Sbostic 	if (rflag)
9466860Sbostic 		rm_tree(argv);
9545769Sbostic 	else
9666860Sbostic 		rm_file(argv);
9759515Sbostic 	exit (eval);
981083Sbill }
991083Sbill 
10059515Sbostic void
10166860Sbostic rm_tree(argv)
10245769Sbostic 	char **argv;
1031083Sbill {
10466561Spendry 	FTS *fts;
10566561Spendry 	FTSENT *p;
10666561Spendry 	int needstat;
107*67575Spendry 	int flags;
1081083Sbill 
10945769Sbostic 	/*
11045769Sbostic 	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
11145769Sbostic 	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
11245769Sbostic 	 */
11345769Sbostic 	needstat = !fflag && !iflag && stdin_ok;
11445769Sbostic 
11545769Sbostic 	/*
11645769Sbostic 	 * If the -i option is specified, the user can skip on the pre-order
11745769Sbostic 	 * visit.  The fts_number field flags skipped directories.
11845769Sbostic 	 */
11945769Sbostic #define	SKIPPED	1
12045769Sbostic 
121*67575Spendry 	flags = FTS_PHYSICAL;
122*67575Spendry 	if (!needstat)
123*67575Spendry 		flags |= FTS_NOSTAT;
124*67575Spendry 	if (Wflag)
125*67575Spendry 		flags |= FTS_WHITEOUT;
126*67575Spendry 	if (!(fts = fts_open(argv, flags, (int (*)())NULL)))
12759515Sbostic 		err(1, NULL);
12866605Sbostic 	while ((p = fts_read(fts)) != NULL) {
12959515Sbostic 		switch (p->fts_info) {
13045769Sbostic 		case FTS_DNR:
13166605Sbostic 			if (!fflag || p->fts_errno != ENOENT) {
13266605Sbostic 				warnx("%s: %s",
13366605Sbostic 				    p->fts_path, strerror(p->fts_errno));
13461007Sbostic 				eval = 1;
13561007Sbostic 			}
13661007Sbostic 			continue;
13745769Sbostic 		case FTS_ERR:
13866605Sbostic 			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
13945769Sbostic 		case FTS_NS:
14059515Sbostic 			/*
14159515Sbostic 			 * FTS_NS: assume that if can't stat the file, it
14259515Sbostic 			 * can't be unlinked.
14359515Sbostic 			 */
14445769Sbostic 			if (!needstat)
14545769Sbostic 				break;
14666605Sbostic 			if (!fflag || p->fts_errno != ENOENT) {
14766605Sbostic 				warnx("%s: %s",
14866605Sbostic 				    p->fts_path, strerror(p->fts_errno));
14959515Sbostic 				eval = 1;
15059515Sbostic 			}
15145769Sbostic 			continue;
15245769Sbostic 		case FTS_D:
15359515Sbostic 			/* Pre-order: give user chance to skip. */
15445769Sbostic 			if (iflag && !check(p->fts_path, p->fts_accpath,
15552238Sbostic 			    p->fts_statp)) {
15645769Sbostic 				(void)fts_set(fts, p, FTS_SKIP);
15745769Sbostic 				p->fts_number = SKIPPED;
15818327Sralph 			}
15945769Sbostic 			continue;
16045769Sbostic 		case FTS_DP:
16159515Sbostic 			/* Post-order: see if user skipped. */
16245769Sbostic 			if (p->fts_number == SKIPPED)
16345769Sbostic 				continue;
16445769Sbostic 			break;
16518277Sralph 		}
16645769Sbostic 		if (!fflag &&
16752238Sbostic 		    !check(p->fts_path, p->fts_accpath, p->fts_statp))
16845769Sbostic 			continue;
16945769Sbostic 
17045769Sbostic 		/*
17145769Sbostic 		 * If we can't read or search the directory, may still be
17245769Sbostic 		 * able to remove it.  Don't print out the un{read,search}able
17345769Sbostic 		 * message unless the remove fails.
17445769Sbostic 		 */
175*67575Spendry 		switch (p->fts_info) {
176*67575Spendry 		case FTS_DP:
177*67575Spendry 		case FTS_DNR:
17845769Sbostic 			if (!rmdir(p->fts_accpath))
17918277Sralph 				continue;
18045769Sbostic 			if (errno == ENOENT) {
18145769Sbostic 				if (fflag)
18245769Sbostic 					continue;
18345769Sbostic 			} else if (p->fts_info != FTS_DP)
18459515Sbostic 				warnx("%s: unable to read", p->fts_path);
185*67575Spendry 			break;
186*67575Spendry 
187*67575Spendry 		case FTS_W:
188*67575Spendry 			if (!unwhiteout(p->fts_accpath) ||
189*67575Spendry 			    fflag && errno == ENOENT)
190*67575Spendry 				continue;
191*67575Spendry 			break;
192*67575Spendry 
193*67575Spendry 		default:
19466860Sbostic 			if (Pflag)
19566860Sbostic 				rm_overwrite(p->fts_accpath, NULL);
19666860Sbostic 			if (!unlink(p->fts_accpath) || fflag && errno == ENOENT)
19766860Sbostic 				continue;
19866860Sbostic 		}
19959515Sbostic 		warn("%s", p->fts_path);
20059515Sbostic 		eval = 1;
2011083Sbill 	}
20266605Sbostic 	if (errno)
20366605Sbostic 		err(1, "fts_read");
20445769Sbostic }
2051083Sbill 
20659515Sbostic void
20766860Sbostic rm_file(argv)
20845769Sbostic 	char **argv;
20945769Sbostic {
21066860Sbostic 	struct stat sb;
21166860Sbostic 	int df, rval;
21266561Spendry 	char *f;
21345769Sbostic 
21445769Sbostic 	df = dflag;
21545769Sbostic 	/*
21645769Sbostic 	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
21745769Sbostic 	 * to remove a directory is an error, so must always stat the file.
21845769Sbostic 	 */
21966605Sbostic 	while ((f = *argv++) != NULL) {
22045769Sbostic 		/* Assume if can't stat the file, can't unlink it. */
22145769Sbostic 		if (lstat(f, &sb)) {
222*67575Spendry 			if (Wflag) {
223*67575Spendry 				sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
224*67575Spendry 			} else {
225*67575Spendry 				if (!fflag || errno != ENOENT) {
226*67575Spendry 					warn("%s", f);
227*67575Spendry 					eval = 1;
228*67575Spendry 				}
229*67575Spendry 				continue;
23059515Sbostic 			}
231*67575Spendry 		} else if (Wflag) {
232*67575Spendry 			warnx("%s: %s", f, strerror(EEXIST));
233*67575Spendry 			eval = 1;
23445769Sbostic 			continue;
2351083Sbill 		}
236*67575Spendry 
23745769Sbostic 		if (S_ISDIR(sb.st_mode) && !df) {
23859515Sbostic 			warnx("%s: is a directory", f);
23959515Sbostic 			eval = 1;
24045769Sbostic 			continue;
24118327Sralph 		}
242*67575Spendry 		if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
24345769Sbostic 			continue;
244*67575Spendry 		if (S_ISWHT(sb.st_mode))
245*67575Spendry 			rval = unwhiteout(f);
246*67575Spendry 		else if (S_ISDIR(sb.st_mode))
24766860Sbostic 			rval = rmdir(f);
24866860Sbostic 		else {
24966860Sbostic 			if (Pflag)
25066860Sbostic 				rm_overwrite(f, &sb);
25166860Sbostic 			rval = unlink(f);
25266860Sbostic 		}
25366860Sbostic 		if (rval && (!fflag || errno != ENOENT)) {
25459515Sbostic 			warn("%s", f);
25559515Sbostic 			eval = 1;
25659515Sbostic 		}
2571083Sbill 	}
2581083Sbill }
2591083Sbill 
26066860Sbostic /*
26166860Sbostic  * rm_overwrite --
26266860Sbostic  *	Overwrite the file 3 times with varying bit patterns.
26366860Sbostic  *
26466860Sbostic  * XXX
26566860Sbostic  * This is a cheap way to *really* delete files.  Note that only regular
26666860Sbostic  * files are deleted, directories (and therefore names) will remain.
26766860Sbostic  * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
26866860Sbostic  * System V file system).  In a logging file system, you'll have to have
26966860Sbostic  * kernel support.
27066860Sbostic  */
27166860Sbostic void
27266860Sbostic rm_overwrite(file, sbp)
27366860Sbostic 	char *file;
27466860Sbostic 	struct stat *sbp;
27566860Sbostic {
27666860Sbostic 	struct stat sb;
27766860Sbostic 	off_t len;
27866860Sbostic 	int fd, wlen;
27966860Sbostic 	char buf[8 * 1024];
28066860Sbostic 
28166860Sbostic 	fd = -1;
28266860Sbostic 	if (sbp == NULL) {
28366860Sbostic 		if (lstat(file, &sb))
28466860Sbostic 			goto err;
28566860Sbostic 		sbp = &sb;
28666860Sbostic 	}
28766860Sbostic 	if (!S_ISREG(sbp->st_mode))
28866860Sbostic 		return;
28966860Sbostic 	if ((fd = open(file, O_WRONLY, 0)) == -1)
29066860Sbostic 		goto err;
29166860Sbostic 
29266860Sbostic #define	PASS(byte) {							\
29366860Sbostic 	memset(buf, byte, sizeof(buf));					\
29466860Sbostic 	for (len = sbp->st_size; len > 0; len -= wlen) {		\
29566860Sbostic 		wlen = len < sizeof(buf) ? len : sizeof(buf);		\
29666860Sbostic 		if (write(fd, buf, wlen) != wlen)			\
29766860Sbostic 			goto err;					\
29866860Sbostic 	}								\
29966860Sbostic }
30066860Sbostic 	PASS(0xff);
30166860Sbostic 	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
30266860Sbostic 		goto err;
30366860Sbostic 	PASS(0x00);
30466860Sbostic 	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
30566860Sbostic 		goto err;
30666860Sbostic 	PASS(0xff);
30766860Sbostic 	if (!fsync(fd) && !close(fd))
30866860Sbostic 		return;
30966860Sbostic 
31066860Sbostic err:	eval = 1;
31166860Sbostic 	warn("%s", file);
31266860Sbostic }
31366860Sbostic 
31466860Sbostic 
31559515Sbostic int
31645769Sbostic check(path, name, sp)
31745769Sbostic 	char *path, *name;
31845769Sbostic 	struct stat *sp;
3191083Sbill {
32066561Spendry 	int ch, first;
32159515Sbostic 	char modep[15];
3221083Sbill 
32345769Sbostic 	/* Check -i first. */
32445769Sbostic 	if (iflag)
32545769Sbostic 		(void)fprintf(stderr, "remove %s? ", path);
32645769Sbostic 	else {
32745769Sbostic 		/*
32845769Sbostic 		 * If it's not a symbolic link and it's unwritable and we're
32945769Sbostic 		 * talking to a terminal, ask.  Symbolic links are excluded
33065344Sbostic 		 * because their permissions are meaningless.  Check stdin_ok
33165344Sbostic 		 * first because we may not have stat'ed the file.
33245769Sbostic 		 */
33365344Sbostic 		if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK))
33459515Sbostic 			return (1);
33545769Sbostic 		strmode(sp->st_mode, modep);
33645769Sbostic 		(void)fprintf(stderr, "override %s%s%s/%s for %s? ",
33745769Sbostic 		    modep + 1, modep[9] == ' ' ? "" : " ",
33845769Sbostic 		    user_from_uid(sp->st_uid, 0),
33945769Sbostic 		    group_from_gid(sp->st_gid, 0), path);
34045769Sbostic 	}
34145769Sbostic 	(void)fflush(stderr);
34245769Sbostic 
34345769Sbostic 	first = ch = getchar();
34445769Sbostic 	while (ch != '\n' && ch != EOF)
34545769Sbostic 		ch = getchar();
34659515Sbostic 	return (first == 'y');
3471083Sbill }
34818277Sralph 
34945769Sbostic #define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2]))
35059515Sbostic void
35145769Sbostic checkdot(argv)
35245769Sbostic 	char **argv;
35318277Sralph {
35466561Spendry 	char *p, **save, **t;
35545769Sbostic 	int complained;
35618277Sralph 
35745769Sbostic 	complained = 0;
35845769Sbostic 	for (t = argv; *t;) {
35966605Sbostic 		if ((p = strrchr(*t, '/')) != NULL)
36045769Sbostic 			++p;
36145769Sbostic 		else
36245769Sbostic 			p = *t;
36345769Sbostic 		if (ISDOT(p)) {
36445769Sbostic 			if (!complained++)
36559515Sbostic 				warnx("\".\" and \"..\" may not be removed");
36659515Sbostic 			eval = 1;
36766605Sbostic 			for (save = t; (t[0] = t[1]) != NULL; ++t);
36845769Sbostic 			t = save;
36945769Sbostic 		} else
37045769Sbostic 			++t;
37145769Sbostic 	}
37218277Sralph }
37334044Sbostic 
37459515Sbostic void
37534044Sbostic usage()
37634044Sbostic {
37766561Spendry 
37845769Sbostic 	(void)fprintf(stderr, "usage: rm [-dfiRr] file ...\n");
37934044Sbostic 	exit(1);
38034044Sbostic }
381