blob: 5a981000b97ff3f7eade31fee32d11758633d5ee [file] [edit]
// 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.
// syscalls.cpp will implement the syscalls of the new file system replacing the
// old JS version. Current Status: Work in Progress. See
// https://github.com/emscripten-core/emscripten/issues/15041.
#include "backend.h"
#include "file.h"
#include "file_table.h"
#include "wasmfs.h"
#include <dirent.h>
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <errno.h>
#include <mutex>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <utility>
#include <vector>
#include <wasi/api.h>
// File permission macros for wasmfs.
// Used to improve readability compared to those in stat.h
#define WASMFS_PERM_WRITE 0222
extern "C" {
using namespace wasmfs;
// Copy the file specified by the pathname into JS.
// Return a pointer to the JS buffer in HEAPU8.
// The buffer will also contain the file length.
// Caller must free the returned pointer.
void* emscripten_wasmfs_read_file(char* path) {
struct stat file;
int err = 0;
err = stat(path, &file);
if (err < 0) {
emscripten_console_error("Fatal error in FS.readFile");
abort();
}
// The function will return a pointer to a buffer with the file length in the
// first 8 bytes. The remaining bytes will contain the buffer contents. This
// allows the caller to use HEAPU8.subarray(buf + 8, buf + 8 + length).
off_t size = file.st_size;
uint8_t* result = (uint8_t*)malloc((size + sizeof(size)));
*(uint32_t*)result = size;
int fd = open(path, O_RDONLY);
if (fd < 0) {
emscripten_console_error("Fatal error in FS.readFile");
abort();
}
int numRead = pread(fd, result + sizeof(size), size, 0);
// TODO: Generalize this so that it is thread-proof.
// Must guarantee that the file size has not changed by the time it is read.
assert(numRead == size);
err = close(fd);
if (err < 0) {
emscripten_console_error("Fatal error in FS.readFile");
abort();
}
return result;
}
long __syscall_dup3(long oldfd, long newfd, long flags) {
auto fileTable = wasmFS.getLockedFileTable();
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 -EINVAL;
}
// 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 = wasmFS.getLockedFileTable();
// 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 = wasmFS.getLockedFileTable()[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 = wasmFS.getLockedFileTable()[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.getSize();
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 = wasmFS.getLockedFileTable();
// Remove openFileState entry from fileTable.
fileTable[fd] = nullptr;
return __WASI_ERRNO_SUCCESS;
}
static long doStat(std::shared_ptr<File> file, struct stat* buffer) {
auto lockedFile = file->locked();
buffer->st_size = lockedFile.getSize();
// 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();
// TODO: Add mode for symlinks.
if (file->is<Directory>()) {
buffer->st_mode |= S_IFDIR;
} else if (file->is<DataFile>()) {
buffer->st_mode |= S_IFREG;
}
buffer->st_ino = file->getIno();
// 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;
}
long __syscall_stat64(long path, long buf) {
auto pathParts = splitPath((char*)path);
if (pathParts.empty()) {
return -EINVAL;
}
auto base = pathParts.back();
long err;
auto parentDir = getDir(pathParts.begin(), pathParts.end() - 1, err);
// Parent node doesn't exist.
if (!parentDir) {
return err;
}
auto lockedParentDir = parentDir->locked();
// TODO: In future PR, edit function to just return the requested file instead
// of having to first obtain the parent dir.
auto curr = lockedParentDir.getEntry(base);
if (curr) {
struct stat* buffer = (struct stat*)buf;
return doStat(curr, buffer);
} else {
return -ENOENT;
}
}
long __syscall_lstat64(long path, long buf) {
// TODO: When symlinks are introduced, lstat will return information about the
// link itself rather than the file it refers to.
return __syscall_stat64(path, buf);
}
long __syscall_fstat64(long fd, long buf) {
auto openFile = wasmFS.getLockedFileTable()[fd];
if (!openFile) {
return -EBADF;
}
struct stat* buffer = (struct stat*)buf;
return doStat(openFile.locked().getFile(), buffer);
}
__wasi_fd_t __syscall_open(long pathname, long flags, ...) {
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 | O_CLOEXEC)) == 0);
auto pathParts = splitPath((char*)pathname);
if (pathParts.empty()) {
return -EINVAL;
}
auto base = pathParts.back();
// Root directory
if (pathParts.size() == 1 && pathParts[0] == "/") {
auto openFile =
std::make_shared<OpenFileState>(0, flags, wasmFS.getRootDirectory());
return wasmFS.getLockedFileTable().add(openFile);
}
long err;
auto parentDir = getDir(pathParts.begin(), pathParts.end() - 1, err);
// Parent node doesn't exist.
if (!parentDir) {
return err;
}
auto lockedParentDir = parentDir->locked();
auto curr = lockedParentDir.getEntry(base);
// The requested node was not found.
if (!curr) {
// If curr is the last element and the create flag is specified
// If O_DIRECTORY is also specified, still create a regular file:
// https://man7.org/linux/man-pages/man2/open.2.html#BUGS
if (flags & O_CREAT) {
// Since mode is optional, mode is specified using varargs.
mode_t mode = 0;
va_list vl;
va_start(vl, flags);
mode = va_arg(vl, int);
va_end(vl);
// Mask all permissions sent via mode.
mode &= S_IALLUGO;
// Create an empty in-memory file.
auto backend = lockedParentDir.getBackend();
auto created = backend->createFile(mode);
// TODO: When rename is implemented make sure that one can atomically
// remove the file from the source directory and then set its parent to
// the dest directory.
lockedParentDir.setEntry(base, created);
auto openFile = std::make_shared<OpenFileState>(0, flags, created);
return wasmFS.getLockedFileTable().add(openFile);
} else {
return -ENOENT;
}
}
// 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 wasmFS.getLockedFileTable().add(openFile);
}
long __syscall_mkdir(long path, long mode) {
auto pathParts = splitPath((char*)path);
if (pathParts.empty()) {
return -EINVAL;
}
// Root (/) directory.
if (pathParts.empty() || pathParts.size() == 1 && pathParts[0] == "/") {
return -EEXIST;
}
auto base = pathParts.back();
long err;
auto parentDir = getDir(pathParts.begin(), pathParts.end() - 1, err);
if (!parentDir) {
// parent node doesn't exist
return err;
}
auto lockedParentDir = parentDir->locked();
auto curr = lockedParentDir.getEntry(base);
// Check if the requested directory already exists.
if (curr) {
return -EEXIST;
} else {
// Mask rwx permissions for user, group and others, and the sticky bit.
// This prevents users from entering S_IFREG for example.
// https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html
mode &= S_IRWXUGO | S_ISVTX;
// Create an empty in-memory directory.
auto backend = lockedParentDir.getBackend();
auto created = backend->createDirectory(mode);
lockedParentDir.setEntry(base, created);
return 0;
}
}
__wasi_errno_t __wasi_fd_seek(__wasi_fd_t fd,
__wasi_filedelta_t offset,
__wasi_whence_t whence,
__wasi_filesize_t* newoffset) {
auto openFile = wasmFS.getLockedFileTable()[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 state is altered in seek. Locking the underlying
// data file here once is sufficient.
position = lockedOpenFile.getFile()->locked().getSize() + offset;
} else {
return __WASI_ERRNO_INVAL;
}
if (position < 0) {
return __WASI_ERRNO_INVAL;
}
lockedOpenFile.position() = position;
if (newoffset) {
*newoffset = position;
}
return __WASI_ERRNO_SUCCESS;
}
long __syscall_chdir(long path) {
auto pathParts = splitPath((char*)path);
if (pathParts.empty()) {
return -ENOENT;
}
long err;
auto dir = getDir(pathParts.begin(), pathParts.end(), err);
if (!dir) {
return err;
}
wasmFS.setCWD(dir);
return 0;
}
long __syscall_getcwd(long buf, long size) {
// Check if buf points to a bad address.
if (!buf && size > 0) {
return -EFAULT;
}
// Check if the size argument is zero and buf is not a null pointer.
if (buf && size == 0) {
return -EINVAL;
}
auto curr = wasmFS.getCWD();
std::string result = "";
while (curr != wasmFS.getRootDirectory()) {
auto parent = curr->locked().getParent();
// Check if the parent exists. The parent may not exist if the CWD or one
// of its ancestors has been unlinked.
if (!parent) {
return -ENOENT;
}
auto parentDir = parent->dynCast<Directory>();
auto name = parentDir->locked().getName(curr);
result = '/' + name + result;
curr = parentDir;
}
// Check if the cwd is the root directory.
if (result.empty()) {
result = "/";
}
auto res = result.c_str();
// Check if the size argument is less than the length of the absolute
// pathname of the working directory, including null terminator.
if (strlen(res) >= size - 1) {
return -ENAMETOOLONG;
}
// Return value is a null-terminated c string.
strcpy((char*)buf, res);
return 0;
}
__wasi_errno_t __wasi_fd_fdstat_get(__wasi_fd_t fd, __wasi_fdstat_t* stat) {
// TODO: This is only partial implementation of __wasi_fd_fdstat_get. Enough
// to get __wasi_fd_is_valid working.
// There are other fields in the stat structure that we should really
// be filling in here.
auto openFile = wasmFS.getLockedFileTable()[fd];
if (!openFile) {
return __WASI_ERRNO_BADF;
}
if (openFile.locked().getFile()->is<Directory>()) {
stat->fs_filetype = __WASI_FILETYPE_DIRECTORY;
} else {
stat->fs_filetype = __WASI_FILETYPE_REGULAR_FILE;
}
return __WASI_ERRNO_SUCCESS;
}
// This enum specifies whether rmdir or unlink is being performed.
enum class UnlinkMode { Rmdir, Unlink };
static long doUnlink(char* path, UnlinkMode unlinkMode) {
auto pathParts = splitPath(path);
// TODO: Ensure that . and .. are invalid when path parsing is updated.
// TODO: Change to check root directory pointer instead of path.
// This can be done when path parsing is refactored.
// Current state just matches JS file system behaviour.
if (pathParts.size() == 1 && pathParts[0] == "/") {
return -EBUSY;
}
if (pathParts.empty()) {
return -EINVAL;
}
auto base = pathParts.back();
long err;
auto parentDir = getDir(pathParts.begin(), pathParts.end() - 1, err);
// Parent node doesn't exist.
if (!parentDir) {
return err;
}
// Hold the locked directory to prevent the state from being changed during
// the operation.
auto lockedParentDir = parentDir->locked();
auto curr = lockedParentDir.getEntry(base);
if (!curr) {
return -ENOENT;
}
// rmdir checks if the target is a directory and if the directory is empty.
auto targetDir = curr->dynCast<Directory>();
if (unlinkMode == UnlinkMode::Rmdir) {
if (!targetDir) {
return -ENOTDIR;
}
// A directory can only be removed if it has zero entries.
if (targetDir->locked().getNumEntries() > 0) {
return -ENOTEMPTY;
}
} else {
// unlink cannot remove a directory.
if (targetDir) {
return -EISDIR;
}
}
// Cannot unlink/rmdir if the parent dir doesn't have write permissions.
if (!(lockedParentDir.mode() & WASMFS_PERM_WRITE)) {
return -EACCES;
}
// Input is valid, perform the unlink.
lockedParentDir.unlinkEntry(base);
return 0;
}
long __syscall_rmdir(long path) {
return doUnlink((char*)path, UnlinkMode::Rmdir);
}
long __syscall_unlink(long path) {
return doUnlink((char*)path, UnlinkMode::Unlink);
}
long __syscall_getdents64(long fd, long dirp, long count) {
auto openFile = wasmFS.getLockedFileTable()[fd];
if (!openFile) {
return -EBADF;
}
dirent* result = (dirent*)dirp;
// Check if the result buffer is too small.
if (count / sizeof(dirent) == 0) {
return -EINVAL;
}
auto file = openFile.locked().getFile();
auto directory = file->dynCast<Directory>();
if (!directory) {
return -ENOTDIR;
}
// Hold the locked directory to prevent the state from being changed during
// the operation.
auto lockedDir = directory->locked();
off_t bytesRead = 0;
// A directory's position corresponds to the index in its entries vector.
int index = openFile.locked().position();
// In the root directory, ".." refers to itself.
auto dotdot =
file == wasmFS.getRootDirectory() ? file : lockedDir.getParent();
// If the directory is unlinked then the parent pointer should be null.
if (!dotdot) {
return -ENOENT;
}
// There are always two hardcoded directories "." and ".."
std::vector<Directory::Entry> entries = {{".", file}, {"..", dotdot}};
auto dirEntries = lockedDir.getEntries();
entries.insert(entries.end(), dirEntries.begin(), dirEntries.end());
#ifdef WASMFS_DEBUG
for (auto pair : entries) {
emscripten_console_log(pair.name.c_str());
}
#endif
for (; index < entries.size() && bytesRead + sizeof(dirent) <= count;
index++) {
auto curr = entries[index];
result->d_ino = curr.file->getIno();
result->d_off = bytesRead + sizeof(dirent);
result->d_reclen = sizeof(dirent);
result->d_type =
curr.file->is<Directory>() ? DT_DIR : DT_REG; // TODO: add symlinks.
// TODO: Enforce that the name can fit in the d_name field.
assert(curr.name.size() + 1 <= sizeof(result->d_name));
strcpy(result->d_name, curr.name.c_str());
++result;
bytesRead += sizeof(dirent);
}
// Set the directory's offset position:
openFile.locked().position() = index;
return bytesRead;
}
// TODO: Revisit this syscall after refactoring file system locking strategy.
long __syscall_rename(long old_path, long new_path) {
// The rename syscall must be atomic to prevent other file system operations
// from concurrently changing the directories. If it were not atomic, then the
// file system could be left in an inconsistent state. For example, a
// rename("src/a","dest/b") and an rmdir("/dest") could conflict. Assume that
// the src/a file is unlinked first before moving it to the empty /dest
// directory. In the meantime, another thread could remove the empty /dest
// directory. src/a can still attach itself to the /dest directory since we
// have its shared_ptr, and the syscall would succeed. However, this may be
// unexpected for the user, who might have expected the subsequent
// rmdir("/dest") to fail since /dest is no longer empty. Both syscalls
// succeeding would result in a data loss, which would have been prevented if
// either of them was blocked by the other. Thus rename should guarantee that
// the order of operations is consistent.
// Trylocking on the new_path parent is needed in the case where two renames
// are operating on opposing sources and destinations. This would cause them
// to lock the directories in reverse order and could cause deadlock.
// Edge case: rename("dir", "dir/somename") - in this scenario it should not
// be possible to rename the destination if the source is an ancestor.
// Edge case: rename("dir/somename", "dir") - in this scenario it should not
// be possible to rename the destination since by definition it is
// non-empty.
auto oldPathParts = splitPath((char*)old_path);
if (oldPathParts.empty()) {
return -EINVAL;
}
// In Linux, renaming the root directory returns EBUSY.
// TODO: Fix this when path parsing is refactored.
std::vector<std::string> root = {"/"};
if (oldPathParts == root) {
return -EBUSY;
}
auto oldBase = oldPathParts.back();
long err;
auto oldParentDir = getDir(oldPathParts.begin(), oldPathParts.end() - 1, err);
if (!oldParentDir) {
return err;
}
// For this operation to be atomic we must lock the source parent directory.
auto lockedOldParentDir = oldParentDir->locked();
auto oldPath = lockedOldParentDir.getEntry(oldBase);
if (!oldPath) {
return -ENOENT;
}
// Obtain the destination parent directory to see if it exists.
auto newPathParts = splitPath((char*)new_path);
if (newPathParts.empty()) {
return -EINVAL;
}
// In Linux, renaming a directory to the root directory returns ENOTEMPTY.
// TODO: Fix this when path parsing is refactored.
if (newPathParts == root) {
return -ENOTEMPTY;
}
auto newBase = newPathParts.back();
// oldPath is the forbidden ancestor.
auto newParentDir =
getDir(newPathParts.begin(), newPathParts.end() - 1, err, oldPath);
// If the destination parent directory doesn't exist, the source file cannot
// be moved.
if (!newParentDir) {
return err;
}
// Edge case: a/b -> a/c share the same parent. Trylocking the same parent
// with a recursive mutex should work, so we don't need to treat that case
// specially here.
auto maybeLockedNewParentDir = newParentDir->maybeLocked();
if (!maybeLockedNewParentDir) {
return -EBUSY;
}
auto lockedNewParentDir = std::move(*maybeLockedNewParentDir);
auto newPath = lockedNewParentDir.getEntry(newBase);
// If old_path and new_path are the same, do nothing.
if (newPath == oldPath) {
return 0;
}
// Cannot move from source directory without write permissions.
if (!(lockedOldParentDir.mode() & WASMFS_PERM_WRITE)) {
return -EACCES;
}
// Cannot move to a destination parent directory without write permissions.
if (!(lockedNewParentDir.mode() & WASMFS_PERM_WRITE)) {
return -EACCES;
}
// new path must be removed if it exists.
if (newPath) {
if (oldPath->is<DataFile>()) {
// Cannot overwrite a file with a directory.
if (newPath->is<Directory>()) {
return -EISDIR;
}
} else if (oldPath->is<Directory>()) {
auto newPathDirectory = newPath->dynCast<Directory>();
// Cannot overwrite a directory with a file.
if (!newPathDirectory) {
return -ENOTDIR;
}
// This should also cover the case in where
// the destination is an ancestor of the source:
// rename("dir/subdir", "dir");
if (newPathDirectory->locked().getNumEntries() > 0) {
return -ENOTEMPTY;
}
} else {
assert(false && "Unhandled file kind in rename");
}
lockedNewParentDir.unlinkEntry(newBase);
}
// Unlink the oldpath and add the oldpath to the new parent dir.
lockedOldParentDir.unlinkEntry(oldBase);
lockedNewParentDir.setEntry(newBase, oldPath);
return 0;
}
}