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