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