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