From 970fc860323334e1009d1479350b0ce646a3ef11 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Tue, 2 Jun 2020 12:59:33 +0100
Subject: [PATCH] bwrap-lock: Add at-fd-based path resolution

This lets us hold a fd to the parent directory open and resolve paths
relative to it (see openat(2)).

Signed-off-by: Simon McVittie <smcv@collabora.com>
---
 src/bwrap-lock.c   |  8 ++++++--
 src/bwrap-lock.h   |  3 ++-
 src/runtime.c      |  2 +-
 src/with-lock.c    |  2 +-
 tests/bwrap-lock.c | 34 ++++++++++++++++++++++------------
 5 files changed, 32 insertions(+), 17 deletions(-)

diff --git a/src/bwrap-lock.c b/src/bwrap-lock.c
index 991f1e73f..69c183bcc 100644
--- a/src/bwrap-lock.c
+++ b/src/bwrap-lock.c
@@ -55,6 +55,8 @@ struct _PvBwrapLock
 
 /**
  * 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
@@ -74,7 +76,8 @@ struct _PvBwrapLock
  *  or %NULL.
  */
 PvBwrapLock *
-pv_bwrap_lock_new (const gchar *path,
+pv_bwrap_lock_new (int at_fd,
+                   const gchar *path,
                    PvBwrapLockFlags flags,
                    GError **error)
 {
@@ -95,7 +98,8 @@ pv_bwrap_lock_new (const gchar *path,
   else
     open_flags |= O_RDONLY;
 
-  fd = TEMP_FAILURE_RETRY (openat (AT_FDCWD, path, open_flags, 0644));
+  at_fd = glnx_dirfd_canonicalize (at_fd);
+  fd = TEMP_FAILURE_RETRY (openat (at_fd, path, open_flags, 0644));
 
   if (fd < 0)
     {
diff --git a/src/bwrap-lock.h b/src/bwrap-lock.h
index 562d28e5e..41c5aba6e 100644
--- a/src/bwrap-lock.h
+++ b/src/bwrap-lock.h
@@ -56,7 +56,8 @@ typedef enum
 
 typedef struct _PvBwrapLock PvBwrapLock;
 
-PvBwrapLock *pv_bwrap_lock_new (const gchar *path,
+PvBwrapLock *pv_bwrap_lock_new (int at_fd,
+                                const gchar *path,
                                 PvBwrapLockFlags flags,
                                 GError **error);
 PvBwrapLock *pv_bwrap_lock_new_take (int fd,
diff --git a/src/runtime.c b/src/runtime.c
index 89ef744bb..b4def4e1d 100644
--- a/src/runtime.c
+++ b/src/runtime.c
@@ -314,7 +314,7 @@ pv_runtime_initable_init (GInitable *initable,
   /* Take a lock on the runtime until we're finished with setup,
    * to make sure it doesn't get deleted. */
   files_ref = g_build_filename (self->source_files, ".ref", NULL);
-  self->runtime_lock = pv_bwrap_lock_new (files_ref,
+  self->runtime_lock = pv_bwrap_lock_new (AT_FDCWD, files_ref,
                                           PV_BWRAP_LOCK_FLAGS_CREATE,
                                           error);
 
diff --git a/src/with-lock.c b/src/with-lock.c
index cf185dc7d..2ac1b4331 100644
--- a/src/with-lock.c
+++ b/src/with-lock.c
@@ -117,7 +117,7 @@ opt_lock_file_cb (const char *name,
   if (opt_wait)
     flags |= PV_BWRAP_LOCK_FLAGS_WAIT;
 
-  lock = pv_bwrap_lock_new (value, flags, error);
+  lock = pv_bwrap_lock_new (AT_FDCWD, value, flags, error);
 
   if (lock == NULL)
     return FALSE;
diff --git a/tests/bwrap-lock.c b/tests/bwrap-lock.c
index 7e3fa0d62..6fe95c39f 100644
--- a/tests/bwrap-lock.c
+++ b/tests/bwrap-lock.c
@@ -78,12 +78,14 @@ test_locks (Fixture *f,
   lock = g_build_filename (tmpdir.path, "lockfile", NULL);
 
   /* Take a shared (read) lock */
-  read_lock1 = pv_bwrap_lock_new (lock, PV_BWRAP_LOCK_FLAGS_CREATE, &error);
+  read_lock1 = pv_bwrap_lock_new (AT_FDCWD, lock, PV_BWRAP_LOCK_FLAGS_CREATE,
+                                  &error);
   g_assert_no_error (error);
   g_assert_nonnull (read_lock1);
 
   /* We cannot take an exclusive (write) lock at the same time */
-  write_lock1 = pv_bwrap_lock_new (lock, PV_BWRAP_LOCK_FLAGS_WRITE, &error);
+  write_lock1 = pv_bwrap_lock_new (AT_FDCWD, lock, PV_BWRAP_LOCK_FLAGS_WRITE,
+                                   &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_BUSY);
   g_assert_null (write_lock1);
   g_clear_error (&error);
@@ -92,7 +94,8 @@ test_locks (Fixture *f,
   is_ofd = pv_bwrap_lock_is_ofd (read_lock1);
   fd = pv_bwrap_lock_steal_fd (read_lock1);
   g_assert_cmpint (fd, >=, 0);
-  write_lock1 = pv_bwrap_lock_new (lock, PV_BWRAP_LOCK_FLAGS_WRITE, &error);
+  write_lock1 = pv_bwrap_lock_new (AT_FDCWD, lock, PV_BWRAP_LOCK_FLAGS_WRITE,
+                                   &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_BUSY);
   g_assert_null (write_lock1);
   g_clear_error (&error);
@@ -101,7 +104,8 @@ test_locks (Fixture *f,
 
   /* The lock is held even after we free the original lock abstraction */
   g_clear_pointer (&read_lock1, pv_bwrap_lock_free);
-  write_lock1 = pv_bwrap_lock_new (lock, PV_BWRAP_LOCK_FLAGS_WRITE, &error);
+  write_lock1 = pv_bwrap_lock_new (AT_FDCWD, lock, PV_BWRAP_LOCK_FLAGS_WRITE,
+                                   &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_BUSY);
   g_assert_null (write_lock1);
   g_clear_error (&error);
@@ -109,35 +113,41 @@ test_locks (Fixture *f,
   /* We can make a new lock from an existing one */
   read_lock1 = pv_bwrap_lock_new_take (glnx_steal_fd (&fd), is_ofd);
   g_assert_nonnull (read_lock1);
-  write_lock1 = pv_bwrap_lock_new (lock, PV_BWRAP_LOCK_FLAGS_WRITE, &error);
+  write_lock1 = pv_bwrap_lock_new (AT_FDCWD, lock, PV_BWRAP_LOCK_FLAGS_WRITE,
+                                   &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_BUSY);
   g_assert_null (write_lock1);
   g_clear_error (&error);
 
   /* We can take a second read lock at the same time */
-  read_lock2 = pv_bwrap_lock_new (lock, PV_BWRAP_LOCK_FLAGS_CREATE, &error);
+  read_lock2 = pv_bwrap_lock_new (AT_FDCWD, lock, PV_BWRAP_LOCK_FLAGS_CREATE,
+                                  &error);
   g_assert_no_error (error);
   g_assert_nonnull (read_lock2);
 
   /* Releasing one read lock is not enough */
   g_clear_pointer (&read_lock1, pv_bwrap_lock_free);
-  write_lock1 = pv_bwrap_lock_new (lock, PV_BWRAP_LOCK_FLAGS_WRITE, &error);
+  write_lock1 = pv_bwrap_lock_new (AT_FDCWD, lock, PV_BWRAP_LOCK_FLAGS_WRITE,
+                                   &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_BUSY);
   g_assert_null (write_lock1);
   g_clear_error (&error);
 
-  /* Releasing both read locks is enough to allow a write lock */
+  /* Releasing both read locks is enough to allow a write lock. This
+   * incidentally also tests the normalization of -1 to AT_FDCWD. */
   g_clear_pointer (&read_lock2, pv_bwrap_lock_free);
-  write_lock1 = pv_bwrap_lock_new (lock, PV_BWRAP_LOCK_FLAGS_WRITE, &error);
+  write_lock1 = pv_bwrap_lock_new (-1, lock, PV_BWRAP_LOCK_FLAGS_WRITE, &error);
   g_assert_no_error (error);
   g_assert_nonnull (write_lock1);
 
-  /* We cannot take read or write locks while this lock is held */
-  write_lock2 = pv_bwrap_lock_new (lock, PV_BWRAP_LOCK_FLAGS_WRITE, &error);
+  /* We cannot take read or write locks while this lock is held.
+   * The second part here also exercises a non-trivial at_fd. */
+  write_lock2 = pv_bwrap_lock_new (-1, lock, PV_BWRAP_LOCK_FLAGS_WRITE, &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_BUSY);
   g_assert_null (write_lock2);
   g_clear_error (&error);
-  read_lock1 = pv_bwrap_lock_new (lock, PV_BWRAP_LOCK_FLAGS_NONE, &error);
+  read_lock1 = pv_bwrap_lock_new (tmpdir.fd, "lockfile",
+                                  PV_BWRAP_LOCK_FLAGS_NONE, &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_BUSY);
   g_assert_null (read_lock1);
   g_clear_error (&error);
-- 
GitLab