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