From 6bcd1ccdc931061bf24ffce8c8aeb91e0201ecf6 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 27 Nov 2025 18:31:14 +0300 Subject: [PATCH] initial commit Signed-off-by: NotAShelf Change-Id: I8fb23c4cd0a1b173a390edb8495b781c6a6a6964 --- Makefile | 24 +++ main.c | 601 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 625 insertions(+) create mode 100644 Makefile create mode 100644 main.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9311ece --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +PREFIX ?= /usr +BINDIR ?= $(PREFIX)/bin +CC ?= gcc + +CFLAGS ?= -O2 -Wall -Wextra -Wpedantic +LDFLAGS ?= -Wl,-z,relro,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code -pie +LDLIBS ?= -lsystemd + +SRC = main.c +BIN = sin + +.PHONY: all clean install test + +all: $(BIN) + +$(BIN): $(SRC) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(SRC) $(LDLIBS) + +install: $(BIN) + install -d $(DESTDIR)$(BINDIR) + install -m 0755 $(BIN) $(DESTDIR)$(BINDIR)/$(BIN) + +clean: + -rm -f $(BIN) test_sin diff --git a/main.c b/main.c new file mode 100644 index 0000000..0b96d66 --- /dev/null +++ b/main.c @@ -0,0 +1,601 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define EXIT_OK 0 +#define EXIT_ARGS 1 +#define EXIT_INIT 2 +#define EXIT_RUNTIME 3 + +#define MAX_PATTERNS 128 +#define MAX_PATTERN_LEN 4096U +#define MIN_POLL_INTERVAL 0.1 +#define MAX_POLL_INTERVAL 3600.0 +#define DEFAULT_POLL_INTERVAL 2.0 +#define CMDLINE_INITIAL_SIZE 4096U +#define CMDLINE_MAX_SIZE (1024U * 1024U) +#define DBUS_CALL_TIMEOUT_USEC UINT64_C(10000000) + +static const char *valid_what[] = {"idle", + "sleep", + "shutdown", + "handle-power-key", + "handle-suspend-key", + "handle-hibernate-key", + "handle-lid-switch", + NULL}; + +static const char *valid_mode[] = {"block", "delay", NULL}; + +static volatile sig_atomic_t running = 1; +static volatile sig_atomic_t cleanup_requested = 0; + +static void handle_sig(int sig) { + (void)sig; + running = 0; + cleanup_requested = 1; +} + +struct Inhibitor { + sd_bus *bus; + int fd; + const char *what; + const char *who; + const char *why; + const char *mode; +}; + +static int validate_value(const char *value, const char **allowed) { + if (!value || !allowed) + return 0; + for (int i = 0; allowed[i] != NULL; i++) { + if (strcmp(value, allowed[i]) == 0) + return 1; + } + return 0; +} + +static int inhibitor_init(struct Inhibitor *i, const char *what, + const char *who, const char *why, const char *mode) { + int r; + + if (!i || !what || !who || !why || !mode) { + fprintf(stderr, "inhibitor_init: NULL parameter\n"); + return -EINVAL; + } + + if (!validate_value(what, valid_what)) { + fprintf(stderr, "Invalid --what value: %s\n", what); + fprintf(stderr, "Valid values: idle, sleep, shutdown, handle-power-key, " + "handle-suspend-key, handle-hibernate-key, " + "handle-lid-switch\n"); + return -EINVAL; + } + + if (!validate_value(mode, valid_mode)) { + fprintf(stderr, "Invalid --mode value: %s\n", mode); + fprintf(stderr, "Valid values: block, delay\n"); + return -EINVAL; + } + + memset(i, 0, sizeof(*i)); + i->fd = -1; + i->what = what; + i->who = who; + i->why = why; + i->mode = mode; + + r = sd_bus_open_system(&i->bus); + if (r < 0) { + fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r)); + return r; + } + + // Set a reasonable timeout for D-Bus calls + r = sd_bus_set_method_call_timeout(i->bus, DBUS_CALL_TIMEOUT_USEC); + if (r < 0) { + fprintf(stderr, "Warning: Failed to set D-Bus timeout: %s\n", strerror(-r)); + // Non-fatal, continue + } + + return 0; +} + +static void inhibitor_free(struct Inhibitor *i) { + if (!i) + return; + if (i->fd >= 0) { + close(i->fd); + i->fd = -1; + } + if (i->bus) { + sd_bus_flush_close_unref(i->bus); + i->bus = NULL; + } +} + +static int inhibitor_acquire(struct Inhibitor *i) { + if (!i) + return -EINVAL; + + if (i->fd >= 0) + return 0; + + if (!i->bus) { + fprintf(stderr, "inhibitor_acquire: bus not initialized\n"); + return -EINVAL; + } + + sd_bus_message *m = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = sd_bus_call_method(i->bus, "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", "Inhibit", &error, + &m, "ssss", i->what, i->who, i->why, i->mode); + if (r < 0) { + fprintf(stderr, "Inhibit call failed: %s\n", + error.message ? error.message : strerror(-r)); + sd_bus_error_free(&error); + return r; + } + sd_bus_error_free(&error); + + int received_fd = -1; + r = sd_bus_message_read(m, "h", &received_fd); + if (r < 0) { + fprintf(stderr, "Failed to read fd from Inhibit reply: %s\n", strerror(-r)); + sd_bus_message_unref(m); + return r; + } + + if (received_fd < 0) { + fprintf(stderr, "Received invalid fd from Inhibit: %d\n", received_fd); + sd_bus_message_unref(m); + return -EIO; + } + + // Duplicate FD to ensure we own it and can close it independently + int dupfd = fcntl(received_fd, F_DUPFD_CLOEXEC, 3); + if (dupfd < 0) { + // Fallback without CLOEXEC if F_DUPFD_CLOEXEC not supported + dupfd = dup(received_fd); + if (dupfd < 0) { + int saved_errno = errno; + fprintf(stderr, "dup failed: %s\n", strerror(errno)); + sd_bus_message_unref(m); + return -saved_errno; + } + int flags = fcntl(dupfd, F_GETFD); + if (flags >= 0) { + (void)fcntl(dupfd, F_SETFD, flags | FD_CLOEXEC); + } + } + + i->fd = dupfd; + sd_bus_message_unref(m); + return 0; +} + +static void inhibitor_release(struct Inhibitor *i) { + if (!i) + return; + if (i->fd >= 0) { + close(i->fd); + i->fd = -1; + } +} + +/* Read /proc//cmdline into a heap-allocated, NUL-terminated string. + * Caller must free(). Returns NULL on error or empty cmdline. + */ +static char *read_cmdline(pid_t pid) { + char path[64]; + int n = snprintf(path, sizeof(path), "/proc/%d/cmdline", (int)pid); + if (n < 0 || (size_t)n >= sizeof(path)) { + return NULL; + } + + int fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return NULL; + + size_t cap = CMDLINE_INITIAL_SIZE; + char *buf = malloc(cap); + if (!buf) { + close(fd); + return NULL; + } + + size_t len = 0; + for (;;) { + if (len >= CMDLINE_MAX_SIZE) { + // Prevent unbounded growth + free(buf); + close(fd); + return NULL; + } + + ssize_t r = read(fd, buf + len, cap - len); + if (r < 0) { + if (errno == EINTR) + continue; + free(buf); + close(fd); + return NULL; + } + if (r == 0) + break; + + len += (size_t)r; + if (len >= cap) { + size_t new_cap = cap * 2U; + if (new_cap > CMDLINE_MAX_SIZE) + new_cap = CMDLINE_MAX_SIZE; + if (new_cap <= cap) { + break; + } + char *nb = realloc(buf, new_cap); + if (!nb) { + free(buf); + close(fd); + return NULL; + } + buf = nb; + cap = new_cap; + } + } + close(fd); + + if (len == 0) { + free(buf); + return NULL; + } + + // cmdline is NUL-separated; make a printable space-separated string + for (size_t j = 0; j < len; ++j) { + if (buf[j] == '\0') + buf[j] = ' '; + } + + if (len >= cap) { + char *nb = realloc(buf, len + 1); + if (!nb) { + free(buf); + return NULL; + } + buf = nb; + } + buf[len] = '\0'; + return buf; +} + +// Return 1 if any process' cmdline contains any of the patterns. +static int any_process_running(char **patterns, int npats) { + if (!patterns || npats <= 0) + return 0; + + DIR *d = opendir("/proc"); + if (!d) { + fprintf(stderr, "opendir /proc failed: %s\n", strerror(errno)); + return 0; + } + + struct dirent *ent; + int found = 0; + + while ((ent = readdir(d)) != NULL && !found) { + const char *name = ent->d_name; + + // Skip non-numeric entries + if (!isdigit((unsigned char)name[0])) + continue; + + // Verify all characters are digits + int all_digits = 1; + for (const char *p = name; *p; ++p) { + if (!isdigit((unsigned char)*p)) { + all_digits = 0; + break; + } + } + if (!all_digits) + continue; + + // Convert to pid + char *endptr; + long pid_long = strtol(name, &endptr, 10); + if (*endptr != '\0' || pid_long <= 0 || pid_long > INT_MAX) + continue; + + pid_t pid = (pid_t)pid_long; + char *cmd = read_cmdline(pid); + if (!cmd) + continue; + + // Check all patterns + for (int i = 0; i < npats; ++i) { + if (strstr(cmd, patterns[i]) != NULL) { + found = 1; + break; + } + } + free(cmd); + } + closedir(d); + return found; +} + +static void usage(FILE *f, const char *prog) { + fprintf(f, + "Usage: %s -n [-n ...] " + "[--what=WHAT] [--mode=MODE]\n" + " [--who=WHO] [--why=WHY] [--poll=SECONDS]\n" + "\n" + "Options:\n" + " -n, --name PATTERN Process command-line substring to watch " + "(repeatable)\n" + " --what WHAT What to inhibit (default: idle)\n" + " Valid: idle, sleep, shutdown, " + "handle-power-key,\n" + " handle-suspend-key, " + "handle-hibernate-key,\n" + " handle-lid-switch\n" + " --mode MODE Mode: block or delay (default: block)\n" + " --who WHO Who string for Inhibit (default: " + "inhibit-monitor)\n" + " --why WHY Why string for Inhibit (default: prevent " + "system idle...)\n" + " --poll SECONDS Poll interval in seconds (%.1f-%.1f, " + "default: %.1f)\n" + " -h, --help Show this help\n", + prog, MIN_POLL_INTERVAL, MAX_POLL_INTERVAL, DEFAULT_POLL_INTERVAL); +} + +static int validate_string_param(const char *param, const char *name, + size_t max_len) { + if (!param || param[0] == '\0') { + fprintf(stderr, "Error: %s cannot be empty\n", name); + return 0; + } + size_t len = strlen(param); + if (len > max_len) { + fprintf(stderr, "Error: %s too long (max %zu characters)\n", name, max_len); + return 0; + } + return 1; +} + +// Safe realloc wrapper that doesn't leak on failure +static void *safe_realloc(void *ptr, size_t new_size) { + void *new_ptr = realloc(ptr, new_size); + if (!new_ptr && new_size > 0) { + return NULL; // realloc failed, original pointer still valid + } + return new_ptr; +} + +int main(int argc, char **argv) { + double poll = DEFAULT_POLL_INTERVAL; + const char *what = "idle"; + const char *who = "inhibit-monitor"; + const char *why = "prevent system idle while important process runs"; + const char *mode = "block"; + + char **patterns = NULL; + int npats = 0; + int exit_code = EXIT_OK; + + // Oh Clap how I miss you so... + for (int i = 1; i < argc; ++i) { + if ((strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--name") == 0)) { + if (i + 1 >= argc) { + fprintf(stderr, "Error: %s requires an argument\n", argv[i]); + usage(stderr, argv[0]); + free(patterns); + return EXIT_ARGS; + } + i++; + + if (npats >= MAX_PATTERNS) { + fprintf(stderr, "Error: Too many patterns (max %d)\n", MAX_PATTERNS); + free(patterns); + return EXIT_ARGS; + } + + if (!validate_string_param(argv[i], "pattern", MAX_PATTERN_LEN)) { + free(patterns); + return EXIT_ARGS; + } + + char **new_patterns = + safe_realloc(patterns, (size_t)(npats + 1) * sizeof(char *)); + if (!new_patterns) { + fprintf(stderr, "Error: Memory allocation failed\n"); + free(patterns); + return EXIT_INIT; + } + patterns = new_patterns; + patterns[npats++] = argv[i]; + + } else if (strncmp(argv[i], "--what=", 7U) == 0) { + what = argv[i] + 7; + if (!validate_string_param(what, "--what", 256U)) { + free(patterns); + return EXIT_ARGS; + } + + } else if (strncmp(argv[i], "--mode=", 7U) == 0) { + mode = argv[i] + 7; + if (!validate_string_param(mode, "--mode", 256U)) { + free(patterns); + return EXIT_ARGS; + } + + } else if (strncmp(argv[i], "--who=", 6U) == 0) { + who = argv[i] + 6; + if (!validate_string_param(who, "--who", 256U)) { + free(patterns); + return EXIT_ARGS; + } + + } else if (strncmp(argv[i], "--why=", 6U) == 0) { + why = argv[i] + 6; + if (!validate_string_param(why, "--why", 1024U)) { + free(patterns); + return EXIT_ARGS; + } + + } else if (strncmp(argv[i], "--poll=", 7U) == 0) { + char *endptr; + errno = 0; + poll = strtod(argv[i] + 7, &endptr); + + if (errno != 0 || *endptr != '\0' || endptr == argv[i] + 7) { + fprintf(stderr, "Error: Invalid --poll value: %s\n", argv[i] + 7); + free(patterns); + return EXIT_ARGS; + } + + if (poll < MIN_POLL_INTERVAL || poll > MAX_POLL_INTERVAL) { + fprintf(stderr, "Error: --poll must be between %.1f and %.1f seconds\n", + MIN_POLL_INTERVAL, MAX_POLL_INTERVAL); + free(patterns); + return EXIT_ARGS; + } + + } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + usage(stdout, argv[0]); + free(patterns); + return EXIT_OK; + + } else if (argv[i][0] == '-' && argv[i][1] == 'n' && argv[i][2] != '\0') { + // Support "-npattern" form + if (npats >= MAX_PATTERNS) { + fprintf(stderr, "Error: Too many patterns (max %d)\n", MAX_PATTERNS); + free(patterns); + return EXIT_ARGS; + } + + if (!validate_string_param(argv[i] + 2, "pattern", MAX_PATTERN_LEN)) { + free(patterns); + return EXIT_ARGS; + } + + char **new_patterns = + safe_realloc(patterns, (size_t)(npats + 1) * sizeof(char *)); + if (!new_patterns) { + fprintf(stderr, "Error: Memory allocation failed\n"); + free(patterns); + return EXIT_INIT; + } + patterns = new_patterns; + patterns[npats++] = argv[i] + 2; + + } else { + fprintf(stderr, "Error: Unknown argument: %s\n", argv[i]); + usage(stderr, argv[0]); + free(patterns); + return EXIT_ARGS; + } + } + + if (npats == 0) { + fprintf(stderr, "Error: At least one -n/--name pattern is required\n"); + usage(stderr, argv[0]); + free(patterns); + return EXIT_ARGS; + } + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handle_sig; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + if (sigaction(SIGINT, &sa, NULL) < 0) { + fprintf(stderr, "Warning: Failed to set SIGINT handler: %s\n", + strerror(errno)); + } + if (sigaction(SIGTERM, &sa, NULL) < 0) { + fprintf(stderr, "Warning: Failed to set SIGTERM handler: %s\n", + strerror(errno)); + } + + struct Inhibitor inh; + if (inhibitor_init(&inh, what, who, why, mode) < 0) { + free(patterns); + return EXIT_INIT; + } + + // Main monitoring loop + while (running) { + int found = any_process_running(patterns, npats); + + if (found) { + if (inh.fd < 0) { + fprintf(stderr, "Target process found - acquiring inhibitor\n"); + if (inhibitor_acquire(&inh) < 0) { + fprintf( + stderr, + "Failed to acquire inhibitor; retrying after poll interval\n"); + exit_code = EXIT_RUNTIME; + } else { + fprintf(stderr, "Inhibitor acquired (fd=%d)\n", inh.fd); + } + } + + // Keep inhibitor active while target(s) present + while (running && any_process_running(patterns, npats)) { + struct timespec ts; + ts.tv_sec = (time_t)poll; + ts.tv_nsec = (long)((poll - (time_t)poll) * 1e9); + + // Use nanosleep with proper interrupt handling + while (nanosleep(&ts, &ts) < 0) { + if (errno != EINTR || !running) + break; + } + } + + if (inh.fd >= 0 && running) { + fprintf(stderr, "Target exited - releasing inhibitor\n"); + inhibitor_release(&inh); + } + } else { + // No target process; sleep and check again + struct timespec ts; + ts.tv_sec = (time_t)poll; + ts.tv_nsec = (long)((poll - (time_t)poll) * 1e9); + + while (nanosleep(&ts, &ts) < 0) { + if (errno != EINTR || !running) + break; + } + } + } + + // Cleanup + if (cleanup_requested) { + fprintf(stderr, "Shutting down gracefully...\n"); + } + + inhibitor_free(&inh); + free(patterns); + + return exit_code; +}