in Sources/TSFCASFileTree/Internal/FileSegmenter.swift [181:293]
func fetchSegment(segmentNumber: Int) throws -> (LLBFastData, isEOF: Bool)? {
let (fileOffset, overflow) = segmentSize.multipliedReportingOverflow(by: segmentNumber)
guard !overflow else {
// FIXME: Need to support ERANGE in FileSystemError.
throw FileSystemError(.unknownOSError)
}
let fileSize = Int(statInfo.st_size)
let currentSegmentSize = min(fileSize - fileOffset, segmentSize)
if currentSegmentSize <= 0 {
if fileOffset == 0, statInfo.st_size == 0 {
// It is safe to not to check whether the file is still zero.
// This way even if the file changes we're on the safe side
// of the race, sending the idea that the file had changed
// either completely before or completely after we've used it.
return (LLBFastData([]), isEOF: true)
} else {
return nil
}
}
let atEOF = fileOffset + currentSegmentSize >= fileSize
if let basePointer = self.mappedAt {
let ptr = UnsafeRawBufferPointer(start: basePointer.advanced(by: fileOffset), count: currentSegmentSize)
return (LLBFastData(ptr, deallocator: { _ in
withExtendedLifetime(self) { }
}), isEOF: atEOF)
}
let fd: CInt
switch reuseFD.exchange(with: -1) {
case let reused where reused >= 0:
fd = reused
default:
let newFD: CInt
do {
newFD = try LLBFutureFileSystem.openImpl(path.pathString, flags: O_RDONLY | O_NOFOLLOW)
} catch {
guard allowInconsistency else {
// Ignore the details of the actual error: in most cases
// (file not found, or something else) it is as good as
// "something is different with this file".
// After all, we have succeeded opening it before.
throw Error.resourceChanged(reason: "Can't reopen: \(error)")
}
// If we can't open something that we used to be able to open,
// just consider it deleted/truncated and upload empty
// (if less than a segment size) or truncated.
return (LLBFastData([]), isEOF: true)
}
// The main check to perform here is whether it is a regular file
// (S_IFREG). The rest is just an early bail out: we do check the
// stat information again after the file chunk is read.
let inconsistency = self.checkConsistency(ofSameFileOpenedAs: newFD)
switch inconsistency {
case .Same,
.Modified where allowInconsistency == true:
// Do not log here, avoid duplicate logging.
break
case .Deleted, .Replaced:
close(newFD)
throw FileSystemError(.noEntry, path)
case .Modified:
close(newFD)
throw Error.resourceChanged(reason: String(describing: inconsistency))
}
fd = newFD
}
defer { close(fd) }
let data = try LLBFutureFileSystem.syncReadComplete(fd: fd, readSize: currentSegmentSize, fileOffset: fileOffset)
if atEOF, segmentNumber == 0 {
// If we've just slurped the whole relatively short (1 segm) file,
// we don't have to check whether the file has been modified or
// not at the end of the read:
// - If it is a first read, we'll have a chance to check
// this on a subsequent read. And upload something else instead
// if it changed.
// - If it is the last read (where we use data for the actual
// upload), then this situation is just freezes the inconsistent
// state of the [relatively small] file.
// Actually, ignore all that, let's get safety over speed.
// Also, testing didn't find material changes.
}
// Even if we read the file correctly, make sure it didn't change
// after we've read it. That would be a race.
let inconsistency: ConsistencyStatus
if data.count != currentSegmentSize {
inconsistency = .Modified(reason: "File size shrunk")
} else {
inconsistency = self.checkConsistency(ofSameFileOpenedAs: fd)
}
switch inconsistency {
case .Same:
break
case .Modified where allowInconsistency == true:
// Note inconsistency
break
case .Deleted, .Replaced:
// The file was deleted at _some_ point in the middle of processing.
throw FileSystemError(.noEntry, path)
case .Modified(let reason):
throw Error.resourceChanged(reason: reason)
}
return (LLBFastData(data), isEOF: atEOF)
}