diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makemodule.am | 8 | ||||
-rw-r--r-- | lib/canonicalize.c | 99 | ||||
-rw-r--r-- | lib/colors.c | 20 | ||||
-rw-r--r-- | lib/cpuset.c | 32 | ||||
-rw-r--r-- | lib/loopdev.c | 41 | ||||
-rw-r--r-- | lib/monotonic.c | 6 | ||||
-rw-r--r-- | lib/path.c | 54 | ||||
-rw-r--r-- | lib/strutils.c | 15 | ||||
-rw-r--r-- | lib/sysfs.c | 14 | ||||
-rw-r--r-- | lib/timer.c | 40 | ||||
-rw-r--r-- | lib/timeutils.c | 27 |
11 files changed, 276 insertions, 80 deletions
diff --git a/lib/Makemodule.am b/lib/Makemodule.am index 13b19f151..862a06c17 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -48,10 +48,12 @@ libcommon_la_SOURCES += lib/cpuset.c endif if HAVE_OPENAT +if HAVE_DIRFD libcommon_la_SOURCES += lib/path.c libcommon_la_SOURCES += lib/procutils.c libcommon_la_SOURCES += lib/sysfs.c endif +endif noinst_LTLIBRARIES += libtcolors.la libtcolors_la_CFLAGS = $(AM_CFLAGS) @@ -96,9 +98,11 @@ check_PROGRAMS += \ endif if HAVE_OPENAT +if HAVE_DIRFD check_PROGRAMS += test_procutils check_PROGRAMS += test_path endif +endif test_ttyutils_SOURCES = lib/ttyutils.c test_ttyutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_TTYUTILS @@ -126,11 +130,10 @@ test_randutils_SOURCES = lib/randutils.c test_randutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_RANDUTILS if HAVE_OPENAT +if HAVE_DIRFD test_procutils_SOURCES = lib/procutils.c test_procutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PROCUTILS -endif -if HAVE_OPENAT test_path_SOURCES = lib/path.c lib/fileutils.c if HAVE_CPU_SET_T test_path_SOURCES += lib/cpuset.c @@ -138,6 +141,7 @@ endif test_path_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PATH test_path_LDADD = $(LDADD) endif +endif if LINUX test_cpuset_SOURCES = lib/cpuset.c diff --git a/lib/canonicalize.c b/lib/canonicalize.c index f3a2a3af2..fe104953d 100644 --- a/lib/canonicalize.c +++ b/lib/canonicalize.c @@ -14,9 +14,11 @@ #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> +#include <sys/wait.h> #include "canonicalize.h" #include "pathnames.h" +#include "all-io.h" /* * Converts private "dm-N" names to "/dev/mapper/<name>" @@ -140,39 +142,92 @@ char *canonicalize_path(const char *path) char *canonicalize_path_restricted(const char *path) { - char *canonical, *dmname; - int errsv; - uid_t euid; - gid_t egid; + char *canonical = NULL; + int errsv = 0; + int pipes[2]; + ssize_t len; + pid_t pid; if (!path || !*path) return NULL; - euid = geteuid(); - egid = getegid(); - - /* drop permissions */ - if (setegid(getgid()) < 0 || seteuid(getuid()) < 0) + if (pipe(pipes) != 0) return NULL; - errsv = errno = 0; - - canonical = realpath(path, NULL); - if (!canonical) - errsv = errno; - else if (is_dm_devname(canonical, &dmname)) { - char *dm = canonicalize_dm_name(dmname); - if (dm) { - free(canonical); - canonical = dm; + /* + * To accurately assume identity of getuid() we must use setuid() + * but if we do that, we lose ability to reassume euid of 0, so + * we fork to do the check to keep euid intact. + */ + pid = fork(); + switch (pid) { + case -1: + close(pipes[0]); + close(pipes[1]); + return NULL; /* fork error */ + case 0: + close(pipes[0]); /* close unused end */ + pipes[0] = -1; + errno = 0; + + /* drop permissions */ + if (setgid(getgid()) < 0 || setuid(getuid()) < 0) + canonical = NULL; /* failed */ + else { + char *dmname = NULL; + + canonical = realpath(path, NULL); + if (canonical && is_dm_devname(canonical, &dmname)) { + char *dm = canonicalize_dm_name(dmname); + if (dm) { + free(canonical); + canonical = dm; + } + } } + + len = canonical ? strlen(canonical) : errno ? -errno : -EINVAL; + + /* send length or errno */ + write_all(pipes[1], (char *) &len, sizeof(len)); + if (canonical) + write_all(pipes[1], canonical, len); + exit(0); + default: + break; } - /* restore */ - if (setegid(egid) < 0 || seteuid(euid) < 0) { + close(pipes[1]); /* close unused end */ + pipes[1] = -1; + + /* read size or -errno */ + if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len)) + goto done; + if (len < 0) { + errsv = -len; + goto done; + } + + canonical = malloc(len + 1); + if (!canonical) { + errsv = ENOMEM; + goto done; + } + /* read path */ + if (read_all(pipes[0], canonical, len) != len) { + errsv = errno; + goto done; + } + canonical[len] = '\0'; +done: + if (errsv) { free(canonical); - return NULL; + canonical = NULL; } + close(pipes[0]); + + /* We make a best effort to reap child */ + waitpid(pid, NULL, 0); errno = errsv; return canonical; diff --git a/lib/colors.c b/lib/colors.c index f636ecc4f..bea7bd1d7 100644 --- a/lib/colors.c +++ b/lib/colors.c @@ -653,9 +653,6 @@ static int colors_terminal_is_ready(void) { int ncolors = -1; - if (isatty(STDOUT_FILENO) != 1) - goto none; - #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) { int ret; @@ -692,11 +689,16 @@ int colors_init(int mode, const char *name) struct ul_color_ctl *cc = &ul_colors; cc->utilname = name; - cc->mode = mode; termcolors_init_debug(); - if (mode == UL_COLORMODE_UNDEF && (ready = colors_terminal_is_ready())) { + if (mode != UL_COLORMODE_ALWAYS && !isatty(STDOUT_FILENO)) + cc->mode = UL_COLORMODE_NEVER; + else + cc->mode = mode; + + if (cc->mode == UL_COLORMODE_UNDEF + && (ready = colors_terminal_is_ready())) { int rc = colors_read_configuration(cc); if (rc) cc->mode = UL_COLORMODE_DEFAULT; @@ -755,6 +757,14 @@ int colors_wanted(void) } /* + * Returns mode + */ +int colors_mode(void) +{ + return ul_colors.mode; +} + +/* * Enable @seq color */ void color_fenable(const char *seq, FILE *f) diff --git a/lib/cpuset.c b/lib/cpuset.c index 011b6882b..2847db853 100644 --- a/lib/cpuset.c +++ b/lib/cpuset.c @@ -260,6 +260,19 @@ int cpumask_parse(const char *str, cpu_set_t *set, size_t setsize) return 0; } +static int nextnumber(const char *str, char **end, unsigned int *result) +{ + errno = 0; + if (str == NULL || *str == '\0' || !isdigit(*str)) + return -EINVAL; + *result = (unsigned int) strtoul(str, end, 10); + if (errno) + return -errno; + if (str == *end) + return -EINVAL; + return 0; +} + /* * Parses string with list of CPU ranges. * Returns 0 on success. @@ -272,7 +285,7 @@ int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail) { size_t max = cpuset_nbits(setsize); const char *p, *q; - int r = 0; + char *end = NULL; q = str; CPU_ZERO_S(setsize, set); @@ -282,21 +295,24 @@ int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail) unsigned int b; /* end of range */ unsigned int s; /* stride */ const char *c1, *c2; - char c; - if ((r = sscanf(p, "%u%c", &a, &c)) < 1) + if (nextnumber(p, &end, &a) != 0) return 1; b = a; s = 1; + p = end; c1 = nexttoken(p, '-'); c2 = nexttoken(p, ','); + if (c1 != NULL && (c2 == NULL || c1 < c2)) { - if ((r = sscanf(c1, "%u%c", &b, &c)) < 1) + if (nextnumber(c1, &end, &b) != 0) return 1; - c1 = nexttoken(c1, ':'); + + c1 = end && *end ? nexttoken(end, ':') : NULL; + if (c1 != NULL && (c2 == NULL || c1 < c2)) { - if ((r = sscanf(c1, "%u%c", &s, &c)) < 1) + if (nextnumber(c1, &end, &s) != 0) return 1; if (s == 0) return 1; @@ -313,7 +329,7 @@ int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail) } } - if (r == 2) + if (end && *end) return 1; return 0; } @@ -390,7 +406,7 @@ int main(int argc, char *argv[]) usage_err: fprintf(stderr, - "usage: %s [--ncpus <num>] --mask <mask> | --range <list>", + "usage: %s [--ncpus <num>] --mask <mask> | --range <list>\n", program_invocation_short_name); exit(EXIT_FAILURE); } diff --git a/lib/loopdev.c b/lib/loopdev.c index de0d50ba2..5d2e95b7e 100644 --- a/lib/loopdev.c +++ b/lib/loopdev.c @@ -96,6 +96,7 @@ int loopcxt_set_device(struct loopdev_cxt *lc, const char *device) } lc->fd = -1; lc->mode = 0; + lc->blocksize = 0; lc->has_info = 0; lc->info_failed = 0; *lc->device = '\0'; @@ -127,7 +128,7 @@ int loopcxt_set_device(struct loopdev_cxt *lc, const char *device) return 0; } -int loopcxt_has_device(struct loopdev_cxt *lc) +inline int loopcxt_has_device(struct loopdev_cxt *lc) { return lc && *lc->device; } @@ -362,7 +363,7 @@ int loopcxt_deinit_iterator(struct loopdev_cxt *lc) /* * Same as loopcxt_set_device, but also checks if the device is - * associeted with any file. + * associated with any file. * * Returns: <0 on error, 0 on success, 1 device does not match with * LOOPITER_FL_{USED,FREE} flags. @@ -691,7 +692,7 @@ char *loopcxt_get_backing_file(struct loopdev_cxt *lc) if (sysfs) /* - * This is always preffered, the loop_info64 + * This is always preferred, the loop_info64 * has too small buffer for the filename. */ ul_path_read_string(sysfs, &res, "loop/backing_file"); @@ -1107,6 +1108,22 @@ int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit) } /* + * The blocksize will be used by loopcxt_set_device(). For already exiting + * devices use loopcxt_ioctl_blocksize(). + * + * The setting is removed by loopcxt_set_device() loopcxt_next()! + */ +int loopcxt_set_blocksize(struct loopdev_cxt *lc, uint64_t blocksize) +{ + if (!lc) + return -EINVAL; + lc->blocksize = blocksize; + + DBG(CXT, ul_debugobj(lc, "set blocksize=%jd", blocksize)); + return 0; +} + +/* * @lc: context * @flags: kernel LO_FLAGS_{READ_ONLY,USE_AOPS,AUTOCLEAR} flags * @@ -1216,7 +1233,7 @@ static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd) "size mismatch (%ju/%ju)", size, expected_size)); - if (loopcxt_set_capacity(lc)) { + if (loopcxt_ioctl_capacity(lc)) { /* ioctl not available */ if (errno == ENOTTY || errno == EINVAL) errno = ERANGE; @@ -1331,6 +1348,12 @@ int loopcxt_setup_device(struct loopdev_cxt *lc) DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD: OK")); + if (lc->blocksize > 0 + && (rc = loopcxt_ioctl_blocksize(lc, lc->blocksize)) < 0) { + errsv = -rc; + goto err; + } + if (ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info)) { rc = -errno; errsv = errno; @@ -1374,7 +1397,7 @@ err: * * Returns: <0 on error, 0 on success. */ -int loopcxt_set_status(struct loopdev_cxt *lc) +int loopcxt_ioctl_status(struct loopdev_cxt *lc) { int dev_fd, rc = -1; @@ -1397,7 +1420,7 @@ int loopcxt_set_status(struct loopdev_cxt *lc) return 0; } -int loopcxt_set_capacity(struct loopdev_cxt *lc) +int loopcxt_ioctl_capacity(struct loopdev_cxt *lc) { int fd = loopcxt_get_fd(lc); @@ -1415,7 +1438,7 @@ int loopcxt_set_capacity(struct loopdev_cxt *lc) return 0; } -int loopcxt_set_dio(struct loopdev_cxt *lc, unsigned long use_dio) +int loopcxt_ioctl_dio(struct loopdev_cxt *lc, unsigned long use_dio) { int fd = loopcxt_get_fd(lc); @@ -1437,7 +1460,7 @@ int loopcxt_set_dio(struct loopdev_cxt *lc, unsigned long use_dio) * Kernel uses "unsigned long" as ioctl arg, but we use u64 for all sizes to * keep loopdev internal API simple. */ -int loopcxt_set_blocksize(struct loopdev_cxt *lc, uint64_t blocksize) +int loopcxt_ioctl_blocksize(struct loopdev_cxt *lc, uint64_t blocksize) { int fd = loopcxt_get_fd(lc); @@ -1753,7 +1776,7 @@ char *loopdev_find_by_backing_file(const char *filename, uint64_t offset, uint64 /* * Returns number of loop devices associated with @file, if only one loop - * device is associeted with the given @filename and @loopdev is not NULL then + * device is associated with the given @filename and @loopdev is not NULL then * @loopdev returns name of the device. */ int loopdev_count_by_backing_file(const char *filename, char **loopdev) diff --git a/lib/monotonic.c b/lib/monotonic.c index bb58e15d7..96ead1ee0 100644 --- a/lib/monotonic.c +++ b/lib/monotonic.c @@ -52,12 +52,8 @@ int gettime_monotonic(struct timeval *tv) int ret; struct timespec ts; -# ifdef CLOCK_MONOTONIC_RAW /* Linux specific, can't slew */ - if (!(ret = clock_gettime(CLOCK_MONOTONIC_RAW, &ts))) { -# else - if (!(ret = clock_gettime(CLOCK_MONOTONIC, &ts))) { -# endif + if (!(ret = clock_gettime(UL_CLOCK_MONOTONIC, &ts))) { tv->tv_sec = ts.tv_sec; tv->tv_usec = ts.tv_nsec / 1000; } diff --git a/lib/path.c b/lib/path.c index 9cc2e3e2e..a9c47f2a1 100644 --- a/lib/path.c +++ b/lib/path.c @@ -94,8 +94,7 @@ void ul_unref_path(struct path_cxt *pc) DBG(CXT, ul_debugobj(pc, "dealloc")); if (pc->dialect) pc->free_dialect(pc); - if (pc->dir_fd >= 0) - close(pc->dir_fd); + ul_path_close_dirfd(pc); free(pc->dir_path); free(pc->prefix); free(pc); @@ -212,6 +211,23 @@ int ul_path_get_dirfd(struct path_cxt *pc) return pc->dir_fd; } +/* Note that next ul_path_get_dirfd() will reopen the directory */ +void ul_path_close_dirfd(struct path_cxt *pc) +{ + assert(pc); + + if (pc->dir_fd >= 0) { + DBG(CXT, ul_debugobj(pc, "closing dir: '%s'", pc->dir_path)); + close(pc->dir_fd); + pc->dir_fd = -1; + } +} + +int ul_path_isopen_dirfd(struct path_cxt *pc) +{ + return pc && pc->dir_fd >= 0; +} + static const char *ul_path_mkpath(struct path_cxt *pc, const char *path, va_list ap) { int rc = vsnprintf(pc->path_buffer, sizeof(pc->path_buffer), path, ap); @@ -301,7 +317,7 @@ int ul_path_open(struct path_cxt *pc, int flags, const char *path) if (!pc) { fd = open(path, flags); - DBG(CXT, ul_debug("opening '%s'", path)); + DBG(CXT, ul_debug("opening '%s' [no context]", path)); } else { int fdx; int dir = ul_path_get_dirfd(pc); @@ -567,9 +583,9 @@ int ul_path_read_string(struct path_cxt *pc, char **str, const char *path) rc = ul_path_read(pc, buf, sizeof(buf) - 1, path); if (rc < 0 || !str) - return rc;; + return rc; - /* Remove tailing newline (usuall in sysfs) */ + /* Remove tailing newline (usual in sysfs) */ if (rc > 0 && *(buf + rc - 1) == '\n') --rc; @@ -600,9 +616,9 @@ int ul_path_read_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char { int rc = ul_path_read(pc, buf, bufsz - 1, path); if (rc < 0) - return rc;; + return rc; - /* Remove tailing newline (usuall in sysfs) */ + /* Remove tailing newline (usual in sysfs) */ if (rc > 0 && *(buf + rc - 1) == '\n') --rc; @@ -687,7 +703,7 @@ int ul_path_readf_s64(struct path_cxt *pc, int64_t *res, const char *path, ...) va_end(ap); if (!p) - return -EINVAL;; + return -EINVAL; return ul_path_read_s64(pc, res, p); } @@ -834,6 +850,28 @@ int ul_path_writef_string(struct path_cxt *pc, const char *str, const char *path return ul_path_write_string(pc, str, p); } +int ul_path_write_s64(struct path_cxt *pc, int64_t num, const char *path) +{ + char buf[sizeof(stringify_value(LLONG_MAX))]; + int rc, errsv; + int fd, len; + + fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path); + if (fd < 0) + return -errno; + + len = snprintf(buf, sizeof(buf), "%" PRId64, num); + if (len < 0 || (size_t) len >= sizeof(buf)) + rc = len < 0 ? -errno : -E2BIG; + else + rc = write_all(fd, buf, len); + + errsv = errno; + close(fd); + errno = errsv; + return rc; +} + int ul_path_write_u64(struct path_cxt *pc, uint64_t num, const char *path) { char buf[sizeof(stringify_value(ULLONG_MAX))]; diff --git a/lib/strutils.c b/lib/strutils.c index b71dde596..369d50159 100644 --- a/lib/strutils.c +++ b/lib/strutils.c @@ -551,6 +551,7 @@ char *size_to_human_string(int options, uint64_t bytes) if (options & SIZE_SUFFIX_SPACE) *psuf++ = ' '; + exp = get_exp(bytes); c = *(letters + (exp ? exp / 10 : 0)); dec = exp ? bytes / (1ULL << exp) : bytes; @@ -569,11 +570,17 @@ char *size_to_human_string(int options, uint64_t bytes) * exp, suffix[0], dec, frac); */ + /* round */ if (frac) { - /* round */ - frac = (frac / (1ULL << (exp - 10)) + 50) / 100; - if (frac == 10) - dec++, frac = 0; + if (options & SIZE_DECIMAL_2DIGITS) { + frac = (frac / (1ULL << (exp - 10)) + 5) / 10; + if (frac % 10 == 0) + frac /= 10; /* convert N.90 to N.9 */ + } else { + frac = (frac / (1ULL << (exp - 10)) + 50) / 100; + if (frac == 10) + dec++, frac = 0; + } } if (frac) { diff --git a/lib/sysfs.c b/lib/sysfs.c index 626451bdd..6916c2584 100644 --- a/lib/sysfs.c +++ b/lib/sysfs.c @@ -999,6 +999,20 @@ char *sysfs_devno_to_devname(dev_t devno, char *buf, size_t bufsiz) return res; } +int sysfs_devno_count_partitions(dev_t devno) +{ + struct path_cxt *pc = ul_new_sysfs_path(devno, NULL, NULL); + int n = 0; + + if (pc) { + char buf[PATH_MAX + 1]; + char *name = sysfs_blkdev_get_name(pc, buf, sizeof(buf)); + + n = sysfs_blkdev_count_partitions(pc, name); + ul_unref_path(pc); + } + return n; +} #ifdef TEST_PROGRAM_SYSFS diff --git a/lib/timer.c b/lib/timer.c index 210c726cb..c1ea54eb8 100644 --- a/lib/timer.c +++ b/lib/timer.c @@ -27,7 +27,9 @@ * The applications need to ensure that they can tolerate multiple signal * deliveries. */ -int setup_timer(timer_t * t_id, struct itimerval *timeout, +#ifdef HAVE_TIMER_CREATE +int setup_timer(struct ul_timer *timer, + struct itimerval *timeout, void (*timeout_handler)(int, siginfo_t *, void *)) { time_t sec = timeout->it_value.tv_sec; @@ -52,14 +54,42 @@ int setup_timer(timer_t * t_id, struct itimerval *timeout, if (sigaction(SIGALRM, &sig_a, NULL)) return 1; - if (timer_create(CLOCK_MONOTONIC, &sig_e, t_id)) + if (timer_create(CLOCK_MONOTONIC, &sig_e, &timer->t_id)) return 1; - if (timer_settime(*t_id, 0, &val, NULL)) + if (timer_settime(timer->t_id, 0, &val, NULL)) return 1; return 0; } +void cancel_timer(struct ul_timer *timer) +{ + timer_delete(timer->t_id); +} + +#else /* !HAVE_TIMER_CREATE */ + +int setup_timer(struct ul_timer *timer, + struct itimerval *timeout, + void (*timeout_handler)(int, siginfo_t *, void *)) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + memset(timer, 0, sizeof(*timer)); -void cancel_timer(timer_t *t_id) + sa.sa_flags = SA_SIGINFO | SA_RESETHAND; + sa.sa_sigaction = timeout_handler; + + if (sigaction(SIGALRM, &sa, &timer->old_sa)) + return 1; + if (setitimer(ITIMER_REAL, timeout, &timer->old_timer) != 0) + return 1; + return 0; +} + +void cancel_timer(struct ul_timer *timer) { - timer_delete(*t_id); + setitimer(ITIMER_REAL, &timer->old_timer, NULL); + sigaction(SIGALRM, &timer->old_sa, NULL); + } +#endif /* !HAVE_TIMER_CREATE */ diff --git a/lib/timeutils.c b/lib/timeutils.c index 9c286aebc..d403ced90 100644 --- a/lib/timeutils.c +++ b/lib/timeutils.c @@ -503,34 +503,37 @@ int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz) } /* relative time functions */ -int time_is_today(const time_t *t, struct timeval *now) +static inline int time_is_thisyear(struct tm const *const tm, + struct tm const *const tmnow) { - if (now->tv_sec == 0) - gettimeofday(now, NULL); - return *t / (3600 * 24) == now->tv_sec / (3600 * 24); + return tm->tm_year == tmnow->tm_year; } -int time_is_thisyear(const time_t *t, struct timeval *now) +static inline int time_is_today(struct tm const *const tm, + struct tm const *const tmnow) { - if (now->tv_sec == 0) - gettimeofday(now, NULL); - return *t / (3600 * 24 * 365) == now->tv_sec / (3600 * 24 * 365); + return (tm->tm_yday == tmnow->tm_yday && + time_is_thisyear(tm, tmnow)); } int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz) { - struct tm tm; + struct tm tm, tmnow; int rc = 0; - localtime_r(t, &tm); + if (now->tv_sec == 0) + gettimeofday(now, NULL); + + localtime_r(t, &tm); + localtime_r(&now->tv_sec, &tmnow); - if (time_is_today(t, now)) { + if (time_is_today(&tm, &tmnow)) { rc = snprintf(buf, bufsz, "%02d:%02d", tm.tm_hour, tm.tm_min); if (rc < 0 || (size_t) rc > bufsz) return -1; rc = 1; - } else if (time_is_thisyear(t, now)) { + } else if (time_is_thisyear(&tm, &tmnow)) { if (flags & UL_SHORTTIME_THISYEAR_HHMM) rc = strftime(buf, bufsz, "%b%d/%H:%M", &tm); else |