Superblock.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  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, opts) {
  26. const vol = new Superblock(opts);
  27. vol.fromJSON(json, cwd);
  28. return vol;
  29. }
  30. static fromNestedJSON(json, cwd, opts) {
  31. const vol = new Superblock(opts);
  32. vol.fromNestedJSON(json, cwd);
  33. return vol;
  34. }
  35. constructor(opts = {}) {
  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. this.process = opts.process ?? process_1.default;
  304. const root = this.createLink();
  305. root.setNode(this.createNode(fs_node_utils_1.constants.S_IFDIR | 0o777));
  306. root.setChild('.', root);
  307. root.getNode().nlink++;
  308. root.setChild('..', root);
  309. root.getNode().nlink++;
  310. this.root = root;
  311. }
  312. createLink(parent, name, isDirectory = false, mode) {
  313. if (!parent) {
  314. return new Link_1.Link(this, void 0, '');
  315. }
  316. if (!name) {
  317. throw new Error('createLink: name cannot be empty');
  318. }
  319. // If no explicit permission is provided, use defaults based on type
  320. const finalPerm = mode ?? (isDirectory ? 0o777 : 0o666);
  321. // To prevent making a breaking change, `mode` can also just be a permission number
  322. // and the file type is set based on `isDirectory`
  323. const hasFileType = mode && mode & fs_node_utils_1.constants.S_IFMT;
  324. 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;
  325. const finalMode = (finalPerm & ~fs_node_utils_1.constants.S_IFMT) | modeType;
  326. return parent.createChild(name, this.createNode(finalMode));
  327. }
  328. deleteLink(link) {
  329. const parent = link.parent;
  330. if (parent) {
  331. parent.deleteChild(link);
  332. return true;
  333. }
  334. return false;
  335. }
  336. newInoNumber() {
  337. const releasedFd = this.releasedInos.pop();
  338. if (releasedFd)
  339. return releasedFd;
  340. else {
  341. this.ino = (this.ino + 1) % 0xffffffff;
  342. return this.ino;
  343. }
  344. }
  345. newFdNumber() {
  346. const releasedFd = this.releasedFds.pop();
  347. return typeof releasedFd === 'number' ? releasedFd : Superblock.fd--;
  348. }
  349. createNode(mode) {
  350. const uid = this.process.getuid?.() ?? 0;
  351. const gid = this.process.getgid?.() ?? 0;
  352. const node = new Node_1.Node(this.newInoNumber(), mode, uid, gid);
  353. this.inodes[node.ino] = node;
  354. return node;
  355. }
  356. deleteNode(node) {
  357. node.del();
  358. delete this.inodes[node.ino];
  359. this.releasedInos.push(node.ino);
  360. }
  361. walk(stepsOrFilenameOrLink, resolveSymlinks = false, checkExistence = false, checkAccess = false, funcName) {
  362. let steps;
  363. let filename;
  364. if (stepsOrFilenameOrLink instanceof Link_1.Link) {
  365. steps = stepsOrFilenameOrLink.steps;
  366. filename = pathSep + steps.join(pathSep);
  367. }
  368. else if (typeof stepsOrFilenameOrLink === 'string') {
  369. steps = (0, util_1.filenameToSteps)(stepsOrFilenameOrLink);
  370. filename = stepsOrFilenameOrLink;
  371. }
  372. else {
  373. steps = stepsOrFilenameOrLink;
  374. filename = pathSep + steps.join(pathSep);
  375. }
  376. let curr = this.root;
  377. let i = 0;
  378. const uid = this.process.getuid?.() ?? 0;
  379. const gid = this.process.getgid?.() ?? 0;
  380. while (i < steps.length) {
  381. let node = curr.getNode();
  382. // Check access permissions if current link is a directory
  383. if (node.isDirectory()) {
  384. if (checkAccess && !node.canExecute(uid, gid)) {
  385. return (0, result_1.Err)((0, util_1.createStatError)("EACCES" /* ERROR_CODE.EACCES */, funcName, filename));
  386. }
  387. }
  388. else {
  389. if (i < steps.length - 1) {
  390. return (0, result_1.Err)((0, util_1.createStatError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, funcName, filename));
  391. }
  392. }
  393. curr = curr.getChild(steps[i]) ?? null;
  394. // Check existence of current link
  395. if (!curr)
  396. if (checkExistence) {
  397. return (0, result_1.Err)((0, util_1.createStatError)("ENOENT" /* ERROR_CODE.ENOENT */, funcName, filename));
  398. }
  399. else {
  400. return (0, result_1.Ok)(null);
  401. }
  402. node = curr?.getNode();
  403. // Resolve symlink if we're resolving all symlinks OR if this is an intermediate path component
  404. // This allows lstat to traverse through symlinks in intermediate directories while not resolving the final component
  405. if (node.isSymlink() && (resolveSymlinks || i < steps.length - 1)) {
  406. const resolvedPath = (0, path_1.isAbsolute)(node.symlink) ? node.symlink : pathJoin((0, path_1.dirname)(curr.getPath()), node.symlink); // Relative to symlink's parent
  407. steps = (0, util_1.filenameToSteps)(resolvedPath).concat(steps.slice(i + 1));
  408. curr = this.root;
  409. i = 0;
  410. continue;
  411. }
  412. // After resolving symlinks, check if it's not a directory and we still have more steps
  413. // This handles the case where we try to traverse through a file
  414. // Only do this check when we're doing filesystem operations (checkExistence = true)
  415. if (checkExistence && !node.isDirectory() && i < steps.length - 1) {
  416. // On Windows, use ENOENT for consistency with Node.js behavior
  417. // On other platforms, use ENOTDIR which is more semantically correct
  418. const errorCode = this.process.platform === 'win32' ? "ENOENT" /* ERROR_CODE.ENOENT */ : "ENOTDIR" /* ERROR_CODE.ENOTDIR */;
  419. return (0, result_1.Err)((0, util_1.createStatError)(errorCode, funcName, filename));
  420. }
  421. i++;
  422. }
  423. return (0, result_1.Ok)(curr);
  424. }
  425. // Returns a `Link` (hard link) referenced by path "split" into steps.
  426. getLink(steps) {
  427. const result = this.walk(steps, false, false, false);
  428. if (result.ok) {
  429. return result.value;
  430. }
  431. throw result.err.toError();
  432. }
  433. // Just link `getLink`, but throws a correct user error, if link to found.
  434. getLinkOrThrow(filename, funcName) {
  435. const result = this.walk(filename, false, true, true, funcName);
  436. if (result.ok) {
  437. return result.value;
  438. }
  439. throw result.err.toError();
  440. }
  441. // Just like `getLink`, but also dereference/resolves symbolic links.
  442. getResolvedLink(filenameOrSteps) {
  443. const result = this.walk(filenameOrSteps, true, false, false);
  444. if (result.ok) {
  445. return result.value;
  446. }
  447. throw result.err.toError();
  448. }
  449. /**
  450. * Just like `getLinkOrThrow`, but also dereference/resolves symbolic links.
  451. */
  452. getResolvedLinkOrThrow(filename, funcName) {
  453. const result = this.walk(filename, true, true, true, funcName);
  454. if (result.ok) {
  455. return result.value;
  456. }
  457. throw result.err.toError();
  458. }
  459. getResolvedLinkResult(filename, funcName) {
  460. const result = this.walk(filename, true, true, true, funcName);
  461. if (result.ok) {
  462. return (0, result_1.Ok)(result.value);
  463. }
  464. return result;
  465. }
  466. resolveSymlinks(link) {
  467. return this.getResolvedLink(link.steps.slice(1));
  468. }
  469. /**
  470. * Just like `getLinkOrThrow`, but also verifies that the link is a directory.
  471. */
  472. getLinkAsDirOrThrow(filename, funcName) {
  473. const link = this.getLinkOrThrow(filename, funcName);
  474. if (!link.getNode().isDirectory())
  475. throw (0, util_1.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, funcName, filename);
  476. return link;
  477. }
  478. // Get the immediate parent directory of the link.
  479. getLinkParent(steps) {
  480. return this.getLink(steps.slice(0, -1));
  481. }
  482. getLinkParentAsDirOrThrow(filenameOrSteps, funcName) {
  483. const steps = (filenameOrSteps instanceof Array ? filenameOrSteps : (0, util_1.filenameToSteps)(filenameOrSteps)).slice(0, -1);
  484. const filename = pathSep + steps.join(pathSep);
  485. const link = this.getLinkOrThrow(filename, funcName);
  486. if (!link.getNode().isDirectory())
  487. throw (0, util_1.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, funcName, filename);
  488. return link;
  489. }
  490. getFileByFd(fd) {
  491. return this.fds[String(fd)];
  492. }
  493. getFileByFdOrThrow(fd, funcName) {
  494. if (!(0, util_1.isFd)(fd))
  495. throw TypeError(fs_node_utils_2.ERRSTR.FD);
  496. const file = this.getFileByFd(fd);
  497. if (!file)
  498. throw (0, util_1.createError)("EBADF" /* ERROR_CODE.EBADF */, funcName);
  499. return file;
  500. }
  501. _toJSON(link = this.root, json = {}, path, asBuffer) {
  502. let isEmpty = true;
  503. let children = link.children;
  504. if (link.getNode().isFile()) {
  505. children = new Map([[link.getName(), link.parent.getChild(link.getName())]]);
  506. link = link.parent;
  507. }
  508. for (const name of children.keys()) {
  509. if (name === '.' || name === '..') {
  510. continue;
  511. }
  512. isEmpty = false;
  513. const child = link.getChild(name);
  514. if (!child) {
  515. throw new Error('_toJSON: unexpected undefined');
  516. }
  517. const node = child.getNode();
  518. if (node.isFile()) {
  519. let filename = child.getPath();
  520. if (path)
  521. filename = pathRelative(path, filename);
  522. json[filename] = asBuffer ? node.getBuffer() : node.getString();
  523. }
  524. else if (node.isDirectory()) {
  525. this._toJSON(child, json, path, asBuffer);
  526. }
  527. }
  528. let dirPath = link.getPath();
  529. if (path)
  530. dirPath = pathRelative(path, dirPath);
  531. if (dirPath && isEmpty) {
  532. json[dirPath] = null;
  533. }
  534. return json;
  535. }
  536. toJSON(paths, json = {}, isRelative = false, asBuffer = false) {
  537. const links = [];
  538. if (paths) {
  539. if (!Array.isArray(paths))
  540. paths = [paths];
  541. for (const path of paths) {
  542. const filename = (0, util_1.pathToFilename)(path);
  543. const link = this.getResolvedLink(filename);
  544. if (!link)
  545. continue;
  546. links.push(link);
  547. }
  548. }
  549. else {
  550. links.push(this.root);
  551. }
  552. if (!links.length)
  553. return json;
  554. for (const link of links)
  555. this._toJSON(link, json, isRelative ? link.getPath() : '', asBuffer);
  556. return json;
  557. }
  558. fromJSON(json, cwd = this.process.cwd()) {
  559. for (let filename in json) {
  560. const data = json[filename];
  561. filename = (0, util_1.resolve)(filename, cwd);
  562. if (typeof data === 'string' || data instanceof buffer_1.Buffer) {
  563. const dir = (0, path_1.dirname)(filename);
  564. this.mkdirp(dir, 511 /* MODE.DIR */);
  565. const buffer = (0, util_1.dataToBuffer)(data);
  566. this.writeFile(filename, buffer, fs_node_utils_2.FLAGS.w, 438 /* MODE.DEFAULT */);
  567. }
  568. else {
  569. this.mkdirp(filename, 511 /* MODE.DIR */);
  570. }
  571. }
  572. }
  573. fromNestedJSON(json, cwd) {
  574. this.fromJSON((0, json_1.flattenJSON)(json), cwd);
  575. }
  576. reset() {
  577. this.ino = 0;
  578. this.inodes = {};
  579. this.releasedInos = [];
  580. this.fds = {};
  581. this.releasedFds = [];
  582. this.openFiles = 0;
  583. this.root = this.createLink();
  584. this.root.setNode(this.createNode(fs_node_utils_1.constants.S_IFDIR | 0o777));
  585. }
  586. // Legacy interface
  587. mountSync(mountpoint, json) {
  588. this.fromJSON(json, mountpoint);
  589. }
  590. openLink(link, flagsNum, resolveSymlinks = true) {
  591. if (this.openFiles >= this.maxFiles) {
  592. // Too many open files.
  593. throw (0, util_1.createError)("EMFILE" /* ERROR_CODE.EMFILE */, 'open', link.getPath());
  594. }
  595. // Resolve symlinks.
  596. //
  597. // @TODO: This should be superfluous. This method is only ever called by openFile(), which does its own symlink resolution
  598. // prior to calling.
  599. let realLink = link;
  600. if (resolveSymlinks)
  601. realLink = this.getResolvedLinkOrThrow(link.getPath(), 'open');
  602. const node = realLink.getNode();
  603. // Check whether node is a directory
  604. if (node.isDirectory()) {
  605. if ((flagsNum & (O_RDONLY | O_RDWR | O_WRONLY)) !== O_RDONLY)
  606. throw (0, util_1.createError)("EISDIR" /* ERROR_CODE.EISDIR */, 'open', link.getPath());
  607. }
  608. else {
  609. if (flagsNum & O_DIRECTORY)
  610. throw (0, util_1.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, 'open', link.getPath());
  611. }
  612. // Check node permissions
  613. // For read access: check if flags are O_RDONLY or O_RDWR (i.e., not only O_WRONLY)
  614. if ((flagsNum & (O_RDONLY | O_RDWR | O_WRONLY)) !== O_WRONLY) {
  615. if (!node.canRead()) {
  616. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'open', link.getPath());
  617. }
  618. }
  619. // For write access: check if flags are O_WRONLY or O_RDWR
  620. if (flagsNum & (O_WRONLY | O_RDWR)) {
  621. if (!node.canWrite()) {
  622. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'open', link.getPath());
  623. }
  624. }
  625. const file = new File_1.File(link, node, flagsNum, this.newFdNumber());
  626. this.fds[file.fd] = file;
  627. this.openFiles++;
  628. if (flagsNum & O_TRUNC)
  629. file.truncate();
  630. return file;
  631. }
  632. openFile(filename, flagsNum, modeNum, resolveSymlinks = true) {
  633. const steps = (0, util_1.filenameToSteps)(filename);
  634. let link;
  635. try {
  636. link = resolveSymlinks ? this.getResolvedLinkOrThrow(filename, 'open') : this.getLinkOrThrow(filename, 'open');
  637. // Check if file already existed when trying to create it exclusively (O_CREAT and O_EXCL flags are set).
  638. // This is an error, see https://pubs.opengroup.org/onlinepubs/009695399/functions/open.html:
  639. // "If O_CREAT and O_EXCL are set, open() shall fail if the file exists."
  640. if (link && flagsNum & O_CREAT && flagsNum & O_EXCL)
  641. throw (0, util_1.createError)("EEXIST" /* ERROR_CODE.EEXIST */, 'open', filename);
  642. }
  643. catch (err) {
  644. // Try creating a new file, if it does not exist and O_CREAT flag is set.
  645. // Note that this will still throw if the ENOENT came from one of the
  646. // intermediate directories instead of the file itself.
  647. if (err.code === "ENOENT" /* ERROR_CODE.ENOENT */ && flagsNum & O_CREAT) {
  648. const dirName = (0, path_1.dirname)(filename);
  649. const dirLink = this.getResolvedLinkOrThrow(dirName);
  650. const dirNode = dirLink.getNode();
  651. // Check that the place we create the new file is actually a directory and that we are allowed to do so:
  652. if (!dirNode.isDirectory())
  653. throw (0, util_1.createError)("ENOTDIR" /* ERROR_CODE.ENOTDIR */, 'open', filename);
  654. if (!dirNode.canExecute() || !dirNode.canWrite())
  655. throw (0, util_1.createError)("EACCES" /* ERROR_CODE.EACCES */, 'open', filename);
  656. // This is a difference to the original implementation, which would simply not create a file unless modeNum was specified.
  657. // However, current Node versions will default to 0o666.
  658. modeNum ?? (modeNum = 0o666);
  659. link = this.createLink(dirLink, steps[steps.length - 1], false, modeNum);
  660. }
  661. else
  662. throw err;
  663. }
  664. if (link)
  665. return this.openLink(link, flagsNum, resolveSymlinks);
  666. throw (0, util_1.createError)("ENOENT" /* ERROR_CODE.ENOENT */, 'open', filename);
  667. }
  668. closeFile(file) {
  669. if (!this.fds[file.fd])
  670. return;
  671. this.openFiles--;
  672. delete this.fds[file.fd];
  673. this.releasedFds.push(file.fd);
  674. }
  675. write(fd, buf, offset, length, position) {
  676. const file = this.getFileByFdOrThrow(fd, 'write');
  677. if (file.node.isSymlink()) {
  678. throw (0, util_1.createError)("EBADF" /* ERROR_CODE.EBADF */, 'write', file.link.getPath());
  679. }
  680. return file.write(buf, offset, length, position === -1 || typeof position !== 'number' ? undefined : position);
  681. }
  682. }
  683. exports.Superblock = Superblock;
  684. /**
  685. * Global file descriptor counter. UNIX file descriptors start from 0 and go sequentially
  686. * up, so here, in order not to conflict with them, we choose some big number and descrease
  687. * the file descriptor of every new opened file.
  688. * @type {number}
  689. * @todo This should not be static, right?
  690. */
  691. Superblock.fd = 0x7fffffff;
  692. //# sourceMappingURL=Superblock.js.map