1/* $OpenBSD: util.c,v 1.98 2023/03/08 04:43:11 guenther 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 95static void updateprogressmeter(int); 96 97/* 98 * Connect to peer server and 99 * auto-login, if possible. 100 */ 101void 102setpeer(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 */ 215int 216ftp_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 267tryagain: 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 332int 333another(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 */ 366char * 367remglob2(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 441again: 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 */ 469char * 470remglob(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 478int 479confirm(const char *cmd, const char *file) 480{ 481 char str[BUFSIZ]; 482 483 if (file && (confirmrest || !interactive)) 484 return (1); 485top: 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': 515quit: 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 */ 538int 539globulize(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 */ 568off_t 569remotesize(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 */ 605time_t 606remotemodtime(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 */ 679int 680fileindir(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 */ 711int 712foregroundproc(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 724static void 725updateprogressmeter(int signo) 726{ 727 int save_errno = errno; 728 729 /* update progressmeter if foreground process or in -m mode */ 730 if (foregroundproc() || progress == -1) 731 progressmeter(0, NULL); 732 errno = save_errno; 733} 734 735/* 736 * Display a transfer progress bar if progress is non-zero. 737 * SIGALRM is hijacked for use by this function. 738 * - Before the transfer, set filesize to size of file (or -1 if unknown), 739 * and call with flag = -1. This starts the once per second timer, 740 * and a call to updateprogressmeter() upon SIGALRM. 741 * - During the transfer, updateprogressmeter will call progressmeter 742 * with flag = 0 743 * - After the transfer, call with flag = 1 744 */ 745static struct timespec start; 746 747char *action; 748 749void 750progressmeter(int flag, const char *filename) 751{ 752 /* 753 * List of order of magnitude prefixes. 754 * The last is `P', as 2^64 = 16384 Petabytes 755 */ 756 static const char prefixes[] = " KMGTP"; 757 758 static struct timespec lastupdate; 759 static off_t lastsize; 760 static char *title = NULL; 761 struct timespec now, td, wait; 762 off_t cursize, abbrevsize; 763 double elapsed; 764 int ratio, barlength, i, remaining, overhead = 30; 765 char buf[512], *filenamebuf; 766 767 if (flag == -1) { 768 clock_gettime(CLOCK_MONOTONIC, &start); 769 lastupdate = start; 770 lastsize = restart_point; 771 } 772 clock_gettime(CLOCK_MONOTONIC, &now); 773 if (!progress || filesize < 0) 774 return; 775 cursize = bytes + restart_point; 776 777 if (filesize) 778 ratio = cursize * 100 / filesize; 779 else 780 ratio = 100; 781 ratio = MAXIMUM(ratio, 0); 782 ratio = MINIMUM(ratio, 100); 783 if (!verbose && flag == -1) { 784 if ((filenamebuf = strdup(filename)) != NULL && 785 (filename = basename(filenamebuf)) != NULL) { 786 free(title); 787 title = strdup(filename); 788 } 789 free(filenamebuf); 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 */ 920void 921ptransfer(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 967void 968list_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 */ 1009void 1010setttywidth(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 */ 1025void 1026alarmtimer(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 1042void 1043controlediting(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 * connect(2) with an optional timeout if secs > 0. 1079 */ 1080int 1081timed_connect(int s, const struct sockaddr *name, socklen_t namelen, int secs) 1082{ 1083 struct timespec now, target, timebuf, *timeout = NULL; 1084 int flags, nready, optval, ret = -1; 1085 socklen_t optlen; 1086 struct pollfd pfd; 1087 1088 if (secs > 0) { 1089 timebuf.tv_sec = secs; 1090 timebuf.tv_nsec = 0; 1091 timeout = &timebuf; 1092 clock_gettime(CLOCK_MONOTONIC, &target); 1093 timespecadd(&target, timeout, &target); 1094 } 1095 1096 flags = fcntl(s, F_GETFL, 0); 1097 if (flags == -1) { 1098 warn("fcntl(F_GETFL)"); 1099 return -1; 1100 } 1101 if (!(flags & O_NONBLOCK)) { 1102 if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) { 1103 warn("fcntl(F_SETFL)"); 1104 return -1; 1105 } 1106 } 1107 1108 ret = connect(s, name, namelen); 1109 if (ret == 0 || errno != EINPROGRESS) 1110 goto done; 1111 1112 for (;;) { 1113 pfd.fd = s; 1114 pfd.events = POLLOUT; 1115 nready = ppoll(&pfd, 1, timeout, NULL); 1116 switch (nready) { 1117 case -1: 1118 if (errno != EINTR && errno != EAGAIN) { 1119 warn("ppoll"); 1120 goto done; 1121 } 1122 if (timeout == NULL) 1123 continue; 1124 clock_gettime(CLOCK_MONOTONIC, &now); 1125 timespecsub(&now, &target, timeout); 1126 if (timeout->tv_sec >= 0) 1127 continue; 1128 /* FALLTHROUGH */ 1129 case 0: 1130 errno = ETIMEDOUT; 1131 goto done; 1132 default: 1133 optlen = sizeof(optval); 1134 ret = getsockopt(s, SOL_SOCKET, SO_ERROR, &optval, 1135 &optlen); 1136 if (ret == 0 && optval != 0) { 1137 ret = -1; 1138 errno = optval; 1139 } 1140 goto done; 1141 } 1142 } 1143 1144done: 1145 if (!(flags & O_NONBLOCK)) { 1146 if (fcntl(s, F_SETFL, flags) == -1) { 1147 warn("fcntl(F_SETFL)"); 1148 ret = -1; 1149 } 1150 } 1151 1152 return ret; 1153} 1154 1155#ifndef SMALL 1156ssize_t 1157http_time(time_t t, char *tmbuf, size_t len) 1158{ 1159 struct tm tm; 1160 1161 /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */ 1162 if (gmtime_r(&t, &tm) == NULL) 1163 return 0; 1164 else 1165 return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm)); 1166} 1167#endif /* !SMALL */ 1168