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