/*
 * nfsdcld.c -- NFSv4 client name tracking daemon
 *
 * Copyright (C) 2011  Red Hat, Jeff Layton <jlayton@redhat.com>
 *
 * 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., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <errno.h>
#include <event2/event.h>
#include <stdbool.h>
#include <getopt.h>
#include <signal.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <libgen.h>
#include <sys/inotify.h>
#ifdef HAVE_SYS_CAPABILITY_H
#include <sys/prctl.h>
#include <sys/capability.h>
#endif

#include "xlog.h"
#include "nfslib.h"
#include "cld.h"
#include "cld-internal.h"
#include "sqlite.h"
#include "version.h"
#include "conffile.h"
#include "legacy.h"

#ifndef DEFAULT_PIPEFS_DIR
#define DEFAULT_PIPEFS_DIR NFS_STATEDIR "/rpc_pipefs"
#endif

#define DEFAULT_CLD_PATH	"/nfsd/cld"

#ifndef CLD_DEFAULT_STORAGEDIR
#define CLD_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcld"
#endif

#define NFSD_END_GRACE_FILE "/proc/fs/nfsd/v4_end_grace"

/* private data structures */

/* global variables */
static char pipefs_dir[PATH_MAX] = DEFAULT_PIPEFS_DIR;
static char pipepath[PATH_MAX];
static int 		inotify_fd = -1;
static struct event	 *pipedir_event;
static struct event_base *evbase;
static bool old_kernel = false;
static bool signal_received = false;

uint64_t current_epoch;
uint64_t recovery_epoch;
int first_time;
int num_cltrack_records;
int num_legacy_records;

static struct option longopts[] =
{
	{ "help", 0, NULL, 'h' },
	{ "foreground", 0, NULL, 'F' },
	{ "debug", 0, NULL, 'd' },
	{ "pipefsdir", 1, NULL, 'p' },
	{ "storagedir", 1, NULL, 's' },
	{ NULL, 0, 0, 0 },
};

/* forward declarations */
static void cldcb(int UNUSED(fd), short which, void *data);

static void
sig_die(int signal)
{
	if (signal_received) {
		xlog(D_GENERAL, "forced exiting on signal %d\n", signal);
		exit(0);
	}

	signal_received = true;
	xlog(D_GENERAL, "exiting on signal %d\n", signal);
	event_base_loopexit(evbase, NULL);
}

static void
usage(char *progname)
{
	printf("%s [ -hFd ] [ -p pipefsdir ] [ -s storagedir ]\n", progname);
}

static int
cld_set_caps(void)
{
	int ret = 0;
#ifdef HAVE_SYS_CAPABILITY_H
	unsigned long i;
	cap_t caps;

	if (getuid() != 0) {
		xlog(L_ERROR, "Not running as root. Daemon won't be able to "
			      "open the pipe after dropping capabilities!");
		return -EINVAL;
	}

	/* prune the bounding set to nothing */
	for (i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0 ; ++i) {
		ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
		if (ret) {
			xlog(L_ERROR, "Unable to prune capability %lu from "
				      "bounding set: %m", i);
			return -errno;
		}
	}

	/* get a blank capset */
	caps = cap_init();
	if (caps == NULL) {
		xlog(L_ERROR, "Unable to get blank capability set: %m");
		return -errno;
	}

	/* reset the process capabilities */
	if (cap_set_proc(caps) != 0) {
		xlog(L_ERROR, "Unable to set process capabilities: %m");
		ret = -errno;
	}
	cap_free(caps);
#endif
	return ret;
}

#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX)

static int
cld_pipe_open(struct cld_client *clnt)
{
	int fd;
	struct event *ev;

	xlog(D_GENERAL, "%s: opening upcall pipe %s", __func__, pipepath);
	fd = open(pipepath, O_RDWR, 0);
	if (fd < 0) {
		xlog(D_GENERAL, "%s: open of %s failed: %m", __func__, pipepath);
		return -errno;
	}

	ev = event_new(evbase, fd, EV_READ, cldcb, clnt);
	if (ev == NULL) {
		xlog(D_GENERAL, "%s: failed to create event for %s", __func__, pipepath);
		close(fd);
		return -ENOMEM;
	}

	if (clnt->cl_event && event_initialized(clnt->cl_event)) {
		event_del(clnt->cl_event);
		event_free(clnt->cl_event);
	}
	if (clnt->cl_fd >= 0)
		close(clnt->cl_fd);

	clnt->cl_fd = fd;
	clnt->cl_event = ev;
	/* event_add is done by the caller */
	return 0;
}

static void
cld_inotify_cb(int UNUSED(fd), short which, void *data)
{
	int ret;
	size_t elen;
	ssize_t rret;
	char evbuf[INOTIFY_EVENT_MAX];
	char *dirc = NULL, *pname;
	struct inotify_event *event = (struct inotify_event *)evbuf;
	struct cld_client *clnt = data;

	if (which != EV_READ)
		return;

	xlog(D_GENERAL, "%s: called for EV_READ", __func__);

	dirc = strndup(pipepath, PATH_MAX);
	if (!dirc) {
		xlog(L_ERROR, "%s: unable to allocate memory", __func__);
		goto out;
	}

	rret = read(inotify_fd, evbuf, INOTIFY_EVENT_MAX);
	if (rret < 0) {
		xlog(L_ERROR, "%s: read from inotify fd failed: %m", __func__);
		goto out;
	}

	/* check to see if we have a filename in the evbuf */
	if (!event->len) {
		xlog(D_GENERAL, "%s: no filename in inotify event", __func__);
		goto out;
	}

	pname = basename(dirc);
	elen = strnlen(event->name, event->len);

	/* does the filename match our pipe? */
	if (strlen(pname) != elen || memcmp(pname, event->name, elen)) {
		xlog(D_GENERAL, "%s: wrong filename (%s)", __func__,
				event->name);
		goto out;
	}

	ret = cld_pipe_open(clnt);
	switch (ret) {
	case 0:
		/* readd the event for the cl_event pipe */
		event_add(clnt->cl_event, NULL);
		break;
	case -ENOENT:
		/* pipe must have disappeared, wait for it to come back */
		goto out;
	default:
		/* anything else is fatal */
		xlog(L_FATAL, "%s: unable to open new pipe (%d). Aborting.",
			__func__, ret);
		exit(ret);
	}

out:
	event_add(pipedir_event, NULL);
	free(dirc);
}

static int
cld_inotify_setup(void)
{
	int ret;
	char *dirc, *dname;

	dirc = strndup(pipepath, PATH_MAX);
	if (!dirc) {
		xlog_err("%s: unable to allocate memory", __func__);
		ret = -ENOMEM;
		goto out_free;
	}

	dname = dirname(dirc);

	inotify_fd = inotify_init();
	if (inotify_fd < 0) {
		xlog_err("%s: inotify_init failed: %m", __func__);
		ret = -errno;
		goto out_free;
	}

	ret = inotify_add_watch(inotify_fd, dname, IN_CREATE);
	if (ret < 0) {
		xlog_err("%s: inotify_add_watch failed: %m", __func__);
		ret = -errno;
		goto out_err;
	} else
		ret = 0;

out_free:
	free(dirc);
	return ret;
out_err:
	close(inotify_fd);
	goto out_free;
}

/*
 * Set an inotify watch on the directory that should contain the pipe, and then
 * try to open it. If it fails with anything but -ENOENT, return the error
 * immediately.
 *
 * If it succeeds, then set up the pipe event handler. At that point, set up
 * the inotify event handler and go ahead and return success.
 */
static int
cld_pipe_init(struct cld_client *clnt)
{
	int ret;

	xlog(D_GENERAL, "%s: init pipe handlers", __func__);

	ret = cld_inotify_setup();
	if (ret != 0)
		goto out;

	clnt->cl_fd = -1;
	ret = cld_pipe_open(clnt);
	switch (ret) {
	case 0:
		/* add the event and we're good to go */
		event_add(clnt->cl_event, NULL);
		break;
	case -ENOENT:
		/* ignore this error -- cld_inotify_cb will handle it */
		ret = 0;
		break;
	default:
		/* anything else is fatal */
		close(inotify_fd);
		goto out;
	}

	/* set event for inotify read */
	pipedir_event = event_new(evbase, inotify_fd, EV_READ, cld_inotify_cb, clnt);
	if (pipedir_event == NULL) {
		close(inotify_fd);
		return -ENOMEM;
	}
	event_add(pipedir_event, NULL);
out:
	return ret;
}

/*
 * Older kernels will not tell nfsdcld when a grace period has started.
 * Therefore we have to peek at the /proc/fs/nfsd/v4_end_grace file to
 * see if nfsd is in grace.  We have to do this for create and remove
 * upcalls to ensure that the correct table is being updated - otherwise
 * we could lose client records when the grace period is lifted.
 */
static int
cld_check_grace_period(void)
{
	int fd, ret = 0;
	char c;

	if (!old_kernel)
		return 0;
	if (recovery_epoch != 0)
		return 0;
	fd = open(NFSD_END_GRACE_FILE, O_RDONLY);
	if (fd < 0) {
		xlog(L_WARNING, "Unable to open %s: %m",
			NFSD_END_GRACE_FILE);
		return 1;
	}
	if (read(fd, &c, 1) < 0) {
		xlog(L_WARNING, "Unable to read from %s: %m",
			NFSD_END_GRACE_FILE);
		close(fd);
		return 1;
	}
	close(fd);
	if (c == 'N') {
		xlog(L_WARNING, "nfsd is in grace but didn't send a gracestart upcall, "
			"please update the kernel");
		ret = sqlite_grace_start();
	}
	return ret;
}

#if UPCALL_VERSION >= 2
static ssize_t cld_message_size(void *msg)
{
	struct cld_msg_hdr *hdr = (struct cld_msg_hdr *)msg;

	switch (hdr->cm_vers) {
	case 1:
		return sizeof(struct cld_msg);
	case 2:
		return sizeof(struct cld_msg_v2);
	default:
		xlog(L_FATAL, "%s invalid upcall version %d", __func__,
		     hdr->cm_vers);
		exit(-EINVAL);
	}
}
#else
static ssize_t cld_message_size(void *UNUSED(msg))
{
	return sizeof(struct cld_msg);
}
#endif

static void
cld_not_implemented(struct cld_client *clnt)
{
	int ret;
	ssize_t bsize, wsize;
#if UPCALL_VERSION >= 2
	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
#else
	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
#endif

	xlog(D_GENERAL, "%s: downcalling with not implemented error", __func__);

	/* set up reply */
	cmsg->cm_status = -EOPNOTSUPP;

	bsize = cld_message_size(cmsg);
	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
	if (wsize != bsize)
		xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m",
			 __func__, wsize);

	/* reopen pipe, just to be sure */
	ret = cld_pipe_open(clnt);
	if (ret) {
		xlog(L_FATAL, "%s: unable to reopen pipe: %d", __func__, ret);
		exit(ret);
	}
}

static void
cld_get_version(struct cld_client *clnt)
{
	int ret;
	ssize_t bsize, wsize;
#if UPCALL_VERSION >= 2
	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
#else
	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
#endif

	xlog(D_GENERAL, "%s: version = %u.", __func__, UPCALL_VERSION);

	cmsg->cm_u.cm_version = UPCALL_VERSION;
	cmsg->cm_status = 0;

	bsize = cld_message_size(cmsg);
	xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status);
	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
	if (wsize != bsize) {
		xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m",
			 __func__, wsize);
		ret = cld_pipe_open(clnt);
		if (ret) {
			xlog(L_FATAL, "%s: unable to reopen pipe: %d",
					__func__, ret);
			exit(ret);
		}
	}
}

static void
cld_create(struct cld_client *clnt)
{
	int ret;
	ssize_t bsize, wsize;
#if UPCALL_VERSION >= 2
	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
#else
	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
#endif

	ret = cld_check_grace_period();
	if (ret)
		goto reply;

	xlog(D_GENERAL, "%s: create client record.", __func__);

#if UPCALL_VERSION >= 2
	if (cmsg->cm_vers >= 2)
		ret = sqlite_insert_client_and_princhash(
					cmsg->cm_u.cm_clntinfo.cc_name.cn_id,
					cmsg->cm_u.cm_clntinfo.cc_name.cn_len,
					cmsg->cm_u.cm_clntinfo.cc_princhash.cp_data,
					cmsg->cm_u.cm_clntinfo.cc_princhash.cp_len);
	else
		ret = sqlite_insert_client(cmsg->cm_u.cm_name.cn_id,
					   cmsg->cm_u.cm_name.cn_len);
#else
	ret = sqlite_insert_client(cmsg->cm_u.cm_name.cn_id,
				   cmsg->cm_u.cm_name.cn_len);
#endif

reply:
	cmsg->cm_status = ret ? -EREMOTEIO : ret;

	bsize = cld_message_size(cmsg);
	xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status);
	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
	if (wsize != bsize) {
		xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m",
			 __func__, wsize);
		ret = cld_pipe_open(clnt);
		if (ret) {
			xlog(L_FATAL, "%s: unable to reopen pipe: %d",
					__func__, ret);
			exit(ret);
		}
	}
}

static void
cld_remove(struct cld_client *clnt)
{
	int ret;
	ssize_t bsize, wsize;
#if UPCALL_VERSION >= 2
	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
#else
	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
#endif

	ret = cld_check_grace_period();
	if (ret)
		goto reply;

	xlog(D_GENERAL, "%s: remove client record.", __func__);

	ret = sqlite_remove_client(cmsg->cm_u.cm_name.cn_id,
				   cmsg->cm_u.cm_name.cn_len);

reply:
	cmsg->cm_status = ret ? -EREMOTEIO : ret;

	bsize = cld_message_size(cmsg);
	xlog(D_GENERAL, "%s: downcall with status %d", __func__,
			cmsg->cm_status);
	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
	if (wsize != bsize) {
		xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m",
			 __func__, wsize);
		ret = cld_pipe_open(clnt);
		if (ret) {
			xlog(L_FATAL, "%s: unable to reopen pipe: %d",
					__func__, ret);
			exit(ret);
		}
	}
}

static void
cld_check(struct cld_client *clnt)
{
	int ret;
	ssize_t bsize, wsize;
#if UPCALL_VERSION >= 2
	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
#else
	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
#endif

	/*
	 * If we get a check upcall at all, it means we're talking to an old
	 * kernel.  Furthermore, if we're not in grace it means this is the
	 * first client to do a reclaim.  Log a message and use
	 * sqlite_grace_start() to advance the epoch numbers.
	 */
	if (recovery_epoch == 0) {
		xlog(D_GENERAL, "%s: received a check upcall, please update the kernel",
			__func__);
		ret = sqlite_grace_start();
		if (ret)
			goto reply;
	}

	xlog(D_GENERAL, "%s: check client record", __func__);

	ret = sqlite_check_client(cmsg->cm_u.cm_name.cn_id,
				  cmsg->cm_u.cm_name.cn_len);

reply:
	/* set up reply */
	cmsg->cm_status = ret ? -EACCES : ret;

	bsize = cld_message_size(cmsg);
	xlog(D_GENERAL, "%s: downcall with status %d", __func__,
			cmsg->cm_status);
	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
	if (wsize != bsize) {
		xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m",
			 __func__, wsize);
		ret = cld_pipe_open(clnt);
		if (ret) {
			xlog(L_FATAL, "%s: unable to reopen pipe: %d",
					__func__, ret);
			exit(ret);
		}
	}
}

static void
cld_gracedone(struct cld_client *clnt)
{
	int ret;
	ssize_t bsize, wsize;
#if UPCALL_VERSION >= 2
	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
#else
	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
#endif

	/*
	 * If we got a "gracedone" upcall while we're not in grace, then
	 * 1) we must be talking to an old kernel
	 * 2) no clients attempted to reclaim
	 * In that case, log a message and use sqlite_grace_start() to
	 * advance the epoch numbers, and then proceed as normal.
	 */
	if (recovery_epoch == 0) {
		xlog(D_GENERAL, "%s: received gracedone upcall "
			"while not in grace, please update the kernel",
			__func__);
		ret = sqlite_grace_start();
		if (ret)
			goto reply;
	}

	xlog(D_GENERAL, "%s: grace done.", __func__);

	ret = sqlite_grace_done();

	if (first_time) {
		if (num_cltrack_records > 0)
			sqlite_delete_cltrack_records();
		if (num_legacy_records > 0)
			legacy_clear_recdir();
		sqlite_first_time_done();
		first_time = 0;
	}

reply:
	/* set up reply: downcall with 0 status */
	cmsg->cm_status = ret ? -EREMOTEIO : ret;

	bsize = cld_message_size(cmsg);
	xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status);
	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
	if (wsize != bsize) {
		xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m",
			 __func__, wsize);
		ret = cld_pipe_open(clnt);
		if (ret) {
			xlog(L_FATAL, "%s: unable to reopen pipe: %d",
					__func__, ret);
			exit(ret);
		}
	}
}

static int
gracestart_callback(struct cld_client *clnt) {
	ssize_t bsize, wsize;
#if UPCALL_VERSION >= 2
	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
#else
	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
#endif

	cmsg->cm_status = -EINPROGRESS;

	bsize = cld_message_size(cmsg);
	xlog(D_GENERAL, "Sending client %.*s",
			cmsg->cm_u.cm_name.cn_len, cmsg->cm_u.cm_name.cn_id);
	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
	if (wsize != bsize)
		return -EIO;
	return 0;
}

static void
cld_gracestart(struct cld_client *clnt)
{
	int ret;
	ssize_t bsize, wsize;
#if UPCALL_VERSION >= 2
	struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
#else
	struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
#endif

	xlog(D_GENERAL, "%s: updating grace epochs", __func__);

	ret = sqlite_grace_start();
	if (ret)
		goto reply;

	xlog(D_GENERAL, "%s: sending client records to the kernel", __func__);

	ret = sqlite_iterate_recovery(&gracestart_callback, clnt);

reply:
	/* set up reply: downcall with 0 status */
	cmsg->cm_status = ret ? -EREMOTEIO : ret;

	bsize = cld_message_size(cmsg);
	xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status);
	wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize);
	if (wsize != bsize) {
		xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m",
			 __func__, wsize);
		ret = cld_pipe_open(clnt);
		if (ret) {
			xlog(L_FATAL, "%s: unable to reopen pipe: %d",
					__func__, ret);
			exit(ret);
		}
	}
}

static int
cld_pipe_read_msg(struct cld_client *clnt)
{
	ssize_t len, left_len;
	ssize_t hdr_len = sizeof(struct cld_msg_hdr);
	struct cld_msg_hdr *hdr = (struct cld_msg_hdr *)&clnt->cl_u;

	len = atomicio(read, clnt->cl_fd, hdr, hdr_len);

	if (len <= 0) {
		xlog(L_ERROR, "%s: pipe read failed: %m", __func__);
		goto fail_read;
	}

	switch (hdr->cm_vers) {
	case 1:
		left_len = sizeof(struct cld_msg) - hdr_len;
		break;
	case 2:
		left_len = sizeof(struct cld_msg_v2) - hdr_len;
		break;
	default:
		xlog(L_ERROR, "%s: unsupported upcall version: %hu",
			__func__, hdr->cm_vers);
		goto fail_read;
	}

	len = atomicio(read, clnt->cl_fd, hdr + 1, left_len);

	if (len <= 0) {
		xlog(L_ERROR, "%s: pipe read failed: %m", __func__);
		goto fail_read;
	}

	return 0;

fail_read:
	cld_pipe_open(clnt);
	return -1;
}

static void
cldcb(int UNUSED(fd), short which, void *data)
{
	struct cld_client *clnt = data;
	struct cld_msg_hdr *hdr = (struct cld_msg_hdr *)&clnt->cl_u;

	if (which != EV_READ)
		goto out;

	if (cld_pipe_read_msg(clnt) < 0)
		goto out;

	switch (hdr->cm_cmd) {
	case Cld_Create:
		cld_create(clnt);
		break;
	case Cld_Remove:
		cld_remove(clnt);
		break;
	case Cld_Check:
		cld_check(clnt);
		break;
	case Cld_GraceDone:
		cld_gracedone(clnt);
		break;
	case Cld_GraceStart:
		cld_gracestart(clnt);
		break;
	case Cld_GetVersion:
		cld_get_version(clnt);
		break;
	default:
		xlog(L_WARNING, "%s: command %u is not yet implemented",
				__func__, hdr->cm_cmd);
		cld_not_implemented(clnt);
	}
out:
	event_add(clnt->cl_event, NULL);
}

int
main(int argc, char **argv)
{
	int arg;
	int rc = 0;
	bool foreground = false;
	char *progname;
	char *storagedir = CLD_DEFAULT_STORAGEDIR;
	struct cld_client clnt;
	char *s;
	first_time = 0;
	num_cltrack_records = 0;
	num_legacy_records = 0;

	memset(&clnt, 0, sizeof(clnt));

	progname = strdup(basename(argv[0]));
	if (!progname) {
		fprintf(stderr, "%s: unable to allocate memory.\n", argv[0]);
		return 1;
	}

	evbase = event_base_new();
	if (evbase == NULL) {
		fprintf(stderr, "%s: unable to allocate event base.\n", argv[0]);
		return 1;
	}
	xlog_syslog(0);
	xlog_stderr(1);

	conf_init_file(NFS_CONFFILE);
	s = conf_get_str("general", "pipefs-directory");
	if (s)
		strlcpy(pipefs_dir, s, sizeof(pipefs_dir));
	s = conf_get_str("nfsdcld", "storagedir");
	if (s)
		storagedir = s;
	rc = conf_get_num("nfsdcld", "debug", 0);
	if (rc > 0)
		xlog_config(D_ALL, 1);

	/* process command-line options */
	while ((arg = getopt_long(argc, argv, "hdFp:s:", longopts,
				  NULL)) != EOF) {
		switch (arg) {
		case 'd':
			xlog_config(D_ALL, 1);
			break;
		case 'F':
			foreground = true;
			break;
		case 'p':
			strlcpy(pipefs_dir, optarg, sizeof(pipefs_dir));
			break;
		case 's':
			storagedir = optarg;
			break;
		default:
			usage(progname);
			free(progname);
			return 0;
		}
	}

	strlcpy(pipepath, pipefs_dir, sizeof(pipepath));
	strlcat(pipepath, DEFAULT_CLD_PATH, sizeof(pipepath));

	xlog_open(progname);
	if (!foreground) {
		xlog_syslog(1);
		xlog_stderr(0);
		rc = daemon(0, 0);
		if (rc) {
			xlog(L_ERROR, "Unable to daemonize: %m");
			goto out;
		}
	}

	/* drop all capabilities */
	rc = cld_set_caps();
	if (rc)
		goto out;

	/*
	 * now see if the storagedir is writable by root w/o CAP_DAC_OVERRIDE.
	 * If it isn't then give the user a warning but proceed as if
	 * everything is OK. If the DB has already been created, then
	 * everything might still work. If it doesn't exist at all, then
	 * assume that the maindb init will be able to create it. Fail on
	 * anything else.
	 */
	if (access(storagedir, W_OK) == -1) {
		switch (errno) {
		case EACCES:
			xlog(L_WARNING, "Storage directory %s is not writable. "
					"Should be owned by root and writable "
					"by owner!", storagedir);
			break;
		case ENOENT:
			/* ignore and assume that we can create dir as root */
			break;
		default:
			xlog(L_ERROR, "Unexpected error when checking access "
				      "on %s: %m", storagedir);
			rc = -errno;
			goto out;
		}
	}

	if (linux_version_code() < MAKE_VERSION(4, 20, 0))
		old_kernel = true;

	/* set up storage db */
	rc = sqlite_prepare_dbh(storagedir);
	if (rc) {
		xlog(L_ERROR, "Failed to open main database: %d", rc);
		goto out;
	}

	/* set up event handler */
	rc = cld_pipe_init(&clnt);
	if (rc)
		goto out;

	signal(SIGINT, sig_die);
	signal(SIGTERM, sig_die);

	xlog(D_GENERAL, "%s: Starting event dispatch handler.", __func__);
	rc = event_base_dispatch(evbase);
	if (rc < 0)
		xlog(L_ERROR, "%s: event_dispatch failed: %m", __func__);

out:
	if (clnt.cl_event)
		event_free(clnt.cl_event);
	if (clnt.cl_fd != -1)
		close(clnt.cl_fd);
	if (pipedir_event)
		event_free(pipedir_event);
	if (inotify_fd != -1)
		close(inotify_fd);

	event_base_free(evbase);
	sqlite_shutdown();

	free(progname);
	return rc;
}
