diff options
Diffstat (limited to 'src/daemon/Daemon.cpp')
-rw-r--r-- | src/daemon/Daemon.cpp | 1000 |
1 files changed, 1000 insertions, 0 deletions
diff --git a/src/daemon/Daemon.cpp b/src/daemon/Daemon.cpp new file mode 100644 index 00000000..735da5af --- /dev/null +++ b/src/daemon/Daemon.cpp @@ -0,0 +1,1000 @@ +/* + Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + 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. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include <syslog.h> +#include <pthread.h> +#include <resolv.h> /* res_init */ +#include <string> +#include <sys/inotify.h> +#include <sys/ioctl.h> /* ioctl(FIONREAD) */ +#include <xmlrpc-c/base.h> +#include <xmlrpc-c/client.h> +#include <glib.h> +#if HAVE_CONFIG_H + #include <config.h> +#endif +#if HAVE_LOCALE_H + #include <locale.h> +#endif +#if ENABLE_NLS + #include <libintl.h> + #define _(S) gettext(S) +#else + #define _(S) (S) +#endif +#include "abrtlib.h" +#include "abrt_exception.h" +#include "CrashWatcher.h" +#include "debug_dump.h" +#include "Daemon.h" +#include "dumpsocket.h" +#include "rpm.h" + +using namespace std; + + +/* Daemon initializes, then sits in glib main loop, waiting for events. + * Events can be: + * - inotify: something new appeared under /var/spool/abrt + * - DBus: dbus message arrived + * - signal: we got SIGTERM or SIGINT + * + * DBus methods we have: + * - GetCrashInfos(): returns a vector_map_crash_data_t (vector_map_vector_string_t) + * of crashes for given uid + * v[N]["executable"/"uid"/"kernel"/"backtrace"][N] = "contents" + * - StartJob(crash_id,force): starts creating a report for /var/spool/abrt/DIR with this UID:UUID. + * Returns job id (uint64). + * After thread returns, when report creation thread has finished, + * JobDone() dbus signal is emitted. + * - CreateReport(crash_id): returns map_crash_data_t (map_vector_string_t) + * - Report(map_crash_data_t (map_vector_string_t[, map_map_string_t])): + * "Please report this crash": calls Report() of all registered reporter plugins. + * Returns report_status_t (map_vector_string_t) - the status of each call. + * 2nd parameter is the contents of user's abrt.conf. + * - DeleteDebugDump(crash_id): delete it from DB and delete corresponding /var/spool/abrt/DIR + * - GetPluginsInfo(): returns map_map_string_t + * map["plugin"] = { "Name": "plugin", "Enabled": "yes" ... } + * - GetPluginSettings(PluginName): returns map_plugin_settings_t (map_string_t) + * - SetPluginSettings(PluginName, map_plugin_settings_t): returns void + * - RegisterPlugin(PluginName): returns void + * - UnRegisterPlugin(PluginName): returns void + * - GetSettings(): returns map_abrt_settings_t (map_map_string_t) + * - SetSettings(map_abrt_settings_t): returns void + * + * DBus signals we emit: + * - Crash(progname, crash_id, uid) - a new crash occurred (new /var/spool/abrt/DIR is found) + * - JobDone(client_dbus_ID) - see StartJob above. + * Sent as unicast to the client which did StartJob. + * - Warning(msg) + * - Update(msg) + * Both are sent as unicast to last client set by set_client_name(name). + * If set_client_name(NULL) was done, they are not sent. + */ + + +#define VAR_RUN_LOCK_FILE VAR_RUN"/abrt/abrtd.lock" +#define VAR_RUN_PIDFILE VAR_RUN"/abrtd.pid" + + +//FIXME: add some struct to be able to join all threads! +typedef struct cron_callback_data_t +{ + std::string m_sPluginName; + std::string m_sPluginArgs; + unsigned int m_nTimeout; + + cron_callback_data_t( + const std::string& pPluginName, + const std::string& pPluginArgs, + const unsigned int& pTimeout) : + m_sPluginName(pPluginName), + m_sPluginArgs(pPluginArgs), + m_nTimeout(pTimeout) + {} +} cron_callback_data_t; + + +static volatile sig_atomic_t s_sig_caught; +static int s_signal_pipe[2]; +static int s_signal_pipe_write = -1; +static int s_upload_watch = -1; +static unsigned s_timeout; +static bool s_exiting; + +CCommLayerServer* g_pCommLayer; + + +static void cron_delete_callback_data_cb(gpointer data) +{ + cron_callback_data_t* cronDeleteCallbackData = static_cast<cron_callback_data_t*>(data); + delete cronDeleteCallbackData; +} + +static gboolean cron_activation_periodic_cb(gpointer data) +{ + cron_callback_data_t* cronPeriodicCallbackData = static_cast<cron_callback_data_t*>(data); + VERB1 log("Activating plugin: %s", cronPeriodicCallbackData->m_sPluginName.c_str()); + RunAction(DEBUG_DUMPS_DIR, + cronPeriodicCallbackData->m_sPluginName.c_str(), + cronPeriodicCallbackData->m_sPluginArgs.c_str() + ); + return TRUE; +} +static gboolean cron_activation_one_cb(gpointer data) +{ + cron_callback_data_t* cronOneCallbackData = static_cast<cron_callback_data_t*>(data); + VERB1 log("Activating plugin: %s", cronOneCallbackData->m_sPluginName.c_str()); + RunAction(DEBUG_DUMPS_DIR, + cronOneCallbackData->m_sPluginName.c_str(), + cronOneCallbackData->m_sPluginArgs.c_str() + ); + return FALSE; +} +static gboolean cron_activation_reshedule_cb(gpointer data) +{ + cron_callback_data_t* cronResheduleCallbackData = static_cast<cron_callback_data_t*>(data); + VERB1 log("Rescheduling plugin: %s", cronResheduleCallbackData->m_sPluginName.c_str()); + cron_callback_data_t* cronPeriodicCallbackData = new cron_callback_data_t(cronResheduleCallbackData->m_sPluginName, + cronResheduleCallbackData->m_sPluginArgs, + cronResheduleCallbackData->m_nTimeout); + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + cronPeriodicCallbackData->m_nTimeout, + cron_activation_periodic_cb, + static_cast<gpointer>(cronPeriodicCallbackData), + cron_delete_callback_data_cb + ); + return FALSE; +} + +static int SetUpMW() +{ + set_string_t::iterator it_k = g_settings_setOpenGPGPublicKeys.begin(); + for (; it_k != g_settings_setOpenGPGPublicKeys.end(); it_k++) + { + LoadOpenGPGPublicKey(it_k->c_str()); + } + VERB1 log("Adding actions or reporters"); + vector_pair_string_string_t::iterator it_ar = g_settings_vectorActionsAndReporters.begin(); + for (; it_ar != g_settings_vectorActionsAndReporters.end(); it_ar++) + { + AddActionOrReporter(it_ar->first.c_str(), it_ar->second.c_str()); + } + VERB1 log("Adding analyzers, actions or reporters"); + map_analyzer_actions_and_reporters_t::iterator it_aar = g_settings_mapAnalyzerActionsAndReporters.begin(); + for (; it_aar != g_settings_mapAnalyzerActionsAndReporters.end(); it_aar++) + { + vector_pair_string_string_t::iterator it_ar = it_aar->second.begin(); + for (; it_ar != it_aar->second.end(); it_ar++) + { + AddAnalyzerActionOrReporter(it_aar->first.c_str(), it_ar->first.c_str(), it_ar->second.c_str()); + } + } + return 0; +} + +static int SetUpCron() +{ + map_cron_t::iterator it_c = g_settings_mapCron.begin(); + for (; it_c != g_settings_mapCron.end(); it_c++) + { + std::string::size_type pos = it_c->first.find(":"); + int timeout = 0; + int nH = -1; + int nM = -1; + int nS = -1; + +//TODO: rewrite using good old sscanf? + + if (pos != std::string::npos) + { + std::string sH; + std::string sM; + + sH = it_c->first.substr(0, pos); + nH = xatou(sH.c_str()); + nH = nH > 23 ? 23 : nH; + nH = nH < 0 ? 0 : nH; + timeout += nH * 60 * 60; + sM = it_c->first.substr(pos + 1); + nM = xatou(sM.c_str()); + nM = nM > 59 ? 59 : nM; + nM = nM < 0 ? 0 : nM; + timeout += nM * 60; + } + else + { + std::string sS; + + sS = it_c->first; + nS = xatou(sS.c_str()); + nS = nS <= 0 ? 1 : nS; + timeout = nS; + } + + if (nS != -1) + { + vector_pair_string_string_t::iterator it_ar = it_c->second.begin(); + for (; it_ar != it_c->second.end(); it_ar++) + { + cron_callback_data_t* cronPeriodicCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, timeout); + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + timeout, + cron_activation_periodic_cb, + static_cast<gpointer>(cronPeriodicCallbackData), + cron_delete_callback_data_cb); + } + } + else + { + time_t actTime = time(NULL); + struct tm locTime; + localtime_r(&actTime, &locTime); + locTime.tm_hour = nH; + locTime.tm_min = nM; + locTime.tm_sec = 0; + time_t nextTime = mktime(&locTime); + if (nextTime == ((time_t)-1)) + { + /* paranoia */ + perror_msg("Can't set up cron time"); + return -1; + } + if (actTime > nextTime) + { + timeout = 24*60*60 + (nextTime - actTime); + } + else + { + timeout = nextTime - actTime; + } + vector_pair_string_string_t::iterator it_ar = it_c->second.begin(); + for (; it_ar != it_c->second.end(); it_ar++) + { + cron_callback_data_t* cronOneCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, timeout); + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + timeout, + cron_activation_one_cb, + static_cast<gpointer>(cronOneCallbackData), + cron_delete_callback_data_cb); + cron_callback_data_t* cronResheduleCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, 24 * 60 * 60); + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + timeout, + cron_activation_reshedule_cb, + static_cast<gpointer>(cronResheduleCallbackData), + cron_delete_callback_data_cb); + } + } + } + return 0; +} + +static void FindNewDumps(const char* pPath) +{ + /* Get all debugdump directories in the pPath directory */ + vector_string_t dirs; + DIR *dp = opendir(pPath); + if (dp == NULL) + { + perror_msg("Can't open directory '%s'", pPath); + return; + } + struct dirent *ep; + while ((ep = readdir(dp))) + { + if (dot_or_dotdot(ep->d_name)) + continue; /* skip "." and ".." */ + std::string dname = concat_path_file(pPath, ep->d_name); + struct stat stats; + if (lstat(dname.c_str(), &stats) == 0) + { + if (S_ISDIR(stats.st_mode)) + { + VERB1 log("Will check directory '%s'", ep->d_name); + dirs.push_back(dname); + } + } + } + closedir(dp); + + unsigned size = dirs.size(); + if (size == 0) + return; + log("Checking for unsaved crashes (dirs to check:%u)", size); + + /* Get potentially non-processed debugdumps */ + vector_string_t::iterator itt = dirs.begin(); + for (; itt != dirs.end(); ++itt) + { + try + { + const char *dir_name = itt->c_str(); + map_crash_data_t crashinfo; + mw_result_t res = SaveDebugDump(dir_name, crashinfo); + switch (res) + { + case MW_OK: + /* Not VERB1: this is new, unprocessed crash dump. + * Last abrtd somehow missed it - need to inform user */ + log("Non-processed crash in %s, saving into database", dir_name); + /* Run automatic actions and reporters on it (if we have them configured) */ + RunActionsAndReporters(dir_name); + break; + case MW_IN_DB: + /* This debugdump was found in DB, nothing else was done + * by SaveDebugDump or needs to be done by us */ + VERB1 log("%s is already saved in database", dir_name); + break; + case MW_REPORTED: /* already reported dup */ + case MW_OCCURRED: /* not-yet-reported dup */ + VERB1 log("Duplicate crash %s, deleting", dir_name); + delete_debug_dump_dir(dir_name); + break; + default: + log("Corrupted or bad crash %s (res:%d), deleting", dir_name, (int)res); + delete_debug_dump_dir(dir_name); + break; + } + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + } + } + log("Done checking for unsaved crashes"); +} + +static int CreatePidFile() +{ + int fd; + + /* JIC */ + unlink(VAR_RUN_PIDFILE); + + /* open the pidfile */ + fd = open(VAR_RUN_PIDFILE, O_WRONLY|O_CREAT|O_EXCL, 0644); + if (fd >= 0) + { + /* write our pid to it */ + char buf[sizeof(long)*3 + 2]; + int len = sprintf(buf, "%lu\n", (long)getpid()); + write(fd, buf, len); + close(fd); + return 0; + } + + /* something went wrong */ + perror_msg("Can't open '%s'", VAR_RUN_PIDFILE); + return -1; +} + +static int Lock() +{ + int lfd = open(VAR_RUN_LOCK_FILE, O_RDWR|O_CREAT, 0640); + if (lfd < 0) + { + perror_msg("Can't open '%s'", VAR_RUN_LOCK_FILE); + return -1; + } + if (lockf(lfd, F_TLOCK, 0) < 0) + { + perror_msg("Can't lock file '%s'", VAR_RUN_LOCK_FILE); + return -1; + } + close_on_exec_on(lfd); + return 0; + /* we leak opened lfd intentionally */ +} + +static void handle_fatal_signal(int signo) +{ + // Enable for debugging only, malloc/printf are unsafe in signal handlers + //VERB3 log("Got signal %d", signo); + + uint8_t l_sig_caught; + s_sig_caught = l_sig_caught = signo; + /* Using local copy of s_sig_caught so that concurrent signal + * won't change it under us */ + if (s_signal_pipe_write >= 0) + write(s_signal_pipe_write, &l_sig_caught, 1); +} + +/* Signal pipe handler */ +static gboolean handle_signal_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr_unused) +{ + char signo; + gsize len = 0; + g_io_channel_read(gio, &signo, 1, &len); + if (len == 1) + { + /* we did receive a signal */ + VERB3 log("Got signal %d through signal pipe", signo); + s_exiting = 1; + return TRUE; + } + return FALSE; +} + +/* Inotify handler */ +static gboolean handle_inotify_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr_unused) +{ + /* Default size: 128 simultaneous actions (about 1/2 meg) */ +#define INOTIFY_BUF_SIZE ((sizeof(struct inotify_event) + FILENAME_MAX)*128) + /* Determine how much to read (it usually is much smaller) */ + /* NB: this variable _must_ be int-sized, ioctl expects that! */ + int inotify_bytes = INOTIFY_BUF_SIZE; + if (ioctl(g_io_channel_unix_get_fd(gio), FIONREAD, &inotify_bytes) != 0 + || inotify_bytes < sizeof(struct inotify_event) + || inotify_bytes > INOTIFY_BUF_SIZE + ) { + inotify_bytes = INOTIFY_BUF_SIZE; + } + VERB3 log("FIONREAD:%d", inotify_bytes); + + char *buf = (char*)xmalloc(inotify_bytes); + errno = 0; + gsize len; + GIOError err = g_io_channel_read(gio, buf, inotify_bytes, &len); + if (err != G_IO_ERROR_NONE) + { + perror_msg("Error reading inotify fd"); + free(buf); + return FALSE; + } + + /* Reconstruct each event and send message to the dbus */ + gsize i = 0; + while (i < len) + { + struct inotify_event *event = (struct inotify_event *) &buf[i]; + const char *name = NULL; + if (event->len) + name = event->name; + //log("i:%d len:%d event->mask:%x IN_ISDIR:%x IN_CLOSE_WRITE:%x event->len:%d", + // i, len, event->mask, IN_ISDIR, IN_CLOSE_WRITE, event->len); + i += sizeof(*event) + event->len; + + if (event->wd == s_upload_watch) + { + /* Was the (presumable newly created) file closed in upload dir, + * or a file moved to upload dir? */ + if (!(event->mask & IN_ISDIR) + && event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO) + && name + ) { + const char *ext = strrchr(name, '.'); + if (ext && strcmp(ext + 1, "working") == 0) + continue; + + const char *dir = g_settings_sWatchCrashdumpArchiveDir.c_str(); + log("Detected creation of file '%s' in upload directory '%s'", name, dir); + if (fork() == 0) + { + xchdir(dir); + execlp("abrt-handle-upload", "abrt-handle-upload", DEBUG_DUMPS_DIR, dir, name, (char*)NULL); + error_msg_and_die("Can't execute '%s'", "abrt-handle-upload"); + } + } + continue; + } + + if (!(event->mask & IN_ISDIR) || !name) + { + /* ignore lock files and such */ + // Happens all the time during normal run + //VERB3 log("File '%s' creation detected, ignoring", name); + continue; + } + if (strcmp(strchrnul(name, '.'), ".new") == 0) + { + //VERB3 log("Directory '%s' creation detected, ignoring", name); + continue; + } + log("Directory '%s' creation detected", name); + + if (g_settings_nMaxCrashReportsSize > 0) + { + std::string worst_dir; + while (g_settings_nMaxCrashReportsSize > 0 + && get_dirsize_find_largest_dir(DEBUG_DUMPS_DIR, &worst_dir, name) / (1024*1024) >= g_settings_nMaxCrashReportsSize + && worst_dir != "" + ) { + log("Size of '%s' >= %u MB, deleting '%s'", DEBUG_DUMPS_DIR, g_settings_nMaxCrashReportsSize, worst_dir.c_str()); + g_pCommLayer->QuotaExceed(_("The size of the report exceeded the quota. Please check system's MaxCrashReportsSize value in abrt.conf.")); + /* deletes both directory and DB record */ + DeleteDebugDump_by_dir(concat_path_file(DEBUG_DUMPS_DIR, worst_dir.c_str()).c_str()); + worst_dir = ""; + } + } + + try + { + std::string fullname = concat_path_file(DEBUG_DUMPS_DIR, name); + /* Note: SaveDebugDump does not save crashinfo, it _fetches_ crashinfo */ + map_crash_data_t crashinfo; + mw_result_t res = SaveDebugDump(fullname.c_str(), crashinfo); + switch (res) + { + case MW_OK: + log("New crash %s, processing", fullname.c_str()); + /* Run automatic actions and reporters on it (if we have them configured) */ + RunActionsAndReporters(fullname.c_str()); + /* Fall through */ + + case MW_REPORTED: /* already reported dup */ + case MW_OCCURRED: /* not-yet-reported dup */ + { + if (res != MW_OK) + { + const char *first = get_crash_data_item_content(crashinfo, CD_DUMPDIR).c_str(); + log("Deleting crash %s (dup of %s), sending dbus signal", + strrchr(fullname.c_str(), '/') + 1, + strrchr(first, '/') + 1); + delete_debug_dump_dir(fullname.c_str()); + } +#define fullname fullname_should_not_be_used_here + + const char *analyzer = get_crash_data_item_content(crashinfo, FILENAME_ANALYZER).c_str(); + const char *uid_str = get_crash_data_item_content(crashinfo, CD_UID).c_str(); + + /* Autoreport it if configured to do so */ + if (res != MW_REPORTED + && analyzer_has_AutoReportUIDs(analyzer, uid_str) + ) { + VERB1 log("Reporting the crash automatically"); + map_crash_data_t crash_report; + string crash_id = ssprintf("%s:%s", uid_str, get_crash_data_item_content(crashinfo, CD_UUID).c_str()); + mw_result_t crash_result = CreateCrashReport( + crash_id.c_str(), + /*caller_uid:*/ 0, + /*force:*/ 0, + crash_report + ); + if (crash_result == MW_OK) + { + map_analyzer_actions_and_reporters_t::const_iterator it = g_settings_mapAnalyzerActionsAndReporters.find(analyzer); + map_analyzer_actions_and_reporters_t::const_iterator end = g_settings_mapAnalyzerActionsAndReporters.end(); + if (it != end) + { + vector_pair_string_string_t keys = it->second; + unsigned size = keys.size(); + for (unsigned ii = 0; ii < size; ii++) + { + autoreport(keys[ii], crash_report); + } + } + } + } + /* Send dbus signal */ + if (analyzer_has_InformAllUsers(analyzer)) + uid_str = NULL; + char *crash_id = xasprintf("%s:%s", + get_crash_data_item_content(crashinfo, CD_UID).c_str(), + get_crash_data_item_content(crashinfo, CD_UUID).c_str() + ); + g_pCommLayer->Crash(get_crash_data_item_content(crashinfo, FILENAME_PACKAGE).c_str(), + crash_id, + uid_str); + free(crash_id); + break; +#undef fullname + } + case MW_IN_DB: + log("Huh, this crash is already in db?! Nothing to do"); + break; + case MW_BLACKLISTED: + case MW_CORRUPTED: + case MW_PACKAGE_ERROR: + case MW_GPG_ERROR: + case MW_FILE_ERROR: + default: + log("Corrupted or bad crash %s (res:%d), deleting", fullname.c_str(), (int)res); + delete_debug_dump_dir(fullname.c_str()); + break; + } + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + } + catch (...) + { + free(buf); + throw; + } + } /* while */ + + free(buf); + return TRUE; +} + +/* Run main loop with idle timeout. + * Basically, almost like glib's g_main_run(loop) + */ +static void run_main_loop(GMainLoop* loop) +{ + GMainContext *context = g_main_loop_get_context(loop); + time_t old_time = 0; + time_t dns_conf_hash = 0; + + while (!s_exiting) + { + /* we have just a handful of sources, 32 should be ample */ + const unsigned NUM_POLLFDS = 32; + GPollFD fds[NUM_POLLFDS]; + gboolean some_ready; + gint max_priority; + gint timeout; + + some_ready = g_main_context_prepare(context, &max_priority); + if (some_ready) + g_main_context_dispatch(context); + + gint nfds = g_main_context_query(context, max_priority, &timeout, fds, NUM_POLLFDS); + if (nfds > NUM_POLLFDS) + error_msg_and_die("Internal error"); + + if (s_timeout) + alarm(s_timeout); + g_poll(fds, nfds, timeout); + if (s_timeout) + alarm(0); + + /* res_init() makes glibc reread /etc/resolv.conf. + * I'd think libc should be clever enough to do it itself + * at every name resolution attempt, but no... + * We need to guess ourself whether we want to do it. + */ + time_t now = time(NULL) >> 2; + if (old_time != now) /* check once in 4 seconds */ + { + old_time = now; + + time_t hash = 0; + struct stat sb; + if (stat("/etc/resolv.conf", &sb) == 0) + hash = sb.st_mtime; + if (stat("/etc/host.conf", &sb) == 0) + hash += sb.st_mtime; + if (stat("/etc/hosts", &sb) == 0) + hash += sb.st_mtime; + if (stat("/etc/nsswitch.conf", &sb) == 0) + hash += sb.st_mtime; + if (dns_conf_hash != hash) + { + dns_conf_hash = hash; + res_init(); + } + } + + some_ready = g_main_context_check(context, max_priority, fds, nfds); + if (some_ready) + g_main_context_dispatch(context); + } + + g_main_context_unref(context); +} + +static void start_syslog_logging() +{ + /* Open stdin to /dev/null */ + xmove_fd(xopen("/dev/null", O_RDWR), STDIN_FILENO); + /* We must not leave fds 0,1,2 closed. + * Otherwise fprintf(stderr) dumps messages into random fds, etc. */ + xdup2(STDIN_FILENO, STDOUT_FILENO); + xdup2(STDIN_FILENO, STDERR_FILENO); + openlog("abrtd", 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; +} + +static void ensure_writable_dir(const char *dir, mode_t mode, const char *user) +{ + struct stat sb; + + if (mkdir(dir, mode) != 0 && errno != EEXIST) + perror_msg_and_die("Can't create '%s'", dir); + if (stat(dir, &sb) != 0 || !S_ISDIR(sb.st_mode)) + error_msg_and_die("'%s' is not a directory", dir); + + struct passwd *pw = getpwnam(user); + if (!pw) + perror_msg_and_die("Can't find user '%s'", user); + + if ((sb.st_uid != pw->pw_uid || sb.st_gid != pw->pw_gid) && chown(dir, pw->pw_uid, pw->pw_gid) != 0) + perror_msg_and_die("Can't set owner %u:%u on '%s'", (unsigned int)pw->pw_uid, (unsigned int)pw->pw_gid, dir); + if ((sb.st_mode & 07777) != mode && chmod(dir, mode) != 0) + perror_msg_and_die("Can't set mode %o on '%s'", mode, dir); +} + +static void sanitize_dump_dir_rights() +{ + /* We can't allow everyone to create dumps: otherwise users can flood + * us with thousands of bogus or malicious dumps */ + /* 07000 bits are setuid, setgit, and sticky, and they must be unset */ + /* 00777 bits are usual "rwxrwxrwx" access rights */ + ensure_writable_dir(DEBUG_DUMPS_DIR, 0755, "abrt"); + /* debuginfo cache */ + ensure_writable_dir(DEBUG_INFO_DIR, 0755, "root"); + /* temp dir */ + ensure_writable_dir(VAR_RUN"/abrt", 0755, "root"); +} + +int main(int argc, char** argv) +{ + bool daemonize = true; + int opt; + int parent_pid = getpid(); + + setlocale(LC_ALL, ""); + +#if ENABLE_NLS + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + if (getuid() != 0) + error_msg_and_die("ABRT daemon must be run as root"); + + while ((opt = getopt(argc, argv, "dsvt:")) != -1) + { + unsigned long ul; + + switch (opt) + { + case 'd': + daemonize = false; + break; + case 's': + start_syslog_logging(); + break; + case 'v': + g_verbose++; + break; + case 't': + char *end; + errno = 0; + s_timeout = ul = strtoul(optarg, &end, 0); + if (errno == 0 && *end == '\0' && ul <= INT_MAX) + break; + /* fall through to error */ + default: + error_msg_and_die( + "Usage: abrtd [-dsv] [-t SEC]\n" + "\nOptions:" + "\n\t-d\tDo not daemonize" + "\n\t-s\tLog to syslog even with -d" + "\n\t-t SEC\tExit after SEC seconds of inactivity" + "\n\t-v\tVerbose" + ); + } + } + + msg_prefix = "abrtd: "; /* for log(), error_msg() and such */ + + xpipe(s_signal_pipe); + close_on_exec_on(s_signal_pipe[0]); + close_on_exec_on(s_signal_pipe[1]); + signal(SIGTERM, handle_fatal_signal); + signal(SIGINT, handle_fatal_signal); + if (s_timeout) + signal(SIGALRM, handle_fatal_signal); + + /* Daemonize unless -d */ + if (daemonize) + { + /* forking to background */ + pid_t pid = fork(); + if (pid < 0) + { + perror_msg_and_die("fork"); + } + if (pid > 0) + { + /* Parent */ + /* Wait for child to notify us via SIGTERM that it feels ok */ + int i = 20; /* 2 sec */ + while (s_sig_caught == 0 && --i) + { + usleep(100 * 1000); + } + if (s_sig_caught == SIGTERM) + { + exit(0); + } + if (s_sig_caught) + { + error_msg_and_die("Failed to start: got sig %d", s_sig_caught); + } + error_msg_and_die("Failed to start: timeout waiting for child"); + } + /* Child (daemon) continues */ + setsid(); /* never fails */ + if (g_verbose == 0 && logmode != LOGMODE_SYSLOG) + start_syslog_logging(); + } + + GMainLoop* pMainloop = NULL; + GIOChannel* channel_inotify = NULL; + guint channel_inotify_event_id = 0; + GIOChannel* channel_signal = NULL; + guint channel_signal_event_id = 0; + bool lockfile_created = false; + bool pidfile_created = false; + CCrashWatcher watcher; + + /* Initialization */ + try + { + init_daemon_logging(&watcher); + + VERB1 log("Loading settings"); + if (LoadSettings() != 0) + throw 1; + + VERB1 log("Initializing XML-RPC library"); + xmlrpc_env env; + xmlrpc_env_init(&env); + xmlrpc_client_setup_global_const(&env); + if (env.fault_occurred) + error_msg_and_die("XML-RPC Fault: %s(%d)", env.fault_string, env.fault_code); + + VERB1 log("Initializing rpm library"); + rpm_init(); + + VERB1 log("Creating glib main loop"); + pMainloop = g_main_loop_new(NULL, FALSE); + /* Watching DEBUG_DUMPS_DIR for new files... */ + + VERB1 log("Initializing inotify"); + sanitize_dump_dir_rights(); + errno = 0; + int inotify_fd = inotify_init(); + if (inotify_fd == -1) + perror_msg_and_die("inotify_init failed"); + close_on_exec_on(inotify_fd); + if (inotify_add_watch(inotify_fd, DEBUG_DUMPS_DIR, IN_CREATE | IN_MOVED_TO) < 0) + perror_msg_and_die("inotify_add_watch failed on '%s'", DEBUG_DUMPS_DIR); + if (!g_settings_sWatchCrashdumpArchiveDir.empty()) + { + s_upload_watch = inotify_add_watch(inotify_fd, g_settings_sWatchCrashdumpArchiveDir.c_str(), IN_CLOSE_WRITE|IN_MOVED_TO); + if (s_upload_watch < 0) + perror_msg_and_die("inotify_add_watch failed on '%s'", g_settings_sWatchCrashdumpArchiveDir.c_str()); + } + VERB1 log("Adding inotify watch to glib main loop"); + channel_inotify = g_io_channel_unix_new(inotify_fd); + channel_inotify_event_id = g_io_add_watch(channel_inotify, + G_IO_IN, + handle_inotify_cb, + NULL); + + VERB1 log("Loading plugins from "PLUGINS_LIB_DIR); + g_pPluginManager = new CPluginManager(); + g_pPluginManager->LoadPlugins(); + + if (SetUpMW() != 0) /* logging is inside */ + throw 1; + if (SetUpCron() != 0) + throw 1; + + /* Add an event source which waits for INT/TERM signal */ + VERB1 log("Adding signal pipe watch to glib main loop"); + channel_signal = g_io_channel_unix_new(s_signal_pipe[0]); + channel_signal_event_id = g_io_add_watch(channel_signal, + G_IO_IN, + handle_signal_cb, + NULL); + + /* Mark the territory */ + VERB1 log("Creating lock file"); + if (Lock() != 0) + throw 1; + lockfile_created = true; + VERB1 log("Creating pid file"); + if (CreatePidFile() != 0) + throw 1; + pidfile_created = true; + + /* Open socket to receive new crashes. */ + dumpsocket_init(); + + /* Note: this already may process a few dbus messages, + * therefore it should be the last thing to initialize. + */ + VERB1 log("Initializing dbus"); + g_pCommLayer = new CCommLayerServerDBus(); + if (g_pCommLayer->m_init_error) + throw 1; + } + catch (...) + { + /* Initialization error */ + error_msg("Error while initializing daemon"); + /* Inform parent that initialization failed */ + if (daemonize) + kill(parent_pid, SIGINT); + goto cleanup; + } + + /* Inform parent that we initialized ok */ + if (daemonize) + { + VERB1 log("Signalling parent"); + kill(parent_pid, SIGTERM); + if (logmode != LOGMODE_SYSLOG) + start_syslog_logging(); + } + + /* Only now we want signal pipe to work */ + s_signal_pipe_write = s_signal_pipe[1]; + + /* Enter the event loop */ + try + { + /* This may take a while, therefore we don't do it in init section */ + FindNewDumps(DEBUG_DUMPS_DIR); + log("Init complete, entering main loop"); + run_main_loop(pMainloop); + } + catch (CABRTException& e) + { + error_msg("Error: %s", e.what()); + } + catch (std::exception& e) + { + error_msg("Error: %s", e.what()); + } + + cleanup: + /* Error or INT/TERM. Clean up, in reverse order. + * Take care to not undo things we did not do. + */ + dumpsocket_shutdown(); + rpm_destroy(); + if (pidfile_created) + unlink(VAR_RUN_PIDFILE); + if (lockfile_created) + unlink(VAR_RUN_LOCK_FILE); + + if (channel_signal_event_id > 0) + g_source_remove(channel_signal_event_id); + if (channel_signal) + g_io_channel_unref(channel_signal); + if (channel_inotify_event_id > 0) + g_source_remove(channel_inotify_event_id); + if (channel_inotify) + g_io_channel_unref(channel_inotify); + + delete g_pCommLayer; + if (g_pPluginManager) + { + /* This restores /proc/sys/kernel/core_pattern, among other things: */ + g_pPluginManager->UnLoadPlugins(); + delete g_pPluginManager; + } + if (pMainloop) + g_main_loop_unref(pMainloop); + + /* Exiting */ + if (s_sig_caught && s_sig_caught != SIGALRM) + { + error_msg_and_die("Got signal %d, exiting", s_sig_caught); + signal(s_sig_caught, SIG_DFL); + raise(s_sig_caught); + } + error_msg_and_die("Exiting"); +} |