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$");
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>
54241014Smdf#include <stdint.h>
551556Srgrimes#include <stdio.h>
561556Srgrimes#include <stdlib.h>
571556Srgrimes#include <string.h>
5850539Smharo#include <sysexits.h>
591556Srgrimes#include <unistd.h>
601556Srgrimes
61226961Sedstatic int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
62249948Seadlerstatic int rflag, Iflag, xflag;
63226961Sedstatic uid_t uid;
64226961Sedstatic volatile sig_atomic_t info;
651556Srgrimes
66249949Seadlerstatic int	check(const char *, const char *, struct stat *);
67249949Seadlerstatic int	check2(char **);
68249949Seadlerstatic void	checkdot(char **);
69249949Seadlerstatic void	checkslash(char **);
70249949Seadlerstatic void	rm_file(char **);
71249949Seadlerstatic int	rm_overwrite(const char *, struct stat *);
72249949Seadlerstatic void	rm_tree(char **);
73191670Simpstatic void siginfo(int __unused);
74249949Seadlerstatic void	usage(void);
751556Srgrimes
761556Srgrimes/*
771556Srgrimes * rm --
781556Srgrimes *	This rm is different from historic rm's, but is expected to match
79136112Sdes *	POSIX 1003.2 behavior.	The most visible difference is that -f
801556Srgrimes *	has two specific effects now, ignore non-existent files and force
81136112Sdes *	file removal.
821556Srgrimes */
831556Srgrimesint
8490110Simpmain(int argc, char *argv[])
851556Srgrimes{
86137009Sdelphij	int ch;
8754895Ssheldonh	char *p;
881556Srgrimes
8954895Ssheldonh	/*
9054895Ssheldonh	 * Test for the special case where the utility is called as
9154895Ssheldonh	 * "unlink", for which the functionality provided is greatly
9254895Ssheldonh	 * simplified.
9354895Ssheldonh	 */
94219680Sjilles	if ((p = strrchr(argv[0], '/')) == NULL)
9554895Ssheldonh		p = argv[0];
9654895Ssheldonh	else
9754895Ssheldonh		++p;
9854895Ssheldonh	if (strcmp(p, "unlink") == 0) {
9997533Stjr		while (getopt(argc, argv, "") != -1)
10054895Ssheldonh			usage();
10197533Stjr		argc -= optind;
10297533Stjr		argv += optind;
10399858Stjr		if (argc != 1)
10497533Stjr			usage();
10597533Stjr		rm_file(&argv[0]);
10697533Stjr		exit(eval);
10754895Ssheldonh	}
10854895Ssheldonh
109249948Seadler	Pflag = rflag = xflag = 0;
110249948Seadler	while ((ch = getopt(argc, argv, "dfiIPRrvWx")) != -1)
1111556Srgrimes		switch(ch) {
1121556Srgrimes		case 'd':
1131556Srgrimes			dflag = 1;
1141556Srgrimes			break;
1151556Srgrimes		case 'f':
1161556Srgrimes			fflag = 1;
1171556Srgrimes			iflag = 0;
1181556Srgrimes			break;
1191556Srgrimes		case 'i':
1201556Srgrimes			fflag = 0;
1211556Srgrimes			iflag = 1;
1221556Srgrimes			break;
123137009Sdelphij		case 'I':
124137009Sdelphij			Iflag = 1;
125137009Sdelphij			break;
1261556Srgrimes		case 'P':
1271556Srgrimes			Pflag = 1;
1281556Srgrimes			break;
1291556Srgrimes		case 'R':
1301556Srgrimes		case 'r':			/* Compatibility. */
1311556Srgrimes			rflag = 1;
1321556Srgrimes			break;
13350872Smharo		case 'v':
13450872Smharo			vflag = 1;
13550872Smharo			break;
13620421Ssteve		case 'W':
13720421Ssteve			Wflag = 1;
13820421Ssteve			break;
139249948Seadler		case 'x':
140249948Seadler			xflag = 1;
141249948Seadler			break;
1421556Srgrimes		default:
1431556Srgrimes			usage();
1441556Srgrimes		}
1451556Srgrimes	argc -= optind;
1461556Srgrimes	argv += optind;
1471556Srgrimes
14844282Sjkh	if (argc < 1) {
14944282Sjkh		if (fflag)
150122409Sguido			return (0);
1511556Srgrimes		usage();
15244282Sjkh	}
1531556Srgrimes
1541556Srgrimes	checkdot(argv);
155136124Sdes	if (getenv("POSIXLY_CORRECT") == NULL)
156136124Sdes		checkslash(argv);
1577798Sache	uid = geteuid();
1581556Srgrimes
159191670Simp	(void)signal(SIGINFO, siginfo);
16020421Ssteve	if (*argv) {
16120421Ssteve		stdin_ok = isatty(STDIN_FILENO);
16220421Ssteve
163137009Sdelphij		if (Iflag) {
164137009Sdelphij			if (check2(argv) == 0)
165137009Sdelphij				exit (1);
166137009Sdelphij		}
16720421Ssteve		if (rflag)
16820421Ssteve			rm_tree(argv);
16920421Ssteve		else
17020421Ssteve			rm_file(argv);
17120421Ssteve	}
17220421Ssteve
1731556Srgrimes	exit (eval);
1741556Srgrimes}
1751556Srgrimes
176249949Seadlerstatic void
17790110Simprm_tree(char **argv)
1781556Srgrimes{
1791556Srgrimes	FTS *fts;
1801556Srgrimes	FTSENT *p;
1811556Srgrimes	int needstat;
18220421Ssteve	int flags;
1837798Sache	int rval;
1841556Srgrimes
1851556Srgrimes	/*
1861556Srgrimes	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
1871556Srgrimes	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
1881556Srgrimes	 */
18920421Ssteve	needstat = !uid || (!fflag && !iflag && stdin_ok);
1901556Srgrimes
1911556Srgrimes	/*
1921556Srgrimes	 * If the -i option is specified, the user can skip on the pre-order
1931556Srgrimes	 * visit.  The fts_number field flags skipped directories.
1941556Srgrimes	 */
1951556Srgrimes#define	SKIPPED	1
1961556Srgrimes
19751230Sbde	flags = FTS_PHYSICAL;
19820421Ssteve	if (!needstat)
19920421Ssteve		flags |= FTS_NOSTAT;
20020421Ssteve	if (Wflag)
20120421Ssteve		flags |= FTS_WHITEOUT;
202249948Seadler	if (xflag)
203249948Seadler		flags |= FTS_XDEV;
204137639Sjkh	if (!(fts = fts_open(argv, flags, NULL))) {
205137639Sjkh		if (fflag && errno == ENOENT)
206137639Sjkh			return;
20799744Sdillon		err(1, "fts_open");
208137639Sjkh	}
2091556Srgrimes	while ((p = fts_read(fts)) != NULL) {
2101556Srgrimes		switch (p->fts_info) {
2111556Srgrimes		case FTS_DNR:
2121556Srgrimes			if (!fflag || p->fts_errno != ENOENT) {
2131556Srgrimes				warnx("%s: %s",
2141556Srgrimes				    p->fts_path, strerror(p->fts_errno));
2151556Srgrimes				eval = 1;
2161556Srgrimes			}
2171556Srgrimes			continue;
2181556Srgrimes		case FTS_ERR:
2191556Srgrimes			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
2201556Srgrimes		case FTS_NS:
2211556Srgrimes			/*
222124041Skuriyama			 * Assume that since fts_read() couldn't stat the
223124041Skuriyama			 * file, it can't be unlinked.
2241556Srgrimes			 */
2251556Srgrimes			if (!needstat)
2261556Srgrimes				break;
2271556Srgrimes			if (!fflag || p->fts_errno != ENOENT) {
2281556Srgrimes				warnx("%s: %s",
2291556Srgrimes				    p->fts_path, strerror(p->fts_errno));
2301556Srgrimes				eval = 1;
2311556Srgrimes			}
2321556Srgrimes			continue;
2331556Srgrimes		case FTS_D:
2341556Srgrimes			/* Pre-order: give user chance to skip. */
23520421Ssteve			if (!fflag && !check(p->fts_path, p->fts_accpath,
2361556Srgrimes			    p->fts_statp)) {
2371556Srgrimes				(void)fts_set(fts, p, FTS_SKIP);
2381556Srgrimes				p->fts_number = SKIPPED;
2391556Srgrimes			}
2407798Sache			else if (!uid &&
2417798Sache				 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
2427798Sache				 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
243193087Sjilles				 lchflags(p->fts_accpath,
2447798Sache					 p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
2457798Sache				goto err;
2461556Srgrimes			continue;
2471556Srgrimes		case FTS_DP:
2481556Srgrimes			/* Post-order: see if user skipped. */
2491556Srgrimes			if (p->fts_number == SKIPPED)
2501556Srgrimes				continue;
2511556Srgrimes			break;
25220421Ssteve		default:
25320421Ssteve			if (!fflag &&
25420421Ssteve			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
25520421Ssteve				continue;
2561556Srgrimes		}
2571556Srgrimes
2587798Sache		rval = 0;
2597798Sache		if (!uid &&
2607798Sache		    (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
2617798Sache		    !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
262193087Sjilles			rval = lchflags(p->fts_accpath,
2637798Sache				       p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
26453819Smharo		if (rval == 0) {
2657798Sache			/*
2667798Sache			 * If we can't read or search the directory, may still be
2677798Sache			 * able to remove it.  Don't print out the un{read,search}able
2687798Sache			 * message unless the remove fails.
2697798Sache			 */
27020421Ssteve			switch (p->fts_info) {
27120421Ssteve			case FTS_DP:
27220421Ssteve			case FTS_DNR:
27353819Smharo				rval = rmdir(p->fts_accpath);
27453819Smharo				if (rval == 0 || (fflag && errno == ENOENT)) {
27553819Smharo					if (rval == 0 && vflag)
27650872Smharo						(void)printf("%s\n",
27770219Sobrien						    p->fts_path);
278191670Simp					if (rval == 0 && info) {
279191670Simp						info = 0;
280191670Simp						(void)printf("%s\n",
281191670Simp						    p->fts_path);
282191670Simp					}
2831556Srgrimes					continue;
28450539Smharo				}
28520421Ssteve				break;
28620421Ssteve
28720421Ssteve			case FTS_W:
28853819Smharo				rval = undelete(p->fts_accpath);
28953819Smharo				if (rval == 0 && (fflag && errno == ENOENT)) {
29053819Smharo					if (vflag)
29150872Smharo						(void)printf("%s\n",
29270219Sobrien						    p->fts_path);
293191670Simp					if (info) {
294191670Simp						info = 0;
295191670Simp						(void)printf("%s\n",
296191670Simp						    p->fts_path);
297191670Simp					}
29820421Ssteve					continue;
29950539Smharo				}
30020421Ssteve				break;
30120421Ssteve
302124041Skuriyama			case FTS_NS:
303124041Skuriyama				/*
304124041Skuriyama				 * Assume that since fts_read() couldn't stat
305124041Skuriyama				 * the file, it can't be unlinked.
306124041Skuriyama				 */
307124041Skuriyama				if (fflag)
308124041Skuriyama					continue;
309124041Skuriyama				/* FALLTHROUGH */
310237339Sdelphij
311237339Sdelphij			case FTS_F:
312237339Sdelphij			case FTS_NSOK:
3137798Sache				if (Pflag)
314237339Sdelphij					if (!rm_overwrite(p->fts_accpath, p->fts_info ==
315237339Sdelphij					    FTS_NSOK ? NULL : p->fts_statp))
316122409Sguido						continue;
317237339Sdelphij				/* FALLTHROUGH */
318237339Sdelphij
319237339Sdelphij			default:
32053819Smharo				rval = unlink(p->fts_accpath);
32153819Smharo				if (rval == 0 || (fflag && errno == ENOENT)) {
32253819Smharo					if (rval == 0 && vflag)
32350872Smharo						(void)printf("%s\n",
32470219Sobrien						    p->fts_path);
325191670Simp					if (rval == 0 && info) {
326191670Simp						info = 0;
327191670Simp						(void)printf("%s\n",
328191670Simp						    p->fts_path);
329191670Simp					}
3307798Sache					continue;
33150539Smharo				}
3327798Sache			}
3331556Srgrimes		}
3347798Sacheerr:
3351556Srgrimes		warn("%s", p->fts_path);
3361556Srgrimes		eval = 1;
3371556Srgrimes	}
338272372Sgjb	if (!fflag && errno)
3391556Srgrimes		err(1, "fts_read");
340157770Smaxim	fts_close(fts);
3411556Srgrimes}
3421556Srgrimes
343249950Seadlerstatic void
34490110Simprm_file(char **argv)
3451556Srgrimes{
3461556Srgrimes	struct stat sb;
34720421Ssteve	int rval;
3481556Srgrimes	char *f;
3491556Srgrimes
3501556Srgrimes	/*
3511556Srgrimes	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
3521556Srgrimes	 * to remove a directory is an error, so must always stat the file.
3531556Srgrimes	 */
3541556Srgrimes	while ((f = *argv++) != NULL) {
3551556Srgrimes		/* Assume if can't stat the file, can't unlink it. */
3561556Srgrimes		if (lstat(f, &sb)) {
35720421Ssteve			if (Wflag) {
35820421Ssteve				sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
35920421Ssteve			} else {
36020421Ssteve				if (!fflag || errno != ENOENT) {
36120421Ssteve					warn("%s", f);
36220421Ssteve					eval = 1;
36320421Ssteve				}
36420421Ssteve				continue;
3651556Srgrimes			}
36620421Ssteve		} else if (Wflag) {
36720421Ssteve			warnx("%s: %s", f, strerror(EEXIST));
36820421Ssteve			eval = 1;
3691556Srgrimes			continue;
3701556Srgrimes		}
37120421Ssteve
37220421Ssteve		if (S_ISDIR(sb.st_mode) && !dflag) {
3731556Srgrimes			warnx("%s: is a directory", f);
3741556Srgrimes			eval = 1;
3751556Srgrimes			continue;
3761556Srgrimes		}
37720421Ssteve		if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
3781556Srgrimes			continue;
3797798Sache		rval = 0;
380163485Smaxim		if (!uid && !S_ISWHT(sb.st_mode) &&
3817798Sache		    (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
3827798Sache		    !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
383193087Sjilles			rval = lchflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
38453819Smharo		if (rval == 0) {
38520421Ssteve			if (S_ISWHT(sb.st_mode))
38620421Ssteve				rval = undelete(f);
38720421Ssteve			else if (S_ISDIR(sb.st_mode))
3887798Sache				rval = rmdir(f);
3897798Sache			else {
3907798Sache				if (Pflag)
391122409Sguido					if (!rm_overwrite(f, &sb))
392122409Sguido						continue;
3937798Sache				rval = unlink(f);
3947798Sache			}
3951556Srgrimes		}
3961556Srgrimes		if (rval && (!fflag || errno != ENOENT)) {
3971556Srgrimes			warn("%s", f);
3981556Srgrimes			eval = 1;
3991556Srgrimes		}
40053819Smharo		if (vflag && rval == 0)
40150539Smharo			(void)printf("%s\n", f);
402191670Simp		if (info && rval == 0) {
403191670Simp			info = 0;
404191670Simp			(void)printf("%s\n", f);
405191670Simp		}
4061556Srgrimes	}
4071556Srgrimes}
4081556Srgrimes
4091556Srgrimes/*
4101556Srgrimes * rm_overwrite --
4111556Srgrimes *	Overwrite the file 3 times with varying bit patterns.
4121556Srgrimes *
4131556Srgrimes * XXX
4141556Srgrimes * This is a cheap way to *really* delete files.  Note that only regular
4151556Srgrimes * files are deleted, directories (and therefore names) will remain.
416102230Strhodes * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
417213582Suqs * System V file system).  In a logging or COW file system, you'll have to
418213582Suqs * have kernel support.
4191556Srgrimes */
420249950Seadlerstatic int
421249949Seadlerrm_overwrite(const char *file, struct stat *sbp)
4221556Srgrimes{
423237284Skevlo	struct stat sb, sb2;
42447584Skris	struct statfs fsb;
4251556Srgrimes	off_t len;
42647584Skris	int bsize, fd, wlen;
42747584Skris	char *buf = NULL;
4281556Srgrimes
4291556Srgrimes	fd = -1;
4301556Srgrimes	if (sbp == NULL) {
4311556Srgrimes		if (lstat(file, &sb))
4321556Srgrimes			goto err;
4331556Srgrimes		sbp = &sb;
4341556Srgrimes	}
4351556Srgrimes	if (!S_ISREG(sbp->st_mode))
436122409Sguido		return (1);
437163812Sdelphij	if (sbp->st_nlink > 1 && !fflag) {
438241014Smdf		warnx("%s (inode %ju): not overwritten due to multiple links",
439241014Smdf		    file, (uintmax_t)sbp->st_ino);
440163812Sdelphij		return (0);
441163777Sdelphij	}
442237284Skevlo	if ((fd = open(file, O_WRONLY|O_NONBLOCK|O_NOFOLLOW, 0)) == -1)
4431556Srgrimes		goto err;
444237284Skevlo	if (fstat(fd, &sb2))
445237284Skevlo		goto err;
446237284Skevlo	if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino ||
447237284Skevlo	    !S_ISREG(sb2.st_mode)) {
448237284Skevlo		errno = EPERM;
449237284Skevlo		goto err;
450237284Skevlo	}
45147584Skris	if (fstatfs(fd, &fsb) == -1)
45247584Skris		goto err;
45347584Skris	bsize = MAX(fsb.f_iosize, 1024);
45447584Skris	if ((buf = malloc(bsize)) == NULL)
455122304Sbde		err(1, "%s: malloc", file);
4561556Srgrimes
4571556Srgrimes#define	PASS(byte) {							\
45847584Skris	memset(buf, byte, bsize);					\
4591556Srgrimes	for (len = sbp->st_size; len > 0; len -= wlen) {		\
46047584Skris		wlen = len < bsize ? len : bsize;			\
4611556Srgrimes		if (write(fd, buf, wlen) != wlen)			\
4621556Srgrimes			goto err;					\
4631556Srgrimes	}								\
4641556Srgrimes}
4651556Srgrimes	PASS(0xff);
4661556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
4671556Srgrimes		goto err;
4681556Srgrimes	PASS(0x00);
4691556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
4701556Srgrimes		goto err;
4711556Srgrimes	PASS(0xff);
47247584Skris	if (!fsync(fd) && !close(fd)) {
47347584Skris		free(buf);
474122409Sguido		return (1);
47547584Skris	}
4761556Srgrimes
4771556Srgrimeserr:	eval = 1;
47847584Skris	if (buf)
47947584Skris		free(buf);
480122304Sbde	if (fd != -1)
481122304Sbde		close(fd);
4821556Srgrimes	warn("%s", file);
483122409Sguido	return (0);
4841556Srgrimes}
4851556Srgrimes
4861556Srgrimes
487249949Seadlerstatic int
488249949Seadlercheck(const char *path, const char *name, struct stat *sp)
4891556Srgrimes{
4901556Srgrimes	int ch, first;
49161749Sjoe	char modep[15], *flagsp;
4921556Srgrimes
4931556Srgrimes	/* Check -i first. */
4941556Srgrimes	if (iflag)
4951556Srgrimes		(void)fprintf(stderr, "remove %s? ", path);
4961556Srgrimes	else {
4971556Srgrimes		/*
4981556Srgrimes		 * If it's not a symbolic link and it's unwritable and we're
499249949Seadler		 * talking to a terminal, ask.  Symbolic links are excluded
5001556Srgrimes		 * because their permissions are meaningless.  Check stdin_ok
5011556Srgrimes		 * first because we may not have stat'ed the file.
5021556Srgrimes		 */
503150729Sdougb		if (!stdin_ok || S_ISLNK(sp->st_mode) ||
50420421Ssteve		    (!access(name, W_OK) &&
5057798Sache		    !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
50620421Ssteve		    (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
5071556Srgrimes			return (1);
5081556Srgrimes		strmode(sp->st_mode, modep);
50961749Sjoe		if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
51099744Sdillon			err(1, "fflagstostr");
511150729Sdougb		if (Pflag)
512150729Sdougb			errx(1,
513150729Sdougb			    "%s: -P was specified, but file is not writable",
514150729Sdougb			    path);
51561749Sjoe		(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
5161556Srgrimes		    modep + 1, modep[9] == ' ' ? "" : " ",
5171556Srgrimes		    user_from_uid(sp->st_uid, 0),
5187798Sache		    group_from_gid(sp->st_gid, 0),
519136112Sdes		    *flagsp ? flagsp : "", *flagsp ? " " : "",
5207798Sache		    path);
52161749Sjoe		free(flagsp);
5221556Srgrimes	}
5231556Srgrimes	(void)fflush(stderr);
5241556Srgrimes
5251556Srgrimes	first = ch = getchar();
5261556Srgrimes	while (ch != '\n' && ch != EOF)
5271556Srgrimes		ch = getchar();
52814409Swosch	return (first == 'y' || first == 'Y');
5291556Srgrimes}
5301556Srgrimes
531136113Sdes#define ISSLASH(a)	((a)[0] == '/' && (a)[1] == '\0')
532249949Seadlerstatic void
533136113Sdescheckslash(char **argv)
534136113Sdes{
535136113Sdes	char **t, **u;
536136113Sdes	int complained;
537136113Sdes
538136113Sdes	complained = 0;
539136113Sdes	for (t = argv; *t;) {
540136113Sdes		if (ISSLASH(*t)) {
541136113Sdes			if (!complained++)
542136113Sdes				warnx("\"/\" may not be removed");
543136113Sdes			eval = 1;
544136113Sdes			for (u = t; u[0] != NULL; ++u)
545136113Sdes				u[0] = u[1];
546136113Sdes		} else {
547136113Sdes			++t;
548136113Sdes		}
549136113Sdes	}
550136113Sdes}
551136113Sdes
552249949Seadlerstatic int
553137009Sdelphijcheck2(char **argv)
554137009Sdelphij{
555137009Sdelphij	struct stat st;
556137009Sdelphij	int first;
557137009Sdelphij	int ch;
558137009Sdelphij	int fcount = 0;
559137009Sdelphij	int dcount = 0;
560137009Sdelphij	int i;
561137009Sdelphij	const char *dname = NULL;
562137009Sdelphij
563137009Sdelphij	for (i = 0; argv[i]; ++i) {
564137009Sdelphij		if (lstat(argv[i], &st) == 0) {
565137009Sdelphij			if (S_ISDIR(st.st_mode)) {
566137009Sdelphij				++dcount;
567137009Sdelphij				dname = argv[i];    /* only used if 1 dir */
568137009Sdelphij			} else {
569137009Sdelphij				++fcount;
570137009Sdelphij			}
571137009Sdelphij		}
572137009Sdelphij	}
573137009Sdelphij	first = 0;
574137009Sdelphij	while (first != 'n' && first != 'N' && first != 'y' && first != 'Y') {
575137009Sdelphij		if (dcount && rflag) {
576137009Sdelphij			fprintf(stderr, "recursively remove");
577137009Sdelphij			if (dcount == 1)
578137009Sdelphij				fprintf(stderr, " %s", dname);
579137009Sdelphij			else
580137009Sdelphij				fprintf(stderr, " %d dirs", dcount);
581137009Sdelphij			if (fcount == 1)
582137009Sdelphij				fprintf(stderr, " and 1 file");
583137009Sdelphij			else if (fcount > 1)
584137009Sdelphij				fprintf(stderr, " and %d files", fcount);
585137009Sdelphij		} else if (dcount + fcount > 3) {
586137009Sdelphij			fprintf(stderr, "remove %d files", dcount + fcount);
587137009Sdelphij		} else {
588137009Sdelphij			return(1);
589137009Sdelphij		}
590137009Sdelphij		fprintf(stderr, "? ");
591137009Sdelphij		fflush(stderr);
592137009Sdelphij
593137009Sdelphij		first = ch = getchar();
594137009Sdelphij		while (ch != '\n' && ch != EOF)
595137009Sdelphij			ch = getchar();
596137009Sdelphij		if (ch == EOF)
597137009Sdelphij			break;
598137009Sdelphij	}
599137009Sdelphij	return (first == 'y' || first == 'Y');
600137009Sdelphij}
601137009Sdelphij
6022927Sphk#define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
603249949Seadlerstatic void
60490110Simpcheckdot(char **argv)
6051556Srgrimes{
6061556Srgrimes	char *p, **save, **t;
6071556Srgrimes	int complained;
6081556Srgrimes
6091556Srgrimes	complained = 0;
6101556Srgrimes	for (t = argv; *t;) {
6111556Srgrimes		if ((p = strrchr(*t, '/')) != NULL)
6121556Srgrimes			++p;
6131556Srgrimes		else
6141556Srgrimes			p = *t;
6151556Srgrimes		if (ISDOT(p)) {
6161556Srgrimes			if (!complained++)
6171556Srgrimes				warnx("\".\" and \"..\" may not be removed");
6181556Srgrimes			eval = 1;
61920421Ssteve			for (save = t; (t[0] = t[1]) != NULL; ++t)
62020421Ssteve				continue;
6211556Srgrimes			t = save;
6221556Srgrimes		} else
6231556Srgrimes			++t;
6241556Srgrimes	}
6251556Srgrimes}
6261556Srgrimes
627249949Seadlerstatic void
62890110Simpusage(void)
6291556Srgrimes{
63053819Smharo
63154895Ssheldonh	(void)fprintf(stderr, "%s\n%s\n",
632249948Seadler	    "usage: rm [-f | -i] [-dIPRrvWx] file ...",
63354895Ssheldonh	    "       unlink file");
63450539Smharo	exit(EX_USAGE);
6351556Srgrimes}
636191670Simp
637191670Simpstatic void
638191670Simpsiginfo(int sig __unused)
639191670Simp{
640191670Simp
641191670Simp	info = 1;
642191670Simp}
643