1'use strict'; 2 3const { 4 ArrayPrototypePush, 5 Error, 6 MathMax, 7 MathMin, 8 NumberIsSafeInteger, 9 Promise, 10 PromisePrototypeFinally, 11 PromiseResolve, 12 Symbol, 13 Uint8Array, 14} = primordials; 15 16const { 17 F_OK, 18 O_SYMLINK, 19 O_WRONLY, 20 S_IFMT, 21 S_IFREG 22} = internalBinding('constants').fs; 23const binding = internalBinding('fs'); 24const { Buffer } = require('buffer'); 25 26const { AbortError, codes, hideStackFrames } = require('internal/errors'); 27const { 28 ERR_FS_FILE_TOO_LARGE, 29 ERR_INVALID_ARG_TYPE, 30 ERR_INVALID_ARG_VALUE, 31 ERR_METHOD_NOT_IMPLEMENTED, 32} = codes; 33const { isArrayBufferView } = require('internal/util/types'); 34const { rimrafPromises } = require('internal/fs/rimraf'); 35const { 36 constants: { 37 kIoMaxLength, 38 kMaxUserId, 39 kReadFileBufferLength, 40 kReadFileUnknownBufferLength, 41 kWriteFileMaxChunkSize, 42 }, 43 copyObject, 44 getDirents, 45 getOptions, 46 getStatsFromBinding, 47 getValidatedPath, 48 getValidMode, 49 nullCheck, 50 preprocessSymlinkDestination, 51 stringToFlags, 52 stringToSymlinkType, 53 toUnixTimestamp, 54 validateBufferArray, 55 validateOffsetLengthRead, 56 validateOffsetLengthWrite, 57 validateRmOptions, 58 validateRmdirOptions, 59 validatePrimitiveStringAfterArrayBufferView, 60 warnOnNonPortableTemplate, 61} = require('internal/fs/utils'); 62const { opendir } = require('internal/fs/dir'); 63const { 64 parseFileMode, 65 validateAbortSignal, 66 validateBuffer, 67 validateInteger, 68 validateString, 69} = require('internal/validators'); 70const pathModule = require('path'); 71const { promisify } = require('internal/util'); 72const { watch } = require('internal/fs/watchers'); 73const { isIterable } = require('internal/streams/utils'); 74 75const kHandle = Symbol('kHandle'); 76const kFd = Symbol('kFd'); 77const kRefs = Symbol('kRefs'); 78const kClosePromise = Symbol('kClosePromise'); 79const kCloseResolve = Symbol('kCloseResolve'); 80const kCloseReject = Symbol('kCloseReject'); 81 82const { kUsePromises } = binding; 83const { 84 JSTransferable, kDeserialize, kTransfer, kTransferList 85} = require('internal/worker/js_transferable'); 86 87const getDirectoryEntriesPromise = promisify(getDirents); 88const validateRmOptionsPromise = promisify(validateRmOptions); 89 90let DOMException; 91const lazyDOMException = hideStackFrames((message, name) => { 92 if (DOMException === undefined) 93 DOMException = internalBinding('messaging').DOMException; 94 return new DOMException(message, name); 95}); 96 97class FileHandle extends JSTransferable { 98 constructor(filehandle) { 99 super(); 100 this[kHandle] = filehandle; 101 this[kFd] = filehandle ? filehandle.fd : -1; 102 103 this[kRefs] = 1; 104 this[kClosePromise] = null; 105 } 106 107 getAsyncId() { 108 return this[kHandle].getAsyncId(); 109 } 110 111 get fd() { 112 return this[kFd]; 113 } 114 115 appendFile(data, options) { 116 return fsCall(writeFile, this, data, options); 117 } 118 119 chmod(mode) { 120 return fsCall(fchmod, this, mode); 121 } 122 123 chown(uid, gid) { 124 return fsCall(fchown, this, uid, gid); 125 } 126 127 datasync() { 128 return fsCall(fdatasync, this); 129 } 130 131 sync() { 132 return fsCall(fsync, this); 133 } 134 135 read(buffer, offset, length, position) { 136 return fsCall(read, this, buffer, offset, length, position); 137 } 138 139 readv(buffers, position) { 140 return fsCall(readv, this, buffers, position); 141 } 142 143 readFile(options) { 144 return fsCall(readFile, this, options); 145 } 146 147 stat(options) { 148 return fsCall(fstat, this, options); 149 } 150 151 truncate(len = 0) { 152 return fsCall(ftruncate, this, len); 153 } 154 155 utimes(atime, mtime) { 156 return fsCall(futimes, this, atime, mtime); 157 } 158 159 write(buffer, offset, length, position) { 160 return fsCall(write, this, buffer, offset, length, position); 161 } 162 163 writev(buffers, position) { 164 return fsCall(writev, this, buffers, position); 165 } 166 167 writeFile(data, options) { 168 return fsCall(writeFile, this, data, options); 169 } 170 171 close = () => { 172 if (this[kFd] === -1) { 173 return PromiseResolve(); 174 } 175 176 if (this[kClosePromise]) { 177 return this[kClosePromise]; 178 } 179 180 this[kRefs]--; 181 if (this[kRefs] === 0) { 182 this[kFd] = -1; 183 this[kClosePromise] = this[kHandle].close().finally(() => { 184 this[kClosePromise] = undefined; 185 }); 186 } else { 187 this[kClosePromise] = new Promise((resolve, reject) => { 188 this[kCloseResolve] = resolve; 189 this[kCloseReject] = reject; 190 }).finally(() => { 191 this[kClosePromise] = undefined; 192 this[kCloseReject] = undefined; 193 this[kCloseResolve] = undefined; 194 }); 195 } 196 197 return this[kClosePromise]; 198 } 199 200 [kTransfer]() { 201 if (this[kClosePromise] || this[kRefs] > 1) { 202 const DOMException = internalBinding('messaging').DOMException; 203 throw new DOMException('Cannot transfer FileHandle while in use', 204 'DataCloneError'); 205 } 206 207 const handle = this[kHandle]; 208 this[kFd] = -1; 209 this[kHandle] = null; 210 this[kRefs] = 0; 211 212 return { 213 data: { handle }, 214 deserializeInfo: 'internal/fs/promises:FileHandle' 215 }; 216 } 217 218 [kTransferList]() { 219 return [ this[kHandle] ]; 220 } 221 222 [kDeserialize]({ handle }) { 223 this[kHandle] = handle; 224 this[kFd] = handle.fd; 225 } 226} 227 228async function fsCall(fn, handle, ...args) { 229 if (handle[kRefs] === undefined) { 230 throw new ERR_INVALID_ARG_TYPE('filehandle', 'FileHandle', handle); 231 } 232 233 if (handle.fd === -1) { 234 // eslint-disable-next-line no-restricted-syntax 235 const err = new Error('file closed'); 236 err.code = 'EBADF'; 237 err.syscall = fn.name; 238 throw err; 239 } 240 241 try { 242 handle[kRefs]++; 243 return await fn(handle, ...args); 244 } finally { 245 handle[kRefs]--; 246 if (handle[kRefs] === 0) { 247 handle[kFd] = -1; 248 handle[kHandle] 249 .close() 250 .then(handle[kCloseResolve], handle[kCloseReject]); 251 } 252 } 253} 254 255function checkAborted(signal) { 256 if (signal && signal.aborted) 257 throw new AbortError(); 258} 259 260async function writeFileHandle(filehandle, data, signal, encoding) { 261 checkAborted(signal); 262 if (isCustomIterable(data)) { 263 for await (const buf of data) { 264 checkAborted(signal); 265 await write( 266 filehandle, buf, undefined, 267 isArrayBufferView(buf) ? buf.byteLength : encoding); 268 checkAborted(signal); 269 } 270 return; 271 } 272 data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); 273 let remaining = data.byteLength; 274 if (remaining === 0) return; 275 do { 276 if (signal?.aborted) { 277 throw lazyDOMException('The operation was aborted', 'AbortError'); 278 } 279 const { bytesWritten } = 280 await write(filehandle, data, 0, 281 MathMin(kWriteFileMaxChunkSize, data.byteLength)); 282 remaining -= bytesWritten; 283 data = new Uint8Array( 284 data.buffer, 285 data.byteOffset + bytesWritten, 286 data.byteLength - bytesWritten 287 ); 288 } while (remaining > 0); 289} 290 291async function readFileHandle(filehandle, options) { 292 const signal = options && options.signal; 293 294 if (signal && signal.aborted) { 295 throw lazyDOMException('The operation was aborted', 'AbortError'); 296 } 297 const statFields = await binding.fstat(filehandle.fd, false, kUsePromises); 298 299 if (signal && signal.aborted) { 300 throw lazyDOMException('The operation was aborted', 'AbortError'); 301 } 302 303 let size; 304 if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) { 305 size = statFields[8/* size */]; 306 } else { 307 size = 0; 308 } 309 310 if (size > kIoMaxLength) 311 throw new ERR_FS_FILE_TOO_LARGE(size); 312 313 let endOfFile = false; 314 let totalRead = 0; 315 const noSize = size === 0; 316 const buffers = []; 317 const fullBuffer = noSize ? undefined : Buffer.allocUnsafeSlow(size); 318 do { 319 if (signal && signal.aborted) { 320 throw lazyDOMException('The operation was aborted', 'AbortError'); 321 } 322 let buffer; 323 let offset; 324 let length; 325 if (noSize) { 326 buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength); 327 offset = 0; 328 length = kReadFileUnknownBufferLength; 329 } else { 330 buffer = fullBuffer; 331 offset = totalRead; 332 length = MathMin(size - totalRead, kReadFileBufferLength); 333 } 334 335 const bytesRead = (await binding.read(filehandle.fd, buffer, offset, 336 length, -1, kUsePromises)) || 0; 337 totalRead += bytesRead; 338 endOfFile = bytesRead === 0 || totalRead === size; 339 if (noSize && bytesRead > 0) { 340 const isBufferFull = bytesRead === kReadFileUnknownBufferLength; 341 const chunkBuffer = isBufferFull ? buffer : buffer.slice(0, bytesRead); 342 ArrayPrototypePush(buffers, chunkBuffer); 343 } 344 } while (!endOfFile); 345 346 let result; 347 if (size > 0) { 348 result = totalRead === size ? fullBuffer : fullBuffer.slice(0, totalRead); 349 } else { 350 result = buffers.length === 1 ? buffers[0] : Buffer.concat(buffers, 351 totalRead); 352 } 353 354 return options.encoding ? result.toString(options.encoding) : result; 355} 356 357// All of the functions are defined as async in order to ensure that errors 358// thrown cause promise rejections rather than being thrown synchronously. 359async function access(path, mode = F_OK) { 360 path = getValidatedPath(path); 361 362 mode = getValidMode(mode, 'access'); 363 return binding.access(pathModule.toNamespacedPath(path), mode, 364 kUsePromises); 365} 366 367async function copyFile(src, dest, mode) { 368 src = getValidatedPath(src, 'src'); 369 dest = getValidatedPath(dest, 'dest'); 370 mode = getValidMode(mode, 'copyFile'); 371 return binding.copyFile(pathModule.toNamespacedPath(src), 372 pathModule.toNamespacedPath(dest), 373 mode, 374 kUsePromises); 375} 376 377// Note that unlike fs.open() which uses numeric file descriptors, 378// fsPromises.open() uses the fs.FileHandle class. 379async function open(path, flags, mode) { 380 path = getValidatedPath(path); 381 const flagsNumber = stringToFlags(flags); 382 mode = parseFileMode(mode, 'mode', 0o666); 383 return new FileHandle( 384 await binding.openFileHandle(pathModule.toNamespacedPath(path), 385 flagsNumber, mode, kUsePromises)); 386} 387 388async function read(handle, bufferOrOptions, offset, length, position) { 389 let buffer = bufferOrOptions; 390 if (!isArrayBufferView(buffer)) { 391 if (bufferOrOptions === undefined) { 392 bufferOrOptions = {}; 393 } 394 if (bufferOrOptions.buffer) { 395 buffer = bufferOrOptions.buffer; 396 validateBuffer(buffer); 397 } else { 398 buffer = Buffer.alloc(16384); 399 } 400 offset = bufferOrOptions.offset || 0; 401 length = buffer.byteLength; 402 position = bufferOrOptions.position ?? null; 403 } 404 405 if (offset == null) { 406 offset = 0; 407 } else { 408 validateInteger(offset, 'offset', 0); 409 } 410 411 length |= 0; 412 413 if (length === 0) 414 return { bytesRead: length, buffer }; 415 416 if (buffer.byteLength === 0) { 417 throw new ERR_INVALID_ARG_VALUE('buffer', buffer, 418 'is empty and cannot be written'); 419 } 420 421 validateOffsetLengthRead(offset, length, buffer.byteLength); 422 423 if (!NumberIsSafeInteger(position)) 424 position = -1; 425 426 const bytesRead = (await binding.read(handle.fd, buffer, offset, length, 427 position, kUsePromises)) || 0; 428 429 return { bytesRead, buffer }; 430} 431 432async function readv(handle, buffers, position) { 433 validateBufferArray(buffers); 434 435 if (typeof position !== 'number') 436 position = null; 437 438 const bytesRead = (await binding.readBuffers(handle.fd, buffers, position, 439 kUsePromises)) || 0; 440 return { bytesRead, buffers }; 441} 442 443async function write(handle, buffer, offset, length, position) { 444 if (buffer && buffer.byteLength === 0) 445 return { bytesWritten: 0, buffer }; 446 447 if (isArrayBufferView(buffer)) { 448 if (offset == null) { 449 offset = 0; 450 } else { 451 validateInteger(offset, 'offset', 0); 452 } 453 if (typeof length !== 'number') 454 length = buffer.byteLength - offset; 455 if (typeof position !== 'number') 456 position = null; 457 validateOffsetLengthWrite(offset, length, buffer.byteLength); 458 const bytesWritten = 459 (await binding.writeBuffer(handle.fd, buffer, offset, 460 length, position, kUsePromises)) || 0; 461 return { bytesWritten, buffer }; 462 } 463 464 validatePrimitiveStringAfterArrayBufferView(buffer, 'buffer'); 465 const bytesWritten = (await binding.writeString(handle.fd, buffer, offset, 466 length, kUsePromises)) || 0; 467 return { bytesWritten, buffer }; 468} 469 470async function writev(handle, buffers, position) { 471 validateBufferArray(buffers); 472 473 if (typeof position !== 'number') 474 position = null; 475 476 const bytesWritten = (await binding.writeBuffers(handle.fd, buffers, position, 477 kUsePromises)) || 0; 478 return { bytesWritten, buffers }; 479} 480 481async function rename(oldPath, newPath) { 482 oldPath = getValidatedPath(oldPath, 'oldPath'); 483 newPath = getValidatedPath(newPath, 'newPath'); 484 return binding.rename(pathModule.toNamespacedPath(oldPath), 485 pathModule.toNamespacedPath(newPath), 486 kUsePromises); 487} 488 489async function truncate(path, len = 0) { 490 const fd = await open(path, 'r+'); 491 return PromisePrototypeFinally(ftruncate(fd, len), fd.close); 492} 493 494async function ftruncate(handle, len = 0) { 495 validateInteger(len, 'len'); 496 len = MathMax(0, len); 497 return binding.ftruncate(handle.fd, len, kUsePromises); 498} 499 500async function rm(path, options) { 501 path = pathModule.toNamespacedPath(getValidatedPath(path)); 502 options = await validateRmOptionsPromise(path, options); 503 return rimrafPromises(path, options); 504} 505 506async function rmdir(path, options) { 507 path = pathModule.toNamespacedPath(getValidatedPath(path)); 508 options = validateRmdirOptions(options); 509 510 if (options.recursive) { 511 return rimrafPromises(path, options); 512 } 513 514 return binding.rmdir(path, kUsePromises); 515} 516 517async function fdatasync(handle) { 518 return binding.fdatasync(handle.fd, kUsePromises); 519} 520 521async function fsync(handle) { 522 return binding.fsync(handle.fd, kUsePromises); 523} 524 525async function mkdir(path, options) { 526 if (typeof options === 'number' || typeof options === 'string') { 527 options = { mode: options }; 528 } 529 const { 530 recursive = false, 531 mode = 0o777 532 } = options || {}; 533 path = getValidatedPath(path); 534 if (typeof recursive !== 'boolean') 535 throw new ERR_INVALID_ARG_TYPE('options.recursive', 'boolean', recursive); 536 537 return binding.mkdir(pathModule.toNamespacedPath(path), 538 parseFileMode(mode, 'mode', 0o777), recursive, 539 kUsePromises); 540} 541 542async function readdir(path, options) { 543 options = getOptions(options, {}); 544 path = getValidatedPath(path); 545 const result = await binding.readdir(pathModule.toNamespacedPath(path), 546 options.encoding, 547 !!options.withFileTypes, 548 kUsePromises); 549 return options.withFileTypes ? 550 getDirectoryEntriesPromise(path, result) : 551 result; 552} 553 554async function readlink(path, options) { 555 options = getOptions(options, {}); 556 path = getValidatedPath(path, 'oldPath'); 557 return binding.readlink(pathModule.toNamespacedPath(path), 558 options.encoding, kUsePromises); 559} 560 561async function symlink(target, path, type_) { 562 const type = (typeof type_ === 'string' ? type_ : null); 563 target = getValidatedPath(target, 'target'); 564 path = getValidatedPath(path); 565 return binding.symlink(preprocessSymlinkDestination(target, type, path), 566 pathModule.toNamespacedPath(path), 567 stringToSymlinkType(type), 568 kUsePromises); 569} 570 571async function fstat(handle, options = { bigint: false }) { 572 const result = await binding.fstat(handle.fd, options.bigint, kUsePromises); 573 return getStatsFromBinding(result); 574} 575 576async function lstat(path, options = { bigint: false }) { 577 path = getValidatedPath(path); 578 const result = await binding.lstat(pathModule.toNamespacedPath(path), 579 options.bigint, kUsePromises); 580 return getStatsFromBinding(result); 581} 582 583async function stat(path, options = { bigint: false }) { 584 path = getValidatedPath(path); 585 const result = await binding.stat(pathModule.toNamespacedPath(path), 586 options.bigint, kUsePromises); 587 return getStatsFromBinding(result); 588} 589 590async function link(existingPath, newPath) { 591 existingPath = getValidatedPath(existingPath, 'existingPath'); 592 newPath = getValidatedPath(newPath, 'newPath'); 593 return binding.link(pathModule.toNamespacedPath(existingPath), 594 pathModule.toNamespacedPath(newPath), 595 kUsePromises); 596} 597 598async function unlink(path) { 599 path = getValidatedPath(path); 600 return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises); 601} 602 603async function fchmod(handle, mode) { 604 mode = parseFileMode(mode, 'mode'); 605 return binding.fchmod(handle.fd, mode, kUsePromises); 606} 607 608async function chmod(path, mode) { 609 path = getValidatedPath(path); 610 mode = parseFileMode(mode, 'mode'); 611 return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises); 612} 613 614async function lchmod(path, mode) { 615 if (O_SYMLINK === undefined) 616 throw new ERR_METHOD_NOT_IMPLEMENTED('lchmod()'); 617 618 const fd = await open(path, O_WRONLY | O_SYMLINK); 619 return PromisePrototypeFinally(fchmod(fd, mode), fd.close); 620} 621 622async function lchown(path, uid, gid) { 623 path = getValidatedPath(path); 624 validateInteger(uid, 'uid', -1, kMaxUserId); 625 validateInteger(gid, 'gid', -1, kMaxUserId); 626 return binding.lchown(pathModule.toNamespacedPath(path), 627 uid, gid, kUsePromises); 628} 629 630async function fchown(handle, uid, gid) { 631 validateInteger(uid, 'uid', -1, kMaxUserId); 632 validateInteger(gid, 'gid', -1, kMaxUserId); 633 return binding.fchown(handle.fd, uid, gid, kUsePromises); 634} 635 636async function chown(path, uid, gid) { 637 path = getValidatedPath(path); 638 validateInteger(uid, 'uid', -1, kMaxUserId); 639 validateInteger(gid, 'gid', -1, kMaxUserId); 640 return binding.chown(pathModule.toNamespacedPath(path), 641 uid, gid, kUsePromises); 642} 643 644async function utimes(path, atime, mtime) { 645 path = getValidatedPath(path); 646 return binding.utimes(pathModule.toNamespacedPath(path), 647 toUnixTimestamp(atime), 648 toUnixTimestamp(mtime), 649 kUsePromises); 650} 651 652async function futimes(handle, atime, mtime) { 653 atime = toUnixTimestamp(atime, 'atime'); 654 mtime = toUnixTimestamp(mtime, 'mtime'); 655 return binding.futimes(handle.fd, atime, mtime, kUsePromises); 656} 657 658async function lutimes(path, atime, mtime) { 659 path = getValidatedPath(path); 660 return binding.lutimes(pathModule.toNamespacedPath(path), 661 toUnixTimestamp(atime), 662 toUnixTimestamp(mtime), 663 kUsePromises); 664} 665 666async function realpath(path, options) { 667 options = getOptions(options, {}); 668 path = getValidatedPath(path); 669 return binding.realpath(path, options.encoding, kUsePromises); 670} 671 672async function mkdtemp(prefix, options) { 673 options = getOptions(options, {}); 674 675 validateString(prefix, 'prefix'); 676 nullCheck(prefix); 677 warnOnNonPortableTemplate(prefix); 678 return binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, kUsePromises); 679} 680 681async function writeFile(path, data, options) { 682 options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); 683 const flag = options.flag || 'w'; 684 685 if (!isArrayBufferView(data) && !isCustomIterable(data)) { 686 validatePrimitiveStringAfterArrayBufferView(data, 'data'); 687 data = Buffer.from(data, options.encoding || 'utf8'); 688 } 689 690 validateAbortSignal(options.signal); 691 if (path instanceof FileHandle) 692 return writeFileHandle(path, data, options.signal, options.encoding); 693 694 if (options.signal?.aborted) { 695 throw lazyDOMException('The operation was aborted', 'AbortError'); 696 } 697 698 const fd = await open(path, flag, options.mode); 699 return PromisePrototypeFinally( 700 writeFileHandle(fd, data, options.signal, options.encoding), fd.close); 701} 702 703function isCustomIterable(obj) { 704 return isIterable(obj) && !isArrayBufferView(obj) && typeof obj !== 'string'; 705} 706 707async function appendFile(path, data, options) { 708 options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); 709 options = copyObject(options); 710 options.flag = options.flag || 'a'; 711 return writeFile(path, data, options); 712} 713 714async function readFile(path, options) { 715 options = getOptions(options, { flag: 'r' }); 716 const flag = options.flag || 'r'; 717 718 if (path instanceof FileHandle) 719 return readFileHandle(path, options); 720 721 if (options.signal?.aborted) { 722 throw lazyDOMException('The operation was aborted', 'AbortError'); 723 } 724 725 const fd = await open(path, flag, 0o666); 726 return PromisePrototypeFinally(readFileHandle(fd, options), fd.close); 727} 728 729module.exports = { 730 exports: { 731 access, 732 copyFile, 733 open, 734 opendir: promisify(opendir), 735 rename, 736 truncate, 737 rm, 738 rmdir, 739 mkdir, 740 readdir, 741 readlink, 742 symlink, 743 lstat, 744 stat, 745 link, 746 unlink, 747 chmod, 748 lchmod, 749 lchown, 750 chown, 751 utimes, 752 lutimes, 753 realpath, 754 mkdtemp, 755 writeFile, 756 appendFile, 757 readFile, 758 watch, 759 }, 760 761 FileHandle 762}; 763