fix(files): fsync read-only files on POSIX

This commit is contained in:
Jinjing
2026-08-12 22:36:22 -07:00
parent 889dd4c7d2
commit 67ca905df4
3 changed files with 67 additions and 8 deletions
@@ -3,6 +3,7 @@ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import {
createDefaultLocalOrcaProfile,
DEFAULT_LOCAL_ORCA_PROFILE_ID,
DEFAULT_LOCAL_ORCA_PROFILE_NAME,
ORCA_PROFILE_INDEX_SCHEMA_VERSION,
@@ -154,6 +155,27 @@ describe('profile index store', () => {
expect(() => setActiveOrcaProfile('missing-profile')).toThrow('unknown_orca_profile')
})
const posixIt = process.platform === 'win32' ? it.skip : it
posixIt('writes a fresh profile index when umask removes owner-write permission', async () => {
const store = await loadProfileIndexStore()
const indexPath = store.getOrcaProfileIndexPath()
const profile = createDefaultLocalOrcaProfile(1)
const index: OrcaProfileIndex = {
schemaVersion: ORCA_PROFILE_INDEX_SCHEMA_VERSION,
activeProfileId: profile.id,
profiles: [profile]
}
const originalUmask = process.umask(0o200)
try {
expect(() => store.writeProfileIndex(indexPath, index)).not.toThrow()
} finally {
process.umask(originalUmask)
}
expect(readJson(indexPath)).toEqual(index)
})
it('recovers a corrupted profile index from the backup copy', async () => {
const store = await loadProfileIndexStore()
store.ensureActiveOrcaProfile()
+44 -7
View File
@@ -1,23 +1,33 @@
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import type * as NodeFs from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
const openedPaths = vi.hoisted(() => [] as { path: string; flags: string | number }[])
const openedPaths = vi.hoisted(
() => [] as { path: string; flags: string | number; ownerWritable: boolean }[]
)
vi.mock('node:fs', async () => {
const actual = await vi.importActual<typeof NodeFs>('node:fs')
return {
...actual,
openSync: (path: NodeFs.PathLike, flags: string | number, mode?: NodeFs.Mode) => {
openedPaths.push({ path: String(path), flags })
openedPaths.push({
path: String(path),
flags,
ownerWritable: Boolean(actual.statSync(path).mode & 0o200)
})
return actual.openSync(path, flags, mode)
}
}
})
import { bestEffortFsyncDirectorySync, fsyncFileSync } from './secure-file'
import {
bestEffortFsyncDirectorySync,
fsyncFileSync,
writeDurableSecureJsonFile
} from './secure-file'
const createdPaths: string[] = []
@@ -29,7 +39,8 @@ afterEach(() => {
})
describe('secure file fsync flags', () => {
it('opens files read/write before fsync', () => {
const windowsIt = process.platform === 'win32' ? it : it.skip
windowsIt('opens files read/write before fsync on Windows', () => {
const directory = mkdtempSync(join(tmpdir(), 'orca-file-fsync-'))
createdPaths.push(directory)
const path = join(directory, 'record.json')
@@ -37,16 +48,42 @@ describe('secure file fsync flags', () => {
fsyncFileSync(path)
expect(openedPaths).toEqual([{ path, flags: 'r+' }])
expect(openedPaths).toMatchObject([{ path, flags: 'r+' }])
})
const posixIt = process.platform === 'win32' ? it.skip : it
posixIt('fsyncs files without owner-write permission', () => {
const directory = mkdtempSync(join(tmpdir(), 'orca-file-fsync-read-only-'))
createdPaths.push(directory)
const path = join(directory, 'record.json')
writeFileSync(path, '{}')
chmodSync(path, 0o400)
expect(() => fsyncFileSync(path)).not.toThrow()
expect(openedPaths).toEqual([{ path, flags: 'r', ownerWritable: false }])
})
posixIt('durably writes when umask removes owner-write from the temporary file', () => {
const directory = mkdtempSync(join(tmpdir(), 'orca-durable-file-fsync-read-only-'))
createdPaths.push(directory)
const path = join(directory, 'record.json')
const originalUmask = process.umask(0o200)
try {
expect(() => writeDurableSecureJsonFile(path, { ok: true })).not.toThrow()
} finally {
process.umask(originalUmask)
}
expect(openedPaths[0]).toMatchObject({ flags: 'r', ownerWritable: false })
})
posixIt('opens directories read-only before fsync', () => {
const directory = mkdtempSync(join(tmpdir(), 'orca-directory-fsync-'))
createdPaths.push(directory)
bestEffortFsyncDirectorySync(directory)
expect(openedPaths).toEqual([{ path: directory, flags: 'r' }])
expect(openedPaths).toMatchObject([{ path: directory, flags: 'r' }])
})
})
+1 -1
View File
@@ -144,7 +144,7 @@ function fsyncPathSync(path: string, flags: 'r' | 'r+'): void {
export function fsyncFileSync(path: string): void {
// FlushFileBuffers requires a write-capable handle on Windows.
fsyncPathSync(path, 'r+')
fsyncPathSync(path, process.platform === 'win32' ? 'r+' : 'r')
}
export function bestEffortFsyncDirectorySync(directory: string): void {