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/shib-threads.h>
24 #include <sys/types.h>
27 #include <log4cpp/Category.hh>
30 using namespace shibboleth;
31 using namespace shibtarget;
33 class HeaderIterator : public shibtarget::ShibINI::Iterator {
35 HeaderIterator (ShibINIPriv* ini);
38 const string* begin();
42 map<string, map<string,string> >::const_iterator iter;
46 class TagIterator : public ShibINI::Iterator {
48 TagIterator (ShibINIPriv* ini, const string& header);
51 const string* begin();
56 map<string,string>::const_iterator iter;
60 class shibtarget::ShibINIPriv {
63 ~ShibINIPriv() { delete rwlock; }
64 log4cpp::Category *log;
66 map<string, map<string, string> > table;
70 unsigned long modtime;
72 unsigned long iterators;
75 bool exists(const std::string& header);
76 bool exists(const std::string& header, const std::string& tag);
79 ShibINIPriv::ShibINIPriv()
81 string ctx = "shibtarget.ShibINI";
82 log = &(log4cpp::Category::getInstance(ctx));
83 rwlock = RWLock::create();
88 static void trimline (string& s)
90 int end = s.size() - 1, start = 0;
92 // Trim stuff on right.
93 while (end > 0 && !isgraph(s[end])) end--;
95 // Trim stuff on left.
96 while (start < end && !isgraph(s[start])) start++;
99 s = s.substr(start, end - start + 1);
102 static void to_lowercase (string& s)
104 for (int i = 0, sz = s.size(); i < sz; i++)
105 s[i] = tolower(s[i]);
108 ShibINI::~ShibINI() {
112 void ShibINI::init (string& f, bool case_sensitive)
114 m_priv = new ShibINIPriv();
116 m_priv->cs = case_sensitive;
117 m_priv->log->info ("initializing INI file: %s (sensitive=%s)", f.c_str(),
118 (case_sensitive ? "true" : "false"));
120 ReadLock lock(m_priv->rwlock);
125 // Must be called holding the ReadLock.
127 void ShibINI::refresh(void)
129 saml::NDC ndc("refresh");
131 // check if we need to refresh
133 struct _stat stat_buf;
134 if (_stat (m_priv->file.c_str(), &stat_buf) < 0)
136 struct stat stat_buf;
137 if (stat (m_priv->file.c_str(), &stat_buf) < 0)
139 m_priv->log->error("stat failed: %s", m_priv->file.c_str());
142 m_priv->log->info("refresh: last modtime at %d; file is %d; iters: %d",
143 m_priv->modtime, stat_buf.st_mtime, m_priv->iterators);
146 if (m_priv->modtime >= stat_buf.st_mtime || m_priv->iterators > 0)
149 // Release the read lock -- grab the write lock. Don't worry if
150 // this is non-atomic -- we'll recheck the status.
151 m_priv->rwlock->unlock();
152 m_priv->rwlock->wrlock();
154 // Recheck the modtime
155 if (m_priv->modtime >= stat_buf.st_mtime) {
156 // Yep, another thread got to it. We can exit now... Release
157 // the write lock and reaquire the read-lock.
159 m_priv->rwlock->unlock();
160 m_priv->rwlock->rdlock();
164 // Ok, we've got the write lock. Let's update our state.
166 m_priv->modtime = stat_buf.st_mtime;
168 // clear the existing maps
169 m_priv->table.clear();
171 m_priv->log->info("reading %s", m_priv->file.c_str());
176 ifstream infile (m_priv->file.c_str());
178 m_priv->log->warn("cannot open file: %s", m_priv->file.c_str());
179 m_priv->rwlock->unlock();
180 m_priv->rwlock->rdlock();
184 const int MAXLEN = 1024;
185 char linebuffer[MAXLEN];
186 string current_header;
187 bool have_header = false;
190 infile.getline (linebuffer, MAXLEN);
191 string line (linebuffer);
193 if (line[0] == '#') continue;
196 if (line.size() <= 1) continue;
198 if (line[0] == '[') {
202 m_priv->log->info("Found what appears to be a header line");
207 // find the end of the header
208 int endpos = line.find (']');
209 if (endpos == line.npos) {
211 m_priv->log->info("Weird: no end found.. punting");
213 continue; // HUH? No end?
217 current_header = line.substr (1, endpos-1);
218 trimline (current_header);
220 if (!m_priv->cs) to_lowercase (current_header);
222 m_priv->table[current_header] = map<string,string>();
225 m_priv->log->info("current header: \"%s\"", current_header.c_str());
228 } else if (have_header) {
232 m_priv->log->info("Found what appears to be a tag line");
236 int mid = line.find ('=');
238 if (mid == line.npos) {
240 m_priv->log->info("Weird: no '=' found.. punting");
242 continue; // Can't find the value's setting
245 tag = line.substr (0,mid);
246 setting = line.substr (mid+1, line.size()-mid);
251 if (!m_priv->cs) to_lowercase (tag);
253 // If it already exists, log an error and do not save it
254 if (m_priv->exists (current_header, tag))
255 m_priv->log->error("Duplicate tag found in section %s: \"%s\"",
256 current_header.c_str(), tag.c_str());
258 (m_priv->table[current_header])[tag] = setting;
261 m_priv->log->info("new tag: \"%s\" = \"%s\"",
262 tag.c_str(), setting.c_str());
267 } // until the file ends
270 // In case there are exceptions.
273 // Now release the write lock and reaquire the read lock
274 m_priv->rwlock->unlock();
275 m_priv->rwlock->rdlock();
278 const std::string ShibINI::get (const string& header, const string& tag)
280 ReadLock rwlock(m_priv->rwlock);
283 static string empty = "";
293 if (!m_priv->exists(h)) return empty;
295 map<string,string>::const_iterator i = m_priv->table[h].find(t);
296 if (i == m_priv->table[h].end())
301 bool ShibINIPriv::exists(const std::string& header)
304 if (!cs) to_lowercase (h);
306 return (table.find(h) != table.end());
309 bool ShibINI::exists(const std::string& header)
311 ReadLock rwlock(m_priv->rwlock);
314 return m_priv->exists(header);
317 bool ShibINIPriv::exists(const std::string& header, const std::string& tag)
327 if (!exists(h)) return false;
328 return (table[h].find(t) != table[h].end());
331 bool ShibINI::exists(const std::string& header, const std::string& tag)
333 ReadLock rwlock(m_priv->rwlock);
336 return m_priv->exists(header, tag);
339 bool ShibINI::get_tag (string& header, string& tag, bool try_general, string* result)
341 if (!result) return false;
343 m_priv->rwlock->rdlock();
345 m_priv->rwlock->unlock();
347 if (m_priv->exists (header, tag)) {
348 *result = get (header, tag);
351 if (try_general && exists (SHIBTARGET_GENERAL, tag)) {
352 *result = get (SHIBTARGET_GENERAL, tag);
359 void ShibINI::dump (ostream& os)
361 ReadLock rwlock(m_priv->rwlock);
364 os << "File: " << m_priv->file << "\n";
365 os << "Case-Sensitive: " << ( m_priv->cs ? "Yes\n" : "No\n" );
366 os << "File Entries:\n";
368 for (map<string, map<string, string> >::const_iterator i = m_priv->table.begin();
369 i != m_priv->table.end(); i++) {
371 os << "[" << i->first << "]\n";
373 for (map<string,string>::const_iterator j=i->second.begin();
374 j != i->second.end(); j++) {
376 os << " " << j->first << " = " << j->second << "\n";
383 ShibINI::Iterator* ShibINI::header_iterator()
385 ReadLock rwlock(m_priv->rwlock);
387 HeaderIterator* iter = new HeaderIterator(m_priv);
388 return (ShibINI::Iterator*) iter;
391 ShibINI::Iterator* ShibINI::tag_iterator(const std::string& header)
393 ReadLock rwlock(m_priv->rwlock);
395 TagIterator* iter = new TagIterator(m_priv, header);
396 return (ShibINI::Iterator*) iter;
400 // XXX: FIXME: there may be a race condition in the iterators if a
401 // caller holds an active Iterator, the underlying file changes, and
402 // then calls one of the get() routines. It's possible the iterator
403 // may screw up -- I don't know whether the iterator actually depends
404 // on the underlying infrastructure or not.
407 HeaderIterator::HeaderIterator (ShibINIPriv* inip)
409 saml::NDC ndc("HeaderIterator");
412 ini->rwlock->rdlock();
414 ini->log->debug("iterators: %d", ini->iterators);
417 HeaderIterator::~HeaderIterator ()
419 saml::NDC ndc("~HeaderIterator");
421 ini->rwlock->unlock();
422 ini->log->debug("iterators: %d", ini->iterators);
425 const string* HeaderIterator::begin ()
427 iter = ini->table.begin();
428 if (iter == ini->table.end()) {
436 const string* HeaderIterator::next ()
441 if (iter == ini->table.end()) {
448 TagIterator::TagIterator (ShibINIPriv* inip, const string& headerp)
451 saml::NDC ndc("TagIterator");
454 ini->rwlock->rdlock();
456 ini->log->debug("iterators: %d", ini->iterators);
459 TagIterator::~TagIterator ()
461 saml::NDC ndc("~TagIterator");
463 ini->rwlock->unlock();
464 ini->log->debug("iterators: %d", ini->iterators);
467 const string* TagIterator::begin ()
469 iter = ini->table[header].begin();
470 if (iter == ini->table[header].end()) {
478 const string* TagIterator::next ()
483 if (iter == ini->table[header].end()) {
490 bool ShibINI::boolean(string& value)
492 const char* v = value.c_str();
493 #ifdef HAVE_STRCASECMP
494 if (!strncasecmp (v, "on", 2) || !strncasecmp (v, "true", 4) || !strncmp(v, "1", 1))
497 if (!strnicmp (v, "on", 2) || !strnicmp (v, "true", 4) || !strncmp(v, "1", 1))
503 ShibINI::Iterator::~Iterator() {}