Fixed DevStudio 2003 build with memory check code.
[pwlib.git] / src / ptlib / unix / config.cxx
blob1ff18cde07e4a302053b4c906e3f3ed396c2f898
1 /*
2 * config.cxx
4 * System/application configuration class implementation
6 * Portable Windows Library
8 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
25 * All Rights Reserved.
27 * Contributor(s): ______________________________________.
29 * $Log$
30 * Revision 1.37 2006/06/21 03:28:44 csoutheren
31 * Various cleanups thanks for Frederic Heem
33 * Revision 1.36 2004/04/03 23:55:59 csoutheren
34 * Added fix for PConfig environment variables under Linux
35 * Thanks to Michal Zygmuntowicz
37 * Revision 1.35 2003/03/17 08:10:59 robertj
38 * Fixed bug with parsing lines with no equal sign
40 * Revision 1.34 2003/01/30 23:46:05 dereks
41 * Fix compile error on gcc 3.2
43 * Revision 1.33 2003/01/26 03:57:12 robertj
44 * Fixed problem with last change so can still operate if do not have write
45 * access to the directory config file is in.
46 * Improved error reporting.
48 * Revision 1.32 2003/01/24 12:12:20 robertj
49 * Changed so that a crash in some other thread can no longer cause the
50 * config file to be truncated to zero bytes.
52 * Revision 1.31 2001/09/14 07:36:27 robertj
53 * Fixed bug where fails to read last line of config file if not ended with '\n'.
55 * Revision 1.30 2001/06/30 06:59:07 yurik
56 * Jac Goudsmit from Be submit these changes 6/28. Implemented by Yuri Kiryanov
58 * Revision 1.29 2001/05/24 00:56:38 robertj
59 * Fixed problem with config file being written every time on exit. Now is
60 * only written if it the config was modified by the application.
62 * Revision 1.28 2001/03/10 04:15:29 robertj
63 * Incorrect case for .ini extension
65 * Revision 1.27 2001/03/09 06:31:22 robertj
66 * Added ability to set default PConfig file or path to find it.
68 * Revision 1.26 2000/10/19 04:17:04 craigs
69 * Changed to allow writing of config files whilst config file is open
71 * Revision 1.25 2000/10/02 20:58:06 robertj
72 * Fixed bug where subsequent config file opening uses first opened filename.
74 * Revision 1.24 2000/08/30 04:45:02 craigs
75 * Added ability to have multiple lines with the same key
77 * Revision 1.23 2000/08/16 04:21:27 robertj
78 * Fixed subtle difference between UNix and Win32 section names (ignore trailing backslash)
80 * Revision 1.22 2000/05/25 12:10:06 robertj
81 * Added PConfig::HasKey() function to determine if value actually set.
83 * Revision 1.21 2000/05/02 08:30:26 craigs
84 * Removed "memory leaks" caused by brain-dead GNU linker
86 * Revision 1.20 1998/12/16 12:40:41 robertj
87 * Fixed bug where .ini file is not written when service run as a daemon.
89 * Revision 1.19 1998/12/16 09:57:37 robertj
90 * Fixed bug in writing .ini file, not truncating file when shrinking.
92 * Revision 1.18 1998/11/30 21:51:41 robertj
93 * New directory structure.
95 * Revision 1.17 1998/11/03 02:30:38 robertj
96 * Fixed emeory leak of environment.
98 * Revision 1.16 1998/09/24 04:12:11 robertj
99 * Added open software license.
103 #define _CONFIG_CXX
105 #pragma implementation "config.h"
107 #include <ptlib.h>
108 #include <ptlib/pprocess.h>
110 #include "../common/pconfig.cxx"
113 #define SYS_CONFIG_NAME "pwlib"
115 #define APP_CONFIG_DIR ".pwlib_config/"
116 #define SYS_CONFIG_DIR "/usr/local/pwlib/"
118 #define EXTENSION ".ini"
119 #define ENVIRONMENT_CONFIG_STR "/\~~environment~~\/"
122 // a single key/value pair
124 PDECLARE_CLASS (PXConfigValue, PCaselessString)
125 public:
126 PXConfigValue(const PString & theKey, const PString & theValue = "")
127 : PCaselessString(theKey), value(theValue) { }
128 PString GetValue() const { return value; }
129 void SetValue(const PString & theValue) { value = theValue; }
131 protected:
132 PString value;
136 // a list of key/value pairs
138 PLIST (PXConfigSectionList, PXConfigValue);
141 // a list of key value pairs, with a section name
143 PDECLARE_CLASS(PXConfigSection, PCaselessString)
144 public:
145 PXConfigSection(const PCaselessString & theName)
146 : PCaselessString(theName) { list.AllowDeleteObjects(); }
148 PXConfigSectionList & GetList() { return list; }
150 protected:
151 PXConfigSectionList list;
155 // a list of sections
158 PDECLARE_LIST(PXConfig, PXConfigSection)
159 public:
160 PXConfig(int i = 0);
162 void Wait() { mutex.Wait(); }
163 void Signal() { mutex.Signal(); }
165 BOOL ReadFromFile (const PFilePath & filename);
166 void ReadFromEnvironment (char **envp);
168 BOOL WriteToFile(const PFilePath & filename);
169 BOOL Flush(const PFilePath & filename);
171 void SetDirty() { dirty = TRUE; }
173 BOOL AddInstance();
174 BOOL RemoveInstance(const PFilePath & filename);
176 PINDEX GetSectionsIndex(const PString & theSection) const;
178 protected:
179 int instanceCount;
180 PMutex mutex;
181 BOOL dirty;
182 BOOL canSave;
186 // a dictionary of configurations, keyed by filename
188 PDECLARE_DICTIONARY(PXConfigDictionary, PFilePath, PXConfig)
189 public:
190 PXConfigDictionary(int dummy);
191 ~PXConfigDictionary();
192 PXConfig * GetFileConfigInstance(const PFilePath & key, const PFilePath & readKey);
193 PXConfig * GetEnvironmentInstance();
194 void RemoveInstance(PXConfig * instance);
195 void WriteChangedInstances();
197 protected:
198 PMutex mutex;
199 PXConfig * environmentInstance;
200 PThread * writeThread;
201 PSyncPointAck stopConfigWriteThread;
205 PDECLARE_CLASS(PXConfigWriteThread, PThread)
206 public:
207 PXConfigWriteThread(PSyncPointAck & stop);
208 ~PXConfigWriteThread();
209 void Main();
210 private:
211 PSyncPointAck & stop;
215 PXConfigDictionary * configDict;
217 #define new PNEW
219 //////////////////////////////////////////////////////
221 void PProcess::CreateConfigFilesDictionary()
223 configFiles = new PXConfigDictionary(0);
227 PXConfigWriteThread::PXConfigWriteThread(PSyncPointAck & s)
228 : PThread(10000, NoAutoDeleteThread, NormalPriority, "PXConfigWriteThread"),
229 stop(s)
231 Resume();
234 PXConfigWriteThread::~PXConfigWriteThread()
238 void PXConfigWriteThread::Main()
240 while (!stop.Wait(30000)) // if stop.Wait() returns TRUE, we are shutting down
241 configDict->WriteChangedInstances(); // check dictionary for items that need writing
243 configDict->WriteChangedInstances();
245 stop.Acknowledge();
250 PXConfig::PXConfig(int)
252 // make sure content gets removed
253 AllowDeleteObjects();
255 // no instances, initially
256 instanceCount = 0;
258 // we start off clean
259 dirty = FALSE;
261 // normally save on exit (except for environment configs)
262 canSave = TRUE;
265 BOOL PXConfig::AddInstance()
267 mutex.Wait();
268 BOOL stat = instanceCount++ == 0;
269 mutex.Signal();
271 return stat;
274 BOOL PXConfig::RemoveInstance(const PFilePath & /*filename*/)
276 mutex.Wait();
278 PAssert(instanceCount != 0, "PConfig instance count dec past zero");
280 BOOL stat = --instanceCount == 0;
282 mutex.Signal();
284 return stat;
287 BOOL PXConfig::Flush(const PFilePath & filename)
289 mutex.Wait();
291 BOOL stat = instanceCount == 0;
293 if (canSave && dirty) {
294 WriteToFile(filename);
295 dirty = FALSE;
298 mutex.Signal();
300 return stat;
303 BOOL PXConfig::WriteToFile(const PFilePath & filename)
305 // make sure the directory that the file is to be written into exists
306 PDirectory dir = filename.GetDirectory();
307 if (!dir.Exists() && !dir.Create(
308 PFileInfo::UserExecute |
309 PFileInfo::UserWrite |
310 PFileInfo::UserRead)) {
311 PProcess::PXShowSystemWarning(2000, "Cannot create PWLIB config directory");
312 return FALSE;
315 PTextFile file;
316 if (!file.Open(filename + ".new", PFile::WriteOnly))
317 file.Open(filename, PFile::WriteOnly);
319 if (!file.IsOpen()) {
320 PProcess::PXShowSystemWarning(2001, "Cannot create PWLIB config file: " + file.GetErrorText());
321 return FALSE;
324 for (PINDEX i = 0; i < GetSize(); i++) {
325 PXConfigSectionList & section = (*this)[i].GetList();
326 file << "[" << (*this)[i] << "]" << endl;
327 for (PINDEX j = 0; j < section.GetSize(); j++) {
328 PXConfigValue & value = section[j];
329 PStringArray lines = value.GetValue().Tokenise('\n', TRUE);
330 PINDEX k;
331 for (k = 0; k < lines.GetSize(); k++)
332 file << value << "=" << lines[k] << endl;
334 file << endl;
337 file.flush();
338 file.SetLength(file.GetPosition());
339 file.Close();
341 if (file.GetFilePath() != filename) {
342 if (!file.Rename(file.GetFilePath(), filename.GetFileName(), TRUE)) {
343 PProcess::PXShowSystemWarning(2001, "Cannot rename config file: " + file.GetErrorText());
344 return FALSE;
348 PTRACE(4, "PWLib\tSaved config file: " << filename);
349 return TRUE;
353 BOOL PXConfig::ReadFromFile(const PFilePath & filename)
355 PINDEX len;
357 // clear out all information
358 RemoveAll();
360 // attempt to open file
361 PTextFile file;
362 if (!file.Open(filename, PFile::ReadOnly))
363 return FALSE;
365 PXConfigSection * currentSection = NULL;
367 // read lines in the file
368 while (file.good()) {
369 PString line;
370 file >> line;
371 line = line.Trim();
372 if ((len = line.GetLength()) > 0) {
374 // ignore comments and blank lines
375 char ch = line[0];
376 if ((len > 0) && (ch != ';') && (ch != '#')) {
377 if (ch == '[') {
378 PCaselessString sectionName = (line.Mid(1,len-(line[len-1]==']'?2:1))).Trim();
379 PINDEX index;
380 if ((index = GetValuesIndex(sectionName)) != P_MAX_INDEX)
381 currentSection = &(*this )[index];
382 else {
383 currentSection = new PXConfigSection(sectionName);
384 Append(currentSection);
386 } else if (currentSection != NULL) {
387 PINDEX equals = line.Find('=');
388 if (equals > 0 && equals != P_MAX_INDEX) {
389 PString keyStr = line.Left(equals).Trim();
390 PString valStr = line.Right(len - equals - 1).Trim();
392 PINDEX index;
393 if ((index = currentSection->GetList().GetValuesIndex(keyStr)) != P_MAX_INDEX) {
394 PXConfigValue & value = currentSection->GetList()[index];
395 value.SetValue(value.GetValue() + '\n' + valStr);
396 } else {
397 PXConfigValue * value = new PXConfigValue(keyStr, valStr);
398 currentSection->GetList().Append(value);
406 // close the file and return
407 file.Close();
408 return TRUE;
411 void PXConfig::ReadFromEnvironment (char **envp)
413 // clear out all information
414 RemoveAll();
416 PXConfigSection * currentSection = new PXConfigSection("Options");
417 Append(currentSection);
419 while (*envp != NULL && **envp != '\0') {
420 PString line(*envp);
421 PINDEX equals = line.Find('=');
422 if (equals > 0) {
423 PXConfigValue * value = new PXConfigValue(line.Left(equals), line.Right(line.GetLength() - equals - 1));
424 currentSection->GetList().Append(value);
426 envp++;
429 // can't save environment configs
430 canSave = FALSE;
433 PINDEX PXConfig::GetSectionsIndex(const PString & theSection) const
435 PINDEX len = theSection.GetLength()-1;
436 if (theSection[len] != '\\')
437 return GetValuesIndex(theSection);
438 else
439 return GetValuesIndex(theSection.Left(len));
443 static BOOL LocateFile(const PString & baseName,
444 PFilePath & readFilename,
445 PFilePath & filename)
447 // check the user's home directory first
448 filename = readFilename = PProcess::Current().GetConfigurationFile();
449 if (PFile::Exists(filename))
450 return TRUE;
452 // otherwise check the system directory for a file to read,
453 // and then create
454 readFilename = SYS_CONFIG_DIR + baseName + EXTENSION;
455 return PFile::Exists(readFilename);
458 ///////////////////////////////////////////////////////////////////////////////
460 PString PProcess::GetConfigurationFile()
462 if (configurationPaths.IsEmpty()) {
463 configurationPaths.AppendString(PXGetHomeDir() + APP_CONFIG_DIR);
464 configurationPaths.AppendString(SYS_CONFIG_DIR);
467 // See if explicit filename
468 if (configurationPaths.GetSize() == 1 && !PDirectory::Exists(configurationPaths[0]))
469 return configurationPaths[0];
471 PString iniFilename = executableFile.GetTitle() + ".ini";
473 for (PINDEX i = 0; i < configurationPaths.GetSize(); i++) {
474 PFilePath cfgFile = PDirectory(configurationPaths[i]) + iniFilename;
475 if (PFile::Exists(cfgFile))
476 return cfgFile;
479 return PDirectory(configurationPaths[0]) + iniFilename;
483 ////////////////////////////////////////////////////////////
485 // PXConfigDictionary
488 PXConfigDictionary::PXConfigDictionary(int)
490 environmentInstance = NULL;
491 writeThread = NULL;
492 configDict = this;
496 PXConfigDictionary::~PXConfigDictionary()
498 if (writeThread != NULL) {
499 stopConfigWriteThread.Signal();
500 writeThread->WaitForTermination();
501 delete writeThread;
503 delete environmentInstance;
507 PXConfig * PXConfigDictionary::GetEnvironmentInstance()
509 mutex.Wait();
510 if (environmentInstance == NULL) {
511 environmentInstance = new PXConfig(0);
512 environmentInstance->ReadFromEnvironment(PProcess::Current().PXGetEnvp());
514 mutex.Signal();
515 return environmentInstance;
519 PXConfig * PXConfigDictionary::GetFileConfigInstance(const PFilePath & key, const PFilePath & readKey)
521 mutex.Wait();
523 // start write thread, if not already started
524 if (writeThread == NULL)
525 writeThread = new PXConfigWriteThread(stopConfigWriteThread);
527 PXConfig * config = GetAt(key);
528 if (config != NULL)
529 config->AddInstance();
530 else {
531 config = new PXConfig(0);
532 config->ReadFromFile(readKey);
533 config->AddInstance();
534 SetAt(key, config);
537 mutex.Signal();
538 return config;
541 void PXConfigDictionary::RemoveInstance(PXConfig * instance)
543 mutex.Wait();
545 if (instance != environmentInstance) {
546 PINDEX index = GetObjectsIndex(instance);
547 PAssert(index != P_MAX_INDEX, "Cannot find PXConfig instance to remove");
549 // decrement the instance count, but don't remove it yet
550 PFilePath key = GetKeyAt(index);
551 instance->RemoveInstance(key);
554 mutex.Signal();
557 void PXConfigDictionary::WriteChangedInstances()
559 mutex.Wait();
561 PINDEX i;
562 for (i = 0; i < GetSize(); i++) {
563 PFilePath key = GetKeyAt(i);
564 GetAt(key)->Flush(key);
567 mutex.Signal();
570 ////////////////////////////////////////////////////////////
572 // PConfig::
574 // Create a new configuration object
576 ////////////////////////////////////////////////////////////
578 void PConfig::Construct(Source src,
579 const PString & appname,
580 const PString & /*manuf*/)
582 // handle cnvironment configs differently
583 if (src == PConfig::Environment) {
584 config = configDict->GetEnvironmentInstance();
585 return;
588 PString name;
589 PFilePath filename, readFilename;
591 // look up file name to read, and write
592 if (src == PConfig::System)
593 LocateFile(SYS_CONFIG_NAME, readFilename, filename);
594 else
595 filename = readFilename = PProcess::Current().GetConfigurationFile();
597 // get, or create, the configuration
598 config = configDict->GetFileConfigInstance(filename, readFilename);
601 PConfig::PConfig(int, const PString & name)
602 : defaultSection("Options")
604 PFilePath readFilename, filename;
605 LocateFile(name, readFilename, filename);
606 config = configDict->GetFileConfigInstance(filename, readFilename);
609 void PConfig::Construct(const PFilePath & theFilename)
612 config = configDict->GetFileConfigInstance(theFilename, theFilename);
615 PConfig::~PConfig()
618 configDict->RemoveInstance(config);
621 ////////////////////////////////////////////////////////////
623 // PConfig::
625 // Return a list of all the section names in the file.
627 ////////////////////////////////////////////////////////////
629 PStringList PConfig::GetSections() const
631 PAssert(config != NULL, "config instance not set");
632 config->Wait();
634 PStringList list;
636 for (PINDEX i = 0; i < (*config).GetSize(); i++)
637 list.AppendString((*config)[i]);
639 config->Signal();
641 return list;
645 ////////////////////////////////////////////////////////////
647 // PConfig::
649 // Return a list of all the keys in the section. If the section name is
650 // not specified then use the default section.
652 ////////////////////////////////////////////////////////////
654 PStringList PConfig::GetKeys(const PString & theSection) const
656 PAssert(config != NULL, "config instance not set");
657 config->Wait();
659 PINDEX index;
660 PStringList list;
662 if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
663 PXConfigSectionList & section = (*config)[index].GetList();
664 for (PINDEX i = 0; i < section.GetSize(); i++)
665 list.AppendString(section[i]);
668 config->Signal();
669 return list;
674 ////////////////////////////////////////////////////////////
676 // PConfig::
678 // Delete all variables in the specified section. If the section name is
679 // not specified then use the default section.
681 ////////////////////////////////////////////////////////////
683 void PConfig::DeleteSection(const PString & theSection)
686 PAssert(config != NULL, "config instance not set");
687 config->Wait();
689 PStringList list;
691 PINDEX index;
692 if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
693 config->RemoveAt(index);
694 config->SetDirty();
697 config->Signal();
701 ////////////////////////////////////////////////////////////
703 // PConfig::
705 // Delete the particular variable in the specified section.
707 ////////////////////////////////////////////////////////////
709 void PConfig::DeleteKey(const PString & theSection, const PString & theKey)
711 PAssert(config != NULL, "config instance not set");
712 config->Wait();
714 PINDEX index;
715 if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
716 PXConfigSectionList & section = (*config)[index].GetList();
717 PINDEX index_2;
718 if ((index_2 = section.GetValuesIndex(theKey)) != P_MAX_INDEX) {
719 section.RemoveAt(index_2);
720 config->SetDirty();
724 config->Signal();
729 ////////////////////////////////////////////////////////////
731 // PConfig::
733 // Test if there is a value for the key.
735 ////////////////////////////////////////////////////////////
737 BOOL PConfig::HasKey(const PString & theSection, const PString & theKey) const
739 PAssert(config != NULL, "config instance not set");
740 config->Wait();
742 BOOL present = FALSE;
743 PINDEX index;
744 if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
745 PXConfigSectionList & section = (*config)[index].GetList();
746 present = section.GetValuesIndex(theKey) != P_MAX_INDEX;
749 config->Signal();
750 return present;
755 ////////////////////////////////////////////////////////////
757 // PConfig::
759 // Get a string variable determined by the key in the section.
761 ////////////////////////////////////////////////////////////
763 PString PConfig::GetString(const PString & theSection,
764 const PString & theKey, const PString & dflt) const
766 PAssert(config != NULL, "config instance not set");
767 config->Wait();
769 PString value = dflt;
770 PINDEX index;
771 if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX) {
773 PXConfigSectionList & section = (*config)[index].GetList();
774 if ((index = section.GetValuesIndex(theKey)) != P_MAX_INDEX)
775 value = section[index].GetValue();
778 config->Signal();
779 return value;
783 ////////////////////////////////////////////////////////////
785 // PConfig::
787 // Set a string variable determined by the key in the section.
789 ////////////////////////////////////////////////////////////
791 void PConfig::SetString(const PString & theSection,
792 const PString & theKey,
793 const PString & theValue)
795 PAssert(config != NULL, "config instance not set");
796 config->Wait();
798 PINDEX index;
799 PXConfigSection * section;
800 PXConfigValue * value;
802 if ((index = config->GetSectionsIndex(theSection)) != P_MAX_INDEX)
803 section = &(*config)[index];
804 else {
805 section = new PXConfigSection(theSection);
806 config->Append(section);
807 config->SetDirty();
810 if ((index = section->GetList().GetValuesIndex(theKey)) != P_MAX_INDEX)
811 value = &(section->GetList()[index]);
812 else {
813 value = new PXConfigValue(theKey);
814 section->GetList().Append(value);
815 config->SetDirty();
818 if (theValue != value->GetValue()) {
819 value->SetValue(theValue);
820 config->SetDirty();
823 config->Signal();
827 ///////////////////////////////////////////////////////////////////////////////