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 := &current.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  }