dnfdaemon: Make it possible to set 'interactive' option for Offline::cancel(), Offline::clean(), Offline::set_finish_action()

The operation can require root privileges, for which it can ask the user
through polkit. It's okay when the operation is interactive, aka issued
by a user, but when the operation is ran by a background service, then
the password prompt coming out of blue is wrong. This change allows to
tell the daemon server whether it can ask for the root password or not.
This commit is contained in:
Milan Crha 2025-07-02 18:24:46 +02:00
parent 0f1b5f2dc8
commit 2ae58eed2e
3 changed files with 186 additions and 11 deletions

View File

@ -37,6 +37,25 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
<arg name="transaction_status" type="a{sv}" direction="out" />
</method>
<!--
cancel_with_options:
@options: an array of key/value pairs to modify the call behavior
@success: boolean, returns `false` if there was an error during the transaction cancellation, or if the offline transaction was initiated by another tool than dnf5. Returns `true` if the offline transaction was successfully cancelled or if no offline transaction was configured.
@error_msg: string, contains error encountered while cancelling the transaction
Cancel the dnf5 offline transaction configured for the next reboot. Offline updates scheduled by another tool are not cancelled.
Following @options are supported:
- interactive: boolean, default true
Set to "true", when the operation is done by a user, thus user interaction like password prompts can be done.
Unknown options are ignored.
-->
<method name="cancel_with_options">
<arg name="options" type="a{sv}" direction="in" />
<arg name="success" type="b" direction="out" />
<arg name="error_msg" type="s" direction="out" />
</method>
<!--
cancel:
@ -44,20 +63,67 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
@error_msg: string, contains error encountered while cancelling the transaction
Cancel the dnf5 offline transaction configured for the next reboot. Offline updates scheduled by another tool are not cancelled.
This is equivalent to call "cancel_with_options()" with empty options.
-->
<method name="cancel">
<arg name="success" type="b" direction="out" />
<arg name="error_msg" type="s" direction="out" />
</method>
<!--
clean_with_options:
@options: an array of key/value pairs to modify the call behavior
@success: boolean, returns `false` if there was an error during the transaction cleanup. Returns `true` if the offline transaction was successfully cleaned or if no offline transaction was configured.
@error_msg: string, contains error encountered while cleaning the transaction
Cancel the dnf5 offline transaction configured for the next reboot and remove all stored offline transaction data, including downloaded packages. Offline updates scheduled by another tool are not affected.
Following @options are supported:
- interactive: boolean, default true
Set to "true", when the operation is done by a user, thus user interaction like password prompts can be done.
Unknown options are ignored.
-->
<method name="clean_with_options">
<arg name="success" type="b" direction="out" />
<arg name="error_msg" type="s" direction="out" />
</method>
<!--
clean:
@success: boolean, returns `false` if there was an error during the transaction cleanup. Returns `true` if the offline transaction was successfully cleaned or if no offline transaction was configured.
@error_msg: string, contains error encountered while cleaning the transaction
Cancel the dnf5 offline transaction configured for the next reboot and remove all stored offline transaction data, including downloaded packages. Offline updates scheduled by another tool are not affected.
This is equivalent to call "clean_with_options()" with empty options.
-->
<method name="clean">
<arg name="options" type="a{sv}" direction="in" />
<arg name="success" type="b" direction="out" />
<arg name="error_msg" type="s" direction="out" />
</method>
<!--
set_finish_action_with_options:
@action: string, one of "poweroff", or "reboot". If set to "poweroff", the system will be powered off after applying the offline transaction. Otherwise the system will reboot.
@options: an array of key/value pairs to modify the call behavior
@success: boolean, true if the action was successfully set
@error_msg: string, contains error encountered while setting the action
Set the action that should be performed after the offline transaction is applied. If the `action` is "poweroff", the system will be powered off, otherwise it will be rebooted (which is default).
The call might fail in case there is no scheduled offline transaction, or the transaction was not scheduled using libdnf5.
Following @options are supported:
- interactive: boolean, default true
Set to "true", when the operation is done by a user, thus user interaction like password prompts can be done.
Unknown options are ignored.
-->
<method name="set_finish_action_with_options">
<arg name="action" type="s" direction="in" />
<arg name="options" type="a{sv}" direction="in" />
<arg name="success" type="b" direction="out" />
<arg name="error_msg" type="s" direction="out" />
</method>
@ -70,6 +136,9 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
Set the action that should be performed after the offline transaction is applied. If the `action` is "poweroff", the system will be powered off, otherwise it will be rebooted (which is default).
The call might fail in case there is no scheduled offline transaction, or the transaction was not scheduled using libdnf5.
This is equivalent to call "set_finish_action_with_options()" with empty options.
-->
<method name="set_finish_action">
<arg name="action" type="s" direction="in" />

View File

@ -55,6 +55,16 @@ void Offline::dbus_register() {
#ifdef SDBUS_CPP_VERSION_2
dbus_object
->addVTable(
sdbus::MethodVTableItem{
sdbus::MethodName{"cancel_with_options"},
sdbus::Signature{"a{sv}"},
{"options"},
sdbus::Signature{"bs"},
{"success", "error_msg"},
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(*this, &Offline::cancel_with_options, call, session.session_locale);
},
{}},
sdbus::MethodVTableItem{
sdbus::MethodName{"cancel"},
{},
@ -76,6 +86,16 @@ void Offline::dbus_register() {
*this, &Offline::get_status, call, session.session_locale);
},
{}},
sdbus::MethodVTableItem{
sdbus::MethodName{"clean_with_options"},
sdbus::Signature{"a{sv}"},
{"options"},
sdbus::Signature{"bs"},
{"success", "error_msg"},
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(*this, &Offline::clean_with_options, call, session.session_locale);
},
{}},
sdbus::MethodVTableItem{
sdbus::MethodName{"clean"},
{},
@ -86,6 +106,17 @@ void Offline::dbus_register() {
session.get_threads_manager().handle_method(*this, &Offline::clean, call, session.session_locale);
},
{}},
sdbus::MethodVTableItem{
sdbus::MethodName{"set_finish_action_with_options"},
sdbus::Signature{"sa{sv}"},
{"action", "options"},
sdbus::Signature{"bs"},
{"success", "error_msg"},
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(
*this, &Offline::set_finish_action_with_options, call, session.session_locale);
},
{}},
sdbus::MethodVTableItem{
sdbus::MethodName{"set_finish_action"},
sdbus::Signature{"s"},
@ -99,6 +130,16 @@ void Offline::dbus_register() {
{}})
.forInterface(dnfdaemon::INTERFACE_OFFLINE);
#else
dbus_object->registerMethod(
dnfdaemon::INTERFACE_OFFLINE,
"cancel_with_options",
{"a{sv}"},
{"options"},
"bs",
{"success", "error_msg"},
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(*this, &Offline::cancel_with_options, call, session.session_locale);
});
dbus_object->registerMethod(
dnfdaemon::INTERFACE_OFFLINE,
"cancel",
@ -119,6 +160,16 @@ void Offline::dbus_register() {
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(*this, &Offline::get_status, call, session.session_locale);
});
dbus_object->registerMethod(
dnfdaemon::INTERFACE_OFFLINE,
"clean_with_options",
{"a{sv}"},
{"options"},
"bs",
{"success", "error_msg"},
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(*this, &Offline::clean_with_options, call, session.session_locale);
});
dbus_object->registerMethod(
dnfdaemon::INTERFACE_OFFLINE,
"clean",
@ -129,6 +180,17 @@ void Offline::dbus_register() {
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(*this, &Offline::clean, call, session.session_locale);
});
dbus_object->registerMethod(
dnfdaemon::INTERFACE_OFFLINE,
"set_finish_action_with_options",
"sa{sv}",
{"action", "options"},
"bs",
{"success", "error_msg"},
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(
*this, &Offline::set_finish_action_with_options, call, session.session_locale);
});
dbus_object->registerMethod(
dnfdaemon::INTERFACE_OFFLINE,
"set_finish_action",
@ -167,8 +229,9 @@ sdbus::MethodReply Offline::get_status(sdbus::MethodCall & call) {
return reply;
}
sdbus::MethodReply Offline::cancel(sdbus::MethodCall & call) {
if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRUSTED_TRANSACTION, call.getSender())) {
sdbus::MethodReply Offline::impl_cancel(sdbus::MethodCall & call, const dnfdaemon::KeyValueMap & options) {
bool interactive = dnfdaemon::key_value_map_get<bool>(options, "interactive", true);
if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRUSTED_TRANSACTION, call.getSender(), interactive)) {
throw std::runtime_error("Not authorized");
}
bool success = true;
@ -194,8 +257,20 @@ sdbus::MethodReply Offline::cancel(sdbus::MethodCall & call) {
return reply;
}
sdbus::MethodReply Offline::clean(sdbus::MethodCall & call) {
if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRUSTED_TRANSACTION, call.getSender())) {
sdbus::MethodReply Offline::cancel_with_options(sdbus::MethodCall & call) {
dnfdaemon::KeyValueMap options;
call >> options;
return impl_cancel(call, options);
}
sdbus::MethodReply Offline::cancel(sdbus::MethodCall & call) {
dnfdaemon::KeyValueMap options{};
return impl_cancel(call, options);
}
sdbus::MethodReply Offline::impl_clean(sdbus::MethodCall & call, const dnfdaemon::KeyValueMap & options) {
bool interactive = dnfdaemon::key_value_map_get<bool>(options, "interactive", true);
if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRUSTED_TRANSACTION, call.getSender(), interactive)) {
throw std::runtime_error("Not authorized");
}
std::vector<std::string> error_msgs;
@ -223,19 +298,30 @@ sdbus::MethodReply Offline::clean(sdbus::MethodCall & call) {
return reply;
}
sdbus::MethodReply Offline::set_finish_action(sdbus::MethodCall & call) {
if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRUSTED_TRANSACTION, call.getSender())) {
sdbus::MethodReply Offline::clean_with_options(sdbus::MethodCall & call) {
dnfdaemon::KeyValueMap options;
call >> options;
return impl_clean(call, options);
}
sdbus::MethodReply Offline::clean(sdbus::MethodCall & call) {
dnfdaemon::KeyValueMap options{};
return impl_clean(call, options);
}
sdbus::MethodReply Offline::impl_set_finish_action(sdbus::MethodCall & call, const std::string & action,
const dnfdaemon::KeyValueMap & options) {
bool interactive = dnfdaemon::key_value_map_get<bool>(options, "interactive", true);
if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRUSTED_TRANSACTION, call.getSender(), interactive)) {
throw std::runtime_error("Not authorized");
}
bool success{false};
std::string error_msg{};
std::string finish_action;
call >> finish_action;
// check finish_action validity
if (finish_action != "poweroff" && finish_action != "reboot") {
if (action != "poweroff" && action != "reboot") {
error_msg = fmt::format(
"Unsupported finish action \"{}\". Valid options are \"reboot\", or \"poweroff\".", finish_action);
"Unsupported finish action \"{}\". Valid options are \"reboot\", or \"poweroff\".", action);
} else {
const std::filesystem::path state_path{get_datadir() / libdnf5::offline::TRANSACTION_STATE_FILENAME};
std::error_code ec;
@ -248,7 +334,7 @@ sdbus::MethodReply Offline::set_finish_action(sdbus::MethodCall & call) {
const auto & read_exception = state.get_read_exception();
if (read_exception == nullptr) {
// set the poweroff_after item accordingly
state.get_data().set_poweroff_after(finish_action == "poweroff");
state.get_data().set_poweroff_after(action == "poweroff");
// write the new state
state.write();
success = true;
@ -266,3 +352,17 @@ sdbus::MethodReply Offline::set_finish_action(sdbus::MethodCall & call) {
reply << error_msg;
return reply;
}
sdbus::MethodReply Offline::set_finish_action_with_options(sdbus::MethodCall & call) {
std::string action;
dnfdaemon::KeyValueMap options;
call >> action >> options;
return impl_set_finish_action(call, action, options);
}
sdbus::MethodReply Offline::set_finish_action(sdbus::MethodCall & call) {
std::string action;
dnfdaemon::KeyValueMap options{};
call >> action;
return impl_set_finish_action(call, action, options);
}

View File

@ -34,9 +34,15 @@ public:
void dbus_deregister();
private:
sdbus::MethodReply impl_cancel(sdbus::MethodCall & call, const dnfdaemon::KeyValueMap & options);
sdbus::MethodReply cancel_with_options(sdbus::MethodCall & call);
sdbus::MethodReply cancel(sdbus::MethodCall & call);
sdbus::MethodReply impl_clean(sdbus::MethodCall & call, const dnfdaemon::KeyValueMap & options);
sdbus::MethodReply clean_with_options(sdbus::MethodCall & call);
sdbus::MethodReply clean(sdbus::MethodCall & call);
sdbus::MethodReply get_status(sdbus::MethodCall & call);
sdbus::MethodReply impl_set_finish_action(sdbus::MethodCall & call, const std::string & action, const dnfdaemon::KeyValueMap & options);
sdbus::MethodReply set_finish_action_with_options(sdbus::MethodCall & call);
sdbus::MethodReply set_finish_action(sdbus::MethodCall & call);
enum class Scheduled { NOT_SCHEDULED, ANOTHER_TOOL, SCHEDULED };