blob: fa1a3f153ca65fd6378255fb55001e805dcdd0e4 [file] [log] [blame]
// 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.
// wasmfs.cpp will implement a new file system that replaces the existing JS
// filesystem. Current Status: Work in Progress. See
// https://github.com/emscripten-core/emscripten/issues/15041.
#include "file.h"
#include "file_table.h"
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <errno.h>
#include <mutex>
#include <stdlib.h>
#include <sys/stat.h>
#include <utility>
#include <vector>
#include <wasi/api.h>
extern "C" {
using namespace wasmfs;
long __syscall_dup2(long oldfd, long newfd) {
auto fileTable = FileTable::get();
auto oldOpenFile = fileTable[oldfd];
// If oldfd is not a valid file descriptor, then the call fails,
// and newfd is not closed.
if (!oldOpenFile) {
return -(EBADF);
}
if (newfd < 0) {
return -(EBADF);
}
if (oldfd == newfd) {
return oldfd;
}
// If the file descriptor newfd was previously open, it will just be
// overwritten silently.
fileTable[newfd] = oldOpenFile.unlocked();
return newfd;
}
long __syscall_dup(long fd) {
auto fileTable = FileTable::get();
// Check that an open file exists corresponding to the given fd.
auto openFile = fileTable[fd];
if (!openFile) {
return -(EBADF);
}
return fileTable.add(openFile.unlocked());
}
// This enum specifies whether file offset will be provided by the open file
// state or provided by argument in the case of pread or pwrite.
enum class OffsetHandling { OpenFileState, Argument };
// Internal write function called by __wasi_fd_write and __wasi_fd_pwrite
// Receives an open file state offset.
// Optionally sets open file state offset.
static __wasi_errno_t writeAtOffset(OffsetHandling setOffset,
__wasi_fd_t fd,
const __wasi_ciovec_t* iovs,
size_t iovs_len,
__wasi_size_t* nwritten,
__wasi_filesize_t offset = 0) {
if (iovs_len < 0 || offset < 0) {
return __WASI_ERRNO_INVAL;
}
auto openFile = FileTable::get()[fd];
if (!openFile) {
return __WASI_ERRNO_BADF;
}
auto lockedOpenFile = openFile.locked();
auto* file = lockedOpenFile.getFile()->dynCast<DataFile>();
// If file is nullptr, then the file was not a DataFile.
// TODO: change to add support for symlinks.
if (!file) {
return __WASI_ERRNO_ISDIR;
}
auto lockedFile = file->locked();
off_t currOffset = setOffset == OffsetHandling::OpenFileState
? lockedOpenFile.position()
: offset;
off_t oldOffset = currOffset;
auto finish = [&] {
*nwritten = currOffset - oldOffset;
if (setOffset == OffsetHandling::OpenFileState) {
lockedOpenFile.position() = currOffset;
}
};
for (size_t i = 0; i < iovs_len; i++) {
const uint8_t* buf = iovs[i].buf;
off_t len = iovs[i].buf_len;
// Check if the sum of the buf_len values overflows an off_t (63 bits).
if (addWillOverFlow(currOffset, len)) {
return __WASI_ERRNO_FBIG;
}
// Check if buf_len specifies a positive length buffer but buf is a
// null pointer
if (!buf && len > 0) {
return __WASI_ERRNO_INVAL;
}
auto result = lockedFile.write(buf, len, currOffset);
if (result != __WASI_ERRNO_SUCCESS) {
finish();
return result;
}
currOffset += len;
}
finish();
return __WASI_ERRNO_SUCCESS;
}
// Internal read function called by __wasi_fd_read and __wasi_fd_pread
// Receives an open file state offset.
// Optionally sets open file state offset.
static __wasi_errno_t readAtOffset(OffsetHandling setOffset,
__wasi_fd_t fd,
const __wasi_iovec_t* iovs,
size_t iovs_len,
__wasi_size_t* nread,
__wasi_filesize_t offset = 0) {
if (iovs_len < 0 || offset < 0) {
return __WASI_ERRNO_INVAL;
}
auto openFile = FileTable::get()[fd];
if (!openFile) {
return __WASI_ERRNO_BADF;
}
auto lockedOpenFile = openFile.locked();
auto* file = lockedOpenFile.getFile()->dynCast<DataFile>();
// If file is nullptr, then the file was not a DataFile.
// TODO: change to add support for symlinks.
if (!file) {
return __WASI_ERRNO_ISDIR;
}
auto lockedFile = file->locked();
off_t currOffset = setOffset == OffsetHandling::OpenFileState
? lockedOpenFile.position()
: offset;
off_t oldOffset = currOffset;
auto finish = [&] {
*nread = currOffset - oldOffset;
if (setOffset == OffsetHandling::OpenFileState) {
lockedOpenFile.position() = currOffset;
}
};
size_t size = lockedFile.size();
for (size_t i = 0; i < iovs_len; i++) {
// Check if currOffset has exceeded size of file data.
ssize_t dataLeft = size - currOffset;
if (dataLeft <= 0) {
break;
}
uint8_t* buf = iovs[i].buf;
// Check if buf_len specifies a positive length buffer
// but buf is a null pointer.
if (!buf && iovs[i].buf_len > 0) {
return __WASI_ERRNO_INVAL;
}
size_t bytesToRead = std::min(size_t(dataLeft), iovs[i].buf_len);
auto result = lockedFile.read(buf, bytesToRead, currOffset);
if (result != __WASI_ERRNO_SUCCESS) {
finish();
return result;
}
currOffset += bytesToRead;
}
finish();
return __WASI_ERRNO_SUCCESS;
}
__wasi_errno_t __wasi_fd_write(__wasi_fd_t fd,
const __wasi_ciovec_t* iovs,
size_t iovs_len,
__wasi_size_t* nwritten) {
return writeAtOffset(
OffsetHandling::OpenFileState, fd, iovs, iovs_len, nwritten);
}
__wasi_errno_t __wasi_fd_read(__wasi_fd_t fd,
const __wasi_iovec_t* iovs,
size_t iovs_len,
__wasi_size_t* nread) {
return readAtOffset(OffsetHandling::OpenFileState, fd, iovs, iovs_len, nread);
}
__wasi_errno_t __wasi_fd_pwrite(__wasi_fd_t fd,
const __wasi_ciovec_t* iovs,
size_t iovs_len,
__wasi_filesize_t offset,
__wasi_size_t* nwritten) {
return writeAtOffset(
OffsetHandling::Argument, fd, iovs, iovs_len, nwritten, offset);
}
__wasi_errno_t __wasi_fd_pread(__wasi_fd_t fd,
const __wasi_iovec_t* iovs,
size_t iovs_len,
__wasi_filesize_t offset,
__wasi_size_t* nread) {
return readAtOffset(
OffsetHandling::Argument, fd, iovs, iovs_len, nread, offset);
}
__wasi_errno_t __wasi_fd_close(__wasi_fd_t fd) {
auto fileTable = FileTable::get();
// Remove openFileState entry from fileTable.
fileTable[fd] = nullptr;
return __WASI_ERRNO_SUCCESS;
}
long __syscall_fstat64(long fd, long buf) {
auto openFile = FileTable::get()[fd];
if (!openFile) {
return -(EBADF);
}
auto file = openFile.locked().getFile();
struct stat* buffer = (struct stat*)buf;
auto lockedFile = file->locked();
if (file->is<Directory>()) {
buffer->st_size = 4096;
} else if (file->is<DataFile>()) {
buffer->st_size = lockedFile.size();
} else { // TODO: add size of symlinks
buffer->st_size = 0;
}
// ATTN: hard-coded constant values are copied from the existing JS file
// system. Specific values were chosen to match existing library_fs.js values.
buffer->st_dev =
1; // ID of device containing file: Hardcode 1 for now, no meaning at the
// moment for Emscripten.
buffer->st_mode = lockedFile.mode();
buffer->st_ino = fd;
// The number of hard links is 1 since they are unsupported.
buffer->st_nlink = 1;
buffer->st_uid = 0;
buffer->st_gid = 0;
buffer->st_rdev =
1; // Device ID (if special file) No meaning right now for Emscripten.
// The syscall docs state this is hardcoded to # of 512 byte blocks.
buffer->st_blocks = (buffer->st_size + 511) / 512;
buffer->st_blksize =
4096; // Specifies the preferred blocksize for efficient disk I/O.
buffer->st_atim.tv_sec = lockedFile.atime();
buffer->st_mtim.tv_sec = lockedFile.mtime();
buffer->st_ctim.tv_sec = lockedFile.ctime();
return __WASI_ERRNO_SUCCESS;
}
__wasi_fd_t __syscall_open(long pathname, long flags, long mode) {
int accessMode = (flags & O_ACCMODE);
bool canWrite = false;
if (accessMode == O_WRONLY || accessMode == O_RDWR) {
canWrite = true;
}
// TODO: remove assert when all functionality is complete.
assert(((flags) & ~(O_CREAT | O_EXCL | O_DIRECTORY | O_TRUNC | O_APPEND |
O_RDWR | O_WRONLY | O_RDONLY | O_LARGEFILE)) == 0);
std::vector<std::string> pathParts;
char newPathName[strlen((char*)pathname) + 1];
strcpy(newPathName, (char*)pathname);
// TODO: Support relative paths. i.e. specify cwd if path is relative.
// TODO: Other path parsing edge cases.
char* current;
current = strtok(newPathName, "/\n");
while (current != NULL) {
pathParts.push_back(current);
current = strtok(NULL, "/\n");
}
std::shared_ptr<File> curr = getRootDirectory();
for (int i = 0; i < pathParts.size(); i++) {
auto directory = curr->dynCast<Directory>();
// If file is nullptr, then the file was not a Directory.
// TODO: Change this to accommodate symlinks
if (!directory) {
return -(ENOTDIR);
}
// Find the next entry in the current directory entry
#ifdef WASMFS_DEBUG
directory->locked().printKeys();
#endif
curr = directory->locked().getEntry(pathParts[i]);
// Requested entry (file or directory)
if (!curr) {
// Create last element in path if O_CREAT is specified.
if (i == pathParts.size() - 1 && flags & O_CREAT) {
auto lockedDir = directory->locked();
// Create an empty in-memory file.
auto created = std::make_shared<MemoryFile>(mode);
lockedDir.setEntry(pathParts[i], created);
auto openFile = std::make_shared<OpenFileState>(0, flags, created);
return FileTable::get().add(openFile);
} else {
return -(ENOENT);
}
}
#ifdef WASMFS_DEBUG
std::vector<char> temp(pathParts[i].begin(), pathParts[i].end());
emscripten_console_log(&temp[0]);
#endif
}
// Fail if O_DIRECTORY is specified and pathname is not a directory
if (flags & O_DIRECTORY && !curr->is<Directory>()) {
return -(ENOTDIR);
}
// Return an error if the file exists and O_CREAT and O_EXCL are specified.
if (flags & O_EXCL && flags & O_CREAT) {
return -(EEXIST);
}
auto openFile = std::make_shared<OpenFileState>(0, flags, curr);
return FileTable::get().add(openFile);
}
__wasi_errno_t __wasi_fd_seek(__wasi_fd_t fd,
__wasi_filedelta_t offset,
__wasi_whence_t whence,
__wasi_filesize_t* newoffset) {
auto openFile = FileTable::get()[fd];
if (!openFile) {
return __WASI_ERRNO_BADF;
}
auto lockedOpenFile = openFile.locked();
off_t position;
if (whence == SEEK_SET) {
position = offset;
} else if (whence == SEEK_CUR) {
position = lockedOpenFile.position() + offset;
} else if (whence == SEEK_END) {
// Only the open file stat is altered in seek. Locking the underlying data
// file here once is sufficient.
position = lockedOpenFile.getFile()->locked().size() + offset;
} else {
return __WASI_ERRNO_INVAL;
}
if (position < 0) {
return __WASI_ERRNO_INVAL;
}
lockedOpenFile.position() = position;
if (newoffset) {
*newoffset = position;
}
return __WASI_ERRNO_SUCCESS;
}
}