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