gitee.com/mysnapcore/mysnapd@v0.1.0/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 "strings" 29 "time" 30 31 "gitee.com/mysnapcore/mysnapd/dirs" 32 "gitee.com/mysnapcore/mysnapd/logger" 33 "gitee.com/mysnapcore/mysnapd/osutil" 34 ) 35 36 const defaultFreezerCgroupV1Dir = "/sys/fs/cgroup/freezer" 37 38 var freezerCgroupV1Dir = defaultFreezerCgroupV1Dir 39 40 func init() { 41 dirs.AddRootDirCallback(func(root string) { 42 freezerCgroupV1Dir = filepath.Join(root, defaultFreezerCgroupV1Dir) 43 }) 44 } 45 46 func pickFreezerV1Impl() { 47 FreezeSnapProcesses = freezeSnapProcessesImplV1 48 ThawSnapProcesses = thawSnapProcessesImplV1 49 } 50 51 func pickFreezerV2Impl() { 52 FreezeSnapProcesses = freezeSnapProcessesImplV2 53 ThawSnapProcesses = thawSnapProcessesImplV2 54 } 55 56 // FreezeSnapProcesses suspends execution of all the processes belonging to 57 // a given snap. Processes remain frozen until ThawSnapProcesses is called, 58 // care must be taken not to freezer processes indefinitely. 59 // 60 // The freeze operation is not instant. Once commenced it proceeds 61 // asynchronously. Internally the function waits for the freezing to complete 62 // in at most 3000ms. If this time is insufficient then the processes are 63 // thawed and an error is returned. 64 // 65 // A correct implementation is picked depending on cgroup v1 or v2 use in the 66 // system. When cgroup v1 is detected, the call will directly act on the freezer 67 // group created when a snap process was started, while with v2 the call will 68 // act on all tracking groups of a snap. 69 // 70 // This operation can be mocked with MockFreezing 71 var FreezeSnapProcesses = freezeSnapProcessesImplV1 72 73 // ThawSnapProcesses resumes execution of all processes belonging to a given snap. 74 // 75 // A correct implementation is picked depending on cgroup v1 or v2 use in the 76 // system. When cgroup v1 is detected, the call will directly act on the freezer 77 // group created when a snap process was started, while with v2 the call will 78 // act on all tracking groups of a snap. 79 // 80 // This operation can be mocked with MockFreezing 81 var ThawSnapProcesses = thawSnapProcessesImplV1 82 83 // freezeSnapProcessesImplV1 freezes all the processes originating from the given snap. 84 // Processes are frozen regardless of which particular snap application they 85 // originate from. 86 func freezeSnapProcessesImplV1(snapName string) error { 87 fname := filepath.Join(freezerCgroupV1Dir, fmt.Sprintf("snap.%s", snapName), "freezer.state") 88 if err := ioutil.WriteFile(fname, []byte("FROZEN"), 0644); err != nil && os.IsNotExist(err) { 89 // When there's no freezer cgroup we don't have to freeze anything. 90 // This can happen when no process belonging to a given snap has been 91 // started yet. 92 return nil 93 } else if err != nil { 94 return fmt.Errorf("cannot freeze processes of snap %q, %v", snapName, err) 95 } 96 for i := 0; i < 30; i++ { 97 data, err := ioutil.ReadFile(fname) 98 if err != nil { 99 return fmt.Errorf("cannot determine the freeze state of processes of snap %q, %v", snapName, err) 100 } 101 // If the cgroup is still freezing then wait a moment and try again. 102 if bytes.Equal(data, []byte("FREEZING")) { 103 time.Sleep(100 * time.Millisecond) 104 continue 105 } 106 return nil 107 } 108 // If we got here then we timed out after seeing FREEZING for too long. 109 ThawSnapProcesses(snapName) // ignore the error, this is best-effort. 110 return fmt.Errorf("cannot finish freezing processes of snap %q", snapName) 111 } 112 113 func thawSnapProcessesImplV1(snapName string) error { 114 fname := filepath.Join(freezerCgroupV1Dir, fmt.Sprintf("snap.%s", snapName), "freezer.state") 115 if err := ioutil.WriteFile(fname, []byte("THAWED"), 0644); err != nil && os.IsNotExist(err) { 116 // When there's no freezer cgroup we don't have to thaw anything. 117 // This can happen when no process belonging to a given snap has been 118 // started yet. 119 return nil 120 } else if err != nil { 121 return fmt.Errorf("cannot thaw processes of snap %q", snapName) 122 } 123 return nil 124 } 125 126 func applyToSnap(snapName string, action func(groupName string) error, skipError func(err error) bool) error { 127 if action == nil { 128 return fmt.Errorf("internal error: action is nil") 129 } 130 if skipError == nil { 131 return fmt.Errorf("internal error: skip error is nil") 132 } 133 canary := fmt.Sprintf("snap.%s.", snapName) 134 cgroupRoot := filepath.Join(rootPath, cgroupMountPoint) 135 if _, dir, _ := osutil.DirExists(cgroupRoot); !dir { 136 return nil 137 } 138 return filepath.Walk(filepath.Join(rootPath, cgroupMountPoint), func(name string, info os.FileInfo, err error) error { 139 if err != nil { 140 if skipError(err) { 141 // we don't know whether it's a file or 142 // directory, so just return nil instead 143 return nil 144 } 145 return err 146 } 147 if !info.IsDir() { 148 return nil 149 } 150 if !strings.HasPrefix(info.Name(), canary) { 151 return nil 152 } 153 // snap applications end up inside a cgroup related to a 154 // service, or when ran standalone, a scope 155 if ext := filepath.Ext(info.Name()); ext != ".scope" && ext != ".service" { 156 return nil 157 } 158 // found a group 159 if err := action(name); err != nil && !skipError(err) { 160 return err 161 } 162 return filepath.SkipDir 163 }) 164 } 165 166 // writeExistingFile can be used as a drop-in replacement for ioutil.WriteFile, 167 // but does not create a file when it does not exist 168 func writeExistingFile(where string, data []byte, mode os.FileMode) error { 169 f, err := os.OpenFile(where, os.O_WRONLY|os.O_TRUNC, mode) 170 if err != nil { 171 return err 172 } 173 _, errW := f.Write(data) 174 errC := f.Close() 175 // pick the right error 176 if errW != nil { 177 return errW 178 } 179 return errC 180 } 181 182 // freezeSnapProcessesImplV2 freezes all the processes originating from the 183 // given snap. Processes are frozen regardless of which particular snap 184 // application they originate from. 185 func freezeSnapProcessesImplV2(snapName string) error { 186 // in case of v2, the process calling this code, (eg. snap-update-ns) 187 // may already be part of the trackign cgroup for particular snap, care 188 // must be taken to not freeze ourselves 189 ownGroup, err := cgroupProcessPathInTrackingCgroup(os.Getpid()) 190 if err != nil { 191 return err 192 } 193 ownGroupDir := filepath.Join(rootPath, cgroupMountPoint, ownGroup) 194 freezeOne := func(dir string) error { 195 if dir == ownGroupDir { 196 // let's not freeze ourselves 197 logger.Debugf("freeze, skipping own group %v", dir) 198 return nil 199 } 200 fname := filepath.Join(dir, "cgroup.freeze") 201 if err := writeExistingFile(fname, []byte("1"), 0644); err != nil { 202 if os.IsNotExist(err) { 203 // the group may be gone already 204 return nil 205 } 206 return fmt.Errorf("cannot freeze processes of snap %q, %v", snapName, err) 207 } 208 for i := 0; i < 30; i++ { 209 data, err := ioutil.ReadFile(fname) 210 if err != nil { 211 if os.IsNotExist(err) { 212 // group may be gone 213 return nil 214 } 215 return fmt.Errorf("cannot determine the freeze state of processes of snap %q, %v", snapName, err) 216 } 217 // If the cgroup is still freezing then wait a moment and try again. 218 if bytes.Equal(bytes.TrimSpace(data), []byte("1")) { 219 // we're done 220 return nil 221 } 222 // add a bit of delay 223 time.Sleep(100 * time.Millisecond) 224 } 225 return fmt.Errorf("cannot freeze processes of snap %q in group %v", snapName, filepath.Base(dir)) 226 } 227 // freeze, skipping ENOENT errors 228 err = applyToSnap(snapName, freezeOne, os.IsNotExist) 229 if err == nil { 230 return nil 231 } 232 // we either got here because we hit a timeout freezing snap processes 233 // or some other error 234 235 // ignore errors when thawing processes, this is best-effort. 236 alwaysSkipError := func(_ error) bool { return true } 237 thawSnapProcessesV2(snapName, alwaysSkipError) 238 return fmt.Errorf("cannot finish freezing processes of snap %q: %v", snapName, err) 239 } 240 241 func thawSnapProcessesV2(snapName string, skipError func(error) bool) error { 242 if skipError == nil { 243 return fmt.Errorf("internal error: skip error is nil") 244 } 245 thawOne := func(dir string) error { 246 fname := filepath.Join(dir, "cgroup.freeze") 247 if err := writeExistingFile(fname, []byte("0"), 0644); err != nil && os.IsNotExist(err) { 248 // the group may be gone already 249 return nil 250 } else if err != nil && !skipError(err) { 251 return fmt.Errorf("cannot thaw processes of snap %q, %v", snapName, err) 252 } 253 return nil 254 } 255 return applyToSnap(snapName, thawOne, skipError) 256 } 257 258 func thawSnapProcessesImplV2(snapName string) error { 259 // thaw skipping ENOENT errors 260 return thawSnapProcessesV2(snapName, os.IsNotExist) 261 } 262 263 // MockFreezing replaces the real implementation of freeze and thaw. 264 func MockFreezing(freeze, thaw func(snapName string) error) (restore func()) { 265 oldFreeze := FreezeSnapProcesses 266 oldThaw := ThawSnapProcesses 267 268 FreezeSnapProcesses = freeze 269 ThawSnapProcesses = thaw 270 271 return func() { 272 FreezeSnapProcesses = oldFreeze 273 ThawSnapProcesses = oldThaw 274 } 275 }