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