4242#include < libdnf5/utils/fs/file.hpp>
4343#include < libdnf5/utils/patterns.hpp>
4444#include < rpm/rpmtypes.h>
45+ #include < signal.h>
4546
4647#include < algorithm>
4748#include < cctype>
@@ -58,6 +59,48 @@ namespace dnf5 {
5859
5960namespace {
6061
62+ // Helper function to format active transaction information for error messages
63+ std::string format_active_transaction_info (const libdnf5::base::ActiveTransactionInfo & info) {
64+ std::string result;
65+
66+ if (!info.get_description ().empty ()) {
67+ result += libdnf5::utils::sformat (_ (" Command: {}" ), info.get_description ());
68+ }
69+
70+ if (info.get_pid () > 0 ) {
71+ if (!result.empty ()) {
72+ result += " \n " ;
73+ }
74+ if (kill (info.get_pid (), 0 ) == 0 ) {
75+ result += libdnf5::utils::sformat (_ (" Process ID: {} (still running)" ), info.get_pid ());
76+ } else {
77+ result += libdnf5::utils::sformat (_ (" Process ID: {}" ), info.get_pid ());
78+ }
79+ }
80+
81+ if (info.get_start_time () > 0 ) {
82+ if (!result.empty ()) {
83+ result += " \n " ;
84+ }
85+ auto start_time = static_cast <time_t >(info.get_start_time ());
86+ auto time_str = std::string (std::ctime (&start_time));
87+ // Remove trailing newline from ctime
88+ if (!time_str.empty () && time_str.back () == ' \n ' ) {
89+ time_str.pop_back ();
90+ }
91+ result += libdnf5::utils::sformat (_ (" Started: {}" ), time_str);
92+ }
93+
94+ if (!info.get_comment ().empty ()) {
95+ if (!result.empty ()) {
96+ result += " \n " ;
97+ }
98+ result += libdnf5::utils::sformat (_ (" Comment: {}" ), info.get_comment ());
99+ }
100+
101+ return result;
102+ }
103+
61104// The `KeyImportRepoCB` class implements callback only for importing repository key.
62105class KeyImportRepoCB : public libdnf5 ::repo::RepoCallbacks2_1 {
63106public:
@@ -377,6 +420,24 @@ void Context::Impl::store_offline(libdnf5::base::Transaction & transaction) {
377420 if (result != libdnf5::base::Transaction::TransactionRunResult::SUCCESS) {
378421 print_error (libdnf5::utils::sformat (
379422 _ (" Transaction failed: {}" ), libdnf5::base::Transaction::transaction_result_to_string (result)));
423+
424+ // For transaction lock errors, provide detailed information about the running transaction
425+ if (result == libdnf5::base::Transaction::TransactionRunResult::ERROR_LOCK) {
426+ auto active_info = base.get_active_transaction_info ();
427+ if (active_info) {
428+ print_error (_ (" Details about the currently running transaction:" ));
429+ auto info_str = format_active_transaction_info (*active_info);
430+ if (!info_str.empty ()) {
431+ // Print each line of the formatted info with proper indentation
432+ std::istringstream ss (info_str);
433+ std::string line;
434+ while (std::getline (ss, line)) {
435+ print_error (libdnf5::utils::sformat (_ (" {}" ), line));
436+ }
437+ }
438+ }
439+ }
440+
380441 for (auto const & entry : transaction.get_gpg_signature_problems ()) {
381442 print_error (entry);
382443 }
@@ -530,6 +591,19 @@ void Context::Impl::download_and_run(libdnf5::base::Transaction & transaction) {
530591 if (result != libdnf5::base::Transaction::TransactionRunResult::SUCCESS) {
531592 print_error (libdnf5::utils::sformat (
532593 _ (" Transaction failed: {}" ), libdnf5::base::Transaction::transaction_result_to_string (result)));
594+
595+ // For transaction lock errors, provide detailed information about the running transaction
596+ if (result == libdnf5::base::Transaction::TransactionRunResult::ERROR_LOCK) {
597+ auto active_info = base.get_active_transaction_info ();
598+ if (active_info) {
599+ auto info_str = format_active_transaction_info (*active_info);
600+ if (!info_str.empty ()) {
601+ print_error (_ (" Details about the currently running transaction:" ));
602+ print_error (info_str);
603+ }
604+ }
605+ }
606+
533607 for (auto const & entry : transaction.get_gpg_signature_problems ()) {
534608 print_error (entry);
535609 }
0 commit comments