Line data Source code
1 : /** \file sshDigger.hpp
2 : * \brief The MagAO-X SSH tunnel manager
3 : *
4 : * \ingroup sshDigger_files
5 : */
6 :
7 : #ifndef sshDigger_hpp
8 : #define sshDigger_hpp
9 :
10 : #include <sys/wait.h>
11 :
12 : #include <iostream>
13 :
14 : #include <mx/sys/timeUtils.hpp>
15 :
16 : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
17 : #include "../../magaox_git_version.h"
18 :
19 : /** \defgroup sshDigger SSH Tunnel Manager
20 : * \brief Manage the SSH tunnels for MagAO-X communications..
21 : *
22 : * <a href="../handbook/operating/software/apps/sshDigger.html">Application Documentation</a>
23 : *
24 : * \ingroup apps
25 : *
26 : */
27 :
28 : /** \defgroup sshDigger_files SSH Tunnel Files
29 : * \ingroup sshDigger
30 : */
31 :
32 : // Return codes, these are primarily for testing purposes
33 : #define SSHDIGGER_E_NOTUNNELS ( -10 )
34 : #define SSHDIGGER_E_NOTUNNELFOUND ( -11 )
35 : #define SSHDIGGER_E_NOHOSTNAME ( -12 )
36 : #define SSHDIGGER_E_NOLOCALPORT ( -13 )
37 : #define SSHDIGGER_E_NOREMOTEPORT ( -14 )
38 :
39 : namespace MagAOX
40 : {
41 : namespace app
42 : {
43 :
44 : /// The MagAO-X SSH tunnel manager
45 : /** Each instance of this app manages one SSH tunnel to another computer.
46 : * These tunnels are opened via the `autossh` app, which itself calls `ssh`.
47 : *
48 : *
49 : * \todo add options for verboseness of ssh and autossh (loglevel)
50 : * \todo add options for ssh and autossh log thread priorities
51 : *
52 : * \ingroup sshDigger
53 : */
54 : class sshDigger : public MagAOXApp<false>
55 : {
56 :
57 : // Give the test harness access.
58 : friend class sshDigger_test;
59 :
60 : protected:
61 : /** \name Configurable Parameters
62 : *@{
63 : */
64 : std::string m_remoteHost; ///< The name of the remote host
65 : int m_localPort{ 0 }; ///< The local port to forward from
66 : int m_remotePort{ 0 }; ///< The remote port to forward to
67 : int m_monitorPort{ 0 }; ///< The monitor port
68 : bool m_compress{ false }; ///< Control compression on this tunnel. True is on, false is off.
69 : ///@}
70 :
71 : int m_tunnelPID{ 0 }; ///< The PID of the autossh process
72 :
73 : /** \name ssh log capture
74 : * @{
75 : */
76 : int m_sshSTDERR{ -1 }; ///< The output of stderr of the ssh process
77 :
78 : int m_sshSTDERR_input{ -1 }; ///< The input end of stderr, used to wake up the log thread on shutdown.
79 :
80 : int m_sshLogThreadPrio{ 0 }; ///< Priority of the ssh log capture thread, should normally be 0.
81 :
82 : std::thread m_sshLogThread; ///< A separate thread for capturing ssh logs
83 :
84 : std::string m_lastSSHLogs; ///< Used to prevent re-logging
85 :
86 : int m_sshError{ 0 }; ///< Flag to signal when ssh logs an error, and should be restarted via SIGUSR1 to autossh.
87 :
88 : ///@}
89 :
90 : /** \name autossh log capture
91 : * @{
92 : */
93 : std::string m_autosshLogFile; ///< Name of the autossh logfile.
94 : int m_autosshLogFD{ 0 }; ///< File descriptor of the autossh logfile.
95 :
96 : int m_autosshLogThreadPrio{ 0 }; ///< Priority of the autossh log capture thread, should normally be 0.
97 :
98 : std::thread m_autosshLogThread; ///< A separate thread for capturing autossh logs
99 :
100 : ///@}
101 :
102 : public:
103 : /// Default c'tor.
104 : sshDigger();
105 :
106 : /// D'tor, declared and defined for noexcept.
107 17 : ~sshDigger() noexcept
108 17 : {
109 17 : }
110 :
111 : virtual void setupConfig();
112 :
113 : /// Implementation of loadConfig logic, separated for testing.
114 : /** This is called by loadConfig().
115 : */
116 : int loadConfigImpl(
117 : mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/ );
118 :
119 : virtual void loadConfig();
120 :
121 : /// Create the tunnel specification string, [localPort]:localhost:[remotePort].
122 : /**
123 : * \returns a string containing the tunnel specification.
124 : */
125 : std::string tunnelSpec();
126 :
127 : /// Generate the argv vector for the exec of autossh.
128 : void genArgsV( std::vector<std::string> &argsV /**< [out] will contain the argv vector for an autssh exec call */ );
129 :
130 : /// Generate the envp vector for the exec of autossh.
131 : void genEnvp( std::vector<std::string> &envp /**< [out] will contain the envp vector for an autssh exec call */ );
132 :
133 : /// Creates the tunnel in a child process using exec.
134 : int execTunnel();
135 :
136 : /// Thread starter, called by sshLogThreadStart on thread construction. Calls sshLogThreadExec.
137 : static void _sshLogThreadStart( sshDigger *s /**< [in] a pointer to an sshDigger instance (normally this) */ );
138 :
139 : /// Start the log capture.
140 : int sshLogThreadStart();
141 :
142 : /// Execute the log capture.
143 : void sshLogThreadExec();
144 :
145 : /// Process a log entry from indiserver, putting it into MagAO-X standard form
146 : int processSSHLog( const std::string &logs );
147 :
148 : /// Thread starter, called by sshLogThreadStart on thread construction. Calls sshLogThreadExec.
149 : static void _autosshLogThreadStart( sshDigger *s /**< [in] a pointer to an sshDigger instance (normally this) */ );
150 :
151 : /// Start the log capture.
152 : int autosshLogThreadStart();
153 :
154 : /// Execute the log capture.
155 : void autosshLogThreadExec();
156 :
157 : /// Process a log entry from indiserver, putting it into MagAO-X standard form
158 : int processAutoSSHLog( const std::string &logs );
159 :
160 : /// Startup function
161 : /**
162 : *
163 : */
164 : virtual int appStartup();
165 :
166 : /// Implementation of the FSM for sshDigger.
167 : /** Monitors status of m_sshError flag, and sends a signal to autossh if an error is indicated.
168 : *
169 : * Checks that autossh is still alive, and if it has died restarts it.
170 : *
171 : * \returns 0 on no critical error
172 : * \returns -1 on an error requiring shutdown
173 : */
174 : virtual int appLogic();
175 :
176 : /// Shutdown the app.
177 : /** Sends SIGTERM to autossh.
178 : *
179 : * Tells the two logging threads to exit, and waits for them to complete.
180 : *
181 : */
182 : virtual int appShutdown();
183 : };
184 :
185 51 : sshDigger::sshDigger() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
186 : {
187 : // Use the sshTunnels.conf config file
188 17 : m_configBase = "sshTunnels";
189 :
190 : // set mx::app::application flag to not report lack of config file for this app.
191 17 : m_requireConfigPathLocal = false;
192 :
193 17 : return;
194 0 : }
195 :
196 0 : void sshDigger::setupConfig()
197 : {
198 0 : }
199 :
200 24 : int sshDigger::loadConfigImpl( mx::app::appConfigurator &_config )
201 : {
202 :
203 24 : std::vector<std::string> sections;
204 :
205 24 : _config.unusedSections( sections );
206 :
207 24 : if( sections.size() == 0 )
208 : {
209 1 : log<text_log>( "No tunnels found in config.", logPrio::LOG_CRITICAL );
210 :
211 1 : return SSHDIGGER_E_NOTUNNELS;
212 : }
213 :
214 : // Now see if any sections match our m_configName
215 :
216 23 : bool found = false;
217 68 : for( size_t i = 0; i < sections.size(); ++i )
218 : {
219 45 : if( sections[i] == m_configName )
220 : {
221 21 : found = true;
222 : }
223 :
224 : // Here we go through and access each unused config just to avoid the critical error for unrecognized configs.
225 45 : if( sections[i] == "" )
226 7 : continue; // this is an error
227 :
228 38 : std::string rh;
229 38 : _config.configUnused( rh, mx::app::iniFile::makeKey( sections[i], "remoteHost" ) );
230 38 : if( rh == "" )
231 : {
232 5 : log<text_log>( "Config section [" + sections[i] +
233 : "] may be an invalid tunnel configuration (no remoteHost).",
234 : logPrio::LOG_WARNING );
235 : }
236 38 : int lp = 0;
237 38 : _config.configUnused( lp, mx::app::iniFile::makeKey( sections[i], "localPort" ) );
238 38 : if( lp == 0 )
239 : {
240 5 : log<text_log>( "Config section [" + sections[i] +
241 : "] may be an invalid tunnel configuration (no localPort).",
242 : logPrio::LOG_WARNING );
243 : }
244 38 : int rp = 0;
245 38 : _config.configUnused( rp, mx::app::iniFile::makeKey( sections[i], "remotePort" ) );
246 38 : if( rp == 0 )
247 : {
248 5 : log<text_log>( "Config section [" + sections[i] +
249 : "] may be an invalid tunnel configuration (no remotePort).",
250 : logPrio::LOG_WARNING );
251 : }
252 38 : int mp = 0;
253 38 : _config.configUnused( mp, mx::app::iniFile::makeKey( sections[i], "monitorPort" ) );
254 38 : bool cmp = false;
255 38 : _config.configUnused( cmp, mx::app::iniFile::makeKey( sections[i], "compress" ) );
256 38 : }
257 :
258 23 : if( found == false )
259 : {
260 2 : log<text_log>( "No matching tunnel configuration found.", logPrio::LOG_CRITICAL );
261 2 : return SSHDIGGER_E_NOTUNNELFOUND;
262 : }
263 :
264 : // Now we configure the tunnel.
265 :
266 21 : _config.configUnused( m_remoteHost, mx::app::iniFile::makeKey( m_configName, "remoteHost" ) );
267 21 : if( m_remoteHost == "" )
268 : {
269 3 : log<text_log>( "No remote host specified.", logPrio::LOG_CRITICAL );
270 3 : return SSHDIGGER_E_NOHOSTNAME;
271 : }
272 :
273 18 : _config.configUnused( m_localPort, mx::app::iniFile::makeKey( m_configName, "localPort" ) );
274 18 : if( m_localPort == 0 )
275 : {
276 3 : log<text_log>( "No local port specified.", logPrio::LOG_CRITICAL );
277 :
278 3 : return SSHDIGGER_E_NOLOCALPORT;
279 : }
280 :
281 15 : _config.configUnused( m_remotePort, mx::app::iniFile::makeKey( m_configName, "remotePort" ) );
282 15 : if( m_remotePort == 0 )
283 : {
284 3 : log<text_log>( "No remote port specified.", logPrio::LOG_CRITICAL );
285 :
286 3 : return SSHDIGGER_E_NOREMOTEPORT;
287 : }
288 :
289 24 : _config.configUnused( m_monitorPort, mx::app::iniFile::makeKey( m_configName, "monitorPort" ) );
290 :
291 12 : _config.configUnused( m_compress, mx::app::iniFile::makeKey( m_configName, "compress" ) );
292 :
293 12 : return 0;
294 24 : }
295 :
296 0 : void sshDigger::loadConfig()
297 : {
298 0 : if( loadConfigImpl( config ) < 0 )
299 : {
300 0 : m_shutdown = true;
301 :
302 0 : log<text_log>( "configuration error(s) have occurred", logPrio::LOG_CRITICAL );
303 : };
304 0 : }
305 :
306 2 : std::string sshDigger::tunnelSpec()
307 : {
308 2 : return std::format( "{}:localhost:{}", m_localPort, m_remotePort );
309 : }
310 :
311 1 : void sshDigger::genArgsV( std::vector<std::string> &argsV )
312 : {
313 1 : std::string comp = "";
314 1 : if( m_compress )
315 : {
316 0 : comp = "-C";
317 : }
318 :
319 7 : argsV = { "autossh", "-M" + std::to_string( m_monitorPort ), comp, "-nNTL", tunnelSpec(), m_remoteHost };
320 3 : }
321 :
322 0 : void sshDigger::genEnvp( std::vector<std::string> &envp )
323 : {
324 0 : std::string logenv = "AUTOSSH_LOGFILE=" + m_autosshLogFile;
325 :
326 0 : envp = { logenv };
327 0 : }
328 :
329 0 : int sshDigger::execTunnel()
330 : {
331 0 : std::vector<std::string> argsV;
332 0 : genArgsV( argsV );
333 :
334 0 : std::vector<std::string> envps;
335 0 : genEnvp( envps );
336 :
337 0 : if( m_log.logLevel() <= logPrio::LOG_INFO )
338 : {
339 0 : std::string coml = "Starting autossh with command: ";
340 0 : for( size_t i = 0; i < argsV.size(); ++i )
341 : {
342 0 : coml += argsV[i];
343 0 : coml += " ";
344 : }
345 0 : log<text_log>( coml );
346 0 : }
347 :
348 : int filedes[2];
349 0 : if( pipe( filedes ) == -1 )
350 : {
351 0 : return log<software_error, -1>( { errno, "pipe failed" } );
352 : }
353 :
354 0 : m_tunnelPID = fork();
355 :
356 0 : if( m_tunnelPID < 0 )
357 : {
358 0 : return log<software_error, -1>( { errno, "fork failed" } );
359 : }
360 :
361 0 : if( m_tunnelPID == 0 )
362 : {
363 : // Route STDERR of child to pipe input.
364 0 : while( ( dup2( filedes[1], STDERR_FILENO ) == -1 ) && ( errno == EINTR ) )
365 : {
366 : }
367 0 : close( filedes[1] );
368 0 : close( filedes[0] );
369 :
370 0 : const char **args = new const char *[argsV.size() + 1];
371 0 : for( size_t i = 0; i < argsV.size(); ++i )
372 0 : args[i] = argsV[i].data();
373 0 : args[argsV.size()] = NULL;
374 :
375 0 : const char **envp = new const char *[envps.size() + 1];
376 0 : for( size_t i = 0; i < envps.size(); ++i )
377 0 : envp[i] = envps[i].data();
378 0 : envp[envps.size()] = NULL;
379 :
380 0 : execvpe( "autossh", (char *const *)args, (char *const *)envp );
381 :
382 0 : std::cerr << "returned\n";
383 0 : log<software_error>( { errno, std::string( "execvp returned: " ) + strerror( errno ) } );
384 :
385 0 : delete[] args;
386 :
387 0 : return -1;
388 : }
389 :
390 0 : m_sshSTDERR = filedes[0];
391 0 : m_sshSTDERR_input = filedes[1];
392 :
393 0 : if( m_log.logLevel() <= logPrio::LOG_INFO )
394 : {
395 0 : log<text_log>( std::format( "autossh tunnel started with PID {}", m_tunnelPID ) );
396 : }
397 0 : return 0;
398 0 : }
399 :
400 0 : inline void sshDigger::_sshLogThreadStart( sshDigger *s )
401 : {
402 0 : s->sshLogThreadExec();
403 0 : }
404 :
405 0 : inline int sshDigger::sshLogThreadStart()
406 : {
407 : try
408 : {
409 0 : m_sshLogThread = std::thread( _sshLogThreadStart, this );
410 : }
411 0 : catch( const std::exception &e )
412 : {
413 0 : return log<software_error, -1>( { std::string( "Exception on ssh log thread start: " ) + e.what() } );
414 0 : }
415 0 : catch( ... )
416 : {
417 0 : return log<software_error, -1>( { "Unkown exception on ssh log thread start" } );
418 0 : }
419 :
420 0 : if( !m_sshLogThread.joinable() )
421 : {
422 0 : return log<software_error, -1>( { "ssh log thread did not start" } );
423 : }
424 :
425 : sched_param sp;
426 0 : sp.sched_priority = m_sshLogThreadPrio;
427 :
428 0 : int rv = pthread_setschedparam( m_sshLogThread.native_handle(), SCHED_OTHER, &sp );
429 :
430 0 : if( rv != 0 )
431 : {
432 0 : return log<software_error, -1>( { rv, "Error setting thread params." } );
433 : }
434 :
435 0 : return 0;
436 : }
437 :
438 0 : inline void sshDigger::sshLogThreadExec()
439 : {
440 : char buffer[4096];
441 :
442 0 : std::string logs;
443 0 : while( m_shutdown == 0 && m_sshSTDERR > 0 )
444 : {
445 0 : ssize_t count = read( m_sshSTDERR, buffer, sizeof( buffer ) - 1 );
446 0 : if( count <= 0 )
447 : {
448 0 : continue;
449 : }
450 : else
451 : {
452 0 : buffer[count] = '\0';
453 :
454 0 : logs += buffer;
455 :
456 : // Keep reading until \n found, then process.
457 0 : if( logs.back() == '\n' )
458 : {
459 0 : size_t bol = 0;
460 0 : while( bol < logs.size() )
461 : {
462 0 : size_t eol = logs.find( '\n', bol );
463 0 : if( eol == std::string::npos )
464 : {
465 0 : break;
466 : }
467 :
468 0 : processSSHLog( logs.substr( bol, eol - bol ) );
469 0 : bol = eol + 1;
470 : }
471 0 : logs = "";
472 : }
473 : }
474 : }
475 0 : }
476 :
477 0 : inline int sshDigger::processSSHLog( const std::string &logs )
478 : {
479 0 : logPrioT lp = logPrio::LOG_INFO;
480 :
481 0 : if( logs.find( "bind: Address already in use" ) != std::string::npos )
482 : {
483 0 : lp = logPrio::LOG_ERROR;
484 0 : m_sshError = 1; // Means we'll need a restart
485 : }
486 :
487 0 : if( logs.find( "channel_setup_fwd_listener_tcpip: cannot listen to port:" ) != std::string::npos )
488 : {
489 0 : lp = logPrio::LOG_ERROR;
490 0 : m_sshError = 1; // Means we'll need a restart
491 : }
492 :
493 0 : if( logs.find( "connect failed:" ) != std::string::npos )
494 : {
495 0 : lp = logPrio::LOG_ERROR;
496 0 : m_sshError = 2;
497 : }
498 :
499 0 : if( logs != m_lastSSHLogs )
500 : {
501 0 : m_log.log<text_log>( { "SSH: " + logs }, lp );
502 0 : m_lastSSHLogs = logs;
503 : }
504 :
505 0 : return 0;
506 : }
507 :
508 0 : inline void sshDigger::_autosshLogThreadStart( sshDigger *s )
509 : {
510 0 : s->autosshLogThreadExec();
511 0 : }
512 :
513 0 : inline int sshDigger::autosshLogThreadStart()
514 : {
515 : try
516 : {
517 0 : m_autosshLogThread = std::thread( _autosshLogThreadStart, this );
518 : }
519 :
520 0 : catch( const std::exception &e )
521 : {
522 0 : return log<software_error, -1>( { std::string( "Exception on autossh log thread start: " ) + e.what() } );
523 0 : }
524 0 : catch( ... )
525 : {
526 0 : return log<software_error, -1>( { "Unkown exception on autossh log thread start" } );
527 0 : }
528 :
529 0 : if( !m_autosshLogThread.joinable() )
530 : {
531 0 : return log<software_error, -1>( { "autossh log thread did not start" } );
532 : }
533 :
534 : sched_param sp;
535 0 : sp.sched_priority = m_autosshLogThreadPrio;
536 :
537 0 : int rv = pthread_setschedparam( m_autosshLogThread.native_handle(), SCHED_OTHER, &sp );
538 :
539 0 : if( rv != 0 )
540 : {
541 0 : return log<software_error, -1>( { rv, "Error setting thread params." } );
542 : }
543 :
544 0 : return 0;
545 : }
546 :
547 0 : inline void sshDigger::autosshLogThreadExec()
548 : {
549 : char buffer[4096];
550 :
551 : // Open the FIFO
552 0 : m_autosshLogFD = 0;
553 :
554 : // We currently aren't monitoring this thread status, so we might as well retry if there is an error
555 0 : while( m_autosshLogFD <= 0 )
556 : {
557 0 : m_autosshLogFD = open( m_autosshLogFile.c_str(), O_RDONLY );
558 :
559 0 : if( m_autosshLogFD < 0 )
560 : {
561 0 : log<software_error>( { errno, "unable to open auto ssh log fifo" } );
562 0 : mx::sys::sleep( 1 );
563 : }
564 : }
565 :
566 0 : std::string logs;
567 0 : while( m_shutdown == 0 )
568 : {
569 0 : ssize_t count = read( m_autosshLogFD, buffer, sizeof( buffer ) - 1 );
570 0 : if( count <= 0 )
571 : {
572 0 : continue;
573 : }
574 : else
575 : {
576 0 : buffer[count] = '\0';
577 :
578 0 : logs += buffer;
579 :
580 : // Keep reading until \n found, then process.
581 0 : if( logs.back() == '\n' )
582 : {
583 0 : size_t bol = 0;
584 0 : while( bol < logs.size() )
585 : {
586 0 : size_t eol = logs.find( '\n', bol );
587 0 : if( eol == std::string::npos )
588 : {
589 0 : break;
590 : }
591 :
592 0 : processAutoSSHLog( logs.substr( bol, eol - bol ) );
593 0 : bol = eol + 1;
594 : }
595 0 : logs = "";
596 : }
597 : }
598 : }
599 0 : }
600 :
601 0 : inline int sshDigger::processAutoSSHLog( const std::string &logs )
602 : {
603 : ///\todo interpret logs, giving errors vs info vs debug, strip timestamps, etc.
604 :
605 0 : m_log.log<text_log>( { "AUTOSSH: " + logs } );
606 :
607 0 : return 0;
608 : }
609 :
610 0 : int sshDigger::appStartup()
611 : {
612 0 : m_autosshLogFile = "/dev/shm/sshDigger_autossh_" + m_configName + "_" + std::to_string( m_pid );
613 :
614 0 : if( mkfifo( m_autosshLogFile.c_str(), S_IRUSR | S_IWUSR ) < 0 )
615 : {
616 0 : return log<software_critical, -1>( { errno, "unable to create autossh log fifo" } );
617 : }
618 :
619 0 : if( execTunnel() < 0 )
620 : {
621 0 : return log<software_critical, -1>( std::source_location::current() );
622 : }
623 :
624 0 : if( autosshLogThreadStart() < 0 )
625 : {
626 0 : return log<software_critical, -1>( std::source_location::current() );
627 : }
628 :
629 0 : if( sshLogThreadStart() < 0 )
630 : {
631 0 : return log<software_critical, -1>( std::source_location::current() );
632 : }
633 :
634 0 : return 0;
635 : }
636 :
637 0 : int sshDigger::appLogic()
638 : {
639 0 : if( !m_sshError )
640 : {
641 0 : m_lastSSHLogs = "";
642 0 : state( stateCodes::CONNECTED );
643 : }
644 :
645 0 : if( m_sshError == 1 )
646 : {
647 0 : log<text_log>( "sending SIGUSR1 to restart ssh" );
648 0 : kill( m_tunnelPID, SIGUSR1 );
649 0 : state( stateCodes::NOTCONNECTED );
650 0 : m_sshError = 0;
651 : }
652 :
653 0 : if( m_sshError == 2 )
654 : {
655 0 : state( stateCodes::NOTCONNECTED );
656 0 : m_sshError = 0;
657 : }
658 :
659 : // Check if autossh has died for any reason
660 : int status;
661 0 : pid_t exited = waitpid( m_tunnelPID, &status, WNOHANG );
662 :
663 0 : if( exited == m_tunnelPID )
664 : {
665 0 : log<text_log>( "autossh exited, restarting." );
666 0 : m_sshSTDERR = -1; // This tells the sshLogThread to exit
667 0 : char w = '\0';
668 0 : ssize_t nwr = write( m_sshSTDERR_input, &w, 1 ); // And this wakes it up from the blocking read
669 :
670 0 : if( m_sshLogThread.joinable() )
671 : {
672 0 : m_sshLogThread.join();
673 : }
674 :
675 0 : if( nwr != 1 )
676 : {
677 0 : return log<software_error, -1>( { errno, "Error on write to ssh log thread. restart failed." } );
678 : }
679 :
680 : // And now we can restart autossh
681 0 : if( execTunnel() < 0 )
682 : {
683 0 : return log<software_critical, -1>( { "restart of tunnel failed." } );
684 : }
685 :
686 : // And the ssh log thread.
687 0 : if( sshLogThreadStart() < 0 )
688 : {
689 0 : return log<software_critical, -1>( { "restart of ssh log thread failed." } );
690 : }
691 :
692 : // Don't need to restart the autossh log thread, becuase we'll give it the same file as log file.
693 : }
694 :
695 0 : return 0;
696 : }
697 :
698 0 : int sshDigger::appShutdown()
699 : {
700 0 : kill( m_tunnelPID, SIGTERM );
701 0 : waitpid( m_tunnelPID, 0, 0 );
702 :
703 : // Write the the ssh stderr to wake up the ssh log thread, which will then see shutdown is set.
704 0 : char w = '\0';
705 0 : ssize_t nwr = write( m_sshSTDERR_input, &w, 1 );
706 0 : if( nwr != 1 )
707 : {
708 0 : log<software_error>( { errno, "Error on write to ssh log thread. Sending SIGTERM." } );
709 0 : pthread_kill( m_sshLogThread.native_handle(), SIGTERM );
710 : }
711 :
712 0 : if( m_sshLogThread.joinable() )
713 : {
714 0 : m_sshLogThread.join();
715 : }
716 :
717 : // Close the autossh FD to get that thread to shutdown.
718 0 : close( m_autosshLogFD );
719 0 : if( m_autosshLogThread.joinable() )
720 : {
721 0 : m_autosshLogThread.join();
722 : }
723 :
724 0 : return 0;
725 : }
726 :
727 : } // namespace app
728 : } // namespace MagAOX
729 :
730 : #endif // sshDigger_hpp
|