0cd584ccf6
Per OSTree design, /var can start out empty. However, our warning spam here is annoying. Let's first delete some known files - obviously this won't be exhaustive, but it's way faster than trying to fix all of this in the packages right now. The major one is the SELinux policy, which resulted in a lot of spam. Closes: #473 Approved by: jlebon
2039 lines
62 KiB
C
2039 lines
62 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
|
*
|
|
* Copyright (C) 2013,2014 Colin Walters <walters@verbum.org>
|
|
*
|
|
* 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 licence 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 "string.h"
|
|
|
|
#include <ostree.h>
|
|
#include <errno.h>
|
|
#include <json-glib/json-glib.h>
|
|
#include <stdio.h>
|
|
#include <utime.h>
|
|
#include <err.h>
|
|
#include <sys/types.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <unistd.h>
|
|
#include <libglnx.h>
|
|
#include <stdlib.h>
|
|
#include <gio/gunixinputstream.h>
|
|
#include <gio/gunixoutputstream.h>
|
|
|
|
#include "rpmostree-postprocess.h"
|
|
#include "rpmostree-bwrap.h"
|
|
#include "rpmostree-passwd-util.h"
|
|
#include "rpmostree-rpm-util.h"
|
|
#include "rpmostree-json-parsing.h"
|
|
#include "rpmostree-util.h"
|
|
|
|
typedef enum {
|
|
RPMOSTREE_POSTPROCESS_BOOT_LOCATION_LEGACY,
|
|
RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH,
|
|
RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW
|
|
} RpmOstreePostprocessBootLocation;
|
|
|
|
static gboolean
|
|
move_to_dir (GFile *src,
|
|
GFile *dest_dir,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GFile) dest =
|
|
g_file_get_child (dest_dir, gs_file_get_basename_cached (src));
|
|
|
|
return gs_file_rename (src, dest, cancellable, error);
|
|
}
|
|
|
|
static gboolean
|
|
run_sync_in_root_at (int rootfs_fd,
|
|
const char *binpath,
|
|
char **child_argv,
|
|
GError **error)
|
|
{
|
|
const GSpawnFlags bwrap_spawnflags = G_SPAWN_SEARCH_PATH;
|
|
g_autoptr(GPtrArray) bwrap_argv = NULL;
|
|
|
|
bwrap_argv = rpmostree_bwrap_base_argv_new_for_rootfs (rootfs_fd, error);
|
|
if (!bwrap_argv)
|
|
return FALSE;
|
|
|
|
/* Bind all of the primary toplevel dirs; unlike the script case, treecompose
|
|
* isn't yet operating on hardlinks, so we can just bind mount things mutably.
|
|
*/
|
|
rpmostree_ptrarray_append_strdup (bwrap_argv,
|
|
"--bind", "usr", "/usr",
|
|
"--bind", "var", "/var",
|
|
"--bind", "etc", "/etc",
|
|
NULL);
|
|
|
|
g_ptr_array_add (bwrap_argv, g_strdup (binpath));
|
|
/* https://github.com/projectatomic/bubblewrap/issues/91 */
|
|
{ gboolean first = TRUE;
|
|
for (char **iter = child_argv; iter && *iter; iter++)
|
|
{
|
|
if (first)
|
|
first = FALSE;
|
|
else
|
|
g_ptr_array_add (bwrap_argv, g_strdup (*iter));
|
|
}
|
|
}
|
|
g_ptr_array_add (bwrap_argv, NULL);
|
|
|
|
if (!rpmostree_run_sync_fchdir_setup ((char**)bwrap_argv->pdata, bwrap_spawnflags,
|
|
rootfs_fd, error))
|
|
{
|
|
g_prefix_error (error, "Executing bwrap: ");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
run_sync_in_root (GFile *path,
|
|
const char *binpath,
|
|
char **child_argv,
|
|
GError **error)
|
|
{
|
|
glnx_fd_close int dfd = -1;
|
|
|
|
if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (path), TRUE, &dfd, error))
|
|
return FALSE;
|
|
|
|
return run_sync_in_root_at (dfd, binpath, child_argv, error);
|
|
}
|
|
|
|
typedef struct {
|
|
const char *target;
|
|
const char *src;
|
|
} Symlink;
|
|
|
|
static gboolean
|
|
init_rootfs (GFile *targetroot,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
g_autoptr(GFile) child = NULL;
|
|
guint i;
|
|
const char *toplevel_dirs[] = { "dev", "proc", "run", "sys", "var", "sysroot" };
|
|
const Symlink symlinks[] = {
|
|
{ "var/opt", "opt" },
|
|
{ "var/srv", "srv" },
|
|
{ "var/mnt", "mnt" },
|
|
{ "var/roothome", "root" },
|
|
{ "var/home", "home" },
|
|
{ "run/media", "media" },
|
|
{ "sysroot/ostree", "ostree" },
|
|
{ "sysroot/tmp", "tmp" },
|
|
};
|
|
|
|
if (!gs_file_ensure_directory (targetroot, TRUE,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (toplevel_dirs); i++)
|
|
{
|
|
g_autoptr(GFile) dir = g_file_get_child (targetroot, toplevel_dirs[i]);
|
|
|
|
if (!gs_file_ensure_directory (dir, TRUE,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (symlinks); i++)
|
|
{
|
|
const Symlink*linkinfo = symlinks + i;
|
|
g_autoptr(GFile) src =
|
|
g_file_resolve_relative_path (targetroot, linkinfo->src);
|
|
|
|
if (!g_file_make_symbolic_link (src, linkinfo->target,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
find_kernel_and_initramfs_in_bootdir (GFile *bootdir,
|
|
GFile **out_kernel,
|
|
GFile **out_initramfs,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
g_autoptr(GFileEnumerator) direnum = NULL;
|
|
g_autoptr(GFile) ret_kernel = NULL;
|
|
g_autoptr(GFile) ret_initramfs = NULL;
|
|
|
|
direnum = g_file_enumerate_children (bootdir, "standard::name", 0,
|
|
cancellable, error);
|
|
if (!direnum)
|
|
goto out;
|
|
|
|
while (TRUE)
|
|
{
|
|
const char *name;
|
|
GFileInfo *file_info;
|
|
GFile *child;
|
|
|
|
if (!gs_file_enumerator_iterate (direnum, &file_info, &child,
|
|
cancellable, error))
|
|
goto out;
|
|
if (!file_info)
|
|
break;
|
|
|
|
name = g_file_info_get_name (file_info);
|
|
|
|
/* Current Fedora 23 kernel.spec installs as just vmlinuz */
|
|
if (strcmp (name, "vmlinuz") == 0 || g_str_has_prefix (name, "vmlinuz-"))
|
|
{
|
|
if (ret_kernel)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Multiple vmlinuz- in %s",
|
|
gs_file_get_path_cached (bootdir));
|
|
goto out;
|
|
}
|
|
ret_kernel = g_object_ref (child);
|
|
}
|
|
else if (g_str_has_prefix (name, "initramfs-"))
|
|
{
|
|
if (ret_initramfs)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Multiple initramfs- in %s",
|
|
gs_file_get_path_cached (bootdir));
|
|
goto out;
|
|
}
|
|
ret_initramfs = g_object_ref (child);
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
gs_transfer_out_value (out_kernel, &ret_kernel);
|
|
gs_transfer_out_value (out_initramfs, &ret_initramfs);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/* Given a directory @d, find the first child that is a directory,
|
|
* returning it in @out_subdir. If there are multiple directories,
|
|
* return an error.
|
|
*/
|
|
static gboolean
|
|
find_ensure_one_subdirectory (GFile *d,
|
|
GFile **out_subdir,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
g_autoptr(GFileEnumerator) direnum = NULL;
|
|
g_autoptr(GFile) ret_subdir = NULL;
|
|
|
|
direnum = g_file_enumerate_children (d, "standard::name,standard::type", 0,
|
|
cancellable, error);
|
|
if (!direnum)
|
|
goto out;
|
|
|
|
while (TRUE)
|
|
{
|
|
GFileInfo *file_info;
|
|
GFile *child;
|
|
|
|
if (!gs_file_enumerator_iterate (direnum, &file_info, &child,
|
|
cancellable, error))
|
|
goto out;
|
|
if (!file_info)
|
|
break;
|
|
|
|
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
|
|
{
|
|
if (ret_subdir)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Multiple subdirectories found in: %s", gs_file_get_path_cached (d));
|
|
goto out;
|
|
}
|
|
ret_subdir = g_object_ref (child);
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
gs_transfer_out_value (out_subdir, &ret_subdir);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
dracut_supports_reproducible (GFile *root, gboolean *supported,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
int pid, stdout[2];
|
|
if (pipe (stdout) < 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
pid = fork ();
|
|
if (pid < 0)
|
|
{
|
|
close (stdout[0]);
|
|
close (stdout[1]);
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Check that --reproducible is present in the --help output. */
|
|
if (pid == 0)
|
|
{
|
|
int null;
|
|
char *child_argv[] = { "dracut", "--help", NULL };
|
|
|
|
null = open ("/dev/null", O_RDWR);
|
|
if (null < 0
|
|
|| close (stdout[0]) < 0
|
|
|| dup2 (stdout[1], 1) < 0
|
|
|| dup2 (null, 0) < 0
|
|
|| dup2 (null, 2) < 0)
|
|
_exit (1);
|
|
|
|
run_sync_in_root (root, "dracut", child_argv, NULL);
|
|
_exit (1);
|
|
}
|
|
else
|
|
{
|
|
gsize read = 0;
|
|
/* the dracut 0.43 --help output is about 8Kb, leave some room. */
|
|
const gsize buffer_size = 16384;
|
|
g_autofree gchar *buffer = g_new (gchar, buffer_size);
|
|
g_autoptr(GInputStream) in = g_unix_input_stream_new (stdout[0], TRUE);
|
|
|
|
if (close (stdout[1]) < 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_input_stream_read_all (in, buffer, buffer_size, &read,
|
|
cancellable, error))
|
|
return FALSE;
|
|
|
|
*supported = g_strstr_len (buffer, read, "--reproducible") != NULL;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
do_kernel_prep (GFile *yumroot,
|
|
JsonObject *treefile,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
g_autoptr(GFile) bootdir =
|
|
g_file_get_child (yumroot, "boot");
|
|
g_autoptr(GFile) kernel_path = NULL;
|
|
g_autoptr(GFile) initramfs_path = NULL;
|
|
const char *boot_checksum_str = NULL;
|
|
GChecksum *boot_checksum = NULL;
|
|
g_autofree char *kver = NULL;
|
|
|
|
if (!find_kernel_and_initramfs_in_bootdir (bootdir, &kernel_path,
|
|
&initramfs_path,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
if (kernel_path == NULL)
|
|
{
|
|
g_autoptr(GFile) mod_dir = g_file_resolve_relative_path (yumroot, "usr/lib/modules");
|
|
g_autoptr(GFile) modversion_dir = NULL;
|
|
|
|
if (!find_ensure_one_subdirectory (mod_dir, &modversion_dir, cancellable, error))
|
|
goto out;
|
|
|
|
if (modversion_dir)
|
|
{
|
|
kver = g_file_get_basename (modversion_dir);
|
|
if (!find_kernel_and_initramfs_in_bootdir (modversion_dir, &kernel_path,
|
|
&initramfs_path,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (kernel_path == NULL)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Unable to find kernel (vmlinuz) in /boot or /usr/lib/modules");
|
|
goto out;
|
|
}
|
|
|
|
if (initramfs_path)
|
|
{
|
|
g_print ("Removing RPM-generated '%s'\n",
|
|
gs_file_get_path_cached (initramfs_path));
|
|
if (!gs_shutil_rm_rf (initramfs_path, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
if (!kver)
|
|
{
|
|
const char *kname = gs_file_get_basename_cached (kernel_path);
|
|
const char *kver_p;
|
|
|
|
kver_p = strchr (kname, '-');
|
|
g_assert (kver_p);
|
|
kver = g_strdup (kver_p + 1);
|
|
}
|
|
|
|
/* OSTree needs to own this */
|
|
{
|
|
g_autoptr(GFile) loaderdir = g_file_get_child (bootdir, "loader");
|
|
if (!gs_shutil_rm_rf (loaderdir, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
{
|
|
char *child_argv[] = { "depmod", (char*)kver, NULL };
|
|
if (!run_sync_in_root (yumroot, "depmod", child_argv, error))
|
|
goto out;
|
|
}
|
|
|
|
/* Ensure the /etc/machine-id file is present and empty. Apparently systemd
|
|
doesn't work when the file is missing (as of systemd-219-9.fc22) but it is
|
|
correctly populated if the file is there. */
|
|
g_print ("Creating empty machine-id\n");
|
|
{
|
|
const char *hardcoded_machine_id = "";
|
|
g_autoptr(GFile) machineid_path =
|
|
g_file_resolve_relative_path (yumroot, "etc/machine-id");
|
|
if (!g_file_replace_contents (machineid_path, hardcoded_machine_id,
|
|
strlen (hardcoded_machine_id),
|
|
NULL, FALSE, 0, NULL,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
{
|
|
gboolean reproducible;
|
|
g_autoptr(GPtrArray) dracut_argv = g_ptr_array_new ();
|
|
|
|
if (!dracut_supports_reproducible (yumroot, &reproducible, cancellable, error))
|
|
goto out;
|
|
|
|
g_ptr_array_add (dracut_argv, "dracut");
|
|
g_ptr_array_add (dracut_argv, "-v");
|
|
if (reproducible)
|
|
{
|
|
g_ptr_array_add (dracut_argv, "--reproducible");
|
|
g_ptr_array_add (dracut_argv, "--gzip");
|
|
}
|
|
g_ptr_array_add (dracut_argv, "--add");
|
|
g_ptr_array_add (dracut_argv, "ostree");
|
|
g_ptr_array_add (dracut_argv, "--tmpdir=/tmp");
|
|
g_ptr_array_add (dracut_argv, "-f");
|
|
g_ptr_array_add (dracut_argv, "/var/tmp/initramfs.img");
|
|
g_ptr_array_add (dracut_argv, (char*)kver);
|
|
|
|
if (json_object_has_member (treefile, "initramfs-args"))
|
|
{
|
|
guint i, len;
|
|
JsonArray *initramfs_args;
|
|
|
|
initramfs_args = json_object_get_array_member (treefile, "initramfs-args");
|
|
len = json_array_get_length (initramfs_args);
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
const char *arg = _rpmostree_jsonutil_array_require_string_element (initramfs_args, i, error);
|
|
if (!arg)
|
|
goto out;
|
|
g_ptr_array_add (dracut_argv, (char*)arg);
|
|
}
|
|
}
|
|
|
|
g_ptr_array_add (dracut_argv, NULL);
|
|
|
|
if (!run_sync_in_root (yumroot, "dracut", (char**)dracut_argv->pdata, error))
|
|
goto out;
|
|
}
|
|
|
|
initramfs_path = g_file_resolve_relative_path (yumroot, "var/tmp/initramfs.img");
|
|
if (!g_file_query_exists (initramfs_path, NULL))
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Dracut failed to generate '%s'",
|
|
gs_file_get_path_cached (initramfs_path));
|
|
goto out;
|
|
}
|
|
|
|
{
|
|
g_autofree char *initramfs_name = g_strconcat ("initramfs-", kver, ".img", NULL);
|
|
g_autoptr(GFile) initramfs_dest =
|
|
g_file_get_child (bootdir, initramfs_name);
|
|
|
|
if (!gs_file_rename (initramfs_path, initramfs_dest,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
/* Transfer ownership */
|
|
g_object_unref (initramfs_path);
|
|
initramfs_path = initramfs_dest;
|
|
initramfs_dest = NULL;
|
|
}
|
|
|
|
boot_checksum = g_checksum_new (G_CHECKSUM_SHA256);
|
|
if (!_rpmostree_util_update_checksum_from_file (boot_checksum, kernel_path,
|
|
cancellable, error))
|
|
goto out;
|
|
if (!_rpmostree_util_update_checksum_from_file (boot_checksum, initramfs_path,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
boot_checksum_str = g_checksum_get_string (boot_checksum);
|
|
|
|
{
|
|
g_autofree char *new_kernel_name =
|
|
g_strconcat (gs_file_get_basename_cached (kernel_path), "-",
|
|
boot_checksum_str, NULL);
|
|
g_autoptr(GFile) new_kernel_path =
|
|
g_file_get_child (bootdir, new_kernel_name);
|
|
g_autofree char *new_initramfs_name =
|
|
g_strconcat (gs_file_get_basename_cached (initramfs_path), "-",
|
|
boot_checksum_str, NULL);
|
|
g_autoptr(GFile) new_initramfs_path =
|
|
g_file_get_child (bootdir, new_initramfs_name);
|
|
|
|
if (!gs_file_rename (kernel_path, new_kernel_path,
|
|
cancellable, error))
|
|
goto out;
|
|
if (!gs_file_rename (initramfs_path, new_initramfs_path,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
if (boot_checksum) g_checksum_free (boot_checksum);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
convert_var_to_tmpfiles_d_recurse (GOutputStream *tmpfiles_out,
|
|
int dfd,
|
|
GString *prefix,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
|
gsize bytes_written;
|
|
|
|
if (!glnx_dirfd_iterator_init_at (dfd, prefix->str + 1, TRUE, &dfd_iter, error))
|
|
goto out;
|
|
|
|
while (TRUE)
|
|
{
|
|
struct dirent *dent = NULL;
|
|
GString *tmpfiles_d_buf;
|
|
g_autofree char *tmpfiles_d_line = NULL;
|
|
char filetype_c;
|
|
g_autofree char *relpath = NULL;
|
|
|
|
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
|
|
goto out;
|
|
|
|
if (!dent)
|
|
break;
|
|
|
|
switch (dent->d_type)
|
|
{
|
|
case DT_DIR:
|
|
filetype_c = 'd';
|
|
break;
|
|
case DT_LNK:
|
|
filetype_c = 'L';
|
|
break;
|
|
default:
|
|
g_print ("Ignoring non-directory/non-symlink '%s'\n",
|
|
dent->d_name);
|
|
continue;
|
|
}
|
|
|
|
tmpfiles_d_buf = g_string_new ("");
|
|
g_string_append_c (tmpfiles_d_buf, filetype_c);
|
|
g_string_append_c (tmpfiles_d_buf, ' ');
|
|
g_string_append (tmpfiles_d_buf, prefix->str);
|
|
g_string_append_c (tmpfiles_d_buf, '/');
|
|
g_string_append (tmpfiles_d_buf, dent->d_name);
|
|
|
|
if (filetype_c == 'd')
|
|
{
|
|
struct stat stbuf;
|
|
|
|
if (TEMP_FAILURE_RETRY (fstatat (dfd_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
|
|
g_string_append_printf (tmpfiles_d_buf, " 0%02o", stbuf.st_mode & ~S_IFMT);
|
|
g_string_append_printf (tmpfiles_d_buf, " %d %d - -", stbuf.st_uid, stbuf.st_gid);
|
|
|
|
/* Push prefix */
|
|
g_string_append_c (prefix, '/');
|
|
g_string_append (prefix, dent->d_name);
|
|
|
|
if (!convert_var_to_tmpfiles_d_recurse (tmpfiles_out, dfd, prefix,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
/* Pop prefix */
|
|
{
|
|
char *r = memrchr (prefix->str, '/', prefix->len);
|
|
g_assert (r != NULL);
|
|
g_string_truncate (prefix, r - prefix->str);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_autofree char *link = glnx_readlinkat_malloc (dfd_iter.fd, dent->d_name, cancellable, error);
|
|
if (!link)
|
|
goto out;
|
|
g_string_append (tmpfiles_d_buf, " - - - - ");
|
|
g_string_append (tmpfiles_d_buf, link);
|
|
}
|
|
|
|
g_string_append_c (tmpfiles_d_buf, '\n');
|
|
|
|
tmpfiles_d_line = g_string_free (tmpfiles_d_buf, FALSE);
|
|
|
|
if (!g_output_stream_write_all (tmpfiles_out, tmpfiles_d_line,
|
|
strlen (tmpfiles_d_line), &bytes_written,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
convert_var_to_tmpfiles_d (int src_rootfs_dfd,
|
|
int dest_rootfs_dfd,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
g_autoptr(GString) prefix = g_string_new ("/var");
|
|
glnx_fd_close int var_dfd = -1;
|
|
glnx_fd_close int tmpfiles_fd = -1;
|
|
/* List of files that shouldn't be in the tree */
|
|
const char *known_state_files[] = {
|
|
"lib/systemd/random-seed",
|
|
"lib/systemd/catalog/database",
|
|
"lib/plymouth/boot-duration",
|
|
};
|
|
|
|
if (!glnx_opendirat (src_rootfs_dfd, "var", TRUE, &var_dfd, error))
|
|
goto out;
|
|
|
|
/* Here, delete some files ahead of time to avoid emitting warnings
|
|
* for things that are known to be harmless.
|
|
*/
|
|
for (guint i = 0; i < G_N_ELEMENTS (known_state_files); i++)
|
|
{
|
|
const char *path = known_state_files[i];
|
|
if (unlinkat (var_dfd, path, 0) < 0)
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Now, SELinux in Fedora >= 24: https://bugzilla.redhat.com/show_bug.cgi?id=1290659 */
|
|
if (!glnx_shutil_rm_rf_at (var_dfd, "lib/selinux/targeted", cancellable, error))
|
|
goto out;
|
|
|
|
/* Append to an existing one for package layering */
|
|
if ((tmpfiles_fd = TEMP_FAILURE_RETRY (openat (dest_rootfs_dfd, "usr/lib/tmpfiles.d/rpm-ostree-1-autovar.conf",
|
|
O_WRONLY | O_CREAT | O_APPEND | O_NOCTTY, 0644))) == -1)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
|
|
{ glnx_unref_object GOutputStream *tmpfiles_out =
|
|
g_unix_output_stream_new (tmpfiles_fd, FALSE);
|
|
|
|
if (!tmpfiles_out)
|
|
goto out;
|
|
|
|
if (!convert_var_to_tmpfiles_d_recurse (tmpfiles_out, src_rootfs_dfd, prefix, cancellable, error))
|
|
goto out;
|
|
|
|
if (!g_output_stream_close (tmpfiles_out, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/* SELinux uses PCRE pre-compiled regexps for binary caches, which can
|
|
* fail if the version of PCRE on the host differs from the version
|
|
* which generated the cache (in the target root).
|
|
*
|
|
* Note also this function is probably already broken in Fedora
|
|
* 23+ from https://bugzilla.redhat.com/show_bug.cgi?id=1265406
|
|
*/
|
|
static gboolean
|
|
workaround_selinux_cross_labeling_recurse (int dfd,
|
|
const char *path,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
|
|
|
if (!glnx_dirfd_iterator_init_at (dfd, path, TRUE, &dfd_iter, error))
|
|
goto out;
|
|
|
|
while (TRUE)
|
|
{
|
|
struct dirent *dent = NULL;
|
|
const char *name;
|
|
|
|
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
|
|
goto out;
|
|
|
|
if (!dent)
|
|
break;
|
|
|
|
name = dent->d_name;
|
|
|
|
if (dent->d_type == DT_DIR)
|
|
{
|
|
if (!workaround_selinux_cross_labeling_recurse (dfd_iter.fd, name, cancellable, error))
|
|
goto out;
|
|
}
|
|
else if (g_str_has_suffix (name, ".bin"))
|
|
{
|
|
struct stat stbuf;
|
|
const char *lastdot;
|
|
g_autofree char *nonbin_name = NULL;
|
|
|
|
if (TEMP_FAILURE_RETRY (fstatat (dfd_iter.fd, name, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
|
|
lastdot = strrchr (name, '.');
|
|
g_assert (lastdot);
|
|
|
|
nonbin_name = g_strndup (name, lastdot - name);
|
|
|
|
if (TEMP_FAILURE_RETRY (utimensat (dfd_iter.fd, nonbin_name, NULL, 0)) == -1)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
rpmostree_prepare_rootfs_get_sepolicy (int dfd,
|
|
const char *path,
|
|
OstreeSePolicy **out_sepolicy,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
glnx_unref_object OstreeSePolicy *ret_sepolicy = NULL;
|
|
struct stat stbuf;
|
|
|
|
if (TEMP_FAILURE_RETRY (fstatat (dfd, "usr/etc/selinux", &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!workaround_selinux_cross_labeling_recurse (dfd, "usr/etc/selinux",
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
{
|
|
g_autofree char *abspath = glnx_fdrel_abspath (dfd, path);
|
|
glnx_unref_object GFile *rootfs = g_file_new_for_path (abspath);
|
|
ret_sepolicy = ostree_sepolicy_new (rootfs, cancellable, error);
|
|
if (!ret_sepolicy)
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
*out_sepolicy = g_steal_pointer (&ret_sepolicy);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
replace_nsswitch (GFile *target_usretc,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
g_autoptr(GFile) nsswitch_conf =
|
|
g_file_get_child (target_usretc, "nsswitch.conf");
|
|
g_autofree char *nsswitch_contents = NULL;
|
|
g_autofree char *new_nsswitch_contents = NULL;
|
|
|
|
static gsize regex_initialized;
|
|
static GRegex *passwd_regex;
|
|
|
|
if (g_once_init_enter (®ex_initialized))
|
|
{
|
|
passwd_regex = g_regex_new ("^(passwd|group):\\s+files(.*)$",
|
|
G_REGEX_MULTILINE, 0, NULL);
|
|
g_assert (passwd_regex);
|
|
g_once_init_leave (®ex_initialized, 1);
|
|
}
|
|
|
|
nsswitch_contents = glnx_file_get_contents_utf8_at (AT_FDCWD, gs_file_get_path_cached (nsswitch_conf), NULL,
|
|
cancellable, error);
|
|
if (!nsswitch_contents)
|
|
goto out;
|
|
|
|
new_nsswitch_contents = g_regex_replace (passwd_regex,
|
|
nsswitch_contents, -1, 0,
|
|
"\\1: files altfiles\\2",
|
|
0, error);
|
|
if (!new_nsswitch_contents)
|
|
goto out;
|
|
|
|
if (!g_file_replace_contents (nsswitch_conf, new_nsswitch_contents,
|
|
strlen (new_nsswitch_contents),
|
|
NULL, FALSE, 0, NULL,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/* Prepare a root filesystem, taking mainly the contents of /usr from yumroot */
|
|
static gboolean
|
|
create_rootfs_from_yumroot_content (GFile *targetroot,
|
|
GFile *yumroot,
|
|
JsonObject *treefile,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
glnx_fd_close int src_rootfs_fd = -1;
|
|
glnx_fd_close int target_root_dfd = -1;
|
|
g_autoptr(GFile) kernel_path = NULL;
|
|
g_autoptr(GFile) initramfs_path = NULL;
|
|
g_autoptr(GHashTable) preserve_groups_set = NULL;
|
|
gboolean container = FALSE;
|
|
|
|
if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (yumroot), TRUE,
|
|
&src_rootfs_fd, error))
|
|
goto out;
|
|
|
|
if (!_rpmostree_jsonutil_object_get_optional_boolean_member (treefile,
|
|
"container",
|
|
&container,
|
|
error))
|
|
goto out;
|
|
|
|
g_print ("Preparing kernel\n");
|
|
if (!container && !do_kernel_prep (yumroot, treefile, cancellable, error))
|
|
goto out;
|
|
|
|
g_print ("Initializing rootfs\n");
|
|
if (!init_rootfs (targetroot, cancellable, error))
|
|
goto out;
|
|
|
|
if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (targetroot), TRUE, &target_root_dfd, error))
|
|
goto out;
|
|
|
|
g_print ("Migrating /etc/passwd to /usr/lib/\n");
|
|
if (!rpmostree_passwd_migrate_except_root (yumroot, RPM_OSTREE_PASSWD_MIGRATE_PASSWD, NULL,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
if (json_object_has_member (treefile, "etc-group-members"))
|
|
{
|
|
JsonArray *etc_group_members = json_object_get_array_member (treefile, "etc-group-members");
|
|
preserve_groups_set = _rpmostree_jsonutil_jsarray_strings_to_set (etc_group_members);
|
|
}
|
|
|
|
g_print ("Migrating /etc/group to /usr/lib/\n");
|
|
if (!rpmostree_passwd_migrate_except_root (yumroot, RPM_OSTREE_PASSWD_MIGRATE_GROUP,
|
|
preserve_groups_set,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
/* NSS configuration to look at the new files */
|
|
{
|
|
g_autoptr(GFile) yumroot_etc =
|
|
g_file_resolve_relative_path (yumroot, "etc");
|
|
|
|
if (!replace_nsswitch (yumroot_etc, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
/* We take /usr from the yum content */
|
|
g_print ("Moving /usr and /etc to target\n");
|
|
{
|
|
g_autoptr(GFile) usr = g_file_get_child (yumroot, "usr");
|
|
g_autoptr(GFile) etc = g_file_get_child (yumroot, "etc");
|
|
if (!move_to_dir (usr, targetroot, cancellable, error))
|
|
goto out;
|
|
if (!move_to_dir (etc, targetroot, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
if (!rpmostree_rootfs_prepare_links (target_root_dfd, cancellable, error))
|
|
goto out;
|
|
if (!rpmostree_rootfs_postprocess_common (target_root_dfd, cancellable, error))
|
|
goto out;
|
|
|
|
if (!convert_var_to_tmpfiles_d (src_rootfs_fd, target_root_dfd, cancellable, error))
|
|
goto out;
|
|
|
|
/* Move boot, but rename the kernel/initramfs to have a checksum */
|
|
if (!container)
|
|
{
|
|
g_autoptr(GFile) yumroot_boot =
|
|
g_file_get_child (yumroot, "boot");
|
|
g_autoptr(GFile) target_boot =
|
|
g_file_get_child (targetroot, "boot");
|
|
g_autoptr(GFile) target_usrlib =
|
|
g_file_resolve_relative_path (targetroot, "usr/lib");
|
|
g_autoptr(GFile) target_usrlib_ostree_boot =
|
|
g_file_resolve_relative_path (target_usrlib, "ostree-boot");
|
|
RpmOstreePostprocessBootLocation boot_location =
|
|
RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH;
|
|
const char *boot_location_str = NULL;
|
|
|
|
g_print ("Moving /boot\n");
|
|
|
|
if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile,
|
|
"boot_location",
|
|
&boot_location_str, error))
|
|
goto out;
|
|
|
|
if (boot_location_str != NULL)
|
|
{
|
|
if (strcmp (boot_location_str, "legacy") == 0)
|
|
boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_LEGACY;
|
|
else if (strcmp (boot_location_str, "both") == 0)
|
|
boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH;
|
|
else if (strcmp (boot_location_str, "new") == 0)
|
|
boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW;
|
|
else
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Invalid boot location '%s'", boot_location_str);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!gs_file_ensure_directory (target_usrlib, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
switch (boot_location)
|
|
{
|
|
case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_LEGACY:
|
|
{
|
|
g_print ("Using boot location: legacy\n");
|
|
if (!gs_file_rename (yumroot_boot, target_boot, cancellable, error))
|
|
goto out;
|
|
}
|
|
break;
|
|
case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH:
|
|
{
|
|
g_print ("Using boot location: both\n");
|
|
if (!gs_file_rename (yumroot_boot, target_boot, cancellable, error))
|
|
goto out;
|
|
/* Hardlink the existing content, only a little ugly as
|
|
* we'll end up sha256'ing it twice, but oh well. */
|
|
if (!gs_shutil_cp_al_or_fallback (target_boot, target_usrlib_ostree_boot, cancellable, error))
|
|
goto out;
|
|
}
|
|
break;
|
|
case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW:
|
|
{
|
|
g_print ("Using boot location: new\n");
|
|
if (!gs_file_rename (yumroot_boot, target_usrlib_ostree_boot, cancellable, error))
|
|
goto out;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Also carry along toplevel compat links */
|
|
g_print ("Copying toplevel compat symlinks\n");
|
|
{
|
|
guint i;
|
|
const char *toplevel_links[] = { "lib", "lib64", "lib32",
|
|
"bin", "sbin" };
|
|
for (i = 0; i < G_N_ELEMENTS (toplevel_links); i++)
|
|
{
|
|
g_autoptr(GFile) srcpath =
|
|
g_file_get_child (yumroot, toplevel_links[i]);
|
|
|
|
if (g_file_query_file_type (srcpath, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK)
|
|
{
|
|
if (!move_to_dir (srcpath, targetroot, cancellable, error))
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_print ("Adding rpm-ostree-0-integration.conf\n");
|
|
{
|
|
/* This is useful if we're running in an uninstalled configuration, e.g.
|
|
* during tests. */
|
|
const char *pkglibdir_path
|
|
= g_getenv("RPMOSTREE_UNINSTALLED_PKGLIBDIR") ?: PKGLIBDIR;
|
|
|
|
g_autoptr(GFile) src_pkglibdir = g_file_new_for_path (pkglibdir_path);
|
|
g_autoptr(GFile) src_tmpfilesd =
|
|
g_file_get_child (src_pkglibdir, "rpm-ostree-0-integration.conf");
|
|
g_autoptr(GFile) target_tmpfilesd =
|
|
g_file_resolve_relative_path (targetroot, "usr/lib/tmpfiles.d/rpm-ostree-0-integration.conf");
|
|
g_autoptr(GFile) target_tmpfilesd_parent = g_file_get_parent (target_tmpfilesd);
|
|
|
|
if (!gs_file_ensure_directory (target_tmpfilesd_parent, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
if (!g_file_copy (src_tmpfilesd, target_tmpfilesd, 0,
|
|
cancellable, NULL, NULL, error))
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
handle_remove_files_from_package (int rootfs_fd,
|
|
RpmOstreeRefSack *refsack,
|
|
JsonArray *removespec,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
const char *pkgname = json_array_get_string_element (removespec, 0);
|
|
guint i, j, npackages;
|
|
guint len = json_array_get_length (removespec);
|
|
HyQuery query = NULL;
|
|
g_autoptr(GPtrArray) pkglist = NULL;
|
|
|
|
query = hy_query_create (refsack->sack);
|
|
hy_query_filter (query, HY_PKG_NAME, HY_EQ, pkgname);
|
|
pkglist = hy_query_run (query);
|
|
npackages = pkglist->len;
|
|
if (npackages == 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Unable to find package '%s' specified in remove-from-packages", pkgname);
|
|
goto out;
|
|
}
|
|
|
|
for (j = 0; j < npackages; j++)
|
|
{
|
|
DnfPackage *pkg;
|
|
g_auto(GStrv) pkg_files = NULL;
|
|
|
|
pkg = pkglist->pdata[j];
|
|
pkg_files = dnf_package_get_files (pkg);
|
|
|
|
for (i = 1; i < len; i++)
|
|
{
|
|
const char *remove_regex_pattern = json_array_get_string_element (removespec, i);
|
|
GRegex *regex;
|
|
char **strviter;
|
|
|
|
regex = g_regex_new (remove_regex_pattern, G_REGEX_JAVASCRIPT_COMPAT, 0, error);
|
|
|
|
if (!regex)
|
|
goto out;
|
|
|
|
for (strviter = pkg_files; strviter && strviter[0]; strviter++)
|
|
{
|
|
const char *file = *strviter;
|
|
|
|
if (g_regex_match (regex, file, 0, NULL))
|
|
{
|
|
if (file[0] == '/')
|
|
file++;
|
|
|
|
g_print ("Deleting: %s\n", file);
|
|
if (!glnx_shutil_rm_rf_at (rootfs_fd, file, cancellable, error))
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
if (query)
|
|
hy_query_free (query);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
rename_if_exists (int dfd,
|
|
const char *from,
|
|
const char *to,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
struct stat stbuf;
|
|
|
|
if (fstatat (dfd, from, &stbuf, 0) < 0)
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (renameat (dfd, from, dfd, to) < 0)
|
|
{
|
|
/* Handle empty directory in legacy location */
|
|
if (errno == EEXIST)
|
|
{
|
|
if (unlinkat (dfd, from, AT_REMOVEDIR) < 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
g_prefix_error (error, "Renaming %s -> %s: ", from, to);
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
rpmostree_rootfs_symlink_emptydir_at (int rootfs_fd,
|
|
const char *dest,
|
|
const char *src,
|
|
GError **error)
|
|
{
|
|
const char *parent = dirname (strdupa (src));
|
|
struct stat stbuf;
|
|
gboolean make_symlink = TRUE;
|
|
|
|
/* For maximum compatibility, create parent directories too. This
|
|
* is necessary when we're doing layering on top of a base commit,
|
|
* and the /var will be empty. We should probably consider running
|
|
* systemd-tmpfiles to setup the temporary /var.
|
|
*/
|
|
if (parent && strcmp (parent, ".") != 0)
|
|
{
|
|
if (!glnx_shutil_mkdir_p_at (rootfs_fd, parent, 0755, NULL, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (fstatat (rootfs_fd, src, &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (S_ISLNK (stbuf.st_mode))
|
|
make_symlink = FALSE;
|
|
else if (S_ISDIR (stbuf.st_mode))
|
|
{
|
|
if (unlinkat (rootfs_fd, src, AT_REMOVEDIR) < 0)
|
|
{
|
|
glnx_set_prefix_error_from_errno (error, "Removing %s", src);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (make_symlink)
|
|
{
|
|
if (symlinkat (dest, rootfs_fd, src) < 0)
|
|
{
|
|
glnx_set_prefix_error_from_errno (error, "Symlinking %s", src);
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* rpmostree_rootfs_prepare_links:
|
|
*
|
|
* Walk over the root filesystem and perform some core conversions
|
|
* from RPM conventions to OSTree conventions. For example:
|
|
*
|
|
* - Symlink /usr/local -> /var/usrlocal
|
|
* - Symlink /var/lib/alternatives -> /usr/lib/alternatives
|
|
* - Symlink /var/lib/vagrant -> /usr/lib/vagrant
|
|
*/
|
|
gboolean
|
|
rpmostree_rootfs_prepare_links (int rootfs_fd,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
if (!glnx_shutil_rm_rf_at (rootfs_fd, "usr/local", cancellable, error))
|
|
return FALSE;
|
|
if (!rpmostree_rootfs_symlink_emptydir_at (rootfs_fd, "../var/usrlocal", "usr/local", error))
|
|
return FALSE;
|
|
|
|
if (!glnx_shutil_mkdir_p_at (rootfs_fd, "usr/lib/alternatives", 0755, cancellable, error))
|
|
return FALSE;
|
|
if (!rpmostree_rootfs_symlink_emptydir_at (rootfs_fd, "../../usr/lib/alternatives", "var/lib/alternatives", error))
|
|
return FALSE;
|
|
if (!glnx_shutil_mkdir_p_at (rootfs_fd, "usr/lib/vagrant", 0755, cancellable, error))
|
|
return FALSE;
|
|
if (!rpmostree_rootfs_symlink_emptydir_at (rootfs_fd, "../../usr/lib/vagrant", "var/lib/vagrant", error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* rpmostree_rootfs_postprocess_common:
|
|
*
|
|
* Walk over the root filesystem and perform some core conversions
|
|
* from RPM conventions to OSTree conventions. For example:
|
|
*
|
|
* - Move /etc to /usr/etc
|
|
* - Clean up RPM db leftovers
|
|
*/
|
|
gboolean
|
|
rpmostree_rootfs_postprocess_common (int rootfs_fd,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
|
|
|
if (!rename_if_exists (rootfs_fd, "etc", "usr/etc", error))
|
|
goto out;
|
|
|
|
if (!glnx_dirfd_iterator_init_at (rootfs_fd, "usr/share/rpm", TRUE, &dfd_iter, error))
|
|
{
|
|
g_prefix_error (error, "Opening usr/share/rpm: ");
|
|
goto out;
|
|
}
|
|
|
|
while (TRUE)
|
|
{
|
|
struct dirent *dent = NULL;
|
|
const char *name;
|
|
|
|
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
|
|
goto out;
|
|
if (!dent)
|
|
break;
|
|
|
|
if (dent->d_type != DT_REG)
|
|
continue;
|
|
|
|
name = dent->d_name;
|
|
|
|
if (!(g_str_has_prefix (name, "__db.") ||
|
|
strcmp (name, ".dbenv.lock") == 0 ||
|
|
strcmp (name, ".rpm.lock") == 0))
|
|
continue;
|
|
|
|
if (unlinkat (dfd_iter.fd, name, 0) < 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
g_prefix_error (error, "Unlinking %s: ", name);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* rpmostree_copy_additional_files:
|
|
*
|
|
* Copy external files, if specified in the configuration file, from
|
|
* the context directory to the rootfs.
|
|
*/
|
|
gboolean
|
|
rpmostree_copy_additional_files (GFile *rootfs,
|
|
GFile *context_directory,
|
|
JsonObject *treefile,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
JsonArray *add = NULL;
|
|
guint i, len;
|
|
|
|
g_autofree char *dest_rootfs_path = g_strconcat (gs_file_get_path_cached (rootfs), ".post", NULL);
|
|
g_autoptr(GFile) targetroot = g_file_new_for_path (dest_rootfs_path);
|
|
|
|
if (json_object_has_member (treefile, "add-files"))
|
|
{
|
|
add = json_object_get_array_member (treefile, "add-files");
|
|
len = json_array_get_length (add);
|
|
}
|
|
else
|
|
{
|
|
ret = TRUE;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
const char *src, *dest;
|
|
|
|
JsonArray *add_el = json_array_get_array_element (add, i);
|
|
g_autoptr(GFile) child = NULL;
|
|
|
|
if (!add_el)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Element in add-files is not an array");
|
|
goto out;
|
|
}
|
|
|
|
src = _rpmostree_jsonutil_array_require_string_element (add_el, 0, error);
|
|
if (!src)
|
|
goto out;
|
|
|
|
dest = _rpmostree_jsonutil_array_require_string_element (add_el, 1, error);
|
|
if (!dest)
|
|
goto out;
|
|
|
|
{
|
|
g_autoptr(GFile) srcfile = g_file_resolve_relative_path (context_directory, src);
|
|
const char *rootfs_path = gs_file_get_path_cached (rootfs);
|
|
g_autofree char *destpath = g_strconcat (rootfs_path, "/", dest, NULL);
|
|
g_autoptr(GFile) destfile = g_file_resolve_relative_path (targetroot, destpath);
|
|
g_autoptr(GFile) target_tmpfilesd_parent = g_file_get_parent (destfile);
|
|
|
|
g_print ("Adding file '%s'\n", dest);
|
|
|
|
if (!gs_file_ensure_directory (target_tmpfilesd_parent, TRUE, cancellable, error))
|
|
goto out;
|
|
|
|
if (!g_file_copy (srcfile, destfile, 0, cancellable, NULL, NULL, error))
|
|
{
|
|
g_prefix_error (error, "Copying file '%s' into target: ", src);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
mutate_os_release (const char *contents,
|
|
const char *base_version,
|
|
const char *next_version,
|
|
GError **error)
|
|
{
|
|
g_auto(GStrv) lines = NULL;
|
|
GString *new_contents = g_string_sized_new (strlen (contents));
|
|
|
|
lines = g_strsplit (contents, "\n", -1);
|
|
for (char **it = lines; it && *it; it++)
|
|
{
|
|
const char *line = *it;
|
|
|
|
if (strlen (line) == 0)
|
|
continue;
|
|
|
|
/* NB: we don't mutate VERSION_ID because some libraries expect well-known
|
|
* values there*/
|
|
if (g_str_has_prefix (line, "VERSION=") || \
|
|
g_str_has_prefix (line, "PRETTY_NAME="))
|
|
{
|
|
g_autofree char *new_line = NULL;
|
|
const char *equal = strchr (line, '=');
|
|
|
|
g_string_append_len (new_contents, line, equal - line + 1);
|
|
|
|
new_line = rpmostree_str_replace (equal + 1, base_version,
|
|
next_version, error);
|
|
if (new_line == NULL)
|
|
return NULL;
|
|
|
|
g_string_append_printf (new_contents, "%s\n", new_line);
|
|
continue;
|
|
}
|
|
|
|
g_string_append_printf (new_contents, "%s\n", line);
|
|
}
|
|
|
|
/* add a bona fide ostree entry */
|
|
g_string_append_printf (new_contents, "OSTREE_VERSION=%s\n", next_version);
|
|
|
|
return g_string_free (new_contents, FALSE);
|
|
}
|
|
|
|
gboolean
|
|
rpmostree_treefile_postprocessing (int rootfs_fd,
|
|
GFile *context_directory,
|
|
GBytes *serialized_treefile,
|
|
JsonObject *treefile,
|
|
const char *next_version,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
guint i, len;
|
|
JsonArray *units = NULL;
|
|
JsonArray *remove = NULL;
|
|
const char *default_target = NULL;
|
|
const char *postprocess_script = NULL;
|
|
|
|
if (json_object_has_member (treefile, "units"))
|
|
units = json_object_get_array_member (treefile, "units");
|
|
|
|
if (units)
|
|
len = json_array_get_length (units);
|
|
else
|
|
len = 0;
|
|
|
|
{
|
|
glnx_fd_close int multiuser_wants_dfd = -1;
|
|
|
|
if (!glnx_shutil_mkdir_p_at (rootfs_fd, "etc/systemd/system/multi-user.target.wants", 0755,
|
|
cancellable, error))
|
|
goto out;
|
|
if (!glnx_opendirat (rootfs_fd, "etc/systemd/system/multi-user.target.wants", TRUE,
|
|
&multiuser_wants_dfd, error))
|
|
goto out;
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
const char *unitname = _rpmostree_jsonutil_array_require_string_element (units, i, error);
|
|
g_autoptr(GFile) unit_link_target = NULL;
|
|
g_autofree char *symlink_target = NULL;
|
|
struct stat stbuf;
|
|
|
|
if (!unitname)
|
|
goto out;
|
|
|
|
symlink_target = g_strconcat ("/usr/lib/systemd/system/", unitname, NULL);
|
|
|
|
if (fstatat (multiuser_wants_dfd, unitname, &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
else
|
|
continue;
|
|
|
|
g_print ("Adding %s to multi-user.target.wants\n", unitname);
|
|
|
|
if (symlinkat (symlink_target, multiuser_wants_dfd, unitname) < 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
const guint8 *buf;
|
|
gsize len;
|
|
|
|
if (!glnx_shutil_mkdir_p_at (rootfs_fd, "usr/share/rpm-ostree", 0755, cancellable, error))
|
|
goto out;
|
|
|
|
buf = g_bytes_get_data (serialized_treefile, &len);
|
|
|
|
if (!glnx_file_replace_contents_at (rootfs_fd, "usr/share/rpm-ostree/treefile.json",
|
|
buf, len, GLNX_FILE_REPLACE_NODATASYNC,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile, "default_target",
|
|
&default_target, error))
|
|
goto out;
|
|
|
|
if (default_target != NULL)
|
|
{
|
|
g_autofree char *dest_default_target_path =
|
|
g_strconcat ("/usr/lib/systemd/system/", default_target, NULL);
|
|
|
|
(void) unlinkat (rootfs_fd, "etc/systemd/system/default.target", 0);
|
|
|
|
if (symlinkat (dest_default_target_path, rootfs_fd, "etc/systemd/system/default.target") < 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (json_object_has_member (treefile, "remove-files"))
|
|
{
|
|
remove = json_object_get_array_member (treefile, "remove-files");
|
|
len = json_array_get_length (remove);
|
|
}
|
|
else
|
|
len = 0;
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
const char *val = _rpmostree_jsonutil_array_require_string_element (remove, i, error);
|
|
|
|
if (!val)
|
|
return FALSE;
|
|
if (g_path_is_absolute (val))
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"'remove' elements must be relative");
|
|
goto out;
|
|
}
|
|
g_assert (val[0] != '/');
|
|
g_assert (strstr (val, "..") == NULL);
|
|
|
|
g_print ("Deleting: %s\n", val);
|
|
if (!glnx_shutil_rm_rf_at (rootfs_fd, val, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
/* This works around a potential issue with libsolv if we go down the
|
|
* rpmostree_get_pkglist_for_root() path. Though rpm has been using the
|
|
* /usr/share/rpm location (since the RpmOstreeContext set the _dbpath macro),
|
|
* the /var/lib/rpm directory will still exist, but be empty. libsolv gets
|
|
* confused because it sees the /var/lib/rpm dir and doesn't even try the
|
|
* /usr/share/rpm location, and eventually dies when it tries to load the
|
|
* data. XXX: should probably send a patch upstream to libsolv.
|
|
*
|
|
* So we set the symlink now. This is also what we do on boot anyway for
|
|
* compatibility reasons using tmpfiles.
|
|
* */
|
|
if (!glnx_shutil_rm_rf_at (rootfs_fd, "var/lib/rpm", cancellable, error))
|
|
goto out;
|
|
if (symlinkat ("../../usr/share/rpm", rootfs_fd, "var/lib/rpm") < 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
|
|
if (json_object_has_member (treefile, "remove-from-packages"))
|
|
{
|
|
g_autoptr(RpmOstreeRefSack) refsack = NULL;
|
|
guint i;
|
|
|
|
remove = json_object_get_array_member (treefile, "remove-from-packages");
|
|
len = json_array_get_length (remove);
|
|
|
|
if (!rpmostree_get_pkglist_for_root (rootfs_fd, ".", &refsack, NULL,
|
|
cancellable, error))
|
|
{
|
|
g_prefix_error (error, "Reading package set: ");
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
JsonArray *elt = json_array_get_array_element (remove, i);
|
|
if (!handle_remove_files_from_package (rootfs_fd, refsack, elt, cancellable, error))
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
{
|
|
const char *base_version = NULL;
|
|
|
|
if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile,
|
|
"mutate-os-release",
|
|
&base_version,
|
|
error))
|
|
goto out;
|
|
|
|
if (base_version != NULL)
|
|
{
|
|
g_autofree char *contents = NULL;
|
|
g_autofree char *new_contents = NULL;
|
|
const char *path = NULL;
|
|
|
|
/* let's try to find the first non-symlink */
|
|
const char *os_release[] = {
|
|
"etc/os-release",
|
|
"usr/lib/os-release",
|
|
"usr/lib/os.release.d/os-release-fedora"
|
|
};
|
|
|
|
/* fallback on just overwriting etc/os-release */
|
|
path = os_release[0];
|
|
|
|
for (guint i = 0; i < G_N_ELEMENTS (os_release); i++)
|
|
{
|
|
struct stat stbuf;
|
|
|
|
if (TEMP_FAILURE_RETRY (fstatat (rootfs_fd, os_release[i], &stbuf,
|
|
AT_SYMLINK_NOFOLLOW)) != 0)
|
|
{
|
|
glnx_set_prefix_error_from_errno (error, "fstatat(%s)",
|
|
os_release[i]);
|
|
goto out;
|
|
}
|
|
|
|
if (S_ISREG (stbuf.st_mode))
|
|
{
|
|
path = os_release[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_print ("Mutating /%s\n", path);
|
|
|
|
contents = glnx_file_get_contents_utf8_at (rootfs_fd, path, NULL,
|
|
cancellable, error);
|
|
if (contents == NULL)
|
|
goto out;
|
|
|
|
new_contents = mutate_os_release (contents, base_version,
|
|
next_version, error);
|
|
if (new_contents == NULL)
|
|
goto out;
|
|
|
|
if (!glnx_file_replace_contents_at (rootfs_fd, path,
|
|
(guint8*)new_contents, -1, 0,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile, "postprocess-script",
|
|
&postprocess_script, error))
|
|
goto out;
|
|
|
|
if (postprocess_script)
|
|
{
|
|
const char *bn = glnx_basename (postprocess_script);
|
|
g_autofree char *src = g_build_filename (gs_file_get_path_cached (context_directory), postprocess_script, NULL);
|
|
g_autofree char *binpath = g_strconcat ("/usr/bin/rpmostree-postprocess-", bn, NULL);
|
|
/* Clone all the things */
|
|
|
|
/* Note we need to make binpath *not* absolute here */
|
|
if (!glnx_file_copy_at (AT_FDCWD, src, NULL, rootfs_fd, binpath + 1,
|
|
GLNX_FILE_COPY_NOXATTRS, cancellable, error))
|
|
goto out;
|
|
|
|
g_print ("Executing postprocessing script '%s'\n", bn);
|
|
|
|
{
|
|
char *child_argv[] = { binpath, NULL };
|
|
if (!run_sync_in_root_at (rootfs_fd, binpath, child_argv, error))
|
|
{
|
|
g_prefix_error (error, "While executing postprocessing script '%s': ", bn);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
g_print ("Finished postprocessing script '%s'\n", bn);
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* rpmostree_prepare_rootfs_for_commit:
|
|
*
|
|
* Walk over the root filesystem and perform some core conversions
|
|
* from RPM conventions to OSTree conventions. For example:
|
|
*
|
|
* * Move /etc to /usr/etc
|
|
* * Checksum the kernel in /boot
|
|
* * Migrate content in /var to systemd-tmpfiles
|
|
*/
|
|
gboolean
|
|
rpmostree_prepare_rootfs_for_commit (GFile *rootfs,
|
|
JsonObject *treefile,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
g_autofree char *dest_rootfs_path = NULL;
|
|
|
|
dest_rootfs_path = g_strconcat (gs_file_get_path_cached (rootfs), ".post", NULL);
|
|
|
|
if (!glnx_shutil_rm_rf_at (AT_FDCWD, dest_rootfs_path, cancellable, error))
|
|
goto out;
|
|
|
|
{
|
|
g_autoptr(GFile) dest_rootfs = g_file_new_for_path (dest_rootfs_path);
|
|
if (!create_rootfs_from_yumroot_content (dest_rootfs, rootfs, treefile,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
if (!glnx_shutil_rm_rf_at (AT_FDCWD, gs_file_get_path_cached (rootfs), cancellable, error))
|
|
goto out;
|
|
|
|
if (TEMP_FAILURE_RETRY (renameat (AT_FDCWD, dest_rootfs_path,
|
|
AT_FDCWD, gs_file_get_path_cached (rootfs))) != 0)
|
|
{
|
|
glnx_set_error_from_errno (error);
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
struct CommitThreadData {
|
|
volatile gint done;
|
|
off_t n_bytes;
|
|
off_t n_processed;
|
|
volatile gint percent;
|
|
OstreeRepo *repo;
|
|
int rootfs_fd;
|
|
OstreeMutableTree *mtree;
|
|
OstreeSePolicy *sepolicy;
|
|
OstreeRepoCommitModifier *commit_modifier;
|
|
gboolean success;
|
|
GCancellable *cancellable;
|
|
GError **error;
|
|
};
|
|
|
|
static GVariant *
|
|
read_xattrs_cb (OstreeRepo *repo,
|
|
const char *relpath,
|
|
GFileInfo *file_info,
|
|
gpointer user_data)
|
|
{
|
|
struct CommitThreadData *tdata = user_data;
|
|
int rootfs_fd = tdata->rootfs_fd;
|
|
/* If you have a use case for something else, file an issue */
|
|
static const char *accepted_xattrs[] =
|
|
{ "security.capability", /* https://lwn.net/Articles/211883/ */
|
|
"user.pax.flags" /* https://github.com/projectatomic/rpm-ostree/issues/412 */
|
|
};
|
|
guint i;
|
|
g_autoptr(GVariant) existing_xattrs = NULL;
|
|
gs_free_variant_iter GVariantIter *viter = NULL;
|
|
GError *local_error = NULL;
|
|
GError **error = &local_error;
|
|
GVariant *key, *value;
|
|
GVariantBuilder builder;
|
|
|
|
if (relpath[0] == '/')
|
|
relpath++;
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
|
|
|
|
if (!*relpath)
|
|
{
|
|
if (!gs_fd_get_all_xattrs (rootfs_fd, &existing_xattrs, NULL, error))
|
|
goto out;
|
|
}
|
|
else
|
|
{
|
|
if (!gs_dfd_and_name_get_all_xattrs (rootfs_fd, relpath, &existing_xattrs,
|
|
NULL, error))
|
|
goto out;
|
|
}
|
|
|
|
if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY)
|
|
{
|
|
tdata->n_processed += g_file_info_get_size (file_info);
|
|
g_atomic_int_set (&tdata->percent, (gint)((100.0*tdata->n_processed)/tdata->n_bytes));
|
|
}
|
|
|
|
viter = g_variant_iter_new (existing_xattrs);
|
|
|
|
while (g_variant_iter_loop (viter, "(@ay@ay)", &key, &value))
|
|
{
|
|
for (i = 0; i < G_N_ELEMENTS (accepted_xattrs); i++)
|
|
{
|
|
const char *validkey = accepted_xattrs[i];
|
|
const char *attrkey = g_variant_get_bytestring (key);
|
|
if (g_str_equal (validkey, attrkey))
|
|
g_variant_builder_add (&builder, "(@ay@ay)", key, value);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (local_error)
|
|
{
|
|
g_variant_builder_clear (&builder);
|
|
/* Unfortunately we have no way to throw from this callback */
|
|
g_printerr ("Failed to read xattrs of '%s': %s\n",
|
|
relpath, local_error->message);
|
|
exit (1);
|
|
}
|
|
return g_variant_ref_sink (g_variant_builder_end (&builder));
|
|
}
|
|
|
|
static gboolean
|
|
count_filesizes (int dfd,
|
|
const char *path,
|
|
off_t *out_n_bytes,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
|
|
|
if (!glnx_dirfd_iterator_init_at (dfd, path, TRUE, &dfd_iter, error))
|
|
return FALSE;
|
|
|
|
while (TRUE)
|
|
{
|
|
struct dirent *dent = NULL;
|
|
|
|
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
|
|
return FALSE;
|
|
if (!dent)
|
|
break;
|
|
|
|
if (dent->d_type == DT_DIR)
|
|
{
|
|
if (!count_filesizes (dfd_iter.fd, dent->d_name, out_n_bytes,
|
|
cancellable, error))
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
struct stat stbuf;
|
|
|
|
if (fstatat (dfd_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) != 0)
|
|
{
|
|
glnx_set_prefix_error_from_errno (error, "%s", "fstatat");
|
|
return FALSE;
|
|
}
|
|
|
|
(*out_n_bytes) += stbuf.st_size;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gpointer
|
|
write_dfd_thread (gpointer datap)
|
|
{
|
|
struct CommitThreadData *data = datap;
|
|
|
|
if (!ostree_repo_write_dfd_to_mtree (data->repo, data->rootfs_fd, ".",
|
|
data->mtree,
|
|
data->commit_modifier,
|
|
data->cancellable, data->error))
|
|
goto out;
|
|
|
|
data->success = TRUE;
|
|
out:
|
|
g_atomic_int_inc (&data->done);
|
|
g_main_context_wakeup (NULL);
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
on_progress_timeout (gpointer datap)
|
|
{
|
|
struct CommitThreadData *data = datap;
|
|
const gint percent = g_atomic_int_get (&data->percent);
|
|
|
|
glnx_console_progress_text_percent ("Committing:", percent);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
rpmostree_commit (int rootfs_fd,
|
|
OstreeRepo *repo,
|
|
const char *refname,
|
|
GVariant *metadata,
|
|
const char *gpg_keyid,
|
|
gboolean enable_selinux,
|
|
OstreeRepoDevInoCache *devino_cache,
|
|
char **out_new_revision,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
OstreeRepoTransactionStats stats = { 0, };
|
|
off_t n_bytes = 0;
|
|
struct CommitThreadData tdata = { 0, };
|
|
glnx_unref_object OstreeMutableTree *mtree = NULL;
|
|
OstreeRepoCommitModifier *commit_modifier = NULL;
|
|
g_autofree char *parent_revision = NULL;
|
|
g_autofree char *new_revision = NULL;
|
|
g_autoptr(GFile) root_tree = NULL;
|
|
glnx_unref_object OstreeSePolicy *sepolicy = NULL;
|
|
|
|
/* hardcode targeted policy for now */
|
|
if (enable_selinux)
|
|
{
|
|
if (!rpmostree_prepare_rootfs_get_sepolicy (rootfs_fd, ".", &sepolicy, cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
|
|
goto out;
|
|
|
|
mtree = ostree_mutable_tree_new ();
|
|
commit_modifier = ostree_repo_commit_modifier_new (0, NULL, NULL, NULL);
|
|
ostree_repo_commit_modifier_set_xattr_callback (commit_modifier,
|
|
read_xattrs_cb, NULL,
|
|
&tdata);
|
|
|
|
if (sepolicy && ostree_sepolicy_get_name (sepolicy) != NULL)
|
|
ostree_repo_commit_modifier_set_sepolicy (commit_modifier, sepolicy);
|
|
else if (enable_selinux)
|
|
{
|
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"SELinux enabled, but no policy found");
|
|
goto out;
|
|
}
|
|
|
|
if (devino_cache)
|
|
ostree_repo_commit_modifier_set_devino_cache (commit_modifier, devino_cache);
|
|
|
|
if (!count_filesizes (rootfs_fd, ".", &n_bytes, cancellable, error))
|
|
goto out;
|
|
|
|
tdata.n_bytes = n_bytes;
|
|
tdata.repo = repo;
|
|
tdata.rootfs_fd = rootfs_fd;
|
|
tdata.mtree = mtree;
|
|
tdata.sepolicy = sepolicy;
|
|
tdata.commit_modifier = commit_modifier;
|
|
tdata.error = error;
|
|
|
|
{ g_autoptr(GThread) commit_thread = NULL;
|
|
g_auto(GLnxConsoleRef) console = { 0, };
|
|
g_autoptr(GSource) progress_src = NULL;
|
|
|
|
glnx_console_lock (&console);
|
|
|
|
commit_thread = g_thread_new ("commit", write_dfd_thread, &tdata);
|
|
|
|
progress_src = g_timeout_source_new_seconds (console.is_tty ? 1 : 5);
|
|
g_source_set_callback (progress_src, on_progress_timeout, &tdata, NULL);
|
|
g_source_attach (progress_src, NULL);
|
|
|
|
while (g_atomic_int_get (&tdata.done) == 0)
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
glnx_console_progress_text_percent ("Committing:", 100.0);
|
|
|
|
glnx_console_unlock (&console);
|
|
|
|
g_thread_join (commit_thread);
|
|
commit_thread = NULL;
|
|
|
|
if (!tdata.success)
|
|
goto out;
|
|
}
|
|
|
|
if (!ostree_repo_write_mtree (repo, mtree, &root_tree, cancellable, error))
|
|
goto out;
|
|
|
|
if (refname)
|
|
{
|
|
if (!ostree_repo_resolve_rev (repo, refname, TRUE, &parent_revision, error))
|
|
goto out;
|
|
}
|
|
|
|
if (!ostree_repo_write_commit (repo, parent_revision, "", "", metadata,
|
|
(OstreeRepoFile*)root_tree, &new_revision,
|
|
cancellable, error))
|
|
goto out;
|
|
|
|
if (gpg_keyid)
|
|
{
|
|
if (!ostree_repo_sign_commit (repo, new_revision, gpg_keyid, NULL,
|
|
cancellable, error))
|
|
goto out;
|
|
}
|
|
|
|
if (refname)
|
|
ostree_repo_transaction_set_ref (repo, NULL, refname, new_revision);
|
|
|
|
if (!ostree_repo_commit_transaction (repo, &stats, cancellable, error))
|
|
goto out;
|
|
|
|
g_print ("Metadata Total: %u\n", stats.metadata_objects_total);
|
|
g_print ("Metadata Written: %u\n", stats.metadata_objects_written);
|
|
g_print ("Content Total: %u\n", stats.content_objects_total);
|
|
g_print ("Content Written: %u\n", stats.content_objects_written);
|
|
g_print ("Content Bytes Written: %" G_GUINT64_FORMAT "\n", stats.content_bytes_written);
|
|
|
|
ret = TRUE;
|
|
if (out_new_revision)
|
|
*out_new_revision = g_steal_pointer (&new_revision);
|
|
out:
|
|
return ret;
|
|
}
|