github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/wrappers/services.go (about)

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