F_NONNULL static char* gdnsd_realdir(const char* dpath, const char* desc, const bool create, mode_t def_mode) { struct stat st; int stat_rv = stat(dpath, &st); if(stat_rv) { // if we can't create and doesn't exist, let the error fall through to whoever uses it... if(!create) return strdup(dpath); if(mkdir(dpath, def_mode)) log_fatal("mkdir of %s directory '%s' failed: %s", desc, dpath, dmn_logf_strerror(errno)); log_info("Created %s directory %s", desc, dpath); } else if(!S_ISDIR(st.st_mode)) { log_fatal("%s directory '%s' is not a directory (but should be)!", desc, dpath); } char* out = realpath(dpath, NULL); if(!out) log_fatal("Validation of %s directory '%s' failed: %s", desc, dpath, dmn_logf_strerror(errno)); if(strcmp(dpath, out)) log_info("%s directory '%s' cleaned up as '%s'", desc, dpath, out); return out; }
F_NONNULL static void do_repl(gdmaps_t* gdmaps) { dmn_assert(gdmaps); char linebuf[256]; char map_name[128]; char ip_addr[128]; const bool have_tty = isatty(fileno(stdin)) && isatty(fileno(stdout)); while(1) { if(have_tty) { fputs("> ", stdout); fflush(stdout); } if(!fgets(linebuf, 255, stdin)) { if(!feof(stdin)) log_err("fgets(stdin) failed: %s", dmn_logf_strerror(ferror(stdin))); if(have_tty) fputs("\n", stdout); return; } if(2 != sscanf(linebuf, "%127[^ \t\n] %127[^ \t\n]\n", map_name, ip_addr)) { log_err("Invalid input. Please enter a map name followed by an IP address"); continue; } do_lookup(gdmaps, map_name, ip_addr); } }
void gdnsd_prcu_setup_lock(void) { int pthread_err; pthread_rwlockattr_t lockatt; if((pthread_err = pthread_rwlockattr_init(&lockatt))) log_fatal("pthread_rwlockattr_init() failed: %s", dmn_logf_strerror(pthread_err)); // Non-portable way to boost writer priority. Our writelocks are held very briefly // and very rarely, whereas the readlocks could be very spammy, and we don't want to // block the write operation forever. This works on Linux+glibc. # ifdef PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP if((pthread_err = pthread_rwlockattr_setkind_np(&lockatt, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))) log_fatal("pthread_rwlockattr_setkind_np(PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP) failed: %s", dmn_logf_strerror(pthread_err)); # endif if((pthread_err = pthread_rwlock_init(&gdnsd_prcu_rwlock, &lockatt))) log_fatal("pthread_rwlock_init() failed: %s", dmn_logf_strerror(pthread_err)); if((pthread_err = pthread_rwlockattr_destroy(&lockatt))) log_fatal("pthread_rwlockattr_destroy() failed: %s", dmn_logf_strerror(pthread_err)); }
bool emc_read_nbytes(const int fd, const unsigned len, uint8_t* out) { bool rv = false; unsigned seen = 0; while(seen < len) { int readrv = read(fd, out + seen, len - seen); if(readrv < 1) { if(!readrv) { log_debug("plugin_extmon: emc_read_nbytes() failed: pipe closed"); rv = true; break; } else if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { log_debug("plugin_extmon: emc_read_nbytes() failed: %s", dmn_logf_strerror(errno)); rv = true; break; } } else { seen += readrv; } } return rv; }
bool emc_write_string(const int fd, const char* str, const unsigned len) { bool rv = false; unsigned written = 0; while(written < len) { int writerv = write(fd, str + written, len - written); if(writerv < 1) { if(!writerv) { log_debug("plugin_extmon: emc_write_string(%s) failed: pipe closed", str); rv = true; break; } else if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { log_debug("plugin_extmon: emc_write_string(%s) failed: %s", str, dmn_logf_strerror(errno)); rv = true; break; } } else { written += writerv; } } return rv; }
void gdnsd_prcu_destroy_lock(void) { int pthread_err; if((pthread_err = pthread_rwlock_destroy(&gdnsd_prcu_rwlock))) log_fatal("pthread_rwlock_destroy() failed: %s", dmn_logf_strerror(pthread_err)); }
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); } }
static void mon_interval_cb(struct ev_loop* loop, ev_timer* w, int revents V_UNUSED) { dmn_assert(loop); dmn_assert(w); dmn_assert(revents == EV_TIMER); mon_t* this_mon = w->data; dmn_assert(!this_mon->result_pending); if (this_mon->cmd->max_proc > 0 && num_proc >= this_mon->cmd->max_proc) { // If more than max_proc processes are running, reschedule excess // checks to run 0.1 seconds later. After a few passes, this will // smooth the schedule out to prevent a thundering herd. ev_timer_stop(loop, this_mon->interval_timer); ev_timer_set(this_mon->interval_timer, 0.1, this_mon->cmd->interval); ev_timer_start(loop, this_mon->interval_timer); return; } // Before forking, block all signals and save the old mask // to avoid a race condition where local sighandlers execute // in the child between fork and exec(). sigset_t all_sigs; sigfillset(&all_sigs); sigset_t saved_mask; sigemptyset(&saved_mask); if(pthread_sigmask(SIG_SETMASK, &all_sigs, &saved_mask)) log_fatal("pthread_sigmask() failed"); this_mon->cmd_pid = fork(); if(this_mon->cmd_pid == -1) log_fatal("fork() failed: %s", dmn_logf_strerror(errno)); if(!this_mon->cmd_pid) { // child // reset all signal handlers to default before unblocking struct sigaction defaultme; sigemptyset(&defaultme.sa_mask); defaultme.sa_handler = SIG_DFL; defaultme.sa_flags = 0; // we really don't care about error retvals here for(int i = 0; i < NSIG; i++) (void)sigaction(i, &defaultme, NULL); // unblock all sigset_t no_sigs; sigemptyset(&no_sigs); if(pthread_sigmask(SIG_SETMASK, &no_sigs, NULL)) log_fatal("pthread_sigmask() failed"); // technically, we could go ahead and close off stdout/stderr // here for the "startfg" case, but why bother? If the user // is debugging via startfg they might want to see this crap anyways. execv(this_mon->cmd->args[0], this_mon->cmd->args); log_fatal("execv(%s, ...) failed: %s", this_mon->cmd->args[0], dmn_logf_strerror(errno)); } num_proc++; // restore previous signal mask from before fork in parent if(pthread_sigmask(SIG_SETMASK, &saved_mask, NULL)) log_fatal("pthread_sigmask() failed"); this_mon->result_pending = true; ev_timer_set(this_mon->cmd_timeout, this_mon->cmd->timeout, 0); ev_timer_start(loop, this_mon->cmd_timeout); ev_child_set(this_mon->child_watcher, this_mon->cmd_pid, 0); ev_child_start(loop, this_mon->child_watcher); }
F_NONNULL static void mon_connect_cb(struct ev_loop* loop, struct ev_io* io, const int revents V_UNUSED) { dmn_assert(loop); dmn_assert(io); dmn_assert(revents == EV_WRITE); tcp_events_t* md = io->data; dmn_assert(md); dmn_assert(md->tcp_state == TCP_STATE_CONNECTING); dmn_assert(ev_is_active(md->connect_watcher)); dmn_assert(ev_is_active(md->timeout_watcher) || ev_is_pending(md->timeout_watcher)); dmn_assert(md->sock > -1); // nonblocking connect() just finished, need to check status bool success = false; int sock = md->sock; int so_error = 0; unsigned so_error_len = sizeof(so_error); (void)getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_error, &so_error_len); if(unlikely(so_error)) { switch(so_error) { case EPIPE: case ECONNREFUSED: case ETIMEDOUT: case EHOSTUNREACH: case EHOSTDOWN: case ENETUNREACH: log_debug("plugin_tcp_connect: State poll of %s failed quickly: %s", md->desc, dmn_logf_strerror(so_error)); break; default: log_err("plugin_tcp_connect: Failed to connect() monitoring socket to remote server, possible local problem: %s", dmn_logf_strerror(so_error)); } } else { success = true; } shutdown(sock, SHUT_RDWR); close(sock); md->sock = -1; ev_io_stop(loop, md->connect_watcher); ev_timer_stop(loop, md->timeout_watcher); md->tcp_state = TCP_STATE_WAITING; gdnsd_mon_state_updater(md->idx, success); }