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
31SIMPLEQ_HEAD(, usec) usec_queue = SIMPLEQ_HEAD_INITIALIZER(usec_queue);
32struct usec {
33	SIMPLEQ_ENTRY(usec) next;
34	char *pos;
35};
36
37static char		*format = "%b %d %H:%M:%S";
38static char		*buf;
39static char		*outbuf;
40static size_t		 bufsize;
41static size_t		 obsize;
42
43static void		 fmtfmt(void);
44static void		 fmtprint(const struct timespec *);
45static void __dead	 usage(void);
46
47int
48main(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
134static void __dead
135usage(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 */
146static void
147fmtfmt(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
182static void
183fmtprint(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