Upstream tarball 9585
[amule.git] / src / ThreadTasks.cpp
blobcaff9bf10095f86a64243668f173e452299b16f8
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2006-2008 Mikkel Schubert ( xaignar@amule.org / http:://www.amule.org )
5 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
6 // Copyright (c) 2002-2008 Merkur ( devs@emule-project.net / http://www.emule-project.net )
7 //
8 // Any parts of this program derived from the xMule, lMule or eMule project,
9 // or contributed by third-party developers are copyrighted by their
10 // respective authors.
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 2 of the License, or
15 // (at your option) any later version.
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
28 #include <wx/app.h> // Needed for wxTheApp
30 #include "ThreadTasks.h" // Interface declarations
31 #include "PartFile.h" // Needed for CPartFile
32 #include "Logger.h" // Needed for Add(Debug)LogLineM
33 #include <common/Format.h> // Needed for CFormat
34 #include "amule.h" // Needed for theApp
35 #include "KnownFileList.h" // Needed for theApp->knownfiles
36 #include "Preferences.h" // Needed for thePrefs
37 #include "ScopedPtr.h" // Needed for CScopedPtr and CScopedArray
38 #include "PlatformSpecific.h" // Needed for CanFSHandleSpecialChars
41 //! This hash represents the value for an empty MD4 hashing
42 const byte g_emptyMD4Hash[16] = {
43 0x31, 0xD6, 0xCF, 0xE0, 0xD1, 0x6A, 0xE9, 0x31,
44 0xB7, 0x3C, 0x59, 0xD7, 0xE0, 0xC0, 0x89, 0xC0 };
47 ////////////////////////////////////////////////////////////
48 // CHashingTask
50 CHashingTask::CHashingTask(const CPath& path, const CPath& filename, const CPartFile* part)
51 // GetPrintable is used to improve the readability of the log.
52 : CThreadTask(wxT("Hashing"), path.JoinPaths(filename).GetPrintable(), (part ? ETP_High : ETP_Normal)),
53 m_path(path),
54 m_filename(filename),
55 m_toHash((EHashes)(EH_MD4 | EH_AICH)),
56 m_owner(part)
58 // We can only create the AICH hashset if the file is a knownfile or
59 // if the partfile is complete, since the MD4 hashset is checked first,
60 // so that the AICH hashset only gets assigned if the MD4 hashset
61 // matches what we expected. Due to the rareity of post-completion
62 // corruptions, this gives us a nice speedup in most cases.
63 if (part && !part->GetGapList().empty()) {
64 m_toHash = EH_MD4;
69 CHashingTask::CHashingTask(const CKnownFile* toAICHHash)
70 // GetPrintable is used to improve the readability of the log.
71 : CThreadTask(wxT("AICH Hashing"), toAICHHash->GetFilePath().JoinPaths(toAICHHash->GetFileName()).GetPrintable(), ETP_Low),
72 m_path(toAICHHash->GetFilePath()),
73 m_filename(toAICHHash->GetFileName()),
74 m_toHash(EH_AICH),
75 m_owner(toAICHHash)
80 void CHashingTask::Entry()
82 CFileAutoClose file;
84 CPath fullPath = m_path.JoinPaths(m_filename);
85 if (!file.Open(fullPath, CFile::read)) {
86 AddDebugLogLineM(true, logHasher,
87 CFormat(wxT("Warning, failed to open file, skipping: %s")) % fullPath);
88 return;
91 uint64 fileLength = 0;
92 try {
93 fileLength = file.GetLength();
94 } catch (const CIOFailureException&) {
95 AddDebugLogLineM(true, logHasher,
96 CFormat(wxT("Warning, failed to retrieve file-length, skipping: %s")) % fullPath);
97 return;
100 if (fileLength > MAX_FILE_SIZE) {
101 AddDebugLogLineM(true, logHasher,
102 CFormat(wxT("Warning, file is larger than supported size, skipping: %s")) % fullPath);
103 return;
104 } else if (fileLength == 0) {
105 if (m_owner) {
106 // It makes no sense to try to hash empty partfiles ...
107 wxFAIL;
108 } else {
109 // Zero-size partfiles should be hashed, but not zero-sized shared-files.
110 AddDebugLogLineM( true, logHasher,
111 CFormat(wxT("Warning, 0-size file, skipping: %s")) % fullPath);
114 return;
117 // For thread-safety, results are passed via a temporary file object.
118 CScopedPtr<CKnownFile> knownfile(new CKnownFile());
119 knownfile->m_filePath = m_path;
120 knownfile->SetFileName(m_filename);
121 knownfile->SetFileSize(fileLength);
122 knownfile->m_lastDateChanged = CPath::GetModificationTime(fullPath);
123 knownfile->m_AvailPartFrequency.insert(
124 knownfile->m_AvailPartFrequency.begin(),
125 knownfile->GetPartCount(), 0);
127 if ((m_toHash & EH_MD4) && (m_toHash & EH_AICH)) {
128 knownfile->GetAICHHashset()->FreeHashSet();
129 AddDebugLogLineM( false, logHasher, CFormat(
130 _("Starting to create MD4 and AICH hash for file: %s")) %
131 m_filename );
132 } else if ((m_toHash & EH_MD4)) {
133 AddDebugLogLineM( false, logHasher, CFormat(
134 _("Starting to create MD4 hash for file: %s")) % m_filename );
135 } else if ((m_toHash & EH_AICH)) {
136 knownfile->GetAICHHashset()->FreeHashSet();
137 AddDebugLogLineM( false, logHasher, CFormat(
138 _("Starting to create AICH hash for file: %s")) % m_filename );
139 } else {
140 wxCHECK_RET(0, (CFormat(wxT("No hashes requested for file, skipping: %s"))
141 % m_filename).GetString());
145 // This loops creates the part-hashes, loop-de-loop.
146 try {
147 while (!file.Eof() && !TestDestroy()) {
148 if (CreateNextPartHash(file, knownfile.get(), m_toHash) == false) {
149 AddDebugLogLineM(true, logHasher,
150 CFormat(wxT("Error while hashing file, skipping: %s"))
151 % m_filename);
153 return;
156 } catch (const CSafeIOException& e) {
157 AddDebugLogLineM(true, logHasher, wxT("IO exception while hashing file: ") + e.what());
158 return;
161 if ((m_toHash & EH_MD4) && !TestDestroy()) {
162 // If the file is < PARTSIZE, then the filehash is that one hash,
163 // otherwise, the filehash is the hash of the parthashes
164 if ( knownfile->m_hashlist.size() == 1 ) {
165 knownfile->m_abyFileHash = knownfile->m_hashlist[0];
166 knownfile->m_hashlist.clear();
167 } else if ( knownfile->m_hashlist.size() ) {
168 CMD4Hash hash;
169 knownfile->CreateHashFromHashlist(knownfile->m_hashlist, &hash);
170 knownfile->m_abyFileHash = hash;
171 } else {
172 // This should not happen!
173 wxFAIL;
177 // Did we create a AICH hashset?
178 if ((m_toHash & EH_AICH) && !TestDestroy()) {
179 CAICHHashSet* AICHHashSet = knownfile->GetAICHHashset();
181 AICHHashSet->ReCalculateHash(false);
182 if (AICHHashSet->VerifyHashTree(true) ) {
183 AICHHashSet->SetStatus(AICH_HASHSETCOMPLETE);
184 if (!AICHHashSet->SaveHashSet()) {
185 AddDebugLogLineM( true, logHasher,
186 CFormat(wxT("Warning, failed to save AICH hashset for file: %s"))
187 % m_filename );
192 if ((m_toHash == EH_AICH) && !TestDestroy()) {
193 CHashingEvent evt(MULE_EVT_AICH_HASHING, knownfile.release(), m_owner);
195 wxPostEvent(wxTheApp, evt);
196 } else if (!TestDestroy()) {
197 CHashingEvent evt(MULE_EVT_HASHING, knownfile.release(), m_owner);
199 wxPostEvent(wxTheApp, evt);
204 bool CHashingTask::CreateNextPartHash(CFileAutoClose& file, CKnownFile* owner, EHashes toHash)
206 wxCHECK_MSG(!file.Eof(), false, wxT("Unexpected EOF in CreateNextPartHash"));
208 // We'll read at most PARTSIZE bytes per cycle
209 const uint64 partLength = std::min<uint64>(PARTSIZE, file.GetLength() - file.GetPosition());
211 CMD4Hash hash;
212 CMD4Hash* md4Hash = ((toHash & EH_MD4) ? &hash : NULL);
213 CAICHHashTree* aichHash = NULL;
215 // Setup for AICH hashing
216 if (toHash & EH_AICH) {
217 aichHash = owner->GetAICHHashset()->m_pHashTree.FindHash(file.GetPosition(), partLength);
220 owner->CreateHashFromFile(file, partLength, md4Hash, aichHash);
222 if (toHash & EH_MD4) {
223 // Store the md4 hash
224 owner->m_hashlist.push_back(hash);
226 // This is because of the ed2k implementation for parts. A 2 * PARTSIZE
227 // file i.e. will have 3 parts (see CKnownFile::SetFileSize for comments).
228 // So we have to create the hash for the 0-size data, which will be the default
229 // md4 hash for null data: 31D6CFE0D16AE931B73C59D7E0C089C0
230 if ((partLength == PARTSIZE) && file.Eof()) {
231 owner->m_hashlist.push_back(CMD4Hash(g_emptyMD4Hash));
235 return true;
239 void CHashingTask::OnLastTask()
241 if (GetType() == wxT("Hashing")) {
242 // To prevent rehashing in case of crashes, we
243 // explicity save the list of hashed files here.
244 theApp->knownfiles->Save();
246 // Make sure the AICH-hashes are up to date.
247 CThreadScheduler::AddTask(new CAICHSyncTask());
252 ////////////////////////////////////////////////////////////
253 // CAICHSyncTask
255 CAICHSyncTask::CAICHSyncTask()
256 : CThreadTask(wxT("AICH Syncronizing"), wxEmptyString, ETP_Low)
261 void CAICHSyncTask::Entry()
263 ConvertToKnown2ToKnown264();
265 AddDebugLogLineM( false, logAICHThread, wxT("Syncronization thread started.") );
267 // We collect all masterhashs which we find in the known2.met and store them in a list
268 std::list<CAICHHash> hashlist;
269 const CPath fullpath = CPath(theApp->ConfigDir + KNOWN2_MET_FILENAME);
271 CFile file;
272 if (!file.Open(fullpath, (fullpath.FileExists() ? CFile::read_write : CFile::write))) {
273 AddDebugLogLineM( true, logAICHThread, wxT("Error, failed to open 'known2_64.met' file!") );
274 return;
277 uint32 nLastVerifiedPos = 0;
278 try {
279 if (file.Eof()) {
280 file.WriteUInt8(KNOWN2_MET_VERSION);
281 } else {
282 if (file.ReadUInt8() != KNOWN2_MET_VERSION) {
283 throw CEOFException(wxT("Invalid met-file header found, removing file."));
286 uint64 nExistingSize = file.GetLength();
287 while (file.GetPosition() < nExistingSize) {
288 // Read the next hash
289 hashlist.push_back(CAICHHash(&file));
291 uint32 nHashCount = file.ReadUInt32();
292 if (file.GetPosition() + nHashCount * CAICHHash::GetHashSize() > nExistingSize){
293 throw CEOFException(wxT("Hashlist ends past end of file."));
296 // skip the rest of this hashset
297 nLastVerifiedPos = file.Seek(nHashCount * HASHSIZE, wxFromCurrent);
300 } catch (const CEOFException&) {
301 AddDebugLogLineM(true, logAICHThread, wxT("Hashlist corrupted, truncating file."));
302 file.SetLength(nLastVerifiedPos);
303 } catch (const CIOFailureException& e) {
304 AddDebugLogLineM(true, logAICHThread, wxT("IO failure while reading hashlist (Aborting): ") + e.what());
306 return;
309 AddDebugLogLineM( false, logAICHThread, wxT("Masterhashes of known files have been loaded.") );
311 // Now we check that all files which are in the sharedfilelist have a
312 // corresponding hash in our list. Those how don't are queued for hashing.
313 theApp->sharedfiles->CheckAICHHashes(hashlist);
317 bool CAICHSyncTask::ConvertToKnown2ToKnown264()
319 // converting known2.met to known2_64.met to support large files
320 // changing hashcount from uint16 to uint32
322 const CPath oldfullpath = CPath(theApp->ConfigDir + OLD_KNOWN2_MET_FILENAME);
323 const CPath newfullpath = CPath(theApp->ConfigDir + KNOWN2_MET_FILENAME);
325 if (newfullpath.FileExists() || !oldfullpath.FileExists()) {
326 // In this case, there is nothing that we need to do.
327 return false;
330 CFile oldfile;
331 CFile newfile;
333 if (!oldfile.Open(oldfullpath, CFile::read)) {
334 AddDebugLogLineM(true, logAICHThread, wxT("Failed to open 'known2.met' file."));
336 // else -> known2.met also doesn't exists, so nothing to convert
337 return false;
341 if (!newfile.Open(newfullpath, CFile::write_excl)) {
342 AddDebugLogLineM(true, logAICHThread, wxT("Failed to create 'known2_64.met' file."));
344 return false;
347 AddLogLineM(false, CFormat(_("Converting old AICH hashsets in '%s' to 64b in '%s'."))
348 % OLD_KNOWN2_MET_FILENAME % KNOWN2_MET_FILENAME);
350 try {
351 newfile.WriteUInt8(KNOWN2_MET_VERSION);
353 while (newfile.GetPosition() < oldfile.GetLength()) {
354 CAICHHash aichHash(&oldfile);
355 uint32 nHashCount = oldfile.ReadUInt16();
357 CScopedArray<byte> buffer(nHashCount * CAICHHash::GetHashSize());
359 oldfile.Read(buffer.get(), nHashCount * CAICHHash::GetHashSize());
360 newfile.Write(aichHash.GetRawHash(), CAICHHash::GetHashSize());
361 newfile.WriteUInt32(nHashCount);
362 newfile.Write(buffer.get(), nHashCount * CAICHHash::GetHashSize());
364 newfile.Flush();
365 } catch (const CEOFException& e) {
366 AddDebugLogLineM(true, logAICHThread, wxT("Error reading old 'known2.met' file.") + e.what());
367 return false;
368 } catch (const CIOFailureException& e) {
369 AddDebugLogLineM(true, logAICHThread, wxT("IO error while converting 'known2.met' file: ") + e.what());
370 return false;
373 // FIXME LARGE FILES (uncomment)
374 //DeleteFile(oldfullpath);
376 return true;
381 ////////////////////////////////////////////////////////////
382 // CCompletionTask
385 CCompletionTask::CCompletionTask(const CPartFile* file)
386 // GetPrintable is used to improve the readability of the log.
387 : CThreadTask(wxT("Completing"), file->GetFullName().GetPrintable(), ETP_High),
388 m_filename(file->GetFileName()),
389 m_metPath(file->GetFullName()),
390 m_category(file->GetCategory()),
391 m_owner(file),
392 m_error(false)
394 wxASSERT(m_filename.IsOk());
395 wxASSERT(m_metPath.IsOk());
396 wxASSERT(m_owner);
400 void CCompletionTask::Entry()
402 CPath targetPath;
405 #ifndef AMULE_DAEMON
406 // Prevent the preference values from changing underneeth us.
407 wxMutexGuiLocker guiLock;
408 #else
409 //#warning Thread-safety needed
410 #endif
412 targetPath = theApp->glob_prefs->GetCategory(m_category)->path;
413 if (!targetPath.DirExists()) {
414 targetPath = thePrefs::GetIncomingDir();
418 CPath dstName = m_filename.Cleanup(true, PlatformSpecific::CanFSHandleSpecialChars(targetPath));
420 // Avoid empty filenames ...
421 if (!dstName.IsOk()) {
422 dstName = CPath(wxT("Unknown"));
425 if (m_filename != dstName) {
426 AddDebugLogLineM(true, logPartFile, CFormat(_("WARNING: The filename '%s' is invalid and has been renamed to '%s'."))
427 % m_filename % dstName);
430 // Avoid saving to an already existing filename
431 CPath newName = targetPath.JoinPaths(dstName);
432 for (unsigned count = 0; newName.FileExists(); ++count) {
433 wxString postfix = wxString::Format(wxT("(%u)"), count);
435 newName = targetPath.JoinPaths(dstName.AddPostfix(postfix));
438 if (newName != targetPath.JoinPaths(dstName)) {
439 AddDebugLogLineM(true, logPartFile, CFormat(_("WARNING: The file '%s' already exists, new file renamed to '%s'."))
440 % dstName % newName.GetFullName());
443 // Move will handle dirs on the same partition, otherwise copy is needed.
444 CPath partfilename = m_metPath.RemoveExt();
445 if (!CPath::RenameFile(partfilename, newName)) {
446 if (!CPath::CloneFile(partfilename, newName, true)) {
447 m_error = true;
448 return;
451 if (!CPath::RemoveFile(partfilename)) {
452 AddDebugLogLineM(true, logPartFile, CFormat(_("WARNING: Could not remove original '%s' after creating backup"))
453 % partfilename);
457 // Removes the various other data-files
458 const wxChar* otherMetExt[] = { wxT(""), PARTMET_BAK_EXT, wxT(".seeds"), NULL };
459 for (size_t i = 0; otherMetExt[i]; ++i) {
460 CPath toRemove = m_metPath.AppendExt(otherMetExt[i]);
462 if (toRemove.FileExists()) {
463 if (!CPath::RemoveFile(toRemove)) {
464 AddDebugLogLineM(true, logPartFile, CFormat(_("WARNING: Failed to delete %s")) % toRemove);
469 m_newName = newName;
473 void CCompletionTask::OnExit()
475 // Notify the app that the completion has finished for this file.
476 CCompletionEvent evt(m_error, m_owner, m_newName);
478 wxPostEvent(wxTheApp, evt);
483 ////////////////////////////////////////////////////////////
484 // CAllocateFileTask
486 #ifdef HAVE_FALLOCATE
487 # include <linux/falloc.h>
488 #elif defined HAVE_SYS_FALLOCATE
489 # include <sys/syscall.h>
490 # include <sys/types.h>
491 # include <unistd.h>
492 #elif defined HAVE_POSIX_FALLOCATE
493 # define _XOPEN_SOURCE 600
494 # include <stdlib.h>
495 # ifdef HAVE_FCNTL_H
496 # include <fcntl.h>
497 # endif
498 #endif
499 #include <stdlib.h>
500 #include <errno.h>
502 CAllocateFileTask::CAllocateFileTask(CPartFile *file, bool pause)
503 // GetPrintable is used to improve the readability of the log.
504 : CThreadTask(wxT("Allocating"), file->GetFullName().RemoveExt().GetPrintable(), ETP_High),
505 m_file(file), m_pause(pause), m_result(ENOSYS)
507 wxASSERT(file != NULL);
510 void CAllocateFileTask::Entry()
512 if (m_file->GetFileSize() == 0) {
513 m_result = 0;
514 return;
517 uint64_t minFree = thePrefs::IsCheckDiskspaceEnabled() ? thePrefs::GetMinFreeDiskSpace() : 0;
518 int64_t freeSpace = CPath::GetFreeSpaceAt(thePrefs::GetTempDir());
520 // Don't even try to allocate, if there's no space to complete the operation.
521 if (freeSpace != wxInvalidOffset) {
522 if ((uint64_t)freeSpace < m_file->GetFileSize() + minFree) {
523 m_result = ENOSPC;
524 return;
528 CFile file;
529 file.Open(m_file->GetFullName().RemoveExt(), CFile::read_write);
531 #ifdef __WXMSW__
532 try {
533 // File is already created as non-sparse, so we only need to set the length.
534 // This will fail to allocate the file e.g. under wine on linux/ext3,
535 // but works with NTFS and FAT32.
536 file.Seek(m_file->GetFileSize() - 1, wxFromStart);
537 file.WriteUInt8(0);
538 file.Close();
539 m_result = 0;
540 } catch (const CSafeIOException&) {
541 m_result = errno;
543 #else
544 // Use kernel level routines if possible
545 # ifdef HAVE_FALLOCATE
546 m_result = fallocate(file.fd(), 0, 0, m_file->GetFileSize());
547 # elif defined HAVE_SYS_FALLOCATE
548 m_result = syscall(SYS_fallocate, file.fd(), 0, (loff_t)0, (loff_t)m_file->GetFileSize());
549 if (m_result == -1) {
550 m_result = errno;
552 # elif defined HAVE_POSIX_FALLOCATE
553 // otherwise use glibc implementation, if available
554 m_result = posix_fallocate(file.fd(), 0, m_file->GetFileSize());
555 # endif
557 if (m_result != 0 && m_result != ENOSPC) {
558 // If everything else fails, use slow-and-dirty method of allocating the file: write the whole file with zeroes.
559 # define BLOCK_SIZE 1048576 /* Write 1 MB blocks */
560 void *zero = calloc(1, BLOCK_SIZE);
561 if (zero != NULL) {
562 try {
563 uint64_t size = m_file->GetFileSize();
564 for (; size >= BLOCK_SIZE; size -= BLOCK_SIZE) {
565 file.Write(zero, BLOCK_SIZE);
567 if (size > 0) {
568 file.Write(zero, size);
570 file.Close();
571 m_result = 0;
572 } catch (const CSafeIOException&) {
573 m_result = errno;
575 free(zero);
576 } else {
577 m_result = ENOMEM;
581 #endif
582 if (file.IsOpened()) {
583 file.Close();
587 void CAllocateFileTask::OnExit()
589 // Notify the app that the preallocation has finished for this file.
590 CAllocFinishedEvent evt(m_file, m_pause, m_result);
592 wxPostEvent(wxTheApp, evt);
597 ////////////////////////////////////////////////////////////
598 // CHashingEvent
600 DEFINE_LOCAL_EVENT_TYPE(MULE_EVT_HASHING)
601 DEFINE_LOCAL_EVENT_TYPE(MULE_EVT_AICH_HASHING)
603 CHashingEvent::CHashingEvent(wxEventType type, CKnownFile* result, const CKnownFile* owner)
604 : wxEvent(-1, type),
605 m_owner(owner),
606 m_result(result)
611 wxEvent* CHashingEvent::Clone() const
613 return new CHashingEvent(GetEventType(), m_result, m_owner);
617 const CKnownFile* CHashingEvent::GetOwner() const
619 return m_owner;
623 CKnownFile* CHashingEvent::GetResult() const
625 return m_result;
631 ////////////////////////////////////////////////////////////
632 // CCompletionEvent
634 DEFINE_LOCAL_EVENT_TYPE(MULE_EVT_FILE_COMPLETED)
637 CCompletionEvent::CCompletionEvent(bool errorOccured, const CPartFile* owner, const CPath& fullPath)
638 : wxEvent(-1, MULE_EVT_FILE_COMPLETED),
639 m_fullPath(fullPath),
640 m_owner(owner),
641 m_error(errorOccured)
646 wxEvent* CCompletionEvent::Clone() const
648 return new CCompletionEvent(m_error, m_owner, m_fullPath);
652 bool CCompletionEvent::ErrorOccured() const
654 return m_error;
658 const CPartFile* CCompletionEvent::GetOwner() const
660 return m_owner;
664 const CPath& CCompletionEvent::GetFullPath() const
666 return m_fullPath;
670 ////////////////////////////////////////////////////////////
671 // CAllocFinishedEvent
673 DEFINE_LOCAL_EVENT_TYPE(MULE_EVT_ALLOC_FINISHED)
675 wxEvent *CAllocFinishedEvent::Clone() const
677 return new CAllocFinishedEvent(m_file, m_pause, m_result);
680 // File_checked_for_headers