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  }