Index: mythtv/libs/libmythdb/mythsystem.cpp =================================================================== --- mythtv/libs/libmythdb/mythsystem.cpp (revision 27001) +++ mythtv/libs/libmythdb/mythsystem.cpp (working copy) @@ -12,6 +12,7 @@ #include #include #include // for kill() +#include // QT headers #include @@ -19,6 +20,7 @@ #include #include #include +#include # ifdef linux # include @@ -42,286 +44,497 @@ #include "mythverbose.h" #include "exitcodes.h" -#ifndef USING_MINGW -typedef struct { - QMutex mutex; - uint result; - time_t timeout; - bool background; -} PidData_t; +#define CLOSE(x) if( x >= 0 ) close(x) -typedef QMap PidMap_t; +typedef QMap MSMap_t; +typedef QMap pipeMap_t; -class MythSystemReaper : public QThread -{ - public: - void run(void); - uint waitPid( pid_t pid, time_t timeout, bool background = false, - bool processEvents = true ); - uint abortPid( pid_t pid ); - private: - PidMap_t m_pidMap; - QMutex m_mapLock; -}; +static class MythSystemManager *manager = NULL; -static class MythSystemReaper *reaper = NULL; +/********************************** + * MythSystemManager method defines + *********************************/ -void MythSystemReaper::run(void) +void MythSystemManager::run(void) { VERBOSE(VB_GENERAL, "Starting reaper thread"); // gCoreContext is set to NULL during shutdown, and we need this thread to // exit during shutdown. while( gCoreContext ) { - usleep(100000); + usleep(100000); // sleep 100ms time_t now = time(NULL); - PidMap_t::iterator i, next; - PidData_t *pidData; + MSMap_t::iterator i, next; + MythSystem *ms; pid_t pid; int count; + QList msList; + pipeMap_t pMap; m_mapLock.lock(); - count = m_pidMap.size(); - if( !count ) + count = m_pMap.size(); + if( count ) { + // no processes to manage, continue m_mapLock.unlock(); continue; } - for( i = m_pidMap.begin(); i != m_pidMap.end(); i = next ) + for( i = m_pMap.begin(); i != m_pMap.end(); i = next ) { - next = i + 1; - pidData = i.value(); - if( pidData->timeout == 0 || pidData->timeout > now ) + next = i + 1; + ms = i.value(); + // skip processes not past their timeout + if( ms->m_timeout == 0 || ms->m_timeout > now ) continue; - // Timed out + // terminate process pid = i.key(); - - next = m_pidMap.erase(i); - pidData->result = GENERIC_EXIT_TIMEOUT; + next = m_pMap.erase(i); + ms->m_status = GENERIC_EXIT_TIMEOUT; VERBOSE(VB_GENERAL, QString("Child PID %1 timed out, killing") .arg(pid)); kill(pid, SIGTERM); usleep(500); kill(pid, SIGKILL); - pidData->mutex.unlock(); + + // store process for cleanup after final buffering + msList.append(ms); + + if( ms->m_bufferedio ) + { + // output is buffered, queue pipes for processing + if( ms->m_usestdout ) + pMap.insert(ms->m_stdpipe[1], &(ms->m_stdbuff[0].buffer())); + if( ms->m_usestderr ) + pMap.insert(ms->m_stdpipe[2], &(ms->m_stdbuff[1].buffer())); + } } - count = m_pidMap.size(); + count = m_pMap.size(); m_mapLock.unlock(); - if (!count) - continue; + // check for terminated processes + if( count ) + { + // only run if there are active processes that need handling + int status; + while( (pid = waitpid(-1, &status, WNOHANG)) > 0 ) + { + // check for any newly exited process + m_mapLock.lock(); + if( !m_pMap.contains(pid) ) + { + // closed process not one currently being tracked, ignore + VERBOSE(VB_GENERAL, QString("Child PID %1 not found in map!") + .arg(pid)); + m_mapLock.unlock(); + continue; + } - /* There's at least one child to wait on */ - int status; + // pop closed process off managed list + ms = m_pMap.take(pid); + m_mapLock.unlock(); - pid = waitpid(-1, &status, WNOHANG); - if (pid <= 0) - { - if (pid < 0) - VERBOSE(VB_GENERAL, QString("waitpid() failed because %1") - .arg(strerror(errno))); - continue; + if( WIFEXITED(status) ) + { + // handle normal exit + ms->m_status = WEXITSTATUS(status); + VERBOSE(VB_GENERAL, QString("PID %1: exited: status=%2, result=%3") + .arg(pid) .arg(status) .arg(ms->m_status)); + } + else if( WIFSIGNALED(status) ) + { + // handle forced exit + int sig = WTERMSIG(status); + if( sig == 9 ) + ms->m_status = GENERIC_EXIT_ABORTED; + else if( sig == 11 ) + ms->m_status = GENERIC_EXIT_TERMINATED; + else + ms->m_status = GENERIC_EXIT_SIGNALLED; + VERBOSE(VB_GENERAL, QString("PID %1: signal: status=%2, result=%3, signal=%4") + .arg(pid) .arg(status) .arg(ms->m_status) .arg(sig)); + } + else + { + // handle abnormal exit (crash) + ms->m_status = GENERIC_EXIT_NOT_OK; + VERBOSE(VB_GENERAL, QString("PID %1: other: status=%2, result=%3") + .arg(pid) .arg(status) .arg(ms->m_status)); + } + + // store process for cleanup after final buffering + msList.append(ms); + + if( ms->m_bufferedio ) + { + // output is buffered, queue pipes for processing + if( ms->m_usestdout ) + pMap.insert(ms->m_stdpipe[1], &(ms->m_stdbuff[0].buffer())); + if( ms->m_usestderr ) + pMap.insert(ms->m_stdpipe[2], &(ms->m_stdbuff[1].buffer())); + } + else + // no processing needed, unlock mutex to allow + // MythSystem::Wait to acquire + ms->m_pmutex.unlock(); + } } + // pull any additional pipes that need buffering m_mapLock.lock(); - if (!m_pidMap.contains(pid)) + if( m_pMap.size() ) { - VERBOSE(VB_GENERAL, QString("Child PID %1 not found in map!") - .arg(pid)); - m_mapLock.unlock(); - continue; + for( i = m_pMap.begin(); i != m_pMap.end(); ++i ) + { + ms = i.value(); + if( ms->m_bufferedio ) + { + if( ms->m_usestdout ) + pMap.insert(ms->m_stdpipe[1], &(ms->m_stdbuff[0].buffer())); + if( ms->m_usestderr ) + pMap.insert(ms->m_stdpipe[2], &(ms->m_stdbuff[1].buffer())); + } + } } - - pidData = m_pidMap.value(pid); - m_pidMap.remove(pid); m_mapLock.unlock(); - if( WIFEXITED(status) ) + if( pMap.size() ) { - pidData->result = WEXITSTATUS(status); - VERBOSE(VB_GENERAL, QString("PID %1: exited: status=%2, result=%3") - .arg(pid) .arg(status) .arg(pidData->result)); + // build structures for select() + fd_set rfds; + timeval tv; + tv.tv_sec = 0; tv.tv_usec = 0; + + // build descriptor list + FD_ZERO(&rfds); + pipeMap_t::iterator j; + for( j = pMap.begin(); j != pMap.end(); ++j ) + FD_SET(j.key(), &rfds); + + int retval = select(pMap.size(), &rfds, NULL, NULL, &tv); + if( retval == -1 ) + VERBOSE(VB_GENERAL, QString("select() failed because of %1") + .arg(strerror(errno))); + else if( retval > 0 ) + { + // loop through returned descriptors + char buf[65536]; + for( j = pMap.begin(); j != pMap.end(); ++j ) + { + if( FD_ISSET(j.key(), &rfds) ) + { + // zero memory, and push read data to buffer + memset(&buf, 0, 65536); + if( read(j.key(), &buf, 65536) < 0 ) + continue; + j.value()->append(buf); + } + } + } } - else if( WIFSIGNALED(status) ) + + // handle any cleanup of closed processes + if( msList.size() ) { - pidData->result = GENERIC_EXIT_SIGNALLED; - VERBOSE(VB_GENERAL, QString("PID %1: signal: status=%2, result=%3, signal=%4") - .arg(pid) .arg(status) .arg(pidData->result) - .arg(WTERMSIG(status))); + QList::iterator k; + for( k = msList.begin(); k != msList.end(); ++k ) + { + (*k)->HandlePostRun(); + CLOSE((*k)->m_stdpipe[0]); // should these be left open for unbuffered operation? + CLOSE((*k)->m_stdpipe[1]); + CLOSE((*k)->m_stdpipe[2]); + (*k)->m_pmutex.unlock(); + } } - else - { - pidData->result = GENERIC_EXIT_NOT_OK; - VERBOSE(VB_GENERAL, QString("PID %1: other: status=%2, result=%3") - .arg(pid) .arg(status) .arg(pidData->result)); - } - if( pidData->background ) - delete pidData; - else - pidData->mutex.unlock(); + // is there a condition where i should be destroying the MythSystem objects? } } -uint MythSystemReaper::waitPid( pid_t pid, time_t timeout, bool background, - bool processEvents ) +void MythSystemManager::append(MythSystem *ms) { - PidData_t *pidData = new PidData_t; - uint result; + m_mapLock.lock(); + m_pMap.insert(ms->m_pid, ms); + m_mapLock.unlock(); +} - if( timeout > 0 ) - pidData->timeout = time(NULL) + timeout; - else - pidData->timeout = 0; +/******************************* + * MythSystem method defines + ******************************/ - pidData->background = background; +// need to finish up these constructors +MythSystem::MythSystem(const QString &command, uint flags) +{ + ProcessFlags(flags); +// if( m_useshell ) + m_command = QString(command); // i get the feeling this is wrong +/* else + { + //attempt split + }*/ +} - pidData->mutex.lock(); - m_mapLock.lock(); - m_pidMap.insert( pid, pidData ); - m_mapLock.unlock(); +MythSystem::MythSystem(const QString &command, + const QStringList &args, uint flags) +{ + // check to see that 'command' exists + ProcessFlags(flags); +/* if( m_useshell ) + { + // merge args to command + } + else + {*/ + m_command = QString(command); + m_args = QStringList(args); +// } +} - if( !background ) { - /* Now we wait for the thread to see the SIGCHLD */ - while( !pidData->mutex.tryLock(100) ) - if (processEvents) - qApp->processEvents(); +// QBuffers may also need freeing +MythSystem::~MythSystem(void) +{ + CLOSE(m_stdpipe[0]); + CLOSE(m_stdpipe[1]); + CLOSE(m_stdpipe[2]); +} - result = pidData->result; - delete pidData; +/** \fn MythSystem::Run() + * \brief Runs a command inside the /bin/sh shell. Returns immediately + */ +void MythSystem::Run(time_t timeout=0) +{ + // runs pre_flags + // forks child process + // spawns manager and hand off self + HandlePreRun(); + Fork(); - return( result ); + if( manager == NULL ) + { + manager = new MythSystemManager; + manager->start(); } - /* We are running in the background, the reaper will still catch the - child */ - return( GENERIC_EXIT_OK ); + if( timeout ) + m_timeout = time(NULL) + timeout; + m_pmutex.lock(); + + manager->append(this); } -uint MythSystemReaper::abortPid( pid_t pid ) +// should there be a separate 'getstatus' call? or is using +// Wait() for that purpose sufficient? +uint MythSystem::Wait(time_t timeout=0) { - PidData_t *pidData; - uint result; + if( (m_status != GENERIC_EXIT_RUNNING) || m_runinbackground ) + return m_status; - m_mapLock.lock(); - if (!m_pidMap.contains(pid)) + if( m_processevents ) { - VERBOSE(VB_GENERAL, QString("Child PID %1 not found in map!") - .arg(pid)); - m_mapLock.unlock(); - return GENERIC_EXIT_NOT_OK; + if( timeout > 0 ) + timeout += time(NULL); + else + timeout = 2147483648; // we'll have to change this by 2038 + // is there a time_t_max value? + + while( time(NULL) < timeout ) + { + // loop until timeout hits or process ends + if( m_pmutex.tryLock(100) ) + { + m_pmutex.unlock(); + break; + } + + qApp->processEvents(); + } } + else + { + if( timeout > 0 ) + if( m_pmutex.tryLock(timeout*1000) ) + m_pmutex.unlock(); + else + { + m_pmutex.lock(); + m_pmutex.unlock(); + } + } + return m_status; +} - pidData = m_pidMap.value(pid); - m_pidMap.remove(pid); - m_mapLock.unlock(); +void MythSystem::Term(bool force) const +{ + if( (m_status != GENERIC_EXIT_RUNNING) && (m_pid > 0) ) + return; + VERBOSE(VB_GENERAL, QString("Child PID %1 aborted, terminating") + .arg(m_pid)); + // send TERM signal to process + kill(m_pid, SIGTERM); + if( force ) + { + // send KILL if it does not exit within one second + Wait(1); + Kill(); + } +} - delete pidData; +void MythSystem::Kill() const +{ + if( (m_status != GENERIC_EXIT_RUNNING) && (m_pid > 0) ) + return; + VERBOSE(VB_GENERAL, QString("Child PID %1 aborted, killing") + .arg(m_pid)); + kill(m_pid, SIGKILL); +} - VERBOSE(VB_GENERAL, QString("Child PID %1 aborted, killing") .arg(pid)); - kill(pid, SIGTERM); - usleep(500); - kill(pid, SIGKILL); - result = GENERIC_EXIT_ABORTED; - return( result ); +void MythSystem::Stop() const +{ + if( (m_status != GENERIC_EXIT_RUNNING) && (m_pid > 0) ) + return; + VERBOSE(VB_GENERAL, QString("Child PID %1 suspended") + .arg(m_pid)); + kill(m_pid, SIGSTOP); } -#endif +void MythSystem::Cont() const +{ + if( (m_status != GENERIC_EXIT_RUNNING) && (m_pid > 0) ) + return; + VERBOSE(VB_GENERAL, QString("Child PID %1 resumed") + .arg(m_pid)); + kill(m_pid, SIGCONT); +} +void MythSystem::HangUp() const +{ + if( (m_status != GENERIC_EXIT_RUNNING) && (m_pid > 0) ) + return; + VERBOSE(VB_GENERAL, QString("Child PID %1 hung-up") + .arg(m_pid)); + kill(m_pid, SIGHUP); +} -/** \fn myth_system(const QString&, uint, uint) - * \brief Runs a system command inside the /bin/sh shell. - * - * Note: Returns GENERIC_EXIT_NOT_OK if it cannot execute the command. - * \return Exit value from command as an unsigned int in range [0,255]. - */ -uint myth_system(const QString &command, uint flags, uint timeout) +void MythSystem::USR1() const { - uint result; + if( (m_status != GENERIC_EXIT_RUNNING) && (m_pid > 0) ) + return; + VERBOSE(VB_GENERAL, QString("Child PID %1 USR1") + .arg(m_pid)); + kill(m_pid, SIGUSR1); +} - if( !(flags & kMSRunBackground) && command.endsWith("&") ) - { - VERBOSE(VB_GENERAL, "Adding background flag"); - flags |= kMSRunBackground; - } +void MythSystem::USR2() const +{ + if( (m_status != GENERIC_EXIT_RUNNING) && (m_pid > 0) ) + return; + VERBOSE(VB_GENERAL, QString("Child PID %1 USR2") + .arg(m_pid)); + kill(m_pid, SIGUSR2); +} - myth_system_pre_flags( flags ); +bool MythSystem::isBackground() const +{ + return m_runinbackground; +} -#ifndef USING_MINGW - pid_t pid; +void MythSystem::ProcessFlags(uint flags) +{ + m_runinbackground = false; + m_isinui = false; + m_blockinputdevs = false; + m_disabledrawing = false; + m_processevents = false; + m_usestdin = false; + m_usestdout = false; + m_usestderr = false; + m_bufferedio = false; + m_useshell = false; - pid = myth_system_fork( command, result ); + m_status = GENERIC_EXIT_START; - if( result == GENERIC_EXIT_RUNNING ) - result = myth_system_wait( pid, timeout, (flags & kMSRunBackground), - (flags & kMSProcessEvents) ); -#else - STARTUPINFO si; - PROCESS_INFORMATION pi; - QString LOC_ERR = QString("myth_system('%1'): Error: ").arg(command); - memset(&si, 0, sizeof(si)); - memset(&pi, 0, sizeof(pi)); - si.cb = sizeof(si); - QString cmd = QString("cmd.exe /c %1").arg(command); - if (!::CreateProcessA(NULL, cmd.toUtf8().data(), NULL, NULL, - FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) + if( flags & kMSRunBackground ) + m_runinbackground = true; + else if( m_command.endsWith("&") ) { - VERBOSE(VB_GENERAL, (LOC_ERR + "CreateProcess() failed because %1") - .arg(::GetLastError())); - result = MYTHSYSTEM__EXIT__EXECL_ERROR; + VERBOSE(VB_GENERAL, "Adding background flag"); + m_runinbackground = true; + m_useshell = true; } + + m_isinui = gCoreContext->HasGUI() && gCoreContext->IsUIThread(); + if( m_isinui ) + { + // Check for UI-only locks + m_blockinputdevs = !(flags & kMSDontBlockInputDevs); + m_disabledrawing = !(flags & kMSDontDisableDrawing); + m_processevents = flags & kMSProcessEvents; + } else { - if (::WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED) - VERBOSE(VB_GENERAL, - (LOC_ERR + "WaitForSingleObject() failed because %1") - .arg(::GetLastError())); - DWORD exitcode = GENERIC_EXIT_OK; - if (!GetExitCodeProcess(pi.hProcess, &exitcode)) - VERBOSE(VB_GENERAL, (LOC_ERR + - "GetExitCodeProcess() failed because %1") - .arg(::GetLastError())); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - result = exitcode; + m_processevents = false; } -#endif - myth_system_post_flags( flags ); - - return result; + if( flags & kMSStdIn ) + m_usestdin = true; + if( flags & kMSStdOut ) + m_usestdout = true; + if( flags & kMSStdErr ) + m_usestderr = true; + if( flags & kMSBuffered ) + m_bufferedio = true; + if( flags & kMSRunShell ) + m_useshell = true; } - -void myth_system_pre_flags(uint &flags) +QByteArray *MythSystem::_read(int size, int id) { - bool isInUi = gCoreContext->HasGUI() && gCoreContext->IsUIThread(); - if( isInUi ) - { - flags |= kMSInUi; - } + QByteArray *ret; + if( m_stdpipe[id] == -1 ) + ret->append(""); + else if( m_bufferedio ) + ret->append(m_stdbuff[id].read(size)); else { - // These locks only happen in the UI, and only if the flags are cleared - // so as we are not in the UI, set the flags to simplify logic further - // down - flags &= ~kMSInUi; - flags |= kMSDontBlockInputDevs; - flags |= kMSDontDisableDrawing; - flags &= ~kMSProcessEvents; + char *buf = (char *)calloc(size, 0); + read(m_stdpipe[id+1], buf, size); + ret->append(buf); + free(buf); } - if (flags & kMSRunBackground ) - flags &= ~kMSProcessEvents; + return ret; +} +QByteArray *MythSystem::_readall(int id) const +{ + QByteArray *ret; + if( (m_stdpipe[id+1] == -1) || !m_bufferedio ) + ret->append(""); + else + ret->append(m_stdbuff[id].buffer()); + + return ret; +} + +ssize_t MythSystem::Write(const QByteArray *ba) +{ + if( !m_usestdin ) + return 0; + + int size = ba->size(); + return write(m_stdpipe[0], ba->constData(), size); // if i dont free the constData, is that a memory leak? +} + + +void MythSystem::HandlePreRun() +{ // This needs to be a send event so that the MythUI locks the input devices // immediately instead of after existing events are processed // since this function could be called inside one of those events. - if( !(flags & kMSDontBlockInputDevs) ) + if( m_blockinputdevs ) { QEvent event(MythEvent::kLockInputDevicesEventType); QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event); @@ -330,19 +543,19 @@ // This needs to be a send event so that the MythUI m_drawState change is // flagged immediately instead of after existing events are processed // since this function could be called inside one of those events. - if( !(flags & kMSDontDisableDrawing) ) + if( m_disabledrawing ) { QEvent event(MythEvent::kPushDisableDrawingEventType); QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event); } } -void myth_system_post_flags(uint &flags) +void MythSystem::HandlePostRun() { // This needs to be a send event so that the MythUI m_drawState change is // flagged immediately instead of after existing events are processed // since this function could be called inside one of those events. - if( !(flags & kMSDontDisableDrawing) ) + if( m_disabledrawing ) { QEvent event(MythEvent::kPopDisableDrawingEventType); QCoreApplication::sendEvent(gCoreContext->GetGUIObject(), &event); @@ -350,20 +563,48 @@ // This needs to be a post event so that the MythUI unlocks input devices // after all existing (blocked) events are processed and ignored. - if( !(flags & kMSDontBlockInputDevs) ) + if( m_blockinputdevs ) { QEvent *event = new QEvent(MythEvent::kUnlockInputDevicesEventType); QCoreApplication::postEvent(gCoreContext->GetGUIObject(), event); } } - -#ifndef USING_MINGW -pid_t myth_system_fork(const QString &command, uint &result) +void MythSystem::Fork() { - QString LOC_ERR = QString("myth_system('%1'): Error: ").arg(command); - VERBOSE(VB_GENERAL, QString("Launching: %1") .arg(command)); + QString LOC_ERR = QString("myth_system('%1'): Error: ").arg(*m_command); + VERBOSE(VB_GENERAL, QString("Launching: %1").arg(*m_command)); + int p_stdin[] = {-1,-1}; + int p_stdout[] = {-1,-1}; + int p_stderr[] = {-1,-1}; + + /* set up pipes */ + if( m_usestdin ) + { + if( pipe(p_stdin) == -1 ) + { + VERBOSE(VB_GENERAL, (LOC_ERR + "stdin pipe() failed")); + m_status = GENERIC_EXIT_NOT_OK; + } + } + if( m_usestdout ) + { + if( pipe(p_stdout) == -1 ) + { + VERBOSE(VB_GENERAL, (LOC_ERR + "stdout pipe() failed")); + m_status = GENERIC_EXIT_NOT_OK; + } + } + if( m_usestderr ) + { + if( pipe(p_stderr) == -1 ) + { + VERBOSE(VB_GENERAL, (LOC_ERR + "stderr pipe() failed")); + m_status = GENERIC_EXIT_NOT_OK; + } + } + pid_t child = fork(); if (child < 0) @@ -371,44 +612,112 @@ /* Fork failed, still in parent */ VERBOSE(VB_GENERAL, (LOC_ERR + "fork() failed because %1") .arg(strerror(errno))); - result = GENERIC_EXIT_NOT_OK; - return -1; + m_status = GENERIC_EXIT_NOT_OK; } + else if( child > 0 ) + { + /* parent */ + m_pid = child; + m_status = GENERIC_EXIT_RUNNING; + + /* close unused pipe ends */ + CLOSE(p_stdin[0]); + CLOSE(p_stdout[1]); + CLOSE(p_stderr[1]); + + // store the rest + m_stdpipe[0] = p_stdin[1]; + m_stdpipe[1] = p_stdout[0]; + m_stdpipe[2] = p_stderr[0]; + } else if (child == 0) { /* Child - NOTE: it is not safe to use VERBOSE between the fork and * execl calls in the child. It causes occasional locking issues that * cause deadlocked child processes. */ - /* Close all open file descriptors except stdout/stderr */ - for (int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; i--) - close(i); - - /* Try to attach stdin to /dev/null */ - int fd = open("/dev/null", O_RDONLY); - if (fd >= 0) + /* handle standard input */ + if( p_stdin[0] >= 0 ) { - // Note: dup2() will close old stdin descriptor. - if (dup2(fd, 0) < 0) + /* try to attach stdin to input pipe - failure is fatal */ + if( dup2(p_stdin[0], 0) < 0 ) { - // Can't use VERBOSE due to locking fun. - QString message = LOC_ERR + + QString message = LOC_ERR + + "Cannot redirect input pipe to standard input." +ENO; + cerr << message.constData() << endl; + _exit(MYTHSYSTEM__EXIT__PIPE_FAILURE); + } + } + else + { + /* try to attach stdin to /dev/null */ + int fd = open("/dev/null", O_RDONLY); + if( fd >= 0 ) + { + if( dup2(fd, 0) < 0) + { + QString message = LOC_ERR + "Cannot redirect /dev/null to standard input," "\n\t\t\tfailed to duplicate file descriptor." + ENO; + cerr << message.constData() << endl; + } + } + else + { + QString message = LOC_ERR + "Cannot redirect /dev/null " + "to standard input, failed to open." + ENO; cerr << message.constData() << endl; } - close(fd); } + + /* handle standard output */ + if( p_stdout[1] >= 0 ) + { + /* try to attach stdout to output pipe - failure is fatal */ + if( dup2(p_stdout[1], 1) < 0) + { + QString message = LOC_ERR + + "Cannot redirect output pipe to standard output." +ENO; + cerr << message.constData() << endl; + _exit(MYTHSYSTEM__EXIT__PIPE_FAILURE); + } + } + + /* handle standard err */ + if( p_stderr[1] >= 0 ) + { + /* try to attach stderr to error pipe - failure is fatal */ + if( dup2(p_stderr[1], 2) < 0) + { + QString message = LOC_ERR + + "Cannot redirect error pipe to standard error." +ENO; + cerr << message.constData() << endl; + _exit(MYTHSYSTEM__EXIT__PIPE_FAILURE); + } + } + + /* Close all open file descriptors except stdin/stdout/stderr */ + for( int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; i-- ) + close(i); + + /* run command */ + if( m_useshell ) + { + execl("/bin/sh", "sh", "-c", m_command.toUtf8().constData(), (char *)0); + } else { - // Can't use VERBOSE due to locking fun. - QString message = LOC_ERR + "Cannot redirect /dev/null " - "to standard input, failed to open." + ENO; - cerr << message.constData() << endl; + QVector cmdargs; + QStringList::const_iterator it = m_args->constBegin(); + cmdargs << m_command; + while( it != m_args->constEnd() ) + { + cmdargs << *it->toLocal8Bit(); + it++; + } + cmdargs << 0; + execv(m_command, (char**)cmdargs.data()); } - - /* Run command */ - execl("/bin/sh", "sh", "-c", command.toUtf8().constData(), (char *)0); if (errno) { // Can't use VERBOSE due to locking fun. @@ -422,36 +731,28 @@ } /* Parent */ - result = GENERIC_EXIT_RUNNING; - return child; -} - -uint myth_system_wait(pid_t pid, uint timeout, bool background, - bool processEvents) -{ - if( reaper == NULL ) + if( m_status != GENERIC_EXIT_RUNNING ) { - reaper = new MythSystemReaper; - reaper->start(); + CLOSE(p_stdin[0]); + CLOSE(p_stdin[1]); + CLOSE(p_stdout[0]); + CLOSE(p_stdout[1]); + CLOSE(p_stderr[0]); + CLOSE(p_stderr[1]); } - VERBOSE(VB_GENERAL, QString("PID %1: launched%2") .arg(pid) - .arg(background ? " in the background, not waiting" : "")); - return reaper->waitPid(pid, timeout, background, processEvents); } -uint myth_system_abort(pid_t pid) +uint myth_system(const QString &command, uint flags, uint timeout) { - if( reaper == NULL ) - { - reaper = new MythSystemReaper; - reaper->start(); - } - VERBOSE(VB_GENERAL, QString("PID %1: aborted") .arg(pid)); - return reaper->abortPid(pid); + flags &= kMSRunShell; + MythSystem ms = MythSystem(command, flags & kMSRunShell); + ms.Run(timeout); + uint result = ms.Wait(); + return result; } -#endif + /* * vim:ts=4:sw=4:ai:et:si:sts=4 */ Index: mythtv/libs/libmythdb/exitcodes.h =================================================================== --- mythtv/libs/libmythdb/exitcodes.h (revision 27001) +++ mythtv/libs/libmythdb/exitcodes.h (working copy) @@ -20,11 +20,14 @@ #define GENERIC_EXIT_ABORTED 240 #define GENERIC_EXIT_RUNNING 239 #define GENERIC_EXIT_START 239 +#define GENERIC_EXIT_PIPE_FAILURE 238 +#define GENERIC_EXIT_TERMINATED 237 #define GENERIC_EXIT_CMD_NOT_FOUND 127 // libmyth #define MYTHSYSTEM__EXIT__EXECL_ERROR GENERIC_EXIT_EXECL_ERROR #define MYTHSYSTEM__EXIT__CMD_NOT_FOUND GENERIC_EXIT_CMD_NOT_FOUND +#define MYTHSYSTEM__EXIT__PIPE_FAILURE GENERIC_EXIT_PIPE_FAILURE // libmythtv #define CHANNEL__EXIT__EXECL_ERROR GENERIC_EXIT_EXECL_ERROR Index: mythtv/libs/libmythdb/mythsystem.h =================================================================== --- mythtv/libs/libmythdb/mythsystem.h (revision 27001) +++ mythtv/libs/libmythdb/mythsystem.h (working copy) @@ -13,23 +13,93 @@ kMSRunBackground = 0x00000004, //< run child in the background kMSProcessEvents = 0x00000008, //< process events while waiting kMSInUi = 0x00000010, //< the parent is in the UI + kMSStdIn = 0x00000011, //< allow access to stdin + kMSStdOut = 0x00000012, //< allow access to stdout + kMSStdErr = 0x00000014, //< allow access to stderr + kMSBuffered = 0x00000018, //< buffer the IO channels + kMSRunShell = 0x00000020, //< run process through bourne shell } MythSystemFlag; +class MPUBLIC MythSystem : public QObject + +class MythSystemManager : public QThread +{ + public: + void run(void); + void append(MythSystem *); + private: + MSMap_t m_pMap; + QMutex m_mapLock; +}; + +class MPUBLIC MythSystem : public QObject +{ + public: + MythSystem(const QString &, uint); + MythSystem(const QString &, const QStringList &, uint); + ~MythSystem(void); + + void Run(time_t); + uint Wait(time_t); + + ssize_t Write(const QByteArray*); + + void Term(bool) const; + void Kill() const; + void Stop() const; + void Cont() const; + void HangUp() const; + void USR1() const; + void USR2() const; + bool isBackground() const; + + friend class MythSystemManager; + + private: + void ProcessFlags(uint); + void HandlePreRun(); + void HandlePostRun(); + void Fork(); + QByteArray *_read(int, int); + QByteArray *_readall(int) const; + + uint m_status; + pid_t m_pid; + QMutex m_pmutex; + time_t m_timeout; + + const QString *m_command; + const QStringList *m_args; + + int m_stdpipe[3]; // should there be a means of hitting these directly? + QBuffer m_stdbuff[2]; // do these need to be allocated? + + // move to a struct to keep things clean? + // perhaps allow overloaded input using the struct + // rather than the bitwise + bool m_runinbackground; + bool m_isinui; + bool m_blockinputdevs; + bool m_disabledrawing; + bool m_processevents; + bool m_usestdin; + bool m_usestdout; + bool m_usestderr; + bool m_bufferedio; + bool m_useshell; + + public: + // moved down here for ordering + QByteArray *Read(int size) { return _read(size, 0); } + QByteArray *ReadErr(int size) { return _read(size, 1); } + QByteArray *ReadAll() const { return _readall(0); } + QByteArray *ReadAllErr() const { return _readall(1); } +}; + MPUBLIC unsigned int myth_system(const QString &command, uint flags = kMSNone, uint timeout = 0); -MPUBLIC void myth_system_pre_flags(uint &flags); -MPUBLIC void myth_system_post_flags(uint &flags); -#ifndef USING_MINGW -MPUBLIC pid_t myth_system_fork(const QString &command, uint &result); -MPUBLIC uint myth_system_wait(pid_t pid, uint timeout, bool background = false, - bool processEvents = true); -MPUBLIC uint myth_system_abort(pid_t pid); -#endif - -#endif - /* * vim:ts=4:sw=4:ai:et:si:sts=4 */ Index: mythtv/libs/libmythtv/channelbase.cpp =================================================================== --- mythtv/libs/libmythtv/channelbase.cpp (revision 27001) +++ mythtv/libs/libmythtv/channelbase.cpp (working copy) @@ -50,7 +50,7 @@ : m_pParent(parent), m_curchannelname(""), m_currentInputID(-1), m_commfree(false), m_cardid(0), - m_abort_change(false), m_changer_pid(-1) + m_abort_change(false) { m_tuneStatus = changeUnknown; m_tuneThread.tuner = this; @@ -68,9 +68,7 @@ { m_thread_lock.lock(); m_abort_change = true; -#ifndef USING_MINGW - myth_system_abort(m_changer_pid); -#endif + m_changer.Term(true); m_tuneCond.wakeAll(); m_thread_lock.unlock(); m_tuneThread.wait(); @@ -725,12 +723,9 @@ uint flags = kMSNone; uint result; - // Use myth_system, but since we need abort support, do it in pieces - myth_system_pre_flags(flags); - m_changer_pid = myth_system_fork(command, result); - if( result == GENERIC_EXIT_RUNNING ) - result = myth_system_wait(m_changer_pid, 30); - myth_system_post_flags(flags); + m_changer = MythSystem(command, flags); + m_changer.Run(30); + result = m_changer.Wait(); return( result == 0 ); #endif // !USING_MINGW Index: mythtv/libs/libmythtv/channelbase.h =================================================================== --- mythtv/libs/libmythtv/channelbase.h (revision 27001) +++ mythtv/libs/libmythtv/channelbase.h (working copy) @@ -167,11 +167,11 @@ QWaitCondition m_tuneCond; private: - mutable ChannelThread m_tuneThread; - Status m_tuneStatus; - QMutex m_thread_lock; - bool m_abort_change; - pid_t m_changer_pid; + mutable ChannelThread m_tuneThread; + Status m_tuneStatus; + QMutex m_thread_lock; + bool m_abort_change; + MythSystem m_changer; }; #endif