/*
 *   libdi - CD Audio Player Device Interface Library
 *
 *   Copyright (C) 1993-2001  Ti Kan
 *   E-mail: xmcd@amb.org
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#ifndef LINT
static char *_libdi_c_ident_ = "@(#)libdi.c	7.34 01/07/12";
#endif

#include "common_d/appenv.h"
#include "common_d/patchlevel.h"
#include "common_d/util.h"
#include "libdi_d/libdi.h"
#include "libdi_d/scsipt.h"
#include "libdi_d/slioc.h"
#include "libdi_d/fbioc.h"
#include "libdi_d/aixioc.h"


#define PARM_BUF_SZ	(STR_BUF_SZ * 16)	/* Temporary buffer size */


extern appdata_t	app_data;
extern FILE		*errfp;


/* libdi module init routines */
diinit_tbl_t		diinit[] = {
	{ scsipt_init },		/* SCSI pass-through method */
	{ slioc_init },			/* SunOS/Solaris/Linux ioctl method */
	{ fbioc_init },			/* FreeBSD ioctl method */
	{ aixioc_init },		/* AIX IDE ioctl method */
	{ NULL }			/* List terminator */
};


/* libdi interface calling table */
di_tbl_t		ditbl[MAX_METHODS];

/* Application callbacks */
di_client_t		*di_clinfo;

/* Device list table */
char			**di_devlist;

/* Device lock file */
STATIC char		lockfile[FILE_PATH_SZ] = { '\0' };

/* Device path registration list head */
STATIC di_devreg_t	*di_reghead = NULL;


/***********************
 *  internal routines  *
 ***********************/


/*
 * di_boolstr
 *	Return the string "False" or "True" depending upon the
 *	passed in boolean parameter.
 *
 * Args:
 *	parm - boolean parameter
 *
 * Return:
 *	"False" if parm is FALSE.
 *	"True" is parm is TRUE.
 */
STATIC char *
di_boolstr(bool_t parm)
{
	return ((parm == FALSE) ? "False" : "True");
}


/*
 * di_prncfg
 *	Display configuration information
 *
 * Args:
 *	None.
 *
 * Return:
 *	Nothing.
 */
STATIC void
di_prncfg(void)
{
	(void) fprintf(errfp, "\nBasic parameters:\n");

	(void) fprintf(errfp, "\tlibdir:\t\t\t\t%s\n",
		       app_data.libdir);

	(void) fprintf(errfp, "\nX resources:\n");

	(void) fprintf(errfp, "\tversion:\t\t\t%s\n",
		       app_data.version == NULL ?
				"(undef)" : app_data.version);
	(void) fprintf(errfp, "\tmainWindowMode:\t\t\t%d\n",
		       app_data.main_mode);
	(void) fprintf(errfp, "\tmodeChangeGravity:\t\t%d\n",
		       app_data.modechg_grav);
	(void) fprintf(errfp, "\tnormalMainWidth:\t\t%d\n",
		       app_data.normal_width);
	(void) fprintf(errfp, "\tnormalMainHeight:\t\t%d\n",
		       app_data.normal_height);
	(void) fprintf(errfp, "\tbasicMainWidth:\t\t\t%d\n",
		       app_data.basic_width);
	(void) fprintf(errfp, "\tbasicMainHeight:\t\t%d\n",
		       app_data.basic_height);
	(void) fprintf(errfp, "\tdisplayBlinkOnInterval:\t\t%d\n",
		       app_data.blinkon_interval);
	(void) fprintf(errfp, "\tdisplayBlinkOffInterval:\t%d\n",
		       app_data.blinkoff_interval);
	(void) fprintf(errfp, "\tmainShowFocus:\t\t\t%s\n",
		       di_boolstr(app_data.main_showfocus));
	(void) fprintf(errfp, "\tinstallColormap:\t\t%s\n",
		       di_boolstr(app_data.instcmap));

	(void) fprintf(errfp, "\nCommon parameters:\n");

	(void) fprintf(errfp, "\tdevice:\t\t\t\t%s\n",
		       app_data.device);
	(void) fprintf(errfp, "\tcdinfoPath:\t\t\t%s\n",
		       app_data.cdinfo_path);
	(void) fprintf(errfp, "\tcdinfoFileMode:\t\t\t%s\n",
		       app_data.cdinfo_filemode);
	(void) fprintf(errfp, "\tproxyServer:\t\t\t%s\n",
		       app_data.proxy_server == NULL ?
		           "(undef)" : app_data.proxy_server);
	(void) fprintf(errfp, "\thistoryFileMode:\t\t%s\n",
		       app_data.hist_filemode);
	(void) fprintf(errfp, "\thistoryFileDisable:\t\t%s\n",
		       di_boolstr(app_data.histfile_dsbl));
	(void) fprintf(errfp, "\tcddbCacheTimeout:\t\t%d\n",
		       app_data.cache_timeout);
	(void) fprintf(errfp, "\tserviceTimeout:\t\t\t%d\n",
		       app_data.srv_timeout);
	(void) fprintf(errfp, "\tlocalDiscographyMode:\t\t%d\n",
		       app_data.discog_mode);
	(void) fprintf(errfp, "\tmaximumHistory:\t\t\t%d\n",
		       app_data.cdinfo_maxhist);
	(void) fprintf(errfp, "\tstatusPollInterval:\t\t%d\n",
		       app_data.stat_interval);
	(void) fprintf(errfp, "\tinsertPollInterval:\t\t%d\n",
		       app_data.ins_interval);
	(void) fprintf(errfp, "\ttimeDisplayMode:\t\t%d\n",
		       app_data.timedpy_mode);
	(void) fprintf(errfp, "\ttooltipDelayInterval:\t\t%d\n",
		       app_data.tooltip_delay);
	(void) fprintf(errfp, "\ttooltipActiveInterval:\t\t%d\n",
		       app_data.tooltip_time);
	(void) fprintf(errfp, "\tinsertPollDisable:\t\t%s\n",
		       di_boolstr(app_data.ins_disable));
	(void) fprintf(errfp, "\tpreviousThreshold:\t\t%d\n",
		       app_data.prev_threshold);
	(void) fprintf(errfp, "\tsearchSkipBlocks:\t\t%d\n",
		       app_data.skip_blks);
	(void) fprintf(errfp, "\tsearchPauseInterval:\t\t%d\n",
		       app_data.skip_pause);
	(void) fprintf(errfp, "\tsearchSpeedUpCount:\t\t%d\n",
		       app_data.skip_spdup);
	(void) fprintf(errfp, "\tsearchVolumePercent:\t\t%d\n",
		       app_data.skip_vol);
	(void) fprintf(errfp, "\tsearchMinVolume:\t\t%d\n",
		       app_data.skip_minvol);
	(void) fprintf(errfp, "\tsampleBlocks:\t\t\t%d\n",
		       app_data.sample_blks);
	(void) fprintf(errfp, "\tstartupVolume:\t\t\t%d\n",
		       app_data.startup_vol);
	(void) fprintf(errfp, "\tinternetOffline:\t\t%s\n",
		       di_boolstr(app_data.cdinfo_inetoffln));
	(void) fprintf(errfp, "\tcddbUseProxy:\t\t\t%s\n",
		       di_boolstr(app_data.use_proxy));
	(void) fprintf(errfp, "\tproxyAuthorization:\t\t%s\n",
		       di_boolstr(app_data.proxy_auth));
	(void) fprintf(errfp, "\tautoMusicBrowser:\t\t%s\n",
		       di_boolstr(app_data.auto_musicbrowser));
	(void) fprintf(errfp, "\tshowScsiErrMsg:\t\t\t%s\n",
		       di_boolstr(app_data.scsierr_msg));
	(void) fprintf(errfp, "\tsolaris2VolumeManager:\t\t%s\n",
		       di_boolstr(app_data.sol2_volmgt));
	(void) fprintf(errfp, "\ttooltipEnable:\t\t\t%s\n",
		       di_boolstr(app_data.tooltip_enable));
	(void) fprintf(errfp, "\tremoteControlEnable:\t\t%s\n",
		       di_boolstr(app_data.remote_enb));
	(void) fprintf(errfp, "\tremoteControlLog:\t\t%s\n",
		       di_boolstr(app_data.remote_log));
	(void) fprintf(errfp, "\tdebugLevel:\t\t\t0x%x\n",
		       (int) app_data.debug);
	(void) fprintf(errfp, "\texcludeWords:\t\t\t%s\n",
		       app_data.exclude_words == NULL ?
		           "(undef)" : app_data.exclude_words);

	(void) fprintf(errfp, "\nDevice-specific (privileged) parameters:\n");

	(void) fprintf(errfp, "\tdevnum:\t\t\t\t%d\n",
		       app_data.devnum);
	(void) fprintf(errfp, "\tdeviceList:\t\t\t%s\n",
		       app_data.devlist);
	(void) fprintf(errfp, "\tdeviceInterfaceMethod:\t\t%d\n",
		       app_data.di_method);
	(void) fprintf(errfp, "\tdriveVendorCode:\t\t%d\n",
		       app_data.vendor_code);
	(void) fprintf(errfp, "\tscsiVersionCheck:\t\t%s\n",
		       di_boolstr(app_data.scsiverck));
	(void) fprintf(errfp, "\tnumDiscs:\t\t\t%d\n",
		       app_data.numdiscs);
	(void) fprintf(errfp, "\tmediumChangeMethod:\t\t%d\n",
		       app_data.chg_method);
	(void) fprintf(errfp, "\tscsiAudioVolumeBase:\t\t%d\n",
		       app_data.base_scsivol);
	(void) fprintf(errfp, "\tminimumPlayBlocks:\t\t%d\n",
		       app_data.min_playblks);
	(void) fprintf(errfp, "\tplayAudio10Support:\t\t%s\n",
		       di_boolstr(app_data.play10_supp));
	(void) fprintf(errfp, "\tplayAudio12Support:\t\t%s\n",
		       di_boolstr(app_data.play12_supp));
	(void) fprintf(errfp, "\tplayAudioMSFSupport:\t\t%s\n",
		       di_boolstr(app_data.playmsf_supp));
	(void) fprintf(errfp, "\tplayAudioTISupport:\t\t%s\n",
		       di_boolstr(app_data.playti_supp));
	(void) fprintf(errfp, "\tloadSupport:\t\t\t%s\n",
		       di_boolstr(app_data.load_supp));
	(void) fprintf(errfp, "\tejectSupport:\t\t\t%s\n",
		       di_boolstr(app_data.eject_supp));
	(void) fprintf(errfp, "\tmodeSenseSetDBD:\t\t%s\n",
		       di_boolstr(app_data.msen_dbd));
	(void) fprintf(errfp, "\tvolumeControlSupport:\t\t%s\n",
		       di_boolstr(app_data.mselvol_supp));
	(void) fprintf(errfp, "\tbalanceControlSupport:\t\t%s\n",
		       di_boolstr(app_data.balance_supp));
	(void) fprintf(errfp, "\tchannelRouteSupport:\t\t%s\n",
		       di_boolstr(app_data.chroute_supp));
	(void) fprintf(errfp, "\tpauseResumeSupport:\t\t%s\n",
		       di_boolstr(app_data.pause_supp));
	(void) fprintf(errfp, "\tstrictPauseResume:\t\t%s\n",
		       di_boolstr(app_data.strict_pause_resume));
	(void) fprintf(errfp, "\tplayPausePlay:\t\t\t%s\n",
		       di_boolstr(app_data.play_pause_play));
	(void) fprintf(errfp, "\tcaddyLockSupport:\t\t%s\n",
		       di_boolstr(app_data.caddylock_supp));
	(void) fprintf(errfp, "\tcurposFormat:\t\t\t%s\n",
		       di_boolstr(app_data.curpos_fmt));
	(void) fprintf(errfp, "\tnoTURWhenPlaying:\t\t%s\n",
		       di_boolstr(app_data.play_notur));
	(void) fprintf(errfp, "\ttocLBA:\t\t\t\t%s\n",
		       di_boolstr(app_data.toc_lba));
	(void) fprintf(errfp, "\tsubChannelLBA:\t\t\t%s\n",
		       di_boolstr(app_data.subq_lba));
	(void) fprintf(errfp, "\tdriveBlockSize:\t\t\t%d\n",
		       app_data.drv_blksz);
	(void) fprintf(errfp, "\tspinUpInterval:\t\t\t%d\n",
		       app_data.spinup_interval);

	(void) fprintf(errfp,
		       "\nDevice-specific (user-modifiable) parameters:\n");

	(void) fprintf(errfp, "\tvolumeControlTaper:\t\t%d\n",
		       app_data.vol_taper);
	(void) fprintf(errfp, "\tchannelRoute:\t\t\t%d\n",
		       app_data.ch_route);
	(void) fprintf(errfp, "\tspinDownOnLoad:\t\t\t%s\n",
		       di_boolstr(app_data.load_spindown));
	(void) fprintf(errfp, "\tplayOnLoad:\t\t\t%s\n",
		       di_boolstr(app_data.load_play));
	(void) fprintf(errfp, "\tejectOnDone:\t\t\t%s\n",
		       di_boolstr(app_data.done_eject));
	(void) fprintf(errfp, "\texitOnExit:\t\t\t%s\n",
		       di_boolstr(app_data.done_exit));
	(void) fprintf(errfp, "\tejectOnExit:\t\t\t%s\n",
		       di_boolstr(app_data.exit_eject));
	(void) fprintf(errfp, "\tstopOnExit:\t\t\t%s\n",
		       di_boolstr(app_data.exit_stop));
	(void) fprintf(errfp, "\texitOnEject:\t\t\t%s\n",
		       di_boolstr(app_data.eject_exit));
	(void) fprintf(errfp, "\tcloseOnEject:\t\t\t%s\n",
		       di_boolstr(app_data.eject_close));
	(void) fprintf(errfp, "\tcaddyLock:\t\t\t%s\n",
		       di_boolstr(app_data.caddy_lock));
	(void) fprintf(errfp, "\trepeatMode:\t\t\t%s\n",
		       di_boolstr(app_data.repeat_mode));
	(void) fprintf(errfp, "\tshuffleMode:\t\t\t%s\n",
		       di_boolstr(app_data.shuffle_mode));
	(void) fprintf(errfp, "\tmultiPlay:\t\t\t%s\n",
		       di_boolstr(app_data.multi_play));
	(void) fprintf(errfp, "\treversePlay:\t\t\t%s\n",
		       di_boolstr(app_data.reverse));

	(void) fprintf(errfp, "\n");
}


/*
 * di_parse_devlist
 *	Parse the app_data.devlist string and create the di_devlist array.
 *
 * Args:
 *	None.
 *
 * Return:
 *	Nothing.
 */
STATIC void
di_parse_devlist(void)
{
	char		*p,
			*q;
	int		i,
			n,
			listsz;
	curstat_t	*s = di_clinfo->curstat_addr();

	if (app_data.chg_method < 0 || app_data.chg_method >= MAX_CHG_METHODS)
		/* Fix-up in case of mis-configuration */
		app_data.chg_method = CHG_NONE;

	n = app_data.numdiscs;

	switch (app_data.chg_method) {
	case CHG_SCSI_MEDCHG:
		n = 2;
		/*FALLTHROUGH*/

	case CHG_SCSI_LUN:
		/* SCSI LUN addressing method */
		listsz = n * sizeof(char *);

		di_devlist = (char **) MEM_ALLOC("di_devlist", listsz);
		if (di_devlist == NULL) {
			DI_FATAL(app_data.str_nomemory);
			return;
		}
		(void) memset(di_devlist, 0, listsz);

		p = q = app_data.devlist;
		if (p == NULL || *p == '\0') {
			DI_FATAL(app_data.str_devlist_undef);
			return;
		}

		for (i = 0; i < n; i++) {
			q = strchr(p, ';');

			if (q == NULL && i < (n - 1)) {
				DI_FATAL(app_data.str_devlist_count);
				return;
			}

			if (q != NULL)
				*q = '\0';

			if (!util_newstr(&di_devlist[i], p)) {
				DI_FATAL(app_data.str_nomemory);
				return;
			}

			if (q != NULL)
				*q = ';';

			p = q + 1;
		}

		/* In this mode, closeOnEject must be True */
		app_data.eject_close = TRUE;
		break;

	case CHG_OS_IOCTL:
	case CHG_NONE:
	default:
		if (app_data.chg_method == CHG_OS_IOCTL) {
			/* In this mode, closeOnEject must be True */
			app_data.eject_close = TRUE;
		}
		else {
			/* Some fix-ups in case of mis-configuration */
			app_data.numdiscs = 1;
		}

		if (app_data.devlist == NULL) {
			if (!util_newstr(&app_data.devlist, app_data.device)) {
				DI_FATAL(app_data.str_nomemory);
				return;
			}
		}
		else if (strcmp(app_data.devlist, app_data.device) != 0) {
			MEM_FREE(app_data.devlist);
			if (!util_newstr(&app_data.devlist, app_data.device)) {
				DI_FATAL(app_data.str_nomemory);
				return;
			}
		}

		di_devlist = (char **) MEM_ALLOC("di_devlist", sizeof(char *));
		if (di_devlist == NULL) {
			DI_FATAL(app_data.str_nomemory);
			return;
		}
		di_devlist[0] = NULL;
		if (!util_newstr(&di_devlist[0], app_data.devlist)) {
			DI_FATAL(app_data.str_nomemory);
			return;
		}
		break;
	}

	/* Initialize to the first device */
	s->curdev = di_devlist[0];
}


/***********************
 *   public routines   *
 ***********************/


/*
 * di_init
 *	Top-level function to initialize the libdi modules.
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_init(di_client_t *clp)
{
	int		i;
	curstat_t	*s = clp->curstat_addr();

	di_clinfo = clp;

#if defined(DI_SCSIPT) && defined(DEMO_ONLY)
	app_data.di_method = DI_SCSIPT;
#else
	/* Sanity check */
	if (app_data.di_method < 0 || app_data.di_method >= MAX_METHODS) {
		DI_FATAL(app_data.str_nomethod);
		return;
	}
#endif

	/* Parse the device list and set up structure */
	di_parse_devlist();

	/* Check string lengths.  This is to avoid subsequent
	 * string buffer overflows.
	 */
	if (((int) (strlen(app_data.str_notrom) +
		    strlen(app_data.device)) >= ERR_BUF_SZ) ||
	    ((int) (strlen(app_data.str_notscsi2) +
		    strlen(app_data.device)) >= ERR_BUF_SZ) ||
	    ((int) (strlen(app_data.str_staterr) +
		    strlen(app_data.device)) >= ERR_BUF_SZ) ||
	    ((int) (strlen(app_data.str_noderr) +
		    strlen(app_data.device)) >= ERR_BUF_SZ) ||
	    ((int) (strlen(app_data.str_nocfg) + 12 +
		    strlen(app_data.libdir)) >= ERR_BUF_SZ)) {
		DI_FATAL(app_data.str_longpatherr);
		return;
	}

	lockfile[0] = '\0';

	/* Initialize the libdi modules */
	for (i = 0; i < MAX_METHODS; i++) {
		if (diinit[i].init != NULL)
			diinit[i].init(s, &ditbl[i]);
	}

	/* Sanity check again */
	if (ditbl[app_data.di_method].methodstr == NULL) {
		DI_FATAL(app_data.str_nomethod);
		return;
	}
}


/*
 * di_check_disc
 *	Check if disc is ready for use
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	TRUE - success
 *	FALSE - failure
 */
bool_t
di_check_disc(curstat_t *s)
{
	if (ditbl[app_data.di_method].check_disc != NULL)
		return (ditbl[app_data.di_method].check_disc(s));

	return FALSE;
}


/*
 * di_status_upd
 *	Force update of playback status
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_status_upd(curstat_t *s)
{
	if (ditbl[app_data.di_method].status_upd != NULL)
		ditbl[app_data.di_method].status_upd(s);
}


/*
 * di_lock
 *	Caddy lock function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *	enable - whether to enable/disable caddy lock
 *
 * Return:
 *	Nothing.
 */
void
di_lock(curstat_t *s, bool_t enable)
{
	if (ditbl[app_data.di_method].lock != NULL)
		ditbl[app_data.di_method].lock(s, enable);
}


/*
 * di_repeat
 *	Repeat mode function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *	enable - whether to enable/disable repeat mode
 *
 * Return:
 *	Nothing.
 */
void
di_repeat(curstat_t *s, bool_t enable)
{
	if (ditbl[app_data.di_method].repeat != NULL)
		ditbl[app_data.di_method].repeat(s, enable);
}


/*
 * di_shuffle
 *	Shuffle mode function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *	enable - whether to enable/disable shuffle mode
 *
 * Return:
 *	Nothing.
 */
void
di_shuffle(curstat_t *s, bool_t enable)
{
	if (ditbl[app_data.di_method].shuffle != NULL)
		ditbl[app_data.di_method].shuffle(s, enable);
}


/*
 * di_load_eject
 *	CD caddy load and eject function.  If disc caddy is not
 *	loaded, it will attempt to load it.  Otherwise, it will be
 *	ejected.
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_load_eject(curstat_t *s)
{
	if (ditbl[app_data.di_method].load_eject != NULL)
		ditbl[app_data.di_method].load_eject(s);
}


/*
 * di_ab
 *	A->B segment play mode function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_ab(curstat_t *s)
{
	if (ditbl[app_data.di_method].ab != NULL)
		ditbl[app_data.di_method].ab(s);
}


/*
 * di_sample
 *	Sample play mode function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_sample(curstat_t *s)
{
	if (ditbl[app_data.di_method].sample != NULL)
		ditbl[app_data.di_method].sample(s);
}


/*
 * di_level
 *	Audio volume control function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *	level - The volume level to set to
 *	drag - Whether this is an update due to the user dragging the
 *		volume control slider thumb.  If this is FALSE, then
 *		a final volume setting has been found.
 *
 * Return:
 *	Nothing.
 */
void
di_level(curstat_t *s, byte_t level, bool_t drag)
{
	if (ditbl[app_data.di_method].level != NULL)
		ditbl[app_data.di_method].level(s, level, drag);
}


/*
 * di_play_pause
 *	Audio playback and pause function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_play_pause(curstat_t *s)
{
	if (ditbl[app_data.di_method].play_pause != NULL)
		ditbl[app_data.di_method].play_pause(s);
}


/*
 * di_stop
 *	Stop function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *	stop_disc - Whether to actually spin down the disc or just
 *		update status.
 *
 * Return:
 *	Nothing.
 */
void
di_stop(curstat_t *s, bool_t stop_disc)
{
	if (ditbl[app_data.di_method].stop != NULL)
		ditbl[app_data.di_method].stop(s, stop_disc);
}


/*
 * di_chgdisc
 *	Change disc function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_chgdisc(curstat_t *s)
{
	if (ditbl[app_data.di_method].chgdisc != NULL)
		ditbl[app_data.di_method].chgdisc(s);
}


/*
 * di_prevtrk
 *	Previous track function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_prevtrk(curstat_t *s)
{
	if (ditbl[app_data.di_method].prevtrk != NULL)
		ditbl[app_data.di_method].prevtrk(s);
}


/*
 * di_nexttrk
 *	Next track function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_nexttrk(curstat_t *s)
{
	if (ditbl[app_data.di_method].nexttrk != NULL)
		ditbl[app_data.di_method].nexttrk(s);
}


/*
 * di_previdx
 *	Previous index function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_previdx(curstat_t *s)
{
	if (ditbl[app_data.di_method].previdx != NULL)
		ditbl[app_data.di_method].previdx(s);
}


/*
 * di_nextidx
 *	Next index function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_nextidx(curstat_t *s)
{
	if (ditbl[app_data.di_method].nextidx != NULL)
		ditbl[app_data.di_method].nextidx(s);
}


/*
 * di_rew
 *	Search-rewind function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_rew(curstat_t *s, bool_t start)
{
	if (ditbl[app_data.di_method].rew != NULL)
		ditbl[app_data.di_method].rew(s, start);
}


/*
 * di_ff
 *	Search-fast-forward function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_ff(curstat_t *s, bool_t start)
{
	if (ditbl[app_data.di_method].ff != NULL)
		ditbl[app_data.di_method].ff(s, start);
}


/*
 * di_warp
 *	Track warp function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_warp(curstat_t *s)
{
	if (ditbl[app_data.di_method].warp != NULL)
		ditbl[app_data.di_method].warp(s);
}


/*
 * di_route
 *	Channel routing function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_route(curstat_t *s)
{
	if (ditbl[app_data.di_method].route != NULL)
		ditbl[app_data.di_method].route(s);
}


/*
 * di_mute_on
 *	Mute audio function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_mute_on(curstat_t *s)
{
	if (ditbl[app_data.di_method].mute_on != NULL)
		ditbl[app_data.di_method].mute_on(s);
}


/*
 * di_mute_off
 *	Un-mute audio function
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_mute_off(curstat_t *s)
{
	if (ditbl[app_data.di_method].mute_off != NULL)
		ditbl[app_data.di_method].mute_off(s);
}


/*
 * di_start
 *	Start the SCSI pass-through module.
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_start(curstat_t *s)
{
	if (app_data.debug & DBG_ALL)
		di_prncfg();	/* Print debug information */

	if (ditbl[app_data.di_method].start != NULL)
		ditbl[app_data.di_method].start(s);
}


/*
 * di_icon
 *	Handler for main window iconification/de-iconification
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *	iconified - Whether the main window is iconified
 *
 * Return:
 *	Nothing.
 */
void
di_icon(curstat_t *s, bool_t iconified)
{
	if (ditbl[app_data.di_method].icon != NULL)
		ditbl[app_data.di_method].icon(s, iconified);
}


/*
 * di_halt
 *	Shut down the SCSI pass-through and vendor-unique modules.
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing.
 */
void
di_halt(curstat_t *s)
{
	if (ditbl[app_data.di_method].halt != NULL)
		ditbl[app_data.di_method].halt(s);

	di_devunlock(s);
}


/*
 * di_dump_curstat
 *	Display contents of the the curstat_t structure.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
void
di_dump_curstat(curstat_t *s)
{
	int		i;
	di_devreg_t	*rp;

	(void) fprintf(errfp,
		       "\nDumping contents of curstat_t structure at 0x%lx:\n",
		       (unsigned long) s);
	(void) fprintf(errfp,
		       "curdev=%s\nfirst_disc=%d last_disc=%d\n",
		       s->curdev, s->first_disc, s->last_disc);
	(void) fprintf(errfp,
		       "cur_disc=%d prev_disc=%d\n",
		       s->cur_disc, s->prev_disc);
	(void) fprintf(errfp,
		       "mode=%d time_dpy=%d flags=0x%x\n",
		       s->mode, s->time_dpy, s->flags);
	(void) fprintf(errfp,
		       "first_trk=%d last_trk=%d tot_trks=%d\n",
		       s->first_trk, s->last_trk, s->tot_trks);
	(void) fprintf(errfp, "cur_trk=%d cur_idx=%d\n",
		       s->cur_trk, s->cur_idx);
	(void) fprintf(errfp,
		      "discpos_tot.msf=%02u:%02u:%02u discpos_tot.addr=0x%x\n",
		       s->discpos_tot.min, s->discpos_tot.sec,
		       s->discpos_tot.frame, s->discpos_tot.addr);
	(void) fprintf(errfp,
		       "curpos_tot.msf=%02u:%02u:%02u curpos_tot.addr=0x%x\n",
		       s->curpos_tot.min, s->curpos_tot.sec,
		       s->curpos_tot.frame, s->curpos_tot.addr);
	(void) fprintf(errfp,
		       "curpos_trk.msf=%02u:%02u:%02u curpos_trk.addr=0x%x\n",
		       s->curpos_tot.min, s->curpos_tot.sec,
		       s->curpos_tot.frame, s->curpos_tot.addr);

	for (i = 0; i < MAXTRACK; i++) {
		(void) fprintf(errfp,
			"[%d] trkno=%d addr=0x%x msf=%02d:%02d:%02d type=%d\n",
			       i, s->trkinfo[i].trkno, s->trkinfo[i].addr,
			       s->trkinfo[i].min, s->trkinfo[i].sec,
			       s->trkinfo[i].frame, s->trkinfo[i].type);
		if (s->trkinfo[i].trkno == LEAD_OUT_TRACK)
			break;
	}

	(void) fprintf(errfp, "playorder=");
	for (i = 0; i < (int) s->prog_tot; i++)
		(void) fprintf(errfp, "%d ",
			s->trkinfo[s->playorder[i]].trkno);
	(void) fprintf(errfp, "\n");

	(void) fprintf(errfp,
		"sav_iaddr=0x%x rptcnt=%d repeat=%d shuffle=%d program=%d\n",
		s->sav_iaddr, s->rptcnt, s->repeat, s->shuffle, s->program);
	(void) fprintf(errfp,
		"onetrk_prog=%d caddy_lk=%d prog_tot=%d prog_cnt=%d\n",
		s->onetrk_prog, s->caddy_lock,
		s->prog_tot, s->prog_cnt);
	(void) fprintf(errfp,
		"chgrscan=%d level=%d level_left=%d level_right=%d\n",
		s->chgrscan, s->level, s->level_left, s->level_right);
	(void) fprintf(errfp,
		"vendor=\"%-8s\" prod=\"%-16s\" revnum=\"%-4s\"\n",
		s->vendor, s->prod, s->revnum);

	(void) fprintf(errfp, "\nDevice registry: %s\n",
		di_reghead == NULL ? "NULL" : "");
	for (rp = di_reghead; rp != NULL; rp = rp->next)
		(void) fprintf(errfp, "[%d]\t%s\n", rp->fd, rp->path);
}


/*
 * di_common_parmload
 *	Load the common configuration file and initialize parameters.
 *
 * Args:
 *	path - Path name to the file to load.
 *	priv - Whether the privileged keywords are to be recognized.
 *
 * Return:
 *	Nothing.
 */
void
di_common_parmload(char *path, bool_t priv)
{
	FILE		*fp;
	char		*buf,
			*parm,
			errstr[ERR_BUF_SZ],
			trypath[FILE_PATH_SZ];
	struct utsname	*un;
	bool_t		notry2;
	static bool_t	force_debug;
#ifndef __VMS
	pid_t		cpid;
	waitret_t	stat_val;
	int		ret,
			pfd[2];

	un = util_get_uname();

	if (priv && di_isdemo())
		app_data.device = "(sim1)";

	if (PIPE(pfd) < 0) {
		DBGPRN(DBG_GEN)(errfp,
			"di_common_parmload: pipe failed (errno=%d)\n",
			errno);
		if (priv && !di_isdemo()) {
			(void) sprintf(errstr, app_data.str_nocfg, path);
			DI_FATAL(errstr);
		}
		return;
	}
	
	switch (cpid = FORK()) {
	case 0:
		/* Child */

		/* Close un-needed pipe descriptor */
		(void) close(pfd[0]);

		/* Force uid and gid to original setting */
		if (!util_set_ougid()) {
			(void) close(pfd[1]);
			exit(1);
		}

		notry2 = FALSE;
		if (((int) strlen(path) + (int) strlen(un->nodename) + 2)
		    > FILE_PATH_SZ) {
			DBGPRN(DBG_GEN)(errfp, "NOTICE: %s: %s\n",
				"Host-specific config files not used",
				"Name too long");
			(void) strcpy(trypath, path);
			notry2 = TRUE;
		}
		else
			/* Try host-specific config file first */
			(void) sprintf(trypath, "%s-%s", path, un->nodename);

		DBGPRN(DBG_GEN)(errfp,
			"Loading common parameters: %s\n", trypath);
		fp = fopen(trypath, "r");

		if (fp == NULL && !notry2) {
			DBGPRN(DBG_GEN)(errfp, "Cannot open %s\n", trypath);

			/* Try generic config file */
			(void) strcpy(trypath, path);

			DBGPRN(DBG_GEN)(errfp,
				"Loading common parameters: %s\n",
				trypath);
			fp = fopen(trypath, "r");
		}

		if (fp == NULL) {
			DBGPRN(DBG_GEN)(errfp, "Cannot open %s\n", trypath);
			(void) close(pfd[1]);

			if (priv && !di_isdemo())
				exit(3);
			exit(0);
		}

		/* Allocate temporary buffer */
		if ((buf = (char *) MEM_ALLOC("buf", PARM_BUF_SZ)) == NULL) {
			(void) close(pfd[1]);
			(void) fclose(fp);
			exit(2);
		}

		while (fgets(buf, PARM_BUF_SZ, fp) != NULL) {
			/* Skip comments and blank lines */
			if (buf[0] == '#' || buf[0] == '!' || buf[0] == '\n')
				continue;
			(void) write(pfd[1], buf, strlen(buf));
		}
		(void) write(pfd[1], ".\n", 2);

		(void) close(pfd[1]);
		(void) fclose(fp);

		MEM_FREE(buf);
		exit(0);
		/*NOTREACHED*/

	case -1:
		DBGPRN(DBG_GEN)(errfp,
			"di_common_parmload: fork failed (errno=%d)\n",
			errno);
		(void) close(pfd[0]);
		(void) close(pfd[1]);

		if (priv && !di_isdemo()) {
			(void) sprintf(errstr, app_data.str_nocfg, path);
			DI_FATAL(errstr);
		}
		return;

	default:
		/* Parent */

		/* Close un-needed pipe descriptor */
		(void) close(pfd[1]);

		if ((fp = fdopen(pfd[0], "r")) == NULL) {
			DBGPRN(DBG_GEN)(errfp,
			    "di_common_parmload: read pipe fdopen failed\n");
			if (priv && !di_isdemo()) {
				/* Cannot open pipe */
				(void) sprintf(errstr, app_data.str_nocfg,
					       path);
				DI_FATAL(errstr);
			}
			return;
		}
		break;
	}
#else
	un = util_get_uname();

	notry2 = FALSE;
	if (((int) strlen(path) + (int) strlen(un->nodename) + 2)
	    > FILE_PATH_SZ) {
		DBGPRN(DBG_GEN)(errfp, "NOTICE: %s: %s\n",
			"Host-specific config files not used",
			"Name too long");
		(void) strcpy(trypath, path);
		notry2 = TRUE;
	}
	else
		/* Try host-specific config file first */
		(void) sprintf(trypath, "%s-%s", path, un->nodename);

	DBGPRN(DBG_GEN)(errfp, "Loading common parameters: %s\n", trypath);
	fp = fopen(trypath, "r");

	if (fp == NULL && !notry2) {
		DBGPRN(DBG_GEN)(errfp, "Cannot open %s\n", trypath);

		/* Try generic config file */
		(void) strcpy(trypath, path);

		DBGPRN(DBG_GEN)(errfp,
			"Loading common parameters: %s\n", trypath);
		fp = fopen(trypath, "r");
	}

	if (fp == NULL) {
		DBGPRN(DBG_GEN)(errfp, "Cannot open %s\n", trypath);
		if (priv && !di_isdemo()) {
			/* Cannot open system config file. */
			(void) sprintf(errstr, app_data.str_nocfg, path);
			DI_FATAL(errstr);
		}
		return;
	}
#endif	/* __VMS */

	/* Allocate temporary buffer */
	buf = (char *) MEM_ALLOC("buf", PARM_BUF_SZ);
	parm = (char *) MEM_ALLOC("parmbuf", PARM_BUF_SZ);
	if (buf == NULL || parm == NULL) {
		DI_FATAL(app_data.str_nomemory);
		return;
	}

	force_debug = (bool_t) (priv && (app_data.debug & DBG_ALL));

	/* Read in common parameters */
	while (fgets(buf, PARM_BUF_SZ, fp) != NULL) {
		/* Skip comments and blank lines */
		if (buf[0] == '#' || buf[0] == '!' || buf[0] == '\n')
			continue;

		/* Done */
		if (buf[0] == '.' && buf[1] == '\n')
			break;

		if (priv && sscanf(buf, "device: %[^\n]\n", parm) > 0) {
			if (di_isdemo())
				continue;

			/* If app_data.device is not NULL, then it means
			 * the user had specified a device on the command
			 * line, and that should take precedence.
			 */
			if (app_data.device == NULL) {
				static char	device[FILE_PATH_SZ];

				(void) sprintf(device, "%.255s", parm);
				app_data.device = device;
			}
			continue;
		}
		if (sscanf(buf, "cdinfoPath: %[^\n]\n", parm) > 0) {
			if (!util_newstr(&app_data.cdinfo_path, parm)) {
				DI_FATAL(app_data.str_nomemory);
				return;
			}
			continue;
		}
		if (sscanf(buf, "proxyServer: %[^\n]\n", parm) > 0) {
			if (!util_newstr(&app_data.proxy_server, parm)) {
				DI_FATAL(app_data.str_nomemory);
				return;
			}
			continue;
		}
		if (sscanf(buf, "cddbCacheTimeout: %s\n", parm) > 0) {
			app_data.cache_timeout = atoi(parm);
			continue;
		}
		if (sscanf(buf, "serviceTimeout: %s\n", parm) > 0) {
			app_data.srv_timeout = atoi(parm);
			continue;
		}
		if (sscanf(buf, "localDiscographyMode: %s\n", parm) > 0) {
			app_data.discog_mode = atoi(parm);
			continue;
		}
		if (sscanf(buf, "maximumHistory: %s\n", parm) > 0) {
			app_data.cdinfo_maxhist = atoi(parm);
			continue;
		}
		if (sscanf(buf, "internetOffline: %s\n", parm) > 0) {
			app_data.cdinfo_inetoffln = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "cddbUseProxy: %s\n", parm) > 0) {
			app_data.use_proxy = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "proxyAuthorization: %s\n", parm) > 0) {
			app_data.proxy_auth = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "autoMusicBrowser: %s\n", parm) > 0) {
			app_data.auto_musicbrowser = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "cdinfoFileMode: %s\n", parm) > 0) {
			if (!util_newstr(&app_data.cdinfo_filemode, parm)) {
				DI_FATAL(app_data.str_nomemory);
				return;
			}
			continue;
		}
		if (sscanf(buf, "historyFileMode: %s\n", parm) > 0) {
			if (app_data.hist_filemode == NULL) {
			    if (!util_newstr(&app_data.hist_filemode, parm)) {
				DI_FATAL(app_data.str_nomemory);
				return;
			    }
			}
			continue;
		}
		if (sscanf(buf, "statusPollInterval: %s\n", parm) > 0) {
			app_data.stat_interval = atoi(parm);
			continue;
		}
		if (sscanf(buf, "insertPollInterval: %s\n", parm) > 0) {
			app_data.ins_interval = atoi(parm);
			continue;
		}
		if (sscanf(buf, "insertPollDisable: %s\n", parm) > 0) {
			app_data.ins_disable = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "previousThreshold: %s\n", parm) > 0) {
			app_data.prev_threshold = atoi(parm);
			continue;
		}
		if (sscanf(buf, "sampleBlocks: %s\n", parm) > 0) {
			app_data.sample_blks = atoi(parm);
			continue;
		}
		if (sscanf(buf, "timeDisplayMode: %s\n", parm) > 0) {
			app_data.timedpy_mode = atoi(parm);
			continue;
		}
		if (sscanf(buf, "solaris2VolumeManager: %s\n", parm) > 0) {
			app_data.sol2_volmgt = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "showScsiErrMsg: %s\n", parm) > 0) {
			app_data.scsierr_msg = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "curfileEnable: %s\n", parm) > 0) {
			app_data.write_curfile = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "tooltipEnable: %s\n", parm) > 0) {
			app_data.tooltip_enable = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "tooltipDelayInterval: %s\n", parm) > 0) {
			app_data.tooltip_delay = atoi(parm);
			continue;
		}
		if (sscanf(buf, "tooltipActiveInterval: %s\n", parm) > 0) {
			app_data.tooltip_time = atoi(parm);
			continue;
		}
		if (sscanf(buf, "historyFileDisable: %s\n", parm) > 0) {
			app_data.histfile_dsbl = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "remoteControlEnable: %s\n", parm) > 0) {
			app_data.remote_enb = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "remoteControlLog: %s\n", parm) > 0) {
			app_data.remote_log = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "debugLevel: %s\n", parm) > 0) {
			if (!force_debug)
				app_data.debug = (word32_t) atoi(parm);
			continue;
		}
		if (sscanf(buf, "excludeWords: %[^\n]\n", parm) > 0) {
			if (!util_newstr(&app_data.exclude_words, parm)) {
				DI_FATAL(app_data.str_nomemory);
				return;
			}
			continue;
		}
	}

	MEM_FREE(buf);
	MEM_FREE(parm);

	(void) fclose(fp);

	/* In case of error */
	if (app_data.device == NULL || app_data.device[0] == '\0')
		app_data.device = "/dev/cdrom";
	if (app_data.timedpy_mode < 0 ||
	    app_data.timedpy_mode >= TIMEDPY_MAX_MODES)
		app_data.timedpy_mode = 0;
	if (app_data.tooltip_delay < 0)
		app_data.tooltip_delay = 1000;
	if (app_data.tooltip_time < 0)
		app_data.tooltip_delay = 3000;
	if (app_data.cdinfo_maxhist < 0)
		app_data.cdinfo_maxhist = 0;
	if (app_data.cache_timeout <= 0)
		app_data.cache_timeout = DEF_CACHE_TIMEOUT;
	if (app_data.srv_timeout <= 0)
		app_data.srv_timeout = DEF_SRV_TIMEOUT;

#ifndef __VMS
	while ((ret = WAITPID(cpid, &stat_val, 0)) != cpid) {
		if (ret < 0)
			break;
	}
	if (ret == cpid) {
	    if (WIFEXITED(stat_val)) {
		if (WEXITSTATUS(stat_val) != 0) {
			DBGPRN(DBG_GEN)(errfp,
			    "di_common_parmload: child exited (status=%d)\n",
			    WEXITSTATUS(stat_val));
			(void) sprintf(errstr, app_data.str_nocfg, path);
			if (priv && !di_isdemo()) {
				DI_FATAL(errstr);
			}
			else {
				DI_WARNING(errstr);
			}
		}
	    }
	    else if (WIFSIGNALED(stat_val)) {
		DBGPRN(DBG_GEN)(errfp, "di_common_parmload: child killed.\n");
		(void) sprintf(errstr, app_data.str_nocfg, path);
		if (priv && !di_isdemo()) {
			DI_FATAL(errstr);
		}
		else {
			DI_WARNING(errstr);
		}
	    }
	}
#endif
}


/*
 * di_devspec_parmload
 *	Load the specified device-specific configuration file and
 *	initialize parameters.
 *
 * Args:
 *	path - Path name to the file to load.
 *	priv - Whether the privileged keywords are to be recognized.
 *
 * Return:
 *	Nothing.
 */
void
di_devspec_parmload(char *path, bool_t priv)
{
	FILE		*fp;
	char		*buf,
			*parm,
			errstr[ERR_BUF_SZ],
			trypath[FILE_PATH_SZ];
	struct utsname	*un;
	bool_t		notry2;
#ifndef __VMS
	pid_t		cpid;
	waitret_t	stat_val;
	int		ret,
			pfd[2];

	un = util_get_uname();

	if (priv && di_isdemo()) {
		char	*cp;

		cp = "(sim1);(sim2);(sim3);(sim4);(sim5);(sim6);(sim7);(sim8)";
		if (!util_newstr(&app_data.devlist, cp)) {
			DI_FATAL(app_data.str_nomemory);
			return;
		}
		app_data.numdiscs = 8;
		app_data.chg_method = CHG_SCSI_LUN;
		app_data.multi_play = TRUE;
	}

	if (PIPE(pfd) < 0) {
		DBGPRN(DBG_GEN)(errfp,
			"di_devspec_parmload: pipe failed (errno=%d)\n",
			errno);
		if (priv && !di_isdemo()) {
			(void) sprintf(errstr, app_data.str_nocfg, path);
			DI_FATAL(errstr);
		}
		return;
	}

	switch (cpid = FORK()) {
	case 0:
		/* Child */

		/* Close un-needed pipe descriptor */
		(void) close(pfd[0]);

		/* Force uid and gid to original setting */
		if (!util_set_ougid()) {
			(void) close(pfd[1]);
			exit(1);
		}

		notry2 = FALSE;
		if (((int) strlen(path) + (int) strlen(un->nodename) + 2)
		    > FILE_PATH_SZ) {
			DBGPRN(DBG_GEN)(errfp, "NOTICE: %s: %s\n",
				"Host-specific config files not used",
				"Name too long");
			(void) strcpy(trypath, path);
			notry2 = TRUE;
		}
		else
			/* Try host-specific config file first */
			(void) sprintf(trypath, "%s-%s", path, un->nodename);

		DBGPRN(DBG_GEN)(errfp,
			"Loading device-specific parameters: %s\n", trypath);

		fp = fopen(trypath, "r");

		if (fp == NULL && !notry2) {
			DBGPRN(DBG_GEN)(errfp, "Cannot open %s\n", trypath);

			/* Try generic config file */
			(void) strcpy(trypath, path);

			DBGPRN(DBG_GEN)(errfp,
				"Loading device-specific parameters: %s\n",
				trypath);
			fp = fopen(trypath, "r");
		}

		if (fp == NULL) {
			DBGPRN(DBG_GEN)(errfp, "Cannot open %s\n", trypath);
			(void) close(pfd[1]);

			if (priv && !di_isdemo())
				exit(3);
			exit(0);
		}

		/* Allocate temporary buffer */
		if ((buf = (char *) MEM_ALLOC("buf", PARM_BUF_SZ)) == NULL) {
			(void) close(pfd[1]);
			(void) fclose(fp);
			exit(2);
		}

		while (fgets(buf, PARM_BUF_SZ, fp) != NULL) {
			/* Skip comments and blank lines */
			if (buf[0] == '#' || buf[0] == '!' || buf[0] == '\n')
				continue;
			(void) write(pfd[1], buf, strlen(buf));
		}
		(void) write(pfd[1], ".\n", 2);

		(void) close(pfd[1]);
		(void) fclose(fp);

		MEM_FREE(buf);
		exit(0);
		/*NOTREACHED*/

	case -1:
		DBGPRN(DBG_GEN)(errfp,
			"di_devspec_parmload: fork failed (errno=%d)\n",
			errno);
		(void) close(pfd[0]);
		(void) close(pfd[1]);

		if (priv && !di_isdemo()) {
			(void) sprintf(errstr, app_data.str_nocfg, path);
			DI_FATAL(errstr);
		}
		return;

	default:
		/* Parent */

		/* Close un-needed pipe descriptor */
		(void) close(pfd[1]);

		if ((fp = fdopen(pfd[0], "r")) == NULL) {
			DBGPRN(DBG_GEN)(errfp,
			    "di_devspec_parmload: read pipe fdopen failed\n");
			if (priv && !di_isdemo()) {
				/* Cannot open pipe */
				(void) sprintf(errstr, app_data.str_nocfg,
					       path);
				DI_FATAL(errstr);
			}
			return;
		}
		break;
	}
#else
	un = util_get_uname();

	notry2 = FALSE;
	if (((int) strlen(path) + (int) strlen(un->nodename) + 2)
	    > FILE_PATH_SZ) {
		DBGPRN(DBG_GEN)(errfp, "NOTICE: %s: %s\n",
			"Host-specific config files not used",
			"Name too long");
		(void) strcpy(trypath, path);
		notry2 = TRUE;
	}
	else
		/* Try host-specific config file first */
		(void) sprintf(trypath, "%s-%s", path, un->nodename);

	DBGPRN(DBG_GEN)(errfp,
		"Loading device-specific parameters: %s\n", trypath);
	fp = fopen(trypath, "r");

	if (fp == NULL && !notry2) {
		DBGPRN(DBG_GEN)(errfp, "Cannot open %s\n", trypath);

		/* Try generic config file */
		(void) strcpy(trypath, path);

		DBGPRN(DBG_GEN)(errfp,
			"Loading device-specific parameters: %s\n", trypath);
		fp = fopen(trypath, "r");
	}

	if (fp == NULL) {
		DBGPRN(DBG_GEN)(errfp, "Cannot open %s\n", trypath);
		if (priv && !di_isdemo()) {
			/* Cannot open master device-specific
			 * config file.
			 */
			(void) sprintf(errstr, app_data.str_nocfg, path);
			DI_FATAL(errstr);
		}
		return;
	}
#endif	/* __VMS */

	/* Allocate temporary buffer */
	buf = (char *) MEM_ALLOC("buf", PARM_BUF_SZ);
	parm = (char *) MEM_ALLOC("parmbuf", PARM_BUF_SZ);
	if (buf == NULL || parm == NULL) {
		DI_FATAL(app_data.str_nomemory);
		return;
	}

	/* Read in device-specific parameters */
	while (fgets(buf, PARM_BUF_SZ, fp) != NULL) {
		/* Skip comments and blank lines */
		if (buf[0] == '#' || buf[0] == '!' || buf[0] == '\n')
			continue;

		/* Done */
		if (buf[0] == '.' && buf[1] == '\n')
			break;

		/* These are privileged parameters and users
		 * cannot overide them in their .xmcdcfg file.
		 */
		if (priv) {
			if (sscanf(buf, "logicalDriveNumber: %s\n",
				   parm) > 0) {
				app_data.devnum = atoi(parm);
				continue;
			}
			if (sscanf(buf, "deviceList: %[^\n]\n", parm) > 0) {
				if (!util_newstr(&app_data.devlist, parm)) {
					DI_FATAL(app_data.str_nomemory);
					return;
				}
				continue;
			}
			if (sscanf(buf, "deviceInterfaceMethod: %s\n",
				   parm) > 0) {
				app_data.di_method = atoi(parm);
				continue;
			}
			if (sscanf(buf, "driveVendorCode: %s\n",
				   parm) > 0) {
				app_data.vendor_code = atoi(parm);
				continue;
			}
			if (sscanf(buf, "scsiVersionCheck: %s\n",
				   parm) > 0) {
				app_data.scsiverck = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "numDiscs: %s\n", parm) > 0) {
				app_data.numdiscs = atoi(parm);
				continue;
			}
			if (sscanf(buf, "mediumChangeMethod: %s\n",
				   parm) > 0) {
				app_data.chg_method = atoi(parm);
				continue;
			}
			if (sscanf(buf, "scsiAudioVolumeBase: %s\n",
				   parm) > 0) {
				app_data.base_scsivol = atoi(parm);
				continue;
			}
			if (sscanf(buf, "minimumPlayBlocks: %s\n",
				   parm) > 0) {
				app_data.min_playblks = atoi(parm);
				continue;
			}
			if (sscanf(buf, "playAudio10Support: %s\n",
				   parm) > 0) {
				app_data.play10_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "playAudio12Support: %s\n",
				   parm) > 0) {
				app_data.play12_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "playAudioMSFSupport: %s\n",
				   parm) > 0) {
				app_data.playmsf_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "playAudioTISupport: %s\n",
				   parm) > 0) {
				app_data.playti_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "loadSupport: %s\n",
				   parm) > 0) {
				app_data.load_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "ejectSupport: %s\n",
				   parm) > 0) {
				app_data.eject_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "modeSenseSetDBD: %s\n",
				   parm) > 0) {
				app_data.msen_dbd = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "volumeControlSupport: %s\n",
				   parm) > 0) {
				app_data.mselvol_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "balanceControlSupport: %s\n",
				   parm) > 0) {
				app_data.balance_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "channelRouteSupport: %s\n",
				   parm) > 0) {
				app_data.chroute_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "pauseResumeSupport: %s\n",
				   parm) > 0) {
				app_data.pause_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "strictPauseResume: %s\n",
				   parm) > 0) {
				app_data.strict_pause_resume = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "playPausePlay: %s\n",
				   parm) > 0) {
				app_data.play_pause_play = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "caddyLockSupport: %s\n",
				   parm) > 0) {
				app_data.caddylock_supp = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "curposFormat: %s\n",
				   parm) > 0) {
				app_data.curpos_fmt = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "noTURWhenPlaying: %s\n",
				   parm) > 0) {
				app_data.play_notur = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "tocLBA: %s\n",
				   parm) > 0) {
				app_data.toc_lba = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "subChannelLBA: %s\n",
				   parm) > 0) {
				app_data.subq_lba = util_stob(parm);
				continue;
			}
			if (sscanf(buf, "driveBlockSize: %s\n", parm) > 0) {
				app_data.drv_blksz = atoi(parm);
				continue;
			}
			if (sscanf(buf, "spinUpInterval: %s\n", parm) > 0) {
				app_data.spinup_interval = atoi(parm);
				continue;
			}
		}

		/* These are general parameters that can be
		 * changed by the user.
		 */
		if (sscanf(buf, "volumeControlTaper: %s\n", parm) > 0) {
			app_data.vol_taper = atoi(parm);
			continue;
		}
		if (sscanf(buf, "startupVolume: %s\n", parm) > 0) {
			app_data.startup_vol = atoi(parm);
			continue;
		}
		if (sscanf(buf, "channelRoute: %s\n", parm) > 0) {
			app_data.ch_route = atoi(parm);
			continue;
		}
		if (sscanf(buf, "searchSkipBlocks: %s\n", parm) > 0) {
			app_data.skip_blks = atoi(parm);
			continue;
		}
		if (sscanf(buf, "searchPauseInterval: %s\n", parm) > 0) {
			app_data.skip_pause = atoi(parm);
			continue;
		}
		if (sscanf(buf, "searchSpeedUpCount: %s\n", parm) > 0) {
			app_data.skip_spdup = atoi(parm);
			continue;
		}
		if (sscanf(buf, "searchVolumePercent: %s\n", parm) > 0) {
			app_data.skip_vol = atoi(parm);
			continue;
		}
		if (sscanf(buf, "searchMinVolume: %s\n", parm) > 0) {
			app_data.skip_minvol = atoi(parm);
			continue;
		}
		if (sscanf(buf, "spinDownOnLoad: %s\n", parm) > 0) {
			app_data.load_spindown = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "playOnLoad: %s\n", parm) > 0) {
			app_data.load_play = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "ejectOnDone: %s\n", parm) > 0) {
			app_data.done_eject = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "exitOnDone: %s\n", parm) > 0) {
			app_data.done_exit = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "ejectOnExit: %s\n", parm) > 0) {
			app_data.exit_eject = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "stopOnExit: %s\n", parm) > 0) {
			app_data.exit_stop = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "exitOnEject: %s\n", parm) > 0) {
			app_data.eject_exit = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "closeOnEject: %s\n", parm) > 0) {
			app_data.eject_close = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "caddyLock: %s\n", parm) > 0) {
			app_data.caddy_lock = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "repeatMode: %s\n", parm) > 0) {
			app_data.repeat_mode = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "shuffleMode: %s\n", parm) > 0) {
			app_data.shuffle_mode = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "multiPlay: %s\n", parm) > 0) {
			app_data.multi_play = util_stob(parm);
			continue;
		}
		if (sscanf(buf, "reversePlay: %s\n", parm) > 0) {
			app_data.reverse = util_stob(parm);
			continue;
		}
	}

	MEM_FREE(buf);
	MEM_FREE(parm);

	(void) fclose(fp);

#ifndef __VMS
	while ((ret = WAITPID(cpid, &stat_val, 0)) != cpid) {
		if (ret < 0)
			break;
	}
	if (ret == cpid) {
	    if (WIFEXITED(stat_val)) {
		if (WEXITSTATUS(stat_val) != 0) {
			DBGPRN(DBG_GEN)(errfp,
			    "di_devspec_parmload: child exited (status=%d)\n",
			    WEXITSTATUS(stat_val));
			(void) sprintf(errstr, app_data.str_nocfg, path);
			if (priv && !di_isdemo()) {
				DI_FATAL(errstr);
			}
			else {
				DI_WARNING(errstr);
			}
		}
	    }
	    else if (WIFSIGNALED(stat_val)) {
		DBGPRN(DBG_GEN)(errfp, "di_devspec_parmload: child killed.\n");
		(void) sprintf(errstr, app_data.str_nocfg, path);
		if (priv && !di_isdemo()) {
			DI_FATAL(errstr);
		}
		else {
			DI_WARNING(errstr);
		}
	    }
	}
#endif

	if (!priv) {
		/* If the drive does not support software eject, then we
		 * can't lock the caddy.
		 */
		if (!app_data.eject_supp) {
			app_data.caddylock_supp = FALSE;
			app_data.done_eject = FALSE;
			app_data.exit_eject = FALSE;
		}

		/* playOnLoad overrides spinDownOnLoad */
		if (app_data.load_play)
			app_data.load_spindown = FALSE;

		/* If the drive does not support locking the caddy, don't
		 * attempt to lock it.
		 */
		if (!app_data.caddylock_supp)
			app_data.caddy_lock = FALSE;

		/* If the drive does not support software volume
		 * control, then it can't support the balance
		 * control either.  Also, force the volume control
		 * taper selector to the linear position.
		 */
		if (!app_data.mselvol_supp) {
			app_data.balance_supp = FALSE;
			app_data.vol_taper = 0;
		}

		/* If the drive does not support channel routing,
		 * force the channel routing setting to normal.
		 */
		if (!app_data.chroute_supp)
			app_data.ch_route = 0;

		/* Other fix-ups as needed */
		if (app_data.numdiscs <= 0)
			app_data.numdiscs = 1;

		if (app_data.numdiscs == 1) {
			app_data.multi_play = app_data.reverse = FALSE;
		}

		if (app_data.startup_vol > 100)
			app_data.startup_vol = 100;
		else if (app_data.startup_vol < -1)
			app_data.startup_vol = -1;

		if (app_data.drv_blksz == 0 ||
		    (app_data.drv_blksz % 512) != 0)
			app_data.drv_blksz = STD_CDROM_BLKSZ;
	}
}


/*
 * di_devspec_parmsave
 *	Save the device-specific configuration parameters to file.
 *
 * Args:
 *	path - Path name to the file to save to.
 *
 * Return:
 *	Nothing.
 */
void
di_devspec_parmsave(char *path)
{
	FILE		*fp;
	char		*truestr = "True",
			*falsestr = "False";
#ifndef __VMS
	int		ret;
	pid_t		cpid;
	waitret_t	stat_val;
	char		*dirpath,
			errstr[ERR_BUF_SZ];

	/* Fork child to perform actual I/O */
	switch (cpid = FORK()) {
	case 0:
		/* Child process */
		break;

	case -1:
		DBGPRN(DBG_GEN)(errfp,
			"di_devspec_parmsave: fork failed (errno=%d)\n",
			errno);
		return;

	default:
		/* Parent process: wait for child to exit */
		while ((ret = WAITPID(cpid, &stat_val, 0)) != cpid) {
			if (ret < 0)
				return;
		}

		if (WIFEXITED(stat_val)) {
		    if (WEXITSTATUS(stat_val) != 0) {
			DBGPRN(DBG_GEN)(errfp,
			    "di_devspec_parmsave: child exited (status=%d)\n",
			    WEXITSTATUS(stat_val));
			(void) sprintf(errstr, app_data.str_nocfg,
				       path);
			DI_WARNING(errstr);
		    }
		    return;
		}
		else if (WIFSIGNALED(stat_val)) {
		    DBGPRN(DBG_GEN)(errfp,
			"di_devspec_parmsave: child killed.\n");
		    (void) sprintf(errstr, app_data.str_nocfg, path);
		    DI_WARNING(errstr);
		    return;
		}
		else
		    return;
	}

	/* Force uid and gid to original setting */
	if (!util_set_ougid())
		exit(1);

	dirpath = util_dirname(path);
	if (!util_mkdir(dirpath, 0755)) {
		DBGPRN(DBG_GEN)(errfp,
			"di_devspec_parmsave: cannot mkdir %s.\n", dirpath);
		exit(2);
	}
#endif	/* __VMS */

	DBGPRN(DBG_GEN)(errfp, "Writing device-specific file %s\n", path);

	/* Open file for writing */
	if ((fp = fopen(path, "w")) == NULL) {
		DBGPRN(DBG_GEN)(errfp,
			"di_devspec_parmsave: cannot open %s.\n", path);
#ifdef __VMS
		return;
#else
		exit(2);
#endif
	}

	/* Write banner */
	(void) fprintf(fp, "# xmcd %s.%s Device-Specific Configuration File\n",
		       VERSION_MAJ, VERSION_MIN);
	(void) fprintf(fp, "# %s\n#\n# %s %s%s\n# %s\n#\n",
		       COPYRIGHT,
		       "See the", app_data.libdir, "/config/device.cfg",
		       "file for details about the parameters.");

	/* Write only user-changeable parameters */
	(void) fprintf(fp, "volumeControlTaper:\t%d\n",
		       app_data.vol_taper);
	(void) fprintf(fp, "channelRoute:\t\t%d\n",
		       app_data.ch_route);
	(void) fprintf(fp, "spinDownOnLoad:\t\t%s\n",
		       app_data.load_spindown ? truestr : falsestr);
	(void) fprintf(fp, "playOnLoad:\t\t%s\n",
		       app_data.load_play ? truestr : falsestr);
	(void) fprintf(fp, "ejectOnDone:\t\t%s\n",
		       app_data.done_eject ? truestr : falsestr);
	(void) fprintf(fp, "exitOnDone:\t\t%s\n",
		       app_data.done_exit ? truestr : falsestr);
	(void) fprintf(fp, "ejectOnExit:\t\t%s\n",
		       app_data.exit_eject ? truestr : falsestr);
	(void) fprintf(fp, "stopOnExit:\t\t%s\n",
		       app_data.exit_stop ? truestr : falsestr);
	(void) fprintf(fp, "exitOnEject:\t\t%s\n",
		       app_data.eject_exit ? truestr : falsestr);
	(void) fprintf(fp, "caddyLock:\t\t%s\n",
		       app_data.caddy_lock ? truestr : falsestr);
	(void) fprintf(fp, "repeatMode:\t\t%s\n",
		       app_data.repeat_mode ? truestr : falsestr);
	(void) fprintf(fp, "shuffleMode:\t\t%s\n",
		       app_data.shuffle_mode ? truestr : falsestr);
	(void) fprintf(fp, "multiPlay:\t\t%s\n",
		       app_data.multi_play ? truestr : falsestr);
	(void) fprintf(fp, "reversePlay:\t\t%s\n",
		       app_data.reverse ? truestr : falsestr);

	if (fclose(fp) != 0) {
#ifdef __VMS
		return;
#else
		exit(1);
#endif
	}

	(void) chmod(path, 0644);

	/* Child exits here */
#ifndef __VMS
	exit(0);
#endif
	/*NOTREACHED*/
}


/*
 * di_devlock
 *	Create a lock to prevent another cooperating CD audio process
 *	from accessing the same CD-ROM device.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *	path - The CD-ROM device node path name.
 *
 * Return:
 *	TRUE if the lock was successful.  If FALSE, then it indicates
 *	that another xmcd process currently has the lock.
 */
/*ARGSUSED*/
bool_t
di_devlock(curstat_t *s, char *path)
{
#ifndef __VMS
/* UNIX */
	int		fd;
	pid_t		pid,
			mypid;
	char		buf[12];
	struct stat	stbuf;

	if (di_isdemo())
		return TRUE;	/* No locking needed in demo mode */

	if (stat(path, &stbuf) < 0) {
		if (errno == ENOENT)
			/* If the device node is missing it is probably
			 * due to dynamic node creation/removal
			 * on some platforms based on presence of media.
			 * In this case, let the lock call succeed without
			 * actually setting a lock.  Otherwise the status
			 * would erroneously become "cd busy".
			 */
			return TRUE;
		else
			return FALSE;
	}

	(void) sprintf(lockfile, "%s/lock.%x", TEMP_DIR, (int) stbuf.st_rdev);

	DBGPRN(DBG_GEN)(errfp, "\nLock file: %s\n", lockfile);

	mypid = getpid();

	for (;;) {
		fd = open(lockfile, O_CREAT | O_EXCL | O_WRONLY);
		if (fd < 0) {
			if (errno == EEXIST) {
				if ((fd = open(lockfile, O_RDONLY)) < 0)
					return FALSE;

				if (read(fd, buf, 12) > 0)
					pid = (pid_t) atoi(buf);
				else {
					(void) close(fd);
					return FALSE;
				}

				(void) close(fd);

				if (pid == mypid)
					/* Our own lock */
					return TRUE;

				if (pid <= 0 ||
				    (kill(pid, 0) < 0 && errno == ESRCH)) {
					/* Pid died, steal its lockfile */
					(void) UNLINK(lockfile);
				}
				else {
					/* Pid still running: clash */
					return FALSE;
				}
			}
			else
				return FALSE;
		}
		else {
			(void) sprintf(buf, "%d\n", (int) mypid);
			(void) write(fd, buf, strlen(buf));

			(void) close(fd);
			(void) chmod(lockfile, 0644);

			return TRUE;
		}
	}
#else
/* OpenVMS */
	static char		ref_cnt;
	int			status;

	struct {
		short		buflen;
		short		code;
		char		*bufadr;
		int		*length;
		int		terminator;
	} itemlist = {
		4,
		DVI$_REFCNT,
		&ref_cnt,
		NULL,
		0
	};

        struct dsc$descriptor	dev_nam_desc;

	if (path == NULL)
		return TRUE;

	ref_cnt = 1;

	dev_nam_desc.dsc$b_class = DSC$K_CLASS_S;
	dev_nam_desc.dsc$b_dtype = DSC$K_DTYPE_T;
	dev_nam_desc.dsc$a_pointer = path;
	dev_nam_desc.dsc$w_length = strlen(path);

	status = SYS$GETDVIW(0, 0, &dev_nam_desc, &itemlist, 0, 0, 0, 0);

	if (status != 1) { 
		(void) fprintf(errfp,
		       "CD audio: Cannot get information on device: %s\n",
			    path);
		return FALSE;
	}
	
	return ((bool_t) (ref_cnt == 0));
#endif	/* __VMS */
}


/*
 * di_devunlock
 *	Unlock the lock that was created with di_devlock().
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
/*ARGSUSED*/
void
di_devunlock(curstat_t *s)
{
#ifndef __VMS
	if (lockfile[0] != '\0' && s->devlocked)
		(void) UNLINK(lockfile);
#endif
}


/*
 * di_devreg
 *	Register a path name in the devicepath registry.
 *
 * Args:
 *	fd - The device file descriptor
 *	path - The device path name
 *
 * Return:
 *	TRUE - success
 *	FALSE - failure
 */
bool_t
di_devreg(int fd, char *path)
{
	di_devreg_t	*rp;

	if (path == NULL || *path == '\0')
		return FALSE;

	rp = (di_devreg_t *)(void *) MEM_ALLOC(
		"di_devreg_t",
		sizeof(di_devreg_t)
	);
	if (rp == NULL) {
		DI_FATAL(app_data.str_nomemory);
		return FALSE;
	}

	rp->path = NULL;
	if (!util_newstr(&rp->path, path)) {
		DI_FATAL(app_data.str_nomemory);
		return FALSE;
	}

	rp->fd = fd;
	rp->next = di_reghead;
	di_reghead = rp;
	DBGPRN(DBG_DEVIO)(errfp, "\nRegistered %s\n", path);

	return TRUE;
}


/*
 * di_devunreg
 *	Unregister a device from the device path registry.
 *
 * Args:
 *	fd - The device file descriptor.
 *
 * Return:
 *	Nothing.
 */
void
di_devunreg(int fd)
{
	di_devreg_t	*rp,
			*prp;

	for (rp = di_reghead, prp = NULL; rp != NULL; rp = rp->next) {
		if (rp->fd == fd) {
			if (prp == NULL)
				di_reghead = rp->next;
			else
				prp->next = rp->next;

			DBGPRN(DBG_DEVIO)(errfp,
				"\nUnregistered %s\n", rp->path);

			MEM_FREE(rp->path);
			MEM_FREE(rp);
			break;
		}
		prp = rp;
	}
}


/*
 * di_devgetpath
 *	Search the device path registry and return the path name
 *	string corresponding to the specified file descriptor.
 *
 * Args:
 *	fd - The device file descriptor.
 *
 * Return:
 *	Pointer to the device path string, or "(unknown)" if not found
 *	in the registered path list.
 */
char *
di_devgetpath(int fd)
{
	static di_devreg_t	*rp = NULL;

	if (rp != NULL && rp->fd == fd)
		return (rp->path);

	for (rp = di_reghead; rp != NULL; rp = rp->next) {
		if (rp->fd == fd)
			break;
	}

	return (rp == NULL ? "(unknown)" : rp->path);
}


/*
 * di_methodstr
 *	Return a text string indicating the current operating mode.
 *
 * Args:
 *	Nothing.
 *
 * Return:
 *	Mode text string.
 */
char *
di_methodstr(void)
{
	if (ditbl[app_data.di_method].methodstr != NULL)
		return (ditbl[app_data.di_method].methodstr());

	return ("");
}


/*
 * di_isdemo
 *	Query if this is a demo-only version of the CD player.
 *
 * Args:
 *	Nothing.
 *
 * Return:
 *	TRUE - demo-only version.
 *	FALSE - real version.
 */
bool_t
di_isdemo(void)
{
#ifdef DEMO_ONLY
	return TRUE;
#else
	return FALSE;
#endif
}


/*
 * di_curtrk_pos
 *	Return the trkinfo table offset location of the current playing
 *	CD track.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Integer offset into the trkinfo table, or -1 if not currently
 *	playing audio.
 */
int
di_curtrk_pos(curstat_t *s)
{
	int	i;

	if ((int) s->cur_trk <= 0)
		return -1;

	i = (int) s->cur_trk - 1;

	if (s->trkinfo[i].trkno == s->cur_trk)
		return (i);

	for (i = 0; i < MAXTRACK; i++) {
		if (s->trkinfo[i].trkno == s->cur_trk)
			return (i);
	}
	return -1;
}


/*
 * di_curprog_pos
 *	Return an integer representing the position of the current
 *	program or shuffle mode playing order (0 = first, 1 = second, ...).
 *	This routine should be used only when in program or shuffle play
 *	mode.
 *
 * Arg:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	An integer representing the position of the current program
 *	or shuffle mode playing order, or -1 if not in the appropriate mode.
 */
int
di_curprog_pos(curstat_t *s)
{
	return ((int) s->playorder[s->prog_cnt]);
}


/*
 * di_reset_curstat
 *	Reset the curstat_t structure to initial defaults.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *	clear_toc - Whether the trkinfo CD table-of-contents 
 *		should be cleared.
 *	eject - Whether the medium is being ejected
 *
 * Return:
 *	Nothing.
 */
void
di_reset_curstat(curstat_t *s, bool_t clear_toc, bool_t eject)
{
	sword32_t	i;
	static bool_t	first_time = TRUE;

	s->cur_trk = s->cur_idx = -1;
	s->curpos_tot.min = s->curpos_tot.sec = s->curpos_tot.frame = 0;
	s->curpos_trk.min = s->curpos_trk.sec = s->curpos_trk.frame = 0;
	s->curpos_tot.addr = s->curpos_trk.addr = 0;
	s->sav_iaddr = 0;
	s->prog_cnt = 0;

	if (s->onetrk_prog) {
		s->onetrk_prog = FALSE;
		PROGCLEAR(s);
	}

	if (clear_toc) {
		s->flags = 0;
		s->segplay = SEGP_NONE;
		s->first_trk = s->last_trk = -1;
		s->discpos_tot.min = s->discpos_tot.sec = 0;
		s->tot_trks = 0;
		s->discpos_tot.addr = 0;
		s->prog_tot = 0;
		s->program = FALSE;

		for (i = 0; i < MAXTRACK; i++) {
			s->trkinfo[i].trkno = -1;
			s->trkinfo[i].min = 0;
			s->trkinfo[i].sec = 0;
			s->trkinfo[i].frame = 0;
			s->trkinfo[i].addr = 0;
			s->trkinfo[i].type = TYP_AUDIO;
			s->playorder[i] = -1;
		}
	}

	if (eject) {
		s->mode = MOD_NODISC;
		s->rptcnt = 0;
	}

	if (first_time) {
		/* These are to be initialized only once */
		first_time = FALSE;

		s->first_disc = 1;
		s->last_disc = app_data.numdiscs;
		s->cur_disc = app_data.reverse ? s->last_disc : s->first_disc;
		s->prev_disc = -1;
		s->time_dpy = T_ELAPSED_TRACK;
		s->repeat = s->shuffle = FALSE;
		s->rptcnt = 0;
		s->level = 0;
		s->caddy_lock = FALSE;
		s->vendor[0] = '\0';
		s->prod[0] = '\0';
		s->revnum[0] = '\0';
	}
}


/*
 * di_reset_shuffle
 *	Recompute a new shuffle play sequence.  Updates the playorder
 *	table in the curstat_t structure.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
void
di_reset_shuffle(curstat_t *s)
{
	sword32_t	i,
			j,
			n,
			x;

	srand((unsigned) time(NULL));
	s->prog_cnt = 0;

	/* Initialize the play-list with only audio tracks */
	n = 0;
	for (i = 0; i < (int) s->tot_trks; i++) {
		if (s->trkinfo[i].type == TYP_AUDIO)
			s->playorder[n++] = i;
	}

	s->prog_tot = (byte_t) n;

	/* Initialize rest of list */
	for (; n < MAXTRACK; n++)
		s->playorder[n] = -1;

	/*
	 * Now shuffle the playlist by swapping random pairs:
	 * this is the most efficient alogorithm for a linear
	 * distribution.
	 */
	for (i = (int) s->prog_tot - 1; i > 0; i--) {
		j = rand() % (i + 1);
		if (j != i) {
			x = s->playorder[i];
			s->playorder[i] = s->playorder[j];
			s->playorder[j] = x;
		}
	}

	if (app_data.debug & DBG_GEN) {
		(void) fprintf(errfp, "\nShuffle tracks: ");

		for (i = 0; i < (int) s->prog_tot; i++)
			(void) fprintf(errfp, "%d ",
				       s->trkinfo[s->playorder[i]].trkno);

		(void) fprintf(errfp, "\n");
	}
}


/*
 * di_taper_vol
 *	Translate the volume level based on the configured taper
 *	characteristics.
 *
 * Args:
 *	v - The linear volume value.
 *
 * Return:
 *	The curved volume value.
 */
int
di_taper_vol(int v)
{
	switch (app_data.vol_taper) {
	case 1:
		/* squared taper */
		return (SQR(v) / MAX_VOL);
	case 2:
		/* inverse-squared taper */
		return (MAX_VOL - (SQR(MAX_VOL - v) / MAX_VOL));
	case 0:
	default:
		/* linear taper */
		return (v);
	}
	/*NOTREACHED*/
}


/*
 * di_untaper_vol
 *	Translate the volume level based on the configured taper
 *	characteristics.
 *
 * Args:
 *	v - The curved volume value.
 *
 * Return:
 *	The linear volume value.
 */
int
di_untaper_vol(int v)
{
	switch (app_data.vol_taper) {
	case 1:
		/* squared taper */
		return (util_isqrt(v) * 10);
	case 2:
		/* inverse-squared taper */
		return (MAX_VOL - util_isqrt(SQR(MAX_VOL) - (MAX_VOL * v)));
	case 0:
	default:
		/* linear taper */
		return (v);
	}
	/*NOTREACHED*/
}


/*
 * di_scale_vol
 *	Scale logical audio volume value (0-100) to an 8-bit value
 *	(0-0xff) range.
 *
 * Args:
 *	v - The logical volume value
 *
 * Return:
 *	The scaled volume value
 */
int
di_scale_vol(int v)
{
	/* Convert logical audio volume value to 8-bit volume */
	return ((v * (0xff - app_data.base_scsivol) / MAX_VOL) +
	        app_data.base_scsivol);
}


/*
 * di_unscale_vol
 *	Scale an 8-bit audio volume parameter value (0-0xff) to the
 *	logical volume value (0-100).
 *
 * Args:
 *	v - The 8-bit volume value
 *
 * Return:
 *	The logical volume value
 */
int
di_unscale_vol(int v)
{
	register int	val;

	/* Convert 8-bit audio volume value to logical volume */
	val = (v - app_data.base_scsivol) * MAX_VOL /
	      (0xff - app_data.base_scsivol);

	return ((val < 0) ? 0 : val);
}


/*
 * di_xlate_blk
 *	Translate the CD-ROM drive's native LBA/length into an
 *	LBA/length that is based on 2048-byte blocks.  This is
 *	needed to support drives that are configured to a block
 *	size other than 2048 (such as Sun or SGI).
 *
 * Args:
 *	addr - The LBA address from the drive
 *
 * Return:
 *	The converted LBA.
 */
word32_t
di_xlate_blk(word32_t addr)
{
	if (app_data.drv_blksz == STD_CDROM_BLKSZ)
		return (addr);
	else if (app_data.drv_blksz < STD_CDROM_BLKSZ)
		return (addr / (STD_CDROM_BLKSZ / app_data.drv_blksz));
	else
		return (addr * (app_data.drv_blksz / STD_CDROM_BLKSZ));
}


/*
 * di_unxlate_blk
 *	Translate an LBA/length that is based on 2048-byte blocks
 *	into the CD-ROM drive's native LBA/length.  This is needed
 *	to support drives that are configured to a block size other
 *	than 2048 (such as Sun or SGI).
 *
 * Args:
 *	addr - The LBA address from the drive
 *
 * Return:
 *	The converted LBA
 */
word32_t
di_unxlate_blk(word32_t addr)
{
	if (app_data.drv_blksz == STD_CDROM_BLKSZ)
		return (addr);
	else if (app_data.drv_blksz < STD_CDROM_BLKSZ)
		return (addr * (STD_CDROM_BLKSZ / app_data.drv_blksz));
	else
		return (addr / (app_data.drv_blksz / STD_CDROM_BLKSZ));
}


