1/* $NetBSD: progressbar.c,v 1.24 2021/01/06 04:43:14 lukem Exp $ */ 2 3/*- 4 * Copyright (c) 1997-2021 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Luke Mewburn. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32#include <sys/cdefs.h> 33#ifndef lint 34__RCSID("$NetBSD: progressbar.c,v 1.24 2021/01/06 04:43:14 lukem Exp $"); 35#endif /* not lint */ 36 37/* 38 * FTP User Program -- Misc support routines 39 */ 40#include <sys/param.h> 41#include <sys/types.h> 42#include <sys/time.h> 43 44#include <err.h> 45#include <errno.h> 46#include <signal.h> 47#include <stdio.h> 48#include <stdlib.h> 49#include <string.h> 50#include <time.h> 51#include <tzfile.h> 52#include <unistd.h> 53 54#include "progressbar.h" 55 56#if !defined(NO_PROGRESS) 57/* 58 * return non-zero if we're the current foreground process 59 */ 60int 61foregroundproc(void) 62{ 63 static pid_t pgrp = -1; 64 65 if (pgrp == -1) 66 pgrp = getpgrp(); 67 68 return (tcgetpgrp(fileno(ttyout)) == pgrp); 69} 70#endif /* !defined(NO_PROGRESS) */ 71 72 73static void updateprogressmeter(int); 74 75/* 76 * SIGALRM handler to update the progress meter 77 */ 78static void 79updateprogressmeter(int dummy) 80{ 81 int oerrno = errno; 82 83 progressmeter(0); 84 errno = oerrno; 85} 86 87/* 88 * List of order of magnitude suffixes, per IEC 60027-2. 89 */ 90#if !defined(NO_PROGRESS) || !defined(STANDALONE_PROGRESS) 91static const char * const suffixes[] = { 92 "", /* 2^0 (byte) */ 93 "KiB", /* 2^10 Kibibyte */ 94 "MiB", /* 2^20 Mebibyte */ 95 "GiB", /* 2^30 Gibibyte */ 96 "TiB", /* 2^40 Tebibyte */ 97 "PiB", /* 2^50 Pebibyte */ 98 "EiB", /* 2^60 Exbibyte */ 99#if 0 100 /* The following are not necessary for signed 64-bit off_t */ 101 "ZiB", /* 2^70 Zebibyte */ 102 "YiB", /* 2^80 Yobibyte */ 103#endif 104}; 105#define NSUFFIXES (int)(sizeof(suffixes) / sizeof(suffixes[0])) 106#endif 107 108/* 109 * Display a transfer progress bar if progress is non-zero. 110 * SIGALRM is hijacked for use by this function. 111 * - Before the transfer, set filesize to size of file (or -1 if unknown), 112 * and call with flag = -1. This starts the once per second timer, 113 * and a call to updateprogressmeter() upon SIGALRM. 114 * - During the transfer, updateprogressmeter will call progressmeter 115 * with flag = 0 116 * - After the transfer, call with flag = 1 117 */ 118static struct timeval start; 119static struct timeval lastupdate; 120 121#define BUFLEFT (sizeof(buf) - len) 122 123void 124progressmeter(int flag) 125{ 126 static off_t lastsize; 127 off_t cursize; 128 struct timeval now, wait; 129#ifndef NO_PROGRESS 130 struct timeval td; 131 off_t abbrevsize, bytespersec; 132 double elapsed; 133 int ratio, i, remaining, barlength; 134 135 /* 136 * Work variables for progress bar. 137 * 138 * XXX: if the format of the progress bar changes 139 * (especially the number of characters in the 140 * `static' portion of it), be sure to update 141 * these appropriately. 142 */ 143#endif 144#if !defined(NO_PROGRESS) || !defined(STANDALONE_PROGRESS) 145 size_t len; 146 char buf[256]; /* workspace for progress bar */ 147#endif 148#ifndef NO_PROGRESS 149#define BAROVERHEAD 45 /* non `*' portion of progress bar */ 150 /* 151 * stars should contain at least 152 * sizeof(buf) - BAROVERHEAD entries 153 */ 154 static const char stars[] = 155"*****************************************************************************" 156"*****************************************************************************" 157"*****************************************************************************"; 158 159#endif 160 161 if (flag == -1) { 162 (void)gettimeofday(&start, NULL); 163 lastupdate = start; 164 lastsize = restart_point; 165 } 166 167 (void)gettimeofday(&now, NULL); 168 cursize = bytes + restart_point; 169 timersub(&now, &lastupdate, &wait); 170 if (cursize > lastsize) { 171 lastupdate = now; 172 lastsize = cursize; 173 wait.tv_sec = 0; 174 } else { 175#ifndef STANDALONE_PROGRESS 176 if (quit_time > 0 && wait.tv_sec > quit_time) { 177 len = snprintf(buf, sizeof(buf), "\r\n%s: " 178 "transfer aborted because stalled for %lu sec.\r\n", 179 getprogname(), (unsigned long)wait.tv_sec); 180 (void)write(fileno(ttyout), buf, len); 181 alarmtimer(0); 182 (void)xsignal(SIGALRM, SIG_DFL); 183 siglongjmp(toplevel, 1); 184 } 185#endif /* !STANDALONE_PROGRESS */ 186 } 187 /* 188 * Always set the handler even if we are not the foreground process. 189 */ 190#ifdef STANDALONE_PROGRESS 191 if (progress) { 192#else 193 if (quit_time > 0 || progress) { 194#endif /* !STANDALONE_PROGRESS */ 195 if (flag == -1) { 196 (void)xsignal(SIGALRM, updateprogressmeter); 197 alarmtimer(1); /* set alarm timer for 1 Hz */ 198 } else if (flag == 1) { 199 alarmtimer(0); 200 (void)xsignal(SIGALRM, SIG_DFL); 201 } 202 } 203#ifndef NO_PROGRESS 204 if (!progress) 205 return; 206 len = 0; 207 208 /* 209 * print progress bar only if we are foreground process. 210 */ 211 if (! foregroundproc()) 212 return; 213 214 len += snprintf(buf + len, BUFLEFT, "\r"); 215 if (prefix) 216 len += snprintf(buf + len, BUFLEFT, "%s", prefix); 217 if (filesize > 0) { 218 ratio = (int)((double)cursize * 100.0 / (double)filesize); 219 ratio = MAX(ratio, 0); 220 ratio = MIN(ratio, 100); 221 len += snprintf(buf + len, BUFLEFT, "%3d%% ", ratio); 222 223 /* 224 * calculate the length of the `*' bar, ensuring that 225 * the number of stars won't exceed the buffer size 226 */ 227 barlength = MIN((int)(sizeof(buf) - 1), ttywidth) - BAROVERHEAD; 228 if (prefix) 229 barlength -= (int)strlen(prefix); 230 if (barlength > 0) { 231 i = barlength * ratio / 100; 232 len += snprintf(buf + len, BUFLEFT, 233 "|%.*s%*s|", i, stars, (int)(barlength - i), ""); 234 } 235 } 236 237 abbrevsize = cursize; 238 for (i = 0; abbrevsize >= 100000 && i < NSUFFIXES; i++) 239 abbrevsize >>= 10; 240 if (i == NSUFFIXES) 241 i--; 242 len += snprintf(buf + len, BUFLEFT, " " LLFP("5") " %-3s ", 243 (LLT)abbrevsize, 244 suffixes[i]); 245 246 timersub(&now, &start, &td); 247 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 248 249 bytespersec = 0; 250 if (bytes > 0) { 251 bytespersec = bytes; 252 if (elapsed > 0.0) 253 bytespersec /= elapsed; 254 } 255 for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++) 256 bytespersec >>= 10; 257 len += snprintf(buf + len, BUFLEFT, 258 " " LLFP("3") ".%02d %.2sB/s ", 259 (LLT)(bytespersec / 1024), 260 (int)((bytespersec % 1024) * 100 / 1024), 261 suffixes[i]); 262 263 if (filesize > 0) { 264 if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { 265 len += snprintf(buf + len, BUFLEFT, " --:-- ETA"); 266 } else if (wait.tv_sec >= STALLTIME) { 267 len += snprintf(buf + len, BUFLEFT, " - stalled -"); 268 } else { 269 remaining = (int) 270 ((filesize - restart_point) / (bytes / elapsed) - 271 elapsed); 272 if (remaining >= 100 * SECSPERHOUR) 273 len += snprintf(buf + len, BUFLEFT, 274 " --:-- ETA"); 275 else { 276 i = remaining / SECSPERHOUR; 277 if (i) 278 len += snprintf(buf + len, BUFLEFT, 279 "%2d:", i); 280 else 281 len += snprintf(buf + len, BUFLEFT, 282 " "); 283 i = remaining % SECSPERHOUR; 284 len += snprintf(buf + len, BUFLEFT, 285 "%02d:%02d ETA", i / 60, i % 60); 286 } 287 } 288 } 289 if (flag == 1) 290 len += snprintf(buf + len, BUFLEFT, "\n"); 291 (void)write(fileno(ttyout), buf, len); 292 293#endif /* !NO_PROGRESS */ 294} 295 296#ifndef STANDALONE_PROGRESS 297/* 298 * Display transfer statistics. 299 * Requires start to be initialised by progressmeter(-1), 300 * direction to be defined by xfer routines, and filesize and bytes 301 * to be updated by xfer routines 302 * If siginfo is nonzero, an ETA is displayed, and the output goes to stderr 303 * instead of ttyout. 304 */ 305void 306ptransfer(int siginfo) 307{ 308 struct timeval now, td, wait; 309 double elapsed; 310 off_t bytespersec; 311 int remaining, hh, i; 312 size_t len; 313 314 char buf[256]; /* Work variable for transfer status. */ 315 316 if (!verbose && !progress && !siginfo) 317 return; 318 319 (void)gettimeofday(&now, NULL); 320 timersub(&now, &start, &td); 321 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 322 bytespersec = 0; 323 if (bytes > 0) { 324 bytespersec = bytes; 325 if (elapsed > 0.0) 326 bytespersec /= elapsed; 327 } 328 len = 0; 329 len += snprintf(buf + len, BUFLEFT, LLF " byte%s %s in ", 330 (LLT)bytes, bytes == 1 ? "" : "s", direction); 331 remaining = (int)elapsed; 332 if (remaining > SECSPERDAY) { 333 int days; 334 335 days = remaining / SECSPERDAY; 336 remaining %= SECSPERDAY; 337 len += snprintf(buf + len, BUFLEFT, 338 "%d day%s ", days, days == 1 ? "" : "s"); 339 } 340 hh = remaining / SECSPERHOUR; 341 remaining %= SECSPERHOUR; 342 if (hh) 343 len += snprintf(buf + len, BUFLEFT, "%2d:", hh); 344 len += snprintf(buf + len, BUFLEFT, 345 "%02d:%02d ", remaining / 60, remaining % 60); 346 347 for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++) 348 bytespersec >>= 10; 349 if (i == NSUFFIXES) 350 i--; 351 len += snprintf(buf + len, BUFLEFT, "(" LLF ".%02d %.2sB/s)", 352 (LLT)(bytespersec / 1024), 353 (int)((bytespersec % 1024) * 100 / 1024), 354 suffixes[i]); 355 356 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 357 && bytes + restart_point <= filesize) { 358 remaining = (int)((filesize - restart_point) / 359 (bytes / elapsed) - elapsed); 360 hh = remaining / SECSPERHOUR; 361 remaining %= SECSPERHOUR; 362 len += snprintf(buf + len, BUFLEFT, " ETA: "); 363 if (hh) 364 len += snprintf(buf + len, BUFLEFT, "%2d:", hh); 365 len += snprintf(buf + len, BUFLEFT, "%02d:%02d", 366 remaining / 60, remaining % 60); 367 timersub(&now, &lastupdate, &wait); 368 if (wait.tv_sec >= STALLTIME) 369 len += snprintf(buf + len, BUFLEFT, " (stalled)"); 370 } 371 len += snprintf(buf + len, BUFLEFT, "\n"); 372 (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, len); 373} 374 375/* 376 * SIG{INFO,QUIT} handler to print transfer stats if a transfer is in progress 377 */ 378void 379psummary(int notused) 380{ 381 int oerrno = errno; 382 383 if (bytes > 0) { 384 if (fromatty) 385 write(fileno(ttyout), "\n", 1); 386 ptransfer(1); 387 } 388 errno = oerrno; 389} 390#endif /* !STANDALONE_PROGRESS */ 391 392 393/* 394 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 395 */ 396void 397alarmtimer(int wait) 398{ 399 struct itimerval itv; 400 401 itv.it_value.tv_sec = wait; 402 itv.it_value.tv_usec = 0; 403 itv.it_interval = itv.it_value; 404 setitimer(ITIMER_REAL, &itv, NULL); 405} 406 407/* 408 * Install a non-restartable POSIX signal handler. 409 */ 410sigfunc 411xsignal(int sig, sigfunc func) 412{ 413 struct sigaction act, oact; 414 act.sa_handler = func; 415 416 sigemptyset(&act.sa_mask); 417 act.sa_flags = 0; 418#if defined(SA_INTERRUPT) /* SunOS 4.x */ 419 act.sa_flags = SA_INTERRUPT; 420#endif 421 if (sigaction(sig, &act, &oact) < 0) 422 return (SIG_ERR); 423 return (oact.sa_handler); 424} 425