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