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