xref: /openbsd/bin/rm/rm.c (revision f90e70de)
1*f90e70deSderaadt /*	$OpenBSD: rm.c,v 1.44 2022/08/16 13:52:41 deraadt Exp $	*/
2df930be7Sderaadt /*	$NetBSD: rm.c,v 1.19 1995/09/07 06:48:50 jtc Exp $	*/
3df930be7Sderaadt 
4df930be7Sderaadt /*-
5df930be7Sderaadt  * Copyright (c) 1990, 1993, 1994
6df930be7Sderaadt  *	The Regents of the University of California.  All rights reserved.
7df930be7Sderaadt  *
8df930be7Sderaadt  * Redistribution and use in source and binary forms, with or without
9df930be7Sderaadt  * modification, are permitted provided that the following conditions
10df930be7Sderaadt  * are met:
11df930be7Sderaadt  * 1. Redistributions of source code must retain the above copyright
12df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer.
13df930be7Sderaadt  * 2. Redistributions in binary form must reproduce the above copyright
14df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer in the
15df930be7Sderaadt  *    documentation and/or other materials provided with the distribution.
1629295d1cSmillert  * 3. Neither the name of the University nor the names of its contributors
17df930be7Sderaadt  *    may be used to endorse or promote products derived from this software
18df930be7Sderaadt  *    without specific prior written permission.
19df930be7Sderaadt  *
20df930be7Sderaadt  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21df930be7Sderaadt  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22df930be7Sderaadt  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23df930be7Sderaadt  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24df930be7Sderaadt  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25df930be7Sderaadt  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26df930be7Sderaadt  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27df930be7Sderaadt  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28df930be7Sderaadt  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29df930be7Sderaadt  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30df930be7Sderaadt  * SUCH DAMAGE.
31df930be7Sderaadt  */
32df930be7Sderaadt 
33df930be7Sderaadt #include <sys/types.h>
34df930be7Sderaadt #include <sys/stat.h>
3513b097dfSaaron #include <sys/mount.h>
36df930be7Sderaadt 
37df930be7Sderaadt #include <err.h>
38df930be7Sderaadt #include <errno.h>
39df930be7Sderaadt #include <fcntl.h>
40df930be7Sderaadt #include <fts.h>
41df930be7Sderaadt #include <stdio.h>
42df930be7Sderaadt #include <stdlib.h>
43df930be7Sderaadt #include <string.h>
44df930be7Sderaadt #include <unistd.h>
45b9fc9a72Sderaadt #include <limits.h>
46764064c4Smickey #include <pwd.h>
47764064c4Smickey #include <grp.h>
48df930be7Sderaadt 
49b9fc9a72Sderaadt #define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
50b9fc9a72Sderaadt 
519517852aSmpech extern char *__progname;
529517852aSmpech 
53e507bd9aStedu int dflag, eval, fflag, iflag, Pflag, vflag, stdin_ok;
54df930be7Sderaadt 
55c72b5b24Smillert int	check(char *, char *, struct stat *);
5653ac4003Sdaniel void	checkdot(char **);
57c72b5b24Smillert void	rm_file(char **);
58fc82f293Stedu int	rm_overwrite(char *, struct stat *);
597c5c57baStedu int	pass(int, off_t, char *, size_t);
60c72b5b24Smillert void	rm_tree(char **);
61c72b5b24Smillert void	usage(void);
62df930be7Sderaadt 
63df930be7Sderaadt /*
64df930be7Sderaadt  * rm --
65df930be7Sderaadt  *	This rm is different from historic rm's, but is expected to match
66df930be7Sderaadt  *	POSIX 1003.2 behavior.  The most visible difference is that -f
67df930be7Sderaadt  *	has two specific effects now, ignore non-existent files and force
68df930be7Sderaadt  * 	file removal.
69df930be7Sderaadt  */
70df930be7Sderaadt int
main(int argc,char * argv[])71ab83b6d6Sderaadt main(int argc, char *argv[])
72df930be7Sderaadt {
73df930be7Sderaadt 	int ch, rflag;
74df930be7Sderaadt 
75df930be7Sderaadt 	Pflag = rflag = 0;
76e507bd9aStedu 	while ((ch = getopt(argc, argv, "dfiPRrv")) != -1)
77df930be7Sderaadt 		switch(ch) {
78df930be7Sderaadt 		case 'd':
79df930be7Sderaadt 			dflag = 1;
80df930be7Sderaadt 			break;
81df930be7Sderaadt 		case 'f':
82df930be7Sderaadt 			fflag = 1;
83df930be7Sderaadt 			iflag = 0;
84df930be7Sderaadt 			break;
85df930be7Sderaadt 		case 'i':
86df930be7Sderaadt 			fflag = 0;
87df930be7Sderaadt 			iflag = 1;
88df930be7Sderaadt 			break;
89df930be7Sderaadt 		case 'P':
90df930be7Sderaadt 			Pflag = 1;
91df930be7Sderaadt 			break;
92df930be7Sderaadt 		case 'R':
93df930be7Sderaadt 		case 'r':			/* Compatibility. */
94df930be7Sderaadt 			rflag = 1;
95df930be7Sderaadt 			break;
96e507bd9aStedu 		case 'v':
97e507bd9aStedu 			vflag = 1;
98e507bd9aStedu 			break;
99df930be7Sderaadt 		default:
100df930be7Sderaadt 			usage();
101df930be7Sderaadt 		}
102df930be7Sderaadt 	argc -= optind;
103df930be7Sderaadt 	argv += optind;
104df930be7Sderaadt 
105d5cafc5dSderaadt 	if (Pflag) {
106204e6881Sderaadt 		if (pledge("stdio rpath wpath cpath getpw", NULL) == -1)
1070bd1216cSderaadt 			err(1, "pledge");
108d5cafc5dSderaadt 	} else {
109204e6881Sderaadt 		if (pledge("stdio rpath cpath getpw", NULL) == -1)
1100bd1216cSderaadt 			err(1, "pledge");
111d5cafc5dSderaadt 	}
112d5cafc5dSderaadt 
113849591b1Smillert 	if (argc < 1 && fflag == 0)
114df930be7Sderaadt 		usage();
115df930be7Sderaadt 
11653ac4003Sdaniel 	checkdot(argv);
117df930be7Sderaadt 
118df930be7Sderaadt 	if (*argv) {
119df930be7Sderaadt 		stdin_ok = isatty(STDIN_FILENO);
120df930be7Sderaadt 
121df930be7Sderaadt 		if (rflag)
122df930be7Sderaadt 			rm_tree(argv);
123df930be7Sderaadt 		else
124df930be7Sderaadt 			rm_file(argv);
125df930be7Sderaadt 	}
126df930be7Sderaadt 
12758715da7Sschwarze 	return (eval);
128df930be7Sderaadt }
129df930be7Sderaadt 
130df930be7Sderaadt void
rm_tree(char ** argv)131ab83b6d6Sderaadt rm_tree(char **argv)
132df930be7Sderaadt {
133df930be7Sderaadt 	FTS *fts;
134df930be7Sderaadt 	FTSENT *p;
135df930be7Sderaadt 	int needstat;
136df930be7Sderaadt 	int flags;
137df930be7Sderaadt 
138df930be7Sderaadt 	/*
139df930be7Sderaadt 	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
140df930be7Sderaadt 	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
141df930be7Sderaadt 	 */
142df930be7Sderaadt 	needstat = !fflag && !iflag && stdin_ok;
143df930be7Sderaadt 
144df930be7Sderaadt 	/*
145df930be7Sderaadt 	 * If the -i option is specified, the user can skip on the pre-order
146df930be7Sderaadt 	 * visit.  The fts_number field flags skipped directories.
147df930be7Sderaadt 	 */
148df930be7Sderaadt #define	SKIPPED	1
149df930be7Sderaadt 
150df930be7Sderaadt 	flags = FTS_PHYSICAL;
151df930be7Sderaadt 	if (!needstat)
152df930be7Sderaadt 		flags |= FTS_NOSTAT;
153cbc6bef2Stedu 	if (!(fts = fts_open(argv, flags, NULL)))
154df930be7Sderaadt 		err(1, NULL);
155df930be7Sderaadt 	while ((p = fts_read(fts)) != NULL) {
156df930be7Sderaadt 		switch (p->fts_info) {
157df930be7Sderaadt 		case FTS_DNR:
158df930be7Sderaadt 			if (!fflag || p->fts_errno != ENOENT) {
159df930be7Sderaadt 				warnx("%s: %s",
160df930be7Sderaadt 				    p->fts_path, strerror(p->fts_errno));
161df930be7Sderaadt 				eval = 1;
162df930be7Sderaadt 			}
163df930be7Sderaadt 			continue;
164df930be7Sderaadt 		case FTS_ERR:
165cd5cfea9Sguenther 			errc(1, p->fts_errno, "%s", p->fts_path);
166df930be7Sderaadt 		case FTS_NS:
167df930be7Sderaadt 			/*
168df930be7Sderaadt 			 * FTS_NS: assume that if can't stat the file, it
169df930be7Sderaadt 			 * can't be unlinked.
170df930be7Sderaadt 			 */
171df930be7Sderaadt 			if (!needstat)
172df930be7Sderaadt 				break;
173df930be7Sderaadt 			if (!fflag || p->fts_errno != ENOENT) {
174df930be7Sderaadt 				warnx("%s: %s",
175df930be7Sderaadt 				    p->fts_path, strerror(p->fts_errno));
176df930be7Sderaadt 				eval = 1;
177df930be7Sderaadt 			}
178df930be7Sderaadt 			continue;
179df930be7Sderaadt 		case FTS_D:
180df930be7Sderaadt 			/* Pre-order: give user chance to skip. */
181df930be7Sderaadt 			if (!fflag && !check(p->fts_path, p->fts_accpath,
182df930be7Sderaadt 			    p->fts_statp)) {
183df930be7Sderaadt 				(void)fts_set(fts, p, FTS_SKIP);
184df930be7Sderaadt 				p->fts_number = SKIPPED;
185df930be7Sderaadt 			}
186df930be7Sderaadt 			continue;
187df930be7Sderaadt 		case FTS_DP:
188df930be7Sderaadt 			/* Post-order: see if user skipped. */
189df930be7Sderaadt 			if (p->fts_number == SKIPPED)
190df930be7Sderaadt 				continue;
191df930be7Sderaadt 			break;
192df930be7Sderaadt 		default:
193df930be7Sderaadt 			if (!fflag &&
194df930be7Sderaadt 			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
195df930be7Sderaadt 				continue;
196df930be7Sderaadt 		}
197df930be7Sderaadt 
198df930be7Sderaadt 		/*
199df930be7Sderaadt 		 * If we can't read or search the directory, may still be
200df930be7Sderaadt 		 * able to remove it.  Don't print out the un{read,search}able
201df930be7Sderaadt 		 * message unless the remove fails.
202df930be7Sderaadt 		 */
203df930be7Sderaadt 		switch (p->fts_info) {
204df930be7Sderaadt 		case FTS_DP:
205df930be7Sderaadt 		case FTS_DNR:
206*f90e70deSderaadt 			if (!rmdir(p->fts_accpath)) {
207e507bd9aStedu 				if (vflag)
2087feebab4Stedu 					fprintf(stdout, "%s\n", p->fts_path);
209df930be7Sderaadt 				continue;
210e507bd9aStedu 			}
211*f90e70deSderaadt 			if (fflag && errno == ENOENT)
212*f90e70deSderaadt 				continue;
213df930be7Sderaadt 			break;
214df930be7Sderaadt 
21507a53563Sguenther 		case FTS_F:
21607a53563Sguenther 		case FTS_NSOK:
217df930be7Sderaadt 			if (Pflag)
21807a53563Sguenther 				rm_overwrite(p->fts_accpath, p->fts_info ==
21907a53563Sguenther 				    FTS_NSOK ? NULL : p->fts_statp);
22007a53563Sguenther 			/* FALLTHROUGH */
22107a53563Sguenther 		default:
222*f90e70deSderaadt 			if (!unlink(p->fts_accpath)) {
223e507bd9aStedu 				if (vflag)
2247feebab4Stedu 					fprintf(stdout, "%s\n", p->fts_path);
225df930be7Sderaadt 				continue;
226df930be7Sderaadt 			}
227*f90e70deSderaadt 			if (fflag && errno == ENOENT)
228*f90e70deSderaadt 				continue;
229e507bd9aStedu 		}
230df930be7Sderaadt 		warn("%s", p->fts_path);
231df930be7Sderaadt 		eval = 1;
232df930be7Sderaadt 	}
233df930be7Sderaadt 	if (errno)
234df930be7Sderaadt 		err(1, "fts_read");
23519a1923bSotto 	fts_close(fts);
236df930be7Sderaadt }
237df930be7Sderaadt 
238df930be7Sderaadt void
rm_file(char ** argv)239ab83b6d6Sderaadt rm_file(char **argv)
240df930be7Sderaadt {
241df930be7Sderaadt 	struct stat sb;
242df930be7Sderaadt 	int rval;
243df930be7Sderaadt 	char *f;
244df930be7Sderaadt 
245df930be7Sderaadt 	/*
246df930be7Sderaadt 	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
247df930be7Sderaadt 	 * to remove a directory is an error, so must always stat the file.
248df930be7Sderaadt 	 */
249df930be7Sderaadt 	while ((f = *argv++) != NULL) {
250df930be7Sderaadt 		/* Assume if can't stat the file, can't unlink it. */
251df930be7Sderaadt 		if (lstat(f, &sb)) {
252df930be7Sderaadt 			if (!fflag || errno != ENOENT) {
253df930be7Sderaadt 				warn("%s", f);
254df930be7Sderaadt 				eval = 1;
255df930be7Sderaadt 			}
256df930be7Sderaadt 			continue;
257df930be7Sderaadt 		}
258df930be7Sderaadt 
259df930be7Sderaadt 		if (S_ISDIR(sb.st_mode) && !dflag) {
260df930be7Sderaadt 			warnx("%s: is a directory", f);
261df930be7Sderaadt 			eval = 1;
262df930be7Sderaadt 			continue;
263df930be7Sderaadt 		}
26486d5f11cSmillert 		if (!fflag && !check(f, f, &sb))
265df930be7Sderaadt 			continue;
266df930be7Sderaadt 		else if (S_ISDIR(sb.st_mode))
267df930be7Sderaadt 			rval = rmdir(f);
268df930be7Sderaadt 		else {
269df930be7Sderaadt 			if (Pflag)
270b74da601Sray 				rm_overwrite(f, &sb);
271df930be7Sderaadt 			rval = unlink(f);
272df930be7Sderaadt 		}
273df930be7Sderaadt 		if (rval && (!fflag || errno != ENOENT)) {
274df930be7Sderaadt 			warn("%s", f);
275df930be7Sderaadt 			eval = 1;
276*f90e70deSderaadt 		} else if (rval == 0 && vflag)
277e507bd9aStedu 			(void)fprintf(stdout, "%s\n", f);
278df930be7Sderaadt 	}
279df930be7Sderaadt }
280df930be7Sderaadt 
281df930be7Sderaadt /*
282df930be7Sderaadt  * rm_overwrite --
2837c5c57baStedu  *	Overwrite the file with varying bit patterns.
284df930be7Sderaadt  *
285df930be7Sderaadt  * XXX
286df930be7Sderaadt  * This is a cheap way to *really* delete files.  Note that only regular
287df930be7Sderaadt  * files are deleted, directories (and therefore names) will remain.
288df930be7Sderaadt  * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
289df930be7Sderaadt  * System V file system).  In a logging file system, you'll have to have
290df930be7Sderaadt  * kernel support.
291fc82f293Stedu  * Returns 1 for success.
292df930be7Sderaadt  */
293fc82f293Stedu int
rm_overwrite(char * file,struct stat * sbp)294ab83b6d6Sderaadt rm_overwrite(char *file, struct stat *sbp)
295df930be7Sderaadt {
296a5353f8dSmillert 	struct stat sb, sb2;
29713b097dfSaaron 	struct statfs fsb;
29860b36417Sotto 	size_t bsize;
29960b36417Sotto 	int fd;
3004ac1cca3Sweingart 	char *buf = NULL;
301df930be7Sderaadt 
302df930be7Sderaadt 	fd = -1;
303df930be7Sderaadt 	if (sbp == NULL) {
304df930be7Sderaadt 		if (lstat(file, &sb))
305df930be7Sderaadt 			goto err;
306df930be7Sderaadt 		sbp = &sb;
307df930be7Sderaadt 	}
308df930be7Sderaadt 	if (!S_ISREG(sbp->st_mode))
309fc82f293Stedu 		return (1);
31044cd3b97Shugh 	if (sbp->st_nlink > 1) {
3114a323c25Sderaadt 		warnx("%s (inode %llu): not overwritten due to multiple links",
3124a323c25Sderaadt 		    file, (unsigned long long)sbp->st_ino);
313fc82f293Stedu 		return (0);
31444cd3b97Shugh 	}
315b7041c07Sderaadt 	if ((fd = open(file, O_WRONLY|O_NONBLOCK|O_NOFOLLOW)) == -1)
316df930be7Sderaadt 		goto err;
317a5353f8dSmillert 	if (fstat(fd, &sb2))
318a5353f8dSmillert 		goto err;
319a5353f8dSmillert 	if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino ||
320a5353f8dSmillert 	    !S_ISREG(sb2.st_mode)) {
321a5353f8dSmillert 		errno = EPERM;
322a5353f8dSmillert 		goto err;
323a5353f8dSmillert 	}
32413b097dfSaaron 	if (fstatfs(fd, &fsb) == -1)
32513b097dfSaaron 		goto err;
326b9fc9a72Sderaadt 	bsize = MAXIMUM(fsb.f_iosize, 1024U);
32713b097dfSaaron 	if ((buf = malloc(bsize)) == NULL)
328fc82f293Stedu 		err(1, "%s: malloc", file);
329df930be7Sderaadt 
3307c5c57baStedu 	if (!pass(fd, sbp->st_size, buf, bsize))
331df930be7Sderaadt 		goto err;
3327c5c57baStedu 	if (fsync(fd))
333fc82f293Stedu 		goto err;
334fc82f293Stedu 	close(fd);
33513b097dfSaaron 	free(buf);
336fc82f293Stedu 	return (1);
337df930be7Sderaadt 
338fc82f293Stedu err:
33960b36417Sotto 	warn("%s", file);
340fc82f293Stedu 	close(fd);
341fc82f293Stedu 	eval = 1;
34213b097dfSaaron 	free(buf);
343fc82f293Stedu 	return (0);
344df930be7Sderaadt }
345df930be7Sderaadt 
34660b36417Sotto int
pass(int fd,off_t len,char * buf,size_t bsize)3477c5c57baStedu pass(int fd, off_t len, char *buf, size_t bsize)
34860b36417Sotto {
34960b36417Sotto 	size_t wlen;
35060b36417Sotto 
35160b36417Sotto 	for (; len > 0; len -= wlen) {
35260b36417Sotto 		wlen = len < bsize ? len : bsize;
353b85d50d7Snaddy 		arc4random_buf(buf, wlen);
35460b36417Sotto 		if (write(fd, buf, wlen) != wlen)
35560b36417Sotto 			return (0);
35660b36417Sotto 	}
35760b36417Sotto 	return (1);
35860b36417Sotto }
359df930be7Sderaadt 
360df930be7Sderaadt int
check(char * path,char * name,struct stat * sp)361ab83b6d6Sderaadt check(char *path, char *name, struct stat *sp)
362df930be7Sderaadt {
363df930be7Sderaadt 	int ch, first;
364df930be7Sderaadt 	char modep[15];
365df930be7Sderaadt 
366df930be7Sderaadt 	/* Check -i first. */
367df930be7Sderaadt 	if (iflag)
368df930be7Sderaadt 		(void)fprintf(stderr, "remove %s? ", path);
369df930be7Sderaadt 	else {
370df930be7Sderaadt 		/*
371df930be7Sderaadt 		 * If it's not a symbolic link and it's unwritable and we're
372df930be7Sderaadt 		 * talking to a terminal, ask.  Symbolic links are excluded
373df930be7Sderaadt 		 * because their permissions are meaningless.  Check stdin_ok
374df930be7Sderaadt 		 * first because we may not have stat'ed the file.
375df930be7Sderaadt 		 */
376af322779Sotto 		if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK) ||
377af322779Sotto 		    errno != EACCES)
378df930be7Sderaadt 			return (1);
379df930be7Sderaadt 		strmode(sp->st_mode, modep);
380df930be7Sderaadt 		(void)fprintf(stderr, "override %s%s%s/%s for %s? ",
381df930be7Sderaadt 		    modep + 1, modep[9] == ' ' ? "" : " ",
382df930be7Sderaadt 		    user_from_uid(sp->st_uid, 0),
383df930be7Sderaadt 		    group_from_gid(sp->st_gid, 0), path);
384df930be7Sderaadt 	}
385df930be7Sderaadt 	(void)fflush(stderr);
386df930be7Sderaadt 
387df930be7Sderaadt 	first = ch = getchar();
388df930be7Sderaadt 	while (ch != '\n' && ch != EOF)
389df930be7Sderaadt 		ch = getchar();
390df930be7Sderaadt 	return (first == 'y' || first == 'Y');
391df930be7Sderaadt }
392df930be7Sderaadt 
393df930be7Sderaadt /*
394df930be7Sderaadt  * POSIX.2 requires that if "." or ".." are specified as the basename
395df930be7Sderaadt  * portion of an operand, a diagnostic message be written to standard
396df930be7Sderaadt  * error and nothing more be done with such operands.
397df930be7Sderaadt  *
398df930be7Sderaadt  * Since POSIX.2 defines basename as the final portion of a path after
399df930be7Sderaadt  * trailing slashes have been removed, we'll remove them here.
400df930be7Sderaadt  */
401764064c4Smickey #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
402df930be7Sderaadt void
checkdot(char ** argv)40353ac4003Sdaniel checkdot(char **argv)
404df930be7Sderaadt {
405df930be7Sderaadt 	char *p, **save, **t;
40653ac4003Sdaniel 	int complained;
407c11d908cStedu 	struct stat sb, root;
408df930be7Sderaadt 
409c11d908cStedu 	stat("/", &root);
41053ac4003Sdaniel 	complained = 0;
411df930be7Sderaadt 	for (t = argv; *t;) {
412c11d908cStedu 		if (lstat(*t, &sb) == 0 &&
413c11d908cStedu 		    root.st_ino == sb.st_ino && root.st_dev == sb.st_dev) {
414c11d908cStedu 			if (!complained++)
415c11d908cStedu 				warnx("\"/\" may not be removed");
416c11d908cStedu 			goto skip;
417c11d908cStedu 		}
418df930be7Sderaadt 		/* strip trailing slashes */
419df930be7Sderaadt 		p = strrchr(*t, '\0');
420df930be7Sderaadt 		while (--p > *t && *p == '/')
421df930be7Sderaadt 			*p = '\0';
422df930be7Sderaadt 
423df930be7Sderaadt 		/* extract basename */
424df930be7Sderaadt 		if ((p = strrchr(*t, '/')) != NULL)
425df930be7Sderaadt 			++p;
426df930be7Sderaadt 		else
427df930be7Sderaadt 			p = *t;
428df930be7Sderaadt 
429df930be7Sderaadt 		if (ISDOT(p)) {
43053ac4003Sdaniel 			if (!complained++)
431df930be7Sderaadt 				warnx("\".\" and \"..\" may not be removed");
432c11d908cStedu skip:
433df930be7Sderaadt 			eval = 1;
434df930be7Sderaadt 			for (save = t; (t[0] = t[1]) != NULL; ++t)
435df930be7Sderaadt 				continue;
436df930be7Sderaadt 			t = save;
43753ac4003Sdaniel 		} else
43853ac4003Sdaniel 			++t;
439df930be7Sderaadt 	}
440df930be7Sderaadt }
441df930be7Sderaadt 
442df930be7Sderaadt void
usage(void)443ab83b6d6Sderaadt usage(void)
444df930be7Sderaadt {
445e507bd9aStedu 	(void)fprintf(stderr, "usage: %s [-dfiPRrv] file ...\n", __progname);
446df930be7Sderaadt 	exit(1);
447df930be7Sderaadt }
448