Files
erofs-freebsd-out-tree/tests/pre15/fixtures/B32-cache-probe.c
T
2026-08-18 09:20:44 +02:00

406 lines
11 KiB
C

#define _POSIX_C_SOURCE 200809L
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
struct concurrent_ctx {
const unsigned char *expected;
size_t length;
off_t offset;
long loops;
int actual_fd;
int expected_errno;
pthread_barrier_t barrier;
pthread_mutex_t lock;
int failure;
ssize_t mismatch_count;
int mismatch_errno;
};
struct vnode_ctx {
const char *actual_path;
const unsigned char *expected;
size_t expected_size;
long loops;
pthread_barrier_t barrier;
pthread_mutex_t lock;
int failure;
};
static int
fail(const char *message)
{
fprintf(stderr, "B32 probe failure: %s\n", message);
return (1);
}
static long
parse_long(const char *text, long minimum, long maximum)
{
char *end;
long value;
errno = 0;
value = strtol(text, &end, 10);
if (errno != 0 || *text == '\0' || *end != '\0' || value < minimum ||
value > maximum)
return (-1);
return (value);
}
static unsigned char *
read_expected(const char *path, off_t offset, size_t length)
{
unsigned char *buffer;
ssize_t count;
int descriptor;
descriptor = open(path, O_RDONLY);
if (descriptor < 0)
return (NULL);
buffer = malloc(length);
if (buffer == NULL) {
close(descriptor);
return (NULL);
}
count = pread(descriptor, buffer, length, offset);
close(descriptor);
if (count != (ssize_t)length) {
free(buffer);
return (NULL);
}
return (buffer);
}
static void
record_failure(pthread_mutex_t *lock, int *failure, int error)
{
pthread_mutex_lock(lock);
if (*failure == 0)
*failure = error != 0 ? error : EIO;
pthread_mutex_unlock(lock);
}
static void
record_concurrent_failure(struct concurrent_ctx *ctx, ssize_t count,
int saved_errno)
{
pthread_mutex_lock(&ctx->lock);
if (ctx->failure == 0) {
ctx->failure = saved_errno != 0 ? saved_errno : EIO;
ctx->mismatch_count = count;
ctx->mismatch_errno = saved_errno;
}
pthread_mutex_unlock(&ctx->lock);
}
static void *
concurrent_worker(void *opaque)
{
struct concurrent_ctx *ctx;
unsigned char *buffer;
ssize_t count;
long loop;
int barrier_result, saved_errno;
ctx = opaque;
buffer = malloc(ctx->length);
if (buffer == NULL) {
record_failure(&ctx->lock, &ctx->failure, ENOMEM);
return (NULL);
}
barrier_result = pthread_barrier_wait(&ctx->barrier);
if (barrier_result != 0 && barrier_result != PTHREAD_BARRIER_SERIAL_THREAD) {
record_failure(&ctx->lock, &ctx->failure, barrier_result);
free(buffer);
return (NULL);
}
for (loop = 0; loop < ctx->loops; ++loop) {
errno = 0;
count = pread(ctx->actual_fd, buffer, ctx->length, ctx->offset);
saved_errno = errno;
if (ctx->expected_errno != 0) {
if (count != -1 || saved_errno != ctx->expected_errno) {
record_concurrent_failure(ctx, count, saved_errno);
break;
}
} else if (count != (ssize_t)ctx->length ||
memcmp(buffer, ctx->expected, ctx->length) != 0) {
record_concurrent_failure(ctx, count, saved_errno);
break;
}
}
free(buffer);
return (NULL);
}
static int
run_concurrent(const char *actual_path, const char *expected_path,
long expected_errno, long workers, long loops, off_t offset, size_t length)
{
struct concurrent_ctx ctx;
pthread_t *threads;
unsigned char *expected;
long index;
memset(&ctx, 0, sizeof(ctx));
expected = expected_errno == 0 ?
read_expected(expected_path, offset, length) : calloc(1, length);
if (expected == NULL)
return (fail("could not prepare expected bytes"));
ctx.expected = expected;
ctx.length = length;
ctx.offset = offset;
ctx.loops = loops;
ctx.expected_errno = (int)expected_errno;
ctx.actual_fd = open(actual_path, O_RDONLY);
if (ctx.actual_fd < 0) {
free(expected);
return (fail("could not open actual file"));
}
if (pthread_barrier_init(&ctx.barrier, NULL, (unsigned int)workers) != 0 ||
pthread_mutex_init(&ctx.lock, NULL) != 0) {
close(ctx.actual_fd);
free(expected);
return (fail("could not initialize concurrent controls"));
}
threads = calloc((size_t)workers, sizeof(*threads));
if (threads == NULL)
return (fail("could not allocate worker handles"));
for (index = 0; index < workers; ++index) {
if (pthread_create(&threads[index], NULL, concurrent_worker, &ctx) != 0)
return (fail("could not create concurrent worker"));
}
for (index = 0; index < workers; ++index) {
if (pthread_join(threads[index], NULL) != 0)
return (fail("could not join concurrent worker"));
}
free(threads);
pthread_barrier_destroy(&ctx.barrier);
pthread_mutex_destroy(&ctx.lock);
close(ctx.actual_fd);
free(expected);
if (ctx.failure != 0) {
fprintf(stderr,
"concurrent mismatch count=%zd errno=%d expected_errno=%d\n",
ctx.mismatch_count, ctx.mismatch_errno, ctx.expected_errno);
errno = ctx.failure;
perror("concurrent read");
return (1);
}
printf("concurrent PASS workers=%ld loops=%ld assertions=%ld errno=%ld "
"offset=%ld length=%ld\n", workers, loops, workers * loops,
expected_errno, (long)offset, (long)length);
return (0);
}
static void *
vnode_worker(void *opaque)
{
struct vnode_ctx *ctx;
unsigned char buffer[4096];
struct stat status;
ssize_t count;
long loop;
int barrier_result, descriptor, saved_errno;
ctx = opaque;
barrier_result = pthread_barrier_wait(&ctx->barrier);
if (barrier_result != 0 && barrier_result != PTHREAD_BARRIER_SERIAL_THREAD) {
record_failure(&ctx->lock, &ctx->failure, barrier_result);
return (NULL);
}
for (loop = 0; loop < ctx->loops; ++loop) {
descriptor = open(ctx->actual_path, O_RDONLY);
if (descriptor < 0) {
record_failure(&ctx->lock, &ctx->failure, errno);
break;
}
if (fstat(descriptor, &status) != 0 ||
(size_t)status.st_size != ctx->expected_size) {
saved_errno = errno;
close(descriptor);
record_failure(&ctx->lock, &ctx->failure,
saved_errno != 0 ? saved_errno : EIO);
break;
}
count = pread(descriptor, buffer, sizeof(buffer), 0);
saved_errno = errno;
close(descriptor);
if (count != (ssize_t)sizeof(buffer) ||
memcmp(buffer, ctx->expected, sizeof(buffer)) != 0) {
record_failure(&ctx->lock, &ctx->failure,
saved_errno != 0 ? saved_errno : EIO);
break;
}
}
return (NULL);
}
static int
run_vnode_race(const char *actual_path, const char *expected_path, long workers,
long loops)
{
struct vnode_ctx ctx;
struct stat status;
pthread_t *threads;
long index;
memset(&ctx, 0, sizeof(ctx));
if (stat(expected_path, &status) != 0 || status.st_size < 4096)
return (fail("could not stat vnode-race reference"));
ctx.actual_path = actual_path;
ctx.expected_size = (size_t)status.st_size;
ctx.expected = read_expected(expected_path, 0, 4096);
ctx.loops = loops;
if (ctx.expected == NULL)
return (fail("could not read vnode-race reference"));
if (pthread_barrier_init(&ctx.barrier, NULL, (unsigned int)workers) != 0 ||
pthread_mutex_init(&ctx.lock, NULL) != 0)
return (fail("could not initialize vnode-race controls"));
threads = calloc((size_t)workers, sizeof(*threads));
if (threads == NULL)
return (fail("could not allocate vnode-race handles"));
for (index = 0; index < workers; ++index) {
if (pthread_create(&threads[index], NULL, vnode_worker, &ctx) != 0)
return (fail("could not create vnode-race worker"));
}
for (index = 0; index < workers; ++index) {
if (pthread_join(threads[index], NULL) != 0)
return (fail("could not join vnode-race worker"));
}
free(threads);
pthread_barrier_destroy(&ctx.barrier);
pthread_mutex_destroy(&ctx.lock);
free((void *)ctx.expected);
if (ctx.failure != 0) {
errno = ctx.failure;
perror("vnode race");
return (1);
}
printf("vnode-race PASS workers=%ld loops=%ld assertions=%ld\n", workers,
loops, workers * loops);
return (0);
}
static bool
marker_exists(const char *directory, const char *name)
{
char path[1024];
if (snprintf(path, sizeof(path), "%s/%s", directory, name) >=
(int)sizeof(path))
return (false);
return (access(path, F_OK) == 0);
}
static int
write_marker(const char *directory, const char *name, const char *content)
{
char path[1024];
ssize_t length;
int descriptor;
if (snprintf(path, sizeof(path), "%s/%s", directory, name) >=
(int)sizeof(path))
return (-1);
descriptor = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (descriptor < 0)
return (-1);
length = (ssize_t)strlen(content);
if (write(descriptor, content, (size_t)length) != length) {
close(descriptor);
return (-1);
}
return (close(descriptor));
}
static int
run_lifecycle(const char *path, const char *directory)
{
struct timespec delay = { .tv_sec = 0, .tv_nsec = 10000000 };
char result[128];
ssize_t count;
long attempt;
int descriptor, saved_errno;
bool closed;
descriptor = open(path, O_RDONLY);
if (descriptor < 0)
return (fail("lifecycle open failed"));
if (write_marker(directory, "ready", "ready\n") != 0)
return (fail("lifecycle ready marker failed"));
closed = false;
for (attempt = 0; attempt < 18000; ++attempt) {
if (!closed && marker_exists(directory, "close")) {
if (close(descriptor) != 0)
return (fail("lifecycle close failed"));
closed = true;
if (write_marker(directory, "closed", "closed\n") != 0)
return (fail("lifecycle closed marker failed"));
}
if (marker_exists(directory, "read")) {
errno = 0;
count = pread(descriptor, result, sizeof(result), 0);
saved_errno = errno;
if (snprintf(result, sizeof(result), "%zd %d\n", count,
saved_errno) >= (int)sizeof(result) ||
write_marker(directory, "result", result) != 0)
return (fail("lifecycle result marker failed"));
if (!closed)
close(descriptor);
return (0);
}
if (marker_exists(directory, "exit")) {
if (!closed)
close(descriptor);
return (0);
}
nanosleep(&delay, NULL);
}
if (!closed)
close(descriptor);
return (fail("lifecycle control timed out"));
}
int
main(int argc, char **argv)
{
long expected_errno, workers, loops, offset, length;
if (argc == 9 && strcmp(argv[1], "concurrent") == 0) {
expected_errno = parse_long(argv[4], 0, 255);
workers = parse_long(argv[5], 1, 256);
loops = parse_long(argv[6], 1, 10000);
offset = parse_long(argv[7], 0, INT64_MAX);
length = parse_long(argv[8], 1, 1048576);
if (expected_errno < 0 || workers < 0 || loops < 0 || offset < 0 ||
length < 0)
return (fail("invalid concurrent arguments"));
return (run_concurrent(argv[2], argv[3], expected_errno, workers,
loops, (off_t)offset, (size_t)length));
}
if (argc == 6 && strcmp(argv[1], "vnode-race") == 0) {
workers = parse_long(argv[4], 1, 256);
loops = parse_long(argv[5], 1, 10000);
if (workers < 0 || loops < 0)
return (fail("invalid vnode-race arguments"));
return (run_vnode_race(argv[2], argv[3], workers, loops));
}
if (argc == 4 && strcmp(argv[1], "lifecycle") == 0)
return (run_lifecycle(argv[2], argv[3]));
return (fail("usage: concurrent ACTUAL EXPECTED ERRNO WORKERS LOOPS OFFSET LENGTH | vnode-race ACTUAL EXPECTED WORKERS LOOPS | lifecycle PATH CONTROL_DIR"));
}