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