2 * shib-ini.h -- INI file handling
4 * Created By: Derek Atkins <derek@ihtfp.com>
9 // eventually we might be able to support autoconf via cygwin...
10 #if defined (_MSC_VER) || defined(__BORLANDC__)
11 # include "config_win32.h"
16 #include "shib-target.h"
17 #include "shib-threads.h"
24 #include <sys/types.h>
27 #include <log4cpp/Category.hh>
30 using namespace shibtarget;
32 class HeaderIterator : public shibtarget::ShibINI::Iterator {
34 HeaderIterator (ShibINIPriv* ini);
37 const string* begin();
41 map<string, map<string,string> >::const_iterator iter;
45 class TagIterator : public ShibINI::Iterator {
47 TagIterator (ShibINIPriv* ini, const string& header);
50 const string* begin();
55 map<string,string>::const_iterator iter;
59 class shibtarget::ShibINIPriv {
62 ~ShibINIPriv() { delete rwlock; }
63 log4cpp::Category *log;
65 map<string, map<string, string> > table;
69 unsigned long modtime;
71 unsigned long iterators;
74 bool exists(const std::string& header);
75 bool exists(const std::string& header, const std::string& tag);
78 ShibINIPriv::ShibINIPriv()
80 string ctx = "shibtarget.ShibINI";
81 log = &(log4cpp::Category::getInstance(ctx));
82 rwlock = RWLock::create();
86 static void trimline (string& s)
88 int end = s.size() - 1, start = 0;
90 // Trim stuff on right.
91 while (end > 0 && !isgraph(s[end])) end--;
93 // Trim stuff on left.
94 while (start < end && !isgraph(s[start])) start++;
97 s = s.substr(start, end - start + 1);
100 static void to_lowercase (string& s)
102 for (int i = 0, sz = s.size(); i < sz; i++)
103 s[i] = tolower(s[i]);
106 ShibINI::~ShibINI() {
110 void ShibINI::init (string& f, bool case_sensitive)
112 m_priv = new ShibINIPriv();
114 m_priv->cs = case_sensitive;
115 m_priv->log->info ("initializing INI file: %s (sensitive=%s)", f.c_str(),
116 (case_sensitive ? "true" : "false"));
118 ReadLock lock(m_priv->rwlock);
123 // Must be called holding the ReadLock.
125 void ShibINI::refresh(void)
127 saml::NDC ndc("refresh");
129 // check if we need to refresh
131 struct _stat stat_buf;
132 if (_stat (m_priv->file.c_str(), &stat_buf) < 0)
134 struct stat stat_buf;
135 if (stat (m_priv->file.c_str(), &stat_buf) < 0)
137 m_priv->log->error("stat failed: %s", m_priv->file.c_str());
139 if (m_priv->modtime >= stat_buf.st_mtime || m_priv->iterators > 0)
142 // Release the read lock -- grab the write lock. Don't worry if
143 // this is non-atomic -- we'll recheck the status.
144 m_priv->rwlock->unlock();
145 m_priv->rwlock->wrlock();
147 // Recheck the modtime
148 if (m_priv->modtime >= stat_buf.st_mtime) {
149 // Yep, another thread got to it. We can exit now... Release
150 // the write lock and reaquire the read-lock.
152 m_priv->rwlock->unlock();
153 m_priv->rwlock->rdlock();
157 // Ok, we've got the write lock. Let's update our state.
159 m_priv->modtime = stat_buf.st_mtime;
161 // clear the existing maps
162 m_priv->table.clear();
164 m_priv->log->info("reading %s", m_priv->file.c_str());
169 ifstream infile (m_priv->file.c_str());
171 m_priv->log->warn("cannot open file: %s", m_priv->file.c_str());
172 m_priv->rwlock->unlock();
173 m_priv->rwlock->rdlock();
177 const int MAXLEN = 1024;
178 char linebuffer[MAXLEN];
179 string current_header;
180 bool have_header = false;
183 infile.getline (linebuffer, MAXLEN);
184 string line (linebuffer);
186 if (line[0] == '#') continue;
189 if (line.size() <= 1) continue;
191 if (line[0] == '[') {
194 m_priv->log->debug("Found what appears to be a header line");
198 // find the end of the header
199 int endpos = line.find (']');
200 if (endpos == line.npos) {
201 m_priv->log->debug("Weird: no end found.. punting");
202 continue; // HUH? No end?
206 current_header = line.substr (1, endpos-1);
207 trimline (current_header);
209 if (!m_priv->cs) to_lowercase (current_header);
211 m_priv->table[current_header] = map<string,string>();
213 m_priv->log->debug("current header: \"%s\"", current_header.c_str());
215 } else if (have_header) {
218 m_priv->log->debug("Found what appears to be a tag line");
221 int mid = line.find ('=');
223 if (mid == line.npos) {
224 m_priv->log->debug("Weird: no '=' found.. punting");
225 continue; // Can't find the value's setting
228 tag = line.substr (0,mid);
229 setting = line.substr (mid+1, line.size()-mid);
234 if (!m_priv->cs) to_lowercase (tag);
236 // If it already exists, log an error and do not save it
237 if (m_priv->exists (current_header, tag))
238 m_priv->log->error("Duplicate tag found in section %s: \"%s\"",
239 current_header.c_str(), tag.c_str());
241 (m_priv->table[current_header])[tag] = setting;
243 m_priv->log->debug("new tag: \"%s\" = \"%s\"",
244 tag.c_str(), setting.c_str());
248 } // until the file ends
251 // In case there are exceptions.
254 // Now release the write lock and reaquire the read lock
255 m_priv->rwlock->unlock();
256 m_priv->rwlock->rdlock();
259 const std::string ShibINI::get (const string& header, const string& tag)
261 ReadLock rwlock(m_priv->rwlock);
264 static string empty = "";
274 if (!exists(h)) return empty;
276 map<string,string>::const_iterator i = m_priv->table[h].find(t);
277 if (i == m_priv->table[h].end())
282 bool ShibINIPriv::exists(const std::string& header)
285 if (!cs) to_lowercase (h);
287 return (table.find(h) != table.end());
290 bool ShibINI::exists(const std::string& header)
292 ReadLock rwlock(m_priv->rwlock);
295 return m_priv->exists(header);
298 bool ShibINIPriv::exists(const std::string& header, const std::string& tag)
308 if (!exists(h)) return false;
309 return (table[h].find(t) != table[h].end());
312 bool ShibINI::exists(const std::string& header, const std::string& tag)
314 ReadLock rwlock(m_priv->rwlock);
317 return m_priv->exists(header, tag);
320 bool ShibINI::get_tag (string& header, string& tag, bool try_general, string* result)
322 if (!result) return false;
324 ReadLock rwlock(m_priv->rwlock);
327 if (exists (header, tag)) {
328 *result = get (header, tag);
331 if (try_general && exists (SHIBTARGET_GENERAL, tag)) {
332 *result = get (SHIBTARGET_GENERAL, tag);
339 void ShibINI::dump (ostream& os)
341 ReadLock rwlock(m_priv->rwlock);
344 os << "File: " << m_priv->file << "\n";
345 os << "Case-Sensitive: " << ( m_priv->cs ? "Yes\n" : "No\n" );
346 os << "File Entries:\n";
348 for (map<string, map<string, string> >::const_iterator i = m_priv->table.begin();
349 i != m_priv->table.end(); i++) {
351 os << "[" << i->first << "]\n";
353 for (map<string,string>::const_iterator j=i->second.begin();
354 j != i->second.end(); j++) {
356 os << " " << j->first << " = " << j->second << "\n";
363 ShibINI::Iterator* ShibINI::header_iterator()
365 ReadLock rwlock(m_priv->rwlock);
367 HeaderIterator* iter = new HeaderIterator(m_priv);
368 return (ShibINI::Iterator*) iter;
371 ShibINI::Iterator* ShibINI::tag_iterator(const std::string& header)
373 ReadLock rwlock(m_priv->rwlock);
375 TagIterator* iter = new TagIterator(m_priv, header);
376 return (ShibINI::Iterator*) iter;
380 // XXX: FIXME: there may be a race condition in the iterators if a
381 // caller holds an active Iterator, the underlying file changes, and
382 // then calls one of the get() routines. It's possible the iterator
383 // may screw up -- I don't know whether the iterator actually depends
384 // on the underlying infrastructure or not.
387 HeaderIterator::HeaderIterator (ShibINIPriv* inip)
391 ini->rwlock->rdlock();
395 HeaderIterator::~HeaderIterator ()
398 ini->rwlock->unlock();
401 const string* HeaderIterator::begin ()
403 iter = ini->table.begin();
404 if (iter == ini->table.end()) {
412 const string* HeaderIterator::next ()
417 if (iter == ini->table.end()) {
424 TagIterator::TagIterator (ShibINIPriv* inip, const string& headerp)
429 ini->rwlock->rdlock();
433 TagIterator::~TagIterator ()
436 ini->rwlock->unlock();
439 const string* TagIterator::begin ()
441 iter = ini->table[header].begin();
442 if (iter == ini->table[header].end()) {
450 const string* TagIterator::next ()
455 if (iter == ini->table[header].end()) {
462 bool ShibINI::boolean(string& value)
464 const char* v = value.c_str();
465 #ifdef HAVE_STRCASECMP
466 if (!strncasecmp (v, "on", 2) || !strncasecmp (v, "true", 4) || !strncmp(v, "1", 1))
469 if (!strnicmp (v, "on", 2) || !strnicmp (v, "true", 4) || !strncmp(v, "1", 1))