github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  	"sort"
    25  	"strings"
    26  
    27  	"github.com/snapcore/snapd/cmd/snaplock"
    28  	"github.com/snapcore/snapd/sandbox/cgroup"
    29  	"github.com/snapcore/snapd/snap"
    30  )
    31  
    32  // pidsOfSnap is a mockable version of PidsOfSnap
    33  var pidsOfSnap = cgroup.PidsOfSnap
    34  
    35  func genericRefreshCheck(info *snap.Info, canAppRunDuringRefresh func(app *snap.AppInfo) bool) error {
    36  	// Grab per-snap lock to prevent new processes from starting. This is
    37  	// sufficient to perform the check, even though individual processes
    38  	// may fork or exit, we will have per-security-tag information about
    39  	// what is running.
    40  	lock, err := snaplock.OpenLock(info.SnapName())
    41  	if err != nil {
    42  		return err
    43  	}
    44  	// Closing the lock also unlocks it, if locked.
    45  	defer lock.Close()
    46  	if err := lock.Lock(); err != nil {
    47  		return err
    48  	}
    49  	knownPids, err := pidsOfSnap(info.InstanceName())
    50  	if err != nil {
    51  		return err
    52  	}
    53  	// As soon as the lock is released the guarantee promised by pidsOfSnap is
    54  	// no longer true. This is an existing limitation. To cite the
    55  	// documentation of pidsOfSnap:
    56  	//
    57  	// > If the per-snap lock is held while computing the set, then the following
    58  	// > guarantee is true: If a security tag is not among the result then no such
    59  	// > tag can come into existence while the lock is held.
    60  	//
    61  	// This lock will be wrapped by another lock, the snap-inhibition-lock,
    62  	// which stalls startup of new apps and hooks. Unlike the snap-lock it can
    63  	// be held for many minutes or longer, enough to complete arbitrary data
    64  	// copy and download operations. The idea is that this refresh check will
    65  	// be performed while holding the snap lock (externally, the locking code
    66  	// will move to the call site), and if successful (the check indicated that
    67  	// refresh is possible) an inhibition lock will be grabbed before releasing
    68  	// the snap lock. This will remove the race condition and give the caller a
    69  	// chance to perform time-consuming operations.
    70  	lock.Unlock()
    71  
    72  	var busyAppNames []string
    73  	var busyHookNames []string
    74  	var busyPIDs []int
    75  
    76  	// Currently there are no situations when hooks might be allowed to run
    77  	// during the refresh process. The function exists to make the next two
    78  	// chunks of code symmetric.
    79  	canHookRunDuringRefresh := func(hook *snap.HookInfo) bool {
    80  		return false
    81  	}
    82  
    83  	for name, app := range info.Apps {
    84  		if canAppRunDuringRefresh(app) {
    85  			continue
    86  		}
    87  		if PIDs := knownPids[app.SecurityTag()]; len(PIDs) > 0 {
    88  			busyAppNames = append(busyAppNames, name)
    89  			busyPIDs = append(busyPIDs, PIDs...)
    90  		}
    91  	}
    92  
    93  	for name, hook := range info.Hooks {
    94  		if canHookRunDuringRefresh(hook) {
    95  			continue
    96  		}
    97  		if PIDs := knownPids[hook.SecurityTag()]; len(PIDs) > 0 {
    98  			busyHookNames = append(busyHookNames, name)
    99  			busyPIDs = append(busyPIDs, PIDs...)
   100  		}
   101  	}
   102  	if len(busyAppNames) == 0 && len(busyHookNames) == 0 {
   103  		return nil
   104  	}
   105  	sort.Strings(busyAppNames)
   106  	sort.Strings(busyHookNames)
   107  	sort.Ints(busyPIDs)
   108  	return &BusySnapError{
   109  		SnapName:      info.SnapName(),
   110  		busyAppNames:  busyAppNames,
   111  		busyHookNames: busyHookNames,
   112  		pids:          busyPIDs,
   113  	}
   114  }
   115  
   116  // SoftNothingRunningRefreshCheck looks if there are at most only service processes alive.
   117  //
   118  // The check is designed to run early in the refresh pipeline. Before
   119  // downloading or stopping services for the update, we can check that only
   120  // services are running, that is, that no non-service apps or hooks are
   121  // currently running.
   122  //
   123  // Since services are stopped during the update this provides a good early
   124  // precondition check. The check is also deliberately racy as existing snap
   125  // commands can fork new processes or existing processes can die. After the
   126  // soft check passes the user is free to start snap applications and block the
   127  // hard check.
   128  func SoftNothingRunningRefreshCheck(info *snap.Info) error {
   129  	return genericRefreshCheck(info, func(app *snap.AppInfo) bool {
   130  		return app.IsService()
   131  	})
   132  }
   133  
   134  // HardNothingRunningRefreshCheck looks if there are any undesired processes alive.
   135  //
   136  // The check is designed to run late in the refresh pipeline, after stopping
   137  // snap services. At this point non-enduring services should be stopped, hooks
   138  // should no longer run, and applications should be barred from running
   139  // externally (e.g. by using a new inhibition mechanism for snap run).
   140  //
   141  // The check fails if any process belonging to the snap, apart from services
   142  // that are enduring refresh, is still alive. If a snap is busy it cannot be
   143  // refreshed and the refresh process is aborted.
   144  func HardNothingRunningRefreshCheck(info *snap.Info) error {
   145  	return genericRefreshCheck(info, func(app *snap.AppInfo) bool {
   146  		// TODO: use a constant instead of "endure"
   147  		return app.IsService() && app.RefreshMode == "endure"
   148  	})
   149  }
   150  
   151  // BusySnapError indicates that snap has apps or hooks running and cannot refresh.
   152  type BusySnapError struct {
   153  	SnapName      string
   154  	pids          []int
   155  	busyAppNames  []string
   156  	busyHookNames []string
   157  }
   158  
   159  // Error formats an error string describing what is running.
   160  func (err *BusySnapError) Error() string {
   161  	switch {
   162  	case len(err.busyAppNames) > 0 && len(err.busyHookNames) > 0:
   163  		return fmt.Sprintf("snap %q has running apps (%s) and hooks (%s)",
   164  			err.SnapName, strings.Join(err.busyAppNames, ", "), strings.Join(err.busyHookNames, ", "))
   165  	case len(err.busyAppNames) > 0:
   166  		return fmt.Sprintf("snap %q has running apps (%s)",
   167  			err.SnapName, strings.Join(err.busyAppNames, ", "))
   168  	case len(err.busyHookNames) > 0:
   169  		return fmt.Sprintf("snap %q has running hooks (%s)",
   170  			err.SnapName, strings.Join(err.busyHookNames, ", "))
   171  	default:
   172  		return fmt.Sprintf("snap %q has running apps or hooks", err.SnapName)
   173  	}
   174  }
   175  
   176  // Pids returns the set of process identifiers that are running.
   177  //
   178  // Since this list is a snapshot it should be only acted upon if there is an
   179  // external synchronization system applied (e.g. all processes are frozen) at
   180  // the time the snapshot was taken.
   181  //
   182  // The list is intended for snapd to forcefully kill all processes for a forced
   183  // refresh scenario.
   184  func (err BusySnapError) Pids() []int {
   185  	return err.pids
   186  }