406 lines
11 KiB
C
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"));
|
|
}
|