(path, mode, cb)
| 542 | }, |
| 543 | |
| 544 | create(path, mode, cb) { |
| 545 | try { |
| 546 | if (exists(path)) { |
| 547 | cb(ERRNO.EEXIST, 0); |
| 548 | return; |
| 549 | } |
| 550 | if (hasDeferredCreate) { |
| 551 | // Single transaction at release time: defer the inode INSERT |
| 552 | // and the chunk commit into one round trip. |
| 553 | directWriteVfs.openWriteBufferForCreateSync?.(toVfs(path), { mode }); |
| 554 | cb(0, openFileHandle(path)); |
| 555 | return; |
| 556 | } |
| 557 | if (hasDirectWrites) { |
| 558 | directWriteVfs.createFileSync?.(toVfs(path), { mode }); |
| 559 | if (hasBufferedWrites) { |
| 560 | directWriteVfs.openWriteBufferSync?.(toVfs(path)); |
| 561 | } |
| 562 | cb(0, openFileHandle(path)); |
| 563 | return; |
| 564 | } |
| 565 | // Defer the VFS inode write until flush/release/fsync. Most create |
| 566 | // workloads immediately write content, so persisting an empty file here |
| 567 | // doubles provider work for tiny files. |
| 568 | // |
| 569 | // dirty=true with an empty dirtyRanges array is deliberate: it |
| 570 | // routes flushEntry to the whole-file writeFileSync path, which |
| 571 | // is what registers the inode the first time. The subsequent |
| 572 | // write() / truncate() ops append real ranges. |
| 573 | files.set(path, { |
| 574 | buf: Buffer.alloc(0), |
| 575 | size: 0, |
| 576 | dirty: true, |
| 577 | dirtyRanges: [], |
| 578 | pendingCreate: true, |
| 579 | mode, |
| 580 | pendingMtime: new Date(), |
| 581 | }); |
| 582 | cb(0, openFileHandle(path)); |
| 583 | } catch (error) { |
| 584 | cb(toErrno(error), 0); |
| 585 | } |
| 586 | }, |
| 587 | |
| 588 | read(path, _fh, buffer, length, position, cb) { |
| 589 | let entry = files.get(path); |
no test coverage detected