diff --git a/README.md b/README.md index c11006b..c954638 100644 --- a/README.md +++ b/README.md @@ -328,3 +328,11 @@ Asynchronous readlink(2). Callback gets `(error, linkContents)`, where `linkCont #### fs.lstat(path, callback) Asynchronous lstat(2). Callback gets `(error, stats)`, See `fs.stat`. + +#### fs.truncate(path, length, callback) + +Asynchronous truncate(2). Callback gets no additional arguments. + +#### fs.ftruncate(fd, length, callback) + +Asynchronous ftruncate(2). Callback gets no additional arguments. diff --git a/src/fs.js b/src/fs.js index 1d03232..a80b5df 100644 --- a/src/fs.js +++ b/src/fs.js @@ -943,6 +943,96 @@ define(function(require) { } } + function truncate_file(context, path, length, callback) { + path = normalize(path); + + var fileNode; + + function read_file_data (error, node) { + if (error) { + callback(error); + } else if(node.mode == MODE_DIRECTORY ) { + callback(new EIsDirectory('the named file is a directory')); + } else{ + fileNode = node; + context.get(fileNode.data, truncate_file_data); + } + } + + function truncate_file_data(error, fileData) { + if (error) { + callback(error); + } else { + var data = new Uint8Array(length); + if(fileData) { + data.set(fileData.subarray(0, length)); + } + context.put(fileNode.data, data, update_file_node); + } + } + + function update_file_node (error) { + if(error) { + callback(error); + } else { + fileNode.size = length; + fileNode.mtime = Date.now(); + fileNode.version += 1; + context.put(fileNode.id, fileNode, callback); + } + } + + if(length < 0) { + callback(new EInvalid('length cannot be negative')); + } else { + find_node(context, path, read_file_data); + } + } + + function ftruncate_file(context, ofd, length, callback) { + var fileNode; + + function read_file_data (error, node) { + if (error) { + callback(error); + } else if(node.mode == MODE_DIRECTORY ) { + callback(new EIsDirectory('the named file is a directory')); + } else{ + fileNode = node; + context.get(fileNode.data, truncate_file_data); + } + } + + function truncate_file_data(error, fileData) { + if (error) { + callback(error); + } else { + var data = new Uint8Array(length); + if(fileData) { + data.set(fileData.subarray(0, length)); + } + context.put(fileNode.data, data, update_file_node); + } + } + + function update_file_node (error) { + if(error) { + callback(error); + } else { + fileNode.size = length; + fileNode.mtime = Date.now(); + fileNode.version += 1; + context.put(fileNode.id, fileNode, callback); + } + } + + if(length < 0) { + callback(new EInvalid('length cannot be negative')); + } else { + context.get(ofd.id, read_file_data); + } + } + function validate_flags(flags) { if(!_(O_FLAGS).has(flags)) { return null; @@ -1505,14 +1595,38 @@ define(function(require) { lstat_file(context, path, check_result); } - function _truncate(path, length, callback) { - // TODO - // if(!nullCheck(path, callback)) return; + function _truncate(context, path, length, callback) { + if(!nullCheck(path, callback)) return; + + function check_result(error) { + if(error) { + callback(error); + } else { + callback(null); + } + } + + truncate_file(context, path, length, check_result); } - function _ftruncate(fd, length, callback) { - // TODO - // if(!nullCheck(path, callback)) return; + function _ftruncate(fs, context, fd, length, callback) { + function check_result(error) { + if(error) { + callback(error); + } else { + callback(null); + } + } + + var ofd = fs.openFiles[fd]; + + if(!ofd) { + callback(new EBadFileDescriptor('invalid file descriptor')); + } else if(!_(ofd.flags).contains(O_WRITE)) { + callback(new EBadFileDescriptor('descriptor does not permit writing')); + } else { + ftruncate_file(context, ofd, length, check_result); + } } @@ -1723,6 +1837,28 @@ define(function(require) { ); if(error) callback(error); }; + FileSystem.prototype.truncate = function(path, length, callback) { + callback = maybeCallback(callback); + var fs = this; + var error = fs.queueOrRun( + function() { + var context = fs.provider.getReadWriteContext(); + _truncate(context, path, length, callback); + } + ); + if(error) callback(error); + }; + FileSystem.prototype.ftruncate = function(fd, length, callback) { + callback = maybeCallback(callback); + var fs = this; + var error = fs.queueOrRun( + function() { + var context = fs.provider.getReadWriteContext(); + _ftruncate(fs, context, fd, length, callback); + } + ); + if(error) callback(error); + }; return { FileSystem: FileSystem, diff --git a/tests/spec/fs.truncate.spec.js b/tests/spec/fs.truncate.spec.js new file mode 100644 index 0000000..a9fe563 --- /dev/null +++ b/tests/spec/fs.truncate.spec.js @@ -0,0 +1,278 @@ +define(["IDBFS"], function(IDBFS) { + + describe('fs.truncate', function() { + beforeEach(function() { + this.db_name = mk_db_name(); + this.fs = new IDBFS.FileSystem({ + name: this.db_name, + flags: 'FORMAT' + }); + }); + + afterEach(function() { + indexedDB.deleteDatabase(this.db_name); + delete this.fs; + }); + + it('should be a function', function() { + expect(typeof this.fs.truncate).toEqual('function'); + }); + + it('should error when length is negative', function() { + var complete = false; + var _error, _result; + var that = this; + + var contents = "This is a file."; + + that.fs.writeFile('/myfile', contents, function(error) { + if(error) throw error; + + that.fs.truncate('/myfile', -1, function(error) { + _error = error; + complete = true; + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toBeDefined(); + }); + }); + + it('should error when path is not a file', function() { + var complete = false; + var _error, _result; + var that = this; + + that.fs.truncate('/', 0, function(error) { + _error = error; + complete = true; + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toBeDefined(); + }); + }); + + it('should truncate a file', function() { + var complete = false; + var _error, _result; + var that = this; + + var buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + var truncated = new Uint8Array([1]); + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + + var fd = result; + that.fs.write(fd, buffer, 0, buffer.length, 0, function(error, result) { + if(error) throw error; + + that.fs.close(fd, function(error) { + if(error) throw error; + + that.fs.truncate('/myfile', 1, function(error) { + _error = error; + + that.fs.readFile('/myfile', function(error, result) { + if(error) throw error; + + _result = result; + complete = true; + }); + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toEqual(null); + expect(_result).toEqual(truncated); + }); + }); + + it('should pad a file with zeros when the length is greater than the file size', function() { + var complete = false; + var _error, _result; + var that = this; + + var buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + var truncated = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 0]); + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + + var fd = result; + that.fs.write(fd, buffer, 0, buffer.length, 0, function(error, result) { + if(error) throw error; + + that.fs.close(fd, function(error) { + if(error) throw error; + + that.fs.truncate('/myfile', 9, function(error) { + _error = error; + + that.fs.readFile('/myfile', function(error, result) { + if(error) throw error; + + _result = result; + complete = true; + }); + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toEqual(null); + expect(_result).toEqual(truncated); + }); + }); + + it('should update the file size', function() { + var complete = false; + var _error, _result; + var that = this; + + var buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + + var fd = result; + that.fs.write(fd, buffer, 0, buffer.length, 0, function(error, result) { + if(error) throw error; + + that.fs.close(fd, function(error) { + if(error) throw error; + + that.fs.truncate('/myfile', 0, function(error) { + _error = error; + + that.fs.stat('/myfile', function(error, result) { + if(error) throw error; + + _result = result; + complete = true; + }); + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toEqual(null); + expect(_result.size).toEqual(0); + }); + }); + + it('should truncate a valid descriptor', function() { + var complete = false; + var _error, _result; + var that = this; + + var buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + + var fd = result; + that.fs.write(fd, buffer, 0, buffer.length, 0, function(error, result) { + if(error) throw error; + + that.fs.ftruncate(fd, 0, function(error) { + _error = error; + + that.fs.fstat(fd, function(error, result) { + if(error) throw error; + + _result = result; + complete=true; + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toEqual(null); + expect(_result.size).toEqual(0); + }); + }); + + it('should follow symbolic links', function() { + var complete = false; + var _error, _result, _result2; + var that = this; + + var buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + + var fd = result; + that.fs.write(fd, buffer, 0, buffer.length, 0, function(error, result) { + if(error) throw error; + + that.fs.close(fd, function(error) { + if(error) throw error; + + that.fs.symlink('/myfile', '/mylink', function(error) { + if(error) throw error; + + that.fs.truncate('/mylink', 0, function(error) { + _error = error; + + that.fs.stat('/myfile', function(error, result) { + if(error) throw error; + + _result = result; + that.fs.lstat('/mylink', function(error, result) { + if(error) throw error; + + _result2 = result; + complete=true; + }); + }); + }); + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toEqual(null); + expect(_result.size).toEqual(0); + expect(_result2.size).not.toEqual(0); + }); + }); + }); +}); \ No newline at end of file diff --git a/tests/test-manifest.js b/tests/test-manifest.js index 0f30a63..72c44a9 100644 --- a/tests/test-manifest.js +++ b/tests/test-manifest.js @@ -26,6 +26,7 @@ define([ "spec/fs.lseek.spec", "spec/fs.symlink.spec", "spec/fs.readlink.spec", + "spec/fs.truncate.spec", "spec/path-resolution.spec", // IDBFS.FileSystem.providers.*