From 794a27cd0b342e6c523aefc18caeb5716f9d2fb2 Mon Sep 17 00:00:00 2001 From: scui73414-bot <301006987+scui73414-bot@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:55:24 +0800 Subject: [PATCH] fix(lock): open owner file writable before fdatasync --- src/canonicalIdentityLock.js | 3 ++- test/canonicalIdentityLock.test.js | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/canonicalIdentityLock.js b/src/canonicalIdentityLock.js index 236b9f2c..d0867fa0 100644 --- a/src/canonicalIdentityLock.js +++ b/src/canonicalIdentityLock.js @@ -298,7 +298,8 @@ function prepareOwnerFile(lockDir, token) { mode: PRIVATE_FILE_MODE, flag: 'wx', }); - descriptor = fs.openSync(preparedOwnerFile, 'r'); + // Windows requires a writable handle for FlushFileBuffers/fdatasync. + descriptor = fs.openSync(preparedOwnerFile, 'r+'); fs.fdatasyncSync(descriptor); fs.closeSync(descriptor); descriptor = null; diff --git a/test/canonicalIdentityLock.test.js b/test/canonicalIdentityLock.test.js index a39835c0..4d566576 100644 --- a/test/canonicalIdentityLock.test.js +++ b/test/canonicalIdentityLock.test.js @@ -177,6 +177,28 @@ test('partial owner preparation failure never exposes a canonical lock', () => { } }); +test('prepares owner files with a writable descriptor for fdatasync', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'canonical-lock-fdatasync-')); + const nodeIdFile = path.join(root, 'node_id'); + const lock = require('../src/canonicalIdentityLock'); + const originalOpenSync = fs.openSync; + let stagedOwnerFlags = null; + + try { + fs.openSync = function captureStagedOwnerFlags(file, flags, ...args) { + if (String(file).endsWith('.tmp')) stagedOwnerFlags = flags; + return originalOpenSync.call(fs, file, flags, ...args); + }; + + const release = lock.acquireCanonicalIdentityLock(nodeIdFile); + release(); + assert.equal(stagedOwnerFlags, 'r+'); + } finally { + fs.openSync = originalOpenSync; + fs.rmSync(root, { recursive: true, force: true }); + } +}); + test('release cleanup failure leaves canonical path available to a successor', () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'canonical-lock-release-')); const nodeIdFile = path.join(root, 'node_id');