Skip to content
This repository has been archived by the owner on Jan 20, 2022. It is now read-only.

Commit

Permalink
[LibOS] Implement POSIX locks (fcntl)
Browse files Browse the repository at this point in the history
Signed-off-by: Paweł Marczewski <pawel@invisiblethingslab.com>
  • Loading branch information
pwmarcz committed Jun 30, 2021
1 parent af2f780 commit 32d2135
Show file tree
Hide file tree
Showing 17 changed files with 1,474 additions and 107 deletions.
9 changes: 9 additions & 0 deletions LibOS/shim/include/shim_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ struct shim_fs_ops {
* pretends to have many files in a directory. */
#define DENTRY_MAX_CHILDREN 1000000

struct fs_lock_info;

DEFINE_LIST(shim_dentry);
DEFINE_LISTP(shim_dentry);
struct shim_dentry {
Expand Down Expand Up @@ -152,6 +154,13 @@ struct shim_dentry {
/* Filesystem-specific data. Protected by `lock`. */
void* data;

/* File lock information, stored in the main process. See shim_fs_lock.c. */
struct fs_lock* fs_lock;

/* True if the file might have locks placed by current process. Used in processes other than
* main process, to prevent unnecessary IPC calls on handle close. See shim_fs_lock.c. */
bool maybe_has_locks;

struct shim_lock lock;
REFTYPE ref_count;
};
Expand Down
104 changes: 104 additions & 0 deletions LibOS/shim/include/shim_fs_lock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2021 Intel Corporation
* Paweł Marczewski <pawel@invisiblethingslab.com>
*/

/*
* File locks. Currently POSIX locks are implemented.
*/

#ifndef SHIM_FS_LOCK_H_
#define SHIM_FS_LOCK_H_


#include <stdbool.h>

#include "list.h"
#include "shim_types.h"

#define FS_LOCK_EOF ((uint64_t)-1)

struct shim_dentry;

/* Initialize the file locking subsystem. */
int init_fs_lock(void);

/*
* POSIX locks (also known as advisory record locks). See `man fcntl` for details.
*
* The current implementation works over IPC and handles all requests in the main process. It has
* the following caveats:
*
* - Lock requests from other processes will always have the overhead of IPC round-trip, even if the
* lock is uncontested.
* - The main process has to be able to look up the same file, so locking will not work for files in
* 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.)
*/

DEFINE_LISTP(posix_lock);
DEFINE_LIST(posix_lock);
struct posix_lock {
/* Lock type: F_RDLCK, F_WRLCK, F_UNLCK */
int type;

/* First byte of range */
uint64_t start;

/* Last byte of range (use FS_LOCK_EOF for a range until end of file) */
uint64_t end;

/* PID of process taking the lock */
IDTYPE pid;

/* List node, used internally */
LIST_TYPE(posix_lock) list;
};

/*!
* \brief Set or remove a lock on a file
*
* \param dent the dentry for a file
* \param pl parameters of new lock
* \param wait if true, will wait until a lock can be taken
*
* This is the equivalent of `fnctl(F_SETLK/F_SETLKW)`.
*
* If `pl->type` is `F_UNLCK`, the function will remove any locks held by the given PID for the
* given range. Removing a locks never waits.
*
* If `pl->type` is `F_RDLCK` or `F_WRLCK`, the function will create a new lock for the given PID
* and range, replacing the existing locks held by the given PID for that range. If there are
* conflicting locks, the function either waits (if `wait` is true), or fails with `-EAGAIN` (if
* `wait` is false).
*/
int posix_lock_set(struct shim_dentry* dent, struct posix_lock* pl, bool wait);

/*!
* \brief Check for conflicting locks on a file
*
* \param dent the dentry for a file
* \param pl parameters of new lock (type cannot be `F_UNLCK`)
* \param[out] out_pl on success, set to `F_UNLCK` or details of a conflicting lock
*
* This is the equivalent of `fcntl(F_GETLK)`.
*
* The function checks if there are locks by other PIDs preventing the proposed lock from being
* placed. If the lock could be placed, `out_pl->type` is set to `F_UNLCK`. Otherwise, `out_pl`
* fields (`type`, `start, `end`, `pid`) are set to details of a conflicting lock.
*/
int posix_lock_get(struct shim_dentry* dent, struct posix_lock* pl, struct posix_lock* out_pl);

/* Removes all locks for a given PID. Should be called before process exit. */
int posix_lock_clear_pid(IDTYPE pid);

/* Version of `posix_lock_set` called from IPC callback. */
int posix_lock_set_from_ipc(const char* path, struct posix_lock* pl, bool wait, IDTYPE vmid,
unsigned long seq, bool* postponed);

/* Version of `posix_lock_get` called from IPC callback. */
int posix_lock_get_from_ipc(const char* path, struct posix_lock* pl, struct posix_lock* out_pl);

#endif /* SHIM_FS_LOCK_H */
38 changes: 38 additions & 0 deletions LibOS/shim/include/shim_ipc.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ enum {
IPC_MSG_SYNC_CONFIRM_UPGRADE,
IPC_MSG_SYNC_CONFIRM_DOWNGRADE,
IPC_MSG_SYNC_CONFIRM_CLOSE,
IPC_MSG_POSIX_LOCK_SET,
IPC_MSG_POSIX_LOCK_GET,
IPC_MSG_POSIX_LOCK_CLEAR_PID,
IPC_MSG_CODE_BOUND,
};

Expand Down Expand Up @@ -253,4 +256,39 @@ int ipc_sync_confirm_upgrade_callback(IDTYPE src, void* data, unsigned long seq)
int ipc_sync_confirm_downgrade_callback(IDTYPE src, void* data, unsigned long seq);
int ipc_sync_confirm_close_callback(IDTYPE src, void* data, unsigned long seq);

/*
* POSIX_LOCK_SET: `struct shim_ipc_posix_lock` -> `int`
* POSIX_LOCK_GET: `struct shim_ipc_posix_lock` -> `struct shim_ipc_posix_lock_resp`
* POSIX_LOCK_CLEAR_PID: `IDTYPE` -> `int`
*/

struct shim_ipc_posix_lock {
int type;
uint64_t start;
uint64_t end;
IDTYPE pid;

bool wait;
char path[]; /* null-terminated */
};

struct shim_ipc_posix_lock_resp {
int result;

int type;
uint64_t start;
uint64_t end;
IDTYPE pid;
};

struct posix_lock;

int ipc_posix_lock_set(const char* path, struct posix_lock* pl, bool wait);
int ipc_posix_lock_set_send_response(IDTYPE vmid, unsigned long seq, int result);
int ipc_posix_lock_get(const char* path, struct posix_lock* pl, struct posix_lock* out_pl);
int ipc_posix_lock_clear_pid(IDTYPE pid);
int ipc_posix_lock_set_callback(IDTYPE src, void* data, unsigned long seq);
int ipc_posix_lock_get_callback(IDTYPE src, void* data, unsigned long seq);
int ipc_posix_lock_clear_pid_callback(IDTYPE src, void* data, unsigned long seq);

#endif /* SHIM_IPC_H_ */
17 changes: 17 additions & 0 deletions LibOS/shim/src/bookkeep/shim_handle.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "pal_error.h"
#include "shim_checkpoint.h"
#include "shim_fs.h"
#include "shim_fs_lock.h"
#include "shim_handle.h"
#include "shim_internal.h"
#include "shim_lock.h"
Expand Down Expand Up @@ -313,6 +314,22 @@ struct shim_handle* detach_fd_handle(FDTYPE fd, int* flags, struct shim_handle_m
handle = __detach_fd_handle(handle_map->map[fd], flags, handle_map);

unlock(&handle_map->lock);

if (handle && handle->dentry) {
/* Clear POSIX locks for a file. We are required to do that every time a FD is closed
* closed, even if the process holds other handles for that file, or duplicated FDs for the
* same handle. */
struct posix_lock pl = {
.type = F_UNLCK,
.start = 0,
.end = FS_LOCK_EOF,
.pid = g_process.pid,
};
int ret = posix_lock_set(handle->dentry, &pl, /*block=*/false);
if (ret < 0)
log_warning("error releasing locks: %d", ret);
}

return handle;
}

Expand Down
2 changes: 2 additions & 0 deletions LibOS/shim/src/fs/shim_dcache.c
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ BEGIN_CP_FUNC(dentry) {
new_dent->data = NULL;
}

new_dent->fs_lock = NULL;

DO_CP_IN_MEMBER(qstr, new_dent, name);

if (new_dent->mount)
Expand Down
Loading

0 comments on commit 32d2135

Please sign in to comment.