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 <ctime>
34 #include <fcntl.h>
35 #include <unistd.h>
36 
37 // QT headers
38 #include <QCoreApplication>
39 
40 // libmythbase headers
41 #include "referencecounter.h"
42 #include "mythcorecontext.h"
43 #include "mythevent.h"
44 #include "mythlogging.h"
45 #include "exitcodes.h"
46 
47 #if CONFIG_CYGWIN || defined(_WIN32)
48 #include "mythsystemwindows.h"
49 #else
50 #include "mythsystemunix.h"
51 #endif
52 
53 
54 /*******************************
55  * MythSystemLegacy method defines
56  ******************************/
57 
59 {
60  m_nice = 0;
61  m_ioprio = 0;
62 #if CONFIG_CYGWIN || defined(_WIN32)
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 
77 MythSystemLegacy::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 
90 void 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 
121 MythSystemLegacy::MythSystemLegacy(const QString &command,
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 
135 void 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))
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 
190 void 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 
216 {
217  if (!d)
219 
220  if (GetStatus() != GENERIC_EXIT_START)
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  if (timeout > 0)
256  timeout += time(nullptr);
257 
258  while (!timeout || time(nullptr) < timeout)
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 > 0)
273  {
274  if (m_semReady.tryAcquire(1, timeout*1000))
275  m_semReady.release(1);
276  }
277  else
278  {
279  m_semReady.acquire(1);
280  m_semReady.release(1);
281  }
282  }
283  return GetStatus();
284 }
285 
287 {
288  if (!d)
290 
292  return;
293 
294  d->Term(force);
295 }
296 
298 {
299  if (!d)
301 
303  return;
304 
305  int posix_signal = SIGTRAP;
306  switch (sig)
307  {
308  case kSignalNone: break;
309  case kSignalUnknown: break;
310  case kSignalHangup: posix_signal = SIGHUP; break;
311  case kSignalInterrupt: posix_signal = SIGINT; break;
312  case kSignalContinue: posix_signal = SIGCONT; break;
313  case kSignalQuit: posix_signal = SIGQUIT; break;
314  case kSignalSegfault: posix_signal = SIGSEGV; break;
315  case kSignalKill: posix_signal = SIGKILL; break;
316  case kSignalUser1: posix_signal = SIGUSR1; break;
317  case kSignalUser2: posix_signal = SIGUSR2; break;
318  case kSignalTerm: posix_signal = SIGTERM; break;
319  case kSignalStop: posix_signal = SIGSTOP; break;
320  }
321 
322  // The default less switch above will cause a compiler warning
323  // if someone adds a signal without updating the switch, but in
324  // case that is missed print out a message.
325  if (SIGTRAP == posix_signal)
326  {
327  LOG(VB_SYSTEM, LOG_ERR,
328  QString("Programmer error: Unknown signal %1").arg(sig));
329  return;
330  }
331 
332  d->Signal(posix_signal);
333 }
334 
335 
337 {
339  {
340  LOG(VB_SYSTEM, LOG_DEBUG, QString("status: %1").arg(m_status));
341  return;
342  }
343 
345 
346  if (flags & kMSRunBackground)
347  m_settings["RunInBackground"] = true;
348 
349  if (m_command.endsWith("&"))
350  {
351  if (!GetSetting("RunInBackground"))
352  LOG(VB_SYSTEM, LOG_DEBUG, "Adding background flag");
353 
354  // Remove the &
355  m_command.chop(1);
356  m_command = m_command.trimmed();
357  m_settings["RunInBackground"] = true;
358  m_settings["UseShell"] = true;
359  m_settings["IsInUI"] = false;
360  }
361 
362  if (GetSetting("IsInUI"))
363  {
364  // Check for UI-only locks
365  m_settings["BlockInputDevs"] = !(flags & kMSDontBlockInputDevs);
366  m_settings["DisableDrawing"] = !(flags & kMSDontDisableDrawing);
367  m_settings["ProcessEvents"] = flags & kMSProcessEvents;
368  m_settings["DisableUDP"] = flags & kMSDisableUDPListener;
369  }
370 
371  if (flags & kMSStdIn)
372  m_settings["UseStdin"] = true;
373  if (flags & kMSStdOut)
374  m_settings["UseStdout"] = true;
375  if (flags & kMSStdErr)
376  m_settings["UseStderr"] = true;
377  if (flags & kMSRunShell)
378  m_settings["UseShell"] = true;
379  if (flags & kMSAutoCleanup && GetSetting("RunInBackground"))
380  m_settings["AutoCleanup"] = true;
381  if (flags & kMSAnonLog)
382  m_settings["AnonLog"] = true;
383  if (flags & kMSLowExitVal)
384  m_settings["OnlyLowExitVal"] = true;
385  if (flags & kMSPropagateLogs)
386  m_settings["PropagateLogs"] = true;
387 }
388 
389 QByteArray MythSystemLegacy::Read(int size)
390 {
391  return m_stdbuff[1].read(size);
392 }
393 
394 QByteArray MythSystemLegacy::ReadErr(int size)
395 {
396  return m_stdbuff[2].read(size);
397 }
398 
399 QByteArray& MythSystemLegacy::ReadAll(void)
400 {
401  return m_stdbuff[1].buffer();
402 }
403 
405 {
406  return m_stdbuff[2].buffer();
407 }
408 
413 int MythSystemLegacy::Write(const QByteArray &ba)
414 {
415  if (!GetSetting("UseStdin"))
416  return 0;
417 
418  return m_stdbuff[0].write(ba.constData());
419 }
420 
422 {
423  // This needs to be a send event so that the MythUI locks the input devices
424  // immediately instead of after existing events are processed
425  // since this function could be called inside one of those events.
426  if (GetSetting("BlockInputDevs"))
427  {
429  QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event);
430  }
431 
432  // This needs to be a send event so that the listener is disabled
433  // immediately instead of after existing events are processed, since the
434  // listen server must be terminated before the spawned application tries
435  // to start its own
436  if (GetSetting("DisableUDP"))
437  {
439  QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event);
440  }
441 
442  // This needs to be a send event so that the MythUI m_drawState change is
443  // flagged immediately instead of after existing events are processed
444  // since this function could be called inside one of those events.
445  if (GetSetting("DisableDrawing"))
446  {
448  QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event);
449  }
450 }
451 
453 {
454  // Since this is *not* running in the UI thread (but rather the signal
455  // handler thread), we need to use postEvents
456  if (GetSetting("DisableDrawing"))
457  {
458  QEvent *event = new QEvent(MythEvent::kPopDisableDrawingEventType);
459  QCoreApplication::postEvent(gCoreContext->GetGUIObject(), event);
460  }
461 
462  // This needs to be a post event so we do not try to start listening on
463  // the UDP ports before the child application has stopped and terminated
464  if (GetSetting("DisableUDP"))
465  {
466  QEvent *event = new QEvent(MythEvent::kEnableUDPListenerEventType);
467  QCoreApplication::postEvent(gCoreContext->GetGUIObject(), event);
468  }
469 
470  // This needs to be a post event so that the MythUI unlocks input devices
471  // after all existing (blocked) events are processed and ignored.
472  if (GetSetting("BlockInputDevs"))
473  {
474  QEvent *event = new QEvent(MythEvent::kUnlockInputDevicesEventType);
475  QCoreApplication::postEvent(gCoreContext->GetGUIObject(), event);
476  }
477 }
478 
479 QString MythSystemLegacy::ShellEscape(const QString &in)
480 {
481  QString out = in;
482 
483  if (out.contains("\""))
484  out = out.replace("\"", "\\\"");
485 
486  if (out.contains("\'"))
487  out = out.replace("\'", "\\\'");
488 
489  if (out.contains(" "))
490  {
491  out.prepend("\"");
492  out.append("\"");
493  }
494 
495  return out;
496 }
497 
499  ReferenceCounter(debugName)
500 {
501 }
502 
503 uint myth_system(const QString &command, uint flags, uint timeout)
504 {
505  flags |= kMSRunShell | kMSAutoCleanup;
506  MythSystemLegacy *ms = new MythSystemLegacy(command, flags);
507  ms->Run(timeout);
508  uint result = ms->Wait(0);
509  if (!ms->GetSetting("RunInBackground"))
510  delete ms;
511 
512  return result;
513 }
514 
515 extern "C" {
516  unsigned int myth_system_c(char *command, uint flags, uint timeout)
517  {
518  QString cmd(command);
519  return myth_system(cmd, flags, timeout);
520  }
521 }
522 
523 /*
524  * vim:ts=4:sw=4:ai:et:si:sts=4
525  */
unsigned int myth_system_c(char *command, uint flags, uint timeout)
virtual void Term(bool force=false)=0
void Run(time_t timeout=0)
Runs a command inside the /bin/sh shell. Returns immediately.
avoid disabling UI drawing
Definition: mythsystem.h:35
allow access to stdin
Definition: mythsystem.h:38
automatically delete if backgrounded
Definition: mythsystem.h:43
static QString ShellEscape(const QString &str)
allow access to stdout
Definition: mythsystem.h:39
virtual void Manage(void)=0
#define SIGSTOP
Definition: compat.h:214
bool HasGUI(void) const
General purpose reference counter.
MythSystemLegacyPrivate * d
run child in the background
Definition: mythsystem.h:36
#define SIGQUIT
Definition: compat.h:207
void error(uint status)
QString logPropagateArgs
Definition: logging.cpp:89
QByteArray Read(int size)
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void SetCommand(const QString &, uint)
Resets an existing MythSystemLegacy object to a new command.
MythSystemLegacyPrivate(const QString &debugName)
static Type kPushDisableDrawingEventType
Definition: mythevent.h:70
void initializePrivate(void)
process events while waiting
Definition: mythsystem.h:37
static Type kPopDisableDrawingEventType
Definition: mythevent.h:71
add arguments for MythTV log propagation
Definition: mythsystem.h:50
anonymize the logs
Definition: mythsystem.h:42
void ProcessFlags(uint flags)
void Term(bool force=false)
allow exit values 0-127 only
Definition: mythsystem.h:45
bool SetNice(int nice)
virtual bool ParseShell(const QString &cmd, QString &abscmd, QStringList &args)=0
#define SIGKILL
Definition: compat.h:208
static Type kLockInputDevicesEventType
Definition: mythevent.h:72
run process through shell
Definition: mythsystem.h:41
#define GENERIC_EXIT_CMD_NOT_FOUND
Command not found.
Definition: exitcodes.h:12
QByteArray & ReadAll()
bool logPropagateQuiet(void)
Check if we are propagating a "--quiet".
Definition: logging.cpp:709
void SetDirectory(const QString &)
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
#define GENERIC_EXIT_NO_HANDLER
No MythSystemLegacy Handler.
Definition: exitcodes.h:27
#define GENERIC_EXIT_RUNNING
Process is running.
Definition: exitcodes.h:25
#define nice(x)
Definition: compat.h:188
bool SetIOPrio(int prio)
uint myth_system(const QString &command, uint flags, uint timeout)
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:99
allow access to stderr
Definition: mythsystem.h:40
#define SIGUSR1
Definition: compat.h:209
disable MythMessage UDP listener for the duration of application.
Definition: mythsystem.h:48
virtual void Fork(time_t timeout)=0
uint Wait(time_t timeout=0)
#define GENERIC_EXIT_START
MythSystemLegacy process starting.
Definition: exitcodes.h:35
static Type kEnableUDPListenerEventType
Definition: mythevent.h:76
static Type kDisableUDPListenerEventType
Definition: mythevent.h:75
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QByteArray ReadErr(int size)
#define SIGUSR2
Definition: compat.h:210
int Write(const QByteArray &)
This writes to the standard input of the program being run.
virtual void Signal(int sig)=0
#define SIGHUP
Definition: compat.h:206
QByteArray & ReadAllErr()
MythSignal
Definition: mythsystem.h:54
void Signal(MythSignal)
MythSystemLegacy(QObject *=nullptr)
bool GetSetting(const char *setting)
#define GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:15
#define SIGCONT
Definition: compat.h:213
QObject * GetGUIObject(void)
static Type kUnlockInputDevicesEventType
Definition: mythevent.h:73
QStringList logPropagateArgList
Definition: logging.cpp:90
void started(void)
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:34