From ee1bae765be2d75613263650c61c63665ed86409 Mon Sep 17 00:00:00 2001 From: Simon McVittie <smcv@collabora.com> Date: Tue, 9 Jun 2020 20:28:39 +0100 Subject: [PATCH] runtime: Garbage-collect unused temporary runtimes I'd originally intended this to be done by a wrapper script, but in fact it's just as straightforward to do it here, which has the advantage that it keeps all the knowledge about the contents of the temporary runtimes directory in one place. Signed-off-by: Simon McVittie <smcv@collabora.com> --- src/runtime.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++++- src/runtime.h | 3 ++ src/wrap.c | 14 ++++++ 3 files changed, 141 insertions(+), 2 deletions(-) diff --git a/src/runtime.c b/src/runtime.c index 592e2a1d2..cd21c859d 100644 --- a/src/runtime.c +++ b/src/runtime.c @@ -328,6 +328,122 @@ pv_runtime_constructed (GObject *object) g_return_if_fail (self->tools_dir != NULL); } +static gboolean +pv_runtime_garbage_collect (PvRuntime *self, + PvBwrapLock *mutable_parent_lock, + GError **error) +{ + g_auto(GLnxDirFdIterator) iter = { FALSE }; + + g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE); + g_return_val_if_fail (self->mutable_parent != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + /* We don't actually *use* this: it just acts as an assertion that + * we are holding the lock on the parent directory. */ + g_return_val_if_fail (mutable_parent_lock != NULL, FALSE); + + if (!glnx_dirfd_iterator_init_at (AT_FDCWD, self->mutable_parent, + TRUE, &iter, error)) + return FALSE; + + while (TRUE) + { + g_autoptr(GError) local_error = NULL; + g_autoptr(PvBwrapLock) temp_lock = NULL; + g_autofree gchar *keep = NULL; + g_autofree gchar *ref = NULL; + struct dirent *dent; + struct stat ignore; + + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent, + NULL, error)) + return FALSE; + + if (dent == NULL) + break; + + switch (dent->d_type) + { + case DT_DIR: + break; + + case DT_BLK: + case DT_CHR: + case DT_FIFO: + case DT_LNK: + case DT_REG: + case DT_SOCK: + case DT_UNKNOWN: + default: + g_debug ("Ignoring %s/%s: not a directory", + self->mutable_parent, dent->d_name); + continue; + } + + if (!g_str_has_prefix (dent->d_name, "tmp-")) + { + g_debug ("Ignoring %s/%s: not tmp-*", + self->mutable_parent, dent->d_name); + continue; + } + + g_debug ("Found temporary runtime %s/%s, considering whether to " + "delete it...", + self->mutable_parent, dent->d_name); + + keep = g_build_filename (dent->d_name, "keep", NULL); + + if (glnx_fstatat (self->mutable_parent_fd, keep, &ignore, + AT_SYMLINK_NOFOLLOW, &local_error)) + { + g_debug ("Not deleting \"%s/%s\": ./keep exists", + self->mutable_parent, dent->d_name); + continue; + } + else if (!g_error_matches (local_error, G_IO_ERROR, + G_IO_ERROR_NOT_FOUND)) + { + /* EACCES or something? Give it the benefit of the doubt */ + g_warning ("Not deleting \"%s/%s\": unable to stat ./keep: %s", + self->mutable_parent, dent->d_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + g_clear_error (&local_error); + + ref = g_build_filename (dent->d_name, ".ref", NULL); + temp_lock = pv_bwrap_lock_new (self->mutable_parent_fd, ref, + (PV_BWRAP_LOCK_FLAGS_CREATE | + PV_BWRAP_LOCK_FLAGS_WRITE), + &local_error); + + if (temp_lock == NULL) + { + g_debug ("Ignoring \"%s/%s\": unable to get lock: %s", + self->mutable_parent, dent->d_name, + local_error->message); + g_clear_error (&local_error); + continue; + } + + g_debug ("Deleting \"%s/%s\"...", self->mutable_parent, dent->d_name); + + /* We have the lock, which would not have happened if someone was + * still using the runtime, so we can safely delete it. */ + if (!glnx_shutil_rm_rf_at (self->mutable_parent_fd, dent->d_name, + NULL, &local_error)) + { + g_debug ("Unable to delete %s/%s: %s", + self->mutable_parent, dent->d_name, local_error->message); + g_clear_error (&local_error); + continue; + } + } + + return TRUE; +} + static gboolean pv_runtime_init_mutable (PvRuntime *self, GError **error) @@ -357,8 +473,7 @@ pv_runtime_init_mutable (PvRuntime *self, return FALSE; /* Lock the parent directory. Anything that directly manipulates the - * temporary runtimes (notably the deploy-runtime and run-in-steamrt - * scripts in SteamLinuxRuntime) is expected to do the same, so that + * temporary runtimes is expected to do the same, so that * it cannot be deleting temporary runtimes at the same time we're * creating them. * @@ -373,6 +488,13 @@ pv_runtime_init_mutable (PvRuntime *self, return glnx_prefix_error (error, "Unable to lock \"%s/%s\"", self->mutable_parent, ".ref"); + /* GC old runtimes (if they have become unused) before we create a + * new one. This means we should only ever have one temporary runtime + * copy per game that is run concurrently. */ + if ((self->flags & PV_RUNTIME_FLAGS_GC_RUNTIMES) != 0 && + !pv_runtime_garbage_collect (self, mutable_lock, error)) + return FALSE; + temp_dir = g_build_filename (self->mutable_parent, "tmp-XXXXXX", NULL); if (g_mkdtemp (temp_dir) == NULL) diff --git a/src/runtime.h b/src/runtime.h index 078b47caa..3c004c722 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -33,6 +33,7 @@ * PvRuntimeFlags: * @PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK: Use host system graphics stack * @PV_RUNTIME_FLAGS_GENERATE_LOCALES: Generate missing locales + * @PV_RUNTIME_FLAGS_GC_RUNTIMES: Garbage-collect old temporary runtimes * @PV_RUNTIME_FLAGS_NONE: None of the above * * Flags affecting how we set up the runtime. @@ -41,12 +42,14 @@ typedef enum { PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK = (1 << 0), PV_RUNTIME_FLAGS_GENERATE_LOCALES = (1 << 1), + PV_RUNTIME_FLAGS_GC_RUNTIMES = (1 << 2), PV_RUNTIME_FLAGS_NONE = 0 } PvRuntimeFlags; #define PV_RUNTIME_FLAGS_MASK \ (PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK \ | PV_RUNTIME_FLAGS_GENERATE_LOCALES \ + | PV_RUNTIME_FLAGS_GC_RUNTIMES \ ) typedef struct _PvRuntime PvRuntime; diff --git a/src/wrap.c b/src/wrap.c index 386a14b02..2791c167f 100644 --- a/src/wrap.c +++ b/src/wrap.c @@ -330,6 +330,7 @@ static char **opt_env_if_host = NULL; static char *opt_fake_home = NULL; static char *opt_freedesktop_app_id = NULL; static char *opt_steam_app_id = NULL; +static gboolean opt_gc_runtimes = TRUE; static gboolean opt_generate_locales = TRUE; static char *opt_home = NULL; static gboolean opt_host_fallback = FALSE; @@ -525,6 +526,15 @@ static GOptionEntry options[] = G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &opt_steam_app_id, "Make --unshare-home use ~/.var/app/com.steampowered.AppN " "as home directory. [Default: $SteamAppId]", "N" }, + { "gc-runtimes", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_gc_runtimes, + "If using --copy-runtime-into, garbage-collect old temporary " + "runtimes. [Default, unless $PRESSURE_VESSEL_GC_RUNTIMES is 0]", + NULL }, + { "no-gc-runtimes", '\0', + G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_gc_runtimes, + "If using --copy-runtime-into, don't garbage-collect old " + "temporary runtimes.", NULL }, { "generate-locales", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_generate_locales, "If using --runtime, attempt to generate any missing locales. " @@ -753,6 +763,7 @@ main (int argc, opt_remove_game_overlay = boolean_environment ("PRESSURE_VESSEL_REMOVE_GAME_OVERLAY", FALSE); opt_share_home = tristate_environment ("PRESSURE_VESSEL_SHARE_HOME"); + opt_gc_runtimes = boolean_environment ("PRESSURE_VESSEL_GC_RUNTIMES", TRUE); opt_generate_locales = boolean_environment ("PRESSURE_VESSEL_GENERATE_LOCALES", TRUE); opt_host_graphics = boolean_environment ("PRESSURE_VESSEL_HOST_GRAPHICS", TRUE); @@ -1103,6 +1114,9 @@ main (int argc, { PvRuntimeFlags flags = PV_RUNTIME_FLAGS_NONE; + if (opt_gc_runtimes) + flags |= PV_RUNTIME_FLAGS_GC_RUNTIMES; + if (opt_generate_locales) flags |= PV_RUNTIME_FLAGS_GENERATE_LOCALES; -- GitLab