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