diff --git a/libos/include/libos_fs_lock.h b/libos/include/libos_fs_lock.h index 5dc6d489a0..e3be58a930 100644 --- a/libos/include/libos_fs_lock.h +++ b/libos/include/libos_fs_lock.h @@ -4,7 +4,8 @@ */ /* - * File locks. Currently only POSIX locks are implemented. + * File locks. Both POSIX locks (fcntl syscall) and BSD locks (flock syscall) are implemented via + * a common struct `posix_lock` (the name is historic). See `man fcntl` and `man flock` for details. */ #pragma once @@ -22,8 +23,8 @@ struct libos_dentry; int init_fs_lock(void); /* - * File locks. Currently describes only POSIX locks (also known as advisory record locks). See `man - * fcntl` for details. + * File locks. Describes both POSIX locks aka advisory record locks (fcntl syscall) and BSD locks + * (flock syscall). See `man fcntl` and `man flock` for details. * * The current implementation works over IPC and handles all requests in the main process. It has * the following caveats: @@ -34,7 +35,7 @@ int init_fs_lock(void); * local-process-only filesystems (tmpfs). * - There is no deadlock detection (EDEADLK). * - The lock requests cannot be interrupted (EINTR). - * - The locks work only on files that have a dentry (no pipes, sockets etc.) + * - The locks work only on files that have a dentry (no pipes, sockets etc.). */ DEFINE_LISTP(libos_file_lock); @@ -54,6 +55,9 @@ struct libos_file_lock { /* List node, used internally */ LIST_TYPE(libos_file_lock) list; + + /* Unique handle id, works as an identifier for `flock` syscall */ + uint64_t handle_id; }; /*! @@ -125,3 +129,14 @@ int file_lock_set_from_ipc(const char* path, struct libos_file_lock* file_lock, */ int file_lock_get_from_ipc(const char* path, struct libos_file_lock* file_lock, struct libos_file_lock* out_file_lock); + +/*! + * \brief Determine whether dentry has flock-typed locks. + * + * \param dent The dentry for a file. + * + * Returns true if at least one associated lock is flock-typed. Otherwise returns false. Note that + * if a dentry has a mix of fcntl and flock locks, then this function returns true (and the + * subsequent behavior is undefined). + */ +bool has_flock_locks(struct libos_dentry* dent); diff --git a/libos/include/libos_handle.h b/libos/include/libos_handle.h index 056e31aab2..25f1b8b0bd 100644 --- a/libos/include/libos_handle.h +++ b/libos/include/libos_handle.h @@ -134,6 +134,10 @@ struct libos_handle { enum libos_handle_type type; bool is_dir; + /* Unique id, works as an identifier for `flock` syscall. This field does not change, so + * reading it does not require holding any locks. */ + uint64_t id; + refcount_t ref_count; struct libos_fs* fs; diff --git a/libos/include/libos_ipc.h b/libos/include/libos_ipc.h index 6478f47cd4..9502713905 100644 --- a/libos/include/libos_ipc.h +++ b/libos/include/libos_ipc.h @@ -281,6 +281,7 @@ struct libos_ipc_file_lock { int type; uint64_t start; uint64_t end; + uint64_t handle_id; IDTYPE pid; bool wait; @@ -294,6 +295,7 @@ struct libos_ipc_file_lock_resp { int type; uint64_t start; uint64_t end; + uint64_t handle_id; IDTYPE pid; }; diff --git a/libos/src/bookkeep/libos_handle.c b/libos/src/bookkeep/libos_handle.c index 7d73c02490..25bc6ff725 100644 --- a/libos/src/bookkeep/libos_handle.c +++ b/libos/src/bookkeep/libos_handle.c @@ -292,7 +292,7 @@ static struct libos_handle* __detach_fd_handle(struct libos_fd_handle* fd, int* } static int clear_posix_locks(struct libos_handle* handle) { - if (handle && handle->dentry) { + if (handle && handle->dentry && (!has_flock_locks(handle->dentry))) { /* Clear file (POSIX) locks for a file. We are required to do that every time a FD is * closed, even if the process holds other handles for that file, or duplicated FDs for the * same handle. */ @@ -301,6 +301,7 @@ static int clear_posix_locks(struct libos_handle* handle) { .start = 0, .end = FS_LOCK_EOF, .pid = g_process.pid, + .handle_id = 0, }; int ret = file_lock_set(handle->dentry, &file_lock, /*block=*/false); if (ret < 0) { @@ -350,6 +351,10 @@ struct libos_handle* get_new_handle(void) { } INIT_LISTP(&new_handle->epoll_items); new_handle->epoll_items_count = 0; + + static uint64_t local_counter = 0; + new_handle->id = ((uint64_t)g_process.pid << 32) + | __atomic_add_fetch(&local_counter, 1, __ATOMIC_RELAXED); return new_handle; } @@ -475,6 +480,26 @@ static void destroy_handle(struct libos_handle* hdl) { free_mem_obj_to_mgr(handle_mgr, hdl); } +static int clear_flock_locks(struct libos_handle* hdl) { + /* Clear flock (BSD) locks for a file. We are required to do that when the handle is closed. */ + if (hdl && hdl->dentry && has_flock_locks(hdl->dentry)) { + assert(hdl->ref_count == 0); + struct libos_file_lock file_lock = { + .type = F_UNLCK, + .start = 0, + .end = FS_LOCK_EOF, + .pid = g_process.pid, + .handle_id = hdl->id, + }; + int ret = file_lock_set(hdl->dentry, &file_lock, /*block=*/false); + if (ret < 0) { + log_warning("error releasing locks: %s", unix_strerror(ret)); + return ret; + } + } + return 0; +} + void put_handle(struct libos_handle* hdl) { refcount_t ref_count = refcount_dec(&hdl->ref_count); @@ -496,8 +521,10 @@ void put_handle(struct libos_handle* hdl) { hdl->pal_handle = NULL; } - if (hdl->dentry) + if (hdl->dentry) { + (void)clear_flock_locks(hdl); put_dentry(hdl->dentry); + } if (hdl->inode) put_inode(hdl->inode); diff --git a/libos/src/fs/libos_fs_lock.c b/libos/src/fs/libos_fs_lock.c index 30948ce067..3119d2fb99 100644 --- a/libos/src/fs/libos_fs_lock.c +++ b/libos/src/fs/libos_fs_lock.c @@ -44,14 +44,22 @@ struct file_lock_request { LIST_TYPE(file_lock_request) list; }; -/* Describes file locks' details for a given dentry. Currently holds only POSIX locks. */ +/* Describes file locks' details for a given dentry. Holds both POSIX (fcntl) and BSD (flock) + * locks. */ DEFINE_LISTP(dent_file_locks); DEFINE_LIST(dent_file_locks); struct dent_file_locks { struct libos_dentry* dent; - /* Currently only POSIX locks, sorted by PID and then by start position (so that we are able to - * merge and split locks). The ranges do not overlap within a given PID. */ + /* + * POSIX (fcntl) and BSD (flock) locks for a given dentry. + * + * For POSIX locks: + * - sorted by PID and then by start position (so that we are able to merge and split locks), + * - the ranges do not overlap within a given PID. + * + * BSD locks do not have ranges, thus the above properties do not apply. + */ LISTP_TYPE(libos_file_lock) file_locks; /* Pending requests. */ @@ -104,11 +112,18 @@ static void file_locks_dump(struct dent_file_locks* dent_file_locks) { struct libos_file_lock* file_lock; LISTP_FOR_EACH_ENTRY(file_lock, &dent_file_locks->file_locks, list) { - if (file_lock->pid != pid) { + if (file_lock->handle_id == 0) { + if (file_lock->pid != pid) { + if (pid != 0) + buf_flush(&buf); + pid = file_lock->pid; + buf_printf(&buf, "fcntl (POSIX): pid=%d:", pid); + } + } else { if (pid != 0) buf_flush(&buf); - pid = file_lock->pid; - buf_printf(&buf, "%d:", pid); + pid = 123; /* dummy pid to force a flush in the line above */ + buf_printf(&buf, " flock (BSD): handle id=%lu:", file_lock->handle_id); } char c; @@ -147,8 +162,10 @@ static void dent_file_locks_gc(struct dent_file_locks* dent_file_locks) { } /* - * Find first lock that conflicts with `file_lock`. Two locks conflict if they have different PIDs, - * their ranges overlap, and at least one of them is a write lock. + * Find first lock that conflicts with `file_lock`. For fcntl locks (file_lock->handle_id == 0), two + * locks conflict if they have different PIDs, their ranges overlap, and at least one of them is a + * write lock. For flock locks, two locks conflict if they have different handle IDs and at least + * one of them is a write lock. */ static struct libos_file_lock* file_lock_find_conflict(struct dent_file_locks* dent_file_locks, struct libos_file_lock* file_lock) { @@ -156,11 +173,21 @@ static struct libos_file_lock* file_lock_find_conflict(struct dent_file_locks* d assert(file_lock->type != F_UNLCK); struct libos_file_lock* cur; - LISTP_FOR_EACH_ENTRY(cur, &dent_file_locks->file_locks, list) { - if (cur->pid != file_lock->pid && file_lock->start <= cur->end - && cur->start <= file_lock->end - && (cur->type == F_WRLCK || file_lock->type == F_WRLCK)) - return cur; + /* Gramine doesn't support mixing POSIX and flock types of locks, and assumes that the + * application won't ever mix them, otherwise it may exhibit unexpected behavior. */ + if (file_lock->handle_id == 0) { + LISTP_FOR_EACH_ENTRY(cur, &dent_file_locks->file_locks, list) { + if (cur->pid != file_lock->pid && file_lock->start <= cur->end + && cur->start <= file_lock->end + && (cur->type == F_WRLCK || file_lock->type == F_WRLCK)) + return cur; + } + } else { + LISTP_FOR_EACH_ENTRY(cur, &dent_file_locks->file_locks, list) { + if (cur->handle_id != file_lock->handle_id + && (cur->type == F_WRLCK || file_lock->type == F_WRLCK)) + return cur; + } } return NULL; } @@ -184,9 +211,29 @@ static int file_lock_add_request(struct dent_file_locks* dent_file_locks, return 0; } +bool has_flock_locks(struct libos_dentry* dent) { + lock(&g_fs_lock_lock); + if (!dent->file_locks) { + unlock(&g_fs_lock_lock); + return false; + } + + bool has_flock = false; + struct dent_file_locks* dent_file_locks = dent->file_locks; + struct libos_file_lock* cur; + LISTP_FOR_EACH_ENTRY(cur, &dent_file_locks->file_locks, list) { + if (cur->handle_id != 0) { + has_flock = true; + break; + } + } + unlock(&g_fs_lock_lock); + return has_flock; +} + /* - * Main part of `file_lock_set`. Adds/removes a lock (depending on `file_lock->type`), assumes we - * already verified there are no conflicts. Replaces existing locks for a given PID, and merges + * Main part of `file_lock_set`. Adds/removes a POSIX lock (depending on `file_lock->type`), assumes + * we already verified there are no conflicts. Replaces existing locks for a given PID, and merges * adjacent locks if possible. * * See also Linux sources (`fs/locks.c`) for a similar implementation. @@ -194,6 +241,7 @@ static int file_lock_add_request(struct dent_file_locks* dent_file_locks, static int _posix_lock_set(struct dent_file_locks* dent_file_locks, struct libos_file_lock* file_lock) { assert(locked(&g_fs_lock_lock)); + assert(file_lock->handle_id == 0); /* Preallocate new locks first, so that we don't fail after modifying something. */ @@ -289,6 +337,7 @@ static int _posix_lock_set(struct dent_file_locks* dent_file_locks, extra->start = end + 1; extra->end = cur->end; extra->pid = cur->pid; + extra->handle_id = 0; cur->end = start - 1; LISTP_ADD_AFTER(extra, cur, &dent_file_locks->file_locks, list); extra = NULL; @@ -324,11 +373,11 @@ static int _posix_lock_set(struct dent_file_locks* dent_file_locks, if (new) { assert(file_lock->type != F_UNLCK); - new->type = file_lock->type; new->start = start; new->end = end; new->pid = file_lock->pid; + new->handle_id = 0; #ifdef DEBUG /* Assert that list order is preserved */ @@ -354,6 +403,49 @@ static int _posix_lock_set(struct dent_file_locks* dent_file_locks, return 0; } +/* + * Main part of `file_lock_set`. Adds/removes a BSD lock (depending on `file_lock->type`), assumes + * we already verified there are no conflicts. Replaces existing locks for a given handle ID. + */ +static int _flock_lock_set(struct dent_file_locks* dent_file_locks, + struct libos_file_lock* file_lock) { + assert(locked(&g_fs_lock_lock)); + assert(file_lock->handle_id); + + /* Lock to be added. Not necessary for F_UNLCK, because we're only removing existing locks. */ + struct libos_file_lock* new = NULL; + if (file_lock->type != F_UNLCK) { + new = malloc(sizeof(*new)); + if (!new) + return -ENOMEM; + } + + struct libos_file_lock* cur; + struct libos_file_lock* tmp; + LISTP_FOR_EACH_ENTRY_SAFE(cur, tmp, &dent_file_locks->file_locks, list) { + if (cur->handle_id == file_lock->handle_id) { + LISTP_DEL(cur, &dent_file_locks->file_locks, list); + free(cur); + break; + } + } + + if (new) { + assert(file_lock->type != F_UNLCK); + new->type = file_lock->type; + /* Lock the whole file; start, end and pid fields are set only for sanity + * (not used by flock). */ + new->start = 0; + new->end = FS_LOCK_EOF; + new->pid = file_lock->pid; + new->handle_id = file_lock->handle_id; + + LISTP_ADD(new, &dent_file_locks->file_locks, list); + } + + return 0; +} + /* * Process pending requests. This function should be called after any modification to the list of * locks, since we might have unblocked a request. @@ -373,7 +465,9 @@ static void file_lock_process_requests(struct dent_file_locks* dent_file_locks) struct libos_file_lock* conflict = file_lock_find_conflict(dent_file_locks, &req->file_lock); if (!conflict) { - int result = _posix_lock_set(dent_file_locks, &req->file_lock); + int result = req->file_lock.handle_id == 0 + ? _posix_lock_set(dent_file_locks, &req->file_lock) + : _flock_lock_set(dent_file_locks, &req->file_lock); LISTP_DEL(req, &dent_file_locks->file_lock_requests, list); /* Notify the waiter that we processed their request. Note that the result might @@ -434,7 +528,8 @@ static int file_lock_set_or_add_request(struct libos_dentry* dent, *out_req = req; } else { - ret = _posix_lock_set(dent_file_locks, file_lock); + ret = file_lock->handle_id == 0 ? _posix_lock_set(dent_file_locks, file_lock) + : _flock_lock_set(dent_file_locks, file_lock); if (ret < 0) goto out; file_lock_process_requests(dent_file_locks); @@ -581,6 +676,7 @@ int file_lock_get(struct libos_dentry* dent, struct libos_file_lock* file_lock, out_file_lock->start = conflict->start; out_file_lock->end = conflict->end; out_file_lock->pid = conflict->pid; + out_file_lock->handle_id = conflict->handle_id; } else { out_file_lock->type = F_UNLCK; } @@ -613,7 +709,7 @@ int file_lock_get_from_ipc(const char* path, struct libos_file_lock* file_lock, return ret; } -/* Removes all locks and lock requests for a given PID and dentry. */ +/* Removes all POSIX locks and lock requests for a given PID and dentry. */ static int file_lock_clear_pid_from_dentry(struct libos_dentry* dent, IDTYPE pid) { assert(locked(&g_fs_lock_lock)); @@ -631,7 +727,7 @@ static int file_lock_clear_pid_from_dentry(struct libos_dentry* dent, IDTYPE pid struct libos_file_lock* file_lock; struct libos_file_lock* file_lock_tmp; LISTP_FOR_EACH_ENTRY_SAFE(file_lock, file_lock_tmp, &dent_file_locks->file_locks, list) { - if (file_lock->pid == pid) { + if (file_lock->handle_id == 0 && file_lock->pid == pid) { LISTP_DEL(file_lock, &dent_file_locks->file_locks, list); free(file_lock); changed = true; @@ -641,7 +737,7 @@ static int file_lock_clear_pid_from_dentry(struct libos_dentry* dent, IDTYPE pid struct file_lock_request* req; struct file_lock_request* req_tmp; LISTP_FOR_EACH_ENTRY_SAFE(req, req_tmp, &dent_file_locks->file_lock_requests, list) { - if (req->file_lock.pid == pid) { + if (req->file_lock.handle_id == 0 && req->file_lock.pid == pid) { assert(!req->notify.event); LISTP_DEL(req, &dent_file_locks->file_lock_requests, list); free(req); diff --git a/libos/src/ipc/libos_ipc_fs_lock.c b/libos/src/ipc/libos_ipc_fs_lock.c index 3b0e96a23d..f43a8a0f37 100644 --- a/libos/src/ipc/libos_ipc_fs_lock.c +++ b/libos/src/ipc/libos_ipc_fs_lock.c @@ -17,6 +17,7 @@ int ipc_file_lock_set(const char* path, struct libos_file_lock* file_lock, bool .type = file_lock->type, .start = file_lock->start, .end = file_lock->end, + .handle_id = file_lock->handle_id, .pid = file_lock->pid, .wait = wait, @@ -60,6 +61,7 @@ int ipc_file_lock_get(const char* path, struct libos_file_lock* file_lock, .type = file_lock->type, .start = file_lock->start, .end = file_lock->end, + .handle_id = file_lock->handle_id, .pid = file_lock->pid, }; @@ -85,6 +87,7 @@ int ipc_file_lock_get(const char* path, struct libos_file_lock* file_lock, out_file_lock->type = resp->type; out_file_lock->start = resp->start; out_file_lock->end = resp->end; + out_file_lock->handle_id = resp->handle_id; out_file_lock->pid = resp->pid; } free(data); @@ -115,6 +118,7 @@ int ipc_file_lock_set_callback(IDTYPE src, void* data, unsigned long seq) { .start = msgin->start, .end = msgin->end, .pid = msgin->pid, + .handle_id = msgin->handle_id, }; return file_lock_set_from_ipc(msgin->path, &file_lock, msgin->wait, src, seq); @@ -127,6 +131,7 @@ int ipc_file_lock_get_callback(IDTYPE src, void* data, unsigned long seq) { .start = msgin->start, .end = msgin->end, .pid = msgin->pid, + .handle_id = msgin->handle_id, }; struct libos_file_lock file_lock2 = {0}; @@ -136,6 +141,7 @@ int ipc_file_lock_get_callback(IDTYPE src, void* data, unsigned long seq) { .type = file_lock2.type, .start = file_lock2.start, .end = file_lock2.end, + .handle_id = file_lock2.handle_id, .pid = file_lock2.pid, }; diff --git a/libos/src/sys/libos_fcntl.c b/libos/src/sys/libos_fcntl.c index e17ae657ac..d373c50369 100644 --- a/libos/src/sys/libos_fcntl.c +++ b/libos/src/sys/libos_fcntl.c @@ -5,7 +5,9 @@ */ /* - * Implementation of system call "fcntl": + * Implementation of system calls "fcntl" and "flock". + * + * The "fcntl" syscall supports: * * - F_DUPFD, F_DUPFD_CLOEXEC (duplicate a file descriptor) * - F_GETFD, F_SETFD (file descriptor flags) @@ -58,7 +60,7 @@ int set_handle_nonblocking(struct libos_handle* handle, bool on) { * We need to return -EINVAL for underflow (positions before start of file), and -EOVERFLOW for * positive overflow. */ -static int flock_to_file_lock(struct flock* fl, struct libos_handle* hdl, +static int flock_to_file_lock(struct flock* fl, struct libos_handle* hdl, uint64_t handle_id, struct libos_file_lock* file_lock) { if (!(fl->l_type == F_RDLCK || fl->l_type == F_WRLCK || fl->l_type == F_UNLCK)) return -EINVAL; @@ -128,6 +130,7 @@ static int flock_to_file_lock(struct flock* fl, struct libos_handle* hdl, file_lock->start = start; file_lock->end = end; file_lock->pid = g_process.pid; + file_lock->handle_id = handle_id; return 0; } @@ -212,7 +215,7 @@ long libos_syscall_fcntl(int fd, int cmd, unsigned long arg) { } struct libos_file_lock file_lock; - ret = flock_to_file_lock(fl, hdl, &file_lock); + ret = flock_to_file_lock(fl, hdl, /*handle_id=*/0, &file_lock); if (ret < 0) break; @@ -235,7 +238,7 @@ long libos_syscall_fcntl(int fd, int cmd, unsigned long arg) { } struct libos_file_lock file_lock; - ret = flock_to_file_lock(fl, hdl, &file_lock); + ret = flock_to_file_lock(fl, hdl, /*handle_id=*/0, &file_lock); if (ret < 0) break; @@ -294,18 +297,29 @@ long libos_syscall_flock(unsigned int fd, unsigned int cmd) { if (!hdl) return -EBADF; + struct flock fl = { .l_whence = SEEK_SET }; + switch (cmd & ~LOCK_NB) { case LOCK_EX: + fl.l_type = F_WRLCK; + break; case LOCK_SH: + fl.l_type = F_RDLCK; + break; case LOCK_UN: + fl.l_type = F_UNLCK; break; default: ret = -EINVAL; goto out; } - /* TODO: add implementation */ - ret = -ENOSYS; + struct libos_file_lock file_lock; + ret = flock_to_file_lock(&fl, hdl, hdl->id, &file_lock); + if (ret < 0) + goto out; + + ret = file_lock_set(hdl->dentry, &file_lock, !(cmd & LOCK_NB)); out: put_handle(hdl); return ret; diff --git a/libos/test/ltp/ltp.cfg b/libos/test/ltp/ltp.cfg index 290fb043eb..e40899a1b9 100644 --- a/libos/test/ltp/ltp.cfg +++ b/libos/test/ltp/ltp.cfg @@ -547,8 +547,8 @@ skip = yes [flistxattr*] skip = yes -# no flock() -[flock*] +# uses futexes on memory shared between processes +[flock03] skip = yes # %fs test, i386 only diff --git a/libos/test/regression/flock_lock.c b/libos/test/regression/flock_lock.c new file mode 100644 index 0000000000..3adbff4cc8 --- /dev/null +++ b/libos/test/regression/flock_lock.c @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2023 Intel Corporation + * Liang Ma <liang3.ma@intel.com> + */ + +/* + * Test for `flock` syscall (`flock(LOCK_EX/LOCK_SH/LOCK_UN`). We assert that the calls succeed (or + * taking a lock fails), and log all details for debugging purposes. + * + * The tests involve multithreaded, dup and file-backed mmap cases. We don't add multi-process cases + * here because they are already covered by LTP tests `flock03` and `flock04`. + */ + +#define _GNU_SOURCE +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/file.h> +#include <sys/mman.h> +#include <unistd.h> + +#include "common.h" + +#define TEST_FILE "tmp/flock_file" +#define FILE_SIZE 1024 + +struct thread_args { + int pipes[2][2]; +}; + +static const char* str_type(int type) { + switch (type) { + case LOCK_SH: return "LOCK_SH"; + case LOCK_EX: return "LOCK_EX"; + case LOCK_UN: return "LOCK_UN"; + case LOCK_SH | LOCK_NB: return "LOCK_SH | LOCK_NB"; + case LOCK_EX | LOCK_NB: return "LOCK_EX | LOCK_NB"; + default: return "???"; + } +} + +static void try_flock(int fd, int operation, int expect_ret) { + int ret = flock(fd, operation); + if (ret != expect_ret) { + errx(1, "flock(%d, %s) error with return value = %d, expected value = %d", + fd, str_type(operation), ret, expect_ret); + } +} + +static void open_pipes(int pipes[2][2]) { + for (unsigned int i = 0; i < 2; i++) { + CHECK(pipe(pipes[i])); + } +} + +static void close_pipes(int pipes[2][2]) { + for (unsigned int i = 0; i < 2; i++) { + for (unsigned int j = 0; j < 2; j++) { + CHECK(close(pipes[i][j])); + } + } +} + +static void write_pipe(int pipe[2]) { + char c = 0; + int ret; + do { + ret = write(pipe[1], &c, sizeof(c)); + } while (ret == -1 && errno == EINTR); + if (ret == -1) + err(1, "write"); +} + +static void read_pipe(int pipe[2]) { + char c; + int ret; + do { + ret = read(pipe[0], &c, sizeof(c)); + } while (ret == -1 && errno == EINTR); + if (ret == -1) + err(1, "read"); + if (ret == 0) + err(1, "pipe closed"); +} + +static void test_flock_dup_open(void) { + printf("testing locks with the dup and open...\n"); + int fd = CHECK(open(TEST_FILE, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)); + try_flock(fd, LOCK_EX, 0); + + int fd2 = CHECK(dup(fd)); + try_flock(fd2, LOCK_EX, 0); + try_flock(fd, LOCK_UN, 0); + try_flock(fd2, LOCK_EX, 0); + + int fd3 = CHECK(open(TEST_FILE, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)); + CHECK(close(fd)); + try_flock(fd3, LOCK_EX | LOCK_NB, -1); + CHECK(close(fd2)); + try_flock(fd3, LOCK_EX | LOCK_NB, 0); + CHECK(close(fd3)); +} + +static void test_mmap_flock_close_unmap(void) { + printf("testing locks with the mmap and flock...\n"); + int fd1, fd2; + void* file_data; + + fd1 = CHECK(open(TEST_FILE, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)); + file_data = mmap(NULL, FILE_SIZE, PROT_READ, MAP_SHARED, fd1, 0); + if (file_data == MAP_FAILED) { + err(1, "mmap"); + } + try_flock(fd1, LOCK_EX, 0); + CHECK(close(fd1)); + fd2 = CHECK(open(TEST_FILE, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)); + try_flock(fd2, LOCK_EX | LOCK_NB, -1); + CHECK(munmap(file_data, FILE_SIZE)); + try_flock(fd2, LOCK_EX, 0); + CHECK(close(fd2)); +} + +static void* thread_flock_first(void* arg) { + struct thread_args* args = (struct thread_args*)arg; + + int fd = CHECK(open(TEST_FILE, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)); + try_flock(fd, LOCK_EX | LOCK_NB, 0); + write_pipe(args->pipes[0]); + read_pipe(args->pipes[1]); + try_flock(fd, LOCK_UN, 0); + write_pipe(args->pipes[0]); + read_pipe(args->pipes[1]); + try_flock(fd, LOCK_SH | LOCK_NB, 0); + CHECK(close(fd)); + + return arg; +} + +static void* thread_flock_second(void* arg) { + struct thread_args* args = (struct thread_args*)arg; + + int fd = CHECK(open(TEST_FILE, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600)); + read_pipe(args->pipes[0]); + try_flock(fd, LOCK_EX | LOCK_NB, -1); + write_pipe(args->pipes[1]); + read_pipe(args->pipes[0]); + try_flock(fd, LOCK_SH | LOCK_NB, 0); + write_pipe(args->pipes[1]); + CHECK(close(fd)); + + return arg; +} + +static void test_flock_multithread(void) { + printf("testing flock with multithread...\n"); + int ret; + pthread_t threads[2]; + struct thread_args args = {0}; + open_pipes(args.pipes); + + ret = pthread_create(&threads[0], NULL, thread_flock_first, (void*)&args); + if (ret != 0) + errx(1, "pthread_create"); + + ret = pthread_create(&threads[1], NULL, thread_flock_second, (void*)&args); + if (ret != 0) + errx(1, "pthread_create"); + + for (int i = 0; i < 2; i++) { + if ((ret = pthread_join(threads[i], NULL)) != 0) { + errx(1, "pthread_join"); + } + } + close_pipes(args.pipes); +} + +int main(void) { + setbuf(stdout, NULL); + + test_flock_dup_open(); + test_mmap_flock_close_unmap(); + test_flock_multithread(); + + CHECK(unlink(TEST_FILE)); + printf("TEST OK\n"); + return 0; +} diff --git a/libos/test/regression/meson.build b/libos/test/regression/meson.build index 9ef611b8a7..15cd4a39dd 100644 --- a/libos/test/regression/meson.build +++ b/libos/test/regression/meson.build @@ -33,6 +33,7 @@ tests = { 'fdleak': {}, 'file_check_policy': {}, 'file_size': {}, + 'flock_lock': {}, 'fopen_cornercases': {}, 'fork_and_exec': {}, 'fp_multithread': { diff --git a/libos/test/regression/test_libos.py b/libos/test/regression/test_libos.py index fb5fbc3f73..3d3fbf039a 100644 --- a/libos/test/regression/test_libos.py +++ b/libos/test/regression/test_libos.py @@ -969,6 +969,14 @@ def test_130_sid(self): stdout, _ = self.run_binary(['sid']) self.assertIn("TEST OK", stdout) + def test_140_flock_lock(self): + try: + stdout, _ = self.run_binary(['flock_lock']) + finally: + if os.path.exists('tmp/flock_file'): + os.remove('tmp/flock_file') + self.assertIn('TEST OK', stdout) + class TC_31_Syscall(RegressionTestCase): def test_000_syscall_redirect(self): stdout, _ = self.run_binary(['syscall']) diff --git a/libos/test/regression/tests.toml b/libos/test/regression/tests.toml index 498ab28d9d..09d9dbca90 100644 --- a/libos/test/regression/tests.toml +++ b/libos/test/regression/tests.toml @@ -36,6 +36,7 @@ manifests = [ "file_check_policy_allow_all_but_log", "file_check_policy_strict", "file_size", + "flock_lock", "fopen_cornercases", "fork_and_exec", "fork_disallowed", diff --git a/libos/test/regression/tests_musl.toml b/libos/test/regression/tests_musl.toml index b747158852..7ea4655c2d 100644 --- a/libos/test/regression/tests_musl.toml +++ b/libos/test/regression/tests_musl.toml @@ -38,6 +38,7 @@ manifests = [ "file_check_policy_allow_all_but_log", "file_check_policy_strict", "file_size", + "flock_lock", "fopen_cornercases", "fork_and_exec", "fork_disallowed",