/* */ #include "MultiDiskAdaptor.h" #include #include #include "DefaultDiskWriter.h" #include "message.h" #include "Util.h" #include "FileEntry.h" #include "MultiFileAllocationIterator.h" #include "DefaultDiskWriterFactory.h" #include "DlAbortEx.h" #include "File.h" #include "StringFormat.h" #include "Logger.h" #include "SimpleRandomizer.h" namespace aria2 { DiskWriterEntry::DiskWriterEntry(const SharedHandle& fileEntry): fileEntry(fileEntry), _open(false), _directIO(false), _needsFileAllocation(false) {} DiskWriterEntry::~DiskWriterEntry() {} std::string DiskWriterEntry::getFilePath(const std::string& topDir) const { return topDir+"/"+fileEntry->getPath(); } void DiskWriterEntry::initAndOpenFile(const std::string& topDir) { if(!diskWriter.isNull()) { diskWriter->initAndOpenFile(getFilePath(topDir), fileEntry->getLength()); if(_directIO) { diskWriter->enableDirectIO(); } _open = true; } } void DiskWriterEntry::openFile(const std::string& topDir) { if(!diskWriter.isNull()) { diskWriter->openFile(getFilePath(topDir), fileEntry->getLength()); if(_directIO) { diskWriter->enableDirectIO(); } _open = true; } } void DiskWriterEntry::openExistingFile(const std::string& topDir) { if(!diskWriter.isNull()) { diskWriter->openExistingFile(getFilePath(topDir), fileEntry->getLength()); if(_directIO) { diskWriter->enableDirectIO(); } _open = true; } } bool DiskWriterEntry::isOpen() const { return _open; } void DiskWriterEntry::closeFile() { if(_open) { diskWriter->closeFile(); _open = false; } } bool DiskWriterEntry::fileExists(const std::string& topDir) { return File(getFilePath(topDir)).exists(); } uint64_t DiskWriterEntry::size() const { assert(!diskWriter.isNull()); return diskWriter->size(); } SharedHandle DiskWriterEntry::getFileEntry() const { return fileEntry; } void DiskWriterEntry::setDiskWriter(const SharedHandle& diskWriter) { this->diskWriter = diskWriter; } SharedHandle DiskWriterEntry::getDiskWriter() const { return diskWriter; } bool DiskWriterEntry::operator<(const DiskWriterEntry& entry) const { return fileEntry < entry.fileEntry; } void DiskWriterEntry::enableDirectIO() { if(_open) { diskWriter->enableDirectIO(); } _directIO = true; } void DiskWriterEntry::disableDirectIO() { if(_open) { diskWriter->disableDirectIO(); } _directIO = false; } bool DiskWriterEntry::needsFileAllocation() const { return _needsFileAllocation; } void DiskWriterEntry::needsFileAllocation(bool f) { _needsFileAllocation = f; } MultiDiskAdaptor::MultiDiskAdaptor(): pieceLength(0), _maxOpenFiles(DEFAULT_MAX_OPEN_FILES), _directIOAllowed(false) {} MultiDiskAdaptor::~MultiDiskAdaptor() {} static SharedHandle createDiskWriterEntry (const SharedHandle& fileEntry, bool needsFileAllocation) { SharedHandle entry(new DiskWriterEntry(fileEntry)); entry->needsFileAllocation(needsFileAllocation); return entry; } void MultiDiskAdaptor::resetDiskWriterEntries() { diskWriterEntries.clear(); if(fileEntries.empty()) { return; } for(std::deque >::const_iterator i = fileEntries.begin(); i != fileEntries.end(); ++i) { diskWriterEntries.push_back (createDiskWriterEntry(*i, (*i)->isRequested())); } // TODO Currently, pieceLength == 0 is used for unit testing only. if(pieceLength > 0) { std::deque >::iterator done = diskWriterEntries.begin(); for(std::deque >::iterator itr = diskWriterEntries.begin(); itr != diskWriterEntries.end();) { if(!(*itr)->getFileEntry()->isRequested()) { ++itr; continue; } off_t pieceStartOffset = ((*itr)->getFileEntry()->getOffset()/pieceLength)*pieceLength; if(itr != diskWriterEntries.begin()) { for(std::deque >::iterator i = itr-1; true; --i) { const SharedHandle& fileEntry = (*i)->getFileEntry(); if(pieceStartOffset <= fileEntry->getOffset() || (uint64_t)pieceStartOffset < fileEntry->getOffset()+fileEntry->getLength()) { (*i)->needsFileAllocation(true); } else { break; } if(i == done) { break; } } } ++itr; for(; itr != diskWriterEntries.end(); ++itr) { if((*itr)->getFileEntry()->getOffset() < static_cast(pieceStartOffset+pieceLength)) { (*itr)->needsFileAllocation(true); } else { break; } } done = itr-1; } } DefaultDiskWriterFactory dwFactory; for(std::deque >::iterator i = diskWriterEntries.begin(); i != diskWriterEntries.end(); ++i) { if((*i)->needsFileAllocation() || (*i)->fileExists(getTopDirPath())) { logger->debug("Creating DiskWriter for filename=%s", (*i)->getFilePath(getTopDirPath()).c_str()); (*i)->setDiskWriter(dwFactory.newDiskWriter()); (*i)->getDiskWriter()->setDirectIOAllowed(_directIOAllowed); } } } std::string MultiDiskAdaptor::getTopDirPath() const { return storeDir+"/"+topDir; } void MultiDiskAdaptor::mkdir(const std::string& topDirPath) const { for(std::deque >::const_iterator i = diskWriterEntries.begin(); i != diskWriterEntries.end(); ++i) { (*i)->getFileEntry()->setupDir(topDirPath); } } void MultiDiskAdaptor::openIfNot (const SharedHandle& entry, void (DiskWriterEntry::*open)(const std::string&), const std::string& topDirPath) { if(!entry->isOpen()) { // logger->debug("DiskWriterEntry: Cache MISS. offset=%s", // Util::itos(entry->getFileEntry()->getOffset()).c_str()); size_t numOpened = _openedDiskWriterEntries.size(); (entry.get()->*open)(topDirPath); if(numOpened >= _maxOpenFiles) { // Cache is full. // Choose one DiskWriterEntry randomly and close it. size_t index = SimpleRandomizer::getInstance()->getRandomNumber(numOpened); std::deque >::iterator i = _openedDiskWriterEntries.begin(); std::advance(i, index); (*i)->closeFile(); (*i) = entry; } else { _openedDiskWriterEntries.push_back(entry); } } else { // logger->debug("DiskWriterEntry: Cache HIT. offset=%s", // Util::itos(entry->getFileEntry()->getOffset()).c_str()); } } void MultiDiskAdaptor::openFile() { _cachedTopDirPath = getTopDirPath(); resetDiskWriterEntries(); mkdir(_cachedTopDirPath); // Call DiskWriterEntry::openFile to make sure that zero-length files are // created. for(DiskWriterEntries::iterator itr = diskWriterEntries.begin(); itr != diskWriterEntries.end(); ++itr) { openIfNot(*itr, &DiskWriterEntry::openFile, _cachedTopDirPath); } } void MultiDiskAdaptor::initAndOpenFile() { _cachedTopDirPath = getTopDirPath(); resetDiskWriterEntries(); mkdir(_cachedTopDirPath); // Call DiskWriterEntry::initAndOpenFile to make files truncated. for(DiskWriterEntries::iterator itr = diskWriterEntries.begin(); itr != diskWriterEntries.end(); ++itr) { openIfNot(*itr, &DiskWriterEntry::initAndOpenFile, _cachedTopDirPath); } } void MultiDiskAdaptor::openExistingFile() { _cachedTopDirPath = getTopDirPath(); resetDiskWriterEntries(); // Not need to call openIfNot here. } void MultiDiskAdaptor::closeFile() { for(DiskWriterEntries::iterator itr = diskWriterEntries.begin(); itr != diskWriterEntries.end(); ++itr) { (*itr)->closeFile(); } } void MultiDiskAdaptor::onDownloadComplete() { closeFile(); openFile(); } static bool isInRange(const DiskWriterEntryHandle entry, off_t offset) { return entry->getFileEntry()->getOffset() <= offset && (uint64_t)offset < entry->getFileEntry()->getOffset()+entry->getFileEntry()->getLength(); } static size_t calculateLength(const DiskWriterEntryHandle entry, off_t fileOffset, size_t rem) { size_t length; if(entry->getFileEntry()->getLength() < (uint64_t)fileOffset+rem) { length = entry->getFileEntry()->getLength()-fileOffset; } else { length = rem; } return length; } class OffsetCompare { public: bool operator()(off_t offset, const SharedHandle& dwe) { return offset < dwe->getFileEntry()->getOffset(); } }; static DiskWriterEntries::const_iterator findFirstDiskWriterEntry(const DiskWriterEntries& diskWriterEntries, off_t offset) { DiskWriterEntries::const_iterator first = std::upper_bound(diskWriterEntries.begin(), diskWriterEntries.end(), offset, OffsetCompare()); --first; // In case when offset is out-of-range if(!isInRange(*first, offset)) { throw DlAbortEx (StringFormat(EX_FILE_OFFSET_OUT_OF_RANGE, Util::itos(offset, true).c_str()).str()); } return first; } static void throwOnDiskWriterNotOpened(const SharedHandle& e, off_t offset, const std::string& topDirPath) { throw DlAbortEx (StringFormat("DiskWriter for offset=%s, filename=%s is not opened.", Util::itos(offset).c_str(), e->getFilePath(topDirPath).c_str()).str()); } void MultiDiskAdaptor::writeData(const unsigned char* data, size_t len, off_t offset) { DiskWriterEntries::const_iterator first = findFirstDiskWriterEntry(diskWriterEntries, offset); size_t rem = len; off_t fileOffset = offset-(*first)->getFileEntry()->getOffset(); for(DiskWriterEntries::const_iterator i = first; i != diskWriterEntries.end(); ++i) { size_t writeLength = calculateLength(*i, fileOffset, rem); openIfNot(*i, &DiskWriterEntry::openFile, _cachedTopDirPath); if(!(*i)->isOpen()) { throwOnDiskWriterNotOpened(*i, offset+(len-rem), _cachedTopDirPath); } (*i)->getDiskWriter()->writeData(data+(len-rem), writeLength, fileOffset); rem -= writeLength; fileOffset = 0; if(rem == 0) { break; } } } ssize_t MultiDiskAdaptor::readData(unsigned char* data, size_t len, off_t offset) { DiskWriterEntries::const_iterator first = findFirstDiskWriterEntry(diskWriterEntries, offset); size_t rem = len; size_t totalReadLength = 0; off_t fileOffset = offset-(*first)->getFileEntry()->getOffset(); for(DiskWriterEntries::const_iterator i = first; i != diskWriterEntries.end(); ++i) { size_t readLength = calculateLength(*i, fileOffset, rem); openIfNot(*i, &DiskWriterEntry::openFile, _cachedTopDirPath); if(!(*i)->isOpen()) { throwOnDiskWriterNotOpened(*i, offset+(len-rem), _cachedTopDirPath); } totalReadLength += (*i)->getDiskWriter()->readData(data+(len-rem), readLength, fileOffset); rem -= readLength; fileOffset = 0; if(rem == 0) { break; } } return totalReadLength; } bool MultiDiskAdaptor::fileExists() { // Don't use _cachedTopDirPath because they are initialized after opening files. // This method could be called before opening files. std::string topDirPath = getTopDirPath(); for(std::deque >::iterator i = fileEntries.begin(); i != fileEntries.end(); ++i) { if(File(topDirPath+"/"+(*i)->getPath()).exists()) { return true; } } return false; } // TODO call DiskWriter::openFile() before calling this function. uint64_t MultiDiskAdaptor::size() { uint64_t size = 0; for(DiskWriterEntries::const_iterator itr = diskWriterEntries.begin(); itr != diskWriterEntries.end(); ++itr) { openIfNot(*itr, &DiskWriterEntry::openFile, _cachedTopDirPath); size += (*itr)->size(); } return size; } FileAllocationIteratorHandle MultiDiskAdaptor::fileAllocationIterator() { return SharedHandle(new MultiFileAllocationIterator(this)); } void MultiDiskAdaptor::enableDirectIO() { for(DiskWriterEntries::const_iterator itr = diskWriterEntries.begin(); itr != diskWriterEntries.end(); ++itr) { (*itr)->enableDirectIO(); } } void MultiDiskAdaptor::disableDirectIO() { for(DiskWriterEntries::const_iterator itr = diskWriterEntries.begin(); itr != diskWriterEntries.end(); ++itr) { (*itr)->disableDirectIO(); } } void MultiDiskAdaptor::cutTrailingGarbage() { for(std::deque >::const_iterator i = diskWriterEntries.begin(); i != diskWriterEntries.end(); ++i) { uint64_t length = (*i)->getFileEntry()->getLength(); if(File((*i)->getFilePath(_cachedTopDirPath)).size() > length) { // We need open file before calling DiskWriter::truncate(uint64_t) openIfNot(*i, &DiskWriterEntry::openFile, _cachedTopDirPath); (*i)->getDiskWriter()->truncate(length); } } } void MultiDiskAdaptor::setMaxOpenFiles(size_t maxOpenFiles) { _maxOpenFiles = maxOpenFiles; } size_t MultiDiskAdaptor::utime(const Time& actime, const Time& modtime) { size_t numOK = 0; for(std::deque >::const_iterator i = fileEntries.begin(); i != fileEntries.end(); ++i) { if((*i)->isRequested()) { File f(getTopDirPath()+"/"+(*i)->getPath()); if(f.isFile() && f.utime(actime, modtime)) { ++numOK; } } } return numOK; } const std::deque >& MultiDiskAdaptor::getDiskWriterEntries() const { return diskWriterEntries; } } // namespace aria2