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