inifile.cc revision 178
1/*
2 * Copyright (c) 2003 The Regents of The University of Michigan
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met: redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer;
9 * redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution;
12 * neither the name of the copyright holders nor the names of its
13 * contributors may be used to endorse or promote products derived from
14 * this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#define USE_CPP
30// #define CPP_PIPE
31
32
33#ifdef USE_CPP
34#include <sys/signal.h>
35#include <sys/types.h>
36#include <sys/wait.h>
37
38#include <stdio.h>
39#include <stdlib.h>
40#include <unistd.h>
41#endif
42
43#include <fstream>
44#include <iostream>
45#if __GNUC__ >= 3
46#include <ext/stdio_filebuf.h>
47#endif
48
49#include <vector>
50#include <string>
51
52#include "base/inifile.hh"
53#include "base/str.hh"
54
55using namespace std;
56
57IniFile::IniFile()
58{}
59
60IniFile::~IniFile()
61{
62    SectionTable::iterator i = table.begin();
63    SectionTable::iterator end = table.end();
64
65    while (i != end) {
66        delete (*i).second;
67        ++i;
68    }
69}
70
71
72#ifdef USE_CPP
73bool
74IniFile::loadCPP(const string &file, vector<char *> &cppArgs)
75{
76    int fd[2];
77
78    // Open the file just to verify that we can.  Otherwise if the
79    // file doesn't exist or has bad permissions the user will get
80    // confusing errors from cpp/g++.
81    ifstream tmpf(file.c_str());
82
83    if (!tmpf.is_open())
84        return false;
85
86    tmpf.close();
87
88#ifdef CPP_PIPE
89    if (pipe(fd) == -1)
90        return false;
91#else
92    char tempfile[] = "/tmp/configXXXXXX";
93    fd[0] = fd[1] = mkstemp(tempfile);
94#endif
95
96    int pid = fork();
97
98    if (pid == -1)
99        return 1;
100
101    if (pid == 0) {
102        char filename[FILENAME_MAX];
103        string::size_type i = file.copy(filename, sizeof(filename) - 1);
104        filename[i] = '\0';
105
106        int arg_count = cppArgs.size();
107
108        char **args = new char *[arg_count + 20];
109
110        int nextArg = 0;
111        args[nextArg++] = "g++";
112        args[nextArg++] = "-E";
113        args[nextArg++] = "-P";
114        args[nextArg++] = "-nostdinc";
115        args[nextArg++] = "-nostdinc++";
116        args[nextArg++] = "-x";
117        args[nextArg++] = "c++";
118        args[nextArg++] = "-undef";
119
120        for (int i = 0; i < arg_count; i++)
121            args[nextArg++] = cppArgs[i];
122
123        args[nextArg++] = filename;
124        args[nextArg++] = NULL;
125
126        close(STDOUT_FILENO);
127        if (dup2(fd[1], STDOUT_FILENO) == -1)
128            return 1;
129
130        execvp("g++", args);
131
132        exit(1);
133    }
134
135    int retval;
136    waitpid(pid, &retval, 0);
137
138    // check for normal completion of CPP
139    if (!WIFEXITED(retval) || WEXITSTATUS(retval) != 0)
140        return false;
141
142#ifdef CPP_PIPE
143    close(fd[1]);
144#else
145    lseek(fd[0], 0, SEEK_SET);
146#endif
147
148    bool status = false;
149
150#if __GNUC__ >= 3
151    using namespace __gnu_cxx;
152    stdio_filebuf<char> fbuf(fd[0], ios_base::in, true,
153        static_cast<stdio_filebuf<char>::int_type>(BUFSIZ));
154
155    if (fbuf.is_open()) {
156        istream f(&fbuf);
157        status = load(f);
158    }
159
160#else
161    ifstream f(fd[0]);
162    if (f.is_open())
163        status = load(f);
164#endif
165
166#ifndef CPP_PIPE
167    unlink(tempfile);
168#endif
169
170    return status;
171}
172#endif
173
174bool
175IniFile::load(const string &file)
176{
177    ifstream f(file.c_str());
178
179    if (!f.is_open())
180        return false;
181
182    return load(f);
183}
184
185
186const string &
187IniFile::Entry::getValue() const
188{
189    referenced = true;
190    return value;
191}
192
193
194void
195IniFile::Section::addEntry(const std::string &entryName,
196                           const std::string &value,
197                           bool append)
198{
199    EntryTable::iterator ei = table.find(entryName);
200
201    if (ei == table.end()) {
202        // new entry
203        table[entryName] = new Entry(value);
204    }
205    else if (append) {
206        // append new reult to old entry
207        ei->second->appendValue(value);
208    }
209    else {
210        // override old entry
211        ei->second->setValue(value);
212    }
213}
214
215
216bool
217IniFile::Section::add(const std::string &assignment)
218{
219    string::size_type offset = assignment.find('=');
220    if (offset == string::npos) {
221        // no '=' found
222        cerr << "Can't parse .ini line " << assignment << endl;
223        return false;
224    }
225
226    // if "+=" rather than just "=" then append value
227    bool append = (assignment[offset-1] == '+');
228
229    string entryName = assignment.substr(0, append ? offset-1 : offset);
230    string value = assignment.substr(offset + 1);
231
232    eat_white(entryName);
233    eat_white(value);
234
235    addEntry(entryName, value, append);
236    return true;
237}
238
239
240IniFile::Entry *
241IniFile::Section::findEntry(const std::string &entryName) const
242{
243    referenced = true;
244
245    EntryTable::const_iterator ei = table.find(entryName);
246
247    return (ei == table.end()) ? NULL : ei->second;
248}
249
250
251IniFile::Section *
252IniFile::addSection(const string &sectionName)
253{
254    SectionTable::iterator i = table.find(sectionName);
255
256    if (i != table.end()) {
257        return i->second;
258    }
259    else {
260        // new entry
261        Section *sec = new Section();
262        table[sectionName] = sec;
263        return sec;
264    }
265}
266
267
268IniFile::Section *
269IniFile::findSection(const string &sectionName) const
270{
271    SectionTable::const_iterator i = table.find(sectionName);
272
273    return (i == table.end()) ? NULL : i->second;
274}
275
276
277// Take string of the form "<section>:<parameter>=<value>" and add to
278// database.  Return true if successful, false if parse error.
279bool
280IniFile::add(const string &str)
281{
282    // find ':'
283    string::size_type offset = str.find(':');
284    if (offset == string::npos)  // no ':' found
285        return false;
286
287    string sectionName = str.substr(0, offset);
288    string rest = str.substr(offset + 1);
289
290    eat_white(sectionName);
291    Section *s = addSection(sectionName);
292
293    return s->add(rest);
294}
295
296bool
297IniFile::load(istream &f)
298{
299    Section *section = NULL;
300
301    while (!f.eof()) {
302        f >> ws; // Eat whitespace
303        if (f.eof()) {
304            break;
305        }
306
307        string line;
308        getline(f, line);
309        if (line.size() == 0)
310            continue;
311
312        eat_end_white(line);
313        int last = line.size() - 1;
314
315        if (line[0] == '[' && line[last] == ']') {
316            string sectionName = line.substr(1, last - 1);
317            eat_white(sectionName);
318            section = addSection(sectionName);
319            continue;
320        }
321
322        if (section == NULL)
323            continue;
324
325        if (!section->add(line))
326            return false;
327    }
328
329    return true;
330}
331
332bool
333IniFile::find(const string &sectionName, const string &entryName,
334              string &value) const
335{
336    Section *section = findSection(sectionName);
337    if (section == NULL)
338        return false;
339
340    Entry *entry = section->findEntry(entryName);
341    if (entry == NULL)
342        return false;
343
344    value = entry->getValue();
345
346    return true;
347}
348
349bool
350IniFile::findDefault(const string &_section, const string &entry,
351                     string &value) const
352{
353    string section = _section;
354    while (!find(section, entry, value)) {
355        if (!find(section, "default", section))
356            return false;
357    }
358
359    return true;
360}
361
362
363bool
364IniFile::Section::printUnreferenced(const string &sectionName)
365{
366    bool unref = false;
367    bool search_unref_entries = false;
368    vector<string> unref_ok_entries;
369
370    Entry *entry = findEntry("unref_entries_ok");
371    if (entry != NULL) {
372        tokenize(unref_ok_entries, entry->getValue(), ' ');
373        if (unref_ok_entries.size()) {
374            search_unref_entries = true;
375        }
376    }
377
378    for (EntryTable::iterator ei = table.begin();
379         ei != table.end(); ++ei) {
380        const string &entryName = ei->first;
381        Entry *entry = ei->second;
382
383        if (entryName == "unref_section_ok" ||
384            entryName == "unref_entries_ok")
385        {
386            continue;
387        }
388
389        if (!entry->isReferenced()) {
390            if (search_unref_entries &&
391                (std::find(unref_ok_entries.begin(), unref_ok_entries.end(),
392                           entryName) != unref_ok_entries.end()))
393            {
394                continue;
395            }
396
397            cerr << "Parameter " << sectionName << ":" << entryName
398                 << " not referenced." << endl;
399            unref = true;
400        }
401    }
402
403    return unref;
404}
405
406
407bool
408IniFile::printUnreferenced()
409{
410    bool unref = false;
411
412    for (SectionTable::iterator i = table.begin();
413         i != table.end(); ++i) {
414        const string &sectionName = i->first;
415        Section *section = i->second;
416
417        if (!section->isReferenced()) {
418            if (section->findEntry("unref_section_ok") == NULL) {
419                cerr << "Section " << sectionName << " not referenced."
420                     << endl;
421                unref = true;
422            }
423        }
424        else {
425            if (section->printUnreferenced(sectionName)) {
426                unref = true;
427            }
428        }
429    }
430
431    return unref;
432}
433
434
435void
436IniFile::Section::dump(const string &sectionName)
437{
438    for (EntryTable::iterator ei = table.begin();
439         ei != table.end(); ++ei) {
440        cout << sectionName << ": " << (*ei).first << " => "
441             << (*ei).second->getValue() << "\n";
442    }
443}
444
445void
446IniFile::dump()
447{
448    for (SectionTable::iterator i = table.begin();
449         i != table.end(); ++i) {
450        i->second->dump(i->first);
451    }
452}
453