xref: /reactos/dll/win32/kernel32/client/file/move.c (revision 84344399)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS system libraries
4  * FILE:            dll/win32/kernel32/client/file/move.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  */
11 
12 /* INCLUDES *****************************************************************/
13 
14 #include <k32.h>
15 #include <malloc.h>
16 
17 #define NDEBUG
18 #include <debug.h>
19 DEBUG_CHANNEL(kernel32file);
20 
21 /* GLOBALS *****************************************************************/
22 
23 /* DEFINES *****************************************************************/
24 typedef struct _COPY_PROGRESS_CONTEXT
25 {
26     ULONG Flags;
27     LPPROGRESS_ROUTINE UserRoutine;
28     LPVOID UserData;
29 } COPY_PROGRESS_CONTEXT, *PCOPY_PROGRESS_CONTEXT;
30 
31 /* FUNCTIONS ****************************************************************/
32 
33 /**
34  * @brief
35  * Adds an entry in the "PendingFileRenameOperations" registry value, that is
36  * parsed at boot-time by SMSS.EXE to check whether there are some files to be
37  * renamed/moved or deleted.
38  *
39  * @param[in]   ExistingPath
40  * Full NT path to the file to rename/move or delete.
41  *
42  * @param[in]   NewPath
43  * Full NT path to the moved/renamed file; or an empty string if the file is
44  * to be deleted.
45  *
46  * @param[in]   KeyId
47  * Selects an alternate "PendingFileRenameOperationsXXX" registry value.
48  *
49  * @param[in]   CreateIfNotFound
50  * TRUE if the file needs to be created if it does not already exist,
51  * FALSE if not.
52  *
53  * @remark
54  * If both ExistingPath and NewPath strings are non-empty, the file is moved,
55  * otherwise it is deleted.
56  *
57  * @note
58  * The registry value is a list of NULL-terminated strings, which is itself
59  * terminated with an empty NULL-terminated string (single 0-byte).
60  * When a new entry is added, if NewPath is empty, then the second entry is
61  * simply a single NULL. Otherwise the second file path goes there.
62  * Each path is in NT format (e.g. path prepended with \??\) and the second
63  * file path gets also a '!' as the first character if MOVEFILE_REPLACE_EXISTING
64  * is specified.
65  *
66  * Examples:
67  *
68  * \??\D:\test\file1[0]
69  * !\??\D:\test\file1_renamed[0]
70  * \??\D:\test\delete[0]
71  * [0]                        <- file is to be deleted, second string empty
72  * \??\D:\test\file2[0]
73  * !\??\D:\test\file2_renamed[0]
74  * [0]                        <- indicates end of strings
75  *
76  * or:
77  *
78  * \??\D:\test\file1[0]
79  * !\??\D:\test\file1_renamed[0]
80  * \??\D:\test\delete[0]
81  * [0]                        <- file is to be deleted, second string empty
82  * [0]                        <- indicates end of strings
83  *
84  * @implemented
85  **/
86 NTSTATUS
87 WINAPI
88 BasepMoveFileDelayed(
89     _In_ PUNICODE_STRING ExistingPath,
90     _In_ PUNICODE_STRING NewPath,
91     _In_ INT KeyId,
92     _In_ BOOL CreateIfNotFound)
93 {
94 #define STRING_LENGTH 0x400
95     NTSTATUS Status;
96     HANDLE KeyHandle;
97     PVOID Buffer, BufferBegin;
98     OBJECT_ATTRIBUTES ObjectAttributes;
99     PWSTR PendingOperations, BufferWrite;
100     ULONG DataSize, BufferLength, StringLength = STRING_LENGTH;
101     UNICODE_STRING SessionManagerString, PendingOperationsString;
102     /* +6 because a INT shouldn't take more than 6 chars. Especially given the call path */
103     WCHAR PendingOperationsBuffer[sizeof(L"PendingFileRenameOperations") / sizeof(WCHAR) + 6];
104 
105     RtlInitUnicodeString(&SessionManagerString,
106                          L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Session Manager");
107 
108     /* Select appropriate key for adding our file */
109     if (KeyId == 1)
110     {
111         PendingOperations = L"PendingFileRenameOperations";
112     }
113     else
114     {
115         RtlStringCbPrintfW(PendingOperationsBuffer,
116                            sizeof(PendingOperationsBuffer),
117                            L"PendingFileRenameOperations%d", KeyId);
118         PendingOperations = PendingOperationsBuffer;
119     }
120     RtlInitUnicodeString(&PendingOperationsString, PendingOperations);
121 
122     InitializeObjectAttributes(&ObjectAttributes,
123                                &SessionManagerString,
124                                OBJ_OPENIF | OBJ_CASE_INSENSITIVE,
125                                NULL, NULL);
126 
127     /* Open parent key */
128     Status = NtCreateKey(&KeyHandle,
129                          GENERIC_READ | GENERIC_WRITE,
130                          &ObjectAttributes, 0, NULL,
131                          REG_OPTION_NON_VOLATILE, NULL);
132     if (Status == STATUS_ACCESS_DENIED)
133     {
134         Status = NtCreateKey(&KeyHandle,
135                              GENERIC_READ | GENERIC_WRITE,
136                              &ObjectAttributes, 0, NULL,
137                              REG_OPTION_BACKUP_RESTORE, NULL);
138     }
139 
140     if (!NT_SUCCESS(Status))
141     {
142         return Status;
143     }
144 
145     /* Reserve enough to read previous string + to append ours with required null chars */
146     BufferLength = NewPath->Length + ExistingPath->Length + STRING_LENGTH + 3 * sizeof(UNICODE_NULL);
147 
148     while (TRUE)
149     {
150         /* Allocate output buffer */
151         Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, BufferLength);
152         if (Buffer == NULL)
153         {
154             NtClose(KeyHandle);
155             return STATUS_NO_MEMORY;
156         }
157 
158         Status = NtQueryValueKey(KeyHandle,
159                                  &PendingOperationsString,
160                                  KeyValuePartialInformation,
161                                  Buffer, StringLength, &DataSize);
162         if (Status != STATUS_BUFFER_OVERFLOW)
163         {
164             break;
165         }
166 
167         /* If buffer was too small, reallocate one which is big enough */
168         StringLength = DataSize;
169         RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
170         BufferLength = ExistingPath->Length + StringLength + NewPath->Length + 3 * sizeof(UNICODE_NULL);
171         /* Check we didn't overflow */
172         if (BufferLength < StringLength)
173         {
174             NtClose(KeyHandle);
175             return STATUS_BUFFER_TOO_SMALL;
176         }
177     }
178 
179     /* Check if it existed; if not, create only IF asked to */
180     if (!NT_SUCCESS(Status) && (Status != STATUS_OBJECT_NAME_NOT_FOUND || !CreateIfNotFound))
181     {
182         NtClose(KeyHandle);
183         RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
184         return Status;
185     }
186 
187     if (!NT_SUCCESS(Status))
188     {
189         /* We didn't find any: we create, so use complete buffer */
190         BufferBegin = Buffer;
191         BufferWrite = Buffer;
192     }
193     else
194     {
195         PKEY_VALUE_PARTIAL_INFORMATION PartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION)Buffer;
196 
197         /* Get data, the buffer beginning, and where data should be appended
198          * (minus NULL char: this is REG_MULTI_SZ, it already includes double
199          * termination, but we keep only one). */
200         BufferBegin = PartialInfo->Data;
201         BufferWrite = (PWSTR)((ULONG_PTR)PartialInfo->Data + PartialInfo->DataLength - sizeof(UNICODE_NULL));
202     }
203 
204     /* First copy existing */
205     RtlCopyMemory(BufferWrite, ExistingPath->Buffer, ExistingPath->Length);
206     BufferWrite += ExistingPath->Length / sizeof(WCHAR);
207     /* And append null char */
208     *BufferWrite = UNICODE_NULL;
209     ++BufferWrite;
210     /* Append destination */
211     RtlCopyMemory(BufferWrite, NewPath->Buffer, NewPath->Length);
212     BufferWrite += NewPath->Length / sizeof(WCHAR);
213     /* And append two null char (end of string) */
214     *BufferWrite = UNICODE_NULL;
215     ++BufferWrite;
216     *BufferWrite = UNICODE_NULL;
217 
218     /* Set new value */
219     Status = NtSetValueKey(KeyHandle,
220                            &PendingOperationsString,
221                            0, REG_MULTI_SZ, BufferBegin,
222                            (ULONG_PTR)BufferWrite - (ULONG_PTR)BufferBegin + sizeof(WCHAR));
223 
224     NtClose(KeyHandle);
225     RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
226 
227     return Status;
228 }
229 
230 
231 /*
232  * @implemented
233  */
234 DWORD
235 WINAPI
236 BasepGetComputerNameFromNtPath(IN PUNICODE_STRING NewPath,
237                                IN HANDLE NewHandle,
238                                OUT PWSTR ComputerName,
239                                IN OUT PULONG ComputerNameLength)
240 {
241     BOOL Query = FALSE;
242     WCHAR Letter;
243     PWSTR AbsolutePath, EndOfName;
244     USHORT AbsolutePathLength, NameLength;
245     WCHAR TargetDevice[MAX_PATH + 1];
246     WCHAR DeviceName[] = {'A', ':', '\0'}; /* Init to something, will be set later */
247     UNICODE_STRING UncString = RTL_CONSTANT_STRING(L"\\??\\UNC\\");
248     UNICODE_STRING GlobalString = RTL_CONSTANT_STRING(L"\\??\\");
249 
250     DPRINT("BasepGetComputerNameFromNtPath(%wZ, %p, %p, %lu)\n",
251            NewPath, NewHandle, ComputerName, ComputerNameLength);
252 
253     /* If it's an UNC path */
254     if (RtlPrefixUnicodeString(&UncString, NewPath, TRUE))
255     {
256         /* Check for broken caller */
257         if (NewPath->Length <= UncString.Length)
258         {
259             return ERROR_BAD_PATHNAME;
260         }
261 
262         /* Skip UNC prefix */
263         AbsolutePath = &NewPath->Buffer[UncString.Length / sizeof(WCHAR)];
264         AbsolutePathLength = NewPath->Length - UncString.Length;
265 
266         /* And query DFS */
267         Query = TRUE;
268     }
269     /* Otherwise, we have to be in global (NT path!), with drive letter */
270     else if (RtlPrefixUnicodeString(&GlobalString, NewPath, TRUE) && NewPath->Buffer[5] == ':')
271     {
272         /* Path is like that: \??\C:\Complete Path\To File.ext */
273         /* Get the letter and upcase it if required */
274         Letter = NewPath->Buffer[4];
275         if (Letter >= 'a' && Letter <= 'z')
276         {
277             Letter -= ('a' - 'A');
278         }
279         DeviceName[0] = Letter;
280 
281         /* Query the associated DOS device */
282         if (!QueryDosDeviceW(DeviceName, TargetDevice, ARRAYSIZE(TargetDevice)))
283         {
284             return GetLastError();
285         }
286 
287         /* If that's a network share */
288         if (TargetDevice == wcsstr(TargetDevice, L"\\Device\\LanmanRedirector\\;"))
289         {
290             /* Path is like that: \Device\LanmanRedirector\;C:0000000000000000\Complete Path\To File.ext */
291             /* Check we have the correct drive letter */
292             if (TargetDevice[26] == DeviceName[0] &&
293                 TargetDevice[27] == ':')
294             {
295                 /* Check for the path begin, computer name is before */
296                 PWSTR Path = wcschr(&TargetDevice[28], L'\\');
297                 if (Path == NULL)
298                 {
299                     return ERROR_BAD_PATHNAME;
300                 }
301 
302                 AbsolutePath = Path + 1;
303                 AbsolutePathLength = sizeof(WCHAR) * (ARRAYSIZE(TargetDevice) - (AbsolutePath - TargetDevice));
304             }
305             else
306             {
307                 return ERROR_BAD_PATHNAME;
308             }
309         }
310         /* If it's a local device */
311         else if (TargetDevice == wcsstr(TargetDevice, L"\\Device\\Harddisk")
312                  || TargetDevice == wcsstr(TargetDevice, L"\\Device\\CdRom")
313                  || TargetDevice == wcsstr(TargetDevice, L"\\Device\\Floppy"))
314         {
315             /* Just query the computer name */
316             if (!GetComputerNameW(ComputerName, ComputerNameLength))
317             {
318                 return GetLastError();
319             }
320 
321             return ERROR_SUCCESS;
322         }
323         /* If it's a DFS share */
324         else if (TargetDevice == wcsstr(TargetDevice, L"\\Device\\WinDfs\\"))
325         {
326             /* Obviously, query DFS */
327             Query = TRUE;
328         }
329         else
330         {
331             return ERROR_BAD_PATHNAME;
332         }
333     }
334     else
335     {
336         return ERROR_BAD_PATHNAME;
337     }
338 
339     /* Query DFS, currently not implemented - shouldn't be missing in ReactOS yet ;-) */
340     if (Query)
341     {
342         UNIMPLEMENTED_DBGBREAK("Querying DFS not implemented!\n");
343         AbsolutePath = NULL;
344         AbsolutePathLength = 0;
345     }
346 
347     /* Now, properly extract the computer name from the full path */
348     EndOfName = AbsolutePath;
349     if (AbsolutePathLength)
350     {
351         for (NameLength = 0; NameLength < AbsolutePathLength; NameLength += sizeof(WCHAR))
352         {
353             /* Look for the next \, it will be the end of computer name */
354             if (EndOfName[0] == L'\\')
355             {
356                 break;
357             }
358             /* Computer name cannot contain ., if we get to that point, something went wrong... */
359             else if (EndOfName[0] == L'.')
360             {
361                 return ERROR_BAD_PATHNAME;
362             }
363 
364             ++EndOfName;
365         }
366     }
367 
368     NameLength = EndOfName - AbsolutePath;
369     /* Check we didn't overflow and that our computer name isn't ill-formed */
370     if (NameLength >= AbsolutePathLength || NameLength >= MAX_COMPUTERNAME_LENGTH * sizeof(WCHAR))
371     {
372         return ERROR_BAD_PATHNAME;
373     }
374 
375     /* Check we can fit */
376     if (NameLength + sizeof(UNICODE_NULL) > *ComputerNameLength * sizeof(WCHAR))
377     {
378         return ERROR_BUFFER_OVERFLOW;
379     }
380 
381     /* Write, zero and done! */
382     RtlCopyMemory(ComputerName, AbsolutePath, NameLength);
383     *ComputerNameLength = NameLength / sizeof(WCHAR);
384     ComputerName[NameLength / sizeof(WCHAR)] = UNICODE_NULL;
385 
386     return ERROR_SUCCESS;
387 }
388 
389 
390 /*
391  * @implemented
392  */
393 NTSTATUS
394 WINAPI
395 BasepNotifyTrackingService(IN OUT PHANDLE ExistingHandle,
396                            IN POBJECT_ATTRIBUTES ObjectAttributes,
397                            IN HANDLE NewHandle,
398                            IN PUNICODE_STRING NewPath)
399 {
400     NTSTATUS Status;
401     ULONG ComputerNameLength, FileAttributes;
402     WCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
403     OEM_STRING ComputerNameStringA;
404     CHAR ComputerNameStringBuffer[MAX_PATH + 1];
405     UNICODE_STRING ComputerNameStringW;
406     IO_STATUS_BLOCK IoStatusBlock;
407     FILE_BASIC_INFORMATION FileBasicInfo;
408     HANDLE hFullWrite;
409     struct
410     {
411         FILE_TRACKING_INFORMATION;
412         CHAR Buffer[(MAX_COMPUTERNAME_LENGTH + 1) * sizeof(WCHAR)];
413     } FileTrackingInfo;
414 
415     DPRINT("BasepNotifyTrackingService(%p, %p, %p, %wZ)\n",
416            *ExistingHandle, ObjectAttributes, NewHandle, NewPath);
417 
418     Status = STATUS_SUCCESS;
419     ComputerNameLength = ARRAYSIZE(ComputerName);
420 
421     /* Attempt to get computer name of target handle */
422     if (BasepGetComputerNameFromNtPath(NewPath, NewHandle, ComputerName, &ComputerNameLength))
423     {
424         /* If we failed to get it, we will just notify with the handle */
425         FileTrackingInfo.ObjectInformationLength = 0;
426     }
427     else
428     {
429         /* Convert the retrieved computer name to ANSI and attach it to the notification */
430         RtlInitEmptyAnsiString(&ComputerNameStringA,
431                                ComputerNameStringBuffer,
432                                sizeof(ComputerNameStringBuffer));
433 
434         RtlInitUnicodeString(&ComputerNameStringW, ComputerName);
435         Status = RtlUnicodeStringToOemString(&ComputerNameStringA, &ComputerNameStringW, FALSE);
436         if (!NT_SUCCESS(Status))
437         {
438             return Status;
439         }
440 
441         RtlCopyMemory(FileTrackingInfo.ObjectInformation, ComputerNameStringA.Buffer, ComputerNameStringA.Length);
442         FileTrackingInfo.ObjectInformation[ComputerNameStringA.Length] = ANSI_NULL;
443         FileTrackingInfo.ObjectInformationLength = ComputerNameStringA.Length + 1;
444     }
445 
446     /* Attach the handle we moved */
447     FileTrackingInfo.DestinationFile = NewHandle;
448 
449     /* Final, notify */
450     Status = NtSetInformationFile(*ExistingHandle,
451                                   &IoStatusBlock,
452                                   &FileTrackingInfo,
453                                   sizeof(FileTrackingInfo),
454                                   FileTrackingInformation);
455     if (Status != STATUS_ACCESS_DENIED)
456     {
457         return Status;
458     }
459 
460     /* If we get here, we got access denied error, this comes from a
461      * read-only flag. So, close the file, in order to reopen it with enough
462      * rights to remove said flag and reattempt notification.
463      */
464     CloseHandle(*ExistingHandle);
465 
466     /* Reopen it, to be able to change the destination file attributes */
467     Status = NtOpenFile(ExistingHandle,
468                         SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
469                         ObjectAttributes,
470                         &IoStatusBlock,
471                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
472                         FILE_SYNCHRONOUS_IO_NONALERT);
473     if (!NT_SUCCESS(Status))
474     {
475         *ExistingHandle = INVALID_HANDLE_VALUE;
476         return Status;
477     }
478 
479     /* Get the file attributes */
480     Status = NtQueryInformationFile(*ExistingHandle,
481                                     &IoStatusBlock,
482                                     &FileBasicInfo,
483                                     sizeof(FileBasicInfo),
484                                     FileBasicInformation);
485     if (!NT_SUCCESS(Status))
486     {
487         return Status;
488     }
489 
490     /* Get rid of the read only flag */
491     FileAttributes = FileBasicInfo.FileAttributes & ~FILE_ATTRIBUTE_READONLY;
492     RtlZeroMemory(&FileBasicInfo, sizeof(FileBasicInfo));
493     FileBasicInfo.FileAttributes = FileAttributes;
494 
495     /* Attempt... */
496     Status = NtSetInformationFile(*ExistingHandle,
497                                   &IoStatusBlock,
498                                   &FileBasicInfo,
499                                   sizeof(FileBasicInfo),
500                                   FileBasicInformation);
501     if (!NT_SUCCESS(Status))
502     {
503         return Status;
504     }
505 
506     /* Now, reopen with maximum accesses to notify */
507     Status = NtOpenFile(&hFullWrite,
508                         GENERIC_WRITE | SYNCHRONIZE,
509                         ObjectAttributes,
510                         &IoStatusBlock,
511                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
512                         FILE_SYNCHRONOUS_IO_NONALERT);
513     if (NT_SUCCESS(Status))
514     {
515         NtClose(*ExistingHandle);
516         *ExistingHandle = hFullWrite;
517 
518         /* Full success, notify! */
519         Status = NtSetInformationFile(*ExistingHandle,
520                                       &IoStatusBlock,
521                                       &FileTrackingInfo,
522                                       sizeof(FileTrackingInfo),
523                                       FileTrackingInformation);
524     }
525 
526     /* If opening with full access failed or if notify failed, restore read-only */
527     if (!NT_SUCCESS(Status))
528     {
529         FileBasicInfo.FileAttributes |= FILE_ATTRIBUTE_READONLY;
530 
531         Status = NtSetInformationFile(*ExistingHandle,
532                                       &IoStatusBlock,
533                                       &FileBasicInfo,
534                                       sizeof(FileBasicInfo),
535                                       FileBasicInformation);
536     }
537 
538     /* We're done */
539     return Status;
540 }
541 
542 
543 /*
544  * @implemented
545  */
546 NTSTATUS
547 WINAPI
548 BasepOpenFileForMove(IN LPCWSTR File,
549                      OUT PUNICODE_STRING RelativeNtName,
550                      OUT LPWSTR * NtName,
551                      OUT PHANDLE FileHandle,
552                      OUT POBJECT_ATTRIBUTES ObjectAttributes,
553                      IN ACCESS_MASK DesiredAccess,
554                      IN ULONG ShareAccess,
555                      IN ULONG OpenOptions)
556 {
557     RTL_RELATIVE_NAME_U RelativeName;
558     NTSTATUS Status;
559     IO_STATUS_BLOCK IoStatusBlock;
560     FILE_ATTRIBUTE_TAG_INFORMATION TagInfo;
561     ULONG IntShareAccess;
562     BOOLEAN HasRelative = FALSE;
563 
564     _SEH2_TRY
565     {
566         /* Zero output */
567         RtlInitEmptyUnicodeString(RelativeNtName, NULL, 0);
568         *NtName = NULL;
569 
570         if (!RtlDosPathNameToRelativeNtPathName_U(File, RelativeNtName, NULL, &RelativeName))
571         {
572             Status = STATUS_OBJECT_PATH_NOT_FOUND;
573             _SEH2_LEAVE;
574         }
575 
576         HasRelative = TRUE;
577         *NtName = RelativeNtName->Buffer;
578 
579         if (RelativeName.RelativeName.Length)
580         {
581             RelativeNtName->Length = RelativeName.RelativeName.Length;
582             RelativeNtName->MaximumLength = RelativeName.RelativeName.MaximumLength;
583             RelativeNtName->Buffer = RelativeName.RelativeName.Buffer;
584         }
585         else
586         {
587             RelativeName.ContainingDirectory = NULL;
588         }
589 
590         InitializeObjectAttributes(ObjectAttributes,
591                                    RelativeNtName,
592                                    OBJ_CASE_INSENSITIVE,
593                                    RelativeName.ContainingDirectory,
594                                    NULL);
595         /* Force certain flags here, given ops we'll do */
596         IntShareAccess = ShareAccess | FILE_SHARE_READ | FILE_SHARE_WRITE;
597         OpenOptions |= FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT;
598 
599         /* We'll try to read reparse tag */
600         Status = NtOpenFile(FileHandle,
601                             DesiredAccess | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
602                             ObjectAttributes,
603                             &IoStatusBlock,
604                             IntShareAccess,
605                             OpenOptions | FILE_OPEN_REPARSE_POINT);
606         if (NT_SUCCESS(Status))
607         {
608             /* Attempt the read */
609             Status = NtQueryInformationFile(*FileHandle,
610                                             &IoStatusBlock,
611                                             &TagInfo,
612                                             sizeof(TagInfo),
613                                             FileAttributeTagInformation);
614 
615             /* Return if failure with a status that wouldn't mean the FSD cannot support reparse points */
616             if (!NT_SUCCESS(Status) &&
617                 (Status != STATUS_NOT_IMPLEMENTED && Status != STATUS_INVALID_PARAMETER))
618             {
619                 _SEH2_LEAVE;
620             }
621 
622             if (NT_SUCCESS(Status))
623             {
624                 /* This cannot happen on mount points */
625                 if (TagInfo.FileAttributes & FILE_ATTRIBUTE_DEVICE ||
626                     TagInfo.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
627                 {
628                     _SEH2_LEAVE;
629                 }
630             }
631 
632             NtClose(*FileHandle);
633             *FileHandle = INVALID_HANDLE_VALUE;
634 
635             IntShareAccess = ShareAccess | FILE_SHARE_READ | FILE_SHARE_DELETE;
636         }
637         else if (Status == STATUS_INVALID_PARAMETER)
638         {
639             IntShareAccess = ShareAccess | FILE_SHARE_READ | FILE_SHARE_WRITE;
640         }
641         else
642         {
643             _SEH2_LEAVE;
644         }
645 
646         /* Reattempt to open normally, following reparse point if needed */
647         Status = NtOpenFile(FileHandle,
648                             DesiredAccess | SYNCHRONIZE,
649                             ObjectAttributes,
650                             &IoStatusBlock,
651                             IntShareAccess,
652                             OpenOptions);
653     }
654     _SEH2_FINALLY
655     {
656         if (HasRelative)
657         {
658             RtlReleaseRelativeName(&RelativeName);
659         }
660     }
661     _SEH2_END;
662 
663     return Status;
664 }
665 
666 
667 /*
668  * @implemented
669  */
670 DWORD
671 WINAPI
672 BasepMoveFileCopyProgress(IN LARGE_INTEGER TotalFileSize,
673                           IN LARGE_INTEGER TotalBytesTransferred,
674                           IN LARGE_INTEGER StreamSize,
675                           IN LARGE_INTEGER StreamBytesTransferred,
676                           IN DWORD dwStreamNumber,
677                           IN DWORD dwCallbackReason,
678                           IN HANDLE hSourceFile,
679                           IN HANDLE hDestinationFile,
680                           IN LPVOID lpData OPTIONAL)
681 {
682     DWORD Ret = 0;
683     PCOPY_PROGRESS_CONTEXT Context = (PCOPY_PROGRESS_CONTEXT)lpData;
684 
685     if (Context->Flags & MOVEFILE_WRITE_THROUGH)
686     {
687         if (!dwCallbackReason)
688         {
689             if (StreamBytesTransferred.QuadPart == StreamSize.QuadPart)
690             {
691                 FlushFileBuffers(hDestinationFile);
692             }
693         }
694     }
695 
696     if (Context->UserRoutine)
697     {
698         Ret = Context->UserRoutine(TotalFileSize,
699                                    TotalBytesTransferred,
700                                    StreamSize,
701                                    StreamBytesTransferred,
702                                    dwStreamNumber,
703                                    dwCallbackReason,
704                                    hSourceFile,
705                                    hDestinationFile,
706                                    Context->UserData);
707     }
708 
709     return Ret;
710 }
711 
712 
713 /*
714  * @implemented
715  */
716 BOOL
717 WINAPI
718 MoveFileWithProgressW(IN LPCWSTR lpExistingFileName,
719                       IN LPCWSTR lpNewFileName,
720                       IN LPPROGRESS_ROUTINE lpProgressRoutine,
721                       IN LPVOID lpData,
722                       IN DWORD dwFlags)
723 {
724     NTSTATUS Status;
725     PWSTR NewBuffer;
726     IO_STATUS_BLOCK IoStatusBlock;
727     COPY_PROGRESS_CONTEXT CopyContext;
728     OBJECT_ATTRIBUTES ObjectAttributes;
729     PFILE_RENAME_INFORMATION RenameInfo;
730     UNICODE_STRING NewPathU, ExistingPathU;
731     FILE_ATTRIBUTE_TAG_INFORMATION TagInfo;
732     HANDLE SourceHandle = INVALID_HANDLE_VALUE, NewHandle, ExistingHandle;
733     BOOL Ret = FALSE, ReplaceIfExists, DelayUntilReboot, AttemptReopenWithoutReparse;
734 
735     DPRINT("MoveFileWithProgressW(%S, %S, %p, %p, %x)\n",
736            lpExistingFileName, lpNewFileName, lpProgressRoutine, lpData, dwFlags);
737 
738     NewPathU.Buffer = NULL;
739     ExistingPathU.Buffer = NULL;
740 
741     _SEH2_TRY
742     {
743         /* Don't allow renaming to a disk */
744         if (lpNewFileName && RtlIsDosDeviceName_U(lpNewFileName))
745         {
746             BaseSetLastNTError(STATUS_OBJECT_NAME_COLLISION);
747             _SEH2_LEAVE;
748         }
749 
750         ReplaceIfExists = !!(dwFlags & MOVEFILE_REPLACE_EXISTING);
751 
752         /* Get file path */
753         if (!RtlDosPathNameToNtPathName_U(lpExistingFileName, &ExistingPathU, NULL, NULL))
754         {
755             BaseSetLastNTError(STATUS_OBJECT_PATH_NOT_FOUND);
756             _SEH2_LEAVE;
757         }
758 
759         /* Sanitize input */
760         DelayUntilReboot = !!(dwFlags & MOVEFILE_DELAY_UNTIL_REBOOT);
761         if (DelayUntilReboot && (dwFlags & MOVEFILE_CREATE_HARDLINK))
762         {
763             BaseSetLastNTError(STATUS_INVALID_PARAMETER);
764             _SEH2_LEAVE;
765         }
766 
767         /* Unless we manage a proper opening, we'll attempt to reopen without reparse support */
768         AttemptReopenWithoutReparse = TRUE;
769         InitializeObjectAttributes(&ObjectAttributes,
770                                    &ExistingPathU,
771                                    OBJ_CASE_INSENSITIVE,
772                                    NULL,
773                                    NULL);
774         /* Attempt to open source file */
775         Status = NtOpenFile(&SourceHandle,
776                             FILE_READ_ATTRIBUTES | DELETE | SYNCHRONIZE,
777                             &ObjectAttributes,
778                             &IoStatusBlock,
779                             FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
780                             FILE_OPEN_FOR_BACKUP_INTENT | ((dwFlags & MOVEFILE_WRITE_THROUGH) ? FILE_WRITE_THROUGH : 0));
781         if (!NT_SUCCESS(Status))
782         {
783             /* If we failed and the file doesn't exist, don't attempt to reopen without reparse */
784             if (DelayUntilReboot &&
785                 (Status == STATUS_SHARING_VIOLATION || Status == STATUS_OBJECT_NAME_NOT_FOUND || Status == STATUS_OBJECT_PATH_NOT_FOUND))
786             {
787                 /* Here we don't fail completely, as we postpone the operation to reboot.
788                  * File might exist afterwards, and we don't need a handle here. */
789                 SourceHandle = INVALID_HANDLE_VALUE;
790                 AttemptReopenWithoutReparse = FALSE;
791             }
792             /* If we failed for any reason than unsupported reparse, fail completely */
793             else if (Status != STATUS_INVALID_PARAMETER)
794             {
795                 BaseSetLastNTError(Status);
796                 _SEH2_LEAVE;
797             }
798         }
799         else
800         {
801             /* We managed to open, so query information */
802             Status = NtQueryInformationFile(SourceHandle,
803                                             &IoStatusBlock,
804                                             &TagInfo,
805                                             sizeof(TagInfo),
806                                             FileAttributeTagInformation);
807             if (!NT_SUCCESS(Status))
808             {
809                 /* Do not tolerate any other error than something related to not supported operation */
810                 if (Status != STATUS_NOT_IMPLEMENTED && Status != STATUS_INVALID_PARAMETER)
811                 {
812                     BaseSetLastNTError(Status);
813                     _SEH2_LEAVE;
814                 }
815 
816                 /* Not a reparse point, no need to reopen, it's fine */
817                 AttemptReopenWithoutReparse = FALSE;
818             }
819             /* Validate the reparse point (do we support it?) */
820             else if ((TagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
821                      (TagInfo.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT))
822             {
823                 NtClose(SourceHandle);
824                 SourceHandle = INVALID_HANDLE_VALUE;
825             }
826             else
827             {
828                 /* Mount point, let's rename it */
829                 AttemptReopenWithoutReparse = FALSE;
830             }
831         }
832 
833         /* Simply reopen if required */
834         if (AttemptReopenWithoutReparse)
835         {
836             Status = NtOpenFile(&SourceHandle,
837                                 DELETE | SYNCHRONIZE,
838                                 &ObjectAttributes,
839                                 &IoStatusBlock,
840                                 FILE_SHARE_READ | FILE_SHARE_WRITE,
841                                 ((dwFlags & MOVEFILE_WRITE_THROUGH) ? FILE_WRITE_THROUGH : 0));
842             if (!NT_SUCCESS(Status))
843             {
844                 BaseSetLastNTError(Status);
845                 _SEH2_LEAVE;
846             }
847         }
848 
849         /* Nullify string if we're to use it */
850         if (DelayUntilReboot && !lpNewFileName)
851         {
852             RtlInitUnicodeString(&NewPathU, NULL);
853         }
854         /* Check whether path exists */
855         else if (!RtlDosPathNameToNtPathName_U(lpNewFileName, &NewPathU, NULL, NULL))
856         {
857             BaseSetLastNTError(STATUS_OBJECT_PATH_NOT_FOUND);
858             _SEH2_LEAVE;
859         }
860 
861         /* Handle postponed renaming */
862         if (DelayUntilReboot)
863         {
864             /* If new file exists and we're allowed to replace, then mark the path with ! */
865             if (ReplaceIfExists && NewPathU.Length)
866             {
867                 NewBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, NewPathU.Length + sizeof(WCHAR));
868                 if (NewBuffer == NULL)
869                 {
870                     BaseSetLastNTError(STATUS_NO_MEMORY);
871                     _SEH2_LEAVE;
872                 }
873 
874                 NewBuffer[0] = L'!';
875                 RtlCopyMemory(&NewBuffer[1], NewPathU.Buffer, NewPathU.Length);
876                 NewPathU.Length += sizeof(WCHAR);
877                 NewPathU.MaximumLength += sizeof(WCHAR);
878                 RtlFreeHeap(RtlGetProcessHeap(), 0, NewPathU.Buffer);
879                 NewPathU.Buffer = NewBuffer;
880             }
881 
882             /* Check whether 'copy' renaming is allowed if required */
883             if ((RtlDetermineDosPathNameType_U(lpExistingFileName) == RtlPathTypeUncAbsolute) ||
884                 (dwFlags & MOVEFILE_COPY_ALLOWED))
885             {
886                 Status = STATUS_INVALID_PARAMETER;
887             }
888             else
889             {
890                 /* First, probe 2nd key to see whether it exists - if so, it will be appended there */
891                 Status = BasepMoveFileDelayed(&ExistingPathU, &NewPathU, 2, FALSE);
892                 if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
893                 {
894                     /* If doesn't exist, append to first key first, creating it if it doesn't exist */
895                     Status = BasepMoveFileDelayed(&ExistingPathU, &NewPathU, 1, TRUE);
896                     if (Status == STATUS_INSUFFICIENT_RESOURCES)
897                     {
898                         /* If it failed because it's too big, then create 2nd key and put it there */
899                         Status = BasepMoveFileDelayed(&ExistingPathU, &NewPathU, 2, TRUE);
900                     }
901                 }
902             }
903 
904             /* If we failed at some point, return the error */
905             if (!NT_SUCCESS(Status))
906             {
907                 BaseSetLastNTError(Status);
908                 _SEH2_LEAVE;
909             }
910 
911             Ret = TRUE;
912             _SEH2_LEAVE;
913         }
914 
915         /* At that point, we MUST have a source handle */
916         ASSERT(SourceHandle != INVALID_HANDLE_VALUE);
917 
918         /* Allocate renaming buffer and fill it */
919         RenameInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0,
920                                      NewPathU.Length + sizeof(FILE_RENAME_INFORMATION));
921         if (RenameInfo == NULL)
922         {
923             BaseSetLastNTError(STATUS_NO_MEMORY);
924             _SEH2_LEAVE;
925         }
926 
927         RtlCopyMemory(&RenameInfo->FileName, NewPathU.Buffer, NewPathU.Length);
928         RenameInfo->ReplaceIfExists = ReplaceIfExists;
929         RenameInfo->RootDirectory = NULL;
930         RenameInfo->FileNameLength = NewPathU.Length;
931 
932         /* Attempt to rename the file */
933         Status = NtSetInformationFile(SourceHandle,
934                                       &IoStatusBlock,
935                                       RenameInfo,
936                                       NewPathU.Length + sizeof(FILE_RENAME_INFORMATION),
937                                       ((dwFlags & MOVEFILE_CREATE_HARDLINK)
938                                         ? FileLinkInformation : FileRenameInformation));
939         RtlFreeHeap(RtlGetProcessHeap(), 0, RenameInfo);
940         if (NT_SUCCESS(Status))
941         {
942             /* If it succeed, all fine, quit */
943             Ret = TRUE;
944             _SEH2_LEAVE;
945         }
946         /* If we failed for any other reason than not the same device, fail.
947          * If we failed because of different devices, only allow renaming if user allowed copy.
948          */
949         if (Status != STATUS_NOT_SAME_DEVICE || !(dwFlags & MOVEFILE_COPY_ALLOWED))
950         {
951             /* ReactOS HACK! To be removed once all FSD have proper renaming support.
952              * Just leave status to error and leave. */
953             if (Status == STATUS_NOT_IMPLEMENTED)
954             {
955                 DPRINT1("Forcing copy, renaming not supported by FSD\n");
956             }
957             else
958             {
959                 BaseSetLastNTError(Status);
960                 _SEH2_LEAVE;
961             }
962         }
963 
964         /* Close source file */
965         NtClose(SourceHandle);
966         SourceHandle = INVALID_HANDLE_VALUE;
967 
968         /* Issue the copy of the file */
969         CopyContext.Flags = dwFlags;
970         CopyContext.UserRoutine = lpProgressRoutine;
971         CopyContext.UserData = lpData;
972         NewHandle = INVALID_HANDLE_VALUE;
973         ExistingHandle = INVALID_HANDLE_VALUE;
974 
975         Ret = BasepCopyFileExW(lpExistingFileName,
976                                lpNewFileName,
977                                BasepMoveFileCopyProgress,
978                                &CopyContext,
979                                NULL,
980                                (!ReplaceIfExists ? COPY_FILE_FAIL_IF_EXISTS : 0)
981                                     | COPY_FILE_OPEN_SOURCE_FOR_WRITE,
982                                0,
983                                &ExistingHandle,
984                                &NewHandle);
985         if (!Ret)
986         {
987             /* If it failed, don't leak any handle */
988             if (ExistingHandle != INVALID_HANDLE_VALUE)
989             {
990                 CloseHandle(ExistingHandle);
991                 ExistingHandle = INVALID_HANDLE_VALUE;
992             }
993         }
994         else if (ExistingHandle != INVALID_HANDLE_VALUE)
995         {
996             if (NewHandle != INVALID_HANDLE_VALUE)
997             {
998                 /* If copying succeed, notify */
999                 Status = BasepNotifyTrackingService(&ExistingHandle,
1000                                                     &ObjectAttributes,
1001                                                     NewHandle,
1002                                                     &NewPathU);
1003                 if (!NT_SUCCESS(Status))
1004                 {
1005                     /* Fail in case it had to succeed */
1006                     if (dwFlags & MOVEFILE_FAIL_IF_NOT_TRACKABLE)
1007                     {
1008                         if (NewHandle != INVALID_HANDLE_VALUE)
1009                             CloseHandle(NewHandle);
1010                         NewHandle = INVALID_HANDLE_VALUE;
1011                         DeleteFileW(lpNewFileName);
1012                         Ret = FALSE;
1013                         BaseSetLastNTError(Status);
1014                     }
1015                 }
1016             }
1017 
1018             CloseHandle(ExistingHandle);
1019             ExistingHandle = INVALID_HANDLE_VALUE;
1020         }
1021 
1022         /* In case copy worked, close file */
1023         if (NewHandle != INVALID_HANDLE_VALUE)
1024         {
1025             CloseHandle(NewHandle);
1026             NewHandle = INVALID_HANDLE_VALUE;
1027         }
1028 
1029         /* If it succeed, delete source file */
1030         if (Ret)
1031         {
1032             if (!DeleteFileW(lpExistingFileName))
1033             {
1034                 /* Reset file attributes if required */
1035                 SetFileAttributesW(lpExistingFileName, FILE_ATTRIBUTE_NORMAL);
1036                 DeleteFileW(lpExistingFileName);
1037             }
1038         }
1039     }
1040     _SEH2_FINALLY
1041     {
1042         if (SourceHandle != INVALID_HANDLE_VALUE)
1043             NtClose(SourceHandle);
1044 
1045         RtlFreeHeap(RtlGetProcessHeap(), 0, ExistingPathU.Buffer);
1046         RtlFreeHeap(RtlGetProcessHeap(), 0, NewPathU.Buffer);
1047     }
1048     _SEH2_END;
1049 
1050     return Ret;
1051 }
1052 
1053 
1054 /*
1055  * @implemented
1056  */
1057 BOOL
1058 WINAPI
1059 MoveFileWithProgressA(IN LPCSTR lpExistingFileName,
1060                       IN LPCSTR lpNewFileName OPTIONAL,
1061                       IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
1062                       IN LPVOID lpData OPTIONAL,
1063                       IN DWORD dwFlags)
1064 {
1065     BOOL Ret;
1066     UNICODE_STRING ExistingFileNameW, NewFileNameW;
1067 
1068     if (!Basep8BitStringToDynamicUnicodeString(&ExistingFileNameW, lpExistingFileName))
1069     {
1070         return FALSE;
1071     }
1072 
1073     if (lpNewFileName)
1074     {
1075         if (!Basep8BitStringToDynamicUnicodeString(&NewFileNameW, lpNewFileName))
1076         {
1077             RtlFreeUnicodeString(&ExistingFileNameW);
1078             return FALSE;
1079         }
1080     }
1081     else
1082     {
1083         NewFileNameW.Buffer = NULL;
1084     }
1085 
1086     Ret = MoveFileWithProgressW(ExistingFileNameW.Buffer,
1087                                 NewFileNameW.Buffer,
1088                                 lpProgressRoutine,
1089                                 lpData,
1090                                 dwFlags);
1091 
1092     RtlFreeUnicodeString(&ExistingFileNameW);
1093     RtlFreeUnicodeString(&NewFileNameW);
1094 
1095     return Ret;
1096 }
1097 
1098 
1099 /*
1100  * @implemented
1101  */
1102 BOOL
1103 WINAPI
1104 MoveFileW(IN LPCWSTR lpExistingFileName,
1105           IN LPCWSTR lpNewFileName)
1106 {
1107     return MoveFileWithProgressW(lpExistingFileName,
1108                                  lpNewFileName,
1109                                  NULL,
1110                                  NULL,
1111                                  MOVEFILE_COPY_ALLOWED);
1112 }
1113 
1114 
1115 /*
1116  * @implemented
1117  */
1118 BOOL
1119 WINAPI
1120 MoveFileExW(IN LPCWSTR lpExistingFileName,
1121             IN LPCWSTR lpNewFileName OPTIONAL,
1122             IN DWORD dwFlags)
1123 {
1124     return MoveFileWithProgressW(lpExistingFileName,
1125                                  lpNewFileName,
1126                                  NULL,
1127                                  NULL,
1128                                  dwFlags);
1129 }
1130 
1131 
1132 /*
1133  * @implemented
1134  */
1135 BOOL
1136 WINAPI
1137 MoveFileA(IN LPCSTR lpExistingFileName,
1138           IN LPCSTR lpNewFileName)
1139 {
1140     return MoveFileWithProgressA(lpExistingFileName,
1141                                  lpNewFileName,
1142                                  NULL,
1143                                  NULL,
1144                                  MOVEFILE_COPY_ALLOWED);
1145 }
1146 
1147 
1148 /*
1149  * @implemented
1150  */
1151 BOOL
1152 WINAPI
1153 MoveFileExA(IN LPCSTR lpExistingFileName,
1154             IN LPCSTR lpNewFileName OPTIONAL,
1155             IN DWORD dwFlags)
1156 {
1157     return MoveFileWithProgressA(lpExistingFileName,
1158                                  lpNewFileName,
1159                                  NULL,
1160                                  NULL,
1161                                  dwFlags);
1162 }
1163 
1164 /*
1165  * @implemented
1166  */
1167 BOOL
1168 WINAPI
1169 ReplaceFileA(IN LPCSTR lpReplacedFileName,
1170              IN LPCSTR lpReplacementFileName,
1171              IN LPCSTR lpBackupFileName OPTIONAL,
1172              IN DWORD dwReplaceFlags,
1173              IN LPVOID lpExclude,
1174              IN LPVOID lpReserved)
1175 {
1176     BOOL Ret;
1177     UNICODE_STRING ReplacedFileNameW, ReplacementFileNameW, BackupFileNameW;
1178 
1179     if (!lpReplacedFileName || !lpReplacementFileName || lpExclude || lpReserved ||
1180         (dwReplaceFlags & ~(REPLACEFILE_WRITE_THROUGH | REPLACEFILE_IGNORE_MERGE_ERRORS)))
1181     {
1182         SetLastError(ERROR_INVALID_PARAMETER);
1183         return FALSE;
1184     }
1185 
1186     if (!Basep8BitStringToDynamicUnicodeString(&ReplacedFileNameW, lpReplacedFileName))
1187     {
1188         return FALSE;
1189     }
1190 
1191     if (!Basep8BitStringToDynamicUnicodeString(&ReplacementFileNameW, lpReplacementFileName))
1192     {
1193         RtlFreeUnicodeString(&ReplacedFileNameW);
1194         return FALSE;
1195     }
1196 
1197     if (lpBackupFileName)
1198     {
1199         if (!Basep8BitStringToDynamicUnicodeString(&BackupFileNameW, lpBackupFileName))
1200         {
1201             RtlFreeUnicodeString(&ReplacementFileNameW);
1202             RtlFreeUnicodeString(&ReplacedFileNameW);
1203             return FALSE;
1204         }
1205     }
1206     else
1207     {
1208         BackupFileNameW.Buffer = NULL;
1209     }
1210 
1211     Ret = ReplaceFileW(ReplacedFileNameW.Buffer,
1212                        ReplacementFileNameW.Buffer,
1213                        BackupFileNameW.Buffer,
1214                        dwReplaceFlags, 0, 0);
1215 
1216     if (lpBackupFileName)
1217     {
1218         RtlFreeUnicodeString(&BackupFileNameW);
1219     }
1220     RtlFreeUnicodeString(&ReplacementFileNameW);
1221     RtlFreeUnicodeString(&ReplacedFileNameW);
1222 
1223     return Ret;
1224 }
1225 
1226 /*
1227  * @unimplemented
1228  */
1229 BOOL
1230 WINAPI
1231 ReplaceFileW(
1232     LPCWSTR lpReplacedFileName,
1233     LPCWSTR lpReplacementFileName,
1234     LPCWSTR lpBackupFileName,
1235     DWORD   dwReplaceFlags,
1236     LPVOID  lpExclude,
1237     LPVOID  lpReserved
1238     )
1239 {
1240     HANDLE hReplaced = NULL, hReplacement = NULL;
1241     UNICODE_STRING NtReplacedName = { 0, 0, NULL };
1242     UNICODE_STRING NtReplacementName = { 0, 0, NULL };
1243     DWORD Error = ERROR_SUCCESS;
1244     NTSTATUS Status;
1245     BOOL Ret = FALSE;
1246     IO_STATUS_BLOCK IoStatusBlock;
1247     OBJECT_ATTRIBUTES ObjectAttributes;
1248     PVOID Buffer = NULL ;
1249 
1250     if (dwReplaceFlags)
1251         FIXME("Ignoring flags %x\n", dwReplaceFlags);
1252 
1253     /* First two arguments are mandatory */
1254     if (!lpReplacedFileName || !lpReplacementFileName)
1255     {
1256         SetLastError(ERROR_INVALID_PARAMETER);
1257         return FALSE;
1258     }
1259 
1260     /* Back it up */
1261     if(lpBackupFileName)
1262     {
1263         if(!CopyFileW(lpReplacedFileName, lpBackupFileName, FALSE))
1264         {
1265             Error = GetLastError();
1266             goto Cleanup ;
1267         }
1268     }
1269 
1270     /* Open the "replaced" file for reading and writing */
1271     if (!(RtlDosPathNameToNtPathName_U(lpReplacedFileName, &NtReplacedName, NULL, NULL)))
1272     {
1273         Error = ERROR_PATH_NOT_FOUND;
1274         goto Cleanup;
1275     }
1276 
1277     InitializeObjectAttributes(&ObjectAttributes,
1278                                &NtReplacedName,
1279                                OBJ_CASE_INSENSITIVE,
1280                                NULL,
1281                                NULL);
1282 
1283     Status = NtOpenFile(&hReplaced,
1284                         GENERIC_READ | GENERIC_WRITE | DELETE | SYNCHRONIZE | WRITE_DAC,
1285                         &ObjectAttributes,
1286                         &IoStatusBlock,
1287                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1288                         FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
1289 
1290     if (!NT_SUCCESS(Status))
1291     {
1292         if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
1293             Error = ERROR_FILE_NOT_FOUND;
1294         else
1295             Error = ERROR_UNABLE_TO_REMOVE_REPLACED;
1296         goto Cleanup;
1297     }
1298 
1299     /* Blank it */
1300     SetEndOfFile(hReplaced) ;
1301 
1302     /*
1303      * Open the replacement file for reading, writing, and deleting
1304      * (deleting is needed when finished)
1305      */
1306     if (!(RtlDosPathNameToNtPathName_U(lpReplacementFileName, &NtReplacementName, NULL, NULL)))
1307     {
1308         Error = ERROR_PATH_NOT_FOUND;
1309         goto Cleanup;
1310     }
1311 
1312     InitializeObjectAttributes(&ObjectAttributes,
1313                                &NtReplacementName,
1314                                OBJ_CASE_INSENSITIVE,
1315                                NULL,
1316                                NULL);
1317 
1318     Status = NtOpenFile(&hReplacement,
1319                         GENERIC_READ | DELETE | SYNCHRONIZE,
1320                         &ObjectAttributes,
1321                         &IoStatusBlock,
1322                         0,
1323                         FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE);
1324 
1325     if (!NT_SUCCESS(Status))
1326     {
1327         Error = RtlNtStatusToDosError(Status);
1328         goto Cleanup;
1329     }
1330 
1331     Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, 0x10000) ;
1332     if (!Buffer)
1333     {
1334         Error = ERROR_NOT_ENOUGH_MEMORY;
1335         goto Cleanup ;
1336     }
1337     while (Status != STATUS_END_OF_FILE)
1338     {
1339         Status = NtReadFile(hReplacement, NULL, NULL, NULL, &IoStatusBlock, Buffer, 0x10000, NULL, NULL) ;
1340         if (NT_SUCCESS(Status))
1341         {
1342             Status = NtWriteFile(hReplaced, NULL, NULL, NULL, &IoStatusBlock, Buffer,
1343                     IoStatusBlock.Information, NULL, NULL) ;
1344             if (!NT_SUCCESS(Status))
1345             {
1346                 Error = RtlNtStatusToDosError(Status);
1347                 goto Cleanup;
1348             }
1349         }
1350         else if (Status != STATUS_END_OF_FILE)
1351         {
1352             Error = RtlNtStatusToDosError(Status);
1353             goto Cleanup;
1354         }
1355     }
1356 
1357     Ret = TRUE;
1358 
1359     /* Perform resource cleanup */
1360 Cleanup:
1361     if (hReplaced) NtClose(hReplaced);
1362     if (hReplacement) NtClose(hReplacement);
1363     if (Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
1364 
1365     if (NtReplacementName.Buffer)
1366         RtlFreeHeap(GetProcessHeap(), 0, NtReplacementName.Buffer);
1367     if (NtReplacedName.Buffer)
1368         RtlFreeHeap(GetProcessHeap(), 0, NtReplacedName.Buffer);
1369 
1370     /* If there was an error, set the error code */
1371     if(!Ret)
1372     {
1373         TRACE("ReplaceFileW failed (error=%lu)\n", Error);
1374         SetLastError(Error);
1375     }
1376     return Ret;
1377 }
1378 
1379 
1380 /*
1381  * @implemented
1382  */
1383 BOOL
1384 WINAPI
1385 PrivMoveFileIdentityW(IN LPCWSTR lpSource, IN LPCWSTR lpDestination, IN DWORD dwFlags)
1386 {
1387     ACCESS_MASK SourceAccess;
1388     UNICODE_STRING NtSource, NtDestination;
1389     LPWSTR RelativeSource, RelativeDestination;
1390     HANDLE SourceHandle, DestinationHandle;
1391     OBJECT_ATTRIBUTES ObjectAttributesSource, ObjectAttributesDestination;
1392     NTSTATUS Status, OldStatus = STATUS_SUCCESS;
1393     ACCESS_MASK DestAccess;
1394     IO_STATUS_BLOCK IoStatusBlock;
1395     FILE_BASIC_INFORMATION SourceInformation, DestinationInformation;
1396     FILE_DISPOSITION_INFORMATION FileDispositionInfo;
1397 
1398     DPRINT("PrivMoveFileIdentityW(%S, %S, %x)\n", lpSource, lpDestination, dwFlags);
1399 
1400     SourceHandle = INVALID_HANDLE_VALUE;
1401     RtlInitEmptyUnicodeString(&NtSource, NULL, 0);
1402     RelativeSource = NULL;
1403     DestinationHandle = INVALID_HANDLE_VALUE;
1404     RtlInitEmptyUnicodeString(&NtDestination, NULL, 0);
1405     RelativeDestination = NULL;
1406 
1407     /* FILE_WRITE_DATA is required for later on notification */
1408     SourceAccess = FILE_READ_ATTRIBUTES | FILE_WRITE_DATA;
1409     if (dwFlags & PRIV_DELETE_ON_SUCCESS)
1410     {
1411         SourceAccess |= DELETE;
1412     }
1413 
1414     _SEH2_TRY
1415     {
1416         /* We will loop twice:
1417          * First we attempt to open with FILE_WRITE_DATA for notification.
1418          * If it fails and we have flag for non-trackable files, we retry
1419          * without FILE_WRITE_DATA.
1420          * If that one fails, then, we quit for real.
1421          */
1422         while (TRUE)
1423         {
1424             Status = BasepOpenFileForMove(lpSource,
1425                                           &NtSource,
1426                                           &RelativeSource,
1427                                           &SourceHandle,
1428                                           &ObjectAttributesSource,
1429                                           SourceAccess,
1430                                           FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1431                                           FILE_OPEN_NO_RECALL);
1432             if (NT_SUCCESS(Status))
1433             {
1434                 break;
1435             }
1436 
1437             /* If we already attempted the opening without FILE_WRITE_DATA
1438              * or if we cannot move on non-trackable files, fail.
1439              */
1440             if (!(SourceAccess & FILE_WRITE_DATA) || !(dwFlags & PRIV_ALLOW_NON_TRACKABLE))
1441             {
1442                 _SEH2_LEAVE;
1443             }
1444 
1445             if (RelativeSource)
1446             {
1447                 RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeSource);
1448                 RelativeSource = NULL;
1449             }
1450 
1451             if (SourceHandle != INVALID_HANDLE_VALUE)
1452             {
1453                 NtClose(SourceHandle);
1454                 SourceHandle = INVALID_HANDLE_VALUE;
1455             }
1456 
1457             SourceAccess &= ~FILE_WRITE_DATA;
1458 
1459             /* Remember fist failure in the path */
1460             if (NT_SUCCESS(OldStatus))
1461             {
1462                 OldStatus = Status;
1463             }
1464         }
1465 
1466         DestAccess = FILE_WRITE_ATTRIBUTES;
1467         /* If we could preserve FILE_WRITE_DATA for source, attempt to
1468          * get it for destination, still for notification purposes. */
1469         if (SourceAccess & FILE_WRITE_DATA)
1470         {
1471             DestAccess |= FILE_WRITE_DATA;
1472         }
1473 
1474         /* cf comment for first loop */
1475         while (TRUE)
1476         {
1477             Status = BasepOpenFileForMove(lpDestination,
1478                                           &NtDestination,
1479                                           &RelativeDestination,
1480                                           &DestinationHandle,
1481                                           &ObjectAttributesDestination,
1482                                           DestAccess,
1483                                           FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1484                                           FILE_OPEN_NO_RECALL);
1485             if (NT_SUCCESS(Status))
1486             {
1487                 break;
1488             }
1489 
1490             /* If we already attempted the opening without FILE_WRITE_DATA
1491              * or if we cannot move on non-trackable files, fail.
1492              */
1493             if (!(DestAccess & FILE_WRITE_DATA) || !(dwFlags & PRIV_ALLOW_NON_TRACKABLE))
1494             {
1495                 _SEH2_LEAVE;
1496             }
1497 
1498             if (RelativeDestination)
1499             {
1500                 RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeDestination);
1501                 RelativeDestination = NULL;
1502             }
1503 
1504             if (DestinationHandle != INVALID_HANDLE_VALUE)
1505             {
1506                 NtClose(DestinationHandle);
1507                 DestinationHandle = INVALID_HANDLE_VALUE;
1508             }
1509 
1510             DestAccess &= ~FILE_WRITE_DATA;
1511 
1512             /* Remember fist failure in the path */
1513             if (NT_SUCCESS(OldStatus))
1514             {
1515                 OldStatus = Status;
1516             }
1517         }
1518 
1519         /* Get the creation time from source */
1520         Status = NtQueryInformationFile(SourceHandle,
1521                                         &IoStatusBlock,
1522                                         &SourceInformation,
1523                                         sizeof(SourceInformation),
1524                                         FileBasicInformation);
1525         if (NT_SUCCESS(Status))
1526         {
1527             /* Then, prepare to set it for destination */
1528             RtlZeroMemory(&DestinationInformation, sizeof(DestinationInformation));
1529             DestinationInformation.CreationTime.QuadPart = SourceInformation.CreationTime.QuadPart;
1530 
1531             /* And set it, that's all folks! */
1532             Status = NtSetInformationFile(DestinationHandle,
1533                                           &IoStatusBlock,
1534                                           &DestinationInformation,
1535                                           sizeof(DestinationInformation),
1536                                           FileBasicInformation);
1537         }
1538 
1539         if (!NT_SUCCESS(Status))
1540         {
1541             if (!(dwFlags & PRIV_ALLOW_NON_TRACKABLE))
1542             {
1543                 _SEH2_LEAVE;
1544             }
1545 
1546             /* Remember the failure for later notification */
1547             if (NT_SUCCESS(OldStatus))
1548             {
1549                 OldStatus = Status;
1550             }
1551         }
1552 
1553         /* If we could open with FILE_WRITE_DATA both source and destination,
1554          * then, notify
1555          */
1556         if (DestAccess & FILE_WRITE_DATA && SourceAccess & FILE_WRITE_DATA)
1557         {
1558             Status = BasepNotifyTrackingService(&SourceHandle,
1559                                                 &ObjectAttributesSource,
1560                                                 DestinationHandle,
1561                                                 &NtDestination);
1562             if (!NT_SUCCESS(Status))
1563             {
1564                 if (dwFlags & PRIV_ALLOW_NON_TRACKABLE)
1565                 {
1566                     if (NT_SUCCESS(OldStatus))
1567                         OldStatus = Status;
1568 
1569                     /* Reset status, we allow non trackable files */
1570                     Status = STATUS_SUCCESS;
1571                 }
1572             }
1573         }
1574     }
1575     _SEH2_FINALLY
1576     {
1577         if (RelativeSource)
1578             RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeSource);
1579 
1580         if (RelativeDestination)
1581             RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeDestination);
1582     }
1583     _SEH2_END;
1584 
1585     /* If caller asked for source deletion, if everything succeed, proceed */
1586     if (NT_SUCCESS(Status) && dwFlags & PRIV_DELETE_ON_SUCCESS)
1587     {
1588         FileDispositionInfo.DeleteFile = TRUE;
1589 
1590         Status = NtSetInformationFile(SourceHandle,
1591                                       &IoStatusBlock,
1592                                       &FileDispositionInfo,
1593                                       sizeof(FileDispositionInfo),
1594                                       FileDispositionInformation);
1595     }
1596 
1597     /* Cleanup/close portion */
1598     if (DestinationHandle != INVALID_HANDLE_VALUE)
1599     {
1600         NtClose(DestinationHandle);
1601     }
1602 
1603     if (SourceHandle != INVALID_HANDLE_VALUE)
1604     {
1605         NtClose(SourceHandle);
1606     }
1607 
1608     /* Set last error if any, and quit */
1609     if (NT_SUCCESS(Status))
1610     {
1611         if (!NT_SUCCESS(OldStatus))
1612         {
1613             BaseSetLastNTError(OldStatus);
1614         }
1615     }
1616     else
1617     {
1618         BaseSetLastNTError(Status);
1619     }
1620 
1621     return NT_SUCCESS(Status);
1622 }
1623 
1624 /* EOF */
1625