aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/library.js677
-rw-r--r--tests/runner.py194
-rw-r--r--tests/stat/output.txt174
-rw-r--r--tests/stat/src.c205
4 files changed, 1198 insertions, 52 deletions
diff --git a/src/library.js b/src/library.js
index c900c90d..bce56b0f 100644
--- a/src/library.js
+++ b/src/library.js
@@ -15,7 +15,622 @@
LibraryManager.library = {
// ==========================================================================
- // stdio.h
+ // File system base.
+ // ==========================================================================
+
+ $FS__deps: ['$ERRNO_CODES', '__setErrNo'],
+ $FS__postset: 'FS.ignorePermissions = false;',
+ $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,
+ // The file creation mask used by the program.
+ cmask: 0x1ff, // S_IRWXU | S_IRWXG | S_IRWXO.
+ // 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],
+ // Whether we are currently ignoring permissions. Useful when preparing the
+ // filesystem and creating files inside read-only folders.
+ ignorePermissions: true,
+ // Converts any path to an absolute path. Resolves embedded "." and ".."
+ // parts.
+ absolutePath: function(relative, base) {
+ if (!relative && relative !== '') return null;
+ 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. If dontResolveLastLink is
+ // set to true and the object is a symbolic link, it will be returned as is
+ // instead of being resolved. Links embedded in the path as still resolved.
+ findObject: function(path, dontResolveLastLink) {
+ 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;
+ var traversed = [''];
+ while (path.length) {
+ var target = path.pop();
+ if (!current.isFolder) {
+ ___setErrNo(ERRNO_CODES.ENOTDIR);
+ return null;
+ } else if (!current.read) {
+ ___setErrNo(ERRNO_CODES.EACCES);
+ return null;
+ } else if (!current.contents.hasOwnProperty(target)) {
+ ___setErrNo(ERRNO_CODES.ENOENT);
+ return null;
+ }
+ current = current.contents[target];
+ if (current.link && !(dontResolveLastLink && path.length == 0)) {
+ var link = FS.absolutePath(current.link, traversed.join('/'));
+ current = FS.findObject(link, dontResolveLastLink);
+ if (++linksVisited > 40) { // Usual Linux SYMLOOP_MAX.
+ ___setErrNo(ERRNO_CODES.ELOOP);
+ return null;
+ }
+ }
+ traversed.push(target);
+ }
+ 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) {
+ ___setErrNo(ERRNO_CODES.EACCES);
+ throw new Error('Parent path must exist.');
+ }
+ if (!parent.isFolder) {
+ ___setErrNo(ERRNO_CODES.ENOTDIR);
+ throw new Error('Parent must be a folder.');
+ }
+ if (!parent.write && !FS.ignorePermissions) {
+ ___setErrNo(ERRNO_CODES.EACCES);
+ throw new Error('Parent folder must be writeable.');
+ }
+ if (!name || name == '.' || name == '..') {
+ ___setErrNo(ERRNO_CODES.ENOENT);
+ throw new Error('Name must not be empty.');
+ }
+ if (parent.contents.hasOwnProperty(name)) {
+ ___setErrNo(ERRNO_CODES.EEXIST);
+ 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.length; 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 character device with input and output callbacks:
+ // input: takes no parameters, returns a byte value.
+ // output: takes a byte value, doesn't return anything.
+ // TODO: Decide how to handle flushing in a consistent manner.
+ createDevice: function(parent, name, input, output) {
+ if (!(input || output)) {
+ throw new Error('A device must have at least one callback defined.');
+ }
+ var ops = {input: input, output: output};
+ return FS.createFile(parent, name, ops, Boolean(input), Boolean(output));
+ },
+ // Makes sure a file's contents are loaded. Returns whether the file has
+ // been loaded successfully. No-op for files that have been loaded already.
+ forceLoadFile: function(obj) {
+ if (obj.input !== undefined || obj.output !== undefined ||
+ obj.link !== undefined || obj.contents !== undefined) return true;
+ var success = true;
+ if (typeof XMLHttpRequest !== 'undefined') {
+ // Browser.
+ // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available.
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', obj.url, false);
+ xhr.overrideMimeType('text/plain; charset=x-user-defined'); // Binary.
+ xhr.send(null);
+ if (xhr.status != 200 && xhr.status != 0) success = false;
+ obj.contents = intArrayFromString(xhr.responseText || '');
+ } else if (typeof read !== 'undefined') {
+ // Command-line.
+ try {
+ obj.contents = intArrayFromString(read(obj.url));
+ } catch (e) {
+ success = false;
+ }
+ } else {
+ throw new Error('Cannot load without read() or XMLHttpRequest.');
+ }
+ if (!success) ___setErrNo(ERRNO_CODES.EIO);
+ return success;
+ }
+ },
+
+ // ==========================================================================
+ // dirent.h
+ // ==========================================================================
+
+ __dirent_struct_layout: Runtime.generateStructInfo(
+ ['d_ino', 'd_off', 'd_reclen', 'd_type', 'd_name'],
+ '%struct.dirent'
+ ),
+ opendir__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__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', '$ERRNO_CODES'],
+ 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', '$ERRNO_CODES'],
+ 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', '$ERRNO_CODES'],
+ 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 {
+ var entries = 0;
+ for (var key in FS.streams[dirp].contents) entries++;
+ if (loc >= entries) {
+ ___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', '$ERRNO_CODES', '__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;
+ var entries = 0;
+ for (var key in stream.contents) entries++;
+ if (loc < -2 || loc >= entries) {
+ {{{ 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') }}}
+ var type = stream.isFolder ? 4 // DT_DIR, directory.
+ : stream.contents !== undefined ? 8 // DT_REG, regular file.
+ : stream.link !== undefined ? 10 // DT_LNK, symbolic link.
+ : 2 // DT_CHR, character device.
+ {{{ makeSetValue('entry', '___dirent_struct_layout.d_type', 'type', 'i8') }}}
+ {{{ makeSetValue('result', '0', 'entry', 'i8*') }}}
+ }
+ return 0;
+ },
+ readdir__deps: ['readdir_r', '__setErrNo', '$ERRNO_CODES'],
+ 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;
+ }
+ }
+ },
+ // TODO: Check if we need to link any aliases.
+
+ // ==========================================================================
+ // utime.h
+ // ==========================================================================
+
+ __utimbuf_struct_layout: Runtime.generateStructInfo(
+ ['actime', 'modtime'],
+ '%struct.utimbuf'
+ ),
+ utime__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', '__utimbuf_struct_layout'],
+ utime: function(path, times) {
+ // int utime(const char *path, const struct utimbuf *times);
+ // http://pubs.opengroup.org/onlinepubs/009695399/basedefs/utime.h.html
+ var time;
+ if (times) {
+ // NOTE: We don't keep track of access timestamps.
+ time = {{{ makeGetValue('times', '___utimbuf_struct_layout.modtime', 'i32') }}}
+ time = new Date(time * 1000);
+ } else {
+ time = new Date();
+ }
+ var file = FS.findObject(Pointer_stringify(path));
+ if (file === null) return -1;
+ if (!file.write) {
+ ___setErrNo(ERRNO_CODES.EPERM);
+ return -1;
+ }
+ file.timestamp = time;
+ return 0;
+ },
+
+ // ==========================================================================
+ // libgen.h
+ // ==========================================================================
+
+ __libgenSplitName: function(path) {
+ if (path === 0 || {{{ makeGetValue('path', 0, 'i8') }}} === 0) {
+ // Null or empty results in '.'.
+ var me = ___libgenSplitName;
+ if (!me.ret) {
+ me.ret = allocate(['.'.charCodeAt(0), 0], 'i8', ALLOC_STATIC);
+ }
+ return [me.ret, -1];
+ } else {
+ var slash = '/'.charCodeAt(0);
+ var allSlashes = true;
+ var slashPositions = [];
+ for (var i = 0; {{{ makeGetValue('path', 'i', 'i8') }}} !== 0; i++) {
+ if ({{{ makeGetValue('path', 'i', 'i8') }}} === slash) {
+ slashPositions.push(i);
+ } else {
+ allSlashes = false;
+ }
+ }
+ var length = i;
+ if (allSlashes) {
+ // All slashes result in a single slash.
+ {{{ makeSetValue('path', '1', '0', 'i8') }}}
+ return [path, -1];
+ } else {
+ // Strip trailing slashes.
+ while (slashPositions.length &&
+ slashPositions[slashPositions.length - 1] == length - 1) {
+ {{{ makeSetValue('path', 'slashPositions.pop(i)', '0', 'i8') }}}
+ length--;
+ }
+ return [path, slashPositions.pop()];
+ }
+ }
+ },
+ basename__deps: ['__libgenSplitName'],
+ basename: function(path) {
+ // char *basename(char *path);
+ // http://pubs.opengroup.org/onlinepubs/007908799/xsh/basename.html
+ var result = ___libgenSplitName(path);
+ return result[0] + result[1] + 1;
+ },
+ __xpg_basename: 'basename',
+ dirname__deps: ['__libgenSplitName'],
+ dirname: function(path) {
+ // char *dirname(char *path);
+ // http://pubs.opengroup.org/onlinepubs/007908799/xsh/dirname.html
+ var result = ___libgenSplitName(path);
+ if (result[1] == 0) {
+ {{{ makeSetValue('result[0]', 1, '0', 'i8') }}}
+ } else if (result[1] !== -1) {
+ {{{ makeSetValue('result[0]', 'result[1]', '0', 'i8') }}}
+ }
+ return result[0];
+ },
+
+ // ==========================================================================
+ // sys/stat.h
+ // ==========================================================================
+
+ __stat_struct_layout: Runtime.generateStructInfo(
+ ['st_dev', '__pad1', 'st_ino', 'st_mode', 'st_nlink', 'st_uid', 'st_gid',
+ 'st_rdev', '__pad2', 'st_size', 'st_blksize', 'st_blocks', 'st_atime',
+ 'st_mtime', 'st_ctime', '__unused4', '__unused5'],
+ '%struct.stat'
+ ),
+ stat__deps: ['$FS', '__stat_struct_layout'],
+ stat: function(path, buf, dontResolveLastLink) {
+ // http://pubs.opengroup.org/onlinepubs/7908799/xsh/stat.html
+ // int stat(const char *path, struct stat *buf);
+ // dontResolveLastLink is a shortcut for lstat(). It should never be used in
+ // client code.
+ var obj = FS.findObject(Pointer_stringify(path), dontResolveLastLink);
+ if (obj === null || !FS.forceLoadFile(obj)) return -1;
+
+ // Constants.
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_nlink', '1', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_uid', '0', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_gid', '0', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_blksize', '4096', 'i32') }}}
+
+ // Variables.
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_ino', 'obj.inodeNumber', 'i32') }}}
+ var time = Math.floor(obj.timestamp.getTime() / 1000);
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_atime', 'time', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_mtime', 'time', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_ctime', 'time', 'i32') }}}
+ var mode = 0;
+ if (obj.input !== undefined || obj.output !== undefined) {
+ // Device numbers reuse inode numbers.
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_dev', 'obj.inodeNumber', 'i64') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_rdev', 'obj.inodeNumber', 'i64') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_size', '0', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_blocks', '0', 'i32') }}}
+ mode = 0x2000; // S_IFCHR.
+ } else {
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_dev', '1', 'i64') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_rdev', '0', 'i64') }}}
+ // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize),
+ // but this is not required by the standard.
+ if (obj.isFolder) {
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_size', '4096', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_blocks', '1', 'i32') }}}
+ mode = 0x4000; // S_IFDIR.
+ } else {
+ var data = obj.contents || obj.link;
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_size', 'data.length', 'i32') }}}
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_blocks', 'Math.ceil(data.length / 4096)', 'i32') }}}
+ mode = obj.link === undefined ? 0x8000 : 0xA000; // S_IFREG, S_IFLNK.
+ }
+ }
+ if (obj.read) mode |= 0x16D; // S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH.
+ if (obj.write) mode |= 0x92; // S_IWUSR | S_IWGRP | S_IWOTH.
+ {{{ makeSetValue('buf', '___stat_struct_layout.st_mode', 'mode', 'i32') }}}
+
+ return 0;
+ },
+ lstat__deps: ['stat'],
+ lstat: function(path, buf) {
+ // int lstat(const char *path, struct stat *buf);
+ // http://pubs.opengroup.org/onlinepubs/7908799/xsh/lstat.html
+ return _stat(path, buf, true);
+ },
+ // TODO: Test once open() is implemented using FS.
+ fstat__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'stat'],
+ fstat: function(fildes, buf) {
+ // int fstat(int fildes, struct stat *buf);
+ // http://pubs.opengroup.org/onlinepubs/7908799/xsh/fstat.html
+ if (!FS.streams[fildes]) {
+ ___setErrNo(ERRNO_CODES.EBADF);
+ return -1;
+ } else {
+ var pathArray = intArrayFromString(FS.streams[filedes].path);
+ var pathPtr = allocate(pathArray, 'i8', ALLOC_NORMAL);
+ var result = _stat(pathPtr, buf);
+ _free(pathPtr);
+ return result;
+ }
+ },
+ mknod__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'strlen', 'strcpy', 'dirname', 'basename'],
+ mknod: function(path, mode, dev) {
+ // int mknod(const char *path, mode_t mode, dev_t dev);
+ // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mknod.html
+ if (dev !== 0 || !(mode & 0xC000)) { // S_IFREG | S_IFDIR.
+ // Can't create devices or pipes through mknod().
+ ___setErrNo(ERRNO_CODES.EINVAL);
+ return -1;
+ } else {
+ var properties = {contents: [], isFolder: Boolean(mode & 0x4000)}; // S_IFDIR.
+ var buffer = _malloc(_strlen(path) + 1);
+ var parent = Pointer_stringify(_dirname(_strcpy(buffer, path)));
+ var name = Pointer_stringify(_basename(_strcpy(buffer, path)));
+ try {
+ FS.createObject(parent, name, properties, mode & 0x100, mode & 0x80); // S_IRUSR, S_IWUSR.
+ _free(buffer);
+ return 0;
+ } catch (e) {
+ _free(buffer);
+ return -1;
+ }
+ }
+ },
+ mkdir__deps: ['mknod'],
+ mkdir: function(path, mode) {
+ // int mkdir(const char *path, mode_t mode);
+ // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mkdir.html
+ return _mknod(path, 0x4000 | (mode & 0x180), 0); // S_IFDIR, S_IRUSR | S_IWUSR.
+ },
+ mkfifo__deps: ['__setErrNo', '$ERRNO_CODES'],
+ mkfifo: function(path, mode) {
+ // int mkfifo(const char *path, mode_t mode);
+ // http://pubs.opengroup.org/onlinepubs/7908799/xsh/mkfifo.html
+ // NOTE: We support running only a single process, and named pipes require
+ // blocking, which we can't provide. The error code is not very
+ // accurate, but it's the closest among those allowed in the standard
+ // and unlikely to result in retries.
+ ___setErrNo(ERRNO_CODES.EROFS);
+ return -1;
+ },
+ chmod__deps: ['$FS'],
+ chmod: function(path, mode) {
+ // int chmod(const char *path, mode_t mode);
+ // http://pubs.opengroup.org/onlinepubs/7908799/xsh/chmod.html
+ var obj = FS.findObject(Pointer_stringify(path));
+ if (obj === null) return -1;
+ obj.read = mode & 0x100; // S_IRUSR.
+ obj.write = mode & 0x80; // S_IWUSR.
+ obj.timestamp = new Date();
+ return 0;
+ },
+ // TODO: Test once open() is implemented using FS.
+ fchmod__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'chmod'],
+ fchmod: function(fildes, mode) {
+ // int fchmod(int fildes, mode_t mode);
+ // http://pubs.opengroup.org/onlinepubs/7908799/xsh/fchmod.html
+ if (!FS.streams[fildes]) {
+ ___setErrNo(ERRNO_CODES.EBADF);
+ return -1;
+ } else {
+ var pathArray = intArrayFromString(FS.streams[filedes].path);
+ var pathPtr = allocate(pathArray, 'i8', ALLOC_NORMAL);
+ var result = _chmod(pathPtr, mode);
+ _free(pathPtr);
+ return result;
+ }
+ },
+ // TODO: Test once cmask is used.
+ umask__deps: ['$FS'],
+ umask: function(newMask) {
+ var oldMask = FS.cmask;
+ FS.cmask = newMask;
+ return oldMask;
+ },
+ // TODO: Check if these or any other aliases are needed:
+ // __01fstat64_: 'fstat',
+ // __01stat64_: 'stat',
+ // __01lstat64_: 'stat',
+
// ==========================================================================
_scanString: function() {
@@ -812,36 +1427,6 @@ LibraryManager.library = {
fcntl: function() { }, // TODO...
- fstat: function(stream, ptr) {
- var info = STDIO.streams[stream];
- if (!info) return -1;
- // XXX: hardcoded indexes into the structure.
- try {
- {{{ makeSetValue('ptr', '$struct_stat___FLATTENER[0]', '1', 'i32') }}} // st_dev
- {{{ makeSetValue('ptr', '$struct_stat___FLATTENER[15]', 'stream', 'i32') }}} // st_ino
- {{{ makeSetValue('ptr', '$struct_stat___FLATTENER[9]', 'info.data.length', 'i32') }}} // st_size
- } catch(e) {
- // no FLATTENER
- {{{ makeSetValue('ptr', '0', '1', 'i32') }}}
- {{{ makeSetValue('ptr', '15', 'stream', 'i32') }}}
- {{{ makeSetValue('ptr', '9', 'info.data.length', 'i32') }}}
- }
- // TODO: other fields
- return 0;
- },
-
- stat__deps: ['open', 'fstat'],
- stat: function(filename, ptr) {
- if (typeof window === 'undefined') {
- // XXX d8 hangs if you try to read a folder.
- // http://code.google.com/p/v8/issues/detail?id=1533
- return 0;
- }
- // TODO: Handle symbolic links.
- var stream = _open(filename, 0, 256); // RDONLY, 0400.
- return _fstat(stream, ptr);
- },
-
mmap: function(start, num, prot, flags, stream, offset) {
// Leaky and non-shared... FIXME
var info = STDIO.streams[stream];
@@ -1072,6 +1657,7 @@ LibraryManager.library = {
{{{ makeCopyValues('pdest+i', 'psrc+i', 1, 'i8') }}}
i ++;
} while ({{{ makeGetValue('psrc', 'i-1', 'i8') }}} != 0);
+ return pdest;
},
strncpy: function(pdest, psrc, num) {
@@ -1081,10 +1667,12 @@ LibraryManager.library = {
{{{ makeSetValue('pdest', 'i', 'curr', 'i8') }}}
padding = padding || {{{ makeGetValue('psrc', 'i', 'i8') }}} == 0;
}
+ return pdest;
},
+ strcat__deps: ['strlen'],
strcat: function(pdest, psrc) {
- var len = Pointer_stringify(pdest).length; // TODO: use strlen, but need dependencies system
+ var len = _strlen(pdest);
var i = 0;
do {
{{{ makeCopyValues('pdest+len+i', 'psrc+i', 1, 'i8') }}}
@@ -1093,8 +1681,9 @@ LibraryManager.library = {
return pdest;
},
+ strncat__deps: ['strlen'],
strncat: function(pdest, psrc, num) {
- var len = Pointer_stringify(pdest).length; // TODO: use strlen, but need dependencies system
+ var len = _strlen(pdest);
var i = 0;
while(1) {
{{{ makeCopyValues('pdest+len+i', 'psrc+i', 1, 'i8') }}}
@@ -1195,8 +1784,9 @@ LibraryManager.library = {
return 0;
},
+ strrchr__deps: ['strlen'],
strrchr: function(ptr, chr) {
- var ptr2 = ptr + Pointer_stringify(ptr).length; // TODO: use strlen, but need dependencies system
+ var ptr2 = ptr + _strlen(ptr);
do {
if ({{{ makeGetValue('ptr2', 0, 'i8') }}} == chr) return ptr2;
ptr2--;
@@ -1567,7 +2157,6 @@ LibraryManager.library = {
return Math.pow(2, x);
},
-
// ==========================================================================
// dlfcn.h - Dynamic library loading
//
@@ -2019,14 +2608,6 @@ LibraryManager.library = {
},
// ==========================================================================
- // stat.h
- // ==========================================================================
-
- __01fstat64_: 'fstat',
- __01stat64_: 'stat',
- __01lstat64_: 'stat',
-
- // ==========================================================================
// locale.h
// ==========================================================================
@@ -2051,7 +2632,7 @@ LibraryManager.library = {
nl_langinfo: function(item) {
var me = _nl_langinfo;
if (!me.ret) {
- me.ret = allocate(intArrayFromString("eh?"), 'i8', ALLOC_NORMAL);
+ me.ret = allocate(intArrayFromString("eh?"), 'i8', ALLOC_NORMAL);
}
return me.ret;
},
@@ -2262,14 +2843,6 @@ LibraryManager.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 25995c5c..4a738c66 100644
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -2078,6 +2078,200 @@ 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)
+
+ def test_stat(self):
+ def addPreRun(filename):
+ src = open(filename, 'r').read().replace(
+ '// {{PRE_RUN_ADDITIONS}}',
+ '''
+ var f1 = FS.createFolder('/', 'test', true, true);
+ var f2 = FS.createDataFile(f1, 'file', 'abcdef', true, true);
+ var f3 = FS.createLink(f1, 'link', 'file', true, true);
+ var f4 = FS.createDevice(f1, 'device', function(){}, function(){});
+ f1.timestamp = f2.timestamp = f3.timestamp = f4.timestamp = new Date(1200000000000);
+ '''
+ )
+ open(filename, 'w').write(src)
+ src = open(path_from_root('tests', 'stat', 'src.c'), 'r').read()
+ expected = open(path_from_root('tests', 'stat', 'output.txt'), 'r').read()
+ self.do_test(src, expected, post_build=addPreRun)
+
+ def test_libgen(self):
+ src = r'''
+ #include <stdio.h>
+ #include <libgen.h>
+
+ int main() {
+ char p1[16] = "/usr/lib", p1x[16] = "/usr/lib";
+ printf("%s -> ", p1);
+ printf("%s : %s\n", dirname(p1x), basename(p1));
+
+ char p2[16] = "/usr", p2x[16] = "/usr";
+ printf("%s -> ", p2);
+ printf("%s : %s\n", dirname(p2x), basename(p2));
+
+ char p3[16] = "/usr/", p3x[16] = "/usr/";
+ printf("%s -> ", p3);
+ printf("%s : %s\n", dirname(p3x), basename(p3));
+
+ char p4[16] = "/usr/lib///", p4x[16] = "/usr/lib///";
+ printf("%s -> ", p4);
+ printf("%s : %s\n", dirname(p4x), basename(p4));
+
+ char p5[16] = "/", p5x[16] = "/";
+ printf("%s -> ", p5);
+ printf("%s : %s\n", dirname(p5x), basename(p5));
+
+ char p6[16] = "///", p6x[16] = "///";
+ printf("%s -> ", p6);
+ printf("%s : %s\n", dirname(p6x), basename(p6));
+
+ char p7[16] = "/usr/../lib/..", p7x[16] = "/usr/../lib/..";
+ printf("%s -> ", p7);
+ printf("%s : %s\n", dirname(p7x), basename(p7));
+
+ char p8[16] = "", p8x[16] = "";
+ printf("(empty) -> %s : %s\n", dirname(p8x), basename(p8));
+
+ printf("(null) -> %s : %s\n", dirname(0), basename(0));
+
+ return 0;
+ }
+ '''
+ expected = '''
+ /usr/lib -> /usr : lib
+ /usr -> / : usr
+ /usr/ -> / : usr
+ /usr/lib/// -> /usr : lib
+ / -> / : /
+ /// -> / : /
+ /usr/../lib/.. -> /usr/../lib : ..
+ (empty) -> . : .
+ (null) -> . : .
+ '''
+ self.do_test(src, re.sub('(^|\n)\s+', '\\1', expected))
+
+ def test_utime(self):
+ def addPreRunAndChecks(filename):
+ src = open(filename, 'r').read().replace(
+ '// {{PRE_RUN_ADDITIONS}}',
+ '''
+ var TEST_F1 = FS.createFolder('/', 'writeable', true, true);
+ var TEST_F2 = FS.createFolder('/', 'unwriteable', true, false);
+ '''
+ ).replace(
+ '// {{POST_RUN_ADDITIONS}}',
+ '''
+ print('first changed: ' + (TEST_F1.timestamp.getTime() == 1200000000000));
+ print('second changed: ' + (TEST_F2.timestamp.getTime() == 1200000000000));
+ '''
+ )
+ open(filename, 'w').write(src)
+
+ src = r'''
+ #include <stdio.h>
+ #include <errno.h>
+ #include <utime.h>
+
+ int main() {
+ struct utimbuf t = {1000000000, 1200000000};
+ char* writeable = "/writeable";
+ char* unwriteable = "/unwriteable";
+
+ utime(writeable, &t);
+ printf("writeable errno: %d\n", errno);
+
+ utime(unwriteable, &t);
+ printf("unwriteable errno: %d\n", errno);
+
+ return 0;
+ }
+ '''
+ expected = '''
+ writeable errno: 0
+ unwriteable errno: 1
+ first changed: true
+ second changed: false
+ '''
+ self.do_test(src, re.sub('(^|\n)\s+', '\\1', expected), post_build=addPreRunAndChecks)
+
### 'Big' tests
def test_fannkuch(self):
diff --git a/tests/stat/output.txt b/tests/stat/output.txt
new file mode 100644
index 00000000..5253a834
--- /dev/null
+++ b/tests/stat/output.txt
@@ -0,0 +1,174 @@
+--stat FOLDER--
+ret: 0
+errno: 0
+st_dev: 1
+st_ino: 2
+st_mode: 040777
+st_nlink: 1
+st_rdev: 0
+st_size: 4096
+st_atime: 1200000000
+st_mtime: 1200000000
+st_ctime: 1200000000
+st_blks