from w3seek: make the tree compile with gcc4 again
[reactos.git] / reactos / lib / recyclebin / recyclebin_v5.c
1 /*
2 * PROJECT: Recycle bin management
3 * LICENSE: GPL v2 - See COPYING in the top level directory
4 * FILE: lib/recyclebin/openclose.c
5 * PURPOSE: Deals with recycle bins of Windows 2000/XP/2003
6 * PROGRAMMERS: Copyright 2006 Hervé Poussineau (hpoussin@reactos.org)
7 */
8
9 #include "recyclebin_v5.h"
10
11 VOID
12 InitializeCallbacks5(
13 IN OUT PRECYCLEBIN_CALLBACKS Callbacks)
14 {
15 Callbacks->CloseHandle = CloseHandle5;
16 Callbacks->DeleteFile = DeleteFile5;
17 Callbacks->EmptyRecycleBin = EmptyRecycleBin5;
18 Callbacks->EnumerateFiles = EnumerateFiles5;
19 Callbacks->GetDetails = GetDetails5;
20 Callbacks->RestoreFile = RestoreFile5;
21 }
22
23 static BOOL
24 CloseHandle5(
25 IN HANDLE hDeletedFile)
26 {
27 /* Nothing to do, as hDeletedFile is simply a DWORD... */
28 return TRUE;
29 }
30
31 static BOOL
32 DeleteFile5(
33 IN PRECYCLE_BIN bin,
34 IN LPCWSTR FullPath,
35 IN LPCWSTR FileName)
36 {
37 HANDLE hFile = INVALID_HANDLE_VALUE;
38 INFO2_HEADER Header;
39 DELETED_FILE_RECORD DeletedFile;
40 DWORD bytesRead, bytesWritten;
41 ULARGE_INTEGER fileSize;
42 SYSTEMTIME SystemTime;
43 WCHAR RootDir[4];
44 WCHAR DeletedFileName[2 * MAX_PATH];
45 LPCWSTR Extension;
46 DWORD ClusterSize, BytesPerSector, SectorsPerCluster;
47 BOOL ret = FALSE;
48
49 if (wcslen(FullPath) >= MAX_PATH)
50 {
51 /* Unable to store a too long path in recycle bin */
52 SetLastError(ERROR_INVALID_NAME);
53 goto cleanup;
54 }
55
56 hFile = CreateFileW(FullPath, 0, 0, NULL, OPEN_EXISTING, 0, NULL);
57 if (hFile == INVALID_HANDLE_VALUE)
58 goto cleanup;
59
60 if (SetFilePointer(bin->hInfo, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
61 goto cleanup;
62 if (!ReadFile(bin->hInfo, &Header, sizeof(INFO2_HEADER), &bytesRead, NULL))
63 goto cleanup;
64 if (bytesRead != sizeof(INFO2_HEADER) || Header.dwRecordSize == 0)
65 {
66 SetLastError(ERROR_GEN_FAILURE);
67 goto cleanup;
68 }
69
70 if (Header.dwVersion != 5 || Header.dwRecordSize != sizeof(DELETED_FILE_RECORD))
71 {
72 SetLastError(ERROR_GEN_FAILURE);
73 goto cleanup;
74 }
75
76 /* Get file size */
77 #if 0
78 if (!GetFileSizeEx(hFile, &fileSize))
79 goto cleanup;
80 #else
81 fileSize.u.LowPart = GetFileSize(hFile, &fileSize.u.HighPart);
82 if (fileSize.u.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
83 goto cleanup;
84 #endif
85 /* Check if file size is > 4Gb */
86 if (fileSize.u.HighPart != 0)
87 {
88 /* FIXME: how to delete files >= 4Gb? */
89 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
90 goto cleanup;
91 }
92 Header.dwTotalLogicalSize += fileSize.u.LowPart;
93
94 /* Generate new name */
95 Header.dwHighestRecordUniqueId++;
96 Extension = wcsrchr(FileName, '.');
97 wsprintfW(DeletedFileName, L"%s\\D%c%lu%s", bin->Folder, FullPath[0] - 'A' + 'a', Header.dwHighestRecordUniqueId, Extension);
98
99 /* Get cluster size */
100 wsprintfW(RootDir, L"%c:\\", bin->Folder[0]);
101 if (!GetDiskFreeSpaceW(RootDir, &SectorsPerCluster, &BytesPerSector, NULL, NULL))
102 goto cleanup;
103 ClusterSize = BytesPerSector * SectorsPerCluster;
104
105 /* Get current time */
106 GetSystemTime(&SystemTime);
107 if (!SystemTimeToFileTime(&SystemTime, &DeletedFile.DeletionTime))
108 goto cleanup;
109
110 /* Update INFO2 */
111 memset(&DeletedFile, 0, sizeof(DELETED_FILE_RECORD));
112 if (WideCharToMultiByte(CP_ACP, 0, FullPath, -1, DeletedFile.FileNameA, MAX_PATH, NULL, NULL) == 0)
113 {
114 SetLastError(ERROR_INVALID_NAME);
115 goto cleanup;
116 }
117 DeletedFile.dwRecordUniqueId = Header.dwHighestRecordUniqueId;
118 DeletedFile.dwDriveNumber = tolower(bin->Folder[0]) - 'a';
119 DeletedFile.dwPhysicalFileSize = ROUND_UP(fileSize.u.LowPart, ClusterSize);
120 wcscpy(DeletedFile.FileNameW, FullPath);
121
122 if (!SetFilePointer(bin->hInfo, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
123 goto cleanup;
124 if (!WriteFile(bin->hInfo, &DeletedFile, sizeof(DELETED_FILE_RECORD), &bytesWritten, NULL))
125 goto cleanup;
126 if (bytesWritten != sizeof(DELETED_FILE_RECORD))
127 {
128 SetLastError(ERROR_GEN_FAILURE);
129 goto cleanup;
130 }
131 Header.dwNumberOfEntries++;
132 if (SetFilePointer(bin->hInfo, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
133 {
134 goto cleanup;
135 }
136 if (!WriteFile(bin->hInfo, &Header, sizeof(INFO2_HEADER), &bytesWritten, NULL))
137 goto cleanup;
138 if (bytesWritten != sizeof(INFO2_HEADER))
139 {
140 SetLastError(ERROR_GEN_FAILURE);
141 goto cleanup;
142 }
143
144 /* Move file */
145 if (!MoveFileW(FullPath, DeletedFileName))
146 goto cleanup;
147
148 ret = TRUE;
149
150 cleanup:
151 if (hFile != INVALID_HANDLE_VALUE)
152 CloseHandle(hFile);
153 return ret;
154 }
155
156 static BOOL
157 EmptyRecycleBin5(
158 IN PRECYCLE_BIN* bin)
159 {
160 LPWSTR InfoFile = NULL;
161 BOOL ret = FALSE;
162
163 InfoFile = HeapAlloc(GetProcessHeap(), 0, wcslen((*bin)->InfoFile) * sizeof(WCHAR) + sizeof(UNICODE_NULL));
164 if (!InfoFile)
165 {
166 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
167 goto cleanup;
168 }
169 wcscpy(InfoFile, (*bin)->InfoFile);
170
171 /* Delete all files in the recycle bin */
172 if (!EnumerateFiles5(*bin, IntEmptyRecycleBinCallback, *bin))
173 goto cleanup;
174
175 /* Delete INFO2 */
176 if (!DereferenceHandle(&(*bin)->refCount))
177 goto cleanup;
178 if (!DeleteFileW(InfoFile))
179 goto cleanup;
180 *bin = NULL;
181 ret = TRUE;
182
183 cleanup:
184 HeapFree(GetProcessHeap(), 0, InfoFile);
185 return ret;
186 }
187
188 static BOOL
189 EnumerateFiles5(
190 IN PRECYCLE_BIN bin,
191 IN PINT_ENUMERATE_RECYCLEBIN_CALLBACK pFnCallback,
192 IN PVOID Context)
193 {
194 INFO2_HEADER Header;
195 DELETED_FILE_RECORD DeletedFile;
196 DWORD bytesRead, dwEntries;
197 BOOL ret = FALSE;
198
199 if (SetFilePointer(bin->hInfo, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
200 goto cleanup;
201 if (!ReadFile(bin->hInfo, &Header, sizeof(INFO2_HEADER), &bytesRead, NULL))
202 goto cleanup;
203 if (bytesRead != sizeof(INFO2_HEADER) || Header.dwRecordSize == 0)
204 {
205 SetLastError(ERROR_GEN_FAILURE);
206 goto cleanup;
207 }
208
209 if (Header.dwVersion != 5 || Header.dwRecordSize != sizeof(DELETED_FILE_RECORD))
210 {
211 SetLastError(ERROR_GEN_FAILURE);
212 goto cleanup;
213 }
214
215 SetLastError(ERROR_SUCCESS);
216 for (dwEntries = 0; dwEntries < Header.dwNumberOfEntries; dwEntries++)
217 {
218 if (!ReadFile(bin->hInfo, &DeletedFile, Header.dwRecordSize, &bytesRead, NULL))
219 goto cleanup;
220 if (bytesRead != Header.dwRecordSize)
221 {
222 SetLastError(ERROR_GEN_FAILURE);
223 goto cleanup;
224 }
225 if (!pFnCallback(Context, (HANDLE)(ULONG_PTR)DeletedFile.dwRecordUniqueId))
226 goto cleanup;
227 if (SetFilePointer(bin->hInfo, sizeof(INFO2_HEADER) + Header.dwRecordSize * dwEntries, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
228 goto cleanup;
229 }
230
231 ret = TRUE;
232
233 cleanup:
234 return ret;
235 }
236
237 static BOOL
238 GetDetails5(
239 IN PRECYCLE_BIN bin,
240 IN HANDLE hDeletedFile,
241 IN DWORD BufferSize,
242 IN OUT PDELETED_FILE_DETAILS_W FileDetails,
243 OUT LPDWORD RequiredSize OPTIONAL)
244 {
245 DELETED_FILE_RECORD DeletedFile;
246 SIZE_T Needed;
247 LPWSTR FullName = NULL;
248 HANDLE hFile = INVALID_HANDLE_VALUE;
249 BOOL ret = FALSE;
250
251 if (!IntSearchRecord(bin, hDeletedFile, &DeletedFile, NULL))
252 goto cleanup;
253 Needed = (DWORD)FIELD_OFFSET(DELETED_FILE_DETAILS_W, FileName) + (wcslen(DeletedFile.FileNameW) + 1) * sizeof(WCHAR);
254 if (RequiredSize)
255 *RequiredSize = (DWORD)Needed;
256 if (Needed > BufferSize)
257 {
258 SetLastError(ERROR_INSUFFICIENT_BUFFER);
259 goto cleanup;
260 }
261
262 if (!IntGetFullName(bin, &DeletedFile, &FullName))
263 goto cleanup;
264
265 /* Open file */
266 FileDetails->Attributes = GetFileAttributesW(FullName);
267 if (FileDetails->Attributes == INVALID_FILE_ATTRIBUTES)
268 goto cleanup;
269 if (FileDetails->Attributes & FILE_ATTRIBUTE_DIRECTORY)
270 hFile = CreateFileW(FullName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
271 else
272 hFile = CreateFileW(FullName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
273 if (hFile == INVALID_HANDLE_VALUE)
274 goto cleanup;
275
276 /* Fill returned structure */
277 if (!GetFileTime(hFile, NULL, NULL, &FileDetails->LastModification))
278 goto cleanup;
279 memcpy(&FileDetails->DeletionTime, &DeletedFile.DeletionTime, sizeof(FILETIME));
280 FileDetails->FileSize.u.LowPart = GetFileSize(hFile, &FileDetails->FileSize.u.HighPart);
281 if (FileDetails->FileSize.u.LowPart == INVALID_FILE_SIZE)
282 goto cleanup;
283 FileDetails->PhysicalFileSize.u.HighPart = 0;
284 FileDetails->PhysicalFileSize.u.LowPart = DeletedFile.dwPhysicalFileSize;
285 wcscpy(FileDetails->FileName, DeletedFile.FileNameW);
286
287 ret = TRUE;
288
289 cleanup:
290 HeapFree(GetProcessHeap(), 0, FullName);
291 if (hFile != INVALID_HANDLE_VALUE)
292 CloseHandle(hFile);
293 return ret;
294 }
295
296 static BOOL
297 RestoreFile5(
298 IN PRECYCLE_BIN bin,
299 IN HANDLE hDeletedFile)
300 {
301 INFO2_HEADER Header;
302 DWORD bytesRead, bytesWritten;
303 LARGE_INTEGER Position;
304 DELETED_FILE_RECORD DeletedFile, LastFile;
305 LPWSTR FullName = NULL;
306 BOOL ret = FALSE;
307
308 if (SetFilePointer(bin->hInfo, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
309 goto cleanup;
310 if (!ReadFile(bin->hInfo, &Header, sizeof(INFO2_HEADER), &bytesRead, NULL))
311 goto cleanup;
312 if (bytesRead != sizeof(INFO2_HEADER) || Header.dwRecordSize == 0)
313 {
314 SetLastError(ERROR_GEN_FAILURE);
315 goto cleanup;
316 }
317
318 if (Header.dwVersion != 5 || Header.dwRecordSize != sizeof(DELETED_FILE_RECORD))
319 {
320 SetLastError(ERROR_GEN_FAILURE);
321 goto cleanup;
322 }
323
324 /* Search deleted entry */
325 if (!IntSearchRecord(bin, hDeletedFile, &DeletedFile, &Position))
326 goto cleanup;
327 /* Get destination full name */
328 if (!IntGetFullName(bin, &DeletedFile, &FullName))
329 goto cleanup;
330 /* Restore file */
331 if (!MoveFileW(FullName, DeletedFile.FileNameW))
332 goto cleanup;
333
334 /* Update INFO2 */
335 /* 1) If not last entry, copy last entry to the current one */
336 if (SetFilePointer(bin->hInfo, -sizeof(DELETED_FILE_RECORD), NULL, FILE_END) == INVALID_SET_FILE_POINTER)
337 goto cleanup;
338 if (!ReadFile(bin->hInfo, &LastFile, sizeof(DELETED_FILE_RECORD), &bytesRead, NULL))
339 goto cleanup;
340 if (bytesRead != sizeof(DELETED_FILE_RECORD))
341 {
342 SetLastError(ERROR_GEN_FAILURE);
343 goto cleanup;
344 }
345 if (LastFile.dwRecordUniqueId != DeletedFile.dwRecordUniqueId)
346 {
347 /* Move the last entry to the current one */
348 if (SetFilePointer(bin->hInfo, Position.u.LowPart, &Position.u.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
349 goto cleanup;
350 if (!WriteFile(bin->hInfo, &LastFile, sizeof(DELETED_FILE_RECORD), &bytesWritten, NULL))
351 goto cleanup;
352 if (bytesWritten != sizeof(DELETED_FILE_RECORD))
353 {
354 SetLastError(ERROR_GEN_FAILURE);
355 goto cleanup;
356 }
357 }
358 /* 2) Update the header */
359 Header.dwNumberOfEntries--;
360 if (SetFilePointer(bin->hInfo, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
361 goto cleanup;
362 if (!WriteFile(bin->hInfo, &Header, sizeof(INFO2_HEADER), &bytesWritten, NULL))
363 goto cleanup;
364 if (bytesWritten != sizeof(INFO2_HEADER))
365 {
366 SetLastError(ERROR_GEN_FAILURE);
367 goto cleanup;
368 }
369 /* 3) Truncate file */
370 if (SetFilePointer(bin->hInfo, -sizeof(DELETED_FILE_RECORD), NULL, FILE_END) == INVALID_SET_FILE_POINTER)
371 goto cleanup;
372 if (!SetEndOfFile(bin->hInfo))
373 goto cleanup;
374 ret = TRUE;
375
376 cleanup:
377 HeapFree(GetProcessHeap(), 0, FullName);
378 return ret;
379 }
380
381 static BOOL
382 IntDeleteRecursive(
383 IN LPCWSTR FullName)
384 {
385 DWORD RemovableAttributes = FILE_ATTRIBUTE_READONLY;
386 DWORD FileAttributes;
387 BOOL ret = FALSE;
388
389 FileAttributes = GetFileAttributesW(FullName);
390 if (FileAttributes == INVALID_FILE_ATTRIBUTES)
391 {
392 if (GetLastError() == ERROR_FILE_NOT_FOUND)
393 ret = TRUE;
394 goto cleanup;
395 }
396 if (FileAttributes & RemovableAttributes)
397 {
398 if (!SetFileAttributesW(FullName, FileAttributes & ~RemovableAttributes))
399 goto cleanup;
400 }
401 if (FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
402 {
403 /* Recursive deletion */
404 /* FIXME: recursive deletion */
405 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
406 goto cleanup;
407
408 if (!RemoveDirectoryW(FullName))
409 goto cleanup;
410 }
411 else
412 {
413 if (!DeleteFileW(FullName))
414 goto cleanup;
415 }
416 ret = TRUE;
417
418 cleanup:
419 return ret;
420 }
421
422 static BOOL
423 IntEmptyRecycleBinCallback(
424 IN PVOID Context,
425 IN HANDLE hDeletedFile)
426 {
427 PRECYCLE_BIN bin = (PRECYCLE_BIN)Context;
428 DELETED_FILE_RECORD DeletedFile;
429 LPWSTR FullName = NULL;
430 BOOL ret = FALSE;
431
432 if (!IntSearchRecord(bin, hDeletedFile, &DeletedFile, NULL))
433 goto cleanup;
434
435 if (!IntGetFullName(bin, &DeletedFile, &FullName))
436 goto cleanup;
437
438 if (!IntDeleteRecursive(FullName))
439 goto cleanup;
440 ret = TRUE;
441
442 cleanup:
443 HeapFree(GetProcessHeap(), 0, FullName);
444 return ret;
445 }
446
447 static BOOL
448 IntGetFullName(
449 IN PRECYCLE_BIN bin,
450 IN PDELETED_FILE_RECORD pDeletedFile,
451 OUT LPWSTR* pFullName)
452 {
453 SIZE_T Needed;
454 LPCWSTR Extension;
455 LPWSTR FullName = NULL;
456 BOOL ret = FALSE;
457
458 *pFullName = NULL;
459 Extension = wcsrchr(pDeletedFile->FileNameW, '.');
460 if (Extension < wcsrchr(pDeletedFile->FileNameW, '\\'))
461 Extension = NULL;
462 Needed = wcslen(bin->Folder) + 13;
463 if (Extension)
464 Needed += wcslen(Extension);
465 FullName = HeapAlloc(GetProcessHeap(), 0, Needed * sizeof(WCHAR));
466 if (!FullName)
467 {
468 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
469 goto cleanup;
470 }
471 wsprintfW(FullName, L"%s\\D%c%lu%s", bin->Folder, pDeletedFile->dwDriveNumber + 'a', pDeletedFile->dwRecordUniqueId, Extension);
472 *pFullName = FullName;
473 ret = TRUE;
474
475 cleanup:
476 if (!ret)
477 HeapFree(GetProcessHeap(), 0, FullName);
478 return ret;
479 }
480
481 static BOOL
482 IntSearchRecord(
483 IN PRECYCLE_BIN bin,
484 IN HANDLE hDeletedFile,
485 OUT PDELETED_FILE_RECORD pDeletedFile,
486 OUT PLARGE_INTEGER Position OPTIONAL)
487 {
488 INFO2_HEADER Header;
489 DELETED_FILE_RECORD DeletedFile;
490 DWORD bytesRead, dwEntries;
491 BOOL ret = FALSE;
492
493 if (SetFilePointer(bin->hInfo, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
494 goto cleanup;
495 if (!ReadFile(bin->hInfo, &Header, sizeof(INFO2_HEADER), &bytesRead, NULL))
496 goto cleanup;
497 if (bytesRead != sizeof(INFO2_HEADER) || Header.dwRecordSize == 0)
498 {
499 SetLastError(ERROR_GEN_FAILURE);
500 goto cleanup;
501 }
502
503 if (Header.dwVersion != 5 || Header.dwRecordSize != sizeof(DELETED_FILE_RECORD))
504 {
505 SetLastError(ERROR_GEN_FAILURE);
506 goto cleanup;
507 }
508
509 SetLastError(ERROR_SUCCESS);
510 for (dwEntries = 0; dwEntries < Header.dwNumberOfEntries; dwEntries++)
511 {
512 if (Position)
513 {
514 LARGE_INTEGER Zero;
515 Zero.QuadPart = 0;
516 if (!SetFilePointerEx(bin->hInfo, Zero, Position, FILE_CURRENT))
517 goto cleanup;
518 }
519 if (!ReadFile(bin->hInfo, &DeletedFile, Header.dwRecordSize, &bytesRead, NULL))
520 goto cleanup;
521 if (bytesRead != Header.dwRecordSize)
522 {
523 SetLastError(ERROR_GEN_FAILURE);
524 goto cleanup;
525 }
526 if (DeletedFile.dwRecordUniqueId == (DWORD)(ULONG_PTR)hDeletedFile)
527 {
528 memcpy(pDeletedFile, &DeletedFile, Header.dwRecordSize);
529 ret = TRUE;
530 goto cleanup;
531 }
532 }
533
534 /* Entry not found */
535 SetLastError(ERROR_INVALID_HANDLE);
536
537 cleanup:
538 return ret;
539 }