xref: /openbsd-src/bin/rm/rm.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: rm.c,v 1.23 2009/10/27 23:59:22 deraadt 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/param.h>
36 #include <sys/mount.h>
37 
38 #include <locale.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <fts.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include <pwd.h>
48 #include <grp.h>
49 
50 extern char *__progname;
51 
52 int dflag, eval, fflag, iflag, Pflag, stdin_ok;
53 
54 int	check(char *, char *, struct stat *);
55 void	checkdot(char **);
56 void	rm_file(char **);
57 int	rm_overwrite(char *, struct stat *);
58 int	pass(int, int, off_t, char *, size_t);
59 void	rm_tree(char **);
60 void	usage(void);
61 
62 /*
63  * rm --
64  *	This rm is different from historic rm's, but is expected to match
65  *	POSIX 1003.2 behavior.  The most visible difference is that -f
66  *	has two specific effects now, ignore non-existent files and force
67  * 	file removal.
68  */
69 int
70 main(int argc, char *argv[])
71 {
72 	int ch, rflag;
73 
74 	setlocale(LC_ALL, "");
75 
76 	Pflag = rflag = 0;
77 	while ((ch = getopt(argc, argv, "dfiPRr")) != -1)
78 		switch(ch) {
79 		case 'd':
80 			dflag = 1;
81 			break;
82 		case 'f':
83 			fflag = 1;
84 			iflag = 0;
85 			break;
86 		case 'i':
87 			fflag = 0;
88 			iflag = 1;
89 			break;
90 		case 'P':
91 			Pflag = 1;
92 			break;
93 		case 'R':
94 		case 'r':			/* Compatibility. */
95 			rflag = 1;
96 			break;
97 		default:
98 			usage();
99 		}
100 	argc -= optind;
101 	argv += optind;
102 
103 	if (argc < 1 && fflag == 0)
104 		usage();
105 
106 	checkdot(argv);
107 
108 	if (*argv) {
109 		stdin_ok = isatty(STDIN_FILENO);
110 
111 		if (rflag)
112 			rm_tree(argv);
113 		else
114 			rm_file(argv);
115 	}
116 
117 	exit (eval);
118 }
119 
120 void
121 rm_tree(char **argv)
122 {
123 	FTS *fts;
124 	FTSENT *p;
125 	int needstat;
126 	int flags;
127 
128 	/*
129 	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
130 	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
131 	 */
132 	needstat = !fflag && !iflag && stdin_ok;
133 
134 	/*
135 	 * If the -i option is specified, the user can skip on the pre-order
136 	 * visit.  The fts_number field flags skipped directories.
137 	 */
138 #define	SKIPPED	1
139 
140 	flags = FTS_PHYSICAL;
141 	if (!needstat)
142 		flags |= FTS_NOSTAT;
143 	if (!(fts = fts_open(argv, flags, NULL)))
144 		err(1, NULL);
145 	while ((p = fts_read(fts)) != NULL) {
146 		switch (p->fts_info) {
147 		case FTS_DNR:
148 			if (!fflag || p->fts_errno != ENOENT) {
149 				warnx("%s: %s",
150 				    p->fts_path, strerror(p->fts_errno));
151 				eval = 1;
152 			}
153 			continue;
154 		case FTS_ERR:
155 			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
156 		case FTS_NS:
157 			/*
158 			 * FTS_NS: assume that if can't stat the file, it
159 			 * can't be unlinked.
160 			 */
161 			if (!needstat)
162 				break;
163 			if (!fflag || p->fts_errno != ENOENT) {
164 				warnx("%s: %s",
165 				    p->fts_path, strerror(p->fts_errno));
166 				eval = 1;
167 			}
168 			continue;
169 		case FTS_D:
170 			/* Pre-order: give user chance to skip. */
171 			if (!fflag && !check(p->fts_path, p->fts_accpath,
172 			    p->fts_statp)) {
173 				(void)fts_set(fts, p, FTS_SKIP);
174 				p->fts_number = SKIPPED;
175 			}
176 			continue;
177 		case FTS_DP:
178 			/* Post-order: see if user skipped. */
179 			if (p->fts_number == SKIPPED)
180 				continue;
181 			break;
182 		default:
183 			if (!fflag &&
184 			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
185 				continue;
186 		}
187 
188 		/*
189 		 * If we can't read or search the directory, may still be
190 		 * able to remove it.  Don't print out the un{read,search}able
191 		 * message unless the remove fails.
192 		 */
193 		switch (p->fts_info) {
194 		case FTS_DP:
195 		case FTS_DNR:
196 			if (!rmdir(p->fts_accpath) ||
197 			    (fflag && errno == ENOENT))
198 				continue;
199 			break;
200 
201 		default:
202 			if (Pflag)
203 				rm_overwrite(p->fts_accpath, NULL);
204 			if (!unlink(p->fts_accpath) ||
205 			    (fflag && errno == ENOENT))
206 				continue;
207 		}
208 		warn("%s", p->fts_path);
209 		eval = 1;
210 	}
211 	if (errno)
212 		err(1, "fts_read");
213 	fts_close(fts);
214 }
215 
216 void
217 rm_file(char **argv)
218 {
219 	struct stat sb;
220 	int rval;
221 	char *f;
222 
223 	/*
224 	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
225 	 * to remove a directory is an error, so must always stat the file.
226 	 */
227 	while ((f = *argv++) != NULL) {
228 		/* Assume if can't stat the file, can't unlink it. */
229 		if (lstat(f, &sb)) {
230 			if (!fflag || errno != ENOENT) {
231 				warn("%s", f);
232 				eval = 1;
233 			}
234 			continue;
235 		}
236 
237 		if (S_ISDIR(sb.st_mode) && !dflag) {
238 			warnx("%s: is a directory", f);
239 			eval = 1;
240 			continue;
241 		}
242 		if (!fflag && !check(f, f, &sb))
243 			continue;
244 		else if (S_ISDIR(sb.st_mode))
245 			rval = rmdir(f);
246 		else {
247 			if (Pflag)
248 				rm_overwrite(f, &sb);
249 			rval = unlink(f);
250 		}
251 		if (rval && (!fflag || errno != ENOENT)) {
252 			warn("%s", f);
253 			eval = 1;
254 		}
255 	}
256 }
257 
258 /*
259  * rm_overwrite --
260  *	Overwrite the file 3 times with varying bit patterns.
261  *
262  * XXX
263  * This is a cheap way to *really* delete files.  Note that only regular
264  * files are deleted, directories (and therefore names) will remain.
265  * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
266  * System V file system).  In a logging file system, you'll have to have
267  * kernel support.
268  * Returns 1 for success.
269  */
270 int
271 rm_overwrite(char *file, struct stat *sbp)
272 {
273 	struct stat sb;
274 	struct statfs fsb;
275 	size_t bsize;
276 	int fd;
277 	char *buf = NULL;
278 
279 	fd = -1;
280 	if (sbp == NULL) {
281 		if (lstat(file, &sb))
282 			goto err;
283 		sbp = &sb;
284 	}
285 	if (!S_ISREG(sbp->st_mode))
286 		return (1);
287 	if (sbp->st_nlink > 1) {
288 		warnx("%s (inode %u): not overwritten due to multiple links",
289 		    file, sbp->st_ino);
290 		return (0);
291 	}
292 	if ((fd = open(file, O_WRONLY, 0)) == -1)
293 		goto err;
294 	if (fstatfs(fd, &fsb) == -1)
295 		goto err;
296 	bsize = MAX(fsb.f_iosize, 1024U);
297 	if ((buf = malloc(bsize)) == NULL)
298 		err(1, "%s: malloc", file);
299 
300 	if (!pass(0xff, fd, sbp->st_size, buf, bsize) || fsync(fd) ||
301 	    lseek(fd, (off_t)0, SEEK_SET))
302 		goto err;
303 	if (!pass(0x00, fd, sbp->st_size, buf, bsize) || fsync(fd) ||
304 	    lseek(fd, (off_t)0, SEEK_SET))
305 		goto err;
306 	if (!pass(0xff, fd, sbp->st_size, buf, bsize) || fsync(fd))
307 		goto err;
308 	close(fd);
309 	free(buf);
310 	return (1);
311 
312 err:
313 	warn("%s", file);
314 	close(fd);
315 	eval = 1;
316 	free(buf);
317 	return (0);
318 }
319 
320 int
321 pass(int val, int fd, off_t len, char *buf, size_t bsize)
322 {
323 	size_t wlen;
324 
325 	memset(buf, val, bsize);
326 	for (; len > 0; len -= wlen) {
327 		wlen = len < bsize ? len : bsize;
328 		if (write(fd, buf, wlen) != wlen)
329 			return (0);
330 	}
331 	return (1);
332 }
333 
334 int
335 check(char *path, char *name, struct stat *sp)
336 {
337 	int ch, first;
338 	char modep[15];
339 
340 	/* Check -i first. */
341 	if (iflag)
342 		(void)fprintf(stderr, "remove %s? ", path);
343 	else {
344 		/*
345 		 * If it's not a symbolic link and it's unwritable and we're
346 		 * talking to a terminal, ask.  Symbolic links are excluded
347 		 * because their permissions are meaningless.  Check stdin_ok
348 		 * first because we may not have stat'ed the file.
349 		 */
350 		if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK) ||
351 		    errno != EACCES)
352 			return (1);
353 		strmode(sp->st_mode, modep);
354 		(void)fprintf(stderr, "override %s%s%s/%s for %s? ",
355 		    modep + 1, modep[9] == ' ' ? "" : " ",
356 		    user_from_uid(sp->st_uid, 0),
357 		    group_from_gid(sp->st_gid, 0), path);
358 	}
359 	(void)fflush(stderr);
360 
361 	first = ch = getchar();
362 	while (ch != '\n' && ch != EOF)
363 		ch = getchar();
364 	return (first == 'y' || first == 'Y');
365 }
366 
367 /*
368  * POSIX.2 requires that if "." or ".." are specified as the basename
369  * portion of an operand, a diagnostic message be written to standard
370  * error and nothing more be done with such operands.
371  *
372  * Since POSIX.2 defines basename as the final portion of a path after
373  * trailing slashes have been removed, we'll remove them here.
374  */
375 #define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
376 void
377 checkdot(char **argv)
378 {
379 	char *p, **save, **t;
380 	int complained;
381 
382 	complained = 0;
383 	for (t = argv; *t;) {
384 		/* strip trailing slashes */
385 		p = strrchr (*t, '\0');
386 		while (--p > *t && *p == '/')
387 			*p = '\0';
388 
389 		/* extract basename */
390 		if ((p = strrchr(*t, '/')) != NULL)
391 			++p;
392 		else
393 			p = *t;
394 
395 		if (ISDOT(p)) {
396 			if (!complained++)
397 				warnx("\".\" and \"..\" may not be removed");
398 			eval = 1;
399 			for (save = t; (t[0] = t[1]) != NULL; ++t)
400 				continue;
401 			t = save;
402 		} else
403 			++t;
404 	}
405 }
406 
407 void
408 usage(void)
409 {
410 	(void)fprintf(stderr, "usage: %s [-dfiPRr] file ...\n", __progname);
411 	exit(1);
412 }
413