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