0

Greetings StackOverflow comrades. Last time I inquired about environment variables. Thanks to Remy for informing me.

Thanks to him I completed my Process class. Now the real problem was connecting to and communicating with MariaDb. I successfully launched MariaDb; but for some reason, reading from MariaDb deadlocks my program. I know before hand that, once connected to MariaDb using, mysql --user=root, MariaDb writes MariaDb[NONE]> to the console. And expects an SQL query input. But I my application deadlocks when trying to read.

I am wondering if MariaDb is using the handles I passed it in CreateProcess StartUpInfo. I did some search on google and found a library on MariaDb website which allows C/C++ programs to connect to MariaDb. So probably they are coercing us to use there library to connect to MariaDb.

Edit: @Secumen I am trying to communicate with MariaDb via win32 CreateProcess; you know that popular database program? I am using the one shipped with Xampp software.

I want to be able to automate the tasks of adding tables, data, users, etc.

I created the pipes with CreatePipe(...). Then I launched MariaDb using CreateProcess(...). The second argument to CreateProcess was the command line, mysql --user=root. Note that Xampp calls MariaDb MySql. Now I am connected to MariaDb and expect it to write MariaDb[NONE]> to the console. Which means that I should have data to read via ReadFile(...). However ReadFile deadlocks and PeekNamedFile shows that there was zero bytes available to be read.

How the heck then would I communicate with MariaDb if it is not writing to the handles I passed it in CreateProcess?

Edit - Minimal Example

SECURITY_ATTRIBUTES sa = {};
sa.bInheritHandle = true;
sa.lpSecurityDescriptor =NULL;
sa.nLength  = sizeof(sa);

HANDLE r,w;
HANDLE r1,w1;

if(!CreatePipe(&r,&w,&sa,0)) throw "Failed to create pipe\n";
if(!CreatePipe(&r1,&w1,&sa,0)) throw "Failed to create pipe\n";

auto cmd = "MYSQL --user=root";
auto current_dir = R"(C:\Program Files\xampp\mysql\bin)";

SetCurrentDirectoryA(current_dir);

STARTUPINFOA si = {sizeof(si)};
PROCESS_INFORMATION pi;

si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdError = w;
si.hStdOutput = w;
si.hStdInput = r1;

if(!CreateProcessA(NULL,cmd,NULL,NULL,true,0,NULL,NULL,&si,&pi))
    throw "Failed to create process";

CloseHandle(w);
CloseHandle(r1);

{
 DWORD sz, avail;
 char *buf = new char[1024];

 PeekNamedPipe(r,NULL,0,NULL,&avail,NULL);
 printf("available %i",avail);

 ReadFile(r,buf,1023,&sz,NULL);
 buf[sz] = 0;
 printf("%s",buf);

 delete[] buf;
}

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
user13947194
  • 337
  • 5
  • 7
  • 1
    Use ODBC to connect. – i486 Aug 21 '21 at 22:16
  • What are you going to do? Please explain more correctly you want. – secuman Aug 22 '21 at 05:25
  • @secuman I am going to edit my question in an effort to explain my problem thoroughly. – user13947194 Aug 22 '21 at 08:29
  • I wouldn't use the CLI tool in a program, that's what libraries are for. Also, the CLI might behave differently when it's not running in a terminal but as a regular childprocess. That said, it would help if you provided a [mcve]. Not only is that required for a question here, but it also helps you find a solution. – Ulrich Eckhardt Aug 22 '21 at 08:47
  • @UlrichEckhardt sure. But you will need MariaDb. – user13947194 Aug 22 '21 at 08:49
  • Of course, in order to communicate with a MariaDB, you need one. I'm not sure what your objection is... – Ulrich Eckhardt Aug 22 '21 at 08:55
  • @UlrichEckhardt was not objecting. Just a reminder. I have posted the minimal example. – user13947194 Aug 22 '21 at 08:59
  • 1
    I think you would be better to use Mariadb Connector. – secuman Aug 23 '21 at 02:13
  • @secuman if that's what I have to to then that's what I have to do. I feel I extremely irritated that I spent one week creating my Process class for nothing and that I will have to spend another month to learn a brand new api. It doesn't make sense. How is it possible only this api can communicate with MariaDb. I thought the only way to talk to the child process was via pipes. – user13947194 Aug 23 '21 at 04:56
  • @UlrichEckhardt I see where CLI means command line interface. Are you calling CreateProcess a CLI? If so why does it behave differently if not used in a terminal? – user13947194 Aug 23 '21 at 19:46
  • [MySQL Connector/C++ 8.0.26](https://dev.mysql.com/downloads/connector/cpp/)?? – David C. Rankin Aug 24 '21 at 06:52
  • No, `CreateProcess()` is not a CLI. The CLI is rather the interactive interface of `mysql.exe` which you can use to access a MySQL DB from the commandline. The point is, that this executable can detect when it is called from a terminal or from some other program (like yours) and use that info to change its behaviour. For example, for your automated use of it, any interactive code aimed at an actual use is superfluous and can be disabled. Some programs do these things. – Ulrich Eckhardt Aug 24 '21 at 20:30
  • BTW: You still haven't provided a [mcve]. The code you provided never even calls `CreateProcess()`, which makes it pretty useless for this question. – Ulrich Eckhardt Aug 24 '21 at 20:31
  • Thank you very much @UlrichEckhardt for confirming my suspicions. I still don't know exactly what the CLI is. You talk of the CLI as if it is separate from mysql.exe. – user13947194 Aug 24 '21 at 20:41
  • @UlrichEckhardt I don't understand how that happened as I copied and pasted the code. I have added it now. Anyhow, Mr. Secuman has already given me two solutions and as you have stated, "CLI prevents automated access via pipes", I don't expect a better solution using Pipes and CreateProcess. Plus CreateProcess and Pipes seems very faulty API. Android's version is infinitely better. I have thus resorted to MariaDb connector. Which I learnt actually uses sockets to talk to MariaDb. Maybe another way to connect to MariaDb and via Win32 sockets. – user13947194 Aug 24 '21 at 20:53
  • Alot of persons have recommended MariaDb or MySql Connector to communicate with MariaDb. So I think it proper I give links to proper tutorials. MariaDb website pretty much only provides a reference. [Difference between mysql_use_result and mysql_store_result](https://www.informit.com/articles/article.aspx?p=30494&seqNum=6#:~:text=The%20primary%20difference%20between%20the,get%20any%20of%20the%20rows.) And a good book is **MySQL** by Paul Dubois 1999. – user13947194 Aug 25 '21 at 00:11

2 Answers2

1

I have written the following code by referring to MSDN. I am using visual studio 2017 and test with win32 application.
I have passed several SQL statements through PIPE for testing, and confirmed that the results were exactly obtained through PIPE.

#include <string>
#include <iostream>
#include <windows.h>

using namespace std;

HANDLE hChildOutRd = NULL;
HANDLE hChildOutWr = NULL;
HANDLE hChildInRd = NULL;
HANDLE hChildInWr = NULL;

//. Internal functions.
int CreatePipes();
int CreateChildProcess();
int PipeIO(string & request, string & response);

int main()
{
    if (CreatePipes() != ERROR_SUCCESS)
    {
        cout << "Failed to create pipe. error: " << GetLastError() << endl;
        return -1;
    }

    //. Create the child process.
    if (CreateChildProcess() != ERROR_SUCCESS)
    {
        cout << "Failed to create child process. error: " << GetLastError() << endl;
        return -2;
    }

    //. Write and Read.
    string request, response;
    request = "use test_db; select count(*) from test_table;";

    PipeIO(request, response);
    cout << "[Request]: " << request << "\n[Response]: \n" << response << endl << endl;

    return 0;
}

int CreatePipes()
{
    SECURITY_ATTRIBUTES sa{ sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };

    //. Create a pipe for the child process's output.
    if (!CreatePipe(&hChildOutRd, &hChildOutWr, &sa, 0))
    {
        return -1;
    }

    if (!SetHandleInformation(hChildOutRd, HANDLE_FLAG_INHERIT, 0))
    {
        return -2;
    }

    //. Create a pipe for the child process's input.
    if (!CreatePipe(&hChildInRd, &hChildInWr, &sa, 0))
    {
        return -3;
    }

    if (!SetHandleInformation(hChildInWr, HANDLE_FLAG_INHERIT, 0))
    {
        return -4;
    }

    return ERROR_SUCCESS;
}

int CreateChildProcess()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(STARTUPINFO));
    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

    si.cb = sizeof(STARTUPINFO);
    si.hStdError = hChildOutWr;
    si.hStdOutput = hChildOutWr;
    si.hStdInput = hChildInRd;
    si.dwFlags |= STARTF_USESTDHANDLES;

    wchar_t cmd[] = L" -uroot -ppassword";
    BOOL bRet = CreateProcess(L"C:\\xampp\\mysql\\bin\\mysql.exe", cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
    if (!bRet)
    {
        return -5;
    }
    else
    {
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);

        CloseHandle(hChildInRd);
        CloseHandle(hChildOutWr);
    }

    return ERROR_SUCCESS;
}

int PipeIO(string & request, string & response)
{
    int nRet = ERROR_SUCCESS;
    DWORD dwRead = 0, dwWrite = 0;

    response.clear();
    if (!WriteFile(hChildInWr, request.c_str(), request.length(), &dwWrite, NULL))
    {
        cout << "ERROR: failed to write pipe. error: " << GetLastError() << endl;
        return -1;
    }
    CloseHandle(hChildInWr);

    while (true)
    {
        char buffer[1024] = { 0 };
        if (!ReadFile(hChildOutRd, buffer, 1024, &dwRead, NULL) || dwRead == 0)
        {
            break;
        }
        response += buffer;
    }
    CloseHandle(hChildOutRd);

    return ERROR_SUCCESS;
}
secuman
  • 539
  • 4
  • 12
  • Much thanks to you Dr. Secuman. Your example works. To a limit, but it works. I was trying to figure out why it works. It was not because you put the executable name in the 1st argument of CreateProcess. It was not because you called SetHandleInformation, and the order in which you did that. It was because you Closed your write handle to the process after writing to the process! This is as normal as any other behaviour of `CreateProcess`; and implies that I would have to continuously open, write, get feedback and close MariaDb. – user13947194 Aug 23 '21 at 17:48
  • I still cannot get the introduction bytes written by MariaDb. Thus I said I get limited success, but as long as I get my main data I am good. – user13947194 Aug 23 '21 at 17:50
1

Then, you can do this asynchronously. I referred to RbMm's answer at this article.

#include <malloc.h>
#include <windows.h>
#include <winternl.h>

#include <array>
#include <string>
#include <iostream>

typedef ULONG(__stdcall *RTLNTSTATUSTODOSERROR)(NTSTATUS);
RTLNTSTATUSTODOSERROR pRtlNtStatusToDosError = NULL;

struct IO_COUNT
{
    HANDLE _hFile;
    HANDLE _hEvent;
    LONG _dwIoCount;

    IO_COUNT()
    {
        _dwIoCount = 1;
        _hEvent = 0;
    }

    ~IO_COUNT()
    {
        if (_hEvent)
        {
            CloseHandle(_hEvent);
        }
    }   

    void BeginIo()
    {
        InterlockedIncrement(&_dwIoCount);
    }

    void EndIo()
    {
        if (!InterlockedDecrement(&_dwIoCount))
        {
            SetEvent(_hEvent);
        }
    }

    void Wait()
    {
        WaitForSingleObject(_hEvent, INFINITE);
    }

    ULONG Create(HANDLE hFile);
};

struct U_IRP : OVERLAPPED
{
    enum { read, write };

    IO_COUNT* _pIoObject;
    ULONG _code;
    LONG _dwRef;
    char _buffer[256];

    void AddRef()
    {
        InterlockedIncrement(&_dwRef);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRef)) delete this;
    }

    U_IRP(IO_COUNT* pIoObject) : _pIoObject(pIoObject)
    {
        _dwRef = 1;
        pIoObject->BeginIo();
        RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
    }

    ~U_IRP()
    {
        _pIoObject->EndIo();
    }

    ULONG CheckIoResult(BOOL is_ok)
    {
        if (is_ok)
        {
            OnIoComplete(NOERROR, InternalHigh);
            return NOERROR;
        }

        ULONG dwErrorCode = GetLastError();
        if (dwErrorCode != ERROR_IO_PENDING)
        {
            OnIoComplete(dwErrorCode, 0);
        }

        return dwErrorCode;
    }

    ULONG Read()
    {
        _code = read;
        AddRef();

        return CheckIoResult(ReadFile(_pIoObject->_hFile, _buffer, sizeof(_buffer) - 1, 0, this));
    }

    ULONG Write(const void* pvBuffer, ULONG cbBuffer)
    {
        _code = write;
        AddRef();

        return CheckIoResult(WriteFile(_pIoObject->_hFile, pvBuffer, cbBuffer, 0, this));
    }

    VOID OnIoComplete(DWORD dwErrorCode, DWORD_PTR dwNumberOfBytesTransfered)
    {
        switch (_code)
        {
        case read:
            if (dwErrorCode == NOERROR)
            {
                if (dwNumberOfBytesTransfered)
                {
                    _buffer[dwNumberOfBytesTransfered] = 0;
                    std::cout << _buffer;
                }
                Read();
            }
            break;

        case write:
            break;
        }

        Release();
    }

    static VOID WINAPI _OnIoComplete(DWORD dwErrorCode, DWORD_PTR dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
    {
        static_cast<U_IRP*>(lpOverlapped)->OnIoComplete(pRtlNtStatusToDosError(dwErrorCode), dwNumberOfBytesTransfered);
    }
};

ULONG IO_COUNT::Create(HANDLE hFile)
{
    _hFile = hFile;

    return  BindIoCompletionCallback(hFile, (LPOVERLAPPED_COMPLETION_ROUTINE)U_IRP::_OnIoComplete, 0) &&
            SetFileCompletionNotificationModes(hFile, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) &&
            (_hEvent = CreateEvent(0, TRUE, FALSE, 0)) ? NOERROR : GetLastError();
}

int main()
{
    static const WCHAR name[] = L"\\\\?\\pipe\\somename";
    pRtlNtStatusToDosError = (RTLNTSTATUSTODOSERROR)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlNtStatusToDosError");

    HANDLE hFile = CreateNamedPipeW(name, PIPE_ACCESS_DUPLEX | FILE_READ_DATA | FILE_WRITE_DATA | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 0, 0);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        return -1;
    }

    IO_COUNT obj;
    if (obj.Create(hFile) != NOERROR)
    {
        CloseHandle(hFile);
        return -2;
    }

    PROCESS_INFORMATION pi;
    STARTUPINFOW si = { sizeof(si) };
    SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };

    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdError = CreateFile(name, FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, 0);
    if (si.hStdError == INVALID_HANDLE_VALUE)
    {
        CloseHandle(hFile);
        return -3;
    }

    si.hStdInput = si.hStdOutput = si.hStdError;
    WCHAR param[] = L" -uroot -ppassword";
    if (!CreateProcess(L"C:\\xampp\\mysql\\bin\\mysql.exe", param, 0, 0, TRUE, 0, 0, 0, &si, &pi))
    {
        CloseHandle(hFile);
        return -4;
    }

    //. Close unneeded handles.
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    CloseHandle(si.hStdError);

    U_IRP* p;
    if (p = new U_IRP(&obj))
    {
        p->Read();
        p->Release();
    }
    obj.EndIo();

    std::array<std::string, 5> commands = {
        "show databases;\n",
        "use test_db;\n",
        "select count(*) from test_table;\n",
        "select * from test_table;\n",
        "exit\n"
    };

    for (auto & iter : commands)
    {
        if (p = new U_IRP(&obj))
        {
            p->Write(iter.c_str(), iter.length());
            p->Release();
        }
    }
        
    obj.Wait();     
    CloseHandle(hFile);
    DisconnectNamedPipe(hFile);

    return 0;
}
secuman
  • 539
  • 4
  • 12
  • up vote to show the love. This version behaved like other version. I cannot see the `MariaDb[NONE]>`; which is ok. Less ok is I still have to close the writing handle to flush output. I think that is what U_IRP::Release() does, eventually. I don't know if you experience something else. But it was good studying the code, I learnt a bit. Such as how NamedPipe and File handles connect to each other. That part was kool. – user13947194 Aug 24 '21 at 07:16
  • I also wanted to note to that I have downloaded MariaDb connector and have been researching it. I see where the connector mainly uses sockets to talk to Maria Db or MySql. It totally slipped my mind that MariaDb was also a service. The connector doesn't even need to me to instruct it where I installed MariaDb. – user13947194 Aug 24 '21 at 10:24