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
