MythTV  master
mythsystemunix.cpp
Go to the documentation of this file.
1 
2 // Own header
3 #include "mythsystemlegacy.h"
4 #include "mythsystemunix.h"
5 #include "mythmiscutil.h"
6 
7 // compat header
8 #include "compat.h"
9 
10 // C++/C headers
11 #include <cerrno>
12 #include <csignal> // for kill()
13 #include <cstdlib>
14 #include <cstring> // for strerror()
15 #include <ctime>
16 #include <fcntl.h>
17 #include <iostream> // for cerr()
18 #include <sys/select.h>
19 #include <sys/wait.h>
20 #include <unistd.h>
21 
22 // QT headers
23 #include <QCoreApplication>
24 #include <QMutex>
25 #include <QMap>
26 #include <QString>
27 #include <QStringList>
28 
29 // libmythbase headers
30 #include "mythcorecontext.h"
31 #include "mythevent.h"
32 #include "exitcodes.h"
33 #include "mythlogging.h"
34 
35 #if !defined(__syscall_slong_t)
36 using __syscall_slong_t = long int;
37 #endif
38 // Run the IO handler ~100x per second (every 10ms), for ~3MBps throughput
39 static constexpr __syscall_slong_t kIOHandlerInterval {static_cast<__syscall_slong_t>(10)*1000*1000};
40 // Run the Signal handler ~20x per second (every 50ms).
41 static constexpr __syscall_slong_t kSignalHandlerInterval {static_cast<__syscall_slong_t>(50)*1000*1000};
42 
43 struct FDType_t
44 {
46  int m_type;
47 };
48 using FDMap_t = QMap<int, FDType_t*>;
49 
50 /**********************************
51  * MythSystemLegacyManager method defines
52  *********************************/
53 static bool run_system = true;
54 static MythSystemLegacyManager *manager = nullptr;
59 static QMutex listLock;
60 static FDMap_t fdMap;
61 static QMutex fdLock;
62 
63 static inline void CLOSE(int& fd)
64 {
65  if( fd < 0 )
66  return;
67  close(fd);
68  fdLock.lock();
69  delete fdMap.value(fd);
70  fdMap.remove(fd);
71  fdLock.unlock();
72  fd = -1;
73 }
74 
76 {
77  run_system = false;
78  if (manager)
79  manager->wait();
80  if (smanager)
81  smanager->wait();
82  if (readThread)
83  readThread->wait();
84  if (writeThread)
85  writeThread->wait();
86 }
87 
89 {
90  RunProlog();
91  LOG(VB_GENERAL, LOG_INFO, QString("Starting IO manager (%1)")
92  .arg(m_read ? "read" : "write"));
93 
94  m_pLock.lock();
95  BuildFDs();
96  m_pLock.unlock();
97 
98  while( run_system )
99  {
100  {
101  QMutexLocker locker(&m_pWaitLock);
102  m_pWait.wait(&m_pWaitLock);
103  }
104 
105  while( run_system )
106  {
107  struct timespec ts { 0, kIOHandlerInterval };
108  nanosleep(&ts, nullptr);
109  m_pLock.lock();
110  if( m_pMap.isEmpty() )
111  {
112  m_pLock.unlock();
113  break;
114  }
115 
116  timeval tv {0, 0};
117 
118  int retval = -1;
119  fd_set fds = m_fds;
120 
121  if( m_read )
122  retval = select(m_maxfd+1, &fds, nullptr, nullptr, &tv);
123  else
124  retval = select(m_maxfd+1, nullptr, &fds, nullptr, &tv);
125 
126  if( retval == -1 )
127  {
128  LOG(VB_SYSTEM, LOG_ERR,
129  QString("MythSystemLegacyIOHandler: select(%1, %2) failed: %3")
130  .arg(m_maxfd+1).arg(m_read).arg(strerror(errno)));
131 
132  }
133  else if( retval > 0 )
134  {
135  auto it = m_pMap.keyValueBegin();
136  while (it != m_pMap.keyValueEnd())
137  {
138  auto [fd, buffer] = *it;
139  ++it;
140  if( FD_ISSET(fd, &fds) )
141  {
142  if( m_read )
143  HandleRead(fd, buffer);
144  else
145  HandleWrite(fd, buffer);
146  }
147  }
148  }
149  m_pLock.unlock();
150  }
151  }
152 
153  RunEpilog();
154 }
155 
156 void MythSystemLegacyIOHandler::HandleRead(int fd, QBuffer *buff)
157 {
158  errno = 0;
159  int len = read(fd, m_readbuf.data(), m_readbuf.size());
160  if( len <= 0 )
161  {
162  if( errno != EAGAIN )
163  {
164  m_pMap.remove(fd);
165  BuildFDs();
166  }
167  }
168  else
169  {
170  buff->buffer().append(m_readbuf.data(), len);
171 
172  // Get the corresponding MythSystemLegacy instance, and the stdout/stderr
173  // type
174  fdLock.lock();
175  FDType_t *fdType = fdMap.value(fd);
176  fdLock.unlock();
177  if (fdType == nullptr)
178  return;
179 
180  // Emit the data ready signal (1 = stdout, 2 = stderr)
181  MythSystemLegacyUnix *ms = fdType->m_ms;
182  if (ms == nullptr)
183  return;
184  emit ms->readDataReady(fdType->m_type);
185  }
186 }
187 
188 void MythSystemLegacyIOHandler::HandleWrite(int fd, QBuffer *buff)
189 {
190  if( buff->atEnd() )
191  {
192  m_pMap.remove(fd);
193  BuildFDs();
194  return;
195  }
196 
197  int pos = buff->pos();
198  int len = buff->size() - pos;
199  len = (len > 32768 ? 32768 : len);
200 
201  int rlen = write(fd, buff->read(len).constData(), len);
202  if( rlen < 0 )
203  {
204  if( errno != EAGAIN )
205  {
206  m_pMap.remove(fd);
207  BuildFDs();
208  }
209  else
210  buff->seek(pos);
211  }
212  else if( rlen != len )
213  buff->seek(pos+rlen);
214 }
215 
216 void MythSystemLegacyIOHandler::insert(int fd, QBuffer *buff)
217 {
218  m_pLock.lock();
219  m_pMap.insert(fd, buff);
220  BuildFDs();
221  m_pLock.unlock();
222  wake();
223 }
224 
226 {
227  QMutexLocker locker(&m_pLock);
228  while (m_pMap.contains(fd))
229  {
230  locker.unlock();
231  usleep(10ms);
232  locker.relock();
233  }
234 }
235 
237 {
238  m_pLock.lock();
239  if (m_read)
240  {
241  PMap_t::iterator i;
242  i = m_pMap.find(fd);
243  if ( i != m_pMap.end() )
244  HandleRead(i.key(), i.value());
245  }
246  m_pMap.remove(fd);
247  BuildFDs();
248  m_pLock.unlock();
249 }
250 
252 {
253  QMutexLocker locker(&m_pWaitLock);
254  m_pWait.wakeAll();
255 }
256 
258 {
259  // build descriptor list
260  FD_ZERO(&m_fds); //NOLINT(readability-isolate-declaration)
261  m_maxfd = -1;
262 
263  PMap_t::iterator i;
264  for( i = m_pMap.begin(); i != m_pMap.end(); ++i )
265  {
266  FD_SET(i.key(), &m_fds);
267  m_maxfd = (i.key() > m_maxfd ? i.key() : m_maxfd);
268  }
269 }
270 
272 {
273  RunProlog();
274  LOG(VB_GENERAL, LOG_INFO, "Starting process manager");
275 
276  // run_system is set to false during shutdown, and we need this thread to
277  // exit during shutdown.
278  while( run_system )
279  {
280  m_mapLock.lock();
281  m_wait.wait(&m_mapLock, m_pMap.isEmpty() ? 100 : 20);
282 
283  // check for any running processes
284 
285  if( m_pMap.isEmpty() )
286  {
287  m_mapLock.unlock();
288  continue;
289  }
290  m_mapLock.unlock();
291 
292  pid_t pid = 0;
293  int status = 0;
294 
295  // check for any newly exited processes
296  listLock.lock();
297  while( (pid = waitpid(-1, &status, WNOHANG)) > 0 )
298  {
299  m_mapLock.lock();
300  // unmanaged process has exited
301  if( !m_pMap.contains(pid) )
302  {
303  LOG(VB_SYSTEM, LOG_INFO,
304  QString("Unmanaged child (PID: %1) has exited!") .arg(pid));
305  m_mapLock.unlock();
306  continue;
307  }
308 
309  // pop exited process off managed list, add to cleanup list
310  MythSystemLegacyUnix *ms = m_pMap.take(pid);
311  m_mapLock.unlock();
312 
313  // Occasionally, the caller has deleted the structure from under
314  // our feet. If so, just log and move on.
315  if (!ms || !ms->m_parent)
316  {
317  LOG(VB_SYSTEM, LOG_ERR,
318  QString("Structure for child PID %1 already deleted!")
319  .arg(pid));
320  if (ms)
321  msList.append(ms);
322  continue;
323  }
324 
325  msList.append(ms);
326 
327  // Deal with (primarily) Ubuntu which seems to consistently be
328  // screwing up and reporting the signalled case as an exit. This
329  // workaround will limit the valid exit value to 0 - 127. As all
330  // of our return values match that (other than the occasional 255)
331  // this shouldn't cause any issues
332  if (ms->m_parent->onlyLowExitVal() && WIFEXITED(status) &&
333  WEXITSTATUS(status) != 255 && WEXITSTATUS(status) & 0x80)
334  {
335  // Just byte swap the status and it seems to be correctly done
336  uint16_t oldstatus = status;
337  status = ((status & 0x00FF) << 8) | ((status & 0xFF00) >> 8);
338  LOG(VB_SYSTEM, LOG_INFO,
339  QString("Odd return value: swapping from %1 to %2")
340  .arg(oldstatus) .arg(status));
341  }
342 
343  // handle normal exit
344  if( WIFEXITED(status) )
345  {
346  ms->SetStatus(WEXITSTATUS(status));
347  LOG(VB_SYSTEM, LOG_INFO,
348  QString("Managed child (PID: %1) has exited! "
349  "command=%2, status=%3, result=%4")
350  .arg(pid) .arg(ms->GetLogCmd()) .arg(status)
351  .arg(ms->GetStatus()));
352  }
353 
354  // handle forced exit
355  else if( WIFSIGNALED(status) )
356  {
357  // Don't override a timed out process which gets killed, but
358  // otherwise set the return status appropriately
359  if (ms->GetStatus() != GENERIC_EXIT_TIMEOUT)
361 
362  int sig = WTERMSIG(status);
363  LOG(VB_SYSTEM, LOG_INFO,
364  QString("Managed child (PID: %1) has signalled! "
365  "command=%2, status=%3, result=%4, signal=%5")
366  .arg(pid) .arg(ms->GetLogCmd()) .arg(status)
367  .arg(ms->GetStatus()) .arg(sig));
368  }
369 
370  // handle abnormal exit (crash)
371  else
372  {
374  LOG(VB_SYSTEM, LOG_ERR,
375  QString("Managed child (PID: %1) has terminated! "
376  "command=%2, status=%3, result=%4")
377  .arg(pid) .arg(ms->GetLogCmd()) .arg(status)
378  .arg(ms->GetStatus()));
379  }
380  }
381 
382 
383  // loop through running processes for any that require action
384  MSMap_t::iterator i;
385  MSMap_t::iterator next;
386  auto now = SystemClock::now();
387 
388  m_mapLock.lock();
389  m_jumpLock.lock();
390  auto it = m_pMap.keyValueBegin();
391  while (it != m_pMap.keyValueEnd())
392  {
393  auto [pid2, ms] = *it;
394  ++it;
395  if (!ms)
396  continue;
397 
398  // handle processes beyond marked timeout
399  if( ms->m_timeout.time_since_epoch() > 0s && ms->m_timeout < now )
400  {
401  // issuing KILL signal after TERM failed in a timely manner
402  if( ms->GetStatus() == GENERIC_EXIT_TIMEOUT )
403  {
404  LOG(VB_SYSTEM, LOG_INFO,
405  QString("Managed child (PID: %1) timed out"
406  ", issuing KILL signal").arg(pid2));
407  // Prevent constant attempts to kill an obstinate child
408  ms->m_timeout = SystemTime(0s);
409  ms->Signal(SIGKILL);
410  }
411 
412  // issuing TERM signal
413  else
414  {
415  LOG(VB_SYSTEM, LOG_INFO,
416  QString("Managed child (PID: %1) timed out"
417  ", issuing TERM signal").arg(pid2));
418  ms->SetStatus( GENERIC_EXIT_TIMEOUT );
419  ms->m_timeout = now + 1s;
420  ms->Term();
421  }
422  }
423 
424  if ( m_jumpAbort && ms->GetSetting("AbortOnJump") )
425  ms->Term();
426  }
427 
428  m_jumpAbort = false;
429  m_jumpLock.unlock();
430 
431  m_mapLock.unlock();
432 
433  // hold off unlocking until all the way down here to
434  // give the buffer handling a chance to run before
435  // being closed down by signal thread
436  listLock.unlock();
437  }
438 
439  // kick to allow them to close themselves cleanly
440  if (readThread)
441  readThread->wake();
442  if (writeThread)
443  writeThread->wake();
444 
445  RunEpilog();
446 }
447 
449 {
450  m_mapLock.lock();
451  ms->IncrRef();
452  m_pMap.insert(ms->m_pid, ms);
453  m_wait.wakeAll();
454  m_mapLock.unlock();
455 
456  if (ms->m_stdpipe[0] >= 0)
457  {
458  QByteArray ba = ms->GetBuffer(0)->data();
459  QBuffer wtb(&ba);
460  wtb.open(QIODevice::ReadOnly);
461  writeThread->insert(ms->m_stdpipe[0], &wtb);
462  writeThread->Wait(ms->m_stdpipe[0]);
463  writeThread->remove(ms->m_stdpipe[0]);
464  CLOSE(ms->m_stdpipe[0]);
465  }
466 
467  if( ms->GetSetting("UseStdout") )
468  {
469  auto *fdType = new FDType_t;
470  fdType->m_ms = ms;
471  fdType->m_type = 1;
472  fdLock.lock();
473  fdMap.insert( ms->m_stdpipe[1], fdType );
474  fdLock.unlock();
475  readThread->insert(ms->m_stdpipe[1], ms->GetBuffer(1));
476  }
477 
478  if( ms->GetSetting("UseStderr") )
479  {
480  auto *fdType = new FDType_t;
481  fdType->m_ms = ms;
482  fdType->m_type = 2;
483  fdLock.lock();
484  fdMap.insert( ms->m_stdpipe[2], fdType );
485  fdLock.unlock();
486  readThread->insert(ms->m_stdpipe[2], ms->GetBuffer(2));
487  }
488 }
489 
491 {
492  m_jumpLock.lock();
493  m_jumpAbort = true;
494  m_jumpLock.unlock();
495 }
496 
498 {
499  RunProlog();
500  LOG(VB_GENERAL, LOG_INFO, "Starting process signal handler");
501  while (run_system)
502  {
503  struct timespec ts {0, kSignalHandlerInterval};
504  nanosleep(&ts, nullptr);
505 
506  while (run_system)
507  {
508  // handle cleanup and signalling for closed processes
509  listLock.lock();
510  if (msList.isEmpty())
511  {
512  listLock.unlock();
513  break;
514  }
515  MythSystemLegacyUnix *ms = msList.takeFirst();
516  listLock.unlock();
517 
518  // This can happen if it has been deleted already
519  if (!ms)
520  continue;
521 
522  if (ms->m_parent)
523  {
524  ms->m_parent->HandlePostRun();
525  }
526 
527  if (ms->m_stdpipe[0] >= 0)
528  writeThread->remove(ms->m_stdpipe[0]);
529  CLOSE(ms->m_stdpipe[0]);
530 
531  if (ms->m_stdpipe[1] >= 0)
532  readThread->remove(ms->m_stdpipe[1]);
533  CLOSE(ms->m_stdpipe[1]);
534 
535  if (ms->m_stdpipe[2] >= 0)
536  readThread->remove(ms->m_stdpipe[2]);
537  CLOSE(ms->m_stdpipe[2]);
538 
539  if (ms->m_parent)
540  {
541  if (ms->GetStatus() == GENERIC_EXIT_OK)
542  emit ms->finished();
543  else
544  emit ms->error(ms->GetStatus());
545 
546  ms->disconnect();
547  ms->Unlock();
548  }
549 
550  ms->DecrRef();
551  }
552  }
553  RunEpilog();
554 }
555 
556 /*******************************
557  * MythSystemLegacy method defines
558  ******************************/
559 
561  MythSystemLegacyPrivate("MythSystemLegacyUnix")
562 {
563  m_parent = parent;
564 
570 
571  // Start the threads if they haven't been started yet.
572  if( manager == nullptr )
573  {
575  manager->start();
576  }
577 
578  if( smanager == nullptr )
579  {
581  smanager->start();
582  }
583 
584  if( readThread == nullptr )
585  {
587  readThread->start();
588  }
589 
590  if( writeThread == nullptr )
591  {
593  writeThread->start();
594  }
595 }
596 
597 bool MythSystemLegacyUnix::ParseShell(const QString &cmd, QString &abscmd,
598  QStringList &args)
599 {
600  QList<QChar> whitespace; whitespace << ' ' << '\t' << '\n' << '\r';
601  QList<QChar> whitechr; whitechr << 't' << 'n' << 'r';
602  QChar quote = '"';
603  QChar hardquote = '\'';
604  QChar escape = '\\';
605  bool quoted = false;
606  bool hardquoted = false;
607  bool escaped = false;
608 
609  QString tmp;
610  QString::const_iterator i = cmd.begin();
611  while (i != cmd.end())
612  {
613  if (quoted || hardquoted)
614  {
615  if (escaped)
616  {
617  if ((quote == *i) || (escape == *i) ||
618  whitespace.contains(*i))
619  {
620  // pass through escape (\), quote ("), and any whitespace
621  tmp += *i;
622  }
623  else if (whitechr.contains(*i))
624  {
625  // process whitespace escape code, and pass character
626  tmp += whitespace[whitechr.indexOf(*i)+1];
627  }
628  else
629  {
630  // unhandled escape code, abort
631  return false;
632  }
633 
634  escaped = false;
635  }
636 
637  else if (*i == escape)
638  {
639  if (hardquoted)
640  {
641  // hard quotes (') pass everything
642  tmp += *i;
643  }
644  else
645  {
646  // otherwise, mark escaped to handle next character
647  escaped = true;
648  }
649  }
650 
651  else if ((quoted && (*i == quote)) ||
652  (hardquoted && (*i == hardquote)))
653  {
654  // end of quoted sequence
655  quoted = hardquoted = false;
656  }
657  else
658  {
659  // pass through character
660  tmp += *i;
661  }
662  }
663 
664  else if (escaped)
665  {
666  if ((*i == quote) || (*i == hardquote) || (*i == escape) ||
667  whitespace.contains(*i))
668  {
669  // pass through special characters
670  tmp += *i;
671  }
672  else if (whitechr.contains(*i))
673  {
674  // process whitespace escape code, and pass character
675  tmp += whitespace[whitechr.indexOf(*i)+1];
676  }
677  else
678  {
679  // unhandled escape code, abort
680  return false;
681  }
682 
683  escaped = false;
684  }
685 
686  // handle quotes and escape characters
687  else if (quote == *i)
688  quoted = true;
689  else if (hardquote == *i)
690  hardquoted = true;
691  else if (escape == *i)
692  {
693  escaped = true;
694  }
695 
696  // handle whitespace characters
697  else if (whitespace.contains(*i) && !tmp.isEmpty())
698  {
699  args << tmp;
700  tmp.clear();
701  }
702 
703  else
704  // pass everything else
705  tmp += *i;
706 
707  // step forward to next character
708  ++i;
709  }
710 
711  if (quoted || hardquoted || escaped)
712  // command not terminated cleanly
713  return false;
714 
715  if (!tmp.isEmpty())
716  // collect last argument
717  args << tmp;
718 
719  if (args.isEmpty())
720  // this shouldnt happen
721  return false;
722 
723  // grab the first argument to use as the command
724  abscmd = args.takeFirst();
725  if (!abscmd.startsWith('/'))
726  {
727  // search for absolute path
728  QStringList path = qEnvironmentVariable("PATH").split(':');
729  for (const auto& pit : qAsConst(path))
730  {
731  QFile file(QString("%1/%2").arg(pit, abscmd));
732  if (file.exists())
733  {
734  abscmd = file.fileName();
735  break;
736  }
737  }
738  }
739 
740  return true;
741 }
742 
744 {
745  int status = GetStatus();
746  if( (status != GENERIC_EXIT_RUNNING && status != GENERIC_EXIT_TIMEOUT) ||
747  (m_pid <= 0) )
748  {
749  LOG(VB_GENERAL, LOG_DEBUG, QString("Terminate skipped. Status: %1")
750  .arg(status));
751  return;
752  }
753 
754  Signal(SIGTERM);
755  if( force )
756  {
757  // send KILL if it does not exit within one second
758  if( m_parent->Wait(1s) == GENERIC_EXIT_RUNNING )
759  Signal(SIGKILL);
760  }
761 }
762 
764 {
765  int status = GetStatus();
766  if( (status != GENERIC_EXIT_RUNNING && status != GENERIC_EXIT_TIMEOUT) ||
767  (m_pid <= 0) )
768  {
769  LOG(VB_GENERAL, LOG_DEBUG, QString("Signal skipped. Status: %1")
770  .arg(status));
771  return;
772  }
773 
774  LOG(VB_GENERAL, LOG_INFO, QString("Child PID %1 killed with %2")
775  .arg(m_pid).arg(strsignal(sig)));
776  kill(m_pid, sig);
777 }
778 
779 void MythSystemLegacyUnix::Fork(std::chrono::seconds timeout)
780 {
781  QString LOC_ERR = QString("myth_system('%1'): Error: ").arg(GetLogCmd());
782 
783  // For use in the child
784  std::string locerr = qPrintable(LOC_ERR);
785 
786  LOG(VB_SYSTEM, LOG_DEBUG, QString("Launching: %1").arg(GetLogCmd()));
787 
788  std::array<int,2> p_stdin {-1,-1};
789  std::array<int,2> p_stdout {-1,-1};
790  std::array<int,2> p_stderr {-1,-1};
791 
792  /* set up pipes */
793  if( GetSetting("UseStdin") )
794  {
795  if( pipe(p_stdin.data()) == -1 )
796  {
797  LOG(VB_SYSTEM, LOG_ERR, LOC_ERR + "stdin pipe() failed");
799  }
800  else
801  {
802  int flags = fcntl(p_stdin[1], F_GETFL, 0);
803  if (flags == -1)
804  {
805  LOG(VB_SYSTEM, LOG_ERR, LOC_ERR +
806  "fcntl on stdin pipe getting flags failed" +
807  ENO);
808  }
809  else
810  {
811  flags |= O_NONBLOCK;
812  if(fcntl(p_stdin[1], F_SETFL, flags) == -1)
813  {
814  LOG(VB_SYSTEM, LOG_ERR, LOC_ERR +
815  "fcntl on stdin pipe setting non-blocking failed" +
816  ENO);
817  }
818  }
819  }
820  }
821  if( GetSetting("UseStdout") )
822  {
823  if( pipe(p_stdout.data()) == -1 )
824  {
825  LOG(VB_SYSTEM, LOG_ERR, LOC_ERR + "stdout pipe() failed");
827  }
828  else
829  {
830  int flags = fcntl(p_stdout[0], F_GETFL, 0);
831  if (flags == -1)
832  {
833  LOG(VB_SYSTEM, LOG_ERR, LOC_ERR +
834  "fcntl on stdout pipe getting flags failed" +
835  ENO);
836  }
837  else
838  {
839  flags |= O_NONBLOCK;
840  if(fcntl(p_stdout[0], F_SETFL, flags) == -1)
841  {
842  LOG(VB_SYSTEM, LOG_ERR, LOC_ERR +
843  "fcntl on stdout pipe setting non-blocking failed" +
844  ENO);
845  }
846  }
847  }
848  }
849  if( GetSetting("UseStderr") )
850  {
851  if( pipe(p_stderr.data()) == -1 )
852  {
853  LOG(VB_SYSTEM, LOG_ERR, LOC_ERR + "stderr pipe() failed");
855  }
856  else
857  {
858  int flags = fcntl(p_stderr[0], F_GETFL, 0);
859  if (flags == -1)
860  {
861  LOG(VB_SYSTEM, LOG_ERR, LOC_ERR +
862  "fcntl on stderr pipe getting flags failed" +
863  ENO);
864  }
865  else
866  {
867  flags |= O_NONBLOCK;
868  if(fcntl(p_stderr[0], F_SETFL, flags) == -1)
869  {
870  LOG(VB_SYSTEM, LOG_ERR, LOC_ERR +
871  "fcntl on stderr pipe setting non-blocking failed" +
872  ENO);
873  }
874  }
875  }
876  }
877 
878  // set up command args
879  if (GetSetting("UseShell"))
880  {
881  QStringList args = QStringList("-c");
882  args << GetCommand() + " " + GetArgs().join(" ");
883  SetArgs( args );
884  QString cmd = "/bin/sh";
885  SetCommand( cmd );
886  }
887  QStringList args = GetArgs();
888  args.prepend(GetCommand().split('/').last());
889  SetArgs( args );
890 
891  QByteArray cmdUTF8 = GetCommand().toUtf8();
892  char *command = strdup(cmdUTF8.constData());
893 
894  char **cmdargs = (char **)malloc((args.size() + 1) * sizeof(char *));
895 
896  if (cmdargs)
897  {
898  int i = 0;
899  for (auto it = args.constBegin(); it != args.constEnd(); ++it)
900  {
901  cmdargs[i++] = strdup(it->toUtf8().constData());
902  }
903  cmdargs[i] = (char *)nullptr;
904  }
905  else
906  {
907  LOG(VB_GENERAL, LOG_CRIT, LOC_ERR +
908  "Failed to allocate memory for cmdargs " +
909  ENO);
910  free(command);
911  return;
912  }
913 
914  char *directory = nullptr;
915  QString dir = GetDirectory();
916  if (GetSetting("SetDirectory") && !dir.isEmpty())
917  directory = strdup(dir.toUtf8().constData());
918 
919  int niceval = m_parent->GetNice();
920  int ioprioval = m_parent->GetIOPrio();
921 
922  /* Do this before forking in case the child miserably fails */
923  m_timeout = ( timeout != 0s )
924  ? SystemClock::now() + timeout
925  : SystemClock::time_point();
926 
927  listLock.lock();
928  pid_t child = fork();
929 
930  if (child < 0)
931  {
932  /* Fork failed, still in parent */
933  LOG(VB_SYSTEM, LOG_ERR, "fork() failed: " + ENO);
935  listLock.unlock();
936  }
937  else if( child > 0 )
938  {
939  /* parent */
940  m_pid = child;
942 
943  LOG(VB_SYSTEM, LOG_INFO,
944  QString("Managed child (PID: %1) has started! "
945  "%2%3 command=%4, timeout=%5")
946  .arg(QString::number(m_pid),
947  GetSetting("UseShell") ? "*" : "",
948  GetSetting("RunInBackground") ? "&" : "",
949  GetLogCmd(),
950  QString::number(timeout.count())));
951 
952  /* close unused pipe ends */
953  if (p_stdin[0] >= 0)
954  close(p_stdin[0]);
955  if (p_stdout[1] >= 0)
956  close(p_stdout[1]);
957  if (p_stderr[1] >= 0)
958  close(p_stderr[1]);
959 
960  // store the rest
961  m_stdpipe[0] = p_stdin[1];
962  m_stdpipe[1] = p_stdout[0];
963  m_stdpipe[2] = p_stderr[0];
964 
965  if( manager == nullptr )
966  {
968  manager->start();
969  }
970  manager->append(this);
971 
972  listLock.unlock();
973  }
974  else if (child == 0)
975  {
976  /* Child - NOTE: it is not safe to use LOG or QString between the
977  * fork and execv calls in the child. It causes occasional locking
978  * issues that cause deadlocked child processes. */
979 
980  /* handle standard input */
981  if( p_stdin[0] >= 0 )
982  {
983  /* try to attach stdin to input pipe - failure is fatal */
984  if( dup2(p_stdin[0], 0) < 0 )
985  {
986  std::cerr << locerr
987  << "Cannot redirect input pipe to standard input: "
988  << strerror(errno) << std::endl;
990  }
991  }
992  else
993  {
994  /* try to attach stdin to /dev/null */
995  int fd = open("/dev/null", O_RDONLY);
996  if( fd >= 0 )
997  {
998  if( dup2(fd, 0) < 0)
999  {
1000  std::cerr << locerr
1001  << "Cannot redirect /dev/null to standard input,"
1002  "\n\t\t\tfailed to duplicate file descriptor: "
1003  << strerror(errno) << std::endl;
1004  }
1005  if (fd != 0) // if fd was zero, do not close
1006  {
1007  if (close(fd) < 0)
1008  {
1009  std::cerr << locerr
1010  << "Unable to close stdin redirect /dev/null: "
1011  << strerror(errno) << std::endl;
1012  }
1013  }
1014  }
1015  else
1016  {
1017  std::cerr << locerr
1018  << "Cannot redirect /dev/null to standard input, "
1019  "failed to open: "
1020  << strerror(errno) << std::endl;
1021  }
1022  }
1023 
1024  /* handle standard output */
1025  if( p_stdout[1] >= 0 )
1026  {
1027  /* try to attach stdout to output pipe - failure is fatal */
1028  if( dup2(p_stdout[1], 1) < 0)
1029  {
1030  std::cerr << locerr
1031  << "Cannot redirect output pipe to standard output: "
1032  << strerror(errno) << std::endl;
1034  }
1035  }
1036  else
1037  {
1038  /* We aren't sucking this down, redirect stdout to /dev/null */
1039  int fd = open("/dev/null", O_WRONLY);
1040  if( fd >= 0 )
1041  {
1042  if( dup2(fd, 1) < 0)
1043  {
1044  std::cerr << locerr
1045  << "Cannot redirect standard output to /dev/null,"
1046  "\n\t\t\tfailed to duplicate file descriptor: "
1047  << strerror(errno) << std::endl;
1048  }
1049  if (fd != 1) // if fd was one, do not close
1050  {
1051  if (close(fd) < 0)
1052  {
1053  std::cerr << locerr
1054  << "Unable to close stdout redirect /dev/null: "
1055  << strerror(errno) << std::endl;
1056  }
1057  }
1058  }
1059  else
1060  {
1061  std::cerr << locerr
1062  << "Cannot redirect standard output to /dev/null, "
1063  "failed to open: "
1064  << strerror(errno) << std::endl;
1065  }
1066  }
1067 
1068  /* handle standard err */
1069  if( p_stderr[1] >= 0 )
1070  {
1071  /* try to attach stderr to error pipe - failure is fatal */
1072  if( dup2(p_stderr[1], 2) < 0)
1073  {
1074  std::cerr << locerr
1075  << "Cannot redirect error pipe to standard error: "
1076  << strerror(errno) << std::endl;
1078  }
1079  }
1080  else
1081  {
1082  /* We aren't sucking this down, redirect stderr to /dev/null */
1083  int fd = open("/dev/null", O_WRONLY);
1084  if( fd >= 0 )
1085  {
1086  if( dup2(fd, 2) < 0)
1087  {
1088  std::cerr << locerr
1089  << "Cannot redirect standard error to /dev/null,"
1090  "\n\t\t\tfailed to duplicate file descriptor: "
1091  << strerror(errno) << std::endl;
1092  }
1093  if (fd != 2) // if fd was two, do not close
1094  {
1095  if (close(fd) < 0)
1096  {
1097  std::cerr << locerr
1098  << "Unable to close stderr redirect /dev/null: "
1099  << strerror(errno) << std::endl;
1100  }
1101  }
1102  }
1103  else
1104  {
1105  std::cerr << locerr
1106  << "Cannot redirect standard error to /dev/null, "
1107  "failed to open: "
1108  << strerror(errno) << std::endl;
1109  }
1110  }
1111 
1112  /* Close all open file descriptors except stdin/stdout/stderr */
1113  for( int fd = sysconf(_SC_OPEN_MAX) - 1; fd > 2; fd-- )
1114  close(fd);
1115 
1116  /* set directory */
1117  if( directory && chdir(directory) < 0 )
1118  {
1119  std::cerr << locerr
1120  << "chdir() failed: "
1121  << strerror(errno) << std::endl;
1122  }
1123 
1124  /* Set nice and ioprio values if non-default */
1125  if (niceval)
1126  myth_nice(niceval);
1127  if (ioprioval)
1128  myth_ioprio(ioprioval);
1129 
1130  /* run command */
1131  if( execv(command, cmdargs) < 0 )
1132  {
1133  // Can't use LOG due to locking fun.
1134  std::cerr << locerr
1135  << "execv() failed: "
1136  << strerror(errno) << std::endl;
1137  }
1138 
1139  /* Failed to exec */
1140  _exit(GENERIC_EXIT_DAEMONIZING_ERROR); // this exit is ok
1141  }
1142 
1143  /* Parent */
1144 
1145  // clean up the memory use
1146  free(command);
1147 
1148  free(directory);
1149 
1150  if( cmdargs )
1151  {
1152  for (int i = 0; cmdargs[i]; i++)
1153  free( cmdargs[i] );
1154  free( cmdargs );
1155  }
1156 
1157  if( GetStatus() != GENERIC_EXIT_RUNNING )
1158  {
1159  CLOSE(p_stdin[0]);
1160  CLOSE(p_stdin[1]);
1161  CLOSE(p_stdout[0]);
1162  CLOSE(p_stdout[1]);
1163  CLOSE(p_stderr[0]);
1164  CLOSE(p_stderr[1]);
1165  }
1166 }
1167 
1169 {
1170 }
1171 
1173 {
1174  if( manager == nullptr )
1175  {
1177  manager->start();
1178  }
1179  manager->jumpAbort();
1180 }
1181 
1182 /*
1183  * vim:ts=4:sw=4:ai:et:si:sts=4
1184  */
WEXITSTATUS
#define WEXITSTATUS(w)
Definition: compat.h:193
manager
static MythSystemLegacyManager * manager
Definition: mythsystemunix.cpp:54
build_compdb.args
args
Definition: build_compdb.py:11
MythSystemLegacyUnix::m_stdpipe
std::array< int, 3 > m_stdpipe
Definition: mythsystemunix.h:118
mythevent.h
O_NONBLOCK
#define O_NONBLOCK
Definition: compat.h:337
MythSystemLegacyUnix::Manage
void Manage(void) override
Definition: mythsystemunix.cpp:1168
MythSystemLegacyUnix::m_timeout
SystemTime m_timeout
Definition: mythsystemunix.h:116
MythSystemLegacyIOHandler::Wait
void Wait(int fd)
Definition: mythsystemunix.cpp:225
MThread::start
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:73
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:103
MythSystemLegacyIOHandler::m_pLock
QMutex m_pLock
Definition: mythsystemunix.h:53
MythSystemLegacyManager::m_pMap
MSMap_t m_pMap
Definition: mythsystemunix.h:74
MythSystemLegacyManager
Definition: mythsystemunix.h:64
MythSystemLegacyUnix::MythSystemLegacyManager
friend class MythSystemLegacyManager
Definition: mythsystemunix.h:110
MythSystemLegacyUnix
Definition: mythsystemunix.h:92
ReferenceCounter::DecrRef
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
Definition: referencecounter.cpp:125
MythSystemLegacyUnix::MythSystemLegacySignalManager
friend class MythSystemLegacySignalManager
Definition: mythsystemunix.h:111
MythSystemLegacyUnix::m_pid
pid_t m_pid
Definition: mythsystemunix.h:115
MythSystemLegacy
Definition: mythsystemlegacy.h:67
MythSystemLegacyManager::m_mapLock
QMutex m_mapLock
Definition: mythsystemunix.h:75
WIFSIGNALED
#define WIFSIGNALED(w)
Definition: compat.h:191
smanager
static MythSystemLegacySignalManager * smanager
Definition: mythsystemunix.cpp:55
myth_ioprio
bool myth_ioprio(int)
Allows setting the I/O priority of the current process/thread.
Definition: mythmiscutil.cpp:748
MythSystemLegacyPrivate::SetCommand
void SetCommand(const QString &cmd)
Definition: mythsystemprivate.h:55
MythSystemLegacyPrivate
-*- Mode: c++ -*-
Definition: mythsystemprivate.h:21
MythSystemLegacyManager::run
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: mythsystemunix.cpp:271
GENERIC_EXIT_TIMEOUT
@ GENERIC_EXIT_TIMEOUT
Process timed out.
Definition: exitcodes.h:25
MThread::wait
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
discid.disc.read
def read(device=None, features=[])
Definition: disc.py:35
MythSystemLegacyPrivate::finished
void finished(void)
MythSystemLegacyPrivate::SetArgs
void SetArgs(const QStringList &args)
Definition: mythsystemprivate.h:61
SystemTime
std::chrono::time_point< SystemClock > SystemTime
Definition: mythchrono.h:67
MythSystemLegacyPrivate::SetStatus
void SetStatus(uint status)
Definition: mythsystemprivate.h:43
MythSystemLegacyUnix::JumpAbort
void JumpAbort(void) override
Definition: mythsystemunix.cpp:1172
MThread::usleep
static void usleep(std::chrono::microseconds time)
Definition: mthread.cpp:335
mythburn.write
def write(text, progress=True)
Definition: mythburn.py:308
MythSystemLegacy::finished
void finished(void)
MythSystemLegacyUnix::Fork
void Fork(std::chrono::seconds timeout) override
Definition: mythsystemunix.cpp:779
FDType_t::m_ms
MythSystemLegacyUnix * m_ms
Definition: mythsystemunix.cpp:45
MythSystemLegacyUnix::Signal
void Signal(int sig) override
Definition: mythsystemunix.cpp:763
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MThread::RunProlog
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
build_compdb.file
file
Definition: build_compdb.py:55
force
bool force
Definition: mythcommflag.cpp:70
WIFEXITED
#define WIFEXITED(w)
Definition: compat.h:190
MythSystemLegacyIOHandler::m_maxfd
int m_maxfd
Definition: mythsystemunix.h:57
GENERIC_EXIT_OK
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:11
GENERIC_EXIT_PIPE_FAILURE
@ GENERIC_EXIT_PIPE_FAILURE
Error creating I/O pipes.
Definition: exitcodes.h:27
MythSystemLegacySignalManager::run
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: mythsystemunix.cpp:497
close
#define close
Definition: compat.h:43
tmp
static guint32 * tmp
Definition: goom_core.cpp:26
mythsystemlegacy.h
mythlogging.h
MythSystemLegacyIOHandler
Definition: mythsystemunix.h:32
MSList_t
QList< QPointer< MythSystemLegacyUnix > > MSList_t
Definition: mythsystemunix.h:30
MythSystemLegacyPrivate::started
void started(void)
MythSystemLegacy::error
void error(uint status)
MythSystemLegacyUnix::ParseShell
bool ParseShell(const QString &cmd, QString &abscmd, QStringList &args) override
Definition: mythsystemunix.cpp:597
MythSystemLegacy::readDataReady
void readDataReady(int fd)
MythSystemLegacyManager::jumpAbort
void jumpAbort(void)
Definition: mythsystemunix.cpp:490
compat.h
__syscall_slong_t
long int __syscall_slong_t
Definition: mythsystemunix.cpp:36
run_system
static bool run_system
Definition: mythsystemunix.cpp:53
MythSystemLegacyUnix::MythSystemLegacyIOHandler
friend class MythSystemLegacyIOHandler
Definition: mythsystemunix.h:112
MythSystemLegacyManager::m_jumpLock
QMutex m_jumpLock
Definition: mythsystemunix.h:77
MythSystemLegacyIOHandler::m_fds
fd_set m_fds
Definition: mythsystemunix.h:56
MythSystemLegacyPrivate::GetLogCmd
QString & GetLogCmd(void)
Definition: mythsystemprivate.h:46
readThread
static MythSystemLegacyIOHandler * readThread
Definition: mythsystemunix.cpp:56
MythSystemLegacyManager::m_wait
QWaitCondition m_wait
Definition: mythsystemunix.h:78
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
MythSystemLegacyPrivate::Unlock
void Unlock(void)
Definition: mythsystemprivate.h:68
FDType_t::m_type
int m_type
Definition: mythsystemunix.cpp:46
GENERIC_EXIT_NOT_OK
@ GENERIC_EXIT_NOT_OK
Exited with error.
Definition: exitcodes.h:12
MythSystemLegacyIOHandler::HandleWrite
void HandleWrite(int fd, QBuffer *buff)
Definition: mythsystemunix.cpp:188
MythSystemLegacyPrivate::readDataReady
void readDataReady(int fd)
SIGKILL
#define SIGKILL
Definition: compat.h:134
MythSystemLegacy::started
void started(void)
GENERIC_EXIT_DAEMONIZING_ERROR
@ GENERIC_EXIT_DAEMONIZING_ERROR
Error daemonizing or execl.
Definition: exitcodes.h:29
MythSystemLegacyPrivate::GetArgs
QStringList & GetArgs(void)
Definition: mythsystemprivate.h:59
FDType_t
Definition: mythsystemunix.cpp:43
MythSystemLegacyUnix::Term
void Term(bool force=false) override
Definition: mythsystemunix.cpp:743
MythSystemLegacySignalManager
Definition: mythsystemunix.h:81
msList
static MSList_t msList
Definition: mythsystemunix.cpp:58
MythSystemLegacyIOHandler::m_read
bool m_read
Definition: mythsystemunix.h:58
MythSystemLegacyManager::append
void append(MythSystemLegacyUnix *ms)
Definition: mythsystemunix.cpp:448
fdLock
static QMutex fdLock
Definition: mythsystemunix.cpp:61
MythSystemLegacyIOHandler::BuildFDs
void BuildFDs()
Definition: mythsystemunix.cpp:257
MythSystemLegacyIOHandler::remove
void remove(int fd)
Definition: mythsystemunix.cpp:236
MythSystemLegacyPrivate::GetSetting
bool GetSetting(const char *setting)
Definition: mythsystemprivate.h:50
mythmiscutil.h
fdMap
static FDMap_t fdMap
Definition: mythsystemunix.cpp:60
MythSystemLegacyIOHandler::m_pWait
QWaitCondition m_pWait
Definition: mythsystemunix.h:52
mythcorecontext.h
MythSystemLegacyPrivate::GetBuffer
QBuffer * GetBuffer(int index)
Definition: mythsystemprivate.h:65
MythSystemLegacyIOHandler::wake
void wake()
Definition: mythsystemunix.cpp:251
MythSystemLegacyIOHandler::m_readbuf
std::array< char, 65536 > m_readbuf
Definition: mythsystemunix.h:59
ShutdownMythSystemLegacy
void ShutdownMythSystemLegacy(void)
Definition: mythsystemunix.cpp:75
kSignalHandlerInterval
static constexpr __syscall_slong_t kSignalHandlerInterval
Definition: mythsystemunix.cpp:41
MythSystemLegacyManager::m_jumpAbort
bool m_jumpAbort
Definition: mythsystemunix.h:76
GENERIC_EXIT_KILLED
@ GENERIC_EXIT_KILLED
Process killed or stopped.
Definition: exitcodes.h:24
CLOSE
static void CLOSE(int &fd)
Definition: mythsystemunix.cpp:63
MythSystemLegacyPrivate::error
void error(uint status)
mythsystemunix.h
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
MythSystemLegacyPrivate::GetStatus
uint GetStatus(void)
Definition: mythsystemprivate.h:42
MythSystemLegacyPrivate::GetDirectory
QString & GetDirectory(void)
Definition: mythsystemprivate.h:48
hardwareprofile.distros.shared.html.escape
def escape(tagless_text)
Definition: html.py:27
myth_nice
bool myth_nice(int val)
Definition: mythmiscutil.cpp:657
kIOHandlerInterval
static constexpr __syscall_slong_t kIOHandlerInterval
Definition: mythsystemunix.cpp:39
MythSystemLegacyIOHandler::insert
void insert(int fd, QBuffer *buff)
Definition: mythsystemunix.cpp:216
WTERMSIG
#define WTERMSIG(w)
Definition: compat.h:194
writeThread
static MythSystemLegacyIOHandler * writeThread
Definition: mythsystemunix.cpp:57
MythSystemLegacyIOHandler::run
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: mythsystemunix.cpp:88
exitcodes.h
ReferenceCounter::IncrRef
virtual int IncrRef(void)
Increments reference count.
Definition: referencecounter.cpp:101
listLock
static QMutex listLock
Definition: mythsystemunix.cpp:59
GENERIC_EXIT_RUNNING
@ GENERIC_EXIT_RUNNING
Process is running.
Definition: exitcodes.h:26
MythSystemLegacyIOHandler::m_pWaitLock
QMutex m_pWaitLock
Definition: mythsystemunix.h:51
LOC_ERR
#define LOC_ERR
Definition: httplivestream.cpp:49
MythSystemLegacyIOHandler::m_pMap
PMap_t m_pMap
Definition: mythsystemunix.h:54
MythSystemLegacyPrivate::GetCommand
QString & GetCommand(void)
Definition: mythsystemprivate.h:54
MythSystemLegacyPrivate::m_parent
QPointer< MythSystemLegacy > m_parent
Definition: mythsystemprivate.h:40
MythSystemLegacyUnix::MythSystemLegacyUnix
MythSystemLegacyUnix(MythSystemLegacy *parent)
Definition: mythsystemunix.cpp:560
FDMap_t
QMap< int, FDType_t * > FDMap_t
Definition: mythsystemunix.cpp:48
MythSystemLegacyIOHandler::HandleRead
void HandleRead(int fd, QBuffer *buff)
Definition: mythsystemunix.cpp:156