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