xref: /original-bsd/bin/rm/rm.c (revision 1cf9c499)
1 /*-
2  * Copyright (c) 1990, 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char copyright[] =
10 "@(#) Copyright (c) 1990, 1993, 1994\n\
11 	The Regents of the University of California.  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)rm.c	8.4 (Berkeley) 04/01/94";
16 #endif /* not lint */
17 
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 
21 #include <err.h>
22 #include <errno.h>
23 #include <fts.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 int dflag, fflag, iflag, eval, stdin_ok;
30 
31 int	check __P((char *, char *, struct stat *));
32 void	checkdot __P((char **));
33 void	rmfile __P((char **));
34 void	rmtree __P((char **));
35 void	usage __P((void));
36 
37 /*
38  * rm --
39  *	This rm is different from historic rm's, but is expected to match
40  *	POSIX 1003.2 behavior.  The most visible difference is that -f
41  *	has two specific effects now, ignore non-existent files and force
42  * 	file removal.
43  */
44 int
45 main(argc, argv)
46 	int argc;
47 	char *argv[];
48 {
49 	int ch, rflag;
50 
51 	rflag = 0;
52 	while ((ch = getopt(argc, argv, "dfiRr")) != EOF)
53 		switch(ch) {
54 		case 'd':
55 			dflag = 1;
56 			break;
57 		case 'f':
58 			fflag = 1;
59 			iflag = 0;
60 			break;
61 		case 'i':
62 			fflag = 0;
63 			iflag = 1;
64 			break;
65 		case 'R':
66 		case 'r':			/* compatibility */
67 			rflag = 1;
68 			break;
69 		case '?':
70 		default:
71 			usage();
72 		}
73 	argc -= optind;
74 	argv += optind;
75 
76 	if (argc < 1)
77 		usage();
78 
79 	checkdot(argv);
80 	if (!*argv)
81 		exit (eval);
82 
83 	stdin_ok = isatty(STDIN_FILENO);
84 
85 	if (rflag)
86 		rmtree(argv);
87 	else
88 		rmfile(argv);
89 	exit (eval);
90 }
91 
92 void
93 rmtree(argv)
94 	char **argv;
95 {
96 	FTS *fts;
97 	FTSENT *p;
98 	int needstat;
99 
100 	/*
101 	 * Remove a file hierarchy.  If forcing removal (-f), or interactive
102 	 * (-i) or can't ask anyway (stdin_ok), don't stat the file.
103 	 */
104 	needstat = !fflag && !iflag && stdin_ok;
105 
106 	/*
107 	 * If the -i option is specified, the user can skip on the pre-order
108 	 * visit.  The fts_number field flags skipped directories.
109 	 */
110 #define	SKIPPED	1
111 
112 	if (!(fts = fts_open(argv,
113 	    needstat ? FTS_PHYSICAL : FTS_PHYSICAL|FTS_NOSTAT,
114 	    (int (*)())NULL)))
115 		err(1, NULL);
116 	while ((p = fts_read(fts)) != NULL) {
117 		switch (p->fts_info) {
118 		case FTS_DNR:
119 			if (!fflag || p->fts_errno != ENOENT) {
120 				warnx("%s: %s",
121 				    p->fts_path, strerror(p->fts_errno));
122 				eval = 1;
123 			}
124 			continue;
125 		case FTS_ERR:
126 			errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
127 		case FTS_NS:
128 			/*
129 			 * FTS_NS: assume that if can't stat the file, it
130 			 * can't be unlinked.
131 			 */
132 			if (!needstat)
133 				break;
134 			if (!fflag || p->fts_errno != ENOENT) {
135 				warnx("%s: %s",
136 				    p->fts_path, strerror(p->fts_errno));
137 				eval = 1;
138 			}
139 			continue;
140 		case FTS_D:
141 			/* Pre-order: give user chance to skip. */
142 			if (iflag && !check(p->fts_path, p->fts_accpath,
143 			    p->fts_statp)) {
144 				(void)fts_set(fts, p, FTS_SKIP);
145 				p->fts_number = SKIPPED;
146 			}
147 			continue;
148 		case FTS_DP:
149 			/* Post-order: see if user skipped. */
150 			if (p->fts_number == SKIPPED)
151 				continue;
152 			break;
153 		}
154 		if (!fflag &&
155 		    !check(p->fts_path, p->fts_accpath, p->fts_statp))
156 			continue;
157 
158 		/*
159 		 * If we can't read or search the directory, may still be
160 		 * able to remove it.  Don't print out the un{read,search}able
161 		 * message unless the remove fails.
162 		 */
163 		if (p->fts_info == FTS_DP || p->fts_info == FTS_DNR) {
164 			if (!rmdir(p->fts_accpath))
165 				continue;
166 			if (errno == ENOENT) {
167 				if (fflag)
168 					continue;
169 			} else if (p->fts_info != FTS_DP)
170 				warnx("%s: unable to read", p->fts_path);
171 		} else if (!unlink(p->fts_accpath) || fflag && errno == ENOENT)
172 			continue;
173 		warn("%s", p->fts_path);
174 		eval = 1;
175 	}
176 	if (errno)
177 		err(1, "fts_read");
178 }
179 
180 void
181 rmfile(argv)
182 	char **argv;
183 {
184 	int df;
185 	char *f;
186 	struct stat sb;
187 
188 	df = dflag;
189 	/*
190 	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
191 	 * to remove a directory is an error, so must always stat the file.
192 	 */
193 	while ((f = *argv++) != NULL) {
194 		/* Assume if can't stat the file, can't unlink it. */
195 		if (lstat(f, &sb)) {
196 			if (!fflag || errno != ENOENT) {
197 				warn("%s", f);
198 				eval = 1;
199 			}
200 			continue;
201 		}
202 		if (S_ISDIR(sb.st_mode) && !df) {
203 			warnx("%s: is a directory", f);
204 			eval = 1;
205 			continue;
206 		}
207 		if (!fflag && !check(f, f, &sb))
208 			continue;
209 		if ((S_ISDIR(sb.st_mode) ? rmdir(f) : unlink(f)) &&
210 		    (!fflag || errno != ENOENT)) {
211 			warn("%s", f);
212 			eval = 1;
213 		}
214 	}
215 }
216 
217 int
218 check(path, name, sp)
219 	char *path, *name;
220 	struct stat *sp;
221 {
222 	int ch, first;
223 	char modep[15];
224 
225 	/* Check -i first. */
226 	if (iflag)
227 		(void)fprintf(stderr, "remove %s? ", path);
228 	else {
229 		/*
230 		 * If it's not a symbolic link and it's unwritable and we're
231 		 * talking to a terminal, ask.  Symbolic links are excluded
232 		 * because their permissions are meaningless.  Check stdin_ok
233 		 * first because we may not have stat'ed the file.
234 		 */
235 		if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK))
236 			return (1);
237 		strmode(sp->st_mode, modep);
238 		(void)fprintf(stderr, "override %s%s%s/%s for %s? ",
239 		    modep + 1, modep[9] == ' ' ? "" : " ",
240 		    user_from_uid(sp->st_uid, 0),
241 		    group_from_gid(sp->st_gid, 0), path);
242 	}
243 	(void)fflush(stderr);
244 
245 	first = ch = getchar();
246 	while (ch != '\n' && ch != EOF)
247 		ch = getchar();
248 	return (first == 'y');
249 }
250 
251 #define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2]))
252 void
253 checkdot(argv)
254 	char **argv;
255 {
256 	char *p, **save, **t;
257 	int complained;
258 
259 	complained = 0;
260 	for (t = argv; *t;) {
261 		if ((p = strrchr(*t, '/')) != NULL)
262 			++p;
263 		else
264 			p = *t;
265 		if (ISDOT(p)) {
266 			if (!complained++)
267 				warnx("\".\" and \"..\" may not be removed");
268 			eval = 1;
269 			for (save = t; (t[0] = t[1]) != NULL; ++t);
270 			t = save;
271 		} else
272 			++t;
273 	}
274 }
275 
276 void
277 usage()
278 {
279 
280 	(void)fprintf(stderr, "usage: rm [-dfiRr] file ...\n");
281 	exit(1);
282 }
283