rm.c revision 1.2
1/*	$OpenBSD: rm.c,v 1.2 2015/11/17 18:34:00 tedu Exp $	*/
2/*	$NetBSD: rm.c,v 1.19 1995/09/07 06:48:50 jtc Exp $	*/
3
4/*-
5 * Copyright (c) 1990, 1993, 1994
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <sys/mount.h>
36
37#include <locale.h>
38#include <err.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <fts.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46#include <limits.h>
47#include <pwd.h>
48#include <grp.h>
49
50#define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
51
52extern char *__progname;
53
54static int dflag, eval, fflag, iflag, Pflag, stdin_ok;
55
56static int	check(char *, char *, struct stat *);
57static void	checkdot(char **);
58static void	rm_file(char **);
59static int	rm_overwrite(char *, struct stat *);
60static int	pass(int, off_t, char *, size_t);
61static void	rm_tree(char **);
62
63static void __dead
64usage(void)
65{
66	(void)fprintf(stderr, "usage: %s [-dfiPRr] file ...\n", __progname);
67	exit(1);
68}
69
70/*
71 * rm --
72 *	This rm is different from historic rm's, but is expected to match
73 *	POSIX 1003.2 behavior.  The most visible difference is that -f
74 *	has two specific effects now, ignore non-existent files and force
75 * 	file removal.
76 */
77int
78rmmain(int argc, char *argv[])
79{
80	int ch, rflag;
81
82	Pflag = rflag = 0;
83
84	fflag = 1;
85	rflag = 1;
86
87	if (Pflag) {
88		if (pledge("stdio rpath wpath cpath", NULL) == -1)
89			err(1, "pledge");
90	} else {
91		if (pledge("stdio rpath cpath", NULL) == -1)
92			err(1, "pledge");
93	}
94
95	if (argc < 1 && fflag == 0)
96		usage();
97
98	checkdot(argv);
99
100	if (*argv) {
101		stdin_ok = isatty(STDIN_FILENO);
102
103		if (rflag)
104			rm_tree(argv);
105		else
106			rm_file(argv);
107	}
108
109	return (eval);
110}
111
112static void
113rm_tree(char **argv)
114{
115	FTS *fts;
116	FTSENT *p;
117	int needstat;
118	int flags;
119
120	/*
121	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
122	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
123	 */
124	needstat = !fflag && !iflag && stdin_ok;
125
126	/*
127	 * If the -i option is specified, the user can skip on the pre-order
128	 * visit.  The fts_number field flags skipped directories.
129	 */
130#define	SKIPPED	1
131
132	flags = FTS_PHYSICAL;
133	if (!needstat)
134		flags |= FTS_NOSTAT;
135	if (!(fts = fts_open(argv, flags, NULL)))
136		err(1, NULL);
137	while ((p = fts_read(fts)) != NULL) {
138		switch (p->fts_info) {
139		case FTS_DNR:
140			if (!fflag || p->fts_errno != ENOENT) {
141				warnx("%s: %s",
142				    p->fts_path, strerror(p->fts_errno));
143				eval = 1;
144			}
145			continue;
146		case FTS_ERR:
147			errc(1, p->fts_errno, "%s", p->fts_path);
148		case FTS_NS:
149			/*
150			 * FTS_NS: assume that if can't stat the file, it
151			 * can't be unlinked.
152			 */
153			if (!needstat)
154				break;
155			if (!fflag || p->fts_errno != ENOENT) {
156				warnx("%s: %s",
157				    p->fts_path, strerror(p->fts_errno));
158				eval = 1;
159			}
160			continue;
161		case FTS_D:
162			/* Pre-order: give user chance to skip. */
163			if (!fflag && !check(p->fts_path, p->fts_accpath,
164			    p->fts_statp)) {
165				(void)fts_set(fts, p, FTS_SKIP);
166				p->fts_number = SKIPPED;
167			}
168			continue;
169		case FTS_DP:
170			/* Post-order: see if user skipped. */
171			if (p->fts_number == SKIPPED)
172				continue;
173			break;
174		default:
175			if (!fflag &&
176			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
177				continue;
178		}
179
180		/*
181		 * If we can't read or search the directory, may still be
182		 * able to remove it.  Don't print out the un{read,search}able
183		 * message unless the remove fails.
184		 */
185		switch (p->fts_info) {
186		case FTS_DP:
187		case FTS_DNR:
188			if (!rmdir(p->fts_accpath) ||
189			    (fflag && errno == ENOENT))
190				continue;
191			break;
192
193		case FTS_F:
194		case FTS_NSOK:
195			if (Pflag)
196				rm_overwrite(p->fts_accpath, p->fts_info ==
197				    FTS_NSOK ? NULL : p->fts_statp);
198			/* FALLTHROUGH */
199		default:
200			if (!unlink(p->fts_accpath) ||
201			    (fflag && errno == ENOENT))
202				continue;
203		}
204		warn("%s", p->fts_path);
205		eval = 1;
206	}
207	if (errno)
208		err(1, "fts_read");
209	fts_close(fts);
210}
211
212static void
213rm_file(char **argv)
214{
215	struct stat sb;
216	int rval;
217	char *f;
218
219	/*
220	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
221	 * to remove a directory is an error, so must always stat the file.
222	 */
223	while ((f = *argv++) != NULL) {
224		/* Assume if can't stat the file, can't unlink it. */
225		if (lstat(f, &sb)) {
226			if (!fflag || errno != ENOENT) {
227				warn("%s", f);
228				eval = 1;
229			}
230			continue;
231		}
232
233		if (S_ISDIR(sb.st_mode) && !dflag) {
234			warnx("%s: is a directory", f);
235			eval = 1;
236			continue;
237		}
238		if (!fflag && !check(f, f, &sb))
239			continue;
240		else if (S_ISDIR(sb.st_mode))
241			rval = rmdir(f);
242		else {
243			if (Pflag)
244				rm_overwrite(f, &sb);
245			rval = unlink(f);
246		}
247		if (rval && (!fflag || errno != ENOENT)) {
248			warn("%s", f);
249			eval = 1;
250		}
251	}
252}
253
254/*
255 * rm_overwrite --
256 *	Overwrite the file with varying bit patterns.
257 *
258 * XXX
259 * This is a cheap way to *really* delete files.  Note that only regular
260 * files are deleted, directories (and therefore names) will remain.
261 * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
262 * System V file system).  In a logging file system, you'll have to have
263 * kernel support.
264 * Returns 1 for success.
265 */
266static int
267rm_overwrite(char *file, struct stat *sbp)
268{
269	struct stat sb, sb2;
270	struct statfs fsb;
271	size_t bsize;
272	int fd;
273	char *buf = NULL;
274
275	fd = -1;
276	if (sbp == NULL) {
277		if (lstat(file, &sb))
278			goto err;
279		sbp = &sb;
280	}
281	if (!S_ISREG(sbp->st_mode))
282		return (1);
283	if (sbp->st_nlink > 1) {
284		warnx("%s (inode %llu): not overwritten due to multiple links",
285		    file, (unsigned long long)sbp->st_ino);
286		return (0);
287	}
288	if ((fd = open(file, O_WRONLY|O_NONBLOCK|O_NOFOLLOW, 0)) == -1)
289		goto err;
290	if (fstat(fd, &sb2))
291		goto err;
292	if (sb2.st_dev != sbp->st_dev || sb2.st_ino != sbp->st_ino ||
293	    !S_ISREG(sb2.st_mode)) {
294		errno = EPERM;
295		goto err;
296	}
297	if (fstatfs(fd, &fsb) == -1)
298		goto err;
299	bsize = MAXIMUM(fsb.f_iosize, 1024U);
300	if ((buf = malloc(bsize)) == NULL)
301		err(1, "%s: malloc", file);
302
303	if (!pass(fd, sbp->st_size, buf, bsize))
304		goto err;
305	if (fsync(fd))
306		goto err;
307	close(fd);
308	free(buf);
309	return (1);
310
311err:
312	warn("%s", file);
313	close(fd);
314	eval = 1;
315	free(buf);
316	return (0);
317}
318
319static int
320pass(int fd, off_t len, char *buf, size_t bsize)
321{
322	size_t wlen;
323
324	for (; len > 0; len -= wlen) {
325		wlen = len < bsize ? len : bsize;
326		arc4random_buf(buf, wlen);
327		if (write(fd, buf, wlen) != wlen)
328			return (0);
329	}
330	return (1);
331}
332
333static int
334check(char *path, char *name, struct stat *sp)
335{
336	int ch, first;
337	char modep[15];
338
339	/* Check -i first. */
340	if (iflag)
341		(void)fprintf(stderr, "remove %s? ", path);
342	else {
343		/*
344		 * If it's not a symbolic link and it's unwritable and we're
345		 * talking to a terminal, ask.  Symbolic links are excluded
346		 * because their permissions are meaningless.  Check stdin_ok
347		 * first because we may not have stat'ed the file.
348		 */
349		if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK) ||
350		    errno != EACCES)
351			return (1);
352		strmode(sp->st_mode, modep);
353		(void)fprintf(stderr, "override %s%s%s/%s for %s? ",
354		    modep + 1, modep[9] == ' ' ? "" : " ",
355		    user_from_uid(sp->st_uid, 0),
356		    group_from_gid(sp->st_gid, 0), path);
357	}
358	(void)fflush(stderr);
359
360	first = ch = getchar();
361	while (ch != '\n' && ch != EOF)
362		ch = getchar();
363	return (first == 'y' || first == 'Y');
364}
365
366/*
367 * POSIX.2 requires that if "." or ".." are specified as the basename
368 * portion of an operand, a diagnostic message be written to standard
369 * error and nothing more be done with such operands.
370 *
371 * Since POSIX.2 defines basename as the final portion of a path after
372 * trailing slashes have been removed, we'll remove them here.
373 */
374#define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
375static void
376checkdot(char **argv)
377{
378	char *p, **save, **t;
379	int complained;
380
381	complained = 0;
382	for (t = argv; *t;) {
383		/* strip trailing slashes */
384		p = strrchr (*t, '\0');
385		while (--p > *t && *p == '/')
386			*p = '\0';
387
388		/* extract basename */
389		if ((p = strrchr(*t, '/')) != NULL)
390			++p;
391		else
392			p = *t;
393
394		if (ISDOT(p)) {
395			if (!complained++)
396				warnx("\".\" and \"..\" may not be removed");
397			eval = 1;
398			for (save = t; (t[0] = t[1]) != NULL; ++t)
399				continue;
400			t = save;
401		} else
402			++t;
403	}
404}
405