Skip to content

Commit

Permalink
feat: add file mapping and terminal support for Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
edubart committed Nov 9, 2023
1 parent 80099cc commit 965369c
Showing 1 changed file with 130 additions and 14 deletions.
144 changes: 130 additions & 14 deletions src/os.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// with this program (see COPYING). If not, see <https://www.gnu.org/licenses/>.
//

#if !defined(_WIN32) && !defined(NO_TTY)
#if !defined(NO_TTY)
#define HAVE_TTY
#endif

Expand All @@ -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 <fcntl.h> // open
#include <unistd.h> // write/read/close
#endif

#ifdef HAVE_TTY
#if defined(HAVE_TTY) && !defined(_WIN32)
#include <sys/select.h> // select
#endif

Expand All @@ -54,11 +54,11 @@

#ifdef _WIN32
#include <direct.h> // mkdir
#define mkdir(a, b) _mkdir(a)
#else
#include <sys/stat.h> // fstat/mkdir
#include <windows.h>
#endif

#include <sys/stat.h> // fstat

namespace cartesi {

using namespace std::string_literals;
Expand All @@ -73,6 +73,9 @@ struct tty_state {
#ifdef HAVE_TERMIOS
int ttyfd{-1};
termios oldtty{};
#elif defined(_WIN32)
HANDLE hStdin{};
DWORD dwOldStdinMode{};
#endif
};

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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<ssize_t>(numberOfCharsRead);
}
break;
} else {
// Consume input event
ReadConsoleInput(s->hStdin, &inputRecord, 1, &numberOfEventsRead);
}
}
}

#else
const int fd_max{0};
fd_set rfds{};
timeval tv{};
Expand All @@ -197,14 +256,16 @@ void os_poll_tty(uint64_t wait) {
}
s->buf_len = static_cast<ssize_t>(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;
s->buf[0] = 4; // CTRL+D
}
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
Expand Down Expand Up @@ -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);
Expand All @@ -293,16 +353,65 @@ 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<unsigned char *>(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)
close(backing_file);
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<uint64_t>(statbuf.st_size) != length) {
close(backing_file);
throw std::invalid_argument{"image file '"s + path + "' size ("s +
std::to_string(static_cast<uint64_t>(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<HANDLE>(_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<unsigned char *>(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};
}
Expand Down Expand Up @@ -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

0 comments on commit 965369c

Please sign in to comment.