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