blob: 97dfb762d3614a96ced8d76c497aa924a7f7f5c5 [file] [log] [blame] [edit]
// Copyright 2016 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 <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#define __NEED_struct_iovec
#include "syscall_arch.h"
#include <ctype.h>
#include <emscripten/emscripten.h>
#include <emscripten/fetch.h>
#include <emscripten/threading.h>
#include <libc/fcntl.h>
#include <math.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
// Uncomment the following and clear the cache with emcc --clear-cache to rebuild this file to
// enable internal debugging. #define ASMFS_DEBUG
extern "C" {
// http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
#define MAX_PATHNAME_LENGTH 2000
#define INODE_TYPE uint32_t
#define INODE_FILE 1
#define INODE_DIR 2
struct inode {
char name[NAME_MAX + 1]; // NAME_MAX actual bytes + one byte for null termination.
inode* parent; // ID of the parent node
inode* sibling; // ID of a sibling node (these form a singular linked list that specifies the
// content under a directory)
inode* child; // ID of the first child node in a chain of children (the root of a linked list of
// inodes)
uint32_t uid; // User ID of the owner
uint32_t gid; // Group ID of the owning group
uint32_t mode; // r/w/x modes
time_t ctime; // Time when the inode was last modified
time_t mtime; // Time when the content was last modified
time_t atime; // Time when the content was last accessed
size_t size; // Size of the file in bytes
size_t capacity; // Amount of bytes allocated to pointer data
uint8_t* data; // The actual file contents.
INODE_TYPE type;
emscripten_fetch_t* fetch;
// Specifies a remote server address where this inode can be located.
char* remoteurl;
};
#define EM_FILEDESCRIPTOR_MAGIC 0x64666d65U // 'emfd'
struct FileDescriptor {
uint32_t magic;
ssize_t file_pos;
uint32_t mode;
uint32_t flags;
inode* node;
};
static inode* create_inode(INODE_TYPE type, int mode) {
inode* i = (inode*)malloc(sizeof(inode));
memset(i, 0, sizeof(inode));
i->ctime = i->mtime = i->atime = time(0);
i->type = type;
i->mode = mode;
return i;
}
// The current working directory of the application process.
static inode* cwd_inode = 0;
static inode* filesystem_root() {
static inode* root_node = create_inode(INODE_DIR, 0777);
return root_node;
}
static inode* get_cwd() {
if (!cwd_inode)
cwd_inode = filesystem_root();
return cwd_inode;
}
static void set_cwd(inode* node) { cwd_inode = node; }
static void inode_abspath(inode* node, char* dst, int dstLen) {
if (!node) {
assert(dstLen >= (int)strlen("(null)") + 1);
strcpy(dst, "(null)");
return;
}
if (node == filesystem_root()) {
assert(dstLen >= (int)strlen("/") + 1);
strcpy(dst, "/");
return;
}
#define MAX_DIRECTORY_DEPTH 512
inode* stack[MAX_DIRECTORY_DEPTH];
int depth = 0;
while (node->parent && depth < MAX_DIRECTORY_DEPTH) {
stack[depth++] = node;
node = node->parent;
}
char* dstEnd = dst + dstLen;
*dstEnd-- = '\0';
while (depth > 0 && dst < dstEnd) {
if (dst < dstEnd)
*dst++ = '/';
--depth;
int len = strlen(stack[depth]->name);
if (len > dstEnd - dst)
len = dstEnd - dst;
strncpy(dst, stack[depth]->name, len);
dst += len;
}
}
// Deletes the given inode. Ignores (orphans) any children there might be
static void delete_inode(inode* node) {
if (!node)
return;
if (node == filesystem_root())
return; // As special case, do not allow deleting the filesystem root directory
#ifdef ASMFS_DEBUG
EM_ASM(err('delete_inode: ' + UTF8ToString($0)), node->name);
#endif
if (node->fetch)
emscripten_fetch_close(node->fetch);
free(node->remoteurl);
free(node);
}
// Deletes the given inode and its subtree
static void delete_inode_tree(inode* node) {
if (!node)
return;
#ifdef ASMFS_DEBUG
EM_ASM(err('delete_inode_tree: ' + UTF8ToString($0)), node->name);
#endif
inode* child = node->child;
while (child) {
inode* sibling = child->sibling;
delete_inode_tree(child->child);
delete_inode(child);
child = sibling;
}
if (node !=
filesystem_root()) // As special case, do not allow deleting the filesystem root directory
{
delete_inode(node);
} else {
// For filesystem root, just make sure all children are gone.
node->child = 0;
}
}
// Makes node the child of parent.
static void link_inode(inode* node, inode* parent) {
char parentName[PATH_MAX];
inode_abspath(parent, parentName, PATH_MAX);
#ifdef ASMFS_DEBUG
EM_ASM(err('link_inode: node "' + UTF8ToString($0) + '" to parent "' + UTF8ToString($1) + '".'),
node->name, parentName);
#endif
// When linking a node, it can't be part of the filesystem tree (but it can have children of its
// own)
assert(!node->parent);
assert(!node->sibling);
// The inode pointed by 'node' is not yet part of the filesystem, so it's not shared memory and
// only this thread is accessing it. Therefore setting the node's parent here is not yet racy, do
// that operation first.
node->parent = parent;
// This node is to become the first child of the parent, and the old first child of the parent
// should become the sibling of this node, i.e.
// 1) node->sibling = parent->child;
// 2) parent->child = node;
// However these two operations need to occur atomically in order to be coherent. To ensure that,
// run the two operations in a CAS loop, which is possible because the first operation is not racy
// until the node is 'published' to the filesystem tree by the compare_exchange operation.
do {
__atomic_load(
&parent->child, &node->sibling, __ATOMIC_SEQ_CST); // node->sibling <- parent->child
} while (
!__atomic_compare_exchange(&parent->child, &node->sibling, &node, false, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST)); // parent->child <- node if it had not raced to change value in between
}
// Traverse back in sibling linked list, or 0 if no such node exist.
static inode* find_predecessor_sibling(inode* node, inode* parent) {
inode* child = parent->child;
if (child == node)
return 0;
while (child && child->sibling != node)
child = child->sibling;
if (!child->sibling)
return 0;
return child;
}
static void unlink_inode(inode* node) {
#ifdef ASMFS_DEBUG
EM_ASM(
err('unlink_inode: node ' + UTF8ToString($0) + ' from its parent ' + UTF8ToString($1) + '.'),
node->name, node->parent->name);
#endif
inode* parent = node->parent;
if (!parent)
return;
node->parent = 0;
if (parent->child == node) {
parent->child = node->sibling;
} else {
inode* predecessor = find_predecessor_sibling(node, parent);
if (predecessor)
predecessor->sibling = node->sibling;
}
node->parent = node->sibling = 0;
}
// Compares two strings for equality until a '\0' or a '/' is hit. Returns 0 if the strings differ,
// or a pointer to the beginning of the next directory component name of s1 if the strings are
// equal.
static const char* path_cmp(const char* s1, const char* s2, bool* is_directory) {
*is_directory = true;
while (*s1 == *s2) {
if (*s1 == '/')
return s1 + 1;
if (*s1 == '\0') {
*is_directory = false;
return s1;
}
++s1;
++s2;
}
if (*s1 == '/' && *s2 == '\0')
return s1 + 1;
if (*s1 == '\0' && *s2 == '/')
return s1;
return 0;
}
#define NIBBLE_TO_CHAR(x) ("0123456789abcdef"[(x)])
static void uriEncode(char* dst, int dstLengthBytes, const char* src) {
char* end =
dst + dstLengthBytes - 4; // Use last 4 bytes of dst as a guard area to avoid overflow below.
while (*src && dst < end) {
if (isalnum(*src) || *src == '-' || *src == '_' || *src == '.' || *src == '~')
*dst++ = *src;
else if (*src == '/')
*dst++ = *src; // NB. forward slashes should generally be uriencoded, but for file path
// purposes, we want to keep them intact.
else
*dst++ = '%', *dst++ = NIBBLE_TO_CHAR(*src >> 4),
*dst++ = NIBBLE_TO_CHAR(*src & 15); // This charater needs uriencoding.
++src;
}
*dst = '\0';
}
// Copies string 'path' to 'dst', but stops on the first forward slash '/' character.
// Returns number of bytes written, excluding null terminator
static int strcpy_inodename(char* dst, const char* path) {
char* d = dst;
while (*path && *path != '/')
*dst++ = *path++;
*dst = '\0';
return dst - d;
}
// Copies src to dst, writes at most maxBytesToWrite out. Always null terminates dst. Returns the
// number of characters written, excluding null terminator.
static int strcpy_safe(char* dst, const char* src, int maxBytesToWrite) {
char* dst_start = dst;
char* dst_end = dst + maxBytesToWrite - 1;
while (dst < dst_end && *src)
*dst++ = *src++;
*dst = '\0';
return dst - dst_start;
}
// Returns a pointer to the basename part of the string, i.e. the string after the last occurrence
// of a forward slash character
static const char* basename_part(const char* path) {
const char* s = path;
while (*path) {
if (*path == '/')
s = path + 1;
++path;
}
return s;
}
static inode* create_directory_hierarchy_for_file(
inode* root, const char* path_to_file, unsigned int mode) {
assert(root);
if (!root)
return 0;
// Traverse . and ..
while (path_to_file[0] == '.') {
if (path_to_file[1] == '/')
path_to_file += 2; // Skip over redundant "./././././" blocks
else if (path_to_file[1] == '\0')
path_to_file += 1;
else if (path_to_file[1] == '.' &&
(path_to_file[2] == '/' ||
path_to_file[2] == '\0')) // Go up to parent directories with ".."
{
root = root->parent;
if (!root)
return 0;
assert(
root->type == INODE_DIR); // Anything that is a parent should automatically be a directory.
path_to_file += (path_to_file[2] == '/') ? 3 : 2;
} else
break;
}
if (path_to_file[0] == '\0')
return 0;
inode* node = root->child;
while (node) {
bool is_directory = false;
const char* child_path = path_cmp(path_to_file, node->name, &is_directory);
#ifdef ASMFS_DEBUG
EM_ASM_INT({err('path_cmp ' + UTF8ToString($0) + ', ' + UTF8ToString($1) + ', ' +
UTF8ToString($2) + ' .')},
path_to_file, node->name, child_path);
#endif
if (child_path) {
if (is_directory && node->type != INODE_DIR)
return 0; // "A component used as a directory in pathname is not, in fact, a directory"
// The directory name matches.
path_to_file = child_path;
// Traverse . and ..
while (path_to_file[0] == '.') {
if (path_to_file[1] == '/')
path_to_file += 2; // Skip over redundant "./././././" blocks
else if (path_to_file[1] == '\0')
path_to_file += 1;
else if (path_to_file[1] == '.' &&
(path_to_file[2] == '/' ||
path_to_file[2] == '\0')) // Go up to parent directories with ".."
{
node = node->parent;
if (!node)
return 0;
assert(node->type ==
INODE_DIR); // Anything that is a parent should automatically be a directory.
path_to_file += (path_to_file[2] == '/') ? 3 : 2;
} else
break;
}
if (path_to_file[0] == '\0')
return node;
if (path_to_file[0] == '/' && path_to_file[1] == '\0' /* && node is a directory*/)
return node;
root = node;
node = node->child;
} else {
node = node->sibling;
}
}
const char* basename_pos = basename_part(path_to_file);
#ifdef ASMFS_DEBUG
EM_ASM(err('path_to_file ' + UTF8ToString($0) + ' .'), path_to_file);
EM_ASM(err('basename_pos ' + UTF8ToString($0) + ' .'), basename_pos);
#endif
while (*path_to_file && path_to_file < basename_pos) {
node = create_inode(INODE_DIR, mode);
path_to_file += strcpy_inodename(node->name, path_to_file) + 1;
link_inode(node, root);
#ifdef ASMFS_DEBUG
EM_ASM(out('create_directory_hierarchy_for_file: created directory ' + UTF8ToString($0) +
' under parent ' + UTF8ToString($1) + '.'),
node->name, node->parent->name);
#endif
root = node;
}
return root;
}
#define RETURN_NODE_AND_ERRNO(node, errno) \
do { \
*out_errno = (errno); \
return (node); \
} while (0)
// Given a pathname to a file/directory, finds the inode of the directory that would contain the
// file/directory, or 0 if the intermediate path doesn't exist. Note that the file/directory pointed
// to by path does not need to exist, only its parent does.
static inode* find_parent_inode(inode* root, const char* path, int* out_errno) {
char rootName[PATH_MAX];
inode_abspath(root, rootName, PATH_MAX);
#ifdef ASMFS_DEBUG
EM_ASM(err('find_parent_inode(root="' + UTF8ToString($0) + '", path="' + UTF8ToString($1) + '")'),
rootName, path);
#endif
assert(out_errno); // Passing in error is mandatory.
if (!root)
RETURN_NODE_AND_ERRNO(0, ENOENT);
if (!path)
RETURN_NODE_AND_ERRNO(0, ENOENT);
// Traverse . and ..
while (path[0] == '.') {
if (path[1] == '/')
path += 2; // Skip over redundant "./././././" blocks
else if (path[1] == '\0')
path += 1;
else if (path[1] == '.' &&
(path[2] == '/' || path[2] == '\0')) // Go up to parent directories with ".."
{
root = root->parent;
if (!root)
RETURN_NODE_AND_ERRNO(0, ENOENT);
assert(
root->type == INODE_DIR); // Anything that is a parent should automatically be a directory.
path += (path[2] == '/') ? 3 : 2;
} else
break;
}
if (path[0] == '\0')
RETURN_NODE_AND_ERRNO(0, ENOENT);
if (path[0] == '/' && path[1] == '\0')
RETURN_NODE_AND_ERRNO(0, ENOENT);
if (root->type != INODE_DIR)
RETURN_NODE_AND_ERRNO(
0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
// TODO: RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in translating pathname");
// TODO: RETURN_ERRNO(EACCES, "one of the directories in the path prefix of pathname did not allow
// search permission");
const char* basename = basename_part(path);
if (path == basename)
RETURN_NODE_AND_ERRNO(root, 0);
inode* node = root->child;
while (node) {
bool is_directory = false;
const char* child_path = path_cmp(path, node->name, &is_directory);
if (child_path) {
if (is_directory && node->type != INODE_DIR)
RETURN_NODE_AND_ERRNO(
0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
// The directory name matches.
path = child_path;
// Traverse . and ..
while (path[0] == '.') {
if (path[1] == '/')
path += 2; // Skip over redundant "./././././" blocks
else if (path[1] == '\0')
path += 1;
else if (path[1] == '.' &&
(path[2] == '/' || path[2] == '\0')) // Go up to parent directories with ".."
{
node = node->parent;
if (!node)
RETURN_NODE_AND_ERRNO(0, ENOENT);
assert(node->type ==
INODE_DIR); // Anything that is a parent should automatically be a directory.
path += (path[2] == '/') ? 3 : 2;
} else
break;
}
if (path >= basename)
RETURN_NODE_AND_ERRNO(node, 0);
if (!*path)
RETURN_NODE_AND_ERRNO(0, ENOENT);
node = node->child;
if (node->type != INODE_DIR)
RETURN_NODE_AND_ERRNO(
0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
} else {
node = node->sibling;
}
}
RETURN_NODE_AND_ERRNO(
0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
}
// Given a root inode of the filesystem and a path relative to it, e.g.
// "some/directory/dir_or_file", returns the inode that corresponds to "dir_or_file", or 0 if it
// doesn't exist. If the parameter out_closest_parent is specified, the closest (grand)parent node
// will be returned.
static inode* find_inode(inode* root, const char* path, int* out_errno) {
char rootName[PATH_MAX];
inode_abspath(root, rootName, PATH_MAX);
#ifdef ASMFS_DEBUG
EM_ASM(err('find_inode(root="' + UTF8ToString($0) + '", path="' + UTF8ToString($1) + '")'),
rootName, path);
#endif
assert(out_errno); // Passing in error is mandatory.
if (!root)
RETURN_NODE_AND_ERRNO(0, ENOENT);
// TODO: RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in translating pathname");
// TODO: RETURN_ERRNO(EACCES, "one of the directories in the path prefix of pathname did not allow
// search permission");
// special-case finding empty string path "", "." or "/" returns the root searched in.
if (root->type != INODE_DIR)
RETURN_NODE_AND_ERRNO(
0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
if (!path)
RETURN_NODE_AND_ERRNO(root, 0);
// Traverse . and ..
while (path[0] == '.') {
if (path[1] == '/')
path += 2; // Skip over redundant "./././././" blocks
else if (path[1] == '\0')
path += 1;
else if (path[1] == '.' &&
(path[2] == '/' || path[2] == '\0')) // Go up to parent directories with ".."
{
root = root->parent;
if (!root)
RETURN_NODE_AND_ERRNO(0, ENOENT);
assert(
root->type == INODE_DIR); // Anything that is a parent should automatically be a directory.
path += (path[2] == '/') ? 3 : 2;
} else
break;
}
if (path[0] == '\0')
RETURN_NODE_AND_ERRNO(root, 0);
inode* node = root->child;
while (node) {
bool is_directory = false;
const char* child_path = path_cmp(path, node->name, &is_directory);
if (child_path) {
if (is_directory && node->type != INODE_DIR)
RETURN_NODE_AND_ERRNO(
0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
// The directory name matches.
path = child_path;
// Traverse . and ..
while (path[0] == '.') {
if (path[1] == '/')
path += 2; // Skip over redundant "./././././" blocks
else if (path[1] == '\0')
path += 1;
else if (path[1] == '.' &&
(path[2] == '/' || path[2] == '\0')) // Go up to parent directories with ".."
{
node = node->parent;
if (!node)
RETURN_NODE_AND_ERRNO(0, ENOENT);
assert(node->type ==
INODE_DIR); // Anything that is a parent should automatically be a directory.
path += (path[2] == '/') ? 3 : 2;
} else
break;
}
// If we arrived to the end of the search, this is the node we were looking for.
if (path[0] == '\0')
RETURN_NODE_AND_ERRNO(node, 0);
if (path[0] == '/' && node->type != INODE_DIR)
RETURN_NODE_AND_ERRNO(
0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
if (path[0] == '/' && path[1] == '\0')
RETURN_NODE_AND_ERRNO(node, 0);
node = node->child;
} else {
node = node->sibling;
}
}
RETURN_NODE_AND_ERRNO(0, ENOENT);
}
// Same as above, but the root node is deduced from 'path'. (either absolute if path starts with
// "/", or relative)
static inode* find_inode(const char* path, int* out_errno) {
inode* root;
if (path[0] == '/')
root = filesystem_root(), ++path;
else
root = get_cwd();
return find_inode(root, path, out_errno);
}
void emscripten_asmfs_set_remote_url(const char* filename, const char* remoteUrl) {
int err;
inode* node = find_inode(filename, &err);
if (!node)
return;
free(node->remoteurl);
node->remoteurl = strdup(remoteUrl);
}
void emscripten_asmfs_set_file_data(const char* filename, char* data, size_t size) {
int err;
inode* node = find_inode(filename, &err);
if (!node) {
free(data);
return;
}
free(node->data);
node->data = (uint8_t*)data;
node->size = node->capacity = size;
}
char* find_last_occurrence(char* str, char ch) {
char* o = 0;
while (*str) {
if (*str == ch)
o = str;
++str;
}
return o;
}
// Given a filename outputs the remote URL address that file can be located in.
void emscripten_asmfs_remote_url(const char* filename, char* outRemoteUrl, int maxBytesToWrite) {
if (maxBytesToWrite <= 0 || !outRemoteUrl)
return;
*outRemoteUrl = '\0';
if (maxBytesToWrite == 1)
return;
char trailing_path[PATH_MAX + 1] = {};
char full_path[PATH_MAX + 1] = {};
char full_path_temp[PATH_MAX + 1] = {};
strcpy(full_path, filename);
int err;
inode* node = find_inode(full_path, &err);
while (!node) {
char* s = find_last_occurrence(full_path, '/');
if (!s) {
node = filesystem_root();
strcpy(full_path_temp, trailing_path);
strcpy(trailing_path, full_path);
if (full_path_temp[0] != '\0') {
strcat(trailing_path, "/");
strcat(trailing_path, full_path_temp);
}
break;
}
*s = '\0';
node = find_inode(full_path, &err);
strcpy(full_path_temp, trailing_path);
strcpy(trailing_path, filename + (s - full_path));
if (full_path_temp[0] != '\0') {
strcat(trailing_path, "/");
strcat(trailing_path, full_path_temp);
}
}
char uriEncodedPathName[3 * PATH_MAX + 4];
full_path[0] = full_path[PATH_MAX] = full_path_temp[0] = full_path_temp[PATH_MAX] = '\0';
while (node) {
if (node->remoteurl && node->remoteurl[0] != '\0') {
int nWritten = strcpy_safe(outRemoteUrl, node->remoteurl, maxBytesToWrite);
if (maxBytesToWrite - nWritten > 1 && outRemoteUrl[nWritten - 1] != '/' &&
full_path[0] != '/') {
outRemoteUrl[nWritten++] = '/';
outRemoteUrl[nWritten] = '\0';
}
strcat(full_path + strlen(full_path), trailing_path);
uriEncode(uriEncodedPathName, 3 * PATH_MAX + 4, full_path);
strcpy_safe(outRemoteUrl + nWritten,
(outRemoteUrl[nWritten - 1] == '/' && uriEncodedPathName[0] == '/')
? (uriEncodedPathName + 1)
: uriEncodedPathName,
maxBytesToWrite - nWritten);
return;
}
strcpy_safe(full_path_temp, full_path, PATH_MAX);
int nWritten = strcpy_safe(full_path, node->name, PATH_MAX);
if (full_path_temp[0] != '\0') {
full_path[nWritten++] = '/';
full_path[nWritten] = '\0';
strcpy_safe(full_path + nWritten, full_path_temp, PATH_MAX - nWritten);
}
node = node->parent;
}
strcat(full_path + strlen(full_path), trailing_path);
uriEncode(uriEncodedPathName, 3 * PATH_MAX + 4, full_path);
strcpy_safe(outRemoteUrl, uriEncodedPathName, maxBytesToWrite);
}
// Debug function that dumps out the filesystem tree to console.
void emscripten_dump_fs_tree(inode* root, char* path) {
char str[256];
sprintf(str, "%s:", path);
EM_ASM(out(UTF8ToString($0)), str);
// Print out:
// file mode | number of links | owner name | group name | file size in bytes | file last modified
// time | path name which aligns with "ls -AFTRl" on console
inode* child = root->child;
uint64_t totalSize = 0;
while (child) {
sprintf(str, "%c%c%c%c%c%c%c%c%c%c %d user%u group%u %lu Jan 1 1970 %s%c",
child->type == INODE_DIR ? 'd' : '-', (child->mode & S_IRUSR) ? 'r' : '-',
(child->mode & S_IWUSR) ? 'w' : '-', (child->mode & S_IXUSR) ? 'x' : '-',
(child->mode & S_IRGRP) ? 'r' : '-', (child->mode & S_IWGRP) ? 'w' : '-',
(child->mode & S_IXGRP) ? 'x' : '-', (child->mode & S_IROTH) ? 'r' : '-',
(child->mode & S_IWOTH) ? 'w' : '-', (child->mode & S_IXOTH) ? 'x' : '-',
1, // number of links to this file
child->uid, child->gid,
child->size ? child->size : (child->fetch ? (int)child->fetch->numBytes : 0), child->name,
child->type == INODE_DIR ? '/' : ' ');
EM_ASM(out(UTF8ToString($0)), str);
totalSize += child->size;
child = child->sibling;
}
sprintf(str, "total %llu bytes\n", totalSize);
EM_ASM(out(UTF8ToString($0)), str);
child = root->child;
char* path_end = path + strlen(path);
while (child) {
if (child->type == INODE_DIR) {
strcpy(path_end, child->name);
strcat(path_end, "/");
emscripten_dump_fs_tree(child, path);
}
child = child->sibling;
}
}
void emscripten_asmfs_dump() {
EM_ASM({err('emscripten_asmfs_dump()')});
char path[PATH_MAX] = "/";
emscripten_dump_fs_tree(filesystem_root(), path);
}
void emscripten_asmfs_discard_tree(const char* path) {
#ifdef ASMFS_DEBUG
emscripten_asmfs_dump();
EM_ASM(err('emscripten_asmfs_discard_tree: ' + UTF8ToString($0)), path);
#endif
int err;
inode* node = find_inode(path, &err);
if (node && !err) {
unlink_inode(node);
delete_inode_tree(node);
}
#ifdef ASMFS_DEBUG
else
EM_ASM(err('emscripten_asmfs_discard_tree failed, error ' + $0), err);
emscripten_asmfs_dump();
#endif
}
#ifdef ASMFS_DEBUG
#define RETURN_ERRNO(errno, error_reason) \
do { \
EM_ASM(err(UTF8ToString($0) + '() returned errno ' + #errno + '(' + $1 + '): ' + \
error_reason + '!'), \
__FUNCTION__, errno); \
return -errno; \
} while (0)
#else
#define RETURN_ERRNO(errno, error_reason) \
do { \
return -(errno); \
} while (0)
#endif
static char stdout_buffer[4096] = {};
static int stdout_buffer_end = 0;
static char stderr_buffer[4096] = {};
static int stderr_buffer_end = 0;
static void print_stream(void* bytes, int numBytes, bool stdout) {
char* buffer = stdout ? stdout_buffer : stderr_buffer;
int& buffer_end = stdout ? stdout_buffer_end : stderr_buffer_end;
memcpy(buffer + buffer_end, bytes, numBytes);
buffer_end += numBytes;
int new_buffer_start = 0;
for (int i = 0; i < buffer_end; ++i) {
if (buffer[i] == '\n') {
buffer[i] = 0;
EM_ASM_INT({out(UTF8ToString($0))}, buffer + new_buffer_start);
new_buffer_start = i + 1;
}
}
size_t new_buffer_size = buffer_end - new_buffer_start;
memmove(buffer, buffer + new_buffer_start, new_buffer_size);
buffer_end = new_buffer_size;
}
long __syscall3(int which, ...) // read
{
va_list vl;
va_start(vl, which);
int fd = va_arg(vl, int);
void* buf = va_arg(vl, void*);
size_t count = va_arg(vl, size_t);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(
err('read(fd=' + $0 + ', buf=0x' + ($1).toString(16) + ', count=' + $2 + ')'), fd, buf, count);
#endif
iovec io = {buf, count};
return __syscall145(145 /*readv*/, fd, &io, 1);
}
long __syscall4(int which, ...) // write
{
va_list vl;
va_start(vl, which);
int fd = va_arg(vl, int);
void* buf = va_arg(vl, void*);
size_t count = va_arg(vl, size_t);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(
err('write(fd=' + $0 + ', buf=0x' + ($1).toString(16) + ', count=' + $2 + ')'), fd, buf, count);
#endif
iovec io = {buf, count};
return __syscall146(146 /*writev*/, fd, &io, 1);
}
// TODO: Make thread-local storage.
static emscripten_asmfs_open_t __emscripten_asmfs_file_open_behavior_mode =
EMSCRIPTEN_ASMFS_OPEN_REMOTE_DISCOVER;
void emscripten_asmfs_set_file_open_behavior(emscripten_asmfs_open_t behavior) {
__emscripten_asmfs_file_open_behavior_mode = behavior;
}
emscripten_asmfs_open_t emscripten_asmfs_get_file_open_behavior() {
return __emscripten_asmfs_file_open_behavior_mode;
}
// Returns true if the given file can be synchronously read by the main browser thread.
static bool emscripten_asmfs_file_is_synchronously_accessible(inode* node) {
return node->data // If file was created from memory without XHR, e.g. via fopen("foo.txt", "w"),
// it will have node->data ptr backing.
||
(node->fetch && node->fetch->data); // If the file was downloaded, it will be backed here.
}
static long open(const char* pathname, int flags, int mode) {
#ifdef ASMFS_DEBUG
EM_ASM(err('open(pathname="' + UTF8ToString($0) + '", flags=0x' + ($1).toString(16) + ', mode=0' +
($2).toString(8) + ')'),
pathname, flags, mode);
#endif
int accessMode = (flags & O_ACCMODE);
if ((flags & O_ASYNC))
RETURN_ERRNO(ENOTSUP, "TODO: Opening files with O_ASYNC flag is not supported in ASMFS");
if ((flags & O_DIRECT))
RETURN_ERRNO(ENOTSUP, "TODO: O_DIRECT flag is not supported in ASMFS");
if ((flags & O_DSYNC))
RETURN_ERRNO(ENOTSUP, "TODO: O_DSYNC flag is not supported in ASMFS");
// Spec says that the result of O_EXCL without O_CREAT is undefined.
// We could enforce it as an error condition, as follows:
// if ((flags & O_EXCL) && !(flags & O_CREAT)) RETURN_ERRNO(EINVAL, "open() with O_EXCL flag
//needs to always be paired with O_CREAT");
// However existing earlier unit tests in Emscripten expect that O_EXCL is simply ignored when
// O_CREAT was not passed. So do that for now.
if ((flags & O_EXCL) && !(flags & O_CREAT)) {
#ifdef ASMFS_DEBUG
EM_ASM(err('warning: open(pathname="' + UTF8ToString($0) + '", flags=0x' + ($1).toString(16) +
', mode=0' + ($2).toString(8) +
': flag O_EXCL should always be paired with O_CREAT. Ignoring O_EXCL)'),
pathname, flags, mode);
#endif
flags &= ~O_EXCL;
}
if ((flags & (O_NONBLOCK | O_NDELAY)))
RETURN_ERRNO(
ENOTSUP, "TODO: Opening files with O_NONBLOCK or O_NDELAY flags is not supported in ASMFS");
if ((flags & O_PATH))
RETURN_ERRNO(ENOTSUP, "TODO: Opening files with O_PATH flag is not supported in ASMFS");
if ((flags & O_SYNC))
RETURN_ERRNO(ENOTSUP, "TODO: Opening files with O_SYNC flag is not supported in ASMFS");
// The flags:O_CLOEXEC flag is ignored, doesn't have meaning for Emscripten
// TODO: the flags:O_DIRECT flag seems like a great way to let applications explicitly control
// XHR/IndexedDB read/write buffering behavior?
// The flags:O_LARGEFILE flag is ignored, we should always be largefile-compatible
// TODO: The flags:O_NOATIME is ignored, file access times have not been implemented yet
// The flags O_NOCTTY, O_NOFOLLOW
if ((flags & O_TMPFILE)) {
if (accessMode != O_WRONLY && accessMode != O_RDWR)
RETURN_ERRNO(
EINVAL, "O_TMPFILE was specified in flags, but neither O_WRONLY nor O_RDWR was specified");
else
RETURN_ERRNO(
EOPNOTSUPP, "TODO: The filesystem containing pathname does not support O_TMPFILE");
}
// TODO: if (too_many_files_open) RETURN_ERRNO(EMFILE, "The per-process limit on the number of
// open file descriptors has been reached, see getrlimit(RLIMIT_NOFILE)");
int len = strlen(pathname);
if (len > MAX_PATHNAME_LENGTH)
RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
if (len == 0)
RETURN_ERRNO(ENOENT, "pathname is empty");
// Find if this file exists already in the filesystem?
inode* root = (pathname[0] == '/') ? filesystem_root() : get_cwd();
const char* relpath = (pathname[0] == '/') ? pathname + 1 : pathname;
int err;
inode* node = find_inode(root, relpath, &err);
if (err == ENOTDIR)
RETURN_ERRNO(
ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
if (err == ELOOP)
RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
if (err == EACCES)
RETURN_ERRNO(EACCES,
"Search permission is denied for one of the directories in the path prefix of pathname");
if (err && err != ENOENT)
RETURN_ERRNO(err, "find_inode() error");
if (node) {
if ((flags & O_DIRECTORY) && node->type != INODE_DIR)
RETURN_ERRNO(ENOTDIR, "O_DIRECTORY was specified and pathname was not a directory");
if (!(node->mode & 0444))
RETURN_ERRNO(EACCES, "The requested access to the file is not allowed");
if ((flags & O_CREAT) && (flags & O_EXCL))
RETURN_ERRNO(EEXIST, "pathname already exists and O_CREAT and O_EXCL were used");
if (node->type == INODE_DIR && accessMode != O_RDONLY)
RETURN_ERRNO(EISDIR, "pathname refers to a directory and the access requested involved "
"writing (that is, O_WRONLY or O_RDWR is set)");
if (node->type == INODE_DIR && (flags & O_TRUNC))
RETURN_ERRNO(EISDIR,
"pathname refers to a directory and the access flags specified invalid flag O_TRUNC");
// A current download exists to the file? Then wait for it to complete.
if (node->fetch) {
// On the main thread, the fetch must have already completed before we come here. If not, we
// cannot stop to wait for it to finish, and must return a failure (file not found)
if (emscripten_is_main_browser_thread()) {
if (emscripten_fetch_wait(node->fetch, 0) != EMSCRIPTEN_RESULT_SUCCESS) {
RETURN_ERRNO(ENOENT, "Attempted to open a file that is still downloading on the main "
"browser thread. Could not block to wait! (try preloading the file "
"to the filesystem before application start)");
}
} else {
// On worker threads, we can pause to wait for the fetch.
emscripten_fetch_wait(node->fetch, INFINITY);
}
}
}
if ((flags & O_CREAT) && ((flags & O_TRUNC) || (flags & O_EXCL))) {
// Create a new empty file or truncate existing one.
if (node) {
if (node->fetch)
emscripten_fetch_close(node->fetch);
node->fetch = 0;
node->size = 0;
} else if ((flags & O_CREAT)) {
inode* directory = create_directory_hierarchy_for_file(root, relpath, mode);
node = create_inode((flags & O_DIRECTORY) ? INODE_DIR : INODE_FILE, mode);
strcpy(node->name, basename_part(pathname));
link_inode(node, directory);
}
} else if (!node || (node->type == INODE_FILE && !node->fetch && !node->data)) {
emscripten_fetch_t* fetch = 0;
if (!(flags & O_DIRECTORY) && accessMode != O_WRONLY) // Opening a file for reading?
{
// If there's no inode entry, check if we're not even interested in downloading the file?
if (!node &&
__emscripten_asmfs_file_open_behavior_mode != EMSCRIPTEN_ASMFS_OPEN_REMOTE_DISCOVER) {
RETURN_ERRNO(
ENOENT, "O_CREAT is not set, the named file does not exist in local filesystem and "
"EMSCRIPTEN_ASMFS_OPEN_REMOTE_DISCOVER is not specified");
}
// Report an error if there is an inode entry, but file data is not synchronously available
// and it should have been.
if (node && !node->data &&
__emscripten_asmfs_file_open_behavior_mode == EMSCRIPTEN_ASMFS_OPEN_MEMORY) {
RETURN_ERRNO(
ENOENT, "O_CREAT is not set, the named file exists, but file data is not synchronously "
"available in memory (EMSCRIPTEN_ASMFS_OPEN_MEMORY specified)");
}
if (emscripten_is_main_browser_thread() &&
(!node || !emscripten_asmfs_file_is_synchronously_accessible(node))) {
RETURN_ERRNO(ENOENT,
"O_CREAT is not set, the named file exists, but file data is not synchronously available "
"in memory, and file open is attempted on the main thread which cannot synchronously "
"open files! (try preloading the file to the filesystem before application start)");
}
// Kick off the file download, either from IndexedDB or via an XHR.
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
strcpy(attr.requestMethod, "GET");
attr.attributes = EMSCRIPTEN_FETCH_APPEND | EMSCRIPTEN_FETCH_LOAD_TO_MEMORY |
EMSCRIPTEN_FETCH_WAITABLE | EMSCRIPTEN_FETCH_PERSIST_FILE;
// If asked to only do a read from IndexedDB, don't perform an XHR.
if (__emscripten_asmfs_file_open_behavior_mode == EMSCRIPTEN_ASMFS_OPEN_INDEXEDDB) {
attr.attributes |= EMSCRIPTEN_FETCH_NO_DOWNLOAD;
}
char
path[3 * PATH_MAX + 4]; // times 3 because uri-encoding can expand the filename at most 3x.
emscripten_asmfs_remote_url(pathname, path, 3 * PATH_MAX + 4);
fetch = emscripten_fetch(&attr, path);
// Synchronously wait for the fetch to complete.
// NOTE: Theoretically could postpone blocking until the first read to the file, but the issue
// there is that we wouldn't be able to return ENOENT below if the file did not exist on the
// server, which could be harmful for some applications. Also fread()/fseek() very often
// immediately follows fopen(), so the win would not be too great anyways.
emscripten_fetch_wait(fetch, INFINITY);
if (!(flags & O_CREAT) && (fetch->status != 200 || fetch->totalBytes == 0)) {
emscripten_fetch_close(fetch);
RETURN_ERRNO(ENOENT, "O_CREAT is not set and the named file does not exist (attempted "
"emscripten_fetch() XHR to download)");
}
}
if (node) {
// If we had an existing inode entry, just associate the entry with the newly fetched data.
if (node->type == INODE_FILE)
node->fetch = fetch;
} else if ((flags &
O_CREAT) // If the filesystem entry did not exist, but we have a create flag, ...
|| (!node && fetch)) // ... or if it did not exist in our fs, but it could be found
// via fetch(), ...
{
// ... add it as a new entry to the fs.
inode* directory = create_directory_hierarchy_for_file(root, relpath, mode);
node = create_inode((flags & O_DIRECTORY) ? INODE_DIR : INODE_FILE, mode);
strcpy(node->name, basename_part(pathname));
node->fetch = fetch;
link_inode(node, directory);
} else {
if (fetch)
emscripten_fetch_close(fetch);
RETURN_ERRNO(ENOENT, "O_CREAT is not set and the named file does not exist");
}
node->size = fetch ? node->fetch->totalBytes : 0;
}
FileDescriptor* desc = (FileDescriptor*)malloc(sizeof(FileDescriptor));
desc->magic = EM_FILEDESCRIPTOR_MAGIC;
desc->node = node;
desc->file_pos = ((flags & O_APPEND) && node->fetch) ? node->fetch->totalBytes : 0;
desc->mode = mode;
desc->flags = flags;
// TODO: The file descriptor needs to be a small number, man page:
// "a small, nonnegative integer for use in subsequent system calls
// (read(2), write(2), lseek(2), fcntl(2), etc.). The file descriptor
// returned by a successful call will be the lowest-numbered file
// descriptor not currently open for the process."
return (long)desc;
}
long __syscall5(int which, ...) // open
{
va_list vl;
va_start(vl, which);
const char* pathname = va_arg(vl, const char*);
int flags = va_arg(vl, int);
int mode = va_arg(vl, int);
va_end(vl);
return open(pathname, flags, mode);
}
static long close(int fd) {
#ifdef ASMFS_DEBUG
EM_ASM(err('close(fd=' + $0 + ')'), fd);
#endif
FileDescriptor* desc = (FileDescriptor*)fd;
if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
if (desc->node && desc->node->fetch) {
if (!emscripten_is_main_browser_thread()) {
// TODO: This should not be necessary, but do it for now for consistency (test this out)
emscripten_fetch_wait(desc->node->fetch, INFINITY);
}
// TODO: What to do to a XHRed/IndexedDB-backed unmodified file in memory when closing the file?
// free() or keep in memory?
// If user intends to reopen the file later (possibly often?), it is faster to keep the
// file in memory. (but it will consume more memory) If running on the main thread, the
// file cannot be loaded back synchronously if we let go of it, so things would break if
// the file is attempted to be loaded up again afterwards. Currently use a heuristic that
// if a file is closed on the main browser thread, do not free its backing storage. This
// can work for many
// cases, but some kind of custom API might be best to add in the future? (e.g.
//emscripten_fclose_and_retain() vs emscripten_fclose_and_free()?)
if (!emscripten_is_main_browser_thread()) {
emscripten_fetch_close(desc->node->fetch);
desc->node->fetch = 0;
}
}
desc->magic = 0;
free(desc);
return 0;
}
void emscripten_asmfs_populate(const char* pathname, int mode) {
emscripten_asmfs_open_t prevBehavior = emscripten_asmfs_get_file_open_behavior();
emscripten_asmfs_set_file_open_behavior(EMSCRIPTEN_ASMFS_OPEN_MEMORY);
int fd = open(pathname, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, mode);
if (fd > 0) {
close(fd);
}
emscripten_asmfs_set_file_open_behavior(prevBehavior);
}
EMSCRIPTEN_RESULT emscripten_asmfs_preload_file(
const char* url, const char* pathname, int mode, emscripten_fetch_attr_t* options) {
if (!options) {
#ifdef ASMFS_DEBUG
EM_ASM(err('emscripten_asmfs_preload_file: options not specified!'));
#endif
return EMSCRIPTEN_RESULT_INVALID_PARAM;
}
if (!pathname) {
#ifdef ASMFS_DEBUG
EM_ASM(err('emscripten_asmfs_preload_file: pathname not specified!'));
#endif
return EMSCRIPTEN_RESULT_INVALID_PARAM;
}
// Find if this file exists already in the filesystem?
inode* root = (pathname[0] == '/') ? filesystem_root() : get_cwd();
const char* relpath = (pathname[0] == '/') ? pathname + 1 : pathname;
int err;
inode* node = find_inode(root, relpath, &err);
// Filesystem traversal error?
if (err && err != ENOENT) {
#ifdef ASMFS_DEBUG
EM_ASM(err('emscripten_asmfs_preload_file: find_inode error ' + $0 + '!'), err);
#endif
return EMSCRIPTEN_RESULT_INVALID_TARGET;
}
if (node && emscripten_asmfs_file_is_synchronously_accessible(node)) {
// The file already exists, and its contents have already been preloaded - immediately fire the
// success callback
if (options->onsuccess)
options->onsuccess(0);
return EMSCRIPTEN_RESULT_SUCCESS;
}
// Kick off the file download, either from IndexedDB or via an XHR.
emscripten_fetch_attr_t attr;
memcpy(&attr, options, sizeof(emscripten_fetch_attr_t));
if (strlen(attr.requestMethod) == 0)
strcpy(attr.requestMethod, "GET");
// In order for the file data to be synchronously accessible to the main browser thread, must load
// it directly to memory.
attr.attributes |= EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
// The following attributes cannot be present for preloading.
#ifdef ASMFS_DEBUG
if ((attr.attributes & (EMSCRIPTEN_FETCH_SYNCHRONOUS | EMSCRIPTEN_FETCH_STREAM_DATA |
EMSCRIPTEN_FETCH_WAITABLE)) != 0)
EM_ASM(err(
'emscripten_asmfs_preload_file: cannot specify EMSCRIPTEN_FETCH_SYNCHRONOUS, EMSCRIPTEN_FETCH_STREAM_DATA or EMSCRIPTEN_FETCH_WAITABLE flags when preloading!'));
#endif
attr.attributes &=
~(EMSCRIPTEN_FETCH_SYNCHRONOUS | EMSCRIPTEN_FETCH_STREAM_DATA | EMSCRIPTEN_FETCH_WAITABLE);
// Default to EMSCRIPTEN_FETCH_APPEND if not specified.
if (!(attr.attributes & (EMSCRIPTEN_FETCH_APPEND | EMSCRIPTEN_FETCH_REPLACE)))
attr.attributes |= EMSCRIPTEN_FETCH_APPEND;
emscripten_fetch_t* fetch;
if (url)
fetch = emscripten_fetch(&attr, url);
else {
char remoteUrl[3 * PATH_MAX +
4]; // times 3 because uri-encoding can expand the filename at most 3x.
emscripten_asmfs_remote_url(pathname, remoteUrl, 3 * PATH_MAX + 4);
fetch = emscripten_fetch(&attr, remoteUrl);
}
if (!node) {
inode* directory = create_directory_hierarchy_for_file(root, relpath, mode);
node = create_inode(INODE_FILE, mode);
strcpy(node->name, basename_part(pathname));
link_inode(node, directory);
}
node->fetch = fetch;
return EMSCRIPTEN_RESULT_SUCCESS;
}
long __syscall6(int which, ...) // close
{
va_list vl;
va_start(vl, which);
int fd = va_arg(vl, int);
va_end(vl);
return close(fd);
}
long __syscall9(int which, ...) // link
{
va_list vl;
va_start(vl, which);
const char* oldpath = va_arg(vl, const char*);
const char* newpath = va_arg(vl, const char*);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('link(oldpath="' + UTF8ToString($0) + '", newpath="' + UTF8ToString($1) + '")'),
oldpath, newpath);
#endif
((void)oldpath);
((void)newpath);
RETURN_ERRNO(ENOTSUP, "TODO: link() is a stub and not yet implemented in ASMFS");
}
long __syscall10(int which, ...) // unlink
{
va_list vl;
va_start(vl, which);
const char* pathname = va_arg(vl, const char*);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('unlink(pathname="' + UTF8ToString($0) + '")'), pathname);
#endif
int len = strlen(pathname);
if (len > MAX_PATHNAME_LENGTH)
RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
if (len == 0)
RETURN_ERRNO(ENOENT, "pathname is empty");
int err;
inode* node = find_inode(pathname, &err);
if (err == ENOTDIR)
RETURN_ERRNO(
ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
if (err == ELOOP)
RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in translating pathname");
if (err == EACCES)
RETURN_ERRNO(EACCES,
"One of the directories in the path prefix of pathname did not allow search permission");
if (err == ENOENT)
RETURN_ERRNO(ENOENT, "A component in pathname does not exist or is a dangling symbolic link");
if (err)
RETURN_ERRNO(err, "find_inode() error");
if (!node)
RETURN_ERRNO(ENOENT, "file does not exist");
inode* parent = node->parent;
if (parent && !(parent->mode & 0222))
RETURN_ERRNO(EACCES, "Write access to the directory containing pathname is not allowed for the "
"process's effective UID");
// TODO: RETURN_ERRNO(EPERM, "The directory containing pathname has the sticky bit (S_ISVTX) set
// and the process's effective user ID is neither the user ID of the file to be deleted nor that
// of the directory containing it, and the process is not privileged");
// TODO: RETURN_ERRNO(EROFS, "pathname refers to a file on a read-only filesystem");
if (!(node->mode & 0222)) {
if (node->type == INODE_DIR)
RETURN_ERRNO(
EISDIR, "directory deletion not permitted"); // Linux quirk: Return EISDIR error for not
// having permission to delete a directory.
else
RETURN_ERRNO(EPERM, "file deletion not permitted"); // but return EPERM error for no
// permission to delete a file.
}
if (node->child)
RETURN_ERRNO(EISDIR, "directory is not empty"); // Linux quirk: Return EISDIR error if not being
// able to delete a nonempty directory.
unlink_inode(node); // Detach this from parent
delete_inode_tree(node); // And delete the whole subtree
return 0;
}
long __syscall12(int which, ...) // chdir
{
va_list vl;
va_start(vl, which);
const char* pathname = va_arg(vl, const char*);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('chdir(pathname="' + UTF8ToString($0) + '")'), pathname);
#endif
int len = strlen(pathname);
if (len > MAX_PATHNAME_LENGTH)
RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
if (len == 0)
RETURN_ERRNO(ENOENT, "pathname is empty");
int err;
inode* node = find_inode(pathname, &err);
if (err == ENOTDIR)
RETURN_ERRNO(
ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
if (err == ELOOP)
RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving path");
if (err == EACCES)
RETURN_ERRNO(EACCES, "Search permission is denied for one of the components of path");
if (err == ENOENT)
RETURN_ERRNO(
ENOENT, "Directory component in pathname does not exist or is a dangling symbolic link");
if (err)
RETURN_ERRNO(err, "find_inode() error");
if (!node)
RETURN_ERRNO(ENOENT, "The directory specified in path does not exist");
if (node->type != INODE_DIR)
RETURN_ERRNO(ENOTDIR, "Path is not a directory");
set_cwd(node);
return 0;
}
long __syscall14(int which, ...) // mknod
{
va_list vl;
va_start(vl, which);
const char* pathname = va_arg(vl, const char*);
mode_t mode = va_arg(vl, mode_t);
int dev = va_arg(vl, int);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('mknod(pathname="' + UTF8ToString($0) + '", mode=0' + ($1).toString(8) + ', dev=' +
$2 + ')'),
pathname, mode, dev);
#endif
(void)pathname;
(void)mode;
(void)dev;
RETURN_ERRNO(ENOTSUP, "TODO: mknod() is a stub and not yet implemented in ASMFS");
}
long __syscall15(int which, ...) // chmod
{
va_list vl;
va_start(vl, which);
const char* pathname = va_arg(vl, const char*);
int mode = va_arg(vl, int);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('chmod(pathname="' + UTF8ToString($0) + '", mode=0' + ($1).toString(8) + ')'),
pathname, mode);
#endif
int len = strlen(pathname);
if (len > MAX_PATHNAME_LENGTH)
RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
if (len == 0)
RETURN_ERRNO(ENOENT, "pathname is empty");
int err;
inode* node = find_inode(pathname, &err);
if (err == ENOTDIR)
RETURN_ERRNO(
ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
if (err == ELOOP)
RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
if (err == EACCES)
RETURN_ERRNO(EACCES, "Search permission is denied on a component of the path prefix");
if (err == ENOENT)
RETURN_ERRNO(
ENOENT, "Directory component in pathname does not exist or is a dangling symbolic link");
if (err)
RETURN_ERRNO(err, "find_inode() error");
if (!node)
RETURN_ERRNO(ENOENT, "The file does not exist");
// TODO: if (not allowed) RETURN_ERRNO(EPERM, "The effective UID does not match the owner of the
// file");
// TODO: read-only filesystems: if (fs is read-only) RETURN_ERRNO(EROFS, "The named file resides
// on a read-only filesystem");
node->mode = mode;
return 0;
}
long __syscall33(int which, ...) // access
{
va_list vl;
va_start(vl, which);
const char* pathname = va_arg(vl, const char*);
int mode = va_arg(vl, int);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('access(pathname="' + UTF8ToString($0) + '", mode=0' + ($1).toString(8) + ')'),
pathname, mode);
#endif
int len = strlen(pathname);
if (len > MAX_PATHNAME_LENGTH)
RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
if (len == 0)
RETURN_ERRNO(ENOENT, "pathname is empty");
if ((mode & F_OK) && (mode & (R_OK | W_OK | X_OK)))
RETURN_ERRNO(EINVAL, "mode was incorrectly specified");
int err;
inode* node = find_inode(pathname, &err);
if (err == ENOTDIR)
RETURN_ERRNO(
ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
if (err == ELOOP)
RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
if (err == EACCES)
RETURN_ERRNO(EACCES,
"Search permission is denied for one of the directories in the path prefix of pathname");
if (err == ENOENT)
RETURN_ERRNO(ENOENT, "A component of pathname does not exist or is a dangling symbolic link");
if (err)
RETURN_ERRNO(err, "find_inode() error");
if (!node)
RETURN_ERRNO(ENOENT, "Pathname does not exist");
// Just testing if a file exists?
if ((mode & F_OK))
return 0;
// TODO: RETURN_ERRNO(EROFS, "Write permission was requested for a file on a read-only
// filesystem");
if ((mode & R_OK) && !(node->mode & 0444))
RETURN_ERRNO(EACCES, "Read access would be denied to the file");
if ((mode & W_OK) && !(node->mode & 0222))
RETURN_ERRNO(EACCES, "Write access would be denied to the file");
if ((mode & X_OK) && !(node->mode & 0111))
RETURN_ERRNO(EACCES, "Execute access would be denied to the file");
return 0;
}
long __syscall36(int which, ...) // sync
{
#ifdef ASMFS_DEBUG
EM_ASM(err('sync()'));
#endif
((void)which);
// Spec mandates that "sync() is always successful".
return 0;
}
// TODO: syscall38, int rename(const char *oldpath, const char *newpath);
long emscripten_asmfs_mkdir(const char* pathname, mode_t mode) {
#ifdef ASMFS_DEBUG
EM_ASM(err('mkdir(pathname="' + UTF8ToString($0) + '", mode=0' + ($1).toString(8) + ')'),
pathname, mode);
#endif
int len = strlen(pathname);
if (len > MAX_PATHNAME_LENGTH)
RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
if (len == 0)
RETURN_ERRNO(ENOENT, "pathname is empty");
inode* root = (pathname[0] == '/') ? filesystem_root() : get_cwd();
const char* relpath = (pathname[0] == '/') ? pathname + 1 : pathname;
int err;
inode* parent_dir = find_parent_inode(root, relpath, &err);
if (err == ENOTDIR)
RETURN_ERRNO(
ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
if (err == ELOOP)
RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
if (err == EACCES)
RETURN_ERRNO(EACCES, "One of the directories in pathname did not allow search permission");
if (err)
RETURN_ERRNO(err, "find_inode() error");
if (!parent_dir)
RETURN_ERRNO(
ENOENT, "A directory component in pathname does not exist or is a dangling symbolic link");
// TODO: if (component of path wasn't actually a directory) RETURN_ERRNO(ENOTDIR, "A component
// used as a directory in pathname is not, in fact, a directory");
inode* existing = find_inode(parent_dir, basename_part(pathname), &err);
if (err == ENOTDIR)
RETURN_ERRNO(
ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
if (err == ELOOP)
RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
if (err == EACCES)
RETURN_ERRNO(EACCES, "One of the directories in pathname did not allow search permission");
if (err && err != ENOENT)
RETURN_ERRNO(err, "find_inode() error");
if (existing)
RETURN_ERRNO(EEXIST, "pathname already exists (not necessarily as a directory)");
if (!(parent_dir->mode & 0222))
RETURN_ERRNO(EACCES, "The parent directory does not allow write permission to the process");
// TODO: read-only filesystems: if (fs is read-only) RETURN_ERRNO(EROFS, "Pathname refers to a
// file on a read-only filesystem");
inode* directory = create_inode(INODE_DIR, mode);
strcpy(directory->name, basename_part(pathname));
link_inode(directory, parent_dir);
return 0;
}
void emscripten_asmfs_unload_data(const char* pathname) {
int err;
inode* node = find_inode(pathname, &err);
if (!node)
return;
free(node->data);
node->data = 0;
node->size = node->capacity = 0;
}
uint64_t emscripten_asmfs_compute_memory_usage_at_node(inode* node) {
if (!node)
return 0;
uint64_t sz = sizeof(inode);
if (node->data)
sz += node->capacity > node->size ? node->capacity : node->size;
if (node->fetch && node->fetch->data)
sz += node->fetch->numBytes;
return sz + emscripten_asmfs_compute_memory_usage_at_node(node->child) +
emscripten_asmfs_compute_memory_usage_at_node(node->sibling);
}
uint64_t emscripten_asmfs_compute_memory_usage() {
return emscripten_asmfs_compute_memory_usage_at_node(filesystem_root());
}
long __syscall39(int which, ...) // mkdir
{
va_list vl;
va_start(vl, which);
const char* pathname = va_arg(vl, const char*);
mode_t mode = va_arg(vl, mode_t);
va_end(vl);
return emscripten_asmfs_mkdir(pathname, mode);
}
long __syscall40(int which, ...) // rmdir
{
va_list vl;
va_start(vl, which);
const char* pathname = va_arg(vl, const char*);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('rmdir(pathname="' + UTF8ToString($0) + '")'), pathname);
#endif
int len = strlen(pathname);
if (len > MAX_PATHNAME_LENGTH)
RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
if (len == 0)
RETURN_ERRNO(ENOENT, "pathname is empty");
if (!strcmp(pathname, ".") || (len >= 2 && !strcmp(pathname + len - 2, "/.")))
RETURN_ERRNO(EINVAL, "pathname has . as last component");
if (!strcmp(pathname, "..") || (len >= 3 && !strcmp(pathname + len - 3, "/..")))
RETURN_ERRNO(ENOTEMPTY, "pathname has .. as its final component");
int err;
inode* node = find_inode(pathname, &err);
if (err == ENOTDIR)
RETURN_ERRNO(
ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
if (err == ELOOP)
RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
if (err == EACCES)
RETURN_ERRNO(EACCES,
"one of the directories in the path prefix of pathname did not allow search permission");
if (err == ENOENT)
RETURN_ERRNO(
ENOENT, "A directory component in pathname does not exist or is a dangling symbolic link");
if (err)
RETURN_ERRNO(err, "find_inode() error");
if (!node)
RETURN_ERRNO(ENOENT, "directory does not exist");
if (node == filesystem_root() || node == get_cwd())
RETURN_ERRNO(EBUSY, "pathname is currently in use by the system or some process that prevents "
"its removal (pathname is currently used as a mount point or is the root "
"directory of the calling process)");
if (node->parent && !(node->parent->mode & 0222))
RETURN_ERRNO(EACCES, "Write access to the directory containing pathname was not allowed");
if (node->type != INODE_DIR)
RETURN_ERRNO(ENOTDIR, "pathname is not a directory");
if (node->child)
RETURN_ERRNO(ENOTEMPTY, "pathname contains entries other than . and ..");
// TODO: RETURN_ERRNO(EPERM, "The directory containing pathname has the sticky bit (S_ISVTX) set
// and the process's effective user ID is neither the user ID of the file to be deleted nor that
// of the directory containing it, and the process is not privileged");
// TODO: RETURN_ERRNO(EROFS, "pathname refers to a directory on a read-only filesystem");
unlink_inode(node);
return 0;
}
long __syscall41(int which, ...) // dup
{
va_list vl;
va_start(vl, which);
unsigned int fd = va_arg(vl, unsigned int);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('dup(fd=' + $0 + ')'), fd);
#endif
FileDescriptor* desc = (FileDescriptor*)fd;
if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
inode* node = desc->node;
if (!node)
RETURN_ERRNO(-1, "ASMFS internal error: file descriptor points to a nonexisting file");
// TODO: RETURN_ERRNO(EMFILE, "The per-process limit on the number of open file descriptors has
// been reached (see RLIMIT_NOFILE)");
RETURN_ERRNO(ENOTSUP, "TODO: dup() is a stub and not yet implemented in ASMFS");
}
// TODO: syscall42: int pipe(int pipefd[2]);
long __syscall54(int which, ...) // ioctl/sysctl
{
va_list vl;
va_start(vl, which);
int fd = va_arg(vl, int);
int request = va_arg(vl, int);
char* argp = va_arg(vl, char*);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('ioctl(fd=' + $0 + ', request=' + $1 + ', argp=0x' + $2 + ')'), fd, request, argp);
#endif
(void)fd;
(void)request;
(void)argp;
RETURN_ERRNO(ENOTSUP, "TODO: ioctl() is a stub and not yet implemented in ASMFS");
}
// TODO: syscall60: mode_t umask(mode_t mask);
// TODO: syscall63: dup2
// TODO: syscall83: symlink
// TODO: syscall85: readlink
// TODO: syscall91: munmap
// TODO: syscall94: fchmod
// TODO: syscall102: socketcall
long __syscall118(int which, ...) // fsync
{
va_list vl;
va_start(vl, which);
unsigned int fd = va_arg(vl, unsigned int);
va_end(vl);
FileDescriptor* desc = (FileDescriptor*)fd;
if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
inode* node = desc->node;
if (!node)
RETURN_ERRNO(-1, "ASMFS internal error: file descriptor points to a non-file");
return 0;
}
// TODO: syscall133: fchdir
long __syscall140(int which, ...) // llseek
{
va_list vl;
va_start(vl, which);
unsigned int fd = va_arg(vl, unsigned int);
unsigned long offset_high = va_arg(vl, unsigned long);
unsigned long offset_low = va_arg(vl, unsigned long);
off_t* result = va_arg(vl, off_t*);
unsigned int whence = va_arg(vl, unsigned int);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('llseek(fd=' + $0 + ', offset_high=' + $1 + ', offset_low=' + $2 + ', result=0x' +
($3).toString(16) + ', whence=' + $4 + ')'),
fd, offset_high, offset_low, result, whence);
#endif
FileDescriptor* desc = (FileDescriptor*)fd;
if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
if (desc->node->fetch) {
if (emscripten_is_main_browser_thread()) {
if (emscripten_fetch_wait(desc->node->fetch, 0) != EMSCRIPTEN_RESULT_SUCCESS) {
RETURN_ERRNO(ENOENT, "Attempted to seek a file that is still downloading on the main "
"browser thread. Could not block to wait! (try preloading the file to "
"the filesystem before application start)");
}
} else
emscripten_fetch_wait(desc->node->fetch, INFINITY);
}
// TODO: The following does not work, for some reason seek is getting called with 32-bit signed
// offsets?
// int64_t offset = (int64_t)(((uint64_t)offset_high << 32) | (uint64_t)offset_low);
int64_t offset = (int64_t)(int32_t)offset_low;
int64_t newPos;
switch (whence) {
case SEEK_SET:
newPos = offset;
break;
case SEEK_CUR:
newPos = desc->file_pos + offset;
break;
case SEEK_END:
newPos = (desc->node->fetch ? desc->node->fetch->numBytes : desc->node->size) + offset;
break;
case 3 /*SEEK_DATA*/:
RETURN_ERRNO(EINVAL, "whence is invalid (sparse files, whence=SEEK_DATA, is not supported");
case 4 /*SEEK_HOLE*/:
RETURN_ERRNO(EINVAL, "whence is invalid (sparse files, whence=SEEK_HOLE, is not supported");
default:
RETURN_ERRNO(EINVAL, "whence is invalid");
}
if (newPos < 0)
RETURN_ERRNO(EINVAL, "The resulting file offset would be negative");
if (newPos > 0x7FFFFFFFLL)
RETURN_ERRNO(EOVERFLOW, "The resulting file offset cannot be represented in an off_t");
desc->file_pos = newPos;
if (result)
*result = desc->file_pos;
return 0;
}
// TODO: syscall144 msync
long __syscall145(int which, ...) // readv
{
va_list vl;
va_start(vl, which);
int fd = va_arg(vl, int);
const iovec* iov = va_arg(vl, const iovec*);
int iovcnt = va_arg(vl, int);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('readv(fd=' + $0 + ', iov=0x' + ($1).toString(16) + ', iovcnt=' + $2 + ')'), fd, iov,
iovcnt);
#endif
FileDescriptor* desc = (FileDescriptor*)fd;
if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
inode* node = desc->node;
if (!node)
RETURN_ERRNO(-1, "ASMFS internal error: file descriptor points to a non-file");
if (node->type == INODE_DIR)
RETURN_ERRNO(EISDIR, "fd refers to a directory");
if (node->type != INODE_FILE /* TODO: && node->type != socket */)
RETURN_ERRNO(EINVAL, "fd is attached to an object which is unsuitable for reading");
// TODO: if (node->type == INODE_FILE && desc has O_NONBLOCK && read would block)
// RETURN_ERRNO(EAGAIN, "The file descriptor fd refers to a file other than a socket and has been
// marked nonblocking (O_NONBLOCK), and the read would block");
// TODO: if (node->type == socket && desc has O_NONBLOCK && read would block)
// RETURN_ERRNO(EWOULDBLOCK, "The file descriptor fd refers to a socket and has been marked
// nonblocking (O_NONBLOCK), and the read would block");
if (node->fetch) {
if (emscripten_is_main_browser_thread()) {
if (emscripten_fetch_wait(node->fetch, 0) != EMSCRIPTEN_RESULT_SUCCESS) {
RETURN_ERRNO(ENOENT, "Attempted to read a file that is still downloading on the main "
"browser thread. Could not block to wait! (try preloading the file to "
"the filesystem before application start)");
}
} else
emscripten_fetch_wait(node->fetch, INFINITY);
}
if (node->size > 0 && !node->data && (!node->fetch || !node->fetch->data))
RETURN_ERRNO(-1, "ASMFS internal error: no file data available");
if (iovcnt < 0)
RETURN_ERRNO(EINVAL, "The vector count, iovcnt, is less than zero");
ssize_t total_read_amount = 0;
for (int i = 0; i < iovcnt; ++i) {
ssize_t n = total_read_amount + iov[i].iov_len;
if (n < total_read_amount)
RETURN_ERRNO(EINVAL, "The sum of the iov_len values overflows an ssize_t value");
if (!iov[i].iov_base && iov[i].iov_len > 0)
RETURN_ERRNO(
EINVAL, "iov_len specifies a positive length buffer but iov_base is a null pointer");
total_read_amount = n;
}
size_t offset = desc->file_pos;
uint8_t* data = node->data ? node->data : (node->fetch ? (uint8_t*)node->fetch->data : 0);
size_t size = node->data ? node->size : (node->fetch ? node->fetch->numBytes : 0);
for (int i = 0; i < iovcnt; ++i) {
ssize_t dataLeft = size - offset;
if (dataLeft <= 0)
break;
size_t bytesToCopy = (size_t)dataLeft < iov[i].iov_len ? dataLeft : iov[i].iov_len;
memcpy(iov[i].iov_base, &data[offset], bytesToCopy);
#ifdef ASMFS_DEBUG
EM_ASM(err('readv requested to read ' + $0 + ', read ' + $1 + ' bytes from offset ' + $2 +
', new offset: ' + $3 + ' (file size: ' + $4 + ')'),
(int)iov[i].iov_len, (int)bytesToCopy, (int)offset, (int)(offset + bytesToCopy), (int)size);
#endif
offset += bytesToCopy;
}
ssize_t numRead = offset - desc->file_pos;
desc->file_pos = offset;
return numRead;
}
long __syscall146(int which, ...) // writev
{
va_list vl;
va_start(vl, which);
int fd = va_arg(vl, int);
const iovec* iov = va_arg(vl, const iovec*);
int iovcnt = va_arg(vl, int);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('writev(fd=' + $0 + ', iov=0x' + ($1).toString(16) + ', iovcnt=' + $2 + ')'), fd, iov,
iovcnt);
#endif
FileDescriptor* desc = (FileDescriptor*)fd;
if (fd != 1 /*stdout*/ &&
fd != 2 /*stderr*/) // TODO: Resolve the hardcoding of stdin,stdout & stderr
{
if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
}
if (iovcnt < 0)
RETURN_ERRNO(EINVAL, "The vector count, iovcnt, is less than zero");
ssize_t total_write_amount = 0;
for (int i = 0; i < iovcnt; ++i) {
ssize_t n = total_write_amount + iov[i].iov_len;
if (n < total_write_amount)
RETURN_ERRNO(EINVAL, "The sum of the iov_len values overflows an ssize_t value");
if (!iov[i].iov_base && iov[i].iov_len > 0)
RETURN_ERRNO(
EINVAL, "iov_len specifies a positive length buffer but iov_base is a null pointer");
total_write_amount = n;
}
if (fd == 1 /*stdout*/ || fd == 2 /*stderr*/) {
ssize_t bytesWritten = 0;
for (int i = 0; i < iovcnt; ++i) {
print_stream(iov[i].iov_base, iov[i].iov_len, fd == 1);
bytesWritten += iov[i].iov_len;
}
return bytesWritten;
} else {
// Enlarge the file in memory to fit space for the new data
size_t newSize = desc->file_pos + total_write_amount;
inode* node = desc->node;
if (node->capacity < newSize) {
size_t newCapacity =
(newSize > (size_t)(node->capacity * 1.25)
? newSize
: (size_t)(
node->capacity * 1.25)); // Geometric increases in size for amortized O(1) behavior
uint8_t* newData = (uint8_t*)realloc(node->data, newCapacity);
if (!newData) {
newData = (uint8_t*)malloc(newCapacity);
memcpy(newData, node->data, node->size);
// TODO: init gaps with zeroes.
free(node->data);
}
node->data = newData;
node->size = newSize;
node->capacity = newCapacity;
}
for (int i = 0; i < iovcnt; ++i) {
memcpy((uint8_t*)node->data + desc->file_pos, iov[i].iov_base, iov[i].iov_len);
desc->file_pos += iov[i].iov_len;
}
}
return total_write_amount;
}
// TODO: syscall148: fdatasync
// TODO: syscall168: poll
// TODO: syscall180: pread64
// TODO: syscall181: pwrite64
long __syscall183(int which, ...) // getcwd
{
va_list vl;
va_start(vl, which);
char* buf = va_arg(vl, char*);
size_t size = va_arg(vl, size_t);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('getcwd(buf=0x' + $0 + ', size= ' + $1 + ')'), buf, size);
#endif
if (!buf && size > 0)
RETURN_ERRNO(EFAULT, "buf points to a bad address");
if (buf && size == 0)
RETURN_ERRNO(EINVAL, "The size argument is zero and buf is not a null pointer");
inode* cwd = get_cwd();
if (!cwd)
RETURN_ERRNO(-1, "ASMFS internal error: no current working directory?!");
// TODO: RETURN_ERRNO(ENOENT, "The current working directory has been unlinked");
// TODO: RETURN_ERRNO(EACCES, "Permission to read or search a component of the filename was
// denied");
inode_abspath(cwd, buf, size);
if (strlen(buf) >= size - 1)
RETURN_ERRNO(ERANGE, "The size argument is less than the length of the absolute pathname of "
"the working directory, including the terminating null byte. You need to "
"allocate a bigger array and try again");
return 0;
}
// TODO: syscall192: mmap2
// TODO: syscall193: truncate64
// TODO: syscall194: ftruncate64
static long __stat64(inode* node, struct stat* buf) {
buf->st_dev =
1; // ID of device containing file: Hardcode 1 for now, no meaning at the moment for Emscripten.
buf->st_ino = (ino_t)node; // TODO: This needs to be an inode ID number proper.
buf->st_mode = node->mode;
switch (node->type) {
case INODE_DIR:
buf->st_mode |= S_IFDIR;
break;
case INODE_FILE:
buf->st_mode |= S_IFREG;
break; // Regular file
/* TODO:
case socket: buf->st_mode |= S_IFSOCK; break;
case symlink: buf->st_mode |= S_IFLNK; break;
case block device: buf->st_mode |= S_IFBLK; break;
case character device: buf->st_mode |= S_IFCHR; break;
case FIFO: buf->st_mode |= S_IFIFO; break;
*/
}
buf->st_nlink = 1; // The number of hard links. TODO: Use this for real when links are supported.
buf->st_uid = node->uid;
buf->st_gid = node->gid;
buf->st_rdev = 1; // Device ID (if special file) No meaning right now for Emscripten.
buf->st_size = node->fetch ? node->fetch->totalBytes : 0;
if (node->size > (size_t)buf->st_size)
buf->st_size = node->size;
buf->st_blocks =
(buf->st_size + 511) / 512; // The syscall docs state this is hardcoded to # of 512 byte blocks.
buf->st_blksize = 1024 * 1024; // Specifies the preferred blocksize for efficient disk I/O.
buf->st_atim.tv_sec = node->atime;
buf->st_mtim.tv_sec = node->mtime;
buf->st_ctim.tv_sec = node->ctime;
return 0;
}
long __syscall195(int which, ...) // SYS_stat64
{
va_list vl;
va_start(vl, which);
const char* pathname = va_arg(vl, const char*);
struct stat* buf = va_arg(vl, struct stat*);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('SYS_stat64(pathname="' + UTF8ToString($0) + '", buf=0x' + ($1).toString(16) + ')'),
pathname, buf);
#endif
int len = strlen(pathname);
if (len > MAX_PATHNAME_LENGTH)
RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
if (len == 0)
RETURN_ERRNO(ENOENT, "pathname is empty");
// Find if this file exists already in the filesystem?
inode* root = (pathname[0] == '/') ? filesystem_root() : get_cwd();
const char* relpath = (pathname[0] == '/') ? pathname + 1 : pathname;
int err;
inode* node = find_inode(root, relpath, &err);
if (!node && (err == ENOENT || err == ENOTDIR)) {
// Populate the file from the CDN to the filesystem if it didn't yet exist.
long fd = open(pathname, O_RDONLY, 0777);
if (fd)
close(fd);
node = find_inode(root, relpath, &err);
}
if (err == ENOTDIR)
RETURN_ERRNO(ENOTDIR, "A component of the path prefix of pathname is not a directory");
if (err == ELOOP)
RETURN_ERRNO(ELOOP, "Too many symbolic links encountered while traversing the path");
if (err == EACCES)
RETURN_ERRNO(EACCES,
"Search permission is denied for one of the directories in the path prefix of pathname");
if (err && err != ENOENT)
RETURN_ERRNO(err, "find_inode() error");
if (err == ENOENT || !node)
RETURN_ERRNO(ENOENT, "A component of pathname does not exist");
return __stat64(node, buf);
}
long __syscall196(int which, ...) // SYS_lstat64
{
va_list vl;
va_start(vl, which);
const char* pathname = va_arg(vl, const char*);
struct stat* buf = va_arg(vl, struct stat*);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(err('SYS_lstat64(pathname="' + UTF8ToString($0) + '", buf=0x' + ($1).toString(16) + ')'),
pathname, buf);
#endif
int len = strlen(pathname);
if (len > MAX_PATHNAME_LENGTH)
RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
if (len == 0)
RETURN_ERRNO(ENOENT, "pathname is empty");
// Find if this file exists already in the filesystem?
inode* root = (pathname[0] == '/') ? filesystem_root() : get_cwd();
const char* relpath = (pathname[0] == '/') ? pathname + 1 : pathname;
// TODO: When symbolic links are implemented, make this return info about the symlink itself and
// not the file it points to.
int err;
inode* node = find_inode(root, relpath, &err);
if (err == ENOTDIR)
RETURN_ERRNO(ENOTDIR, "A component of the path prefix of pathname is not a directory");
if (err == ELOOP)
RETURN_ERRNO(ELOOP, "Too many symbolic links encountered while traversing the path");
if (err == EACCES)
RETURN_ERRNO(EACCES,
"Search permission is denied for one of the directories in the path prefix of pathname");
if (err && err != ENOENT)
RETURN_ERRNO(err, "find_inode() error");
if (err == ENOENT || !node)
RETURN_ERRNO(ENOENT, "A component of pathname does not exist");
return __stat64(node, buf);
}
long __syscall197(int which, ...) // SYS_fstat64
{
va_list vl;
va_start(vl, which);
int fd = va_arg(vl, int);
struct stat* buf = va_arg(vl, struct stat*);
va_end(vl);
#ifdef ASMFS_DEBUG
EM_ASM(
err('SYS_fstat64(fd="' + UTF8ToString($0) + '", buf=0x' + ($1).toString(16) + ')'), fd, buf);
#endif
FileDescriptor* desc = (FileDescriptor*)fd;
if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
inode* node = desc->node;
if (!node)
RETURN_ERRNO(ENOENT, "A component of pathname does not exist");
return __stat64(node, buf);
}
// TODO: syscall198: lchown
// TODO: syscall207: fchown32
// TODO: syscall212: chown32
long __syscall220(int which, ...) // getdents64 (get directory entries 64-bit)
{
va_list vl;
va_start(vl, which);
unsigned int fd = va_arg(vl, unsigned int);
dirent* de = va_arg(vl, dirent*);
unsigned int count = va_arg(vl, unsigned int);
va_end(vl);
unsigned int dirents_size =
count /
sizeof(dirent); // The number of dirent structures that can fit into the provided buffer.
dirent* de_end = de + dirents_size;
#ifdef ASMFS_DEBUG
EM_ASM(err('getdents64(fd=' + $0 + ', de=0x' + ($1).toString(16) + ', count=' + $2 + ')'), fd, de,
count);
#endif
FileDescriptor* desc = (FileDescriptor*)fd;
if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
RETURN_ERRNO(EBADF, "Invalid file descriptor fd");
inode* node = desc->node;
if (!node)
RETURN_ERRNO(ENOENT, "No such directory");
if (dirents_size == 0)
RETURN_ERRNO(EINVAL, "Result buffer is too small");
if (node->type != INODE_DIR)
RETURN_ERRNO(ENOTDIR, "File descriptor does not refer to a directory");
inode* dotdot =
node->parent ? node->parent : node; // In "/", the directory ".." refers to itself.
ssize_t orig_file_pos = desc->file_pos;
ssize_t file_pos = 0;
// There are always two hardcoded directories "." and ".."
if (de >= de_end)
return desc->file_pos - orig_file_pos;
if (desc->file_pos <= file_pos) {
de->d_ino = (ino_t)node; // TODO: Create inode numbers instead of using pointers
de->d_off = file_pos + sizeof(dirent);
de->d_reclen = sizeof(dirent);
de->d_type = DT_DIR;
strcpy(de->d_name, ".");
++de;
desc->file_pos += sizeof(dirent);
}
file_pos += sizeof(dirent);
if (de >= de_end)
return desc->file_pos - orig_file_pos;
if (desc->file_pos <= file_pos) {
de->d_ino = (ino_t)dotdot; // TODO: Create inode numbers instead of using pointers
de->d_off = file_pos + sizeof(dirent);
de->d_reclen = sizeof(dirent);
de->d_type = DT_DIR;
strcpy(de->d_name, "..");
++de;
desc->file_pos += sizeof(dirent);
}
file_pos += sizeof(dirent);
node = node->child;
while (node && de < de_end) {
if (desc->file_pos <= file_pos) {
de->d_ino = (ino_t)node; // TODO: Create inode numbers instead of using pointers
de->d_off = file_pos + sizeof(dirent);
de->d_reclen = sizeof(dirent);
de->d_type = (node->type == INODE_DIR) ? DT_DIR : DT_REG /*Regular file*/;
de->d_name[255] = 0;
strncpy(de->d_name, node->name, 255);
++de;
desc->file_pos += sizeof(dirent);
}
node = node->sibling;
file_pos += sizeof(dirent);
}
return desc->file_pos - orig_file_pos;
}
// TODO: syscall221: fcntl64
// TODO: syscall268: statfs64
// TODO: syscall269: fstatfs64
// TODO: syscall295: openat
// TODO: syscall296: mkdirat
// TODO: syscall297: mknodat
// TODO: syscall298: fchownat
// TODO: syscall300: fstatat64
// TODO: syscall301: unlinkat
// TODO: syscall302: renameat
// TODO: syscall303: linkat
// TODO: syscall304: symlinkat
// TODO: syscall305: readlinkat
// TODO: syscall306: fchmodat
// TODO: syscall307: faccessat
// TODO: syscall320: utimensat
// TODO: syscall324: fallocate
// TODO: syscall330: dup3
// TODO: syscall331: pipe2
// TODO: syscall333: preadv
// TODO: syscall334: pwritev
} // ~extern "C"