diff --git a/bin/system-info.c b/bin/system-info.c index 0a4e8510f7c10ca72c7e8cc94505dcb54214d708..346c0fde7cdfd29bc883375bbf6fea48a933c74c 100644 --- a/bin/system-info.c +++ b/bin/system-info.c @@ -1079,6 +1079,7 @@ main (int argc, GList *va_api_list = NULL; GList *vdpau_list = NULL; GList *glx_list = NULL; + const char *ld_so; json_builder_set_member_name (builder, multiarch_tuples[i]); json_builder_begin_object (builder); @@ -1086,6 +1087,34 @@ main (int argc, can_run = srt_system_info_can_run (info, multiarch_tuples[i]); json_builder_add_boolean_value (builder, can_run); + ld_so = srt_architecture_get_expected_runtime_linker (multiarch_tuples[i]); + + if (ld_so != NULL) + { + json_builder_set_member_name (builder, "runtime-linker"); + json_builder_begin_object (builder); + { + g_autoptr(GError) local_error = NULL; + g_autofree gchar *real = NULL; + + json_builder_set_member_name (builder, "path"); + json_builder_add_string_value (builder, ld_so); + + if (srt_system_info_check_runtime_linker (info, + multiarch_tuples[i], + &real, &local_error)) + { + json_builder_set_member_name (builder, "resolved"); + json_builder_add_string_value (builder, real); + } + else + { + _srt_json_builder_add_error_members (builder, local_error); + } + } + json_builder_end_object (builder); + } + if (can_run) { json_builder_set_member_name (builder, "library-issues-summary"); diff --git a/bin/system-info.md b/bin/system-info.md index 327abf05ad77185facb381226a29b18aa1ca62ab..94e04b7faa8867e3f542e8268bd7a93c60e297f5 100644 --- a/bin/system-info.md +++ b/bin/system-info.md @@ -354,6 +354,32 @@ keys: : A boolean value indicating whether a simple executable for this architecture was run successfully. + **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): + + **path** + : The path required to run Steam binaries on this architecture, + for example `/lib64/ld-linux-x86-64.so.2` on x86_64 + + **resolved** + : The result of resolving symbolic links in **path** if possible, + for example `/lib/x86_64-linux-gnu/ld-2.28.so` on + Debian 10 x86_64 + + If the runtime linker is detected to be missing or unusable, + details of the error appear here: + + **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. + **library-issues-summary** : A summary of issues found when loading the expected libraries, as an array of strings. If empty, no problems were found. diff --git a/steam-runtime-tools/architecture-internal.h b/steam-runtime-tools/architecture-internal.h index 8461ab92408f1553e60ebeee3f27f06e1068f741..e1fb688cbe51c74be76b90c8a1e1aef2d41ae86f 100644 --- a/steam-runtime-tools/architecture-internal.h +++ b/steam-runtime-tools/architecture-internal.h @@ -29,6 +29,14 @@ #include <glib.h> #include <json-glib/json-glib.h> +typedef struct +{ + const char *multiarch_tuple; + const char *interoperable_runtime_linker; +} SrtKnownArchitecture; + +G_GNUC_INTERNAL const SrtKnownArchitecture *_srt_architecture_get_known (void); + G_GNUC_INTERNAL gboolean _srt_architecture_can_run (gchar **envp, const char *helpers_path, const char *multiarch); diff --git a/steam-runtime-tools/architecture.c b/steam-runtime-tools/architecture.c index 5f278b147c09aa49f24887fe5e2a9c0534db3c38..e4a00292fc0a976fd117ae9e02e301100f7fc589 100644 --- a/steam-runtime-tools/architecture.c +++ b/steam-runtime-tools/architecture.c @@ -44,6 +44,38 @@ * operating system. */ +G_DEFINE_QUARK (srt-architecture-error-quark, srt_architecture_error) + +static const SrtKnownArchitecture known_architectures[] = +{ + { + .multiarch_tuple = SRT_ABI_X86_64, + .interoperable_runtime_linker = "/lib64/ld-linux-x86-64.so.2", + }, + + { + .multiarch_tuple = SRT_ABI_I386, + .interoperable_runtime_linker = "/lib/ld-linux.so.2", + }, + + { + .multiarch_tuple = "x86_64-linux-gnux32", + .interoperable_runtime_linker = "/libx32/ld-linux-x32.so.2", + }, + + { NULL } +}; + +/* + * Returns: A table of known architectures, terminated by one + * with @multiarch_tuple set to %NULL. + */ +const SrtKnownArchitecture * +_srt_architecture_get_known (void) +{ + return &known_architectures[0]; +} + gboolean _srt_architecture_can_run (gchar **envp, const char *helpers_path, @@ -164,6 +196,35 @@ srt_architecture_can_run_x86_64 (void) NULL, SRT_ABI_X86_64); } +/** + * srt_architecture_get_expected_runtime_linker: + * @multiarch_tuple: A multiarch tuple defining an ABI, as printed + * by `gcc -print-multiarch` in the Steam Runtime + * + * Return the interoperable path to the runtime linker `ld.so(8)`, + * if known. For example, for x86_64, this returns + * `/lib64/ld-linux-x86-64.so.2`. + * + * Returns: (type filename) (transfer none): An absolute path, + * or %NULL if not known + */ +const char * +srt_architecture_get_expected_runtime_linker (const char *multiarch_tuple) +{ + gsize i; + + g_return_val_if_fail (multiarch_tuple != NULL, NULL); + + for (i = 0; known_architectures[i].multiarch_tuple != NULL; i++) + { + if (strcmp (multiarch_tuple, + known_architectures[i].multiarch_tuple) == 0) + return known_architectures[i].interoperable_runtime_linker; + } + + return NULL; +} + /** * _srt_architecture_can_run_from_report: * @json_obj: (not nullable): A JSON Object used to search for "can-run" diff --git a/steam-runtime-tools/architecture.h b/steam-runtime-tools/architecture.h index 178d3567f11693f13bb1c6f835dfe28e97951351..5e6bfa87aad831f1b58776c31d9806176820c81b 100644 --- a/steam-runtime-tools/architecture.h +++ b/steam-runtime-tools/architecture.h @@ -53,3 +53,34 @@ _SRT_PUBLIC gboolean srt_architecture_can_run_i386 (void); _SRT_PUBLIC gboolean srt_architecture_can_run_x86_64 (void); + +/** + * SrtArchitectureError: + * @SRT_ARCHITECTURE_ERROR_FAILED: Generic error + * @SRT_ARCHITECTURE_ERROR_INTERNAL_ERROR: An internal error occurred + * @SRT_ARCHITECTURE_ERROR_NO_INFORMATION: It is unknown whether the + * given architecture, ld.so, etc. is available or not, for example + * because the interoperable ld.so path for the architecture is unknown, + * or because SrtSystemInfo is reading a JSON report that does not + * contain this information + * + * Errors raised when checking facts about an architecture. + * + * Errors in the #GIOErrorEnum domain can also be raised: for example, + * if srt_system_info_check_runtime_linker() raises %G_IO_ERROR_NOT_FOUND, + * it means the interoperable path for ld.so does not exist. + */ +typedef enum +{ + SRT_ARCHITECTURE_ERROR_FAILED = 0, + SRT_ARCHITECTURE_ERROR_INTERNAL_ERROR, + SRT_ARCHITECTURE_ERROR_NO_INFORMATION, +} SrtArchitectureError; + +_SRT_PUBLIC +GQuark srt_architecture_error_quark (void); + +#define SRT_ARCHITECTURE_ERROR (srt_architecture_error_quark ()) + +_SRT_PUBLIC +const char *srt_architecture_get_expected_runtime_linker (const char *multiarch_tuple); diff --git a/steam-runtime-tools/system-info.c b/steam-runtime-tools/system-info.c index c966f37b5862b24f08328fa438469b47fdcec907..a90d0294487f8e862411d1e0c0c4de9658718cd4 100644 --- a/steam-runtime-tools/system-info.c +++ b/steam-runtime-tools/system-info.c @@ -251,6 +251,9 @@ typedef struct typedef struct { GQuark multiarch_tuple; + const SrtKnownArchitecture *known_architecture; + gchar *runtime_linker_resolved; + GError *runtime_linker_error; Tristate can_run; GHashTable *cached_results; SrtLibraryIssues cached_combined_issues; @@ -267,6 +270,7 @@ static Abi * ensure_abi_unless_immutable (SrtSystemInfo *self, const char *multiarch_tuple) { + const SrtKnownArchitecture *iter; GQuark quark; guint i; Abi *abi = NULL; @@ -286,6 +290,18 @@ ensure_abi_unless_immutable (SrtSystemInfo *self, abi = g_slice_new0 (Abi); abi->multiarch_tuple = quark; + + for (iter = _srt_architecture_get_known (); + iter->multiarch_tuple != NULL; + iter++) + { + if (strcmp (iter->multiarch_tuple, multiarch_tuple) == 0) + { + abi->known_architecture = iter; + break; + } + } + 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; @@ -319,6 +335,8 @@ abi_free (gpointer self) for (i = 0; i < G_N_ELEMENTS (abi->graphics_modules); i++) g_list_free_full (abi->graphics_modules[i].modules, g_object_unref); + g_free (abi->runtime_linker_resolved); + g_clear_error (&abi->runtime_linker_error); g_slice_free (Abi, self); } @@ -595,6 +613,79 @@ static gchar ** _srt_system_info_get_pinned_libs_from_report (JsonObject *json_o const gchar *which, gchar ***messages); +static void +get_runtime_linker_from_report (SrtSystemInfo *self, + Abi *abi, + JsonObject *json_arch_obj) +{ + JsonObject *runtime_linker_obj; + const char *path; + const char *resolved; + + g_return_if_fail (abi->runtime_linker_resolved == NULL); + g_return_if_fail (abi->runtime_linker_error == NULL); + + if (abi->known_architecture == NULL) + return; + + if (abi->known_architecture->interoperable_runtime_linker == NULL) + return; + + if (!json_object_has_member (json_arch_obj, "runtime-linker")) + return; + + runtime_linker_obj = json_object_get_object_member (json_arch_obj, "runtime-linker"); + + path = json_object_get_string_member_with_default (runtime_linker_obj, + "path", NULL); + resolved = json_object_get_string_member_with_default (runtime_linker_obj, + "resolved", NULL); + + if (json_object_has_member (runtime_linker_obj, "error")) + { + GQuark error_domain; + int error_code; + const char *error_message; + + error_domain = g_quark_from_string (json_object_get_string_member_with_default (runtime_linker_obj, "error-domain", NULL)); + error_code = json_object_get_int_member_with_default (runtime_linker_obj, + "error-code", + -1); + error_message = json_object_get_string_member_with_default (runtime_linker_obj, + "error", + NULL); + + if (error_domain == 0) + { + error_domain = SRT_ARCHITECTURE_ERROR; + error_code = SRT_ARCHITECTURE_ERROR_INTERNAL_ERROR; + } + + if (error_message == NULL) + error_message = "Unknown error"; + + g_set_error_literal (&abi->runtime_linker_error, error_domain, error_code, + error_message); + } + else if (path == NULL) + { + /* no information */ + return; + } + else if (g_strcmp0 (path, abi->known_architecture->interoperable_runtime_linker) != 0) + { + g_set_error (&abi->runtime_linker_error, SRT_ARCHITECTURE_ERROR, + SRT_ARCHITECTURE_ERROR_INTERNAL_ERROR, + "Expected \"%s\" in report, but got \"%s\"", + abi->known_architecture->interoperable_runtime_linker, + path); + } + else + { + abi->runtime_linker_resolved = g_strdup (resolved); + } +} + /** * srt_system_info_new_from_json: * @path: (not nullable) (type filename): Path to a JSON report @@ -706,6 +797,8 @@ srt_system_info_new_from_json (const char *path, abi->libraries_cache_available = TRUE; abi->cached_combined_issues = _srt_library_get_issues_from_report (json_arch_obj); + get_runtime_linker_from_report (info, abi, json_arch_obj); + _srt_graphics_get_from_report (json_arch_obj, l->data, &abi->cached_graphics_results); @@ -3833,3 +3926,137 @@ srt_system_info_get_xdg_portal_issues (SrtSystemInfo *self, return srt_xdg_portal_get_issues (self->xdg_portal_data); } + +static void +ensure_runtime_linker (SrtSystemInfo *self, + Abi *abi) +{ + g_autofree gchar *tmp = NULL; + int fd; + + if (abi->runtime_linker_resolved != NULL) + return; + + if (abi->runtime_linker_error != NULL) + return; + + if (abi->known_architecture == NULL) + return; + + if (abi->known_architecture->interoperable_runtime_linker == NULL) + { + g_set_error (&abi->runtime_linker_error, SRT_ARCHITECTURE_ERROR, + SRT_ARCHITECTURE_ERROR_NO_INFORMATION, + "Interoperable runtime_linker for \"%s\" not known", + abi->known_architecture->multiarch_tuple); + return; + } + + if (self->sysroot_fd < 0) + { + g_set_error (&abi->runtime_linker_error, SRT_ARCHITECTURE_ERROR, + SRT_ARCHITECTURE_ERROR_INTERNAL_ERROR, + "Unable to open sysroot"); + return; + } + + fd = _srt_resolve_in_sysroot (self->sysroot_fd, + abi->known_architecture->interoperable_runtime_linker, + SRT_RESOLVE_FLAGS_READABLE, + &tmp, + &abi->runtime_linker_error); + + if (fd >= 0) + { + abi->runtime_linker_resolved = g_strconcat ("/", tmp, NULL); + close (fd); + } +} + +/** + * srt_system_info_check_runtime_linker: + * @self: The #SrtSystemInfo object + * @multiarch_tuple: A multiarch tuple defining an ABI, as printed + * by `gcc -print-multiarch` in the Steam Runtime + * @resolved: (out) (type filename) (transfer full) (optional): Used + * to return the path to the runtime linker after resolving all + * symbolic links. Free with g_free(); may be %NULL to ignore. + * @error: Used to raise an error on failure. + * + * Check whether the runtime linker `ld.so(8)` for the ABI described + * by @multiarch_tuple, as returned by + * srt_architecture_get_expected_runtime_linker(), is available. + * + * If `ld.so` is unavailable, return %FALSE with @error set. + * + * If not enough information is available to determine whether `ld.so` + * is available, raise %SRT_ARCHITECTURE_ERROR_NO_INFORMATION. + * + * Returns: %TRUE if `ld.so(8)` is available at the interoperable path + * for the given architecture + */ +gboolean +srt_system_info_check_runtime_linker (SrtSystemInfo *self, + const char *multiarch_tuple, + gchar **resolved, + GError **error) +{ + Abi *abi; + + g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), FALSE); + g_return_val_if_fail (multiarch_tuple != NULL, FALSE); + + abi = ensure_abi_unless_immutable (self, multiarch_tuple); + + if (abi == NULL) + { + g_set_error (error, SRT_ARCHITECTURE_ERROR, + SRT_ARCHITECTURE_ERROR_NO_INFORMATION, + "ABI \"%s\" not included in report", + multiarch_tuple); + return FALSE; + } + + if (!self->immutable_values) + ensure_runtime_linker (self, abi); + + if (abi->known_architecture == NULL) + { + g_set_error (error, SRT_ARCHITECTURE_ERROR, + SRT_ARCHITECTURE_ERROR_NO_INFORMATION, + "Interoperable runtime linker for \"%s\" not known", + multiarch_tuple); + return FALSE; + } + else if (abi->runtime_linker_resolved != NULL) + { + if (resolved != NULL) + *resolved = g_strdup (abi->runtime_linker_resolved); + + return TRUE; + } + else if (abi->runtime_linker_error != NULL) + { + if (error != NULL) + *error = g_error_copy (abi->runtime_linker_error); + + return FALSE; + } + else if (self->immutable_values) + { + g_set_error (error, SRT_ARCHITECTURE_ERROR, + SRT_ARCHITECTURE_ERROR_NO_INFORMATION, + "Runtime linker for \"%s\" not included in report", + multiarch_tuple); + return FALSE; + } + else + { + /* We shouldn't be able to get here */ + g_set_error (error, SRT_ARCHITECTURE_ERROR, + SRT_ARCHITECTURE_ERROR_INTERNAL_ERROR, + "Runtime linker for \"%s\" not checked", + multiarch_tuple); + g_return_val_if_reached (FALSE); + } +} diff --git a/steam-runtime-tools/system-info.h b/steam-runtime-tools/system-info.h index 3ce01a9b38b6aeb75041e1f0582f0ad51145cf6c..63f1b800899132d022f8cf5d5144e25d30e4896a 100644 --- a/steam-runtime-tools/system-info.h +++ b/steam-runtime-tools/system-info.h @@ -275,6 +275,12 @@ _SRT_PUBLIC SrtXdgPortalIssues srt_system_info_get_xdg_portal_issues (SrtSystemInfo *self, gchar **messages); +_SRT_PUBLIC +gboolean srt_system_info_check_runtime_linker (SrtSystemInfo *self, + const char *multiarch_tuple, + gchar **real_path, + GError **error); + #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtSystemInfo, g_object_unref) #endif diff --git a/tests/architecture.c b/tests/architecture.c index 595ac8dd2f1f75ac2d3696f5effd2f857f9dc396..6aa76b490b281fa45a6999faa573b610a1ed48d2 100644 --- a/tests/architecture.c +++ b/tests/architecture.c @@ -67,6 +67,13 @@ test_architecture (Fixture *f, #if defined(__i386__) g_assert_true (srt_architecture_can_run_i386 ()); #endif + + g_assert_cmpstr (srt_architecture_get_expected_runtime_linker (SRT_ABI_X86_64), + ==, "/lib64/ld-linux-x86-64.so.2"); + g_assert_cmpstr (srt_architecture_get_expected_runtime_linker (SRT_ABI_I386), + ==, "/lib/ld-linux.so.2"); + g_assert_cmpstr (srt_architecture_get_expected_runtime_linker ("potato-glados"), + ==, NULL); } int diff --git a/tests/generate-sysroots.py b/tests/generate-sysroots.py index c6937dcc1020ed44bd23d33d8b034b6ff9025d87..e143ab620f3ee84bc810f341d41b4b4dff329d5c 100755 --- a/tests/generate-sysroots.py +++ b/tests/generate-sysroots.py @@ -107,6 +107,7 @@ ubuntu16/usr/lib/mock-ubuntu-64-bit/vdpau os.makedirs(name, mode=0o755, exist_ok=True) for name in ''' +debian10/usr/lib/i386-linux-gnu/ld.so debian10/usr/lib/i386-linux-gnu/dri/i965_dri.so debian10/usr/lib/i386-linux-gnu/dri/r300_dri.so debian10/usr/lib/i386-linux-gnu/dri/r600_drv_video.so @@ -116,6 +117,7 @@ debian10/usr/lib/i386-linux-gnu/libva.so.2 debian10/usr/lib/i386-linux-gnu/libvdpau.so.1 debian10/usr/lib/i386-linux-gnu/vdpau/libvdpau_r600.so debian10/usr/lib/i386-linux-gnu/vdpau/libvdpau_radeonsi.so.1.0.0 +debian10/usr/lib/x86_64-linux-gnu/ld.so debian10/usr/lib/x86_64-linux-gnu/dri/i965_dri.so debian10/usr/lib/x86_64-linux-gnu/dri/r600_dri.so debian10/usr/lib/x86_64-linux-gnu/dri/r600_drv_video.so @@ -188,6 +190,7 @@ steamrt/overrides/lib/i386-linux-gnu/libGLX_nvidia.so.0 steamrt/run/pressure-vessel/.exists steamrt-overrides-issues/overrides/bin/.keep steamrt-overrides-issues/overrides/lib/i386-linux-gnu/.keep +ubuntu16/lib64/ld-linux-x86-64.so.2 ubuntu16/usr/lib/dri/radeonsi_dri.so ubuntu16/usr/lib/mock-ubuntu-64-bit/dri/i965_dri.so ubuntu16/usr/lib/mock-ubuntu-64-bit/dri/radeon_dri.so @@ -201,6 +204,10 @@ ubuntu16/usr/lib/mock-ubuntu-64-bit/vdpau/libvdpau_radeonsi.so.1.0.0 open(name, 'w').close() for name, target in { + 'debian10/lib/ld-linux.so.2': + '/usr/lib/i386-linux-gnu/ld.so', + 'debian10/lib64/ld-linux-x86-64.so.2': + '../usr/lib/x86_64-linux-gnu/ld.so', 'debian10/usr/lib/i386-linux-gnu/vdpau/libvdpau_radeonsi.so': 'libvdpau_radeonsi.so.1.0.0', 'debian10/usr/lib/i386-linux-gnu/vdpau/libvdpau_radeonsi.so.1': diff --git a/tests/json-report/full-good-report.json b/tests/json-report/full-good-report.json index 371ac3c4be5aa9c4cb2e9334bb7f5af28ae637ef..d926f4b07f7010e7a60a1a95699645f2d3745360 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, + "runtime-linker": { + "path" : "/lib/ld-linux.so.2", + "error-domain" : "g-io-error-quark", + "error-code" : 1, + "error" : "No such file or directory" + }, "graphics-details" : { "x11/vulkan" : { "messages" : "ERROR: [Loader Message] Code 0 : /usr/lib/amdvlk64.so: wrong ELF class: ELFCLASS64\nCannot create Vulkan instance.\n", @@ -120,6 +126,10 @@ }, "x86_64-linux-gnu" : { "can-run" : true, + "runtime-linker": { + "path" : "/lib64/ld-linux-x86-64.so.2", + "resolved" : "/lib/x86_64-linux-gnu/ld-2.31.so" + }, "library-issues-summary" : [ ], "graphics-details" : { diff --git a/tests/json-report/partial-report-2.json b/tests/json-report/partial-report-2.json index b882cd97c2088f6dbba002e746690ca8e72da185..20a77f242596442bde0588d948ba5be873585600 100644 --- a/tests/json-report/partial-report-2.json +++ b/tests/json-report/partial-report-2.json @@ -24,6 +24,10 @@ "architectures" : { "x86_64-linux-gnu" : { "can-run" : true, + "runtime-linker": { + "path": "/foobar", + "resolved": "/foo/bar" + }, "graphics-details" : { "x11/vulkan" : { } diff --git a/tests/json-report/partial-report.json b/tests/json-report/partial-report.json index e769bee394473946fd79ed54a3d9acf6f1d24846..d843f8237787346bd7a777c93c926c8935d11604 100644 --- a/tests/json-report/partial-report.json +++ b/tests/json-report/partial-report.json @@ -13,6 +13,9 @@ "architectures" : { "i386-linux-gnu" : { "can-run" : false, + "runtime-linker": { + "error": "We just don't know" + }, "graphics-details" : { "x11/vdpau" : { "renderer" : "G3DVL VDPAU Driver Shared Library version 1.0\n", diff --git a/tests/system-info.c b/tests/system-info.c index 68fc9cf9d622592f3fd0f720c16397452ee9fbd4..63597d177eb4b2fb898b0237996ac073d46d9d67 100644 --- a/tests/system-info.c +++ b/tests/system-info.c @@ -2408,10 +2408,20 @@ typedef struct gboolean is_available; } GraphicsTest; +typedef struct +{ + const char *path; + const char *resolved; + const char *error_domain; + int error_code; + const char *error_message; +} RuntimeLinkerTest; + typedef struct { gboolean can_run; SrtLibraryIssues issues; + RuntimeLinkerTest runtime_linker; DriverTest dri_drivers[5]; DriverTest va_api_drivers[5]; DriverTest vdpau_drivers[5]; @@ -2556,6 +2566,13 @@ static const JsonTest json_test[] = { { .can_run = FALSE, + .runtime_linker = + { + .path = "/lib/ld-linux.so.2", + .error_domain = "g-io-error-quark", + .error_code = G_IO_ERROR_NOT_FOUND, + .error_message = "No such file or directory", + }, .issues = SRT_LIBRARY_ISSUES_UNKNOWN, .graphics = { @@ -2641,6 +2658,11 @@ static const JsonTest json_test[] = { .can_run = TRUE, + .runtime_linker = + { + .path = "/lib64/ld-linux-x86-64.so.2", + .resolved = "/lib/x86_64-linux-gnu/ld-2.31.so", + }, .graphics = { { @@ -2874,6 +2896,15 @@ static const JsonTest json_test[] = { { .can_run = FALSE, + .runtime_linker = + { + .path = "/lib/ld-linux.so.2", + /* Error domain and code are missing from the report, so we + * make something up */ + .error_domain = "srt-architecture-error-quark", + .error_code = SRT_ARCHITECTURE_ERROR_INTERNAL_ERROR, + .error_message = "We just don't know", + }, .issues = SRT_LIBRARY_ISSUES_UNKNOWN, .graphics = { @@ -2888,6 +2919,13 @@ static const JsonTest json_test[] = { .can_run = TRUE, .issues = SRT_LIBRARY_ISSUES_UNKNOWN, + .runtime_linker = + { + .path = "/lib64/ld-linux-x86-64.so.2", + .error_domain = "srt-architecture-error-quark", + .error_code = SRT_ARCHITECTURE_ERROR_NO_INFORMATION, + .error_message = "Runtime linker for \"x86_64-linux-gnu\" not included in report", + }, }, }, .locale_issues = SRT_LOCALE_ISSUES_UNKNOWN, @@ -2958,10 +2996,26 @@ static const JsonTest json_test[] = { /* i386 */ .issues = SRT_LIBRARY_ISSUES_CANNOT_LOAD, + .runtime_linker = + { + .path = "/lib/ld-linux.so.2", + /* i386 is completely missing from this report */ + .error_domain = "srt-architecture-error-quark", + .error_code = SRT_ARCHITECTURE_ERROR_NO_INFORMATION, + .error_message = "ABI \"i386-linux-gnu\" not included in report", + }, }, { .can_run = TRUE, .issues = SRT_LIBRARY_ISSUES_UNKNOWN, + .runtime_linker = + { + .path = "/lib64/ld-linux-x86-64.so.2", + /* We don't have the expected ld.so in the report */ + .error_domain = "srt-architecture-error-quark", + .error_code = SRT_ARCHITECTURE_ERROR_INTERNAL_ERROR, + .error_message = "Expected \"/lib64/ld-linux-x86-64.so.2\" in report, but got \"/foobar\"", + }, }, }, .locale_issues = SRT_LOCALE_ISSUES_UNKNOWN, @@ -3001,9 +3055,23 @@ static const JsonTest json_test[] = .architecture = { { + .runtime_linker = + { + .path = "/lib/ld-linux.so.2", + .error_domain = "srt-architecture-error-quark", + .error_code = SRT_ARCHITECTURE_ERROR_NO_INFORMATION, + .error_message = "ABI \"i386-linux-gnu\" not included in report", + }, .issues = SRT_LIBRARY_ISSUES_CANNOT_LOAD, }, { + .runtime_linker = + { + .path = "/lib64/ld-linux-x86-64.so.2", + .error_domain = "srt-architecture-error-quark", + .error_code = SRT_ARCHITECTURE_ERROR_NO_INFORMATION, + .error_message = "ABI \"x86_64-linux-gnu\" not included in report", + }, .issues = SRT_LIBRARY_ISSUES_CANNOT_LOAD, }, }, @@ -3063,6 +3131,49 @@ static const JsonTest json_test[] = }, /* End Newer JSON report */ }; +static void +assert_expected_runtime_linker (SrtSystemInfo *info, + const char *multiarch_tuple, + const RuntimeLinkerTest *rtld) +{ + g_autoptr(GError) error = NULL; + g_autofree gchar *resolved = NULL; + const char *expected; + gboolean ok; + + /* shorthand notation for having no expectations */ + if (rtld->path == NULL) + return; + + expected = srt_architecture_get_expected_runtime_linker (multiarch_tuple); + ok = srt_system_info_check_runtime_linker (info, multiarch_tuple, + &resolved, &error); + + g_assert_cmpstr (rtld->path, ==, expected); + + if (rtld->resolved != NULL) + { + g_assert (rtld->error_domain == NULL); + g_assert (rtld->error_code == 0); + g_assert (rtld->error_message == NULL); + + g_assert_no_error (error); + g_assert_true (ok); + g_assert_cmpstr (resolved, ==, rtld->resolved); + } + else + { + g_assert (rtld->error_domain != NULL); + g_assert (rtld->error_message != NULL); + + g_assert_error (error, g_quark_from_string (rtld->error_domain), + rtld->error_code); + g_assert_cmpstr (error->message, ==, rtld->error_message); + g_assert_false (ok); + g_assert_cmpstr (resolved, ==, NULL); + } +} + static void json_parsing (Fixture *f, gconstpointer context) @@ -3171,6 +3282,10 @@ json_parsing (Fixture *f, g_assert_cmpint (this_arch.issues, ==, srt_system_info_check_libraries (info, multiarch_tuples[j], NULL)); + assert_expected_runtime_linker (info, + multiarch_tuples[j], + &this_arch.runtime_linker); + for (jj = 0; this_arch.graphics[jj].is_available; jj++) { SrtGraphics *graphics = NULL; @@ -3442,6 +3557,112 @@ json_parsing (Fixture *f, } } +static void +architecture_symlinks (Fixture *f, + gconstpointer context) +{ + g_autoptr(SrtSystemInfo) info; + g_autofree gchar *sysroot = NULL; + gboolean ret; + + sysroot = g_build_filename (f->sysroots, "debian10", NULL); + + info = srt_system_info_new (NULL); + srt_system_info_set_sysroot (info, sysroot); + + /* In the mock Debian 10 sysroot created by tests/generate-sysroots.py, + * the well-known linker paths are symbolic links, much like they are + * on a real Debian system. */ + + { + g_autoptr(GError) error = NULL; + g_autofree gchar *resolved = NULL; + + ret = srt_system_info_check_runtime_linker (info, SRT_ABI_X86_64, + &resolved, &error); + g_assert_no_error (error); + g_assert_true (ret); + g_assert_cmpstr (resolved, ==, "/usr/lib/x86_64-linux-gnu/ld.so"); + } + + { + g_autoptr(GError) error = NULL; + g_autofree gchar *resolved = NULL; + + ret = srt_system_info_check_runtime_linker (info, SRT_ABI_I386, + &resolved, &error); + g_assert_no_error (error); + g_assert_true (ret); + g_assert_cmpstr (resolved, ==, "/usr/lib/i386-linux-gnu/ld.so"); + } + + /* The sysroot doesn't include x32 support. */ + { + g_autoptr(GError) error = NULL; + g_autofree gchar *resolved = NULL; + + ret = srt_system_info_check_runtime_linker (info, "x86_64-linux-gnux32", + &resolved, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); + g_assert_false (ret); + g_assert_cmpstr (resolved, ==, NULL); + } + + { + g_autoptr(GError) error = NULL; + g_autofree gchar *resolved = NULL; + + ret = srt_system_info_check_runtime_linker (info, "hal9000-netbsd", + &resolved, &error); + /* We have no idea what the runtime linker would be, so we have + * no way to check for it */ + g_assert_error (error, SRT_ARCHITECTURE_ERROR, + SRT_ARCHITECTURE_ERROR_NO_INFORMATION); + g_assert_false (ret); + g_assert_cmpstr (resolved, ==, NULL); + } +} + +static void +architecture_notlinks (Fixture *f, + gconstpointer context) +{ + g_autoptr(SrtSystemInfo) info; + g_autofree gchar *sysroot = NULL; + gboolean ret; + + sysroot = g_build_filename (f->sysroots, "ubuntu16", NULL); + + info = srt_system_info_new (NULL); + srt_system_info_set_sysroot (info, sysroot); + + /* In the mock Ubuntu 16.04 sysroot created by tests/generate-sysroots.py, + * the well-known runtime linker for x86_64 is a real file (unlike + * real Ubuntu systems) and the runtime linker for i386 is missing. */ + + { + g_autoptr(GError) error = NULL; + g_autofree gchar *resolved = NULL; + + ret = srt_system_info_check_runtime_linker (info, "x86_64-linux-gnu", + &resolved, &error); + g_assert_no_error (error); + g_assert_true (ret); + g_assert_cmpstr (resolved, ==, "/lib64/ld-linux-x86-64.so.2"); + } + + { + g_autoptr(GError) error = NULL; + g_autofree gchar *resolved = NULL; + + ret = srt_system_info_check_runtime_linker (info, "i386-linux-gnu", + &resolved, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); + g_assert_false (ret); + g_assert_cmpstr (resolved, ==, NULL); + } +} + int main (int argc, char **argv) @@ -3531,6 +3752,11 @@ main (int argc, g_test_add ("/system-info/json_parsing", Fixture, NULL, setup, json_parsing, teardown); + g_test_add ("/system-info/architecture/symlinks", Fixture, NULL, + setup, architecture_symlinks, teardown); + g_test_add ("/system-info/architecture/notlinks", Fixture, NULL, + setup, architecture_notlinks, teardown); + status = g_test_run (); if (!_srt_global_teardown_private_xdg_dirs ())