Skip to content

Commit d5b4251

Browse files
authored
Merge pull request #1560 from stgraber/main
Expand block reset logic
2 parents fc4d0ca + d4acafd commit d5b4251

File tree

3 files changed

+234
-47
lines changed

3 files changed

+234
-47
lines changed

internal/linux/discard.go

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
package linux
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
8+
"github.com/lxc/incus/v6/shared/subprocess"
9+
)
10+
11+
// ClearBlock fully resets a block device or disk file using the most efficient mechanism available.
12+
// For files, it will truncate them down to zero and back to their original size.
13+
// For blocks, it will attempt a variety of discard options, validating the result with marker files and eventually fallback to full zero-ing.
14+
func ClearBlock(blockPath string) error {
15+
// Open the block device for checking.
16+
fd, err := os.OpenFile(blockPath, os.O_RDWR, 0644)
17+
if err != nil {
18+
if os.IsNotExist(err) {
19+
// If the file is missing, there is nothing to clear.
20+
return nil
21+
}
22+
23+
return err
24+
}
25+
26+
defer fd.Close()
27+
28+
// Get the size of the file/block.
29+
size, err := fd.Seek(0, io.SeekEnd)
30+
if err != nil {
31+
return err
32+
}
33+
34+
// Get all the stat data.
35+
st, err := fd.Stat()
36+
if err != nil {
37+
return err
38+
}
39+
40+
if !IsBlockdev(st.Mode()) {
41+
// For files, truncate them.
42+
err := fd.Truncate(0)
43+
if err != nil {
44+
return err
45+
}
46+
47+
err = fd.Truncate(size)
48+
if err != nil {
49+
return err
50+
}
51+
52+
return nil
53+
}
54+
55+
// Blocks are trickier to reset with options varying based on disk features.
56+
// We use a set of 3 markers to validate whether it was reset.
57+
marker := []byte("INCUS")
58+
markerOffsetStart := int64(0)
59+
markerOffsetMiddle := size / 2
60+
markerOffsetEnd := size - 10
61+
62+
writeMarkers := func(fd *os.File) error {
63+
for _, offset := range []int64{markerOffsetStart, markerOffsetMiddle, markerOffsetEnd} {
64+
// Write the marker at the set offset.
65+
n, err := fd.WriteAt(marker, offset)
66+
if err != nil {
67+
return err
68+
}
69+
70+
if n != len(marker) {
71+
return fmt.Errorf("Only managed to write %d bytes out of %d of the %d offset marker", n, len(marker), offset)
72+
}
73+
}
74+
75+
return nil
76+
}
77+
78+
checkMarkers := func(fd *os.File) (int, error) {
79+
found := 0
80+
81+
for _, offset := range []int64{markerOffsetStart, markerOffsetMiddle, markerOffsetEnd} {
82+
fmt.Printf("offset=%d\n", offset)
83+
buf := make([]byte, 5)
84+
85+
// Read the marker from the offset.
86+
n, err := fd.ReadAt(buf, offset)
87+
if err != nil {
88+
return found, err
89+
}
90+
91+
if n != len(marker) {
92+
return found, fmt.Errorf("Only managed to read %d bytes out of %d of the %d offset marker", n, len(marker), offset)
93+
}
94+
95+
// Check if we found it.
96+
if string(buf) == string(marker) {
97+
found++
98+
}
99+
}
100+
101+
return found, nil
102+
}
103+
104+
// Write and check an initial set of markers.
105+
err = writeMarkers(fd)
106+
if err != nil {
107+
return err
108+
}
109+
110+
found, err := checkMarkers(fd)
111+
if err != nil {
112+
return err
113+
}
114+
115+
if found != 3 {
116+
return fmt.Errorf("Some of our initial markers weren't written properly")
117+
}
118+
119+
// Start clearing the block.
120+
_ = fd.Close()
121+
122+
// Attempt a secure discard run.
123+
_, err = subprocess.RunCommand("blkdiscard", "-f", "-s", blockPath)
124+
if err == nil {
125+
// Check if the markers are gone.
126+
fd, err := os.Open(blockPath)
127+
if err != nil {
128+
return err
129+
}
130+
131+
defer fd.Close()
132+
133+
found, err = checkMarkers(fd)
134+
if err != nil {
135+
return err
136+
}
137+
138+
if found == 0 {
139+
// All markers are gone, secure discard succeeded.
140+
return nil
141+
}
142+
143+
// Some markers were found, go to the next clearing option.
144+
_ = fd.Close()
145+
}
146+
147+
// Attempt a regular discard run.
148+
_, err = subprocess.RunCommand("blkdiscard", "-f", blockPath)
149+
if err == nil {
150+
// Check if the markers are gone.
151+
fd, err := os.Open(blockPath)
152+
if err != nil {
153+
return err
154+
}
155+
156+
defer fd.Close()
157+
158+
found, err = checkMarkers(fd)
159+
if err != nil {
160+
return err
161+
}
162+
163+
if found == 0 {
164+
// All markers are gone, regular discard succeeded.
165+
return nil
166+
}
167+
168+
// Some markers were found, go to the next clearing option.
169+
_ = fd.Close()
170+
}
171+
172+
// Attempt device zero-ing.
173+
_, err = subprocess.RunCommand("blkdiscard", "-f", "-z", blockPath)
174+
if err == nil {
175+
// Check if the markers are gone.
176+
fd, err := os.Open(blockPath)
177+
if err != nil {
178+
return err
179+
}
180+
181+
defer fd.Close()
182+
183+
found, err = checkMarkers(fd)
184+
if err != nil {
185+
return err
186+
}
187+
188+
if found == 0 {
189+
// All markers are gone, device zero-ing succeeded.
190+
return nil
191+
}
192+
193+
// Some markers were found, go to the next clearing option.
194+
_ = fd.Close()
195+
}
196+
197+
// All fast discard attempts have failed, proceed with manual zero-ing.
198+
zero, err := os.Open("/dev/zero")
199+
if err != nil {
200+
return err
201+
}
202+
203+
defer zero.Close()
204+
205+
fd, err = os.OpenFile(blockPath, os.O_WRONLY, 0644)
206+
if err != nil {
207+
return err
208+
}
209+
210+
defer fd.Close()
211+
212+
n, err := io.CopyN(fd, zero, size)
213+
if err != nil {
214+
return err
215+
}
216+
217+
if n != size {
218+
return fmt.Errorf("Only managed to reset %d bytes out of %d", n, size)
219+
}
220+
221+
return nil
222+
}

internal/server/storage/drivers/generic_vfs.go

+12-12
Original file line numberDiff line numberDiff line change
@@ -325,19 +325,19 @@ func genericVFSCreateVolumeFromMigration(d Driver, initVolume func(vol Volume) (
325325
wrapper = localMigration.ProgressTracker(op, "block_progress", volName)
326326
}
327327

328+
// Reset the disk.
329+
err := linux.ClearBlock(path)
330+
if err != nil {
331+
return err
332+
}
333+
328334
to, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0)
329335
if err != nil {
330336
return fmt.Errorf("Error opening file for writing %q: %w", path, err)
331337
}
332338

333339
defer func() { _ = to.Close() }()
334340

335-
// Reset the disk.
336-
err = clearDiskData(path, to)
337-
if err != nil {
338-
return err
339-
}
340-
341341
// Setup progress tracker.
342342
fromPipe := io.ReadCloser(conn)
343343
if wrapper != nil {
@@ -769,6 +769,12 @@ func genericVFSBackupUnpack(d Driver, sysOS *sys.OS, vol Volume, snapshots []str
769769
if hdr.Name == srcFile {
770770
var allowUnsafeResize bool
771771

772+
// Reset the disk.
773+
err = linux.ClearBlock(targetPath)
774+
if err != nil {
775+
return err
776+
}
777+
772778
// Open block file (use O_CREATE to support drivers that use image files).
773779
to, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
774780
if err != nil {
@@ -793,12 +799,6 @@ func genericVFSBackupUnpack(d Driver, sysOS *sys.OS, vol Volume, snapshots []str
793799
logMsg = "Unpacking custom block volume"
794800
}
795801

796-
// Reset the disk.
797-
err = clearDiskData(targetPath, to)
798-
if err != nil {
799-
return err
800-
}
801-
802802
// Copy the data.
803803
d.Logger().Debug(logMsg, logger.Ctx{"source": srcFile, "target": targetPath})
804804
_, err = io.Copy(NewSparseFileWrapper(to), tr)

internal/server/storage/drivers/utils.go

-35
Original file line numberDiff line numberDiff line change
@@ -957,38 +957,3 @@ func roundAbove(above, val int64) int64 {
957957

958958
return rounded
959959
}
960-
961-
// clearDiskData resets a disk file or device to a zero value.
962-
func clearDiskData(diskPath string, disk *os.File) error {
963-
st, err := disk.Stat()
964-
if err != nil {
965-
return err
966-
}
967-
968-
if linux.IsBlockdev(st.Mode()) {
969-
// If dealing with a block device, attempt to discard its current content.
970-
// This saves space and avoids issues with leaving zero blocks to their original value.
971-
_, err = subprocess.RunCommand("blkdiscard", "-f", diskPath)
972-
if err != nil {
973-
logger.Debugf("Unable to clear disk data on %q: %v", diskPath, err)
974-
}
975-
} else {
976-
// Otherwise truncate the file.
977-
err = disk.Truncate(0)
978-
if err != nil {
979-
return err
980-
}
981-
982-
err = disk.Truncate(st.Size())
983-
if err != nil {
984-
return err
985-
}
986-
987-
_, err = disk.Seek(0, 0)
988-
if err != nil {
989-
return err
990-
}
991-
}
992-
993-
return nil
994-
}

0 commit comments

Comments
 (0)