xref: /onnv-gate/usr/src/lib/libcmd/common/rm.c (revision 12068:08a39a083754)
14887Schin /***********************************************************************
24887Schin *                                                                      *
34887Schin *               This software is part of the ast package               *
4*12068SRoger.Faulkner@Oracle.COM *          Copyright (c) 1992-2010 AT&T Intellectual Property          *
54887Schin *                      and is licensed under the                       *
64887Schin *                  Common Public License, Version 1.0                  *
78462SApril.Chin@Sun.COM *                    by AT&T Intellectual Property                     *
84887Schin *                                                                      *
94887Schin *                A copy of the License is available at                 *
104887Schin *            http://www.opensource.org/licenses/cpl1.0.txt             *
114887Schin *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
124887Schin *                                                                      *
134887Schin *              Information and Software Systems Research               *
144887Schin *                            AT&T Research                             *
154887Schin *                           Florham Park NJ                            *
164887Schin *                                                                      *
174887Schin *                 Glenn Fowler <gsf@research.att.com>                  *
184887Schin *                  David Korn <dgk@research.att.com>                   *
194887Schin *                                                                      *
204887Schin ***********************************************************************/
214887Schin #pragma prototyped
224887Schin /*
234887Schin  * Glenn Fowler
244887Schin  * AT&T Research
254887Schin  *
264887Schin  * rm [-fir] [file ...]
274887Schin  */
284887Schin 
294887Schin static const char usage[] =
3010898Sroland.mainz@nrubsig.org "[-?\n@(#)$Id: rm (AT&T Research) 2009-06-18 $\n]"
314887Schin USAGE_LICENSE
324887Schin "[+NAME?rm - remove files]"
334887Schin "[+DESCRIPTION?\brm\b removes the named \afile\a arguments. By default it"
344887Schin "	does not remove directories. If a file is unwritable, the"
354887Schin "	standard input is a terminal, and the \b--force\b option is not"
364887Schin "	given, \brm\b prompts the user for whether to remove the file."
374887Schin "	An affirmative response (\by\b or \bY\b) removes the file, a quit"
384887Schin "	response (\bq\b or \bQ\b) causes \brm\b to exit immediately, and"
394887Schin "	all other responses skip the current file.]"
404887Schin 
414887Schin "[c|F:clear|clobber?Clear the contents of each file before removing by"
424887Schin "	writing a 0 filled buffer the same size as the file, executing"
434887Schin "	\bfsync\b(2) and closing before attempting to remove. Implemented"
444887Schin "	only on systems that support \bfsync\b(2).]"
454887Schin "[d:directory?\bremove\b(3) (or \bunlink\b(2)) directories rather than"
464887Schin "	\brmdir\b(2), and don't require that they be empty before removal."
474887Schin "	The caller requires sufficient privilege, not to mention a strong"
484887Schin "	constitution, to use this option. Even though the directory must"
494887Schin "	not be empty, \brm\b still attempts to empty it before removal.]"
504887Schin "[f:force?Ignore nonexistent files and never prompt the user.]"
514887Schin "[i:interactive|prompt?Prompt whether to remove each file."
524887Schin "	An affirmative response (\by\b or \bY\b) removes the file, a quit"
534887Schin "	response (\bq\b or \bQ\b) causes \brm\b to exit immediately, and"
544887Schin "	all other responses skip the current file.]"
554887Schin "[r|R:recursive?Remove the contents of directories recursively.]"
564887Schin "[u:unconditional?If \b--recursive\b and \b--force\b are also enabled then"
574887Schin "	the owner read, write and execute modes are enabled (if not already"
584887Schin "	enabled) for each directory before attempting to remove directory"
594887Schin "	contents.]"
604887Schin "[v:verbose?Print the name of each file before removing it.]"
614887Schin 
624887Schin "\n"
634887Schin "\nfile ...\n"
644887Schin "\n"
654887Schin 
664887Schin "[+SEE ALSO?\bmv\b(1), \brmdir\b(2), \bunlink\b(2), \bremove\b(3)]"
674887Schin ;
684887Schin 
694887Schin #include <cmd.h>
704887Schin #include <ls.h>
71*12068SRoger.Faulkner@Oracle.COM #include <fts_fix.h>
724887Schin #include <fs3d.h>
734887Schin 
744887Schin #define RM_ENTRY	1
754887Schin 
764887Schin #define beenhere(f)	(((f)->fts_number>>1)==(f)->fts_statp->st_nlink)
774887Schin #define isempty(f)	(!((f)->fts_number&RM_ENTRY))
784887Schin #define nonempty(f)	((f)->fts_parent->fts_number|=RM_ENTRY)
794887Schin #define pathchunk(n)	roundof(n,1024)
804887Schin #define retry(f)	((f)->fts_number=((f)->fts_statp->st_nlink<<1))
814887Schin 
824887Schin typedef struct State_s			/* program state		*/
834887Schin {
8410898Sroland.mainz@nrubsig.org 	void*		context;	/* builtin context		*/
854887Schin 	int		clobber;	/* clear out file data first	*/
864887Schin 	int		directory;	/* remove(dir) not rmdir(dir)	*/
874887Schin 	int		force;		/* force actions		*/
884887Schin 	int		fs3d;		/* 3d enabled			*/
894887Schin 	int		interactive;	/* prompt for approval		*/
904887Schin 	int		recursive;	/* remove subtrees too		*/
914887Schin 	int		terminal;	/* attached to terminal		*/
924887Schin 	int		uid;		/* caller uid			*/
934887Schin 	int		unconditional;	/* enable dir rwx on preorder	*/
944887Schin 	int		verbose;	/* display each file		*/
954887Schin #if _lib_fsync
964887Schin 	char		buf[SF_BUFSIZE];/* clobber buffer		*/
974887Schin #endif
984887Schin } State_t;
994887Schin 
1004887Schin /*
1014887Schin  * remove a single file
1024887Schin  */
1034887Schin 
1044887Schin static int
rm(State_t * state,register FTSENT * ent)1054887Schin rm(State_t* state, register FTSENT* ent)
1064887Schin {
1074887Schin 	register char*	path;
1084887Schin 	register int	n;
1094887Schin 	int		v;
1104887Schin 	struct stat	st;
1114887Schin 
1124887Schin 	if (ent->fts_info == FTS_NS || ent->fts_info == FTS_ERR || ent->fts_info == FTS_SLNONE)
1134887Schin 	{
1144887Schin 		if (!state->force)
1154887Schin 			error(2, "%s: not found", ent->fts_path);
1164887Schin 	}
1174887Schin 	else if (state->fs3d && iview(ent->fts_statp))
1184887Schin 		fts_set(NiL, ent, FTS_SKIP);
1194887Schin 	else switch (ent->fts_info)
1204887Schin 	{
1214887Schin 	case FTS_DNR:
1224887Schin 	case FTS_DNX:
1234887Schin 		if (state->unconditional)
1244887Schin 		{
1254887Schin 			if (!chmod(ent->fts_name, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU))
1264887Schin 			{
1274887Schin 				fts_set(NiL, ent, FTS_AGAIN);
1284887Schin 				break;
1294887Schin 			}
1304887Schin 			error_info.errors++;
1314887Schin 		}
1324887Schin 		else if (!state->force)
1334887Schin 			error(2, "%s: cannot %s directory", ent->fts_path, (ent->fts_info & FTS_NR) ? "read" : "search");
1344887Schin 		else
1354887Schin 			error_info.errors++;
1364887Schin 		fts_set(NiL, ent, FTS_SKIP);
1374887Schin 		nonempty(ent);
1384887Schin 		break;
1394887Schin 	case FTS_D:
1404887Schin 	case FTS_DC:
1414887Schin 		path = ent->fts_name;
1424887Schin 		if (path[0] == '.' && (!path[1] || path[1] == '.' && !path[2]) && (ent->fts_level > 0 || path[1]))
1434887Schin 		{
1444887Schin 			fts_set(NiL, ent, FTS_SKIP);
1454887Schin 			if (!state->force)
1464887Schin 				error(2, "%s: cannot remove", ent->fts_path);
1474887Schin 			else
1484887Schin 				error_info.errors++;
1494887Schin 			break;
1504887Schin 		}
1514887Schin 		if (!state->recursive)
1524887Schin 		{
1534887Schin 			fts_set(NiL, ent, FTS_SKIP);
1544887Schin 			error(2, "%s: directory", ent->fts_path);
1554887Schin 			break;
1564887Schin 		}
1574887Schin 		if (!beenhere(ent))
1584887Schin 		{
1594887Schin 			if (state->unconditional && (ent->fts_statp->st_mode ^ S_IRWXU))
1604887Schin 				chmod(path, (ent->fts_statp->st_mode & S_IPERM)|S_IRWXU);
1614887Schin 			if (ent->fts_level > 0)
1624887Schin 			{
1634887Schin 				char*	s;
1644887Schin 
1654887Schin 				if (ent->fts_accpath == ent->fts_name || !(s = strrchr(ent->fts_accpath, '/')))
1664887Schin 					v = !stat(".", &st);
1674887Schin 				else
1684887Schin 				{
1694887Schin 					path = ent->fts_accpath;
1704887Schin 					*s = 0;
1714887Schin 					v = !stat(path, &st);
1724887Schin 					*s = '/';
1734887Schin 				}
1744887Schin 				if (v)
1754887Schin 					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');
1764887Schin 			}
1774887Schin 			else
1784887Schin 				v = 1;
1794887Schin 			if (v)
1804887Schin 			{
1814887Schin 				if (state->interactive)
1824887Schin 				{
18310898Sroland.mainz@nrubsig.org 					if ((v = astquery(-1, "remove directory %s? ", ent->fts_path)) < 0 || sh_checksig(state->context))
1844887Schin 						return -1;
1854887Schin 					if (v > 0)
1864887Schin 					{
1874887Schin 						fts_set(NiL, ent, FTS_SKIP);
1884887Schin 						nonempty(ent);
1894887Schin 					}
1904887Schin 				}
1914887Schin 				if (ent->fts_info == FTS_D)
1924887Schin 					break;
1934887Schin 			}
1944887Schin 			else
1954887Schin 			{
1964887Schin 				ent->fts_info = FTS_DC;
1974887Schin 				error(1, "%s: hard link to directory", ent->fts_path);
1984887Schin 			}
1994887Schin 		}
2004887Schin 		else if (ent->fts_info == FTS_D)
2014887Schin 			break;
2024887Schin 		/*FALLTHROUGH*/
2034887Schin 	case FTS_DP:
2044887Schin 		if (isempty(ent) || state->directory)
2054887Schin 		{
2064887Schin 			path = ent->fts_name;
2074887Schin 			if (path[0] != '.' || path[1])
2084887Schin 			{
2094887Schin 				path = ent->fts_accpath;
2104887Schin 				if (state->verbose)
2114887Schin 					sfputr(sfstdout, ent->fts_path, '\n');
2124887Schin 				if ((ent->fts_info == FTS_DC || state->directory) ? remove(path) : rmdir(path))
2134887Schin 					switch (errno)
2144887Schin 					{
2158462SApril.Chin@Sun.COM 					case ENOENT:
2168462SApril.Chin@Sun.COM 						break;
2174887Schin 					case EEXIST:
2184887Schin #if defined(ENOTEMPTY) && (ENOTEMPTY) != (EEXIST)
2194887Schin 					case ENOTEMPTY:
2204887Schin #endif
2214887Schin 						if (ent->fts_info == FTS_DP && !beenhere(ent))
2224887Schin 						{
2234887Schin 							retry(ent);
2244887Schin 							fts_set(NiL, ent, FTS_AGAIN);
2254887Schin 							break;
2264887Schin 						}
2274887Schin 						/*FALLTHROUGH*/
2284887Schin 					default:
2294887Schin 						nonempty(ent);
2304887Schin 						if (!state->force)
2314887Schin 							error(ERROR_SYSTEM|2, "%s: directory not removed", ent->fts_path);
2324887Schin 						else
2334887Schin 							error_info.errors++;
2344887Schin 						break;
2354887Schin 					}
2364887Schin 			}
2374887Schin 			else if (!state->force)
2384887Schin 				error(2, "%s: cannot remove", ent->fts_path);
2394887Schin 			else
2404887Schin 				error_info.errors++;
2414887Schin 		}
2424887Schin 		else
2434887Schin 		{
2444887Schin 			nonempty(ent);
2454887Schin 			if (!state->force)
2464887Schin 				error(2, "%s: directory not removed", ent->fts_path);
2474887Schin 			else
2484887Schin 				error_info.errors++;
2494887Schin 		}
2504887Schin 		break;
2514887Schin 	default:
2524887Schin 		path = ent->fts_accpath;
2534887Schin 		if (state->verbose)
2544887Schin 			sfputr(sfstdout, ent->fts_path, '\n');
2554887Schin 		if (state->interactive)
2564887Schin 		{
25710898Sroland.mainz@nrubsig.org 			if ((v = astquery(-1, "remove %s? ", ent->fts_path)) < 0 || sh_checksig(state->context))
2584887Schin 				return -1;
2594887Schin 			if (v > 0)
2604887Schin 			{
2614887Schin 				nonempty(ent);
2624887Schin 				break;
2634887Schin 			}
2644887Schin 		}
2654887Schin 		else if (!state->force && state->terminal && S_ISREG(ent->fts_statp->st_mode))
2664887Schin 		{
2674887Schin 			if ((n = open(path, O_RDWR)) < 0)
2684887Schin 			{
2694887Schin 				if (
2704887Schin #ifdef ENOENT
2714887Schin 					errno != ENOENT &&
2724887Schin #endif
2734887Schin #ifdef EROFS
2744887Schin 					errno != EROFS &&
2754887Schin #endif
2764887Schin 					(v = astquery(-1, "override protection %s for %s? ",
2774887Schin #ifdef ETXTBSY
2784887Schin 					errno == ETXTBSY ? "``running program''" :
2794887Schin #endif
2804887Schin 					ent->fts_statp->st_uid != state->uid ? "``not owner''" :
28110898Sroland.mainz@nrubsig.org 					fmtmode(ent->fts_statp->st_mode & S_IPERM, 0) + 1, ent->fts_path)) < 0 ||
28210898Sroland.mainz@nrubsig.org 					sh_checksig(state->context))
2834887Schin 						return -1;
2844887Schin 					if (v > 0)
2854887Schin 					{
2864887Schin 						nonempty(ent);
2874887Schin 						break;
2884887Schin 					}
2894887Schin 			}
2904887Schin 			else
2914887Schin 				close(n);
2924887Schin 		}
2934887Schin #if _lib_fsync
2944887Schin 		if (state->clobber && S_ISREG(ent->fts_statp->st_mode) && ent->fts_statp->st_size > 0)
2954887Schin 		{
2964887Schin 			if ((n = open(path, O_WRONLY)) < 0)
2974887Schin 				error(ERROR_SYSTEM|2, "%s: cannot clear data", ent->fts_path);
2984887Schin 			else
2994887Schin 			{
3004887Schin 				off_t		c = ent->fts_statp->st_size;
3014887Schin 
3024887Schin 				for (;;)
3034887Schin 				{
3044887Schin 					if (write(n, state->buf, sizeof(state->buf)) != sizeof(state->buf))
3054887Schin 					{
3064887Schin 						error(ERROR_SYSTEM|2, "%s: data clear error", ent->fts_path);
3074887Schin 						break;
3084887Schin 					}
3094887Schin 					if (c <= sizeof(state->buf))
3104887Schin 						break;
3114887Schin 					c -= sizeof(state->buf);
3124887Schin 				}
3134887Schin 				fsync(n);
3144887Schin 				close(n);
3154887Schin 			}
3164887Schin 		}
3174887Schin #endif
3184887Schin 		if (remove(path))
3194887Schin 		{
3204887Schin 			nonempty(ent);
3218462SApril.Chin@Sun.COM 			switch (errno)
3228462SApril.Chin@Sun.COM 			{
3238462SApril.Chin@Sun.COM 			case ENOENT:
3248462SApril.Chin@Sun.COM 				break;
3258462SApril.Chin@Sun.COM 			default:
3268462SApril.Chin@Sun.COM 				if (!state->force || state->interactive)
3278462SApril.Chin@Sun.COM 					error(ERROR_SYSTEM|2, "%s: not removed", ent->fts_path);
3288462SApril.Chin@Sun.COM 				else
3298462SApril.Chin@Sun.COM 					error_info.errors++;
3308462SApril.Chin@Sun.COM 				break;
3318462SApril.Chin@Sun.COM 			}
3324887Schin 		}
3334887Schin 		break;
3344887Schin 	}
3354887Schin 	return 0;
3364887Schin }
3374887Schin 
3384887Schin int
b_rm(int argc,register char ** argv,void * context)3394887Schin b_rm(int argc, register char** argv, void* context)
3404887Schin {
3414887Schin 	State_t		state;
3424887Schin 	FTS*		fts;
3434887Schin 	FTSENT*		ent;
3444887Schin 	int		set3d;
3454887Schin 
3464887Schin 	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
3474887Schin 	memset(&state, 0, sizeof(state));
34810898Sroland.mainz@nrubsig.org 	state.context = context;
3494887Schin 	state.fs3d = fs3d(FS3D_TEST);
3504887Schin 	state.terminal = isatty(0);
3514887Schin 	for (;;)
3524887Schin 	{
3534887Schin 		switch (optget(argv, usage))
3544887Schin 		{
3554887Schin 		case 'd':
3564887Schin 			state.directory = 1;
3574887Schin 			continue;
3584887Schin 		case 'f':
3594887Schin 			state.force = 1;
3604887Schin 			state.interactive = 0;
3614887Schin 			continue;
3624887Schin 		case 'i':
3634887Schin 			state.interactive = 1;
3644887Schin 			state.force = 0;
3654887Schin 			continue;
3664887Schin 		case 'r':
3674887Schin 		case 'R':
3684887Schin 			state.recursive = 1;
3694887Schin 			continue;
3704887Schin 		case 'F':
3714887Schin #if _lib_fsync
3724887Schin 			state.clobber = 1;
3734887Schin #else
3744887Schin 			error(1, "%s not implemented on this system", opt_info.name);
3754887Schin #endif
3764887Schin 			continue;
3774887Schin 		case 'u':
3784887Schin 			state.unconditional = 1;
3794887Schin 			continue;
3804887Schin 		case 'v':
3814887Schin 			state.verbose = 1;
3824887Schin 			continue;
3834887Schin 		case '?':
3844887Schin 			error(ERROR_USAGE|4, "%s", opt_info.arg);
3854887Schin 			continue;
3864887Schin 		case ':':
3874887Schin 			error(2, "%s", opt_info.arg);
3884887Schin 			continue;
3894887Schin 		}
3904887Schin 		break;
3914887Schin 	}
3924887Schin 	argv += opt_info.index;
3934887Schin 	if (*argv && streq(*argv, "-") && !streq(*(argv - 1), "--"))
3944887Schin 		argv++;
3954887Schin 	if (error_info.errors || !*argv)
3964887Schin 		error(ERROR_USAGE|4, "%s", optusage(NiL));
3974887Schin 
3984887Schin 	/*
3994887Schin 	 * do it
4004887Schin 	 */
4014887Schin 
4024887Schin 	if (state.interactive)
4034887Schin 		state.verbose = 0;
4044887Schin 	state.uid = geteuid();
4054887Schin 	state.unconditional = state.unconditional && state.recursive && state.force;
4064887Schin 	if (state.recursive && state.fs3d)
4074887Schin 	{
4084887Schin 		set3d = state.fs3d;
4094887Schin 		state.fs3d = 0;
4104887Schin 		fs3d(0);
4114887Schin 	}
4124887Schin 	else
4134887Schin 		set3d = 0;
4144887Schin 	if (fts = fts_open(argv, FTS_PHYSICAL, NiL))
4154887Schin 	{
4168462SApril.Chin@Sun.COM 		while (!sh_checksig(context) && (ent = fts_read(fts)) && !rm(&state, ent));
4174887Schin 		fts_close(fts);
4184887Schin 	}
4194887Schin 	else if (!state.force)
4204887Schin 		error(ERROR_SYSTEM|2, "%s: cannot remove", argv[0]);
4214887Schin 	if (set3d)
4224887Schin 		fs3d(set3d);
4234887Schin 	return error_info.errors != 0;
4244887Schin }
425