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