Add missing OPTIONAL information in prototypes
[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 OPTIONAL)
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 OPTIONAL,
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 /* Check parameters */
252 if (BufferSize > 0 && FileDetails == NULL)
253 {
254 SetLastError(ERROR_INVALID_PARAMETER);
255 goto cleanup;
256 }
257
258 if (!IntSearchRecord(bin, hDeletedFile, &DeletedFile, NULL))
259 goto cleanup;
260 Needed = FIELD_OFFSET(DELETED_FILE_DETAILS_W, FileName) + (wcslen(DeletedFile.FileNameW) + 1) * sizeof(WCHAR);
261 if (RequiredSize)
262 *RequiredSize = (DWORD)Needed;
263 if (Needed > BufferSize)
264 {
265 SetLastError(ERROR_INSUFFICIENT_BUFFER);
266 goto cleanup;
267 }
268
269 if (!IntGetFullName(bin, &DeletedFile, &FullName))
270 goto cleanup;
271
272 /* Open file */
273 FileDetails->Attributes = GetFileAttributesW(FullName);
274 if (FileDetails->Attributes == INVALID_FILE_ATTRIBUTES)
275 goto cleanup;
276 if (FileDetails->Attributes & FILE_ATTRIBUTE_DIRECTORY)
277 hFile = CreateFileW(FullName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
278 else
279 hFile = CreateFileW(FullName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
280 if (hFile == INVALID_HANDLE_VALUE)
281 goto cleanup;
282
283 /* Fill returned structure */
284 if (!GetFileTime(hFile, NULL, NULL, &FileDetails->LastModification))
285 goto cleanup;
286 memcpy(&FileDetails->DeletionTime, &DeletedFile.DeletionTime, sizeof(FILETIME));
287 FileDetails->FileSize.u.LowPart = GetFileSize(hFile, &FileDetails->FileSize.u.HighPart);
288 if (FileDetails->FileSize.u.LowPart == INVALID_FILE_SIZE)
289 goto cleanup;
290 FileDetails->PhysicalFileSize.u.HighPart = 0;
291 FileDetails->PhysicalFileSize.u.LowPart = DeletedFile.dwPhysicalFileSize;
292 wcscpy(FileDetails->FileName, DeletedFile.FileNameW);
293
294 ret = TRUE;
295
296 cleanup:
297 HeapFree(GetProcessHeap(), 0, FullName);
298 if (hFile != INVALID_HANDLE_VALUE)
299 CloseHandle(hFile);
300 return ret;
301 }
302
303 static BOOL
304 RestoreFile5(
305 IN PRECYCLE_BIN bin,
306 IN HANDLE hDeletedFile)
307 {
308 INFO2_HEADER Header;
309 DWORD bytesRead, bytesWritten;
310 LARGE_INTEGER Position;
311 DELETED_FILE_RECORD DeletedFile, LastFile;
312 LPWSTR FullName = NULL;
313 BOOL ret = FALSE;
314
315 if (SetFilePointer(bin->hInfo, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
316 goto cleanup;
317 if (!ReadFile(bin->hInfo, &Header, sizeof(INFO2_HEADER), &bytesRead, NULL))
318 goto cleanup;
319 if (bytesRead != sizeof(INFO2_HEADER) || Header.dwRecordSize == 0)
320 {
321 SetLastError(ERROR_GEN_FAILURE);
322 goto cleanup;
323 }
324
325 if (Header.dwVersion != 5 || Header.dwRecordSize != sizeof(DELETED_FILE_RECORD))
326 {
327 SetLastError(ERROR_GEN_FAILURE);
328 goto cleanup;
329 }
330
331 /* Search deleted entry */
332 if (!IntSearchRecord(bin, hDeletedFile, &DeletedFile, &Position))
333 goto cleanup;
334 /* Get destination full name */
335 if (!IntGetFullName(bin, &DeletedFile, &FullName))
336 goto cleanup;
337 /* Restore file */
338 if (!MoveFileW(FullName, DeletedFile.FileNameW))
339 goto cleanup;
340
341 /* Update INFO2 */
342 /* 1) If not last entry, copy last entry to the current one */
343 if (SetFilePointer(bin->hInfo, -sizeof(DELETED_FILE_RECORD), NULL, FILE_END) == INVALID_SET_FILE_POINTER)
344 goto cleanup;
345 if (!ReadFile(bin->hInfo, &LastFile, sizeof(DELETED_FILE_RECORD), &bytesRead, NULL))
346 goto cleanup;
347 if (bytesRead != sizeof(DELETED_FILE_RECORD))
348 {
349 SetLastError(ERROR_GEN_FAILURE);
350 goto cleanup;
351 }
352 if (LastFile.dwRecordUniqueId != DeletedFile.dwRecordUniqueId)
353 {
354 /* Move the last entry to the current one */
355 if (SetFilePointer(bin->hInfo, Position.u.LowPart, &Position.u.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
356 goto cleanup;
357 if (!WriteFile(bin->hInfo, &LastFile, sizeof(DELETED_FILE_RECORD), &bytesWritten, NULL))
358 goto cleanup;
359 if (bytesWritten != sizeof(DELETED_FILE_RECORD))
360 {
361 SetLastError(ERROR_GEN_FAILURE);
362 goto cleanup;
363 }
364 }
365 /* 2) Update the header */
366 Header.dwNumberOfEntries--;
367 if (SetFilePointer(bin->hInfo, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
368 goto cleanup;
369 if (!WriteFile(bin->hInfo, &Header, sizeof(INFO2_HEADER), &bytesWritten, NULL))
370 goto cleanup;
371 if (bytesWritten != sizeof(INFO2_HEADER))
372 {
373 SetLastError(ERROR_GEN_FAILURE);
374 goto cleanup;
375 }
376 /* 3) Truncate file */
377 if (SetFilePointer(bin->hInfo, -sizeof(DELETED_FILE_RECORD), NULL, FILE_END) == INVALID_SET_FILE_POINTER)
378 goto cleanup;
379 if (!SetEndOfFile(bin->hInfo))
380 goto cleanup;
381 ret = TRUE;
382
383 cleanup:
384 HeapFree(GetProcessHeap(), 0, FullName);
385 return ret;
386 }
387
388 static BOOL
389 IntDeleteRecursive(
390 IN LPCWSTR FullName)
391 {
392 DWORD RemovableAttributes = FILE_ATTRIBUTE_READONLY;
393 DWORD FileAttributes;
394 BOOL ret = FALSE;
395
396 FileAttributes = GetFileAttributesW(FullName);
397 if (FileAttributes == INVALID_FILE_ATTRIBUTES)
398 {
399 if (GetLastError() == ERROR_FILE_NOT_FOUND)
400 ret = TRUE;
401 goto cleanup;
402 }
403 if (FileAttributes & RemovableAttributes)
404 {
405 if (!SetFileAttributesW(FullName, FileAttributes & ~RemovableAttributes))
406 goto cleanup;
407 }
408 if (FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
409 {
410 /* Recursive deletion */
411 /* FIXME: recursive deletion */
412 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
413 goto cleanup;
414
415 if (!RemoveDirectoryW(FullName))
416 goto cleanup;
417 }
418 else
419 {
420 if (!DeleteFileW(FullName))
421 goto cleanup;
422 }
423 ret = TRUE;
424
425 cleanup:
426 return ret;
427 }
428
429 static BOOL
430 IntEmptyRecycleBinCallback(
431 IN PVOID Context,
432 IN HANDLE hDeletedFile)
433 {
434 PRECYCLE_BIN bin = (PRECYCLE_BIN)Context;
435 DELETED_FILE_RECORD DeletedFile;
436 LPWSTR FullName = NULL;
437 BOOL ret = FALSE;
438
439 if (!IntSearchRecord(bin, hDeletedFile, &DeletedFile, NULL))
440 goto cleanup;
441
442 if (!IntGetFullName(bin, &DeletedFile, &FullName))
443 goto cleanup;
444
445 if (!IntDeleteRecursive(FullName))
446 goto cleanup;
447 ret = TRUE;
448
449 cleanup:
450 HeapFree(GetProcessHeap(), 0, FullName);
451 return ret;
452 }
453
454 static BOOL
455 IntGetFullName(
456 IN PRECYCLE_BIN bin,
457 IN PDELETED_FILE_RECORD pDeletedFile,
458 OUT LPWSTR* pFullName)
459 {
460 SIZE_T Needed;
461 LPCWSTR Extension;
462 LPWSTR FullName = NULL;
463 BOOL ret = FALSE;
464
465 *pFullName = NULL;
466 Extension = wcsrchr(pDeletedFile->FileNameW, '.');
467 if (Extension < wcsrchr(pDeletedFile->FileNameW, '\\'))
468 Extension = NULL;
469 Needed = wcslen(bin->Folder) + 13;
470 if (Extension)
471 Needed += wcslen(Extension);
472 FullName = HeapAlloc(GetProcessHeap(), 0, Needed * sizeof(WCHAR));
473 if (!FullName)
474 {
475 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
476 goto cleanup;
477 }
478 wsprintfW(FullName, L"%s\\D%c%lu%s", bin->Folder, pDeletedFile->dwDriveNumber + 'a', pDeletedFile->dwRecordUniqueId, Extension);
479 *pFullName = FullName;
480 ret = TRUE;
481
482 cleanup:
483 if (!ret)
484 HeapFree(GetProcessHeap(), 0, FullName);
485 return ret;
486 }
487
488 static BOOL
489 IntSearchRecord(
490 IN PRECYCLE_BIN bin,
491 IN HANDLE hDeletedFile,
492 OUT PDELETED_FILE_RECORD pDeletedFile,
493 OUT PLARGE_INTEGER Position OPTIONAL)
494 {
495 INFO2_HEADER Header;
496 DELETED_FILE_RECORD DeletedFile;
497 DWORD bytesRead, dwEntries;
498 BOOL ret = FALSE;
499
500 if (SetFilePointer(bin->hInfo, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
501 goto cleanup;
502 if (!ReadFile(bin->hInfo, &Header, sizeof(INFO2_HEADER), &bytesRead, NULL))
503 goto cleanup;
504 if (bytesRead != sizeof(INFO2_HEADER) || Header.dwRecordSize == 0)
505 {
506 SetLastError(ERROR_GEN_FAILURE);
507 goto cleanup;
508 }
509
510 if (Header.dwVersion != 5 || Header.dwRecordSize != sizeof(DELETED_FILE_RECORD))
511 {
512 SetLastError(ERROR_GEN_FAILURE);
513 goto cleanup;
514 }
515
516 SetLastError(ERROR_SUCCESS);
517 for (dwEntries = 0; dwEntries < Header.dwNumberOfEntries; dwEntries++)
518 {
519 if (Position)
520 {
521 LARGE_INTEGER Zero;
522 Zero.QuadPart = 0;
523 if (!SetFilePointerEx(bin->hInfo, Zero, Position, FILE_CURRENT))
524 goto cleanup;
525 }
526 if (!ReadFile(bin->hInfo, &DeletedFile, Header.dwRecordSize, &bytesRead, NULL))
527 goto cleanup;
528 if (bytesRead != Header.dwRecordSize)
529 {
530 SetLastError(ERROR_GEN_FAILURE);
531 goto cleanup;
532 }
533 if (DeletedFile.dwRecordUniqueId == (DWORD)(ULONG_PTR)hDeletedFile)
534 {
535 memcpy(pDeletedFile, &DeletedFile, Header.dwRecordSize);
536 ret = TRUE;
537 goto cleanup;
538 }
539 }
540
541 /* Entry not found */
542 SetLastError(ERROR_INVALID_HANDLE);
543
544 cleanup:
545 return ret;
546 }