| 2458 | |
| 2459 | |
| 2460 | bool CPartFile::HashSinglePart(uint16 partnumber) |
| 2461 | { |
| 2462 | if ((GetHashCount() <= partnumber) && (GetPartCount() > 1)) { |
| 2463 | AddLogLineC(CFormat( _("WARNING: Unable to hash downloaded part - hashset incomplete for '%s'") ) |
| 2464 | % GetFileName() ); |
| 2465 | m_hashsetneeded = true; |
| 2466 | return true; |
| 2467 | } else if ((GetHashCount() <= partnumber) && GetPartCount() != 1) { |
| 2468 | AddLogLineC(CFormat( _("ERROR: Unable to hash downloaded part - hashset incomplete (%s). This should never happen")) % GetFileName() ); |
| 2469 | m_hashsetneeded = true; |
| 2470 | return true; |
| 2471 | } else { |
| 2472 | CMD4Hash hashresult; |
| 2473 | uint64 offset = PARTSIZE * partnumber; |
| 2474 | uint32 length = GetPartSize(partnumber); |
| 2475 | |
| 2476 | // Drift defense: gap-tracking advances on receive, but bytes |
| 2477 | // only hit disk via the buffered write path; a write failure |
| 2478 | // (ENOSPC, EIO, transient I/O error) followed by a SavePartFile |
| 2479 | // and an unclean shutdown leaves the .met persisted with the |
| 2480 | // optimistic gap state, while the partfile is shorter than |
| 2481 | // the gap-list claims. Reading past EOF would throw and trip |
| 2482 | // PS_ERROR, requiring manual recovery (truncate -s <fullsize>). |
| 2483 | // Detect the divergence pre-read and reopen the gap so the |
| 2484 | // missing bytes are re-fetched cleanly. Also covers external |
| 2485 | // truncation, partial backup restore, and FS corruption. |
| 2486 | const uint64 partfileLen = m_hpartfile.GetLength(); |
| 2487 | if (partfileLen < offset + length) { |
| 2488 | AddLogLineC(CFormat(_( |
| 2489 | "Part %u of '%s' marked complete but partfile is shorter than expected " |
| 2490 | "(have %llu bytes, need %llu); reopening gap for re-download.")) |
| 2491 | % partnumber % GetFileName() |
| 2492 | % (unsigned long long)partfileLen |
| 2493 | % (unsigned long long)(offset + length)); |
| 2494 | AddGap(offset, offset + length - 1); |
| 2495 | return false; |
| 2496 | } |
| 2497 | |
| 2498 | try { |
| 2499 | CreateHashFromFile(m_hpartfile, offset, length, &hashresult, NULL); |
| 2500 | } catch (const CIOFailureException& e) { |
| 2501 | AddLogLineC(CFormat( _("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s")) |
| 2502 | % partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what()); |
| 2503 | SetStatus(PS_ERROR); |
| 2504 | return false; |
| 2505 | } catch (const CEOFException& e) { |
| 2506 | AddLogLineC(CFormat( _("EOF while hashing downloaded part %u with length %u (max %u) of partfile '%s' with length %u: %s")) |
| 2507 | % partnumber % length % (offset+length) % GetFileName() % GetFileSize() % e.what()); |
| 2508 | SetStatus(PS_ERROR); |
| 2509 | return false; |
| 2510 | } |
| 2511 | |
| 2512 | if (GetPartCount() > 1) { |
| 2513 | if (hashresult != GetPartHash(partnumber)) { |
| 2514 | AddDebugLogLineN(logPartFile, CFormat( "%s: Expected hash of part %d: %s") % GetFileName() % partnumber % GetPartHash(partnumber).Encode() ); |
| 2515 | AddDebugLogLineN(logPartFile, CFormat( "%s: Actual hash of part %d: %s") % GetFileName() % partnumber % hashresult.Encode() ); |
| 2516 | return false; |
| 2517 | } else { |