github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/sandbox/cgroup/freezer.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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 cgroup
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	"github.com/snapcore/snapd/dirs"
    31  )
    32  
    33  const defaultFreezerCgroupDir = "/sys/fs/cgroup/freezer"
    34  
    35  var FreezerCgroupDir = defaultFreezerCgroupDir
    36  
    37  func init() {
    38  	dirs.AddRootDirCallback(func(root string) {
    39  		FreezerCgroupDir = filepath.Join(root, defaultFreezerCgroupDir)
    40  	})
    41  }
    42  
    43  // FreezeSnapProcessesImpl suspends execution of all the processes belonging to
    44  // a given snap. Processes remain frozen until ThawSnapProcesses is called,
    45  // care must be taken not to freezer processes indefinitely.
    46  //
    47  // The freeze operation is not instant. Once commenced it proceeds
    48  // asynchronously. Internally the function waits for the freezing to complete
    49  // in at most 3000ms. If this time is insufficient then the processes are
    50  // thawed and an error is returned.
    51  //
    52  // This operation can be mocked with MockFreezing
    53  var FreezeSnapProcesses = freezeSnapProcessesImpl
    54  
    55  // ThawSnapProcesses resumes execution of all processes belonging to a given snap.
    56  //
    57  // This operation can be mocked with MockFreezing
    58  var ThawSnapProcesses = thawSnapProcessesImpl
    59  
    60  // freezeSnapProcessesImpl freezes all the processes originating from the given snap.
    61  // Processes are frozen regardless of which particular snap application they
    62  // originate from.
    63  func freezeSnapProcessesImpl(snapName string) error {
    64  	fname := filepath.Join(FreezerCgroupDir, fmt.Sprintf("snap.%s", snapName), "freezer.state")
    65  	if err := ioutil.WriteFile(fname, []byte("FROZEN"), 0644); err != nil && os.IsNotExist(err) {
    66  		// When there's no freezer cgroup we don't have to freeze anything.
    67  		// This can happen when no process belonging to a given snap has been
    68  		// started yet.
    69  		return nil
    70  	} else if err != nil {
    71  		return fmt.Errorf("cannot freeze processes of snap %q, %v", snapName, err)
    72  	}
    73  	for i := 0; i < 30; i++ {
    74  		data, err := ioutil.ReadFile(fname)
    75  		if err != nil {
    76  			return fmt.Errorf("cannot determine the freeze state of processes of snap %q, %v", snapName, err)
    77  		}
    78  		// If the cgroup is still freezing then wait a moment and try again.
    79  		if bytes.Equal(data, []byte("FREEZING")) {
    80  			time.Sleep(100 * time.Millisecond)
    81  			continue
    82  		}
    83  		return nil
    84  	}
    85  	// If we got here then we timed out after seeing FREEZING for too long.
    86  	ThawSnapProcesses(snapName) // ignore the error, this is best-effort.
    87  	return fmt.Errorf("cannot finish freezing processes of snap %q", snapName)
    88  }
    89  
    90  func thawSnapProcessesImpl(snapName string) error {
    91  	fname := filepath.Join(FreezerCgroupDir, fmt.Sprintf("snap.%s", snapName), "freezer.state")
    92  	if err := ioutil.WriteFile(fname, []byte("THAWED"), 0644); err != nil && os.IsNotExist(err) {
    93  		// When there's no freezer cgroup we don't have to thaw anything.
    94  		// This can happen when no process belonging to a given snap has been
    95  		// started yet.
    96  		return nil
    97  	} else if err != nil {
    98  		return fmt.Errorf("cannot thaw processes of snap %q", snapName)
    99  	}
   100  	return nil
   101  }
   102  
   103  // MockFreezing replaces the real implementation of freeze and thaw.
   104  func MockFreezing(freeze, thaw func(snapName string) error) (restore func()) {
   105  	oldFreeze := FreezeSnapProcesses
   106  	oldThaw := ThawSnapProcesses
   107  
   108  	FreezeSnapProcesses = freeze
   109  	ThawSnapProcesses = thaw
   110  
   111  	return func() {
   112  		FreezeSnapProcesses = oldFreeze
   113  		ThawSnapProcesses = oldThaw
   114  	}
   115  }