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