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