summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkx <kx@radix.pro>2023-12-21 21:15:15 +0300
committerkx <kx@radix.pro>2023-12-21 21:15:15 +0300
commit938dce1e03ee7b5f16c6955dd055916dd2d515d0 (patch)
treec90cb30e3e16408c369f6b7325ec5da16d204909
parentfcf296f311f6cc515afb16b8c08e4ddd3da807cb (diff)
downloadtimedated-938dce1e03ee7b5f16c6955dd055916dd2d515d0.tar.xz
Version 1.0.0
-rw-r--r--AUTHORS2
-rw-r--r--NEWS8
-rw-r--r--README58
-rw-r--r--dbus/meson.build36
-rw-r--r--dbus/org.freedesktop.timedate1.xml58
-rw-r--r--l10n/_generate-pot.sh25
-rw-r--r--meson.build94
-rw-r--r--meson_options.txt26
-rw-r--r--po/LINGUAS4
-rw-r--r--po/POTFILES.in11
-rw-r--r--po/meson.build3
-rw-r--r--po/ru_RU.utf8.po31
-rw-r--r--po/timedated.pot30
-rw-r--r--src/meson.build78
-rw-r--r--src/org.freedesktop.timedate1.conf.in28
-rw-r--r--src/org.freedesktop.timedate1.policy.in53
-rw-r--r--src/org.freedesktop.timedate1.rules.in11
-rw-r--r--src/org.freedesktop.timedate1.service.in5
-rw-r--r--src/rcl-main.c303
-rw-r--r--src/rcl-ntpd-utils.c163
-rw-r--r--src/rcl-ntpd-utils.h59
-rw-r--r--src/rcl-time-utils.c686
-rw-r--r--src/rcl-time-utils.h111
-rw-r--r--src/rcl-timedate.c898
-rw-r--r--src/rcl-timedate.h76
-rw-r--r--src/rcl-zone-utils.c230
-rw-r--r--src/rcl-zone-utils.h49
27 files changed, 3136 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..2818c9d
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+
+Andrey V.Kosteltsev <kx@radix.pro>
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..1a2fcfa
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,8 @@
+
+Version 1.0.0
+-------------
+Released: 2023-12-21
+
+This is the first release of Timedate Daemon.
+
+
diff --git a/README b/README
new file mode 100644
index 0000000..1bdca4b
--- /dev/null
+++ b/README
@@ -0,0 +1,58 @@
+
+Timedate Daemon
+===============
+
+Requirements:
+------------
+
+ glib-2.0 >= 2.58
+ gobject-2.0 >= 2.58
+ gio-2.0 >= 2.58
+ polkit-gobject-1 >= 123
+ libpcre2-8 >= 10.36
+ dbus >= 1.13.18
+
+TimeDate Daemon is a system service that can be used to control
+the system time and related settings.
+
+This is replacement of systemd service that control the org.freedesktop.timedate1
+D-Bus interface for GNU Linux distributions which has not have a systemd.
+
+You can find specification at:
+ https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.timedate1.html
+
+TimeDate Daemon does not support interactive parameter which can be used to control
+whether polkit should interactively ask the user for authentication credentials if required.
+Instead of interactive way users permissions can be set by Polkit rules in the
+/usr/share/polkit-1/rules.d/org.freedesktop.timedate1.rules file. For example,
+system administrator can add Desktop-users into 'wheel group to give him coresponded
+permissions.
+
+
+How to Build:
+============
+
+ $ meson setup --prefix=/usr . ..
+ $ ninja
+ $ ninja install
+
+
+Supported Distributions:
+=======================
+
+ - Radix cross Linux [https://radix.pro]
+ - Slackware [http://www.slackware.com]
+ (needed litle changes in timeconfig script)
+
+For other systems the special implementation of NTP daemon control should be developed.
+
+
+TODO:
+====
+
+ - timedatectl (simply it can be writen in Bash).
+
+
+LICENSE:
+=======
+ GNU GENERAL PUBLIC LICENSE Version 2, June 1991
diff --git a/dbus/meson.build b/dbus/meson.build
new file mode 100644
index 0000000..f9d5563
--- /dev/null
+++ b/dbus/meson.build
@@ -0,0 +1,36 @@
+
+timedated_dbus_interfaces = [
+ [ 'timedate', 'org.freedesktop.timedate1', 'Daemon' ],
+]
+
+timedated_dbus_headers = []
+timedated_dbus_sources = []
+foreach interface: timedated_dbus_interfaces
+ xml = interface[1] + '.xml'
+ t = gnome.gdbus_codegen('rcl-' + interface[0] + '-generated',
+ sources: xml,
+ autocleanup: 'all',
+ annotations:[ [ interface[1], 'org.gtk.GDBus.C.Name', 'Timedate' + interface[2] ] ],
+ namespace: 'Rcl',
+ object_manager: false,
+ )
+ timedated_dbus_sources += t[0]
+ timedated_dbus_headers += t[1]
+
+ install_data(xml,
+ install_dir: dbusdir / 'interfaces',
+ )
+endforeach
+
+
+timedated_dbus = static_library('libtimedate-dbus',
+ sources: timedated_dbus_sources + timedated_dbus_headers,
+ dependencies: [ gobject_dep, gio_dep, gio_unix_dep ],
+)
+
+timedated_dbus_dep = declare_dependency(
+ link_with: timedated_dbus,
+ include_directories: [ '.' ],
+ sources: timedated_dbus_headers,
+ dependencies: [ gio_unix_dep ]
+)
diff --git a/dbus/org.freedesktop.timedate1.xml b/dbus/org.freedesktop.timedate1.xml
new file mode 100644
index 0000000..c8190cf
--- /dev/null
+++ b/dbus/org.freedesktop.timedate1.xml
@@ -0,0 +1,58 @@
+<!DOCTYPE node PUBLIC
+ "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+ "https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd" [
+ <!ENTITY ERROR_GENERAL "org.freedesktop.timedate1.GeneralError">
+]>
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+
+ <interface name="org.freedesktop.timedate1">
+ <property name="Timezone" type="s" access="read">
+ </property>
+ <property name="LocalRTC" type="b" access="read">
+ </property>
+ <property name="CanNTP" type="b" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
+ </property>
+ <property name="NTP" type="b" access="read">
+ </property>
+ <property name="NTPSynchronized" type="b" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
+ </property>
+ <property name="TimeUSec" type="t" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
+ </property>
+ <property name="RTCTimeUSec" type="t" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
+ </property>
+
+ <property name="DaemonVersion" type="s" access="read">
+ <doc:doc><doc:description><doc:para>
+ Version of the running daemon, e.g. <doc:tt>1.0.0</doc:tt>.
+ </doc:para></doc:description></doc:doc>
+ </property>
+
+ <method name="SetTime">
+ <arg type="x" name="usec_utc" direction="in"/>
+ <arg type="b" name="relative" direction="in"/>
+ <arg type="b" name="interactive" direction="in"/>
+ </method>
+ <method name="SetTimezone">
+ <arg type="s" name="timezone" direction="in"/>
+ <arg type="b" name="interactive" direction="in"/>
+ </method>
+ <method name="SetLocalRTC">
+ <arg type="b" name="local_rtc" direction="in"/>
+ <arg type="b" name="fix_system" direction="in"/>
+ <arg type="b" name="interactive" direction="in"/>
+ </method>
+ <method name="SetNTP">
+ <arg type="b" name="use_ntp" direction="in"/>
+ <arg type="b" name="interactive" direction="in"/>
+ </method>
+ <method name="ListTimezones">
+ <arg type="as" name="timezones" direction="out"/>
+ </method>
+
+
+ </interface>
+</node>
diff --git a/l10n/_generate-pot.sh b/l10n/_generate-pot.sh
new file mode 100644
index 0000000..f667c37
--- /dev/null
+++ b/l10n/_generate-pot.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+#
+# This script generates ../po/timedated.pot file
+# and clean ../po/*.po files. After running this
+# script we have to copy translated strings from
+# ../po/*.po.back files.
+#
+
+cd ..
+xgettext --keyword=N_ --keyword=_ --keyword=Q_:1,2 \
+ --language=C --add-comments --sort-output \
+ --msgid-bugs-address="Andrey V.Kosteltsev <support@radix.pro>" \
+ --package-name=timedated \
+ --package-version=1.0.0 \
+ --copyright-holder="Andrey V.Kosteltsev <kx@radix.pro>" \
+ --default-domain=timedated --output=po/timedated.pot \
+ `find -type f -name "*.c"`
+
+( cd po ;
+ mv ru_RU.utf8.po ru_RU.utf8.po.back
+ msginit --no-translator --no-wrap --locale=ru_RU.utf8 --input=timedated.pot --output=ru_RU.utf8.po
+)
+
+cd l10n
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..70ac2e7
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,94 @@
+project('timedated', 'c',
+ version: '1.0.0',
+ license: 'GPLv2+',
+ default_options: [
+ 'buildtype=debugoptimized',
+ 'warning_level=1',
+ 'c_std=gnu99',
+ ],
+ meson_version: '>= 0.56.0')
+
+soversion = 3
+current = 1
+revision = 0
+libversion = '@0@.@1@.@2@'.format(soversion, current, revision)
+
+gnome = import('gnome')
+i18n = import('i18n')
+
+cc = meson.get_compiler('c')
+
+# directories
+prefix = get_option('prefix')
+bindir = get_option('bindir')
+datadir = get_option('datadir')
+libexecdir = get_option('libexecdir')
+
+# TODO: Get rid of these by including config.h where needed
+add_project_arguments([
+ '-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name()),
+ '-DPACKAGE_VERSION="@0@"'.format(meson.project_version()),
+], language: 'c')
+
+cdata = configuration_data()
+cdata.set_quoted('GETTEXT_PACKAGE', meson.project_name())
+cdata.set_quoted('PACKAGE_VERSION', meson.project_version())
+cdata.set_quoted('VERSION', meson.project_version())
+cdata.set_quoted('PACKAGE_SYSCONF_DIR', get_option('sysconfdir'))
+cdata.set_quoted('HWCLOCK_CONF', get_option('hwclock_conf'))
+cdata.set_quoted('ADJTIME_CONF', get_option('adjtime_conf'))
+cdata.set_quoted('NTPD_CONF', get_option('ntpd_conf'))
+cdata.set_quoted('NTPD_RC', get_option('ntpd_rc'))
+
+glib_min_version = '2.58'
+polkit_min_version = '123'
+pcre_min_version = '10.36'
+
+glib_version_def = 'GLIB_VERSION_@0@_@1@'.format(
+ glib_min_version.split('.')[0], glib_min_version.split('.')[1])
+common_cflags = cc.get_supported_arguments([
+ '-DGLIB_VERSION_MIN_REQUIRED=' + glib_version_def,
+ '-DGLIB_VERSION_MAX_ALLOWED=' + glib_version_def,
+])
+add_project_arguments(common_cflags, language: 'c')
+
+
+glib_dep = dependency('glib-2.0', version: '>=' + glib_min_version)
+gobject_dep = dependency('gobject-2.0', version: '>=' + glib_min_version)
+gio_dep = dependency('gio-2.0', version: '>=' + glib_min_version)
+gio_unix_dep = dependency('gio-unix-2.0', version: '>=' + glib_min_version)
+pcre_dep = dependency('libpcre2-8', version: '>=' + pcre_min_version)
+polkit_dep = dependency('polkit-gobject-1', version: '>=' + polkit_min_version)
+m_dep = cc.find_library('m', required: true)
+
+dbusdir = join_paths(datadir, 'dbus-1')
+polkitactionsdir = join_paths(datadir, 'polkit-1', 'actions')
+polkitrulesdir = join_paths(datadir, 'polkit-1', 'rules.d')
+
+# Generate configuration file
+config_h = configure_file(output: 'config.h', configuration: cdata)
+
+subdir('po')
+subdir('dbus')
+subdir('src')
+
+output = []
+output += 'timedated ' + meson.project_version()
+output += 'System Paths'
+output += ' prefix: ' + get_option('prefix')
+output += ' libdir: ' + get_option('libdir')
+output += ' libexecdir: ' + get_option('prefix') / get_option('libexecdir')
+output += ' bindir: ' + get_option('prefix') / get_option('bindir')
+output += ' sbindir: ' + get_option('prefix') / get_option('sbindir')
+output += ' datadir: ' + get_option('prefix') / get_option('datadir')
+output += ' sysconfdir: ' + get_option('sysconfdir')
+output += ' localstatedir: ' + get_option('prefix') / get_option('localstatedir')
+
+output += '\nFeatures'
+output += ' Priviledged group: ' + get_option('privileged-group')
+output += ' Hardware clock config: ' + get_option('hwclock_conf')
+output += ' Adjtime config: ' + get_option('adjtime_conf')
+output += ' NTP daemon config: ' + get_option('ntpd_conf')
+output += ' NTPd start/stop script: ' + get_option('ntpd_rc')
+
+message('\n'+'\n'.join(output)+'\n')
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..d581a2d
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,26 @@
+
+option('privileged-group',
+ type : 'string',
+ value: 'wheel',
+ description : 'The name of the group whose members have administrator privileges')
+
+option('hwclock_conf',
+ type : 'string',
+ value: '/etc/hardwareclock',
+ description : 'Hardware clock time config file')
+
+option('adjtime_conf',
+ type : 'string',
+ value: '/etc/adjtime',
+ description : 'Adjtimex config file')
+
+option('ntpd_conf',
+ type : 'string',
+ value: '/etc/ntp.conf',
+ description : 'NTP daemon config file')
+
+option('ntpd_rc',
+ type : 'string',
+ value: '/etc/rc.d/rc.ntpd',
+ description : 'NTP daemon start/stop script')
+
diff --git a/po/LINGUAS b/po/LINGUAS
new file mode 100644
index 0000000..1df89d3
--- /dev/null
+++ b/po/LINGUAS
@@ -0,0 +1,4 @@
+#
+# Please keep this list sorted alphabetically:
+#
+ru_RU.utf8
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 0000000..a1510d1
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,11 @@
+#
+# List of source files containing translatable strings.
+# Please keep this file sorted alphabetically:
+#
+
+# timedated source files:
+src/rcl-main.c
+src/rcl-ntpd-utils.c
+src/rcl-time-utils.c
+src/rcl-timedate.c
+src/rcl-zone-utils.c
diff --git a/po/meson.build b/po/meson.build
new file mode 100644
index 0000000..111f515
--- /dev/null
+++ b/po/meson.build
@@ -0,0 +1,3 @@
+
+i18n = import('i18n')
+i18n.gettext(meson.project_name(), preset : 'glib')
diff --git a/po/ru_RU.utf8.po b/po/ru_RU.utf8.po
new file mode 100644
index 0000000..5294d28
--- /dev/null
+++ b/po/ru_RU.utf8.po
@@ -0,0 +1,31 @@
+# Russian translations for timedated package
+# Английские переводы для пакета timedated.
+# Copyright (C) 2023 Andrey V.Kosteltsev <kx@radix.pro>
+# This file is distributed under the same license as the timedated package.
+# Automatically generated, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: timedated 1.0.0\n"
+"Report-Msgid-Bugs-To: Andrey V.Kosteltsev <support@radix.pro>\n"
+"POT-Creation-Date: 2023-12-21 15:59+0300\n"
+"PO-Revision-Date: 2023-12-21 15:59+0300\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: src/rcl-main.c:202
+msgid "Enable debugging (implies --verbose)"
+msgstr "Отладка разрешена (режим --verbose)"
+
+#: src/rcl-main.c:200
+msgid "Replace the old daemon"
+msgstr "Заменить работающий демон"
+
+#: src/rcl-main.c:201
+msgid "Show extra debugging information"
+msgstr "Показывать расширенную отладочную информацию"
diff --git a/po/timedated.pot b/po/timedated.pot
new file mode 100644
index 0000000..78ff841
--- /dev/null
+++ b/po/timedated.pot
@@ -0,0 +1,30 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Andrey V.Kosteltsev <kx@radix.pro>
+# This file is distributed under the same license as the timedated package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: timedated 1.0.0\n"
+"Report-Msgid-Bugs-To: Andrey V.Kosteltsev <support@radix.pro>\n"
+"POT-Creation-Date: 2023-12-21 15:59+0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/rcl-main.c:202
+msgid "Enable debugging (implies --verbose)"
+msgstr ""
+
+#: src/rcl-main.c:200
+msgid "Replace the old daemon"
+msgstr ""
+
+#: src/rcl-main.c:201
+msgid "Show extra debugging information"
+msgstr ""
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..2409251
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,78 @@
+
+timedated_deps = declare_dependency(
+ include_directories: [
+ include_directories('.'),
+ include_directories('..'),
+ include_directories('../dbus'),
+ ],
+ dependencies: [
+ m_dep, glib_dep, gobject_dep, gio_dep, gio_unix_dep, pcre_dep, polkit_dep, timedated_dbus_dep
+ ],
+ compile_args: [
+ '-DUP_COMPILATION',
+ ],
+)
+
+timedated_private = static_library('timedated-private',
+ sources: [
+ 'rcl-timedate.h',
+ 'rcl-timedate.c',
+ 'rcl-time-utils.h',
+ 'rcl-time-utils.c',
+ 'rcl-ntpd-utils.h',
+ 'rcl-ntpd-utils.c',
+ 'rcl-zone-utils.h',
+ 'rcl-zone-utils.c',
+ ],
+ dependencies: [ timedated_deps ],
+ c_args: [ '-DG_LOG_DOMAIN="Timedate"' ],
+)
+
+timedated = executable('timedated',
+ sources: [
+ 'rcl-main.c',
+ ],
+ dependencies: timedated_deps,
+ link_with: [ timedated_private ],
+ gnu_symbol_visibility: 'hidden',
+ install: true,
+ install_dir: get_option('prefix') / get_option('libexecdir'),
+ c_args: [ '-DG_LOG_DOMAIN="Timedate"' ],
+)
+
+
+#####################
+# Data/Config files:
+#####################
+
+cdata = configuration_data()
+cdata.set('libexecdir', get_option('prefix') / get_option('libexecdir'))
+cdata.set('PRIVILEDGED_GROUP', get_option('privileged-group'))
+
+configure_file(
+ input: 'org.freedesktop.timedate1.service.in',
+ output: 'org.freedesktop.timedate1.service',
+ install_dir: dbusdir / 'system-services',
+ configuration: cdata,
+)
+
+configure_file(
+ input: 'org.freedesktop.timedate1.conf.in',
+ output: 'org.freedesktop.timedate1.conf',
+ install_dir: dbusdir / 'system.d',
+ configuration: cdata,
+)
+
+configure_file(
+ input: 'org.freedesktop.timedate1.policy.in',
+ output: 'org.freedesktop.timedate1.policy',
+ install_dir: polkitactionsdir,
+ configuration: cdata,
+)
+
+configure_file(
+ input: 'org.freedesktop.timedate1.rules.in',
+ output: 'org.freedesktop.timedate1.rules',
+ install_dir: polkitrulesdir,
+ configuration: cdata,
+)
diff --git a/src/org.freedesktop.timedate1.conf.in b/src/org.freedesktop.timedate1.conf.in
new file mode 100644
index 0000000..948b965
--- /dev/null
+++ b/src/org.freedesktop.timedate1.conf.in
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*- XML -*-->
+
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+
+ <policy user="root">
+ <allow own="org.freedesktop.timedate1"/>
+ <allow send_destination="org.freedesktop.timedate1"/>
+ <allow receive_sender="org.freedesktop.timedate1"/>
+ </policy>
+
+ <policy context="default">
+ <!-- Basic D-Bus API stuff -->
+ <allow send_destination="org.freedesktop.timedate1"
+ send_interface="org.freedesktop.DBus.Introspectable"/>
+ <allow send_destination="org.freedesktop.timedate1"
+ send_interface="org.freedesktop.DBus.Properties"/>
+ <allow send_destination="org.freedesktop.timedate1"
+ send_interface="org.freedesktop.DBus.ObjectManager"/>
+
+ <allow send_destination="org.freedesktop.timedate1"/>
+ <allow receive_sender="org.freedesktop.timedate1"/>
+ </policy>
+
+</busconfig>
diff --git a/src/org.freedesktop.timedate1.policy.in b/src/org.freedesktop.timedate1.policy.in
new file mode 100644
index 0000000..2efb9bf
--- /dev/null
+++ b/src/org.freedesktop.timedate1.policy.in
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*- XML -*-->
+
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<policyconfig>
+
+<vendor>The Radix cross Linux Project</vendor>
+<vendor_url>https://radix.pro</vendor_url>
+
+ <action id="org.freedesktop.timedate1.set-time">
+ <description>Set system time</description>
+ <message>Authentication is required to set the system time.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.timedate1.set-timezone org.freedesktop.timedate1.set-ntp</annotate>
+ </action>
+
+ <action id="org.freedesktop.timedate1.set-timezone">
+ <description>Set system timezone</description>
+ <message>Authentication is required to set the system timezone.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.timedate1.set-local-rtc">
+ <description>Set RTC to local timezone or UTC</description>
+ <message>Authentication is required to control whether the RTC stores the local or UTC time.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.freedesktop.timedate1.set-ntp">
+ <description>Turn NTP synchronization on or off</description>
+ <message>Authentication is required to control whether network time synchronization shall be enabled.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+</policyconfig>
diff --git a/src/org.freedesktop.timedate1.rules.in b/src/org.freedesktop.timedate1.rules.in
new file mode 100644
index 0000000..50aa694
--- /dev/null
+++ b/src/org.freedesktop.timedate1.rules.in
@@ -0,0 +1,11 @@
+polkit.addRule(
+ function(action, subject) {
+ if ( (action.id == "org.freedesktop.timedate1.set-time" ||
+ action.id == "org.freedesktop.timedate1.set-timezone" ||
+ action.id == "org.freedesktop.timedate1.set-local-rtc" ||
+ action.id == "org.freedesktop.timedate1.set-ntp")
+ && subject.isInGroup("@PRIVILEDGED_GROUP@") ) {
+ return polkit.Result.YES;
+ }
+ }
+);
diff --git a/src/org.freedesktop.timedate1.service.in b/src/org.freedesktop.timedate1.service.in
new file mode 100644
index 0000000..5ee2383
--- /dev/null
+++ b/src/org.freedesktop.timedate1.service.in
@@ -0,0 +1,5 @@
+[D-BUS Service]
+Name=org.freedesktop.timedate1
+Exec=@libexecdir@/timedated
+User=root
+
diff --git a/src/rcl-main.c b/src/rcl-main.c
new file mode 100644
index 0000000..8a1a7e4
--- /dev/null
+++ b/src/rcl-main.c
@@ -0,0 +1,303 @@
+
+/*
+ * Copyright (C) 2023 Andrey V.Kosteltsev <kx@radix.pro>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib-unix.h>
+#include <glib-object.h>
+#include <glib/gi18n-lib.h>
+#include <locale.h>
+
+
+#include "rcl-timedate.h"
+
+#define TIMEDATE_SERVICE_NAME "org.freedesktop.timedate1"
+
+typedef struct RclState
+{
+ RclDaemon *daemon;
+ GMainLoop *loop;
+} RclState;
+
+static void
+rcl_state_free( RclState *state )
+{
+ rcl_daemon_shutdown( state->daemon );
+
+ g_clear_object( &state->daemon );
+ g_clear_pointer( &state->loop, g_main_loop_unref );
+
+ g_free( state );
+}
+
+static RclState *
+rcl_state_new( void )
+{
+ RclState *state = g_new0( RclState, 1 );
+
+ state->daemon = rcl_daemon_new();
+ state->loop = g_main_loop_new( NULL, FALSE );
+
+ return state;
+}
+
+/************************
+ rcl_main_bus_acquired:
+ */
+static void
+rcl_main_bus_acquired( GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data )
+{
+ RclState *state = user_data;
+
+ if( !rcl_daemon_startup( state->daemon, connection ) )
+ {
+ g_warning( "Could not startup daemon" );
+ g_main_loop_quit( state->loop );
+ }
+}
+
+/*************************
+ rcl_main_name_acquired:
+ */
+static void
+rcl_main_name_acquired( GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_debug( "Acquired the name %s", name );
+}
+
+/*********************
+ rcl_main_name_lost:
+ */
+static void
+rcl_main_name_lost( GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data )
+{
+ RclState *state = user_data;
+ g_debug( "Name lost, exiting" );
+ g_main_loop_quit( state->loop );
+}
+
+/*********************
+ rcl_main_sigint_cb:
+ */
+static gboolean
+rcl_main_sigint_cb( gpointer user_data )
+{
+ RclState *state = user_data;
+ g_debug( "Handling SIGINT" );
+ g_main_loop_quit( state->loop );
+ return FALSE;
+}
+
+static gboolean
+rcl_main_sigterm_cb( gpointer user_data )
+{
+ RclState *state = user_data;
+ g_debug( "Handling SIGTERM" );
+ g_main_loop_quit( state->loop );
+ return FALSE;
+}
+
+/*************************
+ rcl_main_log_ignore_cb:
+ */
+static void
+rcl_main_log_ignore_cb( const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data )
+{
+}
+
+/**************************
+ rcl_main_log_handler_cb:
+ */
+static void
+rcl_main_log_handler_cb( const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data )
+{
+ gchar str_time[255];
+ time_t the_time;
+
+ /* header always in green */
+ time( &the_time );
+ strftime( str_time, 254, "%H:%M:%S", localtime( &the_time ) );
+ g_print( "%c[%dmTI:%s\t", 0x1B, 32, str_time );
+
+ /* critical is also in red */
+ if( log_level == G_LOG_LEVEL_CRITICAL ||
+ log_level == G_LOG_LEVEL_WARNING ||
+ log_level == G_LOG_LEVEL_ERROR )
+ {
+ g_print( "%c[%dm%s\n%c[%dm", 0x1B, 31, message, 0x1B, 0 );
+ }
+ else
+ {
+ /* debug in blue */
+ g_print( "%c[%dm%s\n%c[%dm", 0x1B, 34, message, 0x1B, 0 );
+ }
+}
+
+static gboolean
+rcl_main_timed_cb( gpointer user_data )
+{
+ RclState *state = (RclState *)user_data;
+ RclTimedateDaemon *object = RCL_TIMEDATE_DAEMON( state->daemon );
+
+ /* g_debug( "Synchronize properties" ); */
+
+ rcl_daemon_sync_dbus_properties( object );
+
+ return TRUE;
+}
+
+/*******
+ main:
+ */
+gint main( gint argc, gchar **argv )
+{
+ GError *error = NULL;
+ GOptionContext *context;
+ guint timer_id = 0;
+ gboolean debug = FALSE;
+ gboolean verbose = FALSE;
+ RclState *state;
+ GBusNameOwnerFlags bus_flags;
+ gboolean replace = FALSE;
+
+ const GOptionEntry options[] = {
+ { "replace", 'r', 0, G_OPTION_ARG_NONE, &replace, _("Replace the old daemon"), NULL },
+ { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, _("Show extra debugging information"), NULL },
+ { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, _("Enable debugging (implies --verbose)"), NULL },
+ { NULL }
+ };
+
+#if !defined(GLIB_VERSION_2_36)
+ g_type_init();
+#endif
+ setlocale( LC_ALL, "" );
+
+ /* NOTE: do not use bind_textdomain() when use gi18n-lib.h */
+ bind_textdomain_codeset( GETTEXT_PACKAGE, "UTF-8" );
+ textdomain( GETTEXT_PACKAGE );
+
+ context = g_option_context_new( "" );
+ g_option_context_add_main_entries( context, options, NULL );
+ if( !g_option_context_parse (context, &argc, &argv, &error ) )
+ {
+ g_warning( "Failed to parse command-line options: %s", error->message );
+ g_error_free( error );
+ return 1;
+ }
+ g_option_context_free( context );
+
+ if( debug )
+ verbose = TRUE;
+
+ /* verbose? */
+ if( verbose )
+ {
+ g_log_set_fatal_mask( NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL );
+ g_log_set_handler( G_LOG_DOMAIN,
+ G_LOG_LEVEL_ERROR |
+ G_LOG_LEVEL_CRITICAL |
+ G_LOG_LEVEL_DEBUG |
+ G_LOG_LEVEL_WARNING,
+ rcl_main_log_handler_cb, NULL );
+ g_log_set_handler( "Timedate-Linux",
+ G_LOG_LEVEL_ERROR |
+ G_LOG_LEVEL_CRITICAL |
+ G_LOG_LEVEL_DEBUG |
+ G_LOG_LEVEL_WARNING,
+ rcl_main_log_handler_cb, NULL );
+ }
+ else
+ {
+ /* hide all debugging */
+ g_log_set_fatal_mask( NULL, G_LOG_LEVEL_ERROR );
+ g_log_set_handler( G_LOG_DOMAIN,
+ G_LOG_LEVEL_DEBUG,
+ rcl_main_log_ignore_cb,
+ NULL );
+ g_log_set_handler( "Timedate-Linux",
+ G_LOG_LEVEL_DEBUG,
+ rcl_main_log_ignore_cb,
+ NULL );
+ }
+
+ /* initialize state */
+ state = rcl_state_new();
+ rcl_daemon_set_debug( state->daemon, debug );
+
+ /* do stuff on ctrl-c */
+ g_unix_signal_add_full( G_PRIORITY_DEFAULT,
+ SIGINT,
+ rcl_main_sigint_cb,
+ state,
+ NULL );
+
+ /* Clean shutdown on SIGTERM */
+ g_unix_signal_add_full( G_PRIORITY_DEFAULT,
+ SIGTERM,
+ rcl_main_sigterm_cb,
+ state,
+ NULL );
+
+ /* acquire name */
+ bus_flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
+ if( replace )
+ bus_flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
+
+ g_bus_own_name( G_BUS_TYPE_SYSTEM,
+ TIMEDATE_SERVICE_NAME,
+ bus_flags,
+ rcl_main_bus_acquired,
+ rcl_main_name_acquired,
+ rcl_main_name_lost,
+ state, NULL );
+
+ g_debug( "Starting timedated version %s", PACKAGE_VERSION );
+
+ /* in msec = 1/1000 sec */
+ timer_id = g_timeout_add( 1000, (GSourceFunc)rcl_main_timed_cb, (gpointer)state );
+ g_source_set_name_by_id( timer_id, "[timedate] rcl_main_timed_cb" );
+
+
+ /* wait for input or timeout */
+ g_main_loop_run( state->loop );
+ rcl_state_free( state );
+
+ return 0;
+}
diff --git a/src/rcl-ntpd-utils.c b/src/rcl-ntpd-utils.c
new file mode 100644
index 0000000..ff4eb96
--- /dev/null
+++ b/src/rcl-ntpd-utils.c
@@ -0,0 +1,163 @@
+
+/*
+ * Copyright (C) 2023 Andrey V.Kosteltsev <kx@radix.pro>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "rcl-ntpd-utils.h"
+
+static gboolean exec_cmd( const gchar *cmd )
+{
+ int exit_status;
+ GError *error = NULL;
+ gboolean ret = TRUE;
+
+ if( !cmd || *cmd == '\0' ) return FALSE;
+
+ if( !g_spawn_command_line_sync( cmd, NULL, NULL, &exit_status, &error ) )
+ {
+ g_error_free( error );
+ ret = FALSE;
+ }
+ g_free( (gpointer)cmd );
+
+ if( exit_status != 0 )
+ ret = FALSE;
+
+ return ret;
+}
+
+gboolean ntp_daemon_installed( void )
+{
+ if( g_file_test( NTPD_CONF, G_FILE_TEST_EXISTS ) &&
+ g_file_test( NTPD_RC, G_FILE_TEST_EXISTS ) )
+ return TRUE;
+ else
+ return FALSE;
+}
+
+gboolean ntp_daemon_enabled( void )
+{
+ if( g_file_test( NTPD_CONF, G_FILE_TEST_EXISTS ) &&
+ g_file_test( NTPD_RC, G_FILE_TEST_EXISTS ) &&
+ g_file_test( NTPD_RC, G_FILE_TEST_IS_EXECUTABLE ) )
+ return TRUE;
+ else
+ return FALSE;
+}
+
+gboolean ntp_daemon_status( void )
+{
+ gchar *cmd;
+
+ if( g_file_test( NTPD_RC, G_FILE_TEST_EXISTS ) &&
+ g_file_test( NTPD_RC, G_FILE_TEST_IS_EXECUTABLE ) )
+ {
+ cmd = g_strconcat( NTPD_RC, " status", NULL );
+ if( !exec_cmd( (const gchar *)cmd ) )
+ return FALSE;
+ else
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean stop_ntp_daemon( void )
+{
+ if( ntp_daemon_enabled() && !ntp_daemon_status() )
+ return TRUE;
+
+ if( ntp_daemon_enabled() )
+ {
+ gchar *cmd;
+ cmd = g_strconcat( NTPD_RC, " stop", NULL );
+ if( !exec_cmd( (const gchar *)cmd ) )
+ return FALSE;
+ else
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean start_ntp_daemon( void )
+{
+ if( ntp_daemon_enabled() && ntp_daemon_status() )
+ return TRUE;
+
+ if( ntp_daemon_enabled() )
+ {
+ gchar *cmd;
+ cmd = g_strconcat( NTPD_RC, " start", NULL );
+ if( !exec_cmd( (const gchar *)cmd ) )
+ return FALSE;
+ else
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean disable_ntp_daemon( void )
+{
+ gchar *cmd;
+
+ if( !ntp_daemon_enabled() )
+ return TRUE;
+
+ if( ntp_daemon_status() )
+ (void)stop_ntp_daemon();
+
+ if( g_file_test( NTPD_RC, G_FILE_TEST_EXISTS ) &&
+ !g_file_test( NTPD_RC, G_FILE_TEST_IS_EXECUTABLE ) )
+ return TRUE;
+
+ if( g_file_test( NTPD_RC, G_FILE_TEST_EXISTS ) )
+ {
+ cmd = g_strconcat( "chmod 0644 ", NTPD_RC, NULL );
+ if( !exec_cmd( (const gchar *)cmd ) )
+ return FALSE;
+ else
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+gboolean enable_ntp_daemon( void )
+{
+ gchar *cmd;
+
+ if( ntp_daemon_enabled() )
+ return TRUE;
+
+ if( g_file_test( NTPD_RC, G_FILE_TEST_EXISTS ) &&
+ g_file_test( NTPD_RC, G_FILE_TEST_IS_EXECUTABLE ) )
+ return TRUE;
+
+ if( g_file_test( NTPD_RC, G_FILE_TEST_EXISTS ) )
+ {
+ cmd = g_strconcat( "chmod 0755 ", NTPD_RC, NULL );
+ if( !exec_cmd( (const gchar *)cmd ) )
+ return FALSE;
+ else
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
diff --git a/src/rcl-ntpd-utils.h b/src/rcl-ntpd-utils.h
new file mode 100644
index 0000000..ab4f86f
--- /dev/null
+++ b/src/rcl-ntpd-utils.h
@@ -0,0 +1,59 @@
+
+/*
+ * Copyright (C) 2023 Andrey V.Kosteltsev <kx@radix.pro>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __RCL_NTPD_UTILS_H__
+#define __RCL_NTPD_UTILS_H__
+
+#include "config.h"
+
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <linux/rtc.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <locale.h>
+
+
+#if !defined( NTPD_CONF )
+#define NTPD_CONF "/etc/ntp.conf"
+#endif
+
+#if !defined( NTPD_RC )
+#define NTPD_RC "/etc/rc.d/rc.ntpd"
+#endif
+
+extern gboolean ntp_daemon_installed ( void );
+extern gboolean ntp_daemon_enabled ( void );
+extern gboolean ntp_daemon_status ( void );
+extern gboolean stop_ntp_daemon ( void );
+extern gboolean start_ntp_daemon ( void );
+extern gboolean disable_ntp_daemon ( void );
+extern gboolean enable_ntp_daemon ( void );
+
+
+#endif /* __RCL_NTPD_UTILS_H__ */
diff --git a/src/rcl-time-utils.c b/src/rcl-time-utils.c
new file mode 100644
index 0000000..37fae68
--- /dev/null
+++ b/src/rcl-time-utils.c
@@ -0,0 +1,686 @@
+
+/*
+ * Copyright (C) 2023 Andrey V.Kosteltsev <kx@radix.pro>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "rcl-time-utils.h"
+
+
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#endif
+
+
+static const char *rtc_dev_name;
+static int rtc_dev_fd = -1;
+
+/***************************************************************
+ Static RTC functions:
+ */
+static void close_rtc( void )
+{
+ if( rtc_dev_fd != 1 )
+ close( rtc_dev_fd );
+ rtc_dev_fd = -1;
+}
+
+static int open_rtc( void )
+{
+ static const char *fls[] = {
+ "/dev/rtc0",
+ "/dev/rtc",
+ "/dev/misc/rtc"
+ };
+ size_t i;
+
+ if( rtc_dev_fd != -1 )
+ return rtc_dev_fd;
+
+ if( rtc_dev_name )
+ {
+ rtc_dev_fd = open( rtc_dev_name, O_RDONLY );
+ }
+ else
+ {
+ for( i = 0; i < ARRAY_SIZE(fls); ++i )
+ {
+ rtc_dev_fd = open( fls[i], O_RDONLY );
+
+ if( rtc_dev_fd < 0 )
+ {
+ if( errno == ENOENT || errno == ENODEV )
+ continue;
+ }
+ rtc_dev_name = fls[i];
+ break;
+ }
+ if( rtc_dev_fd < 0 )
+ rtc_dev_name = *fls; /* default for error messages */
+ }
+
+ if( rtc_dev_fd != -1 )
+ atexit( close_rtc );
+
+ return rtc_dev_fd;
+}
+
+/***************************************************************
+ Static functions:
+ */
+static gboolean
+symlink_atomic( const char *target, const char *link_path )
+{
+ if( symlink( target, link_path ) == 0 )
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static gchar *skip_root( const gchar *path )
+{
+ if( !path || *path == '\0' ) return NULL;
+
+ return (gchar *)(path + 1);
+}
+
+/***************************************************************
+ Clock functions:
+ */
+gboolean ntp_synchronized( void )
+{
+ struct timex txc = {};
+
+ if( adjtimex( &txc ) < 0 )
+ return FALSE;
+
+ /*
+ Consider the system clock synchronized if the reported maximum error is
+ smaller than the maximum value (16 seconds). Ignore the STA_UNSYNC flag
+ as it may have been set to prevent the kernel from touching the RTC.
+ */
+ /* return txc.maxerror < 16000000; */
+ return txc.maxerror < 32000000;
+}
+
+
+guint64 timespec_load( const struct timespec *ts )
+{
+ if( !ts )
+ return (guint64)0;
+
+ if( ts->tv_sec < 0 || ts->tv_nsec < 0 )
+ return USEC_INFINITY;
+
+ if( (guint64)ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC )
+ return USEC_INFINITY;
+
+ return
+ (guint64)ts->tv_sec * USEC_PER_SEC +
+ (guint64)ts->tv_nsec / NSEC_PER_USEC;
+}
+
+guint64 timespec_load_nsec( const struct timespec *ts )
+{
+ if( !ts )
+ return (guint64)0;
+
+ if( ts->tv_sec < 0 || ts->tv_nsec < 0 )
+ return NSEC_INFINITY;
+
+ if( (guint64)ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC )
+ return NSEC_INFINITY;
+
+ return (guint64) ts->tv_sec * NSEC_PER_SEC + (guint64)ts->tv_nsec;
+}
+
+struct timespec *timespec_store( struct timespec *ts, guint64 u )
+{
+ if( !ts )
+ return NULL;
+
+ if( u == USEC_INFINITY ||
+ u / USEC_PER_SEC >= TIME_T_MAX )
+ {
+ ts->tv_sec = (time_t) -1;
+ ts->tv_nsec = -1L;
+ return ts;
+ }
+
+ ts->tv_sec = (time_t)(u / USEC_PER_SEC);
+ ts->tv_nsec = (long)((u % USEC_PER_SEC) * NSEC_PER_USEC);
+
+ return ts;
+}
+
+struct timespec *timespec_store_nsec( struct timespec *ts, guint64 n )
+{
+ if( !ts )
+ return NULL;
+
+ if( n == NSEC_INFINITY ||
+ n / NSEC_PER_SEC >= TIME_T_MAX )
+ {
+ ts->tv_sec = (time_t) -1;
+ ts->tv_nsec = -1L;
+ return ts;
+ }
+
+ ts->tv_sec = (time_t)(n / NSEC_PER_SEC);
+ ts->tv_nsec = (long)(n % NSEC_PER_SEC);
+
+ return ts;
+}
+
+guint64 timeval_load( const struct timeval *tv )
+{
+ if( !tv )
+ return (guint64)0;
+
+ if( tv->tv_sec < 0 || tv->tv_usec < 0 )
+ return USEC_INFINITY;
+
+ if( (guint64)tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC )
+ return USEC_INFINITY;
+
+ return
+ (guint64) tv->tv_sec * USEC_PER_SEC +
+ (guint64) tv->tv_usec;
+}
+
+struct timeval *timeval_store( struct timeval *tv, guint64 u )
+{
+ if( !tv )
+ return NULL;
+
+ if( u == USEC_INFINITY ||
+ u / USEC_PER_SEC > TIME_T_MAX )
+ {
+ tv->tv_sec = (time_t) -1;
+ tv->tv_usec = (suseconds_t) -1;
+ }
+ else
+ {
+ tv->tv_sec = (time_t)(u / USEC_PER_SEC);
+ tv->tv_usec = (suseconds_t)(u % USEC_PER_SEC);
+ }
+
+ return tv;
+}
+
+guint64 now( clockid_t clock_id )
+{
+ struct timespec ts;
+
+ if( clock_gettime( CLOCK_REALTIME, &ts ) != 0 )
+ return (guint64)0;
+
+ return (guint64)timespec_load( &ts );
+}
+
+guint64 now_nsec( clockid_t clock_id )
+{
+ struct timespec ts;
+
+ if( clock_gettime( CLOCK_REALTIME, &ts ) != 0 )
+ return (guint64)0;
+
+ return timespec_load_nsec( &ts );
+}
+
+
+struct tm *localtime_or_gmtime_r( const time_t *t, struct tm *tm, gboolean utc )
+{
+ if( !t || !tm ) return NULL;
+
+ return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
+}
+
+time_t mktime_or_timegm( struct tm *tm, gboolean utc )
+{
+ if( !tm ) return -1;
+
+ return utc ? timegm(tm) : mktime(tm);
+}
+
+gboolean clock_get_hwclock( struct tm *tm )
+{
+ int rc = -1;
+ gchar *ioctlname;
+
+ if( !tm )
+ return FALSE;
+
+ (void)open_rtc();
+
+ if( rtc_dev_fd < 0 )
+ {
+ g_debug( "error: Canot open '%s' device", rtc_dev_name );
+ return FALSE;
+ }
+
+ ioctlname = "RTC_RD_TIME";
+ rc = ioctl( rtc_dev_fd, RTC_RD_TIME, tm );
+ if( rc == -1 )
+ {
+ g_debug( "warning: ioctl(%s) to '%s' to read the time failed", ioctlname, rtc_dev_name );
+ }
+
+ tm->tm_isdst = -1; /* don't know whether it's dst */
+
+ close_rtc();
+
+ return TRUE;
+}
+
+gboolean clock_set_hwclock( const struct tm *tm )
+{
+ int rc = -1;
+ gchar *ioctlname;
+
+ (void)open_rtc();
+
+ if( rtc_dev_fd < 0 )
+ {
+ g_debug( "error: Canot open '%s' device", rtc_dev_name );
+ return FALSE;
+ }
+
+ ioctlname = "RTC_SET_TIME";
+ rc = ioctl( rtc_dev_fd, RTC_SET_TIME, tm );
+ if( rc == -1 )
+ {
+ g_debug( "warning: ioctl(%s) to '%s' to set the time failed", ioctlname, rtc_dev_name );
+ }
+
+ close_rtc();
+
+ return TRUE;
+}
+
+gboolean clock_set_timezone( int *ret_minutesdelta )
+{
+ struct timespec ts;
+ struct tm tm;
+ int minutesdelta;
+ struct timezone tz;
+
+ if( clock_gettime(CLOCK_REALTIME, &ts) != 0 )
+ return FALSE;
+
+ if( !localtime_r( &ts.tv_sec, &tm ) )
+ return FALSE;
+
+ minutesdelta = tm.tm_gmtoff / 60;
+
+ tz = (struct timezone)
+ {
+ .tz_minuteswest = -minutesdelta,
+ .tz_dsttime = 0, /* DST_NONE */
+ };
+
+ /* If the RTC does not run in UTC but in local time, the very first call to settimeofday() will set
+ * the kernel's timezone and will warp the system clock, so that it runs in UTC instead of the local
+ * time we have read from the RTC. */
+ if( settimeofday( NULL, &tz ) < 0 )
+ return FALSE;
+
+ if( ret_minutesdelta )
+ *ret_minutesdelta = minutesdelta;
+
+ return TRUE;
+}
+
+
+/***************************************************************
+ Timezone functions:
+ */
+gboolean timezone_is_valid( const gchar *name )
+{
+ const gchar *p, *fname;
+ int fd;
+ gchar buf[4];
+ gboolean slash = FALSE;
+ gboolean ret = TRUE;
+
+ if( !name || *name == '\0' ) return FALSE;
+
+ /*
+ Always accept "UTC" as valid timezone,
+ since it's the fallback, even if user
+ has no timezones installed.
+ */
+ if( g_strcmp0( name, "UTC" ) == 0 ) return TRUE;
+
+ if( name[0] == '/' ) return FALSE;
+ for( p = (gchar *)name; *p; ++p )
+ {
+ if( !g_ascii_isdigit( *p ) && !g_ascii_isalpha( *p ) && ( *p != '-' && *p != '_' && *p != '+' && *p != '/' ) )
+ return FALSE;
+ if( *p == '/' )
+ {
+ if( slash ) return FALSE;
+ slash = TRUE;
+ }
+ else
+ {
+ slash = FALSE;
+ }
+ }
+
+ if( slash )
+ return FALSE;
+
+ if( p - name >= PATH_MAX )
+ return FALSE; /* name too long */
+
+ fname = g_strjoin( "/", SYSTEM_ZONEINFO_DIR, name, NULL );
+
+ fd = g_open( fname, O_RDONLY | O_CLOEXEC );
+ if( fd < 0 )
+ {
+ /* log: "Failed to open timezone file '%s': %s", fname, strerror() */
+ g_free( (gpointer)fname );
+ return FALSE;
+ }
+
+ if( !g_file_test( fname, G_FILE_TEST_IS_REGULAR ) )
+ {
+ /* log: "Timezone file '%s' is not a regular file: %s", fname, strerror() */
+ g_free( (gpointer)fname );
+ return FALSE;
+ }
+
+ if( read( fd, &buf, 4 ) != 4 )
+ {
+ /* log: "Failed to read from timezone file '%s': %m", fname, strerror() */
+ g_free( (gpointer)fname );
+ return FALSE;
+ }
+
+ /* Magic from tzfile(5) */
+ if( memcmp( buf, "TZif", 4 ) != 0 )
+ {
+ /* log: "Timezone file '%s' has wrong magic bytes", fname */
+ g_free( (gpointer)fname );
+ return FALSE;
+ }
+
+ g_free( (gpointer)fname );
+
+ return ret;
+}
+
+gboolean set_system_timezone( const gchar *name )
+{
+ const gchar *fname, *source;
+ gboolean ret = TRUE;
+
+
+ if( !name || *name == '\0' || g_strcmp0( name, "UTC" ) == 0 )
+ {
+ fname = g_strjoin( "/", SYSTEM_ZONEINFO_DIR, "UTC", NULL );
+
+ if( !g_file_test( fname, G_FILE_TEST_IS_REGULAR ) )
+ {
+ /* log: "Timezone file '%s' is not a regular file: %s", fname, strerror() */
+ g_free( (gpointer)fname );
+ return FALSE;
+ }
+ source = g_strjoin( "/", "..", skip_root( SYSTEM_ZONEINFO_DIR ), "UTC", NULL );
+ }
+ else
+ {
+ source = g_strjoin( "/", "..", skip_root( SYSTEM_ZONEINFO_DIR ), name, NULL );
+ }
+
+ /* Create symlink to the new timezone */
+ unlink( "/etc/localtime" );
+ ret = symlink_atomic( (const char *)source, "/etc/localtime" );
+ g_free( (gpointer)source );
+
+ /* Make glibc notice the new timezone */
+ tzset();
+
+ /* Tell the kernel our timezone */
+ (void)clock_set_timezone( NULL );
+
+ return ret;
+}
+
+gboolean get_system_timezone( gchar **ret )
+{
+ GError *error = NULL;
+ gchar *link_target;
+ gchar *timezone;
+
+ if( !ret ) return FALSE;
+
+ link_target = g_file_read_link( "/etc/localtime", &error );
+
+ if( error != NULL )
+ {
+ timezone = g_strdup( "UTC" );
+ if( !timezone )
+ {
+ g_error_free( error );
+ g_free( (gpointer)link_target );
+ return FALSE;
+ }
+ g_error_free( error );
+ goto out;
+ }
+
+ if( !g_path_is_absolute( link_target ) )
+ {
+ gchar *absolute_link_target = g_strdup( link_target + 2 ); /* skip '..' */
+ g_free( (gpointer)link_target );
+ link_target = g_strdup( absolute_link_target );
+ g_free( (gpointer)absolute_link_target );
+ }
+
+ timezone = g_strdup( link_target + strlen( SYSTEM_ZONEINFO_DIR ) + 1 );
+
+ if( !timezone_is_valid( timezone ) )
+ {
+ g_free( (gpointer)timezone );
+ g_free( (gpointer)link_target );
+ return FALSE;
+ }
+
+out:
+ g_free( (gpointer)link_target );
+ *ret = timezone;
+
+ return TRUE;
+}
+
+/***************************************************************
+ LocalRTC functions:
+ */
+static
+gboolean exec_cmd( const gchar *cmd )
+{
+ int exit_status;
+ GError *error = NULL;
+ gboolean ret = TRUE;
+
+ if( !cmd || *cmd == '\0' ) return FALSE;
+
+ if( !g_spawn_command_line_sync( cmd, NULL, NULL, &exit_status, &error ) )
+ {
+ g_error_free( error );
+ ret = FALSE;
+ }
+ g_free( (gpointer)cmd );
+
+ if( exit_status != 0 )
+ ret = FALSE;
+
+ return ret;
+}
+
+gboolean write_data_local_rtc( gboolean local_rtc )
+{
+ gchar *w = NULL;
+ gboolean ret = TRUE;
+ gchar *cmd;
+
+ if( !g_file_test( ADJTIME_CONF, G_FILE_TEST_EXISTS ) )
+ {
+ if( !local_rtc )
+ {
+ if( !(w = g_strdup( NULL_ADJTIME_UTC )) ) return FALSE;
+ if( !(g_file_set_contents( ADJTIME_CONF, w, -1, NULL )) )
+ {
+ g_free( (gpointer)w );
+ return FALSE;
+ }
+ g_free( (gpointer)w );
+ }
+ else
+ {
+ if( !(w = g_strdup( NULL_ADJTIME_LOCAL )) ) return FALSE;
+ if( !(g_file_set_contents( ADJTIME_CONF, w, -1, NULL )) )
+ {
+ g_free( (gpointer)w );
+ return FALSE;
+ }
+ g_free( (gpointer)w );
+ }
+
+ return ret;
+ }
+
+ if( !g_file_test( HWCLOCK_CONF, G_FILE_TEST_EXISTS ) )
+ {
+ const gchar *localtime = "#\n"
+ "# /etc/hardwareclockn\n"
+ "#\n"
+ "# Tells how the hardware clock time is stored.\n"
+ "# You should run timeconfig to edit this file.\n"
+ "\n"
+ "localtime\n";
+
+ const gchar *UTC = "#\n"
+ "# /etc/hardwareclockn\n"
+ "#\n"
+ "# Tells how the hardware clock time is stored.\n"
+ "# You should run timeconfig to edit this file.\n"
+ "\n"
+ "UTC\n";
+
+ if( !local_rtc )
+ {
+ if( !(g_file_set_contents( HWCLOCK_CONF, UTC, -1, NULL )) ) return FALSE;
+ }
+ else
+ {
+ if( !(g_file_set_contents( HWCLOCK_CONF, localtime, -1, NULL )) ) return FALSE;
+ }
+
+ return ret;
+ }
+
+
+ if( !local_rtc )
+ {
+ cmd = g_strconcat( "sed -i 's,^LOCAL,UTC,' ", ADJTIME_CONF, NULL );
+ if( !exec_cmd( (const gchar *)cmd ) ) ret = FALSE;
+
+ cmd = g_strconcat( "sed -i 's,^localtime,UTC,' ", HWCLOCK_CONF, NULL );
+ if( !exec_cmd( (const gchar *)cmd ) ) ret = FALSE;
+ }
+ else
+ {
+ cmd = g_strconcat( "sed -i 's,^UTC,LOCAL,' ", ADJTIME_CONF, NULL );
+ if( !exec_cmd( (const gchar *)cmd ) ) ret = FALSE;
+
+ cmd = g_strconcat( "sed -i 's,^UTC,localtime,' ", HWCLOCK_CONF, NULL );
+ if( !exec_cmd( (const gchar *)cmd ) ) ret = FALSE;
+ }
+
+ return ret;
+}
+
+gboolean read_data_local_rtc( gboolean *local_rtc )
+{
+ gboolean ret = FALSE;
+
+ if( !local_rtc ) return FALSE;
+
+ if( g_file_test( HWCLOCK_CONF, G_FILE_TEST_EXISTS ) )
+ {
+ gchar *s = NULL;
+ gsize len;
+
+ ret = g_file_get_contents( HWCLOCK_CONF, &s, &len, NULL );
+ if( !ret )
+ return FALSE;
+
+ ret = g_regex_match_simple( "localtime", (const gchar *)s, 0, 0 );
+ if( ret )
+ {
+ *local_rtc = TRUE;
+ g_free( (gpointer)s );
+ return TRUE;
+ }
+
+ ret = g_regex_match_simple ( "UTC", (const gchar *)s, 0, 0 );
+ if( ret )
+ {
+ *local_rtc = FALSE;
+ g_free( (gpointer)s );
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else if( g_file_test( ADJTIME_CONF, G_FILE_TEST_EXISTS ) )
+ {
+ gchar *s = NULL;
+ gsize len;
+
+ ret = g_file_get_contents( ADJTIME_CONF, &s, &len, NULL );
+ if( !ret )
+ return FALSE;
+
+ ret = g_regex_match_simple( "LOCAL", (const gchar *)s, 0, 0 );
+ if( ret )
+ {
+ *local_rtc = TRUE;
+ g_free( (gpointer)s );
+ return TRUE;
+ }
+
+ ret = g_regex_match_simple ( "UTC", (const gchar *)s, 0, 0 );
+ if( ret )
+ {
+ *local_rtc = FALSE;
+ g_free( (gpointer)s );
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else
+ {
+ return ret;
+ }
+
+ return ret;
+}
diff --git a/src/rcl-time-utils.h b/src/rcl-time-utils.h
new file mode 100644
index 0000000..286fc1f
--- /dev/null
+++ b/src/rcl-time-utils.h
@@ -0,0 +1,111 @@
+
+/*
+ * Copyright (C) 2023 Andrey V.Kosteltsev <kx@radix.pro>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __RCL_TIME_UTILS_H__
+#define __RCL_TIME_UTILS_H__
+
+#include "config.h"
+
+#include <string.h>
+#include <stdint.h>
+#include <time.h>
+#include <fcntl.h>
+#include <linux/rtc.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/timex.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <locale.h>
+
+#define USEC_INFINITY ((guint64) UINT64_MAX)
+#define NSEC_INFINITY ((guint64) UINT64_MAX)
+
+#define MSEC_PER_SEC 1000ULL
+#define USEC_PER_SEC ((guint64) 1000000ULL)
+#define USEC_PER_MSEC ((guint64) 1000ULL)
+#define NSEC_PER_SEC ((guint64) 1000000000ULL)
+#define NSEC_PER_MSEC ((guint64) 1000000ULL)
+#define NSEC_PER_USEC ((guint64) 1000ULL)
+
+#define USEC_PER_MINUTE ((guint64) (60ULL*USEC_PER_SEC))
+#define NSEC_PER_MINUTE ((guint64) (60ULL*NSEC_PER_SEC))
+#define USEC_PER_HOUR ((guint64) (60ULL*USEC_PER_MINUTE))
+#define NSEC_PER_HOUR ((guint64) (60ULL*NSEC_PER_MINUTE))
+#define USEC_PER_DAY ((guint64) (24ULL*USEC_PER_HOUR))
+#define NSEC_PER_DAY ((guint64) (24ULL*NSEC_PER_HOUR))
+#define USEC_PER_WEEK ((guint64) (7ULL*USEC_PER_DAY))
+#define NSEC_PER_WEEK ((guint64) (7ULL*NSEC_PER_DAY))
+#define USEC_PER_MONTH ((guint64) (2629800ULL*USEC_PER_SEC))
+#define NSEC_PER_MONTH ((guint64) (2629800ULL*NSEC_PER_SEC))
+#define USEC_PER_YEAR ((guint64) (31557600ULL*USEC_PER_SEC))
+#define NSEC_PER_YEAR ((guint64) (31557600ULL*NSEC_PER_SEC))
+
+#define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1)
+
+
+#define SYSTEM_ZONEINFO_DIR "/usr/share/zoneinfo"
+
+#define NULL_ADJTIME_UTC "0.0 0 0.0\n0\nUTC\n"
+#define NULL_ADJTIME_LOCAL "0.0 0 0.0\n0\nLOCAL\n"
+
+#if !defined( HWCLOCK_CONF )
+#define HWCLOCK_CONF "/etc/hardwareclock"
+#endif
+
+#if !defined( ADJTIME_CONF )
+#define ADJTIME_CONF "/etc/adjtime"
+#endif
+
+
+extern gboolean ntp_synchronized ( void );
+
+extern guint64 timespec_load ( const struct timespec *ts );
+extern guint64 timespec_load_nsec ( const struct timespec *ts );
+extern struct timespec *timespec_store ( struct timespec *ts, guint64 u );
+extern struct timespec *timespec_store_nsec ( struct timespec *ts, guint64 n );
+extern guint64 timeval_load ( const struct timeval *tv );
+extern struct timeval *timeval_store ( struct timeval *tv, guint64 u );
+extern guint64 now ( clockid_t clock_id );
+extern guint64 now_nsec ( clockid_t clock_id );
+
+extern struct tm *localtime_or_gmtime_r ( const time_t *t, struct tm *tm, gboolean utc );
+extern time_t mktime_or_timegm ( struct tm *tm, gboolean utc );
+
+extern gboolean clock_get_hwclock ( struct tm *tm );
+extern gboolean clock_set_hwclock ( const struct tm *tm );
+
+extern gboolean clock_set_timezone ( int *ret_minutesdelta );
+
+extern gboolean timezone_is_valid ( const gchar *name );
+extern gboolean set_system_timezone ( const gchar *name );
+extern gboolean get_system_timezone ( gchar **ret );
+
+extern gboolean write_data_local_rtc ( gboolean local_rtc );
+extern gboolean read_data_local_rtc ( gboolean *local_rtc );
+
+
+#endif /* __RCL_TIME_UTILS_H__ */
diff --git a/src/rcl-timedate.c b/src/rcl-timedate.c
new file mode 100644
index 0000000..3478026
--- /dev/null
+++ b/src/rcl-timedate.c
@@ -0,0 +1,898 @@
+
+/*
+ * Copyright (C) 2023 Andrey V.Kosteltsev <kx@radix.pro>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <glib-object.h>
+#include <locale.h>
+
+#include <polkit/polkit.h>
+
+#include "rcl-timedate.h"
+#include "rcl-time-utils.h"
+#include "rcl-ntpd-utils.h"
+#include "rcl-zone-utils.h"
+
+struct RclDaemonPrivate
+{
+ gboolean debug;
+ gchar *timezone;
+ gboolean local_rtc;
+ gboolean can_ntp;
+ gboolean use_ntp;
+ PolkitAuthority *auth;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (RclDaemon, rcl_daemon, RCL_TYPE_TIMEDATE_DAEMON_SKELETON)
+
+#define RCL_DAEMON_ACTION_DELAY 20 /* seconds */
+#define RCL_INTERFACE_PREFIX "org.freedesktop.timedate1."
+
+
+/***************************************************************
+ Polkit functions:
+ ================
+ */
+static gboolean
+_check_polkit_for_action( RclDaemon *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *function )
+{
+ const gchar *action = g_strjoin( "", RCL_INTERFACE_PREFIX, function, NULL );
+ const gchar *sender;
+ GError *error;
+ PolkitSubject *subject;
+ PolkitAuthorizationResult *result;
+
+ error = NULL;
+
+ /* Check that caller is privileged */
+ sender = g_dbus_method_invocation_get_sender( invocation );
+ subject = polkit_system_bus_name_new( sender );
+
+ /********************************************************************************************
+ flag = POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION causes a pop-up dialog box.
+ We have not to use any pop-up windows.
+ */
+ result = polkit_authority_check_authorization_sync( object->priv->auth,
+ subject,
+ action,
+ NULL,
+ POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE,
+ NULL,
+ &error );
+ g_object_unref( G_OBJECT(subject) );
+
+ if( error )
+ {
+ g_dbus_method_invocation_return_gerror( invocation, error );
+ g_error_free( error );
+ g_free( (gpointer)action );
+
+ return FALSE;
+ }
+
+ if( !polkit_authorization_result_get_is_authorized( result ) )
+ {
+ error = g_error_new( RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_NOT_PRIVILEGED,
+ "User is not privileged for action %s", action );
+ g_dbus_method_invocation_return_gerror( invocation, error );
+
+ g_error_free( error );
+ g_object_unref( G_OBJECT(result) );
+ g_free( (gpointer)action );
+
+ return FALSE;
+ }
+
+ g_object_unref( G_OBJECT(result) );
+ g_free( (gpointer)action );
+
+ return TRUE;
+}
+
+
+/***************************************************************
+ DBus Properties:
+ ===============
+ */
+static gboolean get_ntpsynchronized( RclTimedateDaemon *object )
+{
+ gboolean ntp_synced = ntp_synchronized();
+
+ rcl_timedate_daemon_set_ntpsynchronized( object, ntp_synced );
+
+ return ntp_synced;
+}
+
+static guint64 get_rtctime_usec( RclTimedateDaemon *object )
+{
+ struct tm tm = {};
+ guint64 usec = 0;
+
+ if( !clock_get_hwclock( &tm ) )
+ {
+ g_debug( "get-ntpsynchronized: error: Cannot get RTC clock" );
+
+ rcl_timedate_daemon_set_rtctime_usec( object, (guint64)0 );
+
+ return (guint64)0;
+ }
+
+ usec = (guint64)timegm( &tm ) * USEC_PER_SEC;
+
+ rcl_timedate_daemon_set_rtctime_usec( object, usec );
+
+ return usec;
+}
+
+static guint64 get_time_usec( RclTimedateDaemon *object )
+{
+ guint64 usec;
+
+ usec = now( CLOCK_REALTIME );
+
+ rcl_timedate_daemon_set_time_usec( object, usec );
+
+ return usec;
+}
+
+void rcl_daemon_sync_dbus_properties( RclTimedateDaemon *object )
+{
+
+ /* Update NTPSynchronized */
+ (void)get_ntpsynchronized( object );
+
+ /* Update RTCTimeUSec */
+ (void)get_rtctime_usec( object );
+
+ /* Update TimeUSec */
+ (void)get_time_usec( object );
+}
+
+
+/***************************************************************
+ DBus Handlers:
+ =============
+ */
+gboolean handle_set_timezone( RclTimedateDaemon *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *timezone,
+ gboolean interactive,
+ RclDaemon *daemon )
+{
+ gboolean ret = TRUE;
+
+ if( !timezone_is_valid( timezone ) )
+ {
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_INVALID_TIMEZONE_FILE,
+ "Requested timezone '%s' is invalid", timezone );
+ return TRUE;
+ }
+
+ if( g_strcmp0( (const char *)daemon->priv->timezone, (const char *)timezone ) == 0 )
+ goto out;
+
+ if( !_check_polkit_for_action( daemon, invocation, "set-timezone" ) )
+ {
+ g_debug( "set-timezone: error: '%s'", "User is not privileged" );
+ return TRUE;
+ }
+
+ ret = set_system_timezone( timezone );
+ if( ret )
+ {
+ if( daemon->priv->local_rtc )
+ {
+ struct timespec ts;
+ struct tm tm;
+
+ /* Sync RTC from system clock, with the new delta */
+ if( clock_gettime(CLOCK_REALTIME, &ts) != 0 )
+ {
+ g_debug( "set-timezone: error: Sync RTC from system clock: '%s'", "clock_gettime(): failed" );
+ return TRUE;
+ }
+ if( !localtime_r( &ts.tv_sec, &tm ) )
+ {
+ g_debug( "set-timezone: error: Sync RTC from system clock: '%s'", "localtime_r(): failed" );
+ return TRUE;
+ }
+ if( !clock_set_hwclock( &tm ) )
+ {
+ g_debug( "set-timezone: error: Sync RTC from system clock: '%s'", "Cannot update '/dev/rtc' (ignoring)" );
+ }
+ }
+ }
+ else
+ {
+ g_debug( "set-timezone: error: Cannot set system timezone '%s'", timezone );
+ return TRUE;
+ }
+
+ g_free( (gpointer)daemon->priv->timezone );
+ daemon->priv->timezone = g_strdup( timezone );
+ rcl_timedate_daemon_set_timezone( object, (const gchar *)daemon->priv->timezone );
+
+out:
+ g_debug( "set-timezone: SetTimezone to '%s' returns successful status (interactive=%s)",
+ daemon->priv->timezone, (interactive) ? "true" : "false" );
+
+ rcl_timedate_daemon_complete_set_timezone( object, invocation );
+
+ return TRUE;
+}
+
+
+gboolean handle_set_local_rtc( RclTimedateDaemon *object,
+ GDBusMethodInvocation *invocation,
+ gboolean local_rtc,
+ gboolean fix_system,
+ gboolean interactive,
+ RclDaemon *daemon )
+{
+ struct timespec ts;
+ gboolean ret = TRUE;
+
+ if( daemon->priv->local_rtc == local_rtc && !fix_system )
+ goto out;
+
+ if( !_check_polkit_for_action( daemon, invocation, "set-local-rtc" ) )
+ {
+ g_debug( "set-local-rtc: error: '%s'", "User is not privileged" );
+ return TRUE;
+ }
+
+ if( daemon->priv->local_rtc != local_rtc )
+ {
+ daemon->priv->local_rtc = local_rtc;
+
+ /* Write new configuration files */
+ ret = write_data_local_rtc( daemon->priv->local_rtc );
+ if( !ret )
+ {
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_GENERAL,
+ "Cannot set LocalRTC" );
+ return TRUE;
+ }
+
+ }
+
+ /* Tell the kernel our timezone */
+ ret = clock_set_timezone( NULL );
+ if( !ret )
+ {
+ g_debug( "set-local-rtc: error: Cannot set timezone clock after SetLocal_RTC" );
+ return TRUE;
+ }
+
+ /* Synchronize clocks */
+ if( clock_gettime(CLOCK_REALTIME, &ts) != 0 )
+ {
+ g_debug( "set-local-rtc: error: Sync RTC from system clock after SetLocalRTC: '%s'", "clock_gettime(): failed" );
+ return TRUE;
+ }
+
+ if( fix_system )
+ {
+ struct tm tm;
+
+ /* Sync system clock from RTC; first, initialize the timezone fields of struct tm. */
+ localtime_or_gmtime_r( &ts.tv_sec, &tm, !daemon->priv->local_rtc);
+
+ /* Override the main fields of struct tm, but not the timezone fields */
+ ret = clock_get_hwclock( &tm );
+ if( !ret)
+ {
+ g_debug( "set-local-rtc: error: Failed to get hardware clock (ignoring)" );
+ }
+ else
+ {
+ /* And set the system clock with this */
+ ts.tv_sec = mktime_or_timegm( &tm, !daemon->priv->local_rtc );
+
+ if( clock_settime( CLOCK_REALTIME, &ts ) < 0 )
+ {
+ g_debug( "set-local-rtc: error: Failed to update system clock (ignoring)" );
+ }
+ }
+ }
+ else
+ {
+ struct tm tm;
+
+ /* Sync RTC from system clock */
+ localtime_or_gmtime_r( &ts.tv_sec, &tm, !daemon->priv->local_rtc );
+
+ ret = clock_set_hwclock( &tm );
+ if( !ret )
+ {
+ g_debug( "set-local-rtc: error: Failed to sync time to hardware clock (ignoring)" );
+ }
+ }
+
+ g_debug( "set-local-rtc: RTC configured to %s time", (daemon->priv->local_rtc) ? "localtime" : "UTC" );
+
+
+ rcl_timedate_daemon_set_local_rtc( object, daemon->priv->local_rtc );
+
+out:
+ g_debug( "set-local-rtc: SetLocalRTC to '%s' returns successful status (fix_sysrem=%s; interactive=%s)",
+ (daemon->priv->local_rtc) ? "localtime" : "UTC",
+ (fix_system) ? "true" : "false",
+ (interactive) ? "true" : "false" );
+
+ rcl_timedate_daemon_complete_set_local_rtc( object, invocation );
+
+ return TRUE;
+}
+
+
+gboolean handle_set_ntp( RclTimedateDaemon *object,
+ GDBusMethodInvocation *invocation,
+ gboolean use_ntp,
+ gboolean interactive,
+ RclDaemon *daemon )
+{
+ /* check CanNTP (in case NTPD was uninstalled while timedated running) */
+ if( !ntp_daemon_installed() )
+ {
+ daemon->priv->can_ntp = FALSE;
+ rcl_timedate_daemon_set_can_ntp( object, daemon->priv->can_ntp );
+ }
+
+ if( !daemon->priv->can_ntp )
+ {
+ daemon->priv->use_ntp = FALSE;
+ rcl_timedate_daemon_set_ntp( object, daemon->priv->use_ntp );
+ return TRUE;
+ }
+
+ if( !_check_polkit_for_action( daemon, invocation, "set-ntp" ) )
+ {
+ g_debug( "set-ntp: error: '%s'", "User is not privileged" );
+ return TRUE;
+ }
+
+ if( daemon->priv->use_ntp == use_ntp )
+ goto out;
+
+ if( use_ntp ) /* enable and start NTP daemon: */
+ {
+ if( ntp_daemon_enabled() )
+ {
+ if( ntp_daemon_status() )
+ {
+ g_debug( "set-ntp: The NTP Daemon already running" );
+ daemon->priv->use_ntp = TRUE;
+ /* SUCCESS */
+ }
+ else
+ {
+ if( !start_ntp_daemon() )
+ {
+ g_debug( "set-ntp: error: Cannot start NTPD daemon" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_GENERAL,
+ "Cannot start NTP Daemon" );
+ daemon->priv->use_ntp = FALSE;
+ /* FAILURE */
+ return TRUE;
+ }
+ else
+ {
+ g_debug( "set-ntp: The NTPD daemon started successful" );
+ daemon->priv->use_ntp = TRUE;
+ /* SUCCESS */
+ }
+ }
+ }
+ else
+ {
+ if( !enable_ntp_daemon() )
+ {
+ g_debug( "set-ntp: error: Cannot enable NTPD daemon" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_GENERAL,
+ "Cannot enable NTP Daemon" );
+ daemon->priv->use_ntp = FALSE;
+ /* FAILURE */
+ return TRUE;
+ }
+ else
+ {
+ if( !start_ntp_daemon() )
+ {
+ g_debug( "set-ntp: error: Cannot start NTPD daemon" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_GENERAL,
+ "Cannot start NTP Daemon" );
+ daemon->priv->use_ntp = FALSE;
+ /* FAILURE */
+ return TRUE;
+ }
+ else
+ {
+ g_debug( "set-ntp: The NTPD daemon started successful" );
+ daemon->priv->use_ntp = TRUE;
+ /* SUCCESS */
+ }
+ }
+ }
+ }
+ else /* stop and disable NTP daemon: */
+ {
+ if( ntp_daemon_enabled() )
+ {
+ if( ntp_daemon_status() )
+ {
+ /* daemon is running; stop it */
+ if( !stop_ntp_daemon() )
+ {
+ g_debug( "set-ntp: error: Cannot stop NTPD daemon" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_GENERAL,
+ "Cannot stop NTP Daemon" );
+ daemon->priv->use_ntp = TRUE;
+ /* FAILURE */
+ return TRUE;
+ }
+ else
+ {
+ g_debug( "set-ntp: The NTPD daemon stopped successful" );
+ if( !disable_ntp_daemon() )
+ {
+ g_debug( "set-ntp: Cannot disable NTPD daemon" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_GENERAL,
+ "Cannot disable NTP Daemon" );
+ daemon->priv->use_ntp = FALSE;
+ /* daemon stopped but not disabled (will start after reboot) */
+ /* SUCCESS */
+ }
+ else
+ {
+ g_debug( "set-ntp: The NTPD daemon disabled successful" );
+ daemon->priv->use_ntp = FALSE;
+ /* SUCCESS */
+ }
+ }
+ }
+ else
+ {
+ /* daemon is stopped; disable it */
+ if( !disable_ntp_daemon() )
+ {
+ g_debug( "set-ntp: error: Cannot disable NTPD daemon" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_GENERAL,
+ "Cannot disable NTP Daemon" );
+ daemon->priv->use_ntp = FALSE;
+ /* daemon stopped but not disabled (will start after reboot) */
+ /* SUCCESS */
+ }
+ else
+ {
+ g_debug( "set-ntp: The NTPD daemon disabled successful" );
+ daemon->priv->use_ntp = FALSE;
+ /* SUCCESS */
+ }
+ }
+ }
+ else
+ {
+ g_debug( "set-ntp: The NTPD daemon already disabled" );
+ daemon->priv->use_ntp = FALSE;
+ /* SUCCESS */
+ }
+ }
+
+ g_debug( "set-ntp: NTP configured to %s", (daemon->priv->use_ntp) ? "enabled" : "disabled" );
+
+ rcl_timedate_daemon_set_ntp( object, daemon->priv->use_ntp );
+ /* rcl_timedate_daemon_set_ntpsynchronized( object, daemon->priv->use_ntp ); */
+
+out:
+ g_debug( "set-ntp: SetNTP to '%s' returns successful status (interactive=%s)",
+ (daemon->priv->use_ntp) ? "true" : "false",
+ (interactive) ? "true" : "false" );
+
+ rcl_timedate_daemon_complete_set_ntp( object, invocation );
+
+ return TRUE;
+}
+
+
+gboolean handle_set_time( RclTimedateDaemon *object,
+ GDBusMethodInvocation *invocation,
+ gint64 usec_utc,
+ gboolean relative,
+ gboolean interactive,
+ RclDaemon *daemon )
+{
+ struct timespec ts;
+ struct tm tm;
+ guint64 start;
+
+ if( ntp_daemon_installed() && ntp_daemon_enabled() && ntp_daemon_status() )
+ {
+ /* NTP Daemon is running */
+ g_debug( "set-time: error: Automatic time synchronization is enabled" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_GENERAL,
+ "set-time: Automatic time synchronization is enabled" );
+ return TRUE;
+ }
+
+ start = now( CLOCK_MONOTONIC );
+
+ if( !relative && usec_utc <= 0 )
+ {
+ g_debug( "set-time: error: Invalid absolute time" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_INVALID_ARGS,
+ "set-time: Invalid absolute time" );
+ return TRUE;
+ }
+
+ if( relative && usec_utc == 0 )
+ {
+ /* Nothing to do */
+ goto out;
+ }
+
+ if( relative )
+ {
+ guint64 n, x;
+
+ n = now( CLOCK_REALTIME );
+ x = n + usec_utc;
+
+ if( (usec_utc > 0 && x < n) ||
+ (usec_utc < 0 && x > n) )
+ {
+ g_debug( "set-time: error: Time value overflow" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_INVALID_ARGS,
+ "set-time: Time value overflow" );
+ return TRUE;
+ }
+ timespec_store( &ts, x );
+ }
+ else
+ {
+ timespec_store( &ts, (guint64)usec_utc);
+ }
+
+ if( !_check_polkit_for_action( daemon, invocation, "set-time" ) )
+ {
+ g_debug( "set-time: error: '%s'", "User is not privileged" );
+ return TRUE;
+ }
+
+ timespec_store( &ts, timespec_load( &ts ) + (now( CLOCK_MONOTONIC ) - start) );
+
+ /* Set system clock */
+ if( clock_settime( CLOCK_REALTIME, &ts ) < 0 )
+ {
+ g_debug( "set-time: error: Failed to set local time" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_GENERAL,
+ "set-time: Failed to set local time" );
+ return TRUE;
+ }
+
+ /* Sync down to RTC */
+ localtime_or_gmtime_r( &ts.tv_sec, &tm, !daemon->priv->local_rtc );
+
+ if( !clock_set_hwclock( &tm ) )
+ {
+ g_debug( "set-time: error: Failed to update hardware clock (ignoring)" );
+ }
+
+ g_debug( "set-time: SetTime method returns successful status" );
+
+ /* Update RTCTimeUSec (by the way) */
+ (void)get_rtctime_usec( object );
+
+ rcl_timedate_daemon_set_time_usec( object, timespec_load( &ts ) );
+
+out:
+ g_debug( "set-time: SetTime to %ld returns successful status(relative=%s; interactive=%s)",
+ usec_utc,
+ (relative) ? "true" : "false",
+ (interactive) ? "true" : "false" );
+
+ rcl_timedate_daemon_complete_set_time( object, invocation );
+
+ return TRUE;
+}
+
+
+gboolean handle_list_timezones( RclTimedateDaemon *object,
+ GDBusMethodInvocation *invocation,
+ RclDaemon *daemon )
+{
+ gboolean ret;
+ const gchar *const *zones = { NULL };
+
+ ret = get_timezones( &zones );
+ if( !ret )
+ {
+ g_debug( "list-timezones: error: Failed to read list of time zones" );
+ g_dbus_method_invocation_return_error( invocation,
+ RCL_DAEMON_ERROR,
+ RCL_DAEMON_ERROR_GENERAL,
+ "list-timezones: Failed to read list of time zones" );
+ return TRUE;
+ }
+
+ g_debug( "list-timezones: ListTimesones returns successful status" );
+
+ rcl_timedate_daemon_complete_list_timezones( object, invocation, zones );
+
+ timezones_free( &zones );
+
+ return TRUE;
+}
+
+
+
+/***************************************************************
+ Daemon functions:
+ ================
+ */
+
+void
+rcl_daemon_set_debug( RclDaemon *daemon,
+ gboolean debug )
+{
+ daemon->priv->debug = debug;
+}
+
+gboolean
+rcl_daemon_get_debug( RclDaemon *daemon )
+{
+ return daemon->priv->debug;
+}
+
+
+/***************************************************************
+ rcl_daemon_register_timedate_daemon:
+ */
+static gboolean
+rcl_daemon_register_timedate_daemon( RclDaemon *daemon,
+ GDBusConnection *connection )
+{
+ GError *error = NULL;
+
+ daemon->priv->auth = polkit_authority_get_sync( NULL, &error );
+ if( daemon->priv->auth == NULL )
+ {
+ if( error != NULL )
+ {
+ g_critical ("timedated: error: Cannot get system bus: %s", error->message );
+ g_error_free( error );
+ }
+ return FALSE;
+ }
+
+ /* export our interface on the bus */
+ g_dbus_interface_skeleton_export( G_DBUS_INTERFACE_SKELETON( daemon ),
+ connection,
+ "/org/freedesktop/timedate1",
+ &error );
+
+ if( error != NULL )
+ {
+ g_critical( "timedated: error: Cannot register the daemon on system bus: %s", error->message );
+ g_error_free( error );
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/***************************************************************
+ rcl_daemon_startup:
+ */
+gboolean
+rcl_daemon_startup( RclDaemon *daemon,
+ GDBusConnection *connection )
+{
+ gboolean ret;
+
+ /* register on bus */
+ ret = rcl_daemon_register_timedate_daemon( daemon, connection );
+ if( !ret )
+ {
+ g_warning( "timedated: warning: Failed to register the daemon on bus" );
+ goto out;
+ }
+
+ g_debug( "Daemon now started" );
+
+out:
+ return ret;
+}
+
+/***************************************************************
+ rcl_daemon_shutdown:
+
+ Stop the daemon, release all resources.
+ */
+void
+rcl_daemon_shutdown( RclDaemon *daemon )
+{
+}
+
+
+/***************************************************************
+ rcl_daemon_init:
+ */
+static void
+rcl_daemon_init( RclDaemon *daemon )
+{
+ gboolean rtc = TRUE; /* default */
+ gboolean ntp = FALSE; /* default */
+
+ daemon->priv = rcl_daemon_get_instance_private( daemon );
+
+ /**********************
+ Get current Timezone:
+ */
+ if( !get_system_timezone( (gchar **)&daemon->priv->timezone ) )
+ {
+ daemon->priv->timezone = g_strdup( "Europe/Moscow" );
+ }
+
+ /******************
+ Init Properties:
+ */
+ /* Timezone: */
+ rcl_timedate_daemon_set_daemon_version( RCL_TIMEDATE_DAEMON( daemon ), PACKAGE_VERSION );
+ rcl_timedate_daemon_set_timezone( RCL_TIMEDATE_DAEMON( daemon ), (const gchar *)daemon->priv->timezone );
+
+ /* LocalRTC: */
+ (void)read_data_local_rtc( &rtc );
+ daemon->priv->local_rtc = rtc;
+ rcl_timedate_daemon_set_local_rtc( RCL_TIMEDATE_DAEMON( daemon ), daemon->priv->local_rtc );
+
+ /* CanNTP: */
+ ntp = ntp_daemon_installed();
+ daemon->priv->can_ntp = ntp;
+ rcl_timedate_daemon_set_can_ntp( RCL_TIMEDATE_DAEMON( daemon ), daemon->priv->can_ntp );
+
+ /* NTP: */
+ ntp = ( ntp_daemon_installed() && ntp_daemon_enabled() && ntp_daemon_status() );
+ daemon->priv->use_ntp = ntp;
+ rcl_timedate_daemon_set_ntp( RCL_TIMEDATE_DAEMON( daemon ), daemon->priv->use_ntp );
+
+ /* NTPSynchronized: */
+ rcl_timedate_daemon_set_ntpsynchronized( RCL_TIMEDATE_DAEMON( daemon ), ntp_synchronized() );
+
+
+ /******************
+ Handlers:
+ */
+ g_signal_connect( RCL_TIMEDATE_DAEMON( daemon ),
+ "handle-set-timezone",
+ G_CALLBACK( handle_set_timezone ),
+ daemon ); /* user_data */
+
+ g_signal_connect( RCL_TIMEDATE_DAEMON( daemon ),
+ "handle-set-local-rtc",
+ G_CALLBACK( handle_set_local_rtc ),
+ daemon ); /* user_data */
+
+ g_signal_connect( RCL_TIMEDATE_DAEMON( daemon ),
+ "handle-set-ntp",
+ G_CALLBACK( handle_set_ntp ),
+ daemon ); /* user_data */
+
+ g_signal_connect( RCL_TIMEDATE_DAEMON( daemon ),
+ "handle-set-time",
+ G_CALLBACK( handle_set_time ),
+ daemon ); /* user_data */
+
+ g_signal_connect( RCL_TIMEDATE_DAEMON( daemon ),
+ "handle-list-timezones",
+ G_CALLBACK( handle_list_timezones ),
+ daemon ); /* user_data */
+
+}
+
+
+static const GDBusErrorEntry rcl_daemon_error_entries[] = {
+ { RCL_DAEMON_ERROR_GENERAL, RCL_INTERFACE_PREFIX "GeneralError" },
+ { RCL_DAEMON_ERROR_NOT_PRIVILEGED, RCL_INTERFACE_PREFIX "NotPrivileged" },
+ { RCL_DAEMON_ERROR_INVALID_TIMEZONE_FILE, RCL_INTERFACE_PREFIX "InvalidTimezoneFile" },
+ { RCL_DAEMON_ERROR_INVALID_ARGS, RCL_INTERFACE_PREFIX "InvalidArguments" },
+ { RCL_DAEMON_ERROR_NOT_SUPPORTED, RCL_INTERFACE_PREFIX "NotSupported" },
+};
+
+/***************************************************************
+ rcl_daemon_error_quark:
+ */
+GQuark
+rcl_daemon_error_quark( void )
+{
+ static volatile gsize quark_volatile = 0;
+
+ g_dbus_error_register_error_domain( "rcl_timedated_error",
+ &quark_volatile,
+ rcl_daemon_error_entries,
+ G_N_ELEMENTS( rcl_daemon_error_entries ) );
+ return quark_volatile;
+}
+
+
+/***************************************************************
+ rcl_daemon_finalize:
+ */
+static void
+rcl_daemon_finalize( GObject *object )
+{
+ RclDaemon *daemon = RCL_DAEMON( object );
+
+ g_free( daemon->priv->timezone );
+ g_clear_object( &daemon->priv->auth );
+
+ G_OBJECT_CLASS( rcl_daemon_parent_class)->finalize( object );
+}
+
+/***************************************************************
+ rcl_daemon_class_init:
+ */
+static void
+rcl_daemon_class_init( RclDaemonClass *klass )
+{
+ GObjectClass *object_class = G_OBJECT_CLASS( klass );
+ object_class->finalize = rcl_daemon_finalize;
+}
+
+/***************************************************************
+ rcl_daemon_new:
+ */
+RclDaemon *
+rcl_daemon_new( void )
+{
+ return RCL_DAEMON( g_object_new( RCL_TYPE_DAEMON, NULL ) );
+}
diff --git a/src/rcl-timedate.h b/src/rcl-timedate.h
new file mode 100644
index 0000000..75710dd
--- /dev/null
+++ b/src/rcl-timedate.h
@@ -0,0 +1,76 @@
+
+/*
+ * Copyright (C) 2023 Andrey V.Kosteltsev <kx@radix.pro>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __RCL_DAEMON_H__
+#define __RCL_DAEMON_H__
+
+#include <dbus/rcl-timedate-generated.h>
+
+G_BEGIN_DECLS
+
+#define RCL_TYPE_DAEMON (rcl_daemon_get_type ())
+#define RCL_DAEMON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RCL_TYPE_DAEMON, RclDaemon))
+#define RCL_DAEMON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RCL_TYPE_DAEMON, RclDaemonClass))
+#define RCL_IS_DAEMON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RCL_TYPE_DAEMON))
+#define RCL_IS_DAEMON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RCL_TYPE_DAEMON))
+#define RCL_DAEMON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RCL_TYPE_DAEMON, RclDaemonClass))
+
+typedef struct RclDaemonPrivate RclDaemonPrivate;
+
+typedef struct
+{
+ RclTimedateDaemonSkeleton parent;
+ RclDaemonPrivate *priv;
+} RclDaemon;
+
+typedef struct
+{
+ RclTimedateDaemonSkeletonClass parent_class;
+} RclDaemonClass;
+
+typedef enum
+{
+ RCL_DAEMON_ERROR_GENERAL,
+ RCL_DAEMON_ERROR_NOT_PRIVILEGED,
+ RCL_DAEMON_ERROR_INVALID_TIMEZONE_FILE,
+ RCL_DAEMON_ERROR_INVALID_ARGS,
+ RCL_DAEMON_ERROR_NOT_SUPPORTED,
+ RCL_DAEMON_NUM_ERRORS
+} RclDaemonError;
+
+#define RCL_DAEMON_ERROR rcl_daemon_error_quark ()
+
+GQuark rcl_daemon_error_quark ( void );
+GType rcl_daemon_get_type ( void );
+RclDaemon *rcl_daemon_new ( void );
+
+/* private */
+gboolean rcl_daemon_startup ( RclDaemon *daemon,
+ GDBusConnection *connection );
+void rcl_daemon_shutdown ( RclDaemon *daemon );
+void rcl_daemon_set_debug ( RclDaemon *daemon,
+ gboolean debug );
+gboolean rcl_daemon_get_debug ( RclDaemon *daemon );
+
+void rcl_daemon_sync_dbus_properties( RclTimedateDaemon *object );
+
+G_END_DECLS
+
+#endif /* __RCL_DAEMON_H__ */
diff --git a/src/rcl-zone-utils.c b/src/rcl-zone-utils.c
new file mode 100644
index 0000000..4ffe8cd
--- /dev/null
+++ b/src/rcl-zone-utils.c
@@ -0,0 +1,230 @@
+
+/*
+ * Copyright (C) 2023 Andrey V.Kosteltsev <kx@radix.pro>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "rcl-zone-utils.h"
+
+static gsize strv_lenght( const gchar *const *list )
+{
+ gsize len = 0;
+ gchar **p = (gchar **)list;
+
+ if( !list || *list == NULL )
+ return 0;
+
+ while( *p )
+ {
+ ++len;
+ ++p;
+ }
+
+ return len;
+}
+
+static gboolean strv_append( const gchar *const **list, const gchar *value )
+{
+ gchar **c;
+ gchar *v;
+ gsize len;
+
+ if( !list || !value )
+ return FALSE;
+
+ len = strv_lenght( *list );
+
+ v = g_strdup( value );
+ if( !v )
+ return FALSE;
+
+ c = g_realloc( (gpointer)*list, len * sizeof(gchar *) + sizeof(gchar *) * 2 );
+ if( !c )
+ {
+ g_free( (gpointer)v );
+ return FALSE;
+ }
+
+ c[len] = v;
+ c[len+1] = NULL;
+
+ *list = (const gchar *const *)c;
+
+ return TRUE;
+}
+
+static gint comparator( gconstpointer item1, gconstpointer item2 )
+{
+ return g_strcmp0( item1, item2 );
+}
+
+static GSList *g_slist_insert_sorted_unique( GSList *list, gpointer data, GCompareFunc func )
+{
+ if( !data || !func )
+ return list;
+
+ if( !g_slist_find_custom( list, data, func ) )
+ list = g_slist_insert_sorted( list, data, func );
+
+ return list;
+}
+
+static GSList *get_timezones_from_tzdata_zi( void )
+{
+ GSList *list = NULL;
+ FILE *fp = NULL;
+ gchar *ln = NULL, *line = NULL;
+
+ fp = fopen( "/usr/share/zoneinfo/tzdata.zi", "r" );
+ if( !fp )
+ return list;
+
+ line = (gchar *)g_malloc0( (gsize)PATH_MAX );
+ if( !line )
+ {
+ fclose( fp );
+ return list;
+ }
+
+ /**********************************************
+ Zone line format is: 'Zone' 'timezone' ...
+ Link line format is: 'Link' 'target' 'alias'
+ See `man (8) zic' for infirmation.
+ **********************************************/
+ while( (ln = fgets( line, PATH_MAX, fp )) )
+ {
+ gchar *p, *q;
+
+ if( !g_ascii_strncasecmp( ln, "Z", 1 ) )
+ {
+ p = &ln[1];
+
+ /* Skip spaces */
+ while( (*p == ' ' || *p == '\t') && *p != '\n' )
+ ++p;
+
+ q = p;
+
+ /* Take the first entry */
+ while( *q != ' ' && *q != '\t' && *q != '\n' )
+ ++q;
+
+ *q = '\0';
+
+ list = g_slist_insert_sorted_unique( list, (gpointer)g_strdup( p ), (GCompareFunc)comparator );
+ continue;
+ }
+ else if( !g_ascii_strncasecmp( ln, "L", 1 ) )
+ {
+ p = &ln[1];
+
+ /* Skip spaces */
+ while( (*p == ' ' || *p == '\t') && *p != '\n' )
+ ++p;
+
+ q = p;
+
+ /* Skip the first entry */
+ while( *q != ' ' && *q != '\t' && *q != '\n' )
+ ++q;
+
+ p = q;
+
+ /* Skip spaces */
+ while( (*p == ' ' || *p == '\t') && *p != '\n' )
+ ++p;
+
+ q = p;
+
+ /* Take the second entry */
+ while( *q != ' ' && *q != '\t' && *q != '\n' )
+ ++q;
+
+ *q = '\0';
+
+ list = g_slist_insert_sorted_unique( list, (gpointer)g_strdup( p ), (GCompareFunc)comparator );
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+ g_free( (gpointer)line );
+ fclose( fp );
+
+ return list;
+}
+
+void timezones_free( const gchar *const **list )
+{
+ gchar **c, **p;
+
+ if( !list || *list == NULL )
+ return;
+
+ p = c = (gpointer)*list;
+
+ while( *p )
+ {
+ g_free( (gpointer)*p );
+ ++p;
+ }
+
+ g_free( (gpointer)c );
+
+ *list = (const gchar *const *)NULL;
+}
+
+void timezones_print( const gchar *const **list )
+{
+ gchar **p;
+
+ if( !list || *list == NULL )
+ return;
+
+ p = (gpointer)*list;
+
+ while( *p )
+ {
+ g_print( "%s\n", *p );
+ ++p;
+ }
+}
+
+gboolean get_timezones( const gchar *const **list )
+{
+ GSList *slist = NULL, *iterator = NULL;
+ gboolean ret = TRUE;
+
+ slist = get_timezones_from_tzdata_zi();
+ if( !slist )
+ return FALSE;
+
+ for( iterator = slist; iterator; iterator = iterator->next )
+ {
+ gboolean rc = strv_append( list, (const gchar *)iterator->data );
+ if( !rc )
+ ret = FALSE;
+
+ g_free( (gpointer)iterator->data );
+ }
+ g_slist_free(slist);
+
+ return ret;
+}
+
diff --git a/src/rcl-zone-utils.h b/src/rcl-zone-utils.h
new file mode 100644
index 0000000..b746b62
--- /dev/null
+++ b/src/rcl-zone-utils.h
@@ -0,0 +1,49 @@
+
+/*
+ * Copyright (C) 2023 Andrey V.Kosteltsev <kx@radix.pro>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __RCL_ZONE_UTILS_H__
+#define __RCL_ZONE_UTILS_H__
+
+#include "config.h"
+
+#include <string.h>
+#include <stdint.h>
+#include <time.h>
+#include <fcntl.h>
+#include <linux/rtc.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/timex.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <locale.h>
+
+extern gboolean get_timezones ( const gchar *const **list );
+extern void timezones_free ( const gchar *const **list );
+extern void timezones_print ( const gchar *const **list );
+
+
+#endif /* __RCL_ZONE_UTILS_H__ */