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