From 17d8688e666f8398bc8ac7bd195afd91706c28bd Mon Sep 17 00:00:00 2001 From: james Date: Tue, 20 Oct 2026 18:12:43 +0100 Subject: [PATCH] fix(store): replace generated artefacts atomically --- check.ml | 18 ++++++++++++++++++ store.ml | 11 ++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/check.ml b/check.ml index d38de023c4a6332e5304710d68b5c76e643da88b..23e0754221f1a0392fa87e09cad01d9a36454e2d 100644 --- a/check.ml +++ b/check.ml @@ -11,6 +11,23 @@ let rejects action message = let read name = Syntax.load ("cases/" ^ name ^ ".sexp") +let storage folder = + let folder = Filename.concat folder "storage" in + let path = Filename.concat folder "nested/artefact" in + Store.write path "first"; + let previous = open_in_bin path in + Fun.protect ~finally:(fun () -> close_in_noerr previous) (fun () -> + Store.write path "replacement"; + require (Store.read path = "replacement") "artefact replacement"; + require (really_input_string previous 5 = "first") "open reader retains previous artefact"); + let target = Filename.concat folder "directory" in + Store.mkdir target; + let failed = try Store.write target "invalid"; false with Sys_error _ -> true in + require failed "directory cannot be replaced by an artefact"; + require (Sys.is_directory target) "failed replacement preserves destination"; + require (List.sort compare (Array.to_list (Sys.readdir folder)) = ["directory"; "nested"]) + "failed replacement removes temporary file" + let laws () = let source = Layout.generate ~timeout:300000 ^ Store.read "smt/machine.smt2" ^ "\n" ^ Store.read "smt/laws.smt2" in @@ -155,6 +172,7 @@ let () = let started = Unix.gettimeofday () in let folder = Filename.concat (Filename.get_temp_dir_name ()) (Printf.sprintf "reorder%d" (Unix.getpid ())) in Store.mkdir folder; + storage folder; codec (); laws (); fixtures (); diff --git a/store.ml b/store.ml index 953a070d9bb48366bb83a4a7c25e5f95a0232bac..9465e8d1fc5c27b98688f1a1fd0b07b5ea0e17b2 100644 --- a/store.ml +++ b/store.ml @@ -11,9 +11,14 @@ let rec mkdir path = let write path text = mkdir (Filename.dirname path); - let channel = open_out_bin path in - Fun.protect ~finally:(fun () -> close_out_noerr channel) (fun () -> - output_string channel text) + let temporary, channel = Filename.open_temp_file ~mode:[Open_binary] + ~temp_dir:(Filename.dirname path) ".reorder" ".tmp" in + Fun.protect ~finally:(fun () -> + close_out_noerr channel; + if Sys.file_exists temporary then Sys.remove temporary) (fun () -> + output_string channel text; + close_out channel; + Sys.rename temporary path) let root () = let rec find path =