From 06f27e651c748f4a05b2eb73dca8c7451be77d18 Mon Sep 17 00:00:00 2001
From: Ludovico de Nittis <ludovico.denittis@collabora.com>
Date: Fri, 2 Aug 2019 14:26:48 +0200
Subject: [PATCH] Add libraries check to SrtSystemInfo

This provides a high-level API to wrap SrtLibrary and check if the
current system has the expected libraries and symbols.

Signed-off-by: Ludovico de Nittis <ludovico.denittis@collabora.com>
---
 steam-runtime-tools/library.c                 |   7 +-
 steam-runtime-tools/library.h                 |   3 +
 steam-runtime-tools/system-info.c             | 287 ++++++++++
 steam-runtime-tools/system-info.h             |   7 +
 .../i386-linux-gnu/libglib2.0-0.symbols       |  11 +
 .../x86_64-linux-gnu/libglib2.0-0.symbols     |  11 +
 .../i386-linux-gnu/libglib2.0-0.symbols       |   9 +
 .../i386-linux-gnu/zlib1g.symbols             |   7 +
 .../x86_64-linux-gnu/libglib2.0-0.symbols     |   9 +
 .../x86_64-linux-gnu/zlib1g.symbols           |   7 +
 tests/meson.build                             |   1 +
 tests/system-info.c                           | 509 ++++++++++++++++++
 12 files changed, 865 insertions(+), 3 deletions(-)
 create mode 100644 tests/expectations/i386-linux-gnu/libglib2.0-0.symbols
 create mode 100644 tests/expectations/x86_64-linux-gnu/libglib2.0-0.symbols
 create mode 100644 tests/expectations_with_missings/i386-linux-gnu/libglib2.0-0.symbols
 create mode 100644 tests/expectations_with_missings/i386-linux-gnu/zlib1g.symbols
 create mode 100644 tests/expectations_with_missings/x86_64-linux-gnu/libglib2.0-0.symbols
 create mode 100644 tests/expectations_with_missings/x86_64-linux-gnu/zlib1g.symbols

diff --git a/steam-runtime-tools/library.c b/steam-runtime-tools/library.c
index d0b39fc8d..4294f32b0 100644
--- a/steam-runtime-tools/library.c
+++ b/steam-runtime-tools/library.c
@@ -449,9 +449,10 @@ srt_check_library_presence (const char *soname,
   GStrv dependencies = NULL;
   SrtLibraryIssues issues = SRT_LIBRARY_ISSUES_NONE;
 
-  g_return_val_if_fail (soname != NULL, SRT_LIBRARY_ISSUES_CANNOT_LOAD);
-  g_return_val_if_fail (multiarch != NULL, SRT_LIBRARY_ISSUES_CANNOT_LOAD);
-  g_return_val_if_fail (more_details_out == NULL || *more_details_out == NULL, SRT_LIBRARY_ISSUES_CANNOT_LOAD);
+  g_return_val_if_fail (soname != NULL, SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+  g_return_val_if_fail (multiarch != NULL, SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+  g_return_val_if_fail (more_details_out == NULL || *more_details_out == NULL,
+                        SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
 
   switch (symbols_format)
     {
diff --git a/steam-runtime-tools/library.h b/steam-runtime-tools/library.h
index 4a3db271f..a5608ca93 100644
--- a/steam-runtime-tools/library.h
+++ b/steam-runtime-tools/library.h
@@ -52,6 +52,8 @@ GType srt_library_get_type (void);
  *  were not present
  * @SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS: Some of the expected symbols
  *  were available with a different version
+ * @SRT_LIBRARY_ISSUES_INTERNAL_ERROR: Generic internal error, for example
+ *  a function has been called with a missing required parameter
  *
  * A bitfield with flags representing problems with a library, or
  * %SRT_LIBRARY_ISSUES_NONE (which is numerically zero) if no problems
@@ -64,6 +66,7 @@ typedef enum
   SRT_LIBRARY_ISSUES_CANNOT_LOAD = (1 << 0),
   SRT_LIBRARY_ISSUES_MISSING_SYMBOLS = (1 << 1),
   SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS = (1 << 2),
+  SRT_LIBRARY_ISSUES_INTERNAL_ERROR = (1 << 3),
   SRT_LIBRARY_ISSUES_NONE = 0
 } SrtLibraryIssues;
 
diff --git a/steam-runtime-tools/system-info.c b/steam-runtime-tools/system-info.c
index 4a6ae3f06..f3174bdb7 100644
--- a/steam-runtime-tools/system-info.c
+++ b/steam-runtime-tools/system-info.c
@@ -32,6 +32,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <stdio.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -85,6 +86,9 @@ typedef struct
 {
   GQuark multiarch_tuple;
   Tristate can_run;
+  GHashTable *cached_results;
+  SrtLibraryIssues cached_combined_issues;
+  gboolean libraries_cache_available;
 } Abi;
 
 static Abi *
@@ -108,6 +112,9 @@ ensure_abi (SrtSystemInfo *self,
   abi = g_slice_new0 (Abi);
   abi->multiarch_tuple = quark;
   abi->can_run = TRI_MAYBE;
+  abi->cached_results = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+  abi->cached_combined_issues = SRT_LIBRARY_ISSUES_NONE;
+  abi->libraries_cache_available = FALSE;
   /* transfer ownership to self->abis */
   g_ptr_array_add (self->abis, abi);
   return abi;
@@ -116,6 +123,10 @@ ensure_abi (SrtSystemInfo *self,
 static void
 abi_free (gpointer self)
 {
+  Abi *abi = self;
+  if (abi->cached_results != NULL)
+    g_hash_table_unref (abi->cached_results);
+
   g_slice_free (Abi, self);
 }
 
@@ -260,3 +271,279 @@ srt_system_info_can_write_to_uinput (SrtSystemInfo *self)
 
   return (self->can_write_uinput == TRI_YES);
 }
+
+static gint
+library_compare (SrtLibrary *a, SrtLibrary *b)
+{
+  return g_strcmp0 (srt_library_get_soname (a), srt_library_get_soname (b));
+}
+
+/**
+ * srt_system_info_check_libraries:
+ * @self: The #SrtSystemInfo object to use.
+ * @multiarch_tuple: A multiarch tuple like %SRT_ABI_I386, representing an ABI.
+ * @libraries_out: (out) (optional) (element-type SrtLibrary) (transfer full):
+ *  Used to return a #GList object where every element of said list is an
+ *  #SrtLibrary object, representing every `SONAME` found from the expectations
+ *  folder. Free with `g_list_free_full(libraries, g_object_unref)`.
+ *
+ * Check if the running system has all the expected libraries, and related symbols,
+ * as listed in the `deb-symbols(5)` files `*.symbols` in the @multiarch
+ * subdirectory of #SrtSystemInfo:expectations.
+ *
+ * Returns: A bitfield containing problems, or %SRT_LIBRARY_ISSUES_NONE
+ *  if no problems were found.
+ */
+SrtLibraryIssues
+srt_system_info_check_libraries (SrtSystemInfo *self,
+                                 const gchar *multiarch_tuple,
+                                 GList **libraries_out)
+{
+  Abi *abi = NULL;
+  gchar *dir_path = NULL;
+  const gchar *filename = NULL;
+  gchar *symbols_file = NULL;
+  size_t len = 0;
+  ssize_t chars;
+  GDir *dir = NULL;
+  FILE *fp = NULL;
+  GError *error = NULL;
+  SrtLibraryIssues ret = SRT_LIBRARY_ISSUES_INTERNAL_ERROR;
+
+  g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+  g_return_val_if_fail (multiarch_tuple != NULL, SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+  g_return_val_if_fail (libraries_out == NULL || *libraries_out == NULL,
+                        SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+
+  abi = ensure_abi (self, multiarch_tuple);
+
+  /* If we cached already the result, we return it */
+  if (abi->libraries_cache_available)
+    {
+      if (libraries_out != NULL)
+        {
+          *libraries_out = g_list_sort (g_hash_table_get_values (abi->cached_results),
+                                        (GCompareFunc) library_compare);
+          g_list_foreach (*libraries_out, (GFunc) G_CALLBACK (g_object_ref), NULL);
+        }
+
+      return abi->cached_combined_issues;
+    }
+
+  dir_path = g_build_filename (self->expectations, multiarch_tuple, NULL);
+  dir = g_dir_open (dir_path, 0, &error);
+  if (error)
+    {
+      g_debug ("An error occurred while opening the symbols directory: %s", error->message);
+      g_clear_error (&error);
+      goto out;
+    }
+  while ((filename = g_dir_read_name (dir)))
+    {
+      char *line = NULL;
+
+      if (!g_str_has_suffix (filename, ".symbols"))
+        continue;
+
+      symbols_file = g_build_filename (dir_path, filename, NULL);
+      fp = fopen(symbols_file, "r");
+
+      if (fp == NULL)
+        {
+          int saved_errno = errno;
+          g_debug ("Error reading \"%s\": %s\n", symbols_file, strerror (saved_errno));
+          goto out;
+        }
+
+      while ((chars = getline(&line, &len, fp)) != -1)
+        {
+          char *pointer_into_line = line;
+          if (line[chars - 1] == '\n')
+            line[chars - 1] = '\0';
+
+          if (line[0] == '\0')
+            continue;
+
+          if (line[0] != '#' && line[0] != '*' && line[0] != '|' && line[0] != ' ')
+            {
+              /* This line introduces a new SONAME. We extract it and call
+                * `srt_check_library_presence` with the symbols file where we
+                * found it, as an argument. */
+              SrtLibrary *library = NULL;
+              char *soname = g_strdup (strsep (&pointer_into_line, " \t"));
+              abi->cached_combined_issues |= srt_check_library_presence (soname,
+                                                                         multiarch_tuple,
+                                                                         symbols_file,
+                                                                         SRT_LIBRARY_SYMBOLS_FORMAT_DEB_SYMBOLS,
+                                                                         &library);
+              g_hash_table_insert (abi->cached_results, soname, library);
+            }
+        }
+      free (line);
+      g_clear_pointer (&symbols_file, g_free);
+      g_clear_pointer (&fp, fclose);
+    }
+
+  abi->libraries_cache_available = TRUE;
+  if (libraries_out != NULL)
+    {
+      *libraries_out = g_list_sort (g_hash_table_get_values (abi->cached_results),
+                                    (GCompareFunc) library_compare);
+      g_list_foreach (*libraries_out, (GFunc) G_CALLBACK (g_object_ref), NULL);
+    }
+
+  ret = abi->cached_combined_issues;
+
+  out:
+    g_clear_pointer (&symbols_file, g_free);
+    g_clear_pointer (&dir_path, g_free);
+
+    if (fp != NULL)
+      g_clear_pointer (&fp, fclose);
+
+    if (dir != NULL)
+      g_dir_close (dir);
+
+    return ret;
+
+}
+
+/**
+ * srt_system_info_check_library:
+ * @self: The #SrtSystemInfo object to use.
+ * @multiarch_tuple: A multiarch tuple like %SRT_ABI_I386, representing an ABI.
+ * @soname: (type filename): The `SONAME` of a shared library, for example `libjpeg.so.62`.
+ * @more_details_out: (out) (optional) (transfer full): Used to return an
+ *  #SrtLibrary object representing the shared library provided by @soname.
+ *  Free with `g_object_unref()`.
+ *
+ * Check if @soname is available in the running system and whether it conforms
+ * to the `deb-symbols(5)` files `*.symbols` in the @multiarch
+ * subdirectory of #SrtSystemInfo:expectations.
+ *
+ * Returns: A bitfield containing problems, or %SRT_LIBRARY_ISSUES_NONE
+ *  if no problems were found.
+ */
+SrtLibraryIssues
+srt_system_info_check_library (SrtSystemInfo *self,
+                               const gchar *multiarch_tuple,
+                               const gchar *soname,
+                               SrtLibrary **more_details_out)
+{
+  Abi *abi = NULL;
+  SrtLibrary *library = NULL;
+  const gchar *filename = NULL;
+  gchar *symbols_file = NULL;
+  gchar *dir_path = NULL;
+  gchar *line = NULL;
+  size_t len = 0;
+  ssize_t chars;
+  FILE *fp = NULL;
+  SrtLibraryIssues issues;
+  GDir *dir;
+  GError *error = NULL;
+  SrtLibraryIssues ret = SRT_LIBRARY_ISSUES_INTERNAL_ERROR;
+
+  g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+  g_return_val_if_fail (multiarch_tuple != NULL, SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+  g_return_val_if_fail (soname != NULL, SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+  g_return_val_if_fail (more_details_out == NULL || *more_details_out == NULL,
+                        SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+
+  abi = ensure_abi (self, multiarch_tuple);
+
+  /* If we have the result already in cache, we return it */
+  library = g_hash_table_lookup (abi->cached_results, soname);
+  if (library != NULL)
+    {
+      if (more_details_out != NULL)
+        *more_details_out = g_object_ref (library);
+      return srt_library_get_issues (library);
+    }
+
+
+  dir_path = g_build_filename (self->expectations, multiarch_tuple, NULL);
+  dir = g_dir_open (dir_path, 0, &error);
+  if (error)
+    {
+      g_debug ("An error occurred while opening the symbols directory: %s", error->message);
+      g_clear_error (&error);
+      goto out;
+    }
+  while ((filename = g_dir_read_name (dir)))
+    {
+      if (!g_str_has_suffix (filename, ".symbols"))
+        continue;
+
+      symbols_file = g_build_filename (dir_path, filename, NULL);
+      fp = fopen(symbols_file, "r");
+
+      if (fp == NULL)
+        {
+          int saved_errno = errno;
+          g_debug ("Error reading \"%s\": %s\n", symbols_file, strerror (saved_errno));
+          goto out;
+        }
+
+      while ((chars = getline(&line, &len, fp)) != -1)
+        {
+          char *pointer_into_line = line;
+          if (line[chars - 1] == '\n')
+            line[chars - 1] = '\0';
+
+          if (line[0] == '\0')
+            continue;
+
+          if (line[0] != '#' && line[0] != '*' && line[0] != '|' && line[0] != ' ')
+            {
+              /* This line introduces a new SONAME, which might
+                * be the one we are interested in. */
+              char *soname_found = g_strdup (strsep (&pointer_into_line, " \t"));
+              if (g_strcmp0 (soname_found, soname) == 0)
+                {
+                  issues = srt_check_library_presence (soname_found,
+                                                       multiarch_tuple,
+                                                       symbols_file,
+                                                       SRT_LIBRARY_SYMBOLS_FORMAT_DEB_SYMBOLS,
+                                                       &library);
+                  g_hash_table_insert (abi->cached_results, soname_found, library);
+                  abi->cached_combined_issues |= issues;
+                  if (more_details_out != NULL)
+                    *more_details_out = g_object_ref (library);
+                  free (line);
+                  ret = issues;
+                  goto out;
+                }
+              free (soname_found);
+            }
+        }
+      g_clear_pointer (&symbols_file, g_free);
+      g_clear_pointer (&line, g_free);
+      g_clear_pointer (&fp, fclose);
+    }
+  /* The SONAME's symbols file is not available.
+   * We do instead a simple absence/presence check. */
+  issues = srt_check_library_presence (soname,
+                                       multiarch_tuple,
+                                       NULL,
+                                       SRT_LIBRARY_SYMBOLS_FORMAT_DEB_SYMBOLS,
+                                       &library);
+  g_hash_table_insert (abi->cached_results, g_strdup (soname), library);
+  abi->cached_combined_issues |= issues;
+  if (more_details_out != NULL)
+    *more_details_out = g_object_ref (library);
+
+  ret = issues;
+
+  out:
+    g_clear_pointer (&symbols_file, g_free);
+    g_clear_pointer (&dir_path, g_free);
+
+    if (fp != NULL)
+      g_clear_pointer (&fp, fclose);
+
+    if (dir != NULL)
+      g_dir_close (dir);
+
+    return ret;
+}
diff --git a/steam-runtime-tools/system-info.h b/steam-runtime-tools/system-info.h
index cc1f72b29..6213b2d14 100644
--- a/steam-runtime-tools/system-info.h
+++ b/steam-runtime-tools/system-info.h
@@ -51,3 +51,10 @@ SrtSystemInfo *srt_system_info_new (const char *expectations);
 gboolean srt_system_info_can_run (SrtSystemInfo *self,
                                   const char *multiarch_tuple);
 gboolean srt_system_info_can_write_to_uinput (SrtSystemInfo *self);
+SrtLibraryIssues srt_system_info_check_libraries (SrtSystemInfo *self,
+                                                  const gchar *multiarch_tuple,
+                                                  GList **libraries_out);
+SrtLibraryIssues srt_system_info_check_library (SrtSystemInfo *self,
+                                                const gchar *multiarch_tuple,
+                                                const gchar *soname,
+                                                SrtLibrary **more_details_out);
diff --git a/tests/expectations/i386-linux-gnu/libglib2.0-0.symbols b/tests/expectations/i386-linux-gnu/libglib2.0-0.symbols
new file mode 100644
index 000000000..17fc854ac
--- /dev/null
+++ b/tests/expectations/i386-linux-gnu/libglib2.0-0.symbols
@@ -0,0 +1,11 @@
+# Cut-down version of libglib2.0-0:amd64.symbols, to illustrate what we expect
+# to find here
+libgio-2.0.so.0 libglib2.0-0 #MINVER#
+* Build-Depends-Package: libglib2.0-dev
+ g_action_activate@Base 2.28.0
+ g_action_change_state@Base 2.30.0
+libglib-2.0.so.0 libglib2.0-0 #MINVER#
+* Build-Depends-Package: libglib2.0-dev
+ g_access@Base 2.12.0
+ g_allocator_free@Base 2.12.0
+ g_allocator_new@Base 2.12.0
diff --git a/tests/expectations/x86_64-linux-gnu/libglib2.0-0.symbols b/tests/expectations/x86_64-linux-gnu/libglib2.0-0.symbols
new file mode 100644
index 000000000..17fc854ac
--- /dev/null
+++ b/tests/expectations/x86_64-linux-gnu/libglib2.0-0.symbols
@@ -0,0 +1,11 @@
+# Cut-down version of libglib2.0-0:amd64.symbols, to illustrate what we expect
+# to find here
+libgio-2.0.so.0 libglib2.0-0 #MINVER#
+* Build-Depends-Package: libglib2.0-dev
+ g_action_activate@Base 2.28.0
+ g_action_change_state@Base 2.30.0
+libglib-2.0.so.0 libglib2.0-0 #MINVER#
+* Build-Depends-Package: libglib2.0-dev
+ g_access@Base 2.12.0
+ g_allocator_free@Base 2.12.0
+ g_allocator_new@Base 2.12.0
diff --git a/tests/expectations_with_missings/i386-linux-gnu/libglib2.0-0.symbols b/tests/expectations_with_missings/i386-linux-gnu/libglib2.0-0.symbols
new file mode 100644
index 000000000..9d92f3bf0
--- /dev/null
+++ b/tests/expectations_with_missings/i386-linux-gnu/libglib2.0-0.symbols
@@ -0,0 +1,9 @@
+# Cut-down version of libglib2.0-0:amd64.symbols.
+# Also libgio-2.0.so.0 has been renamed to libgio-MISSING-2.0.so.0
+# In this way we can simulate a missing library.
+libgio-MISSING-2.0.so.0 libglib2.0-0 #MINVER#
+* Build-Depends-Package: libglib2.0-dev
+ g_action_activate@Base 2.28.0
+libglib-2.0.so.0 libglib2.0-0 #MINVER#
+* Build-Depends-Package: libglib2.0-dev
+ g_access@Base 2.12.0
diff --git a/tests/expectations_with_missings/i386-linux-gnu/zlib1g.symbols b/tests/expectations_with_missings/i386-linux-gnu/zlib1g.symbols
new file mode 100644
index 000000000..6ceb33235
--- /dev/null
+++ b/tests/expectations_with_missings/i386-linux-gnu/zlib1g.symbols
@@ -0,0 +1,7 @@
+# Cut-down version of zlib1g:amd64.symbols.
+# It has been appended also a missing symbol `missing@NotAvailable`
+# In this way we can simulate an available library with a missing symbol
+libz.so.1 zlib1g #MINVER#
+ adler32@Base 1:1.1.4
+ crc32@WRONG_VERSION 1:1.1.4
+ missing@NotAvailable 1:1.1.4
diff --git a/tests/expectations_with_missings/x86_64-linux-gnu/libglib2.0-0.symbols b/tests/expectations_with_missings/x86_64-linux-gnu/libglib2.0-0.symbols
new file mode 100644
index 000000000..9d92f3bf0
--- /dev/null
+++ b/tests/expectations_with_missings/x86_64-linux-gnu/libglib2.0-0.symbols
@@ -0,0 +1,9 @@
+# Cut-down version of libglib2.0-0:amd64.symbols.
+# Also libgio-2.0.so.0 has been renamed to libgio-MISSING-2.0.so.0
+# In this way we can simulate a missing library.
+libgio-MISSING-2.0.so.0 libglib2.0-0 #MINVER#
+* Build-Depends-Package: libglib2.0-dev
+ g_action_activate@Base 2.28.0
+libglib-2.0.so.0 libglib2.0-0 #MINVER#
+* Build-Depends-Package: libglib2.0-dev
+ g_access@Base 2.12.0
diff --git a/tests/expectations_with_missings/x86_64-linux-gnu/zlib1g.symbols b/tests/expectations_with_missings/x86_64-linux-gnu/zlib1g.symbols
new file mode 100644
index 000000000..6ceb33235
--- /dev/null
+++ b/tests/expectations_with_missings/x86_64-linux-gnu/zlib1g.symbols
@@ -0,0 +1,7 @@
+# Cut-down version of zlib1g:amd64.symbols.
+# It has been appended also a missing symbol `missing@NotAvailable`
+# In this way we can simulate an available library with a missing symbol
+libz.so.1 zlib1g #MINVER#
+ adler32@Base 1:1.1.4
+ crc32@WRONG_VERSION 1:1.1.4
+ missing@NotAvailable 1:1.1.4
diff --git a/tests/meson.build b/tests/meson.build
index d763b615b..5ff5cf52e 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -44,6 +44,7 @@ tests_metadir = join_paths(
 )
 
 install_subdir('expectations', install_dir : tests_dir)
+install_subdir('expectations_with_missings', install_dir : tests_dir)
 
 foreach test_name : tests
   exe = executable(
diff --git a/tests/system-info.c b/tests/system-info.c
index 451253c58..a86d43447 100644
--- a/tests/system-info.c
+++ b/tests/system-info.c
@@ -25,6 +25,8 @@
 
 #include <steam-runtime-tools/steam-runtime-tools.h>
 
+#include <steam-runtime-tools/glib-compat.h>
+
 #include <glib.h>
 #include <glib/gstdio.h>
 
@@ -137,6 +139,503 @@ test_object (Fixture *f,
   g_object_unref (info);
 }
 
+static void
+check_libraries_result (GList *libraries)
+{
+  SrtLibrary *library = NULL;
+  const char * const *missing_symbols;
+  const char * const *misversioned_symbols;
+  const char * const *dependencies;
+  gboolean seen_libc;
+  GList *l;
+
+  g_assert_nonnull (libraries);
+  l = libraries;
+
+  /* Test first library. Alphabetical order is an API guarantee, so we know
+   * which one it should be. */
+  library = l->data;
+  g_assert_nonnull (library);
+  g_assert_cmpstr (srt_library_get_soname (library), ==, "libgio-2.0.so.0");
+  missing_symbols = srt_library_get_missing_symbols (library);
+  g_assert_nonnull (missing_symbols);
+  g_assert_cmpstr (missing_symbols[0], ==, NULL);
+
+  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_NONE);
+
+  misversioned_symbols = srt_library_get_misversioned_symbols (library);
+  g_assert_nonnull (misversioned_symbols);
+  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);
+
+  dependencies = srt_library_get_dependencies (library);
+  g_assert_nonnull (dependencies);
+  g_assert_cmpstr (dependencies[0], !=, NULL);
+  seen_libc = FALSE;
+
+  for (gsize i = 0; dependencies[i] != NULL; i++)
+    {
+      g_debug ("libgio-2.0.so.0 depends on %s", dependencies[i]);
+
+      if (strstr (dependencies[i], "/libc.so.") != NULL)
+        seen_libc = TRUE;
+    }
+
+  g_assert_true (seen_libc);
+
+  /* Test second library */
+  l = g_list_next (l);
+  library = l->data;
+  g_assert_nonnull (library);
+  g_assert_cmpstr (srt_library_get_soname (library), ==, "libglib-2.0.so.0");
+  missing_symbols = srt_library_get_missing_symbols (library);
+  g_assert_nonnull (missing_symbols);
+  g_assert_cmpstr (missing_symbols[0], ==, NULL);
+
+  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_NONE);
+
+  misversioned_symbols = srt_library_get_misversioned_symbols (library);
+  g_assert_nonnull (misversioned_symbols);
+  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);
+
+  dependencies = srt_library_get_dependencies (library);
+  g_assert_nonnull (dependencies);
+  g_assert_cmpstr (dependencies[0], !=, NULL);
+  seen_libc = FALSE;
+
+  for (gsize i = 0; dependencies[i] != NULL; i++)
+    {
+      g_debug ("libglib-2.0.so.0 depends on %s", dependencies[i]);
+
+      if (strstr (dependencies[i], "/libc.so.") != NULL)
+        seen_libc = TRUE;
+    }
+
+  g_assert_true (seen_libc);
+
+  /* Test last library */
+  l = g_list_next (l);
+  library = l->data;
+  g_assert_nonnull (library);
+  g_assert_cmpstr (srt_library_get_soname (library), ==, "libz.so.1");
+  missing_symbols = srt_library_get_missing_symbols (library);
+  g_assert_nonnull (missing_symbols);
+  g_assert_cmpstr (missing_symbols[0], ==, NULL);
+
+  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_NONE);
+
+  misversioned_symbols = srt_library_get_misversioned_symbols (library);
+  g_assert_nonnull (misversioned_symbols);
+  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);
+
+  dependencies = srt_library_get_dependencies (library);
+  g_assert_nonnull (dependencies);
+  g_assert_cmpstr (dependencies[0], !=, NULL);
+  seen_libc = FALSE;
+
+  for (gsize i = 0; dependencies[i] != NULL; i++)
+    {
+      g_debug ("libz.so.1 depends on %s", dependencies[i]);
+
+      if (strstr (dependencies[i], "/libc.so.") != NULL)
+        seen_libc = TRUE;
+    }
+
+  g_assert_true (seen_libc);
+  g_assert_null (g_list_next (l));
+}
+
+/*
+ * Test if the expected libraries are available in the
+ * running system.
+ */
+static void
+libraries_presence (Fixture *f,
+                    gconstpointer context)
+{
+  SrtSystemInfo *info;
+  gchar *expectations_in = NULL;
+  GList *libraries = NULL;
+  SrtLibraryIssues issues;
+
+  if (strcmp (_SRT_MULTIARCH, "") == 0)
+    {
+      g_test_skip ("Unsupported architecture");
+      return;
+    }
+
+  expectations_in = g_build_filename (f->srcdir, "expectations", NULL);
+  info = srt_system_info_new (expectations_in);
+  issues = srt_system_info_check_libraries (info,
+                                            _SRT_MULTIARCH,
+                                            &libraries);
+  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_NONE);
+  check_libraries_result (libraries);
+  g_list_free_full (libraries, g_object_unref);
+  libraries = NULL;
+
+  /* Do the check again, this time using the cache */
+  issues = srt_system_info_check_libraries (info,
+                                            _SRT_MULTIARCH,
+                                            &libraries);
+  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_NONE);
+  check_libraries_result (libraries);
+
+  g_list_free_full (libraries, g_object_unref);
+  g_free (expectations_in);
+  g_object_unref (info);
+}
+
+static void
+check_library_result (SrtLibrary *library)
+{
+  const char * const *missing_symbols;
+  const char * const *misversioned_symbols;
+  const char * const *dependencies;
+  gboolean seen_libc;
+
+  g_assert_cmpstr (srt_library_get_soname (library), ==, "libz.so.1");
+
+  missing_symbols = srt_library_get_missing_symbols (library);
+  g_assert_nonnull (missing_symbols);
+  g_assert_cmpstr (missing_symbols[0], ==, NULL);
+
+  misversioned_symbols = srt_library_get_misversioned_symbols (library);
+  g_assert_nonnull (misversioned_symbols);
+  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);
+
+  dependencies = srt_library_get_dependencies (library);
+  g_assert_nonnull (dependencies);
+  g_assert_cmpstr (dependencies[0], !=, NULL);
+  seen_libc = FALSE;
+
+  for (gsize i = 0; dependencies[i] != NULL; i++)
+    {
+      g_debug ("libz.so.1 depends on %s", dependencies[i]);
+
+      if (strstr (dependencies[i], "/libc.so.") != NULL)
+        seen_libc = TRUE;
+    }
+
+  g_assert_true (seen_libc);
+}
+
+/*
+ * Test if `libz.so.1` is available in the running system and
+ * if it has the expected symbols.
+ */
+static void
+library_presence (Fixture *f,
+                  gconstpointer context)
+{
+  SrtSystemInfo *info;
+  gchar *expectations_in = NULL;
+  SrtLibrary *library = NULL;
+  SrtLibraryIssues issues;
+
+  if (strcmp (_SRT_MULTIARCH, "") == 0)
+    {
+      g_test_skip ("Unsupported architecture");
+      return;
+    }
+
+  expectations_in = g_build_filename (f->srcdir, "expectations", NULL);
+  info = srt_system_info_new (expectations_in);
+
+  issues = srt_system_info_check_library (info,
+                                          _SRT_MULTIARCH,
+                                          "libz.so.1",
+                                          &library);
+  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_NONE);
+
+  check_library_result (library);
+
+  g_clear_pointer (&library, g_object_unref);
+  /* Do the check again, this time using the cache */
+  issues = srt_system_info_check_library (info,
+                                          _SRT_MULTIARCH,
+                                          "libz.so.1",
+                                          &library);
+  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_NONE);
+
+  check_library_result (library);
+
+  g_object_unref (library);
+  g_free (expectations_in);
+  g_object_unref (info);
+}
+
+static void
+check_library_libz_missing_sym_result (SrtLibrary *library)
+{
+  const char * const *missing_symbols;
+  const char * const *misversioned_symbols;
+  const char * const *dependencies;
+  gboolean seen_libc;
+
+  g_assert_nonnull (library);
+  g_assert_cmpstr (srt_library_get_soname (library), ==, "libz.so.1");
+  g_debug ("path to libz.so.1 is %s", srt_library_get_absolute_path (library));
+  g_assert_true (srt_library_get_absolute_path (library)[0] == '/');
+  g_assert_true (g_file_test (srt_library_get_absolute_path (library), G_FILE_TEST_EXISTS));
+
+  g_assert_cmpint (srt_library_get_issues (library) & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS, !=, 0);
+  g_assert_cmpint (srt_library_get_issues (library) & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS, !=, 0);
+
+  missing_symbols = srt_library_get_missing_symbols (library);
+  g_assert_nonnull (missing_symbols);
+  g_assert_cmpstr (missing_symbols[0], ==, "missing@NotAvailable");
+  g_assert_cmpstr (missing_symbols[1], ==, NULL);
+
+  misversioned_symbols = srt_library_get_misversioned_symbols (library);
+  g_assert_nonnull (misversioned_symbols);
+  g_assert_cmpstr (misversioned_symbols[0], ==, "crc32@WRONG_VERSION");
+  g_assert_cmpstr (misversioned_symbols[1], ==, NULL);
+
+  dependencies = srt_library_get_dependencies (library);
+  g_assert_nonnull (dependencies);
+  g_assert_cmpstr (dependencies[0], !=, NULL);
+  seen_libc = FALSE;
+
+  for (gsize i = 0; dependencies[i] != NULL; i++)
+    {
+      g_debug ("libz.so.1 depends on %s", dependencies[i]);
+
+      if (strstr (dependencies[i], "/libc.so.") != NULL)
+        seen_libc = TRUE;
+    }
+
+  g_assert_true (seen_libc);
+}
+
+static void
+check_missing_libraries_result (GList *libraries)
+{
+  SrtLibrary *library = NULL;
+  const char * const *missing_symbols;
+  const char * const *misversioned_symbols;
+  const char * const *dependencies;
+  GList *l;
+
+  g_assert_nonnull (libraries);
+  l = libraries;
+
+  /* Test first library. Alphabetical order is an API guarantee, so we know
+   * which one it should be. */
+  library = l->data;
+  g_assert_nonnull (library);
+  g_assert_cmpstr (srt_library_get_soname (library), ==, "libgio-MISSING-2.0.so.0");
+  g_assert_cmpstr (srt_library_get_absolute_path (library), ==, NULL);
+
+  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_CANNOT_LOAD);
+
+  missing_symbols = srt_library_get_missing_symbols (library);
+  g_assert_nonnull (missing_symbols);
+  g_assert_cmpstr (missing_symbols[0], ==, NULL);
+
+  misversioned_symbols = srt_library_get_misversioned_symbols (library);
+  g_assert_nonnull (misversioned_symbols);
+  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);
+
+  dependencies = srt_library_get_dependencies (library);
+  g_assert_nonnull (dependencies);
+  g_assert_cmpstr (dependencies[0], ==, NULL);
+
+  /* Test second library */
+  l = g_list_next (l);
+  library = l->data;
+  g_assert_nonnull (library);
+  g_assert_cmpstr (srt_library_get_soname (library), ==, "libglib-2.0.so.0");
+  g_debug ("path to libglib-2.0.so.0 is %s", srt_library_get_absolute_path (library));
+  g_assert_true (srt_library_get_absolute_path (library)[0] == '/');
+  g_assert_true (g_file_test (srt_library_get_absolute_path (library), G_FILE_TEST_EXISTS));
+
+  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_NONE);
+
+  missing_symbols = srt_library_get_missing_symbols (library);
+  g_assert_nonnull (missing_symbols);
+  g_assert_cmpstr (missing_symbols[0], ==, NULL);
+
+  misversioned_symbols = srt_library_get_misversioned_symbols (library);
+  g_assert_nonnull (misversioned_symbols);
+  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);
+
+  dependencies = srt_library_get_dependencies (library);
+  g_assert_nonnull (dependencies);
+  g_assert_cmpstr (dependencies[0], !=, NULL);
+
+  /* Test last library */
+  l = g_list_next (l);
+  library = l->data;
+  check_library_libz_missing_sym_result (library);
+}
+
+/*
+ * Test libraries that are either not available or with missing and
+ * misversioned symbols.
+ */
+static void
+libraries_missing (Fixture *f,
+                   gconstpointer context)
+{
+  SrtSystemInfo *info;
+  gchar *expectations_in = NULL;
+  GList *libraries = NULL;
+  SrtLibraryIssues issues;
+
+  if (strcmp (_SRT_MULTIARCH, "") == 0)
+    {
+      g_test_skip ("Unsupported architecture");
+      return;
+    }
+
+  expectations_in = g_build_filename (f->srcdir, "expectations_with_missings", NULL);
+  info = srt_system_info_new (expectations_in);
+  issues = srt_system_info_check_libraries (info,
+                                            _SRT_MULTIARCH,
+                                            &libraries);
+  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS, !=, 0);
+  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_CANNOT_LOAD, !=, 0);
+  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS, !=, 0);
+  check_missing_libraries_result (libraries);
+  g_list_free_full (libraries, g_object_unref);
+  libraries = NULL;
+
+  /* Do the check again, this time using the cache */
+  issues = srt_system_info_check_libraries (info,
+                                            _SRT_MULTIARCH,
+                                            &libraries);
+  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS, !=, 0);
+  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_CANNOT_LOAD, !=, 0);
+  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS, !=, 0);
+  check_missing_libraries_result (libraries);
+
+  g_list_free_full (libraries, g_object_unref);
+  g_free (expectations_in);
+  g_object_unref (info);
+}
+
+static void
+check_library_missing_lib_result (SrtLibrary *library)
+{
+  const char * const *missing_symbols;
+  const char * const *misversioned_symbols;
+  const char * const *dependencies;
+
+  g_assert_nonnull (library);
+  g_assert_cmpstr (srt_library_get_soname (library), ==, "libMISSING.so.62");
+  g_assert_cmpstr (srt_library_get_absolute_path (library), ==, NULL);
+
+  missing_symbols = srt_library_get_missing_symbols (library);
+  g_assert_nonnull (missing_symbols);
+  g_assert_cmpstr (missing_symbols[0], ==, NULL);
+
+  misversioned_symbols = srt_library_get_misversioned_symbols (library);
+  g_assert_nonnull (misversioned_symbols);
+  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);
+
+  dependencies = srt_library_get_dependencies (library);
+  g_assert_nonnull (dependencies);
+  g_assert_cmpstr (dependencies[0], ==, NULL);
+}
+
+/*
+ * Test `libz.so.1` expecting missing and misversioned symbols.
+ * Then test the missing library `libMISSING.so.62`.
+ */
+static void
+library_missing (Fixture *f,
+                 gconstpointer context)
+{
+  SrtSystemInfo *info;
+  gchar *expectations_in = NULL;
+  SrtLibrary *library = NULL;
+  SrtLibraryIssues issues;
+
+  if (strcmp (_SRT_MULTIARCH, "") == 0)
+    {
+      g_test_skip ("Unsupported architecture");
+      return;
+    }
+
+  expectations_in = g_build_filename (f->srcdir, "expectations_with_missings", NULL);
+  info = srt_system_info_new (expectations_in);
+
+  /* Check a present library that has a missing symbol */
+  issues = srt_system_info_check_library (info,
+                                          _SRT_MULTIARCH,
+                                          "libz.so.1",
+                                          &library);
+  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS, !=, 0);
+  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS, !=, 0);
+  check_library_libz_missing_sym_result (library);
+
+  g_clear_pointer (&library, g_object_unref);
+  /* Do the check again, this time using the cache */
+  issues = srt_system_info_check_library (info,
+                                          _SRT_MULTIARCH,
+                                          "libz.so.1",
+                                          &library);
+  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS, !=, 0);
+  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS, !=, 0);
+  check_library_libz_missing_sym_result (library);
+
+  g_clear_pointer (&library, g_object_unref);
+  /* Check for a library that isn't listed in any of the .symbols files */
+  issues = srt_system_info_check_library (info,
+                                          _SRT_MULTIARCH,
+                                          "libMISSING.so.62",
+                                          &library);
+  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_CANNOT_LOAD);
+  check_library_missing_lib_result (library);
+
+  g_clear_pointer (&library, g_object_unref);
+  /* Do the check again, this time using the cache */
+  issues = srt_system_info_check_library (info,
+                                          _SRT_MULTIARCH,
+                                          "libMISSING.so.62",
+                                          &library);
+  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_CANNOT_LOAD);
+  check_library_missing_lib_result (library);
+
+  g_object_unref (library);
+  g_free (expectations_in);
+  g_object_unref (info);
+}
+
+/*
+ * Test libraries with the expectations folder set to NULL.
+ */
+static void
+wrong_expectations (Fixture *f,
+                    gconstpointer context)
+{
+  SrtSystemInfo *info;
+  SrtLibraryIssues issues;
+
+  if (strcmp (_SRT_MULTIARCH, "") == 0)
+    {
+      g_test_skip ("Unsupported architecture");
+      return;
+    }
+
+  /* Set the expectations folder to NULL.
+   * We expect the library checks to fail. */
+  info = srt_system_info_new (NULL);
+
+  issues = srt_system_info_check_libraries (info,
+                                            _SRT_MULTIARCH,
+                                            NULL);
+  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+
+  issues = srt_system_info_check_library (info,
+                                          _SRT_MULTIARCH,
+                                          "libz.so.1",
+                                          NULL);
+  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
+
+  g_object_unref (info);
+}
+
 int
 main (int argc,
       char **argv)
@@ -146,6 +645,16 @@ main (int argc,
   g_test_init (&argc, &argv, NULL);
   g_test_add ("/system-info/object", Fixture, NULL,
               setup, test_object, teardown);
+  g_test_add ("/system-info/libraries_presence", Fixture, NULL,
+              setup, libraries_presence, teardown);
+  g_test_add ("/system-info/library_presence", Fixture, NULL,
+              setup, library_presence, teardown);
+  g_test_add ("/system-info/libraries_missing", Fixture, NULL,
+              setup, libraries_missing, teardown);
+  g_test_add ("/system-info/library_missing", Fixture, NULL,
+              setup, library_missing, teardown);
+  g_test_add ("/system-info/wrong_expectations", Fixture, NULL,
+              setup, wrong_expectations, teardown);
 
   return g_test_run ();
 }
-- 
GitLab