| /* |
| * 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 void |
| cldcb(int UNUSED(fd), short which, void *data) |
| { |
| ssize_t len; |
| struct cld_client *clnt = data; |
| #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 (which != EV_READ) |
| goto out; |
| |
| len = atomicio(read, clnt->cl_fd, cmsg, sizeof(*cmsg)); |
| if (len <= 0) { |
| xlog(L_ERROR, "%s: pipe read failed: %m", __func__); |
| cld_pipe_open(clnt); |
| goto out; |
| } |
| |
| if (cmsg->cm_vers > UPCALL_VERSION) { |
| xlog(L_ERROR, "%s: unsupported upcall version: %hu", |
| __func__, cmsg->cm_vers); |
| cld_pipe_open(clnt); |
| goto out; |
| } |
| |
| switch(cmsg->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__, cmsg->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; |
| } |