diff options
author | Nikola Pajkovsky <npajkovs@redhat.com> | 2010-08-10 10:21:25 +0200 |
---|---|---|
committer | Nikola Pajkovsky <npajkovs@redhat.com> | 2010-08-10 10:21:56 +0200 |
commit | 83a6ce9ad4b1828e163dc7172ef603201b748473 (patch) | |
tree | 9d0580eba6c01cb5964655df42bafab9de91329b /lib/utils | |
parent | e84ab7783d05eb7b5f1b55ab44e7c23c85e50516 (diff) | |
download | abrt-83a6ce9ad4b1828e163dc7172ef603201b748473.tar.gz abrt-83a6ce9ad4b1828e163dc7172ef603201b748473.tar.xz abrt-83a6ce9ad4b1828e163dc7172ef603201b748473.zip |
lower case direcotry(no code changed)
Signed-off-by: Nikola Pajkovsky <npajkovs@redhat.com>
Diffstat (limited to 'lib/utils')
43 files changed, 7467 insertions, 0 deletions
diff --git a/lib/utils/ABRTException.cpp b/lib/utils/ABRTException.cpp new file mode 100644 index 00000000..0ae5d452 --- /dev/null +++ b/lib/utils/ABRTException.cpp @@ -0,0 +1,33 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 "abrt_exception.h" + +CABRTException::CABRTException(abrt_exception_t type, const char* fmt, ...) +{ + m_type = type; + va_list ap; + va_start(ap, fmt); + m_what = xvasprintf(fmt, ap); + va_end(ap); +} + +CABRTException::CABRTException(const CABRTException& rhs): + m_type(rhs.m_type), + m_what(xstrdup(rhs.m_what)) +{} diff --git a/lib/utils/CommLayerInner.cpp b/lib/utils/CommLayerInner.cpp new file mode 100644 index 00000000..3c102d6e --- /dev/null +++ b/lib/utils/CommLayerInner.cpp @@ -0,0 +1,94 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 <pthread.h> +#include <map> +#include "abrtlib.h" +#include "comm_layer_inner.h" + +static CObserver *s_pObs; + +typedef std::map<uint64_t, std::string> map_uint_str_t; +static map_uint_str_t s_mapClientID; +static pthread_mutex_t s_map_mutex; +static bool s_map_mutex_inited; + +/* called via [p]error_msg() */ +static void warn_client(const char *msg) +{ + if (!s_pObs) + return; + + uint64_t key = uint64_t(pthread_self()); + + pthread_mutex_lock(&s_map_mutex); + map_uint_str_t::const_iterator ki = s_mapClientID.find(key); + const char* peer = (ki != s_mapClientID.end() ? ki->second.c_str() : NULL); + pthread_mutex_unlock(&s_map_mutex); + + if (peer) + s_pObs->Warning(msg, peer); +} + +void init_daemon_logging(CObserver *pObs) +{ + s_pObs = pObs; + if (!s_map_mutex_inited) + { + s_map_mutex_inited = true; + pthread_mutex_init(&s_map_mutex, NULL); + g_custom_logger = &warn_client; + } +} + +void set_client_name(const char *name) +{ + uint64_t key = uint64_t(pthread_self()); + + pthread_mutex_lock(&s_map_mutex); + if (!name) { + s_mapClientID.erase(key); + } else { + s_mapClientID[key] = name; + } + pthread_mutex_unlock(&s_map_mutex); +} + +void update_client(const char *fmt, ...) +{ + if (!s_pObs) + return; + + uint64_t key = uint64_t(pthread_self()); + + pthread_mutex_lock(&s_map_mutex); + map_uint_str_t::const_iterator ki = s_mapClientID.find(key); + const char* peer = (ki != s_mapClientID.end() ? ki->second.c_str() : NULL); + pthread_mutex_unlock(&s_map_mutex); + + if (!peer) + return; + + va_list p; + va_start(p, fmt); + char *msg = xvasprintf(fmt, p); + va_end(p); + + s_pObs->Status(msg, peer); + free(msg); +} diff --git a/lib/utils/CrashTypes.cpp b/lib/utils/CrashTypes.cpp new file mode 100644 index 00000000..96be28c1 --- /dev/null +++ b/lib/utils/CrashTypes.cpp @@ -0,0 +1,132 @@ +/* + Copyright (C) 2010 Denys Vlasenko (dvlasenk@redhat.com) + Copyright (C) 2010 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 "abrt_types.h" +#include "abrtlib.h" +#include "crash_types.h" + +const char *const must_have_files[] = { + FILENAME_ARCHITECTURE, + FILENAME_KERNEL , + FILENAME_PACKAGE , + FILENAME_COMPONENT , + FILENAME_RELEASE , + FILENAME_EXECUTABLE , + NULL +}; + +static const char *const editable_files[] = { + FILENAME_DESCRIPTION, + FILENAME_COMMENT , + FILENAME_REPRODUCE , + FILENAME_BACKTRACE , + NULL +}; + +static bool is_editable(const char *name, const char *const *v) +{ + while (*v) { + if (strcmp(*v, name) == 0) + return true; + v++; + } + return false; +} + +bool is_editable_file(const char *file_name) +{ + return is_editable(file_name, editable_files); +} + + +void add_to_crash_data_ext(map_crash_data_t& pCrashData, + const char *pItem, + const char *pType, + const char *pEditable, + const char *pContent) +{ + map_crash_data_t::iterator it = pCrashData.find(pItem); + if (it == pCrashData.end()) { + vector_string_t& v = pCrashData[pItem]; /* create empty vector */ + v.push_back(pType); + v.push_back(pEditable); + v.push_back(pContent); + return; + } + vector_string_t& v = it->second; + while (v.size() < 3) + v.push_back(""); + v[CD_TYPE] = pType; + v[CD_EDITABLE] = pEditable; + v[CD_CONTENT] = pContent; +} + +void add_to_crash_data(map_crash_data_t& pCrashData, + const char *pItem, + const char *pContent) +{ + add_to_crash_data_ext(pCrashData, pItem, CD_TXT, CD_ISNOTEDITABLE, pContent); +} + +static const std::string* helper_get_crash_data_item_content(const map_crash_data_t& crash_data, const char *key) +{ + map_crash_data_t::const_iterator it = crash_data.find(key); + if (it == crash_data.end()) { + return NULL; + } + if (it->second.size() <= CD_CONTENT) { + return NULL; + } + return &it->second[CD_CONTENT]; +} + +const std::string& get_crash_data_item_content(const map_crash_data_t& crash_data, const char *key) +{ + const std::string* sp = helper_get_crash_data_item_content(crash_data, key); + if (sp == NULL) { + if (crash_data.find(key) == crash_data.end()) + error_msg_and_die("Error accessing crash data: no ['%s']", key); + error_msg_and_die("Error accessing crash data: no ['%s'][%d]", key, CD_CONTENT); + } + return *sp; +} + +const char *get_crash_data_item_content_or_NULL(const map_crash_data_t& crash_data, const char *key) +{ + const std::string* sp = helper_get_crash_data_item_content(crash_data, key); + if (!sp) { + return NULL; + } + return sp->c_str(); +} + +void log_map_crash_data(const map_crash_data_t& data, const char *name) +{ + map_crash_data_t::const_iterator it = data.begin(); + while (it != data.end()) + { + ssize_t sz = it->second.size(); + log("%s[%s]:%s/%s/'%.20s'", + name, it->first.c_str(), + sz > 0 ? it->second[0].c_str() : "<NO [0]>", + sz > 1 ? it->second[1].c_str() : "<NO [1]>", + sz > 2 ? it->second[2].c_str() : "<NO [2]>" + ); + it++; + } +} diff --git a/lib/utils/DebugDump.cpp b/lib/utils/DebugDump.cpp new file mode 100644 index 00000000..30ceacc1 --- /dev/null +++ b/lib/utils/DebugDump.cpp @@ -0,0 +1,521 @@ +/* + DebugDump.cpp + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@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 <sys/utsname.h> +#include "abrtlib.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" + +static bool isdigit_str(const char *str) +{ + do + { + if (*str < '0' || *str > '9') return false; + str++; + } while (*str); + return true; +} + +static std::string RemoveBackSlashes(const char *pDir) +{ + unsigned len = strlen(pDir); + while (len != 0 && pDir[len-1] == '/') + len--; + return std::string(pDir, len); +} + +static bool ExistFileDir(const char *pPath) +{ + struct stat buf; + if (stat(pPath, &buf) == 0) + { + if (S_ISDIR(buf.st_mode) || S_ISREG(buf.st_mode)) + { + return true; + } + } + return false; +} + +static void LoadTextFile(const char *pPath, std::string& pData); + +CDebugDump::CDebugDump() : + m_sDebugDumpDir(""), + m_pGetNextFileDir(NULL), + m_bOpened(false), + m_bLocked(false), + m_uid(0), + m_gid(0) +{} + +CDebugDump::~CDebugDump() +{ + /* Paranoia. In C++, destructor will abort() if it was called while unwinding + * the stack and it throws an exception. + */ + try + { + Close(); + m_sDebugDumpDir.clear(); + } + catch (...) + { + error_msg_and_die("Internal error"); + } +} + +void CDebugDump::Open(const char *pDir) +{ + if (m_bOpened) + { + throw CABRTException(EXCEP_ERROR, "CDebugDump is already opened"); + } + m_sDebugDumpDir = RemoveBackSlashes(pDir); + if (!ExistFileDir(m_sDebugDumpDir.c_str())) + { + throw CABRTException(EXCEP_DD_OPEN, "'%s' does not exist", m_sDebugDumpDir.c_str()); + } + Lock(); + m_bOpened = true; + /* In case caller would want to create more files, he'll need uid:gid */ + m_uid = 0; + m_gid = 0; + struct stat stat_buf; + if (stat(m_sDebugDumpDir.c_str(), &stat_buf) == 0) + { + m_uid = stat_buf.st_uid; + m_gid = stat_buf.st_gid; + } +} + +bool CDebugDump::Exist(const char* pPath) +{ + std::string fullPath = concat_path_file(m_sDebugDumpDir.c_str(), pPath); + return ExistFileDir(fullPath.c_str()); +} + +static bool GetAndSetLock(const char* pLockFile, const char* pPID) +{ + while (symlink(pPID, pLockFile) != 0) + { + if (errno != EEXIST) + perror_msg_and_die("Can't create lock file '%s'", pLockFile); + + char pid_buf[sizeof(pid_t)*3 + 4]; + ssize_t r = readlink(pLockFile, pid_buf, sizeof(pid_buf) - 1); + if (r < 0) + { + if (errno == ENOENT) + { + /* Looks like pLockFile was deleted */ + usleep(10 * 1000); /* avoid CPU eating loop */ + continue; + } + perror_msg_and_die("Can't read lock file '%s'", pLockFile); + } + pid_buf[r] = '\0'; + + if (strcmp(pid_buf, pPID) == 0) + { + log("Lock file '%s' is already locked by us", pLockFile); + return false; + } + if (isdigit_str(pid_buf)) + { + if (access(ssprintf("/proc/%s", pid_buf).c_str(), F_OK) == 0) + { + log("Lock file '%s' is locked by process %s", pLockFile, pid_buf); + return false; + } + log("Lock file '%s' was locked by process %s, but it crashed?", pLockFile, pid_buf); + } + /* The file may be deleted by now by other process. Ignore ENOENT */ + if (unlink(pLockFile) != 0 && errno != ENOENT) + { + perror_msg_and_die("Can't remove stale lock file '%s'", pLockFile); + } + } + + VERB1 log("Locked '%s'", pLockFile); + return true; + +#if 0 +/* Old code was using ordinary files instead of symlinks, + * but it had a race window between open and write, during which file was + * empty. It was seen to happen in practice. + */ + int fd; + while ((fd = open(pLockFile, O_WRONLY | O_CREAT | O_EXCL, 0640)) < 0) + { + if (errno != EEXIST) + perror_msg_and_die("Can't create lock file '%s'", pLockFile); + fd = open(pLockFile, O_RDONLY); + if (fd < 0) + { + if (errno == ENOENT) + continue; /* someone else deleted the file */ + perror_msg_and_die("Can't open lock file '%s'", pLockFile); + } + char pid_buf[sizeof(pid_t)*3 + 4]; + int r = read(fd, pid_buf, sizeof(pid_buf) - 1); + if (r < 0) + perror_msg_and_die("Can't read lock file '%s'", pLockFile); + close(fd); + if (r == 0) + { + /* Other process did not write out PID yet. + * We HOPE it did not crash... */ + continue; + } + pid_buf[r] = '\0'; + if (strcmp(pid_buf, pPID) == 0) + { + log("Lock file '%s' is already locked by us", pLockFile); + return -1; + } + if (isdigit_str(pid_buf)) + { + if (access(ssprintf("/proc/%s", pid_buf).c_str(), F_OK) == 0) + { + log("Lock file '%s' is locked by process %s", pLockFile, pid_buf); + return -1; + } + log("Lock file '%s' was locked by process %s, but it crashed?", pLockFile, pid_buf); + } + /* The file may be deleted by now by other process. Ignore errors */ + unlink(pLockFile); + } + + int len = strlen(pPID); + if (write(fd, pPID, len) != len) + { + unlink(pLockFile); + /* close(fd); - not needed, exiting does it too */ + perror_msg_and_die("Can't write lock file '%s'", pLockFile); + } + close(fd); + + VERB1 log("Locked '%s'", pLockFile); + return true; +#endif +} + +void CDebugDump::Lock() +{ + if (m_bLocked) + error_msg_and_die("Locking bug on '%s'", m_sDebugDumpDir.c_str()); + + std::string lockFile = m_sDebugDumpDir + ".lock"; + char pid_buf[sizeof(long)*3 + 2]; + sprintf(pid_buf, "%lu", (long)getpid()); + while ((m_bLocked = GetAndSetLock(lockFile.c_str(), pid_buf)) != true) + { + sleep(1); /* was 0.5 seconds */ + } +} + +void CDebugDump::UnLock() +{ + if (m_bLocked) + { + m_bLocked = false; + std::string lockFile = m_sDebugDumpDir + ".lock"; + xunlink(lockFile.c_str()); + VERB1 log("UnLocked '%s'", lockFile.c_str()); + } +} + +/* Create a fresh empty debug dump dir. + * + * Security: we should not allow users to write new files or write + * into existing ones, but they should be able to read them. + * + * @param uid + * Crashed application's User Id + * + * We currently have only three callers: + * kernel oops hook: uid=0 + * this hook runs under 0:0 + * ccpp hook: uid=uid of crashed user's binary + * this hook runs under 0:0 + * python hook: uid=uid of crashed user's script + * this hook runs under abrt:gid + * + * Currently, we set dir's gid to passwd(uid)->pw_gid parameter, and we set uid to + * abrt's user id. We do not allow write access to group. + */ +void CDebugDump::Create(const char *pDir, uid_t uid) +{ + if (m_bOpened) + { + throw CABRTException(EXCEP_ERROR, "DebugDump is already opened"); + } + + m_sDebugDumpDir = RemoveBackSlashes(pDir); + if (ExistFileDir(m_sDebugDumpDir.c_str())) + { + throw CABRTException(EXCEP_DD_OPEN, "'%s' already exists", m_sDebugDumpDir.c_str()); + } + + Lock(); + m_bOpened = true; + + /* Was creating it with mode 0700 and user as the owner, but this allows + * the user to replace any file in the directory, changing security-sensitive data + * (e.g. "uid", "analyzer", "executable") + */ + if (mkdir(m_sDebugDumpDir.c_str(), 0750) == -1) + { + UnLock(); + m_bOpened = false; + throw CABRTException(EXCEP_DD_OPEN, "Can't create dir '%s'", pDir); + } + + /* mkdir's mode (above) can be affected by umask, fix it */ + if (chmod(m_sDebugDumpDir.c_str(), 0750) == -1) + { + UnLock(); + m_bOpened = false; + throw CABRTException(EXCEP_DD_OPEN, "Can't change mode of '%s'", pDir); + } + + /* Get ABRT's user id */ + m_uid = 0; + struct passwd *pw = getpwnam("abrt"); + if (pw) + m_uid = pw->pw_uid; + else + error_msg("User 'abrt' does not exist, using uid 0"); + + /* Get crashed application's group id */ + m_gid = 0; + pw = getpwuid(uid); + if (pw) + m_gid = pw->pw_gid; + else + error_msg("User %lu does not exist, using gid 0", (long)uid); + + if (chown(m_sDebugDumpDir.c_str(), m_uid, m_gid) == -1) + { + perror_msg("can't change '%s' ownership to %lu:%lu", m_sDebugDumpDir.c_str(), + (long)m_uid, (long)m_gid); + } + + SaveText(CD_UID, to_string(uid).c_str()); + + { + struct utsname buf; + if (uname(&buf) != 0) + { + perror_msg_and_die("uname"); + } + SaveText(FILENAME_KERNEL, buf.release); + SaveText(FILENAME_ARCHITECTURE, buf.machine); + std::string release; + LoadTextFile("/etc/redhat-release", release); + const char *release_ptr = release.c_str(); + unsigned len_1st_str = strchrnul(release_ptr, '\n') - release_ptr; + release.erase(len_1st_str); /* usually simply removes trailing '\n' */ + SaveText(FILENAME_RELEASE, release.c_str()); + } + + time_t t = time(NULL); + SaveText(FILENAME_TIME, to_string(t).c_str()); +} + +static void DeleteFileDir(const char *pDir) +{ + DIR *dir = opendir(pDir); + if (!dir) + return; + + struct dirent *dent; + while ((dent = readdir(dir)) != NULL) + { + if (dot_or_dotdot(dent->d_name)) + continue; + std::string fullPath = concat_path_file(pDir, dent->d_name); + if (unlink(fullPath.c_str()) == -1) + { + if (errno != EISDIR) + { + closedir(dir); + throw CABRTException(EXCEP_DD_DELETE, "Can't remove dir %s", fullPath.c_str()); + } + DeleteFileDir(fullPath.c_str()); + } + } + closedir(dir); + if (rmdir(pDir) == -1) + { + throw CABRTException(EXCEP_DD_DELETE, "Can't remove dir %s", pDir); + } +} + +void CDebugDump::Delete() +{ + if (!ExistFileDir(m_sDebugDumpDir.c_str())) + { + return; + } + DeleteFileDir(m_sDebugDumpDir.c_str()); +} + +void CDebugDump::Close() +{ + UnLock(); + if (m_pGetNextFileDir != NULL) + { + closedir(m_pGetNextFileDir); + m_pGetNextFileDir = NULL; + } + m_bOpened = false; +} + +static void LoadTextFile(const char *pPath, std::string& pData) +{ + FILE *fp = fopen(pPath, "r"); + if (!fp) + { + throw CABRTException(EXCEP_DD_LOAD, "Can't open file '%s'", pPath); + } + pData = ""; + int ch; + while ((ch = fgetc(fp)) != EOF) + { + if (ch == '\0') + { + pData += ' '; + } + else if (isspace(ch) || (isascii(ch) && !iscntrl(ch))) + { + pData += ch; + } + } + fclose(fp); +} + +static void SaveBinaryFile(const char *pPath, const char* pData, unsigned pSize, uid_t uid, gid_t gid) +{ + /* "Why 0640?!" See ::Create() for security analysis */ + unlink(pPath); + int fd = open(pPath, O_WRONLY | O_TRUNC | O_CREAT, 0640); + if (fd < 0) + { + throw CABRTException(EXCEP_DD_SAVE, "Can't open file '%s': %s", pPath, errno ? strerror(errno) : "errno == 0"); + } + if (fchown(fd, uid, gid) == -1) + { + perror_msg("can't change '%s' ownership to %lu:%lu", pPath, (long)uid, (long)gid); + } + unsigned r = full_write(fd, pData, pSize); + close(fd); + if (r != pSize) + { + throw CABRTException(EXCEP_DD_SAVE, "Can't save file '%s'", pPath); + } +} + +void CDebugDump::LoadText(const char* pName, std::string& pData) +{ + if (!m_bOpened) + { + throw CABRTException(EXCEP_DD_OPEN, "DebugDump is not opened"); + } + std::string fullPath = concat_path_file(m_sDebugDumpDir.c_str(), pName); + LoadTextFile(fullPath.c_str(), pData); +} + +void CDebugDump::SaveText(const char* pName, const char* pData) +{ + if (!m_bOpened) + { + throw CABRTException(EXCEP_DD_OPEN, "DebugDump is not opened"); + } + std::string fullPath = concat_path_file(m_sDebugDumpDir.c_str(), pName); + SaveBinaryFile(fullPath.c_str(), pData, strlen(pData), m_uid, m_gid); +} + +void CDebugDump::SaveBinary(const char* pName, const char* pData, unsigned pSize) +{ + if (!m_bOpened) + { + throw CABRTException(EXCEP_DD_OPEN, "DebugDump is not opened"); + } + std::string fullPath = concat_path_file(m_sDebugDumpDir.c_str(), pName); + SaveBinaryFile(fullPath.c_str(), pData, pSize, m_uid, m_gid); +} + +void CDebugDump::InitGetNextFile() +{ + if (!m_bOpened) + { + throw CABRTException(EXCEP_DD_OPEN, "DebugDump is not opened"); + } + if (m_pGetNextFileDir != NULL) + { + closedir(m_pGetNextFileDir); + } + m_pGetNextFileDir = opendir(m_sDebugDumpDir.c_str()); + if (m_pGetNextFileDir == NULL) + { + throw CABRTException(EXCEP_DD_OPEN, "Can't open dir '%s'", m_sDebugDumpDir.c_str()); + } +} + +bool CDebugDump::GetNextFile(std::string *short_name, std::string *full_name) +{ + if (m_pGetNextFileDir == NULL) + { + return false; + } + + struct dirent *dent; + while ((dent = readdir(m_pGetNextFileDir)) != NULL) + { + if (is_regular_file(dent, m_sDebugDumpDir.c_str())) + { + if (short_name) + *short_name = dent->d_name; + if (full_name) + *full_name = concat_path_file(m_sDebugDumpDir.c_str(), dent->d_name); + return true; + } + } + closedir(m_pGetNextFileDir); + m_pGetNextFileDir = NULL; + return false; +} + +/* Utility function */ +void delete_debug_dump_dir(const char *pDebugDumpDir) +{ + try + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + dd.Delete(); + } + catch (CABRTException& e) + { + /* Ignoring "directory already deleted" and such */ + } +} diff --git a/lib/utils/Makefile.am b/lib/utils/Makefile.am new file mode 100644 index 00000000..04211a7c --- /dev/null +++ b/lib/utils/Makefile.am @@ -0,0 +1,86 @@ +# ABRTUtils has small set of deps. This reduces deps of smaller abrt binaries +# ABRTdUtils has much more. It is used by daemon and plugins only +lib_LTLIBRARIES = libABRTUtils.la libABRTdUtils.la +HEADER_DIR = $(srcdir)/../../inc +AM_CPPFLAGS = -I$(HEADER_DIR) +AM_YFLAGS = --verbose + +# Not used just yet: +# time.cpp +# xconnect.cpp + +libABRTUtils_la_SOURCES = \ + xfuncs.c \ + encbase64.cpp \ + read_write.c read_write.h \ + logging.c logging.h \ + copyfd.cpp \ + daemon.cpp \ + skip_whitespace.cpp \ + xatonum.cpp numtoa.cpp \ + spawn.cpp \ + stringops.cpp \ + dirsize.cpp \ + DebugDump.cpp \ + abrt_dbus.h abrt_dbus.cpp \ + CrashTypes.cpp \ + ABRTException.cpp \ + backtrace.h backtrace.c \ + backtrace_parser.y \ + strbuf.h strbuf.c \ + abrt_packages.c abrt_packages.h \ + hooklib.h hooklib.cpp +libABRTUtils_la_CPPFLAGS = \ + -Wall -Werror \ + -I$(srcdir)/../../inc \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) \ + -D_GNU_SOURCE +libABRTUtils_la_LDFLAGS = \ + -version-info 0:1:0 +libABRTUtils_la_LIBADD = \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) \ + -ldl + + +libABRTdUtils_la_SOURCES = \ + parse_release.cpp \ + make_descr.cpp \ + $(HEADER_DIR)/comm_layer_inner.h CommLayerInner.cpp \ + $(HEADER_DIR)/abrt_xmlrpc.h abrt_xmlrpc.cpp \ + abrt_rh_support.h abrt_rh_support.cpp \ + abrt_curl.h abrt_curl.cpp \ + $(HEADER_DIR)/plugin.h Plugin.cpp \ + Polkit.h Polkit.cpp + +libABRTdUtils_la_CPPFLAGS = \ + -Wall -Werror \ + -I$(srcdir)/../../inc \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + $(XMLRPC_CFLAGS) $(XMLRPC_CLIENT_CFLAGS) \ + $(POLKIT_CFLAGS) \ + $(LIBXML_CFLAGS) \ + -D_GNU_SOURCE +libABRTdUtils_la_LDFLAGS = \ + -version-info 0:1:0 +libABRTdUtils_la_LIBADD = \ + $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) \ + $(POLKIT_LIBS) \ + $(LIBXML_LIBS) \ + -ldl + +$(DESTDIR)/$(DEBUG_DUMPS_DIR): + $(mkdir_p) '$@' +# no need to chmod it here +#chmod 1777 '$@' +install-data-local: $(DESTDIR)/$(DEBUG_DUMPS_DIR) diff --git a/lib/utils/Plugin.cpp b/lib/utils/Plugin.cpp new file mode 100644 index 00000000..40fa39de --- /dev/null +++ b/lib/utils/Plugin.cpp @@ -0,0 +1,104 @@ +/* + Copyright (C) 2009 Zdenek Prikryl (zprikryl@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 "plugin.h" +#include "abrtlib.h" + +CPlugin::CPlugin() {} + +/* class CPlugin's virtuals */ +CPlugin::~CPlugin() {} +void CPlugin::Init() {} +void CPlugin::DeInit() {} +void CPlugin::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; +} + +const map_plugin_settings_t& CPlugin::GetSettings() +{ + return m_pSettings; +} + +bool LoadPluginSettings(const char *pPath, map_plugin_settings_t& pSettings, + bool skipKeysWithoutValue /*= true*/) +{ + FILE *fp = fopen(pPath, "r"); + if (!fp) + return false; + + char line[512]; + while (fgets(line, sizeof(line), fp)) + { + strchrnul(line, '\n')[0] = '\0'; + unsigned ii; + bool is_value = false; + bool valid = false; + bool in_quote = false; + std::string key; + std::string value; + for (ii = 0; line[ii] != '\0'; ii++) + { + if (line[ii] == '"') + { + in_quote = !in_quote; + } + if (isspace(line[ii]) && !in_quote) + { + continue; + } + if (line[ii] == '#' && !in_quote && key == "") + { + break; + } + if (line[ii] == '=' && !in_quote) + { + is_value = true; + valid = true; + continue; + } + if (!is_value) + { + key += line[ii]; + } + else + { + value += line[ii]; + } + } + + /* Skip broken or empty lines. */ + if (!valid) + continue; + + /* Skip lines with empty key. */ + if (key.length() == 0) + continue; + + if (skipKeysWithoutValue && value.length() == 0) + continue; + + /* Skip lines with unclosed quotes. */ + if (in_quote) + continue; + + pSettings[key] = value; + } + fclose(fp); + return true; +} diff --git a/lib/utils/Polkit.cpp b/lib/utils/Polkit.cpp new file mode 100644 index 00000000..a5e07760 --- /dev/null +++ b/lib/utils/Polkit.cpp @@ -0,0 +1,102 @@ +/* + Polkit.cpp - PolicyKit integration for ABRT + + Copyright (C) 2009 Daniel Novotny (dnovotny@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 <polkit/polkit.h> +#include <glib-object.h> +#include <sys/types.h> +#include <unistd.h> + +#include "Polkit.h" +#include "abrtlib.h" + +/*number of seconds: timeout for the authorization*/ +#define POLKIT_TIMEOUT 20 + +static gboolean do_cancel(GCancellable* cancellable) +{ + log("Timer has expired; cancelling authorization check\n"); + g_cancellable_cancel(cancellable); + return FALSE; +} + + +static PolkitResult do_check(PolkitSubject *subject, const char *action_id) +{ + PolkitAuthority *authority; + PolkitAuthorizationResult *result; + GError *error = NULL; + GCancellable * cancellable; + + authority = polkit_authority_get(); + cancellable = g_cancellable_new(); + + guint cancel_timeout = g_timeout_add(POLKIT_TIMEOUT * 1000, + (GSourceFunc) do_cancel, + cancellable); + + result = polkit_authority_check_authorization_sync(authority, + subject, + action_id, + NULL, + POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, + cancellable, + &error); + g_object_unref(authority); + g_source_remove(cancel_timeout); + if (error) + { + g_error_free(error); + return PolkitUnknown; + } + + if (result) + { + if (polkit_authorization_result_get_is_challenge(result)) + { + /* Can't happen (happens only with + * POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE flag) */ + g_object_unref(result); + return PolkitChallenge; + } + if (polkit_authorization_result_get_is_authorized(result)) + { + g_object_unref(result); + return PolkitYes; + } + g_object_unref(result); + return PolkitNo; + } + + return PolkitUnknown; +} + +PolkitResult polkit_check_authorization(const char *dbus_name, const char *action_id) +{ + g_type_init(); + PolkitSubject *subject = polkit_system_bus_name_new(dbus_name); + return do_check(subject, action_id); +} + +PolkitResult polkit_check_authorization(pid_t pid, const char *action_id) +{ + g_type_init(); + PolkitSubject *subject = polkit_unix_process_new(pid); + return do_check(subject, action_id); +} diff --git a/lib/utils/Polkit.h b/lib/utils/Polkit.h new file mode 100644 index 00000000..d9e097ac --- /dev/null +++ b/lib/utils/Polkit.h @@ -0,0 +1,42 @@ +/* + Polkit.h - header file for PolicyKit integration + + Copyright (C) 2009 Daniel Novotny (dnovotny@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. +*/ +#ifndef ABRT_POLKIT_H +#define ABRT_POLKIT_H + +#include <sys/types.h> +#include <unistd.h> + +typedef enum { +/* Authorization status is unknown */ + PolkitUnknown = 0x0, + /* Subject is authorized for the action */ + PolkitYes = 0x01, + /* Subject is not authorized for the action */ + PolkitNo = 0x02, + /* Challenge is needed for this action, only when flag is + * POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE */ + PolkitChallenge = 0x03 +} PolkitResult; + +PolkitResult polkit_check_authorization(const char *dbus_name, const char *action_id); +PolkitResult polkit_check_authorization(pid_t pid, const char *action_id); + +#endif diff --git a/lib/utils/abrt_curl.cpp b/lib/utils/abrt_curl.cpp new file mode 100644 index 00000000..af3defca --- /dev/null +++ b/lib/utils/abrt_curl.cpp @@ -0,0 +1,320 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 "abrtlib.h" +#include "abrt_curl.h" +#include "comm_layer_inner.h" + +using namespace std; + +/* + * Utility functions + */ +CURL* xcurl_easy_init() +{ + CURL* curl = curl_easy_init(); + if (!curl) + { + error_msg_and_die("Can't create curl handle"); + } + return curl; +} + +static char* +check_curl_error(CURLcode err, const char* msg) +{ + if (err) + return xasprintf("%s: %s", msg, curl_easy_strerror(err)); + return NULL; +} + +static void +die_if_curl_error(CURLcode err) +{ + if (err) { + char *msg = check_curl_error(err, "curl"); + error_msg_and_die("%s", msg); + } +} + +static void +xcurl_easy_setopt_ptr(CURL *handle, CURLoption option, const void *parameter) +{ + CURLcode err = curl_easy_setopt(handle, option, parameter); + if (err) { + char *msg = check_curl_error(err, "curl"); + error_msg_and_die("%s", msg); + } +} +static inline void +xcurl_easy_setopt_long(CURL *handle, CURLoption option, long parameter) +{ + xcurl_easy_setopt_ptr(handle, option, (void*)parameter); +} + +/* + * post_state utility functions + */ + +abrt_post_state_t *new_abrt_post_state(int flags) +{ + abrt_post_state_t *state = (abrt_post_state_t *)xzalloc(sizeof(*state)); + state->flags = flags; + return state; +} + +void free_abrt_post_state(abrt_post_state_t *state) +{ + char **headers = state->headers; + if (headers) + { + while (*headers) + free(*headers++); + free(state->headers); + } + free(state->curl_error_msg); + free(state->body); + free(state); +} + +char *find_header_in_abrt_post_state(abrt_post_state_t *state, const char *str) +{ + char **headers = state->headers; + if (headers) + { + unsigned len = strlen(str); + while (*headers) + { + if (strncmp(*headers, str, len) == 0) + return skip_whitespace(*headers + len); + headers++; + } + } + return NULL; +} + +/* + * abrt_post: perform HTTP POST transaction + */ + +/* "save headers" callback */ +static size_t +save_headers(void *buffer_pv, size_t count, size_t nmemb, void *ptr) +{ + abrt_post_state_t* state = (abrt_post_state_t*)ptr; + size_t size = count * nmemb; + + char *h = xstrndup((char*)buffer_pv, size); + strchrnul(h, '\r')[0] = '\0'; + strchrnul(h, '\n')[0] = '\0'; + + unsigned cnt = state->header_cnt; + + /* Check for the case when curl follows a redirect: + * header 0: 'HTTP/1.1 301 Moved Permanently' + * header 1: 'Connection: close' + * header 2: 'Location: NEW_URL' + * header 3: '' + * header 0: 'HTTP/1.1 200 OK' <-- we need to forget all hdrs and start anew + */ + if (cnt != 0 + && strncmp(h, "HTTP/", 5) == 0 + && state->headers[cnt-1][0] == '\0' /* prev header is an empty string */ + ) { + char **headers = state->headers; + if (headers) + { + while (*headers) + free(*headers++); + } + cnt = 0; + } + + VERB3 log("save_headers: header %d: '%s'", cnt, h); + state->headers = (char**)xrealloc(state->headers, (cnt+2) * sizeof(state->headers[0])); + state->headers[cnt] = h; + state->header_cnt = ++cnt; + state->headers[cnt] = NULL; + + return size; +} + +int +abrt_post(abrt_post_state_t *state, + const char *url, + const char *content_type, + const char *data, + off_t data_size) +{ + CURLcode curl_err; + long response_code; + abrt_post_state_t localstate; + + VERB3 log("abrt_post('%s','%s')", url, data); + + if (!state) + { + memset(&localstate, 0, sizeof(localstate)); + state = &localstate; + } + + state->http_resp_code = response_code = -1; + + CURL *handle = xcurl_easy_init(); + + // Buffer[CURL_ERROR_SIZE] curl stores human readable error messages in. + // This may be more helpful than just return code from curl_easy_perform. + // curl will need it until curl_easy_cleanup. + state->errmsg[0] = '\0'; + xcurl_easy_setopt_ptr(handle, CURLOPT_ERRORBUFFER, state->errmsg); + // "Display a lot of verbose information about its operations. + // Very useful for libcurl and/or protocol debugging and understanding. + // The verbose information will be sent to stderr, or the stream set + // with CURLOPT_STDERR" + //xcurl_easy_setopt_long(handle, CURLOPT_VERBOSE, 1); + // Shut off the built-in progress meter completely + xcurl_easy_setopt_long(handle, CURLOPT_NOPROGRESS, 1); + + // TODO: do we need to check for CURLE_URL_MALFORMAT error *here*, + // not in curl_easy_perform? + xcurl_easy_setopt_ptr(handle, CURLOPT_URL, url); + + // Auth if configured + if (state->username) { + // bitmask of allowed auth methods + xcurl_easy_setopt_long(handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + xcurl_easy_setopt_ptr(handle, CURLOPT_USERNAME, state->username); + xcurl_easy_setopt_ptr(handle, CURLOPT_PASSWORD, (state->password ? state->password : "")); + } + + // Do a regular HTTP post. This also makes curl use + // a "Content-Type: application/x-www-form-urlencoded" header. + // (This is by far the most commonly used POST method). + xcurl_easy_setopt_long(handle, CURLOPT_POST, 1); + // Supply POST data... + struct curl_httppost* post = NULL; + struct curl_httppost* last = NULL; + FILE* data_file = NULL; + if (data_size == ABRT_POST_DATA_FROMFILE) { + // ...from a file + data_file = fopen(data, "r"); + if (!data_file) +//FIXME: + perror_msg_and_die("can't open '%s'", data); + xcurl_easy_setopt_ptr(handle, CURLOPT_READDATA, data_file); + } else if (data_size == ABRT_POST_DATA_FROMFILE_AS_FORM_DATA) { + // ...from a file, in multipart/formdata format + CURLFORMcode curlform_err = curl_formadd(&post, &last, + CURLFORM_PTRNAME, "file", + CURLFORM_FILE, data, // filename to read from + CURLFORM_CONTENTTYPE, content_type, + CURLFORM_FILENAME, data, // filename to put in the form + CURLFORM_END); + if (curlform_err != 0) +//FIXME: + error_msg_and_die("out of memory or read error"); + xcurl_easy_setopt_ptr(handle, CURLOPT_HTTPPOST, post); + } else { + // .. from a blob in memory + xcurl_easy_setopt_ptr(handle, CURLOPT_POSTFIELDS, data); + // note1: if data_size == ABRT_POST_DATA_STRING == -1, curl will use strlen(data) + xcurl_easy_setopt_long(handle, CURLOPT_POSTFIELDSIZE, data_size); + // note2: CURLOPT_POSTFIELDSIZE_LARGE can't be used: xcurl_easy_setopt_long() + // truncates data_size on 32-bit arch. Need xcurl_easy_setopt_long_long()? + // Also, I'm not sure CURLOPT_POSTFIELDSIZE_LARGE special-cases -1. + } + // Override "Content-Type:" + struct curl_slist *httpheader_list = NULL; + if (data_size != ABRT_POST_DATA_FROMFILE_AS_FORM_DATA) + { + char *content_type_header = xasprintf("Content-Type: %s", content_type); + // Note: curl_slist_append() copies content_type_header + httpheader_list = curl_slist_append(httpheader_list, content_type_header); + if (!httpheader_list) + error_msg_and_die("out of memory"); + free(content_type_header); + xcurl_easy_setopt_ptr(handle, CURLOPT_HTTPHEADER, httpheader_list); + } + + // Please handle 301/302 redirects for me + xcurl_easy_setopt_long(handle, CURLOPT_FOLLOWLOCATION, 1); + xcurl_easy_setopt_long(handle, CURLOPT_MAXREDIRS, 10); + // Bitmask to control how libcurl acts on redirects after POSTs. + // Bit 0 set (value CURL_REDIR_POST_301) makes libcurl + // not convert POST requests into GET requests when following + // a 301 redirection. Bit 1 (value CURL_REDIR_POST_302) makes libcurl + // maintain the request method after a 302 redirect. + // CURL_REDIR_POST_ALL is a convenience define that sets both bits. + // The non-RFC behaviour is ubiquitous in web browsers, so the library + // does the conversion by default to maintain consistency. + // However, a server may require a POST to remain a POST. + //xcurl_easy_setopt_long(CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); + + // Prepare for saving information + if (state->flags & ABRT_POST_WANT_HEADERS) + { + xcurl_easy_setopt_ptr(handle, CURLOPT_HEADERFUNCTION, (void*)save_headers); + xcurl_easy_setopt_ptr(handle, CURLOPT_WRITEHEADER, state); + } + FILE* body_stream = NULL; + if (state->flags & ABRT_POST_WANT_BODY) + { + body_stream = open_memstream(&state->body, &state->body_size); + if (!body_stream) + error_msg_and_die("out of memory"); + xcurl_easy_setopt_ptr(handle, CURLOPT_WRITEDATA, body_stream); + } + if (!(state->flags & ABRT_POST_WANT_SSL_VERIFY)) + { + xcurl_easy_setopt_long(handle, CURLOPT_SSL_VERIFYPEER, 0); + xcurl_easy_setopt_long(handle, CURLOPT_SSL_VERIFYHOST, 0); + } + + // This is the place where everything happens. + // Here errors are not limited to "out of memory", can't just die. + curl_err = curl_easy_perform(handle); + if (curl_err) + { + VERB2 log("curl_easy_perform: error %d", (int)curl_err); + if (state->flags & ABRT_POST_WANT_ERROR_MSG) + { + state->curl_error_msg = check_curl_error(curl_err, "curl_easy_perform"); + VERB3 log("curl_easy_perform: error_msg: %s", state->curl_error_msg); + } + goto ret; + } + + // Headers/body are already saved (if requested), extract more info + curl_err = curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code); + die_if_curl_error(curl_err); + state->http_resp_code = response_code; + VERB3 log("after curl_easy_perform: http code %ld body:'%s'", response_code, state->body); + + ret: + curl_easy_cleanup(handle); + if (httpheader_list) + curl_slist_free_all(httpheader_list); + if (body_stream) + fclose(body_stream); + if (data_file) + fclose(data_file); + if (post) + curl_formfree(post); + + return response_code; +} diff --git a/lib/utils/abrt_curl.h b/lib/utils/abrt_curl.h new file mode 100644 index 00000000..1f34e7ec --- /dev/null +++ b/lib/utils/abrt_curl.h @@ -0,0 +1,88 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ +#ifndef ABRT_CURL_H_ +#define ABRT_CURL_H_ + +#include <curl/curl.h> + +CURL* xcurl_easy_init(); + +typedef struct abrt_post_state { + /* Supplied by caller: */ + int flags; + const char *username; + const char *password; + /* Results of POST transaction: */ + int http_resp_code; + unsigned header_cnt; + char **headers; + char *curl_error_msg; + char *body; + size_t body_size; + char errmsg[CURL_ERROR_SIZE]; +} abrt_post_state_t; + +abrt_post_state_t *new_abrt_post_state(int flags); +void free_abrt_post_state(abrt_post_state_t *state); +char *find_header_in_abrt_post_state(abrt_post_state_t *state, const char *str); + +enum { + ABRT_POST_WANT_HEADERS = (1 << 0), + ABRT_POST_WANT_ERROR_MSG = (1 << 1), + ABRT_POST_WANT_BODY = (1 << 2), + ABRT_POST_WANT_SSL_VERIFY = (1 << 3), +}; +enum { + /* Must be -1! CURLOPT_POSTFIELDSIZE interprets -1 as "use strlen" */ + ABRT_POST_DATA_STRING = -1, + ABRT_POST_DATA_FROMFILE = -2, + ABRT_POST_DATA_FROMFILE_AS_FORM_DATA = -3, +}; +int +abrt_post(abrt_post_state_t *state, + const char *url, + const char *content_type, + const char *data, + off_t data_size); +static inline int +abrt_post_string(abrt_post_state_t *state, + const char *url, + const char *content_type, + const char *str) +{ + return abrt_post(state, url, content_type, str, ABRT_POST_DATA_STRING); +} +static inline int +abrt_post_file(abrt_post_state_t *state, + const char *url, + const char *content_type, + const char *filename) +{ + return abrt_post(state, url, content_type, filename, ABRT_POST_DATA_FROMFILE); +} +static inline int +abrt_post_file_as_form(abrt_post_state_t *state, + const char *url, + const char *content_type, + const char *filename) +{ + return abrt_post(state, url, content_type, filename, ABRT_POST_DATA_FROMFILE_AS_FORM_DATA); +} + +#endif diff --git a/lib/utils/abrt_dbus.cpp b/lib/utils/abrt_dbus.cpp new file mode 100644 index 00000000..4319d103 --- /dev/null +++ b/lib/utils/abrt_dbus.cpp @@ -0,0 +1,430 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 <dbus/dbus.h> +#include <glib.h> +#include "abrtlib.h" +#include "abrt_dbus.h" + +DBusConnection* g_dbus_conn; + + +/* + * Helpers for building DBus messages + */ + +//void store_bool(DBusMessageIter* iter, bool val) +//{ +// dbus_bool_t db = val; +// if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &db)) +// die_out_of_memory(); +//} +void store_int32(DBusMessageIter* iter, int32_t val) +{ + if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &val)) + die_out_of_memory(); +} +void store_uint32(DBusMessageIter* iter, uint32_t val) +{ + if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &val)) + die_out_of_memory(); +} +void store_int64(DBusMessageIter* iter, int64_t val) +{ + if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_INT64, &val)) + die_out_of_memory(); +} +void store_uint64(DBusMessageIter* iter, uint64_t val) +{ + if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &val)) + die_out_of_memory(); +} + +/* dbus daemon will simply close our connection if we send broken utf8. + * Therefore we must never do that. + */ +static char *sanitize_utf8(const char *src) +{ + const char *initial_src = src; + char *sanitized = NULL; + unsigned sanitized_pos = 0; + + while (*src) + { + int bytes = 0; + + unsigned c = (unsigned char) *src; + if (c <= 0x7f) + { + bytes = 1; + goto good_byte; + } + + /* Unicode -> utf8: */ + /* 80-7FF -> 110yyyxx 10xxxxxx */ + /* 800-FFFF -> 1110yyyy 10yyyyxx 10xxxxxx */ + /* 10000-1FFFFF -> 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx */ + /* 200000-3FFFFFF -> 111110tt 10zzzzzz 10zzyyyy 10yyyyxx 10xxxxxx */ + /* 4000000-FFFFFFFF -> 111111tt 10tttttt 10zzzzzz 10zzyyyy 10yyyyxx 10xxxxxx */ + do { + c <<= 1; + bytes++; + } while ((c & 0x80) && bytes < 6); + if (bytes == 1) + { + /* A bare "continuation" byte. Say, 80 */ + goto bad_byte; + } + + c = (uint8_t)(c) >> bytes; + { + const char *pp = src; + int cnt = bytes; + while (--cnt) + { + unsigned ch = (unsigned char) *++pp; + if ((ch & 0xc0) != 0x80) /* Missing "continuation" byte. Example: e0 80 */ + { + goto bad_byte; + } + c = (c << 6) + (ch & 0x3f); + } + } + /* TODO */ + /* Need to check that c isn't produced by overlong encoding */ + /* Example: 11000000 10000000 converts to NUL */ + /* 11110000 10000000 10000100 10000000 converts to 0x100 */ + /* correct encoding: 11000100 10000000 */ + if (c <= 0x7f) /* crude check: only catches bad encodings which map to chars <= 7f */ + { + goto bad_byte; + } + + good_byte: + while (--bytes >= 0) + { + c = (unsigned char) *src++; + if (sanitized) + { + sanitized = (char*) xrealloc(sanitized, sanitized_pos + 2); + sanitized[sanitized_pos++] = c; + sanitized[sanitized_pos] = '\0'; + } + } + continue; + + bad_byte: + if (!sanitized) + { + sanitized_pos = src - initial_src; + sanitized = xstrndup(initial_src, sanitized_pos); + } + sanitized = (char*) xrealloc(sanitized, sanitized_pos + 5); + sanitized[sanitized_pos++] = '['; + c = (unsigned char) *src++; + sanitized[sanitized_pos++] = "0123456789ABCDEF"[c >> 4]; + sanitized[sanitized_pos++] = "0123456789ABCDEF"[c & 0xf]; + sanitized[sanitized_pos++] = ']'; + sanitized[sanitized_pos] = '\0'; + } + + if (sanitized) + VERB2 log("note: bad utf8, converted '%s' -> '%s'", initial_src, sanitized); + + return sanitized; /* usually NULL: the whole string is ok */ +} +void store_string(DBusMessageIter* iter, const char* val) +{ + const char *sanitized = sanitize_utf8(val); + if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, sanitized ? &sanitized : &val)) + die_out_of_memory(); + free((char*)sanitized); +} + + +/* + * Helpers for parsing DBus messages + */ + +//int load_bool(DBusMessageIter* iter, bool& val) +//{ +// int type = dbus_message_iter_get_arg_type(iter); +// if (type != DBUS_TYPE_BOOLEAN) +// error_msg_and_die("%s expected in dbus message, but not found ('%c')", "bool", type); +// dbus_bool_t db; +// dbus_message_iter_get_basic(iter, &db); +// val = db; +// return dbus_message_iter_next(iter); +//} +int load_int32(DBusMessageIter* iter, int32_t& val) +{ + int type = dbus_message_iter_get_arg_type(iter); + if (type != DBUS_TYPE_INT32) + { + error_msg("%s expected in dbus message, but not found ('%c')", "int32", type); + return -1; + } + dbus_message_iter_get_basic(iter, &val); + return dbus_message_iter_next(iter); +} +int load_uint32(DBusMessageIter* iter, uint32_t& val) +{ + int type = dbus_message_iter_get_arg_type(iter); + if (type != DBUS_TYPE_UINT32) + { + error_msg("%s expected in dbus message, but not found ('%c')", "uint32", type); + return -1; + } + dbus_message_iter_get_basic(iter, &val); + return dbus_message_iter_next(iter); +} +int load_int64(DBusMessageIter* iter, int64_t& val) +{ + int type = dbus_message_iter_get_arg_type(iter); + if (type != DBUS_TYPE_INT64) + { + error_msg("%s expected in dbus message, but not found ('%c')", "int64", type); + return -1; + } + dbus_message_iter_get_basic(iter, &val); + return dbus_message_iter_next(iter); +} +int load_uint64(DBusMessageIter* iter, uint64_t& val) +{ + int type = dbus_message_iter_get_arg_type(iter); + if (type != DBUS_TYPE_UINT64) + { + error_msg("%s expected in dbus message, but not found ('%c')", "uint64", type); + return -1; + } + dbus_message_iter_get_basic(iter, &val); + return dbus_message_iter_next(iter); +} +int load_charp(DBusMessageIter* iter, const char*& val) +{ + int type = dbus_message_iter_get_arg_type(iter); + if (type != DBUS_TYPE_STRING) + { + error_msg("%s expected in dbus message, but not found ('%c')", "string", type); + return -1; + } + dbus_message_iter_get_basic(iter, &val); +//log("load_charp:'%s'", val); + return dbus_message_iter_next(iter); +} + + +/* + * Glib integration machinery + */ + +/* Callback: "glib says dbus fd is active" */ +static gboolean handle_dbus_fd(GIOChannel *gio, GIOCondition condition, gpointer data) +{ + DBusWatch *watch = (DBusWatch*)data; + + VERB3 log("%s(gio, condition:%x [bits:IN/PRI/OUT/ERR/HUP...], data)", __func__, int(condition)); + + /* Notify the D-Bus library when a previously-added watch + * is ready for reading or writing, or has an exception such as a hangup. + */ + int glib_flags = int(condition); + int dbus_flags = 0; + if (glib_flags & G_IO_IN) dbus_flags |= DBUS_WATCH_READABLE; + if (glib_flags & G_IO_OUT) dbus_flags |= DBUS_WATCH_WRITABLE; + if (glib_flags & G_IO_ERR) dbus_flags |= DBUS_WATCH_ERROR; + if (glib_flags & G_IO_HUP) dbus_flags |= DBUS_WATCH_HANGUP; + /* + * TODO: + * If dbus_watch_handle returns FALSE, then the file descriptor + * may still be ready for reading or writing, but more memory + * is needed in order to do the reading or writing. If you ignore + * the FALSE return, your application may spin in a busy loop + * on the file descriptor until memory becomes available, + * but nothing more catastrophic should happen. + */ + dbus_watch_handle(watch, dbus_flags); + + while (dbus_connection_dispatch(g_dbus_conn) == DBUS_DISPATCH_DATA_REMAINS) + VERB3 log("%s: more data to process, looping", __func__); + return TRUE; /* "glib, do not remove this event source!" */ +} + +struct watch_app_info_t +{ + GIOChannel *channel; + guint event_source_id; + bool watch_enabled; +}; +/* Callback: "dbus_watch_get_enabled() may return a different value than it did before" */ +static void toggled_watch(DBusWatch *watch, void* data) +{ + VERB3 log("%s(watch:%p, data)", __func__, watch); + + watch_app_info_t* app_info = (watch_app_info_t*)dbus_watch_get_data(watch); + if (dbus_watch_get_enabled(watch)) + { + if (!app_info->watch_enabled) + { + app_info->watch_enabled = true; + int dbus_flags = dbus_watch_get_flags(watch); + int glib_flags = 0; + if (dbus_flags & DBUS_WATCH_READABLE) glib_flags |= G_IO_IN; + if (dbus_flags & DBUS_WATCH_WRITABLE) glib_flags |= G_IO_OUT; + VERB3 log(" adding watch to glib main loop. dbus_flags:%x glib_flags:%x", dbus_flags, glib_flags); + app_info->event_source_id = g_io_add_watch(app_info->channel, GIOCondition(glib_flags), handle_dbus_fd, watch); + } + /* else: it was already enabled */ + } else { + if (app_info->watch_enabled) + { + app_info->watch_enabled = false; + /* does it free the hidden GSource too? */ + VERB3 log(" removing watch from glib main loop"); + g_source_remove(app_info->event_source_id); + } + /* else: it was already disabled */ + } +} +/* Callback: "libdbus needs a new watch to be monitored by the main loop" */ +static dbus_bool_t add_watch(DBusWatch *watch, void* data) +{ + VERB3 log("%s(watch:%p, data)", __func__, watch); + + watch_app_info_t* app_info = (watch_app_info_t*)xzalloc(sizeof(*app_info)); + dbus_watch_set_data(watch, app_info, free); + + int fd = dbus_watch_get_unix_fd(watch); + VERB3 log(" dbus_watch_get_unix_fd():%d", fd); + app_info->channel = g_io_channel_unix_new(fd); + /* _unconditionally_ adding it to event loop would be an error */ + toggled_watch(watch, data); + return TRUE; +} +/* Callback: "libdbus no longer needs a watch to be monitored by the main loop" */ +static void remove_watch(DBusWatch *watch, void* data) +{ + VERB3 log("%s()", __func__); + watch_app_info_t* app_info = (watch_app_info_t*)dbus_watch_get_data(watch); + if (app_info->watch_enabled) + { + app_info->watch_enabled = false; + g_source_remove(app_info->event_source_id); + } + g_io_channel_unref(app_info->channel); +} + +/* Callback: "libdbus needs a new timeout to be monitored by the main loop" */ +static dbus_bool_t add_timeout(DBusTimeout *timeout, void* data) +{ + VERB3 log("%s()", __func__); + return TRUE; +} +/* Callback: "libdbus no longer needs a timeout to be monitored by the main loop" */ +static void remove_timeout(DBusTimeout *timeout, void* data) +{ + VERB3 log("%s()", __func__); +} +/* Callback: "dbus_timeout_get_enabled() may return a different value than it did before" */ +static void timeout_toggled(DBusTimeout *timeout, void* data) +{ +//seems to be never called, let's make it noisy + error_msg_and_die("%s(): FIXME: some dbus machinery is missing here", __func__); +} + +/* Callback: "DBusObjectPathVTable is unregistered (or its connection is freed)" */ +static void unregister_vtable(DBusConnection *conn, void* data) +{ + VERB3 log("%s()", __func__); +} + +/* + * Initialization works as follows: + * + * we have a DBusConnection* (say, obtained with dbus_bus_get) + * we call dbus_connection_set_watch_functions + * libdbus calls back add_watch(watch:0x2341090, data), this watch is for writing + * we call toggled_watch, but it finds that watch is not to be enabled yet + * libdbus calls back add_watch(watch:0x23410e0, data), this watch is for reading + * we call toggled_watch, it adds watch's fd to glib main loop with POLLIN + * (note: these watches are different objects, but they have the same fd) + * we call dbus_connection_set_timeout_functions + * we call dbus_connection_register_object_path + * + * Note: if user will later call dbus_bus_request_name(conn, ...): + * libdbus calls back add_timeout() + * libdbus calls back remove_timeout() + * note - no callback to timeout_toggled()! + * (therefore there is no code yet in timeout_toggled (see above), it's not used) + */ +void attach_dbus_conn_to_glib_main_loop(DBusConnection* conn, + const char* object_path, + DBusHandlerResult (*message_received_func)(DBusConnection *conn, DBusMessage *msg, void* data) +) { + if (g_dbus_conn) + error_msg_and_die("Internal bug: can't connect to more than one dbus"); + g_dbus_conn = conn; + +//do we need this? why? +//log("dbus_connection_set_dispatch_status_function"); +// dbus_connection_set_dispatch_status_function(conn, +// dispatch, /* void dispatch(DBusConnection *conn, DBusDispatchStatus new_status, void* data) */ +// NULL, /* data */ +// NULL /* free_data_function */ +// ) + VERB3 log("dbus_connection_set_watch_functions"); + if (!dbus_connection_set_watch_functions(conn, + add_watch, + remove_watch, + toggled_watch, + NULL, /* data */ + NULL /* free_data_function */ + ) + ) { + die_out_of_memory(); + } + VERB3 log("dbus_connection_set_timeout_functions"); + if (!dbus_connection_set_timeout_functions(conn, + add_timeout, + remove_timeout, + timeout_toggled, + NULL, /* data */ + NULL /* free_data_function */ + ) + ) { + die_out_of_memory(); + } + + if (object_path && message_received_func) + { + /* Table */ + const DBusObjectPathVTable vtable = { + /* .unregister_function = */ unregister_vtable, + /* .message_function = */ message_received_func, + }; + VERB3 log("dbus_connection_register_object_path"); + if (!dbus_connection_register_object_path(conn, + object_path, + &vtable, + NULL /* data */ + ) + ) { + die_out_of_memory(); + } + } +} diff --git a/lib/utils/abrt_dbus.h b/lib/utils/abrt_dbus.h new file mode 100644 index 00000000..58a8d912 --- /dev/null +++ b/lib/utils/abrt_dbus.h @@ -0,0 +1,302 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ +#ifndef ABRT_UTIL_DBUS_H +#define ABRT_UTIL_DBUS_H + +#include <dbus/dbus.h> +#include <map> +#include <vector> +#include "abrtlib.h" + +extern DBusConnection* g_dbus_conn; + +/* + * Glib integration machinery + */ + +/* Hook up to DBus and to glib main loop. + * Usage cases: + * + * - server: + * conn = dbus_bus_get(DBUS_BUS_SYSTEM/SESSION, &err); + * attach_dbus_conn_to_glib_main_loop(conn, "/some/path", handler_of_calls_to_some_path); + * rc = dbus_bus_request_name(conn, "server.name", DBUS_NAME_FLAG_REPLACE_EXISTING, &err); + * + * - client which does not receive signals (only makes calls and emits signals): + * conn = dbus_bus_get(DBUS_BUS_SYSTEM/SESSION, &err); + * // needed only if you need to use async dbus calls (not shown below): + * attach_dbus_conn_to_glib_main_loop(conn); + * // syncronous method call: + * msg = dbus_message_new_method_call("some.serv", "/path/on/serv", "optional.iface.on.serv", "method_name"); + * reply = dbus_connection_send_with_reply_and_block(conn, msg, timeout, &err); + * // emitting signal: + * msg = dbus_message_new_signal("/path/sig/emitted/from", "iface.sig.emitted.from", "sig_name"); + * // (note: "iface.sig.emitted.from" is not optional for signals!) + * dbus_message_set_destination(msg, "peer"); // optional + * dbus_connection_send(conn, msg, &serial); // &serial can be NULL + * + * - client which receives and processes signals: + * conn = dbus_bus_get(DBUS_BUS_SYSTEM/SESSION, &err); + * attach_dbus_conn_to_glib_main_loop(conn); + * dbus_connection_add_filter(conn, handle_message, NULL, NULL) + * dbus_bus_add_match(system_conn, "type='signal',...", &err); + * // signal is a dbus message which looks like this: + * // sender=XXX dest=YYY(or null) path=/path/sig/emitted/from interface=iface.sig.emitted.from member=sig_name + * // and handler_for_signals(conn,msg,opaque) will be called by glib + * // main loop to process received signals (and other messages + * // if you ask for them in dbus_bus_add_match[es], but this + * // would turn you into a server if you handle them too) ;] + */ +void attach_dbus_conn_to_glib_main_loop(DBusConnection* conn, + /* NULL if you are just a client */ + const char* object_path_to_register = NULL, + /* makes sense only if you use object_path_to_register: */ + DBusHandlerResult (*message_received_func)(DBusConnection *conn, DBusMessage *msg, void* data) = NULL +); + + +/* + * Helpers for building DBus messages + */ + +//void store_bool(DBusMessageIter* iter, bool val); +void store_int32(DBusMessageIter* iter, int32_t val); +void store_uint32(DBusMessageIter* iter, uint32_t val); +void store_int64(DBusMessageIter* iter, int64_t val); +void store_uint64(DBusMessageIter* iter, uint64_t val); +void store_string(DBusMessageIter* iter, const char* val); + +//static inline void store_val(DBusMessageIter* iter, bool val) { store_bool(iter, val); } +static inline void store_val(DBusMessageIter* iter, int32_t val) { store_int32(iter, val); } +static inline void store_val(DBusMessageIter* iter, uint32_t val) { store_uint32(iter, val); } +static inline void store_val(DBusMessageIter* iter, int64_t val) { store_int64(iter, val); } +static inline void store_val(DBusMessageIter* iter, uint64_t val) { store_uint64(iter, val); } +static inline void store_val(DBusMessageIter* iter, const char* val) { store_string(iter, val); } +static inline void store_val(DBusMessageIter* iter, const std::string& val) { store_string(iter, val.c_str()); } + +/* Templates for vector and map */ +template <typename T> struct abrt_dbus_type {}; +//template <> struct abrt_dbus_type<bool> { static const char* csig() { return "b"; } }; +template <> struct abrt_dbus_type<int32_t> { static const char* csig() { return "i"; } static std::string sig(); }; +template <> struct abrt_dbus_type<uint32_t> { static const char* csig() { return "u"; } static std::string sig(); }; +template <> struct abrt_dbus_type<int64_t> { static const char* csig() { return "x"; } static std::string sig(); }; +template <> struct abrt_dbus_type<uint64_t> { static const char* csig() { return "t"; } static std::string sig(); }; +template <> struct abrt_dbus_type<std::string> { static const char* csig() { return "s"; } static std::string sig(); }; +#define ABRT_DBUS_SIG(T) (abrt_dbus_type<T>::csig() ? abrt_dbus_type<T>::csig() : abrt_dbus_type<T>::sig().c_str()) +template <typename E> +struct abrt_dbus_type< std::vector<E> > { + static const char* csig() { return NULL; } + static std::string sig() { return ssprintf("a%s", ABRT_DBUS_SIG(E)); } +}; +template <typename K, typename V> +struct abrt_dbus_type< std::map<K,V> > { + static const char* csig() { return NULL; } + static std::string sig() { return ssprintf("a{%s%s}", ABRT_DBUS_SIG(K), ABRT_DBUS_SIG(V)); } +}; + +template<typename E> +static void store_vector(DBusMessageIter* iter, const std::vector<E>& val) +{ + DBusMessageIter sub_iter; + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, ABRT_DBUS_SIG(E), &sub_iter)) + die_out_of_memory(); + + typename std::vector<E>::const_iterator vit = val.begin(); + for (; vit != val.end(); ++vit) + { + store_val(&sub_iter, *vit); + } + + if (!dbus_message_iter_close_container(iter, &sub_iter)) + die_out_of_memory(); +} +/* +template<> +static void store_vector(DBus::MessageIter &iter, const std::vector<uint8_t>& val) +{ + if we use such vector, MUST add specialized code here (see in dbus-c++ source) +} +*/ +template<typename K, typename V> +static void store_map(DBusMessageIter* iter, const std::map<K,V>& val) +{ + DBusMessageIter sub_iter; + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + ssprintf("{%s%s}", ABRT_DBUS_SIG(K), ABRT_DBUS_SIG(V)).c_str(), + &sub_iter)) + die_out_of_memory(); + + typename std::map<K,V>::const_iterator mit = val.begin(); + for (; mit != val.end(); ++mit) + { + DBusMessageIter sub_sub_iter; + if (!dbus_message_iter_open_container(&sub_iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub_sub_iter)) + die_out_of_memory(); + store_val(&sub_sub_iter, mit->first); + store_val(&sub_sub_iter, mit->second); + if (!dbus_message_iter_close_container(&sub_iter, &sub_sub_iter)) + die_out_of_memory(); + } + + if (!dbus_message_iter_close_container(iter, &sub_iter)) + die_out_of_memory(); +} + +template<typename E> +static inline void store_val(DBusMessageIter* iter, const std::vector<E>& val) { store_vector(iter, val); } +template<typename K, typename V> +static inline void store_val(DBusMessageIter* iter, const std::map<K,V>& val) { store_map(iter, val); } + + +/* + * Helpers for parsing DBus messages + */ + +enum { + ABRT_DBUS_ERROR = -1, + ABRT_DBUS_LAST_FIELD = 0, + ABRT_DBUS_MORE_FIELDS = 1, + /* note that dbus_message_iter_next() returns FALSE on last field + * and TRUE if there are more fields. + * It maps exactly on the above constants. */ +}; +/* Checks type, loads data, advances to the next arg. + * Returns TRUE if next arg exists. + */ +//int load_bool(DBusMessageIter* iter, bool& val); +int load_int32(DBusMessageIter* iter, int32_t &val); +int load_uint32(DBusMessageIter* iter, uint32_t &val); +int load_int64(DBusMessageIter* iter, int64_t &val); +int load_uint64(DBusMessageIter* iter, uint64_t &val); +int load_charp(DBusMessageIter* iter, const char*& val); +//static inline int load_val(DBusMessageIter* iter, bool &val) { return load_bool(iter, val); } +static inline int load_val(DBusMessageIter* iter, int32_t &val) { return load_int32(iter, val); } +static inline int load_val(DBusMessageIter* iter, uint32_t &val) { return load_uint32(iter, val); } +static inline int load_val(DBusMessageIter* iter, int64_t &val) { return load_int64(iter, val); } +static inline int load_val(DBusMessageIter* iter, uint64_t &val) { return load_uint64(iter, val); } +static inline int load_val(DBusMessageIter* iter, const char*& val) { return load_charp(iter, val); } +static inline int load_val(DBusMessageIter* iter, std::string& val) +{ + const char* str; + int r = load_charp(iter, str); + val = str; + return r; +} + +/* Templates for vector and map */ +template<typename E> +static int load_vector(DBusMessageIter* iter, std::vector<E>& val) +{ + int type = dbus_message_iter_get_arg_type(iter); + if (type != DBUS_TYPE_ARRAY) + { + error_msg("array expected in dbus message, but not found ('%c')", type); + return -1; + } + + DBusMessageIter sub_iter; + dbus_message_iter_recurse(iter, &sub_iter); + + int r; +//int cnt = 0; + /* When the vector has 0 elements, we see DBUS_TYPE_INVALID here */ + type = dbus_message_iter_get_arg_type(&sub_iter); + if (type != DBUS_TYPE_INVALID) + { + do { + E elem; +//cnt++; + r = load_val(&sub_iter, elem); + if (r < 0) + return r; + val.push_back(elem); + } while (r == ABRT_DBUS_MORE_FIELDS); + } +//log("%s: %d elems", __func__, cnt); + + return dbus_message_iter_next(iter); +} +/* +template<> +static int load_vector(DBusMessageIter* iter, std::vector<uint8_t>& val) +{ + if we use such vector, MUST add specialized code here (see in dbus-c++ source) +} +*/ +template<typename K, typename V> +static int load_map(DBusMessageIter* iter, std::map<K,V>& val) +{ + int type = dbus_message_iter_get_arg_type(iter); + if (type != DBUS_TYPE_ARRAY) + { + error_msg("array expected in dbus message, but not found ('%c')", type); + return -1; + } + + DBusMessageIter sub_iter; + dbus_message_iter_recurse(iter, &sub_iter); + + bool next_exists; + int r; +//int cnt = 0; + do { + type = dbus_message_iter_get_arg_type(&sub_iter); + if (type != DBUS_TYPE_DICT_ENTRY) + { + /* When the map has 0 elements, we see DBUS_TYPE_INVALID (on the first iteration) */ + if (type == DBUS_TYPE_INVALID) + break; + error_msg("sub_iter type is not DBUS_TYPE_DICT_ENTRY (%c)!", type); + return -1; + } + + DBusMessageIter sub_sub_iter; + dbus_message_iter_recurse(&sub_iter, &sub_sub_iter); + + K key; + r = load_val(&sub_sub_iter, key); + if (r != ABRT_DBUS_MORE_FIELDS) + { + if (r == ABRT_DBUS_LAST_FIELD) + error_msg("malformed map element in dbus message"); + return -1; + } + V value; + r = load_val(&sub_sub_iter, value); + if (r != ABRT_DBUS_LAST_FIELD) + { + if (r == ABRT_DBUS_MORE_FIELDS) + error_msg("malformed map element in dbus message"); + return -1; + } + val[key] = value; +//cnt++; + next_exists = dbus_message_iter_next(&sub_iter); + } while (next_exists); +//log("%s: %d elems", __func__, cnt); + + return dbus_message_iter_next(iter); +} + +template<typename E> +static inline int load_val(DBusMessageIter* iter, std::vector<E>& val) { return load_vector(iter, val); } +template<typename K, typename V> +static inline int load_val(DBusMessageIter* iter, std::map<K,V>& val) { return load_map(iter, val); } + +#endif diff --git a/lib/utils/abrt_packages.c b/lib/utils/abrt_packages.c new file mode 100644 index 00000000..d9ffe44a --- /dev/null +++ b/lib/utils/abrt_packages.c @@ -0,0 +1,44 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 "abrt_packages.h" + +/* cuts the name from the NVR format: foo-1.2.3-1.el6 + returns a newly allocated string +*/ +char* get_package_name_from_NVR_or_NULL(const char* packageNVR) +{ + char* package_name = NULL; + if (packageNVR != NULL) + { + VERB1 log("packageNVR %s", packageNVR); + package_name = xstrdup(packageNVR); + char *pos = strrchr(package_name, '-'); + if (pos != NULL) + { + *pos = 0; + pos = strrchr(package_name, '-'); + if (pos != NULL) + { + *pos = 0; + } + } + } + return package_name; +} diff --git a/lib/utils/abrt_packages.h b/lib/utils/abrt_packages.h new file mode 100644 index 00000000..e6209d81 --- /dev/null +++ b/lib/utils/abrt_packages.h @@ -0,0 +1,35 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ + +#ifndef ABRT_PACKAGES_H +#define ABRT_PACKAGES_H + +#include "xfuncs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +char* get_package_name_from_NVR_or_NULL(const char* packageNVR); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/utils/abrt_rh_support.cpp b/lib/utils/abrt_rh_support.cpp new file mode 100644 index 00000000..7e804f9f --- /dev/null +++ b/lib/utils/abrt_rh_support.cpp @@ -0,0 +1,513 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ +//#define _GNU_SOURCE + +#include <libxml/encoding.h> +#include <libxml/xmlwriter.h> +#include <curl/curl.h> +#include "abrtlib.h" +#include "abrt_curl.h" +#include "abrt_xmlrpc.h" +#include "abrt_exception.h" +#include "abrt_rh_support.h" + +using namespace std; + +struct reportfile { + xmlTextWriterPtr writer; + xmlBufferPtr buf; +}; + +static void __attribute__((__noreturn__)) +die_xml_oom(void) +{ + error_msg_and_die("can't create XML attribute (out of memory?)"); +} + +static xmlBufferPtr +xxmlBufferCreate(void) +{ + xmlBufferPtr r = xmlBufferCreate(); + if (!r) + die_xml_oom(); + return r; +} + +static xmlTextWriterPtr +xxmlNewTextWriterMemory(xmlBufferPtr buf /*, int compression*/) +{ + xmlTextWriterPtr r = xmlNewTextWriterMemory(buf, /*compression:*/ 0); + if (!r) + die_xml_oom(); + return r; +} + +static void +xxmlTextWriterStartDocument(xmlTextWriterPtr writer, + const char * version, + const char * encoding, + const char * standalone) +{ + if (xmlTextWriterStartDocument(writer, version, encoding, standalone) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterEndDocument(xmlTextWriterPtr writer) +{ + if (xmlTextWriterEndDocument(writer) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterStartElement(xmlTextWriterPtr writer, const char *name) +{ + // these bright guys REDEFINED CHAR (!) to unsigned char... + if (xmlTextWriterStartElement(writer, (unsigned char*)name) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterEndElement(xmlTextWriterPtr writer) +{ + if (xmlTextWriterEndElement(writer) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterWriteElement(xmlTextWriterPtr writer, const char *name, const char *content) +{ + if (xmlTextWriterWriteElement(writer, (unsigned char*)name, (unsigned char*)content) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterWriteAttribute(xmlTextWriterPtr writer, const char *name, const char *content) +{ + if (xmlTextWriterWriteAttribute(writer, (unsigned char*)name, (unsigned char*)content) < 0) + die_xml_oom(); +} + +#if 0 //unused +static void +xxmlTextWriterWriteString(xmlTextWriterPtr writer, const char *content) +{ + if (xmlTextWriterWriteString(writer, (unsigned char*)content) < 0) + die_xml_oom(); +} +#endif + +// +// End the reportfile, and prepare it for delivery. +// No more bindings can be added after this. +// +static void +close_writer(reportfile_t* file) +{ + if (!file->writer) + return; + + // close off the end of the xml file + xxmlTextWriterEndDocument(file->writer); + xmlFreeTextWriter(file->writer); + file->writer = NULL; +} + +// +// This allocates a reportfile_t structure and initializes it. +// +reportfile_t* +new_reportfile(void) +{ + // create a new reportfile_t + reportfile_t* file = (reportfile_t*)xmalloc(sizeof(*file)); + + // set up a libxml 'buffer' and 'writer' to that buffer + file->buf = xxmlBufferCreate(); + file->writer = xxmlNewTextWriterMemory(file->buf); + + // start a new xml document: + // <report xmlns="http://www.redhat.com/gss/strata">... + xxmlTextWriterStartDocument(file->writer, /*version:*/ NULL, /*encoding:*/ NULL, /*standalone:*/ NULL); + xxmlTextWriterStartElement(file->writer, "report"); + xxmlTextWriterWriteAttribute(file->writer, "xmlns", "http://www.redhat.com/gss/strata"); + + return file; +} + +static void +internal_reportfile_start_binding(reportfile_t* file, const char* name, int isbinary, const char* filename) +{ + // <binding name=NAME [fileName=FILENAME] type=text/binary... + xxmlTextWriterStartElement(file->writer, "binding"); + xxmlTextWriterWriteAttribute(file->writer, "name", name); + if (filename) + xxmlTextWriterWriteAttribute(file->writer, "fileName", filename); + if (isbinary) + xxmlTextWriterWriteAttribute(file->writer, "type", "binary"); + else + xxmlTextWriterWriteAttribute(file->writer, "type", "text"); +} + +// +// Add a new text binding +// +void +reportfile_add_binding_from_string(reportfile_t* file, const char* name, const char* value) +{ + // <binding name=NAME type=text value=VALUE> + internal_reportfile_start_binding(file, name, /*isbinary:*/ 0, /*filename:*/ NULL); + xxmlTextWriterWriteAttribute(file->writer, "value", value); + xxmlTextWriterEndElement(file->writer); +} + +// +// Add a new binding to a report whose value is represented as a file. +// +void +reportfile_add_binding_from_namedfile(reportfile_t* file, + const char* on_disk_filename, /* unused so far */ + const char* binding_name, + const char* recorded_filename, + int isbinary) +{ + // <binding name=NAME fileName=FILENAME type=text/binary... + internal_reportfile_start_binding(file, binding_name, isbinary, recorded_filename); + // ... href=content/NAME> + string href_name = concat_path_file("content", binding_name); + xxmlTextWriterWriteAttribute(file->writer, "href", href_name.c_str()); +} + +// +// Return the contents of the reportfile as a string. +// +const char* +reportfile_as_string(reportfile_t* file) +{ + close_writer(file); + // unsigned char -> char + return (char*)file->buf->content; +} + +void +reportfile_free(reportfile_t* file) +{ + if (!file) + return; + close_writer(file); + xmlBufferFree(file->buf); + free(file); +} + + +// +// post_signature() +// +char* +post_signature(const char* baseURL, bool ssl_verify, const char* signature) +{ + string URL = concat_path_file(baseURL, "/signatures"); + + abrt_post_state *state = new_abrt_post_state(0 + + ABRT_POST_WANT_HEADERS + + ABRT_POST_WANT_BODY + + ABRT_POST_WANT_ERROR_MSG + + (ssl_verify ? ABRT_POST_WANT_SSL_VERIFY : 0) + ); + int http_resp_code = abrt_post_string(state, URL.c_str(), "application/xml", signature); + + char *retval; + const char *strata_msg; + switch (http_resp_code) + { + case 200: + case 201: + if (state->body) + { + retval = state->body; + state->body = NULL; + break; + } + strata_msg = find_header_in_abrt_post_state(state, "Strata-Message:"); + if (strata_msg && strcmp(strata_msg, "CREATED") != 0) { + retval = xstrdup(strata_msg); + break; + } + retval = xstrdup("Signature submitted successfully"); + break; + + default: + strata_msg = find_header_in_abrt_post_state(state, "Strata-Message:"); + if (strata_msg) + { + retval = xasprintf("Error (HTTP response %d): %s", + http_resp_code, + strata_msg); + break; + } + if (state->curl_error_msg) + { + if (http_resp_code >= 0) + retval = xasprintf("Error (HTTP response %d): %s", http_resp_code, state->curl_error_msg); + else + retval = xasprintf("Error in HTTP transaction: %s", state->curl_error_msg); + break; + } + retval = xasprintf("Error (HTTP response %d), body:\n%s", http_resp_code, state->body); + break; + } + + free_abrt_post_state(state); + return retval; +} + + +// +// send_report_to_new_case() +// + +static char* +make_case_data(const char* summary, const char* description, + const char* product, const char* version, + const char* component) +{ + char* retval; + xmlTextWriterPtr writer; + xmlBufferPtr buf; + + buf = xxmlBufferCreate(); + writer = xxmlNewTextWriterMemory(buf); + + xxmlTextWriterStartDocument(writer, NULL, "UTF-8", "yes"); + xxmlTextWriterStartElement(writer, "case"); + xxmlTextWriterWriteAttribute(writer, "xmlns", + "http://www.redhat.com/gss/strata"); + + xxmlTextWriterWriteElement(writer, "summary", summary); + xxmlTextWriterWriteElement(writer, "description", description); + if (product) { + xxmlTextWriterWriteElement(writer, "product", product); + } + if (version) { + xxmlTextWriterWriteElement(writer, "version", version); + } + if (component) { + xxmlTextWriterWriteElement(writer, "component", component); + } + + xxmlTextWriterEndDocument(writer); + retval = xstrdup((const char*)buf->content); + xmlFreeTextWriter(writer); + xmlBufferFree(buf); + return retval; +} + +#if 0 //unused +static char* +make_response(const char* title, const char* body, + const char* actualURL, const char* displayURL) +{ + char* retval; + xmlTextWriterPtr writer; + xmlBufferPtr buf; + + buf = xxmlBufferCreate(); + writer = xxmlNewTextWriterMemory(buf); + + xxmlTextWriterStartDocument(writer, NULL, "UTF-8", "yes"); + xxmlTextWriterStartElement(writer, "response"); + if (title) { + xxmlTextWriterWriteElement(writer, "title", title); + } + if (body) { + xxmlTextWriterWriteElement(writer, "body", body); + } + if (actualURL || displayURL) { + xxmlTextWriterStartElement(writer, "URL"); + if (actualURL) { + xxmlTextWriterWriteAttribute(writer, "href", actualURL); + } + if (displayURL) { + xxmlTextWriterWriteString(writer, displayURL); + } + } + + xxmlTextWriterEndDocument(writer); + retval = xstrdup((const char*)buf->content); + xmlFreeTextWriter(writer); + xmlBufferFree(buf); + return retval; +} +//Example: +//<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +//<response><title>Case Created and Report Attached</title><body></body><URL href="http://support-services-devel.gss.redhat.com:8080/Strata/cases/00005129/attachments/ccbf3e65-b941-3db7-a016-6a3831691a32">New Case URL</URL></response> +#endif + +char* +send_report_to_new_case(const char* baseURL, + const char* username, + const char* password, + bool ssl_verify, + const char* summary, + const char* description, + const char* component, + const char* report_file_name) +{ + string case_url = concat_path_file(baseURL, "/cases"); + + char *case_data = make_case_data(summary, description, + "Red Hat Enterprise Linux", "6.0", + component); + + int redirect_count = 0; + char *errmsg; + char *allocated = NULL; + char* retval = NULL; + abrt_post_state *case_state; + + redirect_case: + case_state = new_abrt_post_state(0 + + ABRT_POST_WANT_HEADERS + + ABRT_POST_WANT_BODY + + ABRT_POST_WANT_ERROR_MSG + + (ssl_verify ? ABRT_POST_WANT_SSL_VERIFY : 0) + ); + case_state->username = username; + case_state->password = password; + abrt_post_string(case_state, case_url.c_str(), "application/xml", case_data); + + char *case_location = find_header_in_abrt_post_state(case_state, "Location:"); + switch (case_state->http_resp_code) + { + case 305: /* "305 Use Proxy" */ + if (++redirect_count < 10 && case_location) { + case_url = case_location; + free_abrt_post_state(case_state); + goto redirect_case; + } + goto bad_resp_code; + + case 404: + /* Not strictly necessary, but makes this typical error less cryptic: + * instead of returning html-encoded body, we show short concise message, + * and show offending URL (typos in which is a typical cause) */ + retval = xasprintf("error in case creation, " + "HTTP code: 404 (Not found), URL:'%s'", case_url.c_str()); + break; + + default: + bad_resp_code: + errmsg = case_state->curl_error_msg; + if (errmsg) + retval = xasprintf("error in case creation: %s", errmsg); + else + { + errmsg = find_header_in_abrt_post_state(case_state, "Strata-Message:"); + if ((!errmsg || !errmsg[0]) && case_state->body && case_state->body[0]) + errmsg = case_state->body; + if (errmsg) + retval = xasprintf("error in case creation, HTTP code: %d, server says: '%s'", + case_state->http_resp_code, errmsg); + else + retval = xasprintf("error in case creation, HTTP code: %d", + case_state->http_resp_code); + } + break; + + case 200: + case 201: + if (!case_location) { + /* Case Creation returned valid code, but no location */ + retval = xasprintf("error in case creation: no Location URL, HTTP code: %d", + case_state->http_resp_code); + break; + } + + string atch_url = concat_path_file(case_location, "/attachments"); + abrt_post_state *atch_state; + redirect_attach: + atch_state = new_abrt_post_state(0 + + ABRT_POST_WANT_HEADERS + + ABRT_POST_WANT_BODY + + ABRT_POST_WANT_ERROR_MSG + + (ssl_verify ? ABRT_POST_WANT_SSL_VERIFY : 0) + ); + atch_state->username = username; + atch_state->password = password; + abrt_post_file_as_form(atch_state, atch_url.c_str(), "application/binary", report_file_name); + + char *atch_location = find_header_in_abrt_post_state(atch_state, "Location:"); + switch (atch_state->http_resp_code) + { + case 305: /* "305 Use Proxy" */ + if (++redirect_count < 10 && atch_location) { + atch_url = atch_location; + free_abrt_post_state(atch_state); + goto redirect_attach; + } + /* fall through */ + + default: + /* Case Creation Succeeded, attachement FAILED */ + errmsg = find_header_in_abrt_post_state(atch_state, "Strata-Message:"); + if (!errmsg || !errmsg[0]) + errmsg = atch_state->curl_error_msg; + if (atch_state->body && atch_state->body[0]) + { + if (errmsg && errmsg[0] + && strcmp(errmsg, atch_state->body) != 0 + ) /* both strata/curl error and body are present (and aren't the same) */ + allocated = errmsg = xasprintf("%s. %s", + atch_state->body, + errmsg); + else /* only body exists */ + errmsg = atch_state->body; + } + /* Note: to prevent URL misparsing, make sure to delimit + * case_location only using spaces */ + retval = xasprintf("Case created: %s but report attachment failed (HTTP code %d)%s%s", + case_location, + atch_state->http_resp_code, + errmsg ? ": " : "", + errmsg ? errmsg : "" + ); + break; + + case 200: + case 201: + // unused + //char *body = atch_state->body; + //if (case_state->body && case_state->body[0]) + //{ + // body = case_state->body; + // if (atch_state->body && atch_state->body[0]) + // allocated = body = xasprintf("%s\n%s", + // case_state->body, + // atch_state->body); + //} + retval = xasprintf("Case created: %s", /*body,*/ case_location); + } /* switch (attach HTTP code) */ + free_abrt_post_state(atch_state); + + } /* switch (case HTTP code) */ + + free_abrt_post_state(case_state); + free(allocated); + return retval; +} diff --git a/lib/utils/abrt_rh_support.h b/lib/utils/abrt_rh_support.h new file mode 100644 index 00000000..393a1a2c --- /dev/null +++ b/lib/utils/abrt_rh_support.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ +#ifndef ABRT_RH_SUPPORT_H_ +#define ABRT_RH_SUPPORT_H_ 1 + +typedef struct reportfile reportfile_t; + +reportfile_t *new_reportfile(void); +void reportfile_free(reportfile_t* file); + +void reportfile_add_binding_from_string(reportfile_t* file, const char* name, const char* value); +void reportfile_add_binding_from_namedfile(reportfile_t* file, + const char* on_disk_filename, /* unused so far */ + const char* binding_name, + const char* recorded_filename, + int isbinary); + +const char* reportfile_as_string(reportfile_t* file); + +char* post_signature(const char* baseURL, bool ssl_verify, const char* signature); +char* +send_report_to_new_case(const char* baseURL, + const char* username, + const char* password, + bool ssl_verify, + const char* summary, + const char* description, + const char* component, + const char* report_file_name); + +#endif diff --git a/lib/utils/abrt_xmlrpc.cpp b/lib/utils/abrt_xmlrpc.cpp new file mode 100644 index 00000000..ac2654ed --- /dev/null +++ b/lib/utils/abrt_xmlrpc.cpp @@ -0,0 +1,100 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include "abrtlib.h" +#include "abrt_xmlrpc.h" +#include "abrt_exception.h" + +void throw_xml_fault(xmlrpc_env *env) +{ + std::string errmsg = ssprintf("XML-RPC Fault(%d): %s", env->fault_code, env->fault_string); + xmlrpc_env_clean(env); // this is needed ONLY if fault_occurred + xmlrpc_env_init(env); // just in case user catches ex and _continues_ to use env + error_msg("%s", errmsg.c_str()); // show error in daemon log + throw CABRTException(EXCEP_PLUGIN, errmsg.c_str()); +} + +void throw_if_xml_fault_occurred(xmlrpc_env *env) +{ + if (env->fault_occurred) + { + throw_xml_fault(env); + } +} + +void abrt_xmlrpc_conn::new_xmlrpc_client(const char* url, bool ssl_verify) +{ + m_pClient = NULL; + m_pServer_info = NULL; + + xmlrpc_env env; + xmlrpc_env_init(&env); + + /* This should be done at program startup, once. + * We do it in abrtd's main */ + /* xmlrpc_client_setup_global_const(&env); */ + + struct xmlrpc_curl_xportparms curlParms; + memset(&curlParms, 0, sizeof(curlParms)); + /* curlParms.network_interface = NULL; - done by memset */ + curlParms.no_ssl_verifypeer = !ssl_verify; + curlParms.no_ssl_verifyhost = !ssl_verify; +#ifdef VERSION + curlParms.user_agent = PACKAGE_NAME"/"VERSION; +#else + curlParms.user_agent = "abrt"; +#endif + + struct xmlrpc_clientparms clientParms; + memset(&clientParms, 0, sizeof(clientParms)); + clientParms.transport = "curl"; + clientParms.transportparmsP = &curlParms; + clientParms.transportparm_size = XMLRPC_CXPSIZE(user_agent); + + xmlrpc_client_create(&env, XMLRPC_CLIENT_NO_FLAGS, + PACKAGE_NAME, VERSION, + &clientParms, XMLRPC_CPSIZE(transportparm_size), + &m_pClient); + if (env.fault_occurred) + throw_xml_fault(&env); + + m_pServer_info = xmlrpc_server_info_new(&env, url); + if (env.fault_occurred) + { + xmlrpc_client_destroy(m_pClient); + m_pClient = NULL; + throw_xml_fault(&env); + } +} + +void abrt_xmlrpc_conn::destroy_xmlrpc_client() +{ + if (m_pServer_info) + { + xmlrpc_server_info_free(m_pServer_info); + m_pServer_info = NULL; + } + if (m_pClient) + { + xmlrpc_client_destroy(m_pClient); + m_pClient = NULL; + } +} diff --git a/lib/utils/backtrace.c b/lib/utils/backtrace.c new file mode 100644 index 00000000..a2efa766 --- /dev/null +++ b/lib/utils/backtrace.c @@ -0,0 +1,846 @@ +/* + 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 "backtrace.h" +#include "strbuf.h" +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "xfuncs.h" + +struct frame *frame_new() +{ + struct frame *f = malloc(sizeof(struct frame)); + if (!f) + { + puts("Error while allocating memory for backtrace frame."); + exit(5); + } + + f->function = NULL; + f->number = 0; + f->sourcefile = NULL; + f->signal_handler_called = false; + f->next = NULL; + return f; +} + +void frame_free(struct frame *f) +{ + if (f->function) + free(f->function); + if (f->sourcefile) + free(f->sourcefile); + free(f); +} + +struct frame *frame_add_sibling(struct frame *a, struct frame *b) +{ + struct frame *aa = a; + while (aa->next) + aa = aa->next; + + aa->next = b; + return a; +} + +/* Appends a string representation of 'frame' to the 'str'. */ +static void frame_append_str(struct frame *frame, struct strbuf *str, bool verbose) +{ + if (verbose) + strbuf_append_strf(str, " #%d", frame->number); + else + strbuf_append_str(str, " "); + + if (frame->function) + strbuf_append_strf(str, " %s", frame->function); + if (verbose && frame->sourcefile) + { + if (frame->function) + strbuf_append_str(str, " at"); + strbuf_append_strf(str, " %s", frame->sourcefile); + } + + if (frame->signal_handler_called) + strbuf_append_str(str, " <signal handler called>"); + + strbuf_append_str(str, "\n"); +} + +static bool frame_is_exit_handler(struct frame *frame) +{ + return (frame->function + && frame->sourcefile + && 0 == strcmp(frame->function, "__run_exit_handlers") + && NULL != strstr(frame->sourcefile, "exit.c")); +} + +/* Checks if a frame contains abort function used + * by operating system to exit application. + * E.g. in C it's called "abort" or "raise". + */ +static bool frame_is_abort_frame(struct frame *frame) +{ + if (!frame->function || !frame->sourcefile) + return false; + + if (0 == strcmp(frame->function, "raise") + && (NULL != strstr(frame->sourcefile, "pt-raise.c") + || NULL != strstr(frame->sourcefile, "/libc.so.6"))) + return true; + else if (0 == strcmp(frame->function, "exit") + && NULL != strstr(frame->sourcefile, "exit.c")) + return true; + else if (0 == strcmp(frame->function, "abort") + && (NULL != strstr(frame->sourcefile, "abort.c") + || NULL != strstr(frame->sourcefile, "/libc.so.6"))) + return true; + else if (frame_is_exit_handler(frame)) + return true; + + return false; +} + +static bool frame_is_noncrash_frame(struct frame *frame) +{ + /* Abort frames. */ + if (frame_is_abort_frame(frame)) + return true; + + if (!frame->function) + return false; + + if (0 == strcmp(frame->function, "__kernel_vsyscall")) + return true; + + if (0 == strcmp(frame->function, "__assert_fail")) + return true; + + if (!frame->sourcefile) + return false; + + /* GDK */ + if (0 == strcmp(frame->function, "gdk_x_error") + && 0 == strcmp(frame->sourcefile, "gdkmain-x11.c")) + return true; + + /* X.org */ + if (0 == strcmp(frame->function, "_XReply") + && 0 == strcmp(frame->sourcefile, "xcb_io.c")) + return true; + if (0 == strcmp(frame->function, "_XError") + && 0 == strcmp(frame->sourcefile, "XlibInt.c")) + return true; + if (0 == strcmp(frame->function, "XSync") + && 0 == strcmp(frame->sourcefile, "Sync.c")) + return true; + if (0 == strcmp(frame->function, "process_responses") + && 0 == strcmp(frame->sourcefile, "xcb_io.c")) + return true; + + /* glib */ + if (0 == strcmp(frame->function, "IA__g_log") + && 0 == strcmp(frame->sourcefile, "gmessages.c")) + return true; + if (0 == strcmp(frame->function, "IA__g_logv") + && 0 == strcmp(frame->sourcefile, "gmessages.c")) + return true; + if (0 == strcmp(frame->function, "IA__g_assertion_message") + && 0 == strcmp(frame->sourcefile, "gtestutils.c")) + return true; + if (0 == strcmp(frame->function, "IA__g_assertion_message_expr") + && 0 == strcmp(frame->sourcefile, "gtestutils.c")) + return true; + + /* DBus */ + if (0 == strcmp(frame->function, "gerror_to_dbus_error_message") + && 0 == strcmp(frame->sourcefile, "dbus-gobject.c")) + return true; + if (0 == strcmp(frame->function, "dbus_g_method_return_error") + && 0 == strcmp(frame->sourcefile, "dbus-gobject.c")) + return true; + + /* libstdc++ */ + if (0 == strcmp(frame->function, "__gnu_cxx::__verbose_terminate_handler") + && NULL != strstr(frame->sourcefile, "/vterminate.cc")) + return true; + if (0 == strcmp(frame->function, "__cxxabiv1::__terminate") + && NULL != strstr(frame->sourcefile, "/eh_terminate.cc")) + return true; + if (0 == strcmp(frame->function, "std::terminate") + && NULL != strstr(frame->sourcefile, "/eh_terminate.cc")) + return true; + if (0 == strcmp(frame->function, "__cxxabiv1::__cxa_throw") + && NULL != strstr(frame->sourcefile, "/eh_throw.cc")) + return true; + + return false; +} + +struct thread *thread_new() +{ + struct thread *t = malloc(sizeof(struct thread)); + if (!t) + { + puts("Error while allocating memory for backtrace thread."); + exit(5); + } + + t->number = 0; + t->frames = NULL; + t->next = NULL; + return t; +} + +void thread_free(struct thread *t) +{ + while (t->frames) + { + struct frame *rm = t->frames; + t->frames = rm->next; + frame_free(rm); + } + + free(t); +} + +struct thread *thread_add_sibling(struct thread *a, struct thread *b) +{ + struct thread *aa = a; + while (aa->next) + aa = aa->next; + + aa->next = b; + return a; +} + +static int thread_get_frame_count(struct thread *thread) +{ + struct frame *f = thread->frames; + int count = 0; + while (f) + { + f = f->next; + ++count; + } + return count; +} + +/* Appends string representation of 'thread' to the 'str'. */ +static void thread_append_str(struct thread *thread, struct strbuf *str, bool verbose) +{ + int framecount = thread_get_frame_count(thread); + if (verbose) + strbuf_append_strf(str, "Thread no. %d (%d frames)\n", thread->number, framecount); + else + strbuf_append_str(str, "Thread\n"); + struct frame *frame = thread->frames; + while (frame) + { + frame_append_str(frame, str, verbose); + frame = frame->next; + } +} + +/* + * Checks whether the thread it contains some known "abort" function. + * If a frame with the function is found, it is returned. + * If there are multiple frames with abort function, the lowest + * one is returned. + * Nonrecursive. + */ +struct frame *thread_find_abort_frame(struct thread *thread) +{ + struct frame *frame = thread->frames; + struct frame *result = NULL; + while (frame) + { + if (frame_is_abort_frame(frame)) + result = frame; + + frame = frame->next; + } + + return result; +} + +static void thread_remove_exit_handlers(struct thread *thread) +{ + struct frame *frame = thread->frames; + while (frame) + { + if (frame_is_exit_handler(frame)) + { + /* Delete all frames from the beginning to this frame. */ + while (thread->frames != frame) + { + struct frame *rm = thread->frames; + thread->frames = thread->frames->next; + frame_free(rm); + } + return; + } + + frame = frame->next; + } +} + +static void thread_remove_noncrash_frames(struct thread *thread) +{ + struct frame *prev = NULL; + struct frame *cur = thread->frames; + while (cur) + { + if (frame_is_noncrash_frame(cur)) + { + /* This frame must be skipped, because it will + be deleted. */ + if (prev) + prev->next = cur->next; + else + thread->frames = cur->next; + + frame_free(cur); + + /* Set cur to be valid, as it will be used to + advance to next item. */ + if (prev) + cur = prev; + else + { + cur = thread->frames; + continue; + } + } + + prev = cur; + cur = cur->next; + } +} + +/* Counts the number of quality frames and the number of all frames + * in a thread. + * @param ok_count + * @param all_count + * Not zeroed. This function just adds the numbers to + * ok_count and all_count. + */ +static void thread_rating(struct thread *thread, int *ok_count, int *all_count) +{ + struct frame *frame = thread->frames; + while (frame) + { + *all_count += 1; + if (frame->signal_handler_called || + (frame->function && 0 != strcmp(frame->function, "??"))) + { + *ok_count += 1; + } + frame = frame->next; + } +} + +struct backtrace *backtrace_new() +{ + struct backtrace *bt = malloc(sizeof(struct backtrace)); + if (!bt) + { + puts("Error while allocating memory for backtrace."); + exit(5); + } + + bt->threads = NULL; + bt->crash = NULL; + return bt; +} + +void backtrace_free(struct backtrace *bt) +{ + while (bt->threads) + { + struct thread *rm = bt->threads; + bt->threads = rm->next; + thread_free(rm); + } + + if (bt->crash) + frame_free(bt->crash); + + free(bt); +} + +static int backtrace_get_thread_count(struct backtrace *bt) +{ + struct thread *t = bt->threads; + int count = 0; + while (t) + { + t = t->next; + ++count; + } + return count; +} + +void backtrace_print_tree(struct backtrace *backtrace, bool verbose) +{ + struct strbuf *strbuf = backtrace_tree_as_str(backtrace, verbose); + puts(strbuf->buf); + strbuf_free(strbuf); +} + +struct strbuf *backtrace_tree_as_str(struct backtrace *backtrace, bool verbose) +{ + struct strbuf *str = strbuf_new(); + if (verbose) + strbuf_append_strf(str, "Thread count: %d\n", backtrace_get_thread_count(backtrace)); + + if (backtrace->crash && verbose) + { + strbuf_append_str(str, "Crash frame: "); + frame_append_str(backtrace->crash, str, verbose); + } + + struct thread *thread = backtrace->threads; + while (thread) + { + thread_append_str(thread, str, verbose); + thread = thread->next; + } + + return str; +} + +void backtrace_remove_threads_except_one(struct backtrace *backtrace, + struct thread *one) +{ + while (backtrace->threads) + { + struct thread *rm = backtrace->threads; + backtrace->threads = rm->next; + if (rm != one) + thread_free(rm); + } + + one->next = NULL; + backtrace->threads = one; +} + +/* + * Loop through all threads and if a single one contains the crash frame on the top, + * return it. Otherwise, return NULL. + * + * If require_abort is true, it is also required that the thread containing + * the crash frame contains some known "abort" function. In this case there can be + * multiple threads with the crash frame on the top, but only one of them might + * contain the abort function to succeed. + */ +static struct thread *backtrace_find_crash_thread_from_crash_frame(struct backtrace *backtrace, + bool require_abort) +{ + /* + * This code can be extended to compare something else when the function + * name is not available. + */ + if (!backtrace->threads || !backtrace->crash || !backtrace->crash->function) + return NULL; + + struct thread *result = NULL; + struct thread *thread = backtrace->threads; + while (thread) + { + if (thread->frames + && thread->frames->function + && 0 == strcmp(thread->frames->function, backtrace->crash->function) + && (!require_abort || thread_find_abort_frame(thread))) + { + if (result == NULL) + result = thread; + else + { + /* Second frame with the same function. Failure. */ + return NULL; + } + } + + thread = thread->next; + } + + return result; +} + +struct thread *backtrace_find_crash_thread(struct backtrace *backtrace) +{ + /* If there is no thread, be silent and report NULL. */ + if (!backtrace->threads) + return NULL; + + /* If there is just one thread, it is simple. */ + if (!backtrace->threads->next) + return backtrace->threads; + + /* If we have a crash frame *and* there is just one thread which has + * this frame on the top, it is also simple. + */ + struct thread *thread; + thread = backtrace_find_crash_thread_from_crash_frame(backtrace, false); + if (thread) + return thread; + + /* There are multiple threads with a frame indistinguishable from + * the crash frame on the top of stack. + * Try to search for known abort functions. + */ + thread = backtrace_find_crash_thread_from_crash_frame(backtrace, true); + + return thread; /* result or null */ +} + +void backtrace_limit_frame_depth(struct backtrace *backtrace, int depth) +{ + if (depth <= 0) + return; + + struct thread *thread = backtrace->threads; + while (thread) + { + struct frame *frame = thread_find_abort_frame(thread); + if (frame) + frame = frame->next; /* Start counting from the frame following the abort fr. */ + else + frame = thread->frames; /* Start counting from the first frame. */ + + /* Skip some frames to get the required stack depth. */ + int i = depth; + struct frame *last_frame = NULL; + while (frame && i) + { + last_frame = frame; + frame = frame->next; + --i; + } + + /* Delete the remaining frames. */ + if (last_frame) + last_frame->next = NULL; + + while (frame) + { + struct frame *rm = frame; + frame = frame->next; + frame_free(rm); + } + + thread = thread->next; + } +} + +void backtrace_remove_exit_handlers(struct backtrace *backtrace) +{ + struct thread *thread = backtrace->threads; + while (thread) + { + thread_remove_exit_handlers(thread); + thread = thread->next; + } +} + +void backtrace_remove_noncrash_frames(struct backtrace *backtrace) +{ + struct thread *thread = backtrace->threads; + while (thread) + { + thread_remove_noncrash_frames(thread); + thread = thread->next; + } +} + +/* Belongs to independent_backtrace(). */ +struct header +{ + struct strbuf *text; + struct header *next; +}; + +/* Belongs to independent_backtrace(). */ +static struct header *header_new() +{ + struct header *head = malloc(sizeof(struct header)); + if (!head) + { + puts("Error while allocating memory for backtrace header."); + exit(5); + } + head->text = NULL; + head->next = NULL; + return head; +} + +/* Recursively frees siblings. */ +/* Belongs to independent_backtrace(). */ +static void header_free(struct header *head) +{ + if (head->text) + strbuf_free(head->text); + if (head->next) + header_free(head->next); + free(head); +} + +/* Inserts new header to array if it is not already there. */ +/* Belongs to independent_backtrace(). */ +static void header_set_insert(struct header *cur, struct strbuf *new) +{ + /* Duplicate found case. */ + if (strcmp(cur->text->buf, new->buf) == 0) + return; + + /* Last item case, insert new header here. */ + if (cur->next == NULL) + { + cur->next = header_new(); + cur->next->text = new; + return; + } + + /* Move to next item in array case. */ + header_set_insert(cur->next, new); +} + +struct strbuf *independent_backtrace(const char *input) +{ + struct strbuf *header = strbuf_new(); + bool in_bracket = false; + bool in_quote = false; + bool in_header = false; + bool in_digit = false; + bool has_at = false; + bool has_filename = false; + bool has_bracket = false; + struct header *headers = NULL; + + const char *bk = input; + while (*bk) + { + if (bk[0] == '#' + && bk[1] >= '0' && bk[1] <= '7' + && bk[2] == ' ' /* take only #0...#7 (8 last stack frames) */ + && !in_quote) + { + if (in_header && !has_filename) + strbuf_clear(header); + in_header = true; + } + + if (!in_header) + { + ++bk; + continue; + } + + if (isdigit(*bk) && !in_quote && !has_at) + in_digit = true; + else if (bk[0] == '\\' && bk[1] == '\"') + bk++; + else if (*bk == '\"') + in_quote = in_quote == true ? false : true; + else if (*bk == '(' && !in_quote) + { + in_bracket = true; + in_digit = false; + strbuf_append_char(header, '('); + } + else if (*bk == ')' && !in_quote) + { + in_bracket = false; + has_bracket = true; + in_digit = false; + strbuf_append_char(header, '('); + } + else if (*bk == '\n' && has_filename) + { + if (headers == NULL) + { + headers = header_new(); + headers->text = header; + } + else + header_set_insert(headers, header); + + header = strbuf_new(); + in_bracket = false; + in_quote = false; + in_header = false; + in_digit = false; + has_at = false; + has_filename = false; + has_bracket = false; + } + else if (*bk == ',' && !in_quote) + in_digit = false; + else if (isspace(*bk) && !in_quote) + in_digit = false; + else if (bk[0] == 'a' && bk[1] == 't' && has_bracket && !in_quote) + { + has_at = true; + strbuf_append_char(header, 'a'); + } + else if (bk[0] == ':' && has_at && isdigit(bk[1]) && !in_quote) + has_filename = true; + else if (in_header && !in_digit && !in_quote && !in_bracket) + strbuf_append_char(header, *bk); + + bk++; + } + + strbuf_free(header); + + struct strbuf *result = strbuf_new(); + struct header *loop = headers; + while (loop) + { + strbuf_append_str(result, loop->text->buf); + strbuf_append_char(result, '\n'); + loop = loop->next; + } + + if (headers) + header_free(headers); /* recursive */ + + return result; +} + +/* Belongs to backtrace_rate_old(). */ +enum line_rating +{ + // RATING EXAMPLE + MissingEverything = 0, // #0 0x0000dead in ?? () + MissingFunction = 1, // #0 0x0000dead in ?? () from /usr/lib/libfoobar.so.4 + MissingLibrary = 2, // #0 0x0000dead in foobar() + MissingSourceFile = 3, // #0 0x0000dead in FooBar::FooBar () from /usr/lib/libfoobar.so.4 + Good = 4, // #0 0x0000dead in FooBar::crash (this=0x0) at /home/user/foobar.cpp:204 + BestRating = Good, +}; + +/* Belongs to backtrace_rate_old(). */ +static enum line_rating rate_line(const char *line) +{ +#define FOUND(x) (strstr(line, x) != NULL) + /* see the comments at enum line_rating for possible combinations */ + if (FOUND(" at ")) + return Good; + const char *function = strstr(line, " in "); + if (function && function[4] == '?') /* " in ??" does not count */ + function = NULL; + bool library = FOUND(" from "); + if (function && library) + return MissingSourceFile; + if (function) + return MissingLibrary; + if (library) + return MissingFunction; + + return MissingEverything; +#undef FOUND +} + +/* just a fallback function, to be removed one day */ +int backtrace_rate_old(const char *backtrace) +{ + int i, len; + int multiplier = 0; + int rating = 0; + int best_possible_rating = 0; + char last_lvl = 0; + + /* We look at the frames in reversed order, since: + * - rate_line() checks starting from the first line of the frame + * (note: it may need to look at more than one line!) + * - we increase weight (multiplier) for every frame, + * so that topmost frames end up most important + */ + len = 0; + for (i = strlen(backtrace) - 1; i >= 0; i--) + { + if (backtrace[i] == '#' + && (backtrace[i+1] >= '0' && backtrace[i+1] <= '9') /* #N */ + && (i == 0 || backtrace[i-1] == '\n')) /* it's at line start */ + { + /* For one, "#0 xxx" always repeats, skip repeats */ + if (backtrace[i+1] == last_lvl) + continue; + last_lvl = backtrace[i+1]; + + char *s = xstrndup(backtrace + i + 1, len); + /* Replace tabs with spaces, rate_line() does not expect tabs. + * Actually, even newlines may be there. Example of multiline frame + * where " at SRCFILE" is on 2nd line: + * #3 0x0040b35d in __libc_message (do_abort=<value optimized out>, + * fmt=<value optimized out>) at ../sysdeps/unix/sysv/linux/libc_fatal.c:186 + */ + char *p; + for (p = s; *p; p++) + { + if (*p == '\t' || *p == '\n') + *p = ' '; + } + int lrate = rate_line(s); + multiplier++; + rating += lrate * multiplier; + best_possible_rating += BestRating * multiplier; + //log("lrate:%d rating:%d best_possible_rating:%d s:'%-.40s'", + // lrate, rating, best_possible_rating, s); + free(s); + len = 0; /* starting new line */ + } + else + { + len++; + } + } + + /* Bogus 'backtrace' with zero frames? */ + if (best_possible_rating == 0) + return 0; + + /* Returning number of "stars" to show */ + if (rating*10 >= best_possible_rating*8) /* >= 0.8 */ + return 4; + if (rating*10 >= best_possible_rating*6) + return 3; + if (rating*10 >= best_possible_rating*4) + return 2; + if (rating*10 >= best_possible_rating*2) + return 1; + + return 0; +} + +float backtrace_quality(struct backtrace *backtrace) +{ + int ok_count = 0; + int all_count = 0; + struct thread *thread = backtrace->threads; + while (thread) + { + thread_rating(thread, &ok_count, &all_count); + thread = thread->next; + } + + if (all_count == 0) + return 0; + return ok_count / (float)all_count; +} diff --git a/lib/utils/backtrace.h b/lib/utils/backtrace.h new file mode 100644 index 00000000..df5def56 --- /dev/null +++ b/lib/utils/backtrace.h @@ -0,0 +1,152 @@ +/* + Backtrace parsing and processing. + + If we transform analyzer plugins to separate applications one day, + this functionality should be moved to CCpp analyzer, which will + then easily provide what abrt-backtrace utility provides now. Currently + the code is used by abrt-backtrace, so it is shared in the utils + library. + + Copyright (C) 2009, 2010 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. +*/ +#ifndef BACKTRACE_H +#define BACKTRACE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <stdbool.h> + +struct frame +{ + /* Function name, or NULL. */ + char *function; + /* Frame number. */ + int number; + /* Name of the source file, or binary file, or NULL. */ + char *sourcefile; + bool signal_handler_called; + /* Sibling frame, or NULL if this is the last frame in a thread. */ + struct frame *next; +}; + +struct thread +{ + int number; + struct frame *frames; + /* Sibling thread, or NULL if this is the last thread in a backtrace. */ + struct thread *next; +}; + +struct backtrace +{ + struct thread *threads; + /* + * The frame where the crash happened according to GDB. + * It might be that we can not tell to which thread this frame belongs, + * because all threads end with mutually indistinguishable frames. + */ + struct frame *crash; +}; + +extern struct frame *frame_new(); +extern void frame_free(struct frame *f); +extern struct frame *frame_add_sibling(struct frame *a, struct frame *b); + +extern struct thread *thread_new(); +extern void thread_free(struct thread *t); +extern struct thread *thread_add_sibling(struct thread *a, struct thread *b); +extern struct frame *thread_find_abort_frame(struct thread *thread); + +extern struct backtrace *backtrace_new(); +extern void backtrace_free(struct backtrace *bt); + +/* Prints how internal backtrace representation looks to stdout. */ +extern void backtrace_print_tree(struct backtrace *backtrace, bool verbose); + +/* Returns the backtrace tree string representation. */ +extern struct strbuf *backtrace_tree_as_str(struct backtrace *backtrace, bool verbose); + +/* + * Frees all threads except the one provided as parameters. + * It does not check whether one is a member of backtrace. + * Caller must know that. + */ +extern void backtrace_remove_threads_except_one(struct backtrace *backtrace, + struct thread *one); + +/* + * Search all threads and tries to find the one that caused the crash. + * It might return NULL if the thread cannot be determined. + */ +extern struct thread *backtrace_find_crash_thread(struct backtrace *backtrace); + +extern void backtrace_limit_frame_depth(struct backtrace *backtrace, int depth); + +/* + * Exit handlers are all stack frames above __run_exit_handlers() + */ +extern void backtrace_remove_exit_handlers(struct backtrace *backtrace); + +/* + * Removes frames known as not causing crash, but that are often + * a part of a backtrace. + */ +extern void backtrace_remove_noncrash_frames(struct backtrace *backtrace); + +/* Parses the backtrace and stores it to a structure. + * @returns + * Returns the backtrace struct representation, or NULL if the parser failed. + * Caller of this function is responsible for backtrace_free()ing the returned value. + * Defined in backtrace_parser.y. + */ +extern struct backtrace *backtrace_parse(char *input, bool debug_parser, bool debug_scanner); + +/* Reads the input file and calculates "independent" backtrace from it. "Independent" means + * that the memory addresses that differ from run to run are removed from the backtrace, and + * also variable names and values are removed. + * + * This function can be called when backtrace_parse() call fails. It provides a shorter + * version of backtrace, with a chance that hash calculated from the returned value can be used + * to detect duplicates. However, this kind of duplicate detection is very low-quality. + * @returns + * The independent backtrace. Caller is responsible for calling + * strbuf_free() on it. + */ +extern struct strbuf *independent_backtrace(const char *input); + +/* Get the quality of backtrace, as a number of "stars". + * @returns + * Value 0 to 4. + */ +extern int backtrace_rate_old(const char *backtrace); + +/* Evaluates the quality of the backtrace, meaning the ratio of frames + * with function name fully known to all frames. + * @returns + * A number between 0 and 1. 0 means the lowest quality, + * 1 means full backtrace is known. + */ +extern float backtrace_quality(struct backtrace *backtrace); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/utils/backtrace_parser.y b/lib/utils/backtrace_parser.y new file mode 100644 index 00000000..8e2fc2b1 --- /dev/null +++ b/lib/utils/backtrace_parser.y @@ -0,0 +1,684 @@ +%{ /* -*- mode: yacc -*- + 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 <math.h> +#include <stdio.h> +#include <stdlib.h> +#include "backtrace.h" +#include "strbuf.h" + +struct backtrace *g_backtrace; + +#define YYDEBUG 1 +#define YYMAXDEPTH 10000000 +void yyerror(char const *s) +{ + fprintf (stderr, "\nParser error: %s\n", s); +} + +int yylex(); + +%} + +/* This defines the type of yylval */ +%union { + struct backtrace *backtrace; + struct thread *thread; + struct frame *frame; + char *str; + int num; + char c; + + struct strbuf *strbuf; +} + +/* Bison declarations. */ +%token END 0 "end of file" + +%type <backtrace> backtrace +%type <thread> threads + thread +%type <frame> frames + frame + frame_head + frame_head_1 + frame_head_2 + frame_head_3 + frame_head_4 + frame_head_5 +%type <strbuf> identifier + hexadecimal_digit_sequence + hexadecimal_number + file_name + file_location + function_call + function_name + digit_sequence + frame_address_in_function + identifier_braces + identifier_braces_inside + identifier_template + identifier_template_inside +%type <c> nondigit + digit + hexadecimal_digit + file_name_char + identifier_char + identifier_char_no_templates + identifier_first_char + identifier_braces_inside_char + identifier_template_inside_char + variables_char + variables_char_no_framestart + ws + ws_nonl + '(' ')' '+' '-' '/' '.' '_' '~' '[' ']' '\r' '?' '{' '}' + 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' + 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z' + 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' + 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z' + '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' + '\'' '`' ',' '#' '@' '<' '>' '=' ':' '"' ';' ' ' + '\n' '\t' '\\' '!' '*' '%' '|' '^' '&' '$' +%type <num> frame_start + +%destructor { thread_free($$); } <thread> +%destructor { frame_free($$); } <frame> +%destructor { strbuf_free($$); } <strbuf> + +%start backtrace +%glr-parser +%error-verbose +%locations + +%% /* The grammar follows. */ + +backtrace : /* empty */ %dprec 1 + { $$ = g_backtrace = backtrace_new(); } + | threads wsa %dprec 2 + { + $$ = g_backtrace = backtrace_new(); + $$->threads = $1; + } + | frame_head wss threads wsa %dprec 4 + { + $$ = g_backtrace = backtrace_new(); + $$->threads = $3; + $$->crash = $1; + } + | frame wss threads wsa %dprec 3 + { + $$ = g_backtrace = backtrace_new(); + $$->threads = $3; + $$->crash = $1; + } +; + +threads : thread + | threads '\n' thread { $$ = thread_add_sibling($1, $3); } +; + +thread : keyword_thread wss digit_sequence wsa '(' keyword_thread wss digit_sequence wsa ')' ':' wsa frames + { + $$ = thread_new(); + $$->frames = $13; + + if (sscanf($3->buf, "%d", &$$->number) != 1) + { + printf("Error while parsing thread number '%s'", $3->buf); + exit(5); + } + strbuf_free($3); + strbuf_free($8); + } +; + +frames : frame { $$ = $1; } + | frames frame { $$ = frame_add_sibling($1, $2); } +; + +frame : frame_head_1 wss variables %dprec 3 + | frame_head_2 wss variables %dprec 4 + | frame_head_3 wss variables %dprec 5 + | frame_head_4 wss variables %dprec 2 + | frame_head_5 wss variables %dprec 1 +; + +frame_head : frame_head_1 %dprec 3 + | frame_head_2 %dprec 4 + | frame_head_3 %dprec 5 + | frame_head_4 %dprec 2 + | frame_head_5 %dprec 1 +; + +frame_head_1 : frame_start wss function_call wsa keyword_at wss file_location + { + $$ = frame_new(); + $$->number = $1; + $$->function = $3->buf; + strbuf_free_nobuf($3); + $$->sourcefile = $7->buf; + strbuf_free_nobuf($7); + } +; + +frame_head_2 : frame_start wss frame_address_in_function wss keyword_at wss file_location + { + $$ = frame_new(); + $$->number = $1; + $$->function = $3->buf; + strbuf_free_nobuf($3); + $$->sourcefile = $7->buf; + strbuf_free_nobuf($7); + } +; + +frame_head_3 : frame_start wss frame_address_in_function wss keyword_from wss file_location + { + $$ = frame_new(); + $$->number = $1; + $$->function = $3->buf; + strbuf_free_nobuf($3); + $$->sourcefile = $7->buf; + strbuf_free_nobuf($7); + } +; + +frame_head_4 : frame_start wss frame_address_in_function + { + $$ = frame_new(); + $$->number = $1; + $$->function = $3->buf; + strbuf_free_nobuf($3); + } +; + +frame_head_5 : frame_start wss keyword_sighandler + { + $$ = frame_new(); + $$->number = $1; + $$->signal_handler_called = true; + } + +frame_start: '#' digit_sequence + { + if (sscanf($2->buf, "%d", &$$) != 1) + { + printf("Error while parsing frame number '%s'.\n", $2->buf); + exit(5); + } + strbuf_free($2); + } +; + +frame_address_in_function : hexadecimal_number wss keyword_in wss function_call + { + strbuf_free($1); + $$ = $5; + } + | hexadecimal_number wss keyword_in wss keyword_vtable wss keyword_for wss function_call + { + strbuf_free($1); + $$ = $9; + } +; + +file_location : file_name ':' digit_sequence + { + $$ = $1; + strbuf_free($3); /* line number not needed for now */ + } + | file_name +; + +variables : variables_line '\n' + | variables_line END + | variables_line wss_nonl '\n' + | variables_line wss_nonl END + | variables variables_line '\n' + | variables variables_line END + | variables variables_line wss_nonl '\n' + | variables variables_line wss_nonl END + | variables wss_nonl variables_line '\n' + | variables wss_nonl variables_line END + | variables wss_nonl variables_line wss_nonl '\n' + | variables wss_nonl variables_line wss_nonl END +; + +variables_line : variables_char_no_framestart + | variables_line variables_char + | variables_line wss_nonl variables_char +; + +variables_char : '#' | variables_char_no_framestart +; + +/* Manually synchronized with function_args_char_base, except the first line. */ +variables_char_no_framestart : digit | nondigit | '"' | '(' | ')' | '\\' + | '+' | '-' | '<' | '>' | '/' | '.' + | '[' | ']' | '?' | '\'' | '`' | ',' + | '=' | '{' | '}' | '^' | '&' | '$' + | ':' | ';' | '!' | '@' | '*' + | '%' | '|' | '~' +; + +function_call : function_name wss function_args %dprec 3 + | return_type wss_nonl function_name wss function_args %dprec 2 + { $$ = $3; } + | function_name wss_nonl identifier_template wss function_args %dprec 1 + { $$ = $1; strbuf_free($3); } +; + +return_type : identifier { strbuf_free($1); } +; + +function_name : identifier + | '?' '?' + { + $$ = strbuf_new(); + strbuf_append_str($$, "??"); + } +; + +function_args : '(' wsa ')' + | '(' wsa function_args_sequence wsa ')' +; + +function_args_sequence : function_args_char + | function_args_sequence wsa '(' wsa ')' + | function_args_sequence wsa '(' wsa function_args_string wsa ')' + | function_args_sequence wsa '(' wsa function_args_sequence wsa ')' + | function_args_sequence wsa function_args_char + | function_args_sequence wsa function_args_string +; + +function_args_string : '"' wsa function_args_string_sequence wsa '"' + | '"' wsa '"' +; + +/* Manually synchronized with variables_char_no_framestart, + * except the first line. + */ +function_args_char_base : digit | nondigit | '#' + | '+' | '-' | '<' | '>' | '/' | '.' + | '[' | ']' | '?' | '\'' | '`' | ',' + | '=' | '{' | '}' | '^' | '&' | '$' + | ':' | ';' | '!' | '@' | '*' + | '%' | '|' | '~' +; +function_args_escaped_char : '\\' function_args_char_base + | '\\' '\\' + | '\\' '"' +; +function_args_char : function_args_char_base + | function_args_escaped_char +; + + +function_args_string_sequence : function_args_string_char + | function_args_string_sequence function_args_string_char + | function_args_string_sequence wss_nonl function_args_string_char +; + +function_args_string_char : function_args_char | '(' | ')' +; + +file_name : file_name_char { $$ = strbuf_new(); strbuf_append_char($$, $1); } + | file_name file_name_char { $$ = strbuf_append_char($1, $2); } +; + +file_name_char : digit | nondigit | '-' | '+' | '/' | '.' +; + + /* Function name, sometimes mangled. + * Example: something@GLIB_2_2 + * CClass::operator= + */ +identifier : identifier_first_char %dprec 1 + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + } + | identifier_braces %dprec 1 /* e.g. (anonymous namespace)::WorkerThread */ + | identifier identifier_char %dprec 1 + { $$ = strbuf_append_char($1, $2); } + | identifier identifier_braces %dprec 1 + { + $$ = strbuf_append_str($1, $2->buf); + strbuf_free($2); + } + | identifier identifier_template %dprec 2 + { + $$ = strbuf_append_str($1, $2->buf); + strbuf_free($2); + } +; + +identifier_first_char: nondigit + | '~' /* destructor */ + | '*' +; + +identifier_char_no_templates : digit | nondigit | '@' | '.' | ':' | '=' + | '!' | '*' | '+' | '-' | '[' | ']' + | '~' | '&' | '/' | '%' | '^' + | '|' | ',' +; + +/* Most of the special characters are required to support C++ + * operator overloading. + */ +identifier_char : identifier_char_no_templates | '<'| '>' +; + +identifier_braces : '(' ')' + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + strbuf_append_char($$, $2); + } + | '(' identifier_braces_inside ')' + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + strbuf_append_str($$, $2->buf); + strbuf_free($2); + strbuf_append_char($$, $3); + } +; + +identifier_braces_inside : identifier_braces_inside_char %dprec 1 + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + } + | identifier_braces_inside identifier_braces_inside_char %dprec 1 + { $$ = strbuf_append_char($1, $2); } + | identifier_braces_inside '(' identifier_braces_inside ')' %dprec 1 + { + $$ = strbuf_append_char($1, $2); + $$ = strbuf_append_str($1, $3->buf); + strbuf_free($3); + $$ = strbuf_append_char($1, $4); + } + | identifier_braces_inside '(' ')' %dprec 1 + { + $$ = strbuf_append_char($1, $2); + $$ = strbuf_append_char($1, $3); + } + | identifier_braces_inside identifier_template %dprec 2 + { + $$ = strbuf_append_str($1, $2->buf); + strbuf_free($2); + } +; + +identifier_braces_inside_char : identifier_char | ws_nonl +; + +identifier_template : '<' identifier_template_inside '>' + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + strbuf_append_str($$, $2->buf); + strbuf_free($2); + strbuf_append_char($$, $3); + } +; + +identifier_template_inside : identifier_template_inside_char + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + } + | identifier_template_inside identifier_template_inside_char + { $$ = strbuf_append_char($1, $2); } + | identifier_template_inside '<' identifier_template_inside '>' + { + $$ = strbuf_append_char($1, $2); + $$ = strbuf_append_str($1, $3->buf); + strbuf_free($3); + $$ = strbuf_append_char($1, $4); + } + | identifier_template_inside identifier_braces + { + $$ = strbuf_append_str($1, $2->buf); + strbuf_free($2); + } +; + +identifier_template_inside_char : identifier_char_no_templates | ws_nonl +; + +digit_sequence : digit { $$ = strbuf_new(); strbuf_append_char($$, $1); } + | digit_sequence digit { $$ = strbuf_append_char($1, $2); } +; + +hexadecimal_number : '0' 'x' hexadecimal_digit_sequence + { + $$ = $3; + strbuf_prepend_str($$, "0x"); + } + | '0' 'X' hexadecimal_digit_sequence + { + $$ = $3; + strbuf_prepend_str($$, "0X"); + } +; + +hexadecimal_digit_sequence : hexadecimal_digit + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + } + | hexadecimal_digit_sequence hexadecimal_digit + { $$ = strbuf_append_char($1, $2); } +; + +hexadecimal_digit : digit + | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' + | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' +; + +digit : '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' +; + +nondigit : 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' + | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' + | 'x' | 'y' | 'z' + | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' + | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' + | 'X' | 'Y' | 'Z' + | '_' +; + + /* whitespace */ +ws : ws_nonl | '\n' | '\r' +; + + /* No newline.*/ +ws_nonl : '\t' | ' ' +; + + /* whitespace sequence without a newline */ +wss_nonl : ws_nonl + | wss_nonl ws_nonl +; + + /* whitespace sequence */ +wss : ws + | wss ws +; + + /* whitespace sequence allowed */ +wsa : + | wss +; + +keyword_in : 'i' 'n' +; + +keyword_at : 'a' 't' +; + +keyword_for : 'f' 'o' 'r' +; + +keyword_vtable : 'v' 't' 'a' 'b' 'l' 'e' +; + +keyword_from : 'f' 'r' 'o' 'm' +; + +keyword_thread: 'T' 'h' 'r' 'e' 'a' 'd' +; + +keyword_sighandler: '<' 's' 'i' 'g' 'n' 'a' 'l' ' ' 'h' 'a' 'n' 'd' 'l' 'e' 'r' ' ' 'c' 'a' 'l' 'l' 'e' 'd' '>' +; + +%% + +static bool scanner_echo = false; +static char *yyin; + +int yylex() +{ + char c = *yyin; + if (c == '\0') + return END; + ++yyin; + + /* Debug output. */ + if (scanner_echo) + putchar(c); + + yylval.c = c; + + /* Return a single char. */ + return c; +} + +/* This is the function that is actually called from outside. + * @returns + * Backtrace structure. Caller is responsible for calling + * backtrace_free() on this. + * Returns NULL when parsing failed. + */ +struct backtrace *backtrace_parse(char *input, bool debug_parser, bool debug_scanner) +{ + /* Skip the backtrace header information. */ + char *btnoheader_a = strstr(input, "\nThread "); + char *btnoheader_b = strstr(input, "\n#"); + char *btnoheader = input; + if (btnoheader_a) + { + if (btnoheader_b && btnoheader_b < btnoheader_a) + btnoheader = btnoheader_b + 1; + else + btnoheader = btnoheader_a + 1; + } + else if (btnoheader_b) + btnoheader = btnoheader_b + 1; + + /* Bug fixing hack for broken backtraces. + * Sometimes the empty line is missing before new Thread section. + * This is against rules, but a bug (now fixed) in Linux kernel caused + * this. + */ + char *thread_fixer = btnoheader + 1; + while ((thread_fixer = strstr(thread_fixer, "\nThread")) != NULL) + { + if (thread_fixer[-1] != '\n') + thread_fixer[-1] = '\n'; + + ++thread_fixer; + } + + /* Bug fixing hack for GDB - remove wrongly placed newlines from the backtrace. + * Sometimes there is a newline in the local variable section. + * This is caused by some GDB hooks. + * Example: rhbz#538440 + * #1 0x0000000000420939 in sync_deletions (mse=0x0, mfld=0x1b85020) + * at mail-stub-exchange.c:1119 + * status = <value optimized out> + * iter = 0x1af38d0 + * known_messages = 0x1b5c460Traceback (most recent call last): + * File "/usr/share/glib-2.0/gdb/glib.py", line 98, in next + * if long (node["key_hash"]) >= 2: + * RuntimeError: Cannot access memory at address 0x11 + * + * __PRETTY_FUNCTION__ = "sync_deletions" + * #2 0x0000000000423e6b in refresh_folder (stub=0x1b77f10 [MailStubExchange], + * ... + * + * The code removes every empty line (also those containing only spaces), + * which is not followed by a new Thread section. + * + * rhbz#555251 contains empty lines with spaces + */ + char *empty_line = btnoheader; + char *c = btnoheader; + while (*c) + { + if (*c == '\n') + { + char *cend = c + 1; + while (*cend == ' ' || *cend == '\t') + ++cend; + if (*cend == '\n' && 0 != strncmp(cend, "\nThread", strlen("\nThread"))) + memmove(c, cend, strlen(cend) + 1); + } + ++c; + } + while ((empty_line = strstr(empty_line, "\n\n")) != NULL) + { + if (0 != strncmp(empty_line, "\n\nThread", strlen("\n\nThread"))) + { + /* Remove the empty line by converting the first newline to char. */ + empty_line[0] = 'X'; + } + ++empty_line; + } + + /* Prepare for running parser. */ + g_backtrace = 0; + yyin = btnoheader; +#if YYDEBUG == 1 + if (debug_parser) + yydebug = 1; +#endif + scanner_echo = debug_scanner; + + /* Parse. */ + int failure = yyparse(); + + /* Separate debugging output. */ + if (scanner_echo) + putchar('\n'); + + if (failure) + { + if (g_backtrace) + { + backtrace_free(g_backtrace); + g_backtrace = NULL; + } + fprintf(stderr, "Error while parsing backtrace.\n"); + } + + return g_backtrace; +} diff --git a/lib/utils/copyfd.cpp b/lib/utils/copyfd.cpp new file mode 100644 index 00000000..cbcf7005 --- /dev/null +++ b/lib/utils/copyfd.cpp @@ -0,0 +1,171 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ + +/* + * Utility routines. + * + */ + +#include "abrtlib.h" + +#define CONFIG_FEATURE_COPYBUF_KB 4 + +static const char msg_write_error[] = "write error"; +static const char msg_read_error[] = "read error"; + +static off_t full_fd_action(int src_fd, int dst_fd, off_t size, int flags = 0) +{ + int status = -1; + off_t total = 0; + int last_was_seek = 0; +#if CONFIG_FEATURE_COPYBUF_KB <= 4 + char buffer[CONFIG_FEATURE_COPYBUF_KB * 1024]; + enum { buffer_size = sizeof(buffer) }; +#else + char *buffer; + int buffer_size; + + /* We want page-aligned buffer, just in case kernel is clever + * and can do page-aligned io more efficiently */ + buffer = mmap(NULL, CONFIG_FEATURE_COPYBUF_KB * 1024, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + /* ignored: */ -1, 0); + buffer_size = CONFIG_FEATURE_COPYBUF_KB * 1024; + if (buffer == MAP_FAILED) { + buffer = alloca(4 * 1024); + buffer_size = 4 * 1024; + } +#endif + + if (src_fd < 0) + goto out; + + if (!size) { + size = buffer_size; + status = 1; /* copy until eof */ + } + + while (1) { + ssize_t rd; + + rd = safe_read(src_fd, buffer, size > buffer_size ? buffer_size : size); + + if (!rd) { /* eof - all done */ + if (last_was_seek) { + if (lseek(dst_fd, -1, SEEK_CUR) < 0 + || safe_write(dst_fd, "", 1) != 1 + ) { + perror_msg("%s", msg_write_error); + break; + } + } + status = 0; + break; + } + if (rd < 0) { + perror_msg("%s", msg_read_error); + break; + } + /* dst_fd == -1 is a fake, else... */ + if (dst_fd >= 0) { + if (flags & COPYFD_SPARSE) { + ssize_t cnt = rd; + while (--cnt >= 0) + if (buffer[cnt] != 0) + goto need2write; + if (lseek(dst_fd, rd, SEEK_CUR) < 0) { + flags &= ~COPYFD_SPARSE; + goto need2write; + } + last_was_seek = 1; + } else { + need2write: + ssize_t wr = full_write(dst_fd, buffer, rd); + if (wr < rd) { + perror_msg("%s", msg_write_error); + break; + } + last_was_seek = 0; + } + } + total += rd; + if (status < 0) { /* if we aren't copying till EOF... */ + size -= rd; + if (!size) { + /* 'size' bytes copied - all done */ + status = 0; + break; + } + } + } + out: + +#if CONFIG_FEATURE_COPYBUF_KB > 4 + if (buffer_size != 4 * 1024) + munmap(buffer, buffer_size); +#endif + return status ? -1 : total; +} + +off_t copyfd_size(int fd1, int fd2, off_t size, int flags) +{ + if (size) { + return full_fd_action(fd1, fd2, size, flags); + } + return 0; +} + +void copyfd_exact_size(int fd1, int fd2, off_t size) +{ + off_t sz = copyfd_size(fd1, fd2, size); + if (sz == size) + return; + if (sz != -1) + error_msg_and_die("short read"); + /* if sz == -1, copyfd_XX already complained */ + xfunc_die(); +} + +off_t copyfd_eof(int fd1, int fd2, int flags) +{ + return full_fd_action(fd1, fd2, 0, flags); +} + +off_t copy_file(const char *src_name, const char *dst_name, int mode) +{ + off_t r; + int src = open(src_name, O_RDONLY); + if (src < 0) + { + perror_msg("Can't open '%s'", src_name); + return -1; + } + int dst = open(dst_name, O_WRONLY | O_TRUNC | O_CREAT, mode); + if (dst < 0) + { + close(src); + perror_msg("Can't open '%s'", dst_name); + return -1; + } + r = copyfd_eof(src, dst); + close(src); + close(dst); + return r; +} diff --git a/lib/utils/daemon.cpp b/lib/utils/daemon.cpp new file mode 100644 index 00000000..944aef0c --- /dev/null +++ b/lib/utils/daemon.cpp @@ -0,0 +1,139 @@ +/* + 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 "abrtlib.h" + +#define VAR_RUN_PID_FILE VAR_RUN"/abrtd.pid" + +static char *append_escaped(char *start, const char *s) +{ + char hex_char_buf[] = "\\x00"; + + *start++ = ' '; + char *dst = start; + const unsigned char *p = (unsigned char *)s; + + while (1) + { + const unsigned char *old_p = p; + while (*p > ' ' && *p <= 0x7e && *p != '\"' && *p != '\'' && *p != '\\') + p++; + if (dst == start) + { + if (p != (unsigned char *)s && *p == '\0') + { + /* entire word does not need escaping and quoting */ + strcpy(dst, s); + dst += strlen(s); + return dst; + } + *dst++ = '\''; + } + + strncpy(dst, (char *)old_p, (p - old_p)); + dst += (p - old_p); + + if (*p == '\0') + { + *dst++ = '\''; + *dst = '\0'; + return dst; + } + const char *a; + switch (*p) + { + case '\r': a = "\\r"; break; + case '\n': a = "\\n"; break; + case '\t': a = "\\t"; break; + case '\'': a = "\\\'"; break; + case '\"': a = "\\\""; break; + case '\\': a = "\\\\"; break; + case ' ': a = " "; break; + default: + hex_char_buf[2] = "0123456789abcdef"[*p >> 4]; + hex_char_buf[3] = "0123456789abcdef"[*p & 0xf]; + a = hex_char_buf; + } + strcpy(dst, a); + dst += strlen(a); + p++; + } +} + +// taken from kernel +#define COMMAND_LINE_SIZE 2048 +char* get_cmdline(pid_t pid) +{ + char path[sizeof("/proc/%lu/cmdline") + sizeof(long)*3]; + char cmdline[COMMAND_LINE_SIZE]; + char escaped_cmdline[COMMAND_LINE_SIZE*4 + 4]; + + escaped_cmdline[1] = '\0'; + sprintf(path, "/proc/%lu/cmdline", (long)pid); + int fd = open(path, O_RDONLY); + if (fd >= 0) + { + int len = read(fd, cmdline, sizeof(cmdline) - 1); + close(fd); + + if (len > 0) + { + cmdline[len] = '\0'; + char *src = cmdline; + char *dst = escaped_cmdline; + while ((src - cmdline) < len) + { + dst = append_escaped(dst, src); + src += strlen(src) + 1; + } + } + } + + return xstrdup(escaped_cmdline + 1); /* +1 skips extraneous leading space */ +} + +int daemon_is_ok() +{ + int fd = open(VAR_RUN_PID_FILE, O_RDONLY); + if (fd < 0) + { + return 0; + } + + char pid[sizeof(pid_t)*3 + 2]; + int len = read(fd, pid, sizeof(pid)-1); + close(fd); + if (len <= 0) + return 0; + + pid[len] = '\0'; + *strchrnul(pid, '\n') = '\0'; + /* paranoia: we don't want to check /proc//stat or /proc///stat */ + if (pid[0] == '\0' || pid[0] == '/') + return 0; + + /* TODO: maybe readlink and check that it is "xxx/abrt"? */ + char path[sizeof("/proc/%s/stat") + sizeof(pid)]; + sprintf(path, "/proc/%s/stat", pid); + struct stat sb; + if (stat(path, &sb) == -1) + { + return 0; + } + + return 1; +} diff --git a/lib/utils/dirsize.cpp b/lib/utils/dirsize.cpp new file mode 100644 index 00000000..739b6b73 --- /dev/null +++ b/lib/utils/dirsize.cpp @@ -0,0 +1,100 @@ +/* + 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 "abrtlib.h" + +using namespace std; + +double get_dirsize(const char *pPath) +{ + DIR *dp = opendir(pPath); + if (dp == NULL) + return 0; + + struct dirent *ep; + struct stat statbuf; + double size = 0; + while ((ep = readdir(dp)) != NULL) + { + if (dot_or_dotdot(ep->d_name)) + continue; + string dname = concat_path_file(pPath, ep->d_name); + if (lstat(dname.c_str(), &statbuf) != 0) + continue; + if (S_ISDIR(statbuf.st_mode)) + { + size += get_dirsize(dname.c_str()); + } + else if (S_ISREG(statbuf.st_mode)) + { + size += statbuf.st_size; + } + } + closedir(dp); + return size; +} + +double get_dirsize_find_largest_dir( + const char *pPath, + string *worst_dir, + const char *excluded) +{ + DIR *dp = opendir(pPath); + if (dp == NULL) + return 0; + + struct dirent *ep; + struct stat statbuf; + double size = 0; + double maxsz = 0; + while ((ep = readdir(dp)) != NULL) + { + if (dot_or_dotdot(ep->d_name)) + continue; + string dname = concat_path_file(pPath, ep->d_name); + if (lstat(dname.c_str(), &statbuf) != 0) + continue; + if (S_ISDIR(statbuf.st_mode)) + { + double sz = get_dirsize(dname.c_str()); + size += sz; + + if (worst_dir && (!excluded || strcmp(excluded, ep->d_name) != 0)) + { + /* Calculate "weighted" size and age + * w = sz_kbytes * age_mins */ + sz /= 1024; + long age = (time(NULL) - statbuf.st_mtime) / 60; + if (age > 0) + sz *= age; + + if (sz > maxsz) + { + maxsz = sz; + *worst_dir = ep->d_name; + } + } + } + else if (S_ISREG(statbuf.st_mode)) + { + size += statbuf.st_size; + } + } + closedir(dp); + return size; +} diff --git a/lib/utils/encbase64.cpp b/lib/utils/encbase64.cpp new file mode 100644 index 00000000..6a6f1f75 --- /dev/null +++ b/lib/utils/encbase64.cpp @@ -0,0 +1,78 @@ +/* + * Copyright 2006 Rob Landley <rob@landley.net> + * + * Licensed under GPLv2 or later. + */ +#include "abrtlib.h" /* xmalloc */ + +/* Conversion table for base 64 */ +static const char tbl_base64[65 /*+ 2*/] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', + '=' /* termination character */, + // '\n', '\0' /* needed for uudecode.c */ +}; + +/* Conversion table for uuencode +const char tbl_uuencode[65] ALIGN1 = { + '`', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`' +}; +*/ + +/* + * Encode bytes at S of length LENGTH. + * Result will be 0-terminated, and must point to a writable + * buffer of at least 1+BASE64_LENGTH(length) bytes, + * where BASE64_LENGTH(len) = 4 * ((LENGTH + 2) / 3) + */ +static void encode_64bit(char *p, const void *src, int length, const char *tbl) +{ + const unsigned char *s = (const unsigned char *)src; + + /* Transform the 3x8 bits to 4x6 bits */ + while (length > 0) { + unsigned s1, s2; + + /* Are s[1], s[2] valid or should be assumed 0? */ + s1 = s2 = 0; + length -= 3; /* can be >=0, -1, -2 */ + if (length >= -1) { + s1 = s[1]; + if (length >= 0) + s2 = s[2]; + } + *p++ = tbl[s[0] >> 2]; + *p++ = tbl[((s[0] & 3) << 4) + (s1 >> 4)]; + *p++ = tbl[((s1 & 0xf) << 2) + (s2 >> 6)]; + *p++ = tbl[s2 & 0x3f]; + s += 3; + } + /* Zero-terminate */ + *p = '\0'; + /* If length is -2 or -1, pad last char or two */ + while (length) { + *--p = tbl[64]; + length++; + } +} + +char *encode_base64(const void *src, int length) +{ + char *dst = (char *)xmalloc(4 * ((length + 2) / 3) + 1); + encode_64bit(dst, src, length, tbl_base64); + return dst; +} diff --git a/lib/utils/hooklib.cpp b/lib/utils/hooklib.cpp new file mode 100644 index 00000000..68970661 --- /dev/null +++ b/lib/utils/hooklib.cpp @@ -0,0 +1,133 @@ +/* + 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 "abrtlib.h" +#include "hooklib.h" +#include "debug_dump.h" +#include <sys/statvfs.h> + +using namespace std; + +void parse_conf(const char *additional_conf, unsigned *setting_MaxCrashReportsSize, bool *setting_MakeCompatCore, bool *setting_SaveBinaryImage) +{ + FILE *fp = fopen(CONF_DIR"/abrt.conf", "r"); + if (!fp) + return; + + char line[256]; + while (1) + { + if (fgets(line, sizeof(line), fp) == NULL) + { + fclose(fp); + if (additional_conf) + { + /* Next .conf file plz */ + fp = fopen(additional_conf, "r"); + if (fp) + { + additional_conf = NULL; + continue; + } + } + break; + } + + strchrnul(line, '\n')[0] = '\0'; + const char *p = skip_whitespace(line); +#undef DIRECTIVE +#define DIRECTIVE "MaxCrashReportsSize" + if (setting_MaxCrashReportsSize && strncmp(p, DIRECTIVE, sizeof(DIRECTIVE)-1) == 0) + { + p = skip_whitespace(p + sizeof(DIRECTIVE)-1); + if (*p != '=') + continue; + p = skip_whitespace(p + 1); + if (isdigit(*p)) + { + /* x1.25: go a bit up, so that usual in-daemon trimming + * kicks in first, and we don't "fight" with it. */ + *setting_MaxCrashReportsSize = (unsigned long)xatou(p) * 5 / 4; + } + continue; + } +#undef DIRECTIVE +#define DIRECTIVE "MakeCompatCore" + if (setting_MakeCompatCore && strncmp(p, DIRECTIVE, sizeof(DIRECTIVE)-1) == 0) + { + p = skip_whitespace(p + sizeof(DIRECTIVE)-1); + if (*p != '=') + continue; + p = skip_whitespace(p + 1); + *setting_MakeCompatCore = string_to_bool(p); + continue; + } +#undef DIRECTIVE +#define DIRECTIVE "SaveBinaryImage" + if (setting_SaveBinaryImage && strncmp(p, DIRECTIVE, sizeof(DIRECTIVE)-1) == 0) + { + p = skip_whitespace(p + sizeof(DIRECTIVE)-1); + if (*p != '=') + continue; + p = skip_whitespace(p + 1); + *setting_SaveBinaryImage = string_to_bool(p); + continue; + } +#undef DIRECTIVE + /* add more 'if (strncmp(p, DIRECTIVE, sizeof(DIRECTIVE)-1) == 0)' here... */ + } +} + +void check_free_space(unsigned setting_MaxCrashReportsSize) +{ + struct statvfs vfs; + if (statvfs(DEBUG_DUMPS_DIR, &vfs) != 0) + { + perror_msg_and_die("statvfs('%s')", DEBUG_DUMPS_DIR); + } + + /* Check that at least MaxCrashReportsSize/4 MBs are free */ + + /* fs_free_mb_x4 ~= vfs.f_bfree * vfs.f_bsize * 4, expressed in MBytes. + * Need to neither overflow nor round f_bfree down too much. */ + unsigned long fs_free_mb_x4 = ((unsigned long long)vfs.f_bfree / (1024/4)) * vfs.f_bsize / 1024; + if (fs_free_mb_x4 < setting_MaxCrashReportsSize) + { + error_msg_and_die("aborting dump: only %luMiB is available on %s", fs_free_mb_x4 / 4, DEBUG_DUMPS_DIR); + } +} + +/* rhbz#539551: "abrt going crazy when crashing process is respawned". + * Check total size of dump dir, if it overflows, + * delete oldest/biggest dumps. + */ +void trim_debug_dumps(unsigned setting_MaxCrashReportsSize, const char *exclude_path) +{ + int count = 10; + string worst_dir; + while (--count >= 0) + { + const char *base_dirname = strrchr(exclude_path, '/') + 1; /* never NULL */ + /* We exclude our own dump from candidates for deletion (3rd param): */ + double dirsize = get_dirsize_find_largest_dir(DEBUG_DUMPS_DIR, &worst_dir, base_dirname); + if (dirsize / (1024*1024) < setting_MaxCrashReportsSize || worst_dir == "") + break; + log("size of '%s' >= %u MB, deleting '%s'", DEBUG_DUMPS_DIR, setting_MaxCrashReportsSize, worst_dir.c_str()); + delete_debug_dump_dir(concat_path_file(DEBUG_DUMPS_DIR, worst_dir.c_str()).c_str()); + worst_dir = ""; + } +} diff --git a/lib/utils/hooklib.h b/lib/utils/hooklib.h new file mode 100644 index 00000000..1651204f --- /dev/null +++ b/lib/utils/hooklib.h @@ -0,0 +1,21 @@ +/* + 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. +*/ + +void parse_conf(const char *additional_conf, unsigned *setting_MaxCrashReportsSize, bool *setting_MakeCompatCore, bool *setting_SaveBinaryImage); +void check_free_space(unsigned setting_MaxCrashReportsSize); +void trim_debug_dumps(unsigned setting_MaxCrashReportsSize, const char *exclude_path); diff --git a/lib/utils/logging.c b/lib/utils/logging.c new file mode 100644 index 00000000..a6eaa50f --- /dev/null +++ b/lib/utils/logging.c @@ -0,0 +1,143 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ + +/* + * Utility routines. + * + */ + +#include "logging.h" + +int xfunc_error_retval = EXIT_FAILURE; +int g_verbose; +int logmode = LOGMODE_STDIO; +const char *msg_prefix = ""; +const char *msg_eol = "\n"; +void (*g_custom_logger)(const char*); + +void xfunc_die(void) +{ + exit(xfunc_error_retval); +} + +static void verror_msg_helper(const char *s, va_list p, const char* strerr, int flags) +{ + char *msg; + int prefix_len, strerr_len, msgeol_len, used; + + if (!logmode) + return; + + used = vasprintf(&msg, s, p); + if (used < 0) + return; + + /* This is ugly and costs +60 bytes compared to multiple + * fprintf's, but is guaranteed to do a single write. + * This is needed for e.g. when multiple children + * can produce log messages simultaneously. */ + + prefix_len = strlen(msg_prefix); + strerr_len = strerr ? strlen(strerr) : 0; + msgeol_len = strlen(msg_eol); + /* +3 is for ": " before strerr and for terminating NUL */ + msg = (char*) xrealloc(msg, prefix_len + used + strerr_len + msgeol_len + 3); + /* TODO: maybe use writev instead of memmoving? Need full_writev? */ + if (prefix_len) { + memmove(msg + prefix_len, msg, used); + used += prefix_len; + memcpy(msg, msg_prefix, prefix_len); + } + if (strerr) { + if (s[0]) { + msg[used++] = ':'; + msg[used++] = ' '; + } + strcpy(&msg[used], strerr); + used += strerr_len; + } + strcpy(&msg[used], msg_eol); + + if (flags & LOGMODE_STDIO) { + fflush(stdout); + full_write(STDERR_FILENO, msg, used + msgeol_len); + } + msg[used] = '\0'; /* remove msg_eol (usually "\n") */ + if (flags & LOGMODE_SYSLOG) { + syslog(LOG_ERR, "%s", msg + prefix_len); + } + if ((flags & LOGMODE_CUSTOM) && g_custom_logger) { + g_custom_logger(msg + prefix_len); + } + free(msg); +} + +void log_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + verror_msg_helper(s, p, NULL, logmode); + va_end(p); +} + +void error_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + verror_msg_helper(s, p, NULL, (logmode | LOGMODE_CUSTOM)); + va_end(p); +} + +void error_msg_and_die(const char *s, ...) +{ + va_list p; + + va_start(p, s); + verror_msg_helper(s, p, NULL, (logmode | LOGMODE_CUSTOM)); + va_end(p); + xfunc_die(); +} + +void perror_msg_and_die(const char *s, ...) +{ + va_list p; + + va_start(p, s); + /* Guard against "<error message>: Success" */ + verror_msg_helper(s, p, errno ? strerror(errno) : NULL, (logmode | LOGMODE_CUSTOM)); + va_end(p); + xfunc_die(); +} + +void perror_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + /* Guard against "<error message>: Success" */ + verror_msg_helper(s, p, errno ? strerror(errno) : NULL, (logmode | LOGMODE_CUSTOM)); + va_end(p); +} + +void die_out_of_memory(void) +{ + error_msg_and_die("Out of memory, exiting"); +} diff --git a/lib/utils/logging.h b/lib/utils/logging.h new file mode 100644 index 00000000..f682feb0 --- /dev/null +++ b/lib/utils/logging.h @@ -0,0 +1,83 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ + +#ifndef LOGGING_H +#define LOGGING_H + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <sys/syslog.h> + +#include "read_write.h" +#include "xfuncs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NORETURN __attribute__ ((noreturn)) + + +enum { + LOGMODE_NONE = 0, + LOGMODE_STDIO = (1 << 0), + LOGMODE_SYSLOG = (1 << 1), + LOGMODE_BOTH = LOGMODE_SYSLOG + LOGMODE_STDIO, + LOGMODE_CUSTOM = (1 << 2), +}; + +extern void (*g_custom_logger)(const char*); +extern const char *msg_prefix; +extern const char *msg_eol; +extern int logmode; +extern int xfunc_error_retval; +void xfunc_die(void) NORETURN; +void log_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +/* It's a macro, not function, since it collides with log() from math.h */ +#undef log +#define log(...) log_msg(__VA_ARGS__) +/* error_msg family will use g_custom_logger. log_msg does not. */ +void error_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +void error_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); +/* Reports error message with libc's errno error description attached. */ +void perror_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +void perror_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); +void perror_nomsg_and_die(void) NORETURN; +void perror_nomsg(void); +void verror_msg(const char *s, va_list p, const char *strerr); +void die_out_of_memory(void) NORETURN; + +/* Verbosity level */ +extern int g_verbose; +/* VERB1 log("what you sometimes want to see, even on a production box") */ +#define VERB1 if (g_verbose >= 1) +/* VERB2 log("debug message, not going into insanely small details") */ +#define VERB2 if (g_verbose >= 2) +/* VERB3 log("lots and lots of details") */ +#define VERB3 if (g_verbose >= 3) +/* there is no level > 3 */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/utils/make_descr.cpp b/lib/utils/make_descr.cpp new file mode 100644 index 00000000..46d9644d --- /dev/null +++ b/lib/utils/make_descr.cpp @@ -0,0 +1,236 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 "abrtlib.h" +#include "crash_types.h" +#include "debug_dump.h" /* FILENAME_ARCHITECTURE etc */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#if ENABLE_NLS +# include <libintl.h> +# define _(S) gettext(S) +#else +# define _(S) (S) +#endif + +using namespace std; + +static void add_content(bool &was_multiline, string& description, const char *header, const char *content) +{ + /* We separate multiline contents with emply line */ + if (was_multiline) + description += '\n'; + + while (content[0] == '\n') + content++; + + if (strchr(content, '\n') == NULL) + { + if (skip_whitespace(content)[0] == '\0') + { + /* empty, dont report at all */ + return; + } + /* one string value, like OS release */ + description += header; + description += ": "; + description += content; + description += '\n'; + was_multiline = 0; + } + else + { + /* multi-string value, like backtrace */ + if (!was_multiline && description.size() != 0) /* if wasn't yet separated */ + description += '\n'; /* do it now */ + description += header; + description += "\n-----\n"; + description += content; + if (content[strlen(content) - 1] != '\n') + description += '\n'; + was_multiline = 1; + } +} + +/* Items we don't want to include */ +static const char *const blacklisted_items[] = { + FILENAME_ANALYZER , + FILENAME_COREDUMP , + FILENAME_DESCRIPTION, /* package description - basically useless */ + FILENAME_HOSTNAME , + FILENAME_GLOBAL_UUID, + CD_UUID , + CD_INFORMALL , + CD_DUPHASH , + CD_DUMPDIR , + CD_COUNT , + CD_REPORTED , + CD_MESSAGE , + NULL +}; + +string make_description_bz(const map_crash_data_t& pCrashData) +{ + string description; + string long_description; + + map_crash_data_t::const_iterator it = pCrashData.begin(); + for (; it != pCrashData.end(); it++) + { + const string& itemname = it->first; + const string& type = it->second[CD_TYPE]; + const string& content = it->second[CD_CONTENT]; + if (type == CD_TXT) + { + /* Skip items we are not interested in */ + const char *const *bl = blacklisted_items; + while (*bl) + { + if (itemname == *bl) + break; + bl++; + } + if (*bl) + continue; /* blacklisted */ + if (content == "1.\n2.\n3.\n") + continue; /* user did not change default "How to reproduce" */ + + if (content.size() <= CD_TEXT_ATT_SIZE) + { + /* Add small (less than few kb) text items inline */ + bool was_multiline = 0; + string tmp; + add_content(was_multiline, + tmp, + /* "reproduce: blah" looks ugly, fixing: */ + itemname == FILENAME_REPRODUCE ? "How to reproduce" : itemname.c_str(), + content.c_str() + ); + + if (was_multiline) + { + /* Not one-liner */ + if (long_description.size() != 0) + long_description += '\n'; + long_description += tmp; + } + else + { + description += tmp; + } + } else { + bool was_multiline = 0; + add_content(was_multiline, description, "Attached file", itemname.c_str()); + } + } + } + + /* One-liners go first, then multi-line items */ + if (description.size() != 0 && long_description.size() != 0) + { + description += '\n'; + } + description += long_description; + + return description; +} + +string make_description_logger(const map_crash_data_t& pCrashData) +{ + string description; + string long_description; + + map_crash_data_t::const_iterator it = pCrashData.begin(); + for (; it != pCrashData.end(); it++) + { + const string &filename = it->first; + const string &type = it->second[CD_TYPE]; + const string &content = it->second[CD_CONTENT]; + if (type == CD_TXT + || type == CD_BIN + ) { + /* Skip items we are not interested in */ + const char *const *bl = blacklisted_items; + while (*bl) + { + if (filename == *bl) + break; + bl++; + } + if (*bl) + continue; /* blacklisted */ + if (content == "1.\n2.\n3.\n") + continue; /* user did not change default "How to reproduce" */ + + bool was_multiline = 0; + string tmp; + add_content(was_multiline, tmp, filename.c_str(), content.c_str()); + + if (was_multiline) + { + if (long_description.size() != 0) + long_description += '\n'; + long_description += tmp; + } + else + { + description += tmp; + } + } + } + + if (description.size() != 0 && long_description.size() != 0) + { + description += '\n'; + } + description += long_description; + + return description; +} + +string make_description_reproduce_comment(const map_crash_data_t& pCrashData) +{ + map_crash_data_t::const_iterator end = pCrashData.end(); + map_crash_data_t::const_iterator it; + + string howToReproduce; + it = pCrashData.find(FILENAME_REPRODUCE); + if (it != end) + { + if ((it->second[CD_CONTENT].size() > 0) + && (it->second[CD_CONTENT] != "1.\n2.\n3.\n")) + { + howToReproduce = "\n\nHow to reproduce\n" + "-----\n"; + howToReproduce += it->second[CD_CONTENT]; + } + } + string comment; + it = pCrashData.find(FILENAME_COMMENT); + if (it != end) + { + if (it->second[CD_CONTENT].size() > 0) + { + comment = "\n\nComment\n" + "-----\n"; + comment += it->second[CD_CONTENT]; + } + } + return howToReproduce + comment; +} diff --git a/lib/utils/numtoa.cpp b/lib/utils/numtoa.cpp new file mode 100644 index 00000000..061da553 --- /dev/null +++ b/lib/utils/numtoa.cpp @@ -0,0 +1,34 @@ +/* + Number to string conversions + + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 "abrtlib.h" + +std::string unsigned_to_string(unsigned long long x) +{ + char buf[sizeof(x)*3]; + sprintf(buf, "%llu", x); + return buf; +} +std::string signed_to_string(long long x) +{ + char buf[sizeof(x)*3]; + sprintf(buf, "%lld", x); + return buf; +} diff --git a/lib/utils/parse_release.cpp b/lib/utils/parse_release.cpp new file mode 100644 index 00000000..3d6b572f --- /dev/null +++ b/lib/utils/parse_release.cpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 "abrtlib.h" +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "strbuf.h" + +// caller is reposible for freeing *product* and *version* +void parse_release(const char *release, char** product, char** version) +{ + if (strstr(release, "Rawhide")) + { + *product = xstrdup("Fedora"); + *version = xstrdup("rawhide"); + VERB3 log("%s: version:'%s' product:'%s'", __func__, *version, *product); + return; + } + + struct strbuf *buf_product = strbuf_new(); + if (strstr(release, "Fedora")) + strbuf_append_str(buf_product, "Fedora"); + else if (strstr(release, "Red Hat Enterprise Linux")) + strbuf_append_str(buf_product, "Red Hat Enterprise Linux "); + + const char *r = strstr(release, "release"); + const char *space = r ? strchr(r, ' ') : NULL; + + struct strbuf *buf_version = strbuf_new(); + if (space++) + { + while (*space != '\0' && *space != ' ') + { + /* Eat string like "5.2" */ + strbuf_append_char(buf_version, *space); + if ((strcmp(buf_product->buf, "Red Hat Enterprise Linux ") == 0)) + strbuf_append_char(buf_product, *space); + + space++; + } + } + + *version = strbuf_free_nobuf(buf_version); + *product = strbuf_free_nobuf(buf_product); + + VERB3 log("%s: version:'%s' product:'%s'", __func__, *version, *product); +} diff --git a/lib/utils/read_write.c b/lib/utils/read_write.c new file mode 100644 index 00000000..3f2133e6 --- /dev/null +++ b/lib/utils/read_write.c @@ -0,0 +1,94 @@ +/* + * Utility routines. + * + * Licensed under GPLv2 or later, see file COPYING in this tarball for details. + */ + +#include "read_write.h" + +ssize_t safe_read(int fd, void *buf, size_t count) +{ + ssize_t n; + + do { + n = read(fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} + +ssize_t full_read(int fd, void *buf, size_t len) +{ + ssize_t cc; + ssize_t total; + + total = 0; + + while (len) { + cc = safe_read(fd, buf, len); + + if (cc < 0) { + if (total) { + /* we already have some! */ + /* user can do another read to know the error code */ + return total; + } + return cc; /* read() returns -1 on failure. */ + } + if (cc == 0) + break; + buf = ((char *)buf) + cc; + total += cc; + len -= cc; + } + + return total; +} + +/* Die with an error message if we can't read the entire buffer. */ +void xread(int fd, void *buf, size_t count) +{ + if (count) { + ssize_t size = full_read(fd, buf, count); + if ((size_t)size != count) + error_msg_and_die("short read"); + } +} + +ssize_t safe_write(int fd, const void *buf, size_t count) +{ + ssize_t n; + + do { + n = write(fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} + +ssize_t full_write(int fd, const void *buf, size_t len) +{ + ssize_t cc; + ssize_t total; + + total = 0; + + while (len) { + cc = safe_write(fd, buf, len); + + if (cc < 0) { + if (total) { + /* we already wrote some! */ + /* user can do another write to know the error code */ + return total; + } + return cc; /* write() returns -1 on failure. */ + } + + total += cc; + buf = ((const char *)buf) + cc; + len -= cc; + } + + return total; +} diff --git a/lib/utils/read_write.h b/lib/utils/read_write.h new file mode 100644 index 00000000..5a351869 --- /dev/null +++ b/lib/utils/read_write.h @@ -0,0 +1,49 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ + +#ifndef READ_WRITE_H +#define READ_WRITE_H + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include "logging.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +// NB: will return short read on error, not -1, +// if some data was read before error occurred +void xread(int fd, void *buf, size_t count); + +ssize_t safe_read(int fd, void *buf, size_t count); +ssize_t safe_write(int fd, const void *buf, size_t count); + +ssize_t full_read(int fd, void *buf, size_t count); +ssize_t full_write(int fd, const void *buf, size_t count); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/utils/skip_whitespace.cpp b/lib/utils/skip_whitespace.cpp new file mode 100644 index 00000000..816928bf --- /dev/null +++ b/lib/utils/skip_whitespace.cpp @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2003 Manuel Novoa III <mjn3@codepoet.org> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include "abrtlib.h" + +char* skip_whitespace(const char *s) +{ + /* NB: isspace('\0') returns 0 */ + while (isspace(*s)) ++s; + + return (char *) s; +} + +char* skip_non_whitespace(const char *s) +{ + while (*s && !isspace(*s)) ++s; + + return (char *) s; +} diff --git a/lib/utils/spawn.cpp b/lib/utils/spawn.cpp new file mode 100644 index 00000000..dace7d4b --- /dev/null +++ b/lib/utils/spawn.cpp @@ -0,0 +1,142 @@ +/* + * Utility routines. + * + * Licensed under GPLv2, see file COPYING in this tarball for details. + */ +#include "abrtlib.h" + +using namespace std; + +static string concat_str_vector(char **strings) +{ + string result; + while (*strings) { + result += *strings++; + if (*strings) + result += ' '; + } + return result; +} + +/* Returns pid */ +pid_t fork_execv_on_steroids(int flags, + char **argv, + int *pipefds, + char **unsetenv_vec, + const char *dir, + uid_t uid) +{ + pid_t child; + /* Reminder: [0] is read end, [1] is write end */ + int pipe_to_child[2]; + int pipe_fm_child[2]; + + /* Sanitize flags */ + if (!pipefds) + flags &= ~(EXECFLG_INPUT | EXECFLG_OUTPUT); + + if (flags & EXECFLG_INPUT) + xpipe(pipe_to_child); + if (flags & EXECFLG_OUTPUT) + xpipe(pipe_fm_child); + + fflush(NULL); + child = fork(); + if (child == -1) { + perror_msg_and_die("fork"); + } + if (child == 0) { + /* Child */ + + if (dir) + xchdir(dir); + + if (flags & EXECFLG_SETGUID) { + struct passwd* pw = getpwuid(uid); + gid_t gid = pw ? pw->pw_gid : uid; + setgroups(1, &gid); + xsetregid(gid, gid); + xsetreuid(uid, uid); + } + + if (unsetenv_vec) { + while (*unsetenv_vec) + unsetenv(*unsetenv_vec++); + } + + /* Play with stdio descriptors */ + if (flags & EXECFLG_INPUT) { + xmove_fd(pipe_to_child[0], STDIN_FILENO); + close(pipe_to_child[1]); + } else if (flags & EXECFLG_INPUT_NUL) { + xmove_fd(xopen("/dev/null", O_RDWR), STDIN_FILENO); + } + if (flags & EXECFLG_OUTPUT) { + xmove_fd(pipe_fm_child[1], STDOUT_FILENO); + close(pipe_fm_child[0]); + } else if (flags & EXECFLG_OUTPUT_NUL) { + xmove_fd(xopen("/dev/null", O_RDWR), STDOUT_FILENO); + } + + /* This should be done BEFORE stderr redirect */ + VERB1 log("Executing: %s", concat_str_vector(argv).c_str()); + + if (flags & EXECFLG_ERR2OUT) { + /* Want parent to see errors in the same stream */ + xdup2(STDOUT_FILENO, STDERR_FILENO); + } else if (flags & EXECFLG_ERR_NUL) { + xmove_fd(xopen("/dev/null", O_RDWR), STDERR_FILENO); + } + + if (flags & EXECFLG_SETSID) + setsid(); + + execvp(argv[0], argv); + if (!(flags & EXECFLG_QUIET)) + perror_msg("Can't execute '%s'", argv[0]); + exit(127); /* shell uses this exitcode in this case */ + } + + if (flags & EXECFLG_INPUT) { + close(pipe_to_child[0]); + pipefds[1] = pipe_to_child[1]; + } + if (flags & EXECFLG_OUTPUT) { + close(pipe_fm_child[1]); + pipefds[0] = pipe_fm_child[0]; + } + + return child; +} + +char *run_in_shell_and_save_output(int flags, + const char *cmd, + const char *dir, + size_t *size_p) +{ + flags |= EXECFLG_OUTPUT; + flags &= ~EXECFLG_INPUT; + + const char *argv[] = { "/bin/sh", "-c", cmd, NULL }; + int pipeout[2]; + pid_t child = fork_execv_on_steroids(flags, (char **)argv, pipeout, + /*unsetenv_vec:*/ NULL, dir, /*uid (unused):*/ 0); + + size_t pos = 0; + char *result = NULL; + while (1) { + result = (char*) xrealloc(result, pos + 4*1024 + 1); + size_t sz = safe_read(pipeout[0], result + pos, 4*1024); + if (sz <= 0) { + break; + } + pos += sz; + } + result[pos] = '\0'; + if (size_p) + *size_p = pos; + close(pipeout[0]); + waitpid(child, NULL, 0); + + return result; +} diff --git a/lib/utils/strbuf.c b/lib/utils/strbuf.c new file mode 100644 index 00000000..9ad74714 --- /dev/null +++ b/lib/utils/strbuf.c @@ -0,0 +1,147 @@ +/* + String buffer implementation + + 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 "strbuf.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <stdarg.h> +#include "xfuncs.h" + + +int prefixcmp(const char *str, const char *prefix) +{ + for (; ; str++, prefix++) + if (!*prefix) + return 0; + else if (*str != *prefix) + return (unsigned char)*prefix - (unsigned char)*str; +} + +int suffixcmp(const char *str, const char *suffix) +{ + int len_minus_suflen = strlen(str) - strlen(suffix); + if (len_minus_suflen < 0) + return len_minus_suflen; + else + return strcmp(str + len_minus_suflen, suffix); +} + +struct strbuf *strbuf_new() +{ + struct strbuf *buf = xmalloc(sizeof(struct strbuf)); + buf->alloc = 8; + buf->len = 0; + buf->buf = xmalloc(buf->alloc); + buf->buf[buf->len] = '\0'; + return buf; +} + +void strbuf_free(struct strbuf *strbuf) +{ + free(strbuf->buf); + free(strbuf); +} + +char* strbuf_free_nobuf(struct strbuf *strbuf) +{ + char *ret = strbuf->buf; + free(strbuf); + return ret; +} + + +void strbuf_clear(struct strbuf *strbuf) +{ + assert(strbuf->alloc > 0); + strbuf->len = 0; + strbuf->buf[0] = '\0'; +} + +/* Ensures that the buffer can be extended by num characters + * without touching malloc/realloc. + */ +static void strbuf_grow(struct strbuf *strbuf, int num) +{ + if (strbuf->len + num + 1 > strbuf->alloc) + { + while (strbuf->len + num + 1 > strbuf->alloc) + strbuf->alloc *= 2; /* huge grow = infinite loop */ + + strbuf->buf = xrealloc(strbuf->buf, strbuf->alloc); + } +} + +struct strbuf *strbuf_append_char(struct strbuf *strbuf, char c) +{ + strbuf_grow(strbuf, 1); + strbuf->buf[strbuf->len++] = c; + strbuf->buf[strbuf->len] = '\0'; + return strbuf; +} + +struct strbuf *strbuf_append_str(struct strbuf *strbuf, const char *str) +{ + int len = strlen(str); + strbuf_grow(strbuf, len); + assert(strbuf->len + len < strbuf->alloc); + strcpy(strbuf->buf + strbuf->len, str); + strbuf->len += len; + return strbuf; +} + +struct strbuf *strbuf_prepend_str(struct strbuf *strbuf, const char *str) +{ + int len = strlen(str); + strbuf_grow(strbuf, len); + assert(strbuf->len + len < strbuf->alloc); + memmove(strbuf->buf + len, strbuf->buf, strbuf->len + 1); + memcpy(strbuf->buf, str, len); + strbuf->len += len; + return strbuf; +} + +struct strbuf *strbuf_append_strf(struct strbuf *strbuf, const char *format, ...) +{ + va_list p; + char *string_ptr; + + va_start(p, format); + string_ptr = xvasprintf(format, p); + va_end(p); + + strbuf_append_str(strbuf, string_ptr); + free(string_ptr); + return strbuf; +} + +struct strbuf *strbuf_prepend_strf(struct strbuf *strbuf, const char *format, ...) +{ + va_list p; + char *string_ptr; + + va_start(p, format); + string_ptr = xvasprintf(format, p); + va_end(p); + + strbuf_prepend_str(strbuf, string_ptr); + free(string_ptr); + return strbuf; +} diff --git a/lib/utils/strbuf.h b/lib/utils/strbuf.h new file mode 100644 index 00000000..3f3ba51a --- /dev/null +++ b/lib/utils/strbuf.h @@ -0,0 +1,51 @@ +/* + strbuf.h - string buffer + + 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. +*/ +#ifndef STRBUF_H +#define STRBUF_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct strbuf +{ + /* Size of the allocated buffer. Always > 0. */ + int alloc; + /* Length of the message, without the ending \0. */ + int len; + char *buf; +}; + +extern struct strbuf *strbuf_new(); +extern void strbuf_free(struct strbuf *strbuf); +/* Releases strbuf, but not the internal buffer. */ +extern char* strbuf_free_nobuf(struct strbuf *strbuf); +extern void strbuf_clear(struct strbuf *strbuf); +extern struct strbuf *strbuf_append_char(struct strbuf *strbuf, char c); +extern struct strbuf *strbuf_append_str(struct strbuf *strbuf, const char *str); +extern struct strbuf *strbuf_prepend_str(struct strbuf *strbuf, const char *str); +extern struct strbuf *strbuf_append_strf(struct strbuf *strbuf, const char *format, ...); +extern struct strbuf *strbuf_prepend_strf(struct strbuf *strbuf, const char *format, ...); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/utils/stringops.cpp b/lib/utils/stringops.cpp new file mode 100644 index 00000000..7bc5413f --- /dev/null +++ b/lib/utils/stringops.cpp @@ -0,0 +1,57 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 "abrtlib.h" + +void parse_args(const char *psArgs, vector_string_t& pArgs, int quote) +{ + unsigned ii; + bool inside_quotes = false; + std::string item; + + for (ii = 0; psArgs[ii]; ii++) + { + if (quote != -1) + { + if (psArgs[ii] == quote) + { + inside_quotes = !inside_quotes; + continue; + } + /* inside quotes we support escaping with \x */ + if (inside_quotes && psArgs[ii] == '\\' && psArgs[ii+1]) + { + ii++; + item += psArgs[ii]; + continue; + } + } + if (psArgs[ii] == ',' && !inside_quotes) + { + pArgs.push_back(item); + item.clear(); + continue; + } + item += psArgs[ii]; + } + + if (item.size() != 0) + { + pArgs.push_back(item); + } +} diff --git a/lib/utils/test.cpp b/lib/utils/test.cpp new file mode 100644 index 00000000..35edb0c5 --- /dev/null +++ b/lib/utils/test.cpp @@ -0,0 +1,107 @@ +/* + test.cpp - simple library test + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@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 "MiddleWare.h" +#include "DebugDump.h" +#include "CrashTypes.h" +#include <iostream> +#include <sys/types.h> +#include <unistd.h> +#include <iostream> + + +int main(int argc, char** argv) +{ + + if (argc < 2) + { + std::cerr << "Usage: " << argv[0] << " <DebugDumpDir>" << std::endl; + return -1; + } + try + { + CMiddleWare middleWare(PLUGINS_CONF_DIR, + PLUGINS_LIB_DIR); + vector_map_string_t loaded_plugins; + middleWare.RegisterPlugin("CCpp"); + middleWare.RegisterPlugin("Mailx"); + middleWare.RegisterPlugin("Logger"); + middleWare.RegisterPlugin("RunApp"); + middleWare.RegisterPlugin("SQLite3"); + middleWare.SetDatabase("SQLite3"); + middleWare.SetOpenGPGCheck(false); + middleWare.AddActionOrReporter("Logger", ""); + middleWare.AddAnalyzerActionOrReporter("CCpp", "Mailx", ""); + middleWare.AddAnalyzerActionOrReporter("CCpp", "RunApp", "date"); + + loaded_plugins = middleWare.GetPluginsInfo(); + std::cout << "Loaded plugins" << std::endl; + int ii; + for ( ii = 0; ii < loaded_plugins.size(); ii++) + { + std::cout << "-------------------------------------------" << std::endl; + map_plugin_settings_t settings; + std::cout << "Enabled: " << loaded_plugins[ii]["Enabled"] << std::endl; + std::cout << "Type: " << loaded_plugins[ii]["Type"] << std::endl; + std::cout << "Name: " << loaded_plugins[ii]["Name"] << std::endl; + std::cout << "Version: " << loaded_plugins[ii]["Version"] << std::endl; + std::cout << "Description: " << loaded_plugins[ii]["Description"] << std::endl; + std::cout << "Email: " << loaded_plugins[ii]["Email"] << std::endl; + std::cout << "WWW: " << loaded_plugins[ii]["WWW"] << std::endl; + std::cout << "GTKBuilder: " << loaded_plugins[ii]["GTKBuilder"] << std::endl; + if (loaded_plugins[ii]["Enabled"] == "yes") + { + std::cout << std::endl << "Settings: " << std::endl; + settings = middleWare.GetPluginSettings(loaded_plugins[ii]["Name"]); + map_plugin_settings_t::iterator it; + for (it = settings.begin(); it != settings.end(); it++) + { + std::cout << "\t" << it->first << ": " << it->second << std::endl; + } + } + std::cout << "-------------------------------------------" << std::endl; + } + /* Try to save it into DB */ + map_crash_data_t crashInfo; + if (middleWare.SaveDebugDump(argv[1], crashInfo)) + { + std::cout << "Application Crashed! " << + crashInfo[FILENAME_PACKAGE][CD_CONTENT] << ", " << + crashInfo[FILENAME_EXECUTABLE][CD_CONTENT] << ", " << + crashInfo[CD_COUNT][CD_CONTENT] << ", " << std::endl; + + /* Get Report, so user can change data (remove private stuff) + * If we do not want user interaction, just send data immediately + */ + map_crash_data_t crashReport; + middleWare.CreateCrashReport(crashInfo[CD_DUPHASH][CD_CONTENT], + crashInfo[CD_UID][CD_CONTENT], + crashReport); + /* Report crash */ + middleWare.Report(crashReport); + } + } + catch (std::string sError) + { + std::cerr << sError << std::endl; + } + + return 0; +} diff --git a/lib/utils/time.cpp b/lib/utils/time.cpp new file mode 100644 index 00000000..37ade2cc --- /dev/null +++ b/lib/utils/time.cpp @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ +#include "abrtlib.h" + +#define ENABLE_MONOTONIC_SYSCALL 1 + +#if ENABLE_MONOTONIC_SYSCALL + +#include <sys/syscall.h> +/* Old glibc (< 2.3.4) does not provide this constant. We use syscall + * directly so this definition is safe. */ +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 1 +#endif + +/* libc has incredibly messy way of doing this, + * typically requiring -lrt. We just skip all this mess */ +static void get_mono(struct timespec *ts) +{ + if (syscall(__NR_clock_gettime, CLOCK_MONOTONIC, ts)) + error_msg_and_die("clock_gettime(MONOTONIC) failed"); +} +unsigned long long monotonic_ns(void) +{ + struct timespec ts; + get_mono(&ts); + return ts.tv_sec * 1000000000ULL + ts.tv_nsec; +} +unsigned long long monotonic_us(void) +{ + struct timespec ts; + get_mono(&ts); + return ts.tv_sec * 1000000ULL + ts.tv_nsec/1000; +} +unsigned monotonic_sec(void) +{ + struct timespec ts; + get_mono(&ts); + return ts.tv_sec; +} + +#else + +unsigned long long monotonic_ns(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000000000ULL + tv.tv_usec * 1000; +} +unsigned long long monotonic_us(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000000ULL + tv.tv_usec; +} +unsigned monotonic_sec(void) +{ + return time(NULL); +} + +#endif diff --git a/lib/utils/xatonum.cpp b/lib/utils/xatonum.cpp new file mode 100644 index 00000000..83146298 --- /dev/null +++ b/lib/utils/xatonum.cpp @@ -0,0 +1,50 @@ +/* + * Utility routines. + * + * Copyright (C) 2003 Manuel Novoa III <mjn3@codepoet.org> + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ +#include "abrtlib.h" + +unsigned xatou(const char *numstr) +{ + unsigned long r; + int old_errno; + char *e; + + if (*numstr < '0' || *numstr > '9') + goto inval; + + old_errno = errno; + errno = 0; + r = strtoul(numstr, &e, 10); + if (errno || numstr == e || *e != '\0' || r > UINT_MAX) + goto inval; /* error / no digits / illegal trailing chars */ + errno = old_errno; /* Ok. So restore errno. */ + return r; + + inval: + error_msg_and_die("invalid number '%s'", numstr); +} + +int xatoi_u(const char *numstr) +{ + unsigned r = xatou(numstr); + if (r > (unsigned)INT_MAX) + error_msg_and_die("invalid number '%s'", numstr); + return r; +} + +int xatoi(const char *numstr) +{ + unsigned r; + + if (*numstr != '-') + return xatoi_u(numstr); + + r = xatou(numstr + 1); + if (r > (unsigned)INT_MAX + 1) + error_msg_and_die("invalid number '%s'", numstr); + return - (int)r; +} diff --git a/lib/utils/xconnect.cpp b/lib/utils/xconnect.cpp new file mode 100644 index 00000000..0d02b1a4 --- /dev/null +++ b/lib/utils/xconnect.cpp @@ -0,0 +1,415 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Connect to host at port using address resolution from getaddrinfo + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ +#include "abrtlib.h" +#include <sys/socket.h> /* netinet/in.h needs it */ +#include <netinet/in.h> +#include <net/if.h> +#include <sys/un.h> +#include <netdb.h> + +#define ENABLE_FEATURE_IPV6 1 +#define ENABLE_FEATURE_PREFER_IPV4_ADDRESS 1 + +static const int const_int_1 = 1; + +void setsockopt_reuseaddr(int fd) +{ + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &const_int_1, sizeof(const_int_1)); +} +int setsockopt_broadcast(int fd) +{ + return setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &const_int_1, sizeof(const_int_1)); +} +int setsockopt_bindtodevice(int fd, const char *iface) +{ + int r; + struct ifreq ifr; + strncpy(ifr.ifr_name, iface, IFNAMSIZ); + /* NB: passing (iface, strlen(iface) + 1) does not work! + * (maybe it works on _some_ kernels, but not on 2.6.26) + * Actually, ifr_name is at offset 0, and in practice + * just giving char[IFNAMSIZ] instead of struct ifreq works too. + * But just in case it's not true on some obscure arch... */ + r = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); + if (r) + perror_msg("can't bind to interface %s", iface); + return r; +} + +len_and_sockaddr* get_sock_lsa(int fd) +{ + len_and_sockaddr lsa; + len_and_sockaddr *lsa_ptr; + + lsa.len = LSA_SIZEOF_SA; + if (getsockname(fd, &lsa.u.sa, &lsa.len) != 0) + return NULL; + + lsa_ptr = (len_and_sockaddr *)xzalloc(LSA_LEN_SIZE + lsa.len); + if (lsa.len > LSA_SIZEOF_SA) { /* rarely (if ever) happens */ + lsa_ptr->len = lsa.len; + getsockname(fd, &lsa_ptr->u.sa, &lsa_ptr->len); + } else { + memcpy(lsa_ptr, &lsa, LSA_LEN_SIZE + lsa.len); + } + return lsa_ptr; +} + +void xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen) +{ + if (connect(s, s_addr, addrlen) < 0) { + close(s); + if (s_addr->sa_family == AF_INET) + perror_msg_and_die("%s (%s)", + "cannot connect to remote host", + inet_ntoa(((struct sockaddr_in *)s_addr)->sin_addr)); + perror_msg_and_die("cannot connect to remote host"); + } +} + +/* Return port number for a service. + * If "port" is a number use it as the port. + * If "port" is a name it is looked up in /etc/services, + * if it isnt found return default_port + */ +unsigned lookup_port(const char *port, const char *protocol, unsigned default_port) +{ + unsigned port_nr = default_port; + if (port) { + int old_errno; + char *end; + + /* Since this is a lib function, we're not allowed to reset errno to 0. + * Doing so could break an app that is deferring checking of errno. */ + old_errno = errno; + errno = 0; + port_nr = strtoul(port, &end, 10); + if (errno || *end || port_nr > 65535) { + struct servent *tserv = getservbyname(port, protocol); + port_nr = default_port; + if (tserv) + port_nr = ntohs(tserv->s_port); + } + errno = old_errno; + } + return (uint16_t)port_nr; +} + +int get_nport(const struct sockaddr *sa) +{ +#if ENABLE_FEATURE_IPV6 + if (sa->sa_family == AF_INET6) { + return ((struct sockaddr_in6*)sa)->sin6_port; + } +#endif + if (sa->sa_family == AF_INET) { + return ((struct sockaddr_in*)sa)->sin_port; + } + /* What? UNIX socket? IPX?? :) */ + return -1; +} + +void set_nport(len_and_sockaddr *lsa, unsigned port) +{ +#if ENABLE_FEATURE_IPV6 + if (lsa->u.sa.sa_family == AF_INET6) { + lsa->u.sin6.sin6_port = port; + return; + } +#endif + if (lsa->u.sa.sa_family == AF_INET) { + lsa->u.sin.sin_port = port; + return; + } + /* What? UNIX socket? IPX?? :) */ +} + +/* We hijack this constant to mean something else */ +/* It doesn't hurt because we will remove this bit anyway */ +#define DIE_ON_ERROR AI_CANONNAME + +/* host: "1.2.3.4[:port]", "www.google.com[:port]" + * port: if neither of above specifies port # */ +static len_and_sockaddr* str2sockaddr( + const char *host, int port, + sa_family_t af, + int ai_flags) +{ + int rc; + len_and_sockaddr *r; + struct addrinfo *result = NULL; + struct addrinfo *used_res; + const char *org_host = host; /* only for error msg */ + const char *cp; + struct addrinfo hint; + + r = NULL; + + /* Ugly parsing of host:addr */ + if (ENABLE_FEATURE_IPV6 && host[0] == '[') { + /* Even uglier parsing of [xx]:nn */ + host++; + cp = strchr(host, ']'); + if (!cp || (cp[1] != ':' && cp[1] != '\0')) { + /* Malformed: must be [xx]:nn or [xx] */ + error_msg("bad address '%s'", org_host); + if (ai_flags & DIE_ON_ERROR) + xfunc_die(); + return NULL; + } + } else { + cp = strrchr(host, ':'); + if (ENABLE_FEATURE_IPV6 && cp && strchr(host, ':') != cp) { + /* There is more than one ':' (e.g. "::1") */ + cp = NULL; /* it's not a port spec */ + } + } + if (cp) { /* points to ":" or "]:" */ + int sz = cp - host + 1; + char *hbuf = (char*)alloca(sz); + hbuf[--sz] = '\0'; + host = strncpy(hbuf, host, sz); + if (ENABLE_FEATURE_IPV6 && *cp != ':') { + cp++; /* skip ']' */ + if (*cp == '\0') /* [xx] without port */ + goto skip; + } + cp++; /* skip ':' */ + char *end; + errno = 0; + port = strtoul(cp, &end, 10); + if (errno || *end || (unsigned)port > 0xffff) { + error_msg("bad port spec '%s'", org_host); + if (ai_flags & DIE_ON_ERROR) + xfunc_die(); + return NULL; + } + skip: ; + } + + memset(&hint, 0 , sizeof(hint)); +#if !ENABLE_FEATURE_IPV6 + hint.ai_family = AF_INET; /* do not try to find IPv6 */ +#else + hint.ai_family = af; +#endif + /* Needed. Or else we will get each address thrice (or more) + * for each possible socket type (tcp,udp,raw...): */ + hint.ai_socktype = SOCK_STREAM; + hint.ai_flags = ai_flags & ~DIE_ON_ERROR; + rc = getaddrinfo(host, NULL, &hint, &result); + if (rc || !result) { + error_msg("bad address '%s'", org_host); + if (ai_flags & DIE_ON_ERROR) + xfunc_die(); + goto ret; + } + used_res = result; +#if ENABLE_FEATURE_PREFER_IPV4_ADDRESS + while (1) { + if (used_res->ai_family == AF_INET) + break; + used_res = used_res->ai_next; + if (!used_res) { + used_res = result; + break; + } + } +#endif + r = (len_and_sockaddr *)xmalloc(offsetof(len_and_sockaddr, u.sa) + used_res->ai_addrlen); + r->len = used_res->ai_addrlen; + memcpy(&r->u.sa, used_res->ai_addr, used_res->ai_addrlen); + set_nport(r, htons(port)); + ret: + freeaddrinfo(result); + return r; +} +#if !ENABLE_FEATURE_IPV6 +#define str2sockaddr(host, port, af, ai_flags) str2sockaddr(host, port, ai_flags) +#endif + +#if ENABLE_FEATURE_IPV6 +len_and_sockaddr* host_and_af2sockaddr(const char *host, int port, sa_family_t af) +{ + return str2sockaddr(host, port, af, 0); +} + +len_and_sockaddr* xhost_and_af2sockaddr(const char *host, int port, sa_family_t af) +{ + return str2sockaddr(host, port, af, DIE_ON_ERROR); +} +#endif + +len_and_sockaddr* host2sockaddr(const char *host, int port) +{ + return str2sockaddr(host, port, AF_UNSPEC, 0); +} + +len_and_sockaddr* xhost2sockaddr(const char *host, int port) +{ + return str2sockaddr(host, port, AF_UNSPEC, DIE_ON_ERROR); +} + +len_and_sockaddr* xdotted2sockaddr(const char *host, int port) +{ + return str2sockaddr(host, port, AF_UNSPEC, AI_NUMERICHOST | DIE_ON_ERROR); +} + +#undef xsocket_type +int xsocket_type(len_and_sockaddr **lsap, int family, int sock_type) +{ + len_and_sockaddr *lsa; + int fd; + int len; + +#if ENABLE_FEATURE_IPV6 + if (family == AF_UNSPEC) { + fd = socket(AF_INET6, sock_type, 0); + if (fd >= 0) { + family = AF_INET6; + goto done; + } + family = AF_INET; + } +#endif + fd = xsocket(family, sock_type, 0); + len = sizeof(struct sockaddr_in); +#if ENABLE_FEATURE_IPV6 + if (family == AF_INET6) { + done: + len = sizeof(struct sockaddr_in6); + } +#endif + lsa = (len_and_sockaddr *)xzalloc(offsetof(len_and_sockaddr, u.sa) + len); + lsa->len = len; + lsa->u.sa.sa_family = family; + *lsap = lsa; + return fd; +} + +int xsocket_stream(len_and_sockaddr **lsap) +{ + return xsocket_type(lsap, AF_UNSPEC, SOCK_STREAM); +} + +static int create_and_bind_or_die(const char *bindaddr, int port, int sock_type) +{ + int fd; + len_and_sockaddr *lsa; + + if (bindaddr && bindaddr[0]) { + lsa = xdotted2sockaddr(bindaddr, port); + /* user specified bind addr dictates family */ + fd = xsocket(lsa->u.sa.sa_family, sock_type, 0); + } else { + fd = xsocket_type(&lsa, AF_UNSPEC, sock_type); + set_nport(lsa, htons(port)); + } + setsockopt_reuseaddr(fd); + xbind(fd, &lsa->u.sa, lsa->len); + free(lsa); + return fd; +} + +int create_and_bind_stream_or_die(const char *bindaddr, int port) +{ + return create_and_bind_or_die(bindaddr, port, SOCK_STREAM); +} + +int create_and_bind_dgram_or_die(const char *bindaddr, int port) +{ + return create_and_bind_or_die(bindaddr, port, SOCK_DGRAM); +} + + +int create_and_connect_stream_or_die(const char *peer, int port) +{ + int fd; + len_and_sockaddr *lsa; + + lsa = xhost2sockaddr(peer, port); + fd = xsocket(lsa->u.sa.sa_family, SOCK_STREAM, 0); + setsockopt_reuseaddr(fd); + xconnect(fd, &lsa->u.sa, lsa->len); + free(lsa); + return fd; +} + +int xconnect_stream(const len_and_sockaddr *lsa) +{ + int fd = xsocket(lsa->u.sa.sa_family, SOCK_STREAM, 0); + xconnect(fd, &lsa->u.sa, lsa->len); + return fd; +} + +/* We hijack this constant to mean something else */ +/* It doesn't hurt because we will add this bit anyway */ +#define IGNORE_PORT NI_NUMERICSERV +static char* sockaddr2str(const struct sockaddr *sa, int flags) +{ + char host[128]; + char serv[16]; + int rc; + socklen_t salen; + + salen = LSA_SIZEOF_SA; +#if ENABLE_FEATURE_IPV6 + if (sa->sa_family == AF_INET) + salen = sizeof(struct sockaddr_in); + if (sa->sa_family == AF_INET6) + salen = sizeof(struct sockaddr_in6); +#endif + rc = getnameinfo(sa, salen, + host, sizeof(host), + /* can do ((flags & IGNORE_PORT) ? NULL : serv) but why bother? */ + serv, sizeof(serv), + /* do not resolve port# into service _name_ */ + flags | NI_NUMERICSERV + ); + if (rc) + return NULL; + if (flags & IGNORE_PORT) + return xstrdup(host); +#if ENABLE_FEATURE_IPV6 + if (sa->sa_family == AF_INET6) { + if (strchr(host, ':')) /* heh, it's not a resolved hostname */ + return xasprintf("[%s]:%s", host, serv); + /*return xasprintf("%s:%s", host, serv);*/ + /* - fall through instead */ + } +#endif + /* For now we don't support anything else, so it has to be INET */ + /*if (sa->sa_family == AF_INET)*/ + return xasprintf("%s:%s", host, serv); + /*return xstrdup(host);*/ +} + +char* xmalloc_sockaddr2host(const struct sockaddr *sa) +{ + return sockaddr2str(sa, 0); +} + +char* xmalloc_sockaddr2host_noport(const struct sockaddr *sa) +{ + return sockaddr2str(sa, IGNORE_PORT); +} + +char* xmalloc_sockaddr2hostonly_noport(const struct sockaddr *sa) +{ + return sockaddr2str(sa, NI_NAMEREQD | IGNORE_PORT); +} +char* xmalloc_sockaddr2dotted(const struct sockaddr *sa) +{ + return sockaddr2str(sa, NI_NUMERICHOST); +} + +char* xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa) +{ + return sockaddr2str(sa, NI_NUMERICHOST | IGNORE_PORT); +} diff --git a/lib/utils/xfuncs.c b/lib/utils/xfuncs.c new file mode 100644 index 00000000..bd6efec8 --- /dev/null +++ b/lib/utils/xfuncs.c @@ -0,0 +1,391 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ + +/* + * Utility routines. + * + */ + +#include "xfuncs.h" + +/* Turn on nonblocking I/O on a fd */ +int ndelay_on(int fd) +{ + return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); +} + +int ndelay_off(int fd) +{ + return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK); +} + +int close_on_exec_on(int fd) +{ + return fcntl(fd, F_SETFD, FD_CLOEXEC); +} + +// Die if we can't allocate size bytes of memory. +void* xmalloc(size_t size) +{ + void *ptr = malloc(size); + if (ptr == NULL && size != 0) + die_out_of_memory(); + return ptr; +} + +// Die if we can't resize previously allocated memory. (This returns a pointer +// to the new memory, which may or may not be the same as the old memory. +// It'll copy the contents to a new chunk and free the old one if necessary.) +void* xrealloc(void *ptr, size_t size) +{ + ptr = realloc(ptr, size); + if (ptr == NULL && size != 0) + die_out_of_memory(); + return ptr; +} + +// Die if we can't allocate and zero size bytes of memory. +void* xzalloc(size_t size) +{ + void *ptr = xmalloc(size); + memset(ptr, 0, size); + return ptr; +} + +// Die if we can't copy a string to freshly allocated memory. +char* xstrdup(const char *s) +{ + char *t; + if (s == NULL) + return NULL; + + t = strdup(s); + + if (t == NULL) + die_out_of_memory(); + + return t; +} + +// Die if we can't allocate n+1 bytes (space for the null terminator) and copy +// the (possibly truncated to length n) string into it. +char* xstrndup(const char *s, int n) +{ + int m; + char *t; + + /* We can just xmalloc(n+1) and strncpy into it, */ + /* but think about xstrndup("abc", 10000) wastage! */ + m = n; + t = (char*) s; + while (m) + { + if (!*t) break; + m--; + t++; + } + n -= m; + t = (char*) xmalloc(n + 1); + t[n] = '\0'; + + return (char*) memcpy(t, s, n); +} + +void xpipe(int filedes[2]) +{ + if (pipe(filedes)) + perror_msg_and_die("can't create pipe"); +} + +void xdup(int from) +{ + if (dup(from) < 0) + perror_msg_and_die("can't duplicate file descriptor"); +} + +void xdup2(int from, int to) +{ + if (dup2(from, to) != to) + perror_msg_and_die("can't duplicate file descriptor"); +} + +// "Renumber" opened fd +void xmove_fd(int from, int to) +{ + if (from == to) + return; + xdup2(from, to); + close(from); +} + +// Die with an error message if we can't write the entire buffer. +void xwrite(int fd, const void *buf, size_t count) +{ + if (count == 0) + return; + ssize_t size = full_write(fd, buf, count); + if ((size_t)size != count) + error_msg_and_die("short write"); +} + +void xwrite_str(int fd, const char *str) +{ + xwrite(fd, str, strlen(str)); +} + +// Die with an error message if we can't lseek to the right spot. +off_t xlseek(int fd, off_t offset, int whence) +{ + off_t off = lseek(fd, offset, whence); + if (off == (off_t)-1) { + if (whence == SEEK_SET) + perror_msg_and_die("lseek(%llu)", (long long)offset); + perror_msg_and_die("lseek"); + } + return off; +} + +void xchdir(const char *path) +{ + if (chdir(path)) + perror_msg_and_die("chdir(%s)", path); +} + +char* xvasprintf(const char *format, va_list p) +{ + int r; + char *string_ptr; + +#if 1 + // GNU extension + r = vasprintf(&string_ptr, format, p); +#else + // Bloat for systems that haven't got the GNU extension. + va_list p2; + va_copy(p2, p); + r = vsnprintf(NULL, 0, format, p); + string_ptr = xmalloc(r+1); + r = vsnprintf(string_ptr, r+1, format, p2); + va_end(p2); +#endif + + if (r < 0) + die_out_of_memory(); + return string_ptr; +} + +// Die with an error message if we can't malloc() enough space and do an +// sprintf() into that space. +char* xasprintf(const char *format, ...) +{ + va_list p; + char *string_ptr; + + va_start(p, format); + string_ptr = xvasprintf(format, p); + va_end(p); + + return string_ptr; +} + +void xsetenv(const char *key, const char *value) +{ + if (setenv(key, value, 1)) + die_out_of_memory(); +} + +// Die with an error message if we can't open a new socket. +int xsocket(int domain, int type, int protocol) +{ + int r = socket(domain, type, protocol); + if (r < 0) + { + const char *s = "INET"; + if (domain == AF_PACKET) s = "PACKET"; + if (domain == AF_NETLINK) s = "NETLINK"; + if (domain == AF_INET6) s = "INET6"; + perror_msg_and_die("socket(AF_%s)", s); + } + + return r; +} + +// Die with an error message if we can't bind a socket to an address. +void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen) +{ + if (bind(sockfd, my_addr, addrlen)) + perror_msg_and_die("bind"); +} + +// Die with an error message if we can't listen for connections on a socket. +void xlisten(int s, int backlog) +{ + if (listen(s, backlog)) + perror_msg_and_die("listen"); +} + +// Die with an error message if sendto failed. +// Return bytes sent otherwise +ssize_t xsendto(int s, const void *buf, size_t len, + const struct sockaddr *to, + socklen_t tolen) +{ + ssize_t ret = sendto(s, buf, len, 0, to, tolen); + if (ret < 0) + { + close(s); + perror_msg_and_die("sendto"); + } + return ret; +} + +// xstat() - a stat() which dies on failure with meaningful error message +void xstat(const char *name, struct stat *stat_buf) +{ + if (stat(name, stat_buf)) + perror_msg_and_die("can't stat '%s'", name); +} + +const char *get_home_dir(uid_t uid) +{ + struct passwd* pw = getpwuid(uid); + // TODO: handle errno + return pw ? pw->pw_dir : NULL; +} + +// Die if we can't open a file and return a fd +int xopen3(const char *pathname, int flags, int mode) +{ + int ret; + ret = open(pathname, flags, mode); + if (ret < 0) + perror_msg_and_die("can't open '%s'", pathname); + return ret; +} + +// Die if we can't open an existing file and return a fd +int xopen(const char *pathname, int flags) +{ + return xopen3(pathname, flags, 0666); +} + +#if 0 //UNUSED +// Warn if we can't open a file and return a fd. +int open3_or_warn(const char *pathname, int flags, int mode) +{ + int ret; + ret = open(pathname, flags, mode); + if (ret < 0) + perror_msg("can't open '%s'", pathname); + return ret; +} + +// Warn if we can't open a file and return a fd. +int open_or_warn(const char *pathname, int flags) +{ + return open3_or_warn(pathname, flags, 0666); +} +#endif + +void xunlink(const char *pathname) +{ + if (unlink(pathname)) + perror_msg_and_die("can't remove file '%s'", pathname); +} + +/* Just testing dent->d_type == DT_REG is wrong: some filesystems + * do not report the type, they report DT_UNKNOWN for every dirent + * (and this is not a bug in filesystem, this is allowed by standards). + */ +int is_regular_file(struct dirent *dent, const char *dirname) +{ + if (dent->d_type == DT_REG) + return 1; + if (dent->d_type != DT_UNKNOWN) + return 0; + + char *fullname = xasprintf("%s/%s", dirname, dent->d_name); + struct stat statbuf; + int r = lstat(fullname, &statbuf); + free(fullname); + + return r == 0 && S_ISREG(statbuf.st_mode); +} + +/* Is it "." or ".."? */ +/* abrtlib candidate */ +bool dot_or_dotdot(const char *filename) +{ + if (filename[0] != '.') return false; + if (filename[1] == '\0') return true; + if (filename[1] != '.') return false; + if (filename[2] == '\0') return true; + return false; +} + +/* Find out if the last character of a string matches the one given. + * Don't underrun the buffer if the string length is 0. + */ +char *last_char_is(const char *s, int c) +{ + if (s && *s) + { + s += strlen(s) - 1; + if ((unsigned char)*s == c) + return (char*)s; + } + return NULL; +} + +bool string_to_bool(const char *s) +{ + if (s[0] == '1' && s[1] == '\0') + return true; + if (strcasecmp(s, "on") == 0) + return true; + if (strcasecmp(s, "yes") == 0) + return true; + if (strcasecmp(s, "true") == 0) + return true; + return false; +} + +void xseteuid(uid_t euid) +{ + if (seteuid(euid) != 0) + perror_msg_and_die("can't set %cid %lu", 'u', (long)euid); +} + +void xsetegid(gid_t egid) +{ + if (setegid(egid) != 0) + perror_msg_and_die("can't set %cid %lu", 'g', (long)egid); +} + +void xsetreuid(uid_t ruid, uid_t euid) +{ + if (setreuid(ruid, euid) != 0) + perror_msg_and_die("can't set %cid %lu", 'u', (long)ruid); +} + +void xsetregid(gid_t rgid, gid_t egid) +{ + if (setregid(rgid, egid) != 0) + perror_msg_and_die("can't set %cid %lu", 'g', (long)rgid); +} |