actions.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/trunk/src/utils/actions.cpp $
00003   version : $LastChangedRevision: 746 $  $LastChangedBy: jdetaeye $
00004   date : $LastChangedDate: 2008-04-19 11:23:51 +0200 (Sat, 19 Apr 2008) $
00005  ***************************************************************************/
00006 
00007 /***************************************************************************
00008  *                                                                         *
00009  * Copyright (C) 2007 by Johan De Taeye                                    *
00010  *                                                                         *
00011  * This library is free software; you can redistribute it and/or modify it *
00012  * under the terms of the GNU Lesser General Public License as published   *
00013  * by the Free Software Foundation; either version 2.1 of the License, or  *
00014  * (at your option) any later version.                                     *
00015  *                                                                         *
00016  * This library is distributed in the hope that it will be useful,         *
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
00019  * General Public License for more details.                                *
00020  *                                                                         *
00021  * You should have received a copy of the GNU Lesser General Public        *
00022  * License along with this library; if not, write to the Free Software     *
00023  * Foundation Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA *
00024  *                                                                         *
00025  ***************************************************************************/
00026 
00027 #define FREPPLE_CORE
00028 #include "frepple/utils.h"
00029 
00030 
00031 // These headers are required for the loading of dynamic libraries
00032 #ifdef WIN32
00033   #include <windows.h>
00034 #else
00035   #include <dlfcn.h>
00036 #endif
00037 
00038 
00039 namespace frepple
00040 {
00041 
00042 
00043 DECLARE_EXPORT bool Command::getVerbose() const
00044 {
00045   if (verbose==INHERIT)
00046     // Note: a command gets the level INHERIT by default. In case the command
00047     // was never added to a commandlist, the owner field will be NULL. In such
00048     // case the value INHERIT is interpreted as SILENT.
00049     return owner ? owner->getVerbose() : false;
00050   else
00051     return verbose==YES;
00052 }
00053 
00054 
00055 DECLARE_EXPORT void Command::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00056 {
00057   if (pAttr.isA(Tags::tag_verbose)) setVerbose(pElement.getBool());
00058 }
00059 
00060 
00061 //
00062 // COMMAND LIST
00063 //
00064 
00065 
00066 DECLARE_EXPORT bool CommandList::getAbortOnError() const
00067 {
00068   if (abortOnError==INHERIT)
00069   {
00070     // A command list can be nested in another command list. In this case we
00071     // inherit this field from the owning command list
00072     CommandList *owning_list = dynamic_cast<CommandList*>(owner);
00073     return owning_list ? owning_list->getAbortOnError() : true;
00074   }
00075   else
00076     return abortOnError==YES;
00077 }
00078 
00079 
00080 DECLARE_EXPORT void CommandList::add(Command* c)
00081 {
00082   // Validity check
00083   if (!c) throw LogicException("Adding NULL command to a command list");
00084   if (curCommand)
00085     throw RuntimeException("Can't add a command to the list during execution");
00086 
00087   // Set the owner of the command
00088   c->owner = this;
00089 
00090   // Maintenance of the linked list of child commands
00091   c->prev = lastCommand;
00092   if (lastCommand)
00093     // Let the last command in the chain point to this new extra command
00094     lastCommand->next = c;
00095   else
00096     // This is the first command in this command list
00097     firstCommand = c;
00098   lastCommand = c;
00099 
00100   // Update the undoable field
00101   if (!c->undoable()) can_undo = false;
00102 }
00103 
00104 
00105 DECLARE_EXPORT void CommandList::undo(Command *c)
00106 {
00107   // Check validity of argument
00108   if (c && c->owner != this)
00109     throw LogicException("Invalid call to CommandList::undoable(Command*)");
00110 
00111   // Don't even try to undo a list which can't be undone.
00112   if (!c && !undoable(c))
00113     throw RuntimeException("Trying to undo a CommandList which " \
00114         "contains non-undoable actions or is executed in parallel.");
00115 
00116   // Undo all commands and delete them.
00117   // Note that undoing an operation that hasn't been executed yet or has been
00118   // undone already is expected to be harmless, so we don't need to worry
00119   // about that...
00120   for (Command *i = lastCommand; i != c; )
00121   {
00122     Command *t = i;  // Temporarily store the pointer to be deleted
00123     i = i->prev;
00124     delete t; // The delete is expected to also undo the command!
00125   }
00126 
00127   // Maintain the linked list of commands still present
00128   if (c)
00129   {
00130     // Partially undo
00131     c->next = NULL;
00132     lastCommand = c;
00133   }
00134   else
00135   {
00136     // Completely erase the list
00137     firstCommand = NULL;
00138     lastCommand = NULL;
00139   }
00140 }
00141 
00142 
00143 DECLARE_EXPORT bool CommandList::undoable(const Command *c) const
00144 {
00145   // Check validity of argument
00146   if (c && c->owner!=this)
00147     throw LogicException("Invalid call to CommandList::undoable(Command*)");
00148 
00149   // Parallel commands can't be undone
00150   if (maxparallel > 1) return false;
00151 
00152   // Easy cases
00153   if (!c || can_undo) return can_undo;
00154 
00155   // Step over the remaining commands and check whether they can be undone
00156   for (; c; c = c->next) if (!c->undoable()) return false;
00157   return true;
00158 }
00159 
00160 
00161 DECLARE_EXPORT Command* CommandList::selectCommand()
00162 {
00163   ScopeMutexLock l(lock );
00164   Command *c = curCommand;
00165   if (curCommand) curCommand = curCommand->next;
00166   return c;
00167 }
00168 
00169 
00170 DECLARE_EXPORT void CommandList::execute()
00171 {
00172   // Execute the actions
00173   // This field is set asap in this method since it is used a flag to
00174   // recognize that execution is in progress.
00175   curCommand = firstCommand;
00176 
00177   // Message
00178   if (getVerbose())
00179     logger << "Start executing command list at " << Date::now() << endl;
00180   Timer t;
00181 
00182 #ifndef MT
00183   // Compile 1: No multithreading
00184   if (maxparallel>1) maxparallel = 1;
00185 #else
00186   if (maxparallel>1)
00187   {
00188     // MODE 1: Parallel execution of the commands
00189     int numthreads = getNumberOfCommands();
00190     // Limit the number of threads to the maximum allowed
00191     if (numthreads>maxparallel) numthreads = maxparallel;
00192     if (numthreads == 1)
00193       // Only a single command in the list: no need for threads
00194       wrapper(curCommand);
00195     else if (numthreads > 1)
00196     {
00197       int worker = 0;
00198 #ifdef HAVE_PTHREAD_H
00199       // Create a thread for every command list. The main thread will then
00200       // wait for all of them to finish.
00201       pthread_t threads[numthreads];     // holds thread info
00202       int errcode;                       // holds pthread error code
00203  
00204       // Create the threads
00205       for (; worker<numthreads; ++worker)
00206       {
00207         if ((errcode=pthread_create(&threads[worker],  // thread struct
00208             NULL,                  // default thread attributes
00209             wrapper,               // start routine
00210             this)))                // arg to routine
00211         {
00212           if (!worker)
00213           {
00214             ostringstream ch;
00215             ch << "Can't create any threads, error " << errcode;
00216             throw RuntimeException(ch.str());
00217           }
00218           // Some threads could be created.
00219           // Let these threads run and do all the work.
00220           logger << "Warning: Could create only " << worker
00221             << " threads, error " << errcode << endl;
00222         }
00223       }
00224 
00225       // Wait for the threads as they exit
00226       for (--worker; worker>=0; --worker)
00227         // Wait for thread to terminate. 
00228         // The second arg is NULL, since we don't care about the return status
00229         // of the finished threads.
00230         if ((errcode=pthread_join(threads[worker],NULL)))
00231         {
00232           ostringstream ch;
00233           ch << "Can't join with thread " << worker << ", error " << errcode;
00234           throw RuntimeException(ch.str());
00235         }
00236 #else
00237       // Create a thread for every command list. The main thread will then
00238       // wait for all of them to finish.
00239       HANDLE* threads = new HANDLE[numthreads];
00240       unsigned int * m_id = new unsigned int[numthreads];
00241 
00242       // Create the threads
00243       for (; worker<numthreads; ++worker)
00244       {
00245         threads[worker] =  reinterpret_cast<HANDLE>(
00246           _beginthreadex(0,  // Security atrtributes
00247           0,                 // Stack size
00248           &wrapper,          // Thread function
00249           this,              // Argument list
00250           0,                 // Initial state is 0, "running"
00251           &m_id[worker]));   // Address to receive the thread identifier
00252         if (!threads[worker])
00253         {
00254           if (!worker)
00255           {
00256             // No threads could be created at all.
00257             delete threads;
00258             delete m_id;
00259             throw RuntimeException("Can't create any threads, error " + errno);
00260           }
00261           // Some threads could be created.
00262           // Let these threads run and do all the work.
00263           logger << "Warning: Could create only " << worker
00264             << " threads, error " << errno << endl;
00265           break; // Step out of the thread creation loop
00266         }
00267       }
00268 
00269       // Wait for the threads as they exit
00270       int res = WaitForMultipleObjects(worker, threads, true, INFINITE);
00271       if (res == WAIT_FAILED)
00272       {
00273         char error[256];
00274         FormatMessage(
00275           FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
00276           NULL,
00277           GetLastError(),
00278           0,
00279           error,
00280           256,
00281           NULL );
00282         delete threads;
00283         delete m_id;
00284         throw RuntimeException(string("Can't join threads: ") + error);
00285       }
00286 
00287       // Cleanup
00288       for (--worker; worker>=0; --worker)
00289         CloseHandle(threads[worker]);
00290       delete threads;
00291       delete m_id;
00292 #endif
00293     }  // End: else if (numthreads>1)
00294   }
00295   else // Else: sequential
00296 #endif
00297   if (getAbortOnError())
00298   {
00299     // MODE 2: Sequential execution, and a single command failure aborts the
00300     // whole sequence.
00301     try
00302     {
00303       for (; curCommand; curCommand = curCommand->next) curCommand->execute();
00304     }
00305     catch (...)
00306     {
00307       logger << "Error: Caught an exception while executing command '"
00308       << curCommand->getDescription() << "':" <<  endl;
00309       try { throw; }
00310       catch (exception& e) {logger << "  " << e.what() << endl;}
00311       catch (...) {logger << "  Unknown type" << endl;}
00312       // Undo all commands executed so far
00313       if (undoable()) undo();
00314     }
00315   }
00316   else
00317     // MODE 3: Sequential execution, and when a command in the sequence fails
00318     // the rest continues
00319     wrapper(this);
00320 
00321   // Clean it up after executing ALL actions.
00322   for (Command *i=lastCommand; i; )
00323   {
00324     Command *t = i;
00325     i = i->prev;
00326     delete t;
00327   }
00328   firstCommand = NULL;
00329   lastCommand = NULL;
00330 
00331   // Log
00332   if (getVerbose())
00333     logger << "Finished executing command list at " << Date::now()
00334     << " : " << t << endl;
00335 }
00336 
00337 
00338 #if defined(HAVE_PTHREAD_H) || !defined(MT)
00339 void* CommandList::wrapper(void *arg)
00340 #else
00341 unsigned __stdcall CommandList::wrapper(void *arg)
00342 #endif
00343 {
00344   CommandList *l = static_cast<CommandList*>(arg);
00345   for (Command *c = l->selectCommand(); c; c = l->selectCommand())
00346   {
00347 #if defined(HAVE_PTHREAD_H) || !defined(MT)
00348     // Verfiy whether there has been a cancellation request in the meantime
00349     pthread_testcancel();
00350 #endif
00351     try { c->execute(); }
00352     catch (...)
00353     {
00354       // Error message
00355       logger << "Error: Caught an exception while executing command '"
00356       << c->getDescription() << "':" << endl;
00357       try { throw; }
00358       catch (exception& e) {logger << "  " << e.what() << endl;}
00359       catch (...) {logger << "  Unknown type" << endl;}
00360     }
00361   }
00362   return 0;
00363 }
00364 
00365 
00366 DECLARE_EXPORT CommandList::~CommandList()
00367 {
00368   if (!firstCommand) return;
00369   logger << "Warning: Deleting an action list with actions that have"
00370     << " not been committed or undone" << endl;
00371   for (Command *i = lastCommand; i; )
00372   {
00373     Command *t = i;  // Temporary storage for the object to delete
00374     i = i->prev;
00375     delete t;
00376   }
00377 }
00378 
00379 
00380 DECLARE_EXPORT void CommandList::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00381 {
00382   if (pAttr.isA(Tags::tag_command) && !pIn.isObjectEnd())
00383   {
00384     // We're unlucky with our tag names here. Subcommands end with
00385     // </COMMAND>, but the command list itself also ends with that tag.
00386     // We use the isObjectEnd() method to differentiate between both.
00387     Command * b = dynamic_cast<Command*>(pIn.getPreviousObject());
00388     if (b) add(b);
00389     else throw LogicException("Incorrect object type during read operation");
00390   }
00391   else if (pAttr.isA(Tags::tag_abortonerror))
00392     setAbortOnError(pElement.getBool());
00393   else if (pAttr.isA(Tags::tag_maxparallel))
00394     setMaxParallel(pElement.getInt());
00395   else
00396     Command::endElement(pIn, pAttr, pElement);
00397 }
00398 
00399 
00400 DECLARE_EXPORT void CommandList::beginElement (XMLInput& pIn, const Attribute& pAttr)
00401 {
00402   if (pAttr.isA (Tags::tag_command))
00403     pIn.readto( MetaCategory::ControllerDefault(Command::metadata,pIn.getAttributes()) );
00404 }
00405 
00406 
00407 //
00408 // SYSTEM COMMAND
00409 //
00410 
00411 
00412 DECLARE_EXPORT void CommandSystem::execute()
00413 {
00414   // Log
00415   if (getVerbose())
00416     logger << "Start executing system command '" << cmdLine
00417     << "' at " << Date::now() << endl;
00418   Timer t;
00419 
00420   // Check
00421   if (cmdLine.empty())
00422     throw DataException("Error: Trying to execute empty system command");
00423 
00424   // Expand environment variables on the command line with their value
00425   Environment::resolveEnvironment(cmdLine);
00426 
00427   // Execute the command
00428   if (system(cmdLine.c_str()))  // Execution through system() call
00429     throw RuntimeException("Error while executing system command: " + cmdLine);
00430 
00431   // Log
00432   if (getVerbose())
00433     logger << "Finished executing system command '" << cmdLine
00434     << "' at " << Date::now() << " : " << t << endl;
00435 }
00436 
00437 
00438 DECLARE_EXPORT void CommandSystem::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00439 {
00440   if (pAttr.isA(Tags::tag_cmdline))
00441     // No need to replace environment variables here. It's done at execution
00442     // time by the command shell.
00443     pElement >> cmdLine;
00444   else
00445     Command::endElement(pIn, pAttr, pElement);
00446 }
00447 
00448 
00449 //
00450 // LOADLIBRARY COMMAND
00451 //
00452 
00453 
00454 DECLARE_EXPORT void CommandLoadLibrary::execute()
00455 {
00456   // Type definition of the initialization function
00457   typedef const char* (*func)(const ParameterList&);
00458 
00459   // Log
00460   if (getVerbose())
00461     logger << "Start loading library '" << lib << "' at " << Date::now() << endl;
00462   Timer t;
00463 
00464   // Validate
00465   if (lib.empty())
00466     throw DataException("Error: No library name specified for loading");
00467 
00468   // Expand environment variables in the library name with their value
00469   Environment::resolveEnvironment(lib);
00470 
00471 #ifdef WIN32
00472   // Load the library - The windows way
00473   // Change the error mode: we handle errors now, not the operating system
00474   UINT em = SetErrorMode(SEM_FAILCRITICALERRORS);
00475   HINSTANCE handle = LoadLibraryEx(lib.c_str(),NULL,LOAD_WITH_ALTERED_SEARCH_PATH);
00476   if (!handle) handle = LoadLibraryEx(lib.c_str(), NULL, 0);
00477   if (!handle)
00478   {
00479     // Get the error description
00480     char error[256];
00481     FormatMessage(
00482       FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
00483       NULL,
00484       GetLastError(),
00485       0,
00486       error,
00487       256,
00488       NULL );
00489     throw RuntimeException(error);
00490   }
00491   SetErrorMode(em);  // Restore the previous error mode
00492 
00493   // Find the initialization routine
00494   func inithandle =
00495     reinterpret_cast<func>(GetProcAddress(HMODULE(handle), "initialize"));
00496   if (!inithandle)
00497   {
00498     // Get the error description
00499     char error[256];
00500     FormatMessage(
00501       FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
00502       NULL,
00503       GetLastError(),
00504       0,
00505       error,
00506       256,
00507       NULL );
00508     throw RuntimeException(error);
00509   }
00510 
00511 #else
00512   // Load the library - The unix way
00513   dlerror(); // Clear the previous error
00514   void *handle = dlopen(lib.c_str(), RTLD_NOW | RTLD_GLOBAL);
00515   const char *err = dlerror();  // Pick up the error string
00516   if (err) throw RuntimeException(err);
00517 
00518   // Find the initialization routine
00519   func inithandle = (func)(dlsym(handle, "initialize"));
00520   err = dlerror(); // Pick up the error string
00521   if (err) throw RuntimeException(err);
00522 #endif
00523 
00524   // Call the initialization routine with the parameter list
00525   string x = (inithandle)(parameters);
00526   if (x.empty()) throw DataException("Invalid module name returned.");
00527 
00528   // Insert the new module in the registry
00529   registry.insert(x);
00530 
00531   // Log
00532   if (getVerbose())
00533     logger << "Finished loading module '" << x << "' from library '" << lib
00534     << "' at " << Date::now() << " : " << t << endl;
00535 }
00536 
00537 
00538 DECLARE_EXPORT void CommandLoadLibrary::printModules()
00539 {
00540   logger << "Loaded modules:" << endl;
00541   for (set<string>::const_iterator i=registry.begin(); i!=registry.end(); ++i)
00542     logger << "   " << *i << endl;
00543   logger << endl;
00544 }
00545 
00546 
00547 DECLARE_EXPORT void CommandLoadLibrary::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00548 {
00549   if (pAttr.isA(Tags::tag_filename))
00550     pElement >> lib;
00551   else if (pAttr.isA(Tags::tag_name))
00552     pElement >> tempName;
00553   else if (pAttr.isA(Tags::tag_value))
00554     pElement >> tempValue;
00555   else if (pAttr.isA(Tags::tag_parameter))
00556   {
00557     if (!tempValue.empty() && !tempName.empty())
00558     {
00559       // New parameter name+value pair ready
00560       parameters[tempName] = tempValue;
00561       tempValue.clear();
00562       tempName.clear();
00563     }
00564     else
00565       // Incomplete data
00566       throw DataException("Invalid parameter specification");
00567   }
00568   else if (pIn.isObjectEnd())
00569   {
00570     tempValue.clear();
00571     tempName.clear();
00572   }
00573   else
00574     Command::endElement(pIn, pAttr, pElement);
00575 }
00576 
00577 
00578 //
00579 // SETENV COMMAND
00580 //
00581 
00582 
00583 DECLARE_EXPORT void CommandSetEnv::execute()
00584 {
00585   // Message
00586   if (getVerbose())
00587     logger << "Start updating variable '" << variable << "' to '"
00588     << value << "' at " << Date::now() << endl;
00589   Timer t;
00590 
00591   // Data exception
00592   if (variable.empty())
00593     throw DataException("Missing environment variable name");
00594 
00595   // Expand variable names
00596   Environment::resolveEnvironment(value);
00597 
00598   // Update the variable
00599   // Note: we have to 'leak' this string. Putenv takes it as part of
00600   // the environment.
00601   string *tmp = new string(variable + "=" + value);
00602   #if defined(HAVE_PUTENV)
00603   putenv(const_cast<char*>(tmp->c_str()));
00604   #elif defined(HAVE__PUTENV) || defined(_MSC_VER)
00605   _putenv(tmp->c_str());
00606   #else
00607   #error("missing function to set an environment variable")
00608   #endif
00609 
00610   // Log
00611   if (getVerbose())
00612   {
00613     const char* res = getenv(variable.c_str());
00614     logger << "Finished updating variable '" << variable << "' to '"
00615     << (res ? res : "NULL") << "' at " << Date::now() << endl;
00616   }
00617 }
00618 
00619 
00620 DECLARE_EXPORT void CommandSetEnv::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00621 {
00622   if (pAttr.isA(Tags::tag_variable))
00623     pElement >> variable;
00624   if (pAttr.isA(Tags::tag_value))
00625     pElement >> value;
00626   else
00627     Command::endElement(pIn, pAttr, pElement);
00628 }
00629 
00630 
00631 }

Documentation generated by  doxygen