Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Preserve kernel argument ordering #487

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 61 additions & 22 deletions kernelargs.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,46 +13,85 @@

package firecracker

import "strings"
import (
"fmt"
"sort"
"strings"
)

// kernelArgs serializes+deserializes kernel boot parameters from/into a map.
// kernelArg represents a key and optional value pair for passing an argument
// into the kernel. Additionally, it also saves the position of the argument
// in the whole command line input. This is important because the kernel stops reading
// everything after `--` and passes these keys into the init process
type kernelArg struct {
position uint
key string
value *string
}

func (karg kernelArg) String() string {
if karg.value != nil {
return fmt.Sprintf("%s=%s", karg.key, *karg.value)
}
return karg.key
}

// kernelArgs serializes + deserializes kernel boot parameters from/into a map.
// Kernel docs: https://www.kernel.org/doc/Documentation/admin-guide/kernel-parameters.txt
//
// "key=value" will result in map["key"] = &"value"
// "key=" will result in map["key"] = &""
// "key" will result in map["key"] = nil
type kernelArgs map[string]*string
// "key=value flag emptykey=" will be converted to
// map["key"] = { position: 0, key: "key", value: &"value" }
// map["flag"] = { position: 1, key: "flag", value: nil }
// map["emptykey"] = { position: 2, key: "emptykey", value: &"" }
type kernelArgs map[string]kernelArg

// serialize the kernelArgs back to a string that can be provided
// to the kernel
// Sorts the arguments by its position
// and serializes the map back into a single string
func (kargs kernelArgs) String() string {
var fields []string
for key, value := range kargs {
field := key
if value != nil {
field += "=" + *value
}
fields = append(fields, field)
sortedArgs := make([]kernelArg, 0)
for _, arg := range kargs {
sortedArgs = append(sortedArgs, arg)
}
sort.SliceStable(sortedArgs, func(i, j int) bool {
return sortedArgs[i].position < sortedArgs[j].position
})

args := make([]string, 0)
for _, arg := range sortedArgs {
args = append(args, arg.String())
}
return strings.Join(args, " ")
}

// Add a new kernel argument to the kernelArgs, also the position is saved
func (kargs kernelArgs) Add(key string, value *string) {
kargs[key] = kernelArg{
position: uint(len(kargs)),
key: key,
value: value,
}
return strings.Join(fields, " ")
}

// deserialize the provided string to a kernelArgs map
// Parses an input string and deserializes it into a map
// saving its position in the command line
func parseKernelArgs(rawString string) kernelArgs {
argMap := make(map[string]*string)
for _, kv := range strings.Fields(rawString) {
args := make(map[string]kernelArg)
for index, kv := range strings.Fields(rawString) {
// only split into up to 2 fields (before and after the first "=")
kvSplit := strings.SplitN(kv, "=", 2)

key := kvSplit[0]

var value *string
if len(kvSplit) == 2 {
value = &kvSplit[1]
}

argMap[key] = value
args[key] = kernelArg{
position: uint(index),
key: key,
value: value,
}
}

return argMap
return args
}
44 changes: 36 additions & 8 deletions kernelargs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,42 @@ func TestKernelArgsSerder(t *testing.T) {
booVal,
)

expectedParsedArgs := kernelArgs(map[string]*string{
"foo": &fooVal,
"doo": &dooVal,
"blah": nil,
"huh": &emptyVal,
"bleh": nil,
"duh": &emptyVal,
"boo": &booVal,
expectedParsedArgs := kernelArgs(map[string]kernelArg{
"foo": {
position: 0,
key: "foo",
value: &fooVal,
},
"blah": {
position: 1,
key: "blah",
value: nil,
},
"doo": {
position: 2,
key: "doo",
value: &dooVal,
},
"huh": {
position: 3,
key: "huh",
value: &emptyVal,
},
"bleh": {
position: 4,
key: "bleh",
value: nil,
},
"duh": {
position: 5,
key: "duh",
value: &emptyVal,
},
"boo": {
position: 6,
key: "boo",
value: &booVal,
},
})

actualParsedArgs := parseKernelArgs(argsString)
Expand Down
4 changes: 2 additions & 2 deletions machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ func (m *Machine) setupKernelArgs(ctx context.Context) error {
// Validation that we are not overriding an existing "ip=" setting happens in the network validation
if staticIPInterface := m.Cfg.NetworkInterfaces.staticIPInterface(); staticIPInterface != nil {
ipBootParam := staticIPInterface.StaticConfiguration.IPConfiguration.ipBootParam()
kernelArgs["ip"] = &ipBootParam
kernelArgs.Add("ip", &ipBootParam)
}

m.Cfg.KernelArgs = kernelArgs.String()
Expand Down Expand Up @@ -649,7 +649,7 @@ func (m *Machine) startVMM(ctx context.Context) error {
return nil
}

//StopVMM stops the current VMM.
// StopVMM stops the current VMM.
func (m *Machine) StopVMM() error {
return m.stopVMM()
}
Expand Down