2 * lsclocks(1) - display system clocks
4 * Copyright (C) 2023 Thomas Weißschuh <thomas@t-8ch.de>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it would be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 #include <sys/ioctl.h>
29 #include <libsmartcols.h>
31 #include <linux/rtc.h>
36 #include "timeutils.h"
37 #include "closestream.h"
39 #include "pathnames.h"
43 #ifndef CLOCK_REALTIME
44 #define CLOCK_REALTIME 0
47 #ifndef CLOCK_MONOTONIC
48 #define CLOCK_MONOTONIC 1
51 #ifndef CLOCK_MONOTONIC_RAW
52 #define CLOCK_MONOTONIC_RAW 4
55 #ifndef CLOCK_REALTIME_COARSE
56 #define CLOCK_REALTIME_COARSE 5
59 #ifndef CLOCK_MONOTONIC_COARSE
60 #define CLOCK_MONOTONIC_COARSE 6
63 #ifndef CLOCK_BOOTTIME
64 #define CLOCK_BOOTTIME 7
67 #ifndef CLOCK_REALTIME_ALARM
68 #define CLOCK_REALTIME_ALARM 8
71 #ifndef CLOCK_BOOTTIME_ALARM
72 #define CLOCK_BOOTTIME_ALARM 9
86 static const char *clock_type_name(enum CLOCK_TYPE type
)
98 errx(EXIT_FAILURE
, _("Unknown clock type %d"), type
);
102 enum CLOCK_TYPE type
;
104 const char * const id_name
;
105 const char * const name
;
106 const char * const ns_offset_name
;
110 static const struct clockinfo clocks
[] = {
111 { CT_SYS
, CLOCK_REALTIME
, "CLOCK_REALTIME", "realtime" },
112 { CT_SYS
, CLOCK_MONOTONIC
, "CLOCK_MONOTONIC", "monotonic",
113 .ns_offset_name
= "monotonic" },
114 { CT_SYS
, CLOCK_MONOTONIC_RAW
, "CLOCK_MONOTONIC_RAW", "monotonic-raw" },
115 { CT_SYS
, CLOCK_REALTIME_COARSE
, "CLOCK_REALTIME_COARSE", "realtime-coarse" },
116 { CT_SYS
, CLOCK_MONOTONIC_COARSE
, "CLOCK_MONOTONIC_COARSE", "monotonic-coarse" },
117 { CT_SYS
, CLOCK_BOOTTIME
, "CLOCK_BOOTTIME", "boottime",
118 .ns_offset_name
= "boottime" },
119 { CT_SYS
, CLOCK_REALTIME_ALARM
, "CLOCK_REALTIME_ALARM", "realtime-alarm" },
120 { CT_SYS
, CLOCK_BOOTTIME_ALARM
, "CLOCK_BOOTTIME_ALARM", "boottime-alarm" },
121 { CT_SYS
, CLOCK_TAI
, "CLOCK_TAI", "tai" },
140 const char * const name
; /* header */
141 double whint
; /* width hint (N < 1 is in percent of termwidth) */
142 int flags
; /* SCOLS_FL_* */
143 int json_type
; /* SCOLS_JSON_* */
144 const char * const help
;
147 /* columns descriptions */
148 static const struct colinfo infos
[] = {
149 [COL_TYPE
] = { "TYPE", 1, 0, SCOLS_JSON_STRING
, N_("type") },
150 [COL_ID
] = { "ID", 1, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
, N_("numeric id") },
151 [COL_CLOCK
] = { "CLOCK", 1, 0, SCOLS_JSON_STRING
, N_("symbolic name") },
152 [COL_NAME
] = { "NAME", 1, 0, SCOLS_JSON_STRING
, N_("readable name") },
153 [COL_TIME
] = { "TIME", 1, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
, N_("numeric time") },
154 [COL_ISO_TIME
] = { "ISO_TIME", 1, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
, N_("human readable ISO time") },
155 [COL_RESOL
] = { "RESOL", 1, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
, N_("human readable resolution") },
156 [COL_RESOL_RAW
] = { "RESOL_RAW", 1, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
, N_("resolution") },
157 [COL_REL_TIME
] = { "REL_TIME", 1, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
, N_("human readable relative time") },
158 [COL_NS_OFFSET
] = { "NS_OFFSET", 1, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
, N_("namespace offset") },
161 static int column_name_to_id(const char *name
, size_t namesz
)
165 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++) {
166 const char *cn
= infos
[i
].name
;
168 if (!strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
))
171 warnx(_("unknown column: %s"), name
);
176 static void __attribute__((__noreturn__
)) usage(void)
181 fputs(USAGE_HEADER
, out
);
182 fprintf(out
, _(" %s [options]\n"), program_invocation_short_name
);
184 fputs(USAGE_OPTIONS
, out
);
185 fputs(_(" -J, --json use JSON output format\n"), out
);
186 fputs(_(" -n, --noheadings don't print headings\n"), out
);
187 fputs(_(" -o, --output <list> output columns\n"), out
);
188 fputs(_(" --output-all output all columns\n"), out
);
189 fputs(_(" -r, --raw use raw output format\n"), out
);
190 fputs(_(" -t, --time <clock> show current time of single clock\n"), out
);
191 fputs(_(" --no-discover-dynamic do not try to discover dynamic clocks\n"), out
);
192 fputs(_(" -d, --dynamic-clock <path> also display specified dynamic clock\n"), out
);
193 fputs(_(" -c, --cpu-clock <pid> also display CPU clock of specified process\n"), out
);
195 fputs(USAGE_SEPARATOR
, out
);
196 fprintf(out
, USAGE_HELP_OPTIONS(29));
198 fputs(USAGE_COLUMNS
, out
);
200 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++)
201 fprintf(out
, " %16s %-10s%s\n", infos
[i
].name
,
202 infos
[i
].json_type
== SCOLS_JSON_STRING
? "<string>":
203 infos
[i
].json_type
== SCOLS_JSON_ARRAY_STRING
? "<string>":
204 infos
[i
].json_type
== SCOLS_JSON_ARRAY_NUMBER
? "<string>":
205 infos
[i
].json_type
== SCOLS_JSON_NUMBER
? "<number>":
209 fprintf(out
, USAGE_MAN_TAIL("lsclocks(1)"));
214 __attribute__ ((__format__ (__printf__
, 3, 4)))
215 static void scols_line_asprintf(struct libscols_line
*ln
, size_t n
, const char *format
, ...)
220 va_start(args
, format
);
221 xvasprintf(&data
, format
, args
);
224 scols_line_refer_data(ln
, n
, data
);
227 static void scols_line_format_timespec(struct libscols_line
*ln
, size_t n
, const struct timespec
*ts
)
229 scols_line_asprintf(ln
, n
, "%ju.%09" PRId32
, (uintmax_t) ts
->tv_sec
, (uint32_t) ts
->tv_nsec
);
232 static clockid_t
parse_clock(const char *name
)
238 rc
= ul_strtou32(name
, &id
, 10);
240 for (i
= 0; i
< ARRAY_SIZE(clocks
); i
++) {
241 if (!strcmp(name
, clocks
[i
].id_name
)
242 || !strcmp(name
, clocks
[i
].name
))
244 if (rc
== 0 && (clockid_t
) id
== clocks
[i
].id
)
248 errx(EXIT_FAILURE
, _("Unknown clock: %s"), name
);
251 static int64_t get_namespace_offset(const char *name
)
253 char *tokstr
, *buf
, *saveptr
, *line
, *space
;
257 fd
= open(_PATH_PROC_TIMENS_OFF
, O_RDONLY
);
259 err(EXIT_FAILURE
, _("Could not open %s"), _PATH_PROC_TIMENS_OFF
);
261 read_all_alloc(fd
, &buf
);
263 for (tokstr
= buf
; ; tokstr
= NULL
) {
264 line
= strtok_r(tokstr
, "\n", &saveptr
);
267 line
= (char *) startswith(line
, name
);
268 if (!line
|| line
[0] != ' ')
271 line
= (char *) skip_blank(line
);
272 space
= strchr(line
, ' ');
275 ret
= strtos64_or_err(line
, _("Invalid offset"));
283 static void add_clock_line(struct libscols_table
*tb
, const int *columns
,
284 size_t ncolumns
, const struct clockinfo
*clockinfo
,
285 const struct timespec
*now
, const struct timespec
*resolution
)
287 char buf
[FORMAT_TIMESTAMP_MAX
];
288 struct libscols_line
*ln
;
292 ln
= scols_table_new_line(tb
, NULL
);
294 errx(EXIT_FAILURE
, _("failed to allocate output line"));
296 for (i
= 0; i
< ncolumns
; i
++) {
297 switch (columns
[i
]) {
299 scols_line_set_data(ln
, i
, clock_type_name(clockinfo
->type
));
302 if (!clockinfo
->no_id
)
303 scols_line_asprintf(ln
, i
, "%ju", (uintmax_t) clockinfo
->id
);
306 scols_line_set_data(ln
, i
, clockinfo
->id_name
);
309 scols_line_set_data(ln
, i
, clockinfo
->name
);
312 if (now
->tv_nsec
== -1)
315 scols_line_format_timespec(ln
, i
, now
);
318 if (now
->tv_nsec
== -1)
321 rc
= strtimespec_iso(now
,
322 ISO_GMTIME
| ISO_DATE
| ISO_TIME
| ISO_T
| ISO_DOTNSEC
| ISO_TIMEZONE
,
325 errx(EXIT_FAILURE
, _("failed to format iso time"));
326 scols_line_set_data(ln
, i
, buf
);
329 if (resolution
->tv_nsec
== -1)
332 rc
= strtimespec_relative(resolution
, buf
, sizeof(buf
));
334 errx(EXIT_FAILURE
, _("failed to format relative time"));
335 scols_line_set_data(ln
, i
, buf
);
338 if (resolution
->tv_nsec
== -1)
340 scols_line_format_timespec(ln
, i
, resolution
);
343 if (now
->tv_nsec
== -1)
345 rc
= strtimespec_relative(now
, buf
, sizeof(buf
));
347 errx(EXIT_FAILURE
, _("failed to format relative time"));
348 scols_line_set_data(ln
, i
, buf
);
351 if (clockinfo
->ns_offset_name
)
352 scols_line_asprintf(ln
, i
, "%"PRId64
,
353 get_namespace_offset(clockinfo
->ns_offset_name
));
359 static void add_posix_clock_line(struct libscols_table
*tb
, const int *columns
,
360 size_t ncolumns
, const struct clockinfo
*clockinfo
)
362 struct timespec resolution
, now
;
365 rc
= clock_gettime(clockinfo
->id
, &now
);
369 rc
= clock_getres(clockinfo
->id
, &resolution
);
371 resolution
.tv_nsec
= -1;
373 add_clock_line(tb
, columns
, ncolumns
, clockinfo
, &now
, &resolution
);
377 struct list_head head
;
381 static void add_dynamic_clock_from_path(struct libscols_table
*tb
,
382 const int *columns
, size_t ncolumns
,
383 const char *path
, bool explicit)
385 int fd
= open(path
, O_RDONLY
);
388 err(EXIT_FAILURE
, _("Could not open %s"), path
);
393 struct clockinfo clockinfo
= {
399 add_posix_clock_line(tb
, columns
, ncolumns
, &clockinfo
);
403 static void add_dynamic_clocks_from_discovery(struct libscols_table
*tb
,
404 const int *columns
, size_t ncolumns
)
410 rc
= glob("/dev/ptp*", 0, NULL
, &state
);
411 if (rc
== GLOB_NOMATCH
)
414 errx(EXIT_FAILURE
, _("Could not glob: %d"), rc
);
416 for (i
= 0; i
< state
.gl_pathc
; i
++)
417 add_dynamic_clock_from_path(tb
, columns
, ncolumns
,
418 state
.gl_pathv
[i
], false);
423 static void add_rtc_clock_from_path(struct libscols_table
*tb
,
424 const int *columns
, size_t ncolumns
,
425 const char *path
, bool explicit)
428 struct rtc_time rtc_time
;
429 struct tm tm
= { 0 };
430 struct timespec now
= { 0 }, resolution
= { .tv_nsec
= -1 };
432 fd
= open(path
, O_RDONLY
);
435 err(EXIT_FAILURE
, _("Could not open %s"), path
);
440 rc
= ioctl(fd
, RTC_RD_TIME
, &rtc_time
);
443 _("ioctl(RTC_RD_NAME) to %s to read the time failed"), path
);
445 tm
.tm_sec
= rtc_time
.tm_sec
;
446 tm
.tm_min
= rtc_time
.tm_min
;
447 tm
.tm_hour
= rtc_time
.tm_hour
;
448 tm
.tm_mday
= rtc_time
.tm_mday
;
449 tm
.tm_mon
= rtc_time
.tm_mon
;
450 tm
.tm_year
= rtc_time
.tm_year
;
451 tm
.tm_wday
= rtc_time
.tm_wday
;
452 tm
.tm_yday
= rtc_time
.tm_yday
;
454 now
.tv_sec
= mktime(&tm
);
456 struct clockinfo clockinfo
= {
462 add_clock_line(tb
, columns
, ncolumns
, &clockinfo
, &now
, &resolution
);
467 static void add_rtc_clocks_from_discovery(struct libscols_table
*tb
,
468 const int *columns
, size_t ncolumns
)
474 rc
= glob("/dev/rtc*", 0, NULL
, &state
);
475 if (rc
== GLOB_NOMATCH
)
478 errx(EXIT_FAILURE
, _("Could not glob: %d"), rc
);
480 for (i
= 0; i
< state
.gl_pathc
; i
++)
481 add_rtc_clock_from_path(tb
, columns
, ncolumns
,
482 state
.gl_pathv
[i
], false);
488 struct list_head head
;
490 char name
[sizeof(stringify_value(SINT_MAX(pid_t
)))];
493 static void add_cpu_clock(struct libscols_table
*tb
,
494 const int *columns
, size_t ncolumns
,
495 pid_t pid
, const char *name
)
500 rc
= clock_getcpuclockid(pid
, &clockid
);
502 errx(EXIT_FAILURE
, _("Could not get CPU clock of process %jd: %s"),
503 (intmax_t) pid
, strerror(rc
));
505 struct clockinfo clockinfo
= {
510 add_posix_clock_line(tb
, columns
, ncolumns
, &clockinfo
);
514 int main(int argc
, char **argv
)
518 const struct colinfo
*colinfo
;
520 struct libscols_table
*tb
;
521 struct libscols_column
*col
;
523 bool noheadings
= false, raw
= false, json
= false,
524 disc_dynamic
= true, disc_rtc
= true;
525 const char *outarg
= NULL
;
526 int columns
[ARRAY_SIZE(infos
) * 2];
528 clockid_t clock
= -1;
529 struct path_clock
*path_clock
;
530 struct cpu_clock
*cpu_clock
;
531 struct list_head
*current_path_clock
, *current_cpu_clock
;
532 struct list_head dynamic_clocks
, cpu_clocks
, rtc_clocks
;
537 OPT_OUTPUT_ALL
= CHAR_MAX
+ 1,
541 static const struct option longopts
[] = {
542 { "noheadings", no_argument
, NULL
, 'n' },
543 { "output", required_argument
, NULL
, 'o' },
544 { "output-all", no_argument
, NULL
, OPT_OUTPUT_ALL
},
545 { "version", no_argument
, NULL
, 'V' },
546 { "help", no_argument
, NULL
, 'h' },
547 { "json", no_argument
, NULL
, 'J' },
548 { "raw", no_argument
, NULL
, 'r' },
549 { "time", required_argument
, NULL
, 't' },
550 { "no-discover-dynamic", no_argument
, NULL
, OPT_NO_DISC_DYN
},
551 { "dynamic-clock", required_argument
, NULL
, 'd' },
552 { "cpu-clock", required_argument
, NULL
, 'c' },
553 { "no-discover-rtc", no_argument
, NULL
, OPT_NO_DISC_RTC
},
554 { "rtc", required_argument
, NULL
, 'x' },
558 setlocale(LC_ALL
, "");
559 bindtextdomain(PACKAGE
, LOCALEDIR
);
561 close_stdout_atexit();
563 INIT_LIST_HEAD(&dynamic_clocks
);
564 INIT_LIST_HEAD(&cpu_clocks
);
565 INIT_LIST_HEAD(&rtc_clocks
);
567 while ((c
= getopt_long(argc
, argv
, "no:Jrt:d:c:x:Vh", longopts
, NULL
)) != -1) {
576 for (ncolumns
= 0; ncolumns
< ARRAY_SIZE(infos
); ncolumns
++)
577 columns
[ncolumns
] = ncolumns
;
586 clock
= parse_clock(optarg
);
589 path_clock
= xmalloc(sizeof(*path_clock
));
590 path_clock
->path
= optarg
;
591 list_add(&path_clock
->head
, &dynamic_clocks
);
593 case OPT_NO_DISC_DYN
:
594 disc_dynamic
= false;
597 cpu_clock
= xmalloc(sizeof(*cpu_clock
));
598 cpu_clock
->pid
= strtopid_or_err(optarg
, _("failed to parse pid"));
599 snprintf(cpu_clock
->name
, sizeof(cpu_clock
->name
),
600 "%jd", (intmax_t) cpu_clock
->pid
);
601 list_add(&cpu_clock
->head
, &cpu_clocks
);
604 path_clock
= xmalloc(sizeof(*path_clock
));
605 path_clock
->path
= optarg
;
606 list_add(&path_clock
->head
, &rtc_clocks
);
608 case OPT_NO_DISC_RTC
:
612 print_version(EXIT_SUCCESS
);
616 errtryhelp(EXIT_FAILURE
);
621 errtryhelp(EXIT_FAILURE
);
624 rc
= clock_gettime(clock
, &now
);
626 err(EXIT_FAILURE
, _("failed to get time"));
627 printf("%ju.%09"PRId32
"\n", (uintmax_t) now
.tv_sec
, (uint32_t) now
.tv_nsec
);
632 columns
[ncolumns
++] = COL_ID
;
633 columns
[ncolumns
++] = COL_NAME
;
634 columns
[ncolumns
++] = COL_TYPE
;
635 columns
[ncolumns
++] = COL_TIME
;
636 columns
[ncolumns
++] = COL_RESOL
;
637 columns
[ncolumns
++] = COL_ISO_TIME
;
640 if (outarg
&& string_add_to_idarray(outarg
, columns
, ARRAY_SIZE(columns
),
641 &ncolumns
, column_name_to_id
) < 0)
646 tb
= scols_new_table();
648 errx(EXIT_FAILURE
, _("failed to allocate output table"));
649 scols_table_set_name(tb
, "clocks");
651 for (i
= 0; i
< ncolumns
; i
++) {
652 colinfo
= &infos
[columns
[i
]];
654 col
= scols_table_new_column(tb
, colinfo
->name
, colinfo
->whint
, colinfo
->flags
);
656 errx(EXIT_FAILURE
, _("failed to allocate output column"));
658 scols_column_set_json_type(col
, colinfo
->json_type
);
661 for (i
= 0; i
< ARRAY_SIZE(clocks
); i
++)
662 add_posix_clock_line(tb
, columns
, ncolumns
, &clocks
[i
]);
665 add_dynamic_clocks_from_discovery(tb
, columns
, ncolumns
);
667 list_for_each(current_path_clock
, &dynamic_clocks
) {
668 path_clock
= list_entry(current_path_clock
, struct path_clock
, head
);
669 add_dynamic_clock_from_path(tb
, columns
, ncolumns
, path_clock
->path
, true);
672 list_free(&dynamic_clocks
, struct path_clock
, head
, free
);
675 add_rtc_clocks_from_discovery(tb
, columns
, ncolumns
);
677 list_for_each(current_path_clock
, &rtc_clocks
) {
678 path_clock
= list_entry(current_path_clock
, struct path_clock
, head
);
679 add_rtc_clock_from_path(tb
, columns
, ncolumns
, path_clock
->path
, true);
682 list_free(&rtc_clocks
, struct path_clock
, head
, free
);
684 list_for_each(current_cpu_clock
, &cpu_clocks
) {
685 cpu_clock
= list_entry(current_cpu_clock
, struct cpu_clock
, head
);
686 add_cpu_clock(tb
, columns
, ncolumns
, cpu_clock
->pid
, cpu_clock
->name
);
689 list_free(&cpu_clocks
, struct cpu_clock
, head
, free
);
691 scols_table_enable_json(tb
, json
);
692 scols_table_enable_raw(tb
, raw
);
693 scols_table_enable_noheadings(tb
, noheadings
);
694 scols_print_table(tb
);
695 scols_unref_table(tb
);