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