/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* dbus-sysdeps-wince-glue.c Wrappers for Windows CE around system/libc features (internal to D-BUS implementation)
 * 
 * Copyright (C) 2002, 2003  Red Hat, Inc.
 * Copyright (C) 2003 CodeFactory AB
 * Copyright (C) 2005 Novell, Inc.
 * Copyright (C) 2006 Ralf Habacker <ralf.habacker@freenet.de>
 * Copyright (C) 2006 Peter Kümmel  <syntheticpp@gmx.net>
 * Copyright (C) 2006 Christian Ehrlicher <ch.ehrlicher@gmx.de>
 *
 * Licensed under the Academic Free License version 2.1
 * 
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <config.h>
#include "dbus-internals.h"
#include "dbus-sysdeps.h"
#include "dbus-sysdeps-win.h"

#include <windows.h>
/* Including shlobj.h creates trouble on some compilers.  Just chicken
   out here by defining just what we need.  */
#ifndef CSIDL_PERSONAL
#define CSIDL_PERSONAL 5
#endif


/* Copy SRC to DEST, returning the address of the terminating '\0' in DEST.  */
static char *
stpcpy (char *dest, const char *src)
{
  char *d = dest;
  const char *s = src;

  do
    *d++ = *s;
  while (*s++ != '\0');

  return d - 1;
}


/* This is special cased, because we must avoid using many dbus
   functions (such as memory allocations): Those functions may in turn
   cause verbose output and check the flag!  */
static char *
get_verbose_setting()
{
  const wchar_t dir[] = L"Software\\freedesktop\\DBus";
  const wchar_t name[] = L"Verbose";
  HKEY root_key;
  HKEY key_handle;
  DWORD nbytes;
  DWORD n1;
  DWORD type;
  wchar_t *result_w = NULL;
  char *result;
  int len;

  root_key = HKEY_LOCAL_MACHINE;
  if (RegOpenKeyExW (root_key, dir, 0, KEY_READ, &key_handle))
    return NULL;

  nbytes = 1;
  if (RegQueryValueExW (key_handle, name, 0, NULL, NULL, &nbytes))
    {
      RegCloseKey (key_handle);
      return NULL;
    }
  /* Round up to multiple of wchar_t, convert to number of wchar_t's, and add 1.  */
  n1 = ((nbytes + sizeof(wchar_t) - 1) / sizeof (wchar_t)) + 1;
  result_w = malloc (n1 * sizeof (wchar_t));
  if (!result_w)
    {
      RegCloseKey (key_handle);
      return NULL;
    }
  if (RegQueryValueExW (key_handle, name, 0, &type, (LPBYTE) result_w, &nbytes))
    {
      RegCloseKey (key_handle);
      free (result_w);
      return NULL;
    }
  RegCloseKey (key_handle);
  result_w[n1 - 1] = 0; /* Make sure it is really a string.  */

  /* NOTE: REG_MULTI_SZ and REG_EXPAND_SZ not supported, because they
     are not needed in this module.  */
  if (type != REG_SZ)
    {
      free (result_w);
      return NULL;
    }

  len = WideCharToMultiByte (CP_UTF8, 0, result_w, -1, NULL, 0, NULL, NULL);
  if (len < 0)
    {
      free (result_w);
      return NULL;
    }

  result = malloc (len + 1);
  if (!result)
    {
      free (result_w);
      return NULL;
    }

  len = WideCharToMultiByte (CP_UTF8, 0, result_w, -1, result, len, NULL, NULL);
  free (result_w);
  if (len < 0)
    {
      free (result);
      return NULL;
    }
  return result;
}


/* Return a string from the W32 Registry or NULL in case of error.
   Caller must release the return value.  A NULL for root is an alias
   for HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE in turn. */
static char *
read_w32_registry_string (const char *root, const char *dir, const char *name)
{
  HKEY root_key, key_handle;
  DWORD n1, nbytes, type;
  char *result = NULL;
	
  if ( !root )
    root_key = HKEY_CURRENT_USER;
  else if ( !strcmp( root, "HKEY_CLASSES_ROOT" ) )
    root_key = HKEY_CLASSES_ROOT;
  else if ( !strcmp( root, "HKEY_CURRENT_USER" ) )
    root_key = HKEY_CURRENT_USER;
  else if ( !strcmp( root, "HKEY_LOCAL_MACHINE" ) )
    root_key = HKEY_LOCAL_MACHINE;
  else if ( !strcmp( root, "HKEY_USERS" ) )
    root_key = HKEY_USERS;
  else
    return NULL;

  if (RegOpenKeyExA (root_key, dir, 0, KEY_READ, &key_handle))
    {
      if (root)
        return NULL; /* no need for a RegClose, so return direct */
      /* It seems to be common practise to fall back to HKLM. */
      if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle))
        return NULL; /* still no need for a RegClose, so return direct */
    }

  nbytes = 1;
  if (RegQueryValueExA (key_handle, name, 0, NULL, NULL, &nbytes))
    {
      if (root)
        goto out;
      /* Try to fallback to HKLM also for a missing value.  */
      RegCloseKey (key_handle);
      if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle))
        return NULL; /* Nope.  */
      if (RegQueryValueExA (key_handle, name, 0, NULL, NULL, &nbytes))
        goto out;
    }
  n1 = nbytes + 1;
  result = malloc (n1);
  if (!result)
    goto out;
  if (RegQueryValueExA (key_handle, name, 0, &type, result, &n1))
    {
      free(result);
      result = NULL;
      goto out;
    }
  result[nbytes] = 0; /* Make sure it is really a string.  */

 out:
  RegCloseKey (key_handle);
  return result;
}


static char *
find_inst_dir ()
{
  return read_w32_registry_string ("HKEY_LOCAL_MACHINE",
				  "Software\\freedesktop\\DBus",
				  "Install Directory");
}


static char *
find_env_in_registry (const char *name)
{
  return read_w32_registry_string ("HKEY_LOCAL_MACHINE",
                                   "Software\\freedesktop\\DBus",
                                   name);
}


static char *
find_program_in_inst_dir (const char *name)
{
  char *result = NULL;
  char *tmp;

  tmp = find_inst_dir ();
  if (!tmp)
    return NULL;

  result = malloc (strlen (tmp) + 5 + strlen (name) + 1);
  if (!result)
    {
      free (tmp);
      return NULL;
    }

  strcpy (stpcpy (stpcpy (result, tmp), "\\bin\\"), name);
  free (tmp);

  return result;
}


static char *
find_inst_subdir (const char *name)
{
  char *result = NULL;
  char *tmp;

  tmp = find_inst_dir ();
  if (!tmp)
    return NULL;

  result = malloc (strlen (tmp) + 1 + strlen (name) + 1);
  if (!result)
    {
      free (tmp);
      return NULL;
    }

  strcpy (stpcpy (stpcpy (result, tmp), "\\"), name);
  free (tmp);

  return result;
}


static char *
find_my_documents_folder ()
{
  /* One for safety, just in case.  */
  char dir[MAX_PATH + 1];
  char *result;

  dir[0] = '\0';
  /* May return false even if successful.  */
  SHGetSpecialFolderPathA (0, dir, CSIDL_PERSONAL, 0);
  if (dir[0] == '\0')
    return NULL;

  result = malloc (strlen (dir) + 1);
  if (!result)
    return NULL;
  strcpy (result, dir);
  return result;
}


#define MAX_ENV 30

char *environ[MAX_ENV + 1];

char *
getenv (const char *name)
{
  static char *past_result;
  char **envp;
  int idx;

  if (past_result)
    {
      free (past_result);
      past_result = NULL;
    }

  if (! strcmp (name, "DBUS_VERBOSE"))
    return past_result = get_verbose_setting ();
  else if (! strcmp (name, "HOMEPATH"))
    return past_result = find_my_documents_folder ();
  else if (! strcmp (name, "DBUS_DATADIR"))
    return past_result = find_inst_subdir ("share");

  for (envp = environ; *envp != 0; envp++)
    {
      const char *varp = name;
      char *ep = *envp;
      int same_name = 0;

      while (*varp == *ep && *varp != '\0')
	{
	  ++ep;
	  ++varp;
	};

      if (*varp == '\0' && *ep == '=')
	return ep + 1;
    }

  return NULL;
}


int
putenv (char *str)
{
  char **envp;
  int idx;
  for (envp = environ; *envp != 0; envp++)
    {
      char *varp = str;
      char *ep = *envp;
      int same_name = 0;

      while (*varp == *ep && *varp != '\0')
	{
	  if (*varp == '=')
	    same_name = 1;
	  ++ep;
	  ++varp;
	};

      if (*varp == *ep && *varp == '\0')
	return 0;
      if (same_name)
	{
	  *envp = str;
	  return 0;
	}
    }

  idx = envp - environ;
  if (idx > MAX_ENV)
    {
      _dbus_win_set_errno (ENOMEM);
      return -1;
    }

  environ[idx] = str;
  return 0;
}


clock_t
clock (void)
{
  return GetTickCount ();
}


void
abort (void)
{
  /* This is what windows does.  */
  exit (3);
}


void
GetSystemTimeAsFileTime (LPFILETIME ftp)
{
  SYSTEMTIME st;
  GetSystemTime (&st);
  SystemTimeToFileTime (&st, ftp);
}


unsigned char*
_mbsrchr (const unsigned char* str, unsigned int ch)
{
  /* FIXME.  This is not multi-byte safe.  */
  return strrchr (str, ch);
}


HANDLE OpenFileMappingA(DWORD dwDesiredAccess,
			BOOL bInheritHandle,
			LPCSTR lpName)
{
  DWORD flProtect = 0;
  HANDLE hMapping;

  if (dwDesiredAccess & FILE_MAP_READ)
    flProtect |= PAGE_READONLY;

  if (dwDesiredAccess & FILE_MAP_WRITE)
    flProtect |= PAGE_READWRITE;

  SetLastError (0);
  hMapping = CreateFileMappingA(INVALID_HANDLE_VALUE,
				NULL, flProtect, 0, 0, lpName);
  if (hMapping != INVALID_HANDLE_VALUE)
    {
      /* Just in case Windows CE changes its behaviour, we check for
         the right error value here.  */
      if (GetLastError () != ERROR_ALREADY_EXISTS)
        {
          CloseHandle(hMapping);
          hMapping = INVALID_HANDLE_VALUE;
        }
    }
  return hMapping;
}


BOOL
MoveFileExA (LPCSTR lpExistingFileName, LPCSTR lpNewFileName, DWORD dwFlags)
{
  _dbus_assert (dwFlags == MOVEFILE_REPLACE_EXISTING);

  if (_dbus_file_exists (lpNewFileName))
    {
      BOOL result = DeleteFileA (lpNewFileName);
      if (result == 0)
	return FALSE;
    }
  return MoveFileA (lpExistingFileName, lpNewFileName);
}


BOOL
SetHandleInformation (HANDLE hObject, DWORD dwMask, DWORD dwFlags)
{
  _dbus_assert (dwMask == (HANDLE_FLAG_INHERIT | HANDLE_FLAG_PROTECT_FROM_CLOSE));
  _dbus_assert (dwFlags == 0);

  /* Not supported on Windows CE, and actually the default.  So just
     return overwhelming success.  */
  return 1;
}


DWORD
SearchPathA (LPCSTR lpPath, LPCSTR lpFileName, LPCSTR lpExtension,
             DWORD nBufferLength, LPSTR lpBuffer, LPSTR* lpFilePart)
{
  char *filename;
  char *filepart;
  int filename_len;
  
  _dbus_assert (lpPath == NULL);
  _dbus_assert (lpExtension == NULL);
  
  filename = find_program_in_inst_dir (lpFileName);
  if (!filename)
    {
      SetLastError (ERROR_FILE_NOT_FOUND);
      return 0;
    }

  filename_len = strlen (filename) + 1;
  if (filename_len > nBufferLength)
    {
      free (filename);
      return filename_len;
    }

  strcpy (lpBuffer, filename);
  free (filename);

  filepart = _mbsrchr (lpBuffer, '\\');
  if (!filepart)
    filepart = lpBuffer;
  *lpFilePart = filepart;

  return filename_len - 1;
}


/** Gets our SID
 * @param points to sid buffer, need to be freed with LocalFree()
 * @returns process sid
 */
dbus_bool_t
_dbus_getsid(char **sid)
{
  /* There is nothing like this on Windows CE, so we fake it.  */
  static const char asid[] = "S-1-5-21-515967899-920026266-1708537768-1000";
  char *buf = LocalAlloc (LMEM_FIXED, sizeof (asid));
  if (!buf)
    {
      _dbus_win_warn_win_error ("LocalAlloc failed", GetLastError ());
      return FALSE;
    }

  memcpy (buf, asid, sizeof (asid));
  *sid = buf;
  return TRUE;
}


BOOL
LookupAccountNameW (LPCWSTR lpSystemName, LPCWSTR lpAccountName, PSID Sid, PDWORD cbSid,
                    LPWSTR ReferencedDomainName, PDWORD cchReferencedDomainName, PSID_NAME_USE peUse)
{
  /* Currently not needed.  */
  return FALSE;
}


BOOL
IsValidSid (PSID psid)
{
  /* Currently not needed.  */
  return FALSE;
}


HANDLE
CreateFileA (LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwSharedMode,
	     LPSECURITY_ATTRIBUTES lpSecurityAttributes,
	     DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
	     HANDLE hTemplateFile)
{
  wchar_t *filename;
  HANDLE result;
  int err;

  filename = _dbus_win_utf8_to_utf16 (lpFileName, NULL);
  if (!filename)
    return INVALID_HANDLE_VALUE;

  result = CreateFileW (filename, dwDesiredAccess, dwSharedMode,
			lpSecurityAttributes, dwCreationDisposition,
			dwFlagsAndAttributes, hTemplateFile);

  err = GetLastError ();
  dbus_free (filename);
  SetLastError (err);
  return result;
}


BOOL
DeleteFileA (LPCSTR lpFileName)
{
  wchar_t *filename;
  BOOL result;
  int err;

  filename = _dbus_win_utf8_to_utf16 (lpFileName, NULL);
  if (!filename)
    return FALSE;

  result = DeleteFileW (filename);

  err = GetLastError ();
  dbus_free (filename);
  SetLastError (err);
  return result;
}


BOOL
MoveFileA (LPCSTR lpExistingFileName, LPCSTR lpNewFileName)
{
  wchar_t *existing_filename;
  wchar_t *new_filename;
  BOOL result;
  int err;

  existing_filename = _dbus_win_utf8_to_utf16 (lpExistingFileName, NULL);
  if (! existing_filename)
    return FALSE;

  new_filename = _dbus_win_utf8_to_utf16 (lpNewFileName, NULL);
  if (! new_filename)
    {
      dbus_free (existing_filename);
      return FALSE;
    }

  result = MoveFileW (existing_filename, new_filename);

  err = GetLastError ();
  dbus_free (existing_filename);
  dbus_free (new_filename);
  SetLastError (err);
  return result;
}


DWORD
GetFileAttributesA(LPCSTR lpFileName)
{
  wchar_t *filename;
  DWORD result;
  int err;

  filename = _dbus_win_utf8_to_utf16 (lpFileName, NULL);
  if (!filename)
    return INVALID_FILE_ATTRIBUTES;

  result = GetFileAttributesW (filename);

  err = GetLastError ();
  dbus_free (filename);
  SetLastError (err);
  return result;
}


BOOL
GetFileAttributesExA (LPCSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId,
                      PVOID lpFileInformation)
{
  wchar_t *filename;
  DWORD result;
  int err;

  filename = _dbus_win_utf8_to_utf16 (lpFileName, NULL);
  if (!filename)
    return INVALID_FILE_ATTRIBUTES;

  result = GetFileAttributesExW (filename, fInfoLevelId, lpFileInformation);

  err = GetLastError ();
  dbus_free (filename);
  SetLastError (err);
  return result;
}


HANDLE
CreateFileMappingA (HANDLE hFile, LPSECURITY_ATTRIBUTES lpAttributes,
		    DWORD flProtect, DWORD dwMaximumSizeHigh,
		    DWORD dwMaximumSizeLow, LPCSTR lpName)
{
  wchar_t *name;
  HANDLE result;
  int err;

  if (lpName)
    {
      name = _dbus_win_utf8_to_utf16 (lpName, NULL);
      if (!name)
	return INVALID_HANDLE_VALUE;
    }
  else
    name = NULL;

  result = CreateFileMappingW (hFile, lpAttributes, flProtect,
			       dwMaximumSizeHigh, dwMaximumSizeLow,
			       name);

  err = GetLastError ();
  dbus_free (name);
  SetLastError (err);
  return result;
}


BOOL
CreateDirectoryA (LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes)
{
  wchar_t *pathname;
  BOOL result;
  int err;

  pathname = _dbus_win_utf8_to_utf16 (lpPathName, NULL);
  if (!pathname)
    return FALSE;

  result = CreateDirectoryW (pathname, lpSecurityAttributes);

  err = GetLastError ();
  dbus_free (pathname);
  SetLastError (err);
  return result;
}


BOOL
RemoveDirectoryA (LPCSTR lpPathName)
{
  wchar_t *pathname;
  BOOL result;
  int err;

  pathname = _dbus_win_utf8_to_utf16 (lpPathName, NULL);
  if (!pathname)
    return FALSE;

  result = RemoveDirectoryW (pathname);

  err = GetLastError ();
  dbus_free (pathname);
  SetLastError (err);
  return result;
}


static BOOL
convert_find_data (LPWIN32_FIND_DATAW fdw, LPWIN32_FIND_DATAA fda)
{
  char *filename;
  int len;

  fda->dwFileAttributes = fdw->dwFileAttributes;
  fda->ftCreationTime = fdw->ftCreationTime;
  fda->ftLastAccessTime = fdw->ftLastAccessTime;
  fda->ftLastWriteTime = fdw->ftLastWriteTime;
  fda->nFileSizeHigh = fdw->nFileSizeHigh;
  fda->nFileSizeLow = fdw->nFileSizeLow;

  filename = _dbus_win_utf16_to_utf8 (fdw->cFileName, NULL);
  if (!filename)
    return FALSE;

  len = sizeof (fda->cFileName);
  strncpy (fda->cFileName, filename, len);
  fda->cFileName[len - 1] = '\0';
  
  return TRUE;
}


HANDLE
FindFirstFileA (LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData)
{
  wchar_t *pathname;
  WIN32_FIND_DATAW find_file_data;
  HANDLE result;
  int err;

  pathname = _dbus_win_utf8_to_utf16 (lpFileName, NULL);
  if (!pathname)
    return INVALID_HANDLE_VALUE;

  result = FindFirstFileW (pathname, &find_file_data);
  if (result != INVALID_HANDLE_VALUE)
    {
      BOOL res = convert_find_data (&find_file_data, lpFindFileData);
      if (! res)
        {
          err = GetLastError ();
          FindClose (result);
          SetLastError (err);
          result = INVALID_HANDLE_VALUE;
        }
    }

  err = GetLastError ();
  dbus_free (pathname);
  SetLastError (err);
  return result;
}


BOOL
FindNextFileA (HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData)
{
  WIN32_FIND_DATAW find_file_data;
  BOOL result;
  int err;

  result = FindNextFileW (hFindFile, &find_file_data);
  if (result)
    result = convert_find_data (&find_file_data, lpFindFileData);

  return result;  
}


HANDLE
CreateMutexA (LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner,
	      LPCSTR lpName)
{
  wchar_t *name;
  HANDLE result;
  int err;

  if (lpName)
    {
      name = _dbus_win_utf8_to_utf16 (lpName, NULL);
      if (!name)
	return INVALID_HANDLE_VALUE;
    }
  else
    name = NULL;

  result = CreateMutexW (lpMutexAttributes, bInitialOwner, name);

  err = GetLastError ();
  dbus_free (name);
  SetLastError (err);
  return result;
}


BOOL
CreateProcessA (LPCSTR pszImageName, LPSTR pszCmdLine,
                LPSECURITY_ATTRIBUTES psaProcess,
                LPSECURITY_ATTRIBUTES psaThread, BOOL fInheritHandles,
                DWORD fdwCreate, PVOID pvEnvironment, LPCSTR pszCurDir,
                LPSTARTUPINFOA psiStartInfo,
                LPPROCESS_INFORMATION pProcInfo)
{
  wchar_t *image_name = NULL;
  wchar_t *cmd_line = NULL;
  BOOL result;
  int err;

  _dbus_assert (psaProcess == NULL);
  _dbus_assert (psaThread == NULL);
  _dbus_assert (fInheritHandles == FALSE);
  _dbus_assert (pvEnvironment == NULL);
  _dbus_assert (pszCurDir == NULL);
  /* psiStartInfo is generally not NULL.  */

  if (pszImageName)
    {
      image_name = _dbus_win_utf8_to_utf16 (pszImageName, NULL);
      if (!image_name)
	return 0;
    }
  if (pszCmdLine)
    {
      cmd_line = _dbus_win_utf8_to_utf16 (pszCmdLine, NULL);
      if (!cmd_line)
        {
          if (image_name)
            dbus_free (image_name);
          return 0;
        }
    }

  result = CreateProcessW (image_name, cmd_line, NULL, NULL, FALSE,
                           fdwCreate, NULL, NULL, NULL, pProcInfo);

  err = GetLastError ();
  dbus_free (image_name);
  dbus_free (cmd_line);
  SetLastError (err);
  return result;
}


LONG
RegOpenKeyExA (HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions,
               REGSAM samDesired, PHKEY phkResult)
{
  wchar_t *subkey;
  LONG result;
  int err;

  if (lpSubKey)
    {
      subkey = _dbus_win_utf8_to_utf16 (lpSubKey, NULL);
      if (!subkey)
	return 0;
    }
  else
    subkey = NULL;

  result = RegOpenKeyEx (hKey, subkey, ulOptions, samDesired, phkResult);

  err = GetLastError ();
  dbus_free (subkey);
  SetLastError (err);
  return result;
}


LONG
RegQueryValueExA (HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved,
                  LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData)
{
  wchar_t *name;
  LONG err;
  BYTE *data;
  DWORD data_len;
  DWORD type;

  if (lpValueName)
    {
      name = _dbus_win_utf8_to_utf16 (lpValueName, NULL);
      if (!name)
	return GetLastError ();
    }
  else
    name = NULL;

  data_len = 0;
  err = RegQueryValueExW (hKey, name, lpReserved, lpType, NULL, &data_len);
  if (err || !lpcbData)
    {
      dbus_free (name);
      return err;
    }

  data = malloc (data_len + sizeof (wchar_t));
  if (!data)
    {
      dbus_free (name);
      return ERROR_NOT_ENOUGH_MEMORY;
    }
  
  err = RegQueryValueExW (hKey, name, lpReserved, &type, data, &data_len);
  if (lpType)
    *lpType = type;
  dbus_free (name);
  /* If err is ERROR_MORE_DATA, there probably was a race condition.
     We can punt this to the caller just as well.  */
  if (err)
    {
      free (data);
      return err;
    }

  /* NOTE: REG_MULTI_SZ and REG_EXPAND_SZ not supported, because they
     are not needed in this module.  */
  if (type == REG_SZ)
    {
      char *data_c;
      int data_c_len;

      /* This is valid since we allocated one more above.  */
      data[data_len] = '\0';
      data[data_len + 1] = '\0';

      /* The cast is valid because malloc guarantees alignment of
         basic types.  */
      data_c = _dbus_win_utf16_to_utf8 ((wchar_t*) data, NULL);
      if (!data_c)
        {
          free (data);
          return GetLastError();
        }

      data_c_len = strlen (data_c) + 1;
      _dbus_assert (data_c_len <= data_len + sizeof (wchar_t));
      memcpy (data, data_c, data_c_len);
      data_len = data_c_len;
      dbus_free (data_c);
    }

  /* DATA and DATA_LEN now contain the result.  */
  if (lpData)
    {
      if (data_len > *lpcbData)
        err = ERROR_MORE_DATA;
      else
        memcpy (lpData, data, data_len);
    }
  free (data);
  *lpcbData = data_len;
  return err;
}


DWORD
FormatMessageA (DWORD dwFlags, PCVOID lpSource, DWORD dwMessageId,
		DWORD dwLanguageId, LPSTR lpBuffer, DWORD nSize,
		va_list* Arguments)
{
  LPWSTR buffer_w = NULL;
  LPSTR buffer_c;
  DWORD len;
  char *buffer_new;
  DWORD buffer_new_len;
  BOOL buffer_w_free;

  len = FormatMessageW (dwFlags | FORMAT_MESSAGE_ALLOCATE_BUFFER,
			lpSource, dwMessageId, dwLanguageId,
			(LPWSTR) &buffer_w, 0, Arguments);
  if (len == 0)
    return 0;

  buffer_c = _dbus_win_utf16_to_utf8 (buffer_w, NULL);
  if (! buffer_c)
    {
      LocalFree (buffer_w);
      return 0;
    }

  if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
    {
      /* We need to return a buffer that's freeable with LocalFree.  */
      buffer_new = (char *) buffer_w;
      buffer_new_len = sizeof (wchar_t) * (len + 1);
      buffer_w_free = FALSE;
      /* Avoid alignment issue by using memcpy.  */
      memcpy (lpBuffer, &buffer_new, sizeof (buffer_new));
    }
  else
    {
      buffer_new = lpBuffer;
      buffer_new_len = nSize;
      buffer_w_free = TRUE;
    }

  strncpy (buffer_new, buffer_c, buffer_new_len);
  dbus_free (buffer_c);
  buffer_new[buffer_new_len - 1] = '\0';
  if (buffer_w_free)
    LocalFree (buffer_w);

  /* strlen is correct (not _mbstrlen), because we want storage and
     not string length.  */
  return strlen (buffer_new);
}


DWORD
GetModuleFileNameA (HINSTANCE hModule, LPSTR lpFilename, DWORD nSize)
{
  wchar_t *filename_w;
  char *filename_c;
  DWORD len;

  if (nSize == 0)
    {
      /* Windows XP/2000.  */
      SetLastError (0);
      return 0;
    }

  filename_w = malloc (sizeof (wchar_t) * nSize);
  if (! filename_w)
    return 0;

  len = GetModuleFileNameW (hModule, filename_w, nSize);
  if (len == 0)
    {
      /* Note: If we fail with ERROR_INSUFFICIENT_BUFFER, this is still
       (approximately) correct.  */
      free (filename_w);
      return 0;
    }

  filename_w[nSize - 1] = '\0';
  filename_c = _dbus_win_utf16_to_utf8 (filename_w, NULL);
  free (filename_w);
  if (! filename_c)
    return 0;

  strncpy (lpFilename, filename_c, nSize);
  dbus_free (filename_c);
  lpFilename[nSize - 1] = '\0';
  /* strlen is correct (not _mbstrlen), because we want storage and
     not string length.  */
  return strlen (lpFilename);
}


DWORD
GetTempPathA (DWORD nBufferLength, LPSTR lpBuffer)
{
  wchar_t dummy[1];
  DWORD len;

  len = GetTempPathW (0, dummy);
  if (len == 0)
    return 0;

  _dbus_assert (len <= MAX_PATH);

  /* Better be safe than sorry.  MSDN doesn't say if len is with or
     without terminating 0.  */
  len++;

  {
    wchar_t *buffer_w;
    DWORD len_w;
    char *buffer_c;
    DWORD len_c;

    buffer_w = malloc (sizeof (wchar_t) * len);
    if (! buffer_w)
      return 0;

    len_w = GetTempPathW (len, buffer_w);
    /* Give up if we still can't get at it.  */
    if (len_w == 0 || len_w >= len)
      {
        free (buffer_w);
        return 0;
      }

    /* Better be really safe.  */
    buffer_w[len_w] = '\0';

    buffer_c = _dbus_win_utf16_to_utf8 (buffer_w, NULL);
    free (buffer_w);
    if (! buffer_c)
      return 0;

    /* strlen is correct (not _mbstrlen), because we want storage and
       not string length.  */
    len_c = strlen (buffer_c) + 1;
    if (len_c > nBufferLength)
      return len_c;

    strcpy (lpBuffer, buffer_c);
    dbus_free (buffer_c);
    return len_c - 1;
  }
}


BOOL
SHGetSpecialFolderPathA (HWND hwndOwner, LPSTR lpszPath, int nFolder,
                         BOOL fCreate)
{
  wchar_t path[MAX_PATH];
  char *path_c;
  BOOL result;

  path[0] = (wchar_t) 0;
  result = SHGetSpecialFolderPathW (hwndOwner, path, nFolder, fCreate);
  /* Note: May return false even if succeeds.  */

  path[MAX_PATH - 1] = (wchar_t) 0;
  path_c = _dbus_win_utf16_to_utf8 (path, NULL);
  if (! path_c)
    return 0;
  
  strncpy (lpszPath, path_c, MAX_PATH);
  dbus_free (path_c);
  lpszPath[MAX_PATH - 1] = '\0';
  return result;
}


void
OutputDebugStringA (LPCSTR lpOutputString)
{
  wchar_t *str;
  HANDLE result;
  int err;

  str = _dbus_win_utf8_to_utf16 (lpOutputString, NULL);
  if (!str)
    return;

  OutputDebugStringW (str);

  err = GetLastError ();
  dbus_free (str);
  SetLastError (err);
}
