1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1990, 1993, 1994
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. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/stat.h>
33#include <sys/param.h>
34#include <sys/mount.h>
35
36#include <err.h>
37#include <errno.h>
38#include <fcntl.h>
39#include <fts.h>
40#include <grp.h>
41#include <locale.h>
42#include <pwd.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <sysexits.h>
47#include <unistd.h>
48
49static int dflag, eval, fflag, iflag, vflag, Wflag, stdin_ok;
50static int rflag, Iflag, xflag;
51static uid_t uid;
52static volatile sig_atomic_t info;
53
54static int	check(const char *, const char *, struct stat *);
55static int	check2(char **);
56static void	checkdot(char **);
57static void	checkslash(char **);
58static void	rm_file(char **);
59static void	rm_tree(char **);
60static void siginfo(int __unused);
61static void	usage(void);
62
63/*
64 * rm --
65 *	This rm is different from historic rm's, but is expected to match
66 *	POSIX 1003.2 behavior.	The most visible difference is that -f
67 *	has two specific effects now, ignore non-existent files and force
68 *	file removal.
69 */
70int
71main(int argc, char *argv[])
72{
73	int ch;
74	char *p;
75
76	(void)setlocale(LC_ALL, "");
77
78	/*
79	 * Test for the special case where the utility is called as
80	 * "unlink", for which the functionality provided is greatly
81	 * simplified.
82	 */
83	if ((p = strrchr(argv[0], '/')) == NULL)
84		p = argv[0];
85	else
86		++p;
87	if (strcmp(p, "unlink") == 0) {
88		if (argc == 2)
89			rm_file(&argv[1]);
90		else if (argc == 3 && strcmp(argv[1], "--") == 0)
91			rm_file(&argv[2]);
92		else
93			usage();
94		exit(eval);
95	}
96
97	rflag = xflag = 0;
98	while ((ch = getopt(argc, argv, "dfiIPRrvWx")) != -1)
99		switch(ch) {
100		case 'd':
101			dflag = 1;
102			break;
103		case 'f':
104			fflag = 1;
105			iflag = 0;
106			break;
107		case 'i':
108			fflag = 0;
109			iflag = 1;
110			break;
111		case 'I':
112			Iflag = 1;
113			break;
114		case 'P':
115			/* Compatibility no-op. */
116			break;
117		case 'R':
118		case 'r':			/* Compatibility. */
119			rflag = 1;
120			break;
121		case 'v':
122			vflag = 1;
123			break;
124		case 'W':
125			Wflag = 1;
126			break;
127		case 'x':
128			xflag = 1;
129			break;
130		default:
131			usage();
132		}
133	argc -= optind;
134	argv += optind;
135
136	if (argc < 1) {
137		if (fflag)
138			return (0);
139		usage();
140	}
141
142	checkdot(argv);
143	checkslash(argv);
144	uid = geteuid();
145
146	(void)signal(SIGINFO, siginfo);
147	if (*argv) {
148		stdin_ok = isatty(STDIN_FILENO);
149
150		if (Iflag) {
151			if (check2(argv) == 0)
152				exit (1);
153		}
154		if (rflag)
155			rm_tree(argv);
156		else
157			rm_file(argv);
158	}
159
160	exit (eval);
161}
162
163static void
164rm_tree(char **argv)
165{
166	FTS *fts;
167	FTSENT *p;
168	int needstat;
169	int flags;
170	int rval;
171
172	/*
173	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
174	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
175	 */
176	needstat = !uid || (!fflag && !iflag && stdin_ok);
177
178	/*
179	 * If the -i option is specified, the user can skip on the pre-order
180	 * visit.  The fts_number field flags skipped directories.
181	 */
182#define	SKIPPED	1
183
184	flags = FTS_PHYSICAL;
185	if (!needstat)
186		flags |= FTS_NOSTAT;
187	if (Wflag)
188		flags |= FTS_WHITEOUT;
189	if (xflag)
190		flags |= FTS_XDEV;
191	if (!(fts = fts_open(argv, flags, NULL))) {
192		if (fflag && errno == ENOENT)
193			return;
194		err(1, "fts_open");
195	}
196	while (errno = 0, (p = fts_read(fts)) != NULL) {
197		switch (p->fts_info) {
198		case FTS_DNR:
199			if (!fflag || p->fts_errno != ENOENT) {
200				warnx("%s: %s",
201				    p->fts_path, strerror(p->fts_errno));
202				eval = 1;
203			}
204			continue;
205		case FTS_ERR:
206			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
207		case FTS_NS:
208			/*
209			 * Assume that since fts_read() couldn't stat the
210			 * file, it can't be unlinked.
211			 */
212			if (!needstat)
213				break;
214			if (!fflag || p->fts_errno != ENOENT) {
215				warnx("%s: %s",
216				    p->fts_path, strerror(p->fts_errno));
217				eval = 1;
218			}
219			continue;
220		case FTS_D:
221			/* Pre-order: give user chance to skip. */
222			if (!fflag && !check(p->fts_path, p->fts_accpath,
223			    p->fts_statp)) {
224				(void)fts_set(fts, p, FTS_SKIP);
225				p->fts_number = SKIPPED;
226			}
227			else if (!uid &&
228				 (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
229				 !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
230				 lchflags(p->fts_accpath,
231					 p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
232				goto err;
233			continue;
234		case FTS_DP:
235			/* Post-order: see if user skipped. */
236			if (p->fts_number == SKIPPED)
237				continue;
238			break;
239		default:
240			if (!fflag &&
241			    !check(p->fts_path, p->fts_accpath, p->fts_statp))
242				continue;
243		}
244
245		rval = 0;
246		if (!uid &&
247		    (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
248		    !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
249			rval = lchflags(p->fts_accpath,
250				       p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
251		if (rval == 0) {
252			/*
253			 * If we can't read or search the directory, may still be
254			 * able to remove it.  Don't print out the un{read,search}able
255			 * message unless the remove fails.
256			 */
257			switch (p->fts_info) {
258			case FTS_DP:
259			case FTS_DNR:
260				rval = rmdir(p->fts_accpath);
261				if (rval == 0 || (fflag && errno == ENOENT)) {
262					if (rval == 0 && vflag)
263						(void)printf("%s\n",
264						    p->fts_path);
265					if (rval == 0 && info) {
266						info = 0;
267						(void)printf("%s\n",
268						    p->fts_path);
269					}
270					continue;
271				}
272				break;
273
274			case FTS_W:
275				rval = undelete(p->fts_accpath);
276				if (rval == 0 && (fflag && errno == ENOENT)) {
277					if (vflag)
278						(void)printf("%s\n",
279						    p->fts_path);
280					if (info) {
281						info = 0;
282						(void)printf("%s\n",
283						    p->fts_path);
284					}
285					continue;
286				}
287				break;
288
289			case FTS_NS:
290				/*
291				 * Assume that since fts_read() couldn't stat
292				 * the file, it can't be unlinked.
293				 */
294				if (fflag)
295					continue;
296				/* FALLTHROUGH */
297
298			case FTS_F:
299			case FTS_NSOK:
300			default:
301				rval = unlink(p->fts_accpath);
302				if (rval == 0 || (fflag && errno == ENOENT)) {
303					if (rval == 0 && vflag)
304						(void)printf("%s\n",
305						    p->fts_path);
306					if (rval == 0 && info) {
307						info = 0;
308						(void)printf("%s\n",
309						    p->fts_path);
310					}
311					continue;
312				}
313			}
314		}
315err:
316		warn("%s", p->fts_path);
317		eval = 1;
318	}
319	if (!fflag && errno)
320		err(1, "fts_read");
321	fts_close(fts);
322}
323
324static void
325rm_file(char **argv)
326{
327	struct stat sb;
328	int rval;
329	char *f;
330
331	/*
332	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
333	 * to remove a directory is an error, so must always stat the file.
334	 */
335	while ((f = *argv++) != NULL) {
336		/* Assume if can't stat the file, can't unlink it. */
337		if (lstat(f, &sb)) {
338			if (Wflag) {
339				sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
340			} else {
341				if (!fflag || errno != ENOENT) {
342					warn("%s", f);
343					eval = 1;
344				}
345				continue;
346			}
347		} else if (Wflag) {
348			warnx("%s: %s", f, strerror(EEXIST));
349			eval = 1;
350			continue;
351		}
352
353		if (S_ISDIR(sb.st_mode) && !dflag) {
354			warnx("%s: is a directory", f);
355			eval = 1;
356			continue;
357		}
358		if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
359			continue;
360		rval = 0;
361		if (!uid && !S_ISWHT(sb.st_mode) &&
362		    (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
363		    !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
364			rval = lchflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
365		if (rval == 0) {
366			if (S_ISWHT(sb.st_mode))
367				rval = undelete(f);
368			else if (S_ISDIR(sb.st_mode))
369				rval = rmdir(f);
370			else
371				rval = unlink(f);
372		}
373		if (rval && (!fflag || errno != ENOENT)) {
374			warn("%s", f);
375			eval = 1;
376		}
377		if (vflag && rval == 0)
378			(void)printf("%s\n", f);
379		if (info && rval == 0) {
380			info = 0;
381			(void)printf("%s\n", f);
382		}
383	}
384}
385
386static int
387check(const char *path, const char *name, struct stat *sp)
388{
389	int ch, first;
390	char modep[15], *flagsp;
391
392	/* Check -i first. */
393	if (iflag)
394		(void)fprintf(stderr, "remove %s? ", path);
395	else {
396		/*
397		 * If it's not a symbolic link and it's unwritable and we're
398		 * talking to a terminal, ask.  Symbolic links are excluded
399		 * because their permissions are meaningless.  Check stdin_ok
400		 * first because we may not have stat'ed the file.
401		 */
402		if (!stdin_ok || S_ISLNK(sp->st_mode) ||
403		    (!access(name, W_OK) &&
404		    !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
405		    (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
406			return (1);
407		strmode(sp->st_mode, modep);
408		if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
409			err(1, "fflagstostr");
410		(void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
411		    modep + 1, modep[10] == ' ' ? "" : " ",
412		    user_from_uid(sp->st_uid, 0),
413		    group_from_gid(sp->st_gid, 0),
414		    *flagsp ? flagsp : "", *flagsp ? " " : "",
415		    path);
416		free(flagsp);
417	}
418	(void)fflush(stderr);
419
420	first = ch = getchar();
421	while (ch != '\n' && ch != EOF)
422		ch = getchar();
423	return (first == 'y' || first == 'Y');
424}
425
426#define ISSLASH(a)	((a)[0] == '/' && (a)[1] == '\0')
427static void
428checkslash(char **argv)
429{
430	char **t, **u;
431	int complained;
432
433	complained = 0;
434	for (t = argv; *t;) {
435		if (ISSLASH(*t)) {
436			if (!complained++)
437				warnx("\"/\" may not be removed");
438			eval = 1;
439			for (u = t; u[0] != NULL; ++u)
440				u[0] = u[1];
441		} else {
442			++t;
443		}
444	}
445}
446
447static int
448check2(char **argv)
449{
450	struct stat st;
451	int first;
452	int ch;
453	int fcount = 0;
454	int dcount = 0;
455	int i;
456	const char *dname = NULL;
457
458	for (i = 0; argv[i]; ++i) {
459		if (lstat(argv[i], &st) == 0) {
460			if (S_ISDIR(st.st_mode)) {
461				++dcount;
462				dname = argv[i];    /* only used if 1 dir */
463			} else {
464				++fcount;
465			}
466		}
467	}
468	first = 0;
469	while (first != 'n' && first != 'N' && first != 'y' && first != 'Y') {
470		if (dcount && rflag) {
471			fprintf(stderr, "recursively remove");
472			if (dcount == 1)
473				fprintf(stderr, " %s", dname);
474			else
475				fprintf(stderr, " %d dirs", dcount);
476			if (fcount == 1)
477				fprintf(stderr, " and 1 file");
478			else if (fcount > 1)
479				fprintf(stderr, " and %d files", fcount);
480		} else if (dcount + fcount > 3) {
481			fprintf(stderr, "remove %d files", dcount + fcount);
482		} else {
483			return(1);
484		}
485		fprintf(stderr, "? ");
486		fflush(stderr);
487
488		first = ch = getchar();
489		while (ch != '\n' && ch != EOF)
490			ch = getchar();
491		if (ch == EOF)
492			break;
493	}
494	return (first == 'y' || first == 'Y');
495}
496
497#define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
498static void
499checkdot(char **argv)
500{
501	char *p, **save, **t;
502	int complained;
503
504	complained = 0;
505	for (t = argv; *t;) {
506		if ((p = strrchr(*t, '/')) != NULL)
507			++p;
508		else
509			p = *t;
510		if (ISDOT(p)) {
511			if (!complained++)
512				warnx("\".\" and \"..\" may not be removed");
513			eval = 1;
514			for (save = t; (t[0] = t[1]) != NULL; ++t)
515				continue;
516			t = save;
517		} else
518			++t;
519	}
520}
521
522static void
523usage(void)
524{
525
526	(void)fprintf(stderr, "%s\n%s\n",
527	    "usage: rm [-f | -i] [-dIPRrvWx] file ...",
528	    "       unlink [--] file");
529	exit(EX_USAGE);
530}
531
532static void
533siginfo(int sig __unused)
534{
535
536	info = 1;
537}
538