00001
00002
00003 #include "Iguana/Framework/interface/IgModuleCache.h"
00004 #include "Iguana/Framework/interface/IgModuleDescriptor.h"
00005 #include "Iguana/Framework/interface/IgModule.h"
00006 #include "Iguana/Framework/interface/IgPluginManager.h"
00007 #include "Iguana/Framework/interface/IgPluginError.h"
00008 #include "IgPluginParserError.h"
00009 #include "debug.h"
00010 #include "classlib/utils/DebugAids.h"
00011 #include "classlib/utils/Log.h"
00012 #include "classlib/iobase/DirIterator.h"
00013 #include "classlib/utils/SharedLibrary.h"
00014 #include "classlib/utils/StringFormat.h"
00015 #include <algorithm>
00016 #include <cstdio>
00017 #include <cstring>
00018 #include <cstdlib>
00019 #include <cctype>
00020 #include <cerrno>
00021 #include <sys/stat.h>
00022 #include <sstream>
00023 #include <vector>
00024 #include <string>
00025
00026
00027
00028
00029 static const char SAFE [] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
00030 "abcdefghijklmnopqrstuvwxyz"
00031 "01234567890.,/_-" };
00032 static const char HEX [] = { "0123456789abcdef" };
00033
00034
00035
00036 class IgModuleCache::FileByName
00037 {
00038 lat::Filename m_name;
00039 public:
00040 FileByName (const lat::Filename &name) : m_name (name) {}
00041 bool operator() (const IgModule *info)
00042 { return info->libraryName().nondirectory() == m_name; }
00043 };
00044
00045
00046
00047
00048
00049 const char IgModuleCache::s_cacheFile [] = ".iglets";
00050 const char IgModuleCache::s_cacheTag [] = "cache";
00051
00052
00053
00056 static std::string
00057 encode (const std::string &input)
00058 {
00059 std::string result;
00060 result.reserve (input.size ());
00061 for (const char *s = input.c_str (); *s; ++s)
00062 if (strchr (SAFE, *s))
00063 result += *s;
00064 else
00065 {
00066 result += '%';
00067 result += HEX [((unsigned char) *s >> 4) & 0xf];
00068 result += HEX [((unsigned char) *s ) & 0xf];
00069 }
00070 return result;
00071 }
00072
00076 static std::string
00077 decode (const std::string &input)
00078 {
00079 std::string result;
00080 result.reserve (input.size ());
00081 for (const char *s = input.c_str (); *s; ++s)
00082 if (strchr (SAFE, *s))
00083 result += *s;
00084 else if (*s == '%' && isxdigit (s[1]) && isxdigit (s[2]))
00085 {
00086 result += (char) (((strchr (HEX, tolower (s[1])) - HEX) << 4)
00087 + ((strchr (HEX, tolower (s[2])) - HEX)));
00088 s += 2;
00089 }
00090 else
00091 throw IgPluginError (new IgPluginParserError ("bad character"));
00092
00093 return result;
00094 }
00095
00096
00097
00098
00099
00103 IgModuleCache::IgModuleCache (IgPluginManager *manager, const lat::Filename &directory)
00104 : m_manager (manager),
00105 m_directory (directory.asDirectory ())
00106 {
00107 ASSERT (! m_directory.empty ());
00108
00109 m_state.status = CACHE_MISSING;
00110 m_state.added = m_state.removed
00111 = m_state.changed
00112 = m_state.skipped
00113 = false;
00114
00115 m_parse.module = 0;
00116 refresh ();
00117 }
00118
00120 IgModuleCache::~IgModuleCache (void)
00121 {
00122 LOG (0, trace, LFplugin_manager,
00123 "destroying module cache for `" << m_directory << "'\n" << lat::indent);
00124 ASSERT (! m_directory.empty ());
00125 for (ModuleIterator m = m_cache.begin (); m != m_cache.end (); ++m)
00126 delete *m;
00127
00128 LOG (0, trace, LFplugin_manager, lat::undent);
00129 }
00130
00132 lat::Filename
00133 IgModuleCache::directory (void) const
00134 { return m_directory; }
00135
00137 IgModuleCache::Iterator
00138 IgModuleCache::begin (void) const
00139 { return m_modules.begin (); }
00140
00142 IgModuleCache::Iterator
00143 IgModuleCache::end (void) const
00144 { return m_modules.end (); }
00145
00149 void
00150 IgModuleCache::refresh (void)
00151 {
00152 load ();
00153 rebuild ();
00154 notify ();
00155 update ();
00156 }
00157
00166 bool
00167 IgModuleCache::parse (const lat::Filename &file, IgModuleDescriptor *root )
00168 {
00169
00170 struct stat statbuf;
00171 lat::Filename filename (m_directory, file);
00172 bool errors = false;
00173 IgModuleDescriptor *regdata = root;
00174
00175 if (::stat (filename, &statbuf) == -1)
00176 {
00177 delete regdata;
00178 return true;
00179 }
00180
00181 LOG (0, trace, LFplugin_manager, "parsing file: " << file << "\n");
00182
00183 ASSERT (m_parse.result.empty ());
00184 ASSERT (! m_parse.module);
00185
00186
00187 try {
00188
00189 FILE *f = fopen (filename, "r");
00190 if (! f)
00191 throw IgPluginError (new IgPluginParserError ("can't read the file"));
00192
00193 typedef std::vector<IgModuleDescriptor *> ContextStack;
00194 ContextStack stack;
00195 int offset = 0;
00196 int ch;
00197
00198
00199
00200
00201
00202 while (root)
00203 {
00204 ++offset;
00205 stack.push_back (root);
00206 root = root->children () ? root->child (0) : 0;
00207 }
00208
00209
00210 do {
00211 std::string token;
00212 int level = offset;
00213
00214 while ((ch = fgetc (f)) != EOF && ch == ' ')
00215 ++level;
00216
00217 if (ch == EOF)
00218 break;
00219
00220 if (level == 0 && regdata)
00221 {
00222 fclose (f);
00223 throw IgPluginError (new IgPluginParserError ("duplicate top level line"));
00224 }
00225
00226 stack.resize (level+1);
00227 IgModuleDescriptor *doc
00228 = new IgModuleDescriptor (level ? stack [level-1] : 0);
00229 stack [level] = doc;
00230 if (level == 0)
00231 regdata = doc;
00232
00233 for ( ; ch != EOF; ch = fgetc (f))
00234 if (ch == '\n')
00235 {
00236 if (! token.empty ())
00237 doc->addToken (decode (token));
00238
00239 if (doc->tokens () == 0)
00240 {
00241 fclose (f);
00242 throw IgPluginError (new IgPluginParserError ("empty line"));
00243 }
00244
00245 break;
00246 }
00247 else if (ch == ' ')
00248 {
00249 if (! token.empty ())
00250 doc->addToken (decode (token));
00251 token = "";
00252 }
00253 else
00254 token += ch;
00255 } while (ch != EOF);
00256
00257 fclose (f);
00258 reconstruct (regdata);
00259
00260 ASSERT (! m_parse.module);
00261 } catch (lat::Error &e) {
00262 m_manager->feedback (IgPluginManager::ErrorBadCacheFile, file, &e);
00263
00264
00265 LOG (0, error, LFplugin_manager,
00266 file << ": error, file ignored (" << e.explain() << ")\n");
00267
00268 errors = true;
00269 } catch (...) {
00270 m_manager->feedback (IgPluginManager::Other, file);
00271
00272
00273 LOG (0, error, LFplugin_manager,
00274 file << ": unknown error, file ignored\n");
00275
00276 errors = true;
00277 }
00278
00279 delete regdata;
00280
00281 if (errors)
00282 {
00283 delete m_parse.module;
00284 m_parse.module = 0;
00285
00286 for (ModuleIterator m = m_parse.result.begin ();
00287 m != m_parse.result.end (); ++m)
00288 delete *m;
00289
00290 m_parse.result.clear ();
00291 }
00292
00293 return !errors;
00294 }
00295
00302 void
00303 IgModuleCache::load (void)
00304 {
00305 LOG (0, trace, LFplugin_manager, "loading cache "
00306 << lat::Filename (m_directory, s_cacheFile) << '\n' << lat::indent);
00307
00308 m_state.status = CACHE_MISSING;
00309 m_state.added = m_state.removed
00310 = m_state.changed
00311 = m_state.skipped
00312 = false;
00313
00314 if (parse (s_cacheFile))
00315 {
00316 m_state.status = CACHE_OLD;
00317
00318
00319
00320 for (ModuleIterator m = m_cache.begin (); m != m_cache.end (); ++m)
00321 delete *m;
00322
00323 m_cache.clear ();
00324 m_cache.splice (m_cache.end (), m_parse.result);
00325 }
00326
00327 LOG (0, trace, LFplugin_manager, lat::undent << "done\n");
00328 }
00329
00331 IgModuleDescriptor *
00332 IgModuleCache::makeBad (const std::string &file, const std::string &time)
00333 {
00334 IgPluginManager::get ()->feedback (IgPluginManager::ErrorBadCacheFile, file);
00335 IgModuleDescriptor *top = new IgModuleDescriptor (0, s_cacheTag);
00336 new IgModuleDescriptor (top, IgModule::tag (), file, time, "bad");
00337 return top;
00338 }
00339
00343 IgModuleCache::CacheStatus
00344 IgModuleCache::scanModules (ModuleList &cache)
00345 {
00346 LOG (0, trace, LFplugin_manager,
00347 "scanning `" << m_directory << "'\n" << lat::indent);
00348
00349 CacheStatus result = CACHE_MISSING;
00350 lat::DirIterator file (m_directory);
00351 lat::DirIterator end;
00352
00353 ASSERT (cache.empty ());
00354 for (; file != end; ++file)
00355 {
00356
00357 if (*file == s_cacheFile)
00358 continue;
00359
00360
00361
00362 struct stat statbuf;
00363 lat::Filename filename (m_directory, *file);
00364 if (filename.extension () != "iglet")
00365 continue;
00366
00367
00368 int status = stat (filename, &statbuf);
00369 if (status == -1)
00370 {
00371 LOG (0, error, LFplugin_manager,
00372 "stat() failed with error " << errno << ", ignoring `"
00373 << *file << "'\n");
00374 m_state.skipped = true;
00375 continue;
00376 }
00377
00378
00379
00380 ModuleIterator match
00381 = std::find_if (m_cache.begin(), m_cache.end(),
00382 FileByName (*file));
00383
00384 if (match == m_cache.end ())
00385 {
00386 LOG (0, trace, LFplugin_manager,
00387 "==> new module definition `" << *file << "'\n");
00388 m_state.added = true;
00389 }
00390 else if ((*match)->time () != (unsigned long) statbuf.st_mtime)
00391 {
00392 LOG (0, trace, LFplugin_manager,
00393 "==> modified module definition `" << *file << "' (file is "
00394 << statbuf.st_mtime << ", cached was " << (*match)->time ()
00395 << ")\n");
00396 m_state.changed = true;
00397 }
00398 else
00399 {
00400
00401
00402
00403
00404 LOG (0, trace, LFplugin_manager,
00405 "==> unchanged definition `" << *file << "'\n");
00406 cache.splice (cache.end (), m_cache, match);
00407 continue;
00408 }
00409
00410
00411 std::string time = lat::StringFormat ("%1").arg (statbuf.st_mtime);
00412 IgModuleDescriptor *top = new IgModuleDescriptor (0, s_cacheTag);
00413 new IgModuleDescriptor (top, IgModule::tag (), *file, time, "good");
00414 ASSERT (m_parse.result.empty ());
00415 ASSERT (! m_parse.module);
00416 reconstruct (top);
00417 ASSERT (! m_parse.module);
00418
00419
00420
00421
00422
00423
00424
00425 ASSERT (m_parse.result.size () == 1);
00426 for (ModuleIterator m = m_parse.result.begin () ; m != m_parse.result.end (); ++m)
00427 try
00428 {
00429 (*m)->load ();
00430 (*m)->query ();
00431 (*m)->unload ();
00432 }
00433 catch (lat::Error &e)
00434 {
00435 m_manager->feedback (IgPluginManager::ErrorBadModule,
00436 (*m)->libraryName (), &e);
00437
00438
00439 LOG (0, error, LFplugin_manager,
00440 *file << ": bad module library `"
00441 << (*m)->libraryName ()
00442 << "', module ignored\n (error was: "
00443 << e.explain () << ")\n");
00444 m_state.skipped = true;
00445 (*m)->bad (true);
00446 break;
00447 }
00448
00449
00450 cache.splice (cache.end (), m_parse.result);
00451 }
00452
00453
00454 for (ModuleIterator m = m_cache.begin (); m != m_cache.end (); ++m)
00455 if (std::find_if (cache.begin (), cache.end (),
00456 FileByName ((*m)->libraryName ().nondirectory ()))
00457 == cache.end ())
00458 {
00459 LOG (0, trace, LFplugin_manager,
00460 "==> removed module `" << (*m)->libraryName () << "'\n");
00461 m_state.removed = true;
00462 }
00463
00464
00465
00466
00467 result = (m_state.added
00468 || m_state.removed
00469 || m_state.changed
00470 || m_state.skipped)
00471 ? CACHE_OLD : CACHE_VALID;
00472
00473 LOG (0, trace, LFplugin_manager,
00474 lat::undent << "done (" << (int) result << ")\n");
00475 return result;
00476 }
00477
00482 void
00483 IgModuleCache::rebuild (void)
00484 {
00485 LOG (0, trace, LFplugin_manager,
00486 "rebuilding cache in `" << m_directory << "'\n" << lat::indent);
00487
00488
00489
00490
00491
00492 ModuleList cache;
00493 m_state.status = scanModules (cache);
00494 m_cache.swap (cache);
00495 for (ModuleIterator m = cache.begin (); m != cache.end (); ++m)
00496 delete *m;
00497
00498 LOG (0, trace, LFplugin_manager,
00499 lat::undent << "done (" << (int) m_state.status
00500 << ", added = " << m_state.added
00501 << ", removed = " << m_state.removed
00502 << ", changed = " << m_state.changed
00503 << ", skipped = " << m_state.skipped
00504 << ")\n");
00505 }
00506
00508 void
00509 IgModuleCache::notify (void)
00510 {
00511 LOG (0, trace, LFplugin_manager, "collecting modules\n" << lat::indent);
00512 for (ModuleIterator m = m_cache.begin (); m != m_cache.end (); ++m)
00513 if (! (*m)->bad ()) m_modules.push_back (*m);
00514 LOG (0, trace, LFplugin_manager, lat::undent);
00515 }
00516
00520
00524 void
00525 IgModuleCache::write (std::ostream &s, IgModuleDescriptor *doc, int level)
00526 {
00527 s << std::string (level, ' ') << encode (doc->token (0));
00528 for (unsigned i = 1; i < doc->tokens (); ++i)
00529 s << ' ' << encode (doc->token (i));
00530 s << std::endl;
00531
00532 for (unsigned i = 0; i < doc->children (); ++i)
00533 write (s, doc->child (i), level+1);
00534 }
00535
00537 void
00538 IgModuleCache::update (void)
00539 {
00540 if (m_state.status == CACHE_VALID)
00541 return;
00542
00543
00544 LOG (0, trace, LFplugin_manager,
00545 "updating cache in `" << m_directory << "'\n" << lat::indent);
00546
00547
00548 IgModuleDescriptor *all = new IgModuleDescriptor (0, s_cacheTag);
00549 for (ModuleIterator m = m_cache.begin (); m != m_cache.end (); ++m)
00550 (*m)->cache (all);
00551
00552 std::ostringstream buf;
00553 write (buf, all);
00554 delete all;
00555
00556 lat::Filename filename (m_directory, s_cacheFile);
00557 FILE *file = STDC::fopen (filename, "wb");
00558
00559 if (! file)
00560 {
00561 LOG (0, error, LFplugin_manager,
00562 filename << ": error " << errno << ", unable to open the cache"
00563 " for writing -- cache not updated\n" << lat::undent);
00564 return;
00565 }
00566
00567 std::string data (buf.str ());
00568 if (STDC::fwrite (data.c_str (), 1, data.size (), file) != data.size ())
00569 {
00570 LOG (0, error, LFplugin_manager,
00571 filename << ": error " << errno << " while writing cache out"
00572 " -- forcing file truncation\n");
00573
00574 file = STDC::freopen (filename, "wb", file);
00575 }
00576 else
00577 m_state.status = CACHE_VALID;
00578
00579 STDC::fclose (file);
00580
00581 LOG (0, trace, LFplugin_manager, lat::undent << "done\n");
00582 }
00583
00587
00588 void
00589 IgModuleCache::reconstruct (IgModuleDescriptor *info)
00590 {
00591
00592 if (! info || ! info->tokens ())
00593 throw IgPluginError (new IgPluginParserError ("empty descriptor"));
00594
00595
00596 if (info->token (0) == s_cacheTag)
00597 {
00598 if (info->tokens () != 1)
00599 throw IgPluginError (new IgPluginParserError
00600 ("`cache' does not take arguments"));
00601 else if (info->parent ())
00602 throw IgPluginError (new IgPluginParserError
00603 ("`cache' must be at the root"));
00604
00605 for (unsigned i = 0; i < info->children (); ++i)
00606 reconstruct (info->child (i));
00607
00608 ASSERT (! m_parse.module);
00609 }
00610
00611 else if (info->token (0) == IgModule::tag ())
00612 {
00613 if (info->tokens () != 4)
00614 throw IgPluginError (new IgPluginParserError
00615 ("`module' must have three arguments"));
00616 if (! info->parent () || info->parent ()->token (0) != s_cacheTag)
00617 throw IgPluginError (new IgPluginParserError
00618 ("`module' must be under `cache'"));
00619 if (info->token (3) != "good" && info->token (3) != "bad")
00620 throw IgPluginError (new IgPluginParserError
00621 ("`module' last argument must be"
00622 " 'good' or 'bad'"));
00623
00624 ASSERT (! m_parse.module);
00625 m_parse.module = new IgModule (m_manager,
00626 lat::Filename (m_directory, info->token (1)),
00627 info->token (2),
00628 info->token (3));
00629
00630 for (unsigned i = 0; i < info->children (); ++i)
00631 reconstruct (info->child (i));
00632
00633 ASSERT (m_parse.module);
00634 m_parse.result.push_back (m_parse.module);
00635 m_parse.module = 0;
00636 }
00637
00638 else
00639 {
00640 if (! m_parse.module)
00641 throw IgPluginError (new IgPluginParserError
00642 (info->token (0) + " unexpected"));
00643
00644 if (info->tokens () >= 2)
00645 LOG (0, trace, LFplugin_manager, "processing "
00646 << info->token(0) << ' ' << info->token(1) << '\n');
00647
00648 m_parse.module->restore (info);
00649 }
00650 }