1 // Copyright 2016 The Emscripten Authors.  All rights reserved.
2 // Emscripten is available under two separate licenses, the MIT license and the
3 // University of Illinois/NCSA Open Source License.  Both these licenses can be
4 // found in the LICENSE file.
5 
6 #include <assert.h>
7 #include <dirent.h>
8 #include <errno.h>
9 #include <stdarg.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #define __NEED_struct_iovec
13 #include "syscall_arch.h"
14 #include <ctype.h>
15 #include <emscripten/emscripten.h>
16 #include <emscripten/fetch.h>
17 #include <emscripten/threading.h>
18 #include <libc/fcntl.h>
19 #include <math.h>
20 #include <string.h>
21 #include <sys/stat.h>
22 #include <time.h>
23 #include <wasi/api.h>
24 
25 // Uncomment the following and clear the cache with emcc --clear-cache to rebuild this file to
26 // enable internal debugging. #define ASMFS_DEBUG
27 
28 extern "C" {
29 
30 // http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
31 #define MAX_PATHNAME_LENGTH 2000
32 
33 #define INODE_TYPE uint32_t
34 #define INODE_FILE 1
35 #define INODE_DIR 2
36 
37 struct inode {
38   char name[NAME_MAX + 1]; // NAME_MAX actual bytes + one byte for null termination.
39   inode* parent;           // ID of the parent node
40   inode* sibling; // ID of a sibling node (these form a singular linked list that specifies the
41                   // content under a directory)
42   inode* child;   // ID of the first child node in a chain of children (the root of a linked list of
43                   // inodes)
44   uint32_t uid;   // User ID of the owner
45   uint32_t gid;   // Group ID of the owning group
46   uint32_t mode;  // r/w/x modes
47   time_t ctime;   // Time when the inode was last modified
48   time_t mtime;   // Time when the content was last modified
49   time_t atime;   // Time when the content was last accessed
50   size_t size;    // Size of the file in bytes
51   size_t capacity; // Amount of bytes allocated to pointer data
52   uint8_t* data;   // The actual file contents.
53 
54   INODE_TYPE type;
55 
56   emscripten_fetch_t* fetch;
57 
58   // Specifies a remote server address where this inode can be located.
59   char* remoteurl;
60 };
61 
62 #define EM_FILEDESCRIPTOR_MAGIC 0x64666d65U // 'emfd'
63 struct FileDescriptor {
64   uint32_t magic;
65   ssize_t file_pos;
66   uint32_t mode;
67   uint32_t flags;
68 
69   inode* node;
70 };
71 
create_inode(INODE_TYPE type,int mode)72 static inode* create_inode(INODE_TYPE type, int mode) {
73   inode* i = (inode*)malloc(sizeof(inode));
74   memset(i, 0, sizeof(inode));
75   i->ctime = i->mtime = i->atime = time(0);
76   i->type = type;
77   i->mode = mode;
78   return i;
79 }
80 
81 // The current working directory of the application process.
82 static inode* cwd_inode = 0;
83 
filesystem_root()84 static inode* filesystem_root() {
85   static inode* root_node = create_inode(INODE_DIR, 0777);
86   return root_node;
87 }
88 
get_cwd()89 static inode* get_cwd() {
90   if (!cwd_inode)
91     cwd_inode = filesystem_root();
92   return cwd_inode;
93 }
94 
set_cwd(inode * node)95 static void set_cwd(inode* node) { cwd_inode = node; }
96 
inode_abspath(inode * node,char * dst,int dstLen)97 static void inode_abspath(inode* node, char* dst, int dstLen) {
98   if (!node) {
99     assert(dstLen >= (int)strlen("(null)") + 1);
100     strcpy(dst, "(null)");
101     return;
102   }
103   if (node == filesystem_root()) {
104     assert(dstLen >= (int)strlen("/") + 1);
105     strcpy(dst, "/");
106     return;
107   }
108 #define MAX_DIRECTORY_DEPTH 512
109   inode* stack[MAX_DIRECTORY_DEPTH];
110   int depth = 0;
111   while (node->parent && depth < MAX_DIRECTORY_DEPTH) {
112     stack[depth++] = node;
113     node = node->parent;
114   }
115   char* dstEnd = dst + dstLen;
116   *dstEnd-- = '\0';
117   while (depth > 0 && dst < dstEnd) {
118     if (dst < dstEnd)
119       *dst++ = '/';
120     --depth;
121     int len = strlen(stack[depth]->name);
122     if (len > dstEnd - dst)
123       len = dstEnd - dst;
124     strncpy(dst, stack[depth]->name, len);
125     dst += len;
126   }
127 }
128 
129 // Deletes the given inode. Ignores (orphans) any children there might be
delete_inode(inode * node)130 static void delete_inode(inode* node) {
131   if (!node)
132     return;
133   if (node == filesystem_root())
134     return; // As special case, do not allow deleting the filesystem root directory
135 #ifdef ASMFS_DEBUG
136   EM_ASM(err('delete_inode: ' + UTF8ToString($0)), node->name);
137 #endif
138   if (node->fetch)
139     emscripten_fetch_close(node->fetch);
140   free(node->remoteurl);
141   free(node);
142 }
143 
144 // Deletes the given inode and its subtree
delete_inode_tree(inode * node)145 static void delete_inode_tree(inode* node) {
146   if (!node)
147     return;
148 #ifdef ASMFS_DEBUG
149   EM_ASM(err('delete_inode_tree: ' + UTF8ToString($0)), node->name);
150 #endif
151   inode* child = node->child;
152   while (child) {
153     inode* sibling = child->sibling;
154     delete_inode_tree(child->child);
155     delete_inode(child);
156     child = sibling;
157   }
158   if (node !=
159       filesystem_root()) // As special case, do not allow deleting the filesystem root directory
160   {
161     delete_inode(node);
162   } else {
163     // For filesystem root, just make sure all children are gone.
164     node->child = 0;
165   }
166 }
167 
168 // Makes node the child of parent.
link_inode(inode * node,inode * parent)169 static void link_inode(inode* node, inode* parent) {
170   char parentName[PATH_MAX];
171   inode_abspath(parent, parentName, PATH_MAX);
172 #ifdef ASMFS_DEBUG
173   EM_ASM(err('link_inode: node "' + UTF8ToString($0) + '" to parent "' + UTF8ToString($1) + '".'),
174     node->name, parentName);
175 #endif
176   // When linking a node, it can't be part of the filesystem tree (but it can have children of its
177   // own)
178   assert(!node->parent);
179   assert(!node->sibling);
180 
181   // The inode pointed by 'node' is not yet part of the filesystem, so it's not shared memory and
182   // only this thread is accessing it. Therefore setting the node's parent here is not yet racy, do
183   // that operation first.
184   node->parent = parent;
185 
186   // This node is to become the first child of the parent, and the old first child of the parent
187   // should become the sibling of this node, i.e.
188   //  1) node->sibling = parent->child;
189   //  2) parent->child = node;
190   // However these two operations need to occur atomically in order to be coherent. To ensure that,
191   // run the two operations in a CAS loop, which is possible because the first operation is not racy
192   // until the node is 'published' to the filesystem tree by the compare_exchange operation.
193   do {
194     __atomic_load(
195       &parent->child, &node->sibling, __ATOMIC_SEQ_CST); // node->sibling <- parent->child
196   } while (
197     !__atomic_compare_exchange(&parent->child, &node->sibling, &node, false, __ATOMIC_SEQ_CST,
198       __ATOMIC_SEQ_CST)); // parent->child <- node if it had not raced to change value in between
199 }
200 
201 // Traverse back in sibling linked list, or 0 if no such node exist.
find_predecessor_sibling(inode * node,inode * parent)202 static inode* find_predecessor_sibling(inode* node, inode* parent) {
203   inode* child = parent->child;
204   if (child == node)
205     return 0;
206   while (child && child->sibling != node)
207     child = child->sibling;
208   if (!child->sibling)
209     return 0;
210   return child;
211 }
212 
unlink_inode(inode * node)213 static void unlink_inode(inode* node) {
214 #ifdef ASMFS_DEBUG
215   EM_ASM(
216     err('unlink_inode: node ' + UTF8ToString($0) + ' from its parent ' + UTF8ToString($1) + '.'),
217     node->name, node->parent->name);
218 #endif
219   inode* parent = node->parent;
220   if (!parent)
221     return;
222   node->parent = 0;
223 
224   if (parent->child == node) {
225     parent->child = node->sibling;
226   } else {
227     inode* predecessor = find_predecessor_sibling(node, parent);
228     if (predecessor)
229       predecessor->sibling = node->sibling;
230   }
231   node->parent = node->sibling = 0;
232 }
233 
234 // Compares two strings for equality until a '\0' or a '/' is hit. Returns 0 if the strings differ,
235 // or a pointer to the beginning of the next directory component name of s1 if the strings are
236 // equal.
path_cmp(const char * s1,const char * s2,bool * is_directory)237 static const char* path_cmp(const char* s1, const char* s2, bool* is_directory) {
238   *is_directory = true;
239   while (*s1 == *s2) {
240     if (*s1 == '/')
241       return s1 + 1;
242     if (*s1 == '\0') {
243       *is_directory = false;
244       return s1;
245     }
246     ++s1;
247     ++s2;
248   }
249   if (*s1 == '/' && *s2 == '\0')
250     return s1 + 1;
251   if (*s1 == '\0' && *s2 == '/')
252     return s1;
253   return 0;
254 }
255 
256 #define NIBBLE_TO_CHAR(x) ("0123456789abcdef"[(x)])
uriEncode(char * dst,int dstLengthBytes,const char * src)257 static void uriEncode(char* dst, int dstLengthBytes, const char* src) {
258   char* end =
259     dst + dstLengthBytes - 4; // Use last 4 bytes of dst as a guard area to avoid overflow below.
260   while (*src && dst < end) {
261     if (isalnum(*src) || *src == '-' || *src == '_' || *src == '.' || *src == '~')
262       *dst++ = *src;
263     else if (*src == '/')
264       *dst++ = *src; // NB. forward slashes should generally be uriencoded, but for file path
265                      // purposes, we want to keep them intact.
266     else
267       *dst++ = '%', *dst++ = NIBBLE_TO_CHAR(*src >> 4),
268       *dst++ = NIBBLE_TO_CHAR(*src & 15); // This charater needs uriencoding.
269     ++src;
270   }
271   *dst = '\0';
272 }
273 
274 // Copies string 'path' to 'dst', but stops on the first forward slash '/' character.
275 // Returns number of bytes written, excluding null terminator
strcpy_inodename(char * dst,const char * path)276 static int strcpy_inodename(char* dst, const char* path) {
277   char* d = dst;
278   while (*path && *path != '/')
279     *dst++ = *path++;
280   *dst = '\0';
281   return dst - d;
282 }
283 
284 // Copies src to dst, writes at most maxBytesToWrite out. Always null terminates dst. Returns the
285 // number of characters written, excluding null terminator.
strcpy_safe(char * dst,const char * src,int maxBytesToWrite)286 static int strcpy_safe(char* dst, const char* src, int maxBytesToWrite) {
287   char* dst_start = dst;
288   char* dst_end = dst + maxBytesToWrite - 1;
289   while (dst < dst_end && *src)
290     *dst++ = *src++;
291   *dst = '\0';
292   return dst - dst_start;
293 }
294 
295 // Returns a pointer to the basename part of the string, i.e. the string after the last occurrence
296 // of a forward slash character
basename_part(const char * path)297 static const char* basename_part(const char* path) {
298   const char* s = path;
299   while (*path) {
300     if (*path == '/')
301       s = path + 1;
302     ++path;
303   }
304   return s;
305 }
306 
create_directory_hierarchy_for_file(inode * root,const char * path_to_file,unsigned int mode)307 static inode* create_directory_hierarchy_for_file(
308   inode* root, const char* path_to_file, unsigned int mode) {
309   assert(root);
310   if (!root)
311     return 0;
312 
313   // Traverse . and ..
314   while (path_to_file[0] == '.') {
315     if (path_to_file[1] == '/')
316       path_to_file += 2; // Skip over redundant "./././././" blocks
317     else if (path_to_file[1] == '\0')
318       path_to_file += 1;
319     else if (path_to_file[1] == '.' &&
320              (path_to_file[2] == '/' ||
321                path_to_file[2] == '\0')) // Go up to parent directories with ".."
322     {
323       root = root->parent;
324       if (!root)
325         return 0;
326       assert(
327         root->type == INODE_DIR); // Anything that is a parent should automatically be a directory.
328       path_to_file += (path_to_file[2] == '/') ? 3 : 2;
329     } else
330       break;
331   }
332   if (path_to_file[0] == '\0')
333     return 0;
334 
335   inode* node = root->child;
336   while (node) {
337     bool is_directory = false;
338     const char* child_path = path_cmp(path_to_file, node->name, &is_directory);
339 #ifdef ASMFS_DEBUG
340     EM_ASM_INT({err('path_cmp ' + UTF8ToString($0) + ', ' + UTF8ToString($1) + ', ' +
341                     UTF8ToString($2) + ' .')},
342       path_to_file, node->name, child_path);
343 #endif
344     if (child_path) {
345       if (is_directory && node->type != INODE_DIR)
346         return 0; // "A component used as a directory in pathname is not, in fact, a directory"
347 
348       // The directory name matches.
349       path_to_file = child_path;
350 
351       // Traverse . and ..
352       while (path_to_file[0] == '.') {
353         if (path_to_file[1] == '/')
354           path_to_file += 2; // Skip over redundant "./././././" blocks
355         else if (path_to_file[1] == '\0')
356           path_to_file += 1;
357         else if (path_to_file[1] == '.' &&
358                  (path_to_file[2] == '/' ||
359                    path_to_file[2] == '\0')) // Go up to parent directories with ".."
360         {
361           node = node->parent;
362           if (!node)
363             return 0;
364           assert(node->type ==
365                  INODE_DIR); // Anything that is a parent should automatically be a directory.
366           path_to_file += (path_to_file[2] == '/') ? 3 : 2;
367         } else
368           break;
369       }
370       if (path_to_file[0] == '\0')
371         return node;
372       if (path_to_file[0] == '/' && path_to_file[1] == '\0' /* && node is a directory*/)
373         return node;
374       root = node;
375       node = node->child;
376     } else {
377       node = node->sibling;
378     }
379   }
380   const char* basename_pos = basename_part(path_to_file);
381 #ifdef ASMFS_DEBUG
382   EM_ASM(err('path_to_file ' + UTF8ToString($0) + ' .'), path_to_file);
383   EM_ASM(err('basename_pos ' + UTF8ToString($0) + ' .'), basename_pos);
384 #endif
385   while (*path_to_file && path_to_file < basename_pos) {
386     node = create_inode(INODE_DIR, mode);
387     path_to_file += strcpy_inodename(node->name, path_to_file) + 1;
388     link_inode(node, root);
389 #ifdef ASMFS_DEBUG
390     EM_ASM(out('create_directory_hierarchy_for_file: created directory ' + UTF8ToString($0) +
391                ' under parent ' + UTF8ToString($1) + '.'),
392       node->name, node->parent->name);
393 #endif
394     root = node;
395   }
396   return root;
397 }
398 
399 #define RETURN_NODE_AND_ERRNO(node, errno)                                                         \
400   do {                                                                                             \
401     *out_errno = (errno);                                                                          \
402     return (node);                                                                                 \
403   } while (0)
404 
405 // Given a pathname to a file/directory, finds the inode of the directory that would contain the
406 // file/directory, or 0 if the intermediate path doesn't exist. Note that the file/directory pointed
407 // to by path does not need to exist, only its parent does.
find_parent_inode(inode * root,const char * path,int * out_errno)408 static inode* find_parent_inode(inode* root, const char* path, int* out_errno) {
409   char rootName[PATH_MAX];
410   inode_abspath(root, rootName, PATH_MAX);
411 #ifdef ASMFS_DEBUG
412   EM_ASM(err('find_parent_inode(root="' + UTF8ToString($0) + '", path="' + UTF8ToString($1) + '")'),
413     rootName, path);
414 #endif
415 
416   assert(out_errno); // Passing in error is mandatory.
417 
418   if (!root)
419     RETURN_NODE_AND_ERRNO(0, ENOENT);
420   if (!path)
421     RETURN_NODE_AND_ERRNO(0, ENOENT);
422 
423   // Traverse . and ..
424   while (path[0] == '.') {
425     if (path[1] == '/')
426       path += 2; // Skip over redundant "./././././" blocks
427     else if (path[1] == '\0')
428       path += 1;
429     else if (path[1] == '.' &&
430              (path[2] == '/' || path[2] == '\0')) // Go up to parent directories with ".."
431     {
432       root = root->parent;
433       if (!root)
434         RETURN_NODE_AND_ERRNO(0, ENOENT);
435       assert(
436         root->type == INODE_DIR); // Anything that is a parent should automatically be a directory.
437       path += (path[2] == '/') ? 3 : 2;
438     } else
439       break;
440   }
441   if (path[0] == '\0')
442     RETURN_NODE_AND_ERRNO(0, ENOENT);
443   if (path[0] == '/' && path[1] == '\0')
444     RETURN_NODE_AND_ERRNO(0, ENOENT);
445   if (root->type != INODE_DIR)
446     RETURN_NODE_AND_ERRNO(
447       0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
448 
449   // TODO: RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in translating pathname");
450   // TODO: RETURN_ERRNO(EACCES, "one of the directories in the path prefix of pathname did not allow
451   // search permission");
452 
453   const char* basename = basename_part(path);
454   if (path == basename)
455     RETURN_NODE_AND_ERRNO(root, 0);
456   inode* node = root->child;
457   while (node) {
458     bool is_directory = false;
459     const char* child_path = path_cmp(path, node->name, &is_directory);
460     if (child_path) {
461       if (is_directory && node->type != INODE_DIR)
462         RETURN_NODE_AND_ERRNO(
463           0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
464 
465       // The directory name matches.
466       path = child_path;
467 
468       // Traverse . and ..
469       while (path[0] == '.') {
470         if (path[1] == '/')
471           path += 2; // Skip over redundant "./././././" blocks
472         else if (path[1] == '\0')
473           path += 1;
474         else if (path[1] == '.' &&
475                  (path[2] == '/' || path[2] == '\0')) // Go up to parent directories with ".."
476         {
477           node = node->parent;
478           if (!node)
479             RETURN_NODE_AND_ERRNO(0, ENOENT);
480           assert(node->type ==
481                  INODE_DIR); // Anything that is a parent should automatically be a directory.
482           path += (path[2] == '/') ? 3 : 2;
483         } else
484           break;
485       }
486 
487       if (path >= basename)
488         RETURN_NODE_AND_ERRNO(node, 0);
489       if (!*path)
490         RETURN_NODE_AND_ERRNO(0, ENOENT);
491       node = node->child;
492       if (node->type != INODE_DIR)
493         RETURN_NODE_AND_ERRNO(
494           0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
495     } else {
496       node = node->sibling;
497     }
498   }
499   RETURN_NODE_AND_ERRNO(
500     0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
501 }
502 
503 // Given a root inode of the filesystem and a path relative to it, e.g.
504 // "some/directory/dir_or_file", returns the inode that corresponds to "dir_or_file", or 0 if it
505 // doesn't exist. If the parameter out_closest_parent is specified, the closest (grand)parent node
506 // will be returned.
find_inode(inode * root,const char * path,int * out_errno)507 static inode* find_inode(inode* root, const char* path, int* out_errno) {
508   char rootName[PATH_MAX];
509   inode_abspath(root, rootName, PATH_MAX);
510 #ifdef ASMFS_DEBUG
511   EM_ASM(err('find_inode(root="' + UTF8ToString($0) + '", path="' + UTF8ToString($1) + '")'),
512     rootName, path);
513 #endif
514 
515   assert(out_errno); // Passing in error is mandatory.
516 
517   if (!root)
518     RETURN_NODE_AND_ERRNO(0, ENOENT);
519 
520   // TODO: RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in translating pathname");
521   // TODO: RETURN_ERRNO(EACCES, "one of the directories in the path prefix of pathname did not allow
522   // search permission");
523 
524   // special-case finding empty string path "", "." or "/" returns the root searched in.
525   if (root->type != INODE_DIR)
526     RETURN_NODE_AND_ERRNO(
527       0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
528   if (!path)
529     RETURN_NODE_AND_ERRNO(root, 0);
530 
531   // Traverse . and ..
532   while (path[0] == '.') {
533     if (path[1] == '/')
534       path += 2; // Skip over redundant "./././././" blocks
535     else if (path[1] == '\0')
536       path += 1;
537     else if (path[1] == '.' &&
538              (path[2] == '/' || path[2] == '\0')) // Go up to parent directories with ".."
539     {
540       root = root->parent;
541       if (!root)
542         RETURN_NODE_AND_ERRNO(0, ENOENT);
543       assert(
544         root->type == INODE_DIR); // Anything that is a parent should automatically be a directory.
545       path += (path[2] == '/') ? 3 : 2;
546     } else
547       break;
548   }
549   if (path[0] == '\0')
550     RETURN_NODE_AND_ERRNO(root, 0);
551 
552   inode* node = root->child;
553   while (node) {
554     bool is_directory = false;
555     const char* child_path = path_cmp(path, node->name, &is_directory);
556     if (child_path) {
557       if (is_directory && node->type != INODE_DIR)
558         RETURN_NODE_AND_ERRNO(
559           0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
560 
561       // The directory name matches.
562       path = child_path;
563 
564       // Traverse . and ..
565       while (path[0] == '.') {
566         if (path[1] == '/')
567           path += 2; // Skip over redundant "./././././" blocks
568         else if (path[1] == '\0')
569           path += 1;
570         else if (path[1] == '.' &&
571                  (path[2] == '/' || path[2] == '\0')) // Go up to parent directories with ".."
572         {
573           node = node->parent;
574           if (!node)
575             RETURN_NODE_AND_ERRNO(0, ENOENT);
576           assert(node->type ==
577                  INODE_DIR); // Anything that is a parent should automatically be a directory.
578           path += (path[2] == '/') ? 3 : 2;
579         } else
580           break;
581       }
582 
583       // If we arrived to the end of the search, this is the node we were looking for.
584       if (path[0] == '\0')
585         RETURN_NODE_AND_ERRNO(node, 0);
586       if (path[0] == '/' && node->type != INODE_DIR)
587         RETURN_NODE_AND_ERRNO(
588           0, ENOTDIR); // "A component used as a directory in pathname is not, in fact, a directory"
589       if (path[0] == '/' && path[1] == '\0')
590         RETURN_NODE_AND_ERRNO(node, 0);
591       node = node->child;
592     } else {
593       node = node->sibling;
594     }
595   }
596   RETURN_NODE_AND_ERRNO(0, ENOENT);
597 }
598 
599 // Same as above, but the root node is deduced from 'path'. (either absolute if path starts with
600 // "/", or relative)
find_inode(const char * path,int * out_errno)601 static inode* find_inode(const char* path, int* out_errno) {
602   inode* root;
603   if (path[0] == '/')
604     root = filesystem_root(), ++path;
605   else
606     root = get_cwd();
607   return find_inode(root, path, out_errno);
608 }
609 
emscripten_asmfs_set_remote_url(const char * filename,const char * remoteUrl)610 void emscripten_asmfs_set_remote_url(const char* filename, const char* remoteUrl) {
611   int err;
612   inode* node = find_inode(filename, &err);
613   if (!node)
614     return;
615   free(node->remoteurl);
616   node->remoteurl = strdup(remoteUrl);
617 }
618 
emscripten_asmfs_set_file_data(const char * filename,char * data,size_t size)619 void emscripten_asmfs_set_file_data(const char* filename, char* data, size_t size) {
620   int err;
621   inode* node = find_inode(filename, &err);
622   if (!node) {
623     free(data);
624     return;
625   }
626   free(node->data);
627   node->data = (uint8_t*)data;
628   node->size = node->capacity = size;
629 }
630 
find_last_occurrence(char * str,char ch)631 char* find_last_occurrence(char* str, char ch) {
632   char* o = 0;
633   while (*str) {
634     if (*str == ch)
635       o = str;
636     ++str;
637   }
638   return o;
639 }
640 
641 // Given a filename outputs the remote URL address that file can be located in.
emscripten_asmfs_remote_url(const char * filename,char * outRemoteUrl,int maxBytesToWrite)642 void emscripten_asmfs_remote_url(const char* filename, char* outRemoteUrl, int maxBytesToWrite) {
643   if (maxBytesToWrite <= 0 || !outRemoteUrl)
644     return;
645   *outRemoteUrl = '\0';
646   if (maxBytesToWrite == 1)
647     return;
648 
649   char trailing_path[PATH_MAX + 1] = {};
650   char full_path[PATH_MAX + 1] = {};
651   char full_path_temp[PATH_MAX + 1] = {};
652   strcpy(full_path, filename);
653 
654   int err;
655   inode* node = find_inode(full_path, &err);
656   while (!node) {
657     char* s = find_last_occurrence(full_path, '/');
658     if (!s) {
659       node = filesystem_root();
660       strcpy(full_path_temp, trailing_path);
661       strcpy(trailing_path, full_path);
662       if (full_path_temp[0] != '\0') {
663         strcat(trailing_path, "/");
664         strcat(trailing_path, full_path_temp);
665       }
666       break;
667     }
668     *s = '\0';
669     node = find_inode(full_path, &err);
670 
671     strcpy(full_path_temp, trailing_path);
672     strcpy(trailing_path, filename + (s - full_path));
673     if (full_path_temp[0] != '\0') {
674       strcat(trailing_path, "/");
675       strcat(trailing_path, full_path_temp);
676     }
677   }
678 
679   char uriEncodedPathName[3 * PATH_MAX + 4];
680   full_path[0] = full_path[PATH_MAX] = full_path_temp[0] = full_path_temp[PATH_MAX] = '\0';
681 
682   while (node) {
683     if (node->remoteurl && node->remoteurl[0] != '\0') {
684       int nWritten = strcpy_safe(outRemoteUrl, node->remoteurl, maxBytesToWrite);
685       if (maxBytesToWrite - nWritten > 1 && outRemoteUrl[nWritten - 1] != '/' &&
686           full_path[0] != '/') {
687         outRemoteUrl[nWritten++] = '/';
688         outRemoteUrl[nWritten] = '\0';
689       }
690       strcat(full_path + strlen(full_path), trailing_path);
691       uriEncode(uriEncodedPathName, 3 * PATH_MAX + 4, full_path);
692       strcpy_safe(outRemoteUrl + nWritten,
693         (outRemoteUrl[nWritten - 1] == '/' && uriEncodedPathName[0] == '/')
694           ? (uriEncodedPathName + 1)
695           : uriEncodedPathName,
696         maxBytesToWrite - nWritten);
697       return;
698     }
699 
700     strcpy_safe(full_path_temp, full_path, PATH_MAX);
701     int nWritten = strcpy_safe(full_path, node->name, PATH_MAX);
702     if (full_path_temp[0] != '\0') {
703       full_path[nWritten++] = '/';
704       full_path[nWritten] = '\0';
705       strcpy_safe(full_path + nWritten, full_path_temp, PATH_MAX - nWritten);
706     }
707 
708     node = node->parent;
709   }
710   strcat(full_path + strlen(full_path), trailing_path);
711   uriEncode(uriEncodedPathName, 3 * PATH_MAX + 4, full_path);
712   strcpy_safe(outRemoteUrl, uriEncodedPathName, maxBytesToWrite);
713 }
714 
715 // Debug function that dumps out the filesystem tree to console.
emscripten_dump_fs_tree(inode * root,char * path)716 void emscripten_dump_fs_tree(inode* root, char* path) {
717   char str[256];
718   sprintf(str, "%s:", path);
719   EM_ASM(out(UTF8ToString($0)), str);
720 
721   // Print out:
722   // file mode | number of links | owner name | group name | file size in bytes | file last modified
723   // time | path name which aligns with "ls -AFTRl" on console
724   inode* child = root->child;
725   uint64_t totalSize = 0;
726   while (child) {
727     sprintf(str, "%c%c%c%c%c%c%c%c%c%c  %d user%u group%u %lu Jan 1 1970 %s%c",
728       child->type == INODE_DIR ? 'd' : '-', (child->mode & S_IRUSR) ? 'r' : '-',
729       (child->mode & S_IWUSR) ? 'w' : '-', (child->mode & S_IXUSR) ? 'x' : '-',
730       (child->mode & S_IRGRP) ? 'r' : '-', (child->mode & S_IWGRP) ? 'w' : '-',
731       (child->mode & S_IXGRP) ? 'x' : '-', (child->mode & S_IROTH) ? 'r' : '-',
732       (child->mode & S_IWOTH) ? 'w' : '-', (child->mode & S_IXOTH) ? 'x' : '-',
733       1, // number of links to this file
734       child->uid, child->gid,
735       child->size ? child->size : (child->fetch ? (int)child->fetch->numBytes : 0), child->name,
736       child->type == INODE_DIR ? '/' : ' ');
737     EM_ASM(out(UTF8ToString($0)), str);
738 
739     totalSize += child->size;
740     child = child->sibling;
741   }
742 
743   sprintf(str, "total %llu bytes\n", totalSize);
744   EM_ASM(out(UTF8ToString($0)), str);
745 
746   child = root->child;
747   char* path_end = path + strlen(path);
748   while (child) {
749     if (child->type == INODE_DIR) {
750       strcpy(path_end, child->name);
751       strcat(path_end, "/");
752       emscripten_dump_fs_tree(child, path);
753     }
754     child = child->sibling;
755   }
756 }
757 
emscripten_asmfs_dump()758 void emscripten_asmfs_dump() {
759   EM_ASM({err('emscripten_asmfs_dump()')});
760   char path[PATH_MAX] = "/";
761   emscripten_dump_fs_tree(filesystem_root(), path);
762 }
763 
emscripten_asmfs_discard_tree(const char * path)764 void emscripten_asmfs_discard_tree(const char* path) {
765 #ifdef ASMFS_DEBUG
766   emscripten_asmfs_dump();
767   EM_ASM(err('emscripten_asmfs_discard_tree: ' + UTF8ToString($0)), path);
768 #endif
769   int err;
770   inode* node = find_inode(path, &err);
771   if (node && !err) {
772     unlink_inode(node);
773     delete_inode_tree(node);
774   }
775 #ifdef ASMFS_DEBUG
776   else
777     EM_ASM(err('emscripten_asmfs_discard_tree failed, error ' + $0), err);
778   emscripten_asmfs_dump();
779 #endif
780 }
781 
782 #ifdef ASMFS_DEBUG
783 #define RETURN_ERRNO(errno, error_reason)                                                          \
784   do {                                                                                             \
785     EM_ASM(err(UTF8ToString($0) + '() returned errno ' + #errno + '(' + $1 + '): ' +               \
786                error_reason + '!'),                                                                \
787       __FUNCTION__, errno);                                                                        \
788     return -errno;                                                                                 \
789   } while (0)
790 #else
791 #define RETURN_ERRNO(errno, error_reason)                                                          \
792   do {                                                                                             \
793     return -(errno);                                                                               \
794   } while (0)
795 #endif
796 
797 static char stdout_buffer[4096] = {};
798 static int stdout_buffer_end = 0;
799 static char stderr_buffer[4096] = {};
800 static int stderr_buffer_end = 0;
801 
print_stream(void * bytes,int numBytes,bool stdout)802 static void print_stream(void* bytes, int numBytes, bool stdout) {
803   char* buffer = stdout ? stdout_buffer : stderr_buffer;
804   int& buffer_end = stdout ? stdout_buffer_end : stderr_buffer_end;
805 
806   memcpy(buffer + buffer_end, bytes, numBytes);
807   buffer_end += numBytes;
808   int new_buffer_start = 0;
809   for (int i = 0; i < buffer_end; ++i) {
810     if (buffer[i] == '\n') {
811       buffer[i] = 0;
812       EM_ASM_INT({out(UTF8ToString($0))}, buffer + new_buffer_start);
813       new_buffer_start = i + 1;
814     }
815   }
816   size_t new_buffer_size = buffer_end - new_buffer_start;
817   memmove(buffer, buffer + new_buffer_start, new_buffer_size);
818   buffer_end = new_buffer_size;
819 }
820 
821 // TODO: Make thread-local storage.
822 static emscripten_asmfs_open_t __emscripten_asmfs_file_open_behavior_mode =
823   EMSCRIPTEN_ASMFS_OPEN_REMOTE_DISCOVER;
824 
emscripten_asmfs_set_file_open_behavior(emscripten_asmfs_open_t behavior)825 void emscripten_asmfs_set_file_open_behavior(emscripten_asmfs_open_t behavior) {
826   __emscripten_asmfs_file_open_behavior_mode = behavior;
827 }
828 
emscripten_asmfs_get_file_open_behavior()829 emscripten_asmfs_open_t emscripten_asmfs_get_file_open_behavior() {
830   return __emscripten_asmfs_file_open_behavior_mode;
831 }
832 
833 // Returns true if the given file can be synchronously read by the main browser thread.
emscripten_asmfs_file_is_synchronously_accessible(inode * node)834 static bool emscripten_asmfs_file_is_synchronously_accessible(inode* node) {
835   return node->data // If file was created from memory without XHR, e.g. via fopen("foo.txt", "w"),
836                     // it will have node->data ptr backing.
837          ||
838          (node->fetch && node->fetch->data); // If the file was downloaded, it will be backed here.
839 }
840 
open(const char * pathname,int flags,int mode)841 static long open(const char* pathname, int flags, int mode) {
842 #ifdef ASMFS_DEBUG
843   EM_ASM(err('open(pathname="' + UTF8ToString($0) + '", flags=0x' + ($1).toString(16) + ', mode=0' +
844              ($2).toString(8) + ')'),
845     pathname, flags, mode);
846 #endif
847 
848   int accessMode = (flags & O_ACCMODE);
849 
850   if ((flags & O_ASYNC))
851     RETURN_ERRNO(ENOTSUP, "TODO: Opening files with O_ASYNC flag is not supported in ASMFS");
852   if ((flags & O_DIRECT))
853     RETURN_ERRNO(ENOTSUP, "TODO: O_DIRECT flag is not supported in ASMFS");
854   if ((flags & O_DSYNC))
855     RETURN_ERRNO(ENOTSUP, "TODO: O_DSYNC flag is not supported in ASMFS");
856 
857   // Spec says that the result of O_EXCL without O_CREAT is undefined.
858   // We could enforce it as an error condition, as follows:
859   //	if ((flags & O_EXCL) && !(flags & O_CREAT)) RETURN_ERRNO(EINVAL, "open() with O_EXCL flag
860   //needs to always be paired with O_CREAT");
861   // However existing earlier unit tests in Emscripten expect that O_EXCL is simply ignored when
862   // O_CREAT was not passed. So do that for now.
863   if ((flags & O_EXCL) && !(flags & O_CREAT)) {
864 #ifdef ASMFS_DEBUG
865     EM_ASM(err('warning: open(pathname="' + UTF8ToString($0) + '", flags=0x' + ($1).toString(16) +
866                ', mode=0' + ($2).toString(8) +
867                ': flag O_EXCL should always be paired with O_CREAT. Ignoring O_EXCL)'),
868       pathname, flags, mode);
869 #endif
870     flags &= ~O_EXCL;
871   }
872 
873   if ((flags & (O_NONBLOCK | O_NDELAY)))
874     RETURN_ERRNO(
875       ENOTSUP, "TODO: Opening files with O_NONBLOCK or O_NDELAY flags is not supported in ASMFS");
876   if ((flags & O_PATH))
877     RETURN_ERRNO(ENOTSUP, "TODO: Opening files with O_PATH flag is not supported in ASMFS");
878   if ((flags & O_SYNC))
879     RETURN_ERRNO(ENOTSUP, "TODO: Opening files with O_SYNC flag is not supported in ASMFS");
880 
881   // The flags:O_CLOEXEC flag is ignored, doesn't have meaning for Emscripten
882 
883   // TODO: the flags:O_DIRECT flag seems like a great way to let applications explicitly control
884   // XHR/IndexedDB read/write buffering behavior?
885 
886   // The flags:O_LARGEFILE flag is ignored, we should always be largefile-compatible
887 
888   // TODO: The flags:O_NOATIME is ignored, file access times have not been implemented yet
889   // The flags O_NOCTTY, O_NOFOLLOW
890 
891   if ((flags & O_TMPFILE)) {
892     if (accessMode != O_WRONLY && accessMode != O_RDWR)
893       RETURN_ERRNO(
894         EINVAL, "O_TMPFILE was specified in flags, but neither O_WRONLY nor O_RDWR was specified");
895     else
896       RETURN_ERRNO(
897         EOPNOTSUPP, "TODO: The filesystem containing pathname does not support O_TMPFILE");
898   }
899 
900   // TODO: if (too_many_files_open) RETURN_ERRNO(EMFILE, "The per-process limit on the number of
901   // open file descriptors has been reached, see getrlimit(RLIMIT_NOFILE)");
902 
903   int len = strlen(pathname);
904   if (len > MAX_PATHNAME_LENGTH)
905     RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
906   if (len == 0)
907     RETURN_ERRNO(ENOENT, "pathname is empty");
908 
909   // Find if this file exists already in the filesystem?
910   inode* root = (pathname[0] == '/') ? filesystem_root() : get_cwd();
911   const char* relpath = (pathname[0] == '/') ? pathname + 1 : pathname;
912 
913   int err;
914   inode* node = find_inode(root, relpath, &err);
915   if (err == ENOTDIR)
916     RETURN_ERRNO(
917       ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
918   if (err == ELOOP)
919     RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
920   if (err == EACCES)
921     RETURN_ERRNO(EACCES,
922       "Search permission is denied for one of the directories in the path prefix of pathname");
923   if (err && err != ENOENT)
924     RETURN_ERRNO(err, "find_inode() error");
925   if (node) {
926     if ((flags & O_DIRECTORY) && node->type != INODE_DIR)
927       RETURN_ERRNO(ENOTDIR, "O_DIRECTORY was specified and pathname was not a directory");
928     if (!(node->mode & 0444))
929       RETURN_ERRNO(EACCES, "The requested access to the file is not allowed");
930     if ((flags & O_CREAT) && (flags & O_EXCL))
931       RETURN_ERRNO(EEXIST, "pathname already exists and O_CREAT and O_EXCL were used");
932     if (node->type == INODE_DIR && accessMode != O_RDONLY)
933       RETURN_ERRNO(EISDIR, "pathname refers to a directory and the access requested involved writing (that is, O_WRONLY or O_RDWR is set)");
934     if (node->type == INODE_DIR && (flags & O_TRUNC))
935       RETURN_ERRNO(EISDIR,
936         "pathname refers to a directory and the access flags specified invalid flag O_TRUNC");
937 
938     // A current download exists to the file? Then wait for it to complete.
939     if (node->fetch) {
940       // On the main thread, the fetch must have already completed before we come here. If not, we
941       // cannot stop to wait for it to finish, and must return a failure (file not found)
942       if (emscripten_is_main_browser_thread()) {
943         if (emscripten_fetch_wait(node->fetch, 0) != EMSCRIPTEN_RESULT_SUCCESS) {
944           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)");
945         }
946       } else {
947         // On worker threads, we can pause to wait for the fetch.
948         emscripten_fetch_wait(node->fetch, INFINITY);
949       }
950     }
951   }
952 
953   if ((flags & O_CREAT) && ((flags & O_TRUNC) || (flags & O_EXCL))) {
954     // Create a new empty file or truncate existing one.
955     if (node) {
956       if (node->fetch)
957         emscripten_fetch_close(node->fetch);
958       node->fetch = 0;
959       node->size = 0;
960     } else if ((flags & O_CREAT)) {
961       inode* directory = create_directory_hierarchy_for_file(root, relpath, mode);
962       node = create_inode((flags & O_DIRECTORY) ? INODE_DIR : INODE_FILE, mode);
963       strcpy(node->name, basename_part(pathname));
964       link_inode(node, directory);
965     }
966   } else if (!node || (node->type == INODE_FILE && !node->fetch && !node->data)) {
967     emscripten_fetch_t* fetch = 0;
968     if (!(flags & O_DIRECTORY) && accessMode != O_WRONLY) // Opening a file for reading?
969     {
970       // If there's no inode entry, check if we're not even interested in downloading the file?
971       if (!node &&
972           __emscripten_asmfs_file_open_behavior_mode != EMSCRIPTEN_ASMFS_OPEN_REMOTE_DISCOVER) {
973         RETURN_ERRNO(
974           ENOENT, "O_CREAT is not set, the named file does not exist in local filesystem and EMSCRIPTEN_ASMFS_OPEN_REMOTE_DISCOVER is not specified");
975       }
976 
977       // Report an error if there is an inode entry, but file data is not synchronously available
978       // and it should have been.
979       if (node && !node->data &&
980           __emscripten_asmfs_file_open_behavior_mode == EMSCRIPTEN_ASMFS_OPEN_MEMORY) {
981         RETURN_ERRNO(
982           ENOENT, "O_CREAT is not set, the named file exists, but file data is not synchronously available in memory (EMSCRIPTEN_ASMFS_OPEN_MEMORY specified)");
983       }
984 
985       if (emscripten_is_main_browser_thread() &&
986           (!node || !emscripten_asmfs_file_is_synchronously_accessible(node))) {
987         RETURN_ERRNO(ENOENT,
988           "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)");
989       }
990 
991       // Kick off the file download, either from IndexedDB or via an XHR.
992       emscripten_fetch_attr_t attr;
993       emscripten_fetch_attr_init(&attr);
994       strcpy(attr.requestMethod, "GET");
995       attr.attributes = EMSCRIPTEN_FETCH_APPEND | EMSCRIPTEN_FETCH_LOAD_TO_MEMORY |
996                         EMSCRIPTEN_FETCH_WAITABLE | EMSCRIPTEN_FETCH_PERSIST_FILE;
997       // If asked to only do a read from IndexedDB, don't perform an XHR.
998       if (__emscripten_asmfs_file_open_behavior_mode == EMSCRIPTEN_ASMFS_OPEN_INDEXEDDB) {
999         attr.attributes |= EMSCRIPTEN_FETCH_NO_DOWNLOAD;
1000       }
1001       char
1002         path[3 * PATH_MAX + 4]; // times 3 because uri-encoding can expand the filename at most 3x.
1003       emscripten_asmfs_remote_url(pathname, path, 3 * PATH_MAX + 4);
1004       fetch = emscripten_fetch(&attr, path);
1005 
1006       // Synchronously wait for the fetch to complete.
1007       // NOTE: Theoretically could postpone blocking until the first read to the file, but the issue
1008       // there is that we wouldn't be able to return ENOENT below if the file did not exist on the
1009       // server, which could be harmful for some applications. Also fread()/fseek() very often
1010       // immediately follows fopen(), so the win would not be too great anyways.
1011       emscripten_fetch_wait(fetch, INFINITY);
1012 
1013       if (!(flags & O_CREAT) && (fetch->status != 200 || fetch->totalBytes == 0)) {
1014         emscripten_fetch_close(fetch);
1015         RETURN_ERRNO(ENOENT, "O_CREAT is not set and the named file does not exist (attempted emscripten_fetch() XHR to download)");
1016       }
1017     }
1018 
1019     if (node) {
1020       // If we had an existing inode entry, just associate the entry with the newly fetched data.
1021       if (node->type == INODE_FILE)
1022         node->fetch = fetch;
1023     } else if ((flags &
1024                  O_CREAT) // If the filesystem entry did not exist, but we have a create flag, ...
1025                || (!node && fetch)) // ... or if it did not exist in our fs, but it could be found
1026                                     // via fetch(), ...
1027     {
1028       // ... add it as a new entry to the fs.
1029       inode* directory = create_directory_hierarchy_for_file(root, relpath, mode);
1030       node = create_inode((flags & O_DIRECTORY) ? INODE_DIR : INODE_FILE, mode);
1031       strcpy(node->name, basename_part(pathname));
1032       node->fetch = fetch;
1033       link_inode(node, directory);
1034     } else {
1035       if (fetch)
1036         emscripten_fetch_close(fetch);
1037       RETURN_ERRNO(ENOENT, "O_CREAT is not set and the named file does not exist");
1038     }
1039     node->size = fetch ? node->fetch->totalBytes : 0;
1040   }
1041 
1042   FileDescriptor* desc = (FileDescriptor*)malloc(sizeof(FileDescriptor));
1043   desc->magic = EM_FILEDESCRIPTOR_MAGIC;
1044   desc->node = node;
1045   desc->file_pos = ((flags & O_APPEND) && node->fetch) ? node->fetch->totalBytes : 0;
1046   desc->mode = mode;
1047   desc->flags = flags;
1048 
1049   // TODO: The file descriptor needs to be a small number, man page:
1050   // "a small, nonnegative integer for use in subsequent system calls
1051   // (read(2), write(2), lseek(2), fcntl(2), etc.).  The file descriptor
1052   // returned by a successful call will be the lowest-numbered file
1053   // descriptor not currently open for the process."
1054   return (long)desc;
1055 }
1056 
__syscall5(long path,long flags,...)1057 long __syscall5(long path, long flags, ...) // open
1058 {
1059   va_list vl;
1060   va_start(vl, flags);
1061   int mode = va_arg(vl, int);
1062   va_end(vl);
1063 
1064   return open((const char *)path, flags, mode);
1065 }
1066 
close(int fd)1067 static long close(int fd) {
1068 #ifdef ASMFS_DEBUG
1069   EM_ASM(err('close(fd=' + $0 + ')'), fd);
1070 #endif
1071 
1072   FileDescriptor* desc = (FileDescriptor*)fd;
1073   if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
1074     RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
1075 
1076   if (desc->node && desc->node->fetch) {
1077     if (!emscripten_is_main_browser_thread()) {
1078       // TODO: This should not be necessary, but do it for now for consistency (test this out)
1079       emscripten_fetch_wait(desc->node->fetch, INFINITY);
1080     }
1081 
1082     // TODO: What to do to a XHRed/IndexedDB-backed unmodified file in memory when closing the file?
1083     //       free() or keep in memory?
1084     //       If user intends to reopen the file later (possibly often?), it is faster to keep the
1085     //       file in memory. (but it will consume more memory) If running on the main thread, the
1086     //       file cannot be loaded back synchronously if we let go of it, so things would break if
1087     //       the file is attempted to be loaded up again afterwards. Currently use a heuristic that
1088     //       if a file is closed on the main browser thread, do not free its backing storage. This
1089     //       can work for many
1090     //		 cases, but some kind of custom API might be best to add in the future? (e.g.
1091     //emscripten_fclose_and_retain() vs emscripten_fclose_and_free()?)
1092     if (!emscripten_is_main_browser_thread()) {
1093       emscripten_fetch_close(desc->node->fetch);
1094       desc->node->fetch = 0;
1095     }
1096   }
1097   desc->magic = 0;
1098   free(desc);
1099   return 0;
1100 }
1101 
emscripten_asmfs_populate(const char * pathname,int mode)1102 void emscripten_asmfs_populate(const char* pathname, int mode) {
1103   emscripten_asmfs_open_t prevBehavior = emscripten_asmfs_get_file_open_behavior();
1104   emscripten_asmfs_set_file_open_behavior(EMSCRIPTEN_ASMFS_OPEN_MEMORY);
1105   int fd = open(pathname, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, mode);
1106   if (fd > 0) {
1107     close(fd);
1108   }
1109   emscripten_asmfs_set_file_open_behavior(prevBehavior);
1110 }
1111 
emscripten_asmfs_preload_file(const char * url,const char * pathname,int mode,emscripten_fetch_attr_t * options)1112 EMSCRIPTEN_RESULT emscripten_asmfs_preload_file(
1113   const char* url, const char* pathname, int mode, emscripten_fetch_attr_t* options) {
1114   if (!options) {
1115 #ifdef ASMFS_DEBUG
1116     EM_ASM(err('emscripten_asmfs_preload_file: options not specified!'));
1117 #endif
1118     return EMSCRIPTEN_RESULT_INVALID_PARAM;
1119   }
1120 
1121   if (!pathname) {
1122 #ifdef ASMFS_DEBUG
1123     EM_ASM(err('emscripten_asmfs_preload_file: pathname not specified!'));
1124 #endif
1125     return EMSCRIPTEN_RESULT_INVALID_PARAM;
1126   }
1127 
1128   // Find if this file exists already in the filesystem?
1129   inode* root = (pathname[0] == '/') ? filesystem_root() : get_cwd();
1130   const char* relpath = (pathname[0] == '/') ? pathname + 1 : pathname;
1131 
1132   int err;
1133   inode* node = find_inode(root, relpath, &err);
1134   // Filesystem traversal error?
1135   if (err && err != ENOENT) {
1136 #ifdef ASMFS_DEBUG
1137     EM_ASM(err('emscripten_asmfs_preload_file: find_inode error ' + $0 + '!'), err);
1138 #endif
1139     return EMSCRIPTEN_RESULT_INVALID_TARGET;
1140   }
1141 
1142   if (node && emscripten_asmfs_file_is_synchronously_accessible(node)) {
1143     // The file already exists, and its contents have already been preloaded - immediately fire the
1144     // success callback
1145     if (options->onsuccess)
1146       options->onsuccess(0);
1147     return EMSCRIPTEN_RESULT_SUCCESS;
1148   }
1149 
1150   // Kick off the file download, either from IndexedDB or via an XHR.
1151   emscripten_fetch_attr_t attr;
1152   memcpy(&attr, options, sizeof(emscripten_fetch_attr_t));
1153   if (strlen(attr.requestMethod) == 0)
1154     strcpy(attr.requestMethod, "GET");
1155   // In order for the file data to be synchronously accessible to the main browser thread, must load
1156   // it directly to memory.
1157   attr.attributes |= EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
1158   // The following attributes cannot be present for preloading.
1159 #ifdef ASMFS_DEBUG
1160   if ((attr.attributes & (EMSCRIPTEN_FETCH_SYNCHRONOUS | EMSCRIPTEN_FETCH_STREAM_DATA |
1161                            EMSCRIPTEN_FETCH_WAITABLE)) != 0)
1162     EM_ASM(err(
1163       'emscripten_asmfs_preload_file: cannot specify EMSCRIPTEN_FETCH_SYNCHRONOUS, EMSCRIPTEN_FETCH_STREAM_DATA or EMSCRIPTEN_FETCH_WAITABLE flags when preloading!'));
1164 #endif
1165   attr.attributes &=
1166     ~(EMSCRIPTEN_FETCH_SYNCHRONOUS | EMSCRIPTEN_FETCH_STREAM_DATA | EMSCRIPTEN_FETCH_WAITABLE);
1167   // Default to EMSCRIPTEN_FETCH_APPEND if not specified.
1168   if (!(attr.attributes & (EMSCRIPTEN_FETCH_APPEND | EMSCRIPTEN_FETCH_REPLACE)))
1169     attr.attributes |= EMSCRIPTEN_FETCH_APPEND;
1170 
1171   emscripten_fetch_t* fetch;
1172   if (url)
1173     fetch = emscripten_fetch(&attr, url);
1174   else {
1175     char remoteUrl[3 * PATH_MAX +
1176                    4]; // times 3 because uri-encoding can expand the filename at most 3x.
1177     emscripten_asmfs_remote_url(pathname, remoteUrl, 3 * PATH_MAX + 4);
1178     fetch = emscripten_fetch(&attr, remoteUrl);
1179   }
1180 
1181   if (!node) {
1182     inode* directory = create_directory_hierarchy_for_file(root, relpath, mode);
1183     node = create_inode(INODE_FILE, mode);
1184     strcpy(node->name, basename_part(pathname));
1185     link_inode(node, directory);
1186   }
1187   node->fetch = fetch;
1188 
1189   return EMSCRIPTEN_RESULT_SUCCESS;
1190 }
1191 
__wasi_fd_close(__wasi_fd_t fd)1192 __wasi_errno_t __wasi_fd_close(__wasi_fd_t fd)
1193 {
1194   return close(fd);
1195 }
1196 
__syscall9(long oldpath,long newpath)1197 long __syscall9(long oldpath, long newpath) // link
1198 {
1199 #ifdef ASMFS_DEBUG
1200   EM_ASM(err('link(oldpath="' + UTF8ToString($0) + '", newpath="' + UTF8ToString($1) + '")'),
1201     oldpath, newpath);
1202 #endif
1203   ((void)oldpath);
1204   ((void)newpath);
1205 
1206   RETURN_ERRNO(ENOTSUP, "TODO: link() is a stub and not yet implemented in ASMFS");
1207 }
1208 
__syscall10(long path)1209 long __syscall10(long path) // unlink
1210 {
1211   const char* pathname = (const char *)path;
1212 #ifdef ASMFS_DEBUG
1213   EM_ASM(err('unlink(pathname="' + UTF8ToString($0) + '")'), pathname);
1214 #endif
1215 
1216   int len = strlen(pathname);
1217   if (len > MAX_PATHNAME_LENGTH)
1218     RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
1219   if (len == 0)
1220     RETURN_ERRNO(ENOENT, "pathname is empty");
1221 
1222   int err;
1223   inode* node = find_inode(pathname, &err);
1224   if (err == ENOTDIR)
1225     RETURN_ERRNO(
1226       ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
1227   if (err == ELOOP)
1228     RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in translating pathname");
1229   if (err == EACCES)
1230     RETURN_ERRNO(EACCES,
1231       "One of the directories in the path prefix of pathname did not allow search permission");
1232   if (err == ENOENT)
1233     RETURN_ERRNO(ENOENT, "A component in pathname does not exist or is a dangling symbolic link");
1234   if (err)
1235     RETURN_ERRNO(err, "find_inode() error");
1236 
1237   if (!node)
1238     RETURN_ERRNO(ENOENT, "file does not exist");
1239 
1240   inode* parent = node->parent;
1241 
1242   if (parent && !(parent->mode & 0222))
1243     RETURN_ERRNO(EACCES, "Write access to the directory containing pathname is not allowed for the process's effective UID");
1244 
1245   // TODO: RETURN_ERRNO(EPERM, "The directory containing pathname has the sticky bit (S_ISVTX) set
1246   // and the process's effective user ID is neither the user ID of the file to be deleted nor that
1247   // of the directory containing it, and the process is not privileged");
1248   // TODO: RETURN_ERRNO(EROFS, "pathname refers to a file on a read-only filesystem");
1249 
1250   if (!(node->mode & 0222)) {
1251     if (node->type == INODE_DIR)
1252       RETURN_ERRNO(
1253         EISDIR, "directory deletion not permitted"); // Linux quirk: Return EISDIR error for not
1254                                                      // having permission to delete a directory.
1255     else
1256       RETURN_ERRNO(EPERM, "file deletion not permitted"); // but return EPERM error for no
1257                                                           // permission to delete a file.
1258   }
1259 
1260   if (node->child)
1261     RETURN_ERRNO(EISDIR, "directory is not empty"); // Linux quirk: Return EISDIR error if not being
1262                                                     // able to delete a nonempty directory.
1263 
1264   unlink_inode(node);      // Detach this from parent
1265   delete_inode_tree(node); // And delete the whole subtree
1266 
1267   return 0;
1268 }
1269 
__syscall12(long path)1270 long __syscall12(long path) // chdir
1271 {
1272   const char* pathname = (const char *)path;
1273 #ifdef ASMFS_DEBUG
1274   EM_ASM(err('chdir(pathname="' + UTF8ToString($0) + '")'), pathname);
1275 #endif
1276 
1277   int len = strlen(pathname);
1278   if (len > MAX_PATHNAME_LENGTH)
1279     RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
1280   if (len == 0)
1281     RETURN_ERRNO(ENOENT, "pathname is empty");
1282 
1283   int err;
1284   inode* node = find_inode(pathname, &err);
1285   if (err == ENOTDIR)
1286     RETURN_ERRNO(
1287       ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
1288   if (err == ELOOP)
1289     RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving path");
1290   if (err == EACCES)
1291     RETURN_ERRNO(EACCES, "Search permission is denied for one of the components of path");
1292   if (err == ENOENT)
1293     RETURN_ERRNO(
1294       ENOENT, "Directory component in pathname does not exist or is a dangling symbolic link");
1295   if (err)
1296     RETURN_ERRNO(err, "find_inode() error");
1297   if (!node)
1298     RETURN_ERRNO(ENOENT, "The directory specified in path does not exist");
1299   if (node->type != INODE_DIR)
1300     RETURN_ERRNO(ENOTDIR, "Path is not a directory");
1301 
1302   set_cwd(node);
1303   return 0;
1304 }
1305 
__syscall14(long path,long mode,long dev)1306 long __syscall14(long path, long mode, long dev) // mknod
1307 {
1308   const char* pathname = (const char *)path;
1309 #ifdef ASMFS_DEBUG
1310   EM_ASM(err('mknod(pathname="' + UTF8ToString($0) + '", mode=0' + ($1).toString(8) + ', dev=' +
1311              $2 + ')'),
1312     pathname, mode, dev);
1313 #endif
1314   (void)pathname;
1315   (void)mode;
1316   (void)dev;
1317 
1318   RETURN_ERRNO(ENOTSUP, "TODO: mknod() is a stub and not yet implemented in ASMFS");
1319 }
1320 
__syscall15(long path,long mode)1321 long __syscall15(long path, long mode) // chmod
1322 {
1323   const char* pathname = (const char *)path;
1324 #ifdef ASMFS_DEBUG
1325   EM_ASM(err('chmod(pathname="' + UTF8ToString($0) + '", mode=0' + ($1).toString(8) + ')'),
1326     pathname, mode);
1327 #endif
1328 
1329   int len = strlen(pathname);
1330   if (len > MAX_PATHNAME_LENGTH)
1331     RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
1332   if (len == 0)
1333     RETURN_ERRNO(ENOENT, "pathname is empty");
1334 
1335   int err;
1336   inode* node = find_inode(pathname, &err);
1337   if (err == ENOTDIR)
1338     RETURN_ERRNO(
1339       ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
1340   if (err == ELOOP)
1341     RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
1342   if (err == EACCES)
1343     RETURN_ERRNO(EACCES, "Search permission is denied on a component of the path prefix");
1344   if (err == ENOENT)
1345     RETURN_ERRNO(
1346       ENOENT, "Directory component in pathname does not exist or is a dangling symbolic link");
1347   if (err)
1348     RETURN_ERRNO(err, "find_inode() error");
1349   if (!node)
1350     RETURN_ERRNO(ENOENT, "The file does not exist");
1351 
1352   // TODO: if (not allowed) RETURN_ERRNO(EPERM, "The effective UID does not match the owner of the
1353   // file");
1354   // TODO: read-only filesystems: if (fs is read-only) RETURN_ERRNO(EROFS, "The named file resides
1355   // on a read-only filesystem");
1356 
1357   node->mode = mode;
1358   return 0;
1359 }
1360 
__syscall33(long path,long mode)1361 long __syscall33(long path, long mode) // access
1362 {
1363   const char* pathname = (const char *)path;
1364 #ifdef ASMFS_DEBUG
1365   EM_ASM(err('access(pathname="' + UTF8ToString($0) + '", mode=0' + ($1).toString(8) + ')'),
1366     pathname, mode);
1367 #endif
1368 
1369   int len = strlen(pathname);
1370   if (len > MAX_PATHNAME_LENGTH)
1371     RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
1372   if (len == 0)
1373     RETURN_ERRNO(ENOENT, "pathname is empty");
1374 
1375   if ((mode & F_OK) && (mode & (R_OK | W_OK | X_OK)))
1376     RETURN_ERRNO(EINVAL, "mode was incorrectly specified");
1377 
1378   int err;
1379   inode* node = find_inode(pathname, &err);
1380   if (err == ENOTDIR)
1381     RETURN_ERRNO(
1382       ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
1383   if (err == ELOOP)
1384     RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
1385   if (err == EACCES)
1386     RETURN_ERRNO(EACCES,
1387       "Search permission is denied for one of the directories in the path prefix of pathname");
1388   if (err == ENOENT)
1389     RETURN_ERRNO(ENOENT, "A component of pathname does not exist or is a dangling symbolic link");
1390   if (err)
1391     RETURN_ERRNO(err, "find_inode() error");
1392   if (!node)
1393     RETURN_ERRNO(ENOENT, "Pathname does not exist");
1394 
1395   // Just testing if a file exists?
1396   if ((mode & F_OK))
1397     return 0;
1398 
1399   // TODO: RETURN_ERRNO(EROFS, "Write permission was requested for a file on a read-only
1400   // filesystem");
1401 
1402   if ((mode & R_OK) && !(node->mode & 0444))
1403     RETURN_ERRNO(EACCES, "Read access would be denied to the file");
1404   if ((mode & W_OK) && !(node->mode & 0222))
1405     RETURN_ERRNO(EACCES, "Write access would be denied to the file");
1406   if ((mode & X_OK) && !(node->mode & 0111))
1407     RETURN_ERRNO(EACCES, "Execute access would be denied to the file");
1408 
1409   return 0;
1410 }
1411 
__syscall36()1412 long __syscall36() // sync
1413 {
1414 #ifdef ASMFS_DEBUG
1415   EM_ASM(err('sync()'));
1416 #endif
1417 
1418   // Spec mandates that "sync() is always successful".
1419   return 0;
1420 }
1421 
1422 // TODO: syscall38,  int rename(const char *oldpath, const char *newpath);
1423 
emscripten_asmfs_mkdir(const char * pathname,mode_t mode)1424 long emscripten_asmfs_mkdir(const char* pathname, mode_t mode) {
1425 #ifdef ASMFS_DEBUG
1426   EM_ASM(err('mkdir(pathname="' + UTF8ToString($0) + '", mode=0' + ($1).toString(8) + ')'),
1427     pathname, mode);
1428 #endif
1429 
1430   int len = strlen(pathname);
1431   if (len > MAX_PATHNAME_LENGTH)
1432     RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
1433   if (len == 0)
1434     RETURN_ERRNO(ENOENT, "pathname is empty");
1435 
1436   inode* root = (pathname[0] == '/') ? filesystem_root() : get_cwd();
1437   const char* relpath = (pathname[0] == '/') ? pathname + 1 : pathname;
1438   int err;
1439   inode* parent_dir = find_parent_inode(root, relpath, &err);
1440   if (err == ENOTDIR)
1441     RETURN_ERRNO(
1442       ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
1443   if (err == ELOOP)
1444     RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
1445   if (err == EACCES)
1446     RETURN_ERRNO(EACCES, "One of the directories in pathname did not allow search permission");
1447   if (err)
1448     RETURN_ERRNO(err, "find_inode() error");
1449   if (!parent_dir)
1450     RETURN_ERRNO(
1451       ENOENT, "A directory component in pathname does not exist or is a dangling symbolic link");
1452 
1453   // TODO: if (component of path wasn't actually a directory) RETURN_ERRNO(ENOTDIR, "A component
1454   // used as a directory in pathname is not, in fact, a directory");
1455 
1456   inode* existing = find_inode(parent_dir, basename_part(pathname), &err);
1457   if (err == ENOTDIR)
1458     RETURN_ERRNO(
1459       ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
1460   if (err == ELOOP)
1461     RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
1462   if (err == EACCES)
1463     RETURN_ERRNO(EACCES, "One of the directories in pathname did not allow search permission");
1464   if (err && err != ENOENT)
1465     RETURN_ERRNO(err, "find_inode() error");
1466   if (existing)
1467     RETURN_ERRNO(EEXIST, "pathname already exists (not necessarily as a directory)");
1468   if (!(parent_dir->mode & 0222))
1469     RETURN_ERRNO(EACCES, "The parent directory does not allow write permission to the process");
1470 
1471   // TODO: read-only filesystems: if (fs is read-only) RETURN_ERRNO(EROFS, "Pathname refers to a
1472   // file on a read-only filesystem");
1473 
1474   inode* directory = create_inode(INODE_DIR, mode);
1475   strcpy(directory->name, basename_part(pathname));
1476   link_inode(directory, parent_dir);
1477   return 0;
1478 }
1479 
emscripten_asmfs_unload_data(const char * pathname)1480 void emscripten_asmfs_unload_data(const char* pathname) {
1481   int err;
1482   inode* node = find_inode(pathname, &err);
1483   if (!node)
1484     return;
1485 
1486   free(node->data);
1487   node->data = 0;
1488   node->size = node->capacity = 0;
1489 }
1490 
emscripten_asmfs_compute_memory_usage_at_node(inode * node)1491 uint64_t emscripten_asmfs_compute_memory_usage_at_node(inode* node) {
1492   if (!node)
1493     return 0;
1494   uint64_t sz = sizeof(inode);
1495   if (node->data)
1496     sz += node->capacity > node->size ? node->capacity : node->size;
1497   if (node->fetch && node->fetch->data)
1498     sz += node->fetch->numBytes;
1499   return sz + emscripten_asmfs_compute_memory_usage_at_node(node->child) +
1500          emscripten_asmfs_compute_memory_usage_at_node(node->sibling);
1501 }
1502 
emscripten_asmfs_compute_memory_usage()1503 uint64_t emscripten_asmfs_compute_memory_usage() {
1504   return emscripten_asmfs_compute_memory_usage_at_node(filesystem_root());
1505 }
1506 
__syscall39(long path,long mode)1507 long __syscall39(long path, long mode) // mkdir
1508 {
1509   return emscripten_asmfs_mkdir((const char *)path, mode);
1510 }
1511 
__syscall40(long path)1512 long __syscall40(long path) // rmdir
1513 {
1514   const char* pathname = (const char *)path;
1515 #ifdef ASMFS_DEBUG
1516   EM_ASM(err('rmdir(pathname="' + UTF8ToString($0) + '")'), pathname);
1517 #endif
1518 
1519   int len = strlen(pathname);
1520   if (len > MAX_PATHNAME_LENGTH)
1521     RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
1522   if (len == 0)
1523     RETURN_ERRNO(ENOENT, "pathname is empty");
1524 
1525   if (!strcmp(pathname, ".") || (len >= 2 && !strcmp(pathname + len - 2, "/.")))
1526     RETURN_ERRNO(EINVAL, "pathname has . as last component");
1527   if (!strcmp(pathname, "..") || (len >= 3 && !strcmp(pathname + len - 3, "/..")))
1528     RETURN_ERRNO(ENOTEMPTY, "pathname has .. as its final component");
1529 
1530   int err;
1531   inode* node = find_inode(pathname, &err);
1532   if (err == ENOTDIR)
1533     RETURN_ERRNO(
1534       ENOTDIR, "A component used as a directory in pathname is not, in fact, a directory");
1535   if (err == ELOOP)
1536     RETURN_ERRNO(ELOOP, "Too many symbolic links were encountered in resolving pathname");
1537   if (err == EACCES)
1538     RETURN_ERRNO(EACCES,
1539       "one of the directories in the path prefix of pathname did not allow search permission");
1540   if (err == ENOENT)
1541     RETURN_ERRNO(
1542       ENOENT, "A directory component in pathname does not exist or is a dangling symbolic link");
1543   if (err)
1544     RETURN_ERRNO(err, "find_inode() error");
1545   if (!node)
1546     RETURN_ERRNO(ENOENT, "directory does not exist");
1547   if (node == filesystem_root() || node == get_cwd())
1548     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)");
1549   if (node->parent && !(node->parent->mode & 0222))
1550     RETURN_ERRNO(EACCES, "Write access to the directory containing pathname was not allowed");
1551   if (node->type != INODE_DIR)
1552     RETURN_ERRNO(ENOTDIR, "pathname is not a directory");
1553   if (node->child)
1554     RETURN_ERRNO(ENOTEMPTY, "pathname contains entries other than . and ..");
1555 
1556   // TODO: RETURN_ERRNO(EPERM, "The directory containing pathname has the sticky bit (S_ISVTX) set
1557   // and the process's effective user ID is neither the user ID of the file to be deleted nor that
1558   // of the directory containing it, and the process is not privileged");
1559   // TODO: RETURN_ERRNO(EROFS, "pathname refers to a directory on a read-only filesystem");
1560 
1561   unlink_inode(node);
1562 
1563   return 0;
1564 }
1565 
__syscall41(long fd)1566 long __syscall41(long fd) // dup
1567 {
1568 #ifdef ASMFS_DEBUG
1569   EM_ASM(err('dup(fd=' + $0 + ')'), fd);
1570 #endif
1571 
1572   FileDescriptor* desc = (FileDescriptor*)fd;
1573   if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
1574     RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
1575 
1576   inode* node = desc->node;
1577   if (!node)
1578     RETURN_ERRNO(-1, "ASMFS internal error: file descriptor points to a nonexisting file");
1579 
1580   // TODO: RETURN_ERRNO(EMFILE, "The per-process limit on the number of open file descriptors has
1581   // been reached (see RLIMIT_NOFILE)");
1582 
1583   RETURN_ERRNO(ENOTSUP, "TODO: dup() is a stub and not yet implemented in ASMFS");
1584 }
1585 
1586 // TODO: syscall42: int pipe(int pipefd[2]);
1587 
__syscall54(long fd,long request,...)1588 long __syscall54(long fd, long request, ...) // ioctl/sysctl
1589 {
1590   va_list vl;
1591   va_start(vl, request);
1592   char* argp = va_arg(vl, char*);
1593   va_end(vl);
1594 #ifdef ASMFS_DEBUG
1595   EM_ASM(err('ioctl(fd=' + $0 + ', request=' + $1 + ', argp=0x' + $2 + ')'), fd, request, argp);
1596 #endif
1597   (void)fd;
1598   (void)request;
1599   (void)argp;
1600 
1601   RETURN_ERRNO(ENOTSUP, "TODO: ioctl() is a stub and not yet implemented in ASMFS");
1602 }
1603 
1604 // TODO: syscall60: mode_t umask(mode_t mask);
1605 // TODO: syscall63: dup2
1606 // TODO: syscall83: symlink
1607 // TODO: syscall85: readlink
1608 // TODO: syscall91: munmap
1609 // TODO: syscall94: fchmod
1610 // TODO: syscall102: socketcall
1611 
__wasi_fd_sync(__wasi_fd_t fd)1612 __wasi_errno_t __wasi_fd_sync(__wasi_fd_t fd)
1613 {
1614   FileDescriptor* desc = (FileDescriptor*)fd;
1615   if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
1616     RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
1617 
1618   inode* node = desc->node;
1619   if (!node)
1620     RETURN_ERRNO(-1, "ASMFS internal error: file descriptor points to a non-file");
1621 
1622   return 0;
1623 }
1624 
1625 // TODO: syscall133: fchdir
1626 
__wasi_fd_seek(__wasi_fd_t fd,__wasi_filedelta_t offset,__wasi_whence_t whence,__wasi_filesize_t * newoffset)1627 __wasi_errno_t __wasi_fd_seek(__wasi_fd_t fd, __wasi_filedelta_t offset,
1628     __wasi_whence_t whence, __wasi_filesize_t *newoffset)
1629 {
1630 #ifdef ASMFS_DEBUG
1631   EM_ASM(err('llseek(fd=' + $0 + ', offset=' + $1 + ', newoffset=0x' +
1632              ($2).toString(16) + ', whence=' + $3 + ')'),
1633     fd, offset, newoffset, whence);
1634 #endif
1635 
1636   FileDescriptor* desc = (FileDescriptor*)fd;
1637   if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
1638     RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
1639 
1640   if (desc->node->fetch) {
1641     if (emscripten_is_main_browser_thread()) {
1642       if (emscripten_fetch_wait(desc->node->fetch, 0) != EMSCRIPTEN_RESULT_SUCCESS) {
1643         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)");
1644       }
1645     } else
1646       emscripten_fetch_wait(desc->node->fetch, INFINITY);
1647   }
1648 
1649   int64_t newPos;
1650   switch (whence) {
1651     case SEEK_SET:
1652       newPos = offset;
1653       break;
1654     case SEEK_CUR:
1655       newPos = desc->file_pos + offset;
1656       break;
1657     case SEEK_END:
1658       newPos = (desc->node->fetch ? desc->node->fetch->numBytes : desc->node->size) + offset;
1659       break;
1660     case 3 /*SEEK_DATA*/:
1661       RETURN_ERRNO(EINVAL, "whence is invalid (sparse files, whence=SEEK_DATA, is not supported");
1662     case 4 /*SEEK_HOLE*/:
1663       RETURN_ERRNO(EINVAL, "whence is invalid (sparse files, whence=SEEK_HOLE, is not supported");
1664     default:
1665       RETURN_ERRNO(EINVAL, "whence is invalid");
1666   }
1667   if (newPos < 0)
1668     RETURN_ERRNO(EINVAL, "The resulting file offset would be negative");
1669   if (newPos > 0x7FFFFFFFLL)
1670     RETURN_ERRNO(EOVERFLOW, "The resulting file offset cannot be represented in an off_t");
1671 
1672   desc->file_pos = newPos;
1673 
1674   if (newoffset)
1675     *newoffset = desc->file_pos;
1676   return 0;
1677 }
1678 
1679 // TODO: syscall144 msync
1680 
readv(int fd,const iovec * iov,int iovcnt)1681 static long readv(int fd, const iovec *iov, int iovcnt) // syscall145
1682 {
1683 #ifdef ASMFS_DEBUG
1684   EM_ASM(err('readv(fd=' + $0 + ', iov=0x' + ($1).toString(16) + ', iovcnt=' + $2 + ')'), fd, iov,
1685     iovcnt);
1686 #endif
1687 
1688   FileDescriptor* desc = (FileDescriptor*)fd;
1689   if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
1690     RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
1691 
1692   inode* node = desc->node;
1693   if (!node)
1694     RETURN_ERRNO(-1, "ASMFS internal error: file descriptor points to a non-file");
1695   if (node->type == INODE_DIR)
1696     RETURN_ERRNO(EISDIR, "fd refers to a directory");
1697   if (node->type != INODE_FILE /* TODO: && node->type != socket */)
1698     RETURN_ERRNO(EINVAL, "fd is attached to an object which is unsuitable for reading");
1699 
1700   // TODO: if (node->type == INODE_FILE && desc has O_NONBLOCK && read would block)
1701   // RETURN_ERRNO(EAGAIN, "The file descriptor fd refers to a file other than a socket and has been
1702   // marked nonblocking (O_NONBLOCK), and the read would block");
1703   // TODO: if (node->type == socket && desc has O_NONBLOCK && read would block)
1704   // RETURN_ERRNO(EWOULDBLOCK, "The file descriptor fd refers to a socket and has been marked
1705   // nonblocking (O_NONBLOCK), and the read would block");
1706 
1707   if (node->fetch) {
1708     if (emscripten_is_main_browser_thread()) {
1709       if (emscripten_fetch_wait(node->fetch, 0) != EMSCRIPTEN_RESULT_SUCCESS) {
1710         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)");
1711       }
1712     } else
1713       emscripten_fetch_wait(node->fetch, INFINITY);
1714   }
1715 
1716   if (node->size > 0 && !node->data && (!node->fetch || !node->fetch->data))
1717     RETURN_ERRNO(-1, "ASMFS internal error: no file data available");
1718   if (iovcnt < 0)
1719     RETURN_ERRNO(EINVAL, "The vector count, iovcnt, is less than zero");
1720 
1721   ssize_t total_read_amount = 0;
1722   for (int i = 0; i < iovcnt; ++i) {
1723     ssize_t n = total_read_amount + iov[i].iov_len;
1724     if (n < total_read_amount)
1725       RETURN_ERRNO(EINVAL, "The sum of the iov_len values overflows an ssize_t value");
1726     if (!iov[i].iov_base && iov[i].iov_len > 0)
1727       RETURN_ERRNO(
1728         EINVAL, "iov_len specifies a positive length buffer but iov_base is a null pointer");
1729     total_read_amount = n;
1730   }
1731 
1732   size_t offset = desc->file_pos;
1733   uint8_t* data = node->data ? node->data : (node->fetch ? (uint8_t*)node->fetch->data : 0);
1734   size_t size = node->data ? node->size : (node->fetch ? node->fetch->numBytes : 0);
1735   for (int i = 0; i < iovcnt; ++i) {
1736     ssize_t dataLeft = size - offset;
1737     if (dataLeft <= 0)
1738       break;
1739     size_t bytesToCopy = (size_t)dataLeft < iov[i].iov_len ? dataLeft : iov[i].iov_len;
1740     memcpy(iov[i].iov_base, &data[offset], bytesToCopy);
1741 #ifdef ASMFS_DEBUG
1742     EM_ASM(err('readv requested to read ' + $0 + ', read  ' + $1 + ' bytes from offset ' + $2 +
1743                ', new offset: ' + $3 + ' (file size: ' + $4 + ')'),
1744       (int)iov[i].iov_len, (int)bytesToCopy, (int)offset, (int)(offset + bytesToCopy), (int)size);
1745 #endif
1746     offset += bytesToCopy;
1747   }
1748   ssize_t numRead = offset - desc->file_pos;
1749   desc->file_pos = offset;
1750   return numRead;
1751 }
1752 
__syscall3(long fd,long buf,long count)1753 long __syscall3(long fd, long buf, long count) // read
1754 {
1755 #ifdef ASMFS_DEBUG
1756   EM_ASM(
1757     err('read(fd=' + $0 + ', buf=0x' + ($1).toString(16) + ', count=' + $2 + ')'), fd, buf, count);
1758 #endif
1759 
1760   iovec io = {(void*)buf, (size_t)count};
1761   return readv(fd, &io, 1);
1762 }
1763 
writev(int fd,const iovec * iov,int iovcnt)1764 static long writev(int fd, const iovec *iov, int iovcnt) // syscall146
1765 {
1766 #ifdef ASMFS_DEBUG
1767   EM_ASM(err('writev(fd=' + $0 + ', iov=0x' + ($1).toString(16) + ', iovcnt=' + $2 + ')'), fd, iov,
1768     iovcnt);
1769 #endif
1770 
1771   FileDescriptor* desc = (FileDescriptor*)fd;
1772   if (fd != 1 /*stdout*/ &&
1773       fd != 2 /*stderr*/) // TODO: Resolve the hardcoding of stdin,stdout & stderr
1774   {
1775     if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
1776       RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
1777   }
1778 
1779   if (iovcnt < 0)
1780     RETURN_ERRNO(EINVAL, "The vector count, iovcnt, is less than zero");
1781 
1782   ssize_t total_write_amount = 0;
1783   for (int i = 0; i < iovcnt; ++i) {
1784     ssize_t n = total_write_amount + iov[i].iov_len;
1785     if (n < total_write_amount)
1786       RETURN_ERRNO(EINVAL, "The sum of the iov_len values overflows an ssize_t value");
1787     if (!iov[i].iov_base && iov[i].iov_len > 0)
1788       RETURN_ERRNO(
1789         EINVAL, "iov_len specifies a positive length buffer but iov_base is a null pointer");
1790     total_write_amount = n;
1791   }
1792 
1793   if (fd == 1 /*stdout*/ || fd == 2 /*stderr*/) {
1794     ssize_t bytesWritten = 0;
1795     for (int i = 0; i < iovcnt; ++i) {
1796       print_stream(iov[i].iov_base, iov[i].iov_len, fd == 1);
1797       bytesWritten += iov[i].iov_len;
1798     }
1799     return bytesWritten;
1800   } else {
1801     // Enlarge the file in memory to fit space for the new data
1802     size_t newSize = desc->file_pos + total_write_amount;
1803     inode* node = desc->node;
1804     if (node->capacity < newSize) {
1805       size_t newCapacity =
1806         (newSize > (size_t)(node->capacity * 1.25)
1807             ? newSize
1808             : (size_t)(
1809                 node->capacity * 1.25)); // Geometric increases in size for amortized O(1) behavior
1810       uint8_t* newData = (uint8_t*)realloc(node->data, newCapacity);
1811       if (!newData) {
1812         newData = (uint8_t*)malloc(newCapacity);
1813         memcpy(newData, node->data, node->size);
1814         // TODO: init gaps with zeroes.
1815         free(node->data);
1816       }
1817       node->data = newData;
1818       node->size = newSize;
1819       node->capacity = newCapacity;
1820     }
1821 
1822     for (int i = 0; i < iovcnt; ++i) {
1823       memcpy((uint8_t*)node->data + desc->file_pos, iov[i].iov_base, iov[i].iov_len);
1824       desc->file_pos += iov[i].iov_len;
1825     }
1826   }
1827   return total_write_amount;
1828 }
1829 
__syscall4(long fd,long buf,long count)1830 long __syscall4(long fd, long buf, long count) // write
1831 {
1832 #ifdef ASMFS_DEBUG
1833   EM_ASM(
1834     err('write(fd=' + $0 + ', buf=0x' + ($1).toString(16) + ', count=' + $2 + ')'), fd, buf, count);
1835 #endif
1836 
1837   iovec io = {(void*)buf, (size_t)count};
1838   return writev(fd, &io, 1);
1839 }
1840 
1841 // WASI support: provide a shim between the wasi fd_write syscall and the
1842 // syscall146 that is implemented here in ASMFS.
1843 // TODO: Refactor ASMFS's syscall146 into a direct handler for fd_write.
1844 
__wasi_fd_write(__wasi_fd_t fd,const __wasi_ciovec_t * iovs,size_t iovs_len,size_t * nwritten)1845 __wasi_errno_t __wasi_fd_write(
1846     __wasi_fd_t fd,
1847     const __wasi_ciovec_t *iovs,
1848     size_t iovs_len,
1849     size_t *nwritten
1850 )
1851 {
1852   long result;
1853   result = writev(fd, (const iovec*)iovs, iovs_len);
1854   if (result < 0) {
1855     *nwritten = 0;
1856     return result;
1857   }
1858   *nwritten = result;
1859   return 0;
1860 }
1861 
1862 // TODO: syscall148: fdatasync
1863 // TODO: syscall168: poll
1864 
1865 // TODO: syscall180: pread64
1866 // TODO: syscall181: pwrite64
1867 
__syscall183(long buf,long size)1868 long __syscall183(long buf, long size) // getcwd
1869 {
1870 #ifdef ASMFS_DEBUG
1871   EM_ASM(err('getcwd(buf=0x' + $0 + ', size= ' + $1 + ')'), buf, size);
1872 #endif
1873 
1874   if (!buf && size > 0)
1875     RETURN_ERRNO(EFAULT, "buf points to a bad address");
1876   if (buf && size == 0)
1877     RETURN_ERRNO(EINVAL, "The size argument is zero and buf is not a null pointer");
1878 
1879   inode* cwd = get_cwd();
1880   if (!cwd)
1881     RETURN_ERRNO(-1, "ASMFS internal error: no current working directory?!");
1882   // TODO: RETURN_ERRNO(ENOENT, "The current working directory has been unlinked");
1883   // TODO: RETURN_ERRNO(EACCES, "Permission to read or search a component of the filename was
1884   // denied");
1885   inode_abspath(cwd, (char *)buf, size);
1886   if (strlen((char *)buf) >= size - 1)
1887     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");
1888 
1889   return 0;
1890 }
1891 
1892 // TODO: syscall192: mmap2
1893 // TODO: syscall193: truncate64
1894 // TODO: syscall194: ftruncate64
1895 
__stat64(inode * node,struct stat * buf)1896 static long __stat64(inode* node, struct stat* buf) {
1897   buf->st_dev =
1898     1; // ID of device containing file: Hardcode 1 for now, no meaning at the moment for Emscripten.
1899   buf->st_ino = (ino_t)node; // TODO: This needs to be an inode ID number proper.
1900   buf->st_mode = node->mode;
1901   switch (node->type) {
1902     case INODE_DIR:
1903       buf->st_mode |= S_IFDIR;
1904       break;
1905     case INODE_FILE:
1906       buf->st_mode |= S_IFREG;
1907       break; // Regular file
1908              /* TODO:
1909              case socket: buf->st_mode |= S_IFSOCK; break;
1910              case symlink: buf->st_mode |= S_IFLNK; break;
1911              case block device: buf->st_mode |= S_IFBLK; break;
1912              case character device: buf->st_mode |= S_IFCHR; break;
1913              case FIFO: buf->st_mode |= S_IFIFO; break;
1914              */
1915   }
1916   buf->st_nlink = 1; // The number of hard links. TODO: Use this for real when links are supported.
1917   buf->st_uid = node->uid;
1918   buf->st_gid = node->gid;
1919   buf->st_rdev = 1; // Device ID (if special file) No meaning right now for Emscripten.
1920   buf->st_size = node->fetch ? node->fetch->totalBytes : 0;
1921   if (node->size > (size_t)buf->st_size)
1922     buf->st_size = node->size;
1923   buf->st_blocks =
1924     (buf->st_size + 511) / 512; // The syscall docs state this is hardcoded to # of 512 byte blocks.
1925   buf->st_blksize = 1024 * 1024; // Specifies the preferred blocksize for efficient disk I/O.
1926   buf->st_atim.tv_sec = node->atime;
1927   buf->st_mtim.tv_sec = node->mtime;
1928   buf->st_ctim.tv_sec = node->ctime;
1929   return 0;
1930 }
1931 
__syscall195(long path,long buf)1932 long __syscall195(long path, long buf) // SYS_stat64
1933 {
1934   const char* pathname = (const char *)path;
1935 #ifdef ASMFS_DEBUG
1936   EM_ASM(err('SYS_stat64(pathname="' + UTF8ToString($0) + '", buf=0x' + ($1).toString(16) + ')'),
1937     pathname, buf);
1938 #endif
1939 
1940   int len = strlen(pathname);
1941   if (len > MAX_PATHNAME_LENGTH)
1942     RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
1943   if (len == 0)
1944     RETURN_ERRNO(ENOENT, "pathname is empty");
1945 
1946   // Find if this file exists already in the filesystem?
1947   inode* root = (pathname[0] == '/') ? filesystem_root() : get_cwd();
1948   const char* relpath = (pathname[0] == '/') ? pathname + 1 : pathname;
1949 
1950   int err;
1951   inode* node = find_inode(root, relpath, &err);
1952 
1953   if (!node && (err == ENOENT || err == ENOTDIR)) {
1954     // Populate the file from the CDN to the filesystem if it didn't yet exist.
1955     long fd = open(pathname, O_RDONLY, 0777);
1956     if (fd)
1957       close(fd);
1958     node = find_inode(root, relpath, &err);
1959   }
1960 
1961   if (err == ENOTDIR)
1962     RETURN_ERRNO(ENOTDIR, "A component of the path prefix of pathname is not a directory");
1963   if (err == ELOOP)
1964     RETURN_ERRNO(ELOOP, "Too many symbolic links encountered while traversing the path");
1965   if (err == EACCES)
1966     RETURN_ERRNO(EACCES,
1967       "Search permission is denied for one of the directories in the path prefix of pathname");
1968   if (err && err != ENOENT)
1969     RETURN_ERRNO(err, "find_inode() error");
1970   if (err == ENOENT || !node)
1971     RETURN_ERRNO(ENOENT, "A component of pathname does not exist");
1972 
1973   return __stat64(node, (struct stat *)buf);
1974 }
1975 
__syscall196(long path,long buf)1976 long __syscall196(long path, long buf) // SYS_lstat64
1977 {
1978   const char* pathname = (const char *)path;
1979 #ifdef ASMFS_DEBUG
1980   EM_ASM(err('SYS_lstat64(pathname="' + UTF8ToString($0) + '", buf=0x' + ($1).toString(16) + ')'),
1981     pathname, buf);
1982 #endif
1983 
1984   int len = strlen(pathname);
1985   if (len > MAX_PATHNAME_LENGTH)
1986     RETURN_ERRNO(ENAMETOOLONG, "pathname was too long");
1987   if (len == 0)
1988     RETURN_ERRNO(ENOENT, "pathname is empty");
1989 
1990   // Find if this file exists already in the filesystem?
1991   inode* root = (pathname[0] == '/') ? filesystem_root() : get_cwd();
1992   const char* relpath = (pathname[0] == '/') ? pathname + 1 : pathname;
1993 
1994   // TODO: When symbolic links are implemented, make this return info about the symlink itself and
1995   // not the file it points to.
1996   int err;
1997   inode* node = find_inode(root, relpath, &err);
1998   if (err == ENOTDIR)
1999     RETURN_ERRNO(ENOTDIR, "A component of the path prefix of pathname is not a directory");
2000   if (err == ELOOP)
2001     RETURN_ERRNO(ELOOP, "Too many symbolic links encountered while traversing the path");
2002   if (err == EACCES)
2003     RETURN_ERRNO(EACCES,
2004       "Search permission is denied for one of the directories in the path prefix of pathname");
2005   if (err && err != ENOENT)
2006     RETURN_ERRNO(err, "find_inode() error");
2007   if (err == ENOENT || !node)
2008     RETURN_ERRNO(ENOENT, "A component of pathname does not exist");
2009 
2010   return __stat64(node, (struct stat*)buf);
2011 }
2012 
__syscall197(long fd,long buf)2013 long __syscall197(long fd, long buf) // SYS_fstat64
2014 {
2015 #ifdef ASMFS_DEBUG
2016   EM_ASM(
2017     err('SYS_fstat64(fd="' + UTF8ToString($0) + '", buf=0x' + ($1).toString(16) + ')'), fd, buf);
2018 #endif
2019 
2020   FileDescriptor* desc = (FileDescriptor*)fd;
2021   if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
2022     RETURN_ERRNO(EBADF, "fd isn't a valid open file descriptor");
2023 
2024   inode* node = desc->node;
2025   if (!node)
2026     RETURN_ERRNO(ENOENT, "A component of pathname does not exist");
2027 
2028   return __stat64(node, (struct stat*)buf);
2029 }
2030 
2031 // TODO: syscall198: lchown
2032 // TODO: syscall207: fchown32
2033 // TODO: syscall212: chown32
2034 
__syscall220(long fd,long dirp,long count)2035 long __syscall220(long fd, long dirp, long count) // getdents64 (get directory entries 64-bit)
2036 {
2037   dirent* de = (dirent*)dirp;
2038   unsigned int dirents_size =
2039     count /
2040     sizeof(dirent); // The number of dirent structures that can fit into the provided buffer.
2041   dirent* de_end = de + dirents_size;
2042 #ifdef ASMFS_DEBUG
2043   EM_ASM(err('getdents64(fd=' + $0 + ', de=0x' + ($1).toString(16) + ', count=' + $2 + ')'), fd, de,
2044     count);
2045 #endif
2046 
2047   FileDescriptor* desc = (FileDescriptor*)fd;
2048   if (!desc || desc->magic != EM_FILEDESCRIPTOR_MAGIC)
2049     RETURN_ERRNO(EBADF, "Invalid file descriptor fd");
2050 
2051   inode* node = desc->node;
2052   if (!node)
2053     RETURN_ERRNO(ENOENT, "No such directory");
2054   if (dirents_size == 0)
2055     RETURN_ERRNO(EINVAL, "Result buffer is too small");
2056   if (node->type != INODE_DIR)
2057     RETURN_ERRNO(ENOTDIR, "File descriptor does not refer to a directory");
2058 
2059   inode* dotdot =
2060     node->parent ? node->parent : node; // In "/", the directory ".." refers to itself.
2061 
2062   ssize_t orig_file_pos = desc->file_pos;
2063   ssize_t file_pos = 0;
2064   // There are always two hardcoded directories "." and ".."
2065   if (de >= de_end)
2066     return desc->file_pos - orig_file_pos;
2067   if (desc->file_pos <= file_pos) {
2068     de->d_ino = (ino_t)node; // TODO: Create inode numbers instead of using pointers
2069     de->d_off = file_pos + sizeof(dirent);
2070     de->d_reclen = sizeof(dirent);
2071     de->d_type = DT_DIR;
2072     strcpy(de->d_name, ".");
2073     ++de;
2074     desc->file_pos += sizeof(dirent);
2075   }
2076   file_pos += sizeof(dirent);
2077 
2078   if (de >= de_end)
2079     return desc->file_pos - orig_file_pos;
2080   if (desc->file_pos <= file_pos) {
2081     de->d_ino = (ino_t)dotdot; // TODO: Create inode numbers instead of using pointers
2082     de->d_off = file_pos + sizeof(dirent);
2083     de->d_reclen = sizeof(dirent);
2084     de->d_type = DT_DIR;
2085     strcpy(de->d_name, "..");
2086     ++de;
2087     desc->file_pos += sizeof(dirent);
2088   }
2089   file_pos += sizeof(dirent);
2090 
2091   node = node->child;
2092   while (node && de < de_end) {
2093     if (desc->file_pos <= file_pos) {
2094       de->d_ino = (ino_t)node; // TODO: Create inode numbers instead of using pointers
2095       de->d_off = file_pos + sizeof(dirent);
2096       de->d_reclen = sizeof(dirent);
2097       de->d_type = (node->type == INODE_DIR) ? DT_DIR : DT_REG /*Regular file*/;
2098       de->d_name[255] = 0;
2099       strncpy(de->d_name, node->name, 255);
2100       ++de;
2101       desc->file_pos += sizeof(dirent);
2102     }
2103     node = node->sibling;
2104     file_pos += sizeof(dirent);
2105   }
2106 
2107   return desc->file_pos - orig_file_pos;
2108 }
2109 
2110 // TODO: syscall221: fcntl64
2111 // TODO: syscall268: statfs64
2112 // TODO: syscall269: fstatfs64
2113 // TODO: syscall295: openat
2114 // TODO: syscall296: mkdirat
2115 // TODO: syscall297: mknodat
2116 // TODO: syscall298: fchownat
2117 // TODO: syscall300: fstatat64
2118 // TODO: syscall301: unlinkat
2119 // TODO: syscall302: renameat
2120 // TODO: syscall303: linkat
2121 // TODO: syscall304: symlinkat
2122 // TODO: syscall305: readlinkat
2123 // TODO: syscall306: fchmodat
2124 // TODO: syscall307: faccessat
2125 // TODO: syscall320: utimensat
2126 // TODO: syscall324: fallocate
2127 // TODO: syscall330: dup3
2128 // TODO: syscall331: pipe2
2129 // TODO: syscall333: preadv
2130 // TODO: syscall334: pwritev
2131 
2132 } // ~extern "C"
2133