progressbar.c revision 1.13
1/* $NetBSD: progressbar.c,v 1.13 2006/05/01 23:02:03 christos 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.13 2006/05/01 23:02:03 christos 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 */ 66int 67foregroundproc(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 79static void updateprogressmeter(int); 80 81/* 82 * SIGALRM handler to update the progress meter 83 */ 84static void 85updateprogressmeter(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 */ 97static 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 */ 109static struct timeval start; 110static struct timeval lastupdate; 111 112#define BUFLEFT (sizeof(buf) - len) 113 114void 115progressmeter(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, barlength; 125 126 /* 127 * Work variables for progress bar. 128 * 129 * XXX: if the format of the progress bar changes 130 * (especially the number of characters in the 131 * `static' portion of it), be sure to update 132 * these appropriately. 133 */ 134#endif 135 size_t len; 136 char buf[256]; /* workspace for progress bar */ 137#ifndef NO_PROGRESS 138#define BAROVERHEAD 43 /* non `*' portion of progress bar */ 139 /* 140 * stars should contain at least 141 * sizeof(buf) - BAROVERHEAD entries 142 */ 143 static const char stars[] = 144"*****************************************************************************" 145"*****************************************************************************" 146"*****************************************************************************"; 147 148#endif 149 150 if (flag == -1) { 151 (void)gettimeofday(&start, NULL); 152 lastupdate = start; 153 lastsize = restart_point; 154 } 155 156 (void)gettimeofday(&now, NULL); 157 cursize = bytes + restart_point; 158 timersub(&now, &lastupdate, &wait); 159 if (cursize > lastsize) { 160 lastupdate = now; 161 lastsize = cursize; 162 wait.tv_sec = 0; 163 } else { 164#ifndef STANDALONE_PROGRESS 165 if (quit_time > 0 && wait.tv_sec > quit_time) { 166 len = snprintf(buf, sizeof(buf), "\r\n%s: " 167 "transfer aborted because stalled for %lu sec.\r\n", 168 getprogname(), (unsigned long)wait.tv_sec); 169 (void)write(fileno(ttyout), buf, len); 170 (void)xsignal(SIGALRM, SIG_DFL); 171 alarmtimer(0); 172 siglongjmp(toplevel, 1); 173 } 174#endif /* !STANDALONE_PROGRESS */ 175 } 176 /* 177 * Always set the handler even if we are not the foreground process. 178 */ 179#ifdef STANDALONE_PROGRESS 180 if (progress) { 181#else 182 if (quit_time > 0 || progress) { 183#endif /* !STANDALONE_PROGRESS */ 184 if (flag == -1) { 185 (void)xsignal_restart(SIGALRM, updateprogressmeter, 1); 186 alarmtimer(1); /* set alarm timer for 1 Hz */ 187 } else if (flag == 1) { 188 (void)xsignal(SIGALRM, SIG_DFL); 189 alarmtimer(0); 190 } 191 } 192#ifndef NO_PROGRESS 193 if (!progress) 194 return; 195 len = 0; 196 197 /* 198 * print progress bar only if we are foreground process. 199 */ 200 if (! foregroundproc()) 201 return; 202 203 len += snprintf(buf + len, BUFLEFT, "\r"); 204 if (prefix) 205 len += snprintf(buf + len, BUFLEFT, "%s", prefix); 206 if (filesize > 0) { 207 ratio = (int)((double)cursize * 100.0 / (double)filesize); 208 ratio = MAX(ratio, 0); 209 ratio = MIN(ratio, 100); 210 len += snprintf(buf + len, BUFLEFT, "%3d%% ", ratio); 211 212 /* 213 * calculate the length of the `*' bar, ensuring that 214 * the number of stars won't exceed the buffer size 215 */ 216 barlength = MIN(sizeof(buf) - 1, ttywidth) - BAROVERHEAD; 217 if (prefix) 218 barlength -= (int)strlen(prefix); 219 if (barlength > 0) { 220 i = barlength * ratio / 100; 221 len += snprintf(buf + len, BUFLEFT, 222 "|%.*s%*s|", i, stars, (int)(barlength - i), ""); 223 } 224 } 225 226 abbrevsize = cursize; 227 for (i = 0; abbrevsize >= 100000 && i < sizeof(prefixes); i++) 228 abbrevsize >>= 10; 229 if (i == sizeof(prefixes)) 230 i--; 231 len += snprintf(buf + len, BUFLEFT, " " LLFP("5") " %c%c ", 232 (LLT)abbrevsize, 233 prefixes[i], 234 i == 0 ? ' ' : 'B'); 235 236 timersub(&now, &start, &td); 237 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 238 239 bytespersec = 0; 240 if (bytes > 0) { 241 bytespersec = bytes; 242 if (elapsed > 0.0) 243 bytespersec /= elapsed; 244 } 245 for (i = 1; bytespersec >= 1024000 && i < sizeof(prefixes); i++) 246 bytespersec >>= 10; 247 len += snprintf(buf + len, BUFLEFT, 248 " " LLFP("3") ".%02d %cB/s ", 249 (LLT)(bytespersec / 1024), 250 (int)((bytespersec % 1024) * 100 / 1024), 251 prefixes[i]); 252 253 if (filesize > 0) { 254 if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { 255 len += snprintf(buf + len, BUFLEFT, " --:-- ETA"); 256 } else if (wait.tv_sec >= STALLTIME) { 257 len += snprintf(buf + len, BUFLEFT, " - stalled -"); 258 } else { 259 remaining = (int) 260 ((filesize - restart_point) / (bytes / elapsed) - 261 elapsed); 262 if (remaining >= 100 * SECSPERHOUR) 263 len += snprintf(buf + len, BUFLEFT, 264 " --:-- ETA"); 265 else { 266 i = remaining / SECSPERHOUR; 267 if (i) 268 len += snprintf(buf + len, BUFLEFT, 269 "%2d:", i); 270 else 271 len += snprintf(buf + len, BUFLEFT, 272 " "); 273 i = remaining % SECSPERHOUR; 274 len += snprintf(buf + len, BUFLEFT, 275 "%02d:%02d ETA", i / 60, i % 60); 276 } 277 } 278 } 279 if (flag == 1) 280 len += snprintf(buf + len, BUFLEFT, "\n"); 281 (void)write(fileno(ttyout), buf, len); 282 283#endif /* !NO_PROGRESS */ 284} 285 286#ifndef STANDALONE_PROGRESS 287/* 288 * Display transfer statistics. 289 * Requires start to be initialised by progressmeter(-1), 290 * direction to be defined by xfer routines, and filesize and bytes 291 * to be updated by xfer routines 292 * If siginfo is nonzero, an ETA is displayed, and the output goes to stderr 293 * instead of ttyout. 294 */ 295void 296ptransfer(int siginfo) 297{ 298 struct timeval now, td, wait; 299 double elapsed; 300 off_t bytespersec; 301 int remaining, hh, i; 302 size_t 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 if (i == sizeof(prefixes)) 340 i--; 341 len += snprintf(buf + len, BUFLEFT, "(" LLF ".%02d %cB/s)", 342 (LLT)(bytespersec / 1024), 343 (int)((bytespersec % 1024) * 100 / 1024), 344 prefixes[i]); 345 346 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 347 && bytes + restart_point <= filesize) { 348 remaining = (int)((filesize - restart_point) / 349 (bytes / elapsed) - elapsed); 350 hh = remaining / SECSPERHOUR; 351 remaining %= SECSPERHOUR; 352 len += snprintf(buf + len, BUFLEFT, " ETA: "); 353 if (hh) 354 len += snprintf(buf + len, BUFLEFT, "%2d:", hh); 355 len += snprintf(buf + len, BUFLEFT, "%02d:%02d", 356 remaining / 60, remaining % 60); 357 timersub(&now, &lastupdate, &wait); 358 if (wait.tv_sec >= STALLTIME) 359 len += snprintf(buf + len, BUFLEFT, " (stalled)"); 360 } 361 len += snprintf(buf + len, BUFLEFT, "\n"); 362 (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, len); 363} 364 365/* 366 * SIG{INFO,QUIT} handler to print transfer stats if a transfer is in progress 367 */ 368void 369psummary(int notused) 370{ 371 int oerrno = errno; 372 373 if (bytes > 0) { 374 if (fromatty) 375 write(fileno(ttyout), "\n", 1); 376 ptransfer(1); 377 } 378 errno = oerrno; 379} 380#endif /* !STANDALONE_PROGRESS */ 381 382 383/* 384 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 385 */ 386void 387alarmtimer(int wait) 388{ 389 struct itimerval itv; 390 391 itv.it_value.tv_sec = wait; 392 itv.it_value.tv_usec = 0; 393 itv.it_interval = itv.it_value; 394 setitimer(ITIMER_REAL, &itv, NULL); 395} 396 397 398/* 399 * Install a POSIX signal handler, allowing the invoker to set whether 400 * the signal should be restartable or not 401 */ 402sigfunc 403xsignal_restart(int sig, sigfunc func, int restartable) 404{ 405 struct sigaction act, oact; 406 act.sa_handler = func; 407 408 sigemptyset(&act.sa_mask); 409#if defined(SA_RESTART) /* 4.4BSD, Posix(?), SVR4 */ 410 act.sa_flags = restartable ? SA_RESTART : 0; 411#elif defined(SA_INTERRUPT) /* SunOS 4.x */ 412 act.sa_flags = restartable ? 0 : SA_INTERRUPT; 413#else 414#error "system must have SA_RESTART or SA_INTERRUPT" 415#endif 416 if (sigaction(sig, &act, &oact) < 0) 417 return (SIG_ERR); 418 return (oact.sa_handler); 419} 420 421/* 422 * Install a signal handler with the `restartable' flag set dependent upon 423 * which signal is being set. (This is a wrapper to xsignal_restart()) 424 */ 425sigfunc 426xsignal(int sig, sigfunc func) 427{ 428 int restartable; 429 430 /* 431 * Some signals print output or change the state of the process. 432 * There should be restartable, so that reads and writes are 433 * not affected. Some signals should cause program flow to change; 434 * these signals should not be restartable, so that the system call 435 * will return with EINTR, and the program will go do something 436 * different. If the signal handler calls longjmp() or siglongjmp(), 437 * it doesn't matter if it's restartable. 438 */ 439 440 switch(sig) { 441#ifdef SIGINFO 442 case SIGINFO: 443#endif 444 case SIGQUIT: 445 case SIGUSR1: 446 case SIGUSR2: 447 case SIGWINCH: 448 restartable = 1; 449 break; 450 451 case SIGALRM: 452 case SIGINT: 453 case SIGPIPE: 454 restartable = 0; 455 break; 456 457 default: 458 /* 459 * This is unpleasant, but I don't know what would be better. 460 * Right now, this "can't happen" 461 */ 462 errx(1, "xsignal_restart called with signal %d", sig); 463 } 464 465 return(xsignal_restart(sig, func, restartable)); 466} 467