github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/inode/file.go (about) 1 // Copyright (c) 2015-2021, NVIDIA CORPORATION. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package inode 5 6 import ( 7 "fmt" 8 "strconv" 9 "time" 10 11 "github.com/swiftstack/sortedmap" 12 13 "github.com/swiftstack/ProxyFS/blunder" 14 "github.com/swiftstack/ProxyFS/headhunter" 15 "github.com/swiftstack/ProxyFS/logger" 16 "github.com/swiftstack/ProxyFS/stats" 17 "github.com/swiftstack/ProxyFS/utils" 18 ) 19 20 type fileExtentStruct struct { 21 FileOffset uint64 22 Length uint64 23 LogSegmentNumber uint64 24 LogSegmentOffset uint64 25 } 26 27 func (vS *volumeStruct) CreateFile(filePerm InodeMode, userID InodeUserID, groupID InodeGroupID) (fileInodeNumber InodeNumber, err error) { 28 err = enforceRWMode(false) 29 if nil != err { 30 return 31 } 32 33 fileInode, err := vS.createFileInode(filePerm, userID, groupID) 34 if err != nil { 35 return 0, err 36 } 37 return fileInode.InodeNumber, nil 38 } 39 40 // REVIEW TODO: Should one of these (and its siblings, CreateDir & CreateSymlink) be doing flush here? 41 42 func (vS *volumeStruct) createFileInode(filePerm InodeMode, userID InodeUserID, groupID InodeGroupID) (fileInode *inMemoryInodeStruct, err error) { 43 stats.IncrementOperations(&stats.FileCreateOps) 44 45 // Create file mode out of file permissions plus inode type 46 fileMode, err := determineMode(filePerm, FileType) 47 if err != nil { 48 return nil, err 49 } 50 51 fileInode, err = vS.makeInMemoryInode(FileType, fileMode, userID, groupID) 52 if err != nil { 53 return nil, err 54 } 55 56 fileInode.dirty = true 57 58 // The payload of a file inode is a B+-tree map whose keys are uint64 file 59 // offsets and whose values are `fileExtent`s. 60 61 extents := 62 sortedmap.NewBPlusTree( 63 vS.maxExtentsPerFileNode, 64 sortedmap.CompareUint64, 65 &fileInodeCallbacks{treeNodeLoadable{inode: fileInode}}, 66 globals.fileExtentMapCache) 67 68 fileInode.payload = extents 69 70 ok, err := vS.inodeCacheInsert(fileInode) 71 if nil != err { 72 return 73 } 74 if !ok { 75 err = fmt.Errorf("inodeCacheInsert(fileInode) failed") 76 return 77 } 78 79 stats.IncrementOperations(&stats.FileCreateSuccessOps) 80 return 81 } 82 83 func fileLen(extents sortedmap.BPlusTree) uint64 { 84 numExtents, err := extents.Len() 85 if nil != err { 86 panic(err) 87 } 88 if numExtents == 0 { 89 return 0 90 } 91 92 _, value, ok, err := extents.GetByIndex(numExtents - 1) 93 if nil != err { 94 panic(err) 95 } 96 if !ok { 97 panic("couldn't find last extent of nonempty file") 98 } 99 100 lastFileExtent := value.(*fileExtentStruct) 101 102 return lastFileExtent.FileOffset + lastFileExtent.Length 103 } 104 105 // Update a file inode to have a new size, zero-padding as necessary. 106 // 107 // Doesn't flush anything. 108 func setSizeInMemory(fileInode *inMemoryInodeStruct, size uint64) (err error) { 109 extents := fileInode.payload.(sortedmap.BPlusTree) 110 extentIndex, found, err := extents.BisectLeft(size) 111 if nil != err { 112 panic(err) 113 } 114 115 if !found { 116 if 0 <= extentIndex { 117 // Potentially trim preceeding extent 118 _, extentValue, ok, getByIndexErr := extents.GetByIndex(extentIndex) 119 if nil != getByIndexErr { 120 panic(getByIndexErr) 121 } 122 if !ok { 123 unexpectedErr := fmt.Errorf("unexpected extents indexing problem") 124 panic(unexpectedErr) 125 } 126 extent := extentValue.(*fileExtentStruct) 127 if (extent.FileOffset + extent.Length) > size { 128 // Yes, we need to trim the preceeding extent 129 trimSize := extent.Length - (size - extent.FileOffset) 130 extent.Length -= trimSize 131 ok, patchByIndexErr := extents.PatchByIndex(extentIndex, extent) 132 if nil != patchByIndexErr { 133 panic(patchByIndexErr) 134 } 135 if !ok { 136 unexpectedErr := fmt.Errorf("unexpected extents indexing problem") 137 panic(unexpectedErr) 138 } 139 decrementLogSegmentMapFileData(fileInode, extent.LogSegmentNumber, trimSize) 140 } 141 } 142 143 extentIndex++ // Step to next extent entry (beyond potentially truncated preceeding extent) 144 } 145 146 for { 147 _, extentValue, ok, getByIndexErr := extents.GetByIndex(extentIndex) 148 if nil != getByIndexErr { 149 panic(getByIndexErr) 150 } 151 if !ok { 152 break 153 } 154 extent := extentValue.(*fileExtentStruct) 155 decrementLogSegmentMapFileData(fileInode, extent.LogSegmentNumber, extent.Length) 156 ok, deleteByIndexErr := extents.DeleteByIndex(extentIndex) 157 if nil != deleteByIndexErr { 158 panic(deleteByIndexErr) 159 } 160 if !ok { 161 deleteByIndexErr = fmt.Errorf("extent just indexed should have been delete-able") 162 panic(deleteByIndexErr) 163 } 164 } 165 166 fileInode.dirty = true 167 fileInode.Size = size 168 169 updateTime := time.Now() 170 fileInode.ModificationTime = updateTime 171 fileInode.AttrChangeTime = updateTime 172 return 173 } 174 175 func (vS *volumeStruct) Read(fileInodeNumber InodeNumber, offset uint64, length uint64, profiler *utils.Profiler) (buf []byte, err error) { 176 var ( 177 fileInode *inMemoryInodeStruct 178 readPlan []ReadPlanStep 179 readPlanBytes uint64 180 snapShotID uint64 181 ) 182 183 fileInode, err = vS.fetchInodeType(fileInodeNumber, FileType) 184 if nil != err { 185 logger.ErrorWithError(err) 186 return 187 } 188 189 _, snapShotID, _ = vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber)) 190 191 readPlan, readPlanBytes, err = vS.getReadPlanHelper(snapShotID, fileInode, &offset, &length) 192 if nil != err { 193 logger.WarnWithError(err) 194 return 195 } 196 197 buf, err = vS.doReadPlan(fileInode, readPlan, readPlanBytes) 198 if nil != err { 199 logger.WarnWithError(err) 200 return 201 } 202 203 stats.IncrementOperationsAndBucketedBytes(stats.FileRead, uint64(len(buf))) 204 205 err = nil 206 return 207 } 208 209 func (vS *volumeStruct) GetReadPlan(fileInodeNumber InodeNumber, offset *uint64, length *uint64) (readPlan []ReadPlanStep, err error) { 210 var ( 211 readPlanBytes uint64 212 snapShotID uint64 213 ) 214 215 fileInode, err := vS.fetchInodeType(fileInodeNumber, FileType) 216 if nil != err { 217 logger.ErrorWithError(err) 218 return 219 } 220 221 if fileInode.dirty { 222 err = flush(fileInode, false) 223 if nil != err { 224 logger.ErrorWithError(err) 225 return 226 } 227 } 228 229 _, snapShotID, _ = vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber)) 230 231 readPlan, readPlanBytes, err = vS.getReadPlanHelper(snapShotID, fileInode, offset, length) 232 if nil != err { 233 logger.ErrorWithError(err) 234 return 235 } 236 237 stats.IncrementOperationsBucketedEntriesAndBucketedBytes(stats.FileReadplan, uint64(len(readPlan)), readPlanBytes) 238 return 239 } 240 241 func (vS *volumeStruct) getReadPlanHelper(snapShotID uint64, fileInode *inMemoryInodeStruct, requestedOffset *uint64, requestedLength *uint64) (readPlan []ReadPlanStep, readPlanBytes uint64, err error) { 242 var ( 243 offset uint64 244 ) 245 246 readPlan = make([]ReadPlanStep, 0) 247 248 if requestedOffset == nil && requestedLength == nil { 249 err = fmt.Errorf("requestedOffset and requestedLength cannot both be nil") 250 return 251 } else if requestedOffset == nil { 252 // Suffix request, e.g. "bytes=-10", the last 10 bytes of the file 253 if fileInode.Size > *requestedLength { 254 offset = fileInode.Size - *requestedLength 255 readPlanBytes = *requestedLength 256 } else { 257 // A suffix request for more bytes than the file has must get the whole file. 258 offset = 0 259 readPlanBytes = fileInode.Size 260 } 261 } else if requestedLength == nil { 262 // Prefix request, e.g. "bytes=25-", from byte 25 to the end 263 offset = *requestedOffset 264 readPlanBytes = fileInode.Size - *requestedOffset 265 } else { 266 offset = *requestedOffset 267 readPlanBytes = *requestedLength 268 } 269 270 if (offset + readPlanBytes) > fileInode.Size { 271 if fileInode.Size > offset { 272 readPlanBytes = fileInode.Size - offset 273 } else { 274 readPlanBytes = 0 275 } 276 } 277 278 if 0 == readPlanBytes { 279 return 280 } 281 282 extents := fileInode.payload.(sortedmap.BPlusTree) 283 284 curOffset := offset 285 terminalOffset := offset + readPlanBytes 286 287 curExtentIndex, _, err := extents.BisectLeft(offset) 288 if nil != err { 289 panic(err) 290 } 291 292 for { 293 if 0 > curExtentIndex { 294 // Skip to next (0th) extent 295 curExtentIndex++ 296 } 297 298 // Fetch curExtent 299 _, curExtentValue, ok, getByIndexErr := extents.GetByIndex(curExtentIndex) 300 if nil != getByIndexErr { 301 panic(getByIndexErr) 302 } 303 if !ok { 304 // We have reached the end of extents 305 break 306 } 307 curExtent := curExtentValue.(*fileExtentStruct) 308 309 if curExtent.FileOffset >= terminalOffset { 310 // [curOffset:terminalOffset) ends before curExtent... so we need look no further 311 break 312 } 313 314 if (curExtent.FileOffset + curExtent.Length) <= curOffset { 315 // [curOffset:terminalOffset) starts beyond curExtent... so move to the next extent 316 curExtentIndex++ 317 continue 318 } 319 320 // At this point, we know [curOffset:terminalOffset) intersects curExtent 321 322 if curOffset < curExtent.FileOffset { 323 // [curOffset:terminalOffset) starts before curExtent... so insert a zero-fill ReadPlanStep to get to curExtent 324 step := ReadPlanStep{ 325 LogSegmentNumber: 0, 326 Offset: 0, 327 Length: curExtent.FileOffset - curOffset, 328 AccountName: "", 329 ContainerName: "", 330 ObjectName: "", 331 ObjectPath: "", 332 } 333 readPlan = append(readPlan, step) 334 curOffset = curExtent.FileOffset 335 } 336 337 skipSize := uint64(0) 338 339 if curOffset > curExtent.FileOffset { 340 // [curOffset:terminalOffset) starts after curExtent... so update skipSize appropriately 341 skipSize = curOffset - curExtent.FileOffset 342 } 343 344 if terminalOffset <= (curExtent.FileOffset + curExtent.Length) { 345 // [curOffset:terminalOffset) is completed by some or all of curExtent 346 step := ReadPlanStep{ 347 LogSegmentNumber: vS.headhunterVolumeHandle.SnapShotIDAndNonceEncode(snapShotID, curExtent.LogSegmentNumber), 348 Offset: curExtent.LogSegmentOffset + skipSize, 349 Length: terminalOffset - curOffset, 350 AccountName: vS.accountName, 351 } 352 step.ContainerName, step.ObjectName, step.ObjectPath, err = vS.getObjectLocationFromLogSegmentNumber(step.LogSegmentNumber) 353 if nil != err { 354 return 355 } 356 readPlan = append(readPlan, step) 357 curOffset = terminalOffset 358 break 359 } else { 360 // [curOffset:terminalOffset) extends beyond curExtent 361 step := ReadPlanStep{ 362 LogSegmentNumber: vS.headhunterVolumeHandle.SnapShotIDAndNonceEncode(snapShotID, curExtent.LogSegmentNumber), 363 Offset: curExtent.LogSegmentOffset + skipSize, 364 Length: curExtent.Length - skipSize, 365 AccountName: vS.accountName, 366 } 367 step.ContainerName, step.ObjectName, step.ObjectPath, err = vS.getObjectLocationFromLogSegmentNumber(step.LogSegmentNumber) 368 if nil != err { 369 return 370 } 371 readPlan = append(readPlan, step) 372 curOffset += step.Length 373 curExtentIndex++ 374 } 375 } 376 377 // Append trailing zero-fill ReadPlanStep if necessary 378 379 if curOffset < terminalOffset { 380 // We need a trailing zero-fill ReadPlanStep 381 step := ReadPlanStep{ 382 LogSegmentNumber: 0, 383 Offset: 0, 384 Length: terminalOffset - curOffset, 385 AccountName: "", 386 ContainerName: "", 387 ObjectName: "", 388 ObjectPath: "", 389 } 390 391 readPlan = append(readPlan, step) 392 } 393 394 err = nil 395 return 396 } 397 398 func (vS *volumeStruct) FetchExtentMapChunk(fileInodeNumber InodeNumber, fileOffset uint64, maxEntriesFromFileOffset int64, maxEntriesBeforeFileOffset int64) (extentMapChunk *ExtentMapChunkStruct, err error) { 399 var ( 400 containerName string 401 encodedLogSegmentNumber uint64 402 extentMap sortedmap.BPlusTree 403 extentMapLen int 404 extentMapIndex int 405 extentMapIndexAtOffset int 406 extentMapIndexAtOffsetFound bool 407 extentMapIndexEnd int 408 extentMapIndexStart int 409 fileExtent *fileExtentStruct 410 fileExtentAsValue sortedmap.Value 411 fileInode *inMemoryInodeStruct 412 objectName string 413 snapShotID uint64 414 ) 415 416 // Validate args 417 418 if maxEntriesFromFileOffset < 1 { 419 err = fmt.Errorf("inode.FetchExtentMap() requires maxEntriesFromOffset (%d) >= 1", maxEntriesFromFileOffset) 420 return 421 } 422 if maxEntriesBeforeFileOffset < 0 { 423 err = fmt.Errorf("inode.FetchExtentMap() requires maxEntriesBeforeOffset (%d) >= 0", maxEntriesBeforeFileOffset) 424 return 425 } 426 427 fileInode, err = vS.fetchInodeType(fileInodeNumber, FileType) 428 if nil != err { 429 return 430 } 431 432 // Ensure in-flight LogSegments are flushed 433 434 if fileInode.dirty { 435 err = flush(fileInode, false) 436 if nil != err { 437 logger.ErrorWithError(err) 438 return 439 } 440 } 441 442 // Locate extent that either contains fileOffset, 443 // or if no extent does, that we select the one just after fileOffset 444 445 extentMap = fileInode.payload.(sortedmap.BPlusTree) 446 447 extentMapLen, err = extentMap.Len() 448 if nil != err { 449 panic(err) 450 } 451 452 if 0 == extentMapLen { 453 // In the absence of any extents, just describe entire fileInode as zero-filled 454 455 extentMapChunk = &ExtentMapChunkStruct{ 456 FileOffsetRangeStart: 0, 457 FileOffsetRangeEnd: fileInode.Size, 458 FileSize: fileInode.Size, 459 ExtentMapEntry: make([]ExtentMapEntryStruct, 0), 460 } 461 return 462 } 463 464 extentMapIndexAtOffset, extentMapIndexAtOffsetFound, err = extentMap.BisectLeft(fileOffset) 465 if nil != err { 466 panic(err) 467 } 468 469 if !extentMapIndexAtOffsetFound { 470 if extentMapIndexAtOffset >= 0 { 471 _, fileExtentAsValue, _, err = extentMap.GetByIndex(extentMapIndexAtOffset) 472 if nil != err { 473 panic(err) 474 } 475 476 fileExtent = fileExtentAsValue.(*fileExtentStruct) 477 478 if (fileExtent.FileOffset + fileExtent.Length) <= fileOffset { 479 extentMapIndexAtOffset++ 480 } 481 } else { 482 extentMapIndexAtOffset = 0 483 } 484 } 485 486 // Compute extent indices surrounding fileOffset that are also requested 487 488 if int64(extentMapIndexAtOffset) > maxEntriesBeforeFileOffset { 489 extentMapIndexStart = extentMapIndexAtOffset - int(maxEntriesBeforeFileOffset) 490 } else { 491 extentMapIndexStart = 0 492 } 493 494 if int64(extentMapLen-extentMapIndexAtOffset) <= maxEntriesFromFileOffset { 495 extentMapIndexEnd = extentMapLen - 1 496 } else { 497 extentMapIndexEnd = extentMapIndexAtOffset + int(maxEntriesFromFileOffset) - 1 498 } 499 500 // Populate extentMapChunk with selected extents 501 502 _, snapShotID, _ = vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber)) 503 504 extentMapChunk = &ExtentMapChunkStruct{ 505 FileSize: fileInode.Size, 506 ExtentMapEntry: make([]ExtentMapEntryStruct, 0, extentMapIndexEnd-extentMapIndexStart+1), 507 } 508 509 // Fill in FileOffsetRangeStart to include zero-filled (non-)extent just before first returned extent 510 511 if extentMapIndexStart > 0 { 512 _, fileExtentAsValue, _, err = extentMap.GetByIndex(extentMapIndexStart - 1) 513 if nil != err { 514 panic(err) 515 } 516 517 fileExtent = fileExtentAsValue.(*fileExtentStruct) 518 519 extentMapChunk.FileOffsetRangeStart = fileExtent.FileOffset + fileExtent.Length 520 } else { 521 extentMapChunk.FileOffsetRangeStart = 0 522 } 523 524 for extentMapIndex = extentMapIndexStart; extentMapIndex <= extentMapIndexEnd; extentMapIndex++ { 525 _, fileExtentAsValue, _, err = extentMap.GetByIndex(extentMapIndex) 526 if nil != err { 527 panic(err) 528 } 529 530 fileExtent = fileExtentAsValue.(*fileExtentStruct) 531 532 encodedLogSegmentNumber = vS.headhunterVolumeHandle.SnapShotIDAndNonceEncode(snapShotID, fileExtent.LogSegmentNumber) 533 534 containerName, objectName, _, err = vS.getObjectLocationFromLogSegmentNumber(encodedLogSegmentNumber) 535 if nil != err { 536 panic(err) 537 } 538 539 extentMapChunk.ExtentMapEntry = append(extentMapChunk.ExtentMapEntry, ExtentMapEntryStruct{ 540 FileOffset: fileExtent.FileOffset, 541 LogSegmentOffset: fileExtent.LogSegmentOffset, 542 Length: fileExtent.Length, 543 ContainerName: containerName, 544 ObjectName: objectName, 545 }) 546 } 547 548 // Fill in FileOffsetRangeEnd to included zero-filled (non-)extent just after last returned extent 549 550 if (extentMapIndexEnd + 1) == extentMapLen { 551 extentMapChunk.FileOffsetRangeEnd = fileInode.Size 552 } else { 553 _, fileExtentAsValue, _, err = extentMap.GetByIndex(extentMapIndexEnd + 1) 554 if nil != err { 555 panic(err) 556 } 557 558 fileExtent = fileExtentAsValue.(*fileExtentStruct) 559 560 extentMapChunk.FileOffsetRangeEnd = fileExtent.FileOffset 561 } 562 563 return 564 } 565 566 func incrementLogSegmentMapFileData(fileInode *inMemoryInodeStruct, logSegmentNumber uint64, incrementAmount uint64) { 567 logSegmentRecord, ok := fileInode.LogSegmentMap[logSegmentNumber] 568 if ok { 569 logSegmentRecord += incrementAmount 570 fileInode.LogSegmentMap[logSegmentNumber] = logSegmentRecord 571 } else { 572 fileInode.LogSegmentMap[logSegmentNumber] = incrementAmount 573 } 574 } 575 576 func decrementLogSegmentMapFileData(fileInode *inMemoryInodeStruct, logSegmentNumber uint64, decrementAmount uint64) { 577 logSegmentRecord, ok := fileInode.LogSegmentMap[logSegmentNumber] 578 if ok { 579 if decrementAmount > logSegmentRecord { 580 err := fmt.Errorf("Unexpected decrementLogSegmentMapFileData() call would make FileData \"go negative\"") 581 panic(err) 582 } 583 logSegmentRecord -= decrementAmount 584 fileInode.LogSegmentMap[logSegmentNumber] = logSegmentRecord 585 } else { 586 err := fmt.Errorf("Unexpected decrementLogSegmentMapFileData() call referenced non-existent logSegmentNumber") 587 panic(err) 588 } 589 } 590 591 // `recordWrite` is called by `Write` and `Wrote` to update the file inode 592 // payload's record of the extents that compose the file. 593 func recordWrite(fileInode *inMemoryInodeStruct, fileOffset uint64, length uint64, logSegmentNumber uint64, logSegmentOffset uint64) (err error) { 594 extents := fileInode.payload.(sortedmap.BPlusTree) 595 596 // First we need to eliminate extents or portions thereof that overlap the specified write 597 598 extentIndex, found, err := extents.BisectLeft(fileOffset) 599 if nil != err { 600 panic(err) 601 } 602 603 if !found { 604 if 0 <= extentIndex { 605 // Potentially split preceeding extent 606 _, extentValue, ok, getByIndexErr := extents.GetByIndex(extentIndex) 607 if nil != getByIndexErr { 608 panic(getByIndexErr) 609 } 610 if !ok { 611 unexpectedErr := fmt.Errorf("unexpected extents indexing problem") 612 panic(unexpectedErr) 613 } 614 leftExtent := extentValue.(*fileExtentStruct) 615 if (leftExtent.FileOffset + leftExtent.Length) > fileOffset { 616 // Yes, we need to split the preceeding extent 617 splitOutSize := (leftExtent.FileOffset + leftExtent.Length) - fileOffset 618 leftExtent.Length -= splitOutSize 619 ok, patchByIndexErr := extents.PatchByIndex(extentIndex, leftExtent) 620 if nil != patchByIndexErr { 621 panic(patchByIndexErr) 622 } 623 if !ok { 624 unexpectedErr := fmt.Errorf("unexpected extents indexing problem") 625 panic(unexpectedErr) 626 } 627 rightExtent := &fileExtentStruct{ 628 FileOffset: fileOffset, 629 Length: splitOutSize, 630 LogSegmentNumber: leftExtent.LogSegmentNumber, 631 LogSegmentOffset: leftExtent.LogSegmentOffset + leftExtent.Length, 632 } 633 ok, putErr := extents.Put(rightExtent.FileOffset, rightExtent) 634 if nil != putErr { 635 panic(putErr) 636 } 637 if !ok { 638 unexpectedErr := fmt.Errorf("unexpected extents key problem") 639 panic(unexpectedErr) 640 } 641 } 642 } 643 644 extentIndex++ // Step to next extent entry (beyond potentially truncated preceeding extent) 645 } 646 647 for { 648 _, extentValue, ok, getByIndexErr := extents.GetByIndex(extentIndex) 649 if nil != getByIndexErr { 650 panic(getByIndexErr) 651 } 652 if !ok { 653 // We have reached the end of extents 654 break 655 } 656 leftExtent := extentValue.(*fileExtentStruct) 657 if leftExtent.FileOffset >= (fileOffset + length) { 658 // We are done pruning extents 659 break 660 } 661 if (fileOffset + length) >= (leftExtent.FileOffset + leftExtent.Length) { 662 // This extent entirely overwritten... just delete it 663 decrementLogSegmentMapFileData(fileInode, leftExtent.LogSegmentNumber, leftExtent.Length) 664 ok, deleteByIndexErr := extents.DeleteByIndex(extentIndex) 665 if nil != deleteByIndexErr { 666 panic(deleteByIndexErr) 667 } 668 if !ok { 669 unexpectedErr := fmt.Errorf("unexpected extents indexing problem") 670 panic(unexpectedErr) 671 } 672 } else { 673 // This extent partially overwritten... trim it from the front and we will be done trimming 674 ok, deleteByIndexErr := extents.DeleteByIndex(extentIndex) 675 if nil != deleteByIndexErr { 676 panic(deleteByIndexErr) 677 } 678 if !ok { 679 unexpectedErr := fmt.Errorf("unexpected extents indexing problem") 680 panic(unexpectedErr) 681 } 682 overlapSize := (fileOffset + length) - leftExtent.FileOffset 683 rightExtent := &fileExtentStruct{ 684 FileOffset: leftExtent.FileOffset + overlapSize, 685 Length: leftExtent.Length - overlapSize, 686 LogSegmentNumber: leftExtent.LogSegmentNumber, 687 LogSegmentOffset: leftExtent.LogSegmentOffset + overlapSize, 688 } 689 ok, putErr := extents.Put(rightExtent.FileOffset, rightExtent) 690 if nil != putErr { 691 panic(putErr) 692 } 693 if !ok { 694 unexpectedErr := fmt.Errorf("unexpected extents key problem") 695 panic(unexpectedErr) 696 } 697 decrementLogSegmentMapFileData(fileInode, leftExtent.LogSegmentNumber, overlapSize) 698 break 699 } 700 } 701 702 // Now that there will be no overlap, see if we can append to the preceding fileExtent 703 704 prevIndex, found, err := extents.BisectLeft(fileOffset) 705 if nil != err { 706 panic(err) 707 } 708 if found { 709 unexpectedErr := fmt.Errorf("unexpected to find fileOffset in extents at this point)") 710 panic(unexpectedErr) 711 } 712 713 var prevExtent *fileExtentStruct 714 715 prevExtent = nil 716 717 if 0 <= prevIndex { 718 _, prevExtentValue, ok, getByIndexErr := extents.GetByIndex(prevIndex) 719 if nil != getByIndexErr { 720 panic(getByIndexErr) 721 } 722 if !ok { 723 unexpectedErr := fmt.Errorf("unexpected to not find predecessor in extents at this point") 724 panic(unexpectedErr) 725 } 726 727 prevExtent = prevExtentValue.(*fileExtentStruct) 728 } 729 730 if (nil != prevExtent) && (prevExtent.LogSegmentNumber == logSegmentNumber) && ((prevExtent.FileOffset + prevExtent.Length) == fileOffset) && ((prevExtent.LogSegmentOffset + prevExtent.Length) == logSegmentOffset) { 731 // APPEND Case: We are able to simply lengthen prevExtent 732 733 prevExtent.Length += length 734 ok, patchByIndexErr := extents.PatchByIndex(prevIndex, prevExtent) 735 if nil != patchByIndexErr { 736 panic(patchByIndexErr) 737 } 738 if !ok { 739 unexpectedErr := fmt.Errorf("unexpected to not be able to PATCH at this point") 740 panic(unexpectedErr) 741 } 742 } else { 743 // Non-APPEND Case: We need to insert a new extent 744 745 newExtent := &fileExtentStruct{ 746 FileOffset: fileOffset, 747 Length: length, 748 LogSegmentNumber: logSegmentNumber, 749 LogSegmentOffset: logSegmentOffset, 750 } 751 752 ok, putErr := extents.Put(newExtent.FileOffset, newExtent) 753 if nil != putErr { 754 panic(putErr) 755 } 756 if !ok { 757 unexpectedErr := fmt.Errorf("unexpected to not be able to PUT at this point") 758 panic(unexpectedErr) 759 } 760 } 761 762 if (fileOffset + length) > fileInode.Size { 763 fileInode.Size = fileOffset + length 764 } 765 766 incrementLogSegmentMapFileData(fileInode, logSegmentNumber, length) 767 768 return nil 769 } 770 771 func (vS *volumeStruct) Write(fileInodeNumber InodeNumber, offset uint64, buf []byte, profiler *utils.Profiler) (err error) { 772 err = enforceRWMode(true) 773 if nil != err { 774 return 775 } 776 777 snapShotIDType, _, _ := vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber)) 778 if headhunter.SnapShotIDTypeLive != snapShotIDType { 779 err = fmt.Errorf("Write() on non-LiveView fileInodeNumber not allowed") 780 return 781 } 782 783 fileInode, err := vS.fetchInodeType(fileInodeNumber, FileType) 784 if nil != err { 785 logger.ErrorWithError(err) 786 return 787 } 788 789 // writes of length 0 succeed but do not change mtime 790 if len(buf) == 0 { 791 return 792 } 793 794 fileInode.dirty = true 795 796 logSegmentNumber, logSegmentOffset, err := vS.doSendChunk(fileInode, buf) 797 if nil != err { 798 logger.ErrorWithError(err) 799 return 800 } 801 802 length := uint64(len(buf)) 803 startingSize := fileInode.Size 804 805 err = recordWrite(fileInode, offset, length, logSegmentNumber, logSegmentOffset) 806 if nil != err { 807 logger.ErrorWithError(err) 808 return 809 } 810 811 appendedBytes := fileInode.Size - startingSize 812 overwrittenBytes := length - appendedBytes 813 814 stats.IncrementOperationsBucketedBytesAndAppendedOverwritten(stats.FileWrite, length, appendedBytes, overwrittenBytes) 815 816 updateTime := time.Now() 817 fileInode.AttrChangeTime = updateTime 818 fileInode.ModificationTime = updateTime 819 fileInode.NumWrites++ 820 821 return 822 } 823 824 func (vS *volumeStruct) Wrote(fileInodeNumber InodeNumber, containerName string, objectName string, fileOffset []uint64, objectOffset []uint64, length []uint64, wroteTime time.Time, patchOnly bool) (err error) { 825 err = enforceRWMode(false) 826 if nil != err { 827 return 828 } 829 830 if (len(fileOffset) != len(objectOffset)) || (len(objectOffset) != len(length)) { 831 err = fmt.Errorf("Wrote() called with unequal # of fileOffset's (%d), objectOffset's (%d), and length's (%d)", len(fileOffset), len(objectOffset), len(length)) 832 return 833 } 834 835 snapShotIDType, _, _ := vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber)) 836 if headhunter.SnapShotIDTypeLive != snapShotIDType { 837 err = fmt.Errorf("Wrote() on non-LiveView fileInodeNumber not allowed") 838 return 839 } 840 841 fileInode, err := vS.fetchInodeType(fileInodeNumber, FileType) 842 if err != nil { 843 logger.ErrorWithError(err) 844 return err 845 } 846 847 if fileInode.dirty { 848 err = flush(fileInode, false) 849 if nil != err { 850 logger.ErrorWithError(err) 851 return 852 } 853 } 854 855 logSegmentNumber, err := strconv.ParseUint(objectName, 16, 64) 856 if err != nil { 857 return 858 } 859 860 err = fileInode.volume.setLogSegmentContainer(logSegmentNumber, containerName) 861 if nil != err { 862 return 863 } 864 865 fileInode.dirty = true 866 867 if !patchOnly { 868 err = setSizeInMemory(fileInode, 0) 869 if err != nil { 870 logger.ErrorWithError(err) 871 return 872 } 873 } 874 875 bytesWritten := uint64(0) 876 877 for i, thisLength := range length { 878 if 0 < thisLength { 879 err = recordWrite(fileInode, fileOffset[i], thisLength, logSegmentNumber, objectOffset[i]) 880 if err != nil { 881 logger.ErrorWithError(err) 882 return 883 } 884 885 fileInode.NumWrites++ 886 bytesWritten += thisLength 887 } 888 } 889 890 if !patchOnly { 891 // For this case only, make it appear we did precisely one write 892 893 fileInode.NumWrites = 1 894 } 895 896 fileInode.AttrChangeTime = wroteTime 897 fileInode.ModificationTime = wroteTime 898 899 err = fileInode.volume.flushInode(fileInode) 900 if err != nil { 901 logger.ErrorWithError(err) 902 return 903 } 904 905 stats.IncrementOperationsAndBucketedBytes(stats.FileWrote, bytesWritten) 906 907 return 908 } 909 910 func (vS *volumeStruct) SetSize(fileInodeNumber InodeNumber, size uint64) (err error) { 911 err = enforceRWMode(false) 912 if nil != err { 913 return 914 } 915 916 snapShotIDType, _, _ := vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(fileInodeNumber)) 917 if headhunter.SnapShotIDTypeLive != snapShotIDType { 918 err = fmt.Errorf("SetSize() on non-LiveView fileInodeNumber not allowed") 919 return 920 } 921 922 fileInode, err := vS.fetchInodeType(fileInodeNumber, FileType) 923 if nil != err { 924 return err 925 } 926 927 fileInode.dirty = true 928 929 err = setSizeInMemory(fileInode, size) 930 if nil != err { 931 logger.ErrorWithError(err) 932 return 933 } 934 935 // changing the file's size is just like a write 936 fileInode.NumWrites++ 937 938 err = fileInode.volume.flushInode(fileInode) 939 if nil != err { 940 logger.ErrorWithError(err) 941 return err 942 } 943 944 stats.IncrementOperations(&stats.DirSetsizeOps) 945 946 return 947 } 948 949 func (vS *volumeStruct) Flush(fileInodeNumber InodeNumber, andPurge bool) (err error) { 950 err = enforceRWMode(false) 951 if nil != err { 952 return 953 } 954 955 fileInode, ok, err := vS.fetchInode(fileInodeNumber) 956 if nil != err { 957 // this indicates disk corruption or software bug 958 // (err includes volume name and inode number) 959 logger.ErrorfWithError(err, "%s: request to flush inode %d volume '%s' failed", 960 utils.GetFnName(), fileInodeNumber, vS.volumeName) 961 return 962 } 963 if !ok { 964 // this can happen if background flush loses a race with unlink() 965 logger.Infof("%s: request to flush free inode %d volume '%s' ignored", 966 utils.GetFnName(), fileInodeNumber, vS.volumeName) 967 return 968 } 969 if fileInode.InodeType != FileType { 970 // this should never happen unless there's disk corruption 971 logger.Errorf("%s: request to flush inode %d volume '%s' type '%v' ignored", 972 utils.GetFnName(), fileInodeNumber, vS.volumeName, fileInode.InodeType) 973 return 974 } 975 976 if fileInode.dirty { 977 err = flush(fileInode, andPurge) 978 if nil != err { 979 logger.ErrorWithError(err) 980 return 981 } 982 } 983 984 stats.IncrementOperations(&stats.FileFlushOps) 985 986 return 987 } 988 989 func flush(fileInode *inMemoryInodeStruct, andPurge bool) (err error) { 990 vS := fileInode.volume 991 err = vS.flushInode(fileInode) 992 if nil != err { 993 logger.ErrorfWithError(err, "flushInode(fileInode) failed") 994 } 995 996 if andPurge { 997 var ok bool 998 ok, err = vS.inodeCacheDrop(fileInode) 999 if nil != err { 1000 return 1001 } 1002 if !ok { 1003 err = fmt.Errorf("inodeCacheDrop(fileInode) failed") 1004 return 1005 } 1006 } 1007 1008 return 1009 } 1010 1011 func (vS *volumeStruct) resetFileInodeInMemory(fileInode *inMemoryInodeStruct) (err error) { 1012 var ( 1013 fileInodeExtentMap sortedmap.BPlusTree 1014 ok bool 1015 ) 1016 1017 fileInode.dirty = true 1018 fileInodeExtentMap = fileInode.payload.(sortedmap.BPlusTree) 1019 1020 ok = true 1021 for ok { 1022 ok, err = fileInodeExtentMap.DeleteByIndex(0) 1023 if nil != err { 1024 err = fmt.Errorf("resetFileInodeInMemory() on Inode# 0x%016X failed: %v", fileInode.InodeNumber, err) 1025 return 1026 } 1027 } 1028 1029 fileInode.LogSegmentMap = make(map[uint64]uint64) 1030 fileInode.Size = 0 1031 fileInode.NumWrites = 0 1032 1033 err = nil 1034 return 1035 } 1036 1037 func (vS *volumeStruct) Coalesce(destInodeNumber InodeNumber, metaDataName string, metaData []byte, elements []*CoalesceElement) (attrChangeTime time.Time, modificationTime time.Time, numWrites uint64, fileSize uint64, err error) { 1038 var ( 1039 alreadyInInodeMap bool 1040 coalesceTime time.Time 1041 destInode *inMemoryInodeStruct 1042 destInodeExtentMap sortedmap.BPlusTree 1043 destInodeOffsetBeforeElementAppend uint64 1044 dirEntryInodeNumber InodeNumber 1045 element *CoalesceElement 1046 elementInode *inMemoryInodeStruct 1047 elementInodeExtent *fileExtentStruct 1048 elementInodeExtentAsValue sortedmap.Value 1049 elementInodeExtentMap sortedmap.BPlusTree 1050 elementInodeExtentMapIndex int 1051 elementInodeExtentMapLen int 1052 inodeList []*inMemoryInodeStruct 1053 inodeMap map[InodeNumber]*inMemoryInodeStruct 1054 localErr error 1055 logSegmentReferencedBytes uint64 1056 ok bool 1057 snapShotIDType headhunter.SnapShotIDType 1058 toDestroyInodeNumber InodeNumber 1059 ) 1060 1061 err = enforceRWMode(false) 1062 if nil != err { 1063 return 1064 } 1065 1066 // Validate all referenced {Dir|File}Inodes 1067 1068 inodeMap = make(map[InodeNumber]*inMemoryInodeStruct) 1069 inodeList = make([]*inMemoryInodeStruct, 0, 1+len(elements)) 1070 1071 snapShotIDType, _, _ = vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(destInodeNumber)) 1072 if headhunter.SnapShotIDTypeLive != snapShotIDType { 1073 err = blunder.NewError(blunder.PermDeniedError, "Coalesce into non-LiveView destInodeNumber 0x%016X not allowed", destInodeNumber) 1074 return 1075 } 1076 1077 destInode, ok, err = vS.fetchInode(destInodeNumber) 1078 if nil != err { 1079 err = blunder.NewError(blunder.BadFileError, "Coalesce() couldn't fetch destInodeNumber 0x%016X: %v", destInodeNumber, err) 1080 return 1081 } 1082 if !ok { 1083 err = blunder.NewError(blunder.NotFoundError, "Coalesce() couldn't find destInodeNumber 0x%16X", destInodeNumber) 1084 return 1085 } 1086 if destInode.InodeType != FileType { 1087 err = blunder.NewError(blunder.PermDeniedError, "Coalesce() called for destInodeNumber 0x%016X that is not a FileInode", destInodeNumber) 1088 return 1089 } 1090 1091 inodeMap[destInodeNumber] = destInode 1092 inodeList = append(inodeList, destInode) 1093 1094 for _, element = range elements { 1095 snapShotIDType, _, _ = vS.headhunterVolumeHandle.SnapShotU64Decode(uint64(element.ElementInodeNumber)) 1096 if headhunter.SnapShotIDTypeLive != snapShotIDType { 1097 err = blunder.NewError(blunder.PermDeniedError, "Coalesce() from non-LiveView element.ElementInodeNumber (0x%016X) not allowed", element.ElementInodeNumber) 1098 return 1099 } 1100 1101 _, alreadyInInodeMap = inodeMap[element.ElementInodeNumber] 1102 if alreadyInInodeMap { 1103 err = blunder.NewError(blunder.InvalidArgError, "Coalesce() called with duplicate Element Inode 0x%016X", element.ElementInodeNumber) 1104 return 1105 } 1106 1107 elementInode, ok, err = vS.fetchInode(element.ElementInodeNumber) 1108 if nil != err { 1109 err = blunder.NewError(blunder.BadFileError, "Coalesce() couldn't fetch ElementInodeNumber 0x%016X: %v", element.ElementInodeNumber, err) 1110 return 1111 } 1112 1113 inodeMap[element.ElementInodeNumber] = elementInode 1114 inodeList = append(inodeList, elementInode) 1115 1116 if !ok { 1117 err = blunder.NewError(blunder.NotFoundError, "Coalesce() couldn't find ElementInodeNumber 0x%16X", element.ElementInodeNumber) 1118 return 1119 } 1120 if elementInode.InodeType != FileType { 1121 err = blunder.NewError(blunder.PermDeniedError, "Coalesce() called for ElementInodeNumber 0x%016X that is not a FileInode", element.ElementInodeNumber) 1122 return 1123 } 1124 if elementInode.LinkCount != 1 { 1125 err = blunder.NewError(blunder.TooManyLinksError, "Coalesce() called for ElementInodeNumber 0x%016X with LinkCount not == 1 (%v)", element.ElementInodeNumber, elementInode.LinkCount) 1126 return 1127 } 1128 1129 dirEntryInodeNumber, err = vS.lookupByDirInodeNumber(element.ContainingDirectoryInodeNumber, element.ElementName) 1130 if nil != err { 1131 err = blunder.NewError(blunder.InvalidArgError, "Coalesce() called for ElementName %s not found in ContainingDir 0x%016X: %v", element.ElementName, element.ContainingDirectoryInodeNumber, err) 1132 return 1133 } 1134 if dirEntryInodeNumber != element.ElementInodeNumber { 1135 err = blunder.NewError(blunder.InvalidArgError, "Coalesce() called for ElementName %s in ContainingDir 0x%016X had mismatched InodeNumber", element.ElementName, element.ContainingDirectoryInodeNumber) 1136 return 1137 } 1138 } 1139 1140 // Ensure all referenced FileInodes are pre-flushed 1141 1142 err = vS.flushInodes(inodeList) 1143 if nil != err { 1144 err = blunder.NewError(blunder.InvalidArgError, "Coalesce() unable to flush inodeList: %v", err) 1145 return 1146 } 1147 1148 // Now "append" each Element's extents to destInode (creating duplicate references to LogSegments for now) 1149 1150 destInodeExtentMap = destInode.payload.(sortedmap.BPlusTree) 1151 1152 destInode.dirty = true 1153 1154 destInodeOffsetBeforeElementAppend = fileLen(destInodeExtentMap) 1155 1156 for _, element = range elements { 1157 elementInode = inodeMap[element.ElementInodeNumber] 1158 destInode.NumWrites++ 1159 elementInodeExtentMap = elementInode.payload.(sortedmap.BPlusTree) 1160 elementInodeExtentMapLen, err = elementInodeExtentMap.Len() 1161 for elementInodeExtentMapIndex = 0; elementInodeExtentMapIndex < elementInodeExtentMapLen; elementInodeExtentMapIndex++ { 1162 _, elementInodeExtentAsValue, ok, err = elementInodeExtentMap.GetByIndex(elementInodeExtentMapIndex) 1163 if nil != err { 1164 localErr = vS.resetFileInodeInMemory(destInode) 1165 if nil != localErr { 1166 logger.Fatalf("Coalesce() doing resetFileInodeInMemory(destInode) failed: %v", localErr) 1167 } 1168 localErr = vS.flushInode(destInode) 1169 if nil != localErr { 1170 logger.Errorf("Coalesce() doing flushInode(destInode) failed: %v", localErr) 1171 } 1172 err = blunder.NewError(blunder.InvalidArgError, "Coalesce() unable to fetch fileExtentStruct from ExtentMap: %v", err) 1173 return 1174 } 1175 elementInodeExtent = elementInodeExtentAsValue.(*fileExtentStruct) 1176 elementInodeExtent.FileOffset += destInodeOffsetBeforeElementAppend 1177 _, err = destInodeExtentMap.Put(elementInodeExtent.FileOffset, elementInodeExtent) 1178 if nil != err { 1179 localErr = vS.resetFileInodeInMemory(destInode) 1180 if nil != localErr { 1181 logger.Fatalf("Coalesce() doing resetFileInodeInMemory(destInode) failed: %v", localErr) 1182 } 1183 localErr = vS.flushInode(destInode) 1184 if nil != localErr { 1185 logger.Errorf("Coalesce() doing flushInode(destInode) failed: %v", localErr) 1186 } 1187 err = blunder.NewError(blunder.InvalidArgError, "Coalesce() unable to append elementInodeExtent to destInodeExtentMap: %v", err) 1188 return 1189 } 1190 logSegmentReferencedBytes, ok = destInode.LogSegmentMap[elementInodeExtent.LogSegmentNumber] 1191 if ok { 1192 destInode.LogSegmentMap[elementInodeExtent.LogSegmentNumber] = elementInodeExtent.Length + logSegmentReferencedBytes 1193 } else { 1194 destInode.LogSegmentMap[elementInodeExtent.LogSegmentNumber] = elementInodeExtent.Length 1195 } 1196 } 1197 destInodeOffsetBeforeElementAppend += elementInode.Size 1198 err = setSizeInMemory(destInode, destInodeOffsetBeforeElementAppend) 1199 if nil != err { 1200 localErr = vS.resetFileInodeInMemory(destInode) 1201 if nil != localErr { 1202 logger.Fatalf("Coalesce() doing resetFileInodeInMemory(destInode) failed: %v", localErr) 1203 } 1204 localErr = vS.flushInode(destInode) 1205 if nil != localErr { 1206 logger.Errorf("Coalesce() doing flushInode(destInode) failed: %v", localErr) 1207 } 1208 err = blunder.NewError(blunder.InvalidArgError, "Coalesce() unable to setSize() destInodeNumber 0x%016X: %v", destInodeNumber, err) 1209 return 1210 } 1211 } 1212 1213 // Now, destInode is fully assembled... update its metadata & assemble remaining results 1214 coalesceTime = time.Now() 1215 destInode.CreationTime = coalesceTime 1216 destInode.AttrChangeTime = coalesceTime 1217 destInode.ModificationTime = coalesceTime 1218 1219 // attach new middleware headers (why are we making a copy?) 1220 inodeStreamBuf := make([]byte, len(metaData)) 1221 copy(inodeStreamBuf, metaData) 1222 destInode.StreamMap[metaDataName] = inodeStreamBuf 1223 1224 // collect the NumberOfWrites value while locked (important for Etag) 1225 numWrites = destInode.NumWrites 1226 fileSize = destInode.Size 1227 attrChangeTime = destInode.AttrChangeTime 1228 modificationTime = destInode.ModificationTime 1229 1230 // Now, destInode is fully assembled... need to remove all elements references to currently shared LogSegments 1231 1232 for _, element = range elements { 1233 localErr = vS.resetFileInodeInMemory(inodeMap[element.ElementInodeNumber]) 1234 if nil != err { 1235 logger.Fatalf("Coalesce() doing resetFileInodeInMemory(inodeMap[element.ElementInodeNumber]) failed: %v", localErr) 1236 } 1237 } 1238 1239 // Time to flush all affected FileInodes 1240 1241 err = vS.flushInodes(inodeList) 1242 if nil != err { 1243 err = fmt.Errorf("Coalesce() doing flushInodes(inodeList) failed: %v", err) 1244 return 1245 } 1246 1247 // Now we can Unlink and Destroy each element 1248 1249 for _, element = range elements { 1250 toDestroyInodeNumber, err = vS.Unlink(element.ContainingDirectoryInodeNumber, element.ElementName, false) 1251 if nil != err { 1252 err = fmt.Errorf("Coalesce() doing Unlink(element.ContainingDirectoryInodeNumber, element.ElementName, false) failed: %v", err) 1253 return 1254 } 1255 if toDestroyInodeNumber != element.ElementInodeNumber { 1256 logger.Fatalf("Coalesce() doing Unlink(element.ContainingDirectoryInodeNumber, element.ElementName, false) was expected to return toDestroyInodeNumber == element.ElementInodeNumber") 1257 } 1258 err = vS.Destroy(element.ElementInodeNumber) 1259 if nil != err { 1260 err = fmt.Errorf("Coalesce() doing Destroy(element.ElementInodeNumber) failed: %v", err) 1261 return 1262 } 1263 } 1264 1265 // All done 1266 1267 err = nil 1268 return 1269 } 1270 1271 func (vS *volumeStruct) DefragmentFile(fileInodeNumber InodeNumber, startingFileOffset uint64, chunkSize uint64) (nextFileOffset uint64, eofReached bool, err error) { 1272 var ( 1273 chunk []byte 1274 chunkSizeCapped uint64 1275 fileInode *inMemoryInodeStruct 1276 ) 1277 1278 err = enforceRWMode(false) 1279 if nil != err { 1280 return 1281 } 1282 1283 fileInode, err = vS.fetchInodeType(fileInodeNumber, FileType) 1284 if nil != err { 1285 return 1286 } 1287 1288 if startingFileOffset >= fileInode.Size { 1289 nextFileOffset = fileInode.Size 1290 eofReached = true 1291 err = nil 1292 return 1293 } 1294 1295 if (startingFileOffset + chunkSize) >= fileInode.Size { 1296 chunkSizeCapped = fileInode.Size - startingFileOffset 1297 eofReached = true 1298 } else { 1299 chunkSizeCapped = chunkSize 1300 eofReached = false 1301 } 1302 1303 nextFileOffset = startingFileOffset + chunkSizeCapped 1304 1305 chunk, err = vS.Read(fileInodeNumber, startingFileOffset, chunkSizeCapped, nil) 1306 if nil != err { 1307 return 1308 } 1309 1310 err = vS.Write(fileInodeNumber, startingFileOffset, chunk, nil) 1311 1312 return // err as returned by Write() is sufficient 1313 } 1314 1315 func (vS *volumeStruct) setLogSegmentContainer(logSegmentNumber uint64, containerName string) (err error) { 1316 containerNameAsByteSlice := utils.StringToByteSlice(containerName) 1317 err = vS.headhunterVolumeHandle.PutLogSegmentRec(logSegmentNumber, containerNameAsByteSlice) 1318 return 1319 } 1320 1321 func (vS *volumeStruct) getLogSegmentContainer(logSegmentNumber uint64) (containerName string, err error) { 1322 containerNameAsByteSlice, err := vS.headhunterVolumeHandle.GetLogSegmentRec(logSegmentNumber) 1323 if nil != err { 1324 return 1325 } 1326 containerName = utils.ByteSliceToString(containerNameAsByteSlice) 1327 return 1328 } 1329 1330 func (vS *volumeStruct) getObjectLocationFromLogSegmentNumber(logSegmentNumber uint64) (containerName string, objectName string, objectPath string, err error) { 1331 var ( 1332 nonce uint64 1333 ) 1334 1335 containerName, err = vS.getLogSegmentContainer(logSegmentNumber) 1336 if nil != err { 1337 return 1338 } 1339 1340 _, _, nonce = vS.headhunterVolumeHandle.SnapShotU64Decode(logSegmentNumber) 1341 1342 objectName = fmt.Sprintf("%016X", nonce) 1343 objectPath = fmt.Sprintf("/v1/%s/%s/%016X", vS.accountName, containerName, nonce) 1344 return 1345 }