

/*
 * $Id: dvch.frag,v 1.22 96/01/09 08:27:36 abe Exp $
 */


#if	defined(HASDCACHE)
/*
 * dvch.frag - fragment 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.
 *
 *	DCACHE_CLR	is the name of the function that clears the clone
 *			and pseudo caches when reading the device cache
 *			fails.
 *
 *	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.)
 *
 *	DVCH_ST_CTIME	is the dialect's special element name for ctime in
 *			the stat structure (default = st_ctime).
 *
 *	DVCH_ST_MTIME	is the dialect's special element name for mtime in
 *			the stat structure (default = st_mtime).
 *
 *	These functions are supplied a single integer argument, where:
 *
 *		1 = read
 *		2 = write
 *
 *	The functions must return 0 on success and 1 on failure.  The
 *	failure return must be accompanied by the issuance of stderr
 *	messages under the discretion of Fwarn.
 */


/*
 * Local definitions
 */

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

# if	!defined(DVCH_ST_CTIME)
#define	DVCH_ST_CTIME	st_ctime
# endif	/* !defined(DVCH_ST_CTIME) */

# if	!defined(DVCH_ST_MTIME)
#define	DVCH_ST_MTIME	st_mtime
# endif	/* !defined(DVCH_ST_MTIME) */

/*
 * Local storage
 */

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


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

void
alloc_dcache()
{
	if ((Devtp = (struct l_dev *)calloc((MALLOC_S)Ndev,
				     sizeof(struct l_dev)))
	== NULL) {
	    (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)))
	== NULL) {
	    (void) fprintf(stderr, "%s: no space for device pointers\n",
		Pn);
	    exit(1);
	}
}


/*
 * 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)
	int rw;				/* read (1) or write (2) mode */
{
	char buf[MAXPATHLEN+1], *cp1, *cp2, hn[MAXPATHLEN+1];
	int endf;
	int i, j, l;
	int ierr = 0;			/* intermediate error state */
	int merr = 0;			/* malloc error state */
	struct passwd *p = NULL;
	uid_t ul;
	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] = NULL;
	}
	if (DCpath[3]) {
	    (void) free((FREE_P *)DCpath[3]);
	    DCpath[3] = 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] = 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)) != NULL && (l = strlen(cp1)) > 0
	&&  !Setuidroot && Myuid && (rw == 1 || !Setgid)) {
	    if ((cp2 = (char *)malloc((MALLOC_S)(l + 1))) == NULL) {
		(void) fprintf(stderr,
		    "%s: no space for device cache path: %s=%s\n",
		    Pn, HASENVDC, cp1);
		merr = 1;
	    } else {
		(void) strcpy(cp2, cp1);
		DCpath[1] = cp2;
	    }
	} else if (cp1 && l > 0) {
	    if (!Fwarn && wenv)
		(void) fprintf(stderr,
		    "%s: WARNING: ignoring environment: %s=%s\n",
		    Pn, HASENVDC, cp1);
	    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] = 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 < 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 < 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 == NULL && (p = getpwuid(Myuid)) == NULL) {
		    if (!Fwarn) {
			(void) fprintf(stderr,
			    "%s: WARNING: can't get home dir for UID: %d\n",
			    Pn, Myuid);
		    }
		    ierr = 1;
		    break;
		}
		if ((i + (l = strlen(p->pw_dir))) >= 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, '.')) != NULL && cp2 > hn)
		    *cp2 = '\0';
		j = strlen(hn);
		if ((j + i) < 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)) != NULL
		&&  (l = strlen(cp2)) > 0
		&&  !Setuidroot
		&&  Myuid
		&&  (rw == 1 || !Setgid))
		{
		    if (i && buf[i - 1] == '/' && *cp2 == '/') {
			cp2++;
			l--;
		    }
		    if ((i + l) < (sizeof(buf) - 1)) {
			(void) strcpy(&buf[i], cp2);
			i += l;
			if (buf[i - 1] != '/') {
			    if (i < 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=%s\n",
				Pn, HASPERSDCPATH, cp2);
			} 
			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 == NULL && (p = getpwuid(Myuid)) == NULL) {
		    if (!Fwarn) {
			(void) fprintf(stderr,
			    "%s: WARNING: can't get login name for UID: %d\n",
			    Pn, Myuid);
		    }
		    ierr = 1;
		    break;
		}
		if ((i + (l = strlen(p->pw_name))) >= 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) sprintf(hn, "%d", Myuid);
		if ((i + (l = strlen(hn))) >= 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 = (char *)malloc((MALLOC_S)(i + 1))) == NULL) {
		(void) fprintf(stderr,
		    "%s: no space for device cache path: %s\n",
		    Pn, buf);
		merr = 1;
	    } else {
		(void) strcpy(cp1, buf);
		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) {
	    (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; s = write */
	int r;			/* create DCpath[] if 0, reuse if 1 */
	struct stat *s;		/* stat() receiver */
{
	char buf[128];
	int fp, lp;
	char *w = NULL;
/*
 * Get the device cache file paths.
 */
	if (!r) {
	    if ((DCpathX = dcpath(m)) < 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 ((s->st_mode & 07777) != ((DCpathX == 2) ? 0644 : 0600)) {
		(void) sprintf(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));
		    (void) unlink(DCpath[DCpathX]);
		    goto close_exit;
	 	}
	    }
	    if (!Fwarn)
		(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);		/* to make IRIX 6.0's C compiler happy */
}


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

int
read_dcache()
{
	char buf[MAXPATHLEN*2], cbuf[32], *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] != NULL) {
		    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 isn't greater than
 * DVCH_DEVPATH's mtime, 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.DVCH_ST_MTIME <= devsb.DVCH_ST_MTIME
	    ||  sb.DVCH_ST_MTIME <= devsb.DVCH_ST_CTIME) {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: %s needs to be rebuilt; %s is newer.\n",
			Pn, DCpath[DCpathX], DVCH_DEVPATH);
		if (DCstate != 2) {
		    (void) close(DCfd);
		    DCfd = -1;
		    return(1);
		}
	    }
	}
	if ((DCfs = fdopen(DCfd, "r")) == NULL) {
	    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) == NULL) {

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 = NULL;
		if (Devtp) {
			for (i = 0; i < Ndev; i++) {
				if (Devtp[i].name) {
					(void) free((FREE_P *)Devtp[i].name);
					Devtp[i].name = NULL;
				}
			}
			(void) free((FREE_P *)Devtp);
			Devtp = NULL;
		}
		if (Sdev) {
			(void) free((FREE_P *)Sdev);
			Sdev = NULL;
		}
		Ndev = 0;

# 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(DCACHE_CLONE)
	i++;
	cp = "s";
# endif	/* defined(DCACHE_CLONE) */

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

	(void) sprintf(cbuf, "%d section%s\n", i, cp);
	if (strcmp(buf, cbuf) != 0) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: bad section count line in %s: %s\n",
		    Pn, DCpath[DCpathX], buf);
	    goto read_close;
	}
/*
 * Read device section header and validate it.
 */
	if (fgets(buf, sizeof(buf), DCfs) == NULL)
	    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: %s\n",
		    Pn, DCpath[DCpathX], buf);
	    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) == NULL) {
		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)) == NULL || *cp != ' ') {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: device %d: bad device in %s: %s",
			Pn, i + 1, DCpath[DCpathX], buf);
		goto read_close;
	    }
	/*
	 * Convert inode number.
	 */
	    for (cp++, Devtp[i].inode = 0; *cp != ' '; cp++) {
		if (*cp < '0' || *cp > '9') {
		if (!Fwarn)
		    (void) fprintf(stderr,
			"%s: WARNING: device %d: bad inode # in %s: %s",
			Pn, i + 1, DCpath[DCpathX], buf);
		    goto read_close;
		}
		Devtp[i].inode = (Devtp[i].inode * 10) + (int)(*cp - '0');
	    }
	/*
	 * Get path name; allocate space for it; copy it; store the
	 * pointer in Devtp[]; 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: %s",
			Pn, i + 1, DCpath[DCpathX], buf);
		    goto read_close;
	    }
	    if ((Devtp[i].name = (char *)malloc(len)) == NULL) {
		(void) fprintf(stderr,
		    "%s: no space for %s", Pn, buf);
		exit(1);
	    }
	    *(cp + len - 1) = '\0';
	    (void) strcpy(Devtp[i].name, cp);
	    Sdev[i] = &Devtp[i];
	}

# 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) sprintf(cbuf, "CRC section: %x\n", DCcksum);
	if (fgets(buf, sizeof(buf), DCfs) == NULL || strcmp(buf, cbuf) != 0) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: bad CRC section in %s: %s",
		    Pn, DCpath[DCpathX], buf);
	    goto read_close;
	}
	if (fgets(buf, sizeof(buf), DCfs) != NULL) {
	    if (!Fwarn)
		(void) fprintf(stderr,
		    "%s: WARNING: data follows CRC section in %s: %s",
		    Pn, DCpath[DCpathX], buf);
	    goto read_close;
	}
/*
 * Check one device entry at random -- the randomness based on our
 * PID.
 */
	i = 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 = NULL;
	return(0);
}


/*
 * 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(DCACHE_CLONE)
	i++;
	cp = "s";
# endif	/* defined(DCACHE_CLONE) */

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

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

# 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) sprintf(buf, "CRC section: %x\n", DCcksum);
	if (wr2DCfd(buf, 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;
}


/*
 * 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);
}
#endif	/* defined(HASDCACHE) */
