Compare commits

...

16 Commits

Author SHA1 Message Date
c21f6d2b64 Make README generic, accounting for various DEs
All checks were successful
Build / build (push) Successful in 18s
2024-07-14 11:35:37 +02:00
29434bc41e Add a deployment job
All checks were successful
Build / build (push) Successful in 14s
Deploy / deploy (push) Successful in 4s
2024-07-13 11:58:20 +02:00
c97f1fc87c Support s6-rc for service control
All checks were successful
Build / build (push) Successful in 16s
2024-07-05 19:46:32 +02:00
78f9939afc Fix setting the clock by one hour back
All checks were successful
Build / build (push) Successful in 17s
2024-06-30 20:46:40 +02:00
8bf3a92cbc Implement SetLocalRTC
All checks were successful
Build / build (push) Successful in 16s
2024-06-29 16:01:57 +02:00
d75053b243 Implement TimeUSec property
All checks were successful
Build / build (push) Successful in 16s
2024-06-27 01:24:37 +02:00
b11e18e560 Implement TimeUSecRTC property
All checks were successful
Build / build (push) Successful in 20s
2024-06-24 13:09:00 +02:00
3a85891f2e Replace ntp_adjtime with adjtimex
All checks were successful
Build / build (push) Successful in 19s
ntp_adjtime is not supported by musl.
2024-06-22 10:24:39 +02:00
cbbefae284 Add the NTPSynchronized property
Some checks failed
Build / build (push) Failing after 19s
2024-06-21 16:07:28 +02:00
6fc7f86a89 Implement CanNTP property
All checks were successful
Build / build (push) Successful in 18s
2024-06-19 12:11:56 +02:00
9dc8fb2640 Generate path configuration file
All checks were successful
Build / build (push) Successful in 18s
2024-06-18 11:19:44 +02:00
7b5fe4b9d1 Separate service implementation from the main
All checks were successful
Build / build (push) Successful in 21s
2024-06-18 10:20:18 +02:00
bbb4efde18 Report errors from clock_gettime and clock_settime
All checks were successful
Build / build (push) Successful in 16s
2024-06-15 11:34:37 +02:00
ad19cb19d1 Read installed XML on start
All checks were successful
Build / build (push) Successful in 17s
2024-06-13 19:47:56 +02:00
e72976840c Report back SetNTP errors
All checks were successful
Build / build (push) Successful in 19s
2024-06-13 13:09:52 +02:00
8d43caadcc Use glibmm API for variants
All checks were successful
Build / build (push) Successful in 16s
2024-06-10 16:42:09 +02:00
11 changed files with 611 additions and 440 deletions

View File

@ -0,0 +1,22 @@
name: Deploy
on:
push:
tags:
- '**'
jobs:
deploy:
runs-on: buildenv
steps:
- uses: actions/checkout@v4
- name: Archive
run: |
DISTRIBUTION=$(echo $GITHUB_REF_NAME | awk '{ gsub(/^v/, "slack-timedate-"); print $0 }')
ln -s . $DISTRIBUTION
touch $DISTRIBUTION.tar.xz
tar --exclude="${DISTRIBUTION}/${DISTRIBUTION}*" --exclude-vcs -Jcvhf $DISTRIBUTION.tar.xz $DISTRIBUTION
- uses: akkuman/gitea-release-action@v1
with:
files: "*.tar.xz"
token: ${{ secrets.API_KEY }}

View File

@ -1,10 +1,14 @@
# slack-timedate # slack-timedate
This program implements timedated1 interface from systemd what is This program implements timedated1 interface from systemd that is
required by GNOME Control Center for now. required by GNOME Control Center and can be used by KDE Settings.
slack-timedate's purpose is to make it possible to enjoy full GNOME
functionality on Slackware Linux which doesn't use systemd as its slack-timedate's purpose is to make it possible to enjoy full desktop
init-system. environment functionality on Slackware Linux which doesn't use
systemd as its init-system.
slack-timedate supports Slackware's System V style init system as well
as s6.
## Installation ## Installation

View File

@ -1,5 +1,7 @@
find_program(SED sed) find_program(SED sed)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/resources.h.in" "resources.h")
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/org.freedesktop.timedate1.service add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/org.freedesktop.timedate1.service
COMMAND ${SED} -e s|@LIBEXECDIR@|${CMAKE_INSTALL_FULL_LIBEXECDIR}| ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.timedate1.service.in > ${PROJECT_BINARY_DIR}/org.freedesktop.timedate1.service COMMAND ${SED} -e s|@LIBEXECDIR@|${CMAKE_INSTALL_FULL_LIBEXECDIR}| ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.timedate1.service.in > ${PROJECT_BINARY_DIR}/org.freedesktop.timedate1.service
MAIN_DEPENDENCY org.freedesktop.timedate1.service.in MAIN_DEPENDENCY org.freedesktop.timedate1.service.in

View File

@ -5,6 +5,10 @@
<property name="Timezone" type="s" access="read"/> <property name="Timezone" type="s" access="read"/>
<property name="LocalRTC" type="b" access="read"/> <property name="LocalRTC" type="b" access="read"/>
<property name="NTP" type="b" access="read"/> <property name="NTP" type="b" access="read"/>
<property name="CanNTP" type="b" access="read"/>
<property name="NTPSynchronized" type="b" access="read"/>
<property name="TimeUSec" type="t" access="read"/>
<property name="RTCTimeUSec" type="t" access="read"/>
<method name="SetTime"> <method name="SetTime">
<arg name="usec_utc" type="x" direction="in"/> <arg name="usec_utc" type="x" direction="in"/>
<arg name="relative" type="b" direction="in"/> <arg name="relative" type="b" direction="in"/>

1
data/resources.h.in Normal file
View File

@ -0,0 +1 @@
#define DATADIR_INTERFACES "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_DATADIR@/dbus-1/interfaces"

View File

@ -1,10 +1,10 @@
find_package(PkgConfig) find_package(PkgConfig)
pkg_check_modules(GDBUS REQUIRED gio-2.0 dbus-1 glibmm-2.4 giomm-2.4) pkg_check_modules(GDBUS REQUIRED gio-2.0 dbus-1 glibmm-2.4 giomm-2.4)
add_executable(slack-timedate add_executable(slack-timedate main.cpp
slack-timedate.cpp slack-timedate.h timedate.cpp timedate.h
) )
target_include_directories(slack-timedate PRIVATE ${GDBUS_INCLUDE_DIRS}) target_include_directories(slack-timedate PRIVATE ${CMAKE_BINARY_DIR}/data ${GDBUS_INCLUDE_DIRS})
target_link_libraries(slack-timedate ${GDBUS_LIBRARIES}) target_link_libraries(slack-timedate ${GDBUS_LIBRARIES})
install(TARGETS slack-timedate DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}) install(TARGETS slack-timedate DESTINATION ${CMAKE_INSTALL_LIBEXECDIR})

50
src/main.cpp Normal file
View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2013-2024 Eugen Wissner <belka@caraus.de>
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "timedate.h"
bool timeout_callback(Glib::RefPtr<Glib::MainLoop> loop2quit)
{
loop2quit->quit();
return false;
}
constexpr unsigned int default_exit_sec = 300;
int main(int argc, char **argv)
{
Gio::init();
auto timedate1 = std::make_unique<dlackware::timedate::timedate1>();
guint owner_id = Gio::DBus::own_name(Gio::DBus::BUS_TYPE_SYSTEM,
Glib::ustring(dlackware::timedate::bus_name.cbegin(), dlackware::timedate::bus_name.cend()),
std::bind(&dlackware::timedate::timedate1::on_bus_acquired, timedate1.get(),
std::placeholders::_1, std::placeholders::_2),
Gio::DBus::SlotNameAcquired(),
std::bind(&dlackware::timedate::timedate1::on_name_lost, timedate1.get(),
std::placeholders::_1, std::placeholders::_2),
Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE);
Glib::RefPtr<Glib::MainLoop> loop = Glib::MainLoop::create(false);
Glib::signal_timeout().connect_seconds(sigc::bind(&timeout_callback, loop), default_exit_sec);
loop->run();
Gio::DBus::unown_name(owner_id);
return EXIT_SUCCESS;
}

View File

@ -1,330 +0,0 @@
/*
* Copyright (C) 2013-2024 Eugen Wissner <belka@caraus.de>
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <cstring>
#include <filesystem>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/stat.h>
#include "slack-timedate.h"
static void slack_method_call(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& sender,
const Glib::ustring& object_path, const Glib::ustring& interface_name, const Glib::ustring& method_name,
const Glib::VariantContainerBase& parameters, const Glib::RefPtr<Gio::DBus::MethodInvocation>& invocation) {
gchar *timezone;
gboolean user_interaction, relative, is_localtime, use_ntp;
gint64 usec_utc;
auto parameters_copy = parameters.gobj_copy();
// Set time zone
if (method_name == "SetTimezone")
{
g_variant_get(parameters_copy, "(&sb)", &timezone, &user_interaction);
std::error_code error_code;
if (dlackware::timedate::set_timezone(timezone, error_code))
{
g_dbus_method_invocation_return_value(invocation->gobj(), NULL);
}
else
{
g_dbus_method_invocation_return_error(invocation->gobj(), G_IO_ERROR, G_IO_ERROR_FAILED,
"Set time zone: %s",
error_code ? error_code.message().c_str() : "Zone info is not a regular file");
}
g_free (timezone);
}
else if (method_name == "SetTime")
{
g_variant_get(parameters_copy, "(xbb)", &usec_utc, &relative, &user_interaction);
// Set time
//if (!slack_set_time (usec_utc, slack_get_is_localtime ())) {
if (slack_set_time(usec_utc, relative))
{
g_dbus_method_invocation_return_value(invocation->gobj(), NULL);
}
else
{
g_dbus_method_invocation_return_error(invocation->gobj(), G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to set system clock");
}
}
else if (method_name == "SetNTP")
{
g_variant_get(parameters_copy, "(bb)", &use_ntp, &user_interaction);
// Enable NTP
if (slack_set_ntp (use_ntp))
{
g_dbus_method_invocation_return_value(invocation->gobj(), NULL);
}
else
{
g_dbus_method_invocation_return_error(invocation->gobj(),
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Error enabling NTP");
}
}
else if (method_name == "ListTimezones")
{
try
{
auto return_tuple = Glib::Variant<std::vector<Glib::ustring>>::create_tuple({
dlackware::timedate::list_timezones()
});
g_dbus_method_invocation_return_value(invocation->gobj(), return_tuple.gobj());
}
catch (const std::exception& exception)
{
g_dbus_method_invocation_return_error(invocation->gobj(), G_IO_ERROR, G_IO_ERROR_FAILED,
"List time zones: %s", exception.what());
}
}
g_free(parameters_copy);
}
static void slack_get_property(Glib::VariantBase& result, const Glib::RefPtr<Gio::DBus::Connection>& connection,
const Glib::ustring& sender, const Glib::ustring& object_path, const Glib::ustring& interface_name,
const Glib::ustring& prop_name)
{
if (prop_name == "Timezone")
{
result = Glib::Variant<Glib::ustring>::create(dlackware::timedate::timezone());
}
else if (prop_name == "LocalRTC")
{
result = Glib::Variant<bool>::create(slack_get_is_localtime());
}
else if (prop_name == "NTP")
{
result = Glib::Variant<bool>::create(slack_get_ntp());
}
}
gboolean timeout_callback(Glib::RefPtr<Glib::MainLoop> loop2quit)
{
loop2quit->quit();
return false;
}
int main(int argc, char **argv)
{
Gio::init();
auto timedate1 = std::make_unique<dlackware::timedate::timedate1>();
guint owner_id = Gio::DBus::own_name(Gio::DBus::BUS_TYPE_SYSTEM, BUS_NAME,
std::bind(&dlackware::timedate::timedate1::on_bus_acquired, timedate1.get(),
std::placeholders::_1, std::placeholders::_2),
Gio::DBus::SlotNameAcquired(),
std::bind(&dlackware::timedate::timedate1::on_name_lost, timedate1.get(),
std::placeholders::_1, std::placeholders::_2),
Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE);
Glib::RefPtr<Glib::MainLoop> loop = Glib::MainLoop::create(false);
Glib::signal_timeout().connect_seconds(sigc::bind(&timeout_callback, loop), DEFAULT_EXIT_SEC);
loop->run();
Gio::DBus::unown_name(owner_id);
return EXIT_SUCCESS;
}
namespace dlackware::timedate
{
timedate1::timedate1()
: interface_vtable{ &slack_method_call, &slack_get_property }
{
}
void timedate1::on_name_lost(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name)
{
g_warning("Failed to acquire the service %s.\n", name.data());
exit(1);
}
void timedate1::on_bus_acquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name)
{
Glib::RefPtr<Gio::DBus::NodeInfo> introspection_data;
try
{
introspection_data = Gio::DBus::NodeInfo::create_for_xml(INTROSPECTION_XML);
}
catch (Glib::Error& slack_err)
{
g_error("Failed to parse D-Bus introspection XML: %s\n", slack_err.what().data());
}
try
{
guint registration_id = connection->register_object(BUS_PATH,
introspection_data->lookup_interface(), this->interface_vtable);
}
catch (Glib::Error& slack_err)
{
g_critical("Failed to register callbacks for the exported object with the D-Bus interface: %s\n",
slack_err.what().data());
}
}
void list_timezones(const std::string& prefix, std::vector<Glib::ustring>& accumulator)
{
auto zoneinfo_path = std::filesystem::path(zoneinfo_database) / prefix;
for (auto zoneinfo_entry : std::filesystem::directory_iterator(zoneinfo_path))
{
auto new_prefix = prefix + zoneinfo_entry.path().filename().string();
if (zoneinfo_entry.is_directory())
{
list_timezones(new_prefix + "/", accumulator);
}
else if (zoneinfo_entry.is_regular_file() && !zoneinfo_entry.path().has_extension()
&& zoneinfo_entry.path().filename() != "leapseconds")
{
accumulator.emplace_back(new_prefix);
}
}
}
Glib::Variant<std::vector<Glib::ustring>> list_timezones()
{
std::vector<Glib::ustring> result;
list_timezones("", result);
return Glib::Variant<std::vector<Glib::ustring>>::create(result);
}
Glib::ustring timezone()
{
std::unique_ptr<gchar[], decltype(&g_free)> zone_copied_from(
g_file_read_link("/etc/localtime", NULL), &g_free);
if (zone_copied_from == nullptr)
{
return nullptr;
}
return Glib::ustring{ zone_copied_from.get() + strlen(zoneinfo_database) + 1 };
}
bool set_timezone(gchar *zone, std::error_code& ec)
{
ec.clear();
auto zone_file = std::filesystem::path("/usr/share/zoneinfo") / zone;
if (!std::filesystem::is_regular_file(zone_file, ec) || ec)
{
return false;
}
std::filesystem::path etc_localtime = "/etc/localtime";
std::filesystem::remove(etc_localtime);
std::filesystem::create_symlink(zone_file, etc_localtime, ec);
return !ec;
}
}
gboolean slack_set_time(gint64 seconds_since_epoch, gboolean relative)
{
timespec ts;
if (relative)
{
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
{
return FALSE;
}
ts.tv_sec += static_cast<time_t>(seconds_since_epoch / USEC_PER_SEC);
ts.tv_nsec += (seconds_since_epoch % USEC_PER_SEC) * NSEC_PER_USEC;
if (ts.tv_nsec < 0)
{
--ts.tv_sec;
ts.tv_nsec += USEC_PER_SEC;
}
}
else
{
ts.tv_sec = static_cast<time_t>(seconds_since_epoch / USEC_PER_SEC);
ts.tv_nsec = (seconds_since_epoch % USEC_PER_SEC) * NSEC_PER_USEC;
}
return clock_settime (CLOCK_REALTIME, &ts) == 0;
}
gboolean slack_get_is_localtime()
{
std::ifstream fh{ "/etc/hardwareclock", std::ios::in };
if (!fh.is_open())
{
return false;
}
std::array<char, 10> time_str; // "localtime" is longer than "UTC" and has 9 letters + \0
while (fh.good())
{
fh.getline(time_str.data(), time_str.size());
fh.clear(fh.rdstate() & (~std::ios_base::failbit));
if (std::strncmp(time_str.data(), "localtime", time_str.size()) == 0)
{
return true;
}
}
return false;
}
gboolean slack_get_ntp()
{
return Glib::file_test("/etc/rc.d/rc.ntpd", Glib::FileTest::FILE_TEST_IS_EXECUTABLE);
}
gboolean slack_set_ntp(gboolean xntp)
{
std::filesystem::perms rc_mode = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write
| std::filesystem::perms::group_read | std::filesystem::perms::others_read;
if (xntp)
{
rc_mode |= std::filesystem::perms::owner_exec
| std::filesystem::perms::group_exec | std::filesystem::perms::others_exec;
g_message("Please don't forget to configure the NTP daemon");
/* It doesn't matter if fails.
* The ntpdate command is considered obsolete, but "orthodox" ntpd -g
* will fail if your system clock is off for more than half an hour. */
g_spawn_command_line_async("/usr/sbin/ntpdate pool.ntp.org", NULL);
}
if (Glib::file_test("/etc/rc.d/rc.ntpd", Glib::FileTest::FILE_TEST_IS_REGULAR))
{
std::error_code ec;
std::filesystem::permissions("/etc/rc.d/rc.ntpd", rc_mode, ec);
return !ec;
}
else
{
g_error("The NTP daemon isn't installed");
return false;
}
}

View File

@ -1,102 +0,0 @@
/*
* Copyright (C) 2013-2024 Eugen Wissner <belka@caraus.de>
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <dbus/dbus.h>
#include <glibmm.h>
#include <giomm.h>
#include <gio/gio.h>
#define BUS_NAME "org.freedesktop.timedate1"
#define BUS_PATH "/org/freedesktop/timedate1"
#define BUS_INTERFACE "org.freedesktop.timedate1"
#define INTROSPECTION_XML DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
"<node>\n" \
" <interface name=\"org.freedesktop.timedate1\">\n" \
" <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n" \
" <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n" \
" <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \
" <method name=\"SetTime\">\n" \
" <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \
" <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \
" <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"SetTimezone\">\n" \
" <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"SetLocalRTC\">\n" \
" <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \
" <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \
" <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"SetNTP\">\n" \
" <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \
" <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"ListTimezones\">\n" \
" <arg name=\"timezones\" type=\"as\" direction=\"out\"/>\n" \
" </method>" \
" </interface>\n" \
"</node>\n"
#define DEFAULT_EXIT_SEC 300
constexpr gint32 USEC_PER_SEC = 1000000ULL;
constexpr gint32 NSEC_PER_USEC = 1000ULL;
namespace dlackware::timedate
{
constexpr const char *zoneinfo_database = "/usr/share/zoneinfo";
// Returns the timezones available on the system.
Glib::Variant<std::vector<Glib::ustring>> list_timezones();
void list_timezones(const std::string& prefix, std::vector<Glib::ustring>& accumulator);
// Returns the system time zone.
Glib::ustring timezone();
// Sets the system time zone to the one passed by the argument
// Returns true on success, false otherwise
bool set_timezone(gchar *, std::error_code& ec);
class timedate1
{
const Gio::DBus::InterfaceVTable interface_vtable;
public:
timedate1();
void on_bus_acquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);
void on_name_lost(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);
};
}
// Changes the date/time
// Takes the amount of seconds since UNIX epoche and
// Returns true on success, false otherwise
gboolean slack_set_time(gint64 seconds_since_epoch, gboolean relative);
// Returns if the hardware clock is set to local time or not
gboolean slack_get_is_localtime ();
// Returns if NTP is enabled
gboolean slack_get_ntp ();
// Sets NTP
gboolean slack_set_ntp (gboolean);

439
src/timedate.cpp Normal file
View File

@ -0,0 +1,439 @@
/*
* Copyright (C) 2013-2024 Eugen Wissner <belka@caraus.de>
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <sys/timex.h>
#include "timedate.h"
static void slack_method_call(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& sender,
const Glib::ustring& object_path, const Glib::ustring& interface_name, const Glib::ustring& method_name,
const Glib::VariantContainerBase& parameters, const Glib::RefPtr<Gio::DBus::MethodInvocation>& invocation)
{
// Set time zone
if (method_name == "SetTimezone")
{
Glib::Variant<Glib::ustring> timezone;
Glib::Variant<bool> user_interaction;
parameters.get_child(timezone, 0);
parameters.get_child(user_interaction, 1);
try
{
dlackware::timedate::set_timezone(timezone.get(), user_interaction.get());
invocation->return_value(Glib::VariantContainerBase());
}
catch (const std::filesystem::filesystem_error& filesystem_error)
{
invocation->return_error(Gio::DBus::Error{ Gio::DBus::Error::FAILED, filesystem_error.what() });
}
}
else if (method_name == "SetLocalRTC")
{
Glib::Variant<bool> local_rtc;
Glib::Variant<bool> fix_system;
Glib::Variant<bool> user_interaction;
parameters.get_child(local_rtc, 0);
parameters.get_child(fix_system, 1);
parameters.get_child(user_interaction, 2);
try
{
dlackware::timedate::set_local_rtc(local_rtc.get(), fix_system.get(), user_interaction.get());
invocation->return_value(Glib::VariantContainerBase());
}
catch (const std::system_error& error)
{
invocation->return_error(Gio::DBus::Error{ Gio::DBus::Error::FAILED, error.what() });
}
catch (const Glib::SpawnError& spawn_error)
{
invocation->return_error(Gio::DBus::Error{ Gio::DBus::Error::FAILED, spawn_error.what() });
}
}
else if (method_name == "SetTime")
{
Glib::Variant<gint64> usec_utc;
Glib::Variant<bool> relative;
Glib::Variant<bool> user_interaction;
parameters.get_child(usec_utc, 0);
parameters.get_child(relative, 1);
parameters.get_child(user_interaction, 2);
try
{
dlackware::timedate::set_time(usec_utc.get(), relative.get(), user_interaction.get());
invocation->return_value(Glib::VariantContainerBase());
}
catch (const std::system_error& error)
{
invocation->return_error(Gio::DBus::Error{ Gio::DBus::Error::FAILED, error.what() });
}
}
else if (method_name == "SetNTP")
{
Glib::Variant<bool> use_ntp;
Glib::Variant<bool> user_interaction;
parameters.get_child(use_ntp, 0);
parameters.get_child(user_interaction, 1);
// Enable NTP
try
{
dlackware::timedate::set_ntp(use_ntp.get(), user_interaction.get());
invocation->return_value(Glib::VariantContainerBase());
}
catch (const std::filesystem::filesystem_error& filesystem_error)
{
invocation->return_error(Gio::DBus::Error{ Gio::DBus::Error::FAILED, filesystem_error.what() });
}
}
else if (method_name == "ListTimezones")
{
try
{
auto return_tuple = Glib::VariantContainerBase::create_tuple({
Glib::Variant<std::vector<Glib::ustring>>::create(dlackware::timedate::list_timezones())
});
invocation->return_value(return_tuple);
}
catch (const std::exception& exception)
{
Gio::DBus::Error error{ Gio::DBus::Error::FAILED, exception.what() };
invocation->return_error(error);
}
}
}
static void slack_get_property(Glib::VariantBase& result, const Glib::RefPtr<Gio::DBus::Connection>& connection,
const Glib::ustring& sender, const Glib::ustring& object_path, const Glib::ustring& interface_name,
const Glib::ustring& prop_name)
{
if (prop_name == "Timezone")
{
result = Glib::Variant<Glib::ustring>::create(dlackware::timedate::timezone());
}
else if (prop_name == "LocalRTC")
{
result = Glib::Variant<bool>::create(dlackware::timedate::local_rtc());
}
else if (prop_name == "NTP")
{
result = Glib::Variant<bool>::create(dlackware::timedate::ntp());
}
else if (prop_name == "CanNTP")
{
result = Glib::Variant<bool>::create(dlackware::timedate::can_ntp());
}
else if (prop_name == "NTPSynchronized")
{
result = Glib::Variant<bool>::create(dlackware::timedate::ntp_synchronized());
}
else if (prop_name == "TimeUSec")
{
result = Glib::Variant<std::uint64_t>::create(dlackware::timedate::time_usec());
}
else if (prop_name == "RTCTimeUSec")
{
result = Glib::Variant<std::uint64_t>::create(dlackware::timedate::rtc_time_usec());
}
}
namespace dlackware::timedate
{
timedate1::timedate1()
: interface_vtable{ &slack_method_call, &slack_get_property }
{
}
void timedate1::on_name_lost(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name)
{
g_warning("Failed to acquire the service %s.\n", name.data());
exit(1);
}
void timedate1::on_bus_acquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name)
{
Glib::RefPtr<Gio::DBus::NodeInfo> introspection_data;
try
{
auto introspection_xml = Glib::file_get_contents(
(std::filesystem::path(DATADIR_INTERFACES) / dlackware::timedate::bus_name).concat(".xml")
);
introspection_data = Gio::DBus::NodeInfo::create_for_xml(introspection_xml);
}
catch (Glib::Error& slack_err)
{
g_error("Failed to parse D-Bus introspection XML: %s\n", slack_err.what().data());
}
try
{
guint registration_id = connection->register_object(bus_path,
introspection_data->lookup_interface(), this->interface_vtable);
}
catch (Glib::Error& slack_err)
{
g_critical("Failed to register callbacks for the exported object with the D-Bus interface: %s\n",
slack_err.what().data());
}
}
static void list_timezones(const std::string& prefix, std::vector<Glib::ustring>& accumulator)
{
auto zoneinfo_path = std::filesystem::path(zoneinfo_database) / prefix;
for (auto zoneinfo_entry : std::filesystem::directory_iterator(zoneinfo_path))
{
auto new_prefix = prefix + zoneinfo_entry.path().filename().string();
if (zoneinfo_entry.is_directory())
{
list_timezones(new_prefix + "/", accumulator);
}
else if (zoneinfo_entry.is_regular_file() && !zoneinfo_entry.path().has_extension()
&& zoneinfo_entry.path().filename() != "leapseconds")
{
accumulator.emplace_back(new_prefix);
}
}
}
std::vector<Glib::ustring> list_timezones()
{
std::vector<Glib::ustring> result;
list_timezones("", result);
return result;
}
Glib::ustring timezone()
{
std::unique_ptr<gchar[], decltype(&g_free)> zone_copied_from(
g_file_read_link("/etc/localtime", NULL), &g_free);
if (zone_copied_from == nullptr)
{
return nullptr;
}
return Glib::ustring{ zone_copied_from.get() + zoneinfo_database.size() + 1 };
}
void set_timezone(const Glib::ustring& zone, bool)
{
auto zone_file = std::filesystem::path("/usr/share/zoneinfo") / zone.data();
std::error_code ec{};
if (!std::filesystem::is_regular_file(zone_file, ec))
{
throw std::filesystem::filesystem_error("Zone info is not a regular file", zone_file, ec);
}
std::filesystem::path etc_localtime = "/etc/localtime";
std::filesystem::remove(etc_localtime);
std::filesystem::create_symlink(zone_file, etc_localtime);
}
void set_ntp(bool use_ntp, bool)
{
try
{
std::string service_state = use_ntp ? "-u" : "-d";
const auto command_flags = Glib::SpawnFlags::SPAWN_SEARCH_PATH
| Glib::SpawnFlags::SPAWN_STDERR_TO_DEV_NULL | Glib::SpawnFlags::SPAWN_STDERR_TO_DEV_NULL;
int wait_status{-1};
Glib::spawn_sync("", std::vector<std::string>{ "s6-rc", service_state, "change", "ntpd" },
command_flags, {}, nullptr, nullptr, &wait_status);
if (wait_status == 0 || wait_status == 0x300) // Unknown service name in the arguments.
{
return;
}
}
catch (const Glib::SpawnError& spawn_error)
{
}
std::filesystem::perms rc_mode = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write
| std::filesystem::perms::group_read | std::filesystem::perms::others_read;
if (use_ntp)
{
rc_mode |= std::filesystem::perms::owner_exec
| std::filesystem::perms::group_exec | std::filesystem::perms::others_exec;
}
std::error_code ec;
const std::filesystem::path service_path{ "/etc/rc.d/rc.ntpd" };
if (std::filesystem::is_regular_file(service_path, ec))
{
std::filesystem::permissions("/etc/rc.d/rc.ntpd", rc_mode);
}
else
{
throw std::filesystem::filesystem_error("The NTP daemon isn't installed", service_path, ec);
}
}
void set_time(std::int64_t usec_utc, bool relative, bool)
{
timespec ts;
std::chrono::system_clock::duration current_time_since_epoch;
if (relative)
{
current_time_since_epoch = std::chrono::system_clock::now().time_since_epoch();
current_time_since_epoch += std::chrono::microseconds(usec_utc);
}
else
{
current_time_since_epoch = std::chrono::microseconds(usec_utc);
}
auto seconds_since_epoch = std::chrono::floor<std::chrono::seconds>(current_time_since_epoch);
ts.tv_sec = seconds_since_epoch.count();
ts.tv_nsec = std::chrono::duration_cast<std::chrono::microseconds>(
current_time_since_epoch - seconds_since_epoch).count();
if (clock_settime(CLOCK_REALTIME, &ts) == -1)
{
throw std::system_error(errno, std::generic_category());
}
}
void set_local_rtc(bool local_rtc, bool fix_system, bool)
{
std::ofstream hardwareclock{ "/etc/hardwareclock", std::ios::trunc };
std::string hwclock_argument;
hardwareclock << "# /etc/hardwareclock" << std::endl
<< "#" << std::endl
<< "# Tells how the hardware clock time is stored." << std::endl
<< "# You should run timeconfig to edit this file." << std::endl
<< std::endl;
if (local_rtc)
{
hwclock_argument = "--localtime";
hardwareclock << "localtime";
}
else
{
hwclock_argument = "--utc";
hardwareclock << "UTC";
}
hardwareclock << std::endl;
if (!fix_system)
{
Glib::spawn_sync("", std::vector<std::string>{ "/sbin/hwclock", hwclock_argument, "--hctosys" },
Glib::SpawnFlags::SPAWN_STDOUT_TO_DEV_NULL | Glib::SpawnFlags::SPAWN_STDERR_TO_DEV_NULL,
{}, nullptr, nullptr, static_cast<int *>(nullptr));
}
}
bool local_rtc()
{
std::ifstream fh{ "/etc/hardwareclock", std::ios::in };
if (!fh.is_open())
{
return false;
}
std::array<char, 10> time_str; // "localtime" is longer than "UTC" and has 9 letters + \0
while (fh.good())
{
fh.getline(time_str.data(), time_str.size());
fh.clear(fh.rdstate() & (~std::ios_base::failbit));
if (std::strncmp(time_str.data(), "localtime", time_str.size()) == 0)
{
return true;
}
}
return false;
}
bool ntp()
{
return Glib::file_test("/etc/rc.d/rc.ntpd", Glib::FileTest::FILE_TEST_IS_EXECUTABLE);
}
bool can_ntp()
{
std::string standard_output;
int wait_status{-1};
try
{
Glib::spawn_sync("", std::vector<std::string>{ "s6-rc-db", "type", "ntpd" },
Glib::SpawnFlags::SPAWN_SEARCH_PATH | Glib::SpawnFlags::SPAWN_STDERR_TO_DEV_NULL,
{}, &standard_output, nullptr, &wait_status);
}
catch (const Glib::SpawnError& spawn_error)
{
return std::filesystem::exists("/etc/rc.d/rc.ntpd");
}
return wait_status == 0 && standard_output == "bundle\n";
}
bool ntp_synchronized()
{
timex buffer{ .modes = 0 };
int ntp_result = adjtimex(&buffer);
if (ntp_result == -1)
{
throw std::system_error(errno, std::generic_category());
}
return ntp_result != TIME_ERROR && (buffer.status & STA_UNSYNC) == 0;
}
std::uint64_t time_usec()
{
timespec spec;
if (clock_gettime(CLOCK_REALTIME, &spec) == -1)
{
throw std::system_error(errno, std::generic_category());
}
time_t seconds = spec.tv_sec;
tm time;
if (local_rtc())
{
gmtime_r(&seconds, &time);
}
else
{
localtime_r(&seconds, &time);
}
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::seconds(std::mktime(&time)) + std::chrono::nanoseconds(spec.tv_nsec)).count();
}
std::uint64_t rtc_time_usec()
{
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
}
}

81
src/timedate.h Normal file
View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2013-2024 Eugen Wissner <belka@caraus.de>
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <dbus/dbus.h>
#include <glibmm.h>
#include <giomm.h>
#include "resources.h"
namespace dlackware::timedate
{
constexpr const char *bus_path = "/org/freedesktop/timedate1";
constexpr const std::string_view bus_name = "org.freedesktop.timedate1";
constexpr const std::string_view zoneinfo_database = "/usr/share/zoneinfo";
// Returns the system time zone.
Glib::ustring timezone();
// Returns if the hardware clock is set to local time or not
bool local_rtc();
// Returns whether NTP is enabled.
bool ntp();
// Returns whether NTP is available.
bool can_ntp();
// Shows whether the kernel reports the time as synchronized.
bool ntp_synchronized();
// Shows the current time in the RTC.
std::uint64_t rtc_time_usec();
// Shows the current time on the system.
std::uint64_t time_usec();
// Returns the timezones available on the system.
std::vector<Glib::ustring> list_timezones();
// Sets the system time zone to the one passed by the argument
// Throws std::filesystem::filesystem_error.
void set_timezone(const Glib::ustring& zone, bool user_interaction);
// Sets NTP
// Throws std::filesystem::filesystem_error.
void set_ntp(bool use_ntp, bool user_interaction);
// Changes the date/time
// Takes the amount of seconds since UNIX epoche and
// Throws std::system_error.
void set_time(std::int64_t usec_utc, bool relative, bool user_interaction);
// Controls whether the RTC is local time or UTC.
void set_local_rtc(bool local_rtc, bool fix_system, bool user_interaction);
class timedate1
{
const Gio::DBus::InterfaceVTable interface_vtable;
public:
timedate1();
void on_bus_acquired(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);
void on_name_lost(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);
};
}