MythTV master
mythsystemlegacy.cpp
Go to the documentation of this file.
1/* -*- Mode: c++ -*-
2 * Class MythSystemLegacy
3 *
4 * Copyright (C) Gavin Hurlbut 2012
5 * Copyright (C) Issac Richards 2008
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22// compat header
23#include "compat.h"
24
25// Own header
26#include "mythsystemlegacy.h"
27
28// C++/C headers
29#include <cerrno>
30#include <csignal> // for kill() and SIGXXX
31#include <cstdlib>
32#include <cstring>
33#include <fcntl.h>
34#include <unistd.h>
35
36// QT headers
37#include <QtGlobal>
38#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
39#include <QtSystemDetection>
40#endif
41#include <QCoreApplication>
42
43// libmythbase headers
44#include "referencecounter.h"
45#include "mythcorecontext.h"
46#include "mythevent.h"
47#include "mythlogging.h"
48
49#ifdef Q_OS_WINDOWS
50#include "mythsystemwindows.h"
51#else
52#include "mythsystemunix.h"
53#endif
54
55
56/*******************************
57 * MythSystemLegacy method defines
58 ******************************/
59
61{
62#ifdef Q_OS_WINDOWS
63 d = new MythSystemLegacyWindows(this);
64#else
65 d = new MythSystemLegacyUnix(this);
66#endif
67}
68
70 QObject(parent)
71{
72 setObjectName("MythSystemLegacy()");
73 m_semReady.release(1); // initialize
75}
76
77MythSystemLegacy::MythSystemLegacy(const QString &command, uint flags,
78 QObject *parent) :
79 QObject(parent)
80{
81 setObjectName(QString("MythSystemLegacy(%1)").arg(command));
82 m_semReady.release(1); // initialize
84 SetCommand(command, flags);
85}
86
90void MythSystemLegacy::SetCommand(const QString &command, uint flags)
91{
92 if (flags & kMSRunShell)
93 {
94 SetCommand(command, QStringList(), flags);
95 }
96 else
97 {
98 QString abscommand;
99 QStringList args;
100 if (!d->ParseShell(command, abscommand, args))
101 {
102 LOG(VB_GENERAL, LOG_ERR,
103 QString("MythSystemLegacy(%1) command not understood")
104 .arg(command));
106 return;
107 }
108
109 SetCommand(abscommand, args, flags);
110 }
111
112 if (m_settings["UseStdin"])
113 m_stdbuff[0].open(QIODevice::WriteOnly);
114 if (m_settings["UseStdout"])
115 m_stdbuff[1].open(QIODevice::ReadOnly);
116 if (m_settings["UseStderr"])
117 m_stdbuff[2].open(QIODevice::ReadOnly);
118}
119
120
122 const QStringList &args,
123 uint flags,
124 QObject *parent) :
125 QObject(parent)
126{
127 m_semReady.release(1); // initialize
129 SetCommand(command, args, flags);
130}
131
135void MythSystemLegacy::SetCommand(const QString &command,
136 const QStringList &args, uint flags)
137{
139 m_command = QString(command).trimmed();
140 m_args = QStringList(args);
141
142 ProcessFlags(flags);
143
144 // add logging arguments
145 if (GetSetting("PropagateLogs"))
146 {
147 if (GetSetting("UseShell") && m_args.isEmpty())
148 {
150 if (!logPropagateQuiet())
151 m_command += " --quiet";
152 }
153 else
154 {
156 if (!logPropagateQuiet())
157 m_args << "--quiet";
158 }
159 }
160
161 // check for execute rights
162 if (!GetSetting("UseShell") && (access(command.toUtf8().constData(), X_OK)) != 0)
163 {
164 LOG(VB_GENERAL, LOG_ERR,
165 QString("MythSystemLegacy(%1) command not executable, ")
166 .arg(command) + ENO);
168 }
169
170 m_logcmd = (m_command + " " + m_args.join(" ")).trimmed();
171
172 if (GetSetting("AnonLog"))
173 {
174 m_logcmd.truncate(m_logcmd.indexOf(" "));
175 m_logcmd.append(" (anonymized)");
176 }
177}
178
179// QBuffers may also need freeing
181{
183 {
184 Term(true);
185 Wait();
186 }
187 d->DecrRef();
188}
189
190void MythSystemLegacy::SetDirectory(const QString &directory)
191{
192 m_settings["SetDirectory"] = true;
193 m_directory = QString(directory);
194}
195
197{
198 if (!d || (GetStatus() != GENERIC_EXIT_START))
199 return false;
200
201 m_nice = nice;
202 return true;
203}
204
206{
207 if (!d || (GetStatus() != GENERIC_EXIT_START))
208 return false;
209
210 m_ioprio = prio;
211 return true;
212}
213
215void MythSystemLegacy::Run(std::chrono::seconds timeout)
216{
217 if (!d)
219
221 {
222 emit error(GetStatus());
223 return;
224 }
225
226 // Handle any locking of drawing, etc
227 HandlePreRun();
228
229 d->Fork(timeout);
230
232 {
233 m_semReady.acquire(1);
234 emit started();
235 d->Manage();
236 }
237 else
238 {
239 emit error(GetStatus());
240 }
241}
242
243// should there be a separate 'getstatus' call? or is using
244// Wait() for that purpose sufficient?
246{
247 if (!d)
249
250 if ((GetStatus() != GENERIC_EXIT_RUNNING) || GetSetting("RunInBackground"))
251 return GetStatus();
252
253 if (GetSetting("ProcessEvents"))
254 {
255 auto tt = (timeout > 0s)
256 ? SystemClock::now() + timeout
257 : SystemTime::max();
258 while (SystemClock::now() < tt)
259 {
260 // loop until timeout hits or process ends
261 if (m_semReady.tryAcquire(1,100))
262 {
263 m_semReady.release(1);
264 break;
265 }
266
267 qApp->processEvents();
268 }
269 }
270 else
271 {
272 if (timeout > 0s)
273 {
274 auto msec = duration_cast<std::chrono::milliseconds>(timeout);
275 if (m_semReady.tryAcquire(1, msec.count()))
276 m_semReady.release(1);
277 }
278 else
279 {
280 m_semReady.acquire(1);
281 m_semReady.release(1);
282 }
283 }
284 return GetStatus();
285}
286
288{
289 if (!d)
291
293 return;
294
295 d->Term(force);
296}
297
299{
300 if (!d)
302
304 return;
305
306#ifndef SIGTRAP /* For Mingw */
307#define SIGTRAP -1
308#endif
309 int posix_signal = SIGTRAP;
310 switch (sig)
311 {
312 case kSignalNone:
313 case kSignalUnknown:
314 break;
315 case kSignalHangup: posix_signal = SIGHUP; break;
316 case kSignalInterrupt: posix_signal = SIGINT; break;
317 case kSignalContinue: posix_signal = SIGCONT; break;
318 case kSignalQuit: posix_signal = SIGQUIT; break;
319 case kSignalSegfault: posix_signal = SIGSEGV; break;
320 case kSignalKill: posix_signal = SIGKILL; break;
321 case kSignalUser1: posix_signal = SIGUSR1; break;
322 case kSignalUser2: posix_signal = SIGUSR2; break;
323 case kSignalTerm: posix_signal = SIGTERM; break;
324 case kSignalStop: posix_signal = SIGSTOP; break;
325 }
326
327 // The default less switch above will cause a compiler warning
328 // if someone adds a signal without updating the switch, but in
329 // case that is missed print out a message.
330 if (SIGTRAP == posix_signal)
331 {
332 LOG(VB_SYSTEM, LOG_ERR,
333 QString("Programmer error: Unknown signal %1").arg(sig));
334 return;
335 }
336
337 d->Signal(posix_signal);
338}
339
340
342{
344 {
345 LOG(VB_SYSTEM, LOG_DEBUG, QString("status: %1").arg(m_status));
346 return;
347 }
348
350
351 if (flags & kMSRunBackground)
352 m_settings["RunInBackground"] = true;
353
354 if (m_command.endsWith("&"))
355 {
356 if (!GetSetting("RunInBackground"))
357 LOG(VB_SYSTEM, LOG_DEBUG, "Adding background flag");
358
359 // Remove the &
360 m_command.chop(1);
361 m_command = m_command.trimmed();
362 m_settings["RunInBackground"] = true;
363 m_settings["UseShell"] = true;
364 m_settings["IsInUI"] = false;
365 }
366
367 if (GetSetting("IsInUI"))
368 {
369 // Check for UI-only locks
370 m_settings["BlockInputDevs"] = ((flags & kMSDontBlockInputDevs) == 0U);
371 m_settings["DisableDrawing"] = ((flags & kMSDontDisableDrawing) == 0U);
372 m_settings["ProcessEvents"] = ((flags & kMSProcessEvents) != 0U);
373 m_settings["DisableUDP"] = ((flags & kMSDisableUDPListener) != 0U);
374 }
375
376 if (flags & kMSStdIn)
377 m_settings["UseStdin"] = true;
378 if (flags & kMSStdOut)
379 m_settings["UseStdout"] = true;
380 if (flags & kMSStdErr)
381 m_settings["UseStderr"] = true;
382 if (flags & kMSRunShell)
383 m_settings["UseShell"] = true;
384 if (flags & kMSAutoCleanup && GetSetting("RunInBackground"))
385 m_settings["AutoCleanup"] = true;
386 if (flags & kMSAnonLog)
387 m_settings["AnonLog"] = true;
388 if (flags & kMSLowExitVal)
389 m_settings["OnlyLowExitVal"] = true;
390 if (flags & kMSPropagateLogs)
391 m_settings["PropagateLogs"] = true;
392}
393
394QByteArray MythSystemLegacy::Read(int size)
395{
396 return m_stdbuff[1].read(size);
397}
398
399QByteArray MythSystemLegacy::ReadErr(int size)
400{
401 return m_stdbuff[2].read(size);
402}
403
405{
406 return m_stdbuff[1].buffer();
407}
408
410{
411 return m_stdbuff[2].buffer();
412}
413
418int MythSystemLegacy::Write(const QByteArray &ba)
419{
420 if (!GetSetting("UseStdin"))
421 return 0;
422
423 return m_stdbuff[0].write(ba.constData());
424}
425
427{
428 // This needs to be a send event so that the MythUI locks the input devices
429 // immediately instead of after existing events are processed
430 // since this function could be called inside one of those events.
431 if (GetSetting("BlockInputDevs"))
432 {
434 QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event);
435 }
436
437 // This needs to be a send event so that the listener is disabled
438 // immediately instead of after existing events are processed, since the
439 // listen server must be terminated before the spawned application tries
440 // to start its own
441 if (GetSetting("DisableUDP"))
442 {
444 QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event);
445 }
446
447 // This needs to be a send event so that the MythUI m_drawState change is
448 // flagged immediately instead of after existing events are processed
449 // since this function could be called inside one of those events.
450 if (GetSetting("DisableDrawing"))
451 {
453 QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event);
454 }
455}
456
458{
459 // Since this is *not* running in the UI thread (but rather the signal
460 // handler thread), we need to use postEvents
461 if (GetSetting("DisableDrawing"))
462 {
463 auto *event = new QEvent(MythEvent::kPopDisableDrawingEventType);
464 QCoreApplication::postEvent(gCoreContext->GetGUIObject(), event);
465 }
466
467 // This needs to be a post event so we do not try to start listening on
468 // the UDP ports before the child application has stopped and terminated
469 if (GetSetting("DisableUDP"))
470 {
471 auto *event = new QEvent(MythEvent::kEnableUDPListenerEventType);
472 QCoreApplication::postEvent(gCoreContext->GetGUIObject(), event);
473 }
474
475 // This needs to be a post event so that the MythUI unlocks input devices
476 // after all existing (blocked) events are processed and ignored.
477 if (GetSetting("BlockInputDevs"))
478 {
479 auto *event = new QEvent(MythEvent::kUnlockInputDevicesEventType);
480 QCoreApplication::postEvent(gCoreContext->GetGUIObject(), event);
481 }
482}
483
484QString MythSystemLegacy::ShellEscape(const QString &in)
485{
486 QString out = in;
487
488 if (out.contains("\""))
489 out = out.replace("\"", "\\\"");
490
491 if (out.contains("\'"))
492 out = out.replace("\'", "\\\'");
493
494 if (out.contains(" "))
495 {
496 out.prepend("\"");
497 out.append("\"");
498 }
499
500 return out;
501}
502
504 ReferenceCounter(debugName)
505{
506}
507
508uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
509{
510 flags |= kMSRunShell | kMSAutoCleanup;
511 auto *ms = new MythSystemLegacy(command, flags);
512 ms->Run(timeout);
513 uint result = ms->Wait(0s);
514 if (!ms->GetSetting("RunInBackground"))
515 delete ms;
516
517 return result;
518}
519
520uint myth_system(const QString &Command, const QStringList& Args, uint Flags,
521 std::chrono::seconds Timeout)
522{
523 Flags |= kMSRunShell | kMSAutoCleanup;
524 auto *ms = new MythSystemLegacy(Command, Args, Flags);
525 ms->Run(Timeout);
526 uint result = ms->Wait(0s);
527 if (!ms->GetSetting("RunInBackground"))
528 delete ms;
529
530 return result;
531}
532
533/*
534 * vim:ts=4:sw=4:ai:et:si:sts=4
535 */
QObject * GetGUIObject(void)
bool HasGUI(void) const
static const Type kPushDisableDrawingEventType
Definition: mythevent.h:84
static const Type kDisableUDPListenerEventType
Definition: mythevent.h:89
static const Type kUnlockInputDevicesEventType
Definition: mythevent.h:87
static const Type kPopDisableDrawingEventType
Definition: mythevent.h:85
static const Type kEnableUDPListenerEventType
Definition: mythevent.h:90
static const Type kLockInputDevicesEventType
Definition: mythevent.h:86
virtual void Fork(std::chrono::seconds timeout)=0
MythSystemLegacyPrivate(const QString &debugName)
virtual void Signal(int sig)=0
virtual void Term(bool force=false)=0
virtual bool ParseShell(const QString &cmd, QString &abscmd, QStringList &args)=0
virtual void Manage(void)=0
void SetCommand(const QString &command, uint flags)
Resets an existing MythSystemLegacy object to a new command.
uint Wait(std::chrono::seconds timeout=0s)
QByteArray ReadErr(int size)
MythSystemLegacyPrivate * d
~MythSystemLegacy(void) override
QByteArray & ReadAllErr()
void started(void)
static QString ShellEscape(const QString &in)
void error(uint status)
MythSystemLegacy(QObject *parent=nullptr)
uint GetStatus(void) const
bool SetNice(int nice)
void Term(bool force=false)
void SetDirectory(const QString &directory)
int Write(const QByteArray &ba)
This writes to the standard input of the program being run.
void Run(std::chrono::seconds timeout=0s)
Runs a command inside the /bin/sh shell. Returns immediately.
bool SetIOPrio(int prio)
void ProcessFlags(uint flags)
QByteArray Read(int size)
std::array< QBuffer, 3 > m_stdbuff
void Signal(MythSignal sig)
QByteArray & ReadAll()
bool GetSetting(const char *setting)
void initializePrivate(void)
General purpose reference counter.
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
#define SIGCONT
Definition: compat.h:93
#define SIGSTOP
Definition: compat.h:94
#define SIGHUP
Definition: compat.h:86
#define SIGQUIT
Definition: compat.h:87
unsigned int uint
Definition: compat.h:68
#define nice(x)
Definition: compat.h:74
#define SIGUSR1
Definition: compat.h:89
#define SIGUSR2
Definition: compat.h:90
#define SIGKILL
Definition: compat.h:88
@ GENERIC_EXIT_START
MythSystemLegacy process starting.
Definition: exitcodes.h:38
@ GENERIC_EXIT_CMD_NOT_FOUND
Command not found.
Definition: exitcodes.h:15
@ GENERIC_EXIT_NO_HANDLER
No MythSystemLegacy Handler.
Definition: exitcodes.h:30
@ GENERIC_EXIT_RUNNING
Process is running.
Definition: exitcodes.h:28
@ GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:18
bool logPropagateQuiet(void)
Check if we are propagating a "--quiet".
Definition: logging.cpp:637
QString logPropagateArgs
Definition: logging.cpp:85
QStringList logPropagateArgList
Definition: logging.cpp:86
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythSignal
Definition: mythsystem.h:56
@ kSignalContinue
Definition: mythsystem.h:61
@ kSignalSegfault
Definition: mythsystem.h:63
@ kSignalUser1
Definition: mythsystem.h:65
@ kSignalQuit
Definition: mythsystem.h:62
@ kSignalInterrupt
Definition: mythsystem.h:60
@ kSignalHangup
Definition: mythsystem.h:59
@ kSignalNone
Definition: mythsystem.h:57
@ kSignalUser2
Definition: mythsystem.h:66
@ kSignalUnknown
Definition: mythsystem.h:58
@ kSignalStop
Definition: mythsystem.h:68
@ kSignalKill
Definition: mythsystem.h:64
@ kSignalTerm
Definition: mythsystem.h:67
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:36
@ kMSProcessEvents
process events while waiting
Definition: mythsystem.h:39
@ kMSStdIn
allow access to stdin
Definition: mythsystem.h:40
@ kMSStdErr
allow access to stderr
Definition: mythsystem.h:42
@ kMSPropagateLogs
add arguments for MythTV log propagation
Definition: mythsystem.h:52
@ kMSStdOut
allow access to stdout
Definition: mythsystem.h:41
@ kMSLowExitVal
allow exit values 0-127 only
Definition: mythsystem.h:47
@ kMSRunShell
run process through shell
Definition: mythsystem.h:43
@ kMSDisableUDPListener
disable MythMessage UDP listener for the duration of application.
Definition: mythsystem.h:50
@ kMSAnonLog
anonymize the logs
Definition: mythsystem.h:44
@ kMSRunBackground
run child in the background
Definition: mythsystem.h:38
@ kMSDontDisableDrawing
avoid disabling UI drawing
Definition: mythsystem.h:37
@ kMSAutoCleanup
automatically delete if backgrounded
Definition: mythsystem.h:45
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
#define SIGTRAP