• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypePush,
5  ArrayPrototypeShift,
6  FunctionPrototypeBind,
7  ObjectDefineProperty,
8  PromiseReject,
9  Symbol,
10  SymbolAsyncIterator,
11} = primordials;
12
13const pathModule = require('path');
14const binding = internalBinding('fs');
15const dirBinding = internalBinding('fs_dir');
16const {
17  codes: {
18    ERR_DIR_CLOSED,
19    ERR_DIR_CONCURRENT_OPERATION,
20    ERR_MISSING_ARGS,
21  },
22} = require('internal/errors');
23
24const { FSReqCallback } = binding;
25const internalUtil = require('internal/util');
26const {
27  getDirent,
28  getOptions,
29  getValidatedPath,
30  handleErrorFromBinding,
31} = require('internal/fs/utils');
32const {
33  validateFunction,
34  validateUint32,
35} = require('internal/validators');
36
37const kDirHandle = Symbol('kDirHandle');
38const kDirPath = Symbol('kDirPath');
39const kDirBufferedEntries = Symbol('kDirBufferedEntries');
40const kDirClosed = Symbol('kDirClosed');
41const kDirOptions = Symbol('kDirOptions');
42const kDirReadImpl = Symbol('kDirReadImpl');
43const kDirReadPromisified = Symbol('kDirReadPromisified');
44const kDirClosePromisified = Symbol('kDirClosePromisified');
45const kDirOperationQueue = Symbol('kDirOperationQueue');
46
47class Dir {
48  constructor(handle, path, options) {
49    if (handle == null) throw new ERR_MISSING_ARGS('handle');
50    this[kDirHandle] = handle;
51    this[kDirBufferedEntries] = [];
52    this[kDirPath] = path;
53    this[kDirClosed] = false;
54
55    // Either `null` or an Array of pending operations (= functions to be called
56    // once the current operation is done).
57    this[kDirOperationQueue] = null;
58
59    this[kDirOptions] = {
60      bufferSize: 32,
61      ...getOptions(options, {
62        encoding: 'utf8',
63      }),
64    };
65
66    validateUint32(this[kDirOptions].bufferSize, 'options.bufferSize', true);
67
68    this[kDirReadPromisified] = FunctionPrototypeBind(
69      internalUtil.promisify(this[kDirReadImpl]), this, false);
70    this[kDirClosePromisified] = FunctionPrototypeBind(
71      internalUtil.promisify(this.close), this);
72  }
73
74  get path() {
75    return this[kDirPath];
76  }
77
78  read(callback) {
79    return this[kDirReadImpl](true, callback);
80  }
81
82  [kDirReadImpl](maybeSync, callback) {
83    if (this[kDirClosed] === true) {
84      throw new ERR_DIR_CLOSED();
85    }
86
87    if (callback === undefined) {
88      return this[kDirReadPromisified]();
89    }
90
91    validateFunction(callback, 'callback');
92
93    if (this[kDirOperationQueue] !== null) {
94      ArrayPrototypePush(this[kDirOperationQueue], () => {
95        this[kDirReadImpl](maybeSync, callback);
96      });
97      return;
98    }
99
100    if (this[kDirBufferedEntries].length > 0) {
101      try {
102        const dirent = ArrayPrototypeShift(this[kDirBufferedEntries]);
103
104        if (this[kDirOptions].recursive && dirent.isDirectory()) {
105          this.readSyncRecursive(dirent);
106        }
107
108        if (maybeSync)
109          process.nextTick(callback, null, dirent);
110        else
111          callback(null, dirent);
112        return;
113      } catch (error) {
114        return callback(error);
115      }
116    }
117
118    const req = new FSReqCallback();
119    req.oncomplete = (err, result) => {
120      process.nextTick(() => {
121        const queue = this[kDirOperationQueue];
122        this[kDirOperationQueue] = null;
123        for (const op of queue) op();
124      });
125
126      if (err || result === null) {
127        return callback(err, result);
128      }
129
130      try {
131        this.processReadResult(this[kDirPath], result);
132        const dirent = ArrayPrototypeShift(this[kDirBufferedEntries]);
133        if (this[kDirOptions].recursive && dirent.isDirectory()) {
134          this.readSyncRecursive(dirent);
135        }
136        callback(null, dirent);
137      } catch (error) {
138        callback(error);
139      }
140    };
141
142    this[kDirOperationQueue] = [];
143    this[kDirHandle].read(
144      this[kDirOptions].encoding,
145      this[kDirOptions].bufferSize,
146      req,
147    );
148  }
149
150  processReadResult(path, result) {
151    for (let i = 0; i < result.length; i += 2) {
152      ArrayPrototypePush(
153        this[kDirBufferedEntries],
154        getDirent(
155          pathModule.join(path, result[i]),
156          result[i],
157          result[i + 1],
158        ),
159      );
160    }
161  }
162
163  readSyncRecursive(dirent) {
164    const ctx = { path: dirent.path };
165    const handle = dirBinding.opendir(
166      pathModule.toNamespacedPath(dirent.path),
167      this[kDirOptions].encoding,
168      undefined,
169      ctx,
170    );
171    handleErrorFromBinding(ctx);
172    const result = handle.read(
173      this[kDirOptions].encoding,
174      this[kDirOptions].bufferSize,
175      undefined,
176      ctx,
177    );
178
179    if (result) {
180      this.processReadResult(dirent.path, result);
181    }
182
183    handle.close(undefined, ctx);
184    handleErrorFromBinding(ctx);
185  }
186
187  readSync() {
188    if (this[kDirClosed] === true) {
189      throw new ERR_DIR_CLOSED();
190    }
191
192    if (this[kDirOperationQueue] !== null) {
193      throw new ERR_DIR_CONCURRENT_OPERATION();
194    }
195
196    if (this[kDirBufferedEntries].length > 0) {
197      const dirent = ArrayPrototypeShift(this[kDirBufferedEntries]);
198      if (this[kDirOptions].recursive && dirent.isDirectory()) {
199        this.readSyncRecursive(dirent);
200      }
201      return dirent;
202    }
203
204    const ctx = { path: this[kDirPath] };
205    const result = this[kDirHandle].read(
206      this[kDirOptions].encoding,
207      this[kDirOptions].bufferSize,
208      undefined,
209      ctx,
210    );
211    handleErrorFromBinding(ctx);
212
213    if (result === null) {
214      return result;
215    }
216
217    this.processReadResult(this[kDirPath], result);
218
219    const dirent = ArrayPrototypeShift(this[kDirBufferedEntries]);
220    if (this[kDirOptions].recursive && dirent.isDirectory()) {
221      this.readSyncRecursive(dirent);
222    }
223    return dirent;
224  }
225
226  close(callback) {
227    // Promise
228    if (callback === undefined) {
229      if (this[kDirClosed] === true) {
230        return PromiseReject(new ERR_DIR_CLOSED());
231      }
232      return this[kDirClosePromisified]();
233    }
234
235    // callback
236    validateFunction(callback, 'callback');
237
238    if (this[kDirClosed] === true) {
239      process.nextTick(callback, new ERR_DIR_CLOSED());
240      return;
241    }
242
243    if (this[kDirOperationQueue] !== null) {
244      ArrayPrototypePush(this[kDirOperationQueue], () => {
245        this.close(callback);
246      });
247      return;
248    }
249
250    this[kDirClosed] = true;
251    const req = new FSReqCallback();
252    req.oncomplete = callback;
253    this[kDirHandle].close(req);
254  }
255
256  closeSync() {
257    if (this[kDirClosed] === true) {
258      throw new ERR_DIR_CLOSED();
259    }
260
261    if (this[kDirOperationQueue] !== null) {
262      throw new ERR_DIR_CONCURRENT_OPERATION();
263    }
264
265    this[kDirClosed] = true;
266    const ctx = { path: this[kDirPath] };
267    const result = this[kDirHandle].close(undefined, ctx);
268    handleErrorFromBinding(ctx);
269    return result;
270  }
271
272  async* entries() {
273    try {
274      while (true) {
275        const result = await this[kDirReadPromisified]();
276        if (result === null) {
277          break;
278        }
279        yield result;
280      }
281    } finally {
282      await this[kDirClosePromisified]();
283    }
284  }
285}
286
287ObjectDefineProperty(Dir.prototype, SymbolAsyncIterator, {
288  __proto__: null,
289  value: Dir.prototype.entries,
290  enumerable: false,
291  writable: true,
292  configurable: true,
293});
294
295function opendir(path, options, callback) {
296  callback = typeof options === 'function' ? options : callback;
297  validateFunction(callback, 'callback');
298
299  path = getValidatedPath(path);
300  options = getOptions(options, {
301    encoding: 'utf8',
302  });
303
304  function opendirCallback(error, handle) {
305    if (error) {
306      callback(error);
307    } else {
308      callback(null, new Dir(handle, path, options));
309    }
310  }
311
312  const req = new FSReqCallback();
313  req.oncomplete = opendirCallback;
314
315  dirBinding.opendir(
316    pathModule.toNamespacedPath(path),
317    options.encoding,
318    req,
319  );
320}
321
322function opendirSync(path, options) {
323  path = getValidatedPath(path);
324  options = getOptions(options, {
325    encoding: 'utf8',
326  });
327
328  const ctx = { path };
329  const handle = dirBinding.opendir(
330    pathModule.toNamespacedPath(path),
331    options.encoding,
332    undefined,
333    ctx,
334  );
335  handleErrorFromBinding(ctx);
336
337  return new Dir(handle, path, options);
338}
339
340module.exports = {
341  Dir,
342  opendir,
343  opendirSync,
344};
345