rm.c revision 136113
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 * 4. Neither the name of the University nor the names of its contributors
141556Srgrimes *    may be used to endorse or promote products derived from this software
151556Srgrimes *    without specific prior written permission.
161556Srgrimes *
171556Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181556Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191556Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201556Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211556Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221556Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231556Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241556Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251556Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261556Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271556Srgrimes * SUCH DAMAGE.
281556Srgrimes */
291556Srgrimes
30114433Sobrien#if 0
311556Srgrimes#ifndef lint
3227959Sstevestatic const char copyright[] =
331556Srgrimes"@(#) Copyright (c) 1990, 1993, 1994\n\
341556Srgrimes	The Regents of the University of California.  All rights reserved.\n";
3527964Ssteve#endif /* not lint */
3627964Ssteve
3727964Ssteve#ifndef lint
3827964Sstevestatic char sccsid[] = "@(#)rm.c	8.5 (Berkeley) 4/18/94";
39114433Sobrien#endif /* not lint */
4027964Ssteve#endif
4199110Sobrien#include <sys/cdefs.h>
4299110Sobrien__FBSDID("$FreeBSD: head/bin/rm/rm.c 136113 2004-10-04 11:26:01Z des $");
431556Srgrimes
441556Srgrimes#include <sys/stat.h>
4547584Skris#include <sys/param.h>
4647584Skris#include <sys/mount.h>
471556Srgrimes
481556Srgrimes#include <err.h>
491556Srgrimes#include <errno.h>
501556Srgrimes#include <fcntl.h>
511556Srgrimes#include <fts.h>
5290644Simp#include <grp.h>
5390644Simp#include <pwd.h>
541556Srgrimes#include <stdio.h>
551556Srgrimes#include <stdlib.h>
561556Srgrimes#include <string.h>
5750539Smharo#include <sysexits.h>
581556Srgrimes#include <unistd.h>
591556Srgrimes
6050872Smharoint dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
617798Sacheuid_t uid;
621556Srgrimes
6390110Simpint	check(char *, char *, struct stat *);
6490110Simpvoid	checkdot(char **);
65136113Sdesvoid	checkslash(char **);
6690110Simpvoid	rm_file(char **);
67122409Sguidoint	rm_overwrite(char *, struct stat *);
6890110Simpvoid	rm_tree(char **);
6990110Simpvoid	usage(void);
701556Srgrimes
711556Srgrimes/*
721556Srgrimes * rm --
731556Srgrimes *	This rm is different from historic rm's, but is expected to match
74136112Sdes *	POSIX 1003.2 behavior.	The most visible difference is that -f
751556Srgrimes *	has two specific effects now, ignore non-existent files and force
76136112Sdes *	file removal.
771556Srgrimes */
781556Srgrimesint
7990110Simpmain(int argc, char *argv[])
801556Srgrimes{
811556Srgrimes	int ch, rflag;
8254895Ssheldonh	char *p;
831556Srgrimes
8454895Ssheldonh	/*
8554895Ssheldonh	 * Test for the special case where the utility is called as
8654895Ssheldonh	 * "unlink", for which the functionality provided is greatly
8754895Ssheldonh	 * simplified.
8854895Ssheldonh	 */
8954895Ssheldonh	if ((p = rindex(argv[0], '/')) == NULL)
9054895Ssheldonh		p = argv[0];
9154895Ssheldonh	else
9254895Ssheldonh		++p;
9354895Ssheldonh	if (strcmp(p, "unlink") == 0) {
9497533Stjr		while (getopt(argc, argv, "") != -1)
9554895Ssheldonh			usage();
9697533Stjr		argc -= optind;
9797533Stjr		argv += optind;
9899858Stjr		if (argc != 1)
9997533Stjr			usage();
10097533Stjr		rm_file(&argv[0]);
10197533Stjr		exit(eval);
10254895Ssheldonh	}
10354895Ssheldonh
10420421Ssteve	Pflag = rflag = 0;
10550872Smharo	while ((ch = getopt(argc, argv, "dfiPRrvW")) != -1)
1061556Srgrimes		switch(ch) {
1071556Srgrimes		case 'd':
1081556Srgrimes			dflag = 1;
1091556Srgrimes			break;
1101556Srgrimes		case 'f':
1111556Srgrimes			fflag = 1;
1121556Srgrimes			iflag = 0;
1131556Srgrimes			break;
1141556Srgrimes		case 'i':
1151556Srgrimes			fflag = 0;
1161556Srgrimes			iflag = 1;
1171556Srgrimes			break;
1181556Srgrimes		case 'P':
1191556Srgrimes			Pflag = 1;
1201556Srgrimes			break;
1211556Srgrimes		case 'R':
1221556Srgrimes		case 'r':			/* Compatibility. */
1231556Srgrimes			rflag = 1;
1241556Srgrimes			break;
12550872Smharo		case 'v':
12650872Smharo			vflag = 1;
12750872Smharo			break;
12820421Ssteve		case 'W':
12920421Ssteve			Wflag = 1;
13020421Ssteve			break;
1311556Srgrimes		default:
1321556Srgrimes			usage();
1331556Srgrimes		}
1341556Srgrimes	argc -= optind;
1351556Srgrimes	argv += optind;
1361556Srgrimes
13744282Sjkh	if (argc < 1) {
13844282Sjkh		if (fflag)
139122409Sguido			return (0);
1401556Srgrimes		usage();
14144282Sjkh	}
1421556Srgrimes
1431556Srgrimes	checkdot(argv);
144136113Sdes	checkslash(argv);
1457798Sache	uid = geteuid();
1461556Srgrimes
14720421Ssteve	if (*argv) {
14820421Ssteve		stdin_ok = isatty(STDIN_FILENO);
14920421Ssteve
15020421Ssteve		if (rflag)
15120421Ssteve			rm_tree(argv);
15220421Ssteve		else
15320421Ssteve			rm_file(argv);
15420421Ssteve	}
15520421Ssteve
1561556Srgrimes	exit (eval);
1571556Srgrimes}
1581556Srgrimes
1591556Srgrimesvoid
16090110Simprm_tree(char **argv)
1611556Srgrimes{
1621556Srgrimes	FTS *fts;
1631556Srgrimes	FTSENT *p;
1641556Srgrimes	int needstat;
16520421Ssteve	int flags;
1667798Sache	int rval;
1671556Srgrimes
1681556Srgrimes	/*
1691556Srgrimes	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
1701556Srgrimes	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
1711556Srgrimes	 */
17220421Ssteve	needstat = !uid || (!fflag && !iflag && stdin_ok);
1731556Srgrimes
1741556Srgrimes	/*
1751556Srgrimes	 * If the -i option is specified, the user can skip on the pre-order
1761556Srgrimes	 * visit.  The fts_number field flags skipped directories.
1771556Srgrimes	 */
1781556Srgrimes#define	SKIPPED	1
1791556Srgrimes
18051230Sbde	flags = FTS_PHYSICAL;
18120421Ssteve	if (!needstat)
18220421Ssteve		flags |= FTS_NOSTAT;
18320421Ssteve	if (Wflag)
18420421Ssteve		flags |= FTS_WHITEOUT;
18578170Sru	if (!(fts = fts_open(argv, flags, NULL)))
18699744Sdillon		err(1, "fts_open");
1871556Srgrimes	while ((p = fts_read(fts)) != NULL) {
1881556Srgrimes		switch (p->fts_info) {
1891556Srgrimes		case FTS_DNR:
1901556Srgrimes			if (!fflag || p->fts_errno != ENOENT) {
1911556Srgrimes				warnx("%s: %s",
1921556Srgrimes				    p->fts_path, strerror(p->fts_errno));
1931556Srgrimes				eval = 1;
1941556Srgrimes			}
1951556Srgrimes			continue;
1961556Srgrimes		case FTS_ERR:
1971556Srgrimes			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
1981556Srgrimes		case FTS_NS:
1991556Srgrimes			/*
200124041Skuriyama			 * Assume that since fts_read() couldn't stat the
201124041Skuriyama			 * file, it can't be unlinked.
2021556Srgrimes			 */
2031556Srgrimes			if (!needstat)
2041556Srgrimes				break;
2051556Srgrimes			if (!fflag || p->fts_errno != ENOENT) {
2061556Srgrimes				warnx("%s: %s",
2071556Srgrimes				    p->fts_path, strerror(p->fts_errno));
2081556Srgrimes				eval = 1;
2091556Srgrimes			}
2101556Srgrimes			continue;
2111556Srgrimes		case FTS_D:
2121556Srgrimes			/* Pre-order: give user chance to skip. */
21320421Ssteve			if (!fflag && !check(p->fts_path, p->fts_accpath,
2141556Srgrimes			    p->fts_statp)) {
2151556Srgrimes				(void)fts_set(fts, p, FTS_SKIP);
2161556Srgrimes				p->fts_number = SKIPPED;
2171556Srgrimes			}
2187798Sache			else if (!uid &&
2197798Sache				 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
2207798Sache				 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
2217798Sache				 chflags(p->fts_accpath,
2227798Sache					 p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
2237798Sache				goto err;
2241556Srgrimes			continue;
2251556Srgrimes		case FTS_DP:
2261556Srgrimes			/* Post-order: see if user skipped. */
2271556Srgrimes			if (p->fts_number == SKIPPED)
2281556Srgrimes				continue;
2291556Srgrimes			break;
23020421Ssteve		default:
23120421Ssteve			if (!fflag &&
23220421Ssteve			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
23320421Ssteve				continue;
2341556Srgrimes		}
2351556Srgrimes
2367798Sache		rval = 0;
2377798Sache		if (!uid &&
2387798Sache		    (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
2397798Sache		    !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
2407798Sache			rval = chflags(p->fts_accpath,
2417798Sache				       p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
24253819Smharo		if (rval == 0) {
2437798Sache			/*
2447798Sache			 * If we can't read or search the directory, may still be
2457798Sache			 * able to remove it.  Don't print out the un{read,search}able
2467798Sache			 * message unless the remove fails.
2477798Sache			 */
24820421Ssteve			switch (p->fts_info) {
24920421Ssteve			case FTS_DP:
25020421Ssteve			case FTS_DNR:
25153819Smharo				rval = rmdir(p->fts_accpath);
25253819Smharo				if (rval == 0 || (fflag && errno == ENOENT)) {
25353819Smharo					if (rval == 0 && vflag)
25450872Smharo						(void)printf("%s\n",
25570219Sobrien						    p->fts_path);
2561556Srgrimes					continue;
25750539Smharo				}
25820421Ssteve				break;
25920421Ssteve
26020421Ssteve			case FTS_W:
26153819Smharo				rval = undelete(p->fts_accpath);
26253819Smharo				if (rval == 0 && (fflag && errno == ENOENT)) {
26353819Smharo					if (vflag)
26450872Smharo						(void)printf("%s\n",
26570219Sobrien						    p->fts_path);
26620421Ssteve					continue;
26750539Smharo				}
26820421Ssteve				break;
26920421Ssteve
270124041Skuriyama			case FTS_NS:
271124041Skuriyama				/*
272124041Skuriyama				 * Assume that since fts_read() couldn't stat
273124041Skuriyama				 * the file, it can't be unlinked.
274124041Skuriyama				 */
275124041Skuriyama				if (fflag)
276124041Skuriyama					continue;
277124041Skuriyama				/* FALLTHROUGH */
27820421Ssteve			default:
2797798Sache				if (Pflag)
280122409Sguido					if (!rm_overwrite(p->fts_accpath, NULL))
281122409Sguido						continue;
28253819Smharo				rval = unlink(p->fts_accpath);
28353819Smharo				if (rval == 0 || (fflag && errno == ENOENT)) {
28453819Smharo					if (rval == 0 && vflag)
28550872Smharo						(void)printf("%s\n",
28670219Sobrien						    p->fts_path);
2877798Sache					continue;
28850539Smharo				}
2897798Sache			}
2901556Srgrimes		}
2917798Sacheerr:
2921556Srgrimes		warn("%s", p->fts_path);
2931556Srgrimes		eval = 1;
2941556Srgrimes	}
2951556Srgrimes	if (errno)
2961556Srgrimes		err(1, "fts_read");
2971556Srgrimes}
2981556Srgrimes
2991556Srgrimesvoid
30090110Simprm_file(char **argv)
3011556Srgrimes{
3021556Srgrimes	struct stat sb;
30320421Ssteve	int rval;
3041556Srgrimes	char *f;
3051556Srgrimes
3061556Srgrimes	/*
3071556Srgrimes	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
3081556Srgrimes	 * to remove a directory is an error, so must always stat the file.
3091556Srgrimes	 */
3101556Srgrimes	while ((f = *argv++) != NULL) {
3111556Srgrimes		/* Assume if can't stat the file, can't unlink it. */
3121556Srgrimes		if (lstat(f, &sb)) {
31320421Ssteve			if (Wflag) {
31420421Ssteve				sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
31520421Ssteve			} else {
31620421Ssteve				if (!fflag || errno != ENOENT) {
31720421Ssteve					warn("%s", f);
31820421Ssteve					eval = 1;
31920421Ssteve				}
32020421Ssteve				continue;
3211556Srgrimes			}
32220421Ssteve		} else if (Wflag) {
32320421Ssteve			warnx("%s: %s", f, strerror(EEXIST));
32420421Ssteve			eval = 1;
3251556Srgrimes			continue;
3261556Srgrimes		}
32720421Ssteve
32820421Ssteve		if (S_ISDIR(sb.st_mode) && !dflag) {
3291556Srgrimes			warnx("%s: is a directory", f);
3301556Srgrimes			eval = 1;
3311556Srgrimes			continue;
3321556Srgrimes		}
33320421Ssteve		if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
3341556Srgrimes			continue;
3357798Sache		rval = 0;
3367798Sache		if (!uid &&
3377798Sache		    (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
3387798Sache		    !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
3397798Sache			rval = chflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
34053819Smharo		if (rval == 0) {
34120421Ssteve			if (S_ISWHT(sb.st_mode))
34220421Ssteve				rval = undelete(f);
34320421Ssteve			else if (S_ISDIR(sb.st_mode))
3447798Sache				rval = rmdir(f);
3457798Sache			else {
3467798Sache				if (Pflag)
347122409Sguido					if (!rm_overwrite(f, &sb))
348122409Sguido						continue;
3497798Sache				rval = unlink(f);
3507798Sache			}
3511556Srgrimes		}
3521556Srgrimes		if (rval && (!fflag || errno != ENOENT)) {
3531556Srgrimes			warn("%s", f);
3541556Srgrimes			eval = 1;
3551556Srgrimes		}
35653819Smharo		if (vflag && rval == 0)
35750539Smharo			(void)printf("%s\n", f);
3581556Srgrimes	}
3591556Srgrimes}
3601556Srgrimes
3611556Srgrimes/*
3621556Srgrimes * rm_overwrite --
3631556Srgrimes *	Overwrite the file 3 times with varying bit patterns.
3641556Srgrimes *
3651556Srgrimes * XXX
3661556Srgrimes * This is a cheap way to *really* delete files.  Note that only regular
3671556Srgrimes * files are deleted, directories (and therefore names) will remain.
368102230Strhodes * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
369102230Strhodes * System V file system).  In a logging file system, you'll have to have
3701556Srgrimes * kernel support.
3711556Srgrimes */
372122409Sguidoint
37390110Simprm_overwrite(char *file, struct stat *sbp)
3741556Srgrimes{
3751556Srgrimes	struct stat sb;
37647584Skris	struct statfs fsb;
3771556Srgrimes	off_t len;
37847584Skris	int bsize, fd, wlen;
37947584Skris	char *buf = NULL;
3801556Srgrimes
3811556Srgrimes	fd = -1;
3821556Srgrimes	if (sbp == NULL) {
3831556Srgrimes		if (lstat(file, &sb))
3841556Srgrimes			goto err;
3851556Srgrimes		sbp = &sb;
3861556Srgrimes	}
3871556Srgrimes	if (!S_ISREG(sbp->st_mode))
388122409Sguido		return (1);
3891556Srgrimes	if ((fd = open(file, O_WRONLY, 0)) == -1)
3901556Srgrimes		goto err;
39147584Skris	if (fstatfs(fd, &fsb) == -1)
39247584Skris		goto err;
39347584Skris	bsize = MAX(fsb.f_iosize, 1024);
39447584Skris	if ((buf = malloc(bsize)) == NULL)
395122304Sbde		err(1, "%s: malloc", file);
3961556Srgrimes
3971556Srgrimes#define	PASS(byte) {							\
39847584Skris	memset(buf, byte, bsize);					\
3991556Srgrimes	for (len = sbp->st_size; len > 0; len -= wlen) {		\
40047584Skris		wlen = len < bsize ? len : bsize;			\
4011556Srgrimes		if (write(fd, buf, wlen) != wlen)			\
4021556Srgrimes			goto err;					\
4031556Srgrimes	}								\
4041556Srgrimes}
4051556Srgrimes	PASS(0xff);
4061556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
4071556Srgrimes		goto err;
4081556Srgrimes	PASS(0x00);
4091556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
4101556Srgrimes		goto err;
4111556Srgrimes	PASS(0xff);
41247584Skris	if (!fsync(fd) && !close(fd)) {
41347584Skris		free(buf);
414122409Sguido		return (1);
41547584Skris	}
4161556Srgrimes
4171556Srgrimeserr:	eval = 1;
41847584Skris	if (buf)
41947584Skris		free(buf);
420122304Sbde	if (fd != -1)
421122304Sbde		close(fd);
4221556Srgrimes	warn("%s", file);
423122409Sguido	return (0);
4241556Srgrimes}
4251556Srgrimes
4261556Srgrimes
4271556Srgrimesint
42890110Simpcheck(char *path, char *name, struct stat *sp)
4291556Srgrimes{
4301556Srgrimes	int ch, first;
43161749Sjoe	char modep[15], *flagsp;
4321556Srgrimes
4331556Srgrimes	/* Check -i first. */
4341556Srgrimes	if (iflag)
4351556Srgrimes		(void)fprintf(stderr, "remove %s? ", path);
4361556Srgrimes	else {
4371556Srgrimes		/*
4381556Srgrimes		 * If it's not a symbolic link and it's unwritable and we're
439136112Sdes		 * talking to a terminal, ask.	Symbolic links are excluded
4401556Srgrimes		 * because their permissions are meaningless.  Check stdin_ok
4411556Srgrimes		 * first because we may not have stat'ed the file.
442122409Sguido		 * Also skip this check if the -P option was specified because
443122409Sguido		 * we will not be able to overwrite file contents and will
444122409Sguido		 * barf later.
4451556Srgrimes		 */
446122409Sguido		if (!stdin_ok || S_ISLNK(sp->st_mode) || Pflag ||
44720421Ssteve		    (!access(name, W_OK) &&
4487798Sache		    !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
44920421Ssteve		    (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
4501556Srgrimes			return (1);
4511556Srgrimes		strmode(sp->st_mode, modep);
45261749Sjoe		if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
45399744Sdillon			err(1, "fflagstostr");
45461749Sjoe		(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
4551556Srgrimes		    modep + 1, modep[9] == ' ' ? "" : " ",
4561556Srgrimes		    user_from_uid(sp->st_uid, 0),
4577798Sache		    group_from_gid(sp->st_gid, 0),
458136112Sdes		    *flagsp ? flagsp : "", *flagsp ? " " : "",
4597798Sache		    path);
46061749Sjoe		free(flagsp);
4611556Srgrimes	}
4621556Srgrimes	(void)fflush(stderr);
4631556Srgrimes
4641556Srgrimes	first = ch = getchar();
4651556Srgrimes	while (ch != '\n' && ch != EOF)
4661556Srgrimes		ch = getchar();
46714409Swosch	return (first == 'y' || first == 'Y');
4681556Srgrimes}
4691556Srgrimes
470136113Sdes#define ISSLASH(a)	((a)[0] == '/' && (a)[1] == '\0')
471136113Sdesvoid
472136113Sdescheckslash(char **argv)
473136113Sdes{
474136113Sdes	char **t, **u;
475136113Sdes	int complained;
476136113Sdes
477136113Sdes	complained = 0;
478136113Sdes	for (t = argv; *t;) {
479136113Sdes		if (ISSLASH(*t)) {
480136113Sdes			if (!complained++)
481136113Sdes				warnx("\"/\" may not be removed");
482136113Sdes			eval = 1;
483136113Sdes			for (u = t; u[0] != NULL; ++u)
484136113Sdes				u[0] = u[1];
485136113Sdes		} else {
486136113Sdes			++t;
487136113Sdes		}
488136113Sdes	}
489136113Sdes}
490136113Sdes
4912927Sphk#define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
4921556Srgrimesvoid
49390110Simpcheckdot(char **argv)
4941556Srgrimes{
4951556Srgrimes	char *p, **save, **t;
4961556Srgrimes	int complained;
4971556Srgrimes
4981556Srgrimes	complained = 0;
4991556Srgrimes	for (t = argv; *t;) {
5001556Srgrimes		if ((p = strrchr(*t, '/')) != NULL)
5011556Srgrimes			++p;
5021556Srgrimes		else
5031556Srgrimes			p = *t;
5041556Srgrimes		if (ISDOT(p)) {
5051556Srgrimes			if (!complained++)
5061556Srgrimes				warnx("\".\" and \"..\" may not be removed");
5071556Srgrimes			eval = 1;
50820421Ssteve			for (save = t; (t[0] = t[1]) != NULL; ++t)
50920421Ssteve				continue;
5101556Srgrimes			t = save;
5111556Srgrimes		} else
5121556Srgrimes			++t;
5131556Srgrimes	}
5141556Srgrimes}
5151556Srgrimes
5161556Srgrimesvoid
51790110Simpusage(void)
5181556Srgrimes{
51953819Smharo
52054895Ssheldonh	(void)fprintf(stderr, "%s\n%s\n",
52154895Ssheldonh	    "usage: rm [-f | -i] [-dPRrvW] file ...",
52254895Ssheldonh	    "       unlink file");
52350539Smharo	exit(EX_USAGE);
5241556Srgrimes}
525