rm.c revision 163777
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 163777 2006-10-30 03:32:09Z delphij $");
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;
61137009Sdelphijint rflag, Iflag;
627798Sacheuid_t uid;
631556Srgrimes
6490110Simpint	check(char *, char *, struct stat *);
65137009Sdelphijint	check2(char **);
6690110Simpvoid	checkdot(char **);
67136113Sdesvoid	checkslash(char **);
6890110Simpvoid	rm_file(char **);
69122409Sguidoint	rm_overwrite(char *, struct stat *);
7090110Simpvoid	rm_tree(char **);
7190110Simpvoid	usage(void);
721556Srgrimes
731556Srgrimes/*
741556Srgrimes * rm --
751556Srgrimes *	This rm is different from historic rm's, but is expected to match
76136112Sdes *	POSIX 1003.2 behavior.	The most visible difference is that -f
771556Srgrimes *	has two specific effects now, ignore non-existent files and force
78136112Sdes *	file removal.
791556Srgrimes */
801556Srgrimesint
8190110Simpmain(int argc, char *argv[])
821556Srgrimes{
83137009Sdelphij	int ch;
8454895Ssheldonh	char *p;
851556Srgrimes
8654895Ssheldonh	/*
8754895Ssheldonh	 * Test for the special case where the utility is called as
8854895Ssheldonh	 * "unlink", for which the functionality provided is greatly
8954895Ssheldonh	 * simplified.
9054895Ssheldonh	 */
9154895Ssheldonh	if ((p = rindex(argv[0], '/')) == NULL)
9254895Ssheldonh		p = argv[0];
9354895Ssheldonh	else
9454895Ssheldonh		++p;
9554895Ssheldonh	if (strcmp(p, "unlink") == 0) {
9697533Stjr		while (getopt(argc, argv, "") != -1)
9754895Ssheldonh			usage();
9897533Stjr		argc -= optind;
9997533Stjr		argv += optind;
10099858Stjr		if (argc != 1)
10197533Stjr			usage();
10297533Stjr		rm_file(&argv[0]);
10397533Stjr		exit(eval);
10454895Ssheldonh	}
10554895Ssheldonh
10620421Ssteve	Pflag = rflag = 0;
107137009Sdelphij	while ((ch = getopt(argc, argv, "dfiIPRrvW")) != -1)
1081556Srgrimes		switch(ch) {
1091556Srgrimes		case 'd':
1101556Srgrimes			dflag = 1;
1111556Srgrimes			break;
1121556Srgrimes		case 'f':
1131556Srgrimes			fflag = 1;
1141556Srgrimes			iflag = 0;
1151556Srgrimes			break;
1161556Srgrimes		case 'i':
1171556Srgrimes			fflag = 0;
1181556Srgrimes			iflag = 1;
1191556Srgrimes			break;
120137009Sdelphij		case 'I':
121137009Sdelphij			Iflag = 1;
122137009Sdelphij			break;
1231556Srgrimes		case 'P':
1241556Srgrimes			Pflag = 1;
1251556Srgrimes			break;
1261556Srgrimes		case 'R':
1271556Srgrimes		case 'r':			/* Compatibility. */
1281556Srgrimes			rflag = 1;
1291556Srgrimes			break;
13050872Smharo		case 'v':
13150872Smharo			vflag = 1;
13250872Smharo			break;
13320421Ssteve		case 'W':
13420421Ssteve			Wflag = 1;
13520421Ssteve			break;
1361556Srgrimes		default:
1371556Srgrimes			usage();
1381556Srgrimes		}
1391556Srgrimes	argc -= optind;
1401556Srgrimes	argv += optind;
1411556Srgrimes
14244282Sjkh	if (argc < 1) {
14344282Sjkh		if (fflag)
144122409Sguido			return (0);
1451556Srgrimes		usage();
14644282Sjkh	}
1471556Srgrimes
1481556Srgrimes	checkdot(argv);
149136124Sdes	if (getenv("POSIXLY_CORRECT") == NULL)
150136124Sdes		checkslash(argv);
1517798Sache	uid = geteuid();
1521556Srgrimes
15320421Ssteve	if (*argv) {
15420421Ssteve		stdin_ok = isatty(STDIN_FILENO);
15520421Ssteve
156137009Sdelphij		if (Iflag) {
157137009Sdelphij			if (check2(argv) == 0)
158137009Sdelphij				exit (1);
159137009Sdelphij		}
16020421Ssteve		if (rflag)
16120421Ssteve			rm_tree(argv);
16220421Ssteve		else
16320421Ssteve			rm_file(argv);
16420421Ssteve	}
16520421Ssteve
1661556Srgrimes	exit (eval);
1671556Srgrimes}
1681556Srgrimes
1691556Srgrimesvoid
17090110Simprm_tree(char **argv)
1711556Srgrimes{
1721556Srgrimes	FTS *fts;
1731556Srgrimes	FTSENT *p;
1741556Srgrimes	int needstat;
17520421Ssteve	int flags;
1767798Sache	int rval;
1771556Srgrimes
1781556Srgrimes	/*
1791556Srgrimes	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
1801556Srgrimes	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
1811556Srgrimes	 */
18220421Ssteve	needstat = !uid || (!fflag && !iflag && stdin_ok);
1831556Srgrimes
1841556Srgrimes	/*
1851556Srgrimes	 * If the -i option is specified, the user can skip on the pre-order
1861556Srgrimes	 * visit.  The fts_number field flags skipped directories.
1871556Srgrimes	 */
1881556Srgrimes#define	SKIPPED	1
1891556Srgrimes
19051230Sbde	flags = FTS_PHYSICAL;
19120421Ssteve	if (!needstat)
19220421Ssteve		flags |= FTS_NOSTAT;
19320421Ssteve	if (Wflag)
19420421Ssteve		flags |= FTS_WHITEOUT;
195137639Sjkh	if (!(fts = fts_open(argv, flags, NULL))) {
196137639Sjkh		if (fflag && errno == ENOENT)
197137639Sjkh			return;
19899744Sdillon		err(1, "fts_open");
199137639Sjkh	}
2001556Srgrimes	while ((p = fts_read(fts)) != NULL) {
2011556Srgrimes		switch (p->fts_info) {
2021556Srgrimes		case FTS_DNR:
2031556Srgrimes			if (!fflag || p->fts_errno != ENOENT) {
2041556Srgrimes				warnx("%s: %s",
2051556Srgrimes				    p->fts_path, strerror(p->fts_errno));
2061556Srgrimes				eval = 1;
2071556Srgrimes			}
2081556Srgrimes			continue;
2091556Srgrimes		case FTS_ERR:
2101556Srgrimes			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
2111556Srgrimes		case FTS_NS:
2121556Srgrimes			/*
213124041Skuriyama			 * Assume that since fts_read() couldn't stat the
214124041Skuriyama			 * file, it can't be unlinked.
2151556Srgrimes			 */
2161556Srgrimes			if (!needstat)
2171556Srgrimes				break;
2181556Srgrimes			if (!fflag || p->fts_errno != ENOENT) {
2191556Srgrimes				warnx("%s: %s",
2201556Srgrimes				    p->fts_path, strerror(p->fts_errno));
2211556Srgrimes				eval = 1;
2221556Srgrimes			}
2231556Srgrimes			continue;
2241556Srgrimes		case FTS_D:
2251556Srgrimes			/* Pre-order: give user chance to skip. */
22620421Ssteve			if (!fflag && !check(p->fts_path, p->fts_accpath,
2271556Srgrimes			    p->fts_statp)) {
2281556Srgrimes				(void)fts_set(fts, p, FTS_SKIP);
2291556Srgrimes				p->fts_number = SKIPPED;
2301556Srgrimes			}
2317798Sache			else if (!uid &&
2327798Sache				 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
2337798Sache				 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
2347798Sache				 chflags(p->fts_accpath,
2357798Sache					 p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
2367798Sache				goto err;
2371556Srgrimes			continue;
2381556Srgrimes		case FTS_DP:
2391556Srgrimes			/* Post-order: see if user skipped. */
2401556Srgrimes			if (p->fts_number == SKIPPED)
2411556Srgrimes				continue;
2421556Srgrimes			break;
24320421Ssteve		default:
24420421Ssteve			if (!fflag &&
24520421Ssteve			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
24620421Ssteve				continue;
2471556Srgrimes		}
2481556Srgrimes
2497798Sache		rval = 0;
2507798Sache		if (!uid &&
2517798Sache		    (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
2527798Sache		    !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
2537798Sache			rval = chflags(p->fts_accpath,
2547798Sache				       p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
25553819Smharo		if (rval == 0) {
2567798Sache			/*
2577798Sache			 * If we can't read or search the directory, may still be
2587798Sache			 * able to remove it.  Don't print out the un{read,search}able
2597798Sache			 * message unless the remove fails.
2607798Sache			 */
26120421Ssteve			switch (p->fts_info) {
26220421Ssteve			case FTS_DP:
26320421Ssteve			case FTS_DNR:
26453819Smharo				rval = rmdir(p->fts_accpath);
26553819Smharo				if (rval == 0 || (fflag && errno == ENOENT)) {
26653819Smharo					if (rval == 0 && vflag)
26750872Smharo						(void)printf("%s\n",
26870219Sobrien						    p->fts_path);
2691556Srgrimes					continue;
27050539Smharo				}
27120421Ssteve				break;
27220421Ssteve
27320421Ssteve			case FTS_W:
27453819Smharo				rval = undelete(p->fts_accpath);
27553819Smharo				if (rval == 0 && (fflag && errno == ENOENT)) {
27653819Smharo					if (vflag)
27750872Smharo						(void)printf("%s\n",
27870219Sobrien						    p->fts_path);
27920421Ssteve					continue;
28050539Smharo				}
28120421Ssteve				break;
28220421Ssteve
283124041Skuriyama			case FTS_NS:
284124041Skuriyama				/*
285124041Skuriyama				 * Assume that since fts_read() couldn't stat
286124041Skuriyama				 * the file, it can't be unlinked.
287124041Skuriyama				 */
288124041Skuriyama				if (fflag)
289124041Skuriyama					continue;
290124041Skuriyama				/* FALLTHROUGH */
29120421Ssteve			default:
2927798Sache				if (Pflag)
293122409Sguido					if (!rm_overwrite(p->fts_accpath, NULL))
294122409Sguido						continue;
29553819Smharo				rval = unlink(p->fts_accpath);
29653819Smharo				if (rval == 0 || (fflag && errno == ENOENT)) {
29753819Smharo					if (rval == 0 && vflag)
29850872Smharo						(void)printf("%s\n",
29970219Sobrien						    p->fts_path);
3007798Sache					continue;
30150539Smharo				}
3027798Sache			}
3031556Srgrimes		}
3047798Sacheerr:
3051556Srgrimes		warn("%s", p->fts_path);
3061556Srgrimes		eval = 1;
3071556Srgrimes	}
3081556Srgrimes	if (errno)
3091556Srgrimes		err(1, "fts_read");
310157770Smaxim	fts_close(fts);
3111556Srgrimes}
3121556Srgrimes
3131556Srgrimesvoid
31490110Simprm_file(char **argv)
3151556Srgrimes{
3161556Srgrimes	struct stat sb;
31720421Ssteve	int rval;
3181556Srgrimes	char *f;
3191556Srgrimes
3201556Srgrimes	/*
3211556Srgrimes	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
3221556Srgrimes	 * to remove a directory is an error, so must always stat the file.
3231556Srgrimes	 */
3241556Srgrimes	while ((f = *argv++) != NULL) {
3251556Srgrimes		/* Assume if can't stat the file, can't unlink it. */
3261556Srgrimes		if (lstat(f, &sb)) {
32720421Ssteve			if (Wflag) {
32820421Ssteve				sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
32920421Ssteve			} else {
33020421Ssteve				if (!fflag || errno != ENOENT) {
33120421Ssteve					warn("%s", f);
33220421Ssteve					eval = 1;
33320421Ssteve				}
33420421Ssteve				continue;
3351556Srgrimes			}
33620421Ssteve		} else if (Wflag) {
33720421Ssteve			warnx("%s: %s", f, strerror(EEXIST));
33820421Ssteve			eval = 1;
3391556Srgrimes			continue;
3401556Srgrimes		}
34120421Ssteve
34220421Ssteve		if (S_ISDIR(sb.st_mode) && !dflag) {
3431556Srgrimes			warnx("%s: is a directory", f);
3441556Srgrimes			eval = 1;
3451556Srgrimes			continue;
3461556Srgrimes		}
34720421Ssteve		if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
3481556Srgrimes			continue;
3497798Sache		rval = 0;
350163485Smaxim		if (!uid && !S_ISWHT(sb.st_mode) &&
3517798Sache		    (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
3527798Sache		    !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
3537798Sache			rval = chflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
35453819Smharo		if (rval == 0) {
35520421Ssteve			if (S_ISWHT(sb.st_mode))
35620421Ssteve				rval = undelete(f);
35720421Ssteve			else if (S_ISDIR(sb.st_mode))
3587798Sache				rval = rmdir(f);
3597798Sache			else {
3607798Sache				if (Pflag)
361122409Sguido					if (!rm_overwrite(f, &sb))
362122409Sguido						continue;
3637798Sache				rval = unlink(f);
3647798Sache			}
3651556Srgrimes		}
3661556Srgrimes		if (rval && (!fflag || errno != ENOENT)) {
3671556Srgrimes			warn("%s", f);
3681556Srgrimes			eval = 1;
3691556Srgrimes		}
37053819Smharo		if (vflag && rval == 0)
37150539Smharo			(void)printf("%s\n", f);
3721556Srgrimes	}
3731556Srgrimes}
3741556Srgrimes
3751556Srgrimes/*
3761556Srgrimes * rm_overwrite --
3771556Srgrimes *	Overwrite the file 3 times with varying bit patterns.
3781556Srgrimes *
3791556Srgrimes * XXX
3801556Srgrimes * This is a cheap way to *really* delete files.  Note that only regular
3811556Srgrimes * files are deleted, directories (and therefore names) will remain.
382102230Strhodes * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
383102230Strhodes * System V file system).  In a logging file system, you'll have to have
3841556Srgrimes * kernel support.
3851556Srgrimes */
386122409Sguidoint
38790110Simprm_overwrite(char *file, struct stat *sbp)
3881556Srgrimes{
3891556Srgrimes	struct stat sb;
39047584Skris	struct statfs fsb;
3911556Srgrimes	off_t len;
39247584Skris	int bsize, fd, wlen;
39347584Skris	char *buf = NULL;
3941556Srgrimes
3951556Srgrimes	fd = -1;
3961556Srgrimes	if (sbp == NULL) {
3971556Srgrimes		if (lstat(file, &sb))
3981556Srgrimes			goto err;
3991556Srgrimes		sbp = &sb;
4001556Srgrimes	}
4011556Srgrimes	if (!S_ISREG(sbp->st_mode))
402122409Sguido		return (1);
403163777Sdelphij	if (sbp->st_nlink > 1) {
404163777Sdelphij		warnx("%s (inode %u): not overwritten due to multiple links",
405163777Sdelphij		    file, sbp->st_ino);
406163777Sdelphij		return (1);
407163777Sdelphij	}
4081556Srgrimes	if ((fd = open(file, O_WRONLY, 0)) == -1)
4091556Srgrimes		goto err;
41047584Skris	if (fstatfs(fd, &fsb) == -1)
41147584Skris		goto err;
41247584Skris	bsize = MAX(fsb.f_iosize, 1024);
41347584Skris	if ((buf = malloc(bsize)) == NULL)
414122304Sbde		err(1, "%s: malloc", file);
4151556Srgrimes
4161556Srgrimes#define	PASS(byte) {							\
41747584Skris	memset(buf, byte, bsize);					\
4181556Srgrimes	for (len = sbp->st_size; len > 0; len -= wlen) {		\
41947584Skris		wlen = len < bsize ? len : bsize;			\
4201556Srgrimes		if (write(fd, buf, wlen) != wlen)			\
4211556Srgrimes			goto err;					\
4221556Srgrimes	}								\
4231556Srgrimes}
4241556Srgrimes	PASS(0xff);
4251556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
4261556Srgrimes		goto err;
4271556Srgrimes	PASS(0x00);
4281556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
4291556Srgrimes		goto err;
4301556Srgrimes	PASS(0xff);
43147584Skris	if (!fsync(fd) && !close(fd)) {
43247584Skris		free(buf);
433122409Sguido		return (1);
43447584Skris	}
4351556Srgrimes
4361556Srgrimeserr:	eval = 1;
43747584Skris	if (buf)
43847584Skris		free(buf);
439122304Sbde	if (fd != -1)
440122304Sbde		close(fd);
4411556Srgrimes	warn("%s", file);
442122409Sguido	return (0);
4431556Srgrimes}
4441556Srgrimes
4451556Srgrimes
4461556Srgrimesint
44790110Simpcheck(char *path, char *name, struct stat *sp)
4481556Srgrimes{
4491556Srgrimes	int ch, first;
45061749Sjoe	char modep[15], *flagsp;
4511556Srgrimes
4521556Srgrimes	/* Check -i first. */
4531556Srgrimes	if (iflag)
4541556Srgrimes		(void)fprintf(stderr, "remove %s? ", path);
4551556Srgrimes	else {
4561556Srgrimes		/*
4571556Srgrimes		 * If it's not a symbolic link and it's unwritable and we're
458136112Sdes		 * talking to a terminal, ask.	Symbolic links are excluded
4591556Srgrimes		 * because their permissions are meaningless.  Check stdin_ok
4601556Srgrimes		 * first because we may not have stat'ed the file.
4611556Srgrimes		 */
462150729Sdougb		if (!stdin_ok || S_ISLNK(sp->st_mode) ||
46320421Ssteve		    (!access(name, W_OK) &&
4647798Sache		    !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
46520421Ssteve		    (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
4661556Srgrimes			return (1);
4671556Srgrimes		strmode(sp->st_mode, modep);
46861749Sjoe		if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
46999744Sdillon			err(1, "fflagstostr");
470150729Sdougb		if (Pflag)
471150729Sdougb			errx(1,
472150729Sdougb			    "%s: -P was specified, but file is not writable",
473150729Sdougb			    path);
47461749Sjoe		(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
4751556Srgrimes		    modep + 1, modep[9] == ' ' ? "" : " ",
4761556Srgrimes		    user_from_uid(sp->st_uid, 0),
4777798Sache		    group_from_gid(sp->st_gid, 0),
478136112Sdes		    *flagsp ? flagsp : "", *flagsp ? " " : "",
4797798Sache		    path);
48061749Sjoe		free(flagsp);
4811556Srgrimes	}
4821556Srgrimes	(void)fflush(stderr);
4831556Srgrimes
4841556Srgrimes	first = ch = getchar();
4851556Srgrimes	while (ch != '\n' && ch != EOF)
4861556Srgrimes		ch = getchar();
48714409Swosch	return (first == 'y' || first == 'Y');
4881556Srgrimes}
4891556Srgrimes
490136113Sdes#define ISSLASH(a)	((a)[0] == '/' && (a)[1] == '\0')
491136113Sdesvoid
492136113Sdescheckslash(char **argv)
493136113Sdes{
494136113Sdes	char **t, **u;
495136113Sdes	int complained;
496136113Sdes
497136113Sdes	complained = 0;
498136113Sdes	for (t = argv; *t;) {
499136113Sdes		if (ISSLASH(*t)) {
500136113Sdes			if (!complained++)
501136113Sdes				warnx("\"/\" may not be removed");
502136113Sdes			eval = 1;
503136113Sdes			for (u = t; u[0] != NULL; ++u)
504136113Sdes				u[0] = u[1];
505136113Sdes		} else {
506136113Sdes			++t;
507136113Sdes		}
508136113Sdes	}
509136113Sdes}
510136113Sdes
511137009Sdelphijint
512137009Sdelphijcheck2(char **argv)
513137009Sdelphij{
514137009Sdelphij	struct stat st;
515137009Sdelphij	int first;
516137009Sdelphij	int ch;
517137009Sdelphij	int fcount = 0;
518137009Sdelphij	int dcount = 0;
519137009Sdelphij	int i;
520137009Sdelphij	const char *dname = NULL;
521137009Sdelphij
522137009Sdelphij	for (i = 0; argv[i]; ++i) {
523137009Sdelphij		if (lstat(argv[i], &st) == 0) {
524137009Sdelphij			if (S_ISDIR(st.st_mode)) {
525137009Sdelphij				++dcount;
526137009Sdelphij				dname = argv[i];    /* only used if 1 dir */
527137009Sdelphij			} else {
528137009Sdelphij				++fcount;
529137009Sdelphij			}
530137009Sdelphij		}
531137009Sdelphij	}
532137009Sdelphij	first = 0;
533137009Sdelphij	while (first != 'n' && first != 'N' && first != 'y' && first != 'Y') {
534137009Sdelphij		if (dcount && rflag) {
535137009Sdelphij			fprintf(stderr, "recursively remove");
536137009Sdelphij			if (dcount == 1)
537137009Sdelphij				fprintf(stderr, " %s", dname);
538137009Sdelphij			else
539137009Sdelphij				fprintf(stderr, " %d dirs", dcount);
540137009Sdelphij			if (fcount == 1)
541137009Sdelphij				fprintf(stderr, " and 1 file");
542137009Sdelphij			else if (fcount > 1)
543137009Sdelphij				fprintf(stderr, " and %d files", fcount);
544137009Sdelphij		} else if (dcount + fcount > 3) {
545137009Sdelphij			fprintf(stderr, "remove %d files", dcount + fcount);
546137009Sdelphij		} else {
547137009Sdelphij			return(1);
548137009Sdelphij		}
549137009Sdelphij		fprintf(stderr, "? ");
550137009Sdelphij		fflush(stderr);
551137009Sdelphij
552137009Sdelphij		first = ch = getchar();
553137009Sdelphij		while (ch != '\n' && ch != EOF)
554137009Sdelphij			ch = getchar();
555137009Sdelphij		if (ch == EOF)
556137009Sdelphij			break;
557137009Sdelphij	}
558137009Sdelphij	return (first == 'y' || first == 'Y');
559137009Sdelphij}
560137009Sdelphij
5612927Sphk#define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
5621556Srgrimesvoid
56390110Simpcheckdot(char **argv)
5641556Srgrimes{
5651556Srgrimes	char *p, **save, **t;
5661556Srgrimes	int complained;
5671556Srgrimes
5681556Srgrimes	complained = 0;
5691556Srgrimes	for (t = argv; *t;) {
5701556Srgrimes		if ((p = strrchr(*t, '/')) != NULL)
5711556Srgrimes			++p;
5721556Srgrimes		else
5731556Srgrimes			p = *t;
5741556Srgrimes		if (ISDOT(p)) {
5751556Srgrimes			if (!complained++)
5761556Srgrimes				warnx("\".\" and \"..\" may not be removed");
5771556Srgrimes			eval = 1;
57820421Ssteve			for (save = t; (t[0] = t[1]) != NULL; ++t)
57920421Ssteve				continue;
5801556Srgrimes			t = save;
5811556Srgrimes		} else
5821556Srgrimes			++t;
5831556Srgrimes	}
5841556Srgrimes}
5851556Srgrimes
5861556Srgrimesvoid
58790110Simpusage(void)
5881556Srgrimes{
58953819Smharo
59054895Ssheldonh	(void)fprintf(stderr, "%s\n%s\n",
591137009Sdelphij	    "usage: rm [-f | -i] [-dIPRrvW] file ...",
59254895Ssheldonh	    "       unlink file");
59350539Smharo	exit(EX_USAGE);
5941556Srgrimes}
595