gitee.com/mysnapcore/mysnapd@v0.1.0/image/preseed/preseed_linux.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-2022 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 preseed 21 22 import ( 23 "crypto" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "io/ioutil" 28 "os" 29 "os/exec" 30 "path/filepath" 31 "sort" 32 "strings" 33 "syscall" 34 35 "gitee.com/mysnapcore/mysnapd/dirs" 36 "gitee.com/mysnapcore/mysnapd/osutil" 37 "gitee.com/mysnapcore/mysnapd/osutil/squashfs" 38 "gitee.com/mysnapcore/mysnapd/snapdtool" 39 "gitee.com/mysnapcore/mysnapd/strutil" 40 "gitee.com/mysnapcore/mysnapd/timings" 41 ) 42 43 var ( 44 // snapdMountPath is where target core/snapd is going to be mounted in the target chroot 45 snapdMountPath = "/tmp/snapd-preseed" 46 syscallChroot = syscall.Chroot 47 ) 48 49 // checkChroot does a basic validity check of the target chroot environment, e.g. makes 50 // sure critical virtual filesystems (such as proc) are mounted. This is not meant to 51 // be exhaustive check, but one that prevents running the tool against a wrong directory 52 // by an accident, which would lead to hard to understand errors from snapd in preseed 53 // mode. 54 func checkChroot(preseedChroot string) error { 55 exists, isDir, err := osutil.DirExists(preseedChroot) 56 if err != nil { 57 return fmt.Errorf("cannot verify %q: %v", preseedChroot, err) 58 } 59 if !exists || !isDir { 60 return fmt.Errorf("cannot verify %q: is not a directory", preseedChroot) 61 } 62 63 if osutil.FileExists(filepath.Join(preseedChroot, dirs.SnapStateFile)) { 64 return fmt.Errorf("the system at %q appears to be preseeded, pass --reset flag to clean it up", preseedChroot) 65 } 66 67 // validity checks of the critical mountpoints inside chroot directory. 68 required := map[string]bool{} 69 // required mountpoints are relative to the preseed chroot 70 for _, p := range []string{"/sys/kernel/security", "/proc", "/dev"} { 71 required[filepath.Join(preseedChroot, p)] = true 72 } 73 entries, err := osutil.LoadMountInfo() 74 if err != nil { 75 return fmt.Errorf("cannot parse mount info: %v", err) 76 } 77 for _, ent := range entries { 78 if _, ok := required[ent.MountDir]; ok { 79 delete(required, ent.MountDir) 80 } 81 } 82 // non empty required indicates missing mountpoint(s) 83 if len(required) > 0 { 84 var sorted []string 85 for path := range required { 86 sorted = append(sorted, path) 87 } 88 sort.Strings(sorted) 89 parts := append([]string{""}, sorted...) 90 return fmt.Errorf("cannot preseed without the following mountpoints:%s", strings.Join(parts, "\n - ")) 91 } 92 93 path := filepath.Join(preseedChroot, "/sys/kernel/security/apparmor") 94 if exists := osutil.FileExists(path); !exists { 95 return fmt.Errorf("cannot preseed without access to %q", path) 96 } 97 98 return nil 99 } 100 101 var systemSnapFromSeed = func(seedDir, sysLabel string) (systemSnap string, baseSnap string, err error) { 102 seed, err := seedOpen(seedDir, sysLabel) 103 if err != nil { 104 return "", "", err 105 } 106 107 // load assertions into temporary database 108 if err := seed.LoadAssertions(nil, nil); err != nil { 109 return "", "", err 110 } 111 model := seed.Model() 112 113 tm := timings.New(nil) 114 115 if err := seed.LoadEssentialMeta(nil, tm); err != nil { 116 return "", "", err 117 } 118 119 if model.Classic() { 120 fmt.Fprintf(Stdout, "ubuntu classic preseeding\n") 121 } else { 122 if model.Base() == "core20" { 123 fmt.Fprintf(Stdout, "UC20+ preseeding\n") 124 } else { 125 // TODO: support uc20+ 126 return "", "", fmt.Errorf("preseeding of ubuntu core with base %s is not supported", model.Base()) 127 } 128 } 129 130 var required string 131 if seed.UsesSnapdSnap() { 132 required = "snapd" 133 } else { 134 required = "core" 135 } 136 137 var systemSnapPath, baseSnapPath string 138 for _, ess := range seed.EssentialSnaps() { 139 if ess.SnapName() == required { 140 systemSnapPath = ess.Path 141 } 142 if ess.EssentialType == "base" { 143 baseSnapPath = ess.Path 144 } 145 } 146 147 if systemSnapPath == "" { 148 return "", "", fmt.Errorf("%s snap not found", required) 149 } 150 151 return systemSnapPath, baseSnapPath, nil 152 } 153 154 const snapdPreseedSupportVer = `2.43.3+` 155 156 // chooseTargetSnapdVersion checks if the version of snapd under chroot env 157 // is good enough for preseeding. It checks both the snapd from the deb 158 // and from the seeded snap mounted under snapdMountPath and returns the 159 // information (path, version) about snapd to execute as part of preseeding 160 // (it picks the newer version of the two). 161 // The function must be called after syscall.Chroot(..). 162 func chooseTargetSnapdVersion() (*targetSnapdInfo, error) { 163 // read snapd version from the mounted core/snapd snap 164 snapdInfoDir := filepath.Join(snapdMountPath, dirs.CoreLibExecDir) 165 verFromSnap, _, err := snapdtool.SnapdVersionFromInfoFile(snapdInfoDir) 166 if err != nil { 167 return nil, err 168 } 169 170 // read snapd version from the main fs under chroot (snapd from the deb); 171 // assumes running under chroot already. 172 hostInfoDir := filepath.Join(dirs.GlobalRootDir, dirs.CoreLibExecDir) 173 verFromDeb, _, err := snapdtool.SnapdVersionFromInfoFile(hostInfoDir) 174 if err != nil { 175 return nil, err 176 } 177 178 res, err := strutil.VersionCompare(verFromSnap, verFromDeb) 179 if err != nil { 180 return nil, err 181 } 182 183 var whichVer, snapdPath string 184 if res < 0 { 185 // snapd from the deb under chroot is the candidate to run 186 whichVer = verFromDeb 187 snapdPath = filepath.Join(dirs.GlobalRootDir, dirs.CoreLibExecDir, "snapd") 188 } else { 189 // snapd from the mounted core/snapd snap is the candidate to run 190 whichVer = verFromSnap 191 snapdPath = filepath.Join(snapdMountPath, dirs.CoreLibExecDir, "snapd") 192 } 193 194 res, err = strutil.VersionCompare(whichVer, snapdPreseedSupportVer) 195 if err != nil { 196 return nil, err 197 } 198 if res < 0 { 199 return nil, fmt.Errorf("snapd %s from the target system does not support preseeding, the minimum required version is %s", 200 whichVer, snapdPreseedSupportVer) 201 } 202 203 return &targetSnapdInfo{path: snapdPath, version: whichVer}, nil 204 } 205 206 func prepareCore20Mountpoints(prepareImageDir, tmpPreseedChrootDir, snapdSnapBlob, baseSnapBlob, aaFeaturesDir, writable string) (cleanupMounts func(), err error) { 207 underPreseed := func(path string) string { 208 return filepath.Join(tmpPreseedChrootDir, path) 209 } 210 211 if err := os.MkdirAll(filepath.Join(writable, "system-data", "etc"), 0755); err != nil { 212 return nil, err 213 } 214 where := filepath.Join(snapdMountPath) 215 if err := os.MkdirAll(where, 0755); err != nil { 216 return nil, err 217 } 218 219 var mounted []string 220 221 doUnmount := func(mnt string) { 222 cmd := exec.Command("umount", mnt) 223 if out, err := cmd.CombinedOutput(); err != nil { 224 fmt.Fprintf(Stdout, "cannot unmount: %v\n'umount %s' failed with: %s", err, mnt, out) 225 } 226 } 227 228 cleanupMounts = func() { 229 // unmount all the mounts but the first one, which is the base 230 // and it is cleaned up last 231 for i := len(mounted) - 1; i > 0; i-- { 232 mnt := mounted[i] 233 doUnmount(mnt) 234 } 235 236 entries, err := osutil.LoadMountInfo() 237 if err != nil { 238 fmt.Fprintf(Stdout, "cannot parse mount info when cleaning up mount points: %v", err) 239 return 240 } 241 // cleanup after handle-writable-paths 242 for _, ent := range entries { 243 if ent.MountDir != tmpPreseedChrootDir && strings.HasPrefix(ent.MountDir, tmpPreseedChrootDir) { 244 doUnmount(ent.MountDir) 245 } 246 } 247 248 // finally, umount the base snap 249 if len(mounted) > 0 { 250 doUnmount(mounted[0]) 251 } 252 } 253 254 cleanupOnError := func() { 255 if err == nil { 256 return 257 } 258 259 if cleanupMounts != nil { 260 cleanupMounts() 261 } 262 } 263 defer cleanupOnError() 264 265 mounts := [][]string{ 266 {"-o", "loop", baseSnapBlob, tmpPreseedChrootDir}, 267 {"-o", "loop", snapdSnapBlob, snapdMountPath}, 268 {"-t", "tmpfs", "tmpfs", underPreseed("run")}, 269 {"-t", "tmpfs", "tmpfs", underPreseed("var/tmp")}, 270 {"--bind", underPreseed("var/tmp"), underPreseed("tmp")}, 271 {"-t", "proc", "proc", underPreseed("proc")}, 272 {"-t", "sysfs", "sysfs", underPreseed("sys")}, 273 {"-t", "devtmpfs", "udev", underPreseed("dev")}, 274 {"-t", "securityfs", "securityfs", underPreseed("sys/kernel/security")}, 275 {"--bind", writable, underPreseed("writable")}, 276 } 277 278 var out []byte 279 for _, mountArgs := range mounts { 280 cmd := exec.Command("mount", mountArgs...) 281 if out, err = cmd.CombinedOutput(); err != nil { 282 return nil, fmt.Errorf("cannot prepare mountpoint in preseed mode: %v\n'mount %s' failed with: %s", err, strings.Join(mountArgs, " "), out) 283 } 284 mounted = append(mounted, mountArgs[len(mountArgs)-1]) 285 } 286 287 cmd := exec.Command(underPreseed("/usr/lib/core/handle-writable-paths"), tmpPreseedChrootDir) 288 if out, err = cmd.CombinedOutput(); err != nil { 289 return nil, fmt.Errorf("handle-writable-paths failed with: %v\n%s", err, out) 290 } 291 292 for _, dir := range []string{ 293 "etc/udev/rules.d", "etc/systemd/system", "etc/dbus-1/session.d", 294 "var/lib/snapd/seed", "var/cache/snapd", "var/cache/apparmor", 295 "var/snap", "snap", "var/lib/extrausers", 296 } { 297 if err = os.MkdirAll(filepath.Join(writable, dir), 0755); err != nil { 298 return nil, err 299 } 300 } 301 302 underWritable := func(path string) string { 303 return filepath.Join(writable, path) 304 } 305 mounts = [][]string{ 306 {"--bind", underWritable("system-data/var/lib/snapd"), underPreseed("var/lib/snapd")}, 307 {"--bind", underWritable("system-data/var/cache/snapd"), underPreseed("var/cache/snapd")}, 308 {"--bind", underWritable("system-data/var/cache/apparmor"), underPreseed("var/cache/apparmor")}, 309 {"--bind", underWritable("system-data/var/snap"), underPreseed("var/snap")}, 310 {"--bind", underWritable("system-data/snap"), underPreseed("snap")}, 311 {"--bind", underWritable("system-data/etc/systemd"), underPreseed("etc/systemd")}, 312 {"--bind", underWritable("system-data/etc/dbus-1"), underPreseed("etc/dbus-1")}, 313 {"--bind", underWritable("system-data/etc/udev/rules.d"), underPreseed("etc/udev/rules.d")}, 314 {"--bind", underWritable("system-data/var/lib/extrausers"), underPreseed("var/lib/extrausers")}, 315 {"--bind", filepath.Join(snapdMountPath, "/usr/lib/snapd"), underPreseed("/usr/lib/snapd")}, 316 {"--bind", filepath.Join(prepareImageDir, "system-seed"), underPreseed("var/lib/snapd/seed")}, 317 } 318 319 if aaFeaturesDir != "" { 320 mounts = append(mounts, []string{"--bind", aaFeaturesDir, underPreseed("sys/kernel/security/apparmor/features")}) 321 } 322 323 for _, mountArgs := range mounts { 324 cmd := exec.Command("mount", mountArgs...) 325 if out, err = cmd.CombinedOutput(); err != nil { 326 return nil, fmt.Errorf("cannot prepare mountpoint in preseed mode: %v\n'mount %s' failed with: %s", err, strings.Join(mountArgs, " "), out) 327 } 328 mounted = append(mounted, mountArgs[len(mountArgs)-1]) 329 } 330 331 return cleanupMounts, nil 332 } 333 334 func systemForPreseeding(systemsDir string) (label string, err error) { 335 systemLabels, err := filepath.Glob(filepath.Join(systemsDir, "systems", "*")) 336 if err != nil && !os.IsNotExist(err) { 337 return "", fmt.Errorf("cannot list available systems: %v", err) 338 } 339 if len(systemLabels) != 1 { 340 return "", fmt.Errorf("expected a single system for preseeding, found %d", len(systemLabels)) 341 } 342 return filepath.Base(systemLabels[0]), nil 343 } 344 345 var makePreseedTempDir = func() (string, error) { 346 return ioutil.TempDir("", "preseed-") 347 } 348 349 var makeWritableTempDir = func() (string, error) { 350 return ioutil.TempDir("", "writable-") 351 } 352 353 func prepareCore20Chroot(prepareImageDir, aaFeaturesDir string) (preseed *preseedOpts, cleanup func(), err error) { 354 sysDir := filepath.Join(prepareImageDir, "system-seed") 355 sysLabel, err := systemForPreseeding(sysDir) 356 if err != nil { 357 return nil, nil, err 358 } 359 snapdSnapPath, baseSnapPath, err := systemSnapFromSeed(sysDir, sysLabel) 360 if err != nil { 361 return nil, nil, err 362 } 363 364 if snapdSnapPath == "" { 365 return nil, nil, fmt.Errorf("snapd snap not found") 366 } 367 if baseSnapPath == "" { 368 return nil, nil, fmt.Errorf("base snap not found") 369 } 370 371 tmpPreseedChrootDir, err := makePreseedTempDir() 372 if err != nil { 373 return nil, nil, fmt.Errorf("cannot prepare uc20 chroot: %v", err) 374 } 375 writableTmpDir, err := makeWritableTempDir() 376 if err != nil { 377 return nil, nil, fmt.Errorf("cannot prepare uc20 chroot: %v", err) 378 } 379 380 cleanupMounts, err := prepareCore20Mountpoints(prepareImageDir, tmpPreseedChrootDir, snapdSnapPath, baseSnapPath, aaFeaturesDir, writableTmpDir) 381 if err != nil { 382 return nil, nil, fmt.Errorf("cannot prepare uc20 mountpoints: %v", err) 383 } 384 385 cleanup = func() { 386 cleanupMounts() 387 if err := os.RemoveAll(tmpPreseedChrootDir); err != nil { 388 fmt.Fprintf(Stdout, "%v", err) 389 } 390 if err := os.RemoveAll(writableTmpDir); err != nil { 391 fmt.Fprintf(Stdout, "%v", err) 392 } 393 if err := os.RemoveAll(snapdMountPath); err != nil { 394 fmt.Fprintf(Stdout, "%v", err) 395 } 396 } 397 398 opts := &preseedOpts{ 399 PrepareImageDir: prepareImageDir, 400 PreseedChrootDir: tmpPreseedChrootDir, 401 SystemLabel: sysLabel, 402 WritableDir: writableTmpDir, 403 } 404 return opts, cleanup, nil 405 } 406 407 func prepareClassicChroot(preseedChroot string) (*targetSnapdInfo, func(), error) { 408 if err := syscallChroot(preseedChroot); err != nil { 409 return nil, nil, fmt.Errorf("cannot chroot into %s: %v", preseedChroot, err) 410 } 411 412 if err := os.Chdir("/"); err != nil { 413 return nil, nil, fmt.Errorf("cannot chdir to /: %v", err) 414 } 415 416 // GlobalRootDir is now relative to chroot env. We assume all paths 417 // inside the chroot to be identical with the host. 418 rootDir := dirs.GlobalRootDir 419 if rootDir == "" { 420 rootDir = "/" 421 } 422 423 coreSnapPath, _, err := systemSnapFromSeed(dirs.SnapSeedDirUnder(rootDir), "") 424 if err != nil { 425 return nil, nil, err 426 } 427 428 // create mountpoint for core/snapd 429 where := filepath.Join(rootDir, snapdMountPath) 430 if err := os.MkdirAll(where, 0755); err != nil { 431 return nil, nil, err 432 } 433 434 removeMountpoint := func() { 435 if err := os.Remove(where); err != nil { 436 fmt.Fprintf(Stderr, "%v", err) 437 } 438 } 439 440 fstype, fsopts := squashfs.FsType() 441 mountArgs := []string{"-t", fstype, "-o", strings.Join(fsopts, ","), coreSnapPath, where} 442 cmd := exec.Command("mount", mountArgs...) 443 if out, err := cmd.CombinedOutput(); err != nil { 444 removeMountpoint() 445 return nil, nil, fmt.Errorf("cannot mount %s at %s in preseed mode: %v\n'mount %s' failed with: %s", coreSnapPath, where, err, strings.Join(mountArgs, " "), out) 446 } 447 448 unmount := func() { 449 fmt.Fprintf(Stdout, "unmounting: %s\n", snapdMountPath) 450 cmd := exec.Command("umount", snapdMountPath) 451 if err := cmd.Run(); err != nil { 452 fmt.Fprintf(Stderr, "%v", err) 453 } 454 } 455 456 targetSnapd, err := chooseTargetSnapdVersion() 457 if err != nil { 458 unmount() 459 removeMountpoint() 460 return nil, nil, err 461 } 462 463 return targetSnapd, func() { 464 unmount() 465 removeMountpoint() 466 }, nil 467 } 468 469 type preseedFilePatterns struct { 470 Exclude []string `json:"exclude"` 471 Include []string `json:"include"` 472 } 473 474 func createPreseedArtifact(opts *preseedOpts) (digest []byte, err error) { 475 artifactPath := filepath.Join(opts.PrepareImageDir, "system-seed", "systems", opts.SystemLabel, "preseed.tgz") 476 systemData := filepath.Join(opts.WritableDir, "system-data") 477 478 patternsFile := filepath.Join(opts.PreseedChrootDir, "usr/lib/snapd/preseed.json") 479 pf, err := os.Open(patternsFile) 480 if err != nil { 481 return nil, err 482 } 483 484 var patterns preseedFilePatterns 485 dec := json.NewDecoder(pf) 486 if err := dec.Decode(&patterns); err != nil { 487 return nil, err 488 } 489 490 args := []string{"-czf", artifactPath, "-p", "-C", systemData} 491 for _, excl := range patterns.Exclude { 492 args = append(args, "--exclude", excl) 493 } 494 for _, incl := range patterns.Include { 495 // tar doesn't support globs for files to include, since we are not using shell we need to 496 // handle globs explicitly. 497 matches, err := filepath.Glob(filepath.Join(systemData, incl)) 498 if err != nil { 499 return nil, err 500 } 501 for _, m := range matches { 502 relPath, err := filepath.Rel(systemData, m) 503 if err != nil { 504 return nil, err 505 } 506 args = append(args, relPath) 507 } 508 } 509 510 cmd := exec.Command("tar", args...) 511 if out, err := cmd.CombinedOutput(); err != nil { 512 return nil, fmt.Errorf("%v (%s)", err, out) 513 } 514 515 sha3_384, _, err := osutil.FileDigest(artifactPath, crypto.SHA3_384) 516 return sha3_384, err 517 } 518 519 // runPreseedMode runs snapd in a preseed mode. It assumes running in a chroot. 520 // The chroot is expected to be set-up and ready to use (critical system directories mounted). 521 func runPreseedMode(preseedChroot string, targetSnapd *targetSnapdInfo) error { 522 // run snapd in preseed mode 523 cmd := exec.Command(targetSnapd.path) 524 cmd.Env = os.Environ() 525 cmd.Env = append(cmd.Env, "SNAPD_PRESEED=1") 526 cmd.Stderr = Stderr 527 cmd.Stdout = Stdout 528 529 // note, snapdPath is relative to preseedChroot 530 fmt.Fprintf(Stdout, "starting to preseed root: %s\nusing snapd binary: %s (%s)\n", preseedChroot, targetSnapd.path, targetSnapd.version) 531 532 if err := cmd.Run(); err != nil { 533 return fmt.Errorf("error running snapd in preseed mode: %v\n", err) 534 } 535 536 return nil 537 } 538 539 func runUC20PreseedMode(opts *preseedOpts) error { 540 cmd := exec.Command("chroot", opts.PreseedChrootDir, "/usr/lib/snapd/snapd") 541 cmd.Env = os.Environ() 542 cmd.Env = append(cmd.Env, "SNAPD_PRESEED=1") 543 cmd.Stderr = Stderr 544 cmd.Stdout = Stdout 545 fmt.Fprintf(Stdout, "starting to preseed UC20+ system: %s\n", opts.PreseedChrootDir) 546 547 if err := cmd.Run(); err != nil { 548 var errno syscall.Errno 549 if errors.As(err, &errno) && errno == syscall.ENOEXEC { 550 return fmt.Errorf(`error running snapd, please try installing the "qemu-user-static" package: %v`, err) 551 } 552 553 return fmt.Errorf("error running snapd in preseed mode: %v\n", err) 554 } 555 556 digest, err := createPreseedArtifact(opts) 557 if err != nil { 558 return fmt.Errorf("cannot create preseed.tgz: %v", err) 559 } 560 561 if err := writePreseedAssertion(digest, opts); err != nil { 562 return fmt.Errorf("cannot create preseed assertion: %v", err) 563 } 564 565 return nil 566 } 567 568 // Core20 runs preseeding of UC20 system prepared by prepare-image in prepareImageDir 569 // and stores the resulting preseed preseed.tgz file in system-seed/systems/<systemlabel>/preseed.tgz. 570 // Expects single systemlabel under systems directory. 571 func Core20(prepareImageDir, preseedSignKey, aaFeaturesDir string) error { 572 var err error 573 prepareImageDir, err = filepath.Abs(prepareImageDir) 574 if err != nil { 575 return err 576 } 577 578 popts, cleanup, err := prepareCore20Chroot(prepareImageDir, aaFeaturesDir) 579 if err != nil { 580 return err 581 } 582 defer cleanup() 583 584 popts.PreseedSignKey = preseedSignKey 585 return runUC20PreseedMode(popts) 586 } 587 588 // Classic runs preseeding of a classic ubuntu system pointed by chrootDir. 589 func Classic(chrootDir string) error { 590 var err error 591 chrootDir, err = filepath.Abs(chrootDir) 592 if err != nil { 593 return err 594 } 595 596 if err := checkChroot(chrootDir); err != nil { 597 return err 598 } 599 600 var targetSnapd *targetSnapdInfo 601 602 // XXX: if prepareClassicChroot & runPreseedMode were refactored to 603 // use "chroot" inside runPreseedMode (and not syscall.Chroot at the 604 // beginning of prepareClassicChroot), then we could have a single 605 // runPreseedMode/runUC20PreseedMode function that handles both classic 606 // and core20. 607 targetSnapd, cleanup, err := prepareClassicChroot(chrootDir) 608 if err != nil { 609 return err 610 } 611 defer cleanup() 612 613 // executing inside the chroot 614 return runPreseedMode(chrootDir, targetSnapd) 615 } 616 617 func MockSyscallChroot(f func(string) error) (restore func()) { 618 osutil.MustBeTestBinary("mocking can be done only in tests") 619 620 oldSyscallChroot := syscallChroot 621 syscallChroot = f 622 return func() { syscallChroot = oldSyscallChroot } 623 } 624 625 func MockSnapdMountPath(path string) (restore func()) { 626 osutil.MustBeTestBinary("mocking can be done only in tests") 627 628 oldMountPath := snapdMountPath 629 snapdMountPath = path 630 return func() { snapdMountPath = oldMountPath } 631 } 632 633 func MockSystemSnapFromSeed(f func(rootDir, sysLabel string) (string, string, error)) (restore func()) { 634 osutil.MustBeTestBinary("mocking can be done only in tests") 635 636 oldSystemSnapFromSeed := systemSnapFromSeed 637 systemSnapFromSeed = f 638 return func() { systemSnapFromSeed = oldSystemSnapFromSeed } 639 } 640 641 func MockMakePreseedTempDir(f func() (string, error)) (restore func()) { 642 osutil.MustBeTestBinary("mocking can be done only in tests") 643 644 old := makePreseedTempDir 645 makePreseedTempDir = f 646 return func() { 647 makePreseedTempDir = old 648 } 649 } 650 651 func MockMakeWritableTempDir(f func() (string, error)) (restore func()) { 652 osutil.MustBeTestBinary("mocking can be done only in tests") 653 654 old := makeWritableTempDir 655 makeWritableTempDir = f 656 return func() { 657 makeWritableTempDir = old 658 } 659 }