1 /* $OpenBSD: util.c,v 1.95 2021/02/02 12:58:42 robert 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], *filenamebuf; 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 if ((filenamebuf = strdup(filename)) != NULL && 786 (filename = basename(filenamebuf)) != NULL) { 787 free(title); 788 title = strdup(filename); 789 } 790 free(filenamebuf); 791 } 792 793 buf[0] = 0; 794 if (!verbose && action != NULL) { 795 int l = strlen(action); 796 char *dotdot = ""; 797 798 if (l < 7) 799 l = 7; 800 else if (l > 12) { 801 l = 12; 802 dotdot = "..."; 803 overhead += 3; 804 } 805 snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action, 806 dotdot); 807 overhead += l + 1; 808 } else 809 snprintf(buf, sizeof(buf), "\r"); 810 811 if (!verbose && title != NULL) { 812 int l = strlen(title); 813 char *dotdot = ""; 814 815 if (l < 12) 816 l = 12; 817 else if (l > 25) { 818 l = 22; 819 dotdot = "..."; 820 overhead += 3; 821 } 822 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 823 "%-*.*s%s %3d%% ", l, l, title, 824 dotdot, ratio); 825 overhead += l + 1; 826 } else 827 snprintf(buf, sizeof(buf), "\r%3d%% ", ratio); 828 829 barlength = ttywidth - overhead; 830 if (barlength > 0) { 831 i = barlength * ratio / 100; 832 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 833 "|%.*s%*s|", i, 834 "*******************************************************" 835 "*******************************************************" 836 "*******************************************************" 837 "*******************************************************" 838 "*******************************************************" 839 "*******************************************************" 840 "*******************************************************", 841 barlength - i, ""); 842 } 843 844 i = 0; 845 abbrevsize = cursize; 846 while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) { 847 i++; 848 abbrevsize >>= 10; 849 } 850 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 851 " %5lld %c%c ", (long long)abbrevsize, prefixes[i], 852 prefixes[i] == ' ' ? ' ' : 'B'); 853 854 timespecsub(&now, &lastupdate, &wait); 855 if (cursize > lastsize) { 856 lastupdate = now; 857 lastsize = cursize; 858 if (wait.tv_sec >= STALLTIME) { /* fudge out stalled time */ 859 start.tv_sec += wait.tv_sec; 860 start.tv_nsec += wait.tv_nsec; 861 } 862 wait.tv_sec = 0; 863 } 864 865 timespecsub(&now, &start, &td); 866 elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0); 867 868 if (flag == 1) { 869 i = (int)elapsed / 3600; 870 if (i) 871 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 872 "%2d:", i); 873 else 874 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 875 " "); 876 i = (int)elapsed % 3600; 877 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 878 "%02d:%02d ", i / 60, i % 60); 879 } else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { 880 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 881 " --:-- ETA"); 882 } else if (wait.tv_sec >= STALLTIME) { 883 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 884 " - stalled -"); 885 } else { 886 remaining = (int)((filesize - restart_point) / 887 (bytes / elapsed) - elapsed); 888 i = remaining / 3600; 889 if (i) 890 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 891 "%2d:", i); 892 else 893 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 894 " "); 895 i = remaining % 3600; 896 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 897 "%02d:%02d ETA", i / 60, i % 60); 898 } 899 (void)write(fileno(ttyout), buf, strlen(buf)); 900 901 if (flag == -1) { 902 (void)signal(SIGALRM, updateprogressmeter); 903 alarmtimer(1); /* set alarm timer for 1 Hz */ 904 } else if (flag == 1) { 905 alarmtimer(0); 906 (void)putc('\n', ttyout); 907 free(title); 908 title = NULL; 909 } 910 fflush(ttyout); 911 } 912 913 /* 914 * Display transfer statistics. 915 * Requires start to be initialised by progressmeter(-1), 916 * direction to be defined by xfer routines, and filesize and bytes 917 * to be updated by xfer routines 918 * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR 919 * instead of TTYOUT. 920 */ 921 void 922 ptransfer(int siginfo) 923 { 924 struct timespec now, td; 925 double elapsed, pace; 926 off_t bs; 927 int meg, remaining, hh; 928 char buf[100]; 929 930 if (!verbose && !siginfo) 931 return; 932 933 clock_gettime(CLOCK_MONOTONIC, &now); 934 timespecsub(&now, &start, &td); 935 elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0); 936 bs = bytes / (elapsed == 0.0 ? 1 : elapsed); 937 meg = 0; 938 if (bs > (1024 * 1024)) 939 meg = 1; 940 941 pace = bs / (1024.0 * (meg ? 1024.0 : 1.0)); 942 (void)snprintf(buf, sizeof(buf), 943 "%lld byte%s %s in %lld.%02d seconds (%lld.%02d %sB/s)\n", 944 (long long)bytes, bytes == 1 ? "" : "s", direction, 945 (long long)elapsed, (int)(elapsed * 100.0) % 100, 946 (long long)pace, (int)(pace * 100.0) % 100, 947 meg ? "M" : "K"); 948 949 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 && 950 bytes + restart_point <= filesize) { 951 remaining = (int)((filesize - restart_point) / 952 (bytes / elapsed) - elapsed); 953 hh = remaining / 3600; 954 remaining %= 3600; 955 956 /* "buf+len(buf) -1" to overwrite \n */ 957 snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf), 958 " ETA: %02d:%02d:%02d\n", hh, remaining / 60, 959 remaining % 60); 960 } 961 (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf)); 962 } 963 964 /* 965 * List words in stringlist, vertically arranged 966 */ 967 #ifndef SMALL 968 void 969 list_vertical(StringList *sl) 970 { 971 int i, j, w; 972 int columns, width, lines; 973 char *p; 974 975 width = 0; 976 977 for (i = 0 ; i < sl->sl_cur ; i++) { 978 w = strlen(sl->sl_str[i]); 979 if (w > width) 980 width = w; 981 } 982 width = (width + 8) &~ 7; 983 984 columns = ttywidth / width; 985 if (columns == 0) 986 columns = 1; 987 lines = (sl->sl_cur + columns - 1) / columns; 988 for (i = 0; i < lines; i++) { 989 for (j = 0; j < columns; j++) { 990 p = sl->sl_str[j * lines + i]; 991 if (p) 992 fputs(p, ttyout); 993 if (j * lines + i + lines >= sl->sl_cur) { 994 putc('\n', ttyout); 995 break; 996 } 997 w = strlen(p); 998 while (w < width) { 999 w = (w + 8) &~ 7; 1000 (void)putc('\t', ttyout); 1001 } 1002 } 1003 } 1004 } 1005 #endif /* !SMALL */ 1006 1007 /* 1008 * Update the global ttywidth value, using TIOCGWINSZ. 1009 */ 1010 /* ARGSUSED */ 1011 void 1012 setttywidth(int signo) 1013 { 1014 int save_errno = errno; 1015 struct winsize winsize; 1016 1017 if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1) 1018 ttywidth = winsize.ws_col ? winsize.ws_col : 80; 1019 else 1020 ttywidth = 80; 1021 errno = save_errno; 1022 } 1023 1024 /* 1025 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 1026 */ 1027 void 1028 alarmtimer(int wait) 1029 { 1030 int save_errno = errno; 1031 struct itimerval itv; 1032 1033 itv.it_value.tv_sec = wait; 1034 itv.it_value.tv_usec = 0; 1035 itv.it_interval = itv.it_value; 1036 setitimer(ITIMER_REAL, &itv, NULL); 1037 errno = save_errno; 1038 } 1039 1040 /* 1041 * Setup or cleanup EditLine structures 1042 */ 1043 #ifndef SMALL 1044 void 1045 controlediting(void) 1046 { 1047 HistEvent hev; 1048 1049 if (editing && el == NULL && hist == NULL) { 1050 el = el_init(__progname, stdin, ttyout, stderr); /* init editline */ 1051 hist = history_init(); /* init the builtin history */ 1052 history(hist, &hev, H_SETSIZE, 100); /* remember 100 events */ 1053 el_set(el, EL_HIST, history, hist); /* use history */ 1054 1055 el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */ 1056 el_set(el, EL_PROMPT, prompt); /* set the prompt function */ 1057 1058 /* add local file completion, bind to TAB */ 1059 el_set(el, EL_ADDFN, "ftp-complete", 1060 "Context sensitive argument completion", 1061 complete); 1062 el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 1063 1064 el_source(el, NULL); /* read ~/.editrc */ 1065 el_set(el, EL_SIGNAL, 1); 1066 } else if (!editing) { 1067 if (hist) { 1068 history_end(hist); 1069 hist = NULL; 1070 } 1071 if (el) { 1072 el_end(el); 1073 el = NULL; 1074 } 1075 } 1076 } 1077 #endif /* !SMALL */ 1078 1079 /* 1080 * Wait for an asynchronous connect(2) attempt to finish. 1081 */ 1082 int 1083 connect_wait(int s) 1084 { 1085 struct pollfd pfd[1]; 1086 int error = 0; 1087 socklen_t len = sizeof(error); 1088 1089 pfd[0].fd = s; 1090 pfd[0].events = POLLOUT; 1091 1092 if (poll(pfd, 1, -1) == -1) 1093 return -1; 1094 if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1) 1095 return -1; 1096 if (error != 0) { 1097 errno = error; 1098 return -1; 1099 } 1100 return 0; 1101 } 1102 1103 #ifndef SMALL 1104 ssize_t 1105 http_time(time_t t, char *tmbuf, size_t len) 1106 { 1107 struct tm tm; 1108 1109 /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */ 1110 if (gmtime_r(&t, &tm) == NULL) 1111 return 0; 1112 else 1113 return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm)); 1114 } 1115 #endif /* !SMALL */ 1116