From 14ee0f1c63fd90cb7fee72e1ff03bd78a51426a4 Mon Sep 17 00:00:00 2001 From: Ivan Chikish Date: Wed, 25 Dec 2024 16:41:30 +0300 Subject: [PATCH] [C++] Don't use iostream, use static libstdc++ Implement file_reader class (simple fd->container). Some options allow significantly reduce executable size. Static part of libstdc++ now is only 130~150 Kb. --- Makefile | 4 +++- src/config.cpp | 49 ++++++++++++++++++++++++------------------------ src/config.h | 2 ++ src/daemon.cpp | 19 ++++++++++--------- src/keyd.h | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/macro.cpp | 4 ---- 6 files changed, 91 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index 658c4a3..ed4cd6a 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,8 @@ CXXFLAGS:=-DVERSION=\"v$(VERSION)\ \($(COMMIT)\)\" \ -D_FORTIFY_SOURCE=2 \ -D_DEFAULT_SOURCE \ -Werror=format-security \ + -fdata-sections -ffunction-sections \ + -static-libgcc -static-libstdc++\ $(CXXFLAGS) platform=$(shell uname -s) @@ -53,7 +55,7 @@ endif all: compose man mkdir -p bin cp scripts/keyd-application-mapper bin/ - $(CXX) $(CXXFLAGS) -O3 $(COMPAT_FILES) src/*.cpp src/vkbd/$(VKBD).cpp -lpthread -o bin/keyd $(LDFLAGS) + $(CXX) $(CXXFLAGS) -O3 $(COMPAT_FILES) src/*.cpp src/vkbd/$(VKBD).cpp -lpthread -Wl,--gc-sections -o bin/keyd $(LDFLAGS) debug: CFLAGS="-g -fsanitize=address -Wunused" $(MAKE) compose: diff --git a/src/config.cpp b/src/config.cpp index 1f1bd6a..031702f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -26,8 +26,6 @@ #include #include #include -#include -#include #include #include @@ -186,40 +184,35 @@ static std::string resolve_include_path(const char *path, std::string_view inclu return resolved_path; } -static std::string read_file(const char *path) +static std::string read_file(const char *path, size_t recursion_depth = 0) { - constexpr std::string_view include_prefix = "include "; + std::string buf; - std::string buf, line; - - std::ifstream file(path); - if (!file.is_open()) { + std::string file = file_reader(open(path, O_RDONLY), 65536, [&] { err("failed to open %s", path); - return {}; - } + perror("open"); + }); - while (std::getline(file, line)) { - if (line.starts_with(include_prefix)) { + for (auto line : split_char<'\n'>(file)) { + if (line.starts_with("include ") || line.starts_with("include\t")) { std::string_view include_path = line; - include_path.remove_prefix(include_prefix.size()); - while (include_path.starts_with(' ')) - include_path.remove_prefix(1); + include_path.remove_prefix(8); + keyd_log("include %.*s\n", (int)include_path.size(), include_path.data()); - auto resolved_path = resolve_include_path(path, include_path); + std::string resolved_path = resolve_include_path(path, include_path); if (resolved_path.empty()) { - warn("failed to resolve include path: %s", include_path.data()); + warn("failed to resolve include path: %.*s", (int)include_path.size(), include_path.data()); continue; } - std::ifstream file(resolved_path); - if (!file.is_open()) { - warn("failed to %s", line.c_str()); - perror("open"); - } else { - buf.append(std::istreambuf_iterator(file), std::istreambuf_iterator()); + if (recursion_depth >= 10) { + warn("include depth too big or cyclic: %.*s", (int)include_path.size(), include_path.data()); + continue; } + + buf += read_file(resolved_path.c_str(), recursion_depth + 1); } else { - buf += line; + buf.append(line); buf += '\n'; } } @@ -1098,3 +1091,11 @@ void config_backup::restore(struct config& cfg) cfg.macros.resize(macro_count); cfg.commands.resize(cmd_count); } + +config::~config() +{ +} + +config_backup::~config_backup() +{ +} diff --git a/src/config.h b/src/config.h index e4f661c..5d58218 100644 --- a/src/config.h +++ b/src/config.h @@ -183,6 +183,7 @@ struct config { config() = default; config(const config&) = delete; config& operator=(const config&) = delete; + ~config(); }; struct config_backup { @@ -199,6 +200,7 @@ struct config_backup { std::vector layers; explicit config_backup(const struct config& cfg); + ~config_backup(); void restore(struct config& cfg); }; diff --git a/src/daemon.cpp b/src/daemon.cpp index 1ebd00b..0e5c711 100644 --- a/src/daemon.cpp +++ b/src/daemon.cpp @@ -2,7 +2,6 @@ #include "log.h" #include #include -#include #ifndef CONFIG_DIR #define CONFIG_DIR "" @@ -307,12 +306,13 @@ static void reload(std::shared_ptr env) else buf.clear(); buf += "keyd/bindings.conf"; - std::ifstream file(buf, std::ios::binary); - if (!file.is_open()) { + buf = std::string(file_reader(open(buf.c_str(), O_RDONLY), 65536, []{ keyd_log("Unable to open %s\n", buf.c_str()); + perror("open"); + })); + + if (buf.empty()) return; - } - buf.assign(std::istreambuf_iterator(file), std::istreambuf_iterator()); for (auto ent = configs.get(); ent; ent = ent->next.get()) { ent->kbd->config.cfg_use_uid = env->uid; @@ -517,10 +517,10 @@ static void handle_client(int con) if (getuid() < 1000) { // Copy initial environment variables from caller process - std::ifstream envf(("/proc/" + std::to_string(cred.pid) + "/environ").c_str(), std::ios::binary); - if (envf.is_open()) { - std::vector buf; - buf.assign(std::istreambuf_iterator(envf), std::istreambuf_iterator()); + std::vector buf = file_reader(open(("/proc/" + std::to_string(cred.pid) + "/environ").c_str(), O_RDONLY), 8192, [] { + perror("environ"); + }); + if (!buf.empty()) { if (prev && prev->buf == buf) { // Share previous environment variables ephemeral_config.env = prev; @@ -543,6 +543,7 @@ static void handle_client(int con) size_t msg_count = 0; while (handle_message(con, &ephemeral_config, prev ? prev : std::make_shared())) { + ephemeral_config.commands.clear(); msg_count++; } dbg2("%zu messages processed", msg_count); diff --git a/src/keyd.h b/src/keyd.h index 6bf0f74..d02e850 100644 --- a/src/keyd.h +++ b/src/keyd.h @@ -106,4 +106,55 @@ int ipc_connect(); extern struct device device_table[MAX_DEVICES]; extern size_t device_table_sz; +// One-time file reader +struct file_reader +{ + explicit file_reader(int fd, unsigned reserve, auto on_fail) + : fd(fd) + , reserve(reserve) + { + if (fd < 0) { + on_fail(); + } + } + + file_reader(const file_reader&) = delete; + file_reader& operator=(const file_reader&) = delete; + + // Read full file + template + operator T() + { + T result; + if constexpr (requires { result.reserve(reserve); }) + result.reserve(reserve); + size_t rd = 0; + V buf[4096]; + while ((rd = read(this->fd, buf, sizeof(buf))) <= sizeof(buf)) { + if (rd == 0) + break; + // Unimplemented, but won't happen for single-byte types + if (rd % sizeof(V)) + throw buf[0]; + result.insert(result.end(), buf, buf + rd / sizeof(V)); + } + return result; + } + + void reset() + { + if (lseek(fd, 0, SEEK_SET) < 0) + perror("file_reader::lseek"); + } + + ~file_reader() + { + if (fd >= 0) + close(fd); + } +private: + int fd; + unsigned reserve; +}; + #endif diff --git a/src/macro.cpp b/src/macro.cpp index e2cf053..50242c5 100644 --- a/src/macro.cpp +++ b/src/macro.cpp @@ -33,10 +33,6 @@ int macro_parse(std::string_view s, macro& macro, struct config* config) err("incomplete macro command found"); return -1; } - if (is_cmd && !config) { - err("commands are not allowed in this context"); - return -1; - } if (is_cmd && config->commands.size() > std::numeric_limits::max()) { err("max commands exceeded"); return -1;