github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/boot/cmdline.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 boot 21 22 import ( 23 "bufio" 24 "bytes" 25 "errors" 26 "fmt" 27 "io/ioutil" 28 "strings" 29 30 "github.com/snapcore/snapd/asserts" 31 "github.com/snapcore/snapd/bootloader" 32 "github.com/snapcore/snapd/logger" 33 "github.com/snapcore/snapd/strutil" 34 ) 35 36 const ( 37 // ModeRun indicates the regular operating system mode of the device. 38 ModeRun = "run" 39 // ModeInstall is a mode in which a new system is installed on the 40 // device. 41 ModeInstall = "install" 42 // ModeRecover is a mode in which the device boots into the recovery 43 // system. 44 ModeRecover = "recover" 45 ) 46 47 var ( 48 // the kernel commandline - can be overridden in tests 49 procCmdline = "/proc/cmdline" 50 51 validModes = []string{ModeInstall, ModeRecover, ModeRun} 52 ) 53 54 func whichModeAndRecoverySystem(cmdline []byte) (mode string, sysLabel string, err error) { 55 scanner := bufio.NewScanner(bytes.NewBuffer(cmdline)) 56 scanner.Split(bufio.ScanWords) 57 58 for scanner.Scan() { 59 w := scanner.Text() 60 if strings.HasPrefix(w, "snapd_recovery_mode=") { 61 if mode != "" { 62 return "", "", fmt.Errorf("cannot specify mode more than once") 63 } 64 mode = strings.SplitN(w, "=", 2)[1] 65 if mode == "" { 66 mode = ModeInstall 67 } 68 if !strutil.ListContains(validModes, mode) { 69 return "", "", fmt.Errorf("cannot use unknown mode %q", mode) 70 } 71 } 72 if strings.HasPrefix(w, "snapd_recovery_system=") { 73 if sysLabel != "" { 74 return "", "", fmt.Errorf("cannot specify recovery system label more than once") 75 } 76 sysLabel = strings.SplitN(w, "=", 2)[1] 77 } 78 } 79 if err := scanner.Err(); err != nil { 80 return "", "", err 81 } 82 switch { 83 case mode == "" && sysLabel == "": 84 return "", "", fmt.Errorf("cannot detect mode nor recovery system to use") 85 case mode == ModeInstall && sysLabel == "": 86 return "", "", fmt.Errorf("cannot specify install mode without system label") 87 case mode == ModeRun && sysLabel != "": 88 // XXX: should we silently ignore the label? at least log for now 89 logger.Noticef(`ignoring recovery system label %q in "run" mode`, sysLabel) 90 sysLabel = "" 91 } 92 return mode, sysLabel, nil 93 } 94 95 // ModeAndRecoverySystemFromKernelCommandLine returns the current system mode 96 // and the recovery system label as passed in the kernel command line by the 97 // bootloader. 98 func ModeAndRecoverySystemFromKernelCommandLine() (mode, sysLabel string, err error) { 99 cmdline, err := ioutil.ReadFile(procCmdline) 100 if err != nil { 101 return "", "", err 102 } 103 return whichModeAndRecoverySystem(cmdline) 104 } 105 106 // MockProcCmdline overrides the path to /proc/cmdline. For use in tests. 107 func MockProcCmdline(newPath string) (restore func()) { 108 oldProcCmdline := procCmdline 109 procCmdline = newPath 110 return func() { 111 procCmdline = oldProcCmdline 112 } 113 } 114 115 var errBootConfigNotManaged = errors.New("boot config is not managed") 116 117 func getBootloaderManagingItsAssets(where string, opts *bootloader.Options) (bootloader.TrustedAssetsBootloader, error) { 118 bl, err := bootloader.Find(where, opts) 119 if err != nil { 120 return nil, fmt.Errorf("internal error: cannot find trusted assets bootloader under %q: %v", where, err) 121 } 122 mbl, ok := bl.(bootloader.TrustedAssetsBootloader) 123 if !ok { 124 // the bootloader cannot manage its scripts 125 return nil, errBootConfigNotManaged 126 } 127 return mbl, nil 128 } 129 130 const ( 131 currentEdition = iota 132 candidateEdition 133 ) 134 135 func composeCommandLine(model *asserts.Model, currentOrCandidate int, mode, system string) (string, error) { 136 if model.Grade() == asserts.ModelGradeUnset { 137 return "", nil 138 } 139 if mode != ModeRun && mode != ModeRecover { 140 return "", fmt.Errorf("internal error: unsupported command line mode %q", mode) 141 } 142 // get the run mode bootloader under the native run partition layout 143 opts := &bootloader.Options{ 144 Role: bootloader.RoleRunMode, 145 NoSlashBoot: true, 146 } 147 bootloaderRootDir := InitramfsUbuntuBootDir 148 modeArg := "snapd_recovery_mode=run" 149 systemArg := "" 150 if mode == ModeRecover { 151 // dealing with recovery system bootloader 152 opts.Role = bootloader.RoleRecovery 153 bootloaderRootDir = InitramfsUbuntuSeedDir 154 // recovery mode & system command line arguments 155 modeArg = "snapd_recovery_mode=recover" 156 systemArg = fmt.Sprintf("snapd_recovery_system=%v", system) 157 } 158 mbl, err := getBootloaderManagingItsAssets(bootloaderRootDir, opts) 159 if err != nil { 160 if err == errBootConfigNotManaged { 161 return "", nil 162 } 163 return "", err 164 } 165 // TODO:UC20: fetch extra args from gadget 166 extraArgs := "" 167 if currentOrCandidate == currentEdition { 168 return mbl.CommandLine(modeArg, systemArg, extraArgs) 169 } else { 170 return mbl.CandidateCommandLine(modeArg, systemArg, extraArgs) 171 } 172 } 173 174 // ComposeRecoveryCommandLine composes the kernel command line used when booting 175 // a given system in recover mode. 176 func ComposeRecoveryCommandLine(model *asserts.Model, system string) (string, error) { 177 return composeCommandLine(model, currentEdition, ModeRecover, system) 178 } 179 180 // ComposeCommandLine composes the kernel command line used when booting the 181 // system in run mode. 182 func ComposeCommandLine(model *asserts.Model) (string, error) { 183 return composeCommandLine(model, currentEdition, ModeRun, "") 184 } 185 186 // TODO:UC20: add helper to compose candidate command line for a recovery system 187 188 // ComposeCandidateCommandLine composes the kernel command line used when 189 // booting the system in run mode with the current built-in edition of managed 190 // boot assets. 191 func ComposeCandidateCommandLine(model *asserts.Model) (string, error) { 192 return composeCommandLine(model, candidateEdition, ModeRun, "") 193 }