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