diff --git a/src/constants.js b/src/constants.js index 78ae91e..cfb24ed 100644 --- a/src/constants.js +++ b/src/constants.js @@ -76,5 +76,43 @@ module.exports = { ENVIRONMENT: { TMP: '/tmp', PATH: '' + }, + + /** + * Plan 9 related + * - https://github.com/chaos/diod/blob/master/libnpfs/9p.h + * - https://en.wikibooks.org/wiki/C_Programming/POSIX_Reference/sys/stat.h + */ + P9: { + /** + * QID types + * + * @P9_QTDIR: directory + * @P9_QTAPPEND: append-only + * @P9_QTEXCL: excluse use (only one open handle allowed) + * @P9_QTMOUNT: mount points + * @P9_QTAUTH: authentication file + * @P9_QTTMP: non-backed-up files + * @P9_QTSYMLINK: symbolic links (9P2000.u) + * @P9_QTLINK: hard-link (9P2000.u) + * @P9_QTFILE: normal files + */ + QTDIR: 0x80, + QTAPPEND: 0x40, + QTEXCL: 0x20, + QTMOUNT: 0x10, + QTAUTH: 0x08, + QTTMP: 0x04, + QTSYMLINK: 0x02, + QTLINK: 0x01, + QTFILE: 0x00, + + /** + * POSIX System Values + */ + S_IFMT: 0xF000, // mask for file type + S_IFLNK: 0xA000, // symbolic link + S_IFDIR: 0x4000, // directory + S_IFREG: 0x8000 // regular file } }; diff --git a/src/encoding.js b/src/encoding.js index b3f1031..bd6189b 100644 --- a/src/encoding.js +++ b/src/encoding.js @@ -7,7 +7,23 @@ function encode(string) { return new Buffer(string, 'utf8'); } +// https://github.com/darkskyapp/string-hash +function hash32(string) { + var hash = 5381; + var i = string.length; + + while(i) { + hash = (hash * 33) ^ string.charCodeAt(--i); + } + + /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed + * integers. Since we want the results to be always positive, convert the + * signed int to an unsigned by doing an unsigned bitshift. */ + return hash >>> 0; +} + module.exports = { encode: encode, - decode: decode + decode: decode, + hash32: hash32 }; diff --git a/src/filesystem/implementation.js b/src/filesystem/implementation.js index 0dde525..8d5ebda 100644 --- a/src/filesystem/implementation.js +++ b/src/filesystem/implementation.js @@ -12,6 +12,9 @@ var MODE_FILE = Constants.MODE_FILE; var MODE_DIRECTORY = Constants.MODE_DIRECTORY; var MODE_SYMBOLIC_LINK = Constants.MODE_SYMBOLIC_LINK; var MODE_META = Constants.MODE_META; +var P9_QTDIR = Constants.P9.QTDIR; +var P9_QTFILE = Constants.P9.QTFILE; +var P9_QTSYMLINK = Constants.P9.QTSYMLINK; var ROOT_DIRECTORY_NAME = Constants.ROOT_DIRECTORY_NAME; var SUPER_NODE_ID = Constants.SUPER_NODE_ID; @@ -44,17 +47,24 @@ var Buffer = require('../buffer.js'); * and filesystem flags are examined in order to override update logic. */ function update_node_times(context, path, node, times, callback) { + var update = false; + + if(times.ctime || times.mtime) { + // Update the qid's version field, since node has changed. + // TODO: this might more than I need to do... + node.p9.qid.version = times.ctime || times.mtime; + update = true; + } + // Honour mount flags for how we update times var flags = context.flags; if(_(flags).contains(FS_NOCTIME)) { delete times.ctime; } - if(_(flags).contains(FS_NOMTIME)) { + if(_(flags).contains(FS_NOMTIME)) { delete times.mtime; } - // Only do the update if required (i.e., times are still present) - var update = false; if(times.ctime) { node.ctime = times.ctime; // We don't do atime tracking for perf reasons, but do mirror ctime @@ -69,6 +79,8 @@ function update_node_times(context, path, node, times, callback) { } if(times.mtime) { node.mtime = times.mtime; + // Also update the qid's version filed, since file has changed. + node.p9.qid.version = times.mtime; update = true; } @@ -133,7 +145,11 @@ function make_node(context, path, mode, callback) { callback(error); } else { parentNodeData = result; - Node.create({guid: context.guid, mode: mode}, function(error, result) { + Node.create({ + path: path, + guid: context.guid, + mode: mode + }, function(error, result) { if(error) { callback(error); return; @@ -311,7 +327,7 @@ function ensure_root_directory(context, callback) { } else if(error && !(error instanceof Errors.ENOENT)) { callback(error); } else { - SuperNode.create({guid: context.guid}, function(error, result) { + SuperNode.create({ guid: context.guid }, function(error, result) { if(error) { callback(error); return; @@ -326,7 +342,12 @@ function ensure_root_directory(context, callback) { if(error) { callback(error); } else { - Node.create({guid: context.guid, id: superNode.rnode, mode: MODE_DIRECTORY}, function(error, result) { + Node.create({ + guid: context.guid, + id: superNode.rnode, + mode: MODE_DIRECTORY, + path: ROOT_DIRECTORY_NAME + }, function(error, result) { if(error) { callback(error); return; @@ -387,7 +408,11 @@ function make_directory(context, path, callback) { callback(error); } else { parentDirectoryData = result; - Node.create({guid: context.guid, mode: MODE_DIRECTORY}, function(error, result) { + Node.create({ + guid: context.guid, + mode: MODE_DIRECTORY, + path: path + }, function(error, result) { if(error) { callback(error); return; @@ -624,7 +649,11 @@ function open_file(context, path, flags, callback) { } function write_file_node() { - Node.create({guid: context.guid, mode: MODE_FILE}, function(error, result) { + Node.create({ + guid: context.guid, + mode: MODE_FILE, + path: path + }, function(error, result) { if(error) { callback(error); return; @@ -1111,7 +1140,11 @@ function make_symbolic_link(context, srcpath, dstpath, callback) { } function write_file_node() { - Node.create({guid: context.guid, mode: MODE_SYMBOLIC_LINK}, function(error, result) { + Node.create({ + guid: context.guid, + mode: MODE_SYMBOLIC_LINK, + path: dstpath + }, function(error, result) { if(error) { callback(error); return; diff --git a/src/node.js b/src/node.js index 4293500..3a119a7 100644 --- a/src/node.js +++ b/src/node.js @@ -1,4 +1,46 @@ +var path = require('./path.js'); +var hash32 = require('./encoding.js').hash32; + var MODE_FILE = require('./constants.js').MODE_FILE; +var MODE_DIRECTORY = require('./constants.js').MODE_DIRECTORY; +var MODE_SYMBOLIC_LINK = require('./constants.js').MODE_SYMBOLIC_LINK; +var MODE_META = require('./constants.js').MODE_META; + +var P9_QTFILE = require('./constants.js').P9.QTFILE; +var P9_QTDIR = require('./constants.js').P9.QTDIR; +var P9_QTSYMLINK = require('./constants.js').P9.QTSYMLINK; + +var S_IFLNK = require('./constants.js').P9.S_IFLNK; +var S_IFDIR = require('./constants.js').P9.S_IFDIR; +var S_IFREG = require('./constants.js').P9.S_IFREG; + +var ROOT_DIRECTORY_NAME = require('./constants.js').ROOT_DIRECTORY_NAME; + +function getQType(mode) { + switch(mode) { + case MODE_FILE: + return P9_QTFILE; + case MODE_DIRECTORY: + return P9_QTDIR; + case MODE_SYMBOLIC_LINK: + return P9_QTSYMLINK; + default: + return null; + } +} + +function getPOSIXMode(mode) { + switch(mode) { + case MODE_FILE: + return S_IFREG; + case MODE_DIRECTORY: + return S_IFDIR; + case MODE_SYMBOLIC_LINK: + return S_IFLNK; + default: + return null; + } +} function Node(options) { var now = Date.now(); @@ -16,6 +58,44 @@ function Node(options) { this.blksize = undefined; // block size this.nblocks = 1; // blocks count this.data = options.data; // id for data object + + /** + * Plan 9 related metadata: + * https://web.archive.org/web/20170601072902/http://plan9.bell-labs.com/magic/man2html/5/0intro + * + * "The qid represents the server's unique identification for the file being + * accessed: two files on the same server hierarchy are the same if and only + * if their qids are the same. (The client may have multiple fids pointing to + * a single file on a server and hence having a single qid.) The thirteen–byte + * qid fields hold a one–byte type, specifying whether the file is a directory, + * append–only file, etc., and two unsigned integers: first the four–byte qid + * version, then the eight–byte qid path. The path is an integer unique among + * all files in the hierarchy. If a file is deleted and recreated with the same + * name in the same directory, the old and new path components of the qids + * should be different. The version is a version number for a file; typically, + * it is incremented every time the file is modified." + */ + this.p9 = { + qid: { + type: getQType(this.mode) || P9_QTFILE, + // use mtime for version info, since we already keep that updated + version: now, + // files have a unique `path` number, which takes into account files with same + // name but created at different times. + path: hash32(options.path + this.ctime) + }, + // permissions and flags + // TODO: I don't think I'm doing this correctly yet... + mode: getPOSIXMode(this.mode) || S_IFREG, + // Name of file/dir. Must be / if the file is the root directory of the server + // TODO: do I need this or can I derive it from abs path? + name: options.path === ROOT_DIRECTORY_NAME ? ROOT_DIRECTORY_NAME : path.basename(options.path), + uid: 0x0, // owner name + gid: 0x0, // group name + muid: 0x0// name of the user who last modified the file + }; + + console.log('Node', this); } // Make sure the options object has an id on property,