/** 
 * @file    kProcess.cpp
 *
 * @internal
 * Copyright (C) 2018-2022 by LMI Technologies Inc.
 * Licensed under the MIT License.
 * Redistributed files must retain the above copyright notice.
 */

#define K_PLATFORM
#include <kApi/Utils/kProcess.h>

#include <kApi/Data/kArrayList.h>
#include <kApi/Data/kString.h>
#include <kApi/Io/kPath.h>
#include <kApi/Threads/kSemaphore.h>
#include <kApi/Threads/kThread.h>
#include <kApi/Io/kPipeStream.x.h>

kBeginFullClassEx(k, kProcess)
    kAddVMethod(kProcess, kObject, VRelease)
kEndFullClassEx()

#if defined(K_WINDOWS) || defined(K_LINUX)

kFx(kStatus) kProcess_Construct(kProcess* process, const kChar* path, kAlloc allocator)
{
    kAlloc alloc = kAlloc_Fallback(allocator);
    kStatus status = kOK;

    kCheck(kAlloc_GetObject(alloc, kTypeOf(kProcess), process));

    if (!kSuccess(status = kProcess_Init(*process, path, kTypeOf(kProcess), alloc)))
    {
        kAlloc_FreeRef(alloc, process);
    }

    return status;
}

kFx(kStatus) kProcess_Init(kProcess process, const kChar* path, kType type, kAlloc alloc)
{
    kObjR(kProcess, process);
    kStatus status = kOK;  

    kCheck(kObject_Init(process, type, alloc));

    kAtomic32s_Init(&obj->isRunning, kFALSE);
    obj->path = kNULL;
    obj->arguments = kNULL;
    obj->processStdIn = kNULL; 
    obj->processStdOut = kNULL; 
    obj->processStdErr = kNULL; 

    kTry
    {
        kTest(kString_Construct(&obj->path, path, alloc));
        kTest(kArrayList_Construct(&obj->arguments, kTypeOf(kString), 0, alloc));

        kTest(kProcess_InitPlatformFields(process));
    }
    kCatch(&status)
    {
        kProcess_VRelease(process);
        kEndCatch(status);
    }

    return kOK;
}

kFx(kStatus) kProcess_VRelease(kProcess process)
{
    kObj(kProcess, process);

    kCheck(kProcess_Wait(process, 0, kNULL));

    kCheck(kProcess_ReleasePlatformFields(process));

    kCheck(kObject_Destroy(obj->path));
    kCheck(kObject_Dispose(obj->arguments));

    return kObject_VRelease(process);
}

kFx(kStatus) kProcess_AddArgument(kProcess process, const kChar* argument)
{
    kObj(kProcess, process);
    kString arg = kNULL;
    kStatus status = kOK;

    kCheckState(!kAtomic32s_Get(&obj->isRunning));
    
    kCheck(kString_Construct(&arg, argument, kNULL));

    if (!kSuccess(status = kArrayList_AddT(obj->arguments, &arg)))
    {
        kObject_Destroy(arg);
        return status;
    }
        
    return status;    
}

kFx(kStatus) kProcess_AddArguments(kProcess process, const kChar* arguments[], kSize argCount)
{
    kObj(kProcess, process);
    kSize i = 0;

    for (i = 0; i < argCount; i++)
    {
        kCheck(kProcess_AddArgument(process, arguments[i]));
    }

    return kOK;
}

kFx(kStatus) kProcess_ClearArguments(kProcess process)
{
    kObj(kProcess, process);

    kCheckState(!kAtomic32s_Get(&obj->isRunning));

    return kArrayList_Purge(obj->arguments);
}

kFx(kStream) kProcess_AppStdIn()
{
    kStaticObj(kProcess);

    return sobj->appStdIn;
}

kFx(kStream) kProcess_AppStdOut()
{
    kStaticObj(kProcess);

    return sobj->appStdOut;
}

kFx(kStream) kProcess_AppStdErr()
{
    kStaticObj(kProcess);

    return sobj->appStdErr;
}

#endif

#if defined(K_WINDOWS)

kFx(kStatus) xkProcess_InitStatic()
{
    kStaticObj(kProcess);
    JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };

    sobj->appStdIn = kNULL;
    sobj->appStdOut = kNULL;
    sobj->appStdErr = kNULL;

    // Create a job that when closed, kills any processes attached to it.
    sobj->job = CreateJobObject(NULL, NULL);

    if (sobj->job)
    {
        jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK;

        if (SetInformationJobObject(sobj->job, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)) == 0)
        {
            CloseHandle(sobj->job);
            sobj->job = NULL;
        }
    }

    if (!kSuccess(kPipeStream_ConstructFromHandle(&sobj->appStdIn, GetStdHandle(STD_INPUT_HANDLE), kNULL)))
    {
        sobj->appStdIn = kNULL;
    }

    if (!kSuccess(kPipeStream_ConstructFromHandle(&sobj->appStdOut, GetStdHandle(STD_OUTPUT_HANDLE), kNULL)))
    {
        sobj->appStdOut = kNULL;
    }

    if (!kSuccess(kPipeStream_ConstructFromHandle(&sobj->appStdErr, GetStdHandle(STD_ERROR_HANDLE), kNULL)))
    {
        sobj->appStdErr = kNULL;
    }

    return kOK;
}

kFx(kStatus) xkProcess_ReleaseStatic()
{
    kStaticObj(kProcess);

    kCheck(kObject_Destroy(sobj->appStdIn));
    kCheck(kObject_Destroy(sobj->appStdOut));
    kCheck(kObject_Destroy(sobj->appStdErr));

    if (sobj->job)
    {
        CloseHandle(sobj->job);
        sobj->job = NULL;
    }

    return kOK;
}

kFx(kStatus) kProcess_InitPlatformFields(kProcess process)
{
    kObj(kProcess, process);

    obj->process = kNULL;
    obj->stdOutHandle = kNULL;
    obj->stdErrHandle = kNULL; 
    obj->stdInHandle = kNULL;

    return kOK;
}

kFx(kStatus) kProcess_ReleasePlatformFields(kProcess process)
{
    return kOK;
}

kFx(kStatus) kProcess_ConstructArgument(kProcess process, WCHAR** wargument)
{
    kObj(kProcess, process);
    kString argument = kNULL;
    k32s arglength  = 0;
    kSize i = 0;
    kChar fileName[kPATH_MAX];
    kStatus status = kOK;

    kCheck(kPath_FileName(kString_Chars(obj->path), fileName, kCountOf(fileName)));

    kTry
    {
        kTest(kString_Construct(&argument, fileName, kNULL));

        for (i = 0; i < kArrayList_Count(obj->arguments); i++)
        {
            kString a = kArrayList_AsT(obj->arguments, i, kString);

            kTest(kString_Addf(argument, " \"%s\"", kString_Chars(a)));
        }

        if (!(arglength = MultiByteToWideChar(CP_UTF8, 0, kString_Chars(argument), -1, kNULL, 0)))
        {
            kThrow(kERROR_OS);
        }

        kTest(kMemAlloc(arglength * sizeof(WCHAR) + 2, wargument));

        if (MultiByteToWideChar(CP_UTF8, 0, kString_Chars(argument), -1, *wargument, arglength) == 0)
        {
            kThrow(kERROR_PARAMETER);
        }
    }
    kCatchEx(&status)
    {
        kMemFree(&wargument);
        *wargument = kNULL;

        kEndCatchEx(status);
    }
    kFinallyEx
    {
        kObject_Destroy(argument);

        kEndFinally();
    }

    return kOK;
}

kFx(kStatus) kProcess_Start(kProcess process)
{
    kObj(kProcess, process);
    kStaticObj(kProcess);
    PROCESS_INFORMATION procInfo = { 0 };
    STARTUPINFO startupInfo = { 0 };
    SECURITY_ATTRIBUTES securityAttrs = { 0 };
    HANDLE outputWriteOut = NULL;
    HANDLE outputWriteErr = NULL;
    kStatus exception = kOK;
    HANDLE inputRead = kNULL;
    WCHAR wpath[MAX_PATH];
    WCHAR* wargument = kNULL;
    
    kCheckState(!kAtomic32s_Get(&obj->isRunning));

    if (MultiByteToWideChar(CP_UTF8, 0, kString_Chars(obj->path), -1, wpath, kCountOf(wpath)) == 0)
    {
        return kERROR_PARAMETER;
    }    
    
    kTry
    {   
        kTest(kProcess_ConstructArgument(process, &wargument));

        securityAttrs.nLength = sizeof(securityAttrs);
        securityAttrs.lpSecurityDescriptor = NULL;
        securityAttrs.bInheritHandle = TRUE;

        if (!CreatePipe(&obj->stdOutHandle, &outputWriteOut, &securityAttrs, 0))
        {
            kThrow(kERROR_OS);
        }

        if (!CreatePipe(&obj->stdErrHandle, &outputWriteErr, &securityAttrs, 0))
        {
            kThrow(kERROR_OS);
        }

        // Don't inherit the read handle - otherwise child cannot detect read handle closing.
        if (!SetHandleInformation(obj->stdOutHandle, HANDLE_FLAG_INHERIT, 0))
        {
            kThrow(kERROR_OS);
        }

        if (!SetHandleInformation(obj->stdErrHandle, HANDLE_FLAG_INHERIT, 0))
        {
            kThrow(kERROR_OS);
        }

        if (!CreatePipe(&inputRead, &obj->stdInHandle, &securityAttrs, 0))
        {
            kThrow(kERROR_OS);
        }

        if (!SetHandleInformation(obj->stdInHandle, HANDLE_FLAG_INHERIT, 0))
        {
            kThrow(kERROR_OS);
        }

        startupInfo.cb = sizeof(startupInfo);
        startupInfo.hStdOutput = outputWriteOut;
        startupInfo.hStdError = outputWriteErr;
        startupInfo.hStdInput = inputRead;
        startupInfo.dwFlags = STARTF_USESTDHANDLES;

        if (!CreateProcess(wpath, wargument, NULL, NULL, TRUE, CREATE_NO_WINDOW | CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &startupInfo, &procInfo))
        {
            kThrow(kERROR_OS);
        }

        if (!CloseHandle(procInfo.hThread))
        {
            kThrow(kERROR_OS);
        }
        obj->process = procInfo.hProcess;

        // Add the process to the job to guarantee clean-up.

        if (sobj->job)
        {
            if (AssignProcessToJobObject(sobj->job, procInfo.hProcess) == 0)
            {
                kThrow(kERROR_OS);
            }
        }

        // Don't need to write. Open write handle prevents detection of write handle closing by child.
        if (!CloseHandle(outputWriteOut))
        {
            kThrow(kERROR_OS);
        }
        outputWriteOut = NULL;

        if (!CloseHandle(outputWriteErr))
        {
            kThrow(kERROR_OS);
        }
        outputWriteErr = NULL;

        if (!CloseHandle(inputRead))
        {
            kThrow(kERROR_OS);
        }
        inputRead = NULL;

        kAtomic32s_Exchange(&obj->isRunning, kTRUE);
    }
    kCatchEx(&exception)
    {
        if (outputWriteOut)
        {
            CloseHandle(outputWriteOut);
        }

        if (outputWriteErr)
        {
            CloseHandle(outputWriteErr);
        }

        if (inputRead)
        {
            CloseHandle(inputRead);
        }

        if (obj->stdOutHandle)
        {
            CloseHandle(obj->stdOutHandle);
            obj->stdOutHandle = NULL;
        }

        if (obj->stdErrHandle)
        {
            CloseHandle(obj->stdErrHandle);
            obj->stdErrHandle = NULL;
        }

        if (obj->stdInHandle)
        {
            CloseHandle(obj->stdInHandle);
            obj->stdInHandle = NULL;
        }

        if (obj->process)
        {
            TerminateProcess(obj->process, 1);
            CloseHandle(obj->process);
            obj->process = NULL;
        }

        kEndCatchEx(exception);
    }
    kFinallyEx
    {
        kMemFree(wargument);
        kEndFinallyEx();
    }

    return kOK;
}

kFx(kBool) kProcess_IsAlive(kProcess process)
{
    kObj(kProcess, process);
    DWORD exitCode = 0;

    if (!kAtomic32s_Get(&obj->isRunning))
    {
        return kFALSE;
    }

    if (!GetExitCodeProcess(obj->process, &exitCode))
    {
        return kFALSE;
    }

    return exitCode == STILL_ACTIVE;
}

kFx(kStatus) kProcess_Wait(kProcess process, k64u timeout, k64s* exitCode)
{
    kObj(kProcess, process);
    DWORD osTimeout = (DWORD) xkTimeToKernelTime(timeout); 

    if (!kIsNull(exitCode))
    {
        *exitCode = k64S_MAX;
    }

    if (!kAtomic32s_Get(&obj->isRunning))
    {
        return kOK;
    }

    if (WaitForSingleObject(obj->process, osTimeout) != WAIT_OBJECT_0)
    {
        TerminateProcess(obj->process, 0);
    }
    else if (!kIsNull(exitCode))
    {
        DWORD ec = 0;
        *exitCode = k64S_MAX;

        if (GetExitCodeProcess(obj->process, &ec))
        {
            *exitCode = ec;
        }
    }
    
    if (obj->process != kNULL)
    {
        CloseHandle(obj->process);
        obj->process = kNULL;
    }

    if (obj->stdInHandle != kNULL)
    {
        CloseHandle(obj->stdInHandle);
        obj->stdInHandle = kNULL;
    }

    if (obj->stdOutHandle != kNULL)
    {
        CloseHandle(obj->stdOutHandle);
        obj->stdOutHandle = kNULL;
    }

    if (obj->stdErrHandle != kNULL)
    {
        CloseHandle(obj->stdErrHandle);
        obj->stdErrHandle = kNULL;
    }

    kCheck(kDestroyRef(&obj->processStdIn));
    kCheck(kDestroyRef(&obj->processStdOut));
    kCheck(kDestroyRef(&obj->processStdErr));

    kAtomic32s_Exchange(&obj->isRunning, kFALSE);

    return kOK;
}

kFx(kStream) kProcess_StdIn(kProcess process)
{
    kObj(kProcess, process);
    
    if (kIsNull(obj->processStdIn) && kAtomic32s_Get(&obj->isRunning))
    {
        kPipeStream_ConstructFromHandle(&obj->processStdIn, obj->stdInHandle, kNULL);
    }
    
    return obj->processStdIn;
 }

kFx(kStream) kProcess_StdOut(kProcess process)
{
    kObj(kProcess, process);

    if (kIsNull(obj->processStdOut) && kAtomic32s_Get(&obj->isRunning))
    {
        kPipeStream_ConstructFromHandle(&obj->processStdOut, obj->stdOutHandle, kNULL);
    }

    return obj->processStdOut;
}

kFx(kStream) kProcess_StdErr(kProcess process)
{
    kObj(kProcess, process);

    if (kIsNull(obj->processStdErr) && kAtomic32s_Get(&obj->isRunning))
    {
        kPipeStream_ConstructFromHandle(&obj->processStdErr, obj->stdErrHandle, kNULL);
    }

    return obj->processStdErr;
}

#elif defined(K_LINUX)

kFx(kStatus) xkProcess_InitStatic()
{
    kStaticObj(kProcess);
    kStatus status = kOK;

    sobj->appStdIn = kNULL;
    sobj->appStdOut = kNULL;
    sobj->appStdErr = kNULL;
    
    kTry
    {
        kTest(kPipeStream_ConstructFromHandle(&sobj->appStdIn, STDIN_FILENO, kNULL));
        kTest(kPipeStream_ConstructFromHandle(&sobj->appStdOut, STDOUT_FILENO, kNULL));
        kTest(kPipeStream_ConstructFromHandle(&sobj->appStdErr, STDERR_FILENO, kNULL));
    }
    kCatch(&status)
    {
        kObject_Destroy(sobj->appStdIn);
        kObject_Destroy(sobj->appStdOut);
        kObject_Destroy(sobj->appStdErr);
        
        sobj->appStdIn = kNULL;
        sobj->appStdOut = kNULL;
        sobj->appStdErr = kNULL;
        
        kEndCatch(status);
    }

    return kOK;
}

kFx(kStatus) xkProcess_ReleaseStatic()
{
    kStaticObj(kProcess);

    kCheck(kObject_Destroy(sobj->appStdIn));
    kCheck(kObject_Destroy(sobj->appStdOut));
    kCheck(kObject_Destroy(sobj->appStdErr));

    return kOK;
}

kFx(kStatus) kProcess_InitPlatformFields(kProcess process)
{
    kObj(kProcess, process);

    obj->process = -1;
    obj->stdinPipe[kPROCESS_PIPE_READ] = -1;
    obj->stdinPipe[kPROCESS_PIPE_WRITE] = -1;
    obj->stdoutPipe[kPROCESS_PIPE_READ] = -1;
    obj->stdoutPipe[kPROCESS_PIPE_WRITE] = -1;
    obj->stderrPipe[kPROCESS_PIPE_READ] = -1;
    obj->stderrPipe[kPROCESS_PIPE_WRITE] = -1;
    obj->startSem = kNULL; 
    obj->forkThread = kNULL; 
    obj->exitCode = 0;

    kCheck(kSemaphore_Construct(&obj->startSem, 0, kNULL));
    kCheck(kThread_Construct(&obj->forkThread, kNULL));

    return kOK;
}

kFx(kStatus) kProcess_ReleasePlatformFields(kProcess process)
{
    kObj(kProcess, process);

    kCheck(kObject_Destroy(obj->startSem));
    kCheck(kObject_Destroy(obj->forkThread));

    return kOK;
}

kFx(kStatus) kProcess_Start(kProcess process)
{
    kObj(kProcess, process);
    kStatus exitCode = kOK;
    
    kCheckState(!kAtomic32s_Get(&obj->isRunning));

    obj->exitCode = k64S_MAX;

    kCheck(kThread_Start(obj->forkThread, kProcess_ForkThreadEntry, process));

    kCheck(kSemaphore_Wait(obj->startSem, kINFINITE));
    
    if (!kAtomic32s_Get(&obj->isRunning))
    {
        kCheck(kThread_Join(obj->forkThread, kINFINITE, &exitCode));
        return kERROR_OS;
    }

    return kOK;
}

kFx(kStatus) kProcess_ForkThreadEntry(kProcess process)
{
    kObj(kProcess, process);
    pid_t parent = 0;
    k32s status = 0;

    kTry
    {
        if (pipe(obj->stdinPipe) < 0 ||
            pipe(obj->stdoutPipe) < 0 ||
            pipe(obj->stderrPipe) < 0) 
        {
            kProcess_CloseHandles(process);
            kThrow(kERROR_OS); 
        }

        parent = getpid();

        // This creates a new process by duplicating the current one.
        // Return code is used to distinguish between child and parent:
        //    0 --> child process
        //   >0 --> this is the parent process
        obj->process = fork();

        if (obj->process == -1) // Failure
        {
            kProcess_CloseHandles(process);
            kThrow(kERROR_OS); 
        }     
    
        // From here we have two different processes - parent and child.

        if (obj->process > 0) // We are in the parent process.
        {
            kAtomic32s_Exchange(&obj->isRunning, kTRUE);
            kTest(kProcess_SetupParent(process));
        }
        else // We are in the child process. Will never return as exit() is called inside the function
        {
            kProcess_RunChild(process, parent);
        }
    }
    kFinally
    {
        kSemaphore_Post(obj->startSem);
        kEndFinally();
    }

    // wait for the child to exit
    do 
    {
        if (waitpid(obj->process, &status, WUNTRACED | WCONTINUED) == -1)
        {
            return kOK;
        }

        if (WIFEXITED(status)) 
        {
            obj->exitCode =  WEXITSTATUS(status);
        }
    } while (!WIFEXITED(status) && !WIFSIGNALED(status));

    return kOK;
}

kFx(kStatus) kProcess_SetupParent(kProcess process)
{
    kObj(kProcess, process);
    kStatus status = kOK;

    if (close(obj->stdinPipe[kPROCESS_PIPE_READ]))
    {
        status = kERROR_OS;
    }

    if (close(obj->stdoutPipe[kPROCESS_PIPE_WRITE]))
    {
        status = kERROR_OS;
    }

    if (close(obj->stderrPipe[kPROCESS_PIPE_WRITE]))
    {
        status = kERROR_OS;
    }
    
    obj->stdinPipe[kPROCESS_PIPE_READ] = -1;
    obj->stdoutPipe[kPROCESS_PIPE_WRITE] = -1;
    obj->stderrPipe[kPROCESS_PIPE_WRITE] = -1;

    return status;
}


kFx(void) kProcess_RunChild(kProcess process, pid_t parent)
{
    kObj(kProcess, process);
    kChar** arguments = kNULL;
    kChar path[kPATH_MAX];
    k32s returnCode = 0;
    kSize i = 0;
    kSize argumentCount = 0;

    // SIGTERM is the signal that child will get when _THREAD_ which forked ends.
    // That's why we use a separate thread for kProcess_Start on Linux.
    // PR_SET_PDEATHSIG is not supported on WSL so do not care if it's EINVAL.
    if (prctl(PR_SET_PDEATHSIG, SIGTERM) == -1 && errno != EINVAL) 
    { 
        exit(-1);
    }

    // Test in case the original parent exited just before the prctl() call.
    if (getppid() != parent)
    {
        exit(0);
    }

    // redirect standard streams
    if (dup2(obj->stdinPipe[kPROCESS_PIPE_READ], STDIN_FILENO) == -1) 
    {
        exit(-1);
    }

    if (dup2(obj->stdoutPipe[kPROCESS_PIPE_WRITE], STDOUT_FILENO) == -1) 
    {
        exit(-1);
    }

    if (dup2(obj->stderrPipe[kPROCESS_PIPE_WRITE], STDERR_FILENO) == -1) 
    {
        exit(-1);
    }

    if(!kSuccess(kProcess_CloseHandles(process)))
    {
        exit(-1);
    }

    if (!kSuccess(kPath_FileName(kString_Chars(obj->path), path, kCountOf(path))))
    {
        exit(-1);
    }

    argumentCount = kArrayList_Count(obj->arguments);

    if (!kSuccess(kMemAlloc(argumentCount + 2, &arguments)))
    {
        exit(-1);
    }

    arguments[0] = path;
    arguments[argumentCount + 1] = kNULL;

    for (i = 0; i < argumentCount; i++)
    {
        kString item = kArrayList_AsT(obj->arguments, i, kString); 
        arguments[i + 1] = kString_Chars(item);
    }

    // This replaces the current process image with a new process image.
    // Only returns if an error has occurred.
    execv(kString_Chars(obj->path), arguments);
    
    kMemFree(arguments);

    exit(returnCode);
}

kFx(kBool) kProcess_IsAlive(kProcess process)
{
    kObj(kProcess, process);
    k32s status = kOK;

    if (!kAtomic32s_Get(&obj->isRunning))
    {
        return kFALSE;
    }

    if (kThread_Join(obj->forkThread, 0, &status) == kERROR_TIMEOUT)
    {
        return kTRUE;
    }

    kAtomic32s_Exchange(&obj->isRunning, kFALSE);

    return kFALSE;
}

kFx(kStatus) kProcess_Wait(kProcess process, k64u timeout, k64s* exitCode)
{
    kObj(kProcess, process);
    kStatus status = kOK;
    kStatus threadExitCode = kOK;

    if (kAtomic32s_Get(&obj->isRunning) && kThread_Join(obj->forkThread, timeout, &threadExitCode) == kERROR_TIMEOUT)
    {
        if ((kill(obj->process, SIGKILL) < 0) && (errno != ESRCH))
        {
            status = kERROR_OS;
        }

        // In the case of a terminated child, performing a wait allows the system 
        // to release the resources associated with the child; if a wait is not 
        // performed, then the terminated child remains in a "zombie" state.
        if ((waitpid(obj->process, kNULL, 0) < 0) && (errno != ECHILD))
        {
            status = kERROR_OS;
        }

        status = kThread_Join(obj->forkThread, kINFINITE, &threadExitCode);
    }

    kAtomic32s_Exchange(&obj->isRunning, kFALSE);

    if (!kIsNull(exitCode))
    {
        *exitCode = obj->exitCode;
    }

    if ((obj->stdinPipe[kPROCESS_PIPE_WRITE] >= 0) && close(obj->stdinPipe[kPROCESS_PIPE_WRITE]))
    {
        status = kERROR_OS;
    }

    if ((obj->stdoutPipe[kPROCESS_PIPE_READ] >= 0) && close(obj->stdoutPipe[kPROCESS_PIPE_READ]))
    {
        status = kERROR_OS;
    }

    if ((obj->stderrPipe[kPROCESS_PIPE_READ] >= 0) && close(obj->stderrPipe[kPROCESS_PIPE_READ]))
    {
        status = kERROR_OS;
    }

    obj->stdinPipe[kPROCESS_PIPE_WRITE] = -1;
    obj->stdoutPipe[kPROCESS_PIPE_READ] = -1;
    obj->stderrPipe[kPROCESS_PIPE_READ] = -1;

    obj->process = -1;

    kCheck(kDestroyRef(&obj->processStdIn));
    kCheck(kDestroyRef(&obj->processStdOut));
    kCheck(kDestroyRef(&obj->processStdErr));

    return status;
}

kFx(kStream) kProcess_StdIn(kProcess process)
{
    kObj(kProcess, process);

    if (kIsNull(obj->processStdIn) && kAtomic32s_Get(&obj->isRunning))
    {
        kPipeStream_ConstructFromHandle(&obj->processStdIn, obj->stdinPipe[kPROCESS_PIPE_WRITE], kNULL);
    }

    return obj->processStdIn;
}

kFx(kStream) kProcess_StdOut(kProcess process)
{
    kObj(kProcess, process);

    if (kIsNull(obj->processStdOut) && kAtomic32s_Get(&obj->isRunning))
    {
        kPipeStream_ConstructFromHandle(&obj->processStdOut, obj->stdoutPipe[kPROCESS_PIPE_READ], kNULL);
    }

    return obj->processStdOut;
}

kFx(kStream) kProcess_StdErr(kProcess process)
{
    kObj(kProcess, process);

    if (kIsNull(obj->processStdErr) && kAtomic32s_Get(&obj->isRunning))
    {
        kPipeStream_ConstructFromHandle(&obj->processStdErr, obj->stderrPipe[kPROCESS_PIPE_READ], kNULL);
    }

    return obj->processStdErr;
}

kFx(kStatus) kProcess_CloseHandles(kProcess process)
{
    kObj(kProcess, process);
    kStatus status = kOK;

    if ((obj->stdinPipe[kPROCESS_PIPE_READ] >= 0) && (close(obj->stdinPipe[kPROCESS_PIPE_READ]))) status = kERROR_OS;
    if ((obj->stdinPipe[kPROCESS_PIPE_WRITE] >= 0) && (close(obj->stdinPipe[kPROCESS_PIPE_WRITE]))) status = kERROR_OS;
    if ((obj->stdoutPipe[kPROCESS_PIPE_READ] >= 0) && (close(obj->stdoutPipe[kPROCESS_PIPE_READ]))) status = kERROR_OS;
    if ((obj->stdoutPipe[kPROCESS_PIPE_WRITE] >= 0) && (close(obj->stdoutPipe[kPROCESS_PIPE_WRITE]))) status = kERROR_OS;
    if ((obj->stderrPipe[kPROCESS_PIPE_READ] >= 0) && (close(obj->stderrPipe[kPROCESS_PIPE_READ]))) status = kERROR_OS;
    if ((obj->stderrPipe[kPROCESS_PIPE_WRITE] >= 0) && (close(obj->stderrPipe[kPROCESS_PIPE_WRITE]))) status = kERROR_OS;


    obj->stdinPipe[kPROCESS_PIPE_READ] = -1;
    obj->stdinPipe[kPROCESS_PIPE_WRITE] = -1;
    obj->stdoutPipe[kPROCESS_PIPE_READ] = -1;
    obj->stdoutPipe[kPROCESS_PIPE_WRITE] = -1;
    obj->stderrPipe[kPROCESS_PIPE_READ] = -1;
    obj->stderrPipe[kPROCESS_PIPE_WRITE] = -1;

    return status;
}

#else

kFx(kStatus) xkProcess_InitStatic()
{
    return kOK;
}

kFx(kStatus) xkProcess_ReleaseStatic()
{
    return kOK;
}

kFx(kStatus) kProcess_Construct(kProcess* process, const kChar* path, kAlloc allocator)
{
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) kProcess_Init(kProcess process, const kChar* path, kType type, kAlloc alloc)
{
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) kProcess_VRelease(kProcess process)
{
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) kProcess_AddArgument(kProcess process, const kChar* argument)
{
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) kProcess_AddArguments(kProcess process, const kChar* arguments[], kSize argCount)
{
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) kProcess_ClearArguments(kProcess process)
{
    return kERROR_UNIMPLEMENTED;
}

kFx(kStatus) kProcess_Start(kProcess process)
{
    return kERROR_UNIMPLEMENTED;
}

kFx(kBool) kProcess_IsAlive(kProcess process)
{
    return kFALSE;
}

kFx(kStatus) kProcess_Wait(kProcess process, k64u timeout, k64s* exitCode)
{
    return kERROR_UNIMPLEMENTED;
}

kFx(kStream) kProcess_StdIn(kProcess process)
{
    return kNULL;
}

kFx(kStream) kProcess_StdOut(kProcess process)
{
    return kNULL;
}

kFx(kStream) kProcess_StdErr(kProcess process)
{
    return kNULL;
}

kFx(kStream) kProcess_AppStdIn()
{
    return kNULL;
}

kFx(kStream) kProcess_AppStdOut()
{
    return kNULL;
}

kFx(kStream) kProcess_AppStdErr()
{
    return kNULL;
}

#endif


