Relevant Issue: #15041
- Implement mkdir and create relevant tests for the new file system.
This commit is contained in:
Ethan Lee 2021-10-25 20:10:07 -04:00 коммит произвёл GitHub
Родитель 051c5c1a8a
Коммит c273f250d7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 284 добавлений и 72 удалений

Просмотреть файл

@ -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,

5
tests/test_other.py поставляемый
Просмотреть файл

@ -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;
}