| // 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" |