diff --git a/configure.ac b/configure.ac index 3977c61d..b4765d25 100644 --- a/configure.ac +++ b/configure.ac @@ -58,7 +58,7 @@ ES_WITH_READLINE dnl Checks for header files. AC_HEADER_DIRENT AC_HEADER_SYS_WAIT -AC_CHECK_HEADERS(fcntl.h limits.h sys/ioctl.h sys/time.h unistd.h memory.h stdarg.h sys/cdefs.h) +AC_CHECK_HEADERS(fcntl.h limits.h sys/ioctl.h sys/time.h unistd.h memory.h stdarg.h sys/cdefs.h inttypes.h) dnl Checks for typedefs, structures, and compiler characteristics. @@ -71,7 +71,7 @@ AC_TYPE_GETGROUPS AC_FUNC_MMAP AC_CHECK_FUNCS(strerror strtol lstat setrlimit sigrelse sighold sigaction \ -sysconf sigsetjmp getrusage mmap mprotect) +sysconf sigsetjmp getrusage gettimeofday mmap mprotect) AC_CACHE_CHECK(whether getenv can be redefined, es_cv_local_getenv, [if test "$ac_cv_header_stdlib_h" = no || test "$ac_cv_header_stdc" = no; then diff --git a/doc/es.1 b/doc/es.1 index 3fb8e790..84ea19cf 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -2756,7 +2756,7 @@ setnoexport setsignals Some primitives are included in .I es conditionally, based on compile-time configuration options. -Those primitives, and the functions to which they are bound, are +Those primitives, and the functions which use them, are .ta 2i .Ds .ft \*(Cf diff --git a/es.h b/es.h index 83ab2216..29698e22 100644 --- a/es.h +++ b/es.h @@ -376,6 +376,13 @@ extern void blocksignals(void); extern void unblocksignals(void); +/* prim-sys.c */ + +#if BUILTIN_TIME +extern void setbasetime(void); +#endif + + /* open.c */ typedef enum { oOpen, oCreate, oAppend, oReadWrite, oReadCreate, oReadAppend } OpenKind; diff --git a/initial.es b/initial.es index a813803f..ec4f49eb 100644 --- a/initial.es +++ b/initial.es @@ -136,7 +136,18 @@ fn-unwind-protect = $&noreturn @ body cleanup { # and get time from /bin or wherever. if {~ <=$&primitives limit} {fn-limit = $&limit} -if {~ <=$&primitives time} {fn-time = $&time} +if {~ <=$&primitives time} { + fn time cmd { + $&collect + let ((str times) = <=$&time) + unwind-protect { + $cmd + } { + (str times) = <={$&time $times} + echo >[1=2] $str^\t^$^cmd + } + } +} # These builtins are mainly useful for internal functions, but # they're there to be called if you want to use them. diff --git a/main.c b/main.c index f8f19ad4..8cf9ebfd 100644 --- a/main.c +++ b/main.c @@ -115,6 +115,9 @@ int main(int argc, char **argv0) { initconv(); initgc(); +#if BUILTIN_TIME + setbasetime(); +#endif if (argc == 0) { argc = 1; diff --git a/prim-sys.c b/prim-sys.c index c0b4f982..58d69142 100644 --- a/prim-sys.c +++ b/prim-sys.c @@ -288,100 +288,136 @@ PRIM(limit) { } #endif /* BSD_LIMITS */ +/* + * time builtin -- this is nearly as bad as limit + */ + #if BUILTIN_TIME -#if HAVE_GETRUSAGE -/* This function is provided as timersub(3) on some systems, but it's simple enough - * to do ourselves. */ -static void timesub(struct timeval *a, struct timeval *b, struct timeval *res) { - res->tv_sec = a->tv_sec - b->tv_sec; - res->tv_usec = a->tv_usec - b->tv_usec; - if (res->tv_usec < 0) { - res->tv_sec -= 1; - res->tv_usec += 1000000; - } +struct times { + intmax_t real_usec; + intmax_t user_usec; + intmax_t sys_usec; +}; + +static Boolean timethrows = TRUE; + +static void tmerrchk(int result, char *str) { + if (result != -1) + return; + if (timethrows) + fail("$&time", "%s: %s", str, esstrerror(errno)); + eprint("%s: %s\n", str, esstrerror(errno)); + eprint("Calls to `$&time` or `time` in this shell may produce bad values.\n"); } + +static void getrealtime(struct times *ret) { +#if HAVE_GETTIMEOFDAY +#define PRECISE_REALTIME 1 + struct timeval tv; + tmerrchk(gettimeofday(&tv, NULL), "getrealtime()"); + ret->real_usec = (tv.tv_sec * INTMAX_C(1000000)) + tv.tv_usec; +#else /* use time(3p) */ +#define PRECISE_REALTIME 0 + time_t t = time(NULL); + tmerrchk(t, "getrealtime()"); + ret->real_usec = t * 1000000; #endif +} -PRIM(time) { +static void getusagetimes(struct times *ret) { #if HAVE_GETRUSAGE + struct rusage ru_self, ru_child; + tmerrchk(getrusage(RUSAGE_SELF, &ru_self), "getrusage(RUSAGE_SELF)"); + tmerrchk(getrusage(RUSAGE_CHILDREN, &ru_child), "getrusage(RUSAGE_CHILDREN)"); + ret->user_usec = (ru_self.ru_utime.tv_sec * 1000000) + + ru_self.ru_utime.tv_usec + + (ru_child.ru_utime.tv_sec * 1000000) + + ru_child.ru_utime.tv_usec; + ret->sys_usec = (ru_self.ru_stime.tv_sec * 1000000) + + ru_self.ru_stime.tv_usec + + (ru_child.ru_stime.tv_sec * 1000000) + + ru_child.ru_stime.tv_usec; +#else + struct tms tms; + static long mul = -1; + if (mul == -1) + mul = 1000000 / sysconf(_SC_CLK_TCK); + tmerrchk(times(&tms), "getusagetimes()"); + ret->user_usec = ((intmax_t)tms.tms_utime + tms.tms_cutime) * mul; + ret->sys_usec = ((intmax_t)tms.tms_stime + tms.tms_cstime) * mul; +#endif +} - int pid, status; - time_t t0, t1; - struct rusage ru_prev, ru_new, ru_diff; - - Ref(List *, lp, list); +static void gettimes(struct times *ret) { + getrealtime(ret); + getusagetimes(ret); +} - getrusage(RUSAGE_CHILDREN, &ru_prev); - gc(); /* do a garbage collection first to ensure reproducible results */ - t0 = time(NULL); - pid = efork(TRUE, FALSE); - if (pid == 0) - esexit(exitstatus(eval(lp, NULL, evalflags | eval_inchild))); - status = ewait(pid, FALSE); - t1 = time(NULL); - SIGCHK(); - printstatus(0, status); +static void parsetimes(List *list, struct times *ret) { + char *suffix; + if (length(list) != 3) + fail("$&time", "usage: $&time [r u s]"); + + ret->real_usec = strtoimax(getstr(list->term), &suffix, 10); + if (*suffix != '\0') + fail("$&time", "real-time argument not an int", list->term); + ret->user_usec = strtoimax(getstr(list->next->term), &suffix, 10); + if (*suffix != '\0') + fail("$&time", "user-time argument not an int", list->next->term); + ret->sys_usec = strtoimax(getstr(list->next->next->term), &suffix, 10); + if (*suffix != '\0') + fail("$&time", "sys-time argument not an int", list->next->next->term); +} - getrusage(RUSAGE_CHILDREN, &ru_new); - timesub(&ru_new.ru_utime, &ru_prev.ru_utime, &ru_diff.ru_utime); - timesub(&ru_new.ru_stime, &ru_prev.ru_stime, &ru_diff.ru_stime); +static void subtimes(struct times a, struct times b, struct times *ret) { + ret->real_usec = a.real_usec - b.real_usec; + ret->user_usec = a.user_usec - b.user_usec; + ret->sys_usec = a.sys_usec - b.sys_usec; +} - eprint( - "%6ldr %5ld.%ldu %5ld.%lds\t%L\n", - t1 - t0, - ru_diff.ru_utime.tv_sec, (long) (ru_diff.ru_utime.tv_usec / 100000), - ru_diff.ru_stime.tv_sec, (long) (ru_diff.ru_stime.tv_usec / 100000), - lp, " " +static char *strtimes(struct times time) { + return str( +#if PRECISE_REALTIME + "%6.3jd" +#else + "%6jd" +#endif + "r %7.3jdu %7.3jds", +#if PRECISE_REALTIME + time.real_usec / 1000, +#else + time.real_usec / 1000000, +#endif + time.user_usec / 1000, + time.sys_usec / 1000 ); +} - RefEnd(lp); - return mklist(mkstr(mkstatus(status)), NULL); +static struct times base; +extern void setbasetime(void) { + timethrows = FALSE; + gettimes(&base); + timethrows = TRUE; +} -#else /* !HAVE_GETRUSAGE */ +PRIM(time) { + struct times prev, time; - int pid, status; - Ref(List *, lp, list); + gettimes(&time); + subtimes(time, base, &time); - gc(); /* do a garbage collection first to ensure reproducible results */ - pid = efork(TRUE, FALSE); - if (pid == 0) { - clock_t t0, t1; - struct tms tms; - static clock_t ticks = 0; - - if (ticks == 0) - ticks = sysconf(_SC_CLK_TCK); - - t0 = times(&tms); - pid = efork(TRUE, FALSE); - if (pid == 0) - esexit(exitstatus(eval(lp, NULL, evalflags | eval_inchild))); - - status = ewaitfor(pid); - t1 = times(&tms); - SIGCHK(); - printstatus(0, status); - - tms.tms_cutime += ticks / 20; - tms.tms_cstime += ticks / 20; - - eprint( - "%6ldr %5ld.%ldu %5ld.%lds\t%L\n", - (t1 - t0 + ticks / 2) / ticks, - tms.tms_cutime / ticks, ((tms.tms_cutime * 10) / ticks) % 10, - tms.tms_cstime / ticks, ((tms.tms_cstime * 10) / ticks) % 10, - lp, " " - ); - esexit(status); + if (list != NULL) { + parsetimes(list, &prev); + subtimes(time, prev, &time); } - status = ewaitfor(pid); - SIGCHK(); - printstatus(0, status); - RefEnd(lp); - return mklist(mkstr(mkstatus(status)), NULL); - -#endif /* !HAVE_GETRUSAGE */ + gcdisable(); + list = mklist(mkstr(strtimes(time)), + mklist(mkstr(str("%jd", time.real_usec)), + mklist(mkstr(str("%jd", time.user_usec)), + mklist(mkstr(str("%jd", time.sys_usec)), NULL)))); + gcenable(); + return list; } #endif /* BUILTIN_TIME */ diff --git a/print.c b/print.c index d46715ac..c186d203 100644 --- a/print.c +++ b/print.c @@ -19,6 +19,7 @@ static Boolean name(Format *format) { \ Flag(uconv, FMT_unsigned) Flag(hconv, FMT_short) Flag(longconv, FMT_long) +Flag(maxconv, FMT_max) Flag(altconv, FMT_altform) Flag(leftconv, FMT_leftside) Flag(dotconv, FMT_f2set) @@ -78,16 +79,19 @@ static void intconv(Format *format, unsigned int radix, int upper, char *altform "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", }; char padchar; - size_t len, pre, zeroes, padding, width; - long n, flags; - unsigned long u; + size_t len, pre, padding, width; + long flags; + intmax_t n; + uintmax_t u; char number[64], prefix[20]; if (radix > 36) return; flags = format->flags; - if (flags & FMT_long) + if (flags & FMT_max) + n = va_arg(format->args, intmax_t); + else if (flags & FMT_long) n = va_arg(format->args, long); else n = va_arg(format->args, int); @@ -105,30 +109,34 @@ static void intconv(Format *format, unsigned int radix, int upper, char *altform prefix[pre++] = *altform++; len = utostr(u, number, radix, table[upper]) - number; - if ((flags & FMT_f2set) && (size_t) format->f2 > len) - zeroes = format->f2 - len; - else - zeroes = 0; + if (flags & FMT_f2set) { + size_t i, figs = format->f2; + if (figs >= len) { + prefix[pre++] = '0'; + prefix[pre++] = '.'; + for (i = figs - len; i > 0; i--) + prefix[pre++] = '0'; + } else { + len++; + for (i = len; i >= len - figs; i--) + number[i] = number[i-1]; + number[i] = '.'; + } + } - width = pre + zeroes + len; + width = pre + len; if ((flags & FMT_f1set) && (size_t) format->f1 > width) { padding = format->f1 - width; } else padding = 0; padchar = ' '; - if (padding > 0 && flags & FMT_zeropad) { + if (flags & FMT_zeropad) padchar = '0'; - if ((flags & FMT_leftside) == 0) { - zeroes += padding; - padding = 0; - } - } if ((flags & FMT_leftside) == 0) pad(format, padding, padchar); fmtappend(format, prefix, pre); - pad(format, zeroes, '0'); fmtappend(format, number, len); if (flags & FMT_leftside) pad(format, padding, padchar); @@ -188,6 +196,7 @@ static void inittab(void) { fmttab['u'] = uconv; fmttab['h'] = hconv; fmttab['l'] = longconv; + fmttab['j'] = maxconv; fmttab['#'] = altconv; fmttab['-'] = leftconv; fmttab['.'] = dotconv; diff --git a/print.h b/print.h index 0171526c..9fcb709e 100644 --- a/print.h +++ b/print.h @@ -23,7 +23,8 @@ enum { FMT_leftside = 16, /* %- */ FMT_altform = 32, /* %# */ FMT_f1set = 64, /* % */ - FMT_f2set = 128 /* %. */ + FMT_f2set = 128, /* %. */ + FMT_max = 256 /* %j */ }; typedef Boolean (*Conv)(Format *); diff --git a/stdenv.h b/stdenv.h index 98bfbf87..32caaa4b 100644 --- a/stdenv.h +++ b/stdenv.h @@ -30,6 +30,19 @@ #include #endif +/* half-heartedly try to handle a lack of or */ +#if HAVE_STDINT_H +#include +#else +#define intmax_t long +#endif + +#if HAVE_INTTYPES_H +#include +#else +#define strtoimax strtol +#endif + #include #include #include