github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/overlord/snapstate/refresh.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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 snapstate
    21  
    22  import (
    23  	"fmt"
    24  	"path/filepath"
    25  	"sort"
    26  	"strings"
    27  
    28  	"github.com/snapcore/snapd/cmd/snaplock/runinhibit"
    29  	"github.com/snapcore/snapd/osutil"
    30  	"github.com/snapcore/snapd/overlord/snapstate/backend"
    31  	"github.com/snapcore/snapd/overlord/state"
    32  	"github.com/snapcore/snapd/sandbox/cgroup"
    33  	"github.com/snapcore/snapd/snap"
    34  	userclient "github.com/snapcore/snapd/usersession/client"
    35  )
    36  
    37  // pidsOfSnap is a mockable version of PidsOfSnap
    38  var pidsOfSnap = cgroup.PidsOfSnap
    39  
    40  var genericRefreshCheck = func(info *snap.Info, canAppRunDuringRefresh func(app *snap.AppInfo) bool) error {
    41  	knownPids, err := pidsOfSnap(info.InstanceName())
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	// Due to specific of the interaction with locking, all locking is performed by the caller.
    47  	var busyAppNames []string
    48  	var busyHookNames []string
    49  	var busyPIDs []int
    50  
    51  	// Currently there are no situations when hooks might be allowed to run
    52  	// during the refresh process. The function exists to make the next two
    53  	// chunks of code symmetric.
    54  	canHookRunDuringRefresh := func(hook *snap.HookInfo) bool {
    55  		return false
    56  	}
    57  
    58  	for name, app := range info.Apps {
    59  		if canAppRunDuringRefresh(app) {
    60  			continue
    61  		}
    62  		if PIDs := knownPids[app.SecurityTag()]; len(PIDs) > 0 {
    63  			busyAppNames = append(busyAppNames, name)
    64  			busyPIDs = append(busyPIDs, PIDs...)
    65  		}
    66  	}
    67  
    68  	for name, hook := range info.Hooks {
    69  		if canHookRunDuringRefresh(hook) {
    70  			continue
    71  		}
    72  		if PIDs := knownPids[hook.SecurityTag()]; len(PIDs) > 0 {
    73  			busyHookNames = append(busyHookNames, name)
    74  			busyPIDs = append(busyPIDs, PIDs...)
    75  		}
    76  	}
    77  	if len(busyAppNames) == 0 && len(busyHookNames) == 0 {
    78  		return nil
    79  	}
    80  	sort.Strings(busyAppNames)
    81  	sort.Strings(busyHookNames)
    82  	sort.Ints(busyPIDs)
    83  	return &BusySnapError{
    84  		SnapInfo:      info,
    85  		busyAppNames:  busyAppNames,
    86  		busyHookNames: busyHookNames,
    87  		pids:          busyPIDs,
    88  	}
    89  }
    90  
    91  // SoftNothingRunningRefreshCheck looks if there are at most only service processes alive.
    92  //
    93  // The check is designed to run early in the refresh pipeline. Before
    94  // downloading or stopping services for the update, we can check that only
    95  // services are running, that is, that no non-service apps or hooks are
    96  // currently running.
    97  //
    98  // Since services are stopped during the update this provides a good early
    99  // precondition check. The check is also deliberately racy as existing snap
   100  // commands can fork new processes or existing processes can die. After the
   101  // soft check passes the user is free to start snap applications and block the
   102  // hard check.
   103  func SoftNothingRunningRefreshCheck(info *snap.Info) error {
   104  	return genericRefreshCheck(info, func(app *snap.AppInfo) bool {
   105  		return app.IsService()
   106  	})
   107  }
   108  
   109  // HardNothingRunningRefreshCheck looks if there are any undesired processes alive.
   110  //
   111  // The check is designed to run late in the refresh pipeline, after stopping
   112  // snap services. At this point non-enduring services should be stopped, hooks
   113  // should no longer run, and applications should be barred from running
   114  // externally (e.g. by using a new inhibition mechanism for snap run).
   115  //
   116  // The check fails if any process belonging to the snap, apart from services
   117  // that are enduring refresh, is still alive. If a snap is busy it cannot be
   118  // refreshed and the refresh process is aborted.
   119  func HardNothingRunningRefreshCheck(info *snap.Info) error {
   120  	return genericRefreshCheck(info, func(app *snap.AppInfo) bool {
   121  		// TODO: use a constant instead of "endure"
   122  		return app.IsService() && app.RefreshMode == "endure"
   123  	})
   124  }
   125  
   126  // BusySnapError indicates that snap has apps or hooks running and cannot refresh.
   127  type BusySnapError struct {
   128  	SnapInfo      *snap.Info
   129  	pids          []int
   130  	busyAppNames  []string
   131  	busyHookNames []string
   132  }
   133  
   134  // PendingSnapRefreshInfo computes information necessary to perform user notification
   135  // of postponed refresh of a snap, based on the information about snap "business".
   136  //
   137  // The returned value contains the instance name of the snap as well as, if possible,
   138  // information relevant for desktop notification services, such as application name
   139  // and the snapd-generated desktop file name.
   140  func (err *BusySnapError) PendingSnapRefreshInfo() *userclient.PendingSnapRefreshInfo {
   141  	refreshInfo := &userclient.PendingSnapRefreshInfo{
   142  		InstanceName: err.SnapInfo.InstanceName(),
   143  	}
   144  	for _, appName := range err.busyAppNames {
   145  		if app, ok := err.SnapInfo.Apps[appName]; ok {
   146  			path := app.DesktopFile()
   147  			if osutil.FileExists(path) {
   148  				refreshInfo.BusyAppName = appName
   149  				refreshInfo.BusyAppDesktopEntry = strings.SplitN(filepath.Base(path), ".", 2)[0]
   150  				break
   151  			}
   152  		}
   153  	}
   154  	return refreshInfo
   155  }
   156  
   157  // Error formats an error string describing what is running.
   158  func (err *BusySnapError) Error() string {
   159  	switch {
   160  	case len(err.busyAppNames) > 0 && len(err.busyHookNames) > 0:
   161  		return fmt.Sprintf("snap %q has running apps (%s) and hooks (%s)",
   162  			err.SnapInfo.InstanceName(), strings.Join(err.busyAppNames, ", "), strings.Join(err.busyHookNames, ", "))
   163  	case len(err.busyAppNames) > 0:
   164  		return fmt.Sprintf("snap %q has running apps (%s)",
   165  			err.SnapInfo.InstanceName(), strings.Join(err.busyAppNames, ", "))
   166  	case len(err.busyHookNames) > 0:
   167  		return fmt.Sprintf("snap %q has running hooks (%s)",
   168  			err.SnapInfo.InstanceName(), strings.Join(err.busyHookNames, ", "))
   169  	default:
   170  		return fmt.Sprintf("snap %q has running apps or hooks", err.SnapInfo.InstanceName())
   171  	}
   172  }
   173  
   174  // Pids returns the set of process identifiers that are running.
   175  //
   176  // Since this list is a snapshot it should be only acted upon if there is an
   177  // external synchronization system applied (e.g. all processes are frozen) at
   178  // the time the snapshot was taken.
   179  //
   180  // The list is intended for snapd to forcefully kill all processes for a forced
   181  // refresh scenario.
   182  func (err BusySnapError) Pids() []int {
   183  	return err.pids
   184  }
   185  
   186  // hardEnsureNothingRunningDuringRefresh performs the complete hard refresh interaction.
   187  //
   188  // This check uses HardNothingRunningRefreshCheck along with interaction with
   189  // two locks - the snap lock, shared by snap-confine and snapd and the snap run
   190  // inhibition lock, shared by snapd and snap run.
   191  //
   192  // On success this function returns a locked snap lock, allowing the caller to
   193  // atomically, with regards to "snap-confine", finish any action that required
   194  // the apps and hooks not to be running. In addition, the persistent run
   195  // inhibition lock is established, forcing snap-run to pause and postpone
   196  // startup of applications from the given snap.
   197  //
   198  // In practice, we either inhibit app startup and refresh the snap _or_ inhibit
   199  // the refresh change and continue running existing app processes.
   200  func hardEnsureNothingRunningDuringRefresh(backend managerBackend, st *state.State, snapst *SnapState, info *snap.Info) (*osutil.FileLock, error) {
   201  	return backend.RunInhibitSnapForUnlink(info, runinhibit.HintInhibitedForRefresh, func() error {
   202  		// In case of successful refresh inhibition the snap state is modified
   203  		// to indicate when the refresh was first inhibited. If the first
   204  		// refresh inhibition is outside of a grace period then refresh
   205  		// proceeds regardless of the existing processes.
   206  		return inhibitRefresh(st, snapst, info, HardNothingRunningRefreshCheck)
   207  	})
   208  }
   209  
   210  // softCheckNothingRunningForRefresh checks if non-service apps are off for a snap refresh.
   211  //
   212  // The details of the check are explained by SoftNothingRunningRefreshCheck.
   213  // The check is performed while holding the snap lock, which ensures that we
   214  // are not racing with snap-confine, which is starting a new process in the
   215  // context of the given snap.
   216  //
   217  // In the case that the check fails, the state is modified to reflect when the
   218  // refresh was first postponed. Eventually the check does not fail, even if
   219  // non-service apps are running, because this mechanism only allows postponing
   220  // refreshes for a bounded amount of time.
   221  func softCheckNothingRunningForRefresh(st *state.State, snapst *SnapState, info *snap.Info) error {
   222  	// Grab per-snap lock to prevent new processes from starting. This is
   223  	// sufficient to perform the check, even though individual processes may
   224  	// fork or exit, we will have per-security-tag information about what is
   225  	// running.
   226  	return backend.WithSnapLock(info, func() error {
   227  		// Perform the soft refresh viability check, possibly writing to the state
   228  		// on failure.
   229  		return inhibitRefresh(st, snapst, info, SoftNothingRunningRefreshCheck)
   230  	})
   231  }