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