Fix some part of MoveFileExW Bug, Can not move folder, but it still can not move...
[reactos.git] / reactos / lib / kernel32 / file / move.c
1 /* $Id$
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS system libraries
5 * FILE: lib/kernel32/file/file.c
6 * PURPOSE: Directory functions
7 * PROGRAMMER: Ariadne ( ariadne@xs4all.nl)
8 * Gerhard W. Gruber (sparhawk_at_gmx.at)
9 * UPDATE HISTORY:
10 * Created 01/11/98
11 */
12
13 /* INCLUDES *****************************************************************/
14
15 #include <k32.h>
16 #include <malloc.h>
17
18 #define NDEBUG
19 #include "../include/debug.h"
20
21 /* GLOBALS *****************************************************************/
22
23 /* FUNCTIONS ****************************************************************/
24
25 static BOOL
26 AdjustFileAttributes (
27 LPCWSTR ExistingFileName,
28 LPCWSTR NewFileName
29 )
30 {
31 IO_STATUS_BLOCK IoStatusBlock;
32 FILE_BASIC_INFORMATION ExistingInfo,
33 NewInfo;
34 HANDLE hFile;
35 DWORD Attributes;
36 NTSTATUS errCode;
37 BOOL Result = FALSE;
38
39 hFile = CreateFileW (ExistingFileName,
40 FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
41 FILE_SHARE_READ,
42 NULL,
43 OPEN_EXISTING,
44 FILE_ATTRIBUTE_NORMAL,
45 NULL);
46 if (INVALID_HANDLE_VALUE != hFile)
47 {
48 errCode = NtQueryInformationFile (hFile,
49 &IoStatusBlock,
50 &ExistingInfo,
51 sizeof(FILE_BASIC_INFORMATION),
52 FileBasicInformation);
53 if (NT_SUCCESS (errCode))
54 {
55 if (0 != (ExistingInfo.FileAttributes & FILE_ATTRIBUTE_READONLY))
56 {
57 Attributes = ExistingInfo.FileAttributes;
58 ExistingInfo.FileAttributes &= ~ FILE_ATTRIBUTE_READONLY;
59 if (0 == (ExistingInfo.FileAttributes &
60 (FILE_ATTRIBUTE_HIDDEN |
61 FILE_ATTRIBUTE_SYSTEM |
62 FILE_ATTRIBUTE_ARCHIVE)))
63 {
64 ExistingInfo.FileAttributes |= FILE_ATTRIBUTE_NORMAL;
65 }
66 errCode = NtSetInformationFile (hFile,
67 &IoStatusBlock,
68 &ExistingInfo,
69 sizeof(FILE_BASIC_INFORMATION),
70 FileBasicInformation);
71 if (!NT_SUCCESS(errCode))
72 {
73 DPRINT("Removing READONLY attribute from source failed with status 0x%08x\n", errCode);
74 }
75 ExistingInfo.FileAttributes = Attributes;
76 }
77 CloseHandle(hFile);
78
79 if (NT_SUCCESS(errCode))
80 {
81 hFile = CreateFileW (NewFileName,
82 FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
83 FILE_SHARE_READ,
84 NULL,
85 OPEN_EXISTING,
86 FILE_ATTRIBUTE_NORMAL,
87 NULL);
88 if (INVALID_HANDLE_VALUE != hFile)
89 {
90 errCode = NtQueryInformationFile(hFile,
91 &IoStatusBlock,
92 &NewInfo,
93 sizeof(FILE_BASIC_INFORMATION),
94 FileBasicInformation);
95 if (NT_SUCCESS(errCode))
96 {
97 NewInfo.FileAttributes = (NewInfo.FileAttributes &
98 ~ (FILE_ATTRIBUTE_HIDDEN |
99 FILE_ATTRIBUTE_SYSTEM |
100 FILE_ATTRIBUTE_READONLY |
101 FILE_ATTRIBUTE_NORMAL)) |
102 (ExistingInfo.FileAttributes &
103 (FILE_ATTRIBUTE_HIDDEN |
104 FILE_ATTRIBUTE_SYSTEM |
105 FILE_ATTRIBUTE_READONLY |
106 FILE_ATTRIBUTE_NORMAL)) |
107 FILE_ATTRIBUTE_ARCHIVE;
108 NewInfo.CreationTime = ExistingInfo.CreationTime;
109 NewInfo.LastAccessTime = ExistingInfo.LastAccessTime;
110 NewInfo.LastWriteTime = ExistingInfo.LastWriteTime;
111 errCode = NtSetInformationFile (hFile,
112 &IoStatusBlock,
113 &NewInfo,
114 sizeof(FILE_BASIC_INFORMATION),
115 FileBasicInformation);
116 if (NT_SUCCESS(errCode))
117 {
118 Result = TRUE;
119 }
120 else
121 {
122 DPRINT("Setting attributes on dest file failed with status 0x%08x\n", errCode);
123 }
124 }
125 else
126 {
127 DPRINT("Obtaining attributes from dest file failed with status 0x%08x\n", errCode);
128 }
129 CloseHandle(hFile);
130 }
131 else
132 {
133 DPRINT("Opening dest file to set attributes failed with code %d\n", GetLastError());
134 }
135 }
136 }
137 else
138 {
139 DPRINT("Obtaining attributes from source file failed with status 0x%08x\n", errCode);
140 CloseHandle(hFile);
141 }
142 }
143 else
144 {
145 DPRINT("Opening source file to obtain attributes failed with code %d\n", GetLastError());
146 }
147
148 return Result;
149 }
150
151 /***********************************************************************
152 * add_boot_rename_entry
153 *
154 * Adds an entry to the registry that is loaded when windows boots and
155 * checks if there are some files to be removed or renamed/moved.
156 * <fn1> has to be valid and <fn2> may be NULL. If both pointers are
157 * non-NULL then the file is moved, otherwise it is deleted. The
158 * entry of the registrykey is always appended with two zero
159 * terminated strings. If <fn2> is NULL then the second entry is
160 * simply a single 0-byte. Otherwise the second filename goes
161 * there. The entries are prepended with \??\ before the path and the
162 * second filename gets also a '!' as the first character if
163 * MOVEFILE_REPLACE_EXISTING is set. After the final string another
164 * 0-byte follows to indicate the end of the strings.
165 * i.e.:
166 * \??\D:\test\file1[0]
167 * !\??\D:\test\file1_renamed[0]
168 * \??\D:\Test|delete[0]
169 * [0] <- file is to be deleted, second string empty
170 * \??\D:\test\file2[0]
171 * !\??\D:\test\file2_renamed[0]
172 * [0] <- indicates end of strings
173 *
174 * or:
175 * \??\D:\test\file1[0]
176 * !\??\D:\test\file1_renamed[0]
177 * \??\D:\Test|delete[0]
178 * [0] <- file is to be deleted, second string empty
179 * [0] <- indicates end of strings
180 *
181 */
182 static BOOL add_boot_rename_entry( LPCWSTR source, LPCWSTR dest, DWORD flags )
183 {
184 static const WCHAR ValueName[] = {'P','e','n','d','i','n','g',
185 'F','i','l','e','R','e','n','a','m','e',
186 'O','p','e','r','a','t','i','o','n','s',0};
187 static const WCHAR SessionW[] = {'M','a','c','h','i','n','e','\\',
188 'S','y','s','t','e','m','\\',
189 'C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\',
190 'C','o','n','t','r','o','l','\\',
191 'S','e','s','s','i','o','n',' ','M','a','n','a','g','e','r',0};
192 static const int info_size = FIELD_OFFSET( KEY_VALUE_PARTIAL_INFORMATION, Data );
193
194 OBJECT_ATTRIBUTES attr;
195 UNICODE_STRING nameW, source_name, dest_name;
196 KEY_VALUE_PARTIAL_INFORMATION *info;
197 BOOL rc = FALSE;
198 HANDLE Reboot = 0;
199 DWORD len1, len2;
200 DWORD DataSize = 0;
201 BYTE *Buffer = NULL;
202 WCHAR *p;
203
204 DPRINT("Add support to smss for keys created by MOVEFILE_DELAY_UNTIL_REBOOT\n");
205
206 if (!RtlDosPathNameToNtPathName_U( (LPWSTR)source, &source_name, NULL, NULL ))
207 {
208 SetLastError( ERROR_PATH_NOT_FOUND );
209 return FALSE;
210 }
211 dest_name.Buffer = NULL;
212 if (dest && !RtlDosPathNameToNtPathName_U( (LPWSTR)dest, &dest_name, NULL, NULL ))
213 {
214 RtlFreeUnicodeString( &source_name );
215 SetLastError( ERROR_PATH_NOT_FOUND );
216 return FALSE;
217 }
218
219 attr.Length = sizeof(attr);
220 attr.RootDirectory = 0;
221 attr.ObjectName = &nameW;
222 attr.Attributes = 0;
223 attr.SecurityDescriptor = NULL;
224 attr.SecurityQualityOfService = NULL;
225 RtlInitUnicodeString( &nameW, SessionW );
226
227 if (NtCreateKey( &Reboot, KEY_ALL_ACCESS, &attr, 0, NULL, 0, NULL ) != STATUS_SUCCESS)
228 {
229 DPRINT1("Error creating key for reboot managment [%s]\n",
230 "SYSTEM\\CurrentControlSet\\Control\\Session Manager");
231 RtlFreeUnicodeString( &source_name );
232 RtlFreeUnicodeString( &dest_name );
233 return FALSE;
234 }
235
236 len1 = source_name.Length + sizeof(WCHAR);
237 if (dest)
238 {
239 len2 = dest_name.Length + sizeof(WCHAR);
240 if (flags & MOVEFILE_REPLACE_EXISTING)
241 len2 += sizeof(WCHAR); /* Plus 1 because of the leading '!' */
242 }
243 else len2 = sizeof(WCHAR); /* minimum is the 0 characters for the empty second string */
244
245 RtlInitUnicodeString( &nameW, ValueName );
246
247 /* First we check if the key exists and if so how many bytes it already contains. */
248 if (NtQueryValueKey( Reboot, &nameW, KeyValuePartialInformation,
249 NULL, 0, &DataSize ) == STATUS_BUFFER_OVERFLOW)
250 {
251 if (!(Buffer = HeapAlloc( GetProcessHeap(), 0, DataSize + len1 + len2 + sizeof(WCHAR) )))
252 goto Quit;
253 if (NtQueryValueKey( Reboot, &nameW, KeyValuePartialInformation,
254 Buffer, DataSize, &DataSize )) goto Quit;
255 info = (KEY_VALUE_PARTIAL_INFORMATION *)Buffer;
256 if (info->Type != REG_MULTI_SZ) goto Quit;
257 if (DataSize > sizeof(info)) DataSize -= sizeof(WCHAR); /* remove terminating null (will be added back later) */
258 }
259 else
260 {
261 DataSize = info_size;
262 if (!(Buffer = HeapAlloc( GetProcessHeap(), 0, DataSize + len1 + len2 + sizeof(WCHAR) )))
263 goto Quit;
264 }
265
266 memcpy( Buffer + DataSize, source_name.Buffer, len1 );
267 DataSize += len1;
268 p = (WCHAR *)(Buffer + DataSize);
269 if (dest)
270 {
271 if (flags & MOVEFILE_REPLACE_EXISTING)
272 *p++ = '!';
273 memcpy( p, dest_name.Buffer, len2 );
274 DataSize += len2;
275 }
276 else
277 {
278 *p = 0;
279 DataSize += sizeof(WCHAR);
280 }
281
282 /* add final null */
283 p = (WCHAR *)(Buffer + DataSize);
284 *p = 0;
285 DataSize += sizeof(WCHAR);
286
287 rc = !NtSetValueKey(Reboot, &nameW, 0, REG_MULTI_SZ, Buffer + info_size, DataSize - info_size);
288
289 Quit:
290 RtlFreeUnicodeString( &source_name );
291 RtlFreeUnicodeString( &dest_name );
292 if (Reboot) NtClose(Reboot);
293 HeapFree( GetProcessHeap(), 0, Buffer );
294 return(rc);
295 }
296
297
298 /*
299 * @implemented
300 */
301 BOOL
302 STDCALL
303 MoveFileWithProgressW (
304 LPCWSTR lpExistingFileName,
305 LPCWSTR lpNewFileName,
306 LPPROGRESS_ROUTINE lpProgressRoutine,
307 LPVOID lpData,
308 DWORD dwFlags
309 )
310 {
311 HANDLE hFile = NULL;
312 IO_STATUS_BLOCK IoStatusBlock;
313 PFILE_RENAME_INFORMATION FileRename;
314 NTSTATUS errCode;
315 BOOL Result;
316 UNICODE_STRING DstPathU;
317
318 DPRINT("MoveFileWithProgressW()\n");
319
320 if (dwFlags & MOVEFILE_DELAY_UNTIL_REBOOT)
321 return add_boot_rename_entry( lpExistingFileName, lpNewFileName, dwFlags );
322
323 hFile = CreateFileW (lpExistingFileName,
324 GENERIC_ALL,
325 FILE_SHARE_WRITE|FILE_SHARE_READ,
326 NULL,
327 OPEN_EXISTING,
328 FILE_FLAG_BACKUP_SEMANTICS,
329 NULL);
330
331 if (hFile == INVALID_HANDLE_VALUE)
332 {
333 return FALSE;
334 }
335
336 /* validate & translate the filename */
337 if (!RtlDosPathNameToNtPathName_U ((LPWSTR)lpNewFileName,
338 &DstPathU,
339 NULL,
340 NULL))
341 {
342 DPRINT("Invalid destination path\n");
343 CloseHandle(hFile);
344 SetLastError(ERROR_PATH_NOT_FOUND);
345 return FALSE;
346 }
347
348 FileRename = alloca(sizeof(FILE_RENAME_INFORMATION) + DstPathU.Length);
349 if ((dwFlags & MOVEFILE_REPLACE_EXISTING) == MOVEFILE_REPLACE_EXISTING)
350 FileRename->ReplaceIfExists = TRUE;
351 else
352 FileRename->ReplaceIfExists = FALSE;
353
354 memcpy(FileRename->FileName, DstPathU.Buffer, DstPathU.Length);
355 RtlFreeHeap (RtlGetProcessHeap (),
356 0,
357 DstPathU.Buffer);
358 /*
359 * FIXME:
360 * Is the length the count of characters or the length of the buffer?
361 */
362 FileRename->FileNameLength = DstPathU.Length / sizeof(WCHAR);
363 errCode = NtSetInformationFile (hFile,
364 &IoStatusBlock,
365 FileRename,
366 sizeof(FILE_RENAME_INFORMATION) + DstPathU.Length,
367 FileRenameInformation);
368 CloseHandle(hFile);
369
370 /*
371 * FIXME:
372 * Fail now move the folder
373 * Before we fail at CreateFileW
374 */
375
376 if (NT_SUCCESS(errCode))
377 {
378 Result = TRUE;
379 }
380 else if (STATUS_NOT_SAME_DEVICE == errCode &&
381 MOVEFILE_COPY_ALLOWED == (dwFlags & MOVEFILE_COPY_ALLOWED))
382 {
383 Result = CopyFileExW (lpExistingFileName,
384 lpNewFileName,
385 lpProgressRoutine,
386 lpData,
387 NULL,
388 FileRename->ReplaceIfExists ? 0 : COPY_FILE_FAIL_IF_EXISTS);
389 if (Result)
390 {
391 /* Cleanup the source file */
392 AdjustFileAttributes(lpExistingFileName, lpNewFileName);
393 Result = DeleteFileW (lpExistingFileName);
394 }
395 }
396 #if 1
397 /* FIXME file rename not yet implemented in all FSDs so it will always
398 * fail, even when the move is to the same device
399 */
400 else if (STATUS_NOT_IMPLEMENTED == errCode)
401 {
402
403 UNICODE_STRING SrcPathU;
404
405 SrcPathU.Buffer = alloca(sizeof(WCHAR) * MAX_PATH);
406 SrcPathU.MaximumLength = MAX_PATH * sizeof(WCHAR);
407 SrcPathU.Length = GetFullPathNameW(lpExistingFileName, MAX_PATH, SrcPathU.Buffer, NULL);
408 if (SrcPathU.Length >= MAX_PATH)
409 {
410 SetLastError(ERROR_FILENAME_EXCED_RANGE);
411 return FALSE;
412 }
413 SrcPathU.Length *= sizeof(WCHAR);
414
415 DstPathU.Buffer = alloca(sizeof(WCHAR) * MAX_PATH);
416 DstPathU.MaximumLength = MAX_PATH * sizeof(WCHAR);
417 DstPathU.Length = GetFullPathNameW(lpNewFileName, MAX_PATH, DstPathU.Buffer, NULL);
418 if (DstPathU.Length >= MAX_PATH)
419 {
420 SetLastError(ERROR_FILENAME_EXCED_RANGE);
421 return FALSE;
422 }
423 DstPathU.Length *= sizeof(WCHAR);
424
425 if (0 == RtlCompareUnicodeString(&SrcPathU, &DstPathU, TRUE))
426 {
427 /* Source and destination file are the same, nothing to do */
428 return TRUE;
429 }
430
431 Result = CopyFileExW (lpExistingFileName,
432 lpNewFileName,
433 lpProgressRoutine,
434 lpData,
435 NULL,
436 FileRename->ReplaceIfExists ? 0 : COPY_FILE_FAIL_IF_EXISTS);
437 if (Result)
438 {
439 /* Cleanup the source file */
440 AdjustFileAttributes(lpExistingFileName, lpNewFileName);
441 Result = DeleteFileW (lpExistingFileName);
442 }
443 }
444 #endif
445 else
446 {
447 SetLastErrorByStatus (errCode);
448 Result = FALSE;
449 }
450 return Result;
451 }
452
453
454 /*
455 * @implemented
456 */
457 BOOL
458 STDCALL
459 MoveFileWithProgressA (
460 LPCSTR lpExistingFileName,
461 LPCSTR lpNewFileName,
462 LPPROGRESS_ROUTINE lpProgressRoutine,
463 LPVOID lpData,
464 DWORD dwFlags
465 )
466 {
467 PWCHAR ExistingFileNameW;
468 PWCHAR NewFileNameW;
469 BOOL ret;
470
471 if (!(ExistingFileNameW = FilenameA2W(lpExistingFileName, FALSE)))
472 return FALSE;
473
474 if (!(NewFileNameW= FilenameA2W(lpNewFileName, TRUE)))
475 return FALSE;
476
477 ret = MoveFileWithProgressW (ExistingFileNameW ,
478 NewFileNameW,
479 lpProgressRoutine,
480 lpData,
481 dwFlags);
482
483 RtlFreeHeap (RtlGetProcessHeap (), 0, NewFileNameW);
484
485 return ret;
486 }
487
488
489 /*
490 * @implemented
491 */
492 BOOL
493 STDCALL
494 MoveFileW (
495 LPCWSTR lpExistingFileName,
496 LPCWSTR lpNewFileName
497 )
498 {
499 return MoveFileExW (lpExistingFileName,
500 lpNewFileName,
501 MOVEFILE_COPY_ALLOWED);
502 }
503
504
505 /*
506 * @implemented
507 */
508 BOOL
509 STDCALL
510 MoveFileExW (
511 LPCWSTR lpExistingFileName,
512 LPCWSTR lpNewFileName,
513 DWORD dwFlags
514 )
515 {
516 return MoveFileWithProgressW (lpExistingFileName,
517 lpNewFileName,
518 NULL,
519 NULL,
520 dwFlags);
521 }
522
523
524 /*
525 * @implemented
526 */
527 BOOL
528 STDCALL
529 MoveFileA (
530 LPCSTR lpExistingFileName,
531 LPCSTR lpNewFileName
532 )
533 {
534 return MoveFileExA (lpExistingFileName,
535 lpNewFileName,
536 MOVEFILE_COPY_ALLOWED);
537 }
538
539
540 /*
541 * @implemented
542 */
543 BOOL
544 STDCALL
545 MoveFileExA (
546 LPCSTR lpExistingFileName,
547 LPCSTR lpNewFileName,
548 DWORD dwFlags
549 )
550 {
551 return MoveFileWithProgressA (lpExistingFileName,
552 lpNewFileName,
553 NULL,
554 NULL,
555 dwFlags);
556 }
557
558 /* EOF */