github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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/logger"
    29  	"github.com/snapcore/snapd/progress"
    30  	"github.com/snapcore/snapd/snap"
    31  	"github.com/snapcore/snapd/timings"
    32  	"github.com/snapcore/snapd/wrappers"
    33  )
    34  
    35  // LinkContext carries additional information about the current or the previous
    36  // state of the snap
    37  type LinkContext struct {
    38  	// FirstInstall indicates whether this is the first time given snap is
    39  	// installed
    40  	FirstInstall bool
    41  	// PrevDisabledServices is a list snap services that were manually
    42  	// disable in the previous revisions of this snap
    43  	PrevDisabledServices []string
    44  
    45  	// VitalityRank is used to hint how much the services should be
    46  	// protected from the OOM killer
    47  	VitalityRank int
    48  }
    49  
    50  func updateCurrentSymlinks(info *snap.Info) (e error) {
    51  	mountDir := info.MountDir()
    52  
    53  	currentActiveSymlink := filepath.Join(mountDir, "..", "current")
    54  	if err := os.Remove(currentActiveSymlink); err != nil && !os.IsNotExist(err) {
    55  		logger.Noticef("Cannot remove %q: %v", currentActiveSymlink, err)
    56  	}
    57  
    58  	dataDir := info.DataDir()
    59  	currentDataSymlink := filepath.Join(dataDir, "..", "current")
    60  	if err := os.Remove(currentDataSymlink); err != nil && !os.IsNotExist(err) {
    61  		logger.Noticef("Cannot remove %q: %v", currentDataSymlink, err)
    62  	}
    63  
    64  	if err := os.MkdirAll(dataDir, 0755); err != nil {
    65  		return err
    66  	}
    67  	cleanup := []string{dataDir, ""}[:1] // cleanup has cap(2)
    68  	defer func() {
    69  		if e == nil {
    70  			return
    71  		}
    72  		for _, d := range cleanup {
    73  			if err := os.Remove(d); err != nil {
    74  				logger.Noticef("Cannot clean up %q: %v", d, err)
    75  			}
    76  		}
    77  	}()
    78  
    79  	if err := os.Symlink(filepath.Base(dataDir), currentDataSymlink); err != nil {
    80  		return err
    81  	}
    82  	cleanup = append(cleanup, currentDataSymlink)
    83  
    84  	return os.Symlink(filepath.Base(mountDir), currentActiveSymlink)
    85  }
    86  
    87  func hasFontConfigCache(info *snap.Info) bool {
    88  	if info.Type() == snap.TypeOS || info.Type() == snap.TypeSnapd {
    89  		return true
    90  	}
    91  	return false
    92  }
    93  
    94  // LinkSnap makes the snap available by generating wrappers and setting the current symlinks.
    95  func (b Backend) LinkSnap(info *snap.Info, dev boot.Device, linkCtx LinkContext, tm timings.Measurer) (rebootRequired bool, e error) {
    96  	if info.Revision.Unset() {
    97  		return false, fmt.Errorf("cannot link snap %q with unset revision", info.InstanceName())
    98  	}
    99  
   100  	var err error
   101  	timings.Run(tm, "generate-wrappers", fmt.Sprintf("generate wrappers for snap %s", info.InstanceName()), func(timings.Measurer) {
   102  		err = b.generateWrappers(info, linkCtx)
   103  	})
   104  	if err != nil {
   105  		return false, err
   106  	}
   107  	defer func() {
   108  		if e == nil {
   109  			return
   110  		}
   111  		timings.Run(tm, "remove-wrappers", fmt.Sprintf("remove wrappers of snap %s", info.InstanceName()), func(timings.Measurer) {
   112  			removeGeneratedWrappers(info, linkCtx.FirstInstall, progress.Null)
   113  		})
   114  	}()
   115  
   116  	// fontconfig is only relevant on classic and is carried by 'core' or
   117  	// 'snapd' snaps
   118  	// for non-core snaps, fontconfig cache needs to be updated before the
   119  	// snap applications are runnable
   120  	if dev.Classic() && !hasFontConfigCache(info) {
   121  		timings.Run(tm, "update-fc-cache", "update font config caches", func(timings.Measurer) {
   122  			// XXX: does this need cleaning up? (afaict no)
   123  			if err := updateFontconfigCaches(); err != nil {
   124  				logger.Noticef("cannot update fontconfig cache: %v", err)
   125  			}
   126  		})
   127  	}
   128  
   129  	reboot, err := boot.Participant(info, info.Type(), dev).SetNextBoot()
   130  	if err != nil {
   131  		return false, err
   132  	}
   133  
   134  	if err := updateCurrentSymlinks(info); err != nil {
   135  		return false, err
   136  	}
   137  	// if anything below here could return error, you need to
   138  	// somehow clean up whatever updateCurrentSymlinks did
   139  
   140  	// for core snap, fontconfig cache can be updated after the snap has
   141  	// been made available
   142  	if dev.Classic() && hasFontConfigCache(info) {
   143  		timings.Run(tm, "update-fc-cache", "update font config caches", func(timings.Measurer) {
   144  			if err := updateFontconfigCaches(); err != nil {
   145  				logger.Noticef("cannot update fontconfig cache: %v", err)
   146  			}
   147  		})
   148  	}
   149  
   150  	return reboot, nil
   151  }
   152  
   153  func (b Backend) StartServices(apps []*snap.AppInfo, meter progress.Meter, tm timings.Measurer) error {
   154  	return wrappers.StartServices(apps, nil, nil, meter, tm)
   155  }
   156  
   157  func (b Backend) StopServices(apps []*snap.AppInfo, reason snap.ServiceStopReason, meter progress.Meter, tm timings.Measurer) error {
   158  	return wrappers.StopServices(apps, nil, reason, meter, tm)
   159  }
   160  
   161  func (b Backend) generateWrappers(s *snap.Info, linkCtx LinkContext) error {
   162  	var err error
   163  	var cleanupFuncs []func(*snap.Info) error
   164  	defer func() {
   165  		if err != nil {
   166  			for _, cleanup := range cleanupFuncs {
   167  				cleanup(s)
   168  			}
   169  		}
   170  	}()
   171  
   172  	disabledSvcs := linkCtx.PrevDisabledServices
   173  	if s.Type() == snap.TypeSnapd {
   174  		// snapd services are handled separately
   175  		return generateSnapdWrappers(s)
   176  	}
   177  
   178  	// add the CLI apps from the snap.yaml
   179  	if err = wrappers.AddSnapBinaries(s); err != nil {
   180  		return err
   181  	}
   182  	cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapBinaries)
   183  
   184  	// add the daemons from the snap.yaml
   185  	opts := &wrappers.AddSnapServicesOptions{
   186  		Preseeding:   b.preseed,
   187  		VitalityRank: linkCtx.VitalityRank,
   188  	}
   189  	if err = wrappers.AddSnapServices(s, disabledSvcs, opts, progress.Null); err != nil {
   190  		return err
   191  	}
   192  	cleanupFuncs = append(cleanupFuncs, func(s *snap.Info) error {
   193  		return wrappers.RemoveSnapServices(s, progress.Null)
   194  	})
   195  
   196  	// add the desktop files
   197  	if err = wrappers.AddSnapDesktopFiles(s); err != nil {
   198  		return err
   199  	}
   200  	cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapDesktopFiles)
   201  
   202  	// add the desktop icons
   203  	if err = wrappers.AddSnapIcons(s); err != nil {
   204  		return err
   205  	}
   206  	cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapIcons)
   207  
   208  	return nil
   209  }
   210  
   211  func removeGeneratedWrappers(s *snap.Info, firstInstallUndo bool, meter progress.Meter) error {
   212  	if s.Type() == snap.TypeSnapd {
   213  		return removeGeneratedSnapdWrappers(s, firstInstallUndo, progress.Null)
   214  	}
   215  
   216  	err1 := wrappers.RemoveSnapBinaries(s)
   217  	if err1 != nil {
   218  		logger.Noticef("Cannot remove binaries for %q: %v", s.InstanceName(), err1)
   219  	}
   220  
   221  	err2 := wrappers.RemoveSnapServices(s, meter)
   222  	if err2 != nil {
   223  		logger.Noticef("Cannot remove services for %q: %v", s.InstanceName(), err2)
   224  	}
   225  
   226  	err3 := wrappers.RemoveSnapDesktopFiles(s)
   227  	if err3 != nil {
   228  		logger.Noticef("Cannot remove desktop files for %q: %v", s.InstanceName(), err3)
   229  	}
   230  
   231  	err4 := wrappers.RemoveSnapIcons(s)
   232  	if err4 != nil {
   233  		logger.Noticef("Cannot remove desktop icons for %q: %v", s.InstanceName(), err4)
   234  	}
   235  
   236  	return firstErr(err1, err2, err3, err4)
   237  }
   238  
   239  func generateSnapdWrappers(s *snap.Info) error {
   240  	// snapd services are handled separately via an explicit helper
   241  	return wrappers.AddSnapdSnapServices(s, progress.Null)
   242  }
   243  
   244  func removeGeneratedSnapdWrappers(s *snap.Info, firstInstall bool, meter progress.Meter) error {
   245  	if !firstInstall {
   246  		// snapd service units are only removed during first
   247  		// installation of the snapd snap, in other scenarios they are
   248  		// overwritten
   249  		return nil
   250  	}
   251  	return wrappers.RemoveSnapdSnapServicesOnCore(s, meter)
   252  }
   253  
   254  // UnlinkSnap makes the snap unavailable to the system removing wrappers and
   255  // symlinks. The firstInstallUndo is true when undoing the first installation of
   256  // the snap.
   257  func (b Backend) UnlinkSnap(info *snap.Info, linkCtx LinkContext, meter progress.Meter) error {
   258  	// remove generated services, binaries etc
   259  	err1 := removeGeneratedWrappers(info, linkCtx.FirstInstall, meter)
   260  
   261  	// and finally remove current symlinks
   262  	err2 := removeCurrentSymlinks(info)
   263  
   264  	// FIXME: aggregate errors instead
   265  	return firstErr(err1, err2)
   266  }
   267  
   268  // ServicesEnableState returns the current enabled/disabled states of a snap's
   269  // services, primarily for committing before snap removal/disable/revert.
   270  func (b Backend) ServicesEnableState(info *snap.Info, meter progress.Meter) (map[string]bool, error) {
   271  	return wrappers.ServicesEnableState(info, meter)
   272  }
   273  
   274  func (b Backend) QueryDisabledServices(info *snap.Info, pb progress.Meter) ([]string, error) {
   275  	return wrappers.QueryDisabledServices(info, pb)
   276  }
   277  
   278  func removeCurrentSymlinks(info snap.PlaceInfo) error {
   279  	var err1, err2 error
   280  
   281  	// the snap "current" symlink
   282  	currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current")
   283  	err1 = os.Remove(currentActiveSymlink)
   284  	if err1 != nil && !os.IsNotExist(err1) {
   285  		logger.Noticef("Cannot remove %q: %v", currentActiveSymlink, err1)
   286  	} else {
   287  		err1 = nil
   288  	}
   289  
   290  	// the data "current" symlink
   291  	currentDataSymlink := filepath.Join(info.DataDir(), "..", "current")
   292  	err2 = os.Remove(currentDataSymlink)
   293  	if err2 != nil && !os.IsNotExist(err2) {
   294  		logger.Noticef("Cannot remove %q: %v", currentDataSymlink, err2)
   295  	} else {
   296  		err2 = nil
   297  	}
   298  
   299  	if err1 != nil && err2 != nil {
   300  		return fmt.Errorf("cannot remove snap current symlink: %v and %v", err1, err2)
   301  	} else if err1 != nil {
   302  		return fmt.Errorf("cannot remove snap current symlink: %v", err1)
   303  	} else if err2 != nil {
   304  		return fmt.Errorf("cannot remove snap current symlink: %v", err2)
   305  	}
   306  
   307  	return nil
   308  }