kdecore Library API Documentation

kconfigbackend.cpp

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