kmail

kmfolderindex.cpp

Go to the documentation of this file.
00001 /* -*- mode: C++; c-file-style: "gnu" -*-
00002     This file is part of KMail, the KDE mail client.
00003     Copyright (c) 2000 Don Sanders <sanders@kde.org>
00004 
00005     KMail is free software; you can redistribute it and/or modify it
00006     under the terms of the GNU General Public License, version 2, as
00007     published by the Free Software Foundation.
00008 
00009     KMail is distributed in the hope that it will be useful, but
00010     WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     General Public License for more details.
00013 
00014     You should have received a copy of the GNU General Public License
00015     along with this program; if not, write to the Free Software
00016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00017 */
00018 
00019 #include "kmfolderindex.h"
00020 #include "kmfolder.h"
00021 #include <config.h>
00022 #include <qfileinfo.h>
00023 #include <qtimer.h>
00024 #include <kdebug.h>
00025 
00026 
00027 #define HAVE_MMAP //need to get this into autoconf FIXME  --Sam
00028 #include <unistd.h>
00029 #ifdef HAVE_MMAP
00030 #include <sys/mman.h>
00031 #endif
00032 
00033 // Current version of the table of contents (index) files
00034 #define INDEX_VERSION 1506
00035 
00036 #ifndef MAX_LINE
00037 #define MAX_LINE 4096
00038 #endif
00039 
00040 #ifndef INIT_MSGS
00041 #define INIT_MSGS 8
00042 #endif
00043 
00044 #include <errno.h>
00045 #include <assert.h>
00046 #include <utime.h>
00047 #include <fcntl.h>
00048 
00049 #ifdef HAVE_BYTESWAP_H
00050 #include <byteswap.h>
00051 #endif
00052 #include <kapplication.h>
00053 #include <kcursor.h>
00054 #include <kmessagebox.h>
00055 #include <klocale.h>
00056 #include "kmmsgdict.h"
00057 
00058 // We define functions as kmail_swap_NN so that we don't get compile errors
00059 // on platforms where bswap_NN happens to be a function instead of a define.
00060 
00061 /* Swap bytes in 32 bit value.  */
00062 #ifdef bswap_32
00063 #define kmail_swap_32(x) bswap_32(x)
00064 #else
00065 #define kmail_swap_32(x) \
00066      ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |           \
00067       (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))
00068 #endif
00069 
00070 #include <stdlib.h>
00071 #include <sys/types.h>
00072 #include <sys/stat.h>
00073 #include <sys/file.h>
00074 
00075 KMFolderIndex::KMFolderIndex(KMFolder* folder, const char* name)
00076   : FolderStorage(folder, name), mMsgList(INIT_MSGS)
00077 {
00078     mIndexStream = 0;
00079     mIndexStreamPtr = 0;
00080     mIndexStreamPtrLength = 0;
00081     mIndexSwapByteOrder = false;
00082     mIndexSizeOfLong = sizeof(long);
00083     mIndexId = 0;
00084     mHeaderOffset   = 0;
00085 }
00086 
00087 
00088 KMFolderIndex::~KMFolderIndex()
00089 {
00090 }
00091 
00092 
00093 QString KMFolderIndex::indexLocation() const
00094 {
00095   QString sLocation(folder()->path());
00096 
00097   if ( !sLocation.isEmpty() ) {
00098     sLocation += '/';
00099     sLocation += '.';
00100   }
00101   sLocation += dotEscape(fileName());
00102   sLocation += ".index";
00103 
00104   return sLocation;
00105 }
00106 
00107 int KMFolderIndex::updateIndex()
00108 {
00109   if (!mAutoCreateIndex)
00110     return 0;
00111   bool dirty = mDirty;
00112   mDirtyTimer->stop();
00113   for (unsigned int i=0; !dirty && i<mMsgList.high(); i++)
00114     if (mMsgList.at(i))
00115       dirty = !mMsgList.at(i)->syncIndexString();
00116   if (!dirty) { // Update successful
00117       touchFolderIdsFile();
00118       return 0;
00119   }
00120   return writeIndex();
00121 }
00122 
00123 int KMFolderIndex::writeIndex( bool createEmptyIndex )
00124 {
00125   QString tempName;
00126   QString indexName;
00127   mode_t old_umask;
00128   int len;
00129   const uchar *buffer = 0;
00130 
00131   indexName = indexLocation();
00132   tempName = indexName + ".temp";
00133   unlink(QFile::encodeName(tempName));
00134 
00135   // We touch the folder, otherwise the index is regenerated, if KMail is
00136   // running, while the clock switches from daylight savings time to normal time
00137   utime(QFile::encodeName(location()), 0);
00138 
00139   old_umask = umask(077);
00140   FILE *tmpIndexStream = fopen(QFile::encodeName(tempName), "w");
00141   umask(old_umask);
00142   if (!tmpIndexStream)
00143     return errno;
00144 
00145   fprintf(tmpIndexStream, "# KMail-Index V%d\n", INDEX_VERSION);
00146 
00147   // Header
00148   Q_UINT32 byteOrder = 0x12345678;
00149   Q_UINT32 sizeOfLong = sizeof(long);
00150 
00151   Q_UINT32 header_length = sizeof(byteOrder)+sizeof(sizeOfLong);
00152   char pad_char = '\0';
00153   fwrite(&pad_char, sizeof(pad_char), 1, tmpIndexStream);
00154   fwrite(&header_length, sizeof(header_length), 1, tmpIndexStream);
00155 
00156   // Write header
00157   fwrite(&byteOrder, sizeof(byteOrder), 1, tmpIndexStream);
00158   fwrite(&sizeOfLong, sizeof(sizeOfLong), 1, tmpIndexStream);
00159 
00160   off_t nho = ftell(tmpIndexStream);
00161 
00162   if ( !createEmptyIndex ) {
00163     KMMsgBase* msgBase;
00164     for (unsigned int i=0; i<mMsgList.high(); i++)
00165     {
00166       if (!(msgBase = mMsgList.at(i))) continue;
00167       buffer = msgBase->asIndexString(len);
00168       fwrite(&len,sizeof(len), 1, tmpIndexStream);
00169 
00170       off_t tmp = ftell(tmpIndexStream);
00171       msgBase->setIndexOffset(tmp);
00172       msgBase->setIndexLength(len);
00173       if(fwrite(buffer, len, 1, tmpIndexStream) != 1)
00174     kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
00175     }
00176   }
00177 
00178   int fError = ferror( tmpIndexStream );
00179   if( fError != 0 ) {
00180     fclose( tmpIndexStream );
00181     return fError;
00182   }
00183   if(    ( fflush( tmpIndexStream ) != 0 )
00184       || ( fsync( fileno( tmpIndexStream ) ) != 0 ) ) {
00185     int errNo = errno;
00186     fclose( tmpIndexStream );
00187     return errNo;
00188   }
00189   if( fclose( tmpIndexStream ) != 0 )
00190     return errno;
00191 
00192   ::rename(QFile::encodeName(tempName), QFile::encodeName(indexName));
00193   mHeaderOffset = nho;
00194   if (mIndexStream)
00195       fclose(mIndexStream);
00196 
00197   if ( createEmptyIndex )
00198     return 0;
00199 
00200   mIndexStream = fopen(QFile::encodeName(indexName), "r+"); // index file
00201   assert( mIndexStream );
00202   fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
00203 
00204   updateIndexStreamPtr();
00205 
00206   writeFolderIdsFile();
00207 
00208   setDirty( false );
00209   return 0;
00210 }
00211 
00212 
00213 bool KMFolderIndex::readIndex()
00214 {
00215   Q_INT32 len;
00216   KMMsgInfo* mi;
00217 
00218   assert(mIndexStream != 0);
00219   rewind(mIndexStream);
00220 
00221   clearIndex();
00222   int version;
00223 
00224   setDirty( false );
00225 
00226   if (!readIndexHeader(&version)) return false;
00227 
00228   mUnreadMsgs = 0;
00229   mTotalMsgs = 0;
00230   mHeaderOffset = ftell(mIndexStream);
00231 
00232   clearIndex();
00233   while (!feof(mIndexStream))
00234   {
00235     mi = 0;
00236     if(version >= 1505) {
00237       if(!fread(&len, sizeof(len), 1, mIndexStream))
00238         break;
00239 
00240       if (mIndexSwapByteOrder)
00241         len = kmail_swap_32(len);
00242 
00243       off_t offs = ftell(mIndexStream);
00244       if(fseek(mIndexStream, len, SEEK_CUR))
00245         break;
00246       mi = new KMMsgInfo(folder(), offs, len);
00247     }
00248     else
00249     {
00250       QCString line(MAX_LINE);
00251       fgets(line.data(), MAX_LINE, mIndexStream);
00252       if (feof(mIndexStream)) break;
00253       if (*line.data() == '\0') {
00254       fclose(mIndexStream);
00255       mIndexStream = 0;
00256       clearIndex();
00257       return false;
00258       }
00259       mi = new KMMsgInfo(folder());
00260       mi->compat_fromOldIndexString(line, mConvertToUtf8);
00261     }
00262     if(!mi)
00263       break;
00264 
00265     if (mi->isDeleted())
00266     {
00267       delete mi;  // skip messages that are marked as deleted
00268       setDirty( true );
00269       needsCompact = true;  //We have deleted messages - needs to be compacted
00270       continue;
00271     }
00272 #ifdef OBSOLETE
00273     else if (mi->isNew())
00274     {
00275       mi->setStatus(KMMsgStatusUnread);
00276       mi->setDirty(false);
00277     }
00278 #endif
00279     if ((mi->isNew()) || (mi->isUnread()) ||
00280         (folder() == kmkernel->outboxFolder()))
00281     {
00282       ++mUnreadMsgs;
00283       if (mUnreadMsgs == 0) ++mUnreadMsgs;
00284     }
00285     mMsgList.append(mi, false);
00286   }
00287   if( version < 1505)
00288   {
00289     mConvertToUtf8 = false;
00290     setDirty( true );
00291     writeIndex();
00292   }
00293   mTotalMsgs = mMsgList.count();
00294   return true;
00295 }
00296 
00297 
00298 int KMFolderIndex::count(bool cache) const
00299 {
00300   int res = FolderStorage::count(cache);
00301   if (res == -1)
00302     res = mMsgList.count();
00303   return res;
00304 }
00305 
00306 
00307 bool KMFolderIndex::readIndexHeader(int *gv)
00308 {
00309   int indexVersion;
00310   assert(mIndexStream != 0);
00311   mIndexSwapByteOrder = false;
00312   mIndexSizeOfLong = sizeof(long);
00313 
00314   int ret = fscanf(mIndexStream, "# KMail-Index V%d\n", &indexVersion);
00315   if ( ret == EOF || ret == 0 )
00316       return false; // index file has invalid header
00317   if(gv)
00318       *gv = indexVersion;
00319   if (indexVersion < 1505 ) {
00320       if(indexVersion == 1503) {
00321       kdDebug(5006) << "Converting old index file " << indexLocation() << " to utf-8" << endl;
00322       mConvertToUtf8 = true;
00323       }
00324       return true;
00325   } else if (indexVersion == 1505) {
00326   } else if (indexVersion < INDEX_VERSION) {
00327       kdDebug(5006) << "Index file " << indexLocation() << " is out of date. Re-creating it." << endl;
00328       createIndexFromContents();
00329       return false;
00330   } else if(indexVersion > INDEX_VERSION) {
00331       kapp->setOverrideCursor(KCursor::arrowCursor());
00332       int r = KMessageBox::questionYesNo(0,
00333                      i18n(
00334                         "The mail index for '%1' is from an unknown version of KMail (%2).\n"
00335                         "This index can be regenerated from your mail folder, but some "
00336                         "information, including status flags, may be lost. Do you wish "
00337                         "to downgrade your index file?") .arg(name()) .arg(indexVersion), QString::null, i18n("Downgrade"), i18n("Do Not Downgrade") );
00338       kapp->restoreOverrideCursor();
00339       if (r == KMessageBox::Yes)
00340       createIndexFromContents();
00341       return false;
00342   }
00343   else {
00344       // Header
00345       Q_UINT32 byteOrder = 0;
00346       Q_UINT32 sizeOfLong = sizeof(long); // default
00347 
00348       Q_UINT32 header_length = 0;
00349       fseek(mIndexStream, sizeof(char), SEEK_CUR );
00350       fread(&header_length, sizeof(header_length), 1, mIndexStream);
00351       if (header_length > 0xFFFF)
00352          header_length = kmail_swap_32(header_length);
00353 
00354       off_t endOfHeader = ftell(mIndexStream) + header_length;
00355 
00356       bool needs_update = true;
00357       // Process available header parts
00358       if (header_length >= sizeof(byteOrder))
00359       {
00360          fread(&byteOrder, sizeof(byteOrder), 1, mIndexStream);
00361          mIndexSwapByteOrder = (byteOrder == 0x78563412);
00362          header_length -= sizeof(byteOrder);
00363 
00364          if (header_length >= sizeof(sizeOfLong))
00365          {
00366             fread(&sizeOfLong, sizeof(sizeOfLong), 1, mIndexStream);
00367             if (mIndexSwapByteOrder)
00368                sizeOfLong = kmail_swap_32(sizeOfLong);
00369             mIndexSizeOfLong = sizeOfLong;
00370             header_length -= sizeof(sizeOfLong);
00371             needs_update = false;
00372          }
00373       }
00374       if (needs_update || mIndexSwapByteOrder || (mIndexSizeOfLong != sizeof(long)))
00375     setDirty( true );
00376       // Seek to end of header
00377       fseek(mIndexStream, endOfHeader, SEEK_SET );
00378 
00379       if (mIndexSwapByteOrder)
00380          kdDebug(5006) << "Index File has byte order swapped!" << endl;
00381       if (mIndexSizeOfLong != sizeof(long))
00382          kdDebug(5006) << "Index File sizeOfLong is " << mIndexSizeOfLong << " while sizeof(long) is " << sizeof(long) << " !" << endl;
00383 
00384   }
00385   return true;
00386 }
00387 
00388 
00389 #ifdef HAVE_MMAP
00390 bool KMFolderIndex::updateIndexStreamPtr(bool just_close)
00391 #else
00392 bool KMFolderIndex::updateIndexStreamPtr(bool)
00393 #endif
00394 {
00395     // We touch the folder, otherwise the index is regenerated, if KMail is
00396     // running, while the clock switches from daylight savings time to normal time
00397     utime(QFile::encodeName(location()), 0);
00398     utime(QFile::encodeName(indexLocation()), 0);
00399     utime(QFile::encodeName( KMMsgDict::getFolderIdsLocation( *this ) ), 0);
00400 
00401   mIndexSwapByteOrder = false;
00402 #ifdef HAVE_MMAP
00403     if(just_close) {
00404     if(mIndexStreamPtr)
00405         munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
00406     mIndexStreamPtr = 0;
00407     mIndexStreamPtrLength = 0;
00408     return true;
00409     }
00410 
00411     assert(mIndexStream);
00412     struct stat stat_buf;
00413     if(fstat(fileno(mIndexStream), &stat_buf) == -1) {
00414     if(mIndexStreamPtr)
00415         munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
00416     mIndexStreamPtr = 0;
00417     mIndexStreamPtrLength = 0;
00418     return false;
00419     }
00420     if(mIndexStreamPtr)
00421     munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
00422     mIndexStreamPtrLength = stat_buf.st_size;
00423     mIndexStreamPtr = (uchar *)mmap(0, mIndexStreamPtrLength, PROT_READ, MAP_SHARED,
00424                     fileno(mIndexStream), 0);
00425     if(mIndexStreamPtr == MAP_FAILED) {
00426     mIndexStreamPtr = 0;
00427     mIndexStreamPtrLength = 0;
00428     return false;
00429     }
00430 #endif
00431     return true;
00432 }
00433 
00434 
00435 KMFolderIndex::IndexStatus KMFolderIndex::indexStatus()
00436 {
00437     QFileInfo contInfo(location());
00438     QFileInfo indInfo(indexLocation());
00439 
00440     if (!contInfo.exists()) return KMFolderIndex::IndexOk;
00441     if (!indInfo.exists()) return KMFolderIndex::IndexMissing;
00442 
00443     return ( contInfo.lastModified() > indInfo.lastModified() )
00444         ? KMFolderIndex::IndexTooOld
00445         : KMFolderIndex::IndexOk;
00446 }
00447 
00448 void KMFolderIndex::clearIndex(bool autoDelete, bool syncDict)
00449 {
00450     mMsgList.clear(autoDelete, syncDict);
00451 }
00452 
00453 
00454 void KMFolderIndex::truncateIndex()
00455 {
00456   if ( mHeaderOffset )
00457     truncate(QFile::encodeName(indexLocation()), mHeaderOffset);
00458   else
00459     // The index file wasn't opened, so we don't know the header offset.
00460     // So let's just create a new empty index.
00461     writeIndex( true );
00462 }
00463 
00464 void KMFolderIndex::fillMessageDict()
00465 {
00466   open("fillDict");
00467   for (unsigned int idx = 0; idx < mMsgList.high(); idx++)
00468     if ( mMsgList.at( idx ) )
00469       KMMsgDict::mutableInstance()->insert(0, mMsgList.at( idx ), idx);
00470   close("fillDict");
00471 }
00472 
00473 
00474 KMMsgInfo* KMFolderIndex::setIndexEntry( int idx, KMMessage *msg )
00475 {
00476   KMMsgInfo *msgInfo = msg->msgInfo();
00477   if ( !msgInfo )
00478     msgInfo = new KMMsgInfo( folder() );
00479 
00480   *msgInfo = *msg;
00481   mMsgList.set( idx, msgInfo );
00482   msg->setMsgInfo( 0 );
00483   delete msg;
00484   return msgInfo;
00485 }
00486 
00487 void KMFolderIndex::recreateIndex()
00488 {
00489   kapp->setOverrideCursor(KCursor::arrowCursor());
00490   KMessageBox::error(0,
00491        i18n("The mail index for '%1' is corrupted and will be regenerated now, "
00492             "but some information, including status flags, will be lost.").arg(name()));
00493   kapp->restoreOverrideCursor();
00494   createIndexFromContents();
00495   readIndex();
00496 }
00497 
00498 
00499 #include "kmfolderindex.moc"