Skip to content

Commit

Permalink
automatic: Implement waiting for network
Browse files Browse the repository at this point in the history
Automatic plugin will wait up to `timeout` seconds for network to be
on-line. Timeout is taken from automatic configuration file.
  • Loading branch information
m-blaha committed Nov 1, 2023
1 parent f664fb5 commit 50a46db
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 2 deletions.
136 changes: 134 additions & 2 deletions dnf5-plugins/automatic_plugin/automatic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,23 @@ along with libdnf. If not, see <https://www.gnu.org/licenses/>.
#include "emitters.hpp"
#include "transaction_callbacks_simple.hpp"

#include <curl/curl.h>
#include <libdnf5-cli/output/transaction_table.hpp>
#include <libdnf5/conf/const.hpp>
#include <libdnf5/repo/package_downloader.hpp>
#include <libdnf5/utils/bgettext/bgettext-mark-domain.h>
#include <libdnf5/utils/format.hpp>
#include <libsmartcols/libsmartcols.h>
#include <netdb.h>
#include <stdlib.h>

#include <chrono>
#include <filesystem>
#include <iostream>
#include <random>
#include <set>
#include <string>
#include <thread>

namespace {

Expand All @@ -59,11 +63,139 @@ static bool reboot_needed(const libdnf5::base::Transaction & transaction) {
return false;
}

static bool server_available(std::string_view host, std::string_view service) {
// Resolve host name/service to IP address/port
struct addrinfo * server_info;
if (getaddrinfo(host.data(), service.data(), nullptr, &server_info) != 0) {
return false;
}

// Create a socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
freeaddrinfo(server_info);
return false;
}

// Attempt to connect to the server
bool retval = true;
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
if (connect(sockfd, server_info->ai_addr, server_info->ai_addrlen) < 0) {
retval = false;
}

close(sockfd);
freeaddrinfo(server_info);
return retval;
}

} // namespace


namespace dnf5 {

void AutomaticCommand::wait_for_network() {
auto timeout = config_automatic.config_commands.network_online_timeout.get_value();
if (timeout <= 0) {
return;
}

auto & context = get_context();
auto & base = context.base;
auto & logger = *base.get_logger();
logger.debug("Waiting for internet connection...");

const auto time_0 = std::chrono::system_clock::now();
const auto time_end = time_0 + std::chrono::seconds(timeout);

// collect repository addresses to check network availability on
libdnf5::repo::RepoQuery repos(base);
repos.filter_enabled(true);
std::vector<std::string> urls{};
for (const auto & repo : repos) {
std::string proxy{};
try {
proxy = repo->get_config().get_proxy_option().get_value();
if (!proxy.empty()) {
urls.emplace_back(proxy);
continue;
}
} catch (const libdnf5::OptionValueNotSetError & e) {
}
try {
auto mirrorlist = repo->get_config().get_mirrorlist_option().get_value();
if (!mirrorlist.empty()) {
urls.emplace_back(std::move(mirrorlist));
}
} catch (const libdnf5::OptionValueNotSetError & e) {
}
try {
auto metalink = repo->get_config().get_metalink_option().get_value();
if (!metalink.empty()) {
urls.emplace_back(std::move(metalink));
}
} catch (const libdnf5::OptionValueNotSetError & e) {
}
try {
for (auto u : repo->get_config().get_baseurl_option().get_value()) {
if (!u.empty()) {
urls.emplace_back(std::move(u));
}
}
} catch (const libdnf5::OptionValueNotSetError & e) {
}
}

// parse url to [(host, service(port number or scheme))] using libcurl
std::vector<std::tuple<std::string, std::string>> addresses;
CURLUcode rc;
CURLU * url = curl_url();
unsigned int set_flags = CURLU_NON_SUPPORT_SCHEME;
unsigned int get_flags = 0;
for (auto & u : urls) {
rc = curl_url_set(url, CURLUPART_URL, u.c_str(), set_flags);
if (!rc) {
std::string host{};
std::string port{};
char * val = nullptr;
rc = curl_url_get(url, CURLUPART_HOST, &val, get_flags);
if (rc != CURLUE_OK) {
continue;
}
host = val;
curl_free(val);
// service is port or (if not given) scheme
rc = curl_url_get(url, CURLUPART_PORT, &val, get_flags);
if (rc == CURLUE_OK) {
port = val;
} else {
rc = curl_url_get(url, CURLUPART_SCHEME, &val, get_flags);
if (rc != CURLUE_OK) {
continue;
}
port = val;
}
curl_free(val);
addresses.emplace_back(std::move(host), std::move(port));
}
}
curl_url_cleanup(url);

while (std::chrono::system_clock::now() < time_end) {
for (auto const & [host, service] : addresses) {
if (server_available(host, service)) {
return;
}
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}

logger.warning("No network connection detected.");
}

void AutomaticCommand::set_parent_command() {
auto * arg_parser_parent_cmd = get_session().get_argument_parser().get_root_command();
auto * arg_parser_this_cmd = get_argument_parser_command();
Expand Down Expand Up @@ -158,8 +290,6 @@ void AutomaticCommand::pre_configure() {
auto & context = get_context();
auto & base = context.base;

// TODO wait for network

auto random_sleep = config_automatic.config_commands.random_sleep.get_value();
if (timer->get_value() && random_sleep > 0) {
random_wait(random_sleep);
Expand Down Expand Up @@ -199,6 +329,8 @@ void AutomaticCommand::configure() {
context.update_repo_metadata_from_advisory_options(
{}, config_automatic.config_commands.upgrade_type.get_value() == "security", false, false, false, {}, {}, {});
context.set_load_available_repos(Context::LoadAvailableRepos::ENABLED);

wait_for_network();
}

void AutomaticCommand::run() {
Expand Down
2 changes: 2 additions & 0 deletions dnf5-plugins/automatic_plugin/automatic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class AutomaticCommand : public Command {
void run() override;

private:
void wait_for_network();

std::unique_ptr<libdnf5::cli::session::BoolOption> timer{nullptr};
ConfigAutomatic config_automatic;
bool download_callbacks_set{false};
Expand Down

0 comments on commit 50a46db

Please sign in to comment.