MythTV master
mythsystemwindows.cpp
Go to the documentation of this file.
1
2// compat header
3#include "compat.h"
4
5// Own header
6#include "mythsystemlegacy.h"
7#include "mythsystemwindows.h"
8
9// C++/C headers
10#include <cerrno>
11#include <csignal> // for kill()
12#include <cstdio>
13#include <cstdlib>
14#include <cstring>
15#include <thread>
16#include <fcntl.h>
17#include <unistd.h>
18
19// QT headers
20#include <QCoreApplication>
21#include <QMutex>
22#include <QMap>
23#include <QString>
24#include <QStringList>
25
26// libmythbase headers
27#include "mythlogging.h"
28#include "mythevent.h"
29#include "exitcodes.h"
30
31// Windows headers
32#include <windows.h>
33#include <tchar.h>
34
35#define CLOSE(x) \
36if( (x) ) { \
37 CloseHandle((x)); \
38 fdLock.lock(); \
39 delete fdMap.value((x)); \
40 fdMap.remove((x)); \
41 fdLock.unlock(); \
42 (x) = nullptr; \
43}
44
45struct FDType_t
46{
48 int type;
49};
50using FDMap_t = QMap<HANDLE, FDType_t*>;
51
52/**********************************
53 * MythSystemLegacyManager method defines
54 *********************************/
55static bool run_system = true;
61static QMutex listLock;
63static QMutex fdLock;
64
66{
67 run_system = false;
68 if (manager)
69 manager->wait();
70 if (smanager)
71 smanager->wait();
72 if (readThread)
74 if (writeThread)
76}
77
79 MThread(QString("SystemIOHandler%1").arg(read ? "R" : "W")),
80 m_pWaitLock(), m_pWait(), m_pLock(), m_pMap(PMap_t()),
81 m_read(read)
82{
83 m_readbuf[0] = '\0';
84}
85
87{
88 RunProlog();
89
90 LOG(VB_GENERAL, LOG_INFO, QString("Starting IO manager (%1)")
91 .arg(m_read ? "read" : "write"));
92
93 while( run_system )
94 {
95 {
96 QMutexLocker locker(&m_pWaitLock);
97 m_pWait.wait(&m_pWaitLock);
98 }
99
100 while( run_system )
101 {
102 std::this_thread::sleep_for(10ms); // ~100x per second, for ~3MBps throughput
103 m_pLock.lock();
104 if( m_pMap.isEmpty() )
105 {
106 m_pLock.unlock();
107 break;
108 }
109
110 bool datafound = true;
111 m_pLock.unlock();
112
113 while ( datafound && run_system )
114 {
115 m_pLock.lock();
116
117 datafound = false;
118 PMap_t::iterator i, next;
119 for( i = m_pMap.begin(); i != m_pMap.end(); i = next )
120 {
121 next = i + 1;
122
123 if( m_read )
124 datafound |= HandleRead(i.key(), i.value());
125 else
126 datafound |= HandleWrite(i.key(), i.value());
127 }
128 m_pLock.unlock();
129 }
130 }
131 }
132 RunEpilog();
133}
134
135bool MythSystemLegacyIOHandler::HandleRead(HANDLE h, QBuffer *buff)
136{
137 DWORD lenAvail;
138
139 if ( !PeekNamedPipe( h, nullptr, 0, nullptr, &lenAvail, nullptr) )
140 return false;
141
142 if ( lenAvail > 65536 )
143 lenAvail = 65536;
144
145 DWORD lenRead;
146
147 if ( !ReadFile( h, &m_readbuf, lenAvail, &lenRead, nullptr ) || lenRead == 0 )
148 {
149 m_pMap.remove(h);
150 return false;
151 }
152
153 buff->buffer().append(m_readbuf, lenRead);
154
155 // Get the corresponding MythSystemLegacy instance, and the stdout/stderr
156 // type
157 fdLock.lock();
158 FDType_t *fdType = fdMap.value(h);
159 fdLock.unlock();
160
161 // Emit the data ready signal (1 = stdout, 2 = stderr)
162 MythSystemLegacyWindows *ms = fdType->ms;
163 emit ms->readDataReady(fdType->type);
164
165 return true;
166}
167
168bool MythSystemLegacyIOHandler::HandleWrite(HANDLE h, QBuffer *buff)
169{
170 if( buff->atEnd() )
171 {
172 m_pMap.remove(h);
173 return false;
174 }
175
176 int pos = buff->pos();
177 DWORD len = buff->size() - pos;
178 DWORD rlen;
179 len = (len > 32768 ? 32768 : len);
180
181 if( !WriteFile(h, buff->read(len).constData(), len, &rlen, nullptr) )
182 {
183 m_pMap.remove(h);
184 return false;
185 }
186
187 if( rlen != len )
188 buff->seek(pos+rlen);
189
190 return true;
191}
192
193void MythSystemLegacyIOHandler::insert(HANDLE h, QBuffer *buff)
194{
195 m_pLock.lock();
196 m_pMap.insert(h, buff);
197 m_pLock.unlock();
198 wake();
199}
200
202{
203 QMutexLocker locker(&m_pLock);
204 while (m_pMap.contains(h))
205 {
206 locker.unlock();
207 std::this_thread::sleep_for(10ms);
208 locker.relock();
209 }
210}
211
213{
214 m_pLock.lock();
215 if (m_read)
216 {
217 PMap_t::iterator i;
218 i = m_pMap.find(h);
219 HandleRead(i.key(), i.value());
220 }
221 m_pMap.remove(h);
222 m_pLock.unlock();
223}
224
226{
227 QMutexLocker locker(&m_pWaitLock);
228 m_pWait.wakeAll();
229}
230
231
233{
234 if (m_children)
235 free( m_children );
236 wait();
237}
238
240{
241 RunProlog();
242
243 LOG(VB_GENERAL, LOG_INFO, "Starting process manager");
244
245 // run_system is set to false during shutdown, and we need this thread to
246 // exit during shutdown.
247 while( run_system )
248 {
249 // check for any running processes
250 m_mapLock.lock();
251
252 if( m_childCount == 0 )
253 {
254 m_mapLock.unlock();
255 std::this_thread::sleep_for(100ms);
256 continue;
257 }
258
259 DWORD result = WaitForMultipleObjects( m_childCount, m_children,
260 FALSE, 100 );
261
262 if ( result == WAIT_TIMEOUT || result == WAIT_FAILED )
263 {
264 m_mapLock.unlock();
265 continue;
266 }
267
268 int index = result - WAIT_OBJECT_0;
269 if ( index < 0 || index > m_childCount - 1 )
270 {
271 m_mapLock.unlock();
272 continue;
273 }
274 HANDLE child = m_children[index];
275
276 // pop exited process off managed list, add to cleanup list
277 MythSystemLegacyWindows *ms = m_pMap.take(child);
279 m_mapLock.unlock();
280
281 // Occasionally, the caller has deleted the structure from under
282 // our feet. If so, just log and move on.
283 if (!ms || !ms->m_parent)
284 {
285 LOG(VB_SYSTEM, LOG_ERR,
286 QString("Structure for child handle %1 already deleted!")
287 .arg((long long)child));
288 if (ms)
289 {
290 listLock.lock();
291 msList.append(ms);
292 listLock.unlock();
293 }
294 continue;
295 }
296
297 listLock.lock();
298 msList.append(ms);
299
300 DWORD status;
301 GetExitCodeProcess( child, &status );
302
303 ms->SetStatus(status);
304 LOG(VB_SYSTEM, LOG_INFO,
305 QString("Managed child (Handle: %1) has exited! "
306 "command=%2, status=%3, result=%4")
307 .arg((long long)child) .arg(ms->GetLogCmd()) .arg(status)
308 .arg(ms->GetStatus()));
309
310 // loop through running processes for any that require action
311 MSMap_t::iterator i;
312 auto now = SystemClock::now();
313
314 m_mapLock.lock();
315 m_jumpLock.lock();
316 for (i = m_pMap.begin(); i != m_pMap.end(); ++i)
317 {
318 child = i.key();
319 ms = i.value();
320
321 // handle processes beyond marked timeout
322 if( ms->m_timeout.time_since_epoch() > 0s && ms->m_timeout < now )
323 {
324 // issuing KILL signal after TERM failed in a timely manner
325 if( ms->GetStatus() == GENERIC_EXIT_TIMEOUT )
326 {
327 LOG(VB_SYSTEM, LOG_INFO,
328 QString("Managed child (Handle: %1) timed out, "
329 "issuing KILL signal").arg((long long)child));
330 // Prevent constant attempts to kill an obstinate child
331 ms->m_timeout = SystemTime(0s);
332 ms->Signal(SIGKILL);
333 }
334
335 // issuing TERM signal
336 else
337 {
338 LOG(VB_SYSTEM, LOG_INFO,
339 QString("Managed child (Handle: %1) timed out"
340 ", issuing TERM signal").arg((long long)child));
342 ms->m_timeout = now + 1s;
343 ms->Term();
344 }
345 }
346
347 if ( m_jumpAbort && ms->GetSetting("AbortOnJump") )
348 ms->Term();
349 }
350
351 m_jumpAbort = false;
352 m_jumpLock.unlock();
353
354 m_mapLock.unlock();
355
356 // hold off unlocking until all the way down here to
357 // give the buffer handling a chance to run before
358 // being closed down by signal thread
359 listLock.unlock();
360 }
361
362 // kick to allow them to close themselves cleanly
363 readThread->wake();
364 writeThread->wake();
365
366 RunEpilog();
367}
368
369// NOTE: This is only to be run while m_mapLock is locked!!!
371{
372 int oldCount;
373
374 oldCount = m_childCount;
375 m_childCount = m_pMap.size();
376
377 MSMap_t::iterator i;
378 int j;
379 HANDLE child;
380
381 if ( oldCount != m_childCount )
382 {
383 HANDLE *new_children;
384 new_children = (HANDLE *)realloc(m_children,
385 m_childCount * sizeof(HANDLE));
386 if (!new_children && m_childCount)
387 {
388 LOG(VB_SYSTEM, LOG_CRIT, "No memory to allocate new children");
389 free(m_children);
390 m_children = nullptr;
391 return;
392 }
393
394 m_children = new_children;
395 }
396
397 for (i = m_pMap.begin(), j = 0; i != m_pMap.end(); ++i)
398 {
399 child = i.key();
400 m_children[j++] = child;
401 }
402}
403
405{
406 m_mapLock.lock();
407 ms->IncrRef();
408 m_pMap.insert(ms->m_child, ms);
410 m_mapLock.unlock();
411
412 if (ms->m_stdpipe[0])
413 {
414 QByteArray ba = ms->GetBuffer(0)->data();
415 QBuffer wtb(&ba);
416 wtb.open(QIODevice::ReadOnly);
417 writeThread->insert(ms->m_stdpipe[0], &wtb);
418 writeThread->Wait(ms->m_stdpipe[0]);
420 CLOSE(ms->m_stdpipe[0]);
421 }
422
423 if( ms->GetSetting("UseStdout") )
424 {
425 FDType_t *fdType = new FDType_t;
426 fdType->ms = ms;
427 fdType->type = 1;
428 fdLock.lock();
429 fdMap.insert( ms->m_stdpipe[1], fdType );
430 fdLock.unlock();
431 readThread->insert(ms->m_stdpipe[1], ms->GetBuffer(1));
432 }
433
434 if( ms->GetSetting("UseStderr") )
435 {
436 FDType_t *fdType = new FDType_t;
437 fdType->ms = ms;
438 fdType->type = 2;
439 fdLock.lock();
440 fdMap.insert( ms->m_stdpipe[2], fdType );
441 fdLock.unlock();
442 readThread->insert(ms->m_stdpipe[2], ms->GetBuffer(2));
443 }
444}
445
447{
448 m_jumpLock.lock();
449 m_jumpAbort = true;
450 m_jumpLock.unlock();
451}
452
453// spawn separate thread for signals to prevent manager
455{
456 RunProlog();
457
458 LOG(VB_GENERAL, LOG_INFO, "Starting process signal handler");
459 while( run_system )
460 {
461 std::this_thread::sleep_for(50ms);
462 while( run_system )
463 {
464 // handle cleanup and signalling for closed processes
465 listLock.lock();
466 if( msList.isEmpty() )
467 {
468 listLock.unlock();
469 break;
470 }
471 MythSystemLegacyWindows *ms = msList.takeFirst();
472 listLock.unlock();
473
474 if (!ms)
475 continue;
476
477 if (ms->m_parent)
478 {
479 ms->m_parent->HandlePostRun();
480 }
481
482 if (ms->m_stdpipe[0])
484 CLOSE(ms->m_stdpipe[0]);
485
486 if (ms->m_stdpipe[1])
487 readThread->remove(ms->m_stdpipe[1]);
488 CLOSE(ms->m_stdpipe[1]);
489
490 if (ms->m_stdpipe[2])
491 readThread->remove(ms->m_stdpipe[2]);
492 CLOSE(ms->m_stdpipe[2]);
493
494 if (ms->m_parent)
495 {
496 if( ms->GetStatus() == GENERIC_EXIT_OK )
497 emit ms->finished();
498 else
499 emit ms->error(ms->GetStatus());
500
501 ms->disconnect();
502 ms->Unlock();
503 }
504
505 ms->DecrRef();
506 }
507 }
508
509 RunEpilog();
510}
511
512/*******************************
513 * MythSystemLegacy method defines
514 ******************************/
515
517 MythSystemLegacyPrivate("MythSystemLegacyWindows")
518{
519 m_parent = parent;
520
521 m_stdpipe[0] = nullptr;
522 m_stdpipe[1] = nullptr;
523 m_stdpipe[2] = nullptr;
524
530
531 // Start the threads if they haven't been started yet.
532 if (manager == nullptr)
533 {
535 manager->start();
536 }
537
538 if (smanager == nullptr)
539 {
541 smanager->start();
542 }
543
544 if (readThread == nullptr)
545 {
547 readThread->start();
548 }
549
550 if (writeThread == nullptr)
551 {
554 }
555}
556
557bool MythSystemLegacyWindows::ParseShell(const QString&, QString &, QStringList&)
558{
559 return false;
560}
561
563{
564 if( (GetStatus() != GENERIC_EXIT_RUNNING) || (!m_child) )
565 return;
566
567 Signal(SIGTERM);
568 if( force )
569 {
570 // send KILL if it does not exit within one second
571 if( m_parent->Wait(1s) == GENERIC_EXIT_RUNNING )
573 }
574}
575
577{
578 if( (GetStatus() != GENERIC_EXIT_RUNNING) || (!m_child) )
579 return;
580 LOG(VB_SYSTEM, LOG_INFO, QString("Child Handle %1 killed with %2")
581 .arg((long long)m_child).arg(sig));
582 TerminateProcess( m_child, sig * 256 );
583}
584
585
586#define MAX_BUFLEN 1024
587void MythSystemLegacyWindows::Fork(std::chrono::seconds timeout)
588{
589 BOOL bInherit = FALSE;
590
591 QString LOC_ERR = QString("myth_system('%1'): Error: ").arg(GetLogCmd());
592
593 LOG(VB_SYSTEM, LOG_DEBUG, QString("Launching: %1").arg(GetLogCmd()));
594
595 HANDLE p_stdin[2] = { nullptr, nullptr };
596 HANDLE p_stdout[2] = { nullptr, nullptr };
597 HANDLE p_stderr[2] = { nullptr, nullptr };
598
599 SECURITY_ATTRIBUTES saAttr;
600 STARTUPINFO si;
601
602 ZeroMemory(&si, sizeof(STARTUPINFO));
603 si.cb = sizeof(STARTUPINFO);
604
605 // Set the bInheritHandle flag so pipe handles are inherited.
606 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
607 saAttr.bInheritHandle = true;
608 saAttr.lpSecurityDescriptor = nullptr;
609
610 /* set up pipes */
611 if( GetSetting("UseStdin") )
612 {
613 bInherit = TRUE;
614
615 if (!CreatePipe(&p_stdin[0], &p_stdin[1], &saAttr, 0))
616 {
617 LOG(VB_GENERAL, LOG_ERR, LOC_ERR + "stdin pipe() failed");
619 }
620 else
621 {
622 // Ensure the write handle to the pipe for STDIN is not inherited.
623 if (!SetHandleInformation(p_stdin[1], HANDLE_FLAG_INHERIT, 0))
624 {
625 LOG(VB_SYSTEM, LOG_ERR, LOC_ERR + "stdin inheritance error");
627 }
628 else
629 {
630 si.hStdInput = p_stdin[0];
631 si.dwFlags |= STARTF_USESTDHANDLES;
632 }
633 }
634 }
635
636 if( GetSetting("UseStdout") )
637 {
638 bInherit = TRUE;
639
640 if (!CreatePipe(&p_stdout[0], &p_stdout[1], &saAttr, 0))
641 {
642 LOG(VB_SYSTEM, LOG_ERR, LOC_ERR + "stdout pipe() failed");
644 }
645 else
646 {
647 // Ensure the read handle to the pipe for STDOUT is not inherited.
648 if (!SetHandleInformation(p_stdout[0], HANDLE_FLAG_INHERIT, 0))
649 {
650 LOG(VB_SYSTEM, LOG_ERR, LOC_ERR + "stdout inheritance error");
652 }
653 else
654 {
655 si.hStdOutput = p_stdout[1];
656 si.dwFlags |= STARTF_USESTDHANDLES;
657 }
658 }
659 }
660
661 if( GetSetting("UseStderr") )
662 {
663 bInherit = TRUE;
664
665 if (!CreatePipe(&p_stderr[0], &p_stderr[1], &saAttr, 0))
666 {
667 LOG(VB_SYSTEM, LOG_ERR, LOC_ERR + "stderr pipe() failed");
669 }
670 else
671 {
672 // Ensure the read handle to the pipe for STDERR is not inherited.
673 if (!SetHandleInformation(p_stderr[0], HANDLE_FLAG_INHERIT, 0))
674 {
675 LOG(VB_SYSTEM, LOG_ERR, LOC_ERR + "stderr inheritance error");
677 }
678 else
679 {
680 si.hStdError = p_stderr[1];
681 si.dwFlags |= STARTF_USESTDHANDLES;
682 }
683 }
684 }
685
686 // set up command args
687 QString cmd = GetCommand() + " " + GetArgs().join(" ");
688
689 if (GetSetting("UseShell"))
690 cmd.prepend("cmd.exe /c ");
691
692 SetCommand( cmd );
693
694 QString sCmd = GetCommand();
695
696 QString dir = GetDirectory();
697
698 PROCESS_INFORMATION pi;
699 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
700
701 m_timeout = (timeout != 0s)
702 ? SystemClock::now() + timeout
703 : SystemClock::time_point();
704
705 LPCWSTR pDir = nullptr;
706 if (dir.length() > 0)
707 pDir = (LPCWSTR)dir.utf16();
708
709 char sCmdChar[256];
710 sprintf(sCmdChar, "%ls", (LPWSTR)sCmd.utf16() );
711
712 char pDirChar[256];
713 sprintf(pDirChar, "%ls", pDir);
714
715 bool success = CreateProcess( nullptr,
716 sCmdChar, // command line
717 nullptr, // process security attributes
718 nullptr, // primary thread security attributes
719 bInherit, // handles are inherited
720 0, // creation flags
721 nullptr, // use parent's environment
722 pDirChar, // use parent's current directory
723 &si, // STARTUPINFO pointer
724 &pi); // receives PROCESS_INFORMATION
725
726 if (!success)
727 {
728 DWORD dwErr = GetLastError();
729 LOG(VB_SYSTEM, LOG_ERR,
730 QString( "%1 CreateProcess() failed (%2)")
731 .arg( LOC_ERR )
732 .arg( dwErr ));
734 }
735 else
736 {
737 /* parent */
738 m_child = pi.hProcess;
740
741 LOG(VB_SYSTEM, LOG_INFO,
742 QString("Managed child (Handle: %1) has started! "
743 "%2%3 command=%4, timeout=%5")
744 .arg((long long)m_child)
745 .arg(GetSetting("UseShell") ? "*" : "")
746 .arg(GetSetting("RunInBackground") ? "&" : "")
747 .arg(GetLogCmd()).arg(timeout.count()));
748
749 /* close unused pipe ends */
750 CLOSE(p_stdin[0]);
751 CLOSE(p_stdout[1]);
752 CLOSE(p_stderr[1]);
753
754 // store the rest
755 m_stdpipe[0] = p_stdin[1];
756 m_stdpipe[1] = p_stdout[0];
757 m_stdpipe[2] = p_stderr[0];
758
759 }
760
761 /* Parent */
763 {
764 CLOSE(p_stdin[0]);
765 CLOSE(p_stdin[1]);
766 CLOSE(p_stdout[0]);
767 CLOSE(p_stdout[1]);
768 CLOSE(p_stderr[0]);
769 CLOSE(p_stderr[1]);
770 }
771}
772
774{
775 if( manager == nullptr )
776 {
778 manager->start();
779 }
780 manager->append(this);
781}
782
784{
785 if( manager == nullptr )
786 {
788 manager->start();
789 }
791}
792
793#include "moc_mythsystemwindows.cpp"
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:180
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:267
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:193
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:284
void HandleWrite(int fd, QBuffer *buff)
MythSystemLegacyIOHandler(bool read)
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.
~MythSystemLegacyManager() override
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)
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.
friend class MythSystemLegacySignalManager
void Signal(int sig) override
friend class MythSystemLegacyIOHandler
void Manage(void) override
void Fork(std::chrono::seconds timeout) override
friend class MythSystemLegacyManager
void JumpAbort(void) override
void Term(bool force=false) override
MythSystemLegacyWindows(MythSystemLegacy *parent)
bool ParseShell(const QString &cmd, QString &abscmd, QStringList &args) 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 SIGKILL
Definition: compat.h:77
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
@ GENERIC_EXIT_RUNNING
Process is running.
Definition: exitcodes.h:28
@ GENERIC_EXIT_TIMEOUT
Process timed out.
Definition: exitcodes.h:27
@ GENERIC_EXIT_NOT_OK
Exited with error.
Definition: exitcodes.h:14
#define LOC_ERR
std::chrono::time_point< SystemClock > SystemTime
Definition: mythchrono.h:57
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QMap< int, FDType_t * > FDMap_t
QList< QPointer< MythSystemLegacyUnix > > MSList_t
QMap< int, QBuffer * > PMap_t
static MSList_t msList
static bool run_system
void ShutdownMythSystemLegacy(void)
static MythSystemLegacyIOHandler * writeThread
static QMutex listLock
#define CLOSE(x)
static MythSystemLegacyIOHandler * readThread
static MythSystemLegacySignalManager * smanager
static QMutex fdLock
static MythSystemLegacyManager * manager
static FDMap_t fdMap
def read(device=None, features=[])
Definition: disc.py:35
MythSystemLegacyWindows * ms