diff options
author | max99x <max99x@gmail.com> | 2011-07-16 11:53:12 +0300 |
---|---|---|
committer | max99x <max99x@gmail.com> | 2011-07-16 11:53:12 +0300 |
commit | 7784274c687ad7a867b485bf327d1a09f0db4111 (patch) | |
tree | 0dcb6638579a30360048499a95eeed1c9260da4c | |
parent | 7cfc95807c12a3351c640ad5dde954cba1d3ea4d (diff) |
Initial part of the filesystem implementation. Basic folder access done.
-rw-r--r-- | src/library.js | 312 | ||||
-rw-r--r-- | tests/runner.py | 78 |
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): |