From cbd9e214270c5dcf7c8942173396f49335b5f041 Mon Sep 17 00:00:00 2001
From: Ludovico de Nittis <ludovico.denittis@collabora.com>
Date: Wed, 2 Sep 2020 15:03:18 +0200
Subject: [PATCH] utils: Add pv_file_test_in_sysroot

With this new function it will be possible to test the existence or
properties of a given filename under a specific sysroot.

Signed-off-by: Ludovico de Nittis <ludovico.denittis@collabora.com>
---
 src/utils.c   |  73 +++++++++++++++++++++++++++++++++++
 src/utils.h   |   4 ++
 tests/utils.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 180 insertions(+)

diff --git a/src/utils.c b/src/utils.c
index e646399bd..0ca0fa46d 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -40,6 +40,8 @@
 #include "flatpak-utils-base-private.h"
 #include "flatpak-utils-private.h"
 
+#include "resolve-in-sysroot.h"
+
 static void
 child_setup_cb (gpointer user_data)
 {
@@ -926,3 +928,74 @@ pv_terminate_all_child_processes (GTimeSpan wait_period,
 
   return TRUE;
 }
+
+/**
+ * pv_file_test_in_sysroot:
+ * @sysroot: (type filename): A path used as the root
+ * @filename: (type filename): A path below the root directory, either
+ *  absolute or relative (to the root)
+ * @test: The test to perform on the resolved file in @sysroot.
+ *  G_FILE_TEST_IS_SYMLINK is not a valid GFileTest value because the
+ *  path is resolved following symlinks too.
+ *
+ * Returns: %TRUE if the @filename resolved in @sysroot passes the @test.
+ */
+gboolean
+pv_file_test_in_sysroot (const char *sysroot,
+                         const char *filename,
+                         GFileTest test)
+{
+  glnx_autofd int file_fd = -1;
+  glnx_autofd int sysroot_fd = -1;
+  struct stat stat_buf;
+  g_autofree gchar *file_realpath_in_sysroot = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_return_val_if_fail (sysroot != NULL, FALSE);
+  g_return_val_if_fail (filename != NULL, FALSE);
+  /* We reject G_FILE_TEST_IS_SYMLINK because the provided filename is resolved
+   * in sysroot, following the eventual symlinks too. So it is not possible for
+   * the resolved filename to be a symlink */
+  g_return_val_if_fail ((test & (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_EXECUTABLE
+                                 | G_FILE_TEST_IS_REGULAR
+                                 | G_FILE_TEST_IS_DIR)) == test, FALSE);
+
+  if (!glnx_opendirat (-1, sysroot, FALSE, &sysroot_fd, &error))
+    {
+      g_debug ("An error occurred trying to open %s: %s", sysroot,
+               error->message);
+      return FALSE;
+    }
+
+  file_fd = pv_resolve_in_sysroot (sysroot_fd,
+                                   filename, PV_RESOLVE_FLAGS_NONE,
+                                   &file_realpath_in_sysroot, &error);
+
+  if (file_fd < 0)
+    {
+      g_debug ("An error occurred trying to resolve %s in sysroot: %s",
+               filename, error->message);
+      return FALSE;
+    }
+
+  if (fstat (file_fd, &stat_buf) != 0)
+    {
+      g_debug ("fstat %s/%s: %s",
+               sysroot, file_realpath_in_sysroot, g_strerror (errno));
+      return FALSE;
+    }
+
+  if (test & G_FILE_TEST_EXISTS)
+    return TRUE;
+
+  if ((test & G_FILE_TEST_IS_EXECUTABLE) && (stat_buf.st_mode & 0111))
+    return TRUE;
+
+  if ((test & G_FILE_TEST_IS_REGULAR) && S_ISREG (stat_buf.st_mode))
+    return TRUE;
+
+  if ((test & G_FILE_TEST_IS_DIR) && S_ISDIR (stat_buf.st_mode))
+    return TRUE;
+
+  return FALSE;
+}
diff --git a/src/utils.h b/src/utils.h
index 0966efedb..811c0cb39 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -73,3 +73,7 @@ gboolean pv_wait_for_child_processes (pid_t main_process,
 gboolean pv_terminate_all_child_processes (GTimeSpan wait_period,
                                            GTimeSpan grace_period,
                                            GError **error);
+
+gboolean pv_file_test_in_sysroot (const char *sysroot,
+                                  const char *filename,
+                                  GFileTest test);
diff --git a/tests/utils.c b/tests/utils.c
index fd5a2977f..57f944ba3 100644
--- a/tests/utils.c
+++ b/tests/utils.c
@@ -278,6 +278,107 @@ test_search_path_append (Fixture *f,
   g_assert_cmpstr (str->str, ==, "/bin:/usr/bin:/usr/bin");
 }
 
+typedef struct
+{
+  const char *name;
+  mode_t mode;
+} File;
+
+typedef struct
+{
+  const char *name;
+  const char *target;
+} Symlink;
+
+typedef struct
+{
+  const char *path;
+  GFileTest test;
+  gboolean expected_result;
+} InSysrootTest;
+
+static void
+test_file_in_sysroot (Fixture *f,
+                      gconstpointer context)
+{
+  static const char * const prepare_dirs[] =
+  {
+    "dir1/dir2/dir3",
+  };
+
+  static const File prepare_files[] =
+  {
+    { "dir1/file1", 0600 },
+    { "dir1/dir2/file2", 0600 },
+    { "dir1/exec1", 0700 },
+  };
+
+  static const Symlink prepare_symlinks[] =
+  {
+    { "dir1/dir2/symlink_to_dir3", "dir3" },
+    { "dir1/dir2/symlink_to_file2", "file2" },
+    { "dir1/dir2/sym_to_sym_to_file2", "symlink_to_file2" },
+    { "dir1/abs_symlink_to_run", "/run" },
+  };
+
+  static const InSysrootTest tests[] =
+  {
+    { "dir1", G_FILE_TEST_IS_DIR, TRUE },
+    { "dir1", G_FILE_TEST_EXISTS, TRUE },
+    { "/dir1", G_FILE_TEST_EXISTS, TRUE },
+    { "dir1/dir2", G_FILE_TEST_IS_DIR, TRUE },
+    /* These gets solved in sysroot, following symlinks too */
+    { "dir1/dir2/symlink_to_dir3", G_FILE_TEST_IS_DIR, TRUE },
+    { "dir1/dir2/sym_to_sym_to_file2", G_FILE_TEST_IS_REGULAR, TRUE },
+    { "dir1/abs_symlink_to_run", G_FILE_TEST_IS_DIR, FALSE },
+    { "dir1/missing", G_FILE_TEST_EXISTS, FALSE },
+    { "dir1/file1", G_FILE_TEST_IS_REGULAR, TRUE },
+    { "dir1/file1", (G_FILE_TEST_IS_DIR | G_FILE_TEST_IS_EXECUTABLE), FALSE },
+    { "dir1/exec1", G_FILE_TEST_IS_REGULAR, TRUE },
+    { "dir1/exec1", G_FILE_TEST_IS_EXECUTABLE, TRUE },
+  };
+
+  g_autoptr(GError) error = NULL;
+  g_auto(GLnxTmpDir) tmpdir = { FALSE };
+  gsize i;
+
+  glnx_mkdtemp ("test-XXXXXX", 0700, &tmpdir, &error);
+  g_assert_no_error (error);
+
+  for (i = 0; i < G_N_ELEMENTS (prepare_dirs); i++)
+    {
+      const char *it = prepare_dirs[i];
+
+      glnx_shutil_mkdir_p_at (tmpdir.fd, it, 0700, NULL, &error);
+      g_assert_no_error (error);
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (prepare_files); i++)
+    {
+      const File *it = &prepare_files[i];
+
+      glnx_autofd int fd = openat (tmpdir.fd, it->name, O_WRONLY|O_CREAT, it->mode);
+      if (fd == -1)
+        g_error ("openat %s: %s", it->name, g_strerror (errno));
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (prepare_symlinks); i++)
+    {
+      const Symlink *it = &prepare_symlinks[i];
+
+      if (symlinkat (it->target, tmpdir.fd, it->name) != 0)
+        g_error ("symlinkat %s: %s", it->name, g_strerror (errno));
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (tests); i++)
+    {
+      const InSysrootTest *it = &tests[i];
+
+      g_assert_cmpint (pv_file_test_in_sysroot (tmpdir.path, it->path, it->test),
+                       ==, it->expected_result);
+    }
+}
+
 int
 main (int argc,
       char **argv)
@@ -294,6 +395,8 @@ main (int argc,
   g_test_add ("/same-file", Fixture, NULL, setup, test_same_file, teardown);
   g_test_add ("/search-path-append", Fixture, NULL,
               setup, test_search_path_append, teardown);
+  g_test_add ("/test-file-in-sysroot", Fixture, NULL,
+              setup, test_file_in_sysroot, teardown);
 
   return g_test_run ();
 }
-- 
GitLab