3232#include < sys/wait.h>
3333#include < string>
3434#include < charconv>
35+ #include < atomic>
36+ #include < filesystem>
3537
3638#include " DataBlock.h"
3739#include " DataBlockContainer.h"
@@ -148,6 +150,50 @@ void dbLog(const std::string &msg) {
148150 theLog.log (LogInfoDevel_ (3012 ), " %s" , msg.c_str ());
149151}
150152
153+ // an object running a thread redirecting from a file descriptor to infologger
154+ // the file descriptor should be valid for the lifetime of the object
155+ class TheLogRedirection {
156+ public:
157+ TheLogRedirection (int fd, std::string name, int errcode) {
158+ this ->fd = fd;
159+ this ->name = name;
160+ this ->errcode = errcode;
161+ shutdownRequest = 0 ;
162+ std::function<void (void )> f = std::bind (&TheLogRedirection::run, this );
163+ th = std::make_unique<std::thread>(f);
164+ };
165+ ~TheLogRedirection () {
166+ shutdownRequest = 1 ;
167+ if (th != nullptr ) {
168+ th->join ();
169+ th = nullptr ;
170+ }
171+ }
172+ private:
173+ int fd = -1 ; // the file descriptor to monitor
174+ std::string name; // name used as header for the output logs
175+ int errcode = 0 ; // an error code to be used for output messages
176+ std::unique_ptr<std::thread> th; // a thread reading from fd and injecting to theLog
177+ std::atomic<int > shutdownRequest; // flag to terminate thread
178+ void run () {
179+ setThreadName ((std::string (" log-" ) + name).c_str ());
180+ if (fd>=0 ) {
181+ // thread loop
182+ LineBuffer b;
183+ while (!shutdownRequest) {
184+ std::string result;
185+ if (b.appendFromFileDescriptor (fd, 100 )) {
186+ break ;
187+ }
188+ while (!b.getNextLine (result)) {
189+ theLog.log (LogInfoDevel_ (errcode), " %s: %s" , name.c_str (), result.c_str ());
190+ }
191+ }
192+ }
193+ theLog.log (LogInfoDevel_ (errcode)," %s: logs completed" , name.c_str ());
194+ }
195+ };
196+
151197class Readout
152198{
153199
@@ -191,11 +237,13 @@ class Readout
191237 int cfgMaxMsgError; // maximum number of error messages before stopping run
192238 int cfgMaxMsgWarning; // maximum number of warning messages before stopping run
193239 int cfgCustomCommandsEnabled = 0 ; // when set, a sub-process bash is launched to execute custom commands
194- std::string cfgCustomCommandsShell = " o2-readout-command-launcher" ; // or leave empty for raw bash commands
240+ std::string cfgCustomCommandsShell = " /bin/sh o2-readout-command-launcher" ;
195241 std::map<std::string,std::string> customCommands; // map of state / command pairs to be executed
196242 pid_t customCommandsShellPid = 0 ; // pid of shell for custom commands
197243 int customCommandsShellFdIn = -1 ; // input to shell
198244 int customCommandsShellFdOut = -1 ; // output from shell
245+ int customCommandsShellFdErr = -1 ; // err from shell
246+ std::unique_ptr<TheLogRedirection> customCommandsShellLog; // thread redirecting shell stderr to infologger
199247 void executeCustomCommand (const char *stateChange); // execute a custom command for given state transition, if any
200248
201249 // runtime entities
@@ -216,8 +264,6 @@ class Readout
216264
217265 bool isError = 0 ; // flag set to 1 when error has been detected
218266 bool logFirstError = 0 ; // flag set to 1 after 1 error reported from iterateCheck/iterateRunning procedures
219- std::vector<std::string> strErrors; // errors backlog
220- std::mutex mutexErrors; // mutex to guard access to error variables
221267
222268#ifdef WITH_LOGBOOK
223269 std::unique_ptr<bookkeeping::BookkeepingInterface> logbookHandle; // handle to logbook
@@ -360,9 +406,9 @@ int Readout::init(int argc, char* argv[])
360406 gReadoutStats .counters .state = stringToUint64 (" standby" );
361407 int readoutStatsErr = gReadoutStats .startPublish (cfgStatsPublishAddress, cfgStatsPublishInterval);
362408 if (readoutStatsErr == 0 ) {
363- initLogs.push_back ({LogInfoSupport , " Started Stats publish @ " + cfgStatsPublishAddress});
409+ initLogs.push_back ({LogInfoDevel , " Started Stats publish @ " + cfgStatsPublishAddress});
364410 } else if (readoutStatsErr > 0 ) {
365- initLogs.push_back ({LogWarningSupport_ (3236 ), " Failed to start Stats publish" });
411+ initLogs.push_back ({LogWarningDevel_ (3236 ), " Failed to start Stats publish" });
366412 } // otherwise: disabled
367413
368414 // configure signal handlers for clean exit
@@ -454,38 +500,62 @@ int Readout::init(int argc, char* argv[])
454500 // init shell for custom commands
455501 if (cfgCustomCommandsEnabled) {
456502 for (;;) {
457- int p_stdin[2 ], p_stdout[2 ];
503+ int p_stdin[2 ], p_stdout[2 ], p_stderr[ 2 ] ;
458504 pid_t pid;
459505
460- if (pipe (p_stdin) != 0 || pipe (p_stdout) != 0 ) break ;
461- pid = fork ();
506+ if (pipe (p_stdin) != 0 || pipe (p_stdout) != 0 || pipe (p_stderr) != 0 ) break ;
462507
508+ // create shell launching command
509+ std::vector<std::string> shellArgvStrings;
510+ getListFromString (cfgCustomCommandsShell, shellArgvStrings, ' ' );
511+ if (shellArgvStrings.size ()<1 ) {
512+ break ;
513+ }
514+ theLog.log (LogInfoDevel_ (3013 ), " Executing %s" , cfgCustomCommandsShell.c_str ());
515+
516+ pid = fork ();
463517 if (pid < 0 ) {
464518 break ;
465519 } else if (pid == 0 ) {
466520 dup2 (p_stdin[0 ], STDIN_FILENO);
467521 dup2 (p_stdout[1 ], STDOUT_FILENO);
522+ dup2 (p_stderr[1 ], STDERR_FILENO);
468523 close (p_stdin[0 ]);
469524 close (p_stdin[1 ]);
470525 close (p_stdout[0 ]);
471526 close (p_stdout[1 ]);
472- execl (" /bin/sh" ," sh" , cfgCustomCommandsShell.c_str (), NULL );
527+ close (p_stderr[0 ]);
528+ close (p_stderr[1 ]);
529+
530+ std::vector<const char *> argv;
531+ std::string filename = std::filesystem::path (shellArgvStrings[0 ]).filename ();
532+ argv.push_back (filename.c_str ());
533+ for (unsigned int i = 1 ; i< shellArgvStrings.size (); i++) {
534+ argv.push_back (shellArgvStrings[i].c_str ());
535+ }
536+ argv.push_back (NULL );
537+ fprintf (stderr, " %s\n " , shellArgvStrings[0 ].c_str ());
538+ execv (shellArgvStrings[0 ].c_str (), (char * const *)&argv[0 ]);
539+ fprintf (stderr, " Failed to start shell for custom commands: %s\n " , strerror (errno));
473540 exit (1 );
474541 }
475542 close (p_stdin[0 ]);
476543 close (p_stdout[1 ]);
544+ close (p_stderr[1 ]);
477545 customCommandsShellFdIn = p_stdin[1 ];
478546 customCommandsShellFdOut = p_stdout[0 ];
547+ customCommandsShellFdErr = p_stderr[0 ];
479548 customCommandsShellPid = pid;
480549 break ;
481550 }
482551 if (customCommandsShellPid) {
483- theLog.log (LogInfoDevel_ (3013 ), " Shell started for custom commands - pid %d" , (int )customCommandsShellPid);
552+ // create a thread to redirect stderr shell output
553+ customCommandsShellLog = std::make_unique<TheLogRedirection>(customCommandsShellFdErr, " shell" , 3013 );
554+ theLog.log (LogInfoDevel_ (3013 ), " Process started for custom commands - pid %d" , (int )customCommandsShellPid);
484555 } else {
485556 cfgCustomCommandsEnabled = 0 ;
486557 }
487558 }
488-
489559 // print some built-in constants
490560 if (cfgVerbose) {
491561 // theLog.log(LogInfoDevel, "DataBlockHeader size = %d", (int)sizeof(DataBlockHeader));
@@ -529,7 +599,7 @@ int Readout::configure(const boost::property_tree::ptree& properties)
529599#endif
530600 }
531601 } catch (std::string err) {
532- theLog.log (LogErrorSupport_ (3100 ), " %s" , err.c_str ());
602+ theLog.log (LogErrorSupport_ (3100 ), " Failed to read config: %s" , err.c_str ());
533603 return -1 ;
534604 }
535605
@@ -600,7 +670,9 @@ int Readout::configure(const boost::property_tree::ptree& properties)
600670 theLog.log (LogWarningDevel_ (3102 ), " Failed to parse custom commands" );
601671 customCommands.clear ();
602672 } else {
603- theLog.log (LogInfoDevel_ (3013 ), " Registered custom commands:" );
673+ if (customCommands.size ()) {
674+ theLog.log (LogInfoDevel_ (3013 ), " Registered custom commands:" );
675+ }
604676 for (const auto &kv : customCommands) {
605677 theLog.log (LogInfoDevel_ (3013 ), " %s : %s" , kv.first .c_str (), kv.second .c_str ());
606678 }
@@ -1597,6 +1669,10 @@ Readout::~Readout() {
15971669 theLog.log (LogInfoDevel_ (3013 ), " Killing %d" , (int )customCommandsShellPid);
15981670 kill (customCommandsShellPid, SIGKILL);
15991671 }
1672+ if (customCommandsShellFdErr >= 0 ) {
1673+ customCommandsShellLog = nullptr ;
1674+ close (customCommandsShellFdErr);
1675+ }
16001676 }
16011677
16021678 #ifdef WITH_DB
@@ -1615,7 +1691,9 @@ void Readout::executeCustomCommand(const char *stateChange) {
16151691 fsync (customCommandsShellFdIn);
16161692 LineBuffer b;
16171693 const int cmdTimeout = 10000 ; // 10s timeout
1618- b.appendFromFileDescriptor (customCommandsShellFdOut, cmdTimeout);
1694+ if (b.appendFromFileDescriptor (customCommandsShellFdOut, cmdTimeout)) {
1695+ theLog.log (LogInfoDevel_ (3013 ), " Command launcher unavailable" ); // EOF
1696+ }
16191697 std::string result;
16201698 if (b.getNextLine (result) == 0 ) {
16211699 theLog.log (LogInfoDevel_ (3013 ), " Command executed: %s" , result.c_str ());
0 commit comments