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