1 /*
2 * PROJECT: Recycle bin management
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Deals with recycle bins of Windows 2000/XP/2003
5 * COPYRIGHT: Copyright 2006-2007 Hervé Poussineau (hpoussin@reactos.org)
6 * Copyright 2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7 */
8
9 #include "recyclebin_private.h"
10 #include <atlstr.h>
11 #include <shlwapi.h>
12 #include "sddl.h"
13
14 EXTERN_C HRESULT WINAPI SHUpdateRecycleBinIcon(void);
15
16 class CZZWStr
17 {
18 LPWSTR m_sz;
19
20 public:
~CZZWStr()21 ~CZZWStr() { SHFree(m_sz); }
CZZWStr()22 CZZWStr() : m_sz(NULL) {}
23 CZZWStr(const CZZWStr&) = delete;
24 CZZWStr& operator=(const CZZWStr&) = delete;
25
Initialize(LPCWSTR Str)26 bool Initialize(LPCWSTR Str)
27 {
28 SIZE_T cch = wcslen(Str) + 1;
29 m_sz = (LPWSTR)SHAlloc((cch + 1) * sizeof(*Str));
30 if (!m_sz)
31 return false;
32 CopyMemory(m_sz, Str, cch * sizeof(*Str));
33 m_sz[cch] = UNICODE_NULL; // Double-null terminate
34 return true;
35 }
c_str()36 inline LPWSTR c_str() { return m_sz; }
37 };
38
SHELL_SingleFileOperation(HWND hWnd,UINT Op,LPCWSTR pszFrom,LPCWSTR pszTo,FILEOP_FLAGS Flags)39 static int SHELL_SingleFileOperation(HWND hWnd, UINT Op, LPCWSTR pszFrom, LPCWSTR pszTo, FILEOP_FLAGS Flags)
40 {
41 CZZWStr szzFrom, szzTo;
42 if (!szzFrom.Initialize(pszFrom) || !szzTo.Initialize(pszTo))
43 return ERROR_OUTOFMEMORY; // Note: Not one of the DE errors but also not in the DE range
44 SHFILEOPSTRUCTW fos = { hWnd, Op, szzFrom.c_str(), szzTo.c_str(), Flags };
45 return SHFileOperationW(&fos);
46 }
47
48 static BOOL
IntDeleteRecursive(IN LPCWSTR FullName)49 IntDeleteRecursive(
50 IN LPCWSTR FullName)
51 {
52 DWORD RemovableAttributes = FILE_ATTRIBUTE_READONLY;
53 WIN32_FIND_DATAW FindData;
54 HANDLE hSearch = INVALID_HANDLE_VALUE;
55 LPWSTR FullPath = NULL, pFilePart;
56 DWORD FileAttributes;
57 SIZE_T dwLength;
58 BOOL ret = FALSE;
59
60 FileAttributes = GetFileAttributesW(FullName);
61 if (FileAttributes == INVALID_FILE_ATTRIBUTES)
62 {
63 if (GetLastError() == ERROR_FILE_NOT_FOUND)
64 ret = TRUE;
65 goto cleanup;
66 }
67 if (FileAttributes & RemovableAttributes)
68 {
69 if (!SetFileAttributesW(FullName, FileAttributes & ~RemovableAttributes))
70 goto cleanup;
71 }
72 if (FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
73 {
74 /* Prepare file specification */
75 dwLength = wcslen(FullName);
76 FullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (dwLength + 1 + MAX_PATH + 1) * sizeof(WCHAR));
77 if (!FullPath)
78 {
79 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
80 goto cleanup;
81 }
82 wcscpy(FullPath, FullName);
83 if (FullPath[dwLength - 1] != '\\')
84 {
85 FullPath[dwLength] = '\\';
86 dwLength++;
87 }
88 pFilePart = &FullPath[dwLength];
89 wcscpy(pFilePart, L"*");
90
91 /* Enumerate contents, and delete it */
92 hSearch = FindFirstFileW(FullPath, &FindData);
93 if (hSearch == INVALID_HANDLE_VALUE)
94 goto cleanup;
95 do
96 {
97 if (!(FindData.cFileName[0] == '.' &&
98 (FindData.cFileName[1] == '\0' || (FindData.cFileName[1] == '.' && FindData.cFileName[2] == '\0'))))
99 {
100 wcscpy(pFilePart, FindData.cFileName);
101 if (!IntDeleteRecursive(FullPath))
102 {
103 FindClose(hSearch);
104 goto cleanup;
105 }
106 }
107 }
108 while (FindNextFileW(hSearch, &FindData));
109 FindClose(hSearch);
110 if (GetLastError() != ERROR_NO_MORE_FILES)
111 goto cleanup;
112
113 /* Remove (now empty) directory */
114 if (!RemoveDirectoryW(FullName))
115 goto cleanup;
116 }
117 else
118 {
119 if (!DeleteFileW(FullName))
120 goto cleanup;
121 }
122 ret = TRUE;
123
124 cleanup:
125 HeapFree(GetProcessHeap(), 0, FullPath);
126 return ret;
127 }
128
129 class RecycleBin5 : public IRecycleBin5
130 {
131 public:
132 RecycleBin5();
133 virtual ~RecycleBin5();
134
135 HRESULT Init(_In_ LPCWSTR VolumePath);
136
137 /* IUnknown interface */
138 STDMETHODIMP QueryInterface(_In_ REFIID riid, _Out_ void **ppvObject) override;
139 STDMETHODIMP_(ULONG) AddRef() override;
140 STDMETHODIMP_(ULONG) Release() override;
141
142 /* IRecycleBin interface */
143 STDMETHODIMP DeleteFile(_In_ LPCWSTR szFileName) override;
144 STDMETHODIMP EmptyRecycleBin() override;
145 STDMETHODIMP EnumObjects(_Out_ IRecycleBinEnumList **ppEnumList) override;
GetDirectory(LPWSTR szPath)146 STDMETHODIMP GetDirectory(LPWSTR szPath) override
147 {
148 if (!m_Folder[0])
149 return E_UNEXPECTED;
150 lstrcpynW(szPath, m_Folder, MAX_PATH);
151 return S_OK;
152 }
153
154 /* IRecycleBin5 interface */
155 STDMETHODIMP Delete(
156 _In_ LPCWSTR pDeletedFileName,
157 _In_ DELETED_FILE_RECORD *pDeletedFile) override;
158 STDMETHODIMP Restore(
159 _In_ LPCWSTR pDeletedFileName,
160 _In_ DELETED_FILE_RECORD *pDeletedFile) override;
161 STDMETHODIMP OnClosing(_In_ IRecycleBinEnumList *prbel) override;
162
163 protected:
164 LONG m_ref;
165 HANDLE m_hInfo;
166 HANDLE m_hInfoMapped;
167 DWORD m_EnumeratorCount;
168 CStringW m_VolumePath;
169 CStringW m_Folder; /* [drive]:\[RECYCLE_BIN_DIRECTORY]\{SID} */
170 };
171
QueryInterface(_In_ REFIID riid,_Out_ void ** ppvObject)172 STDMETHODIMP RecycleBin5::QueryInterface(_In_ REFIID riid, _Out_ void **ppvObject)
173 {
174 TRACE("(%p, %s, %p)\n", this, debugstr_guid(&riid), ppvObject);
175
176 if (!ppvObject)
177 return E_POINTER;
178
179 if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IRecycleBin))
180 *ppvObject = static_cast<IRecycleBin5 *>(this);
181 else if (IsEqualIID(riid, IID_IRecycleBin5))
182 *ppvObject = static_cast<IRecycleBin5 *>(this);
183 else
184 {
185 *ppvObject = NULL;
186 return E_NOINTERFACE;
187 }
188
189 AddRef();
190 return S_OK;
191 }
192
STDMETHODIMP_(ULONG)193 STDMETHODIMP_(ULONG) RecycleBin5::AddRef()
194 {
195 TRACE("(%p)\n", this);
196 return InterlockedIncrement(&m_ref);
197 }
198
~RecycleBin5()199 RecycleBin5::~RecycleBin5()
200 {
201 TRACE("(%p)\n", this);
202
203 if (m_hInfo && m_hInfo != INVALID_HANDLE_VALUE)
204 CloseHandle(m_hInfo);
205 if (m_hInfoMapped)
206 CloseHandle(m_hInfoMapped);
207 }
208
STDMETHODIMP_(ULONG)209 STDMETHODIMP_(ULONG) RecycleBin5::Release()
210 {
211 TRACE("(%p)\n", this);
212
213 ULONG refCount = InterlockedDecrement(&m_ref);
214 if (refCount == 0)
215 delete this;
216 return refCount;
217 }
218
DeleteFile(_In_ LPCWSTR szFileName)219 STDMETHODIMP RecycleBin5::DeleteFile(_In_ LPCWSTR szFileName)
220 {
221 LPWSTR szFullName = NULL;
222 DWORD dwBufferLength = 0;
223 LPWSTR lpFilePart;
224 LPCWSTR Extension;
225 CStringW DeletedFileName;
226 WCHAR szUniqueId[64];
227 DWORD len;
228 HANDLE hFile = INVALID_HANDLE_VALUE;
229 PINFO2_HEADER pHeader = NULL;
230 PDELETED_FILE_RECORD pDeletedFile;
231 ULARGE_INTEGER FileSize;
232 DWORD dwAttributes, dwEntries;
233 SYSTEMTIME SystemTime;
234 DWORD ClusterSize, BytesPerSector, SectorsPerCluster;
235 HRESULT hr;
236 WIN32_FIND_DATAW wfd = {};
237
238 TRACE("(%p, %s)\n", this, debugstr_w(szFileName));
239
240 if (m_EnumeratorCount != 0)
241 return HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION);
242
243 /* Get full file name */
244 while (TRUE)
245 {
246 len = GetFullPathNameW(szFileName, dwBufferLength, szFullName, &lpFilePart);
247 if (len == 0)
248 {
249 if (szFullName)
250 CoTaskMemFree(szFullName);
251 return HResultFromWin32(GetLastError());
252 }
253 else if (len < dwBufferLength)
254 break;
255 if (szFullName)
256 CoTaskMemFree(szFullName);
257 dwBufferLength = len;
258 szFullName = (LPWSTR)CoTaskMemAlloc(dwBufferLength * sizeof(WCHAR));
259 if (!szFullName)
260 return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
261 }
262
263 /* Check if file exists */
264 dwAttributes = GetFileAttributesW(szFullName);
265 if (dwAttributes == INVALID_FILE_ATTRIBUTES)
266 {
267 CoTaskMemFree(szFullName);
268 return HResultFromWin32(GetLastError());
269 }
270
271 if (dwBufferLength < 2 || szFullName[1] != ':')
272 {
273 /* Not a local file */
274 CoTaskMemFree(szFullName);
275 return HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
276 }
277
278 hFile = CreateFileW(szFullName, 0, 0, NULL, OPEN_EXISTING, (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : 0, NULL);
279 if (hFile == INVALID_HANDLE_VALUE)
280 {
281 hr = HResultFromWin32(GetLastError());
282 goto cleanup;
283 }
284
285 /* Increase INFO2 file size */
286 CloseHandle(m_hInfoMapped);
287 SetFilePointer(m_hInfo, sizeof(DELETED_FILE_RECORD), NULL, FILE_END);
288 SetEndOfFile(m_hInfo);
289 m_hInfoMapped = CreateFileMappingW(m_hInfo, NULL, PAGE_READWRITE | SEC_COMMIT, 0, 0, NULL);
290 if (!m_hInfoMapped)
291 {
292 hr = HResultFromWin32(GetLastError());
293 goto cleanup;
294 }
295
296 /* Open INFO2 file */
297 pHeader = (PINFO2_HEADER)MapViewOfFile(m_hInfoMapped, FILE_MAP_WRITE, 0, 0, 0);
298 if (!pHeader)
299 {
300 hr = HResultFromWin32(GetLastError());
301 goto cleanup;
302 }
303
304 /* Get number of entries */
305 FileSize.u.LowPart = GetFileSize(m_hInfo, &FileSize.u.HighPart);
306 if (FileSize.u.LowPart < sizeof(INFO2_HEADER))
307 {
308 hr = HResultFromWin32(GetLastError());
309 goto cleanup;
310 }
311 dwEntries = (DWORD)((FileSize.QuadPart - sizeof(INFO2_HEADER)) / sizeof(DELETED_FILE_RECORD)) - 1;
312 pDeletedFile = ((PDELETED_FILE_RECORD)(pHeader + 1)) + dwEntries;
313
314 /* Get file size */
315 #if 0
316 if (!GetFileSizeEx(hFile, (PLARGE_INTEGER)&FileSize))
317 {
318 hr = HResultFromWin32(GetLastError());
319 goto cleanup;
320 }
321 #else
322 FileSize.u.LowPart = GetFileSize(hFile, &FileSize.u.HighPart);
323 if (FileSize.u.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
324 {
325 hr = HResultFromWin32(GetLastError());
326 goto cleanup;
327 }
328 #endif
329 /* Check if file size is > 4Gb */
330 if (FileSize.u.HighPart != 0)
331 {
332 /* Yes, this recyclebin can't support this file */
333 hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
334 goto cleanup;
335 }
336 pHeader->dwTotalLogicalSize += FileSize.u.LowPart;
337
338 /* Generate new name */
339 Extension = PathFindExtensionW(szFullName);
340 ZeroMemory(pDeletedFile, sizeof(DELETED_FILE_RECORD));
341 if (dwEntries == 0)
342 pDeletedFile->dwRecordUniqueId = 0;
343 else
344 {
345 PDELETED_FILE_RECORD pLastDeleted = ((PDELETED_FILE_RECORD)(pHeader + 1)) + dwEntries - 1;
346 pDeletedFile->dwRecordUniqueId = pLastDeleted->dwRecordUniqueId + 1;
347 }
348
349 pDeletedFile->dwDriveNumber = tolower(szFullName[0]) - 'a';
350 _ultow(pDeletedFile->dwRecordUniqueId, szUniqueId, 10);
351
352 DeletedFileName = m_Folder;
353 DeletedFileName += L"\\D";
354 DeletedFileName += (WCHAR)(L'a' + pDeletedFile->dwDriveNumber);
355 DeletedFileName += szUniqueId;
356 DeletedFileName += Extension;
357
358 /* Get cluster size */
359 if (!GetDiskFreeSpaceW(m_VolumePath, &SectorsPerCluster, &BytesPerSector, NULL, NULL))
360 {
361 hr = HResultFromWin32(GetLastError());
362 goto cleanup;
363 }
364 ClusterSize = BytesPerSector * SectorsPerCluster;
365
366 /* Get current time */
367 GetSystemTime(&SystemTime);
368 if (!SystemTimeToFileTime(&SystemTime, &pDeletedFile->DeletionTime))
369 {
370 hr = HResultFromWin32(GetLastError());
371 goto cleanup;
372 }
373 pDeletedFile->dwPhysicalFileSize = ROUND_UP(FileSize.u.LowPart, ClusterSize);
374
375 /* Set name */
376 wcscpy(pDeletedFile->FileNameW, szFullName);
377 if (WideCharToMultiByte(CP_ACP, 0, pDeletedFile->FileNameW, -1, pDeletedFile->FileNameA, MAX_PATH, NULL, NULL) == 0)
378 {
379 hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
380 SetLastError(ERROR_INVALID_NAME);
381 goto cleanup;
382 }
383
384 wfd.dwFileAttributes = dwAttributes;
385 wfd.nFileSizeLow = FileSize.u.LowPart;
386 GetFileTime(hFile, &wfd.ftCreationTime, &wfd.ftLastAccessTime, &wfd.ftLastWriteTime);
387
388 /* Move file */
389 if (MoveFileW(szFullName, DeletedFileName))
390 hr = S_OK;
391 else
392 hr = HResultFromWin32(GetLastError());
393
394 if (SUCCEEDED(hr))
395 {
396 RECYCLEBINFILEIDENTITY ident = { pDeletedFile->DeletionTime, DeletedFileName };
397 CRecycleBin_NotifyRecycled(szFullName, &wfd, &ident);
398 }
399
400 cleanup:
401 if (pHeader)
402 UnmapViewOfFile(pHeader);
403 if (hFile != INVALID_HANDLE_VALUE)
404 CloseHandle(hFile);
405 CoTaskMemFree(szFullName);
406 return hr;
407 }
408
EmptyRecycleBin()409 STDMETHODIMP RecycleBin5::EmptyRecycleBin()
410 {
411 TRACE("(%p)\n", this);
412
413 while (TRUE)
414 {
415 IRecycleBinEnumList *prbel;
416 HRESULT hr = EnumObjects(&prbel);
417 if (!SUCCEEDED(hr))
418 return hr;
419
420 IRecycleBinFile *prbf;
421 hr = prbel->Next(1, &prbf, NULL);
422 prbel->Release();
423 if (hr == S_FALSE)
424 return S_OK;
425 hr = prbf->Delete();
426 prbf->Release();
427 if (!SUCCEEDED(hr))
428 return hr;
429 }
430 }
431
EnumObjects(_Out_ IRecycleBinEnumList ** ppEnumList)432 STDMETHODIMP RecycleBin5::EnumObjects(_Out_ IRecycleBinEnumList **ppEnumList)
433 {
434 TRACE("(%p, %p)\n", this, ppEnumList);
435
436 IUnknown *pUnk;
437 HRESULT hr = RecycleBin5Enum_Constructor(this, m_hInfo, m_hInfoMapped, m_Folder, &pUnk);
438 if (!SUCCEEDED(hr))
439 return hr;
440
441 IRecycleBinEnumList *prbel;
442 hr = pUnk->QueryInterface(IID_IRecycleBinEnumList, (void **)&prbel);
443 if (SUCCEEDED(hr))
444 {
445 m_EnumeratorCount++;
446 *ppEnumList = prbel;
447 }
448
449 pUnk->Release();
450 return hr;
451 }
452
Delete(_In_ LPCWSTR pDeletedFileName,_In_ DELETED_FILE_RECORD * pDeletedFile)453 STDMETHODIMP RecycleBin5::Delete(
454 _In_ LPCWSTR pDeletedFileName,
455 _In_ DELETED_FILE_RECORD *pDeletedFile)
456 {
457 ULARGE_INTEGER FileSize;
458 PINFO2_HEADER pHeader;
459 DELETED_FILE_RECORD *pRecord, *pLast;
460 DWORD dwEntries, i;
461
462 TRACE("(%p, %s, %p)\n", this, debugstr_w(pDeletedFileName), pDeletedFile);
463
464 if (m_EnumeratorCount != 0)
465 return HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION);
466
467 pHeader = (PINFO2_HEADER)MapViewOfFile(m_hInfoMapped, FILE_MAP_WRITE, 0, 0, 0);
468 if (!pHeader)
469 return HRESULT_FROM_WIN32(GetLastError());
470
471 FileSize.u.LowPart = GetFileSize(m_hInfo, &FileSize.u.HighPart);
472 if (FileSize.u.LowPart == 0)
473 {
474 UnmapViewOfFile(pHeader);
475 return HRESULT_FROM_WIN32(GetLastError());
476 }
477 dwEntries = (DWORD)((FileSize.QuadPart - sizeof(INFO2_HEADER)) / sizeof(DELETED_FILE_RECORD));
478
479 pRecord = (DELETED_FILE_RECORD *)(pHeader + 1);
480 for (i = 0; i < dwEntries; i++)
481 {
482 if (pRecord->dwRecordUniqueId == pDeletedFile->dwRecordUniqueId)
483 {
484 /* Delete file */
485 if (!IntDeleteRecursive(pDeletedFileName))
486 {
487 UnmapViewOfFile(pHeader);
488 return HRESULT_FROM_WIN32(GetLastError());
489 }
490
491 /* Clear last entry in the file */
492 MoveMemory(pRecord, pRecord + 1, (dwEntries - i - 1) * sizeof(DELETED_FILE_RECORD));
493 pLast = pRecord + (dwEntries - i - 1);
494 ZeroMemory(pLast, sizeof(DELETED_FILE_RECORD));
495 UnmapViewOfFile(pHeader);
496
497 /* Resize file */
498 CloseHandle(m_hInfoMapped);
499 SetFilePointer(m_hInfo, -(LONG)sizeof(DELETED_FILE_RECORD), NULL, FILE_END);
500 SetEndOfFile(m_hInfo);
501 m_hInfoMapped = CreateFileMappingW(m_hInfo, NULL, PAGE_READWRITE | SEC_COMMIT, 0, 0, NULL);
502 if (!m_hInfoMapped)
503 return HRESULT_FROM_WIN32(GetLastError());
504 return S_OK;
505 }
506 pRecord++;
507 }
508 UnmapViewOfFile(pHeader);
509 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
510 }
511
Restore(_In_ LPCWSTR pDeletedFileName,_In_ DELETED_FILE_RECORD * pDeletedFile)512 STDMETHODIMP RecycleBin5::Restore(
513 _In_ LPCWSTR pDeletedFileName,
514 _In_ DELETED_FILE_RECORD *pDeletedFile)
515 {
516 ULARGE_INTEGER FileSize;
517 PINFO2_HEADER pHeader;
518 DELETED_FILE_RECORD *pRecord, *pLast;
519 DWORD dwEntries, i;
520 int res;
521
522 TRACE("(%p, %s, %p)\n", this, debugstr_w(pDeletedFileName), pDeletedFile);
523
524 if (m_EnumeratorCount != 0)
525 return HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION);
526
527 pHeader = (PINFO2_HEADER)MapViewOfFile(m_hInfoMapped, FILE_MAP_WRITE, 0, 0, 0);
528 if (!pHeader)
529 return HRESULT_FROM_WIN32(GetLastError());
530
531 FileSize.u.LowPart = GetFileSize(m_hInfo, &FileSize.u.HighPart);
532 if (FileSize.u.LowPart == 0)
533 {
534 UnmapViewOfFile(pHeader);
535 return HRESULT_FROM_WIN32(GetLastError());
536 }
537 dwEntries = (DWORD)((FileSize.QuadPart - sizeof(INFO2_HEADER)) / sizeof(DELETED_FILE_RECORD));
538
539 pRecord = (DELETED_FILE_RECORD *)(pHeader + 1);
540 for (i = 0; i < dwEntries; i++)
541 {
542 if (pRecord->dwRecordUniqueId == pDeletedFile->dwRecordUniqueId)
543 {
544 res = SHELL_SingleFileOperation(NULL, FO_MOVE, pDeletedFileName, pDeletedFile->FileNameW, 0);
545 if (res)
546 {
547 ERR("SHFileOperationW failed with 0x%x\n", res);
548 UnmapViewOfFile(pHeader);
549 return E_FAIL;
550 }
551
552 /* Clear last entry in the file */
553 MoveMemory(pRecord, pRecord + 1, (dwEntries - i - 1) * sizeof(DELETED_FILE_RECORD));
554 pLast = pRecord + (dwEntries - i - 1);
555 ZeroMemory(pLast, sizeof(DELETED_FILE_RECORD));
556 UnmapViewOfFile(pHeader);
557
558 /* Resize file */
559 CloseHandle(m_hInfoMapped);
560 SetFilePointer(m_hInfo, -(LONG)sizeof(DELETED_FILE_RECORD), NULL, FILE_END);
561 SetEndOfFile(m_hInfo);
562 m_hInfoMapped = CreateFileMappingW(m_hInfo, NULL, PAGE_READWRITE | SEC_COMMIT, 0, 0, NULL);
563 if (!m_hInfoMapped)
564 return HRESULT_FROM_WIN32(GetLastError());
565 if (dwEntries == 1)
566 SHUpdateRecycleBinIcon(); // Full --> Empty
567 return S_OK;
568 }
569 pRecord++;
570 }
571
572 UnmapViewOfFile(pHeader);
573 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
574 }
575
OnClosing(_In_ IRecycleBinEnumList * prbel)576 STDMETHODIMP RecycleBin5::OnClosing(_In_ IRecycleBinEnumList *prbel)
577 {
578 TRACE("(%p, %p)\n", this, prbel);
579 m_EnumeratorCount--;
580 return S_OK;
581 }
582
583 static HRESULT
RecycleBin5_Create(_In_ LPCWSTR Folder,_In_ PSID OwnerSid OPTIONAL)584 RecycleBin5_Create(
585 _In_ LPCWSTR Folder,
586 _In_ PSID OwnerSid OPTIONAL)
587 {
588 LPWSTR BufferName = NULL;
589 LPWSTR Separator; /* Pointer into BufferName buffer */
590 LPWSTR FileName; /* Pointer into BufferName buffer */
591 LPCSTR DesktopIniContents = "[.ShellClassInfo]\r\nCLSID={645FF040-5081-101B-9F08-00AA002F954E}\r\n";
592 INFO2_HEADER Info2Contents[] = { { 5, 0, 0, 0x320, 0 } };
593 DWORD BytesToWrite, BytesWritten, Needed;
594 HANDLE hFile = INVALID_HANDLE_VALUE;
595 HRESULT hr;
596
597 Needed = (wcslen(Folder) + 1 + max(wcslen(RECYCLE_BIN_FILE_NAME), wcslen(L"desktop.ini")) + 1) * sizeof(WCHAR);
598 BufferName = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, Needed);
599 if (!BufferName)
600 {
601 hr = ERROR_NOT_ENOUGH_MEMORY;
602 goto cleanup;
603 }
604
605 wcscpy(BufferName, Folder);
606 Separator = wcsstr(&BufferName[3], L"\\");
607 if (Separator)
608 *Separator = UNICODE_NULL;
609 if (!CreateDirectoryW(BufferName, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
610 {
611 hr = HRESULT_FROM_WIN32(GetLastError());
612 goto cleanup;
613 }
614 SetFileAttributesW(BufferName, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
615 if (Separator)
616 {
617 *Separator = L'\\';
618 if (!CreateDirectoryW(BufferName, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
619 {
620 hr = HRESULT_FROM_WIN32(GetLastError());
621 goto cleanup;
622 }
623 }
624
625 if (OwnerSid)
626 {
627 //DWORD rc;
628
629 /* Add ACL to allow only user/SYSTEM to open it */
630 /* FIXME: rc = SetNamedSecurityInfo(
631 BufferName,
632 SE_FILE_OBJECT,
633 ???,
634 OwnerSid,
635 NULL,
636 ???,
637 ???);
638 if (rc != ERROR_SUCCESS)
639 {
640 hr = HRESULT_FROM_WIN32(rc);
641 goto cleanup;
642 }
643 */
644 }
645
646 wcscat(BufferName, L"\\");
647 FileName = &BufferName[wcslen(BufferName)];
648
649 /* Create desktop.ini */
650 wcscpy(FileName, L"desktop.ini");
651 hFile = CreateFileW(BufferName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN, NULL);
652 if (hFile == INVALID_HANDLE_VALUE)
653 {
654 hr = HRESULT_FROM_WIN32(GetLastError());
655 goto cleanup;
656 }
657 BytesToWrite = strlen(DesktopIniContents);
658 if (!WriteFile(hFile, DesktopIniContents, (DWORD)BytesToWrite, &BytesWritten, NULL))
659 {
660 hr = HRESULT_FROM_WIN32(GetLastError());
661 goto cleanup;
662 }
663 if (BytesWritten != BytesToWrite)
664 {
665 hr = E_FAIL;
666 goto cleanup;
667 }
668 CloseHandle(hFile);
669 hFile = INVALID_HANDLE_VALUE;
670
671 /* Create empty INFO2 file */
672 wcscpy(FileName, RECYCLE_BIN_FILE_NAME);
673 hFile = CreateFileW(BufferName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL);
674 if (hFile == INVALID_HANDLE_VALUE)
675 {
676 hr = HRESULT_FROM_WIN32(GetLastError());
677 goto cleanup;
678 }
679 BytesToWrite = sizeof(Info2Contents);
680 if (!WriteFile(hFile, Info2Contents, (DWORD)BytesToWrite, &BytesWritten, NULL))
681 {
682 hr = HRESULT_FROM_WIN32(GetLastError());
683 goto cleanup;
684 }
685 if (BytesWritten == BytesToWrite)
686 hr = S_OK;
687 else
688 hr = E_FAIL;
689
690 cleanup:
691 HeapFree(GetProcessHeap(), 0, BufferName);
692 if (hFile != INVALID_HANDLE_VALUE)
693 CloseHandle(hFile);
694 return hr;
695 }
696
RecycleBin5()697 RecycleBin5::RecycleBin5()
698 : m_ref(1)
699 , m_hInfo(NULL)
700 , m_hInfoMapped(NULL)
701 , m_EnumeratorCount(0)
702 {
703 }
704
Init(_In_ LPCWSTR VolumePath)705 HRESULT RecycleBin5::Init(_In_ LPCWSTR VolumePath)
706 {
707 DWORD FileSystemFlags;
708 LPCWSTR RecycleBinDirectory;
709 HANDLE tokenHandle = INVALID_HANDLE_VALUE;
710 PTOKEN_USER TokenUserInfo = NULL;
711 LPWSTR StringSid = NULL;
712 DWORD Needed;
713 INT len;
714 HRESULT hr;
715
716 m_VolumePath = VolumePath;
717
718 /* Get information about file system */
719 if (!GetVolumeInformationW(VolumePath, NULL, 0, NULL, NULL, &FileSystemFlags, NULL, 0))
720 {
721 hr = HRESULT_FROM_WIN32(GetLastError());
722 goto cleanup;
723 }
724
725 if (!(FileSystemFlags & FILE_PERSISTENT_ACLS))
726 {
727 RecycleBinDirectory = RECYCLE_BIN_DIRECTORY_WITHOUT_ACL;
728 }
729 else
730 {
731 RecycleBinDirectory = RECYCLE_BIN_DIRECTORY_WITH_ACL;
732
733 /* Get user SID */
734 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &tokenHandle))
735 {
736 hr = HRESULT_FROM_WIN32(GetLastError());
737 goto cleanup;
738 }
739 if (GetTokenInformation(tokenHandle, TokenUser, NULL, 0, &Needed))
740 {
741 hr = E_FAIL;
742 goto cleanup;
743 }
744 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
745 {
746 hr = HRESULT_FROM_WIN32(GetLastError());
747 goto cleanup;
748 }
749 TokenUserInfo = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), 0, Needed);
750 if (!TokenUserInfo)
751 {
752 hr = E_OUTOFMEMORY;
753 goto cleanup;
754 }
755 if (!GetTokenInformation(tokenHandle, TokenUser, TokenUserInfo, (DWORD)Needed, &Needed))
756 {
757 hr = HRESULT_FROM_WIN32(GetLastError());
758 goto cleanup;
759 }
760 if (!ConvertSidToStringSidW(TokenUserInfo->User.Sid, &StringSid))
761 {
762 hr = HRESULT_FROM_WIN32(GetLastError());
763 goto cleanup;
764 }
765 }
766
767 m_Folder = VolumePath;
768 m_Folder += RecycleBinDirectory;
769 if (StringSid)
770 {
771 m_Folder += L'\\';
772 m_Folder += StringSid;
773 }
774 len = m_Folder.GetLength();
775 m_Folder += L"\\" RECYCLE_BIN_FILE_NAME;
776
777 m_hInfo = CreateFileW(m_Folder, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
778 if (m_hInfo == INVALID_HANDLE_VALUE &&
779 (GetLastError() == ERROR_PATH_NOT_FOUND || GetLastError() == ERROR_FILE_NOT_FOUND))
780 {
781 m_Folder = m_Folder.Left(len);
782 hr = RecycleBin5_Create(m_Folder, TokenUserInfo ? TokenUserInfo->User.Sid : NULL);
783 m_Folder += L"\\" RECYCLE_BIN_FILE_NAME;
784 if (!SUCCEEDED(hr))
785 goto cleanup;
786 m_hInfo = CreateFileW(m_Folder, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
787 }
788
789 if (m_hInfo == INVALID_HANDLE_VALUE)
790 {
791 hr = HRESULT_FROM_WIN32(GetLastError());
792 goto cleanup;
793 }
794
795 m_hInfoMapped = CreateFileMappingW(m_hInfo, NULL, PAGE_READWRITE | SEC_COMMIT, 0, 0, NULL);
796 if (!m_hInfoMapped)
797 {
798 hr = HRESULT_FROM_WIN32(GetLastError());
799 goto cleanup;
800 }
801
802 m_Folder = m_Folder.Left(len);
803 hr = S_OK;
804
805 cleanup:
806 if (tokenHandle != INVALID_HANDLE_VALUE)
807 CloseHandle(tokenHandle);
808 HeapFree(GetProcessHeap(), 0, TokenUserInfo);
809 if (StringSid)
810 LocalFree(StringSid);
811 return hr;
812 }
813
814 EXTERN_C
RecycleBin5_Constructor(_In_ LPCWSTR VolumePath,_Out_ IUnknown ** ppUnknown)815 HRESULT RecycleBin5_Constructor(_In_ LPCWSTR VolumePath, _Out_ IUnknown **ppUnknown)
816 {
817 if (!ppUnknown)
818 return E_POINTER;
819
820 *ppUnknown = NULL;
821
822 RecycleBin5 *pThis = new RecycleBin5();
823 if (!pThis)
824 return E_OUTOFMEMORY;
825
826 HRESULT hr = pThis->Init(VolumePath);
827 if (FAILED(hr))
828 {
829 delete pThis;
830 return hr;
831 }
832
833 *ppUnknown = static_cast<IRecycleBin5 *>(pThis);
834 return S_OK;
835 }
836