Home | History | Annotate | Line # | Download | only in ftp
progressbar.c revision 1.2
      1 /*	$NetBSD: progressbar.c,v 1.2 2003/02/12 15:18:28 grant Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1997-2003 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Luke Mewburn.
      9  *
     10  * This code is derived from software contributed to The NetBSD Foundation
     11  * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
     12  * NASA Ames Research Center.
     13  *
     14  * Redistribution and use in source and binary forms, with or without
     15  * modification, are permitted provided that the following conditions
     16  * are met:
     17  * 1. Redistributions of source code must retain the above copyright
     18  *    notice, this list of conditions and the following disclaimer.
     19  * 2. Redistributions in binary form must reproduce the above copyright
     20  *    notice, this list of conditions and the following disclaimer in the
     21  *    documentation and/or other materials provided with the distribution.
     22  * 3. All advertising materials mentioning features or use of this software
     23  *    must display the following acknowledgement:
     24  *	This product includes software developed by the NetBSD
     25  *	Foundation, Inc. and its contributors.
     26  * 4. Neither the name of The NetBSD Foundation nor the names of its
     27  *    contributors may be used to endorse or promote products derived
     28  *    from this software without specific prior written permission.
     29  *
     30  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     31  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     32  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     33  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     34  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     35  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     36  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     37  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     38  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     39  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     40  * POSSIBILITY OF SUCH DAMAGE.
     41  */
     42 
     43 /*
     44  * Copyright (c) 1985, 1989, 1993, 1994
     45  *	The Regents of the University of California.  All rights reserved.
     46  *
     47  * Redistribution and use in source and binary forms, with or without
     48  * modification, are permitted provided that the following conditions
     49  * are met:
     50  * 1. Redistributions of source code must retain the above copyright
     51  *    notice, this list of conditions and the following disclaimer.
     52  * 2. Redistributions in binary form must reproduce the above copyright
     53  *    notice, this list of conditions and the following disclaimer in the
     54  *    documentation and/or other materials provided with the distribution.
     55  * 3. All advertising materials mentioning features or use of this software
     56  *    must display the following acknowledgement:
     57  *	This product includes software developed by the University of
     58  *	California, Berkeley and its contributors.
     59  * 4. Neither the name of the University nor the names of its contributors
     60  *    may be used to endorse or promote products derived from this software
     61  *    without specific prior written permission.
     62  *
     63  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     64  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     65  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     66  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     67  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     68  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     69  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     70  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     71  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     72  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     73  * SUCH DAMAGE.
     74  */
     75 
     76 #include <sys/cdefs.h>
     77 #ifndef lint
     78 __RCSID("$NetBSD: progressbar.c,v 1.2 2003/02/12 15:18:28 grant Exp $");
     79 #endif /* not lint */
     80 
     81 /*
     82  * FTP User Program -- Misc support routines
     83  */
     84 #include <sys/types.h>
     85 #include <sys/param.h>
     86 
     87 #include <err.h>
     88 #include <errno.h>
     89 #include <signal.h>
     90 #include <stdio.h>
     91 #include <stdlib.h>
     92 #include <time.h>
     93 #include <tzfile.h>
     94 #include <unistd.h>
     95 
     96 #include "progressbar.h"
     97 
     98 #if !defined(NO_PROGRESS)
     99 /*
    100  * return non-zero if we're the current foreground process
    101  */
    102 int
    103 foregroundproc(void)
    104 {
    105 	static pid_t pgrp = -1;
    106 
    107 	if (pgrp == -1)
    108 		pgrp = getpgrp();
    109 
    110 	return (tcgetpgrp(fileno(ttyout)) == pgrp);
    111 }
    112 #endif	/* !defined(NO_PROGRESS) */
    113 
    114 
    115 #ifndef	NO_PROGRESS
    116 static void updateprogressmeter(int);
    117 
    118 /*
    119  * SIGALRM handler to update the progress meter
    120  */
    121 static void
    122 updateprogressmeter(int dummy)
    123 {
    124 	int oerrno = errno;
    125 
    126 	progressmeter(0);
    127 	errno = oerrno;
    128 }
    129 #endif	/* NO_PROGRESS */
    130 
    131 
    132 /*
    133  * List of order of magnitude prefixes.
    134  * The last is `P', as 2^64 = 16384 Petabytes
    135  */
    136 static const char prefixes[] = " KMGTP";
    137 
    138 /*
    139  * Display a transfer progress bar if progress is non-zero.
    140  * SIGALRM is hijacked for use by this function.
    141  * - Before the transfer, set filesize to size of file (or -1 if unknown),
    142  *   and call with flag = -1. This starts the once per second timer,
    143  *   and a call to updateprogressmeter() upon SIGALRM.
    144  * - During the transfer, updateprogressmeter will call progressmeter
    145  *   with flag = 0
    146  * - After the transfer, call with flag = 1
    147  */
    148 static struct timeval start;
    149 static struct timeval lastupdate;
    150 
    151 #define	BUFLEFT	(sizeof(buf) - len)
    152 
    153 void
    154 progressmeter(int flag)
    155 {
    156 	static off_t lastsize;
    157 	off_t cursize;
    158 	struct timeval now, wait;
    159 #ifndef NO_PROGRESS
    160 	struct timeval td;
    161 	off_t abbrevsize, bytespersec;
    162 	double elapsed;
    163 	int ratio, barlength, i, len, remaining;
    164 
    165 			/*
    166 			 * Work variables for progress bar.
    167 			 *
    168 			 * XXX:	if the format of the progress bar changes
    169 			 *	(especially the number of characters in the
    170 			 *	`static' portion of it), be sure to update
    171 			 *	these appropriately.
    172 			 */
    173 	char		buf[256];	/* workspace for progress bar */
    174 #define	BAROVERHEAD	43		/* non `*' portion of progress bar */
    175 					/*
    176 					 * stars should contain at least
    177 					 * sizeof(buf) - BAROVERHEAD entries
    178 					 */
    179 	static const char	stars[] =
    180 "*****************************************************************************"
    181 "*****************************************************************************"
    182 "*****************************************************************************";
    183 
    184 #endif
    185 
    186 	if (flag == -1) {
    187 		(void)gettimeofday(&start, NULL);
    188 		lastupdate = start;
    189 		lastsize = restart_point;
    190 	}
    191 
    192 	(void)gettimeofday(&now, NULL);
    193 	cursize = bytes + restart_point;
    194 	timersub(&now, &lastupdate, &wait);
    195 	if (cursize > lastsize) {
    196 		lastupdate = now;
    197 		lastsize = cursize;
    198 		wait.tv_sec = 0;
    199 	} else {
    200 #ifndef STANDALONE_PROGRESS
    201 		if (quit_time > 0 && wait.tv_sec > quit_time) {
    202 			len = snprintf(buf, sizeof(buf), "\r\n%s: "
    203 			    "transfer aborted because stalled for %lu sec.\r\n",
    204 			    getprogname(), (unsigned long)wait.tv_sec);
    205 			(void)write(fileno(ttyout), buf, len);
    206 			(void)xsignal(SIGALRM, SIG_DFL);
    207 			alarmtimer(0);
    208 			siglongjmp(toplevel, 1);
    209 		}
    210 #endif	/* !STANDALONE_PROGRESS */
    211 	}
    212 	/*
    213 	 * Always set the handler even if we are not the foreground process.
    214 	 */
    215 #ifdef STANDALONE_PROGRESS
    216 	if (progress) {
    217 #else
    218 	if (quit_time > 0 || progress) {
    219 #endif /* !STANDALONE_PROGRESS */
    220 		if (flag == -1) {
    221 			(void)xsignal_restart(SIGALRM, updateprogressmeter, 1);
    222 			alarmtimer(1);		/* set alarm timer for 1 Hz */
    223 		} else if (flag == 1) {
    224 			(void)xsignal(SIGALRM, SIG_DFL);
    225 			alarmtimer(0);
    226 		}
    227 	}
    228 #ifndef NO_PROGRESS
    229 	if (!progress)
    230 		return;
    231 	len = 0;
    232 
    233 	/*
    234 	 * print progress bar only if we are foreground process.
    235 	 */
    236 	if (! foregroundproc())
    237 		return;
    238 
    239 	len += snprintf(buf + len, BUFLEFT, "\r");
    240 	if (filesize > 0) {
    241 		ratio = (int)((double)cursize * 100.0 / (double)filesize);
    242 		ratio = MAX(ratio, 0);
    243 		ratio = MIN(ratio, 100);
    244 		len += snprintf(buf + len, BUFLEFT, "%3d%% ", ratio);
    245 
    246 			/*
    247 			 * calculate the length of the `*' bar, ensuring that
    248 			 * the number of stars won't exceed the buffer size
    249 			 */
    250 		barlength = MIN(sizeof(buf) - 1, ttywidth) - BAROVERHEAD;
    251 		if (barlength > 0) {
    252 			i = barlength * ratio / 100;
    253 			len += snprintf(buf + len, BUFLEFT,
    254 			    "|%.*s%*s|", i, stars, barlength - i, "");
    255 		}
    256 	}
    257 
    258 	abbrevsize = cursize;
    259 	for (i = 0; abbrevsize >= 100000 && i < sizeof(prefixes); i++)
    260 		abbrevsize >>= 10;
    261 	len += snprintf(buf + len, BUFLEFT, " " LLFP("5") " %c%c ",
    262 	    (LLT)abbrevsize,
    263 	    prefixes[i],
    264 	    i == 0 ? ' ' : 'B');
    265 
    266 	timersub(&now, &start, &td);
    267 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
    268 
    269 	bytespersec = 0;
    270 	if (bytes > 0) {
    271 		bytespersec = bytes;
    272 		if (elapsed > 0.0)
    273 			bytespersec /= elapsed;
    274 	}
    275 	for (i = 1; bytespersec >= 1024000 && i < sizeof(prefixes); i++)
    276 		bytespersec >>= 10;
    277 	len += snprintf(buf + len, BUFLEFT,
    278 	    " " LLFP("3") ".%02d %cB/s ",
    279 	    (LLT)(bytespersec / 1024),
    280 	    (int)((bytespersec % 1024) * 100 / 1024),
    281 	    prefixes[i]);
    282 
    283 	if (filesize > 0) {
    284 		if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
    285 			len += snprintf(buf + len, BUFLEFT, "   --:-- ETA");
    286 		} else if (wait.tv_sec >= STALLTIME) {
    287 			len += snprintf(buf + len, BUFLEFT, " - stalled -");
    288 		} else {
    289 			remaining = (int)
    290 			    ((filesize - restart_point) / (bytes / elapsed) -
    291 			    elapsed);
    292 			if (remaining >= 100 * SECSPERHOUR)
    293 				len += snprintf(buf + len, BUFLEFT,
    294 				    "   --:-- ETA");
    295 			else {
    296 				i = remaining / SECSPERHOUR;
    297 				if (i)
    298 					len += snprintf(buf + len, BUFLEFT,
    299 					    "%2d:", i);
    300 				else
    301 					len += snprintf(buf + len, BUFLEFT,
    302 					    "   ");
    303 				i = remaining % SECSPERHOUR;
    304 				len += snprintf(buf + len, BUFLEFT,
    305 				    "%02d:%02d ETA", i / 60, i % 60);
    306 			}
    307 		}
    308 	}
    309 	if (flag == 1)
    310 		len += snprintf(buf + len, BUFLEFT, "\n");
    311 	(void)write(fileno(ttyout), buf, len);
    312 
    313 #endif	/* !NO_PROGRESS */
    314 }
    315 
    316 #ifndef STANDALONE_PROGRESS
    317 /*
    318  * Display transfer statistics.
    319  * Requires start to be initialised by progressmeter(-1),
    320  * direction to be defined by xfer routines, and filesize and bytes
    321  * to be updated by xfer routines
    322  * If siginfo is nonzero, an ETA is displayed, and the output goes to stderr
    323  * instead of ttyout.
    324  */
    325 void
    326 ptransfer(int siginfo)
    327 {
    328 	struct timeval now, td, wait;
    329 	double elapsed;
    330 	off_t bytespersec;
    331 	int remaining, hh, i, len;
    332 
    333 	char buf[256];		/* Work variable for transfer status. */
    334 
    335 	if (!verbose && !progress && !siginfo)
    336 		return;
    337 
    338 	(void)gettimeofday(&now, NULL);
    339 	timersub(&now, &start, &td);
    340 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
    341 	bytespersec = 0;
    342 	if (bytes > 0) {
    343 		bytespersec = bytes;
    344 		if (elapsed > 0.0)
    345 			bytespersec /= elapsed;
    346 	}
    347 	len = 0;
    348 	len += snprintf(buf + len, BUFLEFT, LLF " byte%s %s in ",
    349 	    (LLT)bytes, bytes == 1 ? "" : "s", direction);
    350 	remaining = (int)elapsed;
    351 	if (remaining > SECSPERDAY) {
    352 		int days;
    353 
    354 		days = remaining / SECSPERDAY;
    355 		remaining %= SECSPERDAY;
    356 		len += snprintf(buf + len, BUFLEFT,
    357 		    "%d day%s ", days, days == 1 ? "" : "s");
    358 	}
    359 	hh = remaining / SECSPERHOUR;
    360 	remaining %= SECSPERHOUR;
    361 	if (hh)
    362 		len += snprintf(buf + len, BUFLEFT, "%2d:", hh);
    363 	len += snprintf(buf + len, BUFLEFT,
    364 	    "%02d:%02d ", remaining / 60, remaining % 60);
    365 
    366 	for (i = 1; bytespersec >= 1024000 && i < sizeof(prefixes); i++)
    367 		bytespersec >>= 10;
    368 	len += snprintf(buf + len, BUFLEFT, "(" LLF ".%02d %cB/s)",
    369 	    (LLT)(bytespersec / 1024),
    370 	    (int)((bytespersec % 1024) * 100 / 1024),
    371 	    prefixes[i]);
    372 
    373 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0
    374 	    && bytes + restart_point <= filesize) {
    375 		remaining = (int)((filesize - restart_point) /
    376 				  (bytes / elapsed) - elapsed);
    377 		hh = remaining / SECSPERHOUR;
    378 		remaining %= SECSPERHOUR;
    379 		len += snprintf(buf + len, BUFLEFT, "  ETA: ");
    380 		if (hh)
    381 			len += snprintf(buf + len, BUFLEFT, "%2d:", hh);
    382 		len += snprintf(buf + len, BUFLEFT, "%02d:%02d",
    383 		    remaining / 60, remaining % 60);
    384 		timersub(&now, &lastupdate, &wait);
    385 		if (wait.tv_sec >= STALLTIME)
    386 			len += snprintf(buf + len, BUFLEFT, "  (stalled)");
    387 	}
    388 	len += snprintf(buf + len, BUFLEFT, "\n");
    389 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, len);
    390 }
    391 
    392 /*
    393  * SIG{INFO,QUIT} handler to print transfer stats if a transfer is in progress
    394  */
    395 void
    396 psummary(int notused)
    397 {
    398 	int oerrno = errno;
    399 
    400 	if (bytes > 0) {
    401 		if (fromatty)
    402 			write(fileno(ttyout), "\n", 1);
    403 		ptransfer(1);
    404 	}
    405 	errno = oerrno;
    406 }
    407 #endif	/* !STANDALONE_PROGRESS */
    408 
    409 
    410 /*
    411  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
    412  */
    413 void
    414 alarmtimer(int wait)
    415 {
    416 	struct itimerval itv;
    417 
    418 	itv.it_value.tv_sec = wait;
    419 	itv.it_value.tv_usec = 0;
    420 	itv.it_interval = itv.it_value;
    421 	setitimer(ITIMER_REAL, &itv, NULL);
    422 }
    423 
    424 
    425 /*
    426  * Install a POSIX signal handler, allowing the invoker to set whether
    427  * the signal should be restartable or not
    428  */
    429 sigfunc
    430 xsignal_restart(int sig, sigfunc func, int restartable)
    431 {
    432 	struct sigaction act, oact;
    433 	act.sa_handler = func;
    434 
    435 	sigemptyset(&act.sa_mask);
    436 #if defined(SA_RESTART)			/* 4.4BSD, Posix(?), SVR4 */
    437 	act.sa_flags = restartable ? SA_RESTART : 0;
    438 #elif defined(SA_INTERRUPT)		/* SunOS 4.x */
    439 	act.sa_flags = restartable ? 0 : SA_INTERRUPT;
    440 #else
    441 #error "system must have SA_RESTART or SA_INTERRUPT"
    442 #endif
    443 	if (sigaction(sig, &act, &oact) < 0)
    444 		return (SIG_ERR);
    445 	return (oact.sa_handler);
    446 }
    447 
    448 /*
    449  * Install a signal handler with the `restartable' flag set dependent upon
    450  * which signal is being set. (This is a wrapper to xsignal_restart())
    451  */
    452 sigfunc
    453 xsignal(int sig, sigfunc func)
    454 {
    455 	int restartable;
    456 
    457 	/*
    458 	 * Some signals print output or change the state of the process.
    459 	 * There should be restartable, so that reads and writes are
    460 	 * not affected.  Some signals should cause program flow to change;
    461 	 * these signals should not be restartable, so that the system call
    462 	 * will return with EINTR, and the program will go do something
    463 	 * different.  If the signal handler calls longjmp() or siglongjmp(),
    464 	 * it doesn't matter if it's restartable.
    465 	 */
    466 
    467 	switch(sig) {
    468 #ifdef SIGINFO
    469 	case SIGINFO:
    470 #endif
    471 	case SIGQUIT:
    472 	case SIGUSR1:
    473 	case SIGUSR2:
    474 	case SIGWINCH:
    475 		restartable = 1;
    476 		break;
    477 
    478 	case SIGALRM:
    479 	case SIGINT:
    480 	case SIGPIPE:
    481 		restartable = 0;
    482 		break;
    483 
    484 	default:
    485 		/*
    486 		 * This is unpleasant, but I don't know what would be better.
    487 		 * Right now, this "can't happen"
    488 		 */
    489 		errx(1, "xsignal_restart called with signal %d", sig);
    490 	}
    491 
    492 	return(xsignal_restart(sig, func, restartable));
    493 }
    494