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