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