|  | /* | 
|  | * Implementation of the ODBC driver installer | 
|  | * | 
|  | * Copyright 2005 Mike McCormack for CodeWeavers | 
|  | * Copyright 2005 Hans Leidekker | 
|  | * Copyright 2007 Bill Medland | 
|  | * | 
|  | * 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 <assert.h> | 
|  | #include <stdarg.h> | 
|  |  | 
|  | #define COBJMACROS | 
|  | #define NONAMELESSUNION | 
|  |  | 
|  | #include "windef.h" | 
|  | #include "winbase.h" | 
|  | #include "winreg.h" | 
|  | #include "winnls.h" | 
|  | #include "wine/debug.h" | 
|  |  | 
|  | #include "odbcinst.h" | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(odbc); | 
|  |  | 
|  | /* Registry key names */ | 
|  | static const WCHAR drivers_key[] = {'S','o','f','t','w','a','r','e','\\','O','D','B','C','\\','O','D','B','C','I','N','S','T','.','I','N','I','\\','O','D','B','C',' ','D','r','i','v','e','r','s',0}; | 
|  |  | 
|  | /* This config mode is known to be process-wide. | 
|  | * MSDN documentation suggests that the value is hidden somewhere in the registry but I haven't found it yet. | 
|  | * Although both the registry and the ODBC.ini files appear to be maintained together they are not maintained automatically through the registry's IniFileMapping. | 
|  | */ | 
|  | static UWORD config_mode = ODBC_BOTH_DSN; | 
|  |  | 
|  | /* MSDN documentation suggests that the error subsystem handles errors 1 to 8 | 
|  | * only and experimentation (Windows 2000) shows that the errors are process- | 
|  | * wide so go for the simple solution; static arrays. | 
|  | */ | 
|  | static int num_errors; | 
|  | static int error_code[8]; | 
|  | static const WCHAR *error_msg[8]; | 
|  | static const WCHAR odbc_error_general_err[] = {'G','e','n','e','r','a','l',' ','e','r','r','o','r',0}; | 
|  | static const WCHAR odbc_error_invalid_buff_len[] = {'I','n','v','a','l','i','d',' ','b','u','f','f','e','r',' ','l','e','n','g','t','h',0}; | 
|  | static const WCHAR odbc_error_component_not_found[] = {'C','o','m','p','o','n','e','n','t',' ','n','o','t',' ','f','o','u','n','d',0}; | 
|  | static const WCHAR odbc_error_out_of_mem[] = {'O','u','t',' ','o','f',' ','m','e','m','o','r','y',0}; | 
|  | static const WCHAR odbc_error_invalid_param_sequence[] = {'I','n','v','a','l','i','d',' ','p','a','r','a','m','e','t','e','r',' ','s','e','q','u','e','n','c','e',0}; | 
|  |  | 
|  | /* Push an error onto the error stack, taking care of ranges etc. */ | 
|  | static void push_error(int code, LPCWSTR msg) | 
|  | { | 
|  | if (num_errors < sizeof error_code/sizeof error_code[0]) | 
|  | { | 
|  | error_code[num_errors] = code; | 
|  | error_msg[num_errors] = msg; | 
|  | num_errors++; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Clear the error stack */ | 
|  | static void clear_errors(void) | 
|  | { | 
|  | num_errors = 0; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI ODBCCPlApplet( LONG i, LONG j, LONG * p1, LONG * p2) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME( "( %d %d %p %p) : stub!\n", i, j, p1, p2); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | static LPWSTR SQLInstall_strdup_multi(LPCSTR str) | 
|  | { | 
|  | LPCSTR p; | 
|  | LPWSTR ret = NULL; | 
|  | DWORD len; | 
|  |  | 
|  | if (!str) | 
|  | return ret; | 
|  |  | 
|  | for (p = str; *p; p += lstrlenA(p) + 1) | 
|  | ; | 
|  |  | 
|  | len = MultiByteToWideChar(CP_ACP, 0, str, p - str, NULL, 0 ); | 
|  | ret = HeapAlloc(GetProcessHeap(), 0, (len+1)*sizeof(WCHAR)); | 
|  | MultiByteToWideChar(CP_ACP, 0, str, p - str, ret, len ); | 
|  | ret[len] = 0; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static LPWSTR SQLInstall_strdup(LPCSTR str) | 
|  | { | 
|  | DWORD len; | 
|  | LPWSTR ret = NULL; | 
|  |  | 
|  | if (!str) | 
|  | return ret; | 
|  |  | 
|  | len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0 ); | 
|  | ret = HeapAlloc(GetProcessHeap(), 0, len*sizeof(WCHAR)); | 
|  | MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len ); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Convert the wide string or zero-length-terminated list of wide strings to a | 
|  | * narrow string or zero-length-terminated list of narrow strings. | 
|  | * Do not try to emulate windows undocumented excesses (e.g. adding a third \0 | 
|  | * to a list) | 
|  | * Arguments | 
|  | *   mode Indicates the sort of string. | 
|  | *     1 denotes that the buffers contain strings terminated by a single nul | 
|  | *       character | 
|  | *     2 denotes that the buffers contain zero-length-terminated lists | 
|  | *       (frequently erroneously referred to as double-null-terminated) | 
|  | *   buffer The narrow-character buffer into which to place the result.  This | 
|  | *          must be a non-null pointer to the first element of a buffer whose | 
|  | *          length is passed in buffer_length. | 
|  | *   str The wide-character buffer containing the string or list of strings to | 
|  | *       be converted.  str_length defines how many wide characters in the | 
|  | *       buffer are to be converted, including all desired terminating nul | 
|  | *       characters. | 
|  | *   str_length Effective length of str | 
|  | *   buffer_length Length of buffer | 
|  | *   returned_length A pointer to a variable that will receive the number of | 
|  | *                   narrow characters placed into the buffer.  This pointer | 
|  | *                   may be NULL. | 
|  | */ | 
|  | static BOOL SQLInstall_narrow(int mode, LPSTR buffer, LPCWSTR str, WORD str_length, WORD buffer_length, WORD *returned_length) | 
|  | { | 
|  | LPSTR pbuf; /* allows us to allocate a temporary buffer only if needed */ | 
|  | int len; /* Length of the converted list */ | 
|  | BOOL success = FALSE; | 
|  | assert(mode == 1 || mode == 2); | 
|  | assert(buffer_length); | 
|  | len = WideCharToMultiByte(CP_ACP, 0, str, str_length, 0, 0, NULL, NULL); | 
|  | if (len > 0) | 
|  | { | 
|  | if (len > buffer_length) | 
|  | { | 
|  | pbuf = HeapAlloc(GetProcessHeap(), 0, len); | 
|  | } | 
|  | else | 
|  | { | 
|  | pbuf = buffer; | 
|  | } | 
|  | len = WideCharToMultiByte(CP_ACP, 0, str, str_length, pbuf, len, NULL, NULL); | 
|  | if (len > 0) | 
|  | { | 
|  | if (pbuf != buffer) | 
|  | { | 
|  | if (buffer_length > (mode - 1)) | 
|  | { | 
|  | memcpy (buffer, pbuf, buffer_length-mode); | 
|  | *(buffer+buffer_length-mode) = '\0'; | 
|  | } | 
|  | *(buffer+buffer_length-1) = '\0'; | 
|  | } | 
|  | if (returned_length) | 
|  | { | 
|  | *returned_length = pbuf == buffer ? len : buffer_length; | 
|  | } | 
|  | success = TRUE; | 
|  | } | 
|  | else | 
|  | { | 
|  | ERR("transferring wide to narrow\n"); | 
|  | } | 
|  | if (pbuf != buffer) | 
|  | { | 
|  | HeapFree(GetProcessHeap(), 0, pbuf); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | ERR("measuring wide to narrow\n"); | 
|  | } | 
|  | return success; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLConfigDataSourceW(HWND hwndParent, WORD fRequest, | 
|  | LPCWSTR lpszDriver, LPCWSTR lpszAttributes) | 
|  | { | 
|  | LPCWSTR p; | 
|  |  | 
|  | clear_errors(); | 
|  | FIXME("%p %d %s %s\n", hwndParent, fRequest, debugstr_w(lpszDriver), | 
|  | debugstr_w(lpszAttributes)); | 
|  |  | 
|  | for (p = lpszAttributes; *p; p += lstrlenW(p) + 1) | 
|  | FIXME("%s\n", debugstr_w(p)); | 
|  |  | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLConfigDataSource(HWND hwndParent, WORD fRequest, | 
|  | LPCSTR lpszDriver, LPCSTR lpszAttributes) | 
|  | { | 
|  | FIXME("%p %d %s %s\n", hwndParent, fRequest, debugstr_a(lpszDriver), | 
|  | debugstr_a(lpszAttributes)); | 
|  | clear_errors(); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLConfigDriverW(HWND hwndParent, WORD fRequest, LPCWSTR lpszDriver, | 
|  | LPCWSTR lpszArgs, LPWSTR lpszMsg, WORD cbMsgMax, WORD *pcbMsgOut) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("(%p %d %s %s %p %d %p)\n", hwndParent, fRequest, debugstr_w(lpszDriver), | 
|  | debugstr_w(lpszArgs), lpszMsg, cbMsgMax, pcbMsgOut); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLConfigDriver(HWND hwndParent, WORD fRequest, LPCSTR lpszDriver, | 
|  | LPCSTR lpszArgs, LPSTR lpszMsg, WORD cbMsgMax, WORD *pcbMsgOut) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("(%p %d %s %s %p %d %p)\n", hwndParent, fRequest, debugstr_a(lpszDriver), | 
|  | debugstr_a(lpszArgs), lpszMsg, cbMsgMax, pcbMsgOut); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLCreateDataSourceW(HWND hwnd, LPCWSTR lpszDS) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLCreateDataSource(HWND hwnd, LPCSTR lpszDS) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLGetAvailableDriversW(LPCWSTR lpszInfFile, LPWSTR lpszBuf, | 
|  | WORD cbBufMax, WORD *pcbBufOut) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLGetAvailableDrivers(LPCSTR lpszInfFile, LPSTR lpszBuf, | 
|  | WORD cbBufMax, WORD *pcbBufOut) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLGetConfigMode(UWORD *pwConfigMode) | 
|  | { | 
|  | clear_errors(); | 
|  | if (pwConfigMode) | 
|  | *pwConfigMode = config_mode; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* This is implemented sensibly rather than according to exact conformance to Microsoft's buggy implementations | 
|  | * e.g. The Microsoft one occasionally actually adds a third nul character (possibly beyond the buffer). | 
|  | * e.g. If the key has no drivers then version 3.525.1117.0 does not modify the buffer at all, not even a nul character. | 
|  | */ | 
|  | BOOL WINAPI SQLGetInstalledDriversW(LPWSTR lpszBuf, WORD cbBufMax, | 
|  | WORD *pcbBufOut) | 
|  | { | 
|  | HKEY hDrivers; /* Registry handle to the Drivers key */ | 
|  | LONG reg_ret; /* Return code from registry functions */ | 
|  | BOOL success = FALSE; /* The value we will return */ | 
|  |  | 
|  | clear_errors(); | 
|  | if (!lpszBuf || cbBufMax == 0) | 
|  | { | 
|  | push_error(ODBC_ERROR_INVALID_BUFF_LEN, odbc_error_invalid_buff_len); | 
|  | } | 
|  | else if ((reg_ret = RegOpenKeyExW (HKEY_LOCAL_MACHINE /* The drivers does not depend on the config mode */, | 
|  | drivers_key, 0, KEY_READ /* Maybe overkill */, | 
|  | &hDrivers)) == ERROR_SUCCESS) | 
|  | { | 
|  | DWORD index = 0; | 
|  | cbBufMax--; | 
|  | success = TRUE; | 
|  | while (cbBufMax > 0) | 
|  | { | 
|  | DWORD size_name; | 
|  | size_name = cbBufMax; | 
|  | if ((reg_ret = RegEnumValueW(hDrivers, index, lpszBuf, &size_name, NULL, NULL, NULL, NULL)) == ERROR_SUCCESS) | 
|  | { | 
|  | index++; | 
|  | assert (size_name < cbBufMax && *(lpszBuf + size_name) == 0); | 
|  | size_name++; | 
|  | cbBufMax-= size_name; | 
|  | lpszBuf+=size_name; | 
|  | } | 
|  | else | 
|  | { | 
|  | if (reg_ret != ERROR_NO_MORE_ITEMS) | 
|  | { | 
|  | success = FALSE; | 
|  | push_error(ODBC_ERROR_GENERAL_ERR, odbc_error_general_err); | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  | *lpszBuf = 0; | 
|  | if ((reg_ret = RegCloseKey (hDrivers)) != ERROR_SUCCESS) | 
|  | TRACE ("Error %d closing ODBC Drivers key\n", reg_ret); | 
|  | } | 
|  | else | 
|  | { | 
|  | /* MSDN states that it returns failure with COMPONENT_NOT_FOUND in this case. | 
|  | * Version 3.525.1117.0 (Windows 2000) does not; it actually returns success. | 
|  | * I doubt if it will actually be an issue. | 
|  | */ | 
|  | push_error(ODBC_ERROR_COMPONENT_NOT_FOUND, odbc_error_component_not_found); | 
|  | } | 
|  | return success; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLGetInstalledDrivers(LPSTR lpszBuf, WORD cbBufMax, | 
|  | WORD *pcbBufOut) | 
|  | { | 
|  | BOOL ret; | 
|  | int size_wbuf = cbBufMax; | 
|  | LPWSTR wbuf; | 
|  | WORD size_used; | 
|  | wbuf = HeapAlloc(GetProcessHeap(), 0, size_wbuf*sizeof(WCHAR)); | 
|  | if (wbuf) | 
|  | { | 
|  | ret = SQLGetInstalledDriversW(wbuf, size_wbuf, &size_used); | 
|  | if (ret) | 
|  | { | 
|  | if (!(ret = SQLInstall_narrow(2, lpszBuf, wbuf, size_used, cbBufMax, pcbBufOut))) | 
|  | { | 
|  | push_error(ODBC_ERROR_GENERAL_ERR, odbc_error_general_err); | 
|  | } | 
|  | } | 
|  | HeapFree(GetProcessHeap(), 0, wbuf); | 
|  | /* ignore failure; we have achieved the aim */ | 
|  | } | 
|  | else | 
|  | { | 
|  | push_error(ODBC_ERROR_OUT_OF_MEM, odbc_error_out_of_mem); | 
|  | ret = FALSE; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int WINAPI SQLGetPrivateProfileStringW(LPCWSTR lpszSection, LPCWSTR lpszEntry, | 
|  | LPCWSTR lpszDefault, LPCWSTR RetBuffer, int cbRetBuffer, | 
|  | LPCWSTR lpszFilename) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | int WINAPI SQLGetPrivateProfileString(LPCSTR lpszSection, LPCSTR lpszEntry, | 
|  | LPCSTR lpszDefault, LPCSTR RetBuffer, int cbRetBuffer, | 
|  | LPCSTR lpszFilename) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLGetTranslatorW(HWND hwndParent, LPWSTR lpszName, WORD cbNameMax, | 
|  | WORD *pcbNameOut, LPWSTR lpszPath, WORD cbPathMax, | 
|  | WORD *pcbPathOut, DWORD *pvOption) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLGetTranslator(HWND hwndParent, LPSTR lpszName, WORD cbNameMax, | 
|  | WORD *pcbNameOut, LPSTR lpszPath, WORD cbPathMax, | 
|  | WORD *pcbPathOut, DWORD *pvOption) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallDriverW(LPCWSTR lpszInfFile, LPCWSTR lpszDriver, | 
|  | LPWSTR lpszPath, WORD cbPathMax, WORD * pcbPathOut) | 
|  | { | 
|  | DWORD usage; | 
|  |  | 
|  | clear_errors(); | 
|  | TRACE("%s %s %p %d %p\n", debugstr_w(lpszInfFile), | 
|  | debugstr_w(lpszDriver), lpszPath, cbPathMax, pcbPathOut); | 
|  |  | 
|  | if (lpszInfFile) | 
|  | return FALSE; | 
|  |  | 
|  | return SQLInstallDriverExW(lpszDriver, NULL, lpszPath, cbPathMax, | 
|  | pcbPathOut, ODBC_INSTALL_COMPLETE, &usage); | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallDriver(LPCSTR lpszInfFile, LPCSTR lpszDriver, | 
|  | LPSTR lpszPath, WORD cbPathMax, WORD * pcbPathOut) | 
|  | { | 
|  | DWORD usage; | 
|  |  | 
|  | clear_errors(); | 
|  | TRACE("%s %s %p %d %p\n", debugstr_a(lpszInfFile), | 
|  | debugstr_a(lpszDriver), lpszPath, cbPathMax, pcbPathOut); | 
|  |  | 
|  | if (lpszInfFile) | 
|  | return FALSE; | 
|  |  | 
|  | return SQLInstallDriverEx(lpszDriver, NULL, lpszPath, cbPathMax, | 
|  | pcbPathOut, ODBC_INSTALL_COMPLETE, &usage); | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallDriverExW(LPCWSTR lpszDriver, LPCWSTR lpszPathIn, | 
|  | LPWSTR lpszPathOut, WORD cbPathOutMax, WORD *pcbPathOut, | 
|  | WORD fRequest, LPDWORD lpdwUsageCount) | 
|  | { | 
|  | UINT len; | 
|  | LPCWSTR p; | 
|  | WCHAR path[MAX_PATH]; | 
|  |  | 
|  | clear_errors(); | 
|  | TRACE("%s %s %p %d %p %d %p\n", debugstr_w(lpszDriver), | 
|  | debugstr_w(lpszPathIn), lpszPathOut, cbPathOutMax, pcbPathOut, | 
|  | fRequest, lpdwUsageCount); | 
|  |  | 
|  | for (p = lpszDriver; *p; p += lstrlenW(p) + 1) | 
|  | TRACE("%s\n", debugstr_w(p)); | 
|  |  | 
|  | len = GetSystemDirectoryW(path, MAX_PATH); | 
|  |  | 
|  | if (pcbPathOut) | 
|  | *pcbPathOut = len; | 
|  |  | 
|  | len = GetSystemDirectoryW(path, MAX_PATH); | 
|  |  | 
|  | if (lpszPathOut && cbPathOutMax > len) | 
|  | { | 
|  | lstrcpyW(lpszPathOut, path); | 
|  | return TRUE; | 
|  | } | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallDriverEx(LPCSTR lpszDriver, LPCSTR lpszPathIn, | 
|  | LPSTR lpszPathOut, WORD cbPathOutMax, WORD *pcbPathOut, | 
|  | WORD fRequest, LPDWORD lpdwUsageCount) | 
|  | { | 
|  | LPCSTR p; | 
|  | LPWSTR driver, pathin; | 
|  | WCHAR pathout[MAX_PATH]; | 
|  | BOOL ret; | 
|  | WORD cbOut = 0; | 
|  |  | 
|  | clear_errors(); | 
|  | TRACE("%s %s %p %d %p %d %p\n", debugstr_a(lpszDriver), | 
|  | debugstr_a(lpszPathIn), lpszPathOut, cbPathOutMax, pcbPathOut, | 
|  | fRequest, lpdwUsageCount); | 
|  |  | 
|  | for (p = lpszDriver; *p; p += lstrlenA(p) + 1) | 
|  | TRACE("%s\n", debugstr_a(p)); | 
|  |  | 
|  | driver = SQLInstall_strdup_multi(lpszDriver); | 
|  | pathin = SQLInstall_strdup(lpszPathIn); | 
|  |  | 
|  | ret = SQLInstallDriverExW(driver, pathin, pathout, MAX_PATH, &cbOut, | 
|  | fRequest, lpdwUsageCount); | 
|  | if (ret) | 
|  | { | 
|  | int len =  WideCharToMultiByte(CP_ACP, 0, pathout, -1, lpszPathOut, | 
|  | 0, NULL, NULL); | 
|  | if (len) | 
|  | { | 
|  | if (pcbPathOut) | 
|  | *pcbPathOut = len - 1; | 
|  |  | 
|  | if (!lpszPathOut || cbPathOutMax < len) | 
|  | { | 
|  | ret = FALSE; | 
|  | goto out; | 
|  | } | 
|  | len =  WideCharToMultiByte(CP_ACP, 0, pathout, -1, lpszPathOut, | 
|  | cbPathOutMax, NULL, NULL); | 
|  | } | 
|  | } | 
|  |  | 
|  | out: | 
|  | HeapFree(GetProcessHeap(), 0, driver); | 
|  | HeapFree(GetProcessHeap(), 0, pathin); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallDriverManagerW(LPWSTR lpszPath, WORD cbPathMax, | 
|  | WORD *pcbPathOut) | 
|  | { | 
|  | UINT len; | 
|  | WCHAR path[MAX_PATH]; | 
|  |  | 
|  | clear_errors(); | 
|  | TRACE("(%p %d %d)\n", lpszPath, cbPathMax, *pcbPathOut); | 
|  |  | 
|  | len = GetSystemDirectoryW(path, MAX_PATH); | 
|  |  | 
|  | if (pcbPathOut) | 
|  | *pcbPathOut = len; | 
|  |  | 
|  | if (lpszPath && cbPathMax > len) | 
|  | { | 
|  | lstrcpyW(lpszPath, path); | 
|  | return TRUE; | 
|  | } | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallDriverManager(LPSTR lpszPath, WORD cbPathMax, | 
|  | WORD *pcbPathOut) | 
|  | { | 
|  | BOOL ret; | 
|  | WORD len, cbOut = 0; | 
|  | WCHAR path[MAX_PATH]; | 
|  |  | 
|  | clear_errors(); | 
|  | TRACE("(%p %d %d)\n", lpszPath, cbPathMax, *pcbPathOut); | 
|  |  | 
|  | ret = SQLInstallDriverManagerW(path, MAX_PATH, &cbOut); | 
|  | if (ret) | 
|  | { | 
|  | len =  WideCharToMultiByte(CP_ACP, 0, path, -1, lpszPath, 0, | 
|  | NULL, NULL); | 
|  | if (len) | 
|  | { | 
|  | if (pcbPathOut) | 
|  | *pcbPathOut = len - 1; | 
|  |  | 
|  | if (!lpszPath || cbPathMax < len) | 
|  | return FALSE; | 
|  |  | 
|  | len =  WideCharToMultiByte(CP_ACP, 0, path, -1, lpszPath, | 
|  | cbPathMax, NULL, NULL); | 
|  | } | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallODBCW(HWND hwndParent, LPCWSTR lpszInfFile, | 
|  | LPCWSTR lpszSrcPath, LPCWSTR lpszDrivers) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallODBC(HWND hwndParent, LPCSTR lpszInfFile, | 
|  | LPCSTR lpszSrcPath, LPCSTR lpszDrivers) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | SQLRETURN WINAPI SQLInstallerErrorW(WORD iError, DWORD *pfErrorCode, | 
|  | LPWSTR lpszErrorMsg, WORD cbErrorMsgMax, WORD *pcbErrorMsg) | 
|  | { | 
|  | TRACE("%d %p %p %d %p\n", iError, pfErrorCode, lpszErrorMsg, | 
|  | cbErrorMsgMax, pcbErrorMsg); | 
|  |  | 
|  | if (iError == 0) | 
|  | { | 
|  | return SQL_ERROR; | 
|  | } | 
|  | else if (iError <= num_errors) | 
|  | { | 
|  | BOOL truncated = FALSE; | 
|  | WORD len; | 
|  | LPCWSTR msg; | 
|  | iError--; | 
|  | if (pfErrorCode) | 
|  | *pfErrorCode = error_code[iError]; | 
|  | msg = error_msg[iError]; | 
|  | len = msg ? lstrlenW(msg) : 0; | 
|  | if (pcbErrorMsg) | 
|  | *pcbErrorMsg = len; | 
|  | len++; | 
|  | if (cbErrorMsgMax < len) | 
|  | { | 
|  | len = cbErrorMsgMax; | 
|  | truncated = TRUE; | 
|  | } | 
|  | if (lpszErrorMsg && len) | 
|  | { | 
|  | if (msg) | 
|  | { | 
|  | memcpy (lpszErrorMsg, msg, len * sizeof(WCHAR)); | 
|  | } | 
|  | else | 
|  | { | 
|  | assert(len==1); | 
|  | *lpszErrorMsg = 0; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Yes.  If you pass a null pointer and a large length it is not an error! */ | 
|  | truncated = TRUE; | 
|  | } | 
|  |  | 
|  | return truncated ? SQL_SUCCESS_WITH_INFO : SQL_SUCCESS; | 
|  | } | 
|  |  | 
|  | /* At least on Windows 2000 , the buffers are not altered in this case.  However that is a little too dangerous a test for just now */ | 
|  | if (pcbErrorMsg) | 
|  | *pcbErrorMsg = 0; | 
|  |  | 
|  | if (lpszErrorMsg && cbErrorMsgMax > 0) | 
|  | *lpszErrorMsg = '\0'; | 
|  |  | 
|  | return SQL_NO_DATA; | 
|  | } | 
|  |  | 
|  | SQLRETURN WINAPI SQLInstallerError(WORD iError, DWORD *pfErrorCode, | 
|  | LPSTR lpszErrorMsg, WORD cbErrorMsgMax, WORD *pcbErrorMsg) | 
|  | { | 
|  | SQLRETURN ret; | 
|  | LPWSTR wbuf; | 
|  | WORD cbwbuf; | 
|  | TRACE("%d %p %p %d %p\n", iError, pfErrorCode, lpszErrorMsg, | 
|  | cbErrorMsgMax, pcbErrorMsg); | 
|  |  | 
|  | wbuf = 0; | 
|  | if (lpszErrorMsg && cbErrorMsgMax) | 
|  | { | 
|  | wbuf = HeapAlloc(GetProcessHeap(), 0, cbErrorMsgMax*sizeof(WCHAR)); | 
|  | if (!wbuf) | 
|  | return SQL_ERROR; | 
|  | } | 
|  | ret = SQLInstallerErrorW(iError, pfErrorCode, wbuf, cbErrorMsgMax, &cbwbuf); | 
|  | if (wbuf) | 
|  | { | 
|  | WORD cbBuf = 0; | 
|  | SQLInstall_narrow(1, lpszErrorMsg, wbuf, cbwbuf+1, cbErrorMsgMax, &cbBuf); | 
|  | HeapFree(GetProcessHeap(), 0, wbuf); | 
|  | if (pcbErrorMsg) | 
|  | *pcbErrorMsg = cbBuf-1; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallTranslatorExW(LPCWSTR lpszTranslator, LPCWSTR lpszPathIn, | 
|  | LPWSTR lpszPathOut, WORD cbPathOutMax, WORD *pcbPathOut, | 
|  | WORD fRequest, LPDWORD lpdwUsageCount) | 
|  | { | 
|  | UINT len; | 
|  | LPCWSTR p; | 
|  | WCHAR path[MAX_PATH]; | 
|  |  | 
|  | clear_errors(); | 
|  | TRACE("%s %s %p %d %p %d %p\n", debugstr_w(lpszTranslator), | 
|  | debugstr_w(lpszPathIn), lpszPathOut, cbPathOutMax, pcbPathOut, | 
|  | fRequest, lpdwUsageCount); | 
|  |  | 
|  | for (p = lpszTranslator; *p; p += lstrlenW(p) + 1) | 
|  | TRACE("%s\n", debugstr_w(p)); | 
|  |  | 
|  | len = GetSystemDirectoryW(path, MAX_PATH); | 
|  |  | 
|  | if (pcbPathOut) | 
|  | *pcbPathOut = len; | 
|  |  | 
|  | if (lpszPathOut && cbPathOutMax > len) | 
|  | { | 
|  | lstrcpyW(lpszPathOut, path); | 
|  | return TRUE; | 
|  | } | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallTranslatorEx(LPCSTR lpszTranslator, LPCSTR lpszPathIn, | 
|  | LPSTR lpszPathOut, WORD cbPathOutMax, WORD *pcbPathOut, | 
|  | WORD fRequest, LPDWORD lpdwUsageCount) | 
|  | { | 
|  | LPCSTR p; | 
|  | LPWSTR translator, pathin; | 
|  | WCHAR pathout[MAX_PATH]; | 
|  | BOOL ret; | 
|  | WORD cbOut = 0; | 
|  |  | 
|  | clear_errors(); | 
|  | TRACE("%s %s %p %d %p %d %p\n", debugstr_a(lpszTranslator), | 
|  | debugstr_a(lpszPathIn), lpszPathOut, cbPathOutMax, pcbPathOut, | 
|  | fRequest, lpdwUsageCount); | 
|  |  | 
|  | for (p = lpszTranslator; *p; p += lstrlenA(p) + 1) | 
|  | TRACE("%s\n", debugstr_a(p)); | 
|  |  | 
|  | translator = SQLInstall_strdup_multi(lpszTranslator); | 
|  | pathin = SQLInstall_strdup(lpszPathIn); | 
|  |  | 
|  | ret = SQLInstallTranslatorExW(translator, pathin, pathout, MAX_PATH, | 
|  | &cbOut, fRequest, lpdwUsageCount); | 
|  | if (ret) | 
|  | { | 
|  | int len =  WideCharToMultiByte(CP_ACP, 0, pathout, -1, lpszPathOut, | 
|  | 0, NULL, NULL); | 
|  | if (len) | 
|  | { | 
|  | if (pcbPathOut) | 
|  | *pcbPathOut = len - 1; | 
|  |  | 
|  | if (!lpszPathOut || cbPathOutMax < len) | 
|  | { | 
|  | ret = FALSE; | 
|  | goto out; | 
|  | } | 
|  | len =  WideCharToMultiByte(CP_ACP, 0, pathout, -1, lpszPathOut, | 
|  | cbPathOutMax, NULL, NULL); | 
|  | } | 
|  | } | 
|  |  | 
|  | out: | 
|  | HeapFree(GetProcessHeap(), 0, translator); | 
|  | HeapFree(GetProcessHeap(), 0, pathin); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallTranslator(LPCSTR lpszInfFile, LPCSTR lpszTranslator, | 
|  | LPCSTR lpszPathIn, LPSTR lpszPathOut, WORD cbPathOutMax, | 
|  | WORD *pcbPathOut, WORD fRequest, LPDWORD lpdwUsageCount) | 
|  | { | 
|  | clear_errors(); | 
|  | TRACE("%s %s %s %p %d %p %d %p\n", debugstr_a(lpszInfFile), | 
|  | debugstr_a(lpszTranslator), debugstr_a(lpszPathIn), lpszPathOut, | 
|  | cbPathOutMax, pcbPathOut, fRequest, lpdwUsageCount); | 
|  |  | 
|  | if (lpszInfFile) | 
|  | return FALSE; | 
|  |  | 
|  | return SQLInstallTranslatorEx(lpszTranslator, lpszPathIn, lpszPathOut, | 
|  | cbPathOutMax, pcbPathOut, fRequest, lpdwUsageCount); | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLInstallTranslatorW(LPCWSTR lpszInfFile, LPCWSTR lpszTranslator, | 
|  | LPCWSTR lpszPathIn, LPWSTR lpszPathOut, WORD cbPathOutMax, | 
|  | WORD *pcbPathOut, WORD fRequest, LPDWORD lpdwUsageCount) | 
|  | { | 
|  | clear_errors(); | 
|  | TRACE("%s %s %s %p %d %p %d %p\n", debugstr_w(lpszInfFile), | 
|  | debugstr_w(lpszTranslator), debugstr_w(lpszPathIn), lpszPathOut, | 
|  | cbPathOutMax, pcbPathOut, fRequest, lpdwUsageCount); | 
|  |  | 
|  | if (lpszInfFile) | 
|  | return FALSE; | 
|  |  | 
|  | return SQLInstallTranslatorExW(lpszTranslator, lpszPathIn, lpszPathOut, | 
|  | cbPathOutMax, pcbPathOut, fRequest, lpdwUsageCount); | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLManageDataSources(HWND hwnd) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | SQLRETURN WINAPI SQLPostInstallerErrorW(DWORD fErrorCode, LPCWSTR szErrorMsg) | 
|  | { | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | SQLRETURN WINAPI SQLPostInstallerError(DWORD fErrorCode, LPCSTR szErrorMsg) | 
|  | { | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLReadFileDSNW(LPCWSTR lpszFileName, LPCWSTR lpszAppName, | 
|  | LPCWSTR lpszKeyName, LPWSTR lpszString, WORD cbString, | 
|  | WORD *pcbString) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLReadFileDSN(LPCSTR lpszFileName, LPCSTR lpszAppName, | 
|  | LPCSTR lpszKeyName, LPSTR lpszString, WORD cbString, | 
|  | WORD *pcbString) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLRemoveDefaultDataSource(void) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLRemoveDriverW(LPCWSTR lpszDriver, BOOL fRemoveDSN, | 
|  | LPDWORD lpdwUsageCount) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLRemoveDriver(LPCSTR lpszDriver, BOOL fRemoveDSN, | 
|  | LPDWORD lpdwUsageCount) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLRemoveDriverManager(LPDWORD pdwUsageCount) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLRemoveDSNFromIniW(LPCWSTR lpszDSN) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLRemoveDSNFromIni(LPCSTR lpszDSN) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLRemoveTranslatorW(LPCWSTR lpszTranslator, LPDWORD lpdwUsageCount) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLRemoveTranslator(LPCSTR lpszTranslator, LPDWORD lpdwUsageCount) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLSetConfigMode(UWORD wConfigMode) | 
|  | { | 
|  | clear_errors(); | 
|  | if (wConfigMode > ODBC_SYSTEM_DSN) | 
|  | { | 
|  | push_error(ODBC_ERROR_INVALID_PARAM_SEQUENCE, odbc_error_invalid_param_sequence); | 
|  | return FALSE; | 
|  | } | 
|  | else | 
|  | { | 
|  | config_mode = wConfigMode; | 
|  | return TRUE; | 
|  | } | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLValidDSNW(LPCWSTR lpszDSN) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLValidDSN(LPCSTR lpszDSN) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLWriteDSNToIniW(LPCWSTR lpszDSN, LPCWSTR lpszDriver) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLWriteDSNToIni(LPCSTR lpszDSN, LPCSTR lpszDriver) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLWriteFileDSNW(LPCWSTR lpszFileName, LPCWSTR lpszAppName, | 
|  | LPCWSTR lpszKeyName, LPCWSTR lpszString) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLWriteFileDSN(LPCSTR lpszFileName, LPCSTR lpszAppName, | 
|  | LPCSTR lpszKeyName, LPCSTR lpszString) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLWritePrivateProfileStringW(LPCWSTR lpszSection, LPCWSTR lpszEntry, | 
|  | LPCWSTR lpszString, LPCWSTR lpszFilename) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | BOOL WINAPI SQLWritePrivateProfileString(LPCSTR lpszSection, LPCSTR lpszEntry, | 
|  | LPCSTR lpszString, LPCSTR lpszFilename) | 
|  | { | 
|  | clear_errors(); | 
|  | FIXME("\n"); | 
|  | SetLastError(ERROR_CALL_NOT_IMPLEMENTED); | 
|  | return FALSE; | 
|  | } |