Loading CMakeLists.txt +8 −0 Changes for CMakeLists.txt: 8 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -10,6 +10,11 @@ set(SYSLITE_UNIT_DIR "/etc/syslite/services" CACHE STRING "Service unit file dir set(SYSLITE_DEFAULT_TARGET "multi-user.target" CACHE STRING "Default boot target") set(SYSLITE_TTY_COUNT 3 CACHE STRING "Number of TTYs to spawn") set(SYSLITE_JOURNAL "/var/log/syslite/journal.log" CACHE STRING "Journal: syslite's and the services' output") set(SYSLITE_COREDUMP_DIR "/var/lib/syslite/coredump" CACHE STRING "Core dumps (see src/coredump.h)") set(SYSLITE_COREDUMP_MAX_SIZE 2147483648 CACHE STRING "Core dumps: biggest core kept, bytes") set(SYSLITE_COREDUMP_KEEP 20 CACHE STRING "Core dumps: how many are kept") set(SYSLITE_COREDUMP_KEEP_FREE 15 CACHE STRING "Core dumps: percent of the file system left free") set(SYSLITE_COREDUMP_MAX_USE 10 CACHE STRING "Core dumps: percent of the file system all may use") configure_file(config.h.in ${CMAKE_BINARY_DIR}/config.h) Loading @@ -30,6 +35,7 @@ add_executable(syslite src/unitfile.cpp src/mount.cpp src/journal.cpp src/coredump.cpp src/service.cpp src/tty.cpp src/network.cpp Loading Loading @@ -66,6 +72,8 @@ install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink syslite \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/sbin/systemctl)") install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink syslitectl \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/sbin/journalctl)") install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink syslitectl \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/sbin/coredumpctl)") install(TARGETS sysbus EXPORT sysbusTargets ARCHIVE DESTINATION lib) install(DIRECTORY include/sysbus DESTINATION include) Loading config.h.in +5 −0 Changes for config.h.in: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -8,3 +8,8 @@ #define SYSLITE_HOSTNAME_LEGACY "/etc/hostname" #define SYSLITE_JOURNAL "@SYSLITE_JOURNAL@" #define SYSLITE_VERSION "@PROJECT_VERSION@" #define SYSLITE_COREDUMP_DIR "@SYSLITE_COREDUMP_DIR@" #define SYSLITE_COREDUMP_MAX_SIZE @SYSLITE_COREDUMP_MAX_SIZE@ULL #define SYSLITE_COREDUMP_KEEP @SYSLITE_COREDUMP_KEEP@ #define SYSLITE_COREDUMP_KEEP_FREE @SYSLITE_COREDUMP_KEEP_FREE@ #define SYSLITE_COREDUMP_MAX_USE @SYSLITE_COREDUMP_MAX_USE@ src/coredump.cpp 0 → 100644 +314 −0 Changes for src/coredump.cpp: 314 added lines, 0 removed lines. Original line number Diff line number Diff line #include "coredump.h" #include "journal.h" #include "config.h" #include "sysbus/client.h" #include <algorithm> #include <cerrno> #include <climits> #include <cstdint> #include <cstdlib> #include <cstring> #include <ctime> #include <filesystem> #include <fstream> #include <iostream> #include <map> #include <sstream> #include <string> #include <vector> #include <fcntl.h> #include <sys/resource.h> #include <sys/stat.h> #include <sys/statvfs.h> #include <unistd.h> namespace fs = std::filesystem; namespace syslite { // ---- boot ---- void setupCoredumps() { // the kernel wants an absolute path; this binary, wherever it is char buf[PATH_MAX]; ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1); std::string self = n > 0 ? std::string(buf, n) : "/sbin/syslite"; if (self.find(' ') != std::string::npos) self = "/sbin/syslite"; // " (deleted)" std::string pattern = "|" + self + " --coredump %P %u %g %s %t %c %e"; if (pattern.size() >= 128) { // CORENAME_MAX_SIZE std::cerr << "[syslite] Core dumps: " << self << " is too long a path for core_pattern" << std::endl; return; } std::ofstream cp("/proc/sys/kernel/core_pattern"); if (!(cp << pattern << "\n" << std::flush)) { std::cerr << "[syslite] Core dumps: cannot set core_pattern" << std::endl; return; } // the crashed process stays in /proc while the handler runs // (exe, cmdline, its unit), for up to 16 crashes at once std::ofstream("/proc/sys/kernel/core_pipe_limit") << "16\n"; // for pipes the kernel ignores RLIMIT_CORE but hands it over (%c); // the handler honors it, so everyone gets unlimited -- ulimit -c 0 // still turns cores off for a process struct rlimit rl{RLIM_INFINITY, RLIM_INFINITY}; setrlimit(RLIMIT_CORE, &rl); std::cout << "[syslite] Core dumps go to " << SYSLITE_COREDUMP_DIR << std::endl; } // ---- the handler ---- namespace { const char *signalName(int sig) { switch (sig) { case 3: return "SIGQUIT"; case 4: return "SIGILL"; case 5: return "SIGTRAP"; case 6: return "SIGABRT"; case 7: return "SIGBUS"; case 8: return "SIGFPE"; case 11: return "SIGSEGV"; case 24: return "SIGXCPU"; case 25: return "SIGXFSZ"; case 31: return "SIGSYS"; default: return ""; } } // one line: control characters as '?' std::string oneLine(const std::string &s) { std::string out; for (unsigned char c : s) out += (c < 0x20 && c != '\t') || c == 0x7f ? '?' : static_cast<char>(c); return out; } std::string readFile(const std::string &path) { std::ifstream in(path, std::ios::binary); std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } // "412 (sshd) S 1 412 412 ..." -> ppid, session bool parentAndSession(pid_t pid, pid_t &ppid, pid_t &sid) { std::string stat = readFile("/proc/" + std::to_string(pid) + "/stat"); size_t close = stat.rfind(')'); if (close == std::string::npos) return false; std::istringstream in(stat.substr(close + 1)); std::string state; pid_t pgrp; return static_cast<bool>(in >> state >> ppid >> pgrp >> sid); } // the unit the process belongs to: a unit's main pid is the process, // one of its ancestors, or its session (services run setsid()) std::string unitOf(pid_t pid) { std::vector<pid_t> related{pid}; pid_t p = pid, ppid, sid; for (int i = 0; i < 64 && parentAndSession(p, ppid, sid); ++i) { if (i == 0 && sid > 1) related.push_back(sid); if (ppid <= 1) break; related.push_back(ppid); p = ppid; } sysbus::Client bus; if (!bus.connect()) return ""; sysbus::Message r = bus.call(sysbus::method::ListUnits, {}, 3000); if (r.type == sysbus::MessageType::Error) return ""; std::map<pid_t, std::string> units; std::string name; for (auto &f : r.fields) { if (f.first == "name") name = f.second; else if (f.first == "pid" && std::atol(f.second.c_str()) > 1) units[std::atol(f.second.c_str())] = name; } for (pid_t q : related) { auto it = units.find(q); if (it != units.end()) return it->second; } return ""; } std::string sizeText(uint64_t n) { const char *unit[] = {"B", "KiB", "MiB", "GiB", "TiB"}; double v = static_cast<double>(n); int u = 0; while (v >= 1024 && u < 4) { v /= 1024; ++u; } char out[32]; snprintf(out, sizeof(out), u == 0 ? "%.0f %s" : "%.1f %s", v, unit[u]); return out; } struct Stored { fs::path info; fs::path core; // empty: none uint64_t size = 0; fs::file_time_type mtime; }; // the kept dumps, oldest first std::vector<Stored> storedDumps(const fs::path &dir) { std::vector<Stored> out; std::error_code ec; for (auto &e : fs::directory_iterator(dir, ec)) { std::string name = e.path().filename().string(); if (name.rfind("core.", 0) != 0 || e.path().extension() != ".info") continue; Stored s; s.info = e.path(); s.mtime = fs::last_write_time(e.path(), ec); fs::path core = e.path(); core.replace_extension(); if (fs::exists(core, ec)) { s.core = core; s.size = fs::file_size(core, ec); } out.push_back(s); } std::sort(out.begin(), out.end(), [](const Stored &a, const Stored &b) { return a.mtime < b.mtime; }); return out; } void removeDump(const Stored &s) { std::error_code ec; fs::remove(s.info, ec); if (!s.core.empty()) fs::remove(s.core, ec); } } // namespace int coredumpMain(int argc, char **argv) { // argv: syslite --coredump PID UID GID SIGNAL TIME LIMIT COMM... if (argc < 9) { std::cerr << "usage: syslite --coredump PID UID GID SIGNAL TIME LIMIT COMM\n" << "(run by the kernel, see /proc/sys/kernel/core_pattern)\n"; return 1; } umask(077); // cores hold the process' memory: passwords, keys pid_t pid = std::atol(argv[2]); long uid = std::atol(argv[3]), gid = std::atol(argv[4]); int sig = std::atoi(argv[5]); time_t when = std::atoll(argv[6]); uint64_t limit = std::strtoull(argv[7], nullptr, 10); // RLIM_INFINITY: 2^64-1 std::string comm = argv[8]; for (int i = 9; i < argc; ++i) comm += std::string(" ") + argv[i]; // older kernels split %e std::string proc = "/proc/" + std::to_string(pid); char buf[PATH_MAX]; ssize_t n = readlink((proc + "/exe").c_str(), buf, sizeof(buf) - 1); std::string exe = n > 0 ? std::string(buf, n) : ""; std::string cmdline = readFile(proc + "/cmdline"); while (!cmdline.empty() && cmdline.back() == '\0') cmdline.pop_back(); std::replace(cmdline.begin(), cmdline.end(), '\0', ' '); std::string unit = unitOf(pid); std::string safeComm; for (unsigned char c : comm) safeComm += isalnum(c) || c == '-' || c == '_' || c == '.' ? static_cast<char>(c) : '_'; std::string base = "core." + safeComm + "." + std::to_string(uid) + "." + std::to_string(pid) + "." + std::to_string(static_cast<long long>(when)); fs::path dir = SYSLITE_COREDUMP_DIR; std::error_code ec; fs::create_directories(dir, ec); fs::path corePath = dir / base, infoPath = dir / (base + ".info"); // room: the size limit, the process' RLIMIT_CORE, the free space // to keep, the share of the file system all dumps may use std::vector<Stored> old = storedDumps(dir); while (!old.empty() && old.size() >= static_cast<size_t>(SYSLITE_COREDUMP_KEEP)) { removeDump(old.front()); old.erase(old.begin()); } uint64_t room = std::min<uint64_t>(limit, SYSLITE_COREDUMP_MAX_SIZE); std::string why; if (limit == 0) why = "RLIMIT_CORE is 0"; struct statvfs sv; uint64_t maxUse = UINT64_MAX; if (statvfs(dir.c_str(), &sv) == 0) { uint64_t total = static_cast<uint64_t>(sv.f_blocks) * sv.f_frsize; uint64_t avail = static_cast<uint64_t>(sv.f_bavail) * sv.f_frsize; uint64_t keepFree = total / 100 * SYSLITE_COREDUMP_KEEP_FREE; maxUse = total / 100 * SYSLITE_COREDUMP_MAX_USE; room = std::min(room, avail > keepFree ? avail - keepFree : 0); room = std::min(room, maxUse); } uint64_t size = 0; bool stored = false; if (why.empty()) { fs::path tmp = dir / (".#" + base); int fd = ::open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600); if (fd < 0) why = std::string("cannot write ") + tmp.string() + ": " + std::strerror(errno); static char chunk[1 << 20]; while (fd >= 0) { ssize_t r = ::read(STDIN_FILENO, chunk, sizeof(chunk)); if (r < 0 && errno == EINTR) continue; if (r <= 0) break; if (size + r > room) { why = "bigger than the " + sizeText(room) + " it may take"; break; } // the core is mostly zeros: holes instead bool zeros = std::all_of(chunk, chunk + r, [](char c) { return c == 0; }); bool ok = zeros ? lseek(fd, r, SEEK_CUR) >= 0 : ::write(fd, chunk, r) == r; if (!ok) { why = std::string("write failed: ") + std::strerror(errno); break; } size += r; } if (fd >= 0) { if (why.empty() && (ftruncate(fd, size) != 0 || fsync(fd) != 0)) why = std::string("write failed: ") + std::strerror(errno); ::close(fd); if (why.empty() && ::rename(tmp.c_str(), corePath.c_str()) == 0) stored = true; else fs::remove(tmp, ec); } } // where the time is the crash's, in the journal's format char date[32], zone[8]; struct tm tm{}; localtime_r(&when, &tm); strftime(date, sizeof(date), "%Y-%m-%dT%H:%M:%S", &tm); if (strftime(zone, sizeof(zone), "%z", &tm) == 0) strcpy(zone, "+0000"); std::ofstream info(infoPath); info << "pid: " << pid << "\n" << "uid: " << uid << "\n" << "gid: " << gid << "\n" << "signal: " << sig << " " << signalName(sig) << "\n" << "time: " << date << zone << "\n" << "comm: " << oneLine(comm) << "\n" << "exe: " << oneLine(exe) << "\n" << "cmdline: " << oneLine(cmdline) << "\n" << "unit: " << unit << "\n" << "size: " << (stored ? size : 0) << "\n" << "core: " << (stored ? corePath.string() : "") << "\n"; if (!stored) info << "missing: " << oneLine(why) << "\n"; info.close(); // the whole directory within its share: the oldest go if (stored) { std::vector<Stored> all = storedDumps(dir); uint64_t use = 0; for (auto &s : all) use += s.size; for (auto &s : all) { if (use <= maxUse || s.info == infoPath) break; use -= s.size; removeDump(s); } } std::string sigText = std::to_string(sig); if (*signalName(sig)) sigText += " (" + std::string(signalName(sig)) + ")"; std::string line = "Process " + std::to_string(pid) + " (" + comm + ") of user " + std::to_string(uid) + " dumped core: signal " + sigText; if (!unit.empty()) line += ", unit " + unit; line += stored ? "; core in " + corePath.string() + " (" + sizeText(size) + ")" : "; core not kept: " + why; Journal::instance().write("coredump", pid, line); return 0; } } src/coredump.h 0 → 100644 +32 −0 Changes for src/coredump.h: 32 added lines, 0 removed lines. Original line number Diff line number Diff line #pragma once // Core dumps, like systemd-coredump: syslite (PID 1) points the kernel's // core_pattern at itself, // // |/sbin/syslite --coredump %P %u %g %s %t %c %e // // and the kernel runs that for every crash, the core on stdin. It is kept // in SYSLITE_COREDUMP_DIR as // // core.<comm>.<uid>.<pid>.<time> the core (0600) // core.<comm>.<uid>.<pid>.<time>.info "key: value" lines: pid, uid, // gid, signal, time, comm, exe, // cmdline, unit, size, core // // and the journal gets a line from "coredump". Not kept (only the .info and // the journal line): a process with RLIMIT_CORE 0 (ulimit -c 0), a core // bigger than SYSLITE_COREDUMP_MAX_SIZE, or one that would leave less than // SYSLITE_COREDUMP_KEEP_FREE percent of the file system free. Only the // newest SYSLITE_COREDUMP_KEEP dumps stay. coredumpctl (syslitectl // coredump) lists and shows them. namespace syslite { // boot (PID 1): core_pattern, core_pipe_limit, RLIMIT_CORE unlimited // for all processes (so cores are kept unless one says ulimit -c 0) void setupCoredumps(); // syslite --coredump PID UID GID SIGNAL TIME LIMIT COMM... int coredumpMain(int argc, char **argv); } src/main.cpp +5 −0 Changes for src/main.cpp: 5 added lines, 0 removed lines. Original line number Diff line number Diff line #include "syslite.h" #include "journal.h" #include "coredump.h" #include <iostream> #include <cstring> Loading @@ -26,10 +27,14 @@ static void sigHupHandler(int) { } int main(int argc, char **argv) { // run by the kernel for a crash (core_pattern, see coredump.h) if (argc > 1 && std::strcmp(argv[1], "--coredump") == 0) return syslite::coredumpMain(argc, argv); for (int i = 1; i < argc; ++i) { if (std::strcmp(argv[i], "-h") == 0 || std::strcmp(argv[i], "--help") == 0) { std::cout << "syslite — lightweight Linux init system\n" << " -h, --help Show help\n" << " --coredump ... Store a core dump (run by the kernel, see core_pattern)\n" << " Config is compile-time (no runtime config files needed)\n"; return 0; } Loading Loading
CMakeLists.txt +8 −0 Changes for CMakeLists.txt: 8 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -10,6 +10,11 @@ set(SYSLITE_UNIT_DIR "/etc/syslite/services" CACHE STRING "Service unit file dir set(SYSLITE_DEFAULT_TARGET "multi-user.target" CACHE STRING "Default boot target") set(SYSLITE_TTY_COUNT 3 CACHE STRING "Number of TTYs to spawn") set(SYSLITE_JOURNAL "/var/log/syslite/journal.log" CACHE STRING "Journal: syslite's and the services' output") set(SYSLITE_COREDUMP_DIR "/var/lib/syslite/coredump" CACHE STRING "Core dumps (see src/coredump.h)") set(SYSLITE_COREDUMP_MAX_SIZE 2147483648 CACHE STRING "Core dumps: biggest core kept, bytes") set(SYSLITE_COREDUMP_KEEP 20 CACHE STRING "Core dumps: how many are kept") set(SYSLITE_COREDUMP_KEEP_FREE 15 CACHE STRING "Core dumps: percent of the file system left free") set(SYSLITE_COREDUMP_MAX_USE 10 CACHE STRING "Core dumps: percent of the file system all may use") configure_file(config.h.in ${CMAKE_BINARY_DIR}/config.h) Loading @@ -30,6 +35,7 @@ add_executable(syslite src/unitfile.cpp src/mount.cpp src/journal.cpp src/coredump.cpp src/service.cpp src/tty.cpp src/network.cpp Loading Loading @@ -66,6 +72,8 @@ install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink syslite \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/sbin/systemctl)") install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink syslitectl \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/sbin/journalctl)") install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink syslitectl \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/sbin/coredumpctl)") install(TARGETS sysbus EXPORT sysbusTargets ARCHIVE DESTINATION lib) install(DIRECTORY include/sysbus DESTINATION include) Loading
config.h.in +5 −0 Changes for config.h.in: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -8,3 +8,8 @@ #define SYSLITE_HOSTNAME_LEGACY "/etc/hostname" #define SYSLITE_JOURNAL "@SYSLITE_JOURNAL@" #define SYSLITE_VERSION "@PROJECT_VERSION@" #define SYSLITE_COREDUMP_DIR "@SYSLITE_COREDUMP_DIR@" #define SYSLITE_COREDUMP_MAX_SIZE @SYSLITE_COREDUMP_MAX_SIZE@ULL #define SYSLITE_COREDUMP_KEEP @SYSLITE_COREDUMP_KEEP@ #define SYSLITE_COREDUMP_KEEP_FREE @SYSLITE_COREDUMP_KEEP_FREE@ #define SYSLITE_COREDUMP_MAX_USE @SYSLITE_COREDUMP_MAX_USE@
src/coredump.cpp 0 → 100644 +314 −0 Changes for src/coredump.cpp: 314 added lines, 0 removed lines. Original line number Diff line number Diff line #include "coredump.h" #include "journal.h" #include "config.h" #include "sysbus/client.h" #include <algorithm> #include <cerrno> #include <climits> #include <cstdint> #include <cstdlib> #include <cstring> #include <ctime> #include <filesystem> #include <fstream> #include <iostream> #include <map> #include <sstream> #include <string> #include <vector> #include <fcntl.h> #include <sys/resource.h> #include <sys/stat.h> #include <sys/statvfs.h> #include <unistd.h> namespace fs = std::filesystem; namespace syslite { // ---- boot ---- void setupCoredumps() { // the kernel wants an absolute path; this binary, wherever it is char buf[PATH_MAX]; ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1); std::string self = n > 0 ? std::string(buf, n) : "/sbin/syslite"; if (self.find(' ') != std::string::npos) self = "/sbin/syslite"; // " (deleted)" std::string pattern = "|" + self + " --coredump %P %u %g %s %t %c %e"; if (pattern.size() >= 128) { // CORENAME_MAX_SIZE std::cerr << "[syslite] Core dumps: " << self << " is too long a path for core_pattern" << std::endl; return; } std::ofstream cp("/proc/sys/kernel/core_pattern"); if (!(cp << pattern << "\n" << std::flush)) { std::cerr << "[syslite] Core dumps: cannot set core_pattern" << std::endl; return; } // the crashed process stays in /proc while the handler runs // (exe, cmdline, its unit), for up to 16 crashes at once std::ofstream("/proc/sys/kernel/core_pipe_limit") << "16\n"; // for pipes the kernel ignores RLIMIT_CORE but hands it over (%c); // the handler honors it, so everyone gets unlimited -- ulimit -c 0 // still turns cores off for a process struct rlimit rl{RLIM_INFINITY, RLIM_INFINITY}; setrlimit(RLIMIT_CORE, &rl); std::cout << "[syslite] Core dumps go to " << SYSLITE_COREDUMP_DIR << std::endl; } // ---- the handler ---- namespace { const char *signalName(int sig) { switch (sig) { case 3: return "SIGQUIT"; case 4: return "SIGILL"; case 5: return "SIGTRAP"; case 6: return "SIGABRT"; case 7: return "SIGBUS"; case 8: return "SIGFPE"; case 11: return "SIGSEGV"; case 24: return "SIGXCPU"; case 25: return "SIGXFSZ"; case 31: return "SIGSYS"; default: return ""; } } // one line: control characters as '?' std::string oneLine(const std::string &s) { std::string out; for (unsigned char c : s) out += (c < 0x20 && c != '\t') || c == 0x7f ? '?' : static_cast<char>(c); return out; } std::string readFile(const std::string &path) { std::ifstream in(path, std::ios::binary); std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } // "412 (sshd) S 1 412 412 ..." -> ppid, session bool parentAndSession(pid_t pid, pid_t &ppid, pid_t &sid) { std::string stat = readFile("/proc/" + std::to_string(pid) + "/stat"); size_t close = stat.rfind(')'); if (close == std::string::npos) return false; std::istringstream in(stat.substr(close + 1)); std::string state; pid_t pgrp; return static_cast<bool>(in >> state >> ppid >> pgrp >> sid); } // the unit the process belongs to: a unit's main pid is the process, // one of its ancestors, or its session (services run setsid()) std::string unitOf(pid_t pid) { std::vector<pid_t> related{pid}; pid_t p = pid, ppid, sid; for (int i = 0; i < 64 && parentAndSession(p, ppid, sid); ++i) { if (i == 0 && sid > 1) related.push_back(sid); if (ppid <= 1) break; related.push_back(ppid); p = ppid; } sysbus::Client bus; if (!bus.connect()) return ""; sysbus::Message r = bus.call(sysbus::method::ListUnits, {}, 3000); if (r.type == sysbus::MessageType::Error) return ""; std::map<pid_t, std::string> units; std::string name; for (auto &f : r.fields) { if (f.first == "name") name = f.second; else if (f.first == "pid" && std::atol(f.second.c_str()) > 1) units[std::atol(f.second.c_str())] = name; } for (pid_t q : related) { auto it = units.find(q); if (it != units.end()) return it->second; } return ""; } std::string sizeText(uint64_t n) { const char *unit[] = {"B", "KiB", "MiB", "GiB", "TiB"}; double v = static_cast<double>(n); int u = 0; while (v >= 1024 && u < 4) { v /= 1024; ++u; } char out[32]; snprintf(out, sizeof(out), u == 0 ? "%.0f %s" : "%.1f %s", v, unit[u]); return out; } struct Stored { fs::path info; fs::path core; // empty: none uint64_t size = 0; fs::file_time_type mtime; }; // the kept dumps, oldest first std::vector<Stored> storedDumps(const fs::path &dir) { std::vector<Stored> out; std::error_code ec; for (auto &e : fs::directory_iterator(dir, ec)) { std::string name = e.path().filename().string(); if (name.rfind("core.", 0) != 0 || e.path().extension() != ".info") continue; Stored s; s.info = e.path(); s.mtime = fs::last_write_time(e.path(), ec); fs::path core = e.path(); core.replace_extension(); if (fs::exists(core, ec)) { s.core = core; s.size = fs::file_size(core, ec); } out.push_back(s); } std::sort(out.begin(), out.end(), [](const Stored &a, const Stored &b) { return a.mtime < b.mtime; }); return out; } void removeDump(const Stored &s) { std::error_code ec; fs::remove(s.info, ec); if (!s.core.empty()) fs::remove(s.core, ec); } } // namespace int coredumpMain(int argc, char **argv) { // argv: syslite --coredump PID UID GID SIGNAL TIME LIMIT COMM... if (argc < 9) { std::cerr << "usage: syslite --coredump PID UID GID SIGNAL TIME LIMIT COMM\n" << "(run by the kernel, see /proc/sys/kernel/core_pattern)\n"; return 1; } umask(077); // cores hold the process' memory: passwords, keys pid_t pid = std::atol(argv[2]); long uid = std::atol(argv[3]), gid = std::atol(argv[4]); int sig = std::atoi(argv[5]); time_t when = std::atoll(argv[6]); uint64_t limit = std::strtoull(argv[7], nullptr, 10); // RLIM_INFINITY: 2^64-1 std::string comm = argv[8]; for (int i = 9; i < argc; ++i) comm += std::string(" ") + argv[i]; // older kernels split %e std::string proc = "/proc/" + std::to_string(pid); char buf[PATH_MAX]; ssize_t n = readlink((proc + "/exe").c_str(), buf, sizeof(buf) - 1); std::string exe = n > 0 ? std::string(buf, n) : ""; std::string cmdline = readFile(proc + "/cmdline"); while (!cmdline.empty() && cmdline.back() == '\0') cmdline.pop_back(); std::replace(cmdline.begin(), cmdline.end(), '\0', ' '); std::string unit = unitOf(pid); std::string safeComm; for (unsigned char c : comm) safeComm += isalnum(c) || c == '-' || c == '_' || c == '.' ? static_cast<char>(c) : '_'; std::string base = "core." + safeComm + "." + std::to_string(uid) + "." + std::to_string(pid) + "." + std::to_string(static_cast<long long>(when)); fs::path dir = SYSLITE_COREDUMP_DIR; std::error_code ec; fs::create_directories(dir, ec); fs::path corePath = dir / base, infoPath = dir / (base + ".info"); // room: the size limit, the process' RLIMIT_CORE, the free space // to keep, the share of the file system all dumps may use std::vector<Stored> old = storedDumps(dir); while (!old.empty() && old.size() >= static_cast<size_t>(SYSLITE_COREDUMP_KEEP)) { removeDump(old.front()); old.erase(old.begin()); } uint64_t room = std::min<uint64_t>(limit, SYSLITE_COREDUMP_MAX_SIZE); std::string why; if (limit == 0) why = "RLIMIT_CORE is 0"; struct statvfs sv; uint64_t maxUse = UINT64_MAX; if (statvfs(dir.c_str(), &sv) == 0) { uint64_t total = static_cast<uint64_t>(sv.f_blocks) * sv.f_frsize; uint64_t avail = static_cast<uint64_t>(sv.f_bavail) * sv.f_frsize; uint64_t keepFree = total / 100 * SYSLITE_COREDUMP_KEEP_FREE; maxUse = total / 100 * SYSLITE_COREDUMP_MAX_USE; room = std::min(room, avail > keepFree ? avail - keepFree : 0); room = std::min(room, maxUse); } uint64_t size = 0; bool stored = false; if (why.empty()) { fs::path tmp = dir / (".#" + base); int fd = ::open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600); if (fd < 0) why = std::string("cannot write ") + tmp.string() + ": " + std::strerror(errno); static char chunk[1 << 20]; while (fd >= 0) { ssize_t r = ::read(STDIN_FILENO, chunk, sizeof(chunk)); if (r < 0 && errno == EINTR) continue; if (r <= 0) break; if (size + r > room) { why = "bigger than the " + sizeText(room) + " it may take"; break; } // the core is mostly zeros: holes instead bool zeros = std::all_of(chunk, chunk + r, [](char c) { return c == 0; }); bool ok = zeros ? lseek(fd, r, SEEK_CUR) >= 0 : ::write(fd, chunk, r) == r; if (!ok) { why = std::string("write failed: ") + std::strerror(errno); break; } size += r; } if (fd >= 0) { if (why.empty() && (ftruncate(fd, size) != 0 || fsync(fd) != 0)) why = std::string("write failed: ") + std::strerror(errno); ::close(fd); if (why.empty() && ::rename(tmp.c_str(), corePath.c_str()) == 0) stored = true; else fs::remove(tmp, ec); } } // where the time is the crash's, in the journal's format char date[32], zone[8]; struct tm tm{}; localtime_r(&when, &tm); strftime(date, sizeof(date), "%Y-%m-%dT%H:%M:%S", &tm); if (strftime(zone, sizeof(zone), "%z", &tm) == 0) strcpy(zone, "+0000"); std::ofstream info(infoPath); info << "pid: " << pid << "\n" << "uid: " << uid << "\n" << "gid: " << gid << "\n" << "signal: " << sig << " " << signalName(sig) << "\n" << "time: " << date << zone << "\n" << "comm: " << oneLine(comm) << "\n" << "exe: " << oneLine(exe) << "\n" << "cmdline: " << oneLine(cmdline) << "\n" << "unit: " << unit << "\n" << "size: " << (stored ? size : 0) << "\n" << "core: " << (stored ? corePath.string() : "") << "\n"; if (!stored) info << "missing: " << oneLine(why) << "\n"; info.close(); // the whole directory within its share: the oldest go if (stored) { std::vector<Stored> all = storedDumps(dir); uint64_t use = 0; for (auto &s : all) use += s.size; for (auto &s : all) { if (use <= maxUse || s.info == infoPath) break; use -= s.size; removeDump(s); } } std::string sigText = std::to_string(sig); if (*signalName(sig)) sigText += " (" + std::string(signalName(sig)) + ")"; std::string line = "Process " + std::to_string(pid) + " (" + comm + ") of user " + std::to_string(uid) + " dumped core: signal " + sigText; if (!unit.empty()) line += ", unit " + unit; line += stored ? "; core in " + corePath.string() + " (" + sizeText(size) + ")" : "; core not kept: " + why; Journal::instance().write("coredump", pid, line); return 0; } }
src/coredump.h 0 → 100644 +32 −0 Changes for src/coredump.h: 32 added lines, 0 removed lines. Original line number Diff line number Diff line #pragma once // Core dumps, like systemd-coredump: syslite (PID 1) points the kernel's // core_pattern at itself, // // |/sbin/syslite --coredump %P %u %g %s %t %c %e // // and the kernel runs that for every crash, the core on stdin. It is kept // in SYSLITE_COREDUMP_DIR as // // core.<comm>.<uid>.<pid>.<time> the core (0600) // core.<comm>.<uid>.<pid>.<time>.info "key: value" lines: pid, uid, // gid, signal, time, comm, exe, // cmdline, unit, size, core // // and the journal gets a line from "coredump". Not kept (only the .info and // the journal line): a process with RLIMIT_CORE 0 (ulimit -c 0), a core // bigger than SYSLITE_COREDUMP_MAX_SIZE, or one that would leave less than // SYSLITE_COREDUMP_KEEP_FREE percent of the file system free. Only the // newest SYSLITE_COREDUMP_KEEP dumps stay. coredumpctl (syslitectl // coredump) lists and shows them. namespace syslite { // boot (PID 1): core_pattern, core_pipe_limit, RLIMIT_CORE unlimited // for all processes (so cores are kept unless one says ulimit -c 0) void setupCoredumps(); // syslite --coredump PID UID GID SIGNAL TIME LIMIT COMM... int coredumpMain(int argc, char **argv); }
src/main.cpp +5 −0 Changes for src/main.cpp: 5 added lines, 0 removed lines. Original line number Diff line number Diff line #include "syslite.h" #include "journal.h" #include "coredump.h" #include <iostream> #include <cstring> Loading @@ -26,10 +27,14 @@ static void sigHupHandler(int) { } int main(int argc, char **argv) { // run by the kernel for a crash (core_pattern, see coredump.h) if (argc > 1 && std::strcmp(argv[1], "--coredump") == 0) return syslite::coredumpMain(argc, argv); for (int i = 1; i < argc; ++i) { if (std::strcmp(argv[i], "-h") == 0 || std::strcmp(argv[i], "--help") == 0) { std::cout << "syslite — lightweight Linux init system\n" << " -h, --help Show help\n" << " --coredump ... Store a core dump (run by the kernel, see core_pattern)\n" << " Config is compile-time (no runtime config files needed)\n"; return 0; } Loading