sockroot

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 1227d7141855ae0f746df8ff99abe26b8edd45b1
Author: Friedel Schön <[email protected]>
Date:   Sat,  7 Sep 2024 18:12:18 +0200

first commit

Diffstat:
A.clang-format | 49+++++++++++++++++++++++++++++++++++++++++++++++++
AMakefile | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abin/sockclient | 0
Abin/sockroot | 0
Acompile_flags.txt | 5+++++
Arootmount.sh | 27+++++++++++++++++++++++++++
Asrc/exec/sockclient.c | 31+++++++++++++++++++++++++++++++
Asrc/exec/sockroot.c | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asupervise.c | 228+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 581 insertions(+), 0 deletions(-)

diff --git a/.clang-format b/.clang-format @@ -0,0 +1,48 @@ +{ + "AlignAfterOpenBracket": "Align", + "AlignConsecutiveAssignments": true, + "AlignConsecutiveDeclarations": true, + "AlignConsecutiveMacros": true, + "AlignEscapedNewlines": true, + "AlignOperands": "AlignAfterOperator", + "AllowShortBlocksOnASingleLine": true, + "AllowShortIfStatementsOnASingleLine": true, + "AllowShortLoopsOnASingleLine": true, + "BreakBeforeBraces": "Attach", + "BreakBeforeTernaryOperators": true, + "BreakConstructorInitializers": "BeforeComma", + "BreakInheritanceList": "BeforeComma", + "BreakStringLiterals": false, + "ColumnLimit": 0, + "Cpp11BracedListStyle": false, + "FixNamespaceComments": true, + "IncludeBlocks": "Regroup", + "IndentCaseLabels": true, + "IndentPPDirectives": "AfterHash", + "IndentWidth": 4, + "MaxEmptyLinesToKeep": 2, + "NamespaceIndentation": "All", + "PointerAlignment": "Left", + "SortIncludes": true, + "SortUsingDeclarations": true, + "SpaceAfterCStyleCast": true, + "SpaceAfterLogicalNot": false, + "SpaceAfterTemplateKeyword": false, + "SpaceBeforeAssignmentOperators": true, + "SpaceBeforeCpp11BracedList": false, + "SpaceBeforeCtorInitializerColon": true, + "SpaceBeforeInheritanceColon": true, + "SpaceBeforeRangeBasedForLoopColon": true, + "SpaceBeforeSquareBrackets": false, + "SpaceInEmptyBlock": false, + "SpaceInEmptyParentheses": false, + "SpacesBeforeTrailingComments": 4, + "SpacesInAngles": false, + "SpacesInCStyleCastParentheses": false, + "SpacesInConditionalStatement": false, + "SpacesInContainerLiterals": false, + "SpacesInParentheses": false, + "SpacesInSquareBrackets": false, + "TabWidth": 4, + "UseTab": "ForIndentation" +} +\ No newline at end of file diff --git a/Makefile b/Makefile @@ -0,0 +1,81 @@ +# Directories +SRC_DIR := src +BUILD_DIR := build +INCLUDE_DIR := include +BIN_DIR := bin +EXEC_DIR := $(SRC_DIR)/exec +MAN_DIR := src/man +ROFF_DIR := man + +# Compiler Options +CC ?= clang +CFLAGS += -g -std=gnu99 -Wpedantic -Wunused-result -Wno-gnu-zero-variadic-macro-arguments +LDFLAGS += -fPIE + +# Executable-specific flags +# finit_FLAGS := -static + +# File lists +SOURCE_FILES := $(wildcard $(SRC_DIR)/*.c) +EXEC_FILES := $(wildcard $(EXEC_DIR)/*) +OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SOURCE_FILES)) +BIN_FILES := $(patsubst $(EXEC_DIR)/%.c,$(BIN_DIR)/%,$(EXEC_FILES)) \ + $(patsubst $(EXEC_DIR)/%.sh,$(BIN_DIR)/%,$(EXEC_FILES)) \ + $(patsubst $(EXEC_DIR)/%.lnk,$(BIN_DIR)/%,$(EXEC_FILES)) +INCLUDE_FILES := $(wildcard $(INCLUDE_DIR)/*.h) + +MAN_FILES := $(wildcard $(MAN_DIR)/*) +ROFF_FILES := $(patsubst $(MAN_DIR)/%.md,$(ROFF_DIR)/%,$(MAN_FILES)) \ + $(patsubst $(MAN_DIR)/%.roff,$(ROFF_DIR)/%,$(MAN_FILES)) + +# Intermediate directories +INTERMED_DIRS := $(BIN_DIR) $(BUILD_DIR) $(ROFF_DIR) + +# Magic targets +.PHONY: all clean manual binary + +.PRECIOUS: $(OBJ_FILES) + + +# Default target +all: compile_flags.txt binary manual + +# Clean target +clean: + rm -rf $(INTERMED_DIRS) + +binary: $(BIN_FILES) + +manual: $(ROFF_FILES) + +# Directory rules +$(INTERMED_DIRS): + mkdir -p $@ + +# Object rules +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c $(INCLUDE_FILES) | $(BUILD_DIR) + $(CC) -o $@ -c -I$(INCLUDE_DIR) $(CFLAGS) $< + +# Executables +$(BIN_DIR)/%: $(EXEC_DIR)/%.c $(INCLUDE_FILES) $(OBJ_FILES) | $(BIN_DIR) + $(CC) -o $@ -I$(INCLUDE_DIR) $(CFLAGS) $< $(OBJ_FILES) $($(notdir $@)_FLAGS) $(LDFLAGS) + +$(BIN_DIR)/%: $(EXEC_DIR)/%.sh | $(BIN_DIR) + cp $< $@ + chmod +x $@ + +$(BIN_DIR)/%: $(EXEC_DIR)/%.lnk | $(BIN_DIR) + ln -sf $(shell cat $<) $@ + + +# Manual targets + +$(ROFF_DIR)/%: $(MAN_DIR)/%.md | $(ROFF_DIR) + md2man-roff $< > $@ + +$(ROFF_DIR)/%: $(MAN_DIR)/%.roff | $(ROFF_DIR) + cp $< $@ + +# Debug +compile_flags.txt: + echo $(CFLAGS) | tr " " "\n" > compile_flags.txt +\ No newline at end of file diff --git a/bin/sockclient b/bin/sockclient Binary files differ. diff --git a/bin/sockroot b/bin/sockroot Binary files differ. diff --git a/compile_flags.txt b/compile_flags.txt @@ -0,0 +1,5 @@ +-std=gnu99 +-Wpedantic +-Wall +-Wextra +-Wno-gnu-zero-variadic-macro-arguments diff --git a/rootmount.sh b/rootmount.sh @@ -0,0 +1,27 @@ +#!/bin/sh -e +# xchroot DIR [CMD...] - chroot into a Void (or other Linux) installation + +fail() { + printf '%s\n' "$1" 1>&2 + exit 1 +} + +if [ "$(id -u)" -ne 0 ]; then + fail 'xchroot needs to run as root' +fi + +CHROOT=$1; shift + +[ -d "$CHROOT" ] || fail 'not a directory' +[ -d "$CHROOT/dev" ] || fail 'no /dev in chroot' +[ -d "$CHROOT/proc" ] || fail 'no /proc in chroot' +[ -d "$CHROOT/sys" ] || fail 'no /sys in chroot' + +for _fs in dev proc sys; do + mount --rbind "/$_fs" "$CHROOT/$_fs" + mount --make-rslave "$CHROOT/$_fs" +done + +touch "$CHROOT/etc/resolv.conf" +mount --bind /etc/resolv.conf "$CHROOT/etc/resolv.conf" + diff --git a/src/exec/sockclient.c b/src/exec/sockclient.c @@ -0,0 +1,31 @@ +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#define SERVER_SOCK_FILE "sockroot.sock" + + +int main(void) { + int client; + + if ((client = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + fprintf(stderr, "error: cannot open socket: %s\n", strerror(errno)); + return 1; + } + + // bind socket to address + struct sockaddr_un addr = { 0 }; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, SERVER_SOCK_FILE, sizeof(addr.sun_path)); + if (connect(client, (struct sockaddr*) &addr, sizeof(addr)) == -1) { + fprintf(stderr, "error: cannot connect to socket: %s\n", strerror(errno)); + return 1; + } + + + return 0; +} diff --git a/src/exec/sockroot.c b/src/exec/sockroot.c @@ -0,0 +1,159 @@ +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <unistd.h> + +#define SERVER_SOCK_FILE "sockroot.sock" +#define CHROOT "test/" +#define EXECUTE "/bin/sh" +#define SOCKET_LIMIT 64 + +int processes[SOCKET_LIMIT]; +int processes_size = 0; + +void handle_client(int client) { + int pid; + + if (processes_size == SOCKET_LIMIT) { + fprintf(stderr, "error: cannot accept client: process-limit reached\n"); + return; + } + + while ((pid = fork()) == -1) { + fprintf(stderr, "warning: fork for process: %s, waiting 1sec...\n", strerror(errno)); + sleep(1); + } + + if (pid == 0) { // is child + dup2(client, 0); + dup2(client, 1); + dup2(client, 2); + close(client); + + if (chroot(CHROOT) == -1) { + fprintf(stderr, "error: cannot chroot to '" CHROOT "': %s\n", strerror(errno)); + _exit(1); + } + if (chdir("/") == -1) { + fprintf(stderr, "error: cannot chdir to '/': %s\n", strerror(errno)); + _exit(1); + } + + execl(EXECUTE, EXECUTE, "-i", NULL); + fprintf(stderr, "error: cannot execute '" EXECUTE "': %s\n", strerror(errno)); + _exit(1); + } + + close(client); + + printf("new connection: %d\n", pid); + + for (int i = 0; i < SOCKET_LIMIT; i++) { + if (processes[i] == 0) { + processes[i] = pid; + processes_size++; + return; + } + } +} + +void on_child(int signal) { + (void) signal; + + int pid, status; + if ((pid = wait(&status)) == -1) { + fprintf(stderr, "error: cannot await child: %s\n", strerror(errno)); + return; + } + printf("%d terminated\n", pid); + + for (int i = 0; i < SOCKET_LIMIT; i++) { + if (processes[i] == pid) { + processes[i] = 0; + processes_size--; + return; + } + } + fprintf(stderr, "error: cannot find %d in process-table\n", pid); +} + +void on_interrupt(int signal) { + (void) signal; + + for (int i = 0; i < SOCKET_LIMIT; i++) { + if (processes[i] > 0 && kill(processes[i], SIGTERM) == -1) { + fprintf(stderr, "warning: unable to terminate %d: %s\n", processes[i], strerror(errno)); + } + } + + while (processes_size > 0) { + printf("awaiting %d processes to stop\n", processes_size); + sleep(1); + } + + if (processes_size > 0) { + printf("kill %d left over processes\n", processes_size); + for (int i = 0; i < SOCKET_LIMIT; i++) { + if (processes[i] > 0 && kill(processes[i], SIGKILL) == -1) { + fprintf(stderr, "warning: unable to kill %d: %s\n", processes[i], strerror(errno)); + } + } + sleep(1); + } + exit(0); +} + +int main(void) { + if (unlink(SERVER_SOCK_FILE) == -1 && errno != ENOENT) { + fprintf(stderr, "error: cannot unlink socket: %s\n", strerror(errno)); + return 1; + } + + int server; + + if ((server = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "error: cannot create socket: %s\n", strerror(errno)); + return 1; + } + + umask(0); + + // bind socket to address + struct sockaddr_un addr = { 0 }; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, SERVER_SOCK_FILE, sizeof(addr.sun_path)); + if (bind(server, (struct sockaddr*) &addr, sizeof(addr)) == -1) { + fprintf(stderr, "error: cannot bind to socket: %s\n", strerror(errno)); + return 1; + } + + // listen for connections + if (listen(server, 5) == -1) { + fprintf(stderr, "error: cannot listen socket: %s\n", strerror(errno)); + return 1; + } + + for (int i = 0; i < SOCKET_LIMIT; i++) { + processes[i] = 0; + } + + signal(SIGINT, on_interrupt); + signal(SIGCHLD, on_child); + + while (1) { + int client_fd; + if ((client_fd = accept(server, NULL, NULL)) == -1) { + fprintf(stderr, "error: cannot accept client from socket: %s\n", strerror(errno)); + continue; + } + handle_client(client_fd); + } + + return 0; +} diff --git a/supervise.c b/supervise.c @@ -0,0 +1,228 @@ +#include "config.h" +#include "service.h" +#include "util.h" + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <unistd.h> + + +bool daemon_running = true; + +static void signal_child(int unused) { + (void) unused; + + int status; + pid_t died_pid; + service_t* s = NULL; + + if ((died_pid = wait(&status)) == -1) { + print_error("error: cannot wait for process: %s\n"); + return; + } + + if (!WIFEXITED(status) && !WIFSIGNALED(status)) + return; + + for (int i = 0; i < services_size; i++) { + if (services[i].pid == died_pid) { + s = &services[i]; + break; + } + } + if (s == NULL) + return; + + service_handle_exit(s, WIFSIGNALED(status), WIFSIGNALED(status) ? WTERMSIG(status) : WEXITSTATUS(status)); +} + +static void check_deaths(void) { + service_t* s; + for (int i = 0; i < services_size; i++) { + s = &services[i]; + if (s->state == STATE_ACTIVE_PID) { + if (kill(s->pid, 0) == -1 && errno == ESRCH) + service_handle_exit(s, false, 0); + } + } +} + +static void check_services(void) { + service_t* s; + for (int i = 0; i < services_size; i++) { + s = &services[i]; + if (s->state == STATE_DEAD) + continue; + if (service_need_restart(s)) { + if (s->state == STATE_INACTIVE) { + service_start(s, NULL); + s->status_change = time(NULL); + service_update_status(s); + } + } else { + if (s->state != STATE_INACTIVE) { + service_stop(s, NULL); + s->status_change = time(NULL); + service_update_status(s); + } + } + } +} + +static void accept_socket(void) { + int client_fd; + if ((client_fd = accept(control_socket, NULL, NULL)) == -1) { + if (errno == EWOULDBLOCK) { + sleep(SV_ACCEPT_INTERVAL); + } else { + print_error("error: cannot accept client from control-socket: %s\n"); + } + } else { + service_handle_client(client_fd); + } +} + +static void control_sockets(void) { +#if SV_RUNIT_COMPAT != 0 + service_t* s; + char cmd; + for (int i = 0; i < services_size; i++) { + s = &services[i]; + + while (recv(s->control, &cmd, 1, MSG_DONTWAIT) == 1) { + service_handle_command_runit(s, cmd); + } + } +#endif +} + +int service_supervise(const char* service_dir_, const char* runlevel_, bool force_socket) { + struct sigaction sigact = { 0 }; + sigact.sa_handler = signal_child; + sigaction(SIGCHLD, &sigact, NULL); + sigact.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sigact, NULL); + + strncpy(runlevel, runlevel_, SV_NAME_MAX); + service_dir_path = service_dir_; + if ((service_dir = open(service_dir_, O_DIRECTORY)) == -1) { + print_error("error: cannot open directory %s: %s\n", service_dir_); + return 1; + } + + // setenv("SERVICE_RUNLEVEL", runlevel, true); + + umask(0002); + + char socket_path[PATH_MAX]; + snprintf(socket_path, PATH_MAX, SV_CONTROL_SOCKET, runlevel); + + if ((null_fd = open("/dev/null", O_RDWR)) == -1) { + print_error("error: cannot open /dev/null: %s\n"); + null_fd = 1; + } + + struct stat socket_stat; + if (force_socket) { + if (unlink(socket_path) == -1 && errno != ENOENT) { + print_error("error: cannot unlink socket: %s\n"); + } + } else if (stat(socket_path, &socket_stat) != -1 && S_ISREG(socket_stat.st_mode)) { + printf("error: %s exist and is locking supervision,\nrun this program with '-f' flag if you are sure no other superviser is running.", socket_path); + return 1; + } + // create socket + if ((control_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + print_error("error: cannot create socket: %s\n"); + return 1; + } + + // bind socket to address + struct sockaddr_un addr = { 0 }; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)); + if (bind(control_socket, (struct sockaddr*) &addr, sizeof(addr)) == -1) { + print_error("error: cannot bind %s to socket: %s\n", socket_path); + return 1; + } + + // listen for connections + if (listen(control_socket, 5) == -1) { + print_error("error: cannot listen to control socket: %s\n"); + return 1; + } + + int sockflags = fcntl(control_socket, F_GETFL, 0); + if (sockflags == -1) { + print_error("warn: fcntl-getflags on control-socket failed: %s\n"); + } else if (fcntl(control_socket, F_SETFL, sockflags | O_NONBLOCK) == -1) { + print_error("warn: fcntl-setflags on control-socket failed: %s\n"); + } + + printf(":: starting services on '%s'\n", runlevel); + + if (service_refresh_directory() < 0) + return 1; + + printf(":: started services\n"); + + // accept connections and handle requests + while (daemon_running) { + check_deaths(); + service_refresh_directory(); + check_services(); + control_sockets(); + accept_socket(); + } + + close(control_socket); + + if (unlink(socket_path) == -1 && errno != ENOENT) { + print_error("error: cannot unlink socket: %s\n"); + } + + printf(":: terminating\n"); + + service_t* s; + for (int i = 0; i < services_size; i++) { + s = &services[i]; + service_stop(s, NULL); + } + + time_t start = time(NULL); + int running; + do { + sleep(1); // sleep for one second + running = 0; + for (int i = 0; i < services_size; i++) { + if (services[i].state != STATE_INACTIVE) + running++; + } + printf(":: %d running...\r", running); + } while (running > 0 && (time(NULL) - start) < SV_STOP_TIMEOUT); + + printf("\n"); + + for (int i = 0; i < services_size; i++) { + if (services[i].pid) { + printf(":: killing %s\n", services[i].name); + service_kill(&services[i], SIGKILL); + } + } + + printf(":: all services stopped\n"); + + signal(SIGPIPE, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGCONT, SIG_DFL); + return 0; +}