| ... | @@ -70,8 +70,9 @@ async function pathExists(target: string): Promise<boolean> { | ... | @@ -70,8 +70,9 @@ async function pathExists(target: string): Promise<boolean> { |
| 70 | } | 70 | } |
| 71 | } | 71 | } |
| 72 | | 72 | |
| | 73 | // Opened read-write: Windows refuses to flush a handle without write access. |
| 73 | async function syncFile(filePath: string): Promise<void> { | 74 | async function syncFile(filePath: string): Promise<void> { |
| 74 | const handle = await NodeFSP.open(filePath, "r"); | 75 | const handle = await NodeFSP.open(filePath, "r+"); |
| 75 | try { | 76 | try { |
| 76 | await handle.sync(); | 77 | await handle.sync(); |
| 77 | } finally { | 78 | } finally { |
| ... | @@ -79,10 +80,15 @@ async function syncFile(filePath: string): Promise<void> { | ... | @@ -79,10 +80,15 @@ async function syncFile(filePath: string): Promise<void> { |
| 79 | } | 80 | } |
| 80 | } | 81 | } |
| 81 | | 82 | |
| | 83 | // Flushes a directory entry so a rename into it survives power loss. Windows |
| | 84 | // has no directory fsync: the handle opens but sync fails with EPERM, and |
| | 85 | // NTFS journals the rename on its own. |
| 82 | async function syncDirectory(directory: string): Promise<void> { | 86 | async function syncDirectory(directory: string): Promise<void> { |
| 83 | const handle = await NodeFSP.open(directory, "r"); | 87 | const handle = await NodeFSP.open(directory, "r"); |
| 84 | try { | 88 | try { |
| 85 | await handle.sync(); | 89 | await handle.sync(); |
| | 90 | } catch (error) { |
| | 91 | if ((error as NodeJS.ErrnoException).code !== "EPERM") throw error; |
| 86 | } finally { | 92 | } finally { |
| 87 | await handle.close(); | 93 | await handle.close(); |
| 88 | } | 94 | } |
| ... | @@ -188,12 +194,7 @@ export async function writeServiceState(filePath: string, state: ServiceState): | ... | @@ -188,12 +194,7 @@ export async function writeServiceState(filePath: string, state: ServiceState): |
| 188 | await handle.close(); | 194 | await handle.close(); |
| 189 | handle = undefined; | 195 | handle = undefined; |
| 190 | await NodeFSP.rename(tempPath, filePath); | 196 | await NodeFSP.rename(tempPath, filePath); |
| 191 | const directoryHandle = await NodeFSP.open(directory, "r"); | 197 | await syncDirectory(directory); |
| 192 | try { | | |
| 193 | await directoryHandle.sync(); | | |
| 194 | } finally { | | |
| 195 | await directoryHandle.close(); | | |
| 196 | } | | |
| 197 | } finally { | 198 | } finally { |
| 198 | await handle?.close().catch(() => undefined); | 199 | await handle?.close().catch(() => undefined); |
| 199 | await NodeFSP.rm(tempPath, { force: true }).catch(() => undefined); | 200 | await NodeFSP.rm(tempPath, { force: true }).catch(() => undefined); |