/*
 * Copyright (c) 2000 Silicon Graphics, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.  Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 * Mountain View, CA  94043, or:
 *
 * http://www.sgi.com
 *
 * For further information regarding this notice, see:
 *
 * http://oss.sgi.com/projects/GenInfo/NoticeExplan/
 */

/* $Id: rmobj.c,v 1.5 2009/07/20 10:59:32 vapier Exp $ */

/**********************************************************
 *
 *    OS Testing - Silicon Graphics, Inc.
 *
 *    FUNCTION NAME     : rmobj()
 *
 *    FUNCTION TITLE    : Remove an object
 *
 *    SYNOPSIS:
 *      int rmobj(char *obj, char **errmsg)
 *
 *    AUTHOR            : Kent Rogers
 *
 *    INITIAL RELEASE   : UNICOS 7.0
 *
 *    USER DESCRIPTION
 *      This routine will remove the specified object.  If the specified
 *      object is a directory, it will recursively remove the directory
 *      and everything underneath it.  It assumes that it has privilege
 *      to remove everything that it tries to remove.  If rmobj() encounters
 *      any problems, and errmsg is not NULL, errmsg is set to point to a
 *      string explaining the error.
 *
 *    DETAILED DESCRIPTION
 *      Allocate space for the directory and its contents
 *      Open the directory to get access to what is in it
 *      Loop through the objects in the directory:
 *        If the object is not "." or "..":
 *          Determine the file type by calling lstat()
 *          If the object is not a directory:
 *            Remove the object with unlink()
 *         Else:
 *            Call rmobj(object) to remove the object's contents
 *            Determine the link count on object by calling lstat()
 *            If the link count >= 3:
 *              Remove the directory with unlink()
 *            Else
 *               Remove the directory with rmdir()
 *      Close the directory and free the pointers
 *
 *    RETURN VALUE
 *      If there are any problems, rmobj() will set errmsg (if it was not
 *      NULL) and return -1.  Otherwise it will return 0.
 *
 ************************************************************/
#define _GNU_SOURCE
#include <errno.h>		/* for errno */
#include <stdio.h>		/* for NULL */
#include <stdlib.h>		/* for malloc() */
#include <string.h>		/* for string function */
#include <limits.h>		/* for PATH_MAX */
#include <sys/types.h>		/* for opendir(), readdir(), closedir(), stat() */
#include <sys/stat.h>		/* for [l]stat() */
#include <fcntl.h>
#include <dirent.h>		/* for opendir(), readdir(), closedir() */
#include <unistd.h>		/* for rmdir(), unlink() */
#include "rmobj.h"

#define SYSERR strerror(errno)

int rmobj(char *obj, char **errmsg)
{
	int ret_val = 0;	/* return value from this routine */
	DIR *dir;		/* pointer to a directory */
	struct dirent *dir_ent;	/* pointer to directory entries */
	char dirobj[PATH_MAX];	/* object inside directory to modify */
	struct stat statbuf;	/* used to hold stat information */
	static char err_msg[1024];	/* error message */
	int fd;

	/* Determine the file type */

	fd = open(obj, O_DIRECTORY | O_NOFOLLOW);
	if (fd != -1) {
		close(fd);
		/* object is a directory */

		/* Do NOT perform the request if the directory is "/" */
		if (!strcmp(obj, "/")) {
			if (errmsg != NULL) {
				sprintf(err_msg, "Cannot remove /");
				*errmsg = err_msg;
			}
			return -1;
		}

		/* Open the directory to get access to what is in it */
		if ((dir = opendir(obj)) == NULL) {
			if (rmdir(obj) != 0) {
				if (errmsg != NULL) {
					sprintf(err_msg,
						"rmdir(%s) failed; errno=%d: %s",
						obj, errno, SYSERR);
					*errmsg = err_msg;
				}
				return -1;
			} else {
				return 0;
			}
		}

		/* Loop through the entries in the directory, removing each one */
		for (dir_ent = (struct dirent *)readdir(dir);
		     dir_ent != NULL; dir_ent = (struct dirent *)readdir(dir)) {

			/* Don't remove "." or ".." */
			if (!strcmp(dir_ent->d_name, ".")
			    || !strcmp(dir_ent->d_name, ".."))
				continue;

			/* Recursively call this routine to remove the current entry */
			sprintf(dirobj, "%s/%s", obj, dir_ent->d_name);
			if (rmobj(dirobj, errmsg) != 0)
				ret_val = -1;
		}

		/* Close the directory */
		closedir(dir);

		/* If there were problems removing an entry, don't attempt to
		   remove the directory itself */
		if (ret_val == -1)
			return -1;

		/* Get the link count, now that all the entries have been removed */
		if (lstat(obj, &statbuf) < 0) {
			if (errmsg != NULL) {
				sprintf(err_msg,
					"lstat(%s) failed; errno=%d: %s", obj,
					errno, SYSERR);
				*errmsg = err_msg;
			}
			return -1;
		}

		/* Remove the directory itself */
		if (statbuf.st_nlink >= 3) {
			/* The directory is linked; unlink() must be used */
			if (unlink(obj) < 0) {
				if (errmsg != NULL) {
					sprintf(err_msg,
						"unlink(%s) failed; errno=%d: %s",
						obj, errno, SYSERR);
					*errmsg = err_msg;
				}
				return -1;
			}
		} else {
			/* The directory is not linked; remove() can be used */
			if (remove(obj) < 0) {
				if (errmsg != NULL) {
					sprintf(err_msg,
						"remove(%s) failed; errno=%d: %s",
						obj, errno, SYSERR);
					*errmsg = err_msg;
				}
				return -1;
			}
		}
	} else {
		/* object is not a directory; just use unlink() */
		if (unlink(obj) < 0) {
			if (errmsg != NULL) {
				sprintf(err_msg,
					"unlink(%s) failed; errno=%d: %s", obj,
					errno, SYSERR);
				*errmsg = err_msg;
			}
			return -1;
		}
	}			/* if obj is a directory */

	/*
	 * Everything must have went ok.
	 */
	return 0;
}				/* rmobj() */
