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