Skip to content

Commit 57cdeea

Browse files
committed
Roll back partial macOS storage clones without removing existing files
1 parent 4849059 commit 57cdeea

2 files changed

Lines changed: 82 additions & 5 deletions

File tree

‎lib/instances/macos_darwin.go‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,25 @@ import (
1212
"golang.org/x/sys/unix"
1313
)
1414

15-
func cloneMacOSStorage(root, disk, aux string) error {
16-
for src, dst := range map[string]string{root: disk, filepath.Join(filepath.Dir(root), "aux.img"): aux} {
17-
if err := unix.Clonefile(src, dst, unix.CLONE_NOFOLLOW|unix.CLONE_NOOWNERCOPY); err != nil {
15+
func cloneMacOSStorage(root, disk, aux string) (err error) {
16+
var created []string
17+
defer func() {
18+
if err != nil {
19+
// Only remove files this operation created, never pre-existing storage.
20+
for _, path := range created {
21+
os.Remove(path)
22+
}
23+
}
24+
}()
25+
for _, file := range []struct{ src, dst string }{
26+
{root, disk},
27+
{filepath.Join(filepath.Dir(root), "aux.img"), aux},
28+
} {
29+
if err = unix.Clonefile(file.src, file.dst, unix.CLONE_NOFOLLOW|unix.CLONE_NOOWNERCOPY); err != nil {
1830
return err
1931
}
20-
if err := os.Chmod(dst, 0600); err != nil {
32+
created = append(created, file.dst)
33+
if err = os.Chmod(file.dst, 0600); err != nil {
2134
return err
2235
}
2336
}

‎lib/instances/macos_darwin_test.go‎

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,75 @@
33
package instances
44

55
import (
6-
"github.com/stretchr/testify/require"
6+
"errors"
77
"net"
8+
"os"
9+
"path/filepath"
810
"testing"
11+
12+
"github.com/stretchr/testify/require"
13+
"golang.org/x/sys/unix"
914
)
1015

16+
func TestMacOSStorageClone(t *testing.T) {
17+
root := t.TempDir()
18+
source := filepath.Join(root, "rootfs.raw")
19+
auxSource := filepath.Join(root, "aux.img")
20+
require.NoError(t, os.WriteFile(source, []byte("template disk"), 0644))
21+
require.NoError(t, os.WriteFile(auxSource, []byte("template aux"), 0644))
22+
probe := filepath.Join(root, "probe")
23+
err := unix.Clonefile(source, probe, unix.CLONE_NOFOLLOW|unix.CLONE_NOOWNERCOPY)
24+
if errors.Is(err, unix.ENOTSUP) || errors.Is(err, unix.EXDEV) {
25+
t.Skip("test filesystem does not support clonefile")
26+
}
27+
require.NoError(t, err)
28+
require.NoError(t, os.Remove(probe))
29+
30+
t.Run("private writable copies", func(t *testing.T) {
31+
dest := t.TempDir()
32+
disk, aux := filepath.Join(dest, "disk"), filepath.Join(dest, "aux")
33+
require.NoError(t, cloneMacOSStorage(source, disk, aux))
34+
for path, expected := range map[string]string{disk: "template disk", aux: "template aux"} {
35+
data, err := os.ReadFile(path)
36+
require.NoError(t, err)
37+
require.Equal(t, expected, string(data))
38+
info, err := os.Stat(path)
39+
require.NoError(t, err)
40+
require.Equal(t, os.FileMode(0600), info.Mode().Perm())
41+
require.NoError(t, os.WriteFile(path, []byte("instance changes"), 0600))
42+
}
43+
for path, expected := range map[string]string{source: "template disk", auxSource: "template aux"} {
44+
data, err := os.ReadFile(path)
45+
require.NoError(t, err)
46+
require.Equal(t, expected, string(data))
47+
}
48+
})
49+
50+
t.Run("aux failure rolls back only new disk", func(t *testing.T) {
51+
dest := t.TempDir()
52+
disk, aux := filepath.Join(dest, "disk"), filepath.Join(dest, "aux")
53+
require.NoError(t, os.WriteFile(aux, []byte("existing aux"), 0600))
54+
require.Error(t, cloneMacOSStorage(source, disk, aux))
55+
_, err := os.Stat(disk)
56+
require.ErrorIs(t, err, os.ErrNotExist)
57+
data, err := os.ReadFile(aux)
58+
require.NoError(t, err)
59+
require.Equal(t, "existing aux", string(data))
60+
})
61+
62+
t.Run("existing disk is preserved", func(t *testing.T) {
63+
dest := t.TempDir()
64+
disk, aux := filepath.Join(dest, "disk"), filepath.Join(dest, "aux")
65+
require.NoError(t, os.WriteFile(disk, []byte("existing disk"), 0600))
66+
require.Error(t, cloneMacOSStorage(source, disk, aux))
67+
data, err := os.ReadFile(disk)
68+
require.NoError(t, err)
69+
require.Equal(t, "existing disk", string(data))
70+
_, err = os.Stat(aux)
71+
require.ErrorIs(t, err, os.ErrNotExist)
72+
})
73+
}
74+
1175
func TestMacOSLeaseNormalization(t *testing.T) {
1276
mac, err := net.ParseMAC("02:00:01:0a:0b:ff")
1377
require.NoError(t, err)

0 commit comments

Comments
 (0)