C++ common classes
General purpose classes for use across projects (Apache license)
executeshell.cpp
Go to the documentation of this file.
00001 /*
00002    Copyright [2010] [Richard Bross]
00003 
00004    Licensed under the Apache License, Version 2.0 (the "License");
00005    you may not use this file except in compliance with the License.
00006    You may obtain a copy of the License at
00007 
00008        http://www.apache.org/licenses/LICENSE-2.0
00009 
00010    Unless required by applicable law or agreed to in writing, software
00011    distributed under the License is distributed on an "AS IS" BASIS,
00012    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013    See the License for the specific language governing permissions and
00014    limitations under the License.
00015 */// rbross - 12/31/09
00016 
00017 #include <errno.h>
00018 #include "executeshell.h"
00019 #include <stdio.h>
00020 #include <string.h>
00021 #include <fcntl.h>
00022 
00023 #define STDOUT_BUFFER_SIZE      8192
00024 
00025 char ExecuteShell::szStdOut[ESHELL_FORMAT_SIZE];
00026 
00027 ExecuteShell::ExecuteShell()
00028 {
00029     ::memset(szStdOut, 0, sizeof (szStdOut));
00030 }
00031 
00032 ExecuteShell::~ExecuteShell()
00033 {
00034 }
00035 
00036 // Execute a command and return stdout in a string buffer
00037 // If a command is forked, we could sit and wait for stdout forever
00038 
00039 int ExecuteShell::Execute(const char *szCommand, string &sStdout, string *sStderr, const char *strForkExit)
00040 {
00041     FILE *pFile;
00042     int iRet = 0;
00043     int filedes[2];
00044     char strCommand[64];
00045     bool bStderr = true;
00046     bool bForkExit = false;
00047     char sReadBuffer[STDOUT_BUFFER_SIZE];
00048     int iExit;
00049 
00050     // Open file
00051     try
00052     {
00053         sStdout.clear();
00054         string sCommand = szCommand;
00055         ::memset(filedes, 0, sizeof (filedes));
00056         // Redirect stderr.  "pipe" creates a pipe with a read end and a write end
00057         if (sStderr == NULL || pipe(filedes) == -1)
00058             bStderr = false;
00059         else
00060         {
00061             ::sprintf(strCommand, " 2>&%d", filedes[1]);
00062             sCommand += strCommand;
00063             sStderr->clear();
00064         }
00065 
00066         // Execute command
00067         pFile = ::popen(sCommand.c_str(), "r");
00068         if (!pFile)
00069         {
00070             iRet = -1;
00071             throw (-1);
00072         }
00073 
00074         // Fill buffer if command writes to stdout
00075         // No blocking
00076         int fd = fileno(pFile); // Get file descriptor
00077         int flags = fcntl(fd, F_GETFL, 0); // Get flags
00078         flags |= O_NONBLOCK; // Add non-blocking
00079         fcntl(fd, F_SETFL, flags); // Set flags
00080         // Read
00081         while (!::feof(pFile))
00082         {
00083             if (::fgets(sReadBuffer, STDOUT_BUFFER_SIZE, pFile) != NULL)
00084             {
00085                 sStdout += sReadBuffer;
00086                 // If we know that we have been forked, we are waiting for an explicit string to exit
00087                 if (strForkExit)
00088                 {
00089                     if ((iExit = sStdout.find(strForkExit)) != string::npos)
00090                     { // We got that string, so set exit flag
00091                         sStdout.resize(iExit);
00092                         bForkExit = true;
00093                         break;
00094                     }
00095                 }
00096             }
00097             // This SHOULD work, but it only returns errno EAGAIN
00098             if (::ferror(pFile) && errno != EAGAIN && errno != EWOULDBLOCK)
00099                 break;
00100         }
00101 
00102         // Fill buffer if command writes to stderr and we did NOT get the fork exit flag
00103         if (bStderr && !bForkExit)
00104         {
00105             sStderr->clear();
00106             char cBuf;
00107             int flags = ::fcntl(filedes[0], F_GETFL, 0);
00108             ::fcntl(filedes[0], F_SETFL, flags | O_NONBLOCK);
00109             while (::read(filedes[0], &cBuf, 1) > 0)
00110                 *sStderr += cBuf;
00111         }
00112 
00113         // pclose returns the exit code of the process
00114         iRet = ::pclose(pFile);
00115     }
00116     catch (exception exc)
00117     {
00118         exc.what();
00119     }
00120 
00121     // Make sure that we close the pipes
00122     if (filedes[0])
00123         ::close(filedes[0]);
00124     if (filedes[1])
00125         ::close(filedes[1]);
00126 
00127     return (iRet);
00128 }
00129 
00130 
00131 // Find the executables that we need
00132 
00133 const char *ExecuteShell::FindBinary(const char *pName)
00134 {
00135     static string sExecPath;
00136     char sShellCmd[1024];
00137     const char *pFormat = "find %s -type f -name %s -perm -g+x,u+x";
00138     int iRet;
00139 
00140     // Format command.  First look in /usr
00141     ::memset(sShellCmd, 0, sizeof (sShellCmd));
00142     ::sprintf(sShellCmd, pFormat, "/usr", pName);
00143     iRet = Execute(sShellCmd, sExecPath);
00144     if (iRet) // No success. Look in /etc
00145     {
00146         ::sprintf(sShellCmd, pFormat, "/etc", pName);
00147         iRet = Execute(sShellCmd, sExecPath);
00148         if (iRet) // No success. Look in /bin
00149         {
00150             ::sprintf(sShellCmd, pFormat, "/bin", pName);
00151             iRet = Execute(sShellCmd, sExecPath);
00152         }
00153     }
00154 
00155     if (!iRet) // Success
00156     {
00157         int iIndex = sExecPath.find('\n', 0);
00158         if (iIndex != -1)
00159             sExecPath.resize(iIndex);
00160         return sExecPath.c_str();
00161     }
00162 
00163     // Fail
00164     return NULL;
00165 }
00166 
00167 
00168 // Write to standard out
00169 
00170 void ExecuteShell::WriteStdout(bool bNewline)
00171 {
00172     // One way or another, this WILL be 0 terminated
00173     if (bNewline)
00174     {
00175         szStdOut[ESHELL_FORMAT_SIZE - 3] = '\0';
00176         ::strcat(szStdOut, "\n\r");
00177     }
00178     else
00179         szStdOut[ESHELL_FORMAT_SIZE - 1] = '\0';
00180 
00181     if (::write(STDOUT_FILENO, szStdOut, ::strlen(szStdOut) - 1))
00182         return;
00183 }
00184 
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Defines