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