#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: progressbar.c,v 1.27 2024/10/04 18:06:19 christos Exp $");
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <tzfile.h>
#include <unistd.h>
#include "progressbar.h"
#if !defined(NO_PROGRESS)
int
foregroundproc(void)
{
static pid_t pgrp = -1;
if (pgrp == -1)
pgrp = getpgrp();
return (tcgetpgrp(fileno(ttyout)) == pgrp);
}
#endif
static void updateprogressmeter(int);
static void
updateprogressmeter(int dummy __unused)
{
int oerrno = errno;
progressmeter(0);
errno = oerrno;
}
#if !defined(NO_PROGRESS) || !defined(STANDALONE_PROGRESS)
static const char * const suffixes[] = {
"",
"KiB",
"MiB",
"GiB",
"TiB",
"PiB",
"EiB",
#if 0
"ZiB",
"YiB",
#endif
};
#define NSUFFIXES (int)(sizeof(suffixes) / sizeof(suffixes[0]))
#endif
static struct timeval start;
static struct timeval lastupdate;
#define BUFLEFT (sizeof(buf) - len)
void
progressmeter(int flag)
{
static uint64_t lastsize;
uint64_t cursize;
struct timeval now, wait;
#ifndef NO_PROGRESS
struct timeval td;
uint64_t abbrevsize, bytespersec;
double elapsed;
int ratio, i, remaining, barlength;
#endif
#if !defined(NO_PROGRESS) || !defined(STANDALONE_PROGRESS)
size_t len;
char buf[256];
#endif
#ifndef NO_PROGRESS
#define BAROVERHEAD 45
static const char stars[] =
"*****************************************************************************"
"*****************************************************************************"
"*****************************************************************************";
#endif
if (flag == -1) {
(void)gettimeofday(&start, NULL);
lastupdate = start;
lastsize = restart_point;
}
(void)gettimeofday(&now, NULL);
cursize = bytes + restart_point;
timersub(&now, &lastupdate, &wait);
if (cursize > lastsize) {
lastupdate = now;
lastsize = cursize;
wait.tv_sec = 0;
} else {
#ifndef STANDALONE_PROGRESS
if (quit_time > 0 && wait.tv_sec > quit_time) {
len = snprintf(buf, sizeof(buf), "\r\n%s: "
"transfer aborted because stalled for %lu sec.\r\n",
getprogname(), (unsigned long)wait.tv_sec);
(void)write(fileno(ttyout), buf, len);
alarmtimer(0);
(void)xsignal(SIGALRM, SIG_DFL);
siglongjmp(toplevel, 1);
}
#endif
}
#ifdef STANDALONE_PROGRESS
if (progress) {
#else
if (quit_time > 0 || progress) {
#endif
if (flag == -1) {
(void)xsignal(SIGALRM, updateprogressmeter);
alarmtimer(1);
} else if (flag == 1) {
alarmtimer(0);
(void)xsignal(SIGALRM, SIG_DFL);
}
}
#ifndef NO_PROGRESS
if (!progress)
return;
len = 0;
if (! foregroundproc())
return;
len += snprintf(buf + len, BUFLEFT, "\r");
if (prefix)
len += snprintf(buf + len, BUFLEFT, "%s", prefix);
if (filesize > 0) {
ratio = (int)((double)cursize * 100.0 / (double)filesize);
ratio = MAX(ratio, 0);
ratio = MIN(ratio, 100);
len += snprintf(buf + len, BUFLEFT, "%3d%% ", ratio);
barlength = MIN((int)(sizeof(buf) - 1), ttywidth) - BAROVERHEAD;
if (prefix)
barlength -= (int)strlen(prefix);
if (barlength > 0) {
i = barlength * ratio / 100;
len += snprintf(buf + len, BUFLEFT,
"|%.*s%*s|", i, stars, (int)(barlength - i), "");
}
}
abbrevsize = cursize;
for (i = 0; abbrevsize >= 100000 && i < NSUFFIXES; i++)
abbrevsize >>= 10;
if (i == NSUFFIXES)
i--;
len += snprintf(buf + len, BUFLEFT, " " LLFP("5") " %-3s ",
(LLT)abbrevsize,
suffixes[i]);
timersub(&now, &start, &td);
elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
bytespersec = 0;
if (bytes > 0) {
bytespersec = bytes;
if (elapsed > 0.0)
bytespersec /= elapsed;
}
for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++)
bytespersec >>= 10;
len += snprintf(buf + len, BUFLEFT,
" " LLFP("3") ".%02d %.2sB/s ",
(LLT)(bytespersec / 1024),
(int)((bytespersec % 1024) * 100 / 1024),
suffixes[i]);
if (filesize > 0) {
if (bytes <= 0 || elapsed <= 0.0 || cursize > (uint64_t)filesize) {
len += snprintf(buf + len, BUFLEFT, " --:-- ETA");
} else if (wait.tv_sec >= STALLTIME) {
len += snprintf(buf + len, BUFLEFT, " - stalled -");
} else {
remaining = (int)
((filesize - restart_point) / (bytes / elapsed) -
elapsed);
if (remaining >= 100 * SECSPERHOUR)
len += snprintf(buf + len, BUFLEFT,
" --:-- ETA");
else {
i = remaining / SECSPERHOUR;
if (i)
len += snprintf(buf + len, BUFLEFT,
"%2d:", i);
else
len += snprintf(buf + len, BUFLEFT,
" ");
i = remaining % SECSPERHOUR;
len += snprintf(buf + len, BUFLEFT,
"%02d:%02d ETA", i / 60, i % 60);
}
}
}
if (flag == 1)
len += snprintf(buf + len, BUFLEFT, "\n");
(void)write(fileno(ttyout), buf, len);
#endif
}
#ifndef STANDALONE_PROGRESS
void
ptransfer(int siginfo)
{
struct timeval now, td, wait;
double elapsed;
off_t bytespersec;
int remaining, hh, i;
size_t len;
char buf[256];
if (!verbose && !progress && !siginfo)
return;
(void)gettimeofday(&now, NULL);
timersub(&now, &start, &td);
elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
bytespersec = 0;
if (bytes > 0) {
bytespersec = bytes;
if (elapsed > 0.0)
bytespersec /= elapsed;
}
len = 0;
len += snprintf(buf + len, BUFLEFT, LLF " byte%s %s in ",
(LLT)bytes, bytes == 1 ? "" : "s", direction);
remaining = (int)elapsed;
if (remaining > SECSPERDAY) {
int days;
days = remaining / SECSPERDAY;
remaining %= SECSPERDAY;
len += snprintf(buf + len, BUFLEFT,
"%d day%s ", days, days == 1 ? "" : "s");
}
hh = remaining / SECSPERHOUR;
remaining %= SECSPERHOUR;
if (hh)
len += snprintf(buf + len, BUFLEFT, "%2d:", hh);
len += snprintf(buf + len, BUFLEFT,
"%02d:%02d ", remaining / 60, remaining % 60);
for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++)
bytespersec >>= 10;
if (i == NSUFFIXES)
i--;
len += snprintf(buf + len, BUFLEFT, "(" LLF ".%02d %.2sB/s)",
(LLT)(bytespersec / 1024),
(int)((bytespersec % 1024) * 100 / 1024),
suffixes[i]);
if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0
&& bytes + restart_point <= filesize) {
remaining = (int)((filesize - restart_point) /
(bytes / elapsed) - elapsed);
hh = remaining / SECSPERHOUR;
remaining %= SECSPERHOUR;
len += snprintf(buf + len, BUFLEFT, " ETA: ");
if (hh)
len += snprintf(buf + len, BUFLEFT, "%2d:", hh);
len += snprintf(buf + len, BUFLEFT, "%02d:%02d",
remaining / 60, remaining % 60);
timersub(&now, &lastupdate, &wait);
if (wait.tv_sec >= STALLTIME)
len += snprintf(buf + len, BUFLEFT, " (stalled)");
}
len += snprintf(buf + len, BUFLEFT, "\n");
(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, len);
}
void
psummary(int notused __unused)
{
int oerrno = errno;
if (bytes > 0) {
if (fromatty)
write(fileno(ttyout), "\n", 1);
ptransfer(1);
}
errno = oerrno;
}
#endif
void
alarmtimer(int wait)
{
struct itimerval itv;
itv.it_value.tv_sec = wait;
itv.it_value.tv_usec = 0;
itv.it_interval = itv.it_value;
setitimer(ITIMER_REAL, &itv, NULL);
}
sigfunc
xsignal(int sig, sigfunc func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
#if defined(SA_INTERRUPT)
act.sa_flags = SA_INTERRUPT;
#endif
if (sigaction(sig, &act, &oact) < 0)
return (SIG_ERR);
return (oact.sa_handler);
}