github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/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  	"fmt"
    15  	"io"
    16  	"io/ioutil"
    17  	"log"
    18  	"os"
    19  	"strings"
    20  	"sync"
    21  	"unicode"
    22  
    23  	"github.com/u-root/u-root/pkg/shlex"
    24  )
    25  
    26  // CmdLine lets people view the raw & parsed /proc/cmdline in one place
    27  type CmdLine struct {
    28  	Raw   string
    29  	AsMap map[string]string
    30  	Err   error
    31  }
    32  
    33  var (
    34  	// procCmdLine package level static variable initialized once
    35  	once        sync.Once
    36  	procCmdLine CmdLine
    37  )
    38  
    39  func cmdLineOpener() {
    40  	cmdlineReader, err := os.Open("/proc/cmdline")
    41  	if err != nil {
    42  		errorMsg := fmt.Sprintf("Can't open /proc/cmdline: %v", err)
    43  		log.Print(errorMsg)
    44  		procCmdLine = CmdLine{Err: fmt.Errorf(errorMsg)}
    45  		return
    46  	}
    47  
    48  	procCmdLine = parse(cmdlineReader)
    49  	cmdlineReader.Close()
    50  }
    51  
    52  // NewCmdLine returns a populated CmdLine struct
    53  func NewCmdLine() CmdLine {
    54  	// We use cmdLineReader so tests can inject here
    55  	once.Do(cmdLineOpener)
    56  	return procCmdLine
    57  }
    58  
    59  // FullCmdLine returns the full, raw cmdline string
    60  func FullCmdLine() string {
    61  	once.Do(cmdLineOpener)
    62  	return procCmdLine.Raw
    63  }
    64  
    65  // parse returns the current command line, trimmed
    66  func parse(cmdlineReader io.Reader) CmdLine {
    67  	raw, err := ioutil.ReadAll(cmdlineReader)
    68  	line := CmdLine{}
    69  	if err != nil {
    70  		log.Printf("Can't read command line: %v", err)
    71  		line.Err = err
    72  		line.Raw = ""
    73  	} else {
    74  		line.Raw = strings.TrimRight(string(raw), "\n")
    75  		line.AsMap = parseToMap(line.Raw)
    76  	}
    77  	return line
    78  }
    79  
    80  //
    81  func doParse(input string, handler func(flag, key, canonicalKey, value, trimmedValue string)) {
    82  
    83  	lastQuote := rune(0)
    84  	quotedFieldsCheck := func(c rune) bool {
    85  		switch {
    86  		case c == lastQuote:
    87  			lastQuote = rune(0)
    88  			return false
    89  		case lastQuote != rune(0):
    90  			return false
    91  		case unicode.In(c, unicode.Quotation_Mark):
    92  			lastQuote = c
    93  			return false
    94  		default:
    95  			return unicode.IsSpace(c)
    96  		}
    97  	}
    98  
    99  	for _, flag := range strings.FieldsFunc(string(input), quotedFieldsCheck) {
   100  		// kernel variables must allow '-' and '_' to be equivalent in variable
   101  		// names. We will replace dashes with underscores for processing.
   102  
   103  		// Split the flag into a key and value, setting value="1" if none
   104  		split := strings.Index(flag, "=")
   105  
   106  		if len(flag) == 0 {
   107  			continue
   108  		}
   109  		var key, value string
   110  		if split == -1 {
   111  			key = flag
   112  			value = "1"
   113  		} else {
   114  			key = flag[:split]
   115  			value = flag[split+1:]
   116  		}
   117  		canonicalKey := strings.Replace(key, "-", "_", -1)
   118  		trimmedValue := strings.Trim(value, "\"'")
   119  
   120  		// Call the user handler
   121  		handler(flag, key, canonicalKey, value, trimmedValue)
   122  	}
   123  
   124  }
   125  
   126  // parseToMap turns a space-separated kernel commandline into a map
   127  func parseToMap(input string) map[string]string {
   128  
   129  	flagMap := make(map[string]string)
   130  	doParse(input, func(flag, key, canonicalKey, value, trimmedValue string) {
   131  		// We store the value twice, once with dash, once with underscores
   132  		// Just in case people check with the wrong method
   133  		flagMap[canonicalKey] = trimmedValue
   134  		flagMap[key] = trimmedValue
   135  	})
   136  
   137  	return flagMap
   138  }
   139  
   140  // ContainsFlag verifies that the kernel cmdline has a flag set
   141  func ContainsFlag(flag string) bool {
   142  	once.Do(cmdLineOpener)
   143  	_, present := Flag(flag)
   144  	return present
   145  }
   146  
   147  // Flag returns the a flag, and whether it was set
   148  func Flag(flag string) (string, bool) {
   149  	once.Do(cmdLineOpener)
   150  	canonicalFlag := strings.Replace(flag, "-", "_", -1)
   151  	value, present := procCmdLine.AsMap[canonicalFlag]
   152  	return value, present
   153  }
   154  
   155  // getFlagMap gets specified flags as a map
   156  func getFlagMap(flagName string) map[string]string {
   157  	return parseToMap(flagName)
   158  }
   159  
   160  // GetInitFlagMap gets the init flags as a map
   161  func GetInitFlagMap() map[string]string {
   162  	initflags, _ := Flag("uroot.initflags")
   163  	return getFlagMap(initflags)
   164  }
   165  
   166  // GetUinitArgs gets the uinit argvs.
   167  func GetUinitArgs() []string {
   168  	uinitargs, _ := Flag("uroot.uinitargs")
   169  	return shlex.Argv(uinitargs)
   170  }
   171  
   172  // FlagsForModule gets all flags for a designated module
   173  // and returns them as a space-seperated string designed to be passed to insmod
   174  // Note that similarly to flags, module names with - and _ are treated the same.
   175  func FlagsForModule(name string) string {
   176  	once.Do(cmdLineOpener)
   177  	var ret string
   178  	flagsAdded := make(map[string]bool) // Ensures duplicate flags aren't both added
   179  	// Module flags come as moduleName.flag in /proc/cmdline
   180  	prefix := strings.Replace(name, "-", "_", -1) + "."
   181  	for flag, val := range procCmdLine.AsMap {
   182  		canonicalFlag := strings.Replace(flag, "-", "_", -1)
   183  		if !flagsAdded[canonicalFlag] && strings.HasPrefix(canonicalFlag, prefix) {
   184  			flagsAdded[canonicalFlag] = true
   185  			// They are passed to insmod space seperated as flag=val
   186  			ret += strings.TrimPrefix(canonicalFlag, prefix) + "=" + val + " "
   187  		}
   188  	}
   189  	return ret
   190  }