summaryrefslogtreecommitdiff
path: root/progressmeter.c
diff options
context:
space:
mode:
Diffstat (limited to 'progressmeter.c')
-rw-r--r--progressmeter.c423
1 files changed, 220 insertions, 203 deletions
diff --git a/progressmeter.c b/progressmeter.c
index c315464ee..90eb97f37 100644
--- a/progressmeter.c
+++ b/progressmeter.c
@@ -1,5 +1,6 @@
1/* 1/*
2 * Copyright (c) 2003 Nils Nordman. All rights reserved. 2 * Copyright (c) 1999 Theo de Raadt. All rights reserved.
3 * Copyright (c) 1999 Aaron Campbell. All rights reserved.
3 * 4 *
4 * Redistribution and use in source and binary forms, with or without 5 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions 6 * modification, are permitted provided that the following conditions
@@ -22,244 +23,260 @@
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */ 24 */
24 25
25#include "includes.h" 26/*
26RCSID("$OpenBSD: progressmeter.c,v 1.15 2003/08/31 12:14:22 markus Exp $"); 27 * Parts from:
27 28 *
28#include "progressmeter.h" 29 * Copyright (c) 1983, 1990, 1992, 1993, 1995
29#include "atomicio.h" 30 * The Regents of the University of California. All rights reserved.
30#include "misc.h" 31 *
32 * Redistribution and use in source and binary forms, with or without
33 * modification, are permitted provided that the following conditions
34 * are met:
35 * 1. Redistributions of source code must retain the above copyright
36 * notice, this list of conditions and the following disclaimer.
37 * 2. Redistributions in binary form must reproduce the above copyright
38 * notice, this list of conditions and the following disclaimer in the
39 * documentation and/or other materials provided with the distribution.
40 * 3. All advertising materials mentioning features or use of this software
41 * must display the following acknowledgement:
42 * This product includes software developed by the University of
43 * California, Berkeley and its contributors.
44 * 4. Neither the name of the University nor the names of its contributors
45 * may be used to endorse or promote products derived from this software
46 * without specific prior written permission.
47 *
48 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
49 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
50 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
51 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
52 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
53 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
54 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
55 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
56 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
57 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
58 * SUCH DAMAGE.
59 *
60 */
31 61
32#define DEFAULT_WINSIZE 80 62#include "includes.h"
33#define MAX_WINSIZE 512 63RCSID("$OpenBSD: progressmeter.c,v 1.3 2003/03/17 10:38:38 markus Exp $");
34#define PADDING 1 /* padding between the progress indicators */
35#define UPDATE_INTERVAL 1 /* update the progress meter every second */
36#define STALL_TIME 5 /* we're stalled after this many seconds */
37 64
38/* determines whether we can output to the terminal */ 65#ifdef HAVE_LIBGEN_H
39static int can_output(void); 66#include <libgen.h>
67#endif
40 68
41/* formats and inserts the specified size into the given buffer */ 69#include "atomicio.h"
42static void format_size(char *, int, off_t); 70#include "progressmeter.h"
43static void format_rate(char *, int, off_t);
44 71
45/* updates the progressmeter to reflect the current state of the transfer */ 72/* Number of seconds before xfer considered "stalled". */
46void refresh_progress_meter(void); 73#define STALLTIME 5
74/* alarm() interval for updating progress meter. */
75#define PROGRESSTIME 1
47 76
48/* signal handler for updating the progress meter */ 77/* Signal handler used for updating the progress meter. */
49static void update_progress_meter(int); 78static void update_progress_meter(int);
50 79
51static time_t start; /* start progress */ 80/* Returns non-zero if we are the foreground process. */
52static time_t last_update; /* last progress update */ 81static int foregroundproc(void);
53static char *file; /* name of the file being transferred */
54static off_t end_pos; /* ending position of transfer */
55static off_t cur_pos; /* transfer position as of last refresh */
56static volatile off_t *counter; /* progress counter */
57static long stalled; /* how long we have been stalled */
58static int bytes_per_second; /* current speed in bytes per second */
59static int win_size; /* terminal window size */
60 82
61/* units for format_size */ 83/* Returns width of the terminal (for progress meter calculations). */
62static const char unit[] = " KMGT"; 84static int get_tty_width(void);
63 85
64static int 86/* Visual statistics about files as they are transferred. */
65can_output(void) 87static void draw_progress_meter(void);
66{
67 return (getpgrp() == tcgetpgrp(STDOUT_FILENO));
68}
69 88
70static void 89/* Time a transfer started. */
71format_rate(char *buf, int size, off_t bytes) 90static struct timeval start;
72{
73 int i;
74
75 bytes *= 100;
76 for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++)
77 bytes = (bytes + 512) / 1024;
78 if (i == 0) {
79 i++;
80 bytes = (bytes + 512) / 1024;
81 }
82 snprintf(buf, size, "%3lld.%1lld%c%s",
83 (int64_t) bytes / 100,
84 (int64_t) (bytes + 5) / 10 % 10,
85 unit[i],
86 i ? "B" : " ");
87}
88 91
89static void 92/* Number of bytes of current file transferred so far. */
90format_size(char *buf, int size, off_t bytes) 93static volatile off_t *statbytes;
91{
92 int i;
93
94 for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++)
95 bytes = (bytes + 512) / 1024;
96 snprintf(buf, size, "%4lld%c%s",
97 (int64_t) bytes,
98 unit[i],
99 i ? "B" : " ");
100}
101 94
102void 95/* Total size of current file. */
103refresh_progress_meter(void) 96static off_t totalbytes;
104{
105 char buf[MAX_WINSIZE + 1];
106 time_t now;
107 off_t transferred;
108 double elapsed;
109 int percent;
110 int bytes_left;
111 int cur_speed;
112 int hours, minutes, seconds;
113 int i, len;
114 int file_len;
115
116 transferred = *counter - cur_pos;
117 cur_pos = *counter;
118 now = time(NULL);
119 bytes_left = end_pos - cur_pos;
120
121 if (bytes_left > 0)
122 elapsed = now - last_update;
123 else
124 elapsed = now - start;
125 97
126 /* calculate speed */ 98/* Name of current file being transferred. */
127 if (elapsed != 0) 99static char *curfile;
128 cur_speed = (transferred / elapsed);
129 else
130 cur_speed = 0;
131 100
132#define AGE_FACTOR 0.9 101/* Time of last update. */
133 if (bytes_per_second != 0) { 102static struct timeval lastupdate;
134 bytes_per_second = (bytes_per_second * AGE_FACTOR) +
135 (cur_speed * (1.0 - AGE_FACTOR));
136 } else
137 bytes_per_second = cur_speed;
138
139 /* filename */
140 buf[0] = '\0';
141 file_len = win_size - 35;
142 if (file_len > 0) {
143 len = snprintf(buf, file_len + 1, "\r%s", file);
144 if (len < 0)
145 len = 0;
146 for (i = len; i < file_len; i++ )
147 buf[i] = ' ';
148 buf[file_len] = '\0';
149 }
150 103
151 /* percent of transfer done */ 104/* Size at the time of the last update. */
152 if (end_pos != 0) 105static off_t lastsize;
153 percent = ((float)cur_pos / end_pos) * 100;
154 else
155 percent = 100;
156 snprintf(buf + strlen(buf), win_size - strlen(buf),
157 " %3d%% ", percent);
158
159 /* amount transferred */
160 format_size(buf + strlen(buf), win_size - strlen(buf),
161 cur_pos);
162 strlcat(buf, " ", win_size);
163
164 /* bandwidth usage */
165 format_rate(buf + strlen(buf), win_size - strlen(buf),
166 bytes_per_second);
167 strlcat(buf, "/s ", win_size);
168
169 /* ETA */
170 if (!transferred)
171 stalled += elapsed;
172 else
173 stalled = 0;
174
175 if (stalled >= STALL_TIME)
176 strlcat(buf, "- stalled -", win_size);
177 else if (bytes_per_second == 0 && bytes_left)
178 strlcat(buf, " --:-- ETA", win_size);
179 else {
180 if (bytes_left > 0)
181 seconds = bytes_left / bytes_per_second;
182 else
183 seconds = elapsed;
184 106
185 hours = seconds / 3600; 107void
186 seconds -= hours * 3600; 108start_progress_meter(char *file, off_t filesize, off_t *counter)
187 minutes = seconds / 60; 109{
188 seconds -= minutes * 60; 110 if ((curfile = basename(file)) == NULL)
111 curfile = file;
189 112
190 if (hours != 0) 113 totalbytes = filesize;
191 snprintf(buf + strlen(buf), win_size - strlen(buf), 114 statbytes = counter;
192 "%d:%02d:%02d", hours, minutes, seconds); 115 (void) gettimeofday(&start, (struct timezone *) 0);
193 else 116 lastupdate = start;
194 snprintf(buf + strlen(buf), win_size - strlen(buf), 117 lastsize = 0;
195 " %02d:%02d", minutes, seconds);
196 118
197 if (bytes_left > 0) 119 draw_progress_meter();
198 strlcat(buf, " ETA", win_size); 120 signal(SIGALRM, update_progress_meter);
199 else 121 alarm(PROGRESSTIME);
200 strlcat(buf, " ", win_size); 122}
201 }
202 123
203 atomicio(vwrite, STDOUT_FILENO, buf, win_size); 124void
204 last_update = now; 125stop_progress_meter()
126{
127 alarm(0);
128 draw_progress_meter();
129 if (foregroundproc() != 0)
130 atomicio(write, fileno(stdout), "\n", 1);
205} 131}
206 132
207static void 133static void
208update_progress_meter(int ignore) 134update_progress_meter(int ignore)
209{ 135{
210 int save_errno; 136 int save_errno = errno;
211
212 save_errno = errno;
213
214 if (can_output())
215 refresh_progress_meter();
216 137
138 draw_progress_meter();
217 signal(SIGALRM, update_progress_meter); 139 signal(SIGALRM, update_progress_meter);
218 alarm(UPDATE_INTERVAL); 140 alarm(PROGRESSTIME);
219 errno = save_errno; 141 errno = save_errno;
220} 142}
221 143
222void 144static int
223start_progress_meter(char *f, off_t filesize, off_t *stat) 145foregroundproc(void)
224{ 146{
225 struct winsize winsize; 147 static pid_t pgrp = -1;
148 int ctty_pgrp;
149
150 if (pgrp == -1)
151 pgrp = getpgrp();
152
153#ifdef HAVE_TCGETPGRP
154 return ((ctty_pgrp = tcgetpgrp(STDOUT_FILENO)) != -1 &&
155 ctty_pgrp == pgrp);
156#else
157 return ((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
158 ctty_pgrp == pgrp));
159#endif
160}
226 161
227 start = last_update = time(NULL); 162static void
228 file = f; 163draw_progress_meter()
229 end_pos = filesize; 164{
230 cur_pos = 0; 165 static const char spaces[] = " "
231 counter = stat; 166 " "
232 stalled = 0; 167 " "
233 bytes_per_second = 0; 168 " "
234 169 " "
235 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 && 170 " ";
236 winsize.ws_col != 0) { 171 static const char prefixes[] = " KMGTP";
237 if (winsize.ws_col > MAX_WINSIZE) 172 struct timeval now, td, wait;
238 win_size = MAX_WINSIZE; 173 off_t cursize, abbrevsize, bytespersec;
239 else 174 double elapsed;
240 win_size = winsize.ws_col; 175 int ratio, remaining, i, ai, bi, nspaces;
176 char buf[512];
177
178 if (foregroundproc() == 0)
179 return;
180
181 (void) gettimeofday(&now, (struct timezone *) 0);
182 cursize = *statbytes;
183 if (totalbytes != 0) {
184 ratio = 100.0 * cursize / totalbytes;
185 ratio = MAX(ratio, 0);
186 ratio = MIN(ratio, 100);
241 } else 187 } else
242 win_size = DEFAULT_WINSIZE; 188 ratio = 100;
243 win_size += 1; /* trailing \0 */
244 189
245 if (can_output()) 190 abbrevsize = cursize;
246 refresh_progress_meter(); 191 for (ai = 0; abbrevsize >= 10000 && ai < sizeof(prefixes); ai++)
192 abbrevsize >>= 10;
247 193
248 signal(SIGALRM, update_progress_meter); 194 timersub(&now, &lastupdate, &wait);
249 alarm(UPDATE_INTERVAL); 195 if (cursize > lastsize) {
196 lastupdate = now;
197 lastsize = cursize;
198 wait.tv_sec = 0;
199 }
200 timersub(&now, &start, &td);
201 elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
202
203 bytespersec = 0;
204 if (cursize > 0) {
205 bytespersec = cursize;
206 if (elapsed > 0.0)
207 bytespersec /= elapsed;
208 }
209 for (bi = 1; bytespersec >= 1024000 && bi < sizeof(prefixes); bi++)
210 bytespersec >>= 10;
211
212 nspaces = MIN(get_tty_width() - 79, sizeof(spaces) - 1);
213
214#ifdef HAVE_LONG_LONG_INT
215 snprintf(buf, sizeof(buf),
216 "\r%-45.45s%.*s%3d%% %4lld%c%c %3lld.%01d%cB/s",
217 curfile,
218 nspaces,
219 spaces,
220 ratio,
221 (long long)abbrevsize,
222 prefixes[ai],
223 ai == 0 ? ' ' : 'B',
224 (long long)(bytespersec / 1024),
225 (int)((bytespersec % 1024) * 10 / 1024),
226 prefixes[bi]
227 );
228#else
229 /* XXX: Handle integer overflow? */
230 snprintf(buf, sizeof(buf),
231 "\r%-45.45s%.*s%3d%% %4lu%c%c %3lu.%01d%cB/s",
232 curfile,
233 nspaces,
234 spaces,
235 ratio,
236 (u_long)abbrevsize,
237 prefixes[ai],
238 ai == 0 ? ' ' : 'B',
239 (u_long)(bytespersec / 1024),
240 (int)((bytespersec % 1024) * 10 / 1024),
241 prefixes[bi]
242 );
243#endif
244
245 if (cursize <= 0 || elapsed <= 0.0 || cursize > totalbytes) {
246 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
247 " --:-- ETA");
248 } else if (wait.tv_sec >= STALLTIME) {
249 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
250 " - stalled -");
251 } else {
252 if (cursize != totalbytes)
253 remaining = (int)(totalbytes / (cursize / elapsed) -
254 elapsed);
255 else
256 remaining = elapsed;
257
258 i = remaining / 3600;
259 if (i)
260 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
261 "%2d:", i);
262 else
263 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
264 " ");
265 i = remaining % 3600;
266 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
267 "%02d:%02d%s", i / 60, i % 60,
268 (cursize != totalbytes) ? " ETA" : " ");
269 }
270 atomicio(write, fileno(stdout), buf, strlen(buf));
250} 271}
251 272
252void 273static int
253stop_progress_meter(void) 274get_tty_width(void)
254{ 275{
255 alarm(0); 276 struct winsize winsize;
256
257 if (!can_output())
258 return;
259
260 /* Ensure we complete the progress */
261 if (cur_pos != end_pos)
262 refresh_progress_meter();
263 277
264 atomicio(vwrite, STDOUT_FILENO, "\n", 1); 278 if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1)
279 return (winsize.ws_col ? winsize.ws_col : 80);
280 else
281 return (80);
265} 282}