aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormax99x <max99x@gmail.com>2011-07-16 11:53:12 +0300
committermax99x <max99x@gmail.com>2011-07-16 11:53:12 +0300
commit7784274c687ad7a867b485bf327d1a09f0db4111 (patch)
tree0dcb6638579a30360048499a95eeed1c9260da4c
parent7cfc95807c12a3351c640ad5dde954cba1d3ea4d (diff)
Initial part of the filesystem implementation. Basic folder access done.
-rw-r--r--src/library.js312
-rw-r--r--tests/runner.py78
2 files changed, 381 insertions, 9 deletions
diff --git a/src/library.js b/src/library.js
index 21078c3c..a60d5207 100644
--- a/src/library.js
+++ b/src/library.js
@@ -15,7 +15,309 @@
var Library = {
// ==========================================================================
- // stdio.h
+ // File system base.
+ // ==========================================================================
+
+ $FS__deps: ['$ERRNO_CODES', '__setErrNo'],
+ $FS: {
+ // The main file system tree. All the contents are inside this.
+ root: {
+ read: true,
+ write: false,
+ isFolder: true,
+ timestamp: new Date(),
+ inodeNumber: 1,
+ contents: {}
+ },
+ // The path to the current folder.
+ currentPath: '/',
+ // The inode to assign to the next created object.
+ nextInode: 2,
+ // Currently opened file or directory streams. Padded with null so the zero
+ // index is unused, as the indices are used as pointers. This is not split
+ // into separate fileStreams and folderStreams lists because the pointers
+ // must be interchangeable, e.g. when used in fdopen().
+ streams: [null],
+ // Converts any path to an absolute path. Resolves embedded "." and ".."
+ // parts.
+ absolutePath: function(relative, base) {
+ if (base === undefined) base = FS.currentPath;
+ else if (relative[0] == '/') base = '';
+ var full = base + '/' + relative;
+ var parts = full.split('/').reverse();
+ var absolute = [''];
+ while (parts.length) {
+ var part = parts.pop();
+ if (part == '' || part == '.') {
+ // Nothing.
+ } else if (part == '..') {
+ if (absolute.length <= 1) return null;
+ absolute.pop();
+ } else {
+ absolute.push(part);
+ }
+ }
+ return absolute.join('/');
+ },
+ // Finds the file system object at a given path.
+ findObject: function(path) {
+ var linksVisited = 0;
+ path = FS.absolutePath(path);
+ if (path === null) {
+ ___setErrNo(ERRNO_CODES.ENOENT);
+ return null;
+ }
+ path = path.split('/').reverse();
+ path.pop();
+ var current = FS.root;
+ while (path.length) {
+ var target = path.pop();
+ if (!current.isFolder) {
+ ___setErrNo(ERRNO_CODES.ENOTDIR);
+ return null;
+ } else if (!current.read) {
+ ___setErrNo(ERRNO_CODES.EACCESS);
+ return null;
+ } else if (!current.contents.hasOwnProperty(target)) {
+ ___setErrNo(ERRNO_CODES.ENOENT);
+ return null;
+ }
+ current = current.contents[target];
+ if (current.link) {
+ current = FS.findObject(current.link);
+ if (++linksVisited > 64) {
+ ___setErrNo(ERRNO_CODES.ELOOP);
+ return null;
+ }
+ }
+ }
+ return current;
+ },
+ // Creates a file system record: file, link, device or folder.
+ createObject: function(parent, name, properties, canRead, canWrite) {
+ if (!parent) parent = '/';
+ if (typeof parent === 'string') parent = FS.findObject(parent);
+
+ if (!parent) throw new Error('Parent path must exist.');
+ if (!parent.isFolder) throw new Error('Parent must be a folder.');
+ if (!name || name == '.' || name == '..') {
+ throw new Error('Name must not be empty.');
+ }
+ if (parent.contents.hasOwnProperty(name)) {
+ throw new Error("Can't overwrite object.");
+ }
+
+ parent.contents[name] = {
+ read: canRead === undefined ? true : canRead,
+ write: canWrite === undefined ? false : canWrite,
+ timestamp: new Date(),
+ inodeNumber: FS.nextInode++
+ };
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key)) {
+ parent.contents[name][key] = properties[key];
+ }
+ }
+
+ return parent.contents[name];
+ },
+ // Creates a folder.
+ createFolder: function(parent, name, canRead, canWrite) {
+ var properties = {isFolder: true, contents: {}};
+ return FS.createObject(parent, name, properties, canRead, canWrite);
+ },
+ // Creates a a folder and all its missing parents.
+ createPath: function(parent, path, canRead, canWrite) {
+ var current = FS.findObject(parent);
+ if (current === null) throw new Error('Invalid parent.');
+ path = path.split('/').reverse();
+ while (path.length) {
+ var part = path.pop();
+ if (!part) continue;
+ if (!current.contents.hasOwnProperty(part)) {
+ FS.createFolder(current, part, canRead, canWrite);
+ }
+ current = current.contents[part];
+ }
+ return current;
+ },
+
+ // Creates a file record, given specific properties.
+ createFile: function(parent, name, properties, canRead, canWrite) {
+ properties.isFolder = false;
+ return FS.createObject(parent, name, properties, canRead, canWrite);
+ },
+ // Creates a file record from existing data.
+ createDataFile: function(parent, name, data, canRead, canWrite) {
+ if (typeof data === 'string') {
+ var dataArray = [];
+ for (var i = 0; i < data; i++) dataArray.push(data.charCodeAt(i));
+ data = dataArray;
+ }
+ return FS.createFile(parent, name, {contents: data}, canRead, canWrite);
+ },
+ // Creates a file record for lazy-loading from a URL.
+ createLazyFile: function(parent, name, url, canRead, canWrite) {
+ return FS.createFile(parent, name, {url: url}, canRead, canWrite);
+ },
+ // Creates a link to a sepcific local path.
+ createLink: function(parent, name, target, canRead, canWrite) {
+ return FS.createFile(parent, name, {link: target}, canRead, canWrite);
+ },
+ // Creates a device with read and write callbacks.
+ createDevice: function(parent, name, read, write) {
+ var ops = {read: read, write: write};
+ return FS.createFile(parent, name, ops, Boolean(read), Boolean(write));
+ },
+ },
+
+ // ==========================================================================
+ // dirent.h
+ // ==========================================================================
+
+ // TODO: Switch to dynamically calculated layout.
+ //__dirent_struct_layout: Runtime.generateStructInfo('dirent'),
+ __dirent_struct_layout: {
+ __size__: 268,
+ // The inode number of the entry.
+ d_ino: 0,
+ // The offset of the next entry.
+ d_off: 4,
+ // The length of the d_name buffer.
+ d_reclen: 8,
+ // The filename of the entry.
+ d_name: 11
+ },
+ opendir__deps: ['$FS', '__setErrNo', '__dirent_struct_layout'],
+ opendir: function(dirname) {
+ // DIR *opendir(const char *dirname);
+ // http://pubs.opengroup.org/onlinepubs/007908799/xsh/opendir.html
+ // NOTE: Calculating absolute path redundantly since we need to associate it
+ // with the opened stream.
+ var path = FS.absolutePath(Pointer_stringify(dirname));
+ if (path === null) {
+ ___setErrNo(ERRNO_CODES.ENOENT);
+ return null;
+ }
+ var target = FS.findObject(path);
+ if (target === null) return 0;
+ if (!target.isFolder) {
+ ___setErrNo(ERRNO_CODES.ENOTDIR);
+ return 0;
+ } else if (!target.read) {
+ ___setErrNo(ERRNO_CODES.EACCES);
+ return 0;
+ }
+ var id = FS.streams.length;
+ var contents = [];
+ for (var key in target.contents) contents.push(key);
+ FS.streams[id] = {
+ isFolder: true,
+ path: path,
+ object: target,
+ // Remember the contents at the time of opening in an array, so we can
+ // seek between them relying on a single order.
+ contents: contents,
+ // An index into contents. Special values: -2 is ".", -1 is "..".
+ position: -2,
+ // Each stream has its own area for readdir() returns.
+ currentEntry: _malloc(___dirent_struct_layout.__size__)
+ };
+ return id;
+ },
+ closedir__deps: ['$FS', '__setErrNo'],
+ closedir: function(dirp) {
+ // int closedir(DIR *dirp);
+ // http://pubs.opengroup.org/onlinepubs/007908799/xsh/closedir.html
+ if (!FS.streams[dirp] || !FS.streams[dirp].isFolder) {
+ return ___setErrNo(ERRNO_CODES.EBADF);
+ } else {
+ _free(FS.streams[dirp].currentEntry);
+ delete FS.streams[dirp];
+ return 0;
+ }
+ },
+ telldir__deps: ['$FS', '__setErrNo'],
+ telldir: function(dirp) {
+ // long int telldir(DIR *dirp);
+ // http://pubs.opengroup.org/onlinepubs/007908799/xsh/telldir.html
+ if (!FS.streams[dirp] || !FS.streams[dirp].isFolder) {
+ return ___setErrNo(ERRNO_CODES.EBADF);
+ } else {
+ return FS.streams[dirp].position;
+ }
+ },
+ seekdir__deps: ['$FS', '__setErrNo'],
+ seekdir: function(dirp, loc) {
+ // void seekdir(DIR *dirp, long int loc);
+ // http://pubs.opengroup.org/onlinepubs/007908799/xsh/seekdir.html
+ if (!FS.streams[dirp] || !FS.streams[dirp].isFolder) {
+ ___setErrNo(ERRNO_CODES.EBADF);
+ } else if (loc >= FS.streams[dirp].contents.length) {
+ ___setErrNo(ERRNO_CODES.EINVAL);
+ } else {
+ FS.streams[dirp].position = loc;
+ }
+ },
+ rewinddir__deps: ['seekdir'],
+ rewinddir: function(dirp) {
+ // void rewinddir(DIR *dirp);
+ // http://pubs.opengroup.org/onlinepubs/007908799/xsh/rewinddir.html
+ _seekdir(dirp, -2);
+ },
+ readdir_r__deps: ['$FS', '__setErrNo', '__dirent_struct_layout'],
+ readdir_r: function(dirp, entry, result) {
+ // int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
+ // http://pubs.opengroup.org/onlinepubs/007908799/xsh/readdir_r.html
+ if (!FS.streams[dirp] || !FS.streams[dirp].isFolder) {
+ return ___setErrNo(ERRNO_CODES.EBADF);
+ }
+ var stream = FS.streams[dirp];
+ var loc = stream.position;
+ if (loc < -2 || loc >= FS.streams[dirp].contents.length) {
+ {{{ makeSetValue('result', '0', '0', '*i8') }}}
+ } else {
+ var name, inode;
+ if (loc === -2) {
+ name = '.';
+ inode = 1; // Really undefined.
+ } else if (loc === -1) {
+ name = '..';
+ inode = 1; // Really undefined.
+ } else {
+ name = stream.contents[loc];
+ inode = stream.object.contents[name].inodeNumber;
+ }
+ stream.position++;
+ {{{ makeSetValue('entry', '___dirent_struct_layout.d_ino', 'inode', 'i32') }}}
+ {{{ makeSetValue('entry', '___dirent_struct_layout.d_off', 'stream.position', 'i32') }}}
+ {{{ makeSetValue('entry', '___dirent_struct_layout.d_reclen', 'name.length + 1', 'i32') }}}
+ for (var i = 0; i < name.length; i++) {
+ {{{ makeSetValue('entry', '___dirent_struct_layout.d_name + i', 'name.charCodeAt(i)', 'i8') }}}
+ }
+ {{{ makeSetValue('entry', '___dirent_struct_layout.d_name + i', '0', 'i8') }}}
+ {{{ makeSetValue('result', '0', 'entry', '*i8') }}}
+ }
+ return 0;
+ },
+ readdir__deps: ['readdir_r', '__setErrNo'],
+ readdir: function(dirp) {
+ // struct dirent *readdir(DIR *dirp);
+ // http://pubs.opengroup.org/onlinepubs/007908799/xsh/readdir_r.html
+ if (!FS.streams[dirp] || !FS.streams[dirp].isFolder) {
+ ___setErrNo(ERRNO_CODES.EBADF);
+ return 0;
+ } else {
+ if (!_readdir.result) _readdir.result = _malloc(4);
+ _readdir_r(dirp, FS.streams[dirp].currentEntry, _readdir.result);
+ if ({{{ makeGetValue(0, '_readdir.result', '*i8') }}} === 0) {
+ return 0;
+ } else {
+ return FS.streams[dirp].currentEntry;
+ }
+ }
+ },
+
// ==========================================================================
_scanString: function() {
@@ -2271,14 +2573,6 @@ var Library = {
},
// ==========================================================================
- // dirent.h
- // ==========================================================================
-
- opendir: function(pname) {
- return 0;
- },
-
- // ==========================================================================
// ** emscripten.h **
// ==========================================================================
emscripten_run_script: function(ptr) {
diff --git a/tests/runner.py b/tests/runner.py
index d3573d2c..bd55a573 100644
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -2078,6 +2078,84 @@ if 'benchmark' not in sys.argv:
src = open(path_from_root('tests', 'files.cpp'), 'r').read()
self.do_test(src, 'size: 7\ndata: 100,-56,50,25,10,77,123\ninput:hi there!\ntexto\ntexte\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\n', post_build=post)
+ def test_folders(self):
+ def addPreRun(filename):
+ src = open(filename, 'r').read().replace(
+ '// {{PRE_RUN_ADDITIONS}}',
+ '''
+ FS.createFolder('/', 'test', true, false);
+ FS.createPath('/', 'test/hello/world/', true, false);
+ FS.createPath('/test', 'goodbye/world/', true, false);
+ FS.createPath('/test/goodbye', 'noentry', false, false);
+ FS.createDataFile('/test', 'freeforall.ext', 'abc', true, true);
+ FS.createDataFile('/test', 'restricted.ext', 'def', false, false);
+ '''
+ )
+ open(filename, 'w').write(src)
+
+ src = r'''
+ #include <stdio.h>
+ #include <dirent.h>
+ #include <errno.h>
+
+ int main() {
+ struct dirent *e;
+
+ // Basic correct behaviour.
+ DIR* d = opendir("/test");
+ printf("--E: %d\n", errno);
+ while ((e = readdir(d))) puts(e->d_name);
+ printf("--E: %d\n", errno);
+
+ // Empty folder; tell/seek.
+ puts("****");
+ d = opendir("/test/hello/world/");
+ e = readdir(d);
+ puts(e->d_name);
+ int pos = telldir(d);
+ e = readdir(d);
+ puts(e->d_name);
+ seekdir(d, pos);
+ e = readdir(d);
+ puts(e->d_name);
+
+ // Errors.
+ puts("****");
+ printf("--E: %d\n", errno);
+ d = opendir("/test/goodbye/noentry");
+ printf("--E: %d, D: %d\n", errno, d);
+ d = opendir("/i/dont/exist");
+ printf("--E: %d, D: %d\n", errno, d);
+ d = opendir("/test/freeforall.ext");
+ printf("--E: %d, D: %d\n", errno, d);
+ while ((e = readdir(d))) puts(e->d_name);
+ printf("--E: %d\n", errno);
+
+ return 0;
+ }
+ '''
+ expected = '''
+ --E: 0
+ .
+ ..
+ hello
+ goodbye
+ freeforall.ext
+ restricted.ext
+ --E: 0
+ ****
+ .
+ ..
+ ..
+ ****
+ --E: 0
+ --E: 13, D: 0
+ --E: 2, D: 0
+ --E: 20, D: 0
+ --E: 9
+ '''
+ self.do_test(src, re.sub('(^|\n)\s+', '\\1', expected), post_build=addPreRun)
+
### 'Big' tests
def test_fannkuch(self):