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