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/keyd.service.in b/keyd.service.in index 73b06a8..c12a0e0 100644 --- a/keyd.service.in +++ b/keyd.service.in @@ -6,6 +6,7 @@ Documentation=man:keyd(1) Type=simple ExecStart=@PREFIX@/bin/keyd KillMode=process +Environment="GLIBCXX_TUNABLES=glibcxx.eh_pool.obj_count=2:glibcxx.eh_pool.obj_size=4" [Install] WantedBy=multi-user.target diff --git a/src/config.cpp b/src/config.cpp index 1f1bd6a..2632d97 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -26,8 +26,6 @@ #include #include #include -#include -#include #include #include @@ -186,40 +184,33 @@ 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), 4096, [&] { err("failed to open %s", path); - return {}; - } + perror("open"); + }); - while (std::getline(file, line)) { - if (line.starts_with(include_prefix)) { - std::string_view include_path = line; - include_path.remove_prefix(include_prefix.size()); - while (include_path.starts_with(' ')) - include_path.remove_prefix(1); + for (auto line : split_char<'\n'>(file)) { + if (line.starts_with("include ") || line.starts_with("include\t")) { + auto include_path = line.substr(8); - 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 +1089,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..30a7bc6 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 "" @@ -183,7 +182,6 @@ static void on_layer_change(const struct keyboard *kbd, struct layer *layer, uin static void load_configs() { DIR *dh = opendir(CONFIG_DIR); - struct dirent *dirent; if (!dh) { perror("opendir"); @@ -192,22 +190,18 @@ static void load_configs() configs.reset(); - while ((dirent = readdir(dh))) { - char path[1024]; - int len; - + while (struct dirent* dirent = readdir(dh)) { if (dirent->d_type == DT_DIR) continue; - len = snprintf(path, sizeof path, "%s/%s", CONFIG_DIR, dirent->d_name); - - if (len >= 5 && !strcmp(path + len - 5, ".conf")) { + auto name = concat(CONFIG_DIR "/", dirent->d_name); + if (name.ends_with(".conf")) { auto ent = std::make_unique(); - keyd_log("CONFIG: parsing b{%s}\n", path); + keyd_log("CONFIG: parsing b{%s}\n", name.c_str()); auto kbd = std::make_unique(); - if (!config_parse(&kbd->config, path)) { + if (!config_parse(&kbd->config, name.c_str())) { kbd->output = { .send_key = send_key, .on_layer_change = on_layer_change, @@ -220,7 +214,7 @@ static void load_configs() ent->next = std::move(configs); configs = std::move(ent); } else { - keyd_log("DEVICE: y{WARNING} failed to parse %s\n", path); + keyd_log("DEVICE: y{WARNING} failed to parse %s\n", name.c_str()); } } @@ -266,7 +260,7 @@ static void manage_device(struct device *dev) if ((ent = lookup_config_ent(dev->id, flags))) { if (device_grab(dev)) { - keyd_log("DEVICE: y{WARNING} Failed to grab %s\n", dev->path); + keyd_log("DEVICE: y{WARNING} Failed to grab /dev/input/%u\n", dev->num); dev->data = NULL; return; } @@ -287,7 +281,6 @@ static void manage_device(struct device *dev) static void reload(std::shared_ptr env) { restore_leds(); - configs.reset(); load_configs(); for (size_t i = 0; i < device_table_sz; i++) @@ -307,12 +300,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), 4096, []{ 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 +511,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(concat("/proc/", 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 +537,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); @@ -692,11 +687,15 @@ static int event_handler(struct event *ev) return timeout; } +#ifndef VERSION +#define VERSION "unknown" +#endif + int run_daemon(int, char *[]) { - ipcfd = ipc_create_server(/* SOCKET_PATH */); + ipcfd = ipc_create_server(); if (ipcfd < 0) - die("failed to create %s (another instance already running?)", SOCKET_PATH); + die("failed to create socket (another instance already running?)"); vkbd = vkbd_init(VKBD_NAME); diff --git a/src/device.cpp b/src/device.cpp index 21f73ed..680db76 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -127,7 +127,7 @@ uint32_t generate_uid(uint32_t num_keys, uint8_t absmask, uint8_t relmask, const return hash; } -static int device_init(const char *path, struct device *dev) +static int device_init(struct device *dev) { int fd; int capabilities; @@ -136,15 +136,16 @@ static int device_init(const char *path, struct device *dev) uint8_t absmask; struct input_absinfo absinfo; - if ((fd = open(path, O_RDWR | O_NONBLOCK | O_CLOEXEC, 0600)) < 0) { - keyd_log("failed to open %s\n", path); + auto path = concat("/dev/input/event", dev->num); + if ((fd = open(path.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC, 0600)) < 0) { + keyd_log("failed to open %s\n", path.c_str()); return -1; } capabilities = resolve_device_capabilities(fd, &num_keys, &relmask, &absmask); - if (ioctl(fd, EVIOCGNAME(sizeof(dev->name)), dev->name) == -1) { - keyd_log("ERROR: could not fetch device name of %s\n", dev->path); + if (ioctl(fd, EVIOCGNAME(sizeof(dev->name) - 1), dev->name) == -1) { + keyd_log("ERROR: could not fetch device name of /dev/input/event%u\n", dev->num); return -1; } @@ -166,7 +167,7 @@ static int device_init(const char *path, struct device *dev) dev->_maxy = absinfo.maximum; } - dbg2("capabilities of %s (%s): %x", path, dev->name, capabilities); + dbg2("capabilities of %s (%s): %x", path.c_str(), dev->name, capabilities); if (capabilities) { struct input_id info; @@ -176,9 +177,6 @@ static int device_init(const char *path, struct device *dev) return -1; } - strncpy(dev->path, path, sizeof(dev->path)-1); - dev->path[sizeof(dev->path)-1] = 0; - /* * Attempt to generate a reproducible unique identifier for each device. * The product and vendor ids are insufficient to identify some devices since @@ -206,14 +204,13 @@ static int device_init(const char *path, struct device *dev) struct device_worker { pthread_t tid; - char path[1024]; struct device dev; }; static void *device_scan_worker(void *arg) { struct device_worker *w = (struct device_worker *)arg; - if (device_init(w->path, &w->dev) < 0) + if (device_init(&w->dev) < 0) return NULL; return &w->dev; @@ -237,7 +234,8 @@ int device_scan(struct device devices[MAX_DEVICES]) assert(n < MAX_DEVICES); struct device_worker *w = &workers[n++]; - snprintf(w->path, sizeof(w->path), "/dev/input/%s", ent->d_name); + w->dev = {}; + w->dev.num = atoi(ent->d_name + 5); pthread_create(&w->tid, NULL, device_scan_worker, w); } } @@ -292,7 +290,6 @@ int devmon_read_device(int fd, struct device *dev) static char *ptr = buf; while (1) { - char path[1024]; struct inotify_event *ev; if (ptr >= (buf+buf_sz)) { @@ -310,9 +307,8 @@ int devmon_read_device(int fd, struct device *dev) if (strncmp(ev->name, "event", 5)) continue; - snprintf(path, sizeof path, "/dev/input/%s", ev->name); - - if (!device_init(path, dev)) + dev->num = atoi(ev->name + 5); + if (!device_init(dev)) return 0; } } diff --git a/src/device.h b/src/device.h index e847ea6..ae22976 100644 --- a/src/device.h +++ b/src/device.h @@ -26,11 +26,11 @@ struct device { uint8_t capabilities; uint8_t is_virtual; - uint8_t led_state[LED_CNT]; + char id[25]; + uint32_t num; + char name[96]; - char id[64]; - char name[64]; - char path[256]; + uint8_t led_state[LED_CNT]; /* Internal. */ uint32_t _maxx; diff --git a/src/ipc.cpp b/src/ipc.cpp index f7e37db..68df3bf 100644 --- a/src/ipc.cpp +++ b/src/ipc.cpp @@ -8,6 +8,10 @@ /* TODO (maybe): settle on an API and publish the protocol. */ +#ifndef SOCKET_PATH +#define SOCKET_PATH "/var/run/keyd.socket" +#endif + static void chgid() { struct group *g = getgrnam("keyd"); diff --git a/src/keyd.cpp b/src/keyd.cpp index b74697c..b9c17ea 100644 --- a/src/keyd.cpp +++ b/src/keyd.cpp @@ -36,6 +36,10 @@ static int ipc_exec(enum ipc_msg_type_e type, const char *data, size_t sz, uint3 return msg.type == IPC_FAIL; } +#ifndef VERSION +#define VERSION "unknown" +#endif + static int version(int, char *[]) { printf("keyd " VERSION "\n"); diff --git a/src/keyd.h b/src/keyd.h index 6bf0f74..ce33fcf 100644 --- a/src/keyd.h +++ b/src/keyd.h @@ -106,4 +106,53 @@ 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; + 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; diff --git a/src/monitor.cpp b/src/monitor.cpp index 8e461bb..ad41f1a 100644 --- a/src/monitor.cpp +++ b/src/monitor.cpp @@ -38,12 +38,10 @@ int event_handler(struct event *ev) const char *name; case EV_DEV_ADD: - keyd_log("device added: %s %s (%s)\n", - ev->dev->id, ev->dev->name, ev->dev->path); + keyd_log("device added: %s %s (/dev/input/event%u)\n", ev->dev->id, ev->dev->name, ev->dev->num); break; case EV_DEV_REMOVE: - keyd_log("device removed: %s %s (%s)\n", - ev->dev->id, ev->dev->name, ev->dev->path); + keyd_log("device removed: %s %s (/dev/input/event%u)\n", ev->dev->id, ev->dev->name, ev->dev->num); break; case EV_DEV_EVENT: switch (ev->devev->type) { diff --git a/src/strutil.h b/src/strutil.h index 5773fa0..5e1bb22 100644 --- a/src/strutil.h +++ b/src/strutil.h @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include template struct split_str; @@ -109,6 +112,47 @@ static constexpr split_str<-1u> split_chars(std::string_view str, const char* pa return {.pat = pat, .str = str}; } +// Return owning string_view-alike thing +template +struct res : protected std::string_view { + char data[Size]; + const char* c_str() const { return data; } + std::string_view get() const { return *this; } + using std::string_view::starts_with; + using std::string_view::ends_with; +}; + +template ? sizeof(Args) * 3 : sizeof(Args)))> +static constexpr res concat(const Args&... args) { + struct wrapper : public res { + void set(const char* ptr) { + static_cast(*this) = {this->data, ptr}; + } + } result{}; + + char* ptr = result.data; + ([&] { + if constexpr (std::is_integral_v) + ptr = std::to_chars(ptr, ptr + sizeof(Args) * 3, args).ptr; + else if constexpr (std::is_array_v) { + const std::string_view str = std::begin(args); + ptr = std::copy_n(str.begin(), str.size(), ptr); + } else { + exit(-1); + } + }(), ...); + result.set(ptr); + return result; +} + +template +static constexpr std::array hex(T arg) { + std::array result{}; + if (std::to_chars(std::begin(result), std::end(result), arg, 16).ec != std::errc()) + exit(-1); + return result; +} + int utf8_read_char(const char *_s, uint32_t *code); int utf8_read_char(std::string_view s, uint32_t& code); int utf8_strlen(std::string_view s);