From 0fd97a253379a4e6713a588a49f25c64f062a34e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 15 Jul 2021 18:48:56 +0900 Subject: [PATCH 1/3] network: introduce FORMAT_LIFETIME() Fixes a bug introduced by 5291f26d4a6450d1fbf3656640ef20c5e78aa6a5. Fixes #20227. --- src/network/networkd-address.c | 29 +++++++++++++++++++---------- src/network/networkd-address.h | 7 +++++++ src/network/networkd-dhcp6.c | 28 +++++++++------------------- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 3a4ab629f8..c244175088 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -16,6 +16,7 @@ #include "parse-util.h" #include "string-util.h" #include "strv.h" +#include "strxcpyx.h" #define ADDRESSES_PER_LINK_MAX 2048U #define STATIC_ADDRESSES_PER_NETWORK_MAX 1024U @@ -620,9 +621,23 @@ int manager_has_address(Manager *manager, int family, const union in_addr_union return false; } +char *format_lifetime(char *buf, size_t l, uint32_t lifetime) { + char *p = buf; + + assert(buf); + assert(l > 0); + + if (lifetime == CACHE_INFO_INFINITY_LIFE_TIME) { + strscpy(buf, l, "forever"); + return buf; + } + + l -= strpcpy(&p, l, "for "); + return format_timespan(p, l, lifetime * USEC_PER_SEC, USEC_PER_SEC); +} + static void log_address_debug(const Address *address, const char *str, const Link *link) { _cleanup_free_ char *addr = NULL, *peer = NULL, *flags_str = NULL; - const char *valid_str = NULL, *preferred_str = NULL; bool has_peer; assert(address); @@ -637,19 +652,13 @@ static void log_address_debug(const Address *address, const char *str, const Lin if (has_peer) (void) in_addr_to_string(address->family, &address->in_addr_peer, &peer); - if (address->cinfo.ifa_valid != CACHE_INFO_INFINITY_LIFE_TIME) - valid_str = FORMAT_TIMESPAN(address->cinfo.ifa_valid * USEC_PER_SEC, USEC_PER_SEC); - - if (address->cinfo.ifa_prefered != CACHE_INFO_INFINITY_LIFE_TIME) - preferred_str = FORMAT_TIMESPAN(address->cinfo.ifa_prefered * USEC_PER_SEC, USEC_PER_SEC); - (void) address_flags_to_string_alloc(address->flags, address->family, &flags_str); - log_link_debug(link, "%s address: %s%s%s/%u (valid %s%s, preferred %s%s), flags: %s", + log_link_debug(link, "%s address: %s%s%s/%u (valid %s, preferred %s), flags: %s", str, strnull(addr), has_peer ? " peer " : "", has_peer ? strnull(peer) : "", address->prefixlen, - valid_str ? "for " : "forever", strempty(valid_str), - preferred_str ? "for " : "forever", strempty(preferred_str), + FORMAT_LIFETIME(address->cinfo.ifa_valid), + FORMAT_LIFETIME(address->cinfo.ifa_prefered), strna(flags_str)); } diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index ff3d46abdd..1569b588a0 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -50,6 +50,13 @@ typedef struct Address { address_ready_callback_t callback; } Address; +char *format_lifetime(char *buf, size_t l, uint32_t lifetime) _warn_unused_result_; +/* Note: the lifetime of the compound literal is the immediately surrounding block, + * see C11 ยง6.5.2.5, and + * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */ +#define FORMAT_LIFETIME(lifetime) \ + format_lifetime((char[FORMAT_TIMESPAN_MAX+STRLEN("for ")]){}, FORMAT_TIMESPAN_MAX+STRLEN("for "), lifetime) + int address_new(Address **ret); Address *address_free(Address *address); int address_get(Link *link, const Address *in, Address **ret); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 2445acbe07..f03bf0651b 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -373,7 +373,6 @@ static int dhcp6_pd_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Lin } static void log_dhcp6_pd_address(Link *link, const Address *address) { - const char *valid_str = NULL, *preferred_str = NULL; _cleanup_free_ char *buffer = NULL; int log_level; @@ -386,15 +385,11 @@ static void log_dhcp6_pd_address(Link *link, const Address *address) { return; (void) in6_addr_prefix_to_string(&address->in_addr.in6, address->prefixlen, &buffer); - if (address->cinfo.ifa_valid != CACHE_INFO_INFINITY_LIFE_TIME) - valid_str = FORMAT_TIMESPAN(address->cinfo.ifa_valid * USEC_PER_SEC, USEC_PER_SEC); - if (address->cinfo.ifa_prefered != CACHE_INFO_INFINITY_LIFE_TIME) - preferred_str = FORMAT_TIMESPAN(address->cinfo.ifa_prefered * USEC_PER_SEC, USEC_PER_SEC); - log_link_full(link, log_level, "DHCPv6-PD address %s (valid %s%s, preferred %s%s)", + log_link_full(link, log_level, "DHCPv6-PD address %s (valid %s, preferred %s)", strna(buffer), - valid_str ? "for " : "forever", strempty(valid_str), - preferred_str ? "for " : "forever", strempty(preferred_str)); + FORMAT_LIFETIME(address->cinfo.ifa_valid), + FORMAT_LIFETIME(address->cinfo.ifa_prefered)); } static int dhcp6_pd_after_address_configure(Request *req, void *object) { @@ -1068,11 +1063,6 @@ static void log_dhcp6_address(Link *link, const Address *address, char **ret) { (void) in6_addr_prefix_to_string(&address->in_addr.in6, address->prefixlen, &buffer); - const char *valid_str = address->cinfo.ifa_valid == CACHE_INFO_INFINITY_LIFE_TIME ? NULL : - FORMAT_TIMESPAN(address->cinfo.ifa_valid * USEC_PER_SEC, USEC_PER_SEC); - const char *preferred_str = address->cinfo.ifa_prefered == CACHE_INFO_INFINITY_LIFE_TIME ? NULL : - FORMAT_TIMESPAN(address->cinfo.ifa_prefered * USEC_PER_SEC, USEC_PER_SEC); - r = address_get(link, address, &existing); if (r < 0) { /* New address. */ @@ -1095,20 +1085,20 @@ static void log_dhcp6_address(Link *link, const Address *address, char **ret) { break; } - log_link_warning(link, "DHCPv6 address %s (valid %s%s, preferred %s%s) conflicts the existing address %s %s.", + log_link_warning(link, "DHCPv6 address %s (valid %s, preferred %s) conflicts the existing address %s %s.", strna(buffer), - valid_str ? "for " : "forever", strempty(valid_str), - preferred_str ? "for " : "forever", strempty(preferred_str), + FORMAT_LIFETIME(address->cinfo.ifa_valid), + FORMAT_LIFETIME(address->cinfo.ifa_prefered), strna(buffer), by_ndisc ? "assigned by NDISC. Please try to use or update IPv6Token= setting " "to change the address generated by NDISC, or disable UseAutonomousPrefix=" : ""); goto finalize; simple_log: - log_link_full(link, log_level, "DHCPv6 address %s (valid %s%s, preferred %s%s)", + log_link_full(link, log_level, "DHCPv6 address %s (valid %s, preferred %s)", strna(buffer), - valid_str ? "for " : "forever", strempty(valid_str), - preferred_str ? "for " : "forever", strempty(preferred_str)); + FORMAT_LIFETIME(address->cinfo.ifa_valid), + FORMAT_LIFETIME(address->cinfo.ifa_prefered)); finalize: if (ret) From fa3f917a25a015b55087307321fac6bbcc7811f8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 15 Jul 2021 18:56:09 +0900 Subject: [PATCH 2/3] network: slightly simplify log_address_debug() --- src/network/networkd-address.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index c244175088..2d3f8ece2d 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -638,7 +638,6 @@ char *format_lifetime(char *buf, size_t l, uint32_t lifetime) { static void log_address_debug(const Address *address, const char *str, const Link *link) { _cleanup_free_ char *addr = NULL, *peer = NULL, *flags_str = NULL; - bool has_peer; assert(address); assert(str); @@ -648,15 +647,13 @@ static void log_address_debug(const Address *address, const char *str, const Lin return; (void) in_addr_to_string(address->family, &address->in_addr, &addr); - has_peer = in_addr_is_set(address->family, &address->in_addr_peer); - if (has_peer) + if (in_addr_is_set(address->family, &address->in_addr_peer)) (void) in_addr_to_string(address->family, &address->in_addr_peer, &peer); (void) address_flags_to_string_alloc(address->flags, address->family, &flags_str); log_link_debug(link, "%s address: %s%s%s/%u (valid %s, preferred %s), flags: %s", - str, strnull(addr), has_peer ? " peer " : "", - has_peer ? strnull(peer) : "", address->prefixlen, + str, strnull(addr), peer ? " peer " : "", strempty(peer), address->prefixlen, FORMAT_LIFETIME(address->cinfo.ifa_valid), FORMAT_LIFETIME(address->cinfo.ifa_prefered), strna(flags_str)); From f843f85da8814c42117ac8fdffb613a3a2f02cbf Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 15 Jul 2021 20:02:27 +0900 Subject: [PATCH 3/3] tree-wide: FORMAT_TIMESTAMP() or friends must be used as a function argument Follow-ups for #20109. --- src/journal/journald-server.c | 16 ++++---- src/login/loginctl.c | 24 ++++-------- src/machine/machinectl.c | 71 ++++++++++++++-------------------- src/systemctl/systemctl-show.c | 37 +++++++----------- src/test/test-time-util.c | 3 +- 5 files changed, 61 insertions(+), 90 deletions(-) diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 7770df41f1..d410d7cf19 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -209,19 +209,17 @@ void server_space_usage_message(Server *s, JournalStorage *storage) { const JournalMetrics *metrics = &storage->metrics; - const char - *vfs_used = FORMAT_BYTES(storage->space.vfs_used), - *limit = FORMAT_BYTES(storage->space.limit), - *available = FORMAT_BYTES(storage->space.available); - server_driver_message(s, 0, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_USAGE_STR, LOG_MESSAGE("%s (%s) is %s, max %s, %s free.", - storage->name, storage->path, vfs_used, limit, available), + storage->name, storage->path, + FORMAT_BYTES(storage->space.vfs_used), + FORMAT_BYTES(storage->space.limit), + FORMAT_BYTES(storage->space.available)), "JOURNAL_NAME=%s", storage->name, "JOURNAL_PATH=%s", storage->path, "CURRENT_USE=%"PRIu64, storage->space.vfs_used, - "CURRENT_USE_PRETTY=%s", vfs_used, + "CURRENT_USE_PRETTY=%s", FORMAT_BYTES(storage->space.vfs_used), "MAX_USE=%"PRIu64, metrics->max_use, "MAX_USE_PRETTY=%s", FORMAT_BYTES(metrics->max_use), "DISK_KEEP_FREE=%"PRIu64, metrics->keep_free, @@ -229,9 +227,9 @@ void server_space_usage_message(Server *s, JournalStorage *storage) { "DISK_AVAILABLE=%"PRIu64, storage->space.vfs_available, "DISK_AVAILABLE_PRETTY=%s", FORMAT_BYTES(storage->space.vfs_available), "LIMIT=%"PRIu64, storage->space.limit, - "LIMIT_PRETTY=%s", limit, + "LIMIT_PRETTY=%s", FORMAT_BYTES(storage->space.limit), "AVAILABLE=%"PRIu64, storage->space.available, - "AVAILABLE_PRETTY=%s", available, + "AVAILABLE_PRETTY=%s", FORMAT_BYTES(storage->space.available), NULL); } diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 318d71a28a..4b5598ecf6 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -450,7 +450,6 @@ static int print_session_status_info(sd_bus *bus, const char *path, bool *new_li _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - const char *s1, *s2; SessionStatusInfo i = {}; int r; @@ -470,13 +469,10 @@ static int print_session_status_info(sd_bus *bus, const char *path, bool *new_li else printf("%"PRIu32"\n", i.uid); - s1 = FORMAT_TIMESTAMP_RELATIVE(i.timestamp.realtime); - s2 = FORMAT_TIMESTAMP(i.timestamp.realtime); - - if (s1) - printf("\t Since: %s; %s\n", s2, s1); - else if (s2) - printf("\t Since: %s\n", s2); + if (i.timestamp.realtime > 0 && i.timestamp.realtime < USEC_INFINITY) + printf("\t Since: %s; %s\n", + FORMAT_TIMESTAMP(i.timestamp.realtime), + FORMAT_TIMESTAMP_RELATIVE(i.timestamp.realtime)); if (i.leader > 0) { _cleanup_free_ char *t = NULL; @@ -579,7 +575,6 @@ static int print_user_status_info(sd_bus *bus, const char *path, bool *new_line) _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - const char *s1, *s2; _cleanup_(user_status_info_clear) UserStatusInfo i = {}; int r; @@ -597,13 +592,10 @@ static int print_user_status_info(sd_bus *bus, const char *path, bool *new_line) else printf("%"PRIu32"\n", i.uid); - s1 = FORMAT_TIMESTAMP_RELATIVE(i.timestamp.realtime); - s2 = FORMAT_TIMESTAMP(i.timestamp.realtime); - - if (s1) - printf("\t Since: %s; %s\n", s2, s1); - else if (s2) - printf("\t Since: %s\n", s2); + if (i.timestamp.realtime > 0 && i.timestamp.realtime < USEC_INFINITY) + printf("\t Since: %s; %s\n", + FORMAT_TIMESTAMP(i.timestamp.realtime), + FORMAT_TIMESTAMP_RELATIVE(i.timestamp.realtime)); if (!isempty(i.state)) printf("\t State: %s\n", i.state); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 06ca335cff..299e6d8207 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -507,8 +507,7 @@ static void machine_status_info_clear(MachineStatusInfo *info) { } static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { - _cleanup_free_ char *addresses = NULL; - const char *s1, *s2; + _cleanup_free_ char *addresses = NULL, *s1 = NULL, *s2 = NULL; int ifi = -1; assert(bus); @@ -521,12 +520,12 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { else putchar('\n'); - s1 = FORMAT_TIMESTAMP_RELATIVE(i->timestamp.realtime); - s2 = FORMAT_TIMESTAMP(i->timestamp.realtime); + s1 = strdup(strempty(FORMAT_TIMESTAMP_RELATIVE(i->timestamp.realtime))); + s2 = strdup(strempty(FORMAT_TIMESTAMP(i->timestamp.realtime))); - if (s1) - printf("\t Since: %s; %s\n", s2, s1); - else if (s2) + if (!isempty(s1)) + printf("\t Since: %s; %s\n", strna(s2), s1); + else if (!isempty(s2)) printf("\t Since: %s\n", s2); if (i->leader > 0) { @@ -826,8 +825,6 @@ typedef struct ImageStatusInfo { } ImageStatusInfo; static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { - const char *s1, *s2, *s3, *s4; - assert(bus); assert(i); @@ -853,33 +850,29 @@ static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { i->read_only ? "read-only" : "writable", i->read_only ? ansi_normal() : ""); - s1 = FORMAT_TIMESTAMP_RELATIVE(i->crtime); - s2 = FORMAT_TIMESTAMP(i->crtime); - if (s1 && s2) - printf("\t Created: %s; %s\n", s2, s1); - else if (s2) - printf("\t Created: %s\n", s2); + if (i->crtime > 0 && i->crtime < USEC_INFINITY) + printf("\t Created: %s; %s\n", + FORMAT_TIMESTAMP(i->crtime), FORMAT_TIMESTAMP_RELATIVE(i->crtime)); - s1 = FORMAT_TIMESTAMP_RELATIVE(i->mtime); - s2 = FORMAT_TIMESTAMP(i->mtime); - if (s1 && s2) - printf("\tModified: %s; %s\n", s2, s1); - else if (s2) - printf("\tModified: %s\n", s2); + if (i->mtime > 0 && i->mtime < USEC_INFINITY) + printf("\tModified: %s; %s\n", + FORMAT_TIMESTAMP(i->mtime), FORMAT_TIMESTAMP_RELATIVE(i->mtime)); - s3 = FORMAT_BYTES(i->usage); - s4 = i->usage_exclusive != i->usage ? FORMAT_BYTES(i->usage_exclusive) : NULL; - if (s3 && s4) - printf("\t Usage: %s (exclusive: %s)\n", s3, s4); - else if (s3) - printf("\t Usage: %s\n", s3); + if (i->usage != UINT64_MAX) { + if (i->usage_exclusive != i->usage && i->usage_exclusive != UINT64_MAX) + printf("\t Usage: %s (exclusive: %s)\n", + FORMAT_BYTES(i->usage), FORMAT_BYTES(i->usage_exclusive)); + else + printf("\t Usage: %s\n", FORMAT_BYTES(i->usage)); + } - s3 = FORMAT_BYTES(i->limit); - s4 = i->limit_exclusive != i->limit ? FORMAT_BYTES(i->limit_exclusive) : NULL; - if (s3 && s4) - printf("\t Limit: %s (exclusive: %s)\n", s3, s4); - else if (s3) - printf("\t Limit: %s\n", s3); + if (i->limit != UINT64_MAX) { + if (i->limit_exclusive != i->limit && i->limit_exclusive != UINT64_MAX) + printf("\t Limit: %s (exclusive: %s)\n", + FORMAT_BYTES(i->limit), FORMAT_BYTES(i->limit_exclusive)); + else + printf("\t Limit: %s\n", FORMAT_BYTES(i->limit)); + } } static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { @@ -934,18 +927,14 @@ typedef struct PoolStatusInfo { } PoolStatusInfo; static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { - char *s; - if (i->path) printf("\t Path: %s\n", i->path); - s = FORMAT_BYTES(i->usage); - if (s) - printf("\t Usage: %s\n", s); + if (i->usage != UINT64_MAX) + printf("\t Usage: %s\n", FORMAT_BYTES(i->usage)); - s = FORMAT_BYTES(i->limit); - if (s) - printf("\t Limit: %s\n", s); + if (i->limit != UINT64_MAX) + printf("\t Limit: %s\n", FORMAT_BYTES(i->limit)); } static int show_pool_info(sd_bus *bus) { diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index 2c60be40c9..b325d44c5e 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -300,7 +300,7 @@ static void print_status_info( UnitStatusInfo *i, bool *ellipsized) { - const char *s1, *s2, *active_on, *active_off, *on, *off, *ss, *fs; + const char *active_on, *active_off, *on, *off, *ss, *fs; _cleanup_free_ char *formatted_path = NULL; ExecStatusInfo *p; usec_t timestamp; @@ -418,12 +418,10 @@ static void print_status_info( STRPTR_IN_SET(i->active_state, "activating") ? i->inactive_exit_timestamp : i->active_exit_timestamp; - s1 = FORMAT_TIMESTAMP_RELATIVE(timestamp); - s2 = FORMAT_TIMESTAMP_STYLE(timestamp, arg_timestamp_style); - if (s1) - printf(" since %s; %s\n", s2, s1); - else if (s2) - printf(" since %s\n", s2); + if (timestamp > 0 && timestamp < USEC_INFINITY) + printf(" since %s; %s\n", + FORMAT_TIMESTAMP_STYLE(timestamp, arg_timestamp_style), + FORMAT_TIMESTAMP_RELATIVE(timestamp)); else printf("\n"); @@ -440,18 +438,18 @@ static void print_status_info( } if (endswith(i->id, ".timer")) { - const char *next_time; dual_timestamp nw, next = {i->next_elapse_real, i->next_elapse_monotonic}; usec_t next_elapse; dual_timestamp_get(&nw); next_elapse = calc_next_elapse(&nw, &next); - next_time = FORMAT_TIMESTAMP_STYLE(next_elapse, arg_timestamp_style); - printf(" Trigger: %s%s%s\n", - strna(next_time), - next_time ? "; " : "", - next_time ? strna(FORMAT_TIMESTAMP_RELATIVE(next_elapse)) : ""); + if (next_elapse > 0 && next_elapse < USEC_INFINITY) + printf(" Trigger: %s; %s\n", + FORMAT_TIMESTAMP_STYLE(next_elapse, arg_timestamp_style), + FORMAT_TIMESTAMP_RELATIVE(next_elapse)); + else + printf(" Trigger: n/a\n"); } STRV_FOREACH(t, i->triggers) { @@ -469,13 +467,11 @@ static void print_status_info( if (!i->condition_result && i->condition_timestamp > 0) { UnitCondition *c; int n = 0; - const char *rel; - rel = FORMAT_TIMESTAMP_RELATIVE(i->condition_timestamp); - printf(" Condition: start %scondition failed%s at %s%s%s\n", + printf(" Condition: start %scondition failed%s at %s; %s\n", ansi_highlight_yellow(), ansi_normal(), FORMAT_TIMESTAMP_STYLE(i->condition_timestamp, arg_timestamp_style), - rel ? "; " : "", strempty(rel)); + FORMAT_TIMESTAMP_RELATIVE(i->condition_timestamp)); LIST_FOREACH(conditions, c, i->conditions) if (c->tristate < 0) @@ -492,13 +488,10 @@ static void print_status_info( } if (!i->assert_result && i->assert_timestamp > 0) { - const char *rel; - - rel = FORMAT_TIMESTAMP_RELATIVE(i->assert_timestamp); - printf(" Assert: start %sassertion failed%s at %s%s%s\n", + printf(" Assert: start %sassertion failed%s at %s; %s\n", ansi_highlight_red(), ansi_normal(), FORMAT_TIMESTAMP_STYLE(i->assert_timestamp, arg_timestamp_style), - rel ? "; " : "", strempty(rel)); + FORMAT_TIMESTAMP_RELATIVE(i->assert_timestamp)); if (i->failed_assert_trigger) printf(" none of the trigger assertions were met\n"); else if (i->failed_assert) diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index bc23affc81..7079bffa7f 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -384,8 +384,7 @@ static void test_FORMAT_TIMESTAMP(void) { assert_se(parse_timestamp(buf, &y) >= 0); assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); - char *t = FORMAT_TIMESTAMP(x); - assert_se(streq(t, buf)); + assert_se(streq(FORMAT_TIMESTAMP(x), buf)); } }