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