Home | History | Annotate | Line # | Download | only in rm
rm.c revision 1.35
      1 /* $NetBSD: rm.c,v 1.35 2003/08/04 22:31:25 jschauma Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 1990, 1993, 1994, 2003
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. All advertising materials mentioning features or use of this software
     16  *    must display the following acknowledgement:
     17  *	This product includes software developed by the University of
     18  *	California, Berkeley and its contributors.
     19  * 4. Neither the name of the University nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  */
     35 
     36 #include <sys/cdefs.h>
     37 #ifndef lint
     38 __COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\n\
     39 	The Regents of the University of California.  All rights reserved.\n");
     40 #endif /* not lint */
     41 
     42 #ifndef lint
     43 #if 0
     44 static char sccsid[] = "@(#)rm.c	8.8 (Berkeley) 4/27/95";
     45 #else
     46 __RCSID("$NetBSD: rm.c,v 1.35 2003/08/04 22:31:25 jschauma Exp $");
     47 #endif
     48 #endif /* not lint */
     49 
     50 #include <sys/param.h>
     51 #include <sys/stat.h>
     52 #include <sys/types.h>
     53 
     54 #include <err.h>
     55 #include <errno.h>
     56 #include <fcntl.h>
     57 #include <fts.h>
     58 #include <grp.h>
     59 #include <locale.h>
     60 #include <pwd.h>
     61 #include <stdio.h>
     62 #include <stdlib.h>
     63 #include <string.h>
     64 #include <unistd.h>
     65 #include <vis.h>
     66 
     67 int dflag, eval, fflag, iflag, Pflag, stdin_ok, stdout_ok, vflag, Wflag;
     68 
     69 int	check(char *, char *, struct stat *);
     70 void	checkdot(char **);
     71 char 	*printescaped(const char *);
     72 void	rm_file(char **);
     73 void	rm_overwrite(char *, struct stat *);
     74 void	rm_tree(char **);
     75 void	usage(void);
     76 int	main(int, char *[]);
     77 
     78 /*
     79  * For the sake of the `-f' flag, check whether an error number indicates the
     80  * failure of an operation due to an non-existent file, either per se (ENOENT)
     81  * or because its filename argument was illegal (ENAMETOOLONG, ENOTDIR).
     82  */
     83 #define NONEXISTENT(x) \
     84     ((x) == ENOENT || (x) == ENAMETOOLONG || (x) == ENOTDIR)
     85 
     86 /*
     87  * rm --
     88  *	This rm is different from historic rm's, but is expected to match
     89  *	POSIX 1003.2 behavior.  The most visible difference is that -f
     90  *	has two specific effects now, ignore non-existent files and force
     91  * 	file removal.
     92  */
     93 int
     94 main(int argc, char *argv[])
     95 {
     96 	int ch, rflag;
     97 
     98 	setprogname(argv[0]);
     99 	(void)setlocale(LC_ALL, "");
    100 
    101 	Pflag = rflag = 0;
    102 	while ((ch = getopt(argc, argv, "dfiPRrvW")) != -1)
    103 		switch (ch) {
    104 		case 'd':
    105 			dflag = 1;
    106 			break;
    107 		case 'f':
    108 			fflag = 1;
    109 			iflag = 0;
    110 			break;
    111 		case 'i':
    112 			fflag = 0;
    113 			iflag = 1;
    114 			break;
    115 		case 'P':
    116 			Pflag = 1;
    117 			break;
    118 		case 'R':
    119 		case 'r':			/* Compatibility. */
    120 			rflag = 1;
    121 			break;
    122 		case 'v':
    123 			vflag = 1;
    124 			break;
    125 		case 'W':
    126 			Wflag = 1;
    127 			break;
    128 		case '?':
    129 		default:
    130 			usage();
    131 		}
    132 	argc -= optind;
    133 	argv += optind;
    134 
    135 	if (argc < 1)
    136 		usage();
    137 
    138 	checkdot(argv);
    139 
    140 	if (*argv) {
    141 		stdin_ok = isatty(STDIN_FILENO);
    142 		stdout_ok = isatty(STDOUT_FILENO);
    143 
    144 		if (rflag)
    145 			rm_tree(argv);
    146 		else
    147 			rm_file(argv);
    148 	}
    149 
    150 	exit(eval);
    151 	/* NOTREACHED */
    152 }
    153 
    154 void
    155 rm_tree(char **argv)
    156 {
    157 	FTS *fts;
    158 	FTSENT *p;
    159 	int flags, needstat, rval;
    160 	char *fn;
    161 
    162 	/*
    163 	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
    164 	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
    165 	 */
    166 	needstat = !fflag && !iflag && stdin_ok;
    167 
    168 	/*
    169 	 * If the -i option is specified, the user can skip on the pre-order
    170 	 * visit.  The fts_number field flags skipped directories.
    171 	 */
    172 #define	SKIPPED	1
    173 
    174 	flags = FTS_PHYSICAL;
    175 	if (!needstat)
    176 		flags |= FTS_NOSTAT;
    177 	if (Wflag)
    178 		flags |= FTS_WHITEOUT;
    179 	if (!(fts = fts_open(argv, flags,
    180 	    (int (*)(const FTSENT **, const FTSENT **))NULL)))
    181 		err(1, NULL);
    182 	while ((p = fts_read(fts)) != NULL) {
    183 
    184 		switch (p->fts_info) {
    185 		case FTS_DNR:
    186 			if (!fflag || p->fts_errno != ENOENT) {
    187 				fn = printescaped(p->fts_path);
    188 				warnx("%s: %s", fn, strerror(p->fts_errno));
    189 				free(fn);
    190 				eval = 1;
    191 			}
    192 			continue;
    193 		case FTS_ERR:
    194 			errx(EXIT_FAILURE, "%s: %s", printescaped(p->fts_path),
    195 					strerror(p->fts_errno));
    196 			/* NOTREACHED */
    197 		case FTS_NS:
    198 			/*
    199 			 * FTS_NS: assume that if can't stat the file, it
    200 			 * can't be unlinked.
    201 			 */
    202 			if (fflag && NONEXISTENT(p->fts_errno))
    203 				continue;
    204 			if (needstat) {
    205 				fn = printescaped(p->fts_path);
    206 				warnx("%s: %s", fn, strerror(p->fts_errno));
    207 				free(fn);
    208 				eval = 1;
    209 				continue;
    210 			}
    211 			break;
    212 		case FTS_D:
    213 			/* Pre-order: give user chance to skip. */
    214 			if (!fflag && !check(p->fts_path, p->fts_accpath,
    215 			    p->fts_statp)) {
    216 				(void)fts_set(fts, p, FTS_SKIP);
    217 				p->fts_number = SKIPPED;
    218 			}
    219 			continue;
    220 		case FTS_DP:
    221 			/* Post-order: see if user skipped. */
    222 			if (p->fts_number == SKIPPED)
    223 				continue;
    224 			break;
    225 		default:
    226 			if (!fflag &&
    227 			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
    228 				continue;
    229 		}
    230 
    231 		rval = 0;
    232 		/*
    233 		 * If we can't read or search the directory, may still be
    234 		 * able to remove it.  Don't print out the un{read,search}able
    235 		 * message unless the remove fails.
    236 		 */
    237 		switch (p->fts_info) {
    238 		case FTS_DP:
    239 		case FTS_DNR:
    240 			rval = rmdir(p->fts_accpath);
    241 			if (rval != 0 && fflag && errno == ENOENT)
    242 				continue;
    243 			break;
    244 
    245 		case FTS_W:
    246 			rval = undelete(p->fts_accpath);
    247 			if (rval != 0 && fflag && errno == ENOENT)
    248 				continue;
    249 			break;
    250 
    251 		default:
    252 			if (Pflag)
    253 				rm_overwrite(p->fts_accpath, NULL);
    254 			rval = unlink(p->fts_accpath);
    255 			if (rval != 0 && fflag && NONEXISTENT(errno))
    256 				continue;
    257 			break;
    258 		}
    259 		if (rval != 0) {
    260 			fn = printescaped(p->fts_path);
    261 			warn("%s", fn);
    262 			free(fn);
    263 			eval = 1;
    264 		} else if (vflag) {
    265 			fn = printescaped(p->fts_path);
    266 			(void)printf("%s\n", fn);
    267 			free(fn);
    268 		}
    269 	}
    270 	if (errno)
    271 		err(1, "fts_read");
    272 }
    273 
    274 void
    275 rm_file(char **argv)
    276 {
    277 	struct stat sb;
    278 	int rval;
    279 	char *f, *fn;
    280 
    281 	/*
    282 	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
    283 	 * to remove a directory is an error, so must always stat the file.
    284 	 */
    285 	while ((f = *argv++) != NULL) {
    286 		/* Assume if can't stat the file, can't unlink it. */
    287 		if (lstat(f, &sb)) {
    288 			if (Wflag) {
    289 				sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
    290 			} else {
    291 				if (!fflag || !NONEXISTENT(errno)) {
    292 					fn = printescaped(f);
    293 					warn("%s", fn);
    294 					free(fn);
    295 					eval = 1;
    296 				}
    297 				continue;
    298 			}
    299 		} else if (Wflag) {
    300 			fn = printescaped(f);
    301 			warnx("%s: %s", fn, strerror(EEXIST));
    302 			free(fn);
    303 			eval = 1;
    304 			continue;
    305 		}
    306 
    307 		if (S_ISDIR(sb.st_mode) && !dflag) {
    308 			fn = printescaped(f);
    309 			warnx("%s: is a directory", fn);
    310 			free(fn);
    311 			eval = 1;
    312 			continue;
    313 		}
    314 		if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
    315 			continue;
    316 		if (S_ISWHT(sb.st_mode))
    317 			rval = undelete(f);
    318 		else if (S_ISDIR(sb.st_mode))
    319 			rval = rmdir(f);
    320 		else {
    321 			if (Pflag)
    322 				rm_overwrite(f, &sb);
    323 			rval = unlink(f);
    324 		}
    325 		if (rval && (!fflag || !NONEXISTENT(errno))) {
    326 			fn = printescaped(f);
    327 			warn("%s", fn);
    328 			free(fn);
    329 			eval = 1;
    330 		}
    331 		if (vflag && rval == 0) {
    332 			fn = printescaped(f);
    333 			(void)printf("%s\n", fn);
    334 			free(fn);
    335 		}
    336 	}
    337 }
    338 
    339 /*
    340  * rm_overwrite --
    341  *	Overwrite the file 3 times with varying bit patterns.
    342  *
    343  * XXX
    344  * This is a cheap way to *really* delete files.  Note that only regular
    345  * files are deleted, directories (and therefore names) will remain.
    346  * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
    347  * System V file system).  In a logging file system, you'll have to have
    348  * kernel support.
    349  */
    350 void
    351 rm_overwrite(char *file, struct stat *sbp)
    352 {
    353 	struct stat sb;
    354 	off_t len;
    355 	int fd, wlen;
    356 	char buf[8 * 1024];
    357 	char *fn;
    358 
    359 	fd = -1;
    360 	if (sbp == NULL) {
    361 		if (lstat(file, &sb))
    362 			goto err;
    363 		sbp = &sb;
    364 	}
    365 	if (!S_ISREG(sbp->st_mode))
    366 		return;
    367 	if ((fd = open(file, O_WRONLY, 0)) == -1)
    368 		goto err;
    369 
    370 #define	PASS(byte) do {							\
    371 	memset(buf, byte, sizeof(buf));					\
    372 	for (len = sbp->st_size; len > 0; len -= wlen) {		\
    373 		wlen = len < sizeof(buf) ? len : sizeof(buf);		\
    374 		if (write(fd, buf, wlen) != wlen)			\
    375 			goto err;					\
    376 	}								\
    377 } while (/* CONSTCOND */ 0)
    378 	PASS(0xff);
    379 	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
    380 		goto err;
    381 	PASS(0x00);
    382 	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
    383 		goto err;
    384 	PASS(0xff);
    385 	if (!fsync(fd) && !close(fd))
    386 		return;
    387 
    388 err:	eval = 1;
    389 	fn = printescaped(file);
    390 	warn("%s", fn);
    391 	free(fn);
    392 }
    393 
    394 int
    395 check(char *path, char *name, struct stat *sp)
    396 {
    397 	int ch, first;
    398 	char modep[15];
    399 	char *fn;
    400 
    401 	/* Check -i first. */
    402 	if (iflag) {
    403 		fn = printescaped(path);
    404 		(void)fprintf(stderr, "remove '%s'? ", fn);
    405 		free(fn);
    406 	} else {
    407 		/*
    408 		 * If it's not a symbolic link and it's unwritable and we're
    409 		 * talking to a terminal, ask.  Symbolic links are excluded
    410 		 * because their permissions are meaningless.  Check stdin_ok
    411 		 * first because we may not have stat'ed the file.
    412 		 */
    413 		if (!stdin_ok || S_ISLNK(sp->st_mode) ||
    414 		    !(access(name, W_OK) && (errno != ETXTBSY)))
    415 			return (1);
    416 		strmode(sp->st_mode, modep);
    417 		fn =  printescaped(path);
    418 		(void)fprintf(stderr, "override %s%s%s/%s for '%s'? ",
    419 		    modep + 1, modep[9] == ' ' ? "" : " ",
    420 		    user_from_uid(sp->st_uid, 0),
    421 		    group_from_gid(sp->st_gid, 0), fn);
    422 		free(fn);
    423 	}
    424 	(void)fflush(stderr);
    425 
    426 	first = ch = getchar();
    427 	while (ch != '\n' && ch != EOF)
    428 		ch = getchar();
    429 	return (first == 'y' || first == 'Y');
    430 }
    431 
    432 /*
    433  * POSIX.2 requires that if "." or ".." are specified as the basename
    434  * portion of an operand, a diagnostic message be written to standard
    435  * error and nothing more be done with such operands.
    436  *
    437  * Since POSIX.2 defines basename as the final portion of a path after
    438  * trailing slashes have been removed, we'll remove them here.
    439  */
    440 #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
    441 void
    442 checkdot(char **argv)
    443 {
    444 	char *p, **save, **t;
    445 	int complained;
    446 
    447 	complained = 0;
    448 	for (t = argv; *t;) {
    449 		/* strip trailing slashes */
    450 		p = strrchr(*t, '\0');
    451 		while (--p > *t && *p == '/')
    452 			*p = '\0';
    453 
    454 		/* extract basename */
    455 		if ((p = strrchr(*t, '/')) != NULL)
    456 			++p;
    457 		else
    458 			p = *t;
    459 
    460 		if (ISDOT(p)) {
    461 			if (!complained++)
    462 				warnx("\".\" and \"..\" may not be removed");
    463 			eval = 1;
    464 			for (save = t; (t[0] = t[1]) != NULL; ++t)
    465 				continue;
    466 			t = save;
    467 		} else
    468 			++t;
    469 	}
    470 }
    471 
    472 char *
    473 printescaped(const char *src)
    474 {
    475 	size_t len;
    476 	char *retval;
    477 
    478 	len = strlen(src);
    479 	if (len != 0 && SIZE_T_MAX/len <= 4) {
    480 		errx(EXIT_FAILURE, "%s: name too long", src);
    481 		/* NOTREACHED */
    482 	}
    483 
    484 	retval = (char *)malloc(4*len+1);
    485 	if (retval != NULL) {
    486 		if (stdout_ok)
    487 			(void)strvis(retval, src, VIS_NL | VIS_CSTYLE);
    488 		else
    489 			(void)strcpy(retval, src);
    490 		return retval;
    491 	} else
    492 		errx(EXIT_FAILURE, "out of memory!");
    493 		/* NOTREACHED */
    494 }
    495 
    496 void
    497 usage(void)
    498 {
    499 
    500 	(void)fprintf(stderr, "usage: %s [-f|-i] [-dPRrvW] file ...\n",
    501 	    getprogname());
    502 	exit(1);
    503 	/* NOTREACHED */
    504 }
    505