Skip to content

Commit

Permalink
dnfdaemon: Add new API for offline transactions
Browse files Browse the repository at this point in the history
Adds new `Offline` interface for interacting with offline transactions.
The interface provides three methods:

- check_pending() - check whether there is an offline update scheduled
  for the next reboot

- cancel() - cancel the scheduled offline update

- clean() - cancel the scheduled offline update and remove all
  stored offline transaction data.

- set_finish_action() - to set whether after applying the offline
  transaction the system should be rebooted (default) or powered off.
  • Loading branch information
m-blaha committed Jul 8, 2024
1 parent d21f55e commit e7db9ec
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 2 deletions.
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");
// 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

0 comments on commit e7db9ec

Please sign in to comment.