msi: Schedule a rename operation when the file to overwrite is in use.
This commit is contained in:
parent
93c949eba5
commit
9c8b83ce01
|
@ -880,6 +880,9 @@ UINT MSI_InstallPackage( MSIPACKAGE *package, LPCWSTR szPackagePath,
|
||||||
/* finish up running custom actions */
|
/* finish up running custom actions */
|
||||||
ACTION_FinishCustomActions(package);
|
ACTION_FinishCustomActions(package);
|
||||||
|
|
||||||
|
if (rc == ERROR_SUCCESS && package->need_reboot)
|
||||||
|
return ERROR_SUCCESS_REBOOT_REQUIRED;
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,7 @@ static UINT copy_file(MSIFILE *file, LPWSTR source)
|
||||||
return ERROR_SUCCESS;
|
return ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static UINT copy_install_file(MSIFILE *file, LPWSTR source)
|
static UINT copy_install_file(MSIPACKAGE *package, MSIFILE *file, LPWSTR source)
|
||||||
{
|
{
|
||||||
UINT gle;
|
UINT gle;
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ static UINT copy_install_file(MSIFILE *file, LPWSTR source)
|
||||||
if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
|
if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
|
||||||
{
|
{
|
||||||
TRACE("overwriting existing file\n");
|
TRACE("overwriting existing file\n");
|
||||||
gle = ERROR_SUCCESS;
|
return ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
else if (gle == ERROR_ACCESS_DENIED)
|
else if (gle == ERROR_ACCESS_DENIED)
|
||||||
{
|
{
|
||||||
|
@ -162,6 +162,39 @@ static UINT copy_install_file(MSIFILE *file, LPWSTR source)
|
||||||
gle = copy_file(file, source);
|
gle = copy_file(file, source);
|
||||||
TRACE("Overwriting existing file: %d\n", gle);
|
TRACE("Overwriting existing file: %d\n", gle);
|
||||||
}
|
}
|
||||||
|
if (gle == ERROR_SHARING_VIOLATION)
|
||||||
|
{
|
||||||
|
static const WCHAR msiW[] = {'m','s','i',0};
|
||||||
|
static const WCHAR slashW[] = {'\\',0};
|
||||||
|
WCHAR tmpfileW[MAX_PATH], *pathW, *p;
|
||||||
|
DWORD len;
|
||||||
|
|
||||||
|
TRACE("file in use, scheduling rename operation\n");
|
||||||
|
|
||||||
|
GetTempFileNameW(slashW, msiW, 0, tmpfileW);
|
||||||
|
len = strlenW(file->TargetPath) + strlenW(tmpfileW) + 1;
|
||||||
|
if (!(pathW = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR))))
|
||||||
|
return ERROR_OUTOFMEMORY;
|
||||||
|
|
||||||
|
strcpyW(pathW, file->TargetPath);
|
||||||
|
if ((p = strrchrW(pathW, '\\'))) *p = 0;
|
||||||
|
strcatW(pathW, tmpfileW);
|
||||||
|
|
||||||
|
if (CopyFileW(source, pathW, FALSE) &&
|
||||||
|
MoveFileExW(file->TargetPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) &&
|
||||||
|
MoveFileExW(pathW, file->TargetPath, MOVEFILE_DELAY_UNTIL_REBOOT))
|
||||||
|
{
|
||||||
|
file->state = msifs_installed;
|
||||||
|
package->need_reboot = 1;
|
||||||
|
gle = ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gle = GetLastError();
|
||||||
|
WARN("failed to schedule rename operation: %d)\n", gle);
|
||||||
|
}
|
||||||
|
HeapFree(GetProcessHeap(), 0, pathW);
|
||||||
|
}
|
||||||
|
|
||||||
return gle;
|
return gle;
|
||||||
}
|
}
|
||||||
|
@ -296,7 +329,7 @@ UINT ACTION_InstallFiles(MSIPACKAGE *package)
|
||||||
debugstr_w(file->TargetPath));
|
debugstr_w(file->TargetPath));
|
||||||
|
|
||||||
msi_file_update_ui(package, file, szInstallFiles);
|
msi_file_update_ui(package, file, szInstallFiles);
|
||||||
rc = copy_install_file(file, source);
|
rc = copy_install_file(package, file, source);
|
||||||
if (rc != ERROR_SUCCESS)
|
if (rc != ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
ERR("Failed to copy %s to %s (%d)\n", debugstr_w(source),
|
ERR("Failed to copy %s to %s (%d)\n", debugstr_w(source),
|
||||||
|
|
|
@ -338,15 +338,52 @@ static INT_PTR cabinet_copy_file(FDINOTIFICATIONTYPE fdint,
|
||||||
if (handle == INVALID_HANDLE_VALUE)
|
if (handle == INVALID_HANDLE_VALUE)
|
||||||
{
|
{
|
||||||
DWORD err = GetLastError();
|
DWORD err = GetLastError();
|
||||||
DWORD attrs = GetFileAttributesW(path);
|
DWORD attrs2 = GetFileAttributesW(path);
|
||||||
|
|
||||||
if (attrs == INVALID_FILE_ATTRIBUTES)
|
if (attrs2 == INVALID_FILE_ATTRIBUTES)
|
||||||
|
{
|
||||||
ERR("failed to create %s (error %d)\n", debugstr_w(path), err);
|
ERR("failed to create %s (error %d)\n", debugstr_w(path), err);
|
||||||
else if (err == ERROR_ACCESS_DENIED && (attrs & FILE_ATTRIBUTE_READONLY))
|
goto done;
|
||||||
|
}
|
||||||
|
else if (err == ERROR_ACCESS_DENIED && (attrs2 & FILE_ATTRIBUTE_READONLY))
|
||||||
{
|
{
|
||||||
TRACE("removing read-only attribute on %s\n", debugstr_w(path));
|
TRACE("removing read-only attribute on %s\n", debugstr_w(path));
|
||||||
SetFileAttributesW( path, attrs & ~FILE_ATTRIBUTE_READONLY );
|
SetFileAttributesW( path, attrs2 & ~FILE_ATTRIBUTE_READONLY );
|
||||||
handle = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, attrs, NULL);
|
handle = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, attrs2, NULL);
|
||||||
|
|
||||||
|
if (handle != INVALID_HANDLE_VALUE) goto done;
|
||||||
|
err = GetLastError();
|
||||||
|
}
|
||||||
|
if (err == ERROR_SHARING_VIOLATION)
|
||||||
|
{
|
||||||
|
static const WCHAR msiW[] = {'m','s','i',0};
|
||||||
|
static const WCHAR slashW[] = {'\\',0};
|
||||||
|
WCHAR tmpfileW[MAX_PATH], *tmppathW, *p;
|
||||||
|
DWORD len;
|
||||||
|
|
||||||
|
TRACE("file in use, scheduling rename operation\n");
|
||||||
|
|
||||||
|
GetTempFileNameW(slashW, msiW, 0, tmpfileW);
|
||||||
|
len = strlenW(path) + strlenW(tmpfileW) + 1;
|
||||||
|
if (!(tmppathW = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR))))
|
||||||
|
return ERROR_OUTOFMEMORY;
|
||||||
|
|
||||||
|
strcpyW(tmppathW, path);
|
||||||
|
if ((p = strrchrW(tmppathW, '\\'))) *p = 0;
|
||||||
|
strcatW(tmppathW, tmpfileW);
|
||||||
|
|
||||||
|
handle = CreateFileW(tmppathW, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, attrs, NULL);
|
||||||
|
|
||||||
|
if (handle != INVALID_HANDLE_VALUE &&
|
||||||
|
MoveFileExW(path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) &&
|
||||||
|
MoveFileExW(tmppathW, path, MOVEFILE_DELAY_UNTIL_REBOOT))
|
||||||
|
{
|
||||||
|
data->package->need_reboot = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
WARN("failed to schedule rename operation %s (error %d)\n", debugstr_w(path), GetLastError());
|
||||||
|
|
||||||
|
HeapFree(GetProcessHeap(), 0, tmppathW);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
WARN("failed to create %s (error %d)\n", debugstr_w(path), err);
|
WARN("failed to create %s (error %d)\n", debugstr_w(path), err);
|
||||||
|
|
|
@ -340,6 +340,7 @@ typedef struct tagMSIPACKAGE
|
||||||
unsigned char scheduled_action_running : 1;
|
unsigned char scheduled_action_running : 1;
|
||||||
unsigned char commit_action_running : 1;
|
unsigned char commit_action_running : 1;
|
||||||
unsigned char rollback_action_running : 1;
|
unsigned char rollback_action_running : 1;
|
||||||
|
unsigned char need_reboot : 1;
|
||||||
} MSIPACKAGE;
|
} MSIPACKAGE;
|
||||||
|
|
||||||
typedef struct tagMSIPREVIEW
|
typedef struct tagMSIPREVIEW
|
||||||
|
|
|
@ -1551,6 +1551,18 @@ static const msi_table fiu_tables[] =
|
||||||
ADD_TABLE(property),
|
ADD_TABLE(property),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const msi_table fiuc_tables[] =
|
||||||
|
{
|
||||||
|
ADD_TABLE(rof_component),
|
||||||
|
ADD_TABLE(directory),
|
||||||
|
ADD_TABLE(rof_feature),
|
||||||
|
ADD_TABLE(rof_feature_comp),
|
||||||
|
ADD_TABLE(rofc_file),
|
||||||
|
ADD_TABLE(pp_install_exec_seq),
|
||||||
|
ADD_TABLE(rofc_media),
|
||||||
|
ADD_TABLE(property),
|
||||||
|
};
|
||||||
|
|
||||||
/* cabinet definitions */
|
/* cabinet definitions */
|
||||||
|
|
||||||
/* make the max size large so there is only one cab file */
|
/* make the max size large so there is only one cab file */
|
||||||
|
@ -6689,20 +6701,74 @@ static void test_installed_prop(void)
|
||||||
delete_test_files();
|
delete_test_files();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char session_manager[] = "System\\CurrentControlSet\\Control\\Session Manager";
|
||||||
|
static char rename_ops[] = "PendingFileRenameOperations";
|
||||||
|
|
||||||
|
static void process_pending_renames(HKEY hkey)
|
||||||
|
{
|
||||||
|
char *buf, *src, *dst;
|
||||||
|
DWORD size;
|
||||||
|
LONG ret;
|
||||||
|
|
||||||
|
ret = RegQueryValueExA(hkey, rename_ops, NULL, NULL, NULL, &size);
|
||||||
|
buf = HeapAlloc(GetProcessHeap(), 0, size);
|
||||||
|
buf[0] = 0;
|
||||||
|
|
||||||
|
ret = RegQueryValueExA(hkey, rename_ops, NULL, NULL, (LPBYTE)buf, &size);
|
||||||
|
ok(!ret, "RegQueryValueExA failed %d (%u)\n", ret, GetLastError());
|
||||||
|
ok(strstr(buf, "msitest\\maximus") != NULL, "Unexpected value \"%s\"\n", buf);
|
||||||
|
|
||||||
|
for (src = buf; *src; src = dst + strlen(dst) + 1)
|
||||||
|
{
|
||||||
|
DWORD flags = MOVEFILE_COPY_ALLOWED;
|
||||||
|
|
||||||
|
dst = src + strlen(src) + 1;
|
||||||
|
if (*dst == '!')
|
||||||
|
{
|
||||||
|
flags |= MOVEFILE_REPLACE_EXISTING;
|
||||||
|
dst++;
|
||||||
|
}
|
||||||
|
if (src[0] == '\\' && src[1] == '?' && src[2] == '?' && src[3] == '\\') src += 4;
|
||||||
|
if (*dst)
|
||||||
|
{
|
||||||
|
if (dst[0] == '\\' && dst[1] == '?' && dst[2] == '?' && dst[3] == '\\') dst += 4;
|
||||||
|
ok(MoveFileExA(src, dst, flags), "Failed to move file %s -> %s (%u)\n", src, dst, GetLastError());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ok(DeleteFileA(src), "Failed to delete file %s (%u)\n", src, GetLastError());
|
||||||
|
}
|
||||||
|
HeapFree(GetProcessHeap(), 0, buf);
|
||||||
|
RegDeleteValueA(hkey, rename_ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL file_matches_data(LPCSTR file, LPCSTR data)
|
||||||
|
{
|
||||||
|
DWORD len, data_len = strlen(data);
|
||||||
|
HANDLE handle;
|
||||||
|
char buf[128];
|
||||||
|
|
||||||
|
handle = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
||||||
|
ok(handle != INVALID_HANDLE_VALUE, "failed to open %s (%u)\n", file, GetLastError());
|
||||||
|
|
||||||
|
if (ReadFile(handle, buf, sizeof(buf), &len, NULL) && len >= data_len)
|
||||||
|
{
|
||||||
|
CloseHandle(handle);
|
||||||
|
return !memcmp(buf, data, data_len);
|
||||||
|
}
|
||||||
|
CloseHandle(handle);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
static void test_file_in_use(void)
|
static void test_file_in_use(void)
|
||||||
{
|
{
|
||||||
UINT r;
|
UINT r;
|
||||||
DWORD size;
|
DWORD size;
|
||||||
HANDLE file;
|
HANDLE file;
|
||||||
HKEY hkey;
|
HKEY hkey;
|
||||||
LONG ret;
|
char path[MAX_PATH];
|
||||||
char path[MAX_PATH], *buf, *src, *dst;
|
|
||||||
|
|
||||||
static char key[] = "System\\CurrentControlSet\\Control\\Session Manager";
|
RegOpenKeyExA(HKEY_LOCAL_MACHINE, session_manager, 0, KEY_ALL_ACCESS, &hkey);
|
||||||
static char value[] = "PendingFileRenameOperations";
|
if (!RegQueryValueExA(hkey, rename_ops, NULL, NULL, NULL, &size))
|
||||||
|
|
||||||
RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_ALL_ACCESS, &hkey);
|
|
||||||
if (!RegQueryValueExA(hkey, value, NULL, NULL, NULL, &size))
|
|
||||||
{
|
{
|
||||||
skip("Pending file rename operations, skipping test\n");
|
skip("Pending file rename operations, skipping test\n");
|
||||||
return;
|
return;
|
||||||
|
@ -6722,44 +6788,17 @@ static void test_file_in_use(void)
|
||||||
file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
|
file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
|
||||||
r = MsiInstallProductA(msifile, "REBOOT=ReallySuppress FULL=1");
|
r = MsiInstallProductA(msifile, "REBOOT=ReallySuppress FULL=1");
|
||||||
todo_wine ok(r == ERROR_SUCCESS_REBOOT_REQUIRED, "Expected ERROR_SUCCESS_REBOOT_REQUIRED got %u\n", r);
|
ok(r == ERROR_SUCCESS_REBOOT_REQUIRED, "Expected ERROR_SUCCESS_REBOOT_REQUIRED got %u\n", r);
|
||||||
ok(!file_matches(path), "Expected file not to match\n");
|
ok(!file_matches_data(path, "msitest\\maximus"), "Expected file not to match\n");
|
||||||
CloseHandle(file);
|
CloseHandle(file);
|
||||||
|
ok(!file_matches_data(path, "msitest\\maximus"), "Expected file not to match\n");
|
||||||
|
|
||||||
ret = RegQueryValueExA(hkey, value, NULL, NULL, NULL, &size);
|
process_pending_renames(hkey);
|
||||||
buf = HeapAlloc(GetProcessHeap(), 0, size);
|
|
||||||
|
|
||||||
buf[0] = 0;
|
|
||||||
ret = RegQueryValueExA(hkey, value, NULL, NULL, (LPBYTE)buf, &size);
|
|
||||||
todo_wine ok(!ret, "RegQueryValueExA failed %d (%u)\n", ret, GetLastError());
|
|
||||||
todo_wine ok(strstr(buf, "msitest\\maximus") != NULL, "Unexpected value \"%s\"\n", buf);
|
|
||||||
|
|
||||||
for (src = buf; *src; src = dst + strlen(dst) + 1)
|
|
||||||
{
|
|
||||||
DWORD flags = MOVEFILE_COPY_ALLOWED;
|
|
||||||
|
|
||||||
dst = src + strlen(src) + 1;
|
|
||||||
if (*dst == '!')
|
|
||||||
{
|
|
||||||
flags |= MOVEFILE_REPLACE_EXISTING;
|
|
||||||
dst++;
|
|
||||||
}
|
|
||||||
src += strlen("\\??\\");
|
|
||||||
if (*dst)
|
|
||||||
{
|
|
||||||
dst += strlen("\\??\\");
|
|
||||||
ok(MoveFileExA(src, dst, flags), "Failed to move file %s -> %s (%u)\n", src, dst, GetLastError());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ok(DeleteFileA(src), "Failed to delete file %s (%u)\n", src, GetLastError());
|
|
||||||
}
|
|
||||||
HeapFree(GetProcessHeap(), 0, buf);
|
|
||||||
RegDeleteValueA(hkey, value);
|
|
||||||
RegCloseKey(hkey);
|
RegCloseKey(hkey);
|
||||||
|
|
||||||
todo_wine ok(file_matches(path), "Expected file to match\n");
|
ok(file_matches_data(path, "msitest\\maximus"), "Expected file to match\n");
|
||||||
ok(delete_pf("msitest\\maximus", TRUE), "File not present\n");
|
ok(delete_pf("msitest\\maximus", TRUE), "File not present\n");
|
||||||
ok(delete_pf("msitest", FALSE), "Directory not present\n");
|
ok(delete_pf("msitest", FALSE), "Directory not present or not empty\n");
|
||||||
|
|
||||||
r = MsiInstallProductA(msifile, "REMOVE=ALL");
|
r = MsiInstallProductA(msifile, "REMOVE=ALL");
|
||||||
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
|
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
|
||||||
|
@ -6767,6 +6806,57 @@ static void test_file_in_use(void)
|
||||||
delete_test_files();
|
delete_test_files();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_file_in_use_cab(void)
|
||||||
|
{
|
||||||
|
UINT r;
|
||||||
|
DWORD size;
|
||||||
|
HANDLE file;
|
||||||
|
HKEY hkey;
|
||||||
|
char path[MAX_PATH];
|
||||||
|
|
||||||
|
RegOpenKeyExA(HKEY_LOCAL_MACHINE, session_manager, 0, KEY_ALL_ACCESS, &hkey);
|
||||||
|
if (!RegQueryValueExA(hkey, rename_ops, NULL, NULL, NULL, &size))
|
||||||
|
{
|
||||||
|
skip("Pending file rename operations, skipping test\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateDirectoryA("msitest", NULL);
|
||||||
|
create_file("maximus", 500);
|
||||||
|
create_cab_file("test1.cab", MEDIA_SIZE, "maximus\0");
|
||||||
|
DeleteFile("maximus");
|
||||||
|
|
||||||
|
create_database(msifile, fiuc_tables, sizeof(fiuc_tables) / sizeof(msi_table));
|
||||||
|
|
||||||
|
MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL);
|
||||||
|
|
||||||
|
lstrcpy(path, PROG_FILES_DIR);
|
||||||
|
lstrcat(path, "\\msitest");
|
||||||
|
CreateDirectoryA(path, NULL);
|
||||||
|
|
||||||
|
lstrcat(path, "\\maximus");
|
||||||
|
file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
|
||||||
|
r = MsiInstallProductA(msifile, "REBOOT=ReallySuppress FULL=1");
|
||||||
|
ok(r == ERROR_SUCCESS_REBOOT_REQUIRED, "Expected ERROR_SUCCESS_REBOOT_REQUIRED got %u\n", r);
|
||||||
|
ok(!file_matches_data(path, "maximus"), "Expected file not to match\n");
|
||||||
|
CloseHandle(file);
|
||||||
|
ok(!file_matches_data(path, "maximus"), "Expected file not to match\n");
|
||||||
|
|
||||||
|
process_pending_renames(hkey);
|
||||||
|
RegCloseKey(hkey);
|
||||||
|
|
||||||
|
ok(file_matches_data(path, "maximus"), "Expected file to match\n");
|
||||||
|
ok(delete_pf("msitest\\maximus", TRUE), "File not present\n");
|
||||||
|
ok(delete_pf("msitest", FALSE), "Directory not present or not empty\n");
|
||||||
|
|
||||||
|
r = MsiInstallProductA(msifile, "REMOVE=ALL");
|
||||||
|
ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
|
||||||
|
|
||||||
|
delete_cab_files();
|
||||||
|
delete_test_files();
|
||||||
|
}
|
||||||
|
|
||||||
START_TEST(install)
|
START_TEST(install)
|
||||||
{
|
{
|
||||||
DWORD len;
|
DWORD len;
|
||||||
|
@ -6854,6 +6944,7 @@ START_TEST(install)
|
||||||
test_preselected();
|
test_preselected();
|
||||||
test_installed_prop();
|
test_installed_prop();
|
||||||
test_file_in_use();
|
test_file_in_use();
|
||||||
|
test_file_in_use_cab();
|
||||||
|
|
||||||
DeleteFileA(log_file);
|
DeleteFileA(log_file);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue