/* -*- 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 #include "rpmostree-builtins.h" #include "rpmostree-treepkgdiff.h" #include "rpmostree-util.h" #include "rpmostree-builtin-db.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 #include gboolean rpmostree_db_builtin_diff (int argc, char **argv, GCancellable *cancellable, GError **error); gboolean rpmostree_db_builtin_list (int argc, char **argv, GCancellable *cancellable, GError **error); gboolean rpmostree_db_builtin_version (int argc, char **argv, GCancellable *cancellable, GError **error); gboolean rpmostree_db_option_context_parse (GOptionContext *context, const GOptionEntry *main_entries, int *argc, char ***argv, OstreeRepo **out_repo, GFile **out_rpmdbdir, gboolean *out_rpmdbdir_is_tmp, GCancellable *cancellable, GError **error); typedef struct { const char *name; gboolean (*fn) (int argc, char **argv, GCancellable *cancellable, GError **error); } RpmOstreeRpmCommand; static RpmOstreeRpmCommand rpm_subcommands[] = { { "diff", rpmostree_db_builtin_diff }, { "list", rpmostree_db_builtin_list }, { "version", rpmostree_db_builtin_version }, { NULL, NULL } }; static char *opt_format; static char *opt_repo; static char *opt_rpmdbdir; static GOptionEntry global_db_entries[] = { { "repo", 'r', 0, G_OPTION_ARG_STRING, &opt_repo, "Path to OSTree repository (defaults to /sysroot/ostree/repo)", "PATH" }, { "rpmdbdir", 0, 0, G_OPTION_ARG_STRING, &opt_rpmdbdir, "Working directory for rpm", "WORKDIR" }, { NULL } }; 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 (const 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, const 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, const 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); // rpm also aborts on mem errors, so this is fine. g_assert (ts); rpmtsSetVSFlags (ts, _RPMVSF_NODIGESTS | _RPMVSF_NOSIGNATURES); // This only fails if root isn't absolute. g_assert (root && root[0] == '/'); status = rpmtsSetRootDir (ts, root); g_assert (status == 0); /* 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) { 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); // allow_noent returns true for noent, false for other errors. if (!_rpmostree_file_load_contents_utf8_allow_noent (f, &ret, NULL, NULL) || !ret) ret = g_strdup (""); return ret; } static gsize _console_get_width(int fd) { struct winsize w; ioctl (STDOUT_FILENO, TIOCGWINSZ, &w); return w.ws_col; } static gsize _console_get_width_stdout_cached(void) { static gsize align = 0; if (!align) align = _console_get_width (1); return align; } static void pkg_print (GFile *root, Header pkg) { gs_free char *nevra = pkg_nevra_strdup (pkg); gs_free char *from_repo = pkg_yumdb_strdup (root, pkg, "from_repo"); gsize align = _console_get_width_stdout_cached (); if (*from_repo) { if (align) { gsize plen = strlen (nevra); gsize rlen = strlen (from_repo) + 1; int off = 0; --align; // hacky ... for leading spaces. off = align - (plen + rlen); if (align > (plen + rlen)) printf ("%s%*s@%s\n", nevra, off, "", from_repo); else align = 0; } if (!align) printf ("%s @%s\n", nevra, from_repo); } else printf("%s\n", nevra); } static void rpmhdrs_list (GFile *root, struct RpmHeaders *l1) { int num = 0; while (num < l1->hs->len) { Header h1 = l1->hs->pdata[num++]; printf (" "); pkg_print (root, h1); } } 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); } struct RpmHeadersDiff { GPtrArray *hs_add; /* list of rpm header objects from = Header */ GPtrArray *hs_del; /* list of rpm header objects from = Header */ GPtrArray *hs_mod_old; /* list of rpm header objects from = Header */ GPtrArray *hs_mod_new; /* list of rpm header objects from = Header */ }; static struct RpmHeadersDiff * rpmhdrs_diff_new (void) { struct RpmHeadersDiff *ret = g_malloc0(sizeof (struct RpmHeadersDiff)); ret->hs_add = g_ptr_array_new (); ret->hs_del = g_ptr_array_new (); ret->hs_mod_old = g_ptr_array_new (); ret->hs_mod_new = g_ptr_array_new (); return ret; } static void rpmhdrs_diff_free (struct RpmHeadersDiff *diff) { g_ptr_array_free (diff->hs_add, TRUE); g_ptr_array_free (diff->hs_del, TRUE); g_ptr_array_free (diff->hs_mod_old, TRUE); g_ptr_array_free (diff->hs_mod_new, TRUE); g_free (diff); } GS_DEFINE_CLEANUP_FUNCTION0(struct RpmHeadersDiff *, _cleanup_rpmhdrs_diff_free, rpmhdrs_diff_free); #define _cleanup_rpmhdrs_diff_ __attribute__((cleanup(_cleanup_rpmhdrs_diff_free))) struct RpmHeadersDiff * rpmhdrs_diff (struct RpmHeaders *l1, struct RpmHeaders *l2) { int n1 = 0; int n2 = 0; struct RpmHeadersDiff *ret = rpmhdrs_diff_new (); while (n1 < l1->hs->len) { Header h1 = l1->hs->pdata[n1]; if (n2 >= l2->hs->len) { g_ptr_array_add (ret->hs_del, h1); ++n1; } else { Header h2 = l2->hs->pdata[n2]; int cmp = header_name_cmp (h1, h2); if (cmp > 0) { g_ptr_array_add (ret->hs_add, h2); ++n2; } else if (cmp < 0) { g_ptr_array_add (ret->hs_del, h1); ++n1; } else { cmp = rpmVersionCompare (h1, h2); if (!cmp) { ++n1; ++n2; continue; } g_ptr_array_add (ret->hs_mod_old, h1); ++n1; g_ptr_array_add (ret->hs_mod_new, h2); ++n2; } } } while (n2 < l2->hs->len) { Header h2 = l2->hs->pdata[n2]; g_ptr_array_add (ret->hs_add, h2); ++n2; } return ret; } static int _rpmhdrs_diff_cmp_end (const GPtrArray *hs1, const GPtrArray *hs2) { Header h1 = NULL; Header h2 = NULL; if (!hs2->len) return -1; if (!hs1->len) return 1; h1 = hs1->pdata[hs1->len - 1]; h2 = hs2->pdata[hs2->len - 1]; return header_name_cmp (h1, h2); } static void _gptr_array_reverse (GPtrArray *data); void rpmhdrs_diff_prnt_diff (GFile *root1, GFile *root2, struct RpmHeadersDiff *diff) { _gptr_array_reverse (diff->hs_add); _gptr_array_reverse (diff->hs_del); _gptr_array_reverse (diff->hs_mod_old); _gptr_array_reverse (diff->hs_mod_new); g_assert (diff->hs_mod_old->len == diff->hs_mod_new->len); while (diff->hs_add->len || diff->hs_del->len || diff->hs_mod_old->len) { if (_rpmhdrs_diff_cmp_end (diff->hs_mod_old, diff->hs_del) < 0) if (_rpmhdrs_diff_cmp_end (diff->hs_mod_old, diff->hs_add) < 0) { // mod is first Header hm = diff->hs_mod_old->pdata[diff->hs_mod_old->len-1]; printf("!"); pkg_print (root1, hm); g_ptr_array_remove_index(diff->hs_mod_old, diff->hs_mod_old->len-1); printf("="); hm = diff->hs_mod_new->pdata[diff->hs_mod_new->len-1]; pkg_print (root2, hm); g_ptr_array_remove_index(diff->hs_mod_new, diff->hs_mod_new->len-1); } else { // add is first Header ha = diff->hs_add->pdata[diff->hs_add->len-1]; printf ("+"); pkg_print (root2, ha); g_ptr_array_remove_index(diff->hs_add, diff->hs_add->len-1); } else if (_rpmhdrs_diff_cmp_end (diff->hs_del, diff->hs_add) < 0) { // del is first Header hd = diff->hs_del->pdata[diff->hs_del->len-1]; printf ("-"); pkg_print (root1, hd); g_ptr_array_remove_index(diff->hs_del, diff->hs_del->len-1); } else { // add is first Header ha = diff->hs_add->pdata[diff->hs_add->len-1]; printf ("+"); pkg_print (root2, ha); g_ptr_array_remove_index(diff->hs_add, diff->hs_add->len-1); } } rpmhdrs_diff_free (diff); } GS_DEFINE_CLEANUP_FUNCTION0(rpmtd, _cleanup_rpmtdFreeData, rpmtdFreeData); #define _cleanup_rpmtddata_ __attribute__((cleanup(_cleanup_rpmtdFreeData))) static void rpmhdrs_diff_prnt_block (GFile *root1, GFile *root2, struct RpmHeadersDiff *diff) { int num = 0; g_assert (diff->hs_mod_old->len == diff->hs_mod_new->len); if (diff->hs_mod_old->len) { gboolean done = FALSE; for (num = 0; num < diff->hs_mod_new->len; ++num) { Header ho = diff->hs_mod_old->pdata[num]; Header hn = diff->hs_mod_new->pdata[num]; struct rpmtd_s ochanges_date_s; _cleanup_rpmtddata_ rpmtd ochanges_date = NULL; struct rpmtd_s ochanges_name_s; _cleanup_rpmtddata_ rpmtd ochanges_name = NULL; struct rpmtd_s ochanges_text_s; _cleanup_rpmtddata_ rpmtd ochanges_text = NULL; struct rpmtd_s nchanges_date_s; _cleanup_rpmtddata_ rpmtd nchanges_date = NULL; struct rpmtd_s nchanges_name_s; _cleanup_rpmtddata_ rpmtd nchanges_name = NULL; struct rpmtd_s nchanges_text_s; _cleanup_rpmtddata_ rpmtd nchanges_text = NULL; int ocnum = 0; int ncnum = 0; uint64_t ochange_date = 0; const char *ochange_name = NULL; const char *ochange_text = NULL; uint64_t nchange_date = 0; const char *nchange_name = NULL; const char *nchange_text = NULL; g_assert (!header_name_cmp (ho, hn)); if (rpmVersionCompare (ho, hn) > 0) continue; if (!done) { done = TRUE; g_print ("Upgraded:\n"); } printf (" "); pkg_print (root2, hn); // Load the old %changelog entries ochanges_date = &ochanges_date_s; headerGet (ho, RPMTAG_CHANGELOGTIME, ochanges_date, HEADERGET_MINMEM); ochanges_name = &ochanges_name_s; headerGet (ho, RPMTAG_CHANGELOGNAME, ochanges_name, HEADERGET_MINMEM); ochanges_text = &ochanges_text_s; headerGet (ho, RPMTAG_CHANGELOGTEXT, ochanges_text, HEADERGET_MINMEM); ocnum = rpmtdCount (ochanges_date); if (!ocnum) continue; // Load the new %changelog entries nchanges_date = &nchanges_date_s; headerGet (hn, RPMTAG_CHANGELOGTIME, nchanges_date, HEADERGET_MINMEM); nchanges_name = &nchanges_name_s; headerGet (hn, RPMTAG_CHANGELOGNAME, nchanges_name, HEADERGET_MINMEM); nchanges_text = &nchanges_text_s; headerGet (hn, RPMTAG_CHANGELOGTEXT, nchanges_text, HEADERGET_MINMEM); ncnum = rpmtdCount (nchanges_date); if (!ncnum) continue; // Load the latest old %changelog entry. ochange_date = rpmtdGetNumber (ochanges_date); ochange_name = rpmtdGetString (ochanges_name); ochange_text = rpmtdGetString (ochanges_text); while (ncnum > 0) { GDateTime *dt = NULL; gs_free char *date_time_str = NULL; // Load next new %changelog entry, starting at the newest. rpmtdNext (nchanges_date); rpmtdNext (nchanges_name); rpmtdNext (nchanges_text); nchange_date = rpmtdGetNumber (nchanges_date); nchange_name = rpmtdGetString (nchanges_name); nchange_text = rpmtdGetString (nchanges_text); // If we are now older than, or match, the latest old %changelog // then we are done. if (ochange_date > nchange_date) break; if ((ochange_date == nchange_date) && g_str_equal (ochange_name, nchange_name) && g_str_equal (ochange_text, nchange_text)) break; // Otherwise, print. dt = g_date_time_new_from_unix_utc (nchange_date); date_time_str = g_date_time_format (dt, "%a %b %d %Y"); g_date_time_unref (dt); printf ("* %s %s\n%s\n\n", date_time_str, nchange_name, nchange_text); --ncnum; } } done = FALSE; for (num = 0; num < diff->hs_mod_new->len; ++num) { Header ho = diff->hs_mod_old->pdata[num]; Header hn = diff->hs_mod_new->pdata[num]; g_assert (!header_name_cmp (ho, hn)); if (rpmVersionCompare (ho, hn) < 0) continue; if (!done) { done = TRUE; g_print ("Downgraded:\n"); } printf (" "); pkg_print (root2, hn); } } if (diff->hs_del->len) { g_print ("Removed:\n"); for (num = 0; num < diff->hs_del->len; ++num) { Header hd = diff->hs_del->pdata[num]; printf (" "); pkg_print (root1, hd); } } if (diff->hs_add->len) { g_print ("Added:\n"); for (num = 0; num < diff->hs_add->len; ++num) { Header ha = diff->hs_add->pdata[num]; printf (" "); pkg_print (root2, ha); } } rpmhdrs_diff_free (diff); } 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); } struct RpmRevisionData * rpmrev_new (OstreeRepo *repo, GFile *rpmdbdir, const char *rev, const 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)) { g_object_unref (subtree); 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, commit); targetp_path = g_strconcat (commit, "/var/lib", NULL); targetp = g_file_resolve_relative_path (rpmdbdir, targetp_path); target_path = g_strconcat (commit, "/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_USER, 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; } static char * ost_get_prev_commit(OstreeRepo *repo, char *checksum) { char *ret = NULL; gs_unref_variant GVariant *commit = NULL; gs_unref_variant GVariant *parent_csum_v = NULL; GError *tmp_error = NULL; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, &commit, &tmp_error)) goto out; ret = ostree_commit_get_parent (commit); out: g_clear_error (&tmp_error); return ret; } /* glib? */ static void _gptr_array_reverse (GPtrArray *data) { gpointer *ptr = NULL; guint num = 0; g_assert (data); ptr = data->pdata; num = data->len; while (num >= 2) { void *swap = ptr[0]; ptr[0] = ptr[num-1]; ptr[num-1] = swap; num -= 2; ptr++; } } static GPtrArray * ost_get_commit_hashes(OstreeRepo *repo, const char *beg, const char *end, GError **error) { GPtrArray *ret = NULL; gs_free char *beg_checksum = NULL; gs_free char *end_checksum = NULL; gs_free char *parent = NULL; char *checksum = NULL; gboolean worked = FALSE; if (!ostree_repo_read_commit (repo, beg, NULL, &beg_checksum, NULL, error)) goto out; ret = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (ret, g_strdup (beg)); // Add the user defined REFSPEC. if (end && !ostree_repo_read_commit (repo, end, NULL, &end_checksum, NULL, error)) goto out; if (end && g_str_equal (end_checksum, beg_checksum)) goto worked_out; checksum = beg_checksum; while ((parent = ost_get_prev_commit (repo, checksum))) { if (end && g_str_equal (end_checksum, parent)) { // Add the user defined REFSPEC. g_ptr_array_add (ret, g_strdup (end)); break; } g_ptr_array_add (ret, parent); checksum = parent; } if (end && !parent) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid ref range: %s is not a parent of %s", end, beg); goto out; } worked_out: worked = TRUE; out: if (!worked) { g_ptr_array_free (ret, TRUE); ret = NULL; } return ret; } static GPtrArray * cmdline2ptrarray(int argc, char *argv[]) { GPtrArray *ret = NULL; while (argc--) { if (!ret) ret = g_ptr_array_new (); g_ptr_array_add (ret, *argv++); } return ret; } static void _prnt_commit_line(const char *rev, struct RpmRevisionData *rpmrev) { if (!g_str_equal (rev, rpmrev->commit)) printf ("ostree commit: %s (%s)\n", rev, rpmrev->commit); else printf ("ostree commit: %s\n", rev); } static gboolean _builtin_db_version (OstreeRepo *repo, GFile *rpmdbdir, GPtrArray *revs, GCancellable *cancellable, GError **error) { int num = 0; gboolean ret = FALSE; for (num = 0; num < revs->len; num++) { char *rev = revs->pdata[num]; _cleanup_rpmrev_ struct RpmRevisionData *rpmrev = NULL; gs_free char *rpmdbv = NULL; char *mrev = strstr (rev, ".."); if (mrev) { gs_unref_ptrarray GPtrArray *range_revs = NULL; gs_free char *revdup = g_strdup (rev); mrev = revdup + (mrev - rev); *mrev = 0; mrev += 2; if (!*mrev) mrev = NULL; if (!(range_revs = ost_get_commit_hashes (repo, revdup, mrev, error))) goto out; if (!_builtin_db_version (repo, rpmdbdir, range_revs, cancellable, error)) goto out; continue; } 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? _prnt_commit_line (rev, rpmrev); printf (" rpmdbv is: %24s%s\n", "", rpmdbv); } ret = TRUE; out: return ret; } static gboolean _builtin_db_list (OstreeRepo *repo, GFile *rpmdbdir, GPtrArray *revs, const GPtrArray *patterns, GCancellable *cancellable, GError **error) { int num = 0; gboolean ret = FALSE; for (num = 0; num < revs->len; num++) { char *rev = revs->pdata[num]; _cleanup_rpmrev_ struct RpmRevisionData *rpmrev = NULL; char *mrev = strstr (rev, ".."); if (mrev) { gs_unref_ptrarray GPtrArray *range_revs = NULL; gs_free char *revdup = g_strdup (rev); mrev = revdup + (mrev - rev); *mrev = 0; mrev += 2; if (!*mrev) mrev = NULL; if (!(range_revs = ost_get_commit_hashes (repo, revdup, mrev, error))) goto out; if (!_builtin_db_list (repo, rpmdbdir, range_revs, patterns, cancellable, error)) goto out; continue; } rpmrev = rpmrev_new (repo, rpmdbdir, rev, patterns, cancellable, error); if (!rpmrev) goto out; _prnt_commit_line (rev, rpmrev); rpmhdrs_list (rpmrev->root, rpmrev->rpmdb); } ret = TRUE; out: return ret; } static GOptionEntry db_diff_entries[] = { { "format", 'F', 0, G_OPTION_ARG_STRING, &opt_format, "Output format: \"diff\" or (default) \"block\"", "FORMAT" }, { NULL } }; gboolean rpmostree_db_builtin_diff (int argc, char **argv, GCancellable *cancellable, GError **error) { GOptionContext *context; gs_unref_object OstreeRepo *repo = NULL; gs_unref_object GFile *rpmdbdir = NULL; gboolean rpmdbdir_is_tmp = FALSE; struct RpmRevisionData *rpmrev1 = NULL; struct RpmRevisionData *rpmrev2 = NULL; gboolean success = FALSE; context = g_option_context_new ("COMMIT COMMIT - Show package changes between two commits"); if (!rpmostree_db_option_context_parse (context, db_diff_entries, &argc, &argv, &repo, &rpmdbdir, &rpmdbdir_is_tmp, cancellable, error)) goto out; if (argc != 3) { gs_free char *help; g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "\"%s\" takes exactly 2 arguments", g_get_prgname ()); help = g_option_context_get_help (context, FALSE, NULL); g_printerr ("%s", help); goto out; } if (!(rpmrev1 = rpmrev_new (repo, rpmdbdir, argv[1], NULL, cancellable, error))) goto out; if (!(rpmrev2 = rpmrev_new (repo, rpmdbdir, argv[2], NULL, cancellable, error))) goto out; if (!g_str_equal (argv[1], rpmrev1->commit)) printf ("ostree diff commit old: %s (%s)\n", argv[1], rpmrev1->commit); else printf ("ostree diff commit old: %s\n", argv[1]); if (!g_str_equal (argv[2], rpmrev2->commit)) printf ("ostree diff commit new: %s (%s)\n", argv[2], rpmrev2->commit); else printf ("ostree diff commit new: %s\n", argv[2]); if (opt_format == NULL) opt_format = "block"; if (g_str_equal (opt_format, "diff")) { rpmhdrs_diff_prnt_diff (rpmrev1->root, rpmrev2->root, rpmhdrs_diff (rpmrev1->rpmdb, rpmrev2->rpmdb)); } else if (g_str_equal (opt_format, "block")) { rpmhdrs_diff_prnt_block (rpmrev1->root, rpmrev2->root, rpmhdrs_diff (rpmrev1->rpmdb, rpmrev2->rpmdb)); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Format argument is invalid, pick one of: diff, block"); goto out; } success = TRUE; out: /* Free the RpmRevisionData structs explicitly *before* possibly removing * the database directory, since rpmhdrs_free() depends on that directory * being there. */ rpmrev_free (rpmrev1); rpmrev_free (rpmrev2); if (rpmdbdir_is_tmp) (void) gs_shutil_rm_rf (rpmdbdir, NULL, NULL); g_option_context_free (context); return success; } static GOptionEntry db_list_entries[] = { { NULL } }; gboolean rpmostree_db_builtin_list (int argc, char **argv, GCancellable *cancellable, GError **error) { GOptionContext *context; gs_unref_object OstreeRepo *repo = NULL; gs_unref_object GFile *rpmdbdir = NULL; gboolean rpmdbdir_is_tmp = FALSE; gs_unref_ptrarray GPtrArray *patterns = NULL; gboolean success = FALSE; context = g_option_context_new ("[PREFIX-PKGNAME...] COMMIT... - List packages within commits"); if (!rpmostree_db_option_context_parse (context, db_list_entries, &argc, &argv, &repo, &rpmdbdir, &rpmdbdir_is_tmp, cancellable, error)) goto out; /* Put all the arguments in a GPtrArray (because it's easier to deal with * than a string vector), and then split the trailing arguments which are * valid commit checksums into a separate GPtrArray. */ patterns = cmdline2ptrarray (argc - 1, argv + 1); /* XXX Error message for no arguments? */ if (patterns != NULL) { gs_unref_ptrarray GPtrArray *revs = g_ptr_array_new (); GQueue queue = G_QUEUE_INIT; while (patterns->len > 0) { gs_free char *commit = NULL; guint index = patterns->len - 1; char *arg = patterns->pdata[index]; ostree_repo_resolve_rev (repo, arg, TRUE, &commit, NULL); /* Stop when we find a non-commit argument. */ if (commit == NULL) break; /* XXX Need to PREPEND to the 'revs' array to preserve order, but * in GLib versions prior to 2.40 you can only (easily) APPEND * to a GPtrArray. g_ptr_array_insert() is not available to us * at this time, so use a GQueue to swap the order. */ g_queue_push_head (&queue, arg); g_ptr_array_remove_index (patterns, index); } while (!g_queue_is_empty (&queue)) g_ptr_array_add (revs, g_queue_pop_head (&queue)); if (!_builtin_db_list (repo, rpmdbdir, revs, patterns, cancellable, error)) goto out; } success = TRUE; out: if (rpmdbdir_is_tmp) (void) gs_shutil_rm_rf (rpmdbdir, NULL, NULL); g_option_context_free (context); return success; } static GOptionEntry db_version_entries[] = { { NULL } }; gboolean rpmostree_db_builtin_version (int argc, char **argv, GCancellable *cancellable, GError **error) { GOptionContext *context; gs_unref_object OstreeRepo *repo = NULL; gs_unref_object GFile *rpmdbdir = NULL; gboolean rpmdbdir_is_tmp = FALSE; gs_unref_ptrarray GPtrArray *revs = NULL; gboolean success = FALSE; context = g_option_context_new ("COMMIT... - Show rpmdb version of packages within the commits"); if (!rpmostree_db_option_context_parse (context, db_version_entries, &argc, &argv, &repo, &rpmdbdir, &rpmdbdir_is_tmp, cancellable, error)) goto out; revs = cmdline2ptrarray (argc - 1, argv + 1); if (!_builtin_db_version (repo, rpmdbdir, revs, cancellable, error)) goto out; success = TRUE; out: if (rpmdbdir_is_tmp) (void) gs_shutil_rm_rf (rpmdbdir, NULL, NULL); g_option_context_free (context); return success; } static GOptionContext * rpm_option_context_new_with_commands (void) { RpmOstreeRpmCommand *command = rpm_subcommands; GOptionContext *context; GString *summary; context = g_option_context_new ("COMMAND"); summary = g_string_new ("Builtin \"db\" Commands:"); while (command->name != NULL) { g_string_append_printf (summary, "\n %s", command->name); command++; } g_option_context_set_summary (context, summary->str); g_string_free (summary, TRUE); return context; } gboolean rpmostree_db_option_context_parse (GOptionContext *context, const GOptionEntry *main_entries, int *argc, char ***argv, OstreeRepo **out_repo, GFile **out_rpmdbdir, gboolean *out_rpmdbdir_is_tmp, GCancellable *cancellable, GError **error) { gs_unref_object OstreeRepo *repo = NULL; gs_unref_object GFile *rpmdbdir = NULL; gboolean rpmdbdir_is_tmp = FALSE; gboolean success = FALSE; /* Entries are listed in --help output in the order added. We add the * main entries ourselves so that we can add the --repo entry first. */ g_option_context_add_main_entries (context, global_db_entries, NULL); if (!rpmostree_option_context_parse (context, main_entries, argc, argv, error)) goto out; if (opt_repo == NULL) { 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_file = g_file_new_for_path (opt_repo); repo = ostree_repo_new (repo_file); if (!ostree_repo_open (repo, cancellable, error)) goto out; } if (rpmReadConfigFiles (NULL, NULL)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "rpm failed to init: %s", rpmlogMessage ()); goto out; } if (opt_rpmdbdir != NULL) { rpmdbdir = g_file_new_for_path (opt_rpmdbdir); } else { /* tmp on tmpfs is much faster than /var/tmp, * and the rpmdb itself shouldn't be too big. */ gs_free char *tmpd = g_mkdtemp (g_strdup ("/tmp/rpm-ostree.XXXXXX")); rpmdbdir = g_file_new_for_path (tmpd); rpmdbdir_is_tmp = TRUE; ostree_repo_set_disable_fsync (repo, TRUE); } gs_transfer_out_value (out_repo, &repo); gs_transfer_out_value (out_rpmdbdir, &rpmdbdir); if (out_rpmdbdir_is_tmp != NULL) *out_rpmdbdir_is_tmp = rpmdbdir_is_tmp; success = TRUE; out: return success; } gboolean rpmostree_builtin_db (int argc, char **argv, GCancellable *cancellable, GError **error) { RpmOstreeRpmCommand *subcommand; const char *subcommand_name = NULL; gs_free char *prgname = NULL; gboolean ret = FALSE; int in, out; for (in = 1, out = 1; in < argc; in++, out++) { /* The non-option is the command, take it out of the arguments */ if (argv[in][0] != '-') { if (subcommand_name == NULL) { subcommand_name = argv[in]; out--; continue; } } else if (g_str_equal (argv[in], "--")) { break; } argv[out] = argv[in]; } argc = out; subcommand = rpm_subcommands; while (subcommand->name) { if (g_strcmp0 (subcommand_name, subcommand->name) == 0) break; subcommand++; } if (!subcommand->name) { GOptionContext *context; gs_free char *help; context = rpm_option_context_new_with_commands (); /* This will not return for some options (e.g. --version). */ if (rpmostree_option_context_parse (context, NULL, &argc, &argv, error)) { if (subcommand_name == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No \"db\" subcommand specified"); } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown \"db\" subcommand '%s'", subcommand_name); } } help = g_option_context_get_help (context, FALSE, NULL); g_printerr ("%s", help); g_option_context_free (context); goto out; } prgname = g_strdup_printf ("%s %s", g_get_prgname (), subcommand_name); g_set_prgname (prgname); if (!subcommand->fn (argc, argv, cancellable, error)) goto out; ret = TRUE; out: return ret; }