xref: /original-bsd/bin/rm/rm.c (revision 333da485)
1 /*-
2  * Copyright (c) 1990, 1993
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\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.2 (Berkeley) 01/02/94";
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 			if (!fflag || errno != ENOENT) {
121 				warn("%s", p->fts_path);
122 				eval = 1;
123 			}
124 			continue;
125 		case FTS_ERR:
126 			err(1, "%s", p->fts_path);
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 || errno != ENOENT) {
135 				warn("%s", p->fts_path);
136 				eval = 1;
137 			}
138 			continue;
139 		case FTS_D:
140 			/* Pre-order: give user chance to skip. */
141 			if (iflag && !check(p->fts_path, p->fts_accpath,
142 			    p->fts_statp)) {
143 				(void)fts_set(fts, p, FTS_SKIP);
144 				p->fts_number = SKIPPED;
145 			}
146 			continue;
147 		case FTS_DP:
148 			/* Post-order: see if user skipped. */
149 			if (p->fts_number == SKIPPED)
150 				continue;
151 			break;
152 		}
153 		if (!fflag &&
154 		    !check(p->fts_path, p->fts_accpath, p->fts_statp))
155 			continue;
156 
157 		/*
158 		 * If we can't read or search the directory, may still be
159 		 * able to remove it.  Don't print out the un{read,search}able
160 		 * message unless the remove fails.
161 		 */
162 		if (p->fts_info == FTS_DP || p->fts_info == FTS_DNR) {
163 			if (!rmdir(p->fts_accpath))
164 				continue;
165 			if (errno == ENOENT) {
166 				if (fflag)
167 					continue;
168 			} else if (p->fts_info != FTS_DP)
169 				warnx("%s: unable to read", p->fts_path);
170 		} else if (!unlink(p->fts_accpath) || fflag && errno == ENOENT)
171 			continue;
172 		warn("%s", p->fts_path);
173 		eval = 1;
174 	}
175 }
176 
177 void
178 rmfile(argv)
179 	char **argv;
180 {
181 	register int df;
182 	register char *f;
183 	struct stat sb;
184 
185 	df = dflag;
186 	/*
187 	 * Remove a file.  POSIX 1003.2 states that, by default, attempting
188 	 * to remove a directory is an error, so must always stat the file.
189 	 */
190 	while (f = *argv++) {
191 		/* Assume if can't stat the file, can't unlink it. */
192 		if (lstat(f, &sb)) {
193 			if (!fflag || errno != ENOENT) {
194 				warn("%s", f);
195 				eval = 1;
196 			}
197 			continue;
198 		}
199 		if (S_ISDIR(sb.st_mode) && !df) {
200 			warnx("%s: is a directory", f);
201 			eval = 1;
202 			continue;
203 		}
204 		if (!fflag && !check(f, f, &sb))
205 			continue;
206 		if ((S_ISDIR(sb.st_mode) ? rmdir(f) : unlink(f)) &&
207 		    (!fflag || errno != ENOENT)) {
208 			warn("%s", f);
209 			eval = 1;
210 		}
211 	}
212 }
213 
214 int
215 check(path, name, sp)
216 	char *path, *name;
217 	struct stat *sp;
218 {
219 	register int first, ch;
220 	char modep[15];
221 
222 	/* Check -i first. */
223 	if (iflag)
224 		(void)fprintf(stderr, "remove %s? ", path);
225 	else {
226 		/*
227 		 * If it's not a symbolic link and it's unwritable and we're
228 		 * talking to a terminal, ask.  Symbolic links are excluded
229 		 * because their permissions are meaningless.  Check stdin_ok
230 		 * first because we may not have stat'ed the file.
231 		 */
232 		if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK))
233 			return (1);
234 		strmode(sp->st_mode, modep);
235 		(void)fprintf(stderr, "override %s%s%s/%s for %s? ",
236 		    modep + 1, modep[9] == ' ' ? "" : " ",
237 		    user_from_uid(sp->st_uid, 0),
238 		    group_from_gid(sp->st_gid, 0), path);
239 	}
240 	(void)fflush(stderr);
241 
242 	first = ch = getchar();
243 	while (ch != '\n' && ch != EOF)
244 		ch = getchar();
245 	return (first == 'y');
246 }
247 
248 #define ISDOT(a)	((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2]))
249 void
250 checkdot(argv)
251 	char **argv;
252 {
253 	register char *p, **t, **save;
254 	int complained;
255 
256 	complained = 0;
257 	for (t = argv; *t;) {
258 		if (p = strrchr(*t, '/'))
259 			++p;
260 		else
261 			p = *t;
262 		if (ISDOT(p)) {
263 			if (!complained++)
264 				warnx("\".\" and \"..\" may not be removed");
265 			eval = 1;
266 			for (save = t; t[0] = t[1]; ++t);
267 			t = save;
268 		} else
269 			++t;
270 	}
271 }
272 
273 void
274 usage()
275 {
276 	(void)fprintf(stderr, "usage: rm [-dfiRr] file ...\n");
277 	exit(1);
278 }
279