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