github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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/asserts"
    28  	"github.com/snapcore/snapd/boot"
    29  	"github.com/snapcore/snapd/logger"
    30  	"github.com/snapcore/snapd/progress"
    31  	"github.com/snapcore/snapd/release"
    32  	"github.com/snapcore/snapd/snap"
    33  	"github.com/snapcore/snapd/timings"
    34  	"github.com/snapcore/snapd/wrappers"
    35  )
    36  
    37  func updateCurrentSymlinks(info *snap.Info) (e error) {
    38  	mountDir := info.MountDir()
    39  
    40  	currentActiveSymlink := filepath.Join(mountDir, "..", "current")
    41  	if err := os.Remove(currentActiveSymlink); err != nil && !os.IsNotExist(err) {
    42  		logger.Noticef("Cannot remove %q: %v", currentActiveSymlink, err)
    43  	}
    44  
    45  	dataDir := info.DataDir()
    46  	currentDataSymlink := filepath.Join(dataDir, "..", "current")
    47  	if err := os.Remove(currentDataSymlink); err != nil && !os.IsNotExist(err) {
    48  		logger.Noticef("Cannot remove %q: %v", currentDataSymlink, err)
    49  	}
    50  
    51  	if err := os.MkdirAll(dataDir, 0755); err != nil {
    52  		return err
    53  	}
    54  	cleanup := []string{dataDir, ""}[:1] // cleanup has cap(2)
    55  	defer func() {
    56  		if e == nil {
    57  			return
    58  		}
    59  		for _, d := range cleanup {
    60  			if err := os.Remove(d); err != nil {
    61  				logger.Noticef("Cannot clean up %q: %v", d, err)
    62  			}
    63  		}
    64  	}()
    65  
    66  	if err := os.Symlink(filepath.Base(dataDir), currentDataSymlink); err != nil {
    67  		return err
    68  	}
    69  	cleanup = append(cleanup, currentDataSymlink)
    70  
    71  	return os.Symlink(filepath.Base(mountDir), currentActiveSymlink)
    72  }
    73  
    74  func hasFontConfigCache(info *snap.Info) bool {
    75  	if info.GetType() == snap.TypeOS || info.GetType() == snap.TypeSnapd {
    76  		return true
    77  	}
    78  	return false
    79  }
    80  
    81  // LinkSnap makes the snap available by generating wrappers and setting the current symlinks.
    82  func (b Backend) LinkSnap(info *snap.Info, model *asserts.Model, tm timings.Measurer) (e error) {
    83  	if info.Revision.Unset() {
    84  		return fmt.Errorf("cannot link snap %q with unset revision", info.InstanceName())
    85  	}
    86  
    87  	var err error
    88  	timings.Run(tm, "generate-wrappers", fmt.Sprintf("generate wrappers for snap %s", info.InstanceName()), func(timings.Measurer) {
    89  		err = generateWrappers(info)
    90  	})
    91  	if err != nil {
    92  		return err
    93  	}
    94  	defer func() {
    95  		if e == nil {
    96  			return
    97  		}
    98  		timings.Run(tm, "remove-wrappers", fmt.Sprintf("remove wrappers of snap %s", info.InstanceName()), func(timings.Measurer) {
    99  			removeGeneratedWrappers(info, progress.Null)
   100  		})
   101  	}()
   102  
   103  	// fontconfig is only relevant on classic and is carried by 'core' or
   104  	// 'snapd' snaps
   105  	// for non-core snaps, fontconfig cache needs to be updated before the
   106  	// snap applications are runnable
   107  	if release.OnClassic && !hasFontConfigCache(info) {
   108  		timings.Run(tm, "update-fc-cache", "update font config caches", func(timings.Measurer) {
   109  			// XXX: does this need cleaning up? (afaict no)
   110  			if err := updateFontconfigCaches(); err != nil {
   111  				logger.Noticef("cannot update fontconfig cache: %v", err)
   112  			}
   113  		})
   114  	}
   115  
   116  	if err := boot.Participant(info, info.GetType(), model, release.OnClassic).SetNextBoot(); err != nil {
   117  		return err
   118  	}
   119  
   120  	if err := updateCurrentSymlinks(info); err != nil {
   121  		return err
   122  	}
   123  	// if anything below here could return error, you need to
   124  	// somehow clean up whatever updateCurrentSymlinks did
   125  
   126  	// for core snap, fontconfig cache can be updated after the snap has
   127  	// been made available
   128  	if release.OnClassic && hasFontConfigCache(info) {
   129  		timings.Run(tm, "update-fc-cache", "update font config caches", func(timings.Measurer) {
   130  			if err := updateFontconfigCaches(); err != nil {
   131  				logger.Noticef("cannot update fontconfig cache: %v", err)
   132  			}
   133  		})
   134  	}
   135  	return nil
   136  }
   137  
   138  func (b Backend) StartServices(apps []*snap.AppInfo, meter progress.Meter, tm timings.Measurer) error {
   139  	return wrappers.StartServices(apps, meter, tm)
   140  }
   141  
   142  func (b Backend) StopServices(apps []*snap.AppInfo, reason snap.ServiceStopReason, meter progress.Meter, tm timings.Measurer) error {
   143  	return wrappers.StopServices(apps, reason, meter, tm)
   144  }
   145  
   146  func generateWrappers(s *snap.Info) (err error) {
   147  	var cleanupFuncs []func(*snap.Info) error
   148  	defer func() {
   149  		if err != nil {
   150  			for _, cleanup := range cleanupFuncs {
   151  				cleanup(s)
   152  			}
   153  		}
   154  	}()
   155  
   156  	// add the CLI apps from the snap.yaml
   157  	if err = wrappers.AddSnapBinaries(s); err != nil {
   158  		return err
   159  	}
   160  	cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapBinaries)
   161  
   162  	// add the daemons from the snap.yaml
   163  	if err = wrappers.AddSnapServices(s, progress.Null); err != nil {
   164  		return err
   165  	}
   166  	cleanupFuncs = append(cleanupFuncs, func(s *snap.Info) error {
   167  		return wrappers.RemoveSnapServices(s, progress.Null)
   168  	})
   169  
   170  	// add the desktop files
   171  	if err = wrappers.AddSnapDesktopFiles(s); err != nil {
   172  		return err
   173  	}
   174  	cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapDesktopFiles)
   175  
   176  	// add the desktop icons
   177  	if err = wrappers.AddSnapIcons(s); err != nil {
   178  		return err
   179  	}
   180  	cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapIcons)
   181  
   182  	return nil
   183  }
   184  
   185  func removeGeneratedWrappers(s *snap.Info, meter progress.Meter) error {
   186  	err1 := wrappers.RemoveSnapBinaries(s)
   187  	if err1 != nil {
   188  		logger.Noticef("Cannot remove binaries for %q: %v", s.InstanceName(), err1)
   189  	}
   190  
   191  	err2 := wrappers.RemoveSnapServices(s, meter)
   192  	if err2 != nil {
   193  		logger.Noticef("Cannot remove services for %q: %v", s.InstanceName(), err2)
   194  	}
   195  
   196  	err3 := wrappers.RemoveSnapDesktopFiles(s)
   197  	if err3 != nil {
   198  		logger.Noticef("Cannot remove desktop files for %q: %v", s.InstanceName(), err3)
   199  	}
   200  
   201  	err4 := wrappers.RemoveSnapIcons(s)
   202  	if err4 != nil {
   203  		logger.Noticef("Cannot remove desktop icons for %q: %v", s.InstanceName(), err4)
   204  	}
   205  
   206  	return firstErr(err1, err2, err3, err4)
   207  }
   208  
   209  // UnlinkSnap makes the snap unavailable to the system removing wrappers and symlinks.
   210  func (b Backend) UnlinkSnap(info *snap.Info, meter progress.Meter) error {
   211  	// remove generated services, binaries etc
   212  	err1 := removeGeneratedWrappers(info, meter)
   213  
   214  	// and finally remove current symlinks
   215  	err2 := removeCurrentSymlinks(info)
   216  
   217  	// FIXME: aggregate errors instead
   218  	return firstErr(err1, err2)
   219  }
   220  
   221  func removeCurrentSymlinks(info snap.PlaceInfo) error {
   222  	var err1, err2 error
   223  
   224  	// the snap "current" symlink
   225  	currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current")
   226  	err1 = os.Remove(currentActiveSymlink)
   227  	if err1 != nil && !os.IsNotExist(err1) {
   228  		logger.Noticef("Cannot remove %q: %v", currentActiveSymlink, err1)
   229  	} else {
   230  		err1 = nil
   231  	}
   232  
   233  	// the data "current" symlink
   234  	currentDataSymlink := filepath.Join(info.DataDir(), "..", "current")
   235  	err2 = os.Remove(currentDataSymlink)
   236  	if err2 != nil && !os.IsNotExist(err2) {
   237  		logger.Noticef("Cannot remove %q: %v", currentDataSymlink, err2)
   238  	} else {
   239  		err2 = nil
   240  	}
   241  
   242  	if err1 != nil && err2 != nil {
   243  		return fmt.Errorf("cannot remove snap current symlink: %v and %v", err1, err2)
   244  	} else if err1 != nil {
   245  		return fmt.Errorf("cannot remove snap current symlink: %v", err1)
   246  	} else if err2 != nil {
   247  		return fmt.Errorf("cannot remove snap current symlink: %v", err2)
   248  	}
   249  
   250  	return nil
   251  }