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