UNPKG

4.25 kBJavaScriptView Raw
1'use strict';
2
3const {AbortError} = require('./error');
4const {FSReqCallback} = process.binding('fs');
5
6/**
7 * This is a workaround for getting access to the ReadFileContext
8 * prototype, which we need to be able to patch its methods.
9 * @returns {object}
10 */
11exports.getReadFileContextPrototype = function() {
12 const fs = require('fs');
13 const fsBinding = process.binding('fs');
14
15 const originalOpen = fsBinding.open;
16
17 let proto;
18 fsBinding.open = (_path, _flags, _mode, req) => {
19 proto = Object.getPrototypeOf(req.context);
20 return originalOpen.apply(fsBinding, [_path, _flags, _mode, req]);
21 };
22
23 fs.readFile('/ignored.txt', () => {});
24
25 fsBinding.open = originalOpen;
26
27 return proto;
28};
29
30/**
31 * This patches the ReadFileContext prototype to use mocked bindings
32 * when available. This entire implementation is more or less fully
33 * copied over from Node.js's /lib/internal/fs/read_file_context.js
34 *
35 * This patch is required to support Node.js v16+, where the ReadFileContext
36 * closes directly over the internal fs bindings, and is also eagerly loader.
37 *
38 * See https://github.com/tschaub/mock-fs/issues/332 for more information.
39 *
40 * @param {object} prototype The ReadFileContext prototype object to patch.
41 */
42exports.patchReadFileContext = function(prototype) {
43 const origRead = prototype.read;
44 const origClose = prototype.close;
45
46 const kReadFileUnknownBufferLength = 64 * 1024;
47 const kReadFileBufferLength = 512 * 1024;
48
49 function readFileAfterRead(err, bytesRead) {
50 const context = this.context;
51
52 if (err) {
53 return context.close(err);
54 }
55 context.pos += bytesRead;
56
57 if (context.pos === context.size || bytesRead === 0) {
58 context.close();
59 } else {
60 if (context.size === 0) {
61 // Unknown size, just read until we don't get bytes.
62 const buffer =
63 bytesRead === kReadFileUnknownBufferLength
64 ? context.buffer
65 : context.buffer.slice(0, bytesRead);
66 context.buffers.push(buffer);
67 }
68 context.read();
69 }
70 }
71
72 function readFileAfterClose(err) {
73 const context = this.context;
74 const callback = context.callback;
75 let buffer = null;
76
77 if (context.err || err) {
78 // This is a simplification from Node.js, where we don't bother merging the errors
79 return callback(context.err || err);
80 }
81
82 try {
83 if (context.size === 0) {
84 buffer = Buffer.concat(context.buffers, context.pos);
85 } else if (context.pos < context.size) {
86 buffer = context.buffer.slice(0, context.pos);
87 } else {
88 buffer = context.buffer;
89 }
90
91 if (context.encoding) {
92 buffer = buffer.toString(context.encoding);
93 }
94 } catch (err) {
95 return callback(err);
96 }
97
98 callback(null, buffer);
99 }
100
101 prototype.read = function read() {
102 if (!prototype._mockedBinding) {
103 return origRead.apply(this, arguments);
104 }
105
106 let buffer;
107 let offset;
108 let length;
109
110 if (this.signal && this.signal.aborted) {
111 return this.close(new AbortError());
112 }
113 if (this.size === 0) {
114 buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength);
115 offset = 0;
116 length = kReadFileUnknownBufferLength;
117 this.buffer = buffer;
118 } else {
119 buffer = this.buffer;
120 offset = this.pos;
121 length = Math.min(kReadFileBufferLength, this.size - this.pos);
122 }
123
124 const req = new FSReqCallback();
125 req.oncomplete = readFileAfterRead;
126 req.context = this;
127
128 // This call and the one in close() is what we want to change, the
129 // rest is pretty much the same as Node.js except we don't have access
130 // to some of the internal optimizations.
131 prototype._mockedBinding.read(this.fd, buffer, offset, length, -1, req);
132 };
133
134 prototype.close = function close(err) {
135 if (!prototype._mockedBinding) {
136 return origClose.apply(this, arguments);
137 }
138
139 if (this.isUserFd) {
140 process.nextTick(function tick(context) {
141 readFileAfterClose.apply({context}, [null]);
142 }, this);
143 return;
144 }
145
146 const req = new FSReqCallback();
147 req.oncomplete = readFileAfterClose;
148 req.context = this;
149 this.err = err;
150
151 prototype._mockedBinding.close(this.fd, req);
152 };
153};