// -*- mode: C++; c-file-style: "gnu" -*- // Author:: Don Sanders // License GPL #include "kmfolderindex.h" #include #include #include #include #define HAVE_MMAP //need to get this into autoconf FIXME --Sam #include #ifdef HAVE_MMAP #include #endif // Current version of the table of contents (index) files #define INDEX_VERSION 1506 #ifndef MAX_LINE #define MAX_LINE 4096 #endif #ifndef INIT_MSGS #define INIT_MSGS 8 #endif #include #include #include #ifdef HAVE_BYTESWAP_H #include #endif #include #include #include #include #include "kmmsgdict.h" // We define functions as kmail_swap_NN so that we don't get compile errors // on platforms where bswap_NN happens to be a function instead of a define. /* Swap bytes in 32 bit value. */ #ifdef bswap_32 #define kmail_swap_32(x) bswap_32(x) #else #define kmail_swap_32(x) \ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) #endif #include #include #include #include KMFolderIndex::KMFolderIndex(KMFolderDir* parent, const QString& name) : KMFolder(parent, name), mMsgList(INIT_MSGS) { mIndexStream = 0; mIndexStreamPtr = 0; mIndexStreamPtrLength = 0; mIndexSwapByteOrder = false; mIndexSizeOfLong = sizeof(long); mIndexId = 0; mHeaderOffset = 0; } KMFolderIndex::~KMFolderIndex() { } QString KMFolderIndex::indexLocation() const { QString sLocation(path()); if (!sLocation.isEmpty()) sLocation += '/'; sLocation += '.'; sLocation += dotEscape(fileName()); sLocation += ".index"; return sLocation; } int KMFolderIndex::updateIndex() { if (!mAutoCreateIndex) return 0; bool dirty = mDirty; mDirtyTimer->stop(); for (unsigned int i=0; !dirty && isyncIndexString(); if (!dirty) { // Update successful touchMsgDict(); return 0; } return writeIndex(); } int KMFolderIndex::writeIndex( bool createEmptyIndex ) { QString tempName; QString indexName; mode_t old_umask; int len; const uchar *buffer = 0; indexName = indexLocation(); tempName = indexName + ".temp"; unlink(QFile::encodeName(tempName)); // We touch the folder, otherwise the index is regenerated, if KMail is // running, while the clock switches from daylight savings time to normal time utime(QFile::encodeName(location()), 0); old_umask = umask(077); FILE *tmpIndexStream = fopen(QFile::encodeName(tempName), "w"); umask(old_umask); if (!tmpIndexStream) return errno; fprintf(tmpIndexStream, "# KMail-Index V%d\n", INDEX_VERSION); // Header Q_UINT32 byteOrder = 0x12345678; Q_UINT32 sizeOfLong = sizeof(long); Q_UINT32 header_length = sizeof(byteOrder)+sizeof(sizeOfLong); char pad_char = '\0'; fwrite(&pad_char, sizeof(pad_char), 1, tmpIndexStream); fwrite(&header_length, sizeof(header_length), 1, tmpIndexStream); // Write header fwrite(&byteOrder, sizeof(byteOrder), 1, tmpIndexStream); fwrite(&sizeOfLong, sizeof(sizeOfLong), 1, tmpIndexStream); off_t nho = ftell(tmpIndexStream); if ( !createEmptyIndex ) { KMMsgBase* msgBase; for (unsigned int i=0; iasIndexString(len); fwrite(&len,sizeof(len), 1, tmpIndexStream); off_t tmp = ftell(tmpIndexStream); msgBase->setIndexOffset(tmp); msgBase->setIndexLength(len); if(fwrite(buffer, len, 1, tmpIndexStream) != 1) kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl; } } int fError = ferror( tmpIndexStream ); if( fError != 0 ) { fclose( tmpIndexStream ); return fError; } if( ( fflush( tmpIndexStream ) != 0 ) || ( fsync( fileno( tmpIndexStream ) ) != 0 ) ) { int errNo = errno; fclose( tmpIndexStream ); return errNo; } if( fclose( tmpIndexStream ) != 0 ) return errno; ::rename(QFile::encodeName(tempName), QFile::encodeName(indexName)); mHeaderOffset = nho; if (mIndexStream) fclose(mIndexStream); if ( createEmptyIndex ) return 0; mIndexStream = fopen(QFile::encodeName(indexName), "r+"); // index file assert( mIndexStream ); updateIndexStreamPtr(); writeMsgDict(); setDirty( false ); return 0; } bool KMFolderIndex::readIndex() { Q_INT32 len; KMMsgInfo* mi; assert(mIndexStream != 0); rewind(mIndexStream); clearIndex(); int version; setDirty( false ); if (!readIndexHeader(&version)) return false; mUnreadMsgs = 0; mTotalMsgs = 0; mHeaderOffset = ftell(mIndexStream); clearIndex(); while (!feof(mIndexStream)) { mi = 0; if(version >= 1505) { if(!fread(&len, sizeof(len), 1, mIndexStream)) break; if (mIndexSwapByteOrder) len = kmail_swap_32(len); off_t offs = ftell(mIndexStream); if(fseek(mIndexStream, len, SEEK_CUR)) break; mi = new KMMsgInfo(this, offs, len); } else { QCString line(MAX_LINE); fgets(line.data(), MAX_LINE, mIndexStream); if (feof(mIndexStream)) break; if (*line.data() == '\0') { fclose(mIndexStream); mIndexStream = 0; clearIndex(); return false; } mi = new KMMsgInfo(this); mi->compat_fromOldIndexString(line, mConvertToUtf8); } if(!mi) break; if (mi->isDeleted()) { delete mi; // skip messages that are marked as deleted setDirty( true ); needsCompact = true; //We have deleted messages - needs to be compacted continue; } #ifdef OBSOLETE else if (mi->isNew()) { mi->setStatus(KMMsgStatusUnread); mi->setDirty(FALSE); } #endif if ((mi->isNew()) || (mi->isUnread()) || (this == kmkernel->outboxFolder())) { ++mUnreadMsgs; if (mUnreadMsgs == 0) ++mUnreadMsgs; } mMsgList.append(mi, false); } if( version < 1505) { mConvertToUtf8 = FALSE; setDirty( true ); writeIndex(); } mTotalMsgs = mMsgList.count(); return true; } int KMFolderIndex::count(bool cache) const { int res = KMFolder::count(cache); if (res == -1) res = mMsgList.count(); return res; } bool KMFolderIndex::readIndexHeader(int *gv) { int indexVersion; assert(mIndexStream != 0); mIndexSwapByteOrder = false; mIndexSizeOfLong = sizeof(long); int ret = fscanf(mIndexStream, "# KMail-Index V%d\n", &indexVersion); if ( ret == EOF || ret == 0 ) return false; // index file has invalid header if(gv) *gv = indexVersion; if (indexVersion < 1505 ) { if(indexVersion == 1503) { kdDebug(5006) << "Converting old index file " << indexLocation() << " to utf-8" << endl; mConvertToUtf8 = TRUE; } return TRUE; } else if (indexVersion == 1505) { } else if (indexVersion < INDEX_VERSION) { kdDebug(5006) << "Index file " << indexLocation() << " is out of date. Re-creating it." << endl; createIndexFromContents(); return FALSE; } else if(indexVersion > INDEX_VERSION) { kapp->setOverrideCursor(KCursor::arrowCursor()); int r = KMessageBox::questionYesNo(0, i18n( "The mail index for '%1' is from an unknown version of KMail (%2).\n" "This index can be regenerated from your mail folder, but some " "information, including status flags, may be lost. Do you wish " "to downgrade your index file?") .arg(name()) .arg(indexVersion) ); kapp->restoreOverrideCursor(); if (r == KMessageBox::Yes) createIndexFromContents(); return FALSE; } else { // Header Q_UINT32 byteOrder = 0; Q_UINT32 sizeOfLong = sizeof(long); // default Q_UINT32 header_length = 0; fseek(mIndexStream, sizeof(char), SEEK_CUR ); fread(&header_length, sizeof(header_length), 1, mIndexStream); if (header_length > 0xFFFF) header_length = kmail_swap_32(header_length); off_t endOfHeader = ftell(mIndexStream) + header_length; bool needs_update = true; // Process available header parts if (header_length >= sizeof(byteOrder)) { fread(&byteOrder, sizeof(byteOrder), 1, mIndexStream); mIndexSwapByteOrder = (byteOrder == 0x78563412); header_length -= sizeof(byteOrder); if (header_length >= sizeof(sizeOfLong)) { fread(&sizeOfLong, sizeof(sizeOfLong), 1, mIndexStream); if (mIndexSwapByteOrder) sizeOfLong = kmail_swap_32(sizeOfLong); mIndexSizeOfLong = sizeOfLong; header_length -= sizeof(sizeOfLong); needs_update = false; } } if (needs_update || mIndexSwapByteOrder) setDirty( true ); // Seek to end of header fseek(mIndexStream, endOfHeader, SEEK_SET ); if (mIndexSwapByteOrder) kdDebug(5006) << "Index File has byte order swapped!" << endl; if (mIndexSizeOfLong != sizeof(long)) kdDebug(5006) << "Index File sizeOfLong is " << mIndexSizeOfLong << " while sizeof(long) is " << sizeof(long) << " !" << endl; } return TRUE; } #ifdef HAVE_MMAP bool KMFolderIndex::updateIndexStreamPtr(bool just_close) #else bool KMFolderIndex::updateIndexStreamPtr(bool) #endif { // We touch the folder, otherwise the index is regenerated, if KMail is // running, while the clock switches from daylight savings time to normal time utime(QFile::encodeName(location()), 0); utime(QFile::encodeName(indexLocation()), 0); utime(QFile::encodeName(KMMsgDict::getFolderIdsLocation( this )), 0); mIndexSwapByteOrder = false; #ifdef HAVE_MMAP if(just_close) { if(mIndexStreamPtr) munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength); mIndexStreamPtr = 0; mIndexStreamPtrLength = 0; return TRUE; } assert(mIndexStream); struct stat stat_buf; if(fstat(fileno(mIndexStream), &stat_buf) == -1) { if(mIndexStreamPtr) munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength); mIndexStreamPtr = 0; mIndexStreamPtrLength = 0; return FALSE; } if(mIndexStreamPtr) munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength); mIndexStreamPtrLength = stat_buf.st_size; mIndexStreamPtr = (uchar *)mmap(0, mIndexStreamPtrLength, PROT_READ, MAP_SHARED, fileno(mIndexStream), 0); if(mIndexStreamPtr == MAP_FAILED) { mIndexStreamPtr = 0; mIndexStreamPtrLength = 0; return FALSE; } #endif return TRUE; } KMFolderIndex::IndexStatus KMFolderIndex::indexStatus() { QFileInfo contInfo(location()); QFileInfo indInfo(indexLocation()); if (!contInfo.exists()) return KMFolderIndex::IndexOk; if (!indInfo.exists()) return KMFolderIndex::IndexMissing; return ( contInfo.lastModified() > indInfo.lastModified() ) ? KMFolderIndex::IndexTooOld : KMFolderIndex::IndexOk; } void KMFolderIndex::clearIndex(bool autoDelete, bool syncDict) { mMsgList.clear(autoDelete, syncDict); } void KMFolderIndex::truncateIndex() { if ( mHeaderOffset ) truncate(QFile::encodeName(indexLocation()), mHeaderOffset); else // The index file wasn't opened, so we don't know the header offset. // So let's just create a new empty index. writeIndex( true ); } void KMFolderIndex::fillDictFromIndex(KMMsgDict *dict) { open(); mMsgList.fillMsgDict(dict); close(); } KMMsgInfo* KMFolderIndex::setIndexEntry( int idx, KMMessage *msg ) { KMMsgInfo *msgInfo = new KMMsgInfo( this ); *msgInfo = *msg; mMsgList.set( idx, msgInfo ); return msgInfo; } #include "kmfolderindex.moc"