Superblock.js 29 KB

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