/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2013 Colin Walters * * 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 (®ex_initialized)) { regex = g_regex_new ("^([0-9a-f]+)\\.([0-9]+)$", 0, 0, NULL); g_assert (regex); g_once_init_leave (®ex_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 (®ex_initialized)) { regex = g_regex_new ("^/ostree/boot.([01])/([^/]+)/([^/]+)/([0-9]+)$", 0, 0, NULL); g_assert (regex); g_once_init_leave (®ex_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; }