1 /* $OpenBSD: ts.c,v 1.11 2022/10/11 07:36:27 jsg Exp $ */
2 /*
3 * Copyright (c) 2022 Job Snijders <job@openbsd.org>
4 * Copyright (c) 2022 Claudio Jeker <claudio@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/time.h>
22
23 #include <err.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <time.h>
30
31 SIMPLEQ_HEAD(, usec) usec_queue = SIMPLEQ_HEAD_INITIALIZER(usec_queue);
32 struct usec {
33 SIMPLEQ_ENTRY(usec) next;
34 char *pos;
35 };
36
37 static char *format = "%b %d %H:%M:%S";
38 static char *buf;
39 static char *outbuf;
40 static size_t bufsize;
41 static size_t obsize;
42
43 static void fmtfmt(void);
44 static void fmtprint(const struct timespec *);
45 static void __dead usage(void);
46
47 int
main(int argc,char * argv[])48 main(int argc, char *argv[])
49 {
50 int iflag, mflag, sflag;
51 int ch, prev;
52 struct timespec start, now, utc_offset, ts;
53 clockid_t clock = CLOCK_REALTIME;
54
55 if (pledge("stdio", NULL) == -1)
56 err(1, "pledge");
57
58 iflag = mflag = sflag = 0;
59
60 while ((ch = getopt(argc, argv, "ims")) != -1) {
61 switch (ch) {
62 case 'i':
63 iflag = 1;
64 format = "%H:%M:%S";
65 clock = CLOCK_MONOTONIC;
66 break;
67 case 'm':
68 mflag = 1;
69 clock = CLOCK_MONOTONIC;
70 break;
71 case 's':
72 sflag = 1;
73 format = "%H:%M:%S";
74 clock = CLOCK_MONOTONIC;
75 break;
76 default:
77 usage();
78 }
79 }
80 argc -= optind;
81 argv += optind;
82
83 setvbuf(stdout, NULL, _IOLBF, 0);
84
85 if ((iflag && sflag) || argc > 1)
86 usage();
87
88 if (argc == 1)
89 format = *argv;
90
91 bufsize = strlen(format) + 1;
92 if (bufsize > SIZE_MAX / 10)
93 errx(1, "format string too big");
94 bufsize *= 10;
95 obsize = bufsize;
96 if ((buf = calloc(1, bufsize)) == NULL)
97 err(1, NULL);
98 if ((outbuf = calloc(1, obsize)) == NULL)
99 err(1, NULL);
100
101 fmtfmt();
102
103 /* force UTC for interval calculations */
104 if (iflag || sflag)
105 if (setenv("TZ", "UTC", 1) == -1)
106 err(1, "setenv UTC");
107
108 clock_gettime(clock, &start);
109 clock_gettime(CLOCK_REALTIME, &utc_offset);
110 timespecsub(&utc_offset, &start, &utc_offset);
111
112 for (prev = '\n'; (ch = getchar()) != EOF; prev = ch) {
113 if (prev == '\n') {
114 clock_gettime(clock, &now);
115 if (iflag || sflag)
116 timespecsub(&now, &start, &ts);
117 else if (mflag)
118 timespecadd(&now, &utc_offset, &ts);
119 else
120 ts = now;
121 fmtprint(&ts);
122 if (iflag)
123 start = now;
124 }
125 if (putchar(ch) == EOF)
126 break;
127 }
128
129 if (fclose(stdout))
130 err(1, "stdout");
131 return 0;
132 }
133
134 static void __dead
usage(void)135 usage(void)
136 {
137 fprintf(stderr, "usage: %s [-i | -s] [-m] [format]\n", getprogname());
138 exit(1);
139 }
140
141 /*
142 * yo dawg, i heard you like format strings
143 * so i put format strings in your user supplied input
144 * so you can format while you format
145 */
146 static void
fmtfmt(void)147 fmtfmt(void)
148 {
149 char *f;
150 struct usec *u;
151
152 strlcpy(buf, format, bufsize);
153 f = buf;
154
155 do {
156 while ((f = strchr(f, '%')) != NULL && f[1] == '%')
157 f += 2;
158
159 if (f == NULL)
160 break;
161
162 f++;
163 if (f[0] == '.' &&
164 (f[1] == 'S' || f[1] == 's' || f[1] == 'T')) {
165 size_t l;
166
167 f[0] = f[1];
168 f[1] = '.';
169 f += 2;
170 u = malloc(sizeof *u);
171 if (u == NULL)
172 err(1, NULL);
173 u->pos = f;
174 SIMPLEQ_INSERT_TAIL(&usec_queue, u, next);
175 l = strlen(f);
176 memmove(f + 6, f, l + 1);
177 f += 6;
178 }
179 } while (*f != '\0');
180 }
181
182 static void
fmtprint(const struct timespec * ts)183 fmtprint(const struct timespec *ts)
184 {
185 char us[8];
186 struct tm *tm;
187 struct usec *u;
188
189 if ((tm = localtime(&ts->tv_sec)) == NULL)
190 err(1, "localtime");
191
192 /* Update any microsecond substrings in the format buffer. */
193 if (!SIMPLEQ_EMPTY(&usec_queue)) {
194 snprintf(us, sizeof(us), "%06ld", ts->tv_nsec / 1000);
195 SIMPLEQ_FOREACH(u, &usec_queue, next)
196 memcpy(u->pos, us, 6);
197 }
198
199 *outbuf = '\0';
200 if (*buf != '\0') {
201 while (strftime(outbuf, obsize, buf, tm) == 0) {
202 if ((outbuf = reallocarray(outbuf, 2, obsize)) == NULL)
203 err(1, NULL);
204 obsize *= 2;
205 }
206 }
207 fprintf(stdout, "%s ", outbuf);
208 if (ferror(stdout))
209 exit(1);
210 }
211