github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/wrappers/services.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2021 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 "io/ioutil" 27 "os" 28 "path/filepath" 29 "sort" 30 "strconv" 31 "strings" 32 "text/template" 33 "time" 34 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/interfaces" 37 "github.com/snapcore/snapd/logger" 38 "github.com/snapcore/snapd/osutil" 39 "github.com/snapcore/snapd/osutil/sys" 40 "github.com/snapcore/snapd/progress" 41 "github.com/snapcore/snapd/randutil" 42 "github.com/snapcore/snapd/snap" 43 "github.com/snapcore/snapd/snap/quota" 44 "github.com/snapcore/snapd/strutil" 45 "github.com/snapcore/snapd/systemd" 46 "github.com/snapcore/snapd/timeout" 47 "github.com/snapcore/snapd/timeutil" 48 "github.com/snapcore/snapd/timings" 49 "github.com/snapcore/snapd/usersession/client" 50 ) 51 52 type interacter interface { 53 Notify(status string) 54 } 55 56 // wait this time between TERM and KILL 57 var killWait = 5 * time.Second 58 59 func serviceStopTimeout(app *snap.AppInfo) time.Duration { 60 tout := app.StopTimeout 61 if tout == 0 { 62 tout = timeout.DefaultTimeout 63 } 64 return time.Duration(tout) 65 } 66 67 // TODO: this should not accept AddSnapServicesOptions, it should use some other 68 // subset of options, specifically it should not accept Preseeding as an option 69 // here 70 func generateSnapServiceFile(app *snap.AppInfo, opts *AddSnapServicesOptions) ([]byte, error) { 71 if err := snap.ValidateApp(app); err != nil { 72 return nil, err 73 } 74 75 return genServiceFile(app, opts) 76 } 77 78 // generateGroupSliceFile generates a systemd slice unit definition for the 79 // specified quota group. 80 func generateGroupSliceFile(grp *quota.Group) ([]byte, error) { 81 buf := bytes.Buffer{} 82 83 template := `[Unit] 84 Description=Slice for snap quota group %[1]s 85 Before=slices.target 86 X-Snappy=yes 87 88 [Slice] 89 # Always enable memory accounting otherwise the MemoryMax setting does nothing. 90 MemoryAccounting=true 91 MemoryMax=%[2]d 92 # for compatibility with older versions of systemd 93 MemoryLimit=%[2]d 94 95 # Always enable task accounting in order to be able to count the processes/ 96 # threads, etc for a slice 97 TasksAccounting=true 98 ` 99 100 fmt.Fprintf(&buf, template, grp.Name, grp.MemoryLimit) 101 102 return buf.Bytes(), nil 103 } 104 105 func stopUserServices(cli *client.Client, inter interacter, services ...string) error { 106 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout.DefaultTimeout)) 107 defer cancel() 108 failures, err := cli.ServicesStop(ctx, services) 109 for _, f := range failures { 110 inter.Notify(fmt.Sprintf("Could not stop service %q for uid %d: %s", f.Service, f.Uid, f.Error)) 111 } 112 return err 113 } 114 115 func startUserServices(cli *client.Client, inter interacter, services ...string) error { 116 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout.DefaultTimeout)) 117 defer cancel() 118 startFailures, stopFailures, err := cli.ServicesStart(ctx, services) 119 for _, f := range startFailures { 120 inter.Notify(fmt.Sprintf("Could not start service %q for uid %d: %s", f.Service, f.Uid, f.Error)) 121 } 122 for _, f := range stopFailures { 123 inter.Notify(fmt.Sprintf("While trying to stop previously started service %q for uid %d: %s", f.Service, f.Uid, f.Error)) 124 } 125 return err 126 } 127 128 func stopService(sysd systemd.Systemd, app *snap.AppInfo, inter interacter) error { 129 serviceName := app.ServiceName() 130 tout := serviceStopTimeout(app) 131 132 var extraServices []string 133 for _, socket := range app.Sockets { 134 extraServices = append(extraServices, filepath.Base(socket.File())) 135 } 136 if app.Timer != nil { 137 extraServices = append(extraServices, filepath.Base(app.Timer.File())) 138 } 139 140 switch app.DaemonScope { 141 case snap.SystemDaemon: 142 stopErrors := []error{} 143 for _, service := range extraServices { 144 if err := sysd.Stop(service, tout); err != nil { 145 stopErrors = append(stopErrors, err) 146 } 147 } 148 149 if err := sysd.Stop(serviceName, tout); err != nil { 150 if !systemd.IsTimeout(err) { 151 return err 152 } 153 inter.Notify(fmt.Sprintf("%s refused to stop, killing.", serviceName)) 154 // ignore errors for kill; nothing we'd do differently at this point 155 sysd.Kill(serviceName, "TERM", "") 156 time.Sleep(killWait) 157 sysd.Kill(serviceName, "KILL", "") 158 } 159 160 if len(stopErrors) > 0 { 161 return stopErrors[0] 162 } 163 164 case snap.UserDaemon: 165 extraServices = append(extraServices, serviceName) 166 cli := client.New() 167 return stopUserServices(cli, inter, extraServices...) 168 } 169 170 return nil 171 } 172 173 // enableServices enables services specified by apps. On success the returned 174 // disable function can be used to undo all the actions. On error all the 175 // services get disabled automatically (disable is nil). 176 func enableServices(apps []*snap.AppInfo, inter interacter) (disable func(), err error) { 177 var enabled []string 178 var userEnabled []string 179 180 systemSysd := systemd.New(systemd.SystemMode, inter) 181 userSysd := systemd.New(systemd.GlobalUserMode, inter) 182 183 disableEnabledServices := func() { 184 for _, srvName := range enabled { 185 if e := systemSysd.Disable(srvName); e != nil { 186 inter.Notify(fmt.Sprintf("While trying to disable previously enabled service %q: %v", srvName, e)) 187 } 188 } 189 for _, s := range userEnabled { 190 if e := userSysd.Disable(s); e != nil { 191 inter.Notify(fmt.Sprintf("while trying to disable %s due to previous failure: %v", s, e)) 192 } 193 } 194 } 195 196 defer func() { 197 if err != nil { 198 disableEnabledServices() 199 } 200 }() 201 202 for _, app := range apps { 203 var sysd systemd.Systemd 204 switch app.DaemonScope { 205 case snap.SystemDaemon: 206 sysd = systemSysd 207 case snap.UserDaemon: 208 sysd = userSysd 209 } 210 211 svcName := app.ServiceName() 212 213 switch app.DaemonScope { 214 case snap.SystemDaemon: 215 if err = sysd.Enable(svcName); err != nil { 216 return nil, err 217 218 } 219 enabled = append(enabled, svcName) 220 case snap.UserDaemon: 221 if err = userSysd.Enable(svcName); err != nil { 222 return nil, err 223 } 224 userEnabled = append(userEnabled, svcName) 225 } 226 } 227 228 return disableEnabledServices, nil 229 } 230 231 // StartServicesFlags carries extra flags for StartServices. 232 type StartServicesFlags struct { 233 Enable bool 234 } 235 236 // StartServices starts service units for the applications from the snap which 237 // are services. Service units will be started in the order provided by the 238 // caller. 239 func StartServices(apps []*snap.AppInfo, disabledSvcs []string, flags *StartServicesFlags, inter interacter, tm timings.Measurer) (err error) { 240 if flags == nil { 241 flags = &StartServicesFlags{} 242 } 243 244 systemSysd := systemd.New(systemd.SystemMode, inter) 245 userSysd := systemd.New(systemd.GlobalUserMode, inter) 246 cli := client.New() 247 248 var disableEnabledServices func() 249 250 defer func() { 251 if err == nil { 252 return 253 } 254 if disableEnabledServices != nil { 255 disableEnabledServices() 256 } 257 }() 258 259 var toEnable []*snap.AppInfo 260 systemServices := make([]string, 0, len(apps)) 261 userServices := make([]string, 0, len(apps)) 262 263 // gather all non-sockets, non-timers, and non-dbus activated 264 // services to enable first 265 for _, app := range apps { 266 // they're *supposed* to be all services, but checking doesn't hurt 267 if !app.IsService() { 268 continue 269 } 270 // sockets and timers are enabled and started separately (and unconditionally) further down. 271 // dbus activatable services are started on first use. 272 if len(app.Sockets) == 0 && app.Timer == nil && len(app.ActivatesOn) == 0 { 273 if strutil.ListContains(disabledSvcs, app.Name) { 274 continue 275 } 276 svcName := app.ServiceName() 277 switch app.DaemonScope { 278 case snap.SystemDaemon: 279 systemServices = append(systemServices, svcName) 280 case snap.UserDaemon: 281 userServices = append(userServices, svcName) 282 } 283 if flags.Enable { 284 toEnable = append(toEnable, app) 285 } 286 } 287 } 288 289 timings.Run(tm, "enable-services", fmt.Sprintf("enable services %q", toEnable), func(nested timings.Measurer) { 290 disableEnabledServices, err = enableServices(toEnable, inter) 291 }) 292 if err != nil { 293 return err 294 } 295 296 // handle sockets and timers 297 for _, app := range apps { 298 // they're *supposed* to be all services, but checking doesn't hurt 299 if !app.IsService() { 300 continue 301 } 302 303 var sysd systemd.Systemd 304 switch app.DaemonScope { 305 case snap.SystemDaemon: 306 sysd = systemSysd 307 case snap.UserDaemon: 308 sysd = userSysd 309 } 310 311 defer func(app *snap.AppInfo) { 312 if err == nil { 313 return 314 } 315 316 if e := stopService(sysd, app, inter); e != nil { 317 inter.Notify(fmt.Sprintf("While trying to stop previously started service %q: %v", app.ServiceName(), e)) 318 } 319 for _, socket := range app.Sockets { 320 socketService := filepath.Base(socket.File()) 321 if e := sysd.Disable(socketService); e != nil { 322 inter.Notify(fmt.Sprintf("While trying to disable previously enabled socket service %q: %v", socketService, e)) 323 } 324 } 325 if app.Timer != nil { 326 timerService := filepath.Base(app.Timer.File()) 327 if e := sysd.Disable(timerService); e != nil { 328 inter.Notify(fmt.Sprintf("While trying to disable previously enabled timer service %q: %v", timerService, e)) 329 } 330 } 331 }(app) 332 333 for _, socket := range app.Sockets { 334 socketService := filepath.Base(socket.File()) 335 // enable the socket 336 if err = sysd.Enable(socketService); err != nil { 337 return err 338 } 339 340 switch app.DaemonScope { 341 case snap.SystemDaemon: 342 timings.Run(tm, "start-system-socket-service", fmt.Sprintf("start system socket service %q", socketService), func(nested timings.Measurer) { 343 err = sysd.Start(socketService) 344 }) 345 case snap.UserDaemon: 346 timings.Run(tm, "start-user-socket-service", fmt.Sprintf("start user socket service %q", socketService), func(nested timings.Measurer) { 347 err = startUserServices(cli, inter, socketService) 348 }) 349 } 350 if err != nil { 351 return err 352 } 353 } 354 355 if app.Timer != nil { 356 timerService := filepath.Base(app.Timer.File()) 357 // enable the timer 358 if err = sysd.Enable(timerService); err != nil { 359 return err 360 } 361 362 switch app.DaemonScope { 363 case snap.SystemDaemon: 364 timings.Run(tm, "start-system-timer-service", fmt.Sprintf("start system timer service %q", timerService), func(nested timings.Measurer) { 365 err = sysd.Start(timerService) 366 }) 367 case snap.UserDaemon: 368 timings.Run(tm, "start-user-timer-service", fmt.Sprintf("start user timer service %q", timerService), func(nested timings.Measurer) { 369 err = startUserServices(cli, inter, timerService) 370 }) 371 } 372 if err != nil { 373 return err 374 } 375 } 376 } 377 378 for _, srv := range systemServices { 379 // starting all services at once does not create a single 380 // transaction, but instead spawns multiple jobs, make sure the 381 // services started in the original order by bring them up one 382 // by one, see: 383 // https://github.com/systemd/systemd/issues/8102 384 // https://lists.freedesktop.org/archives/systemd-devel/2018-January/040152.html 385 timings.Run(tm, "start-service", fmt.Sprintf("start service %q", srv), func(nested timings.Measurer) { 386 err = systemSysd.Start(srv) 387 }) 388 if err != nil { 389 // cleanup was set up by iterating over apps 390 return err 391 } 392 } 393 394 if len(userServices) != 0 { 395 timings.Run(tm, "start-user-services", "start user services", func(nested timings.Measurer) { 396 err = startUserServices(cli, inter, userServices...) 397 }) 398 if err != nil { 399 return err 400 } 401 } 402 403 return nil 404 } 405 406 func userDaemonReload() error { 407 cli := client.New() 408 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout.DefaultTimeout)) 409 defer cancel() 410 return cli.ServicesDaemonReload(ctx) 411 } 412 413 func tryFileUpdate(path string, desiredContent []byte) (old *osutil.MemoryFileState, modified bool, err error) { 414 newFileState := osutil.MemoryFileState{ 415 Content: desiredContent, 416 Mode: os.FileMode(0644), 417 } 418 419 // get the existing content (if any) of the file to have something to 420 // rollback to if we have any errors 421 422 // note we can't use FileReference here since we may be modifying 423 // the file, and the FileReference.State() wouldn't be evaluated 424 // until _after_ we attempted modification 425 oldFileState := osutil.MemoryFileState{} 426 427 if st, err := os.Stat(path); err == nil { 428 b, err := ioutil.ReadFile(path) 429 if err != nil { 430 return nil, false, err 431 } 432 oldFileState.Content = b 433 oldFileState.Mode = st.Mode() 434 newFileState.Mode = st.Mode() 435 436 // save the old state of the file 437 old = &oldFileState 438 } 439 440 if mkdirErr := os.MkdirAll(filepath.Dir(path), 0755); mkdirErr != nil { 441 return nil, false, mkdirErr 442 } 443 ensureErr := osutil.EnsureFileState(path, &newFileState) 444 switch ensureErr { 445 case osutil.ErrSameState: 446 // didn't change the file 447 return old, false, nil 448 case nil: 449 // we successfully modified the file 450 return old, true, nil 451 default: 452 // some other fatal error trying to write the file 453 return nil, false, ensureErr 454 } 455 } 456 457 type SnapServiceOptions struct { 458 // VitalityRank is the rank of all services in the specified snap used by 459 // the OOM killer when OOM conditions are reached. 460 VitalityRank int 461 462 // QuotaGroup is the quota group for all services in the specified snap. 463 QuotaGroup *quota.Group 464 } 465 466 // ObserveChangeCallback can be invoked by EnsureSnapServices to observe 467 // the previous content of a unit and the new on a change. 468 // unitType can be "service", "socket", "timer". name is empty for a timer. 469 type ObserveChangeCallback func(app *snap.AppInfo, grp *quota.Group, unitType string, name, old, new string) 470 471 // EnsureSnapServicesOptions is the set of options applying to the 472 // EnsureSnapServices operation. It does not include per-snap specific options 473 // such as VitalityRank or RequireMountedSnapdSnap from AddSnapServiceOptions, 474 // since those are expected to be provided in the snaps argument. 475 type EnsureSnapServicesOptions struct { 476 // Preseeding is whether the system is currently being preseeded, in which 477 // case there is not a running systemd for EnsureSnapServicesOptions to 478 // issue commands like systemctl daemon-reload to. 479 Preseeding bool 480 481 // RequireMountedSnapdSnap is whether the generated units should depend on 482 // the snapd snap being mounted, this is specific to systems like UC18 and 483 // UC20 which have the snapd snap and need to have units generated 484 RequireMountedSnapdSnap bool 485 } 486 487 // EnsureSnapServices will ensure that the specified snap services' file states 488 // are up to date with the specified options and infos. It will add new services 489 // if those units don't already exist, but it does not delete existing service 490 // units that are not present in the snap's Info structures. 491 // There are two sets of options; there are global options which apply to the 492 // entire transaction and to every snap service that is ensured, and options 493 // which are per-snap service and specified in the map argument. 494 // If any errors are encountered trying to update systemd units, then all 495 // changes performed up to that point are rolled back, meaning newly written 496 // units are deleted and modified units are attempted to be restored to their 497 // previous state. 498 // To observe which units were added or modified a 499 // ObserveChangeCallback calllback can be provided. The callback is 500 // invoked while processing the changes. Because of that it should not 501 // produce immediate side-effects, as the changes are in effect only 502 // if the function did not return an error. 503 // This function is idempotent. 504 func EnsureSnapServices(snaps map[*snap.Info]*SnapServiceOptions, opts *EnsureSnapServicesOptions, observeChange ObserveChangeCallback, inter interacter) (err error) { 505 // note, sysd is not used when preseeding 506 sysd := systemd.New(systemd.SystemMode, inter) 507 508 if opts == nil { 509 opts = &EnsureSnapServicesOptions{} 510 } 511 512 // we only consider the global EnsureSnapServicesOptions to decide if we 513 // are preseeding or not to reduce confusion about which set of options 514 // determines whether we are preseeding or not during the ensure operation 515 preseeding := opts.Preseeding 516 517 // modifiedUnitsPreviousState is the set of units that were modified and the previous 518 // state of the unit before modification that we can roll back to if there 519 // are any issues. 520 // note that the rollback is best effort, if we are rebooted in the middle, 521 // there is no guarantee about the state of files, some may have been 522 // updated and some may have been rolled back, higher level tasks/changes 523 // should have do/undo handlers to properly handle the case where this 524 // function is interrupted midway 525 modifiedUnitsPreviousState := make(map[string]*osutil.MemoryFileState) 526 var modifiedSystem, modifiedUser bool 527 528 defer func() { 529 if err == nil { 530 return 531 } 532 for file, state := range modifiedUnitsPreviousState { 533 if state == nil { 534 // we don't have anything to rollback to, so just remove the 535 // file 536 if e := os.Remove(file); e != nil { 537 inter.Notify(fmt.Sprintf("while trying to remove %s due to previous failure: %v", file, e)) 538 } 539 } else { 540 // rollback the file to the previous state 541 if e := osutil.EnsureFileState(file, state); e != nil { 542 inter.Notify(fmt.Sprintf("while trying to rollback %s due to previous failure: %v", file, e)) 543 } 544 } 545 } 546 if modifiedSystem && !preseeding { 547 if e := sysd.DaemonReload(); e != nil { 548 inter.Notify(fmt.Sprintf("while trying to perform systemd daemon-reload due to previous failure: %v", e)) 549 } 550 } 551 if modifiedUser && !preseeding { 552 if e := userDaemonReload(); e != nil { 553 inter.Notify(fmt.Sprintf("while trying to perform user systemd daemon-reload due to previous failure: %v", e)) 554 } 555 } 556 }() 557 558 handleFileModification := func(app *snap.AppInfo, unitType string, name, path string, content []byte) error { 559 old, modifiedFile, err := tryFileUpdate(path, content) 560 if err != nil { 561 return err 562 } 563 564 if modifiedFile { 565 if observeChange != nil { 566 var oldContent []byte 567 if old != nil { 568 oldContent = old.Content 569 } 570 observeChange(app, nil, unitType, name, string(oldContent), string(content)) 571 } 572 modifiedUnitsPreviousState[path] = old 573 574 // also mark that we need to reload either the system or 575 // user instance of systemd 576 switch app.DaemonScope { 577 case snap.SystemDaemon: 578 modifiedSystem = true 579 case snap.UserDaemon: 580 modifiedUser = true 581 } 582 } 583 584 return nil 585 } 586 587 neededQuotaGrps := "a.QuotaGroupSet{} 588 589 for s, snapSvcOpts := range snaps { 590 if s.Type() == snap.TypeSnapd { 591 return fmt.Errorf("internal error: adding explicit services for snapd snap is unexpected") 592 } 593 594 // always use RequireMountedSnapdSnap options from the global options 595 genServiceOpts := &AddSnapServicesOptions{ 596 RequireMountedSnapdSnap: opts.RequireMountedSnapdSnap, 597 } 598 if snapSvcOpts != nil { 599 // and if there are per-snap options specified, use that for 600 // VitalityRank 601 genServiceOpts.VitalityRank = snapSvcOpts.VitalityRank 602 genServiceOpts.QuotaGroup = snapSvcOpts.QuotaGroup 603 604 if snapSvcOpts.QuotaGroup != nil { 605 if err := neededQuotaGrps.AddAllNecessaryGroups(snapSvcOpts.QuotaGroup); err != nil { 606 // this error can basically only be a circular reference 607 // in the quota group tree 608 return err 609 } 610 } 611 } 612 // note that the Preseeding option is not used here at all 613 614 for _, app := range s.Apps { 615 if !app.IsService() { 616 continue 617 } 618 619 // create services first; this doesn't trigger systemd 620 621 // Generate new service file state 622 path := app.ServiceFile() 623 content, err := generateSnapServiceFile(app, genServiceOpts) 624 if err != nil { 625 return err 626 } 627 628 if err := handleFileModification(app, "service", app.Name, path, content); err != nil { 629 return err 630 } 631 632 // Generate systemd .socket files if needed 633 socketFiles, err := generateSnapSocketFiles(app) 634 if err != nil { 635 return err 636 } 637 for name, content := range socketFiles { 638 path := app.Sockets[name].File() 639 if err := handleFileModification(app, "socket", name, path, content); err != nil { 640 return err 641 } 642 } 643 644 if app.Timer != nil { 645 content, err := generateSnapTimerFile(app) 646 if err != nil { 647 return err 648 } 649 path := app.Timer.File() 650 if err := handleFileModification(app, "timer", "", path, content); err != nil { 651 return err 652 } 653 } 654 } 655 } 656 657 handleSliceModification := func(grp *quota.Group, path string, content []byte) error { 658 old, modifiedFile, err := tryFileUpdate(path, content) 659 if err != nil { 660 return err 661 } 662 663 if modifiedFile { 664 if observeChange != nil { 665 var oldContent []byte 666 if old != nil { 667 oldContent = old.Content 668 } 669 observeChange(nil, grp, "slice", grp.Name, string(oldContent), string(content)) 670 } 671 672 modifiedUnitsPreviousState[path] = old 673 674 // also mark that we need to reload the system instance of systemd 675 // TODO: also handle reloading the user instance of systemd when 676 // needed 677 modifiedSystem = true 678 } 679 680 return nil 681 } 682 683 // now make sure that all of the slice units exist 684 for _, grp := range neededQuotaGrps.AllQuotaGroups() { 685 content, err := generateGroupSliceFile(grp) 686 if err != nil { 687 return err 688 } 689 690 sliceFileName := grp.SliceFileName() 691 path := filepath.Join(dirs.SnapServicesDir, sliceFileName) 692 if err := handleSliceModification(grp, path, content); err != nil { 693 return err 694 } 695 } 696 697 if !preseeding { 698 if modifiedSystem { 699 if err = sysd.DaemonReload(); err != nil { 700 return err 701 } 702 } 703 if modifiedUser { 704 if err = userDaemonReload(); err != nil { 705 return err 706 } 707 } 708 } 709 710 return nil 711 } 712 713 // AddSnapServicesOptions is a struct for controlling the generated service 714 // definition for a snap service. 715 type AddSnapServicesOptions struct { 716 // VitalityRank is the rank of all services in the specified snap used by 717 // the OOM killer when OOM conditions are reached. 718 VitalityRank int 719 720 // QuotaGroup is the quota group for all services in the specified snap. 721 QuotaGroup *quota.Group 722 723 // RequireMountedSnapdSnap is whether the generated units should depend on 724 // the snapd snap being mounted, this is specific to systems like UC18 and 725 // UC20 which have the snapd snap and need to have units generated 726 RequireMountedSnapdSnap bool 727 728 // Preseeding is whether the system is currently being preseeded, in which 729 // case there is not a running systemd for EnsureSnapServicesOptions to 730 // issue commands like systemctl daemon-reload to. 731 Preseeding bool 732 } 733 734 // AddSnapServices adds service units for the applications from the snap which 735 // are services. The services do not get enabled or started. 736 func AddSnapServices(s *snap.Info, opts *AddSnapServicesOptions, inter interacter) error { 737 m := map[*snap.Info]*SnapServiceOptions{ 738 s: {}, 739 } 740 ensureOpts := &EnsureSnapServicesOptions{} 741 if opts != nil { 742 // set the per-snap service options 743 m[s].VitalityRank = opts.VitalityRank 744 m[s].QuotaGroup = opts.QuotaGroup 745 746 // copy the globally applicable opts from AddSnapServicesOptions to 747 // EnsureSnapServicesOptions, since those options override the per-snap opts 748 // we put in the map argument 749 ensureOpts.Preseeding = opts.Preseeding 750 ensureOpts.RequireMountedSnapdSnap = opts.RequireMountedSnapdSnap 751 } 752 753 return EnsureSnapServices(m, ensureOpts, nil, inter) 754 } 755 756 // StopServicesFlags carries extra flags for StopServices. 757 type StopServicesFlags struct { 758 Disable bool 759 } 760 761 // StopServices stops and optionally disables service units for the applications 762 // from the snap which are services. 763 func StopServices(apps []*snap.AppInfo, flags *StopServicesFlags, reason snap.ServiceStopReason, inter interacter, tm timings.Measurer) error { 764 sysd := systemd.New(systemd.SystemMode, inter) 765 if flags == nil { 766 flags = &StopServicesFlags{} 767 } 768 769 if reason != snap.StopReasonOther { 770 logger.Debugf("StopServices called for %q, reason: %v", apps, reason) 771 } else { 772 logger.Debugf("StopServices called for %q", apps) 773 } 774 for _, app := range apps { 775 // Handle the case where service file doesn't exist and don't try to stop it as it will fail. 776 // This can happen with snap try when snap.yaml is modified on the fly and a daemon line is added. 777 if !app.IsService() || !osutil.FileExists(app.ServiceFile()) { 778 continue 779 } 780 // Skip stop on refresh when refresh mode is set to something 781 // other than "restart" (or "" which is the same) 782 if reason == snap.StopReasonRefresh { 783 logger.Debugf(" %s refresh-mode: %v", app.Name, app.StopMode) 784 switch app.RefreshMode { 785 case "endure": 786 // skip this service 787 continue 788 } 789 } 790 791 var err error 792 timings.Run(tm, "stop-service", fmt.Sprintf("stop service %q", app.ServiceName()), func(nested timings.Measurer) { 793 err = stopService(sysd, app, inter) 794 if err == nil && flags.Disable { 795 err = sysd.Disable(app.ServiceName()) 796 } 797 }) 798 if err != nil { 799 return err 800 } 801 802 // ensure the service is really stopped on remove regardless 803 // of stop-mode 804 if reason == snap.StopReasonRemove && !app.StopMode.KillAll() && app.DaemonScope == snap.SystemDaemon { 805 // FIXME: make this smarter and avoid the killWait 806 // delay if not needed (i.e. if all processes 807 // have died) 808 sysd.Kill(app.ServiceName(), "TERM", "all") 809 time.Sleep(killWait) 810 sysd.Kill(app.ServiceName(), "KILL", "") 811 } 812 } 813 return nil 814 } 815 816 // ServicesEnableState returns a map of service names from the given snap, 817 // together with their enable/disable status. 818 func ServicesEnableState(s *snap.Info, inter interacter) (map[string]bool, error) { 819 sysd := systemd.New(systemd.SystemMode, inter) 820 821 // loop over all services in the snap, querying systemd for the current 822 // systemd state of the snaps 823 snapSvcsState := make(map[string]bool, len(s.Apps)) 824 for name, app := range s.Apps { 825 if !app.IsService() { 826 continue 827 } 828 // FIXME: handle user daemons 829 if app.DaemonScope != snap.SystemDaemon { 830 continue 831 } 832 state, err := sysd.IsEnabled(app.ServiceName()) 833 if err != nil { 834 return nil, err 835 } 836 snapSvcsState[name] = state 837 } 838 return snapSvcsState, nil 839 } 840 841 // RemoveQuotaGroup ensures that the slice file for a quota group is removed. It 842 // assumes that the slice corresponding to the group is not in use anymore by 843 // any services or sub-groups of the group when it is invoked. To remove a group 844 // with sub-groups, one must remove all the sub-groups first. 845 // This function is idempotent, if the slice file doesn't exist no error is 846 // returned. 847 func RemoveQuotaGroup(grp *quota.Group, inter interacter) error { 848 // TODO: it only works on leaf sub-groups currently 849 if len(grp.SubGroups) != 0 { 850 return fmt.Errorf("internal error: cannot remove quota group with sub-groups") 851 } 852 853 systemSysd := systemd.New(systemd.SystemMode, inter) 854 855 // remove the slice file 856 err := os.Remove(filepath.Join(dirs.SnapServicesDir, grp.SliceFileName())) 857 if err != nil && !os.IsNotExist(err) { 858 return err 859 } 860 861 if err == nil { 862 // we deleted the slice unit, so we need to daemon-reload 863 if err := systemSysd.DaemonReload(); err != nil { 864 return err 865 } 866 } 867 return nil 868 } 869 870 // RemoveSnapServices disables and removes service units for the applications 871 // from the snap which are services. The optional flag indicates whether 872 // services are removed as part of undoing of first install of a given snap. 873 func RemoveSnapServices(s *snap.Info, inter interacter) error { 874 if s.Type() == snap.TypeSnapd { 875 return fmt.Errorf("internal error: removing explicit services for snapd snap is unexpected") 876 } 877 systemSysd := systemd.New(systemd.SystemMode, inter) 878 userSysd := systemd.New(systemd.GlobalUserMode, inter) 879 var removedSystem, removedUser bool 880 881 for _, app := range s.Apps { 882 if !app.IsService() || !osutil.FileExists(app.ServiceFile()) { 883 continue 884 } 885 886 var sysd systemd.Systemd 887 switch app.DaemonScope { 888 case snap.SystemDaemon: 889 sysd = systemSysd 890 removedSystem = true 891 case snap.UserDaemon: 892 sysd = userSysd 893 removedUser = true 894 } 895 serviceName := filepath.Base(app.ServiceFile()) 896 897 for _, socket := range app.Sockets { 898 path := socket.File() 899 socketServiceName := filepath.Base(path) 900 if err := sysd.Disable(socketServiceName); err != nil { 901 return err 902 } 903 904 if err := os.Remove(path); err != nil && !os.IsNotExist(err) { 905 logger.Noticef("Failed to remove socket file %q for %q: %v", path, serviceName, err) 906 } 907 } 908 909 if app.Timer != nil { 910 path := app.Timer.File() 911 912 timerName := filepath.Base(path) 913 if err := sysd.Disable(timerName); err != nil { 914 return err 915 } 916 917 if err := os.Remove(path); err != nil && !os.IsNotExist(err) { 918 logger.Noticef("Failed to remove timer file %q for %q: %v", path, serviceName, err) 919 } 920 } 921 922 if err := sysd.Disable(serviceName); err != nil { 923 return err 924 } 925 926 if err := os.Remove(app.ServiceFile()); err != nil && !os.IsNotExist(err) { 927 logger.Noticef("Failed to remove service file for %q: %v", serviceName, err) 928 } 929 930 } 931 932 // only reload if we actually had services 933 if removedSystem { 934 if err := systemSysd.DaemonReload(); err != nil { 935 return err 936 } 937 } 938 if removedUser { 939 if err := userDaemonReload(); err != nil { 940 return err 941 } 942 } 943 944 return nil 945 } 946 947 func genServiceNames(snap *snap.Info, appNames []string) []string { 948 names := make([]string, 0, len(appNames)) 949 950 for _, name := range appNames { 951 if app := snap.Apps[name]; app != nil { 952 names = append(names, app.ServiceName()) 953 } 954 } 955 return names 956 } 957 958 // TODO: this should not accept AddSnapServicesOptions, it should use some other 959 // subset of options, specifically it should not accept Preseeding as an option 960 // here 961 func genServiceFile(appInfo *snap.AppInfo, opts *AddSnapServicesOptions) ([]byte, error) { 962 if opts == nil { 963 opts = &AddSnapServicesOptions{} 964 } 965 966 // assemble all of the service directive snippets for all interfaces that 967 // this service needs to include in the generated systemd file 968 969 // use an ordered set to ensure we don't duplicate any keys from interfaces 970 // that specify the same snippet 971 972 // TODO: maybe we should error if multiple interfaces specify different 973 // values for the same directive, otherwise one of them will overwrite the 974 // other? What happens right now is that the snippet from the plug that 975 // comes last will win in the case of directives that can have only one 976 // value, but for some directives, systemd combines their values into a 977 // list. 978 ifaceServiceSnippets := &strutil.OrderedSet{} 979 980 for _, plug := range appInfo.Plugs { 981 iface, err := interfaces.ByName(plug.Interface) 982 if err != nil { 983 return nil, fmt.Errorf("error processing plugs while generating service unit for %v: %v", appInfo.SecurityTag(), err) 984 } 985 snips, err := interfaces.PermanentPlugServiceSnippets(iface, plug) 986 if err != nil { 987 return nil, fmt.Errorf("error processing plugs while generating service unit for %v: %v", appInfo.SecurityTag(), err) 988 } 989 for _, snip := range snips { 990 ifaceServiceSnippets.Put(snip) 991 } 992 } 993 994 // join the service snippets into one string to be included in the 995 // template 996 ifaceSpecifiedServiceSnippet := strings.Join(ifaceServiceSnippets.Items(), "\n") 997 998 serviceTemplate := `[Unit] 999 # Auto-generated, DO NOT EDIT 1000 Description=Service for snap application {{.App.Snap.InstanceName}}.{{.App.Name}} 1001 {{- if .MountUnit }} 1002 Requires={{.MountUnit}} 1003 {{- end }} 1004 {{- if .PrerequisiteTarget}} 1005 Wants={{.PrerequisiteTarget}} 1006 {{- end}} 1007 {{- if .After}} 1008 After={{ stringsJoin .After " " }} 1009 {{- end}} 1010 {{- if .Before}} 1011 Before={{ stringsJoin .Before " "}} 1012 {{- end}} 1013 {{- if .CoreMountedSnapdSnapDep}} 1014 Wants={{ stringsJoin .CoreMountedSnapdSnapDep " "}} 1015 After={{ stringsJoin .CoreMountedSnapdSnapDep " "}} 1016 {{- end}} 1017 X-Snappy=yes 1018 1019 [Service] 1020 EnvironmentFile=-/etc/environment 1021 ExecStart={{.App.LauncherCommand}} 1022 SyslogIdentifier={{.App.Snap.InstanceName}}.{{.App.Name}} 1023 Restart={{.Restart}} 1024 {{- if .App.RestartDelay}} 1025 RestartSec={{.App.RestartDelay.Seconds}} 1026 {{- end}} 1027 WorkingDirectory={{.WorkingDir}} 1028 {{- if .App.StopCommand}} 1029 ExecStop={{.App.LauncherStopCommand}} 1030 {{- end}} 1031 {{- if .App.ReloadCommand}} 1032 ExecReload={{.App.LauncherReloadCommand}} 1033 {{- end}} 1034 {{- if .App.PostStopCommand}} 1035 ExecStopPost={{.App.LauncherPostStopCommand}} 1036 {{- end}} 1037 {{- if .StopTimeout}} 1038 TimeoutStopSec={{.StopTimeout.Seconds}} 1039 {{- end}} 1040 {{- if .StartTimeout}} 1041 TimeoutStartSec={{.StartTimeout.Seconds}} 1042 {{- end}} 1043 Type={{.App.Daemon}} 1044 {{- if .Remain}} 1045 RemainAfterExit={{.Remain}} 1046 {{- end}} 1047 {{- if .BusName}} 1048 BusName={{.BusName}} 1049 {{- end}} 1050 {{- if .App.WatchdogTimeout}} 1051 WatchdogSec={{.App.WatchdogTimeout.Seconds}} 1052 {{- end}} 1053 {{- if .KillMode}} 1054 KillMode={{.KillMode}} 1055 {{- end}} 1056 {{- if .KillSignal}} 1057 KillSignal={{.KillSignal}} 1058 {{- end}} 1059 {{- if .OOMAdjustScore }} 1060 OOMScoreAdjust={{.OOMAdjustScore}} 1061 {{- end}} 1062 {{- if .InterfaceServiceSnippets}} 1063 {{.InterfaceServiceSnippets}} 1064 {{- end}} 1065 {{- if .SliceUnit}} 1066 Slice={{.SliceUnit}} 1067 {{- end}} 1068 {{- if not (or .App.Sockets .App.Timer .App.ActivatesOn) }} 1069 1070 [Install] 1071 WantedBy={{.ServicesTarget}} 1072 {{- end}} 1073 ` 1074 var templateOut bytes.Buffer 1075 tmpl := template.New("service-wrapper") 1076 tmpl.Funcs(template.FuncMap{ 1077 "stringsJoin": strings.Join, 1078 }) 1079 t := template.Must(tmpl.Parse(serviceTemplate)) 1080 1081 restartCond := appInfo.RestartCond.String() 1082 if restartCond == "" { 1083 restartCond = snap.RestartOnFailure.String() 1084 } 1085 1086 // use score -900+vitalityRank, where vitalityRank starts at 1 1087 // and considering snapd itself has OOMScoreAdjust=-900 1088 const baseOOMAdjustScore = -900 1089 var oomAdjustScore int 1090 if opts.VitalityRank > 0 { 1091 oomAdjustScore = baseOOMAdjustScore + opts.VitalityRank 1092 } 1093 1094 var remain string 1095 if appInfo.Daemon == "oneshot" { 1096 // any restart condition other than "no" is invalid for oneshot daemons 1097 restartCond = "no" 1098 // If StopExec is present for a oneshot service than we also need 1099 // RemainAfterExit=yes 1100 if appInfo.StopCommand != "" { 1101 remain = "yes" 1102 } 1103 } 1104 var killMode string 1105 if !appInfo.StopMode.KillAll() { 1106 killMode = "process" 1107 } 1108 1109 var busName string 1110 if appInfo.Daemon == "dbus" { 1111 busName = appInfo.BusName 1112 if busName == "" && len(appInfo.ActivatesOn) != 0 { 1113 slot := appInfo.ActivatesOn[len(appInfo.ActivatesOn)-1] 1114 if err := slot.Attr("name", &busName); err != nil { 1115 // This should be impossible for a valid AppInfo 1116 logger.Noticef("Cannot get 'name' attribute of dbus slot %q: %v", slot.Name, err) 1117 } 1118 } 1119 } 1120 1121 wrapperData := struct { 1122 App *snap.AppInfo 1123 1124 Restart string 1125 WorkingDir string 1126 StopTimeout time.Duration 1127 StartTimeout time.Duration 1128 ServicesTarget string 1129 PrerequisiteTarget string 1130 MountUnit string 1131 Remain string 1132 KillMode string 1133 KillSignal string 1134 OOMAdjustScore int 1135 BusName string 1136 Before []string 1137 After []string 1138 InterfaceServiceSnippets string 1139 SliceUnit string 1140 1141 Home string 1142 EnvVars string 1143 1144 CoreMountedSnapdSnapDep []string 1145 }{ 1146 App: appInfo, 1147 1148 InterfaceServiceSnippets: ifaceSpecifiedServiceSnippet, 1149 1150 Restart: restartCond, 1151 StopTimeout: serviceStopTimeout(appInfo), 1152 StartTimeout: time.Duration(appInfo.StartTimeout), 1153 Remain: remain, 1154 KillMode: killMode, 1155 KillSignal: appInfo.StopMode.KillSignal(), 1156 OOMAdjustScore: oomAdjustScore, 1157 BusName: busName, 1158 1159 Before: genServiceNames(appInfo.Snap, appInfo.Before), 1160 After: genServiceNames(appInfo.Snap, appInfo.After), 1161 1162 // systemd runs as PID 1 so %h will not work. 1163 Home: "/root", 1164 } 1165 switch appInfo.DaemonScope { 1166 case snap.SystemDaemon: 1167 wrapperData.ServicesTarget = systemd.ServicesTarget 1168 wrapperData.PrerequisiteTarget = systemd.PrerequisiteTarget 1169 wrapperData.MountUnit = filepath.Base(systemd.MountUnitPath(appInfo.Snap.MountDir())) 1170 wrapperData.WorkingDir = appInfo.Snap.DataDir() 1171 wrapperData.After = append(wrapperData.After, "snapd.apparmor.service") 1172 case snap.UserDaemon: 1173 wrapperData.ServicesTarget = systemd.UserServicesTarget 1174 // FIXME: ideally use UserDataDir("%h"), but then the 1175 // unit fails if the directory doesn't exist. 1176 wrapperData.WorkingDir = appInfo.Snap.DataDir() 1177 default: 1178 panic("unknown snap.DaemonScope") 1179 } 1180 1181 // check the quota group slice 1182 if opts.QuotaGroup != nil { 1183 wrapperData.SliceUnit = opts.QuotaGroup.SliceFileName() 1184 } 1185 1186 // Add extra "After" targets 1187 if wrapperData.PrerequisiteTarget != "" { 1188 wrapperData.After = append([]string{wrapperData.PrerequisiteTarget}, wrapperData.After...) 1189 } 1190 if wrapperData.MountUnit != "" { 1191 wrapperData.After = append([]string{wrapperData.MountUnit}, wrapperData.After...) 1192 } 1193 1194 if opts.RequireMountedSnapdSnap { 1195 // on core 18+ systems, the snapd tooling is exported 1196 // into the host system via a special mount unit, which 1197 // also adds an implicit dependency on the snapd snap 1198 // mount thus /usr/bin/snap points 1199 wrapperData.CoreMountedSnapdSnapDep = []string{SnapdToolingMountUnit} 1200 } 1201 1202 if err := t.Execute(&templateOut, wrapperData); err != nil { 1203 // this can never happen, except we forget a variable 1204 logger.Panicf("Unable to execute template: %v", err) 1205 } 1206 1207 return templateOut.Bytes(), nil 1208 } 1209 1210 func genServiceSocketFile(appInfo *snap.AppInfo, socketName string) []byte { 1211 socketTemplate := `[Unit] 1212 # Auto-generated, DO NOT EDIT 1213 Description=Socket {{.SocketName}} for snap application {{.App.Snap.InstanceName}}.{{.App.Name}} 1214 {{- if .MountUnit}} 1215 Requires={{.MountUnit}} 1216 After={{.MountUnit}} 1217 {{- end}} 1218 X-Snappy=yes 1219 1220 [Socket] 1221 Service={{.ServiceFileName}} 1222 FileDescriptorName={{.SocketInfo.Name}} 1223 ListenStream={{.ListenStream}} 1224 {{- if .SocketInfo.SocketMode}} 1225 SocketMode={{.SocketInfo.SocketMode | printf "%04o"}} 1226 {{- end}} 1227 1228 [Install] 1229 WantedBy={{.SocketsTarget}} 1230 ` 1231 var templateOut bytes.Buffer 1232 t := template.Must(template.New("socket-wrapper").Parse(socketTemplate)) 1233 1234 socket := appInfo.Sockets[socketName] 1235 listenStream := renderListenStream(socket) 1236 wrapperData := struct { 1237 App *snap.AppInfo 1238 ServiceFileName string 1239 SocketsTarget string 1240 MountUnit string 1241 SocketName string 1242 SocketInfo *snap.SocketInfo 1243 ListenStream string 1244 }{ 1245 App: appInfo, 1246 ServiceFileName: filepath.Base(appInfo.ServiceFile()), 1247 SocketsTarget: systemd.SocketsTarget, 1248 SocketName: socketName, 1249 SocketInfo: socket, 1250 ListenStream: listenStream, 1251 } 1252 switch appInfo.DaemonScope { 1253 case snap.SystemDaemon: 1254 wrapperData.MountUnit = filepath.Base(systemd.MountUnitPath(appInfo.Snap.MountDir())) 1255 case snap.UserDaemon: 1256 // nothing 1257 default: 1258 panic("unknown snap.DaemonScope") 1259 } 1260 1261 if err := t.Execute(&templateOut, wrapperData); err != nil { 1262 // this can never happen, except we forget a variable 1263 logger.Panicf("Unable to execute template: %v", err) 1264 } 1265 1266 return templateOut.Bytes() 1267 } 1268 1269 func generateSnapSocketFiles(app *snap.AppInfo) (map[string][]byte, error) { 1270 if err := snap.ValidateApp(app); err != nil { 1271 return nil, err 1272 } 1273 1274 socketFiles := make(map[string][]byte) 1275 for name := range app.Sockets { 1276 socketFiles[name] = genServiceSocketFile(app, name) 1277 } 1278 return socketFiles, nil 1279 } 1280 1281 func renderListenStream(socket *snap.SocketInfo) string { 1282 s := socket.App.Snap 1283 listenStream := socket.ListenStream 1284 switch socket.App.DaemonScope { 1285 case snap.SystemDaemon: 1286 listenStream = strings.Replace(listenStream, "$SNAP_DATA", s.DataDir(), -1) 1287 // TODO: when we support User/Group in the generated 1288 // systemd unit, adjust this accordingly 1289 serviceUserUid := sys.UserID(0) 1290 runtimeDir := s.UserXdgRuntimeDir(serviceUserUid) 1291 listenStream = strings.Replace(listenStream, "$XDG_RUNTIME_DIR", runtimeDir, -1) 1292 listenStream = strings.Replace(listenStream, "$SNAP_COMMON", s.CommonDataDir(), -1) 1293 case snap.UserDaemon: 1294 listenStream = strings.Replace(listenStream, "$SNAP_USER_DATA", s.UserDataDir("%h"), -1) 1295 listenStream = strings.Replace(listenStream, "$SNAP_USER_COMMON", s.UserCommonDataDir("%h"), -1) 1296 // FIXME: find some way to share code with snap.UserXdgRuntimeDir() 1297 listenStream = strings.Replace(listenStream, "$XDG_RUNTIME_DIR", fmt.Sprintf("%%t/snap.%s", s.InstanceName()), -1) 1298 default: 1299 panic("unknown snap.DaemonScope") 1300 } 1301 return listenStream 1302 } 1303 1304 func generateSnapTimerFile(app *snap.AppInfo) ([]byte, error) { 1305 timerTemplate := `[Unit] 1306 # Auto-generated, DO NOT EDIT 1307 Description=Timer {{.TimerName}} for snap application {{.App.Snap.InstanceName}}.{{.App.Name}} 1308 {{- if .MountUnit}} 1309 Requires={{.MountUnit}} 1310 After={{.MountUnit}} 1311 {{- end}} 1312 X-Snappy=yes 1313 1314 [Timer] 1315 Unit={{.ServiceFileName}} 1316 {{ range .Schedules }}OnCalendar={{ . }} 1317 {{ end }} 1318 [Install] 1319 WantedBy={{.TimersTarget}} 1320 ` 1321 var templateOut bytes.Buffer 1322 t := template.Must(template.New("timer-wrapper").Parse(timerTemplate)) 1323 1324 timerSchedule, err := timeutil.ParseSchedule(app.Timer.Timer) 1325 if err != nil { 1326 return nil, err 1327 } 1328 1329 schedules := generateOnCalendarSchedules(timerSchedule) 1330 1331 wrapperData := struct { 1332 App *snap.AppInfo 1333 ServiceFileName string 1334 TimersTarget string 1335 TimerName string 1336 MountUnit string 1337 Schedules []string 1338 }{ 1339 App: app, 1340 ServiceFileName: filepath.Base(app.ServiceFile()), 1341 TimersTarget: systemd.TimersTarget, 1342 TimerName: app.Name, 1343 Schedules: schedules, 1344 } 1345 switch app.DaemonScope { 1346 case snap.SystemDaemon: 1347 wrapperData.MountUnit = filepath.Base(systemd.MountUnitPath(app.Snap.MountDir())) 1348 case snap.UserDaemon: 1349 // nothing 1350 default: 1351 panic("unknown snap.DaemonScope") 1352 } 1353 1354 if err := t.Execute(&templateOut, wrapperData); err != nil { 1355 // this can never happen, except we forget a variable 1356 logger.Panicf("Unable to execute template: %v", err) 1357 } 1358 1359 return templateOut.Bytes(), nil 1360 } 1361 1362 func makeAbbrevWeekdays(start time.Weekday, end time.Weekday) []string { 1363 out := make([]string, 0, 7) 1364 for w := start; w%7 != (end + 1); w++ { 1365 out = append(out, time.Weekday(w % 7).String()[0:3]) 1366 } 1367 return out 1368 } 1369 1370 // daysRange generates a string representing a continuous range between given 1371 // day numbers, which due to compatiblilty with old systemd version uses a 1372 // verbose syntax of x,y,z instead of x..z 1373 func daysRange(start, end uint) string { 1374 var buf bytes.Buffer 1375 for i := start; i <= end; i++ { 1376 buf.WriteString(strconv.FormatInt(int64(i), 10)) 1377 if i < end { 1378 buf.WriteRune(',') 1379 } 1380 } 1381 return buf.String() 1382 } 1383 1384 // generateOnCalendarSchedules converts a schedule into OnCalendar schedules 1385 // suitable for use in systemd *.timer units using systemd.time(7) 1386 // https://www.freedesktop.org/software/systemd/man/systemd.time.html 1387 // XXX: old systemd versions do not support x..y ranges 1388 func generateOnCalendarSchedules(schedule []*timeutil.Schedule) []string { 1389 calendarEvents := make([]string, 0, len(schedule)) 1390 for _, sched := range schedule { 1391 days := make([]string, 0, len(sched.WeekSpans)) 1392 for _, week := range sched.WeekSpans { 1393 abbrev := strings.Join(makeAbbrevWeekdays(week.Start.Weekday, week.End.Weekday), ",") 1394 1395 if week.Start.Pos == timeutil.EveryWeek && week.End.Pos == timeutil.EveryWeek { 1396 // eg: mon, mon-fri, fri-mon 1397 days = append(days, fmt.Sprintf("%s *-*-*", abbrev)) 1398 continue 1399 } 1400 // examples: 1401 // mon1 - Mon *-*-1..7 (Monday during the first 7 days) 1402 // fri1 - Fri *-*-1..7 (Friday during the first 7 days) 1403 1404 // entries below will make systemd timer expire more 1405 // frequently than the schedule suggests, however snap 1406 // runner evaluates current time and gates the actual 1407 // action 1408 // 1409 // mon1-tue - *-*-1..7 *-*-8 (anchored at first 1410 // Monday; Monday happens during the 7 days, 1411 // Tuesday can possibly happen on the 8th day if 1412 // the month started on Tuesday) 1413 // 1414 // mon-tue1 - *-*~1 *-*-1..7 (anchored at first 1415 // Tuesday; matching Monday can happen on the 1416 // last day of previous month if Tuesday is the 1417 // 1st) 1418 // 1419 // mon5-tue - *-*~1..7 *-*-1 (anchored at last 1420 // Monday, the matching Tuesday can still happen 1421 // within the last 7 days, or on the 1st of the 1422 // next month) 1423 // 1424 // fri4-mon - *-*-22-31 *-*-1..7 (anchored at 4th 1425 // Friday, can span onto the next month, extreme case in 1426 // February when 28th is Friday) 1427 // 1428 // XXX: since old versions of systemd, eg. 229 available 1429 // in 16.04 does not support x..y ranges, days need to 1430 // be enumerated like so: 1431 // Mon *-*-1..7 -> Mon *-*-1,2,3,4,5,6,7 1432 // 1433 // XXX: old systemd versions do not support the last n 1434 // days syntax eg, *-*~1, thus the range needs to be 1435 // generated in more verbose way like so: 1436 // Mon *-*~1..7 -> Mon *-*-22,23,24,25,26,27,28,29,30,31 1437 // (22-28 is the last week, but the month can have 1438 // anywhere from 28 to 31 days) 1439 // 1440 startPos := week.Start.Pos 1441 endPos := startPos 1442 if !week.AnchoredAtStart() { 1443 startPos = week.End.Pos 1444 endPos = startPos 1445 } 1446 startDay := (startPos-1)*7 + 1 1447 endDay := (endPos) * 7 1448 1449 if week.IsSingleDay() { 1450 // single day, can use the 'weekday' filter 1451 if startPos == timeutil.LastWeek { 1452 // last week of a month, which can be 1453 // 22-28 in case of February, while 1454 // month can have between 28 and 31 days 1455 days = append(days, 1456 fmt.Sprintf("%s *-*-%s", abbrev, daysRange(22, 31))) 1457 } else { 1458 days = append(days, 1459 fmt.Sprintf("%s *-*-%s", abbrev, daysRange(startDay, endDay))) 1460 } 1461 continue 1462 } 1463 1464 if week.AnchoredAtStart() { 1465 // explore the edge cases first 1466 switch startPos { 1467 case timeutil.LastWeek: 1468 // starts in the last week of the month and 1469 // possibly spans into the first week of the 1470 // next month; 1471 // month can have between 28 and 31 1472 // days 1473 days = append(days, 1474 // trailing 29-31 that are not part of a full week 1475 fmt.Sprintf("*-*-%s", daysRange(29, 31)), 1476 fmt.Sprintf("*-*-%s", daysRange(1, 7))) 1477 case 4: 1478 // a range in the 4th week can span onto 1479 // the next week, which is either 28-31 1480 // or in extreme case (eg. February with 1481 // 28 days) 1-7 of the next month 1482 days = append(days, 1483 // trailing 29-31 that are not part of a full week 1484 fmt.Sprintf("*-*-%s", daysRange(29, 31)), 1485 fmt.Sprintf("*-*-%s", daysRange(1, 7))) 1486 default: 1487 // can possibly spill into the next week 1488 days = append(days, 1489 fmt.Sprintf("*-*-%s", daysRange(startDay+7, endDay+7))) 1490 } 1491 1492 if startDay < 28 { 1493 days = append(days, 1494 fmt.Sprintf("*-*-%s", daysRange(startDay, endDay))) 1495 } else { 1496 // from the end of the month 1497 days = append(days, 1498 fmt.Sprintf("*-*-%s", daysRange(startDay-7, endDay-7))) 1499 } 1500 } else { 1501 switch endPos { 1502 case timeutil.LastWeek: 1503 // month can have between 28 and 31 1504 // days, add trailing 29-31 that are not 1505 // part of a full week 1506 days = append(days, fmt.Sprintf("*-*-%s", daysRange(29, 31))) 1507 case 1: 1508 // possibly spans from the last week of the 1509 // previous month and ends in the first week of 1510 // current month 1511 days = append(days, fmt.Sprintf("*-*-%s", daysRange(22, 31))) 1512 default: 1513 // can possibly spill into the previous week 1514 days = append(days, 1515 fmt.Sprintf("*-*-%s", daysRange(startDay-7, endDay-7))) 1516 } 1517 if endDay < 28 { 1518 days = append(days, 1519 fmt.Sprintf("*-*-%s", daysRange(startDay, endDay))) 1520 } else { 1521 days = append(days, 1522 fmt.Sprintf("*-*-%s", daysRange(startDay-7, endDay-7))) 1523 } 1524 } 1525 } 1526 1527 if len(days) == 0 { 1528 // no weekday spec, meaning the timer runs every day 1529 days = []string{"*-*-*"} 1530 } 1531 1532 startTimes := make([]string, 0, len(sched.ClockSpans)) 1533 for _, clocks := range sched.ClockSpans { 1534 // use expanded clock spans 1535 for _, span := range clocks.ClockSpans() { 1536 when := span.Start 1537 if span.Spread { 1538 length := span.End.Sub(span.Start) 1539 if length < 0 { 1540 // span Start wraps around, so we have '00:00.Sub(23:45)' 1541 length = -length 1542 } 1543 if length > 5*time.Minute { 1544 // replicate what timeutil.Next() does 1545 // and cut some time at the end of the 1546 // window so that events do not happen 1547 // directly one after another 1548 length -= 5 * time.Minute 1549 } 1550 when = when.Add(randutil.RandomDuration(length)) 1551 } 1552 if when.Hour == 24 { 1553 // 24:00 for us means the other end of 1554 // the day, for systemd we need to 1555 // adjust it to the 0-23 hour range 1556 when.Hour -= 24 1557 } 1558 1559 startTimes = append(startTimes, when.String()) 1560 } 1561 } 1562 1563 for _, day := range days { 1564 if len(startTimes) == 0 { 1565 // current schedule is days only 1566 calendarEvents = append(calendarEvents, day) 1567 continue 1568 } 1569 1570 for _, startTime := range startTimes { 1571 calendarEvents = append(calendarEvents, fmt.Sprintf("%s %s", day, startTime)) 1572 } 1573 } 1574 } 1575 return calendarEvents 1576 } 1577 1578 type RestartServicesFlags struct { 1579 Reload bool 1580 } 1581 1582 // Restart or reload active services in `svcs`. 1583 // If reload flag is set then "systemctl reload-or-restart" is attempted. 1584 // The services mentioned in `explicitServices` should be a subset of the 1585 // services in svcs. The services included in explicitServices are always 1586 // restarted, regardless of their state. The services in the `svcs` argument 1587 // are only restarted if they are active, so if a service is meant to be 1588 // restarted no matter it's state, it should be included in the 1589 // explicitServices list. 1590 // The list of explicitServices needs to use systemd unit names. 1591 // TODO: change explicitServices format to be less unusual, more consistent 1592 // (introduce AppRef?) 1593 func RestartServices(svcs []*snap.AppInfo, explicitServices []string, 1594 flags *RestartServicesFlags, inter interacter, tm timings.Measurer) error { 1595 sysd := systemd.New(systemd.SystemMode, inter) 1596 1597 unitNames := make([]string, 0, len(svcs)) 1598 for _, srv := range svcs { 1599 // they're *supposed* to be all services, but checking doesn't hurt 1600 if !srv.IsService() { 1601 continue 1602 } 1603 unitNames = append(unitNames, srv.ServiceName()) 1604 } 1605 1606 unitStatuses, err := sysd.Status(unitNames...) 1607 if err != nil { 1608 return err 1609 } 1610 1611 for _, unit := range unitStatuses { 1612 // If the unit was explicitly mentioned in the command line, restart it 1613 // even if it is disabled; otherwise, we only restart units which are 1614 // currently running. Reference: 1615 // https://forum.snapcraft.io/t/command-line-interface-to-manipulate-services/262/47 1616 if !unit.Active && !strutil.ListContains(explicitServices, unit.UnitName) { 1617 continue 1618 } 1619 1620 var err error 1621 timings.Run(tm, "restart-service", fmt.Sprintf("restart service %s", unit.UnitName), func(nested timings.Measurer) { 1622 if flags != nil && flags.Reload { 1623 err = sysd.ReloadOrRestart(unit.UnitName) 1624 } else { 1625 // note: stop followed by start, not just 'restart' 1626 err = sysd.Restart(unit.UnitName, 5*time.Second) 1627 } 1628 }) 1629 if err != nil { 1630 // there is nothing we can do about failed service 1631 return err 1632 } 1633 } 1634 return nil 1635 } 1636 1637 // QueryDisabledServices returns a list of all currently disabled snap services 1638 // in the snap. 1639 func QueryDisabledServices(info *snap.Info, pb progress.Meter) ([]string, error) { 1640 // save the list of services that are in the disabled state before unlinking 1641 // and thus removing the snap services 1642 snapSvcStates, err := ServicesEnableState(info, pb) 1643 if err != nil { 1644 return nil, err 1645 } 1646 1647 disabledSnapSvcs := []string{} 1648 // add all disabled services to the list 1649 for svc, isEnabled := range snapSvcStates { 1650 if !isEnabled { 1651 disabledSnapSvcs = append(disabledSnapSvcs, svc) 1652 } 1653 } 1654 1655 // sort for easier testing 1656 sort.Strings(disabledSnapSvcs) 1657 1658 return disabledSnapSvcs, nil 1659 }