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