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"
17 # define SHIBTARGET_EXPORTS __declspec(dllexport)
20 #include "shib-target.h"
21 #include <shib/shib-threads.h>
28 #include <sys/types.h>
31 #include <log4cpp/Category.hh>
34 using namespace shibboleth;
35 using namespace shibtarget;
37 class HeaderIterator : public shibtarget::ShibINI::Iterator {
39 HeaderIterator (ShibINIPriv* ini);
42 const string* begin();
46 map<string, map<string,string> >::const_iterator iter;
50 class TagIterator : public ShibINI::Iterator {
52 TagIterator (ShibINIPriv* ini, const string& header);
55 const string* begin();
60 map<string,string>::const_iterator iter;
64 class shibtarget::ShibINIPriv {
67 ~ShibINIPriv() { delete rwlock; }
68 log4cpp::Category *log;
70 map<string, map<string, string> > table;
74 unsigned long modtime;
76 unsigned long iterators;
79 bool exists(const std::string& header);
80 bool exists(const std::string& header, const std::string& tag);
83 ShibINIPriv::ShibINIPriv()
85 string ctx = "shibtarget.ShibINI";
86 log = &(log4cpp::Category::getInstance(ctx));
87 rwlock = RWLock::create();
92 static void trimline (string& s)
94 int end = s.size() - 1, start = 0;
96 // Trim stuff on right.
97 while (end > 0 && !isgraph(s[end])) end--;
99 // Trim stuff on left.
100 while (start < end && !isgraph(s[start])) start++;
102 // Modify the string.
103 s = s.substr(start, end - start + 1);
106 static void to_lowercase (string& s)
108 for (int i = 0, sz = s.size(); i < sz; i++)
109 s[i] = tolower(s[i]);
112 ShibINI::~ShibINI() {
116 void ShibINI::init (string& f, bool case_sensitive)
118 m_priv = new ShibINIPriv();
120 m_priv->cs = case_sensitive;
121 m_priv->log->info ("initializing INI file: %s (sensitive=%s)", f.c_str(),
122 (case_sensitive ? "true" : "false"));
124 ReadLock lock(m_priv->rwlock);
129 // Must be called holding the ReadLock.
131 void ShibINI::refresh(void)
133 saml::NDC ndc("refresh");
135 // check if we need to refresh
137 struct _stat stat_buf;
138 if (_stat (m_priv->file.c_str(), &stat_buf) < 0)
140 struct stat stat_buf;
141 if (stat (m_priv->file.c_str(), &stat_buf) < 0)
143 m_priv->log->error("stat failed: %s", m_priv->file.c_str());
146 m_priv->log->info("refresh: last modtime at %d; file is %d; iters: %d",
147 m_priv->modtime, stat_buf.st_mtime, m_priv->iterators);
150 if (m_priv->modtime >= stat_buf.st_mtime || m_priv->iterators > 0)
153 // Release the read lock -- grab the write lock. Don't worry if
154 // this is non-atomic -- we'll recheck the status.
155 m_priv->rwlock->unlock();
156 m_priv->rwlock->wrlock();
158 // Recheck the modtime
159 if (m_priv->modtime >= stat_buf.st_mtime) {
160 // Yep, another thread got to it. We can exit now... Release
161 // the write lock and reaquire the read-lock.
163 m_priv->rwlock->unlock();
164 m_priv->rwlock->rdlock();
168 // Ok, we've got the write lock. Let's update our state.
170 m_priv->modtime = stat_buf.st_mtime;
172 // clear the existing maps
173 m_priv->table.clear();
175 m_priv->log->info("reading %s", m_priv->file.c_str());
180 ifstream infile (m_priv->file.c_str());
182 m_priv->log->warn("cannot open file: %s", m_priv->file.c_str());
183 m_priv->rwlock->unlock();
184 m_priv->rwlock->rdlock();
188 const int MAXLEN = 1024;
189 char linebuffer[MAXLEN];
190 string current_header;
191 bool have_header = false;
194 infile.getline (linebuffer, MAXLEN);
195 string line (linebuffer);
197 if (line[0] == '#') continue;
200 if (line.size() <= 1) continue;
202 if (line[0] == '[') {
206 m_priv->log->info("Found what appears to be a header line");
211 // find the end of the header
212 int endpos = line.find (']');
213 if (endpos == line.npos) {
215 m_priv->log->info("Weird: no end found.. punting");
217 continue; // HUH? No end?
221 current_header = line.substr (1, endpos-1);
222 trimline (current_header);
224 if (!m_priv->cs) to_lowercase (current_header);
226 m_priv->table[current_header] = map<string,string>();
229 m_priv->log->info("current header: \"%s\"", current_header.c_str());
232 } else if (have_header) {
236 m_priv->log->info("Found what appears to be a tag line");
240 int mid = line.find ('=');
242 if (mid == line.npos) {
244 m_priv->log->info("Weird: no '=' found.. punting");
246 continue; // Can't find the value's setting
249 tag = line.substr (0,mid);
250 setting = line.substr (mid+1, line.size()-mid);
255 if (!m_priv->cs) to_lowercase (tag);
257 // If it already exists, log an error and do not save it
258 if (m_priv->exists (current_header, tag))
259 m_priv->log->error("Duplicate tag found in section %s: \"%s\"",
260 current_header.c_str(), tag.c_str());
262 (m_priv->table[current_header])[tag] = setting;
265 m_priv->log->info("new tag: \"%s\" = \"%s\"",
266 tag.c_str(), setting.c_str());
271 } // until the file ends
274 // In case there are exceptions.
277 // Now release the write lock and reaquire the read lock
278 m_priv->rwlock->unlock();
279 m_priv->rwlock->rdlock();
282 const std::string ShibINI::get (const string& header, const string& tag)
284 ReadLock rwlock(m_priv->rwlock);
287 static string empty = "";
297 if (!m_priv->exists(h)) return empty;
299 map<string,string>::const_iterator i = m_priv->table[h].find(t);
300 if (i == m_priv->table[h].end())
305 bool ShibINIPriv::exists(const std::string& header)
308 if (!cs) to_lowercase (h);
310 return (table.find(h) != table.end());
313 bool ShibINI::exists(const std::string& header)
315 ReadLock rwlock(m_priv->rwlock);
318 return m_priv->exists(header);
321 bool ShibINIPriv::exists(const std::string& header, const std::string& tag)
331 if (!exists(h)) return false;
332 return (table[h].find(t) != table[h].end());
335 bool ShibINI::exists(const std::string& header, const std::string& tag)
337 ReadLock rwlock(m_priv->rwlock);
340 return m_priv->exists(header, tag);
343 bool ShibINI::get_tag (string& header, string& tag, bool try_general, string* result)
345 if (!result) return false;
347 m_priv->rwlock->rdlock();
349 m_priv->rwlock->unlock();
351 if (m_priv->exists (header, tag)) {
352 *result = get (header, tag);
355 if (try_general && exists (SHIBTARGET_GENERAL, tag)) {
356 *result = get (SHIBTARGET_GENERAL, tag);
363 void ShibINI::dump (ostream& os)
365 ReadLock rwlock(m_priv->rwlock);
368 os << "File: " << m_priv->file << "\n";
369 os << "Case-Sensitive: " << ( m_priv->cs ? "Yes\n" : "No\n" );
370 os << "File Entries:\n";
372 for (map<string, map<string, string> >::const_iterator i = m_priv->table.begin();
373 i != m_priv->table.end(); i++) {
375 os << "[" << i->first << "]\n";
377 for (map<string,string>::const_iterator j=i->second.begin();
378 j != i->second.end(); j++) {
380 os << " " << j->first << " = " << j->second << "\n";
387 ShibINI::Iterator* ShibINI::header_iterator()
389 ReadLock rwlock(m_priv->rwlock);
391 HeaderIterator* iter = new HeaderIterator(m_priv);
392 return (ShibINI::Iterator*) iter;
395 ShibINI::Iterator* ShibINI::tag_iterator(const std::string& header)
397 ReadLock rwlock(m_priv->rwlock);
399 TagIterator* iter = new TagIterator(m_priv, header);
400 return (ShibINI::Iterator*) iter;
404 // XXX: FIXME: there may be a race condition in the iterators if a
405 // caller holds an active Iterator, the underlying file changes, and
406 // then calls one of the get() routines. It's possible the iterator
407 // may screw up -- I don't know whether the iterator actually depends
408 // on the underlying infrastructure or not.
411 HeaderIterator::HeaderIterator (ShibINIPriv* inip)
413 saml::NDC ndc("HeaderIterator");
416 ini->rwlock->rdlock();
418 ini->log->debug("iterators: %d", ini->iterators);
421 HeaderIterator::~HeaderIterator ()
423 saml::NDC ndc("~HeaderIterator");
425 ini->rwlock->unlock();
426 ini->log->debug("iterators: %d", ini->iterators);
429 const string* HeaderIterator::begin ()
431 iter = ini->table.begin();
432 if (iter == ini->table.end()) {
440 const string* HeaderIterator::next ()
445 if (iter == ini->table.end()) {
452 TagIterator::TagIterator (ShibINIPriv* inip, const string& headerp)
455 saml::NDC ndc("TagIterator");
458 ini->rwlock->rdlock();
460 ini->log->debug("iterators: %d", ini->iterators);
463 TagIterator::~TagIterator ()
465 saml::NDC ndc("~TagIterator");
467 ini->rwlock->unlock();
468 ini->log->debug("iterators: %d", ini->iterators);
471 const string* TagIterator::begin ()
473 iter = ini->table[header].begin();
474 if (iter == ini->table[header].end()) {
482 const string* TagIterator::next ()
487 if (iter == ini->table[header].end()) {
494 bool ShibINI::boolean(string& value)
496 const char* v = value.c_str();
497 #ifdef HAVE_STRCASECMP
498 if (!strncasecmp (v, "on", 2) || !strncasecmp (v, "true", 4) || !strncmp(v, "1", 1))
501 if (!strnicmp (v, "on", 2) || !strnicmp (v, "true", 4) || !strncmp(v, "1", 1))
507 ShibINI::Iterator::~Iterator() {}