xref: /illumos-gate/usr/src/contrib/ast/src/lib/libcmd/rm.c (revision b30d1939)
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