/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)main.c	1.3	92/07/14 SMI"       /* SVr4.0 1.6.1.1 */

/*
 * main.c
 *
 *	Implements the "putdev" command.
 */

/*
 *  G L O B A L   R E F E R E N C E S
 */

/*
 * Header files needed:
 *	<sys/types.h>	Standard UNIX(r) types
 *	<stdio.h>	Standard I/O definitions
 *	<string.h>	String handling definitions
 *	<errno.h>	Error codes
 *	<unistd.h>	Standard Identifiers
 *	<fmtmsg.h>	Standard Message Generation definitions
 *	<devmgmt.h>	Device Management definitions
 */

#include	<sys/types.h>
#include	<stdio.h>
#include	<string.h>
#include	<errno.h>
#include	<unistd.h>
#include	<fmtmsg.h>
#include	<devmgmt.h>


/*
 * Externals referenced (and not defined in header files)
 *	optind		index to next arg on the command line (from getopt())
 *	opterr		FLAG, TRUE tells getopt() to suppress error messages
 *	optarg		Ptr to current option's argument
 *	getopt()	Command-line parsing function
 *	malloc()	Allocate a chunk of main memory
 *	putenv()	Put a value into the environment
 *	exit()		Exit the process
 *	access()	Check the accessibility of a file
 */

extern	int	optind;
extern	int	opterr;
extern	char   *optarg;
extern	int	getopt();
extern	void   *malloc();
extern	void	exit();
extern	int	putenv();
extern	int	access();


/*
 * Device Management Globals referenced:
 *	_adddevtabrec()		Add a device-table entry to the device table
 *	_moddevtabrec()		Modify a device-table entry in the dev tab
 *	_rmdevtabrec()		Remove a device-table entry in the dev tab
 *	_rmdevtabattrs()	Remove attributes from a device-table entry
 *	_validalias()		Is a value a valid alias?
 *	_devtabpath()		Get the pathname of the device table
 */

int	_adddevtabrec();
int	_moddevtabrec();
int	_rmdevtabrec();
int	_rmdevtabattrs();
int	_validalias();
char   *_devtabpath();

/*
 *  L O C A L   D E F I N I T I O N S
 */

/*
 * General Purpose Constants
 *	TRUE		Boolean TRUE (if not already defined)
 *	FALSE		Boolean FALSE (if not already defined)
 *	NULL		Null address (if not already defined)
 */

#ifndef	TRUE
#define	TRUE	(1)
#endif

#ifndef	FALSE
#define	FALSE	(0)
#endif

#ifndef	NULL
#define	NULL	(0)
#endif


/*
 * Exit codes
 *	EX_OK		All went well
 *	EX_ERROR	Usage or internal error
 *	EX_DEVTAB	Had trouble accessing/reading/writing the device table
 *	EX_EXISTS	The specified alias already exists
 *	EX_ATTRIB	One or more attributes requested for removal was not
 *			defined for the device
 *	EX_RELPATH	Pathname supplied for cdevice, bdevice or pathname
 *			attributes was not a full pathname
 */

#define	EX_OK		0
#define	EX_ERROR	1
#define	EX_DEVTAB	2
#define	EX_EXISTS	3
#define	EX_ATTRIB	4
#define	EX_RELPATH	4


/*
 * Error messages
 */

#define	E_USAGE		"usage: putdev -a alias [attribute=value [...]]\n       putdev -m device attribute=value [attribute=value [...]]\n       putdev -d device [attribute [...]]"
#define	E_ALIASIS	"Alias already exists in table: %s"
#define	E_NODEV		"Device does not exist in table: %s"
#define	E_NOALIAS	"Cannot use \"alias\" as an attribute"
#define	E_NOATTR	"Attribute not found: %s"
#define	E_NODEVTAB	"Cannot open the device table: %s"
#define	E_NOMKDTAB	"Cannot create a new device table: %s"
#define	E_INVALIAS	"Not a valid device alias: %s"
#define E_MULTIPLE	"Multiple definitions of an attribute are not allowed."
#define	E_INTERNAL	"Internal error, errno=%d"
#define	E_RELPATH	"Full pathname required for cdevice,bdevice and pathname attributes."


/*
 * Macros
 *	stdmsg(r,l,s,t)	    Using fmtmsg(), write a standard message to the
 *			    standard error stream.
 *			    Where:
 *				r   The recoverability of the error
 *				l   The label-component
 *				s   The severity-component
 *				t   The text-component
 */

#define stdmsg(r,l,s,t) (void) fmtmsg(MM_PRINT|MM_UTIL|r,l,s,t,MM_NULLACT,MM_NULLTAG)


/*
 * Static data
 *	msg		Space for message's text-component
 */

static	char		msg[256];	/* Space for text of message */

/*
 * char *mklbl(cmd)
 *	char   *cmd
 *
 *	This function builds a standard label from the command used to invoke
 *	this process and the standard label prefix ("UX:")
 *
 * Arguments:
 *	char *cmd	The command used to invoke this process.
 *
 * Returns:  char *
 *	Pointer to malloc()ed space containing the standard label,
 *	or (char *) NULL if an error occurred.
 */

static char *
mklbl(cmd)
	char   *cmd;
{
	/* Automatic data */
	char   *rtn;		/* Value to return */
	char   *p;		/* Temporary */

	/* Find the 1st char of the basename of the command */
	if (p = strrchr(cmd, '/')) p++;
	else p = cmd;

	/* Allocate and build the string value to return */
	if (rtn = (char *) malloc(strlen("UX:")+strlen(p)+1)) {
	    (void) strcpy(rtn, "UX:");
	    (void) strcat(rtn, p);
	}


	/* Now that we've done all of that work, change the environment
	 * so that only the text-component is written by fmtmsg().
	 * (This should go away in SVR4.1)
	 */

	(void) putenv("MSGVERB=text");

	
	/* Done */
	return(rtn);
}

/*
 * putdev -a alias [attribute=value [...]]
 * putdev -m alias attribute=value [attribute=value [...]]
 * putdev -d alias [attribute [...]]
 *
 * 	Modify the device-table.  If -a specified, add a record for <alias>
 * 	to the table.  If -m specified, modify the attributes specified for
 *	the <device> specified.  If -d specified, remove the specified
 *	attributes from the specified device or remove the specified device.
 *
 * Options:
 *	-a		Add an alias description to the device table
 *	-m		Modify an existing device description
 *	-d		(if no attributes specified) remove the specified
 *			device from the device table, or (if attributes
 *			specified) remove the specified attributes from
 *			the specified device.
 *
 * Exit values:
 *	0		All went well
 *	1		Usage error (includes specifying "alias" as an
 *			<attribute>)
 *	2		The device table file could not be opened, read
 *			or modified
 *	3		If -a, the alias already exists.  Otherwise, the
 *			specified device does not exist in the table
 *	4		One of the specified attributes did not exist
 *			for the device and therefore wasn't removed
 */

main(argc, argv)
	int	argc;			/* Argument count */
	char   *argv[];			/* Argument list */
{
	/* Automatic data */
	char	      **plist;		/* Ptr to list of undef'nd attrs */
	char	       *lbl;		/* Ptr to label for messages */
	char	       *alias;		/* Ptr to <alias> on command-line */
	char	       *device;		/* Ptr to <device> on command-line */
	char	       *p;		/* Temp ptr to char */
	int		noerr;		/* FLAG, TRUE if all's well */
	int		a_seen;		/* TRUE if -a seen on command-line */
	int		m_seen;		/* TRUE if -m seen on command-line */
	int		d_seen;		/* TRUE if -a seen on command-line */
	int		optchar;	/* Option extracted */
	int		exitcd;		/* Value to return at exit */
	int		nattrs;		/* Number of attributes on command */


	/* Generate the label for messages */
	lbl = mklbl(argv[0]);

	/* Extract arguments - validate usage */
	noerr = TRUE;
	a_seen = FALSE;
	m_seen = FALSE;
	d_seen = FALSE;
	opterr = FALSE;
	while ((optchar = getopt(argc, argv, "a:d:m:")) != EOF) switch (optchar) {

	case 'a':
	    if (!(a_seen || m_seen || d_seen)) {
		a_seen = TRUE;
		alias = optarg;
	    }
	    else noerr = FALSE;
	    break;

	case 'd':
	    if (!(a_seen || m_seen || d_seen)) {
		d_seen = TRUE;
		device = optarg;
	    }
	    else noerr = FALSE;
	    break;

	case 'm':
	    if (!(a_seen || m_seen || d_seen)) {
		m_seen = TRUE;
		device = optarg;
	    }
	    else noerr = FALSE;
	    break;

	case '?':
	default:
	    noerr = FALSE;
	}


	/* Write a usage message if we've seen a blatant error */
	if (!(a_seen || m_seen || d_seen) || !noerr) {
	    stdmsg(MM_NRECOV, lbl, MM_ERROR, E_USAGE);
	    exit(EX_ERROR);
	}


	/* Set up */
	exitcd = EX_OK;
	nattrs = argc - optind;


	/*  putdev -a alias [attr=value [...]] */

	if (a_seen) {

	    /* Syntax check */
	    if (nattrs < 0) {
		stdmsg(MM_NRECOV, lbl, MM_ERROR, E_USAGE);
		exitcd = EX_ERROR;
	    } else {

		/* Attempt to add the new alias */
		if (!(_adddevtabrec(alias, &argv[optind]))) {

		    /* Attempt failed.  Write appropriate error message. */

		    switch(errno) {

		    /*
		     * EINVAL indicates that <alias> is not valid or "alias"
		     * was mentioned as <attr> in <attr>=<value> pair.  If the
		     * alias is a valid alias, assume that's the problem.
		     */

		    case EINVAL:
			if (_validalias(alias))
			    p = E_NOALIAS;
			else (void) sprintf(p=msg, E_INVALIAS, alias);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, p);
			exitcd = EX_ERROR;
			break;

		    /*
		     * EEXIST indicates that the alias <alias> already exists
		     * in the device table.
		     */

		    case EEXIST:
			(void) sprintf(msg, E_ALIASIS, alias);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_EXISTS;
			break;

		    /*
		     * EACCES and ENOENT indicate problems reading or writing
		     * the device table.
		     */

		    case EACCES:
		    case ENOENT:
	                p = _devtabpath();
			if (access(p, R_OK) == 0)
			    (void) sprintf(msg, E_NOMKDTAB, p);
			else
			    (void) sprintf(msg, E_NODEVTAB, p);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_DEVTAB;
			break;

		    /*
		     * EAGAIN indicates that an attribute was defined on the
		     * command line more than once.
		     */

		    case EAGAIN:
			stdmsg(MM_NRECOV, lbl, MM_ERROR, E_MULTIPLE);
			exitcd = EX_ERROR;
			break;

		    /*
		     * ENXIO indicates that a relative pathname was supplied
		     * for the cdevice, bdevice or pathname attributes.  Full
		     * pathnames are required for these attributes.
		     */
		    case ENXIO:
			stdmsg(MM_NRECOV, lbl, MM_ERROR, E_RELPATH);
			exitcd = EX_RELPATH;
			break;

		    /*
		     * Some other problem (odd?)
		     */

		    default:
			(void) sprintf(msg, E_INTERNAL, errno);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_ERROR;
		    }
		}
	    }
	}   /* End -a case */


	/* putdev -m device attr=value [...] */

	else if (m_seen) {

	    /* Check usage */

	    if (nattrs <= 0) {
		stdmsg(MM_NRECOV, lbl, MM_ERROR, E_USAGE);
		exitcd = EX_ERROR;
	    } else {

		/* Attempt to modify a device's record */
		if (!(_moddevtabrec(device, &argv[optind]))) {

		    /* Modification attempt failed */

		    switch(errno) {

		    /*
		     * EINVAL indicates that "alias" was used as an attribute
		     * in an <attr>=<value> pair.
		     */

		    case EINVAL:
			stdmsg(MM_NRECOV, lbl, MM_ERROR, E_NOALIAS);
			exitcd = EX_ERROR;
			break;

		    /*
		     * ENODEV indicates that the device that was to
		     * be modified doesn't exist.
		     */

		    case ENODEV:
			(void) sprintf(msg, E_NODEV, device);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_EXISTS;
			break;

		    /*
		     * ENOENT indicates that the device-table doesn't exist.
		     */

		    case ENOENT:
			(void) sprintf(msg, E_NODEVTAB, _devtabpath());
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_DEVTAB;
			break;

		    /*
		     * EACCES indicates that there was a problem reading the
		     * old device table or creating the new table.  If the
		     * old table is readable, assume that we can't create the
		     * new table.  Otherwise, assume that the old table isn't
		     * accessible.
		     */

		    case EACCES:
	                p = _devtabpath();
			if (access(p, R_OK) == 0)
			    (void) sprintf(msg, E_NOMKDTAB, p);
			else
			    (void) sprintf(msg, E_NODEVTAB, p);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_DEVTAB;
			break;

		    /*
		     * EAGAIN indicates that an attribute was specified more than
		     * once on the command line.
		     */

		    case EAGAIN:
			stdmsg(MM_NRECOV, lbl, MM_ERROR, E_MULTIPLE);
			exitcd = EX_ERROR;
			break;

		    /*
		     * ENXIO indicates that a relative pathname was supplied
		     * for the cdevice, bdevice or pathname attributes.  Full
		     * pathnames are required for these attributes.
		     */
		    case ENXIO:
			stdmsg(MM_NRECOV, lbl, MM_ERROR, E_RELPATH);
			exitcd = EX_RELPATH;
			break;

		    /*
		     * Some strange problem...
		     */

		    default:
			(void) sprintf(msg, E_INTERNAL, errno);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_ERROR;
		    }
		}
	    }
	}   /* End -m case */

	else if (d_seen) {

	    /* putdev -d device [attr [...]] */

	    /* Check usage */
	    if (nattrs < 0) {
		stdmsg(MM_NRECOV, lbl, MM_ERROR, E_USAGE);
		exitcd = EX_ERROR;
	    } else {

		/*
		 * Determine case (removing a device or attributes
		 * to a device.
		 */

		if (nattrs == 0) {

		    /* putdev -d device */

		    /* Attempt to remove the specified device */
		    if (!(_rmdevtabrec(device))) switch(errno) {

			/*
			 * ENODEV indicates that the named device is not
			 * defined in the device table.
			 */

		    case ENODEV:
			(void) sprintf(msg, E_NODEV, device);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_EXISTS;
			break;

			/*
			 * ENOENT indicates that the device table can't
			 * be found.
			 */

		    case ENOENT:
			(void) sprintf(msg, E_NODEVTAB, _devtabpath());
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_DEVTAB;
			break;

			/*
			 * EACCES indicates that there was a problem reading the
			 * old device table or creating the new table.  If the
			 * old table is readable, assume that we can't create the
			 * new table.  Otherwise, assume that the old table isn't
			 * accessible.
			 */

		    case EACCES:
			p = _devtabpath();
			if (access(p, R_OK) == 0)
			    (void) sprintf(msg, E_NOMKDTAB, p);
			else
			    (void) sprintf(msg, E_NODEVTAB, p);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_DEVTAB;
			break;

			/*
			 * Some strange problem...
			 */

		    default:
			(void) sprintf(msg, E_INTERNAL, errno);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_ERROR;

		    }   /* End switch */
		}
		else {

		    /* putdev -d device attr [attr [...]] */

		    /*
		     * Attempt to remove the specified attributes from the
		     * specified device.
		     */
		    if (!(_rmdevtabattrs(device, &argv[optind], &plist))) switch(errno) {

			/*
			 * EINVAL indicates that a named attribute was not
			 * defined for the specified device or "alias" was
			 * requested.  If "plist" points to a list of attrs,
			 * the former is the problem.  Otherwise, the latter
			 * is the problem.
			 */

		    case EINVAL:
			if (plist) {
			    exitcd = EX_ATTRIB;
			    for (; *plist; plist++) {
				(void) sprintf(msg, E_NOATTR, *plist);
				stdmsg(MM_RECOVER, lbl, MM_WARNING, msg);
			    }
			} else {
			    stdmsg(MM_NRECOV, lbl, MM_ERROR, E_NOALIAS);
			    exitcd = EX_ERROR;
			}
			break;

			/*
			 * ENODEV indicates that the named device is not
			 * defined in the device table.
			 */

		    case ENODEV:
			(void) sprintf(msg, E_NODEV, device);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_EXISTS;
			break;

			/*
			 * ENOENT indicates that the device table can't
			 * be found.
			 */

		    case ENOENT:
			(void) sprintf(msg, E_NODEVTAB, _devtabpath());
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_DEVTAB;
			break;

			/*
			 * EACCES indicates that there was a problem reading the
			 * old device table or creating the new table.  If the
			 * old table is readable, assume that we can't create the
			 * new table.  Otherwise, assume that the old table isn't
			 * accessible.
			 */

		    case EACCES:
			p = _devtabpath();
			if (access(p, R_OK) == 0)
			    (void) sprintf(msg, E_NOMKDTAB, p);
			else
			    (void) sprintf(msg, E_NODEVTAB, p);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_DEVTAB;
			break;

			/*
			 * Some strange problem...
			 */

		    default:
			(void) sprintf(msg, E_INTERNAL, errno);
			stdmsg(MM_NRECOV, lbl, MM_ERROR, msg);
			exitcd = EX_ERROR;

		    }  /* End switch */

		}   /* End "putdev -d device attr [...]" case */

	    }   /* End passes usage-check case */

	}   /* End -d case */


	/* Done.  Return exit code (determined above) */
	exit(exitcd);

#ifdef	lint
	return(exitcd);
#endif
}  /* main() */
