Annotation of src/usr.bin/ftp/progressmeter.c, Revision 1.5
1.5 ! florian 1: /* $OpenBSD: progressmeter.c,v 1.4 2019/05/14 18:51:07 deraadt Exp $ */
1.2 jasper 2:
1.1 kmos 3: /*
4: * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org>
5: * Copyright (c) 2003 Nils Nordman. All rights reserved.
6: *
7: * Redistribution and use in source and binary forms, with or without
8: * modification, are permitted provided that the following conditions
9: * are met:
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. Redistributions in binary form must reproduce the above copyright
13: * notice, this list of conditions and the following disclaimer in the
14: * documentation and/or other materials provided with the distribution.
15: *
16: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26: */
27:
28: #include <sys/types.h>
29: #include <sys/ioctl.h>
30:
31: #include <err.h>
32: #include <errno.h>
33: #include <signal.h>
34: #include <stdio.h>
35: #include <string.h>
36: #include <time.h>
37: #include <unistd.h>
38:
39: #include "ftp.h"
40:
41: #define DEFAULT_WINSIZE 80
42: #define MAX_WINSIZE 512
43: #define UPDATE_INTERVAL 1 /* update the progress meter every second */
44: #define STALL_TIME 5 /* we're stalled after this many seconds */
45:
46: time_t monotime(void);
47:
48: /* formats and inserts the specified size into the given buffer */
49: static void format_size(char *, int, off_t);
50: static void format_rate(char *, int, off_t);
51:
52: /* window resizing */
53: static void sig_winch(int);
54: static void setscreensize(void);
55:
56: /* updates the progressmeter to reflect the current state of the transfer */
57: void refresh_progress_meter(void);
58:
59: /* signal handler for updating the progress meter */
60: static void update_progress_meter(int);
61:
62: static const char *title; /* short title for the start of progress bar */
63: static time_t start; /* start progress */
64: static time_t last_update; /* last progress update */
65: static off_t start_pos; /* initial position of transfer */
66: static off_t end_pos; /* ending position of transfer */
67: static off_t cur_pos; /* transfer position as of last refresh */
68: static off_t offset; /* initial offset from start_pos */
69: static volatile off_t *counter; /* progress counter */
70: static long stalled; /* how long we have been stalled */
71: static int bytes_per_second; /* current speed in bytes per second */
72: static int win_size; /* terminal window size */
73: static volatile sig_atomic_t win_resized; /* for window resizing */
74: static const char *filename; /* To be displayed in non-verbose mode */
75: /* units for format_size */
76: static const char unit[] = " KMGT";
77:
78: time_t
79: monotime(void)
80: {
81: struct timespec ts;
82:
83: if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
84: err(1, "monotime");
85:
86: return ts.tv_sec;
87: }
88:
89: static void
90: format_rate(char *buf, int size, off_t bytes)
91: {
92: int i;
93:
94: bytes *= 100;
95: for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++)
96: bytes = (bytes + 512) / 1024;
97: if (i == 0) {
98: i++;
99: bytes = (bytes + 512) / 1024;
100: }
1.5 ! florian 101: snprintf(buf, size, "%lld.%02lld %c%s",
1.1 kmos 102: (long long) (bytes + 5) / 100,
103: (long long) (bytes + 5) / 10 % 10,
104: unit[i],
105: "B");
106: }
107:
108: static void
109: format_size(char *buf, int size, off_t bytes)
110: {
111: int i;
112:
113: for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++)
114: bytes = (bytes + 512) / 1024;
115: snprintf(buf, size, "%4lld%c%s",
116: (long long) bytes,
117: unit[i],
118: i ? "B" : " ");
119: }
120:
121: void
122: refresh_progress_meter(void)
123: {
124: char buf[MAX_WINSIZE + 1];
125: const char *dot = "";
126: time_t now;
127: off_t transferred, bytes_left;
128: double elapsed;
129: int len, cur_speed, hours, minutes, seconds, barlength, i;
130: int percent, overhead = 30;
131:
132: transferred = *counter - (cur_pos ? cur_pos : start_pos);
133: cur_pos = *counter;
134: now = monotime();
135: bytes_left = end_pos - cur_pos;
136:
137: if (bytes_left > 0)
138: elapsed = now - last_update;
139: else {
140: elapsed = now - start;
141: /* Calculate true total speed when done */
142: transferred = end_pos - start_pos;
143: bytes_per_second = 0;
144: }
145:
146: /* calculate speed */
147: if (elapsed != 0)
148: cur_speed = (transferred / elapsed);
149: else
150: cur_speed = transferred;
151:
152: #define AGE_FACTOR 0.9
153: if (bytes_per_second != 0) {
154: bytes_per_second = (bytes_per_second * AGE_FACTOR) +
155: (cur_speed * (1.0 - AGE_FACTOR));
156: } else
157: bytes_per_second = cur_speed;
158:
159: buf[0] = '\0';
160: /* title */
161: if (!verbose && title != NULL) {
162: len = strlen(title);
163: if (len < 7)
164: len = 7;
165: else if (len > 12) {
166: len = 12;
167: dot = "...";
168: overhead += 3;
169: }
170: snprintf(buf, sizeof buf, "\r%-*.*s%s ", len, len, title, dot);
171: overhead += len + 1;
172: } else
173: snprintf(buf, sizeof buf, "\r");
174:
175: if (end_pos == 0 || cur_pos == end_pos)
176: percent = 100;
177: else
178: percent = ((float)cur_pos / end_pos) * 100;
179:
180: /* filename and percent */
181: if (!verbose && filename != NULL) {
182: len = strlen(filename);
183: if (len < 12)
184: len = 12;
185: else if (len > 25) {
186: len = 22;
187: dot = "...";
188: overhead += 3;
189: }
190: snprintf(buf + strlen(buf), sizeof buf - strlen(buf),
191: "%-*.*s%s %3d%% ", len, len, filename, dot, percent);
192: overhead += len + 1;
193: } else
194: snprintf(buf, sizeof buf, "\r%3d%% ", percent);
195:
196: /* bar */
197: barlength = win_size - overhead;
198: if (barlength > 0) {
199: i = barlength * percent / 100;
200: snprintf(buf + strlen(buf), sizeof buf - strlen(buf),
201: "|%.*s%*s| ", i,
202: "*******************************************************"
203: "*******************************************************"
204: "*******************************************************"
205: "*******************************************************"
206: "*******************************************************"
207: "*******************************************************"
208: "*******************************************************",
209: barlength - i, "");
210:
211: }
212:
213: /* amount transferred */
214: format_size(buf + strlen(buf), win_size - strlen(buf), cur_pos);
215: strlcat(buf, " ", win_size);
216:
217: /* ETA */
218: if (!transferred)
219: stalled += elapsed;
220: else
221: stalled = 0;
222:
223: if (stalled >= STALL_TIME)
224: strlcat(buf, "- stalled -", win_size);
225: else if (bytes_per_second == 0 && bytes_left)
226: strlcat(buf, " --:-- ETA", win_size);
227: else {
228: if (bytes_left > 0)
229: seconds = bytes_left / bytes_per_second;
230: else
231: seconds = elapsed;
232:
233: hours = seconds / 3600;
234: seconds -= hours * 3600;
235: minutes = seconds / 60;
236: seconds -= minutes * 60;
237:
238: if (hours != 0)
239: snprintf(buf + strlen(buf), win_size - strlen(buf),
240: "%d:%02d:%02d", hours, minutes, seconds);
241: else
242: snprintf(buf + strlen(buf), win_size - strlen(buf),
243: " %02d:%02d", minutes, seconds);
244:
245: if (bytes_left > 0)
246: strlcat(buf, " ETA", win_size);
247: else
248: strlcat(buf, " ", win_size);
249: }
250:
1.4 deraadt 251: fwrite(buf, strlen(buf), 1, stderr);
1.1 kmos 252: last_update = now;
253: }
254:
255: static void
256: update_progress_meter(int ignore)
257: {
258: int save_errno;
259:
260: save_errno = errno;
261:
262: if (win_resized) {
263: setscreensize();
264: win_resized = 0;
265: }
266:
267: refresh_progress_meter();
268:
269: signal(SIGALRM, update_progress_meter);
270: alarm(UPDATE_INTERVAL);
271: errno = save_errno;
272: }
273:
274: void
1.5 ! florian 275: init_stats(off_t filesize, off_t *ctr)
1.1 kmos 276: {
277: start = last_update = monotime();
278: start_pos = *ctr;
279: offset = *ctr;
280: cur_pos = 0;
281: end_pos = 0;
282: counter = ctr;
283: stalled = 0;
284: bytes_per_second = 0;
1.5 ! florian 285:
! 286: if (filesize > 0)
! 287: end_pos = filesize;
! 288: }
! 289:
! 290: void
! 291: start_progress_meter(const char *fn, const char *t)
! 292: {
1.1 kmos 293: filename = fn;
294: title = t;
295:
296: /*
297: * Suppress progressmeter if filesize isn't known when
298: * Content-Length header has bogus values.
299: */
1.5 ! florian 300:
! 301: if (end_pos == 0)
1.1 kmos 302: return;
303:
304: setscreensize();
305: refresh_progress_meter();
306:
307: signal(SIGALRM, update_progress_meter);
308: signal(SIGWINCH, sig_winch);
309: alarm(UPDATE_INTERVAL);
310: }
311:
312: void
313: stop_progress_meter(void)
314: {
315: alarm(0);
316:
317: /* Ensure we complete the progress */
318: if (end_pos && cur_pos != end_pos)
319: refresh_progress_meter();
320:
321: if (end_pos)
1.4 deraadt 322: fprintf(stderr, "\n");
1.5 ! florian 323: }
! 324:
! 325: void
! 326: finish_stats(void)
! 327: {
! 328: char rate_str[32];
! 329: double elapsed;
1.1 kmos 330:
331: if (!verbose)
332: return;
333:
334: elapsed = monotime() - start;
1.5 ! florian 335:
! 336: if (elapsed != 0)
! 337: bytes_per_second = *counter / elapsed;
! 338: else
! 339: bytes_per_second = *counter;
1.1 kmos 340:
341: format_rate(rate_str, sizeof rate_str, bytes_per_second);
1.5 ! florian 342: log_info("%lld byte%s received in %.2f seconds (%s/s)\n",
! 343: *counter, *counter != 1 ? "s" : "", elapsed, rate_str);
1.1 kmos 344: }
345:
346: static void
347: sig_winch(int sig)
348: {
349: win_resized = 1;
350: }
351:
352: static void
353: setscreensize(void)
354: {
355: struct winsize winsize;
356:
357: if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 &&
358: winsize.ws_col != 0) {
359: if (winsize.ws_col > MAX_WINSIZE)
360: win_size = MAX_WINSIZE;
361: else
362: win_size = winsize.ws_col;
363: } else
364: win_size = DEFAULT_WINSIZE;
365: win_size += 1; /* trailing \0 */
366: }