PageRenderTime 46ms CodeModel.GetById 17ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/bin/rm/rm.c

https://bitbucket.org/freebsd/freebsd-head/
C | 637 lines | 504 code | 45 blank | 88 comment | 216 complexity | be93fe1ebced176421533923f1c29e94 MD5 | raw file
  1/*-
  2 * Copyright (c) 1990, 1993, 1994
  3 *	The Regents of the University of California.  All rights reserved.
  4 *
  5 * Redistribution and use in source and binary forms, with or without
  6 * modification, are permitted provided that the following conditions
  7 * are met:
  8 * 1. Redistributions of source code must retain the above copyright
  9 *    notice, this list of conditions and the following disclaimer.
 10 * 2. Redistributions in binary form must reproduce the above copyright
 11 *    notice, this list of conditions and the following disclaimer in the
 12 *    documentation and/or other materials provided with the distribution.
 13 * 4. Neither the name of the University nor the names of its contributors
 14 *    may be used to endorse or promote products derived from this software
 15 *    without specific prior written permission.
 16 *
 17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 27 * SUCH DAMAGE.
 28 */
 29
 30#if 0
 31#ifndef lint
 32static const char copyright[] =
 33"@(#) Copyright (c) 1990, 1993, 1994\n\
 34	The Regents of the University of California.  All rights reserved.\n";
 35#endif /* not lint */
 36
 37#ifndef lint
 38static char sccsid[] = "@(#)rm.c	8.5 (Berkeley) 4/18/94";
 39#endif /* not lint */
 40#endif
 41#include <sys/cdefs.h>
 42__FBSDID("$FreeBSD$");
 43
 44#include <sys/stat.h>
 45#include <sys/param.h>
 46#include <sys/mount.h>
 47
 48#include <err.h>
 49#include <errno.h>
 50#include <fcntl.h>
 51#include <fts.h>
 52#include <grp.h>
 53#include <pwd.h>
 54#include <stdint.h>
 55#include <stdio.h>
 56#include <stdlib.h>
 57#include <string.h>
 58#include <sysexits.h>
 59#include <unistd.h>
 60
 61static int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
 62static int rflag, Iflag;
 63static uid_t uid;
 64static volatile sig_atomic_t info;
 65
 66int	check(char *, char *, struct stat *);
 67int	check2(char **);
 68void	checkdot(char **);
 69void	checkslash(char **);
 70void	rm_file(char **);
 71int	rm_overwrite(char *, struct stat *);
 72void	rm_tree(char **);
 73static void siginfo(int __unused);
 74void	usage(void);
 75
 76/*
 77 * rm --
 78 *	This rm is different from historic rm's, but is expected to match
 79 *	POSIX 1003.2 behavior.	The most visible difference is that -f
 80 *	has two specific effects now, ignore non-existent files and force
 81 *	file removal.
 82 */
 83int
 84main(int argc, char *argv[])
 85{
 86	int ch;
 87	char *p;
 88
 89	/*
 90	 * Test for the special case where the utility is called as
 91	 * "unlink", for which the functionality provided is greatly
 92	 * simplified.
 93	 */
 94	if ((p = strrchr(argv[0], '/')) == NULL)
 95		p = argv[0];
 96	else
 97		++p;
 98	if (strcmp(p, "unlink") == 0) {
 99		while (getopt(argc, argv, "") != -1)
100			usage();
101		argc -= optind;
102		argv += optind;
103		if (argc != 1)
104			usage();
105		rm_file(&argv[0]);
106		exit(eval);
107	}
108
109	Pflag = rflag = 0;
110	while ((ch = getopt(argc, argv, "dfiIPRrvW")) != -1)
111		switch(ch) {
112		case 'd':
113			dflag = 1;
114			break;
115		case 'f':
116			fflag = 1;
117			iflag = 0;
118			break;
119		case 'i':
120			fflag = 0;
121			iflag = 1;
122			break;
123		case 'I':
124			Iflag = 1;
125			break;
126		case 'P':
127			Pflag = 1;
128			break;
129		case 'R':
130		case 'r':			/* Compatibility. */
131			rflag = 1;
132			break;
133		case 'v':
134			vflag = 1;
135			break;
136		case 'W':
137			Wflag = 1;
138			break;
139		default:
140			usage();
141		}
142	argc -= optind;
143	argv += optind;
144
145	if (argc < 1) {
146		if (fflag)
147			return (0);
148		usage();
149	}
150
151	checkdot(argv);
152	if (getenv("POSIXLY_CORRECT") == NULL)
153		checkslash(argv);
154	uid = geteuid();
155
156	(void)signal(SIGINFO, siginfo);
157	if (*argv) {
158		stdin_ok = isatty(STDIN_FILENO);
159
160		if (Iflag) {
161			if (check2(argv) == 0)
162				exit (1);
163		}
164		if (rflag)
165			rm_tree(argv);
166		else
167			rm_file(argv);
168	}
169
170	exit (eval);
171}
172
173void
174rm_tree(char **argv)
175{
176	FTS *fts;
177	FTSENT *p;
178	int needstat;
179	int flags;
180	int rval;
181
182	/*
183	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
184	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
185	 */
186	needstat = !uid || (!fflag && !iflag && stdin_ok);
187
188	/*
189	 * If the -i option is specified, the user can skip on the pre-order
190	 * visit.  The fts_number field flags skipped directories.
191	 */
192#define	SKIPPED	1
193
194	flags = FTS_PHYSICAL;
195	if (!needstat)
196		flags |= FTS_NOSTAT;
197	if (Wflag)
198		flags |= FTS_WHITEOUT;
199	if (!(fts = fts_open(argv, flags, NULL))) {
200		if (fflag && errno == ENOENT)
201			return;
202		err(1, "fts_open");
203	}
204	while ((p = fts_read(fts)) != NULL) {
205		switch (p->fts_info) {
206		case FTS_DNR:
207			if (!fflag || p->fts_errno != ENOENT) {
208				warnx("%s: %s",
209				    p->fts_path, strerror(p->fts_errno));
210				eval = 1;
211			}
212			continue;
213		case FTS_ERR:
214			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
215		case FTS_NS:
216			/*
217			 * Assume that since fts_read() couldn't stat the
218			 * file, it can't be unlinked.
219			 */
220			if (!needstat)
221				break;
222			if (!fflag || p->fts_errno != ENOENT) {
223				warnx("%s: %s",
224				    p->fts_path, strerror(p->fts_errno));
225				eval = 1;
226			}
227			continue;
228		case FTS_D:
229			/* Pre-order: give user chance to skip. */
230			if (!fflag && !check(p->fts_path, p->fts_accpath,
231			    p->fts_statp)) {
232				(void)fts_set(fts, p, FTS_SKIP);
233				p->fts_number = SKIPPED;
234			}
235			else if (!uid &&
236				 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
237				 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
238				 lchflags(p->fts_accpath,
239					 p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
240				goto err;
241			continue;
242		case FTS_DP:
243			/* Post-order: see if user skipped. */
244			if (p->fts_number == SKIPPED)
245				continue;
246			break;
247		default:
248			if (!fflag &&
249			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
250				continue;
251		}
252
253		rval = 0;
254		if (!uid &&
255		    (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
256		    !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
257			rval = lchflags(p->fts_accpath,
258				       p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
259		if (rval == 0) {
260			/*
261			 * If we can't read or search the directory, may still be
262			 * able to remove it.  Don't print out the un{read,search}able
263			 * message unless the remove fails.
264			 */
265			switch (p->fts_info) {
266			case FTS_DP:
267			case FTS_DNR:
268				rval = rmdir(p->fts_accpath);
269				if (rval == 0 || (fflag && errno == ENOENT)) {
270					if (rval == 0 && vflag)
271						(void)printf("%s\n",
272						    p->fts_path);
273					if (rval == 0 && info) {
274						info = 0;
275						(void)printf("%s\n",
276						    p->fts_path);
277					}
278					continue;
279				}
280				break;
281
282			case FTS_W:
283				rval = undelete(p->fts_accpath);
284				if (rval == 0 && (fflag && errno == ENOENT)) {
285					if (vflag)
286						(void)printf("%s\n",
287						    p->fts_path);
288					if (info) {
289						info = 0;
290						(void)printf("%s\n",
291						    p->fts_path);
292					}
293					continue;
294				}
295				break;
296
297			case FTS_NS:
298				/*
299				 * Assume that since fts_read() couldn't stat
300				 * the file, it can't be unlinked.
301				 */
302				if (fflag)
303					continue;
304				/* FALLTHROUGH */
305
306			case FTS_F:
307			case FTS_NSOK:
308				if (Pflag)
309					if (!rm_overwrite(p->fts_accpath, p->fts_info ==
310					    FTS_NSOK ? NULL : p->fts_statp))
311						continue;
312				/* FALLTHROUGH */
313
314			default:
315				rval = unlink(p->fts_accpath);
316				if (rval == 0 || (fflag && errno == ENOENT)) {
317					if (rval == 0 && vflag)
318						(void)printf("%s\n",
319						    p->fts_path);
320					if (rval == 0 && info) {
321						info = 0;
322						(void)printf("%s\n",
323						    p->fts_path);
324					}
325					continue;
326				}
327			}
328		}
329err:
330		warn("%s", p->fts_path);
331		eval = 1;
332	}
333	if (errno)
334		err(1, "fts_read");
335	fts_close(fts);
336}
337
338void
339rm_file(char **argv)
340{
341	struct stat sb;
342	int rval;
343	char *f;
344
345	/*
346	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
347	 * to remove a directory is an error, so must always stat the file.
348	 */
349	while ((f = *argv++) != NULL) {
350		/* Assume if can't stat the file, can't unlink it. */
351		if (lstat(f, &sb)) {
352			if (Wflag) {
353				sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
354			} else {
355				if (!fflag || errno != ENOENT) {
356					warn("%s", f);
357					eval = 1;
358				}
359				continue;
360			}
361		} else if (Wflag) {
362			warnx("%s: %s", f, strerror(EEXIST));
363			eval = 1;
364			continue;
365		}
366
367		if (S_ISDIR(sb.st_mode) && !dflag) {
368			warnx("%s: is a directory", f);
369			eval = 1;
370			continue;
371		}
372		if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
373			continue;
374		rval = 0;
375		if (!uid && !S_ISWHT(sb.st_mode) &&
376		    (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
377		    !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
378			rval = lchflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
379		if (rval == 0) {
380			if (S_ISWHT(sb.st_mode))
381				rval = undelete(f);
382			else if (S_ISDIR(sb.st_mode))
383				rval = rmdir(f);
384			else {
385				if (Pflag)
386					if (!rm_overwrite(f, &sb))
387						continue;
388				rval = unlink(f);
389			}
390		}
391		if (rval && (!fflag || errno != ENOENT)) {
392			warn("%s", f);
393			eval = 1;
394		}
395		if (vflag && rval == 0)
396			(void)printf("%s\n", f);
397		if (info && rval == 0) {
398			info = 0;
399			(void)printf("%s\n", f);
400		}
401	}
402}
403
404/*
405 * rm_overwrite --
406 *	Overwrite the file 3 times with varying bit patterns.
407 *
408 * XXX
409 * This is a cheap way to *really* delete files.  Note that only regular
410 * files are deleted, directories (and therefore names) will remain.
411 * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
412 * System V file system).  In a logging or COW file system, you'll have to
413 * have kernel support.
414 */
415int
416rm_overwrite(char *file, struct stat *sbp)
417{
418	struct stat sb, sb2;
419	struct statfs fsb;
420	off_t len;
421	int bsize, fd, wlen;
422	char *buf = NULL;
423
424	fd = -1;
425	if (sbp == NULL) {
426		if (lstat(file, &sb))
427			goto err;
428		sbp = &sb;
429	}
430	if (!S_ISREG(sbp->st_mode))
431		return (1);
432	if (sbp->st_nlink > 1 && !fflag) {
433		warnx("%s (inode %ju): not overwritten due to multiple links",
434		    file, (uintmax_t)sbp->st_ino);
435		return (0);
436	}
437	if ((fd = open(file, O_WRONLY|O_NONBLOCK|O_NOFOLLOW, 0)) == -1)
438		goto err;
439	if (fstat(fd, &sb2))
440		goto err;
441	if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino ||
442	    !S_ISREG(sb2.st_mode)) {
443		errno = EPERM;
444		goto err;
445	}
446	if (fstatfs(fd, &fsb) == -1)
447		goto err;
448	bsize = MAX(fsb.f_iosize, 1024);
449	if ((buf = malloc(bsize)) == NULL)
450		err(1, "%s: malloc", file);
451
452#define	PASS(byte) {							\
453	memset(buf, byte, bsize);					\
454	for (len = sbp->st_size; len > 0; len -= wlen) {		\
455		wlen = len < bsize ? len : bsize;			\
456		if (write(fd, buf, wlen) != wlen)			\
457			goto err;					\
458	}								\
459}
460	PASS(0xff);
461	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
462		goto err;
463	PASS(0x00);
464	if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
465		goto err;
466	PASS(0xff);
467	if (!fsync(fd) && !close(fd)) {
468		free(buf);
469		return (1);
470	}
471
472err:	eval = 1;
473	if (buf)
474		free(buf);
475	if (fd != -1)
476		close(fd);
477	warn("%s", file);
478	return (0);
479}
480
481
482int
483check(char *path, char *name, struct stat *sp)
484{
485	int ch, first;
486	char modep[15], *flagsp;
487
488	/* Check -i first. */
489	if (iflag)
490		(void)fprintf(stderr, "remove %s? ", path);
491	else {
492		/*
493		 * If it's not a symbolic link and it's unwritable and we're
494		 * talking to a terminal, ask.	Symbolic links are excluded
495		 * because their permissions are meaningless.  Check stdin_ok
496		 * first because we may not have stat'ed the file.
497		 */
498		if (!stdin_ok || S_ISLNK(sp->st_mode) ||
499		    (!access(name, W_OK) &&
500		    !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
501		    (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
502			return (1);
503		strmode(sp->st_mode, modep);
504		if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
505			err(1, "fflagstostr");
506		if (Pflag)
507			errx(1,
508			    "%s: -P was specified, but file is not writable",
509			    path);
510		(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
511		    modep + 1, modep[9] == ' ' ? "" : " ",
512		    user_from_uid(sp->st_uid, 0),
513		    group_from_gid(sp->st_gid, 0),
514		    *flagsp ? flagsp : "", *flagsp ? " " : "",
515		    path);
516		free(flagsp);
517	}
518	(void)fflush(stderr);
519
520	first = ch = getchar();
521	while (ch != '\n' && ch != EOF)
522		ch = getchar();
523	return (first == 'y' || first == 'Y');
524}
525
526#define ISSLASH(a)	((a)[0] == '/' && (a)[1] == '\0')
527void
528checkslash(char **argv)
529{
530	char **t, **u;
531	int complained;
532
533	complained = 0;
534	for (t = argv; *t;) {
535		if (ISSLASH(*t)) {
536			if (!complained++)
537				warnx("\"/\" may not be removed");
538			eval = 1;
539			for (u = t; u[0] != NULL; ++u)
540				u[0] = u[1];
541		} else {
542			++t;
543		}
544	}
545}
546
547int
548check2(char **argv)
549{
550	struct stat st;
551	int first;
552	int ch;
553	int fcount = 0;
554	int dcount = 0;
555	int i;
556	const char *dname = NULL;
557
558	for (i = 0; argv[i]; ++i) {
559		if (lstat(argv[i], &st) == 0) {
560			if (S_ISDIR(st.st_mode)) {
561				++dcount;
562				dname = argv[i];    /* only used if 1 dir */
563			} else {
564				++fcount;
565			}
566		}
567	}
568	first = 0;
569	while (first != 'n' && first != 'N' && first != 'y' && first != 'Y') {
570		if (dcount && rflag) {
571			fprintf(stderr, "recursively remove");
572			if (dcount == 1)
573				fprintf(stderr, " %s", dname);
574			else
575				fprintf(stderr, " %d dirs", dcount);
576			if (fcount == 1)
577				fprintf(stderr, " and 1 file");
578			else if (fcount > 1)
579				fprintf(stderr, " and %d files", fcount);
580		} else if (dcount + fcount > 3) {
581			fprintf(stderr, "remove %d files", dcount + fcount);
582		} else {
583			return(1);
584		}
585		fprintf(stderr, "? ");
586		fflush(stderr);
587
588		first = ch = getchar();
589		while (ch != '\n' && ch != EOF)
590			ch = getchar();
591		if (ch == EOF)
592			break;
593	}
594	return (first == 'y' || first == 'Y');
595}
596
597#define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
598void
599checkdot(char **argv)
600{
601	char *p, **save, **t;
602	int complained;
603
604	complained = 0;
605	for (t = argv; *t;) {
606		if ((p = strrchr(*t, '/')) != NULL)
607			++p;
608		else
609			p = *t;
610		if (ISDOT(p)) {
611			if (!complained++)
612				warnx("\".\" and \"..\" may not be removed");
613			eval = 1;
614			for (save = t; (t[0] = t[1]) != NULL; ++t)
615				continue;
616			t = save;
617		} else
618			++t;
619	}
620}
621
622void
623usage(void)
624{
625
626	(void)fprintf(stderr, "%s\n%s\n",
627	    "usage: rm [-f | -i] [-dIPRrvW] file ...",
628	    "       unlink file");
629	exit(EX_USAGE);
630}
631
632static void
633siginfo(int sig __unused)
634{
635
636	info = 1;
637}