Main Page | Namespace List | Class Hierarchy | Class List | Directories | File List | Namespace Members | Class Members | File Members

VASTi.C

Go to the documentation of this file.
00001 // Copyright (C) 2001, Compaq Computer Corporation
00002 // 
00003 // This file is part of Vesta.
00004 // 
00005 // Vesta is free software; you can redistribute it and/or
00006 // modify it under the terms of the GNU Lesser General Public
00007 // License as published by the Free Software Foundation; either
00008 // version 2.1 of the License, or (at your option) any later version.
00009 // 
00010 // Vesta is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013 // Lesser General Public License for more details.
00014 // 
00015 // You should have received a copy of the GNU Lesser General Public
00016 // License along with Vesta; if not, write to the Free Software
00017 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00018 
00019 /* File: VASTi.C                                               */
00020 
00021 #include <dirent.h>
00022 #include <time.h>
00023 extern "C" {
00024     // add declaration not included by broken header files
00025     struct tm *_Plocaltime_r(const time_t *timer, struct tm *result);
00026 }
00027 #include <Basics.H>
00028 #include <VestaConfig.H>
00029 #include <SourceOrDerived.H>
00030 #include <ParCacheC.H>
00031 #include <Timer.H>
00032 #include <ReposUI.H>
00033 #include "VASTi.H"
00034 #include "Lex.H"
00035 #include "Parser.H"
00036 #include "Expr.H"
00037 #include "Val.H"
00038 #include "PrimRunTool.H"
00039 #include "ToolDirectoryServer.H"
00040 #include "Location.H"
00041 #include "Err.H"
00042 #include "Files.H"
00043 #include "ApplyCache.H"
00044 #include "ThreadData.H"
00045 
00046 using std::ios;
00047 using std::ostream;
00048 using std::fstream;
00049 using std::cout;
00050 using std::cerr;
00051 using std::endl;
00052 using std::flush;
00053 
00054 // On some platforms (at least Linux) errno.h doesn't define ESUCCESS
00055 #if !defined(ESUCCESS)
00056 #define ESUCCESS 0
00057 #endif
00058 
00059 static Text modelPath;
00060 static mode_t newUMask = 022, oldUMask;
00061 
00062 // In parallel build, we only want to print out one error stack.
00063 // The variable callStackPrinted controls that. It is protected
00064 // by callStackMu.
00065 static bool callStackPrinted = false;
00066 
00067 void PrintCacheStat(ostream *vout) {
00068   *vout << "\nCaching Stats:\n";
00069   *vout << "    Cache: [" << "Server=" << *ParCacheC::Locate() << ", "
00070         << "MetaData=" << VestaConfig::get_Text("CacheServer", "MetaDataRoot")
00071         << "]\n";
00072   *vout << "    Function: [" << "Calls=" << appCallCounter << ", "
00073         << "Hits=" << appHitCounter << "]\n";
00074   *vout << "    Model: ["
00075         << "Calls=[" << "Special=" << sModelCallCounter << ", "
00076         << "Normal=" << nModelCallCounter << "], "
00077         << "Hits=[" << "Special=" << sModelHitCounter << ", "
00078         << "Normal=" << nModelHitCounter << "]" << "]\n";
00079   *vout << "    RunTool: [" << "Calls=" << toolCallCounter << ", "
00080         << "Hits=" << toolHitCounter << "]\n";
00081 }
00082 
00083 void PrintFuncTrace(ostream *vout) {
00084   if (recordTrace) {
00085     *vout << "\nFunction call graph:\n"
00086           << ThreadDataGet()->traceRes->str();
00087   }
00088 }
00089 
00090 void PrintErrorStack(ostream *vout) {
00091   if (!recordCallStack) return;
00092   callStackMu.lock();
00093   if (callStackPrinted) {
00094     callStackMu.unlock();
00095     return;
00096   }
00097   callStackPrinted = true;
00098 
00099   ThreadData *thdata = ThreadDataGet();
00100   *vout << "\nError stack trace:\n";
00101   int counter = 0;
00102   int st = 0;
00103   for (;;) {
00104     int sz = thdata->callStack->size();
00105     for (int i = st; i < sz; i++) {
00106       Expr expr = thdata->callStack->get(i);
00107       *vout << counter++ << ". " << expr->loc->file << ": line "
00108             << expr->loc->line << ", char " << expr->loc->character
00109             << endl;
00110     }
00111     if (thdata->parent == NULL) break;
00112     st = thdata->parent->callStack->size() - thdata->parentCallStackSize;
00113     thdata = thdata->parent;
00114   }
00115   callStackMu.unlock();  
00116 }
00117 
00118 AssocVC* Lookup(Val val) {
00119   /* Get the component of the value specified by shipFromPath. */
00120   Val result = val;
00121   const char *path = shipFromPath.cchars();
00122   int len = shipFromPath.Length();
00123 
00124   Text arc;
00125   int i = 0;
00126   while (i < len) {
00127     int j = i;
00128     while (j < len && !IsDelimiter(path[j])) j++;
00129     arc = shipFromPath.Sub(i, j);
00130     if (result->vKind == BindingVK) {
00131       AssocVC *as = FindInContext(arc, ((BindingVC*)result)->elems);
00132       if (as == nullAssoc) {
00133         cerr << "Warning: the path `" << shipFromPath
00134              << "' does not exist in the result value.\n";
00135         return as;
00136       }
00137       result = as->val;
00138       i = j + 1;
00139     }
00140     else {
00141       Error(Text("Failed to find the value to ship in ") +
00142             shipFromPath + ".\n");
00143       return NULL;
00144     }
00145   }
00146   return NEW_CONSTR(AssocVC, (arc, result));
00147 }
00148 
00149 int Copy(const char *fromPath, const char *toPath) {
00150   /* Copy the file specified by the fromPath to the file specified
00151      by the toPath.  It returns the errno if fails. */
00152   const int BuffSz = 8192;
00153   char buf[BuffSz];
00154   int fromFd = open(fromPath, O_RDONLY);
00155   if (fromFd == -1) return errno;
00156 
00157   // Determine the mode we should use for the shipped copy.  Start
00158   // with rw priviledges.  (Note that these will be modified by
00159   // umask.)
00160   mode_t to_mode = (S_IRUSR | S_IWUSR |
00161                     S_IRGRP | S_IWGRP |
00162                     S_IROTH | S_IWOTH);
00163   // If the file we're copying has execute permission, then give
00164   // execute permission on the shipped copy.
00165   struct stat from_stat;
00166   if(fstat(fromFd, &from_stat) == 0)
00167     {
00168       if(from_stat.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
00169         {
00170           to_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
00171         }
00172     }
00173   else
00174     {
00175       // This shouldn't happen.  We opened it, so we should be able to
00176       // stat it.
00177       int err = errno;
00178       cerr << "Warning: couldn't stat file to be shipped (`" << fromPath
00179            << "'): " << strerror(err) << endl;
00180 
00181     }
00182 
00183   remove(toPath);
00184   int toFd = creat(toPath, to_mode);
00185   if (toFd == -1) {
00186     close(fromFd);
00187     return errno;
00188   }
00189 
00190   int size;
00191   int eno = ESUCCESS;
00192   while (size = read(fromFd, buf, BuffSz)) {
00193     if (write(toFd, buf, size) == -1) {
00194       eno = errno;
00195       break;
00196     }
00197   }
00198   close(fromFd);
00199   close(toFd);
00200   return eno;
00201 }
00202 
00203 int CleanDir(Text path) {
00204   int err = 0;
00205   int entries = 0;
00206 
00207   // Check if .log exists
00208   struct stat statbuf;
00209   bool havelog = (stat(".log", &statbuf) == 0);
00210 
00211   DIR *dirPt = opendir(".");
00212   if (dirPt == NULL) {
00213     Error(Text("Failed to open the directory ") + path + ".\n");
00214     return -1;
00215   }
00216 
00217   for (dirent *dp = readdir(dirPt); dp != NULL; dp = readdir(dirPt)) {
00218     if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..") && 
00219         strcmp(dp->d_name, ".log")) {
00220       entries++;
00221       if (!forceClean && !havelog) continue;
00222       err = lstat(dp->d_name, &statbuf);
00223       if (S_ISDIR(statbuf.st_mode)) {
00224         // must be a directory. Remove it recursively.
00225         err = chdir(dp->d_name);
00226         if (err) return err;
00227         err = CleanDir(path + dp->d_name + "/");
00228         if (err) return err;
00229         err = chdir("..");
00230         if (err) return err;
00231 
00232         if(!hushedShipping)
00233           cout << "Cleaning " << path << dp->d_name << endl;
00234         err = rmdir(dp->d_name);
00235         if (err) return err;
00236 
00237       } else {
00238         // must be a nondirectory. So, remove it.
00239         if(!hushedShipping)
00240           cout << "Cleaning " << path << dp->d_name << endl;
00241         err = remove(dp->d_name);
00242         if (err) return err;
00243       }
00244     }
00245   }
00246   if (havelog) {
00247     err = remove(".log");
00248     if (err) return err;
00249   }
00250   (void)closedir(dirPt);
00251 
00252   if (!forceClean && !havelog && entries > 0) {
00253     Error(Text("Tried to clean a nonempty directory with no .log file: ")
00254           + shipToPath + ".\n");
00255     return -1;
00256   }
00257 
00258   return 0;
00259 }
00260 
00261 enum ShipKind { CopyShip, SymLinkShip };
00262 const char *KindName[] = { "copy", "link" };
00263 
00264 
00265 
00266 void LogShip(ShortId sid, const char *path,
00267   fstream &logFile, struct tm *shipTM, bool is_dir = false) throw ()
00268 {
00269     char dateStr[20];
00270     sprintf(dateStr, "%02d:%02d:%02d %02d-%02d-%04d",
00271       shipTM->tm_hour, shipTM->tm_min, shipTM->tm_sec,
00272       (shipTM->tm_mon + 1), shipTM->tm_mday, (1900 + shipTM->tm_year));
00273 
00274     logFile << modelPath << ": " << path;
00275     if(is_dir) {
00276       logFile << " -> directory";
00277     }
00278     else {
00279       if (sid) {
00280         char sidHex[20];
00281         int err = sprintf(sidHex, "0x%08x", sid); assert(err >= 0);
00282         logFile << " -> " << sidHex;
00283       } else {
00284         logFile << " -> literal";
00285       }
00286     }
00287     
00288     logFile << ' ' << dateStr << endl;
00289     logFile.flush();
00290 }
00291 
00292 int ShipShortId(ShortId sid, const Text &name, ShipKind kind,
00293                 fstream &logFile, struct tm *shipTM)
00294 throw () {
00295   int err = ESUCCESS;
00296   if (sid != NullShortId) {
00297     if (!hushedShipping)
00298       cout << "Shipping " << name << "..." << endl;
00299     char *path = SourceOrDerived::shortIdToName(sid, /*tailonly=*/ false);
00300     switch (kind) {
00301     case CopyShip:
00302       err = Copy(path, name.cchars());
00303       break;
00304     case SymLinkShip:
00305       remove(name.cchars());
00306       if (symlink(path, name.cchars()) == -1) {
00307         err = errno;
00308       }
00309       break;
00310     default:
00311       assert(false);
00312     }
00313     if (err != ESUCCESS) {
00314       Error(Text("Failed to ") + KindName[(int)kind] + " the file " +
00315             name + ": " + strerror(err) + ".\n");
00316     }
00317     else {
00318       LogShip(sid, name.cchars(), logFile, shipTM);
00319     }
00320   }
00321   return err;
00322 }
00323 
00324 int ShipText(const Text &txt, const Text &name,
00325              fstream &logFile, struct tm *shipTM)
00326 throw () {
00327     if (!hushedShipping)
00328       cout << "Shipping " << name << "..." << endl;
00329     remove(name.cchars());
00330     fstream outFile;
00331     outFile.open(name.cchars(), ios::out);
00332     if (outFile.fail()) {
00333       Error(Text("Failed to create the file ") + name +
00334             ": " + strerror(errno) + ".\n");
00335       return errno;
00336     }
00337     outFile << txt << flush;
00338     if (outFile.fail()) {
00339       Error(Text("Failed to write to the file ") + name +
00340             ": " + strerror(errno) + ".\n");
00341       return errno;
00342     }
00343     outFile.close();
00344     LogShip(0, name.cchars(), logFile, shipTM);
00345     return 0;
00346 }
00347 
00348 int ShipValue(Val value, ShipKind kind, struct tm *shipTM) throw () {
00349   fstream logFile;
00350   logFile.open(dotLogFiles?".log":"/dev/null",
00351                ios::out | ios::app);
00352   if (logFile.fail()) {
00353     Error(Text("Failed to open .log file: ") + strerror(errno) + ".\n");
00354     return -1;
00355   }
00356    
00357   /* Assume that the work dir is the place to ship. */
00358   if (value->vKind == BindingVK) {
00359     BindingVC *bv = (BindingVC*)value;
00360     Context work = bv->elems;
00361     while (!work.Null()) {
00362       Assoc elem = work.Pop();
00363       Val val = elem->val;
00364       ShortId sid;
00365       int err = 0;      
00366       switch (val->vKind) {
00367         case TextVK:
00368           if (((TextVC*)val)->HasSid()) {
00369             sid = ((TextVC*)val)->Sid();
00370             err = ShipShortId(sid, elem->name, kind, logFile, shipTM);
00371           }
00372           else { 
00373             err = ShipText(((TextVC*)val)->NDS(), elem->name, logFile, shipTM);
00374           }
00375           if (err) return err;
00376           break;
00377         case ModelVK:
00378           sid = ((ModelVC*)val)->Sid();
00379           err = ShipShortId(sid, elem->name, kind, logFile, shipTM);
00380           if (err) return err;
00381           break;
00382         case BindingVK:
00383           {
00384             if(!FS::IsDirectory(elem->name.cchars())) {
00385               // directory does not exist; create it
00386               err = mkdir(elem->name.cchars(), 0xfff);
00387               if (err) {
00388                 Error(Text("Failed to create the directory ") +
00389                      elem->name + ": " + strerror(errno) + ".\n");
00390               }
00391             }
00392             LogShip(0, elem->name.cchars(), logFile, shipTM, true);           
00393           }
00394           if (err) return err;
00395           err = chdir(elem->name.cchars());
00396           if (err) return err;
00397           err = ShipValue(val, kind, shipTM);
00398           if (err) return err;    
00399           err = chdir("..");
00400           if (err) return err;
00401           break;
00402         default:
00403           // ignore other cases
00404           break;
00405       }
00406     }
00407   }
00408   logFile.close();
00409   return 0;
00410 }
00411 
00412 bool VestaShip(AssocVC *namedVal) {
00413   /* Change to the directory at shipToPath, empty the directory, if requested,
00414      and then ship the result.  */
00415   int err = 0;
00416   const char *path = shipToPath.cchars();
00417   Val shipValue;
00418   Text log_dir = shipToPath;
00419 
00420   err = chdir(path);
00421   if (!err) {
00422     if (shipClean || forceClean) {
00423       // Empty the directory
00424       if(hushedShipping) {
00425         cout << "Cleaning..." << endl;
00426         cout.flush();
00427       }
00428       Text dirPath = shipToPath;
00429       if(dirPath[dirPath.Length()-1] != '/')
00430         dirPath += "/";
00431       err = CleanDir(dirPath);
00432       if (err) {
00433         Error(Text("Failed to delete the contents of the directory ") +
00434               shipToPath + ".\n");
00435         return false;
00436       }
00437     }
00438     if (namedVal->val->vKind == BindingVK) {
00439       shipValue = namedVal->val;
00440     }
00441     else {
00442       shipValue = NEW_CONSTR(BindingVC, (Context(namedVal)));
00443     }
00444   }
00445   else if ((errno == ENOTDIR || errno == ENOENT) &&
00446            (namedVal->val->vKind == TextVK ||
00447             namedVal->val->vKind == ModelVK)) {
00448     // Shipping to a file
00449     Text filename;
00450     try {
00451       FS::SplitPath(shipToPath, filename, log_dir);
00452     }
00453     catch(FS::Failure f) {
00454         Error(Text("FS failure: ") + f.get_op() + ": " + f.get_errno() + ".\n");
00455         return false;
00456     }
00457     err = chdir(log_dir.cchars());
00458     if(!err) {
00459       shipValue = 
00460         NEW_CONSTR(BindingVC, 
00461                    (Context(NEW_CONSTR(AssocVC, (filename, namedVal->val)))));
00462     }
00463   }
00464 
00465   if(err) {
00466     Error(Text("Failed to cd to the directory ") + log_dir + ": "
00467                + strerror(errno) + ".\n");
00468     return false;
00469   }
00470 
00471   // ship the requested value if no errors have occurred
00472   if (hushedShipping) {
00473     cout << "Shipping...\n";
00474     cout.flush();
00475   }
00476   time_t nowT;
00477   if (time(&nowT) == (time_t)(-1)) {
00478     Error(Text("Getting time of day from time(): ") + strerror(errno) + ".\n");
00479     return false;
00480   }
00481   else {
00482     struct tm buffTM, *nowTM;
00483     nowTM = localtime_r(&nowT, &buffTM);
00484     ShipKind kind = (shipBySymLink ? SymLinkShip : CopyShip);
00485       
00486     /* We still need to add a file lock, since there can be multiple
00487        writers to the same log file. */
00488     err = ShipValue(shipValue, kind, nowTM);
00489   }
00490 
00491   if (hushedShipping) {
00492     cout << "Done!\n";
00493     cout.flush();
00494   }
00495   return (err == 0);
00496 }
00497 
00498 bool Interpret(const Text& model) {
00499   Text prefix;
00500   VestaSource *vSource;
00501   fstream *iFile;
00502   VestaSource::errorCode err;
00503   Expr expr;
00504   Val result;
00505   bool success = true;
00506 
00507   uid_t euid = geteuid();
00508   if (seteuid(getuid()) < 0) {
00509     throw(Evaluator::failure(Text("Failed to switch to the real user-id.\n"),
00510                              false));
00511   }
00512 
00513   try {
00514     modelPath = ReposUI::canonicalize(model);
00515   }
00516   catch(ReposUI::failure f) {
00517     Error(Text("ReposUI failure: ") + f.msg + ".\n");
00518     return false;
00519   }
00520 
00521   if (seteuid(euid) < 0) {
00522     throw(Evaluator::failure(
00523              Text("Failed to switch back to the effective user-id.\n"),
00524              false));      
00525   }
00526   if (!OpenSource(NULL, modelPath, noLoc, /*OUT*/ iFile, /*OUT*/ topModelRoot,
00527                   /*OUT*/ topModelSid, /*OUT*/ vSource)) {
00528     // failed to open the top-level model.
00529     success = false;
00530   }
00531   else {
00532     // Parsing the top-level model:
00533     if (iFile == NULL) {
00534       iFile = NEW(SourceOrDerived);
00535       ((SourceOrDerived*)iFile)->open(topModelSid);
00536       if (iFile->bad()) {
00537         throw(Evaluator::failure(Text("Can't open the model ") + modelPath +
00538                                  "; giving up!\n",
00539                                  false));
00540       }
00541     }
00542     try {
00543       expr = Parse(iFile, modelPath, topModelSid, topModelRoot);
00544     } catch (const char* report) {
00545       // ErrorDetail(report);    // Handle parsing error exception.
00546       success = false;
00547     }
00548     iFile->close();
00549   }
00550   if (!success) {
00551     Error("Failed to parse `" + model + "'.\n\n");
00552     return false;
00553   }
00554   if (parseOnly) {
00555     cout << "Parsing completed.\n";
00556     return true;
00557   }
00558 
00559   // Evaluate:
00560   ModelVC *fun = NULL;
00561   try {
00562     ModelEC *modelExpr = (ModelEC*)expr;
00563     Context cc = ProcessModelHead(modelExpr);
00564     fun = NEW_CONSTR(ModelVC, (modelPath, topModelSid, topModelRoot, 
00565                                modelExpr, cc, vSource));
00566     ArgList args = NEW_CONSTR(ArgListEC, (0, modelExpr->loc));
00567     ApplyEC *ae = NEW_CONSTR(ApplyEC, (NEW_CONSTR(NameEC, (model, noLoc)), 
00568                                        args, modelExpr->loc));
00569     if (recordCallStack) {
00570       ThreadDataGet()->callStack->addlo(modelExpr);
00571     }
00572     result = ApplyModel(fun, ae, conInitial);
00573   } catch (SRPC::failure f) {
00574     Error(Text("SRPC failure (") + IntToText(f.r) + "): " + f.msg + ".\n");
00575     success = false;
00576   } catch (Evaluator::failure f) {
00577     //Error(Text("Vesta evaluation failure; ") + f.msg + ".\n");
00578     success = false;
00579     if (f.r) {
00580       // This feature lets us retry the evaluation on certain errors.
00581       // It is not currently used, and might not work properly.
00582       success = StartEval(model);
00583       exit(success ? 0 : 1);
00584     }
00585   } catch (const char* report) {
00586     // ErrorDetail(report);    // Handle parsing error exception.
00587     success = false;
00588   } catch (ReposUI::failure f) {
00589     Error(Text("ReposUI failure: ") + f.msg + ".\n");
00590     success = false;
00591   } catch (VestaConfig::failure f) {
00592     Error(Text("VestaConfig failure: ") + f.msg + ".\n");
00593     success = false;
00594   }
00595 
00596   // Print evaluation result:
00597   if (success && printResult) {
00598     // When there is no exception, always print the evaluation result,
00599     // which may still contains error.
00600     cout << "\nReturn value of `" << model << "':\n";
00601     result->PrintD(&cout);
00602     cout << "\n";
00603   }
00604 
00605   // Make sure we know if there was an error
00606   if (ErrorCount() > 0) success = false;
00607 
00608   // Print error stack, if the evaluation failed and the stack
00609   // has not already been printed.
00610   if (!success) PrintErrorStack(&cerr);
00611     
00612   // Print function call trace:
00613   PrintFuncTrace(&cout);
00614 
00615   // Print cache stats & Checkpoint cache:
00616   if (cacheOption != 0) {
00617     if (printCacheStats) PrintCacheStat(&cout);
00618     // orphanCIs contains all the cache entries that have no parent 
00619     // in the cache.
00620     CacheEntry::IndicesApp* orphanCIs = ThreadDataGet()->orphanCIs;    
00621     if (fun != NULL && orphanCIs->len > 0)
00622       try {
00623         theCache->Checkpoint(topModelRoot->fptag, topModelSid, *orphanCIs, true);
00624       } catch (SRPC::failure f) {
00625         Error(Text("SRPC failure when checkpointing cache (")
00626               + IntToText(f.r) + "): " + f.msg + ".\n");        
00627         success = false;
00628       }
00629   }
00630 
00631   // Ship the value if the evaluation succeeded.
00632   // First follow the shipFromPath to get the component to be shipped
00633   // from the result, and ship it to the place specified by the shipToPath.
00634   if (success && !shipToPath.Empty()) {
00635     AssocVC *shipValue = Lookup(result);
00636     if (shipValue != NULL) {
00637       if (seteuid(getuid()) < 0) {
00638         Error("Failed to switch to the real user-id.\n");
00639         success = false;
00640       }
00641       else {
00642         umask(oldUMask);
00643         success = VestaShip(shipValue);
00644       }
00645     }
00646   }
00647 
00648   // Print this process's status:
00649   if (psStat) {
00650     cout << "\nEvaluator Process Stats:\n   ";
00651     cout.flush();
00652     Text command("ps -o pid,user,vsz,rss,time,comm -A | grep ");
00653     command += IntToText(getpid());
00654     system(command.chars());
00655   }
00656 
00657   // Flush all the output streams:
00658   cout.flush();
00659   return success;
00660 }
00661 
00662 bool StartEval(const Text& filename) {
00663   // Initialization:
00664   ThreadDataInit();
00665   ErrInit();
00666   LexInit();
00667   ValInit();
00668   PrimInit();
00669   PrimRunToolInit();
00670   StartRenewLeaseThread();
00671 
00672   // Make sure umask used when directly writing deriveds into the
00673   // repository is not too restrictive.  Use the same value that
00674   // the repository uses internally.
00675   try {
00676     newUMask = 022; // documented default
00677     newUMask = VestaConfig::get_int("Repository", "umask");
00678   } catch (VestaConfig::failure) {
00679     // ignore errors, use default
00680   }
00681   oldUMask = umask(newUMask);
00682 
00683   // Evaluation:
00684   return Interpret(filename);
00685 }

Generated on Mon May 8 00:48:41 2006 for Vesta by  doxygen 1.4.2