Superblock.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Superblock = void 0;
  4. const path_1 = require("@jsonjoy.com/fs-node-builtins/lib/path");
  5. const Node_1 = require("./Node");
  6. const Link_1 = require("./Link");
  7. const File_1 = require("./File");
  8. const buffer_1 = require("@jsonjoy.com/fs-node-builtins/lib/internal/buffer");
  9. const process_1 = require("./process");
  10. const fs_node_utils_1 = require("@jsonjoy.com/fs-node-utils");
  11. const fs_node_utils_2 = require("@jsonjoy.com/fs-node-utils");
  12. const util_1 = require("./util");
  13. const json_1 = require("./json");
  14. const result_1 = require("./result");
  15. const pathSep = path_1.posix ? path_1.posix.sep : path_1.sep;
  16. const pathRelative = path_1.posix ? path_1.posix.relative : path_1.relative;
  17. const pathJoin = path_1.posix ? path_1.posix.join : path_1.join;
  18. const { O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC, O_APPEND, O_DIRECTORY } = fs_node_utils_1.constants;
  19. /**
  20. * Represents a filesystem superblock, which is the root of a virtual
  21. * filesystem in Linux.
  22. * @see https://lxr.linux.no/linux+v3.11.2/include/linux/fs.h#L1242
  23. */
  24. class Superblock {
  25. static fromJSON(json, cwd) {
  26. const vol = new Superblock();
  27. vol.fromJSON(json, cwd);
  28. return vol;
  29. }
  30. static fromNestedJSON(json, cwd) {
  31. const vol = new Superblock();
  32. vol.fromNestedJSON(json, cwd);
  33. return vol;
  34. }
  35. constructor(props = {}) {
  36. // I-node number counter.
  37. this.ino = 0;
  38. // A mapping for i-node numbers to i-nodes (`Node`);
  39. this.inodes = {};
  40. // List of released i-node numbers, for reuse.
  41. this.releasedInos = [];
  42. // A mapping for file descriptors to `File`s.
  43. this.fds = {};
  44. // A list of reusable (opened and closed) file descriptors, that should be
  45. // used first before creating a new file descriptor.
  46. this.releasedFds = [];
  47. // Max number of open files.
  48. this.maxFiles = 10000;
  49. // Current number of open files.
  50. this.openFiles = 0;
  51. this.open = (filename, flagsNum, modeNum, resolveSymlinks = true) => {
  52. const file = this.openFile(filename, flagsNum, modeNum, resolveSymlinks);
  53. if (!file)
  54. throw (0, util_1.createError)("ENOENT" /* ERROR_CODE.ENOENT */, 'open', filename);
  55. return file.fd;
  56. };
  57. this.writeFile = (id, buf, flagsNum, modeNum) => {
  58. const isUserFd = typeof id === 'number';
  59. let fd;
  60. if (isUserFd)
  61. fd = id;
  62. else
  63. fd = this.open((0, util_1.pathToFilename)(id), flagsNum, modeNum);
  64. let offset = 0;
  65. let length = buf.length;
  66. let position = flagsNum & O_APPEND ? undefined : 0;
  67. try {
  68. while (length > 0) {
  69. const written = this.write(fd, buf, offset, length, position);
  70. offset += written;
  71. length -= written;
  72. if (position !== undefined)
  73. position += written;
  74. }
  75. }
  76. finally {
  77. if (!isUserFd)
  78. this.close(fd);
  79. }
  80. };
  81. this.read = (fd, buffer, offset, length, position) => {
  82. if (buffer.byteLength < length) {
  83. throw (0, util_1.createError)("ERR_OUT_OF_RANGE" /* ERROR_CODE.ERR_OUT_OF_RANGE */, 'read', undefined, undefined, RangeError);
  84. }
  85. const file = this.getFileByFdOrThrow(fd);
  86. if (file.node.isSymlink()) {
  87. throw (0, util_1.createError)("EPERM" /* ERROR_CODE.EPERM */, 'read', file.link.getPath());
  88. }
  89. return file.read(buffer, Number(offset), Number(length), position === -1 || typeof position !== 'number' ? undefined : position);
  90. };
  91. this.readv = (fd, buffers, position) => {
  92. const file = this.getFileByFdOrThrow(fd);
  93. let p = position ?? undefined;
  94. if (p === -1)
  95. p = undefined;
  96. let bytesRead = 0;
  97. for (const buffer of buffers) {
  98. const bytes = file.read(buffer, 0, buffer.byteLength, p);
  99. p = undefined;
  100. bytesRead += bytes;
  101. if (bytes < buffer.byteLength)
  102. break;
  103. }
  104. return bytesRead;
  105. };
  106. this.link = (filename1, filename2) => {
  107. let link1;
  108. try {
  109. link1 = this.getLinkOrThrow(filename1, 'link');
  110. }
  111. catch (err) {
  112. if (err.code)
  113. err = (0, util_1.createError)(err.code, 'link', filename1, filename2);
  114. throw err;
  115. }
  116. const dirname2 = (0, path_1.dirname)(filename2);
  117. let dir2;
  118. try {
  119. dir2 = this.getLinkOrThrow(dirname2, 'link');
  120. }
  121. catch (err) {
  122. // Augment error with filename1
  123. if (err.code)
  124. err = (0, util_1.createError)(err.code, 'link', filename1, filename2);
  125. throw err;
  126. }
  127. const name = (0, path_1.basename)(filename2);
  128. if (dir2.getChild(name))
  129. throw (0, util_1.createError)("EEXIST" /* ERROR_CODE.EEXIST */, 'link', filename1, filename2);
  130. const node = link1.getNode();
  131. node.nlink++;
  132. dir2.createChild(name, node);
  133. };
  134. this.unlink = (filename) => {
  135. const link = this.getLinkOrThrow(filename, 'unlink');
  136. // TODO: Check if it is file, dir, other...
  137. if (link.length)
  138. throw Error('Dir not empty...');
  139. this.deleteLink(link);
  140. const node = link.getNode();
  141. node.nlink--;
  142. // When all hard links to i-node are deleted, remove the i-node, too.
  143. if (node.nlink <= 0) {
  144. this.deleteNode(node);
  145. }
  146. };
  147. this.symlink = (targetFilename, pathFilename) => {
  148. const pathSteps = (0, util_1.filenameToSteps)(pathFilename);
  149. // Check if directory exists, where we about to create a symlink.
  150. let dirLink;
  151. try {
  152. dirLink = this.getLinkParentAsDirOrThrow(pathSteps);
  153. }
  154. catch (err) {
  155. // Catch error to populate with the correct fields - getLinkParentAsDirOrThrow won't be aware of the second path
  156. if (err.code)
  157. err = (0, util_1.createError)(err.code, 'symlink', targetFilename, pathFilename);
  158. throw err;
  159. }
  160. const name = pathSteps[pathSteps.length - 1];
  161. // Check if new file already exists.
  162. if (dirLink.getChild(name))
  163. throw (0, util_1.createError)("EEXIST" /* ERROR_CODE.EEXIST */, 'symlink', targetFilename, pathFilename);
  164. // Check permissions on the path where we are creating the symlink.
  165. // Note we're not checking permissions on the target path: It is not an error to create a symlink to a
  166. // non-existent or inaccessible target
  167. const node = dirLink.getNode();
  168. if (!node.canExecute() || !node.canWrite())
  169. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'symlink', targetFilename, pathFilename);
  170. // Create symlink.
  171. const symlink = dirLink.createChild(name);
  172. symlink.getNode().makeSymlink(targetFilename);
  173. return symlink;
  174. };
  175. this.rename = (oldPathFilename, newPathFilename) => {
  176. let link;
  177. try {
  178. link = this.getResolvedLinkOrThrow(oldPathFilename);
  179. }
  180. catch (err) {
  181. // Augment err with newPathFilename
  182. if (err.code)
  183. err = (0, util_1.createError)(err.code, 'rename', oldPathFilename, newPathFilename);
  184. throw err;
  185. }
  186. // TODO: Check if it is directory, if non-empty, we cannot move it, right?
  187. // Check directory exists for the new location.
  188. let newPathDirLink;
  189. try {
  190. newPathDirLink = this.getLinkParentAsDirOrThrow(newPathFilename);
  191. }
  192. catch (err) {
  193. // Augment error with oldPathFilename
  194. if (err.code)
  195. err = (0, util_1.createError)(err.code, 'rename', oldPathFilename, newPathFilename);
  196. throw err;
  197. }
  198. // TODO: Also treat cases with directories and symbolic links.
  199. // TODO: See: http://man7.org/linux/man-pages/man2/rename.2.html
  200. // Remove hard link from old folder.
  201. const oldLinkParent = link.parent;
  202. if (!oldLinkParent)
  203. throw (0, util_1.createError)("EINVAL" /* ERROR_CODE.EINVAL */, 'rename', oldPathFilename, newPathFilename);
  204. // Check we have access and write permissions in both places
  205. const oldParentNode = oldLinkParent.getNode();
  206. const newPathDirNode = newPathDirLink.getNode();
  207. if (!oldParentNode.canExecute() ||
  208. !oldParentNode.canWrite() ||
  209. !newPathDirNode.canExecute() ||
  210. !newPathDirNode.canWrite()) {
  211. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'rename', oldPathFilename, newPathFilename);
  212. }
  213. oldLinkParent.deleteChild(link);
  214. // Rename should overwrite the new path, if that exists.
  215. const name = (0, path_1.basename)(newPathFilename);
  216. link.name = name;
  217. link.steps = [...newPathDirLink.steps, name];
  218. newPathDirLink.setChild(link.getName(), link);
  219. };
  220. this.mkdir = (filename, modeNum) => {
  221. const steps = (0, util_1.filenameToSteps)(filename);
  222. // This will throw if user tries to create root dir `fs.mkdirSync('/')`.
  223. if (!steps.length)
  224. throw (0, util_1.createError)("EEXIST" /* ERROR_CODE.EEXIST */, 'mkdir', filename);
  225. const dir = this.getLinkParentAsDirOrThrow(filename, 'mkdir');
  226. // Check path already exists.
  227. const name = steps[steps.length - 1];
  228. if (dir.getChild(name))
  229. throw (0, util_1.createError)("EEXIST" /* ERROR_CODE.EEXIST */, 'mkdir', filename);
  230. const node = dir.getNode();
  231. if (!node.canWrite() || !node.canExecute())
  232. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'mkdir', filename);
  233. dir.createChild(name, this.createNode(fs_node_utils_1.constants.S_IFDIR | modeNum));
  234. };
  235. /**
  236. * Creates directory tree recursively.
  237. */
  238. this.mkdirp = (filename, modeNum) => {
  239. let created = false;
  240. const steps = (0, util_1.filenameToSteps)(filename);
  241. let curr = null;
  242. let i = steps.length;
  243. // Find the longest subpath of filename that still exists:
  244. for (i = steps.length; i >= 0; i--) {
  245. curr = this.getResolvedLink(steps.slice(0, i));
  246. if (curr)
  247. break;
  248. }
  249. if (!curr) {
  250. curr = this.root;
  251. i = 0;
  252. }
  253. // curr is now the last directory that still exists.
  254. // (If none of them existed, curr is the root.)
  255. // Check access the lazy way:
  256. curr = this.getResolvedLinkOrThrow(path_1.sep + steps.slice(0, i).join(path_1.sep), 'mkdir');
  257. // Start creating directories:
  258. for (i; i < steps.length; i++) {
  259. const node = curr.getNode();
  260. if (node.isDirectory()) {
  261. // Check we have permissions
  262. if (!node.canExecute() || !node.canWrite())
  263. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'mkdir', filename);
  264. }
  265. else {
  266. throw (0, util_1.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, 'mkdir', filename);
  267. }
  268. created = true;
  269. curr = curr.createChild(steps[i], this.createNode(fs_node_utils_1.constants.S_IFDIR | modeNum));
  270. }
  271. return created ? filename : undefined;
  272. };
  273. this.rmdir = (filename, recursive = false) => {
  274. const link = this.getLinkAsDirOrThrow(filename, 'rmdir');
  275. if (link.length && !recursive)
  276. throw (0, util_1.createError)("ENOTEMPTY" /* ERROR_CODE.ENOTEMPTY */, 'rmdir', filename);
  277. this.deleteLink(link);
  278. };
  279. this.rm = (filename, force = false, recursive = false) => {
  280. // "stat" is used to match Node's native error message.
  281. let link;
  282. try {
  283. link = this.getResolvedLinkOrThrow(filename, 'stat');
  284. }
  285. catch (err) {
  286. // Silently ignore missing paths if force option is true
  287. if (err.code === "ENOENT" /* ERROR_CODE.ENOENT */ && force)
  288. return;
  289. else
  290. throw err;
  291. }
  292. if (link.getNode().isDirectory() && !recursive)
  293. throw (0, util_1.createError)("ERR_FS_EISDIR" /* ERROR_CODE.ERR_FS_EISDIR */, 'rm', filename);
  294. if (!link.parent?.getNode().canWrite())
  295. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'rm', filename);
  296. this.deleteLink(link);
  297. };
  298. this.close = (fd) => {
  299. (0, util_1.validateFd)(fd);
  300. const file = this.getFileByFdOrThrow(fd, 'close');
  301. this.closeFile(file);
  302. };
  303. const root = this.createLink();
  304. root.setNode(this.createNode(fs_node_utils_1.constants.S_IFDIR | 0o777));
  305. root.setChild('.', root);
  306. root.getNode().nlink++;
  307. root.setChild('..', root);
  308. root.getNode().nlink++;
  309. this.root = root;
  310. }
  311. createLink(parent, name, isDirectory = false, mode) {
  312. if (!parent) {
  313. return new Link_1.Link(this, void 0, '');
  314. }
  315. if (!name) {
  316. throw new Error('createLink: name cannot be empty');
  317. }
  318. // If no explicit permission is provided, use defaults based on type
  319. const finalPerm = mode ?? (isDirectory ? 0o777 : 0o666);
  320. // To prevent making a breaking change, `mode` can also just be a permission number
  321. // and the file type is set based on `isDirectory`
  322. const hasFileType = mode && mode & fs_node_utils_1.constants.S_IFMT;
  323. const modeType = hasFileType ? mode & fs_node_utils_1.constants.S_IFMT : isDirectory ? fs_node_utils_1.constants.S_IFDIR : fs_node_utils_1.constants.S_IFREG;
  324. const finalMode = (finalPerm & ~fs_node_utils_1.constants.S_IFMT) | modeType;
  325. return parent.createChild(name, this.createNode(finalMode));
  326. }
  327. deleteLink(link) {
  328. const parent = link.parent;
  329. if (parent) {
  330. parent.deleteChild(link);
  331. return true;
  332. }
  333. return false;
  334. }
  335. newInoNumber() {
  336. const releasedFd = this.releasedInos.pop();
  337. if (releasedFd)
  338. return releasedFd;
  339. else {
  340. this.ino = (this.ino + 1) % 0xffffffff;
  341. return this.ino;
  342. }
  343. }
  344. newFdNumber() {
  345. const releasedFd = this.releasedFds.pop();
  346. return typeof releasedFd === 'number' ? releasedFd : Superblock.fd--;
  347. }
  348. createNode(mode) {
  349. const node = new Node_1.Node(this.newInoNumber(), mode);
  350. this.inodes[node.ino] = node;
  351. return node;
  352. }
  353. deleteNode(node) {
  354. node.del();
  355. delete this.inodes[node.ino];
  356. this.releasedInos.push(node.ino);
  357. }
  358. walk(stepsOrFilenameOrLink, resolveSymlinks = false, checkExistence = false, checkAccess = false, funcName) {
  359. let steps;
  360. let filename;
  361. if (stepsOrFilenameOrLink instanceof Link_1.Link) {
  362. steps = stepsOrFilenameOrLink.steps;
  363. filename = pathSep + steps.join(pathSep);
  364. }
  365. else if (typeof stepsOrFilenameOrLink === 'string') {
  366. steps = (0, util_1.filenameToSteps)(stepsOrFilenameOrLink);
  367. filename = stepsOrFilenameOrLink;
  368. }
  369. else {
  370. steps = stepsOrFilenameOrLink;
  371. filename = pathSep + steps.join(pathSep);
  372. }
  373. let curr = this.root;
  374. let i = 0;
  375. while (i < steps.length) {
  376. let node = curr.getNode();
  377. // Check access permissions if current link is a directory
  378. if (node.isDirectory()) {
  379. if (checkAccess && !node.canExecute()) {
  380. return (0, result_1.Err)((0, util_1.createStatError)("EACCES" /* ERROR_CODE.EACCES */, funcName, filename));
  381. }
  382. }
  383. else {
  384. if (i < steps.length - 1) {
  385. return (0, result_1.Err)((0, util_1.createStatError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, funcName, filename));
  386. }
  387. }
  388. curr = curr.getChild(steps[i]) ?? null;
  389. // Check existence of current link
  390. if (!curr)
  391. if (checkExistence) {
  392. return (0, result_1.Err)((0, util_1.createStatError)("ENOENT" /* ERROR_CODE.ENOENT */, funcName, filename));
  393. }
  394. else {
  395. return (0, result_1.Ok)(null);
  396. }
  397. node = curr?.getNode();
  398. // Resolve symlink if we're resolving all symlinks OR if this is an intermediate path component
  399. // This allows lstat to traverse through symlinks in intermediate directories while not resolving the final component
  400. if (node.isSymlink() && (resolveSymlinks || i < steps.length - 1)) {
  401. const resolvedPath = (0, path_1.isAbsolute)(node.symlink) ? node.symlink : pathJoin((0, path_1.dirname)(curr.getPath()), node.symlink); // Relative to symlink's parent
  402. steps = (0, util_1.filenameToSteps)(resolvedPath).concat(steps.slice(i + 1));
  403. curr = this.root;
  404. i = 0;
  405. continue;
  406. }
  407. // After resolving symlinks, check if it's not a directory and we still have more steps
  408. // This handles the case where we try to traverse through a file
  409. // Only do this check when we're doing filesystem operations (checkExistence = true)
  410. if (checkExistence && !node.isDirectory() && i < steps.length - 1) {
  411. // On Windows, use ENOENT for consistency with Node.js behavior
  412. // On other platforms, use ENOTDIR which is more semantically correct
  413. const errorCode = process_1.default.platform === 'win32' ? "ENOENT" /* ERROR_CODE.ENOENT */ : "ENOTDIR" /* ERROR_CODE.ENOTDIR */;
  414. return (0, result_1.Err)((0, util_1.createStatError)(errorCode, funcName, filename));
  415. }
  416. i++;
  417. }
  418. return (0, result_1.Ok)(curr);
  419. }
  420. // Returns a `Link` (hard link) referenced by path "split" into steps.
  421. getLink(steps) {
  422. const result = this.walk(steps, false, false, false);
  423. if (result.ok) {
  424. return result.value;
  425. }
  426. throw result.err.toError();
  427. }
  428. // Just link `getLink`, but throws a correct user error, if link to found.
  429. getLinkOrThrow(filename, funcName) {
  430. const result = this.walk(filename, false, true, true, funcName);
  431. if (result.ok) {
  432. return result.value;
  433. }
  434. throw result.err.toError();
  435. }
  436. // Just like `getLink`, but also dereference/resolves symbolic links.
  437. getResolvedLink(filenameOrSteps) {
  438. const result = this.walk(filenameOrSteps, true, false, false);
  439. if (result.ok) {
  440. return result.value;
  441. }
  442. throw result.err.toError();
  443. }
  444. /**
  445. * Just like `getLinkOrThrow`, but also dereference/resolves symbolic links.
  446. */
  447. getResolvedLinkOrThrow(filename, funcName) {
  448. const result = this.walk(filename, true, true, true, funcName);
  449. if (result.ok) {
  450. return result.value;
  451. }
  452. throw result.err.toError();
  453. }
  454. getResolvedLinkResult(filename, funcName) {
  455. const result = this.walk(filename, true, true, true, funcName);
  456. if (result.ok) {
  457. return (0, result_1.Ok)(result.value);
  458. }
  459. return result;
  460. }
  461. resolveSymlinks(link) {
  462. return this.getResolvedLink(link.steps.slice(1));
  463. }
  464. /**
  465. * Just like `getLinkOrThrow`, but also verifies that the link is a directory.
  466. */
  467. getLinkAsDirOrThrow(filename, funcName) {
  468. const link = this.getLinkOrThrow(filename, funcName);
  469. if (!link.getNode().isDirectory())
  470. throw (0, util_1.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, funcName, filename);
  471. return link;
  472. }
  473. // Get the immediate parent directory of the link.
  474. getLinkParent(steps) {
  475. return this.getLink(steps.slice(0, -1));
  476. }
  477. getLinkParentAsDirOrThrow(filenameOrSteps, funcName) {
  478. const steps = (filenameOrSteps instanceof Array ? filenameOrSteps : (0, util_1.filenameToSteps)(filenameOrSteps)).slice(0, -1);
  479. const filename = pathSep + steps.join(pathSep);
  480. const link = this.getLinkOrThrow(filename, funcName);
  481. if (!link.getNode().isDirectory())
  482. throw (0, util_1.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, funcName, filename);
  483. return link;
  484. }
  485. getFileByFd(fd) {
  486. return this.fds[String(fd)];
  487. }
  488. getFileByFdOrThrow(fd, funcName) {
  489. if (!(0, util_1.isFd)(fd))
  490. throw TypeError(fs_node_utils_2.ERRSTR.FD);
  491. const file = this.getFileByFd(fd);
  492. if (!file)
  493. throw (0, util_1.createError)("EBADF" /* ERROR_CODE.EBADF */, funcName);
  494. return file;
  495. }
  496. _toJSON(link = this.root, json = {}, path, asBuffer) {
  497. let isEmpty = true;
  498. let children = link.children;
  499. if (link.getNode().isFile()) {
  500. children = new Map([[link.getName(), link.parent.getChild(link.getName())]]);
  501. link = link.parent;
  502. }
  503. for (const name of children.keys()) {
  504. if (name === '.' || name === '..') {
  505. continue;
  506. }
  507. isEmpty = false;
  508. const child = link.getChild(name);
  509. if (!child) {
  510. throw new Error('_toJSON: unexpected undefined');
  511. }
  512. const node = child.getNode();
  513. if (node.isFile()) {
  514. let filename = child.getPath();
  515. if (path)
  516. filename = pathRelative(path, filename);
  517. json[filename] = asBuffer ? node.getBuffer() : node.getString();
  518. }
  519. else if (node.isDirectory()) {
  520. this._toJSON(child, json, path, asBuffer);
  521. }
  522. }
  523. let dirPath = link.getPath();
  524. if (path)
  525. dirPath = pathRelative(path, dirPath);
  526. if (dirPath && isEmpty) {
  527. json[dirPath] = null;
  528. }
  529. return json;
  530. }
  531. toJSON(paths, json = {}, isRelative = false, asBuffer = false) {
  532. const links = [];
  533. if (paths) {
  534. if (!Array.isArray(paths))
  535. paths = [paths];
  536. for (const path of paths) {
  537. const filename = (0, util_1.pathToFilename)(path);
  538. const link = this.getResolvedLink(filename);
  539. if (!link)
  540. continue;
  541. links.push(link);
  542. }
  543. }
  544. else {
  545. links.push(this.root);
  546. }
  547. if (!links.length)
  548. return json;
  549. for (const link of links)
  550. this._toJSON(link, json, isRelative ? link.getPath() : '', asBuffer);
  551. return json;
  552. }
  553. // TODO: `cwd` should probably not invoke `process.cwd()`.
  554. fromJSON(json, cwd = process_1.default.cwd()) {
  555. for (let filename in json) {
  556. const data = json[filename];
  557. filename = (0, util_1.resolve)(filename, cwd);
  558. if (typeof data === 'string' || data instanceof buffer_1.Buffer) {
  559. const dir = (0, path_1.dirname)(filename);
  560. this.mkdirp(dir, 511 /* MODE.DIR */);
  561. const buffer = (0, util_1.dataToBuffer)(data);
  562. this.writeFile(filename, buffer, fs_node_utils_2.FLAGS.w, 438 /* MODE.DEFAULT */);
  563. }
  564. else {
  565. this.mkdirp(filename, 511 /* MODE.DIR */);
  566. }
  567. }
  568. }
  569. fromNestedJSON(json, cwd) {
  570. this.fromJSON((0, json_1.flattenJSON)(json), cwd);
  571. }
  572. reset() {
  573. this.ino = 0;
  574. this.inodes = {};
  575. this.releasedInos = [];
  576. this.fds = {};
  577. this.releasedFds = [];
  578. this.openFiles = 0;
  579. this.root = this.createLink();
  580. this.root.setNode(this.createNode(fs_node_utils_1.constants.S_IFDIR | 0o777));
  581. }
  582. // Legacy interface
  583. mountSync(mountpoint, json) {
  584. this.fromJSON(json, mountpoint);
  585. }
  586. openLink(link, flagsNum, resolveSymlinks = true) {
  587. if (this.openFiles >= this.maxFiles) {
  588. // Too many open files.
  589. throw (0, util_1.createError)("EMFILE" /* ERROR_CODE.EMFILE */, 'open', link.getPath());
  590. }
  591. // Resolve symlinks.
  592. //
  593. // @TODO: This should be superfluous. This method is only ever called by openFile(), which does its own symlink resolution
  594. // prior to calling.
  595. let realLink = link;
  596. if (resolveSymlinks)
  597. realLink = this.getResolvedLinkOrThrow(link.getPath(), 'open');
  598. const node = realLink.getNode();
  599. // Check whether node is a directory
  600. if (node.isDirectory()) {
  601. if ((flagsNum & (O_RDONLY | O_RDWR | O_WRONLY)) !== O_RDONLY)
  602. throw (0, util_1.createError)("EISDIR" /* ERROR_CODE.EISDIR */, 'open', link.getPath());
  603. }
  604. else {
  605. if (flagsNum & O_DIRECTORY)
  606. throw (0, util_1.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, 'open', link.getPath());
  607. }
  608. // Check node permissions
  609. // For read access: check if flags are O_RDONLY or O_RDWR (i.e., not only O_WRONLY)
  610. if ((flagsNum & (O_RDONLY | O_RDWR | O_WRONLY)) !== O_WRONLY) {
  611. if (!node.canRead()) {
  612. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'open', link.getPath());
  613. }
  614. }
  615. // For write access: check if flags are O_WRONLY or O_RDWR
  616. if (flagsNum & (O_WRONLY | O_RDWR)) {
  617. if (!node.canWrite()) {
  618. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'open', link.getPath());
  619. }
  620. }
  621. const file = new File_1.File(link, node, flagsNum, this.newFdNumber());
  622. this.fds[file.fd] = file;
  623. this.openFiles++;
  624. if (flagsNum & O_TRUNC)
  625. file.truncate();
  626. return file;
  627. }
  628. openFile(filename, flagsNum, modeNum, resolveSymlinks = true) {
  629. const steps = (0, util_1.filenameToSteps)(filename);
  630. let link;
  631. try {
  632. link = resolveSymlinks ? this.getResolvedLinkOrThrow(filename, 'open') : this.getLinkOrThrow(filename, 'open');
  633. // Check if file already existed when trying to create it exclusively (O_CREAT and O_EXCL flags are set).
  634. // This is an error, see https://pubs.opengroup.org/onlinepubs/009695399/functions/open.html:
  635. // "If O_CREAT and O_EXCL are set, open() shall fail if the file exists."
  636. if (link && flagsNum & O_CREAT && flagsNum & O_EXCL)
  637. throw (0, util_1.createError)("EEXIST" /* ERROR_CODE.EEXIST */, 'open', filename);
  638. }
  639. catch (err) {
  640. // Try creating a new file, if it does not exist and O_CREAT flag is set.
  641. // Note that this will still throw if the ENOENT came from one of the
  642. // intermediate directories instead of the file itself.
  643. if (err.code === "ENOENT" /* ERROR_CODE.ENOENT */ && flagsNum & O_CREAT) {
  644. const dirName = (0, path_1.dirname)(filename);
  645. const dirLink = this.getResolvedLinkOrThrow(dirName);
  646. const dirNode = dirLink.getNode();
  647. // Check that the place we create the new file is actually a directory and that we are allowed to do so:
  648. if (!dirNode.isDirectory())
  649. throw (0, util_1.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, 'open', filename);
  650. if (!dirNode.canExecute() || !dirNode.canWrite())
  651. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'open', filename);
  652. // This is a difference to the original implementation, which would simply not create a file unless modeNum was specified.
  653. // However, current Node versions will default to 0o666.
  654. modeNum ?? (modeNum = 0o666);
  655. link = this.createLink(dirLink, steps[steps.length - 1], false, modeNum);
  656. }
  657. else
  658. throw err;
  659. }
  660. if (link)
  661. return this.openLink(link, flagsNum, resolveSymlinks);
  662. throw (0, util_1.createError)("ENOENT" /* ERROR_CODE.ENOENT */, 'open', filename);
  663. }
  664. closeFile(file) {
  665. if (!this.fds[file.fd])
  666. return;
  667. this.openFiles--;
  668. delete this.fds[file.fd];
  669. this.releasedFds.push(file.fd);
  670. }
  671. write(fd, buf, offset, length, position) {
  672. const file = this.getFileByFdOrThrow(fd, 'write');
  673. if (file.node.isSymlink()) {
  674. throw (0, util_1.createError)("EBADF" /* ERROR_CODE.EBADF */, 'write', file.link.getPath());
  675. }
  676. return file.write(buf, offset, length, position === -1 || typeof position !== 'number' ? undefined : position);
  677. }
  678. }
  679. exports.Superblock = Superblock;
  680. /**
  681. * Global file descriptor counter. UNIX file descriptors start from 0 and go sequentially
  682. * up, so here, in order not to conflict with them, we choose some big number and descrease
  683. * the file descriptor of every new opened file.
  684. * @type {number}
  685. * @todo This should not be static, right?
  686. */
  687. Superblock.fd = 0x7fffffff;
  688. //# sourceMappingURL=Superblock.js.map