1 /* $OpenBSD: util.c,v 1.66 2014/01/29 16:58:21 dcoppa 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 char *action; 757 758 void 759 progressmeter(int flag, const char *filename) 760 { 761 /* 762 * List of order of magnitude prefixes. 763 * The last is `P', as 2^64 = 16384 Petabytes 764 */ 765 static const char prefixes[] = " KMGTP"; 766 767 static struct timeval lastupdate; 768 static off_t lastsize; 769 static char *title = NULL; 770 struct timeval now, td, wait; 771 off_t cursize, abbrevsize; 772 double elapsed; 773 int ratio, barlength, i, remaining, overhead = 30; 774 char buf[512]; 775 776 if (flag == -1) { 777 (void)gettimeofday(&start, (struct timezone *)0); 778 lastupdate = start; 779 lastsize = restart_point; 780 } 781 (void)gettimeofday(&now, (struct timezone *)0); 782 if (!progress || filesize < 0) 783 return; 784 cursize = bytes + restart_point; 785 786 if (filesize) 787 ratio = cursize * 100 / filesize; 788 else 789 ratio = 100; 790 ratio = MAX(ratio, 0); 791 ratio = MIN(ratio, 100); 792 if (!verbose && flag == -1) { 793 filename = basename(filename); 794 if (filename != NULL) 795 title = strdup(filename); 796 } 797 798 buf[0] = 0; 799 if (!verbose && action != NULL) { 800 int l = strlen(action); 801 char *dotdot = ""; 802 803 if (l < 7) 804 l = 7; 805 else if (l > 12) { 806 l = 12; 807 dotdot = "..."; 808 overhead += 3; 809 } 810 snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action, 811 dotdot); 812 overhead += l + 1; 813 } else 814 snprintf(buf, sizeof(buf), "\r"); 815 816 if (!verbose && title != NULL) { 817 int l = strlen(title); 818 char *dotdot = ""; 819 820 if (l < 12) 821 l = 12; 822 else if (l > 25) { 823 l = 22; 824 dotdot = "..."; 825 overhead += 3; 826 } 827 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 828 "%-*.*s%s %3d%% ", l, l, title, 829 dotdot, ratio); 830 overhead += l + 1; 831 } else 832 snprintf(buf, sizeof(buf), "\r%3d%% ", ratio); 833 834 barlength = ttywidth - overhead; 835 if (barlength > 0) { 836 i = barlength * ratio / 100; 837 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 838 "|%.*s%*s|", i, 839 "*******************************************************" 840 "*******************************************************" 841 "*******************************************************" 842 "*******************************************************" 843 "*******************************************************" 844 "*******************************************************" 845 "*******************************************************", 846 barlength - i, ""); 847 } 848 849 i = 0; 850 abbrevsize = cursize; 851 while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) { 852 i++; 853 abbrevsize >>= 10; 854 } 855 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 856 " %5lld %c%c ", (long long)abbrevsize, prefixes[i], 857 prefixes[i] == ' ' ? ' ' : 'B'); 858 859 timersub(&now, &lastupdate, &wait); 860 if (cursize > lastsize) { 861 lastupdate = now; 862 lastsize = cursize; 863 if (wait.tv_sec >= STALLTIME) { /* fudge out stalled time */ 864 start.tv_sec += wait.tv_sec; 865 start.tv_usec += wait.tv_usec; 866 } 867 wait.tv_sec = 0; 868 } 869 870 timersub(&now, &start, &td); 871 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 872 873 if (flag == 1) { 874 i = (int)elapsed / 3600; 875 if (i) 876 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 877 "%2d:", i); 878 else 879 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 880 " "); 881 i = (int)elapsed % 3600; 882 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 883 "%02d:%02d ", i / 60, i % 60); 884 } else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { 885 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 886 " --:-- ETA"); 887 } else if (wait.tv_sec >= STALLTIME) { 888 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 889 " - stalled -"); 890 } else { 891 remaining = (int)((filesize - restart_point) / 892 (bytes / elapsed) - elapsed); 893 i = remaining / 3600; 894 if (i) 895 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 896 "%2d:", i); 897 else 898 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 899 " "); 900 i = remaining % 3600; 901 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 902 "%02d:%02d ETA", i / 60, i % 60); 903 } 904 (void)write(fileno(ttyout), buf, strlen(buf)); 905 906 if (flag == -1) { 907 (void)signal(SIGALRM, updateprogressmeter); 908 alarmtimer(1); /* set alarm timer for 1 Hz */ 909 } else if (flag == 1) { 910 alarmtimer(0); 911 (void)putc('\n', ttyout); 912 if (title != NULL) { 913 free(title); 914 title = NULL; 915 } 916 } 917 fflush(ttyout); 918 } 919 920 /* 921 * Display transfer statistics. 922 * Requires start to be initialised by progressmeter(-1), 923 * direction to be defined by xfer routines, and filesize and bytes 924 * to be updated by xfer routines 925 * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR 926 * instead of TTYOUT. 927 */ 928 void 929 ptransfer(int siginfo) 930 { 931 struct timeval now, td; 932 double elapsed; 933 off_t bs; 934 int meg, remaining, hh; 935 char buf[100]; 936 937 if (!verbose && !siginfo) 938 return; 939 940 (void)gettimeofday(&now, (struct timezone *)0); 941 timersub(&now, &start, &td); 942 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 943 bs = bytes / (elapsed == 0.0 ? 1 : elapsed); 944 meg = 0; 945 if (bs > (1024 * 1024)) 946 meg = 1; 947 (void)snprintf(buf, sizeof(buf), 948 "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n", 949 (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed, 950 bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K"); 951 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 952 && bytes + restart_point <= filesize) { 953 remaining = (int)((filesize - restart_point) / 954 (bytes / elapsed) - elapsed); 955 hh = remaining / 3600; 956 remaining %= 3600; 957 /* "buf+len(buf) -1" to overwrite \n */ 958 snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf), 959 " ETA: %02d:%02d:%02d\n", hh, remaining / 60, 960 remaining % 60); 961 } 962 (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf)); 963 } 964 965 /* 966 * List words in stringlist, vertically arranged 967 */ 968 #ifndef SMALL 969 void 970 list_vertical(StringList *sl) 971 { 972 int i, j, w; 973 int columns, width, lines; 974 char *p; 975 976 width = 0; 977 978 for (i = 0 ; i < sl->sl_cur ; i++) { 979 w = strlen(sl->sl_str[i]); 980 if (w > width) 981 width = w; 982 } 983 width = (width + 8) &~ 7; 984 985 columns = ttywidth / width; 986 if (columns == 0) 987 columns = 1; 988 lines = (sl->sl_cur + columns - 1) / columns; 989 for (i = 0; i < lines; i++) { 990 for (j = 0; j < columns; j++) { 991 p = sl->sl_str[j * lines + i]; 992 if (p) 993 fputs(p, ttyout); 994 if (j * lines + i + lines >= sl->sl_cur) { 995 putc('\n', ttyout); 996 break; 997 } 998 w = strlen(p); 999 while (w < width) { 1000 w = (w + 8) &~ 7; 1001 (void)putc('\t', ttyout); 1002 } 1003 } 1004 } 1005 } 1006 #endif /* !SMALL */ 1007 1008 /* 1009 * Update the global ttywidth value, using TIOCGWINSZ. 1010 */ 1011 /* ARGSUSED */ 1012 void 1013 setttywidth(int signo) 1014 { 1015 int save_errno = errno; 1016 struct winsize winsize; 1017 1018 if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1) 1019 ttywidth = winsize.ws_col ? winsize.ws_col : 80; 1020 else 1021 ttywidth = 80; 1022 errno = save_errno; 1023 } 1024 1025 /* 1026 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 1027 */ 1028 void 1029 alarmtimer(int wait) 1030 { 1031 struct itimerval itv; 1032 1033 itv.it_value.tv_sec = wait; 1034 itv.it_value.tv_usec = 0; 1035 itv.it_interval = itv.it_value; 1036 setitimer(ITIMER_REAL, &itv, NULL); 1037 } 1038 1039 /* 1040 * Setup or cleanup EditLine structures 1041 */ 1042 #ifndef SMALL 1043 void 1044 controlediting(void) 1045 { 1046 HistEvent hev; 1047 1048 if (editing && el == NULL && hist == NULL) { 1049 el = el_init(__progname, stdin, ttyout, stderr); /* init editline */ 1050 hist = history_init(); /* init the builtin history */ 1051 history(hist, &hev, H_SETSIZE, 100); /* remember 100 events */ 1052 el_set(el, EL_HIST, history, hist); /* use history */ 1053 1054 el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */ 1055 el_set(el, EL_PROMPT, prompt); /* set the prompt function */ 1056 1057 /* add local file completion, bind to TAB */ 1058 el_set(el, EL_ADDFN, "ftp-complete", 1059 "Context sensitive argument completion", 1060 complete); 1061 el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 1062 1063 el_source(el, NULL); /* read ~/.editrc */ 1064 el_set(el, EL_SIGNAL, 1); 1065 } else if (!editing) { 1066 if (hist) { 1067 history_end(hist); 1068 hist = NULL; 1069 } 1070 if (el) { 1071 el_end(el); 1072 el = NULL; 1073 } 1074 } 1075 } 1076 #endif /* !SMALL */ 1077 1078