Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • steamrt/steam-runtime-tools
1 result
Show changes
Commits on Source (32)
steam-runtime-tools (0.20210219.0) UNRELEASED; urgency=medium
steam-runtime-tools (0.20210224.0) UNRELEASED; urgency=medium
[ Simon McVittie ]
* pressure-vessel: Fix processing of Vulkan ICDs/layers outside /usr
......@@ -14,12 +14,19 @@ steam-runtime-tools (0.20210219.0) UNRELEASED; urgency=medium
* pv-launch: Use unset-env option for subsandbox API >= v5.
This was added in Flatpak 1.10.0.
* pressure-vessel: Improve diagnostics for various error conditions
* pressure-vessel: Generalize --copy-runtime-into into --variable-dir
* pressure-vessel: Fix locking behaviour in variable directory
* pressure-vessel: Add support for unpacking runtimes from an archive.
This previously had to be done by the SteamLinuxRuntime shell scripts.
* pressure-vessel: Automatically enable --copy-runtime if running
under Flatpak
[ Ludovico de Nittis ]
* pressure-vessel: Update flatpak-run to the latest upstream version.
This includes a fix to propagate the X11 cookies that have an address
equal to XAUTHLOCALHOSTNAME, fixing X11 authentication on some openSUSE
systems (Resolves: steam-runtime-tools#53)
* pressure-vessel: Improve library binding speed
* pv-adverb: Remove unused locales temporary directories
(Resolves: steam-runtime-tools#56)
* system-info: Report duplicate Vulkan ICDs/layers as an issue.
......@@ -30,7 +37,7 @@ steam-runtime-tools (0.20210219.0) UNRELEASED; urgency=medium
* system-info: Parse Vulkan layers from a report in the right order
* build: Refactoring
-- Simon McVittie <smcv@collabora.com> Mon, 22 Feb 2021 13:34:14 +0000
-- Simon McVittie <smcv@collabora.com> Wed, 24 Feb 2021 15:57:08 +0000
steam-runtime-tools (0.20210203.0) scout; urgency=medium
......
......@@ -81,6 +81,151 @@ def boolean_environment(name, default):
return default
class ContainerRuntime:
def __init__(
self,
path, # type: str
home, # type: str
): # type: (...) -> None
self.home = home
self.path = path
self.description = ''
class DirectoryRuntime(ContainerRuntime):
def __init__(
self,
path, # type: str
home, # type: str
): # type: (...) -> None
super().__init__(path, home=home)
self.description = self.__describe_runtime(path)
def __describe_runtime(
self,
path # type: str
):
# type: (...) -> str
description = path
files = os.path.join(path, 'files')
metadata = os.path.join(path, 'metadata')
if os.path.islink(files):
description = os.path.realpath(files)
if description.startswith(self.home + '/'):
description = '~' + description[len(self.home):]
name = None # type: typing.Optional[str]
pretty_name = None # type: typing.Optional[str]
build_id = None # type: typing.Optional[str]
variant = None # type: typing.Optional[str]
try:
keyfile = GLib.KeyFile.new()
keyfile.load_from_file(
metadata, GLib.KeyFileFlags.NONE)
try:
build_id = keyfile.get_string('Runtime', 'x-flatdeb-build-id')
except GLib.Error:
pass
try:
name = keyfile.get_string('Runtime', 'runtime')
except GLib.Error:
pass
else:
assert name is not None
variant = name.split('.')[-1]
except GLib.Error:
pass
try:
with open(
os.path.join(files, 'lib', 'os-release')
) as reader:
for line in reader:
if line.startswith('PRETTY_NAME='):
pretty_name = line.split('=', 1)[1].strip()
pretty_name = GLib.shell_unquote(pretty_name)
elif line.startswith('BUILD_ID='):
build_id = line.split('=', 1)[1].strip()
build_id = GLib.shell_unquote(build_id)
elif line.startswith('VARIANT='):
variant = line.split('=', 1)[1].strip()
variant = GLib.shell_unquote(variant)
except (GLib.Error, EnvironmentError):
pass
if pretty_name is None:
pretty_name = name
if pretty_name is None:
pretty_name = os.path.basename(path)
if build_id is None:
build_id = ''
else:
build_id = ' build {}'.format(build_id)
if variant is None:
variant = ''
else:
variant = ' {}'.format(variant)
description = '{}{}{}\n({})'.format(
pretty_name,
variant,
build_id,
description,
)
return description
class ArchiveRuntime(ContainerRuntime):
def __init__(
self,
path, # type: str
buildid_file, # type: str
home, # type: str
): # type: (...) -> None
super().__init__(path, home=home)
if path.startswith(self.home + '/'):
path = '~' + path[len(self.home):]
description = os.path.basename(path)
sdk_suffix = ''
if description.startswith('com.valvesoftware.SteamRuntime.'):
description = description[len('com.valvesoftware.SteamRuntime.'):]
if description.startswith('Platform-'):
description = description[len('Platform-'):]
if description.startswith('Sdk-'):
sdk_suffix = '-sdk'
description = description[len('Sdk-'):]
if description.startswith('amd64,i386-'):
description = description[len('amd64,i386-'):]
if description.endswith('.tar.gz'):
description = description[:-len('.tar.gz')]
if description.endswith('-runtime'):
description = description[:-len('-runtime')]
with open(buildid_file) as reader:
build = reader.read().strip()
self.deploy_id = '{}{}_{}'.format(description, sdk_suffix, build)
self.description = '{} build {}\n({})'.format(description, build, path)
class Gui:
def __init__(self):
# type: (...) -> None
......@@ -88,7 +233,8 @@ class Gui:
self.failed = False
self.home = GLib.get_home_dir()
self.container_runtimes = {} # type: typing.Dict[str, str]
self.container_runtimes = {
} # type: typing.Dict[str, ContainerRuntime]
for search in (
os.getenv('PRESSURE_VESSEL_RUNTIME_BASE'),
......@@ -115,8 +261,25 @@ class Gui:
files = os.path.join(path, 'files')
if os.path.isdir(files):
description = self._describe_runtime(path)
self.container_runtimes[path] = description
self.container_runtimes[path] = DirectoryRuntime(
path,
home=self.home,
)
continue
if member.endswith(('-runtime.tar.gz', '-sysroot.tar.gz')):
# runtime and sysroot happen to be the same length!
buildid_file = os.path.join(
source_of_runtimes,
member[:-len('-runtime.tar.gz')] + '-buildid.txt',
)
if os.path.exists(buildid_file):
self.container_runtimes[path] = ArchiveRuntime(
path,
buildid_file=buildid_file,
home=self.home,
)
self.window = Gtk.Window()
self.window.set_default_size(600, 300)
......@@ -156,8 +319,8 @@ class Gui:
'None (use host system and traditional LD_LIBRARY_PATH runtime)'
)
for path, description in sorted(self.container_runtimes.items()):
self.container_runtime_combo.append(path, description)
for path, runtime in sorted(self.container_runtimes.items()):
self.container_runtime_combo.append(path, runtime.description)
if self.container_runtimes:
self.container_runtime_combo.set_active(1)
......@@ -172,10 +335,13 @@ class Gui:
if var_path is None:
value = False
var_path = os.getenv('PRESSURE_VESSEL_VARIABLE_DIR', None)
var_path = None
else:
value = True
var_path = os.getenv('PRESSURE_VESSEL_VARIABLE_DIR', var_path)
value = boolean_environment('PRESSURE_VESSEL_COPY_RUNTIME', value)
if var_path is None:
var_path = os.getenv(
'PRESSURE_VESSEL_RUNTIME_BASE',
......@@ -184,11 +350,34 @@ class Gui:
assert var_path is not None
self.var_path = os.path.realpath(var_path)
self.copy_runtime_into_check = Gtk.CheckButton.new_with_label(
self.gc_runtimes_check = Gtk.CheckButton.new_with_label(
'Clean up old runtimes'
)
# Deliberately ignoring PRESSURE_VESSEL_GC_RUNTIMES: a lot of the
# point of this test UI is the ability to switch between runtimes,
# but if we GC non-current runtimes, that'll be really slow.
self.gc_runtimes_check.set_active(False)
self.grid.attach(self.gc_runtimes_check, 1, row, 1, 1)
row += 1
label = Gtk.Label.new('')
label.set_markup(
'<small><i>'
'Normally this is enabled, but this test UI disables it by '
'default for quicker switching between multiple runtimes.'
'</i></small>'
)
label.set_halign(Gtk.Align.START)
label.set_line_wrap(True)
self.grid.attach(label, 1, row, 1, 1)
row += 1
self.copy_runtime_check = Gtk.CheckButton.new_with_label(
'Create temporary runtime copy on disk'
)
self.copy_runtime_into_check.set_active(value)
self.grid.attach(self.copy_runtime_into_check, 1, row, 1, 1)
self.copy_runtime_check.set_active(value)
self.grid.attach(self.copy_runtime_check, 1, row, 1, 1)
row += 1
label = Gtk.Label.new('')
......@@ -206,18 +395,47 @@ class Gui:
self.grid.attach(label, 1, row, 1, 1)
row += 1
self.host_graphics_check = Gtk.CheckButton.new_with_label(
'Use host-system graphics stack'
)
value = boolean_environment('PRESSURE_VESSEL_HOST_GRAPHICS', True)
self.host_graphics_check.set_active(value)
self.grid.attach(self.host_graphics_check, 1, row, 1, 1)
label = Gtk.Label.new('Graphics stack')
self.grid.attach(label, 0, row, 1, 1)
self.graphics_provider_combo = Gtk.ComboBoxText.new()
env = os.getenv('PRESSURE_VESSEL_GRAPHICS_PROVIDER')
if env is not None:
self.graphics_provider_combo.append(
env, '$PRESSURE_VESSEL_GRAPHICS_PROVIDER ({})'.format(
env or 'empty'
),
)
if env is None or env != '/':
self.graphics_provider_combo.append(
'/', 'Current execution environment',
)
if (
(env is None or env != '/run/host')
and os.path.isdir('/run/host/etc')
and os.path.isdir('/run/host/usr')
):
self.graphics_provider_combo.append(
'/run/host', 'Host system',
)
if env is None or env != '':
self.graphics_provider_combo.append(
'', "Container's own libraries (probably won't work)",
)
self.graphics_provider_combo.set_active(0)
self.grid.attach(self.graphics_provider_combo, 1, row, 1, 1)
row += 1
label = Gtk.Label.new('')
label.set_markup(
'<small><i>'
"Most games and GPUs won't work when this is disabled."
"Most games and GPUs won't work if this is changed."
'</i></small>'
)
label.set_halign(Gtk.Align.START)
......@@ -411,93 +629,13 @@ class Gui:
def _container_runtime_changed(self, combo):
if combo.get_active_id() == '/':
self.copy_runtime_into_check.set_sensitive(False)
self.host_graphics_check.set_sensitive(False)
else:
self.copy_runtime_into_check.set_sensitive(True)
self.host_graphics_check.set_sensitive(True)
def _describe_runtime(
self,
path # type: str
):
# type: (...) -> str
description = path
files = os.path.join(path, 'files')
metadata = os.path.join(path, 'metadata')
if os.path.islink(files):
description = os.path.realpath(files)
if description.startswith(self.home + '/'):
description = '~' + description[len(self.home):]
name = None # type: typing.Optional[str]
pretty_name = None # type: typing.Optional[str]
build_id = None # type: typing.Optional[str]
variant = None # type: typing.Optional[str]
try:
keyfile = GLib.KeyFile.new()
keyfile.load_from_file(
metadata, GLib.KeyFileFlags.NONE)
try:
build_id = keyfile.get_string('Runtime', 'x-flatdeb-build-id')
except GLib.Error:
pass
try:
name = keyfile.get_string('Runtime', 'runtime')
except GLib.Error:
pass
else:
assert name is not None
variant = name.split('.')[-1]
except GLib.Error:
pass
try:
with open(
os.path.join(files, 'lib', 'os-release')
) as reader:
for line in reader:
if line.startswith('PRETTY_NAME='):
pretty_name = line.split('=', 1)[1].strip()
pretty_name = GLib.shell_unquote(pretty_name)
elif line.startswith('BUILD_ID='):
build_id = line.split('=', 1)[1].strip()
build_id = GLib.shell_unquote(build_id)
elif line.startswith('VARIANT='):
variant = line.split('=', 1)[1].strip()
variant = GLib.shell_unquote(variant)
except (GLib.Error, EnvironmentError):
pass
if pretty_name is None:
pretty_name = name
if pretty_name is None:
pretty_name = os.path.basename(path)
if build_id is None:
build_id = ''
self.copy_runtime_check.set_sensitive(False)
self.gc_runtimes_check.set_sensitive(False)
self.graphics_provider_combo.set_sensitive(False)
else:
build_id = ' build {}'.format(build_id)
if variant is None:
variant = ''
else:
variant = ' {}'.format(variant)
description = '{}{}{}\n({})'.format(
pretty_name,
variant,
build_id,
description,
)
return description
self.copy_runtime_check.set_sensitive(True)
self.gc_runtimes_check.set_sensitive(True)
self.graphics_provider_combo.set_sensitive(True)
def run_cb(self, _ignored=None):
# type: (typing.Any) -> None
......@@ -516,21 +654,30 @@ class Gui:
elif id == '/':
argv.append('--runtime=')
else:
argv.append('--runtime=' + id)
runtime = self.container_runtimes[id]
if self.host_graphics_check.get_active():
if os.path.isdir('/run/host'):
argv.append('--graphics-provider=/run/host')
if isinstance(runtime, ArchiveRuntime):
argv.append('--runtime-archive=' + id)
argv.append('--runtime-id=' + runtime.deploy_id)
else:
argv.append('--graphics-provider=/')
else:
argv.append('--graphics-provider=')
argv.append('--runtime=' + id)
argv.append(
'--graphics-provider='
+ self.graphics_provider_combo.get_active_id()
)
if self.copy_runtime_into_check.get_active():
if self.copy_runtime_check.get_active():
os.makedirs(self.var_path, mode=0o755, exist_ok=True)
argv.append('--copy-runtime-into=' + self.var_path)
argv.append('--copy-runtime')
argv.append('--variable-dir=' + self.var_path)
else:
argv.append('--no-copy-runtime')
if self.gc_runtimes_check.get_active():
argv.append('--gc-runtimes')
else:
argv.append('--copy-runtime-into=')
argv.append('--no-gc-runtimes')
if self.unshare_home_check.get_active():
argv.append('--unshare-home')
......
This diff is collapsed.
......@@ -38,6 +38,8 @@
* @PV_RUNTIME_FLAGS_GC_RUNTIMES: Garbage-collect old temporary runtimes
* @PV_RUNTIME_FLAGS_VERBOSE: Be more verbose
* @PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS: Include host Vulkan layers
* @PV_RUNTIME_FLAGS_COPY_RUNTIME: Copy the runtime and modify the copy
* @PV_RUNTIME_FLAGS_UNPACK_ARCHIVE: Source is an archive, not a deployment
* @PV_RUNTIME_FLAGS_NONE: None of the above
*
* Flags affecting how we set up the runtime.
......@@ -49,6 +51,8 @@ typedef enum
PV_RUNTIME_FLAGS_GC_RUNTIMES = (1 << 2),
PV_RUNTIME_FLAGS_VERBOSE = (1 << 3),
PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS = (1 << 4),
PV_RUNTIME_FLAGS_COPY_RUNTIME = (1 << 5),
PV_RUNTIME_FLAGS_UNPACK_ARCHIVE = (1 << 6),
PV_RUNTIME_FLAGS_NONE = 0
} PvRuntimeFlags;
......@@ -58,6 +62,8 @@ typedef enum
| PV_RUNTIME_FLAGS_GC_RUNTIMES \
| PV_RUNTIME_FLAGS_VERBOSE \
| PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS \
| PV_RUNTIME_FLAGS_COPY_RUNTIME \
| PV_RUNTIME_FLAGS_UNPACK_ARCHIVE \
)
typedef struct _PvRuntime PvRuntime;
......@@ -71,8 +77,9 @@ typedef struct _PvRuntimeClass PvRuntimeClass;
#define PV_RUNTIME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PV_TYPE_RUNTIME, PvRuntimeClass)
GType pv_runtime_get_type (void);
PvRuntime *pv_runtime_new (const char *deployment,
const char *mutable_parent,
PvRuntime *pv_runtime_new (const char *source,
const char *id,
const char *variable_dir,
const char *bubblewrap,
const char *tools_dir,
const char *provider_in_current_namespace,
......@@ -90,4 +97,8 @@ gboolean pv_runtime_bind (PvRuntime *self,
GError **error);
void pv_runtime_cleanup (PvRuntime *self);
gboolean pv_runtime_garbage_collect_legacy (const char *variable_dir,
const char *runtime_base,
GError **error);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (PvRuntime, g_object_unref)
......@@ -977,3 +977,72 @@ pv_set_up_logging (gboolean opt_verbose)
opt_timestamp ? pv_log_to_stderr_with_timestamp : pv_log_to_stderr,
NULL);
}
/**
* pv_delete_dangling_symlink:
* @dirfd: An open file descriptor for a directory
* @debug_path: Path to directory represented by @dirfd, used in debug messages
* @name: A filename in @dirfd that is thought to be a symbolic link
*
* If @name exists in @dirfd and is a symbolic link whose target does not
* exist, delete it.
*/
void
pv_delete_dangling_symlink (int dirfd,
const char *debug_path,
const char *name)
{
struct stat stat_buf, lstat_buf;
g_return_if_fail (dirfd >= 0);
g_return_if_fail (name != NULL);
g_return_if_fail (strcmp (name, "") != 0);
g_return_if_fail (strcmp (name, ".") != 0);
g_return_if_fail (strcmp (name, "..") != 0);
if (fstatat (dirfd, name, &lstat_buf, AT_SYMLINK_NOFOLLOW) == 0)
{
if (!S_ISLNK (lstat_buf.st_mode))
{
g_debug ("Ignoring %s/%s: not a symlink",
debug_path, name);
}
else if (fstatat (dirfd, name, &stat_buf, 0) == 0)
{
g_debug ("Ignoring %s/%s: symlink target still exists",
debug_path, name);
}
else if (errno != ENOENT)
{
int saved_errno = errno;
g_debug ("Ignoring %s/%s: fstatat(!NOFOLLOW): %s",
debug_path, name, g_strerror (saved_errno));
}
else
{
g_debug ("Target of %s/%s no longer exists, deleting it",
debug_path, name);
if (unlinkat (dirfd, name, 0) != 0)
{
int saved_errno = errno;
g_debug ("Could not delete %s/%s: unlinkat: %s",
debug_path, name, g_strerror (saved_errno));
}
}
}
else if (errno == ENOENT)
{
/* Silently ignore: symlink doesn't exist so we don't need to
* delete it */
}
else
{
int saved_errno = errno;
g_debug ("Ignoring %s/%s: fstatat(NOFOLLOW): %s",
debug_path, name, g_strerror (saved_errno));
}
}
......@@ -76,3 +76,7 @@ const char *pv_get_path_after (const char *str,
const char *prefix);
void pv_set_up_logging (gboolean opt_verbose);
void pv_delete_dangling_symlink (int dirfd,
const char *debug_path,
const char *name);
......@@ -28,10 +28,18 @@ pressure-vessel-wrap - run programs in a bubblewrap container
: Disable all interactivity and redirection: ignore `--shell`,
all `--shell-` options, `--terminal`, `--tty` and `--xterm`.
`--copy-runtime`, `--no-copy-runtime`
: If a `--runtime` is active, copy it into a subdirectory of the
`--variable-dir`, edit the copy in-place, and mount the copy read-only
in the container, instead of setting up elaborate bind-mount structures.
This option requires the `--variable-dir` option to be used.
`--no-copy-runtime` disables this behaviour and is currently
the default.
`--copy-runtime-into` *DIR*
: If a `--runtime` is active, copy it into a subdirectory of *DIR*,
edit the copy in-place, and mount the copy read-only in the container,
instead of setting up elaborate bind-mount structures.
: If *DIR* is an empty string, equivalent to `--no-copy-runtime`.
Otherwise, equivalent to `--copy-runtime --variable-dir=DIR`.
`--env-if-host` *VAR=VAL*
: If *COMMAND* is run with `/usr` from the host system, set
......@@ -46,7 +54,7 @@ pressure-vessel-wrap - run programs in a bubblewrap container
freedesktop.org app *ID* would use.
`--gc-runtimes`, `--no-gc-runtimes`
: If using `--copy-runtime-into`, garbage-collect old temporary
: If using `--variable-dir`, garbage-collect old temporary
runtimes that are left over from a previous **pressure-vessel-wrap**.
This is the default. `--no-gc-runtimes` disables this behaviour.
......@@ -81,8 +89,8 @@ pressure-vessel-wrap - run programs in a bubblewrap container
`--only-prepare`
: Prepare the runtime, but do not actually run *COMMAND*.
With `--copy-runtime-into`, the prepared runtime will appear in
a subdirectory of *DIR*.
With `--copy-runtime`, the prepared runtime will appear in
a subdirectory of the `--variable-dir`.
`--pass-fd` *FD*
: Pass the file descriptor *FD* (specified as a small positive integer)
......@@ -107,9 +115,38 @@ pressure-vessel-wrap - run programs in a bubblewrap container
(containing bin/sh, bin/env and many other OS files).
For example, a Flatpak runtime is a suitable value for *PATH*.
`--runtime-archive` *ARCHIVE*
: Unpack *ARCHIVE* and use it to provide /usr in the container, similar
to `--runtime`. The `--runtime-id` option is also required, unless
the filename of the *ARCHIVE* ends with a supported suffix
(`-runtime.tar.gz` or `-sysroot.tar.gz`) and it is accompanied by a
`-buildid.txt` file.
If this option is used, then `--variable-dir`
(or its environment variable equivalent) is also required.
This option and `--runtime` cannot both be used.
The archive will be unpacked into a subdirectory of the `--variable-dir`.
Any other subdirectories of the `--variable-dir` that appear to be
different runtimes will be deleted, unless they contain a file
at the top level named `keep` or are currently in use.
The archive must currently be a gzipped tar file whose name ends
with `.tar.gz`. Other formats might be allowed in future.
`--runtime-base` *PATH*
: If `--runtime` specifies a relative path, look for it relative
to *PATH*.
: If `--runtime` or `--runtime-archive` is specified as a relative path,
look for it relative to *PATH*.
`--runtime-id` *ID*
: Use *ID* to construct a directory into which the `--runtime-archive`
will be unpacked, overriding an accompanying `-buildid.txt` file
if present.
If the *ID* is the same as in a previous run of pressure-vessel-wrap,
the content of the `--runtime-archive` will be assumed to be the
same as in that previous run, resulting in the previous runtime
being reused.
`--share-home`, `--unshare-home`
: If `--unshare-home` is specified, use the home directory given
......@@ -168,6 +205,10 @@ pressure-vessel-wrap - run programs in a bubblewrap container
: Perform a smoke-test to determine whether **pressure-vessel-wrap**
can work, and exit. Exit with status 0 if it can or 1 if it cannot.
`--variable-dir` *PATH*
: Use *PATH* as a cache directory for files that are temporarily
unpacked or copied. It will be created automatically if necessary.
`--verbose`
: Be more verbose.
......@@ -211,9 +252,14 @@ The following environment variables (among others) are read by
: If set to `1`, equivalent to `--batch`.
If set to `0`, no effect.
`PRESSURE_VESSEL_COPY_RUNTIME` (boolean)
: If set to `1`, equivalent to `--copy-runtime`.
If set to `0`, equivalent to `--no-copy-runtime`.
`PRESSURE_VESSEL_COPY_RUNTIME_INTO` (path or empty string)
: Equivalent to
`--copy-runtime-into="$PRESSURE_VESSEL_COPY_RUNTIME_INTO"`.
: If the string is empty, equivalent to `--no-copy-runtime`.
Otherwise, equivalent to
`--copy-runtime --variable-dir="$PRESSURE_VESSEL_COPY_RUNTIME_INTO"`.
`PRESSURE_VESSEL_FILESYSTEMS_RO` (`:`-separated list of paths)
: Make these paths available read-only inside the container if they
......@@ -281,6 +327,9 @@ The following environment variables (among others) are read by
`PRESSURE_VESSEL_TERMINAL` (`none`, `auto`, `tty` or `xterm`)
: Equivalent to `--terminal="$PRESSURE_VESSEL_TERMINAL"`.
`PRESSURE_VESSEL_VARIABLE_DIR` (path)
: Equivalent to `--variable-dir="$PRESSURE_VESSEL_VARIABLE_DIR"`.
`PRESSURE_VESSEL_VERBOSE` (boolean)
: If set to `1`, equivalent to `--verbose`.
......
......@@ -706,12 +706,13 @@ typedef enum
} Tristate;
static gboolean opt_batch = FALSE;
static char *opt_copy_runtime_into = NULL;
static gboolean opt_copy_runtime = FALSE;
static char **opt_env_if_host = NULL;
static char *opt_fake_home = NULL;
static char **opt_filesystems = NULL;
static char *opt_freedesktop_app_id = NULL;
static char *opt_steam_app_id = NULL;
static gboolean opt_gc_legacy_runtimes = FALSE;
static gboolean opt_gc_runtimes = TRUE;
static gboolean opt_generate_locales = TRUE;
static char *opt_home = NULL;
......@@ -724,12 +725,15 @@ static gboolean opt_import_vulkan_layers = TRUE;
static PvShell opt_shell = PV_SHELL_NONE;
static GPtrArray *opt_ld_preload = NULL;
static GArray *opt_pass_fds = NULL;
static char *opt_runtime_base = NULL;
static char *opt_runtime = NULL;
static char *opt_runtime_archive = NULL;
static char *opt_runtime_base = NULL;
static char *opt_runtime_id = NULL;
static Tristate opt_share_home = TRISTATE_MAYBE;
static gboolean opt_share_pid = TRUE;
static double opt_terminate_idle_timeout = 0.0;
static double opt_terminate_timeout = -1.0;
static char *opt_variable_dir = NULL;
static gboolean opt_verbose = FALSE;
static gboolean opt_version = FALSE;
static gboolean opt_version_only = FALSE;
......@@ -753,6 +757,35 @@ opt_host_ld_preload_cb (const gchar *option_name,
return TRUE;
}
static gboolean
opt_copy_runtime_into_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
if (value == NULL)
{
opt_copy_runtime = FALSE;
}
else if (value[0] == '\0')
{
g_warning ("%s is deprecated, disable with --no-copy-runtime instead",
option_name);
opt_copy_runtime = FALSE;
}
else
{
g_warning ("%s is deprecated, use --copy-runtime and "
"--variable-dir instead",
option_name);
opt_copy_runtime = TRUE;
g_free (opt_variable_dir);
opt_variable_dir = g_strdup (value);
}
return TRUE;
}
static gboolean
opt_pass_fd_cb (const char *name,
const char *value,
......@@ -937,7 +970,8 @@ opt_with_host_graphics_cb (const gchar *option_name,
/* This is the old way to get the graphics from the host system */
if (g_strcmp0 (option_name, "--with-host-graphics") == 0)
{
if (g_file_test ("/run/host", G_FILE_TEST_IS_DIR))
if (g_file_test ("/run/host/usr", G_FILE_TEST_IS_DIR)
&& g_file_test ("/run/host/etc", G_FILE_TEST_IS_DIR))
opt_graphics_provider = g_strdup ("/run/host");
else
opt_graphics_provider = g_strdup ("/");
......@@ -966,11 +1000,20 @@ static GOptionEntry options[] =
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_batch,
"Disable all interactivity and redirection: ignore --shell*, "
"--terminal, --xterm, --tty. [Default: if $PRESSURE_VESSEL_BATCH]", NULL },
{ "copy-runtime", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_copy_runtime,
"If a --runtime is used, copy it into --variable-dir and edit the "
"copy in-place.",
NULL },
{ "no-copy-runtime", '\0',
G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_copy_runtime,
"Don't behave as described for --copy-runtime. "
"[Default unless $PRESSURE_VESSEL_COPY_RUNTIME is 1 or running in Flatpak]",
NULL },
{ "copy-runtime-into", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_copy_runtime_into,
"If a --runtime is used, copy it into DIR and edit the copy in-place. "
"[Default: $PRESSURE_VESSEL_COPY_RUNTIME_INTO or empty]",
"DIR" },
G_OPTION_FLAG_FILENAME|G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK,
&opt_copy_runtime_into_cb,
"Deprecated alias for --copy-runtime and --variable-dir", "DIR" },
{ "env-if-host", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, &opt_env_if_host,
"Set VAR=VAL if COMMAND is run with /usr from the host system, "
......@@ -991,14 +1034,23 @@ static GOptionEntry options[] =
"Make --unshare-home use ~/.var/app/com.steampowered.AppN "
"as home directory. [Default: $STEAM_COMPAT_APP_ID or $SteamAppId]",
"N" },
{ "gc-legacy-runtimes", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_gc_legacy_runtimes,
"Garbage-collect old unpacked runtimes in $PRESSURE_VESSEL_RUNTIME_BASE.",
NULL },
{ "no-gc-legacy-runtimes", '\0',
G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_gc_legacy_runtimes,
"Don't garbage-collect old unpacked runtimes in "
"$PRESSURE_VESSEL_RUNTIME_BASE [default].",
NULL },
{ "gc-runtimes", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_gc_runtimes,
"If using --copy-runtime-into, garbage-collect old temporary "
"If using --variable-dir, 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 "
"If using --variable-dir, don't garbage-collect old "
"temporary runtimes.", NULL },
{ "generate-locales", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_generate_locales,
......@@ -1023,7 +1075,7 @@ static GOptionEntry options[] =
"and will be adjusted for use on the host system if pressure-vessel "
"is run in a container. The empty string means use the graphics "
"stack from container."
"[Default: $PRESSURE_VESSEL_GRAPHICS_PROVIDER or '/run/host' or '/']", "PATH" },
"[Default: $PRESSURE_VESSEL_GRAPHICS_PROVIDER or '/']", "PATH" },
{ "launcher", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_launcher,
"Instead of specifying a command with its arguments to execute, all the "
......@@ -1066,11 +1118,22 @@ static GOptionEntry options[] =
"it with the provider's graphics stack. The empty string "
"means don't use a runtime. [Default: $PRESSURE_VESSEL_RUNTIME or '']",
"RUNTIME" },
{ "runtime-archive", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_runtime_archive,
"Unpack the ARCHIVE and use it as the runtime, using --runtime-id to "
"avoid repeatedly unpacking the same archive. "
"[Default: $PRESSURE_VESSEL_RUNTIME_ARCHIVE]",
"ARCHIVE" },
{ "runtime-base", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_runtime_base,
"If a --runtime is a relative path, look for it relative to BASE. "
"If a --runtime or --runtime-archive is a relative path, look for "
"it relative to BASE. "
"[Default: $PRESSURE_VESSEL_RUNTIME_BASE or '.']",
"BASE" },
{ "runtime-id", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_runtime_id,
"Reuse a previously-unpacked --runtime-archive if its ID matched this",
"ID" },
{ "share-home", '\0',
G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_share_home_cb,
"Use the real home directory. "
......@@ -1141,6 +1204,10 @@ static GOptionEntry options[] =
"skip SIGTERM and use SIGKILL immediately. Implies --subreaper. "
"[Default: -1.0, meaning don't signal].",
"SECONDS" },
{ "variable-dir", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_variable_dir,
"If a runtime needs to be unpacked or copied, put it in DIR.",
"DIR" },
{ "verbose", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_verbose,
"Be more verbose.", NULL },
......@@ -1246,8 +1313,29 @@ main (int argc,
original_environ = g_get_environ ();
if (is_flatpak_env)
opt_copy_runtime = TRUE;
/* Set defaults */
opt_batch = pv_boolean_environment ("PRESSURE_VESSEL_BATCH", FALSE);
/* Process COPY_RUNTIME_INFO first so that COPY_RUNTIME and VARIABLE_DIR
* can override it */
opt_copy_runtime_into_cb ("$PRESSURE_VESSEL_COPY_RUNTIME_INTO",
g_getenv ("PRESSURE_VESSEL_COPY_RUNTIME_INTO"),
NULL, NULL);
opt_copy_runtime = pv_boolean_environment ("PRESSURE_VESSEL_COPY_RUNTIME",
opt_copy_runtime);
opt_runtime_id = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME_ID"));
{
const char *value = g_getenv ("PRESSURE_VESSEL_VARIABLE_DIR");
if (value != NULL)
{
g_free (opt_variable_dir);
opt_variable_dir = g_strdup (value);
}
}
opt_freedesktop_app_id = g_strdup (g_getenv ("PRESSURE_VESSEL_FDO_APP_ID"));
......@@ -1265,6 +1353,7 @@ main (int argc,
TRUE);
opt_share_home = tristate_environment ("PRESSURE_VESSEL_SHARE_HOME");
opt_gc_legacy_runtimes = pv_boolean_environment ("PRESSURE_VESSEL_GC_LEGACY_RUNTIMES", FALSE);
opt_gc_runtimes = pv_boolean_environment ("PRESSURE_VESSEL_GC_RUNTIMES", TRUE);
opt_generate_locales = pv_boolean_environment ("PRESSURE_VESSEL_GENERATE_LOCALES", TRUE);
......@@ -1290,29 +1379,52 @@ main (int argc,
if (opt_verbose)
pv_set_up_logging (opt_verbose);
if (opt_runtime == NULL)
opt_runtime = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME"));
/* Specifying either one of these mutually-exclusive options as a
* command-line option disables use of the environment variable for
* the other one */
if (opt_runtime == NULL && opt_runtime_archive == NULL)
{
opt_runtime = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME"));
if (opt_runtime_base == NULL)
opt_runtime_base = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME_BASE"));
/* Normalize empty string to NULL to simplify later code */
if (opt_runtime != NULL && opt_runtime[0] == '\0')
g_clear_pointer (&opt_runtime, g_free);
if (opt_runtime != NULL
&& opt_runtime[0] != '\0'
&& !g_path_is_absolute (opt_runtime)
&& opt_runtime_base != NULL
&& opt_runtime_base[0] != '\0')
opt_runtime_archive = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME_ARCHIVE"));
if (opt_runtime_archive != NULL && opt_runtime_archive[0] == '\0')
g_clear_pointer (&opt_runtime_archive, g_free);
}
if (opt_runtime_id != NULL)
{
g_autofree gchar *tmp = g_steal_pointer (&opt_runtime);
const char *p;
if (opt_runtime_id[0] == '-' || opt_runtime_id[0] == '.')
{
g_warning ("--runtime-id must not start with dash or dot");
goto out;
}
opt_runtime = g_build_filename (opt_runtime_base, tmp, NULL);
for (p = opt_runtime_id; *p != '\0'; p++)
{
if (!g_ascii_isalnum (*p) && *p != '_' && *p != '-' && *p != '.')
{
g_warning ("--runtime-id may only contain "
"alphanumerics, underscore, dash or dot");
goto out;
}
}
}
if (opt_copy_runtime_into == NULL)
opt_copy_runtime_into = g_strdup (g_getenv ("PRESSURE_VESSEL_COPY_RUNTIME_INTO"));
if (opt_runtime_base == NULL)
opt_runtime_base = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME_BASE"));
if (opt_copy_runtime_into != NULL
&& opt_copy_runtime_into[0] == '\0')
opt_copy_runtime_into = NULL;
if (opt_runtime != NULL && opt_runtime_archive != NULL)
{
g_warning ("--runtime and --runtime-archive cannot both be used");
goto out;
}
if (opt_graphics_provider == NULL)
opt_graphics_provider = g_strdup (g_getenv ("PRESSURE_VESSEL_GRAPHICS_PROVIDER"));
......@@ -1320,12 +1432,25 @@ main (int argc,
if (opt_graphics_provider == NULL)
{
/* Also check the deprecated 'PRESSURE_VESSEL_HOST_GRAPHICS' */
if (!pv_boolean_environment ("PRESSURE_VESSEL_HOST_GRAPHICS", TRUE))
opt_graphics_provider = g_strdup ("");
else if (g_file_test ("/run/host", G_FILE_TEST_IS_DIR))
opt_graphics_provider = g_strdup ("/run/host");
Tristate value = tristate_environment ("PRESSURE_VESSEL_HOST_GRAPHICS");
if (value == TRISTATE_MAYBE)
{
opt_graphics_provider = g_strdup ("/");
}
else
opt_graphics_provider = g_strdup ("/");
{
g_warning ("$PRESSURE_VESSEL_HOST_GRAPHICS is deprecated, "
"please use PRESSURE_VESSEL_GRAPHICS_PROVIDER instead");
if (value == TRISTATE_NO)
opt_graphics_provider = g_strdup ("");
else if (g_file_test ("/run/host/usr", G_FILE_TEST_IS_DIR)
&& g_file_test ("/run/host/etc", G_FILE_TEST_IS_DIR))
opt_graphics_provider = g_strdup ("/run/host");
else
opt_graphics_provider = g_strdup ("/");
}
}
g_assert (opt_graphics_provider != NULL);
......@@ -1493,6 +1618,12 @@ main (int argc,
}
}
if (opt_copy_runtime && opt_variable_dir == NULL)
{
g_warning ("--copy-runtime requires --variable-dir");
goto out;
}
/* Finished parsing arguments, so any subsequent failures will make
* us exit 1. */
ret = 1;
......@@ -1644,9 +1775,26 @@ main (int argc,
NULL);
}
if (opt_runtime != NULL && opt_runtime[0] != '\0')
if (opt_gc_legacy_runtimes
&& opt_runtime_base != NULL
&& opt_runtime_base[0] != '\0'
&& opt_variable_dir != NULL)
{
if (!pv_runtime_garbage_collect_legacy (opt_variable_dir,
opt_runtime_base,
&local_error))
{
g_warning ("Unable to clean up old runtimes: %s",
local_error->message);
g_clear_error (&local_error);
}
}
if (opt_runtime != NULL || opt_runtime_archive != NULL)
{
PvRuntimeFlags flags = PV_RUNTIME_FLAGS_NONE;
g_autofree gchar *runtime_resolved = NULL;
const char *runtime_path = NULL;
if (opt_gc_runtimes)
flags |= PV_RUNTIME_FLAGS_GC_RUNTIMES;
......@@ -1663,9 +1811,33 @@ main (int argc,
if (opt_import_vulkan_layers)
flags |= PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS;
g_debug ("Configuring runtime %s...", opt_runtime);
if (opt_copy_runtime)
flags |= PV_RUNTIME_FLAGS_COPY_RUNTIME;
if (opt_runtime != NULL)
{
/* already checked for mutually exclusive options */
g_assert (opt_runtime_archive == NULL);
runtime_path = opt_runtime;
}
else
{
flags |= PV_RUNTIME_FLAGS_UNPACK_ARCHIVE;
runtime_path = opt_runtime_archive;
}
if (!g_path_is_absolute (runtime_path)
&& opt_runtime_base != NULL
&& opt_runtime_base[0] != '\0')
{
runtime_resolved = g_build_filename (opt_runtime_base,
runtime_path, NULL);
runtime_path = runtime_resolved;
}
g_debug ("Configuring runtime %s...", runtime_path);
if (is_flatpak_env && opt_copy_runtime_into == NULL)
if (is_flatpak_env && !opt_copy_runtime)
{
glnx_throw (error,
"Cannot set up a runtime inside Flatpak without "
......@@ -1673,8 +1845,9 @@ main (int argc,
goto out;
}
runtime = pv_runtime_new (opt_runtime,
opt_copy_runtime_into,
runtime = pv_runtime_new (runtime_path,
opt_runtime_id,
opt_variable_dir,
bwrap_executable,
tools_dir,
opt_graphics_provider,
......@@ -2539,9 +2712,12 @@ out:
g_clear_pointer (&opt_steam_app_id, g_free);
g_clear_pointer (&opt_home, g_free);
g_clear_pointer (&opt_fake_home, g_free);
g_clear_pointer (&opt_runtime_base, g_free);
g_clear_pointer (&opt_runtime, g_free);
g_clear_pointer (&opt_runtime_archive, g_free);
g_clear_pointer (&opt_runtime_base, g_free);
g_clear_pointer (&opt_runtime_id, g_free);
g_clear_pointer (&opt_pass_fds, g_array_unref);
g_clear_pointer (&opt_variable_dir, g_free);
g_debug ("Exiting with status %d", ret);
return ret;
......
......@@ -482,6 +482,7 @@ class TestContainers(BaseTest):
*,
copy: bool = False,
gc: bool = True,
gc_legacy: bool = True,
locales: bool = False,
only_prepare: bool = False
) -> None:
......@@ -490,6 +491,7 @@ class TestContainers(BaseTest):
runtime,
copy=copy,
gc=gc,
gc_legacy=gc_legacy,
is_scout=True,
locales=locales,
only_prepare=only_prepare,
......@@ -502,6 +504,7 @@ class TestContainers(BaseTest):
*,
copy: bool = False,
gc: bool = True,
gc_legacy: bool = True,
locales: bool = False,
only_prepare: bool = False
) -> None:
......@@ -510,6 +513,7 @@ class TestContainers(BaseTest):
runtime,
copy=copy,
gc=gc,
gc_legacy=gc_legacy,
is_soldier=True,
locales=locales,
only_prepare=only_prepare,
......@@ -520,8 +524,11 @@ class TestContainers(BaseTest):
test_name: str,
runtime: str,
*,
archive: str = '',
copy: bool = False,
fast_path: bool = False,
gc: bool = True,
gc_legacy: bool = True,
is_scout: bool = False,
is_soldier: bool = False,
locales: bool = False,
......@@ -530,8 +537,15 @@ class TestContainers(BaseTest):
if self.bwrap is None and not only_prepare:
self.skipTest('Unable to run bwrap (in a container?)')
if not os.path.isdir(runtime):
self.skipTest('{} not found'.format(runtime))
if archive:
if not os.path.isfile(archive):
self.skipTest('{} not found'.format(archive))
if fast_path and not os.path.isdir(runtime):
self.skipTest('{} not found'.format(runtime))
else:
if not os.path.isdir(runtime):
self.skipTest('{} not found'.format(runtime))
artifacts = os.path.join(
self.artifacts,
......@@ -543,23 +557,74 @@ class TestContainers(BaseTest):
argv = [
self.pv_wrap,
'--runtime', runtime,
'--verbose',
'--write-final-argv', final_argv_temp.name
]
var = os.path.join(self.containers_dir, 'var')
os.makedirs(var, exist_ok=True)
if archive and (fast_path or copy):
argv.extend([
'--runtime-archive', archive,
# For simplicity, we rely on this below
'--runtime-id', 'myruntime_0.1.2',
])
elif archive:
# Assume the archive is accompanied by a -buildid.txt file
argv.extend([
'--runtime-archive', archive,
])
else:
argv.extend(['--runtime', runtime])
var_dir = os.path.join(self.containers_dir, 'var')
os.makedirs(var_dir, exist_ok=True)
if not locales:
argv.append('--no-generate-locales')
with tempfile.TemporaryDirectory(prefix='test-', dir=var) as temp:
with tempfile.TemporaryDirectory(
prefix='test-', dir=var_dir
) as temp, tempfile.TemporaryDirectory(
prefix='test-mock-base-', dir=var_dir
) as mock_base:
argv.extend(['--runtime-base', mock_base])
argv.extend(['--variable-dir', temp])
if fast_path:
# Pretend we had already run this runtime. Rather than
# using the real runtime's name, for simplicity this is
# a fake name.
old_dir = os.path.join(temp, 'deploy-myruntime_0.1.2')
run_subprocess(
['cp', '-al', runtime, old_dir],
check=True,
)
if os.path.isdir(os.path.join(old_dir, 'files')):
old_dir = os.path.join(old_dir, 'files')
if os.path.isdir(os.path.join(old_dir, 'usr')):
old_dir = os.path.join(old_dir, 'usr')
# Touch a flag file so we can detect that we reused the
# old deployment
with open(os.path.join(old_dir, 'OLD-DEPLOYMENT'), 'w'):
pass
if copy:
argv.extend(['--copy-runtime-into', temp])
argv.append('--copy-runtime')
else:
argv.append('--no-copy-runtime')
if not gc:
argv.append('--no-gc-runtimes')
if gc:
argv.append('--gc-runtimes')
else:
argv.append('--no-gc-runtimes')
if gc_legacy:
argv.append('--gc-legacy-runtimes')
else:
argv.append('--no-gc-legacy-runtimes')
if is_scout:
python = 'python3.5'
......@@ -595,7 +660,7 @@ class TestContainers(BaseTest):
os.makedirs(os.path.join(temp, 'tmp-deleteme'), exist_ok=True)
# Delete, and assert that it is recursive
os.makedirs(
os.path.join(temp, 'tmp-deleteme2', 'usr', 'lib'),
os.path.join(temp, 'deploy-deleteme', 'usr', 'lib'),
exist_ok=True,
)
# Do not delete because it has ./keep
......@@ -605,6 +670,34 @@ class TestContainers(BaseTest):
# Do not delete because we will write-lock .ref
os.makedirs(os.path.join(temp, 'tmp-wlock'), exist_ok=True)
for d in [mock_base, temp]:
os.makedirs(
os.path.join(d, 'scout_before_0.20200101.0'),
exist_ok=True,
)
os.makedirs(
os.path.join(d, 'soldier_0.20200101.0'),
exist_ok=True,
)
os.makedirs(
os.path.join(d, '.scout_0.20200202.0_unpack-temp'),
exist_ok=True,
)
os.makedirs(
os.path.join(d, '.soldier_dontdelete'),
exist_ok=True,
)
os.makedirs(
os.path.join(d, 'scout_dontdelete'),
exist_ok=True,
)
os.makedirs(
os.path.join(d, 'soldier_0.20200101.0_keep', 'keep'),
exist_ok=True,
)
os.symlink('soldier_0.20200101.0', os.path.join(d, 'soldier'))
os.symlink('scout_dontdelete', os.path.join(d, 'scout'))
with open(
os.path.join(temp, 'tmp-rlock', '.ref'), 'w+'
) as rlock_writer, open(
......@@ -655,40 +748,82 @@ class TestContainers(BaseTest):
final_argv_temp.close()
for d in [mock_base, temp]:
members = set(os.listdir(d))
if gc_legacy:
self.assertNotIn('scout_before_0.20200101.0', members)
self.assertNotIn('soldier', members)
self.assertNotIn('soldier_0.20200101.0', members)
self.assertNotIn(
'.scout_0.20200202.0_unpack-temp', members,
)
else:
self.assertIn('.scout_0.20200202.0_unpack-temp', members)
self.assertIn('scout_before_0.20200101.0', members)
self.assertIn('soldier', members)
self.assertIn('soldier_0.20200101.0', members)
self.assertIn('.soldier_dontdelete', members)
self.assertIn('scout', members)
self.assertIn('scout_dontdelete', members)
self.assertIn('soldier_0.20200101.0_keep', members)
if copy:
members = set(os.listdir(temp))
self.assertIn('.ref', members)
if fast_path:
self.assertIn('deploy-myruntime_0.1.2', members)
self.assertIn('donotdelete', members)
self.assertIn('tmp-keep', members)
self.assertIn('tmp-rlock', members)
self.assertIn('tmp-wlock', members)
if gc:
if archive:
self.assertNotIn('deploy-deleteme', members)
self.assertNotIn('tmp-deleteme', members)
self.assertNotIn('tmp-deleteme2', members)
else:
# These would have been deleted if not for --no-gc-runtimes
self.assertIn('deploy-deleteme', members)
self.assertIn('tmp-deleteme', members)
self.assertIn('tmp-deleteme2', members)
members.discard('.ref')
members.discard('.scout_0.20200202.0_unpack-temp')
members.discard('.soldier_dontdelete')
members.discard('donotdelete')
members.discard('scout')
members.discard('scout_before_0.20200101.0')
members.discard('scout_dontdelete')
members.discard('soldier')
members.discard('soldier_0.20200101.0')
members.discard('soldier_0.20200101.0_keep')
members.discard('tmp-deleteme')
members.discard('tmp-deleteme2')
members.discard('tmp-keep')
members.discard('tmp-rlock')
members.discard('tmp-wlock')
members.discard('deploy-deleteme')
members.discard('deploy-myruntime_0.1.2')
# After discarding those, there should be exactly one left:
# the one we just created
self.assertEqual(len(members), 1)
tree = os.path.join(temp, members.pop())
if fast_path:
require_flag_file = 'OLD-DEPLOYMENT'
else:
require_flag_file = ''
with self.subTest('mutable sysroot'):
self._assert_mutable_sysroot(
tree,
artifacts,
is_scout=is_scout,
is_soldier=is_soldier,
require_flag_file=require_flag_file,
)
def _assert_mutable_sysroot(
......@@ -697,7 +832,8 @@ class TestContainers(BaseTest):
artifacts: str,
*,
is_scout: bool = False,
is_soldier: bool = False
is_soldier: bool = False,
require_flag_file: str = ''
) -> None:
with open(
os.path.join(artifacts, 'contents.txt'),
......@@ -718,6 +854,11 @@ class TestContainers(BaseTest):
)
self.assertTrue(os.path.isdir(os.path.join(tree, 'sbin')))
if require_flag_file:
self.assertTrue(
os.path.exists(os.path.join(tree, 'usr', require_flag_file)),
)
target = os.readlink(os.path.join(tree, 'overrides'))
self.assertEqual(target, 'usr/lib/pressure-vessel/overrides')
self.assertTrue(
......@@ -1088,7 +1229,7 @@ class TestContainers(BaseTest):
with self.subTest('copy'):
self._test_scout(
'scout_sysroot_copy_usrmerge', scout,
copy=True, gc=False,
copy=True, gc=False, gc_legacy=False,
)
with self.subTest('transient'):
......@@ -1106,6 +1247,38 @@ class TestContainers(BaseTest):
with self.subTest('transient'):
self._test_scout('scout', scout)
def test_unpack(self) -> None:
scout = os.path.join(
self.containers_dir,
'scout',
)
archive = os.path.join(
self.containers_dir,
('com.valvesoftware.SteamRuntime.Platform-amd64,i386-'
'scout-runtime.tar.gz'),
)
self._test_container(
'scout_unpack',
scout,
archive=archive,
copy=True,
is_scout=True,
locales=False,
only_prepare=True,
)
self._test_container(
'scout_skip_unpack',
scout,
archive=archive,
copy=True,
fast_path=True,
is_scout=True,
locales=False,
only_prepare=True,
)
def test_soldier_sysroot(self) -> None:
soldier = os.path.join(self.containers_dir, 'soldier_sysroot')
......
......@@ -38,7 +38,7 @@
typedef struct
{
int unused;
TestsOpenFdSet old_fds;
} Fixture;
typedef struct
......@@ -51,6 +51,8 @@ setup (Fixture *f,
gconstpointer context)
{
G_GNUC_UNUSED const Config *config = context;
f->old_fds = tests_check_fd_leaks_enter ();
}
static void
......@@ -58,6 +60,8 @@ teardown (Fixture *f,
gconstpointer context)
{
G_GNUC_UNUSED const Config *config = context;
tests_check_fd_leaks_leave (f->old_fds);
}
static void
......@@ -162,6 +166,68 @@ test_capture_output (Fixture *f,
g_clear_pointer (&output, g_free);
}
static void
test_delete_dangling_symlink (Fixture *f,
gconstpointer context)
{
g_autoptr(GError) error = NULL;
g_auto(GLnxTmpDir) tmpdir = { FALSE };
struct stat stat_buf;
glnx_mkdtemp ("test-XXXXXX", 0700, &tmpdir, &error);
g_assert_no_error (error);
glnx_file_replace_contents_at (tmpdir.fd, "exists", (const guint8 *) "",
0, 0, NULL, &error);
g_assert_no_error (error);
g_assert_no_errno (mkdirat (tmpdir.fd, "subdir", 0755));
g_assert_no_errno (symlinkat ("exists", tmpdir.fd, "target-exists"));
g_assert_no_errno (symlinkat ("does-not-exist", tmpdir.fd,
"target-does-not-exist"));
g_assert_no_errno (symlinkat ("/etc/ssl/private/nope", tmpdir.fd,
"cannot-stat-target"));
pv_delete_dangling_symlink (tmpdir.fd, tmpdir.path, "cannot-stat-target");
pv_delete_dangling_symlink (tmpdir.fd, tmpdir.path, "does-not-exist");
pv_delete_dangling_symlink (tmpdir.fd, tmpdir.path, "exists");
pv_delete_dangling_symlink (tmpdir.fd, tmpdir.path, "subdir");
pv_delete_dangling_symlink (tmpdir.fd, tmpdir.path, "target-does-not-exist");
pv_delete_dangling_symlink (tmpdir.fd, tmpdir.path, "target-exists");
/* We cannot tell whether ./cannot-stat-target is dangling or not
* (assuming we're not root) so we give it the benefit of the doubt
* and do not delete it */
if (G_LIKELY (stat ("/etc/ssl/private/nope", &stat_buf) < 0
&& errno == EACCES))
{
g_assert_no_errno (fstatat (tmpdir.fd, "cannot-stat-target", &stat_buf,
AT_SYMLINK_NOFOLLOW));
}
/* ./does-not-exist never existed */
g_assert_cmpint (fstatat (tmpdir.fd, "does-not-exist", &stat_buf,
AT_SYMLINK_NOFOLLOW) == 0 ? 0 : errno,
==, ENOENT);
/* ./exists is not a symlink and so was not deleted */
g_assert_no_errno (fstatat (tmpdir.fd, "exists", &stat_buf,
AT_SYMLINK_NOFOLLOW));
/* ./subdir is not a symlink and so was not deleted */
g_assert_no_errno (fstatat (tmpdir.fd, "subdir", &stat_buf,
AT_SYMLINK_NOFOLLOW));
/* ./target-does-not-exist is a dangling symlink and so was deleted */
g_assert_cmpint (fstatat (tmpdir.fd, "target-does-not-exist", &stat_buf,
AT_SYMLINK_NOFOLLOW) == 0 ? 0 : errno,
==, ENOENT);
/* ./target-exists is a non-dangling symlink and so was not deleted */
g_assert_no_errno (fstatat (tmpdir.fd, "target-exists", &stat_buf,
AT_SYMLINK_NOFOLLOW));
}
static void
test_envp_cmp (Fixture *f,
gconstpointer context)
......@@ -307,6 +373,8 @@ main (int argc,
setup, test_arbitrary_key, teardown);
g_test_add ("/capture-output", Fixture, NULL,
setup, test_capture_output, teardown);
g_test_add ("/delete-dangling-symlink", Fixture, NULL,
setup, test_delete_dangling_symlink, teardown);
g_test_add ("/envp-cmp", Fixture, NULL, setup, test_envp_cmp, teardown);
g_test_add ("/get-path-after", Fixture, NULL,
setup, test_get_path_after, teardown);
......
......@@ -56,6 +56,11 @@
#define g_assert_cmpstr(a, op, b) g_assert (g_strcmp0 ((a), (b)) op 0)
#endif
#ifndef g_assert_no_errno
#define g_assert_no_errno(expr) \
g_assert_cmpstr ((expr) >= 0 ? NULL : g_strerror (errno), ==, NULL)
#endif
/*
* Other assorted test helpers.
*/
......