| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f4e7bf1 commit bbdd764
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,10 +1,12 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | + ArrayPrototypePush, | ||
| 4 | 5 | Promise, | |
| 5 | 6 | StringPrototypeStartsWith, | |
| 6 | 7 | } = primordials; | |
| 7 | 8 | ||
| 9 | + const { Buffer } = require('buffer'); | ||
| 8 | 10 | const fs = require('fs'); | |
| 9 | 11 | const path = require('path'); | |
| 10 | 12 | const { VirtualProvider } = require('internal/vfs/provider'); | |
@@ -17,6 +19,8 @@ const { | |||
| 17 | 19 | createENOENT, | |
| 18 | 20 | } = require('internal/vfs/errors'); | |
| 19 | 21 | ||
| 22 | + const kReadFileUnknownBufferLength = 8192; | ||
| 23 | + | ||
| 20 | 24 | /** | |
| 21 | 25 | * A file handle that wraps a real file descriptor. | |
| 22 | 26 | */ | |
@@ -34,6 +38,20 @@ class RealFileHandle extends VirtualFileHandle { | |||
| 34 | 38 | } | |
| 35 | 39 | } | |
| 36 | 40 | ||
| 41 | + #readFileResult(buffer, bytesRead, options) { | ||
| 42 | + buffer = buffer.subarray(0, bytesRead); | ||
| 43 | + const encoding = typeof options === 'string' ? options : options?.encoding; | ||
| 44 | + if (encoding && encoding !== 'buffer') { | ||
| 45 | + buffer = buffer.toString(encoding); | ||
| 46 | + } | ||
| 47 | + return buffer; | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + #readFileUnknownSizeResult(buffers, totalRead, options) { | ||
| 51 | + return this.#readFileResult( | ||
| 52 | + Buffer.concat(buffers, totalRead), totalRead, options); | ||
| 53 | + } | ||
| 54 | + | ||
| 37 | 55 | /** | |
| 38 | 56 | * @param {string} path The VFS path | |
| 39 | 57 | * @param {string} flags The open flags | |
@@ -79,12 +97,77 @@ class RealFileHandle extends VirtualFileHandle { | |||
| 79 | 97 | ||
| 80 | 98 | readFileSync(options) { | |
| 81 | 99 | this.#checkClosed('read'); | |
| 82 | - return fs.readFileSync(this.#realPath, options); | ||
| 100 | + const size = fs.fstatSync(this.#fd).size; | ||
| 101 | + if (size === 0) { | ||
| 102 | + const buffers = []; | ||
| 103 | + let totalRead = 0; | ||
| 104 | + | ||
| 105 | + while (true) { | ||
| 106 | + const buffer = Buffer.allocUnsafe(kReadFileUnknownBufferLength); | ||
| 107 | + const read = fs.readSync( | ||
| 108 | + this.#fd, buffer, 0, buffer.byteLength, totalRead); | ||
| 109 | + if (read === 0) break; | ||
| 110 | + ArrayPrototypePush(buffers, buffer.subarray(0, read)); | ||
| 111 | + totalRead += read; | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + return this.#readFileUnknownSizeResult(buffers, totalRead, options); | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + const buffer = Buffer.allocUnsafe(size); | ||
| 118 | + let bytesRead = 0; | ||
| 119 | + while (bytesRead < buffer.byteLength) { | ||
| 120 | + const read = fs.readSync( | ||
| 121 | + this.#fd, | ||
| 122 | + buffer, | ||
| 123 | + bytesRead, | ||
| 124 | + buffer.byteLength - bytesRead, | ||
| 125 | + bytesRead, | ||
| 126 | + ); | ||
| 127 | + if (read === 0) break; | ||
| 128 | + bytesRead += read; | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + return this.#readFileResult(buffer, bytesRead, options); | ||
| 83 | 132 | } | |
| 84 | 133 | ||
| 85 | 134 | async readFile(options) { | |
| 86 | 135 | this.#checkClosed('read'); | |
| 87 | - return fs.promises.readFile(this.#realPath, options); | ||
| 136 | + const size = (await this.stat()).size; | ||
| 137 | + if (size === 0) { | ||
| 138 | + const buffers = []; | ||
| 139 | + let totalRead = 0; | ||
| 140 | + | ||
| 141 | + while (true) { | ||
| 142 | + const buffer = Buffer.allocUnsafe(kReadFileUnknownBufferLength); | ||
| 143 | + const { bytesRead: read } = await this.read( | ||
| 144 | + buffer, | ||
| 145 | + 0, | ||
| 146 | + buffer.byteLength, | ||
| 147 | + totalRead, | ||
| 148 | + ); | ||
| 149 | + if (read === 0) break; | ||
| 150 | + ArrayPrototypePush(buffers, buffer.subarray(0, read)); | ||
| 151 | + totalRead += read; | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + return this.#readFileUnknownSizeResult(buffers, totalRead, options); | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + const buffer = Buffer.allocUnsafe(size); | ||
| 158 | + let bytesRead = 0; | ||
| 159 | + while (bytesRead < buffer.byteLength) { | ||
| 160 | + const { bytesRead: read } = await this.read( | ||
| 161 | + buffer, | ||
| 162 | + bytesRead, | ||
| 163 | + buffer.byteLength - bytesRead, | ||
| 164 | + bytesRead, | ||
| 165 | + ); | ||
| 166 | + if (read === 0) break; | ||
| 167 | + bytesRead += read; | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + return this.#readFileResult(buffer, bytesRead, options); | ||
| 88 | 171 | } | |
| 89 | 172 | ||
| 90 | 173 | writeFileSync(data, options) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -43,3 +43,32 @@ assert.strictEqual( | |||
| 43 | 43 | } | |
| 44 | 44 | ||
| 45 | 45 | myVfs.unmount(); | |
| 46 | + | ||
| 47 | + // readFileSync via a RealFSProvider fd remains usable after the backing path | ||
| 48 | + // is renamed. | ||
| 49 | + { | ||
| 50 | + const root = path.join('/tmp', 'vfs-real-readFileSync-' + process.pid); | ||
| 51 | + const realMountPoint = path.join('/tmp', 'vfs-real-readFileSync-mount-' + process.pid); | ||
| 52 | + fs.rmSync(root, { recursive: true, force: true }); | ||
| 53 | + fs.rmSync(realMountPoint, { recursive: true, force: true }); | ||
| 54 | + fs.mkdirSync(root, { recursive: true }); | ||
| 55 | + fs.mkdirSync(realMountPoint, { recursive: true }); | ||
| 56 | + | ||
| 57 | + const realVfs = vfs | ||
| 58 | + .create(new vfs.RealFSProvider(root), { emitExperimentalWarning: false }) | ||
| 59 | + .mount(realMountPoint); | ||
| 60 | + try { | ||
| 61 | + fs.writeFileSync(path.join(root, 'a.txt'), 'still readable'); | ||
| 62 | + const fd = fs.openSync(path.join(realMountPoint, 'a.txt'), 'r'); | ||
| 63 | + try { | ||
| 64 | + fs.renameSync(path.join(root, 'a.txt'), path.join(root, 'b.txt')); | ||
| 65 | + assert.strictEqual(fs.readFileSync(fd, 'utf8'), 'still readable'); | ||
| 66 | + } finally { | ||
| 67 | + fs.closeSync(fd); | ||
| 68 | + } | ||
| 69 | + } finally { | ||
| 70 | + realVfs.unmount(); | ||
| 71 | + fs.rmSync(root, { recursive: true, force: true }); | ||
| 72 | + fs.rmSync(realMountPoint, { recursive: true, force: true }); | ||
| 73 | + } | ||
| 74 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -75,6 +75,55 @@ const myVfs = vfs.create(new vfs.RealFSProvider(root)); | |||
| 75 | 75 | await handle.close(); | |
| 76 | 76 | } | |
| 77 | 77 | ||
| 78 | + // ===== readFile through an open real fd survives backing path rename ===== | ||
| 79 | + { | ||
| 80 | + fs.writeFileSync(path.join(root, 'rename-read.txt'), 'still readable'); | ||
| 81 | + const syncHandle = await myVfs.provider.open('/rename-read.txt', 'r'); | ||
| 82 | + const asyncHandle = await myVfs.provider.open('/rename-read.txt', 'r'); | ||
| 83 | + fs.renameSync(path.join(root, 'rename-read.txt'), | ||
| 84 | + path.join(root, 'rename-read-renamed.txt')); | ||
| 85 | + try { | ||
| 86 | + assert.strictEqual(syncHandle.readFileSync('utf8'), 'still readable'); | ||
| 87 | + assert.strictEqual(await asyncHandle.readFile('utf8'), 'still readable'); | ||
| 88 | + } finally { | ||
| 89 | + await syncHandle.close(); | ||
| 90 | + await asyncHandle.close(); | ||
| 91 | + fs.unlinkSync(path.join(root, 'rename-read-renamed.txt')); | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + // ===== readFile reads past the fallback chunk when fstat reports size 0 ===== | ||
| 96 | + { | ||
| 97 | + const content = 'a'.repeat(8192) + 'trailing data'; | ||
| 98 | + fs.writeFileSync(path.join(root, 'zero-stat.txt'), content); | ||
| 99 | + const syncHandle = await myVfs.provider.open('/zero-stat.txt', 'r'); | ||
| 100 | + const asyncHandle = await myVfs.provider.open('/zero-stat.txt', 'r'); | ||
| 101 | + const originalFstatSync = fs.fstatSync; | ||
| 102 | + const originalFstat = fs.fstat; | ||
| 103 | + | ||
| 104 | + fs.fstatSync = common.mustCall(function fstatSync(...args) { | ||
| 105 | + const stats = originalFstatSync.apply(this, args); | ||
| 106 | + stats.size = 0; | ||
| 107 | + return stats; | ||
| 108 | + }); | ||
| 109 | + fs.fstat = common.mustCall(function fstat(fd, options, callback) { | ||
| 110 | + return originalFstat.call(this, fd, options, (err, stats) => { | ||
| 111 | + if (stats) stats.size = 0; | ||
| 112 | + callback(err, stats); | ||
| 113 | + }); | ||
| 114 | + }); | ||
| 115 | + | ||
| 116 | + try { | ||
| 117 | + assert.strictEqual(syncHandle.readFileSync('utf8'), content); | ||
| 118 | + assert.strictEqual(await asyncHandle.readFile('utf8'), content); | ||
| 119 | + } finally { | ||
| 120 | + fs.fstatSync = originalFstatSync; | ||
| 121 | + fs.fstat = originalFstat; | ||
| 122 | + await syncHandle.close(); | ||
| 123 | + await asyncHandle.close(); | ||
| 124 | + } | ||
| 125 | + } | ||
| 126 | + | ||
| 78 | 127 | // ===== EBADF after close ===== | |
| 79 | 128 | { | |
| 80 | 129 | await myVfs.promises.writeFile('/h.txt', 'hello'); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments