1 /* $NetBSD: util.c,v 1.22 1998/02/04 15:23:54 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1985, 1989, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 #ifndef lint 38 __RCSID("$NetBSD: util.c,v 1.22 1998/02/04 15:23:54 christos Exp $"); 39 #endif /* not lint */ 40 41 /* 42 * FTP User Program -- Misc support routines 43 */ 44 #include <sys/ioctl.h> 45 #include <sys/time.h> 46 #include <arpa/ftp.h> 47 48 #include <ctype.h> 49 #include <err.h> 50 #include <fcntl.h> 51 #include <glob.h> 52 #include <limits.h> 53 #include <pwd.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <time.h> 58 #include <tzfile.h> 59 #include <unistd.h> 60 61 #include "ftp_var.h" 62 #include "pathnames.h" 63 64 /* 65 * Connect to peer server and 66 * auto-login, if possible. 67 */ 68 void 69 setpeer(argc, argv) 70 int argc; 71 char *argv[]; 72 { 73 char *host; 74 in_port_t port; 75 76 if (connected) { 77 printf("Already connected to %s, use close first.\n", 78 hostname); 79 code = -1; 80 return; 81 } 82 if (argc < 2) 83 (void)another(&argc, &argv, "to"); 84 if (argc < 2 || argc > 3) { 85 printf("usage: %s host-name [port]\n", argv[0]); 86 code = -1; 87 return; 88 } 89 if (gatemode) 90 port = gateport; 91 else 92 port = ftpport; 93 if (argc > 2) { 94 char *ep; 95 long nport; 96 97 nport = strtol(argv[2], &ep, 10); 98 if (nport < 1 || nport > MAX_IN_PORT_T || *ep != '\0') { 99 printf("%s: bad port number '%s'.\n", argv[1], argv[2]); 100 printf("usage: %s host-name [port]\n", argv[0]); 101 code = -1; 102 return; 103 } 104 port = htons((in_port_t)nport); 105 } 106 107 if (gatemode) { 108 if (gateserver == NULL || *gateserver == '\0') 109 errx(1, "gateserver not defined (shouldn't happen)"); 110 host = hookup(gateserver, port); 111 } else 112 host = hookup(argv[1], port); 113 114 if (host) { 115 int overbose; 116 117 if (gatemode) { 118 if (command("PASSERVE %s", argv[1]) != COMPLETE) 119 return; 120 if (verbose) 121 printf("Connected via pass-through server %s\n", 122 gateserver); 123 } 124 125 connected = 1; 126 /* 127 * Set up defaults for FTP. 128 */ 129 (void)strcpy(typename, "ascii"), type = TYPE_A; 130 curtype = TYPE_A; 131 (void)strcpy(formname, "non-print"), form = FORM_N; 132 (void)strcpy(modename, "stream"), mode = MODE_S; 133 (void)strcpy(structname, "file"), stru = STRU_F; 134 (void)strcpy(bytename, "8"), bytesize = 8; 135 if (autologin) 136 (void)login(argv[1], NULL, NULL); 137 138 overbose = verbose; 139 if (debug == 0) 140 verbose = -1; 141 if (command("SYST") == COMPLETE && overbose) { 142 char *cp, c; 143 c = 0; 144 cp = strchr(reply_string+4, ' '); 145 if (cp == NULL) 146 cp = strchr(reply_string+4, '\r'); 147 if (cp) { 148 if (cp[-1] == '.') 149 cp--; 150 c = *cp; 151 *cp = '\0'; 152 } 153 154 printf("Remote system type is %s.\n", reply_string + 4); 155 if (cp) 156 *cp = c; 157 } 158 if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) { 159 if (proxy) 160 unix_proxy = 1; 161 else 162 unix_server = 1; 163 /* 164 * Set type to 0 (not specified by user), 165 * meaning binary by default, but don't bother 166 * telling server. We can use binary 167 * for text files unless changed by the user. 168 */ 169 type = 0; 170 (void)strcpy(typename, "binary"); 171 if (overbose) 172 printf("Using %s mode to transfer files.\n", 173 typename); 174 } else { 175 if (proxy) 176 unix_proxy = 0; 177 else 178 unix_server = 0; 179 if (overbose && 180 !strncmp(reply_string, "215 TOPS20", 10)) 181 puts( 182 "Remember to set tenex mode when transferring binary files from this machine."); 183 } 184 verbose = overbose; 185 } 186 } 187 188 189 /* 190 * login to remote host, using given username & password if supplied 191 */ 192 int 193 login(host, user, pass) 194 const char *host; 195 char *user, *pass; 196 { 197 char tmp[80]; 198 char *acct; 199 char anonpass[MAXLOGNAME + 1 + MAXHOSTNAMELEN]; /* "user@hostname" */ 200 char hostname[MAXHOSTNAMELEN]; 201 struct passwd *pw; 202 int n, aflag = 0; 203 204 acct = NULL; 205 if (user == NULL) { 206 if (ruserpass(host, &user, &pass, &acct) < 0) { 207 code = -1; 208 return (0); 209 } 210 } 211 212 /* 213 * Set up arguments for an anonymous FTP session, if necessary. 214 */ 215 if ((user == NULL || pass == NULL) && anonftp) { 216 memset(anonpass, 0, sizeof(anonpass)); 217 memset(hostname, 0, sizeof(hostname)); 218 219 /* 220 * Set up anonymous login password. 221 */ 222 if ((user = getlogin()) == NULL) { 223 if ((pw = getpwuid(getuid())) == NULL) 224 user = "anonymous"; 225 else 226 user = pw->pw_name; 227 } 228 gethostname(hostname, MAXHOSTNAMELEN); 229 #ifndef DONT_CHEAT_ANONPASS 230 /* 231 * Every anonymous FTP server I've encountered 232 * will accept the string "username@", and will 233 * append the hostname itself. We do this by default 234 * since many servers are picky about not having 235 * a FQDN in the anonymous password. - thorpej@netbsd.org 236 */ 237 snprintf(anonpass, sizeof(anonpass) - 1, "%s@", 238 user); 239 #else 240 snprintf(anonpass, sizeof(anonpass) - 1, "%s@%s", 241 user, hp->h_name); 242 #endif 243 pass = anonpass; 244 user = "anonymous"; /* as per RFC 1635 */ 245 } 246 247 while (user == NULL) { 248 char *myname = getlogin(); 249 250 if (myname == NULL && (pw = getpwuid(getuid())) != NULL) 251 myname = pw->pw_name; 252 if (myname) 253 printf("Name (%s:%s): ", host, myname); 254 else 255 printf("Name (%s): ", host); 256 *tmp = '\0'; 257 (void)fgets(tmp, sizeof(tmp) - 1, stdin); 258 tmp[strlen(tmp) - 1] = '\0'; 259 if (*tmp == '\0') 260 user = myname; 261 else 262 user = tmp; 263 } 264 n = command("USER %s", user); 265 if (n == CONTINUE) { 266 if (pass == NULL) 267 pass = getpass("Password:"); 268 n = command("PASS %s", pass); 269 } 270 if (n == CONTINUE) { 271 aflag++; 272 if (acct == NULL) 273 acct = getpass("Account:"); 274 n = command("ACCT %s", acct); 275 } 276 if ((n != COMPLETE) || 277 (!aflag && acct != NULL && command("ACCT %s", acct) != COMPLETE)) { 278 warnx("Login failed."); 279 return (0); 280 } 281 if (proxy) 282 return (1); 283 connected = -1; 284 for (n = 0; n < macnum; ++n) { 285 if (!strcmp("init", macros[n].mac_name)) { 286 (void)strcpy(line, "$init"); 287 makeargv(); 288 domacro(margc, margv); 289 break; 290 } 291 } 292 return (1); 293 } 294 295 /* 296 * `another' gets another argument, and stores the new argc and argv. 297 * It reverts to the top level (via main.c's intr()) on EOF/error. 298 * 299 * Returns false if no new arguments have been added. 300 */ 301 int 302 another(pargc, pargv, prompt) 303 int *pargc; 304 char ***pargv; 305 const char *prompt; 306 { 307 int len = strlen(line), ret; 308 309 if (len >= sizeof(line) - 3) { 310 puts("sorry, arguments too long."); 311 intr(); 312 } 313 printf("(%s) ", prompt); 314 line[len++] = ' '; 315 if (fgets(&line[len], sizeof(line) - len, stdin) == NULL) 316 intr(); 317 len += strlen(&line[len]); 318 if (len > 0 && line[len - 1] == '\n') 319 line[len - 1] = '\0'; 320 makeargv(); 321 ret = margc > *pargc; 322 *pargc = margc; 323 *pargv = margv; 324 return (ret); 325 } 326 327 /* 328 * glob files given in argv[] from the remote server. 329 * if errbuf isn't NULL, store error messages there instead 330 * of writing to the screen. 331 */ 332 char * 333 remglob(argv, doswitch, errbuf) 334 char *argv[]; 335 int doswitch; 336 char **errbuf; 337 { 338 char temp[MAXPATHLEN]; 339 static char buf[MAXPATHLEN]; 340 static FILE *ftemp = NULL; 341 static char **args; 342 int oldverbose, oldhash, fd; 343 char *cp, *mode; 344 345 if (!mflag) { 346 if (!doglob) 347 args = NULL; 348 else { 349 if (ftemp) { 350 (void)fclose(ftemp); 351 ftemp = NULL; 352 } 353 } 354 return (NULL); 355 } 356 if (!doglob) { 357 if (args == NULL) 358 args = argv; 359 if ((cp = *++args) == NULL) 360 args = NULL; 361 return (cp); 362 } 363 if (ftemp == NULL) { 364 (void)snprintf(temp, sizeof(temp), "%s/%s", tmpdir, TMPFILE); 365 if ((fd = mkstemp(temp)) < 0) { 366 warn("unable to create temporary file %s", temp); 367 return (NULL); 368 } 369 close(fd); 370 oldverbose = verbose; 371 verbose = (errbuf != NULL) ? -1 : 0; 372 oldhash = hash; 373 hash = 0; 374 if (doswitch) 375 pswitch(!proxy); 376 for (mode = "w"; *++argv != NULL; mode = "a") 377 recvrequest("NLST", temp, *argv, mode, 0, 0); 378 if ((code / 100) != COMPLETE) { 379 if (errbuf != NULL) 380 *errbuf = reply_string; 381 } 382 if (doswitch) 383 pswitch(!proxy); 384 verbose = oldverbose; 385 hash = oldhash; 386 ftemp = fopen(temp, "r"); 387 (void)unlink(temp); 388 if (ftemp == NULL) { 389 if (errbuf == NULL) 390 puts("can't find list of remote files, oops."); 391 else 392 *errbuf = 393 "can't find list of remote files, oops."; 394 return (NULL); 395 } 396 } 397 if (fgets(buf, sizeof(buf), ftemp) == NULL) { 398 (void)fclose(ftemp); 399 ftemp = NULL; 400 return (NULL); 401 } 402 if ((cp = strchr(buf, '\n')) != NULL) 403 *cp = '\0'; 404 return (buf); 405 } 406 407 int 408 confirm(cmd, file) 409 const char *cmd, *file; 410 { 411 char line[BUFSIZ]; 412 413 if (!interactive || confirmrest) 414 return (1); 415 printf("%s %s? ", cmd, file); 416 (void)fflush(stdout); 417 if (fgets(line, sizeof(line), stdin) == NULL) 418 return (0); 419 switch (tolower(*line)) { 420 case 'n': 421 return (0); 422 case 'p': 423 interactive = 0; 424 puts("Interactive mode: off."); 425 break; 426 case 'a': 427 confirmrest = 1; 428 printf("Prompting off for duration of %s.\n", cmd); 429 break; 430 } 431 return (1); 432 } 433 434 /* 435 * Glob a local file name specification with 436 * the expectation of a single return value. 437 * Can't control multiple values being expanded 438 * from the expression, we return only the first. 439 */ 440 int 441 globulize(cpp) 442 char **cpp; 443 { 444 glob_t gl; 445 int flags; 446 447 if (!doglob) 448 return (1); 449 450 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; 451 memset(&gl, 0, sizeof(gl)); 452 if (glob(*cpp, flags, NULL, &gl) || 453 gl.gl_pathc == 0) { 454 warnx("%s: not found", *cpp); 455 globfree(&gl); 456 return (0); 457 } 458 /* XXX: caller should check if *cpp changed, and 459 * free(*cpp) if that is the case 460 */ 461 *cpp = strdup(gl.gl_pathv[0]); 462 globfree(&gl); 463 return (1); 464 } 465 466 /* 467 * determine size of remote file 468 */ 469 off_t 470 remotesize(file, noisy) 471 const char *file; 472 int noisy; 473 { 474 int overbose; 475 off_t size; 476 477 overbose = verbose; 478 size = -1; 479 if (debug == 0) 480 verbose = -1; 481 if (command("SIZE %s", file) == COMPLETE) { 482 char *cp, *ep; 483 484 cp = strchr(reply_string, ' '); 485 if (cp != NULL) { 486 cp++; 487 size = strtoq(cp, &ep, 10); 488 if (*ep != '\0' && !isspace(*ep)) 489 size = -1; 490 } 491 } else if (noisy && debug == 0) 492 puts(reply_string); 493 verbose = overbose; 494 return (size); 495 } 496 497 /* 498 * determine last modification time (in GMT) of remote file 499 */ 500 time_t 501 remotemodtime(file, noisy) 502 const char *file; 503 int noisy; 504 { 505 int overbose; 506 time_t rtime; 507 int ocode; 508 509 overbose = verbose; 510 ocode = code; 511 rtime = -1; 512 if (debug == 0) 513 verbose = -1; 514 if (command("MDTM %s", file) == COMPLETE) { 515 struct tm timebuf; 516 int yy, mo, day, hour, min, sec; 517 sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo, 518 &day, &hour, &min, &sec); 519 memset(&timebuf, 0, sizeof(timebuf)); 520 timebuf.tm_sec = sec; 521 timebuf.tm_min = min; 522 timebuf.tm_hour = hour; 523 timebuf.tm_mday = day; 524 timebuf.tm_mon = mo - 1; 525 timebuf.tm_year = yy - TM_YEAR_BASE; 526 timebuf.tm_isdst = -1; 527 rtime = mktime(&timebuf); 528 if (rtime == -1 && (noisy || debug != 0)) 529 printf("Can't convert %s to a time.\n", reply_string); 530 else 531 rtime += timebuf.tm_gmtoff; /* conv. local -> GMT */ 532 } else if (noisy && debug == 0) 533 puts(reply_string); 534 verbose = overbose; 535 if (rtime == -1) 536 code = ocode; 537 return (rtime); 538 } 539 540 #ifndef SMALL 541 static void updateprogressmeter __P((int)); 542 543 void 544 updateprogressmeter(dummy) 545 int dummy; 546 { 547 static pid_t pgrp = -1; 548 int ctty_pgrp; 549 550 if (pgrp == -1) 551 pgrp = getpgrp(); 552 553 /* 554 * print progress bar only if we are foreground process. 555 */ 556 if (ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 && 557 ctty_pgrp == (int)pgrp) 558 progressmeter(0); 559 } 560 #endif /* SMALL */ 561 562 /* 563 * Display a transfer progress bar if progress is non-zero. 564 * SIGALRM is hijacked for use by this function. 565 * - Before the transfer, set filesize to size of file (or -1 if unknown), 566 * and call with flag = -1. This starts the once per second timer, 567 * and a call to updateprogressmeter() upon SIGALRM. 568 * - During the transfer, updateprogressmeter will call progressmeter 569 * with flag = 0 570 * - After the transfer, call with flag = 1 571 */ 572 static struct timeval start; 573 static struct timeval lastupdate; 574 575 void 576 progressmeter(flag) 577 int flag; 578 { 579 #ifndef SMALL 580 /* 581 * List of order of magnitude prefixes. 582 * The last is `P', as 2^64 = 16384 Petabytes 583 */ 584 static const char prefixes[] = " KMGTP"; 585 586 static off_t lastsize; 587 struct timeval now, td, wait; 588 off_t cursize, abbrevsize; 589 double elapsed; 590 int ratio, barlength, i, len, remaining; 591 char buf[256]; 592 593 len = 0; 594 595 if (flag == -1) { 596 (void)gettimeofday(&start, NULL); 597 lastupdate = start; 598 lastsize = restart_point; 599 } 600 (void)gettimeofday(&now, NULL); 601 if (!progress || filesize <= 0) 602 return; 603 cursize = bytes + restart_point; 604 605 ratio = cursize * 100 / filesize; 606 ratio = MAX(ratio, 0); 607 ratio = MIN(ratio, 100); 608 len += snprintf(buf + len, sizeof(buf) - len, "\r%3d%% ", ratio); 609 610 barlength = ttywidth - 30; 611 if (barlength > 0) { 612 i = barlength * ratio / 100; 613 len += snprintf(buf + len, sizeof(buf) - len, 614 "|%.*s%*s|", i, 615 "*****************************************************************************" 616 "*****************************************************************************", 617 barlength - i, ""); 618 } 619 620 i = 0; 621 abbrevsize = cursize; 622 while (abbrevsize >= 100000 && i < sizeof(prefixes)) { 623 i++; 624 abbrevsize >>= 10; 625 } 626 len += snprintf(buf + len, sizeof(buf) - len, 627 " %5qd %c%c ", (long long)abbrevsize, prefixes[i], 628 prefixes[i] == ' ' ? ' ' : 'B'); 629 630 timersub(&now, &lastupdate, &wait); 631 if (cursize > lastsize) { 632 lastupdate = now; 633 lastsize = cursize; 634 if (wait.tv_sec >= STALLTIME) { /* fudge out stalled time */ 635 start.tv_sec += wait.tv_sec; 636 start.tv_usec += wait.tv_usec; 637 } 638 wait.tv_sec = 0; 639 } 640 641 timersub(&now, &start, &td); 642 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 643 644 if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { 645 len += snprintf(buf + len, sizeof(buf) - len, 646 " --:-- ETA"); 647 } else if (wait.tv_sec >= STALLTIME) { 648 len += snprintf(buf + len, sizeof(buf) - len, 649 " - stalled -"); 650 } else { 651 remaining = (int) 652 ((filesize - restart_point) / (bytes / elapsed) - elapsed); 653 if (remaining >= 100 * SECSPERHOUR) 654 len += snprintf(buf + len, sizeof(buf) - len, 655 " --:-- ETA"); 656 else { 657 i = remaining / SECSPERHOUR; 658 if (i) 659 len += snprintf(buf + len, sizeof(buf) - len, 660 "%2d:", i); 661 else 662 len += snprintf(buf + len, sizeof(buf) - len, 663 " "); 664 i = remaining % SECSPERHOUR; 665 len += snprintf(buf + len, sizeof(buf) - len, 666 "%02d:%02d ETA", i / 60, i % 60); 667 } 668 } 669 (void)write(STDOUT_FILENO, buf, len); 670 671 if (flag == -1) { 672 (void)signal(SIGALRM, updateprogressmeter); 673 alarmtimer(1); /* set alarm timer for 1 Hz */ 674 } else if (flag == 1) { 675 alarmtimer(0); 676 (void)putchar('\n'); 677 } 678 fflush(stdout); 679 #endif /* SMALL */ 680 } 681 682 /* 683 * Display transfer statistics. 684 * Requires start to be initialised by progressmeter(-1), 685 * direction to be defined by xfer routines, and filesize and bytes 686 * to be updated by xfer routines 687 * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR 688 * instead of STDOUT. 689 */ 690 void 691 ptransfer(siginfo) 692 int siginfo; 693 { 694 #ifndef SMALL 695 struct timeval now, td, wait; 696 double elapsed; 697 off_t bs; 698 int meg, remaining, hh, len; 699 char buf[100]; 700 701 if (!verbose && !siginfo) 702 return; 703 704 (void)gettimeofday(&now, NULL); 705 timersub(&now, &start, &td); 706 elapsed = td.tv_sec + (td.tv_usec / 1000000.0); 707 bs = bytes / (elapsed == 0.0 ? 1 : elapsed); 708 meg = 0; 709 if (bs > (1024 * 1024)) 710 meg = 1; 711 len = 0; 712 len += snprintf(buf + len, sizeof(buf) - len, 713 "%qd byte%s %s in ", (long long)bytes, bytes == 1 ? "" : "s", 714 direction); 715 remaining = (int)elapsed; 716 if (remaining > SECSPERDAY) { 717 int days; 718 719 days = remaining / SECSPERDAY; 720 remaining %= SECSPERDAY; 721 len += snprintf(buf + len, sizeof(buf) - len, 722 "%d day%s ", days, days == 1 ? "" : "s"); 723 } 724 hh = remaining / SECSPERHOUR; 725 remaining %= SECSPERHOUR; 726 if (hh) 727 len += snprintf(buf + len, sizeof(buf) - len, "%2d:", hh); 728 len += snprintf(buf + len, sizeof(buf) - len, 729 "%02d:%02d (%.2f %sB/s)", remaining / 60, remaining % 60, 730 bs / (1024.0 * (meg ? 1024.0 : 1.0)), 731 meg ? "M" : "K"); 732 733 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 734 && bytes + restart_point <= filesize) { 735 remaining = (int)((filesize - restart_point) / 736 (bytes / elapsed) - elapsed); 737 hh = remaining / SECSPERHOUR; 738 remaining %= SECSPERHOUR; 739 len += snprintf(buf + len, sizeof(buf) - len, " ETA: "); 740 if (hh) 741 len += snprintf(buf + len, sizeof(buf) - len, "%2d:", 742 hh); 743 len += snprintf(buf + len, sizeof(buf) - len, 744 "%02d:%02d", remaining / 60, remaining % 60); 745 timersub(&now, &lastupdate, &wait); 746 if (wait.tv_sec >= STALLTIME) 747 len += snprintf(buf + len, sizeof(buf) - len, 748 " (stalled)"); 749 } 750 len += snprintf(buf + len, sizeof(buf) - len, "\n"); 751 (void)write(siginfo ? STDERR_FILENO : STDOUT_FILENO, buf, len); 752 #endif /* SMALL */ 753 } 754 755 /* 756 * List words in stringlist, vertically arranged 757 */ 758 void 759 list_vertical(sl) 760 StringList *sl; 761 { 762 int i, j, w; 763 int columns, width, lines, items; 764 char *p; 765 766 width = items = 0; 767 768 for (i = 0 ; i < sl->sl_cur ; i++) { 769 w = strlen(sl->sl_str[i]); 770 if (w > width) 771 width = w; 772 } 773 width = (width + 8) &~ 7; 774 775 columns = ttywidth / width; 776 if (columns == 0) 777 columns = 1; 778 lines = (sl->sl_cur + columns - 1) / columns; 779 for (i = 0; i < lines; i++) { 780 for (j = 0; j < columns; j++) { 781 p = sl->sl_str[j * lines + i]; 782 if (p) 783 fputs(p, stdout); 784 if (j * lines + i + lines >= sl->sl_cur) { 785 putchar('\n'); 786 break; 787 } 788 w = strlen(p); 789 while (w < width) { 790 w = (w + 8) &~ 7; 791 (void)putchar('\t'); 792 } 793 } 794 } 795 } 796 797 /* 798 * Update the global ttywidth value, using TIOCGWINSZ. 799 */ 800 void 801 setttywidth(a) 802 int a; 803 { 804 struct winsize winsize; 805 806 if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1) 807 ttywidth = winsize.ws_col; 808 else 809 ttywidth = 80; 810 } 811 812 /* 813 * Set the SIGALRM interval timer for wait seconds, 0 to disable. 814 */ 815 void 816 alarmtimer(wait) 817 int wait; 818 { 819 struct itimerval itv; 820 821 itv.it_value.tv_sec = wait; 822 itv.it_value.tv_usec = 0; 823 itv.it_interval = itv.it_value; 824 setitimer(ITIMER_REAL, &itv, NULL); 825 } 826 827 /* 828 * Setup or cleanup EditLine structures 829 */ 830 #ifndef SMALL 831 void 832 controlediting() 833 { 834 if (editing && el == NULL && hist == NULL) { 835 HistEvent ev; 836 837 el = el_init(__progname, stdin, stdout); /* init editline */ 838 hist = history_init(); /* init the builtin history */ 839 history(hist, &ev, H_SETMAXSIZE, 100);/* remember 100 events */ 840 el_set(el, EL_HIST, history, hist); /* use history */ 841 842 el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */ 843 el_set(el, EL_PROMPT, prompt); /* set the prompt function */ 844 845 /* add local file completion, bind to TAB */ 846 el_set(el, EL_ADDFN, "ftp-complete", 847 "Context sensitive argument completion", 848 complete); 849 el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 850 851 el_source(el, NULL); /* read ~/.editrc */ 852 el_set(el, EL_SIGNAL, 1); 853 } else if (!editing) { 854 if (hist) { 855 history_end(hist); 856 hist = NULL; 857 } 858 if (el) { 859 el_end(el); 860 el = NULL; 861 } 862 } 863 } 864 #endif /* !SMALL */ 865