From 8b531350ff67a1a96d7a661d858ec330c948165a Mon Sep 17 00:00:00 2001 From: Simon McVittie <smcv@collabora.com> Date: Thu, 14 Nov 2019 14:27:38 +0000 Subject: [PATCH] wrap: Add API for a bubblewrap-compatible fcntl lock Signed-off-by: Simon McVittie <smcv@collabora.com> --- meson.build | 2 + src/bwrap-lock.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++ src/bwrap-lock.h | 57 +++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 src/bwrap-lock.c create mode 100644 src/bwrap-lock.h diff --git a/meson.build b/meson.build index ba209631e..021423178 100644 --- a/meson.build +++ b/meson.build @@ -169,6 +169,8 @@ executable( sources : [ 'src/bwrap.c', 'src/bwrap.h', + 'src/bwrap-lock.c', + 'src/bwrap-lock.h', 'src/glib-backports.c', 'src/glib-backports.h', 'src/flatpak-bwrap.c', diff --git a/src/bwrap-lock.c b/src/bwrap-lock.c new file mode 100644 index 000000000..bb82887c9 --- /dev/null +++ b/src/bwrap-lock.c @@ -0,0 +1,161 @@ +/* + * Copyright © 2019 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" +#include "subprojects/libglnx/config.h" + +#include <gio/gio.h> + +#include "bwrap-lock.h" + +/** + * PvBwrapLock: + * + * A read/write lock compatible with the locks taken out by + * `bwrap --lock-file FILENAME` and Flatpak. + */ +struct _PvBwrapLock +{ + int fd; +}; + +/** + * pv_bwrap_lock_new: + * @path: Runtime directory to lock; the actual lock file will be `$path/.ref` + * @flags: Flags affecting how we lock the directory + * @error: Used to raise an error on failure + * + * Take out a lock on a directory. + * + * If %PV_BWRAP_LOCK_FLAGS_WRITE is in @flags, the lock is a write-lock, + * which can be held by at most one process at a time. This is appropriate + * when about to modify or delete the runtime. Otherwise it is a read-lock, + * which excludes writers but does not exclude other readers. This is + * appropriate when running an app or game using the runtime. + * + * If %PV_BWRAP_LOCK_FLAGS_WAIT is not in @flags, raise %G_IO_ERROR_BUSY + * if the lock cannot be obtained immediately. + * + * Returns: (nullable): A lock (release and free with pv_bwrap_lock_free()) + * or %NULL. + */ +PvBwrapLock * +pv_bwrap_lock_new (const gchar *path, + PvBwrapLockFlags flags, + GError **error) +{ + glnx_autofd int fd = -1; + struct flock l = { + .l_type = F_RDLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0 + }; + const char *type_str = "reading"; + int open_flags = O_CLOEXEC | O_NOCTTY; + int cmd; + + g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (flags & PV_BWRAP_LOCK_FLAGS_CREATE) + open_flags |= O_RDWR | O_CREAT; + else if (flags & PV_BWRAP_LOCK_FLAGS_WRITE) + open_flags |= O_RDWR; + else + open_flags |= O_RDONLY; + + fd = TEMP_FAILURE_RETRY (openat (AT_FDCWD, path, open_flags, 0644)); + + if (fd < 0) + { + glnx_throw_errno_prefix (error, "openat(%s)", path); + return NULL; + } + + if (flags & PV_BWRAP_LOCK_FLAGS_WAIT) + cmd = F_SETLKW; + else + cmd = F_SETLK; + + if (flags & PV_BWRAP_LOCK_FLAGS_WRITE) + { + l.l_type = F_WRLCK; + type_str = "writing"; + } + + if (TEMP_FAILURE_RETRY (fcntl (fd, cmd, &l)) < 0) + { + int saved_errno = errno; + + if (saved_errno == EACCES || saved_errno == EAGAIN) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_BUSY, + "Unable to lock %s for %s: file is busy", + path, type_str); + return NULL; + } + + glnx_throw_errno_prefix (error, "Unable to lock %s for %s", + path, type_str); + return NULL; + } + + return pv_bwrap_lock_new_take (glnx_steal_fd (&fd)); +} + +/** + * pv_bwrap_lock_new_take: + * @fd: A file descriptor, already locked + * + * Convert a simple file descriptor into a #PvBwrapLock. + * + * Returns: (not nullable): A lock (release and free + * with pv_bwrap_lock_free()) + */ +PvBwrapLock * +pv_bwrap_lock_new_take (int fd) +{ + PvBwrapLock *self = NULL; + + g_return_val_if_fail (fd >= 0, NULL); + + self = g_slice_new0 (PvBwrapLock); + self->fd = glnx_steal_fd (&fd); + return self; +} + +void +pv_bwrap_lock_free (PvBwrapLock *self) +{ + glnx_autofd int fd = -1; + + g_return_if_fail (self != NULL); + + fd = glnx_steal_fd (&self->fd); + g_slice_free (PvBwrapLock, self); + /* fd is closed by glnx_autofd if necessary, and that releases the lock */ +} + +int +pv_bwrap_lock_steal_fd (PvBwrapLock *self) +{ + g_return_val_if_fail (self != NULL, -1); + return glnx_steal_fd (&self->fd); +} diff --git a/src/bwrap-lock.h b/src/bwrap-lock.h new file mode 100644 index 000000000..3c85bf78f --- /dev/null +++ b/src/bwrap-lock.h @@ -0,0 +1,57 @@ +/* + * Copyright © 2019 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <glib.h> +#include <glib-object.h> + +#include "glib-backports.h" +#include "libglnx.h" + +/** + * PvBwrapLockFlags: + * @PV_BWRAP_LOCK_FLAGS_CREATE: If the lock file doesn't exist, create it + * @PV_BWRAP_LOCK_FLAGS_WAIT: If another process holds an incompatible lock, + * wait for it to be released; by default pv_bwrap_lock_new() + * raises %G_IO_ERROR_BUSY immediately + * @PV_BWRAP_LOCK_FLAGS_WRITE: Take a write-lock instead of a read-lock; + * by default pv_bwrap_lock_new() takes a read-lock + * @PV_BWRAP_LOCK_FLAGS_NONE: None of the above + * + * Flags affecting how we take a lock on a runtime directory. + */ +typedef enum +{ + PV_BWRAP_LOCK_FLAGS_CREATE = (1 << 0), + PV_BWRAP_LOCK_FLAGS_WAIT = (1 << 1), + PV_BWRAP_LOCK_FLAGS_WRITE = (1 << 2), + PV_BWRAP_LOCK_FLAGS_NONE = 0 +} PvBwrapLockFlags; + +typedef struct _PvBwrapLock PvBwrapLock; + +PvBwrapLock *pv_bwrap_lock_new (const gchar *path, + PvBwrapLockFlags flags, + GError **error); +PvBwrapLock *pv_bwrap_lock_new_take (int fd); +void pv_bwrap_lock_free (PvBwrapLock *self); +int pv_bwrap_lock_steal_fd (PvBwrapLock *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (PvBwrapLock, pv_bwrap_lock_free) -- GitLab