diff --git a/bin/system-info.c b/bin/system-info.c index 56b1cb911dd81ab5621962f01581d21fa203b054..7a11d2267958a75a7fce086bfb0d78d31aefd399 100644 --- a/bin/system-info.c +++ b/bin/system-info.c @@ -632,6 +632,46 @@ jsonify_os_release (JsonBuilder *builder, json_builder_end_object (builder); } +static void +jsonify_container (JsonBuilder *builder, + SrtSystemInfo *info) +{ + SrtContainerType type = srt_system_info_get_container_type (info); + gchar *host_directory = srt_system_info_dup_container_host_directory (info); + gchar **env = NULL; + SrtSystemInfo *host = NULL; + + json_builder_set_member_name (builder, "container"); + json_builder_begin_object (builder); + { + json_builder_set_member_name (builder, "type"); + jsonify_enum (builder, SRT_TYPE_CONTAINER_TYPE, type); + + if (type != SRT_CONTAINER_TYPE_NONE) + { + json_builder_set_member_name (builder, "host"); + json_builder_begin_object (builder); + { + json_builder_set_member_name (builder, "path"); + json_builder_add_string_value (builder, host_directory); + + if (host_directory != NULL) + { + host = srt_system_info_new (NULL); + srt_system_info_set_sysroot (host, host_directory); + jsonify_os_release (builder, host); + } + } + json_builder_end_object (builder); + } + } + json_builder_end_object (builder); + + g_free (host_directory); + g_clear_object (&host); + g_strfreev (env); +} + static const char * const locales[] = { "", @@ -847,6 +887,7 @@ main (int argc, json_builder_end_object (builder); jsonify_os_release (builder, info); + jsonify_container (builder, info); json_builder_set_member_name (builder, "driver_environment"); json_builder_begin_array (builder); diff --git a/bin/system-info.md b/bin/system-info.md index e3fe6a543443246c91ec3ea8867b76deeb6c783e..6c342e024727f92aef4b80dd8fed38e7ef0a8147 100644 --- a/bin/system-info.md +++ b/bin/system-info.md @@ -292,6 +292,29 @@ keys: This is the **VARIANT** from **os-release**(5). +**container** +: Details of the container we are running in, if any. + The keys are strings: + + **type** + : A short machine-readable string identifying the container type, + such as **flatpak**, **pressure-vessel**, **docker**, + **unknown** (if we appear to be in a container but the type is + unknown), or **none** (if we do not appear to be in a container). + + **host** + : An object describing the host system. The keys are strings: + + **path** + : Absolute path to a directory where important files from the + host system can be found, or **null** if unavailable. + In Flatpak and pressure-vessel, this will be either **/run/host** + or **null*. + + **os-release** + : The same as the **os-release** described above, but describing + the host system rather than the container. + **driver_environment** : An array of strings, in the form "NAME=VALUE", with the well-known driver environment variables that are currently set. diff --git a/steam-runtime-tools/system-info.c b/steam-runtime-tools/system-info.c index 63dc2c409dc9c7c1d47e0b388f3b1d82eea0b8d9..8693849b8c74be67c22640f3fda23f4675911125 100644 --- a/steam-runtime-tools/system-info.c +++ b/steam-runtime-tools/system-info.c @@ -150,6 +150,12 @@ struct _SrtSystemInfo gchar **messages_64; gboolean have_data; } pinned_libs; + struct + { + SrtContainerType type; + gchar *host_directory; + gboolean have_data; + } container; SrtOsRelease os_release; SrtTestFlags test_flags; Tristate can_write_uinput; @@ -300,6 +306,8 @@ srt_system_info_init (SrtSystemInfo *self) self->abis = g_ptr_array_new_full (2, abi_free); _srt_os_release_init (&self->os_release); + + self->container.type = SRT_CONTAINER_TYPE_UNKNOWN; } static void @@ -342,6 +350,17 @@ srt_system_info_set_property (GObject *object, } } +/* + * Forget any cached information about container information. + */ +static void +forget_container_info (SrtSystemInfo *self) +{ + g_clear_pointer (&self->container.host_directory, g_free); + self->container.type = SRT_CONTAINER_TYPE_UNKNOWN; + self->container.have_data = FALSE; +} + /* * Forget any cached information about locales. */ @@ -431,6 +450,7 @@ srt_system_info_finalize (GObject *object) { SrtSystemInfo *self = SRT_SYSTEM_INFO (object); + forget_container_info (self); forget_icds (self); forget_locales (self); forget_os (self); @@ -1592,6 +1612,7 @@ srt_system_info_set_sysroot (SrtSystemInfo *self, { g_return_if_fail (SRT_IS_SYSTEM_INFO (self)); + forget_container_info (self); forget_graphics_modules (self); forget_libraries (self); forget_graphics_results (self); @@ -2742,3 +2763,188 @@ srt_system_info_list_driver_environment (SrtSystemInfo *self) else return g_strdupv (self->cached_driver_environment); } + +typedef struct +{ + SrtContainerType type; + const char *name; +} ContainerTypeName; + +static const ContainerTypeName container_types[] = +{ + { SRT_CONTAINER_TYPE_DOCKER, "docker" } +}; + +static SrtContainerType +container_type_from_name (const char *name) +{ + gsize i; + + for (i = 0; i < G_N_ELEMENTS (container_types); i++) + { + const ContainerTypeName *entry = &container_types[i]; + + if (strcmp (entry->name, name) == 0) + return entry->type; + } + + return SRT_CONTAINER_TYPE_UNKNOWN; +} + +static void +ensure_container_info (SrtSystemInfo *self) +{ + const char *sysroot = NULL; + gchar *contents = NULL; + gchar *filename = NULL; + + if (self->container.have_data) + return; + + g_assert (self->container.host_directory == NULL); + g_assert (self->container.type == SRT_CONTAINER_TYPE_UNKNOWN); + + sysroot = self->sysroot; + + if (sysroot == NULL) + sysroot = "/"; + + g_debug ("Finding container info in sysroot %s...", sysroot); + + filename = g_build_filename (sysroot, "run", "systemd", "container", NULL); + + if (g_file_get_contents (filename, &contents, NULL, NULL)) + { + g_strchomp (contents); + self->container.type = container_type_from_name (contents); + g_debug ("Type %d based on %s", self->container.type, filename); + goto out; + } + + g_clear_pointer (&filename, g_free); + filename = g_build_filename (sysroot, ".flatpak-info", NULL); + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) + { + self->container.type = SRT_CONTAINER_TYPE_FLATPAK; + g_debug ("Flatpak based on %s", filename); + goto out; + } + + g_clear_pointer (&filename, g_free); + filename = g_build_filename (sysroot, "run", "pressure-vessel", NULL); + + if (g_file_test (filename, G_FILE_TEST_IS_DIR)) + { + self->container.type = SRT_CONTAINER_TYPE_PRESSURE_VESSEL; + g_debug ("pressure-vessel based on %s", filename); + goto out; + } + + g_clear_pointer (&filename, g_free); + filename = g_build_filename (sysroot, ".dockerenv", NULL); + + if (g_file_test (filename, G_FILE_TEST_EXISTS)) + { + self->container.type = SRT_CONTAINER_TYPE_DOCKER; + g_debug ("Docker based on %s", filename); + goto out; + } + + g_clear_pointer (&filename, g_free); + filename = g_build_filename (sysroot, "proc", "1", "cgroup", NULL); + + if (g_file_get_contents (filename, &contents, NULL, NULL)) + { + if (strstr (contents, "/docker/") != NULL) + self->container.type = SRT_CONTAINER_TYPE_DOCKER; + + if (self->container.type != SRT_CONTAINER_TYPE_UNKNOWN) + { + g_debug ("Type %d based on %s", self->container.type, filename); + goto out; + } + } + + g_clear_pointer (&filename, g_free); + filename = g_build_filename (sysroot, "run", "host", NULL); + + if (g_file_test (filename, G_FILE_TEST_IS_DIR)) + { + g_debug ("Unknown container technology based on %s", filename); + self->container.type = SRT_CONTAINER_TYPE_UNKNOWN; + self->container.host_directory = g_steal_pointer (&filename); + goto out; + } + + g_clear_pointer (&filename, g_free); + + /* We haven't found any particular evidence of being in a container */ + g_debug ("Probably not a container"); + self->container.type = SRT_CONTAINER_TYPE_NONE; + +out: + g_free (contents); + g_free (filename); + + switch (self->container.type) + { + case SRT_CONTAINER_TYPE_FLATPAK: + case SRT_CONTAINER_TYPE_PRESSURE_VESSEL: + self->container.host_directory = g_build_filename (sysroot, "run", + "host", NULL); + break; + + case SRT_CONTAINER_TYPE_DOCKER: + case SRT_CONTAINER_TYPE_UNKNOWN: + case SRT_CONTAINER_TYPE_NONE: + default: + break; + } + + self->container.have_data = TRUE; +} + +/** + * srt_system_info_get_container_type: + * @self: The #SrtSystemInfo object + * + * If the program appears to be running in a container, return what sort + * of container it is. + * + * Returns: A recognised container type, or %SRT_CONTAINER_TYPE_NONE + * if a container cannot be detected, or %SRT_CONTAINER_TYPE_UNKNOWN + * if unsure. + */ +SrtContainerType +srt_system_info_get_container_type (SrtSystemInfo *self) +{ + g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), SRT_CONTAINER_TYPE_UNKNOWN); + + ensure_container_info (self); + return self->container.type; +} + +/** + * srt_system_info_dup_container_host_directory: + * + * If the program appears to be running in a container, return the + * directory where host files can be found. For example, if this function + * returns `/run/host`, it might be possible to load the host system's + * `/usr/lib/os-release` by reading `/run/host/usr/lib/os-release`. + * + * The returned directory is usually not complete. For example, + * in a Flatpak app, `/run/host` will sometimes contain the host system's + * `/etc` and `/usr`, but only if suitable permissions flags are set. + * + * Returns: A path from which at least some host-system files can be + * loaded, typically `/run/host`, or %NULL if unknown or unavailable + */ +gchar * +srt_system_info_dup_container_host_directory (SrtSystemInfo *self) +{ + g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), NULL); + + ensure_container_info (self); + return g_strdup (self->container.host_directory); +} diff --git a/steam-runtime-tools/system-info.h b/steam-runtime-tools/system-info.h index fe3c7da685c08fd665a6caa66763e6e59311e123..da5e80e43257f0eb9fc2316463c0c59f3e6eea7b 100644 --- a/steam-runtime-tools/system-info.h +++ b/steam-runtime-tools/system-info.h @@ -71,6 +71,26 @@ typedef enum SRT_DRIVER_FLAGS_NONE = 0 } SrtDriverFlags; +/** + * SrtContainerType: + * @SRT_CONTAINER_TYPE_UNKNOWN: Unknown container type + * @SRT_CONTAINER_TYPE_NONE: No container detected + * @SRT_CONTAINER_TYPE_FLATPAK: Running in a Flatpak app + * @SRT_CONTAINER_TYPE_PRESSURE_VESSEL: Running in a Steam Runtime container + * using pressure-vessel + * @SRT_CONTAINER_TYPE_DOCKER: Running in a Docker container + * + * A type of container. + */ +typedef enum +{ + SRT_CONTAINER_TYPE_NONE = 0, + SRT_CONTAINER_TYPE_FLATPAK, + SRT_CONTAINER_TYPE_PRESSURE_VESSEL, + SRT_CONTAINER_TYPE_DOCKER, + SRT_CONTAINER_TYPE_UNKNOWN = -1 +} SrtContainerType; + typedef struct _SrtSystemInfo SrtSystemInfo; typedef struct _SrtSystemInfoClass SrtSystemInfoClass; @@ -87,6 +107,9 @@ SrtSystemInfo *srt_system_info_new (const char *expectations); gboolean srt_system_info_can_run (SrtSystemInfo *self, const char *multiarch_tuple); +SrtContainerType srt_system_info_get_container_type (SrtSystemInfo *self); +gchar *srt_system_info_dup_container_host_directory (SrtSystemInfo *self); + gboolean srt_system_info_can_write_to_uinput (SrtSystemInfo *self); SrtLibraryIssues srt_system_info_check_libraries (SrtSystemInfo *self, const gchar *multiarch_tuple, diff --git a/tests/sysroots/debian-unstable/.dockerenv b/tests/sysroots/debian-unstable/.dockerenv new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/sysroots/debian10/run/systemd/container b/tests/sysroots/debian10/run/systemd/container new file mode 100644 index 0000000000000000000000000000000000000000..982793c32ee7f2df6107a721a7814fcf5ec39bbd --- /dev/null +++ b/tests/sysroots/debian10/run/systemd/container @@ -0,0 +1 @@ +whatever diff --git a/tests/sysroots/fedora/run/systemd/container b/tests/sysroots/fedora/run/systemd/container new file mode 100644 index 0000000000000000000000000000000000000000..bdb9670965e4db736d9bdab6edce7cf0d688bea9 --- /dev/null +++ b/tests/sysroots/fedora/run/systemd/container @@ -0,0 +1 @@ +docker diff --git a/tests/sysroots/flatpak-example/run/host/.exists b/tests/sysroots/flatpak-example/run/host/.exists new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/sysroots/invalid-os-release/run/host/.exists b/tests/sysroots/invalid-os-release/run/host/.exists new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/sysroots/steamrt-unofficial/proc/1/cgroup b/tests/sysroots/steamrt-unofficial/proc/1/cgroup new file mode 100644 index 0000000000000000000000000000000000000000..72dac858cf44f4c3a0b6e73276a9e6d8e770e767 --- /dev/null +++ b/tests/sysroots/steamrt-unofficial/proc/1/cgroup @@ -0,0 +1,12 @@ +11:perf_event:/docker/9999999999999999999999999999999999999999999999999999999999999999 +10:freezer:/docker/9999999999999999999999999999999999999999999999999999999999999999 +9:memory:/docker/9999999999999999999999999999999999999999999999999999999999999999 +8:rdma:/ +7:devices:/docker/9999999999999999999999999999999999999999999999999999999999999999 +6:blkio:/docker/9999999999999999999999999999999999999999999999999999999999999999 +5:net_cls,net_prio:/docker/9999999999999999999999999999999999999999999999999999999999999999 +4:cpu,cpuacct:/docker/9999999999999999999999999999999999999999999999999999999999999999 +3:cpuset:/docker/9999999999999999999999999999999999999999999999999999999999999999 +2:pids:/docker/9999999999999999999999999999999999999999999999999999999999999999 +1:name=systemd:/docker/9999999999999999999999999999999999999999999999999999999999999999 +0::/system.slice/docker.service diff --git a/tests/sysroots/steamrt/run/pressure-vessel/.exists b/tests/sysroots/steamrt/run/pressure-vessel/.exists new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/system-info.c b/tests/system-info.c index cb1d11930130200dc41f634d55fe01c8c2a9f749..76f679ac4a4316b2c358acc846e7553f4afdcdfa 100644 --- a/tests/system-info.c +++ b/tests/system-info.c @@ -2182,6 +2182,75 @@ driver_environment (Fixture *f, g_strfreev (envp); } +typedef struct +{ + const char *description; + const char *sysroot; + SrtContainerType type; + const char *host_directory; +} ContainerTest; + +static const ContainerTest container_tests[] = +{ + { "Has /.dockerenv", + "debian-unstable", SRT_CONTAINER_TYPE_DOCKER, NULL }, + { "Has an unknown value in /run/systemd/container", + "debian10", SRT_CONTAINER_TYPE_UNKNOWN, NULL }, + { "Has 'docker' in /run/systemd/container", + "fedora", SRT_CONTAINER_TYPE_DOCKER, NULL }, + { "Has /.flatpak-info", + "flatpak-example", SRT_CONTAINER_TYPE_FLATPAK, "/run/host" }, + { "Has /run/host", + "invalid-os-release", SRT_CONTAINER_TYPE_UNKNOWN, "/run/host" }, + { "Has no evidence of being a container", + "no-os-release", SRT_CONTAINER_TYPE_NONE, NULL }, + { "Has /run/pressure-vessel", + "steamrt", SRT_CONTAINER_TYPE_PRESSURE_VESSEL, "/run/host" }, + { "Has a Docker-looking /proc/1/cgroup", + "steamrt-unofficial", SRT_CONTAINER_TYPE_DOCKER, NULL }, +}; + +static void +test_containers (Fixture *f, + gconstpointer context) +{ + gsize i, j; + + for (i = 0; i < G_N_ELEMENTS (container_tests); i++) + { + const ContainerTest *test = &container_tests[i]; + SrtSystemInfo *info; + gchar *sysroot, *got, *expected; + + g_test_message ("%s: %s", test->sysroot, test->description); + + sysroot = g_build_filename (f->srcdir, "sysroots", test->sysroot, NULL); + + info = srt_system_info_new (NULL); + g_assert_nonnull (info); + srt_system_info_set_sysroot (info, sysroot); + + for (j = 0; j < 2; j++) + { + g_assert_cmpint (srt_system_info_get_container_type (info), ==, + test->type); + got = srt_system_info_dup_container_host_directory (info); + + if (test->host_directory == NULL) + expected = NULL; + else + expected = g_build_filename (sysroot, test->host_directory, NULL); + + g_assert_cmpstr (got, ==, expected); + g_free (got); + g_free (expected); + } + + g_object_unref (info); + g_free (sysroot); + } +} + int main (int argc, char **argv) @@ -2252,5 +2321,8 @@ main (int argc, g_test_add ("/system-info/driver_environment", Fixture, NULL, setup, driver_environment, teardown); + g_test_add ("/system-info/containers", Fixture, NULL, + setup, test_containers, teardown); + return g_test_run (); }