github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/boot/cmdline_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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_test 21 22 import ( 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/boot" 30 "github.com/snapcore/snapd/boot/boottest" 31 "github.com/snapcore/snapd/bootloader" 32 "github.com/snapcore/snapd/bootloader/bootloadertest" 33 "github.com/snapcore/snapd/osutil" 34 "github.com/snapcore/snapd/snap/snaptest" 35 "github.com/snapcore/snapd/testutil" 36 ) 37 38 var _ = Suite(&kernelCommandLineSuite{}) 39 40 // baseBootSuite is used to setup the common test environment 41 type kernelCommandLineSuite struct { 42 testutil.BaseTest 43 rootDir string 44 } 45 46 func (s *kernelCommandLineSuite) SetUpTest(c *C) { 47 s.BaseTest.SetUpTest(c) 48 s.rootDir = c.MkDir() 49 50 err := os.MkdirAll(filepath.Join(s.rootDir, "proc"), 0755) 51 c.Assert(err, IsNil) 52 restore := osutil.MockProcCmdline(filepath.Join(s.rootDir, "proc/cmdline")) 53 s.AddCleanup(restore) 54 } 55 56 func (s *kernelCommandLineSuite) mockProcCmdlineContent(c *C, newContent string) { 57 mockProcCmdline := filepath.Join(s.rootDir, "proc/cmdline") 58 err := ioutil.WriteFile(mockProcCmdline, []byte(newContent), 0644) 59 c.Assert(err, IsNil) 60 } 61 62 func (s *kernelCommandLineSuite) TestModeAndLabel(c *C) { 63 for _, tc := range []struct { 64 cmd string 65 mode string 66 label string 67 err string 68 }{{ 69 cmd: "snapd_recovery_mode= snapd_recovery_system=this-is-a-label other-option=foo", 70 mode: boot.ModeInstall, 71 label: "this-is-a-label", 72 }, { 73 cmd: "snapd_recovery_system=label foo=bar foobaz=\\0\\0123 snapd_recovery_mode=install", 74 label: "label", 75 mode: boot.ModeInstall, 76 }, { 77 cmd: "snapd_recovery_mode=run snapd_recovery_system=1234", 78 mode: boot.ModeRun, 79 }, { 80 cmd: "option=1 other-option=\0123 none", 81 err: "cannot detect mode nor recovery system to use", 82 }, { 83 cmd: "snapd_recovery_mode=install-foo", 84 err: `cannot use unknown mode "install-foo"`, 85 }, { 86 // no recovery system label 87 cmd: "snapd_recovery_mode=install foo=bar", 88 err: `cannot specify install mode without system label`, 89 }, { 90 cmd: "snapd_recovery_system=1234", 91 err: `cannot specify system label without a mode`, 92 }, { 93 // multiple kernel command line params end up using the last one - this 94 // effectively matches the kernel handling too 95 cmd: "snapd_recovery_mode=install snapd_recovery_system=1234 snapd_recovery_mode=run", 96 mode: "run", 97 // label gets unset because it's not used for run mode 98 label: "", 99 }, { 100 cmd: "snapd_recovery_system=not-this-one snapd_recovery_mode=install snapd_recovery_system=1234", 101 mode: "install", 102 label: "1234", 103 }} { 104 c.Logf("tc: %q", tc) 105 s.mockProcCmdlineContent(c, tc.cmd) 106 107 mode, label, err := boot.ModeAndRecoverySystemFromKernelCommandLine() 108 if tc.err == "" { 109 c.Assert(err, IsNil) 110 c.Check(mode, Equals, tc.mode) 111 c.Check(label, Equals, tc.label) 112 } else { 113 c.Assert(err, ErrorMatches, tc.err) 114 } 115 } 116 } 117 118 func (s *kernelCommandLineSuite) TestComposeCommandLineNotManagedHappy(c *C) { 119 model := boottest.MakeMockUC20Model() 120 121 bl := bootloadertest.Mock("btloader", c.MkDir()) 122 bootloader.Force(bl) 123 defer bootloader.Force(nil) 124 125 cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "") 126 c.Assert(err, IsNil) 127 c.Assert(cmdline, Equals, "") 128 129 cmdline, err = boot.ComposeCommandLine(model, "") 130 c.Assert(err, IsNil) 131 c.Assert(cmdline, Equals, "") 132 133 tbl := bl.WithTrustedAssets() 134 bootloader.Force(tbl) 135 136 cmdline, err = boot.ComposeRecoveryCommandLine(model, "20200314", "") 137 c.Assert(err, IsNil) 138 c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314") 139 140 cmdline, err = boot.ComposeCommandLine(model, "") 141 c.Assert(err, IsNil) 142 c.Assert(cmdline, Equals, "snapd_recovery_mode=run") 143 } 144 145 func (s *kernelCommandLineSuite) TestComposeCommandLineNotUC20(c *C) { 146 model := boottest.MakeMockModel() 147 148 bl := bootloadertest.Mock("btloader", c.MkDir()) 149 bootloader.Force(bl) 150 defer bootloader.Force(nil) 151 cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "") 152 c.Assert(err, IsNil) 153 c.Check(cmdline, Equals, "") 154 155 cmdline, err = boot.ComposeCommandLine(model, "") 156 c.Assert(err, IsNil) 157 c.Check(cmdline, Equals, "") 158 } 159 160 func (s *kernelCommandLineSuite) TestComposeCommandLineManagedHappy(c *C) { 161 model := boottest.MakeMockUC20Model() 162 163 tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets() 164 bootloader.Force(tbl) 165 defer bootloader.Force(nil) 166 167 tbl.StaticCommandLine = "panic=-1" 168 169 cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "") 170 c.Assert(err, IsNil) 171 c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314 panic=-1") 172 cmdline, err = boot.ComposeCommandLine(model, "") 173 c.Assert(err, IsNil) 174 c.Assert(cmdline, Equals, "snapd_recovery_mode=run panic=-1") 175 176 cmdline, err = boot.ComposeRecoveryCommandLine(model, "20200314", "") 177 c.Assert(err, IsNil) 178 c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314 panic=-1") 179 cmdline, err = boot.ComposeCommandLine(model, "") 180 c.Assert(err, IsNil) 181 c.Assert(cmdline, Equals, "snapd_recovery_mode=run panic=-1") 182 } 183 184 func (s *kernelCommandLineSuite) TestComposeCandidateCommandLineManagedHappy(c *C) { 185 model := boottest.MakeMockUC20Model() 186 187 tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets() 188 bootloader.Force(tbl) 189 defer bootloader.Force(nil) 190 191 tbl.StaticCommandLine = "panic=-1" 192 tbl.CandidateStaticCommandLine = "candidate panic=0" 193 194 cmdline, err := boot.ComposeCandidateCommandLine(model, "") 195 c.Assert(err, IsNil) 196 c.Assert(cmdline, Equals, "snapd_recovery_mode=run candidate panic=0") 197 } 198 199 func (s *kernelCommandLineSuite) TestComposeCandidateRecoveryCommandLineManagedHappy(c *C) { 200 model := boottest.MakeMockUC20Model() 201 202 tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets() 203 bootloader.Force(tbl) 204 defer bootloader.Force(nil) 205 206 tbl.StaticCommandLine = "panic=-1" 207 tbl.CandidateStaticCommandLine = "candidate panic=0" 208 209 cmdline, err := boot.ComposeCandidateRecoveryCommandLine(model, "1234", "") 210 c.Assert(err, IsNil) 211 c.Check(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=1234 candidate panic=0") 212 213 cmdline, err = boot.ComposeCandidateRecoveryCommandLine(model, "", "") 214 c.Assert(err, ErrorMatches, "internal error: system is unset") 215 c.Check(cmdline, Equals, "") 216 } 217 218 const gadgetSnapYaml = `name: gadget 219 version: 1.0 220 type: gadget 221 ` 222 223 func (s *kernelCommandLineSuite) TestComposeCommandLineWithGadget(c *C) { 224 model := boottest.MakeMockUC20Model() 225 226 tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets() 227 bootloader.Force(tbl) 228 defer bootloader.Force(nil) 229 230 tbl.StaticCommandLine = "panic=-1" 231 tbl.CandidateStaticCommandLine = "candidate panic=0" 232 233 for _, tc := range []struct { 234 which string 235 files [][]string 236 expCommandLine string 237 errMsg string 238 }{{ 239 which: "current", 240 files: [][]string{ 241 {"cmdline.extra", "cmdline extra"}, 242 }, 243 expCommandLine: "snapd_recovery_mode=run panic=-1 cmdline extra", 244 }, { 245 which: "candidate", 246 files: [][]string{ 247 {"cmdline.extra", "cmdline extra"}, 248 }, 249 expCommandLine: "snapd_recovery_mode=run candidate panic=0 cmdline extra", 250 }, { 251 which: "current", 252 files: [][]string{ 253 {"cmdline.full", "cmdline full"}, 254 }, 255 expCommandLine: "snapd_recovery_mode=run cmdline full", 256 }, { 257 which: "candidate", 258 files: [][]string{ 259 {"cmdline.full", "cmdline full"}, 260 }, 261 expCommandLine: "snapd_recovery_mode=run cmdline full", 262 }, { 263 which: "candidate", 264 files: [][]string{ 265 {"cmdline.extra", `bad-quote="`}, 266 }, 267 errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: unbalanced quoting`, 268 }} { 269 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{ 270 {"meta/snap.yaml", gadgetSnapYaml}, 271 }, tc.files...)) 272 var cmdline string 273 var err error 274 switch tc.which { 275 case "current": 276 cmdline, err = boot.ComposeCommandLine(model, sf) 277 case "candidate": 278 cmdline, err = boot.ComposeCandidateCommandLine(model, sf) 279 default: 280 c.Fatalf("unexpected command line type") 281 } 282 if tc.errMsg == "" { 283 c.Assert(err, IsNil) 284 c.Assert(cmdline, Equals, tc.expCommandLine) 285 } else { 286 c.Assert(err, ErrorMatches, tc.errMsg) 287 } 288 } 289 } 290 291 func (s *kernelCommandLineSuite) TestComposeRecoveryCommandLineWithGadget(c *C) { 292 model := boottest.MakeMockUC20Model() 293 294 tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets() 295 bootloader.Force(tbl) 296 defer bootloader.Force(nil) 297 298 tbl.StaticCommandLine = "panic=-1" 299 tbl.CandidateStaticCommandLine = "candidate panic=0" 300 system := "1234" 301 302 for _, tc := range []struct { 303 which string 304 files [][]string 305 expCommandLine string 306 errMsg string 307 }{{ 308 which: "current", 309 files: [][]string{ 310 {"cmdline.extra", "cmdline extra"}, 311 }, 312 expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 panic=-1 cmdline extra", 313 }, { 314 which: "candidate", 315 files: [][]string{ 316 {"cmdline.extra", "cmdline extra"}, 317 }, 318 expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 candidate panic=0 cmdline extra", 319 }, { 320 which: "current", 321 files: [][]string{ 322 {"cmdline.full", "cmdline full"}, 323 }, 324 expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 cmdline full", 325 }, { 326 which: "candidate", 327 files: [][]string{ 328 {"cmdline.full", "cmdline full"}, 329 }, 330 expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 cmdline full", 331 }, { 332 which: "candidate", 333 files: [][]string{ 334 {"cmdline.extra", `bad-quote="`}, 335 }, 336 errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: unbalanced quoting`, 337 }} { 338 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{ 339 {"meta/snap.yaml", gadgetSnapYaml}, 340 }, tc.files...)) 341 var cmdline string 342 var err error 343 switch tc.which { 344 case "current": 345 cmdline, err = boot.ComposeRecoveryCommandLine(model, system, sf) 346 case "candidate": 347 cmdline, err = boot.ComposeCandidateRecoveryCommandLine(model, system, sf) 348 default: 349 c.Fatalf("unexpected command line type") 350 } 351 if tc.errMsg == "" { 352 c.Assert(err, IsNil) 353 c.Assert(cmdline, Equals, tc.expCommandLine) 354 } else { 355 c.Assert(err, ErrorMatches, tc.errMsg) 356 } 357 } 358 } 359 360 func (s *kernelCommandLineSuite) TestBootVarsForGadgetCommandLine(c *C) { 361 for _, tc := range []struct { 362 errMsg string 363 files [][]string 364 expectedVars map[string]string 365 }{{ 366 files: [][]string{ 367 {"cmdline.extra", "foo bar baz"}, 368 }, 369 expectedVars: map[string]string{ 370 "snapd_extra_cmdline_args": "foo bar baz", 371 "snapd_full_cmdline_args": "", 372 }, 373 }, { 374 files: [][]string{ 375 {"cmdline.extra", "snapd.debug=1"}, 376 }, 377 expectedVars: map[string]string{ 378 "snapd_extra_cmdline_args": "snapd.debug=1", 379 "snapd_full_cmdline_args": "", 380 }, 381 }, { 382 files: [][]string{ 383 {"cmdline.extra", "snapd_foo"}, 384 }, 385 errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: disallowed kernel argument \"snapd_foo\"`, 386 }, { 387 files: [][]string{ 388 {"cmdline.full", "full foo bar baz"}, 389 }, 390 expectedVars: map[string]string{ 391 "snapd_extra_cmdline_args": "", 392 "snapd_full_cmdline_args": "full foo bar baz", 393 }, 394 }, { 395 // with no arguments boot variables should be cleared 396 files: [][]string{}, 397 expectedVars: map[string]string{ 398 "snapd_extra_cmdline_args": "", 399 "snapd_full_cmdline_args": "", 400 }, 401 }} { 402 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{ 403 {"meta/snap.yaml", gadgetSnapYaml}, 404 }, tc.files...)) 405 vars, err := boot.BootVarsForTrustedCommandLineFromGadget(sf) 406 if tc.errMsg == "" { 407 c.Assert(err, IsNil) 408 c.Assert(vars, DeepEquals, tc.expectedVars) 409 } else { 410 c.Assert(err, ErrorMatches, tc.errMsg) 411 } 412 } 413 }