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());
76 [[maybe_unused]] std::chrono::milliseconds
timeout,
77 [[maybe_unused]]
const QString & what)
79 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
80 std::array<struct pollfd,2> m_poll {};
83 m_poll[0].events = POLLIN | POLLPRI;
84 int ret = poll(m_poll.data(), 1,
timeout.count());
86 if (m_poll[0].revents & POLLHUP)
88 m_error = what +
" poll eof (POLLHUP)";
91 if (m_poll[0].revents & POLLNVAL)
93 LOG(VB_GENERAL, LOG_ERR,
"poll error");
96 if (m_poll[0].revents & POLLIN)
101 if ((EOVERFLOW == errno))
105 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
113 LOG(VB_RECORD, LOG_ERR,
114 QString(
"ExternIO::Read: already in error state: '%1'")
137 m_error =
"Failed to read from External Recorder: " +
ENO;
138 LOG(VB_RECORD, LOG_WARNING,
139 "External Recorder not ready. Giving up.");
143 LOG(VB_RECORD, LOG_WARNING,
144 QString(
"External Recorder not ready. Will retry (%1/%2).")
146 std::this_thread::sleep_for(100ms);
151 m_error =
"Failed to read from External Recorder: " +
ENO;
163 LOG(VB_RECORD, LOG_DEBUG,
164 QString(
"ExternIO::Read '%1' bytes, buffer size %2")
165 .arg(len).arg(buffer.size()));
174 LOG(VB_RECORD, LOG_ERR,
175 QString(
"ExternIO::GetStatus: already in error state: '%1'")
183 std::array<char,2048> buffer {};
185 m_status << QString::fromLatin1(buffer.data(), len);
193 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::GetStatus '%1'")
203 LOG(VB_RECORD, LOG_ERR,
204 QString(
"ExternIO::Write: already in error state: '%1'")
209 LOG(VB_RECORD, LOG_DEBUG, QString(
"ExternIO::Write('%1')")
210 .arg(QString(buffer).simplified()));
212 int len =
write(
m_appIn, buffer.constData(), buffer.size());
213 if (len != buffer.size())
217 LOG(VB_RECORD, LOG_WARNING,
218 QString(
"ExternIO::Write: only wrote %1 of %2 bytes '%3'")
219 .arg(len).arg(buffer.size()).arg(QString(buffer)));
223 m_error = QString(
"ExternIO: Failed to write '%1' to app's stdin: ")
224 .arg(QString(buffer)) +
ENO;
234 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Run()"));
245 #if defined(Q_OS_DARWIN) || defined(__FreeBSD__) || defined(__OpenBSD__)
247 #elif defined USING_MINGW
249 #elif defined( _MSC_VER )
252 QString grp = QString(
"pgrep -x -f -- \"%1\" 2>&1 > /dev/null").arg(cmd);
253 QString kil = QString(
"pkill --signal 15 -x -f -- \"%1\" 2>&1 > /dev/null")
256 int res_grp = system(grp.toUtf8().constData());
259 LOG(VB_RECORD, LOG_DEBUG, QString(
"'%1' not running.").arg(cmd));
263 LOG(VB_RECORD, LOG_WARNING, QString(
"'%1' already running, killing...")
265 int res_kil = system(kil.toUtf8().constData());
267 LOG(VB_GENERAL, LOG_WARNING, QString(
"'%1' failed: %2")
270 res_grp = system(grp.toUtf8().constData());
273 LOG(
WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
274 QString(
"'%1' terminated.").arg(cmd));
278 std::this_thread::sleep_for(50ms);
280 kil = QString(
"pkill --signal 9 -x -f \"%1\" 2>&1 > /dev/null").arg(cmd);
281 res_kil = system(kil.toUtf8().constData());
283 LOG(VB_GENERAL, LOG_WARNING, QString(
"'%1' failed: %2")
286 res_grp = system(grp.toUtf8().constData());
287 LOG(
WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
289 .arg(cmd,
WEXITSTATUS(res_grp) == 0 ?
"sill running" :
"terminated"));
297 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
300 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO in bad state: '%1'")
305 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
310 std::this_thread::sleep_for(50ms);
313 m_error = QString(
"Unable to kill existing '%1'.")
321 LOG(VB_RECORD, LOG_INFO, QString(
"ExternIO::Fork '%1'").arg(full_command));
323 std::array<int,2> in = {-1, -1};
324 std::array<int,2> out = {-1, -1};
325 std::array<int,2> err = {-1, -1};
327 if (pipe(in.data()) < 0)
332 if (pipe(out.data()) < 0)
339 if (pipe(err.data()) < 0)
373 LOG(VB_GENERAL, LOG_WARNING,
374 "ExternIO::Fork(): Failed to set O_NONBLOCK for FD: " +
ENO);
375 std::this_thread::sleep_for(2s);
379 LOG(VB_RECORD, LOG_INFO,
"Spawned");
387 if (dup2( in[0], 0) < 0)
389 std::cerr <<
"dup2(stdin) failed: " << strerror(errno);
392 else if (dup2(out[1], 1) < 0)
394 std::cerr <<
"dup2(stdout) failed: " << strerror(errno);
397 else if (dup2(err[1], 2) < 0)
399 std::cerr <<
"dup2(stderr) failed: " << strerror(errno);
404 for (
int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; --i)
410 if (setpgid(0,0) < 0)
412 std::cerr <<
"ExternIO: "
413 <<
"setpgid() failed: "
414 << strerror(errno) << std::endl;
418 char *command = strdup(
m_app.canonicalFilePath()
419 .toUtf8().constData());
421 char **arguments =
new char*[
m_args.size() + 1];
422 for (
int i = 0; i <
m_args.size(); ++i)
424 int len =
m_args[i].size() + 1;
425 arguments[i] =
new char[len];
426 memcpy(arguments[i],
m_args[i].toStdString().c_str(), len);
428 arguments[
m_args.size()] =
nullptr;
430 if (execv(command, arguments) < 0)
433 std::cerr <<
"ExternIO: "
434 <<
"execv() failed: "
435 << strerror(errno) << std::endl;
439 std::cerr <<
"ExternIO: "
440 <<
"execv() should not be here?: "
441 << strerror(errno) << std::endl;
444 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
456 int inputid,
int majorid)
460 QMap<int, ExternalStreamHandler*>::iterator it =
s_handlers.find(majorid);
468 LOG(VB_RECORD, LOG_INFO,
469 QString(
"ExternSH[%1:%2]: Creating new stream handler for %3 "
471 .arg(inputid).arg(majorid).arg(devname));
477 LOG(VB_RECORD, LOG_INFO,
478 QString(
"ExternSH[%1:%2]: Using existing stream handler for %3")
479 .arg(inputid).arg(majorid).arg(devname) +
480 QString(
" (%1 in use)").arg(rcount));
497 QMap<int, ExternalStreamHandler*>::iterator it =
505 LOG(VB_RECORD, LOG_INFO,
506 QString(
"ExternSH[%1:%2]: Return handler (%3 still in use)")
507 .arg(inputid).arg(majorid).arg(*rit));
514 LOG(VB_RECORD, LOG_INFO,
515 QString(
"ExternSH[%1:%2]: Closing handler (0 in use)")
516 .arg(inputid).arg(majorid));
522 LOG(VB_GENERAL, LOG_ERR,
523 QString(
"ExternSH[%1:%2]: Error: No handler to return!")
524 .arg(inputid).arg(majorid));
544 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
545 m_args = path.split(
' ',QString::SkipEmptyParts) +
548 m_args = path.split(
' ',Qt::SkipEmptyParts) +
555 if (!
m_args.contains(
"--quiet") && !
m_args.contains(
"-q"))
558 m_args <<
"--inputid" << QString::number(majorid);
559 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"args \"%1\"")
564 LOG(VB_GENERAL, LOG_ERR,
LOC +
565 QString(
"Failed to start %1").arg(
m_device));
582 uint restart_cnt = 0;
586 bool good_data =
false;
587 uint data_proc_err = 0;
588 uint data_short_err = 0;
592 LOG(VB_GENERAL, LOG_ERR,
LOC +
593 QString(
"%1 is not running.").arg(
m_device));
596 status_timer.
start();
600 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): begin");
605 ready_cmd =
"SendBytes";
614 LOG(VB_RECORD, LOG_WARNING,
LOC +
"TS not open yet.");
615 std::this_thread::sleep_for(10ms);
621 std::this_thread::sleep_for(10ms);
631 LOG(VB_RECORD, LOG_WARNING,
LOC +
632 "Internal buffer too full to accept more data from "
633 "external application.");
639 if (result.startsWith(
"ERR"))
641 LOG(VB_GENERAL, LOG_ERR,
LOC +
642 QString(
"Aborting: %1 -> %2")
643 .arg(ready_cmd, result));
649 std::this_thread::sleep_for(20s);
652 LOG(VB_RECORD, LOG_ERR,
LOC +
653 "Failed to restart stream.");
664 if (status_timer.
elapsed() >= 2s)
672 std::this_thread::sleep_for(20s);
675 LOG(VB_RECORD, LOG_ERR,
LOC +
"Failed to restart stream.");
692 if (result.startsWith(
"ERR"))
694 LOG(VB_GENERAL, LOG_ERR,
LOC +
695 QString(
"Aborting: XOFF -> %2")
705 read_len =
m_io->
Read(buffer, sz, 100ms);
715 nodata_timer.
start();
718 if (nodata_timer.
elapsed() >= 50s)
720 LOG(VB_GENERAL, LOG_WARNING,
LOC +
721 "No data for 50 seconds, Restarting stream.");
724 LOG(VB_RECORD, LOG_ERR,
LOC +
725 "Failed to restart stream.");
733 std::this_thread::sleep_for(50ms);
747 LOG(VB_GENERAL, LOG_ERR,
LOC +
"I/O thread has disappeared!");
753 LOG(VB_GENERAL, LOG_ERR,
LOC +
754 QString(
"Fatal Error from External Recorder: %1")
761 len = remainder = buffer.size();
768 if (
m_xon && data_short_err++ == 0)
769 LOG(VB_RECORD, LOG_INFO,
LOC +
"Waiting for a full TS packet.");
770 std::this_thread::sleep_for(50us);
775 if (data_short_err > 1)
777 LOG(VB_RECORD, LOG_INFO,
LOC +
778 QString(
"Waited for a full TS packet %1 times.")
779 .arg(data_short_err));
793 remainder = sit.key()->ProcessData
794 (
reinterpret_cast<const uint8_t *
>
795 (buffer.constData()), buffer.size());
806 LOG(VB_RECORD, LOG_WARNING,
LOC +
807 QString(
"Replay size truncated to %1 bytes")
817 good_data = (len != 0U);
819 else if (len > remainder)
821 buffer.remove(0, len - remainder);
822 good_data = (len != 0U);
824 else if (len == remainder)
831 if (data_proc_err > 1)
833 LOG(VB_RECORD, LOG_WARNING,
LOC +
834 QString(
"Failed to process the data received %1 times.")
835 .arg(data_proc_err));
842 if (data_proc_err++ == 0)
844 LOG(VB_RECORD, LOG_WARNING,
LOC +
845 "Failed to process the data received");
850 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
851 QString(
"%1 shutdown").arg(
m_bError ?
"Error" :
"Normal"));
856 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
"end");
867 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
868 QStringList tokens = result.split(
':', QString::SkipEmptyParts);
870 QStringList tokens = result.split(
':', Qt::SkipEmptyParts);
873 if (tokens.size() > 1)
878 LOG(VB_RECORD, LOG_ERR,
LOC +
879 QString(
"Bad response to 'APIVersion?' - '%1'. "
880 "Expecting 1 or 2").arg(result));
898 m_loc = result.mid(3);
913 LOG(VB_RECORD, LOG_WARNING,
LOC +
"OpenApp: already open!");
921 LOG(VB_GENERAL, LOG_ERR,
LOC +
"ExternIO failed: " +
ENO);
926 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Spawn '%1'").arg(
m_device));
930 LOG(VB_GENERAL, LOG_ERR,
952 LOG(VB_RECORD, LOG_ERR,
LOC +
"Application is not responding.");
962 LOG(VB_RECORD, LOG_ERR,
LOC +
963 QString(
"Bad response to 'HasTuner?' - '%1'").arg(result));
971 LOG(VB_RECORD, LOG_ERR,
LOC +
972 QString(
"Bad response to 'HasPictureAttributes?' - '%1'")
981 result.startsWith(
"OK:Poll");
983 LOG(VB_RECORD, LOG_INFO,
LOC +
"App opened successfully");
984 LOG(VB_RECORD, LOG_INFO,
LOC +
985 QString(
"Capabilities: tuner(%1) "
986 "Picture attributes(%2) "
1001 if (
m_io ==
nullptr)
1003 LOG(VB_RECORD, LOG_WARNING,
LOC +
1004 "WARNING: Unable to communicate with external app.");
1033 LOG(VB_RECORD, LOG_INFO,
LOC +
"CloseRecorder");
1038 if (!result.startsWith(
"OK"))
1040 LOG(VB_RECORD, LOG_INFO,
LOC +
1041 "CloseRecorder failed, sending kill.");
1043 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
1048 std::this_thread::sleep_for(50ms);
1051 LOG(VB_GENERAL, LOG_ERR,
1052 QString(
"Unable to kill existing '%1'.")
1053 .arg(full_command));
1068 LOG(VB_RECORD, LOG_INFO,
LOC +
"Restarting stream.");
1073 std::this_thread::sleep_for(1s);
1105 sit.key()->ProcessData(
reinterpret_cast<const uint8_t *
>
1110 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Replayed %1 bytes")
1132 LOG(VB_RECORD, LOG_INFO,
LOC +
1133 QString(
"StartStreaming with %1 current listeners")
1138 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1146 LogLevel_t level = LOG_ERR;
1147 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1148 level = LOG_WARNING;
1152 LOG(VB_GENERAL, level,
LOC + QString(
"StartStreaming failed: '%1'")
1158 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming started");
1161 LOG(VB_RECORD, LOG_INFO,
LOC +
"Already streaming");
1165 LOG(VB_RECORD, LOG_INFO,
LOC +
1166 QString(
"StartStreaming %1 listeners")
1176 LOG(VB_RECORD, LOG_INFO,
LOC +
1177 QString(
"StopStreaming %1 listeners")
1182 LOG(VB_RECORD, LOG_INFO,
LOC +
1183 "StopStreaming requested, but we are not streaming!");
1189 LOG(VB_RECORD, LOG_INFO,
LOC +
1190 QString(
"StopStreaming delayed, still have %1 listeners")
1195 LOG(VB_RECORD, LOG_INFO,
LOC +
"StopStreaming");
1206 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1213 LogLevel_t level = LOG_ERR;
1214 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1215 level = LOG_WARNING;
1219 LOG(VB_GENERAL, level,
LOC + QString(
"StopStreaming: '%1'")
1226 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming stopped");
1233 std::chrono::milliseconds
timeout,
1243 LOG(VB_RECORD, LOG_ERR,
LOC +
1244 QString(
"Invalid API version %1. Expected 1 or 2").arg(
m_apiVersion));
1250 std::chrono::milliseconds
timeout,
1253 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer1('%1')")
1256 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1262 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1266 QByteArray buf(cmd.toUtf8(), cmd.size());
1271 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1289 LOG(VB_GENERAL, LOG_ERR,
LOC +
1290 "Failed to read from External Recorder: " +
1297 if (result.startsWith(
"STATUS:ERR") ||
1298 result.startsWith(
"0:STATUS:ERR"))
1300 LOG(VB_RECORD, LOG_ERR,
LOC + result);
1301 result.remove(0, result.indexOf(
":ERR") + 1);
1306 if (!result.startsWith(
"STATUS") && !result.startsWith(
"0:STATUS"))
1308 LOG(VB_RECORD, LOG_INFO,
LOC +
1309 QString(
"Ignoring response '%1'").arg(result));
1312 if (result.size() < 1)
1314 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1315 QString(
"External Recorder did not respond to '%1'").arg(cmd));
1319 bool okay = result.startsWith(
"OK");
1320 if (okay || result.startsWith(
"WARN") || result.startsWith(
"ERR"))
1322 LogLevel_t level = LOG_INFO;
1326 level = LOG_WARNING;
1327 else if (cmd.startsWith(
"SendBytes"))
1330 LOG(VB_RECORD, level,
1331 LOC + QString(
"ProcessCommand('%1') = '%2' took %3ms %4")
1333 QString::number(timer.
elapsed().count()),
1334 okay ?
"" :
"<-- NOTE"));
1338 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1339 QString(
"External Recorder invalid response to '%1': '%2'")
1345 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1356 std::chrono::milliseconds
timeout,
1362 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1364 QString cmd = QString(
"%1:%2").arg(++
m_serialNo).arg(command);
1366 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer2('%1') serial(%2)")
1373 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1377 QByteArray buf(cmd.toUtf8(), cmd.size());
1382 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1398 LOG(VB_GENERAL, LOG_ERR,
LOC +
1399 "Failed to read from External Recorder: " +
1405 if (!result.isEmpty())
1408 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1409 tokens = result.split(
':', QString::SkipEmptyParts);
1411 tokens = result.split(
':', Qt::SkipEmptyParts);
1415 if (tokens.size() > 1 && tokens[0].toUInt() >=
m_serialNo)
1421 if (tokens[0].startsWith(
"ERR"))
1425 tokens.removeFirst();
1426 result = tokens.join(
':');
1427 bool err = (tokens.size() > 1 && tokens[1].startsWith(
"ERR"));
1428 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + raw);
1432 tokens.removeFirst();
1433 result = tokens.join(
':');
1441 LOG(VB_RECORD, LOG_ERR,
LOC +
1442 QString(
"ProcessVer2: Giving up waiting for response for "
1443 "command '%2'").arg(cmd));
1445 else if (tokens.size() < 2)
1447 LOG(VB_RECORD, LOG_ERR,
LOC +
1448 QString(
"Did not receive a valid response "
1449 "for command '%1', received '%2'").arg(cmd, result));
1453 LOG(VB_RECORD, LOG_ERR,
LOC +
1454 QString(
"ProcessVer2: Looking for serial no %1, "
1455 "but received %2 for command '%2'")
1456 .arg(QString::number(
m_serialNo), tokens[0], cmd));
1460 tokens.removeFirst();
1461 status = tokens[0].trimmed();
1462 result = tokens.join(
':');
1464 bool okay = (status ==
"OK");
1465 if (okay || status.startsWith(
"WARN") || status.startsWith(
"ERR"))
1467 LogLevel_t level = LOG_INFO;
1471 level = LOG_WARNING;
1472 else if (command.startsWith(
"SendBytes") ||
1473 (command.startsWith(
"TuneStatus") &&
1474 result ==
"OK:InProgress"))
1477 LOG(VB_RECORD, level,
1478 LOC + QString(
"ProcessV2('%1') = '%2' took %3ms %4")
1479 .arg(cmd, result, QString::number(timer.
elapsed().count()),
1480 okay ?
"" :
"<-- NOTE"));
1484 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1485 QString(
"External Recorder invalid response to '%1': '%2'")
1491 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1509 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1515 LOG(VB_GENERAL, LOG_ERR,
"External Recorder in bad state: " +
1523 if (!result.isEmpty())
1527 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1528 QStringList tokens = result.split(
':', QString::SkipEmptyParts);
1530 QStringList tokens = result.split(
':', Qt::SkipEmptyParts);
1533 tokens.removeFirst();
1534 result = tokens.join(
':');
1535 for (
int idx = 1; idx < tokens.size(); ++idx)
1536 err |= tokens[idx].startsWith(
"ERR");
1539 err |= result.startsWith(
"STATUS:ERR");
1541 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + result);
1544 while (!result.isEmpty());