/* Serval DNA logging utility functions Copyright 2013 Serval Project 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 #include #include #include #include #include "log.h" #include "strbuf.h" #include "strbuf_helpers.h" #include "instance.h" #include "str.h" #include "net.h" int serval_log_hexdump(int level, struct __sourceloc whence, char *name, const unsigned char *addr, size_t len) { if (level != LOG_LEVEL_SILENT) { if (name) serval_logf(level, whence, "Dump of %s", name); size_t off = 0; while (off < len) { char buf[100]; strbuf b = strbuf_local_buf(buf); size_t skip = xhexdump_line(XPRINTF_STRBUF(b), addr, len, off); off += skip; serval_logf(level, whence, " %s", strbuf_str(b)); } } return 0; } void serval_log_argv(int level, struct __sourceloc whence, const char *label, int argc, const char *const *argv) { if (level != LOG_LEVEL_SILENT) { struct strbuf b; strbuf_init(&b, NULL, 0); strbuf_append_argv(&b, argc, argv); size_t len = strbuf_count(&b); strbuf_init(&b, alloca(len + 1), len + 1); strbuf_append_argv(&b, argc, argv); if (label) serval_logf(level, whence, "%s %s", label, strbuf_str(&b)); else serval_logf(level, whence, "%s", strbuf_str(&b)); } } void serval_log_multiline(int level, struct __sourceloc whence, const char *str) { if (level != LOG_LEVEL_SILENT) { const char *s = str; const char *p; for (p = str; *p; ++p) { if (*p == '\n') { serval_logf(level, whence, "%.*s", (int)(p - s), s); s = p + 1; } } if (p > s) serval_logf(level, whence, "%.*s", (int)(p - s), s); } } const char *log_level_as_string(int level) { switch (level) { case LOG_LEVEL_SILENT: return "silent"; case LOG_LEVEL_DEBUG: return "debug"; case LOG_LEVEL_INFO: return "info"; case LOG_LEVEL_HINT: return "hint"; case LOG_LEVEL_WARN: return "warn"; case LOG_LEVEL_ERROR: return "error"; case LOG_LEVEL_FATAL: return "fatal"; case LOG_LEVEL_NONE: return "none"; } return NULL; } int string_to_log_level(const char *text) { if (strcasecmp(text, "none") == 0) return LOG_LEVEL_NONE; if (strcasecmp(text, "fatal") == 0) return LOG_LEVEL_FATAL; if (strcasecmp(text, "error") == 0) return LOG_LEVEL_ERROR; if (strcasecmp(text, "warn") == 0) return LOG_LEVEL_WARN; if (strcasecmp(text, "hint") == 0) return LOG_LEVEL_HINT; if (strcasecmp(text, "info") == 0) return LOG_LEVEL_INFO; if (strcasecmp(text, "debug") == 0) return LOG_LEVEL_DEBUG; if (strcasecmp(text, "silent") == 0) return LOG_LEVEL_SILENT; return LOG_LEVEL_INVALID; } int serval_log_backtrace(int level, struct __sourceloc whence) { #ifndef NO_BACKTRACE char execpath[MAXPATHLEN]; if (get_self_executable_path(execpath, sizeof execpath) == -1) return WHY("cannot log backtrace: own executable path unknown"); char tempfile[MAXPATHLEN]; if (!FORMF_SERVAL_TMP_PATH(tempfile, "servalgdb.XXXXXX")) return -1; int tmpfd = mkstemp(tempfile); if (tmpfd == -1) return WHYF_perror("mkstemp(%s)", alloca_str_toprint(tempfile)); if (write_str(tmpfd, "backtrace\n") == -1) { close(tmpfd); unlink(tempfile); return -1; } if (close(tmpfd) == -1) { WHY_perror("close"); unlink(tempfile); return -1; } char pidstr[12]; snprintf(pidstr, sizeof pidstr, "%jd", (intmax_t)getpid()); int stdout_fds[2]; if (pipe(stdout_fds) == -1) return WHY_perror("pipe"); pid_t child_pid; switch (child_pid = fork()) { case -1: // error WHY_perror("fork"); close(stdout_fds[0]); close(stdout_fds[1]); return WHY("cannot log backtrace: fork failed"); case 0: // child if (dup2(stdout_fds[1], 1) == -1 || dup2(stdout_fds[1], 2) == -1) { perror("dup2"); _exit(-1); } close(0); if (open("/dev/null", O_RDONLY) != 0) { perror("open(\"/dev/null\")"); _exit(-2); } close(stdout_fds[0]); // Need the (void*) cast on Solaris because it defines NULL as 0L and gcc doesn't accept it as a // sentinal execlp("gdb", "gdb", "-n", "-batch", "-x", tempfile, execpath, pidstr, (void*)NULL); perror("execlp(\"gdb\")"); do { _exit(-3); } while (1); break; } // parent close(stdout_fds[1]); serval_logf(level, whence, "GDB BACKTRACE"); char buf[1024]; char *const bufe = buf + sizeof buf; char *linep = buf; char *readp = buf; ssize_t nr; while ((nr = read(stdout_fds[0], readp, bufe - readp)) > 0) { char *p = readp; readp = readp + nr; for (; p < readp; ++p) if (*p == '\n' || *p == '\0') { *p = '\0'; serval_logf(level, __NOWHERE__, "GDB %s", linep); linep = p + 1; } if (readp >= bufe && linep == buf) { // Line does not fit into buffer. char t = bufe[-1]; bufe[-1] = '\0'; serval_logf(level, __NOWHERE__, "GDB %s", buf); buf[0] = t; readp = buf + 1; } else if (readp + 120 >= bufe && linep != buf) { // Buffer low on space. if (linep < readp) memmove(buf, linep, readp - linep); readp -= linep - buf; linep = buf; } // Invariant: readp < bufe } if (nr == -1) WHY_perror("read"); if (readp > linep) { *readp = '\0'; serval_logf(level, __NOWHERE__, "GDB %s", linep); } close(stdout_fds[0]); int status = 0; if (waitpid(child_pid, &status, 0) == -1) WHY_perror("waitpid"); strbuf b = strbuf_local_buf(buf); strbuf_append_exit_status(b, status); serval_logf(level, __NOWHERE__, "gdb %s", buf); unlink(tempfile); #endif return 0; }