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