github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/layout.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package gadget 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "sort" 27 ) 28 29 // LayoutConstraints defines the constraints for arranging structures within a 30 // volume 31 type LayoutConstraints struct { 32 // NonMBRStartOffset is the default start offset of non-MBR structure in 33 // the volume. 34 NonMBRStartOffset Size 35 // SectorSize is the size of the sector to be used for calculations 36 SectorSize Size 37 } 38 39 // LaidOutVolume defines the size of a volume and arrangement of all the 40 // structures within it 41 type LaidOutVolume struct { 42 *Volume 43 // Size is the total size of the volume 44 Size Size 45 // SectorSize sector size of the volume 46 SectorSize Size 47 // LaidOutStructure is a list of structures within the volume, sorted 48 // by their start offsets 49 LaidOutStructure []LaidOutStructure 50 // RootDir is the root directory for volume data 51 RootDir string 52 } 53 54 // PartiallyLaidOutVolume defines the layout of volume structures, but lacks the 55 // details about the layout of raw image content within the bare structures. 56 type PartiallyLaidOutVolume struct { 57 *Volume 58 // SectorSize sector size of the volume 59 SectorSize Size 60 // LaidOutStructure is a list of structures within the volume, sorted 61 // by their start offsets 62 LaidOutStructure []LaidOutStructure 63 } 64 65 // LaidOutStructure describes a VolumeStructure that has been placed within the 66 // volume 67 type LaidOutStructure struct { 68 *VolumeStructure 69 // StartOffset defines the start offset of the structure within the 70 // enclosing volume 71 StartOffset Size 72 // AbsoluteOffsetWrite is the resolved absolute position of offset-write 73 // for this structure element within the enclosing volume 74 AbsoluteOffsetWrite *Size 75 // Index of the structure definition in gadget YAML 76 Index int 77 // LaidOutContent is a list of raw content inside the structure 78 LaidOutContent []LaidOutContent 79 } 80 81 func (p LaidOutStructure) String() string { 82 return fmtIndexAndName(p.Index, p.Name) 83 } 84 85 type byStartOffset []LaidOutStructure 86 87 func (b byStartOffset) Len() int { return len(b) } 88 func (b byStartOffset) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 89 func (b byStartOffset) Less(i, j int) bool { return b[i].StartOffset < b[j].StartOffset } 90 91 // LaidOutContent describes raw content that has been placed within the 92 // encompassing structure and volume 93 type LaidOutContent struct { 94 *VolumeContent 95 96 // StartOffset defines the start offset of this content image 97 StartOffset Size 98 // AbsoluteOffsetWrite is the resolved absolute position of offset-write 99 // for this content element within the enclosing volume 100 AbsoluteOffsetWrite *Size 101 // Size is the maximum size occupied by this image 102 Size Size 103 // Index of the content in structure declaration inside gadget YAML 104 Index int 105 } 106 107 func (p LaidOutContent) String() string { 108 if p.Image != "" { 109 return fmt.Sprintf("#%v (%q@%#x{%v})", p.Index, p.Image, p.StartOffset, p.Size) 110 } 111 return fmt.Sprintf("#%v (source:%q)", p.Index, p.Source) 112 } 113 114 func layoutVolumeStructures(volume *Volume, constraints LayoutConstraints) (structures []LaidOutStructure, byName map[string]*LaidOutStructure, err error) { 115 previousEnd := Size(0) 116 structures = make([]LaidOutStructure, len(volume.Structure)) 117 byName = make(map[string]*LaidOutStructure, len(volume.Structure)) 118 119 if constraints.SectorSize == 0 { 120 return nil, nil, fmt.Errorf("cannot lay out volume, invalid constraints: sector size cannot be 0") 121 } 122 123 for idx, s := range volume.Structure { 124 var start Size 125 if s.Offset == nil { 126 if s.EffectiveRole() != schemaMBR && previousEnd < constraints.NonMBRStartOffset { 127 start = constraints.NonMBRStartOffset 128 } else { 129 start = previousEnd 130 } 131 } else { 132 start = *s.Offset 133 } 134 135 end := start + s.Size 136 ps := LaidOutStructure{ 137 VolumeStructure: &volume.Structure[idx], 138 StartOffset: start, 139 Index: idx, 140 } 141 142 if ps.EffectiveRole() != schemaMBR { 143 if s.Size%constraints.SectorSize != 0 { 144 return nil, nil, fmt.Errorf("cannot lay out volume, structure %v size is not a multiple of sector size %v", 145 ps, constraints.SectorSize) 146 } 147 } 148 149 if ps.Name != "" { 150 byName[ps.Name] = &ps 151 } 152 153 structures[idx] = ps 154 155 previousEnd = end 156 } 157 158 // sort by starting offset 159 sort.Sort(byStartOffset(structures)) 160 161 previousEnd = Size(0) 162 for idx, ps := range structures { 163 if ps.StartOffset < previousEnd { 164 return nil, nil, fmt.Errorf("cannot lay out volume, structure %v overlaps with preceding structure %v", ps, structures[idx-1]) 165 } 166 previousEnd = ps.StartOffset + ps.Size 167 168 offsetWrite, err := resolveOffsetWrite(ps.OffsetWrite, byName) 169 if err != nil { 170 return nil, nil, fmt.Errorf("cannot resolve offset-write of structure %v: %v", ps, err) 171 } 172 structures[idx].AbsoluteOffsetWrite = offsetWrite 173 } 174 175 return structures, byName, nil 176 } 177 178 // LayoutVolumePartially attempts to lay out only the structures in the volume using provided constraints 179 func LayoutVolumePartially(volume *Volume, constraints LayoutConstraints) (*PartiallyLaidOutVolume, error) { 180 structures, _, err := layoutVolumeStructures(volume, constraints) 181 if err != nil { 182 return nil, err 183 } 184 vol := &PartiallyLaidOutVolume{ 185 Volume: volume, 186 SectorSize: constraints.SectorSize, 187 LaidOutStructure: structures, 188 } 189 return vol, nil 190 } 191 192 // LayoutVolume attempts to completely lay out the volume, that is the 193 // structures and their content, using provided constraints 194 func LayoutVolume(gadgetRootDir string, volume *Volume, constraints LayoutConstraints) (*LaidOutVolume, error) { 195 196 structures, byName, err := layoutVolumeStructures(volume, constraints) 197 if err != nil { 198 return nil, err 199 } 200 201 farthestEnd := Size(0) 202 fartherstOffsetWrite := Size(0) 203 204 for idx, ps := range structures { 205 if ps.AbsoluteOffsetWrite != nil && *ps.AbsoluteOffsetWrite > fartherstOffsetWrite { 206 fartherstOffsetWrite = *ps.AbsoluteOffsetWrite 207 } 208 if end := ps.StartOffset + ps.Size; end > farthestEnd { 209 farthestEnd = end 210 } 211 212 content, err := layOutStructureContent(gadgetRootDir, &structures[idx], byName) 213 if err != nil { 214 return nil, err 215 } 216 217 for _, c := range content { 218 if c.AbsoluteOffsetWrite != nil && *c.AbsoluteOffsetWrite > fartherstOffsetWrite { 219 fartherstOffsetWrite = *c.AbsoluteOffsetWrite 220 } 221 } 222 223 structures[idx].LaidOutContent = content 224 } 225 226 volumeSize := farthestEnd 227 if fartherstOffsetWrite+SizeLBA48Pointer > farthestEnd { 228 volumeSize = fartherstOffsetWrite + SizeLBA48Pointer 229 } 230 231 vol := &LaidOutVolume{ 232 Volume: volume, 233 Size: volumeSize, 234 SectorSize: constraints.SectorSize, 235 LaidOutStructure: structures, 236 RootDir: gadgetRootDir, 237 } 238 return vol, nil 239 } 240 241 type byContentStartOffset []LaidOutContent 242 243 func (b byContentStartOffset) Len() int { return len(b) } 244 func (b byContentStartOffset) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 245 func (b byContentStartOffset) Less(i, j int) bool { return b[i].StartOffset < b[j].StartOffset } 246 247 func getImageSize(path string) (Size, error) { 248 stat, err := os.Stat(path) 249 if err != nil { 250 return 0, err 251 } 252 return Size(stat.Size()), nil 253 } 254 255 func layOutStructureContent(gadgetRootDir string, ps *LaidOutStructure, known map[string]*LaidOutStructure) ([]LaidOutContent, error) { 256 if ps.HasFilesystem() { 257 // structures with a filesystem do not need any extra layout 258 return nil, nil 259 } 260 if len(ps.Content) == 0 { 261 return nil, nil 262 } 263 264 content := make([]LaidOutContent, len(ps.Content)) 265 previousEnd := Size(0) 266 267 for idx, c := range ps.Content { 268 imageSize, err := getImageSize(filepath.Join(gadgetRootDir, c.Image)) 269 if err != nil { 270 return nil, fmt.Errorf("cannot lay out structure %v: content %q: %v", ps, c.Image, err) 271 } 272 273 var start Size 274 if c.Offset != nil { 275 start = *c.Offset 276 } else { 277 start = previousEnd 278 } 279 280 actualSize := imageSize 281 282 if c.Size != 0 { 283 if c.Size < imageSize { 284 return nil, fmt.Errorf("cannot lay out structure %v: content %q size %v is larger than declared %v", ps, c.Image, actualSize, c.Size) 285 } 286 actualSize = c.Size 287 } 288 289 offsetWrite, err := resolveOffsetWrite(c.OffsetWrite, known) 290 if err != nil { 291 return nil, fmt.Errorf("cannot resolve offset-write of structure %v content %q: %v", ps, c.Image, err) 292 } 293 294 content[idx] = LaidOutContent{ 295 VolumeContent: &ps.Content[idx], 296 Size: actualSize, 297 StartOffset: ps.StartOffset + start, 298 Index: idx, 299 // break for gofmt < 1.11 300 AbsoluteOffsetWrite: offsetWrite, 301 } 302 previousEnd = start + actualSize 303 if previousEnd > ps.Size { 304 return nil, fmt.Errorf("cannot lay out structure %v: content %q does not fit in the structure", ps, c.Image) 305 } 306 } 307 308 sort.Sort(byContentStartOffset(content)) 309 310 previousEnd = ps.StartOffset 311 for idx, pc := range content { 312 if pc.StartOffset < previousEnd { 313 return nil, fmt.Errorf("cannot lay out structure %v: content %q overlaps with preceding image %q", ps, pc.Image, content[idx-1].Image) 314 } 315 previousEnd = pc.StartOffset + pc.Size 316 } 317 318 return content, nil 319 } 320 321 func resolveOffsetWrite(offsetWrite *RelativeOffset, knownStructs map[string]*LaidOutStructure) (*Size, error) { 322 if offsetWrite == nil { 323 return nil, nil 324 } 325 326 var relativeToOffset Size 327 if offsetWrite.RelativeTo != "" { 328 otherStruct, ok := knownStructs[offsetWrite.RelativeTo] 329 if !ok { 330 return nil, fmt.Errorf("refers to an unknown structure %q", offsetWrite.RelativeTo) 331 } 332 relativeToOffset = otherStruct.StartOffset 333 } 334 335 resolvedOffsetWrite := relativeToOffset + offsetWrite.Offset 336 return &resolvedOffsetWrite, nil 337 } 338 339 // ShiftStructureTo translates the starting offset of a laid out structure and 340 // its content to the provided offset. 341 func ShiftStructureTo(ps LaidOutStructure, offset Size) LaidOutStructure { 342 change := int64(offset - ps.StartOffset) 343 344 newPs := ps 345 newPs.StartOffset = Size(int64(ps.StartOffset) + change) 346 347 newPs.LaidOutContent = make([]LaidOutContent, len(ps.LaidOutContent)) 348 for idx, pc := range ps.LaidOutContent { 349 newPc := pc 350 newPc.StartOffset = Size(int64(pc.StartOffset) + change) 351 newPs.LaidOutContent[idx] = newPc 352 } 353 return newPs 354 } 355 356 func isLayoutCompatible(current, new *PartiallyLaidOutVolume) error { 357 if current.ID != new.ID { 358 return fmt.Errorf("incompatible ID change from %v to %v", current.ID, new.ID) 359 } 360 if current.EffectiveSchema() != new.EffectiveSchema() { 361 return fmt.Errorf("incompatible schema change from %v to %v", 362 current.EffectiveSchema(), new.EffectiveSchema()) 363 } 364 if current.Bootloader != new.Bootloader { 365 return fmt.Errorf("incompatible bootloader change from %v to %v", 366 current.Bootloader, new.Bootloader) 367 } 368 369 // XXX: the code below asssumes both volumes have the same number of 370 // structures, this limitation may be lifter later 371 if len(current.LaidOutStructure) != len(new.LaidOutStructure) { 372 return fmt.Errorf("incompatible change in the number of structures from %v to %v", 373 len(current.LaidOutStructure), len(new.LaidOutStructure)) 374 } 375 376 // at the structure level we expect the volume to be identical 377 for i := range current.LaidOutStructure { 378 from := ¤t.LaidOutStructure[i] 379 to := &new.LaidOutStructure[i] 380 if err := canUpdateStructure(from, to, new.EffectiveSchema()); err != nil { 381 return fmt.Errorf("incompatible structure %v change: %v", to, err) 382 } 383 } 384 return nil 385 }