Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions dnf5/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include <libdnf5/utils/fs/file.hpp>
#include <libdnf5/utils/patterns.hpp>
#include <rpm/rpmtypes.h>
#include <signal.h>

#include <algorithm>
#include <cctype>
Expand All @@ -60,6 +61,47 @@ namespace {

constexpr const char * STORED_REPO_PREFIX = "@stored_transaction";

// Helper function to format active transaction information for error messages
std::string format_active_transaction_info(const libdnf5::base::ActiveTransactionInfo & info) {
std::string result;

if (!info.get_description().empty()) {
result += libdnf5::utils::sformat(_("Command: {}"), info.get_description());
}

if (info.get_pid() > 0) {
if (!result.empty()) {
result += "\n";
}
if (kill(info.get_pid(), 0) == 0) {
result += libdnf5::utils::sformat(_("Process ID: {} (still running)"), info.get_pid());
} else {
result += libdnf5::utils::sformat(_("Process ID: {}"), info.get_pid());
}
}

if (info.get_start_time() > 0) {
if (!result.empty()) {
result += "\n";
}
result +=
libdnf5::utils::sformat(_("Started: {}"), libdnf5::utils::string::format_epoch(info.get_start_time()));
}

if (!info.get_comment().empty()) {
if (!result.empty()) {
result += "\n";
}
result += libdnf5::utils::sformat(_("Comment: {}"), info.get_comment());
}

if (!result.empty()) {
result = _("Details about the currently running transaction:\n") + result;
}

return result;
}

// The `KeyImportRepoCB` class implements callback only for importing repository key.
class KeyImportRepoCB : public libdnf5::repo::RepoCallbacks2_1 {
public:
Expand Down Expand Up @@ -379,6 +421,18 @@ void Context::Impl::store_offline(libdnf5::base::Transaction & transaction) {
if (result != libdnf5::base::Transaction::TransactionRunResult::SUCCESS) {
print_error(libdnf5::utils::sformat(
_("Transaction failed: {}"), libdnf5::base::Transaction::transaction_result_to_string(result)));

// For transaction lock errors, provide detailed information about the running transaction
if (result == libdnf5::base::Transaction::TransactionRunResult::ERROR_LOCK) {
auto * active_info = transaction.get_concurrent_transaction();
if (active_info) {
auto info_str = format_active_transaction_info(*active_info);
if (!info_str.empty()) {
print_error(info_str);
}
}
}

for (auto const & entry : transaction.get_gpg_signature_problems()) {
print_error(entry);
}
Expand Down Expand Up @@ -532,6 +586,18 @@ void Context::Impl::download_and_run(libdnf5::base::Transaction & transaction) {
if (result != libdnf5::base::Transaction::TransactionRunResult::SUCCESS) {
print_error(libdnf5::utils::sformat(
_("Transaction failed: {}"), libdnf5::base::Transaction::transaction_result_to_string(result)));

// For transaction lock errors, provide detailed information about the running transaction
if (result == libdnf5::base::Transaction::TransactionRunResult::ERROR_LOCK) {
auto * active_info = transaction.get_concurrent_transaction();
if (active_info) {
auto info_str = format_active_transaction_info(*active_info);
if (!info_str.empty()) {
print_error(info_str);
}
}
}

for (auto const & entry : transaction.get_gpg_signature_problems()) {
print_error(entry);
}
Expand Down
76 changes: 76 additions & 0 deletions include/libdnf5/base/active_transaction_info.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright Contributors to the DNF5 project.
// SPDX-License-Identifier: LGPL-2.1-or-later

#ifndef LIBDNF5_BASE_ACTIVE_TRANSACTION_INFO_HPP
#define LIBDNF5_BASE_ACTIVE_TRANSACTION_INFO_HPP

#include "libdnf5/common/exception.hpp"
#include "libdnf5/defs.h"

#include <unistd.h>

#include <ctime>
#include <memory>
#include <string>

namespace libdnf5::base {

class LIBDNF_API ActiveTransactionInfoParseError : public libdnf5::Error {
public:
using libdnf5::Error::Error;
const char * get_domain_name() const noexcept override { return "libdnf5::base"; }
const char * get_name() const noexcept override { return "ActiveTransactionInfoParseError"; }
};

class LIBDNF_API ActiveTransactionInfo {
public:
ActiveTransactionInfo();
ActiveTransactionInfo(const ActiveTransactionInfo &);
ActiveTransactionInfo(ActiveTransactionInfo &&) noexcept;
ActiveTransactionInfo & operator=(const ActiveTransactionInfo &);
ActiveTransactionInfo & operator=(ActiveTransactionInfo &&) noexcept;
~ActiveTransactionInfo();

// Getters
/// @brief Get the transaction description
/// @return Description string
const std::string & get_description() const noexcept;

/// @brief Get the transaction comment
/// @return Comment string
const std::string & get_comment() const noexcept;

/// @brief Get the process ID of the transaction
/// @return Process ID, or -1 if not available
pid_t get_pid() const noexcept;

/// @brief Get the transaction start time as Unix timestamp
/// @return Unix timestamp (seconds since epoch), or 0 if not available
time_t get_start_time() const noexcept;

// Setters
void set_description(const std::string & description);
void set_comment(const std::string & comment);
void set_pid(pid_t pid);
void set_start_time(time_t start_time);

// TOML conversion methods
/// @brief Convert ActiveTransactionInfo to TOML string format
/// @return TOML formatted string representation
/// @throws std::runtime_error if TOML formatting fails
std::string to_toml() const;

/// @brief Create ActiveTransactionInfo from TOML string
/// @param toml_string The TOML string to parse
/// @return ActiveTransactionInfo object parsed from TOML
/// @throws ActiveTransactionInfoParseError if TOML parsing fails
static ActiveTransactionInfo from_toml(const std::string & toml_string);

private:
class LIBDNF_LOCAL Impl;
std::unique_ptr<Impl> p_impl;
};

} // namespace libdnf5::base

#endif // LIBDNF5_BASE_ACTIVE_TRANSACTION_INFO_HPP
4 changes: 4 additions & 0 deletions include/libdnf5/base/transaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include "transaction_errors.hpp"

#include "libdnf5/base/active_transaction_info.hpp"
#include "libdnf5/base/base_weak.hpp"
#include "libdnf5/base/goal_elements.hpp"
#include "libdnf5/base/log_event.hpp"
Expand Down Expand Up @@ -197,6 +198,9 @@ class LIBDNF_API Transaction {
/// Retrieve captured RPM log messages
std::vector<std::string> get_rpm_messages();

/// Retrieve metadat of concurrently running transaction
const ActiveTransactionInfo * get_concurrent_transaction() const noexcept;

private:
friend class TransactionEnvironment;
friend class TransactionGroup;
Expand Down
10 changes: 10 additions & 0 deletions include/libdnf5/utils/locker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ class LIBDNF_API Locker {
/// @throws libdnf5::SystemError if an unexpected error occurs when checking the lock state, like insufficient privileges
bool write_lock();

/// @brief Write content to the lock file
/// @param content The content to write to the lock file
/// @throws libdnf5::SystemError if the lock file is not opened or writing fails
void write_content(const std::string & content);

/// @brief Read content from the lock file, requires acquiring a lock first.
/// @return The content of the lock file
/// @throws libdnf5::SystemError if reading fails (or if the file doesn't exist)
std::string read_content();

/// @brief Unlock the existing lock and remove the underlying lock file
/// @throws libdnf5::SystemError if an unexpected error occurs when unlocking
void unlock();
Expand Down
101 changes: 101 additions & 0 deletions libdnf5/base/active_transaction_info.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright Contributors to the DNF5 project.
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "libdnf5/base/active_transaction_info.hpp"

#include "libdnf5/utils/bgettext/bgettext-mark-domain.h"

#include <toml.hpp>

#include <ctime>

namespace libdnf5::base {

class ActiveTransactionInfo::Impl {
public:
pid_t pid = -1;
std::string description;
std::string comment;
time_t start_time = 0;
};

ActiveTransactionInfo::ActiveTransactionInfo() : p_impl(new Impl()) {}

ActiveTransactionInfo::ActiveTransactionInfo(const ActiveTransactionInfo & src) : p_impl(new Impl(*src.p_impl)) {}
ActiveTransactionInfo & ActiveTransactionInfo::operator=(const ActiveTransactionInfo & src) {
if (this != &src) {
if (p_impl) {
*p_impl = *src.p_impl;
} else {
p_impl = std::make_unique<Impl>(*src.p_impl);
}
}

return *this;
}

ActiveTransactionInfo::ActiveTransactionInfo(ActiveTransactionInfo && src) noexcept = default;
ActiveTransactionInfo & ActiveTransactionInfo::operator=(ActiveTransactionInfo && src) noexcept = default;

ActiveTransactionInfo::~ActiveTransactionInfo() = default;

const std::string & ActiveTransactionInfo::get_description() const noexcept {
return p_impl->description;
}

const std::string & ActiveTransactionInfo::get_comment() const noexcept {
return p_impl->comment;
}

pid_t ActiveTransactionInfo::get_pid() const noexcept {
return p_impl->pid;
}

time_t ActiveTransactionInfo::get_start_time() const noexcept {
return p_impl->start_time;
}

void ActiveTransactionInfo::set_description(const std::string & description) {
p_impl->description = description;
}

void ActiveTransactionInfo::set_comment(const std::string & comment) {
p_impl->comment = comment;
}

void ActiveTransactionInfo::set_pid(pid_t pid) {
p_impl->pid = pid;
}

void ActiveTransactionInfo::set_start_time(time_t start_time) {
p_impl->start_time = start_time;
}

std::string ActiveTransactionInfo::to_toml() const {
toml::value res;
res["description"] = get_description();
res["comment"] = get_comment();
res["pid"] = get_pid();
res["start_time"] = get_start_time();
return toml::format(res);
}

ActiveTransactionInfo ActiveTransactionInfo::from_toml(const std::string & toml_string) {
ActiveTransactionInfo info;

try {
auto data = toml::parse_str(toml_string);
info.set_description(toml::find_or<std::string>(data, "description", ""));
info.set_comment(toml::find_or<std::string>(data, "comment", ""));
info.set_pid(toml::find_or<pid_t>(data, "pid", -1));
info.set_start_time(toml::find_or<time_t>(data, "start_time", 0));
} catch (const toml::syntax_error & ex) {
throw ActiveTransactionInfoParseError(M_("Error parsing transaction info: {}"), std::string(ex.what()));
} catch (const toml::type_error & ex) {
throw ActiveTransactionInfoParseError(M_("Error parsing transaction info: {}"), std::string(ex.what()));
}

return info;
}

} // namespace libdnf5::base
Loading