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