initial commit

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I8fb23c4cd0a1b173a390edb8495b781c6a6a6964
This commit is contained in:
raf 2025-11-27 18:31:14 +03:00
commit 6bcd1ccdc9
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
2 changed files with 625 additions and 0 deletions

24
Makefile Normal file
View file

@ -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

601
main.c Normal file
View file

@ -0,0 +1,601 @@
#define _GNU_SOURCE
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <systemd/sd-bus.h>
#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/<pid>/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 <pattern> [-n <pattern> ...] "
"[--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;
}