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