github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/virtcontainers/store/filesystem_backend.go (about) 1 // Copyright (c) 2019 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 package store 7 8 import ( 9 "context" 10 "encoding/json" 11 "fmt" 12 "io/ioutil" 13 "os" 14 "path/filepath" 15 "syscall" 16 17 "github.com/kata-containers/runtime/virtcontainers/pkg/rootless" 18 "github.com/kata-containers/runtime/virtcontainers/pkg/uuid" 19 opentracing "github.com/opentracing/opentracing-go" 20 "github.com/sirupsen/logrus" 21 ) 22 23 const ( 24 // ConfigurationFile is the file name used for every JSON sandbox configuration. 25 ConfigurationFile string = "config.json" 26 27 // StateFile is the file name storing a sandbox state. 28 StateFile = "state.json" 29 30 // NetworkFile is the file name storing a sandbox network. 31 NetworkFile = "network.json" 32 33 // HypervisorFile is the file name storing a hypervisor's state. 34 HypervisorFile = "hypervisor.json" 35 36 // AgentFile is the file name storing an agent's state. 37 AgentFile = "agent.json" 38 39 // ProcessFile is the file name storing a container process. 40 ProcessFile = "process.json" 41 42 // LockFile is the file name locking the usage of a resource. 43 LockFile = "lock" 44 45 // MountsFile is the file name storing a container's mount points. 46 MountsFile = "mounts.json" 47 48 // DevicesFile is the file name storing a container's devices. 49 DevicesFile = "devices.json" 50 51 // uuidFile is the file name storing a guest VM uuid state. 52 uuidFile = "uuid.json" 53 ) 54 55 // DirMode is the permission bits used for creating a directory 56 const DirMode = os.FileMode(0750) | os.ModeDir 57 58 // StoragePathSuffix is the suffix used for all storage paths 59 // 60 // Note: this very brief path represents "virtcontainers". It is as 61 // terse as possible to minimise path length. 62 const StoragePathSuffix = "vc" 63 64 // SandboxPathSuffix is the suffix used for sandbox storage 65 const SandboxPathSuffix = "sbs" 66 67 // VMPathSuffix is the suffix used for guest VMs. 68 const VMPathSuffix = "vm" 69 70 // UUIDPathSuffix is the suffix used for uuid storage 71 const UUIDPathSuffix = "uuid" 72 73 // ConfigStoragePath is the sandbox configuration directory. 74 // It will contain one config.json file for each created sandbox. 75 // The function is declared this way for mocking in unit tests 76 var ConfigStoragePath = func() string { 77 path := filepath.Join("/var/lib", StoragePathSuffix, SandboxPathSuffix) 78 if rootless.IsRootless() { 79 return filepath.Join(rootless.GetRootlessDir(), path) 80 } 81 return path 82 } 83 84 // RunStoragePath is the sandbox runtime directory. 85 // It will contain one state.json and one lock file for each created sandbox. 86 // The function is declared this way for mocking in unit tests 87 var RunStoragePath = func() string { 88 path := filepath.Join("/run", StoragePathSuffix, SandboxPathSuffix) 89 if rootless.IsRootless() { 90 return filepath.Join(rootless.GetRootlessDir(), path) 91 } 92 return path 93 } 94 95 // RunVMStoragePath is the vm directory. 96 // It will contain all guest vm sockets and shared mountpoints. 97 // The function is declared this way for mocking in unit tests 98 var RunVMStoragePath = func() string { 99 path := filepath.Join("/run", StoragePathSuffix, VMPathSuffix) 100 if rootless.IsRootless() { 101 return filepath.Join(rootless.GetRootlessDir(), path) 102 } 103 return path 104 } 105 106 // VMUUIDStoragePath is the uuid directory. 107 // It will contain all uuid info used by guest vm. 108 var VMUUIDStoragePath = func() string { 109 path := filepath.Join("/var/lib", StoragePathSuffix, UUIDPathSuffix) 110 if rootless.IsRootless() { 111 return filepath.Join(rootless.GetRootlessDir(), path) 112 } 113 return path 114 115 } 116 117 func itemToFile(item Item) (string, error) { 118 switch item { 119 case Configuration: 120 return ConfigurationFile, nil 121 case State: 122 return StateFile, nil 123 case Network: 124 return NetworkFile, nil 125 case Hypervisor: 126 return HypervisorFile, nil 127 case Agent: 128 return AgentFile, nil 129 case Process: 130 return ProcessFile, nil 131 case Lock: 132 return LockFile, nil 133 case Mounts: 134 return MountsFile, nil 135 case Devices, DeviceIDs: 136 return DevicesFile, nil 137 case UUID: 138 return uuidFile, nil 139 } 140 141 return "", fmt.Errorf("Unknown item %s", item) 142 } 143 144 type filesystem struct { 145 ctx context.Context 146 147 path string 148 rawPath string 149 150 lockTokens map[string]*os.File 151 } 152 153 // Logger returns a logrus logger appropriate for logging Store filesystem messages 154 func (f *filesystem) logger() *logrus.Entry { 155 return storeLog.WithFields(logrus.Fields{ 156 "subsystem": "store", 157 "backend": "filesystem", 158 "path": f.path, 159 }) 160 } 161 162 func (f *filesystem) trace(name string) (opentracing.Span, context.Context) { 163 if f.ctx == nil { 164 f.logger().WithField("type", "bug").Error("trace called before context set") 165 f.ctx = context.Background() 166 } 167 168 span, ctx := opentracing.StartSpanFromContext(f.ctx, name) 169 170 span.SetTag("subsystem", "store") 171 span.SetTag("type", "filesystem") 172 span.SetTag("path", f.path) 173 174 return span, ctx 175 } 176 177 func (f *filesystem) itemToPath(item Item) (string, error) { 178 fileName, err := itemToFile(item) 179 if err != nil { 180 return "", err 181 } 182 183 return filepath.Join(f.path, fileName), nil 184 } 185 186 func (f *filesystem) initialize() error { 187 _, err := os.Stat(f.path) 188 if err == nil { 189 return nil 190 } 191 192 // Our root path does not exist, we need to create the initial layout: 193 // The root directory, a lock file and a raw files directory. 194 195 // Root directory 196 f.logger().WithField("path", f.path).Debugf("Creating root directory") 197 if err := os.MkdirAll(f.path, DirMode); err != nil { 198 return err 199 } 200 201 // Raw directory 202 f.logger().WithField("path", f.rawPath).Debugf("Creating raw directory") 203 if err := os.MkdirAll(f.rawPath, DirMode); err != nil { 204 return err 205 } 206 207 // Lock 208 lockPath := filepath.Join(f.path, LockFile) 209 210 lockFile, err := os.Create(lockPath) 211 if err != nil { 212 f.delete() 213 return err 214 } 215 lockFile.Close() 216 217 return nil 218 } 219 220 func (f *filesystem) new(ctx context.Context, path string, host string) error { 221 f.ctx = ctx 222 f.path = path 223 f.rawPath = filepath.Join(f.path, "raw") 224 f.lockTokens = make(map[string]*os.File) 225 226 f.logger().Debugf("New filesystem store backend for %s", path) 227 228 return f.initialize() 229 } 230 231 func (f *filesystem) delete() error { 232 f.logger().WithField("path", f.path).Debugf("Deleting files") 233 return os.RemoveAll(f.path) 234 } 235 236 func (f *filesystem) load(item Item, data interface{}) error { 237 span, _ := f.trace("load") 238 defer span.Finish() 239 240 span.SetTag("item", item) 241 242 filePath, err := f.itemToPath(item) 243 if err != nil { 244 return err 245 } 246 247 fileData, err := ioutil.ReadFile(filePath) 248 if err != nil { 249 return err 250 } 251 252 if err := json.Unmarshal(fileData, data); err != nil { 253 return err 254 } 255 256 return nil 257 } 258 259 func (f *filesystem) store(item Item, data interface{}) error { 260 span, _ := f.trace("store") 261 defer span.Finish() 262 263 span.SetTag("item", item) 264 265 filePath, err := f.itemToPath(item) 266 if err != nil { 267 return err 268 } 269 270 file, err := os.Create(filePath) 271 if err != nil { 272 return err 273 } 274 defer file.Close() 275 276 jsonOut, err := json.Marshal(data) 277 if err != nil { 278 return fmt.Errorf("Could not marshall data: %s", err) 279 } 280 file.Write(jsonOut) 281 282 return nil 283 } 284 285 func (f *filesystem) raw(id string) (string, error) { 286 span, _ := f.trace("raw") 287 defer span.Finish() 288 289 span.SetTag("id", id) 290 291 // We must use the item ID. 292 if id != "" { 293 filePath := filepath.Join(f.rawPath, id) 294 file, err := os.Create(filePath) 295 if err != nil { 296 return "", err 297 } 298 299 return filesystemScheme + "://" + file.Name(), nil 300 } 301 302 // Generate a random temporary file. 303 file, err := ioutil.TempFile(f.rawPath, "raw-") 304 if err != nil { 305 return "", err 306 } 307 defer file.Close() 308 309 return filesystemScheme + "://" + file.Name(), nil 310 } 311 312 func (f *filesystem) lock(item Item, exclusive bool) (string, error) { 313 itemPath, err := f.itemToPath(item) 314 if err != nil { 315 return "", err 316 } 317 318 itemFile, err := os.Open(itemPath) 319 if err != nil { 320 return "", err 321 } 322 323 var lockType int 324 if exclusive { 325 lockType = syscall.LOCK_EX 326 } else { 327 lockType = syscall.LOCK_SH 328 } 329 330 if err := syscall.Flock(int(itemFile.Fd()), lockType); err != nil { 331 itemFile.Close() 332 return "", err 333 } 334 335 token := uuid.Generate().String() 336 f.lockTokens[token] = itemFile 337 338 return token, nil 339 } 340 341 func (f *filesystem) unlock(item Item, token string) error { 342 itemFile := f.lockTokens[token] 343 if itemFile == nil { 344 return fmt.Errorf("No lock for token %s", token) 345 } 346 347 if err := syscall.Flock(int(itemFile.Fd()), syscall.LOCK_UN); err != nil { 348 return err 349 } 350 351 itemFile.Close() 352 delete(f.lockTokens, token) 353 354 return nil 355 }