/*
 * Copyright (c) 1996 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)volmgt_fsi.c	1.6	97/09/08 SMI"

#include	<stdio.h>
#include	<unistd.h>
#include	<stdlib.h>
#include	<string.h>
#include	<strings.h>
#include	<volmgt.h>
#include	<errno.h>
#include	<sys/types.h>
#include	<sys/mkdev.h>
#include	<sys/ddi.h>
#include	<sys/stat.h>
#include	<sys/errno.h>
#include	<rpc/types.h>
#include	"volmgt.h"
#include	"volmgt_private.h"
#include	"volmgt_fsi_private.h"

/* just utnil volmgt.h is up to date */
#ifndef	VOL_RSV_MAXIDLEN
#define	VOL_RSV_MAXIDLEN	256
#endif


/* this routine used by both acquire and release */
static char	*fsi_xlate_name(char *);

/*
 * arc approved interface
 *	- can not be modified without approval from an arc
 *
 * committment level:
 *	uncommitted
 *
 * description:
 *	volmgt_acquire: try to acquire the volmgt advisory device reservation
 *	for a specific device.
 *
 * arguments:
 *	dev - a device name to attempt reserving.  This string can be:
 *		- a full path name to a device
 *		- a symbolic device name (e.g. floppy0)
 *
 *	id  - a reservation string that hopefully describes the application
 *		making this reservation.
 *
 *	pid - a pointer to a pid_t type.  If this argument is not NULL
 *		and the requested device is already reserved, the process
 *		id of the reservation owner will be returned in this
 *		location.
 *
 *	ovr - an override indicator.  If set to non-zero, the caller requests
 *		that this reservation be made unconditionally.
 *
 *	err - the address of a pointer to a string which is to receive the
 *		id argument used when the current device was reserved.  This
 *		is only used when the current reservation attempt fails due
 *		to an already existing reservation for this device.
 *
 * return value(s):
 *	A non-zero indicator if successful.
 *
 *	A zero indicator if unsuccessful.  If errno is EBUSY, then the err
 *	argument will be set to point to the string that the process currently
 *	holding the reservation supplied when reserving the device.  It is up
 *	to the caller to release the storage occupied by the string via
 *	free(3C) when no longer needed.
 *
 * preconditions:
 *	none
 */
int
volmgt_acquire(char *dev, char *id, int ovr, char **err, pid_t *pidp)
{
	char			*targ_name = NULL;
	struct stat		sb;
	dev_t			idev;
	vol_dbid_t		dbid = (vol_dbid_t)-1;
	vol_db_entry_t		dbe;
	vol_db_entry_t		*dbp;
	int			retval = 0;	/* default return => ERROR */
	int			reterr = 0;


#ifdef	DEBUG
	denter("volmgt_acquire(\"%s\", \"%s\", %s, %#p, %#p): entering\n",
	    dev ? dev : "<null ptr>", id ? id : "<null ptr>",
	    ovr ? "TRUE" : "FALSE", err, pidp);
#endif
	/*
	 * the supplied arguments must not be NULL
	 */
	if ((dev == NULL) || (id == NULL) || (err == NULL)) {
		errno = EINVAL;
		goto dun;
	}

	/*
	 * the id string must not be longer than the maximum allowable
	 * number of characters
	 */
	if (strlen(id) > VOL_RSV_MAXIDLEN) {
		errno = E2BIG;
		goto dun;
	}

	if ((targ_name = fsi_xlate_name(dev)) == NULL) {
		goto dun;
	}

	/*
	 * convert 'char *dev' to major/minor pair
	 */
	if (stat(targ_name, &sb) < 0) {
		goto dun;
	}
	idev = sb.st_rdev;

	/*
	 * open the database file
	 */
	if ((dbid = vol_db_open()) < 0) {
		goto dun;
	}

	if ((dbp = vol_db_find(dbid, idev)) == NULL) {
		/*
		 * the entry wasn't found, so reserve it
		 */
		dbe.dev_major = major(idev);
		dbe.dev_minor = minor(idev);
		dbe.pid = getpid();
		dbe.id_tag = id;
		if (vol_db_insert(dbid, &dbe) != 0) {
			retval = 1;		/* success! */
		}
	} else {
		if (ovr || (vol_db_proc_find(dbp->pid) == 0)) {
			/*
			 * the entry exists but either override was specified
			 * or the process holding the reservation is no longer
			 * active
			 *
			 * in either case we'll usurp the reservation
			 */
			if (vol_db_remove(dbid, idev) != 0) {
				/* reserve the device */
				dbe.dev_major = major(idev);
				dbe.dev_minor = minor(idev);
				dbe.pid = getpid();
				dbe.id_tag = id;
				if (vol_db_insert(dbid, &dbe) != 0) {
					retval = 1;
				}
			}

		} else {

			/*
			 * the entry exists and override was NOT specified
			 */

			/*
			 * optionally return the pid of the reservation
			 * owner
			 */
			if (pidp != NULL) {
				*pidp = dbp->pid;
			}

			*err = strdup(dbp->id_tag);
			reterr = EBUSY;
		}
		vol_db_free(dbp);	/* Release the entry */
	}


	/*
	 * if an error was encountered (currently only EBUSY supported)
	 * set errno to reflect it
	 */
	if (reterr != 0) {
		errno = reterr;
	}

dun:
	if ((int)dbid >= 0) {
		(void) vol_db_close(dbid);
	}
	if (targ_name != NULL) {
		free(targ_name);
	}
#ifdef	DEBUG
	dexit("volmgt_acquire: returning %s\n", retval ? "TRUE" : "FALSE");
#endif
	return (retval);
}


/*
 * arc approved interface
 *	- can not be modified without approval from an arc
 *
 * committment level:
 *	uncommitted
 *
 * description:
 *	volmgt_release: try to release the volmgt advisory device reservation
 *	for a specific device.
 *
 * arguments:
 *	dev - a device name to attempt reserving.  This string can be:
 *		- a full path name to a device
 *		- a symbolic device name (e.g. floppy0)
 *
 * return value(s):
 *	A non-zero indicator if successful
 *	A zero indicator if unsuccessful
 *
 * preconditions:
 *	none
 */
int
volmgt_release(char *dev)
{
	char			*targ_name = NULL;
	struct stat		sb;
	long			idev;
	vol_dbid_t		dbid;
	vol_db_entry_t		*dbp;
	int			retval = 0;	/* default return => FAILURE */
	int			reterr = 0;


#ifdef	DEBUG
	denter("volmgt_release(\"%s\"): entering\n", dev ? dev : "<null ptr>");
#endif
	/*
	 * first let's do some minimal validation of the supplied arguments
	 */

	/*
	 * the supplied argument must not be NULL
	 */
	if (dev == NULL) {
		errno = EINVAL;
		goto dun;
	}

	if ((targ_name = fsi_xlate_name(dev)) == NULL) {
		goto dun;
	}

	/*
	 * convert 'char *dev' to major/minor pair
	 */
	if (stat(targ_name, &sb) < 0) {
		goto dun;
	}
	idev = sb.st_rdev;

	/*
	 * open the database file
	 */
	if ((dbid = vol_db_open()) < 0) {
		goto dun;
	}

	if ((dbp = vol_db_find(dbid, idev)) == NULL) {
		/* the entry wasn't found so I can't clear reservation */
		errno = ENOENT;
		goto dun;
	}

	/* the entry was found so make sure I can clear it */
	if (dbp->pid == getpid()) {
		/*
		 * the reservation was made by me, clear it
		 */
		if (vol_db_remove(dbid, idev) != 0) {
			retval = 1;
		}
	} else {
		/*
		 * the entry wasn't made by me
		 */
		reterr = EBUSY;
	}
	vol_db_free(dbp);

	/*
	 * if an error was encountered (currently only EBUSY supported)
	 * set errno to reflect it
	 */
	if (reterr != 0) {
		errno = reterr;
	}
dun:
	if ((int)dbid >= 0) {
		(void) vol_db_close(dbid);
	}
	if (targ_name != NULL) {
		free(targ_name);
	}
#ifdef	DEBUG
	dexit("volmgt_release: returning %s\n", retval ? "TRUE" : "FALSE");
#endif
	return (retval);
}


/*
 * translate suplied vol name into a pathname
 *
 * if volmgt is running, this pathname will be in /vol (or its equiv)
 *
 * if volmgt is not running then this path may be anywhere
 *
 * in either case the pathname will *not* be verified as a blk/chr dev
 *
 * if the return value is non-null then it's been alloced
 *
 * NOTE: assume "vol" is not a NULL ptr
 */
static char *
fsi_xlate_name(char *vol)
{
	char	*res = NULL;			/* result to return */
	char	*vr;				/* volmgt root dir */
	bool_t	vm_running = volmgt_running();	/* volmgt running? */


#ifdef	DEBUG
	denter("fsi_xlate_name(\"%s\"): entering\n", vol);
#endif

	/* is it an absolute pathname ?? */
	if (*vol == '/') {

		if (vm_running) {
			/* pathname must be in the /vol namespace */
			vr = (char *)volmgt_root();
			if (strncmp(vol, vr, strlen(vr)) != 0) {
				/* not a cool pathname */
				errno = EINVAL;	/* XXX: is this correct */
				goto dun;
			}
		}

		res = strdup(vol);

	} else {

		/*
		 * if volmgt is running we can try to dereference it
		 * if volmgt isn't running then just give up
		 */

		if (!vm_running) {
			/* some unknown "name" */
			errno = ENOENT;
			goto dun;
		}

		res = volmgt_symdev(vol);

	}

dun:
#ifdef	DEBUG
	dexit("fsi_xlate_name: returning %s\n", res ? res : "<null ptr>");
#endif
	return (res);
}
