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