From 2192e686848f2a666b127d0ab7c38d9a0d4e3b06 Mon Sep 17 00:00:00 2001 From: zarzet Date: Sat, 11 Jul 2026 12:44:19 +0700 Subject: [PATCH] fix(download): fsync staged downloads before the promote rename The staged-write-then-rename protocol was atomic against process kills but not against power loss: f2fs/ext4 can persist the rename before the data, leaving a zero-length or truncated file under the final name. Sync the staged file before promoting and fsync the directory after. --- go_backend/extension_runtime_file.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/go_backend/extension_runtime_file.go b/go_backend/extension_runtime_file.go index 9bce493d..99d17362 100644 --- a/go_backend/extension_runtime_file.go +++ b/go_backend/extension_runtime_file.go @@ -333,6 +333,14 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { } } + // Sync before the promote rename so a power loss right after the rename + // cannot leave a truncated file under the final name. + if err := out.Sync(); err != nil { + return r.vm.ToValue(map[string]any{ + "success": false, + "error": fmt.Sprintf("failed to sync file: %v", err), + }) + } if err := out.Close(); err != nil { return r.vm.ToValue(map[string]any{ "success": false, @@ -346,6 +354,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { }) } promoted = true + syncDir(filepath.Dir(fullPath)) GoLog("[Extension:%s] Downloaded %d bytes to %s\n", r.extensionID, written, fullPath) @@ -590,6 +599,14 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full } } + // Sync before the promote rename so a power loss right after the rename + // cannot leave a truncated file under the final name. + if err := out.Sync(); err != nil { + return r.vm.ToValue(map[string]any{ + "success": false, + "error": fmt.Sprintf("failed to sync file: %v", err), + }) + } if err := out.Close(); err != nil { return r.vm.ToValue(map[string]any{ "success": false, @@ -603,6 +620,7 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full }) } promoted = true + syncDir(filepath.Dir(fullPath)) GoLog("[Extension:%s] Chunked download complete: %d bytes to %s\n", r.extensionID, totalWritten, fullPath)