/*
 *   libdi - CD Audio Player Device Interface Library
 *
 *   Copyright (C) 1993-1996  Ti Kan
 *   E-mail: ti@amb.org
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; 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	6.31 96/04/04";
#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"


#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/Linux ioctl method */
	fbioc_init,			/* FreeBSD ioctl method */
	NULL				/* List terminator */
};


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

/* Application callbacks */
di_client_t		*di_clinfo;

/* Device lock file */
STATIC char		lockfile[FILE_PATH_SZ];


/*
 * 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

	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].mode == 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_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 (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);

	if (lockfile[0] != '\0' && s->mode != MOD_BUSY)
		unlink(lockfile);
}


/*
 * 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;

	fprintf(errfp,
		"\nDumping contents of curstat_t structure at 0x%x:\n", s);
	fprintf(errfp,
		"mode=%d time_dpy=%d flags=0x%x first=%d last=%d tot_trks=%d\n",
		s->mode, s->time_dpy, s->flags,
		s->first_trk, s->last_trk, s->tot_trks);
	fprintf(errfp, "cur_trk=%d cur_idx=%d\n", s->cur_trk, s->cur_idx);
	fprintf(errfp, "tot_msf=%02u:%02u:%02u addr=0x%x\n",
		s->tot_min, s->tot_sec, s->tot_frame, s->tot_addr);
	fprintf(errfp, "cur_tot_msf=%02u:%02u:%02u cur_tot_addr=0x%x\n",
		s->cur_tot_min, s->cur_tot_sec, s->cur_tot_frame,
		s->cur_tot_addr);
	fprintf(errfp, "cur_trk_msf=%02u:%02u:%02u cur_trk_addr=0x%x\n",
		s->cur_tot_min, s->cur_tot_sec, s->cur_tot_frame,
		s->cur_tot_addr);

	for (i = 0; i < MAXTRACK; i++) {
		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;
	}

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

	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);
	fprintf(errfp,
		"onetrk_prog=%d caddy_lk=%d cddb=%d prog_tot=%d prog_cnt=%d\n",
		s->onetrk_prog, s->caddy_lock, s->cddb,
		s->prog_tot, s->prog_cnt);
	fprintf(errfp, "level=%d level_left=%d level_right=%d\n",
		s->level, s->level_left, s->level_right);
	fprintf(errfp, "vendor=\"%-0.8s\" prod=\"%-0.16s\" revnum=\"%-0.4s\"\n",
		s->vendor, s->prod, s->revnum);
}


/*
 * 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];
	static bool_t	dev_cmdline = FALSE,
			force_debug = FALSE;

	if (di_isdemo())
		app_data.device = "(none)";

	if ((fp = fopen(path, "r")) == NULL) {
		if (priv && !di_isdemo()) {
			/* Cannot open system config file. */
			sprintf(errstr, app_data.str_nocfg, path);
			DI_FATAL(errstr);
		}
		return;
	}

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

	if (priv && app_data.debug)
		force_debug = TRUE;

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

		if (!di_isdemo() && sscanf(buf, "device: %s\n", parm) > 0) {
			if (priv && app_data.device != NULL)
				dev_cmdline = TRUE;
			if (!dev_cmdline) {
				if (app_data.device != NULL)
					MEM_FREE(app_data.device);

				app_data.device = (char *) MEM_ALLOC(
					strlen(parm) + 1
				);
				if (app_data.device == NULL) {
					DI_FATAL(app_data.str_nomemory);
					return;
				}
				strcpy(app_data.device, parm);
			}
			continue;
		}
		if (sscanf(buf, "cddbPath: %s\n", parm) > 0) {
			if (!priv || app_data.cddb_path == NULL) {
				if (app_data.cddb_path != NULL)
					MEM_FREE(app_data.cddb_path);

				app_data.cddb_path = (char *) MEM_ALLOC(
					strlen(parm) + 1
				);
				if (app_data.cddb_path == NULL) {
					DI_FATAL(app_data.str_nomemory);
					return;
				}
				strcpy(app_data.cddb_path, parm);
			}
			continue;
		}
		if (sscanf(buf, "cddbDefaultPort: %s\n", parm) > 0) {
			app_data.cddb_port = atoi(parm);
			continue;
		}
		if (sscanf(buf, "cddbTimeOut: %s\n", parm) > 0) {
			app_data.cddb_timeout = atoi(parm);
			continue;
		}
		if (sscanf(buf, "cddbFileMode: %s\n", parm) > 0) {
			if (!priv || app_data.cddb_filemode == NULL) {
				if (app_data.cddb_filemode != NULL)
					MEM_FREE(app_data.cddb_filemode);

				app_data.cddb_filemode = (char *) MEM_ALLOC(
					strlen(parm) + 1
				);
				if (app_data.cddb_filemode == NULL) {
					DI_FATAL(app_data.str_nomemory);
					return;
				}
				strcpy(app_data.cddb_filemode, parm);
			}
			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, "previousThreshold: %s\n", parm) > 0) {
			app_data.prev_threshold = 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, "sampleBlocks: %s\n", parm) > 0) {
			app_data.sample_blks = atoi(parm);
			continue;
		}
		if (sscanf(buf, "solaris2VolumeManager: %s\n", parm) > 0) {
			app_data.sol2_volmgt = stob(parm);
			continue;
		}
		if (sscanf(buf, "showScsiErrMsg: %s\n", parm) > 0) {
			app_data.scsierr_msg = stob(parm);
			continue;
		}
		if (sscanf(buf, "debugMode: %s\n", parm) > 0) {
			if (!force_debug)
				app_data.debug = stob(parm);
			continue;
		}
	}

	fclose(fp);
	MEM_FREE(buf);
	MEM_FREE(parm);
}


/*
 * 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];

	if ((fp = fopen(path, "r")) == NULL) {
		if (priv && !di_isdemo()) {
			/* Cannot open master device-specific
			 * config file.
			 */
			sprintf(errstr, app_data.str_nocfg, path);
			DI_FATAL(errstr);
		}
		return;
	}

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

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

		/* 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, "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, "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 = stob(parm);
				continue;
			}
			if (sscanf(buf, "playAudio12Support: %s\n",
				   parm) > 0) {
				app_data.play12_supp = stob(parm);
				continue;
			}
			if (sscanf(buf, "playAudioMSFSupport: %s\n",
				   parm) > 0) {
				app_data.playmsf_supp = stob(parm);
				continue;
			}
			if (sscanf(buf, "playAudioTISupport: %s\n",
				   parm) > 0) {
				app_data.playti_supp = stob(parm);
				continue;
			}
			if (sscanf(buf, "loadSupport: %s\n",
				   parm) > 0) {
				app_data.load_supp = stob(parm);
				continue;
			}
			if (sscanf(buf, "ejectSupport: %s\n",
				   parm) > 0) {
				app_data.eject_supp = stob(parm);
				continue;
			}
			if (sscanf(buf, "modeSenseSetDBD: %s\n",
				   parm) > 0) {
				app_data.msen_dbd = stob(parm);
				continue;
			}
			if (sscanf(buf, "volumeControlSupport: %s\n",
				   parm) > 0) {
				app_data.mselvol_supp = stob(parm);
				continue;
			}
			if (sscanf(buf, "balanceControlSupport: %s\n",
				   parm) > 0) {
				app_data.balance_supp = stob(parm);
				continue;
			}
			if (sscanf(buf, "channelRouteSupport: %s\n",
				   parm) > 0) {
				app_data.chroute_supp = stob(parm);
				continue;
			}
			if (sscanf(buf, "pauseResumeSupport: %s\n",
				   parm) > 0) {
				app_data.pause_supp = stob(parm);
				continue;
			}
			if (sscanf(buf, "caddyLockSupport: %s\n",
				   parm) > 0) {
				app_data.caddylock_supp = stob(parm);
				continue;
			}
			if (sscanf(buf, "curposFormat: %s\n",
				   parm) > 0) {
				app_data.curpos_fmt = stob(parm);
				continue;
			}
			if (sscanf(buf, "noTURWhenPlaying: %s\n",
				   parm) > 0) {
				app_data.play_notur = stob(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, "channelRoute: %s\n", parm) > 0) {
			app_data.ch_route = atoi(parm);
			continue;
		}
		if (sscanf(buf, "spinDownOnLoad: %s\n", parm) > 0) {
			app_data.load_spindown = stob(parm);
			continue;
		}
		if (sscanf(buf, "playOnLoad: %s\n", parm) > 0) {
			app_data.load_play = stob(parm);
			continue;
		}
		if (sscanf(buf, "ejectOnDone: %s\n", parm) > 0) {
			app_data.done_eject = stob(parm);
			continue;
		}
		if (sscanf(buf, "ejectOnExit: %s\n", parm) > 0) {
			app_data.exit_eject = stob(parm);
			continue;
		}
		if (sscanf(buf, "stopOnExit: %s\n", parm) > 0) {
			app_data.exit_stop = stob(parm);
			continue;
		}
		if (sscanf(buf, "exitOnEject: %s\n", parm) > 0) {
			app_data.eject_exit = stob(parm);
			continue;
		}
		if (sscanf(buf, "closeOnEject: %s\n", parm) > 0) {
			app_data.eject_close = stob(parm);
			continue;
		}
		if (sscanf(buf, "caddyLock: %s\n", parm) > 0) {
			app_data.caddy_lock = stob(parm);
			continue;
		}
		if (sscanf(buf, "repeatMode: %s\n", parm) > 0) {
			app_data.repeat_mode = stob(parm);
			continue;
		}
		if (sscanf(buf, "shuffleMode: %s\n", parm) > 0) {
			app_data.shuffle_mode = stob(parm);
			continue;
		}
		if (sscanf(buf, "internalSpeakerEnable: %s\n", parm) > 0) {
			app_data.internal_spkr = stob(parm);
			continue;
		}
	}

	fclose(fp);
	MEM_FREE(buf);
	MEM_FREE(parm);

	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;
	}
}


/*
 * 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)
{
	int		ret;
	pid_t		cpid;
	waitret_t	stat_val;
	struct stat	stbuf;
	FILE		*fp;
	char		*truestr = "True",
			*falsestr = "False",
			*dirpath,
			errstr[ERR_BUF_SZ];

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

	case -1:
		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) {
				sprintf(errstr, app_data.str_nocfg, path);
				DI_WARNING(errstr);
			}
			return;
		}
		else if (WIFSIGNALED(stat_val)) {
			sprintf(errstr, app_data.str_nocfg, path);
			DI_WARNING(errstr);
			return;
		}
		else
			return;
	}

	DBGPRN(errfp, "\nSetting uid to %d, gid to %d\n",
		get_ouid(), get_ogid());

	/* Force uid and gid to original setting */
	if (setuid(get_ouid()) < 0 || setgid(get_ogid()) < 0)
		exit(1);

#ifndef __VMS
	/*
	 * Make sure directory exists.  If not, create it.
	 */
	dirpath = dirname(path);
	if (stat(dirpath, &stbuf) < 0) {
		if (errno == ENOENT) {
			if (mkdir(dirpath, 0777) < 0)
				exit(2);
		}
		else {
			exit(3);
		}
	}
	else if (!S_ISDIR(stbuf.st_mode)) {
		exit(4);
	}

	/* Make sure directory is searchable by public: This is
	 * necessary because it will be read as root, and over an
	 * NFS mount root privilege is equivalent to "other".
	 */
	chmod(dirpath, 0755);
#endif

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

	/* Open file for writing */
	if ((fp = fopen(path, "w")) == NULL)
		exit(2);

	/* Write banner */
	fprintf(fp, "# xmcd %s Device-Specific Configuration File\n",
		VERSION);
	fprintf(fp, "# %s\n#\n# %s\n# %s\n#\n",
		"Copyright (C) 1993-1996 Ti Kan",
		"See the LIBDIR/xmcd/config/device.cfg file for details",
		"about the parameters.");

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

	if (fclose(fp) != 0)
		exit(1);

	/* Make sure file is readable by public: This is necessary
	 * because it will be read as root, and over an NFS mount
	 * root privilege is equivalent to "other".
	 */
	chmod(path, 0644);

	/* Child exits here */
	exit(0);
}


/*
 * 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 lock file path name.
 *
 * Return:
 *	TRUE if the lock was successful.  If FALSE, then it indicates
 *	that another xmcd process currently has the lock.
 */
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;
	}

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

	DBGPRN(errfp, "Lock 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 {
					close(fd);
					return FALSE;
				}

				close(fd);

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

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

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

			return TRUE;
		}
	}
#else
/* OpenVMS */
	int			ref_cnt = 1;
	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;

	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) { 
		fprintf(errfp,
			"CD audio: Cannot get information on device: %s\n",
			path);
		return FALSE;
	}
	
	if (ref_cnt == 0)
		return TRUE;
	else
		return FALSE;
#endif	/* __VMS */
}


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

	return ("");
}


/*
 * di_vers
 *	Return a text string indicating active module's version number
 *
 * Args:
 *	Nothing.
 *
 * Return:
 *	Version text string.
 */
char *
di_vers(void)
{
	if (ditbl[app_data.di_method].vers != NULL)
		return (ditbl[app_data.di_method].vers());

	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.
 *
 * Return:
 *	Nothing.
 */
void
di_reset_curstat(curstat_t *s, bool_t clear_toc)
{
	sword32_t	i;
	static bool_t	first_time = TRUE;

	s->cur_trk = s->cur_idx = -1;
	s->cur_tot_min = s->cur_tot_sec = s->cur_tot_frame = 0;
	s->cur_trk_min = s->cur_trk_sec = s->cur_trk_frame = 0;
	s->cur_tot_addr = s->cur_trk_addr = 0;
	s->sav_iaddr = 0;
	s->prog_cnt = 0;
	s->program = FALSE;
	s->rptcnt = 0;

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

	if (clear_toc) {
		s->mode = MOD_NODISC;
		s->flags = 0;
		s->first_trk = s->last_trk = -1;
		s->tot_min = s->tot_sec = 0;
		s->tot_trks = 0;
		s->tot_addr = 0;
		s->prog_tot = 0;

		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->playorder[i] = -1;
		}
	}

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

		s->time_dpy = T_ELAPSED;
		s->repeat = s->shuffle = FALSE;
		s->cddb = 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;

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

	for (i = 0; i < MAXTRACK; i++) {
		if (i >= (int) s->prog_tot) {
			s->playorder[i] = -1;
			continue;
		}

		do {
			n = rand() % (int) s->prog_tot;
			for (j = 0; j < i; j++) {
				if (n == s->playorder[j])
					break;
			}
		} while (j < i);

		s->playorder[i] = n;
	}


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

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

		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 (isqrt(v) * 10);
	case 2:
		/* inverse-squared taper */
		return (MAX_VOL - 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);
}


