bool emc_write_command(const int fd, const extmon_cmd_t* cmd) { unsigned alloc = 256; unsigned len = 0; char* buf = xmalloc(alloc); // 4 byte prefix "CMD:" memcpy(buf, "CMD:", 4); len += 4; // 2-byte index, 1-byte timeout, 1-byte interval buf[len++] = cmd->idx >> 8; buf[len++] = cmd->idx & 0xFF; buf[len++] = cmd->timeout; buf[len++] = cmd->interval; // skip 2-byte len for rest of packet at offset 8 len += 2; // arg count + NUL-terminated arguments buf[len++] = cmd->num_args; for(unsigned i = 0; i < cmd->num_args; i++) { const unsigned arg_len = strlen(cmd->args[i]) + 1; while((len + arg_len + 16) > alloc) { alloc *= 2; buf = xrealloc(buf, alloc); } memcpy(&buf[len], cmd->args[i], arg_len); len += arg_len; } // NUL-terminated description string const unsigned desc_len = strlen(cmd->desc) + 1; while((len + desc_len + 16) > alloc) { alloc *= 2; buf = xrealloc(buf, alloc); } memcpy(&buf[len], cmd->desc, desc_len); len += desc_len; // now go back and fill in the overall len // of the variable area for desc/args. const unsigned var_len = len - 10; buf[8] = var_len >> 8; buf[9] = var_len & 0xFF; bool rv = emc_write_string(fd, buf, len); free(buf); return rv; }
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); } }
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); }