1 /* $OpenBSD: util.c,v 1.89 2019/05/16 12:44:18 florian 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 } 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 #ifndef SMALL 442 again: 443 #endif 444 if (fgets(buf, sizeof(buf), *ftemp) == NULL) { 445 (void)fclose(*ftemp); 446 *ftemp = NULL; 447 return (NULL); 448 } 449 450 buf[strcspn(buf, "\n")] = '\0'; 451 bufp = buf; 452 453 #ifndef SMALL 454 if (type) { 455 parse_list(&bufp, type); 456 if (!bufp || 457 (bufp[0] == '.' && /* LIST defaults to -a on some ftp */ 458 (bufp[1] == '\0' || /* servers. Ignore '.' and '..'. */ 459 (bufp[1] == '.' && bufp[2] == '\0')))) 460 goto again; 461 } 462 #endif /* !SMALL */ 463 464 return (bufp); 465 } 466 467 /* 468 * wrapper for remglob2 469 */ 470 char * 471 remglob(char *argv[], int doswitch, char **errbuf) 472 { 473 static FILE *ftemp = NULL; 474 475 return remglob2(argv, doswitch, errbuf, &ftemp, NULL); 476 } 477 478 #ifndef SMALL 479 int 480 confirm(const char *cmd, const char *file) 481 { 482 char str[BUFSIZ]; 483 484 if (file && (confirmrest || !interactive)) 485 return (1); 486 top: 487 if (file) 488 fprintf(ttyout, "%s %s? ", cmd, file); 489 else 490 fprintf(ttyout, "Continue with %s? ", cmd); 491 (void)fflush(ttyout); 492 if (fgets(str, sizeof(str), stdin) == NULL) 493 goto quit; 494 switch (tolower((unsigned char)*str)) { 495 case '?': 496 fprintf(ttyout, 497 "? help\n" 498 "a answer yes to all\n" 499 "n answer no\n" 500 "p turn off prompt mode\n" 501 "q answer no to all\n" 502 "y answer yes\n"); 503 goto top; 504 case 'a': 505 confirmrest = 1; 506 fprintf(ttyout, "Prompting off for duration of %s.\n", 507 cmd); 508 break; 509 case 'n': 510 return (0); 511 case 'p': 512 interactive = 0; 513 fputs("Interactive mode: off.\n", ttyout); 514 break; 515 case 'q': 516 quit: 517 mflag = 0; 518 clearerr(stdin); 519 return (0); 520 case 'y': 521 return(1); 522 break; 523 default: 524 fprintf(ttyout, "?, a, n, p, q, y " 525 "are the only acceptable commands!\n"); 526 goto top; 527 break; 528 } 529 return (1); 530 } 531 #endif /* !SMALL */ 532 533 /* 534 * Glob a local file name specification with 535 * the expectation of a single return value. 536 * Can't control multiple values being expanded 537 * from the expression, we return only the first. 538 */ 539 int 540 globulize(char **cpp) 541 { 542 glob_t gl; 543 int flags; 544 545 if (!doglob) 546 return (1); 547 548 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; 549 memset(&gl, 0, sizeof(gl)); 550 if (glob(*cpp, flags, NULL, &gl) || 551 gl.gl_pathc == 0) { 552 warnx("%s: not found", *cpp); 553 globfree(&gl); 554 return (0); 555 } 556 /* XXX: caller should check if *cpp changed, and 557 * free(*cpp) if that is the case 558 */ 559 *cpp = strdup(gl.gl_pathv[0]); 560 if (*cpp == NULL) 561 err(1, NULL); 562 globfree(&gl); 563 return (1); 564 } 565 566 /* 567 * determine size of remote file 568 */ 569 off_t 570 remotesize(const char *file, int noisy) 571 { 572 int overbose; 573 off_t size; 574 575 overbose = verbose; 576 size = -1; 577 #ifndef SMALL 578 if (!debug) 579 #endif /* !SMALL */ 580 verbose = -1; 581 if (command("SIZE %s", file) == COMPLETE) { 582 char *cp, *ep; 583 584 cp = strchr(reply_string, ' '); 585 if (cp != NULL) { 586 cp++; 587 size = strtoll(cp, &ep, 10); 588 if (*ep != '\0' && !isspace((unsigned char)*ep)) 589 size = -1; 590 } 591 } else if (noisy 592 #ifndef SMALL 593 && !debug 594 #endif /* !SMALL */ 595 ) { 596 fputs(reply_string, ttyout); 597 fputc('\n', ttyout); 598 } 599 verbose = overbose; 600 return (size); 601 } 602 603 /* 604 * determine last modification time (in GMT) of remote file 605 */ 606 time_t 607 remotemodtime(const char *file, int noisy) 608 { 609 int overbose; 610 time_t rtime; 611 int ocode; 612 613 overbose = verbose; 614 ocode = code; 615 rtime = -1; 616 #ifndef SMALL 617 if (!debug) 618 #endif /* !SMALL */ 619 verbose = -1; 620 if (command("MDTM %s", file) == COMPLETE) { 621 struct tm timebuf; 622 int yy, mo, day, hour, min, sec; 623 /* 624 * time-val = 14DIGIT [ "." 1*DIGIT ] 625 * YYYYMMDDHHMMSS[.sss] 626 * mdtm-response = "213" SP time-val CRLF / error-response 627 */ 628 /* TODO: parse .sss as well, use timespecs. */ 629 char *timestr = reply_string; 630 631 /* Repair `19%02d' bug on server side */ 632 while (!isspace((unsigned char)*timestr)) 633 timestr++; 634 while (isspace((unsigned char)*timestr)) 635 timestr++; 636 if (strncmp(timestr, "191", 3) == 0) { 637 fprintf(ttyout, 638 "Y2K warning! Fixed incorrect time-val received from server.\n"); 639 timestr[0] = ' '; 640 timestr[1] = '2'; 641 timestr[2] = '0'; 642 } 643 sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo, 644 &day, &hour, &min, &sec); 645 memset(&timebuf, 0, sizeof(timebuf)); 646 timebuf.tm_sec = sec; 647 timebuf.tm_min = min; 648 timebuf.tm_hour = hour; 649 timebuf.tm_mday = day; 650 timebuf.tm_mon = mo - 1; 651 timebuf.tm_year = yy - 1900; 652 timebuf.tm_isdst = -1; 653 rtime = mktime(&timebuf); 654 if (rtime == -1 && (noisy 655 #ifndef SMALL 656 || debug 657 #endif /* !SMALL */ 658 )) 659 fprintf(ttyout, "Can't convert %s to a time.\n", reply_string); 660 else 661 rtime += timebuf.tm_gmtoff; /* conv. local -> GMT */ 662 } else if (noisy 663 #ifndef SMALL 664 && !debug 665 #endif /* !SMALL */ 666 ) { 667 fputs(reply_string, ttyout); 668 fputc('\n', ttyout); 669 } 670 verbose = overbose; 671 if (rtime == -1) 672 code = ocode; 673 return (rtime); 674 } 675 676 /* 677 * Ensure file is in or under dir. 678 * Returns 1 if so, 0 if not (or an error occurred). 679 */ 680 int 681 fileindir(const char *file, const char *dir) 682 { 683 char parentdirbuf[PATH_MAX], *parentdir; 684 char realdir[PATH_MAX]; 685 size_t dirlen; 686 687 /* determine parent directory of file */ 688 (void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf)); 689 parentdir = dirname(parentdirbuf); 690 if (strcmp(parentdir, ".") == 0) 691 return 1; /* current directory is ok */ 692 693 /* find the directory */ 694 if (realpath(parentdir, realdir) == NULL) { 695 warn("Unable to determine real path of `%s'", parentdir); 696 return 0; 697 } 698 if (realdir[0] != '/') /* relative result is ok */ 699 return 1; 700 701 dirlen = strlen(dir); 702 if (strncmp(realdir, dir, dirlen) == 0 && 703 (realdir[dirlen] == '/' || realdir[dirlen] == '\0')) 704 return 1; 705 return 0; 706 } 707 708 709 /* 710 * Returns true if this is the controlling/foreground process, else false. 711 */ 712 int 713 foregroundproc(void) 714 { 715 static pid_t pgrp = -1; 716 int ctty_pgrp; 717 718 if (pgrp == -1) 719 pgrp = getpgrp(); 720 721 return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 && 722 ctty_pgrp == pgrp)); 723 } 724 725 /* ARGSUSED */ 726 static void 727 updateprogressmeter(int signo) 728 { 729 int save_errno = errno; 730 731 /* update progressmeter if foreground process or in -m mode */ 732 if (foregroundproc() || progress == -1) 733 progressmeter(0, NULL); 734 errno = save_errno; 735 } 736 737 /* 738 * Display a transfer progress bar if progress is non-zero. 739 * SIGALRM is hijacked for use by this function. 740 * - Before the transfer, set filesize to size of file (or -1 if unknown), 741 * and call with flag = -1. This starts the once per second timer, 742 * and a call to updateprogressmeter() upon SIGALRM. 743 * - During the transfer, updateprogressmeter will call progressmeter 744 * with flag = 0 745 * - After the transfer, call with flag = 1 746 */ 747 static struct timespec start; 748 749 char *action; 750 751 void 752 progressmeter(int flag, const char *filename) 753 { 754 /* 755 * List of order of magnitude prefixes. 756 * The last is `P', as 2^64 = 16384 Petabytes 757 */ 758 static const char prefixes[] = " KMGTP"; 759 760 static struct timespec lastupdate; 761 static off_t lastsize; 762 static char *title = NULL; 763 struct timespec now, td, wait; 764 off_t cursize, abbrevsize; 765 double elapsed; 766 int ratio, barlength, i, remaining, overhead = 30; 767 char buf[512]; 768 769 if (flag == -1) { 770 clock_gettime(CLOCK_MONOTONIC, &start); 771 lastupdate = start; 772 lastsize = restart_point; 773 } 774 clock_gettime(CLOCK_MONOTONIC, &now); 775 if (!progress || filesize < 0) 776 return; 777 cursize = bytes + restart_point; 778 779 if (filesize) 780 ratio = cursize * 100 / filesize; 781 else 782 ratio = 100; 783 ratio = MAXIMUM(ratio, 0); 784 ratio = MINIMUM(ratio, 100); 785 if (!verbose && flag == -1) { 786 filename = basename(filename); 787 if (filename != NULL) { 788 free(title); 789 title = strdup(filename); 790 } 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; 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 /* XXX floating point printf in signal handler */ 942 (void)snprintf(buf, sizeof(buf), 943 "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n", 944 (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed, 945 bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K"); 946 947 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 && 948 bytes + restart_point <= filesize) { 949 remaining = (int)((filesize - restart_point) / 950 (bytes / elapsed) - elapsed); 951 hh = remaining / 3600; 952 remaining %= 3600; 953 954 /* "buf+len(buf) -1" to overwrite \n */ 955 snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf), 956 " ETA: %02d:%02d:%02d\n", hh, remaining / 60, 957 remaining % 60); 958 } 959 (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf)); 960 } 961 962 /* 963 * List words in stringlist, vertically arranged 964 */ 965 #ifndef SMALL 966 void 967 list_vertical(StringList *sl) 968 { 969 int i, j, w; 970 int columns, width, lines; 971 char *p; 972 973 width = 0; 974 975 for (i = 0 ; i < sl->sl_cur ; i++) { 976 w = strlen(sl->sl_str[i]); 977 if (w > width) 978 width = w; 979 } 980 width = (width + 8) &~ 7; 981 982 columns = ttywidth / width; 983 if (columns == 0) 984 columns = 1; 985 lines = (sl->sl_cur + columns - 1) / columns; 986 for (i = 0; i < lines; i++) { 987 for (j = 0; j < columns; j++) { 988 p = sl->sl_str[j * lines + i]; 989 if (p) 990 fputs(p, ttyout); 991 if (j * lines + i + lines >= sl->sl_cur) { 992 putc('\n', ttyout); 993 break; 994 } 995 w = strlen(p); 996 while (w < width) { 997 w = (w + 8) &~ 7; 998 (void)putc('\t', ttyout); 999 } 1000 } 1001 } 1002 } 1003 #endif /* !SMALL */ 1004 1005 /* 1006 * Update the global ttywidth value, using TIOCGWINSZ. 1007 */ 1008 /* ARGSUSED */ 1009 void 1010 setttywidth(int signo) 1011 { 1012 int save_errno = errno; 1013 struct winsize winsize; 1014 1015 if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1) 1016 ttywidth = winsize.ws_col ? winsize.ws_col : 80; 1017 else 1018 ttywidth = 80; 1019 errno = save_errno; 1020 } 1021 1022 /* 1023 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 1024 */ 1025 void 1026 alarmtimer(int wait) 1027 { 1028 int save_errno = errno; 1029 struct itimerval itv; 1030 1031 itv.it_value.tv_sec = wait; 1032 itv.it_value.tv_usec = 0; 1033 itv.it_interval = itv.it_value; 1034 setitimer(ITIMER_REAL, &itv, NULL); 1035 errno = save_errno; 1036 } 1037 1038 /* 1039 * Setup or cleanup EditLine structures 1040 */ 1041 #ifndef SMALL 1042 void 1043 controlediting(void) 1044 { 1045 HistEvent hev; 1046 1047 if (editing && el == NULL && hist == NULL) { 1048 el = el_init(__progname, stdin, ttyout, stderr); /* init editline */ 1049 hist = history_init(); /* init the builtin history */ 1050 history(hist, &hev, H_SETSIZE, 100); /* remember 100 events */ 1051 el_set(el, EL_HIST, history, hist); /* use history */ 1052 1053 el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */ 1054 el_set(el, EL_PROMPT, prompt); /* set the prompt function */ 1055 1056 /* add local file completion, bind to TAB */ 1057 el_set(el, EL_ADDFN, "ftp-complete", 1058 "Context sensitive argument completion", 1059 complete); 1060 el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 1061 1062 el_source(el, NULL); /* read ~/.editrc */ 1063 el_set(el, EL_SIGNAL, 1); 1064 } else if (!editing) { 1065 if (hist) { 1066 history_end(hist); 1067 hist = NULL; 1068 } 1069 if (el) { 1070 el_end(el); 1071 el = NULL; 1072 } 1073 } 1074 } 1075 #endif /* !SMALL */ 1076 1077 /* 1078 * Wait for an asynchronous connect(2) attempt to finish. 1079 */ 1080 int 1081 connect_wait(int s) 1082 { 1083 struct pollfd pfd[1]; 1084 int error = 0; 1085 socklen_t len = sizeof(error); 1086 1087 pfd[0].fd = s; 1088 pfd[0].events = POLLOUT; 1089 1090 if (poll(pfd, 1, -1) == -1) 1091 return -1; 1092 if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0) 1093 return -1; 1094 if (error != 0) { 1095 errno = error; 1096 return -1; 1097 } 1098 return 0; 1099 } 1100