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

#pragma ident	"@(#)action_workman.c	1.16	98/08/06 SMI"

/*
 * if this cdrom has audio tracks, start up workman
 */

#include	<malloc.h>
#include	<sys/types.h>
#include	<sys/dkio.h>
#include	<sys/cdio.h>
#include	<sys/vtoc.h>
#include	<sys/param.h>
#include	<sys/systeminfo.h>
#include	<sys/stat.h>
#include	<rpc/types.h>
#include	<errno.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<fcntl.h>
#include	<pwd.h>
#include	<limits.h>
#include	<signal.h>
#include	<string.h>
#include	<libintl.h>
#include	<rmmount.h>
#include	<volmgt.h>


static void	setup_user(void);
static void	set_user_env(int, char **);
static void	add_to_args(int *, char ***, int, char **);
static void	clean_args(int *, char **);

extern void	dprintf(const char *fmt, ...);

/* for debug messages -- from rmmount */
extern char	*prog_name;
extern pid_t	prog_pid;

/* name of the config file */
extern char	*rmm_config;

#define	MAX_NEWARGS	20	/* more than this an something's wrong */

#ifdef	DEBUG
static void	print_args(char *, int, char **);
#endif

int
action(struct action_arg **aa, int argc, char **argv)
{
	struct cdrom_tochdr	th;
	struct cdrom_tocentry	te;
	uchar_t			i;
	int			fd;
	int			found_audio = FALSE;
	extern char		*rmm_dsodir;
	char			*atype = getenv("VOLUME_ACTION");


	/* we don't care about removes, just inserts */
	if (strcmp(atype, "insert") != 0) {
		goto dun;
	}

	/* can't do anything without a path */
	if (aa[0]->aa_rawpath == NULL) {
		dprintf("action_workman: no rawpath\n");
		goto dun;
	}

	/* ensure something to exec was passed in */
	if (argc < 2) {
		(void) fprintf(stderr, gettext(
	"%s(%d) error: workman pathname not supplied in %s\n"),
		    prog_name, prog_pid, rmm_config);
		goto dun;
	}

	dprintf("action_workman: using path \"%s\"\n", aa[0]->aa_rawpath);

	/* open the device and read the table of contents */
	if ((fd = open(aa[0]->aa_rawpath, O_RDONLY)) < 0) {
		dprintf("action_workman: open failed; %m\n");
		goto dun;
	}
	if (ioctl(fd, CDROMREADTOCHDR, &th) < 0) {
		dprintf("action_workman: ioctl failed; %m\n");
		(void) close(fd);
		goto dun;
	}
	te.cdte_format = CDROM_MSF;
	for (i = (uchar_t)th.cdth_trk0; i < (uchar_t)th.cdth_trk1+1; i++) {
		te.cdte_track = i;
		if (ioctl(fd, CDROMREADTOCENTRY, &te) < 0) {
			/* can't read an entry -- just try the next */
			continue;
		}
		if ((uint_t)te.cdte_datamode == 255) {
			found_audio = TRUE;
			break;
		}
	}
	(void) close(fd);

	/* if we didn't find any audio then give up now */
	if (found_audio == FALSE) {
		dprintf("action_workman: no audio\n");
		goto dun;
	}

	dprintf("action_workman: found audio (track %d, %d tracks ttl)\n",
	    i, th.cdth_trk1 - th.cdth_trk0 + 1);

	/*
	 * Set the ENXIO on eject attribute.  This causes workman
	 * to get an ENXIO if someone types eject from another
	 * window.  Workman, when started with the -X flag, exits
	 * as soon as it ejects the media, or if it ever sees an
	 * ENXIO from an ioctl.
	 */
	media_setattr(aa[0]->aa_rawpath, "s-enxio", "true");

	/* start workman: don't care about errors just fire and forget */
	if (fork() == 0) {
		int		fd;
		int		argc_new = 0;
		char		*argv_new[MAX_NEWARGS];
		char		sympath[MAXNAMELEN];


#ifdef	DEBUG
		dprintf("DEBUG: child is running (pid = %d)\n", getpid());
#endif
		/* child */
		chdir(rmm_dsodir);

		/* stick their error messages out on the console */
		if ((fd = open("/dev/console", O_RDWR)) >= 0) {
			(void) dup2(fd, fileno(stdin));
			(void) dup2(fd, fileno(stdout));
			(void) dup2(fd, fileno(stderr));
		}

		/* clean up args */
		clean_args(&argc, argv);

		set_user_env(argc, argv);
		setup_user();
#ifdef	DEBUG
		dprintf("DEBUG: child: after setting up env\n");
#endif
		/* remove leading shared-object name (ours!) */
		argc--; argv++;

		/* set up path to use (instead of rawpath) */
		(void) sprintf(sympath, "/vol/dev/aliases/%s",
		    getenv("VOLUME_SYMDEV"));

		/* add to argv passed in */
		argv_new[argc_new++] = strdup("-c");
		argv_new[argc_new++] = sympath;
		argv_new[argc_new++] = "-o";
		argv_new[argc_new++] = "-X";
		argv_new[argc_new] = NULL;
#ifdef	DEBUG
		dprintf("DEBUG: child: about to add to args ...\n");
#endif
		/* combine our argv and the new list */
		add_to_args(&argc, &argv, argc_new, argv_new);
#ifdef	DEBUG
		print_args("before exec", argc, argv);
#endif
		(void) execv(argv[0], argv);

		/* bummer, it failed -- EXIT, don't return!! */
		(void) fprintf(stderr,
		    gettext("%s(%ld) error: exec of \"%s\" failed; %s\n"),
		    prog_name, prog_pid, argv[0], strerror(errno));
		exit(1);
		/*NOTREACHED*/
	}

#ifdef	DEBUG
	dprintf("DEBUG: sleeping a while -- just wait\n");
	(void) sleep(15);
#endif

dun:
	/*
	 * we return false here because audio might not be the only thing
	 * on the disk, and we want actions to continue.
	 */
	return (FALSE);
}


/*
 * setup_user:
 *	set a reasonable user and group to run workman as.  The default
 *	is to make it be daemon/other.
 *	The other thing we want to do is make the cwd to be
 *	the user's home directory so all the nice workman databases
 *	work right.
 */
void
setup_user(void)
{
	struct stat	sb;
	uid_t		uid = 1;	/* daemon */
	gid_t		gid = 1;	/* other */
	struct passwd	*pw;
	char		namebuf[MAXNAMELEN];


	/*
	 * The assumption is that a workstation is being used by
	 * the person that's logged into the console and that they
	 * just inserted the cdrom.  This breaks down on servers,
	 * but the most common case (by far) is someone listening
	 * to a cdrom at thier desk, while logged in and running the
	 * window system.
	 */
	if (stat("/dev/console", &sb) == 0) {
		if (sb.st_uid != 0) {
			uid = sb.st_uid;
		}
	}
	/* set HOME env. var. */
	if ((pw = getpwuid(uid)) != NULL) {
		gid = pw->pw_gid;
		(void) sprintf(namebuf, "HOME=%s", pw->pw_dir);
		(void) putenv(strdup(namebuf));
	} else {
		(void) putenv("HOME=/tmp");
	}

	if (setgid(gid) < 0) {
		perror("Failed to set gid:");
	}

	if (setuid(uid) < 0) {
		perror("Failed to set uid:");
	}

}


/*
 * set_user_env -- set up user environment
 */
static void set_user_env(int ac, char **av)
{
	int			i;
	bool_t			display_specified = FALSE;
	static char		hostname[MAXNAMELEN+1];
	static char		display[MAXNAMELEN+1];
	static char		ld_lib_path[MAXNAMELEN+1];
	static char		xfsp[MAXNAMELEN+1];
	static char		xufsp[MAXNAMELEN+1];



#ifdef	DEBUG
	dprintf("DEBUG: set_user_env(): entering\n");
	print_args("entry to set_user_env()", ac, av);
#endif

	/* only set display if it wasn't passed in */
	for (i = 0; i < ac; i++) {
		if ((strcmp(av[i], "-display") == 0) &&
		    (ac > (i+1))) {
			display_specified = TRUE;
			(void) strcpy(display, av[i+1]);
			break;
		}
	}
#ifdef	DEBUG
	dprintf("DEBUG: set_user_env(): display found = %s\n",
	    display_specified ? "TRUE" : "FALSE");
#endif
	if (!display_specified) {
		(void) sysinfo(SI_HOSTNAME, hostname, MAXNAMELEN);
		(void) sprintf(display, "DISPLAY=%s:0.0", hostname);
		(void) putenv(display);
	}

	(void) sprintf(ld_lib_path, "LD_LIBRARY_PATH=%s:%s:%s",
	    "/usr/lib",
	    "/usr/openwin/lib",
	    "/usr/ucblib");
	(void) putenv(ld_lib_path);

	(void) sprintf(xfsp, "XFILESEARCHPATH=%s:%s",
	    "/usr/openwin/lib/app-defaults/%L/%N",
	    "/usr/openwin/lib/app-defaults/%N");
	(void) putenv(xfsp);

	(void) sprintf(xufsp, "XUSERFILESEARCHPATH=%s:%s",
	    "/usr/openwin/lib/app-defaults/%L/%N",
	    "/usr/openwin/lib/app-defaults/%N");
	(void) putenv(xufsp);

	(void) putenv("OPENWINHOME=/usr/openwin");

#ifdef	DEBUG
	dprintf("DEBUG: set_user_env(): leaving\n");
#endif

}


/*
 * add src args to dest arg list
 */
static void
add_to_args(
	int *dest_argcp,			/* ptr to dest argc */
	char ***dest_argvp,			/* ptr to dest argv */
	int src_argc,				/* src argc */
	char **src_argv)			/* src argv */
{
	int		dest_argc = *dest_argcp;
	char		**dest_argv = *dest_argvp;
	int		amt;
	char		**arr;
	int		ind;
	int		i;



	/* find size of new array */
	amt = dest_argc + src_argc;

	/* allocate array */
	if ((arr = (char **)malloc((size_t)
	    ((amt + 1) * (size_t)sizeof (char *)))) == NULL) {
		(void) fprintf(stderr,
		    gettext("%s(%ld) error: can't allocate space; %s\n"),
		    prog_name, prog_pid, strerror(errno));
		return;		/* just return the orig array ?? */
	}

	/* copy old and new array into our new space */
	ind = 0;
	for (i = 0; i < dest_argc; i++) {
		arr[ind++] = dest_argv[i];
	}
	for (i = 0; i < src_argc; i++) {
		arr[ind++] = src_argv[i];
	}
	arr[ind] = NULL;

	/* return result in place of dest junk */
	*dest_argcp = amt;
	*dest_argvp = arr;
}


/*
 * clean up args:
 *	- ensure av[0] == av[1]
 *	- ensure that *acp is correct
 *	- correct the device crap		-- NOT YET IMPLEMENTED
 */
static void
clean_args(int *acp, char **av)
{
	int	i;

	/* ensure count is correct */
	for (i = 0; i < *acp; i++) {
		if (av[i] == NULL) {
			*acp = i;
			break;
		}
	}
}

#ifdef	DEBUG
static char *
safe(char *str)
{
	static char	*ohoh = "<null ptr>";


	if (str != NULL) {
		return (str);
	}
	return (ohoh);
}


static void
print_args(char *tag, int ac, char **av)
{
	int	i;


	dprintf("DEBUG: %s:\n", tag);

	for (i = 0; i < ac; i++) {
		dprintf(" arg[%d] = \"%s\"\n", i, safe(av[i]));
	}
}

#endif	/* DEBUG */
