API
xindiserver.hpp
Go to the documentation of this file.
1 /** \file xindiserver.hpp
2  * \brief The MagAO-X INDI Server wrapper header.
3  *
4  * \ingroup xindiserver_files
5  */
6 
7 #ifndef xindiserver_hpp
8 #define xindiserver_hpp
9 
10 #include <sys/wait.h>
11 
12 #include <iostream>
13 #include <vector>
14 #include <string>
15 #include <map>
16 #include <unordered_set>
17 
18 #include <mx/ioutils/fileUtils.hpp>
19 
20 #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
21 #include "../../magaox_git_version.h"
22 
23 /** \defgroup xindiserver INDI Server wrapper.
24  * \brief Manages INDI server in the MagAO-X context.
25  *
26  * <a href="../handbook/operating/software/apps/network.html#xindiserver">Application Documentation</a>
27  *
28  * \ingroup apps
29  *
30  */
31 
32 /** \defgroup xindiserver_files xindiserver Files
33  * \ingroup xindiserver
34  */
35 
36 namespace MagAOX
37 {
38 namespace app
39 {
40 
41 #define SSHTUNNEL_E_NOTUNNELS (-10)
42 
43 /// Structure to hold an sshTunnel specification, used for created command line args for indiserver
44 struct sshTunnel
45 {
46  std::string m_remoteHost;
47  int m_localPort {0};
48 };
49 
50 ///The map used to hold tunnel specifications.
51 typedef std::unordered_map<std::string, sshTunnel> tunnelMapT;
52 
53 /// Create the tunnel map from a configurator
54 /**
55  * \returns 0 on success
56  * \returns SSHTUNNEL_E_NOTUNNELS if no tunnels are found (< 0).
57  */
58 inline
59 int loadSSHTunnelConfigs( tunnelMapT & tmap, ///< [out] the tunnel map which will be populated
60  mx::app::appConfigurator & config ///< [in] the configurator which contains tunnel specifications.
61  )
62 {
63  std::vector<std::string> sections;
64 
65  config.unusedSections(sections);
66 
67  if( sections.size() == 0 )
68  {
69  return SSHTUNNEL_E_NOTUNNELS;
70  }
71 
72  size_t matched = 0;
73 
74  //Now see if any sections match a tunnel specification
75  for(size_t i=0; i< sections.size(); ++i)
76  {
77  //A tunnel as remoteHost, localPort, and remotePort.
78  if( config.isSetUnused(mx::app::iniFile::makeKey(sections[i], "remoteHost" )) &&
79  config.isSetUnused(mx::app::iniFile::makeKey(sections[i], "localPort" )) &&
80  config.isSetUnused(mx::app::iniFile::makeKey(sections[i], "remotePort" )) )
81  {
82 
83  std::string remoteHost;
84  int localPort = 0;
85  bool compress = false;
86 
87  config.configUnused( remoteHost, mx::app::iniFile::makeKey(sections[i], "remoteHost" ) );
88  config.configUnused( localPort, mx::app::iniFile::makeKey(sections[i], "localPort" ) );
89  config.configUnused( compress, mx::app::iniFile::makeKey(sections[i], "compress" ) );
90 
91  tmap[sections[i]] = sshTunnel({remoteHost, localPort});
92 
93  ++matched;
94  }
95  }
96 
97  if(matched == 0) return SSHTUNNEL_E_NOTUNNELS;
98 
99  return 0;
100 }
101 
102 
103 #define XINDISERVER_E_BADDRIVERSPEC (-100)
104 #define XINDISERVER_E_DUPLICATEDRIVER (-101)
105 #define XINDISERVER_E_VECTOREXCEPT (-102)
106 #define XINDISERVER_E_NOTUNNELS (-103)
107 #define XINDISERVER_E_TUNNELNOTFOUND (-104)
108 #define XINDISERVER_E_BADSERVERSPEC (-110)
109 
110 
111 /** The INDI Server wrapper application class.
112  *
113  * \ingroup xindiserver
114  *
115  */
116 class xindiserver : public MagAOXApp<false>
117 {
118 
119  //Give the test harness access.
120  friend class xindiserver_test;
121 
122 protected:
123 
124  int indiserver_m {-1}; ///< The indiserver MB behind setting (passed to indiserver)
125  bool indiserver_n {false}; ///< The indiserver ignore /tmp/noindi flag (passed to indiserver)
126  int indiserver_p {-1}; ///< The indiserver port (passed to indiserver)
127  int indiserver_v {-1}; ///< The indiserver verbosity (passed to indiserver)
128  bool indiserver_x {false}; ///< The indiserver terminate after last exit flag (passed to indiserver)
129 
130  std::string m_driverPath; ///< The path to the local drivers
131  std::vector<std::string> m_local; ///< List of local drivers passed in by config
132  std::vector<std::string> m_remote; ///< List of remote drivers passed in by config
133  std::unordered_set<std::string> m_driverNames; ///< List of driver names processed for command line, used to prevent duplication.
134 
135  std::vector<std::string> m_remoteServers; ///< List of other INDI server config files to read remote drivers from.
136 
137  tunnelMapT m_tunnels; ///< Map of the ssh tunnels, used for processing the remote drivers in m_remote.
138 
139  std::vector<std::string> m_indiserverCommand; ///< The command line arguments to indiserver
140 
141  pid_t m_isPID {0}; ///< The PID of the indiserver process
142 
143  int m_isSTDERR {-1}; ///< The output of stderr of the indiserver process
144  int m_isSTDERR_input {-1}; ///< The input end of stderr, used to wake up the log thread on shutdown.
145 
146  int m_isLogThreadPrio {0}; ///< Priority of the indiserver log capture thread, should normally be 0.
147 
148  std::thread m_isLogThread; ///< A separate thread for capturing indiserver logs
149 
150 public:
151  /// Default c'tor.
152  xindiserver();
153 
154  /// D'tor, declared and defined for noexcept.
155  ~xindiserver() noexcept
156  {}
157 
158  virtual void setupConfig();
159 
160  virtual void loadConfig();
161 
162  ///Construct the vector of indiserver arguments for exec.
163  /** The first entry is argv[0], that is "indiserver".
164  *
165  * \returns 0 on success.
166  * \returns -1 on error, including if an exception is caught.
167  */
168  int constructIndiserverCommand(std::vector<std::string> & indiserverCommand /**< [out] the vector of command line arguments for exec */);
169 
170  ///Validate the local driver strings, and append them to the indi server command line arguments.
171  /** Checks that the local driver specs don't contain @,:, or /. Then prepends the MagAO-X standard
172  * driver path, and then appends to the driverArgs vector passed in.
173  *
174  * \returns 0 on success.
175  * \returns -1 on error, either from failed validation or an exception in std::vector.
176  */
177  int addLocalDrivers( std::vector<std::string> & driverArgs /**< [out] the vector of command line arguments for exec*/);
178 
179 
180  ///Validate the remote driver entries, and append them to the indi server command line arguments.
181  /** Parses the remote driver specs, then
182  * constructs the command line arguments and appends them to the driverArgs vector passed in.
183  *
184  * \returns 0 on success.
185  * \returns -1 on error, either from failed validation or an exception in std::vector.
186  */
187  int addRemoteDrivers( std::vector<std::string> & driverArgs /**< [out] the vector of command line arguments for exec*/);
188 
189  ///Validate the remote server entries, read the associated config files for local drivers, and append them to the indi server command line arguments as remote ddrivers.
190  /** Parses the remote server specs, then reads the remote server config files, and then
191  * constructs the command line arguments and appends them to the driverArgs vector passed in.
192  *
193  * \returns 0 on success.
194  * \returns -1 on error, either from failed validation or an exception in std::vector.
195  */
196  int addRemoteServers( std::vector<std::string> & driverArgs /**< [out] the vector of command line arguments for exec*/);
197 
198  ///Forks and exec's the indiserver process with the command constructed from local, remote, and hosts.
199  /** Also saves the PID and stderr pipe file descriptors for log capture.
200  *
201  * \returns 0 on success
202  * \returns -1 on error (fatal)
203  */
204  int forkIndiserver();
205 
206  ///Thread starter, called by isLogThreadStart on thread construction. Calls isLogThreadExec.
207  static void _isLogThreadStart( xindiserver * l /**< [in] a pointer to a xindiserver instance (normally this) */);
208 
209  /// Start the log capture.
210  int isLogThreadStart();
211 
212  /// Execute the log capture.
213  void isLogThreadExec();
214 
215  /// Process a log entry from indiserver, putting it into MagAO-X standard form
216  int processISLog( std::string logs );
217 
218  /// Startup functions
219  /**
220  * Forks and execs the actual indiserver. Captures its stderr output for logging.
221  */
222  virtual int appStartup();
223 
224  /// Implementation of the FSM for xindiserver.
225  virtual int appLogic();
226 
227  /// Kills indiserver, and wakes up the log capture thread.
228  virtual int appShutdown();
229 
230 
231 };
232 
233 inline
234 xindiserver::xindiserver() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
235 {
236  //Use the sshTunnels.conf config file
237  m_configBase = "sshTunnels";
238 
239  return;
240 }
241 
242 inline
244 {
245  config.add("indiserver.m", "m", "", argType::Required, "indiserver", "m", false, "int", "indiserver kills client if it gets more than this many MB behind, default 50");
246  config.add("indiserver.N", "N", "", argType::True, "indiserver", "N", false, "bool", "indiserver: ignore /tmp/noindi. Capitalized to avoid conflict with --name");
247  config.add("indiserver.p", "p", "", argType::Required, "indiserver", "p", false, "int", "indiserver: alternate IP port, default 7624");
248  config.add("indiserver.v", "v", "", argType::True, "indiserver", "v", false, "int", "indiserver: log verbosity, -v, -vv or -vvv");
249  config.add("indiserver.x", "x", "", argType::True, "indiserver", "x", false, "bool", "exit after last client disconnects -- FOR PROFILING ONLY");
250 
251  config.add("local.drivers","L", "local.drivers" , argType::Required, "local", "drivers", false, "vector string", "List of local drivers to start.");
252  config.add("remote.drivers","R", "remote.drivers" , argType::Required, "remote", "drivers", false, "vector string", "List of remote drivers to start, in the form of name@tunnel, where tunnel is the name of a tunnel specified in sshTunnels.conf.");
253 
254  config.add("remote.servers","", "remote.servers" , argType::Required, "remote", "servers", false, "vector string", "List of servers to load remote drivers for, in the form of name@tunnel. Name is used to load the name.conf configuration file, and tunnel is the name of a tunnel specified in sshTunnels.conf.");
255 
256 }
257 
258 
259 
260 inline
262 {
263  //indiserver config:
264  config(indiserver_m, "indiserver.m");
265  config(indiserver_n, "indiserver.N");
266  config(indiserver_p, "indiserver.p");
267 
268  indiserver_v = config.verbosity("indiserver.v");
269 
270  config(indiserver_x, "indiserver.x");
271 
272  config(m_local, "local.drivers");
273  config(m_remote, "remote.drivers");
274  config(m_remoteServers, "remote.servers");
275 
277 }
278 
279 inline
280 int xindiserver::constructIndiserverCommand( std::vector<std::string> & indiserverCommand)
281 {
282  try
283  {
284  indiserverCommand.push_back("indiserver");
285 
286  if(indiserver_m > 0)
287  {
288  indiserverCommand.push_back("-m");
289  indiserverCommand.push_back(mx::ioutils::convertToString(indiserver_m));
290  }
291 
292  if(indiserver_n == true) indiserverCommand.push_back("-n");
293 
294  if(indiserver_p > 0)
295  {
296  indiserverCommand.push_back("-p");
297  indiserverCommand.push_back(mx::ioutils::convertToString(indiserver_p));
298  }
299 
300  if(indiserver_v == 1) indiserverCommand.push_back("-v");
301 
302  if(indiserver_v == 2) indiserverCommand.push_back("-vv");
303 
304  if(indiserver_v >= 3) indiserverCommand.push_back("-vvv");
305 
306  if(indiserver_x == true) indiserverCommand.push_back("-x");
307  }
308  catch(...)
309  {
310  log<software_critical>(software_log::messageT(__FILE__, __LINE__, "Exception thrown by std::vector."));
311  return -1;
312  }
313 
314  return 0;
315 }
316 
317 inline
318 int xindiserver::addLocalDrivers( std::vector<std::string> & driverArgs )
319 {
321  m_driverPath += "/";
323  m_driverPath += "/";
324 
325  for(size_t i=0; i< m_local.size(); ++i)
326  {
327  size_t bad = m_local[i].find_first_of("@:/", 0);
328 
329  if(bad != std::string::npos)
330  {
331  log<software_critical>({__FILE__, __LINE__, "Local driver can't have host spec or path(@,:,/): " + m_local[i]});
332 
334  }
335 
336  if( m_driverNames.count(m_local[i]) > 0)
337  {
338  log<software_critical>({__FILE__, __LINE__, "Duplicate driver name: " + m_local[i]});
340  }
341 
342  m_driverNames.insert(m_local[i]);
343 
344  std::string dname = m_driverPath + m_local[i];
345 
346  try
347  {
348  driverArgs.push_back(dname);
349  }
350  catch(...)
351  {
352  log<software_critical>({__FILE__, __LINE__, "Exception thrown by std::vector"});
354  }
355  }
356 
357  return 0;
358 }
359 
360 inline
361 int xindiserver::addRemoteDrivers( std::vector<std::string> & driverArgs )
362 {
363  for(size_t i=0; i < m_remote.size(); ++i)
364  {
365  std::string driver;
366  std::string tunnel;
367 
368  size_t p = m_remote[i].find('@');
369 
370  if(p == 0 || p == std::string::npos)
371  {
372  log<software_critical>({__FILE__, __LINE__, "Error parsing remote driver specification: " + m_remote[i] + "\n"});
374  }
375 
376  driver = m_remote[i].substr(0, p);
377  tunnel = m_remote[i].substr(p+1);
378 
379  if( m_driverNames.count(driver) > 0)
380  {
381  log<software_critical>({__FILE__, __LINE__, "Duplicate driver name: " + driver});
383  }
384 
385  std::ostringstream oss;
386 
387  if(m_tunnels.size() == 0)
388  {
389  log<software_critical>({__FILE__, __LINE__, "No tunnels specified.\n"});
391  }
392 
393  if(m_tunnels.count(tunnel) != 1)
394  {
395  log<software_critical>({__FILE__, __LINE__, "Tunnel not found for: " + m_remote[i] + "\n"});
397  }
398 
399  m_driverNames.insert(driver);
400 
401  oss << driver << "@localhost:" << m_tunnels[tunnel].m_localPort;
402 
403  try
404  {
405  driverArgs.push_back(oss.str());
406  }
407  catch(...)
408  {
409  log<software_critical>({__FILE__, __LINE__, "Exception thrown by vector::push_back."});
411  }
412  }
413 
414  return 0;
415 
416 }
417 
418 inline
419 int xindiserver::addRemoteServers( std::vector<std::string> & driverArgs )
420 {
421  for(size_t j=0; j < m_remoteServers.size(); ++j)
422  {
423  std::string server;
424  std::string tunnel;
425 
426  size_t p = m_remoteServers[j].find('@');
427 
428  if(p == 0 || p == std::string::npos)
429  {
430  log<software_critical>({__FILE__, __LINE__, "Error parsing remote server specification: " + m_remote[j] + "\n"});
432  }
433 
434  server = m_remoteServers[j].substr(0, p);
435  tunnel = m_remoteServers[j].substr(p+1);
436 
437  if(m_tunnels.size() == 0)
438  {
439  log<software_critical>({__FILE__, __LINE__, "No tunnels specified.\n"});
441  }
442 
443  if(m_tunnels.count(tunnel) != 1)
444  {
445  log<software_critical>({__FILE__, __LINE__, "Tunnel not found for: " + m_remote[j] + "\n"});
447  }
448 
449  //Now we create a local app configurator, and read the other server's config file
450  mx::app::appConfigurator rsconfig;
451 
452  rsconfig.add("local.drivers", "", "" , argType::Required, "local", "drivers", false, "", "");
453 
454  std::string rsconfigPath = m_configDir + "/" + server + ".conf";
455 
456  rsconfig.readConfig(rsconfigPath);
457 
458  std::vector<std::string> local;
459 
460  rsconfig(local, "local.drivers");
461 
462  for(size_t i=0; i < local.size(); ++i)
463  {
464  size_t bad = local[i].find_first_of("@:/", 0);
465 
466  if(bad != std::string::npos)
467  {
468  log<software_critical>({__FILE__, __LINE__, "Remote server's Local driver can't have host spec or path(@,:,/): " + local[i]});
469 
471  }
472 
473  if( m_driverNames.count(local[i]) > 0)
474  {
475  log<software_critical>({__FILE__, __LINE__, "Duplicate driver name from remote server: " + local[i]});
477  }
478 
479  m_driverNames.insert(local[i]);
480 
481  std::ostringstream oss;
482 
483  oss << local[i] << "@localhost:" << m_tunnels[tunnel].m_localPort;
484 
485  try
486  {
487  driverArgs.push_back(oss.str());
488  }
489  catch(...)
490  {
491  log<software_critical>({__FILE__, __LINE__, "Exception thrown by vector::push_back."});
493  }
494  }
495 
496  }
497 
498  return 0;
499 }
500 
501 inline
503 {
504 
506  {
507  std::string coml = "Starting indiserver with command: ";
508  for(size_t i=0;i<m_indiserverCommand.size();++i)
509  {
510  coml += m_indiserverCommand[i];
511  coml += " ";
512  }
513 
514  log<text_log>(coml);
515  std::cerr << coml << std::endl;
516  }
517 
518  int filedes[2];
519  if (pipe(filedes) == -1)
520  {
521  log<software_error>({__FILE__, __LINE__, errno});
522  return -1;
523  }
524 
525 
526  m_isPID = fork();
527 
528  if(m_isPID < 0)
529  {
530  log<software_error>({__FILE__, __LINE__, errno, "fork failed"});
531  return -1;
532  }
533 
534 
535  if(m_isPID == 0)
536  {
537  //Route STDERR of child to pipe input.
538  while ((dup2(filedes[1], STDERR_FILENO) == -1) && (errno == EINTR)) {}
539  close(filedes[1]);
540  close(filedes[0]);
541 
542  const char ** drivers = new const char*[m_indiserverCommand.size()+1];
543 
544  for(size_t i=0; i< m_indiserverCommand.size(); ++i)
545  {
546  drivers[i] = (m_indiserverCommand[i].data());
547  }
548  drivers[m_indiserverCommand.size()] = NULL;
549 
550 
551  execvp("indiserver", (char * const*) drivers);
552 
553  log<software_error>({__FILE__, __LINE__, errno, "execvp returned"});
554 
555  delete[] drivers;
556 
557  return -1;
558  }
559 
560  m_isSTDERR = filedes[0];
561  m_isSTDERR_input = filedes[1];
562 
564  {
565  std::string coml = "indiserver started with PID " + mx::ioutils::convertToString(m_isPID);
566  log<text_log>(coml);
567  }
568 
569  return 0;
570 }
571 
572 inline
574 {
575  l->isLogThreadExec();
576 }
577 
578 inline
580 {
581  try
582  {
583  m_isLogThread = std::thread( _isLogThreadStart, this);
584  }
585  catch( const std::exception & e )
586  {
587  log<software_error>({__FILE__,__LINE__, std::string("Exception on I.S. log thread start: ") + e.what()});
588  return -1;
589  }
590  catch( ... )
591  {
592  log<software_error>({__FILE__,__LINE__, "Unkown exception on I.S. log thread start"});
593  return -1;
594  }
595 
596  if(!m_isLogThread.joinable())
597  {
598  log<software_error>({__FILE__, __LINE__, "I.S. log thread did not start"});
599  return -1;
600  }
601 
602  sched_param sp;
603  sp.sched_priority = m_isLogThreadPrio;
604 
605  int rv = pthread_setschedparam( m_isLogThread.native_handle(), SCHED_OTHER, &sp);
606 
607  if(rv != 0)
608  {
609  log<software_error>({__FILE__, __LINE__, rv, "Error setting thread params."});
610  return -1;
611  }
612 
613  return 0;
614 
615 }
616 
617 
618 
619 inline
621 {
622  char buffer[4096];
623 
624  std::string logs;
625  while(m_shutdown == 0)
626  {
627  ssize_t count = read(m_isSTDERR, buffer, sizeof(buffer)-1); //Make wure we always have room for \0
628  if (count <= 0 || m_shutdown == 1)
629  {
630  continue;
631  }
632  else if(count > (ssize_t) sizeof(buffer)-1)
633  {
634  log<software_error>({__FILE__, __LINE__, "read returned too many bytes."});
635  continue;
636  }
637  else
638  {
639  buffer[count] = '\0';
640 
641  logs += buffer;
642 
643  //Keep reading until \n found, then process.
644  if(logs.back() == '\n')
645  {
646  size_t bol = 0;
647  while(bol < logs.size())
648  {
649  size_t eol = logs.find('\n', bol);
650  if(eol == std::string::npos) break;
651 
652  processISLog(logs.substr(bol, eol-bol));
653  bol = eol + 1;
654  }
655  logs = "";
656  }
657  }
658  }
659 
660 }
661 
662 inline
663 int xindiserver::processISLog( std::string logs )
664 {
665  size_t st = 0;
666  size_t ed;
667 
668  ed = logs.find(':', st);
669  if(ed != std::string::npos) ed = logs.find(':', ed+1);
670  if(ed != std::string::npos) ed = logs.find(':', ed+1);
671 
672  if(ed == std::string::npos)
673  {
674  //log<software_error>({__FILE__, __LINE__, "Did not find timestamp : in log entry"});
675  log<text_log>(logs, logPrio::LOG_INFO);
676  return 0;
677  }
678 
679  std::string ts = logs.substr(st, ed-st);
680 
681  double dsec;
682 
683  tm bdt;
684  mx::sys::ISO8601dateBreakdown(bdt.tm_year, bdt.tm_mon, bdt.tm_mday, bdt.tm_hour, bdt.tm_min, dsec, ts);
685 
686  bdt.tm_year -= 1900;
687  bdt.tm_mon -= 1;
688  bdt.tm_sec = (int) dsec;
689  bdt.tm_isdst = 0;
690  bdt.tm_gmtoff = 0;
691 
692  timespecX tsp;
693 
694  tsp.time_s = timegm(&bdt);
695  tsp.time_ns = (nanosecT) ((dsec-bdt.tm_sec)*1e9 + 0.5);
696 
697  ++ed;
698  st = logs.find_first_not_of(" ", ed);
699 
700  if(st == std::string::npos) st = ed;
701  if(st == logs.size())
702  {
703  log<software_error>({__FILE__, __LINE__, "Did not find log entry."});
704  return -1;
705  }
706 
707  std::string logstr = logs.substr(st, logs.size()-st);
708 
710 
711  //Look for fatal errors
712  if(logstr.find("xindidriver") != std::string::npos) //Errors from xindidriver
713  {
714  if(logstr.find("failed to lock") != std::string::npos)
715  {
716  prio = logPrio::LOG_CRITICAL;
717  }
718  }
719  else if(logstr.find("bind: Address already in use") != std::string::npos) //Errors from indiserver
720  {
721  prio = logPrio::LOG_CRITICAL;
722  }
723 
724  m_log.log<text_log>(tsp, "IS: " + logstr, prio);
725 
726  if(prio == logPrio::LOG_CRITICAL)
727  {
729  m_shutdown = true;
730  }
731 
732  return 0;
733 }
734 
735 inline
737 {
739  {
740  log<software_critical>({__FILE__, __LINE__});
741  return -1;
742  }
743 
745  {
746  log<software_critical>({__FILE__, __LINE__});
747  return -1;
748  }
749 
751  {
752  log<software_critical>({__FILE__, __LINE__});
753  return -1;
754  }
755 
757  {
758  log<software_critical>({__FILE__, __LINE__});
759  return -1;
760  }
761  //--------------------
762  //Make symlinks
763  //--------------------
764  std::string path1 = "/opt/MagAOX/bin/xindidriver";
765  for(size_t i=0; i<m_local.size(); ++i)
766  {
767  elevatedPrivileges elPriv(this);
768 
769  std::cerr << "creating symlink " << path1 << " " << m_driverPath + m_local[i] << "\n";
770 
771  int rv = symlink(path1.c_str(), (m_driverPath + m_local[i]).c_str());
772 
773  if(rv < 0 && errno != EEXIST)
774  {
775  log<software_error>({__FILE__, __LINE__, errno});
776  log<software_error>({__FILE__, __LINE__, "Failed to create symlink for driver: " + m_local[i] + ". Continuing."});
777  }
778  }
779 
780  m_local.clear();
781  m_remote.clear();
782  m_tunnels.clear();
783 
784  //--------------------
785  //Now start indiserver
786  //--------------------
787  if(forkIndiserver() < 0)
788  {
789  log<software_critical>({__FILE__, __LINE__});
790  return -1;
791  }
792 
793  if(isLogThreadStart() < 0)
794  {
795  log<software_critical>({__FILE__, __LINE__});
796  return -1;
797  }
798 
799  return 0;
800 }
801 
802 inline
804 {
805  int status;
806  pid_t result = waitpid(m_isPID, &status, WNOHANG);
807  if (result == 0)
808  {
810  }
811  else
812  {
813  //We don't care why. If indiserver is not alive while in this function then it's a fatal error.
814  log<text_log>("indiserver has exited", logPrio::LOG_CRITICAL);
816  return -1;
817  }
818 
819 
820 
821  return 0;
822 }
823 
824 inline
826 {
827  kill(m_isPID, SIGTERM);
828 
829  char w = '\0';
830  ssize_t nwr = write(m_isSTDERR_input, &w, 1);
831  if(nwr != 1)
832  {
833  log<software_error>({__FILE__, __LINE__, errno });
834  log<software_error>({__FILE__, __LINE__, "Error on write to i.s. log thread. Sending SIGTERM."});
835  pthread_kill(m_isLogThread.native_handle(), SIGTERM);
836 
837  }
838 
839  if(m_isLogThread.joinable()) m_isLogThread.join();
840  return 0;
841 }
842 
843 
844 } //namespace app
845 } //namespace MagAOX
846 
847 #endif //xindiserver_hpp
848 
849 
850 
851 
852 
853 
854 
855 
856 
857 
858 
859 
860 
861 
862 
863 
864 
865 
866 
867 
868 
869 
870 
871 
872 
873 
874 
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:75
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2082
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
Definition: MagAOXApp.hpp:102
std::string m_configDir
The path to configuration files for MagAOX.
Definition: MagAOXApp.hpp:90
std::string m_configBase
The name of a base config class for this app (minus .conf).
Definition: MagAOXApp.hpp:92
int addRemoteServers(std::vector< std::string > &driverArgs)
Validate the remote server entries, read the associated config files for local drivers,...
std::vector< std::string > m_indiserverCommand
The command line arguments to indiserver.
int isLogThreadStart()
Start the log capture.
int indiserver_m
The indiserver MB behind setting (passed to indiserver)
int m_isSTDERR
The output of stderr of the indiserver process.
virtual int appLogic()
Implementation of the FSM for xindiserver.
int addRemoteDrivers(std::vector< std::string > &driverArgs)
Validate the remote driver entries, and append them to the indi server command line arguments.
std::vector< std::string > m_local
List of local drivers passed in by config.
void isLogThreadExec()
Execute the log capture.
virtual void setupConfig()
virtual int appShutdown()
Kills indiserver, and wakes up the log capture thread.
~xindiserver() noexcept
D'tor, declared and defined for noexcept.
static void _isLogThreadStart(xindiserver *l)
Thread starter, called by isLogThreadStart on thread construction. Calls isLogThreadExec.
pid_t m_isPID
The PID of the indiserver process.
xindiserver()
Default c'tor.
int forkIndiserver()
Forks and exec's the indiserver process with the command constructed from local, remote,...
bool indiserver_n
The indiserver ignore /tmp/noindi flag (passed to indiserver)
int addLocalDrivers(std::vector< std::string > &driverArgs)
Validate the local driver strings, and append them to the indi server command line arguments.
bool indiserver_x
The indiserver terminate after last exit flag (passed to indiserver)
std::string m_driverPath
The path to the local drivers.
std::vector< std::string > m_remote
List of remote drivers passed in by config.
std::vector< std::string > m_remoteServers
List of other INDI server config files to read remote drivers from.
std::unordered_set< std::string > m_driverNames
List of driver names processed for command line, used to prevent duplication.
int m_isSTDERR_input
The input end of stderr, used to wake up the log thread on shutdown.
std::thread m_isLogThread
A separate thread for capturing indiserver logs.
int m_isLogThreadPrio
Priority of the indiserver log capture thread, should normally be 0.
tunnelMapT m_tunnels
Map of the ssh tunnels, used for processing the remote drivers in m_remote.
int indiserver_v
The indiserver verbosity (passed to indiserver)
virtual int appStartup()
Startup functions.
int indiserver_p
The indiserver port (passed to indiserver)
virtual void loadConfig()
int constructIndiserverCommand(std::vector< std::string > &indiserverCommand)
Construct the vector of indiserver arguments for exec.
int processISLog(std::string logs)
Process a log entry from indiserver, putting it into MagAO-X standard form.
#define MAGAOX_path
The path to the MagAO-X system files.
Definition: paths.hpp:22
#define MAGAOX_driverRelPath
The relative path to the INDI drivers.
Definition: paths.hpp:78
int8_t logPrioT
The type of the log priority code.
Definition: logDefs.hpp:21
@ FAILURE
The application has failed, should be used when m_shutdown is set for an error.
Definition: stateCodes.hpp:37
@ CONNECTED
The application has connected to the device or service.
Definition: stateCodes.hpp:45
std::ostream & cerr()
std::unordered_map< std::string, sshTunnel > tunnelMapT
The map used to hold tunnel specifications.
Definition: xindiserver.hpp:51
std::string m_remoteHost
Definition: xindiserver.hpp:46
int loadSSHTunnelConfigs(tunnelMapT &tmap, mx::app::appConfigurator &config)
Create the tunnel map from a configurator.
Definition: xindiserver.hpp:59
Structure to hold an sshTunnel specification, used for created command line args for indiserver.
Definition: xindiserver.hpp:45
Definition: dm.hpp:24
constexpr static logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
Definition: logPriority.hpp:37
constexpr static logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
Definition: logPriority.hpp:49
uint32_t nanosecT
The type used for nanoseconds.
Definition: logDefs.hpp:34
void log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry, including a message.
Definition: logManager.hpp:527
int logLevel(logPrioT newLev)
Set a new value of logLevel.
Definition: logManager.hpp:299
A simple text log, a string-type log.
Definition: text_log.hpp:24
A fixed-width timespec structure.
Definition: timespecX.hpp:35
nanosecT time_ns
Nanoseconds.
Definition: timespecX.hpp:37
secT time_s
Time since the Unix epoch.
Definition: timespecX.hpp:36
#define XINDISERVER_E_BADDRIVERSPEC
#define XINDISERVER_E_NOTUNNELS
#define XINDISERVER_E_DUPLICATEDRIVER
#define XINDISERVER_E_BADSERVERSPEC
#define XINDISERVER_E_VECTOREXCEPT
#define SSHTUNNEL_E_NOTUNNELS
Definition: xindiserver.hpp:41
#define XINDISERVER_E_TUNNELNOTFOUND