xref: /reactos/dll/win32/kernel32/client/file/move.c (revision 5bf0c00d)
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
BasepMoveFileDelayed(_In_ PUNICODE_STRING ExistingPath,_In_ PUNICODE_STRING NewPath,_In_ INT KeyId,_In_ BOOL CreateIfNotFound)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
BasepGetComputerNameFromNtPath(IN PUNICODE_STRING NewPath,IN HANDLE NewHandle,OUT PWSTR ComputerName,IN OUT PULONG ComputerNameLength)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
BasepNotifyTrackingService(IN OUT PHANDLE ExistingHandle,IN POBJECT_ATTRIBUTES ObjectAttributes,IN HANDLE NewHandle,IN PUNICODE_STRING NewPath)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
BasepOpenFileForMove(IN LPCWSTR File,OUT PUNICODE_STRING RelativeNtName,OUT LPWSTR * NtName,OUT PHANDLE FileHandle,OUT POBJECT_ATTRIBUTES ObjectAttributes,IN ACCESS_MASK DesiredAccess,IN ULONG ShareAccess,IN ULONG OpenOptions)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
BasepMoveFileCopyProgress(IN LARGE_INTEGER TotalFileSize,IN LARGE_INTEGER TotalBytesTransferred,IN LARGE_INTEGER StreamSize,IN LARGE_INTEGER StreamBytesTransferred,IN DWORD dwStreamNumber,IN DWORD dwCallbackReason,IN HANDLE hSourceFile,IN HANDLE hDestinationFile,IN LPVOID lpData OPTIONAL)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
MoveFileWithProgressW(IN LPCWSTR lpExistingFileName,IN LPCWSTR lpNewFileName,IN LPPROGRESS_ROUTINE lpProgressRoutine,IN LPVOID lpData,IN DWORD dwFlags)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
MoveFileWithProgressA(IN LPCSTR lpExistingFileName,IN LPCSTR lpNewFileName OPTIONAL,IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,IN LPVOID lpData OPTIONAL,IN DWORD dwFlags)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
MoveFileW(IN LPCWSTR lpExistingFileName,IN LPCWSTR lpNewFileName)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
MoveFileExW(IN LPCWSTR lpExistingFileName,IN LPCWSTR lpNewFileName OPTIONAL,IN DWORD dwFlags)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
MoveFileA(IN LPCSTR lpExistingFileName,IN LPCSTR lpNewFileName)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
MoveFileExA(IN LPCSTR lpExistingFileName,IN LPCSTR lpNewFileName OPTIONAL,IN DWORD dwFlags)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
ReplaceFileA(IN LPCSTR lpReplacedFileName,IN LPCSTR lpReplacementFileName,IN LPCSTR lpBackupFileName OPTIONAL,IN DWORD dwReplaceFlags,IN LPVOID lpExclude,IN LPVOID lpReserved)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
ReplaceFileW(LPCWSTR lpReplacedFileName,LPCWSTR lpReplacementFileName,LPCWSTR lpBackupFileName,DWORD dwReplaceFlags,LPVOID lpExclude,LPVOID lpReserved)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
PrivMoveFileIdentityW(IN LPCWSTR lpSource,IN LPCWSTR lpDestination,IN DWORD dwFlags)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