1 /* $NetBSD: progressbar.c,v 1.22 2012/06/27 22:07:36 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c) 1997-2009 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.22 2012/06/27 22:07:36 riastradh 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 */ 60 int 61 foregroundproc(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 73 static void updateprogressmeter(int); 74 75 /* 76 * SIGALRM handler to update the progress meter 77 */ 78 static void 79 updateprogressmeter(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 static const char * const suffixes[] = { 91 "", /* 2^0 (byte) */ 92 "KiB", /* 2^10 Kibibyte */ 93 "MiB", /* 2^20 Mebibyte */ 94 "GiB", /* 2^30 Gibibyte */ 95 "TiB", /* 2^40 Tebibyte */ 96 "PiB", /* 2^50 Pebibyte */ 97 "EiB", /* 2^60 Exbibyte */ 98 #if 0 99 /* The following are not necessary for signed 64-bit off_t */ 100 "ZiB", /* 2^70 Zebibyte */ 101 "YiB", /* 2^80 Yobibyte */ 102 #endif 103 }; 104 #define NSUFFIXES (int)(sizeof(suffixes) / sizeof(suffixes[0])) 105 106 /* 107 * Display a transfer progress bar if progress is non-zero. 108 * SIGALRM is hijacked for use by this function. 109 * - Before the transfer, set filesize to size of file (or -1 if unknown), 110 * and call with flag = -1. This starts the once per second timer, 111 * and a call to updateprogressmeter() upon SIGALRM. 112 * - During the transfer, updateprogressmeter will call progressmeter 113 * with flag = 0 114 * - After the transfer, call with flag = 1 115 */ 116 static struct timeval start; 117 static struct timeval lastupdate; 118 119 #define BUFLEFT (sizeof(buf) - len) 120 121 void 122 progressmeter(int flag) 123 { 124 static off_t lastsize; 125 off_t cursize; 126 struct timeval now, wait; 127 #ifndef NO_PROGRESS 128 struct timeval td; 129 off_t abbrevsize, bytespersec; 130 double elapsed; 131 int ratio, i, remaining, barlength; 132 133 /* 134 * Work variables for progress bar. 135 * 136 * XXX: if the format of the progress bar changes 137 * (especially the number of characters in the 138 * `static' portion of it), be sure to update 139 * these appropriately. 140 */ 141 #endif 142 size_t len; 143 char buf[256]; /* workspace for progress bar */ 144 #ifndef NO_PROGRESS 145 #define BAROVERHEAD 45 /* non `*' portion of progress bar */ 146 /* 147 * stars should contain at least 148 * sizeof(buf) - BAROVERHEAD entries 149 */ 150 static const char stars[] = 151 "*****************************************************************************" 152 "*****************************************************************************" 153 "*****************************************************************************"; 154 155 #endif 156 157 if (flag == -1) { 158 (void)gettimeofday(&start, NULL); 159 lastupdate = start; 160 lastsize = restart_point; 161 } 162 163 (void)gettimeofday(&now, NULL); 164 cursize = bytes + restart_point; 165 timersub(&now, &lastupdate, &wait); 166 if (cursize > lastsize) { 167 lastupdate = now; 168 lastsize = cursize; 169 wait.tv_sec = 0; 170 } else { 171 #ifndef STANDALONE_PROGRESS 172 if (quit_time > 0 && wait.tv_sec > quit_time) { 173 len = snprintf(buf, sizeof(buf), "\r\n%s: " 174 "transfer aborted because stalled for %lu sec.\r\n", 175 getprogname(), (unsigned long)wait.tv_sec); 176 (void)write(fileno(ttyout), buf, len); 177 alarmtimer(0); 178 (void)xsignal(SIGALRM, SIG_DFL); 179 siglongjmp(toplevel, 1); 180 } 181 #endif /* !STANDALONE_PROGRESS */ 182 } 183 /* 184 * Always set the handler even if we are not the foreground process. 185 */ 186 #ifdef STANDALONE_PROGRESS 187 if (progress) { 188 #else 189 if (quit_time > 0 || progress) { 190 #endif /* !STANDALONE_PROGRESS */ 191 if (flag == -1) { 192 (void)xsignal_restart(SIGALRM, updateprogressmeter, 1); 193 alarmtimer(1); /* set alarm timer for 1 Hz */ 194 } else if (flag == 1) { 195 alarmtimer(0); 196 (void)xsignal(SIGALRM, SIG_DFL); 197 } 198 } 199 #ifndef NO_PROGRESS 200 if (!progress) 201 return; 202 len = 0; 203 204 /* 205 * print progress bar only if we are foreground process. 206 */ 207 if (! foregroundproc()) 208 return; 209 210 len += snprintf(buf + len, BUFLEFT, "\r"); 211 if (prefix) 212 len += snprintf(buf + len, BUFLEFT, "%s", prefix); 213 if (filesize > 0) { 214 ratio = (int)((double)cursize * 100.0 / (double)filesize); 215 ratio = MAX(ratio, 0); 216 ratio = MIN(ratio, 100); 217 len += snprintf(buf + len, BUFLEFT, "%3d%% ", ratio); 218 219 /* 220 * calculate the length of the `*' bar, ensuring that 221 * the number of stars won't exceed the buffer size 222 */ 223 barlength = MIN((int)(sizeof(buf) - 1), ttywidth) - BAROVERHEAD; 224 if (prefix) 225 barlength -= (int)strlen(prefix); 226 if (barlength > 0) { 227 i = barlength * ratio / 100; 228 len += snprintf(buf + len, BUFLEFT, 229 "|%.*s%*s|", i, stars, (int)(barlength - i), ""); 230 } 231 } 232 233 abbrevsize = cursize; 234 for (i = 0; abbrevsize >= 100000 && i < NSUFFIXES; i++) 235 abbrevsize >>= 10; 236 if (i == NSUFFIXES) 237 i--; 238 len += snprintf(buf + len, BUFLEFT, " " LLFP("5") " %-3s ", 239 (LLT)abbrevsize, 240 suffixes[i]); 241 242 timersub(&now, &start, &td); 243 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 244 245 bytespersec = 0; 246 if (bytes > 0) { 247 bytespersec = bytes; 248 if (elapsed > 0.0) 249 bytespersec /= elapsed; 250 } 251 for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++) 252 bytespersec >>= 10; 253 len += snprintf(buf + len, BUFLEFT, 254 " " LLFP("3") ".%02d %.2sB/s ", 255 (LLT)(bytespersec / 1024), 256 (int)((bytespersec % 1024) * 100 / 1024), 257 suffixes[i]); 258 259 if (filesize > 0) { 260 if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { 261 len += snprintf(buf + len, BUFLEFT, " --:-- ETA"); 262 } else if (wait.tv_sec >= STALLTIME) { 263 len += snprintf(buf + len, BUFLEFT, " - stalled -"); 264 } else { 265 remaining = (int) 266 ((filesize - restart_point) / (bytes / elapsed) - 267 elapsed); 268 if (remaining >= 100 * SECSPERHOUR) 269 len += snprintf(buf + len, BUFLEFT, 270 " --:-- ETA"); 271 else { 272 i = remaining / SECSPERHOUR; 273 if (i) 274 len += snprintf(buf + len, BUFLEFT, 275 "%2d:", i); 276 else 277 len += snprintf(buf + len, BUFLEFT, 278 " "); 279 i = remaining % SECSPERHOUR; 280 len += snprintf(buf + len, BUFLEFT, 281 "%02d:%02d ETA", i / 60, i % 60); 282 } 283 } 284 } 285 if (flag == 1) 286 len += snprintf(buf + len, BUFLEFT, "\n"); 287 (void)write(fileno(ttyout), buf, len); 288 289 #endif /* !NO_PROGRESS */ 290 } 291 292 #ifndef STANDALONE_PROGRESS 293 /* 294 * Display transfer statistics. 295 * Requires start to be initialised by progressmeter(-1), 296 * direction to be defined by xfer routines, and filesize and bytes 297 * to be updated by xfer routines 298 * If siginfo is nonzero, an ETA is displayed, and the output goes to stderr 299 * instead of ttyout. 300 */ 301 void 302 ptransfer(int siginfo) 303 { 304 struct timeval now, td, wait; 305 double elapsed; 306 off_t bytespersec; 307 int remaining, hh, i; 308 size_t len; 309 310 char buf[256]; /* Work variable for transfer status. */ 311 312 if (!verbose && !progress && !siginfo) 313 return; 314 315 (void)gettimeofday(&now, NULL); 316 timersub(&now, &start, &td); 317 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 318 bytespersec = 0; 319 if (bytes > 0) { 320 bytespersec = bytes; 321 if (elapsed > 0.0) 322 bytespersec /= elapsed; 323 } 324 len = 0; 325 len += snprintf(buf + len, BUFLEFT, LLF " byte%s %s in ", 326 (LLT)bytes, bytes == 1 ? "" : "s", direction); 327 remaining = (int)elapsed; 328 if (remaining > SECSPERDAY) { 329 int days; 330 331 days = remaining / SECSPERDAY; 332 remaining %= SECSPERDAY; 333 len += snprintf(buf + len, BUFLEFT, 334 "%d day%s ", days, days == 1 ? "" : "s"); 335 } 336 hh = remaining / SECSPERHOUR; 337 remaining %= SECSPERHOUR; 338 if (hh) 339 len += snprintf(buf + len, BUFLEFT, "%2d:", hh); 340 len += snprintf(buf + len, BUFLEFT, 341 "%02d:%02d ", remaining / 60, remaining % 60); 342 343 for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++) 344 bytespersec >>= 10; 345 if (i == NSUFFIXES) 346 i--; 347 len += snprintf(buf + len, BUFLEFT, "(" LLF ".%02d %.2sB/s)", 348 (LLT)(bytespersec / 1024), 349 (int)((bytespersec % 1024) * 100 / 1024), 350 suffixes[i]); 351 352 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 353 && bytes + restart_point <= filesize) { 354 remaining = (int)((filesize - restart_point) / 355 (bytes / elapsed) - elapsed); 356 hh = remaining / SECSPERHOUR; 357 remaining %= SECSPERHOUR; 358 len += snprintf(buf + len, BUFLEFT, " ETA: "); 359 if (hh) 360 len += snprintf(buf + len, BUFLEFT, "%2d:", hh); 361 len += snprintf(buf + len, BUFLEFT, "%02d:%02d", 362 remaining / 60, remaining % 60); 363 timersub(&now, &lastupdate, &wait); 364 if (wait.tv_sec >= STALLTIME) 365 len += snprintf(buf + len, BUFLEFT, " (stalled)"); 366 } 367 len += snprintf(buf + len, BUFLEFT, "\n"); 368 (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, len); 369 } 370 371 /* 372 * SIG{INFO,QUIT} handler to print transfer stats if a transfer is in progress 373 */ 374 void 375 psummary(int notused) 376 { 377 int oerrno = errno; 378 379 if (bytes > 0) { 380 if (fromatty) 381 write(fileno(ttyout), "\n", 1); 382 ptransfer(1); 383 } 384 errno = oerrno; 385 } 386 #endif /* !STANDALONE_PROGRESS */ 387 388 389 /* 390 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 391 */ 392 void 393 alarmtimer(int wait) 394 { 395 struct itimerval itv; 396 397 itv.it_value.tv_sec = wait; 398 itv.it_value.tv_usec = 0; 399 itv.it_interval = itv.it_value; 400 setitimer(ITIMER_REAL, &itv, NULL); 401 } 402 403 404 /* 405 * Install a POSIX signal handler, allowing the invoker to set whether 406 * the signal should be restartable or not 407 */ 408 sigfunc 409 xsignal_restart(int sig, sigfunc func, int restartable) 410 { 411 struct sigaction act, oact; 412 act.sa_handler = func; 413 414 sigemptyset(&act.sa_mask); 415 #if defined(SA_RESTART) /* 4.4BSD, Posix(?), SVR4 */ 416 act.sa_flags = restartable ? SA_RESTART : 0; 417 #elif defined(SA_INTERRUPT) /* SunOS 4.x */ 418 act.sa_flags = restartable ? 0 : SA_INTERRUPT; 419 #else 420 #error "system must have SA_RESTART or SA_INTERRUPT" 421 #endif 422 if (sigaction(sig, &act, &oact) < 0) 423 return (SIG_ERR); 424 return (oact.sa_handler); 425 } 426 427 /* 428 * Install a signal handler with the `restartable' flag set dependent upon 429 * which signal is being set. (This is a wrapper to xsignal_restart()) 430 */ 431 sigfunc 432 xsignal(int sig, sigfunc func) 433 { 434 int restartable; 435 436 /* 437 * Some signals print output or change the state of the process. 438 * There should be restartable, so that reads and writes are 439 * not affected. Some signals should cause program flow to change; 440 * these signals should not be restartable, so that the system call 441 * will return with EINTR, and the program will go do something 442 * different. If the signal handler calls longjmp() or siglongjmp(), 443 * it doesn't matter if it's restartable. 444 */ 445 446 switch(sig) { 447 #ifdef SIGINFO 448 case SIGINFO: 449 #endif 450 case SIGQUIT: 451 case SIGUSR1: 452 case SIGUSR2: 453 case SIGWINCH: 454 restartable = 1; 455 break; 456 457 case SIGALRM: 458 case SIGINT: 459 case SIGPIPE: 460 restartable = 0; 461 break; 462 463 default: 464 /* 465 * This is unpleasant, but I don't know what would be better. 466 * Right now, this "can't happen" 467 */ 468 errx(1, "xsignal_restart: called with signal %d", sig); 469 } 470 471 return(xsignal_restart(sig, func, restartable)); 472 } 473