// Copyright 2021 The Emscripten Authors. All rights reserved. // Emscripten is available under two separate licenses, the MIT license and the // University of Illinois/NCSA Open Source License. Both these licenses can be // found in the LICENSE file. // This file defines the standard streams. #include #include #include #include #include "special_files.h" #include "wasmfs_internal.h" namespace wasmfs::SpecialFiles { namespace { // No-op reads and writes: /dev/null class NullFile : public DataFile { int open(oflags_t) override { return 0; } int close() override { return 0; } ssize_t write(const uint8_t* buf, size_t len, off_t offset) override { return len; } ssize_t read(uint8_t* buf, size_t len, off_t offset) override { return 0; } int flush() override { return 0; } off_t getSize() override { return 0; } int setSize(off_t size) override { return -EPERM; } public: NullFile() : DataFile(S_IRUGO | S_IWUGO, NullBackend, S_IFCHR) {} }; class StdinFile : public DataFile { int open(oflags_t) override { return 0; } int close() override { return 0; } ssize_t write(const uint8_t* buf, size_t len, off_t offset) override { return -__WASI_ERRNO_INVAL; } ssize_t read(uint8_t* buf, size_t len, off_t offset) override { for (size_t i = 0; i < len; i++) { auto c = _wasmfs_stdin_get_char(); if (c < 0) { // No more input can be read, return what we did read. return i; } buf[i] = c; } return len; }; int flush() override { return 0; } off_t getSize() override { return 0; } int setSize(off_t size) override { return -EPERM; } public: StdinFile() : DataFile(S_IRUGO, NullBackend, S_IFCHR) { seekable = false; } }; // A standard stream that writes: stdout or stderr. class WritingStdFile : public DataFile { protected: std::vector writeBuffer; int open(oflags_t) override { return 0; } int close() override { return 0; } ssize_t read(uint8_t* buf, size_t len, off_t offset) override { return -__WASI_ERRNO_INVAL; }; int flush() override { // Write a null to flush the output if we have content. if (!writeBuffer.empty()) { const uint8_t nothing = '\0'; write(¬hing, 1, 0); } return 0; } off_t getSize() override { return 0; } int setSize(off_t size) override { return -EPERM; } ssize_t writeToJS(const uint8_t* buf, size_t len, void (*console_write)(const char*), std::vector& fd_write_buffer) { for (size_t j = 0; j < len; j++) { uint8_t current = buf[j]; // Flush on either a null or a newline. if (current == '\0' || current == '\n') { fd_write_buffer.push_back('\0'); // for null-terminated C strings console_write(fd_write_buffer.data()); fd_write_buffer.clear(); } else { fd_write_buffer.push_back(current); } } return len; } public: WritingStdFile() : DataFile(S_IWUGO, NullBackend, S_IFCHR) { seekable = false; } }; class StdoutFile : public WritingStdFile { ssize_t write(const uint8_t* buf, size_t len, off_t offset) override { // Node and worker threads issue in Emscripten: // https://github.com/emscripten-core/emscripten/issues/14804. // Issue filed in Node: https://github.com/nodejs/node/issues/40961 // This is confirmed to occur when running with EXIT_RUNTIME and // PROXY_TO_PTHREAD. This results in only a single console.log statement // being outputted. The solution for now is to use out() and err() instead. return writeToJS(buf, len, &emscripten_out, writeBuffer); } public: StdoutFile() {} }; class StderrFile : public WritingStdFile { ssize_t write(const uint8_t* buf, size_t len, off_t offset) override { // Similar issue with Node and worker threads as emscripten_out. // TODO: May not want to proxy stderr (fd == 2) to the main thread, as // emscripten_err does. // This will not show in HTML - a console.warn in a worker is // sufficient. This would be a change from the current FS. return writeToJS(buf, len, &emscripten_err, writeBuffer); } public: StderrFile() {} }; class RandomFile : public DataFile { int open(oflags_t) override { return 0; } int close() override { return 0; } ssize_t write(const uint8_t* buf, size_t len, off_t offset) override { return -__WASI_ERRNO_INVAL; } ssize_t read(uint8_t* buf, size_t len, off_t offset) override { uint8_t* end = buf + len; for (; buf < end; buf += 256) { [[maybe_unused]] int err = getentropy(buf, std::min(end - buf, 256l)); assert(err == 0); } return len; }; int flush() override { return 0; } off_t getSize() override { return 0; } int setSize(off_t size) override { return -EPERM; } public: RandomFile() : DataFile(S_IRUGO, NullBackend, S_IFCHR) { seekable = false; } }; } // anonymous namespace std::shared_ptr getNull() { static auto null = std::make_shared(); return null; } std::shared_ptr getStdin() { static auto stdin = std::make_shared(); return stdin; } std::shared_ptr getStdout() { static auto stdout = std::make_shared(); return stdout; } std::shared_ptr getStderr() { static auto stderr = std::make_shared(); return stderr; } std::shared_ptr getRandom() { static auto random = std::make_shared(); return random; } std::shared_ptr getURandom() { static auto urandom = std::make_shared(); return urandom; } } // namespace wasmfs::SpecialFiles