github.com/uber/kraken@v0.1.4/lib/store/base/file_op.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package base 15 16 import ( 17 "fmt" 18 "os" 19 "strings" 20 21 "github.com/uber/kraken/lib/store/metadata" 22 ) 23 24 type lockLevel int 25 26 const ( 27 // lockLevelPeek indicates lock for peek. 28 _lockLevelPeek lockLevel = iota 29 // lockLevelRead indicates lock for read. 30 _lockLevelRead 31 // lockLevelWrite indicates lock for read. 32 _lockLevelWrite 33 ) 34 35 // FileOp performs one file or metadata operation on FileStore, given a list of 36 // acceptable states. 37 type FileOp interface { 38 AcceptState(state FileState) FileOp 39 GetAcceptableStates() map[FileState]interface{} 40 41 CreateFile(name string, createState FileState, len int64) error 42 MoveFileFrom(name string, createState FileState, sourcePath string) error 43 MoveFile(name string, goalState FileState) error 44 LinkFileTo(name string, targetPath string) error 45 DeleteFile(name string) error 46 47 GetFilePath(name string) (string, error) 48 GetFileStat(name string) (os.FileInfo, error) 49 50 GetFileReader(name string) (FileReader, error) 51 GetFileReadWriter(name string) (FileReadWriter, error) 52 53 GetFileMetadata(name string, md metadata.Metadata) error 54 SetFileMetadata(name string, md metadata.Metadata) (bool, error) 55 SetFileMetadataAt(name string, md metadata.Metadata, b []byte, offset int64) (bool, error) 56 GetOrSetFileMetadata(name string, md metadata.Metadata) error 57 DeleteFileMetadata(name string, md metadata.Metadata) error 58 59 RangeFileMetadata(name string, f func(metadata.Metadata) error) error 60 61 ListNames() ([]string, error) 62 63 String() string 64 } 65 66 var _ FileOp = (*localFileOp)(nil) 67 68 // localFileOp is a short-lived obj that performs one file or metadata operation 69 // on local disk, given a list of acceptable states. 70 type localFileOp struct { 71 s *localFileStore 72 states map[FileState]interface{} // Set of states that's acceptable. 73 } 74 75 // NewLocalFileOp inits a new FileOp obj. 76 func NewLocalFileOp(s *localFileStore) FileOp { 77 return &localFileOp{ 78 s: s, 79 states: make(map[FileState]interface{}), 80 } 81 } 82 83 // AcceptState adds a new state to the acceptable states list. 84 func (op *localFileOp) AcceptState(state FileState) FileOp { 85 op.states[state] = struct{}{} 86 return op 87 } 88 89 // GetAcceptableStates returns a set of acceptable states. 90 func (op *localFileOp) GetAcceptableStates() map[FileState]interface{} { 91 return op.states 92 } 93 94 // verifyStateHelper verifies file is in one of the acceptable states. 95 func (op *localFileOp) verifyStateHelper(name string, entry FileEntry) error { 96 currState := entry.GetState() 97 for state := range op.states { 98 if currState == state { 99 // File is in one of the acceptable states. 100 return nil 101 } 102 } 103 return &FileStateError{ 104 Op: "verifyStateHelper", 105 Name: name, 106 State: currState, 107 Msg: fmt.Sprintf("desired states: %v", op.states), 108 } 109 } 110 111 // reloadFileEntryHelper tries to reload file from disk into memory. 112 // Note it doesn't try to verify states or reload file from all possible states. 113 // If reload succeeded, return true; 114 // If file already exists in memory, return false; 115 // If file is neither in memory or on disk, return false with os.ErrNotExist. 116 func (op *localFileOp) reloadFileEntryHelper(name string) (reloaded bool, err error) { 117 if op.s.fileMap.Contains(name) { 118 return false, nil 119 } 120 121 // Check if file exists on disk. 122 // TODO: The states need to be guaranteed to be topologically sorted. 123 for state := range op.states { 124 fileEntry, err := op.s.fileEntryFactory.Create(name, state) 125 if err != nil { 126 return false, fmt.Errorf("create: %s", err) 127 } 128 129 // Try load before acquiring lock first. 130 if err = fileEntry.Reload(); err != nil { 131 continue 132 } 133 // Try to store file entry into memory. 134 if stored := op.s.fileMap.TryStore(name, fileEntry, func(name string, entry FileEntry) bool { 135 // Verify the file is still on disk. 136 err = entry.Reload() 137 return err == nil 138 }); err != nil { 139 if os.IsNotExist(err) { 140 continue 141 } 142 return false, err 143 } else if !stored { 144 // The entry was just reloaded by another goroutine, return true. 145 // Since TryStore() updates LAT of existing entry, it's unlikely 146 // that the entry would be deleted before this function returns. 147 return true, nil 148 } 149 return true, nil 150 } 151 return false, os.ErrNotExist 152 } 153 154 // lockHelper runs f under protection of entry level RWMutex. 155 func (op *localFileOp) lockHelper( 156 name string, l lockLevel, f func(name string, entry FileEntry)) (err error) { 157 if _, err = op.reloadFileEntryHelper(name); err != nil { 158 return err 159 } 160 var loaded bool 161 if l == _lockLevelPeek { 162 loaded = op.s.fileMap.LoadForPeek(name, func(name string, entry FileEntry) { 163 if err = op.verifyStateHelper(name, entry); err != nil { 164 return 165 } 166 f(name, entry) 167 }) 168 } else if l == _lockLevelRead { 169 loaded = op.s.fileMap.LoadForRead(name, func(name string, entry FileEntry) { 170 if err = op.verifyStateHelper(name, entry); err != nil { 171 return 172 } 173 f(name, entry) 174 }) 175 } else if l == _lockLevelWrite { 176 loaded = op.s.fileMap.LoadForWrite(name, func(name string, entry FileEntry) { 177 if err = op.verifyStateHelper(name, entry); err != nil { 178 return 179 } 180 f(name, entry) 181 }) 182 } 183 if !loaded { 184 return os.ErrNotExist 185 } 186 return err 187 } 188 189 func (op *localFileOp) deleteHelper( 190 name string, f func(name string, entry FileEntry) bool) (err error) { 191 if _, err = op.reloadFileEntryHelper(name); err != nil { 192 return err 193 } 194 op.s.fileMap.Delete(name, func(name string, entry FileEntry) bool { 195 err = op.verifyStateHelper(name, entry) 196 if err != nil { 197 return false 198 } 199 200 return f(name, entry) 201 }) 202 return err 203 } 204 205 // createFileHelper is a helper function that adds a new file to store. 206 // it either moves the new file from a unmanaged location, or creates an empty 207 // file with specified size. 208 // If file exists and is in an acceptable state, returns os.ErrExist. 209 // If file exists but not in an acceptable state, returns FileStateError. 210 func (op *localFileOp) createFileHelper( 211 name string, targetState FileState, sourcePath string, len int64) (err error) { 212 // Check if file exists in in-memory map and is in an acceptable state. 213 loaded := op.s.fileMap.LoadForRead(name, func(name string, entry FileEntry) { 214 err = op.verifyStateHelper(name, entry) 215 }) 216 if err != nil && !os.IsNotExist(err) { 217 // Includes FileStateError. 218 return err 219 } else if loaded { 220 return os.ErrExist 221 } 222 223 // Check if file is on disk. 224 loaded, err = op.reloadFileEntryHelper(name) 225 if err != nil && !os.IsNotExist(err) { 226 // Includes FileStateError. 227 return err 228 } else if loaded { 229 return os.ErrExist 230 } 231 232 // Create new file entry. 233 err = nil 234 newEntry, err := op.s.fileEntryFactory.Create(name, targetState) 235 if err != nil { 236 return fmt.Errorf("create: %s", err) 237 } 238 if stored := op.s.fileMap.TryStore(name, newEntry, func(name string, entry FileEntry) bool { 239 if sourcePath != "" { 240 err = newEntry.MoveFrom(targetState, sourcePath) 241 if err != nil { 242 return false 243 } 244 } else { 245 err = newEntry.Create(targetState, len) 246 if err != nil { 247 return false 248 } 249 } 250 return true 251 }); err != nil { 252 return err 253 } else if !stored { 254 // Another goroutine created the entry before this one, verify again for 255 // correct error message. 256 // Since TryStore() updates LAT of existing entry, it's unlikely that 257 // the entry would be deleted before this function returns. 258 if loadErr := op.lockHelper(name, _lockLevelRead, func(name string, entry FileEntry) { 259 return 260 }); loadErr != nil { 261 return loadErr 262 } 263 return os.ErrExist 264 } 265 266 return nil 267 } 268 269 // CreateFile creates an empty file with specified size. 270 // If file exists and is in an acceptable state, returns os.ErrExist. 271 // If file exists but not in an acceptable state, returns FileStateError. 272 func (op *localFileOp) CreateFile(name string, targetState FileState, len int64) (err error) { 273 return op.createFileHelper(name, targetState, "", len) 274 } 275 276 // MoveFileFrom moves an unmanaged file into file store. 277 // If file exists and is in an acceptable state, returns os.ErrExist. 278 // If file exists but not in an acceptable state, returns FileStateError. 279 func (op *localFileOp) MoveFileFrom(name string, targetState FileState, sourcePath string) (err error) { 280 return op.createFileHelper(name, targetState, sourcePath, -1) 281 } 282 283 // MoveFile moves a file to a different directory and updates its state 284 // accordingly, and moves all metadata that's `movable`. 285 func (op *localFileOp) MoveFile(name string, targetState FileState) (err error) { 286 if _, err = op.reloadFileEntryHelper(name); err != nil { 287 return err 288 } 289 290 // Verify that the file is not in target state, and is currently in one of 291 // the acceptable states. 292 loaded := op.s.fileMap.LoadForWrite(name, func(name string, entry FileEntry) { 293 currState := entry.GetState() 294 if currState == targetState { 295 err = os.ErrExist 296 return 297 } 298 for state := range op.states { 299 if currState == state { 300 // File is in one of the acceptable states. Perform move. 301 err = entry.Move(targetState) 302 return 303 } 304 } 305 err = &FileStateError{ 306 Op: "MoveFile", 307 State: currState, 308 Name: name, 309 Msg: fmt.Sprintf("desired states: %v", op.states), 310 } 311 }) 312 if !loaded { 313 return os.ErrNotExist 314 } 315 return err 316 } 317 318 // LinkFileTo create a hardlink to an unmanaged path. 319 func (op *localFileOp) LinkFileTo(name string, targetPath string) (err error) { 320 if loadErr := op.lockHelper(name, _lockLevelRead, func(name string, entry FileEntry) { 321 err = entry.LinkTo(targetPath) 322 }); loadErr != nil { 323 return loadErr 324 } 325 return err 326 } 327 328 // DeleteFile removes a file from disk and file map. 329 func (op *localFileOp) DeleteFile(name string) (err error) { 330 if loadErr := op.deleteHelper(name, func(name string, entry FileEntry) bool { 331 err = entry.Delete() 332 // Return true so the entry would be removed from map regardless. 333 return true 334 }); loadErr != nil { 335 return loadErr 336 } 337 return err 338 } 339 340 // GetFilePath returns full path for a file. 341 func (op *localFileOp) GetFilePath(name string) (path string, err error) { 342 if loadErr := op.lockHelper(name, _lockLevelPeek, func(name string, entry FileEntry) { 343 path = entry.GetPath() 344 }); loadErr != nil { 345 return "", loadErr 346 } 347 return path, nil 348 } 349 350 // GetFileStat returns FileInfo for a file. 351 func (op *localFileOp) GetFileStat(name string) (info os.FileInfo, err error) { 352 if loadErr := op.lockHelper(name, _lockLevelPeek, func(name string, entry FileEntry) { 353 info, err = entry.GetStat() 354 }); loadErr != nil { 355 return nil, loadErr 356 } 357 return info, err 358 } 359 360 // GetFileReader returns a FileReader object for read operations. 361 func (op *localFileOp) GetFileReader(name string) (r FileReader, err error) { 362 if loadErr := op.lockHelper(name, _lockLevelRead, func(name string, entry FileEntry) { 363 r, err = entry.GetReader() 364 }); loadErr != nil { 365 return nil, loadErr 366 } 367 return r, err 368 } 369 370 // GetFileReadWriter returns a FileReadWriter object for read/write operations. 371 func (op *localFileOp) GetFileReadWriter(name string) (w FileReadWriter, err error) { 372 if loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) { 373 w, err = entry.GetReadWriter() 374 }); loadErr != nil { 375 return nil, loadErr 376 } 377 return w, err 378 } 379 380 // GetFileMetadata loads metadata assocciated with the file. 381 func (op *localFileOp) GetFileMetadata(name string, md metadata.Metadata) (err error) { 382 if loadErr := op.lockHelper(name, _lockLevelPeek, func(name string, entry FileEntry) { 383 err = entry.GetMetadata(md) 384 }); loadErr != nil { 385 return loadErr 386 } 387 return err 388 } 389 390 // SetFileMetadata creates or overwrites metadata assocciate with the file. 391 func (op *localFileOp) SetFileMetadata(name string, md metadata.Metadata) (updated bool, err error) { 392 if loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) { 393 updated, err = entry.SetMetadata(md) 394 }); loadErr != nil { 395 return false, loadErr 396 } 397 return updated, err 398 } 399 400 // SetFileMetadataAt overwrites metadata assocciate with the file with content. 401 func (op *localFileOp) SetFileMetadataAt( 402 name string, md metadata.Metadata, b []byte, offset int64) (updated bool, err error) { 403 404 if loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) { 405 updated, err = entry.SetMetadataAt(md, b, offset) 406 }); loadErr != nil { 407 return false, loadErr 408 } 409 return updated, err 410 } 411 412 // GetOrSetFileMetadata see localFileEntryInternal. 413 func (op *localFileOp) GetOrSetFileMetadata(name string, md metadata.Metadata) (err error) { 414 if loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) { 415 err = entry.GetOrSetMetadata(md) 416 }); loadErr != nil { 417 return loadErr 418 } 419 return err 420 } 421 422 // DeleteFileMetadata deletes metadata of the specified type for a file. 423 func (op *localFileOp) DeleteFileMetadata(name string, md metadata.Metadata) (err error) { 424 loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) { 425 err = entry.DeleteMetadata(md) 426 }) 427 if loadErr != nil { 428 return loadErr 429 } 430 return err 431 } 432 433 // RangeFileMetadata loops through all metadata of one file and applies function f, until an error happens. 434 func (op *localFileOp) RangeFileMetadata(name string, f func(md metadata.Metadata) error) (err error) { 435 loadErr := op.lockHelper(name, _lockLevelWrite, func(name string, entry FileEntry) { 436 err = entry.RangeMetadata(f) 437 }) 438 if loadErr != nil { 439 return loadErr 440 } 441 return err 442 } 443 444 func (op *localFileOp) ListNames() ([]string, error) { 445 var names []string 446 for state := range op.states { 447 stateNames, err := op.s.fileEntryFactory.ListNames(state) 448 if err != nil { 449 return nil, err 450 } 451 names = append(names, stateNames...) 452 } 453 return names, nil 454 } 455 456 func (op *localFileOp) String() string { 457 var dirs []string 458 for state := range op.states { 459 dirs = append(dirs, state.GetDirectory()) 460 } 461 return fmt.Sprintf("{%s}", strings.Join(dirs, ", ")) 462 }