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 00509 int line = 0; 00510 for(; s < eof; s++) 00511 { 00512 line++; 00513 00514 while((s < eof) && isspace(*s) && (*s != '\n')) 00515 s++; //skip leading whitespace, shouldn't happen too often 00516 00517 //skip empty lines, lines starting with # 00518 if ((s < eof) && ((*s == '\n') || (*s == '#'))) 00519 { 00520 sktoeol: //skip till end-of-line 00521 while ((s < eof) && (*s != '\n')) 00522 s++; 00523 continue; // Empty or comment or no keyword 00524 } 00525 const char *startLine = s; 00526 00527 if (*s == '[') //group 00528 { 00529 // In a group [[ and ]] have a special meaning 00530 while ((s < eof) && (*s != '\n')) 00531 { 00532 if (*s == ']') 00533 { 00534 if ((s+1 < eof) && (*(s+1) == ']')) 00535 s++; // Skip "]]" 00536 else 00537 break; 00538 } 00539 00540 s++; // Search till end of group 00541 } 00542 const char *e = s; 00543 while ((s < eof) && (*s != '\n')) s++; // Search till end of line / end of file 00544 if ((e >= eof) || (*e != ']')) 00545 { 00546 fprintf(stderr, "Invalid group header at %s:%d\n", rFile.name().latin1(), line); 00547 continue; 00548 } 00549 // group found; get the group name by taking everything in 00550 // between the brackets 00551 if ((e-startLine == 3) && 00552 (startLine[1] == '$') && 00553 (startLine[2] == 'i')) 00554 { 00555 if (!kde_kiosk_exception) 00556 fileOptionImmutable = true; 00557 continue; 00558 } 00559 00560 aCurrentGroup = decodeGroup(startLine + 1, e - startLine); 00561 //cout<<"found group ["<<aCurrentGroup<<"]"<<endl; 00562 00563 // Backwards compatibility 00564 if (aCurrentGroup == "KDE Desktop Entry") 00565 aCurrentGroup = "Desktop Entry"; 00566 00567 groupOptionImmutable = fileOptionImmutable; 00568 00569 e++; 00570 if ((e+2 < eof) && (*e++ == '[') && (*e++ == '$')) // Option follows 00571 { 00572 if ((*e == 'i') && !kde_kiosk_exception) 00573 { 00574 groupOptionImmutable = true; 00575 } 00576 } 00577 00578 KEntryKey groupKey(aCurrentGroup, 0); 00579 KEntry entry = pConfig->lookupData(groupKey); 00580 groupSkip = entry.bImmutable; 00581 00582 if (groupSkip && !bDefault) 00583 continue; 00584 00585 entry.bImmutable |= groupOptionImmutable; 00586 pConfig->putData(groupKey, entry, false); 00587 00588 if (pWriteBackMap) 00589 { 00590 // add the special group key indicator 00591 (*pWriteBackMap)[groupKey] = entry; 00592 } 00593 00594 continue; 00595 } 00596 if (groupSkip && !bDefault) 00597 goto sktoeol; // Skip entry 00598 00599 bool optionImmutable = groupOptionImmutable; 00600 bool optionDeleted = false; 00601 bool optionExpand = false; 00602 const char *endOfKey = 0, *locale = 0, *elocale = 0; 00603 for (; (s < eof) && (*s != '\n'); s++) 00604 { 00605 if (*s == '=') //find the equal sign 00606 { 00607 if (!endOfKey) 00608 endOfKey = s; 00609 goto haveeq; 00610 } 00611 if (*s == '[') //find the locale or options. 00612 { 00613 const char *option; 00614 const char *eoption; 00615 endOfKey = s; 00616 option = ++s; 00617 for (;; s++) 00618 { 00619 if ((s >= eof) || (*s == '\n') || (*s == '=')) { 00620 fprintf(stderr, "Invalid entry (missing ']') at %s:%d\n", rFile.name().latin1(), line); 00621 goto sktoeol; 00622 } 00623 if (*s == ']') 00624 break; 00625 } 00626 eoption = s; 00627 if (*option != '$') 00628 { 00629 // Locale 00630 if (locale) { 00631 fprintf(stderr, "Invalid entry (second locale!?) at %s:%d\n", rFile.name().latin1(), line); 00632 goto sktoeol; 00633 } 00634 locale = option; 00635 elocale = eoption; 00636 } 00637 else 00638 { 00639 // Option 00640 while (option < eoption) 00641 { 00642 option++; 00643 if ((*option == 'i') && !kde_kiosk_exception) 00644 optionImmutable = true; 00645 else if (*option == 'e') 00646 optionExpand = true; 00647 else if (*option == 'd') 00648 { 00649 optionDeleted = true; 00650 goto haveeq; 00651 } 00652 else if (*option == ']') 00653 break; 00654 } 00655 } 00656 } 00657 } 00658 fprintf(stderr, "Invalid entry (missing '=') at %s:%d\n", rFile.name().latin1(), line); 00659 continue; 00660 00661 haveeq: 00662 for (endOfKey--; ; endOfKey--) 00663 { 00664 if (endOfKey < startLine) 00665 { 00666 fprintf(stderr, "Invalid entry (empty key) at %s:%d\n", rFile.name().latin1(), line); 00667 goto sktoeol; 00668 } 00669 if (!isspace(*endOfKey)) 00670 break; 00671 } 00672 00673 const char *st = ++s; 00674 while ((s < eof) && (*s != '\n')) s++; // Search till end of line / end of file 00675 00676 if (locale) { 00677 unsigned int cl = static_cast<unsigned int>(elocale - locale); 00678 if ((ll != cl) || memcmp(locale, localeString.data(), ll)) 00679 { 00680 // backward compatibility. C == en_US 00681 if ( cl != 1 || ll != 5 || *locale != 'C' || memcmp(localeString.data(), "en_US", 5)) { 00682 //cout<<"mismatched locale '"<<QCString(locale, elocale-locale +1)<<"'"<<endl; 00683 // We can ignore this one 00684 if (!pWriteBackMap) 00685 continue; // We just ignore it 00686 // We just store it as is to be able to write it back later. 00687 endOfKey = elocale; 00688 locale = 0; 00689 } 00690 } 00691 } 00692 00693 // insert the key/value line 00694 QCString key(startLine, endOfKey - startLine + 2); 00695 QCString val = printableToString(st, s - st); 00696 //qDebug("found key '%s' with value '%s'", key.data(), val.data()); 00697 00698 KEntryKey aEntryKey(aCurrentGroup, decodeKey(key)); 00699 aEntryKey.bLocal = (locale != 0); 00700 aEntryKey.bDefault = bDefault; 00701 00702 KEntry aEntry; 00703 aEntry.mValue = val; 00704 aEntry.bGlobal = bGlobal; 00705 aEntry.bImmutable = optionImmutable; 00706 aEntry.bDeleted = optionDeleted; 00707 aEntry.bExpand = optionExpand; 00708 aEntry.bNLS = (locale != 0); 00709 00710 if (pWriteBackMap) { 00711 // don't insert into the config object but into the temporary 00712 // scratchpad map 00713 pWriteBackMap->insert(aEntryKey, aEntry); 00714 } else { 00715 // directly insert value into config object 00716 // no need to specify localization; if the key we just 00717 // retrieved was localized already, no need to localize it again. 00718 pConfig->putData(aEntryKey, aEntry, false); 00719 } 00720 } 00721 if (fileOptionImmutable) 00722 bFileImmutable = true; 00723 00724 #ifdef HAVE_MMAP 00725 if (map) 00726 { 00727 munmap(( char* )map, rFile.size()); 00728 #ifdef SIGBUS 00729 sigaction (SIGBUS, &mmap_old_sigact, 0); 00730 #endif 00731 } 00732 #endif 00733 } 00734 00735 00736 void KConfigINIBackEnd::sync(bool bMerge) 00737 { 00738 // write-sync is only necessary if there are dirty entries 00739 if (!pConfig->isDirty()) 00740 return; 00741 00742 bool bEntriesLeft = true; 00743 00744 // find out the file to write to (most specific writable file) 00745 // try local app-specific file first 00746 00747 if (!mfileName.isEmpty()) { 00748 // Create the containing dir if needed 00749 if ((resType!="config") && !QDir::isRelativePath(mLocalFileName)) 00750 { 00751 KURL path; 00752 path.setPath(mLocalFileName); 00753 QString dir=path.directory(); 00754 KStandardDirs::makeDir(dir); 00755 } 00756 00757 // Can we allow the write? We can, if the program 00758 // doesn't run SUID. But if it runs SUID, we must 00759 // check if the user would be allowed to write if 00760 // it wasn't SUID. 00761 if (checkAccess(mLocalFileName, W_OK)) { 00762 // File is writable 00763 KLockFile::Ptr lf; 00764 00765 bool mergeLocalFile = bMerge; 00766 // Check if the file has been updated since. 00767 if (mergeLocalFile) 00768 { 00769 lf = lockFile(false); // Lock file for local file 00770 if (lf && lf->isLocked()) 00771 lf = 0; // Already locked, we don't need to lock/unlock again 00772 00773 if (lf) 00774 { 00775 lf->lock( KLockFile::LockForce ); 00776 // But what if the locking failed? Ignore it for now... 00777 } 00778 00779 QFileInfo info(mLocalFileName); 00780 if ((d->localLastSize == info.size()) && 00781 (d->localLastModified == info.lastModified())) 00782 { 00783 // Not changed, don't merge. 00784 mergeLocalFile = false; 00785 } 00786 else 00787 { 00788 // Changed... 00789 d->localLastModified = QDateTime(); 00790 d->localLastSize = 0; 00791 } 00792 } 00793 00794 bEntriesLeft = writeConfigFile( mLocalFileName, false, mergeLocalFile ); 00795 00796 // Only if we didn't have to merge anything can we use our in-memory state 00797 // the next time around. Otherwise the config-file may contain entries 00798 // that are different from our in-memory state which means we will have to 00799 // do a merge from then on. 00800 // We do not automatically update the in-memory state with the on-disk 00801 // state when writing the config to disk. We only do so when 00802 // KCOnfig::reparseConfiguration() is called. 00803 // For KDE 4.0 we may wish to reconsider that. 00804 if (!mergeLocalFile) 00805 { 00806 QFileInfo info(mLocalFileName); 00807 d->localLastModified = info.lastModified(); 00808 d->localLastSize = info.size(); 00809 } 00810 if (lf) lf->unlock(); 00811 } 00812 } 00813 00814 // only write out entries to the kdeglobals file if there are any 00815 // entries marked global (indicated by bEntriesLeft) and 00816 // the useKDEGlobals flag is set. 00817 if (bEntriesLeft && useKDEGlobals) { 00818 00819 // can we allow the write? (see above) 00820 if (checkAccess ( mGlobalFileName, W_OK )) { 00821 KLockFile::Ptr lf = lockFile(true); // Lock file for global file 00822 if (lf && lf->isLocked()) 00823 lf = 0; // Already locked, we don't need to lock/unlock again 00824 00825 if (lf) 00826 { 00827 lf->lock( KLockFile::LockForce ); 00828 // But what if the locking failed? Ignore it for now... 00829 } 00830 writeConfigFile( mGlobalFileName, true, bMerge ); // Always merge 00831 if (lf) lf->unlock(); 00832 } 00833 } 00834 00835 } 00836 00837 static void writeEntries(FILE *pStream, const KEntryMap& entryMap, bool defaultGroup, bool &firstEntry, const QCString &localeString) 00838 { 00839 // now write out all other groups. 00840 QCString currentGroup; 00841 for (KEntryMapConstIterator aIt = entryMap.begin(); 00842 aIt != entryMap.end(); ++aIt) 00843 { 00844 const KEntryKey &key = aIt.key(); 00845 00846 // Either proces the default group or all others 00847 if ((key.mGroup != "<default>") == defaultGroup) 00848 continue; // Skip 00849 00850 // Skip default values and group headers. 00851 if ((key.bDefault) || key.mKey.isEmpty()) 00852 continue; // Skip 00853 00854 const KEntry ¤tEntry = *aIt; 00855 00856 KEntryMapConstIterator aTestIt = aIt; 00857 ++aTestIt; 00858 bool hasDefault = (aTestIt != entryMap.end()); 00859 if (hasDefault) 00860 { 00861 const KEntryKey &defaultKey = aTestIt.key(); 00862 if ((!defaultKey.bDefault) || 00863 (defaultKey.mKey != key.mKey) || 00864 (defaultKey.mGroup != key.mGroup) || 00865 (defaultKey.bLocal != key.bLocal)) 00866 hasDefault = false; 00867 } 00868 00869 00870 if (hasDefault) 00871 { 00872 // Entry had a default value 00873 if ((currentEntry.mValue == (*aTestIt).mValue) && 00874 (currentEntry.bDeleted == (*aTestIt).bDeleted)) 00875 continue; // Same as default, don't write. 00876 } 00877 else 00878 { 00879 // Entry had no default value. 00880 if (currentEntry.bDeleted) 00881 continue; // Don't write deleted entries if there is no default. 00882 } 00883 00884 if (!defaultGroup && (currentGroup != key.mGroup)) { 00885 if (!firstEntry) 00886 fprintf(pStream, "\n"); 00887 currentGroup = key.mGroup; 00888 fprintf(pStream, "[%s]\n", encodeGroup(currentGroup).data()); 00889 } 00890 00891 firstEntry = false; 00892 // it is data for a group 00893 fputs(encodeKey(key.mKey.data()), pStream); // Key 00894 00895 if ( currentEntry.bNLS ) 00896 { 00897 fputc('[', pStream); 00898 fputs(localeString.data(), pStream); 00899 fputc(']', pStream); 00900 } 00901 00902 if (currentEntry.bDeleted) 00903 { 00904 fputs("[$d]\n", pStream); // Deleted 00905 } 00906 else 00907 { 00908 if (currentEntry.bImmutable || currentEntry.bExpand) 00909 { 00910 fputc('[', pStream); 00911 fputc('$', pStream); 00912 if (currentEntry.bImmutable) 00913 fputc('i', pStream); 00914 if (currentEntry.bExpand) 00915 fputc('e', pStream); 00916 00917 fputc(']', pStream); 00918 } 00919 fputc('=', pStream); 00920 fputs(stringToPrintable(currentEntry.mValue).data(), pStream); 00921 fputc('\n', pStream); 00922 } 00923 } // for loop 00924 } 00925 00926 bool KConfigINIBackEnd::getEntryMap(KEntryMap &aTempMap, bool bGlobal, 00927 QFile *mergeFile) 00928 { 00929 bool bEntriesLeft = false; 00930 bFileImmutable = false; 00931 00932 // Read entries from disk 00933 if (mergeFile && mergeFile->open(IO_ReadOnly)) 00934 { 00935 // fill the temporary structure with entries from the file 00936 parseSingleConfigFile(*mergeFile, &aTempMap, bGlobal, false ); 00937 00938 if (bFileImmutable) // File has become immutable on disk 00939 return bEntriesLeft; 00940 } 00941 00942 KEntryMap aMap = pConfig->internalEntryMap(); 00943 00944 // augment this structure with the dirty entries from the config object 00945 for (KEntryMapIterator aIt = aMap.begin(); 00946 aIt != aMap.end(); ++aIt) 00947 { 00948 const KEntry ¤tEntry = *aIt; 00949 if(aIt.key().bDefault) 00950 { 00951 aTempMap.replace(aIt.key(), currentEntry); 00952 continue; 00953 } 00954 00955 if (mergeFile && !currentEntry.bDirty) 00956 continue; 00957 00958 // only write back entries that have the same 00959 // "globality" as the file 00960 if (currentEntry.bGlobal != bGlobal) 00961 { 00962 // wrong "globality" - might have to be saved later 00963 bEntriesLeft = true; 00964 continue; 00965 } 00966 00967 // put this entry from the config object into the 00968 // temporary map, possibly replacing an existing entry 00969 KEntryMapIterator aIt2 = aTempMap.find(aIt.key()); 00970 if (aIt2 != aTempMap.end() && (*aIt2).bImmutable) 00971 continue; // Bail out if the on-disk entry is immutable 00972 00973 aTempMap.insert(aIt.key(), currentEntry, true); 00974 } // loop 00975 00976 return bEntriesLeft; 00977 } 00978 00979 /* antlarr: KDE 4.0: make the first parameter "const QString &" */ 00980 bool KConfigINIBackEnd::writeConfigFile(QString filename, bool bGlobal, 00981 bool bMerge) 00982 { 00983 // is the config object read-only? 00984 if (pConfig->isReadOnly()) 00985 return true; // pretend we wrote it 00986 00987 KEntryMap aTempMap; 00988 QFile *mergeFile = (bMerge ? new QFile(filename) : 0); 00989 bool bEntriesLeft = getEntryMap(aTempMap, bGlobal, mergeFile); 00990 delete mergeFile; 00991 if (bFileImmutable) 00992 return true; // pretend we wrote it 00993 00994 // OK now the temporary map should be full of ALL entries. 00995 // write it out to disk. 00996 00997 // Check if file exists: 00998 int fileMode = -1; 00999 bool createNew = true; 01000 01001 KDE_struct_stat buf; 01002 if (KDE_stat(QFile::encodeName(filename), &buf) == 0) 01003 { 01004 if (buf.st_uid == getuid()) 01005 { 01006 // Preserve file mode if file exists and is owned by user. 01007 fileMode = buf.st_mode & 0777; 01008 } 01009 else 01010 { 01011 // File is not owned by user: 01012 // Don't create new file but write to existing file instead. 01013 createNew = false; 01014 } 01015 } 01016 01017 KSaveFile *pConfigFile = 0; 01018 FILE *pStream = 0; 01019 01020 if (createNew) 01021 { 01022 pConfigFile = new KSaveFile( filename, 0600 ); 01023 01024 if (pConfigFile->status() != 0) 01025 { 01026 delete pConfigFile; 01027 return bEntriesLeft; 01028 } 01029 01030 if (!bGlobal && (fileMode == -1)) 01031 fileMode = mFileMode; 01032 01033 if (fileMode != -1) 01034 { 01035 fchmod(pConfigFile->handle(), fileMode); 01036 } 01037 01038 pStream = pConfigFile->fstream(); 01039 } 01040 else 01041 { 01042 // Open existing file. 01043 // We use open() to ensure that we call without O_CREAT. 01044 int fd = KDE_open( QFile::encodeName(filename), O_WRONLY | O_TRUNC ); 01045 if (fd < 0) 01046 { 01047 return bEntriesLeft; 01048 } 01049 pStream = KDE_fdopen( fd, "w"); 01050 if (!pStream) 01051 { 01052 close(fd); 01053 return bEntriesLeft; 01054 } 01055 } 01056 01057 writeEntries(pStream, aTempMap); 01058 01059 if (pConfigFile) 01060 { 01061 bool bEmptyFile = (ftell(pStream) == 0); 01062 if ( bEmptyFile && ((fileMode == -1) || (fileMode == 0600)) ) 01063 { 01064 // File is empty and doesn't have special permissions: delete it. 01065 ::unlink(QFile::encodeName(filename)); 01066 pConfigFile->abort(); 01067 } 01068 else 01069 { 01070 // Normal case: Close the file 01071 pConfigFile->close(); 01072 } 01073 delete pConfigFile; 01074 } 01075 else 01076 { 01077 fclose(pStream); 01078 } 01079 01080 return bEntriesLeft; 01081 } 01082 01083 void KConfigINIBackEnd::writeEntries(FILE *pStream, const KEntryMap &aTempMap) 01084 { 01085 bool firstEntry = true; 01086 01087 // Write default group 01088 ::writeEntries(pStream, aTempMap, true, firstEntry, localeString); 01089 01090 // Write all other groups 01091 ::writeEntries(pStream, aTempMap, false, firstEntry, localeString); 01092 } 01093 01094 void KConfigBackEnd::virtual_hook( int, void* ) 01095 { /*BASE::virtual_hook( id, data );*/ } 01096 01097 void KConfigINIBackEnd::virtual_hook( int id, void* data ) 01098 { KConfigBackEnd::virtual_hook( id, data ); } 01099 01100 bool KConfigBackEnd::checkConfigFilesWritable(bool warnUser) 01101 { 01102 // WARNING: Do NOT use the event loop as it may not exist at this time. 01103 bool allWritable = true; 01104 QString errorMsg; 01105 if ( !mLocalFileName.isEmpty() && !bFileImmutable && !checkAccess(mLocalFileName,W_OK) ) 01106 { 01107 errorMsg = i18n("Will not save configuration.\n"); 01108 allWritable = false; 01109 errorMsg += i18n("Configuration file \"%1\" not writable.\n").arg(mLocalFileName); 01110 } 01111 // We do not have an immutability flag for kdeglobals. However, making kdeglobals mutable while making 01112 // the local config file immutable is senseless. 01113 if ( !mGlobalFileName.isEmpty() && useKDEGlobals && !bFileImmutable && !checkAccess(mGlobalFileName,W_OK) ) 01114 { 01115 if ( errorMsg.isEmpty() ) 01116 errorMsg = i18n("Will not save configuration.\n"); 01117 errorMsg += i18n("Configuration file \"%1\" not writable.\n").arg(mGlobalFileName); 01118 allWritable = false; 01119 } 01120 01121 if (warnUser && !allWritable) 01122 { 01123 // Note: We don't ask the user if we should not ask this question again because we can't save the answer. 01124 errorMsg += i18n("Please contact your system administrator."); 01125 QString cmdToExec = KStandardDirs::findExe(QString("kdialog")); 01126 KApplication *app = kapp; 01127 if (!cmdToExec.isEmpty() && app) 01128 { 01129 KProcess lprocess; 01130 lprocess << cmdToExec << "--title" << app->instanceName() << "--msgbox" << errorMsg.local8Bit(); 01131 lprocess.start( KProcess::Block ); 01132 } 01133 } 01134 return allWritable; 01135 }