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