github.com/uber/kraken@v0.1.4/lib/store/base/file_entry.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 "bytes" 18 "errors" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "strings" 24 "sync" 25 26 "github.com/uber/kraken/lib/store/metadata" 27 "github.com/uber/kraken/utils/stringset" 28 ) 29 30 // FileEntry errors. 31 var ( 32 ErrFilePersisted = errors.New("file is persisted") 33 ErrInvalidName = errors.New("invalid name") 34 ) 35 36 // FileState decides what directory a file is in. 37 // A file can only be in one state at any given time. 38 type FileState struct { 39 directory string 40 } 41 42 // NewFileState creates a new FileState for directory. 43 func NewFileState(directory string) FileState { 44 return FileState{directory} 45 } 46 47 // GetDirectory returns the FileState's directory. 48 func (s FileState) GetDirectory() string { 49 return s.directory 50 } 51 52 // FileEntryFactory initializes FileEntry obj. 53 type FileEntryFactory interface { 54 // Create creates a file entry given a state directory and a name. 55 // It calls GetRelativePath to generate the actual file path under given directory, 56 Create(name string, state FileState) (FileEntry, error) 57 58 // GetRelativePath returns the relative path for a file entry. 59 // The path is relative to the state directory that file entry belongs to. 60 // i.e. a file entry can have a relative path of 00/0e/filename under directory /var/cache/ 61 GetRelativePath(name string) string 62 63 // ListNames lists all file entry names in state. 64 ListNames(state FileState) ([]string, error) 65 } 66 67 // FileEntry manages one file and its metadata. 68 // It doesn't guarantee thread-safety; That should be handled by FileMap. 69 type FileEntry interface { 70 GetState() FileState 71 GetName() string 72 GetPath() string 73 GetStat() (os.FileInfo, error) 74 75 Create(targetState FileState, len int64) error 76 Reload() error 77 MoveFrom(targetState FileState, sourcePath string) error 78 Move(targetState FileState) error 79 LinkTo(targetPath string) error 80 Delete() error 81 82 GetReader() (FileReader, error) 83 GetReadWriter() (FileReadWriter, error) 84 85 AddMetadata(md metadata.Metadata) error 86 87 GetMetadata(md metadata.Metadata) error 88 SetMetadata(md metadata.Metadata) (bool, error) 89 SetMetadataAt(md metadata.Metadata, b []byte, offset int64) (updated bool, err error) 90 GetOrSetMetadata(md metadata.Metadata) error 91 DeleteMetadata(md metadata.Metadata) error 92 93 RangeMetadata(f func(md metadata.Metadata) error) error 94 } 95 96 var _ FileEntryFactory = (*localFileEntryFactory)(nil) 97 var _ FileEntryFactory = (*casFileEntryFactory)(nil) 98 var _ FileEntry = (*localFileEntry)(nil) 99 100 // localFileEntryFactory initializes localFileEntry obj. 101 type localFileEntryFactory struct{} 102 103 // NewLocalFileEntryFactory is the constructor for localFileEntryFactory. 104 func NewLocalFileEntryFactory() FileEntryFactory { 105 return &localFileEntryFactory{} 106 } 107 108 // Create initializes and returns a FileEntry object. 109 func (f *localFileEntryFactory) Create(name string, state FileState) (FileEntry, error) { 110 if name != filepath.Clean(name) { 111 return nil, ErrInvalidName 112 } 113 if strings.HasPrefix(name, "/") || strings.HasSuffix(name, "/") || strings.HasPrefix(name, "../") { 114 return nil, ErrInvalidName 115 } 116 return newLocalFileEntry(state, name, f.GetRelativePath(name)), nil 117 } 118 119 // GetRelativePath returns name because file entries are stored flat under state directory. 120 func (f *localFileEntryFactory) GetRelativePath(name string) string { 121 return filepath.Join(name, DefaultDataFileName) 122 } 123 124 // ListNames returns the names of all entries in state's directory. 125 func (f *localFileEntryFactory) ListNames(state FileState) ([]string, error) { 126 var names []string 127 128 var readNames func(string) error 129 readNames = func(dir string) error { 130 infos, err := ioutil.ReadDir(dir) 131 if err != nil { 132 if os.IsNotExist(err) { 133 return nil 134 } 135 return err 136 } 137 for _, info := range infos { 138 if info.IsDir() { 139 if err := readNames(filepath.Join(dir, info.Name())); err != nil { 140 return err 141 } 142 continue 143 } 144 if info.Name() == DefaultDataFileName { 145 name, err := filepath.Rel(state.GetDirectory(), dir) 146 if err != nil { 147 return err 148 } 149 names = append(names, name) 150 } 151 } 152 return nil 153 } 154 155 err := readNames(state.GetDirectory()) 156 157 return names, err 158 } 159 160 // casFileEntryFactory initializes localFileEntry obj. 161 // It uses the first few bytes of file digest (which is also used as file name) as shard ID. 162 // For every byte, one more level of directories will be created. 163 type casFileEntryFactory struct{} 164 165 // NewCASFileEntryFactory is the constructor for casFileEntryFactory. 166 func NewCASFileEntryFactory() FileEntryFactory { 167 return &casFileEntryFactory{} 168 } 169 170 // Create initializes and returns a FileEntry object. 171 // TODO: verify name. 172 func (f *casFileEntryFactory) Create(name string, state FileState) (FileEntry, error) { 173 return newLocalFileEntry(state, name, f.GetRelativePath(name)), nil 174 } 175 176 // GetRelativePath returns content-addressable file path under state directory. 177 // Example: 178 // name = 07123e1f482356c415f684407a3b8723e10b2cbbc0b8fcd6282c49d37c9c1abc 179 // shardIDLength = 2 180 // relative path = 07/12/07123e1f482356c415f684407a3b8723e10b2cbbc0b8fcd6282c49d37c9c1abc 181 func (f *casFileEntryFactory) GetRelativePath(name string) string { 182 filePath := "" 183 for i := 0; i < int(DefaultShardIDLength) && i < len(name)/2; i++ { 184 // (1 byte = 2 char of file name assumming file name is in HEX) 185 dirName := name[i*2 : i*2+2] 186 filePath = filepath.Join(filePath, dirName) 187 } 188 189 return filepath.Join(filePath, name, DefaultDataFileName) 190 } 191 192 // ListNames returns the names of all entries within the shards of state. 193 func (f *casFileEntryFactory) ListNames(state FileState) ([]string, error) { 194 var names []string 195 196 var readNames func(string, int) error 197 readNames = func(dir string, depth int) error { 198 infos, err := ioutil.ReadDir(dir) 199 if err != nil { 200 return err 201 } 202 for _, info := range infos { 203 if depth == 0 { 204 names = append(names, info.Name()) 205 } else { 206 if !info.IsDir() { 207 continue 208 } 209 if err := readNames(filepath.Join(dir, info.Name()), depth-1); err != nil { 210 return err 211 } 212 } 213 } 214 return nil 215 } 216 217 err := readNames(state.GetDirectory(), DefaultShardIDLength) 218 219 return names, err 220 } 221 222 // localFileEntry implements FileEntry interface, handles IO operations for one file on local disk. 223 type localFileEntry struct { 224 sync.RWMutex 225 226 state FileState 227 name string 228 relativeDataPath string // Relative path to data file. 229 metadata stringset.Set // Metadata is identified by suffix. 230 } 231 232 func newLocalFileEntry( 233 state FileState, 234 name string, 235 relativeDataPath string, 236 ) *localFileEntry { 237 return &localFileEntry{ 238 state: state, 239 name: name, 240 relativeDataPath: relativeDataPath, 241 metadata: make(stringset.Set), 242 } 243 } 244 245 // GetState returns current state of the file. 246 func (entry *localFileEntry) GetState() FileState { 247 return entry.state 248 } 249 250 // GetName returns name of the file. 251 func (entry *localFileEntry) GetName() string { 252 return entry.name 253 } 254 255 // GetPath returns current path of the file. 256 func (entry *localFileEntry) GetPath() string { 257 return filepath.Join(entry.state.GetDirectory(), entry.relativeDataPath) 258 } 259 260 // GetStat returns a FileInfo describing the named file. 261 func (entry *localFileEntry) GetStat() (os.FileInfo, error) { 262 return os.Stat(entry.GetPath()) 263 } 264 265 // Create creates a file on disk. 266 func (entry *localFileEntry) Create(targetState FileState, size int64) error { 267 if entry.state != targetState { 268 return &FileStateError{ 269 Op: "Create", 270 Name: entry.name, 271 State: entry.state, 272 Msg: fmt.Sprintf("localFileEntry obj has state: %v", entry.state), 273 } 274 } 275 276 // Verify if file was already created. 277 targetPath := entry.GetPath() 278 if _, err := os.Stat(targetPath); err == nil { 279 return os.ErrExist 280 } 281 282 // Create dir. 283 if err := os.MkdirAll(filepath.Dir(targetPath), DefaultDirPermission); err != nil { 284 return err 285 } 286 287 // Create file. 288 f, err := os.Create(targetPath) 289 if err != nil { 290 return err 291 } 292 defer f.Close() 293 294 // Change size. 295 err = f.Truncate(size) 296 if err != nil { 297 // Try to delete file. 298 os.RemoveAll(filepath.Dir(targetPath)) 299 return err 300 } 301 302 return f.Close() 303 } 304 305 // Reload tries to reload a file that doesn't exist in memory from disk. 306 func (entry *localFileEntry) Reload() error { 307 // Verify the file is still on disk. 308 if _, err := os.Stat(entry.GetPath()); err != nil { 309 // Return os.ErrNotExist. 310 return err 311 } 312 313 // Load metadata. 314 files, err := ioutil.ReadDir(filepath.Dir(entry.GetPath())) 315 if err != nil { 316 return err 317 } 318 for _, currFile := range files { 319 // Glob could return the data file itself, and directories. 320 // Verify it's actually a metadata file. 321 if currFile.Name() != DefaultDataFileName { 322 md := metadata.CreateFromSuffix(currFile.Name()) 323 if md != nil { 324 // Add metadata 325 entry.AddMetadata(md) 326 } 327 } 328 } 329 return nil 330 } 331 332 // MoveFrom moves an unmanaged file in. 333 func (entry *localFileEntry) MoveFrom(targetState FileState, sourcePath string) error { 334 if entry.state != targetState { 335 return &FileStateError{ 336 Op: "MoveFrom", 337 Name: entry.name, 338 State: entry.state, 339 Msg: fmt.Sprintf("localFileEntry obj has state: %v", entry.state), 340 } 341 } 342 343 // Verify if file was already created. 344 targetPath := entry.GetPath() 345 if _, err := os.Stat(targetPath); err == nil { 346 return os.ErrExist 347 } 348 349 // Verify the source file exists. 350 if _, err := os.Stat(sourcePath); err != nil { 351 // Return os.ErrNotExist. 352 return err 353 } 354 355 // Create dir. 356 if err := os.MkdirAll(filepath.Dir(targetPath), DefaultDirPermission); err != nil { 357 return err 358 } 359 360 // Move data. 361 return os.Rename(sourcePath, targetPath) 362 } 363 364 // Move moves file to target dir under the same name, moves all metadata that's `movable`, and 365 // updates state in memory. 366 // If for any reason the target path already exists, it will be overwritten. 367 func (entry *localFileEntry) Move(targetState FileState) error { 368 sourcePath := entry.GetPath() 369 targetPath := filepath.Join(targetState.GetDirectory(), entry.relativeDataPath) 370 if err := os.MkdirAll(filepath.Dir(targetPath), DefaultDirPermission); err != nil { 371 return err 372 } 373 374 // Get file stats. 375 if _, err := os.Stat(sourcePath); err != nil { 376 // Return os.ErrNotExist. 377 return err 378 } 379 380 // Copy metadata first. 381 performCopy := func(md metadata.Metadata) error { 382 if md.Movable() { 383 sourceMetadataPath := entry.getMetadataPath(md) 384 targetMetadataPath := filepath.Join(filepath.Dir(targetPath), md.GetSuffix()) 385 bytes, err := ioutil.ReadFile(sourceMetadataPath) 386 if err != nil { 387 return err 388 } 389 if _, err := compareAndWriteFile(targetMetadataPath, bytes); err != nil { 390 return err 391 } 392 } 393 return nil 394 } 395 if err := entry.RangeMetadata(performCopy); err != nil { 396 return err 397 } 398 399 // Move data. This could be a slow operation if source and target are not on the same FS. 400 if err := os.Rename(sourcePath, targetPath); err != nil { 401 return err 402 } 403 404 // Update parent dir in memory. 405 entry.state = targetState 406 407 // Delete source dir. 408 return os.RemoveAll(filepath.Dir(sourcePath)) 409 } 410 411 // LinkTo creates a hardlink to an unmanaged path. 412 func (entry *localFileEntry) LinkTo(targetPath string) error { 413 // Create dir. 414 if err := os.MkdirAll(filepath.Dir(targetPath), DefaultDirPermission); err != nil { 415 return err 416 } 417 418 // Move data. 419 return os.Link(entry.GetPath(), targetPath) 420 } 421 422 // Delete removes file and all of its metedata files from disk. If persist 423 // metadata is present and true, delete returns ErrFilePersisted. 424 func (entry *localFileEntry) Delete() error { 425 var persist metadata.Persist 426 if err := entry.GetMetadata(&persist); err != nil { 427 if !os.IsNotExist(err) { 428 return fmt.Errorf("get persist metadata: %s", err) 429 } 430 } else { 431 if persist.Value { 432 return ErrFilePersisted 433 } 434 } 435 436 // Remove files. 437 return os.RemoveAll(filepath.Dir(entry.GetPath())) 438 } 439 440 // GetReader returns a FileReader object for read operations. 441 func (entry *localFileEntry) GetReader() (FileReader, error) { 442 f, err := os.OpenFile(entry.GetPath(), os.O_RDONLY, 0775) 443 if err != nil { 444 return nil, err 445 } 446 447 reader := &localFileReadWriter{ 448 entry: entry, 449 descriptor: f, 450 } 451 return reader, nil 452 } 453 454 // GetReadWriter returns a FileReadWriter object for read/write operations. 455 func (entry *localFileEntry) GetReadWriter() (FileReadWriter, error) { 456 f, err := os.OpenFile(entry.GetPath(), os.O_RDWR, 0775) 457 if err != nil { 458 return nil, err 459 } 460 461 readWriter := &localFileReadWriter{ 462 entry: entry, 463 descriptor: f, 464 } 465 return readWriter, nil 466 } 467 468 func (entry *localFileEntry) getMetadataPath(md metadata.Metadata) string { 469 return filepath.Join(filepath.Dir(entry.GetPath()), md.GetSuffix()) 470 } 471 472 // AddMetadata adds a new metadata type to metadata. This is primirily used during reload. 473 func (entry *localFileEntry) AddMetadata(md metadata.Metadata) error { 474 filePath := entry.getMetadataPath(md) 475 476 // Check existence. 477 if _, err := os.Stat(filePath); err != nil { 478 return err 479 } 480 entry.metadata.Add(md.GetSuffix()) 481 return nil 482 } 483 484 // GetMetadata reads and unmarshals metadata into md. 485 func (entry *localFileEntry) GetMetadata(md metadata.Metadata) error { 486 filePath := entry.getMetadataPath(md) 487 b, err := ioutil.ReadFile(filePath) 488 if err != nil { 489 return err 490 } 491 return md.Deserialize(b) 492 } 493 494 // SetMetadata updates metadata and returns true only if the file is updated correctly. 495 // It returns false if error happened or file already contains desired content. 496 func (entry *localFileEntry) SetMetadata(md metadata.Metadata) (bool, error) { 497 filePath := entry.getMetadataPath(md) 498 b, err := md.Serialize() 499 if err != nil { 500 return false, fmt.Errorf("marshal metadata: %s", err) 501 } 502 updated, err := compareAndWriteFile(filePath, b) 503 if err == nil { 504 entry.metadata.Add(md.GetSuffix()) 505 } 506 return updated, err 507 } 508 509 // SetMetadataAt overwrites a single byte of metadata. Returns true if the byte 510 // was overwritten. 511 func (entry *localFileEntry) SetMetadataAt( 512 md metadata.Metadata, b []byte, offset int64) (updated bool, err error) { 513 514 filePath := entry.getMetadataPath(md) 515 f, err := os.OpenFile(filePath, os.O_RDWR, 0775) 516 if err != nil { 517 return false, err 518 } 519 defer f.Close() 520 521 prev := make([]byte, len(b)) 522 if _, err := f.ReadAt(prev, offset); err != nil { 523 return false, err 524 } 525 if bytes.Compare(prev, b) == 0 { 526 return false, nil 527 } 528 if _, err := f.WriteAt(b, offset); err != nil { 529 return false, err 530 } 531 return true, nil 532 } 533 534 // GetOrSetMetadata writes b under metadata md if md has not been initialized yet. 535 // If the given metadata is not initialized, md is overwritten. 536 func (entry *localFileEntry) GetOrSetMetadata(md metadata.Metadata) error { 537 if entry.metadata.Has(md.GetSuffix()) { 538 return entry.GetMetadata(md) 539 } 540 b, err := md.Serialize() 541 if err != nil { 542 return fmt.Errorf("marshal metadata: %s", err) 543 } 544 filePath := filepath.Join(filepath.Dir(entry.GetPath()), md.GetSuffix()) 545 if _, err := compareAndWriteFile(filePath, b); err != nil { 546 return err 547 } 548 entry.metadata.Add(md.GetSuffix()) 549 return nil 550 } 551 552 // DeleteMetadata deletes metadata of the specified type. 553 func (entry *localFileEntry) DeleteMetadata(md metadata.Metadata) error { 554 filePath := entry.getMetadataPath(md) 555 556 // Remove from map no matter if the actual metadata file is removed from disk. 557 defer entry.metadata.Remove(md.GetSuffix()) 558 559 return os.RemoveAll(filePath) 560 } 561 562 // RangeMetadata loops through all metadata and applies function f, until an error happens. 563 func (entry *localFileEntry) RangeMetadata(f func(md metadata.Metadata) error) error { 564 for suffix := range entry.metadata { 565 md := metadata.CreateFromSuffix(suffix) 566 if md == nil { 567 return fmt.Errorf("cannot create metadata from suffix %s", suffix) 568 } 569 if err := f(md); err != nil { 570 return err 571 } 572 } 573 return nil 574 } 575 576 // compareAndWriteFile updates file with given bytes and returns true only if the file is updated 577 // correctly. 578 // It returns false if error happened or file already contains desired content. 579 func compareAndWriteFile(filePath string, b []byte) (bool, error) { 580 // Check existence. 581 fs, err := os.Stat(filePath) 582 if err != nil && !os.IsNotExist(err) { 583 return false, err 584 } 585 586 if os.IsNotExist(err) { 587 if err := os.MkdirAll(filepath.Dir(filePath), 0775); err != nil { 588 return false, err 589 } 590 591 if err := ioutil.WriteFile(filePath, b, 0775); err != nil { 592 return false, err 593 } 594 return true, nil 595 } 596 597 f, err := os.OpenFile(filePath, os.O_RDWR, 0775) 598 if err != nil { 599 return false, err 600 } 601 defer f.Close() 602 603 // Compare with existing data, overwrite if different. 604 buf := make([]byte, int(fs.Size())) 605 if _, err := f.Read(buf); err != nil { 606 return false, err 607 } 608 if bytes.Compare(buf, b) == 0 { 609 return false, nil 610 } 611 612 if len(buf) != len(b) { 613 if err := f.Truncate(int64(len(b))); err != nil { 614 return false, err 615 } 616 } 617 618 if _, err := f.WriteAt(b, 0); err != nil { 619 return false, err 620 } 621 return true, nil 622 }