Esempio n. 1
0
bool emc_read_exact(const int fd, const char* str) {
    const unsigned len = strlen(str);
    uint8_t buf[len];
    return (emc_read_nbytes(fd, len, buf)
        || !!memcmp(str, buf, len));
}
Esempio n. 2
0
int main(int argc, char** argv) {
    // Bail out early if we don't have the right argument
    //   count, and try to tell the user not to run us
    //   if stderr happens to be hooked up to a terminal
    if(argc != 5) {
        fprintf(stderr, "This binary is not for human execution!\n");
        exit(99);
    }

    bool debug = false;
    if(!strcmp(argv[1], "Y"))
        debug = true;

    bool use_syslog = false;
    if(!strcmp(argv[2], "S"))
        use_syslog = true;

    dmn_init1(debug, true, use_syslog, "gdnsd_extmon_helper");
    // Note that gdnsd_initialize() would be standard here, but extmon_helper
    // is special: it doesn't actually make use of most of libgdnsd, just the
    // very basic compiler, allocator, and libdmn logging bits.

    // regardless, we seal off stdin now.  We don't need it,
    //   and this way we don't have to deal with it when
    //   execv()-ing child commands later.
    if(!freopen("/dev/null", "r", stdin))
        dmn_log_fatal("Cannot open /dev/null: %s", dmn_logf_strerror(errno));

    // Also unconditionally unset NOTIFY_SOCKET here so that children
    //   don't get any ideas about talking to systemd on our behalf.
    //   (we're done using it in this process for libdmn stuff at this point)
    unsetenv("NOTIFY_SOCKET");

    // these are the main communication pipes to the daemon/plugin
    plugin_read_fd = atoi(argv[3]);
    plugin_write_fd = atoi(argv[4]);

    if(plugin_read_fd < 3 || plugin_read_fd > 1000
        || plugin_write_fd < 3 || plugin_write_fd > 1000)
        log_fatal("Invalid pipe descriptors!");

    if(emc_read_exact(plugin_read_fd, "HELO"))
        log_fatal("Failed to read HELO from plugin");
    if(emc_write_string(plugin_write_fd, "HELO_ACK", 8))
        log_fatal("Failed to write HELO_ACK to plugin");

    uint8_t ccount_buf[7];
    if(emc_read_nbytes(plugin_read_fd, 7, ccount_buf)
        || strncmp((char*)ccount_buf, "CMDS:", 5))
        log_fatal("Failed to read command count from plugin");
    num_mons = ((unsigned)ccount_buf[5] << 8) + ccount_buf[6];
    if(!num_mons)
        log_fatal("Received command count of zero from plugin");
    mons = xcalloc(num_mons, sizeof(mon_t));

    if(emc_write_string(plugin_write_fd, "CMDS_ACK", 8))
        log_fatal("Failed to write CMDS_ACK to plugin");

    // Note, it's merely a happy coincidence that our mons[]
    //   indices exactly match cmd->idx numbers.  Always use
    //   the cmd->idx numbers as the official index when talking
    //   to the main daemon!
    for(unsigned i = 0; i < num_mons; i++) {
        mons[i].cmd = emc_read_command(plugin_read_fd);
        if(!mons[i].cmd)
            log_fatal("Failed to read command %u from plugin", i);
        if(i != mons[i].cmd->idx)
            log_fatal("BUG: plugin index issues, %u vs %u", i, mons[i].cmd->idx);
        if(emc_write_string(plugin_write_fd, "CMD_ACK", 7))
            log_fatal("Failed to write CMD_ACK for command %u to plugin", i);
    }

    if(emc_read_exact(plugin_read_fd, "END_CMDS"))
        log_fatal("Failed to read END_CMDS from plugin");
    if(emc_write_string(plugin_write_fd, "END_CMDS_ACK", 12))
        log_fatal("Failed to write END_CMDS_ACK to plugin");

    // done with the serial setup, close the readpipe and go nonblocking on write for eventloop...
    close(plugin_read_fd);
    if(fcntl(plugin_write_fd, F_SETFL, (fcntl(plugin_write_fd, F_GETFL, 0)) | O_NONBLOCK) == -1)
        log_fatal("Failed to set O_NONBLOCK on pipe: %s", dmn_logf_errno());

    // CLOEXEC the write fd so child scripts can't mess with it
    if(fcntl(plugin_write_fd, F_SETFD, FD_CLOEXEC))
        log_fatal("Failed to set FD_CLOEXEC on plugin write fd: %s", dmn_logf_strerror(errno));

    // init results-sending queue
    sendq_init();

    // Set up libev error callback
    ev_set_syserr_cb(&syserr_for_ev);

    // Construct the default loop for the main thread
    struct ev_loop* def_loop = ev_default_loop(EVFLAG_AUTO);
    if(!def_loop) log_fatal("Could not initialize the default libev loop");

    // Catch SIGINT/TERM/HUP, and do not let them prevent loop exit
    sigterm_watcher = xmalloc(sizeof(ev_signal));
    sigint_watcher = xmalloc(sizeof(ev_signal));
    sighup_watcher = xmalloc(sizeof(ev_signal));
    ev_signal_init(sigterm_watcher, sig_cb, SIGTERM);
    ev_signal_init(sigint_watcher, sig_cb, SIGINT);
    ev_signal_init(sighup_watcher, sig_cb, SIGHUP);
    ev_signal_start(def_loop, sigterm_watcher);
    ev_signal_start(def_loop, sigint_watcher);
    ev_signal_start(def_loop, sighup_watcher);
    ev_unref(def_loop);
    ev_unref(def_loop);
    ev_unref(def_loop);

    // set up primary read/write watchers on the pipe to the daemon's plugin
    plugin_write_watcher = xmalloc(sizeof(ev_io));
    ev_io_init(plugin_write_watcher, plugin_write_cb, plugin_write_fd, EV_WRITE);
    ev_set_priority(plugin_write_watcher, 1);

    // set up interval watchers for each monitor, initially for immediate firing
    //   for the daemon's monitoring init cycle, then repeating every interval.
    for(unsigned i = 0; i < num_mons; i++) {
        mon_t* this_mon = &mons[i];
        this_mon->interval_timer = xmalloc(sizeof(ev_timer));
        ev_timer_init(this_mon->interval_timer, mon_interval_cb, 0., this_mon->cmd->interval);
        this_mon->interval_timer->data = this_mon;
        ev_set_priority(this_mon->interval_timer, 0);
        ev_timer_start(def_loop, this_mon->interval_timer);

        // initialize the other watchers in the mon_t here as well,
        //   but do not start them (the interval callback starts them each interval)
        this_mon->cmd_timeout = xmalloc(sizeof(ev_timer));
        ev_timer_init(this_mon->cmd_timeout, mon_timeout_cb, 0, 0);
        ev_set_priority(this_mon->cmd_timeout, -1);
        this_mon->cmd_timeout->data = this_mon;

        this_mon->child_watcher = xmalloc(sizeof(ev_child));
        ev_child_init(this_mon->child_watcher, mon_child_cb, 0, 0);
        this_mon->child_watcher->data = this_mon;
    }

    log_info("gdnsd_extmon_helper running");
    ev_run(def_loop, 0);

    // graceful shutdown should have cleared out children, but
    // the hard kill/wait below is for (a) ungraceful shutdown
    // on unexpected pipe close and (b) anything else going wrong
    // during graceful shutdown.
    bool needs_wait = false;
    for(unsigned i = 0; i < num_mons; i++) {
        if(mons[i].cmd_pid) {
            log_debug("not-so-graceful shutdown: sending SIGKILL to %li", (long)mons[i].cmd_pid);
            kill(mons[i].cmd_pid, SIGKILL);
            needs_wait = true;
        }
    }

    if(needs_wait) {
        unsigned i = 500; // 5s for OS to give us all the SIGKILL'd zombies
        while(i--) {
            pid_t wprv = waitpid(-1, NULL, WNOHANG);
            if(wprv < 0) {
                if(errno == ECHILD)
                    break;
                else
                    log_fatal("waitpid(-1, NULL, WNOHANG) failed: %s", dmn_logf_errno());
            }
            if(wprv)
                log_debug("not-so-graceful shutdown: waitpid reaped %li", (long)wprv);
            const struct timespec ms_10 = { 0, 10000000 };
            nanosleep(&ms_10, NULL);
        }
    }

    // Bye!
    if(killed_by) {
        log_info("gdnsd_extmon_helper exiting gracefully due to signal %i", killed_by);
#ifdef COVERTEST_EXIT
        exit(0);
#else
        raise(killed_by);
#endif
    }
    else {
        log_info("gdnsd_extmon_helper exiting un-gracefully");
        exit(42);
    }
}
Esempio n. 3
0
extmon_cmd_t* emc_read_command(const int fd) {

    extmon_cmd_t* cmd = NULL;

    {
        uint8_t fixed_part[10];
        if(emc_read_nbytes(fd, 10, fixed_part)
            || strncmp((char*)fixed_part, "CMD:", 4)) {
            log_debug("emc_read_command() failed to read CMD: prefix");
            goto out_error;
        }

        cmd = xmalloc(sizeof(extmon_cmd_t));
        cmd->idx = ((unsigned)fixed_part[4] << 8) + fixed_part[5];
        cmd->timeout = fixed_part[6];
        cmd->interval = fixed_part[7];
        cmd->args = NULL;
        cmd->num_args = 0;

        // note we add an extra NULL at the end of args here, for execl()
        const unsigned var_len = ((unsigned)fixed_part[8] << 8) + fixed_part[9];
        if(var_len < 4) {
            // 4 bytes would be enough for num_args, a single 1-byte argument
            //   and its NUL termiantor, and a zero-length NUL-terminated desc
            log_debug("emc_read_command() variable section too short (%u)!", var_len);
            goto out_error;
        }

        uint8_t var_part[var_len];
        if(emc_read_nbytes(fd, var_len, var_part)) {
            log_debug("emc_read_command() failed to read %u-byte variable section", var_len);
            goto out_error;
        }

        const unsigned n_args = *var_part;
        if(!n_args) {
            log_debug("emc_read_command() got zero-arg command!");
            goto out_error;
        }

        cmd->args = xmalloc((n_args + 1) * sizeof(char*));
        const uint8_t* current = &var_part[1];
        unsigned len_remain = var_len - 1;
        for(cmd->num_args = 0; cmd->num_args < n_args; cmd->num_args++) {
            const unsigned cmdlen = strnlen((const char*)current, len_remain) + 1;
            cmd->args[cmd->num_args] = xmalloc(cmdlen);
            if(!nul_within_n_bytes(current, len_remain)) {
                log_debug("emc_read_command(): argument runs off end of buffer");
                goto out_error;
            }
            memcpy((char*)cmd->args[cmd->num_args], current, cmdlen);
            current += cmdlen;
            len_remain -= cmdlen;
        }
        cmd->args[cmd->num_args] = NULL;

        if(!nul_within_n_bytes(current, len_remain)) {
            log_debug("emc_read_command(): argument runs off end of buffer");
            goto out_error;
        }
        cmd->desc = strdup((const char*)current);
        current += strlen((const char*)current);
        current++;

        if(current != (var_part + var_len)) {
            log_debug("emc_read_command(): unused len at end of buffer!");
            goto out_error;
        }
    }

    return cmd;

    out_error:
    if(cmd) {
        if(cmd->args) {
            for(unsigned x = 0; x < cmd->num_args; x++)
                free((char*)cmd->args[x]);
            free(cmd->args);
        }
        free(cmd);
    }
    return NULL;
}
Esempio n. 4
0
int main(int argc, char** argv) {
    dmn_init_log("gdnsd_extmon_helper", true);

    // start up syslog IFF it appears the daemon
    //   was *not* started via "startfg".  Regular
    //   start/restart would have /dev/null'd the
    //   standard descriptors before forking us off.
    if(!isatty(0))
        dmn_start_syslog();

    // Bail out early if we don't have the right argument
    //   count, and try to tell the user not to run us
    //   if stderr happens to be hooked up to a terminal
    if(argc != 5) {
        fprintf(stderr, "This binary is not for human execution!\n");
        abort();
    }

    // open stderr logging connection using passed fd
    dmn_log_set_alt_stderr(atoi(argv[2]));

    // regardless, we seal off stdin now.  We don't need it,
    //   and this way we don't have to deal with it when
    //   execv()-ing child commands later.
    if(!freopen("/dev/null", "r", stdin))
        dmn_log_fatal("Cannot open /dev/null: %s", dmn_strerror(errno));

    if(!strcmp(argv[1], "Y"))
        dmn_set_debug(true);
    else if(!strcmp(argv[1], "N"))
        dmn_set_debug(false);
    else
        log_fatal("Invalid debug argument on cmdline: '%s'!", argv[1]);

    // these are the main communication pipes to the daemon/plugin
    plugin_read_fd = atoi(argv[3]);
    plugin_write_fd = atoi(argv[4]);

    if(plugin_read_fd < 3 || plugin_read_fd > 1000
        || plugin_write_fd < 3 || plugin_write_fd > 1000)
        log_fatal("Invalid pipe descriptors!");

    // CLOEXEC the direct lines to the main plugin/daemon,
    //   so that child scripts can't screw with them.
    if(fcntl(plugin_read_fd, F_SETFD, FD_CLOEXEC))
        log_fatal("Failed to set FD_CLOEXEC on plugin read fd: %s", dmn_strerror(errno));
    if(fcntl(plugin_write_fd, F_SETFD, FD_CLOEXEC))
        log_fatal("Failed to set FD_CLOEXEC on plugin write fd: %s", dmn_strerror(errno));

    if(emc_read_exact(plugin_read_fd, "HELO"))
        log_fatal("Failed to read HELO from plugin");
    if(emc_write_string(plugin_write_fd, "HELO_ACK", 8))
        log_fatal("Failed to write HELO_ACK to plugin");

    uint8_t ccount_buf[7];
    if(emc_read_nbytes(plugin_read_fd, 7, ccount_buf)
        || strncmp((char*)ccount_buf, "CMDS:", 5))
        log_fatal("Failed to read command count from plugin");
    num_mons = ((unsigned)ccount_buf[5] << 8) + ccount_buf[6];
    if(!num_mons)
        log_fatal("Received command count of zero from plugin");
    mons = calloc(num_mons, sizeof(mon_t));

    if(emc_write_string(plugin_write_fd, "CMDS_ACK", 8))
        log_fatal("Failed to write CMDS_ACK to plugin");

    // Note, it's merely a happy coincidence that our mons[]
    //   indices exactly match cmd->idx numbers.  Always use
    //   the cmd->idx numbers as the official index when talking
    //   to the main daemon!
    for(unsigned i = 0; i < num_mons; i++) {
        mons[i].cmd = emc_read_command(plugin_read_fd);
        if(!mons[i].cmd)
            log_fatal("Failed to read command %u from plugin", i);
        if(i != mons[i].cmd->idx)
            log_fatal("BUG: plugin index issues, %u vs %u", i, mons[i].cmd->idx);
        if(emc_write_string(plugin_write_fd, "CMD_ACK", 7))
            log_fatal("Failed to write CMD_ACK for command %u to plugin", i);
    }

    if(emc_read_exact(plugin_read_fd, "END_CMDS"))
        log_fatal("Failed to read END_CMDS from plugin");
    if(emc_write_string(plugin_write_fd, "END_CMDS_ACK", 12))
        log_fatal("Failed to write END_CMDS_ACK to plugin");

    // done with the serial setup, close the readpipe and go nonblocking on write for eventloop...
    close(plugin_read_fd);
    if(unlikely(fcntl(plugin_write_fd, F_SETFL, (fcntl(plugin_write_fd, F_GETFL, 0)) | O_NONBLOCK) == -1))
        log_fatal("Failed to set O_NONBLOCK on pipe: %s", logf_errno());

    // init results-sending queue
    sendq_init();

    // Set up libev error callback
    ev_set_syserr_cb(&syserr_for_ev);

    // Construct the default loop for the main thread
    struct ev_loop* def_loop = ev_default_loop(EVFLAG_AUTO);
    if(!def_loop) log_fatal("Could not initialize the default libev loop");
    ev_set_timeout_collect_interval(def_loop, 0.1);
    ev_set_io_collect_interval(def_loop, 0.01);

    // set up primary read/write watchers on the pipe to the daemon's plugin
    plugin_read_watcher = malloc(sizeof(ev_io));
    plugin_write_watcher = malloc(sizeof(ev_io));
    ev_io_init(plugin_write_watcher, plugin_write_cb, plugin_write_fd, EV_WRITE);
    ev_set_priority(plugin_write_watcher, 1);

    // set up interval watchers for each monitor, initially for immediate firing
    //   for the daemon's monitoring init cycle, then repeating every interval.
    for(unsigned i = 0; i < num_mons; i++) {
        mon_t* this_mon = &mons[i];
        this_mon->interval_timer = malloc(sizeof(ev_timer));
        ev_timer_init(this_mon->interval_timer, mon_interval_cb, 0., this_mon->cmd->interval);
        this_mon->interval_timer->data = this_mon;
        ev_set_priority(this_mon->interval_timer, 0);
        ev_timer_start(def_loop, this_mon->interval_timer);

        // initialize the other watchers in the mon_t here as well,
        //   but do not start them (the interval callback starts them each interval)
        this_mon->cmd_timeout = malloc(sizeof(ev_timer));
        ev_timer_init(this_mon->cmd_timeout, mon_timeout_cb, 0, 0);
        ev_set_priority(this_mon->cmd_timeout, -1);
        this_mon->cmd_timeout->data = this_mon;

        this_mon->child_watcher = malloc(sizeof(ev_child));
        ev_child_init(this_mon->child_watcher, mon_child_cb, 0, 0);
        this_mon->child_watcher->data = this_mon;
    }

    log_info("gdnsd_extmon_helper running");

    // shut off stderr output from here out...
    dmn_log_close_alt_stderr();

    ev_run(def_loop, 0);
    log_info("gdnsd_extmon_helper terminating");

    // kill -9 on any extant child procs
    for(unsigned i = 0; i < num_mons; i++)
        if(mons[i].cmd_pid)
            kill(mons[i].cmd_pid, SIGKILL);

    // Bye!
    exit(0);
}