diff --git a/pkg/util/metrics/system/cmdline_args.go b/pkg/util/metrics/system/cmdline_args.go new file mode 100644 index 00000000..792261b3 --- /dev/null +++ b/pkg/util/metrics/system/cmdline_args.go @@ -0,0 +1,88 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package system + +import ( + "encoding/json" + "fmt" + "strings" +) + +var cmdlineFilePath = "/proc/cmdline" + +type CmdlineArg struct { + Key string `json:"key"` + Value string `json:"value"` +} + +func (d CmdlineArg) String() string { + s, _ := json.Marshal(d) + return string(s) +} + +// As per the documentation kernel command line parameters can also be specified +// within double quotes and are separated with spaces. +// https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html#the-kernel-s-command-line-parameters +var withinQuotes = false + +func splitAfterSpace(inputChar rune) bool { + // words with space within double quotes cannot be split. + // so we track the start quote and when we find the + // end quote, then we reiterate. + if inputChar == '"' { + withinQuotes = !withinQuotes + return false + } + //ignore spaces when it is within quotes. + if withinQuotes { + return false + } + return inputChar == ' ' +} + +// CmdlineArgs returns all the kernel cmdline. It is read from cat /proc/cmdline. +func CmdlineArgs() ([]CmdlineArg, error) { + lines, err := ReadFileIntoLines(cmdlineFilePath) + if err != nil { + return nil, fmt.Errorf("error reading the file %s, %v", cmdlineFilePath, err) + } + if len(lines) < 1 { + return nil, fmt.Errorf("no lines are retured") + } + cmdlineArgs := strings.FieldsFunc(lines[0], splitAfterSpace) + var result = make([]CmdlineArg, 0, len(cmdlineArgs)) + // for commandline only one line is returned. + for _, words := range cmdlineArgs { + // Ignore the keys that start with double quotes + if strings.Index(words, "\"") == 0 { + continue + } + tokens := strings.Split(words, "=") + if len(tokens) < 2 { + var stats = CmdlineArg{ + Key: tokens[0], + } + result = append(result, stats) + } else { + //remove quotes in the values + trimmedValue := strings.Trim(tokens[1], "\"'") + var stats = CmdlineArg{ + Key: tokens[0], + Value: trimmedValue, + } + result = append(result, stats) + } + } + return result, nil +} diff --git a/pkg/util/metrics/system/cmdline_args_test.go b/pkg/util/metrics/system/cmdline_args_test.go new file mode 100644 index 00000000..6470ffd6 --- /dev/null +++ b/pkg/util/metrics/system/cmdline_args_test.go @@ -0,0 +1,101 @@ +package system + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCmdlineStats(t *testing.T) { + testcases := []struct { + name string + fakeCmdlineFilePath string + expectedCmdlineArgs []CmdlineArg + unExpectedCmdlineArgs []CmdlineArg + }{ + { + name: "default_cos", + fakeCmdlineFilePath: "testdata/cmdline_args_key_cos.txt", + expectedCmdlineArgs: []CmdlineArg{ + { + Key: "console", + Value: "ttyS0", + }, + { + Key: "boot", + Value: "local", + }, + { + Key: "cros_efi", + }, + }, + unExpectedCmdlineArgs: []CmdlineArg{ + { + Key: "hashstart", + Value: "4077568", + }, + { + Key: "vroot", + }, + }, + }, + { + name: "sample", + fakeCmdlineFilePath: "testdata/cmdline_args_sample.txt", + expectedCmdlineArgs: []CmdlineArg{ + { + Key: "key1", + Value: "value1", + }, + { + Key: "key3", + Value: "value2 value3", + }, + { + Key: "key2", + }, + }, + unExpectedCmdlineArgs: []CmdlineArg{ + { + Key: "value2", + Value: "value3", + }, + { + Key: "value3", + }, + }, + }, + } + for _, test := range testcases { + t.Run(test.name, func(t *testing.T) { + originalCmdlineFilePath := cmdlineFilePath + defer func() { + cmdlineFilePath = originalCmdlineFilePath + }() + + cmdlineFilePath = test.fakeCmdlineFilePath + cmdlineArgs, err := CmdlineArgs() + if err != nil { + t.Errorf("Unexpected error retrieving cmdlineArgs: %v\nCmdlineArgsFilePath: %s\n", err, cmdlineFilePath) + } + for _, expectedCmdlineArg := range test.expectedCmdlineArgs { + assert.Contains(t, cmdlineArgs, expectedCmdlineArg, "Failed to find cmdlineArgs: %v\n", expectedCmdlineArg) + } + for _, unExpectedCmdlineArg := range test.unExpectedCmdlineArgs { + assert.NotContains(t, cmdlineArgs, unExpectedCmdlineArg, "Unpected expected cmdlinearg found: %v\n", unExpectedCmdlineArg) + } + }) + } +} + +func TestCmdlineStats_String(t *testing.T) { + v := CmdlineArg{ + Key: "test", + Value: "test", + } + e := `{"key":"test","value":"test"}` + assert.Equal(t, + e, fmt.Sprintf("%v", v), "CmdlineArg string is invalid: %v", v) + +} diff --git a/pkg/util/metrics/system/common.go b/pkg/util/metrics/system/common.go index 77a6125d..6676c694 100644 --- a/pkg/util/metrics/system/common.go +++ b/pkg/util/metrics/system/common.go @@ -18,7 +18,7 @@ import ( "os" ) -// ReadFile reads contents from a file and returns lines. +// ReadFileIntoLines reads contents from a file and returns lines. func ReadFileIntoLines(filename string) ([]string, error) { file, err := os.Open(filename) if err != nil { diff --git a/pkg/util/metrics/system/testdata/cmdline_args_key_cos.txt b/pkg/util/metrics/system/testdata/cmdline_args_key_cos.txt new file mode 100644 index 00000000..c3a5cae0 --- /dev/null +++ b/pkg/util/metrics/system/testdata/cmdline_args_key_cos.txt @@ -0,0 +1 @@ +BOOT_IMAGE=/syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 loadpin.exclude=kernel-module modules-load=loadpin_trigger module.sig_enforce=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi "dm=1 vroot none ro 1,0 verity hashstart=4077568" \ No newline at end of file diff --git a/pkg/util/metrics/system/testdata/cmdline_args_sample.txt b/pkg/util/metrics/system/testdata/cmdline_args_sample.txt new file mode 100644 index 00000000..6108afea --- /dev/null +++ b/pkg/util/metrics/system/testdata/cmdline_args_sample.txt @@ -0,0 +1 @@ +key1=value1 key2 key3="value2 value3" \ No newline at end of file