1 /* $OpenBSD: util.c,v 1.55 2008/08/22 08:52:35 sobrado 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 #if !defined(lint) && !defined(SMALL) 67 static const char rcsid[] = "$OpenBSD: util.c,v 1.55 2008/08/22 08:52:35 sobrado Exp $"; 68 #endif /* not lint and not SMALL */ 69 70 /* 71 * FTP User Program -- Misc support routines 72 */ 73 #include <sys/ioctl.h> 74 #include <sys/time.h> 75 #include <arpa/ftp.h> 76 77 #include <ctype.h> 78 #include <err.h> 79 #include <errno.h> 80 #include <fcntl.h> 81 #include <libgen.h> 82 #include <limits.h> 83 #include <glob.h> 84 #include <pwd.h> 85 #include <signal.h> 86 #include <stdio.h> 87 #include <stdlib.h> 88 #include <string.h> 89 #include <time.h> 90 #include <tzfile.h> 91 #include <unistd.h> 92 93 #include "ftp_var.h" 94 #include "pathnames.h" 95 96 static void updateprogressmeter(int); 97 98 /* 99 * Connect to peer server and 100 * auto-login, if possible. 101 */ 102 void 103 setpeer(int argc, char *argv[]) 104 { 105 char *host, *port; 106 107 if (connected) { 108 fprintf(ttyout, "Already connected to %s, use close first.\n", 109 hostname); 110 code = -1; 111 return; 112 } 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 if (gatemode) 121 port = gateport; 122 else 123 port = ftpport; 124 if (argc > 2) 125 port = argv[2]; 126 127 if (gatemode) { 128 if (gateserver == NULL || *gateserver == '\0') 129 errx(1, "gateserver not defined (shouldn't happen)"); 130 host = hookup(gateserver, port); 131 } else 132 host = hookup(argv[1], port); 133 134 if (host) { 135 int overbose; 136 137 if (gatemode) { 138 if (command("PASSERVE %s", argv[1]) != COMPLETE) 139 return; 140 if (verbose) 141 fprintf(ttyout, 142 "Connected via pass-through server %s\n", 143 gateserver); 144 } 145 146 connected = 1; 147 /* 148 * Set up defaults for FTP. 149 */ 150 (void)strlcpy(formname, "non-print", sizeof formname); 151 form = FORM_N; 152 (void)strlcpy(modename, "stream", sizeof modename); 153 mode = MODE_S; 154 (void)strlcpy(structname, "file", sizeof structname); 155 stru = STRU_F; 156 (void)strlcpy(bytename, "8", sizeof bytename); 157 bytesize = 8; 158 159 /* 160 * Set type to 0 (not specified by user), 161 * meaning binary by default, but don't bother 162 * telling server. We can use binary 163 * for text files unless changed by the user. 164 */ 165 (void)strlcpy(typename, "binary", sizeof typename); 166 curtype = TYPE_A; 167 type = 0; 168 if (autologin) 169 (void)ftp_login(argv[1], NULL, NULL); 170 171 #if (defined(unix) || defined(BSD)) && NBBY == 8 172 /* 173 * this ifdef is to keep someone form "porting" this to an incompatible 174 * system and not checking this out. This way they have to think about it. 175 */ 176 overbose = verbose; 177 #ifndef SMALL 178 if (!debug) 179 #endif /* !SMALL */ 180 verbose = -1; 181 if (command("SYST") == COMPLETE && overbose) { 182 char *cp, c; 183 c = 0; 184 cp = strchr(reply_string + 4, ' '); 185 if (cp == NULL) 186 cp = strchr(reply_string + 4, '\r'); 187 if (cp) { 188 if (cp[-1] == '.') 189 cp--; 190 c = *cp; 191 *cp = '\0'; 192 } 193 194 fprintf(ttyout, "Remote system type is %s.\n", reply_string + 4); 195 if (cp) 196 *cp = c; 197 } 198 if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) { 199 if (proxy) 200 unix_proxy = 1; 201 else 202 unix_server = 1; 203 if (overbose) 204 fprintf(ttyout, "Using %s mode to transfer files.\n", 205 typename); 206 } else { 207 if (proxy) 208 unix_proxy = 0; 209 else 210 unix_server = 0; 211 if (overbose && 212 !strncmp(reply_string, "215 TOPS20", 10)) 213 fputs( 214 "Remember to set tenex mode when transferring binary files from this machine.\n", 215 ttyout); 216 } 217 verbose = overbose; 218 #endif /* unix || BSD */ 219 } 220 } 221 222 /* 223 * login to remote host, using given username & password if supplied 224 */ 225 int 226 ftp_login(const char *host, char *user, char *pass) 227 { 228 char tmp[80], *acctname = NULL, host_name[MAXHOSTNAMELEN]; 229 char anonpass[MAXLOGNAME + 1 + MAXHOSTNAMELEN]; /* "user@hostname" */ 230 int n, aflag = 0, retry = 0; 231 struct passwd *pw; 232 233 #ifndef SMALL 234 if (user == NULL) { 235 if (ruserpass(host, &user, &pass, &acctname) < 0) { 236 code = -1; 237 return (0); 238 } 239 } 240 #endif /* !SMALL */ 241 242 /* 243 * Set up arguments for an anonymous FTP session, if necessary. 244 */ 245 if ((user == NULL || pass == NULL) && anonftp) { 246 memset(anonpass, 0, sizeof(anonpass)); 247 memset(host_name, 0, sizeof(host_name)); 248 249 /* 250 * Set up anonymous login password. 251 */ 252 if ((user = getlogin()) == NULL) { 253 if ((pw = getpwuid(getuid())) == NULL) 254 user = "anonymous"; 255 else 256 user = pw->pw_name; 257 } 258 gethostname(host_name, sizeof(host_name)); 259 #ifndef DONT_CHEAT_ANONPASS 260 /* 261 * Every anonymous FTP server I've encountered 262 * will accept the string "username@", and will 263 * append the hostname itself. We do this by default 264 * since many servers are picky about not having 265 * a FQDN in the anonymous password. - thorpej@netbsd.org 266 */ 267 snprintf(anonpass, sizeof(anonpass) - 1, "%s@", 268 user); 269 #else 270 snprintf(anonpass, sizeof(anonpass) - 1, "%s@%s", 271 user, hp->h_name); 272 #endif 273 pass = anonpass; 274 user = "anonymous"; /* as per RFC 1635 */ 275 } 276 277 tryagain: 278 if (retry) 279 user = "ftp"; /* some servers only allow "ftp" */ 280 281 while (user == NULL) { 282 char *myname = getlogin(); 283 284 if (myname == NULL && (pw = getpwuid(getuid())) != NULL) 285 myname = pw->pw_name; 286 if (myname) 287 fprintf(ttyout, "Name (%s:%s): ", host, myname); 288 else 289 fprintf(ttyout, "Name (%s): ", host); 290 user = myname; 291 if (fgets(tmp, sizeof(tmp), stdin) != NULL) { 292 tmp[strcspn(tmp, "\n")] = '\0'; 293 if (tmp[0] != '\0') 294 user = tmp; 295 } 296 else 297 exit(0); 298 } 299 n = command("USER %s", user); 300 if (n == CONTINUE) { 301 if (pass == NULL) 302 pass = getpass("Password:"); 303 n = command("PASS %s", pass); 304 } 305 if (n == CONTINUE) { 306 aflag++; 307 if (acctname == NULL) 308 acctname = getpass("Account:"); 309 n = command("ACCT %s", acctname); 310 } 311 if ((n != COMPLETE) || 312 (!aflag && acctname != NULL && command("ACCT %s", acctname) != COMPLETE)) { 313 warnx("Login failed."); 314 if (retry || !anonftp) 315 return (0); 316 else 317 retry = 1; 318 goto tryagain; 319 } 320 if (proxy) 321 return (1); 322 connected = -1; 323 for (n = 0; n < macnum; ++n) { 324 if (!strcmp("init", macros[n].mac_name)) { 325 (void)strlcpy(line, "$init", sizeof line); 326 makeargv(); 327 domacro(margc, margv); 328 break; 329 } 330 } 331 return (1); 332 } 333 334 /* 335 * `another' gets another argument, and stores the new argc and argv. 336 * It reverts to the top level (via main.c's intr()) on EOF/error. 337 * 338 * Returns false if no new arguments have been added. 339 */ 340 int 341 another(int *pargc, char ***pargv, const char *prompt) 342 { 343 int len = strlen(line), ret; 344 345 if (len >= sizeof(line) - 3) { 346 fputs("sorry, arguments too long.\n", ttyout); 347 intr(); 348 } 349 fprintf(ttyout, "(%s) ", prompt); 350 line[len++] = ' '; 351 if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL) { 352 clearerr(stdin); 353 intr(); 354 } 355 len += strlen(&line[len]); 356 if (len > 0 && line[len - 1] == '\n') 357 line[len - 1] = '\0'; 358 makeargv(); 359 ret = margc > *pargc; 360 *pargc = margc; 361 *pargv = margv; 362 return (ret); 363 } 364 365 /* 366 * glob files given in argv[] from the remote server. 367 * if errbuf isn't NULL, store error messages there instead 368 * of writing to the screen. 369 * if type isn't NULL, use LIST instead of NLST, and store filetype. 370 * 'd' means directory, 's' means symbolic link, '-' means plain 371 * file. 372 */ 373 char * 374 remglob2(char *argv[], int doswitch, char **errbuf, FILE **ftemp, char *type) 375 { 376 char temp[MAXPATHLEN], *bufp, *cp, *lmode; 377 static char buf[MAXPATHLEN], **args; 378 int oldverbose, oldhash, fd; 379 380 if (!mflag) { 381 if (!doglob) 382 args = NULL; 383 else { 384 if (*ftemp) { 385 (void)fclose(*ftemp); 386 *ftemp = NULL; 387 } 388 } 389 return (NULL); 390 } 391 if (!doglob) { 392 if (args == NULL) 393 args = argv; 394 if ((cp = *++args) == NULL) 395 args = NULL; 396 return (cp); 397 } 398 if (*ftemp == NULL) { 399 int len; 400 401 if ((cp = getenv("TMPDIR")) == NULL || *cp == '\0') 402 cp = _PATH_TMP; 403 len = strlen(cp); 404 if (len + sizeof(TMPFILE) + (cp[len-1] != '/') > sizeof(temp)) { 405 warnx("unable to create temporary file: %s", 406 strerror(ENAMETOOLONG)); 407 return (NULL); 408 } 409 410 (void)strlcpy(temp, cp, sizeof temp); 411 if (temp[len-1] != '/') 412 temp[len++] = '/'; 413 (void)strlcpy(&temp[len], TMPFILE, sizeof temp - len); 414 if ((fd = mkstemp(temp)) < 0) { 415 warn("unable to create temporary file %s", temp); 416 return (NULL); 417 } 418 close(fd); 419 oldverbose = verbose; 420 verbose = (errbuf != NULL) ? -1 : 0; 421 oldhash = hash; 422 hash = 0; 423 if (doswitch) 424 pswitch(!proxy); 425 for (lmode = "w"; *++argv != NULL; lmode = "a") 426 recvrequest(type ? "LIST" : "NLST", temp, *argv, lmode, 427 0, 0); 428 if ((code / 100) != COMPLETE) { 429 if (errbuf != NULL) 430 *errbuf = reply_string; 431 } 432 if (doswitch) 433 pswitch(!proxy); 434 verbose = oldverbose; 435 hash = oldhash; 436 *ftemp = fopen(temp, "r"); 437 (void)unlink(temp); 438 if (*ftemp == NULL) { 439 if (errbuf == NULL) 440 fputs("can't find list of remote files, oops.\n", 441 ttyout); 442 else 443 *errbuf = 444 "can't find list of remote files, oops."; 445 return (NULL); 446 } 447 } 448 again: 449 if (fgets(buf, sizeof(buf), *ftemp) == NULL) { 450 (void)fclose(*ftemp); 451 *ftemp = NULL; 452 return (NULL); 453 } 454 455 buf[strcspn(buf, "\n")] = '\0'; 456 bufp = buf; 457 458 #ifndef SMALL 459 if (type) { 460 parse_list(&bufp, type); 461 if (!bufp) 462 goto again; 463 } 464 #endif /* !SMALL */ 465 466 return (bufp); 467 } 468 469 /* 470 * wrapper for remglob2 471 */ 472 char * 473 remglob(char *argv[], int doswitch, char **errbuf) 474 { 475 static FILE *ftemp = NULL; 476 477 return remglob2(argv, doswitch, errbuf, &ftemp, NULL); 478 } 479 480 int 481 confirm(const char *cmd, const char *file) 482 { 483 char str[BUFSIZ]; 484 485 if (file && (confirmrest || !interactive)) 486 return (1); 487 top: 488 if (file) 489 fprintf(ttyout, "%s %s? ", cmd, file); 490 else 491 fprintf(ttyout, "Continue with %s? ", cmd); 492 (void)fflush(ttyout); 493 if (fgets(str, sizeof(str), stdin) == NULL) 494 goto quit; 495 switch (tolower(*str)) { 496 case '?': 497 fprintf(ttyout, 498 "? help\n" 499 "a answer yes to all\n" 500 "n answer no\n" 501 "p turn off prompt mode\n" 502 "q answer no to all\n" 503 "y answer yes\n"); 504 goto top; 505 case 'a': 506 confirmrest = 1; 507 fprintf(ttyout, "Prompting off for duration of %s.\n", 508 cmd); 509 break; 510 case 'n': 511 return (0); 512 case 'p': 513 interactive = 0; 514 fputs("Interactive mode: off.\n", ttyout); 515 break; 516 case 'q': 517 quit: 518 mflag = 0; 519 clearerr(stdin); 520 return (0); 521 case 'y': 522 return(1); 523 break; 524 default: 525 fprintf(ttyout, "?, a, n, p, q, y " 526 "are the only acceptable commands!\n"); 527 goto top; 528 break; 529 } 530 return (1); 531 } 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 = strtoq(cp, &ep, 10); 588 if (*ep != '\0' && !isspace(*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(*timestr)) 633 timestr++; 634 while (isspace(*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 - TM_YEAR_BASE; 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[MAXPATHLEN], *parentdir; 684 char realdir[MAXPATHLEN]; 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); 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 timeval start; 748 749 void 750 progressmeter(int flag) 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 struct timeval now, td, wait; 761 off_t cursize, abbrevsize; 762 double elapsed; 763 int ratio, barlength, i, remaining; 764 char buf[512]; 765 766 if (flag == -1) { 767 (void)gettimeofday(&start, (struct timezone *)0); 768 lastupdate = start; 769 lastsize = restart_point; 770 } 771 (void)gettimeofday(&now, (struct timezone *)0); 772 if (!progress || filesize < 0) 773 return; 774 cursize = bytes + restart_point; 775 776 if (filesize) 777 ratio = cursize * 100 / filesize; 778 else 779 ratio = 100; 780 ratio = MAX(ratio, 0); 781 ratio = MIN(ratio, 100); 782 snprintf(buf, sizeof(buf), "\r%3d%% ", ratio); 783 784 barlength = ttywidth - 30; 785 if (barlength > 0) { 786 i = barlength * ratio / 100; 787 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 788 "|%.*s%*s|", i, 789 "*******************************************************" 790 "*******************************************************" 791 "*******************************************************" 792 "*******************************************************" 793 "*******************************************************" 794 "*******************************************************" 795 "*******************************************************", 796 barlength - i, ""); 797 } 798 799 i = 0; 800 abbrevsize = cursize; 801 while (abbrevsize >= 100000 && i < sizeof(prefixes)) { 802 i++; 803 abbrevsize >>= 10; 804 } 805 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 806 " %5lld %c%c ", (long long)abbrevsize, prefixes[i], 807 prefixes[i] == ' ' ? ' ' : 'B'); 808 809 timersub(&now, &lastupdate, &wait); 810 if (cursize > lastsize) { 811 lastupdate = now; 812 lastsize = cursize; 813 if (wait.tv_sec >= STALLTIME) { /* fudge out stalled time */ 814 start.tv_sec += wait.tv_sec; 815 start.tv_usec += wait.tv_usec; 816 } 817 wait.tv_sec = 0; 818 } 819 820 timersub(&now, &start, &td); 821 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 822 823 if (flag == 1) { 824 i = (int)elapsed / 3600; 825 if (i) 826 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 827 "%2d:", i); 828 else 829 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 830 " "); 831 i = (int)elapsed % 3600; 832 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 833 "%02d:%02d ", i / 60, i % 60); 834 } else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { 835 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 836 " --:-- ETA"); 837 } else if (wait.tv_sec >= STALLTIME) { 838 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 839 " - stalled -"); 840 } else { 841 remaining = (int)((filesize - restart_point) / 842 (bytes / elapsed) - elapsed); 843 i = remaining / 3600; 844 if (i) 845 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 846 "%2d:", i); 847 else 848 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 849 " "); 850 i = remaining % 3600; 851 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), 852 "%02d:%02d ETA", i / 60, i % 60); 853 } 854 (void)write(fileno(ttyout), buf, strlen(buf)); 855 856 if (flag == -1) { 857 (void)signal(SIGALRM, updateprogressmeter); 858 alarmtimer(1); /* set alarm timer for 1 Hz */ 859 } else if (flag == 1) { 860 alarmtimer(0); 861 (void)putc('\n', ttyout); 862 } 863 fflush(ttyout); 864 } 865 866 /* 867 * Display transfer statistics. 868 * Requires start to be initialised by progressmeter(-1), 869 * direction to be defined by xfer routines, and filesize and bytes 870 * to be updated by xfer routines 871 * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR 872 * instead of TTYOUT. 873 */ 874 void 875 ptransfer(int siginfo) 876 { 877 struct timeval now, td; 878 double elapsed; 879 off_t bs; 880 int meg, remaining, hh; 881 char buf[100]; 882 883 if (!verbose && !siginfo) 884 return; 885 886 (void)gettimeofday(&now, (struct timezone *)0); 887 timersub(&now, &start, &td); 888 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 889 bs = bytes / (elapsed == 0.0 ? 1 : elapsed); 890 meg = 0; 891 if (bs > (1024 * 1024)) 892 meg = 1; 893 (void)snprintf(buf, sizeof(buf), 894 "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n", 895 (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed, 896 bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K"); 897 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 898 && bytes + restart_point <= filesize) { 899 remaining = (int)((filesize - restart_point) / 900 (bytes / elapsed) - elapsed); 901 hh = remaining / 3600; 902 remaining %= 3600; 903 /* "buf+len(buf) -1" to overwrite \n */ 904 snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf), 905 " ETA: %02d:%02d:%02d\n", hh, remaining / 60, 906 remaining % 60); 907 } 908 (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf)); 909 } 910 911 /* 912 * List words in stringlist, vertically arranged 913 */ 914 void 915 list_vertical(StringList *sl) 916 { 917 int i, j, w; 918 int columns, width, lines; 919 char *p; 920 921 width = 0; 922 923 for (i = 0 ; i < sl->sl_cur ; i++) { 924 w = strlen(sl->sl_str[i]); 925 if (w > width) 926 width = w; 927 } 928 width = (width + 8) &~ 7; 929 930 columns = ttywidth / width; 931 if (columns == 0) 932 columns = 1; 933 lines = (sl->sl_cur + columns - 1) / columns; 934 for (i = 0; i < lines; i++) { 935 for (j = 0; j < columns; j++) { 936 p = sl->sl_str[j * lines + i]; 937 if (p) 938 fputs(p, ttyout); 939 if (j * lines + i + lines >= sl->sl_cur) { 940 putc('\n', ttyout); 941 break; 942 } 943 w = strlen(p); 944 while (w < width) { 945 w = (w + 8) &~ 7; 946 (void)putc('\t', ttyout); 947 } 948 } 949 } 950 } 951 952 /* 953 * Update the global ttywidth value, using TIOCGWINSZ. 954 */ 955 /* ARGSUSED */ 956 void 957 setttywidth(int signo) 958 { 959 int save_errno = errno; 960 struct winsize winsize; 961 962 if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1) 963 ttywidth = winsize.ws_col ? winsize.ws_col : 80; 964 else 965 ttywidth = 80; 966 errno = save_errno; 967 } 968 969 /* 970 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 971 */ 972 void 973 alarmtimer(int wait) 974 { 975 struct itimerval itv; 976 977 itv.it_value.tv_sec = wait; 978 itv.it_value.tv_usec = 0; 979 itv.it_interval = itv.it_value; 980 setitimer(ITIMER_REAL, &itv, NULL); 981 } 982 983 /* 984 * Setup or cleanup EditLine structures 985 */ 986 #ifndef SMALL 987 void 988 controlediting(void) 989 { 990 HistEvent hev; 991 992 if (editing && el == NULL && hist == NULL) { 993 el = el_init(__progname, stdin, ttyout, stderr); /* init editline */ 994 hist = history_init(); /* init the builtin history */ 995 history(hist, &hev, H_SETSIZE, 100); /* remember 100 events */ 996 el_set(el, EL_HIST, history, hist); /* use history */ 997 998 el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */ 999 el_set(el, EL_PROMPT, prompt); /* set the prompt function */ 1000 1001 /* add local file completion, bind to TAB */ 1002 el_set(el, EL_ADDFN, "ftp-complete", 1003 "Context sensitive argument completion", 1004 complete); 1005 el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 1006 1007 el_source(el, NULL); /* read ~/.editrc */ 1008 el_set(el, EL_SIGNAL, 1); 1009 } else if (!editing) { 1010 if (hist) { 1011 history_end(hist); 1012 hist = NULL; 1013 } 1014 if (el) { 1015 el_end(el); 1016 el = NULL; 1017 } 1018 } 1019 } 1020 #endif /* !SMALL */ 1021