Skip to content

Commit 747478c

Browse files
committed
storage: new sync option
Add a new driver-specific `sync` configuration option that controls filesystem synchronization during layer operations. When set to "filesystem", the driver ensures all pending writes are flushed to the file system before marking a layer as complete. This helps prevent data corruption in scenarios where the system crashes or loses power before the filesystem has finished writing layer data. Only the vfs and overlay backends support the new flag. Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
1 parent f50361b commit 747478c

11 files changed

Lines changed: 200 additions & 2 deletions

File tree

storage/docs/containers-storage.conf.5.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,16 @@ based file systems.
219219
Use ComposeFS to mount the data layers image. ComposeFS support is experimental and not recommended for production use.
220220
This is a "string bool": "false"|"true" (cannot be native TOML boolean)
221221

222+
**sync**="none|filesystem"
223+
Filesystem synchronization mode for layer creation. (default: "none")
224+
225+
- `none`: No synchronization.
226+
Layer operations complete without calling syncfs().
227+
228+
- `filesystem`: Sync before completion.
229+
Flush all pending writes to the file system before marking the layer
230+
as present. This helps prevent data corruption if the system
231+
crashes or loses power during layer operations.
222232

223233
### STORAGE OPTIONS FOR VFS TABLE
224234

@@ -228,6 +238,16 @@ The `storage.options.vfs` table supports the following options:
228238
ignore_chown_errors can be set to allow a non privileged user running with a single UID within a user namespace to run containers. The user can pull and use any image even those with multiple uids. Note multiple UIDs will be squashed down to the default uid in the container. These images will have no separation between the users in the container.
229239
This is a "string bool": "false"|"true" (cannot be native TOML boolean)
230240

241+
**sync**=""
242+
Filesystem synchronization mode for layer creation. (default: "")
243+
244+
- `none`: No synchronization
245+
Layer operations complete without calling syncfs().
246+
247+
- `filesystem`: Sync before completion
248+
Flush all pending writes to the file system before marking the layer
249+
as present. This helps prevent data corruption if the system
250+
crashes or loses power during layer operations.
231251

232252
### STORAGE OPTIONS FOR ZFS TABLE
233253

storage/drivers/btrfs/btrfs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ func (d *Driver) Cleanup() error {
157157
return mount.Unmount(d.home)
158158
}
159159

160+
// SyncMode returns the sync mode configured for the driver.
161+
// Btrfs does not support sync mode configuration, always returns SyncModeNone.
162+
func (d *Driver) SyncMode() graphdriver.SyncMode {
163+
return graphdriver.SyncModeNone
164+
}
165+
160166
func free(p *C.char) {
161167
C.free(unsafe.Pointer(p))
162168
}

storage/drivers/driver.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"go.podman.io/storage/pkg/directory"
1818
"go.podman.io/storage/pkg/fileutils"
1919
"go.podman.io/storage/pkg/idtools"
20+
"go.podman.io/storage/pkg/system"
2021
)
2122

2223
// FsMagic unsigned id of the filesystem in use.
@@ -27,6 +28,51 @@ const (
2728
FsMagicUnsupported = FsMagic(0x00000000)
2829
)
2930

31+
// SyncMode defines when filesystem synchronization occurs during layer creation.
32+
type SyncMode int
33+
34+
const (
35+
// SyncModeNone - no synchronization
36+
SyncModeNone SyncMode = iota
37+
// SyncModeFilesystem - use syncfs() before layer marked as present
38+
SyncModeFilesystem
39+
)
40+
41+
// String returns the string representation of the sync mode
42+
func (m SyncMode) String() string {
43+
switch m {
44+
case SyncModeNone:
45+
return "none"
46+
case SyncModeFilesystem:
47+
return "filesystem"
48+
default:
49+
return "unknown"
50+
}
51+
}
52+
53+
// ParseSyncMode converts a string to SyncMode
54+
func ParseSyncMode(s string) (SyncMode, error) {
55+
switch strings.ToLower(strings.TrimSpace(s)) {
56+
case "", "none":
57+
return SyncModeNone, nil
58+
case "filesystem":
59+
return SyncModeFilesystem, nil
60+
default:
61+
return SyncModeNone, fmt.Errorf("invalid sync mode: %s", s)
62+
}
63+
}
64+
65+
// Sync applies the sync mode to the given path.
66+
// Returns nil if mode is SyncModeNone or if sync succeeds.
67+
func (m SyncMode) Sync(path string) error {
68+
if m == SyncModeFilesystem {
69+
if err := system.Syncfs(path); err != nil {
70+
return fmt.Errorf("syncing file system for %q: %w", path, err)
71+
}
72+
}
73+
return nil
74+
}
75+
3076
var (
3177
// All registered drivers
3278
drivers map[string]InitFunc
@@ -169,6 +215,8 @@ type ProtoDriver interface {
169215
AdditionalImageStores() []string
170216
// Dedup performs deduplication of the driver's storage.
171217
Dedup(DedupArgs) (DedupResult, error)
218+
// SyncMode returns the sync mode configured for the driver.
219+
SyncMode() SyncMode
172220
}
173221

174222
// DiffDriver is the interface to use to implement graph diffs

storage/drivers/overlay/overlay.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ type overlayOptions struct {
114114
ignoreChownErrors bool
115115
forceMask *os.FileMode
116116
useComposefs bool
117+
syncMode graphdriver.SyncMode
117118
}
118119

119120
// Driver contains information about the home directory and the list of active mounts that are created using this driver.
@@ -594,6 +595,19 @@ func parseOptions(options []string) (*overlayOptions, error) {
594595
}
595596
m := os.FileMode(mask)
596597
o.forceMask = &m
598+
case "sync":
599+
logrus.Debugf("overlay: sync=%s", val)
600+
mode, err := graphdriver.ParseSyncMode(val)
601+
if err != nil {
602+
return nil, fmt.Errorf("invalid sync mode for overlay driver: %w", err)
603+
}
604+
// SyncModeNone and SyncModeFilesystem do not need any special handling because
605+
// the overlay storage is always on the same file system as the metadata, thus
606+
// the Syncfs() in layers.go cover also any file written by the overlay driver.
607+
if mode != SyncModeNone || mode != SyncModeFilesystem {
608+
return nil, fmt.Errorf("invalid mode for overlay driver: %q", val)
609+
}
610+
o.syncMode = mode
597611
default:
598612
return nil, fmt.Errorf("overlay: unknown option %s", key)
599613
}
@@ -866,6 +880,11 @@ func (d *Driver) Cleanup() error {
866880
return mount.Unmount(d.home)
867881
}
868882

883+
// SyncMode returns the sync mode configured for the driver.
884+
func (d *Driver) SyncMode() graphdriver.SyncMode {
885+
return d.options.syncMode
886+
}
887+
869888
// pruneStagingDirectories cleans up any staging directory that was leaked.
870889
// It returns whether any staging directory is still present.
871890
func (d *Driver) pruneStagingDirectories() bool {

storage/drivers/vfs/driver.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error)
6464
if err != nil {
6565
return nil, err
6666
}
67+
case "vfs.sync", ".sync":
68+
logrus.Debugf("vfs: sync=%s", val)
69+
var err error
70+
d.syncMode, err = graphdriver.ParseSyncMode(val)
71+
// SyncModeNone and SyncModeFilesystem do not need any special handling because
72+
// the vfs storage is always on the same file system as the metadata, thus the
73+
// Syncfs() in layers.go cover also any file written by the vfs driver.
74+
if mode != SyncModeNone || mode != SyncModeFilesystem {
75+
return nil, fmt.Errorf("invalid mode for vfs driver: %q", val)
76+
}
77+
if err != nil {
78+
return nil, fmt.Errorf("invalid sync mode for vfs driver: %w", err)
79+
}
6780
default:
6881
return nil, fmt.Errorf("vfs driver does not support %s options", key)
6982
}
@@ -84,6 +97,7 @@ type Driver struct {
8497
home string
8598
additionalHomes []string
8699
ignoreChownErrors bool
100+
syncMode graphdriver.SyncMode
87101
naiveDiff graphdriver.DiffDriver
88102
updater graphdriver.LayerIDMapUpdater
89103
imageStore string
@@ -108,6 +122,11 @@ func (d *Driver) Cleanup() error {
108122
return nil
109123
}
110124

125+
// SyncMode returns the sync mode configured for the driver.
126+
func (d *Driver) SyncMode() graphdriver.SyncMode {
127+
return d.syncMode
128+
}
129+
111130
type fileGetNilCloser struct {
112131
storage.FileGetter
113132
}
@@ -136,7 +155,12 @@ func (d *Driver) ApplyDiff(id string, options graphdriver.ApplyDiffOpts) (size i
136155
if d.ignoreChownErrors {
137156
options.IgnoreChownErrors = d.ignoreChownErrors
138157
}
139-
return d.naiveDiff.ApplyDiff(id, options)
158+
size, err = d.naiveDiff.ApplyDiff(id, options)
159+
if err != nil {
160+
return 0, err
161+
}
162+
163+
return size, nil
140164
}
141165

142166
// CreateReadWrite creates a layer that is writable for use as a container

storage/drivers/vfs/vfs_test.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ package vfs
55
import (
66
"testing"
77

8+
graphdriver "go.podman.io/storage/drivers"
89
"go.podman.io/storage/drivers/graphtest"
9-
1010
"go.podman.io/storage/pkg/reexec"
1111
)
1212

@@ -52,6 +52,23 @@ func TestVfsListLayers(t *testing.T) {
5252
graphtest.DriverTestListLayers(t, "vfs")
5353
}
5454

55+
func TestVfsSyncModeParsing(t *testing.T) {
56+
_, err := graphdriver.ParseSyncMode("invalid")
57+
if err == nil {
58+
t.Error("Expected error for invalid sync mode, got nil")
59+
}
60+
61+
_, err = graphdriver.ParseSyncMode("filesystem")
62+
if err != nil {
63+
t.Errorf("Expected no error for valid sync mode 'filesystem', got %v", err)
64+
}
65+
66+
_, err = graphdriver.ParseSyncMode("none")
67+
if err != nil {
68+
t.Errorf("Expected no error for valid sync mode 'none', got %v", err)
69+
}
70+
}
71+
5572
func TestVfsTeardown(t *testing.T) {
5673
graphtest.PutDriver(t)
5774
}

storage/drivers/zfs/zfs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ func (d *Driver) Cleanup() error {
183183
return nil
184184
}
185185

186+
// SyncMode returns the sync mode configured for the driver.
187+
// ZFS does not support sync mode configuration, always returns SyncModeNone.
188+
func (d *Driver) SyncMode() graphdriver.SyncMode {
189+
return graphdriver.SyncModeNone
190+
}
191+
186192
// Status returns information about the ZFS filesystem. It returns a two dimensional array of information
187193
// such as pool name, dataset name, disk usage, parent quota and compression used.
188194
// Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent',

storage/layers.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,13 @@ func (r *layerStore) saveLayers(saveLocations layerLocations) error {
11391139
if location == volatileLayerLocation {
11401140
opts.NoSync = true
11411141
}
1142+
// If the underlying storage driver is using sync, make sure we sync everything before
1143+
// saving the layer data, this ensures that all files/directories are properly created and written.
1144+
if r.driver.SyncMode() != drivers.SyncModeNone {
1145+
if err := system.Syncfs(filepath.Dir(rpath)); err != nil {
1146+
return err
1147+
}
1148+
}
11421149
if err := ioutils.AtomicWriteFileWithOpts(rpath, jldata, 0o600, &opts); err != nil {
11431150
return err
11441151
}

storage/pkg/config/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@ type OverlayOptionsConfig struct {
3131
// ForceMask indicates the permissions mask (e.g. "0755") to use for new
3232
// files and directories
3333
ForceMask string `toml:"force_mask,omitempty"`
34+
// Sync controls filesystem sync during layer creation
35+
Sync string `toml:"sync,omitempty"`
3436
}
3537

3638
type VfsOptionsConfig struct {
3739
// IgnoreChownErrors is a flag for whether chown errors should be
3840
// ignored when building an image.
3941
IgnoreChownErrors string `toml:"ignore_chown_errors,omitempty"`
42+
// Sync controls filesystem sync during layer creation
43+
Sync string `toml:"sync,omitempty"`
4044
}
4145

4246
type ZfsOptionsConfig struct {
@@ -177,12 +181,18 @@ func GetGraphDriverOptions(driverName string, options OptionsConfig) []string {
177181
if options.Overlay.UseComposefs != "" {
178182
doptions = append(doptions, fmt.Sprintf("%s.use_composefs=%s", driverName, options.Overlay.UseComposefs))
179183
}
184+
if options.Overlay.Sync != "" {
185+
doptions = append(doptions, fmt.Sprintf("%s.sync=%s", driverName, options.Overlay.Sync))
186+
}
180187
case "vfs":
181188
if options.Vfs.IgnoreChownErrors != "" {
182189
doptions = append(doptions, fmt.Sprintf("%s.ignore_chown_errors=%s", driverName, options.Vfs.IgnoreChownErrors))
183190
} else if options.IgnoreChownErrors != "" {
184191
doptions = append(doptions, fmt.Sprintf("%s.ignore_chown_errors=%s", driverName, options.IgnoreChownErrors))
185192
}
193+
if options.Vfs.Sync != "" {
194+
doptions = append(doptions, fmt.Sprintf("%s.sync=%s", driverName, options.Vfs.Sync))
195+
}
186196

187197
case "zfs":
188198
if options.Zfs.Name != "" {

storage/pkg/config/config_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,35 @@ func TestZfsOptions(t *testing.T) {
259259
t.Fatalf("Expected to find size %q, got %v", s100, doptions)
260260
}
261261
}
262+
263+
func TestSyncOptions(t *testing.T) {
264+
var options OptionsConfig
265+
266+
// Test overlay driver sync mode
267+
options.Overlay.Sync = "filesystem"
268+
doptions := GetGraphDriverOptions("overlay", options)
269+
if !searchOptions(doptions, "overlay.sync=filesystem") {
270+
t.Fatalf("Expected to find overlay sync option in %v", doptions)
271+
}
272+
273+
// Test VFS driver
274+
options = OptionsConfig{}
275+
options.Vfs.Sync = "filesystem"
276+
doptions = GetGraphDriverOptions("vfs", options)
277+
if !searchOptions(doptions, "vfs.sync=filesystem") {
278+
t.Fatalf("Expected to find vfs sync option in %v", doptions)
279+
}
280+
281+
// Test empty configuration (no sync options should be added)
282+
options = OptionsConfig{}
283+
doptions = GetGraphDriverOptions("overlay", options)
284+
if searchOptions(doptions, "sync=") {
285+
t.Fatalf("Expected no sync option when not configured, got %v", doptions)
286+
}
287+
288+
options = OptionsConfig{}
289+
doptions = GetGraphDriverOptions("vfs", options)
290+
if searchOptions(doptions, "sync=") {
291+
t.Fatalf("Expected no sync option for vfs when not configured, got %v", doptions)
292+
}
293+
}

0 commit comments

Comments
 (0)