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