gitee.com/mysnapcore/mysnapd@v0.1.0/wrappers/core18.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 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 wrappers 21 22 import ( 23 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "regexp" 29 "syscall" 30 31 "gitee.com/mysnapcore/mysnapd/dirs" 32 "gitee.com/mysnapcore/mysnapd/logger" 33 "gitee.com/mysnapcore/mysnapd/osutil" 34 "gitee.com/mysnapcore/mysnapd/release" 35 "gitee.com/mysnapcore/mysnapd/snap" 36 "gitee.com/mysnapcore/mysnapd/systemd" 37 ) 38 39 // catches units that run /usr/bin/snap (with args), or things in /usr/lib/snapd/ 40 var execStartRe = regexp.MustCompile(`(?m)^ExecStart=(/usr/bin/snap\s+.*|/usr/lib/snapd/.*)$`) 41 42 // snapdToolingMountUnit is the name of the mount unit that makes the 43 const SnapdToolingMountUnit = "usr-lib-snapd.mount" 44 45 func snapdSkipStart(content []byte) bool { 46 return bytes.Contains(content, []byte("X-Snapd-Snap: do-not-start")) 47 } 48 49 // snapdUnitSkipStart returns true for units that should not be started 50 // automatically 51 func snapdUnitSkipStart(unitPath string) (skip bool, err error) { 52 content, err := ioutil.ReadFile(unitPath) 53 if err != nil { 54 if os.IsNotExist(err) { 55 // no point in starting units that do not exist 56 return true, nil 57 } 58 return false, err 59 } 60 return snapdSkipStart(content), nil 61 } 62 63 func writeSnapdToolingMountUnit(sysd systemd.Systemd, prefix string, opts *AddSnapdSnapServicesOptions) error { 64 65 // TODO: the following comment is wrong, we don't need RequiredBy=snapd here? 66 67 // Not using AddMountUnitFile() because we need 68 // "RequiredBy=snapd.service" 69 70 content := []byte(fmt.Sprintf(`[Unit] 71 Description=Make the snapd snap tooling available for the system 72 Before=snapd.service 73 74 [Mount] 75 What=%s/usr/lib/snapd 76 Where=/usr/lib/snapd 77 Type=none 78 Options=bind 79 80 [Install] 81 WantedBy=snapd.service 82 `, prefix)) 83 fullPath := filepath.Join(dirs.SnapServicesDir, SnapdToolingMountUnit) 84 85 err := osutil.EnsureFileState(fullPath, 86 &osutil.MemoryFileState{ 87 Content: content, 88 Mode: 0644, 89 }, 90 ) 91 if err == osutil.ErrSameState { 92 return nil 93 } 94 if err != nil { 95 return err 96 } 97 98 if err := sysd.DaemonReload(); err != nil { 99 return err 100 } 101 102 units := []string{SnapdToolingMountUnit} 103 if err := sysd.EnableNoReload(units); err != nil { 104 return err 105 } 106 107 // meh this is killing snap services that use Requires=<this-unit> because 108 // it doesn't use verbatim systemctl restart, it instead does it with 109 // a systemctl stop and then a systemctl start, which triggers LP #1924805 110 if err := sysd.Restart(units); err != nil { 111 return err 112 } 113 114 return nil 115 } 116 117 func undoSnapdToolingMountUnit(sysd systemd.Systemd) error { 118 mountUnit := "usr-lib-snapd.mount" 119 mountUnitPath := filepath.Join(dirs.SnapServicesDir, mountUnit) 120 121 if !osutil.FileExists(mountUnitPath) { 122 return nil 123 } 124 units := []string{mountUnit} 125 if err := sysd.DisableNoReload(units); err != nil { 126 return err 127 } 128 // XXX: it is ok to stop the mount unit, the failover handler 129 // executes snapd directly from the previous revision of snapd snap or 130 // the core snap, the handler is running directly from the mounted snapd snap 131 if err := sysd.Stop(units); err != nil { 132 return err 133 } 134 return os.Remove(mountUnitPath) 135 } 136 137 type AddSnapdSnapServicesOptions struct { 138 // Preseeding is whether the system is currently being preseeded, in which 139 // case there is not a running systemd for EnsureSnapServicesOptions to 140 // issue commands like systemctl daemon-reload to. 141 Preseeding bool 142 } 143 144 // AddSnapdSnapServices sets up the services based on a given snapd snap in the 145 // system. 146 func AddSnapdSnapServices(s *snap.Info, opts *AddSnapdSnapServicesOptions, inter Interacter) error { 147 if snapType := s.Type(); snapType != snap.TypeSnapd { 148 return fmt.Errorf("internal error: adding explicit snapd services for snap %q type %q is unexpected", s.InstanceName(), snapType) 149 } 150 151 // we never write snapd services on classic 152 if release.OnClassic { 153 return nil 154 } 155 156 if opts == nil { 157 opts = &AddSnapdSnapServicesOptions{} 158 } 159 160 var sysd systemd.Systemd 161 if !opts.Preseeding { 162 sysd = systemd.New(systemd.SystemMode, inter) 163 } else { 164 sysd = systemd.NewEmulationMode("") 165 } 166 167 if err := writeSnapdToolingMountUnit(sysd, s.MountDir(), opts); err != nil { 168 return err 169 } 170 171 serviceUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.service")) 172 if err != nil { 173 return err 174 } 175 socketUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.socket")) 176 if err != nil { 177 return err 178 } 179 timerUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.timer")) 180 if err != nil { 181 return err 182 } 183 units := append(socketUnits, serviceUnits...) 184 units = append(units, timerUnits...) 185 186 snapdUnits := make(map[string]osutil.FileState, len(units)+1) 187 for _, unit := range units { 188 st, err := os.Stat(unit) 189 if err != nil { 190 return err 191 } 192 content, err := ioutil.ReadFile(unit) 193 if err != nil { 194 return err 195 } 196 if execStartRe.Match(content) { 197 content = execStartRe.ReplaceAll(content, []byte(fmt.Sprintf("ExecStart=%s$1", s.MountDir()))) 198 // when the service executes a command from the snapd snap, make 199 // sure the exec path points to the mount dir, and that the 200 // mount happens before the unit is started 201 content = append(content, []byte(fmt.Sprintf("\n[Unit]\nRequiresMountsFor=%s\n", s.MountDir()))...) 202 } 203 204 snapdUnits[filepath.Base(unit)] = &osutil.MemoryFileState{ 205 Content: content, 206 Mode: st.Mode(), 207 } 208 } 209 globs := []string{"snapd.service", "snapd.socket", "snapd.*.service", "snapd.*.timer"} 210 changed, removed, err := osutil.EnsureDirStateGlobs(dirs.SnapServicesDir, globs, snapdUnits) 211 if err != nil { 212 // TODO: uhhhh, what do we do in this case? 213 return err 214 } 215 if (len(changed) + len(removed)) == 0 { 216 // nothing to do 217 return nil 218 } 219 220 // stop all removed units first 221 for _, unit := range removed { 222 serviceUnits := []string{unit} 223 if err := sysd.Stop(serviceUnits); err != nil { 224 logger.Noticef("failed to stop %q: %v", unit, err) 225 } 226 if err := sysd.DisableNoReload(serviceUnits); err != nil { 227 logger.Noticef("failed to disable %q: %v", unit, err) 228 } 229 } 230 231 // daemon-reload so that we get the new services 232 if len(changed) > 0 { 233 if err := sysd.DaemonReload(); err != nil { 234 return err 235 } 236 } 237 238 // enable/start all the new services 239 for _, unit := range changed { 240 // systemd looks at the logical units, even if 'enabled' service 241 // symlink points to /lib/systemd/system location, dropping an 242 // identically named service in /etc overrides the other unit, 243 // therefore it is sufficient to enable the new units only 244 // 245 // Calling sysd.Enable() unconditionally may fail depending on 246 // systemd version, where older versions (eg 229 in 16.04) would 247 // error out unless --force is passed, while new ones remove the 248 // symlink and create a new one. 249 if !opts.Preseeding { 250 enabled, err := sysd.IsEnabled(unit) 251 if err != nil { 252 return err 253 } 254 if enabled { 255 continue 256 } 257 } 258 if err := sysd.EnableNoReload([]string{unit}); err != nil { 259 return err 260 } 261 } 262 263 if !opts.Preseeding { 264 for _, unit := range changed { 265 // Some units (like the snapd.system-shutdown.service) cannot 266 // be started. Others like "snapd.seeded.service" are started 267 // as dependencies of snapd.service. 268 if snapdSkipStart(snapdUnits[unit].(*osutil.MemoryFileState).Content) { 269 continue 270 } 271 // Ensure to only restart if the unit was previously 272 // active. This ensures we DTRT on firstboot and do 273 // not stop e.g. snapd.socket because doing that 274 // would mean that the snapd.seeded.service is also 275 // stopped (independently of snapd.socket being 276 // active) which confuses the boot order (the unit 277 // exists before we are fully seeded). 278 isActive, err := sysd.IsActive(unit) 279 if err != nil { 280 return err 281 } 282 283 serviceUnits := []string{unit} 284 if isActive { 285 // we can never restart the snapd.socket because 286 // this will also bring down snapd itself 287 if unit != "snapd.socket" { 288 if err := sysd.Restart(serviceUnits); err != nil { 289 return err 290 } 291 } 292 } else { 293 if err := sysd.Start(serviceUnits); err != nil { 294 return err 295 } 296 } 297 } 298 } 299 300 // and finally start snapd.service (it will stop by itself and gets 301 // started by systemd then) 302 // Because of the file lock held on the snapstate by the Overlord, the new 303 // snapd will block there until we release it. For this reason, we cannot 304 // start the unit in blocking mode. 305 // TODO: move/share this responsibility with daemon so that we can make the 306 // start blocking again 307 if err := sysd.StartNoBlock([]string{"snapd.service"}); err != nil { 308 return err 309 } 310 if err := sysd.StartNoBlock([]string{"snapd.seeded.service"}); err != nil { 311 return err 312 } 313 // we cannot start snapd.autoimport in blocking mode because 314 // it has a "After=snapd.seeded.service" which means that on 315 // seeding a "systemctl start" that blocks would hang forever 316 // and we deadlock. 317 if err := sysd.StartNoBlock([]string{"snapd.autoimport.service"}); err != nil { 318 return err 319 } 320 321 // Handle the user services 322 if err := writeSnapdUserServicesOnCore(s, inter); err != nil { 323 return err 324 } 325 326 // Handle D-Bus configuration 327 if err := writeSnapdDbusConfigOnCore(s); err != nil { 328 return err 329 } 330 331 if err := writeSnapdDbusActivationOnCore(s); err != nil { 332 return err 333 } 334 335 return nil 336 } 337 338 // undoSnapdUserServicesOnCore attempts to remove services that were deployed in 339 // the filesystem as part of snapd snap installation. This should only be 340 // executed as part of a controlled undo path. 341 func undoSnapdServicesOnCore(s *snap.Info, sysd systemd.Systemd) error { 342 // list service, socket and timer units present in the snapd snap 343 serviceUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.service")) 344 if err != nil { 345 return err 346 } 347 socketUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.socket")) 348 if err != nil { 349 return err 350 } 351 timerUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.timer")) 352 if err != nil { 353 return err 354 } 355 units := append(socketUnits, serviceUnits...) 356 units = append(units, timerUnits...) 357 358 for _, snapdUnit := range units { 359 sysdUnit := filepath.Base(snapdUnit) 360 coreUnit := filepath.Join(dirs.GlobalRootDir, "lib/systemd/system", sysdUnit) 361 writtenUnitPath := filepath.Join(dirs.SnapServicesDir, sysdUnit) 362 if !osutil.FileExists(writtenUnitPath) { 363 continue 364 } 365 existsInCore := osutil.FileExists(coreUnit) 366 367 unit := []string{sysdUnit} 368 if !existsInCore { 369 // new unit that did not exist on core, disable and stop 370 if err := sysd.DisableNoReload(unit); err != nil { 371 logger.Noticef("failed to disable %q: %v", unit, err) 372 } 373 if err := sysd.Stop(unit); err != nil { 374 return err 375 } 376 } 377 if err := os.Remove(writtenUnitPath); err != nil { 378 return err 379 } 380 if !existsInCore { 381 // nothing more to do here 382 continue 383 } 384 385 isEnabled, err := sysd.IsEnabled(sysdUnit) 386 if err != nil { 387 return err 388 } 389 if !isEnabled { 390 if err := sysd.EnableNoReload(unit); err != nil { 391 return err 392 } 393 } 394 395 if sysdUnit == "snapd.socket" { 396 // do not start the socket, snap failover handler will 397 // restart it 398 continue 399 } 400 skipStart, err := snapdUnitSkipStart(coreUnit) 401 if err != nil { 402 return err 403 } 404 if !skipStart { 405 // TODO: consider using sys.Restart() instead of is-active check 406 isActive, err := sysd.IsActive(sysdUnit) 407 if err != nil { 408 return err 409 } 410 if isActive { 411 if err := sysd.Restart(unit); err != nil { 412 return err 413 } 414 } else { 415 if err := sysd.Start(unit); err != nil { 416 return err 417 } 418 } 419 } 420 } 421 return nil 422 } 423 424 func writeSnapdUserServicesOnCore(s *snap.Info, inter Interacter) error { 425 // Ensure /etc/systemd/user exists 426 if err := os.MkdirAll(dirs.SnapUserServicesDir, 0755); err != nil { 427 return err 428 } 429 430 // TODO: use EmulationMode when preseeding (teach EmulationMode about user services)? 431 sysd := systemd.New(systemd.GlobalUserMode, inter) 432 433 serviceUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/lib/systemd/user/*.service")) 434 if err != nil { 435 return err 436 } 437 socketUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/lib/systemd/user/*.socket")) 438 if err != nil { 439 return err 440 } 441 units := append(serviceUnits, socketUnits...) 442 443 snapdUnits := make(map[string]osutil.FileState, len(units)+1) 444 for _, unit := range units { 445 st, err := os.Stat(unit) 446 if err != nil { 447 return err 448 } 449 content, err := ioutil.ReadFile(unit) 450 if err != nil { 451 return err 452 } 453 if execStartRe.Match(content) { 454 content = execStartRe.ReplaceAll(content, []byte(fmt.Sprintf("ExecStart=%s$1", s.MountDir()))) 455 // when the service executes a command from the snapd snap, make 456 // sure the exec path points to the mount dir, and that the 457 // mount happens before the unit is started 458 content = append(content, []byte(fmt.Sprintf("\n[Unit]\nRequiresMountsFor=%s\n", s.MountDir()))...) 459 } 460 461 snapdUnits[filepath.Base(unit)] = &osutil.MemoryFileState{ 462 Content: content, 463 Mode: st.Mode(), 464 } 465 } 466 changed, removed, err := osutil.EnsureDirStateGlobs(dirs.SnapUserServicesDir, []string{"snapd.*.service", "snapd.*.socket"}, snapdUnits) 467 if err != nil { 468 // TODO: uhhhh, what do we do in this case? 469 return err 470 } 471 if (len(changed) + len(removed)) == 0 { 472 // nothing to do 473 return nil 474 } 475 // disable all removed units first 476 for _, unit := range removed { 477 if err := sysd.DisableNoReload([]string{unit}); err != nil { 478 logger.Noticef("failed to disable %q: %v", unit, err) 479 } 480 } 481 482 // enable/start all the new services 483 for _, unit := range changed { 484 units := []string{unit} 485 if err := sysd.DisableNoReload(units); err != nil { 486 logger.Noticef("failed to disable %q: %v", unit, err) 487 } 488 if err := sysd.EnableNoReload(units); err != nil { 489 return err 490 } 491 } 492 493 return nil 494 } 495 496 // undoSnapdUserServicesOnCore attempts to remove user services that were 497 // deployed in the filesystem as part of snapd snap installation. This should 498 // only be executed as part of a controlled undo path. 499 func undoSnapdUserServicesOnCore(s *snap.Info, inter Interacter) error { 500 sysd := systemd.NewUnderRoot(dirs.GlobalRootDir, systemd.GlobalUserMode, inter) 501 502 // list user service and socket units present in the snapd snap 503 serviceUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/lib/systemd/user/*.service")) 504 if err != nil { 505 return err 506 } 507 socketUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/lib/systemd/user/*.socket")) 508 if err != nil { 509 return err 510 } 511 units := append(serviceUnits, socketUnits...) 512 513 for _, srcUnit := range units { 514 unit := filepath.Base(srcUnit) 515 writtenUnitPath := filepath.Join(dirs.SnapUserServicesDir, unit) 516 if !osutil.FileExists(writtenUnitPath) { 517 continue 518 } 519 coreUnit := filepath.Join(dirs.GlobalRootDir, "usr/lib/systemd/user", unit) 520 existsInCore := osutil.FileExists(coreUnit) 521 522 if err := sysd.DisableNoReload([]string{unit}); err != nil { 523 logger.Noticef("failed to disable %q: %v", unit, err) 524 } 525 if err := os.Remove(writtenUnitPath); err != nil { 526 return err 527 } 528 if !existsInCore { 529 // new unit that did not exist on core 530 continue 531 } 532 if err := sysd.EnableNoReload([]string{unit}); err != nil { 533 return err 534 } 535 } 536 return nil 537 } 538 539 func DeriveSnapdDBusConfig(s *snap.Info) (sessionContent, systemContent map[string]osutil.FileState, err error) { 540 sessionConfigs, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/share/dbus-1/session.d/snapd.*.conf")) 541 if err != nil { 542 return nil, nil, err 543 } 544 sessionContent = make(map[string]osutil.FileState, len(sessionConfigs)+1) 545 for _, config := range sessionConfigs { 546 sessionContent[filepath.Base(config)] = &osutil.FileReference{ 547 Path: config, 548 } 549 } 550 551 systemConfigs, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/share/dbus-1/system.d/snapd.*.conf")) 552 if err != nil { 553 return nil, nil, err 554 } 555 systemContent = make(map[string]osutil.FileState, len(systemConfigs)+1) 556 for _, config := range systemConfigs { 557 systemContent[filepath.Base(config)] = &osutil.FileReference{ 558 Path: config, 559 } 560 } 561 562 return sessionContent, systemContent, nil 563 } 564 565 func isReadOnlyFsError(err error) bool { 566 if err == nil { 567 return false 568 } 569 if e, ok := err.(*os.PathError); ok { 570 err = e.Err 571 } 572 if e, ok := err.(syscall.Errno); ok { 573 return e == syscall.EROFS 574 } 575 return false 576 } 577 578 var ensureDirState = osutil.EnsureDirState 579 580 func writeSnapdDbusConfigOnCore(s *snap.Info) error { 581 sessionContent, systemContent, err := DeriveSnapdDBusConfig(s) 582 if err != nil { 583 return err 584 } 585 586 _, _, err = ensureDirState(dirs.SnapDBusSessionPolicyDir, "snapd.*.conf", sessionContent) 587 if err != nil { 588 if isReadOnlyFsError(err) { 589 // If /etc/dbus-1/session.d is read-only (which may be the case on very old core18), then 590 // err is os.PathError with syscall.Errno underneath. Hitting this prevents snapd refresh, 591 // so log the error but carry on. This fixes LP: 1899664. 592 // XXX: ideally we should regenerate session files elsewhere if we fail here (otherwise 593 // this will only happen on future snapd refresh), but realistically this 594 // is not relevant on core18 devices. 595 logger.Noticef("%s appears to be read-only, could not write snapd dbus config files", dirs.SnapDBusSessionPolicyDir) 596 } else { 597 return err 598 } 599 } 600 601 _, _, err = osutil.EnsureDirState(dirs.SnapDBusSystemPolicyDir, "snapd.*.conf", systemContent) 602 if err != nil { 603 return err 604 } 605 606 return nil 607 } 608 609 func undoSnapdDbusConfigOnCore() error { 610 _, _, err := osutil.EnsureDirState(dirs.SnapDBusSystemPolicyDir, "snapd.*.conf", nil) 611 if err != nil { 612 return err 613 } 614 _, _, err = osutil.EnsureDirState(dirs.SnapDBusSessionPolicyDir, "snapd.*.conf", nil) 615 return err 616 } 617 618 var dbusSessionServices = []string{ 619 "io.snapcraft.Launcher.service", 620 "io.snapcraft.Prompt.service", 621 "io.snapcraft.Settings.service", 622 "io.snapcraft.SessionAgent.service", 623 } 624 625 func writeSnapdDbusActivationOnCore(s *snap.Info) error { 626 if err := os.MkdirAll(dirs.SnapDBusSessionServicesDir, 0755); err != nil { 627 return err 628 } 629 630 content := make(map[string]osutil.FileState, len(dbusSessionServices)+1) 631 for _, service := range dbusSessionServices { 632 filePathInSnap := filepath.Join(s.MountDir(), "usr/share/dbus-1/services", service) 633 if !osutil.FileExists(filePathInSnap) { 634 continue 635 } 636 content[service] = &osutil.FileReference{ 637 Path: filePathInSnap, 638 } 639 } 640 641 _, _, err := osutil.EnsureDirStateGlobs(dirs.SnapDBusSessionServicesDir, dbusSessionServices, content) 642 return err 643 } 644 645 func undoSnapdDbusActivationOnCore() error { 646 _, _, err := osutil.EnsureDirStateGlobs(dirs.SnapDBusSessionServicesDir, dbusSessionServices, nil) 647 return err 648 } 649 650 // RemoveSnapdSnapServicesOnCore removes the snapd services generated by a prior 651 // call to AddSnapdSnapServices. The core snap is used as the reference for 652 // restoring the system state, making this undo helper suitable for use when 653 // reverting the first installation of the snapd snap on a core device. 654 func RemoveSnapdSnapServicesOnCore(s *snap.Info, inter Interacter) error { 655 if snapType := s.Type(); snapType != snap.TypeSnapd { 656 return fmt.Errorf("internal error: removing explicit snapd services for snap %q type %q is unexpected", s.InstanceName(), snapType) 657 } 658 659 // snapd services are never written on classic, nothing to remove 660 if release.OnClassic { 661 return nil 662 } 663 664 sysd := systemd.NewUnderRoot(dirs.GlobalRootDir, systemd.SystemMode, inter) 665 666 if err := undoSnapdDbusActivationOnCore(); err != nil { 667 return err 668 } 669 if err := undoSnapdDbusConfigOnCore(); err != nil { 670 return err 671 } 672 if err := undoSnapdServicesOnCore(s, sysd); err != nil { 673 return err 674 } 675 if err := undoSnapdUserServicesOnCore(s, inter); err != nil { 676 return err 677 } 678 if err := undoSnapdToolingMountUnit(sysd); err != nil { 679 return err 680 } 681 // XXX: reload after all operations? 682 return nil 683 }