API
 
Loading...
Searching...
No Matches
sshDigger.hpp
Go to the documentation of this file.
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
39namespace MagAOX
40{
41namespace 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 */
54class 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.
108 {
109 }
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.
153
154 /// Execute the log capture.
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
185sshDigger::sshDigger() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
186{
187 // Use the sshTunnels.conf config file
188 m_configBase = "sshTunnels";
189
190 // set mx::app::application flag to not report lack of config file for this app.
192
193 return;
194}
195
197{
198}
199
200int sshDigger::loadConfigImpl( mx::app::appConfigurator &_config )
201{
202
203 std::vector<std::string> sections;
204
205 _config.unusedSections( sections );
206
207 if( sections.size() == 0 )
208 {
209 log<text_log>( "No tunnels found in config.", logPrio::LOG_CRITICAL );
210
212 }
213
214 // Now see if any sections match our m_configName
215
216 bool found = false;
217 for( size_t i = 0; i < sections.size(); ++i )
218 {
219 if( sections[i] == m_configName )
220 {
221 found = true;
222 }
223
224 // Here we go through and access each unused config just to avoid the critical error for unrecognized configs.
225 if( sections[i] == "" )
226 continue; // this is an error
227
228 std::string rh;
229 _config.configUnused( rh, mx::app::iniFile::makeKey( sections[i], "remoteHost" ) );
230 if( rh == "" )
231 {
232 log<text_log>( "Config section [" + sections[i] +
233 "] may be an invalid tunnel configuration (no remoteHost).",
235 }
236 int lp = 0;
237 _config.configUnused( lp, mx::app::iniFile::makeKey( sections[i], "localPort" ) );
238 if( lp == 0 )
239 {
240 log<text_log>( "Config section [" + sections[i] +
241 "] may be an invalid tunnel configuration (no localPort).",
243 }
244 int rp = 0;
245 _config.configUnused( rp, mx::app::iniFile::makeKey( sections[i], "remotePort" ) );
246 if( rp == 0 )
247 {
248 log<text_log>( "Config section [" + sections[i] +
249 "] may be an invalid tunnel configuration (no remotePort).",
251 }
252 int mp = 0;
253 _config.configUnused( mp, mx::app::iniFile::makeKey( sections[i], "monitorPort" ) );
254 bool cmp = false;
255 _config.configUnused( cmp, mx::app::iniFile::makeKey( sections[i], "compress" ) );
256 }
257
258 if( found == false )
259 {
260 log<text_log>( "No matching tunnel configuration found.", logPrio::LOG_CRITICAL );
262 }
263
264 // Now we configure the tunnel.
265
266 _config.configUnused( m_remoteHost, mx::app::iniFile::makeKey( m_configName, "remoteHost" ) );
267 if( m_remoteHost == "" )
268 {
269 log<text_log>( "No remote host specified.", logPrio::LOG_CRITICAL );
271 }
272
273 _config.configUnused( m_localPort, mx::app::iniFile::makeKey( m_configName, "localPort" ) );
274 if( m_localPort == 0 )
275 {
276 log<text_log>( "No local port specified.", logPrio::LOG_CRITICAL );
277
279 }
280
281 _config.configUnused( m_remotePort, mx::app::iniFile::makeKey( m_configName, "remotePort" ) );
282 if( m_remotePort == 0 )
283 {
284 log<text_log>( "No remote port specified.", logPrio::LOG_CRITICAL );
285
287 }
288
289 _config.configUnused( m_monitorPort, mx::app::iniFile::makeKey( m_configName, "monitorPort" ) );
290
291 _config.configUnused( m_compress, mx::app::iniFile::makeKey( m_configName, "compress" ) );
292
293 return 0;
294}
295
297{
298 if( loadConfigImpl( config ) < 0 )
299 {
300 m_shutdown = true;
301
302 log<text_log>( "configuration error(s) have occurred", logPrio::LOG_CRITICAL );
303 };
304}
305
307{
308 return std::format( "{}:localhost:{}", m_localPort, m_remotePort );
309}
310
311void sshDigger::genArgsV( std::vector<std::string> &argsV )
312{
313 std::string comp = "";
314 if( m_compress )
315 {
316 comp = "-C";
317 }
318
319 argsV = { "autossh", "-M" + std::to_string( m_monitorPort ), comp, "-nNTL", tunnelSpec(), m_remoteHost };
320}
321
322void sshDigger::genEnvp( std::vector<std::string> &envp )
323{
324 std::string logenv = "AUTOSSH_LOGFILE=" + m_autosshLogFile;
325
326 envp = { logenv };
327}
328
330{
331 std::vector<std::string> argsV;
332 genArgsV( argsV );
333
334 std::vector<std::string> envps;
335 genEnvp( envps );
336
338 {
339 std::string coml = "Starting autossh with command: ";
340 for( size_t i = 0; i < argsV.size(); ++i )
341 {
342 coml += argsV[i];
343 coml += " ";
344 }
346 }
347
348 int filedes[2];
349 if( pipe( filedes ) == -1 )
350 {
351 return log<software_error, -1>( { errno, "pipe failed" } );
352 }
353
354 m_tunnelPID = fork();
355
356 if( m_tunnelPID < 0 )
357 {
358 return log<software_error, -1>( { errno, "fork failed" } );
359 }
360
361 if( m_tunnelPID == 0 )
362 {
363 // Route STDERR of child to pipe input.
364 while( ( dup2( filedes[1], STDERR_FILENO ) == -1 ) && ( errno == EINTR ) )
365 {
366 }
367 close( filedes[1] );
368 close( filedes[0] );
369
370 const char **args = new const char *[argsV.size() + 1];
371 for( size_t i = 0; i < argsV.size(); ++i )
372 args[i] = argsV[i].data();
373 args[argsV.size()] = NULL;
374
375 const char **envp = new const char *[envps.size() + 1];
376 for( size_t i = 0; i < envps.size(); ++i )
377 envp[i] = envps[i].data();
378 envp[envps.size()] = NULL;
379
380 execvpe( "autossh", (char *const *)args, (char *const *)envp );
381
382 std::cerr << "returned\n";
383 log<software_error>( { errno, std::string( "execvp returned: " ) + strerror( errno ) } );
384
385 delete[] args;
386
387 return -1;
388 }
389
390 m_sshSTDERR = filedes[0];
392
394 {
395 log<text_log>( std::format( "autossh tunnel started with PID {}", m_tunnelPID ) );
396 }
397 return 0;
398}
399
401{
402 s->sshLogThreadExec();
403}
404
406{
407 try
408 {
409 m_sshLogThread = std::thread( _sshLogThreadStart, this );
410 }
411 catch( const std::exception &e )
412 {
413 return log<software_error, -1>( { std::string( "Exception on ssh log thread start: " ) + e.what() } );
414 }
415 catch( ... )
416 {
417 return log<software_error, -1>( { "Unkown exception on ssh log thread start" } );
418 }
419
420 if( !m_sshLogThread.joinable() )
421 {
422 return log<software_error, -1>( { "ssh log thread did not start" } );
423 }
424
426 sp.sched_priority = m_sshLogThreadPrio;
427
428 int rv = pthread_setschedparam( m_sshLogThread.native_handle(), SCHED_OTHER, &sp );
429
430 if( rv != 0 )
431 {
432 return log<software_error, -1>( { rv, "Error setting thread params." } );
433 }
434
435 return 0;
436}
437
439{
440 char buffer[4096];
441
442 std::string logs;
443 while( m_shutdown == 0 && m_sshSTDERR > 0 )
444 {
445 ssize_t count = read( m_sshSTDERR, buffer, sizeof( buffer ) - 1 );
446 if( count <= 0 )
447 {
448 continue;
449 }
450 else
451 {
452 buffer[count] = '\0';
453
454 logs += buffer;
455
456 // Keep reading until \n found, then process.
457 if( logs.back() == '\n' )
458 {
459 size_t bol = 0;
460 while( bol < logs.size() )
461 {
462 size_t eol = logs.find( '\n', bol );
463 if( eol == std::string::npos )
464 {
465 break;
466 }
467
468 processSSHLog( logs.substr( bol, eol - bol ) );
469 bol = eol + 1;
470 }
471 logs = "";
472 }
473 }
474 }
475}
476
477inline int sshDigger::processSSHLog( const std::string &logs )
478{
480
481 if( logs.find( "bind: Address already in use" ) != std::string::npos )
482 {
484 m_sshError = 1; // Means we'll need a restart
485 }
486
487 if( logs.find( "channel_setup_fwd_listener_tcpip: cannot listen to port:" ) != std::string::npos )
488 {
490 m_sshError = 1; // Means we'll need a restart
491 }
492
493 if( logs.find( "connect failed:" ) != std::string::npos )
494 {
496 m_sshError = 2;
497 }
498
499 if( logs != m_lastSSHLogs )
500 {
501 m_log.log<text_log>( { "SSH: " + logs }, lp );
503 }
504
505 return 0;
506}
507
509{
510 s->autosshLogThreadExec();
511}
512
514{
515 try
516 {
517 m_autosshLogThread = std::thread( _autosshLogThreadStart, this );
518 }
519
520 catch( const std::exception &e )
521 {
522 return log<software_error, -1>( { std::string( "Exception on autossh log thread start: " ) + e.what() } );
523 }
524 catch( ... )
525 {
526 return log<software_error, -1>( { "Unkown exception on autossh log thread start" } );
527 }
528
529 if( !m_autosshLogThread.joinable() )
530 {
531 return log<software_error, -1>( { "autossh log thread did not start" } );
532 }
533
535 sp.sched_priority = m_autosshLogThreadPrio;
536
537 int rv = pthread_setschedparam( m_autosshLogThread.native_handle(), SCHED_OTHER, &sp );
538
539 if( rv != 0 )
540 {
541 return log<software_error, -1>( { rv, "Error setting thread params." } );
542 }
543
544 return 0;
545}
546
548{
549 char buffer[4096];
550
551 // Open the FIFO
552 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 while( m_autosshLogFD <= 0 )
556 {
557 m_autosshLogFD = open( m_autosshLogFile.c_str(), O_RDONLY );
558
559 if( m_autosshLogFD < 0 )
560 {
561 log<software_error>( { errno, "unable to open auto ssh log fifo" } );
562 mx::sys::sleep( 1 );
563 }
564 }
565
566 std::string logs;
567 while( m_shutdown == 0 )
568 {
569 ssize_t count = read( m_autosshLogFD, buffer, sizeof( buffer ) - 1 );
570 if( count <= 0 )
571 {
572 continue;
573 }
574 else
575 {
576 buffer[count] = '\0';
577
578 logs += buffer;
579
580 // Keep reading until \n found, then process.
581 if( logs.back() == '\n' )
582 {
583 size_t bol = 0;
584 while( bol < logs.size() )
585 {
586 size_t eol = logs.find( '\n', bol );
587 if( eol == std::string::npos )
588 {
589 break;
590 }
591
592 processAutoSSHLog( logs.substr( bol, eol - bol ) );
593 bol = eol + 1;
594 }
595 logs = "";
596 }
597 }
598 }
599}
600
601inline int sshDigger::processAutoSSHLog( const std::string &logs )
602{
603 ///\todo interpret logs, giving errors vs info vs debug, strip timestamps, etc.
604
605 m_log.log<text_log>( { "AUTOSSH: " + logs } );
606
607 return 0;
608}
609
611{
612 m_autosshLogFile = "/dev/shm/sshDigger_autossh_" + m_configName + "_" + std::to_string( m_pid );
613
614 if( mkfifo( m_autosshLogFile.c_str(), S_IRUSR | S_IWUSR ) < 0 )
615 {
616 return log<software_critical, -1>( { errno, "unable to create autossh log fifo" } );
617 }
618
619 if( execTunnel() < 0 )
620 {
621 return log<software_critical, -1>( std::source_location::current() );
622 }
623
624 if( autosshLogThreadStart() < 0 )
625 {
626 return log<software_critical, -1>( std::source_location::current() );
627 }
628
629 if( sshLogThreadStart() < 0 )
630 {
631 return log<software_critical, -1>( std::source_location::current() );
632 }
633
634 return 0;
635}
636
638{
639 if( !m_sshError )
640 {
641 m_lastSSHLogs = "";
643 }
644
645 if( m_sshError == 1 )
646 {
647 log<text_log>( "sending SIGUSR1 to restart ssh" );
650 m_sshError = 0;
651 }
652
653 if( m_sshError == 2 )
654 {
656 m_sshError = 0;
657 }
658
659 // Check if autossh has died for any reason
660 int status;
661 pid_t exited = waitpid( m_tunnelPID, &status, WNOHANG );
662
663 if( exited == m_tunnelPID )
664 {
665 log<text_log>( "autossh exited, restarting." );
666 m_sshSTDERR = -1; // This tells the sshLogThread to exit
667 char w = '\0';
668 ssize_t nwr = write( m_sshSTDERR_input, &w, 1 ); // And this wakes it up from the blocking read
669
670 if( m_sshLogThread.joinable() )
671 {
672 m_sshLogThread.join();
673 }
674
675 if( nwr != 1 )
676 {
677 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 if( execTunnel() < 0 )
682 {
683 return log<software_critical, -1>( { "restart of tunnel failed." } );
684 }
685
686 // And the ssh log thread.
687 if( sshLogThreadStart() < 0 )
688 {
689 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 return 0;
696}
697
699{
701 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 char w = '\0';
705 ssize_t nwr = write( m_sshSTDERR_input, &w, 1 );
706 if( nwr != 1 )
707 {
708 log<software_error>( { errno, "Error on write to ssh log thread. Sending SIGTERM." } );
709 pthread_kill( m_sshLogThread.native_handle(), SIGTERM );
710 }
711
712 if( m_sshLogThread.joinable() )
713 {
714 m_sshLogThread.join();
715 }
716
717 // Close the autossh FD to get that thread to shutdown.
718 close( m_autosshLogFD );
719 if( m_autosshLogThread.joinable() )
720 {
721 m_autosshLogThread.join();
722 }
723
724 return 0;
725}
726
727} // namespace app
728} // namespace MagAOX
729
730#endif // sshDigger_hpp
The base-class for XWCTk applications.
std::string m_configName
The name of the configuration file (minus .conf).
stateCodes::stateCodeT state()
Get the current state code.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
pid_t m_pid
This process's PID.
std::string m_configBase
The name of a base config class for this app (minus .conf).
The MagAO-X SSH tunnel manager.
Definition sshDigger.hpp:55
~sshDigger() noexcept
D'tor, declared and defined for noexcept.
std::thread m_sshLogThread
A separate thread for capturing ssh logs.
Definition sshDigger.hpp:82
int m_autosshLogThreadPrio
Priority of the autossh log capture thread, should normally be 0.
Definition sshDigger.hpp:96
int sshLogThreadStart()
Start the log capture.
virtual int appLogic()
Implementation of the FSM for sshDigger.
int m_sshSTDERR_input
The input end of stderr, used to wake up the log thread on shutdown.
Definition sshDigger.hpp:78
int execTunnel()
Creates the tunnel in a child process using exec.
std::string m_remoteHost
The name of the remote host.
Definition sshDigger.hpp:64
static void _sshLogThreadStart(sshDigger *s)
Thread starter, called by sshLogThreadStart on thread construction. Calls sshLogThreadExec.
int m_localPort
The local port to forward from.
Definition sshDigger.hpp:65
int m_tunnelPID
The PID of the autossh process.
Definition sshDigger.hpp:71
void autosshLogThreadExec()
Execute the log capture.
int processSSHLog(const std::string &logs)
Process a log entry from indiserver, putting it into MagAO-X standard form.
static void _autosshLogThreadStart(sshDigger *s)
Thread starter, called by sshLogThreadStart on thread construction. Calls sshLogThreadExec.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
int m_autosshLogFD
File descriptor of the autossh logfile.
Definition sshDigger.hpp:94
int m_sshLogThreadPrio
Priority of the ssh log capture thread, should normally be 0.
Definition sshDigger.hpp:80
std::string m_lastSSHLogs
Used to prevent re-logging.
Definition sshDigger.hpp:84
int m_remotePort
The remote port to forward to.
Definition sshDigger.hpp:66
int m_sshSTDERR
The output of stderr of the ssh process.
Definition sshDigger.hpp:76
int m_monitorPort
The monitor port.
Definition sshDigger.hpp:67
void sshLogThreadExec()
Execute the log capture.
int autosshLogThreadStart()
Start the log capture.
virtual int appShutdown()
Shutdown the app.
virtual int appStartup()
Startup function.
sshDigger()
Default c'tor.
virtual void setupConfig()
int processAutoSSHLog(const std::string &logs)
Process a log entry from indiserver, putting it into MagAO-X standard form.
int m_sshError
Flag to signal when ssh logs an error, and should be restarted via SIGUSR1 to autossh.
Definition sshDigger.hpp:86
void genEnvp(std::vector< std::string > &envp)
Generate the envp vector for the exec of autossh.
std::string tunnelSpec()
Create the tunnel specification string, [localPort]:localhost:[remotePort].
void genArgsV(std::vector< std::string > &argsV)
Generate the argv vector for the exec of autossh.
std::string m_autosshLogFile
Name of the autossh logfile.
Definition sshDigger.hpp:93
virtual void loadConfig()
std::thread m_autosshLogThread
A separate thread for capturing autossh logs.
Definition sshDigger.hpp:98
bool m_compress
Control compression on this tunnel. True is on, false is off.
Definition sshDigger.hpp:68
int8_t logPrioT
The type of the log priority code.
Definition logDefs.hpp:21
@ CONNECTED
The application has connected to the device or service.
@ NOTCONNECTED
The application is not connected to the device or service.
Definition dm.hpp:19
static constexpr logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
static constexpr logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
static constexpr logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
static constexpr logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
#define SSHDIGGER_E_NOLOCALPORT
Definition sshDigger.hpp:36
#define SSHDIGGER_E_NOTUNNELFOUND
Definition sshDigger.hpp:34
#define SSHDIGGER_E_NOREMOTEPORT
Definition sshDigger.hpp:37
#define SSHDIGGER_E_NOHOSTNAME
Definition sshDigger.hpp:35
#define SSHDIGGER_E_NOTUNNELS
Definition sshDigger.hpp:33
void log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry, including a message.
int logLevel(logPrioT newLev)
Set a new value of logLevel.
Software CRITICAL log entry.
Software ERR log entry.
A simple text log, a string-type log.
Definition text_log.hpp:24