/*- * Copyright (c) 1999-2004 Andrey Simonenko * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "config.h" #ifndef lint static const char rcsid[] ATTR_UNUSED = "@(#)$Id: ipa_main.c,v 1.2.2.5 2007/05/11 16:29:59 simon Exp $"; #endif /* !lint */ #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_PTHREAD # include #endif #include "ipa_mod.h" #include "dlapi.h" #include "confcommon.h" #include "memfunc.h" #include "ipa_ac.h" #include "ipa_db.h" #include "ipa_log.h" #include "ipa_time.h" #include "ipa_cmd.h" #include "ipa_conf.h" #include "ipa_ctl.h" #include "ipa_main.h" #include "ipa_rules.h" #include "ipa_autorules.h" #ifndef WCOREDUMP # define WCOREDUMP(x) (0) #endif int debug = 0; /* 1, if -d. */ uid_t myuid; /* Current process UID. */ gid_t mygid; /* Current process GID. */ const uint64_t uint64_zero = UINT64_C(0); const uint64_t uint64_max = UINT64_MAX; ipa_mem_type *m_anon; /* Anonymous memory allocations. */ ipa_mem_type *m_tmp; /* Temporary memory allocations. */ sigset_t sigmask; /* Signal mask. */ sigset_t zeromask; /* Zero mask for unblocking signals. */ fd_set rset_main; /* Main read descriptors set. */ int maxfd; /* Maximum #descriptor + 1 in rset_main. */ u_int main_check_sec; /* Time in seconds since midnight when to wakeup. */ u_int real_main_check_sec; /* The same as main_check_sec, but without wakeup_time adjustment. */ static time_t newdaytime; /* Time of a new day. */ static time_t curtime; /* Current time. */ int stdout_read; /* Read-end of pipe for catching stdout. */ int stderr_read; /* Read-end of pipe for catching stderr. */ int dump_flag = 0; /* 1, if ctl command "dump". */ int freeze_flag = 0; /* 1, if ctl command "freeze". */ /* * All sig_*() functions write one character to sig_pipe, since its * write-end is nonblockable, they can't block in write(). According * to SUSv3 a signal handler can assign value only to a variable of * type volatile sig_atomic_t. */ static int sig_pipe[2]; /* Pipe for IPC from async signals handlers. */ volatile sig_atomic_t need_check_flag = 0; /* 1, if need to check all active rules. */ /* Different flags for signal handlers. */ static volatile sig_atomic_t signal_flag = 0; /* 1, if there was any signal. */ static volatile sig_atomic_t reconf_flag = 0; /* 1, if SIGHUP occurred. */ static volatile sig_atomic_t shutdown_flag = 0; /* 1, if SIGTERM or SIGINT occurred. */ static volatile sig_atomic_t sigchld_flag = 0; /* 1, if SIGCHLD occurred. */ static u_int stat_generation; /* ac_get_rule_stat() generation counter. */ #ifdef WITH_PTHREAD int Sigprocmask(int how, const sigset_t *set, sigset_t *oset) { int error; if ( (error = pthread_sigmask(how, set, oset)) != 0) { errno = error; return -1; } return 0; } #endif /* * Make descriptor nonblockable. */ int set_nonblock(int fd) { int val; if ( (val = fcntl(fd, F_GETFL, 0)) < 0) { logmsg(IPA_LOG_ERR, "set_nonblock: fcntl(%d, F_GETFL)", fd); return -1; } if (fcntl(fd, F_SETFL, val | O_NONBLOCK) < 0) { logmsg(IPA_LOG_ERR, "set_nonblock: fcntl(%d, F_SETFL)", fd); return -1; } return 0; } static int init_sig_pipe(void) { static int called_before = 0; if (called_before) { /* Close previously opened pipe. */ if (close(sig_pipe[0]) < 0 || close(sig_pipe[1]) < 0) { logmsg(IPA_LOG_ERR, "init_sig_pipe: close"); return -1; } } else called_before = 1; /* Create new pipe for IPC from signal handlers. */ if (pipe(sig_pipe) < 0) { logmsg(IPA_LOG_ERR, "init_sig_pipe: pipe"); return -1; } /* Set write- and read-ends as nonblockable. */ if (set_nonblock(sig_pipe[0]) < 0 || set_nonblock(sig_pipe[1]) < 0) { logmsgx(IPA_LOG_ERR, "init_sig_pipe: set_nonblock failed"); return -1; } return 0; } /* * Init everything. */ int init_all(void) { /* Init some of time related variables. */ if (time(&curtime) == (time_t)-1) { logmsg(IPA_LOG_ERR, "init_all: time failed"); return -1; } if (localtime_r(&curtime, (struct tm *)&curdate) == NULL) { logmsg(IPA_LOG_ERR, "init_all: localtime_r failed"); return -1; } curdate.tm_year += 1900; curdate.tm_mon++; curwday = curdate.tm_wday; main_check_sec = cursec = TIME_TO_SEC(&curdate); /* Flush mask of read-ready descriptors. */ FD_ZERO(&rset_main); maxfd = 0; /* Init signal IPC pipe, add read-end to rset_main. */ if (init_sig_pipe() < 0) { logmsgx(IPA_LOG_ERR, "init_all: init_sig_pipe failed"); return -1; } FD_SET(sig_pipe[0], &rset_main); if (maxfd < sig_pipe[0]) maxfd = sig_pipe[0]; /* Set pipes for stdout and stderr om rset_main. */ FD_SET(stdout_read, &rset_main); if (maxfd < stdout_read) maxfd = stdout_read; if (!debug) { FD_SET(stderr_read, &rset_main); if (maxfd < stderr_read) maxfd = stderr_read; } /* Init ctl. */ if (ctl_enable) { if (init_ctl() < 0) { logmsgx(IPA_LOG_ERR, "init_all: init_ctl failed"); return -1; } FD_SET(ctl_listenfd, &rset_main); if (ctl_listenfd > maxfd) maxfd = ctl_listenfd; } ++maxfd; /* Pre-init accounting modules. */ if (pre_init_ac_mods() < 0) { logmsgx(IPA_LOG_ERR, "init_all: pre_init_ac_mods failed"); return -1; } /* Pre-init database modules. */ if (pre_init_db_mods() < 0) { logmsgx(IPA_LOG_ERR, "init_all: pre_init_db_mods failed"); return -1; } #ifdef WITH_AUTORULES /* Init autorules. */ if (init_autorules() < 0) { logmsgx(IPA_LOG_ERR, "init_all: init_autorules failed"); return -1; } #endif #ifdef WITH_RULES /* Init active rules queue. */ init_rules_active(); /* Init static rules, limits and thresholds. */ if (init_rules(1) < 0) { logmsgx(IPA_LOG_ERR, "init_all: init_rules(1) failed"); return -1; } #endif /* Init accounting modules. */ if (init_ac_mods() < 0) { logmsgx(IPA_LOG_ERR, "init_all: init_ac_mods failed"); return -1; } /* Init database modules. */ if (init_db_mods() < 0) { logmsgx(IPA_LOG_ERR, "init_all: init_db_mods failed"); return -1; } return 0; } /* * Deinit everything, ignoring errors. Do the same steps as in * init_all(), but in reverse order. */ int deinit_all(void) { int error = 0; /* First deinit rules. */ if (deinit_rules() < 0) { logmsgx(IPA_LOG_ERR, "deinit_all: deinit_rules failed"); error = -1; } #ifdef WITH_AUTORULES /* Deinit autorules. */ if (deinit_autorules() < 0) { logmsgx(IPA_LOG_ERR, "deinit_all: deinit_autorules failed"); error = -1; } #endif /* Deinit accounting modules. */ if (deinit_ac_mods() < 0) { logmsgx(IPA_LOG_ERR, "deinit_all: deinit_ac_mods failed"); error = -1; } /* Deinit database modules. */ if (deinit_db_mods() < 0) { logmsgx(IPA_LOG_ERR, "deinit_all: deinit_db_mods failed"); error = -1; } /* Deinit ctl. */ if (deinit_ctl() < 0) { logmsgx(IPA_LOG_ERR, "deinit_all: deinit_ctl failed"); error = -1; } return error; } /* * Some of children exited. */ static int check_child(void) { int status; pid_t pid; #ifdef WITH_ANY_LIMITS struct wpid *wpid; struct wpid_hash *wpidh; const struct rule *rule; #endif /** SUSv3 claims that pid_t is signed integer, so can use `>' here. */ while ( (pid = waitpid((pid_t)0, &status, WNOHANG)) > 0) { #ifdef WITH_ANY_LIMITS /* * Find appropriate wpid structure for exited child and * remove it from the hash table (zeroing its pid field). * Sometimes wpid structure does not exist for child's PID, * since we already use it for another child for the same * section or reconfiguration occurred. */ wpidh = &wpid_hash[get_wpid_bucket(pid)]; LIST_FOREACH(wpid, wpidh, hlink) if (wpid->pid == pid) { rem_wpid_from_hash(wpid); switch (wpid->type) { # ifdef WITH_LIMITS case WPID_TYPE_LIMIT: { const struct limit *limit; limit = wpid->u.limit; rule = limit->rule; if (rule->debug_exec) logmsgx(IPA_LOG_INFO, "rule %s, limit %s: check_child: child %ld, which run commands, exited", rule->rule_name, limit->limit_name, (long)pid); } break; # endif # ifdef WITH_SUBLIMITS case WPID_TYPE_SUBLIMIT: { const struct sublimit *sublimit; sublimit = wpid->u.sublimit; rule = sublimit->limit->rule; if (rule->debug_exec) logmsgx(IPA_LOG_INFO, "rule %s, limit %s, sublimit %s: check_child: child %ld, which run commands, exited", rule->rule_name, sublimit->limit->limit_name, sublimit->sublimit_name, (long)pid); } break; # endif # ifdef WITH_THRESHOLDS case WPID_TYPE_THRESHOLD: { const struct threshold *threshold; threshold = wpid->u.threshold; rule = threshold->rule; if (rule->debug_exec) logmsgx(IPA_LOG_INFO, "rule %s, threshold %s: check_child: child %ld, which run commands, exited", rule->rule_name, threshold->threshold_name, (long)pid); } break; # endif } goto found; } #endif /* WITH_ANY_LIMITS */ if (global_debug_exec) logmsgx(IPA_LOG_INFO, "check_child: child %ld, which run commands, exited", (long)pid); #ifdef WITH_ANY_LIMITS found: #endif /* * The child, which run commands, in normal situation * should return 0. */ if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { logmsgx(IPA_LOG_ERR, "check_child: exit status from child %ld, which run commands, is %d", (long)pid, WEXITSTATUS(status)); return -1; } } else { if (WIFSIGNALED(status)) logmsgx(IPA_LOG_ERR, "check_child: abnormal termination of child %ld, which run commands, signal %d%s", (long)pid, WTERMSIG(status), WCOREDUMP(status) ? " (core file generated)" : ""); else logmsgx(IPA_LOG_ERR, "check_child: termination of child %ld, which run commands, unknown status", (long)pid); return -1; } } if (pid == (pid_t)-1 && errno != ECHILD) { logmsg(IPA_LOG_WARNING, "check_child: waitpid"); return -1; } return 0; } /* * This routine is called when a new day came (newday_flag != 0), * or when there is a reconfiguration, or when there are some * problems with system time or local time (newday_flag == 0). * The first_call flag is used only for debugging. */ static int newday(int first_call) { ipa_tm ipa_tm_save; struct tm tm; struct rule *rule; struct tevent *tevent; main_check_sec = SECONDS_IN_WEEK; /* Hint. */ /* Get time of a new day. */ tm = curdate; tm.tm_sec = tm.tm_min = tm.tm_hour = 0; tm.tm_mday++; /* Can be out of range. */ tm.tm_mon--; tm.tm_year -= 1900; tm.tm_isdst = -1; /* mktime(3) will fix all incorrect ranges. */ if ( (newdaytime = mktime(&tm)) == (time_t)-1) { /* This should not happen for 00:00:00. */ logmsg(IPA_LOG_ERR, "newday: mktime failed"); return -1; } if (newday_flag) { /* * We simulate that it is 00:00:00 now, but this can be * incorrect, nevertheless in most cases it should be almost * correct, anyway this is reflected in the database only, * everywhere cursec is used to compare some event's time * with current time. */ ipa_tm_save = curdate; curdate.tm_sec = curdate.tm_min = curdate.tm_hour = 0; } /* Init worktimes for the new day. */ worktimes_newday(!first_call); /* Init all tevents. */ SLIST_FOREACH(tevent, &tevents_list, link) { tevent->event_sec = 0; if (tevent->event_sec <= cursec) while ( (tevent->event_sec += tevent->event_step) <= cursec) if (debug_time > 1) logmsgx(IPA_LOG_INFO, "newday: skipping time event %s for time interval %s", sec_to_buf(tevent->event_sec), time_to_buf(tevent->event_step)); } TAILQ_FOREACH(rule, &rules_list, list) { rule->check_sec = newday_flag ? rule->update_tevent->event_sec : cursec; #ifdef WITH_LIMITS if (limits_newday(rule) < 0) { logmsgx(IPA_LOG_ERR, "newday: limits_newday failed"); return -1; } #endif #ifdef WITH_THRESHOLDS if (thresholds_newday(rule) < 0) { logmsgx(IPA_LOG_ERR, "newday: thresholds_newday failed"); return -1; } #endif if (IS_INACTIVE(rule->worktime)) { if (IS_ACTIVE(rule)) if (set_rule_inactive(rule) < 0) { logmsgx(IPA_LOG_ERR, "newday: set_rule_inactive failed"); return -1; } } else { if (IS_INACTIVE(rule)) { if (set_rule_active(rule) < 0) { logmsgx(IPA_LOG_ERR, "newday: set_rule_active failed"); return -1; } } else { if (db_append_rule(rule, &uint64_zero, 1) < 0) { logmsgx(IPA_LOG_ERR, "rule %s: newday: db_append_rule failed", rule->rule_name); return -1; } } if (rule->check_sec > rule->worktime->inactive_sec) rule->check_sec = rule->worktime->inactive_sec; if (rule->check_sec > rule->append_sec) rule->check_sec = rule->append_sec; } if (main_check_sec > rule->check_sec) main_check_sec = rule->check_sec; } /* Sort inactive rules. */ sort_inactive_rules(); if (main_check_sec > rules_inactive_check_sec) main_check_sec = rules_inactive_check_sec; #ifdef WITH_AUTORULES if (autorules_newday() < 0) { logmsgx(IPA_LOG_ERR, "newday: autorules_newday failed"); return -1; } if (main_check_sec > autorules_active_check_sec) main_check_sec = autorules_active_check_sec; if (main_check_sec > autorules_inactive_check_sec) main_check_sec = autorules_inactive_check_sec; #endif real_main_check_sec = main_check_sec; if (newday_flag) /* Restore real localtime. */ curdate = ipa_tm_save; return 0; } /* * Do accounting for all rules, limits, sublimits and thresholds, * check limits and thresholds events here, calculate new check_sec. */ static int do_ac(void) { int addition; uint64_t chunk; struct rule *rule, *rule_next; struct tevent *tevent; const struct ac_elem *ac; #ifdef WITH_ANY_LIMITS u_int tmpsec; #endif main_check_sec = SECONDS_IN_WEEK; /* Hint. */ /* Schedule next tevents. */ SLIST_FOREACH(tevent, &tevents_list, link) if (tevent->event_sec <= cursec) while ( (tevent->event_sec += tevent->event_step) <= cursec) if (debug_time > 1) logmsgx(IPA_LOG_INFO, "do_ac: skipping time event %s for time interval %s", sec_to_buf(tevent->event_sec), time_to_buf(tevent->event_step)); /* Check worktimes events. */ if (worktimes_check_sec <= cursec) worktimes_check(); /* Check inactive rules. */ if (rules_inactive_check_sec <= cursec) if (check_inactive_rules() < 0) { logmsgx(IPA_LOG_ERR, "do_ac: check_inactive_rules failed"); return -1; } #ifdef WITH_AUTORULES /* Check inactive autorules. */ if (autorules_inactive_check_sec <= cursec) if (check_inactive_autorules() < 0) { logmsgx(IPA_LOG_ERR, "do_ac: check_inactive_autorules failed"); return -1; } #endif for (rule = TAILQ_FIRST(&rules_active); rule != NULL; rule = rule_next) { rule_next = TAILQ_NEXT(rule, queue); if (debug_time) logmsgx(IPA_LOG_INFO, "do_ac: rule %s: check_sec %s (%u)", rule->rule_name, sec_to_buf(rule->check_sec), rule->check_sec); if (rule->check_sec <= cursec) { STAILQ_FOREACH(ac, rule->ac_list, link) { if (ac->ipa_ac_mod->ac_get_rule_stat(stat_generation, rule->newstat, rule->ruleno, &addition, &chunk) < 0) { logmsgx(IPA_LOG_ERR, "module %s: ac_get_rule_stat(%u, %s) failed", ac->mod_file, rule->newstat, rule->rule_name); return -1; } if (rule->newstat) { /* If this is a new stat, then ignore chunk from the module. */ if (chunk > UINT64_C(0)) logmsgx(IPA_LOG_ERR, "module %s: ac_get_rule_stat(%s, 1) returned non-zero chunk %"PRIu64, ac->mod_file, rule->rule_name, chunk); chunk = UINT64_C(0); } else { if (addition) { if (add_chunk_to_rule(rule, &chunk) < 0) { logmsgx(IPA_LOG_ERR, "rule %s: do_ac: add_chunk_to_rule failed", rule->rule_name); return -1; } } else { if (sub_chunk_from_rule(rule, &chunk) < 0) { logmsgx(IPA_LOG_ERR, "rule %s: do_ac: sub_chunk_from_rule failed", rule->rule_name); return -1; } } } } rule->newstat = 0; if (db_update_rule(rule, &rule->cnt) < 0) { logmsgx(IPA_LOG_ERR, "rule %s: do_ac: db_update_rule failed", rule->rule_name); return -1; } rule->check_sec = rule->update_tevent->event_sec; #ifdef WITH_LIMITS if (!STAILQ_EMPTY(&rule->limits)) { if (check_limits_events(rule, &tmpsec) < 0) { logmsgx(IPA_LOG_ERR, "rule %s: do_ac: check_limits_events failed", rule->rule_name); return -1; } if (rule->check_sec > tmpsec) rule->check_sec = tmpsec; } #endif #ifdef WITH_THRESHOLDS if (!STAILQ_EMPTY(&rule->thresholds)) { if (check_thresholds_events(rule, &tmpsec) < 0) { logmsgx(IPA_LOG_ERR, "rule %s: do_ac: check_thresholds_events failed", rule->rule_name); return -1; } if (rule->check_sec > tmpsec) rule->check_sec = tmpsec; } #endif if (rule->append_sec <= cursec && !newday_flag) { /* Append a new record, but do not append it at the end of the day. */ if (db_append_rule(rule, &uint64_zero, 1) < 0) { logmsgx(IPA_LOG_ERR, "rule %s: do_ac: db_append_rule failed", rule->rule_name); return -1; } } if (IS_INACTIVE(rule->worktime)) { /* Rule became inactive. */ if (!newday_flag) if (set_rule_inactive(rule) < 0) { logmsgx(IPA_LOG_ERR, "do_ac: set_rule_inactive failed"); return -1; } } else { /* Rule is still active. */ if (rule->check_sec > rule->worktime->inactive_sec) rule->check_sec = rule->worktime->inactive_sec; if (rule->check_sec > rule->append_sec) rule->check_sec = rule->append_sec; if (main_check_sec > rule->check_sec) main_check_sec = rule->check_sec; if (debug_time) logmsgx(IPA_LOG_INFO, "do_ac: rule %s: next check_sec %s (%u)", rule->rule_name, sec_to_buf(rule->check_sec), rule->check_sec); } } else if (main_check_sec > rule->check_sec) main_check_sec = rule->check_sec; } if (main_check_sec > rules_inactive_check_sec) main_check_sec = rules_inactive_check_sec; #ifdef WITH_AUTORULES if (autorules_active_check_sec <= cursec) if (check_active_autorules() < 0) return -1; if (main_check_sec > autorules_active_check_sec) main_check_sec = autorules_active_check_sec; if (main_check_sec > autorules_inactive_check_sec) main_check_sec = autorules_inactive_check_sec; #endif if (main_check_sec == SECONDS_IN_WEEK) /* No scheduled event, then wakeup at the end of day. */ main_check_sec = SECONDS_IN_DAY; /* * Don't sleep too much, since time changes can be lost * during sleep. */ real_main_check_sec = main_check_sec; if (main_check_sec > cursec + wakeup_time) main_check_sec = cursec + wakeup_time; return 0; } /* * The main function for accounting, it is called from the main() * and do all of the work. Return codes are following: * 0 -- normal exit, without error; * 1 -- need to reconfigure; * -1 -- some error occurred. */ int ipa_main(void) { char ch; /* Dummy variable for freeing signals IPC pipe. */ int sleepsec; /* How much to sleep. */ int nready; /* # of ready descriptors. */ int time_problem; /* 1, if something is wrong with time or date. */ int errno_save; /* For backuping errno value. */ time_t t; /* Used in time(). */ fd_set rset; /* Is set by value of rset_main. */ u_int cursec_new; /* New value for cursec. */ ipa_tm curdate_new; /* New value for curdate. */ double tdiff; /* Used in difftime(). */ struct timeval tv; /* Used in select() for timeout. */ logmsgx(IPA_LOG_INFO, "start accounting..."); if (newday(1) < 0) { logmsgx(IPA_LOG_ERR, "ipa_main: newday(1) failed"); return -1; } time_problem = 0; for (stat_generation = 1;; stat_generation += 2) { /* Get new current time. */ if (time(&t) == (time_t)-1) { logmsg(IPA_LOG_ERR, "ipa_main: time failed"); return -1; } if (localtime_r(&t, (struct tm *)&curdate_new) == NULL) { logmsg(IPA_LOG_ERR, "ipa_main: localtime_r failed"); return -1; } curdate_new.tm_year += 1900; curdate_new.tm_mon++; cursec_new = TIME_TO_SEC(&curdate_new); /* Check if new time is less than old time. */ tdiff = difftime(t, curtime); if (tdiff < 0.0) { logmsgx(IPA_LOG_WARNING, "system time goes back: delta %s (check before ac_get_stat)", tdiff_to_buf(tdiff)); time_problem = 1; } /* * Check if time changes too quickly, note that we * don't sleep more than 1 day. */ if (tdiff > SECONDS_IN_DAY) { logmsgx(IPA_LOG_WARNING, "time changes too quickly: delta %s is greater than 1 day (check before ac_get_stat)", tdiff_to_buf(tdiff)); time_problem = 1; } #ifdef HAVE_TM_GMTOFF /* Check if offset from UTC was changed. */ if (curdate_new.tm_gmtoff != curdate.tm_gmtoff) { logmsgx(IPA_LOG_WARNING, "offset from UTC changed from %ld to %ld seconds (check before ac_get_stat)", (long)curdate.tm_gmtoff, (long)curdate_new.tm_gmtoff); time_problem = 1; } #endif /* Check if Daylight Saving flag was changed. */ if (curdate_new.tm_isdst != curdate.tm_isdst) { logmsgx(IPA_LOG_WARNING, "Daylight Saving flag changed from %d to %d (check before ac_get_stat)", curdate.tm_isdst, curdate_new.tm_isdst); time_problem = 1; } /* Call ac_get_stat() for all accounting modules. */ if (ac_get_stat() < 0) { logmsgx(IPA_LOG_ERR, "ipa_main: ac_get_stat failed"); return -1; } /* Have time related problems? */ if (time_problem) { logmsgx(IPA_LOG_WARNING, "some time related problems occurred"); logmsgx(IPA_LOG_WARNING, "saving current statistics with old timestamps..."); time_problem = 0; /* * Force updating of all statistics with old * values of time related variables. */ set_rules_for_check(); if (do_ac() < 0) { logmsgx(IPA_LOG_ERR, "ipa_main: do_ac for old timestamps failed"); return -1; } /* Set current values for time related variables. */ curtime = t; cursec = cursec_new; curdate = curdate_new; curwday = curdate.tm_wday; /* Reinit limits and thresholds. */ if (init_rules(0) < 0) { logmsgx(IPA_LOG_ERR, "ipa_main: init_rules(0) failed"); return -1; } logmsgx(IPA_LOG_WARNING, "appending new records for all rules..."); if (newday(0) < 0) { logmsgx(IPA_LOG_ERR, "ipa_main: newday(0) failed"); return -1; } /* Poll in select(). */ main_check_sec = cursec; } else { /* No problems with time. */ tdiff = difftime(t, newdaytime); if (tdiff >= 0.0) { /* * It seems that a new day came, can check * only if tm_mday was changed, since above * we checked that change of time is not more * than one day. */ if (curdate_new.tm_mday != curdate.tm_mday) { /* A new day came. */ if (tdiff > sensitive_time) logmsgx(IPA_LOG_WARNING, "new day came, but woke too late: delta %s is greater than \"sensitive_time\" %u seconds", tdiff_to_buf(tdiff), sensitive_time); /* * Make fake previous_day/24:00:00 localtime, * curwday is not changed. */ curdate.tm_sec = curdate.tm_min = 0; curdate.tm_hour = HOURS_IN_DAY; cursec = SECONDS_IN_DAY; /* And update all active rules. */ newday_flag = 1; set_rules_for_check(); if (do_ac() < 0) { logmsgx(IPA_LOG_ERR, "ipa_maim: do_ac for newday failed"); return -1; } /* Set current values for time related variables. */ curtime = t; cursec = cursec_new; curdate = curdate_new; curwday = curdate.tm_wday; if (debug_time) logmsgx(IPA_LOG_INFO, "ipa_main: new day"); if (newday(0) < 0) { logmsgx(IPA_LOG_ERR, "ipa_main: newday(0) failed"); return -1; } newday_flag = 0; } else { logmsgx(IPA_LOG_ERR, "internal error: ipa_main: new day was expected"); return -1; } } else { /* Set current values for time related variables. */ curtime = t; cursec = cursec_new; curdate = curdate_new; if (debug_time) { char buf[BUF_SEC_SIZE]; strcpy(buf, sec_to_buf(cursec)); logmsgx(IPA_LOG_INFO, "ipa_main: cursec %s (%u), main_check_sec %s (%u)", buf, cursec, sec_to_buf(main_check_sec), main_check_sec); } if (cursec > main_check_sec && main_check_sec != 0) { u_int tmp; tmp = cursec - main_check_sec; if (tmp > sensitive_time) { logmsgx(IPA_LOG_WARNING, "time changed too quickly during sleeping or woke too late: delta %s is greater than \"sensitive_time\" %u seconds", time_to_buf(tmp), sensitive_time); logmsgx(IPA_LOG_WARNING, "timestamps in database will be not accurate"); } if (tmp <= FAKE_TIME_DELTA) { /* Fake time, anyway timestamps are fake in most cases. */ cursec = main_check_sec; sec_to_time(cursec, &curdate); } } if (do_ac() < 0) { logmsgx(IPA_LOG_ERR, "ipa_main: do_ac failed"); return -1; } } } if (signal_flag) { /* There was a signal. */ if (shutdown_flag) return 0; signal_flag = 0; if (reconf_flag) { reconf_flag = 0; return 1; } } if (dump_flag) { /* There was ctl command "dump". */ dump_flag = 0; logmsgx(IPA_LOG_INFO, "statistics was successfully saved to database"); if (ctl_send_answer() < 0) { logmsgx(IPA_LOG_ERR, "ipa_main: ctl_send_answer failed"); return -1; } if (sleep_after_dump != 0) { logmsgx(IPA_LOG_INFO, "sleeping after \"dump\" and ignoring signals and ctl connections at least %u seconds...", sleep_after_dump); sleep(sleep_after_dump); } } for (;;) { /* * Try to find problems with time and adjust sleepsec. * This is simplified test, more complex check is above. */ if (time(&t) == (time_t)-1) { logmsg(IPA_LOG_ERR, "ipa_main: time failed"); return -1; } /* Check if new time is greater than older one. */ tdiff = difftime(t, curtime); if (tdiff >= 0.0) { /* Check for new day. */ tdiff = difftime(t, newdaytime); if (tdiff >= 0.0) { /* New day came. */ if (tdiff > sensitive_time) logmsgx(IPA_LOG_WARNING, "new day came, but see this too late: delta %s is greater than \"sensitive_time\" %u seconds (check before sleep)", tdiff_to_buf(tdiff), sensitive_time); /* Poll in select. */ sleepsec = 0; } else { if (localtime_r(&t, (struct tm *)&curdate_new) == NULL) { logmsg(IPA_LOG_ERR, "ipa_main: localtime_r failed"); return -1; } #ifdef HAVE_TM_GMTOFF /* Check if offset from UTC was changed. */ if (curdate_new.tm_gmtoff != curdate.tm_gmtoff) { logmsgx(IPA_LOG_WARNING, "offset from UTC changed from %ld to %ld seconds (check before sleep)", (long)curdate.tm_gmtoff, (long)curdate_new.tm_gmtoff); /* Poll in select. */ sleepsec = 0; } else #endif /* Check if Daylight Saving flag was changed. */ if (curdate_new.tm_isdst != curdate.tm_isdst) { logmsgx(IPA_LOG_WARNING, "Daylight Saving flag changed from %d to %d (check before sleep)", curdate.tm_isdst, curdate_new.tm_isdst); /* Poll in select. */ sleepsec = 0; } else { /* * Update time related variables, this allows * to not lost time during sleeping and * periodical wakeups. */ curtime = t; cursec = TIME_TO_SEC(&curdate_new); curdate_new.tm_year += 1900; curdate_new.tm_mon++; curdate = curdate_new; /* Recalculate number of seconds to sleep. */ if (main_check_sec != real_main_check_sec) { if (real_main_check_sec > cursec + wakeup_time) main_check_sec = cursec + wakeup_time; else main_check_sec = real_main_check_sec; } sleepsec = main_check_sec - cursec; if (sleepsec < 0) { if (-sleepsec > (int)sensitive_time) logmsgx(IPA_LOG_WARNING, "cannot correctly schedule time events: delta -%s is greater than \"sensitive_time\" %u seconds (check before sleep)", time_to_buf(-sleepsec), sensitive_time); /* Poll in select. */ sleepsec = 0; } } } } else { logmsgx(IPA_LOG_WARNING, "system time goes back: delta %s (check before sleep)", tdiff_to_buf(tdiff)); /* Poll in select(). */ sleepsec = 0; } if (debug_time) { logmsgx(IPA_LOG_INFO, "ipa_main: main_check_sec %s (%u)", sec_to_buf(main_check_sec), main_check_sec); logmsgx(IPA_LOG_INFO, "sleeping %s...", time_to_buf(sleepsec)); } tv.tv_sec = sleepsec; tv.tv_usec = 0; for (;;) { /* Unmask all signals. */ if (Sigprocmask(SIG_SETMASK, &zeromask, (sigset_t *)NULL) < 0) { logmsg(IPA_LOG_ERR, "ipa_main: " SIGPROCMASK_NAME "(SIG_SETMASK) for zeromask"); return -1; } rset = rset_main; nready = select(maxfd, &rset, (fd_set *)NULL, (fd_set *)NULL, &tv); errno_save = errno; /* Mask expected signals. */ if (Sigprocmask(SIG_SETMASK, &sigmask, (sigset_t *)NULL) < 0) { logmsg(IPA_LOG_ERR, "ipa_main: " SIGPROCMASK_NAME "(SIG_SETMASK) for sigmask"); return -1; } if (nready < 0) { if (errno_save == EINTR) { tv.tv_sec = 0; tv.tv_usec = 0; continue; } errno = errno_save; logmsg(IPA_LOG_ERR, "ipa_main: select failed"); return -1; } break; } if (nready == 0) { /* Timeout, that is normal sleep. */ if (real_main_check_sec != main_check_sec) continue; break; } if (!debug && FD_ISSET(stderr_read, &rset)) { --nready; log_stderr(); } if (FD_ISSET(stdout_read, &rset)) { --nready; log_stdout(); } if (nready != 0) { if (signal_flag) { /* Remove all queued characters in sig_pipe. */ while (read(sig_pipe[0], &ch, 1) > 0) ; if (shutdown_flag) { logmsgx(IPA_LOG_INFO, "caught signal %d, shutdowning...", (int)shutdown_flag); set_rules_for_check(); /* Ignore possible another queued signals. */ break; } if (reconf_flag) { logmsgx(IPA_LOG_INFO, "caught signal %d, reconfiguring...", SIGHUP); /* Ignore possible another queued signals. */ set_rules_for_check(); break; } if (sigchld_flag) { if (check_child() < 0) { logmsgx(IPA_LOG_ERR, "ipa_main: check_child failed"); return -1; } sigchld_flag = 0; --nready; } } /* * Check for a connection to ctl_listenfd only * if there wasn't poll in select (simplifying code). */ if (nready > 0 && FD_ISSET(ctl_listenfd, &rset) && sleepsec != 0) { /* * Since ctl command can update statistics in * database, need to update curdate and cursec. * This test looks like previous one, but don't * do anything with sleepsec. */ if (time(&t) == (time_t)-1) { logmsg(IPA_LOG_ERR, "ipa_main: time failed"); return -1; } /* Check if new time is greater than older one. */ tdiff = difftime(t, curtime); if (tdiff >= 0.0) { /* Check for new day. */ tdiff = difftime(t, newdaytime); if (tdiff >= 0.0) { /* New day came. */ if (tdiff > sensitive_time) logmsgx(IPA_LOG_WARNING, "new day came, but see this too late: delta %s is greater than \"sensitive_time\" %u seconds (check before ctl command)", tdiff_to_buf(tdiff), sensitive_time); /* Exit from sleep. */ break; } else { if (localtime_r(&t, (struct tm *)&curdate_new) == NULL) { logmsg(IPA_LOG_ERR, "ipa_main: localtime_r failed"); return -1; } #ifdef HAVE_TM_GMTOFF /* Check if offset from UTC was changed. */ if (curdate_new.tm_gmtoff != curdate.tm_gmtoff) { logmsgx(IPA_LOG_WARNING, "offset from UTC changed from %ld to %ld seconds (check before ctl command)", (long)curdate.tm_gmtoff, (long)curdate_new.tm_gmtoff); /* Exit from sleep. */ break; } else #endif /* Check if Daylight Saving flag was changed. */ if (curdate_new.tm_isdst != curdate.tm_isdst) { logmsgx(IPA_LOG_WARNING, "Daylight Saving flag changed from %d to %d (check before ctl command)", curdate.tm_isdst, curdate_new.tm_isdst); /* Exit from sleep. */ break; } else { /* * Update time related variables. * See comment above for more information why * this is needed. */ curtime = t; cursec = TIME_TO_SEC(&curdate_new); curdate_new.tm_year += 1900; curdate_new.tm_mon++; curdate = curdate_new; } } } else { logmsgx(IPA_LOG_WARNING, "system time goes back: delta %s (check before ctl command)", tdiff_to_buf(tdiff)); /* Exit from sleep. */ break; } if (ctl_recv_query() < 0) { logmsgx(IPA_LOG_ERR, "ipa_main: ctl_recv_query failed"); return -1; } if (freeze_flag) { freeze_flag = 0; if (freeze_time > 0) { logmsgx(IPA_LOG_INFO, "freezing and ignoring signals at least %u seconds...", freeze_time); sleep(freeze_time); } else logmsgx(IPA_LOG_WARNING, "do not freeze, since freeze_time is not defined"); } if (main_check_sec == 0) /* It's time to wakeup immediately. */ break; } } if (sleepsec == 0) /* There was a poll in select(), exit from sleep. */ break; } } /* NOTREACHED */ } /* * SIGINT and/or SIGTERM handler. */ void sig_term(int signo) { (void)write(sig_pipe[1], "", 1); signal_flag = 1; need_check_flag = 1; shutdown_flag = signo; } /* * SIGHUP handler, cause reconfiguring. */ /* ARGSUSED */ void sig_hup(int signo ATTR_UNUSED) { (void)write(sig_pipe[1], "", 1); signal_flag = 1; reconf_flag = 1; need_check_flag = 1; } /* * SIGCHLD handler. */ /* ARGSUSED */ void sig_chld(int signo ATTR_UNUSED) { (void)write(sig_pipe[1], "", 1); signal_flag = 1; sigchld_flag = 1; } #ifdef WITH_ANY_LIMITS /* * Convert counter to human readable value according to * the given type and return pointer to allocated buffer * with this value. */ char * cnt_to_buf(const uint64_t *val, u_int type) { static char buf[BUF_CNT_SIZE]; int rv; u_int p2, p3; uint64_t a, p1; a = *val; switch (type) { case IPA_CONF_TYPE_BYTES: p1 = a / MBYTE; a -= p1 * MBYTE; p2 = (u_int)(a / KBYTE); p3 = (u_int)(a - p2 * KBYTE); if (p1 == UINT64_C(0)) { if (p2 == 0) rv = snprintf(buf, sizeof buf, "%uB", p3); else rv = snprintf(buf, sizeof buf, "%uK %uB", p2, p3); } else rv = snprintf(buf, sizeof buf, "%"PRIu64"M %uK %uB", p1, p2, p3); break; case IPA_CONF_TYPE_TIME: p1 = a / SECONDS_IN_HOUR; a -= p1 * SECONDS_IN_HOUR; p2 = (u_int)(a / SECONDS_IN_MINUTE); p3 = (u_int)(a - p2 * SECONDS_IN_MINUTE); if (p1 == UINT64_C(0)) { if (p2 == 0) rv = snprintf(buf, sizeof buf, "%us", p3); else rv = snprintf(buf, sizeof buf, "%um %us", p2, p3); } else rv = snprintf(buf, sizeof buf, "%"PRIu64"h %um %us", p1, p2, p3); break; default: /* IPA_CONF_TYPE_UINT64 */ rv = snprintf(buf, sizeof buf, "%"PRIu64, *val); } return rv < 0 ? "(cnt_to_buf: snprintf failed)" : buf; } #endif /* WITH_ANY_LIMITS */ #if defined(WITH_THRESHOLDS) || defined(WITH_SUBLIMITS) /* * Return part of val. */ uint64_t uint64_per_cent(const uint64_t *val, u_int per_cent) { uint64_t res; if (*val > UINT64_MAX / per_cent) { res = *val / 100; res *= per_cent; } else { res = *val * per_cent; res /= 100; } return res; } #endif /* WITH_THRESHOLDS || WITH_SUBLIMITS */