version 1.3.2.2, 2004/03/04 18:18:16 |
version 1.4, 2003/04/03 07:25:27 |
|
|
|
/* $OpenBSD$ */ |
|
|
/* |
/* |
* Copyright (c) 2003 Nils Nordman. All rights reserved. |
* Copyright (c) 1999 Theo de Raadt. All rights reserved. |
|
* Copyright (c) 1999 Aaron Campbell. All rights reserved. |
* |
* |
* Redistribution and use in source and binary forms, with or without |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* modification, are permitted provided that the following conditions |
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
*/ |
*/ |
|
|
|
/* |
|
* Parts from: |
|
* |
|
* Copyright (c) 1983, 1990, 1992, 1993, 1995 |
|
* The Regents of the University of California. All rights reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in the |
|
* documentation and/or other materials provided with the distribution. |
|
* 3. All advertising materials mentioning features or use of this software |
|
* must display the following acknowledgement: |
|
* This product includes software developed by the University of |
|
* California, Berkeley and its contributors. |
|
* 4. Neither the name of the University nor the names of its contributors |
|
* may be used to endorse or promote products derived from this software |
|
* without specific prior written permission. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
* SUCH DAMAGE. |
|
* |
|
*/ |
|
|
#include "includes.h" |
#include "includes.h" |
RCSID("$OpenBSD$"); |
RCSID("$OpenBSD$"); |
|
|
#include "progressmeter.h" |
#include <libgen.h> |
|
|
#include "atomicio.h" |
#include "atomicio.h" |
|
#include "progressmeter.h" |
|
|
#define DEFAULT_WINSIZE 80 |
/* Number of seconds before xfer considered "stalled". */ |
#define MAX_WINSIZE 512 |
#define STALLTIME 5 |
#define PADDING 1 /* padding between the progress indicators */ |
/* alarm() interval for updating progress meter. */ |
#define UPDATE_INTERVAL 1 /* update the progress meter every second */ |
#define PROGRESSTIME 1 |
#define STALL_TIME 5 /* we're stalled after this many seconds */ |
|
|
|
/* determines whether we can output to the terminal */ |
/* Signal handler used for updating the progress meter. */ |
static int can_output(void); |
static void update_progress_meter(int); |
|
|
/* formats and inserts the specified size into the given buffer */ |
/* Returns non-zero if we are the foreground process. */ |
static void format_size(char *, int, off_t); |
static int foregroundproc(void); |
static void format_rate(char *, int, off_t); |
|
|
|
/* updates the progressmeter to reflect the current state of the transfer */ |
/* Returns width of the terminal (for progress meter calculations). */ |
void refresh_progress_meter(void); |
static int get_tty_width(void); |
|
|
/* signal handler for updating the progress meter */ |
/* Visual statistics about files as they are transferred. */ |
static void update_progress_meter(int); |
static void draw_progress_meter(void); |
|
|
static time_t start; /* start progress */ |
/* Time a transfer started. */ |
static time_t last_update; /* last progress update */ |
static struct timeval start; |
static char *file; /* name of the file being transferred */ |
|
static off_t end_pos; /* ending position of transfer */ |
|
static off_t cur_pos; /* transfer position as of last refresh */ |
|
static volatile off_t *counter; /* progress counter */ |
|
static long stalled; /* how long we have been stalled */ |
|
static int bytes_per_second; /* current speed in bytes per second */ |
|
static int win_size; /* terminal window size */ |
|
|
|
/* units for format_size */ |
/* Number of bytes of current file transferred so far. */ |
static const char unit[] = " KMGT"; |
static volatile off_t *statbytes; |
|
|
static int |
/* Total size of current file. */ |
can_output(void) |
static off_t totalbytes; |
|
|
|
/* Name of current file being transferred. */ |
|
static char *curfile; |
|
|
|
/* Time of last update. */ |
|
static struct timeval lastupdate; |
|
|
|
/* Size at the time of the last update. */ |
|
static off_t lastsize; |
|
|
|
void |
|
start_progress_meter(char *file, off_t filesize, off_t *counter) |
{ |
{ |
return (getpgrp() == tcgetpgrp(STDOUT_FILENO)); |
if ((curfile = basename(file)) == NULL) |
|
curfile = file; |
|
|
|
totalbytes = filesize; |
|
statbytes = counter; |
|
(void) gettimeofday(&start, (struct timezone *) 0); |
|
lastupdate = start; |
|
lastsize = 0; |
|
|
|
draw_progress_meter(); |
|
signal(SIGALRM, update_progress_meter); |
|
alarm(PROGRESSTIME); |
} |
} |
|
|
|
void |
|
stop_progress_meter() |
|
{ |
|
alarm(0); |
|
draw_progress_meter(); |
|
if (foregroundproc() != 0) |
|
atomicio(write, fileno(stdout), "\n", 1); |
|
} |
|
|
static void |
static void |
format_rate(char *buf, int size, off_t bytes) |
update_progress_meter(int ignore) |
{ |
{ |
int i; |
int save_errno = errno; |
|
|
bytes *= 100; |
draw_progress_meter(); |
for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++) |
signal(SIGALRM, update_progress_meter); |
bytes = (bytes + 512) / 1024; |
alarm(PROGRESSTIME); |
if (i == 0) { |
errno = save_errno; |
i++; |
|
bytes = (bytes + 512) / 1024; |
|
} |
|
snprintf(buf, size, "%3lld.%1lld%c%s", |
|
(long long) (bytes + 5) / 100, |
|
(long long) (bytes + 5) / 10 % 10, |
|
unit[i], |
|
i ? "B" : " "); |
|
} |
} |
|
|
static void |
static int |
format_size(char *buf, int size, off_t bytes) |
foregroundproc(void) |
{ |
{ |
int i; |
static pid_t pgrp = -1; |
|
int ctty_pgrp; |
|
|
for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++) |
if (pgrp == -1) |
bytes = (bytes + 512) / 1024; |
pgrp = getpgrp(); |
snprintf(buf, size, "%4lld%c%s", |
|
(long long) bytes, |
return ((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 && |
unit[i], |
ctty_pgrp == pgrp)); |
i ? "B" : " "); |
|
} |
} |
|
|
void |
static void |
refresh_progress_meter(void) |
draw_progress_meter() |
{ |
{ |
char buf[MAX_WINSIZE + 1]; |
static const char spaces[] = " " |
time_t now; |
" " |
off_t transferred; |
" " |
|
" " |
|
" " |
|
" "; |
|
static const char prefixes[] = " KMGTP"; |
|
struct timeval now, td, wait; |
|
off_t cursize, abbrevsize, bytespersec; |
double elapsed; |
double elapsed; |
int percent; |
int ratio, remaining, i, ai, bi, nspaces; |
off_t bytes_left; |
char buf[512]; |
int cur_speed; |
|
int hours, minutes, seconds; |
|
int i, len; |
|
int file_len; |
|
|
|
transferred = *counter - cur_pos; |
if (foregroundproc() == 0) |
cur_pos = *counter; |
return; |
now = time(NULL); |
|
bytes_left = end_pos - cur_pos; |
|
|
|
if (bytes_left > 0) |
(void) gettimeofday(&now, (struct timezone *) 0); |
elapsed = now - last_update; |
cursize = *statbytes; |
else { |
if (totalbytes != 0) { |
elapsed = now - start; |
ratio = 100.0 * cursize / totalbytes; |
/* Calculate true total speed when done */ |
ratio = MAX(ratio, 0); |
transferred = end_pos; |
ratio = MIN(ratio, 100); |
bytes_per_second = 0; |
} else |
} |
ratio = 100; |
|
|
/* calculate speed */ |
abbrevsize = cursize; |
if (elapsed != 0) |
for (ai = 0; abbrevsize >= 10000 && ai < sizeof(prefixes); ai++) |
cur_speed = (transferred / elapsed); |
abbrevsize >>= 10; |
else |
|
cur_speed = transferred; |
|
|
|
#define AGE_FACTOR 0.9 |
timersub(&now, &lastupdate, &wait); |
if (bytes_per_second != 0) { |
if (cursize > lastsize) { |
bytes_per_second = (bytes_per_second * AGE_FACTOR) + |
lastupdate = now; |
(cur_speed * (1.0 - AGE_FACTOR)); |
lastsize = cursize; |
} else |
wait.tv_sec = 0; |
bytes_per_second = cur_speed; |
} |
|
timersub(&now, &start, &td); |
|
elapsed = td.tv_sec + (td.tv_usec / 1000000.0); |
|
|
/* filename */ |
bytespersec = 0; |
buf[0] = '\0'; |
if (cursize > 0) { |
file_len = win_size - 35; |
bytespersec = cursize; |
if (file_len > 0) { |
if (elapsed > 0.0) |
len = snprintf(buf, file_len + 1, "\r%s", file); |
bytespersec /= elapsed; |
if (len < 0) |
|
len = 0; |
|
for (i = len; i < file_len; i++ ) |
|
buf[i] = ' '; |
|
buf[file_len] = '\0'; |
|
} |
} |
|
for (bi = 1; bytespersec >= 1024000 && bi < sizeof(prefixes); bi++) |
|
bytespersec >>= 10; |
|
|
/* percent of transfer done */ |
nspaces = MIN(get_tty_width() - 79, sizeof(spaces) - 1); |
if (end_pos != 0) |
|
percent = ((float)cur_pos / end_pos) * 100; |
|
else |
|
percent = 100; |
|
snprintf(buf + strlen(buf), win_size - strlen(buf), |
|
" %3d%% ", percent); |
|
|
|
/* amount transferred */ |
snprintf(buf, sizeof(buf), |
format_size(buf + strlen(buf), win_size - strlen(buf), |
"\r%-45.45s%.*s%3d%% %4lld%c%c %3lld.%01d%cB/s", |
cur_pos); |
curfile, |
strlcat(buf, " ", win_size); |
nspaces, |
|
spaces, |
|
ratio, |
|
(long long)abbrevsize, |
|
prefixes[ai], |
|
ai == 0 ? ' ' : 'B', |
|
(long long)(bytespersec / 1024), |
|
(int)((bytespersec % 1024) * 10 / 1024), |
|
prefixes[bi] |
|
); |
|
|
/* bandwidth usage */ |
if (cursize <= 0 || elapsed <= 0.0 || cursize > totalbytes) { |
format_rate(buf + strlen(buf), win_size - strlen(buf), |
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), |
bytes_per_second); |
" --:-- ETA"); |
strlcat(buf, "/s ", win_size); |
} else if (wait.tv_sec >= STALLTIME) { |
|
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), |
/* ETA */ |
" - stalled -"); |
if (!transferred) |
} else { |
stalled += elapsed; |
if (cursize != totalbytes) |
else |
remaining = (int)(totalbytes / (cursize / elapsed) - |
stalled = 0; |
elapsed); |
|
|
if (stalled >= STALL_TIME) |
|
strlcat(buf, "- stalled -", win_size); |
|
else if (bytes_per_second == 0 && bytes_left) |
|
strlcat(buf, " --:-- ETA", win_size); |
|
else { |
|
if (bytes_left > 0) |
|
seconds = bytes_left / bytes_per_second; |
|
else |
else |
seconds = elapsed; |
remaining = elapsed; |
|
|
hours = seconds / 3600; |
i = remaining / 3600; |
seconds -= hours * 3600; |
if (i) |
minutes = seconds / 60; |
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), |
seconds -= minutes * 60; |
"%2d:", i); |
|
|
if (hours != 0) |
|
snprintf(buf + strlen(buf), win_size - strlen(buf), |
|
"%d:%02d:%02d", hours, minutes, seconds); |
|
else |
else |
snprintf(buf + strlen(buf), win_size - strlen(buf), |
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), |
" %02d:%02d", minutes, seconds); |
" "); |
|
i = remaining % 3600; |
if (bytes_left > 0) |
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), |
strlcat(buf, " ETA", win_size); |
"%02d:%02d%s", i / 60, i % 60, |
else |
(cursize != totalbytes) ? " ETA" : " "); |
strlcat(buf, " ", win_size); |
|
} |
} |
|
atomicio(write, fileno(stdout), buf, strlen(buf)); |
atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1); |
|
last_update = now; |
|
} |
} |
|
|
static void |
static int |
update_progress_meter(int ignore) |
get_tty_width(void) |
{ |
{ |
int save_errno; |
|
|
|
save_errno = errno; |
|
|
|
if (can_output()) |
|
refresh_progress_meter(); |
|
|
|
signal(SIGALRM, update_progress_meter); |
|
alarm(UPDATE_INTERVAL); |
|
errno = save_errno; |
|
} |
|
|
|
void |
|
start_progress_meter(char *f, off_t filesize, off_t *stat) |
|
{ |
|
struct winsize winsize; |
struct winsize winsize; |
|
|
start = last_update = time(NULL); |
if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1) |
file = f; |
return (winsize.ws_col ? winsize.ws_col : 80); |
end_pos = filesize; |
else |
cur_pos = 0; |
return (80); |
counter = stat; |
|
stalled = 0; |
|
bytes_per_second = 0; |
|
|
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 && |
|
winsize.ws_col != 0) { |
|
if (winsize.ws_col > MAX_WINSIZE) |
|
win_size = MAX_WINSIZE; |
|
else |
|
win_size = winsize.ws_col; |
|
} else |
|
win_size = DEFAULT_WINSIZE; |
|
win_size += 1; /* trailing \0 */ |
|
|
|
if (can_output()) |
|
refresh_progress_meter(); |
|
|
|
signal(SIGALRM, update_progress_meter); |
|
alarm(UPDATE_INTERVAL); |
|
} |
|
|
|
void |
|
stop_progress_meter(void) |
|
{ |
|
alarm(0); |
|
|
|
if (!can_output()) |
|
return; |
|
|
|
/* Ensure we complete the progress */ |
|
if (cur_pos != end_pos) |
|
refresh_progress_meter(); |
|
|
|
atomicio(vwrite, STDOUT_FILENO, "\n", 1); |
|
} |
} |