fix(jsonl): bind MoveFileExW with the Win32 BOOL ABI
Declare MoveFileExW's return value as Koffi int and model it as a numeric 32-bit Win32 BOOL. The previous Koffi bool declaration represented a one-byte C boolean and could read the native return register with the wrong ABI. Test zero and nonzero results explicitly and assert the binding result type while preserving the existing write-through flags, error translation, and durable directory race behavior.
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
import { mkdtemp, rm, stat } from 'node:fs/promises'
|
import { mkdtemp, rm, stat } from 'node:fs/promises'
|
||||||
import { basename, join, parse, resolve, toNamespacedPath } from 'node:path'
|
import { basename, join, parse, resolve, toNamespacedPath } from 'node:path'
|
||||||
|
|
||||||
type MoveFileExW = (existing: string, replacement: string, flags: number) => boolean
|
type MoveFileExW = (existing: string, replacement: string, flags: number) => number
|
||||||
type GetLastError = () => number
|
type GetLastError = () => number
|
||||||
|
|
||||||
interface Win32Bindings {
|
interface Win32Bindings {
|
||||||
@@ -44,7 +44,7 @@ async function win32(): Promise<Win32Bindings> {
|
|||||||
const koffi = (await import('koffi')).default
|
const koffi = (await import('koffi')).default
|
||||||
const kernel32 = koffi.load('kernel32.dll')
|
const kernel32 = koffi.load('kernel32.dll')
|
||||||
bindings = {
|
bindings = {
|
||||||
moveFileExW: kernel32.func('__stdcall', 'MoveFileExW', 'bool', ['str16', 'str16', 'uint']) as MoveFileExW,
|
moveFileExW: kernel32.func('__stdcall', 'MoveFileExW', 'int', ['str16', 'str16', 'uint']) as MoveFileExW,
|
||||||
getLastError: kernel32.func('__stdcall', 'GetLastError', 'uint', []) as GetLastError,
|
getLastError: kernel32.func('__stdcall', 'GetLastError', 'uint', []) as GetLastError,
|
||||||
}
|
}
|
||||||
return bindings
|
return bindings
|
||||||
@@ -113,7 +113,7 @@ async function assertDirectory(path: string): Promise<boolean> {
|
|||||||
export async function publishNewFileWin32(existing: string, replacement: string): Promise<void> {
|
export async function publishNewFileWin32(existing: string, replacement: string): Promise<void> {
|
||||||
const api = await win32()
|
const api = await win32()
|
||||||
const ok = api.moveFileExW(toNamespacedPath(existing), toNamespacedPath(replacement), MOVEFILE_WRITE_THROUGH)
|
const ok = api.moveFileExW(toNamespacedPath(existing), toNamespacedPath(replacement), MOVEFILE_WRITE_THROUGH)
|
||||||
if (!ok) throw win32Error('MoveFileExW', api.getLastError(), existing, replacement)
|
if (ok === 0) throw win32Error('MoveFileExW', api.getLastError(), existing, replacement)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const ERROR_FILE_EXISTS = 80
|
|||||||
const ERROR_INVALID_NAME = 123
|
const ERROR_INVALID_NAME = 123
|
||||||
const ERROR_ALREADY_EXISTS = 183
|
const ERROR_ALREADY_EXISTS = 183
|
||||||
|
|
||||||
type MoveFileExW = (existing: string, replacement: string, flags: number, setLastError: (code: number) => void) => boolean
|
type MoveFileExW = (existing: string, replacement: string, flags: number, setLastError: (code: number) => void) => number
|
||||||
|
|
||||||
const roots: string[] = []
|
const roots: string[] = []
|
||||||
|
|
||||||
@@ -42,14 +42,15 @@ async function importWithMove(moveFileExW: MoveFileExW): Promise<typeof import('
|
|||||||
const setLastError = (code: number): void => { lastError = code }
|
const setLastError = (code: number): void => { lastError = code }
|
||||||
const move: MoveFileExW = (existing, replacement, flags, setError) => {
|
const move: MoveFileExW = (existing, replacement, flags, setError) => {
|
||||||
const ok = moveFileExW(existing, replacement, flags, setError)
|
const ok = moveFileExW(existing, replacement, flags, setError)
|
||||||
lastError = ok ? 0 : lastError
|
lastError = ok === 0 ? lastError : 0
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
default: {
|
default: {
|
||||||
load: () => ({
|
load: () => ({
|
||||||
func: (_convention: string, name: string) => {
|
func: (_convention: string, name: string, result: string) => {
|
||||||
if (name === 'MoveFileExW') return (existing: string, replacement: string, flags: number) => {
|
if (name === 'MoveFileExW') return (existing: string, replacement: string, flags: number) => {
|
||||||
|
expect(result).toBe('int')
|
||||||
const ok = move(existing, replacement, flags, setLastError)
|
const ok = move(existing, replacement, flags, setLastError)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
@@ -68,7 +69,7 @@ async function importWithError(code: number): Promise<typeof import('../src/win3
|
|||||||
default: {
|
default: {
|
||||||
load: () => ({
|
load: () => ({
|
||||||
func: (_convention: string, name: string) => {
|
func: (_convention: string, name: string) => {
|
||||||
if (name === 'MoveFileExW') return () => false
|
if (name === 'MoveFileExW') return () => 0
|
||||||
return () => code
|
return () => code
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -82,10 +83,10 @@ async function importWithFilesystemMove(): Promise<typeof import('../src/win32.t
|
|||||||
expect(flags).toBe(MOVEFILE_WRITE_THROUGH)
|
expect(flags).toBe(MOVEFILE_WRITE_THROUGH)
|
||||||
const from = stripNamespace(existing)
|
const from = stripNamespace(existing)
|
||||||
const to = stripNamespace(replacement)
|
const to = stripNamespace(replacement)
|
||||||
if (!existsSync(from)) { setLastError(ERROR_FILE_NOT_FOUND); return false }
|
if (!existsSync(from)) { setLastError(ERROR_FILE_NOT_FOUND); return 0 }
|
||||||
if (existsSync(to)) { setLastError(ERROR_ALREADY_EXISTS); return false }
|
if (existsSync(to)) { setLastError(ERROR_ALREADY_EXISTS); return 0 }
|
||||||
renameSync(from, to)
|
renameSync(from, to)
|
||||||
return true
|
return 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,12 +136,12 @@ describe('Windows durable namespace helpers', () => {
|
|||||||
if (to === raced) {
|
if (to === raced) {
|
||||||
mkdirSync(to)
|
mkdirSync(to)
|
||||||
setLastError(ERROR_ALREADY_EXISTS)
|
setLastError(ERROR_ALREADY_EXISTS)
|
||||||
return false
|
return 0
|
||||||
}
|
}
|
||||||
if (!existsSync(from)) { setLastError(ERROR_FILE_NOT_FOUND); return false }
|
if (!existsSync(from)) { setLastError(ERROR_FILE_NOT_FOUND); return 0 }
|
||||||
if (existsSync(to)) { setLastError(ERROR_ALREADY_EXISTS); return false }
|
if (existsSync(to)) { setLastError(ERROR_ALREADY_EXISTS); return 0 }
|
||||||
renameSync(from, to)
|
renameSync(from, to)
|
||||||
return true
|
return 1
|
||||||
})
|
})
|
||||||
|
|
||||||
await ensureDurableDirectoryWin32(join(root, 'a', 'b'))
|
await ensureDurableDirectoryWin32(join(root, 'a', 'b'))
|
||||||
|
|||||||
Reference in New Issue
Block a user