1 /* $OpenBSD: util.c,v 1.93 2020/07/06 17:11:29 deraadt 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 && !anonftp) { 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 } else 286 exit(0); 287 } 288 n = command("USER %s", user); 289 if (n == CONTINUE) { 290 if (pass == NULL) 291 pass = getpass("Password:"); 292 n = command("PASS %s", pass); 293 } 294 if (n == CONTINUE) { 295 aflag++; 296 if (acctname == NULL) 297 acctname = getpass("Account:"); 298 n = command("ACCT %s", acctname); 299 } 300 if ((n != COMPLETE) || 301 (!aflag && acctname != NULL && command("ACCT %s", acctname) != COMPLETE)) { 302 warnx("Login %s failed.", user); 303 if (retry || !anonftp) 304 return (0); 305 else 306 retry = 1; 307 goto tryagain; 308 } 309 if (proxy) 310 return (1); 311 connected = -1; 312 #ifndef SMALL 313 for (n = 0; n < macnum; ++n) { 314 if (!strcmp("init", macros[n].mac_name)) { 315 (void)strlcpy(line, "$init", sizeof line); 316 makeargv(); 317 domacro(margc, margv); 318 break; 319 } 320 } 321 #endif /* SMALL */ 322 return (1); 323 } 324 325 /* 326 * `another' gets another argument, and stores the new argc and argv. 327 * It reverts to the top level (via main.c's intr()) on EOF/error. 328 * 329 * Returns false if no new arguments have been added. 330 */ 331 #ifndef SMALL 332 int 333 another(int *pargc, char ***pargv, const char *prompt) 334 { 335 int len = strlen(line), ret; 336 337 if (len >= sizeof(line) - 3) { 338 fputs("sorry, arguments too long.\n", ttyout); 339 intr(); 340 } 341 fprintf(ttyout, "(%s) ", prompt); 342 line[len++] = ' '; 343 if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL) { 344 clearerr(stdin); 345 intr(); 346 } 347 len += strlen(&line[len]); 348 if (len > 0 && line[len - 1] == '\n') 349 line[len - 1] = '\0'; 350 makeargv(); 351 ret = margc > *pargc; 352 *pargc = margc; 353 *pargv = margv; 354 return (ret); 355 } 356 #endif /* !SMALL */ 357 358 /* 359 * glob files given in argv[] from the remote server. 360 * if errbuf isn't NULL, store error messages there instead 361 * of writing to the screen. 362 * if type isn't NULL, use LIST instead of NLST, and store filetype. 363 * 'd' means directory, 's' means symbolic link, '-' means plain 364 * file. 365 */ 366 char * 367 remglob2(char *argv[], int doswitch, char **errbuf, FILE **ftemp, char *type) 368 { 369 char temp[PATH_MAX], *bufp, *cp, *lmode; 370 static char buf[PATH_MAX], **args; 371 int oldverbose, oldhash, fd; 372 373 if (!mflag) { 374 if (!doglob) 375 args = NULL; 376 else { 377 if (*ftemp) { 378 (void)fclose(*ftemp); 379 *ftemp = NULL; 380 } 381 } 382 return (NULL); 383 } 384 if (!doglob) { 385 if (args == NULL) 386 args = argv; 387 if ((cp = *++args) == NULL) 388 args = NULL; 389 return (cp); 390 } 391 if (*ftemp == NULL) { 392 int len; 393 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)) == -1) { 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 #ifndef SMALL 441 again: 442 #endif 443 if (fgets(buf, sizeof(buf), *ftemp) == NULL) { 444 (void)fclose(*ftemp); 445 *ftemp = NULL; 446 return (NULL); 447 } 448 449 buf[strcspn(buf, "\n")] = '\0'; 450 bufp = buf; 451 452 #ifndef SMALL 453 if (type) { 454 parse_list(&bufp, type); 455 if (!bufp || 456 (bufp[0] == '.' && /* LIST defaults to -a on some ftp */ 457 (bufp[1] == '\0' || /* servers. Ignore '.' and '..'. */ 458 (bufp[1] == '.' && bufp[2] == '\0')))) 459 goto again; 460 } 461 #endif /* !SMALL */ 462 463 return (bufp); 464 } 465 466 /* 467 * wrapper for remglob2 468 */ 469 char * 470 remglob(char *argv[], int doswitch, char **errbuf) 471 { 472 static FILE *ftemp = NULL; 473 474 return remglob2(argv, doswitch, errbuf, &ftemp, NULL); 475 } 476 477 #ifndef SMALL 478 int 479 confirm(const char *cmd, const char *file) 480 { 481 char str[BUFSIZ]; 482 483 if (file && (confirmrest || !interactive)) 484 return (1); 485 top: 486 if (file) 487 fprintf(ttyout, "%s %s? ", cmd, file); 488 else 489 fprintf(ttyout, "Continue with %s? ", cmd); 490 (void)fflush(ttyout); 491 if (fgets(str, sizeof(str), stdin) == NULL) 492 goto quit; 493 switch (tolower((unsigned char)*str)) { 494 case '?': 495 fprintf(ttyout, 496 "? help\n" 497 "a answer yes to all\n" 498 "n answer no\n" 499 "p turn off prompt mode\n" 500 "q answer no to all\n" 501 "y answer yes\n"); 502 goto top; 503 case 'a': 504 confirmrest = 1; 505 fprintf(ttyout, "Prompting off for duration of %s.\n", 506 cmd); 507 break; 508 case 'n': 509 return (0); 510 case 'p': 511 interactive = 0; 512 fputs("Interactive mode: off.\n", ttyout); 513 break; 514 case 'q': 515 quit: 516 mflag = 0; 517 clearerr(stdin); 518 return (0); 519 case 'y': 520 return(1); 521 break; 522 default: 523 fprintf(ttyout, "?, a, n, p, q, y " 524 "are the only acceptable commands!\n"); 525 goto top; 526 break; 527 } 528 return (1); 529 } 530 #endif /* !SMALL */ 531 532 /* 533 * Glob a local file name specification with 534 * the expectation of a single return value. 535 * Can't control multiple values being expanded 536 * from the expression, we return only the first. 537 */ 538 int 539 globulize(char **cpp) 540 { 541 glob_t gl; 542 int flags; 543 544 if (!doglob) 545 return (1); 546 547 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; 548 memset(&gl, 0, sizeof(gl)); 549 if (glob(*cpp, flags, NULL, &gl) || 550 gl.gl_pathc == 0) { 551 warnx("%s: not found", *cpp); 552 globfree(&gl); 553 return (0); 554 } 555 /* XXX: caller should check if *cpp changed, and 556 * free(*cpp) if that is the case 557 */ 558 *cpp = strdup(gl.gl_pathv[0]); 559 if (*cpp == NULL) 560 err(1, NULL); 561 globfree(&gl); 562 return (1); 563 } 564 565 /* 566 * determine size of remote file 567 */ 568 off_t 569 remotesize(const char *file, int noisy) 570 { 571 int overbose; 572 off_t size; 573 574 overbose = verbose; 575 size = -1; 576 #ifndef SMALL 577 if (!debug) 578 #endif /* !SMALL */ 579 verbose = -1; 580 if (command("SIZE %s", file) == COMPLETE) { 581 char *cp, *ep; 582 583 cp = strchr(reply_string, ' '); 584 if (cp != NULL) { 585 cp++; 586 size = strtoll(cp, &ep, 10); 587 if (*ep != '\0' && !isspace((unsigned char)*ep)) 588 size = -1; 589 } 590 } else if (noisy 591 #ifndef SMALL 592 && !debug 593 #endif /* !SMALL */ 594 ) { 595 fputs(reply_string, ttyout); 596 fputc('\n', ttyout); 597 } 598 verbose = overbose; 599 return (size); 600 } 601 602 /* 603 * determine last modification time (in GMT) of remote file 604 */ 605 time_t 606 remotemodtime(const char *file, int noisy) 607 { 608 int overbose; 609 time_t rtime; 610 int ocode; 611 612 overbose = verbose; 613 ocode = code; 614 rtime = -1; 615 #ifndef SMALL 616 if (!debug) 617 #endif /* !SMALL */ 618 verbose = -1; 619 if (command("MDTM %s", file) == COMPLETE) { 620 struct tm timebuf; 621 int yy, mo, day, hour, min, sec; 622 /* 623 * time-val = 14DIGIT [ "." 1*DIGIT ] 624 * YYYYMMDDHHMMSS[.sss] 625 * mdtm-response = "213" SP time-val CRLF / error-response 626 */ 627 /* TODO: parse .sss as well, use timespecs. */ 628 char *timestr = reply_string; 629 630 /* Repair `19%02d' bug on server side */ 631 while (!isspace((unsigned char)*timestr)) 632 timestr++; 633 while (isspace((unsigned char)*timestr)) 634 timestr++; 635 if (strncmp(timestr, "191", 3) == 0) { 636 fprintf(ttyout, 637 "Y2K warning! Fixed incorrect time-val received from server.\n"); 638 timestr[0] = ' '; 639 timestr[1] = '2'; 640 timestr[2] = '0'; 641 } 642 sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo, 643 &day, &hour, &min, &sec); 644 memset(&timebuf, 0, sizeof(timebuf)); 645 timebuf.tm_sec = sec; 646 timebuf.tm_min = min; 647 timebuf.tm_hour = hour; 648 timebuf.tm_mday = day; 649 timebuf.tm_mon = mo - 1; 650 timebuf.tm_year = yy - 1900; 651 timebuf.tm_isdst = -1; 652 rtime = mktime(&timebuf); 653 if (rtime == -1 && (noisy 654 #ifndef SMALL 655 || debug 656 #endif /* !SMALL */ 657 )) 658 fprintf(ttyout, "Can't convert %s to a time.\n", reply_string); 659 else 660 rtime += timebuf.tm_gmtoff; /* conv. local -> GMT */ 661 } else if (noisy 662 #ifndef SMALL 663 && !debug 664 #endif /* !SMALL */ 665 ) { 666 fputs(reply_string, ttyout); 667 fputc('\n', ttyout); 668 } 669 verbose = overbose; 670 if (rtime == -1) 671 code = ocode; 672 return (rtime); 673 } 674 675 /* 676 * Ensure file is in or under dir. 677 * Returns 1 if so, 0 if not (or an error occurred). 678 */ 679 int 680 fileindir(const char *file, const char *dir) 681 { 682 char parentdirbuf[PATH_MAX], *parentdir; 683 char realdir[PATH_MAX]; 684 size_t dirlen; 685 686 /* determine parent directory of file */ 687 (void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf)); 688 parentdir = dirname(parentdirbuf); 689 if (strcmp(parentdir, ".") == 0) 690 return 1; /* current directory is ok */ 691 692 /* find the directory */ 693 if (realpath(parentdir, realdir) == NULL) { 694 warn("Unable to determine real path of `%s'", parentdir); 695 return 0; 696 } 697 if (realdir[0] != '/') /* relative result is ok */ 698 return 1; 699 700 dirlen = strlen(dir); 701 if (strncmp(realdir, dir, dirlen) == 0 && 702 (realdir[dirlen] == '/' || realdir[dirlen] == '\0')) 703 return 1; 704 return 0; 705 } 706 707 708 /* 709 * Returns true if this is the controlling/foreground process, else false. 710 */ 711 int 712 foregroundproc(void) 713 { 714 static pid_t pgrp = -1; 715 int ctty_pgrp; 716 717 if (pgrp == -1) 718 pgrp = getpgrp(); 719 720 return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 && 721 ctty_pgrp == pgrp)); 722 } 723 724 /* ARGSUSED */ 725 static void 726 updateprogressmeter(int signo) 727 { 728 int save_errno = errno; 729 730 /* update progressmeter if foreground process or in -m mode */ 731 if (foregroundproc() || progress == -1) 732 progressmeter(0, NULL); 733 errno = save_errno; 734 } 735 736 /* 737 * Display a transfer progress bar if progress is non-zero. 738 * SIGALRM is hijacked for use by this function. 739 * - Before the transfer, set filesize to size of file (or -1 if unknown), 740 * and call with flag = -1. This starts the once per second timer, 741 * and a call to updateprogressmeter() upon SIGALRM. 742 * - During the transfer, updateprogressmeter will call progressmeter 743 * with flag = 0 744 * - After the transfer, call with flag = 1 745 */ 746 static struct timespec start; 747 748 char *action; 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 timespec lastupdate; 760 static off_t lastsize; 761 static char *title = NULL; 762 struct timespec 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 clock_gettime(CLOCK_MONOTONIC, &start); 770 lastupdate = start; 771 lastsize = restart_point; 772 } 773 clock_gettime(CLOCK_MONOTONIC, &now); 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 = MAXIMUM(ratio, 0); 783 ratio = MINIMUM(ratio, 100); 784 if (!verbose && flag == -1) { 785 filename = basename(filename); 786 if (filename != NULL) { 787 free(title); 788 title = strdup(filename); 789 } 790 } 791 792 buf[0] = 0; 793 if (!verbose && action != NULL) { 794 int l = strlen(action); 795 char *dotdot = ""; 796 797 if (l < 7) 798 l = 7; 799 else if (l > 12) { 800 l = 12; 801 dotdot = "..."; 802 overhead += 3; 803 } 804 snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action, 805 dotdot); 806 overhead += l + 1; 807 } else 808 snprintf(buf, sizeof(buf), "\r"); 809 810 if (!verbose && title != NULL) { 811 int l = strlen(title); 812 char *dotdot = ""; 813 814 if (l < 12) 815 l = 12; 816 else if (l > 25) { 817 l = 22; 818 dotdot = "..."; 819 overhead += 3; 820 } 821 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 822 "%-*.*s%s %3d%% ", l, l, title, 823 dotdot, ratio); 824 overhead += l + 1; 825 } else 826 snprintf(buf, sizeof(buf), "\r%3d%% ", ratio); 827 828 barlength = ttywidth - overhead; 829 if (barlength > 0) { 830 i = barlength * ratio / 100; 831 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 832 "|%.*s%*s|", i, 833 "*******************************************************" 834 "*******************************************************" 835 "*******************************************************" 836 "*******************************************************" 837 "*******************************************************" 838 "*******************************************************" 839 "*******************************************************", 840 barlength - i, ""); 841 } 842 843 i = 0; 844 abbrevsize = cursize; 845 while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) { 846 i++; 847 abbrevsize >>= 10; 848 } 849 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 850 " %5lld %c%c ", (long long)abbrevsize, prefixes[i], 851 prefixes[i] == ' ' ? ' ' : 'B'); 852 853 timespecsub(&now, &lastupdate, &wait); 854 if (cursize > lastsize) { 855 lastupdate = now; 856 lastsize = cursize; 857 if (wait.tv_sec >= STALLTIME) { /* fudge out stalled time */ 858 start.tv_sec += wait.tv_sec; 859 start.tv_nsec += wait.tv_nsec; 860 } 861 wait.tv_sec = 0; 862 } 863 864 timespecsub(&now, &start, &td); 865 elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0); 866 867 if (flag == 1) { 868 i = (int)elapsed / 3600; 869 if (i) 870 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 871 "%2d:", i); 872 else 873 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 874 " "); 875 i = (int)elapsed % 3600; 876 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 877 "%02d:%02d ", i / 60, i % 60); 878 } else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { 879 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 880 " --:-- ETA"); 881 } else if (wait.tv_sec >= STALLTIME) { 882 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 883 " - stalled -"); 884 } else { 885 remaining = (int)((filesize - restart_point) / 886 (bytes / elapsed) - elapsed); 887 i = remaining / 3600; 888 if (i) 889 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 890 "%2d:", i); 891 else 892 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 893 " "); 894 i = remaining % 3600; 895 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 896 "%02d:%02d ETA", i / 60, i % 60); 897 } 898 (void)write(fileno(ttyout), buf, strlen(buf)); 899 900 if (flag == -1) { 901 (void)signal(SIGALRM, updateprogressmeter); 902 alarmtimer(1); /* set alarm timer for 1 Hz */ 903 } else if (flag == 1) { 904 alarmtimer(0); 905 (void)putc('\n', ttyout); 906 free(title); 907 title = NULL; 908 } 909 fflush(ttyout); 910 } 911 912 /* 913 * Display transfer statistics. 914 * Requires start to be initialised by progressmeter(-1), 915 * direction to be defined by xfer routines, and filesize and bytes 916 * to be updated by xfer routines 917 * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR 918 * instead of TTYOUT. 919 */ 920 void 921 ptransfer(int siginfo) 922 { 923 struct timespec now, td; 924 double elapsed, pace; 925 off_t bs; 926 int meg, remaining, hh; 927 char buf[100]; 928 929 if (!verbose && !siginfo) 930 return; 931 932 clock_gettime(CLOCK_MONOTONIC, &now); 933 timespecsub(&now, &start, &td); 934 elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0); 935 bs = bytes / (elapsed == 0.0 ? 1 : elapsed); 936 meg = 0; 937 if (bs > (1024 * 1024)) 938 meg = 1; 939 940 pace = bs / (1024.0 * (meg ? 1024.0 : 1.0)); 941 (void)snprintf(buf, sizeof(buf), 942 "%lld byte%s %s in %lld.%02d seconds (%lld.%02d %sB/s)\n", 943 (long long)bytes, bytes == 1 ? "" : "s", direction, 944 (long long)elapsed, (int)(elapsed * 100.0) % 100, 945 (long long)pace, (int)(pace * 100.0) % 100, 946 meg ? "M" : "K"); 947 948 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 && 949 bytes + restart_point <= filesize) { 950 remaining = (int)((filesize - restart_point) / 951 (bytes / elapsed) - elapsed); 952 hh = remaining / 3600; 953 remaining %= 3600; 954 955 /* "buf+len(buf) -1" to overwrite \n */ 956 snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf), 957 " ETA: %02d:%02d:%02d\n", hh, remaining / 60, 958 remaining % 60); 959 } 960 (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf)); 961 } 962 963 /* 964 * List words in stringlist, vertically arranged 965 */ 966 #ifndef SMALL 967 void 968 list_vertical(StringList *sl) 969 { 970 int i, j, w; 971 int columns, width, lines; 972 char *p; 973 974 width = 0; 975 976 for (i = 0 ; i < sl->sl_cur ; i++) { 977 w = strlen(sl->sl_str[i]); 978 if (w > width) 979 width = w; 980 } 981 width = (width + 8) &~ 7; 982 983 columns = ttywidth / width; 984 if (columns == 0) 985 columns = 1; 986 lines = (sl->sl_cur + columns - 1) / columns; 987 for (i = 0; i < lines; i++) { 988 for (j = 0; j < columns; j++) { 989 p = sl->sl_str[j * lines + i]; 990 if (p) 991 fputs(p, ttyout); 992 if (j * lines + i + lines >= sl->sl_cur) { 993 putc('\n', ttyout); 994 break; 995 } 996 w = strlen(p); 997 while (w < width) { 998 w = (w + 8) &~ 7; 999 (void)putc('\t', ttyout); 1000 } 1001 } 1002 } 1003 } 1004 #endif /* !SMALL */ 1005 1006 /* 1007 * Update the global ttywidth value, using TIOCGWINSZ. 1008 */ 1009 /* ARGSUSED */ 1010 void 1011 setttywidth(int signo) 1012 { 1013 int save_errno = errno; 1014 struct winsize winsize; 1015 1016 if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1) 1017 ttywidth = winsize.ws_col ? winsize.ws_col : 80; 1018 else 1019 ttywidth = 80; 1020 errno = save_errno; 1021 } 1022 1023 /* 1024 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 1025 */ 1026 void 1027 alarmtimer(int wait) 1028 { 1029 int save_errno = errno; 1030 struct itimerval itv; 1031 1032 itv.it_value.tv_sec = wait; 1033 itv.it_value.tv_usec = 0; 1034 itv.it_interval = itv.it_value; 1035 setitimer(ITIMER_REAL, &itv, NULL); 1036 errno = save_errno; 1037 } 1038 1039 /* 1040 * Setup or cleanup EditLine structures 1041 */ 1042 #ifndef SMALL 1043 void 1044 controlediting(void) 1045 { 1046 HistEvent hev; 1047 1048 if (editing && el == NULL && hist == NULL) { 1049 el = el_init(__progname, stdin, ttyout, stderr); /* init editline */ 1050 hist = history_init(); /* init the builtin history */ 1051 history(hist, &hev, H_SETSIZE, 100); /* remember 100 events */ 1052 el_set(el, EL_HIST, history, hist); /* use history */ 1053 1054 el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */ 1055 el_set(el, EL_PROMPT, prompt); /* set the prompt function */ 1056 1057 /* add local file completion, bind to TAB */ 1058 el_set(el, EL_ADDFN, "ftp-complete", 1059 "Context sensitive argument completion", 1060 complete); 1061 el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 1062 1063 el_source(el, NULL); /* read ~/.editrc */ 1064 el_set(el, EL_SIGNAL, 1); 1065 } else if (!editing) { 1066 if (hist) { 1067 history_end(hist); 1068 hist = NULL; 1069 } 1070 if (el) { 1071 el_end(el); 1072 el = NULL; 1073 } 1074 } 1075 } 1076 #endif /* !SMALL */ 1077 1078 /* 1079 * Wait for an asynchronous connect(2) attempt to finish. 1080 */ 1081 int 1082 connect_wait(int s) 1083 { 1084 struct pollfd pfd[1]; 1085 int error = 0; 1086 socklen_t len = sizeof(error); 1087 1088 pfd[0].fd = s; 1089 pfd[0].events = POLLOUT; 1090 1091 if (poll(pfd, 1, -1) == -1) 1092 return -1; 1093 if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1) 1094 return -1; 1095 if (error != 0) { 1096 errno = error; 1097 return -1; 1098 } 1099 return 0; 1100 } 1101