IniFile::~IniFile()
 {
-    ConfigTable::iterator i = table.begin();
-    ConfigTable::iterator end = table.end();
+    SectionTable::iterator i = table.begin();
+    SectionTable::iterator end = table.end();
 
     while (i != end) {
         delete (*i).second;
 {
     int fd[2];
 
+    // Open the file just to verify that we can.  Otherwise if the
+    // file doesn't exist or has bad permissions the user will get
+    // confusing errors from cpp/g++.
+    ifstream tmpf(file.c_str());
+
+    if (!tmpf.is_open())
+        return false;
+
+    tmpf.close();
+
 #ifdef CPP_PIPE
     if (pipe(fd) == -1)
         return false;
 
 void
 IniFile::Section::addEntry(const std::string &entryName,
-                           const std::string &value)
+                           const std::string &value,
+                           bool append)
 {
     EntryTable::iterator ei = table.find(entryName);
 
         // new entry
         table[entryName] = new Entry(value);
     }
+    else if (append) {
+        // append new reult to old entry
+        ei->second->appendValue(value);
+    }
     else {
         // override old entry
         ei->second->setValue(value);
 }
 
 
+bool
+IniFile::Section::add(const std::string &assignment)
+{
+    string::size_type offset = assignment.find('=');
+    if (offset == string::npos)  // no '=' found
+        return false;
+
+    // if "+=" rather than just "=" then append value
+    bool append = (assignment[offset-1] == '+');
+
+    string entryName = assignment.substr(0, append ? offset-1 : offset);
+    string value = assignment.substr(offset + 1);
+
+    eat_white(entryName);
+    eat_white(value);
+
+    addEntry(entryName, value, append);
+    return true;
+}
+
+
 IniFile::Entry *
 IniFile::Section::findEntry(const std::string &entryName) const
 {
 IniFile::Section *
 IniFile::addSection(const string §ionName)
 {
-    ConfigTable::iterator ci = table.find(sectionName);
+    SectionTable::iterator i = table.find(sectionName);
 
-    if (ci != table.end()) {
-        return ci->second;
+    if (i != table.end()) {
+        return i->second;
     }
     else {
         // new entry
 IniFile::Section *
 IniFile::findSection(const string §ionName) const
 {
-    ConfigTable::const_iterator ci = table.find(sectionName);
+    SectionTable::const_iterator i = table.find(sectionName);
 
-    return (ci == table.end()) ? NULL : ci->second;
+    return (i == table.end()) ? NULL : i->second;
 }
 
 
     string sectionName = str.substr(0, offset);
     string rest = str.substr(offset + 1);
 
-    offset = rest.find('=');
-    if (offset == string::npos)  // no '='found
-        return false;
-
-    string entryName = rest.substr(0, offset);
-    string value = rest.substr(offset + 1);
-
     eat_white(sectionName);
-    eat_white(entryName);
-    eat_white(value);
-
     Section *s = addSection(sectionName);
-    s->addEntry(entryName, value);
 
-    return true;
+    return s->add(rest);
 }
 
 bool
         if (section == NULL)
             continue;
 
-        string::size_type offset = line.find('=');
-        string entryName = line.substr(0, offset);
-        string value = line.substr(offset + 1);
-
-        eat_white(entryName);
-        eat_white(value);
-
-        section->addEntry(entryName, value);
+        if (!section->add(line))
+            return false;
     }
 
     return true;
 {
     bool unref = false;
 
-    for (ConfigTable::iterator ci = table.begin();
-         ci != table.end(); ++ci) {
-        const string §ionName = ci->first;
-        Section *section = ci->second;
+    for (SectionTable::iterator i = table.begin();
+         i != table.end(); ++i) {
+        const string §ionName = i->first;
+        Section *section = i->second;
 
         if (!section->isReferenced()) {
             if (section->findEntry("unref_section_ok") == NULL) {
     for (EntryTable::iterator ei = table.begin();
          ei != table.end(); ++ei) {
         cout << sectionName << ": " << (*ei).first << " => "
-             << (*ei).second << "\n";
+             << (*ei).second->getValue() << "\n";
     }
 }
 
 void
 IniFile::dump()
 {
-    for (ConfigTable::iterator ci = table.begin();
-         ci != table.end(); ++ci) {
-        ci->second->dump(ci->first);
+    for (SectionTable::iterator i = table.begin();
+         i != table.end(); ++i) {
+        i->second->dump(i->first);
     }
 }
 
 
 #include "base/hashmap.hh"
 
+///
+/// @file
+/// Declaration of IniFile object.
+///
+
+///
+/// This class represents the contents of a ".ini" file.
+///
+/// It's basically a two level lookup table: a set of named sections,
+/// where each section is a set of key/value pairs.  Section names,
+/// keys, and values are all uninterpreted strings.
+///
 class IniFile
 {
   protected:
+
+    ///
+    /// A single key/value pair.
+    ///
     class Entry
     {
-        std::string    value;
-        mutable bool   referenced;
+        std::string    value;          ///< The entry value.
+        mutable bool   referenced;     ///< Has this entry been used?
 
       public:
+        /// Constructor.
         Entry(const std::string &v)
             : value(v), referenced(false)
         {
         }
 
+        /// Has this entry been used?
         bool isReferenced() { return referenced; }
 
+        /// Fetch the value.
         const std::string &getValue() const;
 
+        /// Set the value.
         void setValue(const std::string &v) { value = v; }
+
+        /// Append the given string to the value.  A space is inserted
+        /// between the existing value and the new value.  Since this
+        /// operation is typically used with values that are
+        /// space-separated lists of tokens, this keeps the tokens
+        /// separate.
+        void appendValue(const std::string &v) { value += " "; value += v; }
     };
 
+    ///
+    /// A section.
+    ///
     class Section
     {
+        /// EntryTable type.  Map of strings to Entry object pointers.
         typedef m5::hash_map<std::string, Entry *> EntryTable;
 
-        EntryTable     table;
-        mutable bool   referenced;
+        EntryTable     table;          ///< Table of entries.
+        mutable bool   referenced;     ///< Has this section been used?
 
       public:
+        /// Constructor.
         Section()
             : table(), referenced(false)
         {
         }
 
+        /// Has this section been used?
         bool isReferenced() { return referenced; }
 
-        void addEntry(const std::string &entryName, const std::string &value);
+        /// Add an entry to the table.  If an entry with the same name
+        /// already exists, the 'append' parameter is checked If true,
+        /// the new value will be appended to the existing entry.  If
+        /// false, the new value will replace the existing entry.
+        void addEntry(const std::string &entryName, const std::string &value,
+                      bool append);
+
+        /// Add an entry to the table given a string assigment.
+        /// Assignment should be of the form "param=value" or
+        /// "param+=value" (for append).  This funciton parses the
+        /// assignment statment and calls addEntry().
+        /// @retval True for success, false if parse error.
+        bool add(const std::string &assignment);
+
+        /// Find the entry with the given name.
+        /// @retval Pointer to the entry object, or NULL if none.
         Entry *findEntry(const std::string &entryName) const;
 
+        /// Print the unreferenced entries in this section to cerr.
+        /// Messages can be suppressed using "unref_section_ok" and
+        /// "unref_entries_ok".
+        /// @param sectionName Name of this section, for use in output message.
+        /// @retval True if any entries were printed.
         bool printUnreferenced(const std::string §ionName);
+
+        /// Print the contents of this section to cout (for debugging).
         void dump(const std::string §ionName);
     };
 
-    typedef m5::hash_map<std::string, Section *> ConfigTable;
+    /// SectionTable type.  Map of strings to Section object pointers.
+    typedef m5::hash_map<std::string, Section *> SectionTable;
 
   protected:
-    ConfigTable table;
+    /// Hash of section names to Section object pointers.
+    SectionTable table;
 
+    /// Look up section with the given name, creating a new section if
+    /// not found.
+    /// @retval Pointer to section object.
     Section *addSection(const std::string §ionName);
+
+    /// Look up section with the given name.
+    /// @retval Pointer to section object, or NULL if not found.
     Section *findSection(const std::string §ionName) const;
 
+    /// Load parameter settings from given istream.  This is a helper
+    /// function for load(string) and loadCPP(), which open a file
+    /// and then pass it here.
+    /// @retval True if successful, false if errors were encountered.
     bool load(std::istream &f);
 
   public:
+    /// Constructor.
     IniFile();
+
+    /// Destructor.
     ~IniFile();
 
+    /// Load the specified file, passing it through the C preprocessor.
+    /// Parameter settings found in the file will be merged with any
+    /// already defined in this object.
+    /// @param file The path of the file to load.
+    /// @param cppFlags Vector of extra flags to pass to cpp.
+    /// @retval True if successful, false if errors were encountered.
     bool loadCPP(const std::string &file, std::vector<char *> &cppFlags);
+
+    /// Load the specified file.
+    /// Parameter settings found in the file will be merged with any
+    /// already defined in this object.
+    /// @param file The path of the file to load.
+    /// @retval True if successful, false if errors were encountered.
     bool load(const std::string &file);
 
+    /// Take string of the form "<section>:<parameter>=<value>" or
+    /// "<section>:<parameter>+=<value>" and add to database.
+    /// @retval True if successful, false if parse error.
     bool add(const std::string &s);
 
+    /// Find value corresponding to given section and entry names.
+    /// Value is returned by reference in 'value' param.
+    /// @retval True if found, false if not.
     bool find(const std::string §ion, const std::string &entry,
               std::string &value) const;
+
+    /// Find value corresponding to given section and entry names,
+    /// following "default" links to other sections where possible.
+    /// Value is returned by reference in 'value' param.
+    /// @retval True if found, false if not.
     bool findDefault(const std::string §ion, const std::string &entry,
                      std::string &value) const;
 
+    /// Print unreferenced entries in object.  Iteratively calls
+    /// printUnreferend() on all the constituent sections.
     bool printUnreferenced();
 
+    /// Dump contents to cout.  For debugging.
     void dump();
 };
 
 
 cprintftest: cprintftest.o cprintf.o
        $(CXX) $(LFLAGS) -o $@ $^
 
-initest: initest.o str.o inifile.o
+initest: initest.o str.o inifile.o cprintf.o
        $(CXX) $(LFLAGS) -o $@ $^
 
 lrutest: lru_test.o
 
 
 [General]
 Test3=89
+
+[Junk]
+Test4+=mia
 
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <iostream.h>
-#include <fstream.h>
-#include <unistd.h>
-
+#include <iostream>
+#include <fstream>
 #include <string>
 #include <vector>
 
 #include "base/inifile.hh"
+#include "base/cprintf.hh"
+
+using namespace std;
 
 char *progname;
 
 void
 usage()
 {
-  cout << "Usage: " << progname << " <ini file>\n";
-  exit(1);
+    cout << "Usage: " << progname << " <ini file>\n";
+    exit(1);
 }
 
 #if 0
-    char *defines = getenv("CONFIG_DEFINES");
-    if (defines) {
-      char *c = defines;
-      while ((c = strchr(c, ' ')) != NULL) {
+char *defines = getenv("CONFIG_DEFINES");
+if (defines) {
+    char *c = defines;
+    while ((c = strchr(c, ' ')) != NULL) {
         *c++ = '\0';
         count++;
-      }
-      count++;
     }
+    count++;
+}
 
 #endif
 
 int
 main(int argc, char *argv[])
 {
-  IniFile config;
-
-  progname = argv[0];
-
-  int cpp_options_count = 0;
-  char **cpp_options = new char*[argc * 2];
-  char **cur_opt = cpp_options;
-
-  int ch;
-  while ((ch = getopt(argc, argv, "D:")) != -1) {
-    switch (ch) {
-    case 'D':
-      *cur_opt++ = "-D";
-      *cur_opt++ = optarg;
-      cpp_options_count += 2;
-      break;
-
-    default:
-      usage();
+    IniFile simConfigDB;
+
+    progname = argv[0];
+
+    vector<char *> cppArgs;
+
+    vector<char *> cpp_options;
+    cpp_options.reserve(argc * 2);
+
+    for (int i = 1; i < argc; ++i) {
+        char *arg_str = argv[i];
+
+        // if arg starts with '-', parse as option,
+        // else treat it as a configuration file name and load it
+        if (arg_str[0] == '-') {
+
+            // switch on second char
+            switch (arg_str[1]) {
+              case 'D':
+              case 'U':
+              case 'I':
+                // cpp options: record & pass to cpp.  Note that these
+                // cannot have spaces, i.e., '-Dname=val' is OK, but
+                // '-D name=val' is not.  I don't consider this a
+                // problem, since even though gnu cpp accepts the
+                // latter, other cpp implementations do not (Tru64,
+                // for one).
+                cppArgs.push_back(arg_str);
+                break;
+
+              case '-':
+                // command-line configuration parameter:
+                // '--<section>:<parameter>=<value>'
+
+                if (!simConfigDB.add(arg_str + 2)) {
+                    // parse error
+                    ccprintf(cerr,
+                             "Could not parse configuration argument '%s'\n"
+                             "Expecting --<section>:<parameter>=<value>\n",
+                             arg_str);
+                    exit(0);
+                }
+                break;
+
+              default:
+                usage();
+            }
+        }
+        else {
+            // no '-', treat as config file name
+
+            if (!simConfigDB.loadCPP(arg_str, cppArgs)) {
+                cprintf("Error processing file %s\n", arg_str);
+                exit(1);
+            }
+        }
     }
-  }
-
-  argc -= optind;
-  argv += optind;
 
-  if (argc != 1)
-    usage();
-
-  string file = argv[0];
-
-  if (!config.loadCPP(file, cpp_options, cpp_options_count)) {
-    cout << "File not found!\n";
-    exit(1);
-  }
+    string value;
 
-  string value;
 #define FIND(C, E) \
-  if (config.find(C, E, value)) \
+  if (simConfigDB.find(C, E, value)) \
     cout << ">" << value << "<\n"; \
   else \
     cout << "Not Found!\n"
 
-  FIND("General", "Test2");
-  FIND("Junk", "Test3");
-  FIND("Junk", "Test4");
-  FIND("General", "Test1");
-  FIND("Junk2", "test3");
-  FIND("General", "Test3");
+    FIND("General", "Test2");
+    FIND("Junk", "Test3");
+    FIND("Junk", "Test4");
+    FIND("General", "Test1");
+    FIND("Junk2", "test3");
+    FIND("General", "Test3");
 
-  cout << "\n";
+    cout << "\n";
 
-  config.dump();
+    simConfigDB.dump();
 
-  return 0;
+    return 0;
 }