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