github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/snapstate/backend/link.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package backend
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	"github.com/snapcore/snapd/boot"
    28  	"github.com/snapcore/snapd/cmd/snaplock/runinhibit"
    29  	"github.com/snapcore/snapd/logger"
    30  	"github.com/snapcore/snapd/progress"
    31  	"github.com/snapcore/snapd/snap"
    32  	"github.com/snapcore/snapd/snap/quota"
    33  	"github.com/snapcore/snapd/timings"
    34  	"github.com/snapcore/snapd/wrappers"
    35  )
    36  
    37  // LinkContext carries additional information about the current or the previous
    38  // state of the snap
    39  type LinkContext struct {
    40  	// FirstInstall indicates whether this is the first time given snap is
    41  	// installed
    42  	FirstInstall bool
    43  
    44  	// ServiceOptions is used to configure services.
    45  	ServiceOptions *wrappers.SnapServiceOptions
    46  
    47  	// RunInhibitHint is used only in Unlink snap, and can be used to
    48  	// establish run inhibition lock for refresh operations.
    49  	RunInhibitHint runinhibit.Hint
    50  
    51  	// RequireMountedSnapdSnap indicates that the apps and services
    52  	// generated when linking need to use tooling from the snapd snap mount.
    53  	RequireMountedSnapdSnap bool
    54  }
    55  
    56  func updateCurrentSymlinks(info *snap.Info) (e error) {
    57  	mountDir := info.MountDir()
    58  
    59  	currentActiveSymlink := filepath.Join(mountDir, "..", "current")
    60  	if err := os.Remove(currentActiveSymlink); err != nil && !os.IsNotExist(err) {
    61  		logger.Noticef("Cannot remove %q: %v", currentActiveSymlink, err)
    62  	}
    63  
    64  	dataDir := info.DataDir()
    65  	currentDataSymlink := filepath.Join(dataDir, "..", "current")
    66  	if err := os.Remove(currentDataSymlink); err != nil && !os.IsNotExist(err) {
    67  		logger.Noticef("Cannot remove %q: %v", currentDataSymlink, err)
    68  	}
    69  
    70  	if err := os.MkdirAll(dataDir, 0755); err != nil {
    71  		return err
    72  	}
    73  	cleanup := []string{dataDir, ""}[:1] // cleanup has cap(2)
    74  	defer func() {
    75  		if e == nil {
    76  			return
    77  		}
    78  		for _, d := range cleanup {
    79  			if err := os.Remove(d); err != nil {
    80  				logger.Noticef("Cannot clean up %q: %v", d, err)
    81  			}
    82  		}
    83  	}()
    84  
    85  	if err := os.Symlink(filepath.Base(dataDir), currentDataSymlink); err != nil {
    86  		return err
    87  	}
    88  	cleanup = append(cleanup, currentDataSymlink)
    89  
    90  	return os.Symlink(filepath.Base(mountDir), currentActiveSymlink)
    91  }
    92  
    93  func hasFontConfigCache(info *snap.Info) bool {
    94  	if info.Type() == snap.TypeOS || info.Type() == snap.TypeSnapd {
    95  		return true
    96  	}
    97  	return false
    98  }
    99  
   100  // LinkSnap makes the snap available by generating wrappers and setting the current symlinks.
   101  func (b Backend) LinkSnap(info *snap.Info, dev boot.Device, linkCtx LinkContext, tm timings.Measurer) (rebootRequired bool, e error) {
   102  	if info.Revision.Unset() {
   103  		return false, fmt.Errorf("cannot link snap %q with unset revision", info.InstanceName())
   104  	}
   105  
   106  	var err error
   107  	timings.Run(tm, "generate-wrappers", fmt.Sprintf("generate wrappers for snap %s", info.InstanceName()), func(timings.Measurer) {
   108  		err = b.generateWrappers(info, linkCtx)
   109  	})
   110  	if err != nil {
   111  		return false, err
   112  	}
   113  	defer func() {
   114  		if e == nil {
   115  			return
   116  		}
   117  		timings.Run(tm, "remove-wrappers", fmt.Sprintf("remove wrappers of snap %s", info.InstanceName()), func(timings.Measurer) {
   118  			removeGeneratedWrappers(info, linkCtx.FirstInstall, progress.Null)
   119  		})
   120  	}()
   121  
   122  	// fontconfig is only relevant on classic and is carried by 'core' or
   123  	// 'snapd' snaps
   124  	// for non-core snaps, fontconfig cache needs to be updated before the
   125  	// snap applications are runnable
   126  	if dev.Classic() && !hasFontConfigCache(info) {
   127  		timings.Run(tm, "update-fc-cache", "update font config caches", func(timings.Measurer) {
   128  			// XXX: does this need cleaning up? (afaict no)
   129  			if err := updateFontconfigCaches(); err != nil {
   130  				logger.Noticef("cannot update fontconfig cache: %v", err)
   131  			}
   132  		})
   133  	}
   134  
   135  	reboot, err := boot.Participant(info, info.Type(), dev).SetNextBoot()
   136  	if err != nil {
   137  		return false, err
   138  	}
   139  
   140  	if err := updateCurrentSymlinks(info); err != nil {
   141  		return false, err
   142  	}
   143  	// if anything below here could return error, you need to
   144  	// somehow clean up whatever updateCurrentSymlinks did
   145  
   146  	// for core snap, fontconfig cache can be updated after the snap has
   147  	// been made available
   148  	if dev.Classic() && hasFontConfigCache(info) {
   149  		timings.Run(tm, "update-fc-cache", "update font config caches", func(timings.Measurer) {
   150  			if err := updateFontconfigCaches(); err != nil {
   151  				logger.Noticef("cannot update fontconfig cache: %v", err)
   152  			}
   153  		})
   154  	}
   155  
   156  	// Stop inhibiting application startup by removing the inhibitor file.
   157  	if err := runinhibit.Unlock(info.InstanceName()); err != nil {
   158  		return false, err
   159  	}
   160  
   161  	return reboot, nil
   162  }
   163  
   164  func (b Backend) StartServices(apps []*snap.AppInfo, disabledSvcs []string, meter progress.Meter, tm timings.Measurer) error {
   165  	flags := &wrappers.StartServicesFlags{Enable: true}
   166  	return wrappers.StartServices(apps, disabledSvcs, flags, meter, tm)
   167  }
   168  
   169  func (b Backend) StopServices(apps []*snap.AppInfo, reason snap.ServiceStopReason, meter progress.Meter, tm timings.Measurer) error {
   170  	return wrappers.StopServices(apps, nil, reason, meter, tm)
   171  }
   172  
   173  func (b Backend) generateWrappers(s *snap.Info, linkCtx LinkContext) error {
   174  	var err error
   175  	var cleanupFuncs []func(*snap.Info) error
   176  	defer func() {
   177  		if err != nil {
   178  			for _, cleanup := range cleanupFuncs {
   179  				cleanup(s)
   180  			}
   181  		}
   182  	}()
   183  
   184  	if s.Type() == snap.TypeSnapd {
   185  		// snapd services are handled separately
   186  		return GenerateSnapdWrappers(s)
   187  	}
   188  
   189  	// add the CLI apps from the snap.yaml
   190  	if err = wrappers.AddSnapBinaries(s); err != nil {
   191  		return err
   192  	}
   193  	cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapBinaries)
   194  
   195  	vitalityRank := 0
   196  	var quotaGrp *quota.Group
   197  	if linkCtx.ServiceOptions != nil {
   198  		vitalityRank = linkCtx.ServiceOptions.VitalityRank
   199  		quotaGrp = linkCtx.ServiceOptions.QuotaGroup
   200  	}
   201  	// add the daemons from the snap.yaml
   202  	opts := &wrappers.AddSnapServicesOptions{
   203  		VitalityRank:            vitalityRank,
   204  		Preseeding:              b.preseed,
   205  		RequireMountedSnapdSnap: linkCtx.RequireMountedSnapdSnap,
   206  		QuotaGroup:              quotaGrp,
   207  	}
   208  	// TODO: switch to EnsureSnapServices
   209  	if err = wrappers.AddSnapServices(s, opts, progress.Null); err != nil {
   210  		return err
   211  	}
   212  	cleanupFuncs = append(cleanupFuncs, func(s *snap.Info) error {
   213  		return wrappers.RemoveSnapServices(s, progress.Null)
   214  	})
   215  
   216  	// add D-Bus service activation files
   217  	if err = wrappers.AddSnapDBusActivationFiles(s); err != nil {
   218  		return err
   219  	}
   220  	cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapDBusActivationFiles)
   221  
   222  	// add the desktop files
   223  	if err = wrappers.AddSnapDesktopFiles(s); err != nil {
   224  		return err
   225  	}
   226  	cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapDesktopFiles)
   227  
   228  	// add the desktop icons
   229  	if err = wrappers.AddSnapIcons(s); err != nil {
   230  		return err
   231  	}
   232  	cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapIcons)
   233  
   234  	return nil
   235  }
   236  
   237  func removeGeneratedWrappers(s *snap.Info, firstInstallUndo bool, meter progress.Meter) error {
   238  	if s.Type() == snap.TypeSnapd {
   239  		return removeGeneratedSnapdWrappers(s, firstInstallUndo, progress.Null)
   240  	}
   241  
   242  	err1 := wrappers.RemoveSnapBinaries(s)
   243  	if err1 != nil {
   244  		logger.Noticef("Cannot remove binaries for %q: %v", s.InstanceName(), err1)
   245  	}
   246  
   247  	err2 := wrappers.RemoveSnapDBusActivationFiles(s)
   248  	if err2 != nil {
   249  		logger.Noticef("Cannot remove D-Bus activation for %q: %v", s.InstanceName(), err2)
   250  	}
   251  
   252  	err3 := wrappers.RemoveSnapServices(s, meter)
   253  	if err3 != nil {
   254  		logger.Noticef("Cannot remove services for %q: %v", s.InstanceName(), err3)
   255  	}
   256  
   257  	err4 := wrappers.RemoveSnapDesktopFiles(s)
   258  	if err4 != nil {
   259  		logger.Noticef("Cannot remove desktop files for %q: %v", s.InstanceName(), err4)
   260  	}
   261  
   262  	err5 := wrappers.RemoveSnapIcons(s)
   263  	if err5 != nil {
   264  		logger.Noticef("Cannot remove desktop icons for %q: %v", s.InstanceName(), err5)
   265  	}
   266  
   267  	return firstErr(err1, err2, err3, err4, err5)
   268  }
   269  
   270  func GenerateSnapdWrappers(s *snap.Info) error {
   271  	// snapd services are handled separately via an explicit helper
   272  	return wrappers.AddSnapdSnapServices(s, progress.Null)
   273  }
   274  
   275  func removeGeneratedSnapdWrappers(s *snap.Info, firstInstall bool, meter progress.Meter) error {
   276  	if !firstInstall {
   277  		// snapd service units are only removed during first
   278  		// installation of the snapd snap, in other scenarios they are
   279  		// overwritten
   280  		return nil
   281  	}
   282  	return wrappers.RemoveSnapdSnapServicesOnCore(s, meter)
   283  }
   284  
   285  // UnlinkSnap makes the snap unavailable to the system removing wrappers and
   286  // symlinks. The firstInstallUndo is true when undoing the first installation of
   287  // the snap.
   288  func (b Backend) UnlinkSnap(info *snap.Info, linkCtx LinkContext, meter progress.Meter) error {
   289  	var err0 error
   290  	if hint := linkCtx.RunInhibitHint; hint != runinhibit.HintNotInhibited {
   291  		// inhibit startup of new programs
   292  		err0 = runinhibit.LockWithHint(info.InstanceName(), hint)
   293  	}
   294  
   295  	// remove generated services, binaries etc
   296  	err1 := removeGeneratedWrappers(info, linkCtx.FirstInstall, meter)
   297  
   298  	// and finally remove current symlinks
   299  	err2 := removeCurrentSymlinks(info)
   300  
   301  	// FIXME: aggregate errors instead
   302  	return firstErr(err0, err1, err2)
   303  }
   304  
   305  // ServicesEnableState returns the current enabled/disabled states of a snap's
   306  // services, primarily for committing before snap removal/disable/revert.
   307  func (b Backend) ServicesEnableState(info *snap.Info, meter progress.Meter) (map[string]bool, error) {
   308  	return wrappers.ServicesEnableState(info, meter)
   309  }
   310  
   311  func (b Backend) QueryDisabledServices(info *snap.Info, pb progress.Meter) ([]string, error) {
   312  	return wrappers.QueryDisabledServices(info, pb)
   313  }
   314  
   315  func removeCurrentSymlinks(info snap.PlaceInfo) error {
   316  	var err1, err2 error
   317  
   318  	// the snap "current" symlink
   319  	currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current")
   320  	err1 = os.Remove(currentActiveSymlink)
   321  	if err1 != nil && !os.IsNotExist(err1) {
   322  		logger.Noticef("Cannot remove %q: %v", currentActiveSymlink, err1)
   323  	} else {
   324  		err1 = nil
   325  	}
   326  
   327  	// the data "current" symlink
   328  	currentDataSymlink := filepath.Join(info.DataDir(), "..", "current")
   329  	err2 = os.Remove(currentDataSymlink)
   330  	if err2 != nil && !os.IsNotExist(err2) {
   331  		logger.Noticef("Cannot remove %q: %v", currentDataSymlink, err2)
   332  	} else {
   333  		err2 = nil
   334  	}
   335  
   336  	if err1 != nil && err2 != nil {
   337  		return fmt.Errorf("cannot remove snap current symlink: %v and %v", err1, err2)
   338  	} else if err1 != nil {
   339  		return fmt.Errorf("cannot remove snap current symlink: %v", err1)
   340  	} else if err2 != nil {
   341  		return fmt.Errorf("cannot remove snap current symlink: %v", err2)
   342  	}
   343  
   344  	return nil
   345  }