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
29static const char usage[] =
30"[-?\n@(#)$Id: rm (AT&T Research) 2009-06-18 $\n]"
31USAGE_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
82typedef 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
104static int
105rm(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
338int
339b_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