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