diff options
-rw-r--r-- | hwdb/70-pointingstick.hwdb | 3 | ||||
-rw-r--r-- | man/journalctl.xml | 41 | ||||
-rw-r--r-- | man/systemd.time.xml | 150 | ||||
-rw-r--r-- | shell-completion/bash/journalctl | 2 | ||||
-rw-r--r-- | shell-completion/bash/systemctl.in | 2 | ||||
-rw-r--r-- | shell-completion/zsh/_sd_outputmodes | 2 | ||||
-rw-r--r-- | src/basic/calendarspec.c | 43 | ||||
-rw-r--r-- | src/basic/calendarspec.h | 1 | ||||
-rw-r--r-- | src/basic/time-util.c | 146 | ||||
-rw-r--r-- | src/basic/time-util.h | 4 | ||||
-rw-r--r-- | src/shared/logs-show.c | 183 | ||||
-rw-r--r-- | src/shared/output-mode.c | 1 | ||||
-rw-r--r-- | src/shared/output-mode.h | 1 | ||||
-rw-r--r-- | src/systemctl/systemctl.c | 2 | ||||
-rw-r--r-- | src/test/test-calendarspec.c | 23 | ||||
-rw-r--r-- | src/test/test-time.c | 44 |
16 files changed, 451 insertions, 197 deletions
diff --git a/hwdb/70-pointingstick.hwdb b/hwdb/70-pointingstick.hwdb index ff52f11e45..c0ec8ffbe0 100644 --- a/hwdb/70-pointingstick.hwdb +++ b/hwdb/70-pointingstick.hwdb @@ -96,10 +96,13 @@ evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadX230 # Lenovo Thinkpad X230 tablet evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadX230Tablet:* # Lenovo Thinkpad *40 series +evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??40:* evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??40?:* # Lenovo Thinkpad *50 series +evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??50:* evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??50?:* # Lenovo Thinkpad *60 series +evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??60:* evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPad??60?:* # Lenovo Thinkpad X1 Carbon 3rd gen evdev:name:TPPS/2 IBM TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*:pvrThinkPadX1Carbon3rd:* diff --git a/man/journalctl.xml b/man/journalctl.xml index e77621d7b3..c448a29a51 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -250,6 +250,18 @@ <varlistentry> <term> + <option>short-full</option> + </term> + <listitem> + <para>is very similar, but shows timestamps in the format the <option>--since=</option> and + <option>--until=</option> options accept. Unlike the timestamp information shown in + <option>short</option> output mode this mode includes weekday, year and timezone information in the + output, and is locale-independent.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term> <option>short-iso</option> </term> <listitem> @@ -572,24 +584,17 @@ <term><option>-U</option></term> <term><option>--until=</option></term> - <listitem><para>Start showing entries on or newer than the - specified date, or on or older than the specified date, - respectively. Date specifications should be of the format - <literal>2012-10-30 18:17:16</literal>. If the time part is - omitted, <literal>00:00:00</literal> is assumed. If only the - seconds component is omitted, <literal>:00</literal> is - assumed. If the date component is omitted, the current day is - assumed. Alternatively the strings - <literal>yesterday</literal>, <literal>today</literal>, - <literal>tomorrow</literal> are understood, which refer to - 00:00:00 of the day before the current day, the current day, - or the day after the current day, - respectively. <literal>now</literal> refers to the current - time. Finally, relative times may be specified, prefixed with - <literal>-</literal> or <literal>+</literal>, referring to - times before or after the current time, respectively. For complete - time and date specification, see - <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>. + <listitem><para>Start showing entries on or newer than the specified date, or on or older than the specified + date, respectively. Date specifications should be of the format <literal>2012-10-30 18:17:16</literal>. If the + time part is omitted, <literal>00:00:00</literal> is assumed. If only the seconds component is omitted, + <literal>:00</literal> is assumed. If the date component is omitted, the current day is assumed. Alternatively + the strings <literal>yesterday</literal>, <literal>today</literal>, <literal>tomorrow</literal> are understood, + which refer to 00:00:00 of the day before the current day, the current day, or the day after the current day, + respectively. <literal>now</literal> refers to the current time. Finally, relative times may be specified, + prefixed with <literal>-</literal> or <literal>+</literal>, referring to times before or after the current + time, respectively. For complete time and date specification, see + <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>. Note that + <option>--output=short-full</option> prints timestamps that follow precisely this format. </para> </listitem> </varlistentry> diff --git a/man/systemd.time.xml b/man/systemd.time.xml index aae3accb6c..47229b4a4e 100644 --- a/man/systemd.time.xml +++ b/man/systemd.time.xml @@ -57,14 +57,13 @@ <refsect1> <title>Displaying Time Spans</title> - <para>Time spans refer to time durations. On display, systemd will - present time spans as a space-separated series of time values each - suffixed by a time unit.</para> + <para>Time spans refer to time durations. On display, systemd will present time spans as a space-separated series + of time values each suffixed by a time unit. Example:</para> <programlisting>2h 30min</programlisting> - <para>All specified time values are meant to be added up. The - above hence refers to 150 minutes.</para> + <para>All specified time values are meant to be added up. The above hence refers to 150 minutes. Display is + locale-independent, only English names for the time units are used.</para> </refsect1> <refsect1> @@ -83,13 +82,13 @@ <listitem><para>days, day, d</para></listitem> <listitem><para>weeks, week, w</para></listitem> <listitem><para>months, month, M (defined as 30.44 days)</para></listitem> - <listitem><para>years, year, y (define as 365.25 days)</para></listitem> + <listitem><para>years, year, y (defined as 365.25 days)</para></listitem> </itemizedlist> - <para>If no time unit is specified, generally seconds are assumed, - but some exceptions exist and are marked as such. In a few cases - <literal>ns</literal>, <literal>nsec</literal> is accepted too, - where the granularity of the time span allows for this.</para> + <para>If no time unit is specified, generally seconds are assumed, but some exceptions exist and are marked as + such. In a few cases <literal>ns</literal>, <literal>nsec</literal> is accepted too, where the granularity of the + time span permits this. Parsing is generally locale-independent, non-English names for the time units are not + accepted.</para> <para>Examples for valid time span specifications:</para> @@ -110,30 +109,29 @@ <programlisting>Fri 2012-11-23 23:02:15 CET</programlisting> - <para>The weekday is printed according to the locale choice of the - user.</para> + <para>The weekday is printed in the abbreviated English language form. The formatting is locale-independent.</para> + + <para>In some cases timestamps are shown in the UTC timezone instead of the local timezone, which is indicated via + the <literal>UTC</literal> timezone specifier in the output.</para> + + <para>In some cases timestamps are shown with microsecond granularity. In this case the sub-second remainder is + separated by a full stop from the seconds component.</para> </refsect1> <refsect1> <title>Parsing Timestamps</title> - <para>When parsing, systemd will accept a similar syntax, but - expects no timezone specification, unless it is given as the - literal string "UTC". In this case, the time is considered in UTC, - otherwise in the local timezone. The weekday specification is - optional, but when the weekday is specified, it must either be in - the abbreviated (<literal>Wed</literal>) or non-abbreviated - (<literal>Wednesday</literal>) English language form (case does - not matter), and is not subject to the locale choice of the user. - Either the date, or the time part may be omitted, in which case - the current date or 00:00:00, respectively, is assumed. The seconds - component of the time may also be omitted, in which case ":00" is - assumed. Year numbers may be specified in full or may be - abbreviated (omitting the century).</para> - - <para>A timestamp is considered invalid if a weekday is specified - and the date does not actually match the specified day of the - week.</para> + <para>When parsing, systemd will accept a similar syntax, but expects no timezone specification, unless it is given + as the literal string <literal>UTC</literal> (for the UTC timezone) or is specified to be the locally configured + timezone. Other timezones than the local and UTC are not supported. The weekday specification is optional, but when + the weekday is specified, it must either be in the abbreviated (<literal>Wed</literal>) or non-abbreviated + (<literal>Wednesday</literal>) English language form (case does not matter), and is not subject to the locale + choice of the user. Either the date, or the time part may be omitted, in which case the current date or 00:00:00, + respectively, is assumed. The seconds component of the time may also be omitted, in which case ":00" is + assumed. Year numbers may be specified in full or may be abbreviated (omitting the century).</para> + + <para>A timestamp is considered invalid if a weekday is specified and the date does not match the specified day of + the week.</para> <para>When parsing, systemd will also accept a few special placeholders instead of timestamps: <literal>now</literal> may be @@ -167,8 +165,6 @@ 2012-11-23 → Fri 2012-11-23 00:00:00 12-11-23 → Fri 2012-11-23 00:00:00 11:12:13 → Fri 2012-11-23 11:12:13 - 11:12:13.9900009 → Fri 2012-11-23 11:12:13 - format_timestamp_us: Fri 2012-11-23 11:12:13.990000 11:12 → Fri 2012-11-23 11:12:00 now → Fri 2012-11-23 18:15:22 today → Fri 2012-11-23 00:00:00 @@ -176,28 +172,25 @@ yesterday → Fri 2012-11-22 00:00:00 tomorrow → Fri 2012-11-24 00:00:00 +3h30min → Fri 2012-11-23 21:45:22 - +3h30min UTC → -EINVAL -5s → Fri 2012-11-23 18:15:17 11min ago → Fri 2012-11-23 18:04:22 - 11min ago UTC → -EINVAL @1395716396 → Tue 2014-03-25 03:59:56</programlisting> - <para>Note that timestamps printed by systemd will not be parsed - correctly by systemd, as the timezone specification is not - accepted, and printing timestamps is subject to locale settings - for the weekday, while parsing only accepts English weekday - names.</para> + <para>Note that timestamps displayed by remote systems with a non-matching timezone are usually not parsable + locally, as the timezone component is not understood (unless it happens to be <literal>UTC</literal>).</para> - <para>In some cases, systemd will display a relative timestamp - (relative to the current time, or the time of invocation of the - command) instead or in addition to an absolute timestamp as - described above. A relative timestamp is formatted as - follows:</para> + <para>Timestamps may also be specified with microsecond granularity. The sub-second remainder is expected separated + by a full stop from the seconds component. Example:</para> + + <programlisting>2014-03-25 03:59:56.654563</programlisting> + + <para>In some cases, systemd will display a relative timestamp (relative to the current time, or the time of + invocation of the command) instead of or in addition to an absolute timestamp as described above. A relative + timestamp is formatted as follows:</para> - <para>2 months 5 days ago</para> + <programlisting>2 months 5 days ago</programlisting> - <para>Note that any relative timestamp will also parse correctly - where a timestamp is expected. (see above)</para> + <para>Note that a relative timestamp is also accepted where a timestamp is expected (see above).</para> </refsect1> <refsect1> @@ -239,8 +232,9 @@ second component is not specified, <literal>:00</literal> is assumed.</para> - <para>A timezone specification is not expected, unless it is given - as the literal string "UTC", similarly to timestamps.</para> + <para>A timezone specification is not expected, unless it is given as the literal string <literal>UTC</literal>, or + the local timezone, similar to the supported syntax of timestamps (see above). Non-local timezones except for UTC + are not supported.</para> <para>The special expressions <literal>minutely</literal>, @@ -263,38 +257,38 @@ <para>Examples for valid timestamps and their normalized form:</para> -<programlisting> Sat,Thu,Mon..Wed,Sat..Sun → Mon..Thu,Sat,Sun *-*-* 00:00:00 - Mon,Sun 12-*-* 2,1:23 → Mon,Sun 2012-*-* 01,02:23:00 - Wed *-1 → Wed *-*-01 00:00:00 +<programlisting> Sat,Thu,Mon..Wed,Sat..Sun → Mon..Thu,Sat,Sun *-*-* 00:00:00 + Mon,Sun 12-*-* 2,1:23 → Mon,Sun 2012-*-* 01,02:23:00 + Wed *-1 → Wed *-*-01 00:00:00 Wed..Wed,Wed *-1 → Wed *-*-01 00:00:00 - Wed, 17:48 → Wed *-*-* 17:48:00 + Wed, 17:48 → Wed *-*-* 17:48:00 Wed..Sat,Tue 12-10-15 1:2:3 → Tue..Sat 2012-10-15 01:02:03 - *-*-7 0:0:0 → *-*-07 00:00:00 - 10-15 → *-10-15 00:00:00 - monday *-12-* 17:00 → Mon *-12-* 17:00:00 - Mon,Fri *-*-3,1,2 *:30:45 → Mon,Fri *-*-01,02,03 *:30:45 - 12,14,13,12:20,10,30 → *-*-* 12,13,14:10,20,30:00 - 12..14:10,20,30 → *-*-* 12,13,14:10,20,30:00 - mon,fri *-1/2-1,3 *:30:45 → Mon,Fri *-01/2-01,03 *:30:45 - 03-05 08:05:40 → *-03-05 08:05:40 - 08:05:40 → *-*-* 08:05:40 - 05:40 → *-*-* 05:40:00 - Sat,Sun 12-05 08:05:40 → Sat,Sun *-12-05 08:05:40 - Sat,Sun 08:05:40 → Sat,Sun *-*-* 08:05:40 - 2003-03-05 05:40 → 2003-03-05 05:40:00 -05:40:23.4200004/3.1700005 → 05:40:23.420000/3.170001 - 2003-02..04-05 → 2003-02,03,04-05 00:00:00 - 2003-03-05 05:40 UTC → 2003-03-05 05:40:00 UTC - 2003-03-05 → 2003-03-05 00:00:00 - 03-05 → *-03-05 00:00:00 - hourly → *-*-* *:00:00 - daily → *-*-* 00:00:00 - daily UTC → *-*-* 00:00:00 UTC - monthly → *-*-01 00:00:00 - weekly → Mon *-*-* 00:00:00 - yearly → *-01-01 00:00:00 - annually → *-01-01 00:00:00 - *:2/3 → *-*-* *:02/3:00</programlisting> + *-*-7 0:0:0 → *-*-07 00:00:00 + 10-15 → *-10-15 00:00:00 + monday *-12-* 17:00 → Mon *-12-* 17:00:00 + Mon,Fri *-*-3,1,2 *:30:45 → Mon,Fri *-*-01,02,03 *:30:45 + 12,14,13,12:20,10,30 → *-*-* 12,13,14:10,20,30:00 + 12..14:10,20,30 → *-*-* 12,13,14:10,20,30:00 + mon,fri *-1/2-1,3 *:30:45 → Mon,Fri *-01/2-01,03 *:30:45 + 03-05 08:05:40 → *-03-05 08:05:40 + 08:05:40 → *-*-* 08:05:40 + 05:40 → *-*-* 05:40:00 + Sat,Sun 12-05 08:05:40 → Sat,Sun *-12-05 08:05:40 + Sat,Sun 08:05:40 → Sat,Sun *-*-* 08:05:40 + 2003-03-05 05:40 → 2003-03-05 05:40:00 + 05:40:23.4200004/3.1700005 → 05:40:23.420000/3.170001 + 2003-02..04-05 → 2003-02,03,04-05 00:00:00 + 2003-03-05 05:40 UTC → 2003-03-05 05:40:00 UTC + 2003-03-05 → 2003-03-05 00:00:00 + 03-05 → *-03-05 00:00:00 + hourly → *-*-* *:00:00 + daily → *-*-* 00:00:00 + daily UTC → *-*-* 00:00:00 UTC + monthly → *-*-01 00:00:00 + weekly → Mon *-*-* 00:00:00 + yearly → *-01-01 00:00:00 + annually → *-01-01 00:00:00 + *:2/3 → *-*-* *:02/3:00</programlisting> <para>Calendar events are used by timer units, see <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry> diff --git a/shell-completion/bash/journalctl b/shell-completion/bash/journalctl index 53bedcd92e..a999a10df1 100644 --- a/shell-completion/bash/journalctl +++ b/shell-completion/bash/journalctl @@ -65,7 +65,7 @@ _journalctl() { compopt -o filenames ;; --output|-o) - comps='short short-iso short-precise short-monotonic verbose export json json-pretty json-sse cat' + comps='short short-full short-iso short-precise short-monotonic short-unix verbose export json json-pretty json-sse cat' ;; --field|-F) comps=$(journalctl --fields | sort 2>/dev/null) diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in index 6f2b3f122c..2a45dcbba0 100644 --- a/shell-completion/bash/systemctl.in +++ b/shell-completion/bash/systemctl.in @@ -145,7 +145,7 @@ _systemctl () { comps='full enable-only disable-only' ;; --output|-o) - comps='short short-iso short-precise short-monotonic verbose export json + comps='short short-full short-iso short-precise short-monotonic short-unix verbose export json json-pretty json-sse cat' ;; --machine|-M) diff --git a/shell-completion/zsh/_sd_outputmodes b/shell-completion/zsh/_sd_outputmodes index 3836f79b73..52617c6b7a 100644 --- a/shell-completion/zsh/_sd_outputmodes +++ b/shell-completion/zsh/_sd_outputmodes @@ -1,5 +1,5 @@ #autoload local -a _output_opts -_output_opts=(short short-iso short-precise short-monotonic verbose export json json-pretty json-sse cat) +_output_opts=(short short-full short-iso short-precise short-monotonic short-unix verbose export json json-pretty json-sse cat) _describe -t output 'output mode' _output_opts || compadd "$@" diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c index e4cfab364e..fda293fcb9 100644 --- a/src/basic/calendarspec.c +++ b/src/basic/calendarspec.c @@ -302,6 +302,17 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) { if (c->utc) fputs(" UTC", f); + else if (IN_SET(c->dst, 0, 1)) { + + /* If daylight saving is explicitly on or off, let's show the used timezone. */ + + tzset(); + + if (!isempty(tzname[c->dst])) { + fputc(' ', f); + fputs(tzname[c->dst], f); + } + } r = fflush_and_check(f); if (r < 0) { @@ -747,9 +758,9 @@ fail: } int calendar_spec_from_string(const char *p, CalendarSpec **spec) { + const char *utc; CalendarSpec *c; int r; - const char *utc; assert(p); assert(spec); @@ -760,11 +771,39 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) { c = new0(CalendarSpec, 1); if (!c) return -ENOMEM; + c->dst = -1; utc = endswith_no_case(p, " UTC"); if (utc) { c->utc = true; p = strndupa(p, utc - p); + } else { + const char *e = NULL; + int j; + + tzset(); + + /* Check if the local timezone was specified? */ + for (j = 0; j <= 1; j++) { + if (isempty(tzname[j])) + continue; + + e = endswith_no_case(p, tzname[j]); + if(!e) + continue; + if (e == p) + continue; + if (e[-1] != ' ') + continue; + + break; + } + + /* Found one of the two timezones specified? */ + if (IN_SET(j, 0, 1)) { + p = strndupa(p, e - p - 1); + c->dst = j; + } } if (strcaseeq(p, "minutely")) { @@ -1017,7 +1056,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { for (;;) { /* Normalize the current date */ (void) mktime_or_timegm(&c, spec->utc); - c.tm_isdst = -1; + c.tm_isdst = spec->dst; c.tm_year += 1900; r = find_matching_component(spec->year, &c.tm_year); diff --git a/src/basic/calendarspec.h b/src/basic/calendarspec.h index f6472c1244..c6087228fd 100644 --- a/src/basic/calendarspec.h +++ b/src/basic/calendarspec.h @@ -37,6 +37,7 @@ typedef struct CalendarComponent { typedef struct CalendarSpec { int weekdays_bits; bool utc; + int dst; CalendarComponent *year; CalendarComponent *month; diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 24e681bf85..0ef1f6393e 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -254,32 +254,95 @@ struct timeval *timeval_store(struct timeval *tv, usec_t u) { return tv; } -static char *format_timestamp_internal(char *buf, size_t l, usec_t t, - bool utc, bool us) { +static char *format_timestamp_internal( + char *buf, + size_t l, + usec_t t, + bool utc, + bool us) { + + /* The weekdays in non-localized (English) form. We use this instead of the localized form, so that our + * generated timestamps may be parsed with parse_timestamp(), and always read the same. */ + static const char * const weekdays[] = { + [0] = "Sun", + [1] = "Mon", + [2] = "Tue", + [3] = "Wed", + [4] = "Thu", + [5] = "Fri", + [6] = "Sat", + }; + struct tm tm; time_t sec; - int k; + size_t n; assert(buf); - assert(l > 0); + if (l < + 3 + /* week day */ + 1 + 10 + /* space and date */ + 1 + 8 + /* space and time */ + (us ? 1 + 6 : 0) + /* "." and microsecond part */ + 1 + 1 + /* space and shortest possible zone */ + 1) + return NULL; /* Not enough space even for the shortest form. */ if (t <= 0 || t == USEC_INFINITY) + return NULL; /* Timestamp is unset */ + + sec = (time_t) (t / USEC_PER_SEC); /* Round down */ + if ((usec_t) sec != (t / USEC_PER_SEC)) + return NULL; /* overflow? */ + + if (!localtime_or_gmtime_r(&sec, &tm, utc)) return NULL; - sec = (time_t) (t / USEC_PER_SEC); - localtime_or_gmtime_r(&sec, &tm, utc); + /* Start with the week day */ + assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays)); + memcpy(buf, weekdays[tm.tm_wday], 4); - if (us) - k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm); - else - k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", &tm); + /* Add the main components */ + if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0) + return NULL; /* Doesn't fit */ - if (k <= 0) - return NULL; + /* Append the microseconds part, if that's requested */ if (us) { - snprintf(buf + strlen(buf), l - strlen(buf), ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); - if (strftime(buf + strlen(buf), l - strlen(buf), " %Z", &tm) <= 0) - return NULL; + n = strlen(buf); + if (n + 8 > l) + return NULL; /* Microseconds part doesn't fit. */ + + sprintf(buf + n, ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); + } + + /* Append the timezone */ + n = strlen(buf); + if (utc) { + /* If this is UTC then let's explicitly use the "UTC" string here, because gmtime_r() normally uses the + * obsolete "GMT" instead. */ + if (n + 5 > l) + return NULL; /* "UTC" doesn't fit. */ + + strcpy(buf + n, " UTC"); + + } else if (!isempty(tm.tm_zone)) { + size_t tn; + + /* An explicit timezone is specified, let's use it, if it fits */ + tn = strlen(tm.tm_zone); + if (n + 1 + tn + 1 > l) { + /* The full time zone does not fit in. Yuck. */ + + if (n + 1 + _POSIX_TZNAME_MAX + 1 > l) + return NULL; /* Not even enough space for the POSIX minimum (of 6)? In that case, complain that it doesn't fit */ + + /* So the time zone doesn't fit in fully, but the caller passed enough space for the POSIX + * minimum time zone length. In this case suppress the timezone entirely, in order not to dump + * an overly long, hard to read string on the user. This should be safe, because the user will + * assume the local timezone anyway if none is shown. And so does parse_timestamp(). */ + } else { + buf[n++] = ' '; + strcpy(buf + n, tm.tm_zone); + } } return buf; @@ -539,12 +602,11 @@ int parse_timestamp(const char *t, usec_t *usec) { { "Sat", 6 }, }; - const char *k; - const char *utc; + const char *k, *utc, *tzn = NULL; struct tm tm, copy; time_t x; usec_t x_usec, plus = 0, minus = 0, ret; - int r, weekday = -1; + int r, weekday = -1, dst = -1; unsigned i; /* @@ -609,15 +671,55 @@ int parse_timestamp(const char *t, usec_t *usec) { goto finish; } + /* See if the timestamp is suffixed with UTC */ utc = endswith_no_case(t, " UTC"); if (utc) t = strndupa(t, utc - t); + else { + const char *e = NULL; + int j; + + tzset(); + + /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only + * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because + * there are no nice APIs available to cover this. By accepting the local time zone strings, we make + * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't + * support arbitrary timezone specifications. */ + + for (j = 0; j <= 1; j++) { + + if (isempty(tzname[j])) + continue; + + e = endswith_no_case(t, tzname[j]); + if (!e) + continue; + if (e == t) + continue; + if (e[-1] != ' ') + continue; + + break; + } - x = ret / USEC_PER_SEC; + if (IN_SET(j, 0, 1)) { + /* Found one of the two timezones specified. */ + t = strndupa(t, e - t - 1); + dst = j; + tzn = tzname[j]; + } + } + + x = (time_t) (ret / USEC_PER_SEC); x_usec = 0; - assert_se(localtime_or_gmtime_r(&x, &tm, utc)); - tm.tm_isdst = -1; + if (!localtime_or_gmtime_r(&x, &tm, utc)) + return -EINVAL; + + tm.tm_isdst = dst; + if (tzn) + tm.tm_zone = tzn; if (streq(t, "today")) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; @@ -634,7 +736,6 @@ int parse_timestamp(const char *t, usec_t *usec) { goto from_tm; } - for (i = 0; i < ELEMENTSOF(day_nr); i++) { size_t skip; @@ -727,7 +828,6 @@ parse_usec: return -EINVAL; x_usec = add; - } from_tm: diff --git a/src/basic/time-util.h b/src/basic/time-util.h index 1b058f0e49..99be5ce6ee 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -68,7 +68,9 @@ typedef struct triple_timestamp { #define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC)) #define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC)) -#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1) /* weekdays can be unicode */ +/* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this + * to 6. Let's rely on that. */ +#define FORMAT_TIMESTAMP_MAX (3+1+10+1+8+1+6+1+6+1) #define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */ #define FORMAT_TIMESTAMP_RELATIVE_MAX 256 #define FORMAT_TIMESPAN_MAX 64 diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index d04728f505..f9d9c4ed62 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -45,6 +45,7 @@ #include "parse-util.h" #include "process-util.h" #include "sparse-endian.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "terminal-util.h" @@ -206,6 +207,108 @@ static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, Output return ellipsized; } +static int output_timestamp_monotonic(FILE *f, sd_journal *j, const char *monotonic) { + sd_id128_t boot_id; + uint64_t t; + int r; + + assert(f); + assert(j); + + r = -ENXIO; + if (monotonic) + r = safe_atou64(monotonic, &t); + if (r < 0) + r = sd_journal_get_monotonic_usec(j, &t, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + fprintf(f, "[%5llu.%06llu]", + (unsigned long long) (t / USEC_PER_SEC), + (unsigned long long) (t % USEC_PER_SEC)); + + return 1 + 5 + 1 + 6 + 1; +} + +static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, OutputFlags flags, const char *realtime) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, 64)]; + struct tm *(*gettime_r)(const time_t *, struct tm *); + struct tm tm; + uint64_t x; + time_t t; + int r; + + assert(f); + assert(j); + + r = -ENXIO; + if (realtime) + r = safe_atou64(realtime, &x); + if (r < 0) + r = sd_journal_get_realtime_usec(j, &x); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + if (mode == OUTPUT_SHORT_FULL) { + const char *k; + + if (flags & OUTPUT_UTC) + k = format_timestamp_utc(buf, sizeof(buf), x); + else + k = format_timestamp(buf, sizeof(buf), x); + if (!k) { + log_error("Failed to format timestamp."); + return -EINVAL; + } + + } else { + gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; + t = (time_t) (x / USEC_PER_SEC); + + switch (mode) { + + case OUTPUT_SHORT_UNIX: + xsprintf(buf, "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC)); + break; + + case OUTPUT_SHORT_ISO: + if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)) <= 0) { + log_error("Failed for format ISO time"); + return -EINVAL; + } + break; + + case OUTPUT_SHORT: + case OUTPUT_SHORT_PRECISE: + + if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)) <= 0) { + log_error("Failed to format syslog time"); + return -EINVAL; + } + + if (mode == OUTPUT_SHORT_PRECISE) { + size_t k; + + assert(sizeof(buf) > strlen(buf)); + k = sizeof(buf) - strlen(buf); + + r = snprintf(buf + strlen(buf), k, ".%06llu", (unsigned long long) (x % USEC_PER_SEC)); + if (r <= 0 || (size_t) r >= k) { /* too long? */ + log_error("Failed to format precise time"); + return -EINVAL; + } + } + break; + + default: + assert_not_reached("Unknown time format"); + } + } + + fputs(buf, f); + return (int) strlen(buf); +} + static int output_short( FILE *f, sd_journal *j, @@ -305,78 +408,15 @@ static int output_short( if (priority_len == 1 && *priority >= '0' && *priority <= '7') p = *priority - '0'; - if (mode == OUTPUT_SHORT_MONOTONIC) { - uint64_t t; - sd_id128_t boot_id; - - r = -ENOENT; - - if (monotonic) - r = safe_atou64(monotonic, &t); - - if (r < 0) - r = sd_journal_get_monotonic_usec(j, &t, &boot_id); - - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - fprintf(f, "[%5llu.%06llu]", - (unsigned long long) (t / USEC_PER_SEC), - (unsigned long long) (t % USEC_PER_SEC)); - - n += 1 + 5 + 1 + 6 + 1; - - } else { - char buf[64]; - uint64_t x; - time_t t; - struct tm tm; - struct tm *(*gettime_r)(const time_t *, struct tm *); - - r = -ENOENT; - gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; - - if (realtime) - r = safe_atou64(realtime, &x); - - if (r < 0) - r = sd_journal_get_realtime_usec(j, &x); - - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - t = (time_t) (x / USEC_PER_SEC); - - switch (mode) { - - case OUTPUT_SHORT_UNIX: - r = snprintf(buf, sizeof(buf), "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC)); - break; - - case OUTPUT_SHORT_ISO: - r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)); - break; - - case OUTPUT_SHORT_PRECISE: - r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); - if (r > 0) - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06llu", (unsigned long long) (x % USEC_PER_SEC)); - break; - - default: - r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); - } - - if (r <= 0) { - log_error("Failed to format time."); - return -EINVAL; - } - - fputs(buf, f); - n += strlen(buf); - } + if (mode == OUTPUT_SHORT_MONOTONIC) + r = output_timestamp_monotonic(f, j, monotonic); + else + r = output_timestamp_realtime(f, j, mode, flags, realtime); + if (r < 0) + return r; + n += r; - if (hostname && (flags & OUTPUT_NO_HOSTNAME)) { + if (flags & OUTPUT_NO_HOSTNAME) { /* Suppress display of the hostname if this is requested. */ hostname = NULL; hostname_len = 0; @@ -910,6 +950,7 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])( [OUTPUT_SHORT_PRECISE] = output_short, [OUTPUT_SHORT_MONOTONIC] = output_short, [OUTPUT_SHORT_UNIX] = output_short, + [OUTPUT_SHORT_FULL] = output_short, [OUTPUT_VERBOSE] = output_verbose, [OUTPUT_EXPORT] = output_export, [OUTPUT_JSON] = output_json, diff --git a/src/shared/output-mode.c b/src/shared/output-mode.c index bec53ee0ae..67d8208ad2 100644 --- a/src/shared/output-mode.c +++ b/src/shared/output-mode.c @@ -22,6 +22,7 @@ static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { [OUTPUT_SHORT] = "short", + [OUTPUT_SHORT_FULL] = "short-full", [OUTPUT_SHORT_ISO] = "short-iso", [OUTPUT_SHORT_PRECISE] = "short-precise", [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h index f37189e57f..ff29dafcb5 100644 --- a/src/shared/output-mode.h +++ b/src/shared/output-mode.h @@ -23,6 +23,7 @@ typedef enum OutputMode { OUTPUT_SHORT, + OUTPUT_SHORT_FULL, OUTPUT_SHORT_ISO, OUTPUT_SHORT_PRECISE, OUTPUT_SHORT_MONOTONIC, diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 782824ff38..4fa7adfc41 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -4767,7 +4767,7 @@ static int show_one( else if (streq(verb, "status")) { print_status_info(bus, &info, ellipsized); - if (info.active_state && STR_IN_SET(info.active_state, "inactive", "failed")) + if (info.active_state && !STR_IN_SET(info.active_state, "active", "reloading")) r = EXIT_PROGRAM_NOT_RUNNING; else r = EXIT_PROGRAM_RUNNING_OR_SERVICE_OK; diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c index 4a2b93de59..57d9da4855 100644 --- a/src/test/test-calendarspec.c +++ b/src/test/test-calendarspec.c @@ -88,6 +88,27 @@ static void test_next(const char *input, const char *new_tz, usec_t after, usec_ tzset(); } +static void test_timestamp(void) { + char buf[FORMAT_TIMESTAMP_MAX]; + _cleanup_free_ char *t = NULL; + CalendarSpec *c; + usec_t x, y; + + /* Ensure that a timestamp is also a valid calendar specification. Convert forth and back */ + + x = now(CLOCK_REALTIME); + + assert_se(format_timestamp_us(buf, sizeof(buf), x)); + printf("%s\n", buf); + assert_se(calendar_spec_from_string(buf, &c) >= 0); + assert_se(calendar_spec_to_string(c, &t) >= 0); + calendar_spec_free(c); + printf("%s\n", t); + + assert_se(parse_timestamp(t, &y) >= 0); + assert_se(y == x); +} + int main(int argc, char* argv[]) { CalendarSpec *c; @@ -155,5 +176,7 @@ int main(int argc, char* argv[]) { assert_se(calendar_spec_from_string("00:00:00/0.00000001", &c) < 0); assert_se(calendar_spec_from_string("00:00:00.0..00.9", &c) < 0); + test_timestamp(); + return 0; } diff --git a/src/test/test-time.c b/src/test/test-time.c index ee7d55c5ab..7078a0374d 100644 --- a/src/test/test-time.c +++ b/src/test/test-time.c @@ -19,6 +19,7 @@ #include "strv.h" #include "time-util.h" +#include "random-util.h" static void test_parse_sec(void) { usec_t u; @@ -201,6 +202,48 @@ static void test_usec_sub(void) { assert_se(usec_sub(USEC_INFINITY, 5) == USEC_INFINITY); } +static void test_format_timestamp(void) { + unsigned i; + + for (i = 0; i < 100; i++) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)]; + usec_t x, y; + + random_bytes(&x, sizeof(x)); + x = x % (2147483600 * USEC_PER_SEC) + 1; + + assert_se(format_timestamp(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_utc(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_us(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x == y); + + assert_se(format_timestamp_us_utc(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x == y); + + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + + /* The two calls above will run with a slightly different local time. Make sure we are in the same + * range however, but give enough leeway that this is unlikely to explode. And of course, + * format_timestamp_relative() scales the accuracy with the distance from the current time up to one + * month, cover for that too. */ + assert_se(y > x ? y - x : x - y <= USEC_PER_MONTH + USEC_PER_DAY); + } +} + int main(int argc, char *argv[]) { uintmax_t x; @@ -214,6 +257,7 @@ int main(int argc, char *argv[]) { test_get_timezones(); test_usec_add(); test_usec_sub(); + test_format_timestamp(); /* Ensure time_t is signed */ assert_cc((time_t) -1 < (time_t) 1); |