263 lines
9.4 KiB
C
263 lines
9.4 KiB
C
// User-mode smoke test for POSIX descriptor and open-description semantics.
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <spawn.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);
|
|
}
|
|
|
|
#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 int 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;
|
|
if (posix_spawn(&child, self, NULL, NULL, childArgv, NULL) != 0)
|
|
fail("inheritance child spawn failed");
|
|
|
|
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");
|
|
errno = 0;
|
|
if (write(INHERIT_PIPE_B_WRITE, "y", 1) != -1 || errno != EPIPE)
|
|
fail("final cross-process reader close did not produce EPIPE");
|
|
|
|
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;
|
|
}
|