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