github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/wrappers/services.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2016 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 "context" 25 "fmt" 26 "os" 27 "path/filepath" 28 "sort" 29 "strconv" 30 "strings" 31 "text/template" 32 "time" 33 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/logger" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/osutil/sys" 38 "github.com/snapcore/snapd/progress" 39 "github.com/snapcore/snapd/randutil" 40 "github.com/snapcore/snapd/snap" 41 "github.com/snapcore/snapd/strutil" 42 "github.com/snapcore/snapd/systemd" 43 "github.com/snapcore/snapd/timeout" 44 "github.com/snapcore/snapd/timeutil" 45 "github.com/snapcore/snapd/timings" 46 "github.com/snapcore/snapd/usersession/client" 47 ) 48 49 type interacter interface { 50 Notify(status string) 51 } 52 53 // wait this time between TERM and KILL 54 var killWait = 5 * time.Second 55 56 func serviceStopTimeout(app *snap.AppInfo) time.Duration { 57 tout := app.StopTimeout 58 if tout == 0 { 59 tout = timeout.DefaultTimeout 60 } 61 return time.Duration(tout) 62 } 63 64 func generateSnapServiceFile(app *snap.AppInfo, opts *AddSnapServicesOptions) ([]byte, error) { 65 if err := snap.ValidateApp(app); err != nil { 66 return nil, err 67 } 68 69 return genServiceFile(app, opts), nil 70 } 71 72 func stopUserServices(cli *client.Client, inter interacter, services ...string) error { 73 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout.DefaultTimeout)) 74 defer cancel() 75 failures, err := cli.ServicesStop(ctx, services) 76 for _, f := range failures { 77 inter.Notify(fmt.Sprintf("Could not stop service %q for uid %d: %s", f.Service, f.Uid, f.Error)) 78 } 79 return err 80 } 81 82 func startUserServices(cli *client.Client, inter interacter, services ...string) error { 83 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout.DefaultTimeout)) 84 defer cancel() 85 startFailures, stopFailures, err := cli.ServicesStart(ctx, services) 86 for _, f := range startFailures { 87 inter.Notify(fmt.Sprintf("Could not start service %q for uid %d: %s", f.Service, f.Uid, f.Error)) 88 } 89 for _, f := range stopFailures { 90 inter.Notify(fmt.Sprintf("While trying to stop previously started service %q for uid %d: %s", f.Service, f.Uid, f.Error)) 91 } 92 return err 93 } 94 95 func stopService(sysd systemd.Systemd, app *snap.AppInfo, inter interacter) error { 96 serviceName := app.ServiceName() 97 tout := serviceStopTimeout(app) 98 99 var extraServices []string 100 for _, socket := range app.Sockets { 101 extraServices = append(extraServices, filepath.Base(socket.File())) 102 } 103 if app.Timer != nil { 104 extraServices = append(extraServices, filepath.Base(app.Timer.File())) 105 } 106 107 switch app.DaemonScope { 108 case snap.SystemDaemon: 109 stopErrors := []error{} 110 for _, service := range extraServices { 111 if err := sysd.Stop(service, tout); err != nil { 112 stopErrors = append(stopErrors, err) 113 } 114 } 115 116 if err := sysd.Stop(serviceName, tout); err != nil { 117 if !systemd.IsTimeout(err) { 118 return err 119 } 120 inter.Notify(fmt.Sprintf("%s refused to stop, killing.", serviceName)) 121 // ignore errors for kill; nothing we'd do differently at this point 122 sysd.Kill(serviceName, "TERM", "") 123 time.Sleep(killWait) 124 sysd.Kill(serviceName, "KILL", "") 125 } 126 127 if len(stopErrors) > 0 { 128 return stopErrors[0] 129 } 130 131 case snap.UserDaemon: 132 extraServices = append(extraServices, serviceName) 133 cli := client.New() 134 return stopUserServices(cli, inter, extraServices...) 135 } 136 137 return nil 138 } 139 140 // enableServices enables services specified by apps. On success the returned 141 // disable function can be used to undo all the actions. On error all the 142 // services get disabled automatically (disable is nil). 143 func enableServices(apps []*snap.AppInfo, inter interacter) (disable func(), err error) { 144 var enabled []string 145 var userEnabled []string 146 147 systemSysd := systemd.New(dirs.GlobalRootDir, systemd.SystemMode, inter) 148 userSysd := systemd.New(dirs.GlobalRootDir, systemd.GlobalUserMode, inter) 149 150 disableEnabledServices := func() { 151 for _, srvName := range enabled { 152 if e := systemSysd.Disable(srvName); e != nil { 153 inter.Notify(fmt.Sprintf("While trying to disable previously enabled service %q: %v", srvName, e)) 154 } 155 } 156 for _, s := range userEnabled { 157 if e := userSysd.Disable(s); e != nil { 158 inter.Notify(fmt.Sprintf("while trying to disable %s due to previous failure: %v", s, e)) 159 } 160 } 161 } 162 163 defer func() { 164 if err != nil { 165 disableEnabledServices() 166 } 167 }() 168 169 for _, app := range apps { 170 var sysd systemd.Systemd 171 switch app.DaemonScope { 172 case snap.SystemDaemon: 173 sysd = systemSysd 174 case snap.UserDaemon: 175 sysd = userSysd 176 } 177 178 svcName := app.ServiceName() 179 180 switch app.DaemonScope { 181 case snap.SystemDaemon: 182 if err = sysd.Enable(svcName); err != nil { 183 return nil, err 184 185 } 186 enabled = append(enabled, svcName) 187 case snap.UserDaemon: 188 if err = userSysd.Enable(svcName); err != nil { 189 return nil, err 190 } 191 userEnabled = append(userEnabled, svcName) 192 } 193 } 194 195 return disableEnabledServices, nil 196 } 197 198 // StartServicesFlags carries extra flags for StartServices. 199 type StartServicesFlags struct { 200 Enable bool 201 } 202 203 // StartServices starts service units for the applications from the snap which 204 // are services. Service units will be started in the order provided by the 205 // caller. 206 func StartServices(apps []*snap.AppInfo, disabledSvcs []string, flags *StartServicesFlags, inter interacter, tm timings.Measurer) (err error) { 207 systemSysd := systemd.New(dirs.GlobalRootDir, systemd.SystemMode, inter) 208 userSysd := systemd.New(dirs.GlobalRootDir, systemd.GlobalUserMode, inter) 209 cli := client.New() 210 211 systemServices := make([]string, 0, len(apps)) 212 userServices := make([]string, 0, len(apps)) 213 for _, app := range apps { 214 // they're *supposed* to be all services, but checking doesn't hurt 215 if !app.IsService() { 216 continue 217 } 218 219 var sysd systemd.Systemd 220 switch app.DaemonScope { 221 case snap.SystemDaemon: 222 sysd = systemSysd 223 case snap.UserDaemon: 224 sysd = userSysd 225 } 226 227 defer func(app *snap.AppInfo) { 228 if err == nil { 229 return 230 } 231 if e := stopService(sysd, app, inter); e != nil { 232 inter.Notify(fmt.Sprintf("While trying to stop previously started service %q: %v", app.ServiceName(), e)) 233 } 234 for _, socket := range app.Sockets { 235 socketService := filepath.Base(socket.File()) 236 if e := sysd.Disable(socketService); e != nil { 237 inter.Notify(fmt.Sprintf("While trying to disable previously enabled socket service %q: %v", socketService, e)) 238 } 239 } 240 if app.Timer != nil { 241 timerService := filepath.Base(app.Timer.File()) 242 if e := sysd.Disable(timerService); e != nil { 243 inter.Notify(fmt.Sprintf("While trying to disable previously enabled timer service %q: %v", timerService, e)) 244 } 245 } 246 }(app) 247 248 if len(app.Sockets) == 0 && app.Timer == nil { 249 // check if the service is disabled, if so don't start it up 250 // this could happen for example if the service was disabled in 251 // the install hook by snapctl or if the service was disabled in 252 // the previous installation 253 isEnabled, err := sysd.IsEnabled(app.ServiceName()) 254 if err != nil { 255 return err 256 } 257 if isEnabled { 258 switch app.DaemonScope { 259 case snap.SystemDaemon: 260 systemServices = append(systemServices, app.ServiceName()) 261 case snap.UserDaemon: 262 userServices = append(userServices, app.ServiceName()) 263 } 264 } 265 } 266 267 for _, socket := range app.Sockets { 268 socketService := filepath.Base(socket.File()) 269 // enable the socket 270 if err := sysd.Enable(socketService); err != nil { 271 return err 272 } 273 274 switch app.DaemonScope { 275 case snap.SystemDaemon: 276 timings.Run(tm, "start-system-socket-service", fmt.Sprintf("start system socket service %q", socketService), func(nested timings.Measurer) { 277 err = sysd.Start(socketService) 278 }) 279 case snap.UserDaemon: 280 timings.Run(tm, "start-user-socket-service", fmt.Sprintf("start user socket service %q", socketService), func(nested timings.Measurer) { 281 err = startUserServices(cli, inter, socketService) 282 }) 283 } 284 if err != nil { 285 return err 286 } 287 } 288 289 if app.Timer != nil { 290 timerService := filepath.Base(app.Timer.File()) 291 // enable the timer 292 if err := sysd.Enable(timerService); err != nil { 293 return err 294 } 295 296 switch app.DaemonScope { 297 case snap.SystemDaemon: 298 timings.Run(tm, "start-system-timer-service", fmt.Sprintf("start system timer service %q", timerService), func(nested timings.Measurer) { 299 err = sysd.Start(timerService) 300 }) 301 case snap.UserDaemon: 302 timings.Run(tm, "start-user-timer-service", fmt.Sprintf("start user timer service %q", timerService), func(nested timings.Measurer) { 303 err = startUserServices(cli, inter, timerService) 304 }) 305 } 306 if err != nil { 307 return err 308 } 309 } 310 } 311 312 for _, srv := range systemServices { 313 // starting all services at once does not create a single 314 // transaction, but instead spawns multiple jobs, make sure the 315 // services started in the original order by bring them up one 316 // by one, see: 317 // https://github.com/systemd/systemd/issues/8102 318 // https://lists.freedesktop.org/archives/systemd-devel/2018-January/040152.html 319 timings.Run(tm, "start-service", fmt.Sprintf("start service %q", srv), func(nested timings.Measurer) { 320 err = systemSysd.Start(srv) 321 }) 322 if err != nil { 323 // cleanup was set up by iterating over apps 324 return err 325 } 326 } 327 if len(userServices) != 0 { 328 timings.Run(tm, "start-user-services", "start user services", func(nested timings.Measurer) { 329 err = startUserServices(cli, inter, userServices...) 330 }) 331 if err != nil { 332 return err 333 } 334 } 335 336 return nil 337 } 338 339 func userDaemonReload() error { 340 cli := client.New() 341 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout.DefaultTimeout)) 342 defer cancel() 343 return cli.ServicesDaemonReload(ctx) 344 } 345 346 type AddSnapServicesOptions struct { 347 Preseeding bool 348 VitalityRank int 349 } 350 351 // AddSnapServices adds service units for the applications from the snap which are services. 352 func AddSnapServices(s *snap.Info, disabledSvcs []string, opts *AddSnapServicesOptions, inter interacter) (err error) { 353 if s.Type() == snap.TypeSnapd { 354 return fmt.Errorf("internal error: adding explicit services for snapd snap is unexpected") 355 } 356 357 if opts == nil { 358 opts = &AddSnapServicesOptions{} 359 } 360 361 // check if any previously disabled services are now no longer services and 362 // log messages about that 363 for _, svc := range disabledSvcs { 364 app, ok := s.Apps[svc] 365 if !ok { 366 logger.Noticef("previously disabled service %s no longer exists", svc) 367 } else if !app.IsService() { 368 logger.Noticef("previously disabled service %s is now an app and not a service", svc) 369 } 370 } 371 372 // TODO: remove once services get enabled on start and not when created. 373 preseeding := opts.Preseeding 374 375 sysd := systemd.New(dirs.GlobalRootDir, systemd.SystemMode, inter) 376 var written []string 377 var writtenSystem, writtenUser bool 378 var disableEnabledServices func() 379 380 defer func() { 381 if err == nil { 382 return 383 } 384 if disableEnabledServices != nil { 385 disableEnabledServices() 386 } 387 for _, s := range written { 388 if e := os.Remove(s); e != nil { 389 inter.Notify(fmt.Sprintf("while trying to remove %s due to previous failure: %v", s, e)) 390 } 391 } 392 if writtenSystem && !preseeding { 393 if e := sysd.DaemonReload(); e != nil { 394 inter.Notify(fmt.Sprintf("while trying to perform systemd daemon-reload due to previous failure: %v", e)) 395 } 396 } 397 if writtenUser && !preseeding { 398 if e := userDaemonReload(); e != nil { 399 inter.Notify(fmt.Sprintf("while trying to perform user systemd daemon-reload due to previous failure: %v", e)) 400 } 401 } 402 }() 403 404 var toEnable []*snap.AppInfo 405 406 // create services first; this doesn't trigger systemd 407 for _, app := range s.Apps { 408 if !app.IsService() { 409 continue 410 } 411 // Generate service file 412 content, err := generateSnapServiceFile(app, opts) 413 if err != nil { 414 return err 415 } 416 svcFilePath := app.ServiceFile() 417 os.MkdirAll(filepath.Dir(svcFilePath), 0755) 418 if err := osutil.AtomicWriteFile(svcFilePath, content, 0644, 0); err != nil { 419 return err 420 } 421 written = append(written, svcFilePath) 422 switch app.DaemonScope { 423 case snap.SystemDaemon: 424 writtenSystem = true 425 case snap.UserDaemon: 426 writtenUser = true 427 } 428 429 // Generate systemd .socket files if needed 430 socketFiles, err := generateSnapSocketFiles(app) 431 if err != nil { 432 return err 433 } 434 for path, content := range *socketFiles { 435 os.MkdirAll(filepath.Dir(path), 0755) 436 if err := osutil.AtomicWriteFile(path, content, 0644, 0); err != nil { 437 return err 438 } 439 written = append(written, path) 440 } 441 442 if app.Timer != nil { 443 content, err := generateSnapTimerFile(app) 444 if err != nil { 445 return err 446 } 447 path := app.Timer.File() 448 os.MkdirAll(filepath.Dir(path), 0755) 449 if err := osutil.AtomicWriteFile(path, content, 0644, 0); err != nil { 450 return err 451 } 452 written = append(written, path) 453 } 454 455 if app.Timer != nil || len(app.Sockets) != 0 { 456 // service is socket or timer activated, not during the 457 // boot 458 continue 459 } 460 // XXX: this may become quadratic, optimize. 461 // When preseeding services get enabled in doMarkPresseeded instead at 462 // the moment. 463 if strutil.ListContains(disabledSvcs, app.Name) || preseeding { 464 continue 465 } 466 467 toEnable = append(toEnable, app) 468 } 469 470 disableEnabledServices, err = enableServices(toEnable, inter) 471 if err != nil { 472 return err 473 } 474 475 if !preseeding { 476 if writtenSystem { 477 if err := sysd.DaemonReload(); err != nil { 478 return err 479 } 480 } 481 if writtenUser { 482 if err := userDaemonReload(); err != nil { 483 return err 484 } 485 } 486 } 487 488 return nil 489 } 490 491 // EnableSnapServices enables all services of the snap; the main use case for this is 492 // the first boot of a pre-seeded image with service files already in place but not enabled. 493 // XXX: it should go away once services are fixed and enabled on start. 494 func EnableSnapServices(s *snap.Info, inter interacter) (err error) { 495 sysd := systemd.New(dirs.GlobalRootDir, systemd.SystemMode, inter) 496 for _, app := range s.Apps { 497 if app.IsService() { 498 svcName := app.ServiceName() 499 if err := sysd.Enable(svcName); err != nil { 500 return err 501 } 502 } 503 } 504 return nil 505 } 506 507 // StopServicesFlags carries extra flags for StopServices. 508 type StopServicesFlags struct { 509 Disable bool 510 } 511 512 // StopServices stops and optionally disables service units for the applications 513 // from the snap which are services. 514 func StopServices(apps []*snap.AppInfo, flags *StopServicesFlags, reason snap.ServiceStopReason, inter interacter, tm timings.Measurer) error { 515 sysd := systemd.New(dirs.GlobalRootDir, systemd.SystemMode, inter) 516 if flags == nil { 517 flags = &StopServicesFlags{} 518 } 519 520 if reason != snap.StopReasonOther { 521 logger.Debugf("StopServices called for %q, reason: %v", apps, reason) 522 } else { 523 logger.Debugf("StopServices called for %q", apps) 524 } 525 for _, app := range apps { 526 // Handle the case where service file doesn't exist and don't try to stop it as it will fail. 527 // This can happen with snap try when snap.yaml is modified on the fly and a daemon line is added. 528 if !app.IsService() || !osutil.FileExists(app.ServiceFile()) { 529 continue 530 } 531 // Skip stop on refresh when refresh mode is set to something 532 // other than "restart" (or "" which is the same) 533 if reason == snap.StopReasonRefresh { 534 logger.Debugf(" %s refresh-mode: %v", app.Name, app.StopMode) 535 switch app.RefreshMode { 536 case "endure": 537 // skip this service 538 continue 539 } 540 } 541 542 var err error 543 timings.Run(tm, "stop-service", fmt.Sprintf("stop service %q", app.ServiceName()), func(nested timings.Measurer) { 544 err = stopService(sysd, app, inter) 545 if err == nil && flags.Disable { 546 err = sysd.Disable(app.ServiceName()) 547 } 548 }) 549 if err != nil { 550 return err 551 } 552 553 // ensure the service is really stopped on remove regardless 554 // of stop-mode 555 if reason == snap.StopReasonRemove && !app.StopMode.KillAll() && app.DaemonScope == snap.SystemDaemon { 556 // FIXME: make this smarter and avoid the killWait 557 // delay if not needed (i.e. if all processes 558 // have died) 559 sysd.Kill(app.ServiceName(), "TERM", "all") 560 time.Sleep(killWait) 561 sysd.Kill(app.ServiceName(), "KILL", "") 562 } 563 } 564 return nil 565 } 566 567 // ServicesEnableState returns a map of service names from the given snap, 568 // together with their enable/disable status. 569 func ServicesEnableState(s *snap.Info, inter interacter) (map[string]bool, error) { 570 sysd := systemd.New(dirs.GlobalRootDir, systemd.SystemMode, inter) 571 572 // loop over all services in the snap, querying systemd for the current 573 // systemd state of the snaps 574 snapSvcsState := make(map[string]bool, len(s.Apps)) 575 for name, app := range s.Apps { 576 if !app.IsService() { 577 continue 578 } 579 // FIXME: handle user daemons 580 if app.DaemonScope != snap.SystemDaemon { 581 continue 582 } 583 state, err := sysd.IsEnabled(app.ServiceName()) 584 if err != nil { 585 return nil, err 586 } 587 snapSvcsState[name] = state 588 } 589 return snapSvcsState, nil 590 } 591 592 // RemoveSnapServices disables and removes service units for the applications 593 // from the snap which are services. The optional flag indicates whether 594 // services are removed as part of undoing of first install of a given snap. 595 func RemoveSnapServices(s *snap.Info, inter interacter) error { 596 if s.Type() == snap.TypeSnapd { 597 return fmt.Errorf("internal error: removing explicit services for snapd snap is unexpected") 598 } 599 systemSysd := systemd.New(dirs.GlobalRootDir, systemd.SystemMode, inter) 600 userSysd := systemd.New(dirs.GlobalRootDir, systemd.GlobalUserMode, inter) 601 var removedSystem, removedUser bool 602 603 for _, app := range s.Apps { 604 if !app.IsService() || !osutil.FileExists(app.ServiceFile()) { 605 continue 606 } 607 608 var sysd systemd.Systemd 609 switch app.DaemonScope { 610 case snap.SystemDaemon: 611 sysd = systemSysd 612 removedSystem = true 613 case snap.UserDaemon: 614 sysd = userSysd 615 removedUser = true 616 } 617 serviceName := filepath.Base(app.ServiceFile()) 618 619 for _, socket := range app.Sockets { 620 path := socket.File() 621 socketServiceName := filepath.Base(path) 622 if err := sysd.Disable(socketServiceName); err != nil { 623 return err 624 } 625 626 if err := os.Remove(path); err != nil && !os.IsNotExist(err) { 627 logger.Noticef("Failed to remove socket file %q for %q: %v", path, serviceName, err) 628 } 629 } 630 631 if app.Timer != nil { 632 path := app.Timer.File() 633 634 timerName := filepath.Base(path) 635 if err := sysd.Disable(timerName); err != nil { 636 return err 637 } 638 639 if err := os.Remove(path); err != nil && !os.IsNotExist(err) { 640 logger.Noticef("Failed to remove timer file %q for %q: %v", path, serviceName, err) 641 } 642 } 643 644 if err := sysd.Disable(serviceName); err != nil { 645 return err 646 } 647 648 if err := os.Remove(app.ServiceFile()); err != nil && !os.IsNotExist(err) { 649 logger.Noticef("Failed to remove service file for %q: %v", serviceName, err) 650 } 651 652 } 653 654 // only reload if we actually had services 655 if removedSystem { 656 if err := systemSysd.DaemonReload(); err != nil { 657 return err 658 } 659 } 660 if removedUser { 661 if err := userDaemonReload(); err != nil { 662 return err 663 } 664 } 665 666 return nil 667 } 668 669 func genServiceNames(snap *snap.Info, appNames []string) []string { 670 names := make([]string, 0, len(appNames)) 671 672 for _, name := range appNames { 673 if app := snap.Apps[name]; app != nil { 674 names = append(names, app.ServiceName()) 675 } 676 } 677 return names 678 } 679 680 func genServiceFile(appInfo *snap.AppInfo, opts *AddSnapServicesOptions) []byte { 681 if opts == nil { 682 opts = &AddSnapServicesOptions{} 683 } 684 685 serviceTemplate := `[Unit] 686 # Auto-generated, DO NOT EDIT 687 Description=Service for snap application {{.App.Snap.InstanceName}}.{{.App.Name}} 688 {{- if .MountUnit }} 689 Requires={{.MountUnit}} 690 {{- end }} 691 {{- if .PrerequisiteTarget}} 692 Wants={{.PrerequisiteTarget}} 693 {{- end}} 694 {{- if .After}} 695 After={{ stringsJoin .After " " }} 696 {{- end}} 697 {{- if .Before}} 698 Before={{ stringsJoin .Before " "}} 699 {{- end}} 700 X-Snappy=yes 701 702 [Service] 703 EnvironmentFile=-/etc/environment 704 ExecStart={{.App.LauncherCommand}} 705 SyslogIdentifier={{.App.Snap.InstanceName}}.{{.App.Name}} 706 Restart={{.Restart}} 707 {{- if .App.RestartDelay}} 708 RestartSec={{.App.RestartDelay.Seconds}} 709 {{- end}} 710 WorkingDirectory={{.WorkingDir}} 711 {{- if .App.StopCommand}} 712 ExecStop={{.App.LauncherStopCommand}} 713 {{- end}} 714 {{- if .App.ReloadCommand}} 715 ExecReload={{.App.LauncherReloadCommand}} 716 {{- end}} 717 {{- if .App.PostStopCommand}} 718 ExecStopPost={{.App.LauncherPostStopCommand}} 719 {{- end}} 720 {{- if .StopTimeout}} 721 TimeoutStopSec={{.StopTimeout.Seconds}} 722 {{- end}} 723 {{- if .StartTimeout}} 724 TimeoutStartSec={{.StartTimeout.Seconds}} 725 {{- end}} 726 Type={{.App.Daemon}} 727 {{- if .Remain}} 728 RemainAfterExit={{.Remain}} 729 {{- end}} 730 {{- if .App.BusName}} 731 BusName={{.App.BusName}} 732 {{- end}} 733 {{- if .App.WatchdogTimeout}} 734 WatchdogSec={{.App.WatchdogTimeout.Seconds}} 735 {{- end}} 736 {{- if .KillMode}} 737 KillMode={{.KillMode}} 738 {{- end}} 739 {{- if .KillSignal}} 740 KillSignal={{.KillSignal}} 741 {{- end}} 742 {{- if .OOMAdjustScore }} 743 OOMScoreAdjust={{.OOMAdjustScore}} 744 {{- end}} 745 {{- if not .App.Sockets}} 746 747 [Install] 748 WantedBy={{.ServicesTarget}} 749 {{- end}} 750 ` 751 var templateOut bytes.Buffer 752 tmpl := template.New("service-wrapper") 753 tmpl.Funcs(template.FuncMap{ 754 "stringsJoin": strings.Join, 755 }) 756 t := template.Must(tmpl.Parse(serviceTemplate)) 757 758 restartCond := appInfo.RestartCond.String() 759 if restartCond == "" { 760 restartCond = snap.RestartOnFailure.String() 761 } 762 763 // use score -900+vitalityRank, where vitalityRank starts at 1 764 // and considering snapd itself has OOMScoreAdjust=-900 765 const baseOOMAdjustScore = -900 766 var oomAdjustScore int 767 if opts.VitalityRank > 0 { 768 oomAdjustScore = baseOOMAdjustScore + opts.VitalityRank 769 } 770 771 var remain string 772 if appInfo.Daemon == "oneshot" { 773 // any restart condition other than "no" is invalid for oneshot daemons 774 restartCond = "no" 775 // If StopExec is present for a oneshot service than we also need 776 // RemainAfterExit=yes 777 if appInfo.StopCommand != "" { 778 remain = "yes" 779 } 780 } 781 var killMode string 782 if !appInfo.StopMode.KillAll() { 783 killMode = "process" 784 } 785 786 wrapperData := struct { 787 App *snap.AppInfo 788 789 Restart string 790 WorkingDir string 791 StopTimeout time.Duration 792 StartTimeout time.Duration 793 ServicesTarget string 794 PrerequisiteTarget string 795 MountUnit string 796 Remain string 797 KillMode string 798 KillSignal string 799 OOMAdjustScore int 800 Before []string 801 After []string 802 803 Home string 804 EnvVars string 805 }{ 806 App: appInfo, 807 808 Restart: restartCond, 809 StopTimeout: serviceStopTimeout(appInfo), 810 StartTimeout: time.Duration(appInfo.StartTimeout), 811 Remain: remain, 812 KillMode: killMode, 813 KillSignal: appInfo.StopMode.KillSignal(), 814 OOMAdjustScore: oomAdjustScore, 815 816 Before: genServiceNames(appInfo.Snap, appInfo.Before), 817 After: genServiceNames(appInfo.Snap, appInfo.After), 818 819 // systemd runs as PID 1 so %h will not work. 820 Home: "/root", 821 } 822 switch appInfo.DaemonScope { 823 case snap.SystemDaemon: 824 wrapperData.ServicesTarget = systemd.ServicesTarget 825 wrapperData.PrerequisiteTarget = systemd.PrerequisiteTarget 826 wrapperData.MountUnit = filepath.Base(systemd.MountUnitPath(appInfo.Snap.MountDir())) 827 wrapperData.WorkingDir = appInfo.Snap.DataDir() 828 wrapperData.After = append(wrapperData.After, "snapd.apparmor.service") 829 case snap.UserDaemon: 830 wrapperData.ServicesTarget = systemd.UserServicesTarget 831 // FIXME: ideally use UserDataDir("%h"), but then the 832 // unit fails if the directory doesn't exist. 833 wrapperData.WorkingDir = appInfo.Snap.DataDir() 834 default: 835 panic("unknown snap.DaemonScope") 836 } 837 838 // Add extra "After" targets 839 if wrapperData.PrerequisiteTarget != "" { 840 wrapperData.After = append([]string{wrapperData.PrerequisiteTarget}, wrapperData.After...) 841 } 842 if wrapperData.MountUnit != "" { 843 wrapperData.After = append([]string{wrapperData.MountUnit}, wrapperData.After...) 844 } 845 846 if err := t.Execute(&templateOut, wrapperData); err != nil { 847 // this can never happen, except we forget a variable 848 logger.Panicf("Unable to execute template: %v", err) 849 } 850 851 return templateOut.Bytes() 852 } 853 854 func genServiceSocketFile(appInfo *snap.AppInfo, socketName string) []byte { 855 socketTemplate := `[Unit] 856 # Auto-generated, DO NOT EDIT 857 Description=Socket {{.SocketName}} for snap application {{.App.Snap.InstanceName}}.{{.App.Name}} 858 {{- if .MountUnit}} 859 Requires={{.MountUnit}} 860 After={{.MountUnit}} 861 {{- end}} 862 X-Snappy=yes 863 864 [Socket] 865 Service={{.ServiceFileName}} 866 FileDescriptorName={{.SocketInfo.Name}} 867 ListenStream={{.ListenStream}} 868 {{- if .SocketInfo.SocketMode}} 869 SocketMode={{.SocketInfo.SocketMode | printf "%04o"}} 870 {{- end}} 871 872 [Install] 873 WantedBy={{.SocketsTarget}} 874 ` 875 var templateOut bytes.Buffer 876 t := template.Must(template.New("socket-wrapper").Parse(socketTemplate)) 877 878 socket := appInfo.Sockets[socketName] 879 listenStream := renderListenStream(socket) 880 wrapperData := struct { 881 App *snap.AppInfo 882 ServiceFileName string 883 SocketsTarget string 884 MountUnit string 885 SocketName string 886 SocketInfo *snap.SocketInfo 887 ListenStream string 888 }{ 889 App: appInfo, 890 ServiceFileName: filepath.Base(appInfo.ServiceFile()), 891 SocketsTarget: systemd.SocketsTarget, 892 SocketName: socketName, 893 SocketInfo: socket, 894 ListenStream: listenStream, 895 } 896 switch appInfo.DaemonScope { 897 case snap.SystemDaemon: 898 wrapperData.MountUnit = filepath.Base(systemd.MountUnitPath(appInfo.Snap.MountDir())) 899 case snap.UserDaemon: 900 // nothing 901 default: 902 panic("unknown snap.DaemonScope") 903 } 904 905 if err := t.Execute(&templateOut, wrapperData); err != nil { 906 // this can never happen, except we forget a variable 907 logger.Panicf("Unable to execute template: %v", err) 908 } 909 910 return templateOut.Bytes() 911 } 912 913 func generateSnapSocketFiles(app *snap.AppInfo) (*map[string][]byte, error) { 914 if err := snap.ValidateApp(app); err != nil { 915 return nil, err 916 } 917 918 socketFiles := make(map[string][]byte) 919 for name, socket := range app.Sockets { 920 socketFiles[socket.File()] = genServiceSocketFile(app, name) 921 } 922 return &socketFiles, nil 923 } 924 925 func renderListenStream(socket *snap.SocketInfo) string { 926 s := socket.App.Snap 927 listenStream := socket.ListenStream 928 switch socket.App.DaemonScope { 929 case snap.SystemDaemon: 930 listenStream = strings.Replace(listenStream, "$SNAP_DATA", s.DataDir(), -1) 931 // TODO: when we support User/Group in the generated 932 // systemd unit, adjust this accordingly 933 serviceUserUid := sys.UserID(0) 934 runtimeDir := s.UserXdgRuntimeDir(serviceUserUid) 935 listenStream = strings.Replace(listenStream, "$XDG_RUNTIME_DIR", runtimeDir, -1) 936 listenStream = strings.Replace(listenStream, "$SNAP_COMMON", s.CommonDataDir(), -1) 937 case snap.UserDaemon: 938 listenStream = strings.Replace(listenStream, "$SNAP_USER_DATA", s.UserDataDir("%h"), -1) 939 listenStream = strings.Replace(listenStream, "$SNAP_USER_COMMON", s.UserCommonDataDir("%h"), -1) 940 // FIXME: find some way to share code with snap.UserXdgRuntimeDir() 941 listenStream = strings.Replace(listenStream, "$XDG_RUNTIME_DIR", fmt.Sprintf("%%t/snap.%s", s.InstanceName()), -1) 942 default: 943 panic("unknown snap.DaemonScope") 944 } 945 return listenStream 946 } 947 948 func generateSnapTimerFile(app *snap.AppInfo) ([]byte, error) { 949 timerTemplate := `[Unit] 950 # Auto-generated, DO NOT EDIT 951 Description=Timer {{.TimerName}} for snap application {{.App.Snap.InstanceName}}.{{.App.Name}} 952 {{- if .MountUnit}} 953 Requires={{.MountUnit}} 954 After={{.MountUnit}} 955 {{- end}} 956 X-Snappy=yes 957 958 [Timer] 959 Unit={{.ServiceFileName}} 960 {{ range .Schedules }}OnCalendar={{ . }} 961 {{ end }} 962 [Install] 963 WantedBy={{.TimersTarget}} 964 ` 965 var templateOut bytes.Buffer 966 t := template.Must(template.New("timer-wrapper").Parse(timerTemplate)) 967 968 timerSchedule, err := timeutil.ParseSchedule(app.Timer.Timer) 969 if err != nil { 970 return nil, err 971 } 972 973 schedules := generateOnCalendarSchedules(timerSchedule) 974 975 wrapperData := struct { 976 App *snap.AppInfo 977 ServiceFileName string 978 TimersTarget string 979 TimerName string 980 MountUnit string 981 Schedules []string 982 }{ 983 App: app, 984 ServiceFileName: filepath.Base(app.ServiceFile()), 985 TimersTarget: systemd.TimersTarget, 986 TimerName: app.Name, 987 Schedules: schedules, 988 } 989 switch app.DaemonScope { 990 case snap.SystemDaemon: 991 wrapperData.MountUnit = filepath.Base(systemd.MountUnitPath(app.Snap.MountDir())) 992 case snap.UserDaemon: 993 // nothing 994 default: 995 panic("unknown snap.DaemonScope") 996 } 997 998 if err := t.Execute(&templateOut, wrapperData); err != nil { 999 // this can never happen, except we forget a variable 1000 logger.Panicf("Unable to execute template: %v", err) 1001 } 1002 1003 return templateOut.Bytes(), nil 1004 } 1005 1006 func makeAbbrevWeekdays(start time.Weekday, end time.Weekday) []string { 1007 out := make([]string, 0, 7) 1008 for w := start; w%7 != (end + 1); w++ { 1009 out = append(out, time.Weekday(w % 7).String()[0:3]) 1010 } 1011 return out 1012 } 1013 1014 // daysRange generates a string representing a continuous range between given 1015 // day numbers, which due to compatiblilty with old systemd version uses a 1016 // verbose syntax of x,y,z instead of x..z 1017 func daysRange(start, end uint) string { 1018 var buf bytes.Buffer 1019 for i := start; i <= end; i++ { 1020 buf.WriteString(strconv.FormatInt(int64(i), 10)) 1021 if i < end { 1022 buf.WriteRune(',') 1023 } 1024 } 1025 return buf.String() 1026 } 1027 1028 // generateOnCalendarSchedules converts a schedule into OnCalendar schedules 1029 // suitable for use in systemd *.timer units using systemd.time(7) 1030 // https://www.freedesktop.org/software/systemd/man/systemd.time.html 1031 // XXX: old systemd versions do not support x..y ranges 1032 func generateOnCalendarSchedules(schedule []*timeutil.Schedule) []string { 1033 calendarEvents := make([]string, 0, len(schedule)) 1034 for _, sched := range schedule { 1035 days := make([]string, 0, len(sched.WeekSpans)) 1036 for _, week := range sched.WeekSpans { 1037 abbrev := strings.Join(makeAbbrevWeekdays(week.Start.Weekday, week.End.Weekday), ",") 1038 1039 if week.Start.Pos == timeutil.EveryWeek && week.End.Pos == timeutil.EveryWeek { 1040 // eg: mon, mon-fri, fri-mon 1041 days = append(days, fmt.Sprintf("%s *-*-*", abbrev)) 1042 continue 1043 } 1044 // examples: 1045 // mon1 - Mon *-*-1..7 (Monday during the first 7 days) 1046 // fri1 - Fri *-*-1..7 (Friday during the first 7 days) 1047 1048 // entries below will make systemd timer expire more 1049 // frequently than the schedule suggests, however snap 1050 // runner evaluates current time and gates the actual 1051 // action 1052 // 1053 // mon1-tue - *-*-1..7 *-*-8 (anchored at first 1054 // Monday; Monday happens during the 7 days, 1055 // Tuesday can possibly happen on the 8th day if 1056 // the month started on Tuesday) 1057 // 1058 // mon-tue1 - *-*~1 *-*-1..7 (anchored at first 1059 // Tuesday; matching Monday can happen on the 1060 // last day of previous month if Tuesday is the 1061 // 1st) 1062 // 1063 // mon5-tue - *-*~1..7 *-*-1 (anchored at last 1064 // Monday, the matching Tuesday can still happen 1065 // within the last 7 days, or on the 1st of the 1066 // next month) 1067 // 1068 // fri4-mon - *-*-22-31 *-*-1..7 (anchored at 4th 1069 // Friday, can span onto the next month, extreme case in 1070 // February when 28th is Friday) 1071 // 1072 // XXX: since old versions of systemd, eg. 229 available 1073 // in 16.04 does not support x..y ranges, days need to 1074 // be enumerated like so: 1075 // Mon *-*-1..7 -> Mon *-*-1,2,3,4,5,6,7 1076 // 1077 // XXX: old systemd versions do not support the last n 1078 // days syntax eg, *-*~1, thus the range needs to be 1079 // generated in more verbose way like so: 1080 // Mon *-*~1..7 -> Mon *-*-22,23,24,25,26,27,28,29,30,31 1081 // (22-28 is the last week, but the month can have 1082 // anywhere from 28 to 31 days) 1083 // 1084 startPos := week.Start.Pos 1085 endPos := startPos 1086 if !week.AnchoredAtStart() { 1087 startPos = week.End.Pos 1088 endPos = startPos 1089 } 1090 startDay := (startPos-1)*7 + 1 1091 endDay := (endPos) * 7 1092 1093 if week.IsSingleDay() { 1094 // single day, can use the 'weekday' filter 1095 if startPos == timeutil.LastWeek { 1096 // last week of a month, which can be 1097 // 22-28 in case of February, while 1098 // month can have between 28 and 31 days 1099 days = append(days, 1100 fmt.Sprintf("%s *-*-%s", abbrev, daysRange(22, 31))) 1101 } else { 1102 days = append(days, 1103 fmt.Sprintf("%s *-*-%s", abbrev, daysRange(startDay, endDay))) 1104 } 1105 continue 1106 } 1107 1108 if week.AnchoredAtStart() { 1109 // explore the edge cases first 1110 switch startPos { 1111 case timeutil.LastWeek: 1112 // starts in the last week of the month and 1113 // possibly spans into the first week of the 1114 // next month; 1115 // month can have between 28 and 31 1116 // days 1117 days = append(days, 1118 // trailing 29-31 that are not part of a full week 1119 fmt.Sprintf("*-*-%s", daysRange(29, 31)), 1120 fmt.Sprintf("*-*-%s", daysRange(1, 7))) 1121 case 4: 1122 // a range in the 4th week can span onto 1123 // the next week, which is either 28-31 1124 // or in extreme case (eg. February with 1125 // 28 days) 1-7 of the next month 1126 days = append(days, 1127 // trailing 29-31 that are not part of a full week 1128 fmt.Sprintf("*-*-%s", daysRange(29, 31)), 1129 fmt.Sprintf("*-*-%s", daysRange(1, 7))) 1130 default: 1131 // can possibly spill into the next week 1132 days = append(days, 1133 fmt.Sprintf("*-*-%s", daysRange(startDay+7, endDay+7))) 1134 } 1135 1136 if startDay < 28 { 1137 days = append(days, 1138 fmt.Sprintf("*-*-%s", daysRange(startDay, endDay))) 1139 } else { 1140 // from the end of the month 1141 days = append(days, 1142 fmt.Sprintf("*-*-%s", daysRange(startDay-7, endDay-7))) 1143 } 1144 } else { 1145 switch endPos { 1146 case timeutil.LastWeek: 1147 // month can have between 28 and 31 1148 // days, add trailing 29-31 that are not 1149 // part of a full week 1150 days = append(days, fmt.Sprintf("*-*-%s", daysRange(29, 31))) 1151 case 1: 1152 // possibly spans from the last week of the 1153 // previous month and ends in the first week of 1154 // current month 1155 days = append(days, fmt.Sprintf("*-*-%s", daysRange(22, 31))) 1156 default: 1157 // can possibly spill into the previous week 1158 days = append(days, 1159 fmt.Sprintf("*-*-%s", daysRange(startDay-7, endDay-7))) 1160 } 1161 if endDay < 28 { 1162 days = append(days, 1163 fmt.Sprintf("*-*-%s", daysRange(startDay, endDay))) 1164 } else { 1165 days = append(days, 1166 fmt.Sprintf("*-*-%s", daysRange(startDay-7, endDay-7))) 1167 } 1168 } 1169 } 1170 1171 if len(days) == 0 { 1172 // no weekday spec, meaning the timer runs every day 1173 days = []string{"*-*-*"} 1174 } 1175 1176 startTimes := make([]string, 0, len(sched.ClockSpans)) 1177 for _, clocks := range sched.ClockSpans { 1178 // use expanded clock spans 1179 for _, span := range clocks.ClockSpans() { 1180 when := span.Start 1181 if span.Spread { 1182 length := span.End.Sub(span.Start) 1183 if length < 0 { 1184 // span Start wraps around, so we have '00:00.Sub(23:45)' 1185 length = -length 1186 } 1187 if length > 5*time.Minute { 1188 // replicate what timeutil.Next() does 1189 // and cut some time at the end of the 1190 // window so that events do not happen 1191 // directly one after another 1192 length -= 5 * time.Minute 1193 } 1194 when = when.Add(randutil.RandomDuration(length)) 1195 } 1196 if when.Hour == 24 { 1197 // 24:00 for us means the other end of 1198 // the day, for systemd we need to 1199 // adjust it to the 0-23 hour range 1200 when.Hour -= 24 1201 } 1202 1203 startTimes = append(startTimes, when.String()) 1204 } 1205 } 1206 1207 for _, day := range days { 1208 if len(startTimes) == 0 { 1209 // current schedule is days only 1210 calendarEvents = append(calendarEvents, day) 1211 continue 1212 } 1213 1214 for _, startTime := range startTimes { 1215 calendarEvents = append(calendarEvents, fmt.Sprintf("%s %s", day, startTime)) 1216 } 1217 } 1218 } 1219 return calendarEvents 1220 } 1221 1222 type RestartServicesFlags struct { 1223 Reload bool 1224 } 1225 1226 // Restart or reload services; if reload flag is set then "systemctl reload-or-restart" is attempted. 1227 func RestartServices(svcs []*snap.AppInfo, flags *RestartServicesFlags, inter interacter, tm timings.Measurer) error { 1228 sysd := systemd.New(dirs.GlobalRootDir, systemd.SystemMode, inter) 1229 1230 for _, srv := range svcs { 1231 // they're *supposed* to be all services, but checking doesn't hurt 1232 if !srv.IsService() { 1233 continue 1234 } 1235 1236 var err error 1237 timings.Run(tm, "restart-service", fmt.Sprintf("restart service %q", srv), func(nested timings.Measurer) { 1238 if flags != nil && flags.Reload { 1239 err = sysd.ReloadOrRestart(srv.ServiceName()) 1240 } else { 1241 // note: stop followed by start, not just 'restart' 1242 err = sysd.Restart(srv.ServiceName(), 5*time.Second) 1243 } 1244 }) 1245 if err != nil { 1246 // there is nothing we can do about failed service 1247 return err 1248 } 1249 } 1250 return nil 1251 } 1252 1253 // QueryDisabledServices returns a list of all currently disabled snap services 1254 // in the snap. 1255 func QueryDisabledServices(info *snap.Info, pb progress.Meter) ([]string, error) { 1256 // save the list of services that are in the disabled state before unlinking 1257 // and thus removing the snap services 1258 snapSvcStates, err := ServicesEnableState(info, pb) 1259 if err != nil { 1260 return nil, err 1261 } 1262 1263 disabledSnapSvcs := []string{} 1264 // add all disabled services to the list 1265 for svc, isEnabled := range snapSvcStates { 1266 if !isEnabled { 1267 disabledSnapSvcs = append(disabledSnapSvcs, svc) 1268 } 1269 } 1270 1271 // sort for easier testing 1272 sort.Strings(disabledSnapSvcs) 1273 1274 return disabledSnapSvcs, nil 1275 }