github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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 }