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