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