From 2da6e0ce4a8551c9504ad9e6d26ea017398051da Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Tue, 27 Jul 2021 20:12:32 +0100
Subject: [PATCH] Add a function to classify strings to be loaded by libdl

Signed-off-by: Simon McVittie <smcv@collabora.com>
---
 steam-runtime-tools/libdl-internal.h |  20 ++++
 steam-runtime-tools/libdl.c          |  94 +++++++++++++++++++
 tests/libdl.c                        | 131 +++++++++++++++++++++++++++
 tests/meson.build                    |   1 +
 4 files changed, 246 insertions(+)
 create mode 100644 tests/libdl.c

diff --git a/steam-runtime-tools/libdl-internal.h b/steam-runtime-tools/libdl-internal.h
index dc3b70179..09799f90d 100644
--- a/steam-runtime-tools/libdl-internal.h
+++ b/steam-runtime-tools/libdl-internal.h
@@ -35,3 +35,23 @@ G_GNUC_INTERNAL gchar *_srt_libdl_detect_lib (gchar **envp,
                                               const char *helpers_path,
                                               const char *multiarch_tuple,
                                               GError **error);
+
+typedef enum
+{
+  SRT_LOADABLE_KIND_ERROR,
+  SRT_LOADABLE_KIND_BASENAME,
+  SRT_LOADABLE_KIND_PATH,
+} SrtLoadableKind;
+
+typedef enum
+{
+  SRT_LOADABLE_FLAGS_DYNAMIC_TOKENS = (1 << 0),
+  SRT_LOADABLE_FLAGS_ABI_DEPENDENT = (1 << 1),
+  SRT_LOADABLE_FLAGS_ORIGIN = (1 << 2),
+  SRT_LOADABLE_FLAGS_UNKNOWN_TOKENS = (1 << 3),
+  SRT_LOADABLE_FLAGS_NONE = 0
+} SrtLoadableFlags;
+
+G_GNUC_INTERNAL
+SrtLoadableKind _srt_loadable_classify (const char *loadable,
+                                        SrtLoadableFlags *flags_out);
diff --git a/steam-runtime-tools/libdl.c b/steam-runtime-tools/libdl.c
index 7223f4366..5debbef23 100644
--- a/steam-runtime-tools/libdl.c
+++ b/steam-runtime-tools/libdl.c
@@ -126,3 +126,97 @@ _srt_libdl_detect_lib (gchar **envp,
                                 "detect-lib",
                                 error);
 }
+
+SrtLoadableKind
+_srt_loadable_classify (const char *loadable,
+                        SrtLoadableFlags *flags_out)
+{
+  SrtLoadableFlags flags = SRT_LOADABLE_FLAGS_NONE;
+  SrtLoadableKind kind;
+  gsize i;
+
+  g_return_val_if_fail (loadable != NULL, SRT_LOADABLE_KIND_ERROR);
+
+  if (loadable[0] == '\0')
+    {
+      kind = SRT_LOADABLE_KIND_ERROR;
+      goto out;
+    }
+
+  if (strchr (loadable, '/') != NULL)
+    {
+      kind = SRT_LOADABLE_KIND_PATH;
+    }
+  else
+    {
+      /* Dynamic string tokens are not interpreted in a bare SONAME
+       * so we don't need to do that part. */
+      kind = SRT_LOADABLE_KIND_BASENAME;
+      goto out;
+    }
+
+  for (i = 0; loadable[i] != '\0'; i++)
+    {
+      if (loadable[i] == '$')
+        {
+          const char *token = loadable + i + 1;
+          gsize len;
+
+          flags |= SRT_LOADABLE_FLAGS_DYNAMIC_TOKENS;
+
+          if (token[0] == '{')
+            {
+              token++;
+              len = strcspn (token, "}");
+            }
+          else
+            {
+              len = 0;
+
+              while (g_ascii_isalnum (token[len]) || token[len] == '_')
+                len++;
+            }
+
+          switch (len)
+            {
+              case 3:
+                if (strncmp ("LIB", token, len) == 0)
+                  {
+                    flags |= SRT_LOADABLE_FLAGS_ABI_DEPENDENT;
+                    continue;
+                  }
+
+                break;
+
+              case 6:
+                if (strncmp ("ORIGIN", token, len) == 0)
+                  {
+                    flags |= SRT_LOADABLE_FLAGS_ORIGIN;
+                    continue;
+                  }
+
+                break;
+
+              case 8:
+                if (strncmp ("PLATFORM", token, len) == 0)
+                  {
+                    flags |= SRT_LOADABLE_FLAGS_ABI_DEPENDENT;
+                    continue;
+                  }
+
+                break;
+
+              default:
+                break;
+            }
+
+          flags |= SRT_LOADABLE_FLAGS_UNKNOWN_TOKENS;
+        }
+    }
+
+out:
+  if (flags_out != NULL)
+    *flags_out = flags;
+
+  return kind;
+}
diff --git a/tests/libdl.c b/tests/libdl.c
new file mode 100644
index 000000000..964d0c542
--- /dev/null
+++ b/tests/libdl.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright © 2019 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <steam-runtime-tools/steam-runtime-tools.h>
+
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "steam-runtime-tools/libdl-internal.h"
+#include "test-utils.h"
+
+typedef struct
+{
+  int unused;
+} Fixture;
+
+typedef struct
+{
+  int unused;
+} Config;
+
+static void
+setup (Fixture *f,
+       gconstpointer context)
+{
+  G_GNUC_UNUSED const Config *config = context;
+}
+
+static void
+teardown (Fixture *f,
+          gconstpointer context)
+{
+  G_GNUC_UNUSED const Config *config = context;
+}
+
+#define ERROR SRT_LOADABLE_KIND_ERROR
+#define PATH SRT_LOADABLE_KIND_PATH
+#define BASE SRT_LOADABLE_KIND_BASENAME
+#define DYN SRT_LOADABLE_FLAGS_DYNAMIC_TOKENS
+#define ABI SRT_LOADABLE_FLAGS_ABI_DEPENDENT
+#define ORIGIN SRT_LOADABLE_FLAGS_ORIGIN
+#define UNKNOWN SRT_LOADABLE_FLAGS_UNKNOWN_TOKENS
+#define NONE SRT_LOADABLE_FLAGS_NONE
+static const struct
+{
+  const char *loadable;
+  SrtLoadableKind kind;
+  SrtLoadableFlags flags;
+}
+libdl_classify_tests[] =
+{
+    { "", ERROR, NONE },
+    { "/usr/lib/libc.so.6", PATH, NONE },
+    { "/usr/$LIB/libMangoHud.so", PATH, DYN|ABI },
+    { "${LIB}/libfoo.so", PATH, DYN|ABI },
+    { "/usr/plat-$PLATFORM/libc.so.6", PATH, DYN|ABI },
+    { "${PLATFORM}/libc.so.6", PATH, DYN|ABI },
+    { "${ORIGIN}/../${LIB}/libc.so.6", PATH, DYN|ABI|ORIGIN },
+    { "/$ORIGIN/libfoo", PATH, DYN|ORIGIN },
+    { "$ORIGIN/$FUTURE/${PLATFORM}-libfoo.so", PATH, DYN|ORIGIN|ABI|UNKNOWN },
+    { "${FUTURE}/libfoo.so", PATH, DYN|UNKNOWN },
+    { "libc.so.6", BASE, NONE },
+    { "libMangoHud.so", BASE, NONE },
+    { "looks-like-${TOKENS}-interpreted-but-not-really", BASE, NONE },
+};
+#undef ERROR
+#undef PATH
+#undef BASE
+#undef DYN
+#undef ABI
+#undef ORIGIN
+#undef UNKNOWN
+#undef NONE
+
+static void
+test_libdl_classify (Fixture *f,
+                     gconstpointer context)
+{
+  gsize i;
+
+  for (i = 0; i < G_N_ELEMENTS (libdl_classify_tests); i++)
+    {
+      SrtLoadableFlags flags = -1;
+      SrtLoadableKind kind;
+
+      kind = _srt_loadable_classify (libdl_classify_tests[i].loadable, &flags);
+
+      g_assert_cmpuint (kind, ==, libdl_classify_tests[i].kind);
+      g_assert_cmphex (flags, ==, libdl_classify_tests[i].flags);
+
+      kind = _srt_loadable_classify (libdl_classify_tests[i].loadable, NULL);
+      g_assert_cmpuint (kind, ==, libdl_classify_tests[i].kind);
+    }
+}
+
+int
+main (int argc,
+      char **argv)
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add ("/libdl/classify", Fixture, NULL, setup,
+              test_libdl_classify, teardown);
+
+  return g_test_run ();
+}
diff --git a/tests/meson.build b/tests/meson.build
index 101e75e31..ff8b1be0d 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -42,6 +42,7 @@ tests = [
     ],
   },
   {'name': 'json-utils', 'static': true},
+  {'name': 'libdl', 'static': true},
   {'name': 'library'},
   {'name': 'locale'},
   {'name': 'system-info'},
-- 
GitLab