mirror of
https://github.com/rancher/os.git
synced 2025-06-24 14:01:34 +00:00
Fixes #2755. Allows for quoted arguments with spaces in the kernel cmdline to be parsed properly. An example command line: ``` $ cat /proc/cmdline earlyprintk=serial console=ttyS0 rancher.autologin=ttyS0 rancher.defaults.hostname=ros-vm1 rancher.defaults.network.dns.nameservers=[192.168.64.1] rancher.network.interfaces.eth0.dhcp=true rancher.network.interfaces.eth1.dhcp=false rancher.network.interfaces.eth1.address=192.168.99.11/24 rancher.state.dev=LABEL=RANCHER_STATE rancher.state.autoformat=[/dev/vda] rancher.state.formatzero cc.ssh_authorized_keys=['ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOz8mD0tRrNsHBLHD5jVgmXO26JA7eKFZrj4Ic9KR2y3qXlxU9JCYYn/qDyTCmExt8Rw6SaU/BvgU7WT3Bjsi6c=','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJj5mkpBHBBAW5XClcB5aFTWph+VCL7I0W8gm93AT5w4','ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKfb0O2qXgIgrtD5Mj7fBYdg4jMrT7wetBbkG2e4maDsRR3AtSYjEB3NeEifM8gdvIf0gYs1BNB/Ar76agaQGeqW+Ewb2LWdypr4Ipw09yWCrC9ttVbCnHuzVLYjML0CNgpjIRC+FC5r1X1gm2LufRN4orZ1NQvNhRRWJVT37vRtHo79TecK0DKQmy87Zpj3cNiI/5iObnTk56pZWpIAEiC5hEVkcVxmdkLJs3YonWVZzmK/Y8uvFtF+GhA6Jcpc38zDQHKsOjFWvj3qbWtVEQteNDxsM2pNeXY5wdrhRn4YSdKme9Cm7CdAogIdAdPtqPIfq/jY0QczS12qFZH7zt'] ``` Results in: ``` $ sudo ros config export rancher: defaults: hostname: ros-vm1 network: dns: nameservers: - 192.168.64.1 environment: EXTRA_CMDLINE: /init AAAAC3NzaC1lZDI1NTE5AAAAIJj5mkpBHBBAW5XClcB5aFTWph+VCL7I0W8gm93AT5w4','ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKfb0O2qXgIgrtD5Mj7fBYdg4jMrT7wetBbkG2e4maDsRR3AtSYjEB3NeEifM8gdvIf0gYs1BNB/Ar76agaQGeqW+Ewb2LWdypr4Ipw09yWCrC9ttVbCnHuzVLYjML0CNgpjIRC+FC5r1X1gm2LufRN4orZ1NQvNhRRWJVT37vRtHo79TecK0DKQmy87Zpj3cNiI/5iObnTk56pZWpIAEiC5hEVkcVxmdkLJs3YonWVZzmK/Y8uvFtF+GhA6Jcpc38zDQHKsOjFWvj3qbWtVEQteNDxsM2pNeXY5wdrhRn4YSdKme9Cm7CdAogIdAdPtqPIfq/jY0QczS12qFZH7zt'] network: interfaces: eth0: dhcp: true eth1: address: 192.168.99.11/24 dhcp: false state: autoformat: - /dev/vda dev: LABEL=RANCHER_STATE formatzero: true ssh_authorized_keys: - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOz8mD0tRrNsHBLHD5jVgmXO26JA7eKFZrj4Ic9KR2y3qXlxU9JCYYn/qDyTCmExt8Rw6SaU/BvgU7WT3Bjsi6c= - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJj5mkpBHBBAW5XClcB5aFTWph+VCL7I0W8gm93AT5w4 - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKfb0O2qXgIgrtD5Mj7fBYdg4jMrT7wetBbkG2e4maDsRR3AtSYjEB3NeEifM8gdvIf0gYs1BNB/Ar76agaQGeqW+Ewb2LWdypr4Ipw09yWCrC9ttVbCnHuzVLYjML0CNgpjIRC+FC5r1X1gm2LufRN4orZ1NQvNhRRWJVT37vRtHo79TecK0DKQmy87Zpj3cNiI/5iObnTk56pZWpIAEiC5hEVkcVxmdkLJs3YonWVZzmK/Y8uvFtF+GhA6Jcpc38zDQHKsOjFWvj3qbWtVEQteNDxsM2pNeXY5wdrhRn4YSdKme9Cm7CdAogIdAdPtqPIfq/jY0QczS12qFZH7zt ```
197 lines
4.2 KiB
Go
197 lines
4.2 KiB
Go
package cmdline
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/rancher/os/pkg/util"
|
|
|
|
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
|
)
|
|
|
|
func Read(parseAll bool) (m map[interface{}]interface{}, err error) {
|
|
cmdLine, err := ioutil.ReadFile("/proc/cmdline")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(cmdLine) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
cmdLineObj := Parse(strings.TrimSpace(util.UnescapeKernelParams(string(cmdLine))), parseAll)
|
|
|
|
return cmdLineObj, nil
|
|
}
|
|
|
|
func GetCmdline(key string) interface{} {
|
|
parseAll := true
|
|
if strings.HasPrefix(key, "cc.") || strings.HasPrefix(key, "rancher.") {
|
|
// the normal case
|
|
parseAll = false
|
|
}
|
|
cmdline, _ := Read(parseAll)
|
|
v, _ := GetOrSetVal(key, cmdline, nil)
|
|
return v
|
|
}
|
|
|
|
func GetOrSetVal(args string, data map[interface{}]interface{}, value interface{}) (interface{}, map[interface{}]interface{}) {
|
|
parts := strings.Split(args, ".")
|
|
|
|
tData := data
|
|
if value != nil {
|
|
tData = util.MapCopy(data)
|
|
}
|
|
t := tData
|
|
for i, part := range parts {
|
|
val, ok := t[part]
|
|
last := i+1 == len(parts)
|
|
|
|
// Reached end, set the value
|
|
if last && value != nil {
|
|
if s, ok := value.(string); ok {
|
|
value = UnmarshalOrReturnString(s)
|
|
}
|
|
|
|
t[part] = value
|
|
return value, tData
|
|
}
|
|
|
|
// Missing intermediate key, create key
|
|
if !last && value != nil && !ok {
|
|
newData := map[interface{}]interface{}{}
|
|
t[part] = newData
|
|
t = newData
|
|
continue
|
|
}
|
|
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
if last {
|
|
return val, tData
|
|
}
|
|
|
|
newData, ok := val.(map[interface{}]interface{})
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
t = newData
|
|
}
|
|
|
|
return "", tData
|
|
}
|
|
|
|
// Replace newlines, colons, and question marks with random strings
|
|
// This is done to avoid YAML treating these as special characters
|
|
var (
|
|
newlineMagicString = "9XsJcx6dR5EERYCC"
|
|
colonMagicString = "V0Rc21pIVknMm2rr"
|
|
questionMarkMagicString = "FoPL6JLMAaJqKMJT"
|
|
)
|
|
|
|
func reverseReplacement(result interface{}) interface{} {
|
|
switch val := result.(type) {
|
|
case map[interface{}]interface{}:
|
|
for k, v := range val {
|
|
val[k] = reverseReplacement(v)
|
|
}
|
|
return val
|
|
case []interface{}:
|
|
for i, item := range val {
|
|
val[i] = reverseReplacement(item)
|
|
}
|
|
return val
|
|
case string:
|
|
val = strings.Replace(val, newlineMagicString, "\n", -1)
|
|
val = strings.Replace(val, colonMagicString, ":", -1)
|
|
val = strings.Replace(val, questionMarkMagicString, "?", -1)
|
|
return val
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func UnmarshalOrReturnString(value string) (result interface{}) {
|
|
value = strings.Replace(value, "\n", newlineMagicString, -1)
|
|
value = strings.Replace(value, ":", colonMagicString, -1)
|
|
value = strings.Replace(value, "?", questionMarkMagicString, -1)
|
|
if err := yaml.Unmarshal([]byte(value), &result); err != nil {
|
|
result = value
|
|
}
|
|
result = reverseReplacement(result)
|
|
return
|
|
}
|
|
|
|
//splitCmdLine splits on spaces except when a space is within a quoted or bracketed string.
|
|
func splitCmdLine(cmdLine string) []string {
|
|
lastRune := rune(0)
|
|
f := func(c rune) bool {
|
|
switch {
|
|
case c == lastRune:
|
|
lastRune = rune(0)
|
|
return false
|
|
case lastRune != rune(0):
|
|
return false
|
|
case unicode.In(c, unicode.Quotation_Mark):
|
|
lastRune = c
|
|
return false
|
|
case c == '[':
|
|
lastRune = ']'
|
|
return false
|
|
default:
|
|
return c == ' '
|
|
}
|
|
}
|
|
return strings.FieldsFunc(cmdLine, f)
|
|
}
|
|
|
|
func Parse(cmdLine string, parseAll bool) map[interface{}]interface{} {
|
|
result := map[interface{}]interface{}{}
|
|
|
|
outer:
|
|
for _, part := range splitCmdLine(cmdLine) {
|
|
if strings.HasPrefix(part, "cc.") {
|
|
part = part[3:]
|
|
} else if !strings.HasPrefix(part, "rancher.") {
|
|
if !parseAll {
|
|
continue
|
|
}
|
|
}
|
|
|
|
var value string
|
|
kv := strings.SplitN(part, "=", 2)
|
|
|
|
if len(kv) == 1 {
|
|
value = "true"
|
|
} else {
|
|
value = kv[1]
|
|
}
|
|
|
|
current := result
|
|
keys := strings.Split(kv[0], ".")
|
|
for i, key := range keys {
|
|
if i == len(keys)-1 {
|
|
current[key] = UnmarshalOrReturnString(value)
|
|
} else {
|
|
if obj, ok := current[key]; ok {
|
|
if newCurrent, ok := obj.(map[interface{}]interface{}); ok {
|
|
current = newCurrent
|
|
} else {
|
|
continue outer
|
|
}
|
|
} else {
|
|
newCurrent := make(map[interface{}]interface{})
|
|
current[key] = newCurrent
|
|
current = newCurrent
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|