rm.c revision 53819
11556Srgrimes/*-
21556Srgrimes * Copyright (c) 1990, 1993, 1994
31556Srgrimes *	The Regents of the University of California.  All rights reserved.
41556Srgrimes *
51556Srgrimes * Redistribution and use in source and binary forms, with or without
61556Srgrimes * modification, are permitted provided that the following conditions
71556Srgrimes * are met:
81556Srgrimes * 1. Redistributions of source code must retain the above copyright
91556Srgrimes *    notice, this list of conditions and the following disclaimer.
101556Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111556Srgrimes *    notice, this list of conditions and the following disclaimer in the
121556Srgrimes *    documentation and/or other materials provided with the distribution.
131556Srgrimes * 3. All advertising materials mentioning features or use of this software
141556Srgrimes *    must display the following acknowledgement:
151556Srgrimes *	This product includes software developed by the University of
161556Srgrimes *	California, Berkeley and its contributors.
171556Srgrimes * 4. Neither the name of the University nor the names of its contributors
181556Srgrimes *    may be used to endorse or promote products derived from this software
191556Srgrimes *    without specific prior written permission.
201556Srgrimes *
211556Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221556Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231556Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241556Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251556Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261556Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271556Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281556Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291556Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301556Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311556Srgrimes * SUCH DAMAGE.
321556Srgrimes */
331556Srgrimes
341556Srgrimes#ifndef lint
3527959Sstevestatic const char copyright[] =
361556Srgrimes"@(#) Copyright (c) 1990, 1993, 1994\n\
371556Srgrimes	The Regents of the University of California.  All rights reserved.\n";
3827964Ssteve#endif /* not lint */
3927964Ssteve
4027964Ssteve#ifndef lint
4127964Ssteve#if 0
4227964Sstevestatic char sccsid[] = "@(#)rm.c	8.5 (Berkeley) 4/18/94";
4327964Ssteve#else
4427959Sstevestatic const char rcsid[] =
4550471Speter  "$FreeBSD: head/bin/rm/rm.c 53819 1999-11-28 09:34:21Z mharo $";
4627964Ssteve#endif
471556Srgrimes#endif /* not lint */
481556Srgrimes
491556Srgrimes#include <sys/types.h>
501556Srgrimes#include <sys/stat.h>
5147584Skris#include <sys/param.h>
5247584Skris#include <sys/mount.h>
531556Srgrimes
541556Srgrimes#include <err.h>
551556Srgrimes#include <errno.h>
561556Srgrimes#include <fcntl.h>
571556Srgrimes#include <fts.h>
581556Srgrimes#include <stdio.h>
591556Srgrimes#include <stdlib.h>
601556Srgrimes#include <string.h>
6150539Smharo#include <sysexits.h>
621556Srgrimes#include <unistd.h>
631556Srgrimes
647798Sacheextern char *flags_to_string __P((u_long, char *));
657798Sache
6650872Smharoint dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
677798Sacheuid_t uid;
681556Srgrimes
691556Srgrimesint	check __P((char *, char *, struct stat *));
701556Srgrimesvoid	checkdot __P((char **));
711556Srgrimesvoid	rm_file __P((char **));
721556Srgrimesvoid	rm_overwrite __P((char *, struct stat *));
731556Srgrimesvoid	rm_tree __P((char **));
741556Srgrimesvoid	usage __P((void));
751556Srgrimes
761556Srgrimes/*
771556Srgrimes * rm --
781556Srgrimes *	This rm is different from historic rm's, but is expected to match
791556Srgrimes *	POSIX 1003.2 behavior.  The most visible difference is that -f
801556Srgrimes *	has two specific effects now, ignore non-existent files and force
811556Srgrimes * 	file removal.
821556Srgrimes */
831556Srgrimesint
841556Srgrimesmain(argc, argv)
851556Srgrimes	int argc;
861556Srgrimes	char *argv[];
871556Srgrimes{
881556Srgrimes	int ch, rflag;
891556Srgrimes
9020421Ssteve	Pflag = rflag = 0;
9150872Smharo	while ((ch = getopt(argc, argv, "dfiPRrvW")) != -1)
921556Srgrimes		switch(ch) {
931556Srgrimes		case 'd':
941556Srgrimes			dflag = 1;
951556Srgrimes			break;
961556Srgrimes		case 'f':
971556Srgrimes			fflag = 1;
981556Srgrimes			iflag = 0;
991556Srgrimes			break;
1001556Srgrimes		case 'i':
1011556Srgrimes			fflag = 0;
1021556Srgrimes			iflag = 1;
1031556Srgrimes			break;
1041556Srgrimes		case 'P':
1051556Srgrimes			Pflag = 1;
1061556Srgrimes			break;
1071556Srgrimes		case 'R':
1081556Srgrimes		case 'r':			/* Compatibility. */
1091556Srgrimes			rflag = 1;
1101556Srgrimes			break;
11150872Smharo		case 'v':
11250872Smharo			vflag = 1;
11350872Smharo			break;
11420421Ssteve		case 'W':
11520421Ssteve			Wflag = 1;
11620421Ssteve			break;
1171556Srgrimes		default:
1181556Srgrimes			usage();
1191556Srgrimes		}
1201556Srgrimes	argc -= optind;
1211556Srgrimes	argv += optind;
1221556Srgrimes
12344282Sjkh	if (argc < 1) {
12444282Sjkh		if (fflag)
12544282Sjkh			return 0;
1261556Srgrimes		usage();
12744282Sjkh	}
1281556Srgrimes
1291556Srgrimes	checkdot(argv);
1307798Sache	uid = geteuid();
1311556Srgrimes
13220421Ssteve	if (*argv) {
13320421Ssteve		stdin_ok = isatty(STDIN_FILENO);
13420421Ssteve
13520421Ssteve		if (rflag)
13620421Ssteve			rm_tree(argv);
13720421Ssteve		else
13820421Ssteve			rm_file(argv);
13920421Ssteve	}
14020421Ssteve
1411556Srgrimes	exit (eval);
1421556Srgrimes}
1431556Srgrimes
1441556Srgrimesvoid
1451556Srgrimesrm_tree(argv)
1461556Srgrimes	char **argv;
1471556Srgrimes{
1481556Srgrimes	FTS *fts;
1491556Srgrimes	FTSENT *p;
1501556Srgrimes	int needstat;
15120421Ssteve	int flags;
1527798Sache	int rval;
1531556Srgrimes
1541556Srgrimes	/*
1551556Srgrimes	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
1561556Srgrimes	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
1571556Srgrimes	 */
15820421Ssteve	needstat = !uid || (!fflag && !iflag && stdin_ok);
1591556Srgrimes
1601556Srgrimes	/*
1611556Srgrimes	 * If the -i option is specified, the user can skip on the pre-order
1621556Srgrimes	 * visit.  The fts_number field flags skipped directories.
1631556Srgrimes	 */
1641556Srgrimes#define	SKIPPED	1
1651556Srgrimes
16651230Sbde	flags = FTS_PHYSICAL;
16720421Ssteve	if (!needstat)
16820421Ssteve		flags |= FTS_NOSTAT;
16920421Ssteve	if (Wflag)
17020421Ssteve		flags |= FTS_WHITEOUT;
17120421Ssteve	if (!(fts = fts_open(argv, flags, (int (*)())NULL)))
1721556Srgrimes		err(1, NULL);
1731556Srgrimes	while ((p = fts_read(fts)) != NULL) {
1741556Srgrimes		switch (p->fts_info) {
1751556Srgrimes		case FTS_DNR:
1761556Srgrimes			if (!fflag || p->fts_errno != ENOENT) {
1771556Srgrimes				warnx("%s: %s",
1781556Srgrimes				    p->fts_path, strerror(p->fts_errno));
1791556Srgrimes				eval = 1;
1801556Srgrimes			}
1811556Srgrimes			continue;
1821556Srgrimes		case FTS_ERR:
1831556Srgrimes			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
1841556Srgrimes		case FTS_NS:
1851556Srgrimes			/*
1861556Srgrimes			 * FTS_NS: assume that if can't stat the file, it
1871556Srgrimes			 * can't be unlinked.
1881556Srgrimes			 */
1891556Srgrimes			if (!needstat)
1901556Srgrimes				break;
1911556Srgrimes			if (!fflag || p->fts_errno != ENOENT) {
1921556Srgrimes				warnx("%s: %s",
1931556Srgrimes				    p->fts_path, strerror(p->fts_errno));
1941556Srgrimes				eval = 1;
1951556Srgrimes			}
1961556Srgrimes			continue;
1971556Srgrimes		case FTS_D:
1981556Srgrimes			/* Pre-order: give user chance to skip. */
19920421Ssteve			if (!fflag && !check(p->fts_path, p->fts_accpath,
2001556Srgrimes			    p->fts_statp)) {
2011556Srgrimes				(void)fts_set(fts, p, FTS_SKIP);
2021556Srgrimes				p->fts_number = SKIPPED;
2031556Srgrimes			}
2047798Sache			else if (!uid &&
2057798Sache				 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
2067798Sache				 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
2077798Sache				 chflags(p->fts_accpath,
2087798Sache					 p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
2097798Sache				goto err;
2101556Srgrimes			continue;
2111556Srgrimes		case FTS_DP:
2121556Srgrimes			/* Post-order: see if user skipped. */
2131556Srgrimes			if (p->fts_number == SKIPPED)
2141556Srgrimes				continue;
2151556Srgrimes			break;
21620421Ssteve		default:
21720421Ssteve			if (!fflag &&
21820421Ssteve			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
21920421Ssteve				continue;
2201556Srgrimes		}
2211556Srgrimes
2227798Sache		rval = 0;
2237798Sache		if (!uid &&
2247798Sache		    (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
2257798Sache		    !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
2267798Sache			rval = chflags(p->fts_accpath,
2277798Sache				       p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
22853819Smharo		if (rval == 0) {
2297798Sache			/*
2307798Sache			 * If we can't read or search the directory, may still be
2317798Sache			 * able to remove it.  Don't print out the un{read,search}able
2327798Sache			 * message unless the remove fails.
2337798Sache			 */
23420421Ssteve			switch (p->fts_info) {
23520421Ssteve			case FTS_DP:
23620421Ssteve			case FTS_DNR:
23753819Smharo				rval = rmdir(p->fts_accpath);
23853819Smharo				if (rval == 0 || (fflag && errno == ENOENT)) {
23953819Smharo					if (rval == 0 && vflag)
24050872Smharo						(void)printf("%s\n",
24150872Smharo						    p->fts_accpath);
2421556Srgrimes					continue;
24350539Smharo				}
24420421Ssteve				break;
24520421Ssteve
24620421Ssteve			case FTS_W:
24753819Smharo				rval = undelete(p->fts_accpath);
24853819Smharo				if (rval == 0 && (fflag && errno == ENOENT)) {
24953819Smharo					if (vflag)
25050872Smharo						(void)printf("%s\n",
25150872Smharo						    p->fts_accpath);
25220421Ssteve					continue;
25350539Smharo				}
25420421Ssteve				break;
25520421Ssteve
25620421Ssteve			default:
2577798Sache				if (Pflag)
2587798Sache					rm_overwrite(p->fts_accpath, NULL);
25953819Smharo				rval = unlink(p->fts_accpath);
26053819Smharo				if (rval == 0 || (fflag && errno == ENOENT)) {
26153819Smharo					if (rval == 0 && vflag)
26250872Smharo						(void)printf("%s\n",
26350872Smharo						    p->fts_accpath);
2647798Sache					continue;
26550539Smharo				}
2667798Sache			}
2671556Srgrimes		}
2687798Sacheerr:
2691556Srgrimes		warn("%s", p->fts_path);
2701556Srgrimes		eval = 1;
2711556Srgrimes	}
2721556Srgrimes	if (errno)
2731556Srgrimes		err(1, "fts_read");
2741556Srgrimes}
2751556Srgrimes
2761556Srgrimesvoid
2771556Srgrimesrm_file(argv)
2781556Srgrimes	char **argv;
2791556Srgrimes{
2801556Srgrimes	struct stat sb;
28120421Ssteve	int rval;
2821556Srgrimes	char *f;
2831556Srgrimes
2841556Srgrimes	/*
2851556Srgrimes	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
2861556Srgrimes	 * to remove a directory is an error, so must always stat the file.
2871556Srgrimes	 */
2881556Srgrimes	while ((f = *argv++) != NULL) {
2891556Srgrimes		/* Assume if can't stat the file, can't unlink it. */
2901556Srgrimes		if (lstat(f, &sb)) {
29120421Ssteve			if (Wflag) {
29220421Ssteve				sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
29320421Ssteve			} else {
29420421Ssteve				if (!fflag || errno != ENOENT) {
29520421Ssteve					warn("%s", f);
29620421Ssteve					eval = 1;
29720421Ssteve				}
29820421Ssteve				continue;
2991556Srgrimes			}
30020421Ssteve		} else if (Wflag) {
30120421Ssteve			warnx("%s: %s", f, strerror(EEXIST));
30220421Ssteve			eval = 1;
3031556Srgrimes			continue;
3041556Srgrimes		}
30520421Ssteve
30620421Ssteve		if (S_ISDIR(sb.st_mode) && !dflag) {
3071556Srgrimes			warnx("%s: is a directory", f);
3081556Srgrimes			eval = 1;
3091556Srgrimes			continue;
3101556Srgrimes		}
31120421Ssteve		if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
3121556Srgrimes			continue;
3137798Sache		rval = 0;
3147798Sache		if (!uid &&
3157798Sache		    (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
3167798Sache		    !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
3177798Sache			rval = chflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
31853819Smharo		if (rval == 0) {
31920421Ssteve			if (S_ISWHT(sb.st_mode))
32020421Ssteve				rval = undelete(f);
32120421Ssteve			else if (S_ISDIR(sb.st_mode))
3227798Sache				rval = rmdir(f);
3237798Sache			else {
3247798Sache				if (Pflag)
3257798Sache					rm_overwrite(f, &sb);
3267798Sache				rval = unlink(f);
3277798Sache			}
3281556Srgrimes		}
3291556Srgrimes		if (rval && (!fflag || errno != ENOENT)) {
3301556Srgrimes			warn("%s", f);
3311556Srgrimes			eval = 1;
3321556Srgrimes		}
33353819Smharo		if (vflag && rval == 0)
33450539Smharo			(void)printf("%s\n", f);
3351556Srgrimes	}
3361556Srgrimes}
3371556Srgrimes
3381556Srgrimes/*
3391556Srgrimes * rm_overwrite --
3401556Srgrimes *	Overwrite the file 3 times with varying bit patterns.
3411556Srgrimes *
3421556Srgrimes * XXX
3431556Srgrimes * This is a cheap way to *really* delete files.  Note that only regular
3441556Srgrimes * files are deleted, directories (and therefore names) will remain.
3451556Srgrimes * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
3461556Srgrimes * System V file system).  In a logging file system, you'll have to have
3471556Srgrimes * kernel support.
3481556Srgrimes */
3491556Srgrimesvoid
3501556Srgrimesrm_overwrite(file, sbp)
3511556Srgrimes	char *file;
3521556Srgrimes	struct stat *sbp;
3531556Srgrimes{
3541556Srgrimes	struct stat sb;
35547584Skris	struct statfs fsb;
3561556Srgrimes	off_t len;
35747584Skris	int bsize, fd, wlen;
35847584Skris	char *buf = NULL;
3591556Srgrimes
3601556Srgrimes	fd = -1;
3611556Srgrimes	if (sbp == NULL) {
3621556Srgrimes		if (lstat(file, &sb))
3631556Srgrimes			goto err;
3641556Srgrimes		sbp = &sb;
3651556Srgrimes	}
3661556Srgrimes	if (!S_ISREG(sbp->st_mode))
3671556Srgrimes		return;
3681556Srgrimes	if ((fd = open(file, O_WRONLY, 0)) == -1)
3691556Srgrimes		goto err;
37047584Skris	if (fstatfs(fd, &fsb) == -1)
37147584Skris		goto err;
37247584Skris	bsize = MAX(fsb.f_iosize, 1024);
37347584Skris	if ((buf = malloc(bsize)) == NULL)
37447584Skris		err(1, "malloc");
3751556Srgrimes
3761556Srgrimes#define	PASS(byte) {							\
37747584Skris	memset(buf, byte, bsize);					\
3781556Srgrimes	for (len = sbp->st_size; len > 0; len -= wlen) {		\
37947584Skris		wlen = len < bsize ? len : bsize;			\
3801556Srgrimes		if (write(fd, buf, wlen) != wlen)			\
3811556Srgrimes			goto err;					\
3821556Srgrimes	}								\
3831556Srgrimes}
3841556Srgrimes	PASS(0xff);
3851556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
3861556Srgrimes		goto err;
3871556Srgrimes	PASS(0x00);
3881556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
3891556Srgrimes		goto err;
3901556Srgrimes	PASS(0xff);
39147584Skris	if (!fsync(fd) && !close(fd)) {
39247584Skris		free(buf);
3931556Srgrimes		return;
39447584Skris	}
3951556Srgrimes
3961556Srgrimeserr:	eval = 1;
39747584Skris	if (buf)
39847584Skris		free(buf);
3991556Srgrimes	warn("%s", file);
4001556Srgrimes}
4011556Srgrimes
4021556Srgrimes
4031556Srgrimesint
4041556Srgrimescheck(path, name, sp)
4051556Srgrimes	char *path, *name;
4061556Srgrimes	struct stat *sp;
4071556Srgrimes{
4081556Srgrimes	int ch, first;
4097798Sache	char modep[15], flagsp[128];
4101556Srgrimes
4111556Srgrimes	/* Check -i first. */
4121556Srgrimes	if (iflag)
4131556Srgrimes		(void)fprintf(stderr, "remove %s? ", path);
4141556Srgrimes	else {
4151556Srgrimes		/*
4161556Srgrimes		 * If it's not a symbolic link and it's unwritable and we're
4171556Srgrimes		 * talking to a terminal, ask.  Symbolic links are excluded
4181556Srgrimes		 * because their permissions are meaningless.  Check stdin_ok
4191556Srgrimes		 * first because we may not have stat'ed the file.
4201556Srgrimes		 */
4217798Sache		if (!stdin_ok || S_ISLNK(sp->st_mode) ||
42220421Ssteve		    (!access(name, W_OK) &&
4237798Sache		    !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
42420421Ssteve		    (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
4251556Srgrimes			return (1);
4261556Srgrimes		strmode(sp->st_mode, modep);
4277798Sache		strcpy(flagsp, flags_to_string(sp->st_flags, NULL));
4287798Sache		if (*flagsp)
4297798Sache			strcat(flagsp, " ");
4307798Sache		(void)fprintf(stderr, "override %s%s%s/%s %sfor %s? ",
4311556Srgrimes		    modep + 1, modep[9] == ' ' ? "" : " ",
4321556Srgrimes		    user_from_uid(sp->st_uid, 0),
4337798Sache		    group_from_gid(sp->st_gid, 0),
4347798Sache		    *flagsp ? flagsp : "",
4357798Sache		    path);
4361556Srgrimes	}
4371556Srgrimes	(void)fflush(stderr);
4381556Srgrimes
4391556Srgrimes	first = ch = getchar();
4401556Srgrimes	while (ch != '\n' && ch != EOF)
4411556Srgrimes		ch = getchar();
44214409Swosch	return (first == 'y' || first == 'Y');
4431556Srgrimes}
4441556Srgrimes
4452927Sphk#define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
4461556Srgrimesvoid
4471556Srgrimescheckdot(argv)
4481556Srgrimes	char **argv;
4491556Srgrimes{
4501556Srgrimes	char *p, **save, **t;
4511556Srgrimes	int complained;
4521556Srgrimes
4531556Srgrimes	complained = 0;
4541556Srgrimes	for (t = argv; *t;) {
4551556Srgrimes		if ((p = strrchr(*t, '/')) != NULL)
4561556Srgrimes			++p;
4571556Srgrimes		else
4581556Srgrimes			p = *t;
4591556Srgrimes		if (ISDOT(p)) {
4601556Srgrimes			if (!complained++)
4611556Srgrimes				warnx("\".\" and \"..\" may not be removed");
4621556Srgrimes			eval = 1;
46320421Ssteve			for (save = t; (t[0] = t[1]) != NULL; ++t)
46420421Ssteve				continue;
4651556Srgrimes			t = save;
4661556Srgrimes		} else
4671556Srgrimes			++t;
4681556Srgrimes	}
4691556Srgrimes}
4701556Srgrimes
4711556Srgrimesvoid
4721556Srgrimesusage()
4731556Srgrimes{
47453819Smharo
47550872Smharo	(void)fprintf(stderr, "usage: rm [-f | -i] [-dPRrvW] file ...\n");
47650539Smharo	exit(EX_USAGE);
4771556Srgrimes}
478