timedatectl: introduce new command line client for timedated

Much like logind has a client in loginctl, and journald in journalctl
introduce timedatectl, to change the system time (incl. RTC), timezones
and related settings.
This commit is contained in:
Lennart Poettering 2012-10-17 02:50:09 +02:00
parent 22349cee29
commit 6d0274f115
13 changed files with 959 additions and 32 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/timedatectl
/test-date
/install-tree
/systemd-journal-gatewayd

View File

@ -3194,6 +3194,24 @@ MANPAGES_ALIAS += \
man/systemd-timedated.8
man/systemd-timedated.8: man/systemd-timedated.service.8
timedatectl_SOURCES = \
src/timedate/timedatectl.c
timedatectl_CFLAGS = \
$(AM_CFLAGS) \
$(DBUS_CFLAGS)
timedatectl_LDADD = \
libsystemd-shared.la \
libsystemd-dbus.la
bin_PROGRAMS += \
timedatectl
MANPAGES += \
man/timedatectl.1
endif
polkitpolicy_in_files += \

4
TODO
View File

@ -19,6 +19,10 @@ F18:
Features:
* timedated: export boolean that clarifies whether NTP is even available
* timedated: refuse time changes when NTP is on
* journald: don't make SystemMinFileSize= configurable
* clean up date formatting and parsing so that all absolute/relative timestamps we format can also be parsed

View File

@ -64,6 +64,10 @@
is automatically activated on request and terminates
itself when it is unused.</para>
<para>The tool
<citerefentry><refentrytitle>timedatectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
is a command line client to this service.</para>
<para>See the <ulink
url="http://www.freedesktop.org/wiki/Software/systemd/timedated">
developer documentation</ulink> for information about
@ -75,6 +79,7 @@
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>timedatectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>localtime</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>hwclock</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>

243
man/timedatectl.xml Normal file
View File

@ -0,0 +1,243 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
This file is part of systemd.
Copyright 2012 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
-->
<refentry id="timedatectl">
<refentryinfo>
<title>timedatectl</title>
<productname>systemd</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Lennart</firstname>
<surname>Poettering</surname>
<email>lennart@poettering.net</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>timedatectl</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>timedatectl</refname>
<refpurpose>Control the system time and date</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>timedatectl <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="req">COMMAND</arg></command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>timedatectl</command> may be used to
query and change the system clock and its
settings.</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>--help</option></term>
<term><option>-h</option></term>
<listitem><para>Prints a short help
text and exits.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--version</option></term>
<listitem><para>Prints a short version
string and exits.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--no-pager</option></term>
<listitem><para>Do not pipe output into a
pager.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--no-ask-password</option></term>
<listitem><para>Don't query the user
for authentication for privileged
operations.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-H</option></term>
<term><option>--host</option></term>
<listitem><para>Execute operation
remotely. Specify a hostname, or
username and hostname separated by @,
to connect to. This will use SSH to
talk to a remote
system.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--fix-system</option></term>
<listitem><para>If
<command>set-local-rtc</command> is
invoked and this option is passed the
system clock is synchronized from the
RTC again, taking the new setting into
account. Otherwise the RTC is
synchonized from the system
clock.</para></listitem>
</varlistentry>
</variablelist>
<para>The following commands are understood:</para>
<variablelist>
<varlistentry>
<term><command>status</command></term>
<listitem><para>Show current settings
of the system clock and
RTC.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>set-time [TIME]</command></term>
<listitem><para>Set the system clock
to the specified time. This will also
update the RTC time accordingly. The time
may be specified in the format
"2012-10-30
18:17:16".</para></listitem>
</varlistentry>
<varlistentry>
<term><command>set-timezone [TIMEZONE]</command></term>
<listitem><para>Set the system time
zone to the specified value. Available
time zones my be listed with
<command>list-timezones</command>. If
the RTC is configured to be in the
local time this will also update the
RTC time.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>list-timezones</command></term>
<listitem><para>List available time
zones, one per line. Entries from the
list may selected as the system
timezone with
<command>set-timezone</command>.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>set-local-rtc [BOOL]</command></term>
<listitem><para>Takes a boolean
argument. If <literal>0</literal> the
system is configured to maintain the
RTC in universal time, if
<literal>1</literal> it will maintain
the RTC in local time instead. Note
that maintaining the RTC in the local
timezone is is not fully supported and
will create various problems with time
zone changes and daylight saving
adjustments. If at all possible use
RTC in UTC. Note that invoking this
will also synchronize the RTC from the
system clock, unless
<option>--fix-system</option> is
passed (see above). This command will
change the 3rd line of
<filename>/etc/adjtime</filename>, as
documented in
<citerefentry><refentrytitle>hwclock</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>set-ntp [BOOL]</command></term>
<listitem><para>Takes a boolean
argument. Controls whether NTP based
network time synchronization is
enabled (if
available).</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success 0 is returned, a non-zero failure
code otherwise.</para>
</refsect1>
<refsect1>
<title>Environment</title>
<variablelist>
<varlistentry>
<term><varname>$SYSTEMD_PAGER</varname></term>
<listitem><para>Pager to use when
<option>--no-pager</option> is not given;
overrides <varname>$PAGER</varname>. Setting
this to an empty string or the value
<literal>cat</literal> is equivalent to passing
<option>--no-pager</option>.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>hwclock</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>date</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-timedated.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -83,6 +83,6 @@ KERNEL=="rfkill", MODE="0644"
KERNEL=="fuse", ACTION=="add", MODE="0666", OPTIONS+="static_node=fuse"
SUBSYSTEM=="rtc", ATTR{hctosys}=="1", SYMLINK+="rtc"
SUBSYSTEM=="rtc", ATTR{hctosys}=="1", MODE="0644", SYMLINK+="rtc"
SUBSYSTEM=="firmware", ACTION=="add", IMPORT{builtin}="firmware"

View File

@ -862,7 +862,7 @@ static int print_property(const char *name, DBusMessageIter *iter) {
}
static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) {
DBusMessage *reply = NULL;
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
const char *interface = "";
int r;
DBusMessageIter iter, sub, sub2, sub3;
@ -877,7 +877,7 @@ static int show_one(const char *verb, DBusConnection *bus, const char *path, boo
zero(user_info);
zero(seat_info);
r = bus_method_call_with_reply (
r = bus_method_call_with_reply(
bus,
"org.freedesktop.login1",
path,
@ -887,7 +887,7 @@ static int show_one(const char *verb, DBusConnection *bus, const char *path, boo
NULL,
DBUS_TYPE_STRING, &interface,
DBUS_TYPE_INVALID);
if (r)
if (r < 0)
goto finish;
if (!dbus_message_iter_init(reply, &iter) ||
@ -941,7 +941,6 @@ static int show_one(const char *verb, DBusConnection *bus, const char *path, boo
if (r < 0) {
log_error("Failed to parse reply.");
r = -EIO;
goto finish;
}
@ -957,14 +956,11 @@ static int show_one(const char *verb, DBusConnection *bus, const char *path, boo
print_seat_status_info(&seat_info);
}
strv_free(seat_info.sessions);
strv_free(user_info.sessions);
r = 0;
finish:
if (reply)
dbus_message_unref(reply);
strv_free(seat_info.sessions);
strv_free(user_info.sessions);
return r;
}
@ -1339,16 +1335,16 @@ static int help(void) {
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
"Send control commands to or query the login manager.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" -p --property=NAME Show only properties by this name\n"
" -a --all Show all properties, including empty ones\n"
" --kill-who=WHO Who to send signal to\n"
" -s --signal=SIGNAL Which signal to send\n"
" -H --host=[USER@]HOST\n"
" Show information for remote host\n"
" -P --privileged Acquire privileges before execution\n"
" --no-pager Do not pipe output into a pager\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" -p --property=NAME Show only properties by this name\n"
" -a --all Show all properties, including empty ones\n"
" --kill-who=WHO Who to send signal to\n"
" -s --signal=SIGNAL Which signal to send\n"
" --no-ask-password Don't prompt for password\n"
" -H --host=[USER@]HOST Show information for remote host\n"
" -P --privileged Acquire privileges before execution\n"
" --no-pager Do not pipe output into a pager\n\n"
"Commands:\n"
" list-sessions List sessions\n"
" session-status [ID...] Show session status\n"
@ -1387,17 +1383,17 @@ static int parse_argv(int argc, char *argv[]) {
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "property", required_argument, NULL, 'p' },
{ "all", no_argument, NULL, 'a' },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "kill-who", required_argument, NULL, ARG_KILL_WHO },
{ "signal", required_argument, NULL, 's' },
{ "host", required_argument, NULL, 'H' },
{ "privileged",no_argument, NULL, 'P' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ NULL, 0, NULL, 0 }
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "property", required_argument, NULL, 'p' },
{ "all", no_argument, NULL, 'a' },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "kill-who", required_argument, NULL, ARG_KILL_WHO },
{ "signal", required_argument, NULL, 's' },
{ "host", required_argument, NULL, 'H' },
{ "privileged", no_argument, NULL, 'P' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ NULL, 0, NULL, 0 }
};
int c;

View File

@ -22,6 +22,7 @@
***/
#include <dbus/dbus.h>
#include <inttypes.h>
#ifndef DBUS_ERROR_UNKNOWN_OBJECT
#define DBUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject"

View File

@ -192,6 +192,7 @@ static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) {
#define _cleanup_close_ __attribute__((cleanup(closep)))
#define _cleanup_closedir_ __attribute__((cleanup(closedirp)))
#define _cleanup_umask_ __attribute__((cleanup(umaskp)))
#define _cleanup_strv_free_ __attribute__((cleanup(strv_freep)))
#define VA_FORMAT_ADVANCE(format, ap) \
do { \

View File

@ -64,6 +64,14 @@ void strv_free(char **l) {
free(l);
}
void strv_freep(char ***l) {
if (!l)
return;
strv_free(*l);
*l = NULL;
}
char **strv_copy(char **l) {
char **r, **k;

View File

@ -30,6 +30,7 @@ char *strv_find(char **l, const char *name);
char *strv_find_prefix(char **l, const char *name);
void strv_free(char **l);
void strv_freep(char ***l);
char **strv_copy(char **l) _malloc_;
unsigned strv_length(char **l);

View File

@ -1959,7 +1959,7 @@ char *format_timestamp(char *buf, size_t l, usec_t t) {
sec = (time_t) (t / USEC_PER_SEC);
if (strftime(buf, l, "%a, %d %b %Y %H:%M:%S %z", localtime_r(&sec, &tm)) <= 0)
if (strftime(buf, l, "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) <= 0)
return NULL;
return buf;

649
src/timedate/timedatectl.c Normal file
View File

@ -0,0 +1,649 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2012 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <sys/timex.h>
#include "dbus-common.h"
#include "util.h"
#include "spawn-polkit-agent.h"
#include "build.h"
#include "hwclock.h"
#include "strv.h"
#include "pager.h"
static bool arg_fix_system = false;
static bool arg_no_pager = false;
static enum transport {
TRANSPORT_NORMAL,
TRANSPORT_SSH,
TRANSPORT_POLKIT
} arg_transport = TRANSPORT_NORMAL;
static bool arg_ask_password = true;
static const char *arg_host = NULL;
static void pager_open_if_enabled(void) {
if (arg_no_pager)
return;
pager_open();
}
static void polkit_agent_open_if_enabled(void) {
/* Open the polkit agent as a child process if necessary */
if (!arg_ask_password)
return;
polkit_agent_open();
}
typedef struct StatusInfo {
const char *timezone;
bool local_rtc;
bool ntp;
} StatusInfo;
static bool ntp_synced(void) {
struct timex txc;
zero(txc);
if (adjtimex(&txc) < 0)
return false;
if (txc.status & STA_UNSYNC)
return false;
return true;
}
static void print_status_info(StatusInfo *i) {
usec_t n;
char b[FORMAT_TIMESTAMP_MAX];
struct tm tm;
time_t sec;
int r;
n = now(CLOCK_REALTIME);
sec = (time_t) (n / USEC_PER_SEC);
zero(tm);
assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
char_array_0(b);
printf(" Local time: %s\n", b);
zero(tm);
assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
char_array_0(b);
printf(" Universal time: %s\n", b);
zero(tm);
r = hwclock_get_time(&tm);
if (r >= 0) {
/* Calculcate the week-day */
mktime(&tm);
assert_se(strftime(b, sizeof(b), "%a, %Y-%m-%d %H:%M:%S", &tm) > 0);
char_array_0(b);
printf(" RTC time: %s\n", b);
}
printf(" Timezone: %s\n"
" NTP enabled: %s\n"
"NTP synchronized: %s\n"
" RTC in local TZ: %s\n",
strna(i->timezone),
yes_no(i->ntp),
yes_no(ntp_synced()),
yes_no(i->local_rtc));
if (i->local_rtc)
fputs("\n" ANSI_HIGHLIGHT_ON
"Warning: The RTC is configured to maintain time in the local time zone. This\n"
" mode is not fully supported and will create various problems with time\n"
" zone changes and daylight saving adjustments. If at all possible use\n"
" RTC in UTC, by calling 'timedatectl set-local-rtc 0'" ANSI_HIGHLIGHT_OFF ".\n", stdout);
}
static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
assert(name);
assert(iter);
switch (dbus_message_iter_get_arg_type(iter)) {
case DBUS_TYPE_STRING: {
const char *s;
dbus_message_iter_get_basic(iter, &s);
if (!isempty(s)) {
if (streq(name, "Timezone"))
i->timezone = s;
}
break;
}
case DBUS_TYPE_BOOLEAN: {
dbus_bool_t b;
dbus_message_iter_get_basic(iter, &b);
if (streq(name, "LocalRTC"))
i->local_rtc = b;
else if (streq(name, "NTP"))
i->ntp = b;
}
}
return 0;
}
static int show_status(DBusConnection *bus, char **args, unsigned n) {
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
const char *interface = "";
int r;
DBusMessageIter iter, sub, sub2, sub3;
StatusInfo info;
assert(args);
r = bus_method_call_with_reply(
bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.DBus.Properties",
"GetAll",
&reply,
NULL,
DBUS_TYPE_STRING, &interface,
DBUS_TYPE_INVALID);
if (r < 0)
return r;
if (!dbus_message_iter_init(reply, &iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
log_error("Failed to parse reply.");
return -EIO;
}
dbus_message_iter_recurse(&iter, &sub);
while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
const char *name;
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
log_error("Failed to parse reply.");
return -EIO;
}
dbus_message_iter_recurse(&sub, &sub2);
if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
log_error("Failed to parse reply.");
return -EIO;
}
if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
log_error("Failed to parse reply.");
return -EIO;
}
dbus_message_iter_recurse(&sub2, &sub3);
r = status_property(name, &sub3, &info);
if (r < 0) {
log_error("Failed to parse reply.");
return r;
}
dbus_message_iter_next(&sub);
}
print_status_info(&info);
return 0;
}
static int set_time(DBusConnection *bus, char **args, unsigned n) {
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
dbus_bool_t relative = false, interactive = true;
usec_t t;
dbus_int64_t u;
int r;
assert(args);
assert(n == 2);
polkit_agent_open_if_enabled();
r = parse_timestamp(args[1], &t);
if (r < 0) {
log_error("Failed to parse time specification: %s", args[1]);
return r;
}
u = (dbus_uint64_t) t;
return bus_method_call_with_reply(
bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
"SetTime",
&reply,
NULL,
DBUS_TYPE_INT64, &u,
DBUS_TYPE_BOOLEAN, &relative,
DBUS_TYPE_BOOLEAN, &interactive,
DBUS_TYPE_INVALID);
}
static int set_timezone(DBusConnection *bus, char **args, unsigned n) {
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
dbus_bool_t interactive = true;
assert(args);
assert(n == 2);
polkit_agent_open_if_enabled();
return bus_method_call_with_reply(
bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
"SetTimezone",
&reply,
NULL,
DBUS_TYPE_STRING, &args[1],
DBUS_TYPE_BOOLEAN, &interactive,
DBUS_TYPE_INVALID);
}
static int set_local_rtc(DBusConnection *bus, char **args, unsigned n) {
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
dbus_bool_t interactive = true, b, q;
int r;
assert(args);
assert(n == 2);
polkit_agent_open_if_enabled();
r = parse_boolean(args[1]);
if (r < 0) {
log_error("Failed to parse local RTC setting: %s", args[1]);
return r;
}
b = r;
q = arg_fix_system;
return bus_method_call_with_reply(
bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
"SetLocalRTC",
&reply,
NULL,
DBUS_TYPE_BOOLEAN, &b,
DBUS_TYPE_BOOLEAN, &q,
DBUS_TYPE_BOOLEAN, &interactive,
DBUS_TYPE_INVALID);
}
static int set_ntp(DBusConnection *bus, char **args, unsigned n) {
_cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
dbus_bool_t interactive = true, b;
int r;
assert(args);
assert(n == 2);
polkit_agent_open_if_enabled();
r = parse_boolean(args[1]);
if (r < 0) {
log_error("Failed to parse NTP setting: %s", args[1]);
return r;
}
b = r;
return bus_method_call_with_reply(
bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
"SetNTP",
&reply,
NULL,
DBUS_TYPE_BOOLEAN, &b,
DBUS_TYPE_BOOLEAN, &interactive,
DBUS_TYPE_INVALID);
}
static int zone_compare(const void *_a, const void *_b) {
const char **a = (const char**) _a, **b = (const char**) _b;
return strcmp(*a, *b);
}
static int list_timezones(DBusConnection *bus, char **args, unsigned n) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **zones = NULL;
size_t n_zones;
char **i;
assert(args);
assert(n == 1);
f = fopen("/usr/share/zoneinfo/zone.tab", "re");
if (!f) {
log_error("Failed to open timezone database: %m");
return -errno;
}
for (;;) {
char l[LINE_MAX], *p, **z, *w;
size_t k;
if (!fgets(l, sizeof(l), f)) {
if (feof(f))
break;
log_error("Failed to read timezone database: %m");
return -errno;
}
p = strstrip(l);
if (isempty(p) || *p == '#')
continue;
/* Skip over country code */
p += strcspn(p, WHITESPACE);
p += strspn(p, WHITESPACE);
/* Skip over coordinates */
p += strcspn(p, WHITESPACE);
p += strspn(p, WHITESPACE);
/* Found timezone name */
k = strcspn(p, WHITESPACE);
if (k <= 0)
continue;
w = strndup(p, k);
if (!w)
return log_oom();
z = realloc(zones, sizeof(char*) * (n_zones + 2));
if (!z) {
free(w);
return log_oom();
}
zones = z;
zones[n_zones++] = w;
}
if (zones)
zones[n_zones] = 0;
qsort(zones, n_zones, sizeof(char*), zone_compare);
pager_open_if_enabled();
STRV_FOREACH(i, zones)
puts(*i);
return 0;
}
static int help(void) {
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
"Query or control system time and date settings.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --fix-system Adjust system clock when changing local RTC mode\n"
" --no-pager Do not pipe output into a pager\n"
" --no-ask-password Do not prompt for password\n"
" -H --host=[USER@]HOST Operate on remote host\n\n"
"Commands:\n"
" status Show current time settings\n"
" set-time [TIME] Set system time\n"
" set-timezone [ZONE] Set system timezone\n"
" list-timezones Show known timezones\n"
" set-local-rtc [BOOL] Control whether RTC is in local time\n"
" set-ntp [BOOL] Control whether NTP is enabled\n",
program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_FIX_SYSTEM,
ARG_NO_ASK_PASSWORD
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "host", required_argument, NULL, 'H' },
{ "privileged", no_argument, NULL, 'P' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "fix-system", no_argument, NULL, ARG_FIX_SYSTEM },
{ NULL, 0, NULL, 0 }
};
int c;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "+hp:as:H:P", options, NULL)) >= 0) {
switch (c) {
case 'h':
help();
return 0;
case ARG_VERSION:
puts(PACKAGE_STRING);
puts(DISTRIBUTION);
puts(SYSTEMD_FEATURES);
return 0;
case 'P':
arg_transport = TRANSPORT_POLKIT;
break;
case 'H':
arg_transport = TRANSPORT_SSH;
arg_host = optarg;
break;
case ARG_FIX_SYSTEM:
arg_fix_system = true;
break;
case ARG_NO_PAGER:
arg_no_pager = true;
break;
case '?':
return -EINVAL;
default:
log_error("Unknown option code %c", c);
return -EINVAL;
}
}
return 1;
}
static int timedatectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
static const struct {
const char* verb;
const enum {
MORE,
LESS,
EQUAL
} argc_cmp;
const int argc;
int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
} verbs[] = {
{ "status", LESS, 1, show_status },
{ "set-time", EQUAL, 2, set_time },
{ "set-timezone", EQUAL, 2, set_timezone },
{ "list-timezones", EQUAL, 1, list_timezones },
{ "set-local-rtc", EQUAL, 2, set_local_rtc },
{ "set-ntp", EQUAL, 2, set_ntp, },
};
int left;
unsigned i;
assert(argc >= 0);
assert(argv);
assert(error);
left = argc - optind;
if (left <= 0)
/* Special rule: no arguments means "status" */
i = 0;
else {
if (streq(argv[optind], "help")) {
help();
return 0;
}
for (i = 0; i < ELEMENTSOF(verbs); i++)
if (streq(argv[optind], verbs[i].verb))
break;
if (i >= ELEMENTSOF(verbs)) {
log_error("Unknown operation %s", argv[optind]);
return -EINVAL;
}
}
switch (verbs[i].argc_cmp) {
case EQUAL:
if (left != verbs[i].argc) {
log_error("Invalid number of arguments.");
return -EINVAL;
}
break;
case MORE:
if (left < verbs[i].argc) {
log_error("Too few arguments.");
return -EINVAL;
}
break;
case LESS:
if (left > verbs[i].argc) {
log_error("Too many arguments.");
return -EINVAL;
}
break;
default:
assert_not_reached("Unknown comparison operator.");
}
if (!bus) {
log_error("Failed to get D-Bus connection: %s", error->message);
return -EIO;
}
return verbs[i].dispatch(bus, argv + optind, left);
}
int main(int argc, char *argv[]) {
int r, retval = EXIT_FAILURE;
DBusConnection *bus = NULL;
DBusError error;
dbus_error_init(&error);
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r < 0)
goto finish;
else if (r == 0) {
retval = EXIT_SUCCESS;
goto finish;
}
if (arg_transport == TRANSPORT_NORMAL)
bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
else if (arg_transport == TRANSPORT_POLKIT)
bus_connect_system_polkit(&bus, &error);
else if (arg_transport == TRANSPORT_SSH)
bus_connect_system_ssh(NULL, arg_host, &bus, &error);
else
assert_not_reached("Uh, invalid transport...");
r = timedatectl_main(bus, argc, argv, &error);
retval = r < 0 ? EXIT_FAILURE : r;
finish:
if (bus) {
dbus_connection_flush(bus);
dbus_connection_close(bus);
dbus_connection_unref(bus);
}
dbus_error_free(&error);
dbus_shutdown();
pager_close();
return retval;
}