github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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 "strings" 28 29 "github.com/snapcore/snapd/gadget/quantity" 30 "github.com/snapcore/snapd/kernel" 31 "github.com/snapcore/snapd/strutil" 32 ) 33 34 // LayoutConstraints defines the constraints for arranging structures within a 35 // volume 36 type LayoutConstraints struct { 37 // NonMBRStartOffset is the default start offset of non-MBR structure in 38 // the volume. 39 NonMBRStartOffset quantity.Offset 40 // SkipResolveContent will skip resolving content paths 41 // and `$kernel:` style references 42 SkipResolveContent bool 43 } 44 45 // LaidOutVolume defines the size of a volume and arrangement of all the 46 // structures within it 47 type LaidOutVolume struct { 48 *Volume 49 // Size is the total size of the volume 50 Size quantity.Size 51 // LaidOutStructure is a list of structures within the volume, sorted 52 // by their start offsets 53 LaidOutStructure []LaidOutStructure 54 // RootDir is the root directory for volume data 55 RootDir string 56 } 57 58 // PartiallyLaidOutVolume defines the layout of volume structures, but lacks the 59 // details about the layout of raw image content within the bare structures. 60 type PartiallyLaidOutVolume struct { 61 *Volume 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 // ResolvedContent is a list of filesystem content that has all 82 // relative paths or references resolved 83 ResolvedContent []ResolvedContent 84 } 85 86 // IsRoleMBR returns whether a structure's role is MBR or not. 87 // meh this function is weirdly placed, not sure what to do w/o making schemaMBR 88 // constant exported 89 func IsRoleMBR(ls LaidOutStructure) bool { 90 return ls.Role == schemaMBR 91 } 92 93 func (p LaidOutStructure) String() string { 94 return fmtIndexAndName(p.Index, p.Name) 95 } 96 97 type byStartOffset []LaidOutStructure 98 99 func (b byStartOffset) Len() int { return len(b) } 100 func (b byStartOffset) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 101 func (b byStartOffset) Less(i, j int) bool { return b[i].StartOffset < b[j].StartOffset } 102 103 // LaidOutContent describes raw content that has been placed within the 104 // encompassing structure and volume 105 // 106 // TODO: this can't have "$kernel:" refs at this point, fail in validate 107 // for bare structures with "$kernel:" refs 108 type LaidOutContent struct { 109 *VolumeContent 110 111 // StartOffset defines the start offset of this content image 112 StartOffset quantity.Offset 113 // AbsoluteOffsetWrite is the resolved absolute position of offset-write 114 // for this content element within the enclosing volume 115 AbsoluteOffsetWrite *quantity.Offset 116 // Size is the maximum size occupied by this image 117 Size quantity.Size 118 // Index of the content in structure declaration inside gadget YAML 119 Index int 120 } 121 122 func (p LaidOutContent) String() string { 123 if p.Image != "" { 124 return fmt.Sprintf("#%v (%q@%#x{%v})", p.Index, p.Image, p.StartOffset, p.Size) 125 } 126 return fmt.Sprintf("#%v (source:%q)", p.Index, p.UnresolvedSource) 127 } 128 129 type ResolvedContent struct { 130 *VolumeContent 131 132 // ResolvedSource is the absolute path of the Source after resolving 133 // any references (e.g. to a "$kernel:" snap). 134 ResolvedSource string 135 136 // KernelUpdate is true if this content comes from the kernel 137 // and has the "Update" property set 138 KernelUpdate bool 139 } 140 141 func layoutVolumeStructures(volume *Volume, constraints LayoutConstraints) (structures []LaidOutStructure, byName map[string]*LaidOutStructure, err error) { 142 previousEnd := quantity.Offset(0) 143 structures = make([]LaidOutStructure, len(volume.Structure)) 144 byName = make(map[string]*LaidOutStructure, len(volume.Structure)) 145 146 for idx, s := range volume.Structure { 147 var start quantity.Offset 148 if s.Offset == nil { 149 if s.Role != schemaMBR && previousEnd < constraints.NonMBRStartOffset { 150 start = constraints.NonMBRStartOffset 151 } else { 152 start = previousEnd 153 } 154 } else { 155 start = *s.Offset 156 } 157 158 end := start + quantity.Offset(s.Size) 159 ps := LaidOutStructure{ 160 VolumeStructure: &volume.Structure[idx], 161 StartOffset: start, 162 Index: idx, 163 } 164 165 if ps.Name != "" { 166 byName[ps.Name] = &ps 167 } 168 169 structures[idx] = ps 170 171 previousEnd = end 172 } 173 174 // sort by starting offset 175 sort.Sort(byStartOffset(structures)) 176 177 previousEnd = quantity.Offset(0) 178 for idx, ps := range structures { 179 if ps.StartOffset < previousEnd { 180 return nil, nil, fmt.Errorf("cannot lay out volume, structure %v overlaps with preceding structure %v", ps, structures[idx-1]) 181 } 182 previousEnd = ps.StartOffset + quantity.Offset(ps.Size) 183 184 offsetWrite, err := resolveOffsetWrite(ps.OffsetWrite, byName) 185 if err != nil { 186 return nil, nil, fmt.Errorf("cannot resolve offset-write of structure %v: %v", ps, err) 187 } 188 structures[idx].AbsoluteOffsetWrite = offsetWrite 189 } 190 191 return structures, byName, nil 192 } 193 194 // LayoutVolumePartially attempts to lay out only the structures in the volume using provided constraints 195 func LayoutVolumePartially(volume *Volume, constraints LayoutConstraints) (*PartiallyLaidOutVolume, error) { 196 structures, _, err := layoutVolumeStructures(volume, constraints) 197 if err != nil { 198 return nil, err 199 } 200 201 vol := &PartiallyLaidOutVolume{ 202 Volume: volume, 203 LaidOutStructure: structures, 204 } 205 return vol, nil 206 } 207 208 // LayoutVolume attempts to completely lay out the volume, that is the 209 // structures and their content, using provided constraints 210 func LayoutVolume(gadgetRootDir, kernelRootDir string, volume *Volume, constraints LayoutConstraints) (*LaidOutVolume, error) { 211 var err error 212 213 var kernelInfo *kernel.Info 214 if !constraints.SkipResolveContent { 215 // TODO:UC20: check and error if kernelRootDir == "" here 216 // This needs the upper layer of gadget updates to be 217 // updated to pass the kernel root first. 218 // 219 // Note that the kernelRootDir may reference the running 220 // kernel if there is a gadget update or the new kernel if 221 // there is a kernel update. 222 kernelInfo, err = kernel.ReadInfo(kernelRootDir) 223 if err != nil { 224 return nil, err 225 } 226 } 227 228 structures, byName, err := layoutVolumeStructures(volume, constraints) 229 if err != nil { 230 return nil, err 231 } 232 233 farthestEnd := quantity.Offset(0) 234 fartherstOffsetWrite := quantity.Offset(0) 235 236 for idx, ps := range structures { 237 if ps.AbsoluteOffsetWrite != nil && *ps.AbsoluteOffsetWrite > fartherstOffsetWrite { 238 fartherstOffsetWrite = *ps.AbsoluteOffsetWrite 239 } 240 if end := ps.StartOffset + quantity.Offset(ps.Size); end > farthestEnd { 241 farthestEnd = end 242 } 243 244 // lay out raw content 245 content, err := layOutStructureContent(gadgetRootDir, &structures[idx], byName) 246 if err != nil { 247 return nil, err 248 } 249 250 for _, c := range content { 251 if c.AbsoluteOffsetWrite != nil && *c.AbsoluteOffsetWrite > fartherstOffsetWrite { 252 fartherstOffsetWrite = *c.AbsoluteOffsetWrite 253 } 254 } 255 256 structures[idx].LaidOutContent = content 257 258 // resolve filesystem content 259 if !constraints.SkipResolveContent { 260 resolvedContent, err := resolveVolumeContent(gadgetRootDir, kernelRootDir, kernelInfo, &structures[idx]) 261 if err != nil { 262 return nil, err 263 } 264 structures[idx].ResolvedContent = resolvedContent 265 } 266 } 267 268 volumeSize := quantity.Size(farthestEnd) 269 if fartherstOffsetWrite+quantity.Offset(SizeLBA48Pointer) > farthestEnd { 270 volumeSize = quantity.Size(fartherstOffsetWrite) + SizeLBA48Pointer 271 } 272 273 vol := &LaidOutVolume{ 274 Volume: volume, 275 Size: volumeSize, 276 LaidOutStructure: structures, 277 RootDir: gadgetRootDir, 278 } 279 return vol, nil 280 } 281 282 func resolveVolumeContent(gadgetRootDir, kernelRootDir string, kernelInfo *kernel.Info, ps *LaidOutStructure) ([]ResolvedContent, error) { 283 if !ps.HasFilesystem() { 284 // structures without a file system are not resolved here 285 return nil, nil 286 } 287 if len(ps.Content) == 0 { 288 return nil, nil 289 } 290 291 content := make([]ResolvedContent, len(ps.Content)) 292 for idx := range ps.Content { 293 resolvedSource, kupdate, err := resolveContentPathOrRef(gadgetRootDir, kernelRootDir, kernelInfo, ps.Content[idx].UnresolvedSource) 294 if err != nil { 295 return nil, fmt.Errorf("cannot resolve content for structure %v at index %v: %v", ps, idx, err) 296 } 297 content[idx] = ResolvedContent{ 298 VolumeContent: &ps.Content[idx], 299 ResolvedSource: resolvedSource, 300 KernelUpdate: kupdate, 301 } 302 } 303 304 return content, nil 305 } 306 307 // resolveContentPathOrRef resolves the relative path from gadget 308 // assets and any "$kernel:" references from "pathOrRef" using the 309 // provided gadget/kernel directories and the kernel info. It returns 310 // an absolute path, a flag indicating whether the content is part of 311 // a kernel update, or an error. 312 func resolveContentPathOrRef(gadgetRootDir, kernelRootDir string, kernelInfo *kernel.Info, pathOrRef string) (resolved string, kupdate bool, err error) { 313 314 // TODO: add kernelRootDir == "" error too once all the higher 315 // layers in devicestate call gadget.Update() with a 316 // kernel dir set 317 switch { 318 case gadgetRootDir == "": 319 return "", false, fmt.Errorf("internal error: gadget root dir cannot beempty") 320 case pathOrRef == "": 321 return "", false, fmt.Errorf("cannot use empty source") 322 } 323 324 // content may refer to "$kernel:<name>/<content>" 325 var resolvedSource string 326 if strings.HasPrefix(pathOrRef, "$kernel:") { 327 wantedAsset, wantedContent, err := splitKernelRef(pathOrRef) 328 if err != nil { 329 return "", false, fmt.Errorf("cannot parse kernel ref: %v", err) 330 } 331 kernelAsset, ok := kernelInfo.Assets[wantedAsset] 332 if !ok { 333 return "", false, fmt.Errorf("cannot find %q in kernel info from %q", wantedAsset, kernelRootDir) 334 } 335 if !strutil.ListContains(kernelAsset.Content, wantedContent) { 336 return "", false, fmt.Errorf("cannot find wanted kernel content %q in %q", wantedContent, kernelRootDir) 337 } 338 resolvedSource = filepath.Join(kernelRootDir, wantedContent) 339 kupdate = kernelAsset.Update 340 } else { 341 resolvedSource = filepath.Join(gadgetRootDir, pathOrRef) 342 } 343 344 // restore trailing / if one was there 345 if strings.HasSuffix(pathOrRef, "/") { 346 resolvedSource += "/" 347 } 348 349 return resolvedSource, kupdate, nil 350 } 351 352 type byContentStartOffset []LaidOutContent 353 354 func (b byContentStartOffset) Len() int { return len(b) } 355 func (b byContentStartOffset) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 356 func (b byContentStartOffset) Less(i, j int) bool { return b[i].StartOffset < b[j].StartOffset } 357 358 func getImageSize(path string) (quantity.Size, error) { 359 stat, err := os.Stat(path) 360 if err != nil { 361 return 0, err 362 } 363 return quantity.Size(stat.Size()), nil 364 } 365 366 func layOutStructureContent(gadgetRootDir string, ps *LaidOutStructure, known map[string]*LaidOutStructure) ([]LaidOutContent, error) { 367 if ps.HasFilesystem() { 368 // structures with a filesystem do not need any extra layout 369 return nil, nil 370 } 371 if len(ps.Content) == 0 { 372 return nil, nil 373 } 374 375 content := make([]LaidOutContent, len(ps.Content)) 376 previousEnd := quantity.Offset(0) 377 378 for idx, c := range ps.Content { 379 imageSize, err := getImageSize(filepath.Join(gadgetRootDir, c.Image)) 380 if err != nil { 381 return nil, fmt.Errorf("cannot lay out structure %v: content %q: %v", ps, c.Image, err) 382 } 383 384 var start quantity.Offset 385 if c.Offset != nil { 386 start = *c.Offset 387 } else { 388 start = previousEnd 389 } 390 391 actualSize := imageSize 392 393 if c.Size != 0 { 394 if c.Size < imageSize { 395 return nil, fmt.Errorf("cannot lay out structure %v: content %q size %v is larger than declared %v", ps, c.Image, actualSize, c.Size) 396 } 397 actualSize = c.Size 398 } 399 400 offsetWrite, err := resolveOffsetWrite(c.OffsetWrite, known) 401 if err != nil { 402 return nil, fmt.Errorf("cannot resolve offset-write of structure %v content %q: %v", ps, c.Image, err) 403 } 404 405 content[idx] = LaidOutContent{ 406 VolumeContent: &ps.Content[idx], 407 Size: actualSize, 408 StartOffset: ps.StartOffset + start, 409 Index: idx, 410 // break for gofmt < 1.11 411 AbsoluteOffsetWrite: offsetWrite, 412 } 413 previousEnd = start + quantity.Offset(actualSize) 414 if quantity.Size(previousEnd) > ps.Size { 415 return nil, fmt.Errorf("cannot lay out structure %v: content %q does not fit in the structure", ps, c.Image) 416 } 417 } 418 419 sort.Sort(byContentStartOffset(content)) 420 421 previousEnd = ps.StartOffset 422 for idx, pc := range content { 423 if pc.StartOffset < previousEnd { 424 return nil, fmt.Errorf("cannot lay out structure %v: content %q overlaps with preceding image %q", ps, pc.Image, content[idx-1].Image) 425 } 426 previousEnd = pc.StartOffset + quantity.Offset(pc.Size) 427 } 428 429 return content, nil 430 } 431 432 func resolveOffsetWrite(offsetWrite *RelativeOffset, knownStructs map[string]*LaidOutStructure) (*quantity.Offset, error) { 433 if offsetWrite == nil { 434 return nil, nil 435 } 436 437 var relativeToOffset quantity.Offset 438 if offsetWrite.RelativeTo != "" { 439 otherStruct, ok := knownStructs[offsetWrite.RelativeTo] 440 if !ok { 441 return nil, fmt.Errorf("refers to an unknown structure %q", offsetWrite.RelativeTo) 442 } 443 relativeToOffset = otherStruct.StartOffset 444 } 445 446 resolvedOffsetWrite := relativeToOffset + offsetWrite.Offset 447 return &resolvedOffsetWrite, nil 448 } 449 450 // ShiftStructureTo translates the starting offset of a laid out structure and 451 // its content to the provided offset. 452 func ShiftStructureTo(ps LaidOutStructure, offset quantity.Offset) LaidOutStructure { 453 change := int64(offset - ps.StartOffset) 454 455 newPs := ps 456 newPs.StartOffset = quantity.Offset(int64(ps.StartOffset) + change) 457 458 newPs.LaidOutContent = make([]LaidOutContent, len(ps.LaidOutContent)) 459 for idx, pc := range ps.LaidOutContent { 460 newPc := pc 461 newPc.StartOffset = quantity.Offset(int64(pc.StartOffset) + change) 462 newPs.LaidOutContent[idx] = newPc 463 } 464 return newPs 465 } 466 467 func isLayoutCompatible(current, new *PartiallyLaidOutVolume) error { 468 if current.ID != new.ID { 469 return fmt.Errorf("incompatible ID change from %v to %v", current.ID, new.ID) 470 } 471 if current.Schema != new.Schema { 472 return fmt.Errorf("incompatible schema change from %v to %v", 473 current.Schema, new.Schema) 474 } 475 if current.Bootloader != new.Bootloader { 476 return fmt.Errorf("incompatible bootloader change from %v to %v", 477 current.Bootloader, new.Bootloader) 478 } 479 480 // XXX: the code below asssumes both volumes have the same number of 481 // structures, this limitation may be lifted later 482 if len(current.LaidOutStructure) != len(new.LaidOutStructure) { 483 return fmt.Errorf("incompatible change in the number of structures from %v to %v", 484 len(current.LaidOutStructure), len(new.LaidOutStructure)) 485 } 486 487 // at the structure level we expect the volume to be identical 488 for i := range current.LaidOutStructure { 489 from := ¤t.LaidOutStructure[i] 490 to := &new.LaidOutStructure[i] 491 if err := canUpdateStructure(from, to, new.Schema); err != nil { 492 return fmt.Errorf("incompatible structure %v change: %v", to, err) 493 } 494 } 495 return nil 496 }