/* */ #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" #include "WrDiskCacheEntry.h" namespace aria2 { DiskWriterEntry::DiskWriterEntry(const std::shared_ptr& fileEntry) : fileEntry_{fileEntry}, open_{false}, needsFileAllocation_{false}, needsDiskWriter_{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(); } int64_t DiskWriterEntry::size() const { return File(getFilePath()).size(); } void DiskWriterEntry::setDiskWriter(std::unique_ptr diskWriter) { diskWriter_ = std::move(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 { std::unique_ptr createDiskWriterEntry (const std::shared_ptr& fileEntry) { auto entry = make_unique(fileEntry); entry->needsFileAllocation(fileEntry->isRequested()); return entry; } } // namespace void MultiDiskAdaptor::resetDiskWriterEntries() { diskWriterEntries_.clear(); if(getFileEntries().empty()) { return; } for(auto& fileEntry: getFileEntries()) { diskWriterEntries_.push_back(createDiskWriterEntry(fileEntry)); } // TODO Currently, pieceLength_ == 0 is used for unit testing only. if(pieceLength_ > 0) { // Check shared piece forward int64_t lastOffset = 0; for(auto& dwent : diskWriterEntries_) { auto& fileEntry = dwent->getFileEntry(); if(fileEntry->isRequested()) { // zero length file does not affect lastOffset. if(fileEntry->getLength() > 0) { lastOffset = (fileEntry->getLastOffset()-1)/pieceLength_ * pieceLength_ + pieceLength_; } } else if(fileEntry->getOffset() < lastOffset) { // The files which shares last piece are not needed to be // allocated. They just requre DiskWriter A2_LOG_DEBUG(fmt("%s needs DiskWriter", fileEntry->getPath().c_str())); dwent->needsDiskWriter(true); } } // Check shared piece backword lastOffset = std::numeric_limits::max(); for(auto i = diskWriterEntries_.rbegin(), eoi = diskWriterEntries_.rend(); i != eoi; ++i) { auto& fileEntry = (*i)->getFileEntry(); if(fileEntry->isRequested()) { lastOffset = fileEntry->getOffset()/pieceLength_*pieceLength_; } else if(lastOffset <= fileEntry->getOffset() || // length == 0 case lastOffset < fileEntry->getLastOffset()) { // We needs last part of the file, so file allocation is // required, especially for file system which does not support // sparse files. A2_LOG_DEBUG(fmt("%s needs file allocation", fileEntry->getPath().c_str())); (*i)->needsFileAllocation(true); } } } DefaultDiskWriterFactory dwFactory; for(auto& dwent : diskWriterEntries_) { if(dwent->needsFileAllocation() || dwent->needsDiskWriter() || dwent->fileExists()) { A2_LOG_DEBUG(fmt("Creating DiskWriter for filename=%s", dwent->getFilePath().c_str())); dwent->setDiskWriter(dwFactory.newDiskWriter(dwent->getFilePath())); if(readOnly_) { dwent->getDiskWriter()->enableReadOnly(); } // TODO mmap is not enabled at this moment. Call enableMmap() // after this function call. } } } void MultiDiskAdaptor::openIfNot (DiskWriterEntry* entry, void (DiskWriterEntry::*open)()) { if(!entry->isOpen()) { // A2_LOG_DEBUG(fmt("DiskWriterEntry: Cache MISS. offset=%s", // util::itos(entry->getFileEntry()->getOffset()).c_str())); int numOpened = openedDiskWriterEntries_.size(); (entry->*open)(); if(numOpened >= maxOpenFiles_) { // Cache is full. // Choose one DiskWriterEntry randomly and close it. size_t index = SimpleRandomizer::getInstance()->getRandomNumber(numOpened); auto i = std::begin(openedDiskWriterEntries_); 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(auto& dwent : diskWriterEntries_) { openIfNot(dwent.get(), &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(auto& dwent : diskWriterEntries_) { openIfNot(dwent.get(), &DiskWriterEntry::initAndOpenFile); } } void MultiDiskAdaptor::openExistingFile() { resetDiskWriterEntries(); // Not need to call openIfNot here. } void MultiDiskAdaptor::closeFile() { openedDiskWriterEntries_.clear(); for(auto& dwent : diskWriterEntries_) { dwent->closeFile(); } } namespace { bool isInRange(DiskWriterEntry* entry, int64_t offset) { return entry->getFileEntry()->getOffset() <= offset && offset < entry->getFileEntry()->getLastOffset(); } } // namespace namespace { ssize_t calculateLength(DiskWriterEntry* entry, int64_t fileOffset, ssize_t rem) { if(entry->getFileEntry()->getLength() < fileOffset+rem) { return entry->getFileEntry()->getLength()-fileOffset; } else { return rem; } } } // namespace namespace { class OffsetCompare { public: bool operator()(int64_t offset, const std::unique_ptr& dwe) { return offset < dwe->getFileEntry()->getOffset(); } }; } // namespace namespace { DiskWriterEntries::const_iterator findFirstDiskWriterEntry (const DiskWriterEntries& diskWriterEntries, int64_t offset) { auto first = std::upper_bound(std::begin(diskWriterEntries), std::end(diskWriterEntries), offset, OffsetCompare()); --first; // In case when offset is out-of-range if(!isInRange((*first).get(), offset)) { throw DL_ABORT_EX (fmt(EX_FILE_OFFSET_OUT_OF_RANGE, static_cast(offset))); } return first; } } // namespace namespace { void throwOnDiskWriterNotOpened(DiskWriterEntry* e, int64_t offset) { throw DL_ABORT_EX (fmt("DiskWriter for offset=%" PRId64 ", filename=%s is not opened.", static_cast(offset), e->getFilePath().c_str())); } } // namespace void MultiDiskAdaptor::writeData(const unsigned char* data, size_t len, int64_t offset) { auto first = findFirstDiskWriterEntry(diskWriterEntries_, offset); ssize_t rem = len; int64_t fileOffset = offset-(*first)->getFileEntry()->getOffset(); for(auto i = first, eoi = diskWriterEntries_.cend(); i != eoi; ++i) { ssize_t writeLength = calculateLength((*i).get(), fileOffset, rem); openIfNot((*i).get(), &DiskWriterEntry::openFile); if(!(*i)->isOpen()) { throwOnDiskWriterNotOpened((*i).get(), 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, int64_t offset) { auto first = findFirstDiskWriterEntry(diskWriterEntries_, offset); ssize_t rem = len; ssize_t totalReadLength = 0; int64_t fileOffset = offset-(*first)->getFileEntry()->getOffset(); for(auto i = first, eoi = diskWriterEntries_.cend(); i != eoi; ++i) { ssize_t readLength = calculateLength((*i).get(), fileOffset, rem); openIfNot((*i).get(), &DiskWriterEntry::openFile); if(!(*i)->isOpen()) { throwOnDiskWriterNotOpened((*i).get(), offset+(len-rem)); } totalReadLength += (*i)->getDiskWriter()->readData(data+(len-rem), readLength, fileOffset); rem -= readLength; fileOffset = 0; if(rem == 0) { break; } } return totalReadLength; } void MultiDiskAdaptor::writeCache(const WrDiskCacheEntry* entry) { // Write cached data in 4KiB aligned offset. This reduces disk // activity especially on Windows 7 NTFS. unsigned char buf[16*1024]; size_t buflen = 0; size_t buffoffset = 0; auto& dataSet = entry->getDataSet(); if(dataSet.empty()) { return; } auto dent = findFirstDiskWriterEntry(diskWriterEntries_, (*dataSet.begin())->goff), eod = diskWriterEntries_.cend(); auto i = std::begin(dataSet), eoi = std::end(dataSet); size_t celloff = 0; for(; dent != eod; ++dent) { int64_t lstart = 0, lp = 0; auto& fent = (*dent)->getFileEntry(); for(; i != eoi;) { if(std::max(fent->getOffset(), static_cast((*i)->goff + celloff)) < std::min(fent->getLastOffset(), static_cast((*i)->goff + (*i)->len))) { openIfNot((*dent).get(), &DiskWriterEntry::openFile); if(!(*dent)->isOpen()) { throwOnDiskWriterNotOpened((*dent).get(), (*i)->goff + celloff); } } else { A2_LOG_DEBUG(fmt("%s Cache flush loff=%" PRId64 ", len=%lu", fent->getPath().c_str(), lstart, static_cast(buflen-buffoffset))); (*dent)->getDiskWriter()-> writeData(buf + buffoffset, buflen - buffoffset, lstart); buflen = buffoffset = 0; break; } int64_t loff = fent->gtoloff((*i)->goff + celloff); if(static_cast(lstart + buflen) < loff) { A2_LOG_DEBUG(fmt("%s Cache flush loff=%" PRId64 ", len=%lu", fent->getPath().c_str(), lstart, static_cast(buflen-buffoffset))); (*dent)->getDiskWriter()-> writeData(buf + buffoffset, buflen - buffoffset, lstart); lstart = lp = loff; buflen = buffoffset = 0; } // If the position of the cache data is not aligned, offset // buffer so that next write can be aligned. if(buflen == 0) { buflen = buffoffset = loff & 0xfff; } assert((*i)->len > celloff); for(;;) { size_t wlen = std::min(static_cast((*i)->len - celloff), fent->getLength() - lp); wlen = std::min(wlen, sizeof(buf) - buflen); memcpy(buf + buflen, (*i)->data + (*i)->offset + celloff, wlen); buflen += wlen; celloff += wlen; lp += wlen; if(lp == fent->getLength() || buflen == sizeof(buf)) { A2_LOG_DEBUG(fmt("%s Cache flush loff=%" PRId64 ", len=%lu", fent->getPath().c_str(), lstart, static_cast(buflen-buffoffset))); (*dent)->getDiskWriter()-> writeData(buf + buffoffset, buflen - buffoffset, lstart); lstart += buflen - buffoffset; lp = lstart; buflen = buffoffset = 0; } if(lp == fent->getLength() || celloff == (*i)->len) { break; } } if(celloff == (*i)->len) { ++i; celloff = 0; } } if(i == eoi) { A2_LOG_DEBUG(fmt("%s Cache flush loff=%" PRId64 ", len=%lu", fent->getPath().c_str(), lstart, static_cast(buflen - buffoffset))); (*dent)->getDiskWriter()-> writeData(buf + buffoffset, buflen - buffoffset, lstart); break; } } assert(i == eoi); } bool MultiDiskAdaptor::fileExists() { return std::find_if(std::begin(getFileEntries()), std::end(getFileEntries()), std::mem_fn(&FileEntry::exists)) != std::end(getFileEntries()); } int64_t MultiDiskAdaptor::size() { int64_t size = 0; for(auto& fe : getFileEntries()) { size += File(fe->getPath()).size(); } return size; } std::unique_ptr MultiDiskAdaptor::fileAllocationIterator() { return make_unique(this); } void MultiDiskAdaptor::enableReadOnly() { readOnly_ = true; } void MultiDiskAdaptor::disableReadOnly() { readOnly_ = false; } void MultiDiskAdaptor::enableMmap() { for(auto& dwent : diskWriterEntries_) { auto& dw = dwent->getDiskWriter(); if(dw) { dw->enableMmap(); } } } void MultiDiskAdaptor::cutTrailingGarbage() { for(auto& dwent : diskWriterEntries_) { int64_t length = dwent->getFileEntry()->getLength(); if(File(dwent->getFilePath()).size() > length) { // We need open file before calling DiskWriter::truncate(int64_t) openIfNot(dwent.get(), &DiskWriterEntry::openFile); dwent->getDiskWriter()->truncate(length); } } } void MultiDiskAdaptor::setMaxOpenFiles(int maxOpenFiles) { maxOpenFiles_ = maxOpenFiles; } size_t MultiDiskAdaptor::utime(const Time& actime, const Time& modtime) { size_t numOK = 0; for(auto& fe : getFileEntries()) { if(fe->isRequested()) { File f{fe->getPath()}; if(f.isFile() && f.utime(actime, modtime)) { ++numOK; } } } return numOK; } } // namespace aria2