/*
 *   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 *_hist_c_ident_ = "@(#)hist.c	7.16 01/04/13";
#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 HIST_HYST	10
#define HIST_BANNER	\
"# xmcd %s.%s History file\n# %s\n#\n# \
device disc time type discid categ disc_title\n#\n"

extern appdata_t	app_data;
extern FILE		*errfp;
extern cdinfo_client_t	*cdinfo_clinfo;
extern bool_t		ischild;                

STATIC cdinfo_dlist_t	*cdinfo_hist_head = NULL,
						/* History list head */
			*cdinfo_hist_tail = NULL;
						/* History list tail */
STATIC cdinfo_dlist_t	*cdinfo_chgr_array = NULL;
						/* CD changer list array */
STATIC int		cdinfo_hist_mcnt = 0,	/* History list mem cnt */
			cdinfo_hist_fcnt = 0;	/* History list file cnt */


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

/*
 * cdinfo_hist_writefile
 *	Save the history file as history.bak, and then create a new
 *	history file based on the old history file's contents.
 *
 * Args:
 *	hp1 - Pointer to a cdinfo_dlist_t structure whose entry is
 *	      to be deleted from the history file.  If NULL, then
 *	      no designated entry will be deleted.
 *	hp2 - Pointer to a cdinfo_dlist_t structure whose entry is
 *	      to be appended to the history file.  If NULL, then
 *	      no designated entry will be appended.
 *	skip - Truncate the history file by deleting this number of
 *	       old entries.
 *
 * Return:
 *	Error code, or 0 for success.
 */
STATIC int
cdinfo_hist_writefile(cdinfo_dlist_t *hp1, cdinfo_dlist_t *hp2, int skip)
{
	int		i,
			n,
			ret;
	FILE		*ifp,
			*ofp;
	char		*homep,
			*histbuf,
			device[FILE_PATH_SZ],
			genre[DLIST_BUF_SZ],
			artist[DLIST_BUF_SZ],
			title[DLIST_BUF_SZ],
			path[FILE_PATH_SZ + 32],
			bakpath[FILE_PATH_SZ + 32];
	cdinfo_dlist_t	h;

	homep = util_homedir(util_get_ouid());
	if ((int) strlen(homep) >= FILE_PATH_SZ) {
		/* Path name too long */
		return OPEN_ERR;
	}

	(void) sprintf(path, USR_HIST_PATH, homep);
	(void) sprintf(bakpath, "%s.bak", path);

	/* Allocate temporary buffer */
	if ((histbuf = (char *) MEM_ALLOC("histbuf", DLIST_BUF_SZ)) == NULL)
		return MEM_ERR;

	DBGPRN(DBG_GEN)(errfp, "Writing history file %s\n", path);

	/* Rename old history file */
	(void) UNLINK(bakpath);

#if defined(USE_RENAME)

	if (rename(path, bakpath) < 0) {
		DBGPRN(DBG_GEN)(errfp, "Cannot rename %s -> %s: (errno=%d)\n",
			path, bakpath, errno);
		MEM_FREE(histbuf);
		return LINK_ERR;
	}

#else	/* USE_RENAME */

	if (LINK(path, bakpath) < 0) {
		DBGPRN(DBG_GEN)(errfp, "Cannot link %s -> %s: (errno=%d)\n",
			path, bakpath, errno);
		MEM_FREE(histbuf);
		return LINK_ERR;
	}

	(void) UNLINK(path);

#endif	/* USE_RENAME */

	/* Get history file handles */
	if ((ifp = fopen(bakpath, "r")) == NULL) {
		DBGPRN(DBG_GEN)(errfp, "File open error: %s (errno=%d)\n",
			bakpath, errno);
		MEM_FREE(histbuf);
		return OPEN_ERR;
	}
	if ((ofp = fopen(path, "w")) == NULL) {
		DBGPRN(DBG_GEN)(errfp, "File open error: %s (errno=%d)\n",
			path, errno);
		MEM_FREE(histbuf);
		return OPEN_ERR;
	}

	/* Set file permissions */
	util_setperm(path, app_data.hist_filemode);

	/* Write history file header */
	(void) fprintf(ofp, HIST_BANNER, VERSION_MAJ, VERSION_MIN, COPYRIGHT);

	/* Read from old history file */
	for (i = 1; fgets(histbuf, DLIST_BUF_SZ, ifp) != NULL; i++) {
		/* Skip comments and blank lines */
		if (histbuf[0] == '#' || histbuf[0] == '!' ||
		    histbuf[0] == '\n')
			continue;

		/* Truncate old entries if necessary */
		if (--skip > 0)
			continue;

		if ((n = sscanf(histbuf,
			   "%255s %x %x %x %x %127s %127[^\n/]/ %127[^\n]\n",
			   device,
			   &h.discno,
			   (int *) &h.time,
			   &h.type,
			   &h.discid,
			   genre,
			   artist,
			   title)) != 8) {
			if (n == 7 && artist[0] != '\0') {
				/* Old style line */
				(void) strcpy(title, artist);
				(void) strcpy(artist, "-");
			}
			else {
				DBGPRN(DBG_GEN)(errfp,
				    "Old history file syntax error: line %d\n",
				    i);
				continue;
			}
		}
		n = strlen(artist) - 1;
		if (artist[n] == ' ')
			artist[n] = '\0';

		/* Skip deleted entry */
		if (hp1 != NULL &&
		    h.time == hp1->time && h.discid == hp1->discid)
			continue;

		/* Write to history file */
		if (fprintf(ofp, "%s", histbuf) < 0) {
			ret = errno;
			(void) fclose(ifp);
			(void) fclose(ofp);
			errno = ret;

			DBGPRN(DBG_GEN)(errfp,
				"File write error: %s (errno=%d)\n",
				path, errno);
			MEM_FREE(histbuf);
			return WRITE_ERR;
		}
	}

	/* Append new entry */
	if (hp2 != NULL) {
		ret = fprintf(
			ofp,
			"%.255s %x %x %x %08x %.63s %.127s / %.127s\n",
			hp2->device == NULL ? "-" : hp2->device,
			hp2->discno,
			(int) hp2->time,
			hp2->type,
			(int) hp2->discid,
			hp2->genre == NULL ? "-" : hp2->genre,
			hp2->artist == NULL ? "-" : hp2->artist,
			hp2->title == NULL ? "-" : hp2->title
		);
		if (ret < 0) {
			ret = errno;
			(void) fclose(ifp);
			(void) fclose(ofp);
			errno = ret;

			DBGPRN(DBG_GEN)(errfp,
				"File write error: %s (errno=%d)\n",
				path, errno);
			MEM_FREE(histbuf);
			return WRITE_ERR;
		}
	}

	/* Close file */
	if (fclose(ifp) != 0) {
		DBGPRN(DBG_GEN)(errfp, "File close error: %s (errno=%d)\n",
			bakpath, errno);
		MEM_FREE(histbuf);
		return CLOSE_ERR;
	}
	if (fclose(ofp) != 0) {
		DBGPRN(DBG_GEN)(errfp, "File close error: %s (errno=%d)\n",
			path, errno);
		MEM_FREE(histbuf);
		return CLOSE_ERR;
	}

	MEM_FREE(histbuf);
	return 0;
}


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


/* cdinfo_hist_init
 *	Initialize history mechanism.
 *
 * Args:
 *	None.
 *
 * Return:
 *	Nothing.
 */
void
cdinfo_hist_init(void)
{
	int		i,
			n;
	FILE		*fp;
	char		*histbuf,
			*homep,
			*p,
			device[FILE_PATH_SZ],
			genre[DLIST_BUF_SZ],
			artist[DLIST_BUF_SZ],
			title[DLIST_BUF_SZ],
			path[FILE_PATH_SZ + 32];
	cdinfo_dlist_t	h,
			*hp,
			*nhp;
#ifndef __VMS
	pid_t		cpid;
	waitret_t	stat_val;
	int		ret,
			pfd[2];

	if (app_data.histfile_dsbl)
		return;

	if (PIPE(pfd) < 0) {
		DBGPRN(DBG_GEN)(errfp,
			"cdinfo_hist_init: pipe failed (errno=%d)\n",
			errno);
		return;
	}

	homep = util_homedir(util_get_ouid());
	if ((int) strlen(homep) >= FILE_PATH_SZ)
		/* Path name too long */
		return;

	(void) sprintf(path, USR_HIST_PATH, homep);

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

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

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

		DBGPRN(DBG_GEN)(errfp, "Loading history: %s\n", path);
		if ((fp = fopen(path, "r")) == NULL) {
			DBGPRN(DBG_GEN)(errfp, "Cannot open %s\n", path);
			(void) close(pfd[1]);
			exit(0);
		}
		
		while (fgets(histbuf, DLIST_BUF_SZ, fp) != NULL)
			(void) write(pfd[1], histbuf, strlen(histbuf));
		(void) write(pfd[1], ".\n", 2);

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

		exit(0);
		/*NOTREACHED*/

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

	default:
		/* Parent */

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

		if ((fp = fdopen(pfd[0], "r")) == NULL) {
			DBGPRN(DBG_GEN)(errfp,
			    "cdinfo_hist_init: read pipe fdopen failed\n");
			return;
		}
		break;
	}
#else
	homep = util_homedir(util_get_ouid());
	if (strlen(homep) >= FILE_PATH_SZ)
		/* Path name too long */
		return;

	(void) sprintf(path, USR_HIST_PATH, homep);
	DBGPRN(DBG_GEN)(errfp, "Loading history: %s\n", path);
	if ((fp = fopen(path, "r")) == NULL) {
		DBGPRN(DBG_GEN)(errfp, "Cannot open %s\n", path);
		return;
	}
#endif	/* __VMS */

	/* Allocate temporary buffer */
	if ((histbuf = (char *) MEM_ALLOC("histbuf", DLIST_BUF_SZ)) == NULL) {
		CDINFO_FATAL(app_data.str_nomemory);
		return;
	}

	/* Free old list, if any */
	for (hp = nhp = cdinfo_hist_head; hp != NULL; hp = nhp) {
		if (hp->device != NULL)
			MEM_FREE(hp->device);
		if (hp->genre != NULL)
			MEM_FREE(hp->genre);
		if (hp->artist != NULL)
			MEM_FREE(hp->artist);
		if (hp->title != NULL)
			MEM_FREE(hp->title);
		nhp = hp->next;
		MEM_FREE(hp);
	}
	cdinfo_hist_head = NULL;
	cdinfo_hist_mcnt = cdinfo_hist_fcnt = 0;

	h.device = device;
	h.genre = genre;
	h.artist = artist;
	h.title = title;

	/* Read in history file */
	for (i = 1; fgets(histbuf, DLIST_BUF_SZ, fp) != NULL; i++) {
		/* Skip comments and blank lines */
		if (histbuf[0] == '#' || histbuf[0] == '!' ||
		    histbuf[0] == '\n')
			continue;
		/* Done */
		if (histbuf[0] == '.' && histbuf[1] == '\n')
			break;

		if ((n = sscanf(histbuf,
			   "%255s %x %x %x %x %127s %127[^\n/]/ %127[^\n]\n",
			   h.device,
			   &h.discno,
			   (int *) &h.time,
			   &h.type,
			   &h.discid,
			   h.genre,
			   h.artist,
			   h.title)) != 8) {
			if (n == 7 && h.artist[0] != '\0') {
				/* Old style line */
				(void) strcpy(h.title, h.artist);
				(void) strcpy(h.artist, "-");
			}
			else {
				DBGPRN(DBG_GEN)(errfp,
					"History file syntax error: line %d\n",
					i);
				continue;
			}
		}
		h.device[FILE_PATH_SZ - 1] = '\0';
		h.genre[DLIST_BUF_SZ - 1] = '\0';
		h.artist[DLIST_BUF_SZ - 1] = '\0';
		h.title[DLIST_BUF_SZ - 1] = '\0';
		n = strlen(h.artist) - 1;
		if (h.artist[n] == ' ')
			h.artist[n] = '\0';

		cdinfo_hist_fcnt++;

		if (++cdinfo_hist_mcnt > app_data.cdinfo_maxhist) {
			if (cdinfo_hist_tail == NULL)
				break;	/* Empty list: Cannot continue */

			/* Max entries exceeded: circulate around and
			 * re-use an existing structure.
			 */
			hp = cdinfo_hist_tail;
			cdinfo_hist_tail = hp->prev;
			cdinfo_hist_tail->next = NULL;

			if (hp->device != NULL)
				MEM_FREE(hp->device);
			if (hp->genre != NULL)
				MEM_FREE(hp->genre);
			if (hp->artist != NULL)
				MEM_FREE(hp->artist);
			if (hp->title != NULL)
				MEM_FREE(hp->title);

			if (cdinfo_hist_mcnt > 0)
				cdinfo_hist_mcnt--;
		}
		else {
			/* Allocate new entry */
			hp = (cdinfo_dlist_t *) MEM_ALLOC(
				"cdinfo_hist",
				sizeof(cdinfo_dlist_t)
			);
			if (hp == NULL) {
				CDINFO_FATAL(app_data.str_nomemory);
				return;
			}
		}

		*hp = h;	/* Structure copy */
		hp->device = hp->genre = hp->artist = hp->title = NULL;

		p = (strcmp(h.device, "-") == 0) ? NULL : h.device;
		if (!util_newstr(&hp->device, p)) {
			CDINFO_FATAL(app_data.str_nomemory);
			return;
		}

		p = (strcmp(h.genre, "-") == 0) ? NULL : h.genre;
		if (!util_newstr(&hp->genre, p)) {
			CDINFO_FATAL(app_data.str_nomemory);
			return;
		}

		p = (strcmp(h.artist, "-") == 0) ? NULL : h.artist;
		if (!util_newstr(&hp->artist, p)) {
			CDINFO_FATAL(app_data.str_nomemory);
			return;
		}

		p = (strcmp(h.title, "-") == 0) ? NULL : h.title;
		if (!util_newstr(&hp->title, p)) {
			CDINFO_FATAL(app_data.str_nomemory);
			return;
		}

		hp->next = cdinfo_hist_head;
		hp->prev = NULL;
		if (cdinfo_hist_head != NULL)
			cdinfo_hist_head->prev = hp;
		else
			cdinfo_hist_tail = hp;
		cdinfo_hist_head = hp;
	}

	(void) fclose(fp);
	MEM_FREE(histbuf);

#ifndef __VMS
	while ((ret = WAITPID(cpid, &stat_val, 0)) != cpid) {
		if (ret < 0)
			break;
	}
	if (ret == cpid) {
	    if (WIFEXITED(stat_val)) {
		if (WEXITSTATUS(stat_val) != 0) {
			DBGPRN(DBG_GEN)(errfp,
				"cdinfo_hist_init: child exited (status=%d)\n",
				WEXITSTATUS(stat_val));
		}
	    }
	    else if (WIFSIGNALED(stat_val)) {
		DBGPRN(DBG_GEN)(errfp, "cdinfo_hist_init: child killed.\n");
	    }
	}
#endif
}


/*
 * cdinfo_hist_addent
 *	Add one entry to the in-core history list and to the user's
 *	history file.
 *
 * Args:
 *	hp - Pointer to the filled cdinfo_dlist_t structure containing
 *	     information to save.
 *	updfile - Whether to update the history file
 *
 * Return:
 *	return code as defined by cdinfo_ret_t
 */
cdinfo_ret_t
cdinfo_hist_addent(cdinfo_dlist_t *hp, bool_t updfile)
{
	int		ret,
			skip;
	cdinfo_dlist_t	*hp2;
	FILE		*fp;
	char		*homep,
			path[FILE_PATH_SZ + 32];
	cdinfo_ret_t	retcode;
	bool_t		newfile,
			wholefile;
	struct stat	stbuf;

	if (cdinfo_hist_head != NULL &&
	    cdinfo_hist_head->discid == hp->discid) {
		/* Same disc as the most recent item on history list:
		 * no need to add.
		 */
		return 0;
	}

	/*
	 * Add to in-core list
	 */

	if (++cdinfo_hist_mcnt > app_data.cdinfo_maxhist) {
		if (cdinfo_hist_tail == NULL)
			return 0;	/* Empty list: Cannot continue */

		/* Max entries exceeded: circulate around and
		 * re-use an existing structure.
		 */
		hp2 = cdinfo_hist_tail;
		cdinfo_hist_tail = hp2->prev;
		cdinfo_hist_tail->next = NULL;

		if (hp2->device != NULL)
			MEM_FREE(hp2->device);
		if (hp2->genre != NULL)
			MEM_FREE(hp2->genre);
		if (hp2->artist != NULL)
			MEM_FREE(hp2->artist);
		if (hp2->title != NULL)
			MEM_FREE(hp2->title);

		if (cdinfo_hist_mcnt > 0)
			cdinfo_hist_mcnt--;
	}
	else {
		/* Allocate new entry */
		hp2 = (cdinfo_dlist_t *) MEM_ALLOC(
			"cdinfo_hist",
			sizeof(cdinfo_dlist_t)
		);
		if (hp2 == NULL) {
			CDINFO_FATAL(app_data.str_nomemory);
			return 0;
		}
	}

	*hp2 = *hp;	/* Structure copy */
	hp2->device = hp2->genre = hp2->artist = hp2->title = NULL;

	if (!util_newstr(&hp2->device, hp->device)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}

	if (!util_newstr(&hp2->genre, hp->genre)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}

	if (!util_newstr(&hp2->artist, hp->artist)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}

	if (!util_newstr(&hp2->title, hp->title)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}

	hp2->next = cdinfo_hist_head;
	hp2->prev = NULL;
	if (cdinfo_hist_head != NULL)
		cdinfo_hist_head->prev = hp2;
	else
		cdinfo_hist_tail = hp2;
	cdinfo_hist_head = hp2;

	if (app_data.histfile_dsbl || !updfile)
		return 0;

	cdinfo_hist_fcnt++;

	skip = 0;
	if ((cdinfo_hist_fcnt - cdinfo_hist_mcnt) > HIST_HYST) {
		/* Write out the whole file */
		skip = cdinfo_hist_fcnt - cdinfo_hist_mcnt + 1;
		cdinfo_hist_fcnt = cdinfo_hist_mcnt;
		wholefile = TRUE;
	}
	else
		wholefile = FALSE;

	/*
	 * Write out one entry to file
	 */

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

	if (wholefile) {
		/* Write out the whole file */
		ret = cdinfo_hist_writefile(NULL, hp, skip);
		CH_RET(ret);
	}

	homep = util_homedir(util_get_ouid());
	if ((int) strlen(homep) >= FILE_PATH_SZ)
		/* Path name too long */
		CH_RET(OPEN_ERR);

	(void) sprintf(path, USR_HIST_PATH, homep);

	DBGPRN(DBG_GEN)(errfp, "Writing history file %s\n", path);

	/* Check the path */
	newfile = FALSE;
	if (stat(path, &stbuf) < 0) {
		if (errno == ENOENT)
			newfile = TRUE;
		else {
			DBGPRN(DBG_GEN)(errfp, "Cannot stat %s (errno=%d)\n",
				path, errno);
			CH_RET(OPEN_ERR);
		}
	}
	else {
		if (!S_ISREG(stbuf.st_mode)) {
			DBGPRN(DBG_GEN)(errfp,
				"Not a regular file error: %s\n", path);
			CH_RET(OPEN_ERR);
		}

		if (stbuf.st_size == 0)
			newfile = TRUE;
	}

	/* Get a history file handle */
	if ((fp = fopen(path, "a")) == NULL) {
		DBGPRN(DBG_GEN)(errfp, "Cannot open file: %s\n", path);
		CH_RET(OPEN_ERR);
	}

	/* Set file permissions */
	util_setperm(path, app_data.hist_filemode);

	if (newfile)
		/* Write history file header */
		(void) fprintf(fp, HIST_BANNER,
			       VERSION_MAJ, VERSION_MIN, COPYRIGHT);

	/* Write to history file */
	ret = fprintf(
		fp,
		"%.255s %x %x %x %08x %.63s %.127s / %.127s\n",
		hp->device == NULL ? "-" : hp->device,
		hp->discno,
		(int) hp->time,
		hp->type,
		(int) hp->discid,
		hp->genre == NULL ? "-" : hp->genre,
		hp->artist == NULL ? "-" : hp->artist,
		hp->title == NULL ? "-" : hp->title
	);

	if (ret < 0) {
		ret = errno;
		(void) fclose(fp);
		errno = ret;

		DBGPRN(DBG_GEN)(errfp, "File write error: %s (errno=%d)\n",
			path, errno);
		CH_RET(WRITE_ERR);
	}

	/* Close file */
	if (fclose(fp) != 0) {
		DBGPRN(DBG_GEN)(errfp, "File close error: %s (errno=%d)\n",
			path, errno);
		CH_RET(CLOSE_ERR);
	}

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


/*
 * cdinfo_hist_delent
 *	Delete one entry from the in-core history list and from the user's
 *	history file.
 *
 * Args:
 *	hp - Pointer to the filled cdinfo_dlist_t structure containing
 *	     information to delete.
 *	updfile - Whether to update the history file.
 *
 * Return:
 *	return code as defined by cdinfo_ret_t
 */
cdinfo_ret_t
cdinfo_hist_delent(cdinfo_dlist_t *hp, bool_t updfile)
{
	int		ret,
			skip;
	cdinfo_dlist_t	*hp2,
			h;
	cdinfo_ret_t	retcode;

	h = *hp;	/* Structure copy */

	/*
	 * Delete from in-core list
	 */
	for (hp2 = cdinfo_hist_head; hp2 != NULL; hp2 = hp2->next) {
		if (hp2->time == hp->time && hp2->discid == hp->discid) {
			if (hp2 == cdinfo_hist_head)
				cdinfo_hist_head = hp2->next;
			else
				hp2->prev->next = hp2->next;

			if (hp2->next != NULL)
				hp2->next->prev = hp2->prev;

			if (hp2->device != NULL)
				MEM_FREE(hp2->device);
			if (hp2->genre != NULL)
				MEM_FREE(hp2->genre);
			if (hp2->artist != NULL)
				MEM_FREE(hp2->artist);
			if (hp2->title != NULL)
				MEM_FREE(hp2->title);
			MEM_FREE(hp2);

			if (cdinfo_hist_mcnt > 0)
				cdinfo_hist_mcnt--;
			break;
		}
	}

	if (app_data.histfile_dsbl || !updfile)
		return 0;

	/*
	 * Delete from history file
	 */

	skip = cdinfo_hist_fcnt - cdinfo_hist_mcnt;
	cdinfo_hist_fcnt = cdinfo_hist_mcnt;

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

	/* Write the history file */
	ret = cdinfo_hist_writefile(&h, NULL, skip);

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


/*
 * cdinfo_hist_delall
 *	Delete the user's entire xmcd history from the in-core history list
 *	and the user's history file.
 *
 * Args:
 *	updfile - Whether to update the history file.
 *
 * Return:
 *	return code as defined by cdinfo_ret_t
 */
cdinfo_ret_t
cdinfo_hist_delall(bool_t updfile)
{
	cdinfo_dlist_t	*hp,
			*nhp;
	char		*homep,
			path[FILE_PATH_SZ + 32],
			bakpath[FILE_PATH_SZ + 32];
	cdinfo_ret_t	retcode;

	/* Delete in-core list */
	for (hp = nhp = cdinfo_hist_head; hp != NULL; hp = nhp) {
		nhp = hp->next;
		if (hp->device != NULL)
			MEM_FREE(hp->device);
		if (hp->genre != NULL)
			MEM_FREE(hp->genre);
		if (hp->artist != NULL)
			MEM_FREE(hp->artist);
		if (hp->title != NULL)
			MEM_FREE(hp->title);
		MEM_FREE(hp);
	}
	cdinfo_hist_head = NULL;
	cdinfo_hist_mcnt = cdinfo_hist_fcnt = 0;

	if (app_data.histfile_dsbl || !updfile)
		return 0;

	/* Clear history file */
	homep = util_homedir(util_get_ouid());
	if ((int) strlen(homep) >= FILE_PATH_SZ)
		/* Path name too long */
		return CDINFO_SET_CODE(OPEN_ERR, ENAMETOOLONG);

	(void) sprintf(path, USR_HIST_PATH, homep);
	(void) sprintf(bakpath, "%s.bak", path);

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

	DBGPRN(DBG_GEN)(errfp, "Deleting history file %s\n", path);

	/* Rename old history file */
	(void) UNLINK(bakpath);

#if defined(USE_RENAME)

	if (rename(path, bakpath) < 0) {
		DBGPRN(DBG_GEN)(errfp,
			"Cannot rename file %s -> %s (errno=%d)\n",
			path, bakpath, errno);
		CH_RET(LINK_ERR);
	}

#else	/* USE_RENAME */

	if (LINK(path, bakpath) < 0) {
		DBGPRN(DBG_GEN)(errfp,
			"Cannot link file %s -> %s (errno=%d)\n",
			path, bakpath, errno);
		CH_RET(LINK_ERR);
	}

	(void) UNLINK(path);

#endif	/* USE_RENAME */

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


/*
 * cdinfo_hist_list
 *	Return the head of the xmcd history list.
 *
 * Args:
 *	None.
 *
 * Return:
 *	A pointer to the head of the xmcd history list, or NULL if the list
 *	is empty.
 */
cdinfo_dlist_t *
cdinfo_hist_list(void)
{
	return (cdinfo_hist_head);
}


/*
 * cdinfo_chgr_init
 */
void
cdinfo_chgr_init(void)
{
	int		i,
			lastdisc,
			allocsz;

	/* Allocate array of cdinfo_dlist_t pointers */
	if ((allocsz = sizeof(cdinfo_dlist_t) * app_data.numdiscs) <= 0)
		return;

	cdinfo_chgr_array = (cdinfo_dlist_t *) MEM_ALLOC(
		"cdinfo_chgr_array", allocsz
	);
	if (cdinfo_chgr_array == NULL) {
		CDINFO_FATAL(app_data.str_nomemory);
		return;
	}
	(void) memset(cdinfo_chgr_array, 0, allocsz);

	lastdisc = app_data.numdiscs - 1;
	for (i = 0; i < app_data.numdiscs; i++) {
		cdinfo_chgr_array[i].discno = i+1;

		if (i == lastdisc)
			cdinfo_chgr_array[i].next = NULL;
		else
			cdinfo_chgr_array[i].next = &cdinfo_chgr_array[i+1];

		if (i == 0)
			cdinfo_chgr_array[i].prev = NULL;
		else
			cdinfo_chgr_array[i].prev = &cdinfo_chgr_array[i-1];
	}
}


/*
 * cdinfo_chgr_addent
 *	Add one entry to the CD changer list
 *
 * Args:
 *	hp - Pointer to the filled cdinfo_dlist_t structure containing
 *	     information to add.
 *
 * Return:
 *	return code as defined by cdinfo_ret_t
 */
cdinfo_ret_t
cdinfo_chgr_addent(cdinfo_dlist_t *cp)
{
	int	i;

	if (cdinfo_chgr_array == NULL)
		/* Not yet initialized */
		return 0;

	if (cp->discno <= 0 || cp->discno > app_data.numdiscs)
		return CDINFO_SET_CODE(CMD_ERR, EINVAL);

	i = cp->discno - 1;

	/* Don't do a structure copy here, or we'd mess up the list links */
	cdinfo_chgr_array[i].discno = cp->discno;
	cdinfo_chgr_array[i].type = cp->type;
	cdinfo_chgr_array[i].discid = cp->discid;
	cdinfo_chgr_array[i].time = cp->time;

	if (!util_newstr(&cdinfo_chgr_array[i].device, cp->device)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}

	if (!util_newstr(&cdinfo_chgr_array[i].genre, cp->genre)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}

	if (!util_newstr(&cdinfo_chgr_array[i].artist, cp->artist)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}

	if (!util_newstr(&cdinfo_chgr_array[i].title, cp->title)) {
		CDINFO_FATAL(app_data.str_nomemory);
		return CDINFO_SET_CODE(MEM_ERR, 0);
	}

	return 0;
}


/*
 * cdinfo_chgr_list
 *	Return the head of the CD changer list.
 *
 * Args:
 *	None.
 *
 * Return:
 *	A pointer to the head of the CD changer list, or NULL if the list
 *	is empty.
 */
cdinfo_dlist_t *
cdinfo_chgr_list(void)
{
	return (&cdinfo_chgr_array[0]);
}


