util.c revision 1.5 1 /* $NetBSD: util.c,v 1.5 1997/03/13 06:23:21 lukem Exp $ */
2
3 /*
4 * Copyright (c) 1985, 1989, 1993, 1994
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #ifndef lint
37 static char rcsid[] = "$NetBSD: util.c,v 1.5 1997/03/13 06:23:21 lukem Exp $";
38 #endif /* not lint */
39
40 /*
41 * FTP User Program -- Misc support routines
42 */
43 #include <sys/ioctl.h>
44 #include <sys/time.h>
45 #include <arpa/ftp.h>
46
47 #include <ctype.h>
48 #include <err.h>
49 #include <fcntl.h>
50 #include <glob.h>
51 #include <stdio.h>
52 #include <string.h>
53 #include <time.h>
54 #include <unistd.h>
55
56 #include "ftp_var.h"
57 #include "pathnames.h"
58
59 /*
60 * Connect to peer server and
61 * auto-login, if possible.
62 */
63 void
64 setpeer(argc, argv)
65 int argc;
66 char *argv[];
67 {
68 char *host;
69 short port;
70
71 if (connected) {
72 printf("Already connected to %s, use close first.\n",
73 hostname);
74 code = -1;
75 return;
76 }
77 if (argc < 2)
78 (void)another(&argc, &argv, "to");
79 if (argc < 2 || argc > 3) {
80 printf("usage: %s host-name [port]\n", argv[0]);
81 code = -1;
82 return;
83 }
84 port = ftpport;
85 if (argc > 2) {
86 port = atoi(argv[2]);
87 if (port <= 0) {
88 printf("%s: bad port number '%s'.\n", argv[1], argv[2]);
89 printf("usage: %s host-name [port]\n", argv[0]);
90 code = -1;
91 return;
92 }
93 port = htons(port);
94 }
95 host = hookup(argv[1], port);
96 if (host) {
97 int overbose;
98
99 connected = 1;
100 /*
101 * Set up defaults for FTP.
102 */
103 (void)strcpy(typename, "ascii"), type = TYPE_A;
104 curtype = TYPE_A;
105 (void)strcpy(formname, "non-print"), form = FORM_N;
106 (void)strcpy(modename, "stream"), mode = MODE_S;
107 (void)strcpy(structname, "file"), stru = STRU_F;
108 (void)strcpy(bytename, "8"), bytesize = 8;
109 if (autologin)
110 (void)login(argv[1]);
111
112 overbose = verbose;
113 if (debug == 0)
114 verbose = -1;
115 if (command("SYST") == COMPLETE && overbose) {
116 char *cp, c;
117 c = 0;
118 cp = strchr(reply_string+4, ' ');
119 if (cp == NULL)
120 cp = strchr(reply_string+4, '\r');
121 if (cp) {
122 if (cp[-1] == '.')
123 cp--;
124 c = *cp;
125 *cp = '\0';
126 }
127
128 printf("Remote system type is %s.\n", reply_string + 4);
129 if (cp)
130 *cp = c;
131 }
132 if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) {
133 if (proxy)
134 unix_proxy = 1;
135 else
136 unix_server = 1;
137 /*
138 * Set type to 0 (not specified by user),
139 * meaning binary by default, but don't bother
140 * telling server. We can use binary
141 * for text files unless changed by the user.
142 */
143 type = 0;
144 (void)strcpy(typename, "binary");
145 if (overbose)
146 printf("Using %s mode to transfer files.\n",
147 typename);
148 } else {
149 if (proxy)
150 unix_proxy = 0;
151 else
152 unix_server = 0;
153 if (overbose &&
154 !strncmp(reply_string, "215 TOPS20", 10))
155 puts(
156 "Remember to set tenex mode when transferring binary files from this machine.");
157 }
158 verbose = overbose;
159 }
160 }
161
162 /*
163 * `another' gets another argument, and stores the new argc and argv.
164 * It reverts to the top level (via main.c's intr()) on EOF/error.
165 *
166 * Returns false if no new arguments have been added.
167 */
168 int
169 another(pargc, pargv, prompt)
170 int *pargc;
171 char ***pargv;
172 const char *prompt;
173 {
174 int len = strlen(line), ret;
175
176 if (len >= sizeof(line) - 3) {
177 puts("sorry, arguments too long.");
178 intr();
179 }
180 printf("(%s) ", prompt);
181 line[len++] = ' ';
182 if (fgets(&line[len], sizeof(line) - len, stdin) == NULL)
183 intr();
184 len += strlen(&line[len]);
185 if (len > 0 && line[len - 1] == '\n')
186 line[len - 1] = '\0';
187 makeargv();
188 ret = margc > *pargc;
189 *pargc = margc;
190 *pargv = margv;
191 return (ret);
192 }
193
194 /*
195 * glob files given in argv[] from the remote server.
196 * if errbuf isn't NULL, store error messages there instead
197 * of writing to the screen.
198 */
199 char *
200 remglob(argv, doswitch, errbuf)
201 char *argv[];
202 int doswitch;
203 char **errbuf;
204 {
205 char temp[MAXPATHLEN];
206 static char buf[MAXPATHLEN];
207 static FILE *ftemp = NULL;
208 static char **args;
209 int oldverbose, oldhash, fd;
210 char *cp, *mode;
211
212 if (!mflag) {
213 if (!doglob)
214 args = NULL;
215 else {
216 if (ftemp) {
217 (void)fclose(ftemp);
218 ftemp = NULL;
219 }
220 }
221 return (NULL);
222 }
223 if (!doglob) {
224 if (args == NULL)
225 args = argv;
226 if ((cp = *++args) == NULL)
227 args = NULL;
228 return (cp);
229 }
230 if (ftemp == NULL) {
231 (void)snprintf(temp, sizeof(temp), "%s%s", _PATH_TMP, TMPFILE);
232 if ((fd = mkstemp(temp)) < 0) {
233 warn("unable to create temporary file %s", temp);
234 return (NULL);
235 }
236 close(fd);
237 oldverbose = verbose;
238 verbose = (errbuf != NULL) ? -1 : 0;
239 oldhash = hash;
240 hash = 0;
241 if (doswitch)
242 pswitch(!proxy);
243 for (mode = "w"; *++argv != NULL; mode = "a")
244 recvrequest("NLST", temp, *argv, mode, 0);
245 if ((code / 100) != COMPLETE) {
246 if (errbuf != NULL)
247 *errbuf = reply_string;
248 }
249 if (doswitch)
250 pswitch(!proxy);
251 verbose = oldverbose;
252 hash = oldhash;
253 ftemp = fopen(temp, "r");
254 (void)unlink(temp);
255 if (ftemp == NULL) {
256 if (errbuf == NULL)
257 puts("can't find list of remote files, oops.");
258 else
259 *errbuf =
260 "can't find list of remote files, oops.";
261 return (NULL);
262 }
263 }
264 if (fgets(buf, sizeof(buf), ftemp) == NULL) {
265 (void)fclose(ftemp);
266 ftemp = NULL;
267 return (NULL);
268 }
269 if ((cp = strchr(buf, '\n')) != NULL)
270 *cp = '\0';
271 return (buf);
272 }
273
274 int
275 confirm(cmd, file)
276 const char *cmd, *file;
277 {
278 char line[BUFSIZ];
279
280 if (!interactive || confirmrest)
281 return (1);
282 printf("%s %s? ", cmd, file);
283 (void)fflush(stdout);
284 if (fgets(line, sizeof(line), stdin) == NULL)
285 return (0);
286 switch (tolower(*line)) {
287 case 'n':
288 return (0);
289 case 'p':
290 interactive = 0;
291 puts("Interactive mode: off.");
292 break;
293 case 'a':
294 confirmrest = 1;
295 printf("Prompting off for duration of %s.\n", cmd);
296 break;
297 }
298 return (1);
299 }
300
301 /*
302 * Glob a local file name specification with
303 * the expectation of a single return value.
304 * Can't control multiple values being expanded
305 * from the expression, we return only the first.
306 */
307 int
308 globulize(cpp)
309 char **cpp;
310 {
311 glob_t gl;
312 int flags;
313
314 if (!doglob)
315 return (1);
316
317 flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
318 memset(&gl, 0, sizeof(gl));
319 if (glob(*cpp, flags, NULL, &gl) ||
320 gl.gl_pathc == 0) {
321 warnx("%s: not found", *cpp);
322 globfree(&gl);
323 return (0);
324 }
325 *cpp = strdup(gl.gl_pathv[0]); /* XXX - wasted memory */
326 globfree(&gl);
327 return (1);
328 }
329
330 /*
331 * determine size of remote file
332 */
333 off_t
334 remotesize(file, noisy)
335 const char *file;
336 int noisy;
337 {
338 int overbose;
339 off_t size;
340
341 overbose = verbose;
342 size = -1;
343 if (debug == 0)
344 verbose = -1;
345 if (command("SIZE %s", file) == COMPLETE)
346 sscanf(reply_string, "%*s %qd", &size);
347 else if (noisy && debug == 0)
348 puts(reply_string);
349 verbose = overbose;
350 return (size);
351 }
352
353 /*
354 * determine last modification time (in GMT) of remote file
355 */
356 time_t
357 remotemodtime(file, noisy)
358 const char *file;
359 int noisy;
360 {
361 int overbose;
362 time_t rtime;
363
364 overbose = verbose;
365 rtime = -1;
366 if (debug == 0)
367 verbose = -1;
368 if (command("MDTM %s", file) == COMPLETE) {
369 struct tm timebuf;
370 int yy, mo, day, hour, min, sec;
371 sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo,
372 &day, &hour, &min, &sec);
373 memset(&timebuf, 0, sizeof(timebuf));
374 timebuf.tm_sec = sec;
375 timebuf.tm_min = min;
376 timebuf.tm_hour = hour;
377 timebuf.tm_mday = day;
378 timebuf.tm_mon = mo - 1;
379 timebuf.tm_year = yy - 1900;
380 timebuf.tm_isdst = -1;
381 rtime = mktime(&timebuf);
382 if (rtime == -1 && (noisy || debug != 0))
383 printf("Can't convert %s to a time.\n", reply_string);
384 else
385 rtime += timebuf.tm_gmtoff; /* conv. local -> GMT */
386 } else if (noisy && debug == 0)
387 puts(reply_string);
388 verbose = overbose;
389 return (rtime);
390 }
391
392 void
393 updateprogressmeter()
394 {
395
396 progressmeter(0);
397 }
398
399 /*
400 * Display a transfer progress bar if progress is non-zero.
401 * SIGALRM is hijacked for use by this function.
402 * - Before the transfer, set filesize to size of file (or -1 if unknown),
403 * and call with flag = -1. This starts the once per second timer,
404 * and a call to updateprogressmeter() upon SIGALRM.
405 * - During the transfer, updateprogressmeter will call progressmeter
406 * with flag = 0
407 * - After the transfer, call with flag = 1
408 */
409 static struct timeval start;
410
411 void
412 progressmeter(flag)
413 int flag;
414 {
415 /*
416 * List of order of magnitude prefixes.
417 * The last is `P', as 2^64 = 16384 Petabytes
418 */
419 static const char prefixes[] = " KMGTP";
420
421 static struct timeval lastupdate;
422 static off_t lastsize;
423 struct timeval now, td, wait;
424 off_t cursize, abbrevsize;
425 double elapsed;
426 int ratio, barlength, i, remaining;
427 char buf[256];
428
429 if (flag == -1) {
430 (void)gettimeofday(&start, (struct timezone *)0);
431 lastupdate = start;
432 lastsize = restart_point;
433 }
434 (void)gettimeofday(&now, (struct timezone *)0);
435 if (!progress || filesize <= 0)
436 return;
437 cursize = bytes + restart_point;
438
439 ratio = cursize * 100 / filesize;
440 ratio = MAX(ratio, 0);
441 ratio = MIN(ratio, 100);
442 snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
443
444 barlength = ttywidth - 30;
445 if (barlength > 0) {
446 i = barlength * ratio / 100;
447 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
448 "|%.*s%*s|", i,
449 "*****************************************************************************"
450 "*****************************************************************************",
451 barlength - i, "");
452 }
453
454 i = 0;
455 abbrevsize = cursize;
456 while (abbrevsize >= 100000 && i < sizeof(prefixes)) {
457 i++;
458 abbrevsize >>= 10;
459 }
460 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
461 " %5qd %c%c ", abbrevsize, prefixes[i],
462 prefixes[i] == ' ' ? ' ' : 'B');
463
464 timersub(&now, &lastupdate, &wait);
465 if (cursize > lastsize) {
466 lastupdate = now;
467 lastsize = cursize;
468 if (wait.tv_sec >= STALLTIME) { /* fudge out stalled time */
469 start.tv_sec += wait.tv_sec;
470 start.tv_usec += wait.tv_usec;
471 }
472 wait.tv_sec = 0;
473 }
474
475 timersub(&now, &start, &td);
476 elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
477
478 if (bytes <= 0 || elapsed <= 0.0) {
479 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
480 " --:-- ETA");
481 } else if (wait.tv_sec >= STALLTIME) {
482 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
483 " - stalled -");
484 } else {
485 remaining = (int)((filesize - restart_point) /
486 (bytes / elapsed) - elapsed);
487 i = remaining / 3600;
488 if (i)
489 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
490 "%2d:", i);
491 else
492 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
493 " ");
494 i = remaining % 3600;
495 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
496 "%02d:%02d ETA", i / 60, i % 60);
497 }
498 (void)write(STDOUT_FILENO, buf, strlen(buf));
499
500 if (flag == -1) {
501 (void)signal(SIGALRM, updateprogressmeter);
502 alarmtimer(1); /* set alarm timer for 1 Hz */
503 } else if (flag == 1) {
504 alarmtimer(0);
505 (void)putchar('\n');
506 }
507 fflush(stdout);
508 }
509
510 /*
511 * Display transfer statistics.
512 * Requires start to be initialised by progressmeter(-1),
513 * direction to be defined by xfer routines, and filesize and bytes
514 * to be updated by xfer routines
515 * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
516 * instead of STDOUT.
517 */
518 void
519 ptransfer(siginfo)
520 int siginfo;
521 {
522 struct timeval now, td;
523 double elapsed;
524 off_t bs;
525 int meg, remaining, hh;
526 char buf[100];
527
528 if (!verbose && !siginfo)
529 return;
530
531 (void)gettimeofday(&now, (struct timezone *)0);
532 timersub(&now, &start, &td);
533 elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
534 bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
535 meg = 0;
536 if (bs > (1024 * 1024))
537 meg = 1;
538 (void)snprintf(buf, sizeof(buf),
539 "%qd byte%s %s in %.2f seconds (%.2f %sB/s)\n",
540 bytes, bytes == 1 ? "" : "s", direction, elapsed,
541 bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K");
542 if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0) {
543 remaining = (int)((filesize - restart_point) /
544 (bytes / elapsed) - elapsed);
545 hh = remaining / 3600;
546 remaining %= 3600;
547 snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
548 " ETA: %02d:%02d:%02d\n", hh, remaining / 60,
549 remaining % 60);
550 }
551 (void)write(siginfo ? STDERR_FILENO : STDOUT_FILENO, buf, strlen(buf));
552 }
553
554 /*
555 * List words in stringlist, vertically arranged
556 */
557 void
558 list_vertical(sl)
559 StringList *sl;
560 {
561 int i, j, w;
562 int columns, width, lines, items;
563 char *p;
564
565 width = items = 0;
566
567 for (i = 0 ; i < sl->sl_cur ; i++) {
568 w = strlen(sl->sl_str[i]);
569 if (w > width)
570 width = w;
571 }
572 width = (width + 8) &~ 7;
573
574 columns = ttywidth / width;
575 if (columns == 0)
576 columns = 1;
577 lines = (sl->sl_cur + columns - 1) / columns;
578 for (i = 0; i < lines; i++) {
579 for (j = 0; j < columns; j++) {
580 p = sl->sl_str[j * lines + i];
581 if (p)
582 fputs(p, stdout);
583 if (j * lines + i + lines >= sl->sl_cur) {
584 putchar('\n');
585 break;
586 }
587 w = strlen(p);
588 while (w < width) {
589 w = (w + 8) &~ 7;
590 (void)putchar('\t');
591 }
592 }
593 }
594 }
595
596 /*
597 * Update the global ttywidth value, using TIOCGWINSZ.
598 */
599 void
600 setttywidth(a)
601 int a;
602 {
603 struct winsize winsize;
604
605 if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1)
606 ttywidth = winsize.ws_col;
607 else
608 ttywidth = 80;
609 }
610
611 /*
612 * Set the SIGALRM interval timer for wait seconds, 0 to disable.
613 */
614
615 void
616 alarmtimer(wait)
617 int wait;
618 {
619 struct itimerval itv;
620
621 itv.it_value.tv_sec = wait;
622 itv.it_value.tv_usec = 0;
623 itv.it_interval = itv.it_value;
624 setitimer(ITIMER_REAL, &itv, NULL);
625 }
626