/* * utip -- a simple, tiny ``tip'' program (``u'' means ``micro'') * * revision history * 0.1: Feb. 10, 2003 by Dai ISHIJIMA (use select(2) system call) * 0.2: May 14, 2003 (as tinytip2) * 0.3: Oct. 31, 2003 (as utip, half-duplex, etc.) * 0.4: Jan. 13, 2009 (parity, bits, etc.) * 0.5: May 1, 2013 (for cron, ignore error from STDIN) * 0.6: Dec. 15, 2015 (force disconnect/quit after # sec) */ #include #include #include #include #include #include #include #include #include #include #define YES 1 #define NO 0 #define NEWLINE '\n' #define CR '\r' #define EOS '\0' #define TIP_ESCAPE 0x1d /* Ctrl - ] */ #define HEXDUMP 1 /* ダンプモード */ #define HALF 2 /* 半二重 (ローカルエコー) */ #define MAPCR 4 /* CR -> CR/LF */ #define IGN_ERROR 64 /* シリアルポートのエラーを無視 */ #define DUMPTIME 128 /* ダンプ時にタイムスタンプ */ #define IGN_STDIN_ERR 256 /* stdinのエラーを無視 */ #define BPS_DEFAULT 9600 #define MODEM_DEFAULT "/dev/cuaa0" #ifdef SLZAURUS #undef MODEM_DEFAULT #define MODEM_DEFAULT "/dev/modem" #undef TIP_ESCAPE #define TIP_ESCAPE 0x1b /* Ctrl - [, ESCAPE */ #endif #define SELECT_TIMEOUT 100000 /* 100,000 [us] -> 1/10 [sec] */ #ifndef O_NONBLOCK /* for Sony NEWS, NEWS-OS 4.x */ #include #endif #define FILEMODE 0777 #ifdef _POSIX_VDISABLE #define CC_UNDEF _POSIX_VDISABLE #else #define CC_UNDEF 0 #endif char *prog; #define shift --argc; ++argv static struct termios keyorg, keynew; /* キーボード (stdin) の状態を保持 */ static struct termios portorg, portnew; /* 読み書き用にオープンする */ int opentty(char *name) { int fd; if ((fd = open(name, O_RDWR | O_NONBLOCK, FILEMODE)) <= 0) { fprintf(stderr, "can't open %s\n", name); exit(1); } return(fd); } /* シリアル速度のチェック */ int speed(int sp) { if (sp == 1200) { return(B1200); } else if (sp == 2400) { return(B2400); } else if (sp == 4800) { return(B4800); } else if (sp == 9600) { return(B9600); } else if (sp == 19200) { return(B19200); } else if (sp == 38400) { return(B38400); } #ifdef B57600 else if (sp == 57600) { return(B57600); } #endif #ifdef B115200 else if (sp == 115200) { return(B115200); } #endif else { fprintf(stderr, "%d bps unavailable. 9600 bps selected.\n", sp); } return(B9600); } /* シリアルポートの初期化。速度の設定・RAWモードに */ void ttyinit(int fd, long sp, int cmode) { int i; sp = speed(sp); tcgetattr(fd, &portorg); memcpy(&portorg, &portnew, sizeof(struct termios)); portnew.c_iflag = IGNBRK | IGNPAR; /* ブレークとパリティエラーを無視 */ portnew.c_oflag = 0; portnew.c_cflag = cmode; portnew.c_lflag = 0; cfsetispeed(&portnew, sp); cfsetospeed(&portnew, sp); for (i = 0; i < NCCS; i++) { portnew.c_cc[i] = CC_UNDEF; } #ifndef VMIN_NULL portnew.c_cc[VMIN] = portorg.c_cc[VMIN]; portnew.c_cc[VTIME] = portorg.c_cc[VTIME]; #endif tcsetattr(fd, TCSANOW, &portnew); } /* シリアルポートの設定を元に戻す */ void ttyrestore(int fd) { tcsetattr(fd, TCSANOW, &portorg); } /* シリアルポートを閉じる */ int closetty(int fd) { ttyrestore(fd); return(close(fd)); } /* シリアルポートの制御線状態を表示する */ void showmodemstat(int fd) { int status; ioctl(fd, TIOCMGET, &status); fprintf(stderr, (status & TIOCM_LE) ? "ENABLE|" : "enable|"); fprintf(stderr, (status & TIOCM_DTR) ? "DTR|" : "dtr|"); fprintf(stderr, (status & TIOCM_RTS) ? "RTS|" : "rts|"); fprintf(stderr, (status & TIOCM_CTS) ? "CTS|" : "cts|"); fprintf(stderr, (status & TIOCM_CAR) ? "DCD|" : "dcd|"); fprintf(stderr, (status & TIOCM_RI) ? "RI|" : "ri|"); fprintf(stderr, (status & TIOCM_DSR) ? "DSR" : "dsr"); fprintf(stderr, "\n"); } /* キーボード (stdin) を「生」モード、エコーなしに */ void kbdinit() { if (isatty(fileno(stdin))) { tcgetattr(fileno(stdin), &keyorg); memcpy(&keynew, &keyorg, sizeof(struct termios)); cfmakeraw(&keynew); keynew.c_lflag &= ~ECHO; tcsetattr(fileno(stdin), TCSANOW, &keynew); } } /* キーボード (stdin) の設定を元に戻す */ void kbdrestore() { if (isatty(fileno(stdin))) { tcsetattr(fileno(stdin), TCSANOW, &keyorg); } } /* 16進文字か? */ int hexdigit(char ch) { /* ch に応じて 0〜15までの値を返す */ if (('0' <= ch) && (ch <= '9')) { return(ch - '0'); } else if (('A' <= ch) && (ch <= 'F')) { return(ch - 'A' + 10); } else if (('a' <= ch) && (ch <= 'f')) { return(ch - 'a' + 10); } return(-1); /* 16進文字じゃないときは -1 を返す */ } /* 16進文字列をバイナリ化する。「41 54 5a 0d 0a」→「ATZ\r\n」 */ int hexconv(char *to, char *from) { int m, n; int x; m = n = 0; while (from[m] != EOS) { while ((from[m] != EOS) && ((x = hexdigit(from[m])) < 0)) { ++m; } if ((from[m] != EOS) && ((x = hexdigit(from[m])) >= 0)) { to[n] = x; ++m; } if ((from[m] != EOS) && ((x = hexdigit(from[m])) >= 0)) { to[n] = to[n] << 4 | x; ++m; } if (from[m] == EOS) { break; } ++n; } for (m = 0; m < n; m++) { from[m] = to[m]; } from[m] = EOS; #ifdef EBUG for (m = 0; m < n; m++) { fprintf(stderr, "%02x ", from[m]); } fprintf(stderr, "\n"); #endif return(m); } /* 送信文字列のエスケープ処理 */ int escapeconv(char *to, char *from) { int m, n; m = n = 0; while (from[m] == 's') { ++m; } while ((from[m] == ' ') || (from[m] == '\t')) { ++m; } while (from[m] != EOS) { if (from[m] == '\\') { /* エスケープ文字か? */ ++m; if (from[m] == '\\') { to[n] = '\\'; ++m; } else if (from[m] == 'r') { to[n] = '\r'; ++m; } else if (from[m] == 'n') { to[n] = '\n'; ++m; } else if (from[m] == 't') { to[n] = '\t'; ++m; } else { to[n] = from[m]; ++m; } } else { to[n] = from[m]; ++m; } ++n; } to[n] = EOS; #ifdef EBUG for (m = 0; m < n; m++) { fprintf(stderr, "%02x ", from[m]); } fprintf(stderr, "\n"); #endif return(n); } /* エスケープ文字入力時の処理 */ int tip_escape(char *buf, int maxbuf, int fd, int *mode, long usec) { char s[BUFSIZ]; int n; int nfds; fd_set readfds; struct timeval timeout; /* 連続して入力があるかどうか調べる */ FD_ZERO(&readfds); FD_SET(fileno(stdin), &readfds); timeout.tv_sec = 0; timeout.tv_usec = usec; nfds = select(fileno(stdin) + 1, &readfds, NULL, NULL, &timeout); if ((nfds > 0) && (FD_ISSET(fileno(stdin), &readfds))) { /* 連続入力あり */ n = read(fileno(stdin), buf, 1); if ((!(*mode & IGN_STDIN_ERR)) && (n <= 0)) { fprintf(stderr, "%s: stdin/esc EOF (%d) encountered\r\n", prog, n); } } else { /* プロンプトを出力してコマンド入力待ち */ kbdrestore(); fprintf(stderr, "%s> ", prog); n = 0; if (fgets(s, BUFSIZ, stdin) ==NULL) { fprintf(stderr, "\n%s: EOF encountered\n", prog); n = EOF; } else if (s[0] == 'q') { /* q は quit */ n = EOF; } else if (s[0] == '?') { /* ? はヘルプ */ fputs("q: quit\n", stderr); fputs("v: show modem status\n", stderr); fputs("d: toggle hexdump mode\n", stderr); fputs("h: toggle half-duplex mode\n", stderr); fputs("r: toggle map CR->CR/LF mode\n", stderr); fputs("x [string]: send hex data, ", stderr); fputs("'x 41 54 5a 0d 0a' is equivalent to 'ATZ\\r\\n'\n", stderr); n = 0; } else if (s[0] == 'v') { /* モデムの状態 */ showmodemstat(fd); n = 0; } else if (s[0] == 'd') { /* ダンプモードトグル */ *mode ^= (HEXDUMP | DUMPTIME); n = 0; } else if (s[0] == 'h') { /* 半二重モードトグル */ *mode ^= HALF; n = 0; } else if (s[0] == 'r') { /* MAP CR->CR/LFモードトグル */ *mode ^= MAPCR; n = 0; } else if (s[0] == 'x') { /* 16進文字列 */ n = hexconv(buf, s); } else if (s[0] == 's') { /* 文字列送信 */ n = escapeconv(buf, s); } else if (s[0] == 'n') { /* 単なるコメント。何もしない */ n = 0; } kbdinit(); } return(n); } /* CR->CR/LFマップ処理 */ int mapcr(int n, char *buf) { static char lastch = EOS; int m; int i; char convbuf[BUFSIZ]; if ((lastch == CR) && (n == 0)) { lastch = EOS; buf[0] = NEWLINE; return(1); } m = 0; for (i = 0; (i < n) && (m < BUFSIZ); i++) { if ((lastch == CR) && (buf[i] != NEWLINE) && (m < BUFSIZ)) { convbuf[m] = NEWLINE; ++m; } convbuf[m] = buf[i]; lastch = buf[i]; ++m; } for (i = 0; i < m; i++) { buf[i] = convbuf[i]; } return(m); } /* CR->CR/LFマップ処理 */ int mapcr_in(int n, char *buf) { int m; int i; char convbuf[BUFSIZ]; m = 0; for (i = 0; (i < n) && (m < BUFSIZ); i++) { convbuf[m] = buf[i]; ++m; if ((m < BUFSIZ) && (buf[i] == CR)) { if ((n <= i + 1) || (buf[i + 1] != NEWLINE)) { convbuf[m] = NEWLINE; ++m; } } } for (i = 0; i < m; i++) { buf[i] = convbuf[i]; } return(m); } /* シリアル入出力のメイン部分 */ void communicate(int fd, int mode, long usec, char escape, int quitsec, int idlesec) { int nfds; fd_set readfds; struct timeval timeout; struct timeval current; struct timeval tmstart; struct timeval tmidle; char buf[BUFSIZ]; int n, i; int dumped; dumped = NO; gettimeofday(&tmstart, NULL); gettimeofday(&tmidle, NULL); for (;;) { FD_ZERO(&readfds); FD_SET(fileno(stdin), &readfds); FD_SET(fd, &readfds); timeout.tv_sec = 0; timeout.tv_usec = usec; nfds = select(fd + 1, &readfds, NULL, NULL, &timeout); if ((nfds < 0) && (errno != EINTR)) { fprintf(stderr, "%s: select failed\n", prog); break; } if ((nfds > 0) && (FD_ISSET(fileno(stdin), &readfds))) { /* キーボード入力あり */ buf[0] = EOS; n = read(fileno(stdin), buf, 1); if ((!(mode & IGN_STDIN_ERR)) && (n <= 0)) { fprintf(stderr, "%s: stdin EOF (%d) encountered\r\n", prog, n); break; } else { if (buf[0] == escape) { if ((n = tip_escape(buf, BUFSIZ, fd, &mode, usec)) < 0) { break; } } write(fd, buf, n); gettimeofday(&tmidle, NULL); if (mode & HALF) { if (mode & MAPCR) { n = mapcr_in(n, buf); } write(fileno(stdin), buf, n); } } } if ((nfds > 0) && (FD_ISSET(fd, &readfds))) { /* シリアルポート入力あり */ n = read(fd, buf, BUFSIZ); if ((!(mode & IGN_ERROR)) && (n <= 0)) { fprintf(stderr, "%s: silo EOF (%d) encountered\r\n", prog, n); break; } else { if ((n > 0) && (mode & DUMPTIME)) { gettimeofday(¤t, NULL); fprintf(stderr, "\r\n%8ld.%06ld: ", current.tv_sec, current.tv_usec); } if (mode & HEXDUMP) { for (i = 0; i < n; i++) { printf("%02x ", buf[i]); dumped = YES; gettimeofday(&tmidle, NULL); } } else { if (mode & MAPCR) { n = mapcr_in(n, buf); } write(fileno(stdout), buf, n); gettimeofday(&tmidle, NULL); } } } if ((nfds == 0) && (dumped == YES)) { fprintf(stderr, "\r\n"); fflush(stderr); dumped = NO; } fflush(stdout); gettimeofday(¤t, NULL); if ((quitsec > 0) && ((current.tv_sec - tmstart.tv_sec) > quitsec)) { /* 入出力に関わらず、一定時間が過ぎたら終了 */ break; } if ((idlesec > 0) && ((current.tv_sec - tmidle.tv_sec) > idlesec)) { /* 一定時間入出力がなかったら終了 */ break; } } } /* 使い方の簡易説明 */ void usage() { fprintf(stderr, "Usage: %s [option...] modem\n", prog); fprintf(stderr, " options:\n"); fprintf(stderr, "\t-#: set serial speed [bps]\n"); fprintf(stderr, "\t-d[#]: toggle/set/reset hexdump display mode\n"); fprintf(stderr, "\t-h[#]: toggle/set/reset half-duplex mode\n"); fprintf(stderr, "\t-p[#]: toggle/set/reset parity mode\n"); fprintf(stderr, "\t-b[#]: toggle/set/reset chararcter bits\n"); fprintf(stderr, "\t-T[#]: toggle/set/reset print timestamp on dump\n"); fprintf(stderr, "\t-E[#]: toggle/set/reset ignore comm port error\n"); fprintf(stderr, "\t-I[#]: toggle/set/reset ignore stdin error\n"); fprintf(stderr, "\t-i #: set idle timeout to # seconds\n"); fprintf(stderr, "\t-Q #: quit after # seconds\n"); fprintf(stderr, "\t-t #: set display/keyboard flush timeout [us]\n"); fprintf(stderr, "\t-eX: set escape charactor to ctrl-X\n"); fprintf(stderr, " default:\n"); fprintf(stderr, "\t%s -%d -d0 -h0 -r0 -pn -b8 -I0", prog, BPS_DEFAULT); fprintf(stderr, "-i 0 -Q 0 -t %d ", SELECT_TIMEOUT); fprintf(stderr, "-e'%c' %s\n", TIP_ESCAPE + '@', MODEM_DEFAULT); } /* メイン */ int main(int argc, char *argv[]) { long speed; char *modem; int fd; int mode; int cmode; long timeout; char escape; int quitsec; int idlesec; prog = *argv; shift; speed = BPS_DEFAULT; modem = MODEM_DEFAULT; mode = 0; escape = TIP_ESCAPE; quitsec = idlesec = 0; timeout = SELECT_TIMEOUT; cmode = CREAD | CLOCAL | CS8; while ((argc > 0) && (argv[0][0] == '-')) { if (('0' <= argv[0][1]) && (argv[0][1] <= '9')) { speed = -atoi(*argv); /* 数字 -> bps指定 */ } else if (argv[0][1] == 'd') { /* ダンプモード */ if ((argv[0][2] != EOS) && ((argv[0][2] & 1) == 0)) { mode &= ~HEXDUMP; } else if ((argv[0][2] != EOS) && ((argv[0][2] & 1) != 0)) { mode |= HEXDUMP; } else { mode ^= HEXDUMP; } } else if (argv[0][1] == 'T') { /* ダンプ時タイムスタンプ */ if ((argv[0][2] != EOS) && ((argv[0][2] & 1) == 0)) { mode &= ~DUMPTIME; } else if ((argv[0][2] != EOS) && ((argv[0][2] & 1) != 0)) { mode |= DUMPTIME; } else { mode ^= DUMPTIME; } } else if (argv[0][1] == 'h') { /* 半二重 */ if ((argv[0][2] != EOS) && ((argv[0][2] & 1) == 0)) { mode &= ~HALF; } else if ((argv[0][2] != EOS) && ((argv[0][2] & 1) != 0)) { mode |= HALF; } else if (argv[0][2] == '0') { mode ^= HALF; } } else if (argv[0][1] == 'b') { /* ビット長 */ if (argv[0][2] == EOS) { cmode &= ~CSIZE; cmode |= CS8; } else if (argv[0][2] == '7') { cmode &= ~CSIZE; cmode |= CS7; } else if (argv[0][2] == '8') { cmode &= ~CSIZE; cmode |= CS8; } else { usage(); exit(1); } } else if (argv[0][1] == 'p') { /* パリティ */ if (argv[0][2] == EOS) { cmode ^= PARENB; } else if (argv[0][2] == 'n') { cmode &= ~PARENB; cmode &= ~PARODD; } else if (argv[0][2] == 'e') { cmode |= PARENB; cmode &= ~PARODD; } else if (argv[0][2] == 'o') { cmode |= PARENB; cmode |= PARODD; } else { usage(); exit(1); } } else if (argv[0][1] == 'r') { /* CRLFマップ */ if ((argv[0][2] != EOS) && ((argv[0][2] & 1) == 0)) { mode &= ~MAPCR; } else if ((argv[0][2] != EOS) && ((argv[0][2] & 1) != 0)) { mode |= MAPCR; } else { mode ^= MAPCR; } } else if ((argv[0][1] == 't') && (argc > 1)) { /* タイムアウト */ shift; timeout = atoi(*argv); } else if ((argv[0][1] == 'i') && (argc > 1)) { /* アイドルタイマー */ shift; idlesec = atoi(*argv); } else if ((argv[0][1] == 'Q') && (argc > 1)) { /* 強制終了 */ shift; quitsec = atoi(*argv); } else if ((argv[0][1] == 'e') && (argv[0][2] != EOS)) { escape = argv[0][2] & 0x1f; /* エスケープ文字の指定 */ } else if (argv[0][1] == 'E') { /* シリアルポートのエラー無視 */ if ((argv[0][2] != EOS) && ((argv[0][2] & 1) == 0)) { mode &= ~IGN_ERROR; } else if ((argv[0][2] != EOS) && ((argv[0][2] & 1) != 0)) { mode |= IGN_ERROR; } else { mode ^= IGN_ERROR; usage(); exit(1); } } else if (argv[0][1] == 'I') { /* 標準入力のエラー無視 */ if ((argv[0][2] != EOS) && ((argv[0][2] & 1) == 0)) { mode &= ~IGN_STDIN_ERR; } else if ((argv[0][2] != EOS) && ((argv[0][2] & 1) != 0)) { mode |= IGN_STDIN_ERR; } else { mode ^= IGN_STDIN_ERR; } } else { usage(); exit(1); } shift; } if (argc > 0) { modem = *argv; shift; } fd = opentty(modem); /* シリアルポートを開く */ ttyinit(fd, speed, cmode); /* シリアルポートを初期化 */ kbdinit(); /* キーボードを初期化 */ communicate(fd, mode, timeout, escape, quitsec, idlesec); kbdrestore(); /* キーボードを元に戻す */ closetty(fd); /* シリアルポートを閉じる */ exit(0); } /* Local Variables: */ /* compile-command:"cc -Wall -o utip utip.c" */ /* End: */