1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright (c) 2020 Dmitry Kozlyuk
3  */
4 
5 #include <io.h>
6 
7 #include <rte_os.h>
8 
9 #include "cmdline_private.h"
10 
11 /* Missing from some MinGW-w64 distributions. */
12 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
13 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
14 #endif
15 
16 #ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
17 #define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
18 #endif
19 
20 void
terminal_adjust(struct cmdline * cl)21 terminal_adjust(struct cmdline *cl)
22 {
23 	HANDLE handle;
24 	DWORD mode;
25 
26 	ZeroMemory(&cl->oldterm, sizeof(cl->oldterm));
27 
28 	/* Detect console input, set it up and make it emulate VT100. */
29 	handle = GetStdHandle(STD_INPUT_HANDLE);
30 	if (GetConsoleMode(handle, &mode)) {
31 		cl->oldterm.is_console_input = 1;
32 		cl->oldterm.input_mode = mode;
33 
34 		mode &= ~(
35 			ENABLE_LINE_INPUT |      /* no line buffering */
36 			ENABLE_ECHO_INPUT |      /* no echo */
37 			ENABLE_PROCESSED_INPUT | /* pass Ctrl+C to program */
38 			ENABLE_MOUSE_INPUT |     /* no mouse events */
39 			ENABLE_WINDOW_INPUT);    /* no window resize events */
40 		mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
41 		SetConsoleMode(handle, mode);
42 	}
43 
44 	/* Detect console output and make it emulate VT100. */
45 	handle = GetStdHandle(STD_OUTPUT_HANDLE);
46 	if (GetConsoleMode(handle, &mode)) {
47 		cl->oldterm.is_console_output = 1;
48 		cl->oldterm.output_mode = mode;
49 
50 		mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
51 		mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
52 		SetConsoleMode(handle, mode);
53 	}
54 }
55 
56 void
terminal_restore(const struct cmdline * cl)57 terminal_restore(const struct cmdline *cl)
58 {
59 	if (cl->oldterm.is_console_input) {
60 		HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
61 		SetConsoleMode(handle, cl->oldterm.input_mode);
62 	}
63 
64 	if (cl->oldterm.is_console_output) {
65 		HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
66 		SetConsoleMode(handle, cl->oldterm.output_mode);
67 	}
68 }
69 
70 static int
cmdline_is_key_down(const INPUT_RECORD * record)71 cmdline_is_key_down(const INPUT_RECORD *record)
72 {
73 	return (record->EventType == KEY_EVENT) &&
74 		record->Event.KeyEvent.bKeyDown;
75 }
76 
77 static int
cmdline_poll_char_console(HANDLE handle)78 cmdline_poll_char_console(HANDLE handle)
79 {
80 	INPUT_RECORD record;
81 	DWORD events;
82 
83 	if (!PeekConsoleInput(handle, &record, 1, &events)) {
84 		/* Simulate poll(3) behavior on EOF. */
85 		return (GetLastError() == ERROR_HANDLE_EOF) ? 1 : -1;
86 	}
87 
88 	if ((events == 0) || !cmdline_is_key_down(&record))
89 		return 0;
90 
91 	return 1;
92 }
93 
94 static int
cmdline_poll_char_file(struct cmdline * cl,HANDLE handle)95 cmdline_poll_char_file(struct cmdline *cl, HANDLE handle)
96 {
97 	DWORD type = GetFileType(handle);
98 
99 	/* Since console is handled by cmdline_poll_char_console(),
100 	 * this is either a serial port or input handle had been replaced.
101 	 */
102 	if (type == FILE_TYPE_CHAR)
103 		return cmdline_poll_char_console(handle);
104 
105 	/* PeekNamedPipe() can handle all pipes and also sockets. */
106 	if (type == FILE_TYPE_PIPE) {
107 		DWORD bytes_avail;
108 		if (!PeekNamedPipe(handle, NULL, 0, NULL, &bytes_avail, NULL))
109 			return (GetLastError() == ERROR_BROKEN_PIPE) ? 1 : -1;
110 		return bytes_avail ? 1 : 0;
111 	}
112 
113 	/* There is no straightforward way to peek a file in Windows
114 	 * I/O model. Read the byte, if it is not the end of file,
115 	 * buffer it for subsequent read. This will not work with
116 	 * a file being appended and probably some other edge cases.
117 	 */
118 	if (type == FILE_TYPE_DISK) {
119 		char c;
120 		int ret;
121 
122 		ret = _read(cl->s_in, &c, sizeof(c));
123 		if (ret == 1) {
124 			cl->repeat_count = 1;
125 			cl->repeated_char = c;
126 		}
127 		return ret;
128 	}
129 
130 	/* GetFileType() failed or file of unknown type,
131 	 * which we do not know how to peek anyway.
132 	 */
133 	return -1;
134 }
135 
136 int
cmdline_poll_char(struct cmdline * cl)137 cmdline_poll_char(struct cmdline *cl)
138 {
139 	HANDLE handle = (HANDLE)_get_osfhandle(cl->s_in);
140 	return cl->oldterm.is_console_input ?
141 		cmdline_poll_char_console(handle) :
142 		cmdline_poll_char_file(cl, handle);
143 }
144 
145 ssize_t
cmdline_read_char(struct cmdline * cl,char * c)146 cmdline_read_char(struct cmdline *cl, char *c)
147 {
148 	HANDLE handle;
149 	INPUT_RECORD record;
150 	KEY_EVENT_RECORD *key;
151 	DWORD events;
152 
153 	if (!cl->oldterm.is_console_input)
154 		return _read(cl->s_in, c, 1);
155 
156 	/* Return repeated strokes from previous event. */
157 	if (cl->repeat_count > 0) {
158 		*c = cl->repeated_char;
159 		cl->repeat_count--;
160 		return 1;
161 	}
162 
163 	handle = (HANDLE)_get_osfhandle(cl->s_in);
164 	key = &record.Event.KeyEvent;
165 	do {
166 		if (!ReadConsoleInput(handle, &record, 1, &events)) {
167 			if (GetLastError() == ERROR_HANDLE_EOF) {
168 				*c = EOF;
169 				return 0;
170 			}
171 			return -1;
172 		}
173 	} while (!cmdline_is_key_down(&record));
174 
175 	*c = key->uChar.AsciiChar;
176 
177 	/* Save repeated strokes from a single event. */
178 	if (key->wRepeatCount > 1) {
179 		cl->repeated_char = *c;
180 		cl->repeat_count = key->wRepeatCount - 1;
181 	}
182 
183 	return 1;
184 }
185 
186 int
cmdline_vdprintf(int fd,const char * format,va_list op)187 cmdline_vdprintf(int fd, const char *format, va_list op)
188 {
189 	int copy, ret;
190 	FILE *file;
191 
192 	copy = _dup(fd);
193 	if (copy < 0)
194 		return -1;
195 
196 	file = _fdopen(copy, "a");
197 	if (file == NULL) {
198 		_close(copy);
199 		return -1;
200 	}
201 
202 	ret = vfprintf(file, format, op);
203 
204 	fclose(file); /* also closes copy */
205 
206 	return ret;
207 }
208