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