Skip to content

Commit

Permalink
system-upgrade: implement log subcommand
Browse files Browse the repository at this point in the history
This commit brings in systemd-devel as a build dependency for
dnf5-plugins so system-upgrade can log directly to journald and so the
`log` subcommand can search the journal for upgrade logs. Perhaps in a
subsequent patch, this dependency can be put behind a switch.
  • Loading branch information
evan-goode committed Feb 2, 2024
1 parent 1013f82 commit 857c9d9
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 17 deletions.
3 changes: 2 additions & 1 deletion dnf5-plugins/system_upgrade_plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ add_library(system_upgrade_cmd_plugin MODULE ${SYSTEM_UPGRADE_SOURCES})
set_target_properties(system_upgrade_cmd_plugin PROPERTIES PREFIX "")

pkg_check_modules(SDBUS_CPP REQUIRED sdbus-c++)
pkg_check_modules(LIBSYSTEMD REQUIRED libsystemd)

target_link_libraries(system_upgrade_cmd_plugin PRIVATE dnf5 libdnf5 libdnf5-cli ${SDBUS_CPP_LIBRARIES})
target_link_libraries(system_upgrade_cmd_plugin PRIVATE dnf5 libdnf5 libdnf5-cli ${SDBUS_CPP_LIBRARIES} ${LIBSYSTEMD_LIBRARIES})

install(FILES "dnf5-system-upgrade.service" DESTINATION ${SYSTEMD_UNIT_DIR})
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink
Expand Down
166 changes: 156 additions & 10 deletions dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,26 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.

#include "system_upgrade.hpp"

#include "utils/string.hpp"

#include <libdnf5-cli/utils/userconfirm.hpp>
#include <libdnf5/base/goal.hpp>
#include <libdnf5/conf/const.hpp>
#include <libdnf5/conf/option_path.hpp>
#include <libdnf5/utils/bgettext/bgettext-mark-domain.h>
#include <libdnf5/utils/fs/file.hpp>
#include <sys/wait.h>
#include <systemd/sd-journal.h>

#include <cstdlib>
#include <exception>
#include <iostream>
#include <string>

using namespace libdnf5::cli;

const std::string & ID_TO_IDENTIFY_BOOTS = OFFLINE_STARTED_ID;

OfflineTransactionState::OfflineTransactionState(std::filesystem::path path) : path(std::move(path)) {
read();
}
Expand Down Expand Up @@ -123,6 +130,7 @@ class PlymouthOutput {
/// Extend RpmTransCB to also display messages with Plymouth
class PlymouthTransCB : public RpmTransCB {
public:
PlymouthTransCB(PlymouthOutput plymouth) : plymouth(std::move(plymouth)) {}
void elem_progress(
[[maybe_unused]] const libdnf5::rpm::TransactionItem & item,
[[maybe_unused]] uint64_t amount,
Expand Down Expand Up @@ -189,6 +197,7 @@ void SystemUpgradeCommand::register_subcommands() {
register_subcommand(std::make_unique<SystemUpgradeDownloadCommand>(get_context()));
register_subcommand(std::make_unique<SystemUpgradeRebootCommand>(get_context()));
register_subcommand(std::make_unique<SystemUpgradeUpgradeCommand>(get_context()));
register_subcommand(std::make_unique<SystemUpgradeLogCommand>(get_context()));
}

SystemUpgradeSubcommand::SystemUpgradeSubcommand(Context & context, const std::string & name)
Expand All @@ -215,6 +224,26 @@ void SystemUpgradeSubcommand::configure() {
auto & ctx = get_context();
const std::filesystem::path installroot{ctx.base.get_config().get_installroot_option().get_value()};
magic_symlink = installroot / "system-update";

system_releasever = *libdnf5::Vars::detect_release(ctx.base.get_weak_ptr(), installroot);
target_releasever = ctx.base.get_vars()->get_value("releasever");
}

void SystemUpgradeSubcommand::log_status(const std::string & message, const std::string & message_id) const {
const auto & version = get_application_version();
const std::string & version_string = fmt::format("{}.{}.{}", version.major, version.minor, version.micro);
sd_journal_send(
"MESSAGE=%s",
message.c_str(),
"MESSAGE_ID=%s",
message_id.c_str(),
"SYSTEM_RELEASEVER=%s",
get_system_releasever().c_str(),
"TARGET_RELEASEVER=%s",
get_target_releasever().c_str(),
"DNF_VERSION=%s",
version_string.c_str(),
NULL);
}

void SystemUpgradeDownloadCommand::set_argument_parser() {
Expand Down Expand Up @@ -242,14 +271,9 @@ void SystemUpgradeDownloadCommand::configure() {
SystemUpgradeSubcommand::configure();

auto & ctx = get_context();
auto & conf = ctx.base.get_config();

// Check --releasever
const auto & installroot = conf.get_installroot_option().get_value();
system_releasever = *libdnf5::Vars::detect_release(ctx.base.get_weak_ptr(), installroot);
target_releasever = ctx.base.get_vars()->get_value("releasever");

if (target_releasever == system_releasever) {
if (get_target_releasever() == get_system_releasever()) {
throw libdnf5::cli::CommandExitError(1, M_("Need a --releasever greater than the current system version."));
}

Expand Down Expand Up @@ -281,7 +305,7 @@ void SystemUpgradeDownloadCommand::run() {

ctx.download_and_run(transaction);

if (!transaction.get_transaction_packages_count()) {
if (transaction.get_transaction_packages_count() == 0) {
throw libdnf5::cli::CommandExitError(
1, M_("The system-upgrade transaction is empty; your system is already up-to-date."));
}
Expand All @@ -295,8 +319,8 @@ void SystemUpgradeDownloadCommand::run() {

auto state = get_state();
state.get_data().cachedir = get_cachedir()->get_value();
state.get_data().system_releasever = system_releasever;
state.get_data().target_releasever = target_releasever;
state.get_data().system_releasever = get_system_releasever();
state.get_data().target_releasever = get_target_releasever();
state.get_data().status = STATUS_DOWNLOAD_COMPLETE;
libdnf5::repo::RepoQuery enabled_repos(ctx.base);
enabled_repos.filter_enabled(true);
Expand Down Expand Up @@ -451,6 +475,8 @@ void SystemUpgradeUpgradeCommand::configure() {
void SystemUpgradeUpgradeCommand::run() {
auto & ctx = get_context();

log_status("Starting offline transaction. This will take a while.", OFFLINE_STARTED_ID);

if (!std::filesystem::is_symlink(get_magic_symlink())) {
throw libdnf5::cli::CommandExitError(0, M_("Trigger file does not exist. Exiting."));
}
Expand Down Expand Up @@ -483,7 +509,8 @@ void SystemUpgradeUpgradeCommand::run() {
throw libdnf5::cli::GoalResolveError(transaction);
}

auto callbacks = std::make_unique<PlymouthTransCB>();
PlymouthOutput plymouth;
auto callbacks = std::make_unique<PlymouthTransCB>(plymouth);
/* callbacks->get_multi_progress_bar()->set_total_num_of_bars(num_of_actions); */
transaction.set_callbacks(std::move(callbacks));

Expand All @@ -505,6 +532,15 @@ void SystemUpgradeUpgradeCommand::run() {
std::cerr << entry << std::endl;
}

std::string upgrade_complete_message;
if (state.get_data().poweroff_after) {
upgrade_complete_message = "Upgrade complete! Cleaning up and powering off...";
} else {
upgrade_complete_message = "Upgrade complete! Cleaning up and rebooting...";
}
plymouth.message(upgrade_complete_message);
log_status(upgrade_complete_message, OFFLINE_STARTED_ID);

// If the upgrade succeeded, remove downloaded data
clean(ctx, get_datadir());

Expand All @@ -520,4 +556,114 @@ void SystemUpgradeCleanCommand::run() {
clean(ctx, get_datadir());
}

struct BootEntry {
std::string boot_id;
std::string timestamp;
std::string system_releasever;
std::string target_releasever;
};

std::string get_journal_field(sd_journal * journal, const std::string & field) {
const char * data = nullptr;
size_t length = 0;
auto rc = sd_journal_get_data(journal, field.c_str(), reinterpret_cast<const void **>(&data), &length);
if (rc < 0 || data == nullptr) {
return "";
}
const auto prefix_length = field.length() + 1;
return std::string{data + prefix_length, length - prefix_length};
}

std::vector<BootEntry> find_boots(const std::string & message_id) {
std::vector<BootEntry> boots{};

sd_journal * journal = nullptr;
auto rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY);
if (rc < 0) {
throw libdnf5::RuntimeError(M_("Error reading journal: {}"), std::string{std::strerror(-rc)});
}

const auto & uid_filter_string = fmt::format("MESSAGE_ID={}", message_id);
rc = sd_journal_add_match(journal, uid_filter_string.c_str(), 0);
if (rc < 0) {
throw libdnf5::RuntimeError(M_("Add journal match failed: {}"), std::string{std::strerror(-rc)});
}
SD_JOURNAL_FOREACH(journal) {
uint64_t usec = 0;
rc = sd_journal_get_realtime_usec(journal, &usec);
auto sec = usec / (1000 * 1000);
boots.emplace_back(BootEntry{
get_journal_field(journal, "_BOOT_ID"),
libdnf5::utils::string::format_epoch(sec),
get_journal_field(journal, "SYSTEM_RELEASEVER"),
get_journal_field(journal, "TARGET_RELEASEVER"),
});
}

return boots;
}

void list_logs() {
const auto & boot_entries = find_boots(ID_TO_IDENTIFY_BOOTS);

std::cout << "The following boots appear to contain upgrade logs:" << std::endl;
for (size_t index = 0; index < boot_entries.size(); index += 1) {
const auto & entry = boot_entries[index];
std::cout << fmt::format(
"{} / {}: {} {}→{}",
index + 1,
entry.boot_id,
entry.timestamp,
entry.system_releasever,
entry.target_releasever)
<< std::endl;
}
if (boot_entries.empty()) {
std::cout << "No logs were found." << std::endl;
}
}

void show_log(size_t boot_index) {
const auto & boot_entries = find_boots(ID_TO_IDENTIFY_BOOTS);
if (boot_index >= boot_entries.size()) {
throw libdnf5::cli::CommandExitError(1, M_("Cannot find logs with this index."));
}

const auto & boot_id = boot_entries[boot_index].boot_id;
const auto rc = call(PATH_TO_JOURNALCTL, {"--boot", boot_id});

if (rc != 0) {
throw libdnf5::cli::CommandExitError(1, M_("Unable to match systemd journal entry."));
}
}

void SystemUpgradeLogCommand::set_argument_parser() {
SystemUpgradeSubcommand::set_argument_parser();

auto & ctx = get_context();
auto & parser = ctx.get_argument_parser();
auto & cmd = *get_argument_parser_command();

cmd.set_description("Show logs from past upgrades");

number = dynamic_cast<libdnf5::OptionString *>(parser.add_init_value(std::make_unique<libdnf5::OptionString>("")));

auto * number_arg = parser.add_new_named_arg("number");
number_arg->set_long_name("number");
number_arg->set_has_value(true);

number_arg->set_description("Which log to show. Run without any arguments to get a list of available logs.");
number_arg->link_value(number);
cmd.register_named_arg(number_arg);
}

void SystemUpgradeLogCommand::run() {
if (number->get_value().empty()) {
list_logs();
} else {
std::string number_string{number->get_value()};
show_log(std::stoul(number_string) - 1);
}
}

} // namespace dnf5
21 changes: 15 additions & 6 deletions dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include <toml.hpp>

const std::filesystem::path PATH_TO_PLYMOUTH{"/usr/bin/plymouth"};
const std::filesystem::path PATH_TO_JOURNALCTL{"/usr/bin/journalctl"};
const std::string OFFLINE_STARTED_ID{"3e0a5636d16b4ca4bbe5321d06c6aa62"};
const std::string OFFLINE_FINISHED_ID{"8cec00a1566f4d3594f116450395f06c"};

const std::string STATUS_DOWNLOAD_INCOMPLETE{"download-incomplete"};
const std::string STATUS_DOWNLOAD_COMPLETE{"download-complete"};
Expand Down Expand Up @@ -93,16 +96,21 @@ class SystemUpgradeSubcommand : public Command {
void configure() override;

protected:
libdnf5::OptionPath * get_cachedir() { return cachedir; };
std::filesystem::path get_datadir() { return datadir; };
std::filesystem::path get_magic_symlink() { return magic_symlink; };
OfflineTransactionState get_state() { return state; };
libdnf5::OptionPath * get_cachedir() const { return cachedir; };
std::filesystem::path get_datadir() const { return datadir; };
std::filesystem::path get_magic_symlink() const { return magic_symlink; };
OfflineTransactionState get_state() const { return state; };
std::string get_system_releasever() const { return system_releasever; };
std::string get_target_releasever() const { return target_releasever; };
void log_status(const std::string & message, const std::string & message_id) const;

private:
libdnf5::OptionPath * cachedir{nullptr};
std::filesystem::path datadir{DEFAULT_DATADIR};
std::filesystem::path magic_symlink;
OfflineTransactionState state;
std::string target_releasever;
std::string system_releasever;
};

class SystemUpgradeDownloadCommand : public SystemUpgradeSubcommand {
Expand All @@ -115,8 +123,6 @@ class SystemUpgradeDownloadCommand : public SystemUpgradeSubcommand {
private:
libdnf5::OptionBool * no_downgrade{nullptr};
libdnf5::OptionBool * distro_sync{nullptr};
std::string target_releasever;
std::string system_releasever;
int state_version;
};

Expand Down Expand Up @@ -150,6 +156,9 @@ class SystemUpgradeLogCommand : public SystemUpgradeSubcommand {
explicit SystemUpgradeLogCommand(Context & context) : SystemUpgradeSubcommand(context, "log") {}
void set_argument_parser() override;
void run() override;

private:
libdnf5::OptionString * number{nullptr};
};

} // namespace dnf5
Expand Down
1 change: 1 addition & 0 deletions dnf5.spec
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ BuildRequires: pkgconfig(smartcols)

%if %{with dnf5_plugins}
BuildRequires: libcurl-devel >= 7.62.0
BuildRequires: systemd-devel
%endif

%if %{with dnf5daemon_server}
Expand Down

0 comments on commit 857c9d9

Please sign in to comment.