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();
87 static void trimline (string& s)
89 int end = s.size() - 1, start = 0;
91 // Trim stuff on right.
92 while (end > 0 && !isgraph(s[end])) end--;
94 // Trim stuff on left.
95 while (start < end && !isgraph(s[start])) start++;
98 s = s.substr(start, end - start + 1);
101 static void to_lowercase (string& s)
103 for (int i = 0, sz = s.size(); i < sz; i++)
104 s[i] = tolower(s[i]);
107 ShibINI::~ShibINI() {
111 void ShibINI::init (string& f, bool case_sensitive)
113 m_priv = new ShibINIPriv();
115 m_priv->cs = case_sensitive;
116 m_priv->log->info ("initializing INI file: %s (sensitive=%s)", f.c_str(),
117 (case_sensitive ? "true" : "false"));
119 ReadLock lock(m_priv->rwlock);
124 // Must be called holding the ReadLock.
126 void ShibINI::refresh(void)
128 saml::NDC ndc("refresh");
130 // check if we need to refresh
132 struct _stat stat_buf;
133 if (_stat (m_priv->file.c_str(), &stat_buf) < 0)
135 struct stat stat_buf;
136 if (stat (m_priv->file.c_str(), &stat_buf) < 0)
138 m_priv->log->error("stat failed: %s", m_priv->file.c_str());
141 m_priv->log->info("refresh: last modtime at %d; file is %d; iters: %d",
142 m_priv->modtime, stat_buf.st_mtime, m_priv->iterators);
145 if (m_priv->modtime >= stat_buf.st_mtime || m_priv->iterators > 0)
148 // Release the read lock -- grab the write lock. Don't worry if
149 // this is non-atomic -- we'll recheck the status.
150 m_priv->rwlock->unlock();
151 m_priv->rwlock->wrlock();
153 // Recheck the modtime
154 if (m_priv->modtime >= stat_buf.st_mtime) {
155 // Yep, another thread got to it. We can exit now... Release
156 // the write lock and reaquire the read-lock.
158 m_priv->rwlock->unlock();
159 m_priv->rwlock->rdlock();
163 // Ok, we've got the write lock. Let's update our state.
165 m_priv->modtime = stat_buf.st_mtime;
167 // clear the existing maps
168 m_priv->table.clear();
170 m_priv->log->info("reading %s", m_priv->file.c_str());
175 ifstream infile (m_priv->file.c_str());
177 m_priv->log->warn("cannot open file: %s", m_priv->file.c_str());
178 m_priv->rwlock->unlock();
179 m_priv->rwlock->rdlock();
183 const int MAXLEN = 1024;
184 char linebuffer[MAXLEN];
185 string current_header;
186 bool have_header = false;
189 infile.getline (linebuffer, MAXLEN);
190 string line (linebuffer);
192 if (line[0] == '#') continue;
195 if (line.size() <= 1) continue;
197 if (line[0] == '[') {
201 m_priv->log->info("Found what appears to be a header line");
206 // find the end of the header
207 int endpos = line.find (']');
208 if (endpos == line.npos) {
210 m_priv->log->info("Weird: no end found.. punting");
212 continue; // HUH? No end?
216 current_header = line.substr (1, endpos-1);
217 trimline (current_header);
219 if (!m_priv->cs) to_lowercase (current_header);
221 m_priv->table[current_header] = map<string,string>();
224 m_priv->log->info("current header: \"%s\"", current_header.c_str());
227 } else if (have_header) {
231 m_priv->log->info("Found what appears to be a tag line");
235 int mid = line.find ('=');
237 if (mid == line.npos) {
239 m_priv->log->info("Weird: no '=' found.. punting");
241 continue; // Can't find the value's setting
244 tag = line.substr (0,mid);
245 setting = line.substr (mid+1, line.size()-mid);
250 if (!m_priv->cs) to_lowercase (tag);
252 // If it already exists, log an error and do not save it
253 if (m_priv->exists (current_header, tag))
254 m_priv->log->error("Duplicate tag found in section %s: \"%s\"",
255 current_header.c_str(), tag.c_str());
257 (m_priv->table[current_header])[tag] = setting;
260 m_priv->log->info("new tag: \"%s\" = \"%s\"",
261 tag.c_str(), setting.c_str());
266 } // until the file ends
269 // In case there are exceptions.
272 // Now release the write lock and reaquire the read lock
273 m_priv->rwlock->unlock();
274 m_priv->rwlock->rdlock();
277 const std::string ShibINI::get (const string& header, const string& tag)
279 ReadLock rwlock(m_priv->rwlock);
282 static string empty = "";
292 if (!m_priv->exists(h)) return empty;
294 map<string,string>::const_iterator i = m_priv->table[h].find(t);
295 if (i == m_priv->table[h].end())
300 bool ShibINIPriv::exists(const std::string& header)
303 if (!cs) to_lowercase (h);
305 return (table.find(h) != table.end());
308 bool ShibINI::exists(const std::string& header)
310 ReadLock rwlock(m_priv->rwlock);
313 return m_priv->exists(header);
316 bool ShibINIPriv::exists(const std::string& header, const std::string& tag)
326 if (!exists(h)) return false;
327 return (table[h].find(t) != table[h].end());
330 bool ShibINI::exists(const std::string& header, const std::string& tag)
332 ReadLock rwlock(m_priv->rwlock);
335 return m_priv->exists(header, tag);
338 bool ShibINI::get_tag (string& header, string& tag, bool try_general, string* result)
340 if (!result) return false;
342 m_priv->rwlock->rdlock();
344 m_priv->rwlock->unlock();
346 if (m_priv->exists (header, tag)) {
347 *result = get (header, tag);
350 if (try_general && exists (SHIBTARGET_GENERAL, tag)) {
351 *result = get (SHIBTARGET_GENERAL, tag);
358 void ShibINI::dump (ostream& os)
360 ReadLock rwlock(m_priv->rwlock);
363 os << "File: " << m_priv->file << "\n";
364 os << "Case-Sensitive: " << ( m_priv->cs ? "Yes\n" : "No\n" );
365 os << "File Entries:\n";
367 for (map<string, map<string, string> >::const_iterator i = m_priv->table.begin();
368 i != m_priv->table.end(); i++) {
370 os << "[" << i->first << "]\n";
372 for (map<string,string>::const_iterator j=i->second.begin();
373 j != i->second.end(); j++) {
375 os << " " << j->first << " = " << j->second << "\n";
382 ShibINI::Iterator* ShibINI::header_iterator()
384 ReadLock rwlock(m_priv->rwlock);
386 HeaderIterator* iter = new HeaderIterator(m_priv);
387 return (ShibINI::Iterator*) iter;
390 ShibINI::Iterator* ShibINI::tag_iterator(const std::string& header)
392 ReadLock rwlock(m_priv->rwlock);
394 TagIterator* iter = new TagIterator(m_priv, header);
395 return (ShibINI::Iterator*) iter;
399 // XXX: FIXME: there may be a race condition in the iterators if a
400 // caller holds an active Iterator, the underlying file changes, and
401 // then calls one of the get() routines. It's possible the iterator
402 // may screw up -- I don't know whether the iterator actually depends
403 // on the underlying infrastructure or not.
406 HeaderIterator::HeaderIterator (ShibINIPriv* inip)
410 ini->rwlock->rdlock();
414 HeaderIterator::~HeaderIterator ()
417 ini->rwlock->unlock();
420 const string* HeaderIterator::begin ()
422 iter = ini->table.begin();
423 if (iter == ini->table.end()) {
431 const string* HeaderIterator::next ()
436 if (iter == ini->table.end()) {
443 TagIterator::TagIterator (ShibINIPriv* inip, const string& headerp)
448 ini->rwlock->rdlock();
452 TagIterator::~TagIterator ()
455 ini->rwlock->unlock();
458 const string* TagIterator::begin ()
460 iter = ini->table[header].begin();
461 if (iter == ini->table[header].end()) {
469 const string* TagIterator::next ()
474 if (iter == ini->table[header].end()) {
481 bool ShibINI::boolean(string& value)
483 const char* v = value.c_str();
484 #ifdef HAVE_STRCASECMP
485 if (!strncasecmp (v, "on", 2) || !strncasecmp (v, "true", 4) || !strncmp(v, "1", 1))
488 if (!strnicmp (v, "on", 2) || !strnicmp (v, "true", 4) || !strncmp(v, "1", 1))