timedatectl.c revision e1636421f46db6d06fbd028ef20a3113fa3e11f8
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering/***
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering This file is part of systemd.
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering Copyright 2012 Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering Copyright 2013 Kay Sievers
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering systemd is free software; you can redistribute it and/or modify it
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering under the terms of the GNU Lesser General Public License as published by
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering (at your option) any later version.
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering systemd is distributed in the hope that it will be useful, but
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering Lesser General Public License for more details.
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering You should have received a copy of the GNU Lesser General Public License
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering***/
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include <stdlib.h>
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include <stdbool.h>
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include <unistd.h>
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include <getopt.h>
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include <locale.h>
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include <string.h>
c7f1808add4d971229ba5311cf66e659122aa338Lennart Poettering#include <sys/timex.h>
c7f1808add4d971229ba5311cf66e659122aa338Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "sd-bus.h"
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "bus-util.h"
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "bus-error.h"
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "util.h"
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "spawn-polkit-agent.h"
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "build.h"
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "strv.h"
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "pager.h"
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering#include "time-dst.h"
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poetteringstatic bool arg_no_pager = false;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poetteringstatic bool arg_ask_password = true;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poetteringstatic BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poetteringstatic char *arg_host = NULL;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poetteringstatic bool arg_adjust_system_clock = false;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poetteringstatic void pager_open_if_enabled(void) {
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering if (arg_no_pager)
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering return;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering pager_open(false);
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering}
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poetteringstatic void polkit_agent_open_if_enabled(void) {
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering /* Open the polkit agent as a child process if necessary */
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering if (!arg_ask_password)
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering return;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering polkit_agent_open();
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering}
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poetteringtypedef struct StatusInfo {
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering usec_t time;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering const char *timezone;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering usec_t rtc_time;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering bool rtc_local;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering bool ntp_enabled;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering bool ntp_capable;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering bool ntp_synced;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering} StatusInfo;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poetteringstatic const char *jump_str(int delta_minutes, char *s, size_t size) {
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering if (delta_minutes == 60)
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering return "one hour forward";
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering if (delta_minutes == -60)
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering return "one hour backwards";
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering if (delta_minutes < 0) {
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering snprintf(s, size, "%i minutes backwards", -delta_minutes);
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering return s;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering }
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering if (delta_minutes > 0) {
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering snprintf(s, size, "%i minutes forward", delta_minutes);
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering return s;
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering }
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering return "";
6bedfcbb2970e06a4d3280c8fb62083d252ede73Lennart Poettering}
static void print_status_info(StatusInfo *i) {
char a[FORMAT_TIMESTAMP_MAX];
char b[FORMAT_TIMESTAMP_MAX];
char s[32];
struct tm tm;
time_t sec;
char *zc, *zn;
time_t t, tc, tn;
int dn;
bool is_dstc, is_dstn;
int r;
assert(i);
/* Enforce the values of /etc/localtime */
if (getenv("TZ")) {
fprintf(stderr, "Warning: ignoring the TZ variable, reading the system's timezone setting only.\n\n");
unsetenv("TZ");
}
sec = (time_t) (i->time / USEC_PER_SEC);
zero(tm);
assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) > 0);
char_array_0(a);
printf(" Local time: %s\n", a);
zero(tm);
assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) > 0);
char_array_0(a);
printf(" Universal time: %s\n", a);
if (i->rtc_time > 0) {
time_t rtc_sec;
rtc_sec = (time_t)(i->rtc_time / USEC_PER_SEC);
zero(tm);
assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)) > 0);
char_array_0(a);
printf(" RTC time: %s\n", a);
} else
printf(" RTC time: n/a\n");
zero(tm);
assert_se(strftime(a, sizeof(a), "%Z, %z", localtime_r(&sec, &tm)) > 0);
char_array_0(a);
printf(" Timezone: %s (%s)\n"
" NTP enabled: %s\n"
"NTP synchronized: %s\n"
" RTC in local TZ: %s\n",
strna(i->timezone), a,
i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a",
yes_no(i->ntp_synced),
yes_no(i->rtc_local));
r = time_get_dst(sec, "/etc/localtime",
&tc, &zc, &is_dstc,
&tn, &dn, &zn, &is_dstn);
if (r < 0)
printf(" DST active: n/a\n");
else {
printf(" DST active: %s\n", yes_no(is_dstc));
t = tc - 1;
zero(tm);
assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
char_array_0(a);
zero(tm);
assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tc, &tm)) > 0);
char_array_0(b);
printf(" Last DST change: DST %s at\n"
" %s\n"
" %s\n",
is_dstc ? "began" : "ended", a, b);
t = tn - 1;
zero(tm);
assert_se(strftime(a, sizeof(a), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&t, &tm)) > 0);
char_array_0(a);
zero(tm);
assert_se(strftime(b, sizeof(b), "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&tn, &tm)) > 0);
char_array_0(b);
printf(" Next DST change: DST %s (the clock jumps %s) at\n"
" %s\n"
" %s\n",
is_dstn ? "begins" : "ends", jump_str(dn, s, sizeof(s)), a, b);
free(zc);
free(zn);
}
if (i->rtc_local)
fputs("\n" ANSI_HIGHLIGHT_ON
"Warning: The RTC is configured to maintain time in the local timezone. 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 get_timedate_property_bool(sd_bus *bus, const char *name, bool *target) {
_cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
int r, b;
assert(name);
r = sd_bus_get_property_trivial(
bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
name,
&error,
'b', &b);
if (r < 0) {
log_error("Failed to get property: %s %s", name, bus_error_message(&error, -r));
return r;
}
*target = b;
return 0;
}
static int get_timedate_property_usec(sd_bus *bus, const char *name, usec_t *target) {
_cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(name);
r = sd_bus_get_property_trivial(
bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
name,
&error,
't', target);
if (r < 0) {
log_error("Failed to get property: %s %s", name, bus_error_message(&error, -r));
return r;
}
return 0;
}
static int show_status(sd_bus *bus, char **args, unsigned n) {
_cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
StatusInfo info = {};
assert(bus);
r = sd_bus_get_property(
bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
"Timezone",
&error,
&reply,
"s");
if (r < 0) {
log_error("Failed to get property: Timezone %s", bus_error_message(&error, -r));
return r;
}
r = sd_bus_message_read(reply, "s", &info.timezone);
if (r < 0)
return r;
r = get_timedate_property_bool(bus, "LocalRTC", &info.rtc_local);
if (r < 0)
return r;
r = get_timedate_property_bool(bus, "NTP", &info.ntp_enabled);
if (r < 0)
return r;
r = get_timedate_property_bool(bus, "CanNTP", &info.ntp_capable);
if (r < 0)
return r;
r = get_timedate_property_bool(bus, "NTPSynchronized", &info.ntp_synced);
if (r < 0)
return r;
r = get_timedate_property_usec(bus, "TimeUSec", &info.time);
if (r < 0)
return r;
r = get_timedate_property_usec(bus, "RTCTimeUSec", &info.rtc_time);
if (r < 0)
return r;
print_status_info(&info);
return 0;
}
static int set_time(sd_bus *bus, char **args, unsigned n) {
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
bool relative = false, interactive = arg_ask_password;
usec_t t;
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;
}
r = sd_bus_call_method(bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
"SetTime",
&error,
NULL,
"xbb", (int64_t)t, relative, interactive);
if (r < 0)
log_error("Failed to set time: %s", bus_error_message(&error, -r));
return r;
}
static int set_timezone(sd_bus *bus, char **args, unsigned n) {
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(args);
assert(n == 2);
polkit_agent_open_if_enabled();
r = sd_bus_call_method(bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
"SetTimezone",
&error,
NULL,
"sb", args[1], arg_ask_password);
if (r < 0)
log_error("Failed to set timezone: %s", bus_error_message(&error, -r));
return r;
}
static int set_local_rtc(sd_bus *bus, char **args, unsigned n) {
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
int r, b;
assert(args);
assert(n == 2);
polkit_agent_open_if_enabled();
b = parse_boolean(args[1]);
if (b < 0) {
log_error("Failed to parse local RTC setting: %s", args[1]);
return b;
}
r = sd_bus_call_method(bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
"SetLocalRTC",
&error,
NULL,
"bbb", b, arg_adjust_system_clock, arg_ask_password);
if (r < 0)
log_error("Failed to set local RTC: %s", bus_error_message(&error, -r));
return r;
}
static int set_ntp(sd_bus *bus, char **args, unsigned n) {
_cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
int b, r;
assert(args);
assert(n == 2);
polkit_agent_open_if_enabled();
b = parse_boolean(args[1]);
if (b < 0) {
log_error("Failed to parse NTP setting: %s", args[1]);
return b;
}
r = sd_bus_call_method(bus,
"org.freedesktop.timedate1",
"/org/freedesktop/timedate1",
"org.freedesktop.timedate1",
"SetNTP",
&error,
NULL,
"bb", b, arg_ask_password);
if (r < 0)
log_error("Failed to set ntp: %s", bus_error_message(&error, -r));
return r;
}
static int list_timezones(sd_bus *bus, char **args, unsigned n) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **zones = NULL;
size_t n_zones = 0;
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] = NULL;
pager_open_if_enabled();
strv_sort(zones);
strv_print(zones);
return 0;
}
static int help(void) {
printf("%s [OPTIONS...] COMMAND ...\n\n"
"Query or change system time and date settings.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --adjust-system-clock\n"
" 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"
" -M --machine=CONTAINER Operate on local container\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_ADJUST_SYSTEM_CLOCK,
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' },
{ "machine", required_argument, NULL, 'M' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK },
{ NULL, 0, NULL, 0 }
};
int c;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
switch (c) {
case 'h':
help();
return 0;
case ARG_VERSION:
puts(PACKAGE_STRING);
puts(SYSTEMD_FEATURES);
return 0;
case 'H':
arg_transport = BUS_TRANSPORT_REMOTE;
arg_host = optarg;
break;
case 'M':
arg_transport = BUS_TRANSPORT_CONTAINER;
arg_host = optarg;
break;
case ARG_NO_ASK_PASSWORD:
arg_ask_password = false;
break;
case ARG_ADJUST_SYSTEM_CLOCK:
arg_adjust_system_clock = 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(sd_bus *bus, int argc, char *argv[]) {
static const struct {
const char* verb;
const enum {
MORE,
LESS,
EQUAL
} argc_cmp;
const int argc;
int (* const dispatch)(sd_bus *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);
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.");
}
return verbs[i].dispatch(bus, argv + optind, left);
}
int main(int argc, char *argv[]) {
int r, ret = EXIT_FAILURE;
_cleanup_bus_unref_ sd_bus *bus = NULL;
setlocale(LC_ALL, "");
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r < 0)
goto finish;
else if (r == 0) {
ret = EXIT_SUCCESS;
goto finish;
}
r = bus_open_transport(arg_transport, arg_host, false, &bus);
if (r < 0) {
log_error("Failed to create bus connection: %s", strerror(-r));
ret = EXIT_FAILURE;
goto finish;
}
r = timedatectl_main(bus, argc, argv);
ret = r < 0 ? EXIT_FAILURE : r;
finish:
pager_close();
return ret;
}