#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VERSION "1.0.0" #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 int quiet = 0; static int daemonize = 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] [-d] [-q]\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" " -d, --daemon Run as daemon (fork to background)\n" " -q, --quiet Suppress informational output\n" " -h, --help Show this help\n" " -v, --version Show version\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; } // Daemonize the process static int do_daemonize(void) { pid_t pid = fork(); if (pid < 0) { fprintf(stderr, "fork failed: %s\n", strerror(errno)); return -1; } if (pid > 0) { // Parent exits exit(EXIT_OK); } // Child continues if (setsid() < 0) { fprintf(stderr, "setsid failed: %s\n", strerror(errno)); return -1; } // Ignore SIGHUP signal(SIGHUP, SIG_IGN); // Fork again to ensure we're not a session leader pid = fork(); if (pid < 0) { fprintf(stderr, "second fork failed: %s\n", strerror(errno)); return -1; } if (pid > 0) { // First child exits exit(EXIT_OK); } // Change to root directory if (chdir("/") < 0) { fprintf(stderr, "chdir failed: %s\n", strerror(errno)); return -1; } close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); int fd = open("/dev/null", O_RDWR); if (fd >= 0) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); if (fd > STDERR_FILENO) { close(fd); } } return 0; } 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 (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) { printf("sin %s\n", VERSION); free(patterns); return EXIT_OK; } else if (strcmp(argv[i], "--quiet") == 0 || strcmp(argv[i], "-q") == 0) { quiet = 1; } else if (strcmp(argv[i], "--daemon") == 0 || strcmp(argv[i], "-d") == 0) { daemonize = 1; } 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)); } // Daemonize before initializing inhibitor (must happen before D-Bus // connection) if (daemonize) { if (do_daemonize() < 0) { free(patterns); return EXIT_INIT; } } 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) { if (!quiet) 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 if (!quiet) { 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) { if (!quiet) 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 && !quiet) { fprintf(stderr, "Shutting down gracefully...\n"); } inhibitor_free(&inh); free(patterns); return exit_code; }