Update of /cvsroot/nscldaq/clients/daqstart In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv7719 Added Files: Tag: daqclients-7_4_development CDAQStart.cpp CDAQStart.h CFileSink.cpp CFileSink.h CFileSinkCreator.cpp CFileSinkCreator.h CLocalMonitoredProgram.cpp CLocalMonitoredProgram.h CMonitoredProgram.cpp CMonitoredProgram.h CSink.cpp CSink.h CSinkCreator.h CSinkFactory.cpp CSinkFactory.h Makefile.am daqstart.cpp daqstart.ggo Log Message: Initial good compile of the software --- NEW FILE: daqstart.ggo --- package "daqstart" version "1.0" purpose "Command line parse description for gengetopt for daqstart use --unamed-opts" option "error" e "Error log sink (e.g. file:myfile.log)" string no option "output" o "Output log sink (e.g. file:myfile.log)" string no option "notify" n "Enable exit notification logging" flag on --- NEW FILE: CFileSinkCreator.cpp --- ////////////////////////// FILE_NAME.cpp /////////////////////////// #include "CFileSinkCreator.h" #include "CFileSink.h" // Static attribute storage and initialization for CFileSinkCreator /*! Create an object of type CFileSinkCreator */ CFileSinkCreator::CFileSinkCreator () { } /*! Called to destroy an instance of CFileSinkCreator */ CFileSinkCreator::~CFileSinkCreator ( ) { } /*! Called to create an instance of CFileSinkCreator that is a functional duplicate of another instance. \param rSource (const CFileSinkCreator& ): The object that we will dupliate. */ CFileSinkCreator::CFileSinkCreator (const CFileSinkCreator& aCFileSinkCreator ) : CSinkCreator (aCFileSinkCreator) { } /*! Assign to *this from rhs so that *this becomes a functional duplicate of rhs. \param rhs (const CFileSinkCreator& rhs ): The object that will be functionally copied to *this. */ CFileSinkCreator& CFileSinkCreator::operator= (const CFileSinkCreator& rhs) { if(this != &rhs) { CSinkCreator::operator=(rhs); } return *this; } /*! Compare *this for functional equality with another object of type CFileSinkCreator. \param rhs (const CFileSinkCreator& rhs ): The object to be compared with *this. */ int CFileSinkCreator::operator== (const CFileSinkCreator& rhs) const { return CSinkCreator::operator==(rhs); } // Functions for class CFileSinkCreator /*! Description: Returns an indication of the legality of a filename path. Note that essentially all filename paths are legal (although maybe not usable). Parameters: \param Name (string [in]): The path name to check \return bool \retval true - indicating that all path strings are legal. */ bool CFileSinkCreator::isNameLegal(string Name) { return true; } /*! Description: Attempts to create a file sink that is open on the name passed in. In order to ensure that we catch illegalities, we open the file ourselves, and construct the filesink via its file descriptor. The file is opened for write append. Parameters: \param Name (string [in]) Name of the file to open. \return CSink* \return CSink* \retval NULL - The file, or sink could not be created for whatever reason. Probably the error reason will be in errno \retval !NULL - The sink created (in reality a CFileSink). */ CSink* CFileSinkCreator::Create(string sCommand, string sName) { CSink* pSink; try { pSink = new CFileSink(sCommand, sName); return pSink; } catch(...) { return (CSink*)NULL; } } --- NEW FILE: CDAQStart.h --- //! \class: CDAQStart //! \file: .h /*! \class CDAQStart \file .h The DAQStart main application class. We make all of our applications a class that is invoked from main. This makes it easier to re-use program logic sections for other applications as well as combining several singly threaded applications into a multithreade app. */ // Author: // Ron Fox // NSCL // Michigan State University // East Lansing, MI 48824-1321 // mailto:fo...@ns... // // Copyright #ifndef __CDAQSTART_H //Required for current class #define __CDAQSTART_H // // Include files: // #ifndef CMDLINE_H #include "cmdline.h" // From gengetopt. #ifndef CMDLINE_H // Just in case they change their gaurd. #define CMDLINE_H #endif #endif #ifndef __STL_STRING #include <string> #ifndef __STL_STRING #define __STL_STRING #endif #endif //Required for 1:1 association classes // Forward class definitions (convert to includes if required): class CMonitoredProgram; class CSink; class CDAQStart { private: // Private Member data: CMonitoredProgram* m_pProgram; // Pointer to monitored program bool m_fNotifyExit; // Notify on exit int m_nArgc; char** m_pArgv; public: // Constructors and other canonical operations. // You may need to adjust the parameters // and the visibility esp. if you cannot // implement assignment/copy construction. // safely. CDAQStart (); //!< Constructor. virtual ~ CDAQStart ( ); //!< Destructor. private: CDAQStart (const CDAQStart& rSource ); //!< Copy construction. CDAQStart& operator= (const CDAQStart& rhs); //!< Assignment. int operator== (const CDAQStart& rhs) const; //!< == comparison. int operator!= (const CDAQStart& rhs) const; //!< != comparison. public: public: virtual int operator() (int argc , char** argv) ; // Utilities: protected: void Usage () ; gengetopt_args_info* ParseArgs (int argc, char** argv) ; virtual int MainLoop () ; void StartSubprocess () ; void MonitorOutput (int nMs) ; bool MonitorExit () ; void ReportExit(); private: char* SinkType(char* name); char* SinkName(char* name); CSink* CreateSink(const char* name); }; #endif --- NEW FILE: CMonitoredProgram.h --- // Author: // Ron Fox // NSCL // Michigan State University // East Lansing, MI 48824-1321 // mailto:fo...@ns... // // Copyright /*! \class CMonitoredProgram This abstract base class provides the interface to produce an monitor programs. Monitored programs are started with the same stdin as the running program but stdout and stderr connected ultimately to pipes that can be monitored by this object. Each Monitored program also maintains a pair of CSink derived objects that are used to log the output and potentially the errors from the program. */ #ifndef __CMONITOREDPROGRAM_H //Required for current class #define __CMONITOREDPROGRAM_H // Headers: #ifndef __STL_STRING #include <string> #ifndef __STL_STRING #define __STL_STRING #endif #endif // Forward class references: class CSink; class CMonitoredProgram { private: // Private Member data: int m_nExitStatus; //!< Program final exit value. string m_sProgramName; //!< Program name string. int m_nArgc; //!< Number of command line parameters (including program name). char** m_paArgv; //!< The command line parameters. CSink* m_pOutput; //!< Sink for stdout logging. CSink* m_pError; //!< Sink for stderr logging. protected: // Protected member data. public: // Public member data. public: // Constructors and other canonical operations. // You may need to adjust the parameters // and the visibility esp. if you cannot // implement assignment/copy construction. // safely. CMonitoredProgram (int argc, char** argv);//!< Constructor. virtual ~CMonitoredProgram ( ); //!< Destructor. CMonitoredProgram (const CMonitoredProgram& rSource ); //!< Copy construction. CMonitoredProgram& operator= (const CMonitoredProgram& rhs); //!< Assignment. int operator== (const CMonitoredProgram& rhs) const; //!< == comparison. int operator!= (const CMonitoredProgram& rhs) const { //!< != comparison. return !(operator==(rhs)); } // Selectors (essentially): public: string GetName () const; int GetNargs () const; char** GetArgs () const; int GetFinalStatus () const; // Mutators (essentially) public: CSink* AttachOutput (CSink* pSink) ; CSink* AttachError (CSink* pSink) ; protected: void SetFinalStatus (int nStatus) ; public: virtual void Start () = 0 ; virtual void PollIO (int nMs) = 0 ; virtual bool PollStatus () = 0 ; void StdOut (string line) ; void StdErr (string line) ; // Private utilities. private: void CopyArgv(int argc, char**argv); void FreeArgv(); void CopyIn(const CMonitoredProgram& rhs); bool ArgvSame(int argc, char**argv) const; void Log(int fd, CSink* pSink, string sLine); }; #endif --- NEW FILE: CSinkFactory.h --- // Author: // Ron Fox // NSCL // Michigan State University // East Lansing, MI 48824-1321 // mailto:fo...@ns... // // Copyright /*! \class CSinkFactory Factory class that creates sinks. All member functions are static as we rely a list of creators to do the dirty work for us. */ #ifndef __CSINKFACTORY_H //Required for current class #define __CSINKFACTORY_H // // Include files: // // STL String class. #ifndef __STL_STRING #include <string> #ifndef __STL_STRING #define __STL_STRING #endif #endif // STL map class - holds the creators: #ifndef __STL_MAP #include <map> #ifndef __STL_MAP #define __STL_MAP #endif #endif // Forward classes class CSinkCreator; class CSink; // CSinkFactory definition: class CSinkFactory { // Type definitions: public: typedef map<string, CSinkCreator*> SinkCreatorDictionary; typedef SinkCreatorDictionary::iterator SinkIterator; private: static SinkCreatorDictionary m_SinkCreators; public: public: static CSink* Create (string sType, string sCommand, string sName) ; static void AddCreator (string sType, CSinkCreator* pSink) ; static int SplitName(char* sName, char** ppParts); protected: static CSinkCreator* LocateCreator (string sType) ; }; #endif --- NEW FILE: CSinkFactory.cpp --- /* Implementation file for CSinkFactory for a description of the class see CSinkFactory.h */ ////////////////////////// FILE_NAME.cpp /////////////////////////// // Include files required: #include "CSinkFactory.h" #include "CSinkCreator.h" #include "CSink.h" #include <publib.h> // Static attribute storage and initialization for CSinkFactory // The map below holds the set of sink creators that have been // registered: CSinkFactory::SinkCreatorDictionary CSinkFactory::m_SinkCreators; // Functions for class CSinkFactory /*! Description: Creates a sink given a description of the sink to create. Parameters: \param type (const string& [in]) The type of sink to create (e.g. "file"). \param name [const string& [in]) The name of the entity the sink is connected to (e.g. for a socket perhaps host:port). \return CSink* \retval 0 - The sink could not be created. Most likely illegal sink type or could be illegal sink name. \retval != 0 - Pointer to the newly created sink. */ CSink* CSinkFactory::Create(string sType, string sCommand, string sName) { // Find the sink creator CSinkCreator* pSinkCreator = LocateCreator(sType); if(!pSinkCreator) { return (CSink*)NULL; } // Ask if the sink name is legal... if(pSinkCreator->isNameLegal(sName)) { return pSinkCreator->Create(sCommand, sName); // Legal sink name. } else { return (CSink*)NULL; // Illegal sink name. } } /*! Description: Returns a pointer to the sink creator that will create the sink of type requested by the caller: Parameters: \param sType (const string [in]) Type of string to create. \return CSinkCreator* \retval 0 - String creation failed. \retval != 0 - Pointer to the new sink. */ CSinkCreator* CSinkFactory::LocateCreator(string sType) { SinkIterator i = m_SinkCreators.find(sType); if(i == m_SinkCreators.end()) { return (CSinkCreator*)NULL; // No such sink creator. } return i->second; } /*! Add a new sink creator to the supported set. Note that any existing creator with the same name will vanish from the face of the earth, possibly causing memory leaks. Parameters: \param sType (const string [in]) The type of string creator to add. \param pCreator (CSinkCreator* pSInk [in]) The creator to add \return void */ void CSinkFactory::AddCreator(string sType, CSinkCreator* pSink) { m_SinkCreators[sType] = pSink; } /*! Split a sink name into it's component pieces. The pieces are assumed to be separated by colons. A sink name is identified by a type and an optional connection name. For example: file:/home/fox/testing.log or syslog:user1 or logserver: \param sName (string): The name of the sink. \param ppParts (Actually char *([2])). Storage for the pointers to the two parts. \return int \retval the number of pieces in the name (0,1 or 2). */ int CSinkFactory::SplitName(char* pName, char** ppParts) { // Init the ppParts array since I'm not sure strsplit does that. ppParts[0] = (char*)NULL; ppParts[1] = (char*)NULL; return strsplit(pName, ppParts, 2, ":"); } --- NEW FILE: CSink.h --- // Author: // Ron Fox // NSCL // Michigan State University // East Lansing, MI 48824-1321 // mailto:fo...@ns... // // Copyright /*! \class CSink Abstract base class of the sink class hierarchy. */ #ifndef __CSINK_H //Required for current class #define __CSINK_H #ifndef __STL_STRING #include <string> #ifndef __STL_STRING #define __STL_STRING #endif #endif class CSink { private: string m_sCommand; //!< The command string to put in messages. protected: // Protected member data. public: // Public member data. public: CSink (string sLine); //!< Constructor. virtual ~ CSink ( ); //!< Destructor. CSink (const CSink& rSource ); //!< Copy construction. CSink& operator= (const CSink& rhs); //!< Assignment. int operator== (const CSink& rhs) const; //!< == comparison. int operator!= (const CSink& rhs) const { //!< != comparison. return !(operator==(rhs)); } public: virtual CSink* clone() = 0; //!< Return dup of this . virtual bool Log (const string& Message) = 0 ; virtual string FormatLine (string sMessage) ; }; #endif --- NEW FILE: CFileSinkCreator.h --- // Author: // Ron Fox // NSCL // Michigan State University // East Lansing, MI 48824-1321 // mailto:fo...@ns... // // Copyright /*! \class CFileSinkCreator \file .h Creates file sinks The connection string represents a filesystem path. Since filesystem paths can be pretty much anything if relative paths are allowed, all name strings are considered legal, although you may regeret using some of them. */ #ifndef __CFILESINKCREATOR_H //Required for current class #define __CFILESINKCREATOR_H // // Include files: // //Required for base classes #ifndef __CSINKCREATOR_H //CSinkCreator #include "CSinkCreator.h" #endif class CFileSinkCreator : public CSinkCreator { public: CFileSinkCreator (); //!< Constructor. virtual ~ CFileSinkCreator ( ); //!< Destructor. CFileSinkCreator (const CFileSinkCreator& rSource ); //!< Copy construction. CFileSinkCreator& operator= (const CFileSinkCreator& rhs); //!< Assignment. int operator== (const CFileSinkCreator& rhs) const; //!< == comparison. int operator!= (const CFileSinkCreator& rhs) const; //!< != comparison. public: virtual bool isNameLegal (string Name) ; virtual CSink* Create (string sCommand, string sName) ; }; #endif --- NEW FILE: CDAQStart.cpp --- /* Implementation file for CDAQStart for a description of the class see CDAQStart.h */ ////////////////////////// FILE_NAME.cpp /////////////////////////// // Include files required: #include "CDAQStart.h" #include "CMonitoredProgram.h" #include "CLocalMonitoredProgram.h" #include "CSink.h" #include "CSinkFactory.h" #include <DesignByContract.h> #include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include "cmdline.h" // Static attribute storage and initialization for CDAQStart static const int OUTPUTTIMEOUT(100); // Output timeout in ms. /*! Create an object of type CDAQStart */ CDAQStart::CDAQStart () : m_pProgram(0), m_fNotifyExit(false), m_nArgc(0), m_pArgv(0) { } /*! Called to destroy an instance of CDAQStart */ CDAQStart::~CDAQStart ( ) { if(m_pProgram) { delete m_pProgram; // This should force exit too. } } // The copy constructor, assignment and comparisons don't make sense since // this class represents a singleton application object. // Functions for class CDAQStart /*! Description: Parse the program parameters. The gengetopt parser is used to do the actual parse. Parameters: \param argc (int) Number of command line parameters. \param argv (char**) The parameters themselves. \return gengetopt_args_info* This is dynamically allocated ad must, if not null, be deleted by the caller. \retval NULL - The parse failed, although I'm betting the parser exits the program too. \retval !NULL - The arguments parsed out into the struct. */ gengetopt_args_info* CDAQStart::ParseArgs(int argc, char** argv) { gengetopt_args_info* pArgs = new gengetopt_args_info; int nParseStatus = cmdline_parser(argc, argv, pArgs); if(nParseStatus == EXIT_FAILURE) { delete pArgs; return (gengetopt_args_info*)NULL; } else if (nParseStatus == EXIT_SUCCESS) { // Need to have the command line as an un-named option: if(pArgs->inputs_num < 1) { delete pArgs; return (gengetopt_args_info*)NULL; } return pArgs; } else { CHECK(0, "Unexpected value from cmdline_parser!!"); } } /*! Description: Prints out the program usage string. */ void CDAQStart::Usage() { cmdline_parser_print_help(); } /*! Description: Peforms the main program logic. Parameters: \param argc (int) The count of command line parameters. \param argv (char**) The command line parameters. \return int \retval 0 - Normal exit. \retval other - abnormal exit. */ int CDAQStart::operator()(int argc , char** argv) { gengetopt_args_info* pArgs = ParseArgs(argc, argv); if(pArgs) { // We need to create: The monitored program, // The error and output sinks, // attach them to the monitored program. // Start the program and enter the main loop: m_nArgc = pArgs->inputs_num; m_pArgv = pArgs->inputs; m_pProgram = new CLocalMonitoredProgram(pArgs->inputs_num, pArgs->inputs); if(pArgs->output_given) { CSink* pSink = CreateSink(pArgs->output_arg); if(pSink) { m_pProgram->AttachOutput(pSink); } } if(pArgs->error_given) { CSink* pSink = CreateSink(pArgs->error_arg); if(pSink) { m_pProgram->AttachError(pSink); } } if(pArgs->notify_given) { m_fNotifyExit = true; } else { m_fNotifyExit = false; } StartSubprocess(); return MainLoop(); } else { Usage(); return (-1); } CHECK(0, "Control should not have passed here!"); } /*! Description: The program main loop. While the subprocess is running, the I/O's and return status are polled. \return int status of the program. \retval 0 success \retval !0 failure by convention in unix like systems. */ int CDAQStart::MainLoop() { while(!MonitorExit()) { MonitorOutput(OUTPUTTIMEOUT); } if(m_fNotifyExit) { ReportExit(); } return m_pProgram->GetFinalStatus(); } /*! Starts the subprocess. */ void CDAQStart::StartSubprocess() { m_pProgram->Start(); } /*! Called by the main loop to monitor the output of the prorgram. \param nMs (int) # milleseconds to wait for output from the program. */ void CDAQStart::MonitorOutput(int nMs) { m_pProgram->PollIO(nMs); } /*! Description: Called by MainLoop to monitor the exit status of the subprocess. If the program has exited, it's sink is written to to log this along with the status of the error. If the DISPLAY env variable is set, similar iformation is output to a pop up dialog. \return bool \retval true - program exited \retval false - program is still running. */ bool CDAQStart::MonitorExit() { if(m_pProgram->PollStatus()) { // Program exited. return true; } else { // Program still running return false; } } /*! Return the type field of a sink. Splits the name at the colon. and returns the first field to the left of the colon. \param name (const char*) The sink name to parse. \return char* \retval - NULL if there is no left half. \retval - non null if not. */ char* CDAQStart::SinkType(char* name) { char *(elements[2]); CSinkFactory::SplitName(name, elements); return elements[0]; } /*! Return the name field of a sink. The name is split at the first colon. the right half of the string is returned. \param name (const char*) Sink specification. \return char* \retval the right half of the colon separated descriptor. if there is no right half, an empty string is returned. */ char* CDAQStart::SinkName(char* name) { char *(elements[2]); if(CSinkFactory::SplitName(name, elements) != 2) { return ""; } else { return elements[1]; } } /*! Create a sink of the specified type. The sink name is split into type and name, and then the sink factory is asked to create the sink. \param name (const char*) The name of the sink. This should be of the form: type:name e.g. file:/home/fox/stuff.log It is not necessarily a fatal problem for there only to be on element (e.g. logserver:) if that sinktype has no need for any further qualification. Therefore, the two halves are passed to the factory without any further interpreation to creat the sink. \return CSink* \retval The newly created sink or NULL If the factory cannot create one. */ CSink* CDAQStart::CreateSink(const char* name) { char* name1 = strdup(name); char* name2 = strdup(name); char *pType = SinkType(name1); if(!pType) { free(name1); free(name2); return (CSink*)NULL; // The only failure we can provide } string sType = pType; string sName(SinkType(name2)); free(name1); free(name2); return CSinkFactory::Create(sType, string(*m_pArgv), sName); } /*! Report the exit of a process. */ void CDAQStart::ReportExit() { string ExitMessage("Program exited. Final status: "); char formatted[100]; int status = m_pProgram->GetFinalStatus(); sprintf(formatted, " %d ", WEXITSTATUS(status)); ExitMessage += formatted; if(WIFSIGNALED(status)) { // Exit due to signal: char* pSignalMessage = strsignal(WTERMSIG(status)); sprintf(formatted, " Signal \"%s\" caught", pSignalMessage ? pSignalMessage : "Unknown signal"); ExitMessage += formatted; } m_pProgram->StdErr(ExitMessage); // If the Display env var is present, pop up a dialog: // We're assuming the existence of a program named // PopUp in the bin director of the installation // tree. This program will accept as a parameter // a string to display as the error. // if(getenv("DISPLAY")) { // Have X11 display... string Command("PopUp \""); Command += ExitMessage; Command += "\""; system(Command.c_str()); } } --- NEW FILE: CSink.cpp --- // Author: // Ron Fox // NSCL // Michigan State University // East Lansing, MI 48824-1321 // mailto:fo...@ns... // // Copyright /* Implementation file for CSink for a description of the class see CSink.h */ ////////////////////////// FILE_NAME.cpp /////////////////////////// // Include files required: #include "CSink.h" #include <time.h> /*! Create an object of type CSink */ CSink::CSink (string sLine) : m_sCommand(sLine) { } /*! Called to destroy an instance of CSink */ CSink::~CSink ( ) { } /*! Called to create an instance of CSink that is a functional duplicate of another instance. \param rSource (const CSink& ): The object that we will dupliate. */ CSink::CSink (const CSink& aCSink ) : m_sCommand(aCSink.m_sCommand) { } /*! Assign to *this from rhs so that *this becomes a functional duplicate of rhs. \param rhs (const CSink& rhs ): The object that will be functionally copied to *this. */ CSink& CSink::operator= (const CSink& rhs) { if(this != &rhs) { m_sCommand = rhs.m_sCommand; } return *this; } /*! Compare *this for functional equality with another object of type CSink. \param rhs (const CSink& rhs ): The object to be compared with *this. */ int CSink::operator== (const CSink& rhs) const { m_sCommand == rhs.m_sCommand; } /*! Description: Overridable function that takes a raw line of text and produces the default form of a logfile line. The logfile line looks like: [timestamp] [command] message Parameters: \param sMessage (const string& [in]) The base message that is decorated with the timestampe and the command string information. If the caller wants a host identifier in the command they must put it in. */ string CSink::FormatLine(string sMessage) { string output; // Output string gets built up here. // Get the timestamp appended: time_t t; time(&t); output += '['; output += ctime(&t); output += "] "; // Put in the command string too: output += '['; output += m_sCommand; output += "] "; // Finally put in the client's message and return: output += sMessage; } --- NEW FILE: Makefile.am --- bin_PROGRAMS = daqstart daqstart_SOURCES= cmdline.c CDAQStart.cpp CFileSinkCreator.cpp \ CMonitoredProgram.cpp CSinkFactory.cpp \ CFileSink.cpp CLocalMonitoredProgram.cpp CSink.cpp \ daqstart.cpp include_HEADERS = cmdline.h CDAQStart.h CFileSinkCreator.h \ CMonitoredProgram.h CSinkCreator.h \ CFileSink.h CLocalMonitoredProgram.h CSink.h \ CSinkFactory.h INCLUDES = -I@top_srcdir@/Headers -I@top_srcdir@/Exception -I. daqstart_LDADD = -L@top_srcdir@/Exception -lException -lpub AM_CXXFLAGS = -D_GNU_SOURCE cmdline.c: daqstart.ggo gengetopt --unamed-opts <daqstart.ggo EXTRA_DIST=daqstart.ggo popup.tcl install-exec-local: $(mkinstalldirs) $(prefix)/Scripts $(INSTALL_SCRIPT) popup.tcl $(prefix)/Scripts $(LN_S) $(prefix)/Scripts/popup.tcl $(bindir)/popup --- NEW FILE: CMonitoredProgram.cpp --- /* Implementation file for CMonitoredProgram for a description of the class see CMonitoredProgram.h */ ////////////////////////// FILE_NAME.cpp /////////////////////////// // Include files required: #include "CMonitoredProgram.h" #include "CSink.h" #include <unistd.h> #include <string.h> // Static attribute storage and initialization for CMonitoredProgram /*! Create an object of type CMonitoredProgram \param argc (int) The count of command line paramters that the program monitored would have if started from the command line (e.g. argv[0] is the path to the program. \param argv (char**) The command line parameters as if the program monitored would have started from the command line (e.g. argv[0] is the path to the program. Both argc, and argv must stay in scope for the duration of the lifetime of this. */ CMonitoredProgram::CMonitoredProgram (int argc, char** argv) : m_nExitStatus(0), m_sProgramName(string(argv[0])), m_nArgc(0), m_paArgv(0), m_pOutput(0), m_pError(0) { CopyArgv(argc, argv); } /*! Called to destroy an instance of CMonitoredProgram The m_paArgv array is dynamically allocated and must be freed: */ CMonitoredProgram::~CMonitoredProgram ( ) { FreeArgv(); } /*! Called to create an instance of CMonitoredProgram that is a functional duplicate of another instance. \param rSource (const CMonitoredProgram& ): The object that we will dupliate. */ CMonitoredProgram::CMonitoredProgram (const CMonitoredProgram& aCMonitoredProgram ) { // There's no previously existing data so: CopyIn(aCMonitoredProgram); } /*! Assign to *this from rhs so that *this becomes a functional duplicate of rhs. \param rhs (const CMonitoredProgram& rhs ): The object that will be functionally copied to *this. */ CMonitoredProgram& CMonitoredProgram::operator= (const CMonitoredProgram& rhs) { if(this != &rhs) { // We already exist so first get rid of what we have: delete m_pOutput; delete m_pError; FreeArgv(); // Then copy in: CopyIn(rhs); } return *this; } /*! Compare *this for functional equality with another object of type CMonitoredProgram. \param rhs (const CMonitoredProgram& rhs ): The object to be compared with *this. */ int CMonitoredProgram::operator== (const CMonitoredProgram& rhs) const { return ((m_sProgramName == rhs.m_sProgramName) && ArgvSame(rhs.m_nArgc, rhs.m_paArgv) && (*m_pOutput == *(rhs.m_pOutput)) && (*m_pError == *(rhs.m_pError))); } // Functions for class CMonitoredProgram /*! Description: Returns the value of the final status variable. This is a pure selector and therefore does not discriminate between valid exit statuses that were set by e.g. PollStatus and invalid/uninitialized status values. \retval 0 If valid, the subprocess probably completed normally. \retval !0 If valid probably the subprocess completed abnormally. */ int CMonitoredProgram::GetFinalStatus() const { return m_nExitStatus; } /*! Description: Returns the value of m_sProgram name. This function is a pure selector. \return string \retval m_sProgramName - the name of the program that is monitored by this object. */ string CMonitoredProgram::GetName() const { return m_sProgramName; } /*! Description: Pure selector for m_nArgc - the number of arguments. Parameters: \return int \retval m_nArgc */ int CMonitoredProgram::GetNargs() const { return m_nArgc; } /*! Pure selector for m_paArgv. \return char** \retval m_paArgv */ char** CMonitoredProgram::GetArgs() const { return m_paArgv; } /*! Description: This is a pure mutator that sets the value of the m_nExitStatus variable. It is used by subclass PollStatus functions to set the final exit status when process exit is detected. \param nStatus (int) The final status value */ void CMonitoredProgram::SetFinalStatus(int nStatus) { m_nExitStatus = nStatus; } /*! Description: Attach the output sink to the constructed object. Parameters: \param pSink (CSink* [in[) The sink to attach as the output sinkl. \return CSink* \retval NULL There was no previously attached output sink. \retval !NULL There was a previously attached output sink and this is the pointer to it. */ CSink* CMonitoredProgram::AttachOutput(CSink* pSink) { CSink* pOld = m_pOutput; m_pOutput = pSink; return pOld; } /*! Attaches the error sink to the fully constructed object. \param pSink (CSink*) Pointer to the sink to attach. \return CSink* \retval NULL - Thre was no previously attached sink. \retval !NULL- there was a previously attached sink, returned value points to it. */ CSink* CMonitoredProgram::AttachError(CSink* pSink) { CSink *pOld = m_pError; m_pError = pSink; return pOld; } /*! Description: This is a utility function that is called to indicate to the base class that output on stdout is available. The output is relayed as is to stdout. If the monitor has a sink for stdout, the output is sent to it as well for logging. Parameters: \param line (string) The line of text that has been received from the monitored program. It is the responsibility of the subclass to put any required newlines on this message as the text is sent as is to stdout and modified only as the sink would modify it for the sinks. */ void CMonitoredProgram::StdOut(string line) { Log(STDOUT_FILENO, m_pOutput, line); } /*! Description: This function is identical to StdOut, but the message is relayed to both stderr and the stderr monitor if one is defined. Parameters: \param line (string) The line to write. \return void */ void CMonitoredProgram::StdErr(string line) { Log(STDERR_FILENO, m_pError, line); } /*! Free the argv array: The first m_nArgc elements are malloce'd by strdup and must be free'd. The array of pointers itself must be delete'd as an array: */ void CMonitoredProgram::FreeArgv() { for(int i =0; i < m_nArgc; i++) { free(m_paArgv[i]); // These came from strdup so free() not delete } delete []m_paArgv; // This came from a new, so delete not free() } /*! Copy a set of argv's into this's. The assumption is that at this time, m_nArgc, and m_paArgv are not defined. \param argc (int) Number of parameters to copy in. \param argv (char**) Pointers to the parameters themselves. */ void CMonitoredProgram::CopyArgv(int argc, char** argv) { m_nArgc = argc; // Copy in argv and the strings it points to. m_paArgv = new char*[m_nArgc+1]; // The array of pointers. // including an extra for a trailing null memset(m_paArgv, 0, sizeof(char*)*(m_nArgc+1)); // Set em all to null. for(int i = 0; i < m_nArgc; i++) { m_paArgv[i] = strdup(argv[i]); } } /*! Copy into this from another rhs. The caller must have taken care of any previously existing argv by calling FreeArgv, as well as freeing any other resources. \param rhs (const CMonitoredProgram&) They object we're duplicating into this. */ void CMonitoredProgram::CopyIn(const CMonitoredProgram& rhs) { m_nExitStatus = rhs.m_nExitStatus; m_sProgramName= rhs.m_sProgramName; CopyArgv(rhs.m_nArgc, rhs.m_paArgv); m_pOutput = rhs.m_pOutput->clone(); m_pError = rhs.m_pError->clone(); } /*! Comomn output logging code. The output line is written to fd. If pSink is not NULL it is also logged to the sink. \param fd (int) File descriptor that output is unconditionally written to. \param pSink (CSink*) Sink pointer. If non-null, output is passed to that sink's Log function. \param sLine (string) The string to output, gathered from the program. */ void CMonitoredProgram::Log(int fd, CSink* pSink, string sLine) { write(fd, sLine.c_str(), sLine.size()); if(pSink) { pSink->Log(sLine); } } /*! Compare the argv's of another object to ours. They must agree in number and contents to agree. \param argc (int) Count of arguments in rhs. \param argv (char**) Arguments in rhs. \return bool \retval true argc,argv are == to m_paArgv, m_nArgc \retval false argc,argv are != to m_paArgv, m_nArgc. */ bool CMonitoredProgram::ArgvSame(int argc, char**argv) const { if(argc == m_nArgc) { for(int i=0; i < argc; i++) { if(strcmp(argv[i], m_paArgv[i])) { return false; } } return true; } else { return false; } } --- NEW FILE: CFileSink.h --- // Author: // Ron Fox // NSCL // Michigan State University // East Lansing, MI 48824-1321 // mailto:fo...@ns... // /*! \class CFileSink Sink That is attached to a file. */ #ifndef __CFILESINK_H //Required for current class #define __CFILESINK_H // // Include files: // //Required for base classes #ifndef __CSINK_H //CSink #include "CSink.h" #endif class CFileSink : public CSink { private: int m_nFd; //!< File descriptor of sink. public: // Constructors and other canonical operations. // You may need to adjust the parameters // and the visibility esp. if you cannot // implement assignment/copy construction. // safely. CFileSink (string sCommand, string sFilename); //!< Constructor. virtual ~ CFileSink ( ); //!< Destructor. CFileSink (const CFileSink& rSource ); //!< Copy construction. CFileSink& operator= (const CFileSink& rhs); //!< Assignment. int operator== (const CFileSink& rhs) const; //!< == comparison. int operator!= (const CFileSink& rhs) const { //!< != comparison. return !(operator==(rhs)); } // Class operations: public: virtual CSink* clone(); //!< create duplicate of this. virtual bool Log (const string& Message) ; }; #endif --- NEW FILE: CLocalMonitoredProgram.cpp --- /* Implementation file for CLocalMonitoredProgram for a description of the class see CLocalMonitoredProgram.h */ ////////////////////////// FILE_NAME.cpp /////////////////////////// // Include files required: #include "CLocalMonitoredProgram.h" #include <DesignByContract.h> #include <ErrnoException.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/poll.h> // Static attribute storage and initialization for CLocalMonitoredProgram static const size_t LINESIZE(100); /*! Create an object of type CLocalMonitoredProgram \param argc (int) count of command line parameters for the program. \param argv (char**) the arguments themselves. */ CLocalMonitoredProgram::CLocalMonitoredProgram (int argc, char** argv) : CMonitoredProgram(argc, argv), m_nStdout(-1), m_nStderr(-1), m_fRunning(false), m_nPid(-1), m_sStdoutLine(string("")), m_sStderrLine(string("")) { } /*! Called to destroy an instance of CLocalMonitoredProgram If the program is running it is killed off and the pipes are closed. This is a very bad thing... to destroy this object while the program is running. */ CLocalMonitoredProgram::~CLocalMonitoredProgram ( ) { if(m_fRunning) { kill(m_nPid, SIGKILL); // Kill off child. close(m_nStdout); // Close the pipes. close(m_nStderr); } } // Functions for class CLocalMonitoredProgram /*! Description: Starts the monitored program. Two pipes are created using pipe(2) The program line is then gotten, as well as the program parameters. fork/exec is used to start the subprocess. \pre m_fRunning is false. */ void CLocalMonitoredProgram::Start() { // Check the preconditions; REQUIRE(!m_fRunning, "The subprocess is already running!"); // Get the pipes that will be associated with the // program's stdout an stderr. The output ends of these // pipes will be stuffed into m_sStdoutLine and // m_sSeterrLine: int StdOutPipes[2]; // Pipes for stdout. int StdErrPipes[2]; // Pipes for stderr. if(pipe(StdOutPipes)) { throw CErrnoException("Creating stdout pipes"); } if(pipe(StdErrPipes)) { throw CErrnoException("Creating stderr pipes"); } m_nStdout = StdOutPipes[0]; m_nStderr = StdErrPipes[0]; // Fork the subprocess. The parent process // just records the pid and closes the // output ends of the pipes. // The child process dups the pipes over to the appropriate // file ids, closes the extras and execs the program. int m_nPid = fork(); if(m_nPid == -1) { // Fork failed!! throw CErrnoException("Forking subprocess failed"); } if(m_nPid > 0) { // Parent process. close(StdOutPipes[1]); // Close the write side of stdout close(StdErrPipes[1]); // and stderr pipes. m_fRunning = true; // The subprocess is running. m_sStdoutLine = ""; // Empty the input buffers. m_sStderrLine = ""; BlockingOff(m_nStdout); BlockingOff(m_nStderr); } else if (m_nPid == 0) { // Child process. // Dup the pipes to stderr and stdout. close(STDOUT_FILENO); CHECK(dup2(StdOutPipes[1], STDOUT_FILENO), "Stdout dup failed"); close(STDERR_FILENO); CHECK(dup2(StdErrPipes[1], STDERR_FILENO), "Stderr dup failed"); // Now exec the subprocess: char** argv = GetArgs(); // This is null terminated so just: execvp(argv[0], argv); // Should not return!! CHECK(0, "Execvp failed!!"); } else { // BUG!! CHECK(0, "fork evidently failed, but we didn't exit"); } } /*! Description: Does a poll with timeout on the file descriptors for the process's stdout and stderr. If any data are received they are appended to the appropriate m_sStdoutLine m_sStderrLine If a newline is read, the StdOut and StdErr members are called. \pre m_fRunning Parameters: \param nMs (int) Number of milliseconds to block for fileIO. */ void CLocalMonitoredProgram::PollIO(int nMs) { // Check the precondition. REQUIRE(m_fRunning, "Sub process is not yet running!"); // struct pollfd PollInfo[2]; // One for stdout and another stderr. // The flags will be the same for both polls: short nEventMask = POLLIN | POLLPRI; // Setup the stdout ([0]) element of pollinfo: PollInfo[0].fd = m_nStdout; PollInfo[0].events = nEventMask; // Setup the stderr ([1]) element of pollinfo. PollInfo[1].fd = m_nStderr; PollInfo[1].events = nEventMask; // Now do the poll. int nPollStatus = poll(PollInfo, 2, nMs); // We're interested in nonzero cases. >0 means there's data // to read. < 0 means that the poll somehow failed an that needs // to be thrown to report. // 0's just a timeout and is ignored. if(nPollStatus < 0) { // Error from poll throw CErrnoException("PollIO poll failed"); } else if(nPollStatus > 0) { // At least one fd with stuff. // Flag checks: // POLLLIN | POLLPRI indicate input. // POLLERR | POLLHUP | POLLINVAL indicate a hangup and will // result in our own log message. // if(PollInfo[0].revents) { // Stdout has something if(ProcessInputFd(m_nStdout, m_sStdoutLine, PollInfo[0].revents)) { // full line.. StdOut(m_sStdoutLine); m_sStdoutLine = ""; } } if(PollInfo[1].revents) { // Stderr has something if(ProcessInputFd(m_nStderr, m_sStderrLine, PollInfo[1].revents)) { StdErr(m_sStderrLine); m_sStderrLine = ""; } } } else { // Timeout. ; // Just a timeout; do nothing. } } /*! Description: Does a waitpid without blocking. If the subprocess is completed, the exit status is set and the completion flag is set (subsequent calls will just return). \pre m_fRunning \return bool \retval true - The subprocess has exited, its return status can be gotten via a call to GetFinalStatus \retval false - The subprocess is still running. */ bool CLocalMonitoredProgram::PollStatus() { int nExitStatus; // Check precondition(s) REQUIRE(m_fRunning, "SubProcess is not running at this time"); // Do the wait and look at what we get: pid_t pid = waitpid(m_nPid, &nExitStatus, WNOHANG); if(pid > 0) { // Info on pid returned.. could be stopped. if(WIFEXITED(nExitStatus)) { // So be sure the process exited before m_fRunning = false; // and indicating the process is not running SetFinalStatus(nExitStatus); // Save status in base class. return true; } else { // Not exited. return false; } } else if (pid == 0) { // not exited or stopped. return false; } else { // Pid < 0 is an error: throw CErrnoException("Waitpid failed"); } } // Utility functions: // They don't deserve doxygen entries. Just good comments. /* BlockingOff - This function turns off blocking on a file descriptor. We do this in order to ensure that when we read data from a file descriptor to get the current line, we will not be blocking if the full read cannot be satisfied. Parameters: int nFd - The file descriptor to alter. */ void CLocalMonitoredProgram::BlockingOff(int nFd) { // Need to get the fd flags for the file descriptor, // or in O_NONBLOCK // and set them back.. Either fcntl in theory could fail // failure results in a CErrnoException throw. // long flags; flags = fcntl(nFd, F_GETFL, 0); if(flags == -1) { // Special error state throw CErrnoException("Unable to get file descriptor flags"); } flags |= O_NONBLOCK; if(fcntl(nFd, F_SETFL, flags)) { throw CErrnoException("Unable to set file descriptor flags"); } } /* ProcessInputFd - Takes the status state flags from a poll on a file descriptor and processes them: if POLLIN or POLLPRI are set, the file is read for data and the data is appended to the associated line buffer. if POLLERR, POLLHUP or POLLINVAL are set, this is an error that is thrown as a String exception. Parameters: int nFd - The file descriptor that's being handled. string& sLine - The file's output buffer. short nPollFlags - The flags from poll. Return: true - The line has at least one \n in it. false - the line has no \n's in it. */ bool CLocalMonitoredProgram::ProcessInputFd(int nFd, string& sOutline, short nPollFlags) { if(nPollFlags & (POLLERR | POLLHUP | POLLNVAL)) { throw string(" Poll succeeded but with flags showing errors"); } if(nPollFlags & (POLLIN | POLLPRI)) { // // The data are read into buffer and then appended to // the line. The gymnastics below assure there's a // null terminator on the string. // char buffer[(int)LINESIZE]; memset(buffer,0, LINESIZE); int nRead = read(nFd, buffer, LINESIZE-1); // Ensure's null terminator if(nRead < 0) { // Read error... throw CErrnoException("Read failed after poll was good"); } else if (nRead > 0) { // Characters available. sOutline += buffer; // Append the string... if(index(buffer, '\n')) { return true; } else { return false; } } else { // No chars somehow. ; // Grant forbearance even tho should be chars. } } else { throw string("Poll returned unexpected flags."); } // Control should not land here. ENSURE(0, "Bug in control logic for ProcessInputFd"); } --- NEW FILE: CLocalMonitoredProgram.h --- // Author: // Ron Fox // NSCL // Michigan State University // East Lansing, MI 48824-1321 // mailto:fo...@ns... // // Copyright /*! \class CLocalMonitoredProgram \file .h This class starts a subprocess in the local system and provides hooks to monitor it. The subprocess is started on the ends of pipes (pipes for stdout and stderr). */ #ifndef __CLOCALMONITOREDPROGRAM_H //Required for current class #define __CLOCALMONITOREDPROGRAM_H // // Include files: // //Required for base classes #ifndef __CMONITOREDPROGRAM_H //CMonitoredProgram #include "CMonitoredProgram.h" #endif #ifndef __STL_STRING #include <string> #ifndef __STL_STRING #define __STL_STRING #endif #endif #ifndef __CRTL_SYS_TYPES_H // All this just to get pid_t. #include <sys/types.h> #ifndef __CRTL_SYS_TYPES_H #define __CRTL_SYS_TYPES_H #endif #endif class CLocalMonitoredProgram : public CMonitoredProgram { private: // Private Member data: int m_nStdout; //!< Output end of program's stdout pipe. int m_nStderr; //!< Output end of program's stderr pipe. bool m_fRunning; //!< True if the process is running. pid_t m_nPid; //!< PID of child process. string m_sStdoutLine; //!< The accumluating line for stdout. string m_sStderrLine; //!< The accumulating line for stderr. public: // Constructors and other canonical operations. // You may need to adjust the parameters // and the visibility esp. if you cannot // implement assignment/copy construction. // safely. CLocalMonitoredProgram (int argc, char** argv); //!< Constructor. virtual ~CLocalMonitoredProgram ( ); //!< Destructor. // I'm not sure how to make the various copy operations work, this // also implies I'm not sure what equality means. Therefore: // These canonicals are illegal/unimplemented. private: CLocalMonitoredProgram (const CLocalMonitoredProgram& rSource ); //!< Copy construction. CLocalMonitoredProgram& operator= (const CLocalMonitoredProgram& rhs); //!< Assignment. int operator== (const CLocalMonitoredProgram& rhs) const; //!< == comparison. int operator!= (const CLocalMonitoredProgram& rhs) const; public: // Insert any selectors you wish to export here // visibility should be public. // Insert any mutators you wish to export here. // visibility should be protected. // Class operations: public: virtual void Start (); virtual void PollIO (int nMs); virtual bool PollStatus (); // Local utilities: private: static void BlockingOff(int nFd); //!< Turn off fd blocking. bool ProcessInputFd(int nFd, string& sOutline, short nPollFlags); //!< Process input on an fd. }; #endif --- NEW FILE: CSinkCreator.h --- // Author: // Ron Fox // NSCL // Michigan State University // East Lansing, MI 48824-1321 // mailto:fo...@ns... // // Copyright /*! \class CSinkCreator \file .h Abstract base class for the set of sink creators supported at any given time by the sink factory. This class establishes an interface specification for the SinkCreator hierarchy. */ #ifndef __CSINKCREATOR_H //Required for current class #define __CSINKCREATOR_H // // Include files: // #ifndef __STL_STRING #include <string> #ifndef __STL_STRING #define __STL_STRING #endif #endif // Forward definitions: class CSink; // The CSinkCreator: class CSinkCreator { public: CSinkCreator () {} //!< Constructor. virtual ~ CSinkCreator ( ) {} //!< Destructor. CSinkCreator (const CSinkCreator& rSource ) {} //!< Copy construction. CSinkCreator& operator= (const CSinkCreator& rhs) { //!< Assignment. return *this; } int operator== (const CSinkCreator& rhs) const { //!< == comparison. return (int)true; } int operator!= (const CSinkCreator& rhs) const { //!< != comparison. return !(operator==(rhs)); } // Class operations: public: virtual bool isNameLegal (string sName) = 0 ; virtual CSink* Create (string sCommand, string sName) = 0 ; }; #endif --- NEW FILE: daqstart.cpp --- /*! Main program for daqstart. - Stock the sink factory with the known creators. - Create the application - Run it - catch all exceptions and exit states. */ // Headers: #include <CSinkFactory.h> #include <CFileSinkCreator.h> #include <CDAQStart.h> #include <DesignByContract.h> #include <Exception.h> #include <string> #include <iostream> int main(int argc, char** argv) { // Stock the sink factory with all the legal sink types: CSinkFactory::AddCreator(string("file"), new CFileSinkCreator); // File sinks. // Create and run the application within the giant try block: try { CDAQStart Application; Application(argc, argv); // Run the application } catch(CException& except) { // NSCL exceptions. cerr << argv[0] << "NSCL Exception caught: " << except.ReasonText() << " while " << except.WasDoing() << endl; } catch(DesignByContract::DesignByContractException& except) { // Contract violations. cerr << argv[0] << ": contract exception caught: " << except << endl; } catch (string& msg) { // String throws. cerr << argv[0] << ": string exception caught: " << msg << endl; } catch(...) { // Last chance exceptions... } } --- NEW FILE: CFileSink.cpp --- /* Implementation file for CFileSink for a description of the class see CFileSink.h */ ////////////////////////// FILE_NAME.cpp /////////////////////////// // Include files required: #include "CFileSink.h" #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <ErrnoException.h> // Static attribute storage and initialization for CFileSink /*! Create an object of type CFileSink \param sCommand string The command name of the thing being logged (required by base class constructor. \param sFilename string The name of the logfile. This file is opened at construction time and closed at destruction. \throw CErrnoException if the file cannot be opened. */ CFileSink::CFileSink (string sCommand, string sFilename) : CSink(sCommand), m_nFd(-1) { // Open the file for append, creation is allowed. int nFd = open(sFilename.c_str(), O_CREAT | O_APPEND | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); if(nFd < 0) { throw CErrnoException(string("Opening log file ") + sFilename); } m_nFd = nFd; } /*! Called to destroy an instance of CFileSink */ CFileSink::~CFileSink ( ) { close(m_nFd); // Must be open if fully constructed } /*! Called to create an instance of CFileSink that is a functional duplicate of another instance. \param rSource (const CFileSink& ): The object that we will dupliate. */ CFileSink::CFileSink (const CFileSink& aCFileSink ) : CSink (aCFileSink) { m_nFd = dup(aCFileSink.m_nFd); } /*! Assign to *this from rhs so that *this becomes a functional duplicate of rhs. \param rhs (const CFileSink& rhs ): The object that will be functionally copied to *this. */ CFileSink& CFileSink::operator= (const CFileSink& rhs) { if(this != &rhs) { CSink::operator=(rhs); close(m_nFd); m_nFd = dup(rhs.m_nFd); } return *this; } /*! Compare *this for functional equality with another object of type CFileSink. \param rhs (const CFileSink& rhs ): The object to be compared with *this. */ int CFileSink::operator== (const CFileSink& rhs) const { // If I wasn't so lazy I'd stat the two files and compare // the stat output. return CSink::operator==(rhs); } // Functions for class CFileSink /*! Description: Logs the associated message to the file. - FormatLine is called to produce the line to write to the file - The log file is written with the line. - Output buffers are flushed so that the log file is immediately updated. Parameters: \param Message (const string & [in] The line that is to be written to file. The line will already have a \n to terminate it. \return bool \return int \retval >0 The number of characters written to the file. \retval <=0 Is an error condition. */ bool CFileSink::Log(const string& Message) { string line = FormatLine(Message); write(m_nFd, Message.c_str(), Message.size()); } /*! Create a functional duplicate of *this: \return CSink* \retval new'd copy of this. */ CSink* CFileSink::clone() { return new CFileSink(*this); } |