#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include 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")); }