github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/wrappers/core18.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018 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  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"regexp"
    29  	"syscall"
    30  	"time"
    31  
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/logger"
    34  	"github.com/snapcore/snapd/osutil"
    35  	"github.com/snapcore/snapd/release"
    36  	"github.com/snapcore/snapd/snap"
    37  	"github.com/snapcore/snapd/systemd"
    38  	"github.com/snapcore/snapd/timeout"
    39  )
    40  
    41  var snapdServiceStopTimeout = time.Duration(timeout.DefaultTimeout)
    42  
    43  // catches units that run /usr/bin/snap (with args), or things in /usr/lib/snapd/
    44  var execStartRe = regexp.MustCompile(`(?m)^ExecStart=(/usr/bin/snap\s+.*|/usr/lib/snapd/.*)$`)
    45  
    46  // snapdToolingMountUnit is the name of the mount unit that makes the
    47  const SnapdToolingMountUnit = "usr-lib-snapd.mount"
    48  
    49  func snapdSkipStart(content []byte) bool {
    50  	return bytes.Contains(content, []byte("X-Snapd-Snap: do-not-start"))
    51  }
    52  
    53  // snapdUnitSkipStart returns true for units that should not be started
    54  // automatically
    55  func snapdUnitSkipStart(unitPath string) (skip bool, err error) {
    56  	content, err := ioutil.ReadFile(unitPath)
    57  	if err != nil {
    58  		if os.IsNotExist(err) {
    59  			// no point in starting units that do not exist
    60  			return true, nil
    61  		}
    62  		return false, err
    63  	}
    64  	return snapdSkipStart(content), nil
    65  }
    66  
    67  func writeSnapdToolingMountUnit(sysd systemd.Systemd, prefix string) error {
    68  
    69  	// TODO: the following comment is wrong, we don't need RequiredBy=snapd here?
    70  
    71  	// Not using AddMountUnitFile() because we need
    72  	// "RequiredBy=snapd.service"
    73  
    74  	content := []byte(fmt.Sprintf(`[Unit]
    75  Description=Make the snapd snap tooling available for the system
    76  Before=snapd.service
    77  
    78  [Mount]
    79  What=%s/usr/lib/snapd
    80  Where=/usr/lib/snapd
    81  Type=none
    82  Options=bind
    83  
    84  [Install]
    85  WantedBy=snapd.service
    86  `, prefix))
    87  	fullPath := filepath.Join(dirs.SnapServicesDir, SnapdToolingMountUnit)
    88  
    89  	err := osutil.EnsureFileState(fullPath,
    90  		&osutil.MemoryFileState{
    91  			Content: content,
    92  			Mode:    0644,
    93  		},
    94  	)
    95  	if err == osutil.ErrSameState {
    96  		return nil
    97  	}
    98  	if err != nil {
    99  		return err
   100  	}
   101  	if err := sysd.DaemonReload(); err != nil {
   102  		return err
   103  	}
   104  	if err := sysd.Enable(SnapdToolingMountUnit); err != nil {
   105  		return err
   106  	}
   107  
   108  	// meh this is killing snap services that use Requires=<this-unit> because
   109  	// it doesn't use verbatim systemctl restart, it instead does it with
   110  	// a systemctl stop and then a systemctl start, which triggers LP #1924805
   111  	if err := sysd.Restart(SnapdToolingMountUnit, 5*time.Second); err != nil {
   112  		return err
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  func undoSnapdToolingMountUnit(sysd systemd.Systemd) error {
   119  	unit := "usr-lib-snapd.mount"
   120  	mountUnitPath := filepath.Join(dirs.SnapServicesDir, unit)
   121  
   122  	if !osutil.FileExists(mountUnitPath) {
   123  		return nil
   124  	}
   125  
   126  	if err := sysd.Disable(unit); err != nil {
   127  		return err
   128  	}
   129  	// XXX: it is ok to stop the mount unit, the failover handler
   130  	// executes snapd directly from the previous revision of snapd snap or
   131  	// the core snap, the handler is running directly from the mounted snapd snap
   132  	if err := sysd.Stop(unit, snapdServiceStopTimeout); err != nil {
   133  		return err
   134  	}
   135  	return os.Remove(mountUnitPath)
   136  }
   137  
   138  // AddSnapdSnapServices sets up the services based on a given snapd snap in the
   139  // system.
   140  func AddSnapdSnapServices(s *snap.Info, inter interacter) error {
   141  	if snapType := s.Type(); snapType != snap.TypeSnapd {
   142  		return fmt.Errorf("internal error: adding explicit snapd services for snap %q type %q is unexpected", s.InstanceName(), snapType)
   143  	}
   144  
   145  	// we never write snapd services on classic
   146  	if release.OnClassic {
   147  		return nil
   148  	}
   149  
   150  	sysd := systemd.New(systemd.SystemMode, inter)
   151  
   152  	if err := writeSnapdToolingMountUnit(sysd, s.MountDir()); err != nil {
   153  		return err
   154  	}
   155  
   156  	serviceUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.service"))
   157  	if err != nil {
   158  		return err
   159  	}
   160  	socketUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.socket"))
   161  	if err != nil {
   162  		return err
   163  	}
   164  	timerUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.timer"))
   165  	if err != nil {
   166  		return err
   167  	}
   168  	units := append(socketUnits, serviceUnits...)
   169  	units = append(units, timerUnits...)
   170  
   171  	snapdUnits := make(map[string]osutil.FileState, len(units)+1)
   172  	for _, unit := range units {
   173  		st, err := os.Stat(unit)
   174  		if err != nil {
   175  			return err
   176  		}
   177  		content, err := ioutil.ReadFile(unit)
   178  		if err != nil {
   179  			return err
   180  		}
   181  		if execStartRe.Match(content) {
   182  			content = execStartRe.ReplaceAll(content, []byte(fmt.Sprintf("ExecStart=%s$1", s.MountDir())))
   183  			// when the service executes a command from the snapd snap, make
   184  			// sure the exec path points to the mount dir, and that the
   185  			// mount happens before the unit is started
   186  			content = append(content, []byte(fmt.Sprintf("\n[Unit]\nRequiresMountsFor=%s\n", s.MountDir()))...)
   187  		}
   188  
   189  		snapdUnits[filepath.Base(unit)] = &osutil.MemoryFileState{
   190  			Content: content,
   191  			Mode:    st.Mode(),
   192  		}
   193  	}
   194  	globs := []string{"snapd.service", "snapd.socket", "snapd.*.service", "snapd.*.timer"}
   195  	changed, removed, err := osutil.EnsureDirStateGlobs(dirs.SnapServicesDir, globs, snapdUnits)
   196  	if err != nil {
   197  		// TODO: uhhhh, what do we do in this case?
   198  		return err
   199  	}
   200  	if (len(changed) + len(removed)) == 0 {
   201  		// nothing to do
   202  		return nil
   203  	}
   204  	// stop all removed units first
   205  	for _, unit := range removed {
   206  		if err := sysd.Stop(unit, 5*time.Second); err != nil {
   207  			logger.Noticef("failed to stop %q: %v", unit, err)
   208  		}
   209  		if err := sysd.Disable(unit); err != nil {
   210  			logger.Noticef("failed to disable %q: %v", unit, err)
   211  		}
   212  	}
   213  
   214  	// daemon-reload so that we get the new services
   215  	if len(changed) > 0 {
   216  		if err := sysd.DaemonReload(); err != nil {
   217  			return err
   218  		}
   219  	}
   220  
   221  	// enable/start all the new services
   222  	for _, unit := range changed {
   223  		// systemd looks at the logical units, even if 'enabled' service
   224  		// symlink points to /lib/systemd/system location, dropping an
   225  		// identically named service in /etc overrides the other unit,
   226  		// therefore it is sufficient to enable the new units only
   227  		//
   228  		// Calling sysd.Enable() unconditionally may fail depending on
   229  		// systemd version, where older versions (eg 229 in 16.04) would
   230  		// error out unless --force is passed, while new ones remove the
   231  		// symlink and create a new one.
   232  		enabled, err := sysd.IsEnabled(unit)
   233  		if err != nil {
   234  			return err
   235  		}
   236  		if enabled {
   237  			continue
   238  		}
   239  		if err := sysd.Enable(unit); err != nil {
   240  			return err
   241  		}
   242  	}
   243  
   244  	for _, unit := range changed {
   245  		// Some units (like the snapd.system-shutdown.service) cannot
   246  		// be started. Others like "snapd.seeded.service" are started
   247  		// as dependencies of snapd.service.
   248  		if snapdSkipStart(snapdUnits[unit].(*osutil.MemoryFileState).Content) {
   249  			continue
   250  		}
   251  		// Ensure to only restart if the unit was previously
   252  		// active. This ensures we DTRT on firstboot and do
   253  		// not stop e.g. snapd.socket because doing that
   254  		// would mean that the snapd.seeded.service is also
   255  		// stopped (independently of snapd.socket being
   256  		// active) which confuses the boot order (the unit
   257  		// exists before we are fully seeded).
   258  		isActive, err := sysd.IsActive(unit)
   259  		if err != nil {
   260  			return err
   261  		}
   262  		if isActive {
   263  			// we can never restart the snapd.socket because
   264  			// this will also bring down snapd itself
   265  			if unit != "snapd.socket" {
   266  				if err := sysd.Restart(unit, 5*time.Second); err != nil {
   267  					return err
   268  				}
   269  			}
   270  		} else {
   271  			if err := sysd.Start(unit); err != nil {
   272  				return err
   273  			}
   274  		}
   275  	}
   276  	// and finally start snapd.service (it will stop by itself and gets
   277  	// started by systemd then)
   278  	if err := sysd.Start("snapd.service"); err != nil {
   279  		return err
   280  	}
   281  	if err := sysd.StartNoBlock("snapd.seeded.service"); err != nil {
   282  		return err
   283  	}
   284  	// we cannot start snapd.autoimport in blocking mode because
   285  	// it has a "After=snapd.seeded.service" which means that on
   286  	// seeding a "systemctl start" that blocks would hang forever
   287  	// and we deadlock.
   288  	if err := sysd.StartNoBlock("snapd.autoimport.service"); err != nil {
   289  		return err
   290  	}
   291  
   292  	// Handle the user services
   293  	if err := writeSnapdUserServicesOnCore(s, inter); err != nil {
   294  		return err
   295  	}
   296  
   297  	// Handle D-Bus configuration
   298  	if err := writeSnapdDbusConfigOnCore(s); err != nil {
   299  		return err
   300  	}
   301  
   302  	if err := writeSnapdDbusActivationOnCore(s); err != nil {
   303  		return err
   304  	}
   305  
   306  	return nil
   307  }
   308  
   309  // undoSnapdUserServicesOnCore attempts to remove services that were deployed in
   310  // the filesystem as part of snapd snap installation. This should only be
   311  // executed as part of a controlled undo path.
   312  func undoSnapdServicesOnCore(s *snap.Info, sysd systemd.Systemd) error {
   313  	// list service, socket and timer units present in the snapd snap
   314  	serviceUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.service"))
   315  	if err != nil {
   316  		return err
   317  	}
   318  	socketUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.socket"))
   319  	if err != nil {
   320  		return err
   321  	}
   322  	timerUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.timer"))
   323  	if err != nil {
   324  		return err
   325  	}
   326  	units := append(socketUnits, serviceUnits...)
   327  	units = append(units, timerUnits...)
   328  
   329  	for _, snapdUnit := range units {
   330  		unit := filepath.Base(snapdUnit)
   331  		coreUnit := filepath.Join(dirs.GlobalRootDir, "lib/systemd/system", unit)
   332  		writtenUnitPath := filepath.Join(dirs.SnapServicesDir, unit)
   333  		if !osutil.FileExists(writtenUnitPath) {
   334  			continue
   335  		}
   336  		existsInCore := osutil.FileExists(coreUnit)
   337  
   338  		if !existsInCore {
   339  			// new unit that did not exist on core, disable and stop
   340  			if err := sysd.Disable(unit); err != nil {
   341  				logger.Noticef("failed to disable %q: %v", unit, err)
   342  			}
   343  			if err := sysd.Stop(unit, snapdServiceStopTimeout); err != nil {
   344  				return err
   345  			}
   346  		}
   347  		if err := os.Remove(writtenUnitPath); err != nil {
   348  			return err
   349  		}
   350  		if !existsInCore {
   351  			// nothing more to do here
   352  			continue
   353  		}
   354  
   355  		isEnabled, err := sysd.IsEnabled(unit)
   356  		if err != nil {
   357  			return err
   358  		}
   359  		if !isEnabled {
   360  			if err := sysd.Enable(unit); err != nil {
   361  				return err
   362  			}
   363  		}
   364  
   365  		if unit == "snapd.socket" {
   366  			// do not start the socket, snap failover handler will
   367  			// restart it
   368  			continue
   369  		}
   370  		skipStart, err := snapdUnitSkipStart(coreUnit)
   371  		if err != nil {
   372  			return err
   373  		}
   374  		if !skipStart {
   375  			// TODO: consider using sys.Restart() instead of is-active check
   376  			isActive, err := sysd.IsActive(unit)
   377  			if err != nil {
   378  				return err
   379  			}
   380  			if isActive {
   381  				if err := sysd.Restart(unit, snapdServiceStopTimeout); err != nil {
   382  					return err
   383  				}
   384  			} else {
   385  				if err := sysd.Start(unit); err != nil {
   386  					return err
   387  				}
   388  			}
   389  		}
   390  	}
   391  	return nil
   392  }
   393  
   394  func writeSnapdUserServicesOnCore(s *snap.Info, inter interacter) error {
   395  	// Ensure /etc/systemd/user exists
   396  	if err := os.MkdirAll(dirs.SnapUserServicesDir, 0755); err != nil {
   397  		return err
   398  	}
   399  
   400  	sysd := systemd.New(systemd.GlobalUserMode, inter)
   401  
   402  	serviceUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/lib/systemd/user/*.service"))
   403  	if err != nil {
   404  		return err
   405  	}
   406  	socketUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/lib/systemd/user/*.socket"))
   407  	if err != nil {
   408  		return err
   409  	}
   410  	units := append(serviceUnits, socketUnits...)
   411  
   412  	snapdUnits := make(map[string]osutil.FileState, len(units)+1)
   413  	for _, unit := range units {
   414  		st, err := os.Stat(unit)
   415  		if err != nil {
   416  			return err
   417  		}
   418  		content, err := ioutil.ReadFile(unit)
   419  		if err != nil {
   420  			return err
   421  		}
   422  		if execStartRe.Match(content) {
   423  			content = execStartRe.ReplaceAll(content, []byte(fmt.Sprintf("ExecStart=%s$1", s.MountDir())))
   424  			// when the service executes a command from the snapd snap, make
   425  			// sure the exec path points to the mount dir, and that the
   426  			// mount happens before the unit is started
   427  			content = append(content, []byte(fmt.Sprintf("\n[Unit]\nRequiresMountsFor=%s\n", s.MountDir()))...)
   428  		}
   429  
   430  		snapdUnits[filepath.Base(unit)] = &osutil.MemoryFileState{
   431  			Content: content,
   432  			Mode:    st.Mode(),
   433  		}
   434  	}
   435  	changed, removed, err := osutil.EnsureDirStateGlobs(dirs.SnapUserServicesDir, []string{"snapd.*.service", "snapd.*.socket"}, snapdUnits)
   436  	if err != nil {
   437  		// TODO: uhhhh, what do we do in this case?
   438  		return err
   439  	}
   440  	if (len(changed) + len(removed)) == 0 {
   441  		// nothing to do
   442  		return nil
   443  	}
   444  	// disable all removed units first
   445  	for _, unit := range removed {
   446  		if err := sysd.Disable(unit); err != nil {
   447  			logger.Noticef("failed to disable %q: %v", unit, err)
   448  		}
   449  	}
   450  
   451  	// enable/start all the new services
   452  	for _, unit := range changed {
   453  		if err := sysd.Disable(unit); err != nil {
   454  			logger.Noticef("failed to disable %q: %v", unit, err)
   455  		}
   456  		if err := sysd.Enable(unit); err != nil {
   457  			return err
   458  		}
   459  	}
   460  
   461  	return nil
   462  }
   463  
   464  // undoSnapdUserServicesOnCore attempts to remove user services that were
   465  // deployed in the filesystem as part of snapd snap installation. This should
   466  // only be executed as part of a controlled undo path.
   467  func undoSnapdUserServicesOnCore(s *snap.Info, inter interacter) error {
   468  	sysd := systemd.NewUnderRoot(dirs.GlobalRootDir, systemd.GlobalUserMode, inter)
   469  
   470  	// list user service and socket units present in the snapd snap
   471  	serviceUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/lib/systemd/user/*.service"))
   472  	if err != nil {
   473  		return err
   474  	}
   475  	socketUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/lib/systemd/user/*.socket"))
   476  	if err != nil {
   477  		return err
   478  	}
   479  	units := append(serviceUnits, socketUnits...)
   480  
   481  	for _, srcUnit := range units {
   482  		unit := filepath.Base(srcUnit)
   483  		writtenUnitPath := filepath.Join(dirs.SnapUserServicesDir, unit)
   484  		if !osutil.FileExists(writtenUnitPath) {
   485  			continue
   486  		}
   487  		coreUnit := filepath.Join(dirs.GlobalRootDir, "usr/lib/systemd/user", unit)
   488  		existsInCore := osutil.FileExists(coreUnit)
   489  
   490  		if err := sysd.Disable(unit); err != nil {
   491  			logger.Noticef("failed to disable %q: %v", unit, err)
   492  		}
   493  		if err := os.Remove(writtenUnitPath); err != nil {
   494  			return err
   495  		}
   496  		if !existsInCore {
   497  			// new unit that did not exist on core
   498  			continue
   499  		}
   500  		if err := sysd.Enable(unit); err != nil {
   501  			return err
   502  		}
   503  	}
   504  	return nil
   505  }
   506  
   507  func DeriveSnapdDBusConfig(s *snap.Info) (sessionContent, systemContent map[string]osutil.FileState, err error) {
   508  	sessionConfigs, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/share/dbus-1/session.d/snapd.*.conf"))
   509  	if err != nil {
   510  		return nil, nil, err
   511  	}
   512  	sessionContent = make(map[string]osutil.FileState, len(sessionConfigs)+1)
   513  	for _, config := range sessionConfigs {
   514  		sessionContent[filepath.Base(config)] = &osutil.FileReference{
   515  			Path: config,
   516  		}
   517  	}
   518  
   519  	systemConfigs, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/share/dbus-1/system.d/snapd.*.conf"))
   520  	if err != nil {
   521  		return nil, nil, err
   522  	}
   523  	systemContent = make(map[string]osutil.FileState, len(systemConfigs)+1)
   524  	for _, config := range systemConfigs {
   525  		systemContent[filepath.Base(config)] = &osutil.FileReference{
   526  			Path: config,
   527  		}
   528  	}
   529  
   530  	return sessionContent, systemContent, nil
   531  }
   532  
   533  func isReadOnlyFsError(err error) bool {
   534  	if err == nil {
   535  		return false
   536  	}
   537  	if e, ok := err.(*os.PathError); ok {
   538  		err = e.Err
   539  	}
   540  	if e, ok := err.(syscall.Errno); ok {
   541  		return e == syscall.EROFS
   542  	}
   543  	return false
   544  }
   545  
   546  var ensureDirState = osutil.EnsureDirState
   547  
   548  func writeSnapdDbusConfigOnCore(s *snap.Info) error {
   549  	sessionContent, systemContent, err := DeriveSnapdDBusConfig(s)
   550  	if err != nil {
   551  		return err
   552  	}
   553  
   554  	_, _, err = ensureDirState(dirs.SnapDBusSessionPolicyDir, "snapd.*.conf", sessionContent)
   555  	if err != nil {
   556  		if isReadOnlyFsError(err) {
   557  			// If /etc/dbus-1/session.d is read-only (which may be the case on very old core18), then
   558  			// err is os.PathError with syscall.Errno underneath. Hitting this prevents snapd refresh,
   559  			// so log the error but carry on. This fixes LP: 1899664.
   560  			// XXX: ideally we should regenerate session files elsewhere if we fail here (otherwise
   561  			// this will only happen on future snapd refresh), but realistically this
   562  			// is not relevant on core18 devices.
   563  			logger.Noticef("%s appears to be read-only, could not write snapd dbus config files", dirs.SnapDBusSessionPolicyDir)
   564  		} else {
   565  			return err
   566  		}
   567  	}
   568  
   569  	_, _, err = osutil.EnsureDirState(dirs.SnapDBusSystemPolicyDir, "snapd.*.conf", systemContent)
   570  	if err != nil {
   571  		return err
   572  	}
   573  
   574  	return nil
   575  }
   576  
   577  func undoSnapdDbusConfigOnCore(s *snap.Info) error {
   578  	_, _, err := osutil.EnsureDirState(dirs.SnapDBusSystemPolicyDir, "snapd.*.conf", nil)
   579  	if err != nil {
   580  		return err
   581  	}
   582  	_, _, err = osutil.EnsureDirState(dirs.SnapDBusSessionPolicyDir, "snapd.*.conf", nil)
   583  	return err
   584  }
   585  
   586  var dbusSessionServices = []string{
   587  	"io.snapcraft.Launcher.service",
   588  	"io.snapcraft.Settings.service",
   589  	"io.snapcraft.SessionAgent.service",
   590  }
   591  
   592  func writeSnapdDbusActivationOnCore(s *snap.Info) error {
   593  	if err := os.MkdirAll(dirs.SnapDBusSessionServicesDir, 0755); err != nil {
   594  		return err
   595  	}
   596  
   597  	content := make(map[string]osutil.FileState, len(dbusSessionServices)+1)
   598  	for _, service := range dbusSessionServices {
   599  		content[service] = &osutil.FileReference{
   600  			Path: filepath.Join(s.MountDir(), "usr/share/dbus-1/services", service),
   601  		}
   602  	}
   603  
   604  	_, _, err := osutil.EnsureDirStateGlobs(dirs.SnapDBusSessionServicesDir, dbusSessionServices, content)
   605  	return err
   606  }
   607  
   608  func undoSnapdDbusActivationOnCore(s *snap.Info) error {
   609  	_, _, err := osutil.EnsureDirStateGlobs(dirs.SnapDBusSessionServicesDir, dbusSessionServices, nil)
   610  	return err
   611  }
   612  
   613  // RemoveSnapdSnapServicesOnCore removes the snapd services generated by a prior
   614  // call to AddSnapdSnapServices. The core snap is used as the reference for
   615  // restoring the system state, making this undo helper suitable for use when
   616  // reverting the first installation of the snapd snap on a core device.
   617  func RemoveSnapdSnapServicesOnCore(s *snap.Info, inter interacter) error {
   618  	if snapType := s.Type(); snapType != snap.TypeSnapd {
   619  		return fmt.Errorf("internal error: removing explicit snapd services for snap %q type %q is unexpected", s.InstanceName(), snapType)
   620  	}
   621  
   622  	// snapd services are never written on classic, nothing to remove
   623  	if release.OnClassic {
   624  		return nil
   625  	}
   626  
   627  	sysd := systemd.NewUnderRoot(dirs.GlobalRootDir, systemd.SystemMode, inter)
   628  
   629  	if err := undoSnapdDbusActivationOnCore(s); err != nil {
   630  		return err
   631  	}
   632  	if err := undoSnapdDbusConfigOnCore(s); err != nil {
   633  		return err
   634  	}
   635  	if err := undoSnapdServicesOnCore(s, sysd); err != nil {
   636  		return err
   637  	}
   638  	if err := undoSnapdUserServicesOnCore(s, inter); err != nil {
   639  		return err
   640  	}
   641  	if err := undoSnapdToolingMountUnit(sysd); err != nil {
   642  		return err
   643  	}
   644  	// XXX: reload after all operations?
   645  	return nil
   646  }