From 105b0d41c49b7ffdfcda363a9c26d92be4be5eb6 Mon Sep 17 00:00:00 2001
From: Ludovico de Nittis <ludovico.denittis@collabora.com>
Date: Wed, 21 Apr 2021 13:57:37 +0200
Subject: [PATCH] Add support to detect the expansion of $LIB and $PLATFORM

When loading VDPAU modules we rely on $PLATFORM, trying to support all
its known possible expansions.

However there is always the possibility that we are currently missing
some of them or that new ones will be added in the future.

For this reason we try to detect the expansion of $PLATFORM, and $LIB,
and print its value in the report. In this way it will be easier to
notice unusual and/or new expansions.

Signed-off-by: Ludovico de Nittis <ludovico.denittis@collabora.com>
---
 .gitignore                              |   1 +
 bin/system-info.c                       |  33 ++++
 bin/system-info.md                      |  21 +++
 helpers/create-meson-in-subdir.py       |  35 ++++
 helpers/detect.c                        |  64 +++++++
 helpers/identify-lib.c                  |  37 ++++
 helpers/identify-lib.h                  |  27 +++
 helpers/identify-platform.c             |  37 ++++
 helpers/identify-platform.h             |  27 +++
 helpers/meson.build                     | 110 +++++++++++-
 steam-runtime-tools/libdl-internal.h    |  37 ++++
 steam-runtime-tools/libdl.c             | 128 ++++++++++++++
 steam-runtime-tools/meson.build         |   2 +
 steam-runtime-tools/system-info.c       | 222 ++++++++++++++++++++++++
 steam-runtime-tools/system-info.h       |   9 +
 tests/json-report/full-good-report.json |   8 +
 16 files changed, 797 insertions(+), 1 deletion(-)
 create mode 100644 helpers/create-meson-in-subdir.py
 create mode 100644 helpers/detect.c
 create mode 100644 helpers/identify-lib.c
 create mode 100644 helpers/identify-lib.h
 create mode 100644 helpers/identify-platform.c
 create mode 100644 helpers/identify-platform.h
 create mode 100644 steam-runtime-tools/libdl-internal.h
 create mode 100644 steam-runtime-tools/libdl.c

diff --git a/.gitignore b/.gitignore
index f177cb4b9..5a9de428f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
 /_build/
 /build/
 /builddir/
+/helpers/_generated_*/
 /libcapsule/
 /libcapsule_*.dsc
 /libcapsule_*.tar.[gx]z
diff --git a/bin/system-info.c b/bin/system-info.c
index 88e769928..c9403bd63 100644
--- a/bin/system-info.c
+++ b/bin/system-info.c
@@ -1046,6 +1046,10 @@ main (int argc,
       GList *va_api_list = NULL;
       GList *vdpau_list = NULL;
       GList *glx_list = NULL;
+      g_autofree gchar *libdl_lib = NULL;
+      g_autofree gchar *libdl_platform = NULL;
+      g_autoptr(GError) libdl_lib_error = NULL;
+      g_autoptr(GError) libdl_platform_error = NULL;
       const char *ld_so;
 
       json_builder_set_member_name (builder, multiarch_tuples[i]);
@@ -1054,6 +1058,35 @@ main (int argc,
       can_run = srt_system_info_can_run (info, multiarch_tuples[i]);
       json_builder_add_boolean_value (builder, can_run);
 
+      json_builder_set_member_name (builder, "libdl-LIB");
+      libdl_lib = srt_system_info_dup_libdl_lib (info, multiarch_tuples[i],
+                                                 &libdl_lib_error);
+      if (libdl_lib != NULL)
+        {
+          json_builder_add_string_value (builder, libdl_lib);
+        }
+      else
+        {
+          json_builder_begin_object (builder);
+          _srt_json_builder_add_error_members (builder, libdl_lib_error);
+          json_builder_end_object (builder);
+        }
+
+      json_builder_set_member_name (builder, "libdl-PLATFORM");
+      libdl_platform = srt_system_info_dup_libdl_platform (info,
+                                                           multiarch_tuples[i],
+                                                           &libdl_platform_error);
+      if (libdl_platform != NULL)
+        {
+          json_builder_add_string_value (builder, libdl_platform);
+        }
+      else
+        {
+          json_builder_begin_object (builder);
+          _srt_json_builder_add_error_members (builder, libdl_platform_error);
+          json_builder_end_object (builder);
+        }
+
       ld_so = srt_architecture_get_expected_runtime_linker (multiarch_tuples[i]);
 
       if (ld_so != NULL)
diff --git a/bin/system-info.md b/bin/system-info.md
index 9b9e70517..233885754 100644
--- a/bin/system-info.md
+++ b/bin/system-info.md
@@ -354,6 +354,27 @@ keys:
     :   A boolean value indicating whether a simple executable for this
         architecture was run successfully.
 
+    **libdl-LIB**
+    :   A string containing the dynamic linker expansion of the literal token
+        `$LIB`/`${LIB}`. See `ld.so(8)` section "Dynamic string tokens" for
+        more details. However if an error occurred while trying to determine
+        the expansion value, **libdl-LIB** will instead contain an object with
+        the following details of the error:
+
+        **error-domain**
+        :   A machine-readable string indicating a category of errors.
+
+        **error-code**
+        :   A small integer indicating a specific error. Its meaning depends
+            on the **error-domain**.
+
+        **error**
+        :   A human-readable error message.
+
+    **libdl-PLATFORM**
+    :   The same as the **libdl-LIB** described above, but describing the
+        literal token `$PLATFORM`/`${PLATFORM}`.
+
     **runtime-linker**
     :   If the multiarch tuple is a known one, an object with the
         following keys and values describing the runtime linker **ld.so**(8):
diff --git a/helpers/create-meson-in-subdir.py b/helpers/create-meson-in-subdir.py
new file mode 100644
index 000000000..2e029e4fa
--- /dev/null
+++ b/helpers/create-meson-in-subdir.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+#
+# Copyright © 2021 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.
+
+import sys
+from pathlib import Path
+
+dir_path = Path(sys.argv[1])
+dir_path.mkdir(exist_ok=True)
+
+meson_path = dir_path / "meson.build"
+
+with meson_path.open('w') as text:
+    text.write(sys.argv[2])
diff --git a/helpers/detect.c b/helpers/detect.c
new file mode 100644
index 000000000..1734537ff
--- /dev/null
+++ b/helpers/detect.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2021 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 <dlfcn.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int
+main (int argc,
+      char **argv)
+{
+  void *handle = NULL;
+  const char *err;
+
+#if defined(_SRT_LIB_PATH) && defined(_SRT_FUNCTION)
+  handle = dlopen (_SRT_LIB_PATH, RTLD_NOW);
+  if (handle == NULL)
+    {
+      fprintf (stderr, "Unable to find the library: %s\n", dlerror ());
+      return 1;
+    }
+
+  char *(*fn)(void) = dlsym (handle, _SRT_FUNCTION);
+  err = dlerror ();
+  if (err != NULL)
+    {
+      fprintf (stderr, "Unable to load the library function: %s\n",
+                       dlerror ());
+      dlclose (handle);
+      return 1;
+    }
+
+  printf ("%s\n", fn());
+
+  dlclose (handle);
+  return 0;
+#else
+  return 1;
+#endif
+}
diff --git a/helpers/identify-lib.c b/helpers/identify-lib.c
new file mode 100644
index 000000000..80cf3eefc
--- /dev/null
+++ b/helpers/identify-lib.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2021 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 "identify-lib.h"
+
+
+const
+char *_srt_identify_lib (void)
+{
+  #if defined(_SRT_LIB_VALUE)
+    return _SRT_LIB_VALUE;
+  #else
+    return NULL;
+  #endif
+}
diff --git a/helpers/identify-lib.h b/helpers/identify-lib.h
new file mode 100644
index 000000000..66a10a71f
--- /dev/null
+++ b/helpers/identify-lib.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2021 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.
+ */
+
+__attribute__((visibility("default")))
+const char *_srt_identify_lib (void);
diff --git a/helpers/identify-platform.c b/helpers/identify-platform.c
new file mode 100644
index 000000000..3364c59b8
--- /dev/null
+++ b/helpers/identify-platform.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2021 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 "identify-platform.h"
+
+
+const
+char *_srt_identify_platform (void)
+{
+  #if defined(_SRT_PLATFORM_VALUE)
+    return _SRT_PLATFORM_VALUE;
+  #else
+    return NULL;
+  #endif
+}
diff --git a/helpers/identify-platform.h b/helpers/identify-platform.h
new file mode 100644
index 000000000..6d78777bd
--- /dev/null
+++ b/helpers/identify-platform.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2021 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.
+ */
+
+__attribute__((visibility("default")))
+const char *_srt_identify_platform (void);
diff --git a/helpers/meson.build b/helpers/meson.build
index 0034412f4..7da618da4 100644
--- a/helpers/meson.build
+++ b/helpers/meson.build
@@ -1,4 +1,4 @@
-# Copyright © 2019 Collabora Ltd.
+# Copyright © 2019-2021 Collabora Ltd.
 #
 # SPDX-License-Identifier: MIT
 #
@@ -136,4 +136,112 @@ executable(
   install_rpath : pkglibexec_rpath,
 )
 
+executable(
+  multiarch + '-detect-lib',
+  'detect.c',
+  c_args : [
+    '-D_SRT_LIB_PATH="${ORIGIN}/' + multiarch + '/${LIB}/libidentify-lib.so"',
+    '-D_SRT_FUNCTION="_srt_identify_lib"',
+  ],
+  dependencies : [libdl],
+  include_directories : project_include_dirs,
+  install : true,
+  install_dir : pkglibexecdir,
+)
+
+executable(
+  multiarch + '-detect-platform',
+  'detect.c',
+  c_args : [
+    '-D_SRT_LIB_PATH="${ORIGIN}/' + multiarch + '/${PLATFORM}/libidentify-platform.so"',
+    '-D_SRT_FUNCTION="_srt_identify_platform"',
+  ],
+  dependencies : [libdl],
+  include_directories : project_include_dirs,
+  install : true,
+  install_dir : pkglibexecdir,
+)
+
+# Due to some Meson's limitations it is not possible to build multiple times
+# the same library, with the same name, and place it in a different directory.
+# For this reason we generate the required meson.build files in subdirectories
+# to avoid conflicts.
+# https://github.com/mesonbuild/meson/issues/2320
+# https://github.com/mesonbuild/meson/issues/4019
+
+meson_library_template = '''
+shared_module(
+  'identify-lib',
+  '@0@',
+  c_args : ['-D_SRT_LIB_VALUE="@1@"',],
+  install : true,
+  install_dir : join_paths(
+    pkglibexecdir,
+    multiarch,
+    '@1@',
+  )
+)
+'''
+
+# This was supposed to be a dictionary but meson < 0.53.0 doesn't support
+# the usage of variables containing a string value as keys.
+components = [['lib', '../identify-lib.c']]
+
+if multiarch != ''
+  components += [
+    ['lib' / multiarch, '../../identify-lib.c'],
+    # This is to support Ubuntu 12.04 where $LIB wrongly expanded to just
+    # the multiarch
+    [multiarch, '../identify-lib.c'],
+  ]
+endif
+
+if multiarch == 'i386-linux-gnu'
+  components += [['lib32', '../identify-lib.c']]
+  platform_dirs = ['i386', 'i486', 'i586', 'i686']
+elif multiarch == 'x86_64-linux-gnu'
+  components += [['lib64', '../identify-lib.c']]
+  platform_dirs = ['x86_64', 'haswell', 'xeon_phi']
+endif
+
+foreach component : components
+  subdir = '_generated_' + component[0]
+  meson_library = meson_library_template.format(component[1], component[0])
+  run_command(
+    python,
+    'create-meson-in-subdir.py',
+    meson.current_source_dir() / subdir,
+    meson_library,
+    check : true,
+  )
+  subdir(subdir)
+endforeach
+
+meson_platform_template = '''
+shared_module(
+  'identify-platform',
+  '../identify-platform.c',
+  c_args : ['-D_SRT_PLATFORM_VALUE="@0@"',],
+  install : true,
+  install_dir : join_paths(
+    pkglibexecdir,
+    multiarch,
+    '@0@',
+  )
+)
+'''
+
+foreach platform_dir : platform_dirs
+  generated_dir = '_generated_' + platform_dir
+  meson_platform = meson_platform_template.format(platform_dir)
+  run_command(
+    python,
+    'create-meson-in-subdir.py',
+    meson.current_source_dir() / generated_dir,
+    meson_platform,
+    check : true,
+  )
+  subdir(generated_dir)
+endforeach
+
 # vim:set sw=2 sts=2 et:
diff --git a/steam-runtime-tools/libdl-internal.h b/steam-runtime-tools/libdl-internal.h
new file mode 100644
index 000000000..dc3b70179
--- /dev/null
+++ b/steam-runtime-tools/libdl-internal.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2021 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 <glib.h>
+#include <glib-object.h>
+
+G_GNUC_INTERNAL gchar *_srt_libdl_detect_platform (gchar **envp,
+                                                   const char *helpers_path,
+                                                   const char *multiarch_tuple,
+                                                   GError **error);
+
+G_GNUC_INTERNAL gchar *_srt_libdl_detect_lib (gchar **envp,
+                                              const char *helpers_path,
+                                              const char *multiarch_tuple,
+                                              GError **error);
diff --git a/steam-runtime-tools/libdl.c b/steam-runtime-tools/libdl.c
new file mode 100644
index 000000000..7223f4366
--- /dev/null
+++ b/steam-runtime-tools/libdl.c
@@ -0,0 +1,128 @@
+/*
+ * Copyright © 2021 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/libdl-internal.h"
+
+#include "steam-runtime-tools/glib-backports-internal.h"
+#include "steam-runtime-tools/utils.h"
+#include "steam-runtime-tools/utils-internal.h"
+
+static gchar *
+_srt_libdl_run_helper (gchar **envp,
+                       const char *helpers_path,
+                       const char *multiarch_tuple,
+                       const char *helper_name,
+                       GError **error)
+{
+  g_autoptr(GPtrArray) argv = NULL;
+  g_auto(GStrv) my_environ = NULL;
+  g_autofree gchar *child_stdout = NULL;
+  g_autofree gchar *child_stderr = NULL;
+  gint wait_status;
+  int exit_status;
+  gsize len;
+
+  g_return_val_if_fail (envp != NULL, NULL);
+  g_return_val_if_fail (helper_name != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+  g_return_val_if_fail (_srt_check_not_setuid (), NULL);
+
+  if (multiarch_tuple == NULL)
+    multiarch_tuple = _SRT_MULTIARCH;
+
+  argv = _srt_get_helper (helpers_path, multiarch_tuple, helper_name,
+                          SRT_HELPER_FLAGS_NONE, error);
+
+  if (argv == NULL)
+    return NULL;
+
+  my_environ = _srt_filter_gameoverlayrenderer_from_envp (envp);
+
+  g_ptr_array_add (argv, NULL);
+
+  g_debug ("Running %s", (const char *) g_ptr_array_index (argv, 0));
+
+  if (!g_spawn_sync (NULL,    /* working directory */
+                     (gchar **) argv->pdata,
+                     my_environ,
+                     G_SPAWN_DEFAULT,
+                     _srt_child_setup_unblock_signals,
+                     NULL,    /* user data */
+                     &child_stdout,
+                     &child_stderr,
+                     &wait_status,
+                     error))
+    return NULL;
+
+  if (!WIFEXITED (wait_status))
+    {
+      g_debug ("-> wait status: %d", wait_status);
+      return glnx_null_throw (error, "Unhandled wait status %d (killed by signal?)",
+                              wait_status);
+    }
+
+  exit_status = WEXITSTATUS (wait_status);
+  g_debug ("-> exit status: %d", exit_status);
+  g_debug ("-> stderr: %s", child_stderr);
+
+  if (exit_status != 0)
+    return glnx_null_throw (error, "%s", child_stderr);
+
+  len = strlen (child_stdout);
+
+  /* Emulate shell $() */
+  if (len > 0 && child_stdout[len - 1] == '\n')
+    child_stdout[len - 1] = '\0';
+
+  g_debug ("-> %s", child_stdout);
+
+  return g_steal_pointer(&child_stdout);
+}
+
+gchar *
+_srt_libdl_detect_platform (gchar **envp,
+                            const char *helpers_path,
+                            const char *multiarch_tuple,
+                            GError **error)
+{
+  return _srt_libdl_run_helper (envp,
+                                helpers_path,
+                                multiarch_tuple,
+                                "detect-platform",
+                                error);
+}
+
+gchar *
+_srt_libdl_detect_lib (gchar **envp,
+                       const char *helpers_path,
+                       const char *multiarch_tuple,
+                       GError **error)
+{
+  return _srt_libdl_run_helper (envp,
+                                helpers_path,
+                                multiarch_tuple,
+                                "detect-lib",
+                                error);
+}
diff --git a/steam-runtime-tools/meson.build b/steam-runtime-tools/meson.build
index 7f345e291..1f5ea949b 100644
--- a/steam-runtime-tools/meson.build
+++ b/steam-runtime-tools/meson.build
@@ -40,6 +40,8 @@ libsteamrt_sources = [
     'json-glib-backports-internal.h',
     'json-utils.c',
     'json-utils-internal.h',
+    'libdl-internal.h',
+    'libdl.c',
     'library-internal.h',
     'library.c',
     'locale-internal.h',
diff --git a/steam-runtime-tools/system-info.c b/steam-runtime-tools/system-info.c
index 481941237..4c38f4028 100644
--- a/steam-runtime-tools/system-info.c
+++ b/steam-runtime-tools/system-info.c
@@ -37,6 +37,7 @@
 #include "steam-runtime-tools/graphics.h"
 #include "steam-runtime-tools/graphics-internal.h"
 #include "steam-runtime-tools/json-utils-internal.h"
+#include "steam-runtime-tools/libdl-internal.h"
 #include "steam-runtime-tools/library-internal.h"
 #include "steam-runtime-tools/locale-internal.h"
 #include "steam-runtime-tools/os-internal.h"
@@ -261,6 +262,11 @@ typedef struct
   SrtLibraryIssues cached_combined_issues;
   gboolean libraries_cache_available;
 
+  gchar *libdl_lib;
+  GError *libdl_lib_error;
+  gchar *libdl_platform;
+  GError *libdl_platform_error;
+
   GHashTable *cached_graphics_results;
   SrtGraphicsIssues cached_combined_graphics_issues;
   gboolean graphics_cache_available;
@@ -339,6 +345,10 @@ abi_free (gpointer self)
 
   g_free (abi->runtime_linker_resolved);
   g_clear_error (&abi->runtime_linker_error);
+  g_free (abi->libdl_lib);
+  g_clear_error (&abi->libdl_lib_error);
+  g_free (abi->libdl_platform);
+  g_clear_error (&abi->libdl_platform_error);
   g_slice_free (Abi, self);
 }
 
@@ -698,6 +708,72 @@ get_runtime_linker_from_report (SrtSystemInfo *self,
     }
 }
 
+static void
+get_libdl_from_report (SrtSystemInfo *self,
+                       Abi *abi,
+                       JsonObject *json_arch_obj)
+{
+  gsize i;
+
+  /* Struct to avoid code duplication between all the similar libdl entries */
+  typedef struct
+  {
+    const gchar *member_name;
+    gchar **abi_member;
+    GError **abi_member_error;
+  } LibdlStruct;
+
+  LibdlStruct libdl_elements[] =
+  {
+    { "libdl-LIB", &abi->libdl_lib, &abi->libdl_lib_error },
+    { "libdl-PLATFORM", &abi->libdl_platform, &abi->libdl_platform_error },
+  };
+
+  g_return_if_fail (abi->libdl_lib == NULL);
+  g_return_if_fail (abi->libdl_lib_error == NULL);
+  g_return_if_fail (abi->libdl_platform == NULL);
+  g_return_if_fail (abi->libdl_platform_error == NULL);
+
+  for (i = 0; i < G_N_ELEMENTS (libdl_elements); i++)
+    {
+      JsonNode *subnode;
+      const LibdlStruct *libdl_element = &libdl_elements[i];
+
+      if (!json_object_has_member (json_arch_obj, libdl_element->member_name))
+        continue;
+
+      subnode = json_object_get_member (json_arch_obj, libdl_element->member_name);
+
+      if (JSON_NODE_HOLDS_VALUE (subnode))
+        {
+          *libdl_element->abi_member = g_strdup (json_node_get_string (subnode));
+        }
+      else
+        {
+          JsonObject *json_element_obj;
+          GQuark error_domain;
+          int error_code;
+          const char *error_message;
+
+          json_element_obj = json_object_get_object_member (json_arch_obj,
+                                                            libdl_element->member_name);
+
+          error_domain = g_quark_from_string (json_object_get_string_member_with_default (json_element_obj,
+                                                                                          "error-domain",
+                                                                                          NULL));
+          error_code = json_object_get_int_member_with_default (json_element_obj,
+                                                                "error-code",
+                                                                -1);
+          error_message = json_object_get_string_member_with_default (json_element_obj,
+                                                                      "error",
+                                                                      "Unknown error");
+
+          g_set_error_literal (libdl_element->abi_member_error, error_domain, error_code,
+                               error_message);
+        }
+    }
+}
+
 /**
  * srt_system_info_new_from_json:
  * @path: (not nullable) (type filename): Path to a JSON report
@@ -806,6 +882,8 @@ srt_system_info_new_from_json (const char *path,
 
           abi->can_run = _srt_architecture_can_run_from_report (json_arch_obj);
 
+          get_libdl_from_report (info, abi, json_arch_obj);
+
           abi->libraries_cache_available = TRUE;
           abi->cached_combined_issues = _srt_library_get_issues_from_report (json_arch_obj);
 
@@ -4411,3 +4489,147 @@ srt_system_info_check_runtime_linker (SrtSystemInfo *self,
       g_return_val_if_reached (FALSE);
     }
 }
+
+/**
+ * srt_system_info_dup_libdl_lib:
+ * @self: The #SrtSystemInfo object
+ * @multiarch_tuple: A multiarch tuple defining an ABI, as printed
+ *  by `gcc -print-multiarch` in the Steam Runtime
+ * @error: Used to raise an error on failure.
+ *
+ * Return the expansion of the dynamic linker string token `$LIB`,
+ * if possible.
+ *
+ * If the library path to be loaded with `dlopen()` or similar contains
+ * the literal tokens `$LIB` or `${LIB}`, the dynamic linker will replace
+ * them with the library directory returned by this function. See `ld.so(8)`
+ * section "Dynamic string tokens" for more details.
+ *
+ * Because there is currently no glibc API to determine how this token would
+ * be expanded, only a finite number of known values can currently be detected.
+ * If called on a platform where `$LIB` has a different expansion, this
+ * function will return %NULL.
+ *
+ * %NULL is also returned if an error occurs while attempting to determine the
+ * expansion of this token.
+ *
+ * Typical values look like `lib`, `lib32`, `lib64`, `lib/x86-64-linux-gnu`
+ * or `lib/i386-linux-gnu`.
+ *
+ * Returns: (transfer full) (type filename) (nullable): A filename, or %NULL.
+ */
+gchar *
+srt_system_info_dup_libdl_lib (SrtSystemInfo *self,
+                               const char *multiarch_tuple,
+                               GError **error)
+{
+  Abi *abi = NULL;
+
+  g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), NULL);
+  g_return_val_if_fail (multiarch_tuple != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  abi = ensure_abi_unless_immutable (self, multiarch_tuple);
+
+  if (abi == NULL)
+    return glnx_null_throw (error, "ABI \"%s\" not included in report",
+                            multiarch_tuple);
+
+  /* If we cached already the result, we return it */
+  if (abi->libdl_lib != NULL)
+    {
+      return g_strdup (abi->libdl_lib);
+    }
+  else if (abi->libdl_lib_error != NULL)
+    {
+      if (error != NULL)
+        *error = g_error_copy (abi->libdl_lib_error);
+      return NULL;
+    }
+
+  if (self->immutable_values)
+    return glnx_null_throw (error, "libdl LIB for ABI \"%s\" not included in report",
+                            multiarch_tuple);
+
+  abi->libdl_lib = _srt_libdl_detect_lib (self->env,
+                                          self->helpers_path,
+                                          multiarch_tuple,
+                                          &abi->libdl_lib_error);
+
+  if (abi->libdl_lib_error != NULL && error != NULL)
+    *error = g_error_copy (abi->libdl_lib_error);
+
+  return g_strdup (abi->libdl_lib);
+}
+
+/**
+ * srt_system_info_dup_libdl_platform:
+ * @self: The #SrtSystemInfo object
+ * @multiarch_tuple: A multiarch tuple defining an ABI, as printed
+ *  by `gcc -print-multiarch` in the Steam Runtime
+ * @error: Used to raise an error on failure.
+ *
+ * Return the expansion of the dynamic linker string token `$PLATFORM`,
+ * if possible.
+ *
+ * If the library path to be loaded with `dlopen()` or similar contains
+ * the literal tokens `$PLATFORM` or `${PLATFORM}`, the dynamic linker will
+ * replace them with the library directory returned by this function. See
+ * `ld.so(8)` section "Dynamic string tokens" for more details.
+ *
+ * Because there is currently no glibc API to determine how this token would
+ * be expanded, only a finite number of known values can currently be detected.
+ * If called on a platform where `$PLATFORM` has a different expansion, this
+ * function will return %NULL.
+ *
+ * %NULL is also returned if an error occurs while attempting to determine the
+ * expansion of this token.
+ *
+ * Typical values look like `x86_64`, `haswell`, `xeon_phi`, `i386`, `i486`,
+ * `i586`, or `i686`.
+ *
+ * Returns: (transfer full) (type filename) (nullable): A filename, or %NULL.
+ */
+gchar *
+srt_system_info_dup_libdl_platform (SrtSystemInfo *self,
+                                    const char *multiarch_tuple,
+                                    GError **error)
+{
+  Abi *abi = NULL;
+
+  g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), NULL);
+  g_return_val_if_fail (multiarch_tuple != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  abi = ensure_abi_unless_immutable (self, multiarch_tuple);
+
+  if (abi == NULL)
+    return glnx_null_throw (error, "ABI \"%s\" not included in report",
+                            multiarch_tuple);
+
+  /* If we cached already the result, we return it */
+  if (abi->libdl_platform != NULL)
+    {
+      return g_strdup (abi->libdl_platform);
+    }
+  else if (abi->libdl_platform_error != NULL)
+    {
+      if (error != NULL)
+        *error = g_error_copy (abi->libdl_platform_error);
+      return NULL;
+    }
+
+  if (self->immutable_values)
+    return glnx_null_throw (error, "libdl PLATFORM for ABI \"%s\" not included in report",
+                            multiarch_tuple);
+
+  abi->libdl_platform = _srt_libdl_detect_platform (self->env,
+                                                    self->helpers_path,
+                                                    multiarch_tuple,
+                                                    &abi->libdl_platform_error);
+
+  if (abi->libdl_platform_error != NULL && error != NULL)
+    *error = g_error_copy (abi->libdl_platform_error);
+
+  return g_strdup (abi->libdl_platform);
+}
diff --git a/steam-runtime-tools/system-info.h b/steam-runtime-tools/system-info.h
index 387ed2081..5b3c91a13 100644
--- a/steam-runtime-tools/system-info.h
+++ b/steam-runtime-tools/system-info.h
@@ -288,6 +288,15 @@ gboolean srt_system_info_check_runtime_linker (SrtSystemInfo *self,
                                                gchar **real_path,
                                                GError **error);
 
+_SRT_PUBLIC
+gchar *srt_system_info_dup_libdl_lib (SrtSystemInfo *self,
+                                      const char *multiarch_tuple,
+                                      GError **error);
+_SRT_PUBLIC
+gchar *srt_system_info_dup_libdl_platform (SrtSystemInfo *self,
+                                           const char *multiarch_tuple,
+                                           GError **error);
+
 #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtSystemInfo, g_object_unref)
 #endif
diff --git a/tests/json-report/full-good-report.json b/tests/json-report/full-good-report.json
index d12799f1e..985a3eb93 100644
--- a/tests/json-report/full-good-report.json
+++ b/tests/json-report/full-good-report.json
@@ -53,6 +53,12 @@
   "architectures" : {
     "i386-linux-gnu" : {
       "can-run" : false,
+      "libdl-LIB" : "lib32",
+      "libdl-PLATFORM" : {
+        "error-domain" : "g-io-error-quark",
+        "error-code" : 1,
+        "error" : "Unable to find the library: ${ORIGIN}/i386-linux-gnu/${PLATFORM}/libidentify-platform.so: cannot open shared object file: No such file or directory"
+      },
       "runtime-linker": {
         "path" : "/lib/ld-linux.so.2",
         "error-domain" : "g-io-error-quark",
@@ -130,6 +136,8 @@
     },
     "x86_64-linux-gnu" : {
       "can-run" : true,
+      "libdl-LIB" : "lib",
+      "libdl-PLATFORM" : "x86_64",
       "runtime-linker": {
         "path" : "/lib64/ld-linux-x86-64.so.2",
         "resolved" : "/lib/x86_64-linux-gnu/ld-2.31.so"
-- 
GitLab