diff --git a/lib/filesystem.js b/lib/filesystem.js index ad65ec93..8785dc38 100644 --- a/lib/filesystem.js +++ b/lib/filesystem.js @@ -29,6 +29,7 @@ var STATUS_OK = 0x0; var STATUS_ON_SERVER = 0x2; var STATUS_LOADING = 0x3; var STATUS_UNLINKED = 0x4; +var STATUS_FORWARDING = 0x5; /** @const */ var JSONFS_VERSION = 2; @@ -62,6 +63,9 @@ function FS(baseurl) { this.total_size = 256 * 1024 * 1024 * 1024; this.used_size = 0; + /** @type {!Array} */ + this.mounts = []; + //RegisterMessage("LoadFilesystem", this.LoadFilesystem.bind(this) ); //RegisterMessage("MergeFile", this.MergeFile.bind(this) ); //RegisterMessage("tar", @@ -92,6 +96,8 @@ FS.prototype.get_state = function() } state[3] = this.total_size; state[4] = this.used_size; + state[5] = this.baseurl; + state[6] = this.mounts.map(mnt => mnt.fs); return state; }; @@ -113,6 +119,14 @@ FS.prototype.set_state = function(state) } this.total_size = state[3]; this.used_size = state[4]; + this.baseurl = state[5]; + this.mounts = []; + for(const mount_state of state[6]) + { + const mount = new FSMountInfo(null); + mount.set_state(mount_state); + this.mounts.push(mount); + } }; @@ -341,6 +355,10 @@ function Inode(qidnumber) this.nlinks = 1; this.dirty = false; // has this file changed? + // For forwarders: + this.mount_id = -1; // which fs in this.mounts does this inode forward to? + this.foreign_id = -1; // which foreign inode id does it represent? + //this.qid_type = 0; //this.qid_version = 0; //this.qid_path = qidnumber; @@ -372,6 +390,8 @@ Inode.prototype.get_state = function() state[20] = this.caps; state[21] = this.nlinks; state[22] = this.dirty; + state[23] = this.mount_id; + state[24] = this.foreign_id; return state; }; @@ -400,6 +420,8 @@ Inode.prototype.set_state = function(state) this.caps = state[20]; this.nlinks = state[21]; this.dirty = state[22]; + this.mount_id = state[23]; + this.foreign_id = state[24]; }; FS.prototype.CreateInode = function() { @@ -611,11 +633,21 @@ FS.prototype.Read = function(inodeid, offset, count) }; FS.prototype.Search = function(parentid, name) { - var id = this.inodes[parentid].firstid; + const parent_inode = this.inodes[parentid]; + + if(this.is_forwarder(parent_inode)) + { + const foreign_parentid = parent_inode.foreign_id; + const foreign_id = this.follow_fs(parent_inode).Search(foreign_parentid, name); + if(foreign_id === -1) return -1; + return this.get_forwarder(parent_inode.mount_id, foreign_id); + } + + var id = parent_inode.firstid; while(id != -1) { - if (this.inodes[id].parentid != parentid) { // consistency check - message.Debug("Error in Filesystem: Found inode with wrong parent id"); - } + dbg_assert(this.GetParent(id) === parentid, + "Error in Filesystem: Found inode (" + this.inodes[id].name + ") with wrong parent id. " + + "Expected: " + parentid + ", Actual: " + this.GetParent(id)); if (this.inodes[id].name == name) return id; id = this.inodes[id].nextid; } @@ -763,15 +795,20 @@ FS.prototype.SearchPath = function(path) { var parentid = 0; var id = -1; + let forward_path = null; for(var i=0; i} */ + this.backtrack = new Map(); +} + +FSMountInfo.prototype.get_state = function() +{ + const state = []; + + state[0] = this.fs; + state[1] = []; + for(const entry of this.backtrack.entries()) + { + state[1].push(entry); + } + + return state; +}; + +FSMountInfo.prototype.set_state = function(state) +{ + this.fs = new FS(undefined); + fs.set_state(state[0]); + this.backtrack = new Map(state[1]); +}; + +/** + * @private + * @param {number} idx Local idx of inode. + * @param {number} mount_id Mount number of the destination fs. + * @param {number} foreign_id Foreign idx of destination inode. + */ +FS.prototype.set_forwarder = function(idx, mount_id, foreign_id) +{ + const inode = this.inodes[idx]; + + if(this.is_forwarder(inode)) + { + this.mounts[inode.mount_id].backtrack.delete(inode.foreign_id); + } + + inode.status = STATUS_FORWARDING; + inode.mount_id = mount_id; + inode.foreign_id = foreign_id; + + this.mounts[mount_id].backtrack.set(foreign_id, idx); +}; + +/** + * @private + * @param {number} mount_id Mount number of the destination fs. + * @param {number} foreign_id Foreign idx of destination inode. + * @return {number} Local idx of newly created forwarder. + */ +FS.prototype.create_forwarder = function(mount_id, foreign_id) +{ + dbg_assert(foreign_id !== 0, "Filesystem: root forwarder should not be created."); + + const inode = this.CreateInode(); + + const idx = this.inodes.length; + this.inodes.push(inode); + inode.fid = idx; + + this.set_forwarder(idx, mount_id, foreign_id); + return idx; +}; + +/** + * @private + * @param {Inode} inode + * @return {boolean} + */ +FS.prototype.is_forwarder = function(inode) +{ + return inode.status === STATUS_FORWARDING; +}; + +/** + * Ensures forwarder exists, and returns such forwarder, for the described foreign inode. + * @private + * @param {number} mount_id + * @param {number} foreign_id + * @return {number} Local idx of a forwarder to described inode. + */ +FS.prototype.get_forwarder = function(mount_id, foreign_id) +{ + const mount = this.mounts[mount_id]; + + dbg_assert(foreign_id >= 0, "Filesystem get_forwarder: invalid foreign_id: " + foreign_id); + dbg_assert(mount, "Filesystem get_forwarder: invalid mount number: " + mount_id); + + const result = mount.backtrack.get(foreign_id); + + if(result === undefined) + { + // Create if not already exists. + return this.create_forwarder(mount_id, foreign_id); + } + + return result; +}; + +/** + * @private + * @param {Inode} inode + */ +FS.prototype.delete_forwarder = function(inode) +{ + dbg_assert(this.is_forwarder(inode), "Filesystem delete_forwarder: expected forwarder"); + + inode.status = STATUS_INVALID; + this.mounts[inode.mount_id].backtrack.delete(inode.foreign_id); +}; + +/** + * @private + * @param {Inode} inode + * @return {FS} + */ +FS.prototype.follow_fs = function(inode) +{ + const mount = this.mounts[inode.mount_id]; + + dbg_assert(this.is_forwarder(inode), + "Filesystem follow_fs: inode should be a forwarding inode"); + dbg_assert(mount, "Filesystem follow_fs: inode should point to valid mounted FS"); + + return mount.fs; +}; + +/** + * Mount another filesystem onto an existing directory. + * @param {string} path Path to existing directrory relative to this filesystem. + * @param {FS} fs + */ +FS.prototype.Mount = function(path, fs) +{ + const path_infos = this.SearchPath(path); + + if(path_infos.id === -1) + { + dbg_log("Mount failed: path not found: " + path, LOG_9P); + return -1; + } + if(path_infos.forward_path) + { + const parent = this.inodes[path_infos.parentid]; + this.follow_fs(parent).Mount(path_infos.forward_path, fs); + return; + } + + const mount_id = this.mounts.length; + this.mounts.push(new FSMountInfo(fs)); + + // Existing inode is already linked. Just set forwarding information. + this.set_forwarder(path_infos.id, mount_id, 0); + + return path_infos.id; +};