From 41ac7a82fb9d761b4792d53f82d3801625f5ced8 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Wed, 25 Sep 2019 17:10:17 +0100
Subject: [PATCH] Distinguish between the Steam installation (root) and data
 paths

Until now we've been conflating these two, but Timothee Besset recently
clarified their status for me.

~/.steam/steam is maintained by bin_steam.sh, aka /usr/bin/steam,
in which it is referred to as STEAMDATALINK. It is the data directory
containing user data (cloud-synced configuration and saves), the
download cache, and the default Steam Library directory.

~/.steam/root is maintained by steam.sh, in which it is referred to as
STEAMROOTLINK. It is the installation directory, containing Steam
executables and libraries.

As a result, they are normally the same, but can differ, in particular
in two situations:

* When testing a new Steam client build by running client/steam.sh,
  client/ gets used as the installation directory, in conjunction with
  the existing data directory pointed to by ~/.steam/steam. This avoids
  having to re-login or re-download your library of games for the new
  test installation.

* When Debian bug #916303 was present during the initial Steam
  installation, the Steam client was unpacked into ~/.steam, resulting
  in creation of ~/.steam/steam as a real directory that cannot be
  replaced with a symbolic link. We effectively ended up using
  ~/.steam as the installation path, but with ~/.steam/steam as the
  data path. (The Debian steam package has since been fixed, but we
  cannot easily disentangle existing installations.)

Signed-off-by: Simon McVittie <smcv@collabora.com>
---
 bin/system-info.c                    |  17 +++
 bin/system-info.md                   |  31 +++++-
 steam-runtime-tools/steam-internal.h |   3 +-
 steam-runtime-tools/steam.c          | 153 ++++++++++++++++++++-------
 steam-runtime-tools/steam.h          |  28 ++++-
 steam-runtime-tools/system-info.c    |  88 ++++++++++++---
 steam-runtime-tools/system-info.h    |   1 +
 tests/fake-home.c                    |  60 ++++++++---
 tests/fake-home.h                    |   5 +-
 tests/system-info-cli.c              |   4 +-
 tests/system-info.c                  |  93 +++++++++++-----
 11 files changed, 384 insertions(+), 99 deletions(-)

diff --git a/bin/system-info.c b/bin/system-info.c
index b426b2831..b63cda9ba 100644
--- a/bin/system-info.c
+++ b/bin/system-info.c
@@ -255,6 +255,18 @@ jsonify_steam_issues (JsonBuilder *builder,
 
   if ((issues & SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK) != 0)
     json_builder_add_string_value (builder, "dot-steam-steam-not-symlink");
+
+  if ((issues & SRT_STEAM_ISSUES_CANNOT_FIND_DATA) != 0)
+    json_builder_add_string_value (builder, "cannot-find-data");
+
+  if ((issues & SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY) != 0)
+    json_builder_add_string_value (builder, "dot-steam-steam-not-directory");
+
+  if ((issues & SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_SYMLINK) != 0)
+    json_builder_add_string_value (builder, "dot-steam-root-not-symlink");
+
+  if ((issues & SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_DIRECTORY) != 0)
+    json_builder_add_string_value (builder, "dot-steam-root-not-directory");
 }
 
 static void
@@ -449,6 +461,7 @@ main (int argc,
   gchar *json_output;
   gchar *version = NULL;
   gchar *inst_path = NULL;
+  gchar *data_path = NULL;
   gchar *rt_path = NULL;
   int opt;
   static const char * const multiarch_tuples[] = { SRT_ABI_I386, SRT_ABI_X86_64 };
@@ -503,6 +516,9 @@ main (int argc,
   json_builder_set_member_name (builder, "path");
   inst_path = srt_system_info_dup_steam_installation_path (info);
   json_builder_add_string_value (builder, inst_path);
+  json_builder_set_member_name (builder, "data_path");
+  data_path = srt_system_info_dup_steam_data_path (info);
+  json_builder_add_string_value (builder, data_path);
   json_builder_set_member_name (builder, "issues");
   json_builder_begin_array (builder);
   steam_issues = srt_system_info_get_steam_issues (info);
@@ -638,6 +654,7 @@ main (int argc,
   g_object_unref (builder);
   g_object_unref (info);
   g_free (rt_path);
+  g_free (data_path);
   g_free (inst_path);
   g_free (version);
 
diff --git a/bin/system-info.md b/bin/system-info.md
index 887c29a68..cf48f45a8 100644
--- a/bin/system-info.md
+++ b/bin/system-info.md
@@ -40,7 +40,16 @@ keys:
 :   An object describing the Steam installation. The keys are strings:
 
     **path**
-    :   A string: the absolute path to the Steam installation.
+    :   A string: the absolute path to the Steam installation, typically
+        containing **steam.sh** and **ubuntu12_32** among others.
+        This is usually the same as **data_path** but can differ under some
+        circumstances.
+
+    **data_path**
+    :   A string: the absolute path to the Steam data directory, typically
+        containing **appcache**, **steamapps** and **userdata** among others.
+        This is usually the same as **path** but can differ under some
+        circumstances.
 
     **issues**
     :   An array of strings representing problems with the Steam
@@ -52,9 +61,25 @@ keys:
         **cannot-find**
         :   The Steam installation could not be found.
 
+        **cannot-find-data**
+        :   The Steam data directory could not be found.
+
+        **dot-steam-root-not-directory**
+        :   **~/.steam/root** is not a (symbolic link to a) directory.
+            Steam will probably not work.
+
+        **dot-steam-root-not-symlink**
+        :   **~/.steam/root** is not a symbolic link to the installation
+            directory. Steam will probably not work.
+
+        **dot-steam-steam-not-directory**
+        :   **~/.steam/steam** is not a (symbolic link to a) directory.
+            Steam will probably not work.
+
         **dot-steam-steam-not-symlink**
-        :   **~/.steam/steam** is not a symbolic link, for example
-            due to [Debian bug #916303](https://bugs.debian.org/916303).
+        :   **~/.steam/steam** is not a symbolic link to the data directory,
+            for example due to
+            [Debian bug #916303](https://bugs.debian.org/916303).
 
 **runtime**
 :   An object describing the `LD_LIBRARY_PATH`-based Steam Runtime.
diff --git a/steam-runtime-tools/steam-internal.h b/steam-runtime-tools/steam-internal.h
index 73863cd7a..a0d07465d 100644
--- a/steam-runtime-tools/steam-internal.h
+++ b/steam-runtime-tools/steam-internal.h
@@ -33,5 +33,6 @@
 
 G_GNUC_INTERNAL
 SrtSteamIssues _srt_steam_check (const GStrv env,
-                                 gchar **path_out,
+                                 gchar **install_path_out,
+                                 gchar **data_path_out,
                                  gchar **bin32_out);
diff --git a/steam-runtime-tools/steam.c b/steam-runtime-tools/steam.c
index d14291f7b..5ed75376b 100644
--- a/steam-runtime-tools/steam.c
+++ b/steam-runtime-tools/steam.c
@@ -49,14 +49,19 @@
 /*
  * _srt_steam_check:
  * @environ: (nullable): The list of environment variables to use.
- * @path_out: (out) (optional) (nullable) (type filename):
+ * @install_path_out: (out) (optional) (nullable) (type filename):
  *  Used to return the absolute path to the Steam installation
+ * @data_path_out: (out) (optional) (nullable) (type filename):
+ *  Used to return the absolute path to the Steam data directory,
+ *  which is usually the same as @install_path_out, but may be different
+ *  while testing a new Steam release
  * @bin32_out: (out) (optional) (nullable) (type filename):
  *  Used to return the absolute path to `ubuntu12_32`
  */
 SrtSteamIssues
 _srt_steam_check (const GStrv environ,
-                  gchar **path_out,
+                  gchar **install_path_out,
+                  gchar **data_path_out,
                   gchar **bin32_out)
 {
   SrtSteamIssues issues = SRT_STEAM_ISSUES_NONE;
@@ -64,7 +69,8 @@ _srt_steam_check (const GStrv environ,
   gchar *dot_steam_steam = NULL;
   gchar *dot_steam_root = NULL;
   gchar *default_steam_path = NULL;
-  char *path = NULL;
+  char *install_path = NULL;
+  char *data_path = NULL;
   char *bin32 = NULL;
   const char *home = NULL;
   const char *user_data = NULL;
@@ -72,7 +78,9 @@ _srt_steam_check (const GStrv environ,
 
   g_return_val_if_fail (_srt_check_not_setuid (),
                         SRT_STEAM_ISSUES_INTERNAL_ERROR);
-  g_return_val_if_fail (path_out == NULL || *path_out == NULL,
+  g_return_val_if_fail (install_path_out == NULL || *install_path_out == NULL,
+                        SRT_STEAM_ISSUES_INTERNAL_ERROR);
+  g_return_val_if_fail (data_path_out == NULL || *data_path_out == NULL,
                         SRT_STEAM_ISSUES_INTERNAL_ERROR);
   g_return_val_if_fail (bin32_out == NULL || *bin32_out == NULL,
                         SRT_STEAM_ISSUES_INTERNAL_ERROR);
@@ -95,14 +103,27 @@ _srt_steam_check (const GStrv environ,
   dot_steam_steam = g_build_filename (home, ".steam", "steam", NULL);
   dot_steam_root = g_build_filename (home, ".steam", "root", NULL);
 
-  /* Canonically, ~/.steam/steam is a symlink to the Steam installation.
+  /* Canonically, ~/.steam/steam is a symlink to the Steam data directory.
+   * This is used to install games, for example. It is *not* used to
+   * install the Steam client itself.
+   *
    * (This is ignoring the Valve-internal "beta universe", which uses
-   * ~/.steam/steambeta instead.) */
+   * ~/.steam/steambeta instead, and is not open to the public.) */
   if (g_file_test (dot_steam_steam, G_FILE_TEST_IS_SYMLINK))
     {
-      path = realpath (dot_steam_steam, NULL);
+      data_path = realpath (dot_steam_steam, NULL);
 
-      if (path == NULL)
+      if (data_path == NULL)
+        g_debug ("realpath(%s): %s", dot_steam_steam, g_strerror (errno));
+    }
+  else if (g_file_test (dot_steam_steam, G_FILE_TEST_IS_DIR))
+    {
+      /* e.g. https://bugs.debian.org/916303 */
+      issues |= SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK;
+
+      data_path = realpath (dot_steam_steam, NULL);
+
+      if (data_path == NULL)
         g_debug ("realpath(%s): %s", dot_steam_steam, g_strerror (errno));
     }
   else
@@ -111,20 +132,32 @@ _srt_steam_check (const GStrv environ,
       issues |= SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK;
     }
 
-  /* If that doesn't work, try ~/.steam/root, which points to whichever
-   * one of the public or beta universe was run most recently. */
-  if (path == NULL &&
-      g_file_test (dot_steam_root, G_FILE_TEST_IS_SYMLINK))
+  if (data_path == NULL
+      || !g_file_test (data_path, G_FILE_TEST_IS_DIR))
+    issues |= SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY;
+
+  /* Canonically, ~/.steam/root is a symlink to the Steam installation.
+   * This is *usually* the same thing as the Steam data directory, but
+   * it can be different when testing a new Steam client build. */
+  if (g_file_test (dot_steam_root, G_FILE_TEST_IS_SYMLINK))
     {
-      path = realpath (dot_steam_root, NULL);
+      install_path = realpath (dot_steam_root, NULL);
 
-      if (path == NULL)
+      if (install_path == NULL)
         g_debug ("realpath(%s): %s", dot_steam_root, g_strerror (errno));
     }
+  else
+    {
+      issues |= SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_SYMLINK;
+    }
+
+  if (install_path == NULL
+      || !g_file_test (install_path, G_FILE_TEST_IS_DIR))
+    issues |= SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_DIRECTORY;
 
-  /* If *that* doesn't work, try going up one level from ubuntu12_32,
-   * to which ~/.steam/bin32 is a symlink */
-  if (path == NULL &&
+  /* If ~/.steam/root doesn't work, try going up one level from
+   * ubuntu12_32, to which ~/.steam/bin32 is a symlink */
+  if (install_path == NULL &&
       g_file_test (dot_steam_bin32, G_FILE_TEST_IS_SYMLINK))
     {
       bin32 = realpath (dot_steam_bin32, NULL);
@@ -140,61 +173,103 @@ _srt_steam_check (const GStrv environ,
         }
       else
         {
-          path = strndup (bin32, strlen (bin32) - strlen ("/ubuntu12_32"));
+          install_path = strndup (bin32, strlen (bin32) - strlen ("/ubuntu12_32"));
 
           /* We don't try to survive out-of-memory */
-          if (path == NULL)
+          if (install_path == NULL)
             g_error ("strndup: %s", g_strerror (errno));
         }
     }
 
+  /* If we have an installation path but no data path, or vice versa,
+   * assume they match. */
+  if (install_path == NULL && data_path != NULL)
+    {
+      install_path = strdup (data_path);
+
+      /* We don't try to survive out-of-memory */
+      if (install_path == NULL)
+        g_error ("strdup: %s", g_strerror (errno));
+    }
+
+  if (data_path == NULL && install_path != NULL)
+    {
+      data_path = strdup (install_path);
+
+      /* We don't try to survive out-of-memory */
+      if (data_path == NULL)
+        g_error ("strdup: %s", g_strerror (errno));
+    }
+
   /* If *that* doesn't work, try the default installation location. */
-  if (path == NULL)
+  if (install_path == NULL)
     {
-      path = realpath (default_steam_path, NULL);
+      install_path = realpath (default_steam_path, NULL);
 
-      if (path == NULL)
+      if (install_path == NULL)
         g_debug ("realpath(%s): %s", default_steam_path, g_strerror (errno));
     }
 
-  if (path == NULL)
+  if (data_path == NULL)
+    {
+      data_path = realpath (default_steam_path, NULL);
+
+      if (data_path == NULL)
+        g_debug ("realpath(%s): %s", default_steam_path, g_strerror (errno));
+    }
+
+  if (install_path == NULL)
     {
       g_debug ("Unable to find Steam installation");
       issues |= SRT_STEAM_ISSUES_CANNOT_FIND;
-      goto out;
     }
+  else
+    {
+      g_debug ("Found Steam installation at %s", install_path);
 
-  g_debug ("Found Steam installation at %s", path);
+      /* If we haven't found ubuntu12_32 yet, it's a subdirectory of the
+       * Steam installation */
+      if (bin32 == NULL)
+        {
+          /* We don't try to survive out-of-memory */
+          if (asprintf (&bin32, "%s/ubuntu12_32", install_path) < 0)
+            g_error ("asprintf: %s", g_strerror (errno));
+        }
 
-  /* If we haven't found ubuntu12_32 yet, it's a subdirectory of the
-   * Steam installation */
-  if (bin32 == NULL)
-    {
-      /* We don't try to survive out-of-memory */
-      if (asprintf (&bin32, "%s/ubuntu12_32", path) < 0)
-        g_error ("asprintf: %s", g_strerror (errno));
+      if (bin32 != NULL)
+        {
+          g_debug ("Found ubuntu12_32 directory at %s", bin32);
+        }
+      else
+        {
+          g_debug ("Unable to find ubuntu12_32 directory");
+        }
     }
 
-  if (bin32 != NULL)
+  if (data_path == NULL)
     {
-      g_debug ("Found ubuntu12_32 directory at %s", bin32);
+      g_debug ("Unable to find Steam data");
+      issues |= SRT_STEAM_ISSUES_CANNOT_FIND_DATA;
     }
   else
     {
-      g_debug ("Unable to find ubuntu12_32 directory");
+      g_debug ("Found Steam data at %s", data_path);
     }
 
   /* We can't just transfer ownership here, because in the older GLib
    * that we're targeting, g_free() and free() are not guaranteed to be
    * associated with the same memory pool. */
-  if (path_out != NULL)
-    *path_out = g_strdup (path);
+  if (install_path_out != NULL)
+    *install_path_out = g_strdup (install_path);
+
+  if (data_path_out != NULL)
+    *data_path_out = g_strdup (data_path);
 
   if (bin32_out != NULL)
     *bin32_out = g_strdup (bin32);
 
-out:
-  free (path);
+  free (install_path);
+  free (data_path);
   free (bin32);
   g_free (dot_steam_bin32);
   g_free (dot_steam_steam);
diff --git a/steam-runtime-tools/steam.h b/steam-runtime-tools/steam.h
index 68e64c200..b19b1197e 100644
--- a/steam-runtime-tools/steam.h
+++ b/steam-runtime-tools/steam.h
@@ -34,10 +34,28 @@
  * @SRT_STEAM_ISSUES_NONE: There are no problems
  * @SRT_STEAM_ISSUES_INTERNAL_ERROR: Unable to detect the status of the
  *  Steam installation
- * @SRT_STEAM_ISSUES_CANNOT_FIND: Unable to find the Steam installation
+ * @SRT_STEAM_ISSUES_CANNOT_FIND: Unable to find the Steam installation,
+ *  either via its canonical symlink `~/.steam/root` or various fallback
+ *  methods. See srt_system_info_dup_steam_installation_path().
  * @SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK: `~/.steam/steam` is not
- *  a symbolic link to a Steam installation, which for example can happen
- *  if Steam was installed on a system with https://bugs.debian.org/916303
+ *  a symbolic link to Steam data, which for example can happen
+ *  if Steam was installed on a system with <https://bugs.debian.org/916303>.
+ *  See srt_system_info_dup_steam_data_path().
+ * @SRT_STEAM_ISSUES_CANNOT_FIND_DATA: Unable to find the Steam data,
+ *  either via its canonical symlink `~/.steam/steam` or various fallback
+ *  methods. Steam is unlikely to work in this situation.
+ *  See srt_system_info_dup_steam_data_path().
+ * @SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY: `~/.steam/steam` is
+ *  neither a directory nor a symbolic link to a directory.
+ *  Steam is unlikely to work in this situation.
+ *  See srt_system_info_dup_steam_data_path().
+ * @SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_SYMLINK: `~/.steam/root` is not
+ *  a symbolic link to the Steam installation.
+ *  See srt_system_info_dup_steam_installation_path().
+ * @SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_DIRECTORY: `~/.steam/root` is
+ *  neither a directory nor a symbolic link to a directory.
+ *  Steam is unlikely to work in this situation.
+ *  See srt_system_info_dup_steam_installation_path().
  *
  * A bitfield with flags representing problems with the Steam
  * installation, or %SRT_STEAM_ISSUES_NONE (which is numerically zero)
@@ -50,5 +68,9 @@ typedef enum
   SRT_STEAM_ISSUES_INTERNAL_ERROR = (1 << 0),
   SRT_STEAM_ISSUES_CANNOT_FIND = (1 << 1),
   SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK = (1 << 2),
+  SRT_STEAM_ISSUES_CANNOT_FIND_DATA = (1 << 3),
+  SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY = (1 << 4),
+  SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_SYMLINK = (1 << 5),
+  SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_DIRECTORY = (1 << 6),
   SRT_STEAM_ISSUES_NONE = 0
 } SrtSteamIssues;
diff --git a/steam-runtime-tools/system-info.c b/steam-runtime-tools/system-info.c
index 930b3b167..988a28bf9 100644
--- a/steam-runtime-tools/system-info.c
+++ b/steam-runtime-tools/system-info.c
@@ -100,9 +100,10 @@ struct _SrtSystemInfo
   } locales;
   struct
   {
-    /* path != NULL or issues != NONE indicates we have already checked
-     * the Steam installation */
-    gchar *path;
+    /* install_path != NULL or issues != NONE indicates we have already
+     * checked the Steam installation */
+    gchar *install_path;
+    gchar *data_path;
     gchar *bin32;
     SrtSteamIssues issues;
   } steam;
@@ -313,7 +314,8 @@ forget_steam (SrtSystemInfo *self)
 {
   forget_runtime (self);
   self->steam.issues = SRT_STEAM_ISSUES_NONE;
-  g_clear_pointer (&self->steam.path, g_free);
+  g_clear_pointer (&self->steam.install_path, g_free);
+  g_clear_pointer (&self->steam.data_path, g_free);
   g_clear_pointer (&self->steam.bin32, g_free);
 }
 
@@ -1015,9 +1017,11 @@ static void
 ensure_steam_cached (SrtSystemInfo *self)
 {
   if (self->steam.issues == SRT_STEAM_ISSUES_NONE &&
-      self->steam.path == NULL)
+      self->steam.install_path == NULL &&
+      self->steam.data_path == NULL)
     self->steam.issues = _srt_steam_check (self->env,
-                                           &self->steam.path,
+                                           &self->steam.install_path,
+                                           &self->steam.data_path,
                                            &self->steam.bin32);
 }
 
@@ -1046,13 +1050,28 @@ srt_system_info_get_steam_issues (SrtSystemInfo *self)
  *
  * Return the absolute path to the Steam installation in use (the
  * directory containing `steam.sh` and `ubuntu12_32/` among other
- * files and directories, analogous to `C:\Program Files\Steam` in a
- * typical Windows installation of Steam). This is typically of the form
- * `/home/me/.local/share/Steam`.
+ * files and directories).
+ *
+ * This directory is analogous to `C:\Program Files\Steam` in a
+ * typical Windows installation of Steam, and is typically of the form
+ * `/home/me/.local/share/Steam`. It is also known as the "Steam root",
+ * and is canonically accessed via the symbolic link `~/.steam/root`
+ * (known as the "Steam root link").
  *
- * If the Steam installation could not be found, at least one flag will
+ * Under normal circumstances, this is the same directory as
+ * srt_system_info_dup_steam_data_path(). However, it is possible to
+ * construct situations where they are different, for example when a
+ * Steam developer tests a new client build in its own installation
+ * directory in conjunction with an existing data directory from the
+ * production client, or when Steam was first installed using a Debian
+ * package that suffered from
+ * [#916303](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916303)
+ * (which resulted in `~/.steam/steam` being a plain directory, not a
+ * symbolic link).
+ *
+ * If the Steam installation could not be found, flags will
  * be set in the result of srt_system_info_get_steam_issues() to indicate
- * why.
+ * why: at least %SRT_STEAM_ISSUES_CANNOT_FIND, and possibly others.
  *
  * Returns: (transfer full) (type filename) (nullable): The absolute path
  *  to the Steam installation, or %NULL if it could not be determined.
@@ -1064,7 +1083,49 @@ srt_system_info_dup_steam_installation_path (SrtSystemInfo *self)
   g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), NULL);
 
   ensure_steam_cached (self);
-  return g_strdup (self->steam.path);
+  return g_strdup (self->steam.install_path);
+}
+
+/**
+ * srt_system_info_dup_steam_data_path:
+ * @self: The #SrtSystemInfo object
+ *
+ * Return the absolute path to the Steam data directory in use (the
+ * directory containing `appcache/`, `userdata/` and the default
+ * `steamapps/` or `SteamApps/` installation path for games, among other
+ * files and directories).
+ *
+ * This directory is analogous to `C:\Program Files\Steam` in a
+ * typical Windows installation of Steam, and is typically of the form
+ * `/home/me/.local/share/Steam`. It is canonically accessed via the
+ * symbolic link `~/.steam/steam` (known as the "Steam data link").
+ *
+ * Under normal circumstances, this is the same directory as
+ * srt_system_info_dup_steam_installation_path(). However, it is possible
+ * to construct situations where they are different, for example when a
+ * Steam developer tests a new client build in its own installation
+ * directory in conjunction with an existing data directory from the
+ * production client, or when Steam was first installed using a Debian
+ * package that suffered from
+ * [#916303](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916303)
+ * (which resulted in `~/.steam/steam` being a plain directory, not a
+ * symbolic link).
+ *
+ * If the Steam data could not be found, flags will
+ * be set in the result of srt_system_info_get_steam_issues() to indicate
+ * why: at least %SRT_STEAM_ISSUES_CANNOT_FIND_DATA, and possibly others.
+ *
+ * Returns: (transfer full) (type filename) (nullable): The absolute path
+ *  to the Steam installation, or %NULL if it could not be determined.
+ *  Free with g_free().
+ */
+gchar *
+srt_system_info_dup_steam_data_path (SrtSystemInfo *self)
+{
+  g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), NULL);
+
+  ensure_steam_cached (self);
+  return g_strdup (self->steam.data_path);
 }
 
 /**
@@ -1149,6 +1210,9 @@ srt_system_info_get_runtime_issues (SrtSystemInfo *self)
  * in use (the directory containing `run.sh`, `version.txt` and
  * similar files).
  *
+ * This will typically be below
+ * srt_system_info_dup_steam_installation_path(), unless overridden.
+ *
  * If the Steam Runtime has been disabled or could not be found, at
  * least one flag will be set in the result of
  * srt_system_info_get_runtime_issues() to indicate why.
diff --git a/steam-runtime-tools/system-info.h b/steam-runtime-tools/system-info.h
index 7caf98f9f..3de4da0f8 100644
--- a/steam-runtime-tools/system-info.h
+++ b/steam-runtime-tools/system-info.h
@@ -106,6 +106,7 @@ gchar *srt_system_info_dup_runtime_version (SrtSystemInfo *self);
 
 SrtSteamIssues srt_system_info_get_steam_issues (SrtSystemInfo *self);
 gchar *srt_system_info_dup_steam_installation_path (SrtSystemInfo *self);
+gchar *srt_system_info_dup_steam_data_path (SrtSystemInfo *self);
 
 SrtLocaleIssues srt_system_info_get_locale_issues (SrtSystemInfo *self);
 SrtLocale *srt_system_info_check_locale (SrtSystemInfo *self,
diff --git a/tests/fake-home.c b/tests/fake-home.c
index c67da2f38..80ef0811b 100644
--- a/tests/fake-home.c
+++ b/tests/fake-home.c
@@ -52,10 +52,12 @@ fake_home_new (void)
   fake_home->create_pinning_libs = TRUE;
   fake_home->create_i386_folders = TRUE;
   fake_home->create_amd64_folders = TRUE;
+  fake_home->create_root_symlink = TRUE;
   fake_home->create_steam_symlink = TRUE;
   fake_home->create_steamrt_files = TRUE;
   fake_home->add_environments = TRUE;
   fake_home->has_debian_bug_916303 = FALSE;
+  fake_home->testing_beta_client = FALSE;
   
   return fake_home;
 }
@@ -81,6 +83,7 @@ fake_home_create_structure (FakeHome *f)
   gchar *setup = NULL;
   gchar *version = NULL;
   gchar *dot_steam_bin32 = NULL;
+  gchar *dot_steam_root = NULL;
   gchar *dot_steam_steam = NULL;
   gchar *local_share = NULL;
   gchar *ld_path = NULL;
@@ -94,19 +97,33 @@ fake_home_create_structure (FakeHome *f)
   dot_steam = g_build_filename (f->home, ".steam", NULL);
 
   if (f->has_debian_bug_916303)
-    f->steam_base_folder = g_strdup (dot_steam);
+    {
+      f->steam_install = g_strdup (dot_steam);
+      f->steam_data = g_build_filename (dot_steam, "steam", NULL);
+    }
+  else if (f->testing_beta_client)
+    {
+      f->steam_install = g_build_filename (f->home, "beta-client", NULL);
+      f->steam_data = g_build_filename (f->home, ".local", "share",
+                                        "Steam", NULL);
+    }
   else
-    f->steam_base_folder = g_build_filename (f->home, ".local", "share",
-                                             "Steam", NULL);
+    {
+      f->steam_install = g_build_filename (f->home, ".local", "share",
+                                           "Steam", NULL);
+      f->steam_data = g_strdup (f->steam_install);
+    }
 
-  f->runtime = g_build_filename (f->steam_base_folder, "ubuntu12_32",
+  f->runtime = g_build_filename (f->steam_install, "ubuntu12_32",
                                  "steam-runtime", NULL);
 
   scripts = g_build_filename (f->runtime, "scripts", NULL);
 
   if (g_mkdir_with_parents (dot_steam, 0755) != 0)
     goto out;
-  if (g_mkdir_with_parents (f->steam_base_folder, 0755) != 0)
+  if (g_mkdir_with_parents (f->steam_data, 0755) != 0)
+    goto out;
+  if (g_mkdir_with_parents (f->steam_install, 0755) != 0)
     goto out;
   if (g_mkdir_with_parents (scripts, 0755) != 0)
     goto out;
@@ -177,13 +194,32 @@ fake_home_create_structure (FakeHome *f)
         goto out;
     }
 
+  if (f->create_root_symlink)
+    {
+      if (dot_steam_root == NULL)
+        dot_steam_root = g_build_filename (dot_steam, "root", NULL);
+      symlink = g_file_new_for_path (dot_steam_root);
+
+      g_file_make_symbolic_link (symlink, f->steam_install, NULL, &error);
+      g_object_unref (symlink);
+      g_assert_no_error (error);
+
+      dot_steam_bin32 = g_build_filename (dot_steam, "bin32", NULL);
+      symlink = g_file_new_for_path (dot_steam_bin32);
+
+      ubuntu12_32 = g_build_filename (f->steam_install, "ubuntu12_32", NULL);
+      g_file_make_symbolic_link (symlink, ubuntu12_32, NULL, &error);
+      g_object_unref (symlink);
+      g_assert_no_error (error);
+    }
+
   if (f->create_steam_symlink)
     {
       if (dot_steam_steam == NULL)
         dot_steam_steam = g_build_filename (dot_steam, "steam", NULL);
       symlink = g_file_new_for_path (dot_steam_steam);
 
-      g_file_make_symbolic_link (symlink, f->steam_base_folder, NULL, &error);
+      g_file_make_symbolic_link (symlink, f->steam_data, NULL, &error);
       g_object_unref (symlink);
       if (f->has_debian_bug_916303)
         {
@@ -194,14 +230,6 @@ fake_home_create_structure (FakeHome *f)
         {
           g_assert_no_error (error);
         }
-
-      dot_steam_bin32 = g_build_filename (dot_steam, "bin32", NULL);
-      symlink = g_file_new_for_path (dot_steam_bin32);
-
-      ubuntu12_32 = g_build_filename (f->steam_base_folder, "ubuntu12_32", NULL);
-      g_file_make_symbolic_link (symlink, ubuntu12_32, NULL, &error);
-      g_object_unref (symlink);
-      g_assert_no_error (error);
     }
 
   local_share = g_build_filename (f->home, ".local", "share", NULL);
@@ -250,6 +278,7 @@ fake_home_create_structure (FakeHome *f)
     g_free (setup);
     g_free (version);
     g_free (dot_steam_bin32);
+    g_free (dot_steam_root);
     g_free (dot_steam_steam);
     g_free (local_share);
     g_free (ld_path);
@@ -277,7 +306,8 @@ fake_home_clean_up (FakeHome *f)
     g_debug ("Unable to remove the fake home directory: %s", f->home);
 
   g_free (f->home);
-  g_free (f->steam_base_folder);
+  g_free (f->steam_data);
+  g_free (f->steam_install);
   g_free (f->runtime);
   g_free (f->pinned_32);
   g_free (f->pinned_64);
diff --git a/tests/fake-home.h b/tests/fake-home.h
index 60a9846c0..197580f7f 100644
--- a/tests/fake-home.h
+++ b/tests/fake-home.h
@@ -33,13 +33,16 @@ typedef struct
   gboolean create_pinning_libs;
   gboolean create_i386_folders;
   gboolean create_amd64_folders;
+  gboolean create_root_symlink;
   gboolean create_steam_symlink;
   gboolean create_steamrt_files;
   gboolean add_environments;
   gboolean has_debian_bug_916303;
+  gboolean testing_beta_client;
 
   gchar *home;
-  gchar *steam_base_folder;
+  gchar *steam_install;
+  gchar *steam_data;
   gchar *runtime;
   gchar *pinned_32;
   gchar *pinned_64;
diff --git a/tests/system-info-cli.c b/tests/system-info-cli.c
index 4dc74e6d2..bb6afe0b3 100644
--- a/tests/system-info-cli.c
+++ b/tests/system-info-cli.c
@@ -586,9 +586,11 @@ steam_issues (Fixture *f,
 
   g_assert_true (json_object_has_member (json_sub_object, "issues"));
   array = json_object_get_array_member (json_sub_object, "issues");
-  g_assert_cmpint (json_array_get_length (array), ==, 1);
+  g_assert_cmpint (json_array_get_length (array), ==, 2);
   g_assert_cmpstr (json_array_get_string_element (array, 0), ==,
                    "dot-steam-steam-not-symlink");
+  g_assert_cmpstr (json_array_get_string_element (array, 1), ==,
+                   "dot-steam-steam-not-directory");
 
   g_assert_true (json_object_has_member (json, "runtime"));
   json_sub_object = json_object_get_object_member (json, "runtime");
diff --git a/tests/system-info.c b/tests/system-info.c
index cd27ec555..34d842c91 100644
--- a/tests/system-info.c
+++ b/tests/system-info.c
@@ -714,7 +714,7 @@ steam_runtime (Fixture *f,
   runtime_path = srt_system_info_dup_runtime_path (info);
   g_assert_cmpstr (runtime_path, ==, fake_home->runtime);
   installation_path = srt_system_info_dup_steam_installation_path (info);
-  g_assert_cmpstr (installation_path, ==, fake_home->steam_base_folder);
+  g_assert_cmpstr (installation_path, ==, fake_home->steam_install);
   g_free (runtime_path);
   g_free (installation_path);
 
@@ -724,7 +724,7 @@ steam_runtime (Fixture *f,
   runtime_path = srt_system_info_dup_runtime_path (info);
   g_assert_cmpstr (runtime_path, ==, fake_home->runtime);
   installation_path = srt_system_info_dup_steam_installation_path (info);
-  g_assert_cmpstr (installation_path, ==, fake_home->steam_base_folder);
+  g_assert_cmpstr (installation_path, ==, fake_home->steam_install);
 
   /* Check for Steam issues */
   steam_issues = srt_system_info_get_steam_issues (info);
@@ -1019,7 +1019,7 @@ runtime_unexpected_location (Fixture *f,
 {
   SrtSystemInfo *info;
   SrtRuntimeIssues issues;
-  gchar *dot_steam_steam = NULL;
+  gchar *dot_steam_root = NULL;
   gchar *my_runtime = NULL;
   gchar *ld_path = NULL;
   gchar *env_path = NULL;
@@ -1029,19 +1029,19 @@ runtime_unexpected_location (Fixture *f,
   FakeHome *fake_home;
 
   fake_home = fake_home_new ();
-  fake_home->create_steam_symlink = FALSE;
+  fake_home->create_root_symlink = FALSE;
   fake_home_create_structure (fake_home);
 
   info = srt_system_info_new (NULL);
-  dot_steam_steam = g_build_filename (fake_home->home, ".steam", "steam", NULL);
+  dot_steam_root = g_build_filename (fake_home->home, ".steam", "root", NULL);
 
-  my_runtime = g_build_filename (fake_home->steam_base_folder, "ubuntu12_32",
+  my_runtime = g_build_filename (fake_home->steam_install, "ubuntu12_32",
                                  "my-runtime", NULL);
 
 
   /* Create a new homedir/.steam/steam symlink that doesn't point to
    * the expected steam runtime path. */
-  symlink = g_file_new_for_path (dot_steam_steam);
+  symlink = g_file_new_for_path (dot_steam_root);
   g_file_make_symbolic_link (symlink, fake_home->pinned_64, NULL, &error);
   g_assert_no_error (error);
   srt_system_info_set_environ (info, fake_home->env);
@@ -1063,8 +1063,8 @@ runtime_unexpected_location (Fixture *f,
   env_path = g_strjoinv ("my-runtime", parts);
 
   g_rename (fake_home->runtime, my_runtime);
-  g_remove (dot_steam_steam);
-  symlink = g_file_new_for_path (dot_steam_steam);
+  g_remove (dot_steam_root);
+  symlink = g_file_new_for_path (dot_steam_root);
   g_file_make_symbolic_link (symlink, my_runtime, NULL, &error);
   g_assert_no_error (error);
   fake_home->env = g_environ_setenv (fake_home->env, "LD_LIBRARY_PATH", ld_path, TRUE);
@@ -1078,7 +1078,7 @@ runtime_unexpected_location (Fixture *f,
   fake_home_clean_up (fake_home);
   g_object_unref (symlink);
   g_object_unref (info);
-  g_free (dot_steam_steam);
+  g_free (dot_steam_root);
   g_free (my_runtime);
   g_free (ld_path);
   g_free (env_path);
@@ -1109,30 +1109,26 @@ steam_symlink (Fixture *f,
   dot_steam_steam = g_build_filename (fake_home->home, ".steam", "steam", NULL);
   dot_steam_root = g_build_filename (fake_home->home, ".steam", "root", NULL);
   dot_steam_bin32 = g_build_filename (fake_home->home, ".steam", "bin32", NULL);
-  ubuntu12_32 = g_build_filename (fake_home->steam_base_folder, "ubuntu12_32", NULL);
+  ubuntu12_32 = g_build_filename (fake_home->steam_install, "ubuntu12_32", NULL);
 
   /* We don't have a homedir/.steam/steam symlink. */
   srt_system_info_set_environ (info, fake_home->env);
   issues = srt_system_info_get_steam_issues (info);
-  g_assert_cmpint (SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK, ==, issues);
-
-  /* Create the homedir/.steam/root symlink. */
-  symlink = g_file_new_for_path (dot_steam_root);
-  g_file_make_symbolic_link (symlink, fake_home->steam_base_folder, NULL, &error);
-  g_assert_no_error (error);
-  srt_system_info_set_environ (info, fake_home->env);
-  issues = srt_system_info_get_steam_issues (info);
-  g_assert_cmpint (SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK, ==, issues);
-  g_object_unref (symlink);
+  g_assert_cmpint ((SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK
+                   | SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY), ==, issues);
 
   /* Remove homedir/.steam/root symlink and create homedir/.steam/bin32 symlink. */
   g_remove (dot_steam_root);
+  g_remove (dot_steam_bin32);
   symlink = g_file_new_for_path (dot_steam_bin32);
   g_file_make_symbolic_link (symlink, ubuntu12_32, NULL, &error);
   g_assert_no_error (error);
   srt_system_info_set_environ (info, fake_home->env);
   issues = srt_system_info_get_steam_issues (info);
-  g_assert_cmpint (SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK, ==, issues);
+  g_assert_cmpint ((SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK
+                   | SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY
+                   | SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_SYMLINK
+                   | SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_DIRECTORY), ==, issues);
 
   /* Remove the homedir/.steam/bin32 symlink and set XDG_DATA_HOME env to a
    * folder that is not the expected homedir/.local/share */
@@ -1141,8 +1137,12 @@ steam_symlink (Fixture *f,
   fake_home->env = g_environ_setenv (fake_home->env, "XDG_DATA_HOME", data_home, TRUE);
   srt_system_info_set_environ (info, fake_home->env);
   issues = srt_system_info_get_steam_issues (info);
-  g_assert_cmpint (SRT_STEAM_ISSUES_CANNOT_FIND |
-                   SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK, ==, issues);
+  g_assert_cmpint ((SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK
+                   | SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY
+                   | SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_SYMLINK
+                   | SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_DIRECTORY
+                   | SRT_STEAM_ISSUES_CANNOT_FIND
+                   | SRT_STEAM_ISSUES_CANNOT_FIND_DATA), ==, issues);
   installation_path = srt_system_info_dup_steam_installation_path (info);
   g_assert_cmpstr (installation_path, ==, NULL);
 
@@ -1167,6 +1167,8 @@ debian_bug_916303 (Fixture *f,
   SrtSystemInfo *info;
   SrtSteamIssues issues;
   FakeHome *fake_home;
+  gchar *installation_path;
+  gchar *data_path;
 
   fake_home = fake_home_new ();
   fake_home->has_debian_bug_916303 = TRUE;
@@ -1177,9 +1179,50 @@ debian_bug_916303 (Fixture *f,
 
   issues = srt_system_info_get_steam_issues (info);
   g_assert_cmpint (issues, ==, SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK);
+  installation_path = srt_system_info_dup_steam_installation_path (info);
+  g_assert_cmpstr (installation_path, ==, fake_home->steam_install);
+  g_assert_true (g_str_has_suffix (installation_path, "/.steam"));
+  data_path = srt_system_info_dup_steam_data_path (info);
+  g_assert_cmpstr (data_path, ==, fake_home->steam_data);
+  g_assert_true (g_str_has_suffix (data_path, "/.steam/steam"));
 
   fake_home_clean_up (fake_home);
   g_object_unref (info);
+  g_free (installation_path);
+  g_free (data_path);
+}
+
+/* Behave as though we're testing a beta client. */
+static void
+testing_beta_client (Fixture *f,
+                     gconstpointer context)
+{
+  SrtSystemInfo *info;
+  SrtSteamIssues issues;
+  FakeHome *fake_home;
+  gchar *installation_path;
+  gchar *data_path;
+
+  fake_home = fake_home_new ();
+  fake_home->testing_beta_client = TRUE;
+  fake_home_create_structure (fake_home);
+
+  info = srt_system_info_new (NULL);
+  srt_system_info_set_environ (info, fake_home->env);
+
+  issues = srt_system_info_get_steam_issues (info);
+  g_assert_cmpint (issues, ==, 0);
+  installation_path = srt_system_info_dup_steam_installation_path (info);
+  g_assert_cmpstr (installation_path, ==, fake_home->steam_install);
+  g_assert_true (g_str_has_suffix (installation_path, "/beta-client"));
+  data_path = srt_system_info_dup_steam_data_path (info);
+  g_assert_cmpstr (data_path, ==, fake_home->steam_data);
+  g_assert_true (g_str_has_suffix (data_path, "/.local/share/Steam"));
+
+  fake_home_clean_up (fake_home);
+  g_object_unref (info);
+  g_free (installation_path);
+  g_free (data_path);
 }
 
 int
@@ -1220,6 +1263,8 @@ main (int argc,
               setup, steam_symlink, teardown);
   g_test_add ("/system-info/debian_bug_916303", Fixture, NULL,
               setup, debian_bug_916303, teardown);
+  g_test_add ("/system-info/testing_beta_client", Fixture, NULL,
+              setup, testing_beta_client, teardown);
 
   return g_test_run ();
 }
-- 
GitLab