mirror of
https://github.com/reactos/reactos.git
synced 2025-02-22 16:36:33 +00:00
[MSI_WINETEST]
* Sync to Wine 1.5.4. svn path=/trunk/; revision=56589
This commit is contained in:
parent
1ead8de25d
commit
e416c79745
5 changed files with 964 additions and 4949 deletions
|
@ -73,7 +73,8 @@ static const char component_dat[] =
|
|||
"Two\t{BF03D1A6-20DA-4A65-82F3-6CAC995915CE}\tFIRSTDIR\t2\t\ttwo.txt\n"
|
||||
"dangler\t{6091DF25-EF96-45F1-B8E9-A9B1420C7A3C}\tTARGETDIR\t4\t\tregdata\n"
|
||||
"component\t\tMSITESTDIR\t0\t1\tfile\n"
|
||||
"service_comp\t\tMSITESTDIR\t0\t1\tservice_file";
|
||||
"service_comp\t{935A0A91-22A3-4F87-BCA8-928FFDFE2353}\tMSITESTDIR\t0\t\tservice_file\n"
|
||||
"service_comp2\t{3F7B04A4-9521-4649-BDC9-0C8722740A49}\tMSITESTDIR\t0\t\tservice_file2";
|
||||
|
||||
static const char directory_dat[] =
|
||||
"Directory\tDirectory_Parent\tDefaultDir\n"
|
||||
|
@ -109,7 +110,8 @@ static const char feature_comp_dat[] =
|
|||
"Three\tThree\n"
|
||||
"Two\tTwo\n"
|
||||
"feature\tcomponent\n"
|
||||
"service_feature\tservice_comp\n";
|
||||
"service_feature\tservice_comp\n"
|
||||
"service_feature\tservice_comp2";
|
||||
|
||||
static const char file_dat[] =
|
||||
"File\tComponent_\tFileName\tFileSize\tVersion\tLanguage\tAttributes\tSequence\n"
|
||||
|
@ -121,7 +123,8 @@ static const char file_dat[] =
|
|||
"three.txt\tThree\tthree.txt\t1000\t\t\t0\t3\n"
|
||||
"two.txt\tTwo\ttwo.txt\t1000\t\t\t0\t2\n"
|
||||
"file\tcomponent\tfilename\t100\t\t\t8192\t1\n"
|
||||
"service_file\tservice_comp\tservice.exe\t100\t\t\t8192\t1";
|
||||
"service_file\tservice_comp\tservice.exe\t100\t\t\t8192\t6\n"
|
||||
"service_file2\tservice_comp2\tservice2.exe\t100\t\t\t8192\t7";
|
||||
|
||||
static const char install_exec_seq_dat[] =
|
||||
"Action\tCondition\tSequence\n"
|
||||
|
@ -149,7 +152,7 @@ static const char media_dat[] =
|
|||
"i2\ti4\tL64\tS255\tS32\tS72\n"
|
||||
"Media\tDiskId\n"
|
||||
"1\t3\t\t\tDISK1\t\n"
|
||||
"2\t5\t\tmsitest.cab\tDISK2\t\n";
|
||||
"2\t7\t\tmsitest.cab\tDISK2\t\n";
|
||||
|
||||
static const char property_dat[] =
|
||||
"Property\tValue\n"
|
||||
|
@ -172,7 +175,9 @@ static const char property_dat[] =
|
|||
"AdminProperties\tPOSTADMIN\n"
|
||||
"ROOTDRIVE\tC:\\\n"
|
||||
"SERVNAME\tTestService\n"
|
||||
"SERVNAME2\tTestService2\n"
|
||||
"SERVDISP\tTestServiceDisp\n"
|
||||
"SERVDISP2\tTestServiceDisp2\n"
|
||||
"MSIFASTINSTALL\t1\n";
|
||||
|
||||
static const char environment_dat[] =
|
||||
|
@ -211,13 +216,15 @@ static const char service_install_dat[] =
|
|||
"LoadOrderGroup\tDependencies\tStartName\tPassword\tArguments\tComponent_\tDescription\n"
|
||||
"s72\ts255\tL255\ti4\ti4\ti4\tS255\tS255\tS255\tS255\tS255\ts72\tL255\n"
|
||||
"ServiceInstall\tServiceInstall\n"
|
||||
"TestService\t[SERVNAME]\t[SERVDISP]\t2\t3\t0\t\tservice1[~]+group1[~]service2[~]+group2[~][~]\tTestService\t\t-a arg\tservice_comp\tdescription";
|
||||
"TestService\t[SERVNAME]\t[SERVDISP]\t2\t3\t0\t\tservice1[~]+group1[~]service2[~]+group2[~][~]\tTestService\t\t-a arg\tservice_comp\tdescription\n"
|
||||
"TestService2\tSERVNAME2]\t[SERVDISP2]\t2\t3\t0\t\tservice1[~]+group1[~]service2[~]+group2[~][~]\tTestService2\t\t-a arg\tservice_comp2\tdescription";
|
||||
|
||||
static const char service_control_dat[] =
|
||||
"ServiceControl\tName\tEvent\tArguments\tWait\tComponent_\n"
|
||||
"s72\tl255\ti2\tL255\tI2\ts72\n"
|
||||
"ServiceControl\tServiceControl\n"
|
||||
"ServiceControl\tTestService\t8\t\t0\tservice_comp";
|
||||
"ServiceControl\tTestService3\t8\t\t0\tservice_comp\n"
|
||||
"ServiceControl2\tTestService3\t128\t\t0\tservice_comp2";
|
||||
|
||||
static const char sss_service_control_dat[] =
|
||||
"ServiceControl\tName\tEvent\tArguments\tWait\tComponent_\n"
|
||||
|
@ -254,11 +261,13 @@ static const char sds_install_exec_seq_dat[] =
|
|||
"CostFinalize\t\t1000\n"
|
||||
"InstallValidate\t\t1400\n"
|
||||
"InstallInitialize\t\t1500\n"
|
||||
"DeleteServices\tInstalled\t5000\n"
|
||||
"StopServices\t\t5000\n"
|
||||
"DeleteServices\t\t5050\n"
|
||||
"MoveFiles\t\t5100\n"
|
||||
"InstallFiles\t\t5200\n"
|
||||
"DuplicateFiles\t\t5300\n"
|
||||
"InstallServices\tNOT Installed\t5400\n"
|
||||
"InstallServices\t\t5400\n"
|
||||
"StartServices\t\t5450\n"
|
||||
"RegisterProduct\t\t5500\n"
|
||||
"PublishFeatures\t\t5600\n"
|
||||
"PublishProduct\t\t5700\n"
|
||||
|
@ -2181,6 +2190,7 @@ static void create_test_files(void)
|
|||
|
||||
create_file("msitest\\filename", 100);
|
||||
create_file("msitest\\service.exe", 100);
|
||||
create_file("msitest\\service2.exe", 100);
|
||||
|
||||
DeleteFileA("four.txt");
|
||||
DeleteFileA("five.txt");
|
||||
|
@ -2208,6 +2218,7 @@ static void delete_test_files(void)
|
|||
DeleteFileA("msitest\\first\\two.txt");
|
||||
DeleteFileA("msitest\\one.txt");
|
||||
DeleteFileA("msitest\\service.exe");
|
||||
DeleteFileA("msitest\\service2.exe");
|
||||
DeleteFileA("msitest\\filename");
|
||||
RemoveDirectoryA("msitest\\second");
|
||||
RemoveDirectoryA("msitest\\first");
|
||||
|
@ -4706,6 +4717,7 @@ static void test_envvar(void)
|
|||
delete_pf("msitest\\filename", TRUE);
|
||||
delete_pf("msitest\\one.txt", TRUE);
|
||||
delete_pf("msitest\\service.exe", TRUE);
|
||||
delete_pf("msitest\\service2.exe", TRUE);
|
||||
delete_pf("msitest", FALSE);
|
||||
|
||||
error:
|
||||
|
@ -4830,6 +4842,7 @@ static void test_start_services(void)
|
|||
ok(delete_pf("msitest\\filename", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\one.txt", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\service.exe", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\service2.exe", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest", FALSE), "Directory not created\n");
|
||||
|
||||
delete_test_files();
|
||||
|
@ -4853,6 +4866,8 @@ static void test_start_services(void)
|
|||
static void test_delete_services(void)
|
||||
{
|
||||
UINT r;
|
||||
SC_HANDLE manager, service;
|
||||
DWORD error;
|
||||
|
||||
if (is_process_limited())
|
||||
{
|
||||
|
@ -4860,6 +4875,18 @@ static void test_delete_services(void)
|
|||
return;
|
||||
}
|
||||
|
||||
manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
||||
ok(manager != NULL, "can't open service manager %u\n", GetLastError());
|
||||
if (!manager) return;
|
||||
|
||||
service = CreateServiceA(manager, "TestService3", "TestService3",
|
||||
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START,
|
||||
SERVICE_ERROR_NORMAL, "C:\\doesnt_exist.exe", NULL, NULL, NULL, NULL, NULL);
|
||||
ok(service != NULL, "can't create service %u\n", GetLastError());
|
||||
CloseServiceHandle(service);
|
||||
CloseServiceHandle(manager);
|
||||
if (!service) goto error;
|
||||
|
||||
create_test_files();
|
||||
create_database(msifile, sds_tables, sizeof(sds_tables) / sizeof(msi_table));
|
||||
|
||||
|
@ -4873,6 +4900,16 @@ static void test_delete_services(void)
|
|||
}
|
||||
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
|
||||
|
||||
manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
||||
ok(manager != NULL, "can't open service manager\n");
|
||||
if (!manager) goto error;
|
||||
|
||||
service = OpenServiceA(manager, "TestService3", GENERIC_ALL);
|
||||
error = GetLastError();
|
||||
ok(service == NULL, "TestService3 not deleted\n");
|
||||
ok(error == ERROR_SERVICE_DOES_NOT_EXIST, "wrong error %u\n", error);
|
||||
CloseServiceHandle(manager);
|
||||
|
||||
r = MsiInstallProductA(msifile, "REMOVE=ALL");
|
||||
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
|
||||
|
||||
|
@ -4887,6 +4924,7 @@ static void test_delete_services(void)
|
|||
ok(delete_pf("msitest\\filename", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\one.txt", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\service.exe", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\service2.exe", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest", FALSE), "Directory not created\n");
|
||||
|
||||
error:
|
||||
|
@ -4928,6 +4966,7 @@ static void test_self_registration(void)
|
|||
ok(delete_pf("msitest\\filename", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\one.txt", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\service.exe", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\service2.exe", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest", FALSE), "Directory not created\n");
|
||||
|
||||
error:
|
||||
|
@ -5034,6 +5073,7 @@ static void test_validate_product_id(void)
|
|||
ok(delete_pf("msitest\\filename", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\one.txt", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\service.exe", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest\\service2.exe", TRUE), "File not installed\n");
|
||||
ok(delete_pf("msitest", FALSE), "Directory not created\n");
|
||||
|
||||
error:
|
||||
|
|
|
@ -489,113 +489,79 @@ static DISPID get_dispid( IDispatch *disp, const char *name )
|
|||
return id;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
DISPID did;
|
||||
const char *name;
|
||||
BOOL todo;
|
||||
} get_did_t;
|
||||
|
||||
static const get_did_t get_did_data[] = {
|
||||
{ 1, "CreateRecord" },
|
||||
{ 2, "OpenPackage" },
|
||||
{ 3, "OpenProduct" },
|
||||
{ 4, "OpenDatabase" },
|
||||
{ 5, "SummaryInformation" },
|
||||
{ 6, "UILevel" },
|
||||
{ 7, "EnableLog" },
|
||||
{ 8, "InstallProduct" },
|
||||
{ 9, "Version" },
|
||||
{ 10, "LastErrorRecord" },
|
||||
{ 11, "RegistryValue" },
|
||||
{ 12, "Environment" },
|
||||
{ 13, "FileAttributes" },
|
||||
{ 15, "FileSize" },
|
||||
{ 16, "FileVersion" },
|
||||
{ 17, "ProductState" },
|
||||
{ 18, "ProductInfo" },
|
||||
{ 19, "ConfigureProduct", TRUE },
|
||||
{ 20, "ReinstallProduct", TRUE },
|
||||
{ 21, "CollectUserInfo", TRUE },
|
||||
{ 22, "ApplyPatch", TRUE },
|
||||
{ 23, "FeatureParent", TRUE },
|
||||
{ 24, "FeatureState", TRUE },
|
||||
{ 25, "UseFeature", TRUE },
|
||||
{ 26, "FeatureUsageCount", TRUE },
|
||||
{ 27, "FeatureUsageDate", TRUE },
|
||||
{ 28, "ConfigureFeature", TRUE },
|
||||
{ 29, "ReinstallFeature", TRUE },
|
||||
{ 30, "ProvideComponent", TRUE },
|
||||
{ 31, "ComponentPath", TRUE },
|
||||
{ 32, "ProvideQualifiedComponent", TRUE },
|
||||
{ 33, "QualifierDescription", TRUE },
|
||||
{ 34, "ComponentQualifiers", TRUE },
|
||||
{ 35, "Products" },
|
||||
{ 36, "Features", TRUE },
|
||||
{ 37, "Components", TRUE },
|
||||
{ 38, "ComponentClients", TRUE },
|
||||
{ 39, "Patches", TRUE },
|
||||
{ 40, "RelatedProducts" },
|
||||
{ 41, "PatchInfo", TRUE },
|
||||
{ 42, "PatchTransforms", TRUE },
|
||||
{ 43, "AddSource", TRUE },
|
||||
{ 44, "ClearSourceList", TRUE },
|
||||
{ 45, "ForceSourceListResolution", TRUE },
|
||||
{ 46, "ShortcutTarget", TRUE },
|
||||
{ 47, "FileHash", TRUE },
|
||||
{ 48, "FileSignatureInfo", TRUE },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static void test_dispid(void)
|
||||
{
|
||||
const get_did_t *ptr = get_did_data;
|
||||
DISPID dispid;
|
||||
|
||||
dispid = get_dispid(pInstaller, "CreateRecord");
|
||||
ok(dispid == 1, "Expected 1, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "OpenPackage");
|
||||
ok(dispid == 2, "Expected 2, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "OpenProduct");
|
||||
ok(dispid == 3, "Expected 3, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "OpenDatabase");
|
||||
ok(dispid == 4, "Expected 4, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "SummaryInformation");
|
||||
ok(dispid == 5, "Expected 5, got %d\n", dispid);
|
||||
dispid = get_dispid( pInstaller, "UILevel" );
|
||||
ok(dispid == 6, "Expected 6, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "EnableLog");
|
||||
ok(dispid == 7, "Expected 7, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "InstallProduct");
|
||||
ok(dispid == 8, "Expected 8, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "Version");
|
||||
ok(dispid == 9, "Expected 9, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "LastErrorRecord");
|
||||
ok(dispid == 10, "Expected 10, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "RegistryValue");
|
||||
ok(dispid == 11, "Expected 11, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "Environment");
|
||||
ok(dispid == 12, "Expected 12, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "FileAttributes");
|
||||
ok(dispid == 13, "Expected 13, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "FileSize");
|
||||
ok(dispid == 15, "Expected 15, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "FileVersion");
|
||||
ok(dispid == 16, "Expected 16, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ProductState");
|
||||
ok(dispid == 17, "Expected 17, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ProductInfo");
|
||||
ok(dispid == 18, "Expected 18, got %d\n", dispid);
|
||||
todo_wine
|
||||
while (ptr->name)
|
||||
{
|
||||
dispid = get_dispid(pInstaller, "ConfigureProduct");
|
||||
ok(dispid == 19, "Expected 19, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ReinstallProduct");
|
||||
ok(dispid == 20 , "Expected 20, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "CollectUserInfo");
|
||||
ok(dispid == 21, "Expected 21, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ApplyPatch");
|
||||
ok(dispid == 22, "Expected 22, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "FeatureParent");
|
||||
ok(dispid == 23, "Expected 23, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "FeatureState");
|
||||
ok(dispid == 24, "Expected 24, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "UseFeature");
|
||||
ok(dispid == 25, "Expected 25, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "FeatureUsageCount");
|
||||
ok(dispid == 26, "Expected 26, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "FeatureUsageDate");
|
||||
ok(dispid == 27, "Expected 27, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ConfigureFeature");
|
||||
ok(dispid == 28, "Expected 28, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ReinstallFeature");
|
||||
ok(dispid == 29, "Expected 29, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ProvideComponent");
|
||||
ok(dispid == 30, "Expected 30, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ComponentPath");
|
||||
ok(dispid == 31, "Expected 31, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ProvideQualifiedComponent");
|
||||
ok(dispid == 32, "Expected 32, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "QualifierDescription");
|
||||
ok(dispid == 33, "Expected 33, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ComponentQualifiers");
|
||||
ok(dispid == 34, "Expected 34, got %d\n", dispid);
|
||||
}
|
||||
dispid = get_dispid(pInstaller, "Products");
|
||||
ok(dispid == 35, "Expected 35, got %d\n", dispid);
|
||||
todo_wine
|
||||
{
|
||||
dispid = get_dispid(pInstaller, "Features");
|
||||
ok(dispid == 36, "Expected 36, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "Components");
|
||||
ok(dispid == 37, "Expected 37, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ComponentClients");
|
||||
ok(dispid == 38, "Expected 38, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "Patches");
|
||||
ok(dispid == 39, "Expected 39, got %d\n", dispid);
|
||||
}
|
||||
dispid = get_dispid(pInstaller, "RelatedProducts");
|
||||
ok(dispid == 40, "Expected 40, got %d\n", dispid);
|
||||
todo_wine
|
||||
{
|
||||
dispid = get_dispid(pInstaller, "PatchInfo");
|
||||
ok(dispid == 41, "Expected 41, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "PatchTransforms");
|
||||
ok(dispid == 42, "Expected 42, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "AddSource");
|
||||
ok(dispid == 43, "Expected 43, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ClearSourceList");
|
||||
ok(dispid == 44, "Expected 44, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ForceSourceListResolution");
|
||||
ok(dispid == 45, "Expected 45, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ShortcutTarget");
|
||||
ok(dispid == 46, "Expected 46, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "FileHash");
|
||||
ok(dispid == 47, "Expected 47, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "FileSignatureInfo");
|
||||
ok(dispid == 48, "Expected 48, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, ptr->name);
|
||||
if (ptr->todo)
|
||||
todo_wine
|
||||
ok(dispid == ptr->did, "%s: expected %d, got %d\n", ptr->name, ptr->did, dispid);
|
||||
else
|
||||
ok(dispid == ptr->did, "%s: expected %d, got %d\n", ptr->name, ptr->did, dispid);
|
||||
ptr++;
|
||||
}
|
||||
|
||||
dispid = get_dispid(pInstaller, "RemovePatches");
|
||||
ok(dispid == 49 || dispid == -1, "Expected 49 or -1, got %d\n", dispid);
|
||||
dispid = get_dispid(pInstaller, "ApplyMultiplePatches");
|
||||
|
@ -1921,7 +1887,7 @@ static void test_Session(IDispatch *pSession)
|
|||
ok(hr == S_OK, "Session_ModePut failed, hresult 0x%08x\n", hr);
|
||||
|
||||
hr = Session_ModePut(pSession, MSIRUNMODE_REBOOTNOW, TRUE);
|
||||
todo_wine ok(hr == S_OK, "Session_ModePut failed, hresult 0x%08x\n", hr);
|
||||
ok(hr == S_OK, "Session_ModePut failed, hresult 0x%08x\n", hr);
|
||||
if (hr == DISP_E_EXCEPTION) ok_exception(hr, szModeFlag);
|
||||
|
||||
hr = Session_ModeGet(pSession, MSIRUNMODE_REBOOTNOW, &bool);
|
||||
|
@ -1929,7 +1895,7 @@ static void test_Session(IDispatch *pSession)
|
|||
ok(bool, "Reboot now mode is %d, expected 1\n", bool);
|
||||
|
||||
hr = Session_ModePut(pSession, MSIRUNMODE_REBOOTNOW, FALSE); /* set it again so we don't reboot */
|
||||
todo_wine ok(hr == S_OK, "Session_ModePut failed, hresult 0x%08x\n", hr);
|
||||
ok(hr == S_OK, "Session_ModePut failed, hresult 0x%08x\n", hr);
|
||||
if (hr == DISP_E_EXCEPTION) ok_exception(hr, szModeFlag);
|
||||
|
||||
hr = Session_ModePut(pSession, MSIRUNMODE_MAINTENANCE, TRUE);
|
||||
|
|
|
@ -923,6 +923,30 @@ static void test_viewmodify(void)
|
|||
r = MsiViewModify(hview, MSIMODIFY_INSERT_TEMPORARY, hrec );
|
||||
ok(r == ERROR_FUNCTION_FAILED, "MsiViewModify failed\n");
|
||||
|
||||
/* try to merge the same record */
|
||||
r = MsiViewExecute(hview, 0);
|
||||
ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
|
||||
r = MsiViewModify(hview, MSIMODIFY_MERGE, hrec );
|
||||
ok(r == ERROR_SUCCESS, "MsiViewModify failed\n");
|
||||
|
||||
r = MsiCloseHandle(hrec);
|
||||
ok(r == ERROR_SUCCESS, "failed to close record\n");
|
||||
|
||||
/* try merging a new record */
|
||||
hrec = MsiCreateRecord(3);
|
||||
|
||||
r = MsiRecordSetInteger(hrec, 1, 10);
|
||||
ok(r == ERROR_SUCCESS, "failed to set integer\n");
|
||||
r = MsiRecordSetString(hrec, 2, "pepe");
|
||||
ok(r == ERROR_SUCCESS, "failed to set string\n");
|
||||
r = MsiRecordSetString(hrec, 3, "7654321");
|
||||
ok(r == ERROR_SUCCESS, "failed to set string\n");
|
||||
|
||||
r = MsiViewModify(hview, MSIMODIFY_MERGE, hrec );
|
||||
ok(r == ERROR_SUCCESS, "MsiViewModify failed\n");
|
||||
r = MsiViewExecute(hview, 0);
|
||||
ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
|
||||
|
||||
r = MsiCloseHandle(hrec);
|
||||
ok(r == ERROR_SUCCESS, "failed to close record\n");
|
||||
|
||||
|
|
|
@ -55,6 +55,10 @@ static INSTALLSTATE (WINAPI *pMsiUseFeatureExA)
|
|||
(LPCSTR, LPCSTR ,DWORD, DWORD);
|
||||
static UINT (WINAPI *pMsiGetPatchInfoExA)
|
||||
(LPCSTR, LPCSTR, LPCSTR, MSIINSTALLCONTEXT, LPCSTR, LPSTR, DWORD *);
|
||||
static UINT (WINAPI *pMsiEnumProductsExA)
|
||||
(LPCSTR, LPCSTR, DWORD, DWORD, CHAR[39], MSIINSTALLCONTEXT *, LPSTR, LPDWORD);
|
||||
static UINT (WINAPI *pMsiEnumComponentsExA)
|
||||
(LPCSTR, DWORD, DWORD, CHAR[39], MSIINSTALLCONTEXT *, LPSTR, LPDWORD);
|
||||
|
||||
static void init_functionpointers(void)
|
||||
{
|
||||
|
@ -76,6 +80,8 @@ static void init_functionpointers(void)
|
|||
GET_PROC(hmsi, MsiQueryComponentStateA)
|
||||
GET_PROC(hmsi, MsiUseFeatureExA)
|
||||
GET_PROC(hmsi, MsiGetPatchInfoExA)
|
||||
GET_PROC(hmsi, MsiEnumProductsExA)
|
||||
GET_PROC(hmsi, MsiEnumComponentsExA)
|
||||
|
||||
GET_PROC(hadvapi32, ConvertSidToStringSidA)
|
||||
GET_PROC(hadvapi32, RegDeleteKeyExA)
|
||||
|
@ -411,6 +417,12 @@ static const struct
|
|||
MSIFILEHASHINFO hash;
|
||||
} hash_data[] =
|
||||
{
|
||||
{ "", 0,
|
||||
{ HASHSIZE,
|
||||
{ 0, 0, 0, 0 },
|
||||
},
|
||||
},
|
||||
|
||||
{ "abc", 0,
|
||||
{ HASHSIZE,
|
||||
{ 0x98500190, 0xb04fd23c, 0x7d3f96d6, 0x727fe128 },
|
||||
|
@ -538,8 +550,11 @@ static void create_test_guid(LPSTR prodcode, LPSTR squashed)
|
|||
ok(size == 39, "Expected 39, got %d\n", hr);
|
||||
|
||||
WideCharToMultiByte(CP_ACP, 0, guidW, size, prodcode, MAX_PATH, NULL, NULL);
|
||||
squash_guid(guidW, squashedW);
|
||||
WideCharToMultiByte(CP_ACP, 0, squashedW, -1, squashed, MAX_PATH, NULL, NULL);
|
||||
if (squashed)
|
||||
{
|
||||
squash_guid(guidW, squashedW);
|
||||
WideCharToMultiByte(CP_ACP, 0, squashedW, -1, squashed, MAX_PATH, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static char *get_user_sid(void)
|
||||
|
@ -1631,10 +1646,10 @@ static void test_MsiQueryComponentState(void)
|
|||
ok(state == INSTALLSTATE_SOURCE, "Expected INSTALLSTATE_SOURCE, got %d\n", state);
|
||||
ok(error == 0xdeadbeef, "expected 0xdeadbeef, got %u\n", error);
|
||||
|
||||
res = RegSetValueExA(compkey, prod_squashed, 0, REG_SZ, (const BYTE *)"01", 3);
|
||||
res = RegSetValueExA(compkey, prod_squashed, 0, REG_SZ, (const BYTE *)"01:", 4);
|
||||
ok(res == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", res);
|
||||
|
||||
/* bad INSTALLSTATE_SOURCE */
|
||||
/* registry component */
|
||||
state = MAGIC_ERROR;
|
||||
SetLastError(0xdeadbeef);
|
||||
r = pMsiQueryComponentStateA(prodcode, NULL, MSIINSTALLCONTEXT_MACHINE, component, &state);
|
||||
|
@ -11788,12 +11803,340 @@ static void test_MsiGetFileSignatureInformation(void)
|
|||
hr = MsiGetFileSignatureInformationA( "signature.bin", 0, NULL, NULL, &len );
|
||||
ok(hr == E_INVALIDARG, "expected E_INVALIDARG got 0x%08x\n", hr);
|
||||
|
||||
cert = (const CERT_CONTEXT *)0xdeadbeef;
|
||||
hr = MsiGetFileSignatureInformationA( "signature.bin", 0, &cert, NULL, &len );
|
||||
todo_wine ok(hr == HRESULT_FROM_WIN32(ERROR_FUNCTION_FAILED), "got 0x%08x\n", hr);
|
||||
ok(cert == NULL, "got %p\n", cert);
|
||||
|
||||
DeleteFileA( "signature.bin" );
|
||||
}
|
||||
|
||||
static void test_MsiEnumProductsEx(void)
|
||||
{
|
||||
UINT r;
|
||||
DWORD len, index;
|
||||
MSIINSTALLCONTEXT context;
|
||||
char product0[39], product1[39], product2[39], product3[39], guid[39], sid[128];
|
||||
char product_squashed1[33], product_squashed2[33], product_squashed3[33];
|
||||
char keypath1[MAX_PATH], keypath2[MAX_PATH], keypath3[MAX_PATH];
|
||||
HKEY key1 = NULL, key2 = NULL, key3 = NULL;
|
||||
REGSAM access = KEY_ALL_ACCESS;
|
||||
char *usersid = get_user_sid();
|
||||
|
||||
if (!pMsiEnumProductsExA)
|
||||
{
|
||||
win_skip("MsiEnumProductsExA not implemented\n");
|
||||
return;
|
||||
}
|
||||
|
||||
create_test_guid( product0, NULL );
|
||||
create_test_guid( product1, product_squashed1 );
|
||||
create_test_guid( product2, product_squashed2 );
|
||||
create_test_guid( product3, product_squashed3 );
|
||||
|
||||
if (is_wow64) access |= KEY_WOW64_64KEY;
|
||||
|
||||
strcpy( keypath1, "Software\\Classes\\Installer\\Products\\" );
|
||||
strcat( keypath1, product_squashed1 );
|
||||
|
||||
r = RegCreateKeyExA( HKEY_LOCAL_MACHINE, keypath1, 0, NULL, 0, access, NULL, &key1, NULL );
|
||||
if (r == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
skip( "insufficient rights\n" );
|
||||
goto done;
|
||||
}
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
|
||||
strcpy( keypath2, "Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\Managed\\" );
|
||||
strcat( keypath2, usersid );
|
||||
strcat( keypath2, "\\Installer\\Products\\" );
|
||||
strcat( keypath2, product_squashed2 );
|
||||
|
||||
r = RegCreateKeyExA( HKEY_LOCAL_MACHINE, keypath2, 0, NULL, 0, access, NULL, &key2, NULL );
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
|
||||
strcpy( keypath3, usersid );
|
||||
strcat( keypath3, "\\Software\\Microsoft\\Installer\\Products\\" );
|
||||
strcat( keypath3, product_squashed3 );
|
||||
|
||||
r = RegCreateKeyExA( HKEY_USERS, keypath3, 0, NULL, 0, access, NULL, &key3, NULL );
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
|
||||
r = pMsiEnumProductsExA( NULL, NULL, 0, 0, NULL, NULL, NULL, NULL );
|
||||
ok( r == ERROR_INVALID_PARAMETER, "got %u\n", r );
|
||||
|
||||
len = sizeof(sid);
|
||||
r = pMsiEnumProductsExA( NULL, NULL, 0, 0, NULL, NULL, NULL, &len );
|
||||
ok( r == ERROR_INVALID_PARAMETER, "got %u\n", r );
|
||||
ok( len == sizeof(sid), "got %u\n", len );
|
||||
|
||||
r = pMsiEnumProductsExA( NULL, NULL, MSIINSTALLCONTEXT_ALL, 0, NULL, NULL, NULL, NULL );
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
|
||||
sid[0] = 0;
|
||||
len = sizeof(sid);
|
||||
r = pMsiEnumProductsExA( product0, NULL, MSIINSTALLCONTEXT_ALL, 0, NULL, NULL, sid, &len );
|
||||
ok( r == ERROR_NO_MORE_ITEMS, "got %u\n", r );
|
||||
ok( len == sizeof(sid), "got %u\n", len );
|
||||
ok( !sid[0], "got %s\n", sid );
|
||||
|
||||
sid[0] = 0;
|
||||
len = sizeof(sid);
|
||||
r = pMsiEnumProductsExA( product0, usersid, MSIINSTALLCONTEXT_ALL, 0, NULL, NULL, sid, &len );
|
||||
ok( r == ERROR_NO_MORE_ITEMS, "got %u\n", r );
|
||||
ok( len == sizeof(sid), "got %u\n", len );
|
||||
ok( !sid[0], "got %s\n", sid );
|
||||
|
||||
sid[0] = 0;
|
||||
len = 0;
|
||||
r = pMsiEnumProductsExA( NULL, usersid, MSIINSTALLCONTEXT_USERUNMANAGED, 0, NULL, NULL, sid, &len );
|
||||
ok( r == ERROR_MORE_DATA, "got %u\n", r );
|
||||
ok( len, "length unchanged\n" );
|
||||
ok( !sid[0], "got %s\n", sid );
|
||||
|
||||
guid[0] = 0;
|
||||
context = 0xdeadbeef;
|
||||
sid[0] = 0;
|
||||
len = sizeof(sid);
|
||||
r = pMsiEnumProductsExA( NULL, NULL, MSIINSTALLCONTEXT_ALL, 0, guid, &context, sid, &len );
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
ok( guid[0], "empty guid\n" );
|
||||
ok( context != 0xdeadbeef, "context unchanged\n" );
|
||||
ok( !len, "got %u\n", len );
|
||||
ok( !sid[0], "got %s\n", sid );
|
||||
|
||||
guid[0] = 0;
|
||||
context = 0xdeadbeef;
|
||||
sid[0] = 0;
|
||||
len = sizeof(sid);
|
||||
r = pMsiEnumProductsExA( NULL, usersid, MSIINSTALLCONTEXT_ALL, 0, guid, &context, sid, &len );
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
ok( guid[0], "empty guid\n" );
|
||||
ok( context != 0xdeadbeef, "context unchanged\n" );
|
||||
ok( !len, "got %u\n", len );
|
||||
ok( !sid[0], "got %s\n", sid );
|
||||
|
||||
guid[0] = 0;
|
||||
context = 0xdeadbeef;
|
||||
sid[0] = 0;
|
||||
len = sizeof(sid);
|
||||
r = pMsiEnumProductsExA( NULL, "S-1-1-0", MSIINSTALLCONTEXT_ALL, 0, guid, &context, sid, &len );
|
||||
if (r == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
skip( "insufficient rights\n" );
|
||||
goto done;
|
||||
}
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
ok( guid[0], "empty guid\n" );
|
||||
ok( context != 0xdeadbeef, "context unchanged\n" );
|
||||
ok( !len, "got %u\n", len );
|
||||
ok( !sid[0], "got %s\n", sid );
|
||||
|
||||
index = 0;
|
||||
guid[0] = 0;
|
||||
context = 0xdeadbeef;
|
||||
sid[0] = 0;
|
||||
len = sizeof(sid);
|
||||
while (!pMsiEnumProductsExA( NULL, "S-1-1-0", MSIINSTALLCONTEXT_ALL, index, guid, &context, sid, &len ))
|
||||
{
|
||||
if (!strcmp( product1, guid ))
|
||||
{
|
||||
ok( context == MSIINSTALLCONTEXT_MACHINE, "got %u\n", context );
|
||||
ok( !sid[0], "got \"%s\"\n", sid );
|
||||
ok( !len, "unexpected length %u\n", len );
|
||||
}
|
||||
if (!strcmp( product2, guid ))
|
||||
{
|
||||
ok( context == MSIINSTALLCONTEXT_USERMANAGED, "got %u\n", context );
|
||||
ok( sid[0], "empty sid\n" );
|
||||
ok( len == strlen(sid), "unexpected length %u\n", len );
|
||||
}
|
||||
if (!strcmp( product3, guid ))
|
||||
{
|
||||
ok( context == MSIINSTALLCONTEXT_USERUNMANAGED, "got %u\n", context );
|
||||
ok( sid[0], "empty sid\n" );
|
||||
ok( len == strlen(sid), "unexpected length %u\n", len );
|
||||
}
|
||||
index++;
|
||||
guid[0] = 0;
|
||||
context = 0xdeadbeef;
|
||||
sid[0] = 0;
|
||||
len = sizeof(sid);
|
||||
}
|
||||
|
||||
done:
|
||||
delete_key( key1, "", access );
|
||||
delete_key( key2, "", access );
|
||||
delete_key( key3, "", access );
|
||||
RegCloseKey( key1 );
|
||||
RegCloseKey( key2 );
|
||||
RegCloseKey( key3 );
|
||||
LocalFree( usersid );
|
||||
}
|
||||
|
||||
static void test_MsiEnumComponents(void)
|
||||
{
|
||||
UINT r;
|
||||
int found1, found2;
|
||||
DWORD index;
|
||||
char comp1[39], comp2[39], guid[39];
|
||||
char comp_squashed1[33], comp_squashed2[33];
|
||||
char keypath1[MAX_PATH], keypath2[MAX_PATH];
|
||||
REGSAM access = KEY_ALL_ACCESS;
|
||||
char *usersid = get_user_sid();
|
||||
HKEY key1 = NULL, key2 = NULL;
|
||||
|
||||
create_test_guid( comp1, comp_squashed1 );
|
||||
create_test_guid( comp2, comp_squashed2 );
|
||||
|
||||
if (is_wow64) access |= KEY_WOW64_64KEY;
|
||||
|
||||
strcpy( keypath1, "Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\" );
|
||||
strcat( keypath1, "S-1-5-18\\Components\\" );
|
||||
strcat( keypath1, comp_squashed1 );
|
||||
|
||||
r = RegCreateKeyExA( HKEY_LOCAL_MACHINE, keypath1, 0, NULL, 0, access, NULL, &key1, NULL );
|
||||
if (r == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
skip( "insufficient rights\n" );
|
||||
goto done;
|
||||
}
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
|
||||
strcpy( keypath2, "Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\" );
|
||||
strcat( keypath2, usersid );
|
||||
strcat( keypath2, "\\Components\\" );
|
||||
strcat( keypath2, comp_squashed2 );
|
||||
|
||||
r = RegCreateKeyExA( HKEY_LOCAL_MACHINE, keypath2, 0, NULL, 0, access, NULL, &key2, NULL );
|
||||
if (r == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
skip( "insufficient rights\n" );
|
||||
goto done;
|
||||
}
|
||||
|
||||
r = MsiEnumComponentsA( 0, NULL );
|
||||
ok( r == ERROR_INVALID_PARAMETER, "got %u\n", r );
|
||||
|
||||
index = 0;
|
||||
guid[0] = 0;
|
||||
found1 = found2 = 0;
|
||||
while (!MsiEnumComponentsA( index, guid ))
|
||||
{
|
||||
if (!strcmp( guid, comp1 )) found1 = 1;
|
||||
if (!strcmp( guid, comp2 )) found2 = 1;
|
||||
ok( guid[0], "empty guid\n" );
|
||||
guid[0] = 0;
|
||||
index++;
|
||||
}
|
||||
ok( found1, "comp1 not found\n" );
|
||||
ok( found2, "comp2 not found\n" );
|
||||
|
||||
done:
|
||||
delete_key( key1, "", access );
|
||||
delete_key( key2, "", access );
|
||||
RegCloseKey( key1 );
|
||||
RegCloseKey( key2 );
|
||||
LocalFree( usersid );
|
||||
}
|
||||
|
||||
static void test_MsiEnumComponentsEx(void)
|
||||
{
|
||||
UINT r;
|
||||
int found1, found2;
|
||||
DWORD len, index;
|
||||
MSIINSTALLCONTEXT context;
|
||||
char comp1[39], comp2[39], guid[39], sid[128];
|
||||
char comp_squashed1[33], comp_squashed2[33];
|
||||
char keypath1[MAX_PATH], keypath2[MAX_PATH];
|
||||
HKEY key1 = NULL, key2 = NULL;
|
||||
REGSAM access = KEY_ALL_ACCESS;
|
||||
char *usersid = get_user_sid();
|
||||
|
||||
if (!pMsiEnumComponentsExA)
|
||||
{
|
||||
win_skip( "MsiEnumComponentsExA not implemented\n" );
|
||||
return;
|
||||
}
|
||||
create_test_guid( comp1, comp_squashed1 );
|
||||
create_test_guid( comp2, comp_squashed2 );
|
||||
|
||||
if (is_wow64) access |= KEY_WOW64_64KEY;
|
||||
|
||||
strcpy( keypath1, "Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\" );
|
||||
strcat( keypath1, "S-1-5-18\\Components\\" );
|
||||
strcat( keypath1, comp_squashed1 );
|
||||
|
||||
r = RegCreateKeyExA( HKEY_LOCAL_MACHINE, keypath1, 0, NULL, 0, access, NULL, &key1, NULL );
|
||||
if (r == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
skip( "insufficient rights\n" );
|
||||
goto done;
|
||||
}
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
|
||||
strcpy( keypath2, "Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\" );
|
||||
strcat( keypath2, usersid );
|
||||
strcat( keypath2, "\\Components\\" );
|
||||
strcat( keypath2, comp_squashed2 );
|
||||
|
||||
r = RegCreateKeyExA( HKEY_LOCAL_MACHINE, keypath2, 0, NULL, 0, access, NULL, &key2, NULL );
|
||||
if (r == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
skip( "insufficient rights\n" );
|
||||
goto done;
|
||||
}
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
r = RegSetValueExA( key2, comp_squashed2, 0, REG_SZ, (const BYTE *)"c:\\doesnotexist",
|
||||
sizeof("c:\\doesnotexist"));
|
||||
ok( r == ERROR_SUCCESS, "got %u\n", r );
|
||||
|
||||
index = 0;
|
||||
guid[0] = 0;
|
||||
context = 0xdeadbeef;
|
||||
sid[0] = 0;
|
||||
len = sizeof(sid);
|
||||
found1 = found2 = 0;
|
||||
while (!pMsiEnumComponentsExA( "S-1-1-0", MSIINSTALLCONTEXT_ALL, index, guid, &context, sid, &len ))
|
||||
{
|
||||
if (!strcmp( comp1, guid ))
|
||||
{
|
||||
ok( context == MSIINSTALLCONTEXT_MACHINE, "got %u\n", context );
|
||||
ok( !sid[0], "got \"%s\"\n", sid );
|
||||
ok( !len, "unexpected length %u\n", len );
|
||||
found1 = 1;
|
||||
}
|
||||
if (!strcmp( comp2, guid ))
|
||||
{
|
||||
ok( context == MSIINSTALLCONTEXT_USERUNMANAGED, "got %u\n", context );
|
||||
ok( sid[0], "empty sid\n" );
|
||||
ok( len == strlen(sid), "unexpected length %u\n", len );
|
||||
found2 = 1;
|
||||
}
|
||||
index++;
|
||||
guid[0] = 0;
|
||||
context = 0xdeadbeef;
|
||||
sid[0] = 0;
|
||||
len = sizeof(sid);
|
||||
}
|
||||
ok( found1, "comp1 not found\n" );
|
||||
ok( found2, "comp2 not found\n" );
|
||||
|
||||
r = pMsiEnumComponentsExA( NULL, 0, 0, NULL, NULL, NULL, NULL );
|
||||
ok( r == ERROR_INVALID_PARAMETER, "got %u\n", r );
|
||||
|
||||
r = pMsiEnumComponentsExA( NULL, MSIINSTALLCONTEXT_ALL, 0, NULL, NULL, sid, NULL );
|
||||
ok( r == ERROR_INVALID_PARAMETER, "got %u\n", r );
|
||||
|
||||
done:
|
||||
RegDeleteValueA( key2, comp_squashed2 );
|
||||
delete_key( key1, "", access );
|
||||
delete_key( key2, "", access );
|
||||
RegCloseKey( key1 );
|
||||
RegCloseKey( key2 );
|
||||
LocalFree( usersid );
|
||||
}
|
||||
|
||||
START_TEST(msi)
|
||||
{
|
||||
init_functionpointers();
|
||||
|
@ -11826,6 +12169,9 @@ START_TEST(msi)
|
|||
test_MsiGetPatchInfoEx();
|
||||
test_MsiGetPatchInfo();
|
||||
test_MsiEnumProducts();
|
||||
test_MsiEnumProductsEx();
|
||||
test_MsiEnumComponents();
|
||||
test_MsiEnumComponentsEx();
|
||||
}
|
||||
test_MsiGetFileVersion();
|
||||
test_MsiGetFileSignatureInformation();
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue