ZipFS.js 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.ZipFS = exports.makeEmptyArchive = exports.DEFAULT_COMPRESSION_LEVEL = void 0;
  4. const tslib_1 = require("tslib");
  5. const fs_1 = require("fs");
  6. const stream_1 = require("stream");
  7. const util_1 = require("util");
  8. const zlib_1 = tslib_1.__importDefault(require("zlib"));
  9. const FakeFS_1 = require("./FakeFS");
  10. const NodeFS_1 = require("./NodeFS");
  11. const opendir_1 = require("./algorithms/opendir");
  12. const watchFile_1 = require("./algorithms/watchFile");
  13. const constants_1 = require("./constants");
  14. const errors = tslib_1.__importStar(require("./errors"));
  15. const path_1 = require("./path");
  16. const statUtils = tslib_1.__importStar(require("./statUtils"));
  17. exports.DEFAULT_COMPRESSION_LEVEL = `mixed`;
  18. function toUnixTimestamp(time) {
  19. if (typeof time === `string` && String(+time) === time)
  20. return +time;
  21. if (typeof time === `number` && Number.isFinite(time)) {
  22. if (time < 0) {
  23. return Date.now() / 1000;
  24. }
  25. else {
  26. return time;
  27. }
  28. }
  29. // convert to 123.456 UNIX timestamp
  30. if (util_1.types.isDate(time))
  31. return time.getTime() / 1000;
  32. throw new Error(`Invalid time`);
  33. }
  34. function makeEmptyArchive() {
  35. return Buffer.from([
  36. 0x50, 0x4B, 0x05, 0x06,
  37. 0x00, 0x00, 0x00, 0x00,
  38. 0x00, 0x00, 0x00, 0x00,
  39. 0x00, 0x00, 0x00, 0x00,
  40. 0x00, 0x00, 0x00, 0x00,
  41. 0x00, 0x00,
  42. ]);
  43. }
  44. exports.makeEmptyArchive = makeEmptyArchive;
  45. class ZipFS extends FakeFS_1.BasePortableFakeFS {
  46. constructor(source, opts) {
  47. super();
  48. this.lzSource = null;
  49. this.listings = new Map();
  50. this.entries = new Map();
  51. /**
  52. * A cache of indices mapped to file sources.
  53. * Populated by `setFileSource` calls.
  54. * Required for supporting read after write.
  55. */
  56. this.fileSources = new Map();
  57. this.fds = new Map();
  58. this.nextFd = 0;
  59. this.ready = false;
  60. this.readOnly = false;
  61. this.libzip = opts.libzip;
  62. const pathOptions = opts;
  63. this.level = typeof pathOptions.level !== `undefined`
  64. ? pathOptions.level
  65. : exports.DEFAULT_COMPRESSION_LEVEL;
  66. source !== null && source !== void 0 ? source : (source = makeEmptyArchive());
  67. if (typeof source === `string`) {
  68. const { baseFs = new NodeFS_1.NodeFS() } = pathOptions;
  69. this.baseFs = baseFs;
  70. this.path = source;
  71. }
  72. else {
  73. this.path = null;
  74. this.baseFs = null;
  75. }
  76. if (opts.stats) {
  77. this.stats = opts.stats;
  78. }
  79. else {
  80. if (typeof source === `string`) {
  81. try {
  82. this.stats = this.baseFs.statSync(source);
  83. }
  84. catch (error) {
  85. if (error.code === `ENOENT` && pathOptions.create) {
  86. this.stats = statUtils.makeDefaultStats();
  87. }
  88. else {
  89. throw error;
  90. }
  91. }
  92. }
  93. else {
  94. this.stats = statUtils.makeDefaultStats();
  95. }
  96. }
  97. const errPtr = this.libzip.malloc(4);
  98. try {
  99. let flags = 0;
  100. if (typeof source === `string` && pathOptions.create)
  101. flags |= this.libzip.ZIP_CREATE | this.libzip.ZIP_TRUNCATE;
  102. if (opts.readOnly) {
  103. flags |= this.libzip.ZIP_RDONLY;
  104. this.readOnly = true;
  105. }
  106. if (typeof source === `string`) {
  107. this.zip = this.libzip.open(path_1.npath.fromPortablePath(source), flags, errPtr);
  108. }
  109. else {
  110. const lzSource = this.allocateUnattachedSource(source);
  111. try {
  112. this.zip = this.libzip.openFromSource(lzSource, flags, errPtr);
  113. this.lzSource = lzSource;
  114. }
  115. catch (error) {
  116. this.libzip.source.free(lzSource);
  117. throw error;
  118. }
  119. }
  120. if (this.zip === 0) {
  121. const error = this.libzip.struct.errorS();
  122. this.libzip.error.initWithCode(error, this.libzip.getValue(errPtr, `i32`));
  123. throw this.makeLibzipError(error);
  124. }
  125. }
  126. finally {
  127. this.libzip.free(errPtr);
  128. }
  129. this.listings.set(path_1.PortablePath.root, new Set());
  130. const entryCount = this.libzip.getNumEntries(this.zip, 0);
  131. for (let t = 0; t < entryCount; ++t) {
  132. const raw = this.libzip.getName(this.zip, t, 0);
  133. if (path_1.ppath.isAbsolute(raw))
  134. continue;
  135. const p = path_1.ppath.resolve(path_1.PortablePath.root, raw);
  136. this.registerEntry(p, t);
  137. // If the raw path is a directory, register it
  138. // to prevent empty folder being skipped
  139. if (raw.endsWith(`/`)) {
  140. this.registerListing(p);
  141. }
  142. }
  143. this.symlinkCount = this.libzip.ext.countSymlinks(this.zip);
  144. if (this.symlinkCount === -1)
  145. throw this.makeLibzipError(this.libzip.getError(this.zip));
  146. this.ready = true;
  147. }
  148. makeLibzipError(error) {
  149. const errorCode = this.libzip.struct.errorCodeZip(error);
  150. const strerror = this.libzip.error.strerror(error);
  151. const libzipError = new errors.LibzipError(strerror, this.libzip.errors[errorCode]);
  152. // This error should never come up because of the file source cache
  153. if (errorCode === this.libzip.errors.ZIP_ER_CHANGED)
  154. throw new Error(`Assertion failed: Unexpected libzip error: ${libzipError.message}`);
  155. return libzipError;
  156. }
  157. getExtractHint(hints) {
  158. for (const fileName of this.entries.keys()) {
  159. const ext = this.pathUtils.extname(fileName);
  160. if (hints.relevantExtensions.has(ext)) {
  161. return true;
  162. }
  163. }
  164. return false;
  165. }
  166. getAllFiles() {
  167. return Array.from(this.entries.keys());
  168. }
  169. getRealPath() {
  170. if (!this.path)
  171. throw new Error(`ZipFS don't have real paths when loaded from a buffer`);
  172. return this.path;
  173. }
  174. getBufferAndClose() {
  175. this.prepareClose();
  176. if (!this.lzSource)
  177. throw new Error(`ZipFS was not created from a Buffer`);
  178. // zip_source_open on an unlink-after-write empty archive fails with "Entry has been deleted"
  179. if (this.entries.size === 0) {
  180. this.discardAndClose();
  181. return makeEmptyArchive();
  182. }
  183. try {
  184. // Prevent close from cleaning up the source
  185. this.libzip.source.keep(this.lzSource);
  186. // Close the zip archive
  187. if (this.libzip.close(this.zip) === -1)
  188. throw this.makeLibzipError(this.libzip.getError(this.zip));
  189. // Open the source for reading
  190. if (this.libzip.source.open(this.lzSource) === -1)
  191. throw this.makeLibzipError(this.libzip.source.error(this.lzSource));
  192. // Move to the end of source
  193. if (this.libzip.source.seek(this.lzSource, 0, 0, this.libzip.SEEK_END) === -1)
  194. throw this.makeLibzipError(this.libzip.source.error(this.lzSource));
  195. // Get the size of source
  196. const size = this.libzip.source.tell(this.lzSource);
  197. if (size === -1)
  198. throw this.makeLibzipError(this.libzip.source.error(this.lzSource));
  199. // Move to the start of source
  200. if (this.libzip.source.seek(this.lzSource, 0, 0, this.libzip.SEEK_SET) === -1)
  201. throw this.makeLibzipError(this.libzip.source.error(this.lzSource));
  202. const buffer = this.libzip.malloc(size);
  203. if (!buffer)
  204. throw new Error(`Couldn't allocate enough memory`);
  205. try {
  206. const rc = this.libzip.source.read(this.lzSource, buffer, size);
  207. if (rc === -1)
  208. throw this.makeLibzipError(this.libzip.source.error(this.lzSource));
  209. else if (rc < size)
  210. throw new Error(`Incomplete read`);
  211. else if (rc > size)
  212. throw new Error(`Overread`);
  213. const memory = this.libzip.HEAPU8.subarray(buffer, buffer + size);
  214. return Buffer.from(memory);
  215. }
  216. finally {
  217. this.libzip.free(buffer);
  218. }
  219. }
  220. finally {
  221. this.libzip.source.close(this.lzSource);
  222. this.libzip.source.free(this.lzSource);
  223. this.ready = false;
  224. }
  225. }
  226. prepareClose() {
  227. if (!this.ready)
  228. throw errors.EBUSY(`archive closed, close`);
  229. (0, watchFile_1.unwatchAllFiles)(this);
  230. }
  231. saveAndClose() {
  232. if (!this.path || !this.baseFs)
  233. throw new Error(`ZipFS cannot be saved and must be discarded when loaded from a buffer`);
  234. this.prepareClose();
  235. if (this.readOnly) {
  236. this.discardAndClose();
  237. return;
  238. }
  239. const newMode = this.baseFs.existsSync(this.path) || this.stats.mode === statUtils.DEFAULT_MODE
  240. ? undefined
  241. : this.stats.mode;
  242. // zip_close doesn't persist empty archives
  243. if (this.entries.size === 0) {
  244. this.discardAndClose();
  245. this.baseFs.writeFileSync(this.path, makeEmptyArchive(), { mode: newMode });
  246. }
  247. else {
  248. const rc = this.libzip.close(this.zip);
  249. if (rc === -1)
  250. throw this.makeLibzipError(this.libzip.getError(this.zip));
  251. if (typeof newMode !== `undefined`) {
  252. this.baseFs.chmodSync(this.path, newMode);
  253. }
  254. }
  255. this.ready = false;
  256. }
  257. discardAndClose() {
  258. this.prepareClose();
  259. this.libzip.discard(this.zip);
  260. this.ready = false;
  261. }
  262. resolve(p) {
  263. return path_1.ppath.resolve(path_1.PortablePath.root, p);
  264. }
  265. async openPromise(p, flags, mode) {
  266. return this.openSync(p, flags, mode);
  267. }
  268. openSync(p, flags, mode) {
  269. const fd = this.nextFd++;
  270. this.fds.set(fd, { cursor: 0, p });
  271. return fd;
  272. }
  273. hasOpenFileHandles() {
  274. return !!this.fds.size;
  275. }
  276. async opendirPromise(p, opts) {
  277. return this.opendirSync(p, opts);
  278. }
  279. opendirSync(p, opts = {}) {
  280. const resolvedP = this.resolveFilename(`opendir '${p}'`, p);
  281. if (!this.entries.has(resolvedP) && !this.listings.has(resolvedP))
  282. throw errors.ENOENT(`opendir '${p}'`);
  283. const directoryListing = this.listings.get(resolvedP);
  284. if (!directoryListing)
  285. throw errors.ENOTDIR(`opendir '${p}'`);
  286. const entries = [...directoryListing];
  287. const fd = this.openSync(resolvedP, `r`);
  288. const onClose = () => {
  289. this.closeSync(fd);
  290. };
  291. return (0, opendir_1.opendir)(this, resolvedP, entries, { onClose });
  292. }
  293. async readPromise(fd, buffer, offset, length, position) {
  294. return this.readSync(fd, buffer, offset, length, position);
  295. }
  296. readSync(fd, buffer, offset = 0, length = buffer.byteLength, position = -1) {
  297. const entry = this.fds.get(fd);
  298. if (typeof entry === `undefined`)
  299. throw errors.EBADF(`read`);
  300. const realPosition = position === -1 || position === null
  301. ? entry.cursor
  302. : position;
  303. const source = this.readFileSync(entry.p);
  304. source.copy(buffer, offset, realPosition, realPosition + length);
  305. const bytesRead = Math.max(0, Math.min(source.length - realPosition, length));
  306. if (position === -1 || position === null)
  307. entry.cursor += bytesRead;
  308. return bytesRead;
  309. }
  310. async writePromise(fd, buffer, offset, length, position) {
  311. if (typeof buffer === `string`) {
  312. return this.writeSync(fd, buffer, position);
  313. }
  314. else {
  315. return this.writeSync(fd, buffer, offset, length, position);
  316. }
  317. }
  318. writeSync(fd, buffer, offset, length, position) {
  319. const entry = this.fds.get(fd);
  320. if (typeof entry === `undefined`)
  321. throw errors.EBADF(`read`);
  322. throw new Error(`Unimplemented`);
  323. }
  324. async closePromise(fd) {
  325. return this.closeSync(fd);
  326. }
  327. closeSync(fd) {
  328. const entry = this.fds.get(fd);
  329. if (typeof entry === `undefined`)
  330. throw errors.EBADF(`read`);
  331. this.fds.delete(fd);
  332. }
  333. createReadStream(p, { encoding } = {}) {
  334. if (p === null)
  335. throw new Error(`Unimplemented`);
  336. const fd = this.openSync(p, `r`);
  337. const stream = Object.assign(new stream_1.PassThrough({
  338. emitClose: true,
  339. autoDestroy: true,
  340. destroy: (error, callback) => {
  341. clearImmediate(immediate);
  342. this.closeSync(fd);
  343. callback(error);
  344. },
  345. }), {
  346. close() {
  347. stream.destroy();
  348. },
  349. bytesRead: 0,
  350. path: p,
  351. });
  352. const immediate = setImmediate(async () => {
  353. try {
  354. const data = await this.readFilePromise(p, encoding);
  355. stream.bytesRead = data.length;
  356. stream.end(data);
  357. }
  358. catch (error) {
  359. stream.destroy(error);
  360. }
  361. });
  362. return stream;
  363. }
  364. createWriteStream(p, { encoding } = {}) {
  365. if (this.readOnly)
  366. throw errors.EROFS(`open '${p}'`);
  367. if (p === null)
  368. throw new Error(`Unimplemented`);
  369. const chunks = [];
  370. const fd = this.openSync(p, `w`);
  371. const stream = Object.assign(new stream_1.PassThrough({
  372. autoDestroy: true,
  373. emitClose: true,
  374. destroy: (error, callback) => {
  375. try {
  376. if (error) {
  377. callback(error);
  378. }
  379. else {
  380. this.writeFileSync(p, Buffer.concat(chunks), encoding);
  381. callback(null);
  382. }
  383. }
  384. catch (err) {
  385. callback(err);
  386. }
  387. finally {
  388. this.closeSync(fd);
  389. }
  390. },
  391. }), {
  392. bytesWritten: 0,
  393. path: p,
  394. close() {
  395. stream.destroy();
  396. },
  397. });
  398. stream.on(`data`, chunk => {
  399. const chunkBuffer = Buffer.from(chunk);
  400. stream.bytesWritten += chunkBuffer.length;
  401. chunks.push(chunkBuffer);
  402. });
  403. return stream;
  404. }
  405. async realpathPromise(p) {
  406. return this.realpathSync(p);
  407. }
  408. realpathSync(p) {
  409. const resolvedP = this.resolveFilename(`lstat '${p}'`, p);
  410. if (!this.entries.has(resolvedP) && !this.listings.has(resolvedP))
  411. throw errors.ENOENT(`lstat '${p}'`);
  412. return resolvedP;
  413. }
  414. async existsPromise(p) {
  415. return this.existsSync(p);
  416. }
  417. existsSync(p) {
  418. if (!this.ready)
  419. throw errors.EBUSY(`archive closed, existsSync '${p}'`);
  420. if (this.symlinkCount === 0) {
  421. const resolvedP = path_1.ppath.resolve(path_1.PortablePath.root, p);
  422. return this.entries.has(resolvedP) || this.listings.has(resolvedP);
  423. }
  424. let resolvedP;
  425. try {
  426. resolvedP = this.resolveFilename(`stat '${p}'`, p, undefined, false);
  427. }
  428. catch (error) {
  429. return false;
  430. }
  431. if (resolvedP === undefined)
  432. return false;
  433. return this.entries.has(resolvedP) || this.listings.has(resolvedP);
  434. }
  435. async accessPromise(p, mode) {
  436. return this.accessSync(p, mode);
  437. }
  438. accessSync(p, mode = fs_1.constants.F_OK) {
  439. const resolvedP = this.resolveFilename(`access '${p}'`, p);
  440. if (!this.entries.has(resolvedP) && !this.listings.has(resolvedP))
  441. throw errors.ENOENT(`access '${p}'`);
  442. if (this.readOnly && (mode & fs_1.constants.W_OK)) {
  443. throw errors.EROFS(`access '${p}'`);
  444. }
  445. }
  446. async statPromise(p, opts = { bigint: false }) {
  447. if (opts.bigint)
  448. return this.statSync(p, { bigint: true });
  449. return this.statSync(p);
  450. }
  451. statSync(p, opts = { bigint: false, throwIfNoEntry: true }) {
  452. const resolvedP = this.resolveFilename(`stat '${p}'`, p, undefined, opts.throwIfNoEntry);
  453. if (resolvedP === undefined)
  454. return undefined;
  455. if (!this.entries.has(resolvedP) && !this.listings.has(resolvedP)) {
  456. if (opts.throwIfNoEntry === false)
  457. return undefined;
  458. throw errors.ENOENT(`stat '${p}'`);
  459. }
  460. if (p[p.length - 1] === `/` && !this.listings.has(resolvedP))
  461. throw errors.ENOTDIR(`stat '${p}'`);
  462. return this.statImpl(`stat '${p}'`, resolvedP, opts);
  463. }
  464. async fstatPromise(fd, opts) {
  465. return this.fstatSync(fd, opts);
  466. }
  467. fstatSync(fd, opts) {
  468. const entry = this.fds.get(fd);
  469. if (typeof entry === `undefined`)
  470. throw errors.EBADF(`fstatSync`);
  471. const { p } = entry;
  472. const resolvedP = this.resolveFilename(`stat '${p}'`, p);
  473. if (!this.entries.has(resolvedP) && !this.listings.has(resolvedP))
  474. throw errors.ENOENT(`stat '${p}'`);
  475. if (p[p.length - 1] === `/` && !this.listings.has(resolvedP))
  476. throw errors.ENOTDIR(`stat '${p}'`);
  477. return this.statImpl(`fstat '${p}'`, resolvedP, opts);
  478. }
  479. async lstatPromise(p, opts = { bigint: false }) {
  480. if (opts.bigint)
  481. return this.lstatSync(p, { bigint: true });
  482. return this.lstatSync(p);
  483. }
  484. lstatSync(p, opts = { bigint: false, throwIfNoEntry: true }) {
  485. const resolvedP = this.resolveFilename(`lstat '${p}'`, p, false, opts.throwIfNoEntry);
  486. if (resolvedP === undefined)
  487. return undefined;
  488. if (!this.entries.has(resolvedP) && !this.listings.has(resolvedP)) {
  489. if (opts.throwIfNoEntry === false)
  490. return undefined;
  491. throw errors.ENOENT(`lstat '${p}'`);
  492. }
  493. if (p[p.length - 1] === `/` && !this.listings.has(resolvedP))
  494. throw errors.ENOTDIR(`lstat '${p}'`);
  495. return this.statImpl(`lstat '${p}'`, resolvedP, opts);
  496. }
  497. statImpl(reason, p, opts = {}) {
  498. const entry = this.entries.get(p);
  499. // File, or explicit directory
  500. if (typeof entry !== `undefined`) {
  501. const stat = this.libzip.struct.statS();
  502. const rc = this.libzip.statIndex(this.zip, entry, 0, 0, stat);
  503. if (rc === -1)
  504. throw this.makeLibzipError(this.libzip.getError(this.zip));
  505. const uid = this.stats.uid;
  506. const gid = this.stats.gid;
  507. const size = (this.libzip.struct.statSize(stat) >>> 0);
  508. const blksize = 512;
  509. const blocks = Math.ceil(size / blksize);
  510. const mtimeMs = (this.libzip.struct.statMtime(stat) >>> 0) * 1000;
  511. const atimeMs = mtimeMs;
  512. const birthtimeMs = mtimeMs;
  513. const ctimeMs = mtimeMs;
  514. const atime = new Date(atimeMs);
  515. const birthtime = new Date(birthtimeMs);
  516. const ctime = new Date(ctimeMs);
  517. const mtime = new Date(mtimeMs);
  518. const type = this.listings.has(p)
  519. ? constants_1.S_IFDIR
  520. : this.isSymbolicLink(entry)
  521. ? constants_1.S_IFLNK
  522. : constants_1.S_IFREG;
  523. const defaultMode = type === constants_1.S_IFDIR
  524. ? 0o755
  525. : 0o644;
  526. const mode = type | (this.getUnixMode(entry, defaultMode) & 0o777);
  527. const crc = this.libzip.struct.statCrc(stat);
  528. const statInstance = Object.assign(new statUtils.StatEntry(), { uid, gid, size, blksize, blocks, atime, birthtime, ctime, mtime, atimeMs, birthtimeMs, ctimeMs, mtimeMs, mode, crc });
  529. return opts.bigint === true ? statUtils.convertToBigIntStats(statInstance) : statInstance;
  530. }
  531. // Implicit directory
  532. if (this.listings.has(p)) {
  533. const uid = this.stats.uid;
  534. const gid = this.stats.gid;
  535. const size = 0;
  536. const blksize = 512;
  537. const blocks = 0;
  538. const atimeMs = this.stats.mtimeMs;
  539. const birthtimeMs = this.stats.mtimeMs;
  540. const ctimeMs = this.stats.mtimeMs;
  541. const mtimeMs = this.stats.mtimeMs;
  542. const atime = new Date(atimeMs);
  543. const birthtime = new Date(birthtimeMs);
  544. const ctime = new Date(ctimeMs);
  545. const mtime = new Date(mtimeMs);
  546. const mode = constants_1.S_IFDIR | 0o755;
  547. const crc = 0;
  548. const statInstance = Object.assign(new statUtils.StatEntry(), { uid, gid, size, blksize, blocks, atime, birthtime, ctime, mtime, atimeMs, birthtimeMs, ctimeMs, mtimeMs, mode, crc });
  549. return opts.bigint === true ? statUtils.convertToBigIntStats(statInstance) : statInstance;
  550. }
  551. throw new Error(`Unreachable`);
  552. }
  553. getUnixMode(index, defaultMode) {
  554. const rc = this.libzip.file.getExternalAttributes(this.zip, index, 0, 0, this.libzip.uint08S, this.libzip.uint32S);
  555. if (rc === -1)
  556. throw this.makeLibzipError(this.libzip.getError(this.zip));
  557. const opsys = this.libzip.getValue(this.libzip.uint08S, `i8`) >>> 0;
  558. if (opsys !== this.libzip.ZIP_OPSYS_UNIX)
  559. return defaultMode;
  560. return this.libzip.getValue(this.libzip.uint32S, `i32`) >>> 16;
  561. }
  562. registerListing(p) {
  563. const existingListing = this.listings.get(p);
  564. if (existingListing)
  565. return existingListing;
  566. const parentListing = this.registerListing(path_1.ppath.dirname(p));
  567. parentListing.add(path_1.ppath.basename(p));
  568. const newListing = new Set();
  569. this.listings.set(p, newListing);
  570. return newListing;
  571. }
  572. registerEntry(p, index) {
  573. const parentListing = this.registerListing(path_1.ppath.dirname(p));
  574. parentListing.add(path_1.ppath.basename(p));
  575. this.entries.set(p, index);
  576. }
  577. unregisterListing(p) {
  578. this.listings.delete(p);
  579. const parentListing = this.listings.get(path_1.ppath.dirname(p));
  580. parentListing === null || parentListing === void 0 ? void 0 : parentListing.delete(path_1.ppath.basename(p));
  581. }
  582. unregisterEntry(p) {
  583. this.unregisterListing(p);
  584. const entry = this.entries.get(p);
  585. this.entries.delete(p);
  586. if (typeof entry === `undefined`)
  587. return;
  588. this.fileSources.delete(entry);
  589. if (this.isSymbolicLink(entry)) {
  590. this.symlinkCount--;
  591. }
  592. }
  593. deleteEntry(p, index) {
  594. this.unregisterEntry(p);
  595. const rc = this.libzip.delete(this.zip, index);
  596. if (rc === -1) {
  597. throw this.makeLibzipError(this.libzip.getError(this.zip));
  598. }
  599. }
  600. resolveFilename(reason, p, resolveLastComponent = true, throwIfNoEntry = true) {
  601. if (!this.ready)
  602. throw errors.EBUSY(`archive closed, ${reason}`);
  603. let resolvedP = path_1.ppath.resolve(path_1.PortablePath.root, p);
  604. if (resolvedP === `/`)
  605. return path_1.PortablePath.root;
  606. const fileIndex = this.entries.get(resolvedP);
  607. if (resolveLastComponent && fileIndex !== undefined) {
  608. if (this.symlinkCount !== 0 && this.isSymbolicLink(fileIndex)) {
  609. const target = this.getFileSource(fileIndex).toString();
  610. return this.resolveFilename(reason, path_1.ppath.resolve(path_1.ppath.dirname(resolvedP), target), true, throwIfNoEntry);
  611. }
  612. else {
  613. return resolvedP;
  614. }
  615. }
  616. while (true) {
  617. const parentP = this.resolveFilename(reason, path_1.ppath.dirname(resolvedP), true, throwIfNoEntry);
  618. if (parentP === undefined)
  619. return parentP;
  620. const isDir = this.listings.has(parentP);
  621. const doesExist = this.entries.has(parentP);
  622. if (!isDir && !doesExist) {
  623. if (throwIfNoEntry === false)
  624. return undefined;
  625. throw errors.ENOENT(reason);
  626. }
  627. if (!isDir)
  628. throw errors.ENOTDIR(reason);
  629. resolvedP = path_1.ppath.resolve(parentP, path_1.ppath.basename(resolvedP));
  630. if (!resolveLastComponent || this.symlinkCount === 0)
  631. break;
  632. const index = this.libzip.name.locate(this.zip, resolvedP.slice(1), 0);
  633. if (index === -1)
  634. break;
  635. if (this.isSymbolicLink(index)) {
  636. const target = this.getFileSource(index).toString();
  637. resolvedP = path_1.ppath.resolve(path_1.ppath.dirname(resolvedP), target);
  638. }
  639. else {
  640. break;
  641. }
  642. }
  643. return resolvedP;
  644. }
  645. allocateBuffer(content) {
  646. if (!Buffer.isBuffer(content))
  647. content = Buffer.from(content);
  648. const buffer = this.libzip.malloc(content.byteLength);
  649. if (!buffer)
  650. throw new Error(`Couldn't allocate enough memory`);
  651. // Copy the file into the Emscripten heap
  652. const heap = new Uint8Array(this.libzip.HEAPU8.buffer, buffer, content.byteLength);
  653. heap.set(content);
  654. return { buffer, byteLength: content.byteLength };
  655. }
  656. allocateUnattachedSource(content) {
  657. const error = this.libzip.struct.errorS();
  658. const { buffer, byteLength } = this.allocateBuffer(content);
  659. const source = this.libzip.source.fromUnattachedBuffer(buffer, byteLength, 0, 1, error);
  660. if (source === 0) {
  661. this.libzip.free(error);
  662. throw this.makeLibzipError(error);
  663. }
  664. return source;
  665. }
  666. allocateSource(content) {
  667. const { buffer, byteLength } = this.allocateBuffer(content);
  668. const source = this.libzip.source.fromBuffer(this.zip, buffer, byteLength, 0, 1);
  669. if (source === 0) {
  670. this.libzip.free(buffer);
  671. throw this.makeLibzipError(this.libzip.getError(this.zip));
  672. }
  673. return source;
  674. }
  675. setFileSource(p, content) {
  676. const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
  677. const target = path_1.ppath.relative(path_1.PortablePath.root, p);
  678. const lzSource = this.allocateSource(content);
  679. try {
  680. const newIndex = this.libzip.file.add(this.zip, target, lzSource, this.libzip.ZIP_FL_OVERWRITE);
  681. if (newIndex === -1)
  682. throw this.makeLibzipError(this.libzip.getError(this.zip));
  683. if (this.level !== `mixed`) {
  684. // Use store for level 0, and deflate for 1..9
  685. const method = this.level === 0
  686. ? this.libzip.ZIP_CM_STORE
  687. : this.libzip.ZIP_CM_DEFLATE;
  688. const rc = this.libzip.file.setCompression(this.zip, newIndex, 0, method, this.level);
  689. if (rc === -1) {
  690. throw this.makeLibzipError(this.libzip.getError(this.zip));
  691. }
  692. }
  693. this.fileSources.set(newIndex, buffer);
  694. return newIndex;
  695. }
  696. catch (error) {
  697. this.libzip.source.free(lzSource);
  698. throw error;
  699. }
  700. }
  701. isSymbolicLink(index) {
  702. if (this.symlinkCount === 0)
  703. return false;
  704. const attrs = this.libzip.file.getExternalAttributes(this.zip, index, 0, 0, this.libzip.uint08S, this.libzip.uint32S);
  705. if (attrs === -1)
  706. throw this.makeLibzipError(this.libzip.getError(this.zip));
  707. const opsys = this.libzip.getValue(this.libzip.uint08S, `i8`) >>> 0;
  708. if (opsys !== this.libzip.ZIP_OPSYS_UNIX)
  709. return false;
  710. const attributes = this.libzip.getValue(this.libzip.uint32S, `i32`) >>> 16;
  711. return (attributes & constants_1.S_IFMT) === constants_1.S_IFLNK;
  712. }
  713. getFileSource(index, opts = { asyncDecompress: false }) {
  714. const cachedFileSource = this.fileSources.get(index);
  715. if (typeof cachedFileSource !== `undefined`)
  716. return cachedFileSource;
  717. const stat = this.libzip.struct.statS();
  718. const rc = this.libzip.statIndex(this.zip, index, 0, 0, stat);
  719. if (rc === -1)
  720. throw this.makeLibzipError(this.libzip.getError(this.zip));
  721. const size = this.libzip.struct.statCompSize(stat);
  722. const compressionMethod = this.libzip.struct.statCompMethod(stat);
  723. const buffer = this.libzip.malloc(size);
  724. try {
  725. const file = this.libzip.fopenIndex(this.zip, index, 0, this.libzip.ZIP_FL_COMPRESSED);
  726. if (file === 0)
  727. throw this.makeLibzipError(this.libzip.getError(this.zip));
  728. try {
  729. const rc = this.libzip.fread(file, buffer, size, 0);
  730. if (rc === -1)
  731. throw this.makeLibzipError(this.libzip.file.getError(file));
  732. else if (rc < size)
  733. throw new Error(`Incomplete read`);
  734. else if (rc > size)
  735. throw new Error(`Overread`);
  736. const memory = this.libzip.HEAPU8.subarray(buffer, buffer + size);
  737. const data = Buffer.from(memory);
  738. if (compressionMethod === 0) {
  739. this.fileSources.set(index, data);
  740. return data;
  741. }
  742. else if (opts.asyncDecompress) {
  743. return new Promise((resolve, reject) => {
  744. zlib_1.default.inflateRaw(data, (error, result) => {
  745. if (error) {
  746. reject(error);
  747. }
  748. else {
  749. this.fileSources.set(index, result);
  750. resolve(result);
  751. }
  752. });
  753. });
  754. }
  755. else {
  756. const decompressedData = zlib_1.default.inflateRawSync(data);
  757. this.fileSources.set(index, decompressedData);
  758. return decompressedData;
  759. }
  760. }
  761. finally {
  762. this.libzip.fclose(file);
  763. }
  764. }
  765. finally {
  766. this.libzip.free(buffer);
  767. }
  768. }
  769. async fchmodPromise(fd, mask) {
  770. return this.chmodPromise(this.fdToPath(fd, `fchmod`), mask);
  771. }
  772. fchmodSync(fd, mask) {
  773. return this.chmodSync(this.fdToPath(fd, `fchmodSync`), mask);
  774. }
  775. async chmodPromise(p, mask) {
  776. return this.chmodSync(p, mask);
  777. }
  778. chmodSync(p, mask) {
  779. if (this.readOnly)
  780. throw errors.EROFS(`chmod '${p}'`);
  781. // We don't allow to make the extracted entries group-writable
  782. mask &= 0o755;
  783. const resolvedP = this.resolveFilename(`chmod '${p}'`, p, false);
  784. const entry = this.entries.get(resolvedP);
  785. if (typeof entry === `undefined`)
  786. throw new Error(`Assertion failed: The entry should have been registered (${resolvedP})`);
  787. const oldMod = this.getUnixMode(entry, constants_1.S_IFREG | 0o000);
  788. const newMod = oldMod & (~0o777) | mask;
  789. const rc = this.libzip.file.setExternalAttributes(this.zip, entry, 0, 0, this.libzip.ZIP_OPSYS_UNIX, newMod << 16);
  790. if (rc === -1) {
  791. throw this.makeLibzipError(this.libzip.getError(this.zip));
  792. }
  793. }
  794. async fchownPromise(fd, uid, gid) {
  795. return this.chownPromise(this.fdToPath(fd, `fchown`), uid, gid);
  796. }
  797. fchownSync(fd, uid, gid) {
  798. return this.chownSync(this.fdToPath(fd, `fchownSync`), uid, gid);
  799. }
  800. async chownPromise(p, uid, gid) {
  801. return this.chownSync(p, uid, gid);
  802. }
  803. chownSync(p, uid, gid) {
  804. throw new Error(`Unimplemented`);
  805. }
  806. async renamePromise(oldP, newP) {
  807. return this.renameSync(oldP, newP);
  808. }
  809. renameSync(oldP, newP) {
  810. throw new Error(`Unimplemented`);
  811. }
  812. async copyFilePromise(sourceP, destP, flags) {
  813. const { indexSource, indexDest, resolvedDestP } = this.prepareCopyFile(sourceP, destP, flags);
  814. const source = await this.getFileSource(indexSource, { asyncDecompress: true });
  815. const newIndex = this.setFileSource(resolvedDestP, source);
  816. if (newIndex !== indexDest) {
  817. this.registerEntry(resolvedDestP, newIndex);
  818. }
  819. }
  820. copyFileSync(sourceP, destP, flags = 0) {
  821. const { indexSource, indexDest, resolvedDestP } = this.prepareCopyFile(sourceP, destP, flags);
  822. const source = this.getFileSource(indexSource);
  823. const newIndex = this.setFileSource(resolvedDestP, source);
  824. if (newIndex !== indexDest) {
  825. this.registerEntry(resolvedDestP, newIndex);
  826. }
  827. }
  828. prepareCopyFile(sourceP, destP, flags = 0) {
  829. if (this.readOnly)
  830. throw errors.EROFS(`copyfile '${sourceP} -> '${destP}'`);
  831. if ((flags & fs_1.constants.COPYFILE_FICLONE_FORCE) !== 0)
  832. throw errors.ENOSYS(`unsupported clone operation`, `copyfile '${sourceP}' -> ${destP}'`);
  833. const resolvedSourceP = this.resolveFilename(`copyfile '${sourceP} -> ${destP}'`, sourceP);
  834. const indexSource = this.entries.get(resolvedSourceP);
  835. if (typeof indexSource === `undefined`)
  836. throw errors.EINVAL(`copyfile '${sourceP}' -> '${destP}'`);
  837. const resolvedDestP = this.resolveFilename(`copyfile '${sourceP}' -> ${destP}'`, destP);
  838. const indexDest = this.entries.get(resolvedDestP);
  839. if ((flags & (fs_1.constants.COPYFILE_EXCL | fs_1.constants.COPYFILE_FICLONE_FORCE)) !== 0 && typeof indexDest !== `undefined`)
  840. throw errors.EEXIST(`copyfile '${sourceP}' -> '${destP}'`);
  841. return {
  842. indexSource,
  843. resolvedDestP,
  844. indexDest,
  845. };
  846. }
  847. async appendFilePromise(p, content, opts) {
  848. if (this.readOnly)
  849. throw errors.EROFS(`open '${p}'`);
  850. if (typeof opts === `undefined`)
  851. opts = { flag: `a` };
  852. else if (typeof opts === `string`)
  853. opts = { flag: `a`, encoding: opts };
  854. else if (typeof opts.flag === `undefined`)
  855. opts = { flag: `a`, ...opts };
  856. return this.writeFilePromise(p, content, opts);
  857. }
  858. appendFileSync(p, content, opts = {}) {
  859. if (this.readOnly)
  860. throw errors.EROFS(`open '${p}'`);
  861. if (typeof opts === `undefined`)
  862. opts = { flag: `a` };
  863. else if (typeof opts === `string`)
  864. opts = { flag: `a`, encoding: opts };
  865. else if (typeof opts.flag === `undefined`)
  866. opts = { flag: `a`, ...opts };
  867. return this.writeFileSync(p, content, opts);
  868. }
  869. fdToPath(fd, reason) {
  870. var _a;
  871. const path = (_a = this.fds.get(fd)) === null || _a === void 0 ? void 0 : _a.p;
  872. if (typeof path === `undefined`)
  873. throw errors.EBADF(reason);
  874. return path;
  875. }
  876. async writeFilePromise(p, content, opts) {
  877. const { encoding, mode, index, resolvedP } = this.prepareWriteFile(p, opts);
  878. if (index !== undefined && typeof opts === `object` && opts.flag && opts.flag.includes(`a`))
  879. content = Buffer.concat([await this.getFileSource(index, { asyncDecompress: true }), Buffer.from(content)]);
  880. if (encoding !== null)
  881. content = content.toString(encoding);
  882. const newIndex = this.setFileSource(resolvedP, content);
  883. if (newIndex !== index)
  884. this.registerEntry(resolvedP, newIndex);
  885. if (mode !== null) {
  886. await this.chmodPromise(resolvedP, mode);
  887. }
  888. }
  889. writeFileSync(p, content, opts) {
  890. const { encoding, mode, index, resolvedP } = this.prepareWriteFile(p, opts);
  891. if (index !== undefined && typeof opts === `object` && opts.flag && opts.flag.includes(`a`))
  892. content = Buffer.concat([this.getFileSource(index), Buffer.from(content)]);
  893. if (encoding !== null)
  894. content = content.toString(encoding);
  895. const newIndex = this.setFileSource(resolvedP, content);
  896. if (newIndex !== index)
  897. this.registerEntry(resolvedP, newIndex);
  898. if (mode !== null) {
  899. this.chmodSync(resolvedP, mode);
  900. }
  901. }
  902. prepareWriteFile(p, opts) {
  903. if (typeof p === `number`)
  904. p = this.fdToPath(p, `read`);
  905. if (this.readOnly)
  906. throw errors.EROFS(`open '${p}'`);
  907. const resolvedP = this.resolveFilename(`open '${p}'`, p);
  908. if (this.listings.has(resolvedP))
  909. throw errors.EISDIR(`open '${p}'`);
  910. let encoding = null, mode = null;
  911. if (typeof opts === `string`) {
  912. encoding = opts;
  913. }
  914. else if (typeof opts === `object`) {
  915. ({
  916. encoding = null,
  917. mode = null,
  918. } = opts);
  919. }
  920. const index = this.entries.get(resolvedP);
  921. return {
  922. encoding,
  923. mode,
  924. resolvedP,
  925. index,
  926. };
  927. }
  928. async unlinkPromise(p) {
  929. return this.unlinkSync(p);
  930. }
  931. unlinkSync(p) {
  932. if (this.readOnly)
  933. throw errors.EROFS(`unlink '${p}'`);
  934. const resolvedP = this.resolveFilename(`unlink '${p}'`, p);
  935. if (this.listings.has(resolvedP))
  936. throw errors.EISDIR(`unlink '${p}'`);
  937. const index = this.entries.get(resolvedP);
  938. if (typeof index === `undefined`)
  939. throw errors.EINVAL(`unlink '${p}'`);
  940. this.deleteEntry(resolvedP, index);
  941. }
  942. async utimesPromise(p, atime, mtime) {
  943. return this.utimesSync(p, atime, mtime);
  944. }
  945. utimesSync(p, atime, mtime) {
  946. if (this.readOnly)
  947. throw errors.EROFS(`utimes '${p}'`);
  948. const resolvedP = this.resolveFilename(`utimes '${p}'`, p);
  949. this.utimesImpl(resolvedP, mtime);
  950. }
  951. async lutimesPromise(p, atime, mtime) {
  952. return this.lutimesSync(p, atime, mtime);
  953. }
  954. lutimesSync(p, atime, mtime) {
  955. if (this.readOnly)
  956. throw errors.EROFS(`lutimes '${p}'`);
  957. const resolvedP = this.resolveFilename(`utimes '${p}'`, p, false);
  958. this.utimesImpl(resolvedP, mtime);
  959. }
  960. utimesImpl(resolvedP, mtime) {
  961. if (this.listings.has(resolvedP))
  962. if (!this.entries.has(resolvedP))
  963. this.hydrateDirectory(resolvedP);
  964. const entry = this.entries.get(resolvedP);
  965. if (entry === undefined)
  966. throw new Error(`Unreachable`);
  967. const rc = this.libzip.file.setMtime(this.zip, entry, 0, toUnixTimestamp(mtime), 0);
  968. if (rc === -1) {
  969. throw this.makeLibzipError(this.libzip.getError(this.zip));
  970. }
  971. }
  972. async mkdirPromise(p, opts) {
  973. return this.mkdirSync(p, opts);
  974. }
  975. mkdirSync(p, { mode = 0o755, recursive = false } = {}) {
  976. if (recursive)
  977. return this.mkdirpSync(p, { chmod: mode });
  978. if (this.readOnly)
  979. throw errors.EROFS(`mkdir '${p}'`);
  980. const resolvedP = this.resolveFilename(`mkdir '${p}'`, p);
  981. if (this.entries.has(resolvedP) || this.listings.has(resolvedP))
  982. throw errors.EEXIST(`mkdir '${p}'`);
  983. this.hydrateDirectory(resolvedP);
  984. this.chmodSync(resolvedP, mode);
  985. return undefined;
  986. }
  987. async rmdirPromise(p, opts) {
  988. return this.rmdirSync(p, opts);
  989. }
  990. rmdirSync(p, { recursive = false } = {}) {
  991. if (this.readOnly)
  992. throw errors.EROFS(`rmdir '${p}'`);
  993. if (recursive) {
  994. this.removeSync(p);
  995. return;
  996. }
  997. const resolvedP = this.resolveFilename(`rmdir '${p}'`, p);
  998. const directoryListing = this.listings.get(resolvedP);
  999. if (!directoryListing)
  1000. throw errors.ENOTDIR(`rmdir '${p}'`);
  1001. if (directoryListing.size > 0)
  1002. throw errors.ENOTEMPTY(`rmdir '${p}'`);
  1003. const index = this.entries.get(resolvedP);
  1004. if (typeof index === `undefined`)
  1005. throw errors.EINVAL(`rmdir '${p}'`);
  1006. this.deleteEntry(p, index);
  1007. }
  1008. hydrateDirectory(resolvedP) {
  1009. const index = this.libzip.dir.add(this.zip, path_1.ppath.relative(path_1.PortablePath.root, resolvedP));
  1010. if (index === -1)
  1011. throw this.makeLibzipError(this.libzip.getError(this.zip));
  1012. this.registerListing(resolvedP);
  1013. this.registerEntry(resolvedP, index);
  1014. return index;
  1015. }
  1016. async linkPromise(existingP, newP) {
  1017. return this.linkSync(existingP, newP);
  1018. }
  1019. linkSync(existingP, newP) {
  1020. // Zip archives don't support hard links:
  1021. // https://stackoverflow.com/questions/8859616/are-hard-links-possible-within-a-zip-archive
  1022. throw errors.EOPNOTSUPP(`link '${existingP}' -> '${newP}'`);
  1023. }
  1024. async symlinkPromise(target, p) {
  1025. return this.symlinkSync(target, p);
  1026. }
  1027. symlinkSync(target, p) {
  1028. if (this.readOnly)
  1029. throw errors.EROFS(`symlink '${target}' -> '${p}'`);
  1030. const resolvedP = this.resolveFilename(`symlink '${target}' -> '${p}'`, p);
  1031. if (this.listings.has(resolvedP))
  1032. throw errors.EISDIR(`symlink '${target}' -> '${p}'`);
  1033. if (this.entries.has(resolvedP))
  1034. throw errors.EEXIST(`symlink '${target}' -> '${p}'`);
  1035. const index = this.setFileSource(resolvedP, target);
  1036. this.registerEntry(resolvedP, index);
  1037. const rc = this.libzip.file.setExternalAttributes(this.zip, index, 0, 0, this.libzip.ZIP_OPSYS_UNIX, (constants_1.S_IFLNK | 0o777) << 16);
  1038. if (rc === -1)
  1039. throw this.makeLibzipError(this.libzip.getError(this.zip));
  1040. this.symlinkCount += 1;
  1041. }
  1042. async readFilePromise(p, encoding) {
  1043. // This is messed up regarding the TS signatures
  1044. if (typeof encoding === `object`)
  1045. // @ts-expect-error
  1046. encoding = encoding ? encoding.encoding : undefined;
  1047. const data = await this.readFileBuffer(p, { asyncDecompress: true });
  1048. return encoding ? data.toString(encoding) : data;
  1049. }
  1050. readFileSync(p, encoding) {
  1051. // This is messed up regarding the TS signatures
  1052. if (typeof encoding === `object`)
  1053. // @ts-expect-error
  1054. encoding = encoding ? encoding.encoding : undefined;
  1055. const data = this.readFileBuffer(p);
  1056. return encoding ? data.toString(encoding) : data;
  1057. }
  1058. readFileBuffer(p, opts = { asyncDecompress: false }) {
  1059. if (typeof p === `number`)
  1060. p = this.fdToPath(p, `read`);
  1061. const resolvedP = this.resolveFilename(`open '${p}'`, p);
  1062. if (!this.entries.has(resolvedP) && !this.listings.has(resolvedP))
  1063. throw errors.ENOENT(`open '${p}'`);
  1064. // Ensures that the last component is a directory, if the user said so (even if it is we'll throw right after with EISDIR anyway)
  1065. if (p[p.length - 1] === `/` && !this.listings.has(resolvedP))
  1066. throw errors.ENOTDIR(`open '${p}'`);
  1067. if (this.listings.has(resolvedP))
  1068. throw errors.EISDIR(`read`);
  1069. const entry = this.entries.get(resolvedP);
  1070. if (entry === undefined)
  1071. throw new Error(`Unreachable`);
  1072. return this.getFileSource(entry, opts);
  1073. }
  1074. async readdirPromise(p, opts) {
  1075. return this.readdirSync(p, opts);
  1076. }
  1077. readdirSync(p, opts) {
  1078. const resolvedP = this.resolveFilename(`scandir '${p}'`, p);
  1079. if (!this.entries.has(resolvedP) && !this.listings.has(resolvedP))
  1080. throw errors.ENOENT(`scandir '${p}'`);
  1081. const directoryListing = this.listings.get(resolvedP);
  1082. if (!directoryListing)
  1083. throw errors.ENOTDIR(`scandir '${p}'`);
  1084. const entries = [...directoryListing];
  1085. if (!(opts === null || opts === void 0 ? void 0 : opts.withFileTypes))
  1086. return entries;
  1087. return entries.map(name => {
  1088. return Object.assign(this.statImpl(`lstat`, path_1.ppath.join(p, name)), {
  1089. name,
  1090. });
  1091. });
  1092. }
  1093. async readlinkPromise(p) {
  1094. const entry = this.prepareReadlink(p);
  1095. return (await this.getFileSource(entry, { asyncDecompress: true })).toString();
  1096. }
  1097. readlinkSync(p) {
  1098. const entry = this.prepareReadlink(p);
  1099. return this.getFileSource(entry).toString();
  1100. }
  1101. prepareReadlink(p) {
  1102. const resolvedP = this.resolveFilename(`readlink '${p}'`, p, false);
  1103. if (!this.entries.has(resolvedP) && !this.listings.has(resolvedP))
  1104. throw errors.ENOENT(`readlink '${p}'`);
  1105. // Ensure that the last component is a directory (if it is we'll throw right after with EISDIR anyway)
  1106. if (p[p.length - 1] === `/` && !this.listings.has(resolvedP))
  1107. throw errors.ENOTDIR(`open '${p}'`);
  1108. if (this.listings.has(resolvedP))
  1109. throw errors.EINVAL(`readlink '${p}'`);
  1110. const entry = this.entries.get(resolvedP);
  1111. if (entry === undefined)
  1112. throw new Error(`Unreachable`);
  1113. if (!this.isSymbolicLink(entry))
  1114. throw errors.EINVAL(`readlink '${p}'`);
  1115. return entry;
  1116. }
  1117. async truncatePromise(p, len = 0) {
  1118. const resolvedP = this.resolveFilename(`open '${p}'`, p);
  1119. const index = this.entries.get(resolvedP);
  1120. if (typeof index === `undefined`)
  1121. throw errors.EINVAL(`open '${p}'`);
  1122. const source = await this.getFileSource(index, { asyncDecompress: true });
  1123. const truncated = Buffer.alloc(len, 0x00);
  1124. source.copy(truncated);
  1125. return await this.writeFilePromise(p, truncated);
  1126. }
  1127. truncateSync(p, len = 0) {
  1128. const resolvedP = this.resolveFilename(`open '${p}'`, p);
  1129. const index = this.entries.get(resolvedP);
  1130. if (typeof index === `undefined`)
  1131. throw errors.EINVAL(`open '${p}'`);
  1132. const source = this.getFileSource(index);
  1133. const truncated = Buffer.alloc(len, 0x00);
  1134. source.copy(truncated);
  1135. return this.writeFileSync(p, truncated);
  1136. }
  1137. async ftruncatePromise(fd, len) {
  1138. return this.truncatePromise(this.fdToPath(fd, `ftruncate`), len);
  1139. }
  1140. ftruncateSync(fd, len) {
  1141. return this.truncateSync(this.fdToPath(fd, `ftruncateSync`), len);
  1142. }
  1143. watch(p, a, b) {
  1144. let persistent;
  1145. switch (typeof a) {
  1146. case `function`:
  1147. case `string`:
  1148. case `undefined`:
  1149. {
  1150. persistent = true;
  1151. }
  1152. break;
  1153. default:
  1154. {
  1155. ({ persistent = true } = a);
  1156. }
  1157. break;
  1158. }
  1159. if (!persistent)
  1160. return { on: () => { }, close: () => { } };
  1161. const interval = setInterval(() => { }, 24 * 60 * 60 * 1000);
  1162. return { on: () => { }, close: () => {
  1163. clearInterval(interval);
  1164. } };
  1165. }
  1166. watchFile(p, a, b) {
  1167. const resolvedP = path_1.ppath.resolve(path_1.PortablePath.root, p);
  1168. return (0, watchFile_1.watchFile)(this, resolvedP, a, b);
  1169. }
  1170. unwatchFile(p, cb) {
  1171. const resolvedP = path_1.ppath.resolve(path_1.PortablePath.root, p);
  1172. return (0, watchFile_1.unwatchFile)(this, resolvedP, cb);
  1173. }
  1174. }
  1175. exports.ZipFS = ZipFS;