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>
53287791Sdelphij#include <locale.h>
5490644Simp#include <pwd.h>
55241014Smdf#include <stdint.h>
561556Srgrimes#include <stdio.h>
571556Srgrimes#include <stdlib.h>
581556Srgrimes#include <string.h>
5950539Smharo#include <sysexits.h>
601556Srgrimes#include <unistd.h>
611556Srgrimes
62226961Sedstatic int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
63249948Seadlerstatic int rflag, Iflag, xflag;
64226961Sedstatic uid_t uid;
65226961Sedstatic volatile sig_atomic_t info;
661556Srgrimes
67249949Seadlerstatic int	check(const char *, const char *, struct stat *);
68249949Seadlerstatic int	check2(char **);
69249949Seadlerstatic void	checkdot(char **);
70249949Seadlerstatic void	checkslash(char **);
71249949Seadlerstatic void	rm_file(char **);
72249949Seadlerstatic int	rm_overwrite(const char *, struct stat *);
73249949Seadlerstatic void	rm_tree(char **);
74191670Simpstatic void siginfo(int __unused);
75249949Seadlerstatic void	usage(void);
761556Srgrimes
771556Srgrimes/*
781556Srgrimes * rm --
791556Srgrimes *	This rm is different from historic rm's, but is expected to match
80136112Sdes *	POSIX 1003.2 behavior.	The most visible difference is that -f
811556Srgrimes *	has two specific effects now, ignore non-existent files and force
82136112Sdes *	file removal.
831556Srgrimes */
841556Srgrimesint
8590110Simpmain(int argc, char *argv[])
861556Srgrimes{
87137009Sdelphij	int ch;
8854895Ssheldonh	char *p;
891556Srgrimes
90287791Sdelphij	(void)setlocale(LC_ALL, "");
91287791Sdelphij
9254895Ssheldonh	/*
9354895Ssheldonh	 * Test for the special case where the utility is called as
9454895Ssheldonh	 * "unlink", for which the functionality provided is greatly
9554895Ssheldonh	 * simplified.
9654895Ssheldonh	 */
97219680Sjilles	if ((p = strrchr(argv[0], '/')) == NULL)
9854895Ssheldonh		p = argv[0];
9954895Ssheldonh	else
10054895Ssheldonh		++p;
10154895Ssheldonh	if (strcmp(p, "unlink") == 0) {
10297533Stjr		while (getopt(argc, argv, "") != -1)
10354895Ssheldonh			usage();
10497533Stjr		argc -= optind;
10597533Stjr		argv += optind;
10699858Stjr		if (argc != 1)
10797533Stjr			usage();
10897533Stjr		rm_file(&argv[0]);
10997533Stjr		exit(eval);
11054895Ssheldonh	}
11154895Ssheldonh
112249948Seadler	Pflag = rflag = xflag = 0;
113249948Seadler	while ((ch = getopt(argc, argv, "dfiIPRrvWx")) != -1)
1141556Srgrimes		switch(ch) {
1151556Srgrimes		case 'd':
1161556Srgrimes			dflag = 1;
1171556Srgrimes			break;
1181556Srgrimes		case 'f':
1191556Srgrimes			fflag = 1;
1201556Srgrimes			iflag = 0;
1211556Srgrimes			break;
1221556Srgrimes		case 'i':
1231556Srgrimes			fflag = 0;
1241556Srgrimes			iflag = 1;
1251556Srgrimes			break;
126137009Sdelphij		case 'I':
127137009Sdelphij			Iflag = 1;
128137009Sdelphij			break;
1291556Srgrimes		case 'P':
1301556Srgrimes			Pflag = 1;
1311556Srgrimes			break;
1321556Srgrimes		case 'R':
1331556Srgrimes		case 'r':			/* Compatibility. */
1341556Srgrimes			rflag = 1;
1351556Srgrimes			break;
13650872Smharo		case 'v':
13750872Smharo			vflag = 1;
13850872Smharo			break;
13920421Ssteve		case 'W':
14020421Ssteve			Wflag = 1;
14120421Ssteve			break;
142249948Seadler		case 'x':
143249948Seadler			xflag = 1;
144249948Seadler			break;
1451556Srgrimes		default:
1461556Srgrimes			usage();
1471556Srgrimes		}
1481556Srgrimes	argc -= optind;
1491556Srgrimes	argv += optind;
1501556Srgrimes
15144282Sjkh	if (argc < 1) {
15244282Sjkh		if (fflag)
153122409Sguido			return (0);
1541556Srgrimes		usage();
15544282Sjkh	}
1561556Srgrimes
1571556Srgrimes	checkdot(argv);
158290634Sbapt	checkslash(argv);
1597798Sache	uid = geteuid();
1601556Srgrimes
161191670Simp	(void)signal(SIGINFO, siginfo);
16220421Ssteve	if (*argv) {
16320421Ssteve		stdin_ok = isatty(STDIN_FILENO);
16420421Ssteve
165137009Sdelphij		if (Iflag) {
166137009Sdelphij			if (check2(argv) == 0)
167137009Sdelphij				exit (1);
168137009Sdelphij		}
16920421Ssteve		if (rflag)
17020421Ssteve			rm_tree(argv);
17120421Ssteve		else
17220421Ssteve			rm_file(argv);
17320421Ssteve	}
17420421Ssteve
1751556Srgrimes	exit (eval);
1761556Srgrimes}
1771556Srgrimes
178249949Seadlerstatic void
17990110Simprm_tree(char **argv)
1801556Srgrimes{
1811556Srgrimes	FTS *fts;
1821556Srgrimes	FTSENT *p;
1831556Srgrimes	int needstat;
18420421Ssteve	int flags;
1857798Sache	int rval;
1861556Srgrimes
1871556Srgrimes	/*
1881556Srgrimes	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
1891556Srgrimes	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
1901556Srgrimes	 */
19120421Ssteve	needstat = !uid || (!fflag && !iflag && stdin_ok);
1921556Srgrimes
1931556Srgrimes	/*
1941556Srgrimes	 * If the -i option is specified, the user can skip on the pre-order
1951556Srgrimes	 * visit.  The fts_number field flags skipped directories.
1961556Srgrimes	 */
1971556Srgrimes#define	SKIPPED	1
1981556Srgrimes
19951230Sbde	flags = FTS_PHYSICAL;
20020421Ssteve	if (!needstat)
20120421Ssteve		flags |= FTS_NOSTAT;
20220421Ssteve	if (Wflag)
20320421Ssteve		flags |= FTS_WHITEOUT;
204249948Seadler	if (xflag)
205249948Seadler		flags |= FTS_XDEV;
206137639Sjkh	if (!(fts = fts_open(argv, flags, NULL))) {
207137639Sjkh		if (fflag && errno == ENOENT)
208137639Sjkh			return;
20999744Sdillon		err(1, "fts_open");
210137639Sjkh	}
2111556Srgrimes	while ((p = fts_read(fts)) != NULL) {
2121556Srgrimes		switch (p->fts_info) {
2131556Srgrimes		case FTS_DNR:
2141556Srgrimes			if (!fflag || p->fts_errno != ENOENT) {
2151556Srgrimes				warnx("%s: %s",
2161556Srgrimes				    p->fts_path, strerror(p->fts_errno));
2171556Srgrimes				eval = 1;
2181556Srgrimes			}
2191556Srgrimes			continue;
2201556Srgrimes		case FTS_ERR:
2211556Srgrimes			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
2221556Srgrimes		case FTS_NS:
2231556Srgrimes			/*
224124041Skuriyama			 * Assume that since fts_read() couldn't stat the
225124041Skuriyama			 * file, it can't be unlinked.
2261556Srgrimes			 */
2271556Srgrimes			if (!needstat)
2281556Srgrimes				break;
2291556Srgrimes			if (!fflag || p->fts_errno != ENOENT) {
2301556Srgrimes				warnx("%s: %s",
2311556Srgrimes				    p->fts_path, strerror(p->fts_errno));
2321556Srgrimes				eval = 1;
2331556Srgrimes			}
2341556Srgrimes			continue;
2351556Srgrimes		case FTS_D:
2361556Srgrimes			/* Pre-order: give user chance to skip. */
23720421Ssteve			if (!fflag && !check(p->fts_path, p->fts_accpath,
2381556Srgrimes			    p->fts_statp)) {
2391556Srgrimes				(void)fts_set(fts, p, FTS_SKIP);
2401556Srgrimes				p->fts_number = SKIPPED;
2411556Srgrimes			}
2427798Sache			else if (!uid &&
2437798Sache				 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
2447798Sache				 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
245193087Sjilles				 lchflags(p->fts_accpath,
2467798Sache					 p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
2477798Sache				goto err;
2481556Srgrimes			continue;
2491556Srgrimes		case FTS_DP:
2501556Srgrimes			/* Post-order: see if user skipped. */
2511556Srgrimes			if (p->fts_number == SKIPPED)
2521556Srgrimes				continue;
2531556Srgrimes			break;
25420421Ssteve		default:
25520421Ssteve			if (!fflag &&
25620421Ssteve			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
25720421Ssteve				continue;
2581556Srgrimes		}
2591556Srgrimes
2607798Sache		rval = 0;
2617798Sache		if (!uid &&
2627798Sache		    (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
2637798Sache		    !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
264193087Sjilles			rval = lchflags(p->fts_accpath,
2657798Sache				       p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
26653819Smharo		if (rval == 0) {
2677798Sache			/*
2687798Sache			 * If we can't read or search the directory, may still be
2697798Sache			 * able to remove it.  Don't print out the un{read,search}able
2707798Sache			 * message unless the remove fails.
2717798Sache			 */
27220421Ssteve			switch (p->fts_info) {
27320421Ssteve			case FTS_DP:
27420421Ssteve			case FTS_DNR:
27553819Smharo				rval = rmdir(p->fts_accpath);
27653819Smharo				if (rval == 0 || (fflag && errno == ENOENT)) {
27753819Smharo					if (rval == 0 && vflag)
27850872Smharo						(void)printf("%s\n",
27970219Sobrien						    p->fts_path);
280191670Simp					if (rval == 0 && info) {
281191670Simp						info = 0;
282191670Simp						(void)printf("%s\n",
283191670Simp						    p->fts_path);
284191670Simp					}
2851556Srgrimes					continue;
28650539Smharo				}
28720421Ssteve				break;
28820421Ssteve
28920421Ssteve			case FTS_W:
29053819Smharo				rval = undelete(p->fts_accpath);
29153819Smharo				if (rval == 0 && (fflag && errno == ENOENT)) {
29253819Smharo					if (vflag)
29350872Smharo						(void)printf("%s\n",
29470219Sobrien						    p->fts_path);
295191670Simp					if (info) {
296191670Simp						info = 0;
297191670Simp						(void)printf("%s\n",
298191670Simp						    p->fts_path);
299191670Simp					}
30020421Ssteve					continue;
30150539Smharo				}
30220421Ssteve				break;
30320421Ssteve
304124041Skuriyama			case FTS_NS:
305124041Skuriyama				/*
306124041Skuriyama				 * Assume that since fts_read() couldn't stat
307124041Skuriyama				 * the file, it can't be unlinked.
308124041Skuriyama				 */
309124041Skuriyama				if (fflag)
310124041Skuriyama					continue;
311124041Skuriyama				/* FALLTHROUGH */
312237339Sdelphij
313237339Sdelphij			case FTS_F:
314237339Sdelphij			case FTS_NSOK:
3157798Sache				if (Pflag)
316237339Sdelphij					if (!rm_overwrite(p->fts_accpath, p->fts_info ==
317237339Sdelphij					    FTS_NSOK ? NULL : p->fts_statp))
318122409Sguido						continue;
319237339Sdelphij				/* FALLTHROUGH */
320237339Sdelphij
321237339Sdelphij			default:
32253819Smharo				rval = unlink(p->fts_accpath);
32353819Smharo				if (rval == 0 || (fflag && errno == ENOENT)) {
32453819Smharo					if (rval == 0 && vflag)
32550872Smharo						(void)printf("%s\n",
32670219Sobrien						    p->fts_path);
327191670Simp					if (rval == 0 && info) {
328191670Simp						info = 0;
329191670Simp						(void)printf("%s\n",
330191670Simp						    p->fts_path);
331191670Simp					}
3327798Sache					continue;
33350539Smharo				}
3347798Sache			}
3351556Srgrimes		}
3367798Sacheerr:
3371556Srgrimes		warn("%s", p->fts_path);
3381556Srgrimes		eval = 1;
3391556Srgrimes	}
340272372Sgjb	if (!fflag && errno)
3411556Srgrimes		err(1, "fts_read");
342157770Smaxim	fts_close(fts);
3431556Srgrimes}
3441556Srgrimes
345249950Seadlerstatic void
34690110Simprm_file(char **argv)
3471556Srgrimes{
3481556Srgrimes	struct stat sb;
34920421Ssteve	int rval;
3501556Srgrimes	char *f;
3511556Srgrimes
3521556Srgrimes	/*
3531556Srgrimes	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
3541556Srgrimes	 * to remove a directory is an error, so must always stat the file.
3551556Srgrimes	 */
3561556Srgrimes	while ((f = *argv++) != NULL) {
3571556Srgrimes		/* Assume if can't stat the file, can't unlink it. */
3581556Srgrimes		if (lstat(f, &sb)) {
35920421Ssteve			if (Wflag) {
36020421Ssteve				sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
36120421Ssteve			} else {
36220421Ssteve				if (!fflag || errno != ENOENT) {
36320421Ssteve					warn("%s", f);
36420421Ssteve					eval = 1;
36520421Ssteve				}
36620421Ssteve				continue;
3671556Srgrimes			}
36820421Ssteve		} else if (Wflag) {
36920421Ssteve			warnx("%s: %s", f, strerror(EEXIST));
37020421Ssteve			eval = 1;
3711556Srgrimes			continue;
3721556Srgrimes		}
37320421Ssteve
37420421Ssteve		if (S_ISDIR(sb.st_mode) && !dflag) {
3751556Srgrimes			warnx("%s: is a directory", f);
3761556Srgrimes			eval = 1;
3771556Srgrimes			continue;
3781556Srgrimes		}
37920421Ssteve		if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
3801556Srgrimes			continue;
3817798Sache		rval = 0;
382163485Smaxim		if (!uid && !S_ISWHT(sb.st_mode) &&
3837798Sache		    (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
3847798Sache		    !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
385193087Sjilles			rval = lchflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
38653819Smharo		if (rval == 0) {
38720421Ssteve			if (S_ISWHT(sb.st_mode))
38820421Ssteve				rval = undelete(f);
38920421Ssteve			else if (S_ISDIR(sb.st_mode))
3907798Sache				rval = rmdir(f);
3917798Sache			else {
3927798Sache				if (Pflag)
393122409Sguido					if (!rm_overwrite(f, &sb))
394122409Sguido						continue;
3957798Sache				rval = unlink(f);
3967798Sache			}
3971556Srgrimes		}
3981556Srgrimes		if (rval && (!fflag || errno != ENOENT)) {
3991556Srgrimes			warn("%s", f);
4001556Srgrimes			eval = 1;
4011556Srgrimes		}
40253819Smharo		if (vflag && rval == 0)
40350539Smharo			(void)printf("%s\n", f);
404191670Simp		if (info && rval == 0) {
405191670Simp			info = 0;
406191670Simp			(void)printf("%s\n", f);
407191670Simp		}
4081556Srgrimes	}
4091556Srgrimes}
4101556Srgrimes
4111556Srgrimes/*
4121556Srgrimes * rm_overwrite --
4131556Srgrimes *	Overwrite the file 3 times with varying bit patterns.
4141556Srgrimes *
4151556Srgrimes * XXX
4161556Srgrimes * This is a cheap way to *really* delete files.  Note that only regular
4171556Srgrimes * files are deleted, directories (and therefore names) will remain.
418102230Strhodes * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
419213582Suqs * System V file system).  In a logging or COW file system, you'll have to
420213582Suqs * have kernel support.
4211556Srgrimes */
422249950Seadlerstatic int
423249949Seadlerrm_overwrite(const char *file, struct stat *sbp)
4241556Srgrimes{
425237284Skevlo	struct stat sb, sb2;
42647584Skris	struct statfs fsb;
4271556Srgrimes	off_t len;
42847584Skris	int bsize, fd, wlen;
42947584Skris	char *buf = NULL;
4301556Srgrimes
4311556Srgrimes	fd = -1;
4321556Srgrimes	if (sbp == NULL) {
4331556Srgrimes		if (lstat(file, &sb))
4341556Srgrimes			goto err;
4351556Srgrimes		sbp = &sb;
4361556Srgrimes	}
4371556Srgrimes	if (!S_ISREG(sbp->st_mode))
438122409Sguido		return (1);
439163812Sdelphij	if (sbp->st_nlink > 1 && !fflag) {
440241014Smdf		warnx("%s (inode %ju): not overwritten due to multiple links",
441241014Smdf		    file, (uintmax_t)sbp->st_ino);
442163812Sdelphij		return (0);
443163777Sdelphij	}
444237284Skevlo	if ((fd = open(file, O_WRONLY|O_NONBLOCK|O_NOFOLLOW, 0)) == -1)
4451556Srgrimes		goto err;
446237284Skevlo	if (fstat(fd, &sb2))
447237284Skevlo		goto err;
448237284Skevlo	if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino ||
449237284Skevlo	    !S_ISREG(sb2.st_mode)) {
450237284Skevlo		errno = EPERM;
451237284Skevlo		goto err;
452237284Skevlo	}
45347584Skris	if (fstatfs(fd, &fsb) == -1)
45447584Skris		goto err;
45547584Skris	bsize = MAX(fsb.f_iosize, 1024);
45647584Skris	if ((buf = malloc(bsize)) == NULL)
457122304Sbde		err(1, "%s: malloc", file);
4581556Srgrimes
4591556Srgrimes#define	PASS(byte) {							\
46047584Skris	memset(buf, byte, bsize);					\
4611556Srgrimes	for (len = sbp->st_size; len > 0; len -= wlen) {		\
46247584Skris		wlen = len < bsize ? len : bsize;			\
4631556Srgrimes		if (write(fd, buf, wlen) != wlen)			\
4641556Srgrimes			goto err;					\
4651556Srgrimes	}								\
4661556Srgrimes}
4671556Srgrimes	PASS(0xff);
4681556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
4691556Srgrimes		goto err;
4701556Srgrimes	PASS(0x00);
4711556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
4721556Srgrimes		goto err;
4731556Srgrimes	PASS(0xff);
47447584Skris	if (!fsync(fd) && !close(fd)) {
47547584Skris		free(buf);
476122409Sguido		return (1);
47747584Skris	}
4781556Srgrimes
4791556Srgrimeserr:	eval = 1;
48047584Skris	if (buf)
48147584Skris		free(buf);
482122304Sbde	if (fd != -1)
483122304Sbde		close(fd);
4841556Srgrimes	warn("%s", file);
485122409Sguido	return (0);
4861556Srgrimes}
4871556Srgrimes
4881556Srgrimes
489249949Seadlerstatic int
490249949Seadlercheck(const char *path, const char *name, struct stat *sp)
4911556Srgrimes{
4921556Srgrimes	int ch, first;
49361749Sjoe	char modep[15], *flagsp;
4941556Srgrimes
4951556Srgrimes	/* Check -i first. */
4961556Srgrimes	if (iflag)
4971556Srgrimes		(void)fprintf(stderr, "remove %s? ", path);
4981556Srgrimes	else {
4991556Srgrimes		/*
5001556Srgrimes		 * If it's not a symbolic link and it's unwritable and we're
501249949Seadler		 * talking to a terminal, ask.  Symbolic links are excluded
5021556Srgrimes		 * because their permissions are meaningless.  Check stdin_ok
5031556Srgrimes		 * first because we may not have stat'ed the file.
5041556Srgrimes		 */
505150729Sdougb		if (!stdin_ok || S_ISLNK(sp->st_mode) ||
50620421Ssteve		    (!access(name, W_OK) &&
5077798Sache		    !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
50820421Ssteve		    (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
5091556Srgrimes			return (1);
5101556Srgrimes		strmode(sp->st_mode, modep);
51161749Sjoe		if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
51299744Sdillon			err(1, "fflagstostr");
513150729Sdougb		if (Pflag)
514150729Sdougb			errx(1,
515150729Sdougb			    "%s: -P was specified, but file is not writable",
516150729Sdougb			    path);
51761749Sjoe		(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
5181556Srgrimes		    modep + 1, modep[9] == ' ' ? "" : " ",
5191556Srgrimes		    user_from_uid(sp->st_uid, 0),
5207798Sache		    group_from_gid(sp->st_gid, 0),
521136112Sdes		    *flagsp ? flagsp : "", *flagsp ? " " : "",
5227798Sache		    path);
52361749Sjoe		free(flagsp);
5241556Srgrimes	}
5251556Srgrimes	(void)fflush(stderr);
5261556Srgrimes
5271556Srgrimes	first = ch = getchar();
5281556Srgrimes	while (ch != '\n' && ch != EOF)
5291556Srgrimes		ch = getchar();
53014409Swosch	return (first == 'y' || first == 'Y');
5311556Srgrimes}
5321556Srgrimes
533136113Sdes#define ISSLASH(a)	((a)[0] == '/' && (a)[1] == '\0')
534249949Seadlerstatic void
535136113Sdescheckslash(char **argv)
536136113Sdes{
537136113Sdes	char **t, **u;
538136113Sdes	int complained;
539136113Sdes
540136113Sdes	complained = 0;
541136113Sdes	for (t = argv; *t;) {
542136113Sdes		if (ISSLASH(*t)) {
543136113Sdes			if (!complained++)
544136113Sdes				warnx("\"/\" may not be removed");
545136113Sdes			eval = 1;
546136113Sdes			for (u = t; u[0] != NULL; ++u)
547136113Sdes				u[0] = u[1];
548136113Sdes		} else {
549136113Sdes			++t;
550136113Sdes		}
551136113Sdes	}
552136113Sdes}
553136113Sdes
554249949Seadlerstatic int
555137009Sdelphijcheck2(char **argv)
556137009Sdelphij{
557137009Sdelphij	struct stat st;
558137009Sdelphij	int first;
559137009Sdelphij	int ch;
560137009Sdelphij	int fcount = 0;
561137009Sdelphij	int dcount = 0;
562137009Sdelphij	int i;
563137009Sdelphij	const char *dname = NULL;
564137009Sdelphij
565137009Sdelphij	for (i = 0; argv[i]; ++i) {
566137009Sdelphij		if (lstat(argv[i], &st) == 0) {
567137009Sdelphij			if (S_ISDIR(st.st_mode)) {
568137009Sdelphij				++dcount;
569137009Sdelphij				dname = argv[i];    /* only used if 1 dir */
570137009Sdelphij			} else {
571137009Sdelphij				++fcount;
572137009Sdelphij			}
573137009Sdelphij		}
574137009Sdelphij	}
575137009Sdelphij	first = 0;
576137009Sdelphij	while (first != 'n' && first != 'N' && first != 'y' && first != 'Y') {
577137009Sdelphij		if (dcount && rflag) {
578137009Sdelphij			fprintf(stderr, "recursively remove");
579137009Sdelphij			if (dcount == 1)
580137009Sdelphij				fprintf(stderr, " %s", dname);
581137009Sdelphij			else
582137009Sdelphij				fprintf(stderr, " %d dirs", dcount);
583137009Sdelphij			if (fcount == 1)
584137009Sdelphij				fprintf(stderr, " and 1 file");
585137009Sdelphij			else if (fcount > 1)
586137009Sdelphij				fprintf(stderr, " and %d files", fcount);
587137009Sdelphij		} else if (dcount + fcount > 3) {
588137009Sdelphij			fprintf(stderr, "remove %d files", dcount + fcount);
589137009Sdelphij		} else {
590137009Sdelphij			return(1);
591137009Sdelphij		}
592137009Sdelphij		fprintf(stderr, "? ");
593137009Sdelphij		fflush(stderr);
594137009Sdelphij
595137009Sdelphij		first = ch = getchar();
596137009Sdelphij		while (ch != '\n' && ch != EOF)
597137009Sdelphij			ch = getchar();
598137009Sdelphij		if (ch == EOF)
599137009Sdelphij			break;
600137009Sdelphij	}
601137009Sdelphij	return (first == 'y' || first == 'Y');
602137009Sdelphij}
603137009Sdelphij
6042927Sphk#define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
605249949Seadlerstatic void
60690110Simpcheckdot(char **argv)
6071556Srgrimes{
6081556Srgrimes	char *p, **save, **t;
6091556Srgrimes	int complained;
6101556Srgrimes
6111556Srgrimes	complained = 0;
6121556Srgrimes	for (t = argv; *t;) {
6131556Srgrimes		if ((p = strrchr(*t, '/')) != NULL)
6141556Srgrimes			++p;
6151556Srgrimes		else
6161556Srgrimes			p = *t;
6171556Srgrimes		if (ISDOT(p)) {
6181556Srgrimes			if (!complained++)
6191556Srgrimes				warnx("\".\" and \"..\" may not be removed");
6201556Srgrimes			eval = 1;
62120421Ssteve			for (save = t; (t[0] = t[1]) != NULL; ++t)
62220421Ssteve				continue;
6231556Srgrimes			t = save;
6241556Srgrimes		} else
6251556Srgrimes			++t;
6261556Srgrimes	}
6271556Srgrimes}
6281556Srgrimes
629249949Seadlerstatic void
63090110Simpusage(void)
6311556Srgrimes{
63253819Smharo
63354895Ssheldonh	(void)fprintf(stderr, "%s\n%s\n",
634249948Seadler	    "usage: rm [-f | -i] [-dIPRrvWx] file ...",
63554895Ssheldonh	    "       unlink file");
63650539Smharo	exit(EX_USAGE);
6371556Srgrimes}
638191670Simp
639191670Simpstatic void
640191670Simpsiginfo(int sig __unused)
641191670Simp{
642191670Simp
643191670Simp	info = 1;
644191670Simp}
645