rpm-ostree/src/app/rpmostree-composeutil.cxx
Colin Walters 562e03f7c1 Remove large chunks of rojig code
The inevitable followup to https://github.com/coreos/rpm-ostree/pull/2278
that I was too cowardly to do at the time.  But it's time to admit
the 2 months or so of work on this was wasted.  We have too much
tech debt and this is a large chunk of C/C++ code that touches everything
in the codebase in a nontrivial way.

Bigger picture, I'm going to work on
https://github.com/coreos/fedora-coreos-tracker/issues/828
which will strongly orient rpm-ostree towards the container world instead.
We'll still obviously keep the rpm package world around, but only
as a secondary layer.  What rojig was trying to do in putting "images"
inside an RPM was conflating layers.  It would have had a lot of
benefits probably if we'd truly pushed it over the edge into completion,
but that didn't happen.  Let's focus on containers instead.

There's still a lot more rojig code to delete but this first patch removes
the bulk of it.  Touching everything that references e.g. `RPMOSTREE_REFSPEC_TYPE_ROJIG`
etc. can come as a 3rd phase.
2021-05-18 17:31:36 -04:00

373 lines
15 KiB
C++

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2018 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 <glib-unix.h>
#include <json-glib/json-glib.h>
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include <libdnf/libdnf.h>
#include <libdnf/dnf-repo.h>
#include <sys/mount.h>
#include <stdio.h>
#include <linux/magic.h>
#include <sys/statvfs.h>
#include <sys/vfs.h>
#include <libglnx.h>
#include <rpm/rpmmacro.h>
#include "rpmostree-composeutil.h"
#include "rpmostree-util.h"
#include "rpmostree-core.h"
#include "rpmostree-json-parsing.h"
#include "rpmostree-package-variants.h"
#include "rpmostree-libbuiltin.h"
#include "rpmostree-rpm-util.h"
#include "libglnx.h"
gboolean
rpmostree_composeutil_checksum (HyGoal goal,
OstreeRepo *repo,
const rpmostreecxx::Treefile &tf,
JsonObject *treefile,
char **out_checksum,
GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Computing compose checksum", error);
g_autoptr(GChecksum) checksum = g_checksum_new (G_CHECKSUM_SHA256);
/* Hash in the treefile inputs (this includes all externals like postprocess, add-files,
* etc... and the final flattened treefile -- see treefile.rs for more details). */
auto tf_checksum = tf.get_checksum(*repo);
g_checksum_update (checksum, (const guint8*)tf_checksum.data(), tf_checksum.size());
/* Hash in each package */
if (!rpmostree_dnf_add_checksum_goal (checksum, goal, NULL, error))
return FALSE;
*out_checksum = g_strdup (g_checksum_get_string (checksum));
return TRUE;
}
/* Prepare /dev in the target root with the API devices. TODO:
* Delete this when we implement https://github.com/projectatomic/rpm-ostree/issues/729
*/
gboolean
rpmostree_composeutil_legacy_prep_dev (int rootfs_dfd,
GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Preparing dev (legacy)", error);
glnx_autofd int src_fd = openat (AT_FDCWD, "/dev", O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY);
if (src_fd == -1)
return glnx_throw_errno (error);
if (mkdirat (rootfs_dfd, "dev", 0755) != 0)
{
if (errno != ENOENT)
return glnx_throw_errno (error);
}
glnx_autofd int dest_fd = openat (rootfs_dfd, "dev", O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY);
if (dest_fd == -1)
return glnx_throw_errno (error);
static const char *const devnodes[] = { "null", "zero", "full", "random", "urandom", "tty" };
for (guint i = 0; i < G_N_ELEMENTS (devnodes); i++)
{
const char *nodename = devnodes[i];
struct stat stbuf;
if (!glnx_fstatat_allow_noent (src_fd, nodename, &stbuf, 0, error))
return FALSE;
if (errno == ENOENT)
continue;
if (mknodat (dest_fd, nodename, stbuf.st_mode, stbuf.st_rdev) != 0)
return glnx_throw_errno_prefix (error, "mknodat(%s)", nodename);
if (fchmodat (dest_fd, nodename, stbuf.st_mode, 0) != 0)
return glnx_throw_errno_prefix (error, "fchmodat");
}
{ GLNX_AUTO_PREFIX_ERROR ("Testing /dev/null in target root (is nodev set?)", error);
glnx_autofd int devnull_fd = -1;
if (!glnx_openat_rdonly (dest_fd, "null", TRUE, &devnull_fd, error))
return FALSE;
char buf[1];
ssize_t s = read (devnull_fd, buf, sizeof (buf));
if (s < 0)
return glnx_throw_errno_prefix (error, "read");
}
return TRUE;
}
static gboolean
set_keyfile_string_array_from_json (GKeyFile *keyfile,
const char *keyfile_group,
const char *keyfile_key,
JsonArray *a,
GError **error)
{
g_autoptr(GPtrArray) instlangs_v = g_ptr_array_new ();
guint len = json_array_get_length (a);
for (guint i = 0; i < len; i++)
{
const char *elt = _rpmostree_jsonutil_array_require_string_element (a, i, error);
if (!elt)
return FALSE;
g_ptr_array_add (instlangs_v, (char*)elt);
}
g_key_file_set_string_list (keyfile, keyfile_group, keyfile_key,
(const char*const*)instlangs_v->pdata, instlangs_v->len);
return TRUE;
}
static gboolean
treespec_bind_array (JsonObject *treedata,
GKeyFile *ts,
const char *src_name,
const char *dest_name,
gboolean required,
GError **error)
{
if (!json_object_has_member (treedata, src_name))
{
if (required)
return glnx_throw (error, "Treefile is missing required \"%s\" member", src_name);
return TRUE;
}
JsonArray *a = json_object_get_array_member (treedata, src_name);
g_assert (a);
return set_keyfile_string_array_from_json (ts, "tree", dest_name ?: src_name, a, error);
}
/* Convert a treefile into a "treespec" understood by the core.
*/
RpmOstreeTreespec *
rpmostree_composeutil_get_treespec (RpmOstreeContext *ctx,
rpmostreecxx::Treefile &treefile_rs,
JsonObject *treedata,
GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Parsing treefile", error);
g_autoptr(GKeyFile) treespec = g_key_file_new ();
if (!treespec_bind_array (treedata, treespec, "packages", NULL, TRUE, error))
return NULL;
if (!treespec_bind_array (treedata, treespec, "exclude-packages", NULL, FALSE, error))
return NULL;
if (!treespec_bind_array (treedata, treespec, "repos", NULL, FALSE, error))
return NULL;
if (!treespec_bind_array (treedata, treespec, "lockfile-repos", NULL, FALSE, error))
return NULL;
if (!treespec_bind_array (treedata, treespec, "install-langs", "instlangs", FALSE, error))
return NULL;
return rpmostree_treespec_new_from_keyfile (treespec, error);
}
gboolean
rpmostree_composeutil_read_json_metadata (JsonNode *root,
GHashTable *metadata,
GError **error)
{
g_autoptr(GVariant) jsonmetav = json_gvariant_deserialize (root, "a{sv}", error);
if (!jsonmetav)
return FALSE;
GVariantIter viter;
g_variant_iter_init (&viter, jsonmetav);
{ char *key;
GVariant *value;
while (g_variant_iter_loop (&viter, "{sv}", &key, &value))
g_hash_table_replace (metadata, g_strdup (key), g_variant_ref (value));
}
return TRUE;
}
/* compose tree accepts JSON metadata via file; convert it
* to a hash table of a{sv}; suitable for further extension.
*/
gboolean
rpmostree_composeutil_read_json_metadata_from_file (const char *path,
GHashTable *metadata,
GError **error)
{
const char *errprefix = glnx_strjoina ("While parsing JSON file ", path);
GLNX_AUTO_PREFIX_ERROR (errprefix, error);
glnx_unref_object JsonParser *jparser = json_parser_new ();
if (!json_parser_load_from_file (jparser, path, error))
return FALSE;
JsonNode *metarootval = json_parser_get_root (jparser);
return rpmostree_composeutil_read_json_metadata (metarootval, metadata, error);
}
/* Convert hash table of metadata into finalized GVariant */
GVariant *
rpmostree_composeutil_finalize_metadata (GHashTable *metadata,
int rootfs_dfd,
GError **error)
{
g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
GLNX_HASH_TABLE_FOREACH_KV (metadata, const char*, strkey, GVariant*, v)
g_variant_builder_add (metadata_builder, "{sv}", strkey, v);
/* include list of packages in rpmdb; this is used client-side for easily previewing
* pending updates. once we only support unified core composes, this can easily be much
* more readily injected during assembly */
g_autoptr(GVariant) rpmdb_v = NULL;
if (!rpmostree_create_rpmdb_pkglist_variant (rootfs_dfd, ".", &rpmdb_v,
NULL, error))
return FALSE;
g_variant_builder_add (metadata_builder, "{sv}", "rpmostree.rpmdb.pkglist", rpmdb_v);
g_autoptr(GVariant) ret = g_variant_ref_sink (g_variant_builder_end (metadata_builder));
/* Canonicalize to big endian, like OSTree does. Without this, any numbers
* we place in the metadata will be unreadable since clients won't know
* their endianness.
*/
rpmostree_variant_native_to_be (&ret);
return util::move_nullify (ret);
}
/* Implements --write-composejson-to, and also prints values.
* If `path` is NULL, we'll just print some data.
*/
gboolean
rpmostree_composeutil_write_composejson (OstreeRepo *repo,
const char *path,
const OstreeRepoTransactionStats *stats,
const char *new_revision,
GVariant *new_commit,
GVariantBuilder *builder,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GVariant) new_commit_inline_meta = g_variant_get_child_value (new_commit, 0);
if (stats)
{
g_variant_builder_add (builder, "{sv}", "ostree-n-metadata-total",
g_variant_new_uint32 (stats->metadata_objects_total));
g_print ("Metadata Total: %u\n", stats->metadata_objects_total);
g_variant_builder_add (builder, "{sv}", "ostree-n-metadata-written",
g_variant_new_uint32 (stats->metadata_objects_written));
g_print ("Metadata Written: %u\n", stats->metadata_objects_written);
g_variant_builder_add (builder, "{sv}", "ostree-n-content-total",
g_variant_new_uint32 (stats->content_objects_total));
g_print ("Content Total: %u\n", stats->content_objects_total);
g_print ("Content Written: %u\n", stats->content_objects_written);
g_variant_builder_add (builder, "{sv}", "ostree-n-content-written",
g_variant_new_uint32 (stats->content_objects_written));
g_print ("Content Cache Hits: %u\n", stats->devino_cache_hits);
g_variant_builder_add (builder, "{sv}", "ostree-n-cache-hits",
g_variant_new_uint32 (stats->devino_cache_hits));
g_print ("Content Bytes Written: %" G_GUINT64_FORMAT "\n", stats->content_bytes_written);
g_variant_builder_add (builder, "{sv}", "ostree-content-bytes-written",
g_variant_new_uint64 (stats->content_bytes_written));
}
g_variant_builder_add (builder, "{sv}", "ostree-commit", g_variant_new_string (new_revision));
g_autofree char *content_checksum = ostree_commit_get_content_checksum (new_commit);
g_variant_builder_add (builder, "{sv}", "ostree-content-checksum", g_variant_new_string (content_checksum));
const char *commit_version = NULL;
(void)g_variant_lookup (new_commit_inline_meta, OSTREE_COMMIT_META_KEY_VERSION, "&s", &commit_version);
if (commit_version)
g_variant_builder_add (builder, "{sv}", "ostree-version", g_variant_new_string (commit_version));
/* Since JavaScript doesn't have 64 bit integers and hence neither does JSON,
* store this as a string:
* https://stackoverflow.com/questions/10286204/the-right-json-date-format
* */
{ guint64 commit_ts = ostree_commit_get_timestamp (new_commit);
g_autofree char *commit_ts_iso_8601 = rpmostree_timestamp_str_from_unix_utc (commit_ts);
g_variant_builder_add (builder, "{sv}", "ostree-timestamp", g_variant_new_string (commit_ts_iso_8601));
}
const char *inputhash = NULL;
(void)g_variant_lookup (new_commit_inline_meta, "rpmostree.inputhash", "&s", &inputhash);
/* We may not have the inputhash in the split-up installroot case */
if (inputhash)
g_variant_builder_add (builder, "{sv}", "rpm-ostree-inputhash", g_variant_new_string (inputhash));
g_autofree char *parent_revision = ostree_commit_get_parent (new_commit);
if (path && parent_revision)
{
/* don't error if the parent doesn't exist */
gboolean parent_exists;
if (!ostree_repo_has_object (repo, OSTREE_OBJECT_TYPE_COMMIT, parent_revision,
&parent_exists, cancellable, error))
return FALSE;
if (parent_exists)
{
g_autoptr(GVariant) diffv = NULL;
if (!rpm_ostree_db_diff_variant (repo, parent_revision, new_revision,
TRUE, &diffv, cancellable, error))
return FALSE;
g_variant_builder_add (builder, "{sv}", "pkgdiff", diffv);
}
}
if (path)
{
g_autoptr(GVariant) composemeta_v = g_variant_builder_end (builder);
JsonNode *composemeta_node = json_gvariant_serialize (composemeta_v);
glnx_unref_object JsonGenerator *generator = json_generator_new ();
json_generator_set_root (generator, composemeta_node);
char *dnbuf = strdupa (path);
const char *dn = dirname (dnbuf);
g_auto(GLnxTmpfile) tmpf = { 0, };
if (!glnx_open_tmpfile_linkable_at (AT_FDCWD, dn, O_WRONLY | O_CLOEXEC,
&tmpf, error))
return FALSE;
g_autoptr(GOutputStream) out = g_unix_output_stream_new (tmpf.fd, FALSE);
/* See also similar code in status.c */
if (json_generator_to_stream (generator, out, cancellable, error) <= 0
|| (error != NULL && *error != NULL))
return FALSE;
/* World readable to match --write-commitid-to which uses umask */
if (!glnx_fchmod (tmpf.fd, 0644, error))
return FALSE;
if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE,
AT_FDCWD, path, error))
return FALSE;
}
return TRUE;
}