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