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