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#include <QCoreApplication>
39
40// libmythbase headers
41#include "referencecounter.h"
42#include "mythcorecontext.h"
43#include "mythevent.h"
44#include "mythlogging.h"
45
46#ifdef Q_OS_WIN
47#include "mythsystemwindows.h"
48#else
49#include "mythsystemunix.h"
50#endif
51
52
53/*******************************
54 * MythSystemLegacy method defines
55 ******************************/
56
58{
59#ifdef Q_OS_WIN
60 d = new MythSystemLegacyWindows(this);
61#else
62 d = new MythSystemLegacyUnix(this);
63#endif
64}
65
67 QObject(parent)
68{
69 setObjectName("MythSystemLegacy()");
70 m_semReady.release(1); // initialize
72}
73
74MythSystemLegacy::MythSystemLegacy(const QString &command, uint flags,
75 QObject *parent) :
76 QObject(parent)
77{
78 setObjectName(QString("MythSystemLegacy(%1)").arg(command));
79 m_semReady.release(1); // initialize
81 SetCommand(command, flags);
82}
83
87void MythSystemLegacy::SetCommand(const QString &command, uint flags)
88{
89 if (flags & kMSRunShell)
90 {
91 SetCommand(command, QStringList(), flags);
92 }
93 else
94 {
95 QString abscommand;
96 QStringList args;
97 if (!d->ParseShell(command, abscommand, args))
98 {
99 LOG(VB_GENERAL, LOG_ERR,
100 QString("MythSystemLegacy(%1) command not understood")
101 .arg(command));
103 return;
104 }
105
106 SetCommand(abscommand, args, flags);
107 }
108
109 if (m_settings["UseStdin"])
110 m_stdbuff[0].open(QIODevice::WriteOnly);
111 if (m_settings["UseStdout"])
112 m_stdbuff[1].open(QIODevice::ReadOnly);
113 if (m_settings["UseStderr"])
114 m_stdbuff[2].open(QIODevice::ReadOnly);
115}
116
117
119 const QStringList &args,
120 uint flags,
121 QObject *parent) :
122 QObject(parent)
123{
124 m_semReady.release(1); // initialize
126 SetCommand(command, args, flags);
127}
128
132void MythSystemLegacy::SetCommand(const QString &command,
133 const QStringList &args, uint flags)
134{
136 m_command = QString(command).trimmed();
137 m_args = QStringList(args);
138
139 ProcessFlags(flags);
140
141 // add logging arguments
142 if (GetSetting("PropagateLogs"))
143 {
144 if (GetSetting("UseShell") && m_args.isEmpty())
145 {
147 if (!logPropagateQuiet())
148 m_command += " --quiet";
149 }
150 else
151 {
153 if (!logPropagateQuiet())
154 m_args << "--quiet";
155 }
156 }
157
158 // check for execute rights
159 if (!GetSetting("UseShell") && (access(command.toUtf8().constData(), X_OK)) != 0)
160 {
161 LOG(VB_GENERAL, LOG_ERR,
162 QString("MythSystemLegacy(%1) command not executable, ")
163 .arg(command) + ENO);
165 }
166
167 m_logcmd = (m_command + " " + m_args.join(" ")).trimmed();
168
169 if (GetSetting("AnonLog"))
170 {
171 m_logcmd.truncate(m_logcmd.indexOf(" "));
172 m_logcmd.append(" (anonymized)");
173 }
174}
175
176// QBuffers may also need freeing
178{
180 {
181 Term(true);
182 Wait();
183 }
184 d->DecrRef();
185}
186
187void MythSystemLegacy::SetDirectory(const QString &directory)
188{
189 m_settings["SetDirectory"] = true;
190 m_directory = QString(directory);
191}
192
194{
195 if (!d || (GetStatus() != GENERIC_EXIT_START))
196 return false;
197
198 m_nice = nice;
199 return true;
200}
201
203{
204 if (!d || (GetStatus() != GENERIC_EXIT_START))
205 return false;
206
207 m_ioprio = prio;
208 return true;
209}
210
212void MythSystemLegacy::Run(std::chrono::seconds timeout)
213{
214 if (!d)
216
218 {
219 emit error(GetStatus());
220 return;
221 }
222
223 // Handle any locking of drawing, etc
224 HandlePreRun();
225
226 d->Fork(timeout);
227
229 {
230 m_semReady.acquire(1);
231 emit started();
232 d->Manage();
233 }
234 else
235 {
236 emit error(GetStatus());
237 }
238}
239
240// should there be a separate 'getstatus' call? or is using
241// Wait() for that purpose sufficient?
243{
244 if (!d)
246
247 if ((GetStatus() != GENERIC_EXIT_RUNNING) || GetSetting("RunInBackground"))
248 return GetStatus();
249
250 if (GetSetting("ProcessEvents"))
251 {
252 auto tt = (timeout > 0s)
253 ? SystemClock::now() + timeout
254 : SystemTime::max();
255 while (SystemClock::now() < tt)
256 {
257 // loop until timeout hits or process ends
258 if (m_semReady.tryAcquire(1,100))
259 {
260 m_semReady.release(1);
261 break;
262 }
263
264 qApp->processEvents();
265 }
266 }
267 else
268 {
269 if (timeout > 0s)
270 {
271 auto msec = duration_cast<std::chrono::milliseconds>(timeout);
272 if (m_semReady.tryAcquire(1, msec.count()))
273 m_semReady.release(1);
274 }
275 else
276 {
277 m_semReady.acquire(1);
278 m_semReady.release(1);
279 }
280 }
281 return GetStatus();
282}
283
285{
286 if (!d)
288
290 return;
291
292 d->Term(force);
293}
294
296{
297 if (!d)
299
301 return;
302
303#ifndef SIGTRAP /* For Mingw */
304#define SIGTRAP -1
305#endif
306 int posix_signal = SIGTRAP;
307 switch (sig)
308 {
309 case kSignalNone:
310 case kSignalUnknown:
311 break;
312 case kSignalHangup: posix_signal = SIGHUP; break;
313 case kSignalInterrupt: posix_signal = SIGINT; break;
314 case kSignalContinue: posix_signal = SIGCONT; break;
315 case kSignalQuit: posix_signal = SIGQUIT; break;
316 case kSignalSegfault: posix_signal = SIGSEGV; break;
317 case kSignalKill: posix_signal = SIGKILL; break;
318 case kSignalUser1: posix_signal = SIGUSR1; break;
319 case kSignalUser2: posix_signal = SIGUSR2; break;
320 case kSignalTerm: posix_signal = SIGTERM; break;
321 case kSignalStop: posix_signal = SIGSTOP; break;
322 }
323
324 // The default less switch above will cause a compiler warning
325 // if someone adds a signal without updating the switch, but in
326 // case that is missed print out a message.
327 if (SIGTRAP == posix_signal)
328 {
329 LOG(VB_SYSTEM, LOG_ERR,
330 QString("Programmer error: Unknown signal %1").arg(sig));
331 return;
332 }
333
334 d->Signal(posix_signal);
335}
336
337
339{
341 {
342 LOG(VB_SYSTEM, LOG_DEBUG, QString("status: %1").arg(m_status));
343 return;
344 }
345
347
348 if (flags & kMSRunBackground)
349 m_settings["RunInBackground"] = true;
350
351 if (m_command.endsWith("&"))
352 {
353 if (!GetSetting("RunInBackground"))
354 LOG(VB_SYSTEM, LOG_DEBUG, "Adding background flag");
355
356 // Remove the &
357 m_command.chop(1);
358 m_command = m_command.trimmed();
359 m_settings["RunInBackground"] = true;
360 m_settings["UseShell"] = true;
361 m_settings["IsInUI"] = false;
362 }
363
364 if (GetSetting("IsInUI"))
365 {
366 // Check for UI-only locks
367 m_settings["BlockInputDevs"] = ((flags & kMSDontBlockInputDevs) == 0U);
368 m_settings["DisableDrawing"] = ((flags & kMSDontDisableDrawing) == 0U);
369 m_settings["ProcessEvents"] = ((flags & kMSProcessEvents) != 0U);
370 m_settings["DisableUDP"] = ((flags & kMSDisableUDPListener) != 0U);
371 }
372
373 if (flags & kMSStdIn)
374 m_settings["UseStdin"] = true;
375 if (flags & kMSStdOut)
376 m_settings["UseStdout"] = true;
377 if (flags & kMSStdErr)
378 m_settings["UseStderr"] = true;
379 if (flags & kMSRunShell)
380 m_settings["UseShell"] = true;
381 if (flags & kMSAutoCleanup && GetSetting("RunInBackground"))
382 m_settings["AutoCleanup"] = true;
383 if (flags & kMSAnonLog)
384 m_settings["AnonLog"] = true;
385 if (flags & kMSLowExitVal)
386 m_settings["OnlyLowExitVal"] = true;
387 if (flags & kMSPropagateLogs)
388 m_settings["PropagateLogs"] = true;
389}
390
391QByteArray MythSystemLegacy::Read(int size)
392{
393 return m_stdbuff[1].read(size);
394}
395
396QByteArray MythSystemLegacy::ReadErr(int size)
397{
398 return m_stdbuff[2].read(size);
399}
400
402{
403 return m_stdbuff[1].buffer();
404}
405
407{
408 return m_stdbuff[2].buffer();
409}
410
415int MythSystemLegacy::Write(const QByteArray &ba)
416{
417 if (!GetSetting("UseStdin"))
418 return 0;
419
420 return m_stdbuff[0].write(ba.constData());
421}
422
424{
425 // This needs to be a send event so that the MythUI locks the input devices
426 // immediately instead of after existing events are processed
427 // since this function could be called inside one of those events.
428 if (GetSetting("BlockInputDevs"))
429 {
431 QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event);
432 }
433
434 // This needs to be a send event so that the listener is disabled
435 // immediately instead of after existing events are processed, since the
436 // listen server must be terminated before the spawned application tries
437 // to start its own
438 if (GetSetting("DisableUDP"))
439 {
441 QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event);
442 }
443
444 // This needs to be a send event so that the MythUI m_drawState change is
445 // flagged immediately instead of after existing events are processed
446 // since this function could be called inside one of those events.
447 if (GetSetting("DisableDrawing"))
448 {
450 QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event);
451 }
452}
453
455{
456 // Since this is *not* running in the UI thread (but rather the signal
457 // handler thread), we need to use postEvents
458 if (GetSetting("DisableDrawing"))
459 {
460 auto *event = new QEvent(MythEvent::kPopDisableDrawingEventType);
461 QCoreApplication::postEvent(gCoreContext->GetGUIObject(), event);
462 }
463
464 // This needs to be a post event so we do not try to start listening on
465 // the UDP ports before the child application has stopped and terminated
466 if (GetSetting("DisableUDP"))
467 {
468 auto *event = new QEvent(MythEvent::kEnableUDPListenerEventType);
469 QCoreApplication::postEvent(gCoreContext->GetGUIObject(), event);
470 }
471
472 // This needs to be a post event so that the MythUI unlocks input devices
473 // after all existing (blocked) events are processed and ignored.
474 if (GetSetting("BlockInputDevs"))
475 {
476 auto *event = new QEvent(MythEvent::kUnlockInputDevicesEventType);
477 QCoreApplication::postEvent(gCoreContext->GetGUIObject(), event);
478 }
479}
480
481QString MythSystemLegacy::ShellEscape(const QString &in)
482{
483 QString out = in;
484
485 if (out.contains("\""))
486 out = out.replace("\"", "\\\"");
487
488 if (out.contains("\'"))
489 out = out.replace("\'", "\\\'");
490
491 if (out.contains(" "))
492 {
493 out.prepend("\"");
494 out.append("\"");
495 }
496
497 return out;
498}
499
501 ReferenceCounter(debugName)
502{
503}
504
505uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
506{
507 flags |= kMSRunShell | kMSAutoCleanup;
508 auto *ms = new MythSystemLegacy(command, flags);
509 ms->Run(timeout);
510 uint result = ms->Wait(0s);
511 if (!ms->GetSetting("RunInBackground"))
512 delete ms;
513
514 return result;
515}
516
517uint myth_system(const QString &Command, const QStringList& Args, uint Flags,
518 std::chrono::seconds Timeout)
519{
520 Flags |= kMSRunShell | kMSAutoCleanup;
521 auto *ms = new MythSystemLegacy(Command, Args, Flags);
522 ms->Run(Timeout);
523 uint result = ms->Wait(0s);
524 if (!ms->GetSetting("RunInBackground"))
525 delete ms;
526
527 return result;
528}
529
530/*
531 * vim:ts=4:sw=4:ai:et:si:sts=4
532 */
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:131
#define SIGSTOP
Definition: compat.h:132
#define SIGHUP
Definition: compat.h:124
#define SIGQUIT
Definition: compat.h:125
unsigned int uint
Definition: compat.h:68
#define nice(x)
Definition: compat.h:112
#define SIGUSR1
Definition: compat.h:127
#define SIGUSR2
Definition: compat.h:128
#define SIGKILL
Definition: compat.h:126
@ 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:634
QString logPropagateArgs
Definition: logging.cpp:82
QStringList logPropagateArgList
Definition: logging.cpp:83
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