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