summaryrefslogtreecommitdiff
path: root/scp.c
diff options
context:
space:
mode:
Diffstat (limited to 'scp.c')
-rw-r--r--scp.c335
1 files changed, 134 insertions, 201 deletions
diff --git a/scp.c b/scp.c
index 10235b1be..35d4c5f71 100644
--- a/scp.c
+++ b/scp.c
@@ -75,13 +75,14 @@
75 */ 75 */
76 76
77#include "includes.h" 77#include "includes.h"
78RCSID("$OpenBSD: scp.c,v 1.91 2002/06/19 00:27:55 deraadt Exp $"); 78RCSID("$OpenBSD: scp.c,v 1.102 2003/03/05 22:33:43 markus Exp $");
79 79
80#include "xmalloc.h" 80#include "xmalloc.h"
81#include "atomicio.h" 81#include "atomicio.h"
82#include "pathnames.h" 82#include "pathnames.h"
83#include "log.h" 83#include "log.h"
84#include "misc.h" 84#include "misc.h"
85#include "progressmeter.h"
85 86
86#ifdef HAVE___PROGNAME 87#ifdef HAVE___PROGNAME
87extern char *__progname; 88extern char *__progname;
@@ -89,29 +90,13 @@ extern char *__progname;
89char *__progname; 90char *__progname;
90#endif 91#endif
91 92
92/* For progressmeter() -- number of seconds before xfer considered "stalled" */ 93void bwlimit(int);
93#define STALLTIME 5
94/* alarm() interval for updating progress meter */
95#define PROGRESSTIME 1
96
97/* Visual statistics about files as they are transferred. */
98void progressmeter(int);
99
100/* Returns width of the terminal (for progress meter calculations). */
101int getttywidth(void);
102int do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc);
103 94
104/* Struct for addargs */ 95/* Struct for addargs */
105arglist args; 96arglist args;
106 97
107/* Time a transfer started. */ 98/* Bandwidth limit */
108static struct timeval start; 99off_t limitbw = 0;
109
110/* Number of bytes of current file transferred so far. */
111volatile off_t statbytes;
112
113/* Total size of current file. */
114off_t totalbytes = 0;
115 100
116/* Name of current file being transferred. */ 101/* Name of current file being transferred. */
117char *curfile; 102char *curfile;
@@ -125,6 +110,9 @@ int showprogress = 1;
125/* This is the program to execute for the secured connection. ("ssh" or -S) */ 110/* This is the program to execute for the secured connection. ("ssh" or -S) */
126char *ssh_program = _PATH_SSH_PROGRAM; 111char *ssh_program = _PATH_SSH_PROGRAM;
127 112
113/* This is used to store the pid of ssh_program */
114pid_t do_cmd_pid;
115
128/* 116/*
129 * This function executes the given command as the specified user on the 117 * This function executes the given command as the specified user on the
130 * given host. This returns < 0 if execution fails, and >= 0 otherwise. This 118 * given host. This returns < 0 if execution fails, and >= 0 otherwise. This
@@ -159,7 +147,8 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc)
159 close(reserved[1]); 147 close(reserved[1]);
160 148
161 /* For a child to execute the command on the remote host using ssh. */ 149 /* For a child to execute the command on the remote host using ssh. */
162 if (fork() == 0) { 150 do_cmd_pid = fork();
151 if (do_cmd_pid == 0) {
163 /* Child. */ 152 /* Child. */
164 close(pin[1]); 153 close(pin[1]);
165 close(pout[0]); 154 close(pout[0]);
@@ -177,6 +166,8 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc)
177 execvp(ssh_program, args.list); 166 execvp(ssh_program, args.list);
178 perror(ssh_program); 167 perror(ssh_program);
179 exit(1); 168 exit(1);
169 } else if (do_cmd_pid == -1) {
170 fatal("fork: %s", strerror(errno));
180 } 171 }
181 /* Parent. Close the other side, and return the local side. */ 172 /* Parent. Close the other side, and return the local side. */
182 close(pin[0]); 173 close(pin[0]);
@@ -219,8 +210,9 @@ main(argc, argv)
219 int argc; 210 int argc;
220 char *argv[]; 211 char *argv[];
221{ 212{
222 int ch, fflag, tflag; 213 int ch, fflag, tflag, status;
223 char *targ; 214 double speed;
215 char *targ, *endp;
224 extern char *optarg; 216 extern char *optarg;
225 extern int optind; 217 extern int optind;
226 218
@@ -233,7 +225,7 @@ main(argc, argv)
233 addargs(&args, "-oClearAllForwardings yes"); 225 addargs(&args, "-oClearAllForwardings yes");
234 226
235 fflag = tflag = 0; 227 fflag = tflag = 0;
236 while ((ch = getopt(argc, argv, "dfprtvBCc:i:P:q1246S:o:F:")) != -1) 228 while ((ch = getopt(argc, argv, "dfl:prtvBCc:i:P:q1246S:o:F:")) != -1)
237 switch (ch) { 229 switch (ch) {
238 /* User-visible flags. */ 230 /* User-visible flags. */
239 case '1': 231 case '1':
@@ -255,6 +247,12 @@ main(argc, argv)
255 case 'B': 247 case 'B':
256 addargs(&args, "-oBatchmode yes"); 248 addargs(&args, "-oBatchmode yes");
257 break; 249 break;
250 case 'l':
251 speed = strtod(optarg, &endp);
252 if (speed <= 0 || *endp != '\0')
253 usage();
254 limitbw = speed * 1024;
255 break;
258 case 'p': 256 case 'p':
259 pflag = 1; 257 pflag = 1;
260 break; 258 break;
@@ -319,6 +317,7 @@ main(argc, argv)
319 targetshouldbedirectory = 1; 317 targetshouldbedirectory = 1;
320 318
321 remin = remout = -1; 319 remin = remout = -1;
320 do_cmd_pid = -1;
322 /* Command to be executed on remote system using "ssh". */ 321 /* Command to be executed on remote system using "ssh". */
323 (void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s", 322 (void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s",
324 verbose_mode ? " -v" : "", 323 verbose_mode ? " -v" : "",
@@ -334,6 +333,22 @@ main(argc, argv)
334 if (targetshouldbedirectory) 333 if (targetshouldbedirectory)
335 verifydir(argv[argc - 1]); 334 verifydir(argv[argc - 1]);
336 } 335 }
336 /*
337 * Finally check the exit status of the ssh process, if one was forked
338 * and no error has occured yet
339 */
340 if (do_cmd_pid != -1 && errs == 0) {
341 if (remin != -1)
342 (void) close(remin);
343 if (remout != -1)
344 (void) close(remout);
345 if (waitpid(do_cmd_pid, &status, 0) == -1)
346 errs = 1;
347 else {
348 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
349 errs = 1;
350 }
351 }
337 exit(errs != 0); 352 exit(errs != 0);
338} 353}
339 354
@@ -349,14 +364,12 @@ toremote(targ, argc, argv)
349 if (*targ == 0) 364 if (*targ == 0)
350 targ = "."; 365 targ = ".";
351 366
352 if ((thost = strchr(argv[argc - 1], '@'))) { 367 if ((thost = strrchr(argv[argc - 1], '@'))) {
353 /* user@host */ 368 /* user@host */
354 *thost++ = 0; 369 *thost++ = 0;
355 tuser = argv[argc - 1]; 370 tuser = argv[argc - 1];
356 if (*tuser == '\0') 371 if (*tuser == '\0')
357 tuser = NULL; 372 tuser = NULL;
358 else if (!okname(tuser))
359 exit(1);
360 } else { 373 } else {
361 thost = argv[argc - 1]; 374 thost = argv[argc - 1];
362 tuser = NULL; 375 tuser = NULL;
@@ -370,7 +383,7 @@ toremote(targ, argc, argv)
370 *src++ = 0; 383 *src++ = 0;
371 if (*src == 0) 384 if (*src == 0)
372 src = "."; 385 src = ".";
373 host = strchr(argv[i], '@'); 386 host = strrchr(argv[i], '@');
374 len = strlen(ssh_program) + strlen(argv[i]) + 387 len = strlen(ssh_program) + strlen(argv[i]) +
375 strlen(src) + (tuser ? strlen(tuser) : 0) + 388 strlen(src) + (tuser ? strlen(tuser) : 0) +
376 strlen(thost) + strlen(targ) + 389 strlen(thost) + strlen(targ) +
@@ -382,8 +395,14 @@ toremote(targ, argc, argv)
382 suser = argv[i]; 395 suser = argv[i];
383 if (*suser == '\0') 396 if (*suser == '\0')
384 suser = pwd->pw_name; 397 suser = pwd->pw_name;
385 else if (!okname(suser)) 398 else if (!okname(suser)) {
399 xfree(bp);
386 continue; 400 continue;
401 }
402 if (tuser && !okname(tuser)) {
403 xfree(bp);
404 continue;
405 }
387 snprintf(bp, len, 406 snprintf(bp, len,
388 "%s%s %s -n " 407 "%s%s %s -n "
389 "-l %s %s %s %s '%s%s%s:%s'", 408 "-l %s %s %s %s '%s%s%s:%s'",
@@ -449,7 +468,7 @@ tolocal(argc, argv)
449 *src++ = 0; 468 *src++ = 0;
450 if (*src == 0) 469 if (*src == 0)
451 src = "."; 470 src = ".";
452 if ((host = strchr(argv[i], '@')) == NULL) { 471 if ((host = strrchr(argv[i], '@')) == NULL) {
453 host = argv[i]; 472 host = argv[i];
454 suser = NULL; 473 suser = NULL;
455 } else { 474 } else {
@@ -457,8 +476,6 @@ tolocal(argc, argv)
457 suser = argv[i]; 476 suser = argv[i];
458 if (*suser == '\0') 477 if (*suser == '\0')
459 suser = pwd->pw_name; 478 suser = pwd->pw_name;
460 else if (!okname(suser))
461 continue;
462 } 479 }
463 host = cleanhostname(host); 480 host = cleanhostname(host);
464 len = strlen(src) + CMDNEEDS + 20; 481 len = strlen(src) + CMDNEEDS + 20;
@@ -484,7 +501,7 @@ source(argc, argv)
484 struct stat stb; 501 struct stat stb;
485 static BUF buffer; 502 static BUF buffer;
486 BUF *bp; 503 BUF *bp;
487 off_t i, amt, result; 504 off_t i, amt, result, statbytes;
488 int fd, haderr, indx; 505 int fd, haderr, indx;
489 char *last, *name, buf[2048]; 506 char *last, *name, buf[2048];
490 int len; 507 int len;
@@ -549,7 +566,6 @@ syserr: run_err("%s: %s", name, strerror(errno));
549#endif 566#endif
550 if (verbose_mode) { 567 if (verbose_mode) {
551 fprintf(stderr, "Sending file modes: %s", buf); 568 fprintf(stderr, "Sending file modes: %s", buf);
552 fflush(stderr);
553 } 569 }
554 (void) atomicio(write, remout, buf, strlen(buf)); 570 (void) atomicio(write, remout, buf, strlen(buf));
555 if (response() < 0) 571 if (response() < 0)
@@ -558,10 +574,8 @@ syserr: run_err("%s: %s", name, strerror(errno));
558next: (void) close(fd); 574next: (void) close(fd);
559 continue; 575 continue;
560 } 576 }
561 if (showprogress) { 577 if (showprogress)
562 totalbytes = stb.st_size; 578 start_progress_meter(curfile, stb.st_size, &statbytes);
563 progressmeter(-1);
564 }
565 /* Keep writing after an error so that we stay sync'd up. */ 579 /* Keep writing after an error so that we stay sync'd up. */
566 for (haderr = i = 0; i < stb.st_size; i += bp->cnt) { 580 for (haderr = i = 0; i < stb.st_size; i += bp->cnt) {
567 amt = bp->cnt; 581 amt = bp->cnt;
@@ -580,9 +594,11 @@ next: (void) close(fd);
580 haderr = result >= 0 ? EIO : errno; 594 haderr = result >= 0 ? EIO : errno;
581 statbytes += result; 595 statbytes += result;
582 } 596 }
597 if (limitbw)
598 bwlimit(amt);
583 } 599 }
584 if (showprogress) 600 if (showprogress)
585 progressmeter(1); 601 stop_progress_meter();
586 602
587 if (close(fd) < 0 && !haderr) 603 if (close(fd) < 0 && !haderr)
588 haderr = errno; 604 haderr = errno;
@@ -650,6 +666,60 @@ rsource(name, statp)
650} 666}
651 667
652void 668void
669bwlimit(int amount)
670{
671 static struct timeval bwstart, bwend;
672 static int lamt, thresh = 16384;
673 u_int64_t wait;
674 struct timespec ts, rm;
675
676 if (!timerisset(&bwstart)) {
677 gettimeofday(&bwstart, NULL);
678 return;
679 }
680
681 lamt += amount;
682 if (lamt < thresh)
683 return;
684
685 gettimeofday(&bwend, NULL);
686 timersub(&bwend, &bwstart, &bwend);
687 if (!timerisset(&bwend))
688 return;
689
690 lamt *= 8;
691 wait = (double)1000000L * lamt / limitbw;
692
693 bwstart.tv_sec = wait / 1000000L;
694 bwstart.tv_usec = wait % 1000000L;
695
696 if (timercmp(&bwstart, &bwend, >)) {
697 timersub(&bwstart, &bwend, &bwend);
698
699 /* Adjust the wait time */
700 if (bwend.tv_sec) {
701 thresh /= 2;
702 if (thresh < 2048)
703 thresh = 2048;
704 } else if (bwend.tv_usec < 100) {
705 thresh *= 2;
706 if (thresh > 32768)
707 thresh = 32768;
708 }
709
710 TIMEVAL_TO_TIMESPEC(&bwend, &ts);
711 while (nanosleep(&ts, &rm) == -1) {
712 if (errno != EINTR)
713 break;
714 ts = rm;
715 }
716 }
717
718 lamt = 0;
719 gettimeofday(&bwstart, NULL);
720}
721
722void
653sink(argc, argv) 723sink(argc, argv)
654 int argc; 724 int argc;
655 char *argv[]; 725 char *argv[];
@@ -662,7 +732,7 @@ sink(argc, argv)
662 BUF *bp; 732 BUF *bp;
663 off_t i, j; 733 off_t i, j;
664 int amt, count, exists, first, mask, mode, ofd, omode; 734 int amt, count, exists, first, mask, mode, ofd, omode;
665 off_t size; 735 off_t size, statbytes;
666 int setimes, targisdir, wrerrno = 0; 736 int setimes, targisdir, wrerrno = 0;
667 char ch, *cp, *np, *targ, *why, *vect[1], buf[2048]; 737 char ch, *cp, *np, *targ, *why, *vect[1], buf[2048];
668 struct timeval tv[2]; 738 struct timeval tv[2];
@@ -824,11 +894,9 @@ bad: run_err("%s: %s", np, strerror(errno));
824 cp = bp->buf; 894 cp = bp->buf;
825 wrerr = NO; 895 wrerr = NO;
826 896
827 if (showprogress) {
828 totalbytes = size;
829 progressmeter(-1);
830 }
831 statbytes = 0; 897 statbytes = 0;
898 if (showprogress)
899 start_progress_meter(curfile, size, &statbytes);
832 for (count = i = 0; i < size; i += 4096) { 900 for (count = i = 0; i < size; i += 4096) {
833 amt = 4096; 901 amt = 4096;
834 if (i + amt > size) 902 if (i + amt > size)
@@ -848,6 +916,10 @@ bad: run_err("%s: %s", np, strerror(errno));
848 cp += j; 916 cp += j;
849 statbytes += j; 917 statbytes += j;
850 } while (amt > 0); 918 } while (amt > 0);
919
920 if (limitbw)
921 bwlimit(4096);
922
851 if (count == bp->cnt) { 923 if (count == bp->cnt) {
852 /* Keep reading so we stay sync'd up. */ 924 /* Keep reading so we stay sync'd up. */
853 if (wrerr == NO) { 925 if (wrerr == NO) {
@@ -862,13 +934,13 @@ bad: run_err("%s: %s", np, strerror(errno));
862 } 934 }
863 } 935 }
864 if (showprogress) 936 if (showprogress)
865 progressmeter(1); 937 stop_progress_meter();
866 if (count != 0 && wrerr == NO && 938 if (count != 0 && wrerr == NO &&
867 (j = atomicio(write, ofd, bp->buf, count)) != count) { 939 (j = atomicio(write, ofd, bp->buf, count)) != count) {
868 wrerr = YES; 940 wrerr = YES;
869 wrerrno = j >= 0 ? EIO : errno; 941 wrerrno = j >= 0 ? EIO : errno;
870 } 942 }
871 if (ftruncate(ofd, size)) { 943 if (wrerr == NO && ftruncate(ofd, size) != 0) {
872 run_err("%s: truncate: %s", np, strerror(errno)); 944 run_err("%s: truncate: %s", np, strerror(errno));
873 wrerr = DISPLAYED; 945 wrerr = DISPLAYED;
874 } 946 }
@@ -957,8 +1029,8 @@ void
957usage(void) 1029usage(void)
958{ 1030{
959 (void) fprintf(stderr, 1031 (void) fprintf(stderr,
960 "usage: scp [-pqrvBC46] [-F config] [-S program] [-P port]\n" 1032 "usage: scp [-pqrvBC1246] [-F config] [-S program] [-P port]\n"
961 " [-c cipher] [-i identity] [-o option]\n" 1033 " [-c cipher] [-i identity] [-l limit] [-o option]\n"
962 " [[user@]host1:]file1 [...] [[user@]host2:]file2\n"); 1034 " [[user@]host1:]file1 [...] [[user@]host2:]file2\n");
963 exit(1); 1035 exit(1);
964} 1036}
@@ -1015,9 +1087,18 @@ okname(cp0)
1015 c = (int)*cp; 1087 c = (int)*cp;
1016 if (c & 0200) 1088 if (c & 0200)
1017 goto bad; 1089 goto bad;
1018 if (!isalpha(c) && !isdigit(c) && 1090 if (!isalpha(c) && !isdigit(c)) {
1019 c != '_' && c != '-' && c != '.' && c != '+') 1091 switch (c) {
1020 goto bad; 1092 case '\'':
1093 case '"':
1094 case '`':
1095 case ' ':
1096 case '#':
1097 goto bad;
1098 default:
1099 break;
1100 }
1101 }
1021 } while (*++cp); 1102 } while (*++cp);
1022 return (1); 1103 return (1);
1023 1104
@@ -1038,11 +1119,9 @@ allocbuf(bp, fd, blksize)
1038 run_err("fstat: %s", strerror(errno)); 1119 run_err("fstat: %s", strerror(errno));
1039 return (0); 1120 return (0);
1040 } 1121 }
1041 if (stb.st_blksize == 0) 1122 size = roundup(stb.st_blksize, blksize);
1123 if (size == 0)
1042 size = blksize; 1124 size = blksize;
1043 else
1044 size = blksize + (stb.st_blksize - blksize % stb.st_blksize) %
1045 stb.st_blksize;
1046#else /* HAVE_STRUCT_STAT_ST_BLKSIZE */ 1125#else /* HAVE_STRUCT_STAT_ST_BLKSIZE */
1047 size = blksize; 1126 size = blksize;
1048#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */ 1127#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
@@ -1068,149 +1147,3 @@ lostconn(signo)
1068 else 1147 else
1069 exit(1); 1148 exit(1);
1070} 1149}
1071
1072static void
1073updateprogressmeter(int ignore)
1074{
1075 int save_errno = errno;
1076
1077 progressmeter(0);
1078 signal(SIGALRM, updateprogressmeter);
1079 alarm(PROGRESSTIME);
1080 errno = save_errno;
1081}
1082
1083static int
1084foregroundproc(void)
1085{
1086 static pid_t pgrp = -1;
1087 int ctty_pgrp;
1088
1089 if (pgrp == -1)
1090 pgrp = getpgrp();
1091
1092#ifdef HAVE_TCGETPGRP
1093 return ((ctty_pgrp = tcgetpgrp(STDOUT_FILENO)) != -1 &&
1094 ctty_pgrp == pgrp);
1095#else
1096 return ((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
1097 ctty_pgrp == pgrp));
1098#endif
1099}
1100
1101void
1102progressmeter(int flag)
1103{
1104 static const char prefixes[] = " KMGTP";
1105 static struct timeval lastupdate;
1106 static off_t lastsize;
1107 struct timeval now, td, wait;
1108 off_t cursize, abbrevsize;
1109 double elapsed;
1110 int ratio, barlength, i, remaining;
1111 char buf[512];
1112
1113 if (flag == -1) {
1114 (void) gettimeofday(&start, (struct timezone *) 0);
1115 lastupdate = start;
1116 lastsize = 0;
1117 }
1118 if (foregroundproc() == 0)
1119 return;
1120
1121 (void) gettimeofday(&now, (struct timezone *) 0);
1122 cursize = statbytes;
1123 if (totalbytes != 0) {
1124 ratio = 100.0 * cursize / totalbytes;
1125 ratio = MAX(ratio, 0);
1126 ratio = MIN(ratio, 100);
1127 } else
1128 ratio = 100;
1129
1130 snprintf(buf, sizeof(buf), "\r%-20.20s %3d%% ", curfile, ratio);
1131
1132 barlength = getttywidth() - 51;
1133 if (barlength > 0) {
1134 i = barlength * ratio / 100;
1135 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1136 "|%.*s%*s|", i,
1137 "*******************************************************"
1138 "*******************************************************"
1139 "*******************************************************"
1140 "*******************************************************"
1141 "*******************************************************"
1142 "*******************************************************"
1143 "*******************************************************",
1144 barlength - i, "");
1145 }
1146 i = 0;
1147 abbrevsize = cursize;
1148 while (abbrevsize >= 100000 && i < sizeof(prefixes)) {
1149 i++;
1150 abbrevsize >>= 10;
1151 }
1152 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %5lu %c%c ",
1153 (unsigned long) abbrevsize, prefixes[i],
1154 prefixes[i] == ' ' ? ' ' : 'B');
1155
1156 timersub(&now, &lastupdate, &wait);
1157 if (cursize > lastsize) {
1158 lastupdate = now;
1159 lastsize = cursize;
1160 if (wait.tv_sec >= STALLTIME) {
1161 start.tv_sec += wait.tv_sec;
1162 start.tv_usec += wait.tv_usec;
1163 }
1164 wait.tv_sec = 0;
1165 }
1166 timersub(&now, &start, &td);
1167 elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
1168
1169 if (flag != 1 &&
1170 (statbytes <= 0 || elapsed <= 0.0 || cursize > totalbytes)) {
1171 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1172 " --:-- ETA");
1173 } else if (wait.tv_sec >= STALLTIME) {
1174 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1175 " - stalled -");
1176 } else {
1177 if (flag != 1)
1178 remaining = (int)(totalbytes / (statbytes / elapsed) -
1179 elapsed);
1180 else
1181 remaining = elapsed;
1182
1183 i = remaining / 3600;
1184 if (i)
1185 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1186 "%2d:", i);
1187 else
1188 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1189 " ");
1190 i = remaining % 3600;
1191 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1192 "%02d:%02d%s", i / 60, i % 60,
1193 (flag != 1) ? " ETA" : " ");
1194 }
1195 atomicio(write, fileno(stdout), buf, strlen(buf));
1196
1197 if (flag == -1) {
1198 mysignal(SIGALRM, updateprogressmeter);
1199 alarm(PROGRESSTIME);
1200 } else if (flag == 1) {
1201 alarm(0);
1202 atomicio(write, fileno(stdout), "\n", 1);
1203 statbytes = 0;
1204 }
1205}
1206
1207int
1208getttywidth(void)
1209{
1210 struct winsize winsize;
1211
1212 if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1)
1213 return (winsize.ws_col ? winsize.ws_col : 80);
1214 else
1215 return (80);
1216}