xref: /titanic_50/usr/src/lib/libcmd/common/rm.c (revision 3e14f97f673e8a630f076077de35afdd43dc1587)
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 
29 static const char usage[] =
30 "[-?\n@(#)$Id: rm (AT&T Research) 2009-06-18 $\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 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 
82 typedef 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 
104 static int
rm(State_t * state,register FTSENT * ent)105 rm(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 
338 int
b_rm(int argc,register char ** argv,void * context)339 b_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