bool ProcessInstance::UpdateParameters()

in source/code/scxsystemlib/process/processinstance.cpp [2285:2733]


    bool ProcessInstance::UpdateParameters(void)
    {
#if defined(linux)
        char procCmdName[MAXPATHLEN];
        std::string parambuf;
        bool bFirstParam = true;
        snprintf(procCmdName, sizeof(procCmdName), "/proc/%u/cmdline", static_cast<unsigned int>(m_pid));
        ifstream file(procCmdName);
        if (!file) { return false; } // Process has died, or does no support this
        m_params.clear();
        while(getline(file, parambuf, '\0')) {
            // Interface says two consecutive null bytes ends list; honor that
            // (We use flag to tell if we've read at least one parameter)
            if (0 == parambuf.length() && !bFirstParam)
            {
                break;
            }
            m_params.push_back(parambuf);
            bFirstParam = false;
        }
        return true;
#elif defined(sun)
        bool fRet = true;
        std::string psinfoModule;
        std::string psinfoArgs;
        static SCXCoreLib::LogSuppressor suppressor(SCXCoreLib::eWarning, SCXCoreLib::eTrace);

        // Define arbitary maximum values for parameters on Solaris:
        //   MAX_PARAMETERS:    Maximum number of parameters on a command line
        //   MAX_PARAM_LENGTH:  Maximum length of all parameters (in total) on a command line
        //
        // If these maximums are exceeded, we'll log a one-time error and ignore the parameters
        //
        //
        // WI 26809 (Resolve BVT failures on Solaris 10 x86 machines)
        //
        // Apparently, at times, the argv length is longer than anticipated.  Raised maximum
        // space to allow for this.  A posting on comp.os indicated that the true maximum
        // command line length is 2MB, but I find that very difficult to believe.  Raised it
        // to 1MB to see how that goes.
        //
        // Additionally, we can never read 64-bit processes (since we're built in 32-bit mode)
        // successfully.  In this case, read/pread operations will fail with EOVERFLOW.  When
        // this happens, we log a HYSTERICAL error and bag, which is fine (we'll fallback to use
        // shorter parameters in this case).
        //
        //
        // WI 50210 (Solaris 10 Sparc Agent: An error "argvecsz too large" sometimes occurs)
        //
        // Additional testing indicates that the maximum size for the argument list (at least
        // from the perspective of what can be retrieved, which is all we care about here) is
        // well under 1MB.  Additionally, this limit on the Linux platforms is 4k (4096 bytes);
        // parameters can be much longer than that, but can't be retrieved longer than that.
        //
        // Strangely, this problem (on a test system) fails consistently on process dtlogin.
        // We receive this error on the log file:
        //
        //   2011-10-10T20:49:27,329Z Error      [scx.core.common.pal.system.process.processinstance:21811:2] GetParameters: Process /proc/660/as argvecsz too large, argvecsz = 4495126
        //
        // This indicates that the argvecsz (from the process header) is 4 megabytes, but 'ps'
        // displays this process as follows:
        //
        //   bash-3.00# ps -ef | grep 660
        //   root   660     1   0   Aug 15 ?           0:00 /usr/dt/bin/dtlogin -daemon
        //   root 21814  8481   0 13:50:05 pts/1       0:00 grep 660
        //
        // Given results of testing, I'm comfortable with the 1MB limit here.  I'll change
        // the above to a Warning (rather than an error), and keep the limit of one warning
        // per process.  Making this a warning will stop the group aborts from testing, and
        // this should be put to bed.
        //
        // If we get the above warning, we'll use our fallback mechanism (pr_psargs from file
        // /proc/<pid>/psinfo).  It's pretty short in maximum length, but better than nothing.

        const int MAX_PARAMETERS = 256;
        const int MAX_PARAM_LENGTH = 1048576;   // 1 MB

        char procAsName[MAXPATHLEN];
        const int argc = m_psinfo.pr_argc;
        const uintptr_t argv_addr = m_psinfo.pr_argv;
        const uintptr_t envp_addr = m_psinfo.pr_envp;

        // Get initial argv.
        //
        // Note: This serves as the "default" set of arguments.  The problem
        // with this is that, on the Solaris platform, this can be "truncated".
        // So we read the "/proc/<pid>/as" file to get the complete set of
        // arguments but, if it fails, we fall back to this set.
        string initialcmd(m_psinfo.pr_psargs);

        // Split initialcmd into module name and args.
        std::string delims=" \t";

        // May seem over-cautious, but we should skip any leading spaces.
        std::string::size_type fieldBegin = initialcmd.find_first_not_of(delims, 0);

        if (std::string::npos != fieldBegin)
        {
            std::string::size_type substrlength = std::string::npos;

            // Find next delimiter after first token
            std::string::size_type fieldEnd = initialcmd.find_first_of(delims, fieldBegin);

            // First token is module.
            if (std::string::npos != fieldEnd)
            {
                substrlength = (fieldEnd - fieldBegin);
            }

            psinfoModule.assign(initialcmd.substr(fieldBegin, substrlength));

            // Only args remain
            fieldBegin = initialcmd.find_first_not_of(delims, fieldEnd);
            if (std::string::npos != fieldBegin)
            {
                // Collect the remaining sub-string.
                psinfoArgs.assign(initialcmd.substr(fieldBegin));
            }
        }

        if (0 == argv_addr || argc == 0) {
            return StoreModuleAndArgs(StrFromUTF8(psinfoModule), StrFromUTF8(psinfoArgs));
        }

        snprintf(procAsName, sizeof(procAsName), "/proc/%u/as", static_cast<unsigned int>(m_pid));
        SCX_LOGHYSTERICAL(m_log, StrAppend(L"GetParameters: Process filename = ", procAsName));

        int asfd = open(procAsName, O_RDONLY);
        if (asfd < 0)
        {
            // Failed to open file. Most likely the process died.
            SCX_LOGHYSTERICAL(m_log, StrAppend(L"GetParameters: Error opening process file, err=", errno));
            return StoreModuleAndArgs(StrFromUTF8(psinfoModule), StrFromUTF8(psinfoArgs));
        }

        AutoClose _fd(m_log, asfd);

        /* Read the full argv vector */
        if (argc <= MAX_PARAMETERS)
        {
            SCX_LOGHYSTERICAL(m_log, StrAppend(L"GetParameters: Allocating arg_vec vector of size: ", argc));
        }
        else
        {
            SCXCoreLib::SCXLogSeverity severity(suppressor.GetSeverity(StrFromUTF8(procAsName)));
            SCX_LOG(m_log, severity, StrAppend(
                        StrAppend(L"GetParameters: Process ", procAsName),
                        StrAppend(L" argc too large, argc = ", argc)));
            return StoreModuleAndArgs(StrFromUTF8(psinfoModule), StrFromUTF8(psinfoArgs));
        }

        vector<uintptr_t> arg_vec(argc);
        int a;
        if (pread(asfd, &arg_vec[0], argc * sizeof(uintptr_t), argv_addr) < 0)
        {
            // Error reading data. Throw something? At least write in the log
            // cout << "Failed to read arg vector from " << procAsName << endl;
            SCX_LOGHYSTERICAL(m_log, StrAppend(
                StrAppend(L"GetParameters: Failed to read arg vector from ", procAsName),
                StrAppend(L", err=", errno)));
            return StoreModuleAndArgs(StrFromUTF8(psinfoModule), StrFromUTF8(psinfoArgs));
        }

        /* The idea here is that the environment varaibles are stored after the argv
           strings and we can thus read the whole area at once. But for that to work we 
           need the position of the first item in the enviroment.
        */

        uintptr_t env1 = 0;
        if (envp_addr) {
            if (pread(asfd, &env1, sizeof(uintptr_t), envp_addr) < 0)
            {
                // Error reading data. Throw something? At least write in the log
                // cout << "Failed to read envp vector from " << procAsName << endl;
                SCX_LOGHYSTERICAL(m_log, StrAppend(
                    StrAppend(L"GetParameters: Failed to read envp vector from ", procAsName),
                    StrAppend(L", err=", errno)));
                return StoreModuleAndArgs(StrFromUTF8(psinfoModule), StrFromUTF8(psinfoArgs));
            }
        }

        off_t argvecbase = arg_vec[0]; // Storage area in target coordinates
        // If env1 = 0: problem = " (Can't compute argv area)";
        size_t argvecsz = env1 ? env1 - argvecbase : 256; 
    
        // Test that the CMD exists. (Some programs, incl scxcimserver, clobbers it.)
        if (0 == argvecbase) {        // problem = " (Clobbered its own arguments)";
            SCX_LOGHYSTERICAL(m_log, L"GetParameters: Process clobbered it's own arguments");
            return StoreModuleAndArgs(StrFromUTF8(psinfoModule), StrFromUTF8(psinfoArgs));
        }

        /* Now read full argv area in one swoop */
        if (argvecsz <= MAX_PARAM_LENGTH)
        {
            SCX_LOGHYSTERICAL(m_log, StrAppend(L"GetParameters: Allocating argvarea vector of size: ", argvecsz));
        }
        else
        {
            SCXCoreLib::SCXLogSeverity severity(suppressor.GetSeverity(StrFromUTF8(procAsName)));
            SCX_LOG(m_log, severity, StrAppend(
                        StrAppend(L"GetParameters: Process ", procAsName),
                        StrAppend(L" argvecsz too large, argvecsz = ", argvecsz)));
            return StoreModuleAndArgs(StrFromUTF8(psinfoModule), StrFromUTF8(psinfoArgs));
        }

        vector<char> argvarea(argvecsz);
        if (pread(asfd, &argvarea[0], argvecsz, argvecbase) < 0)
        {
            // Error reading data. Throw something? At least write in the log
            // cout << "Failed to read argv area from " << procAsName << endl;
            SCX_LOGHYSTERICAL(m_log, StrAppend(
                StrAppend(L"GetParameters: Failed to read argv vector from ", procAsName),
                StrAppend(L", err=", errno)));
            return StoreModuleAndArgs(StrFromUTF8(psinfoModule), StrFromUTF8(psinfoArgs));
        }

        argvarea[argvecsz - 1] = '\0';

        /* Verify that the argv read is similar to the potentially truncated version */
        m_params.clear();
        for (int i=0; i < argc; ++i) {
            // Check that this argument pointer is valid
            if (0 == arg_vec[i]) {
                // Nope. No more arguments, regardless of what argc may say
                break;
            }
            int idx = arg_vec[i] - argvecbase;
            if (idx < 0) {
                SCX_LOGHYSTERICAL(m_log, StrAppend(L"GetParameters: Buffer underflow: ", idx));
                break;
            }
            if (idx >= argvecsz) {
                SCX_LOGHYSTERICAL(m_log, StrAppend(L"GetParameters: Buffer overflow: ", idx));
                break;
            }

            // Sicne we're reading an address space that may be changed by the process
            // We log what address we're reading so any mysterious crashes and/or
            // access violations may be easier to find if this happens.
            SCX_LOGHYSTERICAL(m_log, StrAppend(StrAppend(StrAppend(L"GetParameters: arg_vec[", i), L"] = "), static_cast<ulong>(arg_vec[i])));
            
            string arg;
            if (0 == i) { /* The CMD part is a special case */
                arg = &argvarea[0];
            }
            else {
                arg = &argvarea[idx];
            }

            while ( ! initialcmd.empty() &&
                   (initialcmd[0] == ' ' ||
                    initialcmd[0] == '\t' ||
                    initialcmd[0] == '\n')) {
                initialcmd = initialcmd.substr(1);
            }
            if ( ! initialcmd.empty() &&
                arg.substr(0, initialcmd.length()) != initialcmd.substr(0, arg.length())) {
                // The process can manipulate its own string area. We try to detect that
                // by comparing the argv[i] with what was stored in /#/psinfo.
                return true;
            }
            if (arg.length() >= initialcmd.length()) {
                initialcmd = "";
            }
            else {
                initialcmd = initialcmd.substr(arg.length());
            }
            SCX_LOGHYSTERICAL(m_log, StrAppend(StrAppend(StrAppend(StrAppend(StrAppend(L"GetParameters: arg_vec[", i), L"] = "), static_cast<ulong>(arg_vec[i])),L", Parameter Value: "), StrFromUTF8(arg)));
            m_params.push_back(arg);
        }

        return true;
#elif defined(hpux)
        m_log = SCXLogHandleFactory::GetLogHandle(moduleIdentifier);
        bool fRet = false;
        char cmdbuf[1024];
        int cmdlen = 0;
        const char* cmdline = m_pstatus.pst_cmd;
        char pathname[MAXPATHLEN + 1] = { '\0' };
        int pathlen = 0;
        std::string ucomm;

        // Check that command line makes sense
        // Note: PST_CLEN is 64 bytes. If we need more of the command line we
        // can use pstat_getcommandline() and get up to 1020 chars.

        const char *endcmd = static_cast<const char*>(memchr(m_pstatus.pst_cmd, '\0', PST_CLEN));
        if (endcmd == 0) { return false; }
        cmdline = m_pstatus.pst_cmd;
        cmdlen  = endcmd - cmdline;

        // If the pst_cmd field is full, then there is a risk that it was truncated.
        // Let's get the longer version with pstat_getcommandline.

        if (cmdlen == PST_CLEN - 1) {
            cmdline = cmdbuf;
            cmdlen = pstat_getcommandline(cmdbuf, sizeof(cmdbuf), 1, 
                                          static_cast<unsigned int>(m_pid));

            if (cmdlen < 0) {
                /* Race: Process may already have died. Use short version instead. */
                if (ESRCH == errno) {
                    cmdline = m_pstatus.pst_cmd;
                    cmdlen  = endcmd - cmdline;
                } else {
                    throw SCXErrnoException(L"pstat_getcommandline", errno, SCXSRCLOCATION);
                }
            }
        }
        
        std::string c(cmdline, cmdlen);
        size_t firstSpace = c.find_first_of(" ");
        c = c.substr(0, firstSpace);
        size_t slashPos = c.find_last_of("/");
        slashPos = (slashPos == std::string::npos) ? 0 : slashPos + 1;
        m_name = StrFromUTF8(c.substr(slashPos));

        fRet = true;

        pathlen = pstat_getpathname(pathname, MAXPATHLEN, const_cast<pst_fid *>(&(m_pstatus.pst_fid_text)));
        if (pathlen > 0)
        {
            ucomm.assign(pathname, pathlen);
            SCX_LOGTRACE(m_log, wstring(L"Getting pathname from pstat_getpathname: ") + StrFromUTF8(pathname));
        }

        if (ucomm.empty())
        {
            // No executable file found.  Find from name.
            if (!m_name.empty())
            {
                if (!c.empty())
                {
                    m_modulePath = StrFromUTF8(c);
                }
                else if (!FindModuleFromPath(m_name))
                {
                    fRet = false;
                }
                SCX_LOGTRACE(m_log, wstring(L"Getting pathname from CommandLine: ") + m_modulePath);
            }
        }
        else if(wstring::npos == ucomm.find('/'))
        {
            // Executable file not fully qualified.  Find fully qualified file.
            if (!ucomm.empty() && !FindModuleFromPath(StrFromUTF8(ucomm)))
            {
                fRet = false;
            }
        }
        else
        {
            m_modulePath = StrFromUTF8(ucomm);
        }

        if (m_modulePath.empty() && m_pstatus.pst_ucomm[0] != '\0')
        {
            m_modulePath = StrFromUTF8(m_pstatus.pst_ucomm);
        }

        // First entry (argv[0]) is the basename
        // and then each string separated with one space is a new param
        // Note that a commandline parameter with spaces in it can't be distinguished
        // from multiple parameters. There's no way around it.

        c.assign(cmdline, cmdlen);
        std::vector<std::wstring> paramList;
        StrTokenize(StrFromUTF8(c), paramList, L" ", false, false, false);

        // Copy paramList back into m_params
        // TODO: Sloppy - what's involved to change m_params to a std::vector<wstring>?
        m_params.clear();
        for (std::vector<std::wstring>::iterator it = paramList.begin(); it != paramList.end(); ++it)
        {
            m_params.push_back(StrToUTF8(*it));
        }

        return fRet;

#elif defined(aix)
//
//      After performing some testing across various platforms it was determined that
//      even though a process with a large commandline can be executed correctly using exec, the 
//      retrieval of the parameters is limited to 4096 bytes (this is true for RH,SLES,AIX)
//      This is why there is a imposed limit of 4096 bytes on the parameters.
//
        bool fRet = false;
        char cmdbuf[_POSIX_ARG_MAX];
        struct procentry64 processBuffer;
        wstring exeFilename;
        wstring c;

        memset(&processBuffer, 0, sizeof(processBuffer));
        memset(&cmdbuf, 0, sizeof(cmdbuf));

        processBuffer.pi_pid = m_pid;

        const int max_retries = 3;
        bool successful_getargs = false;

        // Attempt to get the arguments for this process.  If getargs fails, retry 'max_retries' number of times.
        for (int current_retry_number = 0; current_retry_number <= max_retries; ++current_retry_number)
        {
            if (0 == getargs(&processBuffer, sizeof(processBuffer), &cmdbuf[0], sizeof(cmdbuf)))
            {
                successful_getargs = true;
                break;
            }
            else if (errno == ESRCH)
            {
                // Race: Process may already have died (in which case ESRCH is returned)
                return false;
            }
        }

        if (! successful_getargs)
        {
            SCXErrnoException e(L"getargs", errno, SCXSRCLOCATION);
            wstringstream errMsg;
            errMsg << L"For process PID " << m_pid
                   << L", error occurred reading arguments; argument data not returned. "
                   << "Error details: " << e.What();
            SCX_LOGERROR(m_log, errMsg.str());
            return false;
        }


        // Let's be certain we can't possibly read beyond our buffer
        // (Set the last two bytes of buffer to null to signfy end)
        cmdbuf[sizeof(cmdbuf)-2] = cmdbuf[sizeof(cmdbuf)-1] = '\0';

        char *argP = &cmdbuf[0];
        m_params.clear();
        while ( argP < &cmdbuf[0] + sizeof(cmdbuf) && *argP )
        {
            m_params.push_back(argP);
            argP += strlen(argP) + 1;
        }

        exeFilename.assign(StrFromUTF8(m_psinfo.pr_fname));
        c.assign(StrFromUTF8(cmdbuf));
        fRet = ModulePathFromCommand(exeFilename, c);

        return fRet;

#else
        return false;
#endif
    }