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