Clean up patch from filip, with small change from me, for MoveFileWithProgressW.
[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 BOOL folder = FALSE;
318
319 DPRINT("MoveFileWithProgressW()\n");
320
321 if (dwFlags & MOVEFILE_DELAY_UNTIL_REBOOT)
322 return add_boot_rename_entry( lpExistingFileName, lpNewFileName, dwFlags );
323
324 hFile = CreateFileW (lpExistingFileName,
325 GENERIC_ALL,
326 FILE_SHARE_WRITE|FILE_SHARE_READ,
327 NULL,
328 OPEN_EXISTING,
329 FILE_FLAG_BACKUP_SEMANTICS,
330 NULL);
331
332 if (hFile == INVALID_HANDLE_VALUE)
333 {
334 return FALSE;
335 }
336
337
338 /* validate & translate the filename */
339 if (!RtlDosPathNameToNtPathName_U ((LPWSTR)lpNewFileName,
340 &DstPathU,
341 NULL,
342 NULL))
343 {
344 DPRINT("Invalid destination path\n");
345 CloseHandle(hFile);
346 SetLastError(ERROR_PATH_NOT_FOUND);
347 return FALSE;
348 }
349
350 FileRename = alloca(sizeof(FILE_RENAME_INFORMATION) + DstPathU.Length);
351 if ((dwFlags & MOVEFILE_REPLACE_EXISTING) == MOVEFILE_REPLACE_EXISTING)
352 FileRename->ReplaceIfExists = TRUE;
353 else
354 FileRename->ReplaceIfExists = FALSE;
355
356 memcpy(FileRename->FileName, DstPathU.Buffer, DstPathU.Length);
357 RtlFreeHeap (RtlGetProcessHeap (),
358 0,
359 DstPathU.Buffer);
360 /*
361 * FIXME:
362 * Is the length the count of characters or the length of the buffer?
363 */
364 FileRename->FileNameLength = DstPathU.Length / sizeof(WCHAR);
365 errCode = NtSetInformationFile (hFile,
366 &IoStatusBlock,
367 FileRename,
368 sizeof(FILE_RENAME_INFORMATION) + DstPathU.Length,
369 FileRenameInformation);
370 CloseHandle(hFile);
371
372 if (GetFileAttributesW(lpExistingFileName) & FILE_ATTRIBUTE_DIRECTORY)
373 {
374 folder = TRUE;
375 }
376
377
378 /*
379 * FIXME:
380 * Fail now move the folder
381 * Before we fail at CreateFileW
382 */
383
384
385 if (NT_SUCCESS(errCode))
386 {
387 Result = TRUE;
388 }
389 else if (STATUS_NOT_IMPLEMENTED == errCode)
390 {
391 if (folder==FALSE)
392 {
393 Result = CopyFileExW (lpExistingFileName,
394 lpNewFileName,
395 lpProgressRoutine,
396 lpData,
397 NULL,
398 FileRename->ReplaceIfExists ? 0 : COPY_FILE_FAIL_IF_EXISTS);
399 if (Result)
400 {
401 /* Cleanup the source file */
402 AdjustFileAttributes(lpExistingFileName, lpNewFileName);
403 Result = DeleteFileW (lpExistingFileName);
404 }
405 }
406 else
407 {
408 /* move folder not complete code */
409 Result = CreateDirectoryW (lpNewFileName, NULL);
410 if (Result == FALSE) return FALSE;
411
412 /* add scan code for move the folder */
413
414 AdjustFileAttributes(lpExistingFileName, lpNewFileName);
415 Result = RemoveDirectoryW(lpExistingFileName);
416 }
417 }
418
419 #if 1
420 /* FIXME file rename not yet implemented in all FSDs so it will always
421 * fail, even when the move is to the same device
422 */
423 else if (STATUS_NOT_IMPLEMENTED == errCode)
424 {
425
426 UNICODE_STRING SrcPathU;
427
428 SrcPathU.Buffer = alloca(sizeof(WCHAR) * MAX_PATH);
429 SrcPathU.MaximumLength = MAX_PATH * sizeof(WCHAR);
430 SrcPathU.Length = GetFullPathNameW(lpExistingFileName, MAX_PATH, SrcPathU.Buffer, NULL);
431 if (SrcPathU.Length >= MAX_PATH)
432 {
433 SetLastError(ERROR_FILENAME_EXCED_RANGE);
434 return FALSE;
435 }
436 SrcPathU.Length *= sizeof(WCHAR);
437
438 DstPathU.Buffer = alloca(sizeof(WCHAR) * MAX_PATH);
439 DstPathU.MaximumLength = MAX_PATH * sizeof(WCHAR);
440 DstPathU.Length = GetFullPathNameW(lpNewFileName, MAX_PATH, DstPathU.Buffer, NULL);
441 if (DstPathU.Length >= MAX_PATH)
442 {
443 SetLastError(ERROR_FILENAME_EXCED_RANGE);
444 return FALSE;
445 }
446 DstPathU.Length *= sizeof(WCHAR);
447
448 if (0 == RtlCompareUnicodeString(&SrcPathU, &DstPathU, TRUE))
449 {
450 /* Source and destination file are the same, nothing to do */
451 return TRUE;
452 }
453
454 Result = CopyFileExW (lpExistingFileName,
455 lpNewFileName,
456 lpProgressRoutine,
457 lpData,
458 NULL,
459 FileRename->ReplaceIfExists ? 0 : COPY_FILE_FAIL_IF_EXISTS);
460 if (Result)
461 {
462 /* Cleanup the source file */
463 AdjustFileAttributes(lpExistingFileName, lpNewFileName);
464 Result = DeleteFileW (lpExistingFileName);
465 }
466 }
467 #endif
468 else
469 {
470 SetLastErrorByStatus (errCode);
471 Result = TRUE;
472 }
473 return Result;
474 }
475
476
477 /*
478 * @implemented
479 */
480 BOOL
481 STDCALL
482 MoveFileWithProgressA (
483 LPCSTR lpExistingFileName,
484 LPCSTR lpNewFileName,
485 LPPROGRESS_ROUTINE lpProgressRoutine,
486 LPVOID lpData,
487 DWORD dwFlags
488 )
489 {
490 PWCHAR ExistingFileNameW;
491 PWCHAR NewFileNameW;
492 BOOL ret;
493
494 if (!(ExistingFileNameW = FilenameA2W(lpExistingFileName, FALSE)))
495 return FALSE;
496
497 if (!(NewFileNameW= FilenameA2W(lpNewFileName, TRUE)))
498 return FALSE;
499
500 ret = MoveFileWithProgressW (ExistingFileNameW ,
501 NewFileNameW,
502 lpProgressRoutine,
503 lpData,
504 dwFlags);
505
506 RtlFreeHeap (RtlGetProcessHeap (), 0, NewFileNameW);
507
508 return ret;
509 }
510
511
512 /*
513 * @implemented
514 */
515 BOOL
516 STDCALL
517 MoveFileW (
518 LPCWSTR lpExistingFileName,
519 LPCWSTR lpNewFileName
520 )
521 {
522 return MoveFileExW (lpExistingFileName,
523 lpNewFileName,
524 MOVEFILE_COPY_ALLOWED);
525 }
526
527
528 /*
529 * @implemented
530 */
531 BOOL
532 STDCALL
533 MoveFileExW (
534 LPCWSTR lpExistingFileName,
535 LPCWSTR lpNewFileName,
536 DWORD dwFlags
537 )
538 {
539 return MoveFileWithProgressW (lpExistingFileName,
540 lpNewFileName,
541 NULL,
542 NULL,
543 dwFlags);
544 }
545
546
547 /*
548 * @implemented
549 */
550 BOOL
551 STDCALL
552 MoveFileA (
553 LPCSTR lpExistingFileName,
554 LPCSTR lpNewFileName
555 )
556 {
557 return MoveFileExA (lpExistingFileName,
558 lpNewFileName,
559 MOVEFILE_COPY_ALLOWED);
560 }
561
562
563 /*
564 * @implemented
565 */
566 BOOL
567 STDCALL
568 MoveFileExA (
569 LPCSTR lpExistingFileName,
570 LPCSTR lpNewFileName,
571 DWORD dwFlags
572 )
573 {
574 return MoveFileWithProgressA (lpExistingFileName,
575 lpNewFileName,
576 NULL,
577 NULL,
578 dwFlags);
579 }
580
581 /* EOF */