9 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
11 #include <sys/ioctl.h>
34 #define LOC QString("ExternSH[%1](%2): ").arg(m_inputId).arg(m_loc)
37 const QStringList &
args)
38 : m_app(QFileInfo(app)),
39 m_status(&m_statusBuf, QIODevice::ReadWrite)
43 m_error = QString(
"ExternIO: '%1' does not exist.").arg(app);
48 m_error = QString(
"ExternIO: '%1' is not readable.")
49 .arg(
m_app.canonicalFilePath());
52 if (!
m_app.isExecutable())
54 m_error = QString(
"ExternIO: '%1' is not executable.")
55 .arg(
m_app.canonicalFilePath());
77 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
78 std::array<struct pollfd,2> m_poll {};
81 m_poll[0].events = POLLIN | POLLPRI;
82 int ret = poll(m_poll.data(), 1,
timeout.count());
84 if (m_poll[0].revents & POLLHUP)
86 m_error = what +
" poll eof (POLLHUP)";
89 if (m_poll[0].revents & POLLNVAL)
91 LOG(VB_GENERAL, LOG_ERR,
"poll error");
94 if (m_poll[0].revents & POLLIN)
99 if ((EOVERFLOW == errno))
107 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
115 LOG(VB_RECORD, LOG_ERR,
116 QString(
"ExternIO::Read: already in error state: '%1'")
139 m_error =
"Failed to read from External Recorder: " +
ENO;
140 LOG(VB_RECORD, LOG_WARNING,
141 "External Recorder not ready. Giving up.");
145 LOG(VB_RECORD, LOG_WARNING,
146 QString(
"External Recorder not ready. Will retry (%1/%2).")
148 std::this_thread::sleep_for(100ms);
153 m_error =
"Failed to read from External Recorder: " +
ENO;
165 LOG(VB_RECORD, LOG_DEBUG,
166 QString(
"ExternIO::Read '%1' bytes, buffer size %2")
167 .arg(len).arg(buffer.size()));
176 LOG(VB_RECORD, LOG_ERR,
177 QString(
"ExternIO::GetStatus: already in error state: '%1'")
185 std::array<char,2048> buffer {};
187 m_status << QString::fromLatin1(buffer.data(), len);
195 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::GetStatus '%1'")
205 LOG(VB_RECORD, LOG_ERR,
206 QString(
"ExternIO::Write: already in error state: '%1'")
211 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::Write('%1')")
212 .arg(QString(buffer).simplified()));
214 int len =
write(
m_appIn, buffer.constData(), buffer.size());
215 if (len != buffer.size())
219 LOG(VB_RECORD, LOG_WARNING,
220 QString(
"ExternIO::Write: only wrote %1 of %2 bytes '%3'")
221 .arg(len).arg(buffer.size()).arg(QString(buffer)));
225 m_error = QString(
"ExternIO: Failed to write '%1' to app's stdin: ")
226 .arg(QString(buffer)) +
ENO;
236 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Run()"));
247 #if defined(Q_OS_DARWIN) || defined(__FreeBSD__) || defined(__OpenBSD__)
250 #elif defined USING_MINGW
253 #elif defined( _MSC_VER )
257 QString grp = QString(
"pgrep -x -f -- \"%1\" 2>&1 > /dev/null").arg(cmd);
258 QString kil = QString(
"pkill --signal 15 -x -f -- \"%1\" 2>&1 > /dev/null")
261 int res_grp = system(grp.toUtf8().constData());
264 LOG(VB_RECORD, LOG_DEBUG, QString(
"'%1' not running.").arg(cmd));
268 LOG(VB_RECORD, LOG_WARNING, QString(
"'%1' already running, killing...")
270 int res_kil = system(kil.toUtf8().constData());
272 LOG(VB_GENERAL, LOG_WARNING, QString(
"'%1' failed: %2")
275 res_grp = system(grp.toUtf8().constData());
278 LOG(
WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
279 QString(
"'%1' terminated.").arg(cmd));
283 std::this_thread::sleep_for(50ms);
285 kil = QString(
"pkill --signal 9 -x -f \"%1\" 2>&1 > /dev/null").arg(cmd);
286 res_kil = system(kil.toUtf8().constData());
288 LOG(VB_GENERAL, LOG_WARNING, QString(
"'%1' failed: %2")
291 res_grp = system(grp.toUtf8().constData());
292 LOG(
WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
294 .arg(cmd,
WEXITSTATUS(res_grp) == 0 ?
"sill running" :
"terminated"));
302 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
305 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO in bad state: '%1'")
310 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
315 std::this_thread::sleep_for(50ms);
318 m_error = QString(
"Unable to kill existing '%1'.")
326 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Fork '%1'").arg(full_command));
328 std::array<int,2> in = {-1, -1};
329 std::array<int,2> out = {-1, -1};
330 std::array<int,2> err = {-1, -1};
332 if (pipe(in.data()) < 0)
337 if (pipe(out.data()) < 0)
344 if (pipe(err.data()) < 0)
378 LOG(VB_GENERAL, LOG_WARNING,
379 "ExternIO::Fork(): Failed to set O_NONBLOCK for FD: " +
ENO);
380 std::this_thread::sleep_for(2s);
384 LOG(VB_RECORD, LOG_INFO,
"Spawned");
392 if (dup2( in[0], 0) < 0)
394 std::cerr <<
"dup2(stdin) failed: " << strerror(errno);
397 else if (dup2(out[1], 1) < 0)
399 std::cerr <<
"dup2(stdout) failed: " << strerror(errno);
402 else if (dup2(err[1], 2) < 0)
404 std::cerr <<
"dup2(stderr) failed: " << strerror(errno);
409 for (
int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; --i)
415 if (setpgid(0,0) < 0)
417 std::cerr <<
"ExternIO: "
418 <<
"setpgid() failed: "
419 << strerror(errno) << std::endl;
423 char *command = strdup(
m_app.canonicalFilePath()
424 .toUtf8().constData());
426 char **arguments =
new char*[
m_args.size() + 1];
427 for (
int i = 0; i <
m_args.size(); ++i)
429 int len =
m_args[i].size() + 1;
430 arguments[i] =
new char[len];
431 memcpy(arguments[i],
m_args[i].toStdString().c_str(), len);
433 arguments[
m_args.size()] =
nullptr;
435 if (execv(command, arguments) < 0)
438 std::cerr <<
"ExternIO: "
439 <<
"execv() failed: "
440 << strerror(errno) << std::endl;
444 std::cerr <<
"ExternIO: "
445 <<
"execv() should not be here?: "
446 << strerror(errno) << std::endl;
449 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
461 int inputid,
int majorid)
465 QMap<int, ExternalStreamHandler*>::iterator it =
s_handlers.find(majorid);
473 LOG(VB_RECORD, LOG_INFO,
474 QString(
"ExternSH[%1:%2]: Creating new stream handler for %3 "
476 .arg(inputid).arg(majorid).arg(devname));
482 LOG(VB_RECORD, LOG_INFO,
483 QString(
"ExternSH[%1:%2]: Using existing stream handler for %3")
484 .arg(inputid).arg(majorid).arg(devname) +
485 QString(
" (%1 in use)").arg(rcount));
502 QMap<int, ExternalStreamHandler*>::iterator it =
510 LOG(VB_RECORD, LOG_INFO,
511 QString(
"ExternSH[%1:%2]: Return handler (%3 still in use)")
512 .arg(inputid).arg(majorid).arg(*rit));
519 LOG(VB_RECORD, LOG_INFO,
520 QString(
"ExternSH[%1:%2]: Closing handler (0 in use)")
521 .arg(inputid).arg(majorid));
527 LOG(VB_GENERAL, LOG_ERR,
528 QString(
"ExternSH[%1:%2]: Error: No handler to return!")
529 .arg(inputid).arg(majorid));
549 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
550 m_args = path.split(
' ',QString::SkipEmptyParts) +
553 m_args = path.split(
' ',Qt::SkipEmptyParts) +
560 if (!
m_args.contains(
"--quiet") && !
m_args.contains(
"-q"))
563 m_args <<
"--inputid" << QString::number(majorid);
564 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"args \"%1\"")
569 LOG(VB_GENERAL, LOG_ERR,
LOC +
570 QString(
"Failed to start %1").arg(
m_device));
587 uint restart_cnt = 0;
591 bool good_data =
false;
592 uint data_proc_err = 0;
593 uint data_short_err = 0;
597 LOG(VB_GENERAL, LOG_ERR,
LOC +
598 QString(
"%1 is not running.").arg(
m_device));
601 status_timer.
start();
605 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): begin");
610 ready_cmd =
"SendBytes";
619 LOG(VB_RECORD, LOG_WARNING,
LOC +
"TS not open yet.");
620 std::this_thread::sleep_for(10ms);
626 std::this_thread::sleep_for(10ms);
636 LOG(VB_RECORD, LOG_WARNING,
LOC +
637 "Internal buffer too full to accept more data from "
638 "external application.");
644 if (result.startsWith(
"ERR"))
646 LOG(VB_GENERAL, LOG_ERR,
LOC +
647 QString(
"Aborting: %1 -> %2")
648 .arg(ready_cmd, result));
654 std::this_thread::sleep_for(20s);
657 LOG(VB_RECORD, LOG_ERR,
LOC +
658 "Failed to restart stream.");
669 if (status_timer.
elapsed() >= 2s)
677 std::this_thread::sleep_for(20s);
680 LOG(VB_RECORD, LOG_ERR,
LOC +
"Failed to restart stream.");
697 if (result.startsWith(
"ERR"))
699 LOG(VB_GENERAL, LOG_ERR,
LOC +
700 QString(
"Aborting: XOFF -> %2")
710 read_len =
m_io->
Read(buffer, sz, 100ms);
720 nodata_timer.
start();
723 if (nodata_timer.
elapsed() >= 50s)
725 LOG(VB_GENERAL, LOG_WARNING,
LOC +
726 "No data for 50 seconds, Restarting stream.");
729 LOG(VB_RECORD, LOG_ERR,
LOC +
730 "Failed to restart stream.");
738 std::this_thread::sleep_for(50ms);
752 LOG(VB_GENERAL, LOG_ERR,
LOC +
"I/O thread has disappeared!");
758 LOG(VB_GENERAL, LOG_ERR,
LOC +
759 QString(
"Fatal Error from External Recorder: %1")
766 len = remainder = buffer.size();
773 if (
m_xon && data_short_err++ == 0)
774 LOG(VB_RECORD, LOG_INFO,
LOC +
"Waiting for a full TS packet.");
775 std::this_thread::sleep_for(50us);
780 if (data_short_err > 1)
782 LOG(VB_RECORD, LOG_INFO,
LOC +
783 QString(
"Waited for a full TS packet %1 times.")
784 .arg(data_short_err));
798 remainder = sit.key()->ProcessData
799 (
reinterpret_cast<const uint8_t *
>
800 (buffer.constData()), buffer.size());
811 LOG(VB_RECORD, LOG_WARNING,
LOC +
812 QString(
"Replay size truncated to %1 bytes")
822 good_data = (len != 0U);
824 else if (len > remainder)
826 buffer.remove(0, len - remainder);
827 good_data = (len != 0U);
829 else if (len == remainder)
836 if (data_proc_err > 1)
838 LOG(VB_RECORD, LOG_WARNING,
LOC +
839 QString(
"Failed to process the data received %1 times.")
840 .arg(data_proc_err));
847 if (data_proc_err++ == 0)
849 LOG(VB_RECORD, LOG_WARNING,
LOC +
850 "Failed to process the data received");
855 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
856 QString(
"%1 shutdown").arg(
m_bError ?
"Error" :
"Normal"));
861 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
"end");
872 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
873 QStringList tokens = result.split(
':', QString::SkipEmptyParts);
875 QStringList tokens = result.split(
':', Qt::SkipEmptyParts);
878 if (tokens.size() > 1)
883 LOG(VB_RECORD, LOG_ERR,
LOC +
884 QString(
"Bad response to 'APIVersion?' - '%1'. "
885 "Expecting 1 or 2").arg(result));
903 m_loc = result.mid(3);
918 LOG(VB_RECORD, LOG_WARNING,
LOC +
"OpenApp: already open!");
926 LOG(VB_GENERAL, LOG_ERR,
LOC +
"ExternIO failed: " +
ENO);
931 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Spawn '%1'").arg(
m_device));
935 LOG(VB_GENERAL, LOG_ERR,
957 LOG(VB_RECORD, LOG_ERR,
LOC +
"Application is not responding.");
967 LOG(VB_RECORD, LOG_ERR,
LOC +
968 QString(
"Bad response to 'HasTuner?' - '%1'").arg(result));
976 LOG(VB_RECORD, LOG_ERR,
LOC +
977 QString(
"Bad response to 'HasPictureAttributes?' - '%1'")
986 result.startsWith(
"OK:Poll");
988 LOG(VB_RECORD, LOG_INFO,
LOC +
"App opened successfully");
989 LOG(VB_RECORD, LOG_INFO,
LOC +
990 QString(
"Capabilities: tuner(%1) "
991 "Picture attributes(%2) "
1006 if (
m_io ==
nullptr)
1008 LOG(VB_RECORD, LOG_WARNING,
LOC +
1009 "WARNING: Unable to communicate with external app.");
1038 LOG(VB_RECORD, LOG_INFO,
LOC +
"CloseRecorder");
1043 if (!result.startsWith(
"OK"))
1045 LOG(VB_RECORD, LOG_INFO,
LOC +
1046 "CloseRecorder failed, sending kill.");
1048 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
1053 std::this_thread::sleep_for(50ms);
1056 LOG(VB_GENERAL, LOG_ERR,
1057 QString(
"Unable to kill existing '%1'.")
1058 .arg(full_command));
1073 LOG(VB_RECORD, LOG_INFO,
LOC +
"Restarting stream.");
1078 std::this_thread::sleep_for(1s);
1110 sit.key()->ProcessData(
reinterpret_cast<const uint8_t *
>
1115 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Replayed %1 bytes")
1137 LOG(VB_RECORD, LOG_INFO,
LOC +
1138 QString(
"StartStreaming with %1 current listeners")
1143 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1151 LogLevel_t level = LOG_ERR;
1152 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1153 level = LOG_WARNING;
1157 LOG(VB_GENERAL, level,
LOC + QString(
"StartStreaming failed: '%1'")
1163 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming started");
1166 LOG(VB_RECORD, LOG_INFO,
LOC +
"Already streaming");
1170 LOG(VB_RECORD, LOG_INFO,
LOC +
1171 QString(
"StartStreaming %1 listeners")
1181 LOG(VB_RECORD, LOG_INFO,
LOC +
1182 QString(
"StopStreaming %1 listeners")
1187 LOG(VB_RECORD, LOG_INFO,
LOC +
1188 "StopStreaming requested, but we are not streaming!");
1194 LOG(VB_RECORD, LOG_INFO,
LOC +
1195 QString(
"StopStreaming delayed, still have %1 listeners")
1200 LOG(VB_RECORD, LOG_INFO,
LOC +
"StopStreaming");
1211 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1218 LogLevel_t level = LOG_ERR;
1219 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1220 level = LOG_WARNING;
1224 LOG(VB_GENERAL, level,
LOC + QString(
"StopStreaming: '%1'")
1231 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming stopped");
1238 std::chrono::milliseconds
timeout,
1248 LOG(VB_RECORD, LOG_ERR,
LOC +
1249 QString(
"Invalid API version %1. Expected 1 or 2").arg(
m_apiVersion));
1255 std::chrono::milliseconds
timeout,
1258 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer1('%1')")
1261 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1267 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1271 QByteArray buf(cmd.toUtf8(), cmd.size());
1276 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1294 LOG(VB_GENERAL, LOG_ERR,
LOC +
1295 "Failed to read from External Recorder: " +
1302 if (result.startsWith(
"STATUS:ERR") ||
1303 result.startsWith(
"0:STATUS:ERR"))
1305 LOG(VB_RECORD, LOG_ERR,
LOC + result);
1306 result.remove(0, result.indexOf(
":ERR") + 1);
1311 if (!result.startsWith(
"STATUS") && !result.startsWith(
"0:STATUS"))
1313 LOG(VB_RECORD, LOG_INFO,
LOC +
1314 QString(
"Ignoring response '%1'").arg(result));
1317 if (result.size() < 1)
1319 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1320 QString(
"External Recorder did not respond to '%1'").arg(cmd));
1324 bool okay = result.startsWith(
"OK");
1325 if (okay || result.startsWith(
"WARN") || result.startsWith(
"ERR"))
1327 LogLevel_t level = LOG_INFO;
1331 level = LOG_WARNING;
1332 else if (cmd.startsWith(
"SendBytes"))
1335 LOG(VB_RECORD, level,
1336 LOC + QString(
"ProcessCommand('%1') = '%2' took %3ms %4")
1338 QString::number(timer.
elapsed().count()),
1339 okay ?
"" :
"<-- NOTE"));
1343 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1344 QString(
"External Recorder invalid response to '%1': '%2'")
1350 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1361 std::chrono::milliseconds
timeout,
1367 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1369 QString cmd = QString(
"%1:%2").arg(++
m_serialNo).arg(command);
1371 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer2('%1') serial(%2)")
1378 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1382 QByteArray buf(cmd.toUtf8(), cmd.size());
1387 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1403 LOG(VB_GENERAL, LOG_ERR,
LOC +
1404 "Failed to read from External Recorder: " +
1410 if (!result.isEmpty())
1413 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1414 tokens = result.split(
':', QString::SkipEmptyParts);
1416 tokens = result.split(
':', Qt::SkipEmptyParts);
1420 if (tokens.size() > 1 && tokens[0].toUInt() >=
m_serialNo)
1426 if (tokens[0].startsWith(
"ERR"))
1430 tokens.removeFirst();
1431 result = tokens.join(
':');
1432 bool err = (tokens.size() > 1 && tokens[1].startsWith(
"ERR"));
1433 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + raw);
1437 tokens.removeFirst();
1438 result = tokens.join(
':');
1446 LOG(VB_RECORD, LOG_ERR,
LOC +
1447 QString(
"ProcessVer2: Giving up waiting for response for "
1448 "command '%2'").arg(cmd));
1450 else if (tokens.size() < 2)
1452 LOG(VB_RECORD, LOG_ERR,
LOC +
1453 QString(
"Did not receive a valid response "
1454 "for command '%1', received '%2'").arg(cmd, result));
1458 LOG(VB_RECORD, LOG_ERR,
LOC +
1459 QString(
"ProcessVer2: Looking for serial no %1, "
1460 "but received %2 for command '%2'")
1461 .arg(QString::number(
m_serialNo), tokens[0], cmd));
1465 tokens.removeFirst();
1466 status = tokens[0].trimmed();
1467 result = tokens.join(
':');
1469 bool okay = (status ==
"OK");
1470 if (okay || status.startsWith(
"WARN") || status.startsWith(
"ERR"))
1472 LogLevel_t level = LOG_INFO;
1476 level = LOG_WARNING;
1477 else if (command.startsWith(
"SendBytes") ||
1478 (command.startsWith(
"TuneStatus") &&
1479 result ==
"OK:InProgress"))
1482 LOG(VB_RECORD, level,
1483 LOC + QString(
"ProcessV2('%1') = '%2' took %3ms %4")
1484 .arg(cmd, result, QString::number(timer.
elapsed().count()),
1485 okay ?
"" :
"<-- NOTE"));
1489 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1490 QString(
"External Recorder invalid response to '%1': '%2'")
1496 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1514 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1520 LOG(VB_GENERAL, LOG_ERR,
"External Recorder in bad state: " +
1528 if (!result.isEmpty())
1532 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1533 QStringList tokens = result.split(
':', QString::SkipEmptyParts);
1535 QStringList tokens = result.split(
':', Qt::SkipEmptyParts);
1538 tokens.removeFirst();
1539 result = tokens.join(
':');
1540 for (
int idx = 1; idx < tokens.size(); ++idx)
1541 err |= tokens[idx].startsWith(
"ERR");
1544 err |= result.startsWith(
"STATUS:ERR");
1546 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + result);
1549 while (!result.isEmpty());