Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dnfdaemon: Support to run transactions offline #1543

Merged
merged 5 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion dnf5/commands/offline/offline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ void OfflineSubcommand::configure() {
const std::filesystem::path installroot = ctx.get_base().get_config().get_installroot_option().get_value();
datadir = installroot / libdnf5::offline::DEFAULT_DATADIR.relative_path();
std::filesystem::create_directories(datadir);
state = std::make_optional<libdnf5::offline::OfflineTransactionState>(datadir / "offline-transaction-state.toml");
state = std::make_optional<libdnf5::offline::OfflineTransactionState>(
datadir / libdnf5::offline::TRANSACTION_STATE_FILENAME);
}

void check_state(const libdnf5::offline::OfflineTransactionState & state) {
Expand Down
1 change: 1 addition & 0 deletions dnf5daemon-server/dbus.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const char * const INTERFACE_RPM = "org.rpm.dnf.v0.rpm.Rpm";
const char * const INTERFACE_GOAL = "org.rpm.dnf.v0.Goal";
const char * const INTERFACE_GROUP = "org.rpm.dnf.v0.comps.Group";
const char * const INTERFACE_ADVISORY = "org.rpm.dnf.v0.Advisory";
const char * const INTERFACE_OFFLINE = "org.rpm.dnf.v0.Offline";
const char * const INTERFACE_SESSION_MANAGER = "org.rpm.dnf.v0.SessionManager";

// signals
Expand Down
80 changes: 80 additions & 0 deletions dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Offline.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">

<!--
Copyright Contributors to the libdnf project.

This file is part of libdnf: https://github.com/rpm-software-management/libdnf/

Libdnf is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 of the License, or
(at your option) any later version.

Libdnf 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with libdnf. If not, see <https://www.gnu.org/licenses/>.
-->

<node>
<!-- org.rpm.dnf.v0.Offline:
@short_description: Interface to offline transactions
-->
<interface name="org.rpm.dnf.v0.Offline">
<!--
check_pending:
@pending: boolean, true if there is a pending offline transaction

Check whether there is an offline transaction configured for the next reboot initiated by dnf5.
-->
<method name="check_pending">
<arg name="pending" type="b" direction="out" />
</method>


<!--
cancel:
@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.
-->
<method name="cancel">
<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.
-->
<method name="clean">
<arg name="success" type="b" direction="out" />
<arg name="error_msg" type="s" direction="out" />
</method>

<!--
set_finish_action:
@action: string, if set to "poweroff", the system will be powered off after applying the offline transaction. Otherwise the system will reboot.
@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.
-->
<method name="set_finish_action">
<arg name="action" type="s" direction="in" />
<arg name="success" type="b" direction="out" />
<arg name="error_msg" type="s" direction="out" />
</method>

</interface>

</node>
1 change: 0 additions & 1 deletion dnf5daemon-server/services/goal/goal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include "utils.hpp"

#include <fmt/format.h>
#include <libdnf5/transaction/offline.hpp>
#include <libdnf5/transaction/transaction_item.hpp>
#include <libdnf5/transaction/transaction_item_action.hpp>
#include <sdbus-c++/sdbus-c++.h>
Expand Down
190 changes: 190 additions & 0 deletions dnf5daemon-server/services/offline/offline.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
Copyright Contributors to the libdnf project.

This file is part of libdnf: https://github.com/rpm-software-management/libdnf/

Libdnf 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.

Libdnf 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 libdnf. If not, see <https://www.gnu.org/licenses/>.
*/

#include "offline.hpp"

#include "dbus.hpp"
#include "utils/string.hpp"

#include <libdnf5/transaction/offline.hpp>
#include <sdbus-c++/sdbus-c++.h>

#include <exception>
#include <filesystem>

const char * const ERR_ANOTHER_TOOL = "Offline transaction was initiated by another tool.";

std::filesystem::path Offline::get_datadir() {
auto base = session.get_base();
const auto & installroot = base->get_config().get_installroot_option().get_value();
return installroot / libdnf5::offline::DEFAULT_DATADIR.relative_path();
}

Offline::Scheduled Offline::offline_transaction_scheduled() {
std::error_code ec;
// magic symlink exists
if (std::filesystem::exists(libdnf5::offline::MAGIC_SYMLINK, ec)) {
// and points to dnf5 location
if (std::filesystem::equivalent(libdnf5::offline::MAGIC_SYMLINK, get_datadir())) {
return Scheduled::SCHEDULED;
} else {
return Scheduled::ANOTHER_TOOL;
}
}
return Scheduled::NOT_SCHEDULED;
}

void Offline::dbus_register() {
auto dbus_object = session.get_dbus_object();
dbus_object->registerMethod(
dnfdaemon::INTERFACE_OFFLINE,
"cancel",
{},
{},
"bs",
{"success", "error_msg"},
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(*this, &Offline::cancel, call, session.session_locale);
});
dbus_object->registerMethod(
dnfdaemon::INTERFACE_OFFLINE,
"check_pending",
{},
{},
"b",
{"pending"},
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(*this, &Offline::check_pending, call, session.session_locale);
});
dbus_object->registerMethod(
dnfdaemon::INTERFACE_OFFLINE,
"clean",
{},
{},
"bs",
{"success", "error_msg"},
[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",
"s",
{"action"},
"bs",
{"success", "error_msg"},
[this](sdbus::MethodCall call) -> void {
session.get_threads_manager().handle_method(
*this, &Offline::set_finish_action, call, session.session_locale);
});
}

sdbus::MethodReply Offline::check_pending(sdbus::MethodCall & call) {
auto reply = call.createReply();
reply << (offline_transaction_scheduled() == Scheduled::SCHEDULED);
return reply;
}

sdbus::MethodReply Offline::cancel(sdbus::MethodCall & call) {
if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRANSACTION, call.getSender())) {
throw std::runtime_error("Not authorized");
}
bool success = true;
std::string error_msg;
switch (offline_transaction_scheduled()) {
case Scheduled::SCHEDULED: {
std::error_code ec;
if (!std::filesystem::remove(libdnf5::offline::MAGIC_SYMLINK, ec) && ec) {
success = false;
error_msg = ec.message();
}
} break;
case Scheduled::ANOTHER_TOOL:
success = false;
error_msg = ERR_ANOTHER_TOOL;
break;
case Scheduled::NOT_SCHEDULED:
break;
}
auto reply = call.createReply();
reply << success;
reply << error_msg;
return reply;
}

sdbus::MethodReply Offline::clean(sdbus::MethodCall & call) {
if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRANSACTION, call.getSender())) {
throw std::runtime_error("Not authorized");
}
std::vector<std::string> error_msgs;
bool success = true;
if (offline_transaction_scheduled() == Scheduled::SCHEDULED) {
// remove the magic symlink if it was created by dnf5
std::error_code ec;
if (!std::filesystem::remove(libdnf5::offline::MAGIC_SYMLINK, ec) && ec) {
success = false;
error_msgs.push_back(ec.message());
}
}
// clean dnf5 offline transaction files
for (const auto & entry : std::filesystem::directory_iterator(get_datadir())) {
std::error_code ec;
std::filesystem::remove_all(entry.path(), ec);
if (ec) {
success = false;
error_msgs.push_back(ec.message());
}
}
auto reply = call.createReply();
reply << success;
reply << libdnf5::utils::string::join(error_msgs, ", ");
return reply;
}

sdbus::MethodReply Offline::set_finish_action(sdbus::MethodCall & call) {
if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRANSACTION, call.getSender())) {
throw std::runtime_error("Not authorized");
}
bool success{false};
std::string error_msg{};
// try load the offline transaction state
const std::filesystem::path state_path{
libdnf5::offline::MAGIC_SYMLINK / libdnf5::offline::TRANSACTION_STATE_FILENAME};
libdnf5::offline::OfflineTransactionState state{state_path};
const auto & read_exception = state.get_read_exception();
if (read_exception == nullptr) {
// set the poweroff_after item accordingly
std::string finish_action;
call >> finish_action;
state.get_data().set_poweroff_after(finish_action == "poweroff");
Copy link
Contributor

@mcrha mcrha Jul 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine to have it like this, even case sensitive, but it might be good to at least test whether the other action is "reboot" and claim a warning on the console about "unknown finish action" if it's neither "poweroff" nor "reboot". That way one can check for typos and the older library will not crash when a new value is proposed in the future.

This may touch also the documentation in the D-Bus interface.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can fill the error_msg output parameter with such warning. Returning an error reply seems too me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning an error, aka failing the call, would make the parameter strict. It's fine by me, it only has certain consequences.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK then, let's make the value check strict and return an error in case of unsupported value.

// write the new state
state.write();
success = true;
} else {
try {
std::rethrow_exception(read_exception);
} catch (const std::exception & ex) {
error_msg = ex.what();
}
}
auto reply = call.createReply();
reply << success;
reply << error_msg;
return reply;
}
47 changes: 47 additions & 0 deletions dnf5daemon-server/services/offline/offline.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright Contributors to the libdnf project.

This file is part of libdnf: https://github.com/rpm-software-management/libdnf/

Libdnf 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.

Libdnf 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 libdnf. If not, see <https://www.gnu.org/licenses/>.
*/

#ifndef DNF5DAEMON_SERVER_SERVICES_OFFLINE_OFFLINE_HPP
#define DNF5DAEMON_SERVER_SERVICES_OFFLINE_OFFLINE_HPP

#include "session.hpp"

#include <sdbus-c++/sdbus-c++.h>

#include <filesystem>

class Offline : public IDbusSessionService {
public:
using IDbusSessionService::IDbusSessionService;
~Offline() = default;
void dbus_register();
void dbus_deregister();

private:
sdbus::MethodReply cancel(sdbus::MethodCall & call);
sdbus::MethodReply check_pending(sdbus::MethodCall & call);
sdbus::MethodReply clean(sdbus::MethodCall & call);
sdbus::MethodReply set_finish_action(sdbus::MethodCall & call);

enum class Scheduled { NOT_SCHEDULED, ANOTHER_TOOL, SCHEDULED };
Scheduled offline_transaction_scheduled();
std::filesystem::path get_datadir();
};

#endif
2 changes: 2 additions & 0 deletions dnf5daemon-server/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include "services/base/base.hpp"
#include "services/comps/group.hpp"
#include "services/goal/goal.hpp"
#include "services/offline/offline.hpp"
#include "services/repo/repo.hpp"
#include "services/rpm/rpm.hpp"
#include "utils.hpp"
Expand Down Expand Up @@ -135,6 +136,7 @@ Session::Session(
services.emplace_back(std::make_unique<Repo>(*this));
services.emplace_back(std::make_unique<Rpm>(*this));
services.emplace_back(std::make_unique<Goal>(*this));
services.emplace_back(std::make_unique<Offline>(*this));
services.emplace_back(std::make_unique<Group>(*this));
services.emplace_back(std::make_unique<dnfdaemon::Advisory>(*this));

Expand Down
Loading