LCOV - code coverage report
Current view: top level - apps/xindiserver - xindiserver.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 24.6 % 329 81
Test Date: 2026-01-03 21:03:39 Functions: 35.3 % 17 6

            Line data    Source code
       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           10 : 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           10 :    std::vector<std::string> sections;
      64              : 
      65           10 :    config.unusedSections(sections);
      66              : 
      67           10 :    if( sections.size() == 0 )
      68              :    {
      69            0 :       return SSHTUNNEL_E_NOTUNNELS;
      70              :    }
      71              : 
      72           10 :    size_t matched = 0;
      73              : 
      74              :    //Now see if any sections match a tunnel specification
      75           26 :    for(size_t i=0; i< sections.size(); ++i)
      76              :    {
      77              :       //A tunnel as remoteHost, localPort, and remotePort.
      78           48 :       if( config.isSetUnused(mx::app::iniFile::makeKey(sections[i], "remoteHost" )) &&
      79           96 :              config.isSetUnused(mx::app::iniFile::makeKey(sections[i], "localPort" )) &&
      80           48 :                 config.isSetUnused(mx::app::iniFile::makeKey(sections[i], "remotePort" )) )
      81              :       {
      82              : 
      83           15 :          std::string remoteHost;
      84           15 :          int localPort = 0;
      85           15 :          bool compress = false;
      86              : 
      87           30 :          config.configUnused( remoteHost, mx::app::iniFile::makeKey(sections[i], "remoteHost" ) );
      88           45 :          config.configUnused( localPort, mx::app::iniFile::makeKey(sections[i], "localPort" ) );
      89           15 :          config.configUnused( compress, mx::app::iniFile::makeKey(sections[i], "compress" ) );
      90              : 
      91           15 :          tmap[sections[i]] = sshTunnel({remoteHost, localPort});
      92              : 
      93           15 :          ++matched;
      94           15 :       }
      95              :    }
      96              : 
      97           10 :    if(matched == 0) return SSHTUNNEL_E_NOTUNNELS;
      98              : 
      99           10 :    return 0;
     100           10 : }
     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           27 :    ~xindiserver() noexcept
     156           27 :    {}
     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           81 : xindiserver::xindiserver() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
     235              : {
     236              :    //Use the sshTunnels.conf config file
     237           27 :    m_configBase = "sshTunnels";
     238              : 
     239           27 :    return;
     240            0 : }
     241              : 
     242              : inline
     243            0 : void xindiserver::setupConfig()
     244              : {
     245            0 :    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            0 :    config.add("indiserver.N", "N", "", argType::True, "indiserver", "N", false,  "bool", "indiserver: ignore /tmp/noindi.  Capitalized to avoid conflict with --name");
     247            0 :    config.add("indiserver.p", "p", "", argType::Required, "indiserver", "p", false,  "int", "indiserver: alternate IP port, default 7624");
     248            0 :    config.add("indiserver.v", "v", "", argType::True, "indiserver", "v", false,  "int", "indiserver: log verbosity, -v, -vv or -vvv");
     249            0 :    config.add("indiserver.x", "x", "", argType::True, "indiserver", "x", false,  "bool", "exit after last client disconnects -- FOR PROFILING ONLY");
     250              : 
     251            0 :    config.add("local.drivers","L", "local.drivers" , argType::Required, "local", "drivers", false,  "vector string", "List of local drivers to start.");
     252            0 :    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            0 :    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            0 : }
     257              : 
     258              : 
     259              : 
     260              : inline
     261            0 : void xindiserver::loadConfig()
     262              : {
     263              :    //indiserver config:
     264            0 :    config(indiserver_m, "indiserver.m");
     265            0 :    config(indiserver_n, "indiserver.N");
     266            0 :    config(indiserver_p, "indiserver.p");
     267              : 
     268            0 :    indiserver_v = config.verbosity("indiserver.v");
     269              : 
     270            0 :    config(indiserver_x, "indiserver.x");
     271              : 
     272            0 :    config(m_local, "local.drivers");
     273            0 :    config(m_remote, "remote.drivers");
     274            0 :    config(m_remoteServers, "remote.servers");
     275              : 
     276            0 :    if(loadSSHTunnelConfigs(m_tunnels, config) < 0)
     277              :    {
     278            0 :       m_shutdown = true;
     279            0 :       return;
     280              :    }
     281              : 
     282            0 :    if( constructIndiserverCommand(m_indiserverCommand) < 0)
     283              :    {
     284            0 :       log<software_critical>({__FILE__, __LINE__});
     285            0 :       m_shutdown = true;
     286            0 :       return;
     287              :    }
     288              : 
     289            0 :    if( addLocalDrivers(m_indiserverCommand) < 0)
     290              :    {
     291            0 :       log<software_critical>({__FILE__, __LINE__});
     292            0 :       m_shutdown = true;
     293            0 :       return;
     294              :    }
     295              : 
     296            0 :    if( addRemoteDrivers(m_indiserverCommand) < 0)
     297              :    {
     298            0 :       log<software_critical>({__FILE__, __LINE__});
     299            0 :       m_shutdown = true;
     300            0 :       return;
     301              :    }
     302              : 
     303            0 :    if( addRemoteServers(m_indiserverCommand) < 0)
     304              :    {
     305            0 :       log<software_critical>({__FILE__, __LINE__});
     306            0 :       m_shutdown = true;
     307            0 :       return;
     308              :    }
     309              : 
     310              : 
     311              : }
     312              : 
     313              : inline
     314           10 : int xindiserver::constructIndiserverCommand( std::vector<std::string> & indiserverCommand)
     315              : {
     316              :    try
     317              :    {
     318           10 :       indiserverCommand.push_back("indiserver");
     319              : 
     320           10 :       if(indiserver_m > 0)
     321              :       {
     322            2 :          indiserverCommand.push_back("-m");
     323            2 :          indiserverCommand.push_back(std::format("{}", indiserver_m));
     324              :       }
     325              : 
     326           14 :       if(indiserver_n == true) indiserverCommand.push_back("-n");
     327              : 
     328           10 :       if(indiserver_p > 0)
     329              :       {
     330            2 :          indiserverCommand.push_back("-p");
     331            2 :          indiserverCommand.push_back(std::format("{}", indiserver_p));
     332              :       }
     333              : 
     334           12 :       if(indiserver_v == 1) indiserverCommand.push_back("-v");
     335              : 
     336           14 :       if(indiserver_v == 2) indiserverCommand.push_back("-vv");
     337              : 
     338           14 :       if(indiserver_v >= 3) indiserverCommand.push_back("-vvv");
     339              : 
     340           14 :       if(indiserver_x == true) indiserverCommand.push_back("-x");
     341              :    }
     342            0 :    catch(...)
     343              :    {
     344            0 :       log<software_critical>({"Exception thrown by std::vector."});
     345            0 :       return -1;
     346            0 :    }
     347              : 
     348           10 :    return 0;
     349              : }
     350              : 
     351              : inline
     352            9 : int xindiserver::addLocalDrivers( std::vector<std::string> & driverArgs )
     353              : {
     354            9 :    m_driverPath = MAGAOX_path;
     355            9 :    m_driverPath += "/";
     356            9 :    m_driverPath += MAGAOX_driverRelPath;
     357            9 :    m_driverPath += "/";
     358              : 
     359           21 :    for(size_t i=0; i< m_local.size(); ++i)
     360              :    {
     361           17 :       size_t bad = m_local[i].find_first_of("@:/", 0);
     362              : 
     363           17 :       if(bad != std::string::npos)
     364              :       {
     365            3 :          log<software_critical>("Local driver can't have host spec or path(@,:,/): " + m_local[i]);
     366              : 
     367            5 :          return XINDISERVER_E_BADDRIVERSPEC;
     368              :       }
     369              : 
     370           14 :       if( m_driverNames.count(m_local[i]) > 0)
     371              :       {
     372            2 :          log<software_critical>("Duplicate driver name: " + m_local[i]);
     373            2 :          return XINDISERVER_E_DUPLICATEDRIVER;
     374              :       }
     375              : 
     376           12 :       m_driverNames.insert(m_local[i]);
     377              : 
     378           12 :       std::string dname = m_driverPath + m_local[i];
     379              : 
     380              :       try
     381              :       {
     382           12 :          driverArgs.push_back(dname);
     383              :       }
     384            0 :       catch(...)
     385              :       {
     386            0 :          log<software_critical>({__FILE__, __LINE__, "Exception thrown by std::vector"});
     387            0 :          return XINDISERVER_E_VECTOREXCEPT;
     388            0 :       }
     389           12 :    }
     390              : 
     391            4 :    return 0;
     392              : }
     393              : 
     394              : inline
     395           10 : int xindiserver::addRemoteDrivers( std::vector<std::string> & driverArgs )
     396              : {
     397           25 :    for(size_t i=0; i < m_remote.size(); ++i)
     398              :    {
     399           18 :       std::string driver;
     400           18 :       std::string tunnel;
     401              : 
     402           18 :       size_t p = m_remote[i].find('@');
     403              : 
     404           18 :       if(p == 0 || p == std::string::npos)
     405              :       {
     406            1 :          log<software_critical>({__FILE__, __LINE__, "Error parsing remote driver specification: " + m_remote[i] + "\n"});
     407            1 :          return XINDISERVER_E_BADDRIVERSPEC;
     408              :       }
     409              : 
     410           17 :       driver = m_remote[i].substr(0, p);
     411           17 :       tunnel = m_remote[i].substr(p+1);
     412              : 
     413           17 :       if( m_driverNames.count(driver) > 0)
     414              :       {
     415            1 :          log<software_critical>({__FILE__, __LINE__, "Duplicate driver name: " + driver});
     416            1 :          return XINDISERVER_E_DUPLICATEDRIVER;
     417              :       }
     418              : 
     419           16 :       std::ostringstream oss;
     420              : 
     421           16 :       if(m_tunnels.size() == 0)
     422              :       {
     423            0 :          log<software_critical>({__FILE__, __LINE__, "No tunnels specified."});
     424            0 :          return XINDISERVER_E_NOTUNNELS;
     425              :       }
     426              : 
     427           16 :       if(m_tunnels.count(tunnel) != 1)
     428              :       {
     429            1 :          log<software_critical>({__FILE__, __LINE__, "Tunnel not found for: " + m_remote[i]});
     430            1 :          return XINDISERVER_E_TUNNELNOTFOUND;
     431              :       }
     432              : 
     433           15 :       m_driverNames.insert(driver);
     434              : 
     435           15 :       oss << driver << "@localhost:" << m_tunnels[tunnel].m_localPort;
     436              : 
     437              :       try
     438              :       {
     439           15 :          driverArgs.push_back(oss.str());
     440              :       }
     441            0 :       catch(...)
     442              :       {
     443            0 :          log<software_critical>({__FILE__, __LINE__, "Exception thrown by vector::push_back."});
     444            0 :          return XINDISERVER_E_VECTOREXCEPT;
     445            0 :       }
     446           22 :    }
     447              : 
     448            7 :    return 0;
     449              : 
     450              : }
     451              : 
     452              : inline
     453            0 : int xindiserver::addRemoteServers( std::vector<std::string> & driverArgs )
     454              : {
     455            0 :    for(size_t j=0; j < m_remoteServers.size(); ++j)
     456              :    {
     457            0 :       std::string server;
     458            0 :       std::string tunnel;
     459              : 
     460            0 :       size_t p = m_remoteServers[j].find('@');
     461              : 
     462            0 :       if(p == 0 || p == std::string::npos)
     463              :       {
     464            0 :          log<software_critical>({__FILE__, __LINE__, "Error parsing remote server specification: " + m_remote[j] + "\n"});
     465            0 :          return XINDISERVER_E_BADSERVERSPEC;
     466              :       }
     467              : 
     468            0 :       server = m_remoteServers[j].substr(0, p);
     469            0 :       tunnel = m_remoteServers[j].substr(p+1);
     470              : 
     471            0 :       if(m_tunnels.size() == 0)
     472              :       {
     473            0 :          log<software_critical>({__FILE__, __LINE__, "No tunnels specified.\n"});
     474            0 :          return XINDISERVER_E_NOTUNNELS;
     475              :       }
     476              : 
     477            0 :       if(m_tunnels.count(tunnel) != 1)
     478              :       {
     479            0 :          log<software_critical>({__FILE__, __LINE__, "Tunnel not found for: " + m_remote[j] + "\n"});
     480            0 :          return XINDISERVER_E_TUNNELNOTFOUND;
     481              :       }
     482              : 
     483              :       //Now we create a local app configurator, and read the other server's config file
     484            0 :       mx::app::appConfigurator rsconfig;
     485              : 
     486            0 :       rsconfig.add("local.drivers", "", "" , argType::Required, "local", "drivers", false,  "", "");
     487              : 
     488            0 :       std::string rsconfigPath = m_configDir + "/" + server + ".conf";
     489              : 
     490            0 :       rsconfig.readConfig(rsconfigPath);
     491              : 
     492            0 :       std::vector<std::string> local;
     493              : 
     494            0 :       rsconfig(local, "local.drivers");
     495              : 
     496            0 :       for(size_t i=0; i < local.size(); ++i)
     497              :       {
     498            0 :          size_t bad = local[i].find_first_of("@:/", 0);
     499              : 
     500            0 :          if(bad != std::string::npos)
     501              :          {
     502            0 :             log<software_critical>({__FILE__, __LINE__, "Remote server's Local driver can't have host spec or path(@,:,/): " + local[i]});
     503              : 
     504            0 :             return XINDISERVER_E_BADDRIVERSPEC;
     505              :          }
     506              : 
     507            0 :          if( m_driverNames.count(local[i]) > 0)
     508              :          {
     509            0 :             log<software_critical>({__FILE__, __LINE__, "Duplicate driver name from remote server: " + local[i]});
     510            0 :             return XINDISERVER_E_DUPLICATEDRIVER;
     511              :          }
     512              : 
     513            0 :          m_driverNames.insert(local[i]);
     514              : 
     515            0 :          std::ostringstream oss;
     516              : 
     517            0 :          oss << local[i] << "@localhost:" << m_tunnels[tunnel].m_localPort;
     518              : 
     519              :          try
     520              :          {
     521            0 :             driverArgs.push_back(oss.str());
     522              :          }
     523            0 :          catch(...)
     524              :          {
     525            0 :             log<software_critical>({__FILE__, __LINE__, "Exception thrown by vector::push_back."});
     526            0 :             return XINDISERVER_E_VECTOREXCEPT;
     527            0 :          }
     528            0 :       }
     529              : 
     530            0 :    }
     531              : 
     532            0 :    return 0;
     533              : }
     534              : 
     535              : inline
     536            0 : int xindiserver::forkIndiserver()
     537              : {
     538              : 
     539            0 :    if(m_log.logLevel() >= logPrio::LOG_INFO)
     540              :    {
     541            0 :       std::string coml = "Starting indiserver with command: ";
     542            0 :       for(size_t i=0;i<m_indiserverCommand.size();++i)
     543              :       {
     544            0 :          coml += m_indiserverCommand[i];
     545            0 :          coml += " ";
     546              :       }
     547              : 
     548            0 :       log<text_log>(coml);
     549            0 :       std::cerr << coml << std::endl;
     550            0 :    }
     551              : 
     552              :    int filedes[2];
     553            0 :    if (pipe(filedes) == -1)
     554              :    {
     555            0 :       log<software_error>({__FILE__, __LINE__, errno});
     556            0 :       return -1;
     557              :    }
     558              : 
     559              : 
     560            0 :    m_isPID = fork();
     561              : 
     562            0 :    if(m_isPID < 0)
     563              :    {
     564            0 :       log<software_error>({__FILE__, __LINE__, errno, "fork failed"});
     565            0 :       return -1;
     566              :    }
     567              : 
     568              : 
     569            0 :    if(m_isPID == 0)
     570              :    {
     571              :       //Route STDERR of child to pipe input.
     572            0 :       while ((dup2(filedes[1], STDERR_FILENO) == -1) && (errno == EINTR)) {}
     573            0 :       close(filedes[1]);
     574            0 :       close(filedes[0]);
     575              : 
     576            0 :       const char ** drivers = new const char*[m_indiserverCommand.size()+1];
     577              : 
     578            0 :       for(size_t i=0; i< m_indiserverCommand.size(); ++i)
     579              :       {
     580            0 :          drivers[i] = (m_indiserverCommand[i].data());
     581              :       }
     582            0 :       drivers[m_indiserverCommand.size()] = NULL;
     583              : 
     584              : 
     585            0 :       execvp("indiserver", (char * const*) drivers);
     586              : 
     587            0 :       log<software_error>({__FILE__, __LINE__, errno, "execvp returned"});
     588              : 
     589            0 :       delete[] drivers;
     590              : 
     591            0 :       return -1;
     592              :    }
     593              : 
     594            0 :    m_isSTDERR = filedes[0];
     595            0 :    m_isSTDERR_input = filedes[1];
     596              : 
     597            0 :    if(m_log.logLevel() <= logPrio::LOG_INFO)
     598              :    {
     599            0 :       log<text_log>(std::format("indiserver started with PID {}", m_isPID));
     600              :    }
     601              : 
     602            0 :    return 0;
     603              : }
     604              : 
     605              : inline
     606            0 : void xindiserver::_isLogThreadStart( xindiserver * l)
     607              : {
     608            0 :    l->isLogThreadExec();
     609            0 : }
     610              : 
     611              : inline
     612            0 : int xindiserver::isLogThreadStart()
     613              : {
     614              :    try
     615              :    {
     616            0 :       m_isLogThread  = std::thread( _isLogThreadStart, this);
     617              :    }
     618            0 :    catch( const std::exception & e )
     619              :    {
     620            0 :       log<software_error>({__FILE__,__LINE__, std::string("Exception on I.S. log thread start: ") + e.what()});
     621            0 :       return -1;
     622            0 :    }
     623            0 :    catch( ... )
     624              :    {
     625            0 :       log<software_error>({__FILE__,__LINE__, "Unkown exception on I.S. log thread start"});
     626            0 :       return -1;
     627            0 :    }
     628              : 
     629            0 :    if(!m_isLogThread.joinable())
     630              :    {
     631            0 :       log<software_error>({__FILE__, __LINE__, "I.S. log thread did not start"});
     632            0 :       return -1;
     633              :    }
     634              : 
     635              :    sched_param sp;
     636            0 :    sp.sched_priority = m_isLogThreadPrio;
     637              : 
     638            0 :    int rv = pthread_setschedparam( m_isLogThread.native_handle(), SCHED_OTHER, &sp);
     639              : 
     640            0 :    if(rv != 0)
     641              :    {
     642            0 :       log<software_error>({__FILE__, __LINE__, rv, "Error setting thread params."});
     643            0 :       return -1;
     644              :    }
     645              : 
     646            0 :    return 0;
     647              : 
     648              : }
     649              : 
     650              : 
     651              : 
     652              : inline
     653            0 : void xindiserver::isLogThreadExec()
     654              : {
     655              :    char buffer[4096];
     656              : 
     657            0 :    std::string logs;
     658            0 :    while(m_shutdown == 0)
     659              :    {
     660            0 :       ssize_t count = read(m_isSTDERR, buffer, sizeof(buffer)-1); //Make wure we always have room for \0
     661            0 :       if (count <= 0 || m_shutdown == 1)
     662              :       {
     663            0 :          continue;
     664              :       }
     665            0 :       else if(count > (ssize_t) sizeof(buffer)-1)
     666              :       {
     667            0 :          log<software_error>({__FILE__, __LINE__, "read returned too many bytes."});
     668            0 :          continue;
     669              :       }
     670              :       else
     671              :       {
     672            0 :          buffer[count] = '\0';
     673              : 
     674            0 :          logs += buffer;
     675              : 
     676              :          //Keep reading until \n found, then process.
     677            0 :          if(logs.back() == '\n')
     678              :          {
     679            0 :             size_t bol = 0;
     680            0 :             while(bol < logs.size())
     681              :             {
     682            0 :                size_t eol = logs.find('\n', bol);
     683            0 :                if(eol == std::string::npos) break;
     684              : 
     685            0 :                processISLog(logs.substr(bol, eol-bol));
     686            0 :                bol = eol + 1;
     687              :             }
     688            0 :             logs = "";
     689              :          }
     690              :       }
     691              :    }
     692              : 
     693            0 : }
     694              : 
     695              : inline
     696            0 : int xindiserver::processISLog( std::string logs )
     697              : {
     698            0 :    size_t st = 0;
     699              :    size_t ed;
     700              : 
     701            0 :    ed = logs.find(':', st);
     702            0 :    if(ed != std::string::npos) ed = logs.find(':', ed+1);
     703            0 :    if(ed != std::string::npos) ed = logs.find(':', ed+1);
     704              : 
     705            0 :    if(ed == std::string::npos)
     706              :    {
     707              :       //log<software_error>({__FILE__, __LINE__, "Did not find timestamp : in log entry"});
     708            0 :       log<text_log>(logs, logPrio::LOG_INFO);
     709            0 :       return 0;
     710              :    }
     711              : 
     712            0 :    std::string ts = logs.substr(st, ed-st);
     713              : 
     714              :    double dsec;
     715              : 
     716              :    tm bdt;
     717            0 :    mx::sys::ISO8601dateBreakdown(bdt.tm_year, bdt.tm_mon, bdt.tm_mday, bdt.tm_hour, bdt.tm_min, dsec, ts);
     718              : 
     719            0 :    bdt.tm_year -= 1900;
     720            0 :    bdt.tm_mon -= 1;
     721            0 :    bdt.tm_sec = (int) dsec;
     722            0 :    bdt.tm_isdst = 0;
     723            0 :    bdt.tm_gmtoff = 0;
     724              : 
     725            0 :    timespecX tsp;
     726              : 
     727            0 :    tsp.time_s = timegm(&bdt);
     728            0 :    tsp.time_ns = (nanosecT) ((dsec-bdt.tm_sec)*1e9 + 0.5);
     729              : 
     730            0 :    ++ed;
     731            0 :    st = logs.find_first_not_of(" ", ed);
     732              : 
     733            0 :    if(st == std::string::npos) st = ed;
     734            0 :    if(st == logs.size())
     735              :    {
     736            0 :       log<software_error>({__FILE__, __LINE__, "Did not find log entry."});
     737            0 :       return -1;
     738              :    }
     739              : 
     740            0 :    std::string logstr = logs.substr(st, logs.size()-st);
     741              : 
     742            0 :    logPrioT prio = logPrio::LOG_INFO;
     743              : 
     744              :    //Look for fatal errors
     745            0 :    if(logstr.find("xindidriver") != std::string::npos) //Errors from xindidriver
     746              :    {
     747            0 :       if(logstr.find("failed to lock") != std::string::npos)
     748              :       {
     749            0 :          prio = logPrio::LOG_CRITICAL;
     750              :       }
     751              :    }
     752            0 :    else if(logstr.find("bind: Address already in use") != std::string::npos) //Errors from indiserver
     753              :    {
     754            0 :       prio = logPrio::LOG_CRITICAL;
     755              :    }
     756              : 
     757            0 :    m_log.log<text_log>(tsp, "IS: " + logstr, prio);
     758              : 
     759            0 :    if(prio == logPrio::LOG_CRITICAL)
     760              :    {
     761            0 :       state(stateCodes::FAILURE);
     762            0 :       m_shutdown = true;
     763              :    }
     764              : 
     765            0 :    return 0;
     766            0 : }
     767              : 
     768              : inline
     769            0 : int xindiserver::appStartup()
     770              : {
     771              : 
     772              :    //--------------------
     773              :    //Make symlinks
     774              :    //--------------------
     775            0 :    std::string path1 = "/opt/MagAOX/bin/xindidriver";
     776            0 :    for(size_t i=0; i<m_local.size(); ++i)
     777              :    {
     778            0 :       elevatedPrivileges elPriv(this);
     779              : 
     780            0 :       std::cerr << "creating symlink " << path1 << " " << m_driverPath + m_local[i] << "\n";
     781              : 
     782            0 :       int rv = symlink(path1.c_str(), (m_driverPath + m_local[i]).c_str());
     783              : 
     784            0 :       if(rv < 0 && errno != EEXIST)
     785              :       {
     786            0 :          log<software_error>({__FILE__, __LINE__, errno});
     787            0 :          log<software_error>({__FILE__, __LINE__, "Failed to create symlink for driver: " + m_local[i] + ". Continuing."});
     788              :       }
     789            0 :    }
     790              : 
     791            0 :    m_local.clear();
     792            0 :    m_remote.clear();
     793            0 :    m_tunnels.clear();
     794              : 
     795              :    //--------------------
     796              :    //Now start indiserver
     797              :    //--------------------
     798            0 :    if(forkIndiserver() < 0)
     799              :    {
     800            0 :       log<software_critical>({__FILE__, __LINE__});
     801            0 :       return -1;
     802              :    }
     803              : 
     804            0 :    if(isLogThreadStart() < 0)
     805              :    {
     806            0 :       log<software_critical>({__FILE__, __LINE__});
     807            0 :       return -1;
     808              :    }
     809              : 
     810            0 :    return 0;
     811            0 : }
     812              : 
     813              : inline
     814            0 : int xindiserver::appLogic()
     815              : {
     816              :    int status;
     817            0 :    pid_t result = waitpid(m_isPID, &status, WNOHANG);
     818            0 :    if (result == 0)
     819              :    {
     820            0 :       state(stateCodes::CONNECTED);
     821              :    }
     822              :    else
     823              :    {
     824              :       //We don't care why.  If indiserver is not alive while in this function then it's a fatal error.
     825            0 :       log<text_log>("indiserver has exited", logPrio::LOG_CRITICAL);
     826            0 :       state(stateCodes::FAILURE);
     827            0 :       return -1;
     828              :    }
     829              : 
     830              : 
     831              : 
     832            0 :    return 0;
     833              : }
     834              : 
     835              : inline
     836            0 : int xindiserver::appShutdown()
     837              : {
     838              : 
     839            0 :    if(m_isPID > 0)
     840              :    {
     841            0 :       kill(m_isPID, SIGTERM);
     842              :    }
     843              : 
     844            0 :    if(m_isSTDERR_input >= 0)
     845              :    {
     846            0 :       char w = '\0';
     847            0 :       ssize_t nwr = write(m_isSTDERR_input, &w, 1);
     848            0 :       if(nwr != 1)
     849              :       {
     850            0 :          log<software_error>({__FILE__, __LINE__, errno });
     851            0 :          log<software_error>({__FILE__, __LINE__, "Error on write to i.s. log thread. Sending SIGTERM."});
     852            0 :          pthread_kill(m_isLogThread.native_handle(), SIGTERM);
     853              : 
     854              :       }
     855              :    }
     856              : 
     857            0 :    if(m_isLogThread.joinable()) m_isLogThread.join();
     858            0 :    return 0;
     859              : }
     860              : 
     861              : 
     862              : } //namespace app
     863              : } //namespace MagAOX
     864              : 
     865              : #endif //xindiserver_hpp
     866              : 
     867              : 
     868              : 
     869              : 
     870              : 
     871              : 
     872              : 
     873              : 
     874              : 
     875              : 
     876              : 
     877              : 
     878              : 
     879              : 
     880              : 
     881              : 
     882              : 
     883              : 
     884              : 
     885              : 
     886              : 
     887              : 
     888              : 
     889              : 
     890              : 
     891              : 
     892              : 
        

Generated by: LCOV version 2.0-1