https://git.reactos.org/?p=reactos.git;a=commitdiff;h=dd34a8a731cf49ba05a9d2e68782f6b18408e021
commit dd34a8a731cf49ba05a9d2e68782f6b18408e021 Author: Max Korostil <[email protected]> AuthorDate: Mon Oct 26 23:58:52 2020 +0300 Commit: Mark Jansen <[email protected]> CommitDate: Sat May 8 14:24:20 2021 +0200 [SDBINST] Update according to review Replace macroses with unicode functions Minor fixes Replace dynamic allocations Add GUID string validation Co-authored-by: Hermès BÉLUSCA - MAÏTO <[email protected]> Co-authored-by: Mark Jansen <[email protected]> --- base/applications/sdbinst/CMakeLists.txt | 3 +- base/applications/sdbinst/sdbinst.c | 280 ++++++++++++------------------- 2 files changed, 106 insertions(+), 177 deletions(-) diff --git a/base/applications/sdbinst/CMakeLists.txt b/base/applications/sdbinst/CMakeLists.txt index 682b84117cb..b33566aa786 100644 --- a/base/applications/sdbinst/CMakeLists.txt +++ b/base/applications/sdbinst/CMakeLists.txt @@ -1,6 +1,7 @@ +project(appcompat) include_directories(${REACTOS_SOURCE_DIR}/dll/appcompat/apphelp) add_executable(sdbinst sdbinst.c) set_module_type(sdbinst win32cui UNICODE) -add_importlibs(sdbinst msvcrt kernel32 ntdll advapi32 apphelp ole32) +add_importlibs(sdbinst advapi32 apphelp ole32 msvcrt kernel32 ntdll) add_cd_file(TARGET sdbinst DESTINATION reactos/system32 FOR all) diff --git a/base/applications/sdbinst/sdbinst.c b/base/applications/sdbinst/sdbinst.c index c4783ae68f7..ed25e70a8f2 100644 --- a/base/applications/sdbinst/sdbinst.c +++ b/base/applications/sdbinst/sdbinst.c @@ -5,22 +5,22 @@ * COPYRIGHT: Copyright 2020 Max Korostil ([email protected]) */ +#include <tchar.h> #include <windef.h> #include <winbase.h> -#include <tchar.h> #include <winreg.h> #include <strsafe.h> #include <objbase.h> #include <apphelp.h> - - #define APPCOMPAT_CUSTOM_REG_PATH L"Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Custom" #define APPCOMPAT_LAYERS_REG_PATH L"Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers" #define APPCOMPAT_INSTALLEDSDB_REG_PATH L"Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB" #define UNINSTALL_REG_PATH L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall" #define DBPATH_KEY_NAME L"DatabasePath" +#define SDB_EXT L".sdb" +#define GUID_STR_LENGTH 38 HRESULT RegisterSdbEntry( @@ -30,21 +30,14 @@ RegisterSdbEntry( _In_ BOOL isInstall, _In_ BOOL isExe) { - PWCHAR regName; - HKEY hKey = NULL; + WCHAR regName[MAX_PATH]; + HKEY hKey = NULL; LSTATUS status; HRESULT hres; - regName = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR)); - if (!regName) - { - hres = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); - goto end; - } - ZeroMemory(regName, MAX_PATH * sizeof(WCHAR)); - - hres = StringCchPrintf(regName, MAX_PATH, L"%ls\\%ls", - isExe ? APPCOMPAT_CUSTOM_REG_PATH : APPCOMPAT_LAYERS_REG_PATH, sdbEntryName); + hres = StringCchPrintfW(regName, MAX_PATH, L"%ls\\%ls", + isExe ? APPCOMPAT_CUSTOM_REG_PATH : APPCOMPAT_LAYERS_REG_PATH, + sdbEntryName); if (FAILED(hres)) { wprintf(L"StringCchPrintfW error: 0x%08X\n", hres); @@ -54,12 +47,12 @@ RegisterSdbEntry( // Remove key if (!isInstall) { - status = RegDeleteKey(HKEY_LOCAL_MACHINE, regName); + status = RegDeleteKeyW(HKEY_LOCAL_MACHINE, regName); return HRESULT_FROM_WIN32(status); } // Create main key - status = RegCreateKeyEx(HKEY_LOCAL_MACHINE, + status = RegCreateKeyExW(HKEY_LOCAL_MACHINE, regName, 0, NULL, @@ -68,7 +61,6 @@ RegisterSdbEntry( NULL, &hKey, NULL); - if (status != ERROR_SUCCESS) { wprintf(L"RegKeyCreateEx error: 0x%08X", status); @@ -76,8 +68,8 @@ RegisterSdbEntry( goto end; } - // Set instlled time - status = RegSetValueEx(hKey, + // Set installed time + status = RegSetValueExW(hKey, dbGuid, 0, REG_QWORD, @@ -85,7 +77,7 @@ RegisterSdbEntry( sizeof(time)); if (status != ERROR_SUCCESS) { - wprintf(L"RegSetValueEx error: 0x%08X", status); + wprintf(L"RegSetValueExW error: 0x%08X", status); hres = HRESULT_FROM_WIN32(status); goto end; } @@ -96,11 +88,6 @@ end: RegCloseKey(hKey); } - if (regName) - { - HeapFree(GetProcessHeap(), 0, regName); - } - return hres; } @@ -110,52 +97,41 @@ AddUninstallKey( _In_ LPCWSTR sdbInstalledPath, _In_ LPCWSTR guidDbStr) { - PWCHAR sdbinstPath; - PWCHAR regName; - PWCHAR uninstString; + WCHAR sdbinstPath[MAX_PATH]; + WCHAR regName[MAX_PATH]; + WCHAR uninstString[MAX_PATH]; HKEY hKey = NULL; HRESULT hres; - sdbinstPath = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR)); - regName = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR)); - uninstString = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR)); - - if (!sdbinstPath || !regName || !uninstString) - { - hres = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); - goto end; - } - - ZeroMemory(sdbinstPath, MAX_PATH * sizeof(WCHAR)); - ZeroMemory(regName, MAX_PATH * sizeof(WCHAR)); - ZeroMemory(uninstString, MAX_PATH * sizeof(WCHAR)); - UINT count = GetSystemWindowsDirectory(sdbinstPath, MAX_PATH); if (sdbinstPath[count - 1] != L'\\') { - sdbinstPath[count] = L'\\'; + hres = StringCchCatW(sdbinstPath, MAX_PATH, L"\\"); + if (FAILED(hres)) + { + wprintf(L"StringCchCatW error: 0x%08X", hres); + goto end; + } } // Full path to sdbinst.exe - hres = StringCchCat(sdbinstPath, MAX_PATH, L"System32\\sdbinst.exe"); + hres = StringCchCatW(sdbinstPath, MAX_PATH, L"System32\\sdbinst.exe"); if (FAILED(hres)) { - wprintf(L"StringCchCat error: 0x%08X", hres); + wprintf(L"StringCchCatW error: 0x%08X", hres); + goto end; } // Sdb guid reg key - hres = StringCchPrintf(regName, MAX_PATH, L"%ls\\%ls", UNINSTALL_REG_PATH, guidDbStr); + hres = StringCchPrintfW(regName, MAX_PATH, L"%ls\\%ls", UNINSTALL_REG_PATH, guidDbStr); if (FAILED(hres)) { wprintf(L"StringCchPrintfW error: 0x%08X", hres); goto end; } - wprintf(L"%ls\n", sdbinstPath); - wprintf(L"%ls\n", regName); - // Create main key - LSTATUS status = RegCreateKeyEx(HKEY_LOCAL_MACHINE, + LSTATUS status = RegCreateKeyExW(HKEY_LOCAL_MACHINE, regName, 0, NULL, @@ -174,7 +150,7 @@ AddUninstallKey( // Set Display name DWORD length = wcslen(dbName) * sizeof(WCHAR); - status = RegSetValueEx(hKey, + status = RegSetValueExW(hKey, L"DisplayName", 0, REG_SZ, @@ -182,13 +158,13 @@ AddUninstallKey( length + sizeof(WCHAR)); if (status != ERROR_SUCCESS) { - wprintf(L"RegSetValueEx error: 0x%08X", status); + wprintf(L"RegSetValueExW error: 0x%08X", status); hres = HRESULT_FROM_WIN32(status); goto end; } // Uninstall full string - hres = StringCchPrintf(uninstString, MAX_PATH, L"%ls -u \"%ls\"", sdbinstPath, sdbInstalledPath); + hres = StringCchPrintfW(uninstString, MAX_PATH, L"%ls -u \"%ls\"", sdbinstPath, sdbInstalledPath); if (FAILED(hres)) { wprintf(L"StringCchPrintfW error: 0x%08X", hres); @@ -197,7 +173,7 @@ AddUninstallKey( // Set uninstall string length = wcslen(uninstString) * sizeof(WCHAR); - status = RegSetValueEx(hKey, + status = RegSetValueExW(hKey, L"UninstallString", 0, REG_SZ, @@ -205,7 +181,7 @@ AddUninstallKey( length + sizeof(WCHAR)); if (status != ERROR_SUCCESS) { - wprintf(L"RegSetValueEx error: 0x%08X", status); + wprintf(L"RegSetValueExW error: 0x%08X", status); hres = HRESULT_FROM_WIN32(status); goto end; } @@ -216,21 +192,6 @@ end: RegCloseKey(hKey); } - if (sdbinstPath) - { - HeapFree(GetProcessHeap(), 0, sdbinstPath); - } - - if (regName) - { - HeapFree(GetProcessHeap(), 0, regName); - } - - if (uninstString) - { - HeapFree(GetProcessHeap(), 0, uninstString); - } - return hres; } @@ -246,7 +207,6 @@ GetSdbGuid( TAGID tagDbId; tagDbId = SdbFindFirstTag(pdb, tagDb, TAG_DATABASE_ID); - if (!tagDbId) { wprintf(L"Can't find database id tag"); @@ -276,7 +236,7 @@ ProcessLayers( TAGID tagLayer = SdbFindFirstTag(pdb, tagDb, TAG_LAYER); - // Add all exe to registry (AppCompatFlags) + // Add all layers to registry (AppCompatFlags) while (tagLayer && (tagLayer != prevTagLayer)) { tagLayerName = SdbFindFirstTag(pdb, tagLayer, TAG_NAME); @@ -287,7 +247,6 @@ ProcessLayers( } LPWSTR name = SdbGetStringTagPtr(pdb, tagLayerName); - wprintf(L"Layer name %ls", name); res = RegisterSdbEntry(name, guidDbStr, time, isInstall, FALSE); if (FAILED(res)) @@ -329,7 +288,6 @@ ProcessExe( } LPWSTR name = SdbGetStringTagPtr(pdb, tagExeName); - wprintf(L"Exe name %ls\n", name); res = RegisterSdbEntry(name, guidDbStr, time, isInstall, TRUE); if (FAILED(res)) @@ -365,13 +323,17 @@ CopySdbToAppPatch( CopyMemory(sysdirPath, destSdbPath, destLen * sizeof(WCHAR)); pTmpSysdir = sysdirPath + destLen; - while (*pTmpSysdir != L'\\') + while (pTmpSysdir > sysdirPath && *pTmpSysdir != L'\\') { *pTmpSysdir = L'\0'; --pTmpSysdir; } - wprintf(L"%ls\n", sysdirPath); + if (pTmpSysdir == sysdirPath) + { + wprintf(L"Can't find directory separator\n"); + goto end; + } // Create directory if need if (!CreateDirectory(sysdirPath, NULL)) @@ -379,7 +341,7 @@ CopySdbToAppPatch( error = GetLastError(); if (error != ERROR_ALREADY_EXISTS) { - wprintf(L"Can't create folder %ls\n Error: 0x%08\n", sysdirPath, error); + wprintf(L"Can't create folder %ls\n Error: 0x%08X\n", sysdirPath, error); goto end; } error = ERROR_SUCCESS; @@ -392,34 +354,37 @@ CopySdbToAppPatch( wprintf(L"Can't copy sdb file"); } - HeapFree(GetProcessHeap(), 0, sysdirPath); - end: + if (sysdirPath) + { + HeapFree(GetProcessHeap(), 0, sysdirPath); + } + return HRESULT_FROM_WIN32(error); } HRESULT BuildPathToSdb( - _In_ PWCHAR* buffer, + _In_ PWCHAR buffer, _In_ SIZE_T bufLen, _In_ LPCWSTR guidDbStr) { - PWCHAR pBuffer = *buffer; - ZeroMemory(pBuffer, bufLen * sizeof(WCHAR)); + ZeroMemory(buffer, bufLen * sizeof(WCHAR)); - UINT count = GetSystemWindowsDirectory(pBuffer, bufLen); - if (pBuffer[count - 1] != L'\\') + // Can't use here SdbGetAppPatchDir, because Windows XP haven't this function + UINT count = GetSystemWindowsDirectory(buffer, bufLen); + if (buffer[count - 1] != L'\\') { - pBuffer[count] = L'\\'; + buffer[count] = L'\\'; } - HRESULT res = StringCchCatW(pBuffer, bufLen, L"AppPatch\\Custom\\"); + HRESULT res = StringCchCatW(buffer, bufLen, L"AppPatch\\Custom\\"); if (FAILED(res)) { goto end; } - res = StringCchCatW(pBuffer, bufLen, guidDbStr); + res = StringCchCatW(buffer, bufLen, guidDbStr); end: return res; @@ -436,15 +401,8 @@ SdbInstall( GUID dbGuid = {0}; FILETIME systemTime = {0}; ULARGE_INTEGER currentTime = {0}; - PWCHAR sysdirPatchPath = NULL; - PWCHAR guidDbStr; - - guidDbStr = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR)); - if (!guidDbStr) - { - goto end; - } - ZeroMemory(guidDbStr, MAX_PATH * sizeof(WCHAR)); + WCHAR sysdirPatchPath[MAX_PATH]; + WCHAR guidDbStr[GUID_STR_LENGTH + ARRAYSIZE(SDB_EXT) + 1]; GetSystemTimeAsFileTime(&systemTime); currentTime.LowPart = systemTime.dwLowDateTime; @@ -473,7 +431,7 @@ SdbInstall( } StringFromGUID2(&dbGuid, guidDbStr, MAX_PATH); - HRESULT hres = StringCchCatW(guidDbStr, MAX_PATH, L".sdb"); + HRESULT hres = StringCchCatW(guidDbStr, MAX_PATH, SDB_EXT); if (FAILED(hres)) { wprintf(L"StringCchCatW error 0x%08X\n", hres); @@ -508,11 +466,8 @@ SdbInstall( goto end; } - SIZE_T bufLen = MAX_PATH * 2; - sysdirPatchPath = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, bufLen * sizeof(WCHAR)); - - // Create full path tos db in system folder - hres = BuildPathToSdb(&sysdirPatchPath, bufLen, guidDbStr); + // Create full path to sdb in system folder + hres = BuildPathToSdb(sysdirPatchPath, ARRAYSIZE(sysdirPatchPath), guidDbStr); if (FAILED(hres)) { wprintf(L"Build path error\n"); @@ -533,7 +488,7 @@ SdbInstall( // Registration if (!SdbRegisterDatabaseEx(sysdirPatchPath, SDB_DATABASE_SHIM, ¤tTime.QuadPart)) { - wprintf(L"SdbRegisterDatabaseEx UNSUCCESS"); + wprintf(L"SdbRegisterDatabaseEx failed"); goto end; } @@ -545,16 +500,6 @@ end: SdbCloseDatabase(pdb); } - if (sysdirPatchPath) - { - HeapFree(GetProcessHeap(), 0, sysdirPatchPath); - } - - if (guidDbStr) - { - HeapFree(GetProcessHeap(), 0, guidDbStr); - } - return res; } @@ -565,7 +510,7 @@ DeleteUninstallKey( HKEY hKey = NULL; HRESULT hres = HRESULT_FROM_WIN32(ERROR_SUCCESS); - LSTATUS status = RegCreateKeyEx(HKEY_LOCAL_MACHINE, + LSTATUS status = RegCreateKeyExW(HKEY_LOCAL_MACHINE, UNINSTALL_REG_PATH, 0, NULL, @@ -581,7 +526,7 @@ DeleteUninstallKey( goto end; } - status = RegDeleteKey(hKey, keyName); + status = RegDeleteKeyW(hKey, keyName); if (status != ERROR_SUCCESS) { hres = HRESULT_FROM_WIN32(status); @@ -676,8 +621,25 @@ end: return res; } -#define BUFFER_SIZE (MAX_PATH * sizeof(WCHAR) + sizeof(WCHAR)) -#define STRING_LEN (MAX_PATH + 1) +BOOL +ValidateGuidString( + _In_ PWCHAR guidStr) +{ + ULONG length = wcslen(guidStr); + + if (length == GUID_STR_LENGTH && + guidStr[0] == L'{' && + guidStr[GUID_STR_LENGTH - 1] == L'}' && + guidStr[9] == L'-' && + guidStr[14] == L'-' && + guidStr[19] == L'-' && + guidStr[24] == L'-') + { + return TRUE; + } + + return FALSE; +} BOOL SdbUninstallByGuid( @@ -687,24 +649,24 @@ SdbUninstallByGuid( HKEY hKey = NULL; HKEY guidKey = NULL; LSTATUS status; - DWORD keyValSize = BUFFER_SIZE; - PWCHAR dbPath = NULL; + WCHAR dbPath[MAX_PATH]; + DWORD keyValSize = sizeof(dbPath); - dbPath = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE); - if (!dbPath) + if (!ValidateGuidString(guidSdbStr)) { - goto end; + wprintf(L"Invalid GUID: %ls\n", guidSdbStr); + return res; } - status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, APPCOMPAT_INSTALLEDSDB_REG_PATH, 0, KEY_READ | KEY_QUERY_VALUE, &hKey); + status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, APPCOMPAT_INSTALLEDSDB_REG_PATH, 0, KEY_READ | KEY_QUERY_VALUE, &hKey); if (status != ERROR_SUCCESS) { - wprintf(L"RegOpenKey error: 0x%08X", status); + wprintf(L"RegOpenKeyW error: 0x%08X", status); goto end; } - status = RegOpenKeyEx(hKey, guidSdbStr, 0, KEY_READ | KEY_QUERY_VALUE, &guidKey); + status = RegOpenKeyExW(hKey, guidSdbStr, 0, KEY_READ | KEY_QUERY_VALUE, &guidKey); if (status != ERROR_SUCCESS) { @@ -712,21 +674,16 @@ SdbUninstallByGuid( goto end; } - status = RegQueryValueEx(guidKey, DBPATH_KEY_NAME, NULL, NULL, (LPBYTE)dbPath, &keyValSize); + status = RegQueryValueExW(guidKey, DBPATH_KEY_NAME, NULL, NULL, (LPBYTE)dbPath, &keyValSize); if (status != ERROR_SUCCESS) { - wprintf(L"RegQueryValueEx: 0x%08X\n", status); + wprintf(L"RegQueryValueExW: 0x%08X\n", status); goto end; } res = SdbUninstall(dbPath); end: - if (dbPath) - { - HeapFree(GetProcessHeap(), 0, dbPath); - } - if (hKey) { RegCloseKey(hKey); @@ -748,31 +705,18 @@ SdbUninstallByName( LSTATUS status; HKEY hKey = NULL; HKEY subKey = NULL; - DWORD index = 0; - DWORD keyNameLen = STRING_LEN; - PWCHAR keyName = NULL; + DWORD index = 0; + WCHAR keyName[MAX_PATH]; + DWORD keyNameLen = ARRAYSIZE(keyName); DWORD keyValSize; - PWCHAR dbDescript = NULL; - PWCHAR dbPath = NULL; - - keyName = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE); - dbDescript = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE); - dbPath = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE); - - if (!keyName || !dbDescript || !dbPath) - { - goto end; - } - - ZeroMemory(keyName, BUFFER_SIZE); - ZeroMemory(dbDescript, BUFFER_SIZE); - ZeroMemory(dbPath, BUFFER_SIZE); + WCHAR dbDescript[MAX_PATH]; + WCHAR dbPath[MAX_PATH]; - status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, APPCOMPAT_INSTALLEDSDB_REG_PATH, 0, KEY_READ | KEY_QUERY_VALUE, &hKey); + status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, APPCOMPAT_INSTALLEDSDB_REG_PATH, 0, KEY_READ | KEY_QUERY_VALUE, &hKey); if (status != ERROR_SUCCESS) { - wprintf(L"RegOpenKey error: 0x%08X", status); + wprintf(L"RegOpenKeyW error: 0x%08X", status); goto end; } @@ -782,14 +726,14 @@ SdbUninstallByName( // Search db guid by name while (status == ERROR_SUCCESS) { - status = RegOpenKeyEx(hKey, keyName, 0, KEY_READ | KEY_QUERY_VALUE, &subKey); + status = RegOpenKeyExW(hKey, keyName, 0, KEY_READ | KEY_QUERY_VALUE, &subKey); if (status != ERROR_SUCCESS) { break; } - keyValSize = BUFFER_SIZE; - status = RegQueryValueEx(subKey, L"DatabaseDescription", NULL, NULL, (LPBYTE)dbDescript, &keyValSize); + keyValSize = sizeof(dbDescript); + status = RegQueryValueExW(subKey, L"DatabaseDescription", NULL, NULL, (LPBYTE)dbDescript, &keyValSize); if (status != ERROR_SUCCESS) { break; @@ -800,8 +744,8 @@ SdbUninstallByName( if (_wcsnicmp(dbDescript, nameSdbStr, keyNameLen) == 0) { // Take db full path - keyValSize = BUFFER_SIZE; - status = RegQueryValueEx(subKey, DBPATH_KEY_NAME, NULL, NULL, (LPBYTE)dbPath, &keyValSize); + keyValSize = sizeof(dbPath); + status = RegQueryValueExW(subKey, DBPATH_KEY_NAME, NULL, NULL, (LPBYTE)dbPath, &keyValSize); if (status != ERROR_SUCCESS) { dbPath[0] = UNICODE_NULL; @@ -818,8 +762,8 @@ SdbUninstallByName( keyName[0] = UNICODE_NULL; ++index; - keyNameLen = STRING_LEN; - status = RegEnumKeyEx(hKey, index, keyName, &keyNameLen, NULL, NULL, NULL, NULL); + keyNameLen = ARRAYSIZE(keyName); + status = RegEnumKeyExW(hKey, index, keyName, &keyNameLen, NULL, NULL, NULL, NULL); } RegCloseKey(hKey); @@ -830,21 +774,6 @@ SdbUninstallByName( } end: - if (dbPath) - { - HeapFree(GetProcessHeap(), 0, dbPath); - } - - if (dbDescript) - { - HeapFree(GetProcessHeap(), 0, dbDescript); - } - - if (keyName) - { - HeapFree(GetProcessHeap(), 0, keyName); - } - return res; } @@ -854,7 +783,6 @@ ShowHelp() { wprintf(L"Using: sdbinst [-?][-q][-u][-g][-n] foo.sdb | {guid} | \"name\" \n" L"-? - show help\n" - //L"-q - silence mode\n" L"-u - uninstall\n" L"-g - {guid} file guid (only uninstall)\n" L"-n - \"name\" - file name (only uninstall)\n");
