mirror of
https://github.com/flatpak/flatpak.git
synced 2026-04-28 02:58:03 -04:00
This lets distributors share a system copy of bubblewrap (>= 0.1.0) between Flatpak and any other projects that benefit from it, if they are careful to keep new versions in sync. The default is still to use the bundled submodule, ensuring compatibility and simplifying dependencies. Enable $PATH search everywhere that runs bwrap, so that $BWRAP doesn't necessarily need to be a fully-qualified path. Signed-off-by: Simon McVittie <smcv@debian.org>
5287 lines
160 KiB
C
5287 lines
160 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 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 <string.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <sys/file.h>
|
|
#include <sys/types.h>
|
|
#include <utime.h>
|
|
|
|
#include <gio/gio.h>
|
|
#include "libgsystem.h"
|
|
#include "libglnx/libglnx.h"
|
|
#include "lib/flatpak-error.h"
|
|
|
|
#include "flatpak-dir.h"
|
|
#include "flatpak-utils.h"
|
|
#include "flatpak-run.h"
|
|
|
|
#include "errno.h"
|
|
|
|
#define NO_SYSTEM_HELPER ((FlatpakSystemHelper *) (gpointer) 1)
|
|
|
|
#define SUMMARY_CACHE_TIMEOUT_SEC 5*60
|
|
|
|
static OstreeRepo * flatpak_dir_create_system_child_repo (FlatpakDir *self,
|
|
GLnxLockFile *file_lock,
|
|
GError **error);
|
|
|
|
|
|
typedef struct
|
|
{
|
|
GBytes *bytes;
|
|
char *remote;
|
|
char *url;
|
|
guint64 time;
|
|
} CachedSummary;
|
|
|
|
struct FlatpakDir
|
|
{
|
|
GObject parent;
|
|
|
|
gboolean user;
|
|
GFile *basedir;
|
|
OstreeRepo *repo;
|
|
gboolean no_system_helper;
|
|
|
|
FlatpakSystemHelper *system_helper;
|
|
|
|
GHashTable *summary_cache;
|
|
|
|
SoupSession *soup_session;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
GObjectClass parent_class;
|
|
} FlatpakDirClass;
|
|
|
|
struct FlatpakDeploy
|
|
{
|
|
GObject parent;
|
|
|
|
GFile *dir;
|
|
GKeyFile *metadata;
|
|
FlatpakContext *system_overrides;
|
|
FlatpakContext *user_overrides;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
GObjectClass parent_class;
|
|
} FlatpakDeployClass;
|
|
|
|
G_DEFINE_TYPE (FlatpakDir, flatpak_dir, G_TYPE_OBJECT)
|
|
G_DEFINE_TYPE (FlatpakDeploy, flatpak_deploy, G_TYPE_OBJECT)
|
|
|
|
enum {
|
|
PROP_0,
|
|
|
|
PROP_USER,
|
|
PROP_PATH
|
|
};
|
|
|
|
#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")
|
|
|
|
static GVariant *
|
|
variant_new_ay_bytes (GBytes *bytes)
|
|
{
|
|
gsize size;
|
|
gconstpointer data;
|
|
data = g_bytes_get_data (bytes, &size);
|
|
g_bytes_ref (bytes);
|
|
return g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("ay"), data, size,
|
|
TRUE, (GDestroyNotify)g_bytes_unref, bytes));
|
|
}
|
|
|
|
static void
|
|
flatpak_deploy_finalize (GObject *object)
|
|
{
|
|
FlatpakDeploy *self = FLATPAK_DEPLOY (object);
|
|
|
|
g_clear_object (&self->dir);
|
|
g_clear_pointer (&self->metadata, g_key_file_unref);
|
|
g_clear_pointer (&self->system_overrides, g_key_file_unref);
|
|
g_clear_pointer (&self->user_overrides, g_key_file_unref);
|
|
|
|
G_OBJECT_CLASS (flatpak_deploy_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
flatpak_deploy_class_init (FlatpakDeployClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = flatpak_deploy_finalize;
|
|
|
|
}
|
|
|
|
static void
|
|
flatpak_deploy_init (FlatpakDeploy *self)
|
|
{
|
|
}
|
|
|
|
GFile *
|
|
flatpak_deploy_get_dir (FlatpakDeploy *deploy)
|
|
{
|
|
return g_object_ref (deploy->dir);
|
|
}
|
|
|
|
GFile *
|
|
flatpak_deploy_get_files (FlatpakDeploy *deploy)
|
|
{
|
|
return g_file_get_child (deploy->dir, "files");
|
|
}
|
|
|
|
FlatpakContext *
|
|
flatpak_deploy_get_overrides (FlatpakDeploy *deploy)
|
|
{
|
|
FlatpakContext *overrides = flatpak_context_new ();
|
|
|
|
if (deploy->system_overrides)
|
|
flatpak_context_merge (overrides, deploy->system_overrides);
|
|
|
|
if (deploy->user_overrides)
|
|
flatpak_context_merge (overrides, deploy->user_overrides);
|
|
|
|
return overrides;
|
|
}
|
|
|
|
GKeyFile *
|
|
flatpak_deploy_get_metadata (FlatpakDeploy *deploy)
|
|
{
|
|
return g_key_file_ref (deploy->metadata);
|
|
}
|
|
|
|
static FlatpakDeploy *
|
|
flatpak_deploy_new (GFile *dir, GKeyFile *metadata)
|
|
{
|
|
FlatpakDeploy *deploy;
|
|
|
|
deploy = g_object_new (FLATPAK_TYPE_DEPLOY, NULL);
|
|
deploy->dir = g_object_ref (dir);
|
|
deploy->metadata = g_key_file_ref (metadata);
|
|
|
|
return deploy;
|
|
}
|
|
|
|
GFile *
|
|
flatpak_get_system_base_dir_location (void)
|
|
{
|
|
static gsize path = 0;
|
|
|
|
if (g_once_init_enter (&path))
|
|
{
|
|
gsize setup_value = 0;
|
|
const char *system_dir = g_getenv ("FLATPAK_SYSTEM_DIR");
|
|
if (system_dir != NULL)
|
|
setup_value = (gsize)system_dir;
|
|
else
|
|
setup_value = (gsize)FLATPAK_SYSTEMDIR;
|
|
g_once_init_leave (&path, setup_value);
|
|
}
|
|
|
|
return g_file_new_for_path ((char *)path);
|
|
}
|
|
|
|
GFile *
|
|
flatpak_get_user_base_dir_location (void)
|
|
{
|
|
g_autofree char *base = g_build_filename (g_get_user_data_dir (), "flatpak", NULL);
|
|
|
|
return g_file_new_for_path (base);
|
|
}
|
|
|
|
GFile *
|
|
flatpak_get_user_cache_dir_location (void)
|
|
{
|
|
g_autoptr(GFile) base_dir = NULL;
|
|
|
|
base_dir = flatpak_get_user_base_dir_location ();
|
|
return g_file_get_child (base_dir, "system-cache");
|
|
}
|
|
|
|
GFile *
|
|
flatpak_ensure_user_cache_dir_location (GError **error)
|
|
{
|
|
g_autoptr(GFile) cache_dir = NULL;
|
|
g_autofree char *cache_path = NULL;
|
|
|
|
cache_dir = flatpak_get_user_cache_dir_location ();
|
|
cache_path = g_file_get_path (cache_dir);
|
|
|
|
if (g_mkdir_with_parents (cache_path, 0755) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer (&cache_dir);
|
|
}
|
|
|
|
static FlatpakSystemHelper *
|
|
flatpak_dir_get_system_helper (FlatpakDir *self)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
if (g_once_init_enter (&self->system_helper))
|
|
{
|
|
FlatpakSystemHelper *system_helper;
|
|
const char *on_session = g_getenv ("FLATPAK_SYSTEM_HELPER_ON_SESSION");
|
|
|
|
/* To ensure reverse mapping */
|
|
flatpak_error_quark ();
|
|
|
|
system_helper =
|
|
flatpak_system_helper_proxy_new_for_bus_sync (on_session != NULL ? G_BUS_TYPE_SESSION : G_BUS_TYPE_SYSTEM,
|
|
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
|
|
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
|
|
"org.freedesktop.Flatpak.SystemHelper",
|
|
"/org/freedesktop/Flatpak/SystemHelper",
|
|
NULL, &error);
|
|
if (error != NULL)
|
|
{
|
|
g_warning ("Can't find org.freedesktop.Flatpak.SystemHelper: %s\n", error->message);
|
|
system_helper = NO_SYSTEM_HELPER;
|
|
}
|
|
g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (system_helper), G_MAXINT);
|
|
g_once_init_leave (&self->system_helper, system_helper);
|
|
}
|
|
|
|
if (self->system_helper != NO_SYSTEM_HELPER)
|
|
return self->system_helper;
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_use_system_helper (FlatpakDir *self)
|
|
{
|
|
FlatpakSystemHelper *system_helper;
|
|
|
|
if (self->no_system_helper || self->user || getuid () == 0)
|
|
return FALSE;
|
|
|
|
system_helper = flatpak_dir_get_system_helper (self);
|
|
|
|
return system_helper != NULL;
|
|
}
|
|
|
|
static OstreeRepo *
|
|
system_ostree_repo_new (GFile *repodir)
|
|
{
|
|
return g_object_new (OSTREE_TYPE_REPO, "path", repodir,
|
|
"remotes-config-dir", FLATPAK_CONFIGDIR "/remotes.d",
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
flatpak_dir_finalize (GObject *object)
|
|
{
|
|
FlatpakDir *self = FLATPAK_DIR (object);
|
|
|
|
g_clear_object (&self->repo);
|
|
g_clear_object (&self->basedir);
|
|
|
|
g_clear_object (&self->system_helper);
|
|
|
|
g_clear_object (&self->soup_session);
|
|
g_clear_pointer (&self->summary_cache, g_hash_table_unref);
|
|
|
|
G_OBJECT_CLASS (flatpak_dir_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
flatpak_dir_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
FlatpakDir *self = FLATPAK_DIR (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_PATH:
|
|
/* Canonicalize */
|
|
self->basedir = g_file_new_for_path (gs_file_get_path_cached (g_value_get_object (value)));
|
|
break;
|
|
|
|
case PROP_USER:
|
|
self->user = g_value_get_boolean (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
flatpak_dir_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
FlatpakDir *self = FLATPAK_DIR (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_PATH:
|
|
g_value_set_object (value, self->basedir);
|
|
break;
|
|
|
|
case PROP_USER:
|
|
g_value_set_boolean (value, self->user);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
flatpak_dir_class_init (FlatpakDirClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->get_property = flatpak_dir_get_property;
|
|
object_class->set_property = flatpak_dir_set_property;
|
|
object_class->finalize = flatpak_dir_finalize;
|
|
|
|
g_object_class_install_property (object_class,
|
|
PROP_USER,
|
|
g_param_spec_boolean ("user",
|
|
"",
|
|
"",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
g_object_class_install_property (object_class,
|
|
PROP_PATH,
|
|
g_param_spec_object ("path",
|
|
"",
|
|
"",
|
|
G_TYPE_FILE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
}
|
|
|
|
static void
|
|
flatpak_dir_init (FlatpakDir *self)
|
|
{
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_is_user (FlatpakDir *self)
|
|
{
|
|
return self->user;
|
|
}
|
|
|
|
void
|
|
flatpak_dir_set_no_system_helper (FlatpakDir *self,
|
|
gboolean no_system_helper)
|
|
{
|
|
self->no_system_helper = no_system_helper;
|
|
}
|
|
|
|
GFile *
|
|
flatpak_dir_get_path (FlatpakDir *self)
|
|
{
|
|
return self->basedir;
|
|
}
|
|
|
|
GFile *
|
|
flatpak_dir_get_changed_path (FlatpakDir *self)
|
|
{
|
|
return g_file_get_child (self->basedir, ".changed");
|
|
}
|
|
|
|
char *
|
|
flatpak_dir_load_override (FlatpakDir *self,
|
|
const char *app_id,
|
|
gsize *length,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) override_dir = NULL;
|
|
g_autoptr(GFile) file = NULL;
|
|
char *metadata_contents;
|
|
|
|
override_dir = g_file_get_child (self->basedir, "overrides");
|
|
file = g_file_get_child (override_dir, app_id);
|
|
|
|
if (!g_file_load_contents (file, NULL,
|
|
&metadata_contents, length, NULL, NULL))
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"No overrides found for %s", app_id);
|
|
return NULL;
|
|
}
|
|
|
|
return metadata_contents;
|
|
}
|
|
|
|
GKeyFile *
|
|
flatpak_load_override_keyfile (const char *app_id, gboolean user, GError **error)
|
|
{
|
|
g_autofree char *metadata_contents = NULL;
|
|
gsize metadata_size;
|
|
|
|
g_autoptr(GKeyFile) metakey = g_key_file_new ();
|
|
g_autoptr(FlatpakDir) dir = NULL;
|
|
|
|
dir = flatpak_dir_get (user);
|
|
|
|
metadata_contents = flatpak_dir_load_override (dir, app_id, &metadata_size, error);
|
|
if (metadata_contents == NULL)
|
|
return NULL;
|
|
|
|
if (!g_key_file_load_from_data (metakey,
|
|
metadata_contents,
|
|
metadata_size,
|
|
0, error))
|
|
return NULL;
|
|
|
|
return g_steal_pointer (&metakey);
|
|
}
|
|
|
|
FlatpakContext *
|
|
flatpak_load_override_file (const char *app_id, gboolean user, GError **error)
|
|
{
|
|
g_autoptr(FlatpakContext) overrides = flatpak_context_new ();
|
|
|
|
g_autoptr(GKeyFile) metakey = NULL;
|
|
g_autoptr(GError) my_error = NULL;
|
|
|
|
metakey = flatpak_load_override_keyfile (app_id, user, &my_error);
|
|
if (metakey == NULL)
|
|
{
|
|
if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&my_error));
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!flatpak_context_load_metadata (overrides, metakey, error))
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer (&overrides);
|
|
}
|
|
|
|
gboolean
|
|
flatpak_save_override_keyfile (GKeyFile *metakey,
|
|
const char *app_id,
|
|
gboolean user,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) base_dir = NULL;
|
|
g_autoptr(GFile) override_dir = NULL;
|
|
g_autoptr(GFile) file = NULL;
|
|
g_autofree char *filename = NULL;
|
|
g_autofree char *parent = NULL;
|
|
|
|
if (user)
|
|
base_dir = flatpak_get_user_base_dir_location ();
|
|
else
|
|
base_dir = flatpak_get_system_base_dir_location ();
|
|
|
|
override_dir = g_file_get_child (base_dir, "overrides");
|
|
file = g_file_get_child (override_dir, app_id);
|
|
|
|
filename = g_file_get_path (file);
|
|
parent = g_path_get_dirname (filename);
|
|
if (g_mkdir_with_parents (parent, 0755))
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
return g_key_file_save_to_file (metakey, filename, error);
|
|
}
|
|
|
|
FlatpakDeploy *
|
|
flatpak_dir_load_deployed (FlatpakDir *self,
|
|
const char *ref,
|
|
const char *checksum,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) deploy_dir = NULL;
|
|
g_autoptr(GKeyFile) metakey = NULL;
|
|
g_autoptr(GFile) metadata = NULL;
|
|
g_auto(GStrv) ref_parts = NULL;
|
|
g_autofree char *metadata_contents = NULL;
|
|
FlatpakDeploy *deploy;
|
|
gsize metadata_size;
|
|
|
|
deploy_dir = flatpak_dir_get_if_deployed (self, ref, checksum, cancellable);
|
|
if (deploy_dir == NULL)
|
|
{
|
|
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED, "%s not installed", ref);
|
|
return NULL;
|
|
}
|
|
|
|
metadata = g_file_get_child (deploy_dir, "metadata");
|
|
if (!g_file_load_contents (metadata, cancellable, &metadata_contents, &metadata_size, NULL, error))
|
|
return NULL;
|
|
|
|
metakey = g_key_file_new ();
|
|
if (!g_key_file_load_from_data (metakey, metadata_contents, metadata_size, 0, error))
|
|
return NULL;
|
|
|
|
deploy = flatpak_deploy_new (deploy_dir, metakey);
|
|
|
|
ref_parts = g_strsplit (ref, "/", -1);
|
|
g_assert (g_strv_length (ref_parts) == 4);
|
|
|
|
/* Only apps have overrides */
|
|
if (strcmp (ref_parts[0], "app") == 0)
|
|
{
|
|
/* Only load system overrides for system installed apps */
|
|
if (!self->user)
|
|
{
|
|
deploy->system_overrides = flatpak_load_override_file (ref_parts[1], FALSE, error);
|
|
if (deploy->system_overrides == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
/* Always load user overrides */
|
|
deploy->user_overrides = flatpak_load_override_file (ref_parts[1], TRUE, error);
|
|
if (deploy->user_overrides == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
return deploy;
|
|
}
|
|
|
|
GFile *
|
|
flatpak_dir_get_deploy_dir (FlatpakDir *self,
|
|
const char *ref)
|
|
{
|
|
return g_file_resolve_relative_path (self->basedir, ref);
|
|
}
|
|
|
|
GFile *
|
|
flatpak_dir_get_exports_dir (FlatpakDir *self)
|
|
{
|
|
return g_file_get_child (self->basedir, "exports");
|
|
}
|
|
|
|
GFile *
|
|
flatpak_dir_get_removed_dir (FlatpakDir *self)
|
|
{
|
|
return g_file_get_child (self->basedir, ".removed");
|
|
}
|
|
|
|
OstreeRepo *
|
|
flatpak_dir_get_repo (FlatpakDir *self)
|
|
{
|
|
return self->repo;
|
|
}
|
|
|
|
|
|
/* This is an exclusive per flatpak installation file lock that is taken
|
|
* whenever any config in the directory outside the repo is to be changed. For
|
|
* instance deployments, overrides or active commit changes.
|
|
*
|
|
* For concurrency protection of the actual repository we rely on ostree
|
|
* to do the right thing.
|
|
*/
|
|
gboolean
|
|
flatpak_dir_lock (FlatpakDir *self,
|
|
GLnxLockFile *lockfile,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) lock_file = g_file_get_child (flatpak_dir_get_path (self), "lock");
|
|
g_autofree char *lock_path = g_file_get_path (lock_file);
|
|
|
|
return glnx_make_lock_file (AT_FDCWD, lock_path, LOCK_EX, lockfile, error);
|
|
}
|
|
|
|
const char *
|
|
flatpak_deploy_data_get_origin (GVariant *deploy_data)
|
|
{
|
|
const char *origin;
|
|
|
|
g_variant_get_child (deploy_data, 0, "&s", &origin);
|
|
return origin;
|
|
}
|
|
|
|
const char *
|
|
flatpak_deploy_data_get_commit (GVariant *deploy_data)
|
|
{
|
|
const char *commit;
|
|
|
|
g_variant_get_child (deploy_data, 1, "&s", &commit);
|
|
return commit;
|
|
}
|
|
|
|
/**
|
|
* flatpak_deploy_data_get_subpaths:
|
|
*
|
|
* Returns: (array length=length zero-terminated=1) (transfer container): an array of constant strings
|
|
**/
|
|
const char **
|
|
flatpak_deploy_data_get_subpaths (GVariant *deploy_data)
|
|
{
|
|
const char **subpaths;
|
|
|
|
g_variant_get_child (deploy_data, 2, "^as", &subpaths);
|
|
return subpaths;
|
|
}
|
|
|
|
guint64
|
|
flatpak_deploy_data_get_installed_size (GVariant *deploy_data)
|
|
{
|
|
guint64 size;
|
|
|
|
g_variant_get_child (deploy_data, 3, "t", &size);
|
|
return GUINT64_FROM_BE (size);
|
|
}
|
|
|
|
static GVariant *
|
|
flatpak_dir_new_deploy_data (const char *origin,
|
|
const char *commit,
|
|
char **subpaths,
|
|
guint64 installed_size,
|
|
GVariant *metadata)
|
|
{
|
|
char *empty_subpaths[] = {NULL};
|
|
GVariantBuilder builder;
|
|
|
|
if (metadata == NULL)
|
|
{
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
|
|
metadata = g_variant_builder_end (&builder);
|
|
}
|
|
|
|
return g_variant_ref_sink (g_variant_new ("(ss^ast@a{sv})",
|
|
origin,
|
|
commit,
|
|
subpaths ? subpaths : empty_subpaths,
|
|
GUINT64_TO_BE (installed_size),
|
|
metadata));
|
|
}
|
|
|
|
static char **
|
|
get_old_subpaths (GFile *deploy_base,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) file = NULL;
|
|
g_autofree char *data = NULL;
|
|
g_autoptr(GError) my_error = NULL;
|
|
g_autoptr(GPtrArray) subpaths = NULL;
|
|
g_auto(GStrv) lines = NULL;
|
|
int i;
|
|
|
|
file = g_file_get_child (deploy_base, "subpaths");
|
|
if (!g_file_load_contents (file, cancellable, &data, NULL, NULL, &my_error))
|
|
{
|
|
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
{
|
|
data = g_strdup ("");
|
|
}
|
|
else
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&my_error));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
lines = g_strsplit (data, "\n", 0);
|
|
|
|
subpaths = g_ptr_array_new ();
|
|
for (i = 0; lines[i] != NULL; i++)
|
|
{
|
|
lines[i] = g_strstrip (lines[i]);
|
|
if (lines[i][0] == '/')
|
|
g_ptr_array_add (subpaths, g_strdup (lines[i]));
|
|
}
|
|
|
|
g_ptr_array_add (subpaths, NULL);
|
|
return (char **) g_ptr_array_free (subpaths, FALSE);
|
|
}
|
|
|
|
static GVariant *
|
|
flatpak_create_deploy_data_from_old (FlatpakDir *self,
|
|
GFile *deploy_dir,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) deploy_base = NULL;
|
|
g_autofree char *old_origin = NULL;
|
|
g_autofree char *commit = NULL;
|
|
g_auto(GStrv) old_subpaths = NULL;
|
|
g_autoptr(GFile) origin = NULL;
|
|
guint64 installed_size;
|
|
|
|
deploy_base = g_file_get_parent (deploy_dir);
|
|
commit = g_file_get_basename (deploy_dir);
|
|
|
|
origin = g_file_get_child (deploy_base, "origin");
|
|
if (!g_file_load_contents (origin, cancellable, &old_origin, NULL, NULL, error))
|
|
return NULL;
|
|
|
|
old_subpaths = get_old_subpaths (deploy_base, cancellable, error);
|
|
if (old_subpaths == NULL)
|
|
return NULL;
|
|
|
|
/* For backwards compat we return a 0 installed size, its to slow to regenerate */
|
|
installed_size = 0;
|
|
|
|
return flatpak_dir_new_deploy_data (old_origin, commit, old_subpaths,
|
|
installed_size, NULL);
|
|
}
|
|
|
|
GVariant *
|
|
flatpak_dir_get_deploy_data (FlatpakDir *self,
|
|
const char *ref,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) deploy_dir = NULL;
|
|
g_autoptr(GFile) data_file = NULL;
|
|
g_autoptr(GError) my_error = NULL;
|
|
char *data = NULL;
|
|
gsize data_size;
|
|
|
|
deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable);
|
|
if (deploy_dir == NULL)
|
|
{
|
|
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED, "%s not installed", ref);
|
|
return NULL;
|
|
}
|
|
|
|
data_file = g_file_get_child (deploy_dir, "deploy");
|
|
if (!g_file_load_contents (data_file, cancellable, &data, &data_size, NULL, &my_error))
|
|
{
|
|
if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&my_error));
|
|
return NULL;
|
|
}
|
|
|
|
return flatpak_create_deploy_data_from_old (self, deploy_dir,
|
|
cancellable, error);
|
|
}
|
|
|
|
return g_variant_ref_sink (g_variant_new_from_data (FLATPAK_DEPLOY_DATA_GVARIANT_FORMAT,
|
|
data, data_size,
|
|
FALSE, g_free, data));
|
|
}
|
|
|
|
|
|
char *
|
|
flatpak_dir_get_origin (FlatpakDir *self,
|
|
const char *ref,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GVariant) deploy_data = NULL;
|
|
|
|
deploy_data = flatpak_dir_get_deploy_data (self, ref,
|
|
cancellable, error);
|
|
if (deploy_data == NULL)
|
|
{
|
|
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED, "%s not installed", ref);
|
|
return NULL;
|
|
}
|
|
|
|
return g_strdup (flatpak_deploy_data_get_origin (deploy_data));
|
|
}
|
|
|
|
char **
|
|
flatpak_dir_get_subpaths (FlatpakDir *self,
|
|
const char *ref,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GVariant) deploy_data = NULL;
|
|
char **subpaths;
|
|
int i;
|
|
|
|
deploy_data = flatpak_dir_get_deploy_data (self, ref,
|
|
cancellable, error);
|
|
if (deploy_data == NULL)
|
|
{
|
|
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED, "%s not installed", ref);
|
|
return NULL;
|
|
}
|
|
|
|
subpaths = (char **) flatpak_deploy_data_get_subpaths (deploy_data);
|
|
for (i = 0; subpaths[i] != NULL; i++)
|
|
subpaths[i] = g_strdup (subpaths[i]);
|
|
|
|
return subpaths;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_ensure_path (FlatpakDir *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
return gs_file_ensure_directory (self->basedir, TRUE, cancellable, error);
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_ensure_repo (FlatpakDir *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_autoptr(GFile) repodir = NULL;
|
|
g_autoptr(OstreeRepo) repo = NULL;
|
|
|
|
if (self->repo == NULL)
|
|
{
|
|
if (!flatpak_dir_ensure_path (self, cancellable, error))
|
|
goto out;
|
|
|
|
repodir = g_file_get_child (self->basedir, "repo");
|
|
if (self->no_system_helper || self->user || getuid () == 0)
|
|
{
|
|
repo = ostree_repo_new (repodir);
|
|
}
|
|
else
|
|
{
|
|
g_autoptr(GFile) cache_dir = NULL;
|
|
g_autofree char *cache_path = NULL;
|
|
|
|
repo = system_ostree_repo_new (repodir);
|
|
|
|
cache_dir = flatpak_ensure_user_cache_dir_location (error);
|
|
if (cache_dir == NULL)
|
|
goto out;
|
|
|
|
cache_path = g_file_get_path (cache_dir);
|
|
if (!ostree_repo_set_cache_dir (repo,
|
|
AT_FDCWD, cache_path,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
if (!g_file_query_exists (repodir, cancellable))
|
|
{
|
|
if (!ostree_repo_create (repo,
|
|
OSTREE_REPO_MODE_BARE_USER,
|
|
cancellable, error))
|
|
{
|
|
gs_shutil_rm_rf (repodir, cancellable, NULL);
|
|
goto out;
|
|
}
|
|
|
|
/* Create .changes file early to avoid polling non-existing file in monitor */
|
|
flatpak_dir_mark_changed (self, NULL);
|
|
}
|
|
else
|
|
{
|
|
if (!ostree_repo_open (repo, cancellable, error))
|
|
{
|
|
g_autofree char *repopath = NULL;
|
|
|
|
repopath = g_file_get_path (repodir);
|
|
g_prefix_error (error, "While opening repository %s: ", repopath);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Make sure we didn't reenter weirdly */
|
|
g_assert (self->repo == NULL);
|
|
self->repo = g_object_ref (repo);
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_mark_changed (FlatpakDir *self,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) changed_file = NULL;
|
|
|
|
changed_file = flatpak_dir_get_changed_path (self);
|
|
if (!g_file_replace_contents (changed_file, "", 0, NULL, FALSE,
|
|
G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_remove_appstream (FlatpakDir *self,
|
|
const char *remote,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) appstream_dir = NULL;
|
|
g_autoptr(GFile) remote_dir = NULL;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
appstream_dir = g_file_get_child (flatpak_dir_get_path (self), "appstream");
|
|
remote_dir = g_file_get_child (appstream_dir, remote);
|
|
|
|
if (g_file_query_exists (remote_dir, cancellable) &&
|
|
!gs_shutil_rm_rf (remote_dir, cancellable, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_deploy_appstream (FlatpakDir *self,
|
|
const char *remote,
|
|
const char *arch,
|
|
gboolean *out_changed,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) appstream_dir = NULL;
|
|
g_autoptr(GFile) remote_dir = NULL;
|
|
g_autoptr(GFile) arch_dir = NULL;
|
|
g_autoptr(GFile) checkout_dir = NULL;
|
|
g_autoptr(GFile) real_checkout_dir = NULL;
|
|
g_autoptr(GFile) timestamp_file = NULL;
|
|
g_autofree char *arch_path = NULL;
|
|
gboolean checkout_exists;
|
|
g_autofree char *remote_and_branch = NULL;
|
|
const char *old_checksum = NULL;
|
|
g_autofree char *new_checksum = NULL;
|
|
g_autoptr(GFile) active_link = NULL;
|
|
g_autofree char *branch = NULL;
|
|
g_autoptr(GFile) old_checkout_dir = NULL;
|
|
g_autofree char *tmpname = NULL;
|
|
g_autoptr(GFile) active_tmp_link = NULL;
|
|
g_autoptr(GError) tmp_error = NULL;
|
|
g_autofree char *checkout_dir_path = NULL;
|
|
OstreeRepoCheckoutOptions options = { 0, };
|
|
glnx_fd_close int dfd = -1;
|
|
g_autoptr(GFileInfo) file_info = NULL;
|
|
|
|
appstream_dir = g_file_get_child (flatpak_dir_get_path (self), "appstream");
|
|
remote_dir = g_file_get_child (appstream_dir, remote);
|
|
arch_dir = g_file_get_child (remote_dir, arch);
|
|
active_link = g_file_get_child (arch_dir, "active");
|
|
timestamp_file = g_file_get_child (arch_dir, ".timestamp");
|
|
|
|
arch_path = g_file_get_path (arch_dir);
|
|
if (g_mkdir_with_parents (arch_path, 0755) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!glnx_opendirat (AT_FDCWD, arch_path, TRUE, &dfd, error))
|
|
return FALSE;
|
|
|
|
old_checksum = NULL;
|
|
file_info = g_file_query_info (active_link, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable, NULL);
|
|
if (file_info != NULL)
|
|
old_checksum = g_file_info_get_symlink_target (file_info);
|
|
|
|
branch = g_strdup_printf ("appstream/%s", arch);
|
|
remote_and_branch = g_strdup_printf ("%s:%s", remote, branch);
|
|
if (!ostree_repo_resolve_rev (self->repo, remote_and_branch, TRUE, &new_checksum, error))
|
|
return FALSE;
|
|
|
|
real_checkout_dir = g_file_get_child (arch_dir, new_checksum);
|
|
checkout_exists = g_file_query_exists (real_checkout_dir, NULL);
|
|
|
|
if (old_checksum != NULL && new_checksum != NULL &&
|
|
strcmp (old_checksum, new_checksum) == 0 &&
|
|
checkout_exists)
|
|
{
|
|
if (!g_file_replace_contents (timestamp_file, "", 0, NULL, FALSE,
|
|
G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error))
|
|
return FALSE;
|
|
|
|
if (out_changed)
|
|
*out_changed = FALSE;
|
|
|
|
return TRUE; /* No changes, don't checkout */
|
|
}
|
|
|
|
{
|
|
g_autofree char *template = g_strdup_printf (".%s-XXXXXX", new_checksum);
|
|
g_autoptr(GFile) tmp_dir_template = g_file_get_child (arch_dir, template);
|
|
checkout_dir_path = g_file_get_path (tmp_dir_template);
|
|
if (g_mkdtemp_full (checkout_dir_path, 0755) == NULL)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't create deploy directory");
|
|
return FALSE;
|
|
}
|
|
}
|
|
checkout_dir = g_file_new_for_path (checkout_dir_path);
|
|
|
|
options.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
|
|
options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
|
|
options.disable_fsync = TRUE; /* We checkout to a temp dir and sync before moving it in place */
|
|
|
|
if (!ostree_repo_checkout_tree_at (self->repo, &options,
|
|
AT_FDCWD, checkout_dir_path, new_checksum,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
tmpname = gs_fileutil_gen_tmp_name (".active-", NULL);
|
|
active_tmp_link = g_file_get_child (arch_dir, tmpname);
|
|
|
|
if (!g_file_make_symbolic_link (active_tmp_link, new_checksum, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (syncfs (dfd) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
/* By now the checkout to the temporary directory is on disk, as is the temporary
|
|
symlink pointing to the final target. */
|
|
|
|
if (!g_file_move (checkout_dir, real_checkout_dir, G_FILE_COPY_NO_FALLBACK_FOR_MOVE,
|
|
cancellable, NULL, NULL, error))
|
|
return FALSE;
|
|
|
|
if (syncfs (dfd) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!gs_file_rename (active_tmp_link,
|
|
active_link,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
if (old_checksum != NULL &&
|
|
g_strcmp0 (old_checksum, new_checksum) != 0)
|
|
{
|
|
old_checkout_dir = g_file_get_child (arch_dir, old_checksum);
|
|
if (!gs_shutil_rm_rf (old_checkout_dir, cancellable, &tmp_error))
|
|
g_warning ("Unable to remove old appstream checkout: %s\n", tmp_error->message);
|
|
}
|
|
|
|
if (!g_file_replace_contents (timestamp_file, "", 0, NULL, FALSE,
|
|
G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error))
|
|
return FALSE;
|
|
|
|
/* If we added a new checkout, touch the toplevel dir to tell people that they need
|
|
to re-scan */
|
|
if (!checkout_exists)
|
|
{
|
|
g_autofree char *appstream_dir_path = g_file_get_path (appstream_dir);
|
|
utime (appstream_dir_path, NULL);
|
|
}
|
|
|
|
if (out_changed)
|
|
*out_changed = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_update_appstream (FlatpakDir *self,
|
|
const char *remote,
|
|
const char *arch,
|
|
gboolean *out_changed,
|
|
OstreeAsyncProgress *progress,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *branch = NULL;
|
|
g_autofree char *remote_and_branch = NULL;
|
|
g_autofree char *new_checksum = NULL;
|
|
|
|
if (out_changed)
|
|
*out_changed = FALSE;
|
|
|
|
if (arch == NULL)
|
|
arch = flatpak_get_arch ();
|
|
|
|
branch = g_strdup_printf ("appstream/%s", arch);
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (flatpak_dir_use_system_helper (self))
|
|
{
|
|
g_autoptr(OstreeRepo) child_repo = NULL;
|
|
g_auto(GLnxLockFile) child_repo_lock = GLNX_LOCK_FILE_INIT;
|
|
FlatpakSystemHelper *system_helper;
|
|
|
|
child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, error);
|
|
if (child_repo == NULL)
|
|
return FALSE;
|
|
|
|
system_helper = flatpak_dir_get_system_helper (self);
|
|
|
|
g_assert (system_helper != NULL);
|
|
|
|
if (!flatpak_dir_pull (self, remote, branch, NULL,
|
|
child_repo, OSTREE_REPO_PULL_FLAGS_MIRROR,
|
|
/* TODO: forcing no-deltas due to issue #144 */ TRUE,
|
|
progress, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!ostree_repo_resolve_rev (child_repo, branch, TRUE, &new_checksum, error))
|
|
return FALSE;
|
|
|
|
if (new_checksum == NULL)
|
|
{
|
|
g_warning ("No appstream branch in remote %s\n", remote);
|
|
}
|
|
else
|
|
{
|
|
if (!flatpak_system_helper_call_deploy_appstream_sync (system_helper,
|
|
gs_file_get_path_cached (ostree_repo_get_path (child_repo)),
|
|
remote,
|
|
arch,
|
|
cancellable,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
(void) glnx_shutil_rm_rf_at (AT_FDCWD,
|
|
gs_file_get_path_cached (ostree_repo_get_path (child_repo)),
|
|
NULL, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (!flatpak_dir_pull (self, remote, branch, NULL, NULL, OSTREE_REPO_PULL_FLAGS_NONE, FALSE, progress,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
remote_and_branch = g_strdup_printf ("%s:%s", remote, branch);
|
|
|
|
if (!ostree_repo_resolve_rev (self->repo, remote_and_branch, TRUE, &new_checksum, error))
|
|
return FALSE;
|
|
|
|
if (new_checksum == NULL)
|
|
{
|
|
g_warning ("No appstream branch in remote %s\n", remote);
|
|
return TRUE;
|
|
}
|
|
|
|
return flatpak_dir_deploy_appstream (self,
|
|
remote,
|
|
arch,
|
|
out_changed,
|
|
cancellable,
|
|
error);
|
|
}
|
|
|
|
/* This is a copy of ostree_repo_pull_one_dir that always disables
|
|
static deltas if subdir is used */
|
|
static gboolean
|
|
repo_pull_one_dir (OstreeRepo *self,
|
|
const char *remote_name,
|
|
const char *dir_to_pull,
|
|
char **refs_to_fetch,
|
|
OstreeRepoPullFlags flags,
|
|
gboolean force_disable_deltas,
|
|
OstreeAsyncProgress *progress,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GVariantBuilder builder;
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
|
|
|
|
if (dir_to_pull)
|
|
{
|
|
g_variant_builder_add (&builder, "{s@v}", "subdir",
|
|
g_variant_new_variant (g_variant_new_string (dir_to_pull)));
|
|
force_disable_deltas = TRUE;
|
|
}
|
|
|
|
if (force_disable_deltas)
|
|
g_variant_builder_add (&builder, "{s@v}", "disable-static-deltas",
|
|
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
|
|
|
g_variant_builder_add (&builder, "{s@v}", "flags",
|
|
g_variant_new_variant (g_variant_new_int32 (flags)));
|
|
if (refs_to_fetch)
|
|
g_variant_builder_add (&builder, "{s@v}", "refs",
|
|
g_variant_new_variant (g_variant_new_strv ((const char * const *) refs_to_fetch, -1)));
|
|
|
|
return ostree_repo_pull_with_options (self, remote_name, g_variant_builder_end (&builder),
|
|
progress, cancellable, error);
|
|
}
|
|
|
|
|
|
gboolean
|
|
flatpak_dir_pull (FlatpakDir *self,
|
|
const char *repository,
|
|
const char *ref,
|
|
const char **subpaths,
|
|
OstreeRepo *repo,
|
|
OstreeRepoPullFlags flags,
|
|
gboolean force_disable_deltas,
|
|
OstreeAsyncProgress *progress,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
GSConsole *console = NULL;
|
|
|
|
g_autoptr(OstreeAsyncProgress) console_progress = NULL;
|
|
const char *refs[2];
|
|
g_autofree char *url = NULL;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
goto out;
|
|
|
|
if (!ostree_repo_remote_get_url (self->repo,
|
|
repository,
|
|
&url,
|
|
error))
|
|
goto out;
|
|
|
|
if (*url == 0)
|
|
return TRUE; /* Empty url, silently disables updates */
|
|
|
|
if (repo == NULL)
|
|
repo = self->repo;
|
|
|
|
if (progress == NULL)
|
|
{
|
|
console = gs_console_get ();
|
|
if (console)
|
|
{
|
|
gs_console_begin_status_line (console, "", NULL, NULL);
|
|
console_progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console);
|
|
progress = console_progress;
|
|
}
|
|
}
|
|
|
|
|
|
refs[0] = ref;
|
|
refs[1] = NULL;
|
|
|
|
if (subpaths == NULL || subpaths[0] == NULL)
|
|
{
|
|
if (!repo_pull_one_dir (repo, repository, NULL,
|
|
(char **) refs, flags, force_disable_deltas,
|
|
progress,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "While pulling %s from remote %s: ", ref, repository);
|
|
goto out;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
|
|
if (!repo_pull_one_dir (repo, repository,
|
|
"/metadata",
|
|
(char **) refs, flags, force_disable_deltas,
|
|
progress,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "While pulling %s from remote %s, metadata: ",
|
|
ref, repository);
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; subpaths[i] != NULL; i++)
|
|
{
|
|
g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL);
|
|
if (!repo_pull_one_dir (repo, repository,
|
|
subpath,
|
|
(char **) refs, flags, force_disable_deltas,
|
|
progress,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "While pulling %s from remote %s, subpath %s: ",
|
|
ref, repository, subpaths[i]);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (console)
|
|
{
|
|
ostree_async_progress_finish (progress);
|
|
gs_console_end_status_line (console, NULL, NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
repo_pull_one_untrusted (OstreeRepo *self,
|
|
const char *remote_name,
|
|
const char *url,
|
|
const char *dir_to_pull,
|
|
const char *ref,
|
|
const char *checksum,
|
|
OstreeAsyncProgress *progress,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
OstreeRepoPullFlags flags = OSTREE_REPO_PULL_FLAGS_UNTRUSTED;
|
|
GVariantBuilder builder;
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
|
|
const char *refs[2] = { NULL, NULL };
|
|
const char *commits[2] = { NULL, NULL };
|
|
|
|
refs[0] = ref;
|
|
commits[0] = checksum;
|
|
|
|
g_variant_builder_add (&builder, "{s@v}", "flags",
|
|
g_variant_new_variant (g_variant_new_int32 (flags)));
|
|
g_variant_builder_add (&builder, "{s@v}", "refs",
|
|
g_variant_new_variant (g_variant_new_strv ((const char * const *) refs, -1)));
|
|
g_variant_builder_add (&builder, "{s@v}", "override-commit-ids",
|
|
g_variant_new_variant (g_variant_new_strv ((const char * const *) commits, -1)));
|
|
g_variant_builder_add (&builder, "{s@v}", "override-remote-name",
|
|
g_variant_new_variant (g_variant_new_string (remote_name)));
|
|
g_variant_builder_add (&builder, "{s@v}", "gpg-verify",
|
|
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
|
g_variant_builder_add (&builder, "{s@v}", "gpg-verify-summary",
|
|
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
|
|
|
if (dir_to_pull)
|
|
{
|
|
g_variant_builder_add (&builder, "{s@v}", "subdir",
|
|
g_variant_new_variant (g_variant_new_string (dir_to_pull)));
|
|
g_variant_builder_add (&builder, "{s@v}", "disable-static-deltas",
|
|
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
|
}
|
|
|
|
return ostree_repo_pull_with_options (self, url, g_variant_builder_end (&builder),
|
|
progress, cancellable, error);
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_pull_untrusted_local (FlatpakDir *self,
|
|
const char *src_path,
|
|
const char *remote_name,
|
|
const char *ref,
|
|
const char **subpaths,
|
|
OstreeAsyncProgress *progress,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
GSConsole *console = NULL;
|
|
|
|
g_autoptr(OstreeAsyncProgress) console_progress = NULL;
|
|
g_autoptr(GFile) path_file = g_file_new_for_path (src_path);
|
|
g_autoptr(GFile) summary_file = g_file_get_child (path_file, "summary");
|
|
g_autoptr(GFile) summary_sig_file = g_file_get_child (path_file, "summary.sig");
|
|
g_autofree char *url = g_file_get_uri (path_file);
|
|
g_autofree char *checksum = NULL;
|
|
gboolean gpg_verify_summary;
|
|
gboolean gpg_verify;
|
|
char *summary_data = NULL;
|
|
char *summary_sig_data = NULL;
|
|
gsize summary_data_size, summary_sig_data_size;
|
|
g_autoptr(GBytes) summary_bytes = NULL;
|
|
g_autoptr(GBytes) summary_sig_bytes = NULL;
|
|
g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL;
|
|
g_autoptr(GVariant) summary = NULL;
|
|
g_autoptr(GVariant) old_commit = NULL;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, remote_name,
|
|
&gpg_verify_summary, error))
|
|
return FALSE;
|
|
|
|
if (!ostree_repo_remote_get_gpg_verify (self->repo, remote_name,
|
|
&gpg_verify, error))
|
|
return FALSE;
|
|
|
|
if (!gpg_verify_summary || !gpg_verify)
|
|
return flatpak_fail (error, "Can't pull from untrusted non-gpg verified remote");
|
|
|
|
/* We verify the summary manually before anything else to make sure
|
|
we've got something right before looking too hard at the repo and
|
|
so we can check for a downgrade before pulling and updating the
|
|
ref */
|
|
|
|
if (!g_file_load_contents (summary_sig_file, cancellable,
|
|
&summary_sig_data, &summary_sig_data_size, NULL, NULL))
|
|
return flatpak_fail (error, "GPG verification enabled, but no summary signatures found");
|
|
|
|
summary_sig_bytes = g_bytes_new_take (summary_sig_data, summary_sig_data_size);
|
|
|
|
if (!g_file_load_contents (summary_file, cancellable,
|
|
&summary_data, &summary_data_size, NULL, NULL))
|
|
return flatpak_fail (error, "No summary found");
|
|
summary_bytes = g_bytes_new_take (summary_data, summary_data_size);
|
|
|
|
gpg_result = ostree_repo_verify_summary (self->repo,
|
|
remote_name,
|
|
summary_bytes,
|
|
summary_sig_bytes,
|
|
cancellable, error);
|
|
if (gpg_result == NULL)
|
|
return FALSE;
|
|
|
|
if (ostree_gpg_verify_result_count_valid (gpg_result) == 0)
|
|
return flatpak_fail (error, "GPG signatures found, but none are in trusted keyring");
|
|
|
|
summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, FALSE));
|
|
if (!flatpak_summary_lookup_ref (summary,
|
|
ref,
|
|
&checksum))
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"Can't find %sin remote %s", ref, remote_name);
|
|
return FALSE;
|
|
}
|
|
|
|
(void) ostree_repo_load_commit (self->repo, checksum, &old_commit, NULL, NULL);
|
|
|
|
if (old_commit)
|
|
{
|
|
g_autoptr(OstreeRepo) src_repo = ostree_repo_new (path_file);
|
|
g_autoptr(GVariant) new_commit = NULL;
|
|
guint64 old_timestamp;
|
|
guint64 new_timestamp;
|
|
|
|
if (!ostree_repo_open (src_repo, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!ostree_repo_load_commit (src_repo, checksum, &new_commit, NULL, error))
|
|
return FALSE;
|
|
|
|
old_timestamp = ostree_commit_get_timestamp (old_commit);
|
|
new_timestamp = ostree_commit_get_timestamp (new_commit);
|
|
|
|
if (new_timestamp < old_timestamp)
|
|
return flatpak_fail (error, "Not allowed to downgrade %s", ref);
|
|
}
|
|
|
|
|
|
if (progress == NULL)
|
|
{
|
|
console = gs_console_get ();
|
|
if (console)
|
|
{
|
|
gs_console_begin_status_line (console, "", NULL, NULL);
|
|
console_progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console);
|
|
progress = console_progress;
|
|
}
|
|
}
|
|
|
|
if (subpaths == NULL || subpaths[0] == NULL)
|
|
{
|
|
if (!repo_pull_one_untrusted (self->repo, remote_name, url,
|
|
NULL, ref, checksum, progress,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "While pulling %s from remote %s: ", ref, remote_name);
|
|
goto out;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
|
|
if (!repo_pull_one_untrusted (self->repo, remote_name, url,
|
|
"/metadata", ref, checksum, progress,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "While pulling %s from remote %s, metadata: ",
|
|
ref, remote_name);
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; subpaths[i] != NULL; i++)
|
|
{
|
|
g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL);
|
|
if (!repo_pull_one_untrusted (self->repo, remote_name, url,
|
|
subpath, ref, checksum, progress,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "While pulling %s from remote %s, subpath %s: ",
|
|
ref, remote_name, subpaths[i]);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (console)
|
|
{
|
|
ostree_async_progress_finish (progress);
|
|
gs_console_end_status_line (console, NULL, NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
char *
|
|
flatpak_dir_current_ref (FlatpakDir *self,
|
|
const char *name,
|
|
GCancellable *cancellable)
|
|
{
|
|
g_autoptr(GFile) base = NULL;
|
|
g_autoptr(GFile) dir = NULL;
|
|
g_autoptr(GFile) current_link = NULL;
|
|
g_autoptr(GFileInfo) file_info = NULL;
|
|
|
|
base = g_file_get_child (flatpak_dir_get_path (self), "app");
|
|
dir = g_file_get_child (base, name);
|
|
|
|
current_link = g_file_get_child (dir, "current");
|
|
|
|
file_info = g_file_query_info (current_link, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable, NULL);
|
|
if (file_info == NULL)
|
|
return NULL;
|
|
|
|
return g_strconcat ("app/", name, "/", g_file_info_get_symlink_target (file_info), NULL);
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_drop_current_ref (FlatpakDir *self,
|
|
const char *name,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) base = NULL;
|
|
g_autoptr(GFile) dir = NULL;
|
|
g_autoptr(GFile) current_link = NULL;
|
|
g_auto(GStrv) refs = NULL;
|
|
g_autofree char *current_ref = NULL;
|
|
const char *other_ref = NULL;
|
|
|
|
base = g_file_get_child (flatpak_dir_get_path (self), "app");
|
|
dir = g_file_get_child (base, name);
|
|
|
|
current_ref = flatpak_dir_current_ref (self, name, cancellable);
|
|
|
|
if (flatpak_dir_list_refs_for_name (self, "app", name, &refs, cancellable, NULL))
|
|
{
|
|
int i;
|
|
for (i = 0; refs[i] != NULL; i++)
|
|
{
|
|
if (g_strcmp0 (refs[i], current_ref) != 0)
|
|
{
|
|
other_ref = refs[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
current_link = g_file_get_child (dir, "current");
|
|
|
|
if (!g_file_delete (current_link, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (other_ref)
|
|
{
|
|
if (!flatpak_dir_make_current_ref (self, other_ref, cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_make_current_ref (FlatpakDir *self,
|
|
const char *ref,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) base = NULL;
|
|
g_autoptr(GFile) dir = NULL;
|
|
g_autoptr(GFile) current_link = NULL;
|
|
g_auto(GStrv) ref_parts = NULL;
|
|
g_autofree char *rest = NULL;
|
|
gboolean ret = FALSE;
|
|
|
|
ref_parts = g_strsplit (ref, "/", -1);
|
|
|
|
g_assert (g_strv_length (ref_parts) == 4);
|
|
g_assert (strcmp (ref_parts[0], "app") == 0);
|
|
|
|
base = g_file_get_child (flatpak_dir_get_path (self), ref_parts[0]);
|
|
dir = g_file_get_child (base, ref_parts[1]);
|
|
|
|
current_link = g_file_get_child (dir, "current");
|
|
|
|
g_file_delete (current_link, cancellable, NULL);
|
|
|
|
if (*ref_parts[3] != 0)
|
|
{
|
|
rest = g_strdup_printf ("%s/%s", ref_parts[2], ref_parts[3]);
|
|
if (!g_file_make_symbolic_link (current_link, rest, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_list_refs_for_name (FlatpakDir *self,
|
|
const char *kind,
|
|
const char *name,
|
|
char ***refs_out,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_autoptr(GFile) base = NULL;
|
|
g_autoptr(GFile) dir = NULL;
|
|
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
|
g_autoptr(GFileInfo) child_info = NULL;
|
|
GError *temp_error = NULL;
|
|
g_autoptr(GPtrArray) refs = NULL;
|
|
|
|
base = g_file_get_child (flatpak_dir_get_path (self), kind);
|
|
dir = g_file_get_child (base, name);
|
|
|
|
refs = g_ptr_array_new ();
|
|
|
|
if (!g_file_query_exists (dir, cancellable))
|
|
{
|
|
ret = TRUE;
|
|
goto out;
|
|
}
|
|
|
|
dir_enum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable, error);
|
|
if (!dir_enum)
|
|
goto out;
|
|
|
|
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)))
|
|
{
|
|
g_autoptr(GFile) child = NULL;
|
|
g_autoptr(GFileEnumerator) dir_enum2 = NULL;
|
|
g_autoptr(GFileInfo) child_info2 = NULL;
|
|
const char *arch;
|
|
|
|
arch = g_file_info_get_name (child_info);
|
|
|
|
if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_DIRECTORY ||
|
|
strcmp (arch, "data") == 0 /* There used to be a data dir here, lets ignore it */)
|
|
{
|
|
g_clear_object (&child_info);
|
|
continue;
|
|
}
|
|
|
|
child = g_file_get_child (dir, arch);
|
|
g_clear_object (&dir_enum2);
|
|
dir_enum2 = g_file_enumerate_children (child, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable, error);
|
|
if (!dir_enum2)
|
|
goto out;
|
|
|
|
while ((child_info2 = g_file_enumerator_next_file (dir_enum2, cancellable, &temp_error)))
|
|
{
|
|
const char *branch;
|
|
|
|
if (g_file_info_get_file_type (child_info2) == G_FILE_TYPE_DIRECTORY)
|
|
{
|
|
branch = g_file_info_get_name (child_info2);
|
|
g_ptr_array_add (refs,
|
|
g_strdup_printf ("%s/%s/%s/%s", kind, name, arch, branch));
|
|
}
|
|
|
|
g_clear_object (&child_info2);
|
|
}
|
|
|
|
|
|
if (temp_error != NULL)
|
|
goto out;
|
|
|
|
g_clear_object (&child_info);
|
|
}
|
|
|
|
if (temp_error != NULL)
|
|
goto out;
|
|
|
|
g_ptr_array_sort (refs, flatpak_strcmp0_ptr);
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (ret)
|
|
{
|
|
g_ptr_array_add (refs, NULL);
|
|
*refs_out = (char **) g_ptr_array_free (refs, FALSE);
|
|
refs = NULL;
|
|
}
|
|
|
|
if (temp_error != NULL)
|
|
g_propagate_error (error, temp_error);
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_list_refs (FlatpakDir *self,
|
|
const char *kind,
|
|
char ***refs_out,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_autoptr(GFile) base;
|
|
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
|
g_autoptr(GFileInfo) child_info = NULL;
|
|
GError *temp_error = NULL;
|
|
g_autoptr(GPtrArray) refs = NULL;
|
|
|
|
refs = g_ptr_array_new ();
|
|
|
|
base = g_file_get_child (flatpak_dir_get_path (self), kind);
|
|
|
|
if (!g_file_query_exists (base, cancellable))
|
|
{
|
|
ret = TRUE;
|
|
goto out;
|
|
}
|
|
|
|
dir_enum = g_file_enumerate_children (base, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable, error);
|
|
if (!dir_enum)
|
|
goto out;
|
|
|
|
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)))
|
|
{
|
|
gchar **sub_refs = NULL;
|
|
const char *name;
|
|
int i;
|
|
|
|
if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_DIRECTORY)
|
|
{
|
|
g_clear_object (&child_info);
|
|
continue;
|
|
}
|
|
|
|
name = g_file_info_get_name (child_info);
|
|
|
|
if (!flatpak_dir_list_refs_for_name (self, kind, name, &sub_refs, cancellable, error))
|
|
goto out;
|
|
|
|
for (i = 0; sub_refs[i] != NULL; i++)
|
|
g_ptr_array_add (refs, sub_refs[i]);
|
|
g_free (sub_refs);
|
|
|
|
g_clear_object (&child_info);
|
|
}
|
|
|
|
if (temp_error != NULL)
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
|
|
g_ptr_array_sort (refs, flatpak_strcmp0_ptr);
|
|
|
|
out:
|
|
if (ret)
|
|
{
|
|
g_ptr_array_add (refs, NULL);
|
|
*refs_out = (char **) g_ptr_array_free (refs, FALSE);
|
|
refs = NULL;
|
|
}
|
|
|
|
if (temp_error != NULL)
|
|
g_propagate_error (error, temp_error);
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *
|
|
flatpak_dir_read_latest (FlatpakDir *self,
|
|
const char *remote,
|
|
const char *ref,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *remote_and_ref = NULL;
|
|
char *res = NULL;
|
|
|
|
/* There may be several remotes with the same branch (if we for
|
|
* instance changed the origin, so prepend the current origin to
|
|
* make sure we get the right one */
|
|
|
|
if (remote)
|
|
remote_and_ref = g_strdup_printf ("%s:%s", remote, ref);
|
|
else
|
|
remote_and_ref = g_strdup (ref);
|
|
|
|
if (!ostree_repo_resolve_rev (self->repo, remote_and_ref, FALSE, &res, error))
|
|
return NULL;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
char *
|
|
flatpak_dir_read_active (FlatpakDir *self,
|
|
const char *ref,
|
|
GCancellable *cancellable)
|
|
{
|
|
g_autoptr(GFile) deploy_base = NULL;
|
|
g_autoptr(GFile) active_link = NULL;
|
|
g_autoptr(GFileInfo) file_info = NULL;
|
|
|
|
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
|
active_link = g_file_get_child (deploy_base, "active");
|
|
|
|
file_info = g_file_query_info (active_link, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable, NULL);
|
|
if (file_info == NULL)
|
|
return NULL;
|
|
|
|
return g_strdup (g_file_info_get_symlink_target (file_info));
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_set_active (FlatpakDir *self,
|
|
const char *ref,
|
|
const char *checksum,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_autoptr(GFile) deploy_base = NULL;
|
|
g_autofree char *tmpname = NULL;
|
|
g_autoptr(GFile) active_tmp_link = NULL;
|
|
g_autoptr(GFile) active_link = NULL;
|
|
g_autoptr(GError) my_error = NULL;
|
|
|
|
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
|
active_link = g_file_get_child (deploy_base, "active");
|
|
|
|
if (checksum != NULL)
|
|
{
|
|
tmpname = gs_fileutil_gen_tmp_name (".active-", NULL);
|
|
active_tmp_link = g_file_get_child (deploy_base, tmpname);
|
|
if (!g_file_make_symbolic_link (active_tmp_link, checksum, cancellable, error))
|
|
goto out;
|
|
|
|
if (!gs_file_rename (active_tmp_link,
|
|
active_link,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
else
|
|
{
|
|
if (!g_file_delete (active_link, cancellable, &my_error) &&
|
|
!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
{
|
|
g_propagate_error (error, my_error);
|
|
my_error = NULL;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
gboolean
|
|
flatpak_dir_run_triggers (FlatpakDir *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
|
g_autoptr(GFileInfo) child_info = NULL;
|
|
g_autoptr(GFile) triggersdir = NULL;
|
|
GError *temp_error = NULL;
|
|
const char *triggerspath;
|
|
|
|
triggerspath = g_getenv ("FLATPAK_TRIGGERSDIR");
|
|
if (triggerspath == NULL)
|
|
triggerspath = FLATPAK_TRIGGERDIR;
|
|
|
|
g_debug ("running triggers from %s", triggerspath);
|
|
|
|
triggersdir = g_file_new_for_path (triggerspath);
|
|
|
|
dir_enum = g_file_enumerate_children (triggersdir, "standard::type,standard::name",
|
|
0, cancellable, error);
|
|
if (!dir_enum)
|
|
goto out;
|
|
|
|
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
|
|
{
|
|
g_autoptr(GFile) child = NULL;
|
|
const char *name;
|
|
GError *trigger_error = NULL;
|
|
|
|
name = g_file_info_get_name (child_info);
|
|
|
|
child = g_file_get_child (triggersdir, name);
|
|
|
|
if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_REGULAR &&
|
|
g_str_has_suffix (name, ".trigger"))
|
|
{
|
|
g_autoptr(GPtrArray) argv_array = NULL;
|
|
/* We need to canonicalize the basedir, because if has a symlink
|
|
somewhere the bind mount will be on the target of that, not
|
|
at that exact path. */
|
|
g_autofree char *basedir_orig = g_file_get_path (self->basedir);
|
|
g_autofree char *basedir = canonicalize_file_name (basedir_orig);
|
|
|
|
g_debug ("running trigger %s", name);
|
|
|
|
argv_array = g_ptr_array_new_with_free_func (g_free);
|
|
#ifdef DISABLE_SANDBOXED_TRIGGERS
|
|
g_ptr_array_add (argv_array, g_file_get_path (child));
|
|
g_ptr_array_add (argv_array, g_strdup (basedir));
|
|
#else
|
|
g_ptr_array_add (argv_array, g_strdup (flatpak_get_bwrap ()));
|
|
g_ptr_array_add (argv_array, g_strdup ("--unshare-ipc"));
|
|
g_ptr_array_add (argv_array, g_strdup ("--unshare-net"));
|
|
g_ptr_array_add (argv_array, g_strdup ("--unshare-pid"));
|
|
g_ptr_array_add (argv_array, g_strdup ("--ro-bind"));
|
|
g_ptr_array_add (argv_array, g_strdup ("/"));
|
|
g_ptr_array_add (argv_array, g_strdup ("/"));
|
|
g_ptr_array_add (argv_array, g_strdup ("--proc"));
|
|
g_ptr_array_add (argv_array, g_strdup ("/proc"));
|
|
g_ptr_array_add (argv_array, g_strdup ("--dev"));
|
|
g_ptr_array_add (argv_array, g_strdup ("/dev"));
|
|
g_ptr_array_add (argv_array, g_strdup ("--bind"));
|
|
g_ptr_array_add (argv_array, g_strdup (basedir));
|
|
g_ptr_array_add (argv_array, g_strdup (basedir));
|
|
#endif
|
|
g_ptr_array_add (argv_array, g_file_get_path (child));
|
|
g_ptr_array_add (argv_array, g_strdup (basedir));
|
|
g_ptr_array_add (argv_array, NULL);
|
|
|
|
if (!g_spawn_sync ("/",
|
|
(char **) argv_array->pdata,
|
|
NULL,
|
|
G_SPAWN_SEARCH_PATH,
|
|
NULL, NULL,
|
|
NULL, NULL,
|
|
NULL, &trigger_error))
|
|
{
|
|
g_warning ("Error running trigger %s: %s", name, trigger_error->message);
|
|
g_clear_error (&trigger_error);
|
|
}
|
|
}
|
|
|
|
g_clear_object (&child_info);
|
|
}
|
|
|
|
if (temp_error != NULL)
|
|
{
|
|
g_propagate_error (error, temp_error);
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
read_fd (int fd,
|
|
struct stat *stat_buf,
|
|
gchar **contents,
|
|
gsize *length,
|
|
GError **error)
|
|
{
|
|
gchar *buf;
|
|
gsize bytes_read;
|
|
gsize size;
|
|
gsize alloc_size;
|
|
|
|
size = stat_buf->st_size;
|
|
|
|
alloc_size = size + 1;
|
|
buf = g_try_malloc (alloc_size);
|
|
|
|
if (buf == NULL)
|
|
{
|
|
g_set_error (error,
|
|
G_FILE_ERROR,
|
|
G_FILE_ERROR_NOMEM,
|
|
"not enough memory");
|
|
return FALSE;
|
|
}
|
|
|
|
bytes_read = 0;
|
|
while (bytes_read < size)
|
|
{
|
|
gssize rc;
|
|
|
|
rc = read (fd, buf + bytes_read, size - bytes_read);
|
|
|
|
if (rc < 0)
|
|
{
|
|
if (errno != EINTR)
|
|
{
|
|
int save_errno = errno;
|
|
|
|
g_free (buf);
|
|
g_set_error (error,
|
|
G_FILE_ERROR,
|
|
g_file_error_from_errno (save_errno),
|
|
"Failed to read from exported file");
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if (rc == 0)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
bytes_read += rc;
|
|
}
|
|
}
|
|
|
|
buf[bytes_read] = '\0';
|
|
|
|
if (length)
|
|
*length = bytes_read;
|
|
|
|
*contents = buf;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* This is conservative, but lets us avoid escaping most
|
|
regular Exec= lines, which is nice as that can sometimes
|
|
cause problems for apps launching desktop files. */
|
|
static gboolean
|
|
need_quotes (const char *str)
|
|
{
|
|
const char *p;
|
|
|
|
for (p = str; *p; p++)
|
|
{
|
|
if (!g_ascii_isalnum (*p) &&
|
|
strchr ("-_%.=:/@", *p) == NULL)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static char *
|
|
maybe_quote (const char *str)
|
|
{
|
|
if (need_quotes (str))
|
|
return g_shell_quote (str);
|
|
return g_strdup (str);
|
|
}
|
|
|
|
static gboolean
|
|
export_desktop_file (const char *app,
|
|
const char *branch,
|
|
const char *arch,
|
|
GKeyFile *metadata,
|
|
int parent_fd,
|
|
const char *name,
|
|
struct stat *stat_buf,
|
|
char **target,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
glnx_fd_close int desktop_fd = -1;
|
|
g_autofree char *tmpfile_name = NULL;
|
|
|
|
g_autoptr(GOutputStream) out_stream = NULL;
|
|
g_autofree gchar *data = NULL;
|
|
gsize data_len;
|
|
g_autofree gchar *new_data = NULL;
|
|
gsize new_data_len;
|
|
g_autoptr(GKeyFile) keyfile = NULL;
|
|
g_autofree gchar *old_exec = NULL;
|
|
gint old_argc;
|
|
g_auto(GStrv) old_argv = NULL;
|
|
g_auto(GStrv) groups = NULL;
|
|
GString *new_exec = NULL;
|
|
g_autofree char *escaped_app = maybe_quote (app);
|
|
g_autofree char *escaped_branch = maybe_quote (branch);
|
|
g_autofree char *escaped_arch = maybe_quote (arch);
|
|
int i;
|
|
|
|
if (!gs_file_openat_noatime (parent_fd, name, &desktop_fd, cancellable, error))
|
|
goto out;
|
|
|
|
if (!read_fd (desktop_fd, stat_buf, &data, &data_len, error))
|
|
goto out;
|
|
|
|
keyfile = g_key_file_new ();
|
|
if (!g_key_file_load_from_data (keyfile, data, data_len, G_KEY_FILE_KEEP_TRANSLATIONS, error))
|
|
goto out;
|
|
|
|
if (g_str_has_suffix (name, ".service"))
|
|
{
|
|
g_autofree gchar *dbus_name = NULL;
|
|
g_autofree gchar *expected_dbus_name = g_strndup (name, strlen (name) - strlen (".service"));
|
|
|
|
dbus_name = g_key_file_get_string (keyfile, "D-BUS Service", "Name", NULL);
|
|
|
|
if (dbus_name == NULL || strcmp (dbus_name, expected_dbus_name) != 0)
|
|
{
|
|
flatpak_fail (error, "dbus service file %s has wrong name", name);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (g_str_has_suffix (name, ".desktop"))
|
|
{
|
|
gsize length;
|
|
g_auto(GStrv) tags = g_key_file_get_string_list (metadata,
|
|
"Application",
|
|
"tags", &length,
|
|
NULL);
|
|
|
|
if (tags != NULL)
|
|
{
|
|
g_key_file_set_string_list (keyfile,
|
|
"Desktop Entry",
|
|
"X-Flatpak-Tags",
|
|
(const char * const *) tags, length);
|
|
}
|
|
}
|
|
|
|
groups = g_key_file_get_groups (keyfile, NULL);
|
|
|
|
for (i = 0; groups[i] != NULL; i++)
|
|
{
|
|
g_key_file_remove_key (keyfile, groups[i], "TryExec", NULL);
|
|
|
|
/* Remove this to make sure nothing tries to execute it outside the sandbox*/
|
|
g_key_file_remove_key (keyfile, groups[i], "X-GNOME-Bugzilla-ExtraInfoScript", NULL);
|
|
|
|
new_exec = g_string_new ("");
|
|
g_string_append_printf (new_exec, FLATPAK_BINDIR "/flatpak run --branch=%s --arch=%s", escaped_branch, escaped_arch);
|
|
|
|
old_exec = g_key_file_get_string (keyfile, groups[i], "Exec", NULL);
|
|
if (old_exec && g_shell_parse_argv (old_exec, &old_argc, &old_argv, NULL) && old_argc >= 1)
|
|
{
|
|
int i;
|
|
g_autofree char *command = maybe_quote (old_argv[0]);
|
|
|
|
g_string_append_printf (new_exec, " --command=%s", command);
|
|
|
|
g_string_append (new_exec, " ");
|
|
g_string_append (new_exec, escaped_app);
|
|
|
|
for (i = 1; i < old_argc; i++)
|
|
{
|
|
g_autofree char *arg = maybe_quote (old_argv[i]);
|
|
g_string_append (new_exec, " ");
|
|
g_string_append (new_exec, arg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_string_append (new_exec, " ");
|
|
g_string_append (new_exec, escaped_app);
|
|
}
|
|
|
|
g_key_file_set_string (keyfile, groups[i], G_KEY_FILE_DESKTOP_KEY_EXEC, new_exec->str);
|
|
}
|
|
|
|
new_data = g_key_file_to_data (keyfile, &new_data_len, error);
|
|
if (new_data == NULL)
|
|
goto out;
|
|
|
|
if (!gs_file_open_in_tmpdir_at (parent_fd, 0755, &tmpfile_name, &out_stream, cancellable, error))
|
|
goto out;
|
|
|
|
if (!g_output_stream_write_all (out_stream, new_data, new_data_len, NULL, cancellable, error))
|
|
goto out;
|
|
|
|
if (!g_output_stream_close (out_stream, cancellable, error))
|
|
goto out;
|
|
|
|
if (target)
|
|
*target = g_steal_pointer (&tmpfile_name);
|
|
|
|
ret = TRUE;
|
|
out:
|
|
|
|
if (new_exec != NULL)
|
|
g_string_free (new_exec, TRUE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
rewrite_export_dir (const char *app,
|
|
const char *branch,
|
|
const char *arch,
|
|
GKeyFile *metadata,
|
|
int source_parent_fd,
|
|
const char *source_name,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_auto(GLnxDirFdIterator) source_iter = {0};
|
|
g_autoptr(GHashTable) visited_children = NULL;
|
|
struct dirent *dent;
|
|
|
|
if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error))
|
|
goto out;
|
|
|
|
visited_children = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
|
|
while (TRUE)
|
|
{
|
|
struct stat stbuf;
|
|
|
|
if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error))
|
|
goto out;
|
|
|
|
if (dent == NULL)
|
|
break;
|
|
|
|
if (g_hash_table_contains (visited_children, dent->d_name))
|
|
continue;
|
|
|
|
/* Avoid processing the same file again if it was re-created during an export */
|
|
g_hash_table_insert (visited_children, g_strdup (dent->d_name), GINT_TO_POINTER (1));
|
|
|
|
if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
|
|
{
|
|
if (errno == ENOENT)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (S_ISDIR (stbuf.st_mode))
|
|
{
|
|
if (!rewrite_export_dir (app, branch, arch, metadata,
|
|
source_iter.fd, dent->d_name,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
else if (S_ISREG (stbuf.st_mode))
|
|
{
|
|
if (!flatpak_has_name_prefix (dent->d_name, app))
|
|
{
|
|
g_warning ("Non-prefixed filename %s in app %s, removing.\n", dent->d_name, app);
|
|
if (unlinkat (source_iter.fd, dent->d_name, 0) != 0 && errno != ENOENT)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (g_str_has_suffix (dent->d_name, ".desktop") ||
|
|
g_str_has_suffix (dent->d_name, ".service"))
|
|
{
|
|
g_autofree gchar *new_name = NULL;
|
|
|
|
if (!export_desktop_file (app, branch, arch, metadata,
|
|
source_iter.fd, dent->d_name, &stbuf, &new_name, cancellable, error))
|
|
goto out;
|
|
|
|
g_hash_table_insert (visited_children, g_strdup (new_name), GINT_TO_POINTER (1));
|
|
|
|
if (renameat (source_iter.fd, new_name, source_iter.fd, dent->d_name) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_warning ("Not exporting file %s of unsupported type\n", dent->d_name);
|
|
if (unlinkat (source_iter.fd, dent->d_name, 0) != 0 && errno != ENOENT)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_rewrite_export_dir (const char *app,
|
|
const char *branch,
|
|
const char *arch,
|
|
GKeyFile *metadata,
|
|
GFile *source,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
/* The fds are closed by this call */
|
|
if (!rewrite_export_dir (app, branch, arch, metadata,
|
|
AT_FDCWD, gs_file_get_path_cached (source),
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
export_dir (int source_parent_fd,
|
|
const char *source_name,
|
|
const char *source_symlink_prefix,
|
|
const char *source_relpath,
|
|
int destination_parent_fd,
|
|
const char *destination_name,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
int res;
|
|
|
|
g_auto(GLnxDirFdIterator) source_iter = {0};
|
|
glnx_fd_close int destination_dfd = -1;
|
|
struct dirent *dent;
|
|
|
|
if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error))
|
|
goto out;
|
|
|
|
do
|
|
res = mkdirat (destination_parent_fd, destination_name, 0755);
|
|
while (G_UNLIKELY (res == -1 && errno == EINTR));
|
|
if (res == -1)
|
|
{
|
|
if (errno != EEXIST)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!gs_file_open_dir_fd_at (destination_parent_fd, destination_name,
|
|
&destination_dfd,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
while (TRUE)
|
|
{
|
|
struct stat stbuf;
|
|
|
|
if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error))
|
|
goto out;
|
|
|
|
if (dent == NULL)
|
|
break;
|
|
|
|
if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
|
|
{
|
|
if (errno == ENOENT)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (S_ISDIR (stbuf.st_mode))
|
|
{
|
|
g_autofree gchar *child_symlink_prefix = g_build_filename ("..", source_symlink_prefix, dent->d_name, NULL);
|
|
g_autofree gchar *child_relpath = g_strconcat (source_relpath, dent->d_name, "/", NULL);
|
|
|
|
if (!export_dir (source_iter.fd, dent->d_name, child_symlink_prefix, child_relpath, destination_dfd, dent->d_name,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
else if (S_ISREG (stbuf.st_mode))
|
|
{
|
|
g_autofree gchar *target = NULL;
|
|
|
|
target = g_build_filename (source_symlink_prefix, dent->d_name, NULL);
|
|
|
|
if (unlinkat (destination_dfd, dent->d_name, 0) != 0 && errno != ENOENT)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
|
|
if (symlinkat (target, destination_dfd, dent->d_name) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_export_dir (GFile *source,
|
|
GFile *destination,
|
|
const char *symlink_prefix,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
if (!gs_file_ensure_directory (destination, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
/* The fds are closed by this call */
|
|
if (!export_dir (AT_FDCWD, gs_file_get_path_cached (source), symlink_prefix, "",
|
|
AT_FDCWD, gs_file_get_path_cached (destination),
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_update_exports (FlatpakDir *self,
|
|
const char *changed_app,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_autoptr(GFile) exports = NULL;
|
|
g_autofree char *current_ref = NULL;
|
|
g_autofree char *active_id = NULL;
|
|
g_autofree char *symlink_prefix = NULL;
|
|
|
|
exports = flatpak_dir_get_exports_dir (self);
|
|
|
|
if (!gs_file_ensure_directory (exports, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
if (changed_app &&
|
|
(current_ref = flatpak_dir_current_ref (self, changed_app, cancellable)) &&
|
|
(active_id = flatpak_dir_read_active (self, current_ref, cancellable)))
|
|
{
|
|
g_autoptr(GFile) deploy_base = NULL;
|
|
g_autoptr(GFile) active = NULL;
|
|
g_autoptr(GFile) export = NULL;
|
|
|
|
deploy_base = flatpak_dir_get_deploy_dir (self, current_ref);
|
|
active = g_file_get_child (deploy_base, active_id);
|
|
export = g_file_get_child (active, "export");
|
|
|
|
if (g_file_query_exists (export, cancellable))
|
|
{
|
|
symlink_prefix = g_build_filename ("..", "app", changed_app, "current", "active", "export", NULL);
|
|
if (!flatpak_export_dir (export, exports,
|
|
symlink_prefix,
|
|
cancellable,
|
|
error))
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!flatpak_remove_dangling_symlinks (exports, cancellable, error))
|
|
goto out;
|
|
|
|
if (!flatpak_dir_run_triggers (self, cancellable, error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_deploy (FlatpakDir *self,
|
|
const char *origin,
|
|
const char *ref,
|
|
const char *checksum_or_latest,
|
|
const char * const * subpaths,
|
|
GVariant *old_deploy_data,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *resolved_ref = NULL;
|
|
|
|
g_autoptr(GFile) root = NULL;
|
|
g_autoptr(GFile) deploy_base = NULL;
|
|
g_autoptr(GFile) checkoutdir = NULL;
|
|
g_autofree char *checkoutdirpath = NULL;
|
|
g_autoptr(GFile) real_checkoutdir = NULL;
|
|
g_autoptr(GFile) dotref = NULL;
|
|
g_autoptr(GFile) files_etc = NULL;
|
|
g_autoptr(GFile) metadata = NULL;
|
|
g_autoptr(GFile) deploy_data_file = NULL;
|
|
g_autoptr(GVariant) deploy_data = NULL;
|
|
g_autoptr(GFile) export = NULL;
|
|
g_autoptr(GKeyFile) keyfile = NULL;
|
|
guint64 installed_size = 0;
|
|
OstreeRepoCheckoutOptions options = { 0, };
|
|
const char *checksum;
|
|
glnx_fd_close int checkoutdir_dfd = -1;
|
|
g_autoptr(GFile) tmp_dir_template = NULL;
|
|
g_autofree char *tmp_dir_path = NULL;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
|
|
|
if (checksum_or_latest == NULL)
|
|
{
|
|
g_debug ("No checksum specified, getting tip of %s", ref);
|
|
|
|
resolved_ref = flatpak_dir_read_latest (self, origin, ref, cancellable, error);
|
|
if (resolved_ref == NULL)
|
|
{
|
|
g_prefix_error (error, "While trying to resolve ref %s: ", ref);
|
|
return FALSE;
|
|
}
|
|
|
|
checksum = resolved_ref;
|
|
g_debug ("tip resolved to: %s", checksum);
|
|
}
|
|
else
|
|
{
|
|
g_autoptr(GFile) root = NULL;
|
|
g_autofree char *commit = NULL;
|
|
|
|
checksum = checksum_or_latest;
|
|
g_debug ("Looking for checksum %s in local repo", checksum);
|
|
if (!ostree_repo_read_commit (self->repo, checksum, &root, &commit, cancellable, NULL))
|
|
return flatpak_fail (error, "%s is not available", ref);
|
|
}
|
|
|
|
real_checkoutdir = g_file_get_child (deploy_base, checksum);
|
|
if (g_file_query_exists (real_checkoutdir, cancellable))
|
|
{
|
|
g_set_error (error, FLATPAK_ERROR,
|
|
FLATPAK_ERROR_ALREADY_INSTALLED,
|
|
"%s branch %s already installed", ref, checksum);
|
|
return FALSE;
|
|
}
|
|
|
|
g_autofree char *template = g_strdup_printf (".%s-XXXXXX", checksum);
|
|
tmp_dir_template = g_file_get_child (deploy_base, template);
|
|
tmp_dir_path = g_file_get_path (tmp_dir_template);
|
|
|
|
if (g_mkdtemp_full (tmp_dir_path, 0755) == NULL)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't create deploy directory");
|
|
return FALSE;
|
|
}
|
|
|
|
checkoutdir = g_file_new_for_path (tmp_dir_path);
|
|
|
|
if (!ostree_repo_read_commit (self->repo, checksum, &root, NULL, cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Failed to read commit %s: ", checksum);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!flatpak_repo_collect_sizes (self->repo, root, &installed_size, NULL, cancellable, error))
|
|
return FALSE;
|
|
|
|
options.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
|
|
options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
|
|
options.disable_fsync = TRUE; /* We checkout to a temp dir and sync before moving it in place */
|
|
checkoutdirpath = g_file_get_path (checkoutdir);
|
|
|
|
if (subpaths == NULL || *subpaths == NULL)
|
|
{
|
|
if (!ostree_repo_checkout_tree_at (self->repo, &options,
|
|
AT_FDCWD, checkoutdirpath,
|
|
checksum,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "While trying to checkout %s into %s: ", checksum, checkoutdirpath);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_autofree char *checkoutdirpath = g_file_get_path (checkoutdir);
|
|
g_autoptr(GFile) files = g_file_get_child (checkoutdir, "files");
|
|
g_autoptr(GFile) root = NULL;
|
|
g_autofree char *commit = NULL;
|
|
int i;
|
|
|
|
if (!g_file_make_directory_with_parents (files, cancellable, error))
|
|
return FALSE;
|
|
|
|
options.subpath = "/metadata";
|
|
|
|
if (!ostree_repo_read_commit (self->repo, checksum, &root, &commit, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!ostree_repo_checkout_tree_at (self->repo, &options,
|
|
AT_FDCWD, checkoutdirpath,
|
|
checksum,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "While trying to checkout metadata subpath: ");
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; subpaths[i] != NULL; i++)
|
|
{
|
|
g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL);
|
|
g_autofree char *dstpath = g_build_filename (checkoutdirpath, "/files", subpaths[i], NULL);
|
|
g_autofree char *dstpath_parent = g_path_get_dirname (dstpath);
|
|
g_autoptr(GFile) child = NULL;
|
|
|
|
child = g_file_resolve_relative_path (root, subpath);
|
|
|
|
if (!g_file_query_exists (child, cancellable))
|
|
{
|
|
g_debug ("subpath %s not in tree", subpaths[i]);
|
|
continue;
|
|
}
|
|
|
|
if (g_mkdir_with_parents (dstpath_parent, 0755))
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
options.subpath = subpath;
|
|
if (!ostree_repo_checkout_tree_at (self->repo, &options,
|
|
AT_FDCWD, dstpath,
|
|
checksum,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "While trying to checkout metadata subpath: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
dotref = g_file_resolve_relative_path (checkoutdir, "files/.ref");
|
|
if (!g_file_replace_contents (dotref, "", 0, NULL, FALSE,
|
|
G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error))
|
|
return TRUE;
|
|
|
|
/* Ensure that various files exists as regular files in /usr/etc, as we
|
|
want to bind-mount over them */
|
|
files_etc = g_file_resolve_relative_path (checkoutdir, "files/etc");
|
|
if (g_file_query_exists (files_etc, cancellable))
|
|
{
|
|
char *etcfiles[] = {"passwd", "group", "machine-id" };
|
|
g_autoptr(GFile) etc_resolve_conf = g_file_get_child (files_etc, "resolv.conf");
|
|
int i;
|
|
for (i = 0; i < G_N_ELEMENTS (etcfiles); i++)
|
|
{
|
|
g_autoptr(GFile) etc_file = g_file_get_child (files_etc, etcfiles[i]);
|
|
GFileType type;
|
|
|
|
type = g_file_query_file_type (etc_file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable);
|
|
if (type == G_FILE_TYPE_REGULAR)
|
|
continue;
|
|
|
|
if (type != G_FILE_TYPE_UNKNOWN)
|
|
{
|
|
/* Already exists, but not regular, probably symlink. Remove it */
|
|
if (!g_file_delete (etc_file, cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_file_replace_contents (etc_file, "", 0, NULL, FALSE,
|
|
G_FILE_CREATE_REPLACE_DESTINATION,
|
|
NULL, cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_file_query_exists (etc_resolve_conf, cancellable) &&
|
|
!g_file_delete (etc_resolve_conf, cancellable, error))
|
|
return TRUE;
|
|
|
|
if (!g_file_make_symbolic_link (etc_resolve_conf,
|
|
"/run/host/monitor/resolv.conf",
|
|
cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
keyfile = g_key_file_new ();
|
|
metadata = g_file_get_child (checkoutdir, "metadata");
|
|
if (g_file_query_exists (metadata, cancellable))
|
|
{
|
|
g_autofree char *path = g_file_get_path (metadata);
|
|
|
|
if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, error))
|
|
return FALSE;
|
|
}
|
|
|
|
export = g_file_get_child (checkoutdir, "export");
|
|
if (g_file_query_exists (export, cancellable))
|
|
{
|
|
g_auto(GStrv) ref_parts = NULL;
|
|
|
|
ref_parts = g_strsplit (ref, "/", -1);
|
|
|
|
if (!flatpak_rewrite_export_dir (ref_parts[1], ref_parts[3], ref_parts[2],
|
|
keyfile, export,
|
|
cancellable,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
deploy_data = flatpak_dir_new_deploy_data (origin,
|
|
checksum,
|
|
(char **) subpaths,
|
|
installed_size,
|
|
NULL);
|
|
|
|
deploy_data_file = g_file_get_child (checkoutdir, "deploy");
|
|
if (!flatpak_variant_save (deploy_data_file, deploy_data, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!glnx_opendirat (AT_FDCWD, checkoutdirpath, TRUE, &checkoutdir_dfd, error))
|
|
return FALSE;
|
|
|
|
if (syncfs (checkoutdir_dfd) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_file_move (checkoutdir, real_checkoutdir, G_FILE_COPY_NO_FALLBACK_FOR_MOVE,
|
|
cancellable, NULL, NULL, error))
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_set_active (self, ref, checksum, cancellable, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_deploy_install (FlatpakDir *self,
|
|
const char *ref,
|
|
const char *origin,
|
|
const char **subpaths,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT;
|
|
g_autoptr(GFile) deploy_base = NULL;
|
|
g_autoptr(GFile) old_deploy_dir = NULL;
|
|
gboolean created_deploy_base = FALSE;
|
|
gboolean ret = FALSE;
|
|
g_autoptr(GError) local_error = NULL;
|
|
g_auto(GStrv) ref_parts = g_strsplit (ref, "/", -1);
|
|
|
|
if (!flatpak_dir_lock (self, &lock,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
old_deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable);
|
|
if (old_deploy_dir != NULL)
|
|
{
|
|
g_set_error (error, FLATPAK_ERROR,
|
|
FLATPAK_ERROR_ALREADY_INSTALLED,
|
|
"%s branch %s already installed",
|
|
ref_parts[1], ref_parts[3]);
|
|
goto out;
|
|
}
|
|
|
|
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
|
if (!g_file_make_directory_with_parents (deploy_base, cancellable, &local_error))
|
|
{
|
|
if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&local_error));
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* After we create the deploy base we must goto out on errors */
|
|
created_deploy_base = TRUE;
|
|
|
|
if (!flatpak_dir_deploy (self, origin, ref, NULL, (const char * const *) subpaths, NULL, cancellable, error))
|
|
goto out;
|
|
|
|
if (g_str_has_prefix (ref, "app/"))
|
|
{
|
|
|
|
if (!flatpak_dir_make_current_ref (self, ref, cancellable, error))
|
|
goto out;
|
|
|
|
if (!flatpak_dir_update_exports (self, ref_parts[1], cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
/* Release lock before doing possibly slow prune */
|
|
glnx_release_lock_file (&lock);
|
|
|
|
flatpak_dir_cleanup_removed (self, cancellable, NULL);
|
|
|
|
if (!flatpak_dir_mark_changed (self, error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (created_deploy_base && !ret)
|
|
gs_shutil_rm_rf (deploy_base, cancellable, NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
gboolean
|
|
flatpak_dir_deploy_update (FlatpakDir *self,
|
|
const char *ref,
|
|
const char *checksum_or_latest,
|
|
const char **opt_subpaths,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GError) my_error = NULL;
|
|
g_autoptr(GVariant) old_deploy_data = NULL;
|
|
g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT;
|
|
g_autofree const char **old_subpaths = NULL;
|
|
const char *old_active;
|
|
const char *old_origin;
|
|
|
|
if (!flatpak_dir_lock (self, &lock,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
old_deploy_data = flatpak_dir_get_deploy_data (self, ref,
|
|
cancellable, error);
|
|
if (old_deploy_data == NULL)
|
|
return FALSE;
|
|
|
|
old_origin = flatpak_deploy_data_get_origin (old_deploy_data);
|
|
old_active = flatpak_deploy_data_get_commit (old_deploy_data);
|
|
old_subpaths = flatpak_deploy_data_get_subpaths (old_deploy_data);
|
|
if (!flatpak_dir_deploy (self,
|
|
old_origin,
|
|
ref,
|
|
checksum_or_latest,
|
|
opt_subpaths ? opt_subpaths : old_subpaths,
|
|
old_deploy_data,
|
|
cancellable, &my_error))
|
|
{
|
|
if (g_error_matches (my_error, FLATPAK_ERROR,
|
|
FLATPAK_ERROR_ALREADY_INSTALLED))
|
|
return TRUE;
|
|
|
|
g_propagate_error (error, my_error);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!flatpak_dir_undeploy (self, ref, old_active,
|
|
FALSE,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
if (g_str_has_prefix (ref, "app/"))
|
|
{
|
|
g_auto(GStrv) ref_parts = g_strsplit (ref, "/", -1);
|
|
|
|
if (!flatpak_dir_update_exports (self, ref_parts[1], cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* Release lock before doing possibly slow prune */
|
|
glnx_release_lock_file (&lock);
|
|
|
|
flatpak_dir_prune (self, cancellable, NULL);
|
|
|
|
if (!flatpak_dir_mark_changed (self, error))
|
|
return FALSE;
|
|
|
|
flatpak_dir_cleanup_removed (self, cancellable, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static OstreeRepo *
|
|
flatpak_dir_create_system_child_repo (FlatpakDir *self,
|
|
GLnxLockFile *file_lock,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) cache_dir = NULL;
|
|
g_autoptr(GFile) repo_dir = NULL;
|
|
g_autoptr(GFile) repo_dir_config = NULL;
|
|
g_autoptr(OstreeRepo) repo = NULL;
|
|
g_autofree char *tmpdir_name = NULL;
|
|
g_autoptr(OstreeRepo) new_repo = NULL;
|
|
g_autoptr(GKeyFile) config = NULL;
|
|
|
|
g_assert (!self->user);
|
|
|
|
if (!flatpak_dir_ensure_repo (self, NULL, error))
|
|
return NULL;
|
|
|
|
cache_dir = flatpak_ensure_user_cache_dir_location (error);
|
|
if (cache_dir == NULL)
|
|
return NULL;
|
|
|
|
if (!flatpak_allocate_tmpdir (AT_FDCWD,
|
|
gs_file_get_path_cached (cache_dir),
|
|
"repo-", &tmpdir_name,
|
|
NULL,
|
|
file_lock,
|
|
NULL,
|
|
NULL, error))
|
|
return NULL;
|
|
|
|
repo_dir = g_file_get_child (cache_dir, tmpdir_name);
|
|
|
|
new_repo = ostree_repo_new (repo_dir);
|
|
|
|
repo_dir_config = g_file_get_child (repo_dir, "config");
|
|
if (!g_file_query_exists (repo_dir_config, NULL))
|
|
{
|
|
if (!ostree_repo_create (new_repo,
|
|
OSTREE_REPO_MODE_BARE_USER,
|
|
NULL, error))
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
if (!ostree_repo_open (new_repo, NULL, error))
|
|
return NULL;
|
|
}
|
|
|
|
/* Ensure the config is updated */
|
|
config = ostree_repo_copy_config (new_repo);
|
|
g_key_file_set_string (config, "core", "parent",
|
|
gs_file_get_path_cached (ostree_repo_get_path (self->repo)));
|
|
|
|
if (!ostree_repo_write_config (new_repo, config, error))
|
|
return NULL;
|
|
|
|
/* We need to reopen to apply the parent config */
|
|
repo = system_ostree_repo_new (repo_dir);
|
|
if (!ostree_repo_open (repo, NULL, error))
|
|
return NULL;
|
|
|
|
return g_steal_pointer (&repo);
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_install (FlatpakDir *self,
|
|
gboolean no_pull,
|
|
gboolean no_deploy,
|
|
const char *ref,
|
|
const char *remote_name,
|
|
const char **opt_subpaths,
|
|
OstreeAsyncProgress *progress,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
if (flatpak_dir_use_system_helper (self))
|
|
{
|
|
g_autoptr(OstreeRepo) child_repo = NULL;
|
|
g_auto(GLnxLockFile) child_repo_lock = GLNX_LOCK_FILE_INIT;
|
|
const char *empty_subpaths[] = {NULL};
|
|
const char **subpaths;
|
|
g_autofree char *child_repo_path = NULL;
|
|
FlatpakSystemHelper *system_helper;
|
|
FlatpakHelperDeployFlags helper_flags = 0;
|
|
g_autofree char *url = NULL;
|
|
|
|
system_helper = flatpak_dir_get_system_helper (self);
|
|
g_assert (system_helper != NULL);
|
|
|
|
if (opt_subpaths)
|
|
subpaths = opt_subpaths;
|
|
else
|
|
subpaths = empty_subpaths;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!ostree_repo_remote_get_url (self->repo,
|
|
remote_name,
|
|
&url,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (no_pull)
|
|
{
|
|
/* Do nothing */
|
|
}
|
|
else if (g_str_has_prefix (url, "file:"))
|
|
{
|
|
/* In the local case we let the system-helper do all the work. That way we can trust its
|
|
reading from the right source, and its not doing any network i/o. */
|
|
|
|
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_LOCAL_PULL;
|
|
}
|
|
else
|
|
{
|
|
/* We're pulling from a remote source, we do the network mirroring pull as a
|
|
user and hand back the resulting data to the system-helper, that trusts us
|
|
due to the GPG signatures in the repo */
|
|
|
|
child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, error);
|
|
if (child_repo == NULL)
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_pull (self, remote_name, ref, subpaths,
|
|
child_repo, OSTREE_REPO_PULL_FLAGS_MIRROR, FALSE,
|
|
progress, cancellable, error))
|
|
return FALSE;
|
|
|
|
child_repo_path = g_file_get_path (ostree_repo_get_path (child_repo));
|
|
}
|
|
|
|
if (no_deploy)
|
|
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_NO_DEPLOY;
|
|
|
|
if (!flatpak_system_helper_call_deploy_sync (system_helper,
|
|
child_repo_path ? child_repo_path : "",
|
|
helper_flags, ref, remote_name,
|
|
(const char * const *) subpaths,
|
|
cancellable,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (child_repo_path)
|
|
(void) glnx_shutil_rm_rf_at (AT_FDCWD, child_repo_path, NULL, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (!no_pull)
|
|
{
|
|
if (!flatpak_dir_pull (self, remote_name, ref, opt_subpaths, NULL, OSTREE_REPO_PULL_FLAGS_NONE, FALSE, progress,
|
|
cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (!no_deploy)
|
|
{
|
|
if (!flatpak_dir_deploy_install (self, ref, remote_name, opt_subpaths,
|
|
cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_install_bundle (FlatpakDir *self,
|
|
GFile *file,
|
|
GBytes *extra_gpg_data,
|
|
char **out_ref,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *ref = NULL;
|
|
gboolean added_remote = FALSE;
|
|
g_autoptr(GFile) deploy_dir = NULL;
|
|
g_autoptr(FlatpakDir) dir_clone = NULL;
|
|
g_autoptr(GVariant) metadata = NULL;
|
|
g_autofree char *origin = NULL;
|
|
g_auto(GStrv) parts = NULL;
|
|
g_autofree char *basename = NULL;
|
|
g_autoptr(GBytes) included_gpg_data = NULL;
|
|
GBytes *gpg_data = NULL;
|
|
g_autofree char *to_checksum = NULL;
|
|
g_autofree char *remote = NULL;
|
|
gboolean ret = FALSE;
|
|
|
|
if (flatpak_dir_use_system_helper (self))
|
|
{
|
|
FlatpakSystemHelper *system_helper;
|
|
g_autoptr(GVariant) gpg_data_v = NULL;
|
|
|
|
system_helper = flatpak_dir_get_system_helper (self);
|
|
g_assert (system_helper != NULL);
|
|
|
|
if (gpg_data != NULL)
|
|
gpg_data_v = variant_new_ay_bytes (gpg_data);
|
|
else
|
|
gpg_data_v = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("ay"), "", 0, TRUE, NULL, NULL));
|
|
|
|
if (!flatpak_system_helper_call_install_bundle_sync (system_helper,
|
|
gs_file_get_path_cached (file),
|
|
0, gpg_data_v,
|
|
&ref,
|
|
cancellable,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (out_ref)
|
|
*out_ref = g_steal_pointer (&ref);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
metadata = flatpak_bundle_load (file, &to_checksum,
|
|
&ref,
|
|
&origin,
|
|
NULL,
|
|
&included_gpg_data,
|
|
error);
|
|
if (metadata == NULL)
|
|
return FALSE;
|
|
|
|
gpg_data = extra_gpg_data ? extra_gpg_data : included_gpg_data;
|
|
|
|
parts = flatpak_decompose_ref (ref, error);
|
|
if (parts == NULL)
|
|
return FALSE;
|
|
|
|
deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable);
|
|
if (deploy_dir != NULL)
|
|
{
|
|
g_set_error (error,
|
|
FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
|
|
"%s branch %s already installed", parts[1], parts[3]);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Add a remote for later updates */
|
|
basename = g_file_get_basename (file);
|
|
remote = flatpak_dir_create_origin_remote (self,
|
|
origin,
|
|
parts[1],
|
|
basename,
|
|
gpg_data,
|
|
cancellable,
|
|
error);
|
|
if (remote == NULL)
|
|
return FALSE;
|
|
|
|
/* From here we need to goto out on error, to clean up */
|
|
added_remote = TRUE;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
goto out;
|
|
|
|
if (!flatpak_pull_from_bundle (self->repo,
|
|
file,
|
|
remote,
|
|
ref,
|
|
gpg_data != NULL,
|
|
cancellable,
|
|
error))
|
|
goto out;
|
|
|
|
if (!flatpak_dir_deploy_install (self, ref, remote, NULL, cancellable, error))
|
|
goto out;
|
|
|
|
if (out_ref)
|
|
*out_ref = g_steal_pointer (&ref);
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (added_remote && !ret)
|
|
ostree_repo_remote_delete (self->repo, remote, NULL, NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
gboolean
|
|
flatpak_dir_update (FlatpakDir *self,
|
|
gboolean no_pull,
|
|
gboolean no_deploy,
|
|
const char *ref,
|
|
const char *remote_name,
|
|
const char *checksum_or_latest,
|
|
const char **opt_subpaths,
|
|
OstreeAsyncProgress *progress,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GVariant) deploy_data = NULL;
|
|
g_autofree const char **old_subpaths = NULL;
|
|
const char *empty_subpaths[] = {NULL};
|
|
const char **subpaths;
|
|
|
|
deploy_data = flatpak_dir_get_deploy_data (self, ref,
|
|
cancellable, NULL);
|
|
|
|
if (opt_subpaths)
|
|
{
|
|
subpaths = opt_subpaths;
|
|
}
|
|
else if (deploy_data != NULL)
|
|
{
|
|
old_subpaths = flatpak_deploy_data_get_subpaths (deploy_data);
|
|
subpaths = old_subpaths;
|
|
}
|
|
else
|
|
{
|
|
subpaths = empty_subpaths;
|
|
}
|
|
|
|
if (flatpak_dir_use_system_helper (self))
|
|
{
|
|
g_autoptr(OstreeRepo) child_repo = NULL;
|
|
g_auto(GLnxLockFile) child_repo_lock = GLNX_LOCK_FILE_INIT;
|
|
g_autofree char *latest_checksum = NULL;
|
|
g_autofree char *active_checksum = NULL;
|
|
FlatpakSystemHelper *system_helper;
|
|
g_autofree char *child_repo_path = NULL;
|
|
FlatpakHelperDeployFlags helper_flags = 0;
|
|
g_autofree char *url = NULL;
|
|
|
|
if (checksum_or_latest != NULL)
|
|
return flatpak_fail (error, "Can't update to a specific commit without root permissions");
|
|
|
|
system_helper = flatpak_dir_get_system_helper (self);
|
|
g_assert (system_helper != NULL);
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!ostree_repo_remote_get_url (self->repo,
|
|
remote_name,
|
|
&url,
|
|
error))
|
|
return FALSE;
|
|
|
|
helper_flags = FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE;
|
|
|
|
if (no_pull)
|
|
{
|
|
if (!ostree_repo_resolve_rev (self->repo, ref, FALSE, &latest_checksum, error))
|
|
return FALSE;
|
|
}
|
|
else if (g_str_has_prefix (url, "file:"))
|
|
{
|
|
/* In the local case we let the system-helper do all the work. That way we can trust its
|
|
reading from the right source, and its not doing any network i/o. */
|
|
|
|
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_LOCAL_PULL;
|
|
}
|
|
else
|
|
{
|
|
/* We're pulling from a remote source, we do the network mirroring pull as a
|
|
user and hand back the resulting data to the system-helper, that trusts us
|
|
due to the GPG signatures in the repo */
|
|
|
|
child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, error);
|
|
if (child_repo == NULL)
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_pull (self, remote_name, ref, subpaths,
|
|
child_repo, OSTREE_REPO_PULL_FLAGS_MIRROR,
|
|
/* TODO: forcing no-deltas due to issue #144 */ TRUE,
|
|
progress, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!ostree_repo_resolve_rev (child_repo, ref, FALSE, &latest_checksum, error))
|
|
return FALSE;
|
|
|
|
child_repo_path = g_file_get_path (ostree_repo_get_path (child_repo));
|
|
}
|
|
|
|
if (no_deploy)
|
|
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_NO_DEPLOY;
|
|
|
|
active_checksum = flatpak_dir_read_active (self, ref, NULL);
|
|
if (g_strcmp0 (active_checksum, latest_checksum) != 0)
|
|
{
|
|
if (!flatpak_system_helper_call_deploy_sync (system_helper,
|
|
child_repo_path ? child_repo_path : "",
|
|
helper_flags, ref, remote_name,
|
|
subpaths,
|
|
cancellable,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (child_repo_path)
|
|
(void) glnx_shutil_rm_rf_at (AT_FDCWD, child_repo_path, NULL, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (!no_pull)
|
|
{
|
|
if (!flatpak_dir_pull (self, remote_name, ref, subpaths,
|
|
NULL, OSTREE_REPO_PULL_FLAGS_NONE, FALSE, progress,
|
|
cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (!no_deploy)
|
|
{
|
|
if (!flatpak_dir_deploy_update (self, ref, checksum_or_latest, subpaths,
|
|
cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_uninstall (FlatpakDir *self,
|
|
const char *ref,
|
|
FlatpakHelperUninstallFlags flags,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
const char *repository;
|
|
g_autofree char *current_ref = NULL;
|
|
gboolean was_deployed;
|
|
gboolean is_app;
|
|
const char *name;
|
|
g_auto(GStrv) parts = NULL;
|
|
g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT;
|
|
g_autoptr(GVariant) deploy_data = NULL;
|
|
gboolean keep_ref = flags & FLATPAK_HELPER_UNINSTALL_FLAGS_KEEP_REF;
|
|
gboolean force_remove = flags & FLATPAK_HELPER_UNINSTALL_FLAGS_FORCE_REMOVE;
|
|
|
|
parts = flatpak_decompose_ref (ref, error);
|
|
if (parts == NULL)
|
|
return FALSE;
|
|
name = parts[1];
|
|
|
|
if (flatpak_dir_use_system_helper (self))
|
|
{
|
|
FlatpakSystemHelper *system_helper;
|
|
|
|
system_helper = flatpak_dir_get_system_helper (self);
|
|
g_assert (system_helper != NULL);
|
|
|
|
if (!flatpak_system_helper_call_uninstall_sync (system_helper,
|
|
flags, ref,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (!flatpak_dir_lock (self, &lock,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
deploy_data = flatpak_dir_get_deploy_data (self, ref,
|
|
cancellable, error);
|
|
if (deploy_data == NULL)
|
|
return FALSE;
|
|
|
|
repository = flatpak_deploy_data_get_origin (deploy_data);
|
|
if (repository == NULL)
|
|
return FALSE;
|
|
|
|
g_debug ("dropping active ref");
|
|
if (!flatpak_dir_set_active (self, ref, NULL, cancellable, error))
|
|
return FALSE;
|
|
|
|
is_app = g_str_has_prefix (ref, "app/");
|
|
if (is_app)
|
|
{
|
|
current_ref = flatpak_dir_current_ref (self, name, cancellable);
|
|
if (g_strcmp0 (ref, current_ref) == 0)
|
|
{
|
|
g_debug ("dropping current ref");
|
|
if (!flatpak_dir_drop_current_ref (self, name, cancellable, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (!flatpak_dir_undeploy_all (self, ref, force_remove, &was_deployed, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!keep_ref &&
|
|
!flatpak_dir_remove_ref (self, repository, ref, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (is_app &&
|
|
!flatpak_dir_update_exports (self, name, cancellable, error))
|
|
return FALSE;
|
|
|
|
glnx_release_lock_file (&lock);
|
|
|
|
if (repository != NULL &&
|
|
g_str_has_suffix (repository, "-origin") &&
|
|
flatpak_dir_get_remote_noenumerate (self, repository))
|
|
ostree_repo_remote_delete (self->repo, repository, NULL, NULL);
|
|
|
|
if (!keep_ref)
|
|
flatpak_dir_prune (self, cancellable, NULL);
|
|
|
|
flatpak_dir_cleanup_removed (self, cancellable, NULL);
|
|
|
|
if (!flatpak_dir_mark_changed (self, error))
|
|
return FALSE;
|
|
|
|
if (!was_deployed)
|
|
{
|
|
g_set_error (error,
|
|
FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
|
|
"%s branch %s is not installed", name, parts[3]);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_collect_deployed_refs (FlatpakDir *self,
|
|
const char *type,
|
|
const char *name_prefix,
|
|
const char *branch,
|
|
const char *arch,
|
|
GHashTable *hash,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_autoptr(GFile) dir = NULL;
|
|
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
|
g_autoptr(GFileInfo) child_info = NULL;
|
|
GError *temp_error = NULL;
|
|
|
|
dir = g_file_get_child (self->basedir, type);
|
|
if (!g_file_query_exists (dir, cancellable))
|
|
return TRUE;
|
|
|
|
dir_enum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable,
|
|
error);
|
|
if (!dir_enum)
|
|
goto out;
|
|
|
|
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
|
|
{
|
|
const char *name = g_file_info_get_name (child_info);
|
|
|
|
if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY &&
|
|
name[0] != '.' && (name_prefix == NULL || g_str_has_prefix (name, name_prefix)))
|
|
{
|
|
g_autoptr(GFile) child1 = g_file_get_child (dir, name);
|
|
g_autoptr(GFile) child2 = g_file_get_child (child1, branch);
|
|
g_autoptr(GFile) child3 = g_file_get_child (child2, arch);
|
|
g_autoptr(GFile) active = g_file_get_child (child3, "active");
|
|
|
|
if (g_file_query_exists (active, cancellable))
|
|
g_hash_table_add (hash, g_strdup (name));
|
|
}
|
|
|
|
g_clear_object (&child_info);
|
|
}
|
|
|
|
if (temp_error != NULL)
|
|
{
|
|
g_propagate_error (error, temp_error);
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_list_deployed (FlatpakDir *self,
|
|
const char *ref,
|
|
char ***deployed_checksums,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_autoptr(GFile) deploy_base = NULL;
|
|
g_autoptr(GPtrArray) checksums = NULL;
|
|
GError *temp_error = NULL;
|
|
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
|
g_autoptr(GFile) child = NULL;
|
|
g_autoptr(GFileInfo) child_info = NULL;
|
|
g_autoptr(GError) my_error = NULL;
|
|
|
|
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
|
|
|
checksums = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
dir_enum = g_file_enumerate_children (deploy_base, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable,
|
|
&my_error);
|
|
if (!dir_enum)
|
|
{
|
|
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
ret = TRUE; /* Success, but empty */
|
|
else
|
|
g_propagate_error (error, g_steal_pointer (&my_error));
|
|
goto out;
|
|
}
|
|
|
|
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
|
|
{
|
|
const char *name;
|
|
|
|
name = g_file_info_get_name (child_info);
|
|
|
|
g_clear_object (&child);
|
|
child = g_file_get_child (deploy_base, name);
|
|
|
|
if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY &&
|
|
name[0] != '.' &&
|
|
strlen (name) == 64)
|
|
g_ptr_array_add (checksums, g_strdup (name));
|
|
|
|
g_clear_object (&child_info);
|
|
}
|
|
|
|
if (temp_error != NULL)
|
|
{
|
|
g_propagate_error (error, temp_error);
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (ret)
|
|
{
|
|
g_ptr_array_add (checksums, NULL);
|
|
*deployed_checksums = (char **) g_ptr_array_free (g_steal_pointer (&checksums), FALSE);
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
static gboolean
|
|
dir_is_locked (GFile *dir)
|
|
{
|
|
glnx_fd_close int ref_fd = -1;
|
|
struct flock lock = {0};
|
|
|
|
g_autoptr(GFile) reffile = NULL;
|
|
|
|
reffile = g_file_resolve_relative_path (dir, "files/.ref");
|
|
|
|
ref_fd = open (gs_file_get_path_cached (reffile), O_RDWR | O_CLOEXEC);
|
|
if (ref_fd != -1)
|
|
{
|
|
lock.l_type = F_WRLCK;
|
|
lock.l_whence = SEEK_SET;
|
|
lock.l_start = 0;
|
|
lock.l_len = 0;
|
|
|
|
if (fcntl (ref_fd, F_GETLK, &lock) == 0)
|
|
return lock.l_type != F_UNLCK;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_undeploy (FlatpakDir *self,
|
|
const char *ref,
|
|
const char *checksum,
|
|
gboolean force_remove,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_autoptr(GFile) deploy_base = NULL;
|
|
g_autoptr(GFile) checkoutdir = NULL;
|
|
g_autoptr(GFile) removed_subdir = NULL;
|
|
g_autoptr(GFile) removed_dir = NULL;
|
|
g_autofree char *tmpname = NULL;
|
|
g_autofree char *active = NULL;
|
|
int i;
|
|
|
|
g_assert (ref != NULL);
|
|
g_assert (checksum != NULL);
|
|
|
|
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
|
|
|
checkoutdir = g_file_get_child (deploy_base, checksum);
|
|
if (!g_file_query_exists (checkoutdir, cancellable))
|
|
{
|
|
g_set_error (error, FLATPAK_ERROR,
|
|
FLATPAK_ERROR_NOT_INSTALLED,
|
|
"%s branch %s not installed", ref, checksum);
|
|
goto out;
|
|
}
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
goto out;
|
|
|
|
active = flatpak_dir_read_active (self, ref, cancellable);
|
|
if (active != NULL && strcmp (active, checksum) == 0)
|
|
{
|
|
g_auto(GStrv) deployed_checksums = NULL;
|
|
const char *some_deployment;
|
|
|
|
/* We're removing the active deployment, start by repointing that
|
|
to another deployment if one exists */
|
|
|
|
if (!flatpak_dir_list_deployed (self, ref,
|
|
&deployed_checksums,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
some_deployment = NULL;
|
|
for (i = 0; deployed_checksums[i] != NULL; i++)
|
|
{
|
|
if (strcmp (deployed_checksums[i], checksum) == 0)
|
|
continue;
|
|
|
|
some_deployment = deployed_checksums[i];
|
|
break;
|
|
}
|
|
|
|
if (!flatpak_dir_set_active (self, ref, some_deployment, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
removed_dir = flatpak_dir_get_removed_dir (self);
|
|
if (!gs_file_ensure_directory (removed_dir, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
tmpname = gs_fileutil_gen_tmp_name ("", checksum);
|
|
removed_subdir = g_file_get_child (removed_dir, tmpname);
|
|
|
|
if (!gs_file_rename (checkoutdir,
|
|
removed_subdir,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
if (force_remove || !dir_is_locked (removed_subdir))
|
|
{
|
|
GError *tmp_error = NULL;
|
|
|
|
if (!gs_shutil_rm_rf (removed_subdir, cancellable, &tmp_error))
|
|
{
|
|
g_warning ("Unable to remove old checkout: %s\n", tmp_error->message);
|
|
g_error_free (tmp_error);
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_undeploy_all (FlatpakDir *self,
|
|
const char *ref,
|
|
gboolean force_remove,
|
|
gboolean *was_deployed_out,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_auto(GStrv) deployed = NULL;
|
|
g_autoptr(GFile) deploy_base = NULL;
|
|
g_autoptr(GFile) arch_dir = NULL;
|
|
g_autoptr(GFile) top_dir = NULL;
|
|
GError *temp_error = NULL;
|
|
int i;
|
|
gboolean was_deployed;
|
|
|
|
if (!flatpak_dir_list_deployed (self, ref, &deployed, cancellable, error))
|
|
return FALSE;
|
|
|
|
for (i = 0; deployed[i] != NULL; i++)
|
|
{
|
|
g_debug ("undeploying %s", deployed[i]);
|
|
if (!flatpak_dir_undeploy (self, ref, deployed[i], force_remove, cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
|
was_deployed = g_file_query_exists (deploy_base, cancellable);
|
|
if (was_deployed)
|
|
{
|
|
g_debug ("removing deploy base");
|
|
if (!gs_shutil_rm_rf (deploy_base, cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
g_debug ("cleaning up empty directories");
|
|
arch_dir = g_file_get_parent (deploy_base);
|
|
if (g_file_query_exists (arch_dir, cancellable) &&
|
|
!g_file_delete (arch_dir, cancellable, &temp_error))
|
|
{
|
|
if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY))
|
|
{
|
|
g_propagate_error (error, temp_error);
|
|
return FALSE;
|
|
}
|
|
g_clear_error (&temp_error);
|
|
}
|
|
|
|
top_dir = g_file_get_parent (arch_dir);
|
|
if (g_file_query_exists (top_dir, cancellable) &&
|
|
!g_file_delete (top_dir, cancellable, &temp_error))
|
|
{
|
|
if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY))
|
|
{
|
|
g_propagate_error (error, temp_error);
|
|
return FALSE;
|
|
}
|
|
g_clear_error (&temp_error);
|
|
}
|
|
|
|
if (was_deployed_out)
|
|
*was_deployed_out = was_deployed;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_remove_ref (FlatpakDir *self,
|
|
const char *remote_name,
|
|
const char *ref,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
if (!ostree_repo_set_ref_immediate (self->repo, remote_name, ref, NULL, cancellable, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_cleanup_removed (FlatpakDir *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
g_autoptr(GFile) removed_dir = NULL;
|
|
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
|
g_autoptr(GFileInfo) child_info = NULL;
|
|
GError *temp_error = NULL;
|
|
|
|
removed_dir = flatpak_dir_get_removed_dir (self);
|
|
if (!g_file_query_exists (removed_dir, cancellable))
|
|
return TRUE;
|
|
|
|
dir_enum = g_file_enumerate_children (removed_dir, OSTREE_GIO_FAST_QUERYINFO,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
cancellable,
|
|
error);
|
|
if (!dir_enum)
|
|
goto out;
|
|
|
|
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
|
|
{
|
|
const char *name = g_file_info_get_name (child_info);
|
|
g_autoptr(GFile) child = g_file_get_child (removed_dir, name);
|
|
|
|
if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY &&
|
|
!dir_is_locked (child))
|
|
{
|
|
GError *tmp_error = NULL;
|
|
if (!gs_shutil_rm_rf (child, cancellable, &tmp_error))
|
|
{
|
|
g_warning ("Unable to remove old checkout: %s\n", tmp_error->message);
|
|
g_error_free (tmp_error);
|
|
}
|
|
}
|
|
|
|
g_clear_object (&child_info);
|
|
}
|
|
|
|
if (temp_error != NULL)
|
|
{
|
|
g_propagate_error (error, temp_error);
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_prune (FlatpakDir *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
gint objects_total, objects_pruned;
|
|
guint64 pruned_object_size_total;
|
|
g_autofree char *formatted_freed_size = NULL;
|
|
g_autoptr(GError) local_error = NULL;
|
|
|
|
if (error == NULL)
|
|
error = &local_error;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
goto out;
|
|
|
|
if (!ostree_repo_prune (self->repo,
|
|
OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY,
|
|
0,
|
|
&objects_total,
|
|
&objects_pruned,
|
|
&pruned_object_size_total,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
formatted_freed_size = g_format_size_full (pruned_object_size_total, 0);
|
|
g_debug ("Pruned %d/%d objects, size %s", objects_total, objects_pruned, formatted_freed_size);
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
|
|
/* There was an issue in ostree where for local pulls we don't get a .commitpartial (now fixed),
|
|
which caused errors when pruning. We print these here, but don't stop processing. */
|
|
if (local_error != NULL)
|
|
g_print ("Pruning repo failed: %s", local_error->message);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
GFile *
|
|
flatpak_dir_get_if_deployed (FlatpakDir *self,
|
|
const char *ref,
|
|
const char *checksum,
|
|
GCancellable *cancellable)
|
|
{
|
|
g_autoptr(GFile) deploy_base = NULL;
|
|
g_autoptr(GFile) deploy_dir = NULL;
|
|
|
|
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
|
|
|
if (checksum != NULL)
|
|
{
|
|
deploy_dir = g_file_get_child (deploy_base, checksum);
|
|
}
|
|
else
|
|
{
|
|
g_autoptr(GFile) active_link = g_file_get_child (deploy_base, "active");
|
|
g_autoptr(GFileInfo) info = NULL;
|
|
const char *target;
|
|
|
|
info = g_file_query_info (active_link,
|
|
G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
NULL,
|
|
NULL);
|
|
if (info == NULL)
|
|
return NULL;
|
|
|
|
target = g_file_info_get_symlink_target (info);
|
|
if (target == NULL)
|
|
return NULL;
|
|
|
|
deploy_dir = g_file_get_child (deploy_base, target);
|
|
}
|
|
|
|
if (g_file_query_file_type (deploy_dir, G_FILE_QUERY_INFO_NONE, cancellable) == G_FILE_TYPE_DIRECTORY)
|
|
return g_object_ref (deploy_dir);
|
|
return NULL;
|
|
}
|
|
|
|
G_LOCK_DEFINE_STATIC (cache);
|
|
|
|
static void
|
|
cached_summary_free (CachedSummary *summary)
|
|
{
|
|
g_bytes_unref (summary->bytes);
|
|
g_free (summary->remote);
|
|
g_free (summary->url);
|
|
g_free (summary);
|
|
}
|
|
|
|
static CachedSummary *
|
|
cached_summary_new (GBytes *bytes,
|
|
const char *remote,
|
|
const char *url)
|
|
{
|
|
CachedSummary *summary = g_new0 (CachedSummary, 1);
|
|
summary->bytes = g_bytes_ref (bytes);
|
|
summary->url = g_strdup (url);
|
|
summary->remote = g_strdup (remote);
|
|
summary->time = g_get_monotonic_time ();
|
|
return summary;
|
|
}
|
|
|
|
static GBytes *
|
|
flatpak_dir_lookup_cached_summary (FlatpakDir *self,
|
|
const char *name,
|
|
const char *url)
|
|
{
|
|
CachedSummary *summary;
|
|
GBytes *res = NULL;
|
|
|
|
G_LOCK (cache);
|
|
|
|
if (self->summary_cache == NULL)
|
|
self->summary_cache = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)cached_summary_free);
|
|
|
|
summary = g_hash_table_lookup (self->summary_cache, name);
|
|
if (summary)
|
|
{
|
|
guint64 now = g_get_monotonic_time ();
|
|
if ((now - summary->time) < (1000 * (SUMMARY_CACHE_TIMEOUT_SEC)) &&
|
|
strcmp (url, summary->url) == 0)
|
|
{
|
|
g_debug ("Using cached summary for remote %s", name);
|
|
res = g_bytes_ref (summary->bytes);
|
|
}
|
|
}
|
|
|
|
G_UNLOCK (cache);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
flatpak_dir_cache_summary (FlatpakDir *self,
|
|
GBytes *bytes,
|
|
const char *name,
|
|
const char *url)
|
|
{
|
|
CachedSummary *summary;
|
|
|
|
/* No sense caching the summary if there isn't one */
|
|
if (!bytes)
|
|
return;
|
|
|
|
G_LOCK (cache);
|
|
|
|
/* This was already initialized in the cache-miss lookup */
|
|
g_assert (self->summary_cache != NULL);
|
|
|
|
summary = cached_summary_new (bytes, name, url);
|
|
g_hash_table_insert (self->summary_cache, summary->remote, summary);
|
|
|
|
G_UNLOCK (cache);
|
|
}
|
|
|
|
static gboolean
|
|
flatpak_dir_remote_fetch_summary (FlatpakDir *self,
|
|
const char *name,
|
|
GBytes **out_summary,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *url = NULL;
|
|
gboolean is_local;
|
|
|
|
if (!ostree_repo_remote_get_url (self->repo, name, &url, error))
|
|
return FALSE;
|
|
|
|
is_local = g_str_has_prefix (url, "file:");
|
|
|
|
/* No caching for local files */
|
|
if (!is_local)
|
|
{
|
|
GBytes *cached_summary = flatpak_dir_lookup_cached_summary (self, name, url);
|
|
if (cached_summary)
|
|
{
|
|
*out_summary = cached_summary;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (!ostree_repo_remote_fetch_summary (self->repo, name,
|
|
out_summary, NULL,
|
|
cancellable,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (!is_local)
|
|
flatpak_dir_cache_summary (self, *out_summary, name, url);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* This duplicates ostree_repo_list_refs so it can use flatpak_dir_remote_fetch_summary
|
|
and get caching */
|
|
static gboolean
|
|
flatpak_dir_remote_list_refs (FlatpakDir *self,
|
|
const char *remote_name,
|
|
GHashTable **out_all_refs,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GBytes) summary_bytes = NULL;
|
|
g_autoptr(GHashTable) ret_all_refs = NULL;
|
|
g_autoptr(GVariant) summary = NULL;
|
|
g_autoptr(GVariant) ref_map = NULL;
|
|
GVariantIter iter;
|
|
GVariant *child;
|
|
|
|
if (!flatpak_dir_remote_fetch_summary (self, remote_name,
|
|
&summary_bytes,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
if (summary_bytes == NULL)
|
|
return flatpak_fail (error, "Remote listing not available; server has no summary file\n" \
|
|
"Check the URL passed to remote-add was valid\n");
|
|
|
|
ret_all_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT,
|
|
summary_bytes, FALSE);
|
|
|
|
ref_map = g_variant_get_child_value (summary, 0);
|
|
|
|
g_variant_iter_init (&iter, ref_map);
|
|
while ((child = g_variant_iter_next_value (&iter)) != NULL)
|
|
{
|
|
const char *ref_name = NULL;
|
|
g_autoptr(GVariant) csum_v = NULL;
|
|
char tmp_checksum[65];
|
|
|
|
g_variant_get_child (child, 0, "&s", &ref_name);
|
|
|
|
if (ref_name != NULL)
|
|
{
|
|
const guchar *csum_bytes;
|
|
|
|
g_variant_get_child (child, 1, "(t@aya{sv})", NULL, &csum_v, NULL);
|
|
csum_bytes = ostree_checksum_bytes_peek_validate (csum_v, error);
|
|
if (csum_bytes == NULL)
|
|
return FALSE;
|
|
|
|
ostree_checksum_inplace_from_bytes (csum_bytes, tmp_checksum);
|
|
|
|
g_hash_table_insert (ret_all_refs,
|
|
g_strdup (ref_name),
|
|
g_strdup (tmp_checksum));
|
|
}
|
|
|
|
g_variant_unref (child);
|
|
}
|
|
|
|
|
|
*out_all_refs = g_steal_pointer (&ret_all_refs);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GPtrArray *
|
|
find_matching_refs (GHashTable *refs,
|
|
const char *opt_name,
|
|
const char *opt_branch,
|
|
const char *opt_arch,
|
|
gboolean app,
|
|
gboolean runtime,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) matched_refs = NULL;
|
|
const char **arches = flatpak_get_arches ();
|
|
const char *opt_arches[] = {opt_arch, NULL};
|
|
GHashTableIter hash_iter;
|
|
gpointer key;
|
|
|
|
if (opt_arch != NULL)
|
|
arches = opt_arches;
|
|
|
|
if (opt_name && !flatpak_is_valid_name (opt_name))
|
|
{
|
|
flatpak_fail (error, "'%s' is not a valid name", opt_name);
|
|
return NULL;
|
|
}
|
|
|
|
if (opt_branch && !flatpak_is_valid_branch (opt_branch))
|
|
{
|
|
flatpak_fail (error, "'%s' is not a valid branch name", opt_branch);
|
|
return NULL;
|
|
}
|
|
|
|
matched_refs = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
g_hash_table_iter_init (&hash_iter, refs);
|
|
while (g_hash_table_iter_next (&hash_iter, &key, NULL))
|
|
{
|
|
g_autofree char *ref = NULL;
|
|
g_auto(GStrv) parts = NULL;
|
|
gboolean is_app, is_runtime;
|
|
|
|
/* Unprefix any remote name if needed */
|
|
ostree_parse_refspec (key, NULL, &ref, NULL);
|
|
if (ref == NULL)
|
|
continue;
|
|
|
|
is_app = g_str_has_prefix (ref, "app/");
|
|
is_runtime = g_str_has_prefix (ref, "runtime/");
|
|
|
|
if ((!app && is_app) ||
|
|
(!runtime && is_runtime) ||
|
|
(!is_app && !is_runtime))
|
|
continue;
|
|
|
|
parts = flatpak_decompose_ref (ref, NULL);
|
|
if (parts == NULL)
|
|
continue;
|
|
|
|
if (opt_name != NULL && strcmp (opt_name, parts[1]) != 0)
|
|
continue;
|
|
|
|
if (!g_strv_contains (arches, parts[2]))
|
|
continue;
|
|
|
|
if (opt_branch != NULL && strcmp (opt_branch, parts[3]) != 0)
|
|
continue;
|
|
|
|
g_ptr_array_add (matched_refs, g_steal_pointer (&ref));
|
|
}
|
|
|
|
return g_steal_pointer (&matched_refs);
|
|
}
|
|
|
|
|
|
static char *
|
|
find_matching_ref (GHashTable *refs,
|
|
const char *name,
|
|
const char *opt_branch,
|
|
const char *opt_arch,
|
|
gboolean app,
|
|
gboolean runtime,
|
|
GError **error)
|
|
{
|
|
const char **arches = flatpak_get_arches ();
|
|
const char *opt_arches[] = {opt_arch, NULL};
|
|
int i;
|
|
|
|
if (opt_arch != NULL)
|
|
arches = opt_arches;
|
|
|
|
/* We stop at the first arch (in prio order) that has a match */
|
|
for (i = 0; arches[i] != NULL; i++)
|
|
{
|
|
g_autoptr(GPtrArray) matched_refs = NULL;
|
|
|
|
matched_refs = find_matching_refs (refs, name, opt_branch, arches[i],
|
|
app, runtime, error);
|
|
if (matched_refs == NULL)
|
|
return NULL;
|
|
|
|
if (matched_refs->len == 0)
|
|
continue;
|
|
|
|
if (matched_refs->len > 1)
|
|
{
|
|
int i;
|
|
g_autoptr(GString) err = g_string_new ("");
|
|
g_string_printf (err, "Multiple branches available for %s, you must specify one of: ", name);
|
|
for (i = 0; i < matched_refs->len; i++)
|
|
{
|
|
g_auto(GStrv) parts = flatpak_decompose_ref (g_ptr_array_index (matched_refs, i), NULL);
|
|
if (i != 0)
|
|
g_string_append (err, ", ");
|
|
|
|
g_string_append (err, parts[3]);
|
|
}
|
|
|
|
flatpak_fail (error, err->str);
|
|
return NULL;
|
|
}
|
|
|
|
return g_strdup (g_ptr_array_index (matched_refs, 0));
|
|
}
|
|
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"Nothing matches %s", name);
|
|
return NULL;
|
|
}
|
|
|
|
char **
|
|
flatpak_dir_find_remote_refs (FlatpakDir *self,
|
|
const char *remote,
|
|
const char *name,
|
|
const char *opt_branch,
|
|
const char *opt_arch,
|
|
gboolean app,
|
|
gboolean runtime,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *refspec_prefix = NULL;
|
|
g_autoptr(GHashTable) remote_refs = NULL;
|
|
GPtrArray *matched_refs;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, NULL, error))
|
|
return NULL;
|
|
|
|
refspec_prefix = g_strconcat (remote, ":.", NULL);
|
|
|
|
if (!flatpak_dir_remote_list_refs (self, remote,
|
|
&remote_refs, cancellable, error))
|
|
return NULL;
|
|
|
|
matched_refs = find_matching_refs (remote_refs, name, opt_branch,
|
|
opt_arch, app, runtime, error);
|
|
if (matched_refs == NULL)
|
|
return NULL;
|
|
|
|
g_ptr_array_add (matched_refs, NULL);
|
|
return (char **)g_ptr_array_free (matched_refs, FALSE);
|
|
}
|
|
|
|
char *
|
|
flatpak_dir_find_remote_ref (FlatpakDir *self,
|
|
const char *remote,
|
|
const char *name,
|
|
const char *opt_branch,
|
|
const char *opt_arch,
|
|
gboolean app,
|
|
gboolean runtime,
|
|
gboolean *is_app,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *refspec_prefix = NULL;
|
|
g_autofree char *remote_ref = NULL;
|
|
g_autoptr(GHashTable) remote_refs = NULL;
|
|
g_autoptr(GError) my_error = NULL;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, NULL, error))
|
|
return NULL;
|
|
|
|
refspec_prefix = g_strconcat (remote, ":.", NULL);
|
|
|
|
if (!flatpak_dir_remote_list_refs (self, remote,
|
|
&remote_refs, cancellable, error))
|
|
return NULL;
|
|
|
|
remote_ref = find_matching_ref (remote_refs, name, opt_branch,
|
|
opt_arch, app, runtime, &my_error);
|
|
if (remote_ref == NULL)
|
|
{
|
|
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
g_clear_error (&my_error);
|
|
else
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&my_error));
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (is_app)
|
|
*is_app = g_str_has_prefix (remote_ref, "app/");
|
|
|
|
return g_steal_pointer (&remote_ref);
|
|
}
|
|
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"Can't find %s in remote %s", name, remote);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static GHashTable *
|
|
flatpak_dir_get_all_installed_refs (FlatpakDir *self,
|
|
gboolean app,
|
|
gboolean runtime,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GHashTable) local_refs = NULL;
|
|
int i;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, NULL, error))
|
|
return NULL;
|
|
|
|
local_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
if (app)
|
|
{
|
|
g_auto(GStrv) app_refs = NULL;
|
|
|
|
if (!flatpak_dir_list_refs (self, "app", &app_refs, NULL, error))
|
|
return NULL;
|
|
|
|
for (i = 0; app_refs[i] != NULL; i++)
|
|
g_hash_table_insert (local_refs, g_strdup (app_refs[i]),
|
|
GINT_TO_POINTER (1));
|
|
}
|
|
if (runtime)
|
|
{
|
|
g_auto(GStrv) runtime_refs = NULL;
|
|
|
|
if (!flatpak_dir_list_refs (self, "runtime", &runtime_refs, NULL, error))
|
|
return NULL;
|
|
|
|
for (i = 0; runtime_refs[i] != NULL; i++)
|
|
g_hash_table_insert (local_refs, g_strdup (runtime_refs[i]),
|
|
GINT_TO_POINTER (1));
|
|
}
|
|
|
|
return g_steal_pointer (&local_refs);
|
|
}
|
|
|
|
char **
|
|
flatpak_dir_find_installed_refs (FlatpakDir *self,
|
|
const char *opt_name,
|
|
const char *opt_branch,
|
|
const char *opt_arch,
|
|
gboolean app,
|
|
gboolean runtime,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GHashTable) local_refs = NULL;
|
|
GPtrArray *matched_refs;
|
|
|
|
local_refs = flatpak_dir_get_all_installed_refs (self, app, runtime, error);
|
|
if (local_refs == NULL)
|
|
return NULL;
|
|
|
|
matched_refs = find_matching_refs (local_refs, opt_name, opt_branch,
|
|
opt_arch, app, runtime, error);
|
|
if (matched_refs == NULL)
|
|
return NULL;
|
|
|
|
g_ptr_array_add (matched_refs, NULL);
|
|
return (char **)g_ptr_array_free (matched_refs, FALSE);
|
|
|
|
}
|
|
|
|
char *
|
|
flatpak_dir_find_installed_ref (FlatpakDir *self,
|
|
const char *opt_name,
|
|
const char *opt_branch,
|
|
const char *opt_arch,
|
|
gboolean app,
|
|
gboolean runtime,
|
|
gboolean *is_app,
|
|
GError **error)
|
|
{
|
|
g_autofree char *local_ref = NULL;
|
|
g_autoptr(GHashTable) local_refs = NULL;
|
|
g_autoptr(GError) my_error = NULL;
|
|
|
|
local_refs = flatpak_dir_get_all_installed_refs (self, app, runtime, error);
|
|
if (local_refs == NULL)
|
|
return NULL;
|
|
|
|
local_ref = find_matching_ref (local_refs, opt_name, opt_branch,
|
|
opt_arch, app, runtime, &my_error);
|
|
if (local_ref == NULL)
|
|
{
|
|
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
g_clear_error (&my_error);
|
|
else
|
|
{
|
|
g_propagate_error (error, g_steal_pointer (&my_error));
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (is_app)
|
|
*is_app = g_str_has_prefix (local_ref, "app/");
|
|
|
|
return g_steal_pointer (&local_ref);
|
|
}
|
|
|
|
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
|
|
"%s %s not installed", opt_name ? opt_name : "*unspecified*", opt_branch ? opt_branch : "master");
|
|
return NULL;
|
|
}
|
|
|
|
FlatpakDir *
|
|
flatpak_dir_new (GFile *path, gboolean user)
|
|
{
|
|
return g_object_new (FLATPAK_TYPE_DIR, "path", path, "user", user, NULL);
|
|
}
|
|
|
|
FlatpakDir *
|
|
flatpak_dir_clone (FlatpakDir *self)
|
|
{
|
|
return flatpak_dir_new (self->basedir, self->user);
|
|
}
|
|
|
|
FlatpakDir *
|
|
flatpak_dir_get_system (void)
|
|
{
|
|
g_autoptr(GFile) path = flatpak_get_system_base_dir_location ();
|
|
return flatpak_dir_new (path, FALSE);
|
|
}
|
|
|
|
FlatpakDir *
|
|
flatpak_dir_get_user (void)
|
|
{
|
|
g_autoptr(GFile) path = flatpak_get_user_base_dir_location ();
|
|
return flatpak_dir_new (path, TRUE);
|
|
}
|
|
|
|
FlatpakDir *
|
|
flatpak_dir_get (gboolean user)
|
|
{
|
|
if (user)
|
|
return flatpak_dir_get_user ();
|
|
else
|
|
return flatpak_dir_get_system ();
|
|
}
|
|
|
|
static char *
|
|
get_group (const char *remote_name)
|
|
{
|
|
return g_strdup_printf ("remote \"%s\"", remote_name);
|
|
}
|
|
|
|
char *
|
|
flatpak_dir_get_remote_title (FlatpakDir *self,
|
|
const char *remote_name)
|
|
{
|
|
GKeyFile *config = ostree_repo_get_config (self->repo);
|
|
g_autofree char *group = get_group (remote_name);
|
|
|
|
if (config)
|
|
return g_key_file_get_string (config, group, "xa.title", NULL);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
flatpak_dir_get_remote_prio (FlatpakDir *self,
|
|
const char *remote_name)
|
|
{
|
|
GKeyFile *config = ostree_repo_get_config (self->repo);
|
|
g_autofree char *group = get_group (remote_name);
|
|
|
|
if (config && g_key_file_has_key (config, group, "xa.prio", NULL))
|
|
return g_key_file_get_integer (config, group, "xa.prio", NULL);
|
|
|
|
return 1;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_get_remote_noenumerate (FlatpakDir *self,
|
|
const char *remote_name)
|
|
{
|
|
GKeyFile *config = ostree_repo_get_config (self->repo);
|
|
g_autofree char *group = get_group (remote_name);
|
|
|
|
if (config)
|
|
return g_key_file_get_boolean (config, group, "xa.noenumerate", NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_get_remote_disabled (FlatpakDir *self,
|
|
const char *remote_name)
|
|
{
|
|
GKeyFile *config = ostree_repo_get_config (self->repo);
|
|
g_autofree char *group = get_group (remote_name);
|
|
|
|
if (config)
|
|
return g_key_file_get_boolean (config, group, "xa.disable", NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gint
|
|
cmp_remote (gconstpointer a,
|
|
gconstpointer b,
|
|
gpointer user_data)
|
|
{
|
|
FlatpakDir *self = user_data;
|
|
const char *a_name = *(const char **) a;
|
|
const char *b_name = *(const char **) b;
|
|
int prio_a, prio_b;
|
|
|
|
prio_a = flatpak_dir_get_remote_prio (self, a_name);
|
|
prio_b = flatpak_dir_get_remote_prio (self, b_name);
|
|
|
|
return prio_b - prio_a;
|
|
}
|
|
|
|
char *
|
|
flatpak_dir_create_origin_remote (FlatpakDir *self,
|
|
const char *url,
|
|
const char *id,
|
|
const char *title,
|
|
GBytes *gpg_data,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *remote = NULL;
|
|
|
|
g_auto(GStrv) remotes = NULL;
|
|
int version = 0;
|
|
g_autoptr(GVariantBuilder) optbuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
remotes = ostree_repo_remote_list (self->repo, NULL);
|
|
|
|
do
|
|
{
|
|
g_autofree char *name = NULL;
|
|
if (version == 0)
|
|
name = g_strdup_printf ("%s-origin", id);
|
|
else
|
|
name = g_strdup_printf ("%s-%d-origin", id, version);
|
|
version++;
|
|
|
|
if (remotes == NULL ||
|
|
!g_strv_contains ((const char * const *) remotes, name))
|
|
remote = g_steal_pointer (&name);
|
|
}
|
|
while (remote == NULL);
|
|
|
|
g_variant_builder_add (optbuilder, "{s@v}",
|
|
"xa.title",
|
|
g_variant_new_variant (g_variant_new_string (title)));
|
|
|
|
g_variant_builder_add (optbuilder, "{s@v}",
|
|
"xa.noenumerate",
|
|
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
|
|
|
g_variant_builder_add (optbuilder, "{s@v}",
|
|
"xa.prio",
|
|
g_variant_new_variant (g_variant_new_string ("0")));
|
|
|
|
if (!ostree_repo_remote_add (self->repo,
|
|
remote, url ? url : "", g_variant_builder_end (optbuilder), cancellable, error))
|
|
return NULL;
|
|
|
|
if (gpg_data)
|
|
{
|
|
g_autoptr(GInputStream) gpg_data_as_stream = g_memory_input_stream_new_from_bytes (gpg_data);
|
|
|
|
if (!ostree_repo_remote_gpg_import (self->repo, remote, gpg_data_as_stream,
|
|
NULL, NULL, cancellable, error))
|
|
{
|
|
ostree_repo_remote_delete (self->repo, remote,
|
|
NULL, NULL);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return g_steal_pointer (&remote);
|
|
}
|
|
|
|
|
|
char **
|
|
flatpak_dir_list_remotes (FlatpakDir *self,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
char **res;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return NULL;
|
|
|
|
res = ostree_repo_remote_list (self->repo, NULL);
|
|
if (res == NULL)
|
|
res = g_new0 (char *, 1); /* Return empty array, not error */
|
|
|
|
g_qsort_with_data (res, g_strv_length (res), sizeof (char *),
|
|
cmp_remote, self);
|
|
|
|
return res;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_remove_remote (FlatpakDir *self,
|
|
gboolean force_remove,
|
|
const char *remote_name,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *prefix = NULL;
|
|
g_autoptr(GHashTable) refs = NULL;
|
|
GHashTableIter hash_iter;
|
|
gpointer key;
|
|
|
|
if (flatpak_dir_use_system_helper (self))
|
|
{
|
|
FlatpakSystemHelper *system_helper;
|
|
g_autoptr(GVariant) gpg_data_v = NULL;
|
|
FlatpakHelperConfigureRemoteFlags flags = 0;
|
|
|
|
gpg_data_v = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("ay"), "", 0, TRUE, NULL, NULL));
|
|
|
|
system_helper = flatpak_dir_get_system_helper (self);
|
|
g_assert (system_helper != NULL);
|
|
|
|
if (force_remove)
|
|
flags |= FLATPAK_HELPER_CONFIGURE_REMOTE_FLAGS_FORCE_REMOVE;
|
|
|
|
if (!flatpak_system_helper_call_configure_remote_sync (system_helper,
|
|
flags, remote_name,
|
|
"",
|
|
gpg_data_v,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!ostree_repo_list_refs (self->repo,
|
|
NULL,
|
|
&refs,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
prefix = g_strdup_printf ("%s:", remote_name);
|
|
|
|
if (!force_remove)
|
|
{
|
|
g_hash_table_iter_init (&hash_iter, refs);
|
|
while (g_hash_table_iter_next (&hash_iter, &key, NULL))
|
|
{
|
|
const char *refspec = key;
|
|
|
|
if (g_str_has_prefix (refspec, prefix))
|
|
{
|
|
const char *unprefixed_refspec = refspec + strlen (prefix);
|
|
g_autofree char *origin = flatpak_dir_get_origin (self, unprefixed_refspec,
|
|
cancellable, NULL);
|
|
|
|
if (g_strcmp0 (origin, remote_name) == 0)
|
|
return flatpak_fail (error, "Can't remove remote '%s' with installed ref %s (at least)",
|
|
remote_name, unprefixed_refspec);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remove all refs */
|
|
g_hash_table_iter_init (&hash_iter, refs);
|
|
while (g_hash_table_iter_next (&hash_iter, &key, NULL))
|
|
{
|
|
const char *refspec = key;
|
|
|
|
if (g_str_has_prefix (refspec, prefix) &&
|
|
!flatpak_dir_remove_ref (self, remote_name, refspec + strlen (prefix), cancellable, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (!flatpak_dir_remove_appstream (self, remote_name,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!ostree_repo_remote_change (self->repo, NULL,
|
|
OSTREE_REPO_REMOTE_CHANGE_DELETE,
|
|
remote_name, NULL,
|
|
NULL,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_mark_changed (self, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_modify_remote (FlatpakDir *self,
|
|
const char *remote_name,
|
|
GKeyFile *config,
|
|
GBytes *gpg_data,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *group = g_strdup_printf ("remote \"%s\"", remote_name);
|
|
g_autofree char *url = NULL;
|
|
g_autofree char *metalink = NULL;
|
|
g_autoptr(GKeyFile) new_config = NULL;
|
|
g_auto(GStrv) keys = NULL;
|
|
int i;
|
|
|
|
if (strchr (remote_name, '/') != NULL)
|
|
return flatpak_fail (error, "Invalid character '/' in remote name: %s",
|
|
remote_name);
|
|
|
|
|
|
if (!g_key_file_has_group (config, group))
|
|
return flatpak_fail (error, "No configuration for remote %s specified",
|
|
remote_name);
|
|
|
|
|
|
if (flatpak_dir_use_system_helper (self))
|
|
{
|
|
FlatpakSystemHelper *system_helper;
|
|
g_autofree char *config_data = g_key_file_to_data (config, NULL, NULL);
|
|
g_autoptr(GVariant) gpg_data_v = NULL;
|
|
|
|
if (gpg_data != NULL)
|
|
gpg_data_v = variant_new_ay_bytes (gpg_data);
|
|
else
|
|
gpg_data_v = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("ay"), "", 0, TRUE, NULL, NULL));
|
|
|
|
system_helper = flatpak_dir_get_system_helper (self);
|
|
g_assert (system_helper != NULL);
|
|
|
|
if (!flatpak_system_helper_call_configure_remote_sync (system_helper,
|
|
0, remote_name,
|
|
config_data,
|
|
gpg_data_v,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
metalink = g_key_file_get_string (config, group, "metalink", NULL);
|
|
if (metalink != NULL && *metalink != 0)
|
|
url = g_strconcat ("metalink=", metalink, NULL);
|
|
else
|
|
url = g_key_file_get_string (config, group, "url", NULL);
|
|
|
|
if (url == NULL || *url == 0)
|
|
return flatpak_fail (error, "No url for remote %s specified",
|
|
remote_name);
|
|
|
|
/* Add it if its not there yet */
|
|
if (!ostree_repo_remote_change (self->repo, NULL,
|
|
OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS,
|
|
remote_name,
|
|
url, NULL, cancellable, error))
|
|
return FALSE;
|
|
|
|
new_config = ostree_repo_copy_config (self->repo);
|
|
|
|
g_key_file_remove_group (new_config, group, NULL);
|
|
|
|
keys = g_key_file_get_keys (config,
|
|
group,
|
|
NULL, error);
|
|
if (keys == NULL)
|
|
return FALSE;
|
|
|
|
for (i = 0; keys[i] != NULL; i++)
|
|
{
|
|
g_autofree gchar *value = g_key_file_get_value (config, group, keys[i], NULL);
|
|
if (value)
|
|
g_key_file_set_value (new_config, group, keys[i], value);
|
|
}
|
|
|
|
if (!ostree_repo_write_config (self->repo, new_config, error))
|
|
return FALSE;
|
|
|
|
if (gpg_data != NULL)
|
|
{
|
|
g_autoptr(GInputStream) input_stream = g_memory_input_stream_new_from_bytes (gpg_data);
|
|
guint imported = 0;
|
|
|
|
if (!ostree_repo_remote_gpg_import (self->repo, remote_name, input_stream,
|
|
NULL, &imported, cancellable, error))
|
|
return FALSE;
|
|
|
|
/* XXX If we ever add internationalization, use ngettext() here. */
|
|
g_debug ("Imported %u GPG key%s to remote \"%s\"",
|
|
imported, (imported == 1) ? "" : "s", remote_name);
|
|
}
|
|
|
|
if (!flatpak_dir_mark_changed (self, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
remove_unless_in_hash (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
GHashTable *table = user_data;
|
|
|
|
return !g_hash_table_contains (table, key);
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_list_remote_refs (FlatpakDir *self,
|
|
const char *remote,
|
|
GHashTable **refs,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GError) my_error = NULL;
|
|
|
|
if (error == NULL)
|
|
error = &my_error;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_remote_list_refs (self, remote, refs,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
if (flatpak_dir_get_remote_noenumerate (self, remote))
|
|
{
|
|
g_autoptr(GHashTable) unprefixed_local_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
g_autoptr(GHashTable) local_refs = NULL;
|
|
GHashTableIter hash_iter;
|
|
gpointer key;
|
|
g_autofree char *refspec_prefix = g_strconcat (remote, ":.", NULL);
|
|
|
|
/* For noenumerate remotes, only return data for already locally
|
|
* available refs */
|
|
|
|
if (!ostree_repo_list_refs (self->repo, refspec_prefix, &local_refs,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
/* First we need to unprefix the remote name from the local refs */
|
|
g_hash_table_iter_init (&hash_iter, local_refs);
|
|
while (g_hash_table_iter_next (&hash_iter, &key, NULL))
|
|
{
|
|
char *ref = NULL;
|
|
ostree_parse_refspec (key, NULL, &ref, NULL);
|
|
|
|
if (ref)
|
|
g_hash_table_insert (unprefixed_local_refs, ref, NULL);
|
|
}
|
|
|
|
/* Then we remove all remote refs not in the local refs set */
|
|
g_hash_table_foreach_remove (*refs,
|
|
remove_unless_in_hash,
|
|
unprefixed_local_refs);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
char *
|
|
flatpak_dir_fetch_remote_title (FlatpakDir *self,
|
|
const char *remote,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GError) my_error = NULL;
|
|
g_autoptr(GBytes) summary_bytes = NULL;
|
|
g_autoptr(GVariant) summary = NULL;
|
|
g_autoptr(GVariant) extensions = NULL;
|
|
GVariantDict dict;
|
|
g_autofree char *title = NULL;
|
|
|
|
if (error == NULL)
|
|
error = &my_error;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return NULL;
|
|
|
|
if (!flatpak_dir_remote_fetch_summary (self, remote,
|
|
&summary_bytes,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
if (summary_bytes == NULL)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Remote title not available; server has no summary file");
|
|
return FALSE;
|
|
}
|
|
|
|
summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT,
|
|
summary_bytes, FALSE);
|
|
extensions = g_variant_get_child_value (summary, 1);
|
|
|
|
g_variant_dict_init (&dict, extensions);
|
|
g_variant_dict_lookup (&dict, "xa.title", "s", &title);
|
|
g_variant_dict_end (&dict);
|
|
|
|
if (title == NULL)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"Remote title not set");
|
|
return FALSE;
|
|
}
|
|
|
|
return g_steal_pointer (&title);
|
|
}
|
|
|
|
static void
|
|
ensure_soup_session (FlatpakDir *self)
|
|
{
|
|
const char *http_proxy;
|
|
|
|
if (g_once_init_enter (&self->soup_session))
|
|
{
|
|
SoupSession *soup_session;
|
|
|
|
soup_session =
|
|
soup_session_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ",
|
|
SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
|
|
SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
|
|
SOUP_SESSION_TIMEOUT, 60,
|
|
SOUP_SESSION_IDLE_TIMEOUT, 60,
|
|
NULL);
|
|
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);
|
|
}
|
|
|
|
if (g_getenv ("OSTREE_DEBUG_HTTP"))
|
|
soup_session_add_feature (soup_session, (SoupSessionFeature *) soup_logger_new (SOUP_LOGGER_LOG_BODY, 500));
|
|
|
|
g_once_init_leave (&self->soup_session, soup_session);
|
|
}
|
|
}
|
|
|
|
static GBytes *
|
|
flatpak_dir_load_uri (FlatpakDir *self,
|
|
const char *uri,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *scheme = NULL;
|
|
|
|
g_autoptr(GBytes) bytes = NULL;
|
|
|
|
scheme = g_uri_parse_scheme (uri);
|
|
if (strcmp (scheme, "file") == 0)
|
|
{
|
|
char *buffer;
|
|
gsize length;
|
|
g_autoptr(GFile) file = NULL;
|
|
|
|
g_debug ("Loading %s using GIO", uri);
|
|
|
|
file = g_file_new_for_uri (uri);
|
|
if (!g_file_load_contents (file, cancellable, &buffer, &length, NULL, NULL))
|
|
return NULL;
|
|
|
|
bytes = g_bytes_new_take (buffer, length);
|
|
}
|
|
else if (strcmp (scheme, "http") == 0 ||
|
|
strcmp (scheme, "https") == 0)
|
|
{
|
|
g_autoptr(SoupMessage) msg = NULL;
|
|
|
|
ensure_soup_session (self);
|
|
|
|
g_debug ("Loading %s using libsoup", uri);
|
|
msg = soup_message_new ("GET", uri);
|
|
soup_session_send_message (self->soup_session, msg);
|
|
|
|
if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
|
|
{
|
|
GIOErrorEnum code;
|
|
|
|
switch (msg->status_code)
|
|
{
|
|
case 404:
|
|
case 410:
|
|
code = G_IO_ERROR_NOT_FOUND;
|
|
break;
|
|
|
|
default:
|
|
code = G_IO_ERROR_FAILED;
|
|
}
|
|
|
|
g_set_error (error, G_IO_ERROR, code,
|
|
"Server returned status %u: %s",
|
|
msg->status_code,
|
|
soup_status_get_phrase (msg->status_code));
|
|
return NULL;
|
|
}
|
|
|
|
bytes = g_bytes_new (msg->response_body->data, msg->response_body->length);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Unsupported uri scheme %s", scheme);
|
|
return FALSE;
|
|
}
|
|
|
|
g_debug ("Received %" G_GSIZE_FORMAT " bytes", g_bytes_get_size (bytes));
|
|
|
|
return g_steal_pointer (&bytes);
|
|
}
|
|
|
|
GBytes *
|
|
flatpak_dir_fetch_remote_object (FlatpakDir *self,
|
|
const char *remote_name,
|
|
const char *checksum,
|
|
const char *type,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autofree char *base_url = NULL;
|
|
g_autofree char *object_url = NULL;
|
|
g_autofree char *part1 = NULL;
|
|
g_autofree char *part2 = NULL;
|
|
|
|
g_autoptr(GBytes) bytes = NULL;
|
|
|
|
if (!ostree_repo_remote_get_url (self->repo, remote_name, &base_url, error))
|
|
return NULL;
|
|
|
|
part1 = g_strndup (checksum, 2);
|
|
part2 = g_strdup_printf ("%s.%s", checksum + 2, type);
|
|
|
|
object_url = g_build_filename (base_url, "objects", part1, part2, NULL);
|
|
|
|
bytes = flatpak_dir_load_uri (self, object_url, cancellable, error);
|
|
if (bytes == NULL)
|
|
return NULL;
|
|
|
|
return g_steal_pointer (&bytes);
|
|
}
|
|
|
|
gboolean
|
|
flatpak_dir_fetch_ref_cache (FlatpakDir *self,
|
|
const char *remote_name,
|
|
const char *ref,
|
|
guint64 *download_size,
|
|
guint64 *installed_size,
|
|
char **metadata,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GBytes) summary_bytes = NULL;
|
|
g_autoptr(GVariant) extensions = NULL;
|
|
g_autoptr(GVariant) summary = NULL;
|
|
g_autoptr(GVariant) cache_v = NULL;
|
|
g_autoptr(GVariant) cache = NULL;
|
|
g_autoptr(GVariant) res = NULL;
|
|
|
|
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
|
return FALSE;
|
|
|
|
if (!flatpak_dir_remote_fetch_summary (self, remote_name,
|
|
&summary_bytes,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
if (summary_bytes == NULL)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Data not available; server has no summary file");
|
|
return FALSE;
|
|
}
|
|
|
|
summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT,
|
|
summary_bytes, FALSE);
|
|
extensions = g_variant_get_child_value (summary, 1);
|
|
|
|
cache_v = g_variant_lookup_value (extensions, "xa.cache", NULL);
|
|
if (cache_v == NULL)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"Data not found");
|
|
return FALSE;
|
|
}
|
|
|
|
cache = g_variant_get_child_value (cache_v, 0);
|
|
res = g_variant_lookup_value (cache, ref, NULL);
|
|
if (res == NULL)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"Data not found for ref %s", ref);
|
|
return FALSE;
|
|
}
|
|
|
|
if (installed_size)
|
|
{
|
|
guint64 v;
|
|
g_variant_get_child (res, 0, "t", &v);
|
|
*installed_size = GUINT64_FROM_BE (v);
|
|
}
|
|
|
|
if (download_size)
|
|
{
|
|
guint64 v;
|
|
g_variant_get_child (res, 1, "t", &v);
|
|
*download_size = GUINT64_FROM_BE (v);
|
|
}
|
|
|
|
if (metadata)
|
|
g_variant_get_child (res, 2, "s", metadata);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GBytes *
|
|
flatpak_dir_fetch_metadata (FlatpakDir *self,
|
|
const char *remote_name,
|
|
const char *commit,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GBytes) commit_bytes = NULL;
|
|
g_autoptr(GBytes) root_bytes = NULL;
|
|
g_autoptr(GBytes) filez_bytes = NULL;
|
|
g_autoptr(GVariant) commit_variant = NULL;
|
|
g_autoptr(GVariant) root_variant = NULL;
|
|
g_autoptr(GVariant) root_csum = NULL;
|
|
g_autoptr(GVariant) files_variant = NULL;
|
|
g_autofree char *file_checksum = NULL;
|
|
g_autofree char *root_checksum = NULL;
|
|
g_autoptr(GConverter) zlib_decomp = NULL;
|
|
g_autoptr(GInputStream) zlib_input = NULL;
|
|
g_autoptr(GMemoryOutputStream) data_stream = NULL;
|
|
g_autoptr(GMemoryInputStream) dataz_stream = NULL;
|
|
gsize filez_size;
|
|
const guchar *filez_data;
|
|
guint32 archive_header_size;
|
|
int i, n;
|
|
|
|
commit_bytes = flatpak_dir_fetch_remote_object (self, remote_name,
|
|
commit, "commit",
|
|
cancellable, error);
|
|
if (commit_bytes == NULL)
|
|
return NULL;
|
|
|
|
commit_variant = g_variant_new_from_bytes (OSTREE_COMMIT_GVARIANT_FORMAT,
|
|
commit_bytes, FALSE);
|
|
|
|
if (!ostree_validate_structureof_commit (commit_variant, error))
|
|
return NULL;
|
|
|
|
g_variant_get_child (commit_variant, 6, "@ay", &root_csum);
|
|
root_checksum = ostree_checksum_from_bytes_v (root_csum);
|
|
|
|
root_bytes = flatpak_dir_fetch_remote_object (self, remote_name,
|
|
root_checksum, "dirtree",
|
|
cancellable, error);
|
|
if (root_bytes == NULL)
|
|
return NULL;
|
|
|
|
root_variant = g_variant_new_from_bytes (OSTREE_TREE_GVARIANT_FORMAT,
|
|
root_bytes, FALSE);
|
|
|
|
if (!ostree_validate_structureof_dirtree (root_variant, error))
|
|
return NULL;
|
|
|
|
files_variant = g_variant_get_child_value (root_variant, 0);
|
|
|
|
n = g_variant_n_children (files_variant);
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
const char *filename;
|
|
g_autoptr(GVariant) csum = NULL;
|
|
|
|
g_variant_get_child (files_variant, i, "(&s@ay)", &filename, &csum);
|
|
|
|
if (strcmp (filename, "metadata") != 0)
|
|
continue;
|
|
|
|
file_checksum = ostree_checksum_from_bytes_v (csum);
|
|
break;
|
|
}
|
|
|
|
if (file_checksum == NULL)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
|
"Can't find metadata file");
|
|
return NULL;
|
|
}
|
|
|
|
filez_bytes = flatpak_dir_fetch_remote_object (self, remote_name,
|
|
file_checksum, "filez",
|
|
cancellable, error);
|
|
if (filez_bytes == NULL)
|
|
return NULL;
|
|
|
|
filez_data = g_bytes_get_data (filez_bytes, &filez_size);
|
|
|
|
if (filez_size < 8)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid header");
|
|
return NULL;
|
|
}
|
|
|
|
archive_header_size = GUINT32_FROM_BE (*(guint32 *) filez_data);
|
|
|
|
archive_header_size += 4 + 4; /* Include header-size and padding */
|
|
|
|
if (archive_header_size > filez_size)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"File header size %u exceeds file size",
|
|
(guint) archive_header_size);
|
|
return NULL;
|
|
}
|
|
|
|
dataz_stream = (GMemoryInputStream *) g_memory_input_stream_new_from_data (filez_data + archive_header_size,
|
|
g_bytes_get_size (filez_bytes) - archive_header_size,
|
|
NULL);
|
|
|
|
zlib_decomp = (GConverter *) g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW);
|
|
zlib_input = g_converter_input_stream_new (G_INPUT_STREAM (dataz_stream), zlib_decomp);
|
|
|
|
data_stream = (GMemoryOutputStream *) g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
|
|
|
|
if (g_output_stream_splice (G_OUTPUT_STREAM (data_stream), zlib_input,
|
|
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
|
|
cancellable, error) < 0)
|
|
return NULL;
|
|
|
|
return g_memory_output_stream_steal_as_bytes (data_stream);
|
|
}
|