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