FileMiddleware.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. const { constants } = require("buffer");
  6. const { pipeline } = require("stream");
  7. const {
  8. constants: zConstants,
  9. // eslint-disable-next-line n/no-unsupported-features/node-builtins
  10. createBrotliCompress,
  11. // eslint-disable-next-line n/no-unsupported-features/node-builtins
  12. createBrotliDecompress,
  13. createGunzip,
  14. createGzip
  15. } = require("zlib");
  16. const { DEFAULTS } = require("../config/defaults");
  17. const createHash = require("../util/createHash");
  18. const { dirname, join, mkdirp } = require("../util/fs");
  19. const memoize = require("../util/memoize");
  20. const SerializerMiddleware = require("./SerializerMiddleware");
  21. /** @typedef {typeof import("../util/Hash")} Hash */
  22. /** @typedef {import("../util/fs").IStats} IStats */
  23. /** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */
  24. /** @typedef {import("./types").BufferSerializableType} BufferSerializableType */
  25. /*
  26. Format:
  27. File -> Header Section*
  28. Version -> u32
  29. AmountOfSections -> u32
  30. SectionSize -> i32 (if less than zero represents lazy value)
  31. Header -> Version AmountOfSections SectionSize*
  32. Buffer -> n bytes
  33. Section -> Buffer
  34. */
  35. // "wpc" + 1 in little-endian
  36. const VERSION = 0x01637077;
  37. const WRITE_LIMIT_TOTAL = 0x7fff0000;
  38. const WRITE_LIMIT_CHUNK = 511 * 1024 * 1024;
  39. /**
  40. * @param {Buffer[]} buffers buffers
  41. * @param {string | Hash} hashFunction hash function to use
  42. * @returns {string} hash
  43. */
  44. const hashForName = (buffers, hashFunction) => {
  45. const hash = createHash(hashFunction);
  46. for (const buf of buffers) hash.update(buf);
  47. return /** @type {string} */ (hash.digest("hex"));
  48. };
  49. const COMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024;
  50. const DECOMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024;
  51. /** @type {(buffer: Buffer, value: number, offset: number) => void} */
  52. const writeUInt64LE = Buffer.prototype.writeBigUInt64LE
  53. ? (buf, value, offset) => {
  54. buf.writeBigUInt64LE(BigInt(value), offset);
  55. }
  56. : (buf, value, offset) => {
  57. const low = value % 0x100000000;
  58. const high = (value - low) / 0x100000000;
  59. buf.writeUInt32LE(low, offset);
  60. buf.writeUInt32LE(high, offset + 4);
  61. };
  62. /** @type {(buffer: Buffer, offset: number) => void} */
  63. const readUInt64LE = Buffer.prototype.readBigUInt64LE
  64. ? (buf, offset) => Number(buf.readBigUInt64LE(offset))
  65. : (buf, offset) => {
  66. const low = buf.readUInt32LE(offset);
  67. const high = buf.readUInt32LE(offset + 4);
  68. return high * 0x100000000 + low;
  69. };
  70. /** @typedef {Promise<void | void[]>} BackgroundJob */
  71. /**
  72. * @typedef {object} SerializeResult
  73. * @property {string | false} name
  74. * @property {number} size
  75. * @property {BackgroundJob=} backgroundJob
  76. */
  77. /** @typedef {{ name: string, size: number }} LazyOptions */
  78. /**
  79. * @typedef {import("./SerializerMiddleware").LazyFunction<BufferSerializableType[], Buffer, FileMiddleware, LazyOptions>} LazyFunction
  80. */
  81. /**
  82. * @param {FileMiddleware} middleware this
  83. * @param {(BufferSerializableType | LazyFunction)[]} data data to be serialized
  84. * @param {string | boolean} name file base name
  85. * @param {(name: string | false, buffers: Buffer[], size: number) => Promise<void>} writeFile writes a file
  86. * @param {string | Hash} hashFunction hash function to use
  87. * @returns {Promise<SerializeResult>} resulting file pointer and promise
  88. */
  89. const serialize = async (
  90. middleware,
  91. data,
  92. name,
  93. writeFile,
  94. hashFunction = DEFAULTS.HASH_FUNCTION
  95. ) => {
  96. /** @type {(Buffer[] | Buffer | Promise<SerializeResult>)[]} */
  97. const processedData = [];
  98. /** @type {WeakMap<SerializeResult, LazyFunction>} */
  99. const resultToLazy = new WeakMap();
  100. /** @type {Buffer[] | undefined} */
  101. let lastBuffers;
  102. for (const item of await data) {
  103. if (typeof item === "function") {
  104. if (!SerializerMiddleware.isLazy(item)) {
  105. throw new Error("Unexpected function");
  106. }
  107. if (!SerializerMiddleware.isLazy(item, middleware)) {
  108. throw new Error(
  109. "Unexpected lazy value with non-this target (can't pass through lazy values)"
  110. );
  111. }
  112. lastBuffers = undefined;
  113. const serializedInfo = SerializerMiddleware.getLazySerializedValue(item);
  114. if (serializedInfo) {
  115. if (typeof serializedInfo === "function") {
  116. throw new Error(
  117. "Unexpected lazy value with non-this target (can't pass through lazy values)"
  118. );
  119. } else {
  120. processedData.push(serializedInfo);
  121. }
  122. } else {
  123. const content = item();
  124. if (content) {
  125. const options = SerializerMiddleware.getLazyOptions(item);
  126. processedData.push(
  127. serialize(
  128. middleware,
  129. /** @type {BufferSerializableType[]} */
  130. (content),
  131. (options && options.name) || true,
  132. writeFile,
  133. hashFunction
  134. ).then(result => {
  135. /** @type {LazyOptions} */
  136. (item.options).size = result.size;
  137. resultToLazy.set(result, item);
  138. return result;
  139. })
  140. );
  141. } else {
  142. throw new Error(
  143. "Unexpected falsy value returned by lazy value function"
  144. );
  145. }
  146. }
  147. } else if (item) {
  148. if (lastBuffers) {
  149. lastBuffers.push(item);
  150. } else {
  151. lastBuffers = [item];
  152. processedData.push(lastBuffers);
  153. }
  154. } else {
  155. throw new Error("Unexpected falsy value in items array");
  156. }
  157. }
  158. /** @type {BackgroundJob[]} */
  159. const backgroundJobs = [];
  160. const resolvedData = (await Promise.all(processedData)).map(item => {
  161. if (Array.isArray(item) || Buffer.isBuffer(item)) return item;
  162. backgroundJobs.push(
  163. /** @type {BackgroundJob} */
  164. (item.backgroundJob)
  165. );
  166. // create pointer buffer from size and name
  167. const name = /** @type {string} */ (item.name);
  168. const nameBuffer = Buffer.from(name);
  169. const buf = Buffer.allocUnsafe(8 + nameBuffer.length);
  170. writeUInt64LE(buf, item.size, 0);
  171. nameBuffer.copy(buf, 8, 0);
  172. const lazy =
  173. /** @type {LazyFunction} */
  174. (resultToLazy.get(item));
  175. SerializerMiddleware.setLazySerializedValue(lazy, buf);
  176. return buf;
  177. });
  178. /** @type {number[]} */
  179. const lengths = [];
  180. for (const item of resolvedData) {
  181. if (Array.isArray(item)) {
  182. let l = 0;
  183. for (const b of item) l += b.length;
  184. while (l > 0x7fffffff) {
  185. lengths.push(0x7fffffff);
  186. l -= 0x7fffffff;
  187. }
  188. lengths.push(l);
  189. } else if (item) {
  190. lengths.push(-item.length);
  191. } else {
  192. throw new Error(`Unexpected falsy value in resolved data ${item}`);
  193. }
  194. }
  195. const header = Buffer.allocUnsafe(8 + lengths.length * 4);
  196. header.writeUInt32LE(VERSION, 0);
  197. header.writeUInt32LE(lengths.length, 4);
  198. for (let i = 0; i < lengths.length; i++) {
  199. header.writeInt32LE(lengths[i], 8 + i * 4);
  200. }
  201. /** @type {Buffer[]} */
  202. const buf = [header];
  203. for (const item of resolvedData) {
  204. if (Array.isArray(item)) {
  205. for (const b of item) buf.push(b);
  206. } else if (item) {
  207. buf.push(item);
  208. }
  209. }
  210. if (name === true) {
  211. name = hashForName(buf, hashFunction);
  212. }
  213. let size = 0;
  214. for (const b of buf) size += b.length;
  215. backgroundJobs.push(writeFile(name, buf, size));
  216. return {
  217. size,
  218. name,
  219. backgroundJob:
  220. backgroundJobs.length === 1
  221. ? backgroundJobs[0]
  222. : /** @type {BackgroundJob} */ (Promise.all(backgroundJobs))
  223. };
  224. };
  225. /**
  226. * @param {FileMiddleware} middleware this
  227. * @param {string | false} name filename
  228. * @param {(name: string | false) => Promise<Buffer[]>} readFile read content of a file
  229. * @returns {Promise<BufferSerializableType[]>} deserialized data
  230. */
  231. const deserialize = async (middleware, name, readFile) => {
  232. const contents = await readFile(name);
  233. if (contents.length === 0) throw new Error(`Empty file ${name}`);
  234. let contentsIndex = 0;
  235. let contentItem = contents[0];
  236. let contentItemLength = contentItem.length;
  237. let contentPosition = 0;
  238. if (contentItemLength === 0) throw new Error(`Empty file ${name}`);
  239. const nextContent = () => {
  240. contentsIndex++;
  241. contentItem = contents[contentsIndex];
  242. contentItemLength = contentItem.length;
  243. contentPosition = 0;
  244. };
  245. /**
  246. * @param {number} n number of bytes to ensure
  247. */
  248. const ensureData = n => {
  249. if (contentPosition === contentItemLength) {
  250. nextContent();
  251. }
  252. while (contentItemLength - contentPosition < n) {
  253. const remaining = contentItem.slice(contentPosition);
  254. let lengthFromNext = n - remaining.length;
  255. const buffers = [remaining];
  256. for (let i = contentsIndex + 1; i < contents.length; i++) {
  257. const l = contents[i].length;
  258. if (l > lengthFromNext) {
  259. buffers.push(contents[i].slice(0, lengthFromNext));
  260. contents[i] = contents[i].slice(lengthFromNext);
  261. lengthFromNext = 0;
  262. break;
  263. } else {
  264. buffers.push(contents[i]);
  265. contentsIndex = i;
  266. lengthFromNext -= l;
  267. }
  268. }
  269. if (lengthFromNext > 0) throw new Error("Unexpected end of data");
  270. contentItem = Buffer.concat(buffers, n);
  271. contentItemLength = n;
  272. contentPosition = 0;
  273. }
  274. };
  275. /**
  276. * @returns {number} value value
  277. */
  278. const readUInt32LE = () => {
  279. ensureData(4);
  280. const value = contentItem.readUInt32LE(contentPosition);
  281. contentPosition += 4;
  282. return value;
  283. };
  284. /**
  285. * @returns {number} value value
  286. */
  287. const readInt32LE = () => {
  288. ensureData(4);
  289. const value = contentItem.readInt32LE(contentPosition);
  290. contentPosition += 4;
  291. return value;
  292. };
  293. /**
  294. * @param {number} l length
  295. * @returns {Buffer} buffer
  296. */
  297. const readSlice = l => {
  298. ensureData(l);
  299. if (contentPosition === 0 && contentItemLength === l) {
  300. const result = contentItem;
  301. if (contentsIndex + 1 < contents.length) {
  302. nextContent();
  303. } else {
  304. contentPosition = l;
  305. }
  306. return result;
  307. }
  308. const result = contentItem.slice(contentPosition, contentPosition + l);
  309. contentPosition += l;
  310. // we clone the buffer here to allow the original content to be garbage collected
  311. return l * 2 < contentItem.buffer.byteLength ? Buffer.from(result) : result;
  312. };
  313. const version = readUInt32LE();
  314. if (version !== VERSION) {
  315. throw new Error("Invalid file version");
  316. }
  317. const sectionCount = readUInt32LE();
  318. const lengths = [];
  319. let lastLengthPositive = false;
  320. for (let i = 0; i < sectionCount; i++) {
  321. const value = readInt32LE();
  322. const valuePositive = value >= 0;
  323. if (lastLengthPositive && valuePositive) {
  324. lengths[lengths.length - 1] += value;
  325. } else {
  326. lengths.push(value);
  327. lastLengthPositive = valuePositive;
  328. }
  329. }
  330. /** @type {BufferSerializableType[]} */
  331. const result = [];
  332. for (let length of lengths) {
  333. if (length < 0) {
  334. const slice = readSlice(-length);
  335. const size = Number(readUInt64LE(slice, 0));
  336. const nameBuffer = slice.slice(8);
  337. const name = nameBuffer.toString();
  338. const lazy =
  339. /** @type {LazyFunction} */
  340. (
  341. SerializerMiddleware.createLazy(
  342. memoize(() => deserialize(middleware, name, readFile)),
  343. middleware,
  344. { name, size },
  345. slice
  346. )
  347. );
  348. result.push(lazy);
  349. } else {
  350. if (contentPosition === contentItemLength) {
  351. nextContent();
  352. } else if (contentPosition !== 0) {
  353. if (length <= contentItemLength - contentPosition) {
  354. result.push(
  355. Buffer.from(
  356. contentItem.buffer,
  357. contentItem.byteOffset + contentPosition,
  358. length
  359. )
  360. );
  361. contentPosition += length;
  362. length = 0;
  363. } else {
  364. const l = contentItemLength - contentPosition;
  365. result.push(
  366. Buffer.from(
  367. contentItem.buffer,
  368. contentItem.byteOffset + contentPosition,
  369. l
  370. )
  371. );
  372. length -= l;
  373. contentPosition = contentItemLength;
  374. }
  375. } else if (length >= contentItemLength) {
  376. result.push(contentItem);
  377. length -= contentItemLength;
  378. contentPosition = contentItemLength;
  379. } else {
  380. result.push(
  381. Buffer.from(contentItem.buffer, contentItem.byteOffset, length)
  382. );
  383. contentPosition += length;
  384. length = 0;
  385. }
  386. while (length > 0) {
  387. nextContent();
  388. if (length >= contentItemLength) {
  389. result.push(contentItem);
  390. length -= contentItemLength;
  391. contentPosition = contentItemLength;
  392. } else {
  393. result.push(
  394. Buffer.from(contentItem.buffer, contentItem.byteOffset, length)
  395. );
  396. contentPosition += length;
  397. length = 0;
  398. }
  399. }
  400. }
  401. }
  402. return result;
  403. };
  404. /** @typedef {BufferSerializableType[]} DeserializedType */
  405. /** @typedef {true} SerializedType */
  406. /** @typedef {{ filename: string, extension?: string }} Context */
  407. /**
  408. * @extends {SerializerMiddleware<DeserializedType, SerializedType, Context>}
  409. */
  410. class FileMiddleware extends SerializerMiddleware {
  411. /**
  412. * @param {IntermediateFileSystem} fs filesystem
  413. * @param {string | Hash} hashFunction hash function to use
  414. */
  415. constructor(fs, hashFunction = DEFAULTS.HASH_FUNCTION) {
  416. super();
  417. this.fs = fs;
  418. this._hashFunction = hashFunction;
  419. }
  420. /**
  421. * @param {DeserializedType} data data
  422. * @param {Context} context context object
  423. * @returns {SerializedType | Promise<SerializedType> | null} serialized data
  424. */
  425. serialize(data, context) {
  426. const { filename, extension = "" } = context;
  427. return new Promise((resolve, reject) => {
  428. mkdirp(this.fs, dirname(this.fs, filename), err => {
  429. if (err) return reject(err);
  430. // It's important that we don't touch existing files during serialization
  431. // because serialize may read existing files (when deserializing)
  432. const allWrittenFiles = new Set();
  433. /**
  434. * @param {string | false} name name
  435. * @param {Buffer[]} content content
  436. * @param {number} size size
  437. * @returns {Promise<void>}
  438. */
  439. const writeFile = async (name, content, size) => {
  440. const file = name
  441. ? join(this.fs, filename, `../${name}${extension}`)
  442. : filename;
  443. await new Promise(
  444. /**
  445. * @param {(value?: undefined) => void} resolve resolve
  446. * @param {(reason?: Error | null) => void} reject reject
  447. */
  448. (resolve, reject) => {
  449. let stream = this.fs.createWriteStream(`${file}_`);
  450. let compression;
  451. if (file.endsWith(".gz")) {
  452. compression = createGzip({
  453. chunkSize: COMPRESSION_CHUNK_SIZE,
  454. level: zConstants.Z_BEST_SPEED
  455. });
  456. } else if (file.endsWith(".br")) {
  457. compression = createBrotliCompress({
  458. chunkSize: COMPRESSION_CHUNK_SIZE,
  459. params: {
  460. [zConstants.BROTLI_PARAM_MODE]: zConstants.BROTLI_MODE_TEXT,
  461. [zConstants.BROTLI_PARAM_QUALITY]: 2,
  462. [zConstants.BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING]: true,
  463. [zConstants.BROTLI_PARAM_SIZE_HINT]: size
  464. }
  465. });
  466. }
  467. if (compression) {
  468. pipeline(compression, stream, reject);
  469. stream = compression;
  470. stream.on("finish", () => resolve());
  471. } else {
  472. stream.on("error", err => reject(err));
  473. stream.on("finish", () => resolve());
  474. }
  475. // split into chunks for WRITE_LIMIT_CHUNK size
  476. /** @type {Buffer[]} */
  477. const chunks = [];
  478. for (const b of content) {
  479. if (b.length < WRITE_LIMIT_CHUNK) {
  480. chunks.push(b);
  481. } else {
  482. for (let i = 0; i < b.length; i += WRITE_LIMIT_CHUNK) {
  483. chunks.push(b.slice(i, i + WRITE_LIMIT_CHUNK));
  484. }
  485. }
  486. }
  487. const len = chunks.length;
  488. let i = 0;
  489. /**
  490. * @param {(Error | null)=} err err
  491. */
  492. const batchWrite = err => {
  493. // will be handled in "on" error handler
  494. if (err) return;
  495. if (i === len) {
  496. stream.end();
  497. return;
  498. }
  499. // queue up a batch of chunks up to the write limit
  500. // end is exclusive
  501. let end = i;
  502. let sum = chunks[end++].length;
  503. while (end < len) {
  504. sum += chunks[end].length;
  505. if (sum > WRITE_LIMIT_TOTAL) break;
  506. end++;
  507. }
  508. while (i < end - 1) {
  509. stream.write(chunks[i++]);
  510. }
  511. stream.write(chunks[i++], batchWrite);
  512. };
  513. batchWrite();
  514. }
  515. );
  516. if (name) allWrittenFiles.add(file);
  517. };
  518. resolve(
  519. serialize(this, data, false, writeFile, this._hashFunction).then(
  520. async ({ backgroundJob }) => {
  521. await backgroundJob;
  522. // Rename the index file to disallow access during inconsistent file state
  523. await new Promise(
  524. /**
  525. * @param {(value?: undefined) => void} resolve resolve
  526. */
  527. resolve => {
  528. this.fs.rename(filename, `${filename}.old`, _err => {
  529. resolve();
  530. });
  531. }
  532. );
  533. // update all written files
  534. await Promise.all(
  535. Array.from(
  536. allWrittenFiles,
  537. file =>
  538. new Promise(
  539. /**
  540. * @param {(value?: undefined) => void} resolve resolve
  541. * @param {(reason?: Error | null) => void} reject reject
  542. * @returns {void}
  543. */
  544. (resolve, reject) => {
  545. this.fs.rename(`${file}_`, file, err => {
  546. if (err) return reject(err);
  547. resolve();
  548. });
  549. }
  550. )
  551. )
  552. );
  553. // As final step automatically update the index file to have a consistent pack again
  554. await new Promise(
  555. /**
  556. * @param {(value?: undefined) => void} resolve resolve
  557. * @returns {void}
  558. */
  559. resolve => {
  560. this.fs.rename(`${filename}_`, filename, err => {
  561. if (err) return reject(err);
  562. resolve();
  563. });
  564. }
  565. );
  566. return /** @type {true} */ (true);
  567. }
  568. )
  569. );
  570. });
  571. });
  572. }
  573. /**
  574. * @param {SerializedType} data data
  575. * @param {Context} context context object
  576. * @returns {DeserializedType | Promise<DeserializedType>} deserialized data
  577. */
  578. deserialize(data, context) {
  579. const { filename, extension = "" } = context;
  580. /**
  581. * @param {string | boolean} name name
  582. * @returns {Promise<Buffer[]>} result
  583. */
  584. const readFile = name =>
  585. new Promise((resolve, reject) => {
  586. const file = name
  587. ? join(this.fs, filename, `../${name}${extension}`)
  588. : filename;
  589. this.fs.stat(file, (err, stats) => {
  590. if (err) {
  591. reject(err);
  592. return;
  593. }
  594. let remaining = /** @type {IStats} */ (stats).size;
  595. /** @type {Buffer | undefined} */
  596. let currentBuffer;
  597. /** @type {number | undefined} */
  598. let currentBufferUsed;
  599. /** @type {Buffer[]} */
  600. const buf = [];
  601. /** @type {import("zlib").Zlib & import("stream").Transform | undefined} */
  602. let decompression;
  603. if (file.endsWith(".gz")) {
  604. decompression = createGunzip({
  605. chunkSize: DECOMPRESSION_CHUNK_SIZE
  606. });
  607. } else if (file.endsWith(".br")) {
  608. decompression = createBrotliDecompress({
  609. chunkSize: DECOMPRESSION_CHUNK_SIZE
  610. });
  611. }
  612. if (decompression) {
  613. /** @typedef {(value: Buffer[] | PromiseLike<Buffer[]>) => void} NewResolve */
  614. /** @typedef {(reason?: Error) => void} NewReject */
  615. /** @type {NewResolve | undefined} */
  616. let newResolve;
  617. /** @type {NewReject | undefined} */
  618. let newReject;
  619. resolve(
  620. Promise.all([
  621. new Promise((rs, rj) => {
  622. newResolve = rs;
  623. newReject = rj;
  624. }),
  625. new Promise(
  626. /**
  627. * @param {(value?: undefined) => void} resolve resolve
  628. * @param {(reason?: Error) => void} reject reject
  629. */
  630. (resolve, reject) => {
  631. decompression.on("data", chunk => buf.push(chunk));
  632. decompression.on("end", () => resolve());
  633. decompression.on("error", err => reject(err));
  634. }
  635. )
  636. ]).then(() => buf)
  637. );
  638. resolve = /** @type {NewResolve} */ (newResolve);
  639. reject = /** @type {NewReject} */ (newReject);
  640. }
  641. this.fs.open(file, "r", (err, _fd) => {
  642. if (err) {
  643. reject(err);
  644. return;
  645. }
  646. const fd = /** @type {number} */ (_fd);
  647. const read = () => {
  648. if (currentBuffer === undefined) {
  649. currentBuffer = Buffer.allocUnsafeSlow(
  650. Math.min(
  651. constants.MAX_LENGTH,
  652. remaining,
  653. decompression ? DECOMPRESSION_CHUNK_SIZE : Infinity
  654. )
  655. );
  656. currentBufferUsed = 0;
  657. }
  658. let readBuffer = currentBuffer;
  659. let readOffset = /** @type {number} */ (currentBufferUsed);
  660. let readLength =
  661. currentBuffer.length -
  662. /** @type {number} */ (currentBufferUsed);
  663. // values passed to fs.read must be valid int32 values
  664. if (readOffset > 0x7fffffff) {
  665. readBuffer = currentBuffer.slice(readOffset);
  666. readOffset = 0;
  667. }
  668. if (readLength > 0x7fffffff) {
  669. readLength = 0x7fffffff;
  670. }
  671. this.fs.read(
  672. fd,
  673. readBuffer,
  674. readOffset,
  675. readLength,
  676. null,
  677. (err, bytesRead) => {
  678. if (err) {
  679. this.fs.close(fd, () => {
  680. reject(err);
  681. });
  682. return;
  683. }
  684. /** @type {number} */
  685. (currentBufferUsed) += bytesRead;
  686. remaining -= bytesRead;
  687. if (
  688. currentBufferUsed ===
  689. /** @type {Buffer} */
  690. (currentBuffer).length
  691. ) {
  692. if (decompression) {
  693. decompression.write(currentBuffer);
  694. } else {
  695. buf.push(
  696. /** @type {Buffer} */
  697. (currentBuffer)
  698. );
  699. }
  700. currentBuffer = undefined;
  701. if (remaining === 0) {
  702. if (decompression) {
  703. decompression.end();
  704. }
  705. this.fs.close(fd, err => {
  706. if (err) {
  707. reject(err);
  708. return;
  709. }
  710. resolve(buf);
  711. });
  712. return;
  713. }
  714. }
  715. read();
  716. }
  717. );
  718. };
  719. read();
  720. });
  721. });
  722. });
  723. return deserialize(this, false, readFile);
  724. }
  725. }
  726. module.exports = FileMiddleware;