]> git.giacomolli.uk - onechat/blob - file.go
Submit root events through the local socket
[onechat] / file.go
1 package main
2
3 import (
4 "errors"
5 "fmt"
6 "os"
7 "path/filepath"
8 )
9
10 // writeFileAtomic replaces the file at path with data through a temporary file
11 // and a rename. A reader or a crash of the process sees the old file or the new
12 // file, never a part of one.
13 func writeFileAtomic(path string, data []byte) error {
14 file, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".*")
15 if err != nil {
16 return fmt.Errorf("create temporary file: %w", err)
17 }
18
19 _, err = file.Write(data)
20 err = errors.Join(err, file.Close())
21 if err == nil {
22 err = os.Rename(file.Name(), path)
23 }
24
25 if err != nil {
26 return errors.Join(fmt.Errorf("replace file: %w", err), os.Remove(file.Name()))
27 }
28
29 return nil
30 }
31
32 // writeFileDurable replaces path only after syncing its private temporary file.
33 // It syncs the directory after rename before it reports success. An error after
34 // rename has an uncertain outcome, so callers must stop writes until recovery.
35 func writeFileDurable(path string, data []byte) (err error) {
36 file, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".*")
37 if err != nil {
38 return fmt.Errorf("create durable temporary file: %w", err)
39 }
40
41 defer func() {
42 removeErr := os.Remove(file.Name())
43 if removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) {
44 err = errors.Join(err, fmt.Errorf("remove durable temporary file: %w", removeErr))
45 }
46 }()
47
48 _, err = file.Write(data)
49 if err == nil {
50 err = file.Sync()
51 }
52
53 err = errors.Join(err, file.Close())
54 if err != nil {
55 return fmt.Errorf("write durable temporary file: %w", err)
56 }
57
58 err = os.Rename(file.Name(), path)
59 if err != nil {
60 return fmt.Errorf("publish durable file: %w", err)
61 }
62
63 directory, err := os.Open(filepath.Dir(path))
64 if err != nil {
65 return fmt.Errorf("open durable directory: %w", err)
66 }
67
68 err = errors.Join(directory.Sync(), directory.Close())
69 if err != nil {
70 return fmt.Errorf("sync durable directory: %w", err)
71 }
72
73 return nil
74 }