/*
 *   cdinfo - CD Information Management 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 *_cdinfo_x_c_ident_ = "@(#)cdinfo_x.c	7.119 01/07/14";
#endif

#ifdef __VMS
typedef char *	caddr_t;
#endif

#define _CDINFO_INTERN	/* Expose internal function protos in cdinfo.h */

#include "common_d/appenv.h"
#include "common_d/patchlevel.h"
#include "common_d/util.h"
#include "cdinfo_d/cdinfo.h"


#define MAX_ENV_LEN	(STR_BUF_SZ * 16)

extern appdata_t	app_data;
extern FILE		*errfp;
extern cdinfo_incore_t	cdinfo_db;		/* Incore CD info structure */
extern cdinfo_client_t	*cdinfo_clinfo;		/* Client info */
extern cdinfo_cddb_t	*cdinfo_cddbp;		/* Opened CDDB handle */
extern w_ent_t		*cdinfo_discog,		/* Local discog menu ptr */
			*cdinfo_scddb;		/* Search CDDB menu ptr */
extern bool_t		ischild;		/* Is a child process */

STATIC pid_t		child_pid = 0;		/* Child pid */
STATIC char		curfile[FILE_PATH_SZ] = { '\0' };
						/* Current disc info file */


/***********************
 *   private routines  *
 ***********************/


/*
 * cdinfo_onalrm
 *	CD info query timeout alarm signal handler.
 *
 * Args:
 *	signo - The signal number
 *
 * Return:
 *	Nothing.
 */
STATIC void
cdinfo_onalrm(int signo)
{
	(void) signal(signo, SIG_IGN);
	DBGPRN(DBG_CDI)(errfp, "\ncdinfo_onalrm: Query timeout\n");
	cdinfo_load_cancel();
	(void) signal(signo, cdinfo_onalrm);
}


/*
 * cdinfo_mkpath
 *	Convert a string to replace all occurrences of non-alpha and
 *	non-digit characters with an underscore.  Multiple such characters
 *	are only substituted with one underscore.  This is used to create
 *	names suitable for use as a path name.  This function allocates
 *	memory which must be freed by the caller via MEM_FREE when done.
 *
 * Args:
 *	str - Input string
 *	len - Max output string length including terminating null character
 *	
 * Return:
 *	Converted string, or NULL on failure.
 */
STATIC char *
cdinfo_mkpath(char *str, int len)
{
	int	i;
	char	*buf,
		*cp,
		*cp2;

	if ((buf = (char *) MEM_ALLOC("cdinfo_mkpath", len)) == NULL)
		return NULL;

	cp = buf;
	cp2 = str;
	for (i = 0; *cp2 != '\0' && i < (len - 1); i++) {
		/* Replace non-alpha and non-digit chars with an underscore */

		if (isalnum((int) *cp2))
			*cp++ = *cp2;
		else if (cp > buf && *(cp-1) != '_')
			*cp++ = '_';
		else
			i--;

		cp2++;
	}
	*cp = '\0';

	return (buf);
}


/*
 * cdinfo_forkwait
 *	Function that forks a child and sets the uid and gid to the original
 *	invoking user.  The parent then waits for the child to do some
 *	work and exit, and return an exit code in the location pointed to
 *	by retcode.  The caller can check the return of this function to
 *	determine whether it's the parent or child.  If SYNCHRONOUS is
 *	defined, this function always returns FALSE and no child is forked.
 *
 * Args:
 *	retcode - Location that the parent is to return an status code of
 *		  the child.
 *
 * Return:
 *	TRUE - Parent process: child has exited and the status code is
 *	       returned in retcode.
 *	FALSE - Child process: Now running with original uid/gid.
 */
bool_t
cdinfo_forkwait(cdinfo_ret_t *retcode)
{
#ifdef SYNCHRONOUS
	*retcode = 0;
	return FALSE;
#else
	int		i,
			ret;
	unsigned int	oa;
	pid_t		cpid;
	waitret_t	stat_val;
	void		(*oh)(int);

	/* Fork child and make child do actual work */
	switch (cpid = FORK()) {
	case 0:
		/* Child process */
		ischild = TRUE;

		/* Exit on receipt of SIGTERM */
		(void) signal(SIGTERM, cdinfo_onterm);

		/* Ignore SIGALRM */
		(void) signal(SIGALRM, SIG_IGN);

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

		return FALSE;

	case -1:
		*retcode = CDINFO_SET_CODE(FORK_ERR, errno);
		return TRUE;

	default:
		/* Parent process: wait for child to exit */
		child_pid = cpid;

		oh = signal(SIGALRM, util_onalrm);
		oa = alarm(1);
		for (i = 0; (ret = WAITPID(cpid, &stat_val, 0)) != cpid; i++) {
			(void) alarm(0);

			if (ret < 0 && errno != EINTR)
				break;

			if (i >= (app_data.srv_timeout + 5))
				/* Timeout: kill child */
				(void) kill(cpid, SIGTERM);

			/* Do some work */
			if (cdinfo_clinfo->workproc != NULL)
				cdinfo_clinfo->workproc(cdinfo_clinfo->arg);

			(void) alarm(1);
		}
		(void) alarm(oa);
		(void) signal(SIGALRM, oh);

		child_pid = 0;

		if (WIFEXITED(stat_val)) {
			if (WEXITSTATUS(stat_val) == 0)
				*retcode = 0;
			else
				*retcode = CDINFO_SET_CODE(
					WEXITSTATUS(stat_val), 0
				);
		}
		else if (WIFSIGNALED(stat_val))
			*retcode = CDINFO_SET_CODE(
				KILLED_ERR, WTERMSIG(stat_val)
			);
		else
			*retcode = 0;

		return TRUE;
	}

	/*NOTREACHED*/
#endif	/* SYNCHRONOUS */
}


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


/*
 * cdinfo_addr
 *	Return a pointer to the incore CD information structure.
 *
 * Args:
 *	None.
 *
 * Return:
 *	Pointer to the incore CD info structure.
 */
cdinfo_incore_t *
cdinfo_addr(void)
{
	return (&cdinfo_db);
}


/*
 * cdinfo_discid
 *      Compute a quasi-unique integer disc identifier based on the
 *	number of tracks, the length of each track, and a checksum of
 *	the string that represents the offset of each track.
 *
 * Args:
 *      s - Pointer to the curstat_t structure.
 *
 * Return:
 *      The integer disc ID.
 */
word32_t
cdinfo_discid(curstat_t *s)
{
	int	i,
		a,
		t = 0,
		n = 0;

	/* For backward compatibility this algorithm must not change */
	a = (int) s->tot_trks;

	for (i = 0; i < a; i++)
		n += cdinfo_sum((s->trkinfo[i].min * 60) + s->trkinfo[i].sec);

	t = ((s->trkinfo[a].min * 60) + s->trkinfo[a].sec) -
	     ((s->trkinfo[0].min * 60) + s->trkinfo[0].sec);

	return ((n % 0xff) << 24 | t << 8 | s->tot_trks);
}


/*
 * cdinfo_txtreduce
 *	Transform a text string into a CGI search string
 *
 * Args:
 *	text - Input text string
 *      reduce - Whether to reduce text (remove punctuations,
 *		 exclude words  from the excludeWords list).
 *
 * Return:
 *	Output string.  The storage is internally allocated per call,
 *	and should be freed by the caller with MEM_FREE() after the
 *	buffer is no longer needed.  If there is no sufficient memory
 *	to allocate the buffer, NULL is returned.
 */
char *
cdinfo_txtreduce(char *text, bool_t reduce)
{
	char	*p,
		*q;

	if (reduce) {
		if ((p = util_text_reduce(text)) == NULL)
		return NULL;
	}
	else
		p = text;

	if ((q = util_cgi_xlate(p)) == NULL)
		return NULL;

	if (reduce)
		MEM_FREE(p);

	return (q);
}


/*
 * cdinfo_tmpl_to_url
 *	Make a URL string from a template.
 *
 * Args:
 *	tmpl - Template string
 *	url - Buffer to store resultant URL string
 *	trkpos - The track position for %T or %t
 *
 * Return:
 *	Nothing.
 */
void
cdinfo_tmpl_to_url(char *tmpl, char *url, int trkpos)
{
	char	*q,
		*r,
		*a;
	char	dtitle[(STR_BUF_SZ * 2) + 4];

	r = url;
	for (a = tmpl; *a != '\0'; a++) {
		switch (*a) {
		case '%':
			switch (*(++a)) {
			case 'X':
				(void) strcpy(r, cdinfo_clinfo->prog);
				r += strlen(cdinfo_clinfo->prog);
				break;

			case 'V':
				(void) sprintf(r, "%s.%s",
					       VERSION_MAJ, VERSION_MIN);
				r += (strlen(VERSION_MAJ) +
				      strlen(VERSION_MIN) + 1);
				break;

			case 'N':
				q = util_loginname();
				(void) strcpy(r, q);
				r += strlen(q);
				break;

			case 'H':
				(void) strcpy(r, cdinfo_clinfo->host);
				r += strlen(cdinfo_clinfo->host);
				break;

			case 'L':
				(void) strcpy(r, app_data.libdir);
				r += strlen(app_data.libdir);
				break;

			case 'C':
				q = cdinfo_genre_path(cdinfo_db.disc.genre);
				(void) strcpy(r, q);
				r += strlen(q);
				break;

			case 'I':
				(void) sprintf(r, "%08x", cdinfo_db.discid);
				r += 8;
				break;

			case 'A':
			case 'a':
				/* Translate disk artist into a
				 * keyword string in CGI form
				 */
				q = cdinfo_txtreduce(
					cdinfo_db.disc.artist,
					(bool_t) (*a == 'a')
				);
				if (q == NULL) {
					CDINFO_FATAL(app_data.str_nomemory);
					return;
				}

				(void) strcpy(r, q);
				r += strlen(q);
				MEM_FREE(q);
				break;

			case 'T':
			case 't':
				/* Translate track title into a
				 * keyword string in CGI form
				 */
				if (trkpos >= 0) {
					q = cdinfo_txtreduce(
						cdinfo_db.track[trkpos].title,
						(bool_t) (*a == 't')
					);
					if (q == NULL) {
					    CDINFO_FATAL(app_data.str_nomemory);
					    return;
					}
					strcpy(r, q);
					r += strlen(q);
					MEM_FREE(q);
					break;
				}
				/*FALLTHROUGH*/

			case 'D':
			case 'd':
				/* Translate disc title into a
				 * keyword string in CGI form
				 */
				q = cdinfo_txtreduce(cdinfo_db.disc.title,
						      (bool_t) (*a == 'd'));
				if (q == NULL) {
					CDINFO_FATAL(app_data.str_nomemory);
					return;
				}

				(void) strcpy(r, q);
				r += strlen(q);
				MEM_FREE(q);
				break;

			case 'B':
			case 'b':
				/* Translate artist and disc title into a
				 * keyword string in CGI form
				 */
				(void) sprintf(dtitle, "%.127s%s%.127s",
					(cdinfo_db.disc.artist == NULL) ?
						"" : cdinfo_db.disc.artist,
					(cdinfo_db.disc.artist != NULL &&
					 cdinfo_db.disc.title != NULL) ?
						" / " : "",
					(cdinfo_db.disc.title == NULL) ?
						"" : cdinfo_db.disc.title);
				q = cdinfo_txtreduce(
					dtitle,
					(bool_t) (*a == 'b')
				);
				if (q == NULL) {
					CDINFO_FATAL(app_data.str_nomemory);
					return;
				}

				(void) strcpy(r, q);
				r += strlen(q);
				MEM_FREE(q);
				break;

			case '%':
				*r++ = '%';
				break;

			default:
				*r++ = '%';
				*r++ = *a;
				break;
			}
			break;
		default:
			*r++ = *a;
			break;
		}
	}
	*r = '\0';
}


/*
 * cdinfo_url_len
 *	Determine a string buffer size large enough to hold a
 *	URL after it is expanded from its template.
 *
 * Args:
 *	tmpl - Template string
 *	up - Pointer to the associated URL attributes structure
 *	trkpos - Pointer to the track position for %T or %t.
 *
 * Return:
 *	The buffer length.
 */
int
cdinfo_url_len(char *tmpl, url_attrib_t *up, int *trkpos)
{
	int	n,
		artist_len,
		title_len,
		dtitle_len;

	artist_len = (cdinfo_db.disc.artist != NULL) ?
		strlen(cdinfo_db.disc.artist) : STR_BUF_SZ;
	title_len = (cdinfo_db.disc.title != NULL) ?
		strlen(cdinfo_db.disc.title) : STR_BUF_SZ;
	dtitle_len = artist_len + title_len;

	n = 16 + strlen(tmpl) + ((up->acnt + up->dcnt) * dtitle_len);

	if (*trkpos < 0 || cdinfo_db.track[*trkpos].title == NULL) {
		*trkpos = -1;
		n += (up->tcnt * dtitle_len);
	}
	else
		n += strlen(cdinfo_db.track[*trkpos].title);

	n += (up->xcnt * strlen(cdinfo_clinfo->prog));
	n += (up->vcnt * (strlen(VERSION_MAJ) + strlen(VERSION_MIN) + 1));
	n += (up->ncnt * STR_BUF_SZ);
	n += (up->hcnt * MAXHOSTNAMELEN);
	n += (up->lcnt * strlen(app_data.libdir));
	if (cdinfo_db.disc.genre != NULL)
		n += (up->ccnt *
		      strlen(cdinfo_genre_path(cdinfo_db.disc.genre)));
	n += (up->icnt * 8);
	n++;

	return (n);
}


/*
 * cdinfo_init
 *	Initialize CD database management services
 *
 * Args:
 *	progname - The client program name string
 *	username - The client user login name string
 *
 * Return:
 *	Nothing.
 */
void
cdinfo_init(cdinfo_client_t *clp)
{
	char		*cp,
			*path;

	cdinfo_clinfo = clp;

	/* Sanity check */
	if (clp->prog[0] == '\0')
		(void) strcpy(clp->prog, "unknown");

	/* Hard wire file modes in case these are missing from the
	 * common.cfg file
	 */
	if (app_data.cdinfo_filemode == NULL)
		app_data.cdinfo_filemode = "0666";
	if (app_data.hist_filemode == NULL)
		app_data.hist_filemode = "0644";

	/* Load XMCD_CDINFOPATH environment variable, if specified */
	if ((cp = (char *) getenv("XMCD_CDINFOPATH")) != NULL &&
	    !util_newstr(&app_data.cdinfo_path, cp)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return;
	}

	if (app_data.cdinfo_path == NULL)
		app_data.cdinfo_path = "CDDB";

	if ((int) strlen(app_data.cdinfo_path) >= MAX_ENV_LEN) {
		CDINFO_FATAL(app_data.str_longpatherr);
		return;
	}

	/* Create the CDDB path list */
	path = app_data.cdinfo_path;
	while ((cp = strchr(path, CDINFOPATH_SEPCHAR)) != NULL) {
		*cp = '\0';

		if (!cdinfo_add_pathent(path))
			return;

		*cp = CDINFOPATH_SEPCHAR;
		path = cp + 1;
	}
	(void) cdinfo_add_pathent(path);
}


/*
 * cdinfo_halt
 *	Shut down cdinfo subsystem.
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Nothing
 */
void
cdinfo_halt(curstat_t *s)
{
	if (cdinfo_cddbp != NULL)
		(void) cdinfo_closecddb(cdinfo_cddbp);

	if (curfile[0] != '\0' && s->devlocked)
		(void) UNLINK(curfile);
}


/*
 * cdinfo_submit
 *	Submit current CD information to CDDB server
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	return code as defined by cdinfo_ret_t
 */
/*ARGSUSED*/
cdinfo_ret_t
cdinfo_submit(curstat_t *s)
{
	cdinfo_cddb_t	*cp;
	cdinfo_ret_t	retcode;

	if (cdinfo_forkwait(&retcode))
		return (retcode);

	/* Open CDDB connection */
	if ((cp = cdinfo_opencddb(s, &retcode)) == NULL) {
		/* CDDB service failure */
		CH_RET(retcode);
	}

	/* Submit disc info to CDDB */
	if (!cdinfo_submitcddb(cp, s, &retcode)) {
		/* Submit failed */
		(void) cdinfo_closecddb(cp);
		CH_RET(retcode);
	}

	/* Close CDDB connection */
	(void) cdinfo_closecddb(cp);

	/* Child exits here */
	CH_RET(0);
	/*NOTREACHED*/
}


/*
 * cdinfo_submit_url
 *	Submit to CDDB a URL pertaining to the current CD
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *	up - Pointer to the cdinfo_url_t structure
 *
 * Return:
 *	return code as defined by cdinfo_ret_t
 */
/*ARGSUSED*/
cdinfo_ret_t
cdinfo_submit_url(curstat_t *s, cdinfo_url_t *up)
{
	cdinfo_cddb_t	*cp;
	cdinfo_ret_t	retcode;

	if (up == NULL ||
	    up->categ == NULL || up->href == NULL || up->disptext == NULL)
		return CDINFO_SET_CODE(ARG_ERR, 0);

	if (cdinfo_forkwait(&retcode))
		return (retcode);

	/* Open CDDB connection */
	if ((cp = cdinfo_opencddb(s, &retcode)) == NULL) {
		/* CDDB service failure */
		CH_RET(retcode);
	}

	/* Submit disc info to CDDB */
	if (!cdinfo_submiturlcddb(cp, up, &retcode)) {
		/* Submit failed */
		(void) cdinfo_closecddb(cp);
		CH_RET(retcode);
	}

	/* Close CDDB connection */
	(void) cdinfo_closecddb(cp);

	/* Child exits here */
	CH_RET(0);
	/*NOTREACHED*/
}


/*
 * cdinfo_flush
 *	Flush CD information cache
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	return code as defined by cdinfo_ret_t
 */
/*ARGSUSED*/
cdinfo_ret_t
cdinfo_flush(curstat_t *s)
{
	cdinfo_cddb_t	*cp;
	cdinfo_ret_t	retcode;

	if (cdinfo_forkwait(&retcode))
		return (retcode);

	/* Open CDDB connection */
	if ((cp = cdinfo_opencddb(s, &retcode)) == NULL) {
		/* CDDB service failure */
		CH_RET(retcode);
	}

	/* Flush CDDB local cache */
	if (!cdinfo_flushcddb(cp)) {
		/* Cache flush failed */
		(void) cdinfo_closecddb(cp);
		CH_RET(FLUSH_ERR);
	}

	/* Close CDDB connection */
	(void) cdinfo_closecddb(cp);

	/* Child exits here */
	CH_RET(0);
	/*NOTREACHED*/
}


/*
 * cdinfo_offline
 *	Set the offline mode to what is specified by app_data.cdinfo_inetoffln
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	return code as defined by cdinfo_ret_t
 */
/*ARGSUSED*/
cdinfo_ret_t
cdinfo_offline(curstat_t *s)
{
	cdinfo_cddb_t	*cp;
	cdinfo_ret_t	retcode;

	if (cdinfo_forkwait(&retcode))
		return (retcode);

	/* Open CDDB connection */
	if ((cp = cdinfo_opencddb(s, &retcode)) == NULL) {
		/* CDDB service failure */
		CH_RET(retcode);
	}

	/* The cdinfo_opencddb call implicitly does the work, so
	 * we don't need to do anything else here.
	 */

	/* Close CDDB connection */
	(void) cdinfo_closecddb(cp);

	/* Child exits here */
	CH_RET(0);
	/*NOTREACHED*/
}


/*
 * cdinfo_load
 *	Load CD database entry for the currently inserted CD.
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Return value will be 0 for a successful connection, and
 *	a successful query will cause the CDINFO_MATCH bit to be
 *	set in the flags field of the cdinfo_incore_t structure.
 *	If an error occurred, then the return code is as defined
 *	by cdinfo_ret_t.
 */
cdinfo_ret_t
cdinfo_load(curstat_t *s)
{
#ifdef SYNCHRONOUS
	cdinfo_path_t		*pp;
	cdinfo_cddb_t		*cp;
	cdinfo_credit_t		*p;
	cdinfo_segment_t	*q;
	char			*path;
	int			i,
				retcode;

	cdinfo_db.flags &= ~CDINFO_MATCH;

	if ((cdinfo_db.discid = cdinfo_discid(s)) == 0)
		return CDINFO_SET_CODE(ARG_ERR, 0);

	if (cdinfo_cddb_iscfg() &&
	    (cdinfo_db.regionlist == NULL ||
	     cdinfo_db.rolelist == NULL ||
	     cdinfo_db.genrelist == NULL)) {
		DBGPRN(DBG_CDI)(errfp, "Initializing CDDB service...\n");

		/* Open CDDB connection */
		if ((cp = cdinfo_opencddb(s, &retcode)) != NULL) {
			/* Check user registration, set up region, role
			 * and genre lists
			 */
			(void) cdinfo_initcddb(cp, &retcode);
			(void) cdinfo_closecddb(cp);
		}
	}

	for (pp = cdinfo_db.pathlist; pp != NULL; pp = pp->next) {
		DBGPRN(DBG_CDI)(errfp, "*** CD info query: ");
		retcode = 0;

		switch (pp->type) {
		case CDINFO_RMT:
			/* Remote: Use CDDB service */
			DBGPRN(DBG_CDI)(errfp, "Trying CDDB service...\n");

			/* Open CDDB connection */
			if ((cp = cdinfo_opencddb(s, &retcode)) == NULL) {
				/* CDDB service failure */
				break;
			}

			/* Read CD information into incore structure */
			if (!cdinfo_querycddb(cp, s, &retcode)) {
				/* Query failed */
				(void) cdinfo_closecddb(cp);
				break;
			}

			if (cdinfo_db.matchlist != NULL) {
				/* Inexact "fuzzy matches" found.
				 * Query user to choose one
				 */
				cdinfo_db.match_tag = 0;

				/* Don't close CDDB connection here,
				 * but save pointer.
				 */
				cdinfo_db.sav_cddbp = cp;

				/* We return here, and continue to
				 * service this query via the callback
				 * cdinfo_load_matchsel().
				 */
				return 0;
			}

			/* Close CDDB connection */
			(void) cdinfo_closecddb(cp);

			break;

		case CDINFO_LOC:
			/* Local: Open local CD info file */

			path = (char *) MEM_ALLOC(
				"path", strlen(pp->path) + 12
			);
			if (path == NULL) {
				CDINFO_FATAL(app_data.str_nomemory);
				return CDINFO_SET_CODE(MEM_ERR, errno);;
			}
			(void) sprintf(path, CDINFOFILE_PATH,
				       pp->path, cdinfo_db.discid);

			DBGPRN(DBG_CDI)(errfp, "Trying %s", path);
			(void) cdinfo_load_locdb(path, pp->categ, s, &retcode);

			MEM_FREE(path);
			break;
		}

		if ((cdinfo_db.flags & CDINFO_MATCH) != 0 ||
		    (cdinfo_db.flags & CDINFO_NEEDREG) != 0) {
			/* Found a match or need user registration */
			break;
		}
	}

	/* If the display name field of the fullname structures are
	 * NULL, * fill them with the appropriate text.  Conversely,
	 * if the display name field is non NULL and the matching
	 * name fields are NULL, back fill the data. 
	 */

	if (cdinfo_db.disc.artistfname.dispname == NULL &&
	    cdinfo_db.disc.artist != NULL &&
	    !util_newstr(&cdinfo_db.disc.artistfname.dispname,
			 cdinfo_db.disc.artist)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}
	else if (cdinfo_db.disc.artist == NULL &&
		 cdinfo_db.disc.artistfname.dispname != NULL &&
		 !util_newstr(&cdinfo_db.disc.artist,
			      cdinfo_db.disc.artistfname.dispname)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}

	for (p = cdinfo_db.disc.credit_list; p != NULL; p = p->next) {
		if (p->crinfo.fullname.dispname == NULL &&
		    p->crinfo.name != NULL &&
		    !util_newstr(&p->crinfo.fullname.dispname,
				 p->crinfo.name)) {
			CDINFO_FATAL(app_data.str_nomemory);
			return CDINFO_SET_CODE(MEM_ERR, 0);
		}
		else if (p->crinfo.name == NULL &&
			 p->crinfo.fullname.dispname != NULL &&
			 !util_newstr(&p->crinfo.name,
				      p->crinfo.fullname.dispname)) {
			CDINFO_FATAL(app_data.str_nomemory);
			return CDINFO_SET_CODE(MEM_ERR, 0);
		}
	}

	for (q = cdinfo_db.disc.segment_list; q != NULL; q = q->next) {
		for (p = q->credit_list; p != NULL; p = p->next) {
			if (p->crinfo.fullname.dispname == NULL &&
			    p->crinfo.name != NULL &&
			    !util_newstr(&p->crinfo.fullname.dispname,
					 p->crinfo.name)) {
				CDINFO_FATAL(app_data.str_nomemory);
				return CDINFO_SET_CODE(MEM_ERR, 0);
			}
			else if (p->crinfo.name == NULL &&
				 p->crinfo.fullname.dispname != NULL &&
				 !util_newstr(&p->crinfo.name,
					      p->crinfo.fullname.dispname)) {
				CDINFO_FATAL(app_data.str_nomemory);
				return CDINFO_SET_CODE(MEM_ERR, 0);
			}
		}
	}

	for (i = 0; i < (int) s->tot_trks; i++) {
		if (cdinfo_db.track[i].artistfname.dispname == NULL &&
		    cdinfo_db.track[i].artist != NULL &&
		    !util_newstr(&cdinfo_db.track[i].artistfname.dispname,
				 cdinfo_db.track[i].artist)) {
			CDINFO_FATAL(app_data.str_nomemory);
			return CDINFO_SET_CODE(MEM_ERR, 0);
		}
		else if (cdinfo_db.track[i].artist == NULL &&
			 cdinfo_db.track[i].artistfname.dispname != NULL &&
			 !util_newstr(&cdinfo_db.track[i].artist,
				cdinfo_db.track[i].artistfname.dispname)) {
			CDINFO_FATAL(app_data.str_nomemory);
			return CDINFO_SET_CODE(MEM_ERR, 0);
		}

		for (p = cdinfo_db.track[i].credit_list; p != NULL;
		     p = p->next){
			if (p->crinfo.fullname.dispname == NULL &&
			    p->crinfo.name != NULL &&
			    !util_newstr(&p->crinfo.fullname.dispname,
					 p->crinfo.name)) {
				CDINFO_FATAL(app_data.str_nomemory);
				return CDINFO_SET_CODE(MEM_ERR, 0);
			}
			else if (p->crinfo.name == NULL &&
				 p->crinfo.fullname.dispname != NULL &&
				 !util_newstr(&p->crinfo.name,
					      p->crinfo.fullname.dispname)) {
				CDINFO_FATAL(app_data.str_nomemory);
				return CDINFO_SET_CODE(MEM_ERR, 0);
			}
		}
	}

	return 0;

#else	/* SYNCHRONOUS */
	int			i,
				ret,
				retcode;
	unsigned int		oa;
	pid_t			cpid;
	waitret_t		stat_val;
	cdinfo_path_t		*pp;
	cdinfo_cddb_t		*cp;
	cdinfo_credit_t		*p;
	cdinfo_segment_t	*q;
	cdinfo_pipe_t		*spp,
				*rpp;
	char			*path;
	bool_t			done;
	void			(*oh)(int);

	cdinfo_db.flags &= ~CDINFO_MATCH;

	if ((cdinfo_db.discid = cdinfo_discid(s)) == 0)
		return CDINFO_SET_CODE(ARG_ERR, 0);

	/* Open pipes for IPC */
	if ((rpp = cdinfo_openpipe(CDINFO_DATAIN)) == NULL)
		return CDINFO_SET_CODE(OPEN_ERR, errno);
	if ((spp = cdinfo_openpipe(CDINFO_DATAOUT)) == NULL) {
		(void) cdinfo_closepipe(rpp);
		return CDINFO_SET_CODE(OPEN_ERR, errno);
	}

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

		/* Exit on receipt of SIGTERM */
		(void) signal(SIGTERM, cdinfo_onterm);

		/* Ignore SIGALRM */
		(void) signal(SIGALRM, SIG_IGN);

		/* Close unneeded descriptors */
		(void) close(rpp->fd[CDINFO_PIPE_R]);
		(void) close(spp->fd[CDINFO_PIPE_W]);
		rpp->fd[CDINFO_PIPE_R] = spp->fd[CDINFO_PIPE_W] = -1;

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

		break;

	case -1:
		(void) cdinfo_closepipe(rpp);
		(void) cdinfo_closepipe(spp);
		return CDINFO_SET_CODE(FORK_ERR, errno);

	default:
		/* Parent process */
		child_pid = cpid;

		/* Close unneeded descriptors */
		(void) close(rpp->fd[CDINFO_PIPE_W]);
		(void) close(spp->fd[CDINFO_PIPE_R]);
		rpp->fd[CDINFO_PIPE_W] = spp->fd[CDINFO_PIPE_R] = -1;

		/* Set alarm so we don't hang forever waiting */
		oh = signal(SIGALRM, cdinfo_onalrm);
		oa = alarm(app_data.srv_timeout + 5);

		/* Read CD info content from pipe into in-core structure */
		(void) cdinfo_read_datapipe(rpp);

		/* Cancel alarm */
		(void) alarm(oa);
		(void) signal(SIGALRM, oh);

		if (cdinfo_db.matchlist != NULL) {
			/* Inexact "fuzzy matches" found.  Query user
			 * to choose one.
			 */
			cdinfo_db.match_tag = 0;

			/* Don't close pipes here, but save pointer */
			cdinfo_db.sav_rpp = rpp;
			cdinfo_db.sav_spp = spp;

			/* We return with child process still running,
			 * and continue to service this query via
			 * the callback cdinfo_load_matchsel().
			 */
			return 0;
		}

		/* Close pipes */
		(void) cdinfo_closepipe(rpp);
		(void) cdinfo_closepipe(spp);

		/* Parent process: wait for child to exit */
		oh = signal(SIGALRM, util_onalrm);
		oa = alarm(1);
		for (i = 0; (ret = WAITPID(cpid, &stat_val, 0)) != cpid; i++) {
			(void) alarm(0);

			if (ret < 0 && errno != EINTR)
				break;

			if (i >= (app_data.srv_timeout + 5))
				/* Timeout: kill child */
				(void) kill(cpid, SIGTERM);

			/* Do some work */
			if (cdinfo_clinfo->workproc != NULL)
				cdinfo_clinfo->workproc(cdinfo_clinfo->arg);

			(void) alarm(1);
		}
		(void) alarm(oa);
		(void) signal(SIGALRM, oh);

		child_pid = 0;

		if (WIFEXITED(stat_val)) {
			if (WEXITSTATUS(stat_val) == 0)
			    return 0;
			else
			    return CDINFO_SET_CODE(WEXITSTATUS(stat_val), 0);
		}
		else if (WIFSIGNALED(stat_val))
			return CDINFO_SET_CODE(KILLED_ERR, WTERMSIG(stat_val));
		else
			return 0;
	}

	if (cdinfo_cddb_iscfg() &&
	    (cdinfo_db.regionlist == NULL ||
	     cdinfo_db.rolelist == NULL ||
	     cdinfo_db.genrelist == NULL)) {
		DBGPRN(DBG_CDI)(errfp, "Initializing CDDB service...\n");

		/* Open CDDB connection */
		if ((cp = cdinfo_opencddb(s, &retcode)) != NULL) {
			/* Check user registration, set up region, role
			 * and genre lists
			 */
			(void) cdinfo_initcddb(cp, &retcode);
			(void) cdinfo_closecddb(cp);
		}
	}

	done = FALSE;
	for (pp = cdinfo_db.pathlist; pp != NULL; pp = pp->next) {
		DBGPRN(DBG_CDI)(errfp, "*** CD info query: ");
		retcode = 0;

		switch (pp->type) {
		case CDINFO_RMT:
			/* Remote: Use CDDB service */
			DBGPRN(DBG_CDI)(errfp, "Trying CDDB service...\n");

			/* Open CDDB connection */
			if ((cp = cdinfo_opencddb(s, &retcode)) == NULL) {
				/* Failure */
				break;
			}

			/* Read CD info content and update incore structures */
			if (!cdinfo_querycddb(cp, s, &retcode)) {
				/* Failure */
				(void) cdinfo_closecddb(cp);
				break;
			}

			if (cdinfo_db.matchlist != NULL) {
				/* Inexact "fuzzy matches" found.
				 * Transmit the list to parent process
				 * for processing.
				 */
				cdinfo_db.match_tag = 0;

				if (!cdinfo_write_datapipe(rpp, s)) {
					/* Failure */
					(void) cdinfo_closecddb(cp);
					(void) cdinfo_closepipe(rpp);
					(void) cdinfo_closepipe(spp);
					CH_RET(WRITE_ERR);
				}

				(void) sleep(2);

				/* Read user selection */
				if (!cdinfo_read_selpipe(spp)) {
					/* Failure */
					(void) cdinfo_closecddb(cp);
					(void) cdinfo_closepipe(rpp);
					(void) cdinfo_closepipe(spp);
					CH_RET(READ_ERR);
				}

				/* Read CD info content and update incore
				 * structures
				 */
				if (!cdinfo_querycddb(cp, s, &retcode)) {
					/* Failure */
					(void) cdinfo_closecddb(cp);
					break;
				}

				/* Deallocate the matchlist */
				cdinfo_free_matchlist();
			}

			/* Close CDDB connection */
			(void) cdinfo_closecddb(cp);

			if ((cdinfo_db.flags & CDINFO_MATCH) != 0 ||
			    (cdinfo_db.flags & CDINFO_NEEDREG) != 0) {
				/* Found a match or needs user reg */
				done = TRUE;
			}
			break;

		case CDINFO_LOC:
			/* Local: Open local CD info file */
			path = (char *) MEM_ALLOC(
				"path", strlen(pp->path) + 12
			);
			if (path == NULL)
				CH_RET(MEM_ERR);

			(void) sprintf(path, CDINFOFILE_PATH,
				       pp->path, cdinfo_db.discid);

			DBGPRN(DBG_CDI)(errfp, "Trying %s", path);
			(void) cdinfo_load_locdb(path, pp->categ, s, &retcode);

			MEM_FREE(path);

			if ((cdinfo_db.flags & CDINFO_MATCH) != 0) {
				/* Found a match */
				done = TRUE;
			}
			break;
		}

		if (done)
			break;
	}

	/* If the display name field of the fullname structures are
	 * NULL, fill them with the appropriate text.  Conversely,
	 * if the display name field is non NULL and the matching
	 * name fields are NULL, back fill the data. 
	 */

	if (cdinfo_db.disc.artistfname.dispname == NULL &&
	    cdinfo_db.disc.artist != NULL &&
	    !util_newstr(&cdinfo_db.disc.artistfname.dispname,
			 cdinfo_db.disc.artist)) {
		CH_RET(MEM_ERR);
	}
	else if (cdinfo_db.disc.artist == NULL &&
		 cdinfo_db.disc.artistfname.dispname != NULL &&
		 !util_newstr(&cdinfo_db.disc.artist,
			      cdinfo_db.disc.artistfname.dispname)) {
		CH_RET(MEM_ERR);
	}

	for (p = cdinfo_db.disc.credit_list; p != NULL; p = p->next) {
		if (p->crinfo.fullname.dispname == NULL &&
		    p->crinfo.name != NULL &&
		    !util_newstr(&p->crinfo.fullname.dispname,
				 p->crinfo.name)) {
			CH_RET(MEM_ERR);
		}
		else if (p->crinfo.name == NULL &&
			 p->crinfo.fullname.dispname != NULL &&
			 !util_newstr(&p->crinfo.name,
				      p->crinfo.fullname.dispname)) {
			CH_RET(MEM_ERR);
		}
	}

	for (q = cdinfo_db.disc.segment_list; q != NULL; q = q->next) {
		for (p = q->credit_list; p != NULL; p = p->next) {
			if (p->crinfo.fullname.dispname == NULL &&
			    p->crinfo.name != NULL &&
			    !util_newstr(&p->crinfo.fullname.dispname,
					 p->crinfo.name)) {
				CH_RET(MEM_ERR);
			}
			else if (p->crinfo.name == NULL &&
				 p->crinfo.fullname.dispname != NULL &&
				 !util_newstr(&p->crinfo.name,
					      p->crinfo.fullname.dispname)) {
				CH_RET(MEM_ERR);
			}
		}
	}

	for (i = 0; i < (int) s->tot_trks; i++) {
		if (cdinfo_db.track[i].artistfname.dispname == NULL &&
		    cdinfo_db.track[i].artist != NULL &&
		    !util_newstr(&cdinfo_db.track[i].artistfname.dispname,
				 cdinfo_db.track[i].artist)) {
			CH_RET(MEM_ERR);
		}
		else if (cdinfo_db.track[i].artist == NULL &&
			 cdinfo_db.track[i].artistfname.dispname != NULL &&
			 !util_newstr(&cdinfo_db.track[i].artist,
				cdinfo_db.track[i].artistfname.dispname)) {
			CH_RET(MEM_ERR);
		}

		for (p = cdinfo_db.track[i].credit_list; p != NULL;
		     p = p->next) {
			if (p->crinfo.fullname.dispname == NULL &&
			    p->crinfo.name != NULL &&
			    !util_newstr(&p->crinfo.fullname.dispname,
					 p->crinfo.name)) {
				CH_RET(MEM_ERR);
			}
			else if (p->crinfo.name == NULL &&
				 p->crinfo.fullname.dispname != NULL &&
				 !util_newstr(&p->crinfo.name,
					      p->crinfo.fullname.dispname)) {
				CH_RET(MEM_ERR);
			}
		}
	}

	/* Write incore CD info into pipe */
	if (!cdinfo_write_datapipe(rpp, s))
		retcode = WRITE_ERR;
	else
		retcode = 0;

	(void) cdinfo_closepipe(rpp);
	(void) cdinfo_closepipe(spp);
	CH_RET(retcode);

	/*NOTREACHED*/
#endif	/* SYNCHRONOUS */
}


/*
 * cdinfo_load_matchsel
 *	Load CD database entry after user chooses a fuzzy match
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Return value will be 0 for a successful connection, and
 *	a successful query will cause the CDINFO_MATCH bit to be
 *	set in the flags field of the cdinfo_incore_t structure.
 *	If an error occurred, then the return code is as defined
 *	by cdinfo_ret_t.
 */
/*ARGSUSED*/
cdinfo_ret_t
cdinfo_load_matchsel(curstat_t *s)
{
#ifdef SYNCHRONOUS
	cdinfo_cddb_t	*cp;
	cdinfo_ret_t	retcode;

	cp = cdinfo_db.sav_cddbp;
	if (cp == NULL)
		return CDINFO_SET_CODE(READ_ERR, 0);

	/* Read CD information into incore structure */
	if (!cdinfo_querycddb(cp, s, &retcode)) {
		/* Query failed */
		(void) cdinfo_closecddb(cp);

		/* Deallocate the matchlist */
		cdinfo_free_matchlist();

		return CDINFO_SET_CODE(retcode, 0);
	}

	/* Close CDDB connection */
	(void) cdinfo_closecddb(cp);

	/* Deallocate the matchlist */
	cdinfo_free_matchlist();

	return 0;

#else	/* SYNCHRONOUS */
	int		i,
			ret;
	unsigned int	oa;
	pid_t		cpid;
	waitret_t	stat_val;
	cdinfo_pipe_t	*spp,
			*rpp;
	void		(*oh)(int);

	/* Restore pointers */
	rpp = cdinfo_db.sav_rpp;
	spp = cdinfo_db.sav_spp;

	/* Child pid */
	cpid = child_pid;

	if (rpp == NULL || spp == NULL || cpid == 0) {
		/* Error */
		return CDINFO_SET_CODE(READ_ERR, 0);
	}

	/* Child process should still be running.  Communicate user
	 * selection to it.
	 */
	if (!cdinfo_write_selpipe(spp)) {
		/* Failure */
		(void) cdinfo_closepipe(rpp);
		(void) cdinfo_closepipe(spp);
		cdinfo_load_cancel();

		/* Deallocate the matchlist */
		cdinfo_free_matchlist();

		return CDINFO_SET_CODE(READ_ERR, 0);
	}

	/* Deallocate the matchlist */
	cdinfo_free_matchlist();

	/* Set alarm so we don't hang forever waiting */
	oh = signal(SIGALRM, cdinfo_onalrm);
	oa = alarm(app_data.srv_timeout + 5);

	/* Read CD info content from pipe into in-core structure */
	(void) cdinfo_read_datapipe(rpp);

	/* Cancel alarm */
	(void) alarm(oa);
	(void) signal(SIGALRM, oh);

	/* Close pipes */
	(void) cdinfo_closepipe(rpp);
	(void) cdinfo_closepipe(spp);

	/* Parent process: wait for child to exit */
	oh = signal(SIGALRM, util_onalrm);
	oa = alarm(1);
	for (i = 0; (ret = WAITPID(cpid, &stat_val, 0)) != cpid; i++) {
		(void) alarm(0);

		if (ret < 0 && errno != EINTR)
			break;

		if (i >= (app_data.srv_timeout + 5))
			/* Timeout: kill child */
			(void) kill(cpid, SIGTERM);

		/* Do some work */
		if (cdinfo_clinfo->workproc != NULL)
			cdinfo_clinfo->workproc(cdinfo_clinfo->arg);

		(void) alarm(1);
	}
	(void) alarm(oa);
	(void) signal(SIGALRM, oh);

	child_pid = 0;

	if (WIFEXITED(stat_val)) {
		if (WEXITSTATUS(stat_val) == 0)
			return 0;
		else
			return CDINFO_SET_CODE(WEXITSTATUS(stat_val), 0);
	}
	else if (WIFSIGNALED(stat_val))
		return CDINFO_SET_CODE(KILLED_ERR, WTERMSIG(stat_val));
	else
		return 0;
#endif	/* SYNCHRONOUS */
}


/*
 * cdinfo_load_prog
 *	Load user-saved track program for the currently inserted CD.
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *
 * Return:
 *	Return value will be 0 for a successful load, or an error value
 *	on failure.
 */
cdinfo_ret_t
cdinfo_load_prog(curstat_t *s)
{
	FILE		*fp;
	char		*homepath,
			buf[STR_BUF_SZ],
			path[FILE_PATH_SZ];
	int		i;
	bool_t		badline;
#ifndef SYNCHRONOUS
	pid_t		cpid;
	waitret_t	stat_val;
	int		ret,
			pfd[2];
	unsigned int	oa;
	void		(*oh)(int);

	if ((cdinfo_db.discid = cdinfo_discid(s)) == 0)
		return CDINFO_SET_CODE(ARG_ERR, 0);

	if (s->program || s->shuffle)
		/* Program or shuffle already in progress */
		return CDINFO_SET_CODE(BUSY_ERR, 0);

	if (PIPE(pfd) < 0) {
		DBGPRN(DBG_CDI)(errfp,
			"cdinfo_load_prog: pipe failed (errno=%d)\n", errno);
		return CDINFO_SET_CODE(OPEN_ERR, 0);
	}
	
	switch (cpid = FORK()) {
	case 0:
		/* Child */
		ischild = TRUE;

		/* Exit on receipt of SIGTERM */
		(void) signal(SIGTERM, cdinfo_onterm);

		/* Ignore SIGALRM */
		(void) signal(SIGALRM, SIG_IGN);

		/* 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(SETUID_ERR);
		}

		homepath = util_homedir(util_get_ouid());

		(void) sprintf(path, USR_PROG_PATH, homepath);
		(void) sprintf(path, "%s/%08x", path, cdinfo_db.discid);

		DBGPRN(DBG_CDI)(errfp, "Loading track program: %s\n", path);

		if ((fp = fopen(path, "r")) == NULL) {
			(void) close(pfd[1]);
			exit(OPEN_ERR);
		}

		while (fgets(buf, sizeof(buf), fp) != NULL) {
			/* Skip comments */
			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);

		exit(0);
		/*NOTREACHED*/

	case -1:
		DBGPRN(DBG_CDI)(errfp,
			"cdinfo_load_prog: fork failed (errno=%d)\n", errno);
		(void) close(pfd[0]);
		(void) close(pfd[1]);
		return CDINFO_SET_CODE(FORK_ERR, 0);
		/*NOTREACHED*/

	default:
		/* Parent */
		child_pid = cpid;

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

		if ((fp = fdopen(pfd[0], "r")) == NULL) {
			DBGPRN(DBG_CDI)(errfp,
				"cdinfo_load_prog: read pipe fdopen failed\n");
			return CDINFO_SET_CODE(READ_ERR, 0);
		}
		break;
	}
#else
	if ((cdinfo_db.discid = cdinfo_discid(s)) == 0)
		return CDINFO_SET_CODE(ARG_ERR, 0);

	if (s->program || s->shuffle)
		/* Program or shuffle already in progress */
		return CDINFO_SET_CODE(BUSY_ERR, 0);

	homepath = util_homedir(util_get_ouid());

	(void) sprintf(path, USR_PROG_PATH, homepath);
	(void) sprintf(path, "%s%08x", path, cdinfo_db.discid);

	DBGPRN(DBG_CDI)(errfp, "Loading track program: %s\n", path);

	if ((fp = fopen(path, "r")) == NULL)
		return CDINFO_SET_CODE(OPEN_ERR, 0);
#endif	/* SYNCHRONOUS */

	while (fgets(buf, sizeof(buf), fp) != NULL) {
		char	*p;

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

		if (buf[0] == '#' || buf[0] == '!' || buf[0] == '\n')
			/* Skip comments */
			continue;

		buf[strlen(buf)-1] = '\0';	/* Zap newline */

		/* Check to make sure it's all digits */
		badline = FALSE;
		for (p = buf; *p != '\0'; p++) {
			if (!isdigit((int) *p)) {
				badline = TRUE;
				break;
			}
		}
		if (badline)
			continue;

		if (cdinfo_db.playorder == NULL) {
			if (!util_newstr(&cdinfo_db.playorder, buf)) {
				(void) fclose(fp);
				CDINFO_FATAL(app_data.str_nomemory);
				return CDINFO_SET_CODE(MEM_ERR, errno);
			}
		}
		else {
			cdinfo_db.playorder = (char *) MEM_REALLOC(
				"playorder",
				cdinfo_db.playorder,
				strlen(cdinfo_db.playorder) + strlen(buf) + 2
			);
			if (cdinfo_db.playorder == NULL) {
				(void) fclose(fp);
				CDINFO_FATAL(app_data.str_nomemory);
				return CDINFO_SET_CODE(MEM_ERR, errno);
			}
			(void) sprintf(cdinfo_db.playorder, "%s,%s",
				       cdinfo_db.playorder, buf);
		}
	}

	(void) fclose(fp);

#ifndef SYNCHRONOUS
	/* Parent process: wait for child to exit */
	oh = signal(SIGALRM, util_onalrm);
	oa = alarm(1);
	for (i = 0; (ret = WAITPID(cpid, &stat_val, 0)) != cpid; i++) {
		(void) alarm(0);

		if (ret < 0 && errno != EINTR)
			break;

		if (i >= (app_data.srv_timeout + 5))
			/* Timeout: kill child */
			(void) kill(cpid, SIGTERM);

		/* Do some work */
		if (cdinfo_clinfo->workproc != NULL)
			cdinfo_clinfo->workproc(cdinfo_clinfo->arg);

		(void) alarm(1);
	}
	(void) alarm(oa);
	(void) signal(SIGALRM, oh);

	child_pid = 0;

	if (WIFEXITED(stat_val)) {
		if (WEXITSTATUS(stat_val) != 0)
			return CDINFO_SET_CODE(WEXITSTATUS(stat_val), 0);
		else
			return 0;
	}
	else if (WIFSIGNALED(stat_val))
		return CDINFO_SET_CODE(KILLED_ERR, WTERMSIG(stat_val));
	else
		return 0;
#else
	return 0;
#endif
}


/*
 * cdinfo_save_prog
 *	Write user-defined track program into file for future retrieval
 *
 * Arg:
 *	None
 *
 * Return:
 *	Return value will be 0 for a successful write, or an error value
 *	on failure.
 */
cdinfo_ret_t
cdinfo_save_prog(void)
{
	FILE		*fp;
	char		*p,
			*homepath,
			path[FILE_PATH_SZ];
	cdinfo_ret_t	retcode;
	int		err;
	bool_t		n;

	if (cdinfo_db.discid == 0 || cdinfo_db.playorder == NULL)
		return (ARG_ERR);

	if (cdinfo_forkwait(&retcode))
		return (retcode);

	homepath = util_homedir(util_get_ouid());

	(void) sprintf(path, USR_PROG_PATH, homepath);
	(void) sprintf(path, "%s/%08x", path, cdinfo_db.discid);

	(void) unlink(path);
	if ((fp = fopen(path, "w")) == NULL) {
		err = errno;
		DBGPRN(DBG_CDI)(errfp,
			"Cannot open file for writing: %s\n", path);
		CH_RET(err);
	}

	DBGPRN(DBG_CDI)(errfp, "Writing track program: %s\n", path);

	(void) fprintf(fp, "# Xmcd %s.%s track program file\n",
		       VERSION_MAJ, VERSION_MIN);
	(void) fprintf(fp, "# ID: %08x\n", cdinfo_db.discid);

	n = FALSE;
	for (p = cdinfo_db.playorder; *p != '\0'; p++) {
		if (isdigit((int) *p)) {
			n = TRUE;
			(void) fputc(*p, fp);
		}
		else if (n) {
			n = FALSE;
			(void) fputc('\n', fp);
		}
	}
	(void) fputc('\n', fp);
	(void) fclose(fp);

	CH_RET(0);
	/*NOTREACHED*/
}


/*
 * cdinfo_del_prog
 *	Delete the user-defined track program file for the loaded CD.
 *
 * Arg:
 *	None
 *
 * Return:
 *	Return value will be 0 for a successful delete, or an error value
 *	on failure.
 */
cdinfo_ret_t
cdinfo_del_prog(void)
{
	char		*homepath,
			path[FILE_PATH_SZ];
	cdinfo_ret_t	retcode;

	if (cdinfo_db.discid == 0)
		return (ARG_ERR);

	if (cdinfo_forkwait(&retcode))
		return (retcode);

	homepath = util_homedir(util_get_ouid());

	(void) sprintf(path, USR_PROG_PATH, homepath);
	(void) sprintf(path, "%s/%08x", path, cdinfo_db.discid);

	DBGPRN(DBG_CDI)(errfp, "Deleting track program: %s\n", path);

	if (unlink(path) < 0)
		CH_RET(errno);

	CH_RET(0);
	/*NOTREACHED*/
}


/*
 * cdinfo_reguser
 *	Register the user with CDDB
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Return value will be 0 for a successful connection, otherwise
 *	the return status will be as defined for cdinfo_ret_t.
 */
cdinfo_ret_t
cdinfo_reguser(curstat_t *s)
{
	cdinfo_cddb_t	*cp;
	cdinfo_ret_t	retcode;

	if (cdinfo_forkwait(&retcode)) {
		if (retcode == 0) {
			/* Clear flag */
			cdinfo_db.flags &= ~CDINFO_NEEDREG;
		}
		return (retcode);
	}

	/* Open CDDB connection */
	if ((cp = cdinfo_opencddb(s, &retcode)) == NULL)
		CH_RET(retcode);

	/* Register user with CDDB */
	if (!cdinfo_uregcddb(cp, &retcode)) {
		(void) cdinfo_closecddb(cp);
		CH_RET(retcode);
	}

	/* Close CDDB connection */
	(void) cdinfo_closecddb(cp);

	/* Clear flag */
	cdinfo_db.flags &= ~CDINFO_NEEDREG;

	/* Child exits here */
	CH_RET(0);
	/*NOTREACHED*/
}


/*
 * cdinfo_getpasshint
 *	Request CDDB to send password hint via e-mail
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Return value will be 0 for a successful connection, otherwise
 *	the return status will be as defined for cdinfo_ret_t.
 */
cdinfo_ret_t
cdinfo_getpasshint(curstat_t *s)
{
	cdinfo_cddb_t	*cp;
	cdinfo_ret_t	retcode;

	if (cdinfo_forkwait(&retcode))
		return (retcode);

	/* Open CDDB connection */
	if ((cp = cdinfo_opencddb(s, &retcode)) == NULL)
		CH_RET(retcode);

	/* Ask CDDB to send password hint */
	if (!cdinfo_passhintcddb(cp, &retcode)) {
		(void) cdinfo_closecddb(cp);
		CH_RET(retcode);
	}

	/* Close CDDB connection */
	(void) cdinfo_closecddb(cp);

	/* Child exits here */
	CH_RET(0);
	/*NOTREACHED*/
}


/*
 * cdinfo_go_musicbrowser
 *	Invoke CDDB music browser.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Return value will be 0 for a successful connection, otherwise
 *	the return status will be as defined for cdinfo_ret_t.
 */
cdinfo_ret_t
cdinfo_go_musicbrowser(curstat_t *s)
{
	cdinfo_ret_t	retcode;
	cdinfo_cddb_t	*cp;

	if (cdinfo_forkwait(&retcode))
		return (retcode);

	/* Open CDDB connection */
	if ((cp = cdinfo_opencddb(s, &retcode)) == NULL) {
		/* Failure */
		CH_RET(retcode);
	}

	/* Invoke CDDB info browser */
	if (!cdinfo_infobrowsercddb(cp)) {
		/* Failure */
		(void) cdinfo_closecddb(cp);
		CH_RET(CMD_ERR);
	}

	/* Close CDDB connection */
	(void) cdinfo_closecddb(cp);

	/* Child exits here */
	CH_RET(0);
	/*NOTREACHED*/
}


/*
 * cdinfo_go_url
 *	Invoke web browser and go to the specified URL.
 *
 * Args:
 *	url - The URL string.
 *
 * Return:
 *	Return value will be 0 for a successful connection, otherwise
 *	the return status will be as defined for cdinfo_ret_t.
 */
cdinfo_ret_t
cdinfo_go_url(char *url)
{
	int	ret;
	char	*cmd;

	if (url == NULL || url[0] == '\0')
		return CDINFO_SET_CODE(CMD_ERR, 0);

	/* Invoke web browser to the specified URL */
	cmd = (char *) MEM_ALLOC("urlstr", strlen(url) + FILE_PATH_SZ + 4);
	if (cmd == NULL) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}
	(void) sprintf(cmd, "%s '%s'", CDINFO_GOBROWSER, url);

	ret = util_runcmd(cmd, cdinfo_clinfo->workproc, cdinfo_clinfo->arg);

	MEM_FREE(cmd);
	return CDINFO_SET_CODE((ret == 0) ? 0 : CMD_ERR, 0);
}


/*
 * cdinfo_go_cddburl
 *	Invoke web browser to go to a URL supplied by CDDB
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *	type - WTYPE_GEN or WTYPE_ALBUM
 *	idx - URL index as found in the cdinfo_db.gen_url_list
 *
 * Return:
 *	Return value will be 0 for a successful connection, otherwise
 *	the return status will be as defined for cdinfo_ret_t.
 */
cdinfo_ret_t
cdinfo_go_cddburl(curstat_t *s, int type, int idx)
{
	cdinfo_cddb_t	*cp;
	cdinfo_ret_t	retcode;

	if (type != WTYPE_GEN && type != WTYPE_ALBUM)
		return CDINFO_SET_CODE(ARG_ERR, 0);

	if (cdinfo_forkwait(&retcode))
		return (retcode);

	/* Open CDDB connection */
	if ((cp = cdinfo_opencddb(s, &retcode)) == NULL) {
		/* Failure */
		CH_RET(retcode);
	}

	/* Invoke web browser to go to the specified URL */
	if (!cdinfo_urlcddb(cp, type, idx)) {
		/* Failure */
		(void) cdinfo_closecddb(cp);
		CH_RET(CMD_ERR);
	}

	/* Close CDDB connection */
	(void) cdinfo_closecddb(cp);

	/* Child exits here */
	CH_RET(0);
	/*NOTREACHED*/
}


/*
 * cdinfo_gen_discog
 *	Write out local discography file containing the currently loaded
 *	CD information.
 *
 * Args:
 *	s - Pointer to the curstat_t structure
 *	baseurl - The URL to the output file
 *
 * Return:
 *	Return value will be 0 for a successful connection, otherwise
 *	the return status will be as defined for cdinfo_ret_t.
 */
cdinfo_ret_t
cdinfo_gen_discog(curstat_t *s)
{
	int		i,
			n,
			ret;
	unsigned int	dmode,
			fmode;
	FILE		*fp;
	char		*p,
			*path,
			*url,
			outdir[FILE_PATH_SZ];
	bool_t		err,
			newdiscog;
	struct stat	stbuf;
	cdinfo_ret_t	retcode;

	if (cdinfo_forkwait(&retcode))
		return (retcode);

	if (cdinfo_discog == NULL) {
		/* Error: local discography not defined in wwwwarp.cfg */
		DBGPRN(DBG_CDI)(errfp,
			"Local discography not defined in %s.\n", WWWWARP_CFG);
		CH_RET(ENOENT);
	}

	i = -1;
	n = cdinfo_url_len(cdinfo_discog->arg, &cdinfo_discog->attrib, &i);
	if ((url = (char *) MEM_ALLOC("cdinfo_genurl", n)) == NULL) {
		DBGPRN(DBG_CDI)(errfp, "Out of memory.\n");
		CH_RET(ENOMEM);
	}
	/* Make the URL from template */
	cdinfo_tmpl_to_url(cdinfo_discog->arg, url, i);

	if (util_urlchk(url, &path, &n) & IS_REMOTE_URL) {
		/* Error: local discography cannot be remote */
		DBGPRN(DBG_CDI)(errfp,
			"Local discography URL error: %s\n", url);
		MEM_FREE(url);
		CH_RET(EINVAL);
	}

	(void) sscanf(app_data.cdinfo_filemode, "%o", &fmode);
	/* Make sure file is at least accessible by user */
	fmode |= S_IRUSR | S_IWUSR;
	fmode &= ~(S_ISUID | S_ISGID);

	/* Set directory perm based on file perm */
	dmode = (fmode | S_IXUSR);
	if (fmode & S_IRGRP)
		dmode |= (S_IRGRP | S_IXGRP);
	if (fmode & S_IWGRP)
		dmode |= (S_IWGRP | S_IXGRP);
	if (fmode & S_IROTH)
		dmode |= (S_IROTH | S_IXOTH);
	if (fmode & S_IWOTH)
		dmode |= (S_IWOTH | S_IXOTH);

	if ((p = util_dirname(path)) != NULL)
		(void) strcpy(outdir, p);
	else {
		DBGPRN(DBG_CDI)(errfp, "Directory path error: %s\n", outdir);
		MEM_FREE(url);
		CH_RET(EINVAL);
	}

	newdiscog = FALSE;
	if (stat(outdir, &stbuf) < 0) {
		if (errno == ENOENT)
			newdiscog = TRUE;
		else {
			ret = errno;
			DBGPRN(DBG_CDI)(errfp, "Cannot stat %s\n", outdir);
			MEM_FREE(url);
			CH_RET(ret);
		}
	}

	/* Make directories and fix perms */
	if (!util_mkdir(outdir, (mode_t) dmode)) {
		DBGPRN(DBG_CDI)(errfp, "Cannot create directory %s\n", outdir);
		MEM_FREE(url);
		CH_RET(EIO);
	}

	(void) unlink(path);

	if ((fp = fopen(path, "w")) == NULL) {
		ret = errno;
		DBGPRN(DBG_CDI)(errfp,
			"Cannot open file for writing: %s\n", path);
		MEM_FREE(url);
		CH_RET(ret);
	}
	(void) chmod(path, (mode_t) fmode);

	DBGPRN(DBG_CDI)(errfp, "Writing local Discography: %s\n", path);

	err = !cdinfo_out_discog(fp, s, url, outdir);
	(void) fclose(fp);

	if (newdiscog) {
		char	*cmd,
			*gpath;

		/* Generate local discography index for this genre */

		gpath = cdinfo_genre_path(cdinfo_db.disc.genre);
		cmd = (char *) MEM_ALLOC("genidx_cmd",
			strlen(gpath) + STR_BUF_SZ
		);
#ifdef __VMS
		(void) sprintf(cmd, "genidx %s", gpath);
#else
		(void) sprintf(cmd, "genidx %s >/dev/null 2>&1 &", gpath);
#endif

		DBGPRN(DBG_CDI)(errfp,
			"Generating local discography index for %s\n", gpath);
		(void) util_runcmd(cmd, cdinfo_clinfo->workproc,
				   cdinfo_clinfo->arg);
		MEM_FREE(cmd);
	}

	MEM_FREE(url);
	CH_RET(err ? EIO : 0);
	/*NOTREACHED*/
}


/*
 * cdinfo_clear
 *	Clear the in-core cdinfo_incore_t structure
 *
 * Args:
 *	reload - Whether this operation is due to a reload of the CD info
 *		(We don't want to clear a play sequence in this case).
 *
 * Return:
 *	Nothing
 */
void
cdinfo_clear(bool_t reload)
{
	int			i;
	cdinfo_url_t		*u,
				*v;
	cdinfo_credit_t		*p,
				*q;
	cdinfo_segment_t	*r,
				*s;

	/* Cancel pending queries */
	cdinfo_load_cancel();

	/* Disc */
	cdinfo_db.disc.compilation = FALSE;

	if (cdinfo_db.disc.artistfname.dispname != NULL) {
		MEM_FREE(cdinfo_db.disc.artistfname.dispname);
		cdinfo_db.disc.artistfname.dispname = NULL;
	}
	if (cdinfo_db.disc.artistfname.lastname != NULL) {
		MEM_FREE(cdinfo_db.disc.artistfname.lastname);
		cdinfo_db.disc.artistfname.lastname = NULL;
	}
	if (cdinfo_db.disc.artistfname.firstname != NULL) {
		MEM_FREE(cdinfo_db.disc.artistfname.firstname);
		cdinfo_db.disc.artistfname.firstname = NULL;
	}
	if (cdinfo_db.disc.artistfname.the != NULL) {
		MEM_FREE(cdinfo_db.disc.artistfname.the);
		cdinfo_db.disc.artistfname.the = NULL;
	}
	if (cdinfo_db.disc.artist != NULL) {
		MEM_FREE(cdinfo_db.disc.artist);
		cdinfo_db.disc.artist = NULL;
	}
	if (cdinfo_db.disc.title != NULL) {
		MEM_FREE(cdinfo_db.disc.title);
		cdinfo_db.disc.title = NULL;
	}
	if (cdinfo_db.disc.sorttitle != NULL) {
		MEM_FREE(cdinfo_db.disc.sorttitle);
		cdinfo_db.disc.sorttitle = NULL;
	}
	if (cdinfo_db.disc.title_the != NULL) {
		MEM_FREE(cdinfo_db.disc.title_the);
		cdinfo_db.disc.title_the = NULL;
	}
	if (cdinfo_db.disc.year != NULL) {
		MEM_FREE(cdinfo_db.disc.year);
		cdinfo_db.disc.year = NULL;
	}
	if (cdinfo_db.disc.label != NULL) {
		MEM_FREE(cdinfo_db.disc.label);
		cdinfo_db.disc.label = NULL;
	}
	if (cdinfo_db.disc.genre != NULL) {
		MEM_FREE(cdinfo_db.disc.genre);
		cdinfo_db.disc.genre = NULL;
	}
	if (cdinfo_db.disc.genre2 != NULL) {
		MEM_FREE(cdinfo_db.disc.genre2);
		cdinfo_db.disc.genre2 = NULL;
	}
	if (cdinfo_db.disc.dnum != NULL) {
		MEM_FREE(cdinfo_db.disc.dnum);
		cdinfo_db.disc.dnum = NULL;
	}
	if (cdinfo_db.disc.tnum != NULL) {
		MEM_FREE(cdinfo_db.disc.tnum);
		cdinfo_db.disc.tnum = NULL;
	}
	if (cdinfo_db.disc.region != NULL) {
		MEM_FREE(cdinfo_db.disc.region);
		cdinfo_db.disc.region = NULL;
	}
	if (cdinfo_db.disc.notes != NULL) {
		MEM_FREE(cdinfo_db.disc.notes);
		cdinfo_db.disc.notes = NULL;
	}
	if (cdinfo_db.disc.mediaid != NULL) {
		MEM_FREE(cdinfo_db.disc.mediaid);
		cdinfo_db.disc.mediaid = NULL;
	}
	if (cdinfo_db.disc.muiid != NULL) {
		MEM_FREE(cdinfo_db.disc.muiid);
		cdinfo_db.disc.muiid = NULL;
	}
	if (cdinfo_db.disc.titleuid != NULL) {
		MEM_FREE(cdinfo_db.disc.titleuid);
		cdinfo_db.disc.titleuid = NULL;
	}
	if (cdinfo_db.disc.revision != NULL) {
		MEM_FREE(cdinfo_db.disc.revision);
		cdinfo_db.disc.revision = NULL;
	}
	if (cdinfo_db.disc.revtag != NULL) {
		MEM_FREE(cdinfo_db.disc.revtag);
		cdinfo_db.disc.revtag = NULL;
	}
	if (cdinfo_db.disc.certifier != NULL) {
		MEM_FREE(cdinfo_db.disc.certifier);
		cdinfo_db.disc.certifier = NULL;
	}
	if (cdinfo_db.disc.langid != NULL) {
		MEM_FREE(cdinfo_db.disc.langid);
		cdinfo_db.disc.langid = NULL;
	}
	for (p = cdinfo_db.disc.credit_list; p != NULL; p = q) {
		q = p->next;
		if (p->crinfo.name != NULL)
			MEM_FREE(p->crinfo.name);
		if (p->crinfo.fullname.dispname != NULL)
			MEM_FREE(p->crinfo.fullname.dispname);
		if (p->crinfo.fullname.lastname != NULL)
			MEM_FREE(p->crinfo.fullname.lastname);
		if (p->crinfo.fullname.firstname != NULL)
			MEM_FREE(p->crinfo.fullname.firstname);
		if (p->crinfo.fullname.the != NULL)
			MEM_FREE(p->crinfo.fullname.the);
		if (p->notes != NULL)
			MEM_FREE(p->notes);
		MEM_FREE(p);
	}
	cdinfo_db.disc.credit_list = NULL;

	for (r = cdinfo_db.disc.segment_list; r != NULL; r = s) {
		s = r->next;
		if (r->name != NULL)
			MEM_FREE(r->name);
		if (r->notes != NULL)
			MEM_FREE(r->notes);
		if (r->start_track != NULL)
			MEM_FREE(r->start_track);
		if (r->start_frame != NULL)
			MEM_FREE(r->start_frame);
		if (r->end_track != NULL)
			MEM_FREE(r->end_track);
		if (r->end_frame != NULL)
			MEM_FREE(r->end_frame);
		for (p = r->credit_list; p != NULL; p = q) {
			q = p->next;
			if (p->crinfo.name != NULL)
				MEM_FREE(p->crinfo.name);
			if (p->crinfo.fullname.dispname != NULL)
				MEM_FREE(p->crinfo.fullname.dispname);
			if (p->crinfo.fullname.lastname != NULL)
				MEM_FREE(p->crinfo.fullname.lastname);
			if (p->crinfo.fullname.firstname != NULL)
				MEM_FREE(p->crinfo.fullname.firstname);
			if (p->crinfo.fullname.the != NULL)
				MEM_FREE(p->crinfo.fullname.the);
			if (p->notes != NULL)
				MEM_FREE(p->notes);
			MEM_FREE(p);
		}
		MEM_FREE(r);
	}
	cdinfo_db.disc.segment_list = NULL;

	/* Tracks */
	for (i = MAXTRACK-1; i >= 0; i--) {
		if (cdinfo_db.track[i].artistfname.dispname != NULL) {
			MEM_FREE(cdinfo_db.track[i].artistfname.dispname);
			cdinfo_db.track[i].artistfname.dispname = NULL;
		}
		if (cdinfo_db.track[i].artistfname.lastname != NULL) {
			MEM_FREE(cdinfo_db.track[i].artistfname.lastname);
			cdinfo_db.track[i].artistfname.lastname = NULL;
		}
		if (cdinfo_db.track[i].artistfname.firstname != NULL) {
			MEM_FREE(cdinfo_db.track[i].artistfname.firstname);
			cdinfo_db.track[i].artistfname.firstname = NULL;
		}
		if (cdinfo_db.track[i].artistfname.the != NULL) {
			MEM_FREE(cdinfo_db.track[i].artistfname.the);
			cdinfo_db.track[i].artistfname.the = NULL;
		}
		if (cdinfo_db.track[i].artist != NULL) {
			MEM_FREE(cdinfo_db.track[i].artist);
			cdinfo_db.track[i].artist = NULL;
		}
		if (cdinfo_db.track[i].title != NULL) {
			MEM_FREE(cdinfo_db.track[i].title);
			cdinfo_db.track[i].title = NULL;
		}
		if (cdinfo_db.track[i].sorttitle != NULL) {
			MEM_FREE(cdinfo_db.track[i].sorttitle);
			cdinfo_db.track[i].sorttitle = NULL;
		}
		if (cdinfo_db.track[i].title_the != NULL) {
			MEM_FREE(cdinfo_db.track[i].title_the);
			cdinfo_db.track[i].title_the = NULL;
		}
		if (cdinfo_db.track[i].year != NULL) {
			MEM_FREE(cdinfo_db.track[i].year);
			cdinfo_db.track[i].year = NULL;
		}
		if (cdinfo_db.track[i].label != NULL) {
			MEM_FREE(cdinfo_db.track[i].label);
			cdinfo_db.track[i].label = NULL;
		}
		if (cdinfo_db.track[i].genre != NULL) {
			MEM_FREE(cdinfo_db.track[i].genre);
			cdinfo_db.track[i].genre = NULL;
		}
		if (cdinfo_db.track[i].genre2 != NULL) {
			MEM_FREE(cdinfo_db.track[i].genre2);
			cdinfo_db.track[i].genre2 = NULL;
		}
		if (cdinfo_db.track[i].bpm != NULL) {
			MEM_FREE(cdinfo_db.track[i].bpm);
			cdinfo_db.track[i].bpm = NULL;
		}
		if (cdinfo_db.track[i].notes != NULL) {
			MEM_FREE(cdinfo_db.track[i].notes);
			cdinfo_db.track[i].notes = NULL;
		}
		if (cdinfo_db.track[i].isrc != NULL) {
			MEM_FREE(cdinfo_db.track[i].isrc);
			cdinfo_db.track[i].isrc = NULL;
		}
		for (p = cdinfo_db.track[i].credit_list; p != NULL; p = q) {
			q = p->next;
			if (p->crinfo.name != NULL)
				MEM_FREE(p->crinfo.name);
			if (p->crinfo.fullname.dispname != NULL)
				MEM_FREE(p->crinfo.fullname.dispname);
			if (p->crinfo.fullname.lastname != NULL)
				MEM_FREE(p->crinfo.fullname.lastname);
			if (p->crinfo.fullname.firstname != NULL)
				MEM_FREE(p->crinfo.fullname.firstname);
			if (p->crinfo.fullname.the != NULL)
				MEM_FREE(p->crinfo.fullname.the);
			if (p->notes != NULL)
				MEM_FREE(p->notes);
			MEM_FREE(p);
		}
		cdinfo_db.track[i].credit_list = NULL;
	}

	/* Album-related URLs */
	for (u = v = cdinfo_db.disc_url_list; u != NULL; u = v) {
		v = u->next;
		if (u->disptext != NULL)
			MEM_FREE(u->disptext);
		if (u->modifier != NULL)
			MEM_FREE(u->modifier);
		if (u->keyname != NULL)
			MEM_FREE(u->keyname);
		MEM_FREE(u);
	}
	cdinfo_db.disc_url_list = NULL;

	/* Misc */
	if (!reload && cdinfo_db.playorder != NULL) {
		MEM_FREE(cdinfo_db.playorder);
		cdinfo_db.playorder = NULL;
	}

	cdinfo_db.discid = 0;
	cdinfo_db.flags = 0;

	/* Deallocate the matchlist */
	cdinfo_free_matchlist();
}


/*
 * cdinfo_genre_name
 *	Given a genre ID, return a pointer to the associated genre structure.
 *
 * Args:
 *	id - The genre ID string.
 *
 * Return:
 *	Pointer to the genre structure.
 */
cdinfo_genre_t *
cdinfo_genre(char *id)
{
	cdinfo_genre_t	*gp,
			*sgp;

	if (id == NULL)
		return NULL;

	for (gp = cdinfo_db.genrelist; gp != NULL; gp = gp->next) {
		if (gp->id != NULL && strcmp(gp->id, id) == 0)
			return (gp);

		for (sgp = gp->child; sgp != NULL; sgp = sgp->next) {
			if (sgp->id != NULL && strcmp(sgp->id, id) == 0)
				return (sgp);
		}
	}

	/* Can't find genre entry */
	return NULL;
}


/*
 * cdinfo_genre_name
 *	Given a genre ID, return the genre name string.
 *
 * Args:
 *	id - The genre ID string.
 *
 * Return:
 *	Pointer to the genre name string.
 */
char *
cdinfo_genre_name(char *id)
{
	cdinfo_genre_t	*gp;
	static char	name[STR_BUF_SZ * 2 + 8];

	if (id == NULL)
		return ("");

	if ((gp = cdinfo_genre(id)) == NULL) {
		/* If genre entry is not found, just use the ID */
		return (id);
	}

	if (gp->parent == NULL) {
		/* Genre */
		(void) sprintf(name, "%.63s",
				gp->name == NULL ? id : gp->name);
		return (name);
	}
	else {
		/* Subgenre */
		(void) sprintf(name, "%.63s -> %.63s",
				gp->parent->name == NULL ?
					gp->parent->id : gp->parent->name,
				gp->name == NULL ? gp->id : gp->name);
		return (name);
	}

	/*NOTREACHED*/
}


/*
 * cdinfo_genre_path
 *	Return a relative file path name for a given genre ID.
 *
 * Args:
 *	id - The genre ID string
 *
 * Return:
 *	Pointer to the path name string.
 */
char *
cdinfo_genre_path(char *id)
{
	char		*cp,
			*cp2;
	cdinfo_genre_t	*gp;
	static char	buf[STR_BUF_SZ * 2 + 2];

	if (id == NULL)
#ifdef __VMS
		return ("Unknown.Unknown");
#else
		return ("Unknown/Unknown");
#endif

	if ((gp = cdinfo_genre(id)) == NULL) {
		/* If genre entry is not found, just use the ID */
		return (id);
	}

	if (gp->parent == NULL) {
		/* Genre */
		cp = cdinfo_mkpath(
			gp->name == NULL ? gp->id : gp->name,
			STR_BUF_SZ * 2
		);
		if (cp == NULL) {
			CDINFO_FATAL(app_data.str_nomemory);
			return ("");
		}

		(void) strcpy(buf, cp);
		MEM_FREE(cp);

		return (buf);
	}
	else {
		/* Subgenre */
		cp = cdinfo_mkpath(
			gp->parent->name == NULL ?
				gp->parent->id : gp->parent->name,
			STR_BUF_SZ
		);
		cp2 = cdinfo_mkpath(
			gp->name == NULL ? gp->id : gp->name,
			STR_BUF_SZ
		);
		if (cp == NULL || cp2 == NULL) {
			CDINFO_FATAL(app_data.str_nomemory);
			return ("");
		}

		(void) strcpy(buf, cp);
#ifdef __VMS
		(void) strcat(buf, ".");
#else
		(void) strcat(buf, "/");
#endif
		(void) strcat(buf, cp2);
		MEM_FREE(cp);
		MEM_FREE(cp2);

		return (buf);
	}

	/*NOTREACHED*/
}


/*
 * cdinfo_region_name
 *	Given a region ID, return the region name.
 *
 * Args:
 *	id - The region ID string.
 *
 * Return:
 *	Pointer to the region name string.
 */
char *
cdinfo_region_name(char *id)
{
	cdinfo_region_t	*rp;

	if (id == NULL)
		return ("");

	for (rp = cdinfo_db.regionlist; rp != NULL; rp = rp->next) {
		if (rp->id != NULL && strcmp(rp->id, id) == 0)
			return (rp->name);
	}
	return ("");
}


/*
 * cdinfo_role
 *	Given a role ID, return a pointer to the role list element.
 *
 * Args:
 *	id - The role ID string.
 *
 * Return:
 *	Pointer to the role list element.
 */
cdinfo_role_t *
cdinfo_role(char *id)
{
	cdinfo_role_t	*rp,
			*srp;

	if (id == NULL)
		return NULL;

	for (rp = cdinfo_db.rolelist; rp != NULL; rp = rp->next) {
		if (rp->id != NULL && strcmp(rp->id, id) == 0)
			return (rp);

		for (srp = rp->child; srp != NULL; srp = srp->next) {
			if (srp->id != NULL && strcmp(srp->id, id) == 0)
				return (srp);
		}
	}

	return NULL;
}


/*
 * cdinfo_role_name
 *	Given a role ID, return the role name.
 *
 * Args:
 *	id - The role ID string.
 *
 * Return:
 *	Pointer to the role name string.
 */
char *
cdinfo_role_name(char *id)
{
	cdinfo_role_t	*rp;

	if ((rp = cdinfo_role(id)) == NULL)
		return ("unknown role");

	return (rp->name);
}


/*
 * cdinfo_load_cancel
 *	Cancel asynchronous CD info load operation, if active.
 *
 * Args:
 *	None.
 *
 * Return:
 *	nothing.
 */
void
cdinfo_load_cancel(void)
{
	if (child_pid > 0) {
		/* Kill child process */
		(void) kill(child_pid, SIGTERM);
		child_pid = 0;
	}
}


/*
 * cdinfo_cddb_ver
 *	Returns the version string of the CDDB interface
 *
 * Args:
 *	None.
 *
 * Return:
 *	The CDDB interface version number
 */
int
cdinfo_cddb_ver(void)
{
	return (cddb_ifver());
}


/*
 * cdinfo_cddbctrl_ver
 *	Returns the version string of the CDDB control
 *
 * Args:
 *	None.
 *
 * Return:
 *	The version string.
 */
char *
cdinfo_cddbctrl_ver(void)
{
	static char	buf[STR_BUF_SZ];

	if (cdinfo_db.ctrl_ver == NULL)
		return ("");

	(void) sprintf(buf, "%.63s\n", cdinfo_db.ctrl_ver);
	return (buf);
}


/*
 * cdinfo_cddb_iscfg
 *	Returns whether CDDB is configured in the cdinfoPath parameter.
 *
 * Args:
 *	None.
 *
 * Return:
 *	TRUE - CDDB is configured
 *	FALSE - CDDB not configured
 */
bool_t
cdinfo_cddb_iscfg(void)
{
	cdinfo_path_t	*pp;

	for (pp = cdinfo_db.pathlist; pp != NULL; pp = pp->next) {
		if (pp->type == CDINFO_RMT)
			return TRUE;
	}
	return FALSE;
}


/*
 * cdinfo_wwwwarp_parmload
 *	Load the wwwwarp configuration file and initialize structures.
 *
 * Args:
 *	None.
 *
 * Return:
 *	Nothing.
 */
void
cdinfo_wwwwarp_parmload(void)
{
	FILE		*fp;
	char		*buf,
			*name,
			*p,
			*q,
			trypath[FILE_PATH_SZ];
	w_ent_t		*wp,
			*wp1,
			*wp2,
			*wp3;
	bool_t		newmenu;
#ifndef SYNCHRONOUS
	pid_t		cpid;
	waitret_t	stat_val;
	int		ret,
			pfd[2];

	if (PIPE(pfd) < 0) {
		DBGPRN(DBG_CDI)(errfp,
			"cdinfo_wwwwarp_parmload: pipe failed (errno=%d)\n",
			errno);
		return;
	}
	
	switch (cpid = FORK()) {
	case 0:
		/* Child */
		ischild = TRUE;

		/* Exit on receipt of SIGTERM */
		(void) signal(SIGTERM, cdinfo_onterm);

		/* Ignore SIGALRM */
		(void) signal(SIGALRM, SIG_IGN);

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

		/* Try the user's own wwwwarp.cfg, if it exists */
		(void) sprintf(trypath, USR_DSCFG_PATH,
				util_homedir(util_get_ouid()), WWWWARP_CFG);
		DBGPRN(DBG_CDI)(errfp,
			"Loading wwwwarp parameters: %s\n", trypath);
		fp = fopen(trypath, "r");

		if (fp == NULL) {
			DBGPRN(DBG_CDI)(errfp, "Cannot open %s\n", trypath);

			/* Try the system-wide wwwwarp.cfg, if it exists */
			(void) sprintf(trypath, SYS_DSCFG_PATH,
					app_data.libdir, WWWWARP_CFG);
			DBGPRN(DBG_CDI)(errfp,
				"Loading wwwwarp parameters: %s\n", trypath);
			fp = fopen(trypath, "r");
		}

		if (fp == NULL) {
			DBGPRN(DBG_CDI)(errfp, "Cannot open %s\n", trypath);

			(void) close(pfd[1]);
			exit(0);
		}
		
		while ((buf = cdinfo_fgetline(fp)) != NULL) {
			(void) write(pfd[1], buf, strlen(buf));
			(void) write(pfd[1], "\n", 1);
			MEM_FREE(buf);
		}
		(void) write(pfd[1], ".\n", 2);

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

		exit(0);
		/*NOTREACHED*/

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

	default:
		/* Parent */
		child_pid = cpid;

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

		if ((fp = fdopen(pfd[0], "r")) == NULL) {
			DBGPRN(DBG_CDI)(errfp,
			"cdinfo_common_parmload: read pipe fdopen failed\n");
			return;
		}
		break;
	}
#else
	/* Try the user's own wwwwarp.cfg, if it exists */
	(void) sprintf(trypath, USR_DSCFG_PATH,
			util_homedir(util_get_ouid()), WWWWARP_CFG);
	DBGPRN(DBG_CDI)(errfp, "Loading wwwwarp parameters: %s\n", trypath);
	fp = fopen(trypath, "r");

	if (fp == NULL) {
		DBGPRN(DBG_CDI)(errfp, "Cannot open %s\n", trypath);

		/* Try the system-wide wwwwarp.cfg, if it exists */
		(void) sprintf(trypath, SYS_DSCFG_PATH,
				app_data.libdir, WWWWARP_CFG);
		DBGPRN(DBG_CDI)(errfp, "Loading wwwwarp parameters: %s\n",
			trypath);
		fp = fopen(trypath, "r");
	}

	if (fp == NULL) {
		DBGPRN(DBG_CDI)(errfp, "Cannot open %s\n", trypath);
		return;
	}

#endif	/* SYNCHRONOUS */

	newmenu = FALSE;
	wp1 = wp2 = NULL;
	name = NULL;

	/* Read in common parameters */
	while ((buf = cdinfo_fgetline(fp)) != NULL) {
		if (strcmp(buf, ".") == 0) {
			/* Done */
			MEM_FREE(buf);
			break;
		}

		p = q = cdinfo_skip_whitespace(buf);

		if (*p == '#' || *p == '!' || *p == '\0') {
			/* Skip comments and blank lines */
			MEM_FREE(buf);
			continue;
		}

		if (*p == '{' || *p == '}') {
			/* Do nothing.  These are only visual aids for
			 * the user.
			 */
			MEM_FREE(buf);
			continue;
		}

		if (strncmp(p, "Menu", 4) == 0) {
			/* Start of new menu */
			p = cdinfo_skip_whitespace(p + 4);
			if (*p == '\0') {
				DBGPRN(DBG_CDI)(errfp, "Error in %s file:\n",
					WWWWARP_CFG);
				DBGPRN(DBG_CDI)(errfp, "line: %s\n", buf);
				MEM_FREE(buf);
				break;
			}
			p = cdinfo_skip_whitespace(p);
			q = cdinfo_skip_nowhitespace(p);
			*q = '\0';

			if (!util_newstr(&name, p)) {
				CDINFO_FATAL(app_data.str_nomemory);
				return;
			}

			newmenu = TRUE;
		}
		else {
			/* Menu entry */
			wp = (w_ent_t *)(void *) MEM_ALLOC(
				"w_ent_t", sizeof(w_ent_t)
			);
			if (wp == NULL) {
				CDINFO_FATAL(app_data.str_nomemory);
				return;
			}
			(void) memset(wp, 0, sizeof(w_ent_t));

			if (newmenu) {
				if (cdinfo_db.wwwwarp_list == NULL)
					cdinfo_db.wwwwarp_list = wp1 = wp;
				else {
					wp1->nextmenu = wp;
					wp1 = wp;
				}
			}
			else if (wp2 == NULL) {
				DBGPRN(DBG_CDI)(errfp, "Error in %s file:\n",
					WWWWARP_CFG);
				DBGPRN(DBG_CDI)(errfp, "line: %s\n", buf);
				MEM_FREE(buf);
				break;
			}
			else
				wp2->nextent = wp;

			wp2 = wp;

			if (!util_newstr(&wp->name, name)) {
				CDINFO_FATAL(app_data.str_nomemory);
				return;
			}

			/* Description */
			if (*p != '"') {
				DBGPRN(DBG_CDI)(errfp, "Error in %s file:\n",
					WWWWARP_CFG);
				DBGPRN(DBG_CDI)(errfp, "line: %s\n", buf);
				wp->type = WTYPE_NULL;
				MEM_FREE(buf);
				break;
			}
			p++;

			if ((q = strchr(p, '"')) == NULL) {
				DBGPRN(DBG_CDI)(errfp, "Error in %s file:\n",
					WWWWARP_CFG);
				DBGPRN(DBG_CDI)(errfp, "line: %s\n", buf);
				wp->type = WTYPE_NULL;
				MEM_FREE(buf);
				break;
			}
			*q = '\0';

			if (!util_newstr(&wp->desc, p)) {
				CDINFO_FATAL(app_data.str_nomemory);
				return;
			}

			p = cdinfo_skip_whitespace(q+1);

			/* Shortcut key */
			if (strncmp(p, "f.", 2) != 0) {
				char	*cp;

				q = cdinfo_skip_nowhitespace(p);
				if (*q == '\0') {
					DBGPRN(DBG_CDI)(errfp,
						"Error in %s file:\n",
						WWWWARP_CFG);
					DBGPRN(DBG_CDI)(errfp,
						"line: %s\n", buf);
					MEM_FREE(buf);
					break;
				}
				*q = '\0';

				if ((cp = strchr(p, '-')) == NULL) {
					DBGPRN(DBG_CDI)(errfp,
						"Error in %s file:\n",
						WWWWARP_CFG);
					DBGPRN(DBG_CDI)(errfp,
						"line: %s\n", buf);
					MEM_FREE(buf);
					break;
				}
				*cp = '\0';

				if (!util_newstr(&wp->modifier, p)) {
					CDINFO_FATAL(app_data.str_nomemory);
					return;
				}
				if (!util_newstr(&wp->keyname, cp+1)) {
					CDINFO_FATAL(app_data.str_nomemory);
					return;
				}

				p = cdinfo_skip_whitespace(q+1);
			}
			else {
				wp->modifier = NULL;
				wp->keyname = NULL;
			}

			/* Type */
			if (strncmp(p, "f.title", 7) == 0) {
				q = p + 7;
				wp->type = WTYPE_TITLE;
			}
			else if (strncmp(p, "f.separator2", 12) == 0) {
				q = p + 12;
				wp->type = WTYPE_SEP2;
			}
			else if (strncmp(p, "f.separator", 11) == 0) {
				q = p + 11;
				wp->type = WTYPE_SEP;
			}
			else if (strncmp(p, "f.about", 7) == 0) {
				q = p + 7;
				wp->type = WTYPE_ABOUT;
			}
			else if (strncmp(p, "f.help", 6) == 0) {
				q = p + 6;
				wp->type = WTYPE_HELP;
			}
			else if (strncmp(p, "f.goto", 6) == 0) {
				q = p + 6;
				wp->type = WTYPE_GOTO;
			}
			else if (strncmp(p, "f.menu", 6) == 0) {
				q = p + 6;
				wp->type = WTYPE_SUBMENU;
			}
			else if (strncmp(p, "f.infoBrowser", 13) == 0) {
				q = p + 13;
				wp->type = WTYPE_BROWSER;
			}
			else if (strncmp(p, "f.discog", 8) == 0) {
				q = p + 8;
				wp->type = WTYPE_DISCOG;
			}
			else if (strncmp(p, "f.generalSites", 14) == 0) {
				q = p + 14;
				wp->type = WTYPE_GEN;
			}
			else if (strncmp(p, "f.albumSites", 12) == 0) {
				q = p + 12;
				wp->type = WTYPE_ALBUM;
			}
			else if (strncmp(p, "f.submitURL", 11) == 0) {
				q = p + 11;
				wp->type = WTYPE_SUBMITURL;
			}
			else {
				DBGPRN(DBG_CDI)(errfp, "Error in %s file:\n",
					WWWWARP_CFG);
				DBGPRN(DBG_CDI)(errfp, "line: %s\n", buf);
				wp->type = WTYPE_NULL;
				MEM_FREE(buf);
				break;
			}

			p = cdinfo_skip_whitespace(q);

			/* Args */
			switch(wp->type) {
			case WTYPE_SUBMENU:
				q = cdinfo_skip_nowhitespace(p);
				*q = '\0';

				if (!util_newstr(&wp->arg, p)) {
					CDINFO_FATAL(app_data.str_nomemory);
					return;
				}
				break;

			case WTYPE_GOTO:
			case WTYPE_DISCOG:
				if (*p == '"')
					p++;

				q = cdinfo_skip_nowhitespace(p);
				if (*(q-1) == '"')
					*(q-1) = '\0';
				else
					*q = '\0';

				if (!util_newstr(&wp->arg, p)) {
					CDINFO_FATAL(app_data.str_nomemory);
					return;
				}

				/* Scan URL and record its attributes */
				cdinfo_scan_url_attrib(wp->arg, &wp->attrib);

				/* Save menu pointer for later use */
				if (wp->type == WTYPE_DISCOG)
					cdinfo_discog = wp;
				else if (strcmp(wp->name,
						"searchMenu") == 0 &&
					 util_strncasecmp(wp->desc,
							  "CDDB", 4) == 0) {
					cdinfo_scddb = wp;
				}

				break;

			case WTYPE_BROWSER:
			case WTYPE_ALBUM:
				wp->attrib.acnt++;
				wp->attrib.dcnt++;
				wp->attrib.tcnt++;
				wp->attrib.icnt++;
				wp->attrib.ccnt++;
				break;

			default:
				break;
			}

			newmenu = FALSE;
		}

		MEM_FREE(buf);
	}
	if (name != NULL)
		MEM_FREE(name);

	(void) fclose(fp);

	/* Bind submenus */
	for (wp = cdinfo_db.wwwwarp_list; wp != NULL; wp = wp->nextmenu) {
	    for (wp2 = wp; wp2 != NULL; wp2 = wp2->nextent) {
		if (wp2->type != WTYPE_SUBMENU)
			continue;

		for (wp3 = cdinfo_db.wwwwarp_list; wp3 != NULL;
		     wp3 = wp3->nextmenu) {
			if (strcmp(wp2->arg, wp3->name) == 0) {
				wp2->submenu = wp3;
				break;
			}
		}
		if (wp3 == NULL) {
			DBGPRN(DBG_CDI)(errfp,
				"Error in %s file: %s not found\n",
				WWWWARP_CFG, wp2->arg);
		}
	    }
	}

	/* Check for circular menu links */
	for (wp = cdinfo_db.wwwwarp_list; wp != NULL; wp = wp->nextmenu) {
		if (!cdinfo_wwwmenu_chk(wp, TRUE)) {
			DBGPRN(DBG_CDI)(errfp,
				"\nError in %s file: circular menu link.\n",
				WWWWARP_CFG);
		}
	}

#ifndef SYNCHRONOUS
	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_CDI)(errfp,
			"cdinfo_wwwwarp_parmload: child exited (status=%d)\n",
			WEXITSTATUS(stat_val));
		}
	    }
	    else if (WIFSIGNALED(stat_val)) {
		    DBGPRN(DBG_CDI)(errfp,
			"cdinfo_wwwwarp_parmload: child killed.\n");
	    }
	}

	child_pid = 0;
#endif
}


/*
 * cdinfo_wwwwarp_ckkey
 *	Given a shortcut key, check to see if it is unique amongst
 *	existing wwwwarp menu shortcuts.
 *
 * Args:
 *	keyname - The key name to check
 *
 * Return:
 *	TRUE  - is unique
 *	FALSE - not unique
 */
bool_t
cdinfo_wwwwarp_ckkey(char *keyname)
{
	w_ent_t	*p,
		*q;

	if (keyname == NULL)
		return FALSE;

	for (p = cdinfo_db.wwwwarp_list; p != NULL; p = p->nextmenu) {
		for (q = p; q != NULL; q = q->nextent) {
			if (q->keyname != NULL &&
			    util_strcasecmp(q->keyname, keyname) == 0)
				return FALSE;
		}
	}
	return TRUE;
}


/*
 * cdinfo_curfileupd
 *	Update the current disc info file.
 *
 * Args:
 *	None.
 *
 * Return:
 *	Nothing
 */
void
cdinfo_curfileupd(void)
{
#ifndef __VMS
	FILE		*fp;
	struct stat	stbuf;
	char		*artist,
			*title,
			*ttitle,
			str[FILE_PATH_SZ * 3];
	static char	prev[FILE_PATH_SZ * 3],
			prev_artist[STR_BUF_SZ],
			prev_title[STR_BUF_SZ],
			prev_ttitle[STR_BUF_SZ];
	curstat_t	*s;
	bool_t		playing,
			changed;

	if (!app_data.write_curfile ||			/* disabled */
	    strncmp(app_data.device, "(sim", 4) == 0)	/* demo */
		return;

	if (cdinfo_clinfo->curstat_addr == NULL)
		return;

	s = cdinfo_clinfo->curstat_addr();

	if (!s->devlocked)
		/* Don't write curfile if we don't own the device */
		return;

	playing = FALSE;
	artist = title = ttitle = NULL;

	if (s->mode != MOD_NODISC) {
		char	modestr[24];

		artist = cdinfo_db.disc.artist;
		title = cdinfo_db.disc.title;

		switch (s->mode) {
		case MOD_PLAY:
		case MOD_PAUSE:
		case MOD_SAMPLE:
			playing = TRUE;

			(void) sprintf(modestr, "Playing track %d",
				       s->cur_trk);

			if (s->cur_trk > 0) {
				int	n;

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

				if (s->trkinfo[n].trkno != s->cur_trk) {
					for (n = 0; n < MAXTRACK; n++) {
						if (s->trkinfo[n].trkno ==
						    s->cur_trk)
							break;
					}
				}

				ttitle = cdinfo_db.track[n].title;
			}
			break;

		case MOD_STOP:
			(void) strcpy(modestr, "Stopped");
			break;

		default:
			modestr[0] = '\0';
			break;
		}

		(void) sprintf(str,
			       "%s\t\t%s\n%s\t\t%s\n%s\t\t%s\n",
			       "Device:", s->curdev == NULL ? "-" : s->curdev,
			       "Genre:",
			       cdinfo_genre_name(cdinfo_db.disc.genre),
			       "Status:", modestr);
	}
	else {
		(void) sprintf(str,
			       "%s\t\t%s\n%s\t\t%s\n%s\t\t%s\n",
			       "Device:", s->curdev == NULL ? "-" : s->curdev,
			       "Genre:", "-",
			       "Status:", "No_Disc");
	}

	changed = FALSE;

	if (playing) {
		if (ttitle == NULL && prev_ttitle[0] != '\0')
			changed = TRUE;
		else if (ttitle != NULL &&
			 strncmp(ttitle, prev_ttitle, STR_BUF_SZ - 1) != 0)
			changed = TRUE;
	}

	if (s->mode != MOD_NODISC) {
		if (artist == NULL && prev_artist[0] != '\0')
			changed = TRUE;
		else if (title == NULL && prev_title[0] != '\0')
			changed = TRUE;
		else if (artist != NULL && 
			 strncmp(artist, prev_artist, STR_BUF_SZ - 1) != 0)
			changed = TRUE;
		else if (title != NULL && 
			 strncmp(title, prev_title, STR_BUF_SZ - 1) != 0)
			changed = TRUE;
	}

	if (strcmp(str, prev) != 0)
		changed = TRUE;

	if (!changed)
		return;

	/* Write to file */
	if (curfile[0] == '\0') {
		if (stat(app_data.device, &stbuf) < 0) {
			DBGPRN(DBG_CDI)(errfp,
				"\nCannot stat %s\n", app_data.device);
			return;
		}

		(void) sprintf(curfile, "%s/curr.%x",
			       TEMP_DIR, (int) stbuf.st_rdev);
	}

	/* Remove original file */
	if (UNLINK(curfile) < 0 && errno != ENOENT) {
		DBGPRN(DBG_CDI)(errfp, "\nCannot unlink old %s\n", curfile);
		return;
	}

	/* Write new file */
	if ((fp = fopen(curfile, "w")) == NULL) {
		DBGPRN(DBG_CDI)(errfp,
			"\nCannot open %s for writing\n", curfile);
		return;
	}

	DBGPRN(DBG_CDI)(errfp,
		"\nWriting current disc info file: %s\n", curfile);

	(void) fprintf(fp, "#\n# Xmcd current CD information\n#\n%s", str);

	if (s->mode != MOD_NODISC) {
		(void) fprintf(fp, "Disc:\t\t%s / %s\n",
			       artist == NULL ?
			       "<unknown artist>" : artist,
			       title == NULL ?
			       "<unknown disc title>" : title);
	}
	else {
		(void) fprintf(fp, "Disc:\t\t-\n");
	}

	if (playing) {
		(void) fprintf(fp, "Track:\t\t%s\n",
			       ttitle == NULL ?
			       "<unknown track title>" : ttitle);
	}
	else {
		(void) fprintf(fp, "Track:\t\t-\n");
	}

	(void) fclose(fp);
	(void) chmod(curfile, 0644);
	
	if (ttitle == NULL)
		prev_ttitle[0] = '\0';
	else {
		(void) strncpy(prev_ttitle, ttitle, STR_BUF_SZ - 1);
		prev_ttitle[STR_BUF_SZ - 1] = '\0';
	}

	if (artist == NULL)
		prev_artist[0] = '\0';
	else {
		(void) strncpy(prev_artist, artist, STR_BUF_SZ - 1);
		prev_artist[STR_BUF_SZ - 1] = '\0';
	}

	if (title == NULL)
		prev_title[0] = '\0';
	else {
		(void) strncpy(prev_title, title, STR_BUF_SZ - 1);
		prev_title[STR_BUF_SZ - 1] = '\0';
	}

	(void) strcpy(prev, str);
#endif	/* __VMS */
}


/*
 * cdinfo_issync
 *	Return a boolean value indicating whether we are running in
 *	synchronous mode.
 *
 * Args:
 *	None.
 *
 * Return:
 *	TRUE - synchronous mode
 *	FALSE - asynchronous mode
 */
bool_t
cdinfo_issync(void)
{
#ifdef SYNCHRONOUS
	return TRUE;
#else
	return FALSE;
#endif
}


/*
 * cdinfo_dump_incore
 *	Displays the contents of the cdinfo_incore_t structure.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Returns:
 *	Nothing.
 */
void
cdinfo_dump_incore(curstat_t *s)
{
	int		i,
			j;

	(void) fprintf(errfp,
		       "\nDumping the cdinfo_incore_t structure at 0x%lx:\n",
		       (unsigned long) &cdinfo_db);
	(void) fprintf(errfp, "ctrl_ver=%s\n",
			cdinfo_db.ctrl_ver == NULL ?
				"NULL" : cdinfo_db.ctrl_ver);
	(void) fprintf(errfp, "discid=%08x flags=0x%x\n",
		       cdinfo_db.discid, cdinfo_db.flags);
	(void) fprintf(errfp, "disc: ----------------------\n");
	(void) fprintf(errfp, "  revision=%s revtag=%s langid=%s\n",
		       cdinfo_db.disc.revision == NULL ?
				"NULL" : cdinfo_db.disc.revision,
		       cdinfo_db.disc.revtag == NULL ?
				"NULL" : cdinfo_db.disc.revtag,
		       cdinfo_db.disc.langid == NULL ?
				"NULL" : cdinfo_db.disc.langid);
	(void) fprintf(errfp, "  compilation=%d dnum=%s tnum=%s\n",
		       (int) cdinfo_db.disc.compilation,
		       cdinfo_db.disc.dnum == NULL ?
				"NULL" : cdinfo_db.disc.dnum,
		       cdinfo_db.disc.tnum == NULL ?
				"NULL" : cdinfo_db.disc.tnum);
	(void) fprintf(errfp,
		       "  fullname: dispname=%s "
		       "lastname=%s firstname=%s the=%s\n",
		       cdinfo_db.disc.artistfname.dispname == NULL ?
				"NULL" : cdinfo_db.disc.artistfname.dispname,
		       cdinfo_db.disc.artistfname.lastname == NULL ?
				"NULL" : cdinfo_db.disc.artistfname.lastname,
		       cdinfo_db.disc.artistfname.firstname == NULL ?
				"NULL" : cdinfo_db.disc.artistfname.firstname,
		       cdinfo_db.disc.artistfname.the == NULL ?
				"NULL" : cdinfo_db.disc.artistfname.the);
	(void) fprintf(errfp,
		       "  artist=%s title=%s\n  sorttitle=%s title_the=%s\n",
		       cdinfo_db.disc.artist == NULL ?
				"NULL" : cdinfo_db.disc.artist,
		       cdinfo_db.disc.title == NULL ?
				"NULL" : cdinfo_db.disc.title,
		       cdinfo_db.disc.sorttitle == NULL ?
				"NULL" : cdinfo_db.disc.sorttitle,
		       cdinfo_db.disc.title_the == NULL ?
				"NULL" : cdinfo_db.disc.title_the);
	(void) fprintf(errfp, "  year=%s label=%s genre=%s genre2=%s\n",
		       cdinfo_db.disc.year == NULL ?
				"NULL" : cdinfo_db.disc.year,
		       cdinfo_db.disc.label == NULL ?
				"NULL" : cdinfo_db.disc.label,
		       cdinfo_db.disc.genre == NULL ?
				"NULL" : cdinfo_db.disc.genre,
		       cdinfo_db.disc.genre2 == NULL ?
				"NULL" : cdinfo_db.disc.genre2);
	(void) fprintf(errfp, "  mediaid=%s\n  muiid=%s titleuid=%s\n",
		       cdinfo_db.disc.mediaid == NULL ?
				"NULL" : cdinfo_db.disc.mediaid,
		       cdinfo_db.disc.muiid == NULL ?
				"NULL" : cdinfo_db.disc.muiid,
		       cdinfo_db.disc.titleuid == NULL ?
				"NULL" : cdinfo_db.disc.titleuid);
	(void) fprintf(errfp, "  certifier=%s\n",
		       cdinfo_db.disc.certifier == NULL ?
				"NULL" : cdinfo_db.disc.certifier);
	if (cdinfo_db.disc.notes == NULL)
		(void) fprintf(errfp, "  notes=NULL\n");
	else
		(void) fprintf(errfp, "  notes=\n%s\n", cdinfo_db.disc.notes);

	if (cdinfo_db.disc.credit_list == NULL)
		(void) fprintf(errfp, "  credit_list=NULL\n");
	else {
		cdinfo_credit_t	*p;

		(void) fprintf(errfp, "credit_list=\n");
		for (i = 0, p = cdinfo_db.disc.credit_list; p != NULL;
		     p = p->next, i++) {
			(void) fprintf(errfp, "  credit[%d]\n", i);
			(void) fprintf(errfp, "    role=%s name=%s\n",
				    p->crinfo.role == NULL ?
					"NULL" : p->crinfo.role->name,
				    p->crinfo.name == NULL ?
					"NULL" : p->crinfo.name);
			(void) fprintf(errfp,
			       "    fullname: dispname=%s lastname=%s "
			       "firstname=%s the=%s\n",
			       p->crinfo.fullname.dispname == NULL ?
				    "NULL" :
				    p->crinfo.fullname.dispname,
			       p->crinfo.fullname.lastname == NULL ?
				    "NULL" :
				    p->crinfo.fullname.lastname,
			       p->crinfo.fullname.firstname == NULL ?
				    "NULL" :
				    p->crinfo.fullname.firstname,
			       p->crinfo.fullname.the == NULL ?
				    "NULL" :
				    p->crinfo.fullname.the);
			if (p->notes == NULL)
				(void) fprintf(errfp,
				    "    notes=NULL\n");
			else
				(void) fprintf(errfp,
				    "    notes=\n%s\n", p->notes);
		}
	}

	if (cdinfo_db.disc.segment_list == NULL)
		(void) fprintf(errfp, "  segment_list=NULL\n");
	else {
		cdinfo_segment_t	*q;

		(void) fprintf(errfp, "segment_list=\n");
		for (i = 0, q = cdinfo_db.disc.segment_list; q != NULL;
		     q = q->next, i++) {
			(void) fprintf(errfp, "  segment[%d]\n", i);
			(void) fprintf(errfp, "    name=%s start=%s/%s ",
				    q->name == NULL ? "NULL" : q->name,
				    q->start_track == NULL ?
					    "?" : q->start_track,
				    q->start_frame == NULL ?
					    "?" : q->start_frame);
			(void) fprintf(errfp, "end=%s/%s\n",
				    q->end_track == NULL ?
					    "?" : q->end_track,
				    q->end_frame == NULL ?
					    "?" : q->end_frame);
			if (q->notes == NULL)
				(void) fprintf(errfp, "    notes=NULL\n");
			else
				(void) fprintf(errfp,
					       "    notes=\n%s\n", q->notes);

			if (q->credit_list == NULL)
			    (void) fprintf(errfp, "    credit_list=NULL\n");
			else {
			    cdinfo_credit_t	*p;

			    for (j = 0, p = q->credit_list; p != NULL;
				 p = p->next, j++) {
				(void) fprintf(errfp, "    credit[%d]\n", j);
				(void) fprintf(errfp,
					    "      role=%s name=%s\n",
					    p->crinfo.role == NULL ?
						"NULL" : p->crinfo.role->name,
					    p->crinfo.name == NULL ?
						"NULL" : p->crinfo.name);
				(void) fprintf(errfp,
				       "      fullname: dispname=%s "
				       "lastname=%s firstname=%s the=%s\n",
				       p->crinfo.fullname.dispname == NULL ?
					    "NULL" :
					    p->crinfo.fullname.dispname,
				       p->crinfo.fullname.lastname == NULL ?
					    "NULL" :
					    p->crinfo.fullname.lastname,
				       p->crinfo.fullname.firstname == NULL ?
					    "NULL" :
					    p->crinfo.fullname.firstname,
				       p->crinfo.fullname.the == NULL ?
					    "NULL" :
					    p->crinfo.fullname.the);
					if (p->notes == NULL)
					    (void) fprintf(errfp,
						    "      notes=NULL\n");
					else
					    (void) fprintf(errfp,
						    "      notes=\n%s\n",
						    p->notes);
			    }
			}
		}
	}

	for (i = 0; i < (int) s->tot_trks; i++) {
		(void) fprintf(errfp, "track[%d] ----------------------\n", i);

		(void) fprintf(errfp,
			       "  fullname: dispname=%s lastname=%s "
			       "firstname=%s the=%s\n",
			   cdinfo_db.track[i].artistfname.dispname == NULL ?
				    "NULL" :
				    cdinfo_db.track[i].artistfname.dispname,
			   cdinfo_db.track[i].artistfname.lastname == NULL ?
				    "NULL" :
				    cdinfo_db.track[i].artistfname.lastname,
			   cdinfo_db.track[i].artistfname.firstname == NULL ?
				    "NULL" :
				    cdinfo_db.track[i].artistfname.firstname,
			   cdinfo_db.track[i].artistfname.the == NULL ?
				    "NULL" :
				    cdinfo_db.track[i].artistfname.the);
		(void) fprintf(errfp, "  artist=%s title=%s\n",
			       cdinfo_db.track[i].artist == NULL ?
					"NULL" : cdinfo_db.track[i].artist,
			       cdinfo_db.track[i].title == NULL ?
					"NULL" : cdinfo_db.track[i].title);
		(void) fprintf(errfp, "  sorttitle=%s title_the=%s\n",
			       cdinfo_db.track[i].sorttitle == NULL ?
					"NULL" :
					cdinfo_db.track[i].sorttitle,
			       cdinfo_db.track[i].title_the == NULL ?
					"NULL" :
					cdinfo_db.track[i].title_the);
		(void) fprintf(errfp,
			       "  year=%s label=%s genre=%s genre2=%s\n",
			       cdinfo_db.track[i].year == NULL ?
					"NULL" : cdinfo_db.track[i].year,
			       cdinfo_db.track[i].label == NULL ?
					"NULL" : cdinfo_db.track[i].label,
			       cdinfo_db.track[i].genre == NULL ?
					"NULL" : cdinfo_db.track[i].genre,
			       cdinfo_db.track[i].genre2 == NULL ?
					"NULL" :
					cdinfo_db.track[i].genre2);
		(void) fprintf(errfp, "  isrc=%s\n",
			       cdinfo_db.track[i].isrc == NULL ?
					"NULL" : cdinfo_db.track[i].isrc);
		if (cdinfo_db.track[i].notes == NULL)
			(void) fprintf(errfp, "  notes=NULL\n");
		else
			(void) fprintf(errfp, "  notes=\n%s\n",
				       cdinfo_db.track[i].notes);

		if (cdinfo_db.track[i].credit_list == NULL)
			(void) fprintf(errfp, "  credit_list=NULL\n");
		else {
			cdinfo_credit_t	*p;

			(void) fprintf(errfp, "  credit_list=\n");
			for (j = 0, p = cdinfo_db.track[j].credit_list;
			     p != NULL; p = p->next, j++) {
				(void) fprintf(errfp, "  credit[%d]\n", j);
				(void) fprintf(errfp, "    role=%s name=%s\n",
					    p->crinfo.role == NULL ?
						"NULL" : p->crinfo.role->name,
					    p->crinfo.name == NULL ?
						"NULL" : p->crinfo.name);
				(void) fprintf(errfp,
				       "    fullname: dispname=%s lastname=%s "
				       "firstname=%s the=%s\n",
				       p->crinfo.fullname.dispname == NULL ?
					    "NULL" :
					    p->crinfo.fullname.dispname,
				       p->crinfo.fullname.lastname == NULL ?
					    "NULL" :
					    p->crinfo.fullname.lastname,
				       p->crinfo.fullname.firstname == NULL ?
					    "NULL" :
					    p->crinfo.fullname.firstname,
				       p->crinfo.fullname.the == NULL ?
					    "NULL" :
					    p->crinfo.fullname.the);
				if (p->notes == NULL)
					(void) fprintf(errfp,
					    "    notes=NULL\n");
				else
					(void) fprintf(errfp,
					    "    notes=\n%s\n", p->notes);
			}
		}
	}

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

	if (cdinfo_db.genrelist == NULL)
		(void) fprintf(errfp, "genrelist=NULL\n");
	else {
		cdinfo_genre_t	*p,
				*q;

		(void) fprintf(errfp, "genrelist:\n");
		for (p = cdinfo_db.genrelist, i = 0; p != NULL;
		     p = p->next, i++) {
		    (void) fprintf(errfp, "genre[%d]: %s (%s)\n",
		    		   i, p->name, p->id);

		    for (q = p->child, j = 0; q != NULL; q = q->next, j++) {
			(void) fprintf(errfp, "  subgenre[%d]: %s (%s)\n",
					j, q->name, q->id);
		    }
		}
	}

	if (cdinfo_db.regionlist == NULL)
		(void) fprintf(errfp, "regionlist=NULL\n");
	else {
		cdinfo_region_t	*p;

		(void) fprintf(errfp, "regionlist:\n");
		for (p = cdinfo_db.regionlist, i = 0; p != NULL;
		     p = p->next, i++) {
			(void) fprintf(errfp, "region[%d]: %s (%s)\n",
				       i, p->name, p->id);
		}
	}

	if (cdinfo_db.rolelist == NULL)
		(void) fprintf(errfp, "rolelist=NULL\n");
	else {
		cdinfo_role_t	*p,
				*q;

		(void) fprintf(errfp, "rolelist:\n");
		for (p = cdinfo_db.rolelist, i = 0; p != NULL;
		     p = p->next, i++) {
			(void) fprintf(errfp, "role[%d]: %s (%s)\n",
				       i, p->name, p->id);
			for (q = p->child, j = 0; q != NULL; q = q->next, j++){
				(void) fprintf(errfp,
					       "  subrole[%d]: %s (%s)\n",
					       j, q->name, q->id);
			}
		}
	}

	if (cdinfo_db.matchlist == NULL)
		(void) fprintf(errfp, "matchlist=NULL\n");
	else {
		cdinfo_match_t	*p;

		for (p = cdinfo_db.matchlist, i = 0; p != NULL;
		     p = p->next, i++) {
			(void) fprintf(errfp,
				       "matchlist[%d] ptr=0x%lx genre=%s ",
				       i, (unsigned long) p,
				       p->genre == NULL ? "NULL" : p->genre);
			(void) fprintf(errfp, "artist=%s title=%s\n",
				       p->artist == NULL ? "NULL" : p->artist,
				       p->title == NULL ? "NULL" : p->title);
		}
	}
	if (cdinfo_db.match_aux == NULL)
		(void) fprintf(errfp, "match_aux=NULL\n");
	else
		(void) fprintf(errfp, "match_aux=0x%lx\n",
			       (unsigned long) cdinfo_db.match_aux);
	(void) fprintf(errfp, "match_tag=%ld\n", cdinfo_db.match_tag);

	if (cdinfo_db.gen_url_list == NULL)
		(void) fprintf(errfp, "gen_url_list=NULL\n");
	else {
		cdinfo_url_t	*u;

		for (u = cdinfo_db.gen_url_list, i = 0; u != NULL;
		     u = u->next, i++) {
			(void) fprintf(errfp,
			       "gen_url_list[%d] ptr=0x%lx key=%s-%s "
			       "type=%s size=%s weight=%s categ=%s\n",
			       i, (unsigned long) u,
			       u->modifier == NULL ? "NULL" : u->modifier,
			       u->keyname == NULL ? "NULL" : u->keyname,
			       u->type == NULL ? "NULL" : u->type,
			       u->size == NULL ? "NULL" : u->size,
			       u->weight == NULL ? "NULL" : u->weight,
			       u->categ == NULL ? "NULL" : u->categ);

			(void) fprintf(errfp,
			       "                 href=%s\n",
			       u->href == NULL ? "NULL" : u->href);
			(void) fprintf(errfp,
			       "                 displink=%s\n",
			       u->displink == NULL ? "NULL" : u->displink);
			(void) fprintf(errfp,
			       "                 disptext=%s\n",
			       u->disptext == NULL ? "NULL" : u->disptext);
		}
	}
	if (cdinfo_db.disc_url_list == NULL)
		(void) fprintf(errfp, "disc_url_list=NULL\n");
	else {
		cdinfo_url_t	*u;

		for (u = cdinfo_db.disc_url_list, i = 0; u != NULL;
		     u = u->next, i++) {
			(void) fprintf(errfp,
			       "gen_disc_list[%d] ptr=0x%lx key=%s-%s "
			       "type=%s size=%s weight=%s categ=%s\n",
			       i, (unsigned long) u,
			       u->modifier == NULL ? "NULL" : u->modifier,
			       u->keyname == NULL ? "NULL" : u->keyname,
			       u->type == NULL ? "NULL" : u->type,
			       u->size == NULL ? "NULL" : u->size,
			       u->weight == NULL ? "NULL" : u->weight,
			       u->categ == NULL ? "NULL" : u->categ);

			(void) fprintf(errfp,
			       "                  href=%s\n",
			       u->href == NULL ? "NULL" : u->href);
			(void) fprintf(errfp,
			       "                  displink=%s\n",
			       u->displink == NULL ? "NULL" : u->displink);
			(void) fprintf(errfp,
			       "                  disptext=%s\n",
			       u->disptext == NULL ? "NULL" : u->disptext);
		}
	}

	(void) fprintf(errfp, "playorder=%s\n",
		       cdinfo_db.playorder == NULL ?
				"NULL" : cdinfo_db.playorder);
}


