/*
 * Process environment management
 *
 * Copyright 1996, 1998 Alexandre Julliard
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"
#include "wine/port.h"

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "wine/library.h"
#include "winternl.h"
#include "wine/unicode.h"
#include "wine/debug.h"

#include "kernel_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(environ);

/* Notes:
 * - contrary to Microsoft docs, the environment strings do not appear
 *   to be sorted on Win95 (although they are on NT); so we don't bother
 *   to sort them either.
 */

static STARTUPINFOW startup_infoW;
static STARTUPINFOA startup_infoA;


/***********************************************************************
 *           GetCommandLineA      (KERNEL32.@)
 *
 * WARNING: there's a Windows incompatibility lurking here !
 * Win32s always includes the full path of the program file,
 * whereas Windows NT only returns the full file path plus arguments
 * in case the program has been started with a full path.
 * Win9x seems to have inherited NT behaviour.
 *
 * Note that both Start Menu Execute and Explorer start programs with
 * fully specified quoted app file paths, which is why probably the only case
 * where you'll see single file names is in case of direct launch
 * via CreateProcess or WinExec.
 *
 * Perhaps we should take care of Win3.1 programs here (Win32s "feature").
 *
 * References: MS KB article q102762.txt (special Win32s handling)
 */
LPSTR WINAPI GetCommandLineA(void)
{
    static char *cmdlineA;  /* ASCII command line */
    
    if (!cmdlineA) /* make an ansi version if we don't have it */
    {
        ANSI_STRING     ansi;
        RtlAcquirePebLock();

        cmdlineA = (RtlUnicodeStringToAnsiString( &ansi, &NtCurrentTeb()->Peb->ProcessParameters->CommandLine, TRUE) == STATUS_SUCCESS) ?
            ansi.Buffer : NULL;
        RtlReleasePebLock();
    }
    return cmdlineA;
}

/***********************************************************************
 *           GetCommandLineW      (KERNEL32.@)
 */
LPWSTR WINAPI GetCommandLineW(void)
{
    return NtCurrentTeb()->Peb->ProcessParameters->CommandLine.Buffer;
}


/***********************************************************************
 *           GetEnvironmentStringsA   (KERNEL32.@)
 *           GetEnvironmentStrings    (KERNEL32.@)
 */
LPSTR WINAPI GetEnvironmentStringsA(void)
{
    LPWSTR      ptrW;
    unsigned    len, slen;
    LPSTR       ret, ptrA;

    RtlAcquirePebLock();

    len = 1;

    ptrW = NtCurrentTeb()->Peb->ProcessParameters->Environment;
    while (*ptrW)
    {
        slen = strlenW(ptrW) + 1;
        len += WideCharToMultiByte( CP_ACP, 0, ptrW, slen, NULL, 0, NULL, NULL );
        ptrW += slen;
    }

    if ((ret = HeapAlloc( GetProcessHeap(), 0, len )) != NULL)
    {
        ptrW = NtCurrentTeb()->Peb->ProcessParameters->Environment;
        ptrA = ret;
        while (*ptrW)
        {
            slen = strlenW(ptrW) + 1;
            WideCharToMultiByte( CP_ACP, 0, ptrW, slen, ptrA, len, NULL, NULL );
            ptrW += slen;
            ptrA += strlen(ptrA) + 1;
        }
        *ptrA = 0;
    }

    RtlReleasePebLock();
    return ret;
}


/***********************************************************************
 *           GetEnvironmentStringsW   (KERNEL32.@)
 */
LPWSTR WINAPI GetEnvironmentStringsW(void)
{
    return NtCurrentTeb()->Peb->ProcessParameters->Environment;
}


/***********************************************************************
 *           FreeEnvironmentStringsA   (KERNEL32.@)
 */
BOOL WINAPI FreeEnvironmentStringsA( LPSTR ptr )
{
    return HeapFree( GetProcessHeap(), 0, ptr );
}


/***********************************************************************
 *           FreeEnvironmentStringsW   (KERNEL32.@)
 */
BOOL WINAPI FreeEnvironmentStringsW( LPWSTR ptr )
{
    return TRUE;
}


/***********************************************************************
 *           GetEnvironmentVariableA   (KERNEL32.@)
 */
DWORD WINAPI GetEnvironmentVariableA( LPCSTR name, LPSTR value, DWORD size )
{
    UNICODE_STRING      us_name;
    PWSTR               valueW;
    DWORD               ret;

    if (!name || !*name)
    {
        SetLastError(ERROR_ENVVAR_NOT_FOUND);
        return 0;
    }

    /* limit the size to sane values */
    size = min(size, 32767);
    if (!(valueW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR))))
        return 0;

    RtlCreateUnicodeStringFromAsciiz( &us_name, name );
    SetLastError(0);
    ret = GetEnvironmentVariableW( us_name.Buffer, valueW, size);
    if (ret && ret < size)
    {
        WideCharToMultiByte( CP_ACP, 0, valueW, ret + 1, value, size, NULL, NULL );
    }
    /* this is needed to tell, with 0 as a return value, the difference between:
     * - an error (GetLastError() != 0)
     * - returning an empty string (in this case, we need to update the buffer)
     */
    if (ret == 0 && size && GetLastError() == 0)
        value[0] = '\0';

    RtlFreeUnicodeString( &us_name );
    HeapFree(GetProcessHeap(), 0, valueW);

    return ret;
}


/***********************************************************************
 *           GetEnvironmentVariableW   (KERNEL32.@)
 */
DWORD WINAPI GetEnvironmentVariableW( LPCWSTR name, LPWSTR val, DWORD size )
{
    UNICODE_STRING      us_name;
    UNICODE_STRING      us_value;
    NTSTATUS            status;
    unsigned            len;

    TRACE("(%s %p %u)\n", debugstr_w(name), val, size);

    if (!name || !*name)
    {
        SetLastError(ERROR_ENVVAR_NOT_FOUND);
        return 0;
    }

    RtlInitUnicodeString(&us_name, name);
    us_value.Length = 0;
    us_value.MaximumLength = (size ? size - 1 : 0) * sizeof(WCHAR);
    us_value.Buffer = val;

    status = RtlQueryEnvironmentVariable_U(NULL, &us_name, &us_value);
    len = us_value.Length / sizeof(WCHAR);
    if (status != STATUS_SUCCESS)
    {
        SetLastError( RtlNtStatusToDosError(status) );
        return (status == STATUS_BUFFER_TOO_SMALL) ? len + 1 : 0;
    }
    if (size) val[len] = '\0';

    return us_value.Length / sizeof(WCHAR);
}


/***********************************************************************
 *           SetEnvironmentVariableA   (KERNEL32.@)
 */
BOOL WINAPI SetEnvironmentVariableA( LPCSTR name, LPCSTR value )
{
    UNICODE_STRING      us_name;
    BOOL                ret;

    if (!name)
    {
        SetLastError(ERROR_ENVVAR_NOT_FOUND);
        return FALSE;
    }

    RtlCreateUnicodeStringFromAsciiz( &us_name, name );
    if (value)
    {
        UNICODE_STRING      us_value;

        RtlCreateUnicodeStringFromAsciiz( &us_value, value );
        ret = SetEnvironmentVariableW( us_name.Buffer, us_value.Buffer );
        RtlFreeUnicodeString( &us_value );
    }
    else ret = SetEnvironmentVariableW( us_name.Buffer, NULL );

    RtlFreeUnicodeString( &us_name );

    return ret;
}


/***********************************************************************
 *           SetEnvironmentVariableW   (KERNEL32.@)
 */
BOOL WINAPI SetEnvironmentVariableW( LPCWSTR name, LPCWSTR value )
{
    UNICODE_STRING      us_name;
    NTSTATUS            status;

    TRACE("(%s %s)\n", debugstr_w(name), debugstr_w(value));

    if (!name)
    {
        SetLastError(ERROR_ENVVAR_NOT_FOUND);
        return FALSE;
    }

    RtlInitUnicodeString(&us_name, name);
    if (value)
    {
        UNICODE_STRING      us_value;

        RtlInitUnicodeString(&us_value, value);
        status = RtlSetEnvironmentVariable(NULL, &us_name, &us_value);
    }
    else status = RtlSetEnvironmentVariable(NULL, &us_name, NULL);

    if (status != STATUS_SUCCESS)
    {
        SetLastError( RtlNtStatusToDosError(status) );
        return FALSE;
    }
    return TRUE;
}


/***********************************************************************
 *           ExpandEnvironmentStringsA   (KERNEL32.@)
 *
 * See ExpandEnvironmentStringsW.
 *
 * Note: overlapping buffers are not supported; this is how it should be.
 * FIXME: return value is wrong for MBCS
 */
DWORD WINAPI ExpandEnvironmentStringsA( LPCSTR src, LPSTR dst, DWORD count )
{
    UNICODE_STRING      us_src;
    PWSTR               dstW = NULL;
    DWORD               ret;

    RtlCreateUnicodeStringFromAsciiz( &us_src, src );
    if (count)
    {
        if (!(dstW = HeapAlloc(GetProcessHeap(), 0, count * sizeof(WCHAR))))
            return 0;
        ret = ExpandEnvironmentStringsW( us_src.Buffer, dstW, count);
        if (ret)
            WideCharToMultiByte( CP_ACP, 0, dstW, ret, dst, count, NULL, NULL );
    }
    else ret = ExpandEnvironmentStringsW( us_src.Buffer, NULL, 0);

    RtlFreeUnicodeString( &us_src );
    HeapFree(GetProcessHeap(), 0, dstW);

    return ret;
}


/***********************************************************************
 *           ExpandEnvironmentStringsW   (KERNEL32.@)
 *
 * Replaces references to environment variables of the form '%EnvVar%'
 * by their value. If the environment variable does not exist, then the
 * reference is left as is.
 *
 * PARAMS
 *  src       [I] The string to be expanded.
 *  dst       [O] The buffer in which to put the expanded string.
 *  len       [I] The buffer size, in characters.
 *
 * RETURNS
 *  The number of characters copied into the buffer. If the buffer is
 *  too small, then the required buffer size, in characters including the
 *  trailing '\0', is returned.
 *  If the function fails for some other reason, then it returns 0.
 */
DWORD WINAPI ExpandEnvironmentStringsW( LPCWSTR src, LPWSTR dst, DWORD len )
{
    UNICODE_STRING      us_src;
    UNICODE_STRING      us_dst;
    NTSTATUS            status;
    DWORD               res;

    TRACE("(%s %p %u)\n", debugstr_w(src), dst, len);

    RtlInitUnicodeString(&us_src, src);

    /* make sure we don't overflow the maximum UNICODE_STRING size */
    if (len > UNICODE_STRING_MAX_CHARS)
        len = UNICODE_STRING_MAX_CHARS;

    us_dst.Length = 0;
    us_dst.MaximumLength = len * sizeof(WCHAR);
    us_dst.Buffer = dst;

    res = 0;
    status = RtlExpandEnvironmentStrings_U(NULL, &us_src, &us_dst, &res);
    res /= sizeof(WCHAR);
    if (status != STATUS_SUCCESS)
    {
        SetLastError( RtlNtStatusToDosError(status) );
        if (status != STATUS_BUFFER_TOO_SMALL) return 0;
        if (len && dst) dst[len - 1] = '\0';
    }

    return res;
}


/***********************************************************************
 *           GetStdHandle    (KERNEL32.@)
 */
HANDLE WINAPI GetStdHandle( DWORD std_handle )
{
    switch (std_handle)
    {
        case STD_INPUT_HANDLE:  return NtCurrentTeb()->Peb->ProcessParameters->hStdInput;
        case STD_OUTPUT_HANDLE: return NtCurrentTeb()->Peb->ProcessParameters->hStdOutput;
        case STD_ERROR_HANDLE:  return NtCurrentTeb()->Peb->ProcessParameters->hStdError;
    }
    SetLastError( ERROR_INVALID_HANDLE );
    return INVALID_HANDLE_VALUE;
}


/***********************************************************************
 *           SetStdHandle    (KERNEL32.@)
 */
BOOL WINAPI SetStdHandle( DWORD std_handle, HANDLE handle )
{
    switch (std_handle)
    {
        case STD_INPUT_HANDLE:  NtCurrentTeb()->Peb->ProcessParameters->hStdInput = handle;  return TRUE;
        case STD_OUTPUT_HANDLE: NtCurrentTeb()->Peb->ProcessParameters->hStdOutput = handle; return TRUE;
        case STD_ERROR_HANDLE:  NtCurrentTeb()->Peb->ProcessParameters->hStdError = handle;  return TRUE;
    }
    SetLastError( ERROR_INVALID_HANDLE );
    return FALSE;
}

/***********************************************************************
 *              GetStartupInfoA         (KERNEL32.@)
 */
VOID WINAPI GetStartupInfoA( LPSTARTUPINFOA info )
{
    *info = startup_infoA;
}


/***********************************************************************
 *              GetStartupInfoW         (KERNEL32.@)
 */
VOID WINAPI GetStartupInfoW( LPSTARTUPINFOW info )
{
    *info = startup_infoW;
}

/******************************************************************
 *		ENV_CopyStartupInformation (internal)
 *
 * Creates the STARTUPINFO information from the ntdll information
 */
void ENV_CopyStartupInformation(void)
{
    RTL_USER_PROCESS_PARAMETERS* rupp;
    ANSI_STRING         ansi;

    RtlAcquirePebLock();
    
    rupp = NtCurrentTeb()->Peb->ProcessParameters;

    startup_infoW.cb                   = sizeof(startup_infoW);
    startup_infoW.lpReserved           = NULL;
    startup_infoW.lpDesktop            = rupp->Desktop.Buffer;
    startup_infoW.lpTitle              = rupp->WindowTitle.Buffer;
    startup_infoW.dwX                  = rupp->dwX;
    startup_infoW.dwY                  = rupp->dwY;
    startup_infoW.dwXSize              = rupp->dwXSize;
    startup_infoW.dwYSize              = rupp->dwYSize;
    startup_infoW.dwXCountChars        = rupp->dwXCountChars;
    startup_infoW.dwYCountChars        = rupp->dwYCountChars;
    startup_infoW.dwFillAttribute      = rupp->dwFillAttribute;
    startup_infoW.dwFlags              = rupp->dwFlags;
    startup_infoW.wShowWindow          = rupp->wShowWindow;
    startup_infoW.cbReserved2          = rupp->RuntimeInfo.MaximumLength;
    startup_infoW.lpReserved2          = rupp->RuntimeInfo.MaximumLength ? (void*)rupp->RuntimeInfo.Buffer : NULL;
    startup_infoW.hStdInput            = rupp->hStdInput ? rupp->hStdInput : INVALID_HANDLE_VALUE;
    startup_infoW.hStdOutput           = rupp->hStdOutput ? rupp->hStdOutput : INVALID_HANDLE_VALUE;
    startup_infoW.hStdError            = rupp->hStdError ? rupp->hStdError : INVALID_HANDLE_VALUE;

    startup_infoA.cb                   = sizeof(startup_infoA);
    startup_infoA.lpReserved           = NULL;
    startup_infoA.lpDesktop = RtlUnicodeStringToAnsiString( &ansi, &rupp->Desktop, TRUE ) == STATUS_SUCCESS ?
        ansi.Buffer : NULL;
    startup_infoA.lpTitle = RtlUnicodeStringToAnsiString( &ansi, &rupp->WindowTitle, TRUE ) == STATUS_SUCCESS ?
        ansi.Buffer : NULL;
    startup_infoA.dwX                  = rupp->dwX;
    startup_infoA.dwY                  = rupp->dwY;
    startup_infoA.dwXSize              = rupp->dwXSize;
    startup_infoA.dwYSize              = rupp->dwYSize;
    startup_infoA.dwXCountChars        = rupp->dwXCountChars;
    startup_infoA.dwYCountChars        = rupp->dwYCountChars;
    startup_infoA.dwFillAttribute      = rupp->dwFillAttribute;
    startup_infoA.dwFlags              = rupp->dwFlags;
    startup_infoA.wShowWindow          = rupp->wShowWindow;
    startup_infoA.cbReserved2          = rupp->RuntimeInfo.MaximumLength;
    startup_infoA.lpReserved2          = rupp->RuntimeInfo.MaximumLength ? (void*)rupp->RuntimeInfo.Buffer : NULL;
    startup_infoA.hStdInput            = rupp->hStdInput ? rupp->hStdInput : INVALID_HANDLE_VALUE;
    startup_infoA.hStdOutput           = rupp->hStdOutput ? rupp->hStdOutput : INVALID_HANDLE_VALUE;
    startup_infoA.hStdError            = rupp->hStdError ? rupp->hStdError : INVALID_HANDLE_VALUE;

    RtlReleasePebLock();
}

/***********************************************************************
 *              GetFirmwareEnvironmentVariableA         (KERNEL32.@)
 */
DWORD WINAPI GetFirmwareEnvironmentVariableA(LPCSTR name, LPCSTR guid, PVOID buffer, DWORD size)
{
    FIXME("stub: %s %s %p %u\n", debugstr_a(name), debugstr_a(guid), buffer, size);
    SetLastError(ERROR_INVALID_FUNCTION);
    return 0;
}

/***********************************************************************
 *              GetFirmwareEnvironmentVariableW         (KERNEL32.@)
 */
DWORD WINAPI GetFirmwareEnvironmentVariableW(LPCWSTR name, LPCWSTR guid, PVOID buffer, DWORD size)
{
    FIXME("stub: %s %s %p %u\n", debugstr_w(name), debugstr_w(guid), buffer, size);
    SetLastError(ERROR_INVALID_FUNCTION);
    return 0;
}
