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