forked from Mirrors/flatpak-builder
2178 lines
60 KiB
C
2178 lines
60 KiB
C
/*
|
|
* Copyright © 2014 Red Hat, Inc
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors:
|
|
* Alexander Larsson <alexl@redhat.com>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "builder-flatpak-utils.h"
|
|
|
|
#include <glib/gi18n.h>
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/file.h>
|
|
#include <sys/types.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <glib.h>
|
|
#include "libglnx/libglnx.h"
|
|
#include <libsoup/soup.h>
|
|
#include <gio/gunixoutputstream.h>
|
|
#include <gio/gunixinputstream.h>
|
|
|
|
|
|
GFile *
|
|
flatpak_file_new_tmp_in (GFile *dir,
|
|
const char *template,
|
|
GError **error)
|
|
{
|
|
glnx_fd_close int tmp_fd = -1;
|
|
g_autofree char *tmpl = g_build_filename (flatpak_file_get_path_cached (dir), template, NULL);
|
|
|
|
tmp_fd = g_mkstemp_full (tmpl, O_RDWR, 0644);
|
|
if (tmp_fd == -1)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return NULL;
|
|
}
|
|
|
|
return g_file_new_for_path (tmpl);
|
|
}
|
|
|
|
gboolean
|
|
flatpak_write_update_checksum (GOutputStream *out,
|
|
gconstpointer data,
|
|
gsize len,
|
|
gsize *out_bytes_written,
|
|
GChecksum **checksums,
|
|
gsize n_checksums,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gsize i;
|
|
|
|
if (out)
|
|
{
|
|
if (!g_output_stream_write_all (out, data, len, out_bytes_written,
|
|
cancellable, error))
|
|
return FALSE;
|
|
}
|
|
else if (out_bytes_written)
|
|
{
|
|
*out_bytes_written = len;
|
|
}
|
|
|
|
for (i = 0; i < n_checksums; i++)
|
|
g_checksum_update (checksums[i], data, len);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_splice_update_checksum (GOutputStream *out,
|
|
GInputStream *in,
|
|
GChecksum **checksums,
|
|
gsize n_checksums,
|
|
FlatpakLoadUriProgress progress,
|
|
gpointer progress_data,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gsize bytes_read, bytes_written;
|
|
char buf[32*1024];
|
|
guint64 downloaded_bytes = 0;
|
|
gint64 progress_start;
|
|
|
|
progress_start = g_get_monotonic_time ();
|
|
do
|
|
{
|
|
if (!g_input_stream_read_all (in, buf, sizeof buf, &bytes_read, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!flatpak_write_update_checksum (out, buf, bytes_read, &bytes_written,
|
|
checksums, n_checksums,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
downloaded_bytes += bytes_read;
|
|
|
|
if (progress &&
|
|
g_get_monotonic_time () - progress_start > 5 * 1000000)
|
|
{
|
|
progress (downloaded_bytes, progress_data);
|
|
progress_start = g_get_monotonic_time ();
|
|
}
|
|
}
|
|
while (bytes_read > 0);
|
|
|
|
if (progress)
|
|
progress (downloaded_bytes, progress_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Returns end of matching path prefix, or NULL if no match */
|
|
const char *
|
|
flatpak_path_match_prefix (const char *pattern,
|
|
const char *string)
|
|
{
|
|
char c, test;
|
|
const char *tmp;
|
|
|
|
while (*pattern == '/')
|
|
pattern++;
|
|
|
|
while (*string == '/')
|
|
string++;
|
|
|
|
while (TRUE)
|
|
{
|
|
switch (c = *pattern++)
|
|
{
|
|
case 0:
|
|
if (*string == '/' || *string == 0)
|
|
return string;
|
|
return NULL;
|
|
|
|
case '?':
|
|
if (*string == '/' || *string == 0)
|
|
return NULL;
|
|
string++;
|
|
break;
|
|
|
|
case '*':
|
|
c = *pattern;
|
|
|
|
while (c == '*')
|
|
c = *++pattern;
|
|
|
|
/* special case * at end */
|
|
if (c == 0)
|
|
{
|
|
char *tmp = strchr (string, '/');
|
|
if (tmp != NULL)
|
|
return tmp;
|
|
return string + strlen (string);
|
|
}
|
|
else if (c == '/')
|
|
{
|
|
string = strchr (string, '/');
|
|
if (string == NULL)
|
|
return NULL;
|
|
break;
|
|
}
|
|
|
|
while ((test = *string) != 0)
|
|
{
|
|
tmp = flatpak_path_match_prefix (pattern, string);
|
|
if (tmp != NULL)
|
|
return tmp;
|
|
if (test == '/')
|
|
break;
|
|
string++;
|
|
}
|
|
return NULL;
|
|
|
|
default:
|
|
if (c != *string)
|
|
return NULL;
|
|
string++;
|
|
break;
|
|
}
|
|
}
|
|
return NULL; /* Should not be reached */
|
|
}
|
|
|
|
#if !defined(__i386__) && !defined(__x86_64__) && !defined(__aarch64__) && !defined(__arm__)
|
|
static const char *
|
|
flatpak_get_kernel_arch (void)
|
|
{
|
|
static struct utsname buf;
|
|
static char *arch = NULL;
|
|
char *m;
|
|
|
|
if (arch != NULL)
|
|
return arch;
|
|
|
|
if (uname (&buf))
|
|
{
|
|
arch = "unknown";
|
|
return arch;
|
|
}
|
|
|
|
/* By default, just pass on machine, good enough for most arches */
|
|
arch = buf.machine;
|
|
|
|
/* Override for some arches */
|
|
|
|
m = buf.machine;
|
|
/* i?86 */
|
|
if (strlen (m) == 4 && m[0] == 'i' && m[2] == '8' && m[3] == '6')
|
|
{
|
|
arch = "i386";
|
|
}
|
|
else if (g_str_has_prefix (m, "arm"))
|
|
{
|
|
if (g_str_has_suffix (m, "b"))
|
|
arch = "armeb";
|
|
else
|
|
arch = "arm";
|
|
}
|
|
else if (strcmp (m, "mips") == 0)
|
|
{
|
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
arch = "mipsel";
|
|
#endif
|
|
}
|
|
else if (strcmp (m, "mips64") == 0)
|
|
{
|
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
arch = "mips64el";
|
|
#endif
|
|
}
|
|
|
|
return arch;
|
|
}
|
|
#endif /* !__i386__ && !__x86_64__ && !__aarch64__ && !__arm__ */
|
|
|
|
/* This maps the kernel-reported uname to a single string representing
|
|
* the cpu family, in the sense that all members of this family would
|
|
* be able to understand and link to a binary file with such cpu
|
|
* opcodes. That doesn't necessarily mean that all members of the
|
|
* family can run all opcodes, for instance for modern 32bit intel we
|
|
* report "i386", even though they support instructions that the
|
|
* original i386 cpu cannot run. Still, such an executable would
|
|
* at least try to execute a 386, whereas an arm binary would not.
|
|
*/
|
|
const char *
|
|
flatpak_get_arch (void)
|
|
{
|
|
/* Avoid using uname on multiarch machines, because uname reports the kernels
|
|
* arch, and that may be different from userspace. If e.g. the kernel is 64bit and
|
|
* the userspace is 32bit we want to use 32bit by default. So, we take the current build
|
|
* arch as the default. */
|
|
#if defined(__i386__)
|
|
return "i386";
|
|
#elif defined(__x86_64__)
|
|
return "x86_64";
|
|
#elif defined(__aarch64__)
|
|
return "aarch64";
|
|
#elif defined(__arm__)
|
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
return "arm";
|
|
#else
|
|
return "armeb";
|
|
#endif
|
|
#else
|
|
return flatpak_get_kernel_arch ();
|
|
#endif
|
|
}
|
|
|
|
gboolean
|
|
flatpak_is_in_sandbox (void)
|
|
{
|
|
static gsize in_sandbox = 0;
|
|
|
|
if (g_once_init_enter (&in_sandbox))
|
|
{
|
|
g_autofree char *path = g_build_filename (g_get_user_runtime_dir (), "flatpak-info", NULL);
|
|
gsize new_in_sandbox;
|
|
|
|
new_in_sandbox = 2;
|
|
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
|
|
new_in_sandbox = 1;
|
|
|
|
g_once_init_leave (&in_sandbox, new_in_sandbox);
|
|
}
|
|
|
|
return in_sandbox == 1;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_break_hardlink (GFile *file, GError **error)
|
|
{
|
|
g_autofree char *path = g_file_get_path (file);
|
|
struct stat st_buf;
|
|
|
|
if (stat (path, &st_buf) == 0 && st_buf.st_nlink > 1)
|
|
{
|
|
g_autoptr(GFile) dir = g_file_get_parent (file);
|
|
g_autoptr(GFile) tmp = NULL;
|
|
|
|
tmp = flatpak_file_new_tmp_in (dir, ".breaklinkXXXXXX", error);
|
|
if (tmp == NULL)
|
|
return FALSE;
|
|
|
|
if (!g_file_copy (file, tmp,
|
|
G_FILE_COPY_OVERWRITE,
|
|
NULL, NULL, NULL, error))
|
|
return FALSE;
|
|
|
|
if (rename (flatpak_file_get_path_cached (tmp), path) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
is_valid_initial_name_character (gint c, gboolean allow_dash)
|
|
{
|
|
return
|
|
(c >= 'A' && c <= 'Z') ||
|
|
(c >= 'a' && c <= 'z') ||
|
|
(c == '_') || (allow_dash && c == '-');
|
|
}
|
|
|
|
static gboolean
|
|
is_valid_name_character (gint c, gboolean allow_dash)
|
|
{
|
|
return
|
|
is_valid_initial_name_character (c, allow_dash) ||
|
|
(c >= '0' && c <= '9');
|
|
}
|
|
|
|
gboolean
|
|
flatpak_has_name_prefix (const char *string,
|
|
const char *name)
|
|
{
|
|
const char *rest;
|
|
|
|
if (!g_str_has_prefix (string, name))
|
|
return FALSE;
|
|
|
|
rest = string + strlen (name);
|
|
return
|
|
*rest == 0 ||
|
|
*rest == '.' ||
|
|
!is_valid_name_character (*rest, FALSE);
|
|
}
|
|
|
|
|
|
/* Dashes are only valid in the last part of the app id, so
|
|
we replace them with underscore so we can suffix the id */
|
|
char *
|
|
flatpak_make_valid_id_prefix (const char *orig_id)
|
|
{
|
|
char *id, *t;
|
|
|
|
id = g_strdup (orig_id);
|
|
t = id;
|
|
while (*t != 0 && *t != '/')
|
|
{
|
|
if (*t == '-')
|
|
*t = '_';
|
|
|
|
t++;
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
char *
|
|
flatpak_compose_ref (gboolean app,
|
|
const char *name,
|
|
const char *branch,
|
|
const char *arch)
|
|
{
|
|
if (app)
|
|
return flatpak_build_app_ref (name, branch, arch);
|
|
else
|
|
return flatpak_build_runtime_ref (name, branch, arch);
|
|
}
|
|
|
|
char *
|
|
flatpak_build_untyped_ref (const char *runtime,
|
|
const char *branch,
|
|
const char *arch)
|
|
{
|
|
if (arch == NULL)
|
|
arch = flatpak_get_arch ();
|
|
|
|
return g_build_filename (runtime, arch, branch, NULL);
|
|
}
|
|
|
|
char *
|
|
flatpak_build_runtime_ref (const char *runtime,
|
|
const char *branch,
|
|
const char *arch)
|
|
{
|
|
if (branch == NULL)
|
|
branch = "master";
|
|
|
|
if (arch == NULL)
|
|
arch = flatpak_get_arch ();
|
|
|
|
return g_build_filename ("runtime", runtime, arch, branch, NULL);
|
|
}
|
|
|
|
char *
|
|
flatpak_build_app_ref (const char *app,
|
|
const char *branch,
|
|
const char *arch)
|
|
{
|
|
if (branch == NULL)
|
|
branch = "master";
|
|
|
|
if (arch == NULL)
|
|
arch = flatpak_get_arch ();
|
|
|
|
return g_build_filename ("app", app, arch, branch, NULL);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GError *error;
|
|
GError *splice_error;
|
|
GMainLoop *loop;
|
|
int refs;
|
|
} SpawnData;
|
|
|
|
static void
|
|
spawn_data_exit (SpawnData *data)
|
|
{
|
|
data->refs--;
|
|
if (data->refs == 0)
|
|
g_main_loop_quit (data->loop);
|
|
}
|
|
|
|
static void
|
|
spawn_output_spliced_cb (GObject *obj,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
SpawnData *data = user_data;
|
|
|
|
g_output_stream_splice_finish (G_OUTPUT_STREAM (obj), result, &data->splice_error);
|
|
spawn_data_exit (data);
|
|
}
|
|
|
|
static void
|
|
spawn_exit_cb (GObject *obj,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
SpawnData *data = user_data;
|
|
|
|
g_subprocess_wait_check_finish (G_SUBPROCESS (obj), result, &data->error);
|
|
spawn_data_exit (data);
|
|
}
|
|
|
|
static gboolean
|
|
needs_quoting (const char *arg)
|
|
{
|
|
while (*arg != 0)
|
|
{
|
|
char c = *arg;
|
|
if (!g_ascii_isalnum (c) &&
|
|
!(c == '-' || c == '/' || c == '~' ||
|
|
c == ':' || c == '.' || c == '_' ||
|
|
c == '='))
|
|
return TRUE;
|
|
arg++;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
char *
|
|
flatpak_quote_argv (const char *argv[])
|
|
{
|
|
GString *res = g_string_new ("");
|
|
int i;
|
|
|
|
for (i = 0; argv[i] != NULL; i++)
|
|
{
|
|
if (i != 0)
|
|
g_string_append_c (res, ' ');
|
|
|
|
if (needs_quoting (argv[i]))
|
|
{
|
|
g_autofree char *quoted = g_shell_quote (argv[i]);
|
|
g_string_append (res, quoted);
|
|
}
|
|
else
|
|
g_string_append (res, argv[i]);
|
|
}
|
|
|
|
return g_string_free (res, FALSE);
|
|
}
|
|
|
|
gboolean
|
|
flatpak_spawn (GFile *dir,
|
|
char **output,
|
|
GSubprocessFlags flags,
|
|
GError **error,
|
|
const gchar *argv0,
|
|
va_list ap)
|
|
{
|
|
GPtrArray *args;
|
|
const gchar *arg;
|
|
gboolean res;
|
|
|
|
args = g_ptr_array_new ();
|
|
g_ptr_array_add (args, (gchar *) argv0);
|
|
while ((arg = va_arg (ap, const gchar *)))
|
|
g_ptr_array_add (args, (gchar *) arg);
|
|
g_ptr_array_add (args, NULL);
|
|
|
|
res = flatpak_spawnv (dir, output, flags, error, (const gchar * const *) args->pdata);
|
|
|
|
g_ptr_array_free (args, TRUE);
|
|
|
|
return res;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_spawnv (GFile *dir,
|
|
char **output,
|
|
GSubprocessFlags flags,
|
|
GError **error,
|
|
const gchar * const *argv)
|
|
{
|
|
g_autoptr(GSubprocessLauncher) launcher = NULL;
|
|
g_autoptr(GSubprocess) subp = NULL;
|
|
GInputStream *in;
|
|
g_autoptr(GOutputStream) out = NULL;
|
|
g_autoptr(GMainLoop) loop = NULL;
|
|
SpawnData data = {0};
|
|
g_autofree gchar *commandline = NULL;
|
|
|
|
launcher = g_subprocess_launcher_new (0);
|
|
|
|
g_subprocess_launcher_setenv (launcher, "LANGUAGE", "C", TRUE);
|
|
|
|
if (output)
|
|
flags |= G_SUBPROCESS_FLAGS_STDOUT_PIPE;
|
|
|
|
g_subprocess_launcher_set_flags (launcher, flags);
|
|
|
|
if (dir)
|
|
{
|
|
g_autofree char *path = g_file_get_path (dir);
|
|
g_subprocess_launcher_set_cwd (launcher, path);
|
|
}
|
|
|
|
commandline = flatpak_quote_argv ((const char **)argv);
|
|
g_debug ("Running: %s", commandline);
|
|
|
|
subp = g_subprocess_launcher_spawnv (launcher, argv, error);
|
|
|
|
if (subp == NULL)
|
|
return FALSE;
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
data.loop = loop;
|
|
data.refs = 1;
|
|
|
|
if (output)
|
|
{
|
|
data.refs++;
|
|
in = g_subprocess_get_stdout_pipe (subp);
|
|
out = g_memory_output_stream_new_resizable ();
|
|
g_output_stream_splice_async (out,
|
|
in,
|
|
G_OUTPUT_STREAM_SPLICE_NONE,
|
|
0,
|
|
NULL,
|
|
spawn_output_spliced_cb,
|
|
&data);
|
|
}
|
|
|
|
g_subprocess_wait_async (subp, NULL, spawn_exit_cb, &data);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
if (data.error)
|
|
{
|
|
g_propagate_error (error, data.error);
|
|
g_clear_error (&data.splice_error);
|
|
return FALSE;
|
|
}
|
|
|
|
if (out)
|
|
{
|
|
if (data.splice_error)
|
|
{
|
|
g_propagate_error (error, data.splice_error);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Null terminate */
|
|
g_output_stream_write (out, "\0", 1, NULL, NULL);
|
|
g_output_stream_close (out, NULL, NULL);
|
|
*output = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (out));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GFile *
|
|
flatpak_build_file_va (GFile *base,
|
|
va_list args)
|
|
{
|
|
g_autoptr(GFile) res = g_object_ref (base);
|
|
const gchar *arg;
|
|
|
|
while ((arg = va_arg (args, const gchar *)))
|
|
{
|
|
GFile *child = g_file_resolve_relative_path (res, arg);
|
|
g_set_object (&res, child);
|
|
}
|
|
|
|
return g_steal_pointer (&res);
|
|
}
|
|
|
|
GFile *
|
|
flatpak_build_file (GFile *base, ...)
|
|
{
|
|
GFile *res;
|
|
va_list args;
|
|
|
|
va_start (args, base);
|
|
res = flatpak_build_file_va (base, args);
|
|
va_end (args);
|
|
|
|
return res;
|
|
}
|
|
|
|
const char *
|
|
flatpak_file_get_path_cached (GFile *file)
|
|
{
|
|
const char *path;
|
|
static GQuark _file_path_quark = 0;
|
|
|
|
if (G_UNLIKELY (_file_path_quark == 0))
|
|
_file_path_quark = g_quark_from_static_string ("flatpak-file-path");
|
|
|
|
do
|
|
{
|
|
path = g_object_get_qdata ((GObject*)file, _file_path_quark);
|
|
if (path == NULL)
|
|
{
|
|
g_autofree char *new_path = NULL;
|
|
new_path = g_file_get_path (file);
|
|
if (new_path == NULL)
|
|
return NULL;
|
|
|
|
if (g_object_replace_qdata ((GObject*)file, _file_path_quark,
|
|
NULL, new_path, g_free, NULL))
|
|
path = g_steal_pointer (&new_path);
|
|
}
|
|
}
|
|
while (path == NULL);
|
|
|
|
return path;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_cp_a (GFile *src,
|
|
GFile *dest,
|
|
FlatpakCpFlags flags,
|
|
GPtrArray *skip_files,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
GFileEnumerator *enumerator = NULL;
|
|
GFileInfo *src_info = NULL;
|
|
GFile *dest_child = NULL;
|
|
int dest_dfd = -1;
|
|
gboolean merge = (flags & FLATPAK_CP_FLAGS_MERGE) != 0;
|
|
gboolean no_chown = (flags & FLATPAK_CP_FLAGS_NO_CHOWN) != 0;
|
|
gboolean move = (flags & FLATPAK_CP_FLAGS_MOVE) != 0;
|
|
g_autoptr(GFileInfo) child_info = NULL;
|
|
GError *temp_error = NULL;
|
|
int r;
|
|
|
|
enumerator = g_file_enumerate_children (src, "standard::type,standard::name,unix::uid,unix::gid,unix::mode",
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable, error);
|
|
if (!enumerator)
|
|
goto out;
|
|
|
|
src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \
|
|
"time::modified,time::modified-usec,time::access,time::access-usec",
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable, error);
|
|
if (!src_info)
|
|
goto out;
|
|
|
|
do
|
|
r = mkdir (flatpak_file_get_path_cached (dest), 0755);
|
|
while (G_UNLIKELY (r == -1 && errno == EINTR));
|
|
if (r == -1 &&
|
|
(!merge || errno != EEXIST))
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
|
|
if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (dest), TRUE,
|
|
&dest_dfd, error))
|
|
goto out;
|
|
|
|
if (!no_chown)
|
|
{
|
|
do
|
|
r = fchown (dest_dfd,
|
|
g_file_info_get_attribute_uint32 (src_info, "unix::uid"),
|
|
g_file_info_get_attribute_uint32 (src_info, "unix::gid"));
|
|
while (G_UNLIKELY (r == -1 && errno == EINTR));
|
|
if (r == -1)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
do
|
|
r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode"));
|
|
while (G_UNLIKELY (r == -1 && errno == EINTR));
|
|
|
|
if (dest_dfd != -1)
|
|
{
|
|
(void) close (dest_dfd);
|
|
dest_dfd = -1;
|
|
}
|
|
|
|
while ((child_info = g_file_enumerator_next_file (enumerator, cancellable, &temp_error)))
|
|
{
|
|
const char *name = g_file_info_get_name (child_info);
|
|
g_autoptr(GFile) src_child = g_file_get_child (src, name);
|
|
gboolean skip = FALSE;
|
|
int i;
|
|
|
|
for (i = 0; skip_files != NULL && i < skip_files->len; i++)
|
|
{
|
|
if (g_file_equal (src_child, g_ptr_array_index (skip_files, i)))
|
|
{
|
|
skip = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dest_child)
|
|
g_object_unref (dest_child);
|
|
dest_child = g_file_get_child (dest, name);
|
|
|
|
if (skip)
|
|
{
|
|
/* skip src */
|
|
}
|
|
else if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY)
|
|
{
|
|
if (!flatpak_cp_a (src_child, dest_child, flags, skip_files,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
else
|
|
{
|
|
(void) unlink (flatpak_file_get_path_cached (dest_child));
|
|
GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS;
|
|
if (!no_chown)
|
|
copyflags |= G_FILE_COPY_ALL_METADATA;
|
|
if (move)
|
|
{
|
|
if (!g_file_move (src_child, dest_child, copyflags,
|
|
cancellable, NULL, NULL, error))
|
|
goto out;
|
|
}
|
|
else
|
|
{
|
|
if (!g_file_copy (src_child, dest_child, copyflags,
|
|
cancellable, NULL, NULL, error))
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
g_clear_object (&child_info);
|
|
}
|
|
|
|
if (temp_error != NULL)
|
|
{
|
|
g_propagate_error (error, temp_error);
|
|
goto out;
|
|
}
|
|
|
|
if (move &&
|
|
!g_file_delete (src, NULL, error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
out:
|
|
if (dest_dfd != -1)
|
|
(void) close (dest_dfd);
|
|
g_clear_object (&src_info);
|
|
g_clear_object (&enumerator);
|
|
g_clear_object (&dest_child);
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_zero_mtime (int parent_dfd,
|
|
const char *rel_path,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
struct stat stbuf;
|
|
|
|
if (TEMP_FAILURE_RETRY (fstatat (parent_dfd, rel_path, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
if (S_ISDIR (stbuf.st_mode))
|
|
{
|
|
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
|
gboolean inited;
|
|
|
|
inited = glnx_dirfd_iterator_init_at (parent_dfd, rel_path, FALSE, &dfd_iter, NULL);
|
|
|
|
while (inited)
|
|
{
|
|
struct dirent *dent;
|
|
|
|
if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, NULL, NULL) || dent == NULL)
|
|
break;
|
|
|
|
if (!flatpak_zero_mtime (dfd_iter.fd, dent->d_name,
|
|
cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* Update stbuf */
|
|
if (TEMP_FAILURE_RETRY (fstat (dfd_iter.fd, &stbuf)) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* OSTree checks out to mtime 0, so we do the same */
|
|
if (stbuf.st_mtime != OSTREE_TIMESTAMP)
|
|
{
|
|
const struct timespec times[2] = { { 0, UTIME_OMIT }, { OSTREE_TIMESTAMP, } };
|
|
|
|
if (TEMP_FAILURE_RETRY (utimensat (parent_dfd, rel_path, times, AT_SYMLINK_NOFOLLOW)) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Make a directory, and its parent. Don't error if it already exists.
|
|
* If you want a failure mode with EEXIST, use g_file_make_directory_with_parents. */
|
|
gboolean
|
|
flatpak_mkdir_p (GFile *dir,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
return glnx_shutil_mkdir_p_at (AT_FDCWD,
|
|
flatpak_file_get_path_cached (dir),
|
|
0777,
|
|
cancellable,
|
|
error);
|
|
}
|
|
|
|
gboolean
|
|
flatpak_rm_rf (GFile *dir,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
return glnx_shutil_rm_rf_at (AT_FDCWD,
|
|
flatpak_file_get_path_cached (dir),
|
|
cancellable, error);
|
|
}
|
|
|
|
gboolean flatpak_file_rename (GFile *from,
|
|
GFile *to,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
|
return FALSE;
|
|
|
|
if (rename (flatpak_file_get_path_cached (from),
|
|
flatpak_file_get_path_cached (to)) < 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#define OSTREE_GIO_FAST_QUERYINFO ("standard::name,standard::type,standard::size,standard::is-symlink,standard::symlink-target," \
|
|
"unix::device,unix::inode,unix::mode,unix::uid,unix::gid,unix::rdev")
|
|
|
|
/* This allocates and locks a subdir of the tmp dir, using an existing
|
|
* one with the same prefix if it is not in use already. */
|
|
gboolean
|
|
flatpak_allocate_tmpdir (int tmpdir_dfd,
|
|
const char *tmpdir_relpath,
|
|
const char *tmpdir_prefix,
|
|
char **tmpdir_name_out,
|
|
int *tmpdir_fd_out,
|
|
GLnxLockFile *file_lock_out,
|
|
gboolean *reusing_dir_out,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean reusing_dir = FALSE;
|
|
g_autofree char *tmpdir_name = NULL;
|
|
glnx_fd_close int tmpdir_fd = -1;
|
|
|
|
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
|
|
|
/* Look for existing tmpdir (with same prefix) to reuse */
|
|
if (!glnx_dirfd_iterator_init_at (tmpdir_dfd, tmpdir_relpath ? tmpdir_relpath : ".", FALSE, &dfd_iter, error))
|
|
return FALSE;
|
|
|
|
while (tmpdir_name == NULL)
|
|
{
|
|
struct dirent *dent;
|
|
glnx_fd_close int existing_tmpdir_fd = -1;
|
|
g_autoptr(GError) local_error = NULL;
|
|
g_autofree char *lock_name = NULL;
|
|
|
|
if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (dent == NULL)
|
|
break;
|
|
|
|
if (!g_str_has_prefix (dent->d_name, tmpdir_prefix))
|
|
continue;
|
|
|
|
/* Quickly skip non-dirs, if unknown we ignore ENOTDIR when opening instead */
|
|
if (dent->d_type != DT_UNKNOWN &&
|
|
dent->d_type != DT_DIR)
|
|
continue;
|
|
|
|
if (!glnx_opendirat (dfd_iter.fd, dent->d_name, FALSE,
|
|
&existing_tmpdir_fd, &local_error))
|
|
{
|
|
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&local_error));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
lock_name = g_strconcat (dent->d_name, "-lock", NULL);
|
|
|
|
/* We put the lock outside the dir, so we can hold the lock
|
|
* until the directory is fully removed */
|
|
if (!glnx_make_lock_file (dfd_iter.fd, lock_name, LOCK_EX | LOCK_NB,
|
|
file_lock_out, &local_error))
|
|
{
|
|
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&local_error));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Touch the reused directory so that we don't accidentally
|
|
* remove it due to being old when cleaning up the tmpdir
|
|
*/
|
|
(void) futimens (existing_tmpdir_fd, NULL);
|
|
|
|
/* We found an existing tmpdir which we managed to lock */
|
|
tmpdir_name = g_strdup (dent->d_name);
|
|
tmpdir_fd = glnx_steal_fd (&existing_tmpdir_fd);
|
|
reusing_dir = TRUE;
|
|
}
|
|
|
|
while (tmpdir_name == NULL)
|
|
{
|
|
g_autofree char *tmpdir_name_template = g_strconcat (tmpdir_prefix, "XXXXXX", NULL);
|
|
g_autoptr(GError) local_error = NULL;
|
|
g_autofree char *lock_name = NULL;
|
|
g_auto(GLnxTmpDir) new_tmpdir = { 0, };
|
|
|
|
/* No existing tmpdir found, create a new */
|
|
|
|
if (!glnx_mkdtempat (dfd_iter.fd, tmpdir_name_template, 0777,
|
|
&new_tmpdir, error))
|
|
return FALSE;
|
|
|
|
lock_name = g_strconcat (new_tmpdir.path, "-lock", NULL);
|
|
|
|
/* Note, at this point we can race with another process that picks up this
|
|
* new directory. If that happens we need to retry, making a new directory. */
|
|
if (!glnx_make_lock_file (dfd_iter.fd, lock_name, LOCK_EX | LOCK_NB,
|
|
file_lock_out, &local_error))
|
|
{
|
|
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
|
|
{
|
|
glnx_tmpdir_unset (&new_tmpdir); /* Don't delete */
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&local_error));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
tmpdir_name = g_strdup (new_tmpdir.path);
|
|
tmpdir_fd = dup (new_tmpdir.fd);
|
|
glnx_tmpdir_unset (&new_tmpdir); /* Don't delete */
|
|
}
|
|
|
|
if (tmpdir_name_out)
|
|
*tmpdir_name_out = g_steal_pointer (&tmpdir_name);
|
|
|
|
if (tmpdir_fd_out)
|
|
*tmpdir_fd_out = glnx_steal_fd (&tmpdir_fd);
|
|
|
|
if (reusing_dir_out)
|
|
*reusing_dir_out = reusing_dir;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
SoupSession *
|
|
flatpak_create_soup_session (const char *user_agent)
|
|
{
|
|
SoupSession *soup_session;
|
|
const char *http_proxy;
|
|
|
|
soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent,
|
|
SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
|
|
SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
|
|
SOUP_SESSION_TIMEOUT, 60,
|
|
SOUP_SESSION_IDLE_TIMEOUT, 60,
|
|
NULL);
|
|
soup_session_remove_feature_by_type (soup_session, SOUP_TYPE_CONTENT_DECODER);
|
|
http_proxy = g_getenv ("http_proxy");
|
|
if (http_proxy)
|
|
{
|
|
g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy);
|
|
if (!proxy_uri)
|
|
g_warning ("Invalid proxy URI '%s'", http_proxy);
|
|
else
|
|
g_object_set (soup_session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
|
|
}
|
|
|
|
return soup_session;
|
|
}
|
|
|
|
CURL *
|
|
flatpak_create_curl_session (const char *user_agent)
|
|
{
|
|
CURL *curl_session;
|
|
|
|
curl_global_init (CURL_GLOBAL_DEFAULT);
|
|
|
|
curl_session = curl_easy_init ();
|
|
if (curl_session == NULL)
|
|
return NULL;
|
|
|
|
curl_easy_setopt (curl_session, CURLOPT_CONNECTTIMEOUT, 60);
|
|
curl_easy_setopt (curl_session, CURLOPT_FAILONERROR, 1);
|
|
curl_easy_setopt (curl_session, CURLOPT_FOLLOWLOCATION, 1);
|
|
curl_easy_setopt (curl_session, CURLOPT_MAXREDIRS, 50);
|
|
curl_easy_setopt (curl_session, CURLOPT_NOPROGRESS, 0);
|
|
curl_easy_setopt (curl_session, CURLOPT_USERAGENT, user_agent);
|
|
|
|
return curl_session;
|
|
}
|
|
|
|
typedef enum {
|
|
FLATPAK_POLICY_NONE,
|
|
FLATPAK_POLICY_SEE,
|
|
FLATPAK_POLICY_TALK,
|
|
FLATPAK_POLICY_OWN
|
|
} FlatpakPolicy;
|
|
|
|
typedef enum {
|
|
FLATPAK_CONTEXT_SHARED_NETWORK = 1 << 0,
|
|
FLATPAK_CONTEXT_SHARED_IPC = 1 << 1,
|
|
} FlatpakContextShares;
|
|
|
|
/* In numerical order of more privs */
|
|
typedef enum {
|
|
FLATPAK_FILESYSTEM_MODE_READ_ONLY = 1,
|
|
FLATPAK_FILESYSTEM_MODE_READ_WRITE = 2,
|
|
FLATPAK_FILESYSTEM_MODE_CREATE = 3,
|
|
} FlatpakFilesystemMode;
|
|
|
|
|
|
/* Same order as enum */
|
|
const char *flatpak_context_shares[] = {
|
|
"network",
|
|
"ipc",
|
|
NULL
|
|
};
|
|
|
|
typedef enum {
|
|
FLATPAK_CONTEXT_SOCKET_X11 = 1 << 0,
|
|
FLATPAK_CONTEXT_SOCKET_WAYLAND = 1 << 1,
|
|
FLATPAK_CONTEXT_SOCKET_PULSEAUDIO = 1 << 2,
|
|
FLATPAK_CONTEXT_SOCKET_SESSION_BUS = 1 << 3,
|
|
FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS = 1 << 4,
|
|
FLATPAK_CONTEXT_SOCKET_FALLBACK_X11 = 1 << 5, /* For backwards compat, also set SOCKET_X11 */
|
|
} FlatpakContextSockets;
|
|
|
|
/* Same order as enum */
|
|
const char *flatpak_context_sockets[] = {
|
|
"x11",
|
|
"wayland",
|
|
"pulseaudio",
|
|
"session-bus",
|
|
"system-bus",
|
|
"fallback-x11",
|
|
NULL
|
|
};
|
|
|
|
const char *dont_mount_in_root[] = {
|
|
".", "..", "lib", "lib32", "lib64", "bin", "sbin", "usr", "boot", "root",
|
|
"tmp", "etc", "app", "run", "proc", "sys", "dev", "var", NULL
|
|
};
|
|
|
|
/* We don't want to export paths pointing into these, because they are readonly
|
|
(so we can't create mountpoints there) and don't match whats on the host anyway */
|
|
const char *dont_export_in[] = {
|
|
"/lib", "/lib32", "/lib64", "/bin", "/sbin", "/usr", "/etc", "/app", NULL
|
|
};
|
|
|
|
typedef enum {
|
|
FLATPAK_CONTEXT_DEVICE_DRI = 1 << 0,
|
|
FLATPAK_CONTEXT_DEVICE_ALL = 1 << 1,
|
|
FLATPAK_CONTEXT_DEVICE_KVM = 1 << 2,
|
|
} FlatpakContextDevices;
|
|
|
|
const char *flatpak_context_devices[] = {
|
|
"dri",
|
|
"all",
|
|
"kvm",
|
|
NULL
|
|
};
|
|
|
|
typedef enum {
|
|
FLATPAK_CONTEXT_FEATURE_DEVEL = 1 << 0,
|
|
FLATPAK_CONTEXT_FEATURE_MULTIARCH = 1 << 1,
|
|
} FlatpakContextFeatures;
|
|
|
|
const char *flatpak_context_features[] = {
|
|
"devel",
|
|
"multiarch",
|
|
NULL
|
|
};
|
|
|
|
struct FlatpakContext
|
|
{
|
|
FlatpakContextShares shares;
|
|
FlatpakContextShares shares_valid;
|
|
FlatpakContextSockets sockets;
|
|
FlatpakContextSockets sockets_valid;
|
|
FlatpakContextDevices devices;
|
|
FlatpakContextDevices devices_valid;
|
|
FlatpakContextFeatures features;
|
|
FlatpakContextFeatures features_valid;
|
|
GHashTable *env_vars;
|
|
GHashTable *persistent;
|
|
GHashTable *filesystems;
|
|
GHashTable *session_bus_policy;
|
|
GHashTable *system_bus_policy;
|
|
GHashTable *generic_policy;
|
|
};
|
|
|
|
FlatpakContext *
|
|
flatpak_context_new (void)
|
|
{
|
|
FlatpakContext *context;
|
|
|
|
context = g_slice_new0 (FlatpakContext);
|
|
context->env_vars = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
context->persistent = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
context->filesystems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
context->session_bus_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
context->system_bus_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
context->generic_policy = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, (GDestroyNotify)g_strfreev);
|
|
|
|
return context;
|
|
}
|
|
|
|
void
|
|
flatpak_context_free (FlatpakContext *context)
|
|
{
|
|
g_hash_table_destroy (context->env_vars);
|
|
g_hash_table_destroy (context->persistent);
|
|
g_hash_table_destroy (context->filesystems);
|
|
g_hash_table_destroy (context->session_bus_policy);
|
|
g_hash_table_destroy (context->system_bus_policy);
|
|
g_hash_table_destroy (context->generic_policy);
|
|
g_slice_free (FlatpakContext, context);
|
|
}
|
|
|
|
static guint32
|
|
flatpak_context_bitmask_from_string (const char *name, const char **names)
|
|
{
|
|
guint32 i;
|
|
|
|
for (i = 0; names[i] != NULL; i++)
|
|
{
|
|
if (strcmp (names[i], name) == 0)
|
|
return 1 << i;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_bitmask_to_args (guint32 enabled, guint32 valid, const char **names,
|
|
const char *enable_arg, const char *disable_arg,
|
|
GPtrArray *args)
|
|
{
|
|
guint32 i;
|
|
|
|
for (i = 0; names[i] != NULL; i++)
|
|
{
|
|
guint32 bitmask = 1 << i;
|
|
if (valid & bitmask)
|
|
{
|
|
if (enabled & bitmask)
|
|
g_ptr_array_add (args, g_strdup_printf ("%s=%s", enable_arg, names[i]));
|
|
else
|
|
g_ptr_array_add (args, g_strdup_printf ("%s=%s", disable_arg, names[i]));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static FlatpakContextShares
|
|
flatpak_context_share_from_string (const char *string, GError **error)
|
|
{
|
|
FlatpakContextShares shares = flatpak_context_bitmask_from_string (string, flatpak_context_shares);
|
|
|
|
if (shares == 0)
|
|
{
|
|
g_autofree char *values = g_strjoinv (", ", (char **)flatpak_context_shares);
|
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
|
|
_("Unknown share type %s, valid types are: %s"), string, values);
|
|
}
|
|
|
|
return shares;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_shared_to_args (FlatpakContextShares shares,
|
|
FlatpakContextShares valid,
|
|
GPtrArray *args)
|
|
{
|
|
return flatpak_context_bitmask_to_args (shares, valid, flatpak_context_shares, "--share", "--unshare", args);
|
|
}
|
|
|
|
static const char *
|
|
flatpak_policy_to_string (FlatpakPolicy policy)
|
|
{
|
|
if (policy == FLATPAK_POLICY_SEE)
|
|
return "see";
|
|
if (policy == FLATPAK_POLICY_TALK)
|
|
return "talk";
|
|
if (policy == FLATPAK_POLICY_OWN)
|
|
return "own";
|
|
|
|
return "none";
|
|
}
|
|
|
|
static gboolean
|
|
flatpak_verify_dbus_name (const char *name, GError **error)
|
|
{
|
|
const char *name_part;
|
|
g_autofree char *tmp = NULL;
|
|
|
|
if (g_str_has_suffix (name, ".*"))
|
|
{
|
|
tmp = g_strndup (name, strlen (name) - 2);
|
|
name_part = tmp;
|
|
}
|
|
else
|
|
{
|
|
name_part = name;
|
|
}
|
|
|
|
if (g_dbus_is_name (name_part) && !g_dbus_is_unique_name (name_part))
|
|
return TRUE;
|
|
|
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
|
|
_("Invalid dbus name %s\n"), name);
|
|
return FALSE;
|
|
}
|
|
|
|
static FlatpakContextSockets
|
|
flatpak_context_socket_from_string (const char *string, GError **error)
|
|
{
|
|
FlatpakContextSockets sockets = flatpak_context_bitmask_from_string (string, flatpak_context_sockets);
|
|
|
|
if (sockets == 0)
|
|
{
|
|
g_autofree char *values = g_strjoinv (", ", (char **)flatpak_context_sockets);
|
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
|
|
_("Unknown socket type %s, valid types are: %s"), string, values);
|
|
}
|
|
|
|
return sockets;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_sockets_to_args (FlatpakContextSockets sockets,
|
|
FlatpakContextSockets valid,
|
|
GPtrArray *args)
|
|
{
|
|
return flatpak_context_bitmask_to_args (sockets, valid, flatpak_context_sockets, "--socket", "--nosocket", args);
|
|
}
|
|
|
|
static FlatpakContextDevices
|
|
flatpak_context_device_from_string (const char *string, GError **error)
|
|
{
|
|
FlatpakContextDevices devices = flatpak_context_bitmask_from_string (string, flatpak_context_devices);
|
|
|
|
if (devices == 0)
|
|
{
|
|
g_autofree char *values = g_strjoinv (", ", (char **)flatpak_context_devices);
|
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
|
|
_("Unknown device type %s, valid types are: %s"), string, values);
|
|
}
|
|
return devices;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_devices_to_args (FlatpakContextDevices devices,
|
|
FlatpakContextDevices valid,
|
|
GPtrArray *args)
|
|
{
|
|
return flatpak_context_bitmask_to_args (devices, valid, flatpak_context_devices, "--device", "--nodevice", args);
|
|
}
|
|
|
|
static FlatpakContextFeatures
|
|
flatpak_context_feature_from_string (const char *string, GError **error)
|
|
{
|
|
FlatpakContextFeatures feature = flatpak_context_bitmask_from_string (string, flatpak_context_features);
|
|
|
|
if (feature == 0)
|
|
{
|
|
g_autofree char *values = g_strjoinv (", ", (char **)flatpak_context_features);
|
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
|
|
_("Unknown feature type %s, valid types are: %s"), string, values);
|
|
}
|
|
|
|
return feature;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_features_to_args (FlatpakContextFeatures features,
|
|
FlatpakContextFeatures valid,
|
|
GPtrArray *args)
|
|
{
|
|
return flatpak_context_bitmask_to_args (features, valid, flatpak_context_features, "--allow", "--disallow", args);
|
|
}
|
|
|
|
static void
|
|
flatpak_context_add_shares (FlatpakContext *context,
|
|
FlatpakContextShares shares)
|
|
{
|
|
context->shares_valid |= shares;
|
|
context->shares |= shares;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_remove_shares (FlatpakContext *context,
|
|
FlatpakContextShares shares)
|
|
{
|
|
context->shares_valid |= shares;
|
|
context->shares &= ~shares;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_add_sockets (FlatpakContext *context,
|
|
FlatpakContextSockets sockets)
|
|
{
|
|
context->sockets_valid |= sockets;
|
|
context->sockets |= sockets;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_remove_sockets (FlatpakContext *context,
|
|
FlatpakContextSockets sockets)
|
|
{
|
|
context->sockets_valid |= sockets;
|
|
context->sockets &= ~sockets;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_add_devices (FlatpakContext *context,
|
|
FlatpakContextDevices devices)
|
|
{
|
|
context->devices_valid |= devices;
|
|
context->devices |= devices;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_remove_devices (FlatpakContext *context,
|
|
FlatpakContextDevices devices)
|
|
{
|
|
context->devices_valid |= devices;
|
|
context->devices &= ~devices;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_add_features (FlatpakContext *context,
|
|
FlatpakContextFeatures features)
|
|
{
|
|
context->features_valid |= features;
|
|
context->features |= features;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_remove_features (FlatpakContext *context,
|
|
FlatpakContextFeatures features)
|
|
{
|
|
context->features_valid |= features;
|
|
context->features &= ~features;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_set_env_var (FlatpakContext *context,
|
|
const char *name,
|
|
const char *value)
|
|
{
|
|
g_hash_table_insert (context->env_vars, g_strdup (name), g_strdup (value));
|
|
}
|
|
|
|
static void
|
|
flatpak_context_set_session_bus_policy (FlatpakContext *context,
|
|
const char *name,
|
|
FlatpakPolicy policy)
|
|
{
|
|
g_hash_table_insert (context->session_bus_policy, g_strdup (name), GINT_TO_POINTER (policy));
|
|
}
|
|
|
|
static void
|
|
flatpak_context_set_system_bus_policy (FlatpakContext *context,
|
|
const char *name,
|
|
FlatpakPolicy policy)
|
|
{
|
|
g_hash_table_insert (context->system_bus_policy, g_strdup (name), GINT_TO_POINTER (policy));
|
|
}
|
|
|
|
static void
|
|
flatpak_context_apply_generic_policy (FlatpakContext *context,
|
|
const char *key,
|
|
const char *value)
|
|
{
|
|
GPtrArray *new = g_ptr_array_new ();
|
|
const char **old_v;
|
|
int i;
|
|
|
|
g_assert (strchr (key, '.') != NULL);
|
|
|
|
old_v = g_hash_table_lookup (context->generic_policy, key);
|
|
for (i = 0; old_v != NULL && old_v[i] != NULL; i++)
|
|
{
|
|
const char *old = old_v[i];
|
|
const char *cmp1 = old;
|
|
const char *cmp2 = value;
|
|
if (*cmp1 == '!')
|
|
cmp1++;
|
|
if (*cmp2 == '!')
|
|
cmp2++;
|
|
if (strcmp (cmp1, cmp2) != 0)
|
|
g_ptr_array_add (new, g_strdup (old));
|
|
}
|
|
|
|
g_ptr_array_add (new, g_strdup (value));
|
|
g_ptr_array_add (new, NULL);
|
|
|
|
g_hash_table_insert (context->generic_policy, g_strdup (key),
|
|
g_ptr_array_free (new, FALSE));
|
|
}
|
|
|
|
static void
|
|
flatpak_context_set_persistent (FlatpakContext *context,
|
|
const char *path)
|
|
{
|
|
g_hash_table_insert (context->persistent, g_strdup (path), GINT_TO_POINTER (1));
|
|
}
|
|
|
|
static gboolean
|
|
get_xdg_dir_from_prefix (const char *prefix,
|
|
const char **where,
|
|
const char **dir)
|
|
{
|
|
if (strcmp (prefix, "xdg-data") == 0)
|
|
{
|
|
if (where)
|
|
*where = "data";
|
|
if (dir)
|
|
*dir = g_get_user_data_dir ();
|
|
return TRUE;
|
|
}
|
|
if (strcmp (prefix, "xdg-cache") == 0)
|
|
{
|
|
if (where)
|
|
*where = "cache";
|
|
if (dir)
|
|
*dir = g_get_user_cache_dir ();
|
|
return TRUE;
|
|
}
|
|
if (strcmp (prefix, "xdg-config") == 0)
|
|
{
|
|
if (where)
|
|
*where = "config";
|
|
if (dir)
|
|
*dir = g_get_user_config_dir ();
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
get_xdg_user_dir_from_string (const char *filesystem,
|
|
const char **config_key,
|
|
const char **suffix,
|
|
const char **dir)
|
|
{
|
|
char *slash;
|
|
const char *rest;
|
|
g_autofree char *prefix = NULL;
|
|
gsize len;
|
|
|
|
slash = strchr (filesystem, '/');
|
|
|
|
if (slash)
|
|
len = slash - filesystem;
|
|
else
|
|
len = strlen (filesystem);
|
|
|
|
rest = filesystem + len;
|
|
while (*rest == '/')
|
|
rest++;
|
|
|
|
if (suffix)
|
|
*suffix = rest;
|
|
|
|
prefix = g_strndup (filesystem, len);
|
|
|
|
if (strcmp (prefix, "xdg-desktop") == 0)
|
|
{
|
|
if (config_key)
|
|
*config_key = "XDG_DESKTOP_DIR";
|
|
if (dir)
|
|
*dir = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
|
|
return TRUE;
|
|
}
|
|
if (strcmp (prefix, "xdg-documents") == 0)
|
|
{
|
|
if (config_key)
|
|
*config_key = "XDG_DOCUMENTS_DIR";
|
|
if (dir)
|
|
*dir = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
|
|
return TRUE;
|
|
}
|
|
if (strcmp (prefix, "xdg-download") == 0)
|
|
{
|
|
if (config_key)
|
|
*config_key = "XDG_DOWNLOAD_DIR";
|
|
if (dir)
|
|
*dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD);
|
|
return TRUE;
|
|
}
|
|
if (strcmp (prefix, "xdg-music") == 0)
|
|
{
|
|
if (config_key)
|
|
*config_key = "XDG_MUSIC_DIR";
|
|
if (dir)
|
|
*dir = g_get_user_special_dir (G_USER_DIRECTORY_MUSIC);
|
|
return TRUE;
|
|
}
|
|
if (strcmp (prefix, "xdg-pictures") == 0)
|
|
{
|
|
if (config_key)
|
|
*config_key = "XDG_PICTURES_DIR";
|
|
if (dir)
|
|
*dir = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
|
|
return TRUE;
|
|
}
|
|
if (strcmp (prefix, "xdg-public-share") == 0)
|
|
{
|
|
if (config_key)
|
|
*config_key = "XDG_PUBLICSHARE_DIR";
|
|
if (dir)
|
|
*dir = g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE);
|
|
return TRUE;
|
|
}
|
|
if (strcmp (prefix, "xdg-templates") == 0)
|
|
{
|
|
if (config_key)
|
|
*config_key = "XDG_TEMPLATES_DIR";
|
|
if (dir)
|
|
*dir = g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES);
|
|
return TRUE;
|
|
}
|
|
if (strcmp (prefix, "xdg-videos") == 0)
|
|
{
|
|
if (config_key)
|
|
*config_key = "XDG_VIDEOS_DIR";
|
|
if (dir)
|
|
*dir = g_get_user_special_dir (G_USER_DIRECTORY_VIDEOS);
|
|
return TRUE;
|
|
}
|
|
if (get_xdg_dir_from_prefix (prefix, NULL, dir))
|
|
{
|
|
if (config_key)
|
|
*config_key = NULL;
|
|
return TRUE;
|
|
}
|
|
/* Don't support xdg-run without suffix, because that doesn't work */
|
|
if (strcmp (prefix, "xdg-run") == 0 &&
|
|
*rest != 0)
|
|
{
|
|
if (config_key)
|
|
*config_key = NULL;
|
|
if (dir)
|
|
*dir = g_get_user_runtime_dir ();
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static char *
|
|
parse_filesystem_flags (const char *filesystem, FlatpakFilesystemMode *mode)
|
|
{
|
|
gsize len = strlen (filesystem);
|
|
|
|
if (mode)
|
|
*mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE;
|
|
|
|
if (g_str_has_suffix (filesystem, ":ro"))
|
|
{
|
|
len -= 3;
|
|
if (mode)
|
|
*mode = FLATPAK_FILESYSTEM_MODE_READ_ONLY;
|
|
}
|
|
else if (g_str_has_suffix (filesystem, ":rw"))
|
|
{
|
|
len -= 3;
|
|
if (mode)
|
|
*mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE;
|
|
}
|
|
else if (g_str_has_suffix (filesystem, ":create"))
|
|
{
|
|
len -= 7;
|
|
if (mode)
|
|
*mode = FLATPAK_FILESYSTEM_MODE_CREATE;
|
|
}
|
|
|
|
return g_strndup (filesystem, len);
|
|
}
|
|
|
|
static gboolean
|
|
flatpak_context_verify_filesystem (const char *filesystem_and_mode,
|
|
GError **error)
|
|
{
|
|
g_autofree char *filesystem = parse_filesystem_flags (filesystem_and_mode, NULL);
|
|
|
|
if (strcmp (filesystem, "host") == 0)
|
|
return TRUE;
|
|
if (strcmp (filesystem, "home") == 0)
|
|
return TRUE;
|
|
if (get_xdg_user_dir_from_string (filesystem, NULL, NULL, NULL))
|
|
return TRUE;
|
|
if (g_str_has_prefix (filesystem, "~/"))
|
|
return TRUE;
|
|
if (g_str_has_prefix (filesystem, "/"))
|
|
return TRUE;
|
|
|
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
|
|
_("Unknown filesystem location %s, valid locations are: host, home, xdg-*[/...], ~/dir, /dir"), filesystem);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
flatpak_context_add_filesystem (FlatpakContext *context,
|
|
const char *what)
|
|
{
|
|
FlatpakFilesystemMode mode;
|
|
char *fs = parse_filesystem_flags (what, &mode);
|
|
|
|
g_hash_table_insert (context->filesystems, fs, GINT_TO_POINTER (mode));
|
|
}
|
|
|
|
static void
|
|
flatpak_context_remove_filesystem (FlatpakContext *context,
|
|
const char *what)
|
|
{
|
|
g_hash_table_insert (context->filesystems,
|
|
parse_filesystem_flags (what, NULL),
|
|
NULL);
|
|
}
|
|
|
|
static gboolean
|
|
option_share_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
FlatpakContextShares share;
|
|
|
|
share = flatpak_context_share_from_string (value, error);
|
|
if (share == 0)
|
|
return FALSE;
|
|
|
|
flatpak_context_add_shares (context, share);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_unshare_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
FlatpakContextShares share;
|
|
|
|
share = flatpak_context_share_from_string (value, error);
|
|
if (share == 0)
|
|
return FALSE;
|
|
|
|
flatpak_context_remove_shares (context, share);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_socket_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
FlatpakContextSockets socket;
|
|
|
|
socket = flatpak_context_socket_from_string (value, error);
|
|
if (socket == 0)
|
|
return FALSE;
|
|
|
|
if (socket == FLATPAK_CONTEXT_SOCKET_FALLBACK_X11)
|
|
socket |= FLATPAK_CONTEXT_SOCKET_X11;
|
|
|
|
flatpak_context_add_sockets (context, socket);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_nosocket_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
FlatpakContextSockets socket;
|
|
|
|
socket = flatpak_context_socket_from_string (value, error);
|
|
if (socket == 0)
|
|
return FALSE;
|
|
|
|
if (socket == FLATPAK_CONTEXT_SOCKET_FALLBACK_X11)
|
|
socket |= FLATPAK_CONTEXT_SOCKET_X11;
|
|
|
|
flatpak_context_remove_sockets (context, socket);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_device_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
FlatpakContextDevices device;
|
|
|
|
device = flatpak_context_device_from_string (value, error);
|
|
if (device == 0)
|
|
return FALSE;
|
|
|
|
flatpak_context_add_devices (context, device);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_nodevice_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
FlatpakContextDevices device;
|
|
|
|
device = flatpak_context_device_from_string (value, error);
|
|
if (device == 0)
|
|
return FALSE;
|
|
|
|
flatpak_context_remove_devices (context, device);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_allow_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
FlatpakContextFeatures feature;
|
|
|
|
feature = flatpak_context_feature_from_string (value, error);
|
|
if (feature == 0)
|
|
return FALSE;
|
|
|
|
flatpak_context_add_features (context, feature);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_disallow_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
FlatpakContextFeatures feature;
|
|
|
|
feature = flatpak_context_feature_from_string (value, error);
|
|
if (feature == 0)
|
|
return FALSE;
|
|
|
|
flatpak_context_remove_features (context, feature);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_filesystem_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
|
|
if (!flatpak_context_verify_filesystem (value, error))
|
|
return FALSE;
|
|
|
|
flatpak_context_add_filesystem (context, value);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_nofilesystem_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
|
|
if (!flatpak_context_verify_filesystem (value, error))
|
|
return FALSE;
|
|
|
|
flatpak_context_remove_filesystem (context, value);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_env_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
|
|
g_auto(GStrv) split = g_strsplit (value, "=", 2);
|
|
|
|
if (split == NULL || split[0] == NULL || split[0][0] == 0 || split[1] == NULL)
|
|
{
|
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
|
|
_("Invalid env format %s"), value);
|
|
return FALSE;
|
|
}
|
|
|
|
flatpak_context_set_env_var (context, split[0], split[1]);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_own_name_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
|
|
if (!flatpak_verify_dbus_name (value, error))
|
|
return FALSE;
|
|
|
|
flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_OWN);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_talk_name_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
|
|
if (!flatpak_verify_dbus_name (value, error))
|
|
return FALSE;
|
|
|
|
flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_TALK);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_system_own_name_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
|
|
if (!flatpak_verify_dbus_name (value, error))
|
|
return FALSE;
|
|
|
|
flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_OWN);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_system_talk_name_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
|
|
if (!flatpak_verify_dbus_name (value, error))
|
|
return FALSE;
|
|
|
|
flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_TALK);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_add_generic_policy_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
char *t;
|
|
g_autofree char *key = NULL;
|
|
const char *policy_value;
|
|
|
|
t = strchr (value, '=');
|
|
if (t == NULL)
|
|
return flatpak_fail (error, "--policy arguments must be in the form SUBSYSTEM.KEY=[!]VALUE");
|
|
policy_value = t + 1;
|
|
key = g_strndup (value, t - value);
|
|
if (strchr (key, '.') == NULL)
|
|
return flatpak_fail (error, "--policy arguments must be in the form SUBSYSTEM.KEY=[!]VALUE");
|
|
|
|
if (policy_value[0] == '!')
|
|
return flatpak_fail (error, "--policy values can't start with \"!\"");
|
|
|
|
flatpak_context_apply_generic_policy (context, key, policy_value);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_remove_generic_policy_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
char *t;
|
|
g_autofree char *key = NULL;
|
|
const char *policy_value;
|
|
g_autofree char *extended_value = NULL;
|
|
|
|
t = strchr (value, '=');
|
|
if (t == NULL)
|
|
return flatpak_fail (error, "--policy arguments must be in the form SUBSYSTEM.KEY=[!]VALUE");
|
|
policy_value = t + 1;
|
|
key = g_strndup (value, t - value);
|
|
if (strchr (key, '.') == NULL)
|
|
return flatpak_fail (error, "--policy arguments must be in the form SUBSYSTEM.KEY=[!]VALUE");
|
|
|
|
if (policy_value[0] == '!')
|
|
return flatpak_fail (error, "--policy values can't start with \"!\"");
|
|
|
|
extended_value = g_strconcat ("!", policy_value, NULL);
|
|
|
|
flatpak_context_apply_generic_policy (context, key, extended_value);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
option_persist_cb (const gchar *option_name,
|
|
const gchar *value,
|
|
gpointer data,
|
|
GError **error)
|
|
{
|
|
FlatpakContext *context = data;
|
|
|
|
flatpak_context_set_persistent (context, value);
|
|
return TRUE;
|
|
}
|
|
|
|
static GOptionEntry context_options[] = {
|
|
{ "share", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_share_cb, N_("Share with host"), N_("SHARE") },
|
|
{ "unshare", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_unshare_cb, N_("Unshare with host"), N_("SHARE") },
|
|
{ "socket", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_socket_cb, N_("Expose socket to app"), N_("SOCKET") },
|
|
{ "nosocket", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nosocket_cb, N_("Don't expose socket to app"), N_("SOCKET") },
|
|
{ "device", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_device_cb, N_("Expose device to app"), N_("DEVICE") },
|
|
{ "nodevice", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nodevice_cb, N_("Don't expose device to app"), N_("DEVICE") },
|
|
{ "allow", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_allow_cb, N_("Allow feature"), N_("FEATURE") },
|
|
{ "disallow", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_disallow_cb, N_("Don't allow feature"), N_("FEATURE") },
|
|
{ "filesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_filesystem_cb, N_("Expose filesystem to app (:ro for read-only)"), N_("FILESYSTEM[:ro]") },
|
|
{ "nofilesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nofilesystem_cb, N_("Don't expose filesystem to app"), N_("FILESYSTEM") },
|
|
{ "env", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_env_cb, N_("Set environment variable"), N_("VAR=VALUE") },
|
|
{ "own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_own_name_cb, N_("Allow app to own name on the session bus"), N_("DBUS_NAME") },
|
|
{ "talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_talk_name_cb, N_("Allow app to talk to name on the session bus"), N_("DBUS_NAME") },
|
|
{ "system-own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_own_name_cb, N_("Allow app to own name on the system bus"), N_("DBUS_NAME") },
|
|
{ "system-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_talk_name_cb, N_("Allow app to talk to name on the system bus"), N_("DBUS_NAME") },
|
|
{ "add-policy", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_add_generic_policy_cb, N_("Add generic policy option"), N_("SUBSYSTEM.KEY=VALUE") },
|
|
{ "remove-policy", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_remove_generic_policy_cb, N_("Remove generic policy option"), N_("SUBSYSTEM.KEY=VALUE") },
|
|
{ "persist", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_persist_cb, N_("Persist home directory"), N_("FILENAME") },
|
|
{ NULL }
|
|
};
|
|
|
|
GOptionGroup *
|
|
flatpak_context_get_options (FlatpakContext *context)
|
|
{
|
|
GOptionGroup *group;
|
|
|
|
group = g_option_group_new ("environment",
|
|
"Runtime Environment",
|
|
"Runtime Environment",
|
|
context,
|
|
NULL);
|
|
g_option_group_set_translation_domain (group, GETTEXT_PACKAGE);
|
|
|
|
g_option_group_add_entries (group, context_options);
|
|
|
|
return group;
|
|
}
|
|
|
|
void
|
|
flatpak_context_to_args (FlatpakContext *context,
|
|
GPtrArray *args)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
flatpak_context_shared_to_args (context->shares, context->shares_valid, args);
|
|
flatpak_context_sockets_to_args (context->sockets, context->sockets_valid, args);
|
|
flatpak_context_devices_to_args (context->devices, context->devices_valid, args);
|
|
flatpak_context_features_to_args (context->features, context->features_valid, args);
|
|
|
|
g_hash_table_iter_init (&iter, context->env_vars);
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
g_ptr_array_add (args, g_strdup_printf ("--env=%s=%s", (char *)key, (char *)value));
|
|
|
|
g_hash_table_iter_init (&iter, context->persistent);
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
g_ptr_array_add (args, g_strdup_printf ("--persist=%s", (char *)key));
|
|
|
|
g_hash_table_iter_init (&iter, context->session_bus_policy);
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
{
|
|
const char *name = key;
|
|
FlatpakPolicy policy = GPOINTER_TO_INT (value);
|
|
|
|
g_ptr_array_add (args, g_strdup_printf ("--%s-name=%s", flatpak_policy_to_string (policy), name));
|
|
}
|
|
|
|
g_hash_table_iter_init (&iter, context->system_bus_policy);
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
{
|
|
const char *name = key;
|
|
FlatpakPolicy policy = GPOINTER_TO_INT (value);
|
|
|
|
g_ptr_array_add (args, g_strdup_printf ("--system-%s-name=%s", flatpak_policy_to_string (policy), name));
|
|
}
|
|
|
|
g_hash_table_iter_init (&iter, context->filesystems);
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
{
|
|
FlatpakFilesystemMode mode = GPOINTER_TO_INT (value);
|
|
|
|
if (mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY)
|
|
g_ptr_array_add (args, g_strdup_printf ("--filesystem=%s:ro", (char *)key));
|
|
else if (mode == FLATPAK_FILESYSTEM_MODE_READ_WRITE)
|
|
g_ptr_array_add (args, g_strdup_printf ("--filesystem=%s", (char *)key));
|
|
else if (mode == FLATPAK_FILESYSTEM_MODE_CREATE)
|
|
g_ptr_array_add (args, g_strdup_printf ("--filesystem=%s:create", (char *)key));
|
|
else
|
|
g_ptr_array_add (args, g_strdup_printf ("--nofilesystem=%s", (char *)key));
|
|
}
|
|
}
|