github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/osutil/kcmdline.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package osutil 21 22 import ( 23 "bytes" 24 "fmt" 25 "io/ioutil" 26 "strings" 27 ) 28 29 var ( 30 procCmdline = "/proc/cmdline" 31 ) 32 33 // MockProcCmdline overrides the path to /proc/cmdline. For use in tests. 34 func MockProcCmdline(newPath string) (restore func()) { 35 MustBeTestBinary("mocking can only be done from tests") 36 oldProcCmdline := procCmdline 37 procCmdline = newPath 38 return func() { 39 procCmdline = oldProcCmdline 40 } 41 } 42 43 // KernelCommandLineSplit tries to split the string comprising full or a part 44 // of a kernel command line into a list of individual arguments. Returns an 45 // error when the input string is incorrectly formatted. 46 // 47 // See https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html for details. 48 func KernelCommandLineSplit(s string) (out []string, err error) { 49 const ( 50 argNone int = iota // initial state 51 argName // looking at argument name 52 argAssign // looking at = 53 argValue // looking at unquoted value 54 argValueQuoteStart // looking at start of quoted value 55 argValueQuoted // looking at quoted value 56 argValueQuoteEnd // looking at end of quoted value 57 ) 58 var b bytes.Buffer 59 var rs = []rune(s) 60 var last = len(rs) - 1 61 var errUnexpectedQuote = fmt.Errorf("unexpected quoting") 62 var errUnbalancedQUote = fmt.Errorf("unbalanced quoting") 63 var errUnexpectedArgument = fmt.Errorf("unexpected argument") 64 var errUnexpectedAssignment = fmt.Errorf("unexpected assignment") 65 // arguments are: 66 // - arg 67 // - arg=value, where value can be any string, spaces are preserve when quoting ".." 68 var state = argNone 69 for idx, r := range rs { 70 maybeSplit := false 71 switch state { 72 case argNone: 73 switch r { 74 case '"': 75 return nil, errUnexpectedQuote 76 case '=': 77 return nil, errUnexpectedAssignment 78 case ' ': 79 maybeSplit = true 80 default: 81 state = argName 82 b.WriteRune(r) 83 } 84 case argName: 85 switch r { 86 case '"': 87 return nil, errUnexpectedQuote 88 case ' ': 89 maybeSplit = true 90 state = argNone 91 case '=': 92 state = argAssign 93 fallthrough 94 default: 95 b.WriteRune(r) 96 } 97 case argAssign: 98 switch r { 99 case '=': 100 return nil, errUnexpectedAssignment 101 case ' ': 102 // no value: arg= 103 maybeSplit = true 104 state = argNone 105 case '"': 106 // arg=".. 107 state = argValueQuoteStart 108 b.WriteRune(r) 109 default: 110 // arg=v.. 111 state = argValue 112 b.WriteRune(r) 113 } 114 case argValue: 115 switch r { 116 case '"': 117 // arg=foo" 118 return nil, errUnexpectedQuote 119 case ' ': 120 state = argNone 121 maybeSplit = true 122 default: 123 // arg=value... 124 b.WriteRune(r) 125 } 126 case argValueQuoteStart: 127 switch r { 128 case '"': 129 // closing quote: arg="" 130 state = argValueQuoteEnd 131 b.WriteRune(r) 132 default: 133 state = argValueQuoted 134 b.WriteRune(r) 135 } 136 case argValueQuoted: 137 switch r { 138 case '"': 139 // closing quote: arg="foo" 140 state = argValueQuoteEnd 141 fallthrough 142 default: 143 b.WriteRune(r) 144 } 145 case argValueQuoteEnd: 146 switch r { 147 case ' ': 148 maybeSplit = true 149 state = argNone 150 case '"': 151 // arg="foo"" 152 return nil, errUnexpectedQuote 153 case '=': 154 // arg="foo"= 155 return nil, errUnexpectedAssignment 156 default: 157 // arg="foo"bar 158 return nil, errUnexpectedArgument 159 } 160 } 161 if maybeSplit || idx == last { 162 // split now 163 if b.Len() != 0 { 164 out = append(out, b.String()) 165 b.Reset() 166 } 167 } 168 } 169 switch state { 170 case argValueQuoteStart, argValueQuoted: 171 // ended at arg=" or arg="foo 172 return nil, errUnbalancedQUote 173 } 174 return out, nil 175 } 176 177 // KernelCommandLineKeyValues returns a map of the specified keys to the values 178 // set for them in the kernel command line (eg. panic=-1). If the value is 179 // missing from the kernel command line or it has no value (eg. quiet), the key 180 // is omitted from the returned map. 181 func KernelCommandLineKeyValues(keys ...string) (map[string]string, error) { 182 cmdline, err := KernelCommandLine() 183 if err != nil { 184 return nil, err 185 } 186 params, err := KernelCommandLineSplit(cmdline) 187 if err != nil { 188 return nil, err 189 } 190 191 m := make(map[string]string, len(keys)) 192 193 for _, param := range params { 194 for _, key := range keys { 195 if strings.HasPrefix(param, fmt.Sprintf("%s=", key)) { 196 res := strings.SplitN(param, "=", 2) 197 // we have confirmed key= prefix, thus len(res) 198 // is always 2 199 m[key] = res[1] 200 break 201 } 202 } 203 } 204 return m, nil 205 } 206 207 // KernelCommandLine returns the command line reported by the running kernel. 208 func KernelCommandLine() (string, error) { 209 buf, err := ioutil.ReadFile(procCmdline) 210 if err != nil { 211 return "", err 212 } 213 return strings.TrimSpace(string(buf)), nil 214 }