progressmeter.c revision 113908
1113908Sdes/*
2113908Sdes * Copyright (c) 1999 Theo de Raadt.  All rights reserved.
3113908Sdes * Copyright (c) 1999 Aaron Campbell.  All rights reserved.
4113908Sdes *
5113908Sdes * Redistribution and use in source and binary forms, with or without
6113908Sdes * modification, are permitted provided that the following conditions
7113908Sdes * are met:
8113908Sdes * 1. Redistributions of source code must retain the above copyright
9113908Sdes *    notice, this list of conditions and the following disclaimer.
10113908Sdes * 2. Redistributions in binary form must reproduce the above copyright
11113908Sdes *    notice, this list of conditions and the following disclaimer in the
12113908Sdes *    documentation and/or other materials provided with the distribution.
13113908Sdes *
14113908Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15113908Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16113908Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17113908Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18113908Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19113908Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20113908Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21113908Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22113908Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23113908Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24113908Sdes */
25113908Sdes
26113908Sdes/*
27113908Sdes * Parts from:
28113908Sdes *
29113908Sdes * Copyright (c) 1983, 1990, 1992, 1993, 1995
30113908Sdes *	The Regents of the University of California.  All rights reserved.
31113908Sdes *
32113908Sdes * Redistribution and use in source and binary forms, with or without
33113908Sdes * modification, are permitted provided that the following conditions
34113908Sdes * are met:
35113908Sdes * 1. Redistributions of source code must retain the above copyright
36113908Sdes *    notice, this list of conditions and the following disclaimer.
37113908Sdes * 2. Redistributions in binary form must reproduce the above copyright
38113908Sdes *    notice, this list of conditions and the following disclaimer in the
39113908Sdes *    documentation and/or other materials provided with the distribution.
40113908Sdes * 3. All advertising materials mentioning features or use of this software
41113908Sdes *    must display the following acknowledgement:
42113908Sdes *	This product includes software developed by the University of
43113908Sdes *	California, Berkeley and its contributors.
44113908Sdes * 4. Neither the name of the University nor the names of its contributors
45113908Sdes *    may be used to endorse or promote products derived from this software
46113908Sdes *    without specific prior written permission.
47113908Sdes *
48113908Sdes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
49113908Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
50113908Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
51113908Sdes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
52113908Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
53113908Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
54113908Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
55113908Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
56113908Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
57113908Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
58113908Sdes * SUCH DAMAGE.
59113908Sdes *
60113908Sdes */
61113908Sdes
62113908Sdes#include "includes.h"
63113908SdesRCSID("$OpenBSD: progressmeter.c,v 1.3 2003/03/17 10:38:38 markus Exp $");
64113908Sdes
65113908Sdes#ifdef HAVE_LIBGEN_H
66113908Sdes#include <libgen.h>
67113908Sdes#endif
68113908Sdes
69113908Sdes#include "atomicio.h"
70113908Sdes#include "progressmeter.h"
71113908Sdes
72113908Sdes/* Number of seconds before xfer considered "stalled". */
73113908Sdes#define STALLTIME	5
74113908Sdes/* alarm() interval for updating progress meter. */
75113908Sdes#define PROGRESSTIME	1
76113908Sdes
77113908Sdes/* Signal handler used for updating the progress meter. */
78113908Sdesstatic void update_progress_meter(int);
79113908Sdes
80113908Sdes/* Returns non-zero if we are the foreground process. */
81113908Sdesstatic int foregroundproc(void);
82113908Sdes
83113908Sdes/* Returns width of the terminal (for progress meter calculations). */
84113908Sdesstatic int get_tty_width(void);
85113908Sdes
86113908Sdes/* Visual statistics about files as they are transferred. */
87113908Sdesstatic void draw_progress_meter(void);
88113908Sdes
89113908Sdes/* Time a transfer started. */
90113908Sdesstatic struct timeval start;
91113908Sdes
92113908Sdes/* Number of bytes of current file transferred so far. */
93113908Sdesstatic volatile off_t *statbytes;
94113908Sdes
95113908Sdes/* Total size of current file. */
96113908Sdesstatic off_t totalbytes;
97113908Sdes
98113908Sdes/* Name of current file being transferred. */
99113908Sdesstatic char *curfile;
100113908Sdes
101113908Sdes/* Time of last update. */
102113908Sdesstatic struct timeval lastupdate;
103113908Sdes
104113908Sdes/* Size at the time of the last update. */
105113908Sdesstatic off_t lastsize;
106113908Sdes
107113908Sdesvoid
108113908Sdesstart_progress_meter(char *file, off_t filesize, off_t *counter)
109113908Sdes{
110113908Sdes	if ((curfile = basename(file)) == NULL)
111113908Sdes		curfile = file;
112113908Sdes
113113908Sdes	totalbytes = filesize;
114113908Sdes	statbytes = counter;
115113908Sdes	(void) gettimeofday(&start, (struct timezone *) 0);
116113908Sdes	lastupdate = start;
117113908Sdes	lastsize = 0;
118113908Sdes
119113908Sdes	draw_progress_meter();
120113908Sdes	signal(SIGALRM, update_progress_meter);
121113908Sdes	alarm(PROGRESSTIME);
122113908Sdes}
123113908Sdes
124113908Sdesvoid
125113908Sdesstop_progress_meter()
126113908Sdes{
127113908Sdes	alarm(0);
128113908Sdes	draw_progress_meter();
129113908Sdes	if (foregroundproc() != 0)
130113908Sdes		atomicio(write, fileno(stdout), "\n", 1);
131113908Sdes}
132113908Sdes
133113908Sdesstatic void
134113908Sdesupdate_progress_meter(int ignore)
135113908Sdes{
136113908Sdes	int save_errno = errno;
137113908Sdes
138113908Sdes	draw_progress_meter();
139113908Sdes	signal(SIGALRM, update_progress_meter);
140113908Sdes	alarm(PROGRESSTIME);
141113908Sdes	errno = save_errno;
142113908Sdes}
143113908Sdes
144113908Sdesstatic int
145113908Sdesforegroundproc(void)
146113908Sdes{
147113908Sdes	static pid_t pgrp = -1;
148113908Sdes	int ctty_pgrp;
149113908Sdes
150113908Sdes	if (pgrp == -1)
151113908Sdes		pgrp = getpgrp();
152113908Sdes
153113908Sdes#ifdef HAVE_TCGETPGRP
154113908Sdes        return ((ctty_pgrp = tcgetpgrp(STDOUT_FILENO)) != -1 &&
155113908Sdes	                ctty_pgrp == pgrp);
156113908Sdes#else
157113908Sdes	return ((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
158113908Sdes		 ctty_pgrp == pgrp));
159113908Sdes#endif
160113908Sdes}
161113908Sdes
162113908Sdesstatic void
163113908Sdesdraw_progress_meter()
164113908Sdes{
165113908Sdes	static const char spaces[] = "                          "
166113908Sdes	    "                                                   "
167113908Sdes	    "                                                   "
168113908Sdes	    "                                                   "
169113908Sdes	    "                                                   "
170113908Sdes	    "                                                   ";
171113908Sdes	static const char prefixes[] = " KMGTP";
172113908Sdes	struct timeval now, td, wait;
173113908Sdes	off_t cursize, abbrevsize, bytespersec;
174113908Sdes	double elapsed;
175113908Sdes	int ratio, remaining, i, ai, bi, nspaces;
176113908Sdes	char buf[512];
177113908Sdes
178113908Sdes	if (foregroundproc() == 0)
179113908Sdes		return;
180113908Sdes
181113908Sdes	(void) gettimeofday(&now, (struct timezone *) 0);
182113908Sdes	cursize = *statbytes;
183113908Sdes	if (totalbytes != 0) {
184113908Sdes		ratio = 100.0 * cursize / totalbytes;
185113908Sdes		ratio = MAX(ratio, 0);
186113908Sdes		ratio = MIN(ratio, 100);
187113908Sdes	} else
188113908Sdes		ratio = 100;
189113908Sdes
190113908Sdes	abbrevsize = cursize;
191113908Sdes	for (ai = 0; abbrevsize >= 10000 && ai < sizeof(prefixes); ai++)
192113908Sdes		abbrevsize >>= 10;
193113908Sdes
194113908Sdes	timersub(&now, &lastupdate, &wait);
195113908Sdes	if (cursize > lastsize) {
196113908Sdes		lastupdate = now;
197113908Sdes		lastsize = cursize;
198113908Sdes		wait.tv_sec = 0;
199113908Sdes	}
200113908Sdes	timersub(&now, &start, &td);
201113908Sdes	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
202113908Sdes
203113908Sdes	bytespersec = 0;
204113908Sdes	if (cursize > 0) {
205113908Sdes		bytespersec = cursize;
206113908Sdes		if (elapsed > 0.0)
207113908Sdes			bytespersec /= elapsed;
208113908Sdes	}
209113908Sdes	for (bi = 1; bytespersec >= 1024000 && bi < sizeof(prefixes); bi++)
210113908Sdes		bytespersec >>= 10;
211113908Sdes
212113908Sdes    	nspaces = MIN(get_tty_width() - 79, sizeof(spaces) - 1);
213113908Sdes
214113908Sdes#ifdef HAVE_LONG_LONG_INT
215113908Sdes	snprintf(buf, sizeof(buf),
216113908Sdes	    "\r%-45.45s%.*s%3d%% %4lld%c%c %3lld.%01d%cB/s",
217113908Sdes	    curfile,
218113908Sdes	    nspaces,
219113908Sdes	    spaces,
220113908Sdes	    ratio,
221113908Sdes	    (long long)abbrevsize,
222113908Sdes	    prefixes[ai],
223113908Sdes	    ai == 0 ? ' ' : 'B',
224113908Sdes	    (long long)(bytespersec / 1024),
225113908Sdes	    (int)((bytespersec % 1024) * 10 / 1024),
226113908Sdes	    prefixes[bi]
227113908Sdes	);
228113908Sdes#else
229113908Sdes		/* XXX: Handle integer overflow? */
230113908Sdes	snprintf(buf, sizeof(buf),
231113908Sdes	    "\r%-45.45s%.*s%3d%% %4lu%c%c %3lu.%01d%cB/s",
232113908Sdes	    curfile,
233113908Sdes	    nspaces,
234113908Sdes	    spaces,
235113908Sdes	    ratio,
236113908Sdes	    (u_long)abbrevsize,
237113908Sdes	    prefixes[ai],
238113908Sdes	    ai == 0 ? ' ' : 'B',
239113908Sdes	    (u_long)(bytespersec / 1024),
240113908Sdes	    (int)((bytespersec % 1024) * 10 / 1024),
241113908Sdes	    prefixes[bi]
242113908Sdes	);
243113908Sdes#endif
244113908Sdes
245113908Sdes	if (cursize <= 0 || elapsed <= 0.0 || cursize > totalbytes) {
246113908Sdes		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
247113908Sdes		    "   --:-- ETA");
248113908Sdes	} else if (wait.tv_sec >= STALLTIME) {
249113908Sdes		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
250113908Sdes		    " - stalled -");
251113908Sdes	} else {
252113908Sdes		if (cursize != totalbytes)
253113908Sdes			remaining = (int)(totalbytes / (cursize / elapsed) -
254113908Sdes			    elapsed);
255113908Sdes		else
256113908Sdes			remaining = elapsed;
257113908Sdes
258113908Sdes		i = remaining / 3600;
259113908Sdes		if (i)
260113908Sdes			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
261113908Sdes			    "%2d:", i);
262113908Sdes		else
263113908Sdes			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
264113908Sdes			    "   ");
265113908Sdes		i = remaining % 3600;
266113908Sdes		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
267113908Sdes		    "%02d:%02d%s", i / 60, i % 60,
268113908Sdes		    (cursize != totalbytes) ? " ETA" : "    ");
269113908Sdes	}
270113908Sdes	atomicio(write, fileno(stdout), buf, strlen(buf));
271113908Sdes}
272113908Sdes
273113908Sdesstatic int
274113908Sdesget_tty_width(void)
275113908Sdes{
276113908Sdes	struct winsize winsize;
277113908Sdes
278113908Sdes	if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1)
279113908Sdes		return (winsize.ws_col ? winsize.ws_col : 80);
280113908Sdes	else
281113908Sdes		return (80);
282113908Sdes}
283