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