From fa50421c01aeae8dace236fe5e2d7b2322b7c89d Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Thu, 27 Jun 2019 15:25:20 +0100
Subject: [PATCH] Detect which architectures we can run by running a helper
 executable

Signed-off-by: Simon McVittie <smcv@collabora.com>
---
 debian/control                                |  15 ++
 .../libsteam-runtime-tools-0-helpers.install  |   1 +
 helpers/meson.build                           |  34 +++++
 helpers/true.c                                |  35 +++++
 meson.build                                   |  13 ++
 steam-runtime-tools/architecture.c            | 136 ++++++++++++++++--
 steam-runtime-tools/meson.build               |   8 +-
 tests/meson.build                             |   1 +
 8 files changed, 230 insertions(+), 13 deletions(-)
 create mode 100644 debian/libsteam-runtime-tools-0-helpers.install
 create mode 100644 helpers/meson.build
 create mode 100644 helpers/true.c

diff --git a/debian/control b/debian/control
index b9f81d65f..43b1bec67 100644
--- a/debian/control
+++ b/debian/control
@@ -57,6 +57,21 @@ Description: Steam Runtime utility library - shared library
  .
  This package contains the shared library.
 
+Package: libsteam-runtime-tools-0-helpers
+Architecture: amd64 i386
+Multi-Arch: same
+Section: misc
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends},
+Description:
+ The Steam Runtime is the library stack used to run the Steam client
+ on Linux. The Steam Runtime Tools utility library contains open-source
+ supporting code used by the Steam client to discover system information.
+ .
+ This package contains helper tools used to examine the library stack
+ available for each architecture.
+
 Package: libsteam-runtime-tools-0-tests
 Architecture: any
 Section: misc
diff --git a/debian/libsteam-runtime-tools-0-helpers.install b/debian/libsteam-runtime-tools-0-helpers.install
new file mode 100644
index 000000000..4d24bb6e6
--- /dev/null
+++ b/debian/libsteam-runtime-tools-0-helpers.install
@@ -0,0 +1 @@
+usr/libexec/steam-runtime-tools-0/*
diff --git a/helpers/meson.build b/helpers/meson.build
new file mode 100644
index 000000000..386c962b1
--- /dev/null
+++ b/helpers/meson.build
@@ -0,0 +1,34 @@
+# 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.
+
+executable(
+  multiarch + '-true',
+  'true.c',
+  install : true,
+  install_dir : join_paths(
+    get_option('libexecdir'),
+    'steam-runtime-tools-' + api_major,
+  )
+)
+
+# vim:set sw=2 sts=2 et:
diff --git a/helpers/true.c b/helpers/true.c
new file mode 100644
index 000000000..57c891fc9
--- /dev/null
+++ b/helpers/true.c
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+/*
+ * This is basically /bin/true. We have it so that we can compile it for
+ * multiple CPU architectures, and check which ones we can run.
+ */
+int
+main (int argc,
+      char **argv)
+{
+  return 0;
+}
diff --git a/meson.build b/meson.build
index 92901f6b5..0c9603a03 100644
--- a/meson.build
+++ b/meson.build
@@ -103,7 +103,20 @@ gobject = dependency(
 )
 project_include_dirs = include_directories('.')
 
+if host_machine.cpu_family() == 'x86_64'
+  multiarch = 'x86_64-linux-gnu'
+elif host_machine.cpu_family() == 'x86'
+  multiarch = 'i386-linux-gnu'
+else
+  multiarch = ''
+endif
+
 subdir('steam-runtime-tools')
+
+if multiarch != ''
+  subdir('helpers')
+endif
+
 subdir('docs')
 subdir('examples')
 subdir('tests')
diff --git a/steam-runtime-tools/architecture.c b/steam-runtime-tools/architecture.c
index 523b7203c..841c13af6 100644
--- a/steam-runtime-tools/architecture.c
+++ b/steam-runtime-tools/architecture.c
@@ -25,24 +25,136 @@
 
 #include "steam-runtime-tools/architecture.h"
 
+#include <dlfcn.h>
+#include <link.h>
+#include <string.h>
+
+static gchar *helpers_path = NULL;
+
+static const char *
+_srt_get_helpers_path (void)
+{
+  const char *path;
+  void *handle = NULL;
+  struct link_map *map = NULL;
+
+  path = helpers_path;
+
+  if (path != NULL)
+    goto out;
+
+  path = g_getenv ("SRT_HELPERS_PATH");
+
+  if (path != NULL)
+    goto out;
+
+  handle = dlopen (NULL, RTLD_LAZY|RTLD_GLOBAL);
+
+  if (handle == NULL)
+    {
+      g_warning ("Unable to dlopen self: %s", dlerror ());
+      goto out;
+    }
+
+  if (dlinfo (handle, RTLD_DI_LINKMAP, &map) != 0)
+    {
+      g_warning ("dlinfo RTLD_DI_LINKMAP: %s", dlerror ());
+      goto out;
+    }
+
+  while (map != NULL && map->l_prev != NULL)
+    map = map->l_prev;
+
+  for (; map != NULL; map = map->l_next)
+    {
+      g_debug ("%s", map->l_name);
+
+      if (g_str_has_suffix (map->l_name, "/" _SRT_SONAME))
+        {
+          gchar *dir = g_path_get_dirname (map->l_name);
+
+          if (g_str_has_suffix (dir, "/" _SRT_MULTIARCH))
+            dir[strlen (dir) - strlen ("/" _SRT_MULTIARCH)] = '\0';
+
+          if (g_str_has_suffix (dir, "/lib"))
+            dir[strlen (dir) - strlen ("/lib")] = '\0';
+
+          /* deliberate one-per-process leak */
+          helpers_path = g_build_filename (
+              dir, "libexec", "steam-runtime-tools-" _SRT_API_MAJOR,
+              NULL);
+          path = helpers_path;
+
+          g_free (dir);
+          break;
+        }
+    }
+
+out:
+  if (handle != NULL)
+    dlclose (handle);
+
+  /* We have to return *something* non-NULL */
+  if (path == NULL)
+    {
+      g_warning ("Unable to determine path to helpers");
+      path = "/";
+    }
+
+  return path;
+}
+
+static gboolean
+_srt_architecture_can_run (const char *multiarch)
+{
+  gchar *helper = NULL;
+  const gchar *argv[] = { "true", NULL };
+  int exit_status = -1;
+  GError *error = NULL;
+  gboolean ret = FALSE;
+
+  helper = g_strdup_printf ("%s/%s-true",
+                            _srt_get_helpers_path (), multiarch);
+  argv[0] = helper;
+  g_debug ("Testing architecture %s with %s", multiarch, helper);
+
+  if (!g_spawn_sync (NULL,    /* working directory */
+                     (gchar **) argv,
+                     NULL,    /* envp */
+                     0,       /* flags */
+                     NULL,    /* child setup */
+                     NULL,    /* user data */
+                     NULL,    /* stdout */
+                     NULL,    /* stderr */
+                     &exit_status,
+                     &error))
+    {
+      g_debug ("... %s", error->message);
+      goto out;
+    }
+
+  if (exit_status != 0)
+    {
+      g_debug ("... wait status %d", exit_status);
+      goto out;
+    }
+
+  g_debug ("... it works");
+  ret = TRUE;
+out:
+  g_free (helper);
+  g_clear_error (&error);
+  return ret;
+}
+
 gboolean
 srt_architecture_can_run_i386 (void)
 {
-  /* TODO */
-#if defined(__i386__)
-  return TRUE;
-#else
-  return FALSE;
-#endif
+  return _srt_architecture_can_run ("i386-linux-gnu");
 }
 
 gboolean
 srt_architecture_can_run_x86_64 (void)
 {
-  /* TODO */
-#if defined(__x86_64__) && defined(__LP64__)
-  return TRUE;
-#else
-  return FALSE;
-#endif
+  return _srt_architecture_can_run ("x86_64-linux-gnu");
 }
diff --git a/steam-runtime-tools/meson.build b/steam-runtime-tools/meson.build
index e278a3b76..3758fad9e 100644
--- a/steam-runtime-tools/meson.build
+++ b/steam-runtime-tools/meson.build
@@ -21,6 +21,8 @@
 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
+libdl = c_compiler.find_library('dl', required : false)
+
 libsteamrt_sources = [
     'architecture.c',
 ]
@@ -41,9 +43,13 @@ libsteamrt = library(
   c_args : [
     '-DG_LOG_DOMAIN="' + meson.project_name() + '"',
     '-D_SRT_COMPILATION',
+    '-D_GNU_SOURCE',
+    '-D_SRT_SONAME="libsteam-runtime-tools-' + api_major + '.so.' + abi_major + '"',
+    '-D_SRT_MULTIARCH="' + multiarch + '"',
+    '-D_SRT_API_MAJOR="' + api_major + '"',
   ],
   include_directories : project_include_dirs,
-  dependencies : [glib, gobject],
+  dependencies : [glib, gobject, libdl],
   soversion : abi_major,
   version : abi_major + '.' + abi_minor,
   install : true,
diff --git a/tests/meson.build b/tests/meson.build
index 2e3cce267..e60bc0139 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -24,6 +24,7 @@
 test_env = environment()
 test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
 test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
+test_env.set('SRT_HELPERS_PATH', join_paths(meson.current_build_dir(), '..', 'helpers'))
 
 tests = [
   'architecture',
-- 
GitLab