xref: /aosp_15_r20/external/toybox/toys/net/microcom.c (revision cf5a6c84e2b8763fc1a7db14496fd4742913b199)
1 /* microcom.c - Simple serial console.
2  *
3  * Copyright 2017 The Android Open Source Project.
4 
5 USE_MICROCOM(NEWTOY(microcom, "<1>1s#X", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NOBUF))
6 
7 config MICROCOM
8   bool "microcom"
9   default y
10   help
11     usage: microcom [-s SPEED] [-X] DEVICE
12 
13     Simple serial console. Hit CTRL-] for menu.
14 
15     -s	Set baud rate to SPEED
16     -X	Ignore ^] menu escape
17 */
18 
19 #define FOR_microcom
20 #include "toys.h"
21 
GLOBALS(long s;int fd,stok;struct termios old_stdin,old_fd;)22 GLOBALS(
23   long s;
24 
25   int fd, stok;
26   struct termios old_stdin, old_fd;
27 )
28 
29 // TODO: tty_sigreset outputs ansi escape sequences, how to disable?
30 static void restore_states(int i)
31 {
32   if (TT.stok) tcsetattr(0, TCSANOW, &TT.old_stdin);
33   tcsetattr(TT.fd, FLAG(s)*TCSAFLUSH, &TT.old_fd);
34 }
35 
36 // TODO bookmark, jump to bottom line (if !end), clear and return afterwards
handle_esc(void)37 static void handle_esc(void)
38 {
39   char input;
40 
41   xputsn("\r\n[b]reak, [p]aste file, [q]uit: ");
42   if (read(0, &input, 1)<1 || input == CTRL('D') || input == 'q') {
43     xputs("exit\r");
44     xexit();
45   }
46   if (input == 'b') tcsendbreak(TT.fd, 0);
47   else if (input == 'p') {
48     long long written = 0, size;
49     char* filename;
50     int len = 0, fd;
51 
52     // TODO: share code with hexedit's prompt() and vi's ex mode.
53     // TODO: tab completion!
54     memset(toybuf, 0, sizeof(toybuf));
55     while (1) {
56       xprintf("\r\e[2K\e[1mFilename: \e[0m%s", toybuf);
57       if (read(0, &input, 1) <= 0 || input == CTRL('[')) {
58         len = 0;
59         break;
60       }
61       if (input == '\r') break;
62       if (input == 0x7f && len > 0) toybuf[--len] = 0;
63       else if (input == CTRL('U')) while (len > 0) toybuf[--len] = 0;
64       else if (input >= ' ' && input <= 0x7f && len < sizeof(toybuf))
65         toybuf[len++] = input;
66     }
67     xputsn("\r\e[2K");
68     toybuf[len] = 0;
69     if (!len) return;
70     if ((fd = xopen(toybuf, O_RDONLY | WARN_ONLY)) < 0) {
71       // xopen() warning message ends with a LF without CR, so manually print a
72       // CR here to move the cursor back to the front.
73       fputc('\r', stderr);
74       return;
75     }
76     // TODO xsendfile with progress indicator
77     filename = xstrdup(toybuf);
78     size = fdlength(fd);
79     // The alternative would be to just feed this fd into the usual loop,
80     // so we're reading back these characters if they're being echoed, but
81     // for my specific use case of pasting into `base64 -d -i > foo`, this
82     // is a much more convenient UI.
83     while ((len = read(fd, toybuf, sizeof(toybuf))) > 0) {
84       written += len;
85       xprintf("\r\e[2KPasting '%s' %lld/%lld (%lld%%)...", filename, written,
86         size, written*100/size);
87       xwrite(TT.fd, toybuf, len);
88     }
89     free(filename);
90     close(fd);
91   } else xprintf("Ignoring unknown command.");
92 
93   xprintf("\r\n");
94 }
95 
microcom_main(void)96 void microcom_main(void)
97 {
98   struct termios tio;
99   struct pollfd fds[2];
100   int i;
101 
102   // Open with O_NDELAY, but switch back to blocking for reads.
103   TT.fd = xopen(*toys.optargs, O_RDWR | O_NOCTTY | O_NDELAY);
104   if (-1==(i = fcntl(TT.fd, F_GETFL, 0)) || fcntl(TT.fd, F_SETFL, i&~O_NDELAY)
105       || tcgetattr(TT.fd, &TT.old_fd))
106     perror_exit_raw(*toys.optargs);
107 
108   // Set both input and output to raw mode.
109   memcpy(&tio, &TT.old_fd, sizeof(struct termios));
110   cfmakeraw(&tio);
111   if (FLAG(s)) xsetspeed(&tio, TT.s);
112   if (tcsetattr(TT.fd, FLAG(s)*TCSAFLUSH, &tio)) perror_exit("set speed");
113   if (!set_terminal(0, 1, 0, &TT.old_stdin)) TT.stok++;
114   // ...and arrange to restore things, however we may exit.
115   sigatexit(restore_states);
116 
117   fds[0].fd = TT.fd;
118   fds[1].fd = 0;
119   fds[0].events = fds[1].events = POLLIN;
120 
121   if (!FLAG(X)) xputs("Escape character is '^]'.\r");
122   while (poll(fds, 2, -1) > 0) {
123 
124     // Read from connection, write to stdout.
125     if (fds[0].revents) {
126       if (0 < (i = read(TT.fd, toybuf, sizeof(toybuf)))) xwrite(0, toybuf, i);
127       else break;
128     }
129 
130     // Read from stdin, write to connection.
131     if (fds[1].revents) {
132       if (read(0, toybuf, 1) != 1) break;
133       if (!FLAG(X) && *toybuf == CTRL(']')) handle_esc();
134       else xwrite(TT.fd, toybuf, 1);
135     }
136   }
137 }
138