Skip to content

Commit

Permalink
address feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
elainezhao96 committed Jan 22, 2025
1 parent d1d3541 commit e311be9
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 124 deletions.
196 changes: 119 additions & 77 deletions toolkit/tools/pkg/imagecustomizerlib/cosicommon.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,22 @@ import (
"github.com/microsoft/azurelinux/toolkit/tools/internal/safeloopback"
)

type cosiPartitionMetadata struct {
PartitionNum int `json:"partitionnum"` // Example: 1
PartitionFilename string `json:"filename"` // Example: image_1.raw.zst
PartLabel string `json:"partlabel"` // Example: boot
FileSystemType string `json:"fstype"` // Example: vfat
PartitionTypeUuid string `json:"parttype"` // Example: c12a7328-f81f-11d2-ba4b-00a0c93ec93b
Uuid string `json:"uuid"` // Example: 4BD9-3A78
PartUuid string `json:"partuuid"` // Example: 7b1367a6-5845-43f2-99b1-a742d873f590
Mountpoint string `json:"mountpoint"` // Example: /mnt/os/boot
UncompressedSize uint64 `json:"uncompressedsize"` // Example: 104857600
Verity []*Verity `json:"verity,omitempty"`
}

type ImageBuildData struct {
Source string
KnownInfo outputPartitionMetadata
KnownInfo cosiPartitionMetadata
Metadata *Image
VeritySource *string
}
Expand All @@ -41,9 +54,9 @@ func convertToCosi(ic *ImageCustomizerParameters) error {
return err
}

cosiMetadata, err := buildCosiData(partitionMetadataOutput, ic.rootPartUUID, ic.rootHash)
cosiMetadata, err := buildCosiData(partitionMetadataOutput, ic.verityMetadata)
if err != nil {
return fmt.Errorf("failed to format COSI data: %w", err)
return fmt.Errorf("failed to format COSI data:\n%w", err)
}

err = buildCosiFile(outputDir, ic.outputImageFile, cosiMetadata, ic.imageUuidStr)
Expand All @@ -61,7 +74,7 @@ func convertToCosi(ic *ImageCustomizerParameters) error {
return nil
}

func buildCosiFile(sourceDir string, outputFile string, expectedImages []outputPartitionMetadata, imageUuidStr string) error {
func buildCosiFile(sourceDir string, outputFile string, expectedImages []cosiPartitionMetadata, imageUuidStr string) error {
metadata := MetadataJson{
Version: "1.0",
OsArch: runtime.GOARCH,
Expand All @@ -73,41 +86,48 @@ func buildCosiFile(sourceDir string, outputFile string, expectedImages []outputP
return fmt.Errorf("no images to build")
}

// Create an interim metadata struct to combine the known data with the metadata
imageData := make([]ImageBuildData, len(expectedImages))
for i, image := range expectedImages {
metadata := &metadata.Images[i]
metadataImage := &metadata.Images[i]
imageData[i] = ImageBuildData{
Source: path.Join(sourceDir, image.PartitionFilename),
Metadata: metadata,
Metadata: metadataImage,
KnownInfo: image,
}

metadata.Image.Path = path.Join("images", image.PartitionFilename)
metadata.PartType = image.PartitionTypeUuid
metadata.MountPoint = image.Mountpoint
metadata.FsType = image.FileSystemType
metadata.FsUuid = image.Uuid
metadata.UncompressedSize = image.UncompressedSize
metadataImage.Image.Path = path.Join("images", image.PartitionFilename)
metadataImage.PartType = image.PartitionTypeUuid
metadataImage.MountPoint = image.Mountpoint
metadataImage.FsType = image.FileSystemType
metadataImage.FsUuid = image.Uuid
metadataImage.Image.UncompressedSize = image.UncompressedSize

if image.Verity != nil {
veritySourcePath := path.Join(sourceDir, image.Verity.Image.Path)
imageData[i].VeritySource = &veritySourcePath

metadata.Verity = &Verity{
Image: ImageFile{
Path: path.Join("images", image.Verity.Image.Path),
},
// Initialize the Verity slice if not already initialized
if metadataImage.Verity == nil {
metadataImage.Verity = []*Verity{}
}

metadata.Verity.Roothash = image.Verity.Roothash
}
for _, verity := range image.Verity {
veritySourcePath := path.Join(sourceDir, verity.Image.Path)
imageData[i].VeritySource = &veritySourcePath

// Append Verity metadata
metadataImage.Verity = append(metadataImage.Verity, &Verity{
Image: ImageFile{
Path: path.Join("images", verity.Image.Path),
UncompressedSize: verity.Image.UncompressedSize,
},
Hash: verity.Hash,
})
}
} else {
metadataImage.Verity = nil
}
}

// Populate metadata for each image
for _, data := range imageData {
logger.Log.Infof("Processing image %s", data.Source)
err := populateMetadata(data)
if err != nil {
return fmt.Errorf("failed to populate metadata for %s:\n%w", data.Source, err)
Expand Down Expand Up @@ -191,96 +211,118 @@ func sha384sum(path string) (string, error) {
return fmt.Sprintf("%x", sha384.Sum(nil)), nil
}

func populateMetadata(data ImageBuildData) error {
stat, err := os.Stat(data.Source)
func populateImageFile(source string, imageFile *ImageFile) error {
stat, err := os.Stat(source)
if err != nil {
return fmt.Errorf("filed to stat %s:\n%w", data.Source, err)
return fmt.Errorf("failed to stat %s:\n%w", source, err)
}
if stat.IsDir() {
return fmt.Errorf("%s is a directory", data.Source)
return fmt.Errorf("%s is a directory", source)
}
data.Metadata.Image.CompressedSize = uint64(stat.Size())
imageFile.CompressedSize = uint64(stat.Size())

// Calculate the sha384 of the image
sha384, err := sha384sum(data.Source)
sha384, err := sha384sum(source)
if err != nil {
return fmt.Errorf("failed to calculate sha384 of %s:\n%w", data.Source, err)
return fmt.Errorf("failed to calculate sha384 of %s:\n%w", source, err)
}
data.Metadata.Image.Sha384 = sha384
imageFile.Sha384 = sha384

err = populateVerityMetadata(data.VeritySource, data.Metadata.Verity)
if err != nil {
return fmt.Errorf("failed to populate verity metadata: %w", err)
}
return nil
}

func populateVerityMetadata(source *string, verity *Verity) error {
if source == nil && verity == nil {
return nil
func populateMetadata(data ImageBuildData) error {
if err := populateImageFile(data.Source, &data.Metadata.Image); err != nil {
return fmt.Errorf("failed to populate metadata: %w", err)
}

if source == nil || verity == nil {
return fmt.Errorf("source and verity file must be both defined or both undefined")
if err := populateVerityMetadata(data.VeritySource, data.Metadata.Verity); err != nil {
return fmt.Errorf("failed to populate verity metadata: %w", err)
}

verityFile := &verity.Image
return nil
}

verityStat, err := os.Stat(*source)
if err != nil {
return fmt.Errorf("failed to stat verity source: %w", err)
func populateVerityMetadata(source *string, verityList []*Verity) error {
if source == nil && len(verityList) == 0 {
return nil
}

verityFile.CompressedSize = uint64(verityStat.Size())

veritySha384, err := sha384sum(*source)
if err != nil {
return fmt.Errorf("failed to calculate sha384 of verity source: %w", err)
if source == nil || len(verityList) == 0 {
return fmt.Errorf("source and verity list must be both defined or both undefined")
}

verityFile.Sha384 = veritySha384
for _, verity := range verityList {
if verity == nil {
continue
}

verityFile.UncompressedSize = uint64(verityStat.Size())
if err := populateImageFile(*source, &verity.Image); err != nil {
return fmt.Errorf("failed to populate verity image metadata: %w", err)
}
}

return nil
}

func buildCosiData(allPartitionMetadata []outputPartitionMetadata, rootPartUUID string, roothash string) ([]outputPartitionMetadata, error) {
var cosiMetadata []outputPartitionMetadata
var roothashFilepath string
func buildCosiData(partitions []outputPartitionMetadata, verityMetadata []verityDeviceMetadata) ([]cosiPartitionMetadata, error) {
var cosiMetadata []cosiPartitionMetadata

// Identify the roothash partition file path
for _, metadata := range allPartitionMetadata {
if metadata.FileSystemType == "DM_verity_hash" {
roothashFilepath = metadata.PartitionFilename
break
}
// Pre-compute a map for quick lookup of partition metadata by UUID
type PartitionMetadata struct {
Filename string
UncompressedSize uint64
}

if roothash != "" && roothashFilepath == "" {
return nil, fmt.Errorf("roothash is defined but no roothash partition was found")
partUuidToMetadata := make(map[string]PartitionMetadata)
for _, partition := range partitions {
partUuidToMetadata[partition.PartUuid] = PartitionMetadata{
Filename: partition.PartitionFilename,
UncompressedSize: partition.UncompressedSize,
}
}
for _, partition := range partitions {
// Skip roothash partitions
if partition.FileSystemType == "DM_verity_hash" {
continue
}

cosiPartition := cosiPartitionMetadata{
PartitionNum: partition.PartitionNum,
PartitionFilename: partition.PartitionFilename,
PartLabel: partition.PartLabel,
FileSystemType: partition.FileSystemType,
PartitionTypeUuid: partition.PartitionTypeUuid,
Uuid: partition.Uuid,
PartUuid: partition.PartUuid,
Mountpoint: partition.Mountpoint,
UncompressedSize: partition.UncompressedSize,
Verity: nil,
}

for _, verity := range verityMetadata {
if partition.PartUuid == verity.dataPartUuid {
metadata, exists := partUuidToMetadata[verity.hashPartUuid]
if !exists || metadata.Filename == "" {
return nil, fmt.Errorf("missing metadata for hash partition UUID: %s", verity.hashPartUuid)
}

for _, metadata := range allPartitionMetadata {
if metadata.PartUuid == rootPartUUID {
if roothash != "" && roothashFilepath != "" {
metadata.Verity = &Verity{
Roothash: roothash,
// Create Verity entry
verityEntry := &Verity{
Hash: verity.hash,
Image: ImageFile{
Path: roothashFilepath,
Path: metadata.Filename,
UncompressedSize: metadata.UncompressedSize,
},
}
} else {
metadata.Verity = nil

if cosiPartition.Verity == nil {
cosiPartition.Verity = []*Verity{}
}
cosiPartition.Verity = append(cosiPartition.Verity, verityEntry)
}
} else if metadata.FileSystemType == "DM_verity_hash" {
// Skip the roothash partition
continue
} else {
metadata.Verity = nil
}

cosiMetadata = append(cosiMetadata, metadata)
cosiMetadata = append(cosiMetadata, cosiPartition)
}

return cosiMetadata, nil
Expand Down
17 changes: 8 additions & 9 deletions toolkit/tools/pkg/imagecustomizerlib/cosimetadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@ type MetadataJson struct {
}

type Image struct {
Image ImageFile `json:"image"`
MountPoint string `json:"mountPoint"`
FsType string `json:"fsType"`
FsUuid string `json:"fsUuid"`
PartType string `json:"partType"`
Verity *Verity `json:"verity"`
UncompressedSize uint64 `json:"uncompressedSize"`
Image ImageFile `json:"image"`
MountPoint string `json:"mountPoint"`
FsType string `json:"fsType"`
FsUuid string `json:"fsUuid"`
PartType string `json:"partType"`
Verity []*Verity `json:"verity"`
}

type Verity struct {
Image ImageFile `json:"image"`
Roothash string `json:"roothash"`
Image ImageFile `json:"image"`
Hash string `json:"hash"`
}

type ImageFile struct {
Expand Down
19 changes: 9 additions & 10 deletions toolkit/tools/pkg/imagecustomizerlib/extractpartitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,15 @@ import (
)

type outputPartitionMetadata struct {
PartitionNum int `json:"partitionnum"` // Example: 1
PartitionFilename string `json:"filename"` // Example: image_1.raw.zst
PartLabel string `json:"partlabel"` // Example: boot
FileSystemType string `json:"fstype"` // Example: vfat
PartitionTypeUuid string `json:"parttype"` // Example: c12a7328-f81f-11d2-ba4b-00a0c93ec93b
Uuid string `json:"uuid"` // Example: 4BD9-3A78
PartUuid string `json:"partuuid"` // Example: 7b1367a6-5845-43f2-99b1-a742d873f590
Mountpoint string `json:"mountpoint"` // Example: /mnt/os/boot
UncompressedSize uint64 `json:"uncompressedsize"` // Example: 104857600
Verity *Verity `json:"verity,omitempty"`
PartitionNum int `json:"partitionnum"` // Example: 1
PartitionFilename string `json:"filename"` // Example: image_1.raw.zst
PartLabel string `json:"partlabel"` // Example: boot
FileSystemType string `json:"fstype"` // Example: vfat
PartitionTypeUuid string `json:"parttype"` // Example: c12a7328-f81f-11d2-ba4b-00a0c93ec93b
Uuid string `json:"uuid"` // Example: 4BD9-3A78
PartUuid string `json:"partuuid"` // Example: 7b1367a6-5845-43f2-99b1-a742d873f590
Mountpoint string `json:"mountpoint"` // Example: /mnt/os/boot
UncompressedSize uint64 `json:"uncompressedsize"` // Example: 104857600
}

const (
Expand Down
Loading

0 comments on commit e311be9

Please sign in to comment.