1 #include "WayToShell.h"
4 #include <MakePTYAndFork.h>
6 #include <termios.h> // Для работы с терминальными атрибутами
9 #include <Environment.h>
10 #include <MatchWildcard.hpp>
11 #include "../../Erroring.h"
13 // use 'export NETROCKS_VERBOSE=1' or '..=2' to see protocol dumps from DebugStr
14 static void DebugStr(const char *info
, const char *str
, size_t len
)
17 for (size_t i
= 0; i
<= len
; ++i
) {
20 } else if ((unsigned char)str
[i
] < 32) {
21 tmp
+= StrPrintf("{%02x}", (unsigned char)str
[i
]);
22 if (str
[i
] == '\r' && i
+ 1 < len
&& str
[i
+ 1] == '\n') {
28 if (!tmp
.empty() && (i
== len
|| str
[i
] == '\r' || str
[i
] == '\n')) {
29 std::cerr
<< info
<< ": " << tmp
<< std::endl
;
35 static void DebugStr(const char *info
, const std::string
&str
)
37 DebugStr(info
, str
.c_str(), str
.size());
40 static void ForkSafePrint(const char *str
)
42 if (write(STDOUT_FILENO
, str
, strlen(str
)) <= 0) {
47 WayToShell::WayToShell(int fd_ipc_recv
, const WayToShellConfig
&cfg
, const StringConfig
&protocol_options
)
48 : _fd_ipc_recv(fd_ipc_recv
)
50 if (!cfg
.command
.empty()) {
51 LaunchCommand(cfg
, protocol_options
);
52 } else if (!cfg
.serial
.empty()) {
53 OpenSerialPort(cfg
, protocol_options
);
55 throw ProtocolError("Command or port not specified");
59 // That long echo after exit makes things work smooter, dunno why.
60 // And it really needs to be rather long
61 static const char s_exit_cmd
[] = "\nexit\necho =================================\n";
63 WayToShell::~WayToShell()
66 while (FinalRead(100)) {
69 if (write(_master_fd
, s_exit_cmd
, sizeof(s_exit_cmd
) - 1) == sizeof(s_exit_cmd
) - 1) {
71 fprintf(stderr
, "~WayToShell: exit delivered\n");
73 perror("~WayToShell: write exit");
77 fprintf(stderr
, "~WayToShell: exit exception\n");
80 CheckedCloseFD(_master_fd
);
81 CheckedCloseFDPair(_stderr_pipe
);
82 if (_pid
!= (pid_t
)-1) {
83 // Проверяем, завершен ли дочерний процесс
85 if (waitpid(_pid
, &status
, WNOHANG
) == 0) {
86 // Если нет, отправляем сигнал для завершения
88 waitpid(_pid
, &status
, 0); // Ожидаем завершения
90 std::cerr
<< "Child exited with status " << status
<< '\n';
94 bool WayToShell::FinalRead(int tmout
)
99 const int r
= os_call_int(poll
, &pfd
, (nfds_t
)1, tmout
);
100 return (r
> 0 && RecvPolledFD(pfd
, STDOUT
));
103 void WayToShell::LaunchCommand(const WayToShellConfig
&cfg
, const StringConfig
&protocol_options
)
105 std::string command
= cfg
.command
;
106 for (unsigned i
= cfg
.options
.size(); i
--;) { // reversed order for proper replacement
107 Substitute(command
, StrPrintf("$OPT%u", i
).c_str(), cfg
.OptionValue(i
, protocol_options
));
109 if (g_netrocks_verbosity
> 0) {
110 fprintf(stderr
, "WayToShell::LaunchCommand: '%s'\n", command
.c_str());
113 if (pipe(_stderr_pipe
) < 0) {
114 throw ProtocolError("pipe error", errno
);
117 struct Argv
: std::vector
<char *>
121 for (auto &arg
: *this) {
127 Environment::ExplodeCommandLine
ecl(command
);
128 for (const auto &arg
: ecl
) {
129 argv
.emplace_back(strdup(arg
.c_str()));
131 argv
.emplace_back(nullptr);
133 _pid
= MakePTYAndFork(_master_fd
);
135 if (_pid
== (pid_t
)-1) {
136 throw ProtocolError("PTY error", errno
);
140 CheckedCloseFD(_stderr_pipe
[0]); // close read end
141 dup2(_stderr_pipe
[1], STDERR_FILENO
); // redirect stderr to the pipe
142 CheckedCloseFD(_stderr_pipe
[1]); // close write end, as it's now duplicated
144 setenv("LANG", "C", 1);
145 setenv("TERM", "xterm-mono", 1);
146 unsetenv("COLORFGBG");
148 // Получаем текущие атрибуты терминала
149 struct termios tios
{};
150 if (tcgetattr(STDIN_FILENO
, &tios
) == -1) {
151 ForkSafePrint("tcgetattr failed\n");
155 // disable echo, CR/LF translation and other nasty things
158 // Применяем новые атрибуты
159 if (tcsetattr(STDIN_FILENO
, TCSANOW
, &tios
) == -1) {
160 ForkSafePrint("tcsetattr failed\n");
163 // ssh should terminate if terminal closed
164 signal(SIGINT
, SIG_DFL
);
165 signal(SIGHUP
, SIG_DFL
);
166 signal(SIGPIPE
, SIG_DFL
);
168 execvp(*argv
.data(), argv
.data());
169 ForkSafePrint("execlp failed\n");
174 CheckedCloseFD(_stderr_pipe
[1]); // close write end
176 // Now you can read from _stderr_pipe[0] to get the stderr output
177 // and from _master_fd to get the stdout output
179 MakeFDNonBlocking(_master_fd
);
180 MakeFDNonBlocking(_stderr_pipe
[0]);
183 void WayToShell::OpenSerialPort(const WayToShellConfig
&cfg
, const StringConfig
&protocol_options
)
185 const auto &opt_baudrate
= cfg
.OptionValue(0, protocol_options
);
186 const auto &opt_flowctrl
= cfg
.OptionValue(1, protocol_options
);
187 const auto &opt_databits
= cfg
.OptionValue(2, protocol_options
);
188 const auto &opt_stopbits
= cfg
.OptionValue(3, protocol_options
);
191 switch (atoi(opt_baudrate
.c_str())) {
192 case 50: baudrate
= B50
; break;
193 case 150: baudrate
= B150
; break;
194 case 300: baudrate
= B300
; break;
195 case 600: baudrate
= B600
; break;
196 case 1200: baudrate
= B1200
; break;
197 case 2400: baudrate
= B2400
; break;
198 case 4800: baudrate
= B4800
; break;
199 case 9600: baudrate
= B9600
; break;
200 case 19200: baudrate
= B19200
; break;
201 case 38400: baudrate
= B38400
; break;
202 case 57600: baudrate
= B57600
; break;
203 case 115200: baudrate
= B115200
; break;
204 case 230400: baudrate
= B230400
; break;
205 case 460800: baudrate
= B460800
; break;
206 case 500000: baudrate
= B500000
; break;
207 case 576000: baudrate
= B576000
; break;
208 case 921600: baudrate
= B921600
; break;
209 case 1000000: baudrate
= B1000000
; break;
210 case 1152000: baudrate
= B1152000
; break;
211 case 1500000: baudrate
= B1500000
; break;
212 case 2000000: baudrate
= B2000000
; break;
213 case 2500000: baudrate
= B2500000
; break;
214 case 3000000: baudrate
= B3000000
; break;
215 case 3500000: baudrate
= B3500000
; break;
216 case 4000000: baudrate
= B4000000
; break;
218 throw ProtocolError("Bad baudrate", opt_baudrate
.c_str());
221 _master_fd
= open(cfg
.serial
.c_str(), O_RDWR
);
222 if (_master_fd
== -1) {
223 throw ProtocolError("open port error", cfg
.serial
.c_str(), errno
);
226 struct termios tios
{};
227 if (tcgetattr(_master_fd
, &tios
) != 0) {
228 const int err
= errno
;
230 throw ProtocolError("tcgetattr error", cfg
.serial
.c_str(), err
);
233 tios
.c_cflag
&= ~PARENB
;
234 tios
.c_cflag
&= ~CSTOPB
;
235 tios
.c_cflag
&= ~CSIZE
;
237 tios
.c_cflag
&= ~CRTSCTS
;
238 tios
.c_cflag
|= CREAD
| CLOCAL
;
240 tios
.c_lflag
&= ~ISIG
;
241 tios
.c_lflag
&= ~ICANON
;
242 tios
.c_lflag
&= ~ECHO
;
243 tios
.c_lflag
&= ~ECHOE
;
244 tios
.c_lflag
&= ~ECHONL
;
246 tios
.c_iflag
&= ~(IXON
| IXOFF
| IXANY
);
247 tios
.c_iflag
&= ~(IGNBRK
| BRKINT
| PARMRK
| ISTRIP
| INLCR
| IGNCR
| ICRNL
);
249 tios
.c_oflag
&= ~OPOST
;
250 tios
.c_oflag
&= ~ONLCR
;
252 tios
.c_cc
[VTIME
] = 0;
255 switch (atoi(opt_databits
.c_str())) {
256 case 5: tios
.c_cflag
|= CS5
; break;
257 case 6: tios
.c_cflag
|= CS6
; break;
258 case 7: tios
.c_cflag
|= CS7
; break;
259 case 8: tios
.c_cflag
|= CS8
; break;
262 throw ProtocolError("wrong databits", opt_databits
.c_str());
265 if (opt_flowctrl
== "Xon/Xoff") {
266 tios
.c_iflag
|= (IXON
| IXOFF
);
267 } else if (opt_flowctrl
== "RTS/CTS") {
268 tios
.c_cflag
|= CRTSCTS
;
269 } else if (opt_flowctrl
!= "None") {
271 throw ProtocolError("wrong flowctrl", opt_flowctrl
.c_str());
274 if (opt_stopbits
== "2") {
275 tios
.c_cflag
|= CSTOPB
;
276 } else if (opt_stopbits
!= "1") {
278 throw ProtocolError("wrong stopbits", opt_stopbits
.c_str());
282 cfsetispeed(&tios
, baudrate
);
283 cfsetospeed(&tios
, baudrate
);
285 if (tcsetattr(_master_fd
, TCSANOW
, &tios
) != 0) {
286 const int err
= errno
;
288 throw ProtocolError("tcsetattr error", cfg
.serial
.c_str(), err
);
291 if (pipe(_stderr_pipe
) < 0) { // create dummy pipe, just for genericity
292 const int err
= errno
;
294 throw ProtocolError("pipe error", err
);
297 MakeFDNonBlocking(_master_fd
);
298 MakeFDNonBlocking(_stderr_pipe
[0]);
301 void WayToShell::GetDescriptors(int &fdinout
, int &fderr
)
303 fdinout
= (_master_fd
!= -1) ? dup(_master_fd
) : -1;
304 fderr
= (_stderr_pipe
[0] != -1) ? dup(_stderr_pipe
[0]) : -1;
307 void WayToShell::ThrowIfAppExited()
309 if (_pid
!= (pid_t
)-1) {
311 if (waitpid(_pid
, &_pid_status
, WNOHANG
) == _pid
) {
313 throw ProtocolError("client app exited", _pid_status
);
318 bool WayToShell::RecvPolledFD(struct pollfd
&fd
, enum OutputType output_type
)
320 if (!(fd
.revents
& POLLIN
)) {
324 std::vector
<char> &data
= (output_type
== STDERR
) ? _stderr_data
: _stdout_data
;
325 const size_t ofs
= data
.size();
326 const size_t piece
= 4096;
327 data
.resize(data
.size() + piece
);
328 ssize_t n
= os_call_ssize(read
, fd
.fd
, (void *)(data
.data() + ofs
), piece
);
330 fprintf(stderr
, " !!! error reading pty n=%ld err=%d\n", n
, errno
);
332 throw ProtocolError("error reading pty", (output_type
== STDERR
) ? "stderr" : "stdout", errno
);
334 data
.resize(ofs
+ n
);
336 // if (g_netrocks_verbosity > 0) {
337 // DebugStr((output_type == STDERR) ? "STDERR" : "STDOUT", data.data() + ofs, n);
343 // if data is non-NULL then send that data, optionally recving incoming stuff
344 // if data is NULL then recv at least some incoming stuff
345 unsigned WayToShell::Xfer(const char *data
, size_t len
)
348 while ((!data
&& out
== 0) || len
) {
349 struct pollfd fds
[3]{};
350 fds
[0].fd
= _master_fd
;
351 fds
[0].events
= (data
&& len
) ? POLLIN
| POLLOUT
: POLLIN
;
352 fds
[1].fd
= _stderr_pipe
[0];
353 fds
[1].events
= POLLIN
;
354 fds
[2].fd
= _fd_ipc_recv
;
355 fds
[2].events
= 0; // this poll'ed only for errors
357 const int r
= os_call_int(poll
, &fds
[0], (nfds_t
)3, 10000);
359 throw ProtocolError("error polling pty", errno
);
366 if (RecvPolledFD(fds
[0], STDOUT
)) {
369 if (RecvPolledFD(fds
[1], STDERR
)) {
372 if (data
&& len
&& (fds
[0].revents
& POLLOUT
) != 0) {
373 const ssize_t wr
= write(_master_fd
, data
, len
);
380 } else if (errno
!= EAGAIN
&& errno
!= EWOULDBLOCK
&& errno
!= EINTR
) {
381 throw ProtocolError("pty write error", errno
);
384 if ( (fds
[0].revents
& (POLLERR
| POLLHUP
)) != 0
385 || (fds
[1].revents
& (POLLERR
| POLLHUP
)) != 0) {
386 throw ProtocolError("pty disrupted", errno
);
388 if ((fds
[2].revents
& (POLLERR
| POLLHUP
)) != 0) {
389 throw ProtocolError("ipc disrupted", errno
);
396 void WayToShell::RecvSomeStdout()
399 while ((Xfer() & STDOUT
) == 0) {
402 } catch (std::exception
&e
) {
403 if (_stderr_data
.empty()) {
406 std::string
stderr_str(_stderr_data
.data(), _stderr_data
.size());
407 throw ProtocolError(e
.what(), stderr_str
.c_str());
411 static ssize_t
DispatchExpectedReply(std::vector
<std::string
> &lines
, std::vector
<char> &incoming
,
412 const std::vector
<const char *> &expected_replies
)
414 // fetch incoming data and pack it into lines only until found match with expected reply
415 for (size_t left
= 0, right
= 0;;) {
416 if (right
== incoming
.size() || incoming
[right
] == '\r' || incoming
[right
] == '\n' || incoming
[right
] == 0) {
417 if (lines
.empty() || lines
.back().back() == '\n') {
418 lines
.emplace_back(incoming
.data() + left
, right
- left
);
420 lines
.back().append(incoming
.data() + left
, right
- left
);
422 const bool append_lf
= (right
!= incoming
.size());
423 if (right
+ 1 < incoming
.size() && incoming
[right
] == '\r' && incoming
[right
+ 1] == '\n') {
425 } else if (right
< incoming
.size()) {
429 if (!lines
.back().empty()) {
433 if (g_netrocks_verbosity
> 1) {
434 std::cerr
<< "Line '" << lines
.back() << "' " << std::endl
;
436 for (size_t index
= 0; index
!= expected_replies
.size(); ++index
) {
437 if (MatchWildcard(lines
.back().c_str(), expected_replies
[index
])) {
438 if (g_netrocks_verbosity
> 2) {
439 std::cerr
<< "Matched '" << expected_replies
[index
] << "' " << std::endl
;
441 incoming
.erase(incoming
.begin(), incoming
.begin() + right
);
444 if (g_netrocks_verbosity
> 2) {
445 std::cerr
<< "NOT Matched '" << expected_replies
[index
] << "' " << std::endl
;
451 if (right
== incoming
.size()) {
461 WaitResult
WayToShell::SendAndWaitReplyInner(const std::string
&send_str
, const std::vector
<const char *> &expected_replies
)
463 if (g_netrocks_verbosity
> 2) {
464 for (size_t index
= 0; index
!= expected_replies
.size(); ++index
) {
465 std::cerr
<< "Expect '" << expected_replies
[index
] << "' " << std::endl
;
470 for (bool first
= true;;first
= false) {
473 xrv
= STDOUT
| STDERR
;
474 if (!send_str
.empty()) {
475 xrv
|= Xfer(send_str
.c_str(), send_str
.size());
481 wr
.index
= DispatchExpectedReply(wr
.stdout_lines
, _stdout_data
, expected_replies
);
482 if (wr
.index
!= -1) {
483 wr
.output_type
= STDOUT
;
488 wr
.index
= DispatchExpectedReply(wr
.stderr_lines
, _stderr_data
, expected_replies
);
489 if (wr
.index
!= -1) {
490 wr
.output_type
= STDERR
;
496 } catch (std::exception
&e
) {
497 std::string stderr_str
;
498 AppendTrimmedLines(stderr_str
, wr
.stderr_lines
);
499 if (!_stderr_data
.empty()) {
501 stderr_str
.append(_stderr_data
.data(), _stderr_data
.size());
503 if (stderr_str
.empty()) {
506 throw ProtocolError(e
.what(), stderr_str
.c_str());
509 if (g_netrocks_verbosity
> 0 && !wr
.stdout_lines
.empty()) {
510 for (const auto &line
: wr
.stdout_lines
) {
511 DebugStr("STDOUT.RPL", line
);
515 if (g_netrocks_verbosity
> 0 && !wr
.stderr_lines
.empty()) {
516 for (const auto &line
: wr
.stderr_lines
) {
517 DebugStr("STDERR.RPL", line
);
524 WaitResult
WayToShell::SendAndWaitReply(const std::string
&send_str
, const std::vector
<const char *> &expected_replies
, bool hide_in_log
)
526 if (g_netrocks_verbosity
> 0) {
528 DebugStr("SEND.HIDE", "???");
530 DebugStr("SEND", send_str
);
534 return SendAndWaitReplyInner(send_str
, expected_replies
);
537 WaitResult
WayToShell::WaitReply(const std::vector
<const char *> &expected_replies
)
539 return SendAndWaitReplyInner(std::string(), expected_replies
);
542 void WayToShell::Send(const char *data
, size_t len
)
544 if (g_netrocks_verbosity
> 1) {
545 DebugStr("SEND.BLOB", data
, len
);
547 } else if (g_netrocks_verbosity
> 0) {
548 DebugStr("SEND.BLOB", StrPrintf("{%lu}", (unsigned long)len
));
554 void WayToShell::Send(const char *data
)
556 const size_t len
= strlen(data
);
557 if (g_netrocks_verbosity
> 0) {
558 DebugStr("SEND.PSZ", data
, len
);
564 void WayToShell::Send(const std::string
&line
)
566 if (g_netrocks_verbosity
> 0) {
567 DebugStr("SEND.STR", line
);
570 Xfer(line
.c_str(), line
.size());
573 void WayToShell::ReadStdout(void *buffer
, size_t len
)
575 for (size_t ofs
= 0;;) {
576 size_t piece
= std::min(len
- ofs
, _stdout_data
.size());
578 memcpy((char *)buffer
+ ofs
, _stdout_data
.data(), piece
);
579 _stdout_data
.erase(_stdout_data
.begin(), _stdout_data
.begin() + piece
);
588 if (g_netrocks_verbosity
> 1) {
589 DebugStr("STDOUT.BLOB", (const char *)buffer
, len
);
590 } else if (g_netrocks_verbosity
> 0) {
591 const auto &info
= StrPrintf("{%lu}", (unsigned long)len
);
592 DebugStr("STDOUT.BLOB", info
);
596 std::string
MultiLineRequest(std::string line
)