//+=============================================================================
//
// file :         CheckProcessUtil.cpp
//
// description :  C++ source for the CheckProcessUtil
//
// project :      TANGO Device Server
//
// $Author$
//
// Copyright (C) :      2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015
//						European Synchrotron Radiation Facility
//                      BP 220, Grenoble 38043
//                      FRANCE
//
// This file is part of Tango.
//
// Tango is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Tango is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Tango.  If not, see <http://www.gnu.org/licenses/>.
//
// $Revision$
// $Date$
//
//-=============================================================================

#if defined(__APPLE__)
#include <array>
#include <vector>
#include <string>
#include <memory>
#include <map>
#include <unistd.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/proc_info.h>
#include <sys/resource.h>
#include <libproc.h>
#endif

#include "CheckProcessUtil.h"

#include "Logging.h"

#ifndef    TIME_VAR
#ifndef _TG_WINDOWS_

#	define    TimeVal    struct timeval
#	define    GetTime(t)    gettimeofday(&t, nullptr);
#	define    Elapsed(before, after)    \
        1000.0*(after.tv_sec-before.tv_sec) + \
        ((double)after.tv_usec-before.tv_usec) / 1000

#else

#	define	TimeVal	struct _timeb
#	define	GetTime(t)	_ftime(&t);
#	define	Elapsed(before, after)	\
        1000*(after.time - before.time) + (after.millitm - before.millitm)

#endif    /*	_TG_WINDOWS_		*/
#endif    /*	TIME_VAR	*/

#ifdef _TG_WINDOWS_
typedef LONG (WINAPI *TNtQueryInformationProcess)(HANDLE,UINT,PVOID,ULONG,PULONG);
#endif

#if defined(__APPLE__)
/**
 * This is a helper function only for macOS which digs into the bowels of macOS
 * and extracts the command line for every process that was started by the
 * current user.
 *
 * I have copied this code from the famous htop project. You can find its
 * source code at https://github.com/htop-dev/htop.git
 * The function that I copied is DarwinProcess_updateCmdLine in file
 * darwin/DarwinProcess.c.
 * The only modifications that I made is to C++14-ify it.
 * @param pid: A processes' ID (PID) for which one wants to know the command
 *             line.
 * @return: The command line for the input PID or an empty string.
 */
static const std::string get_macOS_cmdline(const pid_t pid)
{
    std::string cmdline;

    /* This function is from the old Mac version of htop. Originally from ps? */

    /* Allocate space for the arguments. */
    // The current macOS 12.4 kernel reserves 1048576 bytes for the arguments.
    constexpr std::size_t argmax{1048576};
    std::unique_ptr< std::array< char, argmax > > procargs{
        new std::array< char, argmax >};

    /*
    * Make a sysctl() call to get the raw argument space of the process.
    * The layout is documented in start.s, which is part of the Csu
    * project.  In summary, it looks like:
    *
    * /---------------\ 0x00000000
    * :               :
    * :               :
    * |---------------|
    * | argc          |
    * |---------------|
    * | arg[0]        |
    * |---------------|
    * :               :
    * :               :
    * |---------------|
    * | arg[argc - 1] |
    * |---------------|
    * | 0             |
    * |---------------|
    * | env[0]        |
    * |---------------|
    * :               :
    * :               :
    * |---------------|
    * | env[n]        |
    * |---------------|
    * | 0             |
    * |---------------| <-- Beginning of data returned by sysctl() is here.
    * | argc          |
    * |---------------|
    * | exec_path     |
    * |:::::::::::::::|
    * |               |
    * | String area.  |
    * |               |
    * |---------------| <-- Top of stack.
    * :               :
    * :               :
    * \---------------/ 0xffffffff
    */
    int mib[3]{CTL_KERN, KERN_PROCARGS2, pid};
    std::size_t size{static_cast< size_t >(argmax)};
    if(sysctl(mib, 3, procargs->data(), &size, NULL, 0 ) == - 1)
    {
        // Sorry, but the info for this PID cannot be retrieved, because the
        // process is not owned by the current user.
        std::cerr << "Not owner of PID " << pid << "\n";
        return cmdline;
    }

    int nargs;
    std::memcpy(&nargs, procargs->data(), sizeof(nargs));
    char* cp{procargs->data() + sizeof(nargs)};

    /* Skip the saved exec_path. */
    for(; cp < &((*procargs)[size]); ++cp)
    {
        if(*cp == '\0')
        {
            /* End of exec_path reached. */
            break;
        }
    }

    if(cp == &((*procargs)[size]))
    {
        // The end of the memory where the procargs are stored have been
        // reached. Sorry but this is it and it is time to leave.
        return cmdline;
    }

    /* Skip trailing '\0' characters. */
    for (; cp < &((*procargs)[size]); ++cp)
    {
        if(*cp != '\0')
        {
            /* Beginning of first argument reached. */
            break;
        }
    }

    if(cp == &((*procargs)[size]))
    {
        // The end of the memory where the procargs are stored have been
        // reached. Sorry but this is it and it is time to leave.
        return cmdline;
    }

    /* Save where the argv[0] string starts. */
    char* sp{cp}, *np{nullptr};
    int end;
    for(int c{0}; (c < nargs) && (cp < &((*procargs)[size])); ++cp)
    {
        if(*cp == '\0')
        {
            c++;
            if(np != nullptr)
            {
            /* Convert previous '\0'. */
                *np = ' ';
            }
            /* Note location of current '\0'. */
            np = cp;

            if(end == 0)
            {
                end = cp - sp;
            }
        }
    }

    /*
    * sp points to the beginning of the arguments/environment string, and
    * np should point to the '\0' terminator for the string.
    */
    if(np == nullptr || np == sp)
    {
        /* Empty or unterminated string. */
        return cmdline;
    }

    cmdline = sp;

    return cmdline;
}
#endif

namespace Starter_ns {
//=============================================================
//=============================================================
    ProcessData::ProcessData() {
#ifdef _TG_WINDOWS_
        //	Under win 2000 or before the process name is shorted to 15 char
        //	If win 2000, take it from command line.
        win2000 = isWin2000();
#endif    /*	_TG_WINDOWS_		*/
    }

//=============================================================
//=============================================================
    ProcessData::~ProcessData() {
        //	clear previous list
        for (auto & i : proc_list)
            delete i;
        proc_list.clear();
    }

#ifdef _TG_WINDOWS_
    //=============================================================
    //=============================================================
    std::string  ProcessData::parseNameFromCmdLine(std::string name, std::string cmdline) {
        //	Search last position of name
        std::string::size_type	pos = 0;
        std::string::size_type	tmp;
        while ((tmp=cmdline.find(name, pos+1))!=std::string::npos)
            pos = tmp;

        //	Get name before sppace char
        std::string::size_type	end = cmdline.find(" ", pos);
        if (end==std::string::npos)
            end = cmdline.find("\t", pos);
        std::string	full_name = cmdline.substr(pos, end-pos);

        //	Take off extention if any
        end = full_name.find(".");
        if (end==std::string::npos)
            return full_name;
        else
            return full_name.substr(0, end);
    }
    //=============================================================
    //=============================================================
    bool ProcessData::isWin2000(void) {
        OSVERSIONINFOEX osvi;
        BOOL bOsVersionInfoEx;

        ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
        osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
        if(!(bOsVersionInfoEx=GetVersionEx((OSVERSIONINFO *)&osvi)))
        {
            osvi.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
            if (!GetVersionEx((OSVERSIONINFO *)&osvi) )
                return true;
        }
        double	osversion = osvi.dwMajorVersion + (1.0*osvi.dwMinorVersion/10);
        if (osversion<5.1)
        {
            TANGO_LOG_INFO << "/=======================================" << std::endl;
            TANGO_LOG_INFO << "	Windows 2000 or before !!!" << std::endl;
            TANGO_LOG_INFO << "/=======================================" << std::endl;
            return true;
        }
        else
        {
            TANGO_LOG_INFO << "Windows XP or later" << std::endl;
            return false;
        }
    }
    //=============================================================
    //=============================================================
    std::string ProcessData::wchar2string(WCHAR *wch, int size) {
        char	*ch = new char[size+1];
        int	i;
        for (i=0 ; wch[i]!=0 && i<size ; i++)
            ch[i] = wch[i];
        ch[i] = 0x0;
        std::string	str(ch);
        delete [] ch;
        return str;
    }
    //=============================================================
    //=============================================================
    WCHAR *ProcessData::string2wchar(std::string str) {
        char	*ch  = new char[str.length()+1];
        WCHAR	*wch = new WCHAR[str.length()+1];
        strcpy(ch, str.c_str());

        int	i;
        for (i=0 ; ch[i]!=0 ; i++)
            wch[i] = (short)ch[i];
        wch[i] = 0x0;
        delete [] ch;
        return wch;
    }
    //=============================================================
    //=============================================================
    void ProcessData::read_process_list_from_sys() {
        //	clear previous list
        for (int i=0 ; i<proc_list.size() ; i++)
            delete proc_list[i];
        proc_list.clear();
#ifdef OLD
        // Take a snapshot of all processes in the system.
        HANDLE	hProcessSnap =
            CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPMODULE, 0);
        if( hProcessSnap == INVALID_HANDLE_VALUE)
        {
            std::string desc = errorCodeToString(GetLastError(), "CreateToolhelp32Snapshot" );
            Tango::Except::throw_exception(
                            (const char *)"PROCESS_LIST_FAILED",
                            (const char *) desc.c_str(),
                            (const char *)"Starter::get_process_list()");
        }

        // Set the size of the structure before using it.
        PROCESSENTRY32	pe32;
        pe32.dwSize = sizeof( PROCESSENTRY32 );

        // Retrieve information about the first process,
        // and exit if unsuccessful
        if( !Process32First( hProcessSnap, &pe32 ) ) {
            std::string desc = errorCodeToString(GetLastError(), "Process32First" );  // Show cause of failure
            CloseHandle( hProcessSnap );     // Must clean up the snapshot object!
            Tango::Except::throw_exception(
                            (const char *)"PROCESS_LIST_FAILED",
                            (const char *) desc.c_str(),
                            (const char *)"Starter::get_process_list()");
        }

        //	Get module ntdll
        NTQIP						*lpfnNtQueryInformationProcess;
        WCHAR	*wc = string2wchar("ntdll.dll");
        HINSTANCE	hLibrary = GetModuleHandle(wc);
        delete [] wc;
        if (hLibrary != nullptr) {
            lpfnNtQueryInformationProcess = (NTQIP *)GetProcAddress(hLibrary, "ZwQueryInformationProcess");
        }
        else {
            std::string	desc = errorCodeToString(GetLastError(), "GetModuleHandle() ");
            Tango::Except::throw_exception(
                            (const char *)"PROCESS_LIST_FAILED",
                            (const char *) desc.c_str(),
                            (const char *)"Starter::get_process_list()");
        }

        do {
            // Retrieve the priority class.
            HANDLE	hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID );
            if( hProcess != nullptr )
            {
                PROCESS_BASIC_INFORMATION	pbi;
                DWORD		dwSize=0;
                if (lpfnNtQueryInformationProcess != nullptr)
                    (*lpfnNtQueryInformationProcess)(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &dwSize);
                TANGO_LOG_INFO << pbi.PebBaseAddress <<  "  -  " << dwSize << std::endl;

                std::string cmdline("");
                SIZE_T		sizeT=0;
                __PEB 		PEB;
                DWORD dwPriorityClass = GetPriorityClass( hProcess );
                if( dwPriorityClass )
                {
                    if (ReadProcessMemory(hProcess,
                            pbi.PebBaseAddress,
                            &PEB,
                            sizeof(PEB),
                            &sizeT))
                    {
                        TANGO_LOG_INFO << hex << PEB.dwInfoBlockAddress << "  -  " << sizeT << std::endl;
                        sizeT = 0;
                        __INFOBLOCK	block;
                        if (ReadProcessMemory(hProcess,
                                (LPVOID)PEB.dwInfoBlockAddress,
                                &block,
                                sizeof(block),
                                &sizeT))
                        {
                            sizeT = 0;
                            WCHAR	*buff = new WCHAR[block.wMaxLength+1];
                            if (ReadProcessMemory(hProcess,
                                            (LPVOID)block.dwCmdLineAddress,
                                            buff,
                                            block.wMaxLength,
                                            &sizeT)) {
                                cmdline = wchar2string(buff, sizeT);
                                TANGO_LOG_INFO << cmdline << std::endl;
                            }
                            else
                                errorCodeToString(GetLastError(), "3-ReadProcessMemory()" );
                            delete [] buff;
                        }
                        else
                            errorCodeToString(GetLastError(), "2-ReadProcessMemory()" );
                    }
                    else
                        errorCodeToString(GetLastError(), "1-ReadProcessMemory()" );
                }
                else
                    errorCodeToString(GetLastError(), "GetPriorityClass" );

                CloseHandle( hProcess );

                //	build process object to be added in std::vector
                Process	*process = new Process();

                //	Remove extention from exe name
                std::string	full_name = wchar2string(pe32.szExeFile);
                std::string::size_type	pos = full_name.find('.');
                if (pos!=std::string::npos)
                    process->name = full_name.substr(0, pos);
                else
                    process->name = full_name;

                //	Parse name frome cmd line because file manager truncate it at 15 chars
                if (win2000 && process->name.length()>13)
                    process->name = parseNameFromCmdLine(process->name, cmdline);

                //	On win32 -> exe file is case unsesitive
                transform(process->name.begin(), process->name.end(),
                    process->name.begin(), ::tolower);


                //	add pid and cmd line
                process->pid  = pe32.th32ProcessID;
                process->line = cmdline;
                proc_list.push_back(process);
            }
        } while ( Process32Next(hProcessSnap, &pe32) );

        CloseHandle(hProcessSnap);

#else	//=============================================================================

        // Take a snapshot of all processes in the system.
        HANDLE	hProcessSnap =
            CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPMODULE, 0);
        if( hProcessSnap == INVALID_HANDLE_VALUE)
        {
            std::string desc = errorCodeToString(GetLastError(), "CreateToolhelp32Snapshot" );
            Tango::Except::throw_exception(
                            (const char *)"PROCESS_LIST_FAILED",
                            (const char *) desc.c_str(),
                            (const char *)"Starter::get_process_list()");
        }

        // Set the size of the structure before using it.
        PROCESSENTRY32	pe32;
        pe32.dwSize = sizeof( PROCESSENTRY32 );

        // Retrieve information about the first process,
        // and exit if unsuccessful
        if( !Process32First( hProcessSnap, &pe32 ) ) {
            std::string desc = errorCodeToString(GetLastError(), "Process32First" );  // Show cause of failure
            CloseHandle( hProcessSnap );     // Must clean up the snapshot object!
            Tango::Except::throw_exception(
                            (const char *)"PROCESS_LIST_FAILED",
                            (const char *) desc.c_str(),
                            (const char *)"Starter::get_process_list()");
        }

        //	Get module ntdll
        NTQIP		*lpfnNtQueryInformationProcess;
        HINSTANCE	hLibrary = GetModuleHandleA("ntdll.dll");
        if (hLibrary != nullptr) {
            lpfnNtQueryInformationProcess = (NTQIP *)GetProcAddress(hLibrary, "ZwQueryInformationProcess");
        }
        else {
            std::string	desc = errorCodeToString(GetLastError(), "GetModuleHandle() ");
            Tango::Except::throw_exception(
                            (const char *)"PROCESS_LIST_FAILED",
                            (const char *) desc.c_str(),
                            (const char *)"Starter::get_process_list()");
        }

        do {
            // Retrieve the priority class.
            //HANDLE	hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID );
            HANDLE	hProcess = OpenProcess(
                PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe32.th32ProcessID );
            if( hProcess != nullptr ) {
                std::string strLine("");

                //	Get PEB address
                PVOID	pebAddress = getPebAddress(hProcess);

                //	Get the address of parameters
                PVOID	rtlUserProcParamsAddress = getPebStructure(hProcess, pebAddress);
                if (rtlUserProcParamsAddress!=nullptr)
                {
                    //	Read the command line in UNICODE structure
                    UNICODE_STRING	commandLine =
                        getUnicodeCommandLine(hProcess, rtlUserProcParamsAddress);

                    //	Convert the command line
                    WCHAR *commandLineContents = new WCHAR[commandLine.Length];
                    if (ReadProcessMemory(hProcess, commandLine.Buffer,
                        commandLineContents, commandLine.Length, nullptr)) {
                        strLine = wchar2string(
                            commandLineContents, commandLine.Length/2);
                    }
                    else
                        errorCodeToString(GetLastError(), "Cannot convert the command line" );
                    delete [] commandLineContents;
                }
                else
                    errorCodeToString(GetLastError(), "Cannot get the address of parameters" );


                CloseHandle( hProcess );

                //	build process object to be added in std::vector
                Process	*process = new Process();

                //	Remove extention from exe name
                std::string	full_name = wchar2string(pe32.szExeFile);
                std::string::size_type	pos = full_name.find('.');
                if (pos!=std::string::npos)
                    process->name = full_name.substr(0, pos);
                else
                    process->name = full_name;

                //	Parse name frome cmd line because file manager truncate it at 15 chars
                if (win2000 && process->name.length()>13)
                    process->name = parseNameFromCmdLine(process->name, strLine);

                //	On win32 -> exe file is case unsesitive
                transform(process->name.begin(), process->name.end(),
                    process->name.begin(), ::tolower);


                //	add pid and cmd line
                process->pid  = pe32.th32ProcessID;
                process->line = strLine;
                proc_list.push_back(process);
            }
        } while ( Process32Next(hProcessSnap, &pe32) );
        CloseHandle(hProcessSnap);
#endif
    }

    //============================================================================
    //============================================================================
    UNICODE_STRING ProcessData::getUnicodeCommandLine(HANDLE hProcess, PVOID paramAddress) {
        UNICODE_STRING	commandLine;

#if (_MSC_VER >= 1600)	//	VC10
        RTL_USER_PROCESS_PARAMETERS	*pProcessParam =
            (RTL_USER_PROCESS_PARAMETERS *) paramAddress;
        ReadProcessMemory(hProcess,
            (PCHAR) &pProcessParam->CommandLine,
            &commandLine, sizeof(commandLine), nullptr);
#else
        ReadProcessMemory(hProcess,
            (PCHAR) paramAddress + 0x40,
            &commandLine, sizeof(commandLine), nullptr);
#endif
        return commandLine;
    }
    //============================================================================
    //============================================================================
    PVOID ProcessData::getPebStructure(HANDLE hProcess, PVOID pebAddress) {
        PVOID	rtlUserProcParamsAddress = nullptr;

#if (_MSC_VER >= 1600)	//	VC10
        PEB		*peb = (PEB *)pebAddress;
        ReadProcessMemory(hProcess,
            (PCHAR)&peb->ProcessParameters,
            &rtlUserProcParamsAddress, sizeof(PVOID), nullptr);
#else
        ReadProcessMemory(hProcess,
            (PCHAR)pebAddress+0x10,
            &rtlUserProcParamsAddress, sizeof(PVOID), nullptr);
#endif
        return rtlUserProcParamsAddress;
    }
    //============================================================================
    //============================================================================
    PVOID  ProcessData::getPebAddress(HANDLE pHandle) {
        NTQIP	*ntQueryInformationProcess =
            (NTQIP *)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
        PROCESS_BASIC_INFORMATION	pbi;
        ntQueryInformationProcess(pHandle, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr);
        return pbi.PebBaseAddress;
    }


    // ============================================================================
    // Win32ProcessManager::errorCodeToString
    // ============================================================================
    std::string ProcessData::errorCodeToString (DWORD err_code,  std::string src) {
        WCHAR	*buff;
        std::string	msg;

        if(err_code != ERROR_SUCCESS) {
            FormatMessage(
                FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM |
                FORMAT_MESSAGE_IGNORE_INSERTS,
                nullptr,
                err_code,
                0, // Default language
                (LPTSTR) &buff,
                0,
                nullptr
                );
            msg = src + std::string(" failed : ");
            msg += wchar2string(buff);

            // Free the buffer.
            LocalFree(buff);
        }
        else
            msg = "No Error";

        std::cerr << msg << std::endl;

        return msg;
    }



#else	//	_TG_WINDOWS_


//=============================================================
//=============================================================
    void ProcessData::read_process_list_from_sys() {
        //	clear previous list
        for (auto & i : proc_list)
            delete i;
        proc_list.clear();

#if defined(__APPLE__)
        // Collect a list of all PIDs thats the current user is able to control
        // and store them in a std::map. The PID is the key and a bunch of
        // information is stored in the value as a std::tuple.
        // Assume that macOS cannot run more than 65535 processes at the same
        // time. But verify this against the rlimits.
        struct rlimit max_proc_limits{};
        // I do not check the return value because there is nothing that can be
        // done about a failed getrlimni call. This will only mean that the
        // assumption about the maximum number of concurrently running processes
        // cannot be verified.
        getrlimit(RLIMIT_NPROC, &max_proc_limits);
        constexpr std::size_t assumed_max_proc_limit{65535};
        if(max_proc_limits.rlim_max > assumed_max_proc_limit)
        {
            TANGO_LOG_INFO << "This code assumes that macOS cannot run more than "
            << assumed_max_proc_limit
            << " processes at the same time. The verification that this "
            << "has failed though. macOS has reported that it can run "
            << max_proc_limits.rlim_max
            << " processes at the same time. What does this mean? This means "
            << "that this program will continue to run without any further "
            << "notification. But there is now a non-negligible chance that "
            << "this program will not be able to control all Tango Controls "
            << "processes. What can you do about this? Please open an issue at "
            << "https://gitlab.com/tango-controls/starter and add this "
            << "message. The cppTango developers will address this issue as "
            << "soon as possible.";
        }
        std::array< pid_t, assumed_max_proc_limit > temp_pids{};
        std::size_t number_of_processes;
        const auto uid{getuid()};
        if(uid != 0)
        {
            // Not root. Get only the PIDs that the user's group can control.
            number_of_processes = static_cast< std::size_t >(proc_listpids(PROC_UID_ONLY, static_cast< uint32_t > (uid), &temp_pids, temp_pids.size() * sizeof(pid_t))) / sizeof(pid_t);
        }
        else
        {
            // This is the root user who can control every process. Therfore
            // get a list of all processes.
            number_of_processes = static_cast< std::size_t >(
                proc_listallpids(&temp_pids, temp_pids.size() * sizeof(pid_t)));
        }

        std::vector< pid_t > pids{temp_pids.begin(), temp_pids.end()};
        pids.resize(number_of_processes);
        std::map < pid_t, std::tuple< const std::string, const std::string,
            const struct proc_taskallinfo > > process_info_map;
        for (const auto& pid: pids)
        {
            std::array< uint8_t, PROC_PIDPATHINFO_MAXSIZE > proc_path{};
            const ssize_t proc_path_size{
                proc_pidpath(pid, &proc_path, PROC_PIDPATHINFO_MAXSIZE)};
            std::string path{proc_path.begin(), proc_path.end()};
            path.resize(proc_path_size);

            struct proc_taskallinfo proc_info{};
            const std::string cmdline{get_macOS_cmdline(pid)};
            // An empty command line means that this user does not have access
            // to the program's information.
            if(cmdline.empty() == false)
            {
                process_info_map.emplace(
                    pid, std::make_tuple(path, cmdline, proc_info));
            }
            else
            {
                TANGO_LOG_INFO << "Function manageProcFiles cannot retrieve "
                    "the command line for pid "
                    << pid
                    << "\n";
            }
        }

        for(const auto& info: process_info_map)
        {
            const pid_t pid {info.first};
            const std::string path{std::get< 0 >(info.second)};
            const std::string cmdline{std::get< 1 >(info.second)};
            const struct proc_taskallinfo proc_info{std::get< 2 >(info.second)};
            const std::string comm{proc_info.pbsd.pbi_comm};
            const std::string name{proc_info.pbsd.pbi_name};

            // Get PID
            auto* process{new Process};
            process->pid = pid;
            process->line = cmdline;
            // Add process object to std::vector
            proc_list.push_back(process);
        }
#else
        //	build processes list
        DIR *proc = opendir("/proc");
        if (proc == nullptr) {
            std::string desc;
            //	error
            switch (errno) {
                case EACCES:
                    desc = "Permission denied.";
                    break;
                case EMFILE:
                    desc = "Too many file descriptors in use by process.";
                    break;
                case ENFILE:
                    desc = "Too many file are currently open in the system.";
                    break;
                case ENOENT:
                    desc = "Directory does not exist or NAME is an empty std::string.";
                    break;
                case ENOMEM:
                    desc = "Insufficient memory to complete the operation.";
                    break;
                case ENOTDIR:
                    desc = "NAME is not a directory.";
                    break;
                default:
                    desc = "Undefined error.";
                    break;
            }
            Tango::Except::throw_exception(
                    (const char *) "READ_PROCESS_LIST_FAILED",
                    desc,
                    (const char *) "Starter::get_process_list()");

        }

        struct dirent *ent;
        while ((ent = readdir(proc))) {
            if (isdigit(ent->d_name[0])) {
                //	Get PID
                auto *process = new Process();
                process->pid = atoi(ent->d_name);
                try {
                    //	if process can be read, add process object to std::vector
                    if (manageProcFiles(process))
                        proc_list.push_back(process);
                    else
                        delete process;
                }
                catch (Tango::DevFailed &e) {
                    TANGO_LOG_INFO << "Exception catch during manageProcFiles for pid = "
                         << process->pid << std::endl;
                    TANGO_LOG_INFO << e.errors[0].desc;
                    delete process;
                }
                catch (...) {
                    TANGO_LOG_INFO << "Exception catch during manageProcFiles for pid = "
                         << process->pid << std::endl;
                    delete process;
                }
            }
        }
        closedir(proc);
#endif
    }


//=============================================================
/**
 *	Manage the /proc files
 */
//=============================================================
#if !defined(__APPLE__)
    bool ProcessData::manageProcFiles(Process *process) {
#if (defined __GLIBC__) || (defined __linux__) || (defined __freebsd__)

        //	Read command line file
        TangoSys_OMemStream fname;
        fname << "/proc/" << process->pid << "/cmdline";

        //	Read file
        std::ifstream ifs((char *) fname.str().c_str());
        if (ifs) {
            TangoSys_OMemStream sstr;
            sstr << ifs.rdbuf() << std::ends;
            ifs.close();

            //	Get command line
            process->line = sstr.str();
            //	Replace nullptr with SPACE char
            std::string::size_type pos;
            while ((pos = process->line.find('\0')) != std::string::npos)
                process->line.replace(pos, 1, " ");
            return true;
        } else {
            //removed because it polluted std error
            //std::cerr << fname.str() << ":	" << strerror(errno) << std::endl;
            return false;
        }
#else	//	solaris

        //	Read psinfo file
        TangoSys_OMemStream	fname;
        fname << "/proc/" << process->pid <<"/psinfo";

        int				fid;
        struct psinfo	ps;
        if ((fid=open(fname.str().c_str(), O_RDONLY))!=-1)
        {
            read(fid, (void *) &ps, sizeof(struct psinfo));
            close(fid);
            process->name = ps.pr_fname;

            //	Check if a real process or a shell (cannot be a server)
            if (process->name=="ssh" ||
                process->name=="bash" ||
                process->name=="sh")
                return false;

            uid_t	euid = geteuid();
            if ((euid==0) || (euid == ps.pr_euid))
            {
                /*
                 * To get the argv std::vector and environment variables
                 * for the process you need to be either root or the owner of the process.
                 * Otherwise you will not be able to open the processes memory.
                 */
                int fdesc;
                TangoSys_OMemStream	filepath;
                filepath << "/proc/" << process->pid <<"/as";
                if ( (fdesc = open(filepath.str().c_str(), O_RDONLY|O_NONBLOCK)) < 0 )
                {
                    TangoSys_OMemStream	tms;
                    tms << "Cannot open " << filepath.str() << ":	" <<
                        strerror(errno) << std::endl;
#ifdef TRACE
                    std::cerr << tms.str();
#endif
                    Tango::Except::throw_exception(
                            (const char *)"PROCESS_READ_FAILED",
                            (const char *) tms.str().c_str(),
                            (const char *)"Starter::manageProcFiles()");
                }
                else
                {
                    //	Allocate a pointer array
                    size_t	size = sizeof(char *) * (ps.pr_argc+1);
                    char **argvp = (char **) malloc(size);

                    //	And initialize
                    if (pread(fdesc, argvp, size, ps.pr_argv)>0)
                    {
                        TangoSys_OMemStream	line;

                        //	If argv[n] read -> append to command line
                        char	buff[0x100];
                        for (int n=0; n<ps.pr_argc; n++)
                            if (pread(fdesc, buff ,0xFF, (off_t)argvp[n])>0)
                                line << buff << " ";

                        process->line = line.str();
                        free(argvp);
                        close(fdesc);
                    }
                    else
                    {
                        free(argvp);
                        close(fdesc);
                        TangoSys_OMemStream	tms;
                        tms << "pread failed when getting command line arguments " <<
                            " from memory for process  " << process->name  << " (" <<
                            filepath.str() << ")\n" << strerror(errno) << std::endl;
                        std::cerr << tms.str();
                        Tango::Except::throw_exception(
                                (const char *)"PROCESS_READ_FAILED",
                                (const char *) tms.str().c_str(),
                                (const char *)"Starter::manageProcFiles()");
                    }
                }
            }
            else
            {
                //	Not Owner -> get only the  ps.pr_psargs
                process->line = ps.pr_psargs;
                close(fid);
            }
        }
        else
        {
            TangoSys_OMemStream	tms;
            tms << "open(" << fname.str() << ")  failed\n" <<  strerror(errno) << std::endl;
            std::cerr << tms.str();
            Tango::Except::throw_exception(
                            (const char *)"PROCESS_READ_FAILED",
                            (const char *) tms.str().c_str(),
                            (const char *)"Starter::manageProcFiles()");
        }
        return true;
#endif
    }
#endif //#if !defined(__APPLE__)
#endif    //	_TG_WINDOWS_



//=============================================================
/**
 *	Not only cpp
 *	Check for other than java and python processes
 */
//=============================================================
    void ProcessData::check_cpp_process(Process *process) {
        //	Remove path
#ifndef _TG_WINDOWS_
        if (process->line_args.empty())
            process->name = "";
        else
            process->name = name_from_path(process->line_args[0]);
#endif

        for (unsigned long i = 1; i < process->line_args.size(); i++)
            process->proc_args.push_back(process->line_args[i]);
    }
//=============================================================
/**
 *	Check for  java processes
 */
//=============================================================
    bool ProcessData::check_java_process(Process *process) {
        if (process->line_args.empty())
            return false;
#ifdef _TG_WINDOWS_
        if (process->name!="java" &&
            process->name!="javaw")
            return false;
#else
        if (name_from_path(process->line_args[0]) != "java")
            return false;
#endif

        //	Parse class and instance name
        bool found = false;
        for (unsigned long i = process->line_args.size() - 1; !found && i > 0; i--) {
            if (!process->line_args[i].empty() &&
                 process->line_args[i].c_str()[0] != '-') {
                if (i > 1) {    // -1 because last one is instance
                    //	To get class name, remove package name of previous arg
                    std::string full_name(process->line_args[i - 1]);
                    std::string::size_type start = 0;
                    std::string::size_type end;
                    while ((end = full_name.find('.', start)) != std::string::npos)
                        start = end + 1;
                    std::string name = full_name.substr(start);

                    if (name != "jar") {
                        //	Get last one
                        process->name = name;
                    } else {
                        // if jar file get file name without extension
                        full_name = full_name.substr(0, start - 1);
                        start = full_name.rfind('.'); // last in package
                        if (start == std::string::npos)
                            start = full_name.rfind('/');// last in path
                        if (start == std::string::npos)
                            start = 0; // only name
                        else
                            start++; // after / or .
                        process->name = full_name.substr(start);
                    }
                    //	and take this one as instance
                    process->proc_args.push_back(process->line_args[i]);
                    found = true;
                }
            }
        }
        return true;
    }
//=============================================================
/**
 *	Check for  python processes
 */
//=============================================================
    bool ProcessData::check_python_process(Process *process) {
        if (process->line_args.empty())
            return false;

        //	Check id process is python (must start with "python")
        std::string processName;
#ifdef _TG_WINDOWS_
        processName = process->name;
    //	if (process->name!="python")
    //		return false;
#else
        processName = name_from_path(process->line_args[0]);
//	if (name_from_path(process->line_args[0])!="python")
//		return false;
#endif
        if (processName.find("python") != 0)
            return false;


        if (process->line_args.size() < 2)
            return false;    //	No module loaded

        //	To get python module name
        bool found_py_module = false;
        unsigned long args_idx = 1;
        for (; args_idx < process->line_args.size() - 1; args_idx++) {
            const std::string &arg = process->line_args[args_idx];

            if (arg.empty())
                continue;

            if (arg[0] == '-') {
                // special python arguments that receive and additional parameter
                if (arg.size() > 1 && (arg[1] == 'Q' || arg[1] == 'W'))
                    args_idx++;
                continue;
            }

            // we reached the python file in execution
            found_py_module = true;

            std::string::size_type start = arg.rfind('/');
            if (start == std::string::npos)
                start = arg.rfind('\\');

            if (start == std::string::npos)
                start = 0;
            else
                start++;

            std::string::size_type end = arg.rfind(".py");
            if (end == std::string::npos) {
                //	Check if starts package name
                std::string fullName = arg.substr(start);
                std::string::size_type index = fullName.rfind('.');
                if (index == std::string::npos)
                    process->name = fullName;
                else
                    process->name = fullName.substr(++index);
            } else
                process->name = arg.substr(start, end - start);
            // everything from now is an argument
            args_idx++;
            break;
        }

        if (!found_py_module)
            return false;

        for (unsigned long i = args_idx; i < process->line_args.size(); i++)
            process->proc_args.push_back(process->line_args[i]);
        return true;
    }

//=============================================================
//=============================================================
    std::string ProcessData::name_from_path(const std::string& fullName) {
        std::string::size_type start = 0;
        std::string::size_type end;
        while ((end = fullName.find('/', start)) != std::string::npos)
            start = end + 1;
        //	Get last one
        return fullName.substr(start);
    }

//=============================================================
//=============================================================
    void ProcessData::build_server_names(Process *process) {
        // server is a process with at least one arg
        if (!process->proc_args.empty()) {
            process->servname = process->name;
            process->servname += "/";
            std::string instance(process->proc_args[0]);
            transform(instance.begin(), instance.end(),
                      instance.begin(), ::tolower);
            process->servname += instance;
#ifdef _TG_WINDOWS_
            //	Wain32 is case unsensitive
            transform(process->servname.begin(), process->servname.end(),
                            process->servname.begin(), ::tolower);
#endif
        } else
            process->servname = "";
    }
//=============================================================
//=============================================================




//=============================================================
/**
 *	Public method to update and build process process
 */
//=============================================================
//#define TRACE
    void ProcessData::update_process_list() {
        omni_mutex_lock sync(*this);

        TimeVal t0{}, t1{};
        GetTime(t0)
        read_process_list_from_sys();
        GetTime(t1)
#ifdef TRACE
        TimeVal	t2, t3;
        double max_t = 0;
        Process	*max_t_proc;
        TANGO_LOG_INFO << "	Reading process list = " << Elapsed(t0, t1) << " ms" << std::endl;
#endif

        for (auto process : proc_list) {
#ifdef TRACE
            GetTime(t2);
#endif
            //	Split on Space char
            std::string::size_type start = 0;
            std::string::size_type end;
            bool inCotes = false;
            while ((end = process->line.find(' ', start)) != std::string::npos) {
                std::string s = process->line.substr(start, (end - start));
                start = end + 1;
                //	Check if not empty
                if (!s.empty() && s != " " && s != "\t") {
                    //	Check if between cotes
                    if (!inCotes && s.find('\"') != std::string::npos) //	starting
                    {
                        process->line_args.push_back(s);
                        inCotes = true;
                    } else if (inCotes ) {// inside
                        //	Get last arg and concat with new one
                        std::string arg = process->line_args.back();
                        arg += " " + s;
                        //	And replace
                        process->line_args.pop_back();
                        process->line_args.push_back(arg);

                        if (s.find('\"') != std::string::npos) // ending
                            inCotes = false;
                    } else
                        process->line_args.push_back(s);
                }
            }
            //	Get last one
            std::string s = process->line.substr(start);
            if (!s.empty())
                process->line_args.push_back(s);


#ifndef _TG_WINDOWS_
            if (!process->line_args.empty())
                process->name = process->line_args[0];
            else
                process->name = "";

#endif

            //	Check if java or python process
            if (!check_java_process(process))
                if (!check_python_process(process))
                    check_cpp_process(process);
            build_server_names(process);

#ifdef TRACE2
            TANGO_LOG_INFO << process->pid << "	" << process->name;
            if (process->proc_args.empty()==false)
                TANGO_LOG_INFO << " " << process->proc_args[0];
            TANGO_LOG_INFO << std::endl;
#endif

#ifdef TRACE
            GetTime(t3);
            double	t = Elapsed(t2, t3);
            if (t>max_t) {
                max_t = t;
                max_t_proc = process;
            }
#endif

        }
#ifdef TRACE
        GetTime(t1);
        TANGO_LOG_INFO << "		total = " << Elapsed(t0, t1) << " ms" << std::endl;
        TANGO_LOG_INFO << "	max:	" << max_t << "  for " << max_t_proc->name << " (" <<
                     max_t_proc->pid << ")" << std::endl;
#endif

    }

//=============================================================
//=============================================================
    int ProcessData::get_server_pid(const std::string& argIn) {
        omni_mutex_lock sync(*this);
        for (Process *process : proc_list) {
            // server is a process with at least one arg
            if (!process->proc_args.empty()) {
                std::string servname(process->name);
                servname += "/";
                servname += process->proc_args[0];
                //TANGO_LOG_INFO << serverName << "==" << argIn << std::endl;
                if (servname == argIn)
                    return (int) process->pid;
            }
        }
        return -1;
    }
//=============================================================
/**
 * Returs true if server running
 */
//=============================================================
    bool ProcessData::is_server_running(const std::string& argIn) {
        omni_mutex_lock sync(*this);
        for (Process *process : proc_list) {
            if (!process->servname.empty() && process->servname == argIn)
                return true;
        }
        return false;
    }
//=============================================================
/**
 * Returs true if process running (do not check instance name)
 */
//=============================================================
    bool ProcessData::is_process_running(const std::string& argIn) {
        omni_mutex_lock sync(*this);
        for (Process *process : proc_list) {
            if (!process->servname.empty() && process->name == argIn)
                return true;
        }
        return false;
    }
//=============================================================
/**
 * Returs true if process running
 */
//=============================================================
    int ProcessData::getNbServerInstances(const std::string& argIn) {
        omni_mutex_lock sync(*this);
        int nb = 0;
        for (Process *process : proc_list) {
            if (!process->servname.empty() && process->servname == argIn)
                nb++;
        }
        return nb;
    }

//=============================================================
//=============================================================
    std::vector<Process> ProcessData::get_process_list() {
        omni_mutex_lock sync(*this);

        //	copy list
        std::vector<Process> vReturn;
        for (Process *p_src : proc_list) {
            Process process;
            Process *p_target = &process;
            *p_target = *p_src;
            vReturn.push_back(process);
        }

        return vReturn;
    }
//=============================================================
//=============================================================






//=============================================================
//=============================================================
    int CheckProcessUtil::get_server_pid(const std::string& argIn) {
        return data->get_server_pid(argIn);
    }
//=============================================================
/**
 * Returs true if server running
 */
//=============================================================
    bool CheckProcessUtil::is_server_running(const std::string& argIn) {
        //	Make sure instance is lower case
        std::string::size_type pos = argIn.find('/');
        if (pos == std::string::npos)
            return false;    //	Not a server name
        pos++;
        std::string dsname(argIn.substr(0, pos));
#ifdef _TG_WINDOWS_
        //	Wain32 is case unsensitive
        transform(dsname.begin(), dsname.end(),
                        dsname.begin(), ::tolower);
#endif
        std::string instance(argIn.substr(pos));
        transform(instance.begin(), instance.end(),
                  instance.begin(), ::tolower);
        dsname += instance;
        return data->is_server_running(dsname);
    }
//=============================================================
/**
 * Returs true if process running (do not check instance name)
 */
//=============================================================
    bool CheckProcessUtil::is_process_running(const std::string& argIn) {
        return data->is_process_running(argIn);
    }
//=============================================================
/**
 * Returs true if process running several times (normally cannot occurs)
 */
//=============================================================
    int CheckProcessUtil::getNbServerInstances(const std::string& argIn) {
        return data->getNbServerInstances(argIn);
    }

//=============================================================
//=============================================================
    std::vector<Process> CheckProcessUtil::get_process_list() {
        return data->get_process_list();
    }

//=============================================================
//=============================================================
    void *CheckProcessUtil::run_undetached(TANGO_UNUSED(void *ptr)) {
        while (!stop_thread) {
            try {
                data->update_process_list();
            }
            catch (Tango::DevFailed &e) {
                Tango::Except::print_exception(e);
            }


            //	And wait n times for next loop
            for (int i = 0; i < 2 && !stop_thread; i++) {
                omni_mutex_lock sync(*data);
                data->wait(1000);
            }
        }
        delete data;
        return nullptr;
    }
//=============================================================
//=============================================================


}    //	namespace



