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