cmdline: support Windows
authorDmitry Kozlyuk <dmitry.kozliuk@gmail.com>
Mon, 28 Sep 2020 21:50:51 +0000 (00:50 +0300)
committerThomas Monjalon <thomas@monjalon.net>
Wed, 14 Oct 2020 22:39:10 +0000 (00:39 +0200)
Implement terminal handling, input polling, and vdprintf() for Windows.

Because Windows I/O model differs fundamentally from Unix and there is
no concept of character device, polling is simulated depending on the
underlying input device. Supporting non-terminal input is useful for
automated testing.

Windows emulation of VT100 uses "ESC [ E" for newline instead of
standard "ESC E", so add a workaround.

Signed-off-by: Dmitry Kozlyuk <dmitry.kozliuk@gmail.com>
Acked-by: Olivier Matz <olivier.matz@6wind.com>
config/meson.build
lib/librte_cmdline/cmdline.c
lib/librte_cmdline/cmdline_os_windows.c [new file with mode: 0644]
lib/librte_cmdline/cmdline_parse.c
lib/librte_cmdline/cmdline_private.h
lib/librte_cmdline/cmdline_socket.c
lib/librte_cmdline/cmdline_vt100.h
lib/librte_cmdline/meson.build
lib/meson.build

index d0e5932..e2ea9f1 100644 (file)
@@ -275,6 +275,8 @@ if is_windows
                add_project_arguments('-D__USE_MINGW_ANSI_STDIO', language: 'c')
        endif
 
+       add_project_link_arguments('-lws2_32', language: 'c')
+
        # Contrary to docs, VirtualAlloc2() is exported by mincore.lib
        # in Windows SDK, while MinGW exports it by advapi32.a.
        if is_ms_linker
index 4977086..79ea5f9 100644 (file)
 
 #include "cmdline_private.h"
 
+#ifdef RTE_EXEC_ENV_WINDOWS
+#define write _write
+#endif
+
 static void
 cmdline_valid_buffer(struct rdline *rdl, const char *buf,
                     __rte_unused unsigned int size)
diff --git a/lib/librte_cmdline/cmdline_os_windows.c b/lib/librte_cmdline/cmdline_os_windows.c
new file mode 100644 (file)
index 0000000..e9585c9
--- /dev/null
@@ -0,0 +1,207 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2020 Dmitry Kozlyuk
+ */
+
+#include <io.h>
+
+#include <rte_os.h>
+
+#include "cmdline_private.h"
+
+/* Missing from some MinGW-w64 distributions. */
+#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
+#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
+#endif
+
+#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
+#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
+#endif
+
+void
+terminal_adjust(struct cmdline *cl)
+{
+       HANDLE handle;
+       DWORD mode;
+
+       ZeroMemory(&cl->oldterm, sizeof(cl->oldterm));
+
+       /* Detect console input, set it up and make it emulate VT100. */
+       handle = GetStdHandle(STD_INPUT_HANDLE);
+       if (GetConsoleMode(handle, &mode)) {
+               cl->oldterm.is_console_input = 1;
+               cl->oldterm.input_mode = mode;
+
+               mode &= ~(
+                       ENABLE_LINE_INPUT |      /* no line buffering */
+                       ENABLE_ECHO_INPUT |      /* no echo */
+                       ENABLE_PROCESSED_INPUT | /* pass Ctrl+C to program */
+                       ENABLE_MOUSE_INPUT |     /* no mouse events */
+                       ENABLE_WINDOW_INPUT);    /* no window resize events */
+               mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
+               SetConsoleMode(handle, mode);
+       }
+
+       /* Detect console output and make it emulate VT100. */
+       handle = GetStdHandle(STD_OUTPUT_HANDLE);
+       if (GetConsoleMode(handle, &mode)) {
+               cl->oldterm.is_console_output = 1;
+               cl->oldterm.output_mode = mode;
+
+               mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
+               mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+               SetConsoleMode(handle, mode);
+       }
+}
+
+void
+terminal_restore(const struct cmdline *cl)
+{
+       if (cl->oldterm.is_console_input) {
+               HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
+               SetConsoleMode(handle, cl->oldterm.input_mode);
+       }
+
+       if (cl->oldterm.is_console_output) {
+               HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
+               SetConsoleMode(handle, cl->oldterm.output_mode);
+       }
+}
+
+static int
+cmdline_is_key_down(const INPUT_RECORD *record)
+{
+       return (record->EventType == KEY_EVENT) &&
+               record->Event.KeyEvent.bKeyDown;
+}
+
+static int
+cmdline_poll_char_console(HANDLE handle)
+{
+       INPUT_RECORD record;
+       DWORD events;
+
+       if (!PeekConsoleInput(handle, &record, 1, &events)) {
+               /* Simulate poll(3) behavior on EOF. */
+               return (GetLastError() == ERROR_HANDLE_EOF) ? 1 : -1;
+       }
+
+       if ((events == 0) || !cmdline_is_key_down(&record))
+               return 0;
+
+       return 1;
+}
+
+static int
+cmdline_poll_char_file(struct cmdline *cl, HANDLE handle)
+{
+       DWORD type = GetFileType(handle);
+
+       /* Since console is handled by cmdline_poll_char_console(),
+        * this is either a serial port or input handle had been replaced.
+        */
+       if (type == FILE_TYPE_CHAR)
+               return cmdline_poll_char_console(handle);
+
+       /* PeekNamedPipe() can handle all pipes and also sockets. */
+       if (type == FILE_TYPE_PIPE) {
+               DWORD bytes_avail;
+               if (!PeekNamedPipe(handle, NULL, 0, NULL, &bytes_avail, NULL))
+                       return (GetLastError() == ERROR_BROKEN_PIPE) ? 1 : -1;
+               return bytes_avail ? 1 : 0;
+       }
+
+       /* There is no straightforward way to peek a file in Windows
+        * I/O model. Read the byte, if it is not the end of file,
+        * buffer it for subsequent read. This will not work with
+        * a file being appended and probably some other edge cases.
+        */
+       if (type == FILE_TYPE_DISK) {
+               char c;
+               int ret;
+
+               ret = _read(cl->s_in, &c, sizeof(c));
+               if (ret == 1) {
+                       cl->repeat_count = 1;
+                       cl->repeated_char = c;
+               }
+               return ret;
+       }
+
+       /* GetFileType() failed or file of unknown type,
+        * which we do not know how to peek anyway.
+        */
+       return -1;
+}
+
+int
+cmdline_poll_char(struct cmdline *cl)
+{
+       HANDLE handle = (HANDLE)_get_osfhandle(cl->s_in);
+       return cl->oldterm.is_console_input ?
+               cmdline_poll_char_console(handle) :
+               cmdline_poll_char_file(cl, handle);
+}
+
+ssize_t
+cmdline_read_char(struct cmdline *cl, char *c)
+{
+       HANDLE handle;
+       INPUT_RECORD record;
+       KEY_EVENT_RECORD *key;
+       DWORD events;
+
+       if (!cl->oldterm.is_console_input)
+               return _read(cl->s_in, c, 1);
+
+       /* Return repeated strokes from previous event. */
+       if (cl->repeat_count > 0) {
+               *c = cl->repeated_char;
+               cl->repeat_count--;
+               return 1;
+       }
+
+       handle = (HANDLE)_get_osfhandle(cl->s_in);
+       key = &record.Event.KeyEvent;
+       do {
+               if (!ReadConsoleInput(handle, &record, 1, &events)) {
+                       if (GetLastError() == ERROR_HANDLE_EOF) {
+                               *c = EOF;
+                               return 0;
+                       }
+                       return -1;
+               }
+       } while (!cmdline_is_key_down(&record));
+
+       *c = key->uChar.AsciiChar;
+
+       /* Save repeated strokes from a single event. */
+       if (key->wRepeatCount > 1) {
+               cl->repeated_char = *c;
+               cl->repeat_count = key->wRepeatCount - 1;
+       }
+
+       return 1;
+}
+
+int
+cmdline_vdprintf(int fd, const char *format, va_list op)
+{
+       int copy, ret;
+       FILE *file;
+
+       copy = _dup(fd);
+       if (copy < 0)
+               return -1;
+
+       file = _fdopen(copy, "a");
+       if (file == NULL) {
+               _close(copy);
+               return -1;
+       }
+
+       ret = vfprintf(file, format, op);
+
+       fclose(file); /* also closes copy */
+
+       return ret;
+}
index f120f19..fe36684 100644 (file)
@@ -15,7 +15,7 @@
 
 #include <rte_string_fns.h>
 
-#include "cmdline.h"
+#include "cmdline_private.h"
 
 #ifdef RTE_LIBRTE_CMDLINE_DEBUG
 #define debug_printf printf
index 4d9ea33..a8a6ee9 100644 (file)
@@ -8,9 +8,32 @@
 #include <stdarg.h>
 
 #include <rte_common.h>
+#ifdef RTE_EXEC_ENV_WINDOWS
+#include <rte_windows.h>
+#endif
 
 #include <cmdline.h>
 
+#ifdef RTE_EXEC_ENV_WINDOWS
+struct terminal {
+       DWORD input_mode;
+       DWORD output_mode;
+       int is_console_input;
+       int is_console_output;
+};
+
+struct cmdline {
+       int s_in;
+       int s_out;
+       cmdline_parse_ctx_t *ctx;
+       struct rdline rdl;
+       char prompt[RDLINE_PROMPT_SIZE];
+       struct terminal oldterm;
+       char repeated_char;
+       WORD repeat_count;
+};
+#endif
+
 /* Disable buffering and echoing, save previous settings to oldterm. */
 void terminal_adjust(struct cmdline *cl);
 
index 998e8ad..0fe1497 100644 (file)
 #include "cmdline_private.h"
 #include "cmdline_socket.h"
 
+#ifdef RTE_EXEC_ENV_WINDOWS
+#define open _open
+#endif
+
 struct cmdline *
 cmdline_file_new(cmdline_parse_ctx_t *ctx, const char *prompt, const char *path)
 {
index e33e67e..be9ae8e 100644 (file)
@@ -31,7 +31,11 @@ extern "C" {
 #define vt100_multi_right  "\033\133%uC"
 #define vt100_multi_left   "\033\133%uD"
 #define vt100_suppr        "\033\133\063\176"
+#ifndef RTE_EXEC_ENV_WINDOWS
 #define vt100_home         "\033M\033E"
+#else
+#define vt100_home         "\033M\033[E"
+#endif
 #define vt100_word_left    "\033\142"
 #define vt100_word_right   "\033\146"
 
index 5c9e888..5009b33 100644 (file)
@@ -25,7 +25,9 @@ headers = files('cmdline.h',
        'cmdline_cirbuf.h',
        'cmdline_parse_portlist.h')
 
-if not is_windows
+if is_windows
+       sources += files('cmdline_os_windows.c')
+else
        sources += files('cmdline_os_unix.c')
 endif
 
index e5597f1..40a8a82 100644 (file)
@@ -42,6 +42,7 @@ if is_windows
                'eal',
                'ring',
                'mempool', 'mbuf', 'net', 'meter', 'ethdev', 'pci',
+               'cmdline',
                'hash',
        ] # only supported libraries for windows
 endif