From b7dbabe065c514e8bf7662f6f7a4ff4e7f719a06 Mon Sep 17 00:00:00 2001 From: James Antill Date: Thu, 3 Jul 2014 03:32:21 -0400 Subject: [PATCH] Add "rpm" command, for doing rpm/yum queries on commits. It currently has the following sub-commands: diff COMMIT COMMIT for rpmtree diff. list [prefix...] COMMIT... for "yum list" like command. version COMMIT... for "yum version" like command. ...bunch of FIXME's, UI output isn't great, needs docs. We also don't use the same code as the treediff on upgrade atm. --- Makefile-rpm-ostree.am | 1 + src/main.c | 1 + src/rpmostree-builtin-rpm.c | 769 ++++++++++++++++++++++++++++++++++++ src/rpmostree-builtins.h | 1 + 4 files changed, 772 insertions(+) create mode 100644 src/rpmostree-builtin-rpm.c diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am index 0357691c..6e3efb87 100644 --- a/Makefile-rpm-ostree.am +++ b/Makefile-rpm-ostree.am @@ -47,6 +47,7 @@ rpm_ostree_SOURCES = src/main.c \ src/rpmostree-builtin-rollback.c \ src/rpmostree-builtin-rebase.c \ src/rpmostree-builtin-status.c \ + src/rpmostree-builtin-rpm.c \ src/rpmostree-compose-builtin-tree.c \ src/rpmostree-compose-builtin-sign.c \ $(NULL) diff --git a/src/main.c b/src/main.c index b23e4355..d6a45548 100644 --- a/src/main.c +++ b/src/main.c @@ -37,6 +37,7 @@ static RpmOstreeCommand commands[] = { { "rebase", rpmostree_builtin_rebase, 0 }, { "rollback", rpmostree_builtin_rollback, 0 }, { "status", rpmostree_builtin_status, 0 }, + { "rpm", rpmostree_builtin_rpm, 0 }, { NULL } }; diff --git a/src/rpmostree-builtin-rpm.c b/src/rpmostree-builtin-rpm.c new file mode 100644 index 00000000..ead250ca --- /dev/null +++ b/src/rpmostree-builtin-rpm.c @@ -0,0 +1,769 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2014 James Antil + * + * 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 +#include +#include + +#include "rpmostree-builtins.h" +#include "rpmostree-treepkgdiff.h" +#include "rpmostree-util.h" + +/* FIXME: */ +#define OSTREE_GIO_FAST_QUERYINFO ("standard::name,standard::type,standard::size,standard::is-symlink,standard::symlink-target," \ + "unix::device,unix::inode,unix::mode,unix::uid,unix::gid,unix::rdev") + + +#include "libgsystem.h" + + +#include +#include +#include +#include + + +static char *opt_repo; +static char *opt_rpmdbdir; + +static GOptionEntry option_entries[] = { + { "repo", 'r', 0, G_OPTION_ARG_STRING, &opt_repo, "Path to OSTree repository", "REPO" }, + { "rpmdbdir", 0, 0, G_OPTION_ARG_STRING, &opt_rpmdbdir, "Working directory", "WORKDIR" }, + { NULL } +}; + +/* FIXME: merge and put into libgsystem or rpmostree-utils */ +#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ + static inline void func##p(type *p) { \ + if (*p) \ + func(*p); \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +static void +header_free_p (gpointer data) +{ + headerFree (data); +} + +static int +header_name_cmp (Header h1, Header h2) +{ + const char* n1 = headerGetString (h1, RPMTAG_NAME); + const char* n2 = headerGetString (h2, RPMTAG_NAME); + int cmp = strcmp (n1, n2); + /* printf("%s <=> %s = %u\n", n1, n2, cmp); */ + return cmp; +} + +static int +header_cmp_p (gconstpointer gph1, gconstpointer gph2) +{ + const Header *ph1 = gph1; + const Header *ph2 = gph2; + Header h1 = *ph1; + Header h2 = *ph2; + int cmp = header_name_cmp (h1, h2); + if (!cmp) + cmp = rpmVersionCompare (h1, h2); + return cmp; +} + +/* generate pkg nevra/etc. strings from Header objects */ +static char * +pkg_envra_strdup (Header h1) +{ + const char* name = headerGetString (h1, RPMTAG_NAME); + uint64_t epoch = headerGetNumber (h1, RPMTAG_EPOCH); + const char* version = headerGetString (h1, RPMTAG_VERSION); + const char* release = headerGetString (h1, RPMTAG_RELEASE); + const char* arch = headerGetString (h1, RPMTAG_ARCH); + char *envra = NULL; + + if (!epoch) + envra = g_strdup_printf ("%s-%s-%s.%s", name, version, release, arch); + else + { + unsigned long long ullepoch = epoch; + envra = g_strdup_printf ("%llu:%s-%s-%s.%s", ullepoch, name, + version, release, arch); + } + + return envra; +} + +static char * +pkg_nevra_strdup (Header h1) +{ + const char* name = headerGetString (h1, RPMTAG_NAME); + uint64_t epoch = headerGetNumber (h1, RPMTAG_EPOCH); + const char* version = headerGetString (h1, RPMTAG_VERSION); + const char* release = headerGetString (h1, RPMTAG_RELEASE); + const char* arch = headerGetString (h1, RPMTAG_ARCH); + char *nevra = NULL; + + if (!epoch) + nevra = g_strdup_printf ("%s-%s-%s.%s", name, version, release, arch); + else + { + unsigned long long ullepoch = epoch; + nevra = g_strdup_printf ("%s-%llu:%s-%s.%s", name, + ullepoch, version, release, arch); + } + + return nevra; +} + +static char * +pkg_na_strdup (Header h1) +{ + const char* name = headerGetString (h1, RPMTAG_NAME); + const char* arch = headerGetString (h1, RPMTAG_ARCH); + + return g_strdup_printf ("%s.%s", name, arch); +} + +static char * +pkg_nvr_strdup (Header h1) +{ + const char* name = headerGetString (h1, RPMTAG_NAME); + const char* version = headerGetString (h1, RPMTAG_VERSION); + const char* release = headerGetString (h1, RPMTAG_RELEASE); + + return g_strdup_printf ("%s-%s-%s", name, version, release); +} + + +struct RpmHeaders +{ + rpmts ts; /* rpm transaction set the headers belong to */ + GPtrArray *hs; /* list of rpm header objects from = Header */ +}; + +#define CASENCMP_EQ(x, y, n) (g_ascii_strncasecmp (x, y, n) == 0) +#define CASEFNMATCH_EQ(x, y) (fnmatch (x, y, FNM_CASEFOLD) == 0) + +/* find a common prefix length that doesn't need fnmatch */ +static size_t +pat_fnmatch_prefix (GPtrArray *patterns) +{ + gsize ret = G_MAXSIZE; + int num = 0; + + if (!patterns) + return 0; + + for (num = 0; num < patterns->len; num++) + { + const char *pat = patterns->pdata[num]; + gsize prefix = 0; + + while (*pat) + { + if (*pat == ':') + break; + if (*pat == '-') + break; + if (*pat == '*') + break; + if (*pat == '?') + break; + if (*pat == '.') + break; + if (*pat == '[') + break; + + ++prefix; + ++pat; + } + + ret = MIN(ret, prefix); + } + + return ret; +} + +static gboolean +pat_fnmatch_match (Header pkg, const char *name, + gsize patprefixlen, GPtrArray *patterns) +{ + int num = 0; + gs_free char *pkg_na = NULL; + gs_free char *pkg_nevra = NULL; + gs_free char *pkg_nvr = NULL; + + if (!patterns) + return TRUE; + + for (num = 0; num < patterns->len; num++) + { + const char *pattern = patterns->pdata[num]; + + if (patprefixlen && !CASENCMP_EQ (name, pattern, patprefixlen)) + continue; + + if (!pkg_na) + { + pkg_nevra = pkg_nevra_strdup (pkg); + pkg_na = pkg_na_strdup (pkg); + pkg_nvr = pkg_nvr_strdup (pkg); + } + + if (CASEFNMATCH_EQ (pattern, name) || + CASEFNMATCH_EQ (pattern, pkg_nevra) || + CASEFNMATCH_EQ (pattern, pkg_na) || + CASEFNMATCH_EQ (pattern, pkg_nvr) || + FALSE) + return TRUE; + } + + return FALSE; +} + +static struct RpmHeaders * +rpmhdrs_new (const char *root, GPtrArray *patterns) +{ + rpmts ts = rpmtsCreate(); + int status = -1; + rpmdbMatchIterator iter; + Header h1; + GPtrArray *hs = NULL; + struct RpmHeaders *ret = NULL; + gsize patprefixlen = pat_fnmatch_prefix (patterns);; + + rpmtsSetVSFlags (ts, _RPMVSF_NODIGESTS | _RPMVSF_NOSIGNATURES); + + status = rpmtsSetRootDir (ts, root); + if (status) exit (2); // FIXME: better fail. + + /* iter = rpmtsInitIterator (ts, RPMTAG_NAME, "yum", 0); */ + iter = rpmtsInitIterator (ts, RPMDBI_PACKAGES, NULL, 0); + + hs = g_ptr_array_new_with_free_func (header_free_p); + while ((h1 = rpmdbNextIterator (iter))) + { + const char* name = headerGetString (h1, RPMTAG_NAME); + + if (g_str_equal (name, "gpg-pubkey")) continue; /* rpmdb abstraction leak */ + + if (!pat_fnmatch_match (h1, name, patprefixlen, patterns)) + continue; + + h1 = headerLink (h1); + g_ptr_array_add (hs, h1); + } + iter = rpmdbFreeIterator (iter); + + g_ptr_array_sort (hs, header_cmp_p); + + ret = g_malloc0 (sizeof (struct RpmHeaders)); + + ret->ts = ts; + ret->hs = hs; + + return ret; +} + +static void +rpmhdrs_free (struct RpmHeaders *l1) +{ + g_ptr_array_free (l1->hs, TRUE); + l1->hs = NULL; + l1->ts = rpmtsFree (l1->ts); + + g_free (l1); +} + +static char * +pkg_yumdb_relpath (Header h1) +{ + const char* name = headerGetString (h1, RPMTAG_NAME); + const char* version = headerGetString (h1, RPMTAG_VERSION); + const char* release = headerGetString (h1, RPMTAG_RELEASE); + const char* arch = headerGetString (h1, RPMTAG_ARCH); + const char* pkgid = headerGetString (h1, RPMTAG_SHA1HEADER); + char *path = NULL; + + g_assert (name[0]); + + // FIXME: sanitize name ... remove '/' and '~' + + // FIXME: If pkgid doesn't exist use: name.buildtime + + path = g_strdup_printf ("%c/%s-%s-%s-%s-%s", name[0], + pkgid, name, version, release, arch); + + return path; +} + +static GInputStream * +pkg_yumdb_file_read (GFile *root, Header pkg, const char *yumdb_key, + GCancellable *cancellable, + GError **error) +{ + gs_unref_object GFile *f = NULL; + gs_free char *pkgpath = pkg_yumdb_relpath (pkg); + gs_free char *path = g_strconcat ("/var/lib/yum/yumdb/", pkgpath, "/", + yumdb_key, NULL); + + f = g_file_resolve_relative_path (root, path); + return (GInputStream*)g_file_read (f, cancellable, error); +} + +static char * +pkg_yumdb_strdup (GFile *root, Header pkg, const char *yumdb_key, + GCancellable *cancellable, + GError **error) +{ + gs_unref_object GFile *f = NULL; + gs_free char *pkgpath = pkg_yumdb_relpath (pkg); + gs_free char *path = g_strconcat ("/var/lib/yum/yumdb/", pkgpath, "/", + yumdb_key, NULL); + char *ret = NULL; + + f = g_file_resolve_relative_path (root, path); + + if (!_rpmostree_file_load_contents_utf8_allow_noent (f, &ret, + cancellable, error)) + return g_strdup (""); + + return ret; +} + +static void +pkg_print (GFile *root, Header pkg, + GCancellable *cancellable, + GError **error) +{ + gs_free char *nevra = pkg_nevra_strdup (pkg); + gs_free char *from_repo = pkg_yumdb_strdup (root, pkg, "from_repo", + cancellable, error); + + if (*from_repo) + printf("%s @%s\n", nevra, from_repo); + else + printf("%s\n", nevra); +} + +static void +rpmhdrs_list (GFile *root, struct RpmHeaders *l1, + GCancellable *cancellable, + GError **error) +{ + int num = 0; + + while (num < l1->hs->len) + { + Header h1 = l1->hs->pdata[num++]; + printf (" "); + pkg_print (root, h1, cancellable, error); + } +} + +static char * +rpmhdrs_rpmdbv (GFile *root, struct RpmHeaders *l1, + GCancellable *cancellable, + GError **error) +{ + GChecksum *checksum = g_checksum_new (G_CHECKSUM_SHA1); + gs_free char *checksum_cstr = NULL; + int num = 0; + + while (num < l1->hs->len) + { + Header pkg = l1->hs->pdata[num++]; + gs_unref_object GInputStream *tin = NULL; + gs_unref_object GInputStream *din = NULL; + char tbuf[1024]; + char dbuf[1024]; + gs_free char *envra = pkg_envra_strdup (pkg); + gsize tbytes_read = 0; + gsize dbytes_read = 0; + + g_checksum_update (checksum, (guint8*)envra, strlen(envra)); + + tin = pkg_yumdb_file_read (root, pkg, "checksum_type", cancellable,error); + if (!tin) + continue; + + din = pkg_yumdb_file_read (root, pkg, "checksum_data", cancellable,error); + if (!din) + continue; + + if (!g_input_stream_read_all (tin, tbuf, sizeof(tbuf), &tbytes_read, + cancellable, error)) + continue; + if (!g_input_stream_read_all (din, dbuf, sizeof(dbuf), &dbytes_read, + cancellable, error)) + continue; + + if (tbytes_read >= 512) + continue; // should be == len(md5) or len(sha256) etc. + if (dbytes_read >= 1024) + continue; // should be digest size of md5/sha256/sha512/etc. + + g_checksum_update (checksum, (guint8*)tbuf, tbytes_read); + g_checksum_update (checksum, (guint8*)dbuf, dbytes_read); + } + + checksum_cstr = g_strdup (g_checksum_get_string (checksum)); + + g_checksum_free (checksum); + + return g_strdup_printf ("%u:%s", num, checksum_cstr); +} + +static void +rpmhdrs_diff (GFile *root1, struct RpmHeaders *l1, + GFile *root2, struct RpmHeaders *l2, + GCancellable *cancellable, + GError **error) +{ + int n1 = 0; + int n2 = 0; + + while (n1 < l1->hs->len) + { + Header h1 = l1->hs->pdata[n1]; + if (n2 >= l2->hs->len) + { + printf ("-"); + pkg_print (root1, h1, cancellable, error); + ++n1; + } + else + { + Header h2 = l2->hs->pdata[n2]; + int cmp = header_name_cmp (h1, h2); + + if (cmp > 0) + { + printf("+"); + pkg_print (root2, h2, cancellable, error); + ++n2; + } + else if (cmp < 0) + { + printf("-"); + pkg_print (root1, h1, cancellable, error); + ++n1; + } + else + { + cmp = rpmVersionCompare (h1, h2); + if (!cmp) { ++n1; ++n2; continue; } + + printf("!<= "); + pkg_print (root1, h1, cancellable, error); + ++n1; + printf("!>= "); + pkg_print (root2, h2, cancellable, error); + ++n2; + } + } + } + + while (n2 < l2->hs->len) + { + Header h2 = l2->hs->pdata[n2]; + + printf("+"); + pkg_print (root2, h2, cancellable, error); + ++n2; + } +} + +/* data needed to extract rpm/yum data from a commit revision */ +struct RpmRevisionData +{ + struct RpmHeaders *rpmdb; + GFile *root; + char *commit; +}; + +static void +rpmrev_free (struct RpmRevisionData *ptr) +{ + gs_unref_object GFile *root = NULL; + gs_free char *commit = NULL; + + if (!ptr) + return; + + rpmhdrs_free (ptr->rpmdb); + ptr->rpmdb = NULL; + + root = ptr->root; + ptr->root = NULL; + + commit = ptr->commit; + ptr->commit = NULL; + + g_free (ptr); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct RpmRevisionData *, rpmrev_free); + +#define _cleanup_rpmrev_ __attribute__((cleanup(rpmrev_freep))) + +static struct RpmRevisionData * +rpmrev_new (OstreeRepo *repo, GFile *rpmdbdir, const char *rev, + GPtrArray *patterns, + GCancellable *cancellable, + GError **error) +{ + gs_unref_object GFile *subtree = NULL; + gs_unref_object GFileInfo *file_info = NULL; + gs_unref_object GFile *targetp = NULL; + gs_unref_object GFile *target = NULL; + gs_free char *targetp_path = NULL; + gs_free char *target_path = NULL; + gs_unref_object GFile *revdir = NULL; + GError *tmp_error = NULL; + struct RpmRevisionData *rpmrev = NULL; + gs_unref_object GFile *root = NULL; + gs_free char *commit = NULL; + + if (!ostree_repo_read_commit (repo, rev, &root, &commit, NULL, error)) + goto out; + + subtree = g_file_resolve_relative_path (root, "/var/lib/rpm"); + if (!g_file_query_exists (subtree, cancellable)) + subtree = g_file_resolve_relative_path (root, "/usr/share/rpm"); + + file_info = g_file_query_info (subtree, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, &tmp_error); + if (!file_info) // g_file_query_exists (subtree, cancellable)) + { + g_propagate_error (error, tmp_error); + // g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + // "No rpmdb directory found"); + goto out; + } + + revdir = g_file_resolve_relative_path (rpmdbdir, rev); + + targetp_path = g_strconcat (rev, "/var/lib", NULL); + targetp = g_file_resolve_relative_path (rpmdbdir, targetp_path); + target_path = g_strconcat (rev, "/var/lib/rpm", NULL); + target = g_file_resolve_relative_path (rpmdbdir, target_path); + if (!g_file_query_exists (target, cancellable) && + (!gs_file_ensure_directory (targetp, TRUE, cancellable, error) || + !ostree_repo_checkout_tree (repo, OSTREE_REPO_CHECKOUT_MODE_NONE, + OSTREE_REPO_CHECKOUT_OVERWRITE_NONE, + target, OSTREE_REPO_FILE (subtree), + file_info, cancellable, error))) + goto out; + + rpmrev = g_malloc0 (sizeof(struct RpmRevisionData)); + + rpmrev->root = root; root = NULL; + rpmrev->commit = commit; commit = NULL; + rpmrev->rpmdb = rpmhdrs_new (gs_file_get_path_cached (revdir), patterns); + + out: + return rpmrev; +} + +gboolean +rpmostree_builtin_rpm (int argc, + char **argv, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_ptrarray GPtrArray *deployments = NULL; // list of all depoyments + GOptionContext *context = g_option_context_new ("- Run rpm commands on systems"); + gs_unref_object OstreeRepo *repo = NULL; + gs_unref_object GFile *rpmdbdir = NULL; + gboolean rpmdbdir_is_tmp = FALSE; + const char *cmd = NULL; + int argnum = 2; + + g_option_context_add_main_entries (context, option_entries, NULL); + + if (!g_option_context_parse (context, &argc, &argv, error)) + goto out; + + + if (argc < 3) + { + g_printerr ("usage: rpm-ostree rpm diff|list|version COMMIT...\n"); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Argument processing failed"); + goto out; + } + cmd = argv[1]; + + if ((g_str_equal (cmd, "diff")) && (argc != 4)) + { + g_printerr ("usage: rpm-ostree rpm diff COMMIT COMMIT\n"); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Argument processing failed"); + goto out; + } + + if (!opt_repo) + { + gs_unref_object OstreeSysroot *sysroot = NULL; + + sysroot = ostree_sysroot_new_default (); + if (!ostree_sysroot_load (sysroot, cancellable, error)) + goto out; + + if (!ostree_sysroot_get_repo (sysroot, &repo, cancellable, error)) + goto out; + } + else + { + gs_unref_object GFile *repo_path = NULL; + + repo_path = g_file_new_for_path (opt_repo); + repo = ostree_repo_new (repo_path); + if (!ostree_repo_open (repo, cancellable, error)) + goto out; + } + + if (rpmReadConfigFiles (NULL, NULL)) + exit (2); // FIXME: Better fail + + if (opt_rpmdbdir) + { + rpmdbdir = g_file_new_for_path (opt_rpmdbdir); + } + else + { + gs_free char *tmpd = g_mkdtemp (g_strdup ("/var/tmp/rpm-ostree.XXXXXX")); + rpmdbdir = g_file_new_for_path (tmpd); + rpmdbdir_is_tmp = TRUE; + } + + if (FALSE) {} + else if (g_str_equal(cmd, "version")) + for (; argnum < argc; ++argnum) + { + const char *rev = argv[argnum]; + _cleanup_rpmrev_ struct RpmRevisionData *rpmrev = NULL; + gs_free char *rpmdbv = NULL; + + rpmrev = rpmrev_new (repo, rpmdbdir, rev, NULL, cancellable, error); + if (!rpmrev) + goto out; + + rpmdbv = rpmhdrs_rpmdbv (rpmrev->root, rpmrev->rpmdb, + cancellable, error); + + // FIXME: g_console + if (!g_str_equal (rev, rpmrev->commit)) + printf ("ostree commit: %s (%s)\n", rev, rpmrev->commit); + else + printf ("ostree commit: %s\n", rev); + printf (" rpmdbv is: %24s%s\n", "", rpmdbv); + } + else if (g_str_equal (cmd, "diff")) + { + _cleanup_rpmrev_ struct RpmRevisionData *rpmrev1 = NULL; + _cleanup_rpmrev_ struct RpmRevisionData *rpmrev2 = NULL; + + if (!(rpmrev1 = rpmrev_new (repo, rpmdbdir, argv[2], NULL, + cancellable, error))) + goto out; + if (!(rpmrev2 = rpmrev_new (repo, rpmdbdir, argv[3], NULL, + cancellable, error))) + goto out; + + if (!g_str_equal (argv[2], rpmrev1->commit)) + printf ("ostree diff commit old: %s (%s)\n", argv[2], rpmrev1->commit); + else + printf ("ostree diff commit old: %s\n", argv[2]); + if (!g_str_equal (argv[3], rpmrev2->commit)) + printf ("ostree diff commit new: %s (%s)\n", argv[3], rpmrev2->commit); + else + printf ("ostree diff commit new: %s\n", argv[3]); + + rpmhdrs_diff (rpmrev1->root, rpmrev1->rpmdb, rpmrev2->root,rpmrev2->rpmdb, + cancellable, error); + } + else if (g_str_equal (cmd, "list")) + { + gs_unref_ptrarray GPtrArray *patterns = NULL; + int listnum = argc - 3; + char **listargv = argv + 2; + char *commit; + + // Find first commit arg. ... all before it are list args. + while ((listnum > 0) && + ostree_repo_resolve_rev (repo, listargv[listnum-1], TRUE, + &commit, NULL) && commit) + { + g_free (commit); // FiXME: broken allow_noent? + listnum--; + } + + argc -= listnum; + argv += listnum; + if (listnum) + { + patterns = g_ptr_array_new (); + while (listnum--) + g_ptr_array_add (patterns, *listargv++); + } + + for (; argnum < argc; ++argnum) + { + const char *rev = argv[argnum]; + _cleanup_rpmrev_ struct RpmRevisionData *rpmrev = NULL; + + rpmrev = rpmrev_new (repo, rpmdbdir, rev, patterns, + cancellable, error); + if (!rpmrev) + goto out; + + if (!g_str_equal (rev, rpmrev->commit)) + printf ("ostree commit: %s (%s)\n", rev, rpmrev->commit); + else + printf ("ostree commit: %s\n", rev); + + rpmhdrs_list (rpmrev->root, rpmrev->rpmdb, cancellable, error); + } + } + else + { + g_printerr ("rpm-ostree rpm SUB-COMMANDS:\n"); + g_printerr (" diff COMMIT COMMIT\n"); + g_printerr (" list [prefix-pkgname...] COMMIT...\n"); + g_printerr (" version COMMIT...\n"); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Command processing failed"); + goto out; + } + + ret = TRUE; + out: + if (rpmdbdir_is_tmp) + (void) gs_shutil_rm_rf (rpmdbdir, NULL, NULL); + if (context) + g_option_context_free (context); + + return ret; +} diff --git a/src/rpmostree-builtins.h b/src/rpmostree-builtins.h index 5b19ea1f..6b7bc333 100644 --- a/src/rpmostree-builtins.h +++ b/src/rpmostree-builtins.h @@ -37,6 +37,7 @@ BUILTINPROTO(upgrade); BUILTINPROTO(rebase); BUILTINPROTO(rollback); BUILTINPROTO(status); +BUILTINPROTO(rpm); #undef BUILTINPROTO