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