don't quit on FTS_DNR directories; the user entered 'rm -rf a a a b'
[unix-history] / usr / src / bin / rm / rm.c
CommitLineData
7f9ef9cc
KB
1/*-
2 * Copyright (c) 1990 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * %sccs.include.redist.c%
d1e4967d 6 */
ae80f4a2 7
7f9ef9cc
KB
8#ifndef lint
9char copyright[] =
10"@(#) Copyright (c) 1990 The Regents of the University of California.\n\
11 All rights reserved.\n";
12#endif /* not lint */
d1e4967d 13
7f9ef9cc 14#ifndef lint
d6a1ced2 15static char sccsid[] = "@(#)rm.c 5.2 (Berkeley) %G%";
7f9ef9cc 16#endif /* not lint */
d1e4967d 17
7f9ef9cc
KB
18#include <sys/types.h>
19#include <sys/stat.h>
20#include <sys/errno.h>
411a0820
KB
21
22#include <err.h>
7f9ef9cc 23#include <fts.h>
7f9ef9cc 24#include <stdio.h>
7f9ef9cc 25#include <stdlib.h>
411a0820
KB
26#include <string.h>
27#include <unistd.h>
28
29int dflag, fflag, iflag, eval, stdin_ok;
ae80f4a2 30
411a0820
KB
31int check __P((char *, char *, struct stat *));
32void checkdot __P((char **));
33void rmfile __P((char **));
34void rmtree __P((char **));
35void usage __P((void));
7f9ef9cc
KB
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 */
411a0820 44int
ae80f4a2 45main(argc, argv)
8161a03a 46 int argc;
411a0820 47 char *argv[];
ae80f4a2 48{
8161a03a 49 extern int optind;
7f9ef9cc 50 int ch, rflag;
ae80f4a2 51
7f9ef9cc
KB
52 rflag = 0;
53 while ((ch = getopt(argc, argv, "dfiRr")) != EOF)
54 switch(ch) {
55 case 'd':
56 dflag = 1;
57 break;
8161a03a 58 case 'f':
7f9ef9cc
KB
59 fflag = 1;
60 iflag = 0;
d1e4967d 61 break;
8161a03a 62 case 'i':
7f9ef9cc
KB
63 fflag = 0;
64 iflag = 1;
8161a03a
KB
65 break;
66 case 'R':
307d7749 67 case 'r': /* compatibility */
7f9ef9cc 68 rflag = 1;
8161a03a
KB
69 break;
70 case '?':
71 default:
72 usage();
73 }
7f9ef9cc
KB
74 argc -= optind;
75 argv += optind;
ca02483b 76
7f9ef9cc 77 if (argc < 1)
8161a03a 78 usage();
ae80f4a2 79
7f9ef9cc
KB
80 checkdot(argv);
81 if (!*argv)
411a0820 82 exit (eval);
d1e4967d 83
7f9ef9cc
KB
84 stdin_ok = isatty(STDIN_FILENO);
85
86 if (rflag)
87 rmtree(argv);
60b32b72 88 else
7f9ef9cc 89 rmfile(argv);
411a0820 90 exit (eval);
7f9ef9cc
KB
91}
92
411a0820 93void
7f9ef9cc
KB
94rmtree(argv)
95 char **argv;
96{
97 register FTS *fts;
98 register FTSENT *p;
99 register int needstat;
7f9ef9cc
KB
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,
411a0820
KB
115 (int (*)())NULL)))
116 err(1, NULL);
7f9ef9cc 117 while (p = fts_read(fts)) {
411a0820 118 switch (p->fts_info) {
7f9ef9cc 119 case FTS_DNR:
d6a1ced2
KB
120 if (!fflag || errno != ENOENT) {
121 warn("%s", p->fts_path);
122 eval = 1;
123 }
124 continue;
7f9ef9cc 125 case FTS_ERR:
411a0820 126 err(1, "%s", p->fts_path);
7f9ef9cc 127 case FTS_NS:
411a0820
KB
128 /*
129 * FTS_NS: assume that if can't stat the file, it
130 * can't be unlinked.
131 */
7f9ef9cc 132 if (!needstat)
d1e4967d 133 break;
411a0820
KB
134 if (!fflag || errno != ENOENT) {
135 warn("%s", p->fts_path);
136 eval = 1;
137 }
7f9ef9cc 138 continue;
7f9ef9cc 139 case FTS_D:
411a0820 140 /* Pre-order: give user chance to skip. */
7f9ef9cc 141 if (iflag && !check(p->fts_path, p->fts_accpath,
834beb60 142 p->fts_statp)) {
7f9ef9cc
KB
143 (void)fts_set(fts, p, FTS_SKIP);
144 p->fts_number = SKIPPED;
d1e4967d 145 }
7f9ef9cc 146 continue;
7f9ef9cc 147 case FTS_DP:
411a0820 148 /* Post-order: see if user skipped. */
7f9ef9cc
KB
149 if (p->fts_number == SKIPPED)
150 continue;
151 break;
d1e4967d 152 }
7f9ef9cc 153 if (!fflag &&
834beb60 154 !check(p->fts_path, p->fts_accpath, p->fts_statp))
7f9ef9cc
KB
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 */
307d7749 162 if (p->fts_info == FTS_DP || p->fts_info == FTS_DNR) {
7f9ef9cc
KB
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)
411a0820 169 warnx("%s: unable to read", p->fts_path);
7f9ef9cc 170 } else if (!unlink(p->fts_accpath) || fflag && errno == ENOENT)
307d7749 171 continue;
411a0820
KB
172 warn("%s", p->fts_path);
173 eval = 1;
ae80f4a2 174 }
7f9ef9cc
KB
175}
176
411a0820 177void
7f9ef9cc
KB
178rmfile(argv)
179 char **argv;
180{
181 register int df;
182 register char *f;
183 struct stat sb;
ae80f4a2 184
7f9ef9cc
KB
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)) {
411a0820
KB
193 if (!fflag || errno != ENOENT) {
194 warn("%s", f);
195 eval = 1;
196 }
7f9ef9cc 197 continue;
ae80f4a2 198 }
7f9ef9cc 199 if (S_ISDIR(sb.st_mode) && !df) {
411a0820
KB
200 warnx("%s: is a directory", f);
201 eval = 1;
7f9ef9cc 202 continue;
b2704a87 203 }
7f9ef9cc
KB
204 if (!fflag && !check(f, f, &sb))
205 continue;
206 if ((S_ISDIR(sb.st_mode) ? rmdir(f) : unlink(f)) &&
411a0820
KB
207 (!fflag || errno != ENOENT)) {
208 warn("%s", f);
209 eval = 1;
210 }
ae80f4a2
BJ
211 }
212}
213
411a0820 214int
7f9ef9cc
KB
215check(path, name, sp)
216 char *path, *name;
217 struct stat *sp;
ae80f4a2 218{
7f9ef9cc 219 register int first, ch;
411a0820 220 char modep[15];
ae80f4a2 221
7f9ef9cc
KB
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
307d7749 229 * because their permissions are meaningless.
7f9ef9cc
KB
230 */
231 if (S_ISLNK(sp->st_mode) || !stdin_ok || !access(name, W_OK))
411a0820 232 return (1);
7f9ef9cc
KB
233 strmode(sp->st_mode, modep);
234 (void)fprintf(stderr, "override %s%s%s/%s for %s? ",
235 modep + 1, modep[9] == ' ' ? "" : " ",
236 user_from_uid(sp->st_uid, 0),
237 group_from_gid(sp->st_gid, 0), path);
238 }
239 (void)fflush(stderr);
240
241 first = ch = getchar();
242 while (ch != '\n' && ch != EOF)
243 ch = getchar();
411a0820 244 return (first == 'y');
d1e4967d
RC
245}
246
7f9ef9cc 247#define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2]))
411a0820 248void
7f9ef9cc
KB
249checkdot(argv)
250 char **argv;
251{
252 register char *p, **t, **save;
253 int complained;
254
255 complained = 0;
256 for (t = argv; *t;) {
411a0820 257 if (p = strrchr(*t, '/'))
7f9ef9cc
KB
258 ++p;
259 else
260 p = *t;
261 if (ISDOT(p)) {
262 if (!complained++)
411a0820
KB
263 warnx("\".\" and \"..\" may not be removed");
264 eval = 1;
7f9ef9cc
KB
265 for (save = t; t[0] = t[1]; ++t);
266 t = save;
267 } else
268 ++t;
269 }
270}
271
411a0820 272void
8161a03a
KB
273usage()
274{
7f9ef9cc 275 (void)fprintf(stderr, "usage: rm [-dfiRr] file ...\n");
8161a03a
KB
276 exit(1);
277}