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