github.com/stulluk/snapd@v0.0.0-20210611110309-f6d5d5bd24b0/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  }