1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1992-2010 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Common Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
12 * *
13 * Information and Software Systems Research *
14 * AT&T Research *
15 * Florham Park NJ *
16 * *
17 * Glenn Fowler <gsf@research.att.com> *
18 * David Korn <dgk@research.att.com> *
19 * *
20 ***********************************************************************/
21 #pragma prototyped
22 /*
23 * Glenn Fowler
24 * AT&T Research
25 *
26 * rm [-fir] [file ...]
27 */
28
29 static const char usage[] =
30 "[-?\n@(#)$Id: rm (AT&T Research) 2009-06-18 $\n]"
31 USAGE_LICENSE
32 "[+NAME?rm - remove files]"
33 "[+DESCRIPTION?\brm\b removes the named \afile\a arguments. By default it"
34 " does not remove directories. If a file is unwritable, the"
35 " standard input is a terminal, and the \b--force\b option is not"
36 " given, \brm\b prompts the user for whether to remove the file."
37 " An affirmative response (\by\b or \bY\b) removes the file, a quit"
38 " response (\bq\b or \bQ\b) causes \brm\b to exit immediately, and"
39 " all other responses skip the current file.]"
40
41 "[c|F:clear|clobber?Clear the contents of each file before removing by"
42 " writing a 0 filled buffer the same size as the file, executing"
43 " \bfsync\b(2) and closing before attempting to remove. Implemented"
44 " only on systems that support \bfsync\b(2).]"
45 "[d:directory?\bremove\b(3) (or \bunlink\b(2)) directories rather than"
46 " \brmdir\b(2), and don't require that they be empty before removal."
47 " The caller requires sufficient privilege, not to mention a strong"
48 " constitution, to use this option. Even though the directory must"
49 " not be empty, \brm\b still attempts to empty it before removal.]"
50 "[f:force?Ignore nonexistent files and never prompt the user.]"
51 "[i:interactive|prompt?Prompt whether to remove each file."
52 " An affirmative response (\by\b or \bY\b) removes the file, a quit"
53 " response (\bq\b or \bQ\b) causes \brm\b to exit immediately, and"
54 " all other responses skip the current file.]"
55 "[r|R:recursive?Remove the contents of directories recursively.]"
56 "[u:unconditional?If \b--recursive\b and \b--force\b are also enabled then"
57 " the owner read, write and execute modes are enabled (if not already"
58 " enabled) for each directory before attempting to remove directory"
59 " contents.]"
60 "[v:verbose?Print the name of each file before removing it.]"
61
62 "\n"
63 "\nfile ...\n"
64 "\n"
65
66 "[+SEE ALSO?\bmv\b(1), \brmdir\b(2), \bunlink\b(2), \bremove\b(3)]"
67 ;
68
69 #include <cmd.h>
70 #include <ls.h>
71 #include <fts_fix.h>
72 #include <fs3d.h>
73
74 #define RM_ENTRY 1
75
76 #define beenhere(f) (((f)->fts_number>>1)==(f)->fts_statp->st_nlink)
77 #define isempty(f) (!((f)->fts_number&RM_ENTRY))
78 #define nonempty(f) ((f)->fts_parent->fts_number|=RM_ENTRY)
79 #define pathchunk(n) roundof(n,1024)
80 #define retry(f) ((f)->fts_number=((f)->fts_statp->st_nlink<<1))
81
82 typedef struct State_s /* program state */
83 {
84 void* context; /* builtin context */
85 int clobber; /* clear out file data first */
86 int directory; /* remove(dir) not rmdir(dir) */
87 int force; /* force actions */
88 int fs3d; /* 3d enabled */
89 int interactive; /* prompt for approval */
90 int recursive; /* remove subtrees too */
91 int terminal; /* attached to terminal */
92 int uid; /* caller uid */
93 int unconditional; /* enable dir rwx on preorder */
94 int verbose; /* display each file */
95 #if _lib_fsync
96 char buf[SF_BUFSIZE];/* clobber buffer */
97 #endif
98 } State_t;
99
100 /*
101 * remove a single file
102 */
103
104 static int
rm(State_t * state,register FTSENT * ent)105 rm(State_t* state, register FTSENT* ent)
106 {
107 register char* path;
108 register int n;
109 int v;
110 struct stat st;
111
112 if (ent->fts_info == FTS_NS || ent->fts_info == FTS_ERR || ent->fts_info == FTS_SLNONE)
113 {
114 if (!state->force)
115 error(2, "%s: not found", ent->fts_path);
116 }
117 else if (state->fs3d && iview(ent->fts_statp))
118 fts_set(NiL, ent, FTS_SKIP);
119 else switch (ent->fts_info)
120 {
121 case FTS_DNR:
122 case FTS_DNX:
123 if (state->unconditional)
124 {
125 if (!chmod(ent->fts_name, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU))
126 {
127 fts_set(NiL, ent, FTS_AGAIN);
128 break;
129 }
130 error_info.errors++;
131 }
132 else if (!state->force)
133 error(2, "%s: cannot %s directory", ent->fts_path, (ent->fts_info & FTS_NR) ? "read" : "search");
134 else
135 error_info.errors++;
136 fts_set(NiL, ent, FTS_SKIP);
137 nonempty(ent);
138 break;
139 case FTS_D:
140 case FTS_DC:
141 path = ent->fts_name;
142 if (path[0] == '.' && (!path[1] || path[1] == '.' && !path[2]) && (ent->fts_level > 0 || path[1]))
143 {
144 fts_set(NiL, ent, FTS_SKIP);
145 if (!state->force)
146 error(2, "%s: cannot remove", ent->fts_path);
147 else
148 error_info.errors++;
149 break;
150 }
151 if (!state->recursive)
152 {
153 fts_set(NiL, ent, FTS_SKIP);
154 error(2, "%s: directory", ent->fts_path);
155 break;
156 }
157 if (!beenhere(ent))
158 {
159 if (state->unconditional && (ent->fts_statp->st_mode ^ S_IRWXU))
160 chmod(path, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU);
161 if (ent->fts_level > 0)
162 {
163 char* s;
164
165 if (ent->fts_accpath == ent->fts_name || !(s = strrchr(ent->fts_accpath, '/')))
166 v = !stat(".", &st);
167 else
168 {
169 path = ent->fts_accpath;
170 *s = 0;
171 v = !stat(path, &st);
172 *s = '/';
173 }
174 if (v)
175 v = st.st_nlink <= 2 || st.st_ino == ent->fts_parent->fts_statp->st_ino && st.st_dev == ent->fts_parent->fts_statp->st_dev || strchr(astconf("PATH_ATTRIBUTES", path, NiL), 'l');
176 }
177 else
178 v = 1;
179 if (v)
180 {
181 if (state->interactive)
182 {
183 if ((v = astquery(-1, "remove directory %s? ", ent->fts_path)) < 0 || sh_checksig(state->context))
184 return -1;
185 if (v > 0)
186 {
187 fts_set(NiL, ent, FTS_SKIP);
188 nonempty(ent);
189 }
190 }
191 if (ent->fts_info == FTS_D)
192 break;
193 }
194 else
195 {
196 ent->fts_info = FTS_DC;
197 error(1, "%s: hard link to directory", ent->fts_path);
198 }
199 }
200 else if (ent->fts_info == FTS_D)
201 break;
202 /*FALLTHROUGH*/
203 case FTS_DP:
204 if (isempty(ent) || state->directory)
205 {
206 path = ent->fts_name;
207 if (path[0] != '.' || path[1])
208 {
209 path = ent->fts_accpath;
210 if (state->verbose)
211 sfputr(sfstdout, ent->fts_path, '\n');
212 if ((ent->fts_info == FTS_DC || state->directory) ? remove(path) : rmdir(path))
213 switch (errno)
214 {
215 case ENOENT:
216 break;
217 case EEXIST:
218 #if defined(ENOTEMPTY) && (ENOTEMPTY) != (EEXIST)
219 case ENOTEMPTY:
220 #endif
221 if (ent->fts_info == FTS_DP && !beenhere(ent))
222 {
223 retry(ent);
224 fts_set(NiL, ent, FTS_AGAIN);
225 break;
226 }
227 /*FALLTHROUGH*/
228 default:
229 nonempty(ent);
230 if (!state->force)
231 error(ERROR_SYSTEM|2, "%s: directory not removed", ent->fts_path);
232 else
233 error_info.errors++;
234 break;
235 }
236 }
237 else if (!state->force)
238 error(2, "%s: cannot remove", ent->fts_path);
239 else
240 error_info.errors++;
241 }
242 else
243 {
244 nonempty(ent);
245 if (!state->force)
246 error(2, "%s: directory not removed", ent->fts_path);
247 else
248 error_info.errors++;
249 }
250 break;
251 default:
252 path = ent->fts_accpath;
253 if (state->verbose)
254 sfputr(sfstdout, ent->fts_path, '\n');
255 if (state->interactive)
256 {
257 if ((v = astquery(-1, "remove %s? ", ent->fts_path)) < 0 || sh_checksig(state->context))
258 return -1;
259 if (v > 0)
260 {
261 nonempty(ent);
262 break;
263 }
264 }
265 else if (!state->force && state->terminal && S_ISREG(ent->fts_statp->st_mode))
266 {
267 if ((n = open(path, O_RDWR)) < 0)
268 {
269 if (
270 #ifdef ENOENT
271 errno != ENOENT &&
272 #endif
273 #ifdef EROFS
274 errno != EROFS &&
275 #endif
276 (v = astquery(-1, "override protection %s for %s? ",
277 #ifdef ETXTBSY
278 errno == ETXTBSY ? "``running program''" :
279 #endif
280 ent->fts_statp->st_uid != state->uid ? "``not owner''" :
281 fmtmode(ent->fts_statp->st_mode & S_IPERM, 0) + 1, ent->fts_path)) < 0 ||
282 sh_checksig(state->context))
283 return -1;
284 if (v > 0)
285 {
286 nonempty(ent);
287 break;
288 }
289 }
290 else
291 close(n);
292 }
293 #if _lib_fsync
294 if (state->clobber && S_ISREG(ent->fts_statp->st_mode) && ent->fts_statp->st_size > 0)
295 {
296 if ((n = open(path, O_WRONLY)) < 0)
297 error(ERROR_SYSTEM|2, "%s: cannot clear data", ent->fts_path);
298 else
299 {
300 off_t c = ent->fts_statp->st_size;
301
302 for (;;)
303 {
304 if (write(n, state->buf, sizeof(state->buf)) != sizeof(state->buf))
305 {
306 error(ERROR_SYSTEM|2, "%s: data clear error", ent->fts_path);
307 break;
308 }
309 if (c <= sizeof(state->buf))
310 break;
311 c -= sizeof(state->buf);
312 }
313 fsync(n);
314 close(n);
315 }
316 }
317 #endif
318 if (remove(path))
319 {
320 nonempty(ent);
321 switch (errno)
322 {
323 case ENOENT:
324 break;
325 default:
326 if (!state->force || state->interactive)
327 error(ERROR_SYSTEM|2, "%s: not removed", ent->fts_path);
328 else
329 error_info.errors++;
330 break;
331 }
332 }
333 break;
334 }
335 return 0;
336 }
337
338 int
b_rm(int argc,register char ** argv,void * context)339 b_rm(int argc, register char** argv, void* context)
340 {
341 State_t state;
342 FTS* fts;
343 FTSENT* ent;
344 int set3d;
345
346 cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
347 memset(&state, 0, sizeof(state));
348 state.context = context;
349 state.fs3d = fs3d(FS3D_TEST);
350 state.terminal = isatty(0);
351 for (;;)
352 {
353 switch (optget(argv, usage))
354 {
355 case 'd':
356 state.directory = 1;
357 continue;
358 case 'f':
359 state.force = 1;
360 state.interactive = 0;
361 continue;
362 case 'i':
363 state.interactive = 1;
364 state.force = 0;
365 continue;
366 case 'r':
367 case 'R':
368 state.recursive = 1;
369 continue;
370 case 'F':
371 #if _lib_fsync
372 state.clobber = 1;
373 #else
374 error(1, "%s not implemented on this system", opt_info.name);
375 #endif
376 continue;
377 case 'u':
378 state.unconditional = 1;
379 continue;
380 case 'v':
381 state.verbose = 1;
382 continue;
383 case '?':
384 error(ERROR_USAGE|4, "%s", opt_info.arg);
385 continue;
386 case ':':
387 error(2, "%s", opt_info.arg);
388 continue;
389 }
390 break;
391 }
392 argv += opt_info.index;
393 if (*argv && streq(*argv, "-") && !streq(*(argv - 1), "--"))
394 argv++;
395 if (error_info.errors || !*argv)
396 error(ERROR_USAGE|4, "%s", optusage(NiL));
397
398 /*
399 * do it
400 */
401
402 if (state.interactive)
403 state.verbose = 0;
404 state.uid = geteuid();
405 state.unconditional = state.unconditional && state.recursive && state.force;
406 if (state.recursive && state.fs3d)
407 {
408 set3d = state.fs3d;
409 state.fs3d = 0;
410 fs3d(0);
411 }
412 else
413 set3d = 0;
414 if (fts = fts_open(argv, FTS_PHYSICAL, NiL))
415 {
416 while (!sh_checksig(context) && (ent = fts_read(fts)) && !rm(&state, ent));
417 fts_close(fts);
418 }
419 else if (!state.force)
420 error(ERROR_SYSTEM|2, "%s: cannot remove", argv[0]);
421 if (set3d)
422 fs3d(set3d);
423 return error_info.errors != 0;
424 }
425