diff --git a/src/runtime.c b/src/runtime.c index 592e2a1d2b3a9515f287bfc53d08fd8271649f14..cd21c859da8c6fff2630b196cee10e0ac49f5389 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 078b47caa4bfc8ebcb343c87087221f48b49141d..3c004c72254b4782baff9fc056e327efc110cc6f 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 386a14b02a811287d828ef0cfffc3f27d314c69c..2791c167fc7134bbd0d2d2528c3a1ea117f61627 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;