/*
 * dvch.c -- device cache functions for lsof library
 */


/*
 * Copyright 1997 Purdue Research Foundation, West Lafayette, Indiana
 * 47907.  All rights reserved.
 *
 * Written by Victor A. Abell
 *
 * This software is not subject to any license of the American Telephone
 * and Telegraph Company or the Regents of the University of California.
 *
 * Permission is granted to anyone to use this software for any purpose on
 * any computer system, and to alter it and redistribute it freely, subject
 * to the following restrictions:
 *
 * 1. Neither the authors nor Purdue University are responsible for any
 *    consequences of the use of this software.
 *
 * 2. The origin of this software must not be misrepresented, either by
 *    explicit claim or by omission.  Credit to the authors and Purdue
 *    University must appear in documentation and sources.
 *
 * 3. Altered versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 *
 * 4. This notice may not be removed or altered.
 */


#include "../machine.h"

#if	defined(HASDCACHE)

# if	!defined(lint)
static char copyright[] =
"@(#) Copyright 1997 Purdue Research Foundation.\nAll rights reserved.\n";
# endif	/* !defined(lint) */

#include "../lsof.h"

/*
 * dvch.c - module that contains common device cache functions
 *
 * The caller may define the following:
 *
 *	DCACHE_CLONE	is the name of the function that reads and writes the
 *			clone section of the device cache file.  The clone
 *			section follows the device section.  If DCACHE_CLONE
 *			isn't defined, but HAS_STD_CLONE is defined to be 1,
 *			DCACHE_CLONE defaults to the local static function
 *			rw_clone_sect() that reads and writes a standard
 *			clone cache.
 *
 *	DCACHE_CLR	is the name of the function that clears the clone and
 *			pseudo caches when reading the device cache fails.   If
 *			DCACHE_CLR isn't defined, but HAS_STD_CLONE is defined
 *			to be 1, DCACHE_CLR defaults to the local static
 *			function clr_sect() that clears a standard clone cache.
 *
 *	DCACHE_PSEUDO	is the name of the function that reads and writes
 *			the pseudo section of the device cache file.  The
 *			pseudo section follows the device section and the
 *			clone section, if there is one.
 *
 *	DVCH_CHOWN	if the dialect has no fchown() function, so
 *			chown() must be used instead.
 *
 *	DVCH_DEVPATH	if the path to the device directory isn't "/dev".
 *
 *	DVCH_EXPDEV	if st_rdev must be expanded with the expdev()
 *			macro before use.  (This is an EP/IX artifact.)
 *
 *	HASBLKDEV	if block device information is stored in BDevtp[].
 */


/*
 * Local definitions
 */

# if	!defined(DVCH_DEVPATH)
#define	DVCH_DEVPATH	"/dev"
# endif	/* !defined(DVCH_DEVPATH) */

/*
 * Local storage
 */

static int crctbl[CRC_TBLL];		/* crc partial results table */


/*
 * Local function prototypes
 */

#undef	DCACHE_CLR_LOCAL
# if	!defined(DCACHE_CLR)
#  if	defined(HAS_STD_CLONE) && HAS_STD_CLONE==1
#define	DCACHE_CLR		clr_sect
#define	DCACHE_CLR_LOCAL	1
_PROTOTYPE(static void clr_sect,(void));
#  endif	/* defined(HAS_STD_CLONE) && HAS_STD_CLONE==1 */
# endif	/* !defined(DCACHE_CLR) */

#undef	DCACHE_CLONE_LOCAL
# if	!defined(DCACHE_CLONE)
#  if	defined(HAS_STD_CLONE) && HAS_STD_CLONE==1
#define	DCACHE_CLONE		rw_clone_sect
#define	DCACHE_CLONE_LOCAL	1
_PROTOTYPE(static int rw_clone_sect,(int m));
#  endif	/* defined(HAS_STD_CLONE) && HAS_STD_CLONE==1 */
# endif	/*!defined(DCACHE_CLONE) */


# if	defined(HASBLKDEV)
/*
 * alloc_bdcache() - allocate block device cache
 */

void
alloc_bdcache()
{
	if (!(BDevtp = (struct l_dev *)calloc((MALLOC_S)BNdev,
				       sizeof(struct l_dev))))
	{
	    (void) fprintf(stderr, "%s: no space for block devices\n", Pn);
	    Exit(1);
	}
	if (!(BSdev = (struct l_dev **)malloc((MALLOC_S)(sizeof(struct l_dev *)
				       * BNdev))))
	{
	    (void) fprintf(stderr, "%s: no space for block device pointers\n",
		Pn);
	    Exit(1);
	}
}
# endif	/* defined(HASBLKDEV) */


/*
 * alloc_dcache() - allocate device cache
 */

void
alloc_dcache()
{
	if (!(Devtp = (struct l_dev *)calloc((MALLOC_S)Ndev,
				      sizeof(struct l_dev))))
	{
	    (void) fprintf(stderr, "%s: no space for devices\n", Pn);
	    Exit(1);
	}
	if (!(Sdev = (struct l_dev **)malloc((MALLOC_S)(sizeof(struct l_dev *)
				      * Ndev))))
	{
	    (void) fprintf(stderr, "%s: no space for device pointers\n",
		Pn);
	    Exit(1);
	}
}


/*
 * clr_devtab() - clear the device tables and free their space
 */

void
clr_devtab()
{
	int i;

	if (Devtp) {
	    for (i = 0; i < Ndev; i++) {
		if (Devtp[i].name) {
		    (void) free((FREE_P *)Devtp[i].name);
		    Devtp[i].name = (char *)NULL;
		}
	    }
	    (void) free((FREE_P *)Devtp);
	    Devtp = (struct l_dev *)NULL;
	}
	if (Sdev) {
	    (void) free((FREE_P *)Sdev);
	    Sdev = (struct l_dev **)NULL;
	}
	Ndev = 0;

# if	defined(HASBLKDEV)
	if (BDevtp) {
	    for (i = 0; i < BNdev; i++) {
		if (BDevtp[i].name) {
		    (void) free((FREE_P *)BDevtp[i].name);
		    BDevtp[i].name = (char *)NULL;
		}
	    }
	    (void) free((FREE_P *)BDevtp);
	    BDevtp = (struct l_dev *)NULL;
	}
	if (BSdev) {
	    (void) free((FREE_P *)BSdev);
	    BSdev = (struct l_dev **)NULL;
	}
	BNdev = 0;
# endif	/* defined(HASBLKDEV) */

}


# if	defined(DCACHE_CLR_LOCAL)
/*
 * clr_sect() - clear cached standard clone sections
 */

static void
clr_sect()
{
	struct clone *c, *c1;

	if (Clone) {
	    for (c = Clone; c; c = c1) {
		c1 = c->next;
		(void) free((FREE_P *)c);
	    }
	    Clone = (struct clone *)NULL;
	}
}
# endif	/* defined(DCACHE_CLR_LOCAL) */


/*
 * crc(b, l, s) - compute a crc for a block of bytes
 */

void
crc(b, l, s)
	char *b;			/* block address */
	int  l;				/* length */
	unsigned *s;			/* sum */
{
	char *cp;			/* character pointer */
	char *lm;			/* character limit pointer */
	unsigned sum;			/* check sum */

	cp = b;
	lm = cp + l;
	sum = *s;
	do {
		sum ^= ((int) *cp++) & 0xff;
		sum = (sum >> 8) ^ crctbl[sum & 0xff];
	} while (cp < lm);
	*s = sum;
}


/*
 * crcbld - build the CRC-16 partial results table
 */

void
crcbld()
{
	int bit;			/* temporary bit value */
	unsigned entry;			/* entry under construction */
	int i;				/* polynomial table index */
	int j;				/* bit shift count */

	for(i = 0; i < CRC_TBLL; i++) {
		entry = i;
		for (j = 1; j <= CRC_BITS; j++) {
			bit = entry & 1;
			entry >>= 1;
			if (bit)
				entry ^= CRC_POLY;
		}
		crctbl[i] = entry;
	}
}


/*
 * dcpath() - define device cache file paths
 */

int
dcpath(rw, npw)
	int rw;				/* read (1) or write (2) mode */
	int npw;			/* inhibit (0) or enable (1) no
					 * path warning message */
{
	char buf[MAXPATHLEN+1], *cp1, *cp2, hn[MAXPATHLEN+1];
	int endf;
	int i, j;
	int l = 0;
	int ierr = 0;			/* intermediate error state */
	int merr = 0;			/* malloc error state */
	struct passwd *p = (struct passwd *)NULL;
	static short wenv = 1;		/* HASENVDC warning state */
	static short wpp = 1;		/* HASPERSDCPATH warning state */
/*
 * Release any space reserved by previous path calls to dcpath().
 */
	if (DCpath[1]) {
	    (void) free((FREE_P *)DCpath[1]);
	    DCpath[1] = (char *)NULL;
	}
	if (DCpath[3]) {
	    (void) free((FREE_P *)DCpath[3]);
	    DCpath[3] = (char *)NULL;
	}
/*
 * If a path was specified via -D, it's character address will have been
 * stored in DCpathArg by ctrl_dcache().  Use that address if the real UID
 * of this process is root, or the mode is read, or the process is neither
 * setuid-root nor setgid.
 */
	if (Myuid == 0 || rw == 1 || (!Setuidroot && !Setgid))
	    DCpath[0] = DCpathArg;
	else
	    DCpath[0] = (char *)NULL;

# if	defined(HASENVDC)
/*
 * If HASENVDC is defined, get its value from the environment, unless this
 * is a setuid-root process, or the real UID of the process is 0, or the
 * mode is write and the process is setgid.
 */
	if ((cp1 = getenv(HASENVDC)) && (l = strlen(cp1)) > 0
	&&  !Setuidroot && Myuid && (rw == 1 || !Setgid)) {
	    if (!(cp2 = mkstrcpy(cp1, (MALLOC_S *)NULL))) {
		(void) fprintf(stderr,
		    "%s: no space for device cache path: %s=", Pn, HASENVDC);
		safestrprt(cp1, stderr, 1);
		merr = 1;
	    } else
		DCpath[1] = cp2;
	} else if (cp1 && l > 0) {
	    if (!Fwarn && wenv) {
		(void) fprintf(stderr,
		    "%s: WARNING: ignoring environment: %s=", Pn, HASENVDC);
		safestrprt(cp1, stderr, 1);
	    }
	    wenv = 0;
	}
# endif	/* defined(HASENVDC) */

# if	defined(HASSYSDC)
/*
 * If HASSYSDC is defined, record the path of the system-wide device
 * cache file, unless the mode is write.
 */
	if (rw != 2)
	    DCpath[2] = HASSYSDC;
	else
	    DCpath[2] = (char *)NULL;
# endif	/* defined(HASSYSDC) */

# if	defined(HASPERSDC)
/*
 * If HASPERSDC is defined, form a personal device cache path by
 * interpreting the conversions specified in it.
 *
 * Get (HASPERSDCPATH) from the environment and add it to the home directory
 * path, if possible.
 */
	for (cp1 = HASPERSDC, endf = i = 0; *cp1 && !endf; cp1++) {
	    if (*cp1 != '%') {

	    /*
	     * If the format character isn't a `%', copy it.
	     */
		if (i < (int)sizeof(buf)) {
		    buf[i++] = *cp1;
		    continue;
		} else {
		    ierr = 2;
		    break;
		}
	     }
	/*
	 * `%' starts a conversion; the next character specifies
	 * the conversion type.
	 */
	    cp1++;
	    switch (*cp1) {

	    /*
	     * Two consecutive `%' characters convert to one `%'
	     * character in the output.
	     */

	    case '%':
		if (i < (int)sizeof(buf))
			buf[i++] = '%';
		else
			ierr = 2;
		break;

	    /*
	     * ``%0'' defines a root boundary.  If the effective
	     * (setuid-root) or real UID of the process is root, any
	     * path formed to this point is discarded and path formation
	     * begins with the next character.
	     *
	     * If neither the effective nor the real UID is root, path
	     * formation ends.
	     *
	     * This allows HASPERSDC to specify one path for non-root
	     * UIDs and another for the root (effective or real) UID.
	     */

	    case '0':
		if (Setuidroot || !Myuid)
		    i = 0;
		else
		    endf = 1;
		break;

	    /*
	     * ``%h'' converts to the home directory.
	     */
	
	    case 'h':
		if (!p && !(p = getpwuid(Myuid))) {
		    if (!Fwarn)
			(void) fprintf(stderr,
			    "%s: WARNING: can't get home dir for UID: %d\n",
			    Pn, (int)Myuid);
		    ierr = 1;
		    break;
		}
		if ((i + (l = strlen(p->pw_dir))) >= (int)sizeof(buf)) {
		    ierr = 2;
		    break;
		}
		(void) strcpy(&buf[i], p->pw_dir);
		i += l;
		if (i > 0 && buf[i - 1] == '/' && *(cp1 + 1)) {

		/*
		 * If the home directory ends in a '/' and the next format
		 * character is a '/', delete the '/' at the end of the home
		 * directory.
		 */
		    i--;
		    buf[i] = '\0';
		}
		break;

	    /*
	     * ``%l'' converts to the full host name.
	     *
	     * ``%L'' converts to the first component (characters up
	     * to the first `.') of the host name.
	     */

	    case 'l':
	    case 'L':
		if (gethostname(hn, sizeof(hn) - 1) < 0) {
		    if (!Fwarn)
			(void) fprintf(stderr,
			    "%s: WARNING: no gethostname for %%l or %%L: %s\n",
			    Pn, strerror(errno));
		    ierr = 1;
		    break;
		}
		hn[sizeof(hn) - 1] = '\0';
		if (*cp1 == 'L' && (cp2 = strchr(hn, '.')) && cp2 > hn)
		    *cp2 = '\0';
		j = strlen(hn);
		if ((j + i) < (int)sizeof(buf)) {
		    (void) strcpy(&buf[i], hn);
		    i += j;
		} else
		    ierr = 2;
		break;

	    /*
	     * ``%p'' converts to the contents of LSOFPERSDCPATH, followed
	     * by a '/'.
	     *
	     * It is ignored when:
	     *
	     *    The lsof process is setuid-root;
	     *    The real UID of the lsof process is 0;
	     *    The mode is write and the process is setgid.
	     */

	    case 'p':

#  if	defined(HASPERSDCPATH)
		if ((cp2 = getenv(HASPERSDCPATH))
		&&  (l = strlen(cp2)) > 0
		&&  !Setuidroot
		&&  Myuid
		&&  (rw == 1 || !Setgid))
		{
		    if (i && buf[i - 1] == '/' && *cp2 == '/') {
			cp2++;
			l--;
		    }
		    if ((i + l) < ((int)sizeof(buf) - 1)) {
			(void) strcpy(&buf[i], cp2);
			i += l;
			if (buf[i - 1] != '/') {
			    if (i < ((int)sizeof(buf) - 2)) {
				buf[i++] = '/';
				buf[i] = '\0';
			    } else
				ierr = 2;
			}
		    } else
			ierr = 2;
		} else {
		    if (cp2 && l > 0)  {
			if (!Fwarn && wpp) {
			    (void) fprintf(stderr,
				"%s: WARNING: ignoring environment: %s",
				Pn, HASPERSDCPATH);
			    safestrprt(cp2, stderr, 1);
			} 
			wpp = 0;
		    }
		}
#  else	/* !defined(HASPERSDCPATH) */
		if (!Fwarn && wpp)
		    (void) fprintf(stderr,
			"%s: WARNING: HASPERSDCPATH disabled: %s\n",
			Pn, HASPERSDC);
		ierr = 1;
		wpp = 0;
#  endif	/* defined(HASPERSDCPATH) */

		break;

	    /*
	     * ``%u'' converts to the login name of the real UID of the
	     * lsof process.
	     */

	    case 'u':
		if (!p && !(p = getpwuid(Myuid))) {
		    if (!Fwarn)
			(void) fprintf(stderr,
			    "%s: WARNING: can't get login name for UID: %d\n",
			    Pn, (int)Myuid);
		    ierr = 1;
		    break;
		}
		if ((i + (l = strlen(p->pw_name))) >= (int)sizeof(buf)) {
		    ierr = 2;
		    break;
		}
		(void) strcpy(&buf[i], p->pw_name);
		i += l;
		break;

	    /*
	     * ``%U'' converts to the real UID of the lsof process.
	     */

	    case 'U':
		(void) snpf(hn, sizeof(hn), "%d", (int)Myuid);
		if ((i + (l = strlen(hn))) >= (int)sizeof(buf))
		    ierr = 2;
		else {
		    (void) strcpy(&buf[i], hn);
		    i += l;
		}
		break;
	    default:
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: bad conversion (%%%c): %s\n",
			Pn, *cp1, HASPERSDC);
		ierr = 1;
	    }
	    if (endf || ierr > 1)
		break;
	}
	if (ierr) {

	/*
	 * If there was an intermediate error of some type, handle it.
	 * A type 1 intermediate error has already been noted with a
	 * warning message.  A type 2 intermediate error requires the
	 * issuing of a buffer overlow warning message.
	 */
	    if (ierr == 2 && !Fwarn)
		(void) fprintf(stderr,
	 	    "%s: WARNING: device cache path too large: %s\n",
		    Pn, HASPERSDC);
	    i = 0;
	}
	buf[i] = '\0';
/*
 * If there is one, allocate space for the personal device cache path,
 * copy buf[] to it, and store its pointer in DCpath[3].
 */
	if (i) {
	    if (!(cp1 = mkstrcpy(buf, (MALLOC_S *)NULL))) {
		(void) fprintf(stderr,
		    "%s: no space for device cache path: ", Pn);
		safestrprt(buf, stderr, 1);
		merr = 1;
	    } else
		DCpath[3] = cp1;
	}
# endif	/* defined(HASPERSDC) */

/*
 * Quit if there was a malloc() error.  The appropriate error message
 * will have been issued to stderr.
 */
	if (merr)
	    Exit(1);
/*
 * Return the index of the first defined path.  Since DCpath[] is arranged
 * in priority order, searching it beginning to end follows priority.
 * Return an error indication if the search discloses no path name.
 */
	for (i = 0; i < MAXDCPATH; i++) {
	    if (DCpath[i])
		return(i);
	}
	if (!Fwarn && npw)
	    (void) fprintf(stderr,
		"%s: WARNING: can't form any device cache path\n", Pn);
	return(-1);
}


/*
 * open_dcache() - open device cache file
 */

int
open_dcache(m, r, s)
	int m;			/* mode: 1 = read; 2 = write */
	int r;			/* create DCpath[] if 0, reuse if 1 */
	struct stat *s;		/* stat() receiver */
{
	char buf[128];
	char *w = (char *)NULL;
/*
 * Get the device cache file paths.
 */
	if (!r) {
	    if ((DCpathX = dcpath(m, 1)) < 0)
		return(1);
	}
/*
 * Switch to the requested open() action.
 */
	switch (m) {
	case 1:

	/*
	 * Check for access permission.
	 */
	    if (!is_readable(DCpath[DCpathX], 0)) {
		if (DCpathX == 2 && errno == ENOENT)
		    return(2);
		if (!Fwarn)
		    (void) fprintf(stderr, ACCESSERRFMT,
			Pn, DCpath[DCpathX], strerror(errno));
		return(1);
	    }
	/*
	 * Open for reading.
	 */
	    if ((DCfd = open(DCpath[DCpathX], O_RDONLY, 0)) < 0) {
		if (DCstate == 3 && errno == ENOENT)
		    return(1);

cant_open:
		    (void) fprintf(stderr,
			"%s: WARNING: can't open %s: %s\n",
			Pn, DCpath[DCpathX], strerror(errno));
		return(1);
	    }
	    if (stat(DCpath[DCpathX], s) != 0) {

cant_stat:
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: can't stat(%s): %s\n",
			Pn, DCpath[DCpathX], strerror(errno));
close_exit:
		(void) close(DCfd);
		DCfd = -1;
		return(1);
	    }
	    if ((int)(s->st_mode & 07777) != ((DCpathX == 2) ? 0644 : 0600)) {
		(void) snpf(buf, sizeof(buf), "doesn't have %04o modes",
	    	    (DCpathX == 2) ? 0644 : 0600);
		w = buf;
	    } else if ((s->st_mode & S_IFMT) != S_IFREG)
		w = "isn't a regular file";
	    else if (!s->st_size)
		w = "is empty";
	    if (w) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: %s %s.\n", Pn, DCpath[DCpathX], w);
		goto close_exit;
	    }
	    return(0);
	case 2:

	/*
	 * Open for writing: first unlink any previous version; then
	 * open exclusively, specifying it's an error if the file exists.
	 */
	    if (unlink(DCpath[DCpathX]) < 0) {
		if (errno != ENOENT) {
		    if (!Fwarn)
			(void) fprintf(stderr,
			    "%s: WARNING: can't unlink %s: %s\n",
			    Pn, DCpath[DCpathX], strerror(errno));
		    return(1);
		}
	    }
	    if ((DCfd = open(DCpath[DCpathX], O_RDWR|O_CREAT|O_EXCL, 0600)) < 0)
		goto cant_open;
	/*
	 * If the real user is not root, but the process is setuid-root,
	 * change the ownerships of the file to the real ones.
	 */
	    if (Myuid && Setuidroot) {

# if	defined(DVCH_CHOWN)
	 	if (chown(DCpath[DCpathX], Myuid, getgid()) < 0)
# else	/* !defined(DVCH_CHOWN) */
	 	if (fchown(DCfd, Myuid, getgid()) < 0)
# endif	/* defined(DVCH_CHOWN) */

		{
		    if (!Fwarn)
			(void) fprintf(stderr,
			     "%s: WARNING: can't change ownerships of %s: %s\n",
			     Pn, DCpath[DCpathX], strerror(errno));
	 	}
	    }
	    if (!Fwarn && DCstate != 1 && !DCunsafe)
		(void) fprintf(stderr,
		    "%s: WARNING: created device cache file: %s\n",
			Pn, DCpath[DCpathX]);
	    if (stat(DCpath[DCpathX], s) != 0) {
		(void) unlink(DCpath[DCpathX]);
		goto cant_stat;
	    }
	    return(0);
	default:

	/*
	 * Oops!
	 */
	    (void) fprintf(stderr, "%s: internal error: open_dcache=%d\n",
		Pn, m);
	    Exit(1);
	}
	return(1);
}


/*
 * read_dcache() - read device cache file
 */

int
read_dcache()
{
	char buf[MAXPATHLEN*2], cbuf[64], *cp;
	int i, len, ov;
	struct stat sb, devsb;
/*
 * Open the device cache file.
 *
 * If the open at HASSYSDC fails because the file doesn't exist, and
 * the real UID of this process is not zero, try to open a device cache
 * file at HASPERSDC.
 */
	if ((ov = open_dcache(1, 0, &sb)) != 0) {
	    if (DCpathX == 2) {
		if (ov == 2 && DCpath[3]) {
		    DCpathX = 3;
		    if (open_dcache(1, 1, &sb) != 0)
			return(1);
		} else
		    return(1);
	    } else
		return(1);
	}
/*
 * If the open device cache file's last mtime/ctime isn't greater than
 * DVCH_DEVPATH's mtime/ctime, ignore it, unless -Dr was specified.
 */
	if (stat(DVCH_DEVPATH, &devsb) != 0) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: can't stat(%s): %s\n",
		    Pn, DVCH_DEVPATH, strerror(errno));
	} else {
	    if (sb.st_mtime <= devsb.st_mtime || sb.st_ctime <= devsb.st_ctime)
		DCunsafe = 1;
	}
	if (!(DCfs = fdopen(DCfd, "r"))) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: can't fdopen(%s)\n", Pn, DCpath[DCpathX]);
	    (void) close(DCfd);
	    DCfd = -1;
	    return(1);
	}
/*
 * Read the section count line; initialize the CRC table;
 * validate the section count line.
 */
	if (!fgets(buf, sizeof(buf), DCfs)) {

cant_read:
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: can't fread %s: %s\n", Pn, DCpath[DCpathX],
		    strerror(errno));
read_close:
		(void) fclose(DCfs);
		DCfd = -1;
		DCfs = (FILE *)NULL;
		(void) clr_devtab();

# if	defined(DCACHE_CLR)
		(void) DCACHE_CLR();
# endif	/* defined(DCACHE_CLR) */

		return(1);
	}
	(void) crcbld();
	DCcksum = 0;
	(void) crc(buf, strlen(buf), &DCcksum);
	i = 1;
	cp = "";

# if	defined(HASBLKDEV)
	i++;
	cp = "s";
# endif	/* defined(HASBLKDEV) */

# if	defined(DCACHE_CLONE)
	i++;
	cp = "s";
# endif	/* defined(DCACHE_CLONE) */

# if	defined(DCACHE_PSEUDO)
	i++;
	cp = "s";
# endif	/* defined(DCACHE_PSEUDO) */

	(void) snpf(cbuf, sizeof(cbuf), "%d section%s", i, cp);
	len = strlen(cbuf);
	(void) snpf(&cbuf[len], sizeof(cbuf) - len, ", dev=%lx\n",
		    (long)DevDev);
	if (!strncmp(buf, cbuf, len) && (buf[len] == '\n')) {
	    if (!Fwarn) {
		(void) fprintf(stderr,
		    "%s: WARNING: no /dev device in %s: line ", Pn,
		    DCpath[DCpathX]);
		safestrprt(buf, stderr, 1+4+8);
	    }
	    goto read_close;
	}
	if (strcmp(buf, cbuf)) {
	    if (!Fwarn) {
		(void) fprintf(stderr,
		    "%s: WARNING: bad section count line in %s: line ",
		    Pn, DCpath[DCpathX]);
		safestrprt(buf, stderr, 1+4+8);
	    }
	    goto read_close;
	}
/*
 * Read device section header and validate it.
 */
	if (!fgets(buf, sizeof(buf), DCfs))
	    goto cant_read;
	(void) crc(buf, strlen(buf), &DCcksum);
	len = strlen("device section: ");
	if (strncmp(buf, "device section: ", len) != 0) {

read_dhdr:
	    if (!Fwarn) {
		(void) fprintf(stderr,
		    "%s: WARNING: bad device section header in %s: line ",
		    Pn, DCpath[DCpathX]);
		safestrprt(buf, stderr, 1+4+8);
	    }
	    goto read_close;
	}
/*
 * Compute the device count; allocate Sdev[] and Devtp[] space.
 */
	if ((Ndev = atoi(&buf[len])) < 1)
	    goto read_dhdr;
	alloc_dcache();
/*
 * Read the device lines and store their information in Devtp[].
 * Construct the Sdev[] pointers to Devtp[].
 */
	for (i = 0; i < Ndev; i++) {
	    if (!fgets(buf, sizeof(buf), DCfs)) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: can't read device %d from %s\n",
			Pn, i + 1, DCpath[DCpathX]);
		goto read_close;
	    }
	    (void) crc(buf, strlen(buf), &DCcksum);
	/*
	 * Convert hexadecimal device number.
	 */
	    if (!(cp = x2dev(buf, &Devtp[i].rdev)) || *cp != ' ') {
		if (!Fwarn) {
		    (void) fprintf(stderr,
			"%s: device %d: bad device in %s: line ",
			Pn, i + 1, DCpath[DCpathX]);
		    safestrprt(buf, stderr, 1+4+8);
		}
		goto read_close;
	    }
	/*
	 * Convert inode number.
	 */
	    for (cp++, Devtp[i].inode = (INODETYPE)0; *cp != ' '; cp++) {
		if (*cp < '0' || *cp > '9') {
		    if (!Fwarn) {
			(void) fprintf(stderr,
			    "%s: WARNING: device %d: bad inode # in %s: line ",
			    Pn, i + 1, DCpath[DCpathX]);
			safestrprt(buf, stderr, 1+4+8);
		    }
		    goto read_close;
		}
		Devtp[i].inode = (INODETYPE)((Devtp[i].inode * 10)
			       + (int)(*cp - '0'));
	    }
	/*
	 * Get path name; allocate space for it; copy it; store the
	 * pointer in Devtp[]; clear verify status; construct the Sdev[]
	 * pointer to Devtp[].
	 */
	    if ((len = strlen(++cp)) < 2 || *(cp + len - 1) != '\n') {
		if (!Fwarn) {
		    (void) fprintf(stderr,
			"%s: WARNING: device %d: bad path in %s: line ",
			Pn, i + 1, DCpath[DCpathX]);
		    safestrprt(buf, stderr, 1+4+8);
		 }
		 goto read_close;
	    }
	    *(cp + len - 1) = '\0';
	    if (!(Devtp[i].name = mkstrcpy(cp, (MALLOC_S *)NULL))) {
		(void) fprintf(stderr,
		    "%s: device %d: no space for path: line ", Pn, i + 1);
		safestrprt(buf, stderr, 1+4+8);
		Exit(1);
	    }
	    Devtp[i].v = 0;
	    Sdev[i] = &Devtp[i];
	}

# if	defined(HASBLKDEV)
/*
 * Read block device section header and validate it.
 */
	if (!fgets(buf, sizeof(buf), DCfs))
	    goto cant_read;
	(void) crc(buf, strlen(buf), &DCcksum);
	len = strlen("block device section: ");
	if (strncmp(buf, "block device section: ", len) != 0) {
	    if (!Fwarn) {
		(void) fprintf(stderr,
		    "%s: WARNING: bad block device section header in %s: line ",
		    Pn, DCpath[DCpathX]);
		safestrprt(buf, stderr, 1+4+8);
	    }
	    goto read_close;
	}
/*
 * Compute the block device count; allocate BSdev[] and BDevtp[] space.
 */
	if ((BNdev = atoi(&buf[len])) > 0) {
	    alloc_bdcache();
	/*
	 * Read the block device lines and store their information in BDevtp[].
	 * Construct the BSdev[] pointers to BDevtp[].
	 */
	    for (i = 0; i < BNdev; i++) {
		if (!fgets(buf, sizeof(buf), DCfs)) {
		    if (!Fwarn)
			(void) fprintf(stderr,
			    "%s: WARNING: can't read block device %d from %s\n",
			    Pn, i + 1, DCpath[DCpathX]);
		    goto read_close;
		}
		(void) crc(buf, strlen(buf), &DCcksum);
	    /*
	     * Convert hexadecimal device number.
	     */
		if (!(cp = x2dev(buf, &BDevtp[i].rdev)) || *cp != ' ') {
		    if (!Fwarn) {
			(void) fprintf(stderr,
			    "%s: block dev %d: bad device in %s: line ",
			    Pn, i + 1, DCpath[DCpathX]);
			safestrprt(buf, stderr, 1+4+8);
		    }
		    goto read_close;
		}
	    /*
	     * Convert inode number.
	     */
		for (cp++, BDevtp[i].inode = (INODETYPE)0; *cp != ' '; cp++) {
		    if (*cp < '0' || *cp > '9') {
		      if (!Fwarn) {
			(void) fprintf(stderr,
			  "%s: WARNING: block dev %d: bad inode # in %s: line ",
			  Pn, i + 1, DCpath[DCpathX]);
			safestrprt(buf, stderr, 1+4+8);
		      }
		      goto read_close;
		    }
		    BDevtp[i].inode = (INODETYPE)((BDevtp[i].inode * 10)
				    + (int)(*cp - '0'));
		}
	    /*
	     * Get path name; allocate space for it; copy it; store the
	     * pointer in BDevtp[]; clear verify status; construct the BSdev[]
	     * pointer to BDevtp[].
	     */
		if ((len = strlen(++cp)) < 2 || *(cp + len - 1) != '\n') {
		    if (!Fwarn) {
			(void) fprintf(stderr,
			    "%s: WARNING: block dev %d: bad path in %s: line",
			    Pn, i + 1, DCpath[DCpathX]);
			safestrprt(buf, stderr, 1+4+8);
		    }
		    goto read_close;
		}
	        *(cp + len - 1) = '\0';
		if (!(BDevtp[i].name = mkstrcpy(cp, (MALLOC_S *)NULL))) {
		    (void) fprintf(stderr,
			"%s: block dev %d: no space for path: line", Pn, i + 1);
		    safestrprt(buf, stderr, 1+4+8);
		    Exit(1);
		}
	        BDevtp[i].v = 0;
	        BSdev[i] = &BDevtp[i];
	    }
	}
# endif	/* defined(HASBLKDEV) */

# if	defined(DCACHE_CLONE)
/*
 * Read the clone section.
 */
	if (DCACHE_CLONE(1))
	    goto read_close;
# endif	/* defined(DCACHE_CLONE) */

# if	defined(DCACHE_PSEUDO)
/*
 * Read the pseudo section.
 */
	if (DCACHE_PSEUDO(1))
	    goto read_close;
# endif	/* defined(DCACHE_PSEUDO) */

/*
 * Read and check the CRC section; it must be the last thing in the file.
 */
	(void) snpf(cbuf, sizeof(cbuf), "CRC section: %x\n", DCcksum);
	if (!fgets(buf, sizeof(buf), DCfs) || strcmp(buf, cbuf) != 0) {
	    if (!Fwarn) {
		(void) fprintf(stderr,
		    "%s: WARNING: bad CRC section in %s: line ",
		    Pn, DCpath[DCpathX]);
		safestrprt(buf, stderr, 1+4+8);
	    }
	    goto read_close;
	}
	if (fgets(buf, sizeof(buf), DCfs)) {
	    if (!Fwarn) {
		(void) fprintf(stderr,
		    "%s: WARNING: data follows CRC section in %s: line ",
		    Pn, DCpath[DCpathX]);
		safestrprt(buf, stderr, 1+4+8);
	    }
	    goto read_close;
	}
/*
 * Check one device entry at random -- the randomness based on our
 * PID.
 */
	i = (int)(Mypid % Ndev);
	if (stat(Devtp[i].name, &sb) != 0

# if	defined(DVCH_EXPDEV)
	||  expdev(sb.st_rdev) != Devtp[i].rdev
# else	/* !defined(DVCH_EXPDEV) */
	||  sb.st_rdev != Devtp[i].rdev
# endif	/* defined(DVCH_EXPDEV) */

	|| sb.st_ino != Devtp[i].inode) {
	    if (!Fwarn)
		(void) fprintf(stderr,
			"%s: WARNING: device cache mismatch: %s\n",
			Pn, Devtp[i].name);
	    goto read_close;
	}
/*
 * Close the device cache file and return OK.
 */
	(void) fclose(DCfs);
	DCfd = -1;
	DCfs = (FILE *)NULL;
	return(0);
}


# if	defined(DCACHE_CLONE_LOCAL)
/*
 * rw_clone_sect() - read/write the device cache file clone section
 */

static int
rw_clone_sect(m)
	int m;				/* mode: 1 = read; 2 = write */
{
	char buf[MAXPATHLEN*2], *cp, *cp1;
	struct clone *c;
	struct l_dev *dp;
	int i, j, len, n;

	if (m == 1) {

	/*
	 * Read the clone section header and validate it.
	 */
	    if (!fgets(buf, sizeof(buf), DCfs)) {

bad_clone_sect:
		if (!Fwarn) {
		    (void) fprintf(stderr,
			"%s: bad clone section header in %s: line ",
			Pn, DCpath[DCpathX]);
		    safestrprt(buf, stderr, 1+4+8);
		}
		return(1);
	    }
	    (void) crc(buf, strlen(buf), &DCcksum);
	    len = strlen("clone section: ");
	    if (strncmp(buf, "clone section: ", len) != 0)
		goto bad_clone_sect;
	    if ((n = atoi(&buf[len])) < 0)
		goto bad_clone_sect;
	/*
	 * Read the clone section lines and create the Clone list.
	 */
	    for (i = 0; i < n; i++) {
		if (fgets(buf, sizeof(buf), DCfs) == NULL) {
		    if (!Fwarn) {
			(void) fprintf(stderr,
			    "%s: no %d clone line in %s: line ", Pn, i + 1,
			    DCpath[DCpathX]);
			safestrprt(buf, stderr, 1+4+8);
		    }
		    return(1);
		}
		(void) crc(buf, strlen(buf), &DCcksum);
	    /*
	     * Assemble Devtp[] index and make sure it's correct.
	     */
		for (cp = buf, j = 0; *cp != ' '; cp++) {
		    if (*cp < '0' || *cp > '9') {

bad_clone_index:
			if (!Fwarn) {
			    (void) fprintf(stderr,
				"%s: clone %d: bad cached device index: line ",
				Pn, i + 1);
			    safestrprt(buf, stderr, 1+4+8);
			}
			return(1);
		    }
		    j = (j * 10) + (int)(*cp - '0');
		}
		if (j < 0 || j >= Ndev || (cp1 = strchr(++cp, '\n')) == NULL)
		    goto bad_clone_index;
		if (strncmp(cp, Devtp[j].name, (cp1 - cp)) != 0)
		    goto bad_clone_index;
	    /*
	     * Allocate and complete a clone structure.
	     */
		if (!(c = (struct clone *)malloc(sizeof(struct clone)))) {
		    (void) fprintf(stderr,
			"%s: clone %d: no space for cached clone: line ", Pn,
			i + 1);
		    safestrprt(buf, stderr, 1+4+8);
		    Exit(1);
		}
		c->dx = j;
		c->next = Clone;
		Clone = c;
	    }
	    return(0);
	} else if (m == 2) {

	/*
	 * Write the clone section header.
	 */
	    for (c = Clone, n = 0; c; c = c->next, n++)
		;
	    (void) snpf(buf, sizeof(buf), "clone section: %d\n", n);
	    if (wr2DCfd(buf, &DCcksum))
		return(1);
	/*
	 * Write the clone section lines.
	 */
	    for (c = Clone; c; c = c->next) {
		for (dp = &Devtp[c->dx], j = 0; j < Ndev; j++) {
		    if (dp == Sdev[j])
			break;
		}
		if (j >= Ndev) {
		    if (!Fwarn) {
			(void) fprintf(stderr,
			    "%s: can't make index for clone: ", Pn);
			safestrprt(dp->name, stderr, 1);
		    }
		    (void) unlink(DCpath[DCpathX]);
		    (void) close(DCfd);
		    DCfd = -1;
		    return(1);
		}
		(void) snpf(buf, sizeof(buf), "%d %s\n", j, dp->name);
		if (wr2DCfd(buf, &DCcksum))
		    return(1);
	    }
	    return(0);
	}
/*
 * A shouldn't-happen case: mode neither 1 nor 2.
 */
	(void) fprintf(stderr, "%s: internal rw_clone_sect error: %d\n",
	    Pn, m);
	Exit(1);
	return(1);		/* This useless return(1) keeps some
				 * compilers happy. */
}
# endif	/* defined(DCACHE_CLONE_LOCAL) */


/*
 * write_dcache() - write device cache file
 */

void
write_dcache()
{
	char buf[MAXPATHLEN*2], *cp;
	struct l_dev *dp;
	int i;
	struct stat sb;
/*
 * Open the cache file; set up the CRC table; write the section count.
 */
	if (open_dcache(2, 0, &sb))
    		return;
	i = 1;
	cp = "";

# if	defined(HASBLKDEV)
	i++;
	cp = "s";
# endif	/* defined(HASBLKDEV) */

# if	defined(DCACHE_CLONE)
	i++;
	cp = "s";
# endif	/* defined(DCACHE_CLONE) */

# if	defined(DCACHE_PSEUDO)
	i++;
	cp = "s";
# endif	/* defined(DCACHE_PSEUDO) */

	(void) snpf(buf, sizeof(buf), "%d section%s, dev=%lx\n", i, cp,
	    (long)DevDev);
	(void) crcbld();
	DCcksum = 0;
	if (wr2DCfd(buf, &DCcksum))
		return;
/*
 * Write the device section from the contents of Sdev[] and Devtp[].
 */
	(void) snpf(buf, sizeof(buf), "device section: %d\n", Ndev);
	if (wr2DCfd(buf, &DCcksum))
	    return;
	for (i = 0; i < Ndev; i++) {
	    dp = Sdev[i];
	    (void) snpf(buf, sizeof(buf), "%lx %ld %s\n", (long)dp->rdev,
			(long)dp->inode, dp->name);
	    if (wr2DCfd(buf, &DCcksum))
		return;
	}

# if	defined(HASBLKDEV)
/*
 * Write the block device section from the contents of BSdev[] and BDevtp[].
 */
	(void) snpf(buf, sizeof(buf), "block device section: %d\n", BNdev);
	if (wr2DCfd(buf, &DCcksum))
	    return;
	if (BNdev) {
	    for (i = 0; i < BNdev; i++) {
		dp = BSdev[i];
		(void) snpf(buf, sizeof(buf), "%lx %ld %s\n", (long)dp->rdev,
		    (long)dp->inode, dp->name);
		if (wr2DCfd(buf, &DCcksum))
		    return;
	    }
	}
# endif	/* defined(HASBLKDEV) */

# if	defined(DCACHE_CLONE)
/*
 * Write the clone section.
 */
	if (DCACHE_CLONE(2))
	    return;
# endif	/* defined(DCACHE_CLONE) */

# if	defined(DCACHE_PSEUDO)
/*
 * Write the pseudo section.
 */
	if (DCACHE_PSEUDO(2))
	    return;
# endif	/* defined(DCACHE_PSEUDO) */

/*
 * Write the CRC section and close the file.
 */
	(void) snpf(buf, sizeof(buf), "CRC section: %x\n", DCcksum);
	if (wr2DCfd(buf, (unsigned *)NULL))
		return;
	if (close(DCfd) != 0) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: can't close %s: %s\n",
		    Pn, DCpath[DCpathX], strerror(errno));
	    (void) unlink(DCpath[DCpathX]);
	    DCfd = -1;
	}
	DCfd = -1;
/*
 * If the previous reading of the previous device cache file marked it
 * "unsafe," drop that marking and record that the device cache file was
 * rebuilt.
 */
	if (DCunsafe) {
	    DCunsafe = 0;
	    DCrebuilt = 1;
	}
}


/*
 * wr2DCfd() - write to the DCfd file descriptor
 */

int
wr2DCfd(b, c)
	char *b;			/* buffer */
	unsigned *c;			/* checksum receiver */
{
	int bl, bw;

	bl = strlen(b);
	if (c)
	    (void) crc(b, bl, c);
	while (bl > 0) {
	    if ((bw = write(DCfd, b, bl)) < 0) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: can't write to %s: %s\n",
			Pn, DCpath[DCpathX], strerror(errno));
		(void) unlink(DCpath[DCpathX]);
		(void) close(DCfd);
		DCfd = -1;
		return(1);
	    }
	    b += bw;
	    bl -= bw;
	}
	return(0);
}
#else	/* !defined(HASDCACHE) */
char dvch_d1[] = "d"; char *dvch_d2 = dvch_d1;
#endif	/* defined(HASDCACHE) */
