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

[LibOS] Implement POSIX locks #2481

Merged
merged 1 commit into from
Jul 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 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,15 @@ struct shim_dentry {
/* Filesystem-specific data. Protected by `lock`. */
void* data;

/* File lock information, stored only in the main process. Protected by `lock`. 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. Protected by `lock`. See
* `shim_fs_lock.c`. */
bool maybe_has_fs_locks;

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

/*
* File locks. Currently only 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 lock 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);

/*!
* \brief Set or remove a lock on a file (IPC handler)
*
* \param path absolute path for a file
* \param pl parameters of new lock
* \param wait if true, will postpone the response until a lock can be taken
* \param vmid target process for IPC response
* \param seq sequence number for IPC response
*
* This is a version of `posix_lock_set` called from an IPC callback. This function is responsible
* for either sending an IPC response immediately, or scheduling one for later (if `wait` is true
* and the lock cannot be taken immediately).
*
* This function will only return a negative error code when failing to send a response. A failure
* to add a lock (-EAGAIN, -ENOMEM etc.) will be sent in the response instead.
*/
int posix_lock_set_from_ipc(const char* path, struct posix_lock* pl, bool wait, IDTYPE vmid,
unsigned long seq);

/*!
* \brief Check for conflicting locks on a file (IPC handler)
*
* \param path absolute path 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 a version of `posix_lock_get` called from an IPC callback. The caller is responsible to
* send the returned value and `out_pl` in an IPC response.
*/
int posix_lock_get_from_ipc(const char* path, struct posix_lock* pl, struct posix_lock* out_pl);

#endif /* SHIM_FS_LOCK_H_ */
40 changes: 40 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,41 @@ 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 {
/* see `struct posix_lock` in `shim_fs_lock.h` */
int type;
uint64_t start;
uint64_t end;
IDTYPE pid;

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

struct shim_ipc_posix_lock_resp {
int result;

/* see `struct posix_lock` in `shim_fs_lock.h` */
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, 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
3 changes: 3 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,9 @@ BEGIN_CP_FUNC(dentry) {
new_dent->data = NULL;
}

/* `fs_lock` is used only by process leader. */
new_dent->fs_lock = NULL;

DO_CP_IN_MEMBER(qstr, new_dent, name);

if (new_dent->mount)
Expand Down
Loading