Skip to content

Commit 986fac7

Browse files
committed
storage: fsync staging directory before atomic rename
Signed-off-by: Aditi Sahay <asahay@redhat.com>
1 parent 6e821ae commit 986fac7

3 files changed

Lines changed: 100 additions & 0 deletions

File tree

storage/drivers/overlay/overlay.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"go.podman.io/storage/pkg/directory"
3737
"go.podman.io/storage/pkg/fileutils"
3838
"go.podman.io/storage/pkg/fsutils"
39+
"go.podman.io/storage/pkg/ioutils"
3940
"go.podman.io/storage/pkg/idmap"
4041
"go.podman.io/storage/pkg/idtools"
4142
"go.podman.io/storage/pkg/mount"
@@ -2310,6 +2311,10 @@ func (d *Driver) ApplyDiffFromStagingDirectory(id, parent string, diffOutput *gr
23102311
return err
23112312
}
23122313

2314+
if err := ioutils.SyncDirectoryContents(stagingDirectory); err != nil {
2315+
return fmt.Errorf("sync staging directory before rename: %w", err)
2316+
}
2317+
23132318
return os.Rename(stagingDirectory, diffPath)
23142319
}
23152320

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//go:build linux
2+
3+
package ioutils
4+
5+
import (
6+
"fmt"
7+
"io/fs"
8+
"os"
9+
"path/filepath"
10+
11+
"golang.org/x/sys/unix"
12+
)
13+
14+
// SyncDirectoryContents flushes file data and directory metadata under dir to
15+
// physical storage. Call this before atomically renaming a fully populated
16+
// staging directory to its final location.
17+
func SyncDirectoryContents(dir string) error {
18+
var dirs []string
19+
20+
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, walkErr error) error {
21+
if walkErr != nil {
22+
return walkErr
23+
}
24+
if d.IsDir() {
25+
dirs = append(dirs, path)
26+
return nil
27+
}
28+
29+
f, err := os.Open(path)
30+
if err != nil {
31+
return err
32+
}
33+
34+
syncErr := unix.Fdatasync(int(f.Fd()))
35+
closeErr := f.Close()
36+
if syncErr != nil {
37+
return syncErr
38+
}
39+
40+
return closeErr
41+
})
42+
if err != nil {
43+
return fmt.Errorf("sync directory contents in %q: %w", dir, err)
44+
}
45+
46+
for i := len(dirs) - 1; i >= 0; i-- {
47+
dfd, err := os.Open(dirs[i])
48+
if err != nil {
49+
return fmt.Errorf("open directory %q for sync: %w", dirs[i], err)
50+
}
51+
52+
syncErr := unix.Fsync(int(dfd.Fd()))
53+
closeErr := dfd.Close()
54+
if syncErr != nil {
55+
return fmt.Errorf("sync directory %q: %w", dirs[i], syncErr)
56+
}
57+
if closeErr != nil {
58+
return fmt.Errorf("close directory %q after sync: %w", dirs[i], closeErr)
59+
}
60+
}
61+
62+
return nil
63+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//go:build linux
2+
3+
package ioutils
4+
5+
import (
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
)
10+
11+
func TestSyncDirectoryContents(t *testing.T) {
12+
dir := t.TempDir()
13+
14+
nested := filepath.Join(dir, "nested")
15+
if err := os.MkdirAll(nested, 0o755); err != nil {
16+
t.Fatalf("mkdir nested: %v", err)
17+
}
18+
19+
files := []string{
20+
filepath.Join(dir, "file1"),
21+
filepath.Join(nested, "file2"),
22+
}
23+
for _, file := range files {
24+
if err := os.WriteFile(file, []byte("storage-resilience"), 0o644); err != nil {
25+
t.Fatalf("write file %q: %v", file, err)
26+
}
27+
}
28+
29+
if err := SyncDirectoryContents(dir); err != nil {
30+
t.Fatalf("SyncDirectoryContents: %v", err)
31+
}
32+
}

0 commit comments

Comments
 (0)