OpenSolaris_b135/cmd/svr4pkg/pkgserv/pkgserv.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * The Solaris package installer in-memory database server.
 *
 * We'll keep the contents file as before; but we cache it
 * and we don't write it as often.  Instead, we log all
 * modifications to the log file.
 * Using the contents file and the logfile, the pkgserv can
 * rebuild the up-to-date contents file.
 * The logfile is constructed so that rebuilding the
 * contents file with the logfile is idempotent.
 *
 * The libpkg will start the daemon.
 *
 * The pkgserv will daemonize itself; the parent process
 * waits until the child process has initialized and will
 * start the door server.
 * If any error occurs during start-up, the error messages
 * are printed to stderr and the daemon will exit.
 * After start-up, any further errors are logged to syslog.
 * The parent pkgserv will exit with:
 *	0	- We've started
 *	1	- We couldn't start (locked)
 *	2	- Other problems (error on stderr)
 *     99	- Nothing reported; the caller must report.
 *
 * The daemon will timeout, by default.  It will write the
 * contents file after a first timeout; and after a further
 * timeout, the daemon will exit.
 *
 * The daemon will only timeout if the current "client" has exited;
 * to this end, we always look at the pid of the last caller.
 * If the last client is no longer around, we record the new client.
 * In the typical case of running installf/removef from a post/preinstall
 * script, we continue to follow the pkginstall/pkgremove client's pid.
 *
 * In the particular case of install, we make sure the daemon
 * sticks around.  (Install == install, (live)upgrade, zone install)
 */

#ifdef lint
#undef _FILE_OFFSET_BITS
#endif

#include <door.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <synch.h>
#include <sys/avl.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <syslog.h>
#include <limits.h>
#include <thread.h>
#include <ucred.h>
#include <umem.h>
#include <unistd.h>
#include <libintl.h>
#include <locale.h>

#include <pkglib.h>

#define	SADM_DIR	"/var/sadm/install"

#define	LOCK		".pkg.lock"
#define	CLIENTLOCK	".pkg.lock.client"
#define	CONTENTS	"contents"
#define	TCONTENTS	"t.contents"
#define	BADCONTENTS	"contents.badXXXXXX"

#define	LLNANOSEC	((int64_t)NANOSEC)

#define	DUMPTIMEOUT	60
#define	EXITTIMEOUT	300

/*
 * Contents file storage format.  At install time, the amount of memory
 * might be limited, so we make sure that we use as little memory
 * as possible.  The package tools modify the entries; so we install the
 * single lines.  We also remember the length of the path; this is needed
 * for avlcmp and we return it to the tools.  This saves time.
 *
 * All strings are allocated using umem_alloc.
 */
typedef struct pkgentry {
	char *line;		/* The contents line for the file */
	avl_node_t avl;		/* The avl header */
	int pkgoff;		/* Where the packages live; start with SP */
	int pathlen;		/* The length of the pathname */
	int len;		/* Length of the line (incl NUL) */
} pkgentry_t;

static char IS_ST0[256];
static char IS_ST0Q[256];

static void pkg_door_srv(void *, char *, size_t, door_desc_t *, uint_t);
static char *file_find(pkgfilter_t *, int *);
static void parse_contents(void);
static int parse_log(void);
static void pkgdump(void);
static int logflush(void);
static int avlcmp(const void *, const void *);
static void freeentry(pkgentry_t *);
static void swapentry(pkgentry_t *, pkgentry_t *);
static int establish_lock(char *);
static int no_memory_abort(void);
static int pkgfilter(pkgfilter_t *, door_desc_t *);
static int pkgaddlines(pkgfilter_t *);
static void finish(void);
static void signal_handler(int);
static void my_cond_reltimedwait(hrtime_t, int);
static hrtime_t time_since_(hrtime_t);

/*
 * Server actions
 *	- set mode (contents file, log file)
 *	- roll log
 *	- remove package
 *	- merge package entries
 */

static FILE *log;
static char *door = PKGDOOR;

static avl_tree_t listp, *list = &listp;

/* Keep the "the last command modified the contents file ... */
static char *ccmnt[2];
static int cind = 0;

static mutex_t mtx = DEFAULTMUTEX;
static cond_t cv = DEFAULTCV;

static int flushbeforemark = 1;
static int logerrcnt = 0;
static int loglines = 0;
static int suppressed = 0;
static int logcount;
static int ndumps;
static int ncalls;
static int changes;
static hrtime_t lastchange;
static hrtime_t lastcall;
static volatile int want_to_quit;
static boolean_t read_only = B_FALSE;
static boolean_t permanent = B_FALSE;
static boolean_t one_shot = B_FALSE;
static int write_locked;
static pid_t client_pid;
static int verbose = 1;
static hrtime_t dumptimeout = DUMPTIMEOUT;
static boolean_t sync_needed = B_FALSE;

static uid_t myuid;

static char marker[] = "###Marker\n";

static umem_cache_t *ecache;

static char pkgdir[PATH_MAX];

static void
server_main(int argc, char **argv)
{
	int did;
	int c;
	struct statvfs vfsbuf;
	int imexit = 0;
	pid_t parent;
	char *root = NULL;
	char *sadmdir = NULL;
	hrtime_t delta;
	int dir = 0;
	int dfd;

	(void) set_prog_name("pkgserv");

	openlog("pkgserv", LOG_PID | LOG_ODELAY, LOG_DAEMON);

	while ((c = getopt(argc, argv, "d:eoN:pP:R:r:")) != EOF) {
		switch (c) {
		case 'e':
			imexit = 1;
			break;
		case 'd':
			sadmdir = optarg;
			if (*sadmdir != '/' || strlen(sadmdir) >= PATH_MAX ||
			    access(sadmdir, X_OK) != 0)
				exit(99);
			break;
		case 'N':
			(void) set_prog_name(optarg);
			break;
		case 'o':
			one_shot = B_TRUE;
			verbose = 0;
			break;
		case 'p':
			/*
			 * We are updating possibly many zones; so we're not
			 * dumping based on a short timeout and we will not
			 * exit.
			 */
			permanent = B_TRUE;
			dumptimeout = 3600;
			break;
		case 'P':
			client_pid = atoi(optarg);
			break;
		case 'R':
			root = optarg;
			if (*root != '/' || strlen(root) >= PATH_MAX ||
			    access(root, X_OK) != 0)
				exit(99);
			break;
		case 'r':
			read_only = B_TRUE;
			one_shot = B_TRUE;
			verbose = 0;
			door = optarg;
			break;
		default:
			exit(99);
		}
	}

	if (one_shot && permanent) {
		progerr(gettext("Incorrect Usage"));
		exit(99);
	}

	umem_nofail_callback(no_memory_abort);

	if (root != NULL && strcmp(root, "/") != 0) {
		if (snprintf(pkgdir, PATH_MAX, "%s%s", root,
		    sadmdir == NULL ? SADM_DIR : sadmdir) >= PATH_MAX) {
			exit(99);
		}
	} else {
		if (sadmdir == NULL)
			(void) strcpy(pkgdir, SADM_DIR);
		else
			(void) strcpy(pkgdir, sadmdir);
	}

	if (chdir(pkgdir) != 0) {
		progerr(gettext("can't chdir to %s"), pkgdir);
		exit(2);
	}

	closefrom(3);

	if (!read_only && establish_lock(LOCK) < 0) {
		progerr(gettext(
		    "couldn't lock in %s (server running?): %s"),
		    pkgdir, strerror(errno));
		exit(1);
	}

	did = door_create(pkg_door_srv, 0, DOOR_REFUSE_DESC);
	if (did == -1) {
		progerr("door_create: %s", strerror(errno));
		exit(2);
	}

	(void) fdetach(door);

	if ((dfd = creat(door, 0644)) < 0 || close(dfd) < 0) {
		progerr("door_create: %s", strerror(errno));
		exit(2);
	}

	(void) mutex_lock(&mtx);

	myuid = geteuid();

	(void) sigset(SIGHUP, signal_handler);
	(void) sigset(SIGTERM, signal_handler);
	(void) sigset(SIGINT, signal_handler);
	(void) sigset(SIGQUIT, signal_handler);

	(void) signal(SIGPIPE, SIG_IGN);

	(void) atexit(finish);

	if (fattach(did, door) != 0) {
		progerr(gettext("attach door: %s"), strerror(errno));
		exit(2);
	}
	(void) close(did);

	ecache = umem_cache_create("entry", sizeof (pkgentry_t),
	    sizeof (char *), NULL, NULL, NULL, NULL, NULL, 0);

	avl_create(list, avlcmp, sizeof (pkgentry_t),
	    offsetof(pkgentry_t, avl));

	IS_ST0['\0'] = 1;
	IS_ST0[' '] = 1;
	IS_ST0['\t'] = 1;

	IS_ST0Q['\0'] = 1;
	IS_ST0Q[' '] = 1;
	IS_ST0Q['\t'] = 1;
	IS_ST0Q['='] = 1;

	parse_contents();
	if (parse_log() > 0)
		pkgdump();

	if (imexit)
		exit(0);

	if (statvfs(".", &vfsbuf) != 0) {
		progerr(gettext("statvfs: %s"), strerror(errno));
		exit(2);
	}

	if (strcmp(vfsbuf.f_basetype, "zfs") == 0)
		flushbeforemark = 0;

	/* We've started, tell the parent */
	parent = getppid();
	if (parent != 1)
		(void) kill(parent, SIGUSR1);

	if (!one_shot) {
		int fd;
		(void) setsid();
		fd = open("/dev/null", O_RDWR, 0);
		if (fd >= 0) {
			(void) dup2(fd, STDIN_FILENO);
			(void) dup2(fd, STDOUT_FILENO);
			(void) dup2(fd, STDERR_FILENO);
			if (fd > 2)
				(void) close(fd);
		}
	}

	lastcall = lastchange = gethrtime();

	/*
	 * Start the main thread, here is where we unlock the mutex.
	 */
	for (;;) {
		if (want_to_quit) {
			pkgdump();
			exit(0);
		}
		/* Wait forever when root or when there's a running filter */
		if (write_locked ||
		    (!one_shot && permanent && dir == changes)) {
			(void) cond_wait(&cv, &mtx);
			continue;
		}
		delta = time_since_(lastchange);
		/* Wait until DUMPTIMEOUT after last change before we pkgdump */
		if (delta < dumptimeout * LLNANOSEC) {
			my_cond_reltimedwait(delta, dumptimeout);
			continue;
		}
		/* Client still around? Just wait then. */
		if (client_pid > 1 && kill(client_pid, 0) == 0) {
			lastchange = lastcall = gethrtime();
			continue;
		}
		/* Wait for another EXITTIMEOUT seconds before we exit */
		if ((one_shot || !permanent) && dir == changes) {
			delta = time_since_(lastcall);
			if (delta < EXITTIMEOUT * LLNANOSEC) {
				my_cond_reltimedwait(delta, EXITTIMEOUT);
				continue;
			}
			exit(0);
		}
		pkgdump();
		dir = changes;
	}

	/*NOTREACHED*/
}

/*ARGSUSED*/
static void
nothing(int sig)
{
}

int
main(int argc, char **argv)
{
	int sig;
	sigset_t sset;
	int stat;

	/*
	 * We're starting the daemon; this process exits when the door
	 * server is established or when it fails to establish.
	 * We wait until the child process sends a SIGUSR1 or when it
	 * exits.
	 * We keep around who started us and as long as it lives, we don't
	 * exit.
	 */

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	client_pid = getppid();

	(void) sigemptyset(&sset);
	(void) sigaddset(&sset, SIGUSR1);
	(void) sigaddset(&sset, SIGCLD);

	/* We need to catch the SIGCLD before we can sigwait for it. */
	(void) sigset(SIGCLD, nothing);
	/* We need to make sure that SIGUSR1 is not ignored. */
	(void) sigset(SIGUSR1, SIG_DFL);
	(void) sigprocmask(SIG_BLOCK, &sset, NULL);

	/* We install the contents file readable. */
	(void) umask(022);

	switch (fork()) {
	case -1:
		exit(99);
		/*NOTREACHED*/
	case 0:
		server_main(argc, argv);
		/*NOTREACHED*/
	default:
		/* In the parent */
		break;
	}

	for (;;) {
		sig = sigwait(&sset);

		switch (sig) {
		case SIGCLD:
			if (wait(&stat) > 0) {
				if (WIFEXITED(stat))
					_exit(WEXITSTATUS(stat));
				else if (WIFSIGNALED(stat))
					_exit(99);
			}
			break;
		case SIGUSR1:
			_exit(0);
		}
	}
}

/*ARGSUSED*/
static void
pkg_door_srv(void *cookie, char *argp, size_t asz, door_desc_t *dp,
    uint_t ndesc)
{
	char *p = NULL;
	pkgcmd_t *pcmd = (pkgcmd_t *)argp;
	ucred_t *uc = NULL;
	uid_t caller;
	pid_t pcaller;
	door_desc_t ddp;
	int dnum = 0;
	int one = 1;
	int len = -1;

	if (asz < sizeof (pkgcmd_t)) {
		(void) door_return(NULL, 0, NULL, 0);
		return;
	}

	if (door_ucred(&uc) != 0) {
		(void) door_return(NULL, 0, NULL, 0);
		return;
	}

	caller = ucred_geteuid(uc);
	pcaller = ucred_getpid(uc);
	ucred_free(uc);

	if (caller != myuid) {
		(void) door_return(NULL, 0, NULL, 0);
		return;
	}

	(void) mutex_lock(&mtx);
	ncalls++;

	if (pcaller != client_pid && pcaller != -1 &&
	    (client_pid == 1 || kill(client_pid, 0) != 0)) {
		client_pid = pcaller;
	}

	if (PKG_WRITE_COMMAND(pcmd->cmd))
		while (write_locked > 0)
			(void) cond_wait(&cv, &mtx);

	switch (pcmd->cmd) {
	case PKG_FINDFILE:
		p = file_find((pkgfilter_t *)argp, &len);
		break;
	case PKG_DUMP:
		if (read_only)
			goto err;
		if (logcount > 0)
			pkgdump();
		break;
	case PKG_EXIT:
		if (logcount > 0)
			pkgdump();
		exit(0);
		/*NOTREACHED*/
	case PKG_PKGSYNC:
		if (read_only || logflush() != 0)
			goto err;
		break;
	case PKG_FILTER:
		if (pkgfilter((pkgfilter_t *)argp, &ddp) == 0)
			dnum = 1;
		break;
	case PKG_ADDLINES:
		if (read_only)
			goto err;
		changes++;

		if (pkgaddlines((pkgfilter_t *)argp) != 0)
			goto err;
		/* If we've updated the database, tell the dump thread */
		lastchange = gethrtime();
		(void) cond_broadcast(&cv);
		break;
	case PKG_NOP:
		/* Do nothing but register the current client's pid. */
		break;
	default:
		goto err;
	}

	lastcall = gethrtime();
	(void) mutex_unlock(&mtx);
	(void) door_return(p, len != -1 ? len : p == NULL ? 0 : strlen(p) + 1,
	    dnum == 0 ? NULL : &ddp, dnum);
	return;

err:
	(void) mutex_unlock(&mtx);
	(void) door_return((void *)&one, 4, NULL, NULL);
}

/*
 * This function returns the length of the string including exactly
 * nf fields.
 */
static ptrdiff_t
fieldoff(char *info, int nf)
{
	char *q = info;

	while (nf > 0) {
		if (IS_ST0[(unsigned char)*q++]) {
			if (q[-1] == 0)
				break;
			nf--;
		}
	}
	return (q - info - 1);
}

/*
 * The buf points into list of \n delimited lines.  We copy it,
 * removing the newline and adding a \0.
 */
static char *
mystrcpy(char *buf, int len)
{
	char *res = umem_alloc(len, UMEM_NOFAIL);

	(void) memcpy(res, buf, len - 1);
	res[len - 1] = '\0';
	return (res);
}

/*
 * Entry: a single line without the NEWLINE
 * Return: the package entry with the path determined.
 */
static pkgentry_t *
parse_line(char *buf, int blen, boolean_t full)
{
	char *t;
	pkgentry_t *p;
	int nfields;

	p = umem_cache_alloc(ecache, UMEM_NOFAIL);
	buf = p->line = mystrcpy(buf, blen + 1);
	p->len = blen + 1;

	t = buf;

	while (!IS_ST0Q[(unsigned char)*t++])
		;

	p->pathlen = t - buf - 1;
	if (p->pathlen == 0 || p->pathlen >= PATH_MAX) {
		progerr("bad entry read in contents file");
		logerr("pathname: Unknown");
		logerr("problem: unable to read pathname field");
		if (one_shot)
			exit(2);
	}
	if (t[-1] == '=')
		while (!IS_ST0[(unsigned char)*t++])
			;

	/* Partial as found in the "-" entries for log */
	if (t[-1] == '\0') {
		if (full)
			goto badline;

		p->pkgoff = -1;
		return (p);
	}

	switch (*t) {
	case '?':
		nfields = 0;
		break;
	case 's':
	case 'l':
		/* Fields: class */
		nfields = 1;
		break;
	case 'p':
	case 'x':
	case 'd':
		/* class mode owner group */
		nfields = 4;
		break;
	case 'f':
	case 'e':
	case 'v':
		/* class mode owner group size csum time */
		nfields = 7;
		break;
	case 'c':
	case 'b':
		/* class major minor mode owner group */
		nfields = 6;
		break;
	default:
		progerr("bad entry read in contents file");
		logerr("pathname: %.*s", p->pathlen, p->line);
		logerr("problem: unknown ftype");
		freeentry(p);
		if (one_shot)
			exit(2);
		return (NULL);
	}

	p->pkgoff = t + fieldoff(t, nfields + 1) - buf;

	if (p->line[p->pkgoff] != '\0' || p->pkgoff == p->len - 1)
		return (p);

badline:
	progerr(gettext("bad entry read in contents file"));
	logerr(gettext("pathname: Unknown"));
	logerr(gettext("problem: unknown ftype"));
	freeentry(p);
	if (one_shot)
		exit(2);
	return (NULL);
}

static void
handle_comments(char *buf, int len)
{
	if (cind >= 2)
		return;

	if (buf[0] != '#')
		return;

	if (ccmnt[cind] != NULL)
		umem_free(ccmnt[cind], strlen(ccmnt[cind]) + 1);
	ccmnt[cind] = mystrcpy(buf, len);
	cind++;
}

static void
parse_contents(void)
{
	int cnt;
	pkgentry_t *ent, *e2;
	avl_index_t where;
	int num = 0;
	struct stat stb;
	ptrdiff_t off;
	char *p, *q, *map;
	pkgentry_t *lastentry = NULL;
	int d;
	int cntserrs = 0;

	cnt = open(CONTENTS, O_RDONLY);

	cind = 0;

	if (cnt == -1) {
		if (errno == ENOENT)
			return;
		exit(99);
	}

	if (fstat(cnt, &stb) != 0) {
		(void) close(cnt);
		exit(99);
	}
	if (stb.st_size == 0) {
		(void) close(cnt);
		return;
	}

	map = mmap(0, stb.st_size, PROT_READ, MAP_PRIVATE, cnt, 0);
	(void) close(cnt);
	if (map == (char *)-1)
		return;

	(void) madvise(map, stb.st_size, MADV_WILLNEED);

	for (off = 0; off < stb.st_size; off += q - p) {
		p = map + off;
		q = memchr(p, '\n', stb.st_size - off);
		if (q == NULL)
			break;

		q++;
		num++;
		if (p[0] == '#' || p[0] == '\n') {
			handle_comments(p, q - p);
			continue;
		}
		ent = parse_line(p, q - p - 1, B_TRUE);

		if (ent == NULL) {
			cntserrs++;
			continue;
		}

		/*
		 * We save time by assuming the database is sorted; by
		 * using avl_insert_here(), building the tree is nearly free.
		 * lastentry always contains the last entry in the AVL tree.
		 */
		if (lastentry == NULL) {
			avl_add(list, ent);
			lastentry = ent;
		} else if ((d = avlcmp(ent, lastentry)) == 1) {
			avl_insert_here(list, ent, lastentry, AVL_AFTER);
			lastentry = ent;
		} else if (d == 0 ||
		    (e2 = avl_find(list, ent, &where)) != NULL) {
			/*
			 * This can only happen if the contents file is bad;
			 * this can, e.g., happen with the old SQL contents DB,
			 * it didn't sort properly.  Assume the first one
			 * is the correct one, but who knows?
			 */
			if (d == 0)
				e2 = lastentry;
			if (strcmp(ent->line, e2->line) != 0) {
				progerr(gettext("two entries for %.*s"),
				    ent->pathlen, ent->line);
				cntserrs++;
			}
			freeentry(ent);
		} else {
			/* Out of order: not an error for us, really. */
			progerr(gettext("bad read of contents file"));
			logerr(gettext("pathname: Unknown"));
			logerr(gettext(
			    "problem: unable to read pathname field"));
			if (one_shot)
				exit(2);
			avl_insert(list, ent, where);
		}
	}

	cind = 0;

	(void) munmap(map, stb.st_size);

	/* By default, we ignore bad lines, keep them in a copy. */
	if (cntserrs > 0 && stb.st_nlink == 1) {
		char bcf[sizeof (BADCONTENTS)];

		(void) strcpy(bcf, BADCONTENTS);
		if (mktemp(bcf) != NULL) {
			(void) link(CONTENTS, bcf);
			syslog(LOG_WARNING, "A bad contents file was saved: %s",
			    bcf);
		}
	}
}

static int
parse_log(void)
{
	pkgentry_t *ent, *look;
	avl_index_t where;
	int num = 0;
	int logfd;
	struct stat stb;
	int mlen = strlen(marker);
	off_t realend;
	ptrdiff_t off;
	char *p, *q, *map;

	logfd = open(PKGLOG, O_RDONLY);

	if (logfd < 0) {
		if (errno == ENOENT)
			return (0);
		progerr(gettext("cannot read "PKGLOG": %s"), strerror(errno));
		exit(2);
	}

	if (fstat(logfd, &stb) != 0) {
		progerr(gettext("cannot stat "PKGLOG": %s"), strerror(errno));
		exit(2);
	}

	if (stb.st_size == 0) {
		(void) close(logfd);
		/* Force pkgdump && remove of the logfile. */
		return (1);
	}

	map = mmap(0, stb.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE,
	    logfd, 0);
	(void) close(logfd);
	if (map == (char *)-1) {
		progerr(gettext("Cannot mmap the "PKGLOG": %s"),
		    strerror(errno));
		exit(2);
	}

	cind = 0;

	realend = stb.st_size;

	if (memcmp(map + realend - mlen, marker, mlen) != 0) {
		progerr(gettext(PKGLOG" is not complete"));

		map[stb.st_size - 1] = '\0'; /* for strstr() */
		realend = 0;
		for (p = map; q = strstr(p, marker); ) {
			if (q == map || q[-1] == '\n')
				realend = q - map + mlen;
			p = q + mlen;
		}
		progerr(gettext("Ignoring %ld bytes from log"),
		    (long)(stb.st_size - realend));
	}

	for (off = 0; off < realend; off += q - p) {
		p = map + off;
		q = memchr(p, '\n', realend - off);
		if (q == NULL)
			break;

		q++;
		num++;
		if (p[0] == '#' || p[0] == '\n') {
			if (memcmp(marker, p, mlen) == 0)
				cind = 0;
			else
				handle_comments(p, q - p);
			continue;
		}

		ent = parse_line(p + 1, q - (p + 1) - 1, p[0] != '-');
		if (ent == NULL)
			continue;
		look = avl_find(list, ent, &where);
		/*
		 * The log can be replayed; so any value of "look" is
		 * not unexpected.
		 */
		switch (p[0]) {
		case '+':
		case '=':
			if (look != NULL)
				swapentry(look, ent);
			else
				avl_insert(list, ent, where);
			break;
		case '-':
			if (look != NULL) {
				avl_remove(list, look);
				freeentry(look);
			}
			freeentry(ent);
			break;
		default:
			freeentry(ent);
			progerr(gettext("log %d: bad line"), num);
			break;
		}
	}
	(void) munmap(map, stb.st_size);

	/* Force pkgdump && remove of the logfile if there are no valid mods. */
	return (num == 0 ? 1 : num);
}

static char *
file_find(pkgfilter_t *cmd, int *len)
{
	pkgentry_t p;
	pkgentry_t *look;

	p.line = cmd->buf;
	p.pathlen = cmd->len;

	look = avl_find(list, &p, NULL);

	if (look == NULL)
		return (NULL);

	*len = look->len;
	return (look->line);
}

static void
pkgdump(void)
{
	FILE *cnts;
	int err = 0;
	pkgentry_t *p;

	if (read_only)
		return;

	/* We cannot dump when the current transaction is not complete. */
	if (sync_needed)
		return;

	cnts = fopen(TCONTENTS, "w");

	if (cnts == NULL)
		exit(99);

	for (p = avl_first(list); p != NULL; p = AVL_NEXT(list, p)) {
		if (fprintf(cnts, "%s\n", p->line) < 0)
			err++;
	}

	if (ccmnt[0] != NULL)
		(void) fprintf(cnts, "%s\n", ccmnt[0]);
	if (ccmnt[1] != NULL)
		(void) fprintf(cnts, "%s\n", ccmnt[1]);

	if (err != 0 || fflush(cnts) == EOF || fsync(fileno(cnts)) != 0 ||
	    fclose(cnts) == EOF || rename(TCONTENTS, CONTENTS) != 0) {
		err++;
	}

	if (err != 0) {
		progerr("cannot rewrite the contents file");
		exit(2);
	}

	(void) fclose(log);
	(void) unlink(PKGLOG);
	log = NULL;
	ndumps++;
	logcount = 0;
}

static void
freeentry(pkgentry_t *p)
{
	umem_free(p->line, p->len);
	umem_cache_free(ecache, p);
}

static void
swapentry(pkgentry_t *cur, pkgentry_t *new)
{
	if (cur->len == new->len &&
	    strcmp(cur->line + cur->pathlen,
	    new->line + new->pathlen) == 0) {
		suppressed++;
		freeentry(new);
		return;
	}

	/* Free old line */
	umem_free(cur->line, cur->len);

	/* Copy new value: pathlen is the same and avl is kept */
	cur->line = new->line;
	cur->len = new->len;
	cur->pkgoff = new->pkgoff;

	umem_cache_free(ecache, new);
}

static int
logentry(char type, pkgentry_t *p)
{
	int len;

	if (type == '-')
		len = fprintf(log, "-%.*s\n", p->pathlen, p->line);
	else
		len = fprintf(log, "%c%s\n", type, p->line);

	loglines++;
	if (len < 0) {
		logerrcnt++;
		return (-1);
	}
	logcount += len;
	return (0);
}

static int
logflush(void)
{
	int len;
	static int lastflush;

	if (log == NULL)
		return (0);

	if (lastflush == logcount)
		return (0);

	if (cind == 2) {
		(void) fprintf(log, "%s\n", ccmnt[0]);
		(void) fprintf(log, "%s\n", ccmnt[1]);
		cind = 0;
	}

	/*
	 * When using zfs, if the mark is there, then so is the rest before
	 * it.  But with ufs, we need to flush twice.
	 */
	if (flushbeforemark) {
		if (fflush(log) == EOF)
			logerrcnt++;
	}
	/* Anything before the last marker found in the log will be valid */
	len = fprintf(log, "%s", marker);
	if (len < 0)
		logerrcnt++;
	else
		logcount += len;

	if (fflush(log) == EOF)
		logerrcnt++;

	sync_needed = B_FALSE;

	if (logerrcnt > 0 || logcount > MAXLOGFILESIZE)
		pkgdump();

	if (logerrcnt > 0)
		return (-1);

	lastflush = logcount;

	return (0);
}

static int
avlcmp(const void *ca, const void *cb)
{
	const pkgentry_t *a = ca;
	const pkgentry_t *b = cb;
	int i = memcmp(a->line, b->line,
	    a->pathlen > b->pathlen ? b->pathlen : a->pathlen);

	if (i < 0)
		return (-1);
	else if (i > 0)
		return (1);
	else if (a->pathlen == b->pathlen)
		return (0);
	else if (a->pathlen > b->pathlen)
		return (1);
	else
		return (-1);
}

/*
 * Returns:
 *	0 - if we can get the lock
 *	-1 - we can't lock
 */

static int
establish_lock(char *lock)
{
	int fd = open(lock, O_RDWR|O_CREAT, 0644);
	int i;

	if (fd < 0)
		return (-1);

	for (i = 0; i < 5; i++) {
		if (lockf(fd, F_TLOCK, 0) == 0)
			return (0);
		(void) sleep(1);
	}

	(void) close(fd);
	return (-1);
}

static int
no_memory_abort(void)
{
	return (UMEM_CALLBACK_EXIT(99));
}

/*
 * Dump a part of the contents file in a pipe; grep for the "filter".
 * It doesn't matter if we return too much.
 */

static void *
thr_pkgfilter(void *v)
{
	pkgfilter_t *pf = v;
	pkgentry_t *p;
	int nums[2];
	FILE *cnts;

	cnts = fdopen(pf->cmd, "w");
	if (cnts == NULL)
		goto free;

	/*
	 * Remove wild card: don't care about extra matches; make sure
	 * we remove both the "*" and the "." in front of it.
	 */
	if (pf->len > 0) {
		char *p;

		for (p = pf->buf; *p; p++) {
			if (*p == '*') {
				*p = 0;
				if (p > pf->buf && p[-1] == '.')
					p[-1] = 0;
				break;
			}
		}
	}

	/* Disable modifications while the filter is running */
	(void) mutex_lock(&mtx);
	write_locked++;
	(void) mutex_unlock(&mtx);
	/*
	 * The protocol for the contents file for the clients:
	 * <int:len><int:pathlen><line + 0>
	 */

	for (p = avl_first(list); p != NULL; p = AVL_NEXT(list, p)) {
		if (pf->len > 0 && strstr(p->line, pf->buf) == NULL)
			continue;

		nums[0] = p->len;
		nums[1] = p->pathlen;
		if (fwrite(nums, sizeof (int), 2, cnts) != 2)
			break;
		if (fwrite(p->line, 1, p->len, cnts) != p->len)
			break;
	}

	(void) mutex_lock(&mtx);
	lastcall = gethrtime();
	write_locked--;
	(void) cond_broadcast(&cv);
	(void) mutex_unlock(&mtx);
	(void) fclose(cnts);

free:
	umem_free(pf, sizeof (pkgfilter_t) + pf->len);
	return (NULL);
}

static hrtime_t
time_since_(hrtime_t last)
{
	return (gethrtime() - last);
}

static void
my_cond_reltimedwait(hrtime_t delta, int sec)
{
	hrtime_t wait = sec * LLNANOSEC - delta;
	timestruc_t waitfor;

	waitfor.tv_nsec = wait % LLNANOSEC;
	waitfor.tv_sec = wait / LLNANOSEC;
	(void) cond_reltimedwait(&cv, &mtx, &waitfor);
}

static int
pkgfilter(pkgfilter_t *pf, door_desc_t *dp)
{

	int p[2];
	thread_t tid;
	pkgfilter_t *cpf;

	if (pipe(p) != 0)
		return (-1);

	cpf = umem_alloc(sizeof (pkgfilter_t) + pf->len, UMEM_NOFAIL);

	(void) memcpy(cpf, pf, sizeof (pkgfilter_t) + pf->len);

	/* Copy the file descriptor in the command field */
	cpf->cmd = p[1];

	if (thr_create(NULL, NULL, thr_pkgfilter, cpf, THR_DETACHED,
	    &tid) != 0) {
		(void) close(p[0]);
		(void) close(p[1]);
		umem_free(cpf, sizeof (pkgfilter_t) + pf->len);
		return (-1);
	}
	(void) memset(dp, 0, sizeof (*dp));
	dp->d_attributes = DOOR_DESCRIPTOR | DOOR_RELEASE;
	dp->d_data.d_desc.d_descriptor = p[0];

	return (0);
}

static int
pkgaddlines(pkgfilter_t *pf)
{
	char *map = pf->buf;
	int len = pf->len;
	int off;
	pkgentry_t *ent, *look;
	avl_index_t where;
	char *q, *p;
	char c;
	int r = 0;

	if (log == NULL) {
		log = fopen(PKGLOG, "w");
		if (log == NULL)
			return (-1);
	}

	for (off = 0; off < len; off += q - p) {
		p = map + off;
		q = memchr(p, '\n', len - off);

		if (q == NULL)
			break;

		q++;

		if (p[0] == '#' || p[0] == '\n') {
			handle_comments(p, q - p);
			continue;
		}

		if (*p == '-')
			ent = parse_line(p + 1, q - (p + 1) - 1, B_FALSE);
		else
			ent = parse_line(p, q - p - 1, B_TRUE);

		if (ent == NULL) {
			r++;
			continue;
		}

		look = avl_find(list, ent, &where);
		if (look != NULL) {
			c = *p == '-' ? '-' : '=';
			if (c == '=') {
				swapentry(look, ent);
				ent = look;
			} else {
				avl_remove(list, look);
				freeentry(look);
			}
		} else if (*p == '-') {
			/* Remove something which isn't there: no-op */
			freeentry(ent);
			continue;
		} else {
			avl_insert(list, ent, where);
			c = '+';
		}

		sync_needed = B_TRUE;
		r += logentry(c, ent);
		if (c == '-')
			freeentry(ent);
	}

	return (r);
}

static void
finish(void)
{
	if (verbose) {
		syslog(LOG_DEBUG,
		    "finished: calls %d, pkgdumps %d, loglines %d "
		    "(suppressed %d)\n",
		    ncalls, ndumps, loglines, suppressed);
	}
	(void) fdetach(door);
	if (read_only)
		(void) unlink(door);
}

/*
 * Tell the wait thread to wake up and quit.
 */
/* ARGSUSED */
static void
signal_handler(int sig)
{
	if (read_only)
		exit(0);
	want_to_quit = 1;
	(void) cond_broadcast(&cv);
}