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