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
Select Git revision
  • 0.20240321.x
  • main
  • v0.20231013.x
  • wip/case_insensitive_fs
  • wip/denittis/line_based
  • wip/denittis/portal_helper
  • wip/denittis/t17372
  • wip/denittis/t20052
  • wip/denittis/t20177
  • wip/denittis/t7017
  • wip/denittis/tasks239
  • wip/denittis/up_libcapsule
  • wip/diagnose-task724
  • wip/fix_archlinux_ci
  • wip/flatpak_exports_for_run_host
  • wip/gameoverlayrenderer
  • wip/input-device-diagnostic
  • wip/input-portal
  • wip/issue18
  • wip/luigipiucco/nixos
  • wip/protected/ci
  • wip/protected/scout-on-soldier
  • wip/protected/smcv-ci
  • wip/protected/smcv-merge-slr
  • wip/protected/task679
  • wip/refi64/check-steam-running
  • wip/refi64/ci-coverage-demo
  • wip/refi64/mr_template_test
  • wip/refi64/openxr
  • wip/refi64/openxr-sysinfo
  • wip/refi64/vk-layers
  • wip/regenerate-ldso-cache
  • wip/shorten_socket
  • wip/smcv/always-copy-runtime
  • wip/smcv/ci
  • wip/smcv/clang
  • wip/smcv/delete-in-background
  • wip/smcv/dev-lib-symlinks
  • wip/smcv/diagnose-sfl10602
  • wip/smcv/diffable
  • wip/smcv/doc
  • wip/smcv/docs
  • wip/smcv/fex-3
  • wip/smcv/gcc11
  • wip/smcv/guessed-library-arch
  • wip/smcv/inspect-library
  • wip/smcv/into-library
  • wip/smcv/ld-so-wrapper
  • wip/smcv/lxc
  • wip/smcv/no-nvidia-pkcs11
  • wip/smcv/pv-wrap-to-journal
  • wip/smcv/refcount-locks
  • wip/smcv/relocatable-bin
  • wip/smcv/spawn-really-async
  • wip/smcv/staging
  • wip/smcv/steam-portal
  • wip/smcv/task149-nonfhs
  • wip/smcv/task212-basename
  • wip/smcv/task370-debug
  • wip/smcv/test-diagnostics
  • wip/smcv/variant-init-statically
  • wip/smcv/vulkan-1-1
  • wip/sr640
  • wip/sr684
  • wip/task293-v1
  • wip/task342-3
  • wip/task466
  • wip/task466-more
  • wip/task481-test
  • wip/task488-test
  • wip/task595
  • wip/task634
  • wip/task647
  • wip/task88
  • wip/task88-pre
  • wip/vulkan_shaders
  • wip/xytovl/openxr
  • pressure-vessel-v0.20180430.0
  • pressure-vessel-v0.20180517.0
  • pressure-vessel-v0.20180517.1
  • pressure-vessel-v0.20180518.0
  • pressure-vessel-v0.20180518.1
  • pressure-vessel-v0.20190510.0
  • pressure-vessel-v0.20190815.0
  • pressure-vessel-v0.20190819.0
  • pressure-vessel-v0.20190820.0
  • pressure-vessel-v0.20190822.0
  • pressure-vessel-v0.20190823.0
  • pressure-vessel-v0.20190912.0
  • pressure-vessel-v0.20190926.0
  • pressure-vessel-v0.20190927.0
  • pressure-vessel-v0.20191112.0
  • pressure-vessel-v0.20191112.1
  • pressure-vessel-v0.20191115.0
  • pressure-vessel-v0.20191216.0
  • pressure-vessel-v0.20200108.0
  • pressure-vessel-v0.20200331.0
  • pressure-vessel-v0.20200624.0
  • pressure-vessel-v0.20200708.0
  • pressure-vessel-v0.20200709.0
  • pressure-vessel-v0.20200805.0
  • pressure-vessel-v0.20200819.0
  • pressure-vessel-v0.20200908.0
  • pressure-vessel-v0.20200909.0
  • steamrt/0.20190926.0-0+steamrt1.2
  • v0.20190717.0
  • v0.20190801.0
  • v0.20190802.0
  • v0.20190806.0
  • v0.20190816.0
  • v0.20190820.0
  • v0.20190820.1
  • v0.20190822.0
  • v0.20190909.0
  • v0.20190926.0
  • v0.20190926.1
  • v0.20191024.0
  • v0.20191120.0
  • v0.20191205.0
  • v0.20191209.0
  • v0.20200108.0
  • v0.20200109.0
  • v0.20200306.0
  • v0.20200331.0
  • v0.20200331.1
  • v0.20200415.0
  • v0.20200624.0
  • v0.20200908.0
  • v0.20200908.1
  • v0.20200914.0
  • v0.20201002.0
  • v0.20201007.0
  • v0.20201022.0
  • v0.20201124.0
  • v0.20201203.0
  • v0.20210105.0
  • v0.20210114.0
  • v0.20210126.0
  • v0.20210201.0
  • v0.20210203.0
  • v0.20210305.0
  • v0.20210312.0
  • v0.20210317.0
  • v0.20210331.0
  • v0.20210415.0
  • v0.20210430.0
  • v0.20210518.0
  • v0.20210608.0
  • v0.20210608.1
  • v0.20210608.2
  • v0.20210608.3
  • v0.20210617.0
  • v0.20210617.1
  • v0.20210630.0
  • v0.20210722.0
  • v0.20210806.0
  • v0.20210809.0
  • v0.20210809.1
  • v0.20210820.0
  • v0.20210906.0
  • v0.20210906.1
  • v0.20210906.2
  • v0.20211027.0
  • v0.20211207.0
  • v0.20220107.0
  • v0.20220111.0
  • v0.20220119.0
  • v0.20220131.0
  • v0.20220311.0
  • v0.20220315.0
  • v0.20220420.0
  • v0.20220601.0
  • v0.20220620.0
  • v0.20220624.0
  • v0.20220725.0
  • v0.20220725.1
  • v0.20220726.0
177 results

Target

Select target project
  • steamrt/steam-runtime-tools
1 result
Select Git revision
  • 0.20240321.x
  • main
  • v0.20231013.x
  • wip/case_insensitive_fs
  • wip/denittis/line_based
  • wip/denittis/portal_helper
  • wip/denittis/t17372
  • wip/denittis/t20052
  • wip/denittis/t20177
  • wip/denittis/t7017
  • wip/denittis/tasks239
  • wip/denittis/up_libcapsule
  • wip/diagnose-task724
  • wip/fix_archlinux_ci
  • wip/flatpak_exports_for_run_host
  • wip/gameoverlayrenderer
  • wip/input-device-diagnostic
  • wip/input-portal
  • wip/issue18
  • wip/luigipiucco/nixos
  • wip/protected/ci
  • wip/protected/scout-on-soldier
  • wip/protected/smcv-ci
  • wip/protected/smcv-merge-slr
  • wip/protected/task679
  • wip/refi64/check-steam-running
  • wip/refi64/ci-coverage-demo
  • wip/refi64/mr_template_test
  • wip/refi64/openxr
  • wip/refi64/openxr-sysinfo
  • wip/refi64/vk-layers
  • wip/regenerate-ldso-cache
  • wip/shorten_socket
  • wip/smcv/always-copy-runtime
  • wip/smcv/ci
  • wip/smcv/clang
  • wip/smcv/delete-in-background
  • wip/smcv/dev-lib-symlinks
  • wip/smcv/diagnose-sfl10602
  • wip/smcv/diffable
  • wip/smcv/doc
  • wip/smcv/docs
  • wip/smcv/fex-3
  • wip/smcv/gcc11
  • wip/smcv/guessed-library-arch
  • wip/smcv/inspect-library
  • wip/smcv/into-library
  • wip/smcv/ld-so-wrapper
  • wip/smcv/lxc
  • wip/smcv/no-nvidia-pkcs11
  • wip/smcv/pv-wrap-to-journal
  • wip/smcv/refcount-locks
  • wip/smcv/relocatable-bin
  • wip/smcv/spawn-really-async
  • wip/smcv/staging
  • wip/smcv/steam-portal
  • wip/smcv/task149-nonfhs
  • wip/smcv/task212-basename
  • wip/smcv/task370-debug
  • wip/smcv/test-diagnostics
  • wip/smcv/variant-init-statically
  • wip/smcv/vulkan-1-1
  • wip/sr640
  • wip/sr684
  • wip/task293-v1
  • wip/task342-3
  • wip/task466
  • wip/task466-more
  • wip/task481-test
  • wip/task488-test
  • wip/task595
  • wip/task634
  • wip/task647
  • wip/task88
  • wip/task88-pre
  • wip/vulkan_shaders
  • wip/xytovl/openxr
  • pressure-vessel-v0.20180430.0
  • pressure-vessel-v0.20180517.0
  • pressure-vessel-v0.20180517.1
  • pressure-vessel-v0.20180518.0
  • pressure-vessel-v0.20180518.1
  • pressure-vessel-v0.20190510.0
  • pressure-vessel-v0.20190815.0
  • pressure-vessel-v0.20190819.0
  • pressure-vessel-v0.20190820.0
  • pressure-vessel-v0.20190822.0
  • pressure-vessel-v0.20190823.0
  • pressure-vessel-v0.20190912.0
  • pressure-vessel-v0.20190926.0
  • pressure-vessel-v0.20190927.0
  • pressure-vessel-v0.20191112.0
  • pressure-vessel-v0.20191112.1
  • pressure-vessel-v0.20191115.0
  • pressure-vessel-v0.20191216.0
  • pressure-vessel-v0.20200108.0
  • pressure-vessel-v0.20200331.0
  • pressure-vessel-v0.20200624.0
  • pressure-vessel-v0.20200708.0
  • pressure-vessel-v0.20200709.0
  • pressure-vessel-v0.20200805.0
  • pressure-vessel-v0.20200819.0
  • pressure-vessel-v0.20200908.0
  • pressure-vessel-v0.20200909.0
  • steamrt/0.20190926.0-0+steamrt1.2
  • v0.20190717.0
  • v0.20190801.0
  • v0.20190802.0
  • v0.20190806.0
  • v0.20190816.0
  • v0.20190820.0
  • v0.20190820.1
  • v0.20190822.0
  • v0.20190909.0
  • v0.20190926.0
  • v0.20190926.1
  • v0.20191024.0
  • v0.20191120.0
  • v0.20191205.0
  • v0.20191209.0
  • v0.20200108.0
  • v0.20200109.0
  • v0.20200306.0
  • v0.20200331.0
  • v0.20200331.1
  • v0.20200415.0
  • v0.20200624.0
  • v0.20200908.0
  • v0.20200908.1
  • v0.20200914.0
  • v0.20201002.0
  • v0.20201007.0
  • v0.20201022.0
  • v0.20201124.0
  • v0.20201203.0
  • v0.20210105.0
  • v0.20210114.0
  • v0.20210126.0
  • v0.20210201.0
  • v0.20210203.0
  • v0.20210305.0
  • v0.20210312.0
  • v0.20210317.0
  • v0.20210331.0
  • v0.20210415.0
  • v0.20210430.0
  • v0.20210518.0
  • v0.20210608.0
  • v0.20210608.1
  • v0.20210608.2
  • v0.20210608.3
  • v0.20210617.0
  • v0.20210617.1
  • v0.20210630.0
  • v0.20210722.0
  • v0.20210806.0
  • v0.20210809.0
  • v0.20210809.1
  • v0.20210820.0
  • v0.20210906.0
  • v0.20210906.1
  • v0.20210906.2
  • v0.20211027.0
  • v0.20211207.0
  • v0.20220107.0
  • v0.20220111.0
  • v0.20220119.0
  • v0.20220131.0
  • v0.20220311.0
  • v0.20220315.0
  • v0.20220420.0
  • v0.20220601.0
  • v0.20220620.0
  • v0.20220624.0
  • v0.20220725.0
  • v0.20220725.1
  • v0.20220726.0
177 results
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')
......
......@@ -54,6 +54,8 @@ struct _PvRuntime
GObject parent;
gchar *bubblewrap;
gchar *source;
gchar *id;
gchar *deployment;
gchar *source_files; /* either deployment or that + "/files" */
gchar *tools_dir;
......@@ -61,7 +63,7 @@ struct _PvRuntime
GStrv original_environ;
gchar *libcapsule_knowledge;
gchar *mutable_parent;
gchar *variable_dir;
gchar *mutable_sysroot;
gchar *tmpdir;
gchar *overrides;
......@@ -78,7 +80,7 @@ struct _PvRuntime
const gchar *host_in_current_namespace;
PvRuntimeFlags flags;
int mutable_parent_fd;
int variable_dir_fd;
int mutable_sysroot_fd;
int provider_fd;
gboolean any_libc_from_provider;
......@@ -96,10 +98,11 @@ struct _PvRuntimeClass
enum {
PROP_0,
PROP_BUBBLEWRAP,
PROP_DEPLOYMENT,
PROP_SOURCE,
PROP_ORIGINAL_ENVIRON,
PROP_FLAGS,
PROP_MUTABLE_PARENT,
PROP_ID,
PROP_VARIABLE_DIR,
PROP_PROVIDER_IN_CURRENT_NAMESPACE,
PROP_PROVIDER_IN_CONTAINER_NAMESPACE,
PROP_TOOLS_DIRECTORY,
......@@ -373,7 +376,7 @@ pv_runtime_init (PvRuntime *self)
{
self->any_libc_from_provider = FALSE;
self->all_libc_from_provider = FALSE;
self->mutable_parent_fd = -1;
self->variable_dir_fd = -1;
self->mutable_sysroot_fd = -1;
}
......@@ -399,8 +402,12 @@ pv_runtime_get_property (GObject *object,
g_value_set_flags (value, self->flags);
break;
case PROP_MUTABLE_PARENT:
g_value_set_string (value, self->mutable_parent);
case PROP_ID:
g_value_set_string (value, self->id);
break;
case PROP_VARIABLE_DIR:
g_value_set_string (value, self->variable_dir);
break;
case PROP_PROVIDER_IN_CURRENT_NAMESPACE:
......@@ -411,8 +418,8 @@ pv_runtime_get_property (GObject *object,
g_value_set_string (value, self->provider_in_container_namespace);
break;
case PROP_DEPLOYMENT:
g_value_set_string (value, self->deployment);
case PROP_SOURCE:
g_value_set_string (value, self->source);
break;
case PROP_TOOLS_DIRECTORY:
......@@ -452,25 +459,31 @@ pv_runtime_set_property (GObject *object,
self->flags = g_value_get_flags (value);
break;
case PROP_MUTABLE_PARENT:
case PROP_VARIABLE_DIR:
/* Construct-only */
g_return_if_fail (self->mutable_parent == NULL);
g_return_if_fail (self->variable_dir == NULL);
path = g_value_get_string (value);
if (path != NULL)
{
self->mutable_parent = realpath (path, NULL);
self->variable_dir = realpath (path, NULL);
if (self->mutable_parent == NULL)
if (self->variable_dir == NULL)
{
/* It doesn't exist. Keep the non-canonical path so we
* can warn about it later */
self->mutable_parent = g_strdup (path);
self->variable_dir = g_strdup (path);
}
}
break;
case PROP_ID:
/* Construct-only */
g_return_if_fail (self->id == NULL);
self->id = g_value_dup_string (value);
break;
case PROP_PROVIDER_IN_CURRENT_NAMESPACE:
/* Construct-only */
g_return_if_fail (self->provider_in_current_namespace == NULL);
......@@ -483,20 +496,20 @@ pv_runtime_set_property (GObject *object,
self->provider_in_container_namespace = g_value_dup_string (value);
break;
case PROP_DEPLOYMENT:
case PROP_SOURCE:
/* Construct-only */
g_return_if_fail (self->deployment == NULL);
g_return_if_fail (self->source == NULL);
path = g_value_get_string (value);
if (path != NULL)
{
self->deployment = realpath (path, NULL);
self->source = realpath (path, NULL);
if (self->deployment == NULL)
if (self->source == NULL)
{
/* It doesn't exist. Keep the non-canonical path so we
* can warn about it later */
self->deployment = g_strdup (path);
self->source = g_strdup (path);
}
}
......@@ -524,36 +537,238 @@ pv_runtime_constructed (GObject *object)
g_return_if_fail (self->original_environ != NULL);
g_return_if_fail (self->provider_in_current_namespace != NULL);
g_return_if_fail (self->provider_in_container_namespace != NULL);
g_return_if_fail (self->deployment != NULL);
g_return_if_fail (self->source != NULL);
g_return_if_fail (self->tools_dir != NULL);
}
static void
pv_runtime_maybe_garbage_collect_subdir (const char *description,
const char *parent,
int parent_fd,
const char *member)
{
g_autoptr(GError) local_error = NULL;
g_autoptr(PvBwrapLock) temp_lock = NULL;
g_autofree gchar *keep = NULL;
g_autofree gchar *ref = NULL;
struct stat ignore;
g_return_if_fail (parent != NULL);
g_return_if_fail (parent_fd >= 0);
g_return_if_fail (member != NULL);
g_debug ("Found %s %s/%s, considering whether to delete it...",
description, parent, member);
keep = g_build_filename (member, "keep", NULL);
if (glnx_fstatat (parent_fd, keep, &ignore,
AT_SYMLINK_NOFOLLOW, &local_error))
{
g_debug ("Not deleting \"%s/%s\": ./keep exists",
parent, member);
return;
}
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",
parent, member, local_error->message);
return;
}
g_clear_error (&local_error);
ref = g_build_filename (member, ".ref", NULL);
temp_lock = pv_bwrap_lock_new (parent_fd, ref,
(PV_BWRAP_LOCK_FLAGS_CREATE |
PV_BWRAP_LOCK_FLAGS_WRITE),
&local_error);
if (temp_lock == NULL)
{
g_info ("Not deleting \"%s/%s\": unable to get lock: %s",
parent, member, local_error->message);
return;
}
g_debug ("Deleting \"%s/%s\"...", parent, member);
/* 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 (parent_fd, member, NULL, &local_error))
{
g_debug ("Unable to delete %s/%s: %s",
parent, member, local_error->message);
}
}
static gboolean
is_old_runtime_deployment (const char *name)
{
if (g_str_has_prefix (name, "scout_before_"))
return TRUE;
if (g_str_has_prefix (name, "soldier_before_"))
return TRUE;
if (g_str_has_prefix (name, "scout_0."))
return TRUE;
if (g_str_has_prefix (name, "soldier_0."))
return TRUE;
if (g_str_has_prefix (name, ".scout_")
&& g_str_has_suffix (name, "_unpack-temp"))
return TRUE;
if (g_str_has_prefix (name, ".soldier_")
&& g_str_has_suffix (name, "_unpack-temp"))
return TRUE;
return FALSE;
}
gboolean
pv_runtime_garbage_collect_legacy (const char *variable_dir,
const char *runtime_base,
GError **error)
{
g_autoptr(GError) local_error = NULL;
g_autoptr(PvBwrapLock) variable_lock = NULL;
g_autoptr(PvBwrapLock) base_lock = NULL;
g_auto(GLnxDirFdIterator) variable_dir_iter = { FALSE };
g_auto(GLnxDirFdIterator) runtime_base_iter = { FALSE };
glnx_autofd int variable_dir_fd = -1;
glnx_autofd int runtime_base_fd = -1;
struct
{
const char *path;
GLnxDirFdIterator *iter;
} iters[] = {
{ variable_dir, &variable_dir_iter },
{ runtime_base, &runtime_base_iter },
};
gsize i;
g_return_val_if_fail (variable_dir != NULL, FALSE);
g_return_val_if_fail (runtime_base != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (!glnx_opendirat (AT_FDCWD, variable_dir, TRUE,
&variable_dir_fd, error))
return FALSE;
if (!glnx_opendirat (AT_FDCWD, runtime_base, TRUE,
&runtime_base_fd, error))
return FALSE;
variable_lock = pv_bwrap_lock_new (variable_dir_fd, ".ref",
(PV_BWRAP_LOCK_FLAGS_CREATE
| PV_BWRAP_LOCK_FLAGS_WRITE),
&local_error);
/* If we can't take the lock immediately, just don't do GC */
if (variable_lock == NULL)
return TRUE;
/* We take out locks on both the variable directory and the base
* directory, because historically in the shell scripts we only
* locked the base directory, and we later moved to locking only the
* variable directory. Now that we're in C code it seems safest to
* lock both. */
base_lock = pv_bwrap_lock_new (runtime_base_fd, ".ref",
(PV_BWRAP_LOCK_FLAGS_CREATE
| PV_BWRAP_LOCK_FLAGS_WRITE),
&local_error);
/* Same here */
if (base_lock == NULL)
return TRUE;
for (i = 0; i < G_N_ELEMENTS (iters); i++)
{
const char * const symlinks[] = { "scout", "soldier" };
gsize j;
if (!glnx_dirfd_iterator_init_at (AT_FDCWD, iters[i].path,
TRUE, iters[i].iter, error))
return FALSE;
g_debug ("Cleaning up old subdirectories in %s...",
iters[i].path);
while (TRUE)
{
struct dirent *dent;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (iters[i].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",
iters[i].path, dent->d_name);
continue;
}
if (!is_old_runtime_deployment (dent->d_name))
continue;
pv_runtime_maybe_garbage_collect_subdir ("legacy runtime",
iters[i].path,
iters[i].iter->fd,
dent->d_name);
}
g_debug ("Cleaning up old symlinks in %s...",
iters[i].path);
for (j = 0; j < G_N_ELEMENTS (symlinks); j++)
pv_delete_dangling_symlink (iters[i].iter->fd, iters[i].path,
symlinks[j]);
}
return TRUE;
}
static gboolean
pv_runtime_garbage_collect (PvRuntime *self,
PvBwrapLock *mutable_parent_lock,
PvBwrapLock *variable_dir_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 (self->variable_dir != 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);
g_return_val_if_fail (variable_dir_lock != NULL, FALSE);
if (!glnx_dirfd_iterator_init_at (AT_FDCWD, self->mutable_parent,
if (!glnx_dirfd_iterator_init_at (AT_FDCWD, self->variable_dir,
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))
......@@ -576,126 +791,89 @@ pv_runtime_garbage_collect (PvRuntime *self,
case DT_UNKNOWN:
default:
g_debug ("Ignoring %s/%s: not a directory",
self->mutable_parent, dent->d_name);
self->variable_dir, dent->d_name);
continue;
}
if (!g_str_has_prefix (dent->d_name, "tmp-"))
if (g_str_has_prefix (dent->d_name, "deploy-"))
{
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);
/* Don't GC old deployments unless we know which one is current
* and therefore should not be deleted */
if (self->id == NULL)
{
g_debug ("Ignoring %s/deploy-*: current ID not known",
self->variable_dir);
continue;
}
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;
/* Don't GC the current deployment */
if (strcmp (dent->d_name + strlen ("deploy-"), self->id) == 0)
{
g_debug ("Ignoring %s/%s: is the current version",
self->variable_dir, dent->d_name);
continue;
}
}
else if (!g_error_matches (local_error, G_IO_ERROR,
G_IO_ERROR_NOT_FOUND))
else if (!g_str_has_prefix (dent->d_name, "tmp-"))
{
/* 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);
g_debug ("Ignoring %s/%s: not tmp-*",
self->variable_dir, dent->d_name);
continue;
}
g_clear_error (&local_error);
pv_runtime_maybe_garbage_collect_subdir ("temporary runtime",
self->variable_dir,
self->variable_dir_fd,
dent->d_name);
}
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);
return TRUE;
}
if (temp_lock == NULL)
{
g_info ("Not deleting \"%s/%s\": unable to get lock: %s",
self->mutable_parent, dent->d_name,
local_error->message);
g_clear_error (&local_error);
continue;
}
static gboolean
pv_runtime_init_variable_dir (PvRuntime *self,
GError **error)
{
/* Nothing to do in this case */
if (self->variable_dir == NULL)
return TRUE;
g_debug ("Deleting \"%s/%s\"...", self->mutable_parent, dent->d_name);
if (g_mkdir_with_parents (self->variable_dir, 0700) != 0)
return glnx_throw_errno_prefix (error, "Unable to create %s",
self->variable_dir);
/* 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;
}
}
if (!glnx_opendirat (AT_FDCWD, self->variable_dir, TRUE,
&self->variable_dir_fd, error))
return FALSE;
return TRUE;
}
static gboolean
pv_runtime_init_mutable (PvRuntime *self,
GError **error)
pv_runtime_create_copy (PvRuntime *self,
PvBwrapLock *variable_dir_lock,
GError **error)
{
g_autofree gchar *dest_usr = NULL;
g_autofree gchar *source_usr_subdir = NULL;
g_autofree gchar *temp_dir = NULL;
g_autoptr(GDir) dir = NULL;
g_autoptr(PvBwrapLock) copy_lock = NULL;
g_autoptr(PvBwrapLock) mutable_lock = NULL;
g_autoptr(PvBwrapLock) source_lock = NULL;
const char *member;
const char *source_usr;
glnx_autofd int temp_dir_fd = -1;
gboolean is_just_usr;
/* Nothing to do in this case */
if (self->mutable_parent == NULL)
return TRUE;
if (g_mkdir_with_parents (self->mutable_parent, 0700) != 0)
return glnx_throw_errno_prefix (error, "Unable to create %s",
self->mutable_parent);
if (!glnx_opendirat (AT_FDCWD, self->mutable_parent, TRUE,
&self->mutable_parent_fd, error))
return FALSE;
/* Lock the parent directory. Anything that directly manipulates the
* temporary runtimes is expected to do the same, so that
* it cannot be deleting temporary runtimes at the same time we're
* creating them.
*
* This is a read-mode lock: it's OK to create more than one temporary
* runtime in parallel, as long as nothing is deleting them
* concurrently. */
mutable_lock = pv_bwrap_lock_new (self->mutable_parent_fd, ".ref",
PV_BWRAP_LOCK_FLAGS_CREATE,
error);
if (mutable_lock == NULL)
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;
g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
g_return_val_if_fail (self->variable_dir != NULL, FALSE);
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_COPY_RUNTIME, 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 (variable_dir_lock != NULL, FALSE);
temp_dir = g_build_filename (self->mutable_parent, "tmp-XXXXXX", NULL);
temp_dir = g_build_filename (self->variable_dir, "tmp-XXXXXX", NULL);
if (g_mkdtemp (temp_dir) == NULL)
return glnx_throw_errno_prefix (error,
......@@ -828,14 +1006,210 @@ pv_runtime_init_mutable (PvRuntime *self,
return TRUE;
}
static gboolean
gstring_replace_suffix (GString *s,
const char *suffix,
const char *replacement)
{
gsize len = strlen (suffix);
if (s->len >= len
&& strcmp (&s->str[s->len - len], suffix) == 0)
{
g_string_truncate (s, s->len - len);
g_string_append (s, replacement);
return TRUE;
}
return FALSE;
}
/*
* mutable_lock: (out) (not optional):
*/
static gboolean
pv_runtime_unpack (PvRuntime *self,
PvBwrapLock **mutable_lock,
GError **error)
{
g_autoptr(GString) debug_tarball = NULL;
g_autofree gchar *deploy_basename = NULL;
g_autofree gchar *unpack_dir = NULL;
g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
g_return_val_if_fail (mutable_lock != NULL, FALSE);
g_return_val_if_fail (*mutable_lock == NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_return_val_if_fail (self->source != NULL, FALSE);
g_return_val_if_fail (self->variable_dir != NULL, FALSE);
g_return_val_if_fail (self->variable_dir_fd >= 0, FALSE);
g_return_val_if_fail (self->deployment == NULL, FALSE);
if (!g_file_test (self->source, G_FILE_TEST_IS_REGULAR))
return glnx_throw (error, "\"%s\" is not a regular file", self->source);
if (!g_str_has_suffix (self->source, ".tar.gz"))
return glnx_throw (error, "\"%s\" is not a .tar.gz file", self->source);
if (self->id == NULL)
{
g_autoptr(GString) build_id_file = g_string_new (self->source);
g_autofree char *id = NULL;
gsize len;
gsize i;
if (gstring_replace_suffix (build_id_file, "-runtime.tar.gz",
"-buildid.txt")
|| gstring_replace_suffix (build_id_file, "-sysroot.tar.gz",
"-buildid.txt"))
{
if (!g_file_get_contents (build_id_file->str, &id, &len, error))
{
g_prefix_error (error, "Unable to determine build ID from \"%s\": ",
build_id_file->str);
return FALSE;
}
if (len == 0)
return glnx_throw (error, "Build ID in \"%s\" is empty",
build_id_file->str);
for (i = 0; i < len; i++)
{
/* Ignore a trailing newline */
if (i + 1 == len && id[i] == '\n')
{
id[i] = '\0';
break;
}
/* Allow dot, dash or underscore, but not at the beginning */
if (i > 0 && strchr (".-_", id[i]) != NULL)
continue;
if (!g_ascii_isalnum (id[i]))
return glnx_throw (error, "Build ID in \"%s\" is invalid",
build_id_file->str);
}
self->id = g_steal_pointer (&id);
}
}
if (self->id == NULL)
return glnx_throw (error, "Cannot unpack archive without unique ID");
deploy_basename = g_strdup_printf ("deploy-%s", self->id);
self->deployment = g_build_filename (self->variable_dir,
deploy_basename, NULL);
/* Fast path: if we already unpacked it, nothing more to do! */
if (g_file_test (self->deployment, G_FILE_TEST_IS_DIR))
return TRUE;
/* Lock the parent directory. Anything that directly manipulates the
* unpacked runtimes is expected to do the same, so that
* it cannot be deleting unpacked runtimes at the same time we're
* creating them.
*
* This is an exclusive lock, to avoid two concurrent processes trying
* to unpack the same runtime. */
*mutable_lock = pv_bwrap_lock_new (self->variable_dir_fd, ".ref",
(PV_BWRAP_LOCK_FLAGS_CREATE
| PV_BWRAP_LOCK_FLAGS_WAIT),
error);
if (*mutable_lock == NULL)
return FALSE;
/* Slow path: we need to do this the hard way. */
unpack_dir = g_build_filename (self->variable_dir, "tmp-XXXXXX", NULL);
if (g_mkdtemp (unpack_dir) == NULL)
return glnx_throw_errno_prefix (error,
"Cannot create temporary directory \"%s\"",
unpack_dir);
g_info ("Unpacking \"%s\" into \"%s\"...", self->source, unpack_dir);
{
g_autoptr(FlatpakBwrap) tar = flatpak_bwrap_new (NULL);
flatpak_bwrap_add_args (tar,
"tar",
"--force-local",
"-C", unpack_dir,
NULL);
if (self->flags & PV_RUNTIME_FLAGS_VERBOSE)
flatpak_bwrap_add_arg (tar, "-v");
flatpak_bwrap_add_args (tar,
"-xf", self->source,
NULL);
flatpak_bwrap_finish (tar);
if (!pv_bwrap_run_sync (tar, NULL, error))
{
glnx_shutil_rm_rf_at (-1, unpack_dir, NULL, NULL);
return FALSE;
}
}
debug_tarball = g_string_new (self->source);
if (gstring_replace_suffix (debug_tarball, "-runtime.tar.gz",
"-debug.tar.gz")
&& g_file_test (debug_tarball->str, G_FILE_TEST_EXISTS))
{
g_autoptr(FlatpakBwrap) tar = flatpak_bwrap_new (NULL);
g_autoptr(GError) local_error = NULL;
g_autofree char *files_lib_debug = NULL;
files_lib_debug = g_build_filename (unpack_dir, "files", "lib",
"debug", NULL);
flatpak_bwrap_add_args (tar,
"tar",
"--force-local",
"-C", files_lib_debug,
NULL);
if (self->flags & PV_RUNTIME_FLAGS_VERBOSE)
flatpak_bwrap_add_arg (tar, "-v");
flatpak_bwrap_add_args (tar,
"-xf", debug_tarball->str,
"files/",
NULL);
flatpak_bwrap_finish (tar);
if (!pv_bwrap_run_sync (tar, NULL, &local_error))
g_debug ("Ignoring error unpacking detached debug symbols: %s",
local_error->message);
}
g_info ("Renaming \"%s\" to \"%s\"...", unpack_dir, deploy_basename);
if (!glnx_renameat (self->variable_dir_fd, unpack_dir,
self->variable_dir_fd, deploy_basename,
error))
{
glnx_shutil_rm_rf_at (-1, unpack_dir, NULL, NULL);
return FALSE;
}
return TRUE;
}
static gboolean
pv_runtime_initable_init (GInitable *initable,
GCancellable *cancellable G_GNUC_UNUSED,
GError **error)
{
PvRuntime *self = PV_RUNTIME (initable);
g_autoptr(PvBwrapLock) mutable_lock = NULL;
g_autofree gchar *contents = NULL;
g_autofree gchar *files_ref = NULL;
g_autofree gchar *os_release = NULL;
gsize len;
......@@ -850,11 +1224,24 @@ pv_runtime_initable_init (GInitable *initable,
self->bubblewrap);
}
if (self->mutable_parent != NULL
&& !g_file_test (self->mutable_parent, G_FILE_TEST_IS_DIR))
if (!pv_runtime_init_variable_dir (self, error))
return FALSE;
if (self->flags & PV_RUNTIME_FLAGS_UNPACK_ARCHIVE)
{
return glnx_throw (error, "\"%s\" is not a directory",
self->mutable_parent);
if (self->variable_dir_fd < 0)
return glnx_throw (error,
"Cannot unpack archive without variable directory");
if (!pv_runtime_unpack (self, &mutable_lock, error))
return FALSE;
/* Set by pv_runtime_unpack */
g_assert (self->deployment != NULL);
}
else
{
self->deployment = g_strdup (self->source);
}
if (!g_file_test (self->deployment, G_FILE_TEST_IS_DIR))
......@@ -893,17 +1280,66 @@ pv_runtime_initable_init (GInitable *initable,
* continue to be locked until all processes in the container exit.
* If we make a temporary mutable copy, we only hold this lock until
* setup has finished. */
files_ref = g_build_filename (self->source_files, ".ref", NULL);
self->runtime_lock = pv_bwrap_lock_new (AT_FDCWD, files_ref,
PV_BWRAP_LOCK_FLAGS_CREATE,
error);
if (self->runtime_lock == NULL)
{
g_autofree gchar *files_ref = NULL;
files_ref = g_build_filename (self->source_files, ".ref", NULL);
self->runtime_lock = pv_bwrap_lock_new (AT_FDCWD, files_ref,
PV_BWRAP_LOCK_FLAGS_CREATE,
error);
}
/* If the runtime is being deleted, ... don't use it, I suppose? */
if (self->runtime_lock == NULL)
return FALSE;
if (!pv_runtime_init_mutable (self, error))
return FALSE;
/* 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->variable_dir_fd >= 0
&& (self->flags & PV_RUNTIME_FLAGS_GC_RUNTIMES))
{
g_autoptr(GError) local_error = NULL;
/* Take out an exclusive lock for GC so that we will not conflict
* with other concurrent processes that are halfway through
* deploying or unpacking a runtime. */
if (mutable_lock == NULL)
mutable_lock = pv_bwrap_lock_new (self->variable_dir_fd, ".ref",
(PV_BWRAP_LOCK_FLAGS_CREATE
| PV_BWRAP_LOCK_FLAGS_WRITE),
&local_error);
if (mutable_lock == NULL)
g_debug ("Unable to take an exclusive lock, skipping GC: %s",
local_error->message);
else if (!pv_runtime_garbage_collect (self, mutable_lock, error))
return FALSE;
}
if (self->flags & PV_RUNTIME_FLAGS_COPY_RUNTIME)
{
if (self->variable_dir_fd < 0)
return glnx_throw (error,
"Cannot copy runtime without variable directory");
/* This time take out a non-exclusive lock: any number of processes
* can safely be creating their own temporary copy at the same
* time. If another process is doing GC, wait for it to finish,
* then take our lock. */
if (mutable_lock == NULL)
mutable_lock = pv_bwrap_lock_new (self->variable_dir_fd, ".ref",
(PV_BWRAP_LOCK_FLAGS_CREATE
| PV_BWRAP_LOCK_FLAGS_WAIT),
error);
if (mutable_lock == NULL)
return FALSE;
if (!pv_runtime_create_copy (self, mutable_lock, error))
return FALSE;
}
if (self->mutable_sysroot != NULL)
{
......@@ -995,13 +1431,16 @@ pv_runtime_initable_init (GInitable *initable,
}
}
if (!glnx_opendirat (-1, self->provider_in_current_namespace, FALSE,
&self->provider_fd, error))
return FALSE;
if (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK)
{
if (!glnx_opendirat (-1, self->provider_in_current_namespace, FALSE,
&self->provider_fd, error))
return FALSE;
/* Path that, when resolved in the host namespace, points to the provider */
self->provider_in_host_namespace =
pv_current_namespace_path_to_host_path (self->provider_in_current_namespace);
/* Path that, when resolved in the host namespace, points to the provider */
self->provider_in_host_namespace =
pv_current_namespace_path_to_host_path (self->provider_in_current_namespace);
}
/* If we are in a Flatpak environment we expect to have the host system
* mounted in `/run/host`. Otherwise we assume that the host system, in the
......@@ -1043,8 +1482,8 @@ pv_runtime_finalize (GObject *object)
g_free (self->bubblewrap);
g_strfreev (self->original_environ);
g_free (self->libcapsule_knowledge);
glnx_close_fd (&self->mutable_parent_fd);
g_free (self->mutable_parent);
glnx_close_fd (&self->variable_dir_fd);
g_free (self->variable_dir);
glnx_close_fd (&self->mutable_sysroot_fd);
g_free (self->mutable_sysroot);
glnx_close_fd (&self->provider_fd);
......@@ -1053,6 +1492,7 @@ pv_runtime_finalize (GObject *object)
g_free (self->provider_in_container_namespace);
g_free (self->runtime_files_on_host);
g_free (self->runtime_usr);
g_free (self->source);
g_free (self->source_files);
g_free (self->deployment);
g_free (self->tools_dir);
......@@ -1094,10 +1534,16 @@ pv_runtime_class_init (PvRuntimeClass *cls)
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
properties[PROP_MUTABLE_PARENT] =
g_param_spec_string ("mutable-parent", "Mutable parent",
("Path to a directory in which to create a "
"mutable copy of source-files, or NULL"),
properties[PROP_ID] =
g_param_spec_string ("id", "ID",
"Unique identifier of runtime to be unpacked",
NULL,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
properties[PROP_VARIABLE_DIR] =
g_param_spec_string ("variable-dir", "Variable directory",
("Path to directory for temporary files, or NULL"),
NULL,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
......@@ -1120,10 +1566,10 @@ pv_runtime_class_init (PvRuntimeClass *cls)
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
properties[PROP_DEPLOYMENT] =
g_param_spec_string ("deployment", "Deployment",
properties[PROP_SOURCE] =
g_param_spec_string ("source", "Source",
("Path to read-only runtime files (merged-/usr "
"or sysroot) in current namespace"),
"or sysroot) or archive, in current namespace"),
NULL,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
......@@ -1139,8 +1585,9 @@ pv_runtime_class_init (PvRuntimeClass *cls)
}
PvRuntime *
pv_runtime_new (const char *deployment,
const char *mutable_parent,
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,
......@@ -1149,7 +1596,7 @@ pv_runtime_new (const char *deployment,
PvRuntimeFlags flags,
GError **error)
{
g_return_val_if_fail (deployment != NULL, NULL);
g_return_val_if_fail (source != NULL, NULL);
g_return_val_if_fail (bubblewrap != NULL, NULL);
g_return_val_if_fail (tools_dir != NULL, NULL);
g_return_val_if_fail ((flags & ~(PV_RUNTIME_FLAGS_MASK)) == 0, NULL);
......@@ -1159,8 +1606,9 @@ pv_runtime_new (const char *deployment,
error,
"bubblewrap", bubblewrap,
"original-environ", original_environ,
"mutable-parent", mutable_parent,
"deployment", deployment,
"variable-dir", variable_dir,
"source", source,
"id", id,
"tools-directory", tools_dir,
"provider-in-current-namespace",
provider_in_current_namespace,
......@@ -1343,7 +1791,11 @@ pv_runtime_get_capsule_capture_libs (PvRuntime *self,
const gchar *ld_library_path;
g_autofree gchar *remap_usr = NULL;
g_autofree gchar *remap_lib = NULL;
FlatpakBwrap *ret = pv_bwrap_copy (self->container_access_adverb);
FlatpakBwrap *ret;
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, NULL);
ret = pv_bwrap_copy (self->container_access_adverb);
/* If we have a custom "LD_LIBRARY_PATH", we want to preserve
* it when calling capsule-capture-libs */
......@@ -1390,6 +1842,8 @@ collect_s2tc (PvRuntime *self,
s2tc,
NULL);
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
if (g_file_test (s2tc_in_current_namespace, G_FILE_TEST_EXISTS))
{
g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
......@@ -1481,6 +1935,42 @@ icd_details_free (IcdDetails *self)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (IcdDetails, icd_details_free)
/*
* @destination: (not nullable): where to capture the libraries
* @patterns: (not nullable): array of patterns for capsule-capture-libs
*/
static gboolean
pv_runtime_capture_libraries (PvRuntime *self,
RuntimeArchitecture *arch,
const gchar *destination,
GPtrArray *patterns,
GError **error)
{
g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
gsize i;
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (runtime_architecture_check_valid (arch), FALSE);
g_return_val_if_fail (destination != NULL, FALSE);
g_return_val_if_fail (patterns != NULL, FALSE);
if (!pv_runtime_provide_container_access (self, error))
return FALSE;
temp_bwrap = pv_runtime_get_capsule_capture_libs (self, arch);
flatpak_bwrap_add_args (temp_bwrap, "--dest", destination, NULL);
for (i = 0; i < patterns->len; i++)
flatpak_bwrap_add_arg (temp_bwrap, (gchar *)g_ptr_array_index (patterns, i));
flatpak_bwrap_finish (temp_bwrap);
if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
return FALSE;
return TRUE;
}
/*
* @sequence_number: numbered directory to use to disambiguate between
* colliding files with the same basename
......@@ -1488,11 +1978,16 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (IcdDetails, icd_details_free)
* @use_numbered_subdirs: (inout) (not optional): if %TRUE, use a
* numbered subdirectory per ICD, for the rare case where not all
* drivers have a unique basename or where order matters
* @use_subdir_for_soname: if %TRUE, the @requested_subdir will be always
* @use_subdir_for_kind_soname: if %TRUE, the @requested_subdir will be always
* used. If %FALSE, the @requested_subdir will not be used when the provided
* library is of the kind "ICD_KIND_SONAME".
* @dependency_patterns: (inout) (not nullable): array of patterns for
* capsule-capture-libs
* @search_path: (nullable): Add the parent directory of the resulting
* ICD to this search path if necessary
*
* Bind the provided @details ICD without its dependencies, and update
* @dependency_patterns with @details dependency pattern.
*/
static gboolean
bind_icd (PvRuntime *self,
......@@ -1502,6 +1997,7 @@ bind_icd (PvRuntime *self,
IcdDetails *details,
gboolean *use_numbered_subdirs,
gboolean use_subdir_for_kind_soname,
GPtrArray *dependency_patterns,
GString *search_path,
GError **error)
{
......@@ -1520,6 +2016,7 @@ bind_icd (PvRuntime *self,
gsize dir_elements_after = 0;
const gchar *subdir = requested_subdir;
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (runtime_architecture_check_valid (arch), FALSE);
g_return_val_if_fail (subdir != NULL, FALSE);
g_return_val_if_fail (details != NULL, FALSE);
......@@ -1530,6 +2027,7 @@ bind_icd (PvRuntime *self,
g_return_val_if_fail (details->paths_in_container[multiarch_index] == NULL,
FALSE);
g_return_val_if_fail (use_numbered_subdirs != NULL, FALSE);
g_return_val_if_fail (dependency_patterns != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_info ("Capturing loadable module: %s", details->resolved_library);
......@@ -1648,17 +2146,7 @@ bind_icd (PvRuntime *self,
pv_search_path_append (search_path, in_container);
}
temp_bwrap = pv_runtime_get_capsule_capture_libs (self, arch);
flatpak_bwrap_add_args (temp_bwrap,
"--dest", arch->libdir_in_current_namespace,
dependency_pattern,
NULL);
flatpak_bwrap_finish (temp_bwrap);
if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
return FALSE;
g_clear_pointer (&temp_bwrap, flatpak_bwrap_free);
g_ptr_array_add (dependency_patterns, g_steal_pointer (&dependency_pattern));
if (details->kinds[multiarch_index] == ICD_KIND_ABSOLUTE)
{
......@@ -1758,8 +2246,9 @@ bind_runtime_base (PvRuntime *self,
pv_environ_lock_env (container_env, "XDG_RUNTIME_DIR", xrd);
if (g_strcmp0 (self->provider_in_host_namespace, "/") != 0
|| g_strcmp0 (self->provider_in_container_namespace, "/run/host") != 0)
if ((self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK)
&& (g_strcmp0 (self->provider_in_host_namespace, "/") != 0
|| g_strcmp0 (self->provider_in_container_namespace, "/run/host") != 0))
{
g_autofree gchar *provider_etc = NULL;
......@@ -1816,7 +2305,8 @@ bind_runtime_base (PvRuntime *self,
if (g_strv_contains (from_host, dest))
continue;
if (g_strv_contains (from_provider, dest))
if ((self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK)
&& g_strv_contains (from_provider, dest))
continue;
full = g_build_filename (self->runtime_files,
......@@ -1915,34 +2405,37 @@ bind_runtime_base (PvRuntime *self,
NULL);
}
for (i = 0; from_provider[i] != NULL; i++)
if (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK)
{
const char *item = from_provider[i];
g_autoptr(GError) local_error = NULL;
g_autofree char *path_in_provider = NULL;
glnx_autofd int fd = -1;
for (i = 0; from_provider[i] != NULL; i++)
{
const char *item = from_provider[i];
g_autoptr(GError) local_error = NULL;
g_autofree char *path_in_provider = NULL;
glnx_autofd int fd = -1;
fd = _srt_resolve_in_sysroot (self->provider_fd, item,
SRT_RESOLVE_FLAGS_NONE,
&path_in_provider,
&local_error);
fd = _srt_resolve_in_sysroot (self->provider_fd, item,
SRT_RESOLVE_FLAGS_NONE,
&path_in_provider,
&local_error);
if (fd >= 0)
{
g_autofree char *host_path = NULL;
if (fd >= 0)
{
g_autofree char *host_path = NULL;
host_path = g_build_filename (self->provider_in_host_namespace,
path_in_provider, NULL);
flatpak_bwrap_add_args (bwrap,
"--ro-bind", host_path, item,
NULL);
}
else
{
g_debug ("Cannot resolve \"%s\" in \"%s\": %s",
item, self->provider_in_current_namespace,
local_error->message);
g_clear_error (&local_error);
host_path = g_build_filename (self->provider_in_host_namespace,
path_in_provider, NULL);
flatpak_bwrap_add_args (bwrap,
"--ro-bind", host_path, item,
NULL);
}
else
{
g_debug ("Cannot resolve \"%s\" in \"%s\": %s",
item, self->provider_in_current_namespace,
local_error->message);
g_clear_error (&local_error);
}
}
}
......@@ -2036,6 +2529,7 @@ pv_runtime_take_from_provider (PvRuntime *self,
GError **error)
{
g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (bwrap == NULL || !pv_bwrap_was_finished (bwrap), FALSE);
g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
......@@ -2466,6 +2960,7 @@ pv_runtime_take_ld_so_from_provider (PvRuntime *self,
g_autofree gchar *ld_so_relative_to_provider = NULL;
g_autofree gchar *ld_so_in_provider = NULL;
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
g_debug ("Making provider's ld.so visible in container");
......@@ -2584,6 +3079,7 @@ setup_json_manifest (PvRuntime *self,
gboolean need_provider_json = FALSE;
gsize i;
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
if (SRT_IS_VULKAN_LAYER (details->icd))
......@@ -2728,6 +3224,7 @@ setup_each_json_manifest (PvRuntime *self,
gsize j;
g_autofree gchar *write_to_dir = NULL;
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
write_to_dir = g_build_filename (self->overrides,
......@@ -2753,12 +3250,15 @@ setup_each_json_manifest (PvRuntime *self,
static gboolean
collect_vulkan_layers (PvRuntime *self,
const GPtrArray *layer_details,
GPtrArray *dependency_patterns,
RuntimeArchitecture *arch,
const gchar *dir_name,
GError **error)
{
gsize j;
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (dependency_patterns != NULL, FALSE);
g_return_val_if_fail (dir_name != NULL, FALSE);
for (j = 0; j < layer_details->len; j++)
......@@ -2834,9 +3334,8 @@ collect_vulkan_layers (PvRuntime *self,
}
}
if (!bind_icd (self, arch, j, dir_name, details,
&use_numbered_subdirs, use_subdir_for_kind_soname,
NULL, error))
if (!bind_icd (self, arch, j, dir_name, details, &use_numbered_subdirs,
use_subdir_for_kind_soname, dependency_patterns, NULL, error))
return FALSE;
}
......@@ -2880,8 +3379,6 @@ pv_runtime_get_ld_so (PvRuntime *self,
{
g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
g_autofree gchar *etc = NULL;
g_autofree gchar *provider_etc = NULL;
g_autofree gchar *provider_etc_dest = NULL;
/* Do it the hard way, by asking a process running in the
* container (or at least a container resembling the one we
......@@ -2906,22 +3403,28 @@ pv_runtime_get_ld_so (PvRuntime *self,
"/etc",
NULL);
if (!pv_bwrap_bind_usr (temp_bwrap,
self->provider_in_host_namespace,
self->provider_in_current_namespace,
self->provider_in_container_namespace,
error))
return FALSE;
if (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK)
{
g_autofree gchar *provider_etc = NULL;
g_autofree gchar *provider_etc_dest = NULL;
if (!pv_bwrap_bind_usr (temp_bwrap,
self->provider_in_host_namespace,
self->provider_in_current_namespace,
self->provider_in_container_namespace,
error))
return FALSE;
provider_etc = g_build_filename (self->provider_in_host_namespace,
"etc", NULL);
provider_etc_dest = g_build_filename (self->provider_in_container_namespace,
"etc", NULL);
flatpak_bwrap_add_args (temp_bwrap,
"--ro-bind",
provider_etc,
provider_etc_dest,
NULL);
provider_etc = g_build_filename (self->provider_in_host_namespace,
"etc", NULL);
provider_etc_dest = g_build_filename (self->provider_in_container_namespace,
"etc", NULL);
flatpak_bwrap_add_args (temp_bwrap,
"--ro-bind",
provider_etc,
provider_etc_dest,
NULL);
}
flatpak_bwrap_set_env (temp_bwrap, "PATH", "/usr/bin:/bin", TRUE);
flatpak_bwrap_add_args (temp_bwrap,
......@@ -2937,70 +3440,84 @@ pv_runtime_get_ld_so (PvRuntime *self,
return TRUE;
}
static gboolean
pv_runtime_collect_graphics_libraries (PvRuntime *self,
RuntimeArchitecture *arch,
GError **error)
/*
* @patterns: (inout) (not nullable):
*/
static void
collect_graphics_libraries_patterns (GPtrArray *patterns)
{
g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
static const char * const sonames[] =
{
/* Vulkan */
"libvulkan.so.1",
/* VDPAU */
"libvdpau.so.1",
/* VA-API */
"libva.so.1",
"libva-drm.so.1",
"libva-glx.so.1",
"libva-x11.so.1",
"libva.so.2",
"libva-drm.so.2",
"libva-glx.so.2",
"libva-x11.so.2",
};
static const char * const soname_globs[] =
{
/* NVIDIA proprietary stack */
"libEGL.so.*",
"libEGL_nvidia.so.*",
"libGL.so.*",
"libGLESv1_CM.so.*",
"libGLESv1_CM_nvidia.so.*",
"libGLESv2.so.*",
"libGLESv2_nvidia.so.*",
"libGLX.so.*",
"libGLX_nvidia.so.*",
"libGLX_indirect.so.*",
"libGLdispatch.so.*",
"libOpenGL.so.*",
"libcuda.so.*",
"libglx.so.*",
"libnvidia-cbl.so.*",
"libnvidia-cfg.so.*",
"libnvidia-compiler.so.*",
"libnvidia-egl-wayland.so.*",
"libnvidia-eglcore.so.*",
"libnvidia-encode.so.*",
"libnvidia-fatbinaryloader.so.*",
"libnvidia-fbc.so.*",
"libnvidia-glcore.so.*",
"libnvidia-glsi.so.*",
"libnvidia-glvkspirv.so.*",
"libnvidia-ifr.so.*",
"libnvidia-ml.so.*",
"libnvidia-opencl.so.*",
"libnvidia-opticalflow.so.*",
"libnvidia-ptxjitcompiler.so.*",
"libnvidia-rtcore.so.*",
"libnvidia-tls.so.*",
"libOpenCL.so.*",
"libvdpau_nvidia.so.*",
};
gsize i;
temp_bwrap = pv_runtime_get_capsule_capture_libs (self, arch);
flatpak_bwrap_add_args (temp_bwrap,
"--dest", arch->libdir_in_current_namespace,
/* Mesa GLX, etc. */
"gl:",
/* Vulkan */
"if-exists:if-same-abi:soname:libvulkan.so.1",
/* VDPAU */
"if-exists:if-same-abi:soname:libvdpau.so.1",
/* VA-API */
"if-exists:if-same-abi:soname:libva.so.1",
"if-exists:if-same-abi:soname:libva-drm.so.1",
"if-exists:if-same-abi:soname:libva-glx.so.1",
"if-exists:if-same-abi:soname:libva-x11.so.1",
"if-exists:if-same-abi:soname:libva.so.2",
"if-exists:if-same-abi:soname:libva-drm.so.2",
"if-exists:if-same-abi:soname:libva-glx.so.2",
"if-exists:if-same-abi:soname:libva-x11.so.2",
/* NVIDIA proprietary stack */
"if-exists:even-if-older:soname-match:libEGL.so.*",
"if-exists:even-if-older:soname-match:libEGL_nvidia.so.*",
"if-exists:even-if-older:soname-match:libGL.so.*",
"if-exists:even-if-older:soname-match:libGLESv1_CM.so.*",
"if-exists:even-if-older:soname-match:libGLESv1_CM_nvidia.so.*",
"if-exists:even-if-older:soname-match:libGLESv2.so.*",
"if-exists:even-if-older:soname-match:libGLESv2_nvidia.so.*",
"if-exists:even-if-older:soname-match:libGLX.so.*",
"if-exists:even-if-older:soname-match:libGLX_nvidia.so.*",
"if-exists:even-if-older:soname-match:libGLX_indirect.so.*",
"if-exists:even-if-older:soname-match:libGLdispatch.so.*",
"if-exists:even-if-older:soname-match:libOpenGL.so.*",
"if-exists:even-if-older:soname-match:libcuda.so.*",
"if-exists:even-if-older:soname-match:libglx.so.*",
"if-exists:even-if-older:soname-match:libnvidia-cbl.so.*",
"if-exists:even-if-older:soname-match:libnvidia-cfg.so.*",
"if-exists:even-if-older:soname-match:libnvidia-compiler.so.*",
"if-exists:even-if-older:soname-match:libnvidia-egl-wayland.so.*",
"if-exists:even-if-older:soname-match:libnvidia-eglcore.so.*",
"if-exists:even-if-older:soname-match:libnvidia-encode.so.*",
"if-exists:even-if-older:soname-match:libnvidia-fatbinaryloader.so.*",
"if-exists:even-if-older:soname-match:libnvidia-fbc.so.*",
"if-exists:even-if-older:soname-match:libnvidia-glcore.so.*",
"if-exists:even-if-older:soname-match:libnvidia-glsi.so.*",
"if-exists:even-if-older:soname-match:libnvidia-glvkspirv.so.*",
"if-exists:even-if-older:soname-match:libnvidia-ifr.so.*",
"if-exists:even-if-older:soname-match:libnvidia-ml.so.*",
"if-exists:even-if-older:soname-match:libnvidia-opencl.so.*",
"if-exists:even-if-older:soname-match:libnvidia-opticalflow.so.*",
"if-exists:even-if-older:soname-match:libnvidia-ptxjitcompiler.so.*",
"if-exists:even-if-older:soname-match:libnvidia-rtcore.so.*",
"if-exists:even-if-older:soname-match:libnvidia-tls.so.*",
"if-exists:even-if-older:soname-match:libOpenCL.so.*",
"if-exists:even-if-older:soname-match:libvdpau_nvidia.so.*",
NULL);
flatpak_bwrap_finish (temp_bwrap);
g_return_if_fail (patterns != NULL);
return pv_bwrap_run_sync (temp_bwrap, NULL, error);
/* Mesa GLX, etc. */
g_ptr_array_add (patterns, g_strdup ("gl:"));
for (i = 0; i < G_N_ELEMENTS (sonames); i++)
g_ptr_array_add (patterns,
g_strdup_printf ("if-exists:if-same-abi:soname:%s",
sonames[i]));
for (i = 0; i < G_N_ELEMENTS (soname_globs); i++)
g_ptr_array_add (patterns,
g_strdup_printf ("if-exists:even-if-older:soname-match:%s",
soname_globs[i]));
}
static gboolean
......@@ -3016,6 +3533,7 @@ pv_runtime_collect_libc_family (PvRuntime *self,
g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
g_autofree char *libc_target = NULL;
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
if (!pv_runtime_take_ld_so_from_provider (self, arch,
......@@ -3129,6 +3647,8 @@ pv_runtime_collect_libdrm_data (PvRuntime *self,
g_autofree char *target = NULL;
target = glnx_readlinkat_malloc (-1, libdrm, NULL, NULL);
g_return_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK);
if (target != NULL)
{
g_autofree gchar *dir = NULL;
......@@ -3180,6 +3700,7 @@ pv_runtime_finish_libdrm_data (PvRuntime *self,
{
g_autofree gchar *best_libdrm_data_in_provider = NULL;
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
if (g_hash_table_size (libdrm_data_in_provider) > 0 && !all_libdrm_from_provider)
......@@ -3233,6 +3754,7 @@ pv_runtime_finish_libc_family (PvRuntime *self,
GHashTableIter iter;
const gchar *gconv_path;
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
if (self->any_libc_from_provider && !self->all_libc_from_provider)
......@@ -3379,6 +3901,7 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
g_strdup_printf ("%s/", self->provider_in_container_namespace);
g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK, FALSE);
g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
g_return_val_if_fail (bwrap == NULL || !pv_bwrap_was_finished (bwrap), FALSE);
g_return_val_if_fail (container_env != NULL, FALSE);
......@@ -3537,6 +4060,7 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
g_autoptr(SrtObjectList) va_api_drivers = NULL;
gboolean use_numbered_subdirs;
gboolean use_subdir_for_kind_soname;
g_autoptr(GPtrArray) patterns = NULL;
if (!pv_runtime_get_ld_so (self, arch, &ld_so_in_runtime, error))
return FALSE;
......@@ -3549,6 +4073,10 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
continue;
}
/* Reserve a size of 128 to avoid frequent reallocation due to the
* expected high number of patterns that will be added to the array. */
patterns = g_ptr_array_new_full (128, g_free);
any_architecture_works = TRUE;
g_debug ("Container path: %s -> %s",
arch->ld_so, ld_so_in_runtime);
......@@ -3558,11 +4086,9 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
g_mkdir_with_parents (arch->libdir_in_current_namespace, 0755);
g_debug ("Collecting graphics drivers from provider system...");
if (!pv_runtime_collect_graphics_libraries (self, arch, error))
return FALSE;
collect_graphics_libraries_patterns (patterns);
g_debug ("Collecting %s EGL drivers from host system...",
arch->details->tuple);
......@@ -3586,7 +4112,7 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
if (!bind_icd (self, arch, j, "glvnd", details,
&use_numbered_subdirs, use_subdir_for_kind_soname,
NULL, error))
patterns, NULL, error))
return FALSE;
}
......@@ -3612,20 +4138,20 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
if (!bind_icd (self, arch, j, "vulkan", details,
&use_numbered_subdirs, use_subdir_for_kind_soname,
NULL, error))
patterns, NULL, error))
return FALSE;
}
if (self->flags & PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS)
{
g_debug ("Collecting Vulkan explicit layers from host system...");
if (!collect_vulkan_layers (self, vulkan_exp_layer_details, arch,
"vulkan_exp_layer", error))
if (!collect_vulkan_layers (self, vulkan_exp_layer_details,
patterns, arch, "vulkan_exp_layer", error))
return FALSE;
g_debug ("Collecting Vulkan implicit layers from host system...");
if (!collect_vulkan_layers (self, vulkan_imp_layer_details, arch,
"vulkan_imp_layer", error))
if (!collect_vulkan_layers (self, vulkan_imp_layer_details,
patterns, arch, "vulkan_imp_layer", error))
return FALSE;
}
......@@ -3653,7 +4179,7 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
* ends up in a numbered subdirectory won't get used. */
if (!bind_icd (self, arch, j, "vdpau", details,
&use_numbered_subdirs, use_subdir_for_kind_soname,
NULL, error))
patterns, NULL, error))
return FALSE;
}
......@@ -3679,7 +4205,7 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
if (!bind_icd (self, arch, j, "dri", details,
&use_numbered_subdirs, use_subdir_for_kind_soname,
dri_path, error))
patterns, dri_path, error))
return FALSE;
}
......@@ -3705,10 +4231,15 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
if (!bind_icd (self, arch, j, "dri", details,
&use_numbered_subdirs, use_subdir_for_kind_soname,
va_api_path, error))
patterns, va_api_path, error))
return FALSE;
}
if (!pv_runtime_capture_libraries (self, arch,
arch->libdir_in_current_namespace,
patterns, error))
return FALSE;
libc = g_build_filename (arch->libdir_in_current_namespace, "libc.so.6", NULL);
/* If we are going to use the provider's libc6 (likely)
......
......@@ -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.
*/
......