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 m_args = path.split(
' ',Qt::SkipEmptyParts) +
550 if (!
m_args.contains(
"--quiet") && !
m_args.contains(
"-q"))
553 m_args <<
"--inputid" << QString::number(majorid);
554 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"args \"%1\"")
559 LOG(VB_GENERAL, LOG_ERR,
LOC +
560 QString(
"Failed to start %1").arg(
m_device));
577 uint restart_cnt = 0;
581 bool good_data =
false;
582 uint data_proc_err = 0;
583 uint data_short_err = 0;
587 LOG(VB_GENERAL, LOG_ERR,
LOC +
588 QString(
"%1 is not running.").arg(
m_device));
591 status_timer.
start();
595 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): begin");
600 ready_cmd =
"SendBytes";
609 LOG(VB_RECORD, LOG_WARNING,
LOC +
"TS not open yet.");
610 std::this_thread::sleep_for(10ms);
616 std::this_thread::sleep_for(10ms);
626 LOG(VB_RECORD, LOG_WARNING,
LOC +
627 "Internal buffer too full to accept more data from "
628 "external application.");
634 if (result.startsWith(
"ERR"))
636 LOG(VB_GENERAL, LOG_ERR,
LOC +
637 QString(
"Aborting: %1 -> %2")
638 .arg(ready_cmd, result));
644 std::this_thread::sleep_for(20s);
647 LOG(VB_RECORD, LOG_ERR,
LOC +
648 "Failed to restart stream.");
659 if (status_timer.
elapsed() >= 2s)
667 std::this_thread::sleep_for(20s);
670 LOG(VB_RECORD, LOG_ERR,
LOC +
"Failed to restart stream.");
687 if (result.startsWith(
"ERR"))
689 LOG(VB_GENERAL, LOG_ERR,
LOC +
690 QString(
"Aborting: XOFF -> %2")
704 read_len =
m_io->
Read(buffer, sz, 100ms);
713 nodata_timer.
start();
716 if (nodata_timer.
elapsed() >= 50s)
718 LOG(VB_GENERAL, LOG_WARNING,
LOC +
719 "No data for 50 seconds, Restarting stream.");
722 LOG(VB_RECORD, LOG_ERR,
LOC +
723 "Failed to restart stream.");
731 std::this_thread::sleep_for(50ms);
745 LOG(VB_GENERAL, LOG_ERR,
LOC +
"I/O thread has disappeared!");
751 LOG(VB_GENERAL, LOG_ERR,
LOC +
752 QString(
"Fatal Error from External Recorder: %1")
759 len = remainder = buffer.size();
766 if (
m_xon && data_short_err++ == 0)
767 LOG(VB_RECORD, LOG_INFO,
LOC +
"Waiting for a full TS packet.");
768 std::this_thread::sleep_for(50us);
773 if (data_short_err > 1)
775 LOG(VB_RECORD, LOG_INFO,
LOC +
776 QString(
"Waited for a full TS packet %1 times.")
777 .arg(data_short_err));
791 remainder = sit.key()->ProcessData
792 (
reinterpret_cast<const uint8_t *
>
793 (buffer.constData()), buffer.size());
804 LOG(VB_RECORD, LOG_WARNING,
LOC +
805 QString(
"Replay size truncated to %1 bytes")
815 good_data = (len != 0U);
817 else if (len > remainder)
819 buffer.remove(0, len - remainder);
820 good_data = (len != 0U);
822 else if (len == remainder)
829 if (data_proc_err > 1)
831 LOG(VB_RECORD, LOG_WARNING,
LOC +
832 QString(
"Failed to process the data received %1 times.")
833 .arg(data_proc_err));
840 if (data_proc_err++ == 0)
842 LOG(VB_RECORD, LOG_WARNING,
LOC +
843 "Failed to process the data received");
848 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
849 QString(
"%1 shutdown").arg(
m_bError ?
"Error" :
"Normal"));
854 LOG(VB_RECORD, LOG_INFO,
LOC +
"run(): " +
"end");
865 QStringList tokens = result.split(
':', Qt::SkipEmptyParts);
866 if (tokens.size() > 1)
871 LOG(VB_RECORD, LOG_ERR,
LOC +
872 QString(
"Bad response to 'APIVersion?' - '%1'. "
873 "Expecting 1 or 2").arg(result));
891 m_loc = result.mid(3);
906 LOG(VB_RECORD, LOG_WARNING,
LOC +
"OpenApp: already open!");
914 LOG(VB_GENERAL, LOG_ERR,
LOC +
"ExternIO failed: " +
ENO);
919 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Spawn '%1'").arg(
m_device));
923 LOG(VB_GENERAL, LOG_ERR,
945 LOG(VB_RECORD, LOG_ERR,
LOC +
"Application is not responding.");
955 LOG(VB_RECORD, LOG_ERR,
LOC +
956 QString(
"Bad response to 'HasTuner?' - '%1'").arg(result));
964 LOG(VB_RECORD, LOG_ERR,
LOC +
965 QString(
"Bad response to 'HasPictureAttributes?' - '%1'")
974 result.startsWith(
"OK:Poll");
976 LOG(VB_RECORD, LOG_INFO,
LOC +
"App opened successfully");
977 LOG(VB_RECORD, LOG_INFO,
LOC +
978 QString(
"Capabilities: tuner(%1) "
979 "Picture attributes(%2) "
996 LOG(VB_RECORD, LOG_WARNING,
LOC +
997 "WARNING: Unable to communicate with external app.");
1026 LOG(VB_RECORD, LOG_INFO,
LOC +
"CloseRecorder");
1031 if (!result.startsWith(
"OK"))
1033 LOG(VB_RECORD, LOG_INFO,
LOC +
1034 "CloseRecorder failed, sending kill.");
1036 QString full_command = QString(
"%1").arg(
m_args.join(
" "));
1041 std::this_thread::sleep_for(50ms);
1044 LOG(VB_GENERAL, LOG_ERR,
1045 QString(
"Unable to kill existing '%1'.")
1046 .arg(full_command));
1061 LOG(VB_RECORD, LOG_INFO,
LOC +
"Restarting stream.");
1066 std::this_thread::sleep_for(1s);
1098 sit.key()->ProcessData(
reinterpret_cast<const uint8_t *
>
1103 LOG(VB_RECORD, LOG_INFO,
LOC + QString(
"Replayed %1 bytes")
1125 LOG(VB_RECORD, LOG_INFO,
LOC +
1126 QString(
"StartStreaming with %1 current listeners")
1131 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1139 LogLevel_t level = LOG_ERR;
1140 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1141 level = LOG_WARNING;
1145 LOG(VB_GENERAL, level,
LOC + QString(
"StartStreaming failed: '%1'")
1151 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming started");
1154 LOG(VB_RECORD, LOG_INFO,
LOC +
"Already streaming");
1158 LOG(VB_RECORD, LOG_INFO,
LOC +
1159 QString(
"StartStreaming %1 listeners")
1169 LOG(VB_RECORD, LOG_INFO,
LOC +
1170 QString(
"StopStreaming %1 listeners")
1175 LOG(VB_RECORD, LOG_INFO,
LOC +
1176 "StopStreaming requested, but we are not streaming!");
1182 LOG(VB_RECORD, LOG_INFO,
LOC +
1183 QString(
"StopStreaming delayed, still have %1 listeners")
1188 LOG(VB_RECORD, LOG_INFO,
LOC +
"StopStreaming");
1199 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder not started.");
1206 LogLevel_t level = LOG_ERR;
1207 if (result.startsWith(
"warn", Qt::CaseInsensitive))
1208 level = LOG_WARNING;
1212 LOG(VB_GENERAL, level,
LOC + QString(
"StopStreaming: '%1'")
1219 LOG(VB_RECORD, LOG_INFO,
LOC +
"Streaming stopped");
1226 std::chrono::milliseconds
timeout,
1236 LOG(VB_RECORD, LOG_ERR,
LOC +
1237 QString(
"Invalid API version %1. Expected 1 or 2").arg(
m_apiVersion));
1243 std::chrono::milliseconds
timeout,
1246 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer1('%1')")
1249 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1255 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1259 QByteArray buf(cmd.toUtf8(), cmd.size());
1264 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1282 LOG(VB_GENERAL, LOG_ERR,
LOC +
1283 "Failed to read from External Recorder: " +
1290 if (result.startsWith(
"STATUS:ERR") ||
1291 result.startsWith(
"0:STATUS:ERR"))
1293 LOG(VB_RECORD, LOG_ERR,
LOC + result);
1294 result.remove(0, result.indexOf(
":ERR") + 1);
1299 if (!result.startsWith(
"STATUS") && !result.startsWith(
"0:STATUS"))
1301 LOG(VB_RECORD, LOG_INFO,
LOC +
1302 QString(
"Ignoring response '%1'").arg(result));
1305 if (result.size() < 1)
1307 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1308 QString(
"External Recorder did not respond to '%1'").arg(cmd));
1312 bool okay = result.startsWith(
"OK");
1313 if (okay || result.startsWith(
"WARN") || result.startsWith(
"ERR"))
1315 LogLevel_t level = LOG_INFO;
1319 level = LOG_WARNING;
1320 else if (cmd.startsWith(
"SendBytes"))
1323 LOG(VB_RECORD, level,
1324 LOC + QString(
"ProcessCommand('%1') = '%2' took %3ms %4")
1326 QString::number(timer.
elapsed().count()),
1327 okay ?
"" :
"<-- NOTE"));
1331 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1332 QString(
"External Recorder invalid response to '%1': '%2'")
1338 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1349 std::chrono::milliseconds
timeout,
1355 for (
uint cnt = 0; cnt < retry_cnt; ++cnt)
1357 QString cmd = QString(
"%1:%2").arg(++
m_serialNo).arg(command);
1359 LOG(VB_RECORD, LOG_DEBUG,
LOC + QString(
"ProcessVer2('%1') serial(%2)")
1366 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1370 QByteArray buf(cmd.toUtf8(), cmd.size());
1375 LOG(VB_GENERAL, LOG_ERR,
LOC +
"External Recorder in bad state: " +
1391 LOG(VB_GENERAL, LOG_ERR,
LOC +
1392 "Failed to read from External Recorder: " +
1398 if (!result.isEmpty())
1401 tokens = result.split(
':', Qt::SkipEmptyParts);
1404 if (tokens.size() > 1 && tokens[0].toUInt() >=
m_serialNo)
1410 if (tokens[0].startsWith(
"ERR"))
1414 tokens.removeFirst();
1415 result = tokens.join(
':');
1416 bool err = (tokens.size() > 1 && tokens[1].startsWith(
"ERR"));
1417 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + raw);
1421 tokens.removeFirst();
1422 result = tokens.join(
':');
1430 LOG(VB_RECORD, LOG_ERR,
LOC +
1431 QString(
"ProcessVer2: Giving up waiting for response for "
1432 "command '%2'").arg(cmd));
1434 else if (tokens.size() < 2)
1436 LOG(VB_RECORD, LOG_ERR,
LOC +
1437 QString(
"Did not receive a valid response "
1438 "for command '%1', received '%2'").arg(cmd, result));
1442 LOG(VB_RECORD, LOG_ERR,
LOC +
1443 QString(
"ProcessVer2: Looking for serial no %1, "
1444 "but received %2 for command '%2'")
1445 .arg(QString::number(
m_serialNo), tokens[0], cmd));
1449 tokens.removeFirst();
1450 status = tokens[0].trimmed();
1451 result = tokens.join(
':');
1453 bool okay = (status ==
"OK");
1454 if (okay || status.startsWith(
"WARN") || status.startsWith(
"ERR"))
1456 LogLevel_t level = LOG_INFO;
1460 level = LOG_WARNING;
1461 else if (command.startsWith(
"SendBytes") ||
1462 (command.startsWith(
"TuneStatus") &&
1463 result ==
"OK:InProgress"))
1466 LOG(VB_RECORD, level,
1467 LOC + QString(
"ProcessV2('%1') = '%2' took %3ms %4")
1468 .arg(cmd, result, QString::number(timer.
elapsed().count()),
1469 okay ?
"" :
"<-- NOTE"));
1473 LOG(VB_GENERAL, LOG_WARNING,
LOC +
1474 QString(
"External Recorder invalid response to '%1': '%2'")
1480 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Too many I/O errors.");
1498 LOG(VB_RECORD, LOG_ERR,
LOC +
"External I/O not ready!");
1504 LOG(VB_GENERAL, LOG_ERR,
"External Recorder in bad state: " +
1512 if (!result.isEmpty())
1516 QStringList tokens = result.split(
':', Qt::SkipEmptyParts);
1517 tokens.removeFirst();
1518 result = tokens.join(
':');
1519 for (
int idx = 1; idx < tokens.size(); ++idx)
1520 err |= tokens[idx].startsWith(
"ERR");
1523 err |= result.startsWith(
"STATUS:ERR");
1525 LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO),
LOC + result);
1528 while (!result.isEmpty());