From d0178539f51a2ed34843e60311c89bb798c9b628 Mon Sep 17 00:00:00 2001 From: David Humphrey Date: Sat, 8 Dec 2018 21:59:04 -0500 Subject: [PATCH] Refactor Node to support layout changes with backwad compatibility --- src/filesystem/implementation.js | 15 ++- src/node.js | 160 ++++++++++++++++++++----------- src/open-file-description.js | 5 +- 3 files changed, 119 insertions(+), 61 deletions(-) diff --git a/src/filesystem/implementation.js b/src/filesystem/implementation.js index 586656c..8c40def 100644 --- a/src/filesystem/implementation.js +++ b/src/filesystem/implementation.js @@ -201,7 +201,7 @@ function find_node(context, path, callback) { } else if(!rootDirectoryNode) { callback(new Errors.ENOENT()); } else { - callback(null, rootDirectoryNode); + Node.create(rootDirectoryNode, callback); } } @@ -227,11 +227,18 @@ function find_node(context, path, callback) { callback(new Errors.ENOENT(null, path)); } else { var nodeId = parentDirectoryData[name].id; - context.getObject(nodeId, is_symbolic_link); + context.getObject(nodeId, create_node); } } } + function create_node(error, data) { + if(error) { + return callback(error); + } + Node.create(data, is_symbolic_link); + } + function is_symbolic_link(error, node) { if(error) { callback(error); @@ -664,7 +671,7 @@ function open_file(context, path, flags, mode, callback) { fileNode = result; fileNode.nlinks += 1; if(mode){ - Node.setMode(mode, fileNode); + fileNode.mode = mode; } context.putObject(fileNode.id, fileNode, write_file_data); }); @@ -2006,7 +2013,7 @@ function chmod_file(context, path, mode, callback) { if (error) { callback(error); } else { - Node.setMode(mode, node); + node.mode = mode; update_node_times(context, path, node, { mtime: Date.now() }, callback); } } diff --git a/src/node.js b/src/node.js index 5c59c56..a9b40d9 100644 --- a/src/node.js +++ b/src/node.js @@ -1,85 +1,135 @@ -var Constants = require('./constants.js'); -var NODE_TYPE_FILE = Constants.NODE_TYPE_FILE; -var NODE_TYPE_DIRECTORY = Constants.NODE_TYPE_DIRECTORY; -var NODE_TYPE_SYMBOLIC_LINK = Constants.NODE_TYPE_SYMBOLIC_LINK; +const { + NODE_TYPE_FILE, + NODE_TYPE_DIRECTORY, + NODE_TYPE_SYMBOLIC_LINK, + DEFAULT_FILE_PERMISSIONS, + DEFAULT_DIR_PERMISSIONS +} = require('./constants'); +const { + S_IFREG, + S_IFDIR, + S_IFLNK +} = require('./constants').fsConstants; -var S_IFREG = Constants.fsConstants.S_IFREG; -var S_IFDIR = Constants.fsConstants.S_IFDIR; -var S_IFLNK = Constants.fsConstants.S_IFLNK; +/** + * Make sure the options object has an id on property, + * either from caller or one we generate using supplied guid fn. + */ +function ensureID(options, prop, callback) { + if(options[prop]) { + return callback(); + } -var DEFAULT_FILE_PERMISSIONS = Constants.DEFAULT_FILE_PERMISSIONS; -var DEFAULT_DIR_PERMISSIONS = Constants.DEFAULT_DIR_PERMISSIONS; + options.guid(function(err, id) { + if(err) { + return callback(err); + } + options[prop] = id; + callback(); + }); +} -function getMode(type, mode) { - switch(type) { +/** + * Generate a POSIX mode (integer) for the node type and permissions. + * Use default permissions if we aren't passed any. + */ +function generateMode(nodeType, modePermissions) { + switch(nodeType) { case NODE_TYPE_DIRECTORY: - return (mode || DEFAULT_DIR_PERMISSIONS) | S_IFDIR; + return (modePermissions || DEFAULT_DIR_PERMISSIONS) | S_IFDIR; case NODE_TYPE_SYMBOLIC_LINK: - return (mode || DEFAULT_FILE_PERMISSIONS) | S_IFLNK; - /* jshint -W086 */ + return (modePermissions || DEFAULT_FILE_PERMISSIONS) | S_IFLNK; case NODE_TYPE_FILE: // falls through default: - return (mode || DEFAULT_FILE_PERMISSIONS) | S_IFREG; + return (modePermissions || DEFAULT_FILE_PERMISSIONS) | S_IFREG; } } -function Node(options) { - var now = Date.now(); +/** + * Common properties for the layout of a Node + */ +class Node { + constructor(options) { + var now = Date.now(); - this.id = options.id; - this.type = options.type || NODE_TYPE_FILE; // node type (file, directory, etc) - this.size = options.size || 0; // size (bytes for files, entries for directories) - this.atime = options.atime || now; // access time (will mirror ctime after creation) - this.ctime = options.ctime || now; // creation/change time - this.mtime = options.mtime || now; // modified time - this.flags = options.flags || []; // file flags - this.xattrs = options.xattrs || {}; // extended attributes - this.nlinks = options.nlinks || 0; // links count - this.data = options.data; // id for data object - this.version = options.version || 1; + this.id = options.id; + this.data = options.data; // id for data object + this.size = options.size || 0; // size (bytes for files, entries for directories) + this.atime = options.atime || now; // access time (will mirror ctime after creation) + this.ctime = options.ctime || now; // creation/change time + this.mtime = options.mtime || now; // modified time + this.flags = options.flags || []; // file flags + this.xattrs = options.xattrs || {}; // extended attributes + this.nlinks = options.nlinks || 0; // links count - // permissions and flags - this.mode = options.mode || (getMode(this.type)); - this.uid = options.uid || 0x0; // owner name - this.gid = options.gid || 0x0; // group name -} + // Historically, Filer's node layout has referred to the + // node type as `mode`, and done so using a String. In + // a POSIX filesystem, the mode is a number that combines + // both node type and permission bits. Internal we use `type`, + // but store it in the database as `mode` for backward + // compatibility. + if(typeof options.type === 'string') { + this.type = options.type; + } else if(typeof options.mode === 'string') { + this.type = options.mode; + } else { + this.type = NODE_TYPE_FILE; + } -// Make sure the options object has an id on property, -// either from caller or one we generate using supplied guid fn. -function ensureID(options, prop, callback) { - if(options[prop]) { - callback(null); - } else { - options.guid(function(err, id) { - options[prop] = id; - callback(err); - }); + // Extra mode permissions and ownership info + this.permissions = options.permissions || generateMode(this.type); + this.uid = options.uid || 0x0; // owner name + this.gid = options.gid || 0x0; // group name + } + + /** + * Serialize a Node to JSON. Everything is as expected except + * that we use `mode` for `type` to maintain backward compatibility. + */ + toJSON() { + return { + id: this.id, + data: this.data, + size: this.size, + atime: this.atime, + ctime: this.ctime, + mtime: this.ctime, + flags: this.flags, + xattrs: this.xattrs, + nlinks: this.nlinks, + // Use `mode` for `type` to keep backward compatibility + mode: this.type, + permissions: this.permissions, + uid: this.uid, + gid: this.gid + }; + } + + // Return complete POSIX `mode` for node type + permissions. See: + // http://man7.org/linux/man-pages/man2/chmod.2.html + get mode() { + return generateMode(this.type, this.permissions); + } + // When setting the `mode` we assume permissions bits only (not changing type) + set mode(value) { + this.permissions = value; } } -Node.create = function(options, callback) { +module.exports.create = function create(options, callback) { // We expect both options.id and options.data to be provided/generated. ensureID(options, 'id', function(err) { if(err) { - callback(err); - return; + return callback(err); } ensureID(options, 'data', function(err) { if(err) { - callback(err); - return; + return callback(err); } callback(null, new Node(options)); }); }); }; - -// Update the node's mode (permissions), taking file type bits into account. -Node.setMode = function(mode, node) { - node.mode = getMode(node.type, mode); -}; - -module.exports = Node; diff --git a/src/open-file-description.js b/src/open-file-description.js index d703488..a40ebab 100644 --- a/src/open-file-description.js +++ b/src/open-file-description.js @@ -1,4 +1,5 @@ -var Errors = require('./errors.js'); +const Errors = require('./errors.js'); +const Node = require('./node'); function OpenFileDescription(path, id, flags, position) { this.path = path; @@ -22,7 +23,7 @@ OpenFileDescription.prototype.getNode = function(context, callback) { return callback(new Errors.EBADF('file descriptor refers to unknown node', path)); } - callback(null, node); + Node.create(node, callback); } context.getObject(id, check_if_node_exists);