| // 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 <emscripten/html5.h> |
| #include <unistd.h> |
| #include <vector> |
| #include <wasi/api.h> |
| |
| #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<char> 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<char>& 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<DataFile> getNull() { |
| static auto null = std::make_shared<NullFile>(); |
| return null; |
| } |
| |
| std::shared_ptr<DataFile> getStdin() { |
| static auto stdin = std::make_shared<StdinFile>(); |
| return stdin; |
| } |
| |
| std::shared_ptr<DataFile> getStdout() { |
| static auto stdout = std::make_shared<StdoutFile>(); |
| return stdout; |
| } |
| |
| std::shared_ptr<DataFile> getStderr() { |
| static auto stderr = std::make_shared<StderrFile>(); |
| return stderr; |
| } |
| |
| std::shared_ptr<DataFile> getRandom() { |
| static auto random = std::make_shared<RandomFile>(); |
| return random; |
| } |
| |
| std::shared_ptr<DataFile> getURandom() { |
| static auto urandom = std::make_shared<RandomFile>(); |
| return urandom; |
| } |
| |
| } // namespace wasmfs::SpecialFiles |