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