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