[WASMFS] Mkdir Syscall (#15323)
Relevant Issue: #15041 - Implement mkdir and create relevant tests for the new file system.
This commit is contained in:
Родитель
051c5c1a8a
Коммит
c273f250d7
|
@ -34,6 +34,7 @@ __wasi_errno_t MemoryFile::write(const uint8_t* buf, size_t len, off_t offset) {
|
|||
}
|
||||
|
||||
__wasi_errno_t MemoryFile::read(uint8_t* buf, size_t len, off_t offset) {
|
||||
assert(offset + len - 1 < buffer.size());
|
||||
std::memcpy(buf, &buffer[offset], len);
|
||||
|
||||
return __WASI_ERRNO_SUCCESS;
|
||||
|
|
|
@ -67,7 +67,6 @@ public:
|
|||
Handle locked() { return Handle(shared_from_this()); }
|
||||
|
||||
protected:
|
||||
File(FileKind kind) : kind(kind) {}
|
||||
File(FileKind kind, mode_t mode) : kind(kind), mode(mode) {}
|
||||
// A mutex is needed for multiple accesses to the same file.
|
||||
std::mutex mutex;
|
||||
|
@ -91,7 +90,6 @@ class DataFile : public File {
|
|||
|
||||
public:
|
||||
static constexpr FileKind expectedKind = File::DataFileKind;
|
||||
DataFile() : File(File::DataFileKind) {}
|
||||
DataFile(mode_t mode) : File(File::DataFileKind, mode) {}
|
||||
virtual ~DataFile() = default;
|
||||
|
||||
|
@ -121,7 +119,7 @@ protected:
|
|||
|
||||
public:
|
||||
static constexpr FileKind expectedKind = File::DirectoryKind;
|
||||
Directory() : File(File::DirectoryKind) {}
|
||||
Directory(mode_t mode) : File(File::DirectoryKind, mode) {}
|
||||
|
||||
class Handle : public File::Handle {
|
||||
std::shared_ptr<Directory> getDir() { return file->cast<Directory>(); }
|
||||
|
|
|
@ -40,9 +40,10 @@ class StdinFile : public DataFile {
|
|||
};
|
||||
|
||||
public:
|
||||
StdinFile(mode_t mode) : DataFile(mode) {}
|
||||
static std::shared_ptr<StdinFile> getSingleton() {
|
||||
static const std::shared_ptr<StdinFile> stdinFile =
|
||||
std::make_shared<StdinFile>();
|
||||
std::make_shared<StdinFile>(S_IRUGO);
|
||||
return stdinFile;
|
||||
}
|
||||
};
|
||||
|
@ -59,9 +60,10 @@ class StdoutFile : public DataFile {
|
|||
};
|
||||
|
||||
public:
|
||||
StdoutFile(mode_t mode) : DataFile(mode) {}
|
||||
static std::shared_ptr<StdoutFile> getSingleton() {
|
||||
static const std::shared_ptr<StdoutFile> stdoutFile =
|
||||
std::make_shared<StdoutFile>();
|
||||
std::make_shared<StdoutFile>(S_IWUGO);
|
||||
return stdoutFile;
|
||||
}
|
||||
};
|
||||
|
@ -81,9 +83,10 @@ class StderrFile : public DataFile {
|
|||
};
|
||||
|
||||
public:
|
||||
StderrFile(mode_t mode) : DataFile(mode) {}
|
||||
static std::shared_ptr<StderrFile> getSingleton() {
|
||||
static const std::shared_ptr<StderrFile> stderrFile =
|
||||
std::make_shared<StderrFile>();
|
||||
std::make_shared<StderrFile>(S_IWUGO);
|
||||
return stderrFile;
|
||||
}
|
||||
};
|
||||
|
@ -101,8 +104,9 @@ FileTable::FileTable() {
|
|||
// Refers to same std streams in the open file table.
|
||||
std::shared_ptr<Directory> getRootDirectory() {
|
||||
static const std::shared_ptr<Directory> rootDirectory = [] {
|
||||
std::shared_ptr<Directory> rootDirectory = std::make_shared<Directory>();
|
||||
auto devDirectory = std::make_shared<Directory>();
|
||||
std::shared_ptr<Directory> rootDirectory =
|
||||
std::make_shared<Directory>(S_IRUGO | S_IXUGO);
|
||||
auto devDirectory = std::make_shared<Directory>(S_IRUGO | S_IXUGO);
|
||||
rootDirectory->locked().setEntry("dev", devDirectory);
|
||||
|
||||
auto dir = devDirectory->locked();
|
||||
|
@ -161,6 +165,76 @@ FileTable::Handle::add(std::shared_ptr<OpenFileState> openFileState) {
|
|||
return i;
|
||||
}
|
||||
}
|
||||
return -(EBADF);
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
std::vector<std::string> splitPath(char* pathname) {
|
||||
std::vector<std::string> pathParts;
|
||||
char newPathName[strlen(pathname) + 1];
|
||||
strcpy(newPathName, pathname);
|
||||
|
||||
// TODO: Support relative paths. i.e. specify cwd if path is relative.
|
||||
// TODO: Other path parsing edge cases.
|
||||
char* current;
|
||||
// Handle absolute path.
|
||||
if (newPathName[0] == '/') {
|
||||
pathParts.push_back("/");
|
||||
}
|
||||
|
||||
current = strtok(newPathName, "/");
|
||||
while (current != NULL) {
|
||||
pathParts.push_back(current);
|
||||
current = strtok(NULL, "/");
|
||||
}
|
||||
|
||||
return pathParts;
|
||||
}
|
||||
|
||||
std::shared_ptr<Directory> getDir(std::vector<std::string>::iterator begin,
|
||||
std::vector<std::string>::iterator end,
|
||||
long& err) {
|
||||
|
||||
std::shared_ptr<File> curr;
|
||||
// Check if the first path element is '/', indicating an absolute path.
|
||||
if (*begin == "/") {
|
||||
curr = getRootDirectory();
|
||||
begin++;
|
||||
}
|
||||
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
auto directory = curr->dynCast<Directory>();
|
||||
|
||||
// If file is nullptr, then the file was not a Directory.
|
||||
// TODO: Change this to accommodate symlinks
|
||||
if (!directory) {
|
||||
err = -ENOTDIR;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Find the next entry in the current directory entry
|
||||
#ifdef WASMFS_DEBUG
|
||||
directory->locked().printKeys();
|
||||
#endif
|
||||
curr = directory->locked().getEntry(*it);
|
||||
|
||||
// Requested entry (file or directory)
|
||||
if (!curr) {
|
||||
err = -ENOENT;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef WASMFS_DEBUG
|
||||
emscripten_console_log(it->c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
auto currDirectory = curr->dynCast<Directory>();
|
||||
|
||||
if (!currDirectory) {
|
||||
err = -ENOTDIR;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return currDirectory;
|
||||
}
|
||||
} // namespace wasmfs
|
||||
|
|
|
@ -29,24 +29,32 @@ template<typename T> bool addWillOverFlow(T a, T b) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Obtains parent directory of a given pathname.
|
||||
// Will return a nullptr if the parent is not a directory.
|
||||
std::shared_ptr<Directory> getDir(std::vector<std::string>::iterator begin,
|
||||
std::vector<std::string>::iterator end,
|
||||
long& err);
|
||||
|
||||
// Return a vector of the '/'-delimited components of a path. The first element
|
||||
// will be "/" iff the path is an absolute path.
|
||||
std::vector<std::string> splitPath(char* pathname);
|
||||
|
||||
// Access mode, file creation and file status flags for open.
|
||||
using wasmfs_oflags_t = uint32_t;
|
||||
using oflags_t = uint32_t;
|
||||
|
||||
std::shared_ptr<Directory> getRootDirectory();
|
||||
|
||||
class OpenFileState : public std::enable_shared_from_this<OpenFileState> {
|
||||
std::shared_ptr<File> file;
|
||||
off_t position;
|
||||
wasmfs_oflags_t flags; // RD_ONLY, WR_ONLY, RDWR
|
||||
oflags_t flags; // RD_ONLY, WR_ONLY, RDWR
|
||||
// An OpenFileState needs a mutex if there are concurrent accesses on one open
|
||||
// file descriptor. This could occur if there are multiple seeks on the same
|
||||
// open file descriptor.
|
||||
std::mutex mutex;
|
||||
|
||||
public:
|
||||
OpenFileState(size_t position,
|
||||
wasmfs_oflags_t flags,
|
||||
std::shared_ptr<File> file)
|
||||
OpenFileState(size_t position, oflags_t flags, std::shared_ptr<File> file)
|
||||
: position(position), flags(flags), file(file) {}
|
||||
|
||||
class Handle {
|
||||
|
|
|
@ -28,11 +28,11 @@ long __syscall_dup2(long oldfd, long newfd) {
|
|||
// If oldfd is not a valid file descriptor, then the call fails,
|
||||
// and newfd is not closed.
|
||||
if (!oldOpenFile) {
|
||||
return -(EBADF);
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
if (newfd < 0) {
|
||||
return -(EBADF);
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
if (oldfd == newfd) {
|
||||
|
@ -51,7 +51,7 @@ long __syscall_dup(long fd) {
|
|||
// Check that an open file exists corresponding to the given fd.
|
||||
auto openFile = fileTable[fd];
|
||||
if (!openFile) {
|
||||
return -(EBADF);
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
return fileTable.add(openFile.unlocked());
|
||||
|
@ -244,7 +244,7 @@ long __syscall_fstat64(long fd, long buf) {
|
|||
auto openFile = FileTable::get()[fd];
|
||||
|
||||
if (!openFile) {
|
||||
return -(EBADF);
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
auto file = openFile.locked().getFile();
|
||||
|
@ -298,72 +298,59 @@ __wasi_fd_t __syscall_open(long pathname, long flags, long mode) {
|
|||
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;
|
||||
auto pathParts = splitPath((char*)pathname);
|
||||
|
||||
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");
|
||||
if (pathParts.empty()) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
std::shared_ptr<File> curr = getRootDirectory();
|
||||
auto base = pathParts[pathParts.size() - 1];
|
||||
|
||||
for (int i = 0; i < pathParts.size(); i++) {
|
||||
auto directory = curr->dynCast<Directory>();
|
||||
// Root directory
|
||||
if (pathParts.size() == 1 && pathParts[0] == "/") {
|
||||
auto openFile =
|
||||
std::make_shared<OpenFileState>(0, flags, getRootDirectory());
|
||||
return FileTable::get().add(openFile);
|
||||
}
|
||||
|
||||
// If file is nullptr, then the file was not a Directory.
|
||||
// TODO: Change this to accommodate symlinks
|
||||
if (!directory) {
|
||||
return -(ENOTDIR);
|
||||
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) {
|
||||
// Create an empty in-memory file.
|
||||
auto created = std::make_shared<MemoryFile>(mode);
|
||||
|
||||
lockedParentDir.setEntry(base, created);
|
||||
auto openFile = std::make_shared<OpenFileState>(0, flags, created);
|
||||
|
||||
return FileTable::get().add(openFile);
|
||||
} else {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
// 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 O_DIRECTORY is also specified, still create a regular file:
|
||||
// https://man7.org/linux/man-pages/man2/open.2.html#BUGS
|
||||
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 -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);
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
auto openFile = std::make_shared<OpenFileState>(0, flags, curr);
|
||||
|
@ -371,6 +358,43 @@ __wasi_fd_t __syscall_open(long pathname, long flags, long mode) {
|
|||
return FileTable::get().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[pathParts.size() - 1];
|
||||
|
||||
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 {
|
||||
// Create an empty in-memory directory.
|
||||
auto created = std::make_shared<Directory>(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,
|
||||
|
|
|
@ -120,6 +120,7 @@ def with_wasmfs(f):
|
|||
def metafunc(self, wasmfs):
|
||||
if wasmfs:
|
||||
self.set_setting('WASMFS')
|
||||
self.emcc_args = self.emcc_args.copy() + ['-DWASMFS']
|
||||
f(self)
|
||||
else:
|
||||
f(self)
|
||||
|
@ -11180,3 +11181,7 @@ void foo() {}
|
|||
@with_wasmfs
|
||||
def test_unistd_seek(self):
|
||||
self.do_run_in_out_file_test('wasmfs/wasmfs_seek.c')
|
||||
|
||||
@with_wasmfs
|
||||
def test_unistd_mkdir(self):
|
||||
self.do_run_in_out_file_test('wasmfs/wasmfs_mkdir.c')
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// FIXME: Merge with unlink test
|
||||
|
||||
int main() {
|
||||
// Try to make a directory under the root directory.
|
||||
errno = 0;
|
||||
mkdir("/working", 0777);
|
||||
printf("Errno: %s\n", strerror(errno));
|
||||
assert(errno == 0);
|
||||
|
||||
// Try to create a file in the same directory.
|
||||
int fd = open("/working/test", O_RDWR | O_CREAT, 0777);
|
||||
printf("Errno: %s\n", strerror(errno));
|
||||
assert(errno == 0);
|
||||
|
||||
// Try to read and write to the same file.
|
||||
const char* msg = "Test\n";
|
||||
errno = 0;
|
||||
write(fd, msg, strlen(msg));
|
||||
assert(errno == 0);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
char buf[100] = {};
|
||||
errno = 0;
|
||||
read(fd, buf, sizeof(buf));
|
||||
assert(errno == 0);
|
||||
printf("%s", buf);
|
||||
close(fd);
|
||||
|
||||
// Try to make a directory with an empty pathname.
|
||||
errno = 0;
|
||||
mkdir("", 0777);
|
||||
printf("Errno: %s\n", strerror(errno));
|
||||
assert(errno == EINVAL);
|
||||
|
||||
// Try to make the root directory.
|
||||
errno = 0;
|
||||
mkdir("/", 0777);
|
||||
#ifdef WASMFS
|
||||
assert(errno == EEXIST);
|
||||
#else
|
||||
assert(errno == EINVAL);
|
||||
#endif
|
||||
|
||||
// Try to make a directory that exists already.
|
||||
errno = 0;
|
||||
mkdir("/dev", 0777);
|
||||
printf("Errno: %s\n", strerror(errno));
|
||||
assert(errno == EEXIST);
|
||||
|
||||
// Try to make a directory with a path component that is not a directory.
|
||||
errno = 0;
|
||||
mkdir("/dev/stdout/fake-directory", 0777);
|
||||
// TODO: This may have to change when access modes are implemented, depending
|
||||
// on if we check access mode before file type.
|
||||
#ifdef WASMFS
|
||||
assert(errno == ENOTDIR);
|
||||
#else
|
||||
assert(errno == EACCES);
|
||||
#endif
|
||||
|
||||
// Try to make a directory with a path component that does not exist.
|
||||
errno = 0;
|
||||
mkdir("/dev/false-path/fake-directory", 0777);
|
||||
printf("Errno: %s\n", strerror(errno));
|
||||
assert(errno == ENOENT);
|
||||
|
||||
// Try to make a directory under the `working` directory.
|
||||
errno = 0;
|
||||
mkdir("/working/new-directory", 0777);
|
||||
printf("Errno: %s\n", strerror(errno));
|
||||
assert(errno == 0);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
Errno: No error information
|
||||
Errno: No error information
|
||||
Test
|
||||
Errno: Invalid argument
|
||||
Errno: File exists
|
||||
Errno: No such file or directory
|
||||
Errno: No error information
|
|
@ -57,14 +57,14 @@ int main() {
|
|||
errno = 0;
|
||||
// Attempt to open a non-existent file path.
|
||||
int fd4 = open("/foo", O_RDWR);
|
||||
assert(errno == ENOENT);
|
||||
printf("Errno: %s\n", strerror(errno));
|
||||
assert(errno == ENOENT);
|
||||
|
||||
errno = 0;
|
||||
// Attempt to open a file path with a file intermediary.
|
||||
int fd5 = open("/dev/stdout/foo", O_RDWR);
|
||||
assert(errno == ENOTDIR);
|
||||
printf("Errno: %s\n", strerror(errno));
|
||||
assert(errno == ENOTDIR);
|
||||
|
||||
errno = 0;
|
||||
// Attempt to open and write to the root directory.
|
||||
|
@ -73,5 +73,10 @@ int main() {
|
|||
printf("Errno: %s\n", strerror(errno));
|
||||
assert(errno == EISDIR);
|
||||
|
||||
errno = 0;
|
||||
// Attempt to open a blank path.
|
||||
int fd7 = open("", O_RDONLY);
|
||||
assert(errno == EINVAL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче