Files
docker-rtorrent/tests/rtorrent-smoke.c
T
Ketan Patel 8b88089cb4
Build and Push Docker Image / build (push) Successful in 38s
Add tested Alpine-derived distroless rTorrent variant
2026-09-30 21:51:46 -04:00

108 lines
4.0 KiB
C

#define _POSIX_C_SOURCE 200809L
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static void pause_tick(void) {
struct timespec delay = {0, 100000000};
nanosleep(&delay, NULL);
}
int main(void) {
const char *forbidden[] = {"/bin/sh", "/bin/bash", "/bin/busybox", "/sbin/apk", NULL};
for (int i = 0; forbidden[i]; ++i) {
if (access(forbidden[i], F_OK) == 0) {
fprintf(stderr, "Unexpected runtime tool: %s\n", forbidden[i]);
return 1;
}
}
const char *config = "/tmp/rtorrent-smoke.rc";
const char *lock = "/home/nfs_download/rsession/rtorrent.lock";
FILE *f = fopen(config, "w");
if (!f) return 1;
fputs("system.daemon.set = true\nnetwork.bind_address.set = 0.0.0.0\n"
"network.scgi.open_port = 127.0.0.1:5001\n", f);
fclose(f);
f = fopen(lock, "w");
if (!f) return 1;
fputs("stale smoke-test lock", f);
fclose(f);
pid_t child = fork();
if (child < 0) return 1;
if (child == 0) {
execl("/usr/local/bin/rtorrent-entrypoint", "rtorrent-entrypoint", "-n", "-o",
"import=/tmp/rtorrent-smoke.rc", (char *)NULL);
_exit(127);
}
int result = 1, fd = -1, status;
for (int attempt = 0; attempt < 300; ++attempt) {
if (waitpid(child, &status, WNOHANG) == child) {
fprintf(stderr, "rTorrent exited before SCGI became ready\n");
child = -1;
goto cleanup;
}
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) goto cleanup;
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(5001);
if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) != 1) goto cleanup;
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) break;
close(fd);
fd = -1;
pause_tick();
}
if (fd < 0 || access(lock, F_OK) == 0) goto cleanup;
struct timeval timeout = {5, 0};
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
const char *body = "<?xml version=\"1.0\"?><methodCall><methodName>system.client_version</methodName><params/></methodCall>";
char headers[128], request[512], response[8192];
int hlen = snprintf(headers, sizeof(headers), "CONTENT_LENGTH%c%zu%cSCGI%c1%c", 0, strlen(body), 0, 0, 0);
int prefix = snprintf(request, sizeof(request), "%d:", hlen);
memcpy(request + prefix, headers, (size_t)hlen);
size_t length = (size_t)prefix + (size_t)hlen;
request[length++] = ',';
memcpy(request + length, body, strlen(body));
length += strlen(body);
if (send(fd, request, length, 0) != (ssize_t)length) goto cleanup;
size_t used = 0;
ssize_t n;
while (used < sizeof(response) - 1 && (n = recv(fd, response + used, sizeof(response) - 1 - used, 0)) > 0)
used += (size_t)n;
response[used] = '\0';
if (!strstr(response, "<methodResponse>") || strstr(response, "<fault>")) {
fprintf(stderr, "SCGI test failed: %s\n", response);
goto cleanup;
}
pid_t health = fork();
if (health < 0) goto cleanup;
if (health == 0) {
execl("/usr/local/bin/rtorrent-entrypoint", "rtorrent-entrypoint", "--healthcheck", (char *)NULL);
_exit(127);
}
if (waitpid(health, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) goto cleanup;
puts("Distroless rTorrent: startup, stale lock cleanup, SCGI and healthcheck passed");
result = 0;
cleanup:
if (fd >= 0) close(fd);
if (child > 0) {
kill(child, SIGTERM);
for (int i = 0; i < 100; ++i) {
if (waitpid(child, &status, WNOHANG) == child) { child = -1; break; }
pause_tick();
}
if (child > 0) { kill(child, SIGKILL); waitpid(child, &status, 0); }
}
unlink(config);
unlink(lock);
return result;
}