From 81fec145b9975521418e33a45812437a4b409f8b Mon Sep 17 00:00:00 2001 From: Cameron Katri Date: Wed, 31 Mar 2021 08:44:31 -0400 Subject: New upstream version 9.99.81 --- .gitignore | 2 + Makefile | 31 ++++ include/progressbar.h | 91 +++++++++++ include/tzfile.h | 174 +++++++++++++++++++++ progress.1 | 147 +++++++++++++++++ progress.c | 291 ++++++++++++++++++++++++++++++++++ progressbar.c | 424 ++++++++++++++++++++++++++++++++++++++++++++++++++ strsuftoll.c | 237 ++++++++++++++++++++++++++++ 8 files changed, 1397 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 include/progressbar.h create mode 100644 include/tzfile.h create mode 100644 progress.1 create mode 100644 progress.c create mode 100644 progressbar.c create mode 100644 strsuftoll.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..769578e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +progress diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cc9c47d --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +# $NetBSD: Makefile,v 1.2 2003/01/22 02:56:30 lukem Exp $ + +PROG= progress +SRCS= progress.c progressbar.c strsuftoll.c + + +CPPFLAGS +=-Iinclude -DSTANDALONE_PROGRESS +UNAME ?= $(shell uname) +PREFIX ?= /usr +PROG_PREFIX ?= bsd- + +ifeq ($(UNAME), Linux) +ifeq ($(shell pkg-config libbsd-overlay && echo 1),1) + CPPFLAGS+=$(shell pkg-config libbsd-overlay --cflags) + LDFLAGS+=$(shell pkg-config libbsd-overlay --libs) +endif # ($(shell pkg-config libbsd-overlay && echo 1),1) +endif # ($(UNAME), Linux) + +all: $(PROG) + +$(PROG): $(SRCS:%.c=%.o) + $(CC) $(LDFLAGS) -o $@ $^ + +install: $(PROG) progress.1 + install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG_PREFIX)$(PROG) + install -Dm644 progress.1 $(DESTDIR)$(PREFIX)/share/man/man1/$(PROG_PREFIX)progress.1 + +clean: + rm -f *.o $(PROG) + +.PHONY: all install clean diff --git a/include/progressbar.h b/include/progressbar.h new file mode 100644 index 0000000..daf382b --- /dev/null +++ b/include/progressbar.h @@ -0,0 +1,91 @@ +/* $NetBSD: progressbar.h,v 1.9 2021/01/06 04:43:14 lukem Exp $ */ + +/*- + * Copyright (c) 1996-2021 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Luke Mewburn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STANDALONE_PROGRESS +#include +#endif /* !STANDALONE_PROGRESS */ + +#ifndef GLOBAL +#define GLOBAL extern +#endif + + +#define STALLTIME 5 /* # of seconds of no xfer before "stalling" */ + +typedef void (*sigfunc)(int); + + +GLOBAL FILE *ttyout; /* stdout, or stderr if retrieving to stdout */ + +GLOBAL int progress; /* display transfer progress bar */ +GLOBAL int ttywidth; /* width of tty */ + +GLOBAL off_t bytes; /* current # of bytes read */ +GLOBAL off_t filesize; /* size of file being transferred */ +GLOBAL off_t restart_point; /* offset to restart transfer */ +GLOBAL char *prefix; /* Text written left of progress bar */ + + +#ifndef STANDALONE_PROGRESS +GLOBAL int fromatty; /* input is from a terminal */ +GLOBAL int verbose; /* print messages coming back from server */ +GLOBAL int quit_time; /* maximum time to wait if stalled */ + +GLOBAL const char *direction; /* direction transfer is occurring */ + +GLOBAL sigjmp_buf toplevel; /* non-local goto stuff for cmd scanner */ +#endif /* !STANDALONE_PROGRESS */ + +int foregroundproc(void); +void alarmtimer(int); +void progressmeter(int); +sigfunc xsignal(int, sigfunc); + +#ifndef STANDALONE_PROGRESS +void psummary(int); +void ptransfer(int); +#endif /* !STANDALONE_PROGRESS */ + +#ifdef NO_LONG_LONG +# define LLF "%ld" +# define LLFP(x) "%" x "ld" +# define LLT long +# define ULLF "%lu" +# define ULLFP(x) "%" x "lu" +# define ULLT unsigned long +#else +# define LLF "%lld" +# define LLFP(x) "%" x "lld" +# define LLT long long +# define ULLF "%llu" +# define ULLFP(x) "%" x "llu" +# define ULLT unsigned long long +#endif diff --git a/include/tzfile.h b/include/tzfile.h new file mode 100644 index 0000000..5c7a222 --- /dev/null +++ b/include/tzfile.h @@ -0,0 +1,174 @@ +/* $NetBSD: tzfile.h,v 1.10 2019/07/03 15:49:21 christos Exp $ */ + +#ifndef _TZFILE_H_ +#define _TZFILE_H_ + +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +*/ + +/* +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +** Thank you! +*/ + +/* +** Information about time zone files. +*/ + +#ifndef TZDIR /* Time zone object file directory */ +#define TZDIR "/usr/share/zoneinfo" +#endif /* !defined TZDIR */ + +#ifndef TZDEFAULT +#define TZDEFAULT "/etc/localtime" +#endif /* !defined TZDEFAULT */ + +#ifndef TZDEFRULES +#define TZDEFRULES "posixrules" +#endif /* !defined TZDEFRULES */ + + +/* See Internet RFC 8536 for more details about the following format. */ + +/* +** Each file begins with. . . +*/ + +#define TZ_MAGIC "TZif" + +struct tzhead { + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_version[1]; /* '\0' or '2' or '3' as of 2013 */ + char tzh_reserved[15]; /* reserved; must be zero */ + char tzh_ttisutcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +}; + +/* +** . . .followed by. . . +** +** tzh_timecnt (char [4])s coded transition times a la time(2) +** tzh_timecnt (unsigned char)s types of local time starting at above +** tzh_typecnt repetitions of +** one (char [4]) coded UT offset in seconds +** one (unsigned char) used to set tm_isdst +** one (unsigned char) that's an abbreviation list index +** tzh_charcnt (char)s '\0'-terminated zone abbreviations +** tzh_leapcnt repetitions of +** one (char [4]) coded leap second transition times +** one (char [4]) total correction after above +** tzh_ttisstdcnt (char)s indexed by type; if 1, transition +** time is standard time, if 0, +** transition time is local (wall clock) +** time; if absent, transition times are +** assumed to be local time +** tzh_ttisutcnt (char)s indexed by type; if 1, transition +** time is UT, if 0, transition time is +** local time; if absent, transition +** times are assumed to be local time. +** When this is 1, the corresponding +** std/wall indicator must also be 1. +*/ + +/* +** If tzh_version is '2' or greater, the above is followed by a second instance +** of tzhead and a second instance of the data in which each coded transition +** time uses 8 rather than 4 chars, +** then a POSIX-TZ-environment-variable-style string for use in handling +** instants after the last transition time stored in the file +** (with nothing between the newlines if there is no POSIX representation for +** such instants). +** +** If tz_version is '3' or greater, the above is extended as follows. +** First, the POSIX TZ string's hour offset may range from -167 +** through 167 as compared to the POSIX-required 0 through 24. +** Second, its DST start time may be January 1 at 00:00 and its stop +** time December 31 at 24:00 plus the difference between DST and +** standard time, indicating DST all year. +*/ + +/* +** In the current implementation, "tzset()" refuses to deal with files that +** exceed any of the limits below. +*/ + +#ifndef TZ_MAX_TIMES +#define TZ_MAX_TIMES 2000 +#endif /* !defined TZ_MAX_TIMES */ + +#ifndef TZ_MAX_TYPES +/* This must be at least 17 for Europe/Samara and Europe/Vilnius. */ +#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +#endif /* !defined TZ_MAX_TYPES */ + +#ifndef TZ_MAX_CHARS +#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ + /* (limited by what unsigned chars can hold) */ +#endif /* !defined TZ_MAX_CHARS */ + +#ifndef TZ_MAX_LEAPS +#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ +#endif /* !defined TZ_MAX_LEAPS */ + +#define SECSPERMIN 60 +#define MINSPERHOUR 60 +#define HOURSPERDAY 24 +#define DAYSPERWEEK 7 +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) +#define MONSPERYEAR 12 + +#define TM_SUNDAY 0 +#define TM_MONDAY 1 +#define TM_TUESDAY 2 +#define TM_WEDNESDAY 3 +#define TM_THURSDAY 4 +#define TM_FRIDAY 5 +#define TM_SATURDAY 6 + +#define TM_JANUARY 0 +#define TM_FEBRUARY 1 +#define TM_MARCH 2 +#define TM_APRIL 3 +#define TM_MAY 4 +#define TM_JUNE 5 +#define TM_JULY 6 +#define TM_AUGUST 7 +#define TM_SEPTEMBER 8 +#define TM_OCTOBER 9 +#define TM_NOVEMBER 10 +#define TM_DECEMBER 11 + +#define TM_YEAR_BASE 1900 + +#define EPOCH_YEAR 1970 +#define EPOCH_WDAY TM_THURSDAY + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +/* +** Since everything in isleap is modulo 400 (or a factor of 400), we know that +** isleap(y) == isleap(y % 400) +** and so +** isleap(a + b) == isleap((a + b) % 400) +** or +** isleap(a + b) == isleap(a % 400 + b % 400) +** This is true even if % means modulo rather than Fortran remainder +** (which is allowed by C89 but not C99). +** We use this to avoid addition overflow problems. +*/ + +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) + +#endif /* !defined _TZFILE_H_ */ diff --git a/progress.1 b/progress.1 new file mode 100644 index 0000000..028bda9 --- /dev/null +++ b/progress.1 @@ -0,0 +1,147 @@ +.\" $NetBSD: progress.1,v 1.15 2014/04/13 01:45:34 snj Exp $ +.\" +.\" Copyright (c) 2003-2007 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by John Hawkinson. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd June 6, 2007 +.Dt PROGRESS 1 +.Os +.Sh NAME +.Nm progress +.Nd feed input to a command, displaying a progress bar +.Sh SYNOPSIS +.Nm +.Op Fl ez +.Op Fl b Ar buffersize +.Op Fl f Ar file +.Op Fl l Ar length +.Op Fl p Ar prefix +.Ar cmd +.Op Ar args ... +.Sh DESCRIPTION +The +.Nm +utility opens a pipe to +.Ar cmd +and feeds an input stream into it, while displaying a progress bar to +standard output. +If no filename is specified, +.Nm +reads from standard input. +Where feasible, +.Nm +.Xr fstat 2 Ns s +the input to determine the length, so a time estimate can be calculated. +.Pp +If no length is specified or determined, +.Nm +simply displays a count of the data and the data rate. +.Pp +The options are as follows: +.Bl -tag -width XlXlengthXX +.It Fl b Ar buffersize +Read in buffers of the specified size (default 64k). +An optional suffix (per +.Xr strsuftoll 3 ) +may be given. +.It Fl e +Display progress to standard error instead of standard output. +.It Fl f Ar file +Read from the specified +.Ar file +instead of standard input. +.It Fl l Ar length +Use the specified length for the time estimate, rather than attempting to +.Xr fstat 2 +the input. +An optional suffix (per +.Xr strsuftoll 3 ) +may be given. +.It Fl p Ar prefix +Print the given +.Dq prefix +text before (left of) the progress bar. +.It Fl z +Filter the input through +.Xr gunzip 1 . +If +.Fl f +is specified, calculate the length using +.Ic gzip -l . +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +The command +.Dl progress -zf file.tar.gz tar xf - +will extract the +.Pa file.tar.gz +displaying the progress bar as time passes: +.Bd -literal + 0% | | 0 0.00 KiB/s --:-- ETA + 40% |******** | 273 KiB 271.95 KiB/s 00:01 ETA + 81% |*********************** | 553 KiB 274.61 KiB/s 00:00 ETA +100% |*******************************| 680 KiB 264.59 KiB/s 00:00 ETA +.Ed +.Pp +If it is preferred to monitor the progress of the decompression +process (unlikely), then +.Dl progress -f file.tar.gz tar zxf - +could be used. +.Pp +The command +.Dl dd if=/dev/rwd0d ibs=64k | \e +.Dl progress -l 120g dd of=/dev/rwd1d obs=64k +will copy the 120 GiB disk +.Sy wd0 +.Pa ( /dev/rwd0d ) +to +.Sy wd1 +.Pa ( /dev/rwd1d ) , +displaying a progress bar during the operation. +.Sh SEE ALSO +.Xr ftp 1 , +.Xr strsuftoll 3 +.Sh HISTORY +.Nm +first appeared in +.Nx 1.6.1 . +The dynamic progress bar display code is part of +.Xr ftp 1 . +.Sh AUTHORS +.Nm +was written by +.An John Hawkinson +.Aq jhawk@NetBSD.org . +.Xr ftp 1 Ns 's +dynamic progress bar was written by Luke Mewburn. +.Sh BUGS +Since the progress bar is displayed asynchronously, it may be +difficult to read some error messages, both those produced by the +pipeline, as well as those produced by +.Nm +itself. diff --git a/progress.c b/progress.c new file mode 100644 index 0000000..a921ac9 --- /dev/null +++ b/progress.c @@ -0,0 +1,291 @@ +/* $NetBSD: progress.c,v 1.23 2021/01/07 12:02:52 lukem Exp $ */ + +/*- + * Copyright (c) 2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by John Hawkinson. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define _GNU_SOURCE + +#include +#ifndef lint +__RCSID("$NetBSD: progress.c,v 1.23 2021/01/07 12:02:52 lukem Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* LONGLONG */ +long long strsuftoll(const char *, const char *, long long, long long); + /* LONGLONG */ +long long strsuftollx(const char *, const char *, long long, long long, + char *, size_t); + +#define GLOBAL /* force GLOBAL decls in progressbar.h to be + * declared */ +#include "progressbar.h" + +static void broken_pipe(int unused); +static void usage(void); + +static void +broken_pipe(int unused) +{ + signal(SIGPIPE, SIG_DFL); + progressmeter(1); + kill(getpid(), SIGPIPE); +} + +static void +usage(void) +{ + fprintf(stderr, + "usage: %s [-ez] [-b buffersize] [-f file] [-l length]\n" + " %*.s [-p prefix] cmd [args...]\n", + getprogname(), (int) strlen(getprogname()), ""); + exit(EXIT_FAILURE); +} + +int +main(int argc, char *argv[]) +{ + char *fb_buf; + char *infile = NULL; + pid_t pid = 0, gzippid = 0, deadpid; + int ch, fd, outpipe[2]; + int ws, gzipstat, cmdstat; + int eflag = 0, lflag = 0, zflag = 0; + ssize_t nr, nw, off; + size_t buffersize; + struct stat statb; + struct winsize wins; + + setprogname(argv[0]); + + /* defaults: Read from stdin, 0 filesize (no completion estimate) */ + fd = STDIN_FILENO; + filesize = 0; + buffersize = 64 * 1024; + prefix = NULL; + + while ((ch = getopt(argc, argv, "b:ef:l:p:z")) != -1) + switch (ch) { + case 'b': + buffersize = (size_t) strsuftoll("buffer size", optarg, + 0, SSIZE_MAX); + break; + case 'e': + eflag++; + break; + case 'f': + infile = optarg; + break; + case 'l': + lflag++; + filesize = strsuftoll("input size", optarg, 0, + LLONG_MAX); + break; + case 'p': + prefix = optarg; + break; + case 'z': + zflag++; + break; + case '?': + default: + usage(); + /* NOTREACHED */ + } + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + if (infile && (fd = open(infile, O_RDONLY, 0)) < 0) + err(1, "%s", infile); + + /* stat() to get the filesize unless overridden, or -z */ + if (!zflag && !lflag && (fstat(fd, &statb) == 0)) { + if (S_ISFIFO(statb.st_mode)) { + /* stat(2) on pipe may return only the + * first few bytes with more coming. + * Don't trust! + */ + } else { + filesize = statb.st_size; + } + } + + /* gzip -l the file if we have the name and -z is given */ + if (zflag && !lflag && infile != NULL) { + FILE *gzipsizepipe; + char buf[256], *cp, *cmd; + + /* + * Read second word of last line of gzip -l output. Looks like: + * % gzip -l ../etc.tgz + * compressed uncompressed ratio uncompressed_name + * 119737 696320 82.8% ../etc.tar + */ + + asprintf(&cmd, "gzip -l %s", infile); + if ((gzipsizepipe = popen(cmd, "r")) == NULL) + err(1, "reading compressed file length"); + for (; fgets(buf, 256, gzipsizepipe) != NULL;) + continue; + strtoimax(buf, &cp, 10); + filesize = strtoimax(cp, NULL, 10); + if (pclose(gzipsizepipe) < 0) + err(1, "closing compressed file length pipe"); + free(cmd); + } + /* Pipe input through gzip -dc if -z is given */ + if (zflag) { + int gzippipe[2]; + + if (pipe(gzippipe) < 0) + err(1, "gzip pipe"); + gzippid = fork(); + if (gzippid < 0) + err(1, "fork for gzip"); + + if (gzippid) { + /* parent */ + dup2(gzippipe[0], fd); + close(gzippipe[0]); + close(gzippipe[1]); + } else { + dup2(gzippipe[1], STDOUT_FILENO); + dup2(fd, STDIN_FILENO); + close(gzippipe[0]); + close(gzippipe[1]); + if (execlp("gzip", "gzip", "-dc", NULL)) + err(1, "exec()ing gzip"); + } + } + + /* Initialize progressbar.c's global state */ + bytes = 0; + progress = 1; + ttyout = eflag ? stderr : stdout; + + if (ioctl(fileno(ttyout), TIOCGWINSZ, &wins) == -1) + ttywidth = 80; + else + ttywidth = wins.ws_col; + + fb_buf = malloc(buffersize); + if (fb_buf == NULL) + err(1, "malloc for buffersize"); + + if (pipe(outpipe) < 0) + err(1, "output pipe"); + pid = fork(); + if (pid < 0) + err(1, "fork for output pipe"); + + if (pid == 0) { + /* child */ + dup2(outpipe[0], STDIN_FILENO); + close(outpipe[0]); + close(outpipe[1]); + execvp(argv[0], argv); + err(1, "could not exec %s", argv[0]); + } + close(outpipe[0]); + + signal(SIGPIPE, broken_pipe); + progressmeter(-1); + + while (1) { + do { + nr = read(fd, fb_buf, buffersize); + } while (nr < 0 && errno == EINTR); + if (nr <= 0) + break; + for (off = 0; nr; nr -= nw, off += nw, bytes += nw) + if ((nw = write(outpipe[1], fb_buf + off, + (size_t) nr)) < 0) { + if (errno == EINTR) { + nw = 0; + continue; + } + progressmeter(1); + err(1, "writing %u bytes to output pipe", + (unsigned) nr); + } + } + close(outpipe[1]); + + gzipstat = 0; + cmdstat = 0; + while (pid || gzippid) { + deadpid = wait(&ws); + /* + * We need to exit with an error if the command (or gzip) + * exited abnormally. + * Unfortunately we can't generate a true 'exited by signal' + * error without sending the signal to ourselves :-( + */ + ws = WIFSIGNALED(ws) ? WTERMSIG(ws) : WEXITSTATUS(ws); + + if (deadpid != -1 && errno == EINTR) + continue; + if (deadpid == pid) { + pid = 0; + cmdstat = ws; + continue; + } + if (deadpid == gzippid) { + gzippid = 0; + gzipstat = ws; + continue; + } + break; + } + + progressmeter(1); + signal(SIGPIPE, SIG_DFL); + + free(fb_buf); + + exit(cmdstat ? cmdstat : gzipstat); +} diff --git a/progressbar.c b/progressbar.c new file mode 100644 index 0000000..85f1ecb --- /dev/null +++ b/progressbar.c @@ -0,0 +1,424 @@ +/* $NetBSD: progressbar.c,v 1.24 2021/01/06 04:43:14 lukem Exp $ */ + +/*- + * Copyright (c) 1997-2021 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Luke Mewburn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__RCSID("$NetBSD: progressbar.c,v 1.24 2021/01/06 04:43:14 lukem Exp $"); +#endif /* not lint */ + +/* + * FTP User Program -- Misc support routines + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "progressbar.h" + +#if !defined(NO_PROGRESS) +/* + * return non-zero if we're the current foreground process + */ +int +foregroundproc(void) +{ + static pid_t pgrp = -1; + + if (pgrp == -1) + pgrp = getpgrp(); + + return (tcgetpgrp(fileno(ttyout)) == pgrp); +} +#endif /* !defined(NO_PROGRESS) */ + + +static void updateprogressmeter(int); + +/* + * SIGALRM handler to update the progress meter + */ +static void +updateprogressmeter(int dummy) +{ + int oerrno = errno; + + progressmeter(0); + errno = oerrno; +} + +/* + * List of order of magnitude suffixes, per IEC 60027-2. + */ +#if !defined(NO_PROGRESS) || !defined(STANDALONE_PROGRESS) +static const char * const suffixes[] = { + "", /* 2^0 (byte) */ + "KiB", /* 2^10 Kibibyte */ + "MiB", /* 2^20 Mebibyte */ + "GiB", /* 2^30 Gibibyte */ + "TiB", /* 2^40 Tebibyte */ + "PiB", /* 2^50 Pebibyte */ + "EiB", /* 2^60 Exbibyte */ +#if 0 + /* The following are not necessary for signed 64-bit off_t */ + "ZiB", /* 2^70 Zebibyte */ + "YiB", /* 2^80 Yobibyte */ +#endif +}; +#define NSUFFIXES (int)(sizeof(suffixes) / sizeof(suffixes[0])) +#endif + +/* + * Display a transfer progress bar if progress is non-zero. + * SIGALRM is hijacked for use by this function. + * - Before the transfer, set filesize to size of file (or -1 if unknown), + * and call with flag = -1. This starts the once per second timer, + * and a call to updateprogressmeter() upon SIGALRM. + * - During the transfer, updateprogressmeter will call progressmeter + * with flag = 0 + * - After the transfer, call with flag = 1 + */ +static struct timeval start; +static struct timeval lastupdate; + +#define BUFLEFT (sizeof(buf) - len) + +void +progressmeter(int flag) +{ + static off_t lastsize; + off_t cursize; + struct timeval now, wait; +#ifndef NO_PROGRESS + struct timeval td; + off_t abbrevsize, bytespersec; + double elapsed; + int ratio, i, remaining, barlength; + + /* + * Work variables for progress bar. + * + * XXX: if the format of the progress bar changes + * (especially the number of characters in the + * `static' portion of it), be sure to update + * these appropriately. + */ +#endif +#if !defined(NO_PROGRESS) || !defined(STANDALONE_PROGRESS) + size_t len; + char buf[256]; /* workspace for progress bar */ +#endif +#ifndef NO_PROGRESS +#define BAROVERHEAD 45 /* non `*' portion of progress bar */ + /* + * stars should contain at least + * sizeof(buf) - BAROVERHEAD entries + */ + static const char stars[] = +"*****************************************************************************" +"*****************************************************************************" +"*****************************************************************************"; + +#endif + + if (flag == -1) { + (void)gettimeofday(&start, NULL); + lastupdate = start; + lastsize = restart_point; + } + + (void)gettimeofday(&now, NULL); + cursize = bytes + restart_point; + timersub(&now, &lastupdate, &wait); + if (cursize > lastsize) { + lastupdate = now; + lastsize = cursize; + wait.tv_sec = 0; + } else { +#ifndef STANDALONE_PROGRESS + if (quit_time > 0 && wait.tv_sec > quit_time) { + len = snprintf(buf, sizeof(buf), "\r\n%s: " + "transfer aborted because stalled for %lu sec.\r\n", + getprogname(), (unsigned long)wait.tv_sec); + (void)write(fileno(ttyout), buf, len); + alarmtimer(0); + (void)xsignal(SIGALRM, SIG_DFL); + siglongjmp(toplevel, 1); + } +#endif /* !STANDALONE_PROGRESS */ + } + /* + * Always set the handler even if we are not the foreground process. + */ +#ifdef STANDALONE_PROGRESS + if (progress) { +#else + if (quit_time > 0 || progress) { +#endif /* !STANDALONE_PROGRESS */ + if (flag == -1) { + (void)xsignal(SIGALRM, updateprogressmeter); + alarmtimer(1); /* set alarm timer for 1 Hz */ + } else if (flag == 1) { + alarmtimer(0); + (void)xsignal(SIGALRM, SIG_DFL); + } + } +#ifndef NO_PROGRESS + if (!progress) + return; + len = 0; + + /* + * print progress bar only if we are foreground process. + */ + if (! foregroundproc()) + return; + + len += snprintf(buf + len, BUFLEFT, "\r"); + if (prefix) + len += snprintf(buf + len, BUFLEFT, "%s", prefix); + if (filesize > 0) { + ratio = (int)((double)cursize * 100.0 / (double)filesize); + ratio = MAX(ratio, 0); + ratio = MIN(ratio, 100); + len += snprintf(buf + len, BUFLEFT, "%3d%% ", ratio); + + /* + * calculate the length of the `*' bar, ensuring that + * the number of stars won't exceed the buffer size + */ + barlength = MIN((int)(sizeof(buf) - 1), ttywidth) - BAROVERHEAD; + if (prefix) + barlength -= (int)strlen(prefix); + if (barlength > 0) { + i = barlength * ratio / 100; + len += snprintf(buf + len, BUFLEFT, + "|%.*s%*s|", i, stars, (int)(barlength - i), ""); + } + } + + abbrevsize = cursize; + for (i = 0; abbrevsize >= 100000 && i < NSUFFIXES; i++) + abbrevsize >>= 10; + if (i == NSUFFIXES) + i--; + len += snprintf(buf + len, BUFLEFT, " " LLFP("5") " %-3s ", + (LLT)abbrevsize, + suffixes[i]); + + timersub(&now, &start, &td); + elapsed = td.tv_sec + (td.tv_usec / 1000000.0); + + bytespersec = 0; + if (bytes > 0) { + bytespersec = bytes; + if (elapsed > 0.0) + bytespersec /= elapsed; + } + for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++) + bytespersec >>= 10; + len += snprintf(buf + len, BUFLEFT, + " " LLFP("3") ".%02d %.2sB/s ", + (LLT)(bytespersec / 1024), + (int)((bytespersec % 1024) * 100 / 1024), + suffixes[i]); + + if (filesize > 0) { + if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { + len += snprintf(buf + len, BUFLEFT, " --:-- ETA"); + } else if (wait.tv_sec >= STALLTIME) { + len += snprintf(buf + len, BUFLEFT, " - stalled -"); + } else { + remaining = (int) + ((filesize - restart_point) / (bytes / elapsed) - + elapsed); + if (remaining >= 100 * SECSPERHOUR) + len += snprintf(buf + len, BUFLEFT, + " --:-- ETA"); + else { + i = remaining / SECSPERHOUR; + if (i) + len += snprintf(buf + len, BUFLEFT, + "%2d:", i); + else + len += snprintf(buf + len, BUFLEFT, + " "); + i = remaining % SECSPERHOUR; + len += snprintf(buf + len, BUFLEFT, + "%02d:%02d ETA", i / 60, i % 60); + } + } + } + if (flag == 1) + len += snprintf(buf + len, BUFLEFT, "\n"); + (void)write(fileno(ttyout), buf, len); + +#endif /* !NO_PROGRESS */ +} + +#ifndef STANDALONE_PROGRESS +/* + * Display transfer statistics. + * Requires start to be initialised by progressmeter(-1), + * direction to be defined by xfer routines, and filesize and bytes + * to be updated by xfer routines + * If siginfo is nonzero, an ETA is displayed, and the output goes to stderr + * instead of ttyout. + */ +void +ptransfer(int siginfo) +{ + struct timeval now, td, wait; + double elapsed; + off_t bytespersec; + int remaining, hh, i; + size_t len; + + char buf[256]; /* Work variable for transfer status. */ + + if (!verbose && !progress && !siginfo) + return; + + (void)gettimeofday(&now, NULL); + timersub(&now, &start, &td); + elapsed = td.tv_sec + (td.tv_usec / 1000000.0); + bytespersec = 0; + if (bytes > 0) { + bytespersec = bytes; + if (elapsed > 0.0) + bytespersec /= elapsed; + } + len = 0; + len += snprintf(buf + len, BUFLEFT, LLF " byte%s %s in ", + (LLT)bytes, bytes == 1 ? "" : "s", direction); + remaining = (int)elapsed; + if (remaining > SECSPERDAY) { + int days; + + days = remaining / SECSPERDAY; + remaining %= SECSPERDAY; + len += snprintf(buf + len, BUFLEFT, + "%d day%s ", days, days == 1 ? "" : "s"); + } + hh = remaining / SECSPERHOUR; + remaining %= SECSPERHOUR; + if (hh) + len += snprintf(buf + len, BUFLEFT, "%2d:", hh); + len += snprintf(buf + len, BUFLEFT, + "%02d:%02d ", remaining / 60, remaining % 60); + + for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++) + bytespersec >>= 10; + if (i == NSUFFIXES) + i--; + len += snprintf(buf + len, BUFLEFT, "(" LLF ".%02d %.2sB/s)", + (LLT)(bytespersec / 1024), + (int)((bytespersec % 1024) * 100 / 1024), + suffixes[i]); + + if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 + && bytes + restart_point <= filesize) { + remaining = (int)((filesize - restart_point) / + (bytes / elapsed) - elapsed); + hh = remaining / SECSPERHOUR; + remaining %= SECSPERHOUR; + len += snprintf(buf + len, BUFLEFT, " ETA: "); + if (hh) + len += snprintf(buf + len, BUFLEFT, "%2d:", hh); + len += snprintf(buf + len, BUFLEFT, "%02d:%02d", + remaining / 60, remaining % 60); + timersub(&now, &lastupdate, &wait); + if (wait.tv_sec >= STALLTIME) + len += snprintf(buf + len, BUFLEFT, " (stalled)"); + } + len += snprintf(buf + len, BUFLEFT, "\n"); + (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, len); +} + +/* + * SIG{INFO,QUIT} handler to print transfer stats if a transfer is in progress + */ +void +psummary(int notused) +{ + int oerrno = errno; + + if (bytes > 0) { + if (fromatty) + write(fileno(ttyout), "\n", 1); + ptransfer(1); + } + errno = oerrno; +} +#endif /* !STANDALONE_PROGRESS */ + + +/* + * Set the SIGALRM interval timer for wait seconds, 0 to disable. + */ +void +alarmtimer(int wait) +{ + struct itimerval itv; + + itv.it_value.tv_sec = wait; + itv.it_value.tv_usec = 0; + itv.it_interval = itv.it_value; + setitimer(ITIMER_REAL, &itv, NULL); +} + +/* + * Install a non-restartable POSIX signal handler. + */ +sigfunc +xsignal(int sig, sigfunc func) +{ + struct sigaction act, oact; + act.sa_handler = func; + + sigemptyset(&act.sa_mask); + act.sa_flags = 0; +#if defined(SA_INTERRUPT) /* SunOS 4.x */ + act.sa_flags = SA_INTERRUPT; +#endif + if (sigaction(sig, &act, &oact) < 0) + return (SIG_ERR); + return (oact.sa_handler); +} diff --git a/strsuftoll.c b/strsuftoll.c new file mode 100644 index 0000000..973df9d --- /dev/null +++ b/strsuftoll.c @@ -0,0 +1,237 @@ +/* $NetBSD: strsuftoll.c,v 1.9 2011/10/22 22:08:47 christos Exp $ */ +/*- + * Copyright (c) 2001-2002,2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Luke Mewburn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego and Lance + * Visser of Convex Computer Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#if defined(LIBC_SCCS) && !defined(lint) +__RCSID("$NetBSD: strsuftoll.c,v 1.9 2011/10/22 22:08:47 christos Exp $"); +#endif /* LIBC_SCCS and not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + /* LONGLONG */ +long long strsuftoll(const char *, const char *, long long, long long); + /* LONGLONG */ +long long strsuftollx(const char *, const char *, long long, long long, + char *, size_t); + +/* + * Convert an expression of the following forms to a (u)int64_t. + * 1) A positive decimal number. + * 2) A positive decimal number followed by a b (mult by 512). + * 3) A positive decimal number followed by a k (mult by 1024). + * 4) A positive decimal number followed by a m (mult by 1048576). + * 5) A positive decimal number followed by a g (mult by 1073741824). + * 6) A positive decimal number followed by a t (mult by 1099511627776). + * 7) A positive decimal number followed by a w (mult by sizeof int) + * 8) Two or more positive decimal numbers (with/without k,b or w). + * separated by x (also * for backwards compatibility), specifying + * the product of the indicated values. + * Returns the result upon successful conversion, or exits with an + * appropriate error. + * + */ +/* LONGLONG */ +long long +strsuftoll(const char *desc, const char *val, + long long min, long long max) +{ + long long result; + char errbuf[100]; + + result = strsuftollx(desc, val, min, max, errbuf, sizeof(errbuf)); + if (*errbuf != '\0') + errx(EXIT_FAILURE, "%s", errbuf); + return result; +} + +/* + * As strsuftoll(), but returns the error message into the provided buffer + * rather than exiting with it. + */ +/* LONGLONG */ +static long long +__strsuftollx(const char *desc, const char *val, + long long min, long long max, char *ebuf, size_t ebuflen, size_t depth) +{ + long long num, t; + char *expr; + + (void)(desc != NULL); + (void)(val != NULL); + (void)(ebuf != NULL); + + if (depth > 16) { + snprintf(ebuf, ebuflen, "%s: Recursion limit exceeded", desc); + return 0; + } + + while (isspace((unsigned char)*val)) /* Skip leading space */ + val++; + + errno = 0; + num = strtoll(val, &expr, 10); + if (errno == ERANGE) + goto erange; /* Overflow */ + + if (expr == val) /* No digits */ + goto badnum; + + switch (*expr) { + case 'b': + t = num; + num *= 512; /* 1 block */ + if (t > num) + goto erange; + ++expr; + break; + case 'k': + t = num; + num *= 1024; /* 1 kibibyte */ + if (t > num) + goto erange; + ++expr; + break; + case 'm': + t = num; + num *= 1048576; /* 1 mebibyte */ + if (t > num) + goto erange; + ++expr; + break; + case 'g': + t = num; + num *= 1073741824; /* 1 gibibyte */ + if (t > num) + goto erange; + ++expr; + break; + case 't': + t = num; + num *= 1099511627776LL; /* 1 tebibyte */ + if (t > num) + goto erange; + ++expr; + break; + case 'w': + t = num; + num *= sizeof(int); /* 1 word */ + if (t > num) + goto erange; + ++expr; + break; + } + + switch (*expr) { + case '\0': + break; + case '*': /* Backward compatible */ + case 'x': + t = num; + num *= __strsuftollx(desc, expr + 1, min, max, ebuf, ebuflen, + depth + 1); + if (*ebuf != '\0') + return 0; + if (t > num) { + erange: + errno = ERANGE; + snprintf(ebuf, ebuflen, "%s: %s", desc, strerror(errno)); + return 0; + } + break; + default: + badnum: + snprintf(ebuf, ebuflen, "%s `%s': illegal number", desc, val); + return 0; + } + if (num < min) { + /* LONGLONG */ + snprintf(ebuf, ebuflen, "%s %lld is less than %lld.", + desc, (long long)num, (long long)min); + return 0; + } + if (num > max) { + /* LONGLONG */ + snprintf(ebuf, ebuflen, "%s %lld is greater than %lld.", + desc, (long long)num, (long long)max); + return 0; + } + *ebuf = '\0'; + return num; +} + +long long +strsuftollx(const char *desc, const char *val, + long long min, long long max, char *ebuf, size_t ebuflen) +{ + return __strsuftollx(desc, val, min, max, ebuf, ebuflen, 0); +} -- cgit v1.2.3-56-ge451 -- cgit v1.2.3-56-ge451