9d67a89f7b036d736f834f7a9b2293e7a7824324
[reactos.git] / reactos / dll / win32 / kernel32 / client / file / move.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * FILE: lib/kernel32/file/file.c
5 * PURPOSE: Directory functions
6 * PROGRAMMER: Ariadne ( ariadne@xs4all.nl)
7 * Gerhard W. Gruber (sparhawk_at_gmx.at)
8 * Dmitry Philippov (shedon@mail.ru)
9 * Pierre Schweitzer (pierre@reactos.org)
10 * UPDATE HISTORY:
11 * Created 01/11/98
12 * DP (29/07/2006)
13 * Fix some bugs in the add_boot_rename_entry function
14 */
15
16 /* INCLUDES *****************************************************************/
17
18 #include <k32.h>
19 #include <malloc.h>
20 #include <strsafe.h>
21 #define NDEBUG
22 #include <debug.h>
23 DEBUG_CHANNEL(kernel32file);
24
25 /* GLOBALS *****************************************************************/
26
27 /* DEFINES *****************************************************************/
28 typedef struct _COPY_PROGRESS_CONTEXT
29 {
30 ULONG Flags;
31 LPPROGRESS_ROUTINE UserRoutine;
32 LPVOID UserData;
33 } COPY_PROGRESS_CONTEXT, *PCOPY_PROGRESS_CONTEXT;
34
35 /* FUNCTIONS ****************************************************************/
36 /*
37 * @implemented
38 */
39 NTSTATUS
40 WINAPI
41 BasepMoveFileDelayed(IN PUNICODE_STRING ExistingPath,
42 IN PUNICODE_STRING NewPath,
43 IN INT KeyId,
44 IN BOOL CreateIfNotFound)
45 {
46 #define STRING_LENGTH 0x400
47 NTSTATUS Status;
48 HANDLE KeyHandle;
49 PVOID Buffer, BufferBegin;
50 OBJECT_ATTRIBUTES ObjectAttributes;
51 PWSTR PendingOperations, BufferWrite;
52 ULONG DataSize, BufferLength, StringLength = STRING_LENGTH;
53 UNICODE_STRING SessionManagerString, PendingOperationsString;
54 /* +6 because a INT shouldn't take more than 6 chars. Especially given the call path */
55 WCHAR PendingOperationsBuffer[sizeof(L"PendingFileRenameOperations") / sizeof(WCHAR) + 6];
56
57 RtlInitUnicodeString(&SessionManagerString, L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Session Manager");
58
59 /* Select appropriate key for adding our file */
60 if (KeyId == 1)
61 {
62 PendingOperations = L"PendingFileRenameOperations";
63 }
64 else
65 {
66 StringCbPrintfW(PendingOperationsBuffer, sizeof(PendingOperationsBuffer), L"PendingFileRenameOperations%d", KeyId);
67 PendingOperations = PendingOperationsBuffer;
68 }
69 RtlInitUnicodeString(&PendingOperationsString, PendingOperations);
70
71 InitializeObjectAttributes(&ObjectAttributes,
72 &SessionManagerString,
73 OBJ_OPENIF | OBJ_CASE_INSENSITIVE,
74 NULL, NULL);
75
76 /* Open parent key */
77 Status = NtCreateKey(&KeyHandle,
78 GENERIC_READ | GENERIC_WRITE,
79 &ObjectAttributes, 0, NULL,
80 REG_OPTION_NON_VOLATILE, NULL);
81 if (Status == STATUS_ACCESS_DENIED)
82 {
83 Status = NtCreateKey(&KeyHandle,
84 GENERIC_READ | GENERIC_WRITE,
85 &ObjectAttributes, 0, NULL,
86 REG_OPTION_BACKUP_RESTORE, NULL);
87 }
88
89 if (!NT_SUCCESS(Status))
90 {
91 return Status;
92 }
93
94 /* Reserve enough to read previous string + to append our with required null chars */
95 BufferLength = NewPath->Length + ExistingPath->Length + STRING_LENGTH + 3 * sizeof(UNICODE_NULL);
96
97 while (TRUE)
98 {
99 /* Allocate output buffer */
100 Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, BufferLength);
101 if (Buffer == NULL)
102 {
103 NtClose(KeyHandle);
104 return STATUS_NO_MEMORY;
105 }
106
107 Status = NtQueryValueKey(KeyHandle,
108 &PendingOperationsString,
109 KeyValuePartialInformation,
110 Buffer, StringLength, &DataSize);
111 if (Status != STATUS_BUFFER_OVERFLOW)
112 {
113 break;
114 }
115
116 /* If buffer was too small, then, reallocate one which is big enough */
117 StringLength = DataSize;
118 RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
119 BufferLength = ExistingPath->Length + StringLength + NewPath->Length + 3 * sizeof(UNICODE_NULL);
120 /* Check we didn't overflow */
121 if (BufferLength < StringLength)
122 {
123 NtClose(KeyHandle);
124 return STATUS_BUFFER_TOO_SMALL;
125 }
126 }
127
128 /* Check if it existed - if not, create only IF asked to */
129 if (!NT_SUCCESS(Status) && (Status != STATUS_OBJECT_NAME_NOT_FOUND || !CreateIfNotFound))
130 {
131 NtClose(KeyHandle);
132 RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
133 return Status;
134 }
135
136 if (!NT_SUCCESS(Status))
137 {
138 /* We didn't find any - ie, we create, so use complete buffer */
139 BufferBegin = Buffer;
140 BufferWrite = Buffer;
141 }
142 else
143 {
144 PKEY_VALUE_PARTIAL_INFORMATION PartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION)Buffer;
145
146 /* Get data, our buffer begin and then where we should append data
147 * (- null char, this is REG_MULTI_SZ, it already includes double termination, we keep only one)
148 */
149 BufferBegin = PartialInfo->Data;
150 BufferWrite = (PWSTR)((ULONG_PTR)PartialInfo->Data + PartialInfo->DataLength - sizeof(UNICODE_NULL));
151 }
152
153 /* First copy existing */
154 RtlCopyMemory(BufferWrite, ExistingPath->Buffer, ExistingPath->Length);
155 BufferWrite += ExistingPath->Length / sizeof(WCHAR);
156 /* And append null char */
157 *BufferWrite = UNICODE_NULL;
158 ++BufferWrite;
159 /* Append destination */
160 RtlCopyMemory(BufferWrite, NewPath->Buffer, NewPath->Length);
161 BufferWrite += NewPath->Length / sizeof(WCHAR);
162 /* And append two null char (end of string) */
163 *BufferWrite = UNICODE_NULL;
164 ++BufferWrite;
165 *BufferWrite = UNICODE_NULL;
166
167 /* Set new value */
168 Status = NtSetValueKey(KeyHandle,
169 &PendingOperationsString,
170 0, REG_MULTI_SZ, BufferBegin,
171 (ULONG_PTR)BufferWrite - (ULONG_PTR)BufferBegin + sizeof(WCHAR));
172
173 NtClose(KeyHandle);
174 RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
175
176 return Status;
177 }
178
179
180 /*
181 * @unimplemented
182 */
183 NTSTATUS
184 WINAPI
185 BasepNotifyTrackingService(IN PHANDLE ExistingHandle,
186 IN POBJECT_ATTRIBUTES ObjectAttributes,
187 IN HANDLE NewHandle,
188 IN PUNICODE_STRING NewPath)
189 {
190 return STATUS_NOT_IMPLEMENTED;
191 }
192
193
194 /*
195 * @implemented
196 */
197 DWORD
198 WINAPI
199 BasepMoveFileCopyProgress(IN LARGE_INTEGER TotalFileSize,
200 IN LARGE_INTEGER TotalBytesTransferred,
201 IN LARGE_INTEGER StreamSize,
202 IN LARGE_INTEGER StreamBytesTransferred,
203 IN DWORD dwStreamNumber,
204 IN DWORD dwCallbackReason,
205 IN HANDLE hSourceFile,
206 IN HANDLE hDestinationFile,
207 IN LPVOID lpData OPTIONAL)
208 {
209 DWORD Ret = 0;
210 PCOPY_PROGRESS_CONTEXT Context = (PCOPY_PROGRESS_CONTEXT)lpData;
211
212 if (Context->Flags & MOVEFILE_WRITE_THROUGH)
213 {
214 if (!dwCallbackReason)
215 {
216 if (StreamBytesTransferred.QuadPart == StreamSize.QuadPart)
217 {
218 FlushFileBuffers(hDestinationFile);
219 }
220 }
221 }
222
223 if (Context->UserRoutine)
224 {
225 Ret = Context->UserRoutine(TotalFileSize,
226 TotalBytesTransferred,
227 StreamSize,
228 StreamBytesTransferred,
229 dwStreamNumber,
230 dwCallbackReason,
231 hSourceFile,
232 hDestinationFile,
233 Context->UserData);
234 }
235
236 return Ret;
237 }
238
239
240 /*
241 * @implemented
242 */
243 BOOL
244 WINAPI
245 MoveFileWithProgressW(IN LPCWSTR lpExistingFileName,
246 IN LPCWSTR lpNewFileName,
247 IN LPPROGRESS_ROUTINE lpProgressRoutine,
248 IN LPVOID lpData,
249 IN DWORD dwFlags)
250 {
251 NTSTATUS Status;
252 PWSTR NewBuffer;
253 IO_STATUS_BLOCK IoStatusBlock;
254 COPY_PROGRESS_CONTEXT CopyContext;
255 OBJECT_ATTRIBUTES ObjectAttributes;
256 PFILE_RENAME_INFORMATION RenameInfo;
257 UNICODE_STRING NewPathU, ExistingPathU;
258 FILE_ATTRIBUTE_TAG_INFORMATION FileAttrTagInfo;
259 HANDLE SourceHandle = INVALID_HANDLE_VALUE, NewHandle, ExistingHandle;
260 BOOL Ret = FALSE, ReplaceIfExists, DelayUntilReboot, AttemptReopenWithoutReparse;
261
262 DPRINT("MoveFileWithProgressW(%S, %S, %p, %p, %x)\n", lpExistingFileName, lpNewFileName, lpProgressRoutine, lpData, dwFlags);
263
264 NewPathU.Buffer = NULL;
265 ExistingPathU.Buffer = NULL;
266
267 _SEH2_TRY
268 {
269 /* Don't allow renaming to a disk */
270 if (lpNewFileName && RtlIsDosDeviceName_U(lpNewFileName))
271 {
272 BaseSetLastNTError(STATUS_OBJECT_NAME_COLLISION);
273 _SEH2_LEAVE;
274 }
275
276 ReplaceIfExists = !!(dwFlags & MOVEFILE_REPLACE_EXISTING);
277
278 /* Get file path */
279 if (!RtlDosPathNameToNtPathName_U(lpExistingFileName, &ExistingPathU, NULL, NULL))
280 {
281 BaseSetLastNTError(STATUS_OBJECT_PATH_NOT_FOUND);
282 _SEH2_LEAVE;
283 }
284
285 /* Sanitize input */
286 DelayUntilReboot = !!(dwFlags & MOVEFILE_DELAY_UNTIL_REBOOT);
287 if (DelayUntilReboot && (dwFlags & MOVEFILE_CREATE_HARDLINK))
288 {
289 BaseSetLastNTError(STATUS_INVALID_PARAMETER);
290 _SEH2_LEAVE;
291 }
292
293 /* Unless we manage a proper opening, we'll attempt to reopen without reparse support */
294 AttemptReopenWithoutReparse = TRUE;
295 InitializeObjectAttributes(&ObjectAttributes,
296 &ExistingPathU,
297 OBJ_CASE_INSENSITIVE,
298 NULL,
299 NULL);
300 /* Attempt to open source file */
301 Status = NtOpenFile(&SourceHandle,
302 FILE_READ_ATTRIBUTES | DELETE | SYNCHRONIZE,
303 &ObjectAttributes,
304 &IoStatusBlock,
305 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
306 FILE_OPEN_FOR_BACKUP_INTENT | ((dwFlags & MOVEFILE_WRITE_THROUGH) ? FILE_WRITE_THROUGH : 0));
307 if (!NT_SUCCESS(Status))
308 {
309 /* If we failed and the file doesn't exist, don't attempt to reopen without reparse */
310 if (DelayUntilReboot &&
311 (Status == STATUS_SHARING_VIOLATION || Status == STATUS_OBJECT_NAME_NOT_FOUND || Status == STATUS_OBJECT_PATH_NOT_FOUND))
312 {
313 /* Here we don't fail completely, as we postpone the operation to reboot
314 * File might exist afterwards, and we don't need a handle here
315 */
316 SourceHandle = INVALID_HANDLE_VALUE;
317 AttemptReopenWithoutReparse = FALSE;
318 }
319 /* If we failed for any reason than unsupported reparse, fail completely */
320 else if (Status != STATUS_INVALID_PARAMETER)
321 {
322 BaseSetLastNTError(Status);
323 _SEH2_LEAVE;
324 }
325 }
326 else
327 {
328 /* We managed to open, so query information */
329 Status = NtQueryInformationFile(SourceHandle,
330 &IoStatusBlock,
331 &FileAttrTagInfo,
332 sizeof(FILE_ATTRIBUTE_TAG_INFORMATION),
333 FileAttributeTagInformation);
334 if (!NT_SUCCESS(Status))
335 {
336 /* Do not tolerate any other error than something related to not supported operation */
337 if (Status != STATUS_NOT_IMPLEMENTED && Status != STATUS_INVALID_PARAMETER)
338 {
339 BaseSetLastNTError(Status);
340 _SEH2_LEAVE;
341 }
342
343 /* Not a reparse point, no need to reopen, it's fine */
344 AttemptReopenWithoutReparse = FALSE;
345 }
346 /* Validate the reparse point (do we support it?) */
347 else if (FileAttrTagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
348 FileAttrTagInfo.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
349 {
350 NtClose(SourceHandle);
351 SourceHandle = INVALID_HANDLE_VALUE;
352 }
353 }
354
355 /* Simply reopen if required */
356 if (AttemptReopenWithoutReparse)
357 {
358 Status = NtOpenFile(&SourceHandle,
359 DELETE | SYNCHRONIZE,
360 &ObjectAttributes,
361 &IoStatusBlock,
362 FILE_SHARE_READ | FILE_SHARE_WRITE,
363 ((dwFlags & MOVEFILE_WRITE_THROUGH) ? FILE_WRITE_THROUGH : 0));
364 if (!NT_SUCCESS(Status))
365 {
366 BaseSetLastNTError(Status);
367 _SEH2_LEAVE;
368 }
369 }
370
371 /* Nullify string if we're to use it */
372 if (DelayUntilReboot && !lpNewFileName)
373 {
374 RtlInitUnicodeString(&NewPathU, 0);
375 }
376 /* Check whether path exists */
377 else if (!RtlDosPathNameToNtPathName_U(lpNewFileName, &NewPathU, 0, 0))
378 {
379 BaseSetLastNTError(STATUS_OBJECT_PATH_NOT_FOUND);
380 _SEH2_LEAVE;
381 }
382
383 /* Handle postponed renaming */
384 if (DelayUntilReboot)
385 {
386 /* If new file exists and we're allowed to replace, then mark the path with ! */
387 if (ReplaceIfExists && NewPathU.Length)
388 {
389 NewBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, NewPathU.Length + sizeof(WCHAR));
390 if (NewBuffer == NULL)
391 {
392 BaseSetLastNTError(STATUS_NO_MEMORY);
393 _SEH2_LEAVE;
394 }
395
396 NewBuffer[0] = L'!';
397 RtlCopyMemory(&NewBuffer[1], NewPathU.Buffer, NewPathU.Length);
398 NewPathU.Length += sizeof(WCHAR);
399 NewPathU.MaximumLength += sizeof(WCHAR);
400 RtlFreeHeap(RtlGetProcessHeap(), 0, NewPathU.Buffer);
401 NewPathU.Buffer = NewBuffer;
402 }
403
404 /* Check whether 'copy' renaming is allowed if required */
405 if (RtlDetermineDosPathNameType_U(lpExistingFileName) == RtlPathTypeUncAbsolute || dwFlags & MOVEFILE_COPY_ALLOWED)
406 {
407 Status = STATUS_INVALID_PARAMETER;
408 }
409 else
410 {
411 /* First, probe 2nd key to see whether it exists - if so, it will be appended there */
412 Status = BasepMoveFileDelayed(&ExistingPathU, &NewPathU, 2, FALSE);
413 if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
414 {
415 /* If doesn't exist, append to first key first, creating it if it doesn't exist */
416 Status = BasepMoveFileDelayed(&ExistingPathU, &NewPathU, 1, TRUE);
417
418 if (Status == STATUS_INSUFFICIENT_RESOURCES)
419 {
420 /* If it failed because it's too big, then create 2nd key and put it there */
421 Status = BasepMoveFileDelayed(&ExistingPathU, &NewPathU, 2, TRUE);
422 }
423 }
424 }
425
426 /* If we failed at some point, return the error */
427 if (!NT_SUCCESS(Status))
428 {
429 BaseSetLastNTError(Status);
430 _SEH2_LEAVE;
431 }
432
433 Ret = TRUE;
434 _SEH2_LEAVE;
435 }
436
437 /* At that point, we MUST have a source handle */
438 ASSERT(SourceHandle != INVALID_HANDLE_VALUE);
439
440 /* Allocate renaming buffer and fill it */
441 RenameInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0, NewPathU.Length + sizeof(FILE_RENAME_INFORMATION));
442 if (RenameInfo == NULL)
443 {
444 BaseSetLastNTError(STATUS_NO_MEMORY);
445 _SEH2_LEAVE;
446 }
447
448 RtlCopyMemory(&RenameInfo->FileName, NewPathU.Buffer, NewPathU.Length);
449 RenameInfo->ReplaceIfExists = ReplaceIfExists;
450 RenameInfo->RootDirectory = 0;
451 RenameInfo->FileNameLength = NewPathU.Length;
452
453 /* Attempt to rename the file */
454 Status = NtSetInformationFile(SourceHandle,
455 &IoStatusBlock,
456 RenameInfo,
457 NewPathU.Length + sizeof(FILE_RENAME_INFORMATION),
458 ((dwFlags & MOVEFILE_CREATE_HARDLINK) ? FileLinkInformation : FileRenameInformation));
459 RtlFreeHeap(RtlGetProcessHeap(), 0, RenameInfo);
460 if (NT_SUCCESS(Status))
461 {
462 /* If it succeed, all fine, quit */
463 Ret = TRUE;
464 _SEH2_LEAVE;
465 }
466 /* If we failed for any other reason than not the same device, fail
467 * If we failed because of different devices, only allow renaming if user allowed copy
468 */
469 if (Status != STATUS_NOT_SAME_DEVICE || !(dwFlags & MOVEFILE_COPY_ALLOWED))
470 {
471 /* ReactOS hack! To be removed once all FSD have proper renaming support
472 * Just leave status to error and leave
473 */
474 if (Status == STATUS_NOT_IMPLEMENTED)
475 {
476 DPRINT1("Forcing copy, renaming not supported by FSD\n");
477 }
478 else
479 {
480 BaseSetLastNTError(Status);
481 _SEH2_LEAVE;
482 }
483 }
484
485 /* Close source file */
486 NtClose(SourceHandle);
487 SourceHandle = INVALID_HANDLE_VALUE;
488
489 /* Issue the copy of the file */
490 CopyContext.Flags = dwFlags;
491 CopyContext.UserRoutine = lpProgressRoutine;
492 CopyContext.UserData = lpData;
493 NewHandle = INVALID_HANDLE_VALUE;
494 ExistingHandle = INVALID_HANDLE_VALUE;
495
496 Ret = BasepCopyFileExW(lpExistingFileName,
497 lpNewFileName,
498 BasepMoveFileCopyProgress,
499 &CopyContext,
500 NULL,
501 (ReplaceIfExists == 0) | COPY_FILE_OPEN_SOURCE_FOR_WRITE,
502 0,
503 &ExistingHandle,
504 &NewHandle);
505 if (!Ret)
506 {
507 /* If it failed, don't leak any handle */
508 if (ExistingHandle != INVALID_HANDLE_VALUE)
509 {
510 CloseHandle(ExistingHandle);
511 ExistingHandle = INVALID_HANDLE_VALUE;
512 }
513 }
514 else if (ExistingHandle != INVALID_HANDLE_VALUE)
515 {
516 if (NewHandle != INVALID_HANDLE_VALUE)
517 {
518 /* If copying succeed, notify */
519 Status = BasepNotifyTrackingService(&ExistingHandle, &ObjectAttributes, NewHandle, &NewPathU);
520 if (!NT_SUCCESS(Status))
521 {
522 /* Fail in case it had to succeed */
523 if (dwFlags & MOVEFILE_FAIL_IF_NOT_TRACKABLE)
524 {
525 if (NewHandle != INVALID_HANDLE_VALUE)
526 CloseHandle(NewHandle);
527 NewHandle = INVALID_HANDLE_VALUE;
528 DeleteFileW(lpNewFileName);
529 Ret = FALSE;
530 BaseSetLastNTError(Status);
531 }
532 }
533 }
534
535 CloseHandle(ExistingHandle);
536 ExistingHandle = INVALID_HANDLE_VALUE;
537 }
538
539 /* In case copy worked, close file */
540 if (NewHandle != INVALID_HANDLE_VALUE)
541 {
542 CloseHandle(NewHandle);
543 NewHandle = INVALID_HANDLE_VALUE;
544 }
545
546 /* If it succeed, delete source file */
547 if (Ret)
548 {
549 if (!DeleteFileW(lpExistingFileName))
550 {
551 /* Reset file attributes if required */
552 SetFileAttributesW(lpExistingFileName, FILE_ATTRIBUTE_NORMAL);
553 DeleteFileW(lpExistingFileName);
554 }
555 }
556 }
557 _SEH2_FINALLY
558 {
559 if (SourceHandle != INVALID_HANDLE_VALUE)
560 NtClose(SourceHandle);
561
562 RtlFreeHeap(RtlGetProcessHeap(), 0, ExistingPathU.Buffer);
563 RtlFreeHeap(RtlGetProcessHeap(), 0, NewPathU.Buffer);
564 }
565 _SEH2_END;
566
567 return Ret;
568 }
569
570
571 /*
572 * @implemented
573 */
574 BOOL
575 WINAPI
576 MoveFileWithProgressA(IN LPCSTR lpExistingFileName,
577 IN LPCSTR lpNewFileName OPTIONAL,
578 IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
579 IN LPVOID lpData OPTIONAL,
580 IN DWORD dwFlags)
581 {
582 BOOL Ret;
583 UNICODE_STRING ExistingFileNameW, NewFileNameW;
584
585 if (!Basep8BitStringToDynamicUnicodeString(&ExistingFileNameW, lpExistingFileName))
586 {
587 return FALSE;
588 }
589
590 if (lpNewFileName)
591 {
592 if (!Basep8BitStringToDynamicUnicodeString(&NewFileNameW, lpNewFileName))
593 {
594 RtlFreeUnicodeString(&ExistingFileNameW);
595 return FALSE;
596 }
597 }
598 else
599 {
600 NewFileNameW.Buffer = NULL;
601 }
602
603 Ret = MoveFileWithProgressW(ExistingFileNameW.Buffer, NewFileNameW.Buffer, lpProgressRoutine, lpData, dwFlags);
604
605 RtlFreeUnicodeString(&ExistingFileNameW);
606 RtlFreeUnicodeString(&NewFileNameW);
607
608 return Ret;
609 }
610
611
612 /*
613 * @implemented
614 */
615 BOOL
616 WINAPI
617 MoveFileW(IN LPCWSTR lpExistingFileName,
618 IN LPCWSTR lpNewFileName)
619 {
620 return MoveFileWithProgressW(lpExistingFileName,
621 lpNewFileName,
622 NULL,
623 NULL,
624 MOVEFILE_COPY_ALLOWED);
625 }
626
627
628 /*
629 * @implemented
630 */
631 BOOL
632 WINAPI
633 MoveFileExW(IN LPCWSTR lpExistingFileName,
634 IN LPCWSTR lpNewFileName OPTIONAL,
635 IN DWORD dwFlags)
636 {
637 return MoveFileWithProgressW(lpExistingFileName,
638 lpNewFileName,
639 NULL,
640 NULL,
641 dwFlags);
642 }
643
644
645 /*
646 * @implemented
647 */
648 BOOL
649 WINAPI
650 MoveFileA(IN LPCSTR lpExistingFileName,
651 IN LPCSTR lpNewFileName)
652 {
653 return MoveFileWithProgressA(lpExistingFileName,
654 lpNewFileName,
655 NULL,
656 NULL,
657 MOVEFILE_COPY_ALLOWED);
658 }
659
660
661 /*
662 * @implemented
663 */
664 BOOL
665 WINAPI
666 MoveFileExA(IN LPCSTR lpExistingFileName,
667 IN LPCSTR lpNewFileName OPTIONAL,
668 IN DWORD dwFlags)
669 {
670 return MoveFileWithProgressA(lpExistingFileName,
671 lpNewFileName,
672 NULL,
673 NULL,
674 dwFlags);
675 }
676
677 /*
678 * @implemented
679 */
680 BOOL
681 WINAPI
682 ReplaceFileA(IN LPCSTR lpReplacedFileName,
683 IN LPCSTR lpReplacementFileName,
684 IN LPCSTR lpBackupFileName OPTIONAL,
685 IN DWORD dwReplaceFlags,
686 IN LPVOID lpExclude,
687 IN LPVOID lpReserved)
688 {
689 BOOL Ret;
690 UNICODE_STRING ReplacedFileNameW, ReplacementFileNameW, BackupFileNameW;
691
692 if (!lpReplacedFileName || !lpReplacementFileName || lpExclude || lpReserved || dwReplaceFlags & ~(REPLACEFILE_WRITE_THROUGH | REPLACEFILE_IGNORE_MERGE_ERRORS))
693 {
694 SetLastError(ERROR_INVALID_PARAMETER);
695 return FALSE;
696 }
697
698 if (!Basep8BitStringToDynamicUnicodeString(&ReplacedFileNameW, lpReplacedFileName))
699 {
700 return FALSE;
701 }
702
703 if (!Basep8BitStringToDynamicUnicodeString(&ReplacementFileNameW, lpReplacementFileName))
704 {
705 RtlFreeUnicodeString(&ReplacedFileNameW);
706 return FALSE;
707 }
708
709 if (lpBackupFileName)
710 {
711 if (!Basep8BitStringToDynamicUnicodeString(&BackupFileNameW, lpBackupFileName))
712 {
713 RtlFreeUnicodeString(&ReplacementFileNameW);
714 RtlFreeUnicodeString(&ReplacedFileNameW);
715 return FALSE;
716 }
717 }
718 else
719 {
720 BackupFileNameW.Buffer = NULL;
721 }
722
723 Ret = ReplaceFileW(ReplacedFileNameW.Buffer, ReplacementFileNameW.Buffer, BackupFileNameW.Buffer, dwReplaceFlags, 0, 0);
724
725 if (lpBackupFileName)
726 {
727 RtlFreeUnicodeString(&BackupFileNameW);
728 }
729 RtlFreeUnicodeString(&ReplacementFileNameW);
730 RtlFreeUnicodeString(&ReplacedFileNameW);
731
732 return Ret;
733 }
734
735 /*
736 * @unimplemented
737 */
738 BOOL
739 WINAPI
740 ReplaceFileW(
741 LPCWSTR lpReplacedFileName,
742 LPCWSTR lpReplacementFileName,
743 LPCWSTR lpBackupFileName,
744 DWORD dwReplaceFlags,
745 LPVOID lpExclude,
746 LPVOID lpReserved
747 )
748 {
749 HANDLE hReplaced = NULL, hReplacement = NULL;
750 UNICODE_STRING NtReplacedName = { 0, 0, NULL };
751 UNICODE_STRING NtReplacementName = { 0, 0, NULL };
752 DWORD Error = ERROR_SUCCESS;
753 NTSTATUS Status;
754 BOOL Ret = FALSE;
755 IO_STATUS_BLOCK IoStatusBlock;
756 OBJECT_ATTRIBUTES ObjectAttributes;
757 PVOID Buffer = NULL ;
758
759 if (dwReplaceFlags)
760 FIXME("Ignoring flags %x\n", dwReplaceFlags);
761
762 /* First two arguments are mandatory */
763 if (!lpReplacedFileName || !lpReplacementFileName)
764 {
765 SetLastError(ERROR_INVALID_PARAMETER);
766 return FALSE;
767 }
768
769 /* Back it up */
770 if(lpBackupFileName)
771 {
772 if(!CopyFileW(lpReplacedFileName, lpBackupFileName, FALSE))
773 {
774 Error = GetLastError();
775 goto Cleanup ;
776 }
777 }
778
779 /* Open the "replaced" file for reading and writing */
780 if (!(RtlDosPathNameToNtPathName_U(lpReplacedFileName, &NtReplacedName, NULL, NULL)))
781 {
782 Error = ERROR_PATH_NOT_FOUND;
783 goto Cleanup;
784 }
785
786 InitializeObjectAttributes(&ObjectAttributes,
787 &NtReplacedName,
788 OBJ_CASE_INSENSITIVE,
789 NULL,
790 NULL);
791
792 Status = NtOpenFile(&hReplaced,
793 GENERIC_READ | GENERIC_WRITE | DELETE | SYNCHRONIZE | WRITE_DAC,
794 &ObjectAttributes,
795 &IoStatusBlock,
796 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
797 FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
798
799 if (!NT_SUCCESS(Status))
800 {
801 if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
802 Error = ERROR_FILE_NOT_FOUND;
803 else
804 Error = ERROR_UNABLE_TO_REMOVE_REPLACED;
805 goto Cleanup;
806 }
807
808 /* Blank it */
809 SetEndOfFile(hReplaced) ;
810
811 /*
812 * Open the replacement file for reading, writing, and deleting
813 * (deleting is needed when finished)
814 */
815 if (!(RtlDosPathNameToNtPathName_U(lpReplacementFileName, &NtReplacementName, NULL, NULL)))
816 {
817 Error = ERROR_PATH_NOT_FOUND;
818 goto Cleanup;
819 }
820
821 InitializeObjectAttributes(&ObjectAttributes,
822 &NtReplacementName,
823 OBJ_CASE_INSENSITIVE,
824 NULL,
825 NULL);
826
827 Status = NtOpenFile(&hReplacement,
828 GENERIC_READ | DELETE | SYNCHRONIZE,
829 &ObjectAttributes,
830 &IoStatusBlock,
831 0,
832 FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE);
833
834 if (!NT_SUCCESS(Status))
835 {
836 Error = RtlNtStatusToDosError(Status);
837 goto Cleanup;
838 }
839
840 Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, 0x10000) ;
841 if (!Buffer)
842 {
843 Error = ERROR_NOT_ENOUGH_MEMORY;
844 goto Cleanup ;
845 }
846 while (Status != STATUS_END_OF_FILE)
847 {
848 Status = NtReadFile(hReplacement, NULL, NULL, NULL, &IoStatusBlock, Buffer, 0x10000, NULL, NULL) ;
849 if (NT_SUCCESS(Status))
850 {
851 Status = NtWriteFile(hReplaced, NULL, NULL, NULL, &IoStatusBlock, Buffer,
852 IoStatusBlock.Information, NULL, NULL) ;
853 if (!NT_SUCCESS(Status))
854 {
855 Error = RtlNtStatusToDosError(Status);
856 goto Cleanup;
857 }
858 }
859 else if (Status != STATUS_END_OF_FILE)
860 {
861 Error = RtlNtStatusToDosError(Status);
862 goto Cleanup;
863 }
864 }
865
866 Ret = TRUE;
867
868 /* Perform resource cleanup */
869 Cleanup:
870 if (hReplaced) NtClose(hReplaced);
871 if (hReplacement) NtClose(hReplacement);
872 if (Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
873
874 if (NtReplacementName.Buffer)
875 RtlFreeHeap(GetProcessHeap(), 0, NtReplacementName.Buffer);
876 if (NtReplacedName.Buffer)
877 RtlFreeHeap(GetProcessHeap(), 0, NtReplacedName.Buffer);
878
879 /* If there was an error, set the error code */
880 if(!Ret)
881 {
882 TRACE("ReplaceFileW failed (error=%lu)\n", Error);
883 SetLastError(Error);
884 }
885 return Ret;
886 }
887
888 /*
889 * @implemented
890 */
891 NTSTATUS
892 WINAPI
893 BasepOpenFileForMove(IN LPCWSTR File,
894 OUT PUNICODE_STRING RelativeNtName,
895 OUT LPWSTR * NtName,
896 OUT PHANDLE FileHandle,
897 OUT POBJECT_ATTRIBUTES ObjectAttributes,
898 IN ACCESS_MASK DesiredAccess,
899 IN ULONG ShareAccess,
900 IN ULONG OpenOptions)
901 {
902 RTL_RELATIVE_NAME_U RelativeName;
903 NTSTATUS Status;
904 IO_STATUS_BLOCK IoStatusBlock;
905 FILE_ATTRIBUTE_TAG_INFORMATION TagInfo;
906 ULONG IntShareAccess;
907 BOOLEAN HasRelative = FALSE;
908
909 _SEH2_TRY
910 {
911 /* Zero output */
912 RelativeNtName->Length =
913 RelativeNtName->MaximumLength = 0;
914 RelativeNtName->Buffer = NULL;
915 *NtName = NULL;
916
917 if (!RtlDosPathNameToRelativeNtPathName_U(File, RelativeNtName, NULL, &RelativeName))
918 {
919 Status = STATUS_OBJECT_PATH_NOT_FOUND;
920 _SEH2_LEAVE;
921 }
922
923 HasRelative = TRUE;
924 *NtName = RelativeNtName->Buffer;
925
926 if (RelativeName.RelativeName.Length)
927 {
928 RelativeNtName->Length = RelativeName.RelativeName.Length;
929 RelativeNtName->MaximumLength = RelativeName.RelativeName.MaximumLength;
930 RelativeNtName->Buffer = RelativeName.RelativeName.Buffer;
931 }
932 else
933 {
934 RelativeName.ContainingDirectory = 0;
935 }
936
937 InitializeObjectAttributes(ObjectAttributes,
938 RelativeNtName,
939 OBJ_CASE_INSENSITIVE,
940 RelativeName.ContainingDirectory,
941 NULL);
942 /* Force certain flags here, given ops we'll do */
943 IntShareAccess = ShareAccess | FILE_SHARE_READ | FILE_SHARE_WRITE;
944 OpenOptions |= FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT;
945
946 /* We'll try to read reparse tag */
947 Status = NtOpenFile(FileHandle,
948 DesiredAccess | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
949 ObjectAttributes,
950 &IoStatusBlock,
951 IntShareAccess,
952 OpenOptions | FILE_OPEN_REPARSE_POINT);
953 if (NT_SUCCESS(Status))
954 {
955 /* Attempt the read */
956 Status = NtQueryInformationFile(*FileHandle,
957 &IoStatusBlock,
958 &TagInfo,
959 sizeof(FILE_ATTRIBUTE_TAG_INFORMATION),
960 FileAttributeTagInformation);
961
962 /* Return if failure with a status that wouldn't mean the FSD cannot support reparse points */
963 if (!NT_SUCCESS(Status) &&
964 (Status != STATUS_NOT_IMPLEMENTED && Status != STATUS_INVALID_PARAMETER))
965 {
966 _SEH2_LEAVE;
967 }
968
969 if (NT_SUCCESS(Status))
970 {
971 /* This cannot happen on mount points */
972 if (TagInfo.FileAttributes & FILE_ATTRIBUTE_DEVICE ||
973 TagInfo.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
974 {
975 _SEH2_LEAVE;
976 }
977 }
978
979 NtClose(*FileHandle);
980 *FileHandle = INVALID_HANDLE_VALUE;
981
982 IntShareAccess = ShareAccess | FILE_SHARE_READ | FILE_SHARE_DELETE;
983 }
984 else if (Status == STATUS_INVALID_PARAMETER)
985 {
986 IntShareAccess = ShareAccess | FILE_SHARE_READ | FILE_SHARE_WRITE;
987 }
988 else
989 {
990 _SEH2_LEAVE;
991 }
992
993 /* Reattempt to open normally, following reparse point if needed */
994 Status = NtOpenFile(FileHandle,
995 DesiredAccess | SYNCHRONIZE,
996 ObjectAttributes,
997 &IoStatusBlock,
998 IntShareAccess,
999 OpenOptions);
1000 }
1001 _SEH2_FINALLY
1002 {
1003 if (HasRelative)
1004 {
1005 RtlReleaseRelativeName(&RelativeName);
1006 }
1007 }
1008 _SEH2_END;
1009
1010 return Status;
1011 }
1012
1013 /*
1014 * @implemented
1015 */
1016 BOOL
1017 WINAPI
1018 PrivMoveFileIdentityW(IN LPCWSTR lpSource, IN LPCWSTR lpDestination, IN DWORD dwFlags)
1019 {
1020 ACCESS_MASK SourceAccess;
1021 UNICODE_STRING NtSource, NtDestination;
1022 LPWSTR RelativeSource, RelativeDestination;
1023 HANDLE SourceHandle, DestinationHandle;
1024 OBJECT_ATTRIBUTES ObjectAttributesSource, ObjectAttributesDestination;
1025 NTSTATUS Status, OldStatus = STATUS_SUCCESS;
1026 ACCESS_MASK DestAccess;
1027 IO_STATUS_BLOCK IoStatusBlock;
1028 FILE_BASIC_INFORMATION SourceInformation, DestinationInformation;
1029 FILE_DISPOSITION_INFORMATION FileDispositionInfo;
1030
1031 DPRINT("PrivMoveFileIdentityW(%S, %S, %x)\n", lpSource, lpDestination, dwFlags);
1032
1033 SourceHandle = INVALID_HANDLE_VALUE;
1034 NtSource.Length =
1035 NtSource.MaximumLength = 0;
1036 NtSource.Buffer = NULL;
1037 RelativeSource = NULL;
1038 DestinationHandle = INVALID_HANDLE_VALUE;
1039 NtDestination.Length =
1040 NtDestination.MaximumLength = 0;
1041 NtDestination.Buffer = NULL;
1042 RelativeDestination = NULL;
1043
1044 /* FILE_WRITE_DATA is required for later on notification */
1045 SourceAccess = FILE_READ_ATTRIBUTES | FILE_WRITE_DATA;
1046 if (dwFlags & PRIV_DELETE_ON_SUCCESS)
1047 {
1048 SourceAccess |= DELETE;
1049 }
1050
1051 _SEH2_TRY
1052 {
1053 /* We will loop twice:
1054 * First we attempt to open with FILE_WRITE_DATA for notification
1055 * If it fails and we have flag for non-trackable files, we retry
1056 * without FILE_WRITE_DATA.
1057 * If that one fails, then, we quit for real
1058 */
1059 while (TRUE)
1060 {
1061 Status = BasepOpenFileForMove(lpSource,
1062 &NtSource,
1063 &RelativeSource,
1064 &SourceHandle,
1065 &ObjectAttributesSource,
1066 SourceAccess,
1067 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1068 FILE_OPEN_NO_RECALL);
1069 if (NT_SUCCESS(Status))
1070 {
1071 break;
1072 }
1073
1074 /* If we already attempted the opening without FILE_WRITE_DATA
1075 * or if we cannot move on non-trackable files, fail.
1076 */
1077 if (!(SourceAccess & FILE_WRITE_DATA) || !(dwFlags & PRIV_ALLOW_NON_TRACKABLE))
1078 {
1079 _SEH2_LEAVE;
1080 }
1081
1082 if (RelativeSource)
1083 {
1084 RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeSource);
1085 RelativeSource = NULL;
1086 }
1087
1088 if (SourceHandle != INVALID_HANDLE_VALUE)
1089 {
1090 NtClose(SourceHandle);
1091 SourceHandle = INVALID_HANDLE_VALUE;
1092 }
1093
1094 SourceAccess &= ~FILE_WRITE_DATA;
1095
1096 /* Remember fist failure in the path */
1097 if (NT_SUCCESS(OldStatus))
1098 {
1099 OldStatus = Status;
1100 }
1101 }
1102
1103 DestAccess = FILE_WRITE_ATTRIBUTES;
1104 /* If we could preserve FILE_WRITE_DATA for source, attempt to get it for destination
1105 * Still for notification purposes
1106 */
1107 if (SourceAccess & FILE_WRITE_DATA)
1108 {
1109 DestAccess |= FILE_WRITE_DATA;
1110 }
1111
1112 /* cf comment for first loop */
1113 while (TRUE)
1114 {
1115 Status = BasepOpenFileForMove(lpDestination,
1116 &NtDestination,
1117 &RelativeDestination,
1118 &DestinationHandle,
1119 &ObjectAttributesDestination,
1120 DestAccess,
1121 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1122 FILE_OPEN_NO_RECALL);
1123 if (NT_SUCCESS(Status))
1124 {
1125 break;
1126 }
1127
1128 /* If we already attempted the opening without FILE_WRITE_DATA
1129 * or if we cannot move on non-trackable files, fail.
1130 */
1131 if (!(DestAccess & FILE_WRITE_DATA) || !(dwFlags & PRIV_ALLOW_NON_TRACKABLE))
1132 {
1133 _SEH2_LEAVE;
1134 }
1135
1136 if (RelativeDestination)
1137 {
1138 RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeDestination);
1139 RelativeDestination = NULL;
1140 }
1141
1142 if (DestinationHandle != INVALID_HANDLE_VALUE)
1143 {
1144 NtClose(DestinationHandle);
1145 DestinationHandle = INVALID_HANDLE_VALUE;
1146 }
1147
1148 DestAccess &= ~FILE_WRITE_DATA;
1149
1150 /* Remember fist failure in the path */
1151 if (NT_SUCCESS(OldStatus))
1152 {
1153 OldStatus = Status;
1154 }
1155 }
1156
1157 /* Get the creation time from source */
1158 Status = NtQueryInformationFile(SourceHandle,
1159 &IoStatusBlock,
1160 &SourceInformation,
1161 sizeof(SourceInformation),
1162 FileBasicInformation);
1163 if (NT_SUCCESS(Status))
1164 {
1165 /* Then, prepare to set it for destination */
1166 RtlZeroMemory(&DestinationInformation, sizeof(DestinationInformation));
1167 DestinationInformation.CreationTime.QuadPart = SourceInformation.CreationTime.QuadPart;
1168
1169 /* And set it, that's all folks! */
1170 Status = NtSetInformationFile(DestinationHandle,
1171 &IoStatusBlock,
1172 &DestinationInformation,
1173 sizeof(DestinationInformation),
1174 FileBasicInformation);
1175 }
1176
1177 if (!NT_SUCCESS(Status))
1178 {
1179 if (!(dwFlags & PRIV_ALLOW_NON_TRACKABLE))
1180 {
1181 _SEH2_LEAVE;
1182 }
1183
1184 /* Remember the failure for later notification */
1185 if (NT_SUCCESS(OldStatus))
1186 {
1187 OldStatus = Status;
1188 }
1189 }
1190
1191 /* If we could open with FILE_WRITE_DATA both source and destination,
1192 * then, notify
1193 */
1194 if (DestAccess & FILE_WRITE_DATA && SourceAccess & FILE_WRITE_DATA)
1195 {
1196 Status = BasepNotifyTrackingService(&SourceHandle,
1197 &ObjectAttributesSource,
1198 DestinationHandle,
1199 &NtDestination);
1200 #if 1 // ReactOS HACK
1201 /* FIXME: To be removed once BasepNotifyTrackingService is implemented */
1202 if (Status == STATUS_NOT_IMPLEMENTED)
1203 Status = STATUS_SUCCESS;
1204 #endif
1205 if (!NT_SUCCESS(Status))
1206 {
1207 if (dwFlags & PRIV_ALLOW_NON_TRACKABLE)
1208 {
1209 if (NT_SUCCESS(OldStatus))
1210 OldStatus = Status;
1211 }
1212 }
1213 }
1214 }
1215 _SEH2_FINALLY
1216 {
1217 if (RelativeSource)
1218 RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeSource);
1219
1220 if (RelativeDestination)
1221 RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeDestination);
1222 }
1223 _SEH2_END;
1224
1225 /* If caller asked for source deletion, if everything succeed, proceed */
1226 if (NT_SUCCESS(Status) && dwFlags & PRIV_DELETE_ON_SUCCESS)
1227 {
1228 FileDispositionInfo.DeleteFile = TRUE;
1229
1230 Status = NtSetInformationFile(SourceHandle,
1231 &IoStatusBlock,
1232 &FileDispositionInfo,
1233 sizeof(FileDispositionInfo),
1234 FileDispositionInformation);
1235 }
1236
1237 /* Cleanup/close portion */
1238 if (DestinationHandle != INVALID_HANDLE_VALUE)
1239 {
1240 NtClose(DestinationHandle);
1241 }
1242
1243 if (SourceHandle != INVALID_HANDLE_VALUE)
1244 {
1245 NtClose(SourceHandle);
1246 }
1247
1248 /* Set last error if any, and quit */
1249 if (NT_SUCCESS(Status))
1250 {
1251 if (!NT_SUCCESS(OldStatus))
1252 {
1253 BaseSetLastNTError(OldStatus);
1254 }
1255 }
1256 else
1257 {
1258 BaseSetLastNTError(Status);
1259 }
1260
1261 return NT_SUCCESS(Status);
1262 }
1263
1264 /* EOF */