kdecore Library API Documentation

kconfigbackend.cpp

00001 /*
00002   This file is part of the KDE libraries
00003   Copyright (c) 1999 Preston Brown <pbrown@kde.org>
00004   Copyright (c) 1997-1999 Matthias Kalle Dalheimer <kalle@kde.org>
00005 
00006   This library is free software; you can redistribute it and/or
00007   modify it under the terms of the GNU Library General Public
00008   License as published by the Free Software Foundation; either
00009   version 2 of the License, or (at your option) any later version.
00010 
00011   This library is distributed in the hope that it will be useful,
00012   but WITHOUT ANY WARRANTY; without even the implied warranty of
00013   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014   Library General Public License for more details.
00015 
00016   You should have received a copy of the GNU Library General Public License
00017   along with this library; see the file COPYING.LIB.  If not, write to
00018   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00019   Boston, MA 02111-1307, USA.
00020 */
00021 
00022 #include <config.h>
00023 
00024 #include <unistd.h>
00025 #include <ctype.h>
00026 #ifdef HAVE_SYS_MMAN_H
00027 #include <sys/mman.h>
00028 #endif
00029 #include <sys/types.h>
00030 #ifdef HAVE_SYS_STAT_H
00031 #include <sys/stat.h>
00032 #endif
00033 #include <fcntl.h>
00034 #include <signal.h>
00035 #include <setjmp.h>
00036 
00037 #include <qdir.h>
00038 #include <qfileinfo.h>
00039 #include <qtextcodec.h>
00040 #include <qtextstream.h>
00041 
00042 #include "kconfigbackend.h"
00043 #include "kconfigbase.h"
00044 #include <kapplication.h>
00045 #include <kglobal.h>
00046 #include <kprocess.h>
00047 #include <klocale.h>
00048 #include <kstandarddirs.h>
00049 #include <ksavefile.h>
00050 #include <kurl.h>
00051 #include <kde_file.h>
00052 
00053 extern bool checkAccess(const QString& pathname, int mode);
00054 /* translate escaped escape sequences to their actual values. */
00055 static QCString printableToString(const char *str, int l)
00056 {
00057   // Strip leading white-space.
00058   while((l>0) &&
00059         ((*str == ' ') || (*str == '\t') || (*str == '\r')))
00060   {
00061      str++; l--;
00062   }
00063 
00064   // Strip trailing white-space.
00065   while((l>0) &&
00066         ((str[l-1] == ' ') || (str[l-1] == '\t') || (str[l-1] == '\r')))
00067   {
00068      l--;
00069   }
00070 
00071   QCString result(l + 1);
00072   char *r = result.data();
00073 
00074   for(int i = 0; i < l;i++, str++)
00075   {
00076      if (*str == '\\')
00077      {
00078         i++, str++;
00079         if (i >= l) // End of line. (Line ends with single slash)
00080         {
00081            *r++ = '\\';
00082            break;
00083         }
00084         switch(*str)
00085         {
00086            case 's':
00087               *r++ = ' ';
00088               break;
00089            case 't':
00090               *r++ = '\t';
00091               break;
00092            case 'n':
00093               *r++ = '\n';
00094               break;
00095            case 'r':
00096               *r++ = '\r';
00097               break;
00098            case '\\':
00099               *r++ = '\\';
00100               break;
00101            default:
00102               *r++ = '\\';
00103               *r++ = *str;
00104         }
00105      }
00106      else
00107      {
00108         *r++ = *str;
00109      }
00110   }
00111   result.truncate(r-result.data());
00112   return result;
00113 }
00114 
00115 static QCString stringToPrintable(const QCString& str){
00116   QCString result(str.length()*2); // Maximum 2x as long as source string
00117   register char *r = result.data();
00118   register char *s = str.data();
00119 
00120   if (!s) return QCString("");
00121 
00122   // Escape leading space
00123   if (*s == ' ')
00124   {
00125      *r++ = '\\'; *r++ = 's';
00126      s++;
00127   }
00128 
00129   if (*s)
00130   {
00131    while(*s)
00132    {
00133     if (*s == '\n')
00134     {
00135       *r++ = '\\'; *r++ = 'n';
00136     }
00137     else if (*s == '\t')
00138     {
00139       *r++ = '\\'; *r++ = 't';
00140     }
00141     else if (*s == '\r')
00142     {
00143       *r++ = '\\'; *r++ = 'r';
00144     }
00145     else if (*s == '\\')
00146     {
00147       *r++ = '\\'; *r++ = '\\';
00148     }
00149     else
00150     {
00151       *r++ = *s;
00152     }
00153     s++;
00154    }
00155    // Escape trailing space
00156    if (*(r-1) == ' ')
00157    {
00158       *(r-1) = '\\'; *r++ = 's';
00159    }
00160   }
00161 
00162   result.truncate(r - result.data());
00163   return result;
00164 }
00165 
00166 static QCString decodeGroup(const char*s, int l)
00167 {
00168   QCString result(l);
00169   register char *r = result.data();
00170 
00171   l--; // Correct for trailing \0
00172   while(l)
00173   {
00174     if ((*s == '[') && (l > 1))
00175     {
00176        if ((*(s+1) == '['))
00177        {
00178           l--;
00179           s++;
00180        }
00181     }
00182     if ((*s == ']') && (l > 1))
00183     {
00184        if ((*(s+1) == ']'))
00185        {
00186           l--;
00187           s++;
00188        }
00189     }
00190     *r++ = *s++;
00191     l--;
00192   }
00193   result.truncate(r - result.data());
00194   return result;
00195 }
00196 
00197 static QCString encodeGroup(const QCString &str)
00198 {
00199   int l = str.length();
00200   QCString result(l*2+1);
00201   register char *r = result.data();
00202   register char *s = str.data();
00203   while(l)
00204   {
00205     if ((*s == '[') || (*s == ']'))
00206        *r++ = *s;
00207     *r++ = *s++;
00208     l--;
00209   }
00210   result.truncate(r - result.data());
00211   return result;
00212 }
00213 
00214 class KConfigBackEnd::KConfigBackEndPrivate
00215 {
00216 public:
00217    QDateTime localLastModified;
00218    uint      localLastSize;
00219    KLockFile::Ptr localLockFile;
00220    KLockFile::Ptr globalLockFile;
00221 };
00222 
00223 void KConfigBackEnd::changeFileName(const QString &_fileName,
00224                                     const char * _resType,
00225                                     bool _useKDEGlobals)
00226 {
00227    mfileName = _fileName;
00228    resType = _resType;
00229    useKDEGlobals = _useKDEGlobals;
00230    if (mfileName.isEmpty())
00231       mLocalFileName = QString::null;
00232    else if (!QDir::isRelativePath(mfileName))
00233       mLocalFileName = mfileName;
00234    else
00235       mLocalFileName = KGlobal::dirs()->saveLocation(resType) + mfileName;
00236 
00237    if (useKDEGlobals)
00238       mGlobalFileName = KGlobal::dirs()->saveLocation("config") +
00239           QString::fromLatin1("kdeglobals");
00240    else
00241       mGlobalFileName = QString::null;
00242 
00243    d->localLastModified = QDateTime();
00244    d->localLastSize = 0;
00245    d->localLockFile = 0;
00246    d->globalLockFile = 0;
00247 }
00248 
00249 KLockFile::Ptr KConfigBackEnd::lockFile(bool bGlobal)
00250 {
00251    if (bGlobal)
00252    {
00253       if (d->globalLockFile)
00254          return d->globalLockFile;
00255       
00256       if (!mGlobalFileName.isEmpty())
00257       {
00258          d->globalLockFile = new KLockFile(mGlobalFileName+".lock");
00259          return d->globalLockFile;
00260       }
00261    }
00262    else
00263    {
00264       if (d->localLockFile)
00265          return d->localLockFile;
00266       
00267       if (!mLocalFileName.isEmpty())
00268       {
00269          d->localLockFile = new KLockFile(mLocalFileName+".lock");
00270          return d->localLockFile;
00271       }
00272    }
00273    return 0;
00274 }
00275 
00276 KConfigBackEnd::KConfigBackEnd(KConfigBase *_config,
00277                    const QString &_fileName,
00278                    const char * _resType,
00279                    bool _useKDEGlobals)
00280   : pConfig(_config), bFileImmutable(false), mConfigState(KConfigBase::NoAccess), mFileMode(-1)
00281 {
00282    d = new KConfigBackEndPrivate;
00283    changeFileName(_fileName, _resType, _useKDEGlobals);
00284 }
00285 
00286 KConfigBackEnd::~KConfigBackEnd()
00287 {
00288    delete d;
00289 }
00290 
00291 void KConfigBackEnd::setFileWriteMode(int mode)
00292 {
00293   mFileMode = mode;
00294 }
00295 
00296 bool KConfigINIBackEnd::parseConfigFiles()
00297 {
00298   // Check if we can write to the local file.
00299   mConfigState = KConfigBase::ReadOnly;
00300   if (!mLocalFileName.isEmpty() && !pConfig->isReadOnly())
00301   {
00302      if (checkAccess(mLocalFileName, W_OK))
00303      {
00304         mConfigState = KConfigBase::ReadWrite;
00305      }
00306      else
00307      {
00308         // Create the containing dir, maybe it wasn't there
00309         KURL path;
00310         path.setPath(mLocalFileName);
00311         QString dir=path.directory();
00312         KStandardDirs::makeDir(dir);
00313 
00314         if (checkAccess(mLocalFileName, W_OK))
00315         {
00316            mConfigState = KConfigBase::ReadWrite;
00317         }
00318      }
00319      QFileInfo info(mLocalFileName);
00320      d->localLastModified = info.lastModified();
00321      d->localLastSize = info.size();
00322   }
00323 
00324   // Parse all desired files from the least to the most specific.
00325   bFileImmutable = false;
00326 
00327   // Parse the general config files
00328   if (useKDEGlobals) {
00329     QStringList kdercs = KGlobal::dirs()->
00330       findAllResources("config", QString::fromLatin1("kdeglobals"));
00331 
00332 #ifdef Q_WS_WIN
00333     QString etc_kderc = QFile::decodeName( QCString(getenv("WINDIR")) + "\\kderc" );
00334 #else
00335     QString etc_kderc = QString::fromLatin1("/etc/kderc");
00336 #endif
00337 
00338     if (checkAccess(etc_kderc, R_OK))
00339       kdercs += etc_kderc;
00340 
00341     kdercs += KGlobal::dirs()->
00342       findAllResources("config", QString::fromLatin1("system.kdeglobals"));
00343 
00344     QStringList::ConstIterator it;
00345 
00346     for (it = kdercs.fromLast(); it != kdercs.end(); --it) {
00347 
00348       QFile aConfigFile( *it );
00349       if (!aConfigFile.open( IO_ReadOnly ))
00350        continue;
00351       parseSingleConfigFile( aConfigFile, 0L, true, (*it != mGlobalFileName) );
00352       aConfigFile.close();
00353       if (bFileImmutable)
00354          break;
00355     }
00356   }
00357 
00358   bool bReadFile = !mfileName.isEmpty();
00359   while(bReadFile) {
00360     bReadFile = false;
00361     QString bootLanguage;
00362     if (useKDEGlobals && localeString.isEmpty() && !KGlobal::_locale) {
00363        // Boot strap language
00364        bootLanguage = KLocale::_initLanguage(pConfig);
00365        setLocaleString(bootLanguage.utf8());
00366     }
00367 
00368     bFileImmutable = false;
00369     QStringList list;
00370     if ( !QDir::isRelativePath(mfileName) )
00371        list << mfileName;
00372     else
00373        list = KGlobal::dirs()->findAllResources(resType, mfileName);
00374 
00375     QStringList::ConstIterator it;
00376 
00377     for (it = list.fromLast(); it != list.end(); --it) {
00378 
00379       QFile aConfigFile( *it );
00380       // we can already be sure that this file exists
00381       bool bIsLocal = (*it == mLocalFileName);
00382       if (aConfigFile.open( IO_ReadOnly )) {
00383          parseSingleConfigFile( aConfigFile, 0L, false, !bIsLocal );
00384          aConfigFile.close();
00385          if (bFileImmutable)
00386             break;
00387       }
00388     }
00389     if (KGlobal::dirs()->isRestrictedResource(resType, mfileName))
00390        bFileImmutable = true;
00391     QString currentLanguage;
00392     if (!bootLanguage.isEmpty())
00393     {
00394        currentLanguage = KLocale::_initLanguage(pConfig);
00395        // If the file changed the language, we need to read the file again
00396        // with the new language setting.
00397        if (bootLanguage != currentLanguage)
00398        {
00399           bReadFile = true;
00400           setLocaleString(currentLanguage.utf8());
00401        }
00402     }
00403   }
00404   if (bFileImmutable)
00405      mConfigState = KConfigBase::ReadOnly;
00406 
00407   return true;
00408 }
00409 
00410 #ifdef HAVE_MMAP
00411 #ifdef SIGBUS
00412 static sigjmp_buf mmap_jmpbuf;
00413 struct sigaction mmap_old_sigact;
00414 
00415 extern "C" {
00416    static void mmap_sigbus_handler(int)
00417    {
00418       siglongjmp (mmap_jmpbuf, 1);
00419    }
00420 }
00421 #endif
00422 #endif
00423 
00424 extern bool kde_kiosk_exception;
00425 
00426 void KConfigINIBackEnd::parseSingleConfigFile(QFile &rFile,
00427                           KEntryMap *pWriteBackMap,
00428                           bool bGlobal, bool bDefault)
00429 {
00430    const char *s; // May get clobbered by sigsetjump, but we don't use them afterwards.
00431    const char *eof; // May get clobbered by sigsetjump, but we don't use them afterwards.
00432    QByteArray data;
00433 
00434    if (!rFile.isOpen()) // come back, if you have real work for us ;->
00435       return;
00436 
00437    //using kdDebug() here leads to an infinite loop
00438    //remove this for the release, aleXXX
00439    //qWarning("Parsing %s, global = %s default = %s",
00440    //           rFile.name().latin1(), bGlobal ? "true" : "false", bDefault ? "true" : "false");
00441 
00442    QCString aCurrentGroup("<default>");
00443 
00444    unsigned int ll = localeString.length();
00445 
00446 #ifdef HAVE_MMAP
00447    static volatile const char *map;
00448    map = ( const char* ) mmap(0, rFile.size(), PROT_READ, MAP_PRIVATE,
00449                                           rFile.handle(), 0);
00450 
00451    if ( map != MAP_FAILED )
00452    {
00453       s = (const char*) map;
00454       eof = s + rFile.size();
00455 
00456 #ifdef SIGBUS
00457       struct sigaction act;
00458       act.sa_handler = mmap_sigbus_handler;
00459       sigemptyset( &act.sa_mask );
00460 #ifdef SA_ONESHOT
00461       act.sa_flags = SA_ONESHOT;
00462 #else
00463       act.sa_flags = SA_RESETHAND;
00464 #endif      
00465       sigaction( SIGBUS, &act, &mmap_old_sigact );
00466 
00467       if (sigsetjmp (mmap_jmpbuf, 1))
00468       {
00469 qWarning("SIGBUS while reading %s", rFile.name().latin1());
00470          munmap(( char* )map, rFile.size());
00471          sigaction (SIGBUS, &mmap_old_sigact, 0);
00472          return;
00473       }
00474 #endif
00475    }
00476    else
00477 #endif
00478    {
00479       rFile.at(0);
00480       data = rFile.readAll();
00481       s = data.data();
00482       eof = s + data.size();
00483    }
00484 
00485    bool fileOptionImmutable = false;
00486    bool groupOptionImmutable = false;
00487    bool groupSkip = false;
00488 
00489    int line = 0;
00490    for(; s < eof; s++)
00491    {
00492       line++;
00493 
00494       while((s < eof) && isspace(*s) && (*s != '\n'))
00495          s++; //skip leading whitespace, shouldn't happen too often
00496 
00497       //skip empty lines, lines starting with #
00498       if ((s < eof) && ((*s == '\n') || (*s == '#')))
00499       {
00500     sktoeol:    //skip till end-of-line
00501          while ((s < eof) && (*s != '\n'))
00502             s++;
00503          continue; // Empty or comment or no keyword
00504       }
00505       const char *startLine = s;
00506 
00507       if (*s == '[')  //group
00508       {
00509          // In a group [[ and ]] have a special meaning
00510          while ((s < eof) && (*s != '\n')) 
00511          {
00512             if (*s == ']')
00513             {
00514                if ((s+1 < eof) && (*(s+1) == ']'))
00515                   s++; // Skip "]]"
00516                else
00517                   break;
00518             }
00519 
00520             s++; // Search till end of group
00521          }
00522          const char *e = s;
00523          while ((s < eof) && (*s != '\n')) s++; // Search till end of line / end of file
00524          if ((e >= eof) || (*e != ']'))
00525          {
00526             fprintf(stderr, "Invalid group header at %s:%d\n", rFile.name().latin1(), line);
00527             continue;
00528          }
00529          // group found; get the group name by taking everything in
00530          // between the brackets
00531          if ((e-startLine == 3) &&
00532              (startLine[1] == '$') &&
00533              (startLine[2] == 'i'))
00534          {
00535             if (!kde_kiosk_exception)
00536                fileOptionImmutable = true;
00537             continue;
00538          }
00539 
00540          aCurrentGroup = decodeGroup(startLine + 1, e - startLine);
00541          //cout<<"found group ["<<aCurrentGroup<<"]"<<endl;
00542 
00543          // Backwards compatibility
00544          if (aCurrentGroup == "KDE Desktop Entry")
00545             aCurrentGroup = "Desktop Entry";
00546 
00547          groupOptionImmutable = fileOptionImmutable;
00548 
00549          e++;
00550          if ((e+2 < eof) && (*e++ == '[') && (*e++ == '$')) // Option follows
00551          {
00552             if ((*e == 'i') && !kde_kiosk_exception)
00553             {
00554                groupOptionImmutable = true;
00555             }
00556          }
00557 
00558          KEntryKey groupKey(aCurrentGroup, 0);
00559          KEntry entry = pConfig->lookupData(groupKey);
00560          groupSkip = entry.bImmutable;
00561 
00562          if (groupSkip && !bDefault)
00563             continue;
00564 
00565          entry.bImmutable |= groupOptionImmutable;
00566          pConfig->putData(groupKey, entry, false);
00567 
00568          if (pWriteBackMap)
00569          {
00570             // add the special group key indicator
00571             (*pWriteBackMap)[groupKey] = entry;
00572          }
00573 
00574          continue;
00575       }
00576       if (groupSkip && !bDefault)
00577         goto sktoeol; // Skip entry
00578 
00579       bool optionImmutable = groupOptionImmutable;
00580       bool optionDeleted = false;
00581       bool optionExpand = false;
00582       const char *endOfKey = 0, *locale = 0, *elocale = 0;
00583       for (; (s < eof) && (*s != '\n'); s++)
00584       {
00585          if (*s == '=') //find the equal sign
00586          {
00587         if (!endOfKey)
00588             endOfKey = s;
00589             goto haveeq;
00590      }
00591      if (*s == '[') //find the locale or options.
00592      {
00593             const char *option;
00594             const char *eoption;
00595         endOfKey = s;
00596         option = ++s;
00597         for (;; s++)
00598         {
00599         if ((s >= eof) || (*s == '\n') || (*s == '=')) {
00600             fprintf(stderr, "Invalid entry (missing ']') at %s:%d\n", rFile.name().latin1(), line);
00601             goto sktoeol;
00602         }
00603         if (*s == ']')
00604             break;
00605         }
00606         eoption = s;
00607             if (*option != '$')
00608             {
00609               // Locale
00610               if (locale) {
00611         fprintf(stderr, "Invalid entry (second locale!?) at %s:%d\n", rFile.name().latin1(), line);
00612         goto sktoeol;
00613               }
00614               locale = option;
00615               elocale = eoption;
00616             }
00617             else
00618             {
00619               // Option
00620               while (option < eoption)
00621               {
00622                  option++;
00623                  if ((*option == 'i') && !kde_kiosk_exception)
00624                     optionImmutable = true;
00625                  else if (*option == 'e')
00626                     optionExpand = true;
00627                  else if (*option == 'd')
00628                  {
00629                     optionDeleted = true;
00630                     goto haveeq;
00631                  }
00632          else if (*option == ']')
00633             break;
00634               }
00635             }
00636          }
00637       }
00638       fprintf(stderr, "Invalid entry (missing '=') at %s:%d\n", rFile.name().latin1(), line);
00639       continue;
00640 
00641    haveeq:
00642       for (endOfKey--; ; endOfKey--)
00643       {
00644      if (endOfKey < startLine)
00645      {
00646        fprintf(stderr, "Invalid entry (empty key) at %s:%d\n", rFile.name().latin1(), line);
00647        goto sktoeol;
00648      }
00649      if (!isspace(*endOfKey))
00650         break;
00651       }
00652 
00653       const char *st = ++s;
00654       while ((s < eof) && (*s != '\n')) s++; // Search till end of line / end of file
00655 
00656       if (locale) {
00657           unsigned int cl = static_cast<unsigned int>(elocale - locale);
00658           if ((ll != cl) || memcmp(locale, localeString.data(), ll))
00659           {
00660               // backward compatibility. C == en_US
00661               if ( cl != 1 || ll != 5 || *locale != 'C' || memcmp(localeString.data(), "en_US", 5)) {
00662                   //cout<<"mismatched locale '"<<QCString(locale, elocale-locale +1)<<"'"<<endl;
00663                   // We can ignore this one
00664                   if (!pWriteBackMap)
00665                       continue; // We just ignore it
00666                   // We just store it as is to be able to write it back later.
00667                   endOfKey = elocale;
00668                   locale = 0;
00669               }
00670           }
00671       }
00672 
00673       // insert the key/value line
00674       QCString key(startLine, endOfKey - startLine + 2);
00675       QCString val = printableToString(st, s - st);
00676       //qDebug("found key '%s' with value '%s'", key.data(), val.data());
00677 
00678       KEntryKey aEntryKey(aCurrentGroup, key);
00679       aEntryKey.bLocal = (locale != 0);
00680       aEntryKey.bDefault = bDefault;
00681 
00682       KEntry aEntry;
00683       aEntry.mValue = val;
00684       aEntry.bGlobal = bGlobal;
00685       aEntry.bImmutable = optionImmutable;
00686       aEntry.bDeleted = optionDeleted;
00687       aEntry.bExpand = optionExpand;
00688       aEntry.bNLS = (locale != 0);
00689 
00690       if (pWriteBackMap) {
00691          // don't insert into the config object but into the temporary
00692          // scratchpad map
00693          pWriteBackMap->insert(aEntryKey, aEntry);
00694       } else {
00695          // directly insert value into config object
00696          // no need to specify localization; if the key we just
00697          // retrieved was localized already, no need to localize it again.
00698          pConfig->putData(aEntryKey, aEntry, false);
00699       }
00700    }
00701    if (fileOptionImmutable)
00702       bFileImmutable = true;
00703 
00704 #ifdef HAVE_MMAP
00705    if (map)
00706    {
00707       munmap(( char* )map, rFile.size());
00708 #ifdef SIGBUS
00709       sigaction (SIGBUS, &mmap_old_sigact, 0);
00710 #endif
00711    }
00712 #endif
00713 }
00714 
00715 
00716 void KConfigINIBackEnd::sync(bool bMerge)
00717 {
00718   // write-sync is only necessary if there are dirty entries
00719   if (!pConfig->isDirty())
00720     return;
00721 
00722   bool bEntriesLeft = true;
00723 
00724   // find out the file to write to (most specific writable file)
00725   // try local app-specific file first
00726 
00727   if (!mfileName.isEmpty()) {
00728     // Create the containing dir if needed
00729     if ((resType!="config") && !QDir::isRelativePath(mLocalFileName))
00730     {
00731        KURL path;
00732        path.setPath(mLocalFileName);
00733        QString dir=path.directory();
00734        KStandardDirs::makeDir(dir);
00735     }
00736 
00737     // Can we allow the write? We can, if the program
00738     // doesn't run SUID. But if it runs SUID, we must
00739     // check if the user would be allowed to write if
00740     // it wasn't SUID.
00741     if (checkAccess(mLocalFileName, W_OK)) {
00742       // File is writable
00743       KLockFile::Ptr lf;
00744 
00745       bool mergeLocalFile = bMerge;
00746       // Check if the file has been updated since.
00747       if (mergeLocalFile)
00748       {
00749          lf = lockFile(false); // Lock file for local file
00750          if (lf && lf->isLocked())
00751             lf = 0; // Already locked, we don't need to lock/unlock again
00752 
00753          if (lf) 
00754          {
00755             lf->lock( KLockFile::LockForce );
00756             // But what if the locking failed? Ignore it for now...
00757          }
00758          
00759          QFileInfo info(mLocalFileName);
00760          if ((d->localLastSize == info.size()) &&
00761              (d->localLastModified == info.lastModified()))
00762          {
00763             // Not changed, don't merge.
00764             mergeLocalFile = false;
00765          }
00766          else
00767          {
00768             // Changed...
00769             d->localLastModified = QDateTime();
00770             d->localLastSize = 0;
00771          }
00772       }
00773 
00774       bEntriesLeft = writeConfigFile( mLocalFileName, false, mergeLocalFile );
00775       
00776       // Only if we didn't have to merge anything can we use our in-memory state
00777       // the next time around. Otherwise the config-file may contain entries
00778       // that are different from our in-memory state which means we will have to 
00779       // do a merge from then on. 
00780       // We do not automatically update the in-memory state with the on-disk 
00781       // state when writing the config to disk. We only do so when 
00782       // KCOnfig::reparseConfiguration() is called.
00783       // For KDE 4.0 we may wish to reconsider that.
00784       if (!mergeLocalFile)
00785       {
00786          QFileInfo info(mLocalFileName);
00787          d->localLastModified = info.lastModified();
00788          d->localLastSize = info.size();
00789       }
00790       if (lf) lf->unlock();
00791     }
00792   }
00793 
00794   // only write out entries to the kdeglobals file if there are any
00795   // entries marked global (indicated by bEntriesLeft) and
00796   // the useKDEGlobals flag is set.
00797   if (bEntriesLeft && useKDEGlobals) {
00798 
00799     // can we allow the write? (see above)
00800     if (checkAccess ( mGlobalFileName, W_OK )) {
00801       KLockFile::Ptr lf = lockFile(true); // Lock file for global file
00802       if (lf && lf->isLocked())
00803          lf = 0; // Already locked, we don't need to lock/unlock again
00804 
00805       if (lf) 
00806       {
00807          lf->lock( KLockFile::LockForce );
00808          // But what if the locking failed? Ignore it for now...
00809       }
00810       writeConfigFile( mGlobalFileName, true, bMerge ); // Always merge
00811       if (lf) lf->unlock();
00812     }
00813   }
00814 
00815 }
00816 
00817 static void writeEntries(FILE *pStream, const KEntryMap& entryMap, bool defaultGroup, bool &firstEntry, const QCString &localeString)
00818 {
00819   // now write out all other groups.
00820   QCString currentGroup;
00821   for (KEntryMapConstIterator aIt = entryMap.begin();
00822        aIt != entryMap.end(); ++aIt)
00823   {
00824      const KEntryKey &key = aIt.key();
00825 
00826      // Either proces the default group or all others
00827      if ((key.mGroup != "<default>") == defaultGroup)
00828         continue; // Skip
00829 
00830      // Skip default values and group headers.
00831      if ((key.bDefault) || key.mKey.isEmpty())
00832         continue; // Skip
00833 
00834      const KEntry &currentEntry = *aIt;
00835 
00836      KEntryMapConstIterator aTestIt = aIt;
00837      ++aTestIt;
00838      bool hasDefault = (aTestIt != entryMap.end());
00839      if (hasDefault)
00840      {
00841         const KEntryKey &defaultKey = aTestIt.key();
00842         if ((!defaultKey.bDefault) ||
00843             (defaultKey.mKey != key.mKey) ||
00844             (defaultKey.mGroup != key.mGroup) ||
00845             (defaultKey.bLocal != key.bLocal))
00846            hasDefault = false;
00847      }
00848 
00849 
00850      if (hasDefault)
00851      {
00852         // Entry had a default value
00853         if ((currentEntry.mValue == (*aTestIt).mValue) &&
00854             (currentEntry.bDeleted == (*aTestIt).bDeleted))
00855            continue; // Same as default, don't write.
00856      }
00857      else
00858      {
00859         // Entry had no default value.
00860         if (currentEntry.bDeleted)
00861            continue; // Don't write deleted entries if there is no default.
00862      }
00863 
00864      if (!defaultGroup && (currentGroup != key.mGroup)) {
00865     if (!firstEntry)
00866         fprintf(pStream, "\n");
00867     currentGroup = key.mGroup;
00868     fprintf(pStream, "[%s]\n", encodeGroup(currentGroup).data());
00869      }
00870 
00871      firstEntry = false;
00872      // it is data for a group
00873      fputs(key.mKey.data(), pStream); // Key
00874 
00875      if ( currentEntry.bNLS )
00876      {
00877         fputc('[', pStream);
00878         fputs(localeString.data(), pStream);
00879         fputc(']', pStream);
00880      }
00881 
00882      if (currentEntry.bDeleted)
00883      {
00884         fputs("[$d]\n", pStream); // Deleted
00885      }
00886      else
00887      {
00888         if (currentEntry.bImmutable || currentEntry.bExpand)
00889         {
00890            fputc('[', pStream);
00891            fputc('$', pStream);
00892            if (currentEntry.bImmutable)
00893               fputc('i', pStream);
00894            if (currentEntry.bExpand)
00895               fputc('e', pStream);
00896 
00897            fputc(']', pStream);
00898         }
00899         fputc('=', pStream);
00900         fputs(stringToPrintable(currentEntry.mValue).data(), pStream);
00901         fputc('\n', pStream);
00902      }
00903   } // for loop
00904 }
00905 
00906 bool KConfigINIBackEnd::getEntryMap(KEntryMap &aTempMap, bool bGlobal,
00907                                     QFile *mergeFile)
00908 {
00909   bool bEntriesLeft = false;
00910   bFileImmutable = false;
00911 
00912   // Read entries from disk
00913   if (mergeFile && mergeFile->open(IO_ReadOnly))
00914   {
00915      // fill the temporary structure with entries from the file
00916      parseSingleConfigFile(*mergeFile, &aTempMap, bGlobal, false );
00917 
00918      if (bFileImmutable) // File has become immutable on disk
00919         return bEntriesLeft;
00920   }
00921 
00922   KEntryMap aMap = pConfig->internalEntryMap();
00923 
00924   // augment this structure with the dirty entries from the config object
00925   for (KEntryMapIterator aIt = aMap.begin();
00926        aIt != aMap.end(); ++aIt)
00927   {
00928     const KEntry &currentEntry = *aIt;
00929     if(aIt.key().bDefault)
00930     {
00931        aTempMap.replace(aIt.key(), currentEntry);
00932        continue;
00933     }
00934 
00935     if (mergeFile && !currentEntry.bDirty)
00936        continue;
00937 
00938     // only write back entries that have the same
00939     // "globality" as the file
00940     if (currentEntry.bGlobal != bGlobal)
00941     {
00942        // wrong "globality" - might have to be saved later
00943        bEntriesLeft = true;
00944        continue;
00945     }
00946 
00947     // put this entry from the config object into the
00948     // temporary map, possibly replacing an existing entry
00949     KEntryMapIterator aIt2 = aTempMap.find(aIt.key());
00950     if (aIt2 != aTempMap.end() && (*aIt2).bImmutable)
00951        continue; // Bail out if the on-disk entry is immutable
00952 
00953     aTempMap.insert(aIt.key(), currentEntry, true);
00954   } // loop
00955 
00956   return bEntriesLeft;
00957 }
00958 
00959 /* antlarr: KDE 4.0:  make the first parameter "const QString &" */
00960 bool KConfigINIBackEnd::writeConfigFile(QString filename, bool bGlobal,
00961                     bool bMerge)
00962 {
00963   // is the config object read-only?
00964   if (pConfig->isReadOnly())
00965     return true; // pretend we wrote it
00966 
00967   KEntryMap aTempMap;
00968   QFile *mergeFile = (bMerge ? new QFile(filename) : 0);
00969   bool bEntriesLeft = getEntryMap(aTempMap, bGlobal, mergeFile);
00970   delete mergeFile;
00971   if (bFileImmutable)
00972     return true; // pretend we wrote it
00973 
00974   // OK now the temporary map should be full of ALL entries.
00975   // write it out to disk.
00976 
00977   // Check if file exists:
00978   int fileMode = -1;
00979   bool createNew = true;
00980 
00981   KDE_struct_stat buf;
00982   if (KDE_stat(QFile::encodeName(filename), &buf) == 0)
00983   {
00984      if (buf.st_uid == getuid())
00985      {
00986         // Preserve file mode if file exists and is owned by user.
00987         fileMode = buf.st_mode & 0777;
00988      }
00989      else
00990      {
00991         // File is not owned by user:
00992         // Don't create new file but write to existing file instead.
00993         createNew = false;
00994      }
00995   }
00996 
00997   KSaveFile *pConfigFile = 0;
00998   FILE *pStream = 0;
00999 
01000   if (createNew)
01001   {
01002      pConfigFile = new KSaveFile( filename, 0600 );
01003 
01004      if (pConfigFile->status() != 0)
01005      {
01006         delete pConfigFile;
01007         return bEntriesLeft;
01008      }
01009 
01010      if (!bGlobal && (fileMode == -1))
01011         fileMode = mFileMode;
01012 
01013      if (fileMode != -1)
01014      {
01015         fchmod(pConfigFile->handle(), fileMode);
01016      }
01017 
01018      pStream = pConfigFile->fstream();
01019   }
01020   else
01021   {
01022      // Open existing file.
01023      // We use open() to ensure that we call without O_CREAT.
01024      int fd = KDE_open( QFile::encodeName(filename), O_WRONLY | O_TRUNC );
01025      if (fd < 0)
01026      {
01027         return bEntriesLeft;
01028      }
01029      pStream = KDE_fdopen( fd, "w");
01030      if (!pStream)
01031      {
01032         close(fd);
01033         return bEntriesLeft;
01034      }
01035   }
01036 
01037   writeEntries(pStream, aTempMap);
01038 
01039   if (pConfigFile)
01040   {
01041      bool bEmptyFile = (ftell(pStream) == 0);
01042      if ( bEmptyFile && ((fileMode == -1) || (fileMode == 0600)) )
01043      {
01044         // File is empty and doesn't have special permissions: delete it.
01045         ::unlink(QFile::encodeName(filename));
01046         pConfigFile->abort();
01047      }
01048      else
01049      {
01050         // Normal case: Close the file
01051         pConfigFile->close();
01052      }
01053      delete pConfigFile;
01054   }
01055   else
01056   {
01057      fclose(pStream);
01058   }
01059 
01060   return bEntriesLeft;
01061 }
01062 
01063 void KConfigINIBackEnd::writeEntries(FILE *pStream, const KEntryMap &aTempMap)
01064 {
01065   bool firstEntry = true;
01066 
01067   // Write default group
01068   ::writeEntries(pStream, aTempMap, true, firstEntry, localeString);
01069 
01070   // Write all other groups
01071   ::writeEntries(pStream, aTempMap, false, firstEntry, localeString);
01072 }
01073 
01074 void KConfigBackEnd::virtual_hook( int, void* )
01075 { /*BASE::virtual_hook( id, data );*/ }
01076 
01077 void KConfigINIBackEnd::virtual_hook( int id, void* data )
01078 { KConfigBackEnd::virtual_hook( id, data ); }
01079 
01080 bool KConfigBackEnd::checkConfigFilesWritable(bool warnUser)
01081 {
01082   // WARNING: Do NOT use the event loop as it may not exist at this time.
01083   bool allWritable = true;
01084   QString errorMsg( i18n("Will not save configuration.\n") );
01085   if ( !mLocalFileName.isEmpty() && !bFileImmutable && !checkAccess(mLocalFileName,W_OK) )
01086   {
01087     allWritable = false;
01088     errorMsg += i18n("Configuration file \"%1\" not writable.\n").arg(mLocalFileName);
01089   }
01090   // We do not have an immutability flag for kdeglobals. However, making kdeglobals mutable while making
01091   // the local config file immutable is senseless.
01092   if ( !mGlobalFileName.isEmpty() && useKDEGlobals && !bFileImmutable && !checkAccess(mGlobalFileName,W_OK) )
01093   {
01094     errorMsg += i18n("Configuration file \"%1\" not writable.\n").arg(mGlobalFileName);
01095     allWritable = false;
01096   }
01097 
01098   if (warnUser && !allWritable)
01099   {
01100     // Note: We don't ask the user if we should not ask this question again because we can't save the answer.
01101     errorMsg += i18n("Please contact your system administrator.");
01102     QString cmdToExec = KStandardDirs::findExe(QString("kdialog"));
01103     KApplication *app = kapp;
01104     if (!cmdToExec.isEmpty() && app)
01105     {
01106       KProcess lprocess;
01107       lprocess << cmdToExec << "--title" << app->instanceName() << "--msgbox" << errorMsg.local8Bit();
01108       lprocess.start( KProcess::Block );
01109     }
01110   }
01111   return allWritable;
01112 }
KDE Logo
This file is part of the documentation for kdecore Library Version 3.4.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat May 7 22:04:21 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003