1 /* $NetBSD: progressbar.c,v 1.14 2009/05/20 12:53:47 lukem Exp $ */ 2 /* from NetBSD: progressbar.c,v 1.21 2009/04/12 10:18:52 lukem Exp */ 3 4 /*- 5 * Copyright (c) 1997-2009 The NetBSD Foundation, Inc. 6 * All rights reserved. 7 * 8 * This code is derived from software contributed to The NetBSD Foundation 9 * by Luke Mewburn. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include "tnftp.h" 34 35 #if 0 /* tnftp */ 36 37 #include <sys/cdefs.h> 38 #ifndef lint 39 __RCSID(" NetBSD: progressbar.c,v 1.21 2009/04/12 10:18:52 lukem Exp "); 40 #endif /* not lint */ 41 42 /* 43 * FTP User Program -- Misc support routines 44 */ 45 #include <sys/types.h> 46 #include <sys/param.h> 47 48 #include <err.h> 49 #include <errno.h> 50 #include <signal.h> 51 #include <stdio.h> 52 #include <stdlib.h> 53 #include <string.h> 54 #include <time.h> 55 #include <tzfile.h> 56 #include <unistd.h> 57 58 #endif /* tnftp */ 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 #if GETPGRP_VOID 73 pgrp = getpgrp(); 74 #else /* ! GETPGRP_VOID */ 75 pgrp = getpgrp(0); 76 #endif /* ! GETPGRP_VOID */ 77 78 return (tcgetpgrp(fileno(ttyout)) == pgrp); 79 } 80 #endif /* !defined(NO_PROGRESS) */ 81 82 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 97 /* 98 * List of order of magnitude suffixes, per IEC 60027-2. 99 */ 100 static const char * const suffixes[] = { 101 "", /* 2^0 (byte) */ 102 "KiB", /* 2^10 Kibibyte */ 103 "MiB", /* 2^20 Mebibyte */ 104 "GiB", /* 2^30 Gibibyte */ 105 "TiB", /* 2^40 Tebibyte */ 106 "PiB", /* 2^50 Pebibyte */ 107 "EiB", /* 2^60 Exbibyte */ 108 #if 0 109 /* The following are not necessary for signed 64-bit off_t */ 110 "ZiB", /* 2^70 Zebibyte */ 111 "YiB", /* 2^80 Yobibyte */ 112 #endif 113 }; 114 #define NSUFFIXES (int)(sizeof(suffixes) / sizeof(suffixes[0])) 115 116 /* 117 * Display a transfer progress bar if progress is non-zero. 118 * SIGALRM is hijacked for use by this function. 119 * - Before the transfer, set filesize to size of file (or -1 if unknown), 120 * and call with flag = -1. This starts the once per second timer, 121 * and a call to updateprogressmeter() upon SIGALRM. 122 * - During the transfer, updateprogressmeter will call progressmeter 123 * with flag = 0 124 * - After the transfer, call with flag = 1 125 */ 126 static struct timeval start; 127 static struct timeval lastupdate; 128 129 #define BUFLEFT (sizeof(buf) - len) 130 131 void 132 progressmeter(int flag) 133 { 134 static off_t lastsize; 135 off_t cursize; 136 struct timeval now, wait; 137 #ifndef NO_PROGRESS 138 struct timeval td; 139 off_t abbrevsize, bytespersec; 140 double elapsed; 141 int ratio, i, remaining, barlength; 142 143 /* 144 * Work variables for progress bar. 145 * 146 * XXX: if the format of the progress bar changes 147 * (especially the number of characters in the 148 * `static' portion of it), be sure to update 149 * these appropriately. 150 */ 151 #endif 152 size_t len; 153 char buf[256]; /* workspace for progress bar */ 154 #ifndef NO_PROGRESS 155 #define BAROVERHEAD 45 /* non `*' portion of progress bar */ 156 /* 157 * stars should contain at least 158 * sizeof(buf) - BAROVERHEAD entries 159 */ 160 static const char stars[] = 161 "*****************************************************************************" 162 "*****************************************************************************" 163 "*****************************************************************************"; 164 165 #endif 166 167 if (flag == -1) { 168 (void)gettimeofday(&start, NULL); 169 lastupdate = start; 170 lastsize = restart_point; 171 } 172 173 (void)gettimeofday(&now, NULL); 174 cursize = bytes + restart_point; 175 timersub(&now, &lastupdate, &wait); 176 if (cursize > lastsize) { 177 lastupdate = now; 178 lastsize = cursize; 179 wait.tv_sec = 0; 180 } else { 181 #ifndef STANDALONE_PROGRESS 182 if (quit_time > 0 && wait.tv_sec > quit_time) { 183 len = snprintf(buf, sizeof(buf), "\r\n%s: " 184 "transfer aborted because stalled for %lu sec.\r\n", 185 getprogname(), (unsigned long)wait.tv_sec); 186 (void)write(fileno(ttyout), buf, len); 187 alarmtimer(0); 188 (void)xsignal(SIGALRM, SIG_DFL); 189 siglongjmp(toplevel, 1); 190 } 191 #endif /* !STANDALONE_PROGRESS */ 192 } 193 /* 194 * Always set the handler even if we are not the foreground process. 195 */ 196 #ifdef STANDALONE_PROGRESS 197 if (progress) { 198 #else 199 if (quit_time > 0 || progress) { 200 #endif /* !STANDALONE_PROGRESS */ 201 if (flag == -1) { 202 (void)xsignal_restart(SIGALRM, updateprogressmeter, 1); 203 alarmtimer(1); /* set alarm timer for 1 Hz */ 204 } else if (flag == 1) { 205 alarmtimer(0); 206 (void)xsignal(SIGALRM, SIG_DFL); 207 } 208 } 209 #ifndef NO_PROGRESS 210 if (!progress) 211 return; 212 len = 0; 213 214 /* 215 * print progress bar only if we are foreground process. 216 */ 217 if (! foregroundproc()) 218 return; 219 220 len += snprintf(buf + len, BUFLEFT, "\r"); 221 if (prefix) 222 len += snprintf(buf + len, BUFLEFT, "%s", prefix); 223 if (filesize > 0) { 224 ratio = (int)((double)cursize * 100.0 / (double)filesize); 225 ratio = MAX(ratio, 0); 226 ratio = MIN(ratio, 100); 227 len += snprintf(buf + len, BUFLEFT, "%3d%% ", ratio); 228 229 /* 230 * calculate the length of the `*' bar, ensuring that 231 * the number of stars won't exceed the buffer size 232 */ 233 barlength = MIN((int)(sizeof(buf) - 1), ttywidth) - BAROVERHEAD; 234 if (prefix) 235 barlength -= (int)strlen(prefix); 236 if (barlength > 0) { 237 i = barlength * ratio / 100; 238 len += snprintf(buf + len, BUFLEFT, 239 "|%.*s%*s|", i, stars, (int)(barlength - i), ""); 240 } 241 } 242 243 abbrevsize = cursize; 244 for (i = 0; abbrevsize >= 100000 && i < NSUFFIXES; i++) 245 abbrevsize >>= 10; 246 if (i == NSUFFIXES) 247 i--; 248 len += snprintf(buf + len, BUFLEFT, " " LLFP("5") " %-3s ", 249 (LLT)abbrevsize, 250 suffixes[i]); 251 252 timersub(&now, &start, &td); 253 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 254 255 bytespersec = 0; 256 if (bytes > 0) { 257 bytespersec = bytes; 258 if (elapsed > 0.0) 259 bytespersec /= elapsed; 260 } 261 for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++) 262 bytespersec >>= 10; 263 len += snprintf(buf + len, BUFLEFT, 264 " " LLFP("3") ".%02d %.2sB/s ", 265 (LLT)(bytespersec / 1024), 266 (int)((bytespersec % 1024) * 100 / 1024), 267 suffixes[i]); 268 269 if (filesize > 0) { 270 if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { 271 len += snprintf(buf + len, BUFLEFT, " --:-- ETA"); 272 } else if (wait.tv_sec >= STALLTIME) { 273 len += snprintf(buf + len, BUFLEFT, " - stalled -"); 274 } else { 275 remaining = (int) 276 ((filesize - restart_point) / (bytes / elapsed) - 277 elapsed); 278 if (remaining >= 100 * SECSPERHOUR) 279 len += snprintf(buf + len, BUFLEFT, 280 " --:-- ETA"); 281 else { 282 i = remaining / SECSPERHOUR; 283 if (i) 284 len += snprintf(buf + len, BUFLEFT, 285 "%2d:", i); 286 else 287 len += snprintf(buf + len, BUFLEFT, 288 " "); 289 i = remaining % SECSPERHOUR; 290 len += snprintf(buf + len, BUFLEFT, 291 "%02d:%02d ETA", i / 60, i % 60); 292 } 293 } 294 } 295 if (flag == 1) 296 len += snprintf(buf + len, BUFLEFT, "\n"); 297 (void)write(fileno(ttyout), buf, len); 298 299 #endif /* !NO_PROGRESS */ 300 } 301 302 #ifndef STANDALONE_PROGRESS 303 /* 304 * Display transfer statistics. 305 * Requires start to be initialised by progressmeter(-1), 306 * direction to be defined by xfer routines, and filesize and bytes 307 * to be updated by xfer routines 308 * If siginfo is nonzero, an ETA is displayed, and the output goes to stderr 309 * instead of ttyout. 310 */ 311 void 312 ptransfer(int siginfo) 313 { 314 struct timeval now, td, wait; 315 double elapsed; 316 off_t bytespersec; 317 int remaining, hh, i; 318 size_t len; 319 320 char buf[256]; /* Work variable for transfer status. */ 321 322 if (!verbose && !progress && !siginfo) 323 return; 324 325 (void)gettimeofday(&now, NULL); 326 timersub(&now, &start, &td); 327 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 328 bytespersec = 0; 329 if (bytes > 0) { 330 bytespersec = bytes; 331 if (elapsed > 0.0) 332 bytespersec /= elapsed; 333 } 334 len = 0; 335 len += snprintf(buf + len, BUFLEFT, LLF " byte%s %s in ", 336 (LLT)bytes, bytes == 1 ? "" : "s", direction); 337 remaining = (int)elapsed; 338 if (remaining > SECSPERDAY) { 339 int days; 340 341 days = remaining / SECSPERDAY; 342 remaining %= SECSPERDAY; 343 len += snprintf(buf + len, BUFLEFT, 344 "%d day%s ", days, days == 1 ? "" : "s"); 345 } 346 hh = remaining / SECSPERHOUR; 347 remaining %= SECSPERHOUR; 348 if (hh) 349 len += snprintf(buf + len, BUFLEFT, "%2d:", hh); 350 len += snprintf(buf + len, BUFLEFT, 351 "%02d:%02d ", remaining / 60, remaining % 60); 352 353 for (i = 1; bytespersec >= 1024000 && i < NSUFFIXES; i++) 354 bytespersec >>= 10; 355 if (i == NSUFFIXES) 356 i--; 357 len += snprintf(buf + len, BUFLEFT, "(" LLF ".%02d %.2sB/s)", 358 (LLT)(bytespersec / 1024), 359 (int)((bytespersec % 1024) * 100 / 1024), 360 suffixes[i]); 361 362 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 363 && bytes + restart_point <= filesize) { 364 remaining = (int)((filesize - restart_point) / 365 (bytes / elapsed) - elapsed); 366 hh = remaining / SECSPERHOUR; 367 remaining %= SECSPERHOUR; 368 len += snprintf(buf + len, BUFLEFT, " ETA: "); 369 if (hh) 370 len += snprintf(buf + len, BUFLEFT, "%2d:", hh); 371 len += snprintf(buf + len, BUFLEFT, "%02d:%02d", 372 remaining / 60, remaining % 60); 373 timersub(&now, &lastupdate, &wait); 374 if (wait.tv_sec >= STALLTIME) 375 len += snprintf(buf + len, BUFLEFT, " (stalled)"); 376 } 377 len += snprintf(buf + len, BUFLEFT, "\n"); 378 (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, len); 379 } 380 381 /* 382 * SIG{INFO,QUIT} handler to print transfer stats if a transfer is in progress 383 */ 384 void 385 psummary(int notused) 386 { 387 int oerrno = errno; 388 389 if (bytes > 0) { 390 if (fromatty) 391 write(fileno(ttyout), "\n", 1); 392 ptransfer(1); 393 } 394 errno = oerrno; 395 } 396 #endif /* !STANDALONE_PROGRESS */ 397 398 399 /* 400 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 401 */ 402 void 403 alarmtimer(int wait) 404 { 405 struct itimerval itv; 406 407 itv.it_value.tv_sec = wait; 408 itv.it_value.tv_usec = 0; 409 itv.it_interval = itv.it_value; 410 setitimer(ITIMER_REAL, &itv, NULL); 411 } 412 413 414 /* 415 * Install a POSIX signal handler, allowing the invoker to set whether 416 * the signal should be restartable or not 417 */ 418 sigfunc 419 xsignal_restart(int sig, sigfunc func, int restartable) 420 { 421 #ifdef ultrix /* XXX: this is lame - how do we test sigvec vs. sigaction? */ 422 struct sigvec vec, ovec; 423 424 vec.sv_handler = func; 425 sigemptyset(&vec.sv_mask); 426 vec.sv_flags = 0; 427 if (sigvec(sig, &vec, &ovec) < 0) 428 return (SIG_ERR); 429 return (ovec.sv_handler); 430 #else /* ! ultrix */ 431 struct sigaction act, oact; 432 act.sa_handler = func; 433 434 sigemptyset(&act.sa_mask); 435 #if defined(SA_RESTART) /* 4.4BSD, Posix(?), SVR4 */ 436 act.sa_flags = restartable ? SA_RESTART : 0; 437 #elif defined(SA_INTERRUPT) /* SunOS 4.x */ 438 act.sa_flags = restartable ? 0 : SA_INTERRUPT; 439 #else 440 #error "system must have SA_RESTART or SA_INTERRUPT" 441 #endif 442 if (sigaction(sig, &act, &oact) < 0) 443 return (SIG_ERR); 444 return (oact.sa_handler); 445 #endif /* ! ultrix */ 446 } 447 448 /* 449 * Install a signal handler with the `restartable' flag set dependent upon 450 * which signal is being set. (This is a wrapper to xsignal_restart()) 451 */ 452 sigfunc 453 xsignal(int sig, sigfunc func) 454 { 455 int restartable; 456 457 /* 458 * Some signals print output or change the state of the process. 459 * There should be restartable, so that reads and writes are 460 * not affected. Some signals should cause program flow to change; 461 * these signals should not be restartable, so that the system call 462 * will return with EINTR, and the program will go do something 463 * different. If the signal handler calls longjmp() or siglongjmp(), 464 * it doesn't matter if it's restartable. 465 */ 466 467 switch(sig) { 468 #ifdef SIGINFO 469 case SIGINFO: 470 #endif 471 case SIGQUIT: 472 case SIGUSR1: 473 case SIGUSR2: 474 case SIGWINCH: 475 restartable = 1; 476 break; 477 478 case SIGALRM: 479 case SIGINT: 480 case SIGPIPE: 481 restartable = 0; 482 break; 483 484 default: 485 /* 486 * This is unpleasant, but I don't know what would be better. 487 * Right now, this "can't happen" 488 */ 489 errx(1, "xsignal_restart: called with signal %d", sig); 490 } 491 492 return(xsignal_restart(sig, func, restartable)); 493 } 494