Add chown, fchown, chmod, fchmod and tests.

This commit is contained in:
David Humphrey 2018-05-28 15:03:34 -04:00
parent e77a8bacd3
commit 9508833b37
5 changed files with 348 additions and 5 deletions

View File

@ -13,6 +13,10 @@ var NODE_TYPE_DIRECTORY = Constants.NODE_TYPE_DIRECTORY;
var NODE_TYPE_SYMBOLIC_LINK = Constants.NODE_TYPE_SYMBOLIC_LINK;
var NODE_TYPE_META = Constants.NODE_TYPE_META;
var DEFAULT_FILE_PERMISSIONS = Constants.DEFAULT_FILE_PERMISSIONS;
var DEFAULT_DIR_PERMISSIONS = Constants.DEFAULT_DIR_PERMISSIONS;
var FULL_READ_WRITE_EXEC_PERMISSIONS = Constants.FULL_READ_WRITE_EXEC_PERMISSIONS;
var ROOT_DIRECTORY_NAME = Constants.ROOT_DIRECTORY_NAME;
var SUPER_NODE_ID = Constants.SUPER_NODE_ID;
var SYMLOOP_MAX = Constants.SYMLOOP_MAX;
@ -1587,8 +1591,22 @@ function pathCheck(path, callback) {
function open(fs, context, path, flags, mode, callback) {
// NOTE: we support the same signature as node with a `mode` arg,
// but ignore it.
/**
* NOTE: we support the same signature as node with a `mode` arg,
* but ignore it. We need to add it. Here is what node.js does:
* function open(path, flags, mode, callback) {
* path = getPathFromURL(path);
* validatePath(path);
* const flagsNumber = stringToFlags(flags);
* if (arguments.length < 4) {
* callback = makeCallback(mode);
* mode = 0o666;
* } else {
* mode = validateAndMaskMode(mode, 'mode', 0o666);
* callback = makeCallback(callback);
* }
*/
callback = arguments[arguments.length - 1];
if(!pathCheck(path, callback)) return;
@ -1631,8 +1649,14 @@ function mknod(fs, context, path, type, callback) {
}
function mkdir(fs, context, path, mode, callback) {
// NOTE: we support passing a mode arg, but we ignore it internally for now.
callback = arguments[arguments.length - 1];
if (arguments.length < 5) {
callback = mode;
mode = FULL_READ_WRITE_EXEC_PERMISSIONS;
} else {
mode = validateAndMaskMode(mode, FULL_READ_WRITE_EXEC_PERMISSIONS, callback);
if(!mode) return;
}
if(!pathCheck(path, callback)) return;
make_directory(context, path, callback);
}
@ -1863,6 +1887,121 @@ function exists(fs, context, path, callback) {
stat(fs, context, path, cb);
}
// Based on https://github.com/nodejs/node/blob/c700cc42da9cf73af9fec2098520a6c0a631d901/lib/internal/validators.js#L21
var octalReg = /^[0-7]+$/;
var modeDesc = 'must be a 32-bit unsigned integer or an octal string';
function isUint32(value) {
return value === (value >>> 0);
}
// Validator for mode_t (the S_* constants). Valid numbers or octal strings
// will be masked with 0o777 to be consistent with the behavior in POSIX APIs.
function validateAndMaskMode(value, def, callback) {
if(typeof def === 'function') {
callback = def;
def = undefined;
}
if (isUint32(value)) {
return value & FULL_READ_WRITE_EXEC_PERMISSIONS;
}
if (typeof value === 'number') {
if (!Number.isInteger(value)) {
callback(new Errors.EINVAL('mode not a valid an integer value', value));
return false;
} else {
// 2 ** 32 === 4294967296
callback(new Errors.EINVAL('mode not a valid an integer value', value));
return false;
}
}
if (typeof value === 'string') {
if (!octalReg.test(value)) {
callback(new Errors.EINVAL('mode not a valid octal string', value));
return false;
}
var parsed = parseInt(value, 8);
return parsed & FULL_READ_WRITE_EXEC_PERMISSIONS;
}
// TODO(BridgeAR): Only return `def` in case `value == null`
if (def !== undefined) {
return def;
}
callback(new Errors.EINVAL('mode not valid', value));
return false;
}
function chmod_file(context, path, mode, callback) {
path = normalize(path);
function update_mode(error, node) {
if (error) {
callback(error);
} else {
node.mode = mode;
update_node_times(context, path, node, { mtime: Date.now() }, callback);
}
}
if (typeof mode != 'number') {
callback(new Errors.EINVAL('mode must be number', path));
}
else {
find_node(context, path, update_mode);
}
}
function fchmod_file(context, ofd, mode, callback) {
function update_mode(error, node) {
if (error) {
callback(error);
} else {
node.mode = mode;
update_node_times(context, ofd.path, node, { mtime: Date.now() }, callback);
}
}
if (typeof mode != 'number') {
callback(new Errors.EINVAL('mode must be a number'));
}
else {
ofd.getNode(context, update_mode);
}
}
function chown_file(context, path, uid, gid, callback) {
path = normalize(path);
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_node(context, path, update_owner);
}
function fchown_file(context, ofd, uid, gid, callback) {
function update_owner(error, node) {
if (error) {
callback(error);
} else {
node.uid = uid;
node.gid = gid;
update_node_times(context, ofd.path, node, { mtime: Date.now() }, callback);
}
}
ofd.getNode(context, update_owner);
}
function getxattr(fs, context, path, name, callback) {
if (!pathCheck(path, callback)) return;
getxattr_file(context, path, name, callback);
@ -1994,6 +2133,58 @@ function futimes(fs, context, fd, atime, mtime, callback) {
}
}
function chmod(fs, context, path, mode, callback) {
if(!pathCheck(path, callback)) return;
mode = validateAndMaskMode(mode, 'mode');
if(!mode) return;
chmod_file(context, path, mode, callback);
}
function fchmod(fs, context, fd, mode, callback) {
mode = validateAndMaskMode(mode, 'mode');
if(!mode) return;
var ofd = fs.openFiles[fd];
if(!ofd) {
callback(new Errors.EBADF());
} else if(!_(ofd.flags).contains(O_WRITE)) {
callback(new Errors.EBADF('descriptor does not permit writing'));
} else {
fchmod_file(context, ofd, mode, callback);
}
}
function chown(fs, context, path, uid, gid, callback) {
if(!pathCheck(path, callback)) return;
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));
}
chown_file(context, path, uid, gid, callback);
}
function fchown(fs, context, fd, 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));
}
var ofd = fs.openFiles[fd];
if(!ofd) {
callback(new Errors.EBADF());
} else if(!_(ofd.flags).contains(O_WRITE)) {
callback(new Errors.EBADF('descriptor does not permit writing'));
} else {
fchown_file(context, ofd, uid, gid, callback);
}
}
function rename(fs, context, oldpath, newpath, callback) {
if(!pathCheck(oldpath, callback)) return;
if(!pathCheck(newpath, callback)) return;
@ -2164,6 +2355,10 @@ function ftruncate(fs, context, fd, length, callback) {
module.exports = {
ensureRootDirectory: ensure_root_directory,
open: open,
chmod: chmod,
fchmod: fchmod,
chown: chown,
fchown: fchown,
close: close,
mknod: mknod,
mkdir: mkdir,

View File

@ -94,6 +94,9 @@ function FileSystem(options, callback) {
fs.stdout = STDOUT;
fs.stderr = STDERR;
// Expose Node's fs.constants to users
fs.constants = Constants.fsConstants;
// Expose Shell constructor
this.Shell = Shell.bind(undefined, this);
@ -268,6 +271,10 @@ FileSystem.providers = providers;
*/
[
'open',
'chmod',
'fchmod',
'chown',
'fchown',
'close',
'mknod',
'mkdir',

View File

@ -39,6 +39,8 @@ require("./spec/time-flags.spec");
require("./spec/fs.watch.spec");
require("./spec/errors.spec");
require("./spec/fs.shell.spec");
require("./spec/fs.chmod.spec");
require("./spec/fs.chown.spec")
// Filer.FileSystem.providers.*
require("./spec/providers/providers.spec");

View File

@ -0,0 +1,74 @@
var Filer = require('../..');
var util = require('../lib/test-utils.js');
var expect = require('chai').expect;
describe('fs.chmod, fs.fchmod', function() {
beforeEach(util.setup);
afterEach(util.cleanup);
it('should be functions', function() {
var fs = util.fs();
expect(typeof fs.chmod).to.equal('function');
expect(typeof fs.fchmod).to.equal('function');
});
it('should automatically set mode=755 for a directory', function(done) {
var fs = util.fs();
fs.mkdir('/dir', function(err) {
if(err) throw err;
fs.stat('/dir', function(err, stats) {
if(err) throw err;
expect(stats.mode & 0o755).to.equal(0o755);
done();
});
});
});
it('should automatically set mode=644 for a file', function(done) {
var fs = util.fs();
fs.open('/file', 'w', function(err, fd) {
if(err) throw err;
fs.fstat(fd, function(err, stats) {
if(err) throw err;
expect(stats.mode & 0o644).to.equal(0o644);
fs.close(fd, done);
});
});
});
it('should allow for updating mode of a given file', function(done) {
var fs = util.fs();
fs.open('/file', 'w', function(err, fd) {
if(err) throw err;
fs.fchmod(fd, 0o777, function(err) {
if(err) throw err;
fs.fstat(fd, function(err, stats) {
if(err) throw err;
expect(stats.mode & 0o777).to.equal(0o777);
fs.close(fd, function(err) {
if(err) throw err;
fs.chmod('/file', 0o444, function(err) {
if(err) throw err;
fs.stat('/file', function(err, stats) {
if(err) throw err;
expect(stats.mode & 0o444).to.equal(0o444);
done();
});
});
});
});
});
});
});
});

View File

@ -0,0 +1,65 @@
var Filer = require('../..');
var util = require('../lib/test-utils.js');
var expect = require('chai').expect;
describe('fs.chown, fs.fchown', function() {
beforeEach(util.setup);
afterEach(util.cleanup);
it('should be functions', function() {
var fs = util.fs();
expect(typeof fs.chown).to.equal('function');
expect(typeof fs.fchown).to.equal('function');
});
it('should automatically set a file\s uid and gid to 0 (i.e., root)', function(done) {
var fs = util.fs();
fs.open('/file', 'w', function(err, fd) {
if(err) throw err;
fs.fstat(fd, function(err, stats) {
if(err) throw err;
expect(stats.uid).to.equal(0);
expect(stats.gid).to.equal(0);
fs.close(fd, done);
});
});
});
it('should allow updating gid and uid for a file', function(done) {
var fs = util.fs();
fs.open('/file', 'w', function(err, fd) {
if(err) throw err;
fs.fchown(fd, 1001, 1001, function(err) {
if(err) throw err;
fs.fstat(fd, function(err, stats) {
if(err) throw err;
expect(stats.uid).to.equal(1001);
expect(stats.gid).to.equal(1001);
fs.close(fd, function(err) {
if(err) throw err;
fs.chown('/file', 500, 500, function(err) {
if(err) throw err;
fs.stat('/file', function(err, stats) {
if(err) throw err;
expect(stats.uid).to.equal(500);
expect(stats.gid).to.equal(500);
done();
});
});
});
});
});
});
});
});