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