From 965369c3bedf74f89851a1f54650107698ae72dc Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Wed, 8 Nov 2023 23:04:00 -0300 Subject: [PATCH] feat: add file mapping and terminal support for Windows --- src/os.cpp | 144 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 130 insertions(+), 14 deletions(-) diff --git a/src/os.cpp b/src/os.cpp index 729d15f15..3e636efdb 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -14,7 +14,7 @@ // with this program (see COPYING). If not, see . // -#if !defined(_WIN32) && !defined(NO_TTY) +#if !defined(NO_TTY) #define HAVE_TTY #endif @@ -35,12 +35,12 @@ #include "os.h" #include "unique-c-ptr.h" -#if defined(HAVE_TTY) || defined(HAVE_MMAP) || defined(HAVE_TERMIOS) +#if defined(HAVE_TTY) || defined(HAVE_MMAP) || defined(HAVE_TERMIOS) || defined(_WIN32) #include // open #include // write/read/close #endif -#ifdef HAVE_TTY +#if defined(HAVE_TTY) && !defined(_WIN32) #include // select #endif @@ -54,11 +54,11 @@ #ifdef _WIN32 #include // mkdir -#define mkdir(a, b) _mkdir(a) -#else -#include // fstat/mkdir +#include #endif +#include // fstat + namespace cartesi { using namespace std::string_literals; @@ -73,6 +73,9 @@ struct tty_state { #ifdef HAVE_TERMIOS int ttyfd{-1}; termios oldtty{}; +#elif defined(_WIN32) + HANDLE hStdin{}; + DWORD dwOldStdinMode{}; #endif }; @@ -115,6 +118,7 @@ void os_open_tty(void) { #ifdef HAVE_TTY auto *s = get_state(); s->initialized = true; + #ifdef HAVE_TERMIOS if (s->ttyfd >= 0) { // Already open return; @@ -157,21 +161,44 @@ void os_open_tty(void) { return; } s->ttyfd = ttyfd; +#elif defined(_WIN32) + // Get stdin handle + s->hStdin = GetStdHandle(STD_INPUT_HANDLE); + if (!s->hStdin) { + return; + } + // Set console in raw mode + if (GetConsoleMode(s->hStdin, &s->dwOldStdinMode)) { + DWORD dwMode = s->dwOldStdinMode; + dwMode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); + dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; + SetConsoleMode(s->hStdin, dwMode); + } #endif // HAVE_TERMIOS -#else // HAVE_TTY + +#else throw std::runtime_error("unable to open console input, stdin is unsupported in this platform"); #endif // HAVE_TTY } void os_close_tty(void) { -#ifdef HAVE_TERMIOS +#ifdef HAVE_TTY auto *s = get_state(); + +#ifdef HAVE_TERMIOS if (s->ttyfd >= 0) { // Restore old mode tcsetattr(s->ttyfd, TCSANOW, &s->oldtty); close(s->ttyfd); s->ttyfd = 1; } +#elif defined(_WIN32) + if (s->hStdin) { + SetConsoleMode(s->hStdin, s->dwOldStdinMode); + s->hStdin = NULL; + } #endif // HAVE_TERMIOS + +#endif // HAVE_TTY } void os_poll_tty(uint64_t wait) { @@ -185,6 +212,38 @@ void os_poll_tty(uint64_t wait) { return; } +#ifdef _WIN32 + s->buf_len = -1; + if (s->hStdin) { + // Wait for an input event + const uint64_t wait_millis = (wait + 999) / 1000; + if (WaitForSingleObject(s->hStdin, wait_millis) != WAIT_OBJECT_0) { + // No input events + return; + } + // Consume input events until buffer is full or the event list is empty + INPUT_RECORD inputRecord{}; + DWORD numberOfEventsRead = 0; + while (PeekConsoleInput(s->hStdin, &inputRecord, 1, &numberOfEventsRead)) { + if (numberOfEventsRead == 0) { + // Nothing to read + return; + } else if (inputRecord.EventType == KEY_EVENT && inputRecord.Event.KeyEvent.bKeyDown) { + // Key was pressed + DWORD numberOfCharsRead = 0; + // We must read input buffer through ReadConsole() to read raw terminal input + if (ReadConsole(s->hStdin, s->buf.data(), s->buf.size(), &numberOfCharsRead, NULL)) { + s->buf_len = static_cast(numberOfCharsRead); + } + break; + } else { + // Consume input event + ReadConsoleInput(s->hStdin, &inputRecord, 1, &numberOfEventsRead); + } + } + } + +#else const int fd_max{0}; fd_set rfds{}; timeval tv{}; @@ -197,6 +256,8 @@ void os_poll_tty(uint64_t wait) { } s->buf_len = static_cast(read(STDIN_FILENO, s->buf.data(), s->buf.size())); +#endif // _WIN32 + // If stdin is closed, pass EOF to client if (s->buf_len <= 0) { s->buf_len = 1; @@ -204,7 +265,7 @@ void os_poll_tty(uint64_t wait) { } s->buf_pos = 0; -#else // HAVE_TTY +#else (void) wait; throw std::runtime_error("can't poll console input, it is unsupported in this platform"); #endif // HAVE_TTY @@ -268,7 +329,6 @@ unsigned char *os_map_file(const char *path, uint64_t length, bool shared) { #ifdef HAVE_MMAP const int oflag = shared ? O_RDWR : O_RDONLY; - const int mflag = shared ? MAP_SHARED : MAP_PRIVATE; // Try to open image file const int backing_file = open(path, oflag); @@ -293,6 +353,7 @@ unsigned char *os_map_file(const char *path, uint64_t length, bool shared) { } // Try to map image file to host memory + const int mflag = shared ? MAP_SHARED : MAP_PRIVATE; auto *host_memory = static_cast(mmap(nullptr, length, PROT_READ | PROT_WRITE, mflag, backing_file, 0)); if (host_memory == MAP_FAILED) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr) @@ -300,9 +361,57 @@ unsigned char *os_map_file(const char *path, uint64_t length, bool shared) { throw std::system_error{errno, std::generic_category(), "could not map image file '"s + path + "' to memory"s}; } - // We are can close the file after mapping it, because the OS will retain a reference of the file on its own + // We can close the file after mapping it, because the OS will retain a reference of the file on its own + close(backing_file); + return host_memory; + +#elif defined(_WIN32) + const int oflag = (shared ? O_RDWR : O_RDONLY) | O_BINARY; + + // Try to open image file + const int backing_file = open(path, oflag); + if (backing_file < 0) { + throw std::system_error{errno, std::generic_category(), "could not open image file '"s + path + "'"s}; + } + + // Try to get file size + struct __stat64 statbuf {}; + if (_fstat64(backing_file, &statbuf) < 0) { + close(backing_file); + throw std::system_error{errno, std::generic_category(), + "unable to obtain length of image file '"s + path + "'"s}; + } + + // Check that it matches range length + if (static_cast(statbuf.st_size) != length) { + close(backing_file); + throw std::invalid_argument{"image file '"s + path + "' size ("s + + std::to_string(static_cast(statbuf.st_size)) + ") does not match range length ("s + + std::to_string(length) + ")"s}; + } + + // Try to map image file to host memory + DWORD flProtect = shared ? PAGE_READWRITE : PAGE_READONLY; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + HANDLE hFile = reinterpret_cast(_get_osfhandle(backing_file)); + HANDLE hFileMappingObject = CreateFileMapping(hFile, NULL, flProtect, length >> 32, length & 0xffffffff, NULL); + if (!hFileMappingObject) { + close(backing_file); + throw std::system_error{errno, std::generic_category(), "could not map image file '"s + path + "' to memory"s}; + } + + DWORD dwDesiredAccess = shared ? FILE_MAP_WRITE : FILE_MAP_COPY; + auto *host_memory = static_cast(MapViewOfFile(hFileMappingObject, dwDesiredAccess, 0, 0, length)); + if (!host_memory) { + close(backing_file); + throw std::system_error{errno, std::generic_category(), "could not map image file '"s + path + "' to memory"s}; + } + + // We can close the file after mapping it, because the OS will retain a reference of the file on its own close(backing_file); -#else // HAVE_MMAP + return host_memory; + +#else if (shared) { throw std::runtime_error{"shared image file mapping is unsupported"s}; } @@ -339,17 +448,24 @@ unsigned char *os_map_file(const char *path, uint64_t length, bool shared) { if (ferror(fp.get())) { throw std::system_error{errno, std::generic_category(), "error reading from image file '"s + path + "'"s}; } -#endif // HAVE_MMAP return host_memory; + +#endif // HAVE_MMAP } void os_unmap_file(unsigned char *host_memory, uint64_t length) { #ifdef HAVE_MMAP munmap(host_memory, length); + +#elif defined(_WIN32) + (void) length; + UnmapViewOfFile(host_memory); + #else (void) length; std::free(host_memory); -#endif + +#endif // HAVE_MMAP } } // namespace cartesi