github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/cmdline/cmdline.go (about)

     1  // Copyright 2018 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package cmdline is parser for kernel command-line args from /proc/cmdline.
     6  //
     7  // It's conformant with
     8  // https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html,
     9  // though making 'var_name' and 'var-name' equivalent may need to be done
    10  // separately.
    11  package cmdline
    12  
    13  import (
    14  	"io"
    15  	"strings"
    16  	"unicode"
    17  
    18  	"github.com/mvdan/u-root-coreutils/pkg/shlex"
    19  )
    20  
    21  // CmdLine lets people view the raw & parsed /proc/cmdline in one place
    22  type CmdLine struct {
    23  	Raw   string
    24  	AsMap map[string]string
    25  	Err   error
    26  }
    27  
    28  // NewCmdLine returns a populated CmdLine struct
    29  func NewCmdLine() *CmdLine {
    30  	return getCmdLine()
    31  }
    32  
    33  // FullCmdLine returns the full, raw cmdline string
    34  func FullCmdLine() string {
    35  	return getCmdLine().Raw
    36  }
    37  
    38  // parse returns the current command line, trimmed
    39  func parse(cmdlineReader io.Reader) *CmdLine {
    40  	var line = &CmdLine{}
    41  	raw, err := io.ReadAll(cmdlineReader)
    42  	line.Err = err
    43  	// This works because string(nil) is ""
    44  	line.Raw = strings.TrimRight(string(raw), "\n")
    45  	line.AsMap = parseToMap(line.Raw)
    46  	return line
    47  }
    48  
    49  func doParse(input string, handler func(flag, key, canonicalKey, value, trimmedValue string)) {
    50  	lastQuote := rune(0)
    51  	quotedFieldsCheck := func(c rune) bool {
    52  		switch {
    53  		case c == lastQuote:
    54  			lastQuote = rune(0)
    55  			return false
    56  		case lastQuote != rune(0):
    57  			return false
    58  		case unicode.In(c, unicode.Quotation_Mark):
    59  			lastQuote = c
    60  			return false
    61  		default:
    62  			return unicode.IsSpace(c)
    63  		}
    64  	}
    65  
    66  	for _, flag := range strings.FieldsFunc(string(input), quotedFieldsCheck) {
    67  		// kernel variables must allow '-' and '_' to be equivalent in variable
    68  		// names. We will replace dashes with underscores for processing.
    69  
    70  		// Split the flag into a key and value, setting value="1" if none
    71  		split := strings.Index(flag, "=")
    72  
    73  		if len(flag) == 0 {
    74  			continue
    75  		}
    76  		var key, value string
    77  		if split == -1 {
    78  			key = flag
    79  			value = "1"
    80  		} else {
    81  			key = flag[:split]
    82  			value = flag[split+1:]
    83  		}
    84  		canonicalKey := strings.Replace(key, "-", "_", -1)
    85  		trimmedValue := strings.Trim(value, "\"'")
    86  
    87  		// Call the user handler
    88  		handler(flag, key, canonicalKey, value, trimmedValue)
    89  	}
    90  }
    91  
    92  // parseToMap turns a space-separated kernel commandline into a map
    93  func parseToMap(input string) map[string]string {
    94  	flagMap := make(map[string]string)
    95  	doParse(input, func(flag, key, canonicalKey, value, trimmedValue string) {
    96  		// We store the value twice, once with dash, once with underscores
    97  		// Just in case people check with the wrong method
    98  		flagMap[canonicalKey] = trimmedValue
    99  		flagMap[key] = trimmedValue
   100  	})
   101  
   102  	return flagMap
   103  }
   104  
   105  // ContainsFlag verifies that the kernel cmdline has a flag set
   106  func (c *CmdLine) ContainsFlag(flag string) bool {
   107  	_, present := c.Flag(flag)
   108  	return present
   109  }
   110  
   111  // ContainsFlag verifies that the kernel cmdline has a flag set
   112  func ContainsFlag(flag string) bool {
   113  	return getCmdLine().ContainsFlag(flag)
   114  }
   115  
   116  // Flag returns the value of a flag, and whether it was set
   117  func (c *CmdLine) Flag(flag string) (string, bool) {
   118  	canonicalFlag := strings.Replace(flag, "-", "_", -1)
   119  	value, present := c.AsMap[canonicalFlag]
   120  	return value, present
   121  }
   122  
   123  // Flag returns the value of a flag, and whether it was set
   124  func Flag(flag string) (string, bool) {
   125  	return getCmdLine().Flag(flag)
   126  }
   127  
   128  // getFlagMap gets specified flags as a map
   129  func getFlagMap(flagName string) map[string]string {
   130  	return parseToMap(flagName)
   131  }
   132  
   133  // GetInitFlagMap gets the uroot init flags as a map
   134  func (c *CmdLine) GetInitFlagMap() map[string]string {
   135  	initflags, _ := c.Flag("uroot.initflags")
   136  	return getFlagMap(initflags)
   137  }
   138  
   139  // GetInitFlagMap gets the uroot init flags as a map
   140  func GetInitFlagMap() map[string]string {
   141  	return getCmdLine().GetInitFlagMap()
   142  }
   143  
   144  // GetUinitArgs gets the uinit argvs.
   145  func (c *CmdLine) GetUinitArgs() []string {
   146  	uinitargs, _ := getCmdLine().Flag("uroot.uinitargs")
   147  	return shlex.Argv(uinitargs)
   148  }
   149  
   150  // GetUinitArgs gets the uinit argvs.
   151  func GetUinitArgs() []string {
   152  	return getCmdLine().GetUinitArgs()
   153  }
   154  
   155  // FlagsForModule gets all flags for a designated module
   156  // and returns them as a space-seperated string designed to be passed to insmod
   157  // Note that similarly to flags, module names with - and _ are treated the same.
   158  func (c *CmdLine) FlagsForModule(name string) string {
   159  	var ret string
   160  	flagsAdded := make(map[string]bool) // Ensures duplicate flags aren't both added
   161  	// Module flags come as moduleName.flag in /proc/cmdline
   162  	prefix := strings.Replace(name, "-", "_", -1) + "."
   163  	for flag, val := range c.AsMap {
   164  		canonicalFlag := strings.Replace(flag, "-", "_", -1)
   165  		if !flagsAdded[canonicalFlag] && strings.HasPrefix(canonicalFlag, prefix) {
   166  			flagsAdded[canonicalFlag] = true
   167  			// They are passed to insmod space seperated as flag=val
   168  			ret += strings.TrimPrefix(canonicalFlag, prefix) + "=" + val + " "
   169  		}
   170  	}
   171  	return ret
   172  }
   173  
   174  // FlagsForModule gets all flags for a designated module
   175  // and returns them as a space-seperated string designed to be passed to insmod
   176  // Note that similarly to flags, module names with - and _ are treated the same.
   177  func FlagsForModule(name string) string {
   178  	return getCmdLine().FlagsForModule(name)
   179  }