/*
   vdev: a virtual device manager for *nix
   Copyright (C) 2015  Jude Nelson

   This program is dual-licensed: you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 3 or later as
   published by the Free Software Foundation. For the terms of this
   license, see LICENSE.GPLv3+ or <http://www.gnu.org/licenses/>.

   You are free to use this program under the terms of the GNU General
   Public License, 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.

   Alternatively, you are free to use this program under the terms of the
   Internet Software Consortium License, but WITHOUT ANY WARRANTY; without
   even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   For the terms of this license, see LICENSE.ISC or
   <http://www.isc.org/downloads/software-support-policy/isc-license/>.
*/

#ifdef _VDEV_OS_LINUX

#include "linux.h"
#include "workqueue.h"
#include "libvdev/sglib.h"
#include "libvdev/misc.h"
#include "libvdev/sbuf.h"
#include "libvdev/vdev_ctrl.h"
#include <signal.h>

#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
#include <sys/types.h>

/* Enumerated list of FDs to poll */
enum {
    FD_POLL_SIGNAL = 0,
    FD_POLL_NETLINK,
    FD_POLL_CONTROL,
    FD_POLL_MAX
};
static void vdev_linux_fill_signalfd_mask(sigset_t *set)
{
    sigemptyset(set);
    sigaddset(set, SIGTERM);
    sigaddset(set, SIGINT);
    sigaddset(set, SIGQUIT);
    sigaddset(set, SIGALRM);
    sigaddset(set, SIGHUP);
}

int vdev_linux_signals_block_for_signalfd(void)
{
    sigset_t set;

    vdev_linux_fill_signalfd_mask(&set);
    if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0)
        return -errno;
    return 0;
}

// connection to the linux kernel for hotplug
struct vdev_linux_context {
    // netlink address
    struct sockaddr_nl nl_addr;

    // poll on multiple file descriptors
    struct pollfd pfd[FD_POLL_MAX];

    // path to mounted sysfs
    char sysfs_mountpoint[PATH_MAX + 1];

    // ref to OS context
    struct vdev_os_context *os_ctx;

    /* eudev-compatible /run/udev/control */
    struct vdev_ctrl *ctrl;

    // initial device requests
    pthread_mutex_t initial_requests_lock;
    struct vdev_device_request *initial_requests;
    struct vdev_device_request *initial_requests_tail;
};

static char const *vdev_linux_config_basename(struct vdev_linux_context const *ctx);
static bool vdev_linux_is_long_running(struct vdev_linux_context const *ctx);
static int vdev_linux_promote_hotplug_profile(struct vdev_linux_context *ctx,
                                              char const *target_cfg_arg);
static int vdev_linux_ctrl_init(struct vdev_linux_context *ctx);
static int vdev_linux_ctrl_dispatch(struct vdev_linux_context *ctx);
static void vdev_linux_ctrl_shutdown(struct vdev_linux_context *ctx);
static int vdev_linux_parse_request(struct vdev_linux_context *ctx,
                                    struct vdev_device_request *vreq,
                                    char *nlbuf, ssize_t buflen);

#define BLOCK_SIZE 512

static int initialize_signals(void)
{
    int signal_fd = -1;
    sigset_t mask;

    /*
     * Signals in mask must be blocked (vdev_linux_signals_block_for_signalfd in
     * vdev_start) so delivery is via this fd only.
     */
    vdev_linux_fill_signalfd_mask(&mask);

    if ((signal_fd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK)) < 0) {

        vdev_error("Couldn't setup signal FD: '%s'\n", strerror(errno));
        return -1;
    }

    return signal_fd;
}

// parse a uevent action
static vdev_device_request_t vdev_linux_parse_device_request_type(char const *type)
{
    if (strcmp(type, "add") == 0)
        return VDEV_DEVICE_ADD;
    else if (strcmp(type, "remove") == 0)
        return VDEV_DEVICE_REMOVE;
    else if (strcmp(type, "change") == 0)
        return VDEV_DEVICE_CHANGE;
    else if (strcmp(type, "bind") == 0)
        return VDEV_DEVICE_BIND;
    else if (strcmp(type, "unbind") == 0)
        return VDEV_DEVICE_UNBIND;

    return VDEV_DEVICE_INVALID;
}

/* sysfs discovery/registration helpers live in a separate compilation unit-sized
 * include to keep linux.c focused on event-loop/control flow logic. */
#include "linux_sysfs.inc"

// print a uevent, either with debugging or error loglevels
static int vdev_linux_log_uevent(char const *uevent_buf,
                                 size_t uevent_buf_len, bool debug)
{
    for (unsigned int i = 0; i < uevent_buf_len;) {
        if (debug)
            vdev_debug("uevent '%s'\n", uevent_buf + i);
        else
            vdev_error("uevent '%s'\n", uevent_buf + i);

        i += strlen(uevent_buf + i) + 1;
    }
    return 0;
}

#define vdev_linux_debug_uevent(uevent_buf, uevent_buf_len) vdev_linux_log_uevent(uevent_buf, uevent_buf_len, true)
#define vdev_linux_error_uevent(uevent_buf, uevent_buf_len) vdev_linux_log_uevent(uevent_buf, uevent_buf_len, false)

// parse a uevent, and use the information to fill in a device request.
// nlbuf must be a contiguous concatenation of null-terminated KEY=VALUE strings.
// return 0 on success
static int vdev_linux_parse_request(struct vdev_linux_context *ctx,
                                    struct vdev_device_request *vreq,
                                    char *nlbuf, ssize_t buflen)
{
    char *buf = nlbuf;
    char *key = NULL;
    char *value = NULL;
    int offset = 0;
    int rc = 0;
    unsigned int major = 0;
    unsigned int minor = 0;
    bool have_major = false;
    bool have_minor = false;
    mode_t dev_mode = 0;
    int line_count = 0;
    /* if set to true, add as an OS-specific parameter to the vreq */
    bool not_param = false;

    char *devpath = NULL;      /* sysfs devpath */
    char *subsystem = NULL;    /* sysfs subsystem */
    char *devname = (char*)VDEV_DEVICE_PATH_UNKNOWN;  /* DEVNAME from uevent */
    size_t subsystem_len;

    vdev_device_request_t reqtype = VDEV_DEVICE_INVALID;

    vdev_debug("%p: uevent buffer\n", vreq);
    vdev_linux_debug_uevent(nlbuf, buflen);

    /* sanity check: if the first line is $action@$devpath,
     * then skip it (since the information
     * contained in the uevent will encode the very same bits of information)
     */
    if (strchr(buf, '@') != NULL)
        /* advance to the next line */
        offset += strlen(buf) + 1;

    /* get key/value pairs */
    while (offset < buflen) {
        line_count++;
        not_param = false;

        rc = vdev_keyvalue_next(buf + offset, &key, &value);

        if (rc < 0) {
            vdev_error("Invalid line %d (byte %d): '%s'\n",
                       line_count, offset, buf + offset);
            vdev_linux_error_uevent(nlbuf, buflen);
            return -EINVAL;
        }

        offset += rc + 1;   /* count the \0 at the end */
        rc = 0;

        // is this the action to take?
        if (strcmp(key, "ACTION") == 0) {
            reqtype = vdev_linux_parse_device_request_type(value);
            if (reqtype == VDEV_DEVICE_INVALID) {
                vdev_error("Invalid ACTION '%s'\n", value);
                vdev_linux_error_uevent(nlbuf, buflen);
                return -EINVAL;
            }
            vdev_device_request_set_type(vreq, reqtype);
            not_param = true;
        } else if (strcmp(key, "DEVPATH") == 0) {
            devpath = value;
        } else if (strcmp(key, "DEVNAME") == 0) {
            devname = value;
        /* subsystem given? */
        } else if (strcmp(key, "SUBSYSTEM") == 0) {
            subsystem = vdev_strdup_or_null(value);
        /* is this the major device number? */
        } else if (strcmp(key, "MAJOR") == 0 && !have_major) {
            char *tmp = NULL;
            major = (int)strtol(value, &tmp, 10);
            if (*tmp != '\0') {
                vdev_error("Invalid 'MAJOR' value '%s'\n", value);
                vdev_linux_error_uevent(nlbuf, buflen);
                return -EINVAL;
            }
            have_major = true;
            not_param = true;
        /* is this the minor device number? */
        } else if (strcmp(key, "MINOR") == 0 && !have_minor) {
            char *tmp = NULL;
            minor = (int)strtol(value, &tmp, 10) ;
            if (*tmp != '\0') {
                vdev_error("Invalid 'MINOR' value '%s'\n", value);
                vdev_linux_error_uevent(nlbuf, buflen);
                return -EINVAL;
            }
            have_minor = true;
            not_param = true;
        }

        if (!not_param) {
            // add to OS params
            rc = vdev_device_request_add_param(vreq, key, value);
            if (rc != 0) {
                // could be OOM
                if (subsystem)
                    free(subsystem);
                return rc;
            }
        }
    }

    if (reqtype == VDEV_DEVICE_INVALID) {
        vdev_error("%s", "No ACTION given\n");
        vdev_linux_error_uevent(nlbuf, buflen);
        if (subsystem)
            free(subsystem);
        return -EINVAL;
    }

    if (reqtype == VDEV_DEVICE_BIND)
        vdev_device_request_set_shall_bind(vreq, true);

    if ((!have_major && have_minor) || (have_major && !have_minor)) {
        vdev_error("Missing device information: major=%d, minor=%d\n",
                   have_major, have_minor);
        vdev_linux_error_uevent(nlbuf, buflen);
        if (subsystem)
            free(subsystem);
        return -EINVAL;
    }

    if (have_major && have_minor)
        /* explicit major and minor device numbers given */
        vdev_device_request_set_dev(vreq, makedev(major, minor));

    if (devname)
        /* use this as the device's path */
        vdev_device_request_set_path(vreq, devname);

    if (devpath) {
        /* get any remaining information from sysfs check major/minor? */
        if (!have_major || !have_minor) {
            // see if we have major/minor device numbers for this device...
            rc = vdev_linux_sysfs_read_dev_nums(ctx, devpath, &major, &minor);
            if (rc == 0) {
                // yup!
                vdev_device_request_set_dev(vreq, makedev(major, minor));
                have_major = true;
                have_minor = true;
            } else {
                // it's okay to not have dev numbers
                rc = 0;
            }
        }

        // subsystem?
        if (subsystem == NULL) {
            // see if we have a subsystem
            rc = vdev_sysfs_read_subsystem(ctx->sysfs_mountpoint,
                            devpath, &subsystem, &subsystem_len);
            if (rc == 0) {
                // yup!
                rc = vdev_device_request_add_param(vreq, "SUBSYSTEM", subsystem);
                if (rc != 0) {
                    // OOM
                    free(subsystem);
                    return rc;
                }
            } else if (rc != -ENOMEM) {
                // this is weird...
                vdev_warn("no subsystem found for '%s'\n", devpath);
                rc = 0;
            }
        }
    }

    if (have_major && have_minor) {
        if (subsystem && strcasecmp(subsystem, "block") == 0)
            /* this is a block */
            dev_mode = S_IFBLK;
        else
            /* this is a character device--we have major/minor numbers */
            dev_mode = S_IFCHR;

        vdev_device_request_set_mode(vreq, dev_mode);
    }

    vdev_debug("subsystem = '%s', have_major=%d, major = %u, have_minor=%d, minor = %u, mode = %o\n",
               subsystem, have_major, major, have_minor, minor, dev_mode);

    if (subsystem)
        free(subsystem);

    /* tell helpers where /sys is mounted; non-zero value means OOM */
    return vdev_device_request_add_param(vreq, "SYSFS_MOUNTPOINT",
                                         ctx->sysfs_mountpoint);
}

static int vdev_linux_ctrl_init(struct vdev_linux_context *ctx)
{
    const char *path = getenv("VDEV_UDEV_CONTROL");
    int rc;

    ctx->pfd[FD_POLL_CONTROL].fd = -1;
    ctx->pfd[FD_POLL_CONTROL].events = 0;

    if (geteuid() != 0)
        return 0;

    ctx->ctrl = vdev_ctrl_new(path && path[0] ? path : VDEV_CTRL_DEFAULT_PATH);
    if (!ctx->ctrl)
        return -ENOMEM;

    rc = vdev_ctrl_enable_receiving(ctx->ctrl);
    if (rc != 0) {
        vdev_warn("vdev_ctrl_enable_receiving: %d (control socket disabled)\n", rc);
        vdev_linux_ctrl_shutdown(ctx);
        return 0;
    }

    ctx->pfd[FD_POLL_CONTROL].fd = vdev_ctrl_get_fd(ctx->ctrl);
    ctx->pfd[FD_POLL_CONTROL].events = POLLIN;
    vdev_debug("control socket listening on %s\n",
               vdev_ctrl_get_path(ctx->ctrl));
    return 0;
}

static void vdev_linux_ctrl_shutdown(struct vdev_linux_context *ctx)
{
    if (!ctx)
        return;
    if (ctx->ctrl) {
        vdev_ctrl_cleanup(ctx->ctrl);
        vdev_ctrl_unref(ctx->ctrl);
        ctx->ctrl = NULL;
    }
    ctx->pfd[FD_POLL_CONTROL].fd = -1;
    ctx->pfd[FD_POLL_CONTROL].events = 0;
}

static int vdev_linux_ctrl_dispatch(struct vdev_linux_context *ctx)
{
    struct vdev_ctrl_connection *conn = NULL;
    struct vdev_ctrl_msg *msg = NULL;
    struct vdev_state *state = ctx->os_ctx->state;
    const char *env;
    char *key;
    int rc = 0;

    conn = vdev_ctrl_get_connection(ctx->ctrl);
    if (!conn)
        return 0;

    msg = vdev_ctrl_receive_msg(conn);
    if (!msg) {
        vdev_ctrl_connection_unref(conn);
        return 0;
    }

    switch (vdev_ctrl_msg_type(msg)) {
    case VDEV_CTRL_SET_LOG_LEVEL:
        vdev_set_debug_level(vdev_ctrl_msg_intval(msg));
        vdev_debug("control: SET_LOG_LEVEL %d\n", vdev_ctrl_msg_intval(msg));
        break;
    case VDEV_CTRL_STOP_EXEC_QUEUE:
        state->ctrl_stop_exec_queue = true;
        vdev_debug("%s", "control: STOP_EXEC_QUEUE\n");
        break;
    case VDEV_CTRL_START_EXEC_QUEUE:
        state->ctrl_stop_exec_queue = false;
        vdev_debug("%s", "control: START_EXEC_QUEUE\n");
        break;
    case VDEV_CTRL_RELOAD:
        /*
         * eudev --reload reloads rules in-place.  Do the same for actions:
         * never map RELOAD to SIGHUP/exit (that left OpenRC thinking vdev
         * was still started while vdevd was dead).
         */
        vdev_debug("%s", "control: RELOAD\n");
        {
            int rrc = vdev_actions_reload(state);

            if (rrc != 0)
                vdev_error("vdev_actions_reload rc = %d\n", rrc);
        }
        break;
    case VDEV_CTRL_PROMOTE:
        {
            const char *cfg = vdev_ctrl_msg_buf(msg);
            int prc;

            if (cfg != NULL && cfg[0] == '\0')
                cfg = NULL;
            vdev_debug("control: PROMOTE%s%s\n",
                       cfg ? " " : "", cfg ? cfg : "");
            prc = vdev_linux_promote_hotplug_profile(ctx, cfg);
            if (prc != 0)
                vdev_error("vdev_linux_promote_hotplug_profile rc = %d\n", prc);
        }
        break;
    case VDEV_CTRL_SET_ENV:
        env = vdev_ctrl_msg_buf(msg);
        if (env) {
            key = strdup(env);
            if (key) {
                char *val = strchr(key, '=');

                if (val) {
                    *val++ = '\0';
                    if (val[0] == '\0')
                        unsetenv(key);
                    else
                        setenv(key, val, 1);
                    vdev_debug("control: SET_ENV %s\n", env);
                } else {
                    vdev_error("control: bad SET_ENV '%s'\n", env);
                }
                free(key);
            }
        }
        break;
    case VDEV_CTRL_SET_CHILDREN_MAX: {
        int n = vdev_ctrl_msg_intval(msg);

        if (n >= 1) {
            state->ctrl_children_max = n;
            vdev_debug("control: SET_CHILDREN_MAX %d (vdev uses one worker)\n", n);
        }
        break;
    }
    case VDEV_CTRL_PING:
        /*
         * eudev ping means "daemon has reached the event loop and is not
         * still pre-processing".  For vdev, keep the control connection
         * open until the worker has drained queued *and* in-flight work,
         * then drop /run/udev/queue.  vdevadm settle uses that close as
         * "idle" — it must not wait on a leftover queue path (often a
         * directory under the /run/udev -> metadata symlink).
         */
        {
            int prc = vdev_wq_wait_idle(&state->device_wq, 60);

            if (prc != 0)
                vdev_warn("control: PING wait idle rc=%d\n", prc);
            vdev_ctrl_queue_update_busy(vdev_wq_is_busy(&state->device_wq));
        }
        vdev_debug("%s", "control: PING\n");
        break;
    case VDEV_CTRL_EXIT:
        vdev_debug("%s", "control: EXIT\n");
        state->ctrl_pending_signal = SIGTERM;
        rc = SIGTERM;
        break;
    default:
        vdev_warn("control: unknown message type %d\n",
                  (int)vdev_ctrl_msg_type(msg));
        break;
    }

    vdev_ctrl_msg_unref(msg);
    if (rc != SIGTERM)
        vdev_ctrl_connection_unref(conn);
    else
        /* hold until shutdown, like eudev EXIT */
        vdev_ctrl_connection_unref(conn);

    return rc;
}

static int vdev_linux_promote_hotplug_profile(struct vdev_linux_context *ctx,
                                              char const *target_cfg_arg)
{
    struct vdev_state *state = ctx->os_ctx->state;
    struct vdev_config next_cfg;
    char target_path[PATH_MAX + 1];
    char *dup_cfg_path = NULL;
    char *dup_acts_dir = NULL;
    char *dup_helpers_dir = NULL;
    char *dup_preseed = NULL;
    const char *cfg_dir = NULL;
    const char *target_cfg = NULL;
    int rc = 0;

    if (state == NULL || state->config == NULL)
        return -EINVAL;

    if (target_cfg_arg != NULL && target_cfg_arg[0] != '\0')
        target_cfg = target_cfg_arg;
    else {
        target_cfg = state->globals.long_running;
        if (target_cfg == NULL || target_cfg[0] == '\0')
            target_cfg = "vdevd.conf";
    }

    if (target_cfg[0] == '/') {
        rc = snprintf(target_path, sizeof(target_path), "%s", target_cfg);
    } else {
        cfg_dir = state->globals.config_dir;
        if (cfg_dir == NULL || cfg_dir[0] == '\0')
            cfg_dir = "/etc/vdev";
        rc = snprintf(target_path, sizeof(target_path), "%s/%s", cfg_dir, target_cfg);
    }
    if (rc < 0 || (size_t)rc >= sizeof(target_path))
        return -ENAMETOOLONG;

    memset(&next_cfg, 0, sizeof(next_cfg));
    rc = vdev_config_init(&next_cfg);
    if (rc != 0)
        return rc;

    rc = vdev_config_load(target_path, &next_cfg);
    if (rc != 0) {
        vdev_config_free(&next_cfg);
        vdev_error("promote: vdev_config_load('%s') rc=%d\n", target_path, rc);
        return rc;
    }

    /*
     * vdev_config_load() does not set config_path (only -c / load_from_args
     * does). Use the path we just loaded so promote does not falsely fail
     * with -ENOMEM and leave the process stuck on the early action set.
     */
    dup_cfg_path = vdev_strdup_or_null(target_path);
    dup_acts_dir = vdev_strdup_or_null(next_cfg.acts_dir);
    dup_helpers_dir = vdev_strdup_or_null(next_cfg.helpers_dir);
    dup_preseed = vdev_strdup_or_null(next_cfg.preseed_path);
    if (dup_cfg_path == NULL || dup_acts_dir == NULL || dup_helpers_dir == NULL ||
        (next_cfg.preseed_path != NULL && dup_preseed == NULL)) {
        vdev_error("promote: missing fields from '%s' "
                   "(config_path=%p acts_dir=%p helpers_dir=%p preseed=%p)\n",
                   target_path, (void *)dup_cfg_path, (void *)dup_acts_dir,
                   (void *)dup_helpers_dir, (void *)dup_preseed);
        rc = -ENOMEM;
        goto _out;
    }

    rc = vdev_reload_lock(state);
    if (rc != 0) {
        rc = -rc;
        goto _out;
    }

    free(state->config->config_path);
    state->config->config_path = dup_cfg_path;
    dup_cfg_path = NULL;

    free(state->config->acts_dir);
    state->config->acts_dir = dup_acts_dir;
    dup_acts_dir = NULL;

    free(state->config->helpers_dir);
    state->config->helpers_dir = dup_helpers_dir;
    dup_helpers_dir = NULL;

    free(state->config->preseed_path);
    state->config->preseed_path = dup_preseed;
    dup_preseed = NULL;

    state->config->default_mode = next_cfg.default_mode;
    state->config->default_policy = next_cfg.default_policy;
    state->config->debug_level = next_cfg.debug_level;
    state->config->error_level = next_cfg.error_level;
    state->config->coldplug_only = false;
    state->coldplug_only = false;

    vdev_reload_unlock(state);

    rc = vdev_actions_reload(state);
    if (rc != 0)
        goto _out;

    if (ctx->pfd[FD_POLL_CONTROL].fd < 0) {
        rc = vdev_linux_ctrl_init(ctx);
        if (rc != 0) {
            vdev_error("promote: vdev_linux_ctrl_init rc=%d\n", rc);
            goto _out;
        }
    }

    vdev_info("Promoted hotplug profile to '%s' (pid unchanged)\n", target_path);

_out:
    if (dup_cfg_path)
        free(dup_cfg_path);
    if (dup_acts_dir)
        free(dup_acts_dir);
    if (dup_helpers_dir)
        free(dup_helpers_dir);
    if (dup_preseed)
        free(dup_preseed);
    vdev_config_free(&next_cfg);
    return rc;
}


#include "linux_events.inc"

// start listening for kernel events via netlink
// only do so if we're *NOT* going to run once (check the config)
// return 0 on success
// return -errno on error (failure to socket(2), setsockopt(2), bind(2), or walk sysfs)
static int vdev_linux_context_init(struct vdev_os_context *os_ctx, struct vdev_linux_context *ctx)
{
    int rc = 0;
    size_t slen = VDEV_LINUX_NETLINK_RECV_BUF_MAX;
    int so_passcred_enable = 1;

    memset(ctx, 0, sizeof(struct vdev_linux_context));

    ctx->os_ctx = os_ctx;
    for (int i = 0; i < FD_POLL_MAX; i++)
        ctx->pfd[i].fd = -1;

    if ((ctx->pfd[FD_POLL_SIGNAL].fd = initialize_signals()) < 0) {
        rc = -errno;
        vdev_error("Couldn't initialize signals: '%s'\n", strerror(errno));
        goto _error;
    }

    ctx->pfd[FD_POLL_SIGNAL].events = POLLIN;

    /* Netlink only when not coldplug-only (see state->coldplug_only — reload-safe). */
    if (!ctx->os_ctx->state->coldplug_only) {
        ctx->nl_addr.nl_family = AF_NETLINK;
        ctx->nl_addr.nl_pid = getpid();
        ctx->nl_addr.nl_groups = NETLINK_KOBJECT_UEVENT;

        ctx->pfd[FD_POLL_NETLINK].fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
        if (ctx->pfd[FD_POLL_NETLINK].fd < 0) {
            rc = -errno;
            vdev_error("socket(PF_NETLINK) rc = %d\n", rc);
            goto _error;
        }

        ctx->pfd[FD_POLL_NETLINK].events = POLLIN;

        // big receive buffer, if running as root
        if (geteuid() == 0) {
            rc = setsockopt(ctx->pfd[FD_POLL_NETLINK].fd,
                            SOL_SOCKET, SO_RCVBUFFORCE, &slen, sizeof(slen));
            if (rc < 0) {
                rc = -errno;
                vdev_error("setsockopt(SO_RCVBUFFORCE) rc = %d\n", rc);
                goto _error;
            }
        }

        // check credentials of message--only root should be able talk to us
        rc = setsockopt(ctx->pfd[FD_POLL_NETLINK].fd, SOL_SOCKET, SO_PASSCRED,
                        &so_passcred_enable, sizeof(so_passcred_enable));
        if (rc < 0) {
            rc = -errno;
            vdev_error("setsockopt(SO_PASSCRED) rc = %d\n", rc);
            goto _error;
        }

        // bind to the address
        rc = bind(ctx->pfd[FD_POLL_NETLINK].fd,
                  (struct sockaddr*)&ctx->nl_addr, sizeof(struct sockaddr_nl));
        if (rc != 0) {
            rc = -errno;
            vdev_error("bind(%d) rc = %s\n",
                       ctx->pfd[FD_POLL_NETLINK].fd, ctx->os_ctx->state->config->config_path);
            vdev_error("bind(%d) rc = %d\n",
                       ctx->pfd[FD_POLL_NETLINK].fd, rc);
            goto _error;
        }
    } else {

        ctx->pfd[FD_POLL_NETLINK].fd = -1;
        ctx->pfd[FD_POLL_NETLINK].events = 0;
    }

    // lookup sysfs mountpoint
    rc = vdev_linux_find_sysfs_mountpoint(ctx->sysfs_mountpoint, PATH_MAX);
    if (rc != 0) {
        vdev_error("vdev_linux_find_sysfs_mountpoint rc = %d\n", rc);
        goto _error;
    }

    pthread_mutex_init(&ctx->initial_requests_lock, NULL);

    /*
     * Hotplug listeners own /run/udev/control (early/initrd/long-running).
     * Early binds at start so vdevadm settle can drain the workqueue after
     * subsystem triggers and before control --promote. Coldplug-only must not.
     */
    if (!ctx->os_ctx->state->coldplug_only) {
        rc = vdev_linux_ctrl_init(ctx);
        if (rc != 0) {
            vdev_error("vdev_linux_ctrl_init rc = %d\n", rc);
            goto _error;
        }
    } else {
        ctx->pfd[FD_POLL_CONTROL].fd = -1;
        ctx->pfd[FD_POLL_CONTROL].events = 0;
    }

    if (ctx->os_ctx->state->coldplug_only) {
        /* Coldplug-only: seed from sysfs, then drain and exit (no netlink). */
        rc = vdev_linux_sysfs_register_devices(ctx);
        if (rc != 0) {
            vdev_error("vdev_linux_sysfs_walk_devs rc = %d\n", rc);
            goto _error;
        }
    } else {
        /* Hotplug listener: no sysfs walk; seed via vdevadm trigger. */
        vdev_os_context_signal_coldplug_finished(ctx->os_ctx);
    }

    return 0;
    
_error:

    vdev_linux_ctrl_shutdown(ctx);
    vdev_linux_stop_netlink(ctx);

    if (ctx->pfd[FD_POLL_SIGNAL].fd >= 0) {
        close(ctx->pfd[FD_POLL_SIGNAL].fd);
        ctx->pfd[FD_POLL_SIGNAL].fd = -1;
    }

    return rc;
}


// stop listening
static int vdev_linux_context_shutdown(struct vdev_linux_context *ctx)
{
    if (ctx) {

        vdev_linux_ctrl_shutdown(ctx);

        for (int i = 0; i < FD_POLL_MAX; i++) {
            if (ctx->pfd[i].fd >= 0) {
                close(ctx->pfd[i].fd);
                ctx->pfd[i].fd = -1;
            }
        }
        
        if (ctx->initial_requests != NULL) {
            struct vdev_device_request *itr = ctx->initial_requests;
            struct vdev_device_request *next = NULL;
            while (itr) {
                next = itr->next;
                vdev_device_request_free(itr);
                free(itr);
                itr = next;
            }
            ctx->initial_requests = NULL;
            ctx->initial_requests_tail = NULL;
        }
    }
    return 0;
}


#include "linux_bootstrap.inc"


#endif
