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
29static const char usage[] =
30"[-?\n@(#)$Id: rm (AT&T Research) 2012-02-14 $\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, 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
83typedef 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
105static int
106rm(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
329int
330b_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