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