filesystem.js 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984
  1. // -------------------------------------------------
  2. // ----------------- FILESYSTEM---------------------
  3. // -------------------------------------------------
  4. // Implementation of a unix filesystem in memory.
  5. "use strict";
  6. var S_IRWXUGO = 0x1FF;
  7. var S_IFMT = 0xF000;
  8. var S_IFSOCK = 0xC000;
  9. var S_IFLNK = 0xA000;
  10. var S_IFREG = 0x8000;
  11. var S_IFBLK = 0x6000;
  12. var S_IFDIR = 0x4000;
  13. var S_IFCHR = 0x2000;
  14. //var S_IFIFO 0010000
  15. //var S_ISUID 0004000
  16. //var S_ISGID 0002000
  17. //var S_ISVTX 0001000
  18. var O_RDONLY = 0x0000; // open for reading only
  19. var O_WRONLY = 0x0001; // open for writing only
  20. var O_RDWR = 0x0002; // open for reading and writing
  21. var O_ACCMODE = 0x0003; // mask for above modes
  22. var STATUS_INVALID = -0x1;
  23. var STATUS_OK = 0x0;
  24. var STATUS_ON_STORAGE = 0x2;
  25. var STATUS_UNLINKED = 0x4;
  26. var STATUS_FORWARDING = 0x5;
  27. /** @const */ var JSONFS_VERSION = 3;
  28. /** @const */ var JSONFS_IDX_NAME = 0;
  29. /** @const */ var JSONFS_IDX_SIZE = 1;
  30. /** @const */ var JSONFS_IDX_MTIME = 2;
  31. /** @const */ var JSONFS_IDX_MODE = 3;
  32. /** @const */ var JSONFS_IDX_UID = 4;
  33. /** @const */ var JSONFS_IDX_GID = 5;
  34. /** @const */ var JSONFS_IDX_TARGET = 6;
  35. /** @const */ var JSONFS_IDX_SHA256 = 6;
  36. /**
  37. * @constructor
  38. * @param {!FileStorageInterface} storage
  39. * @param {{ last_qidnumber: number }=} qidcounter Another fs's qidcounter to synchronise with.
  40. */
  41. function FS(storage, qidcounter) {
  42. /** @type {Array.<!Inode>} */
  43. this.inodes = [];
  44. this.events = [];
  45. this.storage = storage;
  46. this.qidcounter = qidcounter || { last_qidnumber: 0 };
  47. //this.tar = new TAR(this);
  48. this.inodedata = {};
  49. this.total_size = 256 * 1024 * 1024 * 1024;
  50. this.used_size = 0;
  51. /** @type {!Array<!FSMountInfo>} */
  52. this.mounts = [];
  53. //RegisterMessage("LoadFilesystem", this.LoadFilesystem.bind(this) );
  54. //RegisterMessage("MergeFile", this.MergeFile.bind(this) );
  55. //RegisterMessage("tar",
  56. // function(data) {
  57. // SendToMaster("tar", this.tar.Pack(data));
  58. // }.bind(this)
  59. //);
  60. //RegisterMessage("sync",
  61. // function(data) {
  62. // SendToMaster("sync", this.tar.Pack(data));
  63. // }.bind(this)
  64. //);
  65. // root entry
  66. this.CreateDirectory("", -1);
  67. }
  68. FS.prototype.get_state = function()
  69. {
  70. let state = [];
  71. state[0] = this.inodes;
  72. state[1] = this.qidcounter.last_qidnumber;
  73. state[2] = [];
  74. for(let entry of Object.entries(this.inodedata))
  75. {
  76. state[2].push(entry);
  77. }
  78. state[3] = this.total_size;
  79. state[4] = this.used_size;
  80. state = state.concat(this.mounts);
  81. return state;
  82. };
  83. FS.prototype.set_state = function(state)
  84. {
  85. this.inodes = state[0].map(state => { const inode = new Inode(0); inode.set_state(state); return inode; });
  86. this.qidcounter.last_qidnumber = state[1];
  87. this.inodedata = {};
  88. for(let [key, value] of state[2])
  89. {
  90. if(value.buffer.byteLength !== value.byteLength)
  91. {
  92. // make a copy if we didn't get one
  93. value = value.slice();
  94. }
  95. this.inodedata[key] = value;
  96. }
  97. this.total_size = state[3];
  98. this.used_size = state[4];
  99. this.mounts = state.slice(5);
  100. };
  101. // -----------------------------------------------------
  102. FS.prototype.AddEvent = function(id, OnEvent) {
  103. var inode = this.inodes[id];
  104. if (inode.status == STATUS_OK || inode.status == STATUS_ON_STORAGE) {
  105. OnEvent();
  106. }
  107. else if(this.is_forwarder(inode))
  108. {
  109. this.follow_fs(inode).AddEvent(inode.foreign_id, OnEvent);
  110. }
  111. else
  112. {
  113. this.events.push({id: id, OnEvent: OnEvent});
  114. }
  115. };
  116. FS.prototype.HandleEvent = function(id) {
  117. const inode = this.inodes[id];
  118. if(this.is_forwarder(inode))
  119. {
  120. this.follow_fs(inode).HandleEvent(inode.foreign_id);
  121. }
  122. //message.Debug("number of events: " + this.events.length);
  123. var newevents = [];
  124. for(var i=0; i<this.events.length; i++) {
  125. if (this.events[i].id == id) {
  126. this.events[i].OnEvent();
  127. } else {
  128. newevents.push(this.events[i]);
  129. }
  130. }
  131. this.events = newevents;
  132. };
  133. FS.prototype.load_from_json = function(fs, done)
  134. {
  135. dbg_assert(fs, "Invalid fs passed to load_from_json");
  136. if(fs["version"] !== JSONFS_VERSION)
  137. {
  138. throw "The filesystem JSON format has changed. " +
  139. "Please update your fs2json (https://github.com/copy/fs2json) and recreate the filesystem JSON.";
  140. }
  141. var fsroot = fs["fsroot"];
  142. this.used_size = fs["size"];
  143. for(var i = 0; i < fsroot.length; i++) {
  144. this.LoadRecursive(fsroot[i], 0);
  145. }
  146. //if(DEBUG)
  147. //{
  148. // this.Check();
  149. //}
  150. done && done();
  151. };
  152. FS.prototype.LoadRecursive = function(data, parentid)
  153. {
  154. var inode = this.CreateInode();
  155. const name = data[JSONFS_IDX_NAME];
  156. inode.size = data[JSONFS_IDX_SIZE];
  157. inode.mtime = data[JSONFS_IDX_MTIME];
  158. inode.ctime = inode.mtime;
  159. inode.atime = inode.mtime;
  160. inode.mode = data[JSONFS_IDX_MODE];
  161. inode.uid = data[JSONFS_IDX_UID];
  162. inode.gid = data[JSONFS_IDX_GID];
  163. var ifmt = inode.mode & S_IFMT;
  164. if(ifmt === S_IFDIR)
  165. {
  166. inode.updatedir = true;
  167. this.PushInode(inode, parentid, name);
  168. this.LoadDir(this.inodes.length - 1, data[JSONFS_IDX_TARGET]);
  169. }
  170. else if(ifmt === S_IFREG)
  171. {
  172. inode.status = STATUS_ON_STORAGE;
  173. inode.sha256sum = data[JSONFS_IDX_SHA256];
  174. dbg_assert(inode.sha256sum);
  175. this.PushInode(inode, parentid, name);
  176. }
  177. else if(ifmt === S_IFLNK)
  178. {
  179. inode.symlink = data[JSONFS_IDX_TARGET];
  180. this.PushInode(inode, parentid, name);
  181. }
  182. else if(ifmt === S_IFSOCK)
  183. {
  184. // socket: ignore
  185. }
  186. else
  187. {
  188. dbg_log("Unexpected ifmt: " + h(ifmt) + " (" + name + ")");
  189. }
  190. };
  191. FS.prototype.LoadDir = function(parentid, children)
  192. {
  193. for(var i = 0; i < children.length; i++) {
  194. this.LoadRecursive(children[i], parentid);
  195. }
  196. };
  197. // -----------------------------------------------------
  198. /**
  199. * @private
  200. * @param {Inode} inode
  201. * @return {boolean}
  202. */
  203. FS.prototype.should_be_linked = function(inode)
  204. {
  205. // Note: Non-root forwarder inode could still have a non-forwarder parent, so don't use
  206. // parent inode to check.
  207. return !this.is_forwarder(inode) || inode.foreign_id === 0;
  208. };
  209. /**
  210. * @private
  211. * @param {number} parentid
  212. * @param {number} idx
  213. * @param {string} name
  214. */
  215. FS.prototype.link_under_dir = function(parentid, idx, name)
  216. {
  217. const inode = this.inodes[idx];
  218. const parent_inode = this.inodes[parentid];
  219. dbg_assert(!this.is_forwarder(parent_inode),
  220. "Filesystem: Shouldn't link under fowarder parents");
  221. dbg_assert(this.IsDirectory(parentid),
  222. "Filesystem: Can't link under non-directories");
  223. dbg_assert(this.should_be_linked(inode),
  224. "Filesystem: Can't link across filesystems apart from their root");
  225. dbg_assert(inode.nlinks >= 0,
  226. "Filesystem: Found negative nlinks value of " + inode.nlinks);
  227. dbg_assert(!parent_inode.direntries.has(name),
  228. "Filesystem: Name '" + name + "' is already taken");
  229. parent_inode.direntries.set(name, idx);
  230. parent_inode.updatedir = true;
  231. inode.nlinks++;
  232. if(this.IsDirectory(idx))
  233. {
  234. dbg_assert(!inode.direntries.has(".."),
  235. "Filesystem: Cannot link a directory twice");
  236. if(!inode.direntries.has(".")) inode.nlinks++;
  237. inode.direntries.set(".", idx);
  238. inode.direntries.set("..", parentid);
  239. parent_inode.nlinks++;
  240. inode.updatedir = true;
  241. }
  242. };
  243. /**
  244. * @private
  245. * @param {number} parentid
  246. * @param {string} name
  247. */
  248. FS.prototype.unlink_from_dir = function(parentid, name)
  249. {
  250. const idx = this.Search(parentid, name);
  251. const inode = this.inodes[idx];
  252. const parent_inode = this.inodes[parentid];
  253. dbg_assert(!this.is_forwarder(parent_inode), "Filesystem: Can't unlink from fowarders");
  254. dbg_assert(this.IsDirectory(parentid), "Filesystem: Can't unlink from non-directories");
  255. const exists = parent_inode.direntries.delete(name);
  256. if(!exists)
  257. {
  258. dbg_assert(false, "Filesystem: Can't unlink non-existent file: " + name);
  259. return;
  260. }
  261. inode.nlinks--;
  262. parent_inode.updatedir = true;
  263. if(this.IsDirectory(idx))
  264. {
  265. dbg_assert(inode.direntries.get("..") === parentid,
  266. "Filesystem: Found directory with bad parent id");
  267. inode.direntries.delete("..");
  268. parent_inode.nlinks--;
  269. inode.updatedir = true;
  270. }
  271. dbg_assert(inode.nlinks >= 0,
  272. "Filesystem: Found negative nlinks value of " + inode.nlinks);
  273. };
  274. FS.prototype.PushInode = function(inode, parentid, name) {
  275. if (parentid != -1) {
  276. this.inodes.push(inode);
  277. inode.fid = this.inodes.length - 1;
  278. this.link_under_dir(parentid, inode.fid, name);
  279. return;
  280. } else {
  281. if (this.inodes.length == 0) { // if root directory
  282. this.inodes.push(inode);
  283. inode.direntries.set(".", 0);
  284. inode.direntries.set("..", 0);
  285. inode.nlinks = 2;
  286. return;
  287. }
  288. }
  289. message.Debug("Error in Filesystem: Pushed inode with name = "+ name + " has no parent");
  290. message.Abort();
  291. };
  292. /** @constructor */
  293. function Inode(qidnumber)
  294. {
  295. this.updatedir = false; // did the directory listing changed?
  296. this.direntries = new Map(); // maps filename to inode id
  297. this.status = 0;
  298. this.size = 0x0;
  299. this.uid = 0x0;
  300. this.gid = 0x0;
  301. this.fid = 0;
  302. this.ctime = 0;
  303. this.atime = 0;
  304. this.mtime = 0;
  305. this.major = 0x0;
  306. this.minor = 0x0;
  307. this.symlink = "";
  308. this.mode = 0x01ED;
  309. this.qid = {
  310. type: 0,
  311. version: 0,
  312. path: qidnumber,
  313. };
  314. this.caps = undefined;
  315. this.nlinks = 0;
  316. this.dirty = false; // has this file changed?
  317. this.sha256sum = "";
  318. /** @type{!Array<!FSLockRegion>} */
  319. this.locks = []; // lock regions applied to the file, sorted by starting offset.
  320. // For forwarders:
  321. this.mount_id = -1; // which fs in this.mounts does this inode forward to?
  322. this.foreign_id = -1; // which foreign inode id does it represent?
  323. //this.qid_type = 0;
  324. //this.qid_version = 0;
  325. //this.qid_path = qidnumber;
  326. }
  327. Inode.prototype.get_state = function()
  328. {
  329. const state = [];
  330. state[0] = this.updatedir;
  331. state[1] = [...this.direntries];
  332. state[2] = this.locks;
  333. //state[3]
  334. state[4] = this.status;
  335. //state[5]
  336. state[6] = this.size;
  337. state[7] = this.uid;
  338. state[8] = this.gid;
  339. state[9] = this.fid;
  340. state[10] = this.ctime;
  341. state[11] = this.atime;
  342. state[12] = this.mtime;
  343. state[13] = this.major;
  344. state[14] = this.minor;
  345. state[15] = this.symlink;
  346. state[16] = this.mode;
  347. state[17] = this.qid.type;
  348. state[18] = this.qid.version;
  349. state[19] = this.qid.path;
  350. state[20] = this.caps;
  351. state[21] = this.nlinks;
  352. state[22] = this.dirty;
  353. state[23] = this.mount_id;
  354. state[24] = this.foreign_id;
  355. state[25] = this.sha256sum;
  356. return state;
  357. };
  358. Inode.prototype.set_state = function(state)
  359. {
  360. this.updatedir = state[0];
  361. this.direntries = new Map(state[1]);
  362. this.locks = [];
  363. for(const lock_state of state[2])
  364. {
  365. const lock = new FSLockRegion();
  366. lock.set_state(lock_state);
  367. this.locks.push(lock);
  368. }
  369. //state[3];
  370. this.status = state[4];
  371. //state[5];
  372. this.size = state[6];
  373. this.uid = state[7];
  374. this.gid = state[8];
  375. this.fid = state[9];
  376. this.ctime = state[10];
  377. this.atime = state[11];
  378. this.mtime = state[12];
  379. this.major = state[13];
  380. this.minor = state[14];
  381. this.symlink = state[15];
  382. this.mode = state[16];
  383. this.qid.type = state[17];
  384. this.qid.version = state[18];
  385. this.qid.path = state[19];
  386. this.caps = state[20];
  387. this.nlinks = state[21];
  388. this.dirty = state[22];
  389. this.mount_id = state[23];
  390. this.foreign_id = state[24];
  391. this.sha256sum = state[25];
  392. };
  393. /**
  394. * Clones given inode to new idx, effectively diverting the inode to new idx value.
  395. * Hence, original idx value is now free to use without losing the original information.
  396. * @private
  397. * @param {number} parentid Parent of target to divert.
  398. * @param {string} filename Name of target to divert.
  399. * @return {number} New idx of diversion.
  400. */
  401. FS.prototype.divert = function(parentid, filename)
  402. {
  403. const old_idx = this.Search(parentid, filename);
  404. const old_inode = this.inodes[old_idx];
  405. const new_inode = new Inode(-1);
  406. dbg_assert(old_inode, "Filesystem divert: name (" + filename + ") not found");
  407. dbg_assert(this.IsDirectory(old_idx) || old_inode.nlinks <= 1,
  408. "Filesystem: can't divert hardlinked file '" + filename + "' with nlinks=" +
  409. old_inode.nlinks);
  410. // Shallow copy is alright.
  411. Object.assign(new_inode, old_inode);
  412. const idx = this.inodes.length;
  413. this.inodes.push(new_inode);
  414. new_inode.fid = idx;
  415. // Relink references
  416. if(this.is_forwarder(old_inode))
  417. {
  418. this.mounts[old_inode.mount_id].backtrack.set(old_inode.foreign_id, idx);
  419. }
  420. if(this.should_be_linked(old_inode))
  421. {
  422. this.unlink_from_dir(parentid, filename);
  423. this.link_under_dir(parentid, idx, filename);
  424. }
  425. // Update children
  426. if(this.IsDirectory(old_idx) && !this.is_forwarder(old_inode))
  427. {
  428. for(const [name, child_id] of new_inode.direntries)
  429. {
  430. if(name === "." || name === "..") continue;
  431. if(this.IsDirectory(child_id))
  432. {
  433. this.inodes[child_id].direntries.set("..", idx);
  434. }
  435. }
  436. }
  437. // Relocate local data if any.
  438. this.inodedata[idx] = this.inodedata[old_idx];
  439. delete this.inodedata[old_idx];
  440. // Retire old reference information.
  441. old_inode.direntries = new Map();
  442. old_inode.nlinks = 0;
  443. return idx;
  444. };
  445. /**
  446. * Copy all non-redundant info.
  447. * References left untouched: local idx value and links
  448. * @private
  449. * @param {!Inode} src_inode
  450. * @param {!Inode} dest_inode
  451. */
  452. FS.prototype.copy_inode = function(src_inode, dest_inode)
  453. {
  454. Object.assign(dest_inode, src_inode, {
  455. fid: dest_inode.fid,
  456. direntries: dest_inode.direntries,
  457. nlinks: dest_inode.nlinks,
  458. });
  459. };
  460. FS.prototype.CreateInode = function() {
  461. //console.log("CreateInode", Error().stack);
  462. const now = Math.round(Date.now() / 1000);
  463. const inode = new Inode(++this.qidcounter.last_qidnumber);
  464. inode.atime = inode.ctime = inode.mtime = now;
  465. return inode;
  466. };
  467. // Note: parentid = -1 for initial root directory.
  468. FS.prototype.CreateDirectory = function(name, parentid) {
  469. const parent_inode = this.inodes[parentid];
  470. if(parentid >= 0 && this.is_forwarder(parent_inode))
  471. {
  472. const foreign_parentid = parent_inode.foreign_id;
  473. const foreign_id = this.follow_fs(parent_inode).CreateDirectory(name, foreign_parentid);
  474. return this.create_forwarder(parent_inode.mount_id, foreign_id);
  475. }
  476. var x = this.CreateInode();
  477. x.mode = 0x01FF | S_IFDIR;
  478. x.updatedir = true;
  479. if (parentid >= 0) {
  480. x.uid = this.inodes[parentid].uid;
  481. x.gid = this.inodes[parentid].gid;
  482. x.mode = (this.inodes[parentid].mode & 0x1FF) | S_IFDIR;
  483. }
  484. x.qid.type = S_IFDIR >> 8;
  485. this.PushInode(x, parentid, name);
  486. this.NotifyListeners(this.inodes.length-1, 'newdir');
  487. return this.inodes.length-1;
  488. };
  489. FS.prototype.CreateFile = function(filename, parentid) {
  490. const parent_inode = this.inodes[parentid];
  491. if(this.is_forwarder(parent_inode))
  492. {
  493. const foreign_parentid = parent_inode.foreign_id;
  494. const foreign_id = this.follow_fs(parent_inode).CreateFile(filename, foreign_parentid);
  495. return this.create_forwarder(parent_inode.mount_id, foreign_id);
  496. }
  497. var x = this.CreateInode();
  498. x.dirty = true;
  499. x.uid = this.inodes[parentid].uid;
  500. x.gid = this.inodes[parentid].gid;
  501. x.qid.type = S_IFREG >> 8;
  502. x.mode = (this.inodes[parentid].mode & 0x1B6) | S_IFREG;
  503. this.PushInode(x, parentid, filename);
  504. this.NotifyListeners(this.inodes.length-1, 'newfile');
  505. return this.inodes.length-1;
  506. };
  507. FS.prototype.CreateNode = function(filename, parentid, major, minor) {
  508. const parent_inode = this.inodes[parentid];
  509. if(this.is_forwarder(parent_inode))
  510. {
  511. const foreign_parentid = parent_inode.foreign_id;
  512. const foreign_id =
  513. this.follow_fs(parent_inode).CreateNode(filename, foreign_parentid, major, minor);
  514. return this.create_forwarder(parent_inode.mount_id, foreign_id);
  515. }
  516. var x = this.CreateInode();
  517. x.major = major;
  518. x.minor = minor;
  519. x.uid = this.inodes[parentid].uid;
  520. x.gid = this.inodes[parentid].gid;
  521. x.qid.type = S_IFSOCK >> 8;
  522. x.mode = (this.inodes[parentid].mode & 0x1B6);
  523. this.PushInode(x, parentid, filename);
  524. return this.inodes.length-1;
  525. };
  526. FS.prototype.CreateSymlink = function(filename, parentid, symlink) {
  527. const parent_inode = this.inodes[parentid];
  528. if(this.is_forwarder(parent_inode))
  529. {
  530. const foreign_parentid = parent_inode.foreign_id;
  531. const foreign_id =
  532. this.follow_fs(parent_inode).CreateSymlink(filename, foreign_parentid, symlink);
  533. return this.create_forwarder(parent_inode.mount_id, foreign_id);
  534. }
  535. var x = this.CreateInode();
  536. x.uid = this.inodes[parentid].uid;
  537. x.gid = this.inodes[parentid].gid;
  538. x.qid.type = S_IFLNK >> 8;
  539. x.symlink = symlink;
  540. x.mode = S_IFLNK;
  541. this.PushInode(x, parentid, filename);
  542. return this.inodes.length-1;
  543. };
  544. FS.prototype.CreateTextFile = async function(filename, parentid, str) {
  545. const parent_inode = this.inodes[parentid];
  546. if(this.is_forwarder(parent_inode))
  547. {
  548. const foreign_parentid = parent_inode.foreign_id;
  549. const foreign_id = await
  550. this.follow_fs(parent_inode).CreateTextFile(filename, foreign_parentid, str);
  551. return this.create_forwarder(parent_inode.mount_id, foreign_id);
  552. }
  553. var id = this.CreateFile(filename, parentid);
  554. var x = this.inodes[id];
  555. var data = new Uint8Array(str.length);
  556. x.dirty = true;
  557. x.size = str.length;
  558. for (var j = 0; j < str.length; j++) {
  559. data[j] = str.charCodeAt(j);
  560. }
  561. await this.set_data(id, data);
  562. return id;
  563. };
  564. /**
  565. * @param {Uint8Array} buffer
  566. */
  567. FS.prototype.CreateBinaryFile = async function(filename, parentid, buffer) {
  568. const parent_inode = this.inodes[parentid];
  569. if(this.is_forwarder(parent_inode))
  570. {
  571. const foreign_parentid = parent_inode.foreign_id;
  572. const foreign_id = await
  573. this.follow_fs(parent_inode).CreateBinaryFile(filename, foreign_parentid, buffer);
  574. return this.create_forwarder(parent_inode.mount_id, foreign_id);
  575. }
  576. var id = this.CreateFile(filename, parentid);
  577. var x = this.inodes[id];
  578. var data = new Uint8Array(buffer.length);
  579. x.dirty = true;
  580. data.set(buffer);
  581. await this.set_data(id, data);
  582. x.size = buffer.length;
  583. return id;
  584. };
  585. FS.prototype.OpenInode = function(id, mode) {
  586. var inode = this.inodes[id];
  587. if(this.is_forwarder(inode))
  588. {
  589. return this.follow_fs(inode).OpenInode(inode.foreign_id, mode);
  590. }
  591. if ((inode.mode&S_IFMT) == S_IFDIR) {
  592. this.FillDirectory(id);
  593. }
  594. /*
  595. var type = "";
  596. switch(inode.mode&S_IFMT) {
  597. case S_IFREG: type = "File"; break;
  598. case S_IFBLK: type = "Block Device"; break;
  599. case S_IFDIR: type = "Directory"; break;
  600. case S_IFCHR: type = "Character Device"; break;
  601. }
  602. */
  603. //message.Debug("open:" + this.GetFullPath(id) + " type: " + inode.mode + " status:" + inode.status);
  604. return true;
  605. };
  606. FS.prototype.CloseInode = async function(id) {
  607. //message.Debug("close: " + this.GetFullPath(id));
  608. var inode = this.inodes[id];
  609. if(this.is_forwarder(inode))
  610. {
  611. return await this.follow_fs(inode).CloseInode(inode.foreign_id);
  612. }
  613. if(inode.status === STATUS_ON_STORAGE)
  614. {
  615. this.storage.uncache(inode.sha256sum);
  616. }
  617. if (inode.status == STATUS_UNLINKED) {
  618. //message.Debug("Filesystem: Delete unlinked file");
  619. inode.status = STATUS_INVALID;
  620. await this.DeleteData(id);
  621. }
  622. };
  623. /**
  624. * @return {!Promise<number>} 0 if success, or -errno if failured.
  625. */
  626. FS.prototype.Rename = async function(olddirid, oldname, newdirid, newname) {
  627. // message.Debug("Rename " + oldname + " to " + newname);
  628. if ((olddirid == newdirid) && (oldname == newname)) {
  629. return 0;
  630. }
  631. var oldid = this.Search(olddirid, oldname);
  632. if(oldid === -1)
  633. {
  634. return -ENOENT;
  635. }
  636. // For event notification near end of method.
  637. var oldpath = this.GetFullPath(olddirid) + "/" + oldname;
  638. var newid = this.Search(newdirid, newname);
  639. if (newid != -1) {
  640. const ret = this.Unlink(newdirid, newname);
  641. if(ret < 0) return ret;
  642. }
  643. var idx = oldid; // idx contains the id which we want to rename
  644. var inode = this.inodes[idx];
  645. const olddir = this.inodes[olddirid];
  646. const newdir = this.inodes[newdirid];
  647. if(!this.is_forwarder(olddir) && !this.is_forwarder(newdir))
  648. {
  649. // Move inode within current filesystem.
  650. this.unlink_from_dir(olddirid, oldname);
  651. this.link_under_dir(newdirid, idx, newname);
  652. inode.qid.version++;
  653. }
  654. else if(this.is_forwarder(olddir) && olddir.mount_id === newdir.mount_id)
  655. {
  656. // Move inode within the same child filesystem.
  657. const ret = await
  658. this.follow_fs(olddir).Rename(olddir.foreign_id, oldname, newdir.foreign_id, newname);
  659. if(ret < 0) return ret;
  660. }
  661. else if(this.is_a_root(idx))
  662. {
  663. // The actual inode is a root of some descendant filesystem.
  664. // Moving mountpoint across fs not supported - needs to update all corresponding forwarders.
  665. dbg_log("XXX: Attempted to move mountpoint (" + oldname + ") - skipped", LOG_9P);
  666. return -EPERM;
  667. }
  668. else if(!this.IsDirectory(idx) && this.GetInode(idx).nlinks > 1)
  669. {
  670. // Move hardlinked inode vertically in mount tree.
  671. dbg_log("XXX: Attempted to move hardlinked file (" + oldname + ") " +
  672. "across filesystems - skipped", LOG_9P);
  673. return -EPERM;
  674. }
  675. else
  676. {
  677. // Jump between filesystems.
  678. // Can't work with both old and new inode information without first diverting the old
  679. // information into a new idx value.
  680. const diverted_old_idx = this.divert(olddirid, oldname);
  681. const old_real_inode = this.GetInode(idx);
  682. const data = await this.Read(diverted_old_idx, 0, old_real_inode.size);
  683. if(this.is_forwarder(newdir))
  684. {
  685. // Create new inode.
  686. const foreign_fs = this.follow_fs(newdir);
  687. const foreign_id = this.IsDirectory(diverted_old_idx) ?
  688. foreign_fs.CreateDirectory(newname, newdir.foreign_id) :
  689. foreign_fs.CreateFile(newname, newdir.foreign_id);
  690. const new_real_inode = foreign_fs.GetInode(foreign_id);
  691. this.copy_inode(old_real_inode, new_real_inode);
  692. // Point to this new location.
  693. this.set_forwarder(idx, newdir.mount_id, foreign_id);
  694. }
  695. else
  696. {
  697. // Replace current forwarder with real inode.
  698. this.delete_forwarder(inode);
  699. this.copy_inode(old_real_inode, inode);
  700. // Link into new location in this filesystem.
  701. this.link_under_dir(newdirid, idx, newname);
  702. }
  703. // Rewrite data to newly created destination.
  704. await this.ChangeSize(idx, old_real_inode.size);
  705. if(data && data.length)
  706. {
  707. await this.Write(idx, 0, data.length, data);
  708. }
  709. // Move children to newly created destination.
  710. if(this.IsDirectory(idx))
  711. {
  712. for(const child_filename of this.GetChildren(diverted_old_idx))
  713. {
  714. const ret = await this.Rename(diverted_old_idx, child_filename, idx, child_filename);
  715. if(ret < 0) return ret;
  716. }
  717. }
  718. // Perform destructive changes only after migration succeeded.
  719. await this.DeleteData(diverted_old_idx);
  720. const ret = this.Unlink(olddirid, oldname);
  721. if(ret < 0) return ret;
  722. }
  723. this.NotifyListeners(idx, "rename", {oldpath: oldpath});
  724. return 0;
  725. };
  726. FS.prototype.Write = async function(id, offset, count, buffer) {
  727. this.NotifyListeners(id, 'write');
  728. var inode = this.inodes[id];
  729. if(this.is_forwarder(inode))
  730. {
  731. const foreign_id = inode.foreign_id;
  732. await this.follow_fs(inode).Write(foreign_id, offset, count, buffer);
  733. return;
  734. }
  735. inode.dirty = true;
  736. var data = await this.get_buffer(id);
  737. if (!data || data.length < (offset+count)) {
  738. await this.ChangeSize(id, Math.floor(((offset+count)*3)/2));
  739. inode.size = offset + count;
  740. data = await this.get_buffer(id);
  741. } else
  742. if (inode.size < (offset+count)) {
  743. inode.size = offset + count;
  744. }
  745. if(buffer)
  746. {
  747. data.set(buffer.subarray(0, count), offset);
  748. }
  749. await this.set_data(id, data);
  750. };
  751. FS.prototype.Read = async function(inodeid, offset, count)
  752. {
  753. const inode = this.inodes[inodeid];
  754. if(this.is_forwarder(inode))
  755. {
  756. const foreign_id = inode.foreign_id;
  757. return await this.follow_fs(inode).Read(foreign_id, offset, count);
  758. }
  759. return await this.get_data(inodeid, offset, count);
  760. };
  761. FS.prototype.Search = function(parentid, name) {
  762. const parent_inode = this.inodes[parentid];
  763. if(this.is_forwarder(parent_inode))
  764. {
  765. const foreign_parentid = parent_inode.foreign_id;
  766. const foreign_id = this.follow_fs(parent_inode).Search(foreign_parentid, name);
  767. if(foreign_id === -1) return -1;
  768. return this.get_forwarder(parent_inode.mount_id, foreign_id);
  769. }
  770. const childid = parent_inode.direntries.get(name);
  771. return childid === undefined ? -1 : childid;
  772. };
  773. FS.prototype.CountUsedInodes = function()
  774. {
  775. let count = this.inodes.length;
  776. for(const { fs, backtrack } of this.mounts)
  777. {
  778. count += fs.CountUsedInodes();
  779. // Forwarder inodes don't count.
  780. count -= backtrack.size;
  781. }
  782. return count;
  783. };
  784. FS.prototype.CountFreeInodes = function()
  785. {
  786. let count = 1024 * 1024;
  787. for(const { fs } of this.mounts)
  788. {
  789. count += fs.CountFreeInodes();
  790. }
  791. return count;
  792. };
  793. FS.prototype.GetTotalSize = function() {
  794. let size = this.used_size;
  795. for(const { fs } of this.mounts)
  796. {
  797. size += fs.GetTotalSize();
  798. }
  799. return size;
  800. //var size = 0;
  801. //for(var i=0; i<this.inodes.length; i++) {
  802. // var d = this.inodes[i].data;
  803. // size += d ? d.length : 0;
  804. //}
  805. //return size;
  806. };
  807. FS.prototype.GetSpace = function() {
  808. let size = this.total_size;
  809. for(const { fs } of this.mounts)
  810. {
  811. size += fs.GetSpace();
  812. }
  813. return this.total_size;
  814. };
  815. /**
  816. * XXX: Not ideal.
  817. * @param {number} idx
  818. * @return {string}
  819. */
  820. FS.prototype.GetDirectoryName = function(idx)
  821. {
  822. const parent_inode = this.inodes[this.GetParent(idx)];
  823. if(this.is_forwarder(parent_inode))
  824. {
  825. return this.follow_fs(parent_inode).GetDirectoryName(this.inodes[idx].foreign_id);
  826. }
  827. // Root directory.
  828. if(!parent_inode) return "";
  829. for(const [name, childid] of parent_inode.direntries)
  830. {
  831. if(childid === idx) return name;
  832. }
  833. dbg_assert(false, "Filesystem: Found directory inode whose parent doesn't link to it");
  834. return "";
  835. };
  836. FS.prototype.GetFullPath = function(idx) {
  837. dbg_assert(this.IsDirectory(idx), "Filesystem: Cannot get full path of non-directory inode");
  838. var path = "";
  839. while(idx != 0) {
  840. path = "/" + this.GetDirectoryName(idx) + path;
  841. idx = this.GetParent(idx);
  842. }
  843. return path.substring(1);
  844. };
  845. /**
  846. * @param {number} parentid
  847. * @param {number} targetid
  848. * @param {string} name
  849. * @return {number} 0 if success, or -errno if failured.
  850. */
  851. FS.prototype.Link = function(parentid, targetid, name)
  852. {
  853. if(this.IsDirectory(targetid))
  854. {
  855. return -EPERM;
  856. }
  857. const parent_inode = this.inodes[parentid];
  858. const inode = this.inodes[targetid];
  859. if(this.is_forwarder(parent_inode))
  860. {
  861. if(!this.is_forwarder(inode) || inode.mount_id !== parent_inode.mount_id)
  862. {
  863. dbg_log("XXX: Attempted to hardlink a file into a child filesystem - skipped", LOG_9P);
  864. return -EPERM;
  865. }
  866. return this.follow_fs(parent_inode).Link(parent_inode.foreign_id, inode.foreign_id, name);
  867. }
  868. if(this.is_forwarder(inode))
  869. {
  870. dbg_log("XXX: Attempted to hardlink file across filesystems - skipped", LOG_9P);
  871. return -EPERM;
  872. }
  873. this.link_under_dir(parentid, targetid, name);
  874. return 0;
  875. };
  876. FS.prototype.Unlink = function(parentid, name) {
  877. if(name === "." || name === "..")
  878. {
  879. // Also guarantees that root cannot be deleted.
  880. return -EPERM;
  881. }
  882. const idx = this.Search(parentid, name);
  883. const inode = this.inodes[idx];
  884. const parent_inode = this.inodes[parentid];
  885. //message.Debug("Unlink " + inode.name);
  886. // forward if necessary
  887. if(this.is_forwarder(parent_inode))
  888. {
  889. dbg_assert(this.is_forwarder(inode), "Children of forwarders should be forwarders");
  890. const foreign_parentid = parent_inode.foreign_id;
  891. return this.follow_fs(parent_inode).Unlink(foreign_parentid, name);
  892. // Keep the forwarder dangling - file is still accessible.
  893. }
  894. if(this.IsDirectory(idx) && !this.IsEmpty(idx))
  895. {
  896. return -ENOTEMPTY;
  897. }
  898. this.unlink_from_dir(parentid, name);
  899. if(inode.nlinks === 0)
  900. {
  901. // don't delete the content. The file is still accessible
  902. inode.status = STATUS_UNLINKED;
  903. this.NotifyListeners(idx, 'delete');
  904. }
  905. return 0;
  906. };
  907. FS.prototype.DeleteData = async function(idx)
  908. {
  909. const inode = this.inodes[idx];
  910. if(this.is_forwarder(inode))
  911. {
  912. await this.follow_fs(inode).DeleteData(inode.foreign_id);
  913. return;
  914. }
  915. inode.size = 0;
  916. delete this.inodedata[idx];
  917. };
  918. /**
  919. * @private
  920. * @param {number} idx
  921. * @return {!Promise<Uint8Array>} The buffer that contains the file contents, which may be larger
  922. * than the data itself. To ensure that any modifications done to this buffer is reflected
  923. * to the file, call set_data with the modified buffer.
  924. */
  925. FS.prototype.get_buffer = async function(idx)
  926. {
  927. const inode = this.inodes[idx];
  928. dbg_assert(inode, `Filesystem get_buffer: idx ${idx} does not point to an inode`);
  929. if(this.inodedata[idx])
  930. {
  931. return this.inodedata[idx];
  932. }
  933. else if(inode.status === STATUS_ON_STORAGE)
  934. {
  935. dbg_assert(inode.sha256sum, "Filesystem get_data: found inode on server without sha256sum");
  936. return await this.storage.read(inode.sha256sum, 0, inode.size);
  937. }
  938. else
  939. {
  940. return null;
  941. }
  942. };
  943. /**
  944. * @private
  945. * @param {number} idx
  946. * @param {number} offset
  947. * @param {number} count
  948. * @return {!Promise<Uint8Array>}
  949. */
  950. FS.prototype.get_data = async function(idx, offset, count)
  951. {
  952. const inode = this.inodes[idx];
  953. dbg_assert(inode, `Filesystem get_data: idx ${idx} does not point to an inode`);
  954. if(this.inodedata[idx])
  955. {
  956. return this.inodedata[idx].subarray(offset, offset + count);
  957. }
  958. else if(inode.status === STATUS_ON_STORAGE)
  959. {
  960. dbg_assert(inode.sha256sum, "Filesystem get_data: found inode on server without sha256sum");
  961. return await this.storage.read(inode.sha256sum, offset, count);
  962. }
  963. else
  964. {
  965. return null;
  966. }
  967. };
  968. /**
  969. * @private
  970. * @param {number} idx
  971. * @param {Uint8Array} buffer
  972. */
  973. FS.prototype.set_data = async function(idx, buffer)
  974. {
  975. // Current scheme: Save all modified buffers into local inodedata.
  976. this.inodedata[idx] = buffer;
  977. if(this.inodes[idx].status === STATUS_ON_STORAGE)
  978. {
  979. this.inodes[idx].status = STATUS_OK;
  980. this.storage.uncache(this.inodes[idx].sha256sum);
  981. }
  982. };
  983. /**
  984. * @param {number} idx
  985. * @return {!Inode}
  986. */
  987. FS.prototype.GetInode = function(idx)
  988. {
  989. dbg_assert(!isNaN(idx), "Filesystem GetInode: NaN idx");
  990. dbg_assert(idx >= 0 && idx < this.inodes.length, "Filesystem GetInode: out of range idx:" + idx);
  991. const inode = this.inodes[idx];
  992. if(this.is_forwarder(inode))
  993. {
  994. return this.follow_fs(inode).GetInode(inode.foreign_id);
  995. }
  996. return inode;
  997. };
  998. FS.prototype.ChangeSize = async function(idx, newsize)
  999. {
  1000. var inode = this.GetInode(idx);
  1001. var temp = await this.get_data(idx, 0, inode.size);
  1002. inode.dirty = true;
  1003. //message.Debug("change size to: " + newsize);
  1004. if (newsize == inode.size) return;
  1005. var data = new Uint8Array(newsize);
  1006. inode.size = newsize;
  1007. if(temp)
  1008. {
  1009. var size = Math.min(temp.length, inode.size);
  1010. data.set(temp.subarray(0, size), 0);
  1011. }
  1012. await this.set_data(idx, data);
  1013. };
  1014. FS.prototype.SearchPath = function(path) {
  1015. //path = path.replace(/\/\//g, "/");
  1016. path = path.replace("//", "/");
  1017. var walk = path.split("/");
  1018. if (walk.length > 0 && walk[walk.length - 1].length === 0) walk.pop();
  1019. if (walk.length > 0 && walk[0].length === 0) walk.shift();
  1020. const n = walk.length;
  1021. var parentid = -1;
  1022. var id = 0;
  1023. let forward_path = null;
  1024. for(var i=0; i<n; i++) {
  1025. parentid = id;
  1026. id = this.Search(parentid, walk[i]);
  1027. if(!forward_path && this.is_forwarder(this.inodes[parentid]))
  1028. {
  1029. forward_path = "/" + walk.slice(i).join("/");
  1030. }
  1031. if (id == -1) {
  1032. if (i < n-1) return {id: -1, parentid: -1, name: walk[i], forward_path }; // one name of the path cannot be found
  1033. return {id: -1, parentid: parentid, name: walk[i], forward_path}; // the last element in the path does not exist, but the parent
  1034. }
  1035. }
  1036. return {id: id, parentid: parentid, name: walk[i], forward_path};
  1037. };
  1038. // -----------------------------------------------------
  1039. /**
  1040. * @param {number} dirid
  1041. * @param {Array<{parentid: number, name: string}>} list
  1042. */
  1043. FS.prototype.GetRecursiveList = function(dirid, list) {
  1044. if(this.is_forwarder(this.inodes[dirid]))
  1045. {
  1046. const foreign_fs = this.follow_fs(this.inodes[dirid]);
  1047. const foreign_dirid = this.inodes[dirid].foreign_id;
  1048. const mount_id = this.inodes[dirid].mount_id;
  1049. const foreign_start = list.length;
  1050. foreign_fs.GetRecursiveList(foreign_dirid, list);
  1051. for(let i = foreign_start; i < list.length; i++)
  1052. {
  1053. list[i].parentid = this.get_forwarder(mount_id, list[i].parentid);
  1054. }
  1055. return;
  1056. }
  1057. for(const [name, id] of this.inodes[dirid].direntries)
  1058. {
  1059. if(name !== "." && name !== "..")
  1060. {
  1061. list.push({ parentid: dirid, name });
  1062. if(this.IsDirectory(id))
  1063. {
  1064. this.GetRecursiveList(id, list);
  1065. }
  1066. }
  1067. }
  1068. };
  1069. FS.prototype.RecursiveDelete = function(path) {
  1070. var toDelete = [];
  1071. var ids = this.SearchPath(path);
  1072. if(ids.id === -1) return;
  1073. this.GetRecursiveList(ids.id, toDelete);
  1074. for(var i=toDelete.length-1; i>=0; i--)
  1075. {
  1076. const ret = this.Unlink(toDelete[i].parentid, toDelete[i].name);
  1077. dbg_assert(ret === 0, "Filesystem RecursiveDelete failed at parent=" + toDelete[i].parentid +
  1078. ", name='" + toDelete[i].name + "' with error code: " + (-ret));
  1079. }
  1080. };
  1081. FS.prototype.DeleteNode = function(path) {
  1082. var ids = this.SearchPath(path);
  1083. if (ids.id == -1) return;
  1084. if ((this.inodes[ids.id].mode&S_IFMT) == S_IFREG){
  1085. const ret = this.Unlink(ids.parentid, ids.name);
  1086. dbg_assert(ret === 0, "Filesystem DeleteNode failed with error code: " + (-ret));
  1087. return;
  1088. }
  1089. if ((this.inodes[ids.id].mode&S_IFMT) == S_IFDIR){
  1090. this.RecursiveDelete(path);
  1091. const ret = this.Unlink(ids.parentid, ids.name);
  1092. dbg_assert(ret === 0, "Filesystem DeleteNode failed with error code: " + (-ret));
  1093. return;
  1094. }
  1095. };
  1096. /** @param {*=} info */
  1097. FS.prototype.NotifyListeners = function(id, action, info) {
  1098. //if(info==undefined)
  1099. // info = {};
  1100. //var path = this.GetFullPath(id);
  1101. //if (this.watchFiles[path] == true && action=='write') {
  1102. // message.Send("WatchFileEvent", path);
  1103. //}
  1104. //for (var directory of this.watchDirectories) {
  1105. // if (this.watchDirectories.hasOwnProperty(directory)) {
  1106. // var indexOf = path.indexOf(directory)
  1107. // if(indexOf == 0 || indexOf == 1)
  1108. // message.Send("WatchDirectoryEvent", {path: path, event: action, info: info});
  1109. // }
  1110. //}
  1111. };
  1112. FS.prototype.Check = function() {
  1113. for(var i=1; i<this.inodes.length; i++)
  1114. {
  1115. if (this.inodes[i].status == STATUS_INVALID) continue;
  1116. var inode = this.GetInode(i);
  1117. if (inode.nlinks < 0) {
  1118. message.Debug("Error in filesystem: negative nlinks=" + inode.nlinks + " at id =" + i);
  1119. }
  1120. if(this.IsDirectory(i))
  1121. {
  1122. const inode = this.GetInode(i);
  1123. if(this.IsDirectory(i) && this.GetParent(i) < 0) {
  1124. message.Debug("Error in filesystem: negative parent id " + i);
  1125. }
  1126. for(const [name, id] of inode.direntries)
  1127. {
  1128. if(name.length === 0) {
  1129. message.Debug("Error in filesystem: inode with no name and id " + id);
  1130. }
  1131. for (const c of name) {
  1132. if (c < 32) {
  1133. message.Debug("Error in filesystem: Unallowed char in filename");
  1134. }
  1135. }
  1136. }
  1137. }
  1138. }
  1139. };
  1140. FS.prototype.FillDirectory = function(dirid) {
  1141. const inode = this.inodes[dirid];
  1142. if(this.is_forwarder(inode))
  1143. {
  1144. // XXX: The ".." of a mountpoint should point back to an inode in this fs.
  1145. // Otherwise, ".." gets the wrong qid and mode.
  1146. this.follow_fs(inode).FillDirectory(inode.foreign_id);
  1147. return;
  1148. }
  1149. if (!inode.updatedir) return;
  1150. inode.updatedir = false;
  1151. let size = 0;
  1152. for(const name of inode.direntries.keys())
  1153. {
  1154. size += 13 + 8 + 1 + 2 + UTF8.UTF8Length(name);
  1155. }
  1156. const data = this.inodedata[dirid] = new Uint8Array(size);
  1157. inode.size = size;
  1158. let offset = 0x0;
  1159. for(const [name, id] of inode.direntries)
  1160. {
  1161. const child = this.GetInode(id);
  1162. offset += marshall.Marshall(
  1163. ["Q", "d", "b", "s"],
  1164. [child.qid,
  1165. offset+13+8+1+2+UTF8.UTF8Length(name),
  1166. child.mode >> 12,
  1167. name],
  1168. data, offset);
  1169. }
  1170. };
  1171. FS.prototype.RoundToDirentry = function(dirid, offset_target)
  1172. {
  1173. const data = this.inodedata[dirid];
  1174. dbg_assert(data, `FS directory data for dirid=${dirid} should be generated`);
  1175. dbg_assert(data.length, "FS directory should have at least an entry");
  1176. if(offset_target >= data.length)
  1177. {
  1178. return data.length;
  1179. }
  1180. let offset = 0;
  1181. while(true)
  1182. {
  1183. const next_offset = marshall.Unmarshall(["Q", "d"], data, { offset })[1];
  1184. if(next_offset > offset_target) break;
  1185. offset = next_offset;
  1186. }
  1187. return offset;
  1188. };
  1189. /**
  1190. * @param {number} idx
  1191. * @return {boolean}
  1192. */
  1193. FS.prototype.IsDirectory = function(idx)
  1194. {
  1195. const inode = this.inodes[idx];
  1196. if(this.is_forwarder(inode))
  1197. {
  1198. return this.follow_fs(inode).IsDirectory(inode.foreign_id);
  1199. }
  1200. return (inode.mode & S_IFMT) === S_IFDIR;
  1201. };
  1202. /**
  1203. * @param {number} idx
  1204. * @return {boolean}
  1205. */
  1206. FS.prototype.IsEmpty = function(idx)
  1207. {
  1208. const inode = this.inodes[idx];
  1209. if(this.is_forwarder(inode))
  1210. {
  1211. return this.follow_fs(inode).IsDirectory(inode.foreign_id);
  1212. }
  1213. for(const name of inode.direntries.keys())
  1214. {
  1215. if(name !== "." && name !== "..") return false;
  1216. }
  1217. return true;
  1218. };
  1219. /**
  1220. * @param {number} idx
  1221. * @return {!Array<string>} List of children names
  1222. */
  1223. FS.prototype.GetChildren = function(idx)
  1224. {
  1225. dbg_assert(this.IsDirectory(idx), "Filesystem: cannot get children of non-directory inode");
  1226. const inode = this.inodes[idx];
  1227. if(this.is_forwarder(inode))
  1228. {
  1229. return this.follow_fs(inode).GetChildren(inode.foreign_id);
  1230. }
  1231. const children = [];
  1232. for(const name of inode.direntries.keys())
  1233. {
  1234. if(name !== "." && name !== "..")
  1235. {
  1236. children.push(name);
  1237. }
  1238. }
  1239. return children;
  1240. };
  1241. /**
  1242. * @param {number} idx
  1243. * @return {number} Local idx of parent
  1244. */
  1245. FS.prototype.GetParent = function(idx)
  1246. {
  1247. dbg_assert(this.IsDirectory(idx), "Filesystem: cannot get parent of non-directory inode");
  1248. const inode = this.inodes[idx];
  1249. if(this.should_be_linked(inode))
  1250. {
  1251. return inode.direntries.get("..");
  1252. }
  1253. else
  1254. {
  1255. const foreign_dirid = this.follow_fs(inode).GetParent(inode.foreign_id);
  1256. dbg_assert(foreign_dirid !== -1, "Filesystem: should not have invalid parent ids");
  1257. return this.get_forwarder(inode.mount_id, foreign_dirid);
  1258. }
  1259. };
  1260. // -----------------------------------------------------
  1261. // only support for security.capabilities
  1262. // should return a "struct vfs_cap_data" defined in
  1263. // linux/capability for format
  1264. // check also:
  1265. // sys/capability.h
  1266. // http://lxr.free-electrons.com/source/security/commoncap.c#L376
  1267. // http://man7.org/linux/man-pages/man7/capabilities.7.html
  1268. // http://man7.org/linux/man-pages/man8/getcap.8.html
  1269. // http://man7.org/linux/man-pages/man3/libcap.3.html
  1270. FS.prototype.PrepareCAPs = function(id) {
  1271. var inode = this.GetInode(id);
  1272. if (inode.caps) return inode.caps.length;
  1273. inode.caps = new Uint8Array(20);
  1274. // format is little endian
  1275. // note: getxattr returns -EINVAL if using revision 1 format.
  1276. // note: getxattr presents revision 3 as revision 2 when revision 3 is not needed.
  1277. // magic_etc (revision=0x02: 20 bytes)
  1278. inode.caps[0] = 0x00;
  1279. inode.caps[1] = 0x00;
  1280. inode.caps[2] = 0x00;
  1281. inode.caps[3] = 0x02;
  1282. // lower
  1283. // permitted (first 32 capabilities)
  1284. inode.caps[4] = 0xFF;
  1285. inode.caps[5] = 0xFF;
  1286. inode.caps[6] = 0xFF;
  1287. inode.caps[7] = 0xFF;
  1288. // inheritable (first 32 capabilities)
  1289. inode.caps[8] = 0xFF;
  1290. inode.caps[9] = 0xFF;
  1291. inode.caps[10] = 0xFF;
  1292. inode.caps[11] = 0xFF;
  1293. // higher
  1294. // permitted (last 6 capabilities)
  1295. inode.caps[12] = 0x3F;
  1296. inode.caps[13] = 0x00;
  1297. inode.caps[14] = 0x00;
  1298. inode.caps[15] = 0x00;
  1299. // inheritable (last 6 capabilities)
  1300. inode.caps[16] = 0x3F;
  1301. inode.caps[17] = 0x00;
  1302. inode.caps[18] = 0x00;
  1303. inode.caps[19] = 0x00;
  1304. return inode.caps.length;
  1305. };
  1306. // -----------------------------------------------------
  1307. /**
  1308. * @constructor
  1309. * @param {FS} filesystem
  1310. */
  1311. function FSMountInfo(filesystem)
  1312. {
  1313. /** @type {FS}*/
  1314. this.fs = filesystem;
  1315. /**
  1316. * Maps foreign inode id back to local inode id.
  1317. * @type {!Map<number,number>}
  1318. */
  1319. this.backtrack = new Map();
  1320. }
  1321. FSMountInfo.prototype.get_state = function()
  1322. {
  1323. const state = [];
  1324. state[0] = this.fs;
  1325. state[1] = [...this.backtrack];
  1326. return state;
  1327. };
  1328. FSMountInfo.prototype.set_state = function(state)
  1329. {
  1330. this.fs = state[0];
  1331. this.backtrack = new Map(state[1]);
  1332. };
  1333. /**
  1334. * @private
  1335. * @param {number} idx Local idx of inode.
  1336. * @param {number} mount_id Mount number of the destination fs.
  1337. * @param {number} foreign_id Foreign idx of destination inode.
  1338. */
  1339. FS.prototype.set_forwarder = function(idx, mount_id, foreign_id)
  1340. {
  1341. const inode = this.inodes[idx];
  1342. dbg_assert(inode.nlinks === 0,
  1343. "Filesystem: attempted to convert an inode into forwarder before unlinking the inode");
  1344. if(this.is_forwarder(inode))
  1345. {
  1346. this.mounts[inode.mount_id].backtrack.delete(inode.foreign_id);
  1347. }
  1348. inode.status = STATUS_FORWARDING;
  1349. inode.mount_id = mount_id;
  1350. inode.foreign_id = foreign_id;
  1351. this.mounts[mount_id].backtrack.set(foreign_id, idx);
  1352. };
  1353. /**
  1354. * @private
  1355. * @param {number} mount_id Mount number of the destination fs.
  1356. * @param {number} foreign_id Foreign idx of destination inode.
  1357. * @return {number} Local idx of newly created forwarder.
  1358. */
  1359. FS.prototype.create_forwarder = function(mount_id, foreign_id)
  1360. {
  1361. const inode = this.CreateInode();
  1362. const idx = this.inodes.length;
  1363. this.inodes.push(inode);
  1364. inode.fid = idx;
  1365. this.set_forwarder(idx, mount_id, foreign_id);
  1366. return idx;
  1367. };
  1368. /**
  1369. * @private
  1370. * @param {Inode} inode
  1371. * @return {boolean}
  1372. */
  1373. FS.prototype.is_forwarder = function(inode)
  1374. {
  1375. return inode.status === STATUS_FORWARDING;
  1376. };
  1377. /**
  1378. * Whether the inode it points to is a root of some filesystem.
  1379. * @private
  1380. * @param {number} idx
  1381. * @return {boolean}
  1382. */
  1383. FS.prototype.is_a_root = function(idx)
  1384. {
  1385. return this.GetInode(idx).fid === 0;
  1386. };
  1387. /**
  1388. * Ensures forwarder exists, and returns such forwarder, for the described foreign inode.
  1389. * @private
  1390. * @param {number} mount_id
  1391. * @param {number} foreign_id
  1392. * @return {number} Local idx of a forwarder to described inode.
  1393. */
  1394. FS.prototype.get_forwarder = function(mount_id, foreign_id)
  1395. {
  1396. const mount = this.mounts[mount_id];
  1397. dbg_assert(foreign_id >= 0, "Filesystem get_forwarder: invalid foreign_id: " + foreign_id);
  1398. dbg_assert(mount, "Filesystem get_forwarder: invalid mount number: " + mount_id);
  1399. const result = mount.backtrack.get(foreign_id);
  1400. if(result === undefined)
  1401. {
  1402. // Create if not already exists.
  1403. return this.create_forwarder(mount_id, foreign_id);
  1404. }
  1405. return result;
  1406. };
  1407. /**
  1408. * @private
  1409. * @param {Inode} inode
  1410. */
  1411. FS.prototype.delete_forwarder = function(inode)
  1412. {
  1413. dbg_assert(this.is_forwarder(inode), "Filesystem delete_forwarder: expected forwarder");
  1414. inode.status = STATUS_INVALID;
  1415. this.mounts[inode.mount_id].backtrack.delete(inode.foreign_id);
  1416. };
  1417. /**
  1418. * @private
  1419. * @param {Inode} inode
  1420. * @return {FS}
  1421. */
  1422. FS.prototype.follow_fs = function(inode)
  1423. {
  1424. const mount = this.mounts[inode.mount_id];
  1425. dbg_assert(this.is_forwarder(inode),
  1426. "Filesystem follow_fs: inode should be a forwarding inode");
  1427. dbg_assert(mount, "Filesystem follow_fs: inode<id=" + inode.fid +
  1428. "> should point to valid mounted FS");
  1429. return mount.fs;
  1430. };
  1431. /**
  1432. * Mount another filesystem to given path.
  1433. * @param {string} path
  1434. * @param {FS} fs
  1435. * @return {number} inode id of mount point if successful, or -errno if mounting failed.
  1436. */
  1437. FS.prototype.Mount = function(path, fs)
  1438. {
  1439. dbg_assert(fs.qidcounter === this.qidcounter,
  1440. "Cannot mount filesystem whose qid numbers aren't synchronised with current filesystem.");
  1441. const path_infos = this.SearchPath(path);
  1442. if(path_infos.parentid === -1)
  1443. {
  1444. dbg_log("Mount failed: parent for path not found: " + path, LOG_9P);
  1445. return -ENOENT;
  1446. }
  1447. if(path_infos.id !== -1)
  1448. {
  1449. dbg_log("Mount failed: file already exists at path: " + path, LOG_9P);
  1450. return -EEXIST;
  1451. }
  1452. if(path_infos.forward_path)
  1453. {
  1454. const parent = this.inodes[path_infos.parentid];
  1455. const ret = this.follow_fs(parent).Mount(path_infos.forward_path, fs);
  1456. if(ret < 0) return ret;
  1457. return this.get_forwarder(parent.mount_id, ret);
  1458. }
  1459. const mount_id = this.mounts.length;
  1460. this.mounts.push(new FSMountInfo(fs));
  1461. const idx = this.create_forwarder(mount_id, 0);
  1462. this.link_under_dir(path_infos.parentid, idx, path_infos.name);
  1463. return idx;
  1464. };
  1465. /**
  1466. * @constructor
  1467. */
  1468. function FSLockRegion()
  1469. {
  1470. this.type = P9_LOCK_TYPE_UNLCK;
  1471. this.start = 0;
  1472. this.length = Infinity;
  1473. this.proc_id = -1;
  1474. this.client_id = "";
  1475. }
  1476. FSLockRegion.prototype.get_state = function()
  1477. {
  1478. const state = [];
  1479. state[0] = this.type;
  1480. state[1] = this.start;
  1481. // Infinity is not JSON.stringify-able
  1482. state[2] = this.length === Infinity ? 0 : this.length;
  1483. state[3] = this.proc_id;
  1484. state[4] = this.client_id;
  1485. return state;
  1486. };
  1487. FSLockRegion.prototype.set_state = function(state)
  1488. {
  1489. this.type = state[0];
  1490. this.start = state[1];
  1491. this.length = state[2] === 0 ? Infinity : state[2];
  1492. this.proc_id = state[3];
  1493. this.client_id = state[4];
  1494. };
  1495. /**
  1496. * @return {FSLockRegion}
  1497. */
  1498. FSLockRegion.prototype.clone = function()
  1499. {
  1500. const new_region = new FSLockRegion();
  1501. new_region.set_state(this.get_state());
  1502. return new_region;
  1503. };
  1504. /**
  1505. * @param {FSLockRegion} region
  1506. * @return {boolean}
  1507. */
  1508. FSLockRegion.prototype.conflicts_with = function(region)
  1509. {
  1510. if(this.proc_id === region.proc_id && this.client_id === region.client_id) return false;
  1511. if(this.type === P9_LOCK_TYPE_UNLCK || region.type === P9_LOCK_TYPE_UNLCK) return false;
  1512. if(this.type !== P9_LOCK_TYPE_WRLCK && region.type !== P9_LOCK_TYPE_WRLCK) return false;
  1513. if(this.start + this.length <= region.start) return false;
  1514. if(region.start + region.length <= this.start) return false;
  1515. return true;
  1516. };
  1517. /**
  1518. * @param {FSLockRegion} region
  1519. * @return {boolean}
  1520. */
  1521. FSLockRegion.prototype.is_alike = function(region)
  1522. {
  1523. return region.proc_id === this.proc_id &&
  1524. region.client_id === this.client_id &&
  1525. region.type === this.type;
  1526. };
  1527. /**
  1528. * @param {FSLockRegion} region
  1529. * @return {boolean}
  1530. */
  1531. FSLockRegion.prototype.may_merge_after = function(region)
  1532. {
  1533. return this.is_alike(region) && region.start + region.length === this.start;
  1534. };
  1535. /**
  1536. * @param {number} type
  1537. * @param {number} start
  1538. * @param {number} length
  1539. * @param {number} proc_id
  1540. * @param {string} client_id
  1541. * @return {!FSLockRegion}
  1542. */
  1543. FS.prototype.DescribeLock = function(type, start, length, proc_id, client_id)
  1544. {
  1545. dbg_assert(type === P9_LOCK_TYPE_RDLCK ||
  1546. type === P9_LOCK_TYPE_WRLCK ||
  1547. type === P9_LOCK_TYPE_UNLCK,
  1548. "Filesystem: Invalid lock type: " + type);
  1549. dbg_assert(start >= 0, "Filesystem: Invalid negative lock starting offset: " + start);
  1550. dbg_assert(length > 0, "Filesystem: Invalid non-positive lock length: " + length);
  1551. const lock = new FSLockRegion();
  1552. lock.type = type;
  1553. lock.start = start;
  1554. lock.length = length;
  1555. lock.proc_id = proc_id;
  1556. lock.client_id = client_id;
  1557. return lock;
  1558. };
  1559. /**
  1560. * @param {number} id
  1561. * @param {FSLockRegion} request
  1562. * @return {FSLockRegion} The first conflicting lock found, or null if requested lock is possible.
  1563. */
  1564. FS.prototype.GetLock = function(id, request)
  1565. {
  1566. const inode = this.inodes[id];
  1567. if(this.is_forwarder(inode))
  1568. {
  1569. const foreign_id = inode.foreign_id;
  1570. return this.follow_fs(inode).GetLock(foreign_id, request);
  1571. }
  1572. for(const region of inode.locks)
  1573. {
  1574. if(request.conflicts_with(region))
  1575. {
  1576. return region.clone();
  1577. }
  1578. }
  1579. return null;
  1580. };
  1581. /**
  1582. * @param {number} id
  1583. * @param {FSLockRegion} request
  1584. * @param {number} flags
  1585. * @return {number} One of P9_LOCK_SUCCESS / P9_LOCK_BLOCKED / P9_LOCK_ERROR / P9_LOCK_GRACE.
  1586. */
  1587. FS.prototype.Lock = function(id, request, flags)
  1588. {
  1589. const inode = this.inodes[id];
  1590. if(this.is_forwarder(inode))
  1591. {
  1592. const foreign_id = inode.foreign_id;
  1593. return this.follow_fs(inode).Lock(foreign_id, request, flags);
  1594. }
  1595. request = request.clone();
  1596. // (1) Check whether lock is possible before any modification.
  1597. if(request.type !== P9_LOCK_TYPE_UNLCK && this.GetLock(id, request))
  1598. {
  1599. return P9_LOCK_BLOCKED;
  1600. }
  1601. // (2) Subtract requested region from locks of the same owner.
  1602. for(let i = 0; i < inode.locks.length; i++)
  1603. {
  1604. const region = inode.locks[i];
  1605. dbg_assert(region.length > 0,
  1606. "Filesystem: Found non-positive lock region length: " + region.length);
  1607. dbg_assert(region.type === P9_LOCK_TYPE_RDLCK || region.type === P9_LOCK_TYPE_WRLCK,
  1608. "Filesystem: Found invalid lock type: " + region.type);
  1609. dbg_assert(!inode.locks[i-1] || inode.locks[i-1].start <= region.start,
  1610. "Filesystem: Locks should be sorted by starting offset");
  1611. // Skip to requested region.
  1612. if(region.start + region.length <= request.start) continue;
  1613. // Check whether we've skipped past the requested region.
  1614. if(request.start + request.length <= region.start) break;
  1615. // Skip over locks of different owners.
  1616. if(region.proc_id !== request.proc_id || region.client_id !== request.client_id)
  1617. {
  1618. dbg_assert(!region.conflicts_with(request),
  1619. "Filesytem: Found conflicting lock region, despite already checked for conflicts");
  1620. continue;
  1621. }
  1622. // Pretend region would be split into parts 1 and 2.
  1623. const start1 = region.start;
  1624. const start2 = request.start + request.length;
  1625. const length1 = request.start - start1;
  1626. const length2 = region.start + region.length - start2;
  1627. if(length1 > 0 && length2 > 0 && region.type === request.type)
  1628. {
  1629. // Requested region is already locked with the required type.
  1630. // Return early - no need to modify anything.
  1631. return P9_LOCK_SUCCESS;
  1632. }
  1633. if(length1 > 0)
  1634. {
  1635. // Shrink from right / first half of the split.
  1636. region.length = length1;
  1637. }
  1638. if(length1 <= 0 && length2 > 0)
  1639. {
  1640. // Shrink from left.
  1641. region.start = start2;
  1642. region.length = length2;
  1643. }
  1644. else if(length2 > 0)
  1645. {
  1646. // Add second half of the split.
  1647. // Fast-forward to correct location.
  1648. while(i < inode.locks.length && inode.locks[i].start < start2) i++;
  1649. inode.locks.splice(i, 0,
  1650. this.DescribeLock(region.type, start2, length2, region.proc_id, region.client_id));
  1651. }
  1652. else if(length1 <= 0)
  1653. {
  1654. // Requested region completely covers this region. Delete.
  1655. inode.locks.splice(i, 1);
  1656. i--;
  1657. }
  1658. }
  1659. // (3) Insert requested lock region as a whole.
  1660. // No point in adding the requested lock region as fragmented bits in the above loop
  1661. // and having to merge them all back into one.
  1662. if(request.type !== P9_LOCK_TYPE_UNLCK)
  1663. {
  1664. let new_region = request;
  1665. let has_merged = false;
  1666. let i = 0;
  1667. // Fast-forward to requested position, and try merging with previous region.
  1668. for(; i < inode.locks.length; i++)
  1669. {
  1670. if(new_region.may_merge_after(inode.locks[i]))
  1671. {
  1672. inode.locks[i].length += request.length;
  1673. new_region = inode.locks[i];
  1674. has_merged = true;
  1675. }
  1676. if(request.start <= inode.locks[i].start) break;
  1677. }
  1678. if(!has_merged)
  1679. {
  1680. inode.locks.splice(i, 0, new_region);
  1681. i++;
  1682. }
  1683. // Try merging with the subsequent alike region.
  1684. for(; i < inode.locks.length; i++)
  1685. {
  1686. if(!inode.locks[i].is_alike(new_region)) continue;
  1687. if(inode.locks[i].may_merge_after(new_region))
  1688. {
  1689. new_region.length += inode.locks[i].length;
  1690. inode.locks.splice(i, 1);
  1691. }
  1692. // No more mergable regions after this.
  1693. break;
  1694. }
  1695. }
  1696. return P9_LOCK_SUCCESS;
  1697. };
  1698. FS.prototype.read_dir = function(path)
  1699. {
  1700. const p = this.SearchPath(path);
  1701. if(p.id === -1)
  1702. {
  1703. return undefined;
  1704. }
  1705. const dir = this.GetInode(p.id);
  1706. return Array.from(dir.direntries.keys()).filter(path => path !== "." && path !== "..");
  1707. };
  1708. FS.prototype.read_file = function(file)
  1709. {
  1710. const p = this.SearchPath(file);
  1711. if(p.id === -1)
  1712. {
  1713. return Promise.resolve(null);
  1714. }
  1715. const inode = this.GetInode(p.id);
  1716. return this.Read(p.id, 0, inode.size);
  1717. };