Commit 2e59a6da78a for nodejs

commit 2e59a6da78adddd4334ebbaf2f3d2e5c84150120
Author: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
Date:   Sun Nov 15 01:17:27 2026 -0300

    fs: use correct position when retrying writev

    writevAll() tracks the position for the next attempt in its `pos`
    argument, but calls fs.writev() with `this.pos`. WriteStream._writev()
    advances `this.pos` by the whole batch right after starting the write,
    so a retry from the asynchronous callback resumes at the end of the
    batch instead of after the bytes that reached the file. A partial write
    leaves a hole and puts the remainder past its place; an EAGAIN retry
    moves forward even though nothing was written.

    Use the local `pos`, as writeAll() already does.

    Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/66203
    Refs: https://github.com/nodejs/node/pull/49211
    Reviewed-By: Robert Nagy <ronagy@icloud.com>
    Reviewed-By: Matteo Collina <matteo.collina@gmail.com>

diff --git a/lib/internal/fs/streams.js b/lib/internal/fs/streams.js
index c8c86b59640..5fd47bb5e49 100644
--- a/lib/internal/fs/streams.js
+++ b/lib/internal/fs/streams.js
@@ -473,7 +473,7 @@ function writeAll(data, size, pos, cb, retries = 0) {
 }

 function writevAll(chunks, size, pos, cb, retries = 0) {
-  this[kFs].writev(this.fd, chunks, this.pos, (er, bytesWritten, buffers) => {
+  this[kFs].writev(this.fd, chunks, pos, (er, bytesWritten, buffers) => {
     // No data currently available and operation should be retried later.
     if (er?.code === 'EAGAIN') {
       er = null;
diff --git a/test/parallel/test-fs-write-stream-writev-retry-position.js b/test/parallel/test-fs-write-stream-writev-retry-position.js
new file mode 100644
index 00000000000..17dfb474cef
--- /dev/null
+++ b/test/parallel/test-fs-write-stream-writev-retry-position.js
@@ -0,0 +1,74 @@
+'use strict';
+const common = require('../common');
+const tmpdir = require('../common/tmpdir');
+const assert = require('assert');
+const fs = require('fs');
+
+tmpdir.refresh();
+
+const data = 'AAAABBBBCCCC';
+
+// Report back fewer bytes than were handed over, asynchronously: the retry has
+// to observe the position as it is after _writev() advanced this.pos by the
+// whole batch.
+function shortWrite(bytes) {
+  return (fd, chunks, position, cb) => {
+    const head = Buffer.concat(chunks).subarray(0, bytes);
+    fs.write(fd, head, 0, bytes, position,
+             (er, bytesWritten) => cb(er, bytesWritten, chunks));
+  };
+}
+
+function eagain(fd, chunks, position, cb) {
+  process.nextTick(cb, Object.assign(new Error(), { code: 'EAGAIN' }), 0, chunks);
+}
+
+// The positions handed to writev() are the contract under test: watching the
+// resulting file alone would still pass if some later rewrite happened to land
+// the bytes correctly by another route.
+function run(name, { prefix = '', start, firstCall }, expectedPositions) {
+  const file = tmpdir.resolve(`partial-writev-${name}.txt`);
+  fs.writeFileSync(file, prefix);
+
+  const positions = [];
+  const stream = fs.createWriteStream(file, {
+    flags: 'r+',
+    start,
+    fs: {
+      ...fs,
+      writev: common.mustCall((fd, chunks, position, cb) => {
+        positions.push(position);
+        if (positions.length === 1) {
+          firstCall(fd, chunks, position, cb);
+        } else {
+          fs.writev(fd, chunks, position, cb);
+        }
+      }, 2),
+    },
+  });
+
+  stream.cork();
+  stream.write('AAAA');
+  stream.write('BBBB');
+  stream.write('CCCC');
+  stream.uncork();
+  stream.end();
+
+  stream.on('error', common.mustNotCall());
+  stream.on('close', common.mustCall(() => {
+    if (expectedPositions !== undefined) {
+      assert.deepStrictEqual(positions, expectedPositions, `${name} positions`);
+    }
+    assert.strictEqual(fs.readFileSync(file, 'utf8'), prefix + data, name);
+  }));
+}
+
+// Partial write: resume right after the bytes that reached the disk.
+run('start-5', { prefix: '#####', start: 5, firstCall: shortWrite(3) }, [5, 8]);
+run('start-0', { start: 0, firstCall: shortWrite(3) }, [0, 3]);
+
+// EAGAIN: nothing was written, so the retry goes back to the same position.
+run('eagain', { prefix: '#####', start: 5, firstCall: eagain }, [5, 5]);
+
+// Without a start the stream follows the fd offset; only the bytes matter.
+run('no-start', { firstCall: shortWrite(3) });