]>
Commit | Line | Data |
---|---|---|
e9ddea79 TW |
1 | /* |
2 | * lsclocks(1) - display system clocks | |
3 | * | |
4 | * Copyright (C) 2023 Thomas Weißschuh <thomas@t-8ch.de> | |
5 | * | |
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. | |
10 | * | |
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. | |
15 | * | |
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. | |
19 | */ | |
20 | ||
21 | #include <stdio.h> | |
22 | #include <stdbool.h> | |
23 | #include <time.h> | |
24 | #include <inttypes.h> | |
25 | #include <getopt.h> | |
6fcb27e7 | 26 | #include <glob.h> |
edba74df | 27 | #include <sys/ioctl.h> |
e9ddea79 TW |
28 | |
29 | #include <libsmartcols.h> | |
30 | ||
edba74df TW |
31 | #include <linux/rtc.h> |
32 | ||
e9ddea79 TW |
33 | #include "c.h" |
34 | #include "nls.h" | |
35 | #include "strutils.h" | |
36 | #include "timeutils.h" | |
37 | #include "closestream.h" | |
38 | #include "xalloc.h" | |
ed59648c TW |
39 | #include "pathnames.h" |
40 | #include "all-io.h" | |
c4620c6a | 41 | #include "list.h" |
e9ddea79 | 42 | |
e9ddea79 TW |
43 | #ifndef CLOCK_REALTIME |
44 | #define CLOCK_REALTIME 0 | |
45 | #endif | |
46 | ||
47 | #ifndef CLOCK_MONOTONIC | |
48 | #define CLOCK_MONOTONIC 1 | |
49 | #endif | |
50 | ||
51 | #ifndef CLOCK_MONOTONIC_RAW | |
52 | #define CLOCK_MONOTONIC_RAW 4 | |
53 | #endif | |
54 | ||
55 | #ifndef CLOCK_REALTIME_COARSE | |
56 | #define CLOCK_REALTIME_COARSE 5 | |
57 | #endif | |
58 | ||
59 | #ifndef CLOCK_MONOTONIC_COARSE | |
60 | #define CLOCK_MONOTONIC_COARSE 6 | |
61 | #endif | |
62 | ||
63 | #ifndef CLOCK_BOOTTIME | |
64 | #define CLOCK_BOOTTIME 7 | |
65 | #endif | |
66 | ||
67 | #ifndef CLOCK_REALTIME_ALARM | |
68 | #define CLOCK_REALTIME_ALARM 8 | |
69 | #endif | |
70 | ||
71 | #ifndef CLOCK_BOOTTIME_ALARM | |
72 | #define CLOCK_BOOTTIME_ALARM 9 | |
73 | #endif | |
74 | ||
75 | #ifndef CLOCK_TAI | |
76 | #define CLOCK_TAI 11 | |
77 | #endif | |
78 | ||
ac9ee302 TW |
79 | enum CLOCK_TYPE { |
80 | CT_SYS, | |
c4620c6a | 81 | CT_PTP, |
dc3f05fc | 82 | CT_CPU, |
edba74df | 83 | CT_RTC, |
ac9ee302 TW |
84 | }; |
85 | ||
86 | static const char *clock_type_name(enum CLOCK_TYPE type) | |
87 | { | |
88 | switch (type) { | |
89 | case CT_SYS: | |
90 | return "sys"; | |
c4620c6a TW |
91 | case CT_PTP: |
92 | return "ptp"; | |
dc3f05fc TW |
93 | case CT_CPU: |
94 | return "cpu"; | |
edba74df TW |
95 | case CT_RTC: |
96 | return "rtc"; | |
ac9ee302 TW |
97 | } |
98 | errx(EXIT_FAILURE, _("Unknown clock type %d"), type); | |
99 | } | |
100 | ||
e9ddea79 | 101 | struct clockinfo { |
ac9ee302 | 102 | enum CLOCK_TYPE type; |
e9ddea79 TW |
103 | clockid_t id; |
104 | const char * const id_name; | |
105 | const char * const name; | |
ed59648c | 106 | const char * const ns_offset_name; |
edba74df | 107 | bool no_id; |
e9ddea79 TW |
108 | }; |
109 | ||
110 | static const struct clockinfo clocks[] = { | |
ac9ee302 TW |
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" }, | |
e9ddea79 TW |
122 | }; |
123 | ||
124 | /* column IDs */ | |
125 | enum { | |
ac9ee302 | 126 | COL_TYPE, |
e9ddea79 TW |
127 | COL_ID, |
128 | COL_CLOCK, | |
129 | COL_NAME, | |
130 | COL_TIME, | |
131 | COL_ISO_TIME, | |
f9c9bb15 | 132 | COL_RESOL, |
36ebc72a | 133 | COL_RESOL_RAW, |
0c7cba92 | 134 | COL_REL_TIME, |
ed59648c | 135 | COL_NS_OFFSET, |
e9ddea79 TW |
136 | }; |
137 | ||
138 | /* column names */ | |
139 | struct colinfo { | |
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; | |
145 | }; | |
146 | ||
147 | /* columns descriptions */ | |
148 | static const struct colinfo infos[] = { | |
ac9ee302 | 149 | [COL_TYPE] = { "TYPE", 1, 0, SCOLS_JSON_STRING, N_("type") }, |
e9ddea79 TW |
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") }, | |
f9c9bb15 | 155 | [COL_RESOL] = { "RESOL", 1, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("human readable resolution") }, |
36ebc72a | 156 | [COL_RESOL_RAW] = { "RESOL_RAW", 1, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("resolution") }, |
0c7cba92 | 157 | [COL_REL_TIME] = { "REL_TIME", 1, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("human readable relative time") }, |
ed59648c | 158 | [COL_NS_OFFSET] = { "NS_OFFSET", 1, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("namespace offset") }, |
e9ddea79 TW |
159 | }; |
160 | ||
161 | static int column_name_to_id(const char *name, size_t namesz) | |
162 | { | |
163 | size_t i; | |
164 | ||
165 | for (i = 0; i < ARRAY_SIZE(infos); i++) { | |
166 | const char *cn = infos[i].name; | |
167 | ||
168 | if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) | |
169 | return i; | |
170 | } | |
171 | warnx(_("unknown column: %s"), name); | |
172 | ||
173 | return -1; | |
174 | } | |
175 | ||
176 | static void __attribute__((__noreturn__)) usage(void) | |
177 | { | |
178 | FILE *out = stdout; | |
179 | size_t i; | |
180 | ||
181 | fputs(USAGE_HEADER, out); | |
182 | fprintf(out, _(" %s [options]\n"), program_invocation_short_name); | |
183 | ||
184 | fputs(USAGE_OPTIONS, out); | |
c4620c6a TW |
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); | |
6fcb27e7 | 191 | fputs(_(" --no-discover-dynamic do not try to discover dynamic clocks\n"), out); |
c4620c6a | 192 | fputs(_(" -d, --dynamic-clock <path> also display specified dynamic clock\n"), out); |
dc3f05fc | 193 | fputs(_(" -c, --cpu-clock <pid> also display CPU clock of specified process\n"), out); |
e9ddea79 TW |
194 | |
195 | fputs(USAGE_SEPARATOR, out); | |
bad4c729 | 196 | fprintf(out, USAGE_HELP_OPTIONS(29)); |
e9ddea79 | 197 | |
38b8eac6 | 198 | fputs(USAGE_COLUMNS, out); |
e9ddea79 TW |
199 | |
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>": | |
206 | "<boolean>", | |
207 | _(infos[i].help)); | |
208 | ||
bad4c729 | 209 | fprintf(out, USAGE_MAN_TAIL("lslocks(1)")); |
e9ddea79 TW |
210 | |
211 | exit(EXIT_SUCCESS); | |
212 | } | |
213 | ||
214 | __attribute__ ((__format__ (__printf__, 3, 4))) | |
215 | static void scols_line_asprintf(struct libscols_line *ln, size_t n, const char *format, ...) | |
216 | { | |
217 | char *data; | |
218 | va_list args; | |
219 | ||
220 | va_start(args, format); | |
221 | xvasprintf(&data, format, args); | |
222 | va_end(args); | |
223 | ||
224 | scols_line_refer_data(ln, n, data); | |
225 | } | |
226 | ||
227 | static void scols_line_format_timespec(struct libscols_line *ln, size_t n, const struct timespec *ts) | |
228 | { | |
229 | scols_line_asprintf(ln, n, "%ju.%09" PRId32, (uintmax_t) ts->tv_sec, (uint32_t) ts->tv_nsec); | |
230 | } | |
231 | ||
232 | static clockid_t parse_clock(const char *name) | |
233 | { | |
234 | size_t i; | |
235 | uint32_t id = -1; | |
236 | int rc; | |
237 | ||
238 | rc = ul_strtou32(name, &id, 10); | |
239 | ||
240 | for (i = 0; i < ARRAY_SIZE(clocks); i++) { | |
241 | if (!strcmp(name, clocks[i].id_name) | |
242 | || !strcmp(name, clocks[i].name)) | |
243 | return clocks[i].id; | |
244 | if (rc == 0 && (clockid_t) id == clocks[i].id) | |
245 | return id; | |
246 | } | |
247 | ||
248 | errx(EXIT_FAILURE, _("Unknown clock: %s"), name); | |
249 | } | |
250 | ||
ed59648c TW |
251 | static int64_t get_namespace_offset(const char *name) |
252 | { | |
253 | char *tokstr, *buf, *saveptr, *line, *space; | |
254 | uint64_t ret; | |
255 | int fd; | |
256 | ||
257 | fd = open(_PATH_PROC_TIMENS_OFF, O_RDONLY); | |
258 | if (fd == -1) | |
259 | err(EXIT_FAILURE, _("Could not open %s"), _PATH_PROC_TIMENS_OFF); | |
260 | ||
261 | read_all_alloc(fd, &buf); | |
262 | ||
263 | for (tokstr = buf; ; tokstr = NULL) { | |
264 | line = strtok_r(tokstr, "\n", &saveptr); | |
265 | if (!line) | |
266 | continue; | |
267 | line = (char *) startswith(line, name); | |
268 | if (!line || line[0] != ' ') | |
269 | continue; | |
270 | ||
271 | line = (char *) skip_blank(line); | |
272 | space = strchr(line, ' '); | |
273 | if (space) | |
274 | *space = '\0'; | |
275 | ret = strtos64_or_err(line, _("Invalid offset")); | |
276 | break; | |
277 | } | |
278 | ||
279 | free(buf); | |
280 | return ret; | |
281 | } | |
282 | ||
c31251ef | 283 | static void add_clock_line(struct libscols_table *tb, const int *columns, |
edba74df TW |
284 | size_t ncolumns, const struct clockinfo *clockinfo, |
285 | const struct timespec *now, const struct timespec *resolution) | |
c31251ef | 286 | { |
c31251ef TW |
287 | char buf[FORMAT_TIMESTAMP_MAX]; |
288 | struct libscols_line *ln; | |
289 | size_t i; | |
290 | int rc; | |
291 | ||
292 | ln = scols_table_new_line(tb, NULL); | |
293 | if (!ln) | |
294 | errx(EXIT_FAILURE, _("failed to allocate output line")); | |
295 | ||
c31251ef TW |
296 | for (i = 0; i < ncolumns; i++) { |
297 | switch (columns[i]) { | |
298 | case COL_TYPE: | |
299 | scols_line_set_data(ln, i, clock_type_name(clockinfo->type)); | |
300 | break; | |
301 | case COL_ID: | |
edba74df | 302 | if (!clockinfo->no_id) |
c4620c6a | 303 | scols_line_asprintf(ln, i, "%ju", (uintmax_t) clockinfo->id); |
c31251ef TW |
304 | break; |
305 | case COL_CLOCK: | |
306 | scols_line_set_data(ln, i, clockinfo->id_name); | |
307 | break; | |
308 | case COL_NAME: | |
309 | scols_line_set_data(ln, i, clockinfo->name); | |
310 | break; | |
311 | case COL_TIME: | |
edba74df | 312 | if (now->tv_nsec == -1) |
c31251ef TW |
313 | break; |
314 | ||
edba74df | 315 | scols_line_format_timespec(ln, i, now); |
c31251ef TW |
316 | break; |
317 | case COL_ISO_TIME: | |
edba74df | 318 | if (now->tv_nsec == -1) |
c31251ef TW |
319 | break; |
320 | ||
edba74df | 321 | rc = strtimespec_iso(now, |
c31251ef TW |
322 | ISO_GMTIME | ISO_DATE | ISO_TIME | ISO_T | ISO_DOTNSEC | ISO_TIMEZONE, |
323 | buf, sizeof(buf)); | |
324 | if (rc) | |
325 | errx(EXIT_FAILURE, _("failed to format iso time")); | |
326 | scols_line_set_data(ln, i, buf); | |
327 | break; | |
328 | case COL_RESOL: | |
edba74df | 329 | if (resolution->tv_nsec == -1) |
c31251ef TW |
330 | break; |
331 | ||
edba74df | 332 | rc = strtimespec_relative(resolution, buf, sizeof(buf)); |
c31251ef TW |
333 | if (rc) |
334 | errx(EXIT_FAILURE, _("failed to format relative time")); | |
335 | scols_line_set_data(ln, i, buf); | |
336 | break; | |
337 | case COL_RESOL_RAW: | |
edba74df | 338 | if (resolution->tv_nsec == -1) |
c31251ef | 339 | break; |
edba74df | 340 | scols_line_format_timespec(ln, i, resolution); |
c31251ef TW |
341 | break; |
342 | case COL_REL_TIME: | |
edba74df | 343 | if (now->tv_nsec == -1) |
c31251ef | 344 | break; |
edba74df | 345 | rc = strtimespec_relative(now, buf, sizeof(buf)); |
c31251ef TW |
346 | if (rc) |
347 | errx(EXIT_FAILURE, _("failed to format relative time")); | |
348 | scols_line_set_data(ln, i, buf); | |
349 | break; | |
350 | case COL_NS_OFFSET: | |
351 | if (clockinfo->ns_offset_name) | |
352 | scols_line_asprintf(ln, i, "%"PRId64, | |
353 | get_namespace_offset(clockinfo->ns_offset_name)); | |
354 | break; | |
355 | } | |
356 | } | |
357 | } | |
358 | ||
edba74df TW |
359 | static void add_posix_clock_line(struct libscols_table *tb, const int *columns, |
360 | size_t ncolumns, const struct clockinfo *clockinfo) | |
361 | { | |
362 | struct timespec resolution, now; | |
363 | int rc; | |
364 | ||
365 | rc = clock_gettime(clockinfo->id, &now); | |
366 | if (rc) | |
367 | now.tv_nsec = -1; | |
368 | ||
369 | rc = clock_getres(clockinfo->id, &resolution); | |
370 | if (rc) | |
371 | resolution.tv_nsec = -1; | |
372 | ||
373 | add_clock_line(tb, columns, ncolumns, clockinfo, &now, &resolution); | |
374 | } | |
375 | ||
782c6564 | 376 | struct path_clock { |
c4620c6a TW |
377 | struct list_head head; |
378 | const char * path; | |
379 | }; | |
380 | ||
381 | static void add_dynamic_clock_from_path(struct libscols_table *tb, | |
382 | const int *columns, size_t ncolumns, | |
6fcb27e7 | 383 | const char *path, bool explicit) |
c4620c6a TW |
384 | { |
385 | int fd = open(path, O_RDONLY); | |
6fcb27e7 TW |
386 | if (fd == -1) { |
387 | if (explicit) | |
388 | err(EXIT_FAILURE, _("Could not open %s"), path); | |
389 | else | |
390 | return; | |
391 | } | |
c4620c6a TW |
392 | |
393 | struct clockinfo clockinfo = { | |
394 | .type = CT_PTP, | |
edba74df | 395 | .no_id = true, |
c4620c6a TW |
396 | .id_name = path, |
397 | .name = path, | |
398 | }; | |
edba74df | 399 | add_posix_clock_line(tb, columns, ncolumns, &clockinfo); |
c4620c6a TW |
400 | close(fd); |
401 | } | |
402 | ||
6fcb27e7 TW |
403 | static void add_dynamic_clocks_from_discovery(struct libscols_table *tb, |
404 | const int *columns, size_t ncolumns) | |
405 | { | |
406 | int rc; | |
407 | size_t i; | |
408 | glob_t state; | |
409 | ||
410 | rc = glob("/dev/ptp*", 0, NULL, &state); | |
db5fffd5 TW |
411 | if (rc == GLOB_NOMATCH) |
412 | return; | |
413 | else if (rc) | |
6fcb27e7 TW |
414 | errx(EXIT_FAILURE, _("Could not glob: %d"), rc); |
415 | ||
416 | for (i = 0; i < state.gl_pathc; i++) | |
417 | add_dynamic_clock_from_path(tb, columns, ncolumns, | |
418 | state.gl_pathv[i], false); | |
419 | ||
420 | globfree(&state); | |
421 | } | |
422 | ||
edba74df TW |
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) | |
426 | { | |
427 | int fd, rc; | |
428 | struct rtc_time rtc_time; | |
429 | struct tm tm = { 0 }; | |
430 | struct timespec now = { 0 }, resolution = { .tv_nsec = -1 }; | |
431 | ||
432 | fd = open(path, O_RDONLY); | |
433 | if (fd == -1) { | |
434 | if (explicit) | |
435 | err(EXIT_FAILURE, _("Could not open %s"), path); | |
436 | else | |
437 | return; | |
438 | } | |
439 | ||
440 | rc = ioctl(fd, RTC_RD_TIME, &rtc_time); | |
441 | if (rc) | |
442 | err(EXIT_FAILURE, | |
443 | _("ioctl(RTC_RD_NAME) to %s to read the time failed"), path); | |
444 | ||
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; | |
453 | ||
454 | now.tv_sec = mktime(&tm); | |
455 | ||
456 | struct clockinfo clockinfo = { | |
457 | .type = CT_RTC, | |
458 | .no_id = true, | |
459 | .id_name = path, | |
460 | .name = path, | |
461 | }; | |
462 | add_clock_line(tb, columns, ncolumns, &clockinfo, &now, &resolution); | |
463 | ||
464 | close(fd); | |
465 | } | |
466 | ||
467 | static void add_rtc_clocks_from_discovery(struct libscols_table *tb, | |
468 | const int *columns, size_t ncolumns) | |
469 | { | |
470 | int rc; | |
471 | size_t i; | |
472 | glob_t state; | |
473 | ||
474 | rc = glob("/dev/rtc*", 0, NULL, &state); | |
475 | if (rc == GLOB_NOMATCH) | |
476 | return; | |
477 | if (rc) | |
478 | errx(EXIT_FAILURE, _("Could not glob: %d"), rc); | |
479 | ||
480 | for (i = 0; i < state.gl_pathc; i++) | |
481 | add_rtc_clock_from_path(tb, columns, ncolumns, | |
482 | state.gl_pathv[i], false); | |
483 | ||
484 | globfree(&state); | |
485 | } | |
486 | ||
dc3f05fc TW |
487 | struct cpu_clock { |
488 | struct list_head head; | |
489 | pid_t pid; | |
490 | char name[sizeof(stringify_value(SINT_MAX(pid_t)))]; | |
491 | }; | |
492 | ||
493 | static void add_cpu_clock(struct libscols_table *tb, | |
494 | const int *columns, size_t ncolumns, | |
495 | pid_t pid, const char *name) | |
496 | { | |
497 | int rc; | |
498 | clockid_t clockid; | |
499 | ||
500 | rc = clock_getcpuclockid(pid, &clockid); | |
501 | if (rc) | |
502 | errx(EXIT_FAILURE, _("Could not get CPU clock of process %jd: %s"), | |
503 | (intmax_t) pid, strerror(rc)); | |
504 | ||
505 | struct clockinfo clockinfo = { | |
506 | .type = CT_CPU, | |
507 | .name = name, | |
edba74df | 508 | .no_id = true, |
dc3f05fc | 509 | }; |
edba74df | 510 | add_posix_clock_line(tb, columns, ncolumns, &clockinfo); |
dc3f05fc TW |
511 | } |
512 | ||
513 | ||
e9ddea79 TW |
514 | int main(int argc, char **argv) |
515 | { | |
c31251ef | 516 | size_t i; |
9f59d21c | 517 | int c, rc; |
e9ddea79 | 518 | const struct colinfo *colinfo; |
e9ddea79 TW |
519 | |
520 | struct libscols_table *tb; | |
e9ddea79 TW |
521 | struct libscols_column *col; |
522 | ||
edba74df TW |
523 | bool noheadings = false, raw = false, json = false, |
524 | disc_dynamic = true, disc_rtc = true; | |
e9ddea79 TW |
525 | const char *outarg = NULL; |
526 | int columns[ARRAY_SIZE(infos) * 2]; | |
527 | size_t ncolumns = 0; | |
528 | clockid_t clock = -1; | |
782c6564 | 529 | struct path_clock *path_clock; |
dc3f05fc | 530 | struct cpu_clock *cpu_clock; |
782c6564 | 531 | struct list_head *current_path_clock, *current_cpu_clock; |
edba74df | 532 | struct list_head dynamic_clocks, cpu_clocks, rtc_clocks; |
e9ddea79 | 533 | |
c31251ef | 534 | struct timespec now; |
e9ddea79 | 535 | |
9f59d21c | 536 | enum { |
6fcb27e7 TW |
537 | OPT_OUTPUT_ALL = CHAR_MAX + 1, |
538 | OPT_NO_DISC_DYN, | |
edba74df | 539 | OPT_NO_DISC_RTC, |
9f59d21c | 540 | }; |
e9ddea79 | 541 | static const struct option longopts[] = { |
6fcb27e7 TW |
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' }, | |
dc3f05fc | 552 | { "cpu-clock", required_argument, NULL, 'c' }, |
edba74df TW |
553 | { "no-discover-rtc", no_argument, NULL, OPT_NO_DISC_RTC }, |
554 | { "rtc", required_argument, NULL, 'x' }, | |
e9ddea79 TW |
555 | { 0 } |
556 | }; | |
557 | ||
558 | setlocale(LC_ALL, ""); | |
559 | bindtextdomain(PACKAGE, LOCALEDIR); | |
560 | textdomain(PACKAGE); | |
561 | close_stdout_atexit(); | |
562 | ||
c4620c6a | 563 | INIT_LIST_HEAD(&dynamic_clocks); |
dc3f05fc | 564 | INIT_LIST_HEAD(&cpu_clocks); |
edba74df | 565 | INIT_LIST_HEAD(&rtc_clocks); |
c4620c6a | 566 | |
edba74df | 567 | while ((c = getopt_long(argc, argv, "no:Jrt:d:c:x:Vh", longopts, NULL)) != -1) { |
e9ddea79 TW |
568 | switch (c) { |
569 | case 'n': | |
570 | noheadings = true; | |
571 | break; | |
572 | case 'o': | |
573 | outarg = optarg; | |
574 | break; | |
9f59d21c TW |
575 | case OPT_OUTPUT_ALL: |
576 | for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++) | |
577 | columns[ncolumns] = ncolumns; | |
578 | break; | |
e9ddea79 TW |
579 | case 'J': |
580 | json = true; | |
581 | break; | |
582 | case 'r': | |
583 | raw = true; | |
584 | break; | |
585 | case 't': | |
586 | clock = parse_clock(optarg); | |
587 | break; | |
c4620c6a | 588 | case 'd': |
782c6564 TW |
589 | path_clock = xmalloc(sizeof(*path_clock)); |
590 | path_clock->path = optarg; | |
591 | list_add(&path_clock->head, &dynamic_clocks); | |
c4620c6a | 592 | break; |
6fcb27e7 TW |
593 | case OPT_NO_DISC_DYN: |
594 | disc_dynamic = false; | |
595 | break; | |
dc3f05fc TW |
596 | case 'c': |
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); | |
602 | break; | |
edba74df TW |
603 | case 'x': |
604 | path_clock = xmalloc(sizeof(*path_clock)); | |
605 | path_clock->path = optarg; | |
606 | list_add(&path_clock->head, &rtc_clocks); | |
607 | break; | |
608 | case OPT_NO_DISC_RTC: | |
609 | disc_rtc = false; | |
610 | break; | |
e9ddea79 TW |
611 | case 'V': |
612 | print_version(EXIT_SUCCESS); | |
613 | case 'h': | |
614 | usage(); | |
615 | default: | |
616 | errtryhelp(EXIT_FAILURE); | |
617 | } | |
618 | } | |
619 | ||
620 | if (argv[optind]) | |
621 | errtryhelp(EXIT_FAILURE); | |
622 | ||
623 | if (clock != -1) { | |
624 | rc = clock_gettime(clock, &now); | |
625 | if (rc) | |
626 | err(EXIT_FAILURE, _("failed to get time")); | |
627 | printf("%ju.%09"PRId32"\n", (uintmax_t) now.tv_sec, (uint32_t) now.tv_nsec); | |
628 | return EXIT_SUCCESS; | |
629 | } | |
630 | ||
631 | if (!ncolumns) { | |
632 | columns[ncolumns++] = COL_ID; | |
e9ddea79 | 633 | columns[ncolumns++] = COL_NAME; |
ac9ee302 | 634 | columns[ncolumns++] = COL_TYPE; |
e9ddea79 | 635 | columns[ncolumns++] = COL_TIME; |
f9c9bb15 | 636 | columns[ncolumns++] = COL_RESOL; |
2664e3a4 | 637 | columns[ncolumns++] = COL_ISO_TIME; |
e9ddea79 TW |
638 | } |
639 | ||
640 | if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), | |
641 | &ncolumns, column_name_to_id) < 0) | |
642 | return EXIT_FAILURE; | |
643 | ||
644 | scols_init_debug(0); | |
645 | ||
646 | tb = scols_new_table(); | |
647 | if (!tb) | |
648 | errx(EXIT_FAILURE, _("failed to allocate output table")); | |
649 | scols_table_set_name(tb, "clocks"); | |
650 | ||
651 | for (i = 0; i < ncolumns; i++) { | |
652 | colinfo = &infos[columns[i]]; | |
653 | ||
654 | col = scols_table_new_column(tb, colinfo->name, colinfo->whint, colinfo->flags); | |
655 | if (!col) | |
656 | errx(EXIT_FAILURE, _("failed to allocate output column")); | |
657 | ||
658 | scols_column_set_json_type(col, colinfo->json_type); | |
659 | } | |
660 | ||
c31251ef | 661 | for (i = 0; i < ARRAY_SIZE(clocks); i++) |
edba74df | 662 | add_posix_clock_line(tb, columns, ncolumns, &clocks[i]); |
e9ddea79 | 663 | |
6fcb27e7 TW |
664 | if (disc_dynamic) |
665 | add_dynamic_clocks_from_discovery(tb, columns, ncolumns); | |
666 | ||
782c6564 TW |
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); | |
c4620c6a TW |
670 | } |
671 | ||
782c6564 | 672 | list_free(&dynamic_clocks, struct path_clock, head, free); |
c4620c6a | 673 | |
edba74df TW |
674 | if (disc_rtc) |
675 | add_rtc_clocks_from_discovery(tb, columns, ncolumns); | |
676 | ||
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); | |
680 | } | |
681 | ||
682 | list_free(&rtc_clocks, struct path_clock, head, free); | |
683 | ||
dc3f05fc TW |
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); | |
687 | } | |
688 | ||
689 | list_free(&cpu_clocks, struct cpu_clock, head, free); | |
690 | ||
e9ddea79 TW |
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); | |
696 | } |