-
Simon McVittie authored
This is preparation for combining pressure-vessel with steam-runtime-tools. Signed-off-by:
Simon McVittie <smcv@collabora.com>
Simon McVittie authoredThis is preparation for combining pressure-vessel with steam-runtime-tools. Signed-off-by:
Simon McVittie <smcv@collabora.com>
bwrap-lock.c 6.54 KiB
/*
* Copyright © 2019-2020 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 <fcntl.h>
#include <unistd.h>
#include "bwrap-lock.h"
/*
* These compatibility definitions let us use OFD locks in older glibc
* versions. This requires Linux kernel >= v3.15.
*/
#ifndef F_OFD_GETLK
#define F_OFD_GETLK 36
#endif
#ifndef F_OFD_SETLK
#define F_OFD_SETLK 37
#endif
#ifndef F_OFD_SETLKW
#define F_OFD_SETLKW 38
#endif
/**
* PvBwrapLock:
*
* A read/write lock compatible with the locks taken out by
* `bwrap --lock-file FILENAME` and Flatpak.
*/
struct _PvBwrapLock
{
int fd;
gboolean is_ofd;
};
/**
* pv_bwrap_lock_new:
* @at_fd: If not `AT_FDCWD` or -1, look up @path relative to this
* directory fd instead of the current working directory, as per `openat(2)`
* @path: File to lock
* @flags: Flags affecting how we lock the file
* @error: Used to raise an error on failure
*
* Take out a lock on a file.
*
* 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 (int at_fd,
const gchar *path,
PvBwrapLockFlags flags,
GError **error)
{
glnx_autofd int fd = -1;
int open_flags = O_CLOEXEC | O_NOCTTY;
int ofd;
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
g_return_val_if_fail ((flags & PV_BWRAP_LOCK_FLAGS_PROCESS_ORIENTED) == 0 ||
(flags & PV_BWRAP_LOCK_FLAGS_REQUIRE_OFD) == 0,
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;
at_fd = glnx_dirfd_canonicalize (at_fd);
fd = TEMP_FAILURE_RETRY (openat (at_fd, path, open_flags, 0644));
if (fd < 0)
{
glnx_throw_errno_prefix (error, "openat(%s)", path);
return NULL;
}
/* If PROCESS_ORIENTED, only do ofd == 0. If not, try 1 then 0. */
for (ofd = (flags & PV_BWRAP_LOCK_FLAGS_PROCESS_ORIENTED) ? 0 : 1;
ofd >= 0;
ofd--)
{
struct flock l = {
.l_type = F_RDLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0
};
const char *type_str = "reading";
int cmd;
int saved_errno;
/*
* We want OFD locks because:
*
* - ordinary process-associated F_SETLK fnctl(2) locks are unlocked on
* fork(), but bwrap forks before calling into user code, so by the
* time we run our child process, it will have lost the lock
* - flock(2) locks are orthogonal to fnctl(2) locks, so we can't take
* a lock that excludes the F_SETLK locks used by Flatpak/bwrap
*
* F_OFD_SETLK and F_SETLK are documented to conflict with each other,
* so for example by holding an OFD read-lock, we can prevent other
* processes from taking a process-associated write-lock, or vice versa.
*/
if (ofd)
{
if (flags & PV_BWRAP_LOCK_FLAGS_WAIT)
cmd = F_OFD_SETLKW;
else
cmd = F_OFD_SETLK;
}
else
{
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)
break;
saved_errno = errno;
/* If the kernel doesn't support OFD locks, fall back to
* process-oriented locks if allowed. */
if (saved_errno == EINVAL &&
ofd &&
(flags & PV_BWRAP_LOCK_FLAGS_REQUIRE_OFD) == 0)
continue;
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;
}
g_assert (ofd == 0 || ofd == 1);
return pv_bwrap_lock_new_take (glnx_steal_fd (&fd), ofd);
}
/**
* pv_bwrap_lock_new_take:
* @fd: A file descriptor, already locked
* @is_ofd: %TRUE if @fd is an open file descriptor lock
*
* 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,
gboolean is_ofd)
{
PvBwrapLock *self = NULL;
g_return_val_if_fail (fd >= 0, NULL);
g_return_val_if_fail (is_ofd == 0 || is_ofd == 1, NULL);
self = g_slice_new0 (PvBwrapLock);
self->fd = glnx_steal_fd (&fd);
self->is_ofd = is_ofd;
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);
}
gboolean
pv_bwrap_lock_is_ofd (PvBwrapLock *self)
{
g_return_val_if_fail (self != NULL, FALSE);
return self->is_ofd;
}