rm.c revision 150729
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 150729 2005-09-29 20:40:29Z dougb $");
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");
3101556Srgrimes}
3111556Srgrimes
3121556Srgrimesvoid
31390110Simprm_file(char **argv)
3141556Srgrimes{
3151556Srgrimes	struct stat sb;
31620421Ssteve	int rval;
3171556Srgrimes	char *f;
3181556Srgrimes
3191556Srgrimes	/*
3201556Srgrimes	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
3211556Srgrimes	 * to remove a directory is an error, so must always stat the file.
3221556Srgrimes	 */
3231556Srgrimes	while ((f = *argv++) != NULL) {
3241556Srgrimes		/* Assume if can't stat the file, can't unlink it. */
3251556Srgrimes		if (lstat(f, &sb)) {
32620421Ssteve			if (Wflag) {
32720421Ssteve				sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
32820421Ssteve			} else {
32920421Ssteve				if (!fflag || errno != ENOENT) {
33020421Ssteve					warn("%s", f);
33120421Ssteve					eval = 1;
33220421Ssteve				}
33320421Ssteve				continue;
3341556Srgrimes			}
33520421Ssteve		} else if (Wflag) {
33620421Ssteve			warnx("%s: %s", f, strerror(EEXIST));
33720421Ssteve			eval = 1;
3381556Srgrimes			continue;
3391556Srgrimes		}
34020421Ssteve
34120421Ssteve		if (S_ISDIR(sb.st_mode) && !dflag) {
3421556Srgrimes			warnx("%s: is a directory", f);
3431556Srgrimes			eval = 1;
3441556Srgrimes			continue;
3451556Srgrimes		}
34620421Ssteve		if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
3471556Srgrimes			continue;
3487798Sache		rval = 0;
3497798Sache		if (!uid &&
3507798Sache		    (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
3517798Sache		    !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
3527798Sache			rval = chflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
35353819Smharo		if (rval == 0) {
35420421Ssteve			if (S_ISWHT(sb.st_mode))
35520421Ssteve				rval = undelete(f);
35620421Ssteve			else if (S_ISDIR(sb.st_mode))
3577798Sache				rval = rmdir(f);
3587798Sache			else {
3597798Sache				if (Pflag)
360122409Sguido					if (!rm_overwrite(f, &sb))
361122409Sguido						continue;
3627798Sache				rval = unlink(f);
3637798Sache			}
3641556Srgrimes		}
3651556Srgrimes		if (rval && (!fflag || errno != ENOENT)) {
3661556Srgrimes			warn("%s", f);
3671556Srgrimes			eval = 1;
3681556Srgrimes		}
36953819Smharo		if (vflag && rval == 0)
37050539Smharo			(void)printf("%s\n", f);
3711556Srgrimes	}
3721556Srgrimes}
3731556Srgrimes
3741556Srgrimes/*
3751556Srgrimes * rm_overwrite --
3761556Srgrimes *	Overwrite the file 3 times with varying bit patterns.
3771556Srgrimes *
3781556Srgrimes * XXX
3791556Srgrimes * This is a cheap way to *really* delete files.  Note that only regular
3801556Srgrimes * files are deleted, directories (and therefore names) will remain.
381102230Strhodes * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
382102230Strhodes * System V file system).  In a logging file system, you'll have to have
3831556Srgrimes * kernel support.
3841556Srgrimes */
385122409Sguidoint
38690110Simprm_overwrite(char *file, struct stat *sbp)
3871556Srgrimes{
3881556Srgrimes	struct stat sb;
38947584Skris	struct statfs fsb;
3901556Srgrimes	off_t len;
39147584Skris	int bsize, fd, wlen;
39247584Skris	char *buf = NULL;
3931556Srgrimes
3941556Srgrimes	fd = -1;
3951556Srgrimes	if (sbp == NULL) {
3961556Srgrimes		if (lstat(file, &sb))
3971556Srgrimes			goto err;
3981556Srgrimes		sbp = &sb;
3991556Srgrimes	}
4001556Srgrimes	if (!S_ISREG(sbp->st_mode))
401122409Sguido		return (1);
4021556Srgrimes	if ((fd = open(file, O_WRONLY, 0)) == -1)
4031556Srgrimes		goto err;
40447584Skris	if (fstatfs(fd, &fsb) == -1)
40547584Skris		goto err;
40647584Skris	bsize = MAX(fsb.f_iosize, 1024);
40747584Skris	if ((buf = malloc(bsize)) == NULL)
408122304Sbde		err(1, "%s: malloc", file);
4091556Srgrimes
4101556Srgrimes#define	PASS(byte) {							\
41147584Skris	memset(buf, byte, bsize);					\
4121556Srgrimes	for (len = sbp->st_size; len > 0; len -= wlen) {		\
41347584Skris		wlen = len < bsize ? len : bsize;			\
4141556Srgrimes		if (write(fd, buf, wlen) != wlen)			\
4151556Srgrimes			goto err;					\
4161556Srgrimes	}								\
4171556Srgrimes}
4181556Srgrimes	PASS(0xff);
4191556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
4201556Srgrimes		goto err;
4211556Srgrimes	PASS(0x00);
4221556Srgrimes	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
4231556Srgrimes		goto err;
4241556Srgrimes	PASS(0xff);
42547584Skris	if (!fsync(fd) && !close(fd)) {
42647584Skris		free(buf);
427122409Sguido		return (1);
42847584Skris	}
4291556Srgrimes
4301556Srgrimeserr:	eval = 1;
43147584Skris	if (buf)
43247584Skris		free(buf);
433122304Sbde	if (fd != -1)
434122304Sbde		close(fd);
4351556Srgrimes	warn("%s", file);
436122409Sguido	return (0);
4371556Srgrimes}
4381556Srgrimes
4391556Srgrimes
4401556Srgrimesint
44190110Simpcheck(char *path, char *name, struct stat *sp)
4421556Srgrimes{
4431556Srgrimes	int ch, first;
44461749Sjoe	char modep[15], *flagsp;
4451556Srgrimes
4461556Srgrimes	/* Check -i first. */
4471556Srgrimes	if (iflag)
4481556Srgrimes		(void)fprintf(stderr, "remove %s? ", path);
4491556Srgrimes	else {
4501556Srgrimes		/*
4511556Srgrimes		 * If it's not a symbolic link and it's unwritable and we're
452136112Sdes		 * talking to a terminal, ask.	Symbolic links are excluded
4531556Srgrimes		 * because their permissions are meaningless.  Check stdin_ok
4541556Srgrimes		 * first because we may not have stat'ed the file.
4551556Srgrimes		 */
456150729Sdougb		if (!stdin_ok || S_ISLNK(sp->st_mode) ||
45720421Ssteve		    (!access(name, W_OK) &&
4587798Sache		    !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
45920421Ssteve		    (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
4601556Srgrimes			return (1);
4611556Srgrimes		strmode(sp->st_mode, modep);
46261749Sjoe		if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
46399744Sdillon			err(1, "fflagstostr");
464150729Sdougb		if (Pflag)
465150729Sdougb			errx(1,
466150729Sdougb			    "%s: -P was specified, but file is not writable",
467150729Sdougb			    path);
46861749Sjoe		(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
4691556Srgrimes		    modep + 1, modep[9] == ' ' ? "" : " ",
4701556Srgrimes		    user_from_uid(sp->st_uid, 0),
4717798Sache		    group_from_gid(sp->st_gid, 0),
472136112Sdes		    *flagsp ? flagsp : "", *flagsp ? " " : "",
4737798Sache		    path);
47461749Sjoe		free(flagsp);
4751556Srgrimes	}
4761556Srgrimes	(void)fflush(stderr);
4771556Srgrimes
4781556Srgrimes	first = ch = getchar();
4791556Srgrimes	while (ch != '\n' && ch != EOF)
4801556Srgrimes		ch = getchar();
48114409Swosch	return (first == 'y' || first == 'Y');
4821556Srgrimes}
4831556Srgrimes
484136113Sdes#define ISSLASH(a)	((a)[0] == '/' && (a)[1] == '\0')
485136113Sdesvoid
486136113Sdescheckslash(char **argv)
487136113Sdes{
488136113Sdes	char **t, **u;
489136113Sdes	int complained;
490136113Sdes
491136113Sdes	complained = 0;
492136113Sdes	for (t = argv; *t;) {
493136113Sdes		if (ISSLASH(*t)) {
494136113Sdes			if (!complained++)
495136113Sdes				warnx("\"/\" may not be removed");
496136113Sdes			eval = 1;
497136113Sdes			for (u = t; u[0] != NULL; ++u)
498136113Sdes				u[0] = u[1];
499136113Sdes		} else {
500136113Sdes			++t;
501136113Sdes		}
502136113Sdes	}
503136113Sdes}
504136113Sdes
505137009Sdelphijint
506137009Sdelphijcheck2(char **argv)
507137009Sdelphij{
508137009Sdelphij	struct stat st;
509137009Sdelphij	int first;
510137009Sdelphij	int ch;
511137009Sdelphij	int fcount = 0;
512137009Sdelphij	int dcount = 0;
513137009Sdelphij	int i;
514137009Sdelphij	const char *dname = NULL;
515137009Sdelphij
516137009Sdelphij	for (i = 0; argv[i]; ++i) {
517137009Sdelphij		if (lstat(argv[i], &st) == 0) {
518137009Sdelphij			if (S_ISDIR(st.st_mode)) {
519137009Sdelphij				++dcount;
520137009Sdelphij				dname = argv[i];    /* only used if 1 dir */
521137009Sdelphij			} else {
522137009Sdelphij				++fcount;
523137009Sdelphij			}
524137009Sdelphij		}
525137009Sdelphij	}
526137009Sdelphij	first = 0;
527137009Sdelphij	while (first != 'n' && first != 'N' && first != 'y' && first != 'Y') {
528137009Sdelphij		if (dcount && rflag) {
529137009Sdelphij			fprintf(stderr, "recursively remove");
530137009Sdelphij			if (dcount == 1)
531137009Sdelphij				fprintf(stderr, " %s", dname);
532137009Sdelphij			else
533137009Sdelphij				fprintf(stderr, " %d dirs", dcount);
534137009Sdelphij			if (fcount == 1)
535137009Sdelphij				fprintf(stderr, " and 1 file");
536137009Sdelphij			else if (fcount > 1)
537137009Sdelphij				fprintf(stderr, " and %d files", fcount);
538137009Sdelphij		} else if (dcount + fcount > 3) {
539137009Sdelphij			fprintf(stderr, "remove %d files", dcount + fcount);
540137009Sdelphij		} else {
541137009Sdelphij			return(1);
542137009Sdelphij		}
543137009Sdelphij		fprintf(stderr, "? ");
544137009Sdelphij		fflush(stderr);
545137009Sdelphij
546137009Sdelphij		first = ch = getchar();
547137009Sdelphij		while (ch != '\n' && ch != EOF)
548137009Sdelphij			ch = getchar();
549137009Sdelphij		if (ch == EOF)
550137009Sdelphij			break;
551137009Sdelphij	}
552137009Sdelphij	return (first == 'y' || first == 'Y');
553137009Sdelphij}
554137009Sdelphij
5552927Sphk#define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
5561556Srgrimesvoid
55790110Simpcheckdot(char **argv)
5581556Srgrimes{
5591556Srgrimes	char *p, **save, **t;
5601556Srgrimes	int complained;
5611556Srgrimes
5621556Srgrimes	complained = 0;
5631556Srgrimes	for (t = argv; *t;) {
5641556Srgrimes		if ((p = strrchr(*t, '/')) != NULL)
5651556Srgrimes			++p;
5661556Srgrimes		else
5671556Srgrimes			p = *t;
5681556Srgrimes		if (ISDOT(p)) {
5691556Srgrimes			if (!complained++)
5701556Srgrimes				warnx("\".\" and \"..\" may not be removed");
5711556Srgrimes			eval = 1;
57220421Ssteve			for (save = t; (t[0] = t[1]) != NULL; ++t)
57320421Ssteve				continue;
5741556Srgrimes			t = save;
5751556Srgrimes		} else
5761556Srgrimes			++t;
5771556Srgrimes	}
5781556Srgrimes}
5791556Srgrimes
5801556Srgrimesvoid
58190110Simpusage(void)
5821556Srgrimes{
58353819Smharo
58454895Ssheldonh	(void)fprintf(stderr, "%s\n%s\n",
585137009Sdelphij	    "usage: rm [-f | -i] [-dIPRrvW] file ...",
58654895Ssheldonh	    "       unlink file");
58750539Smharo	exit(EX_USAGE);
5881556Srgrimes}
589