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

Target

Select target project
  • steamrt/steam-runtime-tools
1 result
Select Git revision
Show changes
Showing
with 2413 additions and 332 deletions
/*
* Copyright © 2021 Collabora Ltd.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "mtree.h"
#include "steam-runtime-tools/profiling-internal.h"
#include "steam-runtime-tools/resolve-in-sysroot-internal.h"
#include "steam-runtime-tools/utils-internal.h"
#include <gio/gunixinputstream.h>
#include "enumtypes.h"
/* Enabling debug logging for this is rather too verbose, so only
* enable it when actively debugging this module */
#if 0
#define trace(...) g_debug (__VA_ARGS__)
#else
#define trace(...) do { } while (0)
#endif
gboolean
pv_mtree_entry_parse (const char *line,
PvMtreeEntry *entry,
const char *filename,
guint line_number,
GError **error)
{
PvMtreeEntry blank = PV_MTREE_ENTRY_BLANK;
g_auto(GStrv) tokens = NULL;
gsize i;
*entry = blank;
if (line[0] == '\0' || line[0] == '#')
return TRUE;
if (line[0] == '/')
return glnx_throw (error,
"%s:%u: Special commands not supported",
filename, line_number);
if (line[0] != '.' || (line[1] != ' ' && line[1] != '/' && line[1] != '\0'))
return glnx_throw (error,
"%s:%u: Filenames not relative to top level not supported",
filename, line_number);
if (g_str_has_suffix (line, "\\"))
return glnx_throw (error,
"%s:%u: Continuation lines not supported",
filename, line_number);
for (i = 0; line[i] != '\0'; i++)
{
if (line[i] == '\\')
{
if (line[i + 1] >= '0' && line[i + 1] <= '9')
continue;
switch (line[i + 1])
{
/* g_strcompress() documents these to work */
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
case '"':
case '\\':
i += 1;
continue;
/* \M, \^, \a, \s, \E, \x, \$, escaped whitespace and escaped
* newline are not supported here */
default:
return glnx_throw (error,
"%s:%u: Unsupported backslash escape: \"\\%c\"",
filename, line_number, line[i + 1]);
}
}
}
tokens = g_strsplit_set (line, " \t", -1);
if (tokens == NULL)
return glnx_throw (error, "%s:%u: Line is empty", filename, line_number);
entry->name = g_strcompress (tokens[0]);
for (i = 1; tokens[i] != NULL; i++)
{
const char *equals;
char *endptr;
equals = strchr (tokens[i], '=');
if (equals == NULL)
return glnx_throw (error,
"%s:%u: No value for keyword '%s'",
filename, line_number, tokens[i]);
if (g_str_has_prefix (tokens[i], "cksum=")
|| g_str_has_prefix (tokens[i], "device=")
|| g_str_has_prefix (tokens[i], "flags=")
|| g_str_has_prefix (tokens[i], "gid=")
|| g_str_has_prefix (tokens[i], "gname=")
|| g_str_has_prefix (tokens[i], "ignore=")
|| g_str_has_prefix (tokens[i], "inode=")
|| g_str_has_prefix (tokens[i], "md5=")
|| g_str_has_prefix (tokens[i], "md5digest=")
|| g_str_has_prefix (tokens[i], "nlink=")
|| g_str_has_prefix (tokens[i], "nochange=")
|| g_str_has_prefix (tokens[i], "optional=")
|| g_str_has_prefix (tokens[i], "resdevice=")
|| g_str_has_prefix (tokens[i], "ripemd160digest=")
|| g_str_has_prefix (tokens[i], "rmd160=")
|| g_str_has_prefix (tokens[i], "rmd160digest=")
|| g_str_has_prefix (tokens[i], "sha1=")
|| g_str_has_prefix (tokens[i], "sha1digest=")
|| g_str_has_prefix (tokens[i], "sha384=")
|| g_str_has_prefix (tokens[i], "sha384digest=")
|| g_str_has_prefix (tokens[i], "sha512=")
|| g_str_has_prefix (tokens[i], "sha512digest=")
|| g_str_has_prefix (tokens[i], "uid=")
|| g_str_has_prefix (tokens[i], "uname="))
continue;
if (g_str_has_prefix (tokens[i], "link="))
{
entry->link = g_strcompress (equals + 1);
continue;
}
if (g_str_has_prefix (tokens[i], "contents="))
{
entry->contents = g_strcompress (equals + 1);
continue;
}
if (g_str_has_prefix (tokens[i], "sha256=")
|| g_str_has_prefix (tokens[i], "sha256digest="))
{
if (entry->sha256 == NULL)
entry->sha256 = g_strdup (equals + 1);
else if (strcmp (entry->sha256, equals + 1) != 0)
return glnx_throw (error,
"%s:%u: sha256 and sha256digest not consistent",
filename, line_number);
continue;
}
if (g_str_has_prefix (tokens[i], "mode="))
{
gint64 value = g_ascii_strtoll (equals + 1, &endptr, 8);
if (equals[1] == '\0' || *endptr != '\0')
return glnx_throw (error,
"%s:%u: Invalid mode %s",
filename, line_number, equals + 1);
entry->mode = value & 07777;
continue;
}
if (g_str_has_prefix (tokens[i], "size="))
{
gint64 value = g_ascii_strtoll (equals + 1, &endptr, 10);
if (equals[1] == '\0' || *endptr != '\0')
return glnx_throw (error,
"%s:%u: Invalid size %s",
filename, line_number, equals + 1);
entry->size = value;
continue;
}
if (g_str_has_prefix (tokens[i], "time="))
{
gdouble value = g_ascii_strtod (equals + 1, &endptr);
if (equals[1] == '\0' || *endptr != '\0')
return glnx_throw (error,
"%s:%u: Invalid time %s",
filename, line_number, equals + 1);
entry->mtime_usec = (value * G_TIME_SPAN_SECOND);
continue;
}
if (g_str_has_prefix (tokens[i], "type="))
{
int value;
if (srt_enum_from_nick (PV_TYPE_MTREE_ENTRY_KIND, equals + 1,
&value, NULL))
entry->kind = value;
else
entry->kind = PV_MTREE_ENTRY_KIND_UNKNOWN;
continue;
}
g_warning ("%s:%u: Unknown mtree keyword %s",
filename, line_number, tokens[i]);
}
if (entry->kind == PV_MTREE_ENTRY_KIND_UNKNOWN)
return glnx_throw (error,
"%s:%u: Unknown mtree entry type",
filename, line_number);
if (entry->link != NULL && entry->kind != PV_MTREE_ENTRY_KIND_LINK)
return glnx_throw (error,
"%s:%u: Non-symlink cannot have a symlink target",
filename, line_number);
if (entry->link == NULL && entry->kind == PV_MTREE_ENTRY_KIND_LINK)
return glnx_throw (error,
"%s:%u: Symlink must have a symlink target",
filename, line_number);
return TRUE;
}
/*
* pv_mtree_apply:
* @mtree: (type filename): Path to a mtree(5) manifest
* @sysroot: (type filename): A directory
* @sysroot_fd: A fd opened on @sysroot
* @source_files: (optional): A directory from which files will be
* hard-linked or copied when populating @sysroot. The `content`
* or filename in @mtree is taken to be relative to @source_files.
* @flags: Flags affecting how this is done
*
* Make the container root filesystem @sysroot conform to @mtree.
*
* @mtree must contain a subset of BSD mtree(5) syntax:
*
* - one entry per line
* - no device nodes, fifos, sockets or other special devices
* - strings are escaped using octal (for example \040 for space)
* - filenames other than "." start with "./"
*
* For regular files, we assert that the file exists, set its mtime,
* and set its permissions to either 0644 or 0755.
*
* For directories, we create the directory with 0755 permissions.
*
* For symbolic links, we create the symbolic link if it does not
* already exist.
*
* A suitable mtree file can be created from a tarball or the filesystem
* with `bsdtar(1)` from the `libarchive-tools` Debian package:
*
* |[
* bsdtar -cf - \
* --format=mtree \
* --options "!all,type,link,mode,size,time" \
* @- < foo.tar.gz
* bsdtar -cf - \
* --format=mtree \
* --options "!all,type,link,mode,size,time" \
* -C files/ .
* ]|
*
* A suitable mtree file can also be created by `mtree(8)` from the
* `netbsd-mtree` Debian package if the filenames happen to be ASCII
* (although this implementation does not support all escaped non-ASCII
* filenames produced by `netbsd-mtree`):
*
* |[
* mtree -p files -c | mtree -C
* ]|
*
* Because hard links are used whenever possible, the permissions or
* modification time of a source file in @source_files might be modified
* to conform to the @mtree.
*
* Returns: %TRUE on success
*/
gboolean
pv_mtree_apply (const char *mtree,
const char *sysroot,
int sysroot_fd,
const char *source_files,
PvMtreeApplyFlags flags,
GError **error)
{
glnx_autofd int mtree_fd = -1;
g_autoptr(GInputStream) istream = NULL;
g_autoptr(GDataInputStream) reader = NULL;
g_autoptr(SrtProfilingTimer) timer = NULL;
glnx_autofd int source_files_fd = -1;
guint line_number = 0;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_return_val_if_fail (mtree != NULL, FALSE);
g_return_val_if_fail (sysroot != NULL, FALSE);
g_return_val_if_fail (sysroot_fd >= 0, FALSE);
timer = _srt_profiling_start ("Apply %s to %s", mtree, sysroot);
if (!glnx_openat_rdonly (AT_FDCWD, mtree, TRUE, &mtree_fd, error))
return FALSE;
istream = g_unix_input_stream_new (glnx_steal_fd (&mtree_fd), TRUE);
if (flags & PV_MTREE_APPLY_FLAGS_GZIP)
{
g_autoptr(GInputStream) filter = NULL;
g_autoptr(GZlibDecompressor) decompressor = NULL;
decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
filter = g_converter_input_stream_new (istream, G_CONVERTER (decompressor));
g_clear_object (&istream);
istream = g_object_ref (filter);
}
reader = g_data_input_stream_new (istream);
g_data_input_stream_set_newline_type (reader, G_DATA_STREAM_NEWLINE_TYPE_LF);
if (source_files != NULL)
{
if (!glnx_opendirat (AT_FDCWD, source_files, FALSE, &source_files_fd,
error))
return FALSE;
}
g_info ("Applying \"%s\" to \"%s\"...", mtree, sysroot);
while (TRUE)
{
g_autofree gchar *line = NULL;
g_autofree gchar *parent = NULL;
const char *base;
g_autoptr(GError) local_error = NULL;
g_auto(PvMtreeEntry) entry = PV_MTREE_ENTRY_BLANK;
glnx_autofd int parent_fd = -1;
glnx_autofd int fd = -1;
int adjusted_mode;
line = g_data_input_stream_read_line (reader, NULL, NULL, &local_error);
if (line == NULL)
{
if (local_error != NULL)
{
g_propagate_prefixed_error (error, g_steal_pointer (&local_error),
"While reading a line from %s: ",
mtree);
return FALSE;
}
else
{
/* End of file, not an error */
break;
}
}
g_strstrip (line);
line_number++;
trace ("line %u: %s", line_number, line);
if (!pv_mtree_entry_parse (line, &entry, mtree, line_number, error))
return FALSE;
if (entry.name == NULL || strcmp (entry.name, ".") == 0)
continue;
trace ("mtree entry: %s", entry.name);
parent = g_path_get_dirname (entry.name);
base = glnx_basename (entry.name);
trace ("Creating %s in %s", parent, sysroot);
parent_fd = _srt_resolve_in_sysroot (sysroot_fd, parent,
SRT_RESOLVE_FLAGS_MKDIR_P,
NULL, error);
if (parent_fd < 0)
return glnx_prefix_error (error,
"Unable to create parent directory for \"%s\" in \"%s\"",
entry.name, sysroot);
switch (entry.kind)
{
case PV_MTREE_ENTRY_KIND_FILE:
if (entry.size == 0)
{
/* For empty files, we can create it from nothing. */
fd = TEMP_FAILURE_RETRY (openat (parent_fd, base,
(O_RDWR | O_CLOEXEC | O_NOCTTY
| O_NOFOLLOW | O_CREAT
| O_TRUNC),
0644));
if (fd < 0)
return glnx_throw_errno_prefix (error,
"Unable to open \"%s\" in \"%s\"",
entry.name, sysroot);
}
else if (source_files_fd >= 0)
{
const char *source = entry.contents;
if (source == NULL)
source = entry.name;
/* If it already exists, assume it's correct */
if (glnx_openat_rdonly (parent_fd, base, FALSE, &fd, NULL))
{
trace ("\"%s\" already exists in \"%s\"",
entry.name, sysroot);
}
/* If we can create a hard link, that's also fine */
else if (TEMP_FAILURE_RETRY (linkat (source_files_fd, source,
parent_fd, base, 0)) == 0)
{
trace ("Created hard link \"%s\" in \"%s\"",
entry.name, sysroot);
}
/* Or if we can copy it, that's fine too */
else
{
g_debug ("Could not create hard link \"%s\" from \"%s/%s\" into \"%s\": %s",
entry.name, source_files, source, sysroot,
g_strerror (errno));
if (!glnx_file_copy_at (source_files_fd, source, NULL,
parent_fd, base,
GLNX_FILE_COPY_OVERWRITE | GLNX_FILE_COPY_NOCHOWN,
NULL, error))
return glnx_prefix_error (error,
"Could not create copy \"%s\" from \"%s/%s\" into \"%s\"",
entry.name, source_files,
source, sysroot);
}
}
/* For other regular files we just assert that it already exists
* (and is not a symlink). */
if (fd < 0
&& !glnx_openat_rdonly (parent_fd, base, FALSE, &fd, error))
return glnx_prefix_error (error,
"Unable to open \"%s\" in \"%s\"",
entry.name, sysroot);
break;
case PV_MTREE_ENTRY_KIND_DIR:
/* Create directories on-demand */
if (!glnx_ensure_dir (parent_fd, base, 0755, error))
return glnx_prefix_error (error,
"Unable to create directory \"%s\" in \"%s\"",
entry.name, sysroot);
/* Assert that it is in fact a directory */
if (!glnx_opendirat (parent_fd, base, FALSE, &fd, error))
return glnx_prefix_error (error,
"Unable to open directory \"%s\" in \"%s\"",
entry.name, sysroot);
break;
case PV_MTREE_ENTRY_KIND_LINK:
{
g_autofree char *target = NULL;
/* Create symlinks on-demand. To be idempotent, don't delete
* an existing symlink. */
target = glnx_readlinkat_malloc (parent_fd, base,
NULL, NULL);
if (target == NULL && symlinkat (entry.link, parent_fd, base) != 0)
return glnx_throw_errno_prefix (error,
"Unable to create symlink \"%s\" in \"%s\"",
entry.name, sysroot);
}
break;
case PV_MTREE_ENTRY_KIND_BLOCK:
case PV_MTREE_ENTRY_KIND_CHAR:
case PV_MTREE_ENTRY_KIND_FIFO:
case PV_MTREE_ENTRY_KIND_SOCKET:
case PV_MTREE_ENTRY_KIND_UNKNOWN:
default:
return glnx_throw (error,
"%s:%u: Special file not supported",
mtree, line_number);
}
if (entry.kind == PV_MTREE_ENTRY_KIND_DIR
|| (entry.mode >= 0 && entry.mode & 0111))
adjusted_mode = 0755;
else
adjusted_mode = 0644;
if (fd >= 0 && !glnx_fchmod (fd, adjusted_mode, error))
{
g_prefix_error (error,
"Unable to set mode of \"%s\" in \"%s\": ",
entry.name, sysroot);
return FALSE;
}
if (entry.mtime_usec >= 0 && fd >= 0 && entry.kind == PV_MTREE_ENTRY_KIND_FILE)
{
struct timespec times[2] =
{
{ .tv_sec = 0, .tv_nsec = UTIME_OMIT }, /* atime */
{
.tv_sec = entry.mtime_usec / G_TIME_SPAN_SECOND,
.tv_nsec = (entry.mtime_usec % G_TIME_SPAN_SECOND) * 1000
} /* mtime */
};
if (futimens (fd, times) != 0)
g_warning ("Unable to set mtime of \"%s\" in \"%s\": %s",
entry.name, sysroot, g_strerror (errno));
}
}
return TRUE;
}
/*
* Free the contents of @entry, but not @entry itself.
*/
void
pv_mtree_entry_clear (PvMtreeEntry *entry)
{
g_clear_pointer (&entry->name, g_free);
g_clear_pointer (&entry->contents, g_free);
g_clear_pointer (&entry->link, g_free);
g_clear_pointer (&entry->sha256, g_free);
}
/*
* Copyright © 2021 Collabora Ltd.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <glib.h>
#include "steam-runtime-tools/glib-backports-internal.h"
#include "libglnx/libglnx.h"
typedef enum
{
PV_MTREE_APPLY_FLAGS_GZIP = (1 << 0),
PV_MTREE_APPLY_FLAGS_NONE = 0
} PvMtreeApplyFlags;
typedef enum
{
PV_MTREE_ENTRY_KIND_UNKNOWN = '\0',
PV_MTREE_ENTRY_KIND_BLOCK = 'b',
PV_MTREE_ENTRY_KIND_CHAR = 'c',
PV_MTREE_ENTRY_KIND_DIR = 'd',
PV_MTREE_ENTRY_KIND_FIFO = 'p',
PV_MTREE_ENTRY_KIND_FILE = '-',
PV_MTREE_ENTRY_KIND_LINK = 'l',
PV_MTREE_ENTRY_KIND_SOCKET = 's',
} PvMtreeEntryKind;
typedef struct _PvMtreeEntry PvMtreeEntry;
struct _PvMtreeEntry
{
gchar *name;
gchar *contents;
gchar *link;
gchar *sha256;
goffset size;
GTimeSpan mtime_usec;
int mode;
PvMtreeEntryKind kind;
};
#define PV_MTREE_ENTRY_BLANK \
{ \
.size = -1, \
.mtime_usec = -1, \
.mode = -1, \
}
gboolean pv_mtree_entry_parse (const char *line,
PvMtreeEntry *entry,
const char *filename,
guint line_number,
GError **error);
void pv_mtree_entry_clear (PvMtreeEntry *entry);
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (PvMtreeEntry, pv_mtree_entry_clear)
gboolean pv_mtree_apply (const char *mtree,
const char *sysroot,
int sysroot_fd,
const char *source_files,
PvMtreeApplyFlags flags,
GError **error);
......@@ -42,6 +42,7 @@
#include "enumtypes.h"
#include "exports.h"
#include "flatpak-run-private.h"
#include "mtree.h"
#include "tree-copy.h"
#include "utils.h"
......@@ -67,7 +68,8 @@ struct _PvRuntime
gchar *id;
gchar *deployment;
gchar *source_files; /* either deployment or that + "/files" */
gchar *tools_dir;
const gchar *pv_prefix;
const gchar *helpers_path;
PvBwrapLock *runtime_lock;
GStrv original_environ;
......@@ -115,7 +117,6 @@ enum {
PROP_FLAGS,
PROP_ID,
PROP_VARIABLE_DIR,
PROP_TOOLS_DIRECTORY,
N_PROPERTIES
};
......@@ -329,7 +330,7 @@ runtime_architecture_init (RuntimeArchitecture *self,
self->capsule_capture_libs_basename = g_strdup_printf ("%s-capsule-capture-libs",
self->details->tuple);
self->capsule_capture_libs = g_build_filename (runtime->tools_dir,
self->capsule_capture_libs = g_build_filename (runtime->helpers_path,
self->capsule_capture_libs_basename,
NULL);
self->libdir_in_current_namespace = g_build_filename (runtime->overrides, "lib",
......@@ -442,10 +443,6 @@ pv_runtime_get_property (GObject *object,
g_value_set_string (value, self->source);
break;
case PROP_TOOLS_DIRECTORY:
g_value_set_string (value, self->tools_dir);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
......@@ -529,12 +526,6 @@ pv_runtime_set_property (GObject *object,
break;
case PROP_TOOLS_DIRECTORY:
/* Construct-only */
g_return_if_fail (self->tools_dir == NULL);
self->tools_dir = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
......@@ -549,7 +540,6 @@ pv_runtime_constructed (GObject *object)
g_return_if_fail (self->original_environ != NULL);
g_return_if_fail (self->source != NULL);
g_return_if_fail (self->tools_dir != NULL);
}
static void
......@@ -820,17 +810,10 @@ pv_runtime_garbage_collect (PvRuntime *self,
if (g_str_has_prefix (dent->d_name, "deploy-"))
{
/* 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;
}
/* Don't GC the current deployment */
if (strcmp (dent->d_name + strlen ("deploy-"), self->id) == 0)
if (_srt_fstatat_is_same_file (self->variable_dir_fd,
dent->d_name,
AT_FDCWD,
self->deployment))
{
g_debug ("Ignoring %s/%s: is the current version",
self->variable_dir, dent->d_name);
......@@ -875,17 +858,17 @@ pv_runtime_init_variable_dir (PvRuntime *self,
static gboolean
pv_runtime_create_copy (PvRuntime *self,
PvBwrapLock *variable_dir_lock,
const char *usr_mtree,
PvMtreeApplyFlags mtree_flags,
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) source_lock = NULL;
g_autoptr(SrtProfilingTimer) timer = NULL;
const char *member;
const char *source_usr;
glnx_autofd int temp_dir_fd = -1;
gboolean is_just_usr;
......@@ -906,28 +889,62 @@ pv_runtime_create_copy (PvRuntime *self,
"Cannot create temporary directory \"%s\"",
temp_dir);
source_usr_subdir = g_build_filename (self->source_files, "usr", NULL);
dest_usr = g_build_filename (temp_dir, "usr", NULL);
is_just_usr = !g_file_test (source_usr_subdir, G_FILE_TEST_IS_DIR);
if (usr_mtree != NULL)
{
is_just_usr = TRUE;
}
else
{
g_autofree gchar *source_usr_subdir = g_build_filename (self->source_files,
"usr", NULL);
is_just_usr = !g_file_test (source_usr_subdir, G_FILE_TEST_IS_DIR);
}
if (is_just_usr)
{
/* ${source_files}/usr does not exist, so assume it's a merged /usr,
* for example ./scout/files. Copy ${source_files}/bin to
* ${temp_dir}/usr/bin, etc. */
source_usr = self->source_files;
if (usr_mtree != NULL)
{
/* If there's a manifest available, it's actually quicker to iterate
* through the manifest and use that to populate a new copy of the
* runtime that it would be to do the equivalent of `cp -al` -
* presumably because the mtree is probably contiguous on disk,
* and the nested directories are probably not. */
glnx_autofd int dest_usr_fd = -1;
if (!glnx_ensure_dir (AT_FDCWD, dest_usr, 0755, error))
return FALSE;
if (!pv_cheap_tree_copy (self->source_files, dest_usr,
PV_COPY_FLAGS_NONE, error))
return FALSE;
if (!glnx_opendirat (AT_FDCWD, dest_usr, FALSE, &dest_usr_fd, error))
{
g_prefix_error (error, "Unable to open \"%s\": ", dest_usr);
return FALSE;
}
if (!pv_mtree_apply (usr_mtree, dest_usr, dest_usr_fd,
self->source_files, mtree_flags,
error))
return FALSE;
}
else
{
/* Fall back to assuming that what's on-disk is correct. */
if (!pv_cheap_tree_copy (self->source_files, dest_usr,
PV_COPY_FLAGS_NONE, error))
return FALSE;
}
}
else
{
/* ${source_files}/usr exists, so assume it's a complete sysroot.
* Merge ${source_files}/bin and ${source_files}/usr/bin into
* ${temp_dir}/usr/bin, etc. */
source_usr = source_usr_subdir;
g_assert (usr_mtree == NULL);
if (!pv_cheap_tree_copy (self->source_files, temp_dir,
PV_COPY_FLAGS_USRMERGE, error))
......@@ -982,7 +999,7 @@ pv_runtime_create_copy (PvRuntime *self,
temp_dir);
}
dir = g_dir_open (source_usr, 0, error);
dir = g_dir_open (dest_usr, 0, error);
if (dir == NULL)
return FALSE;
......@@ -1471,11 +1488,18 @@ pv_runtime_initable_init (GInitable *initable,
g_autoptr(PvBwrapLock) mutable_lock = NULL;
g_autofree gchar *contents = NULL;
g_autofree gchar *os_release = NULL;
g_autofree gchar *usr_mtree = NULL;
gsize len;
PvMtreeApplyFlags mtree_flags = PV_MTREE_APPLY_FLAGS_NONE;
g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
self->pv_prefix = _srt_find_myself (&self->helpers_path, error);
if (self->pv_prefix == NULL)
return FALSE;
/* Enumerating the graphics provider's drivers only requires things
* we already know, so start this first, and let it run in parallel
* with other setup. The results go in the SrtSystemInfo's cache
......@@ -1533,11 +1557,40 @@ pv_runtime_initable_init (GInitable *initable,
self->deployment);
}
/* If it contains ./files/, assume it's a Flatpak-style runtime where
* ./files is a merged /usr and ./metadata is an optional GKeyFile */
/* If the deployment contains usr-mtree.txt, assume that it's a
* Flatpak-style merged-/usr runtime, and usr-mtree.txt describes
* what's in the runtime. The content is taken from the files/
* directory, but files not listed in the mtree are not included.
*
* The manifest compresses well (about 3:1 if sha256sums are included)
* so try to read a compressed version first, falling back to
* uncompressed. */
usr_mtree = g_build_filename (self->deployment, "usr-mtree.txt.gz", NULL);
if (g_file_test (usr_mtree, G_FILE_TEST_IS_REGULAR))
{
mtree_flags |= PV_MTREE_APPLY_FLAGS_GZIP;
}
else
{
g_clear_pointer (&usr_mtree, g_free);
usr_mtree = g_build_filename (self->deployment, "usr-mtree.txt", NULL);
}
if (!g_file_test (usr_mtree, G_FILE_TEST_IS_REGULAR))
g_clear_pointer (&usr_mtree, g_free);
/* Or, if it contains ./files/, assume it's a Flatpak-style runtime where
* ./files is a merged /usr and ./metadata is an optional GKeyFile. */
self->source_files = g_build_filename (self->deployment, "files", NULL);
if (g_file_test (self->source_files, G_FILE_TEST_IS_DIR))
if (usr_mtree != NULL)
{
g_debug ("Assuming %s is a merged-/usr runtime because it has "
"a /usr mtree",
self->deployment);
}
else if (g_file_test (self->source_files, G_FILE_TEST_IS_DIR))
{
g_debug ("Assuming %s is a Flatpak-style runtime", self->deployment);
}
......@@ -1550,12 +1603,6 @@ pv_runtime_initable_init (GInitable *initable,
g_debug ("Taking runtime files from: %s", self->source_files);
if (!g_file_test (self->tools_dir, G_FILE_TEST_IS_DIR))
{
return glnx_throw (error, "\"%s\" is not a directory",
self->tools_dir);
}
/* Take a lock on the runtime until we're finished with setup,
* to make sure it doesn't get deleted.
*
......@@ -1601,6 +1648,10 @@ pv_runtime_initable_init (GInitable *initable,
return FALSE;
}
/* Always copy the runtime into var/ before applying a manifest. */
if (usr_mtree != NULL)
self->flags |= PV_RUNTIME_FLAGS_COPY_RUNTIME;
if (self->flags & PV_RUNTIME_FLAGS_COPY_RUNTIME)
{
if (self->variable_dir_fd < 0)
......@@ -1620,7 +1671,8 @@ pv_runtime_initable_init (GInitable *initable,
if (mutable_lock == NULL)
return FALSE;
if (!pv_runtime_create_copy (self, mutable_lock, error))
if (!pv_runtime_create_copy (self, mutable_lock, usr_mtree,
mtree_flags, error))
return FALSE;
}
......@@ -1785,7 +1837,6 @@ pv_runtime_finalize (GObject *object)
g_free (self->source);
g_free (self->source_files);
g_free (self->deployment);
g_free (self->tools_dir);
if (self->runtime_lock != NULL)
pv_bwrap_lock_free (self->runtime_lock);
......@@ -1855,13 +1906,6 @@ pv_runtime_class_init (PvRuntimeClass *cls)
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
properties[PROP_TOOLS_DIRECTORY] =
g_param_spec_string ("tools-directory", "Tools directory",
"Path to pressure-vessel/bin in current namespace",
NULL,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPERTIES, properties);
}
......@@ -1870,14 +1914,12 @@ pv_runtime_new (const char *source,
const char *id,
const char *variable_dir,
const char *bubblewrap,
const char *tools_dir,
PvGraphicsProvider *provider,
const GStrv original_environ,
PvRuntimeFlags flags,
GError **error)
{
g_return_val_if_fail (source != NULL, NULL);
g_return_val_if_fail (tools_dir != NULL, NULL);
g_return_val_if_fail ((flags & ~(PV_RUNTIME_FLAGS_MASK)) == 0, NULL);
return g_initable_new (PV_TYPE_RUNTIME,
......@@ -1889,7 +1931,6 @@ pv_runtime_new (const char *source,
"variable-dir", variable_dir,
"source", source,
"id", id,
"tools-directory", tools_dir,
"flags", flags,
NULL);
}
......@@ -5236,8 +5277,6 @@ pv_runtime_bind (PvRuntime *self,
PvEnviron *container_env,
GError **error)
{
g_autofree gchar *pressure_vessel_prefix = NULL;
g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
g_return_val_if_fail ((exports == NULL) == (bwrap == NULL), FALSE);
g_return_val_if_fail (bwrap == NULL || !pv_bwrap_was_finished (bwrap), FALSE);
......@@ -5271,8 +5310,6 @@ pv_runtime_bind (PvRuntime *self,
if (bwrap != NULL)
bind_runtime_finish (self, exports, bwrap);
pressure_vessel_prefix = g_path_get_dirname (self->tools_dir);
/* Make sure pressure-vessel itself is visible there. */
if (self->mutable_sysroot != NULL)
{
......@@ -5292,7 +5329,7 @@ pv_runtime_bind (PvRuntime *self,
dest = glnx_fdrel_abspath (parent_dirfd, "from-host");
if (!pv_cheap_tree_copy (pressure_vessel_prefix, dest,
if (!pv_cheap_tree_copy (self->pv_prefix, dest,
PV_COPY_FLAGS_NONE, error))
return FALSE;
......@@ -5308,7 +5345,7 @@ pv_runtime_bind (PvRuntime *self,
else
{
g_autofree gchar *pressure_vessel_prefix_in_host_namespace =
pv_current_namespace_path_to_host_path (pressure_vessel_prefix);
pv_current_namespace_path_to_host_path (self->pv_prefix);
g_assert (bwrap != NULL);
......
......@@ -90,7 +90,6 @@ PvRuntime *pv_runtime_new (const char *source,
const char *id,
const char *variable_dir,
const char *bubblewrap,
const char *tools_dir,
PvGraphicsProvider *provider,
const GStrv original_environ,
PvRuntimeFlags flags,
......
......@@ -46,75 +46,6 @@
static int my_pid = -1;
static const gchar *my_prgname = NULL;
/**
* pv_envp_cmp:
* @p1: a `const char * const *`
* @p2: a `const char * const *`
*
* Compare two environment variables, given as pointers to pointers
* to the actual `KEY=value` string.
*
* In particular this is suitable for sorting a #GStrv using `qsort`.
*
* Returns: negative, 0 or positive if `*p1` compares before, equal to
* or after `*p2`
*/
int
pv_envp_cmp (const void *p1,
const void *p2)
{
const char * const * s1 = p1;
const char * const * s2 = p2;
size_t l1 = strlen (*s1);
size_t l2 = strlen (*s2);
size_t min;
const char *tmp;
int ret;
tmp = strchr (*s1, '=');
if (tmp != NULL)
l1 = tmp - *s1;
tmp = strchr (*s2, '=');
if (tmp != NULL)
l2 = tmp - *s2;
min = MIN (l1, l2);
ret = strncmp (*s1, *s2, min);
/* If they differ before the first '=' (if any) in either s1 or s2,
* then they are certainly different */
if (ret != 0)
return ret;
ret = strcmp (*s1, *s2);
/* If they do not differ at all, then they are equal */
if (ret == 0)
return ret;
/* FOO < FOO=..., and FOO < FOOBAR */
if ((*s1)[min] == '\0')
return -1;
/* FOO=... > FOO, and FOOBAR > FOO */
if ((*s2)[min] == '\0')
return 1;
/* FOO= < FOOBAR */
if ((*s1)[min] == '=' && (*s2)[min] != '=')
return -1;
/* FOOBAR > FOO= */
if ((*s2)[min] == '=' && (*s1)[min] != '=')
return 1;
/* Fall back to plain string comparison */
return ret;
}
/**
* pv_get_current_dirs:
* @cwd_p: (out) (transfer full) (optional): Used to return the
......@@ -812,50 +743,6 @@ pv_terminate_all_child_processes (GTimeSpan wait_period,
return TRUE;
}
/*
* @str: A path
* @prefix: A possible prefix
*
* The same as flatpak_has_path_prefix(), but instead of a boolean,
* return the part of @str after @prefix (non-%NULL but possibly empty)
* if @str has prefix @prefix, or %NULL if it does not.
*
* Returns: (nullable) (transfer none): the part of @str after @prefix,
* or %NULL if @str is not below @prefix
*/
const char *
pv_get_path_after (const char *str,
const char *prefix)
{
while (TRUE)
{
/* Skip consecutive slashes to reach next path
element */
while (*str == '/')
str++;
while (*prefix == '/')
prefix++;
/* No more prefix path elements? Done! */
if (*prefix == 0)
return str;
/* Compare path element */
while (*prefix != 0 && *prefix != '/')
{
if (*str != *prefix)
return NULL;
str++;
prefix++;
}
/* Matched prefix path element,
must be entire str path element */
if (*str != '/' && *str != 0)
return NULL;
}
}
/**
* pv_current_namespace_path_to_host_path:
* @current_env_path: a path in the current environment
......@@ -883,7 +770,7 @@ pv_current_namespace_path_to_host_path (const gchar *current_env_path)
home = g_get_home_dir ();
if (home != NULL)
after = pv_get_path_after (current_env_path, home);
after = _srt_get_path_after (current_env_path, home);
/* If we are inside a Flatpak container, usually, the home
* folder is '${HOME}/.var/app/${FLATPAK_ID}' on the host system */
......@@ -913,7 +800,7 @@ pv_current_namespace_path_to_host_path (const gchar *current_env_path)
}
}
after = pv_get_path_after (current_env_path, "/run/host");
after = _srt_get_path_after (current_env_path, "/run/host");
/* In a Flatpak container, usually, '/run/host' is the root of the
* host system */
......
......@@ -44,9 +44,6 @@
#define pv_log_failure(...) \
g_log (G_LOG_DOMAIN, PV_LOG_LEVEL_FAILURE, __VA_ARGS__)
int pv_envp_cmp (const void *p1,
const void *p2);
void pv_get_current_dirs (gchar **cwd_p,
gchar **cwd_l);
......@@ -79,9 +76,6 @@ gboolean pv_terminate_all_child_processes (GTimeSpan wait_period,
gchar *pv_current_namespace_path_to_host_path (const gchar *current_env_path);
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,
......
/*
* Copyright 2018-2021 Wim Taymans
* Copyright 2021 Collabora Ltd.
*
* SPDX-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "wrap-pipewire.h"
/* From Pipewire 0.3.27 */
#define PW_DEFAULT_REMOTE "pipewire-0"
#define DEFAULT_SYSTEM_RUNTIME_DIR "/run/pipewire"
/* Adapted from Pipewire 0.3.27 */
static const char *
get_remote (void)
{
const char *name = NULL;
name = getenv("PIPEWIRE_REMOTE");
if (name == NULL || name[0] == '\0')
name = PW_DEFAULT_REMOTE;
return name;
}
/* Adapted from Pipewire 0.3.27 */
static const char *
get_runtime_dir (void)
{
const char *runtime_dir;
runtime_dir = g_getenv ("PIPEWIRE_RUNTIME_DIR");
if (runtime_dir == NULL)
runtime_dir = g_getenv ("XDG_RUNTIME_DIR");
if (runtime_dir == NULL)
runtime_dir = g_getenv ("HOME");
if (runtime_dir == NULL)
runtime_dir = g_getenv ("USERPROFILE");
if (runtime_dir == NULL)
runtime_dir = g_get_home_dir ();
return runtime_dir;
}
void
pv_wrap_add_pipewire_args (FlatpakBwrap *sharing_bwrap,
PvEnviron *container_env)
{
g_autoptr(GDir) dir = NULL;
const char *remote = get_remote ();
const char *runtime_dir = get_runtime_dir ();
const char *member;
/* Make Pipewire look in the container's XDG_RUNTIME_DIR */
pv_environ_lock_env (container_env, "PIPEWIRE_RUNTIME_DIR", NULL);
if (g_file_test (DEFAULT_SYSTEM_RUNTIME_DIR, G_FILE_TEST_IS_DIR))
flatpak_bwrap_add_args (sharing_bwrap,
"--ro-bind",
DEFAULT_SYSTEM_RUNTIME_DIR,
DEFAULT_SYSTEM_RUNTIME_DIR,
NULL);
dir = g_dir_open (runtime_dir, 0, NULL);
if (dir == NULL)
return;
for (member = g_dir_read_name (dir);
member != NULL;
member = g_dir_read_name (dir))
{
/* Assume that anything starting with pipewire- is a (default or
* extra) Pipewire socket */
if (g_str_has_prefix (member, "pipewire-"))
{
g_autofree gchar *host_socket =
g_build_filename (runtime_dir, member, NULL);
g_autofree gchar *container_socket =
g_strdup_printf ("/run/user/%d/%s", getuid (), member);
flatpak_bwrap_add_args (sharing_bwrap,
"--ro-bind",
host_socket,
container_socket,
NULL);
}
}
if (!g_str_has_prefix (remote, "pipewire-"))
{
/* If the configured Pipewire socket is something weird, remap it
* to be named pv-pipewire to avoid colliding with anything else */
g_autofree gchar *host_socket =
g_build_filename (runtime_dir, remote, NULL);
if (g_file_test (host_socket, G_FILE_TEST_EXISTS))
{
g_autofree gchar *container_socket =
g_strdup_printf ("/run/user/%d/pv-pipewire", getuid ());
pv_environ_lock_env (container_env, "PIPEWIRE_REMOTE", "pv-pipewire");
flatpak_bwrap_add_args (sharing_bwrap,
"--ro-bind",
host_socket,
container_socket,
NULL);
}
else
{
pv_environ_lock_env (container_env, "PIPEWIRE_REMOTE", NULL);
}
}
}
/*
* Copyright 2021 Collabora Ltd.
*
* SPDX-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "environ.h"
#include "flatpak-bwrap-private.h"
void pv_wrap_add_pipewire_args (FlatpakBwrap *sharing_bwrap,
PvEnviron *container_env);
......@@ -86,6 +86,7 @@ pv_wrap_share_sockets (FlatpakBwrap *bwrap,
flatpak_run_add_session_dbus_args (sharing_bwrap);
flatpak_run_add_system_dbus_args (sharing_bwrap);
flatpak_run_add_resolved_args (sharing_bwrap);
pv_wrap_add_pipewire_args (sharing_bwrap, container_env);
}
envp = pv_bwrap_steal_envp (sharing_bwrap);
......
......@@ -26,6 +26,7 @@
#include "flatpak-bwrap-private.h"
#include "flatpak-exports-private.h"
#include "wrap-pipewire.h"
void pv_wrap_share_sockets (FlatpakBwrap *bwrap,
PvEnviron *container_env,
......
......@@ -1725,7 +1725,7 @@ main (int argc,
g_debug ("Environment variables:");
qsort (env, g_strv_length (env), sizeof (char *), pv_envp_cmp);
qsort (env, g_strv_length (env), sizeof (char *), flatpak_envp_cmp);
for (i = 0; env[i] != NULL; i++)
{
......@@ -1971,7 +1971,6 @@ main (int argc,
opt_runtime_id,
opt_variable_dir,
bwrap_executable,
tools_dir,
graphics_provider,
original_environ,
flags,
......@@ -2749,9 +2748,7 @@ main (int argc,
/* We'll have permuted the order anyway, so we might as well sort it,
* to make debugging a bit easier. */
if (final_argv->envp != NULL)
qsort (final_argv->envp, g_strv_length (final_argv->envp),
sizeof (char *), pv_envp_cmp);
flatpak_bwrap_sort_envp (final_argv);
if (opt_verbose)
{
......
......@@ -281,17 +281,18 @@ _srt_resolve_in_sysroot (int sysroot,
if (!glnx_fstatat (fd, "", &stat_buf, AT_EMPTY_PATH, error))
{
g_prefix_error (error,
"Unable to determine whether \"%s\" "
"Unable to determine whether \"%s/%s\" "
"is a directory",
current_path->str);
current_path->str, next);
return -1;
}
if (!S_ISDIR (stat_buf.st_mode))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
"\"%s\" is not a directory",
current_path->str);
"\"%s/%s\" is not a directory",
current_path->str, next);
return -1;
}
}
......
......@@ -1231,15 +1231,10 @@ ensure_overrides_cached (SrtSystemInfo *self)
if (!self->overrides.have_data)
{
static const char * const paths[] = {
"overrides",
"usr/lib/pressure-vessel/overrides",
"overrides/",
"usr/lib/pressure-vessel/overrides/",
};
g_autoptr(GError) error = NULL;
g_autofree gchar *output = NULL;
g_autofree gchar *messages = NULL;
g_autofree gchar *runtime = NULL;
const gchar *argv[] = {"find", NULL, "-ls", NULL};
int exit_status = -1;
gsize i;
self->overrides.have_data = TRUE;
......@@ -1255,46 +1250,14 @@ ensure_overrides_cached (SrtSystemInfo *self)
if (_srt_file_test_in_sysroot (self->sysroot, self->sysroot_fd,
paths[i], G_FILE_TEST_EXISTS))
{
argv[1] = paths[i];
self->overrides.values = _srt_recursive_list_content (self->sysroot,
self->sysroot_fd,
paths[i],
self->env,
&self->overrides.messages);
break;
}
}
if (argv[1] == NULL)
return;
if (!g_spawn_sync (self->sysroot, /* working directory */
(gchar **) argv,
self->env,
G_SPAWN_SEARCH_PATH,
_srt_child_setup_unblock_signals,
NULL, /* user data */
&output, /* stdout */
&messages, /* stderr */
&exit_status,
&error))
{
g_debug ("An error occurred calling the \"find\" binary: %s", error->message);
self->overrides.messages = g_new0 (gchar *, 2);
self->overrides.messages[0] = g_strdup_printf ("%s %d: %s", g_quark_to_string (error->domain), error->code, error->message);
self->overrides.messages[1] = NULL;
return;
}
if (exit_status != 0)
g_debug ("... wait status %d", exit_status);
if (output != NULL)
{
g_strchomp (output);
self->overrides.values = g_strsplit (output, "\n", -1);
}
if (messages != NULL)
{
g_strchomp (messages);
self->overrides.messages = g_strsplit (messages, "\n", -1);
}
}
}
......@@ -1311,11 +1274,10 @@ ensure_overrides_cached (SrtSystemInfo *self)
*
* The output is intended to be human-readable debugging information,
* rather than something to use programmatically, and its format is
* not guaranteed. It is currently in `find -ls` format.
* not guaranteed.
*
* Similarly, @messages is intended to be human-readable debugging
* information. It is currently whatever was output on standard error
* by `find -ls`.
* information.
*
* Returns: (array zero-terminated=1) (transfer full) (nullable): A
* %NULL-terminated array of libraries that have been overridden,
......@@ -1340,11 +1302,7 @@ srt_system_info_list_pressure_vessel_overrides (SrtSystemInfo *self,
static void
ensure_pinned_libs_cached (SrtSystemInfo *self)
{
gchar *output = NULL;
gchar *messages = NULL;
gchar *runtime = NULL;
int exit_status = -1;
GError *error = NULL;
g_autofree gchar *runtime = NULL;
g_return_if_fail (_srt_check_not_setuid ());
......@@ -1357,86 +1315,18 @@ ensure_pinned_libs_cached (SrtSystemInfo *self)
if (runtime == NULL || g_strcmp0 (runtime, "/") == 0)
return;
const gchar *argv[] = {"find", "pinned_libs_32", "-ls", NULL};
if (!g_spawn_sync (runtime, /* working directory */
(gchar **) argv,
self->env,
G_SPAWN_SEARCH_PATH,
_srt_child_setup_unblock_signals,
NULL, /* user data */
&output, /* stdout */
&messages, /* stderr */
&exit_status,
&error))
{
g_debug ("An error occurred calling the \"find\" binary: %s", error->message);
self->pinned_libs.messages_32 = g_new0 (gchar *, 2);
self->pinned_libs.messages_32[0] = g_strdup_printf ("%s %d: %s", g_quark_to_string (error->domain), error->code, error->message);
self->pinned_libs.messages_32[1] = NULL;
goto out;
}
if (exit_status != 0)
g_debug ("... wait status %d", exit_status);
if (output != NULL)
{
g_strchomp (output);
self->pinned_libs.values_32 = g_strsplit (output, "\n", -1);
}
if (messages != NULL)
{
g_strchomp (messages);
self->pinned_libs.messages_32 = g_strsplit (messages, "\n", -1);
}
g_free (output);
g_free (messages);
/* Do the same check for `pinned_libs_64` */
argv[1] = "pinned_libs_64";
if (!g_spawn_sync (runtime, /* working directory */
(gchar **) argv,
self->env,
G_SPAWN_SEARCH_PATH,
_srt_child_setup_unblock_signals,
NULL, /* user data */
&output, /* stdout */
&messages, /* stderr */
&exit_status,
&error))
{
g_debug ("An error occurred calling the \"find\" binary: %s", error->message);
self->pinned_libs.messages_64 = g_new0 (gchar *, 2);
self->pinned_libs.messages_64[0] = g_strdup_printf ("%s %d: %s", g_quark_to_string (error->domain), error->code, error->message);
self->pinned_libs.messages_64[1] = NULL;
goto out;
}
if (exit_status != 0)
g_debug ("... wait status %d", exit_status);
if (output != NULL)
{
g_strchomp (output);
self->pinned_libs.values_64 = g_strsplit (output, "\n", -1);
}
if (messages != NULL)
{
g_strchomp (messages);
self->pinned_libs.messages_64 = g_strsplit (messages, "\n", -1);
}
self->pinned_libs.values_32 = _srt_recursive_list_content (runtime,
-1,
"pinned_libs_32",
self->env,
&self->pinned_libs.messages_32);
self->pinned_libs.values_64 = _srt_recursive_list_content (runtime,
-1,
"pinned_libs_64",
self->env,
&self->pinned_libs.messages_64);
}
out:
g_clear_error (&error);
g_free (output);
g_free (messages);
g_free (runtime);
}
/**
......@@ -1461,14 +1351,14 @@ ensure_pinned_libs_cached (SrtSystemInfo *self)
*
* The output is intended to be human-readable debugging information,
* rather than something to use programmatically, and its format is
* not guaranteed. It is currently in `find -ls` format.
* not guaranteed.
*
* Similarly, @messages is intended to be human-readable debugging
* information. It is currently whatever was output on standard error
* by `find -ls`.
* information.
*
* Returns: (array zero-terminated=1) (transfer full) (element-type utf8) (nullable):
* An array of strings, or %NULL if we were unable to call "find".
* An array of strings, or %NULL if not in an `LD_LIBRARY_PATH`-based Steam
* Runtime or if it was not possible to list the pinned libs.
* Free with g_strfreev().
*/
gchar **
......@@ -1507,14 +1397,14 @@ srt_system_info_list_pinned_libs_32 (SrtSystemInfo *self,
*
* The output is intended to be human-readable debugging information,
* rather than something to use programmatically, and its format is
* not guaranteed. It is currently in `find -ls` format.
* not guaranteed.
*
* Similarly, @messages is intended to be human-readable debugging
* information. It is currently whatever was output on standard error
* by `find -ls`.
* information.
*
* Returns: (array zero-terminated=1) (transfer full) (element-type utf8) (nullable):
* An array of strings, or %NULL if we were unable to call "find".
* An array of strings, or %NULL if not in an `LD_LIBRARY_PATH`-based Steam
* Runtime or if it was not possible to list the pinned libs.
* Free with g_strfreev().
*/
gchar **
......
......@@ -114,6 +114,15 @@ G_GNUC_INTERNAL gboolean _srt_steam_command_via_pipe (const char * const *argume
gssize n_arguments,
GError **error);
G_GNUC_INTERNAL gchar **_srt_recursive_list_content (const gchar *sysroot,
int sysroot_fd,
const gchar *directory,
gchar **envp,
gchar ***messages_out);
G_GNUC_INTERNAL const char *_srt_get_path_after (const char *str,
const char *prefix);
/*
* _srt_is_same_stat:
* @a: a stat buffer
......
......@@ -1129,3 +1129,290 @@ _srt_steam_command_via_pipe (const char * const *arguments,
return TRUE;
}
typedef struct
{
const char *from;
const char *to;
} CommonReplacements;
/*
* _srt_list_directory_content:
* @working_dir_fd: File descriptor to the current working directory
* @working_dir_path: (not nullable) (type filename): Working directory in
* the sysroot
* @sub_directory: (nullable) (type filename): If %NULL, the @working_dir_path
* itself will be opened
* @common_replacements: (nullable): If not %NULL, perform these replacements
* to the beginning of the targets paths for symlinks that have been found
* @level: Current level of recursion
* @result: (not nullable): The elements that @directory contains are appended
* to this array
* @messages: (not nullable): Human-readable debug information are appended
* to this array
*/
static void
_srt_list_directory_content (int working_dir_fd,
const gchar *working_dir_path,
const gchar *sub_directory,
const CommonReplacements *common_replacements,
int level,
GPtrArray *result,
GPtrArray *messages)
{
g_autofree gchar *full_working_path = NULL;
g_auto(GLnxDirFdIterator) iter = { FALSE };
g_autoptr(GError) error = NULL;
gsize i;
g_return_if_fail (working_dir_path != NULL);
g_return_if_fail (result != NULL);
g_return_if_fail (messages != NULL);
if (sub_directory != NULL)
full_working_path = g_build_filename (working_dir_path, sub_directory, NULL);
else
full_working_path = g_strdup (working_dir_path);
/* Arbitrary limit. If we reach this level of recursion it's a sign that
* something went wrong and it's better to bail out. */
if (level > 9)
{
g_ptr_array_add (messages, g_strdup_printf ("%s/... (too much recursion, not shown)",
full_working_path));
return;
}
if (!glnx_dirfd_iterator_init_at (working_dir_fd,
sub_directory != NULL ? sub_directory : ".",
FALSE,
&iter,
&error))
{
glnx_prefix_error (&error,
"An error occurred trying to initialize an iterator for \"%s\"",
full_working_path);
g_debug ("%s", error->message);
g_ptr_array_add (messages, g_strdup_printf ("%s %d: %s",
g_quark_to_string (error->domain),
error->code, error->message));
return;
}
while (error == NULL)
{
struct dirent *dent;
g_autofree gchar *full_name = NULL;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent, NULL, &error))
{
glnx_prefix_error (&error, "An error occurred trying to initerate through \"%s\"",
full_working_path);
g_debug ("%s", error->message);
g_ptr_array_add (messages, g_strdup_printf ("%s %d: %s",
g_quark_to_string (error->domain),
error->code, error->message));
return;
}
if (dent == NULL)
break;
full_name = g_build_filename (full_working_path, dent->d_name, NULL);
if (dent->d_type == DT_LNK)
{
g_autofree gchar *target = NULL;
target = glnx_readlinkat_malloc (iter.fd, dent->d_name, NULL, &error);
if (target == NULL)
{
glnx_prefix_error (&error, "An error occurred trying to read the symlink \"%s\"",
full_name);
g_debug ("%s", error->message);
g_ptr_array_add (messages, g_strdup_printf ("%s %d: %s",
g_quark_to_string (error->domain),
error->code, error->message));
g_clear_error (&error);
target = g_strdup ("(unknown)");
}
for (i = 0; common_replacements != NULL && common_replacements[i].to != NULL; i++)
{
if (common_replacements[i].from == NULL)
continue;
const gchar *after = _srt_get_path_after (target, common_replacements[i].from);
if (after != NULL)
{
g_autofree gchar *new_target = NULL;
new_target = g_build_filename (common_replacements[i].to, after, NULL);
g_clear_pointer (&target, g_free);
target = g_steal_pointer (&new_target);
break;
}
}
g_ptr_array_add (result, g_strdup_printf ("%s -> %s", full_name, target));
}
else if (dent->d_type == DT_DIR)
{
g_ptr_array_add (result, g_strdup_printf ("%s/", full_name));
_srt_list_directory_content (iter.fd, full_working_path, dent->d_name,
common_replacements, level + 1, result, messages);
}
else
{
g_ptr_array_add (result, g_steal_pointer (&full_name));
}
}
}
/*
* _srt_recursive_list_content:
* @sysroot: (not nullable) (type filename): A path used as the root
* @sysroot_fd: A file descriptor opened on @sysroot, or negative to
* reopen it
* @directory: (not nullable) (type filename): A path below the root directory,
* either absolute or relative (to the root)
* @envp: (array zero-terminated=1) (not nullable): Behave as though `environ`
* was this array
* @messages_out: (optional) (out) (array zero-terminated=1) (transfer full):
* If not %NULL, used to return a %NULL-terminated array of diagnostic
* messages. Free with g_strfreev().
*
* Returns: (array zero-terminated=1) (transfer full) (nullable): A
* %NULL-terminated array of files, symbolic links and directories, that
* are present in the provided @directory. Free with g_strfreev().
*/
gchar **
_srt_recursive_list_content (const gchar *sysroot,
int sysroot_fd,
const gchar *directory,
gchar **envp,
gchar ***messages_out)
{
g_autoptr(GPtrArray) content = NULL;
g_autoptr(GPtrArray) messages = NULL;
glnx_autofd int local_sysroot_fd = -1;
glnx_autofd int top_fd = -1;
g_autoptr(GError) error = NULL;
const gchar *steam_runtime = NULL;
g_return_val_if_fail (sysroot != NULL, NULL);
g_return_val_if_fail (directory != NULL, NULL);
g_return_val_if_fail (envp != NULL, NULL);
g_return_val_if_fail (messages_out == NULL || *messages_out == NULL, NULL);
steam_runtime = g_environ_getenv (envp, "STEAM_RUNTIME");
/* If STEAM_RUNTIME is just the root directory we don't want to replace
* every leading '/' with $STEAM_RUNTIME */
if (g_strcmp0 (steam_runtime, "/") == 0)
steam_runtime = NULL;
const CommonReplacements common_replacements[] =
{
{ steam_runtime, "$STEAM_RUNTIME" },
{ g_environ_getenv (envp, "HOME"), "$HOME" },
{ NULL, NULL },
};
content = g_ptr_array_new_with_free_func (g_free);
messages = g_ptr_array_new_with_free_func (g_free);
if (sysroot_fd < 0)
{
if (!glnx_opendirat (-1, sysroot, FALSE, &local_sysroot_fd, &error))
{
glnx_prefix_error (&error, "An error occurred trying to open sysroot \"%s\"",
sysroot);
g_debug ("%s", error->message);
g_ptr_array_add (messages, g_strdup_printf ("%s %d: %s",
g_quark_to_string (error->domain),
error->code, error->message));
goto out;
}
sysroot_fd = local_sysroot_fd;
}
top_fd = _srt_resolve_in_sysroot (sysroot_fd,
directory,
SRT_RESOLVE_FLAGS_DIRECTORY,
NULL,
&error);
if (top_fd < 0)
{
glnx_prefix_error (&error, "An error occurred trying to resolve \"%s\" in sysroot",
directory);
g_debug ("%s", error->message);
g_ptr_array_add (messages, g_strdup_printf ("%s %d: %s",
g_quark_to_string (error->domain),
error->code, error->message));
goto out;
}
_srt_list_directory_content (top_fd, directory, NULL, common_replacements, 0,
content, messages);
g_ptr_array_sort (content, _srt_indirect_strcmp0);
out:
if (content->len > 0)
g_ptr_array_add (content, NULL);
if (messages_out != NULL && messages->len > 0)
{
g_ptr_array_add (messages, NULL);
*messages_out = (GStrv) g_ptr_array_free (g_steal_pointer (&messages), FALSE);
}
return (GStrv) g_ptr_array_free (g_steal_pointer (&content), FALSE);
}
/*
* @str: A path
* @prefix: A possible prefix
*
* The same as flatpak_has_path_prefix(), but instead of a boolean,
* return the part of @str after @prefix (non-%NULL but possibly empty)
* if @str has prefix @prefix, or %NULL if it does not.
*
* Returns: (nullable) (transfer none): the part of @str after @prefix,
* or %NULL if @str is not below @prefix
*/
const char *
_srt_get_path_after (const char *str,
const char *prefix)
{
while (TRUE)
{
/* Skip consecutive slashes to reach next path
element */
while (*str == '/')
str++;
while (*prefix == '/')
prefix++;
/* No more prefix path elements? Done! */
if (*prefix == 0)
return str;
/* Compare path element */
while (*prefix != 0 && *prefix != '/')
{
if (*str != *prefix)
return NULL;
str++;
prefix++;
}
/* Matched prefix path element,
must be entire str path element */
if (*str != '/' && *str != 0)
return NULL;
}
}
.deps
/.version
/INSTALL
/build-aux/*
/data/*-linux-gnu*-capsule-mkstublib
/m4/*
/test-suite.log
/tests/*.log
/tests/*.t
/tests/*.test
/tests/*.trs
/tests/notgl-dlopener
/tests/notgl-helper-user
/tests/notgl-user
/tests/shim/libnotgl.so.0.c
/tests/shim/libnotgles.so.1.c
Makefile.in
aclocal.m4
autom4te.cache
config.status
config.log
configure
libtool
update
Makefile
.*stamp
.libs
*.la
*.lo
*.o
shim/*.so.c
shim/*.so.symbols
shim/*.so.map
/capsule-capture-libs
/capsule-elf-dump
print-libstubs
capsule-symbols
capsule-version
TAGS
tags
*~
data/*.pc
data/dirconf.txt
*.swp
*.tmp
install.log
*.1
*.3
*-docs.xml
*.types
gtk-doc.make
*.tar.*
*.tgz
*.tar
tmpl/
*.stamp
html/
xml/
libcapsule-*.txt
*.args
*.hierarchy
*.interfaces
*.prerequisites
*.signals
!/build-aux/git-version-gen
!/m4/ax_is_release.m4
!/m4/capsule_*.m4
Vivek Das Mohapatra <vivek@collabora.com>; <vivek@etla.org>
This diff is collapsed.
This diff is collapsed.