Path: blob/main/system/lib/wasmfs/syscalls.cpp
6174 views
// Copyright 2021 The Emscripten Authors. All rights reserved.1// Emscripten is available under two separate licenses, the MIT license and the2// University of Illinois/NCSA Open Source License. Both these licenses can be3// found in the LICENSE file.45// Syscall implementations.67#define _LARGEFILE64_SOURCE // For F_GETLK64 etc89#include <dirent.h>10#include <emscripten/emscripten.h>11#include <emscripten/heap.h>12#include <emscripten/html5.h>13#include <errno.h>14#include <mutex>15#include <poll.h>16#include <stdarg.h>17#include <stdlib.h>18#include <sys/ioctl.h>19#include <sys/mman.h>20#include <sys/stat.h>21#include <sys/statfs.h>22#include <syscall_arch.h>23#include <unistd.h>24#include <utility>25#include <vector>26#include <wasi/api.h>2728#include "backend.h"29#include "file.h"30#include "file_table.h"31#include "paths.h"32#include "pipe_backend.h"33#include "special_files.h"34#include "wasmfs.h"3536// File permission macros for wasmfs.37// Used to improve readability compared to those in stat.h38#define WASMFS_PERM_READ 04443940#define WASMFS_PERM_WRITE 02224142#define WASMFS_PERM_EXECUTE 01114344// In Linux, the maximum length for a filename is 255 bytes.45#define WASMFS_NAME_MAX 2554647extern "C" {4849using namespace wasmfs;5051int __syscall_dup3(int oldfd, int newfd, int flags) {52if (flags & !O_CLOEXEC) {53// TODO: Test this case.54return -EINVAL;55}5657auto fileTable = wasmFS.getFileTable().locked();58auto oldOpenFile = fileTable.getEntry(oldfd);59if (!oldOpenFile) {60return -EBADF;61}62if (newfd < 0 || newfd >= WASMFS_FD_MAX) {63return -EBADF;64}65if (oldfd == newfd) {66return -EINVAL;67}6869// If the file descriptor newfd was previously open, it will just be70// overwritten silently.71(void)fileTable.setEntry(newfd, oldOpenFile);72return newfd;73}7475int __syscall_dup(int fd) {76auto fileTable = wasmFS.getFileTable().locked();7778// Check that an open file exists corresponding to the given fd.79auto openFile = fileTable.getEntry(fd);80if (!openFile) {81return -EBADF;82}83return fileTable.addEntry(openFile);84}8586// This enum specifies whether file offset will be provided by the open file87// state or provided by argument in the case of pread or pwrite.88enum class OffsetHandling { OpenFileState, Argument };8990// Internal write function called by __wasi_fd_write and __wasi_fd_pwrite91// Receives an open file state offset.92// Optionally sets open file state offset.93static __wasi_errno_t writeAtOffset(OffsetHandling setOffset,94__wasi_fd_t fd,95const __wasi_ciovec_t* iovs,96size_t iovs_len,97__wasi_size_t* nwritten,98__wasi_filesize_t offset = 0) {99auto openFile = wasmFS.getFileTable().locked().getEntry(fd);100if (!openFile) {101return __WASI_ERRNO_BADF;102}103104if (iovs_len < 0 || offset < 0) {105return __WASI_ERRNO_INVAL;106}107108auto lockedOpenFile = openFile->locked();109auto file = lockedOpenFile.getFile()->dynCast<DataFile>();110if (!file) {111return __WASI_ERRNO_ISDIR;112}113114auto lockedFile = file->locked();115116if (setOffset == OffsetHandling::OpenFileState) {117if (lockedOpenFile.getFlags() & O_APPEND) {118off_t size = lockedFile.getSize();119if (size < 0) {120// Translate to WASI standard of positive return codes.121return -size;122}123offset = size;124lockedOpenFile.setPosition(offset);125} else {126offset = lockedOpenFile.getPosition();127}128}129130// TODO: Check open file access mode for write permissions.131132size_t bytesWritten = 0;133for (size_t i = 0; i < iovs_len; i++) {134const uint8_t* buf = iovs[i].buf;135off_t len = iovs[i].buf_len;136137// Check if buf_len specifies a positive length buffer but buf is a138// null pointer139if (!buf && len > 0) {140return __WASI_ERRNO_INVAL;141}142143// Check if the sum of the buf_len values overflows an off_t (63 bits).144if (addWillOverFlow(offset, (__wasi_filesize_t)bytesWritten)) {145return __WASI_ERRNO_FBIG;146}147148auto result = lockedFile.write(buf, len, offset + bytesWritten);149if (result < 0) {150// This individual write failed. Report the error unless we've already151// written some bytes, in which case report a successful short write.152if (bytesWritten > 0) {153break;154}155return -result;156}157// The write was successful.158bytesWritten += result;159if (result < len) {160// The write was short, so stop here.161break;162}163}164*nwritten = bytesWritten;165if (setOffset == OffsetHandling::OpenFileState &&166lockedOpenFile.getFile()->isSeekable()) {167lockedOpenFile.setPosition(offset + bytesWritten);168}169if (bytesWritten) {170lockedFile.updateMTime();171}172return __WASI_ERRNO_SUCCESS;173}174175// Internal read function called by __wasi_fd_read and __wasi_fd_pread176// Receives an open file state offset.177// Optionally sets open file state offset.178// TODO: combine this with writeAtOffset because the code is nearly identical.179static __wasi_errno_t readAtOffset(OffsetHandling setOffset,180__wasi_fd_t fd,181const __wasi_iovec_t* iovs,182size_t iovs_len,183__wasi_size_t* nread,184__wasi_filesize_t offset = 0) {185auto openFile = wasmFS.getFileTable().locked().getEntry(fd);186if (!openFile) {187return __WASI_ERRNO_BADF;188}189190auto lockedOpenFile = openFile->locked();191192if (setOffset == OffsetHandling::OpenFileState) {193offset = lockedOpenFile.getPosition();194}195196if (iovs_len < 0 || offset < 0) {197return __WASI_ERRNO_INVAL;198}199200// TODO: Check open file access mode for read permissions.201202auto file = lockedOpenFile.getFile()->dynCast<DataFile>();203204// If file is nullptr, then the file was not a DataFile.205if (!file) {206return __WASI_ERRNO_ISDIR;207}208209auto lockedFile = file->locked();210211size_t bytesRead = 0;212for (size_t i = 0; i < iovs_len; i++) {213uint8_t* buf = iovs[i].buf;214size_t len = iovs[i].buf_len;215216if (!buf && len > 0) {217return __WASI_ERRNO_INVAL;218}219220// TODO: Check for overflow when adding offset + bytesRead.221auto result = lockedFile.read(buf, len, offset + bytesRead);222if (result < 0) {223// This individual read failed. Report the error unless we've already read224// some bytes, in which case report a successful short read.225if (bytesRead > 0) {226break;227}228return -result;229}230231// The read was successful.232233// Backends must only return len or less.234assert(result <= len);235236bytesRead += result;237if (result < len) {238// The read was short, so stop here.239break;240}241}242*nread = bytesRead;243if (setOffset == OffsetHandling::OpenFileState &&244lockedOpenFile.getFile()->isSeekable()) {245lockedOpenFile.setPosition(offset + bytesRead);246}247return __WASI_ERRNO_SUCCESS;248}249250__wasi_errno_t __wasi_fd_write(__wasi_fd_t fd,251const __wasi_ciovec_t* iovs,252size_t iovs_len,253__wasi_size_t* nwritten) {254return writeAtOffset(255OffsetHandling::OpenFileState, fd, iovs, iovs_len, nwritten);256}257258__wasi_errno_t __wasi_fd_read(__wasi_fd_t fd,259const __wasi_iovec_t* iovs,260size_t iovs_len,261__wasi_size_t* nread) {262return readAtOffset(OffsetHandling::OpenFileState, fd, iovs, iovs_len, nread);263}264265__wasi_errno_t __wasi_fd_pwrite(__wasi_fd_t fd,266const __wasi_ciovec_t* iovs,267size_t iovs_len,268__wasi_filesize_t offset,269__wasi_size_t* nwritten) {270return writeAtOffset(271OffsetHandling::Argument, fd, iovs, iovs_len, nwritten, offset);272}273274__wasi_errno_t __wasi_fd_pread(__wasi_fd_t fd,275const __wasi_iovec_t* iovs,276size_t iovs_len,277__wasi_filesize_t offset,278__wasi_size_t* nread) {279return readAtOffset(280OffsetHandling::Argument, fd, iovs, iovs_len, nread, offset);281}282283__wasi_errno_t __wasi_fd_close(__wasi_fd_t fd) {284std::shared_ptr<DataFile> closee;285{286// Do not hold the file table lock while performing the close.287auto fileTable = wasmFS.getFileTable().locked();288auto entry = fileTable.getEntry(fd);289if (!entry) {290return __WASI_ERRNO_BADF;291}292closee = fileTable.setEntry(fd, nullptr);293}294if (closee) {295// Translate to WASI standard of positive return codes.296int ret = -closee->locked().close();297assert(ret >= 0);298return ret;299}300return __WASI_ERRNO_SUCCESS;301}302303__wasi_errno_t __wasi_fd_sync(__wasi_fd_t fd) {304auto openFile = wasmFS.getFileTable().locked().getEntry(fd);305if (!openFile) {306return __WASI_ERRNO_BADF;307}308309// Nothing to flush for anything but a data file, but also not an error either310// way. TODO: in the future we may want syncing of directories.311auto dataFile = openFile->locked().getFile()->dynCast<DataFile>();312if (dataFile) {313auto ret = dataFile->locked().flush();314assert(ret <= 0);315// Translate to WASI standard of positive return codes.316return -ret;317}318319return __WASI_ERRNO_SUCCESS;320}321322int __syscall_fdatasync(int fd) {323// TODO: Optimize this to avoid unnecessarily flushing unnecessary metadata.324return __wasi_fd_sync(fd);325}326327backend_t wasmfs_get_backend_by_fd(int fd) {328auto openFile = wasmFS.getFileTable().locked().getEntry(fd);329if (!openFile) {330return NullBackend;331}332return openFile->locked().getFile()->getBackend();333}334335// This function is exposed to users to allow them to obtain a backend_t for a336// specified path.337backend_t wasmfs_get_backend_by_path(const char* path) {338auto parsed = path::parseFile(path);339if (parsed.getError()) {340// Could not find the file.341return NullBackend;342}343return parsed.getFile()->getBackend();344}345346static timespec ms_to_timespec(double ms) {347long long seconds = ms / 1000;348timespec ts;349ts.tv_sec = seconds; // seconds350ts.tv_nsec = (ms - (seconds * 1000)) * 1000 * 1000; // nanoseconds351return ts;352}353354int __syscall_newfstatat(int dirfd, intptr_t path, intptr_t buf, int flags) {355// Only accept valid flags.356if (flags & ~(AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW)) {357// TODO: Test this case.358return -EINVAL;359}360auto parsed = path::getFileAt(dirfd, (char*)path, flags);361if (auto err = parsed.getError()) {362return err;363}364auto file = parsed.getFile();365366// Extract the information from the file.367auto lockedFile = file->locked();368auto buffer = (struct stat*)buf;369370off_t size = lockedFile.getSize();371if (size < 0) {372return size;373}374buffer->st_size = size;375376// ATTN: hard-coded constant values are copied from the existing JS file377// system. Specific values were chosen to match existing library_fs.js378// values.379// ID of device containing file: Hardcode 1 for now, no meaning at the380// moment for Emscripten.381buffer->st_dev = 1;382buffer->st_mode = lockedFile.getMode();383buffer->st_ino = file->getIno();384// The number of hard links is 1 since they are unsupported.385buffer->st_nlink = 1;386buffer->st_uid = 0;387buffer->st_gid = 0;388// Device ID (if special file) No meaning right now for Emscripten.389buffer->st_rdev = 0;390// The syscall docs state this is hardcoded to # of 512 byte blocks.391buffer->st_blocks = (buffer->st_size + 511) / 512;392// Specifies the preferred blocksize for efficient disk I/O.393buffer->st_blksize = 4096;394buffer->st_atim = ms_to_timespec(lockedFile.getATime());395buffer->st_mtim = ms_to_timespec(lockedFile.getMTime());396buffer->st_ctim = ms_to_timespec(lockedFile.getCTime());397return __WASI_ERRNO_SUCCESS;398}399400int __syscall_stat64(intptr_t path, intptr_t buf) {401return __syscall_newfstatat(AT_FDCWD, path, buf, 0);402}403404int __syscall_lstat64(intptr_t path, intptr_t buf) {405return __syscall_newfstatat(AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW);406}407408int __syscall_fstat64(int fd, intptr_t buf) {409return __syscall_newfstatat(fd, (intptr_t) "", buf, AT_EMPTY_PATH);410}411412// When calling doOpen(), we may request an FD be returned, or we may not need413// that return value (in which case no FD need be allocated, and we return 0 on414// success).415enum class OpenReturnMode { FD, Nothing };416417static __wasi_fd_t doOpen(path::ParsedParent parsed,418int flags,419mode_t mode,420backend_t backend = NullBackend,421OpenReturnMode returnMode = OpenReturnMode::FD) {422int accessMode = (flags & O_ACCMODE);423if (accessMode != O_WRONLY && accessMode != O_RDONLY &&424accessMode != O_RDWR) {425return -EINVAL;426}427428// TODO: remove assert when all functionality is complete.429assert((flags & ~(O_CREAT | O_EXCL | O_DIRECTORY | O_TRUNC | O_APPEND |430O_RDWR | O_WRONLY | O_RDONLY | O_LARGEFILE | O_NOFOLLOW |431O_CLOEXEC | O_NONBLOCK)) == 0);432433if (auto err = parsed.getError()) {434return err;435}436auto& [parent, childName] = parsed.getParentChild();437if (childName.size() > WASMFS_NAME_MAX) {438return -ENAMETOOLONG;439}440441std::shared_ptr<File> child;442{443auto lockedParent = parent->locked();444child = lockedParent.getChild(std::string(childName));445// The requested node was not found.446if (!child) {447// If curr is the last element and the create flag is specified448// If O_DIRECTORY is also specified, still create a regular file:449// https://man7.org/linux/man-pages/man2/open.2.html#BUGS450if (!(flags & O_CREAT)) {451return -ENOENT;452}453454// Inserting into an unlinked directory is not allowed.455if (!lockedParent.getParent()) {456return -ENOENT;457}458459// Mask out everything except the permissions bits.460mode &= S_IALLUGO;461462// If there is no explicitly provided backend, use the parent's backend.463if (!backend) {464backend = parent->getBackend();465}466467// TODO: Check write permissions on the parent directory.468std::shared_ptr<File> created;469if (backend == parent->getBackend()) {470created = lockedParent.insertDataFile(std::string(childName), mode);471if (!created) {472// TODO Receive a specific error code, and report it here. For now,473// report a generic error.474return -EIO;475}476} else {477created = backend->createFile(mode);478if (!created) {479// TODO Receive a specific error code, and report it here. For now,480// report a generic error.481return -EIO;482}483[[maybe_unused]] bool mounted =484lockedParent.mountChild(std::string(childName), created);485assert(mounted);486}487// TODO: Check that the insert actually succeeds.488if (returnMode == OpenReturnMode::Nothing) {489return 0;490}491492std::shared_ptr<OpenFileState> openFile;493if (auto err = OpenFileState::create(created, flags, openFile)) {494assert(err < 0);495return err;496}497return wasmFS.getFileTable().locked().addEntry(openFile);498}499}500501if (auto link = child->dynCast<Symlink>()) {502if (flags & O_NOFOLLOW) {503return -ELOOP;504}505// TODO: The link dereference count starts back at 0 here. We could506// propagate it from the previous path parsing instead.507auto target = link->getTarget();508auto parsedLink = path::getFileFrom(parent, target);509if (auto err = parsedLink.getError()) {510return err;511}512child = parsedLink.getFile();513}514assert(!child->is<Symlink>());515516// Return an error if the file exists and O_CREAT and O_EXCL are specified.517if ((flags & O_EXCL) && (flags & O_CREAT)) {518return -EEXIST;519}520521if (child->is<Directory>() && (accessMode != O_RDONLY || (flags & O_CREAT))) {522return -EISDIR;523}524525// Check user permissions.526auto fileMode = child->locked().getMode();527if ((accessMode == O_RDONLY || accessMode == O_RDWR) &&528!(fileMode & WASMFS_PERM_READ)) {529return -EACCES;530}531if ((accessMode == O_WRONLY || accessMode == O_RDWR) &&532!(fileMode & WASMFS_PERM_WRITE)) {533return -EACCES;534}535536// Fail if O_DIRECTORY is specified and pathname is not a directory537if (flags & O_DIRECTORY && !child->is<Directory>()) {538return -ENOTDIR;539}540541// Note that we open the file before truncating it because some backends may542// truncate opened files more efficiently (e.g. OPFS).543std::shared_ptr<OpenFileState> openFile;544if (auto err = OpenFileState::create(child, flags, openFile)) {545assert(err < 0);546return err;547}548549// If O_TRUNC, truncate the file if possible.550if (flags & O_TRUNC) {551if (!child->is<DataFile>()) {552return -EISDIR;553}554if ((fileMode & WASMFS_PERM_WRITE) == 0) {555return -EACCES;556}557// Try to truncate the file, continuing silently if we cannot.558(void)child->cast<DataFile>()->locked().setSize(0);559}560561return wasmFS.getFileTable().locked().addEntry(openFile);562}563564// This function is exposed to users and allows users to create a file in a565// specific backend. An fd to an open file is returned.566int wasmfs_create_file(char* pathname, mode_t mode, backend_t backend) {567static_assert(std::is_same_v<decltype(doOpen(0, 0, 0, 0)), unsigned int>,568"unexpected conversion from result of doOpen to int");569return doOpen(570path::parseParent((char*)pathname), O_CREAT | O_EXCL, mode, backend);571}572573// TODO: Test this with non-AT_FDCWD values.574int __syscall_openat(int dirfd, intptr_t path, int flags, ...) {575mode_t mode = 0;576va_list v1;577va_start(v1, flags);578mode = va_arg(v1, int);579va_end(v1);580581return doOpen(path::parseParent((char*)path, dirfd), flags, mode);582}583584int __syscall_mknodat(int dirfd, intptr_t path, int mode, int dev) {585assert(dev == 0); // TODO: support special devices586if (mode & S_IFDIR) {587return -EINVAL;588}589if (mode & S_IFIFO) {590return -EPERM;591}592return doOpen(path::parseParent((char*)path, dirfd),593O_CREAT | O_EXCL,594mode,595NullBackend,596OpenReturnMode::Nothing);597}598599static int600doMkdir(path::ParsedParent parsed, int mode, backend_t backend = NullBackend) {601if (auto err = parsed.getError()) {602return err;603}604auto& [parent, childNameView] = parsed.getParentChild();605std::string childName(childNameView);606auto lockedParent = parent->locked();607608if (childName.size() > WASMFS_NAME_MAX) {609return -ENAMETOOLONG;610}611612// Check if the requested directory already exists.613if (lockedParent.getChild(childName)) {614return -EEXIST;615}616617// Mask rwx permissions for user, group and others, and the sticky bit.618// This prevents users from entering S_IFREG for example.619// https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html620mode &= S_IRWXUGO | S_ISVTX;621622if (!(lockedParent.getMode() & WASMFS_PERM_WRITE)) {623return -EACCES;624}625626// By default, the backend that the directory is created in is the same as627// the parent directory. However, if a backend is passed as a parameter,628// then that backend is used.629if (!backend) {630backend = parent->getBackend();631}632633if (backend == parent->getBackend()) {634if (!lockedParent.insertDirectory(childName, mode)) {635// TODO Receive a specific error code, and report it here. For now, report636// a generic error.637return -EIO;638}639} else {640auto created = backend->createDirectory(mode);641if (!created) {642// TODO Receive a specific error code, and report it here. For now, report643// a generic error.644return -EIO;645}646[[maybe_unused]] bool mounted = lockedParent.mountChild(childName, created);647assert(mounted);648}649650// TODO: Check that the insertion is successful.651652return 0;653}654655// This function is exposed to users and allows users to specify a particular656// backend that a directory should be created within.657int wasmfs_create_directory(char* path, int mode, backend_t backend) {658static_assert(std::is_same_v<decltype(doMkdir(0, 0, 0)), int>,659"unexpected conversion from result of doMkdir to int");660return doMkdir(path::parseParent(path), mode, backend);661}662663// TODO: Test this.664int __syscall_mkdirat(int dirfd, intptr_t path, int mode) {665return doMkdir(path::parseParent((char*)path, dirfd), mode);666}667668__wasi_errno_t __wasi_fd_seek(__wasi_fd_t fd,669__wasi_filedelta_t offset,670__wasi_whence_t whence,671__wasi_filesize_t* newoffset) {672auto openFile = wasmFS.getFileTable().locked().getEntry(fd);673if (!openFile) {674return __WASI_ERRNO_BADF;675}676auto lockedOpenFile = openFile->locked();677678if (!lockedOpenFile.getFile()->isSeekable()) {679return __WASI_ERRNO_SPIPE;680}681682off_t position;683if (whence == SEEK_SET) {684position = offset;685} else if (whence == SEEK_CUR) {686position = lockedOpenFile.getPosition() + offset;687} else if (whence == SEEK_END) {688// Only the open file state is altered in seek. Locking the underlying689// data file here once is sufficient.690off_t size = lockedOpenFile.getFile()->locked().getSize();691if (size < 0) {692// Translate to WASI standard of positive return codes.693return -size;694}695position = size + offset;696} else {697return __WASI_ERRNO_INVAL;698}699700if (position < 0) {701return __WASI_ERRNO_INVAL;702}703704lockedOpenFile.setPosition(position);705706if (newoffset) {707*newoffset = position;708}709710return __WASI_ERRNO_SUCCESS;711}712713static int doChdir(std::shared_ptr<File>& file) {714auto dir = file->dynCast<Directory>();715if (!dir) {716return -ENOTDIR;717}718wasmFS.setCWD(dir);719return 0;720}721722int __syscall_chdir(intptr_t path) {723auto parsed = path::parseFile((char*)path);724if (auto err = parsed.getError()) {725return err;726}727return doChdir(parsed.getFile());728}729730int __syscall_fchdir(int fd) {731auto openFile = wasmFS.getFileTable().locked().getEntry(fd);732if (!openFile) {733return -EBADF;734}735return doChdir(openFile->locked().getFile());736}737738int __syscall_getcwd(intptr_t buf, size_t size) {739// Check if buf points to a bad address.740if (!buf && size > 0) {741return -EFAULT;742}743744// Check if the size argument is zero and buf is not a null pointer.745if (buf && size == 0) {746return -EINVAL;747}748749auto curr = wasmFS.getCWD();750751std::string result = "";752753while (curr != wasmFS.getRootDirectory()) {754auto parent = curr->locked().getParent();755// Check if the parent exists. The parent may not exist if the CWD or one756// of its ancestors has been unlinked.757if (!parent) {758return -ENOENT;759}760761auto name = parent->locked().getName(curr);762result = '/' + name + result;763curr = parent;764}765766// Check if the cwd is the root directory.767if (result.empty()) {768result = "/";769}770771int len = result.length() + 1;772773// Check if the size argument is less than the length of the absolute774// pathname of the working directory, including null terminator.775if (len > size) {776return -ERANGE;777}778779// Return value is a null-terminated c string.780strcpy((char*)buf, result.c_str());781782return len;783}784785__wasi_errno_t __wasi_fd_fdstat_get(__wasi_fd_t fd, __wasi_fdstat_t* stat) {786// TODO: This is only partial implementation of __wasi_fd_fdstat_get. Enough787// to get __wasi_fd_is_valid working.788// There are other fields in the stat structure that we should really789// be filling in here.790auto openFile = wasmFS.getFileTable().locked().getEntry(fd);791if (!openFile) {792return __WASI_ERRNO_BADF;793}794795if (openFile->locked().getFile()->is<Directory>()) {796stat->fs_filetype = __WASI_FILETYPE_DIRECTORY;797} else {798stat->fs_filetype = __WASI_FILETYPE_REGULAR_FILE;799}800return __WASI_ERRNO_SUCCESS;801}802803// TODO: Test this with non-AT_FDCWD values.804int __syscall_unlinkat(int dirfd, intptr_t path, int flags) {805if (flags & ~AT_REMOVEDIR) {806// TODO: Test this case.807return -EINVAL;808}809// It is invalid for rmdir paths to end in ".", but we need to distinguish810// this case from the case of `parseParent` returning (root, '.') when parsing811// "/", so we need to find the invalid "/." manually.812if (flags == AT_REMOVEDIR) {813std::string_view p((char*)path);814// Ignore trailing '/'.815while (!p.empty() && p.back() == '/') {816p.remove_suffix(1);817}818if (p.size() >= 2 && p.substr(p.size() - 2) == std::string_view("/.")) {819return -EINVAL;820}821}822auto parsed = path::parseParent((char*)path, dirfd);823if (auto err = parsed.getError()) {824return err;825}826auto& [parent, childNameView] = parsed.getParentChild();827std::string childName(childNameView);828auto lockedParent = parent->locked();829auto file = lockedParent.getChild(childName);830if (!file) {831return -ENOENT;832}833// Disallow removing the root directory, even if it is empty.834if (file == wasmFS.getRootDirectory()) {835return -EBUSY;836}837838auto lockedFile = file->locked();839if (auto dir = file->dynCast<Directory>()) {840if (flags != AT_REMOVEDIR) {841return -EISDIR;842}843// A directory can only be removed if it has no entries.844if (dir->locked().getNumEntries() > 0) {845return -ENOTEMPTY;846}847} else {848// A normal file or symlink.849if (flags == AT_REMOVEDIR) {850return -ENOTDIR;851}852}853854// Cannot unlink/rmdir if the parent dir doesn't have write permissions.855if (!(lockedParent.getMode() & WASMFS_PERM_WRITE)) {856return -EACCES;857}858859// Input is valid, perform the unlink.860return lockedParent.removeChild(childName);861}862863int __syscall_rmdir(intptr_t path) {864return __syscall_unlinkat(AT_FDCWD, path, AT_REMOVEDIR);865}866867// wasmfs_unmount is similar to __syscall_unlinkat, but assumes AT_REMOVEDIR is868// true and will only unlink mountpoints (Empty and nonempty).869int wasmfs_unmount(const char* path) {870auto parsed = path::parseParent(path, AT_FDCWD);871if (auto err = parsed.getError()) {872return err;873}874auto& [parent, childNameView] = parsed.getParentChild();875std::string childName(childNameView);876auto lockedParent = parent->locked();877auto file = lockedParent.getChild(childName);878if (!file) {879return -ENOENT;880}881// Disallow removing the root directory, even if it is empty.882if (file == wasmFS.getRootDirectory()) {883return -EBUSY;884}885886if (!file->dynCast<Directory>()) {887// A normal file or symlink.888return -ENOTDIR;889}890891if (parent->getBackend() == file->getBackend()) {892// The child is not a valid mountpoint.893return -EINVAL;894}895896// Input is valid, perform the unlink.897return lockedParent.removeChild(childName);898}899900int __syscall_getdents64(int fd, intptr_t dirp, size_t count) {901dirent* result = (dirent*)dirp;902903// Check if the result buffer is too small.904if (count / sizeof(dirent) == 0) {905return -EINVAL;906}907908auto openFile = wasmFS.getFileTable().locked().getEntry(fd);909if (!openFile) {910return -EBADF;911}912auto lockedOpenFile = openFile->locked();913914auto dir = lockedOpenFile.getFile()->dynCast<Directory>();915if (!dir) {916return -ENOTDIR;917}918auto lockedDir = dir->locked();919920// A directory's position corresponds to the index in its entries vector.921int index = lockedOpenFile.getPosition();922923// If this directory has been unlinked and has no parent, then it is924// completely empty.925auto parent = lockedDir.getParent();926if (!parent) {927return 0;928}929930off_t bytesRead = 0;931const auto& dirents = openFile->dirents;932for (; index < dirents.size() && bytesRead + sizeof(dirent) <= count;933index++) {934const auto& entry = dirents[index];935result->d_ino = entry.ino;936result->d_off = index + 1;937result->d_reclen = sizeof(dirent);938switch (entry.kind) {939case File::UnknownKind:940result->d_type = DT_UNKNOWN;941break;942case File::DataFileKind:943result->d_type = DT_REG;944break;945case File::DirectoryKind:946result->d_type = DT_DIR;947break;948case File::SymlinkKind:949result->d_type = DT_LNK;950break;951default:952result->d_type = DT_UNKNOWN;953break;954}955assert(entry.name.size() + 1 <= sizeof(result->d_name));956strcpy(result->d_name, entry.name.c_str());957++result;958bytesRead += sizeof(dirent);959}960961// Update position962lockedOpenFile.setPosition(index);963964return bytesRead;965}966967// TODO: Test this with non-AT_FDCWD values.968int __syscall_renameat(int olddirfd,969intptr_t oldpath,970int newdirfd,971intptr_t newpath) {972// Rename is the only syscall that needs to (or is allowed to) acquire locks973// on two directories at once. It requires locks on both the old and new974// parent directories to ensure that the moved file can be atomically removed975// from the old directory and added to the new directory without something976// changing that would prevent the move.977//978// To prevent deadlock in the case of simultaneous renames, serialize renames979// with an additional global lock.980static std::mutex renameMutex;981std::lock_guard<std::mutex> renameLock(renameMutex);982983// Get the old directory.984auto parsedOld = path::parseParent((char*)oldpath, olddirfd);985if (auto err = parsedOld.getError()) {986return err;987}988auto& [oldParent, oldFileNameView] = parsedOld.getParentChild();989std::string oldFileName(oldFileNameView);990991// Get the new directory.992auto parsedNew = path::parseParent((char*)newpath, newdirfd);993if (auto err = parsedNew.getError()) {994return err;995}996auto& [newParent, newFileNameView] = parsedNew.getParentChild();997std::string newFileName(newFileNameView);998999if (newFileNameView.size() > WASMFS_NAME_MAX) {1000return -ENAMETOOLONG;1001}10021003// Lock both directories.1004auto lockedOldParent = oldParent->locked();1005auto lockedNewParent = newParent->locked();10061007// Get the source and destination files.1008auto oldFile = lockedOldParent.getChild(oldFileName);1009auto newFile = lockedNewParent.getChild(newFileName);10101011if (!oldFile) {1012return -ENOENT;1013}10141015// If the source and destination are the same, do nothing.1016if (oldFile == newFile) {1017return 0;1018}10191020// Never allow renaming or overwriting the root.1021auto root = wasmFS.getRootDirectory();1022if (oldFile == root || newFile == root) {1023return -EBUSY;1024}10251026// Cannot modify either directory without write permissions.1027if (!(lockedOldParent.getMode() & WASMFS_PERM_WRITE) ||1028!(lockedNewParent.getMode() & WASMFS_PERM_WRITE)) {1029return -EACCES;1030}10311032// Both parents must have the same backend.1033if (oldParent->getBackend() != newParent->getBackend()) {1034return -EXDEV;1035}10361037// Check that oldDir is not an ancestor of newDir.1038for (auto curr = newParent; curr != root; curr = curr->locked().getParent()) {1039if (curr == oldFile) {1040return -EINVAL;1041}1042}10431044// The new file will be removed if it already exists.1045if (newFile) {1046if (auto newDir = newFile->dynCast<Directory>()) {1047// Cannot overwrite a directory with a non-directory.1048auto oldDir = oldFile->dynCast<Directory>();1049if (!oldDir) {1050return -EISDIR;1051}1052// Cannot overwrite a non-empty directory.1053if (newDir->locked().getNumEntries() > 0) {1054return -ENOTEMPTY;1055}1056} else {1057// Cannot overwrite a non-directory with a directory.1058if (oldFile->is<Directory>()) {1059return -ENOTDIR;1060}1061}1062}10631064// Perform the move.1065if (auto err = lockedNewParent.insertMove(newFileName, oldFile)) {1066assert(err < 0);1067return err;1068}1069return 0;1070}10711072// TODO: Test this with non-AT_FDCWD values.1073int __syscall_symlinkat(intptr_t target, int newdirfd, intptr_t linkpath) {1074auto parsed = path::parseParent((char*)linkpath, newdirfd);1075if (auto err = parsed.getError()) {1076return err;1077}1078auto& [parent, childNameView] = parsed.getParentChild();1079if (childNameView.size() > WASMFS_NAME_MAX) {1080return -ENAMETOOLONG;1081}1082auto lockedParent = parent->locked();1083std::string childName(childNameView);1084if (lockedParent.getChild(childName)) {1085return -EEXIST;1086}1087if (!lockedParent.insertSymlink(childName, (char*)target)) {1088return -EPERM;1089}1090return 0;1091}10921093// TODO: Test this with non-AT_FDCWD values.1094int __syscall_readlinkat(int dirfd,1095intptr_t path,1096intptr_t buf,1097size_t bufsize) {1098// TODO: Handle empty paths.1099auto parsed = path::parseFile((char*)path, dirfd, path::NoFollowLinks);1100if (auto err = parsed.getError()) {1101return err;1102}1103auto link = parsed.getFile()->dynCast<Symlink>();1104if (!link) {1105return -EINVAL;1106}1107const auto& target = link->getTarget();1108auto bytes = std::min((size_t)bufsize, target.size());1109memcpy((char*)buf, target.c_str(), bytes);1110return bytes;1111}11121113static double timespec_to_ms(timespec ts) {1114if (ts.tv_nsec == UTIME_OMIT) {1115return INFINITY;1116}1117if (ts.tv_nsec == UTIME_NOW) {1118return emscripten_date_now();1119}1120return double(ts.tv_sec) * 1000 + double(ts.tv_nsec) / (1000 * 1000);1121}11221123// TODO: Test this with non-AT_FDCWD values.1124int __syscall_utimensat(int dirFD, intptr_t path_, intptr_t times_, int flags) {1125const char* path = (const char*)path_;1126const struct timespec* times = (const struct timespec*)times_;1127if (flags & ~AT_SYMLINK_NOFOLLOW) {1128// TODO: Test this case.1129return -EINVAL;1130}11311132// Add AT_EMPTY_PATH as Linux (and so, musl, and us) has a nonstandard1133// behavior in which an empty path means to operate on whatever is in dirFD1134// (directory or not), which is exactly the behavior of AT_EMPTY_PATH (but1135// without passing that in). See "C library/kernel ABI differences" in1136// https://man7.org/linux/man-pages/man2/utimensat.2.html1137//1138// TODO: Handle AT_SYMLINK_NOFOLLOW once we traverse symlinks correctly.1139auto parsed = path::getFileAt(dirFD, path, flags | AT_EMPTY_PATH);1140if (auto err = parsed.getError()) {1141return err;1142}11431144// TODO: Handle tv_nsec being UTIME_NOW or UTIME_OMIT.1145// TODO: Check for write access to the file (see man page for specifics).1146double aTime, mTime;11471148if (times == nullptr) {1149aTime = mTime = emscripten_date_now();1150} else {1151aTime = timespec_to_ms(times[0]);1152mTime = timespec_to_ms(times[1]);1153}11541155auto locked = parsed.getFile()->locked();1156if (aTime != INFINITY) {1157locked.setATime(aTime);1158}1159if (mTime != INFINITY) {1160locked.setMTime(mTime);1161}11621163return 0;1164}11651166// TODO: Test this with non-AT_FDCWD values.1167int __syscall_fchmodat2(int dirfd, intptr_t path, int mode, int flags) {1168if (flags & ~AT_SYMLINK_NOFOLLOW) {1169// TODO: Test this case.1170return -EINVAL;1171}1172auto parsed = path::getFileAt(dirfd, (char*)path, flags);1173if (auto err = parsed.getError()) {1174return err;1175}1176auto lockedFile = parsed.getFile()->locked();1177lockedFile.setMode(mode);1178// On POSIX, ctime is updated on metadata changes, like chmod.1179lockedFile.updateCTime();1180return 0;1181}11821183int __syscall_chmod(intptr_t path, int mode) {1184return __syscall_fchmodat2(AT_FDCWD, path, mode, 0);1185}11861187int __syscall_fchmod(int fd, int mode) {1188auto openFile = wasmFS.getFileTable().locked().getEntry(fd);1189if (!openFile) {1190return -EBADF;1191}1192auto lockedFile = openFile->locked().getFile()->locked();1193lockedFile.setMode(mode);1194lockedFile.updateCTime();1195return 0;1196}11971198int __syscall_fchownat(1199int dirfd, intptr_t path, int owner, int group, int flags) {1200// Only accept valid flags.1201if (flags & ~(AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) {1202// TODO: Test this case.1203return -EINVAL;1204}1205auto parsed = path::getFileAt(dirfd, (char*)path, flags);1206if (auto err = parsed.getError()) {1207return err;1208}12091210// Ignore the actual owner and group because we don't track those.1211// TODO: Update metadata time stamp.1212return 0;1213}12141215int __syscall_fchown32(int fd, int owner, int group) {1216return __syscall_fchownat(fd, (intptr_t) "", owner, group, AT_EMPTY_PATH);1217}12181219// TODO: Test this with non-AT_FDCWD values.1220int __syscall_faccessat(int dirfd, intptr_t path, int amode, int flags) {1221// The input must be F_OK (check for existence) or a combination of [RWX]_OK1222// flags.1223if (amode != F_OK && (amode & ~(R_OK | W_OK | X_OK))) {1224return -EINVAL;1225}1226if (flags & ~(AT_EACCESS | AT_SYMLINK_NOFOLLOW)) {1227// TODO: Test this case.1228return -EINVAL;1229}12301231// TODO: Handle AT_SYMLINK_NOFOLLOW once we traverse symlinks correctly.1232auto parsed = path::parseFile((char*)path, dirfd);1233if (auto err = parsed.getError()) {1234return err;1235}12361237if (amode != F_OK) {1238auto mode = parsed.getFile()->locked().getMode();1239if ((amode & R_OK) && !(mode & WASMFS_PERM_READ)) {1240return -EACCES;1241}1242if ((amode & W_OK) && !(mode & WASMFS_PERM_WRITE)) {1243return -EACCES;1244}1245if ((amode & X_OK) && !(mode & WASMFS_PERM_EXECUTE)) {1246return -EACCES;1247}1248}12491250return 0;1251}12521253static int doTruncate(std::shared_ptr<File>& file, off_t size) {1254auto dataFile = file->dynCast<DataFile>();12551256if (!dataFile) {1257return -EISDIR;1258}12591260auto locked = dataFile->locked();1261if (!(locked.getMode() & WASMFS_PERM_WRITE)) {1262return -EACCES;1263}12641265if (size < 0) {1266return -EINVAL;1267}12681269int ret = locked.setSize(size);1270assert(ret <= 0);1271return ret;1272}12731274int __syscall_truncate64(intptr_t path, off_t size) {1275auto parsed = path::parseFile((char*)path);1276if (auto err = parsed.getError()) {1277return err;1278}1279return doTruncate(parsed.getFile(), size);1280}12811282int __syscall_ftruncate64(int fd, off_t size) {1283auto openFile = wasmFS.getFileTable().locked().getEntry(fd);1284if (!openFile) {1285return -EBADF;1286}1287auto ret = doTruncate(openFile->locked().getFile(), size);1288// XXX It is not clear from the docs why ftruncate would differ from1289// truncate here. However, on Linux this definitely happens, and the old1290// FS matches that as well, so do the same here.1291if (ret == -EACCES) {1292ret = -EINVAL;1293}1294return ret;1295}12961297static bool isTTY(std::shared_ptr<File>& file) {1298// TODO: Full TTY support. For now, just see stdin/out/err as terminals and1299// nothing else.1300return file == SpecialFiles::getStdin() ||1301file == SpecialFiles::getStdout() || file == SpecialFiles::getStderr();1302}13031304int __syscall_ioctl(int fd, int request, ...) {1305auto openFile = wasmFS.getFileTable().locked().getEntry(fd);1306if (!openFile) {1307return -EBADF;1308}1309if (!isTTY(openFile->locked().getFile())) {1310return -ENOTTY;1311}1312// TODO: Full TTY support. For now this is limited, and matches the old FS.1313switch (request) {1314case TCGETA:1315case TCGETS:1316case TCSETA:1317case TCSETAW:1318case TCSETAF:1319case TCSETS:1320case TCSETSW:1321case TCSETSF:1322case TIOCGWINSZ:1323case TIOCSWINSZ: {1324// TTY operations that we do nothing for anyhow can just be ignored.1325return 0;1326}1327default: {1328return -EINVAL; // not supported1329}1330}1331}13321333int __syscall_pipe(intptr_t fd) {1334auto* fds = (__wasi_fd_t*)fd;13351336// Make a pipe: Two PipeFiles that share a single data source between them, so1337// that writing to one can be read in the other.1338//1339// No backend is needed here, so pass in nullptr for that.1340auto data = std::make_shared<PipeData>();1341auto reader = std::make_shared<PipeFile>(S_IRUGO, data);1342auto writer = std::make_shared<PipeFile>(S_IWUGO, data);13431344std::shared_ptr<OpenFileState> openReader, openWriter;1345(void)OpenFileState::create(reader, O_RDONLY, openReader);1346(void)OpenFileState::create(writer, O_WRONLY, openWriter);13471348auto fileTable = wasmFS.getFileTable().locked();1349fds[0] = fileTable.addEntry(openReader);1350fds[1] = fileTable.addEntry(openWriter);13511352return 0;1353}13541355// int poll(struct pollfd* fds, nfds_t nfds, int timeout);1356int __syscall_poll(intptr_t fds_, int nfds, int timeout) {1357struct pollfd* fds = (struct pollfd*)fds_;1358auto fileTable = wasmFS.getFileTable().locked();13591360// Process the list of FDs and compute their revents masks. Count the number1361// of nonzero such masks, which is our return value.1362int nonzero = 0;1363for (nfds_t i = 0; i < nfds; i++) {1364auto* pollfd = &fds[i];1365auto fd = pollfd->fd;1366if (fd < 0) {1367// Negative FDs are ignored in poll().1368pollfd->revents = 0;1369continue;1370}1371// Assume invalid, unless there is an open file.1372auto mask = POLLNVAL;1373auto openFile = fileTable.getEntry(fd);1374if (openFile) {1375mask = 0;1376auto flags = openFile->locked().getFlags();1377auto accessMode = flags & O_ACCMODE;1378auto readBit = pollfd->events & POLLOUT;1379if (readBit && (accessMode == O_WRONLY || accessMode == O_RDWR)) {1380mask |= readBit;1381}1382auto writeBit = pollfd->events & POLLIN;1383if (writeBit && (accessMode == O_RDONLY || accessMode == O_RDWR)) {1384// If there is data in the file, then there is also the ability to read.1385// TODO: Does this need to consider the position as well? That is, if1386// the position is at the end, we can't read from the current position1387// at least. If we update this, make sure the size isn't an error!1388if (openFile->locked().getFile()->locked().getSize() > 0) {1389mask |= writeBit;1390}1391}1392// TODO: get mask from File dynamically using a poll() hook?1393}1394// TODO: set the state based on the state of the other end of the pipe, for1395// pipes (POLLERR | POLLHUP)1396if (mask) {1397nonzero++;1398}1399pollfd->revents = mask;1400}1401// TODO: This should block based on the timeout. The old FS did not do so due1402// to web limitations, which we should perhaps revisit (especially with1403// pthreads and asyncify).1404return nonzero;1405}14061407int __syscall_fallocate(int fd, int mode, off_t offset, off_t len) {1408assert(mode == 0); // TODO, but other modes were never supported in the old FS14091410auto fileTable = wasmFS.getFileTable().locked();1411auto openFile = fileTable.getEntry(fd);1412if (!openFile) {1413return -EBADF;1414}14151416auto dataFile = openFile->locked().getFile()->dynCast<DataFile>();1417// TODO: support for symlinks.1418if (!dataFile) {1419return -ENODEV;1420}14211422auto locked = dataFile->locked();1423if (!(locked.getMode() & WASMFS_PERM_WRITE)) {1424return -EBADF;1425}14261427if (offset < 0 || len <= 0) {1428return -EINVAL;1429}14301431// TODO: We could only fill zeros for regions that were completely unused1432// before, which for a backend with sparse data storage could make a1433// difference. For that we'd need a new backend API.1434auto newNeededSize = offset + len;1435off_t size = locked.getSize();1436if (size < 0) {1437return size;1438}1439if (newNeededSize > size) {1440if (auto err = locked.setSize(newNeededSize)) {1441assert(err < 0);1442return err;1443}1444}14451446return 0;1447}14481449int __syscall_fcntl64(int fd, int cmd, ...) {1450auto fileTable = wasmFS.getFileTable().locked();1451auto openFile = fileTable.getEntry(fd);1452if (!openFile) {1453return -EBADF;1454}14551456switch (cmd) {1457case F_DUPFD: {1458int newfd;1459va_list v1;1460va_start(v1, cmd);1461newfd = va_arg(v1, int);1462va_end(v1);1463if (newfd < 0) {1464return -EINVAL;1465}14661467// Find the first available fd at arg or after.1468// TODO: Should we check for a limit on the max FD number, if we have one?1469while (1) {1470if (!fileTable.getEntry(newfd)) {1471(void)fileTable.setEntry(newfd, openFile);1472return newfd;1473}1474newfd++;1475}1476}1477case F_GETFD:1478case F_SETFD:1479// FD_CLOEXEC makes no sense for a single process.1480return 0;1481case F_GETFL:1482return openFile->locked().getFlags();1483case F_SETFL: {1484int flags;1485va_list v1;1486va_start(v1, cmd);1487flags = va_arg(v1, int);1488va_end(v1);1489// This syscall should ignore most flags.1490flags = flags & ~(O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL |1491O_NOCTTY | O_TRUNC);1492// Also ignore this flag which musl always adds constantly, but does not1493// matter for us.1494flags = flags & ~O_LARGEFILE;1495// On linux only a few flags can be modified, and we support only a subset1496// of those. Error on anything else.1497auto supportedFlags = flags & O_APPEND;1498if (flags != supportedFlags) {1499return -EINVAL;1500}1501openFile->locked().setFlags(flags);1502return 0;1503}1504case F_GETLK: {1505// If these constants differ then we'd need a case for both.1506static_assert(F_GETLK == F_GETLK64);1507flock* data;1508va_list v1;1509va_start(v1, cmd);1510data = va_arg(v1, flock*);1511va_end(v1);1512// We're always unlocked for now, until we implement byte-range locks.1513data->l_type = F_UNLCK;1514return 0;1515}1516case F_SETLK:1517case F_SETLKW: {1518static_assert(F_SETLK == F_SETLK64);1519static_assert(F_SETLKW == F_SETLKW64);1520// Pretend that the locking is successful. These are process-level locks,1521// and Emscripten programs are a single process. If we supported linking a1522// filesystem between programs, we'd need to do more here.1523// See https://github.com/emscripten-core/emscripten/issues/236971524return 0;1525}1526default: {1527// TODO: support any remaining cmds1528return -EINVAL;1529}1530}1531}15321533static int1534doStatFS(std::shared_ptr<File>& file, size_t size, struct statfs* buf) {1535if (size != sizeof(struct statfs)) {1536// We only know how to write to a standard statfs, not even a truncated one.1537return -EINVAL;1538}15391540// NOTE: None of the constants here are true. We're just returning safe and1541// sane values, that match the long-existing JS FS behavior (except for1542// the inode number, where we can do better).1543buf->f_type = 0;1544buf->f_bsize = 4096;1545buf->f_frsize = 4096;1546buf->f_blocks = 1000000;1547buf->f_bfree = 500000;1548buf->f_bavail = 500000;1549buf->f_files = file->getIno();1550buf->f_ffree = 1000000;1551buf->f_fsid = {0, 0};1552buf->f_flags = ST_NOSUID;1553buf->f_namelen = 255;1554return 0;1555}15561557int __syscall_statfs64(intptr_t path, size_t size, intptr_t buf) {1558auto parsed = path::parseFile((char*)path);1559if (auto err = parsed.getError()) {1560return err;1561}1562return doStatFS(parsed.getFile(), size, (struct statfs*)buf);1563}15641565int __syscall_fstatfs64(int fd, size_t size, intptr_t buf) {1566auto openFile = wasmFS.getFileTable().locked().getEntry(fd);1567if (!openFile) {1568return -EBADF;1569}1570return doStatFS(openFile->locked().getFile(), size, (struct statfs*)buf);1571}15721573int _mmap_js(size_t length,1574int prot,1575int flags,1576int fd,1577off_t offset,1578int* allocated,1579void** addr) {1580// PROT_EXEC is not supported (although we pretend to support the absence of1581// PROT_READ or PROT_WRITE).1582if ((prot & PROT_EXEC)) {1583return -EPERM;1584}15851586if (!length) {1587return -EINVAL;1588}15891590// One of MAP_PRIVATE, MAP_SHARED, or MAP_SHARED_VALIDATE must be used.1591int mapType = flags & MAP_TYPE;1592if (mapType != MAP_PRIVATE && mapType != MAP_SHARED &&1593mapType != MAP_SHARED_VALIDATE) {1594return -EINVAL;1595}15961597if (mapType == MAP_SHARED_VALIDATE) {1598WASMFS_UNREACHABLE("TODO: MAP_SHARED_VALIDATE");1599}16001601auto openFile = wasmFS.getFileTable().locked().getEntry(fd);1602if (!openFile) {1603return -EBADF;1604}16051606std::shared_ptr<DataFile> file;16071608// Keep the open file info locked only for as long as we need that.1609{1610auto lockedOpenFile = openFile->locked();16111612// Check permissions. We always need read permissions, since we need to read1613// the data in the file to map it.1614if ((lockedOpenFile.getFlags() & O_ACCMODE) == O_WRONLY) {1615return -EACCES;1616}16171618// According to the POSIX spec it is possible to write to a file opened in1619// read-only mode with MAP_PRIVATE flag, as all modifications will be1620// visible only in the memory of the current process.1621if ((prot & PROT_WRITE) != 0 && mapType != MAP_PRIVATE &&1622(lockedOpenFile.getFlags() & O_ACCMODE) != O_RDWR) {1623return -EACCES;1624}16251626file = lockedOpenFile.getFile()->dynCast<DataFile>();1627}16281629if (!file) {1630return -ENODEV;1631}16321633// TODO: On MAP_SHARED, install the mapping on the DataFile object itself so1634// that reads and writes can be redirected to the mapped region and so that1635// the mapping can correctly outlive the file being closed. This will require1636// changes to emscripten_mmap.c as well.16371638// Align to a wasm page size, as we expect in the future to get wasm1639// primitives to do this work, and those would presumably be aligned to a page1640// size. Aligning now avoids confusion later.1641uint8_t* ptr = (uint8_t*)emscripten_builtin_memalign(WASM_PAGE_SIZE, length);1642if (!ptr) {1643return -ENOMEM;1644}16451646auto nread = file->locked().read(ptr, length, offset);1647if (nread < 0) {1648// The read failed. Report the error, but first free the allocation.1649emscripten_builtin_free(ptr);1650return nread;1651}16521653// From here on, we have succeeded, and can mark the allocation as having1654// occurred (which means that the caller has the responsibility to free it).1655*allocated = true;1656*addr = (void*)ptr;16571658// The read must be of a valid amount, or we have had an internal logic error.1659assert(nread <= length);16601661// mmap clears any extra bytes after the data itself.1662memset(ptr + nread, 0, length - nread);16631664return 0;1665}16661667int _msync_js(1668intptr_t addr, size_t length, int prot, int flags, int fd, off_t offset) {1669// TODO: This is not correct! Mappings should be associated with files, not1670// fds. Only need to sync if shared and writes are allowed.1671int mapType = flags & MAP_TYPE;1672if (mapType == MAP_SHARED && (prot & PROT_WRITE)) {1673__wasi_ciovec_t iovec;1674iovec.buf = (uint8_t*)addr;1675iovec.buf_len = length;1676__wasi_size_t nwritten;1677// Translate from WASI positive error codes to negative error codes.1678return -__wasi_fd_pwrite(fd, &iovec, 1, offset, &nwritten);1679}1680return 0;1681}16821683int _munmap_js(1684intptr_t addr, size_t length, int prot, int flags, int fd, off_t offset) {1685// TODO: This is not correct! Mappings should be associated with files, not1686// fds.1687// TODO: Syncing should probably be handled in __syscall_munmap instead.1688return _msync_js(addr, length, prot, flags, fd, offset);1689}16901691// Stubs (at least for now)16921693int __syscall_accept4(int sockfd,1694intptr_t addr,1695intptr_t addrlen,1696int flags,1697int dummy1,1698int dummy2) {1699return -ENOSYS;1700}17011702int __syscall_bind(1703int sockfd, intptr_t addr, size_t alen, int dummy, int dummy2, int dummy3) {1704return -ENOSYS;1705}17061707int __syscall_connect(1708int sockfd, intptr_t addr, size_t len, int dummy, int dummy2, int dummy3) {1709return -ENOSYS;1710}17111712int __syscall_socket(1713int domain, int type, int protocol, int dummy1, int dummy2, int dummy3) {1714return -ENOSYS;1715}17161717int __syscall_listen(1718int sockfd, int backlog, int dummy1, int dummy2, int dummy3, int dummy4) {1719return -ENOSYS;1720}17211722int __syscall_getsockopt(int sockfd,1723int level,1724int optname,1725intptr_t optval,1726intptr_t optlen,1727int dummy) {1728return -ENOSYS;1729}17301731int __syscall_getsockname(1732int sockfd, intptr_t addr, intptr_t len, int dummy, int dummy2, int dummy3) {1733return -ENOSYS;1734}17351736int __syscall_getpeername(1737int sockfd, intptr_t addr, intptr_t len, int dummy, int dummy2, int dummy3) {1738return -ENOSYS;1739}17401741int __syscall_sendto(1742int sockfd, intptr_t msg, size_t len, int flags, intptr_t addr, size_t alen) {1743return -ENOSYS;1744}17451746int __syscall_sendmsg(1747int sockfd, intptr_t msg, int flags, intptr_t addr, size_t alen, int dummy) {1748return -ENOSYS;1749}17501751int __syscall_recvfrom(int sockfd,1752intptr_t msg,1753size_t len,1754int flags,1755intptr_t addr,1756intptr_t alen) {1757return -ENOSYS;1758}17591760int __syscall_recvmsg(1761int sockfd, intptr_t msg, int flags, int dummy, int dummy2, int dummy3) {1762return -ENOSYS;1763}17641765int __syscall_fadvise64(int fd, off_t offset, off_t length, int advice) {1766// Advice is currently ignored. TODO some backends might use it1767return 0;1768}17691770} // extern "C"177117721773