/* */ #include "MultiDiskAdaptor.h" #include #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 "fmt.h" #include "Logger.h" #include "LogFactory.h" #include "SimpleRandomizer.h" namespace aria2 { DiskWriterEntry::DiskWriterEntry(const SharedHandle& fileEntry) : fileEntry_(fileEntry), open_(false), needsFileAllocation_(false) {} const std::string& DiskWriterEntry::getFilePath() const { return fileEntry_->getPath(); } void DiskWriterEntry::initAndOpenFile() { if(diskWriter_) { diskWriter_->initAndOpenFile(fileEntry_->getLength()); open_ = true; } } void DiskWriterEntry::openFile() { if(diskWriter_) { diskWriter_->openFile(fileEntry_->getLength()); open_ = true; } } void DiskWriterEntry::openExistingFile() { if(diskWriter_) { diskWriter_->openExistingFile(fileEntry_->getLength()); open_ = true; } } void DiskWriterEntry::closeFile() { if(open_) { diskWriter_->closeFile(); open_ = false; } } bool DiskWriterEntry::fileExists() { return fileEntry_->exists(); } uint64_t DiskWriterEntry::size() const { return File(getFilePath()).size(); } void DiskWriterEntry::setDiskWriter(const SharedHandle& diskWriter) { diskWriter_ = diskWriter; } bool DiskWriterEntry::operator<(const DiskWriterEntry& entry) const { return *fileEntry_ < *entry.fileEntry_; } MultiDiskAdaptor::MultiDiskAdaptor() : pieceLength_(0), maxOpenFiles_(DEFAULT_MAX_OPEN_FILES), readOnly_(false) {} MultiDiskAdaptor::~MultiDiskAdaptor() {} namespace { SharedHandle createDiskWriterEntry (const SharedHandle& fileEntry, bool needsFileAllocation) { SharedHandle entry(new DiskWriterEntry(fileEntry)); entry->needsFileAllocation(needsFileAllocation); return entry; } } // namespace void MultiDiskAdaptor::resetDiskWriterEntries() { diskWriterEntries_.clear(); if(getFileEntries().empty()) { return; } for(std::vector >::const_iterator i = getFileEntries().begin(), eoi = getFileEntries().end(); i != eoi; ++i) { diskWriterEntries_.push_back (createDiskWriterEntry(*i, (*i)->isRequested())); } std::map dwreq; // TODO Currently, pieceLength_ == 0 is used for unit testing only. if(pieceLength_ > 0) { std::vector >::const_iterator done = diskWriterEntries_.begin(); for(std::vector >::const_iterator itr = diskWriterEntries_.begin(), eoi = diskWriterEntries_.end(); itr != eoi;) { const SharedHandle& fileEntry = (*itr)->getFileEntry(); if(!fileEntry->isRequested()) { ++itr; continue; } off_t pieceStartOffset = (fileEntry->getOffset()/pieceLength_)*pieceLength_; if(itr != diskWriterEntries_.begin()) { for(std::vector >::const_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; } } } if(fileEntry->getLength() > 0) { off_t lastPieceStartOffset = (fileEntry->getOffset()+fileEntry->getLength()-1)/ pieceLength_*pieceLength_; A2_LOG_DEBUG(fmt("Checking adjacent backward file to %s" " whose lastPieceStartOffset+pieceLength_=%lld", fileEntry->getPath().c_str(), static_cast (lastPieceStartOffset+pieceLength_))); ++itr; // adjacent backward files are not needed to be allocated. They // just requre DiskWriter for(; itr != eoi && (!(*itr)->getFileEntry()->isRequested() || (*itr)->getFileEntry()->getLength() == 0); ++itr) { A2_LOG_DEBUG (fmt("file=%s, offset=%lld", (*itr)->getFileEntry()->getPath().c_str(), static_cast ((*itr)->getFileEntry()->getOffset()))); if((*itr)->getFileEntry()->getOffset() < static_cast(lastPieceStartOffset+pieceLength_)) { A2_LOG_DEBUG (fmt("%s needs diskwriter", (*itr)->getFileEntry()->getPath().c_str())); dwreq[(*itr)->getFileEntry()->getPath()] = true; } else { break; } } done = itr-1; } else { done = itr; ++itr; } } } DefaultDiskWriterFactory dwFactory; for(std::vector >::const_iterator i = diskWriterEntries_.begin(), eoi = diskWriterEntries_.end(); i != eoi; ++i) { if((*i)->needsFileAllocation() || dwreq.find((*i)->getFileEntry()->getPath()) != dwreq.end() || (*i)->fileExists()) { A2_LOG_DEBUG(fmt("Creating DiskWriter for filename=%s", (*i)->getFilePath().c_str())); (*i)->setDiskWriter(dwFactory.newDiskWriter((*i)->getFilePath())); if(readOnly_) { (*i)->getDiskWriter()->enableReadOnly(); } } } } void MultiDiskAdaptor::openIfNot (const SharedHandle& entry, void (DiskWriterEntry::*open)()) { if(!entry->isOpen()) { // A2_LOG_DEBUG(fmt("DiskWriterEntry: Cache MISS. offset=%s", // util::itos(entry->getFileEntry()->getOffset()).c_str())); size_t numOpened = openedDiskWriterEntries_.size(); (entry.get()->*open)(); if(numOpened >= maxOpenFiles_) { // Cache is full. // Choose one DiskWriterEntry randomly and close it. size_t index = SimpleRandomizer::getInstance()->getRandomNumber(numOpened); std::vector >::iterator i = openedDiskWriterEntries_.begin(); std::advance(i, index); (*i)->closeFile(); (*i) = entry; } else { openedDiskWriterEntries_.push_back(entry); } } else { // A2_LOG_DEBUG(fmt("DiskWriterEntry: Cache HIT. offset=%s", // util::itos(entry->getFileEntry()->getOffset()).c_str())); } } void MultiDiskAdaptor::openFile() { resetDiskWriterEntries(); // util::mkdir() is called in AbstractDiskWriter::createFile(), so // we don't need to call it here. // Call DiskWriterEntry::openFile to make sure that zero-length files are // created. for(DiskWriterEntries::const_iterator itr = diskWriterEntries_.begin(), eoi = diskWriterEntries_.end(); itr != eoi; ++itr) { openIfNot(*itr, &DiskWriterEntry::openFile); } } void MultiDiskAdaptor::initAndOpenFile() { resetDiskWriterEntries(); // util::mkdir() is called in AbstractDiskWriter::createFile(), so // we don't need to call it here. // Call DiskWriterEntry::initAndOpenFile to make files truncated. for(DiskWriterEntries::const_iterator itr = diskWriterEntries_.begin(), eoi = diskWriterEntries_.end(); itr != eoi; ++itr) { openIfNot(*itr, &DiskWriterEntry::initAndOpenFile); } } void MultiDiskAdaptor::openExistingFile() { resetDiskWriterEntries(); // Not need to call openIfNot here. } void MultiDiskAdaptor::closeFile() { std::for_each(diskWriterEntries_.begin(), diskWriterEntries_.end(), mem_fun_sh(&DiskWriterEntry::closeFile)); } namespace { bool isInRange(const DiskWriterEntryHandle entry, off_t offset) { return entry->getFileEntry()->getOffset() <= offset && (uint64_t)offset < entry->getFileEntry()->getOffset()+entry->getFileEntry()->getLength(); } } // namespace namespace { 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; } } // namespace namespace { class OffsetCompare { public: bool operator()(off_t offset, const SharedHandle& dwe) { return offset < dwe->getFileEntry()->getOffset(); } }; } // namespace namespace { 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 DL_ABORT_EX (fmt(EX_FILE_OFFSET_OUT_OF_RANGE, util::itos(offset, true).c_str())); } return first; } } // namespace namespace { void throwOnDiskWriterNotOpened(const SharedHandle& e, off_t offset) { throw DL_ABORT_EX (fmt("DiskWriter for offset=%s, filename=%s is not opened.", util::itos(offset).c_str(), e->getFilePath().c_str())); } } // namespace 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, eoi = diskWriterEntries_.end(); i != eoi; ++i) { size_t writeLength = calculateLength(*i, fileOffset, rem); openIfNot(*i, &DiskWriterEntry::openFile); if(!(*i)->isOpen()) { throwOnDiskWriterNotOpened(*i, offset+(len-rem)); } (*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, eoi = diskWriterEntries_.end(); i != eoi; ++i) { size_t readLength = calculateLength(*i, fileOffset, rem); openIfNot(*i, &DiskWriterEntry::openFile); if(!(*i)->isOpen()) { throwOnDiskWriterNotOpened(*i, offset+(len-rem)); } totalReadLength += (*i)->getDiskWriter()->readData(data+(len-rem), readLength, fileOffset); rem -= readLength; fileOffset = 0; if(rem == 0) { break; } } return totalReadLength; } bool MultiDiskAdaptor::fileExists() { return std::find_if(getFileEntries().begin(), getFileEntries().end(), mem_fun_sh(&FileEntry::exists)) != getFileEntries().end(); } uint64_t MultiDiskAdaptor::size() { uint64_t size = 0; for(std::vector >::const_iterator i = getFileEntries().begin(), eoi = getFileEntries().end(); i != eoi; ++i) { size += File((*i)->getPath()).size(); } return size; } SharedHandle MultiDiskAdaptor::fileAllocationIterator() { return SharedHandle (new MultiFileAllocationIterator(this)); } void MultiDiskAdaptor::enableReadOnly() { readOnly_ = true; } void MultiDiskAdaptor::disableReadOnly() { readOnly_ = false; } void MultiDiskAdaptor::cutTrailingGarbage() { for(std::vector >::const_iterator i = diskWriterEntries_.begin(), eoi = diskWriterEntries_.end(); i != eoi; ++i) { uint64_t length = (*i)->getFileEntry()->getLength(); if(File((*i)->getFilePath()).size() > length) { // We need open file before calling DiskWriter::truncate(uint64_t) openIfNot(*i, &DiskWriterEntry::openFile); (*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::vector >::const_iterator i = getFileEntries().begin(), eoi = getFileEntries().end(); i != eoi; ++i) { if((*i)->isRequested()) { File f((*i)->getPath()); if(f.isFile() && f.utime(actime, modtime)) { ++numOK; } } } return numOK; } } // namespace aria2