diff --git a/test/Makefile b/test/Makefile index 6585edd6..f1202fe4 100644 --- a/test/Makefile +++ b/test/Makefile @@ -22,7 +22,7 @@ TESTS ?= env empty hello_world malloc mmap file fs_perms getpid spawn sched pipe truncate readdir mkdir open stat link symlink chmod chown tls pthread system_info rlimit \ server server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group posix_flock \ ioctl fcntl eventfd emulate_syscall access signal sysinfo prctl rename procfs wait \ - spawn_attribute exec statfs random umask pgrp vfork mount + spawn_attribute exec statfs random umask pgrp vfork mount flock # Benchmarks: need to be compiled and run by bench-% target BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput diff --git a/test/flock/Makefile b/test/flock/Makefile new file mode 100644 index 00000000..9e1b6dec --- /dev/null +++ b/test/flock/Makefile @@ -0,0 +1,5 @@ +include ../test_common.mk + +EXTRA_C_FLAGS := +EXTRA_LINK_FLAGS := +BIN_ARGS := diff --git a/test/flock/main.c b/test/flock/main.c new file mode 100644 index 00000000..2b3869d6 --- /dev/null +++ b/test/flock/main.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "test.h" + +// ============================================================================ +// Helper structs & variables & functions +// ============================================================================ + +const char *g_file_path = "/root/test_flock_file.txt"; +int g_fd; + +static int open_or_create_file() { + int flags = O_RDWR | O_CREAT; + int mode = 00666; + + int fd = open(g_file_path, flags, mode); + if (fd < 0) { + THROW_ERROR("failed to open or create file"); + } + return fd; +} + +static int remove_file() { + if (unlink(g_file_path) < 0) { + THROW_ERROR("failed to unlink the created file"); + } + return 0; +} + +// ============================================================================ +// Test cases for FLOCK +// ============================================================================ + +static int test_invalid_operation() { + // Check the operation with expected errno + int ops_with_expected_errno[5][2] = { + {LOCK_SH | LOCK_EX, EINVAL}, + {LOCK_SH | LOCK_UN, EINVAL}, + {LOCK_EX | LOCK_UN, EINVAL}, + {LOCK_SH | 0x1000, EINVAL}, + {LOCK_NB, EINVAL}, + }; + int row_cnt = (sizeof(ops_with_expected_errno) / sizeof(int)) / + (sizeof(ops_with_expected_errno[0]) / sizeof(int)); + for (int i = 0; i < row_cnt; i++) { + int ops = ops_with_expected_errno[i][0]; + int expected_errno = ops_with_expected_errno[i][1]; + errno = 0; + + int ret = flock(g_fd, ops); + if (!(ret < 0 && errno == expected_errno)) { + THROW_ERROR("failed to check flock with invalid operation"); + } + } + return 0; +} + +static int test_lock() { + int operation = LOCK_EX | LOCK_NB; + if (flock(g_fd, operation) < 0) { + THROW_ERROR("failed to lock file"); + } + + operation = LOCK_SH | LOCK_NB; + if (flock(g_fd, operation) < 0) { + THROW_ERROR("failed to lock file"); + } + return 0; +} + +static int test_spawn_child_and_unlock() { + int status, child_pid; + + char g_fd_buf[16]; + sprintf(g_fd_buf, "%d", g_fd); + const char *child_argv[3] = { + "flock", + g_fd_buf, + NULL + }; + int ret = posix_spawn(&child_pid, + "/bin/flock", NULL, NULL, + (char *const *)child_argv, + NULL); + if (ret < 0) { + THROW_ERROR("spawn process error"); + } + printf("Spawn a child process with pid=%d\n", child_pid); + + // Sleep 3s for the child to run flock test and wait, is 3s enough? + sleep(3); + + // Unlock the flock will cause child process to finish running + int operation = LOCK_UN; + if (flock(g_fd, operation) < 0) { + THROW_ERROR("failed to unlock the lock"); + } + + // Wait for child exit + ret = wait4(child_pid, &status, 0, NULL); + if (ret < 0) { + THROW_ERROR("failed to wait4 the child process"); + } + if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) { + THROW_ERROR("test cases in child faild"); + } + + // The lock will be unlocked on child exit, so we can lock again + operation = LOCK_EX; + ret = flock(g_fd, operation); + if (ret < 0 && errno != EINTR) { + THROW_ERROR("failed to check the result of flock"); + } + + return 0; +} + +// ============================================================================ +// Child Test cases +// ============================================================================ + +static int test_child_lock_wait() { + // Child open the file with new fd + int new_fd = open_or_create_file(); + + int operation = LOCK_SH | LOCK_NB; + if (flock(new_fd, operation) < 0) { + THROW_ERROR("failed set shared flock"); + } + + operation = LOCK_UN; + if (flock(new_fd, operation) < 0) { + THROW_ERROR("failed to unlock the new lock"); + } + + // Child inherits file table, so it can change the old lock to exclusive + operation = LOCK_EX | LOCK_NB; + if (flock(g_fd, operation) < 0) { + THROW_ERROR("failed change the lock type to exclusive lock"); + } + + // Try to set new lock + operation = LOCK_SH | LOCK_NB; + int res = flock(new_fd, operation); + if (!(res < 0 && errno == EAGAIN)) { + THROW_ERROR("failed to check the file lock state"); + } + // Child will wait here + operation = LOCK_SH; + res = flock(new_fd, operation); + if (res < 0 && errno != EINTR) { + THROW_ERROR("failed to check the result of flock with conflict lock"); + } + return 0; +} + +// ============================================================================ +// Test suite main +// ============================================================================ + +static test_case_t test_cases[] = { + TEST_CASE(test_invalid_operation), + TEST_CASE(test_lock), + TEST_CASE(test_spawn_child_and_unlock), +}; + +static test_case_t child_test_cases[] = { + TEST_CASE(test_child_lock_wait), +}; + +int main(int argc, const char *argv[]) { + // Test argc + if (argc == 2) { + g_fd = atoi(argv[1]); + if (test_suite_run(child_test_cases, ARRAY_SIZE(child_test_cases)) < 0) { + THROW_ERROR("failed run child test"); + } + } else { + g_fd = open_or_create_file(); + if (g_fd < 0) { + THROW_ERROR("failed to open/create file"); + } + if (test_suite_run(test_cases, ARRAY_SIZE(test_cases)) < 0) { + THROW_ERROR("failed run test"); + } + close(g_fd); + if (remove_file() < 0) { + THROW_ERROR("failed to remove file after test"); + } + } + return 0; +}