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