summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ravanan/work/utils.scm36
1 files changed, 34 insertions, 2 deletions
diff --git a/ravanan/work/utils.scm b/ravanan/work/utils.scm
index 62b02ef..9f704c3 100644
--- a/ravanan/work/utils.scm
+++ b/ravanan/work/utils.scm
@@ -1,5 +1,5 @@
 ;;; ravanan --- High-reproducibility CWL runner powered by Guix
-;;; Copyright © 2024 Arun Isaac <arunisaac@systemreboot.net>
+;;; Copyright © 2024, 2025 Arun Isaac <arunisaac@systemreboot.net>
 ;;;
 ;;; This file is part of ravanan.
 ;;;
@@ -20,9 +20,12 @@
   #:use-module ((rnrs base) #:select (assertion-violation))
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-71)
   #:use-module (ice-9 filesystem)
   #:use-module (ice-9 match)
   #:use-module (ice-9 popen)
+  #:use-module (system foreign)
+  #:use-module (system foreign-library)
   #:use-module (ravanan work vectors)
   #:export (list->dotted-list
             alist=?
@@ -31,7 +34,8 @@
             json-ref
             canonicalize-json
             call-with-temporary-directory
-            call-with-input-pipe))
+            call-with-input-pipe
+            call-with-atomic-output-file))
 
 (define (list->dotted-list lst)
   "Convert @var{lst} of 2-element lists into a true association list of
@@ -121,3 +125,31 @@ list of program arguments."
          (lambda ()
            (unless (zero? (close-pipe port))
              (error "Command invocation failed" command))))))))
+
+(define (fsync fd)
+  "Synchronize file descriptor @var{fd} with storage device using the @code{fsync}
+system call."
+  (let ((return errno ((pointer->procedure int
+                                           (foreign-library-pointer (dynamic-link)
+                                                                    "fsync")
+                                           (list int)
+                                           #:return-errno? #t)
+                       fd)))
+    (unless (zero? return)
+      (error "fsync failed" (strerror errno)))))
+
+(define (call-with-atomic-output-file file proc)
+  "Call @var{proc} with an output port, data written to which will be atomically
+written to @var{file}."
+  (let ((temporary-file-port #f)
+        (temporary-filename #f))
+    (dynamic-wind (lambda ()
+                    (set! temporary-file-port
+                          (mkstemp (string-append file ".XXXXXX")))
+                    (set! temporary-filename
+                          (port-filename temporary-file-port)))
+                  (cut proc temporary-file-port)
+                  (lambda ()
+                    (fsync (port->fdes temporary-file-port))
+                    (close-port temporary-file-port)
+                    (rename-file temporary-filename file)))))