FileMiddleware.js 21 KB

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