From af41f5018f3653c8d56d925d3ec835ee58565a80 Mon Sep 17 00:00:00 2001 From: Saul Paredes Date: Mon, 25 Aug 2025 17:17:04 -0700 Subject: [PATCH] runtime: share initdata setup code Move setup code such that it can be used by other hypervisors. Signed-off-by: Saul Paredes --- src/runtime/virtcontainers/hypervisor.go | 95 ++++++++++++++++++++++++ src/runtime/virtcontainers/qemu.go | 93 +---------------------- 2 files changed, 96 insertions(+), 92 deletions(-) diff --git a/src/runtime/virtcontainers/hypervisor.go b/src/runtime/virtcontainers/hypervisor.go index 1440ac748c..c95c84fdfc 100644 --- a/src/runtime/virtcontainers/hypervisor.go +++ b/src/runtime/virtcontainers/hypervisor.go @@ -7,10 +7,14 @@ package virtcontainers import ( "bufio" + "bytes" + "compress/gzip" "context" + "encoding/binary" "fmt" "math" "os" + "path/filepath" "runtime" "strings" @@ -1184,3 +1188,94 @@ func KernelParamFields(s string) []string { return params } + +// prepareInitdataMount prepares the on-disk initdata image for a VM/sandbox. +// +// It reads the initdata payload from config.Initdata, creates a working directory +// at /run/kata-containers/shared/initdata/, builds the image file +// (data.img) via prepareInitdataImage, and sets config.InitdataImage to the +// resulting absolute path. +func prepareInitdataMount(logger *logrus.Entry, id string, config *HypervisorConfig) error { + if len(config.Initdata) == 0 { + logger.Info("No initdata provided. Skip prepare initdata device") + return nil + } + + logger.Info("Start to prepare initdata") + initdataWorkdir := filepath.Join("/run/kata-containers/shared/initdata", id) + initdataImagePath := filepath.Join(initdataWorkdir, "data.img") + + if err := os.MkdirAll(initdataWorkdir, 0o755); err != nil { + logger.WithField("initdata", "create initdata image path").WithError(err).Error("mkdir failed") + return err + } + + if err := prepareInitdataImage(config.Initdata, initdataImagePath); err != nil { + logger.WithField("initdata", "prepare initdata image").WithError(err).Error("prepare failed") + return err + } + + config.InitdataImage = initdataImagePath + return nil +} + +// prepareInitdataImage will create an image with a very simple layout +// +// There will be multiple sectors. The first 8 bytes are Magic number "initdata". +// Then a "length" field of 8 bytes follows (unsigned int64). +// Finally the gzipped initdata toml. The image will be padded to an +// integer multiple of the sector size for alignment. +// +// offset 0 8 16 +// 0 'i' 'n' 'i' 't' 'd' 'a' 't' 'a' | gzip length in le | +// 16 gzip(initdata toml) ... +// (end of the last sector) '\0' paddings +func prepareInitdataImage(initdata string, imagePath string) error { + SectorSize := 512 + var buf bytes.Buffer + gzipper := gzip.NewWriter(&buf) + defer gzipper.Close() + + gzipper.Write([]byte(initdata)) + err := gzipper.Close() + if err != nil { + return fmt.Errorf("failed to compress initdata: %v", err) + } + + compressedInitdata := buf.Bytes() + + compressedInitdataLength := len(compressedInitdata) + lengthBuffer := make([]byte, 8) + binary.LittleEndian.PutUint64(lengthBuffer, uint64(compressedInitdataLength)) + + paddingLength := (compressedInitdataLength+16+SectorSize-1)/SectorSize*SectorSize - (compressedInitdataLength + 16) + paddingBuffer := make([]byte, paddingLength) + + file, err := os.OpenFile(imagePath, os.O_CREATE|os.O_RDWR, 0640) + if err != nil { + return fmt.Errorf("failed to create initdata image: %v", err) + } + defer file.Close() + + _, err = file.Write([]byte("initdata")) + if err != nil { + return fmt.Errorf("failed to write magic number to initdata image: %v", err) + } + + _, err = file.Write(lengthBuffer) + if err != nil { + return fmt.Errorf("failed to write data length to initdata image: %v", err) + } + + _, err = file.Write([]byte(compressedInitdata)) + if err != nil { + return fmt.Errorf("failed to write compressed initdata to initdata image: %v", err) + } + + _, err = file.Write(paddingBuffer) + if err != nil { + return fmt.Errorf("failed to write compressed initdata to initdata image: %v", err) + } + + return nil +} diff --git a/src/runtime/virtcontainers/qemu.go b/src/runtime/virtcontainers/qemu.go index d89f34c624..02b6553e66 100644 --- a/src/runtime/virtcontainers/qemu.go +++ b/src/runtime/virtcontainers/qemu.go @@ -9,10 +9,7 @@ package virtcontainers import ( "bufio" - "bytes" - "compress/gzip" "context" - "encoding/binary" "encoding/hex" "encoding/json" "fmt" @@ -544,94 +541,6 @@ func (q *qemu) createVirtiofsDaemon(sharedPath string) (VirtiofsDaemon, error) { }, nil } -// prepareInitdataImage will create an image with a very simple layout -// -// There will be multiple sectors. The first 8 bytes are Magic number "initdata". -// Then a "length" field of 8 bytes follows (unsigned int64). -// Finally the gzipped initdata toml. The image will be padded to an -// integer multiple of the sector size for alignment. -// -// offset 0 8 16 -// 0 'i' 'n' 'i' 't' 'd' 'a' 't' 'a' | gzip length in le | -// 16 gzip(initdata toml) ... -// (end of the last sector) '\0' paddings -func prepareInitdataImage(initdata string, imagePath string) error { - SectorSize := 512 - var buf bytes.Buffer - gzipper := gzip.NewWriter(&buf) - defer gzipper.Close() - - gzipper.Write([]byte(initdata)) - err := gzipper.Close() - if err != nil { - return fmt.Errorf("failed to compress initdata: %v", err) - } - - compressedInitdata := buf.Bytes() - - compressedInitdataLength := len(compressedInitdata) - lengthBuffer := make([]byte, 8) - binary.LittleEndian.PutUint64(lengthBuffer, uint64(compressedInitdataLength)) - - paddingLength := (compressedInitdataLength+16+SectorSize-1)/SectorSize*SectorSize - (compressedInitdataLength + 16) - paddingBuffer := make([]byte, paddingLength) - - file, err := os.OpenFile(imagePath, os.O_CREATE|os.O_RDWR, 0640) - if err != nil { - return fmt.Errorf("failed to create initdata image: %v", err) - } - defer file.Close() - - _, err = file.Write([]byte("initdata")) - if err != nil { - return fmt.Errorf("failed to write magic number to initdata image: %v", err) - } - - _, err = file.Write(lengthBuffer) - if err != nil { - return fmt.Errorf("failed to write data length to initdata image: %v", err) - } - - _, err = file.Write([]byte(compressedInitdata)) - if err != nil { - return fmt.Errorf("failed to write compressed initdata to initdata image: %v", err) - } - - _, err = file.Write(paddingBuffer) - if err != nil { - return fmt.Errorf("failed to write compressed initdata to initdata image: %v", err) - } - - return nil -} - -func (q *qemu) prepareInitdataMount(config *HypervisorConfig) error { - if len(config.Initdata) == 0 { - q.Logger().Info("No initdata provided. Skip prepare initdata device") - return nil - } - - q.Logger().Info("Start to prepare initdata") - initdataWorkdir := filepath.Join("/run/kata-containers/shared/initdata", q.id) - initdataImagePath := filepath.Join(initdataWorkdir, "data.img") - - err := os.MkdirAll(initdataWorkdir, 0755) - if err != nil { - q.Logger().WithField("initdata", "create initdata image path").WithError(err) - return err - } - - err = prepareInitdataImage(config.Initdata, initdataImagePath) - if err != nil { - q.Logger().WithField("initdata", "prepare initdata image").WithError(err) - return err - } - - config.InitdataImage = initdataImagePath - - return nil -} - // CreateVM is the Hypervisor VM creation implementation for govmmQemu. // This function is complex and there's not much to be done about it, unfortunately. // @@ -647,7 +556,7 @@ func (q *qemu) CreateVM(ctx context.Context, id string, network Network, hypervi return err } - if err := q.prepareInitdataMount(hypervisorConfig); err != nil { + if err := prepareInitdataMount(q.Logger(), q.id, hypervisorConfig); err != nil { return err }