diff --git a/README.md b/README.md
index 61e72bc1..5ad3aaeb 100644
--- a/README.md
+++ b/README.md
@@ -372,6 +372,7 @@ const fs = new Filer.FileSystem(options, callback);
* [fs.utimes(path, atime, mtime, callback)](#utimes)
* [fs.chown(path, uid, gid, callback)](#chown)
* [fs.fchown(fd, uid, gid, callback)](#fchown)
+* [fs.lchown(path, uid, gid, callback)](#lchown)
* [fs.chmod(path, mode, callback)](#chmod)
* [fs.fchmod(fd, mode, callback)](#fchmod)
* [fs.futimes(fd, atime, mtime, callback)](#fsutimes)
@@ -923,6 +924,19 @@ fs.open('/myfile.txt', function(err, fd) {
});
```
+#### fs.lchown(path, uid, gid, callback)
+
+lchown() is like chown(), but does not dereference symbolic links. Asynchronous [lchown(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/chown.html). Callback gets no additional arguments. Both `uid` (user id) and `gid` (group id) arguments should be a JavaScript Number. By default, `0x0` is used (i.e., `root:root` ownership).
+
+Example:
+
+```javascript
+fs.lchown('/myfile.txt', 500, 500, function(err) {
+ if(err) throw err;
+ // /myfile.txt is now owned by user with id 500, group 500
+});
+```
+
#### fs.chmod(path, mode, callback)
Changes the mode of a file. Asynchronous [chmod(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/chmod.html). Callback gets no additional arguments. The `mode` argument should be a JavaScript Number, which combines file type and permission information. Here are a list of common values useful for setting the `mode`:
diff --git a/src/filesystem/implementation.js b/src/filesystem/implementation.js
index b5a8f8bf..28442650 100644
--- a/src/filesystem/implementation.js
+++ b/src/filesystem/implementation.js
@@ -278,6 +278,54 @@ function find_node(context, path, callback) {
}
}
+/**
+ * find_symlink_node
+ */
+// in: file or directory path
+// out: symlink node structure, or error
+function find_symlink_node(context, path, callback) {
+ path = normalize(path);
+ var name = basename(path);
+ var parentPath = dirname(path);
+
+ var directoryNode;
+ var directoryData;
+
+ if(ROOT_DIRECTORY_NAME === name) {
+ find_node(context, path, callback);
+ } else {
+ find_node(context, parentPath, read_directory_data);
+ }
+
+ function read_directory_data(error, result) {
+ if(error) {
+ callback(error);
+ } else {
+ directoryNode = result;
+ context.getObject(directoryNode.data, check_if_file_exists);
+ }
+ }
+
+ function create_node(error, data) {
+ if(error) {
+ return callback(error);
+ }
+ Node.create(data, callback);
+ }
+
+ function check_if_file_exists(error, result) {
+ if(error) {
+ callback(error);
+ } else {
+ directoryData = result;
+ if(!directoryData.hasOwnProperty(name)) {
+ callback(new Errors.ENOENT('a component of the path does not name an existing file', path));
+ } else {
+ context.getObject(directoryData[name].id, create_node);
+ }
+ }
+ }
+}
/**
* set extended attribute (refactor)
@@ -895,47 +943,7 @@ function fstat_file(context, ofd, callback) {
}
function lstat_file(context, path, callback) {
- path = normalize(path);
- var name = basename(path);
- var parentPath = dirname(path);
-
- var directoryNode;
- var directoryData;
-
- if(ROOT_DIRECTORY_NAME === name) {
- find_node(context, path, callback);
- } else {
- find_node(context, parentPath, read_directory_data);
- }
-
- function read_directory_data(error, result) {
- if(error) {
- callback(error);
- } else {
- directoryNode = result;
- context.getObject(directoryNode.data, check_if_file_exists);
- }
- }
-
- function create_node(error, data) {
- if(error) {
- return callback(error);
- }
- Node.create(data, callback);
- }
-
- function check_if_file_exists(error, result) {
- if(error) {
- callback(error);
- } else {
- directoryData = result;
- if(!directoryData.hasOwnProperty(name)) {
- callback(new Errors.ENOENT('a component of the path does not name an existing file', path));
- } else {
- context.getObject(directoryData[name].id, create_node);
- }
- }
- }
+ find_symlink_node(context, path, callback);
}
function link_node(context, oldpath, newpath, callback) {
@@ -2060,6 +2068,19 @@ function fchown_file(context, ofd, uid, gid, callback) {
ofd.getNode(context, update_owner);
}
+function lchown_file(context, path, uid, gid, callback) {
+ function update_owner(error, node) {
+ if (error) {
+ callback(error);
+ } else {
+ node.uid = uid;
+ node.gid = gid;
+ update_node_times(context, path, node, { mtime: Date.now() }, callback);
+ }
+ }
+ find_symlink_node(context, path, update_owner);
+}
+
function getxattr(context, path, name, callback) {
getxattr_file(context, path, name, callback);
}
@@ -2244,6 +2265,17 @@ function fchown(context, fd, uid, gid, callback) {
}
}
+function lchown(context, path, uid, gid, callback) {
+ if(!isUint32(uid)) {
+ return callback(new Errors.EINVAL('uid must be a valid integer', uid));
+ }
+ if(!isUint32(gid)) {
+ return callback(new Errors.EINVAL('gid must be a valid integer', gid));
+ }
+
+ lchown_file(context, path, uid, gid, callback);
+}
+
function rename(context, oldpath, newpath, callback) {
oldpath = normalize(oldpath);
newpath = normalize(newpath);
@@ -2425,7 +2457,7 @@ module.exports = {
ftruncate,
futimes,
getxattr,
- // lchown - https://github.com/filerjs/filer/issues/620
+ lchown,
// lchmod - https://github.com/filerjs/filer/issues/619
link,
lseek,
diff --git a/src/filesystem/interface.js b/src/filesystem/interface.js
index 824128b3..67f5016d 100644
--- a/src/filesystem/interface.js
+++ b/src/filesystem/interface.js
@@ -340,8 +340,7 @@ function FileSystem(options, callback) {
{ name: 'ftruncate' },
{ name: 'futimes' },
{ name: 'getxattr', promises: true, absPathArgs: [0] },
- // lchown - https://github.com/filerjs/filer/issues/620
- // lchmod - https://github.com/filerjs/filer/issues/619
+ { name: 'lchown', promises:true },
{ name: 'link', promises: true, absPathArgs: [0, 1] },
{ name: 'lseek' },
{ name: 'lstat', promises: true },
diff --git a/tests/spec/fs.chown.spec.js b/tests/spec/fs.chown.spec.js
index e1413211..d9bfc73c 100644
--- a/tests/spec/fs.chown.spec.js
+++ b/tests/spec/fs.chown.spec.js
@@ -1,7 +1,7 @@
var util = require('../lib/test-utils.js');
var expect = require('chai').expect;
-describe('fs.chown, fs.fchown', function() {
+describe('fs.chown, fs.fchown, fs.lchown', function() {
beforeEach(util.setup);
afterEach(util.cleanup);
@@ -27,6 +27,20 @@ describe('fs.chown, fs.fchown', function() {
});
});
+ it('lchown should expect an interger value for uid', function(done) {
+ var fs = util.fs();
+
+ fs.open('/file', 'w', function(err, fd) {
+ if(err) throw err;
+
+ fs.lchown(fd, '1001', 1001, function(err) {
+ expect(err).to.exist;
+ expect(err.code).to.equal('EINVAL');
+ fs.close(fd, done);
+ });
+ });
+ });
+
it('fchown should expect an interger value for uid', function(done) {
var fs = util.fs();
@@ -55,6 +69,20 @@ describe('fs.chown, fs.fchown', function() {
});
});
+ it('lchown should expect an interger value for gid', function(done) {
+ var fs = util.fs();
+
+ fs.open('/file', 'w', function(err, fd) {
+ if(err) throw err;
+
+ fs.lchown(fd, 1001, '1001', function(err) {
+ expect(err).to.exist;
+ expect(err.code).to.equal('EINVAL');
+ fs.close(fd, done);
+ });
+ });
+ });
+
it('fchown should expect an interger value for gid', function(done) {
var fs = util.fs();
@@ -117,6 +145,34 @@ describe('fs.chown, fs.fchown', function() {
});
});
});
+
+ it('should allow updating gid and uid for a symlink file', function(done) {
+ var fs = util.fs();
+
+ fs.open('/file', 'w', function(err, fd) {
+ if(err) throw err;
+
+ fs.symlink('/file', '/link', function(err) {
+ if(err) throw err;
+
+ fs.lchown('/link', 600, 600, function(err) {
+ if(err) throw err;
+
+ fs.lstat('/link', function(err, stats) {
+ if(err) throw err;
+
+ expect(stats.uid).to.equal(600);
+ expect(stats.gid).to.equal(600);
+
+ fs.close(fd, function(err) {
+ if(err) throw err;
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
});