You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
676 lines
21 KiB
676 lines
21 KiB
/* system-upgrade-redhat.c: upgrade a RHEL system |
|
* |
|
* Copyright © 2012 Red Hat Inc. |
|
* |
|
* This program is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; either version 2 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program 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 General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program; if not, see <http://www.gnu.org/licenses/>. |
|
* |
|
* Author(s): Will Woods <wwoods@redhat.com> |
|
*/ |
|
|
|
#include <stdlib.h> |
|
|
|
#include <glib.h> |
|
#include <glib/gstdio.h> |
|
|
|
#include <rpm/rpmlib.h> |
|
#include <rpm/rpmts.h> |
|
#include <rpm/rpmcli.h> /* rpmShowProgress */ |
|
|
|
/* i18n */ |
|
#define GETTEXT_PACKAGE "redhat-upgrade-tool" |
|
#include <locale.h> |
|
#include <glib/gi18n.h> |
|
|
|
/* File names and locations */ |
|
#define UPGRADE_SYMLINK "system-upgrade" |
|
#define UPGRADE_FILELIST "package.list" |
|
|
|
/* How much of the progress bar should each phase use? */ |
|
#define SETUP_PERCENT 4 |
|
#define TRANS_PERCENT 2 |
|
#define INSTALL_BASE_PERCENT (SETUP_PERCENT+TRANS_PERCENT) |
|
#define INSTALL_PERCENT 70 |
|
#define ERASE_PERCENT 24 |
|
/* TODO: add POSTTRANS_PERCENT */ |
|
|
|
/* globals */ |
|
gchar *packagedir = NULL; /* target of UPGRADE_SYMLINK */ |
|
guint installcount = 0; /* number of installs in transaction */ |
|
guint erasecount = 0; /* number of erases in transaction */ |
|
|
|
/* commandline options */ |
|
static gboolean testing = FALSE; |
|
static gboolean plymouth = FALSE; |
|
static gboolean plymouth_verbose = FALSE; |
|
static gboolean debug = FALSE; |
|
static gchar *root = "/"; |
|
|
|
static GOptionEntry options[] = |
|
{ |
|
{ "testing", 'n', 0, G_OPTION_ARG_NONE, &testing, |
|
"Test mode - don't actually install anything", NULL }, |
|
{ "root", 'r', 0, G_OPTION_ARG_FILENAME, &root, |
|
"Top level directory for upgrade (default: \"/\")", NULL }, |
|
{ "plymouth", 'p', 0, G_OPTION_ARG_NONE, &plymouth, |
|
"Show progress on plymouth splash screen", NULL }, |
|
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &plymouth_verbose, |
|
"Show detailed info on plymouth splash screen", NULL }, |
|
{ "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, |
|
"Print copious debugging info", NULL }, |
|
{ NULL } |
|
}; |
|
|
|
/****************** |
|
* Plymouth stuff * |
|
******************/ |
|
|
|
#include "ply-boot-client.h" |
|
|
|
typedef struct |
|
{ |
|
ply_boot_client_t *client; |
|
ply_event_loop_t *loop; |
|
} ply_t; |
|
|
|
ply_t ply = { 0 }; |
|
|
|
/* callback handlers */ |
|
void ply_success(void *user_data, ply_boot_client_t *client) { |
|
ply_event_loop_exit(ply.loop, TRUE); |
|
} |
|
void ply_failure(void *user_data, ply_boot_client_t *client) { |
|
ply_event_loop_exit(ply.loop, FALSE); |
|
} |
|
void ply_disconnect(void *user_data) { |
|
g_warning("unexpectedly disconnected from plymouth"); |
|
plymouth = FALSE; |
|
ply_event_loop_exit(ply.loop, FALSE); |
|
/* TODO: attempt reconnect? */ |
|
} |
|
|
|
/* display-message <text> */ |
|
gboolean set_plymouth_message(const gchar *message) { |
|
if (!plymouth) |
|
return TRUE; |
|
ply_boot_client_attach_to_event_loop(ply.client, ply.loop); |
|
if (message == NULL || *message == '\0') |
|
ply_boot_client_tell_daemon_to_hide_message(ply.client, message, |
|
ply_success, ply_failure, &ply); |
|
else |
|
ply_boot_client_tell_daemon_to_display_message(ply.client, message, |
|
ply_success, ply_failure, &ply); |
|
return ply_event_loop_run(ply.loop); |
|
} |
|
|
|
/* system-update <progress-percent> */ |
|
gboolean set_plymouth_percent(const guint percent) { |
|
gchar *percentstr; |
|
if (!plymouth) |
|
return TRUE; |
|
percentstr = g_strdup_printf("%u", percent); |
|
ply_boot_client_attach_to_event_loop(ply.client, ply.loop); |
|
ply_boot_client_system_update(ply.client, percentstr, |
|
ply_success, ply_failure, &ply); |
|
g_free(percentstr); /* this is OK - plymouth strdups percentstr */ |
|
return ply_event_loop_run(ply.loop); |
|
} |
|
|
|
gboolean plymouth_setup(void) { |
|
gboolean plymouth_ok = FALSE; |
|
|
|
ply.loop = ply_event_loop_new(); |
|
ply.client = ply_boot_client_new(); |
|
|
|
if (!ply_boot_client_connect(ply.client, |
|
(ply_boot_client_disconnect_handler_t) ply_disconnect, |
|
&ply)) { |
|
g_warning("Couldn't connect to plymouth"); |
|
return FALSE; |
|
} |
|
|
|
ply_boot_client_attach_to_event_loop(ply.client, ply.loop); |
|
ply_boot_client_ping_daemon(ply.client, ply_success, ply_failure, &ply); |
|
plymouth_ok = ply_event_loop_run(ply.loop); |
|
|
|
if (!plymouth_ok) |
|
ply_boot_client_free(ply.client); |
|
return plymouth_ok; |
|
} |
|
|
|
void plymouth_finish(void) { |
|
set_plymouth_percent(100); |
|
ply_boot_client_free(ply.client); |
|
ply_event_loop_free(ply.loop); |
|
} |
|
|
|
/************************* |
|
* RPM transaction stuff * |
|
*************************/ |
|
|
|
/* decide whether to upgrade or install the given pkg */ |
|
int installonly(Header hdr) { |
|
/* installonly pkgs are more bane than boon between RHEL versions, |
|
* so always upgrade |
|
*/ |
|
return 0; |
|
} |
|
|
|
/* Add the given file to the given RPM transaction */ |
|
int add_upgrade(rpmts ts, gchar *file) { |
|
FD_t fd = NULL; |
|
Header hdr = NULL; |
|
gchar *fullfile = NULL; |
|
gint rc = 1; |
|
|
|
fullfile = g_build_filename(packagedir, file, NULL); |
|
if (fullfile == NULL) { |
|
g_warning("failed to allocate memory"); |
|
goto out; |
|
} |
|
|
|
/* open package file */ |
|
fd = Fopen(fullfile, "r.ufdio"); |
|
if (fd == NULL) { |
|
g_warning("failed to open file %s", fullfile); |
|
goto out; |
|
} |
|
|
|
/* get the RPM header */ |
|
rc = rpmReadPackageFile(ts, fd, fullfile, &hdr); |
|
if (rc != RPMRC_OK) { |
|
g_warning("unable to read package %s", file); |
|
goto out; |
|
} |
|
|
|
/* add it to the transaction. |
|
* last two args are 'upgrade' and 'relocs' */ |
|
rc = rpmtsAddInstallElement(ts, hdr, file, installonly(hdr) ? 0 : 1, NULL); |
|
g_debug("added %s to transaction", file); |
|
if (rc) { |
|
g_warning("failed to add %s to transaction", file); |
|
goto out; |
|
} |
|
|
|
rc = 0; /* success */ |
|
|
|
out: |
|
if (fd != NULL) |
|
Fclose(fd); |
|
if (hdr != NULL) |
|
headerFree(hdr); |
|
if (fullfile != NULL) |
|
g_free(fullfile); |
|
return rc; |
|
} |
|
|
|
/* Set up the RPM transaction using the list of packages to install */ |
|
rpmts setup_transaction(gchar *files[]) { |
|
rpmts ts = NULL; |
|
rpmps probs = NULL; |
|
rpmtsi tsi = NULL; |
|
rpmte te = NULL; |
|
gchar **file = NULL; |
|
gint rc = 1; |
|
guint numfiles = 0; |
|
guint setup=0, prevpercent=0, percent=0; |
|
|
|
/* Read config and initialize transaction */ |
|
rpmReadConfigFiles(NULL, NULL); |
|
ts = rpmtsCreate(); |
|
rpmtsSetRootDir(ts, root); |
|
|
|
/* Disable signature checking, as anaconda did */ |
|
rpmtsSetVSFlags(ts, rpmtsVSFlags(ts) | _RPMVSF_NOSIGNATURES); |
|
|
|
/* Make plymouth progress bar show signs of life */ |
|
set_plymouth_percent(1); |
|
|
|
/* Populate the transaction */ |
|
numfiles = g_strv_length(files); |
|
g_message("found %u packages to install", numfiles); |
|
g_message("building RPM transaction, one moment..."); |
|
for (file = files; *file && **file; file++) { |
|
if (add_upgrade(ts, *file)) |
|
g_warning("couldn't add %s to the transaction", *file); |
|
percent = (++setup*SETUP_PERCENT) / numfiles; |
|
if (percent > prevpercent) |
|
set_plymouth_percent(percent); |
|
/* Ignore errors, just like anaconda did */ |
|
} |
|
|
|
/* get some transaction info */ |
|
tsi = rpmtsiInit(ts); |
|
while ((te = rpmtsiNext(tsi, 0)) != NULL) { |
|
if (rpmteType(te) == TR_ADDED) |
|
installcount++; |
|
else |
|
erasecount++; |
|
} |
|
g_message("%u packages to install, %u to erase", installcount, erasecount); |
|
tsi = rpmtsiFree(tsi); |
|
|
|
if (installcount == 0) { |
|
g_warning("nothing to upgrade"); |
|
goto fail; |
|
} |
|
|
|
/* We should be finished with the time-consuming bits of setup here. */ |
|
set_plymouth_percent(SETUP_PERCENT); |
|
|
|
/* Check transaction */ |
|
g_message("checking RPM transaction..."); |
|
rc = rpmtsCheck(ts); |
|
if (rc) { |
|
g_warning("transaction check failed (couldn't open rpmdb)"); |
|
goto fail; |
|
} |
|
|
|
/* Log any transaction problems encountered */ |
|
probs = rpmtsProblems(ts); |
|
if (probs != NULL) { |
|
g_message("non-fatal problems with RPM transaction:"); |
|
/* FIXME: ignore anything but RPMPROB_{CONFLICT,REQUIRES} */ |
|
rpmpsPrint(stdout, probs); |
|
rpmpsFree(probs); |
|
} |
|
/* Continue on, ignoring errors, as is anaconda tradition... */ |
|
|
|
/* Order transaction */ |
|
rc = rpmtsOrder(ts); |
|
if (rc > 0) { |
|
/* this should never happen */ |
|
g_warning("rpm transaction ordering failed"); |
|
goto fail; |
|
} |
|
|
|
/* Clean transaction */ |
|
rpmtsClean(ts); |
|
|
|
/* All ready! Return the ts. */ |
|
return ts; |
|
|
|
fail: |
|
rpmtsFree(ts); |
|
return NULL; |
|
} |
|
|
|
/* tag -> string helper (copied from rpm/lib/rpmscript.c) */ |
|
const char *script_type(rpmTagVal tag) { |
|
switch (tag) { |
|
case RPMTAG_PRETRANS: return "%pretrans"; |
|
case RPMTAG_TRIGGERPREIN: return "%triggerprein"; |
|
case RPMTAG_PREIN: return "%pre"; |
|
case RPMTAG_POSTIN: return "%post"; |
|
case RPMTAG_TRIGGERIN: return "%triggerin"; |
|
case RPMTAG_TRIGGERUN: return "%triggerun"; |
|
case RPMTAG_PREUN: return "%preun"; |
|
case RPMTAG_POSTUN: return "%postun"; |
|
case RPMTAG_POSTTRANS: return "%posttrans"; |
|
case RPMTAG_TRIGGERPOSTUN: return "%triggerpostun"; |
|
case RPMTAG_VERIFYSCRIPT: return "%verify"; |
|
default: break; |
|
} |
|
return "%unknownscript"; |
|
} |
|
|
|
/* Transaction callback handler, to display RPM progress */ |
|
void *rpm_trans_callback(const void *arg, |
|
const rpmCallbackType what, |
|
const rpm_loff_t amount, |
|
const rpm_loff_t total, |
|
fnpyKey key, |
|
void *data) |
|
{ |
|
Header hdr = (Header) arg; |
|
static guint percent; |
|
static guint prevpercent; |
|
static guint installed = 0; |
|
static guint erased = 0; |
|
gchar *pkgfile; |
|
static guint cb_seen = 0; |
|
gchar *nvr = NULL; |
|
gchar *file = (gchar *)key; |
|
void *retval = NULL; |
|
|
|
/* |
|
* The upgrade transaction goes through three phases: |
|
* prep: TRANS_START, TRANS_PROGRESS, TRANS_STOP |
|
* duration: basically negligible |
|
* install: INST_START, INST_OPEN_FILE, INST_STOP, INST_CLOSE_FILE |
|
* duration: very roughly 2/3 the transaction |
|
* cleanup: UNINST_START, UNINST_STOP |
|
* duration: the remainder |
|
*/ |
|
|
|
switch (what) { |
|
|
|
/* prep phase: (start, progress..., stop), just once */ |
|
case RPMCALLBACK_TRANS_START: |
|
g_debug("trans_start()"); |
|
g_message("preparing RPM transaction, one moment..."); |
|
break; |
|
case RPMCALLBACK_TRANS_PROGRESS: |
|
/* FIXME: track progress from SETUP_PERCENT to INSTALL_BASE_PERCENT */ |
|
break; |
|
case RPMCALLBACK_TRANS_STOP: |
|
g_debug("trans_stop()"); |
|
break; |
|
|
|
/* install phase: (open, start, progress..., stop, close) for each pkg */ |
|
case RPMCALLBACK_INST_OPEN_FILE: |
|
/* NOTE: hdr is NULL (because we haven't opened the file yet) */ |
|
g_debug("inst_open_file(\"%s\")", file); |
|
pkgfile = g_build_filename(packagedir, file, NULL); |
|
retval = rpmShowProgress(arg, what, amount, total, pkgfile, NULL); |
|
g_free(pkgfile); |
|
break; |
|
case RPMCALLBACK_INST_START: |
|
g_debug("inst_start(\"%s\")", file); |
|
nvr = headerGetAsString(hdr, RPMTAG_NVR); |
|
g_message("[%u/%u] (%u%%) installing %s...", |
|
installed+1, installcount, percent, nvr); |
|
rfree(nvr); |
|
break; |
|
case RPMCALLBACK_INST_PROGRESS: |
|
break; |
|
case RPMCALLBACK_INST_STOP: |
|
g_debug("inst_stop(\"%s\")", file); |
|
break; |
|
case RPMCALLBACK_INST_CLOSE_FILE: |
|
g_debug("inst_close_file(\"%s\")", file); |
|
rpmShowProgress(arg, what, amount, total, key, NULL); |
|
/* NOTE: we do this here 'cuz test transactions don't do start/stop */ |
|
installed++; |
|
percent = INSTALL_BASE_PERCENT + \ |
|
((INSTALL_PERCENT*installed) / installcount); |
|
if (percent > prevpercent) { |
|
set_plymouth_percent(percent); |
|
prevpercent = percent; |
|
} |
|
break; |
|
|
|
/* cleanup phase: (start, progress..., stop) for each cleanup */ |
|
/* NOTE: file is NULL */ |
|
case RPMCALLBACK_UNINST_START: |
|
nvr = headerGetAsString(hdr, RPMTAG_NVR); |
|
g_debug("uninst_start(\"%s\")", nvr); |
|
g_message("[%u/%u] (%u%%) cleaning %s...", |
|
erased+1, erasecount, percent, nvr); |
|
rfree(nvr); |
|
break; |
|
case RPMCALLBACK_UNINST_PROGRESS: |
|
break; |
|
case RPMCALLBACK_UNINST_STOP: |
|
nvr = headerGetAsString(hdr, RPMTAG_NVR); |
|
g_debug("uninst_stop(\"%s\")", nvr); |
|
erased++; |
|
percent = INSTALL_BASE_PERCENT + INSTALL_PERCENT + \ |
|
((ERASE_PERCENT*erased) / erasecount); |
|
if (percent > prevpercent) { |
|
set_plymouth_percent(percent); |
|
prevpercent = percent; |
|
} |
|
rfree(nvr); |
|
break; |
|
|
|
/* |
|
* SCRIPT CALLBACKS (rpm >= 4.10) - happen all throughout the transaction. |
|
* hdr and file/key are both usable. |
|
* amount is the script type (RPMTAG_PREIN, RPMTAG_POSTIN, ...) |
|
* total is the script exit value (see comments below) |
|
* Ordering is: START; STOP; ERROR if script retval != RPMRC_OK |
|
*/ |
|
case RPMCALLBACK_SCRIPT_START: |
|
/* no exit value here, obviously */ |
|
nvr = headerGetAsString(hdr, RPMTAG_NVR); |
|
g_debug("%s_start(\"%s\")", script_type(amount), nvr); |
|
/* NOTE: %posttrans usually takes a while - report progress! */ |
|
if (amount == RPMTAG_POSTTRANS) |
|
g_message("running %s script for %s", script_type(amount), nvr); |
|
break; |
|
case RPMCALLBACK_SCRIPT_STOP: |
|
/* RPMRC_OK: scriptlet succeeded |
|
* RPMRC_NOTFOUND: scriptlet failed non-fatally (warning) |
|
* other: scriptlet failed, preventing install/erase |
|
* (this only happens for PREIN/PREUN/PRETRANS) */ |
|
nvr = headerGetAsString(hdr, RPMTAG_NVR); |
|
g_debug("%s_stop(\"%s\")", script_type(amount), nvr); |
|
break; |
|
case RPMCALLBACK_SCRIPT_ERROR: |
|
/* RPMRC_OK: scriptlet failed non-fatally (warning) |
|
* other: scriptlet failed, preventing install/erase */ |
|
nvr = headerGetAsString(hdr, RPMTAG_NVR); |
|
g_debug("%s_error(\"%s\"): %lu", script_type(amount), nvr, total); |
|
g_warning("%s %s scriptlet failure in %s (exit code %lu)", |
|
total == RPMRC_OK ? "non-fatal" : "fatal", |
|
script_type(amount), nvr, total); |
|
/* TODO: show the script contents? */ |
|
break; |
|
|
|
/* these are probably fatal, and there's not much we can do about it.. |
|
* the RPM test transaction should catch nearly all of these well before |
|
* we end up here, though. */ |
|
case RPMCALLBACK_UNPACK_ERROR: |
|
case RPMCALLBACK_CPIO_ERROR: |
|
g_warning("error unpacking %s! file may be corrupt!", file); |
|
break; |
|
|
|
default: |
|
if (!(what & cb_seen)) { |
|
g_debug("unhandled callback number %u", what); |
|
cb_seen |= what; |
|
} |
|
break; |
|
} |
|
return retval; |
|
} |
|
|
|
rpmps run_transaction(rpmts ts, gint tsflags) { |
|
/* probFilter seems odd, but that's what anaconda used to do... */ |
|
gint probFilter = ~RPMPROB_FILTER_DISKSPACE; |
|
gint rc; |
|
rpmps probs = NULL; |
|
|
|
/* send scriptlet stderr somewhere useful. */ |
|
rpmtsSetScriptFd(ts, fdDup(STDOUT_FILENO)); |
|
/* rpmSetVerbosity(RPMLOG_INFO) would give us script stdout, if we cared */ |
|
|
|
rpmtsSetNotifyCallback(ts, rpm_trans_callback, NULL); |
|
rpmtsSetFlags(ts, rpmtsFlags(ts)|tsflags); |
|
rc = rpmtsRun(ts, NULL, (rpmprobFilterFlags)probFilter); |
|
g_debug("transaction finished"); |
|
if (rc > 0) |
|
probs = rpmtsProblems(ts); |
|
if (rc < 0) |
|
g_message("Upgrade finished with non-fatal errors."); |
|
/* AFAICT probs would be empty here, so that's all we can say.. */ |
|
return probs; |
|
} |
|
|
|
/******************* |
|
* logging handler * |
|
*******************/ |
|
|
|
void log_handler(const gchar *log_domain, GLogLevelFlags log_level, |
|
const gchar *message, gpointer user_data) |
|
{ |
|
switch (log_level & G_LOG_LEVEL_MASK) { |
|
/* NOTE: ERROR is still handled by the default handler. */ |
|
case G_LOG_LEVEL_CRITICAL: |
|
g_printf("ERROR: %s\n", message); |
|
exit(1); |
|
break; |
|
case G_LOG_LEVEL_WARNING: |
|
/* TODO: once the journal problems are fixed, send warnings and |
|
* scriptlet stderr to stderr. |
|
* see https://bugzilla.redhat.com/show_bug.cgi?id=869061 */ |
|
g_printf("Warning: %s\n", message); |
|
break; |
|
case G_LOG_LEVEL_MESSAGE: |
|
g_printf("%s\n", message); |
|
if (plymouth_verbose) |
|
set_plymouth_message(message); |
|
break; |
|
case G_LOG_LEVEL_INFO: |
|
if (debug) |
|
g_printf("%s\n", message); |
|
break; |
|
case G_LOG_LEVEL_DEBUG: |
|
if (debug) |
|
g_printf("DEBUG: %s\n", message); |
|
break; |
|
} |
|
fflush(stdout); |
|
} |
|
|
|
/******************** |
|
* helper functions * |
|
********************/ |
|
|
|
/* read a list of filenames out of the given file */ |
|
gchar **read_filelist(gchar *path, gchar *name) { |
|
GError *error = NULL; |
|
gchar *filelist_path = NULL; |
|
gchar *filelist_data = NULL; |
|
gchar **files = NULL; |
|
|
|
filelist_path = g_build_filename(path, name, NULL); |
|
if (!g_file_get_contents(filelist_path, &filelist_data, NULL, &error)) |
|
g_critical(error->message); |
|
|
|
/* parse the data into a list of files */ |
|
g_strchomp(filelist_data); |
|
files = g_strsplit(filelist_data, "\n", -1); |
|
|
|
g_free(filelist_path); |
|
g_free(filelist_data); |
|
|
|
return files; |
|
} |
|
|
|
/**************** |
|
* main program * |
|
****************/ |
|
|
|
/* Total runtime for my test system (F17->F18) is ~70m. */ |
|
int main(int argc, char* argv[]) { |
|
gchar *symlink = NULL; |
|
gchar *link_target = NULL; |
|
gchar *origroot = NULL; |
|
gchar **files = NULL; |
|
GError *error = NULL; |
|
rpmts ts = NULL; |
|
rpmps probs = NULL; |
|
gint tsflags = RPMTRANS_FLAG_NONE; |
|
gint retval = EXIT_FAILURE; |
|
GOptionContext *context; |
|
|
|
/* setup */ |
|
setlocale(LC_ALL, ""); |
|
bindtextdomain(GETTEXT_PACKAGE, "/usr/share/locale"); |
|
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); |
|
textdomain(GETTEXT_PACKAGE); |
|
g_log_set_handler(NULL, G_LOG_LEVEL_MASK, log_handler, NULL); |
|
|
|
|
|
/* parse commandline */ |
|
context = g_option_context_new("upgrade a RHEL system"); |
|
g_option_context_add_main_entries(context, options, GETTEXT_PACKAGE); |
|
if (!g_option_context_parse(context, &argc, &argv, &error)) |
|
g_critical("option_parsing failed: %s", error->message); |
|
|
|
if (getuid() != 0 || geteuid() != 0) |
|
g_critical("This program must be run as root."); |
|
|
|
if (g_getenv("UPGRADE_TEST") != NULL) |
|
testing = TRUE; |
|
|
|
if (plymouth) { |
|
if (!plymouth_setup()) { |
|
g_warning("Disabling plymouth output"); |
|
plymouth = FALSE; |
|
} |
|
} |
|
|
|
if (!plymouth) |
|
plymouth_verbose = FALSE; |
|
|
|
if (!g_path_is_absolute(root)) { |
|
origroot = root; |
|
root = realpath(origroot, NULL); |
|
g_debug("root is \"%s\"", root); |
|
} |
|
if ((root == NULL) || (!g_file_test(root, G_FILE_TEST_IS_DIR))) |
|
g_critical("--root: \"%s\" is not a directory", origroot); |
|
|
|
|
|
/* read the magic symlink */ |
|
symlink = g_build_filename(root, UPGRADE_SYMLINK, NULL); |
|
|
|
link_target = g_file_read_link(symlink, &error); |
|
if (link_target == NULL) |
|
g_critical(error->message); |
|
|
|
packagedir = g_build_filename(root, link_target, NULL); |
|
g_debug("%s -> %s", symlink, packagedir); |
|
|
|
g_free(symlink); |
|
g_free(link_target); |
|
|
|
|
|
/* read filelist from packagedir */ |
|
files = read_filelist(packagedir, UPGRADE_FILELIST); |
|
|
|
/* set up RPM transaction - this takes ~90s (~2% total duration) */ |
|
g_message("preparing for upgrade..."); |
|
ts = setup_transaction(files); |
|
if (ts == NULL) |
|
goto out; |
|
|
|
/* don't actually run the transaction if we're just testing */ |
|
if (testing) |
|
tsflags |= RPMTRANS_FLAG_TEST; |
|
|
|
|
|
/* LET'S ROCK. 98% of the program runtime is here. */ |
|
g_message("starting upgrade..."); |
|
probs = run_transaction(ts, tsflags); |
|
|
|
|
|
/* check for failures */ |
|
if (probs != NULL) { |
|
g_message("ERROR: upgrade failed due to the following problems:"); |
|
rpmpsPrint(stdout, probs); |
|
} else { |
|
g_message("upgrade finished."); |
|
retval = EXIT_SUCCESS; |
|
} |
|
|
|
if (plymouth) |
|
plymouth_finish(); |
|
|
|
/* cleanup */ |
|
g_debug("cleaning up..."); |
|
rpmpsFree(probs); |
|
rpmtsFree(ts); |
|
rpmFreeMacros(NULL); |
|
rpmFreeRpmrc(); |
|
|
|
out: |
|
if (packagedir != NULL) |
|
g_free(packagedir); |
|
if (files != NULL) |
|
g_strfreev(files); |
|
return retval; |
|
}
|
|
|