diff options
author | kx <kx@radix.pro> | 2023-12-21 21:15:15 +0300 |
---|---|---|
committer | kx <kx@radix.pro> | 2023-12-21 21:15:15 +0300 |
commit | 938dce1e03ee7b5f16c6955dd055916dd2d515d0 (patch) | |
tree | c90cb30e3e16408c369f6b7325ec5da16d204909 | |
parent | fcf296f311f6cc515afb16b8c08e4ddd3da807cb (diff) | |
download | timedated-938dce1e03ee7b5f16c6955dd055916dd2d515d0.tar.xz |
Version 1.0.0
-rw-r--r-- | AUTHORS | 2 | ||||
-rw-r--r-- | NEWS | 8 | ||||
-rw-r--r-- | README | 58 | ||||
-rw-r--r-- | dbus/meson.build | 36 | ||||
-rw-r--r-- | dbus/org.freedesktop.timedate1.xml | 58 | ||||
-rw-r--r-- | l10n/_generate-pot.sh | 25 | ||||
-rw-r--r-- | meson.build | 94 | ||||
-rw-r--r-- | meson_options.txt | 26 | ||||
-rw-r--r-- | po/LINGUAS | 4 | ||||
-rw-r--r-- | po/POTFILES.in | 11 | ||||
-rw-r--r-- | po/meson.build | 3 | ||||
-rw-r--r-- | po/ru_RU.utf8.po | 31 | ||||
-rw-r--r-- | po/timedated.pot | 30 | ||||
-rw-r--r-- | src/meson.build | 78 | ||||
-rw-r--r-- | src/org.freedesktop.timedate1.conf.in | 28 | ||||
-rw-r--r-- | src/org.freedesktop.timedate1.policy.in | 53 | ||||
-rw-r--r-- | src/org.freedesktop.timedate1.rules.in | 11 | ||||
-rw-r--r-- | src/org.freedesktop.timedate1.service.in | 5 | ||||
-rw-r--r-- | src/rcl-main.c | 303 | ||||
-rw-r--r-- | src/rcl-ntpd-utils.c | 163 | ||||
-rw-r--r-- | src/rcl-ntpd-utils.h | 59 | ||||
-rw-r--r-- | src/rcl-time-utils.c | 686 | ||||
-rw-r--r-- | src/rcl-time-utils.h | 111 | ||||
-rw-r--r-- | src/rcl-timedate.c | 898 | ||||
-rw-r--r-- | src/rcl-timedate.h | 76 | ||||
-rw-r--r-- | src/rcl-zone-utils.c | 230 | ||||
-rw-r--r-- | src/rcl-zone-utils.h | 49 |
27 files changed, 3136 insertions, 0 deletions
@@ -0,0 +1,2 @@ + +Andrey V.Kosteltsev <kx@radix.pro> @@ -0,0 +1,8 @@ + +Version 1.0.0 +------------- +Released: 2023-12-21 + +This is the first release of Timedate Daemon. + + @@ -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__ */ |