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