1'use strict'; 2 3const { 4 MathMax, 5 MathMin, 6 NumberIsSafeInteger, 7 Symbol, 8} = primordials; 9 10const { 11 F_OK, 12 O_SYMLINK, 13 O_WRONLY, 14 S_IFMT, 15 S_IFREG 16} = internalBinding('constants').fs; 17const binding = internalBinding('fs'); 18const { Buffer, kMaxLength } = require('buffer'); 19const { 20 ERR_FS_FILE_TOO_LARGE, 21 ERR_INVALID_ARG_TYPE, 22 ERR_INVALID_ARG_VALUE, 23 ERR_METHOD_NOT_IMPLEMENTED 24} = require('internal/errors').codes; 25const { isUint8Array } = require('internal/util/types'); 26const { rimrafPromises } = require('internal/fs/rimraf'); 27const { 28 copyObject, 29 getDirents, 30 getOptions, 31 getStatsFromBinding, 32 getValidatedPath, 33 nullCheck, 34 preprocessSymlinkDestination, 35 stringToFlags, 36 stringToSymlinkType, 37 toUnixTimestamp, 38 validateBufferArray, 39 validateOffsetLengthRead, 40 validateOffsetLengthWrite, 41 validateRmdirOptions, 42 warnOnNonPortableTemplate 43} = require('internal/fs/utils'); 44const { opendir } = require('internal/fs/dir'); 45const { 46 parseMode, 47 validateBuffer, 48 validateInteger, 49 validateUint32 50} = require('internal/validators'); 51const pathModule = require('path'); 52const { promisify } = require('internal/util'); 53 54const kHandle = Symbol('kHandle'); 55const kFd = Symbol('kFd'); 56const { kUsePromises } = binding; 57const { 58 JSTransferable, kDeserialize, kTransfer, kTransferList 59} = require('internal/worker/js_transferable'); 60 61const getDirectoryEntriesPromise = promisify(getDirents); 62 63class FileHandle extends JSTransferable { 64 constructor(filehandle) { 65 super(); 66 this[kHandle] = filehandle; 67 this[kFd] = filehandle ? filehandle.fd : -1; 68 } 69 70 getAsyncId() { 71 return this[kHandle].getAsyncId(); 72 } 73 74 get fd() { 75 return this[kFd]; 76 } 77 78 appendFile(data, options) { 79 return writeFile(this, data, options); 80 } 81 82 chmod(mode) { 83 return fchmod(this, mode); 84 } 85 86 chown(uid, gid) { 87 return fchown(this, uid, gid); 88 } 89 90 datasync() { 91 return fdatasync(this); 92 } 93 94 sync() { 95 return fsync(this); 96 } 97 98 read(buffer, offset, length, position) { 99 return read(this, buffer, offset, length, position); 100 } 101 102 readv(buffers, position) { 103 return readv(this, buffers, position); 104 } 105 106 readFile(options) { 107 return readFile(this, options); 108 } 109 110 stat(options) { 111 return fstat(this, options); 112 } 113 114 truncate(len = 0) { 115 return ftruncate(this, len); 116 } 117 118 utimes(atime, mtime) { 119 return futimes(this, atime, mtime); 120 } 121 122 write(buffer, offset, length, position) { 123 return write(this, buffer, offset, length, position); 124 } 125 126 writev(buffers, position) { 127 return writev(this, buffers, position); 128 } 129 130 writeFile(data, options) { 131 return writeFile(this, data, options); 132 } 133 134 close = () => { 135 this[kFd] = -1; 136 return this[kHandle].close(); 137 } 138 139 [kTransfer]() { 140 const handle = this[kHandle]; 141 this[kFd] = -1; 142 this[kHandle] = null; 143 144 return { 145 data: { handle }, 146 deserializeInfo: 'internal/fs/promises:FileHandle' 147 }; 148 } 149 150 [kTransferList]() { 151 return [ this[kHandle] ]; 152 } 153 154 [kDeserialize]({ handle }) { 155 this[kHandle] = handle; 156 this[kFd] = handle.fd; 157 } 158} 159 160function validateFileHandle(handle) { 161 if (!(handle instanceof FileHandle)) 162 throw new ERR_INVALID_ARG_TYPE('filehandle', 'FileHandle', handle); 163} 164 165async function writeFileHandle(filehandle, data, options) { 166 let buffer = isUint8Array(data) ? 167 data : Buffer.from('' + data, options.encoding || 'utf8'); 168 let remaining = buffer.length; 169 if (remaining === 0) return; 170 do { 171 const { bytesWritten } = 172 await write(filehandle, buffer, 0, 173 MathMin(16384, buffer.length)); 174 remaining -= bytesWritten; 175 buffer = buffer.slice(bytesWritten); 176 } while (remaining > 0); 177} 178 179// Note: This is different from kReadFileBufferLength used for non-promisified 180// fs.readFile. 181const kReadFileMaxChunkSize = 16384; 182 183async function readFileHandle(filehandle, options) { 184 const statFields = await binding.fstat(filehandle.fd, false, kUsePromises); 185 186 let size; 187 if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) { 188 size = statFields[8/* size */]; 189 } else { 190 size = 0; 191 } 192 193 if (size > kMaxLength) 194 throw new ERR_FS_FILE_TOO_LARGE(size); 195 196 const chunks = []; 197 const chunkSize = size === 0 ? 198 kReadFileMaxChunkSize : 199 MathMin(size, kReadFileMaxChunkSize); 200 let endOfFile = false; 201 do { 202 const buf = Buffer.alloc(chunkSize); 203 const { bytesRead, buffer } = 204 await read(filehandle, buf, 0, chunkSize, -1); 205 endOfFile = bytesRead === 0; 206 if (bytesRead > 0) 207 chunks.push(buffer.slice(0, bytesRead)); 208 } while (!endOfFile); 209 210 const result = Buffer.concat(chunks); 211 212 return options.encoding ? result.toString(options.encoding) : result; 213} 214 215// All of the functions are defined as async in order to ensure that errors 216// thrown cause promise rejections rather than being thrown synchronously. 217async function access(path, mode = F_OK) { 218 path = getValidatedPath(path); 219 220 mode = mode | 0; 221 return binding.access(pathModule.toNamespacedPath(path), mode, 222 kUsePromises); 223} 224 225async function copyFile(src, dest, flags) { 226 src = getValidatedPath(src, 'src'); 227 dest = getValidatedPath(dest, 'dest'); 228 flags = flags | 0; 229 return binding.copyFile(pathModule.toNamespacedPath(src), 230 pathModule.toNamespacedPath(dest), 231 flags, kUsePromises); 232} 233 234// Note that unlike fs.open() which uses numeric file descriptors, 235// fsPromises.open() uses the fs.FileHandle class. 236async function open(path, flags, mode) { 237 path = getValidatedPath(path); 238 if (arguments.length < 2) flags = 'r'; 239 const flagsNumber = stringToFlags(flags); 240 mode = parseMode(mode, 'mode', 0o666); 241 return new FileHandle( 242 await binding.openFileHandle(pathModule.toNamespacedPath(path), 243 flagsNumber, mode, kUsePromises)); 244} 245 246async function read(handle, buffer, offset, length, position) { 247 validateFileHandle(handle); 248 validateBuffer(buffer); 249 250 offset |= 0; 251 length |= 0; 252 253 if (length === 0) 254 return { bytesRead: length, buffer }; 255 256 if (buffer.length === 0) { 257 throw new ERR_INVALID_ARG_VALUE('buffer', buffer, 258 'is empty and cannot be written'); 259 } 260 261 validateOffsetLengthRead(offset, length, buffer.length); 262 263 if (!NumberIsSafeInteger(position)) 264 position = -1; 265 266 const bytesRead = (await binding.read(handle.fd, buffer, offset, length, 267 position, kUsePromises)) || 0; 268 269 return { bytesRead, buffer }; 270} 271 272async function readv(handle, buffers, position) { 273 validateFileHandle(handle); 274 validateBufferArray(buffers); 275 276 if (typeof position !== 'number') 277 position = null; 278 279 const bytesRead = (await binding.readBuffers(handle.fd, buffers, position, 280 kUsePromises)) || 0; 281 return { bytesRead, buffers }; 282} 283 284async function write(handle, buffer, offset, length, position) { 285 validateFileHandle(handle); 286 287 if (buffer.length === 0) 288 return { bytesWritten: 0, buffer }; 289 290 if (isUint8Array(buffer)) { 291 if (typeof offset !== 'number') 292 offset = 0; 293 if (typeof length !== 'number') 294 length = buffer.length - offset; 295 if (typeof position !== 'number') 296 position = null; 297 validateOffsetLengthWrite(offset, length, buffer.byteLength); 298 const bytesWritten = 299 (await binding.writeBuffer(handle.fd, buffer, offset, 300 length, position, kUsePromises)) || 0; 301 return { bytesWritten, buffer }; 302 } 303 304 if (typeof buffer !== 'string') 305 buffer += ''; 306 const bytesWritten = (await binding.writeString(handle.fd, buffer, offset, 307 length, kUsePromises)) || 0; 308 return { bytesWritten, buffer }; 309} 310 311async function writev(handle, buffers, position) { 312 validateFileHandle(handle); 313 validateBufferArray(buffers); 314 315 if (typeof position !== 'number') 316 position = null; 317 318 const bytesWritten = (await binding.writeBuffers(handle.fd, buffers, position, 319 kUsePromises)) || 0; 320 return { bytesWritten, buffers }; 321} 322 323async function rename(oldPath, newPath) { 324 oldPath = getValidatedPath(oldPath, 'oldPath'); 325 newPath = getValidatedPath(newPath, 'newPath'); 326 return binding.rename(pathModule.toNamespacedPath(oldPath), 327 pathModule.toNamespacedPath(newPath), 328 kUsePromises); 329} 330 331async function truncate(path, len = 0) { 332 const fd = await open(path, 'r+'); 333 return ftruncate(fd, len).finally(fd.close.bind(fd)); 334} 335 336async function ftruncate(handle, len = 0) { 337 validateFileHandle(handle); 338 validateInteger(len, 'len'); 339 len = MathMax(0, len); 340 return binding.ftruncate(handle.fd, len, kUsePromises); 341} 342 343async function rmdir(path, options) { 344 path = pathModule.toNamespacedPath(getValidatedPath(path)); 345 options = validateRmdirOptions(options); 346 347 if (options.recursive) { 348 return rimrafPromises(path, options); 349 } 350 351 return binding.rmdir(path, kUsePromises); 352} 353 354async function fdatasync(handle) { 355 validateFileHandle(handle); 356 return binding.fdatasync(handle.fd, kUsePromises); 357} 358 359async function fsync(handle) { 360 validateFileHandle(handle); 361 return binding.fsync(handle.fd, kUsePromises); 362} 363 364async function mkdir(path, options) { 365 if (typeof options === 'number' || typeof options === 'string') { 366 options = { mode: options }; 367 } 368 const { 369 recursive = false, 370 mode = 0o777 371 } = options || {}; 372 path = getValidatedPath(path); 373 if (typeof recursive !== 'boolean') 374 throw new ERR_INVALID_ARG_TYPE('options.recursive', 'boolean', recursive); 375 376 return binding.mkdir(pathModule.toNamespacedPath(path), 377 parseMode(mode, 'mode', 0o777), recursive, 378 kUsePromises); 379} 380 381async function readdir(path, options) { 382 options = getOptions(options, {}); 383 path = getValidatedPath(path); 384 const result = await binding.readdir(pathModule.toNamespacedPath(path), 385 options.encoding, 386 !!options.withFileTypes, 387 kUsePromises); 388 return options.withFileTypes ? 389 getDirectoryEntriesPromise(path, result) : 390 result; 391} 392 393async function readlink(path, options) { 394 options = getOptions(options, {}); 395 path = getValidatedPath(path, 'oldPath'); 396 return binding.readlink(pathModule.toNamespacedPath(path), 397 options.encoding, kUsePromises); 398} 399 400async function symlink(target, path, type_) { 401 const type = (typeof type_ === 'string' ? type_ : null); 402 target = getValidatedPath(target, 'target'); 403 path = getValidatedPath(path); 404 return binding.symlink(preprocessSymlinkDestination(target, type, path), 405 pathModule.toNamespacedPath(path), 406 stringToSymlinkType(type), 407 kUsePromises); 408} 409 410async function fstat(handle, options = { bigint: false }) { 411 validateFileHandle(handle); 412 const result = await binding.fstat(handle.fd, options.bigint, kUsePromises); 413 return getStatsFromBinding(result); 414} 415 416async function lstat(path, options = { bigint: false }) { 417 path = getValidatedPath(path); 418 const result = await binding.lstat(pathModule.toNamespacedPath(path), 419 options.bigint, kUsePromises); 420 return getStatsFromBinding(result); 421} 422 423async function stat(path, options = { bigint: false }) { 424 path = getValidatedPath(path); 425 const result = await binding.stat(pathModule.toNamespacedPath(path), 426 options.bigint, kUsePromises); 427 return getStatsFromBinding(result); 428} 429 430async function link(existingPath, newPath) { 431 existingPath = getValidatedPath(existingPath, 'existingPath'); 432 newPath = getValidatedPath(newPath, 'newPath'); 433 return binding.link(pathModule.toNamespacedPath(existingPath), 434 pathModule.toNamespacedPath(newPath), 435 kUsePromises); 436} 437 438async function unlink(path) { 439 path = getValidatedPath(path); 440 return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises); 441} 442 443async function fchmod(handle, mode) { 444 validateFileHandle(handle); 445 mode = parseMode(mode, 'mode'); 446 return binding.fchmod(handle.fd, mode, kUsePromises); 447} 448 449async function chmod(path, mode) { 450 path = getValidatedPath(path); 451 mode = parseMode(mode, 'mode'); 452 return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises); 453} 454 455async function lchmod(path, mode) { 456 if (O_SYMLINK === undefined) 457 throw new ERR_METHOD_NOT_IMPLEMENTED('lchmod()'); 458 459 const fd = await open(path, O_WRONLY | O_SYMLINK); 460 return fchmod(fd, mode).finally(fd.close); 461} 462 463async function lchown(path, uid, gid) { 464 path = getValidatedPath(path); 465 validateUint32(uid, 'uid'); 466 validateUint32(gid, 'gid'); 467 return binding.lchown(pathModule.toNamespacedPath(path), 468 uid, gid, kUsePromises); 469} 470 471async function fchown(handle, uid, gid) { 472 validateFileHandle(handle); 473 validateUint32(uid, 'uid'); 474 validateUint32(gid, 'gid'); 475 return binding.fchown(handle.fd, uid, gid, kUsePromises); 476} 477 478async function chown(path, uid, gid) { 479 path = getValidatedPath(path); 480 validateUint32(uid, 'uid'); 481 validateUint32(gid, 'gid'); 482 return binding.chown(pathModule.toNamespacedPath(path), 483 uid, gid, kUsePromises); 484} 485 486async function utimes(path, atime, mtime) { 487 path = getValidatedPath(path); 488 return binding.utimes(pathModule.toNamespacedPath(path), 489 toUnixTimestamp(atime), 490 toUnixTimestamp(mtime), 491 kUsePromises); 492} 493 494async function futimes(handle, atime, mtime) { 495 validateFileHandle(handle); 496 atime = toUnixTimestamp(atime, 'atime'); 497 mtime = toUnixTimestamp(mtime, 'mtime'); 498 return binding.futimes(handle.fd, atime, mtime, kUsePromises); 499} 500 501async function lutimes(path, atime, mtime) { 502 path = getValidatedPath(path); 503 return binding.lutimes(pathModule.toNamespacedPath(path), 504 toUnixTimestamp(atime), 505 toUnixTimestamp(mtime), 506 kUsePromises); 507} 508 509async function realpath(path, options) { 510 options = getOptions(options, {}); 511 path = getValidatedPath(path); 512 return binding.realpath(path, options.encoding, kUsePromises); 513} 514 515async function mkdtemp(prefix, options) { 516 options = getOptions(options, {}); 517 if (!prefix || typeof prefix !== 'string') { 518 throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix); 519 } 520 nullCheck(prefix); 521 warnOnNonPortableTemplate(prefix); 522 return binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, kUsePromises); 523} 524 525async function writeFile(path, data, options) { 526 options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); 527 const flag = options.flag || 'w'; 528 529 if (path instanceof FileHandle) 530 return writeFileHandle(path, data, options); 531 532 const fd = await open(path, flag, options.mode); 533 return writeFileHandle(fd, data, options).finally(fd.close); 534} 535 536async function appendFile(path, data, options) { 537 options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); 538 options = copyObject(options); 539 options.flag = options.flag || 'a'; 540 return writeFile(path, data, options); 541} 542 543async function readFile(path, options) { 544 options = getOptions(options, { flag: 'r' }); 545 const flag = options.flag || 'r'; 546 547 if (path instanceof FileHandle) 548 return readFileHandle(path, options); 549 550 const fd = await open(path, flag, 0o666); 551 return readFileHandle(fd, options).finally(fd.close); 552} 553 554module.exports = { 555 exports: { 556 access, 557 copyFile, 558 open, 559 opendir: promisify(opendir), 560 rename, 561 truncate, 562 rmdir, 563 mkdir, 564 readdir, 565 readlink, 566 symlink, 567 lstat, 568 stat, 569 link, 570 unlink, 571 chmod, 572 lchmod, 573 lchown, 574 chown, 575 utimes, 576 lutimes, 577 realpath, 578 mkdtemp, 579 writeFile, 580 appendFile, 581 readFile, 582 }, 583 584 FileHandle 585}; 586