1 /* $OpenBSD: util.c,v 1.21 1998/09/22 04:42:49 deraadt Exp $ */ 2 /* $NetBSD: util.c,v 1.12 1997/08/18 10:20:27 lukem Exp $ */ 3 4 /* 5 * Copyright (c) 1985, 1989, 1993, 1994 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #ifndef lint 38 static char rcsid[] = "$OpenBSD: util.c,v 1.21 1998/09/22 04:42:49 deraadt Exp $"; 39 #endif /* not lint */ 40 41 /* 42 * FTP User Program -- Misc support routines 43 */ 44 #include <sys/ioctl.h> 45 #include <sys/time.h> 46 #include <arpa/ftp.h> 47 48 #include <ctype.h> 49 #include <err.h> 50 #include <errno.h> 51 #include <fcntl.h> 52 #include <limits.h> 53 #include <glob.h> 54 #include <pwd.h> 55 #include <signal.h> 56 #include <stdio.h> 57 #include <stdlib.h> 58 #include <string.h> 59 #include <time.h> 60 #include <tzfile.h> 61 #include <unistd.h> 62 63 #include "ftp_var.h" 64 #include "pathnames.h" 65 66 static void updateprogressmeter __P((int)); 67 68 /* 69 * Connect to peer server and 70 * auto-login, if possible. 71 */ 72 void 73 setpeer(argc, argv) 74 int argc; 75 char *argv[]; 76 { 77 char *host; 78 in_port_t port; 79 80 if (connected) { 81 fprintf(ttyout, "Already connected to %s, use close first.\n", 82 hostname); 83 code = -1; 84 return; 85 } 86 if (argc < 2) 87 (void)another(&argc, &argv, "to"); 88 if (argc < 2 || argc > 3) { 89 fprintf(ttyout, "usage: %s host-name [port]\n", argv[0]); 90 code = -1; 91 return; 92 } 93 if (gatemode) 94 port = gateport; 95 else 96 port = ftpport; 97 if (argc > 2) { 98 char *ep; 99 long nport; 100 101 nport = strtol(argv[2], &ep, 10); 102 if (nport < 1 || nport > USHRT_MAX || *ep != '\0') { 103 fprintf(ttyout, "%s: bad port number '%s'.\n", 104 argv[1], argv[2]); 105 fprintf(ttyout, "usage: %s host-name [port]\n", 106 argv[0]); 107 code = -1; 108 return; 109 } 110 port = htons((in_port_t)nport); 111 } 112 113 if (gatemode) { 114 if (gateserver == NULL || *gateserver == '\0') 115 errx(1, "gateserver not defined (shouldn't happen)"); 116 host = hookup(gateserver, port); 117 } else 118 host = hookup(argv[1], port); 119 120 if (host) { 121 int overbose; 122 123 if (gatemode) { 124 if (command("PASSERVE %s", argv[1]) != COMPLETE) 125 return; 126 if (verbose) 127 fprintf(ttyout, 128 "Connected via pass-through server %s\n", 129 gateserver); 130 } 131 132 connected = 1; 133 /* 134 * Set up defaults for FTP. 135 */ 136 (void)strcpy(typename, "ascii"), type = TYPE_A; 137 curtype = TYPE_A; 138 (void)strcpy(formname, "non-print"), form = FORM_N; 139 (void)strcpy(modename, "stream"), mode = MODE_S; 140 (void)strcpy(structname, "file"), stru = STRU_F; 141 (void)strcpy(bytename, "8"), bytesize = 8; 142 if (autologin) 143 (void)login(argv[1], NULL, NULL); 144 145 #if (defined(unix) || defined(BSD)) && NBBY == 8 146 /* 147 * this ifdef is to keep someone form "porting" this to an incompatible 148 * system and not checking this out. This way they have to think about it. 149 */ 150 overbose = verbose; 151 if (debug == 0) 152 verbose = -1; 153 if (command("SYST") == COMPLETE && overbose) { 154 char *cp, c; 155 c = 0; 156 cp = strchr(reply_string + 4, ' '); 157 if (cp == NULL) 158 cp = strchr(reply_string + 4, '\r'); 159 if (cp) { 160 if (cp[-1] == '.') 161 cp--; 162 c = *cp; 163 *cp = '\0'; 164 } 165 166 fprintf(ttyout, "Remote system type is %s.\n", reply_string + 4); 167 if (cp) 168 *cp = c; 169 } 170 if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) { 171 if (proxy) 172 unix_proxy = 1; 173 else 174 unix_server = 1; 175 /* 176 * Set type to 0 (not specified by user), 177 * meaning binary by default, but don't bother 178 * telling server. We can use binary 179 * for text files unless changed by the user. 180 */ 181 type = 0; 182 (void)strcpy(typename, "binary"); 183 if (overbose) 184 fprintf(ttyout, "Using %s mode to transfer files.\n", 185 typename); 186 } else { 187 if (proxy) 188 unix_proxy = 0; 189 else 190 unix_server = 0; 191 if (overbose && 192 !strncmp(reply_string, "215 TOPS20", 10)) 193 fputs( 194 "Remember to set tenex mode when transferring binary files from this machine.\n", 195 ttyout); 196 } 197 verbose = overbose; 198 #endif /* unix || BSD */ 199 } 200 } 201 202 /* 203 * login to remote host, using given username & password if supplied 204 */ 205 int 206 login(host, user, pass) 207 const char *host; 208 char *user, *pass; 209 { 210 char tmp[80]; 211 char *acct; 212 char anonpass[MAXLOGNAME + 1 + MAXHOSTNAMELEN]; /* "user@hostname" */ 213 char hostname[MAXHOSTNAMELEN]; 214 struct passwd *pw; 215 int n, aflag = 0, retry = 0; 216 217 acct = NULL; 218 if (user == NULL) { 219 if (ruserpass(host, &user, &pass, &acct) < 0) { 220 code = -1; 221 return (0); 222 } 223 } 224 225 /* 226 * Set up arguments for an anonymous FTP session, if necessary. 227 */ 228 if ((user == NULL || pass == NULL) && anonftp) { 229 memset(anonpass, 0, sizeof(anonpass)); 230 memset(hostname, 0, sizeof(hostname)); 231 232 /* 233 * Set up anonymous login password. 234 */ 235 if ((user = getlogin()) == NULL) { 236 if ((pw = getpwuid(getuid())) == NULL) 237 user = "anonymous"; 238 else 239 user = pw->pw_name; 240 } 241 gethostname(hostname, MAXHOSTNAMELEN); 242 #ifndef DONT_CHEAT_ANONPASS 243 /* 244 * Every anonymous FTP server I've encountered 245 * will accept the string "username@", and will 246 * append the hostname itself. We do this by default 247 * since many servers are picky about not having 248 * a FQDN in the anonymous password. - thorpej@netbsd.org 249 */ 250 snprintf(anonpass, sizeof(anonpass) - 1, "%s@", 251 user); 252 #else 253 snprintf(anonpass, sizeof(anonpass) - 1, "%s@%s", 254 user, hp->h_name); 255 #endif 256 pass = anonpass; 257 user = "anonymous"; /* as per RFC 1635 */ 258 } 259 260 tryagain: 261 if (retry) 262 user = "ftp"; /* some servers only allow "ftp" */ 263 264 while (user == NULL) { 265 char *myname = getlogin(); 266 267 if (myname == NULL && (pw = getpwuid(getuid())) != NULL) 268 myname = pw->pw_name; 269 if (myname) 270 fprintf(ttyout, "Name (%s:%s): ", host, myname); 271 else 272 fprintf(ttyout, "Name (%s): ", host); 273 *tmp = '\0'; 274 (void)fgets(tmp, sizeof(tmp) - 1, stdin); 275 tmp[strlen(tmp) - 1] = '\0'; 276 if (*tmp == '\0') 277 user = myname; 278 else 279 user = tmp; 280 } 281 n = command("USER %s", user); 282 if (n == CONTINUE) { 283 if (pass == NULL) 284 pass = getpass("Password:"); 285 n = command("PASS %s", pass); 286 } 287 if (n == CONTINUE) { 288 aflag++; 289 if (acct == NULL) 290 acct = getpass("Account:"); 291 n = command("ACCT %s", acct); 292 } 293 if ((n != COMPLETE) || 294 (!aflag && acct != NULL && command("ACCT %s", acct) != COMPLETE)) { 295 warnx("Login failed."); 296 if (retry || !anonftp) 297 return (0); 298 else 299 retry = 1; 300 goto tryagain; 301 } 302 if (proxy) 303 return (1); 304 connected = -1; 305 for (n = 0; n < macnum; ++n) { 306 if (!strcmp("init", macros[n].mac_name)) { 307 (void)strcpy(line, "$init"); 308 makeargv(); 309 domacro(margc, margv); 310 break; 311 } 312 } 313 return (1); 314 } 315 316 /* 317 * `another' gets another argument, and stores the new argc and argv. 318 * It reverts to the top level (via main.c's intr()) on EOF/error. 319 * 320 * Returns false if no new arguments have been added. 321 */ 322 int 323 another(pargc, pargv, prompt) 324 int *pargc; 325 char ***pargv; 326 const char *prompt; 327 { 328 int len = strlen(line), ret; 329 330 if (len >= sizeof(line) - 3) { 331 fputs("sorry, arguments too long.\n", ttyout); 332 intr(); 333 } 334 fprintf(ttyout, "(%s) ", prompt); 335 line[len++] = ' '; 336 if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL) 337 intr(); 338 len += strlen(&line[len]); 339 if (len > 0 && line[len - 1] == '\n') 340 line[len - 1] = '\0'; 341 makeargv(); 342 ret = margc > *pargc; 343 *pargc = margc; 344 *pargv = margv; 345 return (ret); 346 } 347 348 /* 349 * glob files given in argv[] from the remote server. 350 * if errbuf isn't NULL, store error messages there instead 351 * of writing to the screen. 352 */ 353 char * 354 remglob(argv, doswitch, errbuf) 355 char *argv[]; 356 int doswitch; 357 char **errbuf; 358 { 359 char temp[MAXPATHLEN]; 360 static char buf[MAXPATHLEN]; 361 static FILE *ftemp = NULL; 362 static char **args; 363 int oldverbose, oldhash, fd; 364 char *cp, *mode; 365 366 if (!mflag) { 367 if (!doglob) 368 args = NULL; 369 else { 370 if (ftemp) { 371 (void)fclose(ftemp); 372 ftemp = NULL; 373 } 374 } 375 return (NULL); 376 } 377 if (!doglob) { 378 if (args == NULL) 379 args = argv; 380 if ((cp = *++args) == NULL) 381 args = NULL; 382 return (cp); 383 } 384 if (ftemp == NULL) { 385 int len; 386 387 if ((cp = getenv("TMPDIR")) == NULL) 388 cp = _PATH_TMP; 389 len = strlen(cp); 390 if (len + sizeof(TMPFILE) + (cp[len-1] != '/') > sizeof(temp)) { 391 warnx("unable to create temporary file: %s", 392 strerror(ENAMETOOLONG)); 393 return (NULL); 394 } 395 396 (void)strcpy(temp, cp); 397 if (temp[len-1] != '/') 398 temp[len++] = '/'; 399 (void)strcpy(&temp[len], TMPFILE); 400 if ((fd = mkstemp(temp)) < 0) { 401 warn("unable to create temporary file %s", temp); 402 return (NULL); 403 } 404 close(fd); 405 oldverbose = verbose; 406 verbose = (errbuf != NULL) ? -1 : 0; 407 oldhash = hash; 408 hash = 0; 409 if (doswitch) 410 pswitch(!proxy); 411 for (mode = "w"; *++argv != NULL; mode = "a") 412 recvrequest("NLST", temp, *argv, mode, 0, 0); 413 if ((code / 100) != COMPLETE) { 414 if (errbuf != NULL) 415 *errbuf = reply_string; 416 } 417 if (doswitch) 418 pswitch(!proxy); 419 verbose = oldverbose; 420 hash = oldhash; 421 ftemp = fopen(temp, "r"); 422 (void)unlink(temp); 423 if (ftemp == NULL) { 424 if (errbuf == NULL) 425 fputs("can't find list of remote files, oops.\n", 426 ttyout); 427 else 428 *errbuf = 429 "can't find list of remote files, oops."; 430 return (NULL); 431 } 432 } 433 if (fgets(buf, sizeof(buf), ftemp) == NULL) { 434 (void)fclose(ftemp); 435 ftemp = NULL; 436 return (NULL); 437 } 438 if ((cp = strchr(buf, '\n')) != NULL) 439 *cp = '\0'; 440 return (buf); 441 } 442 443 int 444 confirm(cmd, file) 445 const char *cmd, *file; 446 { 447 char line[BUFSIZ]; 448 449 if (!interactive || confirmrest) 450 return (1); 451 top: 452 fprintf(ttyout, "%s %s? ", cmd, file); 453 (void)fflush(ttyout); 454 if (fgets(line, sizeof(line), stdin) == NULL) 455 return (0); 456 switch (tolower(*line)) { 457 case 'n': 458 return (0); 459 case 'p': 460 interactive = 0; 461 fputs("Interactive mode: off.\n", ttyout); 462 break; 463 case 'a': 464 confirmrest = 1; 465 fprintf(ttyout, "Prompting off for duration of %s.\n", cmd); 466 break; 467 case 'y': 468 return(1); 469 break; 470 default: 471 fprintf(ttyout, "n, y, p, a, are the only acceptable commands!\n"); 472 goto top; 473 break; 474 } 475 return (1); 476 } 477 478 /* 479 * Glob a local file name specification with 480 * the expectation of a single return value. 481 * Can't control multiple values being expanded 482 * from the expression, we return only the first. 483 */ 484 int 485 globulize(cpp) 486 char **cpp; 487 { 488 glob_t gl; 489 int flags; 490 491 if (!doglob) 492 return (1); 493 494 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; 495 memset(&gl, 0, sizeof(gl)); 496 if (glob(*cpp, flags, NULL, &gl) || 497 gl.gl_pathc == 0) { 498 warnx("%s: not found", *cpp); 499 globfree(&gl); 500 return (0); 501 } 502 /* XXX: caller should check if *cpp changed, and 503 * free(*cpp) if that is the case 504 */ 505 *cpp = strdup(gl.gl_pathv[0]); 506 globfree(&gl); 507 return (1); 508 } 509 510 /* 511 * determine size of remote file 512 */ 513 off_t 514 remotesize(file, noisy) 515 const char *file; 516 int noisy; 517 { 518 int overbose; 519 off_t size; 520 521 overbose = verbose; 522 size = -1; 523 if (debug == 0) 524 verbose = -1; 525 if (command("SIZE %s", file) == COMPLETE) { 526 char *cp, *ep; 527 528 cp = strchr(reply_string, ' '); 529 if (cp != NULL) { 530 cp++; 531 size = strtoq(cp, &ep, 10); 532 if (*ep != '\0' && !isspace(*ep)) 533 size = -1; 534 } 535 } else if (noisy && debug == 0) { 536 fputs(reply_string, ttyout); 537 fputc('\n', ttyout); 538 } 539 verbose = overbose; 540 return (size); 541 } 542 543 /* 544 * determine last modification time (in GMT) of remote file 545 */ 546 time_t 547 remotemodtime(file, noisy) 548 const char *file; 549 int noisy; 550 { 551 int overbose; 552 time_t rtime; 553 int ocode; 554 555 overbose = verbose; 556 ocode = code; 557 rtime = -1; 558 if (debug == 0) 559 verbose = -1; 560 if (command("MDTM %s", file) == COMPLETE) { 561 struct tm timebuf; 562 int yy, mo, day, hour, min, sec; 563 sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo, 564 &day, &hour, &min, &sec); 565 memset(&timebuf, 0, sizeof(timebuf)); 566 timebuf.tm_sec = sec; 567 timebuf.tm_min = min; 568 timebuf.tm_hour = hour; 569 timebuf.tm_mday = day; 570 timebuf.tm_mon = mo - 1; 571 timebuf.tm_year = yy - TM_YEAR_BASE; 572 timebuf.tm_isdst = -1; 573 rtime = mktime(&timebuf); 574 if (rtime == -1 && (noisy || debug != 0)) 575 fprintf(ttyout, "Can't convert %s to a time.\n", reply_string); 576 else 577 rtime += timebuf.tm_gmtoff; /* conv. local -> GMT */ 578 } else if (noisy && debug == 0) { 579 fputs(reply_string, ttyout); 580 fputc('\n', ttyout); 581 } 582 verbose = overbose; 583 if (rtime == -1) 584 code = ocode; 585 return (rtime); 586 } 587 588 /* 589 * Returns true if this is the controlling/foreground process, else false. 590 */ 591 int 592 foregroundproc() 593 { 594 static pid_t pgrp = -1; 595 int ctty_pgrp; 596 597 if (pgrp == -1) 598 pgrp = getpgrp(); 599 600 return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 && 601 ctty_pgrp == pgrp)); 602 } 603 604 static void 605 updateprogressmeter(dummy) 606 int dummy; 607 { 608 int save_errno = errno; 609 610 /* update progressmeter if foreground process or in -m mode */ 611 if (foregroundproc() || progress == -1) 612 progressmeter(0); 613 errno = save_errno; 614 } 615 616 /* 617 * Display a transfer progress bar if progress is non-zero. 618 * SIGALRM is hijacked for use by this function. 619 * - Before the transfer, set filesize to size of file (or -1 if unknown), 620 * and call with flag = -1. This starts the once per second timer, 621 * and a call to updateprogressmeter() upon SIGALRM. 622 * - During the transfer, updateprogressmeter will call progressmeter 623 * with flag = 0 624 * - After the transfer, call with flag = 1 625 */ 626 static struct timeval start; 627 628 void 629 progressmeter(flag) 630 int flag; 631 { 632 /* 633 * List of order of magnitude prefixes. 634 * The last is `P', as 2^64 = 16384 Petabytes 635 */ 636 static const char prefixes[] = " KMGTP"; 637 638 static struct timeval lastupdate; 639 static off_t lastsize; 640 struct timeval now, td, wait; 641 off_t cursize, abbrevsize; 642 double elapsed; 643 int ratio, barlength, i, remaining; 644 char buf[256]; 645 646 if (flag == -1) { 647 (void)gettimeofday(&start, (struct timezone *)0); 648 lastupdate = start; 649 lastsize = restart_point; 650 } 651 (void)gettimeofday(&now, (struct timezone *)0); 652 if (!progress || filesize <= 0) 653 return; 654 cursize = bytes + restart_point; 655 656 ratio = cursize * 100 / filesize; 657 ratio = MAX(ratio, 0); 658 ratio = MIN(ratio, 100); 659 snprintf(buf, sizeof(buf), "\r%3d%% ", ratio); 660 661 barlength = ttywidth - 30; 662 if (barlength > 0) { 663 i = barlength * ratio / 100; 664 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 665 "|%.*s%*s|", i, 666 "*****************************************************************************" 667 "*****************************************************************************", 668 barlength - i, ""); 669 } 670 671 i = 0; 672 abbrevsize = cursize; 673 while (abbrevsize >= 100000 && i < sizeof(prefixes)) { 674 i++; 675 abbrevsize >>= 10; 676 } 677 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 678 " %5qd %c%c ", (quad_t)abbrevsize, prefixes[i], 679 prefixes[i] == ' ' ? ' ' : 'B'); 680 681 timersub(&now, &lastupdate, &wait); 682 if (cursize > lastsize) { 683 lastupdate = now; 684 lastsize = cursize; 685 if (wait.tv_sec >= STALLTIME) { /* fudge out stalled time */ 686 start.tv_sec += wait.tv_sec; 687 start.tv_usec += wait.tv_usec; 688 } 689 wait.tv_sec = 0; 690 } 691 692 timersub(&now, &start, &td); 693 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 694 695 if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { 696 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 697 " --:-- ETA"); 698 } else if (wait.tv_sec >= STALLTIME) { 699 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 700 " - stalled -"); 701 } else { 702 remaining = (int)((filesize - restart_point) / 703 (bytes / elapsed) - elapsed); 704 i = remaining / 3600; 705 if (i) 706 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 707 "%2d:", i); 708 else 709 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 710 " "); 711 i = remaining % 3600; 712 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 713 "%02d:%02d ETA", i / 60, i % 60); 714 } 715 (void)write(fileno(ttyout), buf, strlen(buf)); 716 717 if (flag == -1) { 718 (void)signal(SIGALRM, updateprogressmeter); 719 alarmtimer(1); /* set alarm timer for 1 Hz */ 720 } else if (flag == 1) { 721 alarmtimer(0); 722 (void)putc('\n', ttyout); 723 } 724 fflush(ttyout); 725 } 726 727 /* 728 * Display transfer statistics. 729 * Requires start to be initialised by progressmeter(-1), 730 * direction to be defined by xfer routines, and filesize and bytes 731 * to be updated by xfer routines 732 * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR 733 * instead of TTYOUT. 734 */ 735 void 736 ptransfer(siginfo) 737 int siginfo; 738 { 739 struct timeval now, td; 740 double elapsed; 741 off_t bs; 742 int meg, remaining, hh; 743 char buf[100]; 744 745 if (!verbose && !siginfo) 746 return; 747 748 (void)gettimeofday(&now, (struct timezone *)0); 749 timersub(&now, &start, &td); 750 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 751 bs = bytes / (elapsed == 0.0 ? 1 : elapsed); 752 meg = 0; 753 if (bs > (1024 * 1024)) 754 meg = 1; 755 (void)snprintf(buf, sizeof(buf), 756 "%qd byte%s %s in %.2f seconds (%.2f %sB/s)\n", 757 (quad_t)bytes, bytes == 1 ? "" : "s", direction, elapsed, 758 bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K"); 759 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 760 && bytes + restart_point <= filesize) { 761 remaining = (int)((filesize - restart_point) / 762 (bytes / elapsed) - elapsed); 763 hh = remaining / 3600; 764 remaining %= 3600; 765 /* "buf+len(buf) -1" to overwrite \n */ 766 snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf), 767 " ETA: %02d:%02d:%02d\n", hh, remaining / 60, 768 remaining % 60); 769 } 770 (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf)); 771 } 772 773 /* 774 * List words in stringlist, vertically arranged 775 */ 776 void 777 list_vertical(sl) 778 StringList *sl; 779 { 780 int i, j, w; 781 int columns, width, lines, items; 782 char *p; 783 784 width = items = 0; 785 786 for (i = 0 ; i < sl->sl_cur ; i++) { 787 w = strlen(sl->sl_str[i]); 788 if (w > width) 789 width = w; 790 } 791 width = (width + 8) &~ 7; 792 793 columns = ttywidth / width; 794 if (columns == 0) 795 columns = 1; 796 lines = (sl->sl_cur + columns - 1) / columns; 797 for (i = 0; i < lines; i++) { 798 for (j = 0; j < columns; j++) { 799 p = sl->sl_str[j * lines + i]; 800 if (p) 801 fputs(p, ttyout); 802 if (j * lines + i + lines >= sl->sl_cur) { 803 putc('\n', ttyout); 804 break; 805 } 806 w = strlen(p); 807 while (w < width) { 808 w = (w + 8) &~ 7; 809 (void)putc('\t', ttyout); 810 } 811 } 812 } 813 } 814 815 /* 816 * Update the global ttywidth value, using TIOCGWINSZ. 817 */ 818 void 819 setttywidth(a) 820 int a; 821 { 822 int save_errno = errno; 823 struct winsize winsize; 824 825 if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1) 826 ttywidth = winsize.ws_col ? winsize.ws_col : 80; 827 else 828 ttywidth = 80; 829 errno = save_errno; 830 } 831 832 /* 833 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 834 */ 835 void 836 alarmtimer(wait) 837 int wait; 838 { 839 struct itimerval itv; 840 841 itv.it_value.tv_sec = wait; 842 itv.it_value.tv_usec = 0; 843 itv.it_interval = itv.it_value; 844 setitimer(ITIMER_REAL, &itv, NULL); 845 } 846 847 /* 848 * Setup or cleanup EditLine structures 849 */ 850 #ifndef SMALL 851 void 852 controlediting() 853 { 854 if (editing && el == NULL && hist == NULL) { 855 el = el_init(__progname, stdin, ttyout); /* init editline */ 856 hist = history_init(); /* init the builtin history */ 857 history(hist, H_EVENT, 100); /* remember 100 events */ 858 el_set(el, EL_HIST, history, hist); /* use history */ 859 860 el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */ 861 el_set(el, EL_PROMPT, prompt); /* set the prompt function */ 862 863 /* add local file completion, bind to TAB */ 864 el_set(el, EL_ADDFN, "ftp-complete", 865 "Context sensitive argument completion", 866 complete); 867 el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 868 869 el_source(el, NULL); /* read ~/.editrc */ 870 el_set(el, EL_SIGNAL, 1); 871 } else if (!editing) { 872 if (hist) { 873 history_end(hist); 874 hist = NULL; 875 } 876 if (el) { 877 el_end(el); 878 el = NULL; 879 } 880 } 881 } 882 #endif /* !SMALL */ 883