ostree/src/libostree/ostree-sysroot.c

1532 lines
43 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* This library 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include "otutil.h"
#include "ostree-core-private.h"
#include "ostree-sysroot-private.h"
#include "ostree-bootloader-uboot.h"
#include "ostree-bootloader-syslinux.h"
#include "ostree-bootloader-grub2.h"
static gboolean
find_booted_deployment (OstreeSysroot *self,
GPtrArray *deployments,
OstreeDeployment **out_deployment,
GCancellable *cancellable,
GError **error);
/**
* SECTION:libostree-sysroot
* @title: Root partition mount point
* @short_description: Manage physical root filesystem
*
* A #OstreeSysroot object represents a physical root filesystem,
* which in particular should contain a toplevel /ostree directory.
* Inside this directory is an #OstreeRepo in /ostree/repo, plus a set
* of deployments in /ostree/deploy.
*
* This class is not by default safe against concurrent use by threads
* or external processes. You can use ostree_sysroot_lock() to
* perform locking externally.
*/
typedef struct {
GObjectClass parent_class;
} OstreeSysrootClass;
enum {
PROP_0,
PROP_PATH
};
G_DEFINE_TYPE (OstreeSysroot, ostree_sysroot, G_TYPE_OBJECT)
static void
ostree_sysroot_finalize (GObject *object)
{
OstreeSysroot *self = OSTREE_SYSROOT (object);
g_clear_object (&self->path);
g_clear_object (&self->sepolicy);
g_clear_object (&self->repo);
glnx_release_lock_file (&self->lock);
(void) ostree_sysroot_unload (self);
G_OBJECT_CLASS (ostree_sysroot_parent_class)->finalize (object);
}
static void
ostree_sysroot_set_property(GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
OstreeSysroot *self = OSTREE_SYSROOT (object);
switch (prop_id)
{
case PROP_PATH:
self->path = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ostree_sysroot_get_property(GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
OstreeSysroot *self = OSTREE_SYSROOT (object);
switch (prop_id)
{
case PROP_PATH:
g_value_set_object (value, self->path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ostree_sysroot_constructed (GObject *object)
{
OstreeSysroot *self = OSTREE_SYSROOT (object);
g_autoptr(GFile) repo_path = NULL;
/* Ensure the system root path is set. */
if (self->path == NULL)
self->path = g_object_ref (_ostree_get_default_sysroot_path ());
repo_path = g_file_resolve_relative_path (self->path, "ostree/repo");
self->repo = ostree_repo_new_for_sysroot_path (repo_path, self->path);
G_OBJECT_CLASS (ostree_sysroot_parent_class)->constructed (object);
}
static void
ostree_sysroot_class_init (OstreeSysrootClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = ostree_sysroot_constructed;
object_class->get_property = ostree_sysroot_get_property;
object_class->set_property = ostree_sysroot_set_property;
object_class->finalize = ostree_sysroot_finalize;
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
ostree_sysroot_init (OstreeSysroot *self)
{
const GDebugKey keys[] = {
{ "mutable-deployments", OSTREE_SYSROOT_DEBUG_MUTABLE_DEPLOYMENTS },
};
self->debug_flags = g_parse_debug_string (g_getenv("OSTREE_SYSROOT_DEBUG"),
keys, G_N_ELEMENTS (keys));
self->sysroot_fd = -1;
self->lock = (GLnxLockFile)GLNX_LOCK_FILE_INIT;
}
/**
* ostree_sysroot_new:
* @path: (allow-none): Path to a system root directory, or %NULL
*
* Returns: (transfer full): An accessor object for an system root located at @path
*/
OstreeSysroot*
ostree_sysroot_new (GFile *path)
{
return g_object_new (OSTREE_TYPE_SYSROOT, "path", path, NULL);
}
/**
* ostree_sysroot_new_default:
*
* Returns: (transfer full): An accessor for the current visible root / filesystem
*/
OstreeSysroot*
ostree_sysroot_new_default (void)
{
return ostree_sysroot_new (NULL);
}
/**
* ostree_sysroot_get_path:
* @self:
*
* Returns: (transfer none): Path to rootfs
*/
GFile *
ostree_sysroot_get_path (OstreeSysroot *self)
{
return self->path;
}
static gboolean
ensure_sysroot_fd (OstreeSysroot *self,
GError **error)
{
if (self->sysroot_fd == -1)
{
if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (self->path), TRUE,
&self->sysroot_fd, error))
return FALSE;
}
return TRUE;
}
/**
* ostree_sysroot_get_fd:
* @self: Sysroot
*
* Access a file descriptor that refers to the root directory of this
* sysroot. ostree_sysroot_load() must have been invoked prior to
* calling this function.
*
* Returns: A file descriptor valid for the lifetime of @self
*/
int
ostree_sysroot_get_fd (OstreeSysroot *self)
{
g_return_val_if_fail (self->sysroot_fd != -1, -1);
return self->sysroot_fd;
}
gboolean
_ostree_sysroot_bump_mtime (OstreeSysroot *self,
GError **error)
{
/* Allow other systems to monitor for changes */
if (utimensat (self->sysroot_fd, "ostree/deploy", NULL, 0) < 0)
{
glnx_set_prefix_error_from_errno (error, "%s", "futimens");
return FALSE;
}
return TRUE;
}
/**
* ostree_sysroot_unload:
* @self: Sysroot
*
* Release any resources such as file descriptors referring to the
* root directory of this sysroot. Normally, those resources are
* cleared by finalization, but in garbage collected languages that
* may not be predictable.
*
* This undoes the effect of `ostree_sysroot_load()`.
*/
void
ostree_sysroot_unload (OstreeSysroot *self)
{
if (self->sysroot_fd != -1)
(void) close (self->sysroot_fd);
}
/**
* ostree_sysroot_ensure_initialized:
* @self: Sysroot
* @cancellable: Cancellable
* @error: Error
*
* Ensure that @self is set up as a valid rootfs, by creating
* /ostree/repo, among other things.
*/
gboolean
ostree_sysroot_ensure_initialized (OstreeSysroot *self,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autoptr(GFile) dir = NULL;
g_autoptr(GFile) ostree_dir = NULL;
g_autoptr(GFile) repo_dir = NULL;
ostree_dir = g_file_get_child (self->path, "ostree");
repo_dir = g_file_get_child (ostree_dir, "repo");
if (!gs_file_ensure_directory (repo_dir, TRUE, cancellable, error))
goto out;
g_clear_object (&dir);
dir = g_file_get_child (ostree_dir, "deploy");
if (!gs_file_ensure_directory (dir, TRUE, cancellable, error))
goto out;
g_clear_object (&dir);
dir = ot_gfile_get_child_build_path (ostree_dir, "repo", "objects", NULL);
if (!g_file_query_exists (dir, NULL))
{
glnx_unref_object OstreeRepo *repo = ostree_repo_new (repo_dir);
if (!ostree_repo_create (repo, OSTREE_REPO_MODE_BARE,
cancellable, error))
goto out;
}
ret = TRUE;
out:
return ret;
}
static void
match_info_cleanup (void *loc)
{
GMatchInfo **match = (GMatchInfo**)loc;
if (*match) g_match_info_unref (*match);
}
gboolean
_ostree_sysroot_parse_deploy_path_name (const char *name,
char **out_csum,
int *out_serial,
GError **error)
{
gboolean ret = FALSE;
__attribute__((cleanup(match_info_cleanup))) GMatchInfo *match = NULL;
g_autofree char *serial_str = NULL;
static gsize regex_initialized;
static GRegex *regex;
if (g_once_init_enter (&regex_initialized))
{
regex = g_regex_new ("^([0-9a-f]+)\\.([0-9]+)$", 0, 0, NULL);
g_assert (regex);
g_once_init_leave (&regex_initialized, 1);
}
if (!g_regex_match (regex, name, 0, &match))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid deploy name '%s', expected CHECKSUM.TREESERIAL", name);
goto out;
}
*out_csum = g_match_info_fetch (match, 1);
serial_str = g_match_info_fetch (match, 2);
*out_serial = (int)g_ascii_strtoll (serial_str, NULL, 10);
ret = TRUE;
out:
return ret;
}
gboolean
_ostree_sysroot_read_current_subbootversion (OstreeSysroot *self,
int bootversion,
int *out_subbootversion,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
struct stat stbuf;
g_autofree char *ostree_bootdir_name = g_strdup_printf ("ostree/boot.%d", bootversion);
if (!ensure_sysroot_fd (self, error))
goto out;
if (fstatat (self->sysroot_fd, ostree_bootdir_name, &stbuf, AT_SYMLINK_NOFOLLOW) != 0)
{
if (errno == ENOENT)
*out_subbootversion = 0;
else
{
glnx_set_error_from_errno (error);
goto out;
}
}
else
{
g_autofree char *current_subbootdir_name = NULL;
current_subbootdir_name = glnx_readlinkat_malloc (self->sysroot_fd, ostree_bootdir_name,
cancellable, error);
if (!current_subbootdir_name)
goto out;
if (g_str_has_suffix (current_subbootdir_name, ".0"))
*out_subbootversion = 0;
else if (g_str_has_suffix (current_subbootdir_name, ".1"))
*out_subbootversion = 1;
else
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid target '%s' in %s",
current_subbootdir_name,
ostree_bootdir_name);
goto out;
}
}
ret = TRUE;
out:
return ret;
}
static gint
compare_boot_loader_configs (OstreeBootconfigParser *a,
OstreeBootconfigParser *b)
{
const char *a_version = ostree_bootconfig_parser_get (a, "version");
const char *b_version = ostree_bootconfig_parser_get (b, "version");
if (a_version && b_version)
{
int r = strverscmp (a_version, b_version);
/* Reverse */
return -r;
}
else if (a_version)
return -1;
else
return 1;
}
static int
compare_loader_configs_for_sorting (gconstpointer a_pp,
gconstpointer b_pp)
{
OstreeBootconfigParser *a = *((OstreeBootconfigParser**)a_pp);
OstreeBootconfigParser *b = *((OstreeBootconfigParser**)b_pp);
return compare_boot_loader_configs (a, b);
}
gboolean
_ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self,
int bootversion,
GPtrArray **out_loader_configs,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
int fd; /* Temporary owned by iterator */
g_autofree char *entries_path = g_strdup_printf ("boot/loader.%d/entries", bootversion);
g_autoptr(GPtrArray) ret_loader_configs = NULL;
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
if (!ensure_sysroot_fd (self, error))
goto out;
ret_loader_configs = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
fd = glnx_opendirat_with_errno (self->sysroot_fd, entries_path, TRUE);
if (fd == -1)
{
if (errno == ENOENT)
goto done;
else
{
glnx_set_error_from_errno (error);
goto out;
}
}
if (!glnx_dirfd_iterator_init_take_fd (fd, &dfd_iter, error))
goto out;
while (TRUE)
{
struct dirent *dent;
struct stat stbuf;
if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error))
goto out;
if (dent == NULL)
break;
if (fstatat (dfd_iter.fd, dent->d_name, &stbuf, 0) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (g_str_has_prefix (dent->d_name, "ostree-") &&
g_str_has_suffix (dent->d_name, ".conf") &&
S_ISREG (stbuf.st_mode))
{
glnx_unref_object OstreeBootconfigParser *config = ostree_bootconfig_parser_new ();
if (!ostree_bootconfig_parser_parse_at (config, dfd_iter.fd, dent->d_name, cancellable, error))
{
g_prefix_error (error, "Parsing %s: ", dent->d_name);
goto out;
}
g_ptr_array_add (ret_loader_configs, g_object_ref (config));
}
}
/* Callers expect us to give them a sorted array */
g_ptr_array_sort (ret_loader_configs, compare_loader_configs_for_sorting);
done:
gs_transfer_out_value (out_loader_configs, &ret_loader_configs);
ret = TRUE;
out:
return ret;
}
static gboolean
read_current_bootversion (OstreeSysroot *self,
int *out_bootversion,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
int ret_bootversion;
struct stat stbuf;
if (fstatat (self->sysroot_fd, "boot/loader", &stbuf, AT_SYMLINK_NOFOLLOW) != 0)
{
if (errno != ENOENT)
{
glnx_set_error_from_errno (error);
goto out;
}
ret_bootversion = 0;
}
else
{
g_autofree char *target = NULL;
if (!S_ISLNK (stbuf.st_mode))
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Not a symbolic link: boot/loader");
goto out;
}
target = glnx_readlinkat_malloc (self->sysroot_fd, "boot/loader", cancellable, error);
if (!target)
goto out;
if (g_strcmp0 (target, "loader.0") == 0)
ret_bootversion = 0;
else if (g_strcmp0 (target, "loader.1") == 0)
ret_bootversion = 1;
else
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid target '%s' in boot/loader", target);
goto out;
}
}
ret = TRUE;
*out_bootversion = ret_bootversion;
out:
return ret;
}
static gboolean
parse_origin (OstreeSysroot *self,
int deployment_dfd,
const char *deployment_name,
GKeyFile **out_origin,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autoptr(GKeyFile) ret_origin = NULL;
g_autofree char *origin_path = g_strconcat ("../", deployment_name, ".origin", NULL);
struct stat stbuf;
g_autofree char *origin_contents = NULL;
ret_origin = g_key_file_new ();
if (fstatat (deployment_dfd, origin_path, &stbuf, 0) != 0)
{
if (errno == ENOENT)
;
else
{
glnx_set_error_from_errno (error);
goto out;
}
}
else
{
origin_contents = glnx_file_get_contents_utf8_at (deployment_dfd, origin_path,
NULL, cancellable, error);
if (!origin_contents)
goto out;
if (!g_key_file_load_from_data (ret_origin, origin_contents, -1, 0, error))
goto out;
}
ret = TRUE;
gs_transfer_out_value (out_origin, &ret_origin);
out:
if (error)
g_prefix_error (error, "Parsing %s: ", origin_path);
if (ret_origin)
g_key_file_unref (ret_origin);
return ret;
}
static gboolean
parse_bootlink (const char *bootlink,
int *out_entry_bootversion,
char **out_osname,
char **out_bootcsum,
int *out_treebootserial,
GError **error)
{
gboolean ret = FALSE;
__attribute__((cleanup(match_info_cleanup))) GMatchInfo *match = NULL;
g_autofree char *bootversion_str = NULL;
g_autofree char *treebootserial_str = NULL;
static gsize regex_initialized;
static GRegex *regex;
if (g_once_init_enter (&regex_initialized))
{
regex = g_regex_new ("^/ostree/boot.([01])/([^/]+)/([^/]+)/([0-9]+)$", 0, 0, NULL);
g_assert (regex);
g_once_init_leave (&regex_initialized, 1);
}
if (!g_regex_match (regex, bootlink, 0, &match))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid ostree= argument '%s', expected ostree=/ostree/boot.BOOTVERSION/OSNAME/BOOTCSUM/TREESERIAL", bootlink);
goto out;
}
bootversion_str = g_match_info_fetch (match, 1);
*out_entry_bootversion = (int)g_ascii_strtoll (bootversion_str, NULL, 10);
*out_osname = g_match_info_fetch (match, 2);
*out_bootcsum = g_match_info_fetch (match, 3);
treebootserial_str = g_match_info_fetch (match, 4);
*out_treebootserial = (int)g_ascii_strtoll (treebootserial_str, NULL, 10);
ret = TRUE;
out:
return ret;
}
static gboolean
parse_deployment (OstreeSysroot *self,
const char *boot_link,
OstreeDeployment **out_deployment,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
const char *relative_boot_link;
glnx_unref_object OstreeDeployment *ret_deployment = NULL;
int entry_boot_version;
int treebootserial = -1;
int deployserial = -1;
g_autofree char *osname = NULL;
g_autofree char *bootcsum = NULL;
g_autofree char *treecsum = NULL;
glnx_fd_close int deployment_dfd = -1;
const char *deploy_basename;
g_autofree char *treebootserial_target = NULL;
g_autofree char *deploy_dir = NULL;
GKeyFile *origin = NULL;
if (!ensure_sysroot_fd (self, error))
goto out;
if (!parse_bootlink (boot_link, &entry_boot_version,
&osname, &bootcsum, &treebootserial,
error))
goto out;
relative_boot_link = boot_link;
if (*relative_boot_link == '/')
relative_boot_link++;
treebootserial_target = glnx_readlinkat_malloc (self->sysroot_fd, relative_boot_link,
cancellable, error);
if (!treebootserial_target)
goto out;
deploy_basename = glnx_basename (treebootserial_target);
if (!_ostree_sysroot_parse_deploy_path_name (deploy_basename,
&treecsum, &deployserial, error))
goto out;
if (!glnx_opendirat (self->sysroot_fd, relative_boot_link, TRUE,
&deployment_dfd, error))
goto out;
if (!parse_origin (self, deployment_dfd, deploy_basename, &origin,
cancellable, error))
goto out;
ret_deployment = ostree_deployment_new (-1, osname, treecsum, deployserial,
bootcsum, treebootserial);
if (origin)
ostree_deployment_set_origin (ret_deployment, origin);
ret = TRUE;
gs_transfer_out_value (out_deployment, &ret_deployment);
out:
if (origin)
g_key_file_unref (origin);
return ret;
}
static char *
get_ostree_kernel_arg_from_config (OstreeBootconfigParser *config)
{
const char *options;
char *ret = NULL;
char **opts, **iter;
options = ostree_bootconfig_parser_get (config, "options");
if (!options)
return NULL;
opts = g_strsplit (options, " ", -1);
for (iter = opts; *iter; iter++)
{
const char *opt = *iter;
if (g_str_has_prefix (opt, "ostree="))
{
ret = g_strdup (opt + strlen ("ostree="));
break;
}
}
g_strfreev (opts);
return ret;
}
static gboolean
list_deployments_process_one_boot_entry (OstreeSysroot *self,
OstreeBootconfigParser *config,
GPtrArray *inout_deployments,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autofree char *ostree_arg = NULL;
glnx_unref_object OstreeDeployment *deployment = NULL;
ostree_arg = get_ostree_kernel_arg_from_config (config);
if (ostree_arg == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"No ostree= kernel argument found");
goto out;
}
if (!parse_deployment (self, ostree_arg, &deployment,
cancellable, error))
goto out;
ostree_deployment_set_bootconfig (deployment, config);
g_ptr_array_add (inout_deployments, g_object_ref (deployment));
ret = TRUE;
out:
return ret;
}
static gint
compare_deployments_by_boot_loader_version_reversed (gconstpointer a_pp,
gconstpointer b_pp)
{
OstreeDeployment *a = *((OstreeDeployment**)a_pp);
OstreeDeployment *b = *((OstreeDeployment**)b_pp);
OstreeBootconfigParser *a_bootconfig = ostree_deployment_get_bootconfig (a);
OstreeBootconfigParser *b_bootconfig = ostree_deployment_get_bootconfig (b);
return compare_boot_loader_configs (a_bootconfig, b_bootconfig);
}
/**
* ostree_sysroot_load:
* @self: Sysroot
* @cancellable: Cancellable
* @error: Error
*
* Load deployment list, bootversion, and subbootversion from the
* rootfs @self.
*/
gboolean
ostree_sysroot_load (OstreeSysroot *self,
GCancellable *cancellable,
GError **error)
{
return ostree_sysroot_load_if_changed (self, NULL, cancellable, error);
}
gboolean
ostree_sysroot_load_if_changed (OstreeSysroot *self,
gboolean *out_changed,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
guint i;
int bootversion = 0;
int subbootversion = 0;
struct stat stbuf;
g_autoptr(GPtrArray) boot_loader_configs = NULL;
g_autoptr(GPtrArray) deployments = NULL;
if (!ensure_sysroot_fd (self, error))
goto out;
if (!read_current_bootversion (self, &bootversion, cancellable, error))
goto out;
if (!_ostree_sysroot_read_current_subbootversion (self, bootversion, &subbootversion,
cancellable, error))
goto out;
if (fstatat (self->sysroot_fd, "ostree/deploy", &stbuf, 0) < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (out_changed)
{
if (self->loaded_ts.tv_sec == stbuf.st_mtim.tv_sec &&
self->loaded_ts.tv_nsec == stbuf.st_mtim.tv_nsec)
{
*out_changed = FALSE;
ret = TRUE;
goto out;
}
}
g_clear_pointer (&self->deployments, g_ptr_array_unref);
g_clear_pointer (&self->booted_deployment, g_object_unref);
self->bootversion = -1;
self->subbootversion = -1;
if (!_ostree_sysroot_read_boot_loader_configs (self, bootversion, &boot_loader_configs,
cancellable, error))
goto out;
deployments = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
for (i = 0; i < boot_loader_configs->len; i++)
{
OstreeBootconfigParser *config = boot_loader_configs->pdata[i];
if (!list_deployments_process_one_boot_entry (self, config, deployments,
cancellable, error))
goto out;
}
g_ptr_array_sort (deployments, compare_deployments_by_boot_loader_version_reversed);
for (i = 0; i < deployments->len; i++)
{
OstreeDeployment *deployment = deployments->pdata[i];
ostree_deployment_set_index (deployment, i);
}
if (!find_booted_deployment (self, deployments, &self->booted_deployment,
cancellable, error))
goto out;
self->bootversion = bootversion;
self->subbootversion = subbootversion;
self->deployments = deployments;
deployments = NULL; /* Transfer ownership */
self->loaded = TRUE;
self->loaded_ts = stbuf.st_mtim;
ret = TRUE;
if (out_changed)
*out_changed = TRUE;
out:
return ret;
}
int
ostree_sysroot_get_bootversion (OstreeSysroot *self)
{
return self->bootversion;
}
int
ostree_sysroot_get_subbootversion (OstreeSysroot *self)
{
return self->subbootversion;
}
/**
* ostree_sysroot_get_booted_deployment:
* @self: Sysroot
*
* Returns: (transfer none): The currently booted deployment, or %NULL if none
*/
OstreeDeployment *
ostree_sysroot_get_booted_deployment (OstreeSysroot *self)
{
g_return_val_if_fail (self->loaded, NULL);
return self->booted_deployment;
}
/**
* ostree_sysroot_get_deployments:
* @self: Sysroot
*
* Returns: (element-type OstreeDeployment) (transfer container): Ordered list of deployments
*/
GPtrArray *
ostree_sysroot_get_deployments (OstreeSysroot *self)
{
GPtrArray *copy;
guint i;
g_return_val_if_fail (self->loaded, NULL);
copy = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
for (i = 0; i < self->deployments->len; i++)
g_ptr_array_add (copy, g_object_ref (self->deployments->pdata[i]));
return copy;
}
/**
* ostree_sysroot_get_deployment_dirpath:
* @self: Repo
* @deployment: A deployment
*
* Note this function only returns a *relative* path - if you want
* to access, it, you must either use fd-relative api such as openat(),
* or concatenate it with the full ostree_sysroot_get_path().
*
* Returns: (transfer full): Path to deployment root directory, relative to sysroot
*/
char *
ostree_sysroot_get_deployment_dirpath (OstreeSysroot *self,
OstreeDeployment *deployment)
{
return g_strdup_printf ("ostree/deploy/%s/deploy/%s.%d",
ostree_deployment_get_osname (deployment),
ostree_deployment_get_csum (deployment),
ostree_deployment_get_deployserial (deployment));
}
/**
* ostree_sysroot_get_deployment_directory:
* @self: Sysroot
* @deployment: A deployment
*
* Returns: (transfer full): Path to deployment root directory
*/
GFile *
ostree_sysroot_get_deployment_directory (OstreeSysroot *self,
OstreeDeployment *deployment)
{
return g_file_resolve_relative_path (self->path, ostree_sysroot_get_deployment_dirpath (self, deployment));
}
/**
* ostree_sysroot_get_deployment_origin_path:
* @deployment_path: A deployment path
*
* Returns: (transfer full): Path to deployment origin file
*/
GFile *
ostree_sysroot_get_deployment_origin_path (GFile *deployment_path)
{
g_autoptr(GFile) deployment_parent = g_file_get_parent (deployment_path);
return ot_gfile_resolve_path_printf (deployment_parent,
"%s.origin",
gs_file_get_path_cached (deployment_path));
}
/**
* ostree_sysroot_get_repo:
* @self: Sysroot
* @out_repo: (out): Repository in sysroot @self
* @cancellable: Cancellable
* @error: Error
*
* Retrieve the OSTree repository in sysroot @self.
*/
gboolean
ostree_sysroot_get_repo (OstreeSysroot *self,
OstreeRepo **out_repo,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
/* ostree_repo_open() is idempotent. */
if (!ostree_repo_open (self->repo, cancellable, error))
goto out;
if (out_repo != NULL)
*out_repo = g_object_ref (self->repo);
ret = TRUE;
out:
return ret;
}
/**
* ostree_sysroot_query_bootloader:
* @sysroot: Sysroot
* @out_bootloader: (out) (transfer full) (allow-none): Return location for bootloader, may be %NULL
* @cancellable: Cancellable
* @error: Error
*/
gboolean
_ostree_sysroot_query_bootloader (OstreeSysroot *sysroot,
OstreeBootloader **out_bootloader,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
gboolean is_active;
glnx_unref_object OstreeBootloader *ret_loader = NULL;
ret_loader = (OstreeBootloader*)_ostree_bootloader_syslinux_new (sysroot);
if (!_ostree_bootloader_query (ret_loader, &is_active,
cancellable, error))
goto out;
if (!is_active)
{
g_object_unref (ret_loader);
ret_loader = (OstreeBootloader*)_ostree_bootloader_grub2_new (sysroot);
if (!_ostree_bootloader_query (ret_loader, &is_active,
cancellable, error))
goto out;
}
if (!is_active)
{
g_object_unref (ret_loader);
ret_loader = (OstreeBootloader*)_ostree_bootloader_uboot_new (sysroot);
if (!_ostree_bootloader_query (ret_loader, &is_active, cancellable, error))
goto out;
}
if (!is_active)
g_clear_object (&ret_loader);
ret = TRUE;
gs_transfer_out_value (out_bootloader, &ret_loader);
out:
return ret;
}
char *
_ostree_sysroot_join_lines (GPtrArray *lines)
{
GString *buf = g_string_new ("");
guint i;
gboolean prev_was_empty = FALSE;
for (i = 0; i < lines->len; i++)
{
const char *line = lines->pdata[i];
/* Special bit to remove extraneous empty lines */
if (*line == '\0')
{
if (prev_was_empty || i == 0)
continue;
else
prev_was_empty = TRUE;
}
g_string_append (buf, line);
g_string_append_c (buf, '\n');
}
return g_string_free (buf, FALSE);
}
static gboolean
parse_kernel_commandline (OstreeKernelArgs **out_args,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autoptr(GFile) proc_cmdline = g_file_new_for_path ("/proc/cmdline");
g_autofree char *contents = NULL;
gsize len;
if (!g_file_load_contents (proc_cmdline, cancellable, &contents, &len, NULL,
error))
goto out;
g_strchomp (contents);
ret = TRUE;
*out_args = _ostree_kernel_args_from_string (contents);
out:
return ret;
}
static gboolean
find_booted_deployment (OstreeSysroot *self,
GPtrArray *deployments,
OstreeDeployment **out_deployment,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
struct stat root_stbuf;
struct stat self_stbuf;
glnx_unref_object OstreeDeployment *ret_deployment = NULL;
if (stat ("/", &root_stbuf) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (!ensure_sysroot_fd (self, error))
goto out;
if (fstat (self->sysroot_fd, &self_stbuf) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (root_stbuf.st_dev == self_stbuf.st_dev &&
root_stbuf.st_ino == self_stbuf.st_ino)
{
guint i;
const char *bootlink_arg;
__attribute__((cleanup(_ostree_kernel_args_cleanup))) OstreeKernelArgs *kernel_args = NULL;
if (!parse_kernel_commandline (&kernel_args, cancellable, error))
goto out;
bootlink_arg = _ostree_kernel_args_get_last_value (kernel_args, "ostree");
if (bootlink_arg)
{
for (i = 0; i < deployments->len; i++)
{
OstreeDeployment *deployment = deployments->pdata[i];
g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment);
struct stat stbuf;
if (fstatat (self->sysroot_fd, deployment_path, &stbuf, 0) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (stbuf.st_dev == root_stbuf.st_dev &&
stbuf.st_ino == root_stbuf.st_ino)
{
ret_deployment = g_object_ref (deployment);
break;
}
}
if (ret_deployment == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Unexpected state: ostree= kernel argument found, but / is not a deployment root");
goto out;
}
}
else
{
/* Not an ostree system */
}
}
ret = TRUE;
ot_transfer_out_value (out_deployment, &ret_deployment);
out:
return ret;
}
/**
* ostree_sysroot_get_merge_deployment:
* @self: Sysroot
* @osname: (allow-none): Operating system group
*
* Find the deployment to use as a configuration merge source; this is
* the first one in the current deployment list which matches osname.
*
* Returns: (transfer full): Configuration merge deployment
*/
OstreeDeployment *
ostree_sysroot_get_merge_deployment (OstreeSysroot *self,
const char *osname)
{
g_return_val_if_fail (osname != NULL || self->booted_deployment != NULL, NULL);
if (osname == NULL)
osname = ostree_deployment_get_osname (self->booted_deployment);
/* If we're booted into the OS into which we're deploying, then
* merge the currently *booted* configuration, rather than the most
* recently deployed.
*/
if (self->booted_deployment &&
g_strcmp0 (ostree_deployment_get_osname (self->booted_deployment), osname) == 0)
{
return g_object_ref (self->booted_deployment);
}
else
{
guint i;
for (i = 0; i < self->deployments->len; i++)
{
OstreeDeployment *deployment = self->deployments->pdata[i];
if (strcmp (ostree_deployment_get_osname (deployment), osname) != 0)
continue;
return g_object_ref (deployment);
}
}
return NULL;
}
/**
* ostree_sysroot_origin_new_from_refspec:
* @refspec: A refspec
*
* Returns: (transfer full): A new config file which sets @refspec as an origin
*/
GKeyFile *
ostree_sysroot_origin_new_from_refspec (OstreeSysroot *sysroot,
const char *refspec)
{
GKeyFile *ret = g_key_file_new ();
g_key_file_set_string (ret, "origin", "refspec", refspec);
return ret;
}
/**
* ostree_sysroot_lock:
* @self: Self
* @error: Error
*
* Acquire an exclusive multi-process write lock for @self. This call
* blocks until the lock has been acquired. The lock is not
* reentrant.
*
* Release the lock with ostree_sysroot_unlock(). The lock will also
* be released if @self is deallocated.
*/
gboolean
ostree_sysroot_lock (OstreeSysroot *self,
GError **error)
{
if (!ensure_sysroot_fd (self, error))
return FALSE;
return glnx_make_lock_file (self->sysroot_fd, OSTREE_SYSROOT_LOCKFILE,
LOCK_EX, &self->lock, error);
}
/**
* ostree_sysroot_try_lock:
* @self: Self
* @out_acquired: (out): Whether or not the lock has been acquired
* @error: Error
*
* Try to acquire an exclusive multi-process write lock for @self. If
* another process holds the lock, this function will return
* immediately, setting @out_acquired to %FALSE, and returning %TRUE
* (and no error).
*
* Release the lock with ostree_sysroot_unlock(). The lock will also
* be released if @self is deallocated.
*/
gboolean
ostree_sysroot_try_lock (OstreeSysroot *self,
gboolean *out_acquired,
GError **error)
{
gboolean ret = FALSE;
GError *local_error = NULL;
if (!ensure_sysroot_fd (self, error))
goto out;
/* Note use of LOCK_NB */
if (!glnx_make_lock_file (self->sysroot_fd, OSTREE_SYSROOT_LOCKFILE,
LOCK_EX | LOCK_NB, &self->lock, &local_error))
{
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
{
*out_acquired = FALSE;
}
else
{
g_propagate_error (error, local_error);
goto out;
}
}
else
{
*out_acquired = TRUE;
}
ret = TRUE;
out:
return ret;
}
/**
* ostree_sysroot_unlock:
* @self: Self
*
* Clear the lock previously acquired with ostree_sysroot_lock(). It
* is safe to call this function if the lock has not been previously
* acquired.
*/
void
ostree_sysroot_unlock (OstreeSysroot *self)
{
glnx_release_lock_file (&self->lock);
}
static void
lock_in_thread (GTask *task,
gpointer source,
gpointer task_data,
GCancellable *cancellable)
{
GError *local_error = NULL;
OstreeSysroot *self = source;
if (!ostree_sysroot_lock (self, &local_error))
goto out;
if (g_cancellable_set_error_if_cancelled (cancellable, &local_error))
ostree_sysroot_unlock (self);
out:
if (local_error)
g_task_return_error (task, local_error);
else
g_task_return_boolean (task, TRUE);
}
/**
* ostree_sysroot_lock_async:
* @self: Self
* @cancellable: Cancellable
* @callback: Callback
* @user_data: User data
*
* An asynchronous version of ostree_sysroot_lock().
*/
void
ostree_sysroot_lock_async (OstreeSysroot *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = g_task_new (self, cancellable, callback, user_data);
g_task_run_in_thread (task, lock_in_thread);
}
/**
* ostree_sysroot_lock_finish:
* @self: Self
* @result: Result
* @error: Error
*
* Call when ostree_sysroot_lock_async() is ready.
*/
gboolean
ostree_sysroot_lock_finish (OstreeSysroot *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
return g_task_propagate_boolean ((GTask*)result, error);
}
/**
* ostree_sysroot_init_osname:
* @self: Sysroot
* @osname: Name group of operating system checkouts
* @cancellable: Cancellable
* @error: Error
*
* Initialize the directory structure for an "osname", which is a
* group of operating system deployments, with a shared `/var`. One
* is required for generating a deployment.
*/
gboolean
ostree_sysroot_init_osname (OstreeSysroot *self,
const char *osname,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
const char *deploydir = glnx_strjoina ("ostree/deploy/", osname);
glnx_fd_close int dfd = -1;
if (!ensure_sysroot_fd (self, error))
goto out;
if (mkdirat (self->sysroot_fd, deploydir, 0777) < 0)
{
glnx_set_prefix_error_from_errno (error, "Creating %s", deploydir);
goto out;
}
if (!glnx_opendirat (self->sysroot_fd, deploydir, TRUE, &dfd, error))
goto out;
if (mkdirat (dfd, "var", 0777) < 0)
{
glnx_set_prefix_error_from_errno (error, "Creating %s", "var");
goto out;
}
/* This is a bit of a legacy hack...but we have to keep it around
* now. We're ensuring core subdirectories of /var exist.
*/
if (mkdirat (dfd, "var/tmp", 0777) < 0)
{
glnx_set_prefix_error_from_errno (error, "Creating %s", "var/tmp");
goto out;
}
if (fchmodat (dfd, "var/tmp", 01777, 0) < 0)
{
glnx_set_prefix_error_from_errno (error, "Fchmod %s", "var/tmp");
goto out;
}
if (mkdirat (dfd, "var/lib", 0777) < 0)
{
glnx_set_prefix_error_from_errno (error, "Creating %s", "var/tmp");
goto out;
}
if (symlinkat ("../run", dfd, "var/run") < 0)
{
glnx_set_prefix_error_from_errno (error, "Symlinking %s", "var/run");
goto out;
}
if (symlinkat ("../run/lock", dfd, "var/lock") < 0)
{
glnx_set_prefix_error_from_errno (error, "Symlinking %s", "var/lock");
goto out;
}
if (!_ostree_sysroot_bump_mtime (self, error))
goto out;
ret = TRUE;
out:
return ret;
}
/**
* ostree_sysroot_simple_write_deployment:
* @sysroot: Sysroot
* @osname: (allow-none): OS name
* @new_deployment: Prepend this deployment to the list
* @merge_deployment: (allow-none): Use this deployment for configuration merge
* @flags: Flags controlling behavior
* @cancellable: Cancellable
* @error: Error
*
* Prepend @new_deployment to the list of deployments, commit, and
* cleanup. By default, all other deployments for the given @osname
* except the merge deployment and the booted deployment will be
* garbage collected.
*
* If %OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN is
* specified, then all current deployments will be kept.
*/
gboolean
ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot,
const char *osname,
OstreeDeployment *new_deployment,
OstreeDeployment *merge_deployment,
OstreeSysrootSimpleWriteDeploymentFlags flags,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
guint i;
OstreeDeployment *booted_deployment = NULL;
g_autoptr(GPtrArray) deployments = NULL;
g_autoptr(GPtrArray) new_deployments = g_ptr_array_new_with_free_func (g_object_unref);
gboolean retain = (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN) > 0;
deployments = ostree_sysroot_get_deployments (sysroot);
booted_deployment = ostree_sysroot_get_booted_deployment (sysroot);
if (osname == NULL && booted_deployment)
osname = ostree_deployment_get_osname (booted_deployment);
g_ptr_array_add (new_deployments, g_object_ref (new_deployment));
for (i = 0; i < deployments->len; i++)
{
OstreeDeployment *deployment = deployments->pdata[i];
/* Keep deployments with different osnames, as well as the
* booted and merge deployments
*/
if (retain ||
(osname != NULL &&
strcmp (ostree_deployment_get_osname (deployment), osname) != 0) ||
ostree_deployment_equal (deployment, booted_deployment) ||
ostree_deployment_equal (deployment, merge_deployment))
{
g_ptr_array_add (new_deployments, g_object_ref (deployment));
}
}
if (!ostree_sysroot_write_deployments (sysroot, new_deployments, cancellable, error))
goto out;
if (!ostree_sysroot_cleanup (sysroot, cancellable, error))
goto out;
ret = TRUE;
out:
return ret;
}