-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun_helpers.go
More file actions
160 lines (147 loc) · 4.62 KB
/
run_helpers.go
File metadata and controls
160 lines (147 loc) · 4.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package partitionresizer
import (
"errors"
"fmt"
"log"
"os"
"os/exec"
"github.com/diskfs/go-diskfs/disk"
"github.com/diskfs/go-diskfs/partition/gpt"
)
const (
partTmpFilename = "partresizer-shrinkfs-XXXXXXXX"
)
// execResize2fs is the function used to invoke resize2fs. partDevice may be a block device pointing to the actual
// filesystem partition, or an image file with the filesystem at byte 0.
var execResize2fs = func(partDevice string, newSizeMB int64, fixErrors bool) error {
fixFlag := "-n"
if fixErrors {
fixFlag = "-y"
}
cmd := exec.Command("e2fsck", "-f", fixFlag, partDevice)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("e2fsck failed: %w", err)
}
cmd = exec.Command("resize2fs", partDevice, fmt.Sprintf("%dM", newSizeMB))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("resize2fs failed: %w", err)
}
return nil
}
// resizeFilesystem resizes an ext4 filesystem, given a full path to the device and partition data
// Should account for it being a disk image with multiple partitions if needed, i.e. not just an entire disk,
// using the information in filesystemData.
// filesystemData is expected to be the *current* partition data, i.e. before resizing,
// while delta is the expected delta in size.
func resizeFilesystem(
device string,
filesystemData partitionData,
delta int64,
fixErrors bool,
) error {
newSize := filesystemData.size + delta
newSizeMB := newSize / (1024 * 1024)
log.Printf(
"Resizing filesystem on partition %d to %d MB",
filesystemData.number, newSizeMB,
)
f, err := os.Open(device)
if err != nil {
return err
}
defer func() { _ = f.Close() }()
deviceType, err := disk.DetermineDeviceType(f)
if err != nil {
return err
}
switch deviceType {
case disk.DeviceTypeBlockDevice:
return execResize2fs(device, newSizeMB, fixErrors)
case disk.DeviceTypeFile:
// copy the partition, then resize it, then copy it back into the original disk image
tmpFile, err2 := os.CreateTemp("", partTmpFilename)
if err2 != nil {
return err2
}
_ = tmpFile.Close()
defer func() {
_ = os.RemoveAll(tmpFile.Name())
}()
// copy the file over
if err = CopyRange(device, tmpFile.Name(), filesystemData.start, 0, filesystemData.size, 0); err != nil {
return fmt.Errorf("copy to temp file: %w", err)
}
if err = execResize2fs(tmpFile.Name(), newSizeMB, fixErrors); err != nil {
return err
}
err = CopyRange(tmpFile.Name(), device, 0, filesystemData.start, newSize, 0)
case disk.DeviceTypeUnknown:
err = fmt.Errorf("unknown device type for %s", device)
}
return err
}
// planResizes computes the resize plan, including both growing the relevant partitions as well as
// optionally performing an ext4 shrink, if there is insufficient space initially.
// Returns the final plan or an error.
func planResizes(
d *disk.Disk,
table *gpt.Table,
diskPartitionData []partitionData,
growPartitions []PartitionChange,
shrinkPartition *PartitionIdentifier,
) (
[]partitionResizeTarget,
error,
) {
// map PartitionChange to partitionResizeTarget
prTargets, err := partitionChangesToResizeTarget(table, diskPartitionData, growPartitions)
if err != nil {
return nil, err
}
// try to calculate without shrinking
resizes, err := calculateResizes(d.Size, table.Partitions, prTargets)
if err == nil {
return resizes, nil
}
var spaceErr *InsufficientSpaceError
if !errors.As(err, &spaceErr) {
return nil, err
}
// need to shrink: ensure shrinkPartition provided
if shrinkPartition == nil {
return nil, fmt.Errorf("insufficient space to perform requested partition grows, and no shrink partition specified")
}
// compute total space to grow (rounded up to next GB)
var totalGrow int64
for _, gp := range prTargets {
totalGrow += gp.target.size
}
if totalGrow%GB != 0 {
totalGrow = ((totalGrow / GB) + 1) * GB
}
// locate shrink partition data
shrinkDataList, err := partitionIdentifiersToData(table, diskPartitionData, []PartitionIdentifier{*shrinkPartition})
if err != nil {
return nil, err
}
if len(shrinkDataList) != 1 {
return nil, fmt.Errorf("could not find shrink partition data")
}
shrinkData := shrinkDataList[0]
// mark the shrink as first for the resize
target := shrinkData
target.size = shrinkData.size - totalGrow
target.end = shrinkData.end - totalGrow
shrink := partitionResizeTarget{
original: shrinkData,
target: target,
}
prTargetsWithShrink := []partitionResizeTarget{shrink}
prTargetsWithShrink = append(prTargetsWithShrink, prTargets...)
// recalculate resizes with shrinking
return calculateResizes(d.Size, table.Partitions, prTargetsWithShrink)
}