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