Files
nt-lite/code/user/tests/posix_fd_smoke/posix_fd_smoke.c
T

297 lines
10 KiB
C

// User-mode smoke test for POSIX descriptor and open-description semantics.
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <spawn.h>
#include <signal.h>
#include <sys/wait.h>
#include <ntlite/posix_extensions.h>
// Writes one NUL-terminated diagnostic to stdout.
static void print(const char* text) {
(void)write(1, text, strlen(text));
}
// Terminates the smoke test with one diagnostic.
static void fail(const char* message) {
print("posix_fd_smoke: ");
print(message);
print("\n");
exit(1);
}
// Prints one decimal integer without depending on stdio.
static void print_int(int value) {
char buffer[16];
unsigned int magnitude = value < 0 ? (unsigned int)(-(value + 1)) + 1u : (unsigned int)value;
size_t count = 0;
if (value < 0) print("-");
do {
buffer[count++] = (char)('0' + magnitude % 10u);
magnitude /= 10u;
} while (magnitude && count < sizeof(buffer));
while (count) {
char c[2] = { buffer[--count], '\0' };
print(c);
}
}
// Terminates the smoke test with one POSIX error value.
static void fail_error(const char* message, int error) {
print("posix_fd_smoke: ");
print(message);
print(" error=");
print_int(error);
print("\n");
exit(1);
}
#define INHERIT_FILE_FD 10
#define INHERIT_PIPE_A_READ 11
#define INHERIT_PIPE_A_WRITE 12
#define INHERIT_CLOEXEC_FD 13
#define INHERIT_PIPE_B_READ 14
#define INHERIT_PIPE_B_WRITE 15
// Returns whether this invocation is the descriptor-inheritance child.
static bool is_inheritance_child(void) {
const char* command = posix_get_command_line();
return command && strstr(command, "--inherit-child") != NULL;
}
// Runs the receiving side of the cross-process descriptor inheritance contract.
static int run_inheritance_child(void) {
char bytes[3] = {0, 0, 0};
if (read(INHERIT_FILE_FD, bytes, 2) != 2 || strcmp(bytes, "cd") != 0)
fail("inherited shared file offset failed in child");
errno = 0;
if (fcntl(INHERIT_CLOEXEC_FD, F_GETFD) != -1 || errno != EBADF)
fail("FD_CLOEXEC descriptor survived spawn inheritance");
if ((fcntl(INHERIT_PIPE_A_READ, F_GETFL) & O_NONBLOCK) == 0)
fail("inherited pipe status flags were not shared");
if (fcntl(INHERIT_FILE_FD, F_SETFL, O_APPEND) != 0)
fail("child F_SETFL failed");
if (write(INHERIT_PIPE_A_WRITE, "R", 1) != 1)
fail("child ready signal failed");
char byte = 0;
if (read(INHERIT_PIPE_B_READ, &byte, 1) != 1 || byte != 'x')
fail("cross-process pipe read failed");
if (close(INHERIT_PIPE_B_READ) != 0)
fail("child read endpoint close failed");
if (write(INHERIT_PIPE_A_WRITE, "C", 1) != 1)
fail("child close signal failed");
if (close(INHERIT_PIPE_A_WRITE) != 0)
fail("child write endpoint close failed");
return 0;
}
// Moves one descriptor to an exact number and closes the original alias.
static void move_fd(int source, int target) {
if (source < 0 || dup2(source, target) != target)
fail("inheritance dup2 setup failed");
if (source != target && close(source) != 0)
fail("inheritance source close failed");
}
// Waits for and consumes one byte from the inherited control pipe.
static char wait_control_byte(void) {
struct pollfd descriptor;
descriptor.fd = INHERIT_PIPE_A_READ;
descriptor.events = POLLIN | POLLHUP;
descriptor.revents = 0;
if (poll(&descriptor, 1, 5000) <= 0)
fail("inheritance control poll timed out");
char value = 0;
if (read(INHERIT_PIPE_A_READ, &value, 1) != 1)
fail("inheritance control read failed");
return value;
}
// Proves open-description sharing across two distinct native processes.
static void run_cross_process_inheritance(void) {
const char* path = "posix_fd_inherit.tmp";
(void)unlink(path);
int file = open(path, O_CREAT | O_TRUNC | O_RDWR);
if (file < 0 || write(file, "abcdef", 6) != 6 || lseek(file, 0, SEEK_SET) != 0)
fail("inheritance file setup failed");
char prefix[3] = {0, 0, 0};
if (read(file, prefix, 2) != 2 || strcmp(prefix, "ab") != 0)
fail("inheritance initial offset setup failed");
move_fd(file, INHERIT_FILE_FD);
if (dup2(INHERIT_FILE_FD, INHERIT_CLOEXEC_FD) != INHERIT_CLOEXEC_FD ||
fcntl(INHERIT_CLOEXEC_FD, F_SETFD, FD_CLOEXEC) != 0)
fail("inheritance CLOEXEC setup failed");
int pipeA[2];
int pipeB[2];
if (pipe(pipeA) != 0 || pipe(pipeB) != 0)
fail("inheritance pipe setup failed");
move_fd(pipeA[0], INHERIT_PIPE_A_READ);
move_fd(pipeA[1], INHERIT_PIPE_A_WRITE);
move_fd(pipeB[0], INHERIT_PIPE_B_READ);
move_fd(pipeB[1], INHERIT_PIPE_B_WRITE);
if (fcntl(INHERIT_PIPE_A_READ, F_SETFL, O_NONBLOCK) != 0)
fail("inheritance nonblock setup failed");
char self[512];
const char* command = posix_get_command_line();
size_t length = 0;
while (command && command[length] && command[length] != ' ' && command[length] != '\t' &&
length + 1 < sizeof(self)) {
self[length] = command[length];
++length;
}
self[length] = '\0';
if (length == 0 || (command[length] && command[length] != ' ' && command[length] != '\t'))
fail("could not resolve self path");
char* childArgv[] = { self, "--inherit-child", NULL };
pid_t child = 0;
int spawnError = posix_spawn(&child, self, NULL, NULL, childArgv, NULL);
if (spawnError != 0)
fail_error("inheritance child spawn failed", spawnError);
if (close(INHERIT_PIPE_A_WRITE) != 0 || close(INHERIT_PIPE_B_READ) != 0)
fail("parent endpoint close failed");
if (wait_control_byte() != 'R')
fail("unexpected child ready signal");
if ((fcntl(INHERIT_FILE_FD, F_GETFL) & O_APPEND) == 0)
fail("child F_SETFL was not visible in parent");
if (fcntl(INHERIT_CLOEXEC_FD, F_GETFD) != FD_CLOEXEC)
fail("parent descriptor-local CLOEXEC changed");
char suffix[3] = {0, 0, 0};
if (read(INHERIT_FILE_FD, suffix, 2) != 2 || strcmp(suffix, "ef") != 0)
fail("child did not advance shared native file offset");
if (write(INHERIT_PIPE_B_WRITE, "x", 1) != 1)
fail("write while inherited reader alive failed");
if (wait_control_byte() != 'C')
fail("unexpected child close signal");
sighandler_t previousPipe = signal(SIGPIPE, SIG_IGN);
if (previousPipe == SIG_ERR)
fail("ignore inherited SIGPIPE failed");
errno = 0;
if (write(INHERIT_PIPE_B_WRITE, "y", 1) != -1 || errno != EPIPE)
fail("final cross-process reader close did not produce EPIPE");
if (signal(SIGPIPE, previousPipe) == SIG_ERR)
fail("restore inherited SIGPIPE failed");
int childStatus = 0;
if (waitpid(child, &childStatus, 0) != child || childStatus != 0)
fail("inheritance child failed");
char eof = 0;
if (read(INHERIT_PIPE_A_READ, &eof, 1) != 0)
fail("final cross-process writer close did not produce EOF");
if (close(INHERIT_FILE_FD) != 0 || close(INHERIT_CLOEXEC_FD) != 0 ||
close(INHERIT_PIPE_A_READ) != 0 || close(INHERIT_PIPE_B_WRITE) != 0)
fail("inheritance cleanup close failed");
if (unlink(path) != 0)
fail("inheritance cleanup unlink failed");
}
// Runs descriptor duplication and shared-open-description tests.
int main(void) {
if (is_inheritance_child())
return run_inheritance_child();
const char* path = "posix_fd_smoke.tmp";
(void)unlink(path);
int fd = open(path, O_CREAT | O_TRUNC | O_RDWR);
if (fd < 0)
fail("open failed");
if (write(fd, "abcdef", 6) != 6)
fail("write failed");
if (lseek(fd, 0, SEEK_SET) != 0)
fail("initial seek failed");
int duplicate = dup(fd);
if (duplicate < 0 || duplicate == fd)
fail("dup failed");
char bytes[3] = {0, 0, 0};
if (read(duplicate, bytes, 2) != 2 || strcmp(bytes, "ab") != 0)
fail("duplicate read failed");
memset(bytes, 0, sizeof(bytes));
if (read(fd, bytes, 2) != 2 || strcmp(bytes, "cd") != 0)
fail("shared file offset failed");
if (fcntl(duplicate, F_GETFD) != 0)
fail("new duplicate inherited descriptor flags");
if (fcntl(duplicate, F_SETFD, FD_CLOEXEC) != 0)
fail("F_SETFD failed");
if (fcntl(duplicate, F_GETFD) != FD_CLOEXEC)
fail("F_GETFD failed");
if (fcntl(fd, F_GETFD) != 0)
fail("descriptor flags were shared");
int duplicate20 = dup2(fd, 20);
if (duplicate20 != 20)
fail("dup2 failed");
if ((fcntl(duplicate20, F_GETFL) & O_ACCMODE) != O_RDWR)
fail("F_GETFL lost access mode");
int duplicate30 = fcntl(fd, F_DUPFD, 30);
if (duplicate30 < 30)
fail("F_DUPFD failed");
if (close(fd) != 0)
fail("close original failed");
memset(bytes, 0, sizeof(bytes));
if (read(duplicate, bytes, 2) != 2 || strcmp(bytes, "ef") != 0)
fail("duplicate did not retain open description");
if (fcntl(duplicate, F_SETFL, O_APPEND | O_NONBLOCK) != 0)
fail("F_SETFL failed");
int sharedFlags = fcntl(duplicate20, F_GETFL);
if ((sharedFlags & (O_APPEND | O_NONBLOCK)) != (O_APPEND | O_NONBLOCK))
fail("F_SETFL status was not shared");
if (lseek(duplicate, 0, SEEK_SET) != 0)
fail("append setup seek failed");
if (write(duplicate20, "Z", 1) != 1)
fail("append write failed");
if (lseek(duplicate, 6, SEEK_SET) != 6)
fail("append verify seek failed");
char appended = 0;
if (read(duplicate, &appended, 1) != 1 || appended != 'Z')
fail("O_APPEND did not append");
if (fcntl(duplicate, F_SETFL, 0) != 0 ||
(fcntl(duplicate30, F_GETFL) & (O_APPEND | O_NONBLOCK)) != 0)
fail("F_SETFL clear was not shared");
struct flock lock;
memset(&lock, 0, sizeof(lock));
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 4;
if (fcntl(duplicate, F_SETLK, &lock) != 0)
fail("F_SETLK failed");
struct flock query = lock;
if (fcntl(duplicate20, F_GETLK, &query) != 0 || query.l_type != F_UNLCK)
fail("F_GETLK reported this process's own lock");
lock.l_type = F_UNLCK;
if (fcntl(duplicate30, F_SETLK, &lock) != 0)
fail("fcntl unlock failed");
if (close(duplicate) != 0 || close(duplicate20) != 0 || close(duplicate30) != 0)
fail("duplicate close failed");
if (unlink(path) != 0)
fail("unlink failed");
run_cross_process_inheritance();
print("posix_fd_smoke: PASS\n");
return 0;
}