kdecore Library API Documentation

kconfigbackend.cpp

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