1 /* Copyright (c) Mark Harmstone 2016-17
2  *
3  * This file is part of WinBtrfs.
4  *
5  * WinBtrfs is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public Licence as published by
7  * the Free Software Foundation, either version 3 of the Licence, or
8  * (at your option) any later version.
9  *
10  * WinBtrfs is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public Licence for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public Licence
16  * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */
17 
18 #define ISOLATION_AWARE_ENABLED 1
19 #define STRSAFE_NO_DEPRECATE
20 
21 #include "shellext.h"
22 #ifndef __REACTOS__
23 #include <windows.h>
24 #include <strsafe.h>
25 #include <winternl.h>
26 #else
27 #define WIN32_NO_STATUS
28 #include <windef.h>
29 #include <winbase.h>
30 #include <strsafe.h>
31 #include <ndk/iofuncs.h>
32 #include <ndk/iotypes.h>
33 #endif
34 
35 #define NO_SHLWAPI_STRFCNS
36 #include <shlwapi.h>
37 #include <uxtheme.h>
38 
39 #include "propsheet.h"
40 #include "resource.h"
41 
42 #define SUBVOL_ROOT_INODE 0x100
43 
44 #ifndef __REACTOS__
45 #ifndef __MINGW32__ // in winternl.h in mingw
46 
47 typedef struct _FILE_ACCESS_INFORMATION {
48     ACCESS_MASK AccessFlags;
49 } FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION;
50 
51 #define FileAccessInformation (FILE_INFORMATION_CLASS)8
52 
53 typedef struct _FILE_STANDARD_INFORMATION {
54     LARGE_INTEGER AllocationSize;
55     LARGE_INTEGER EndOfFile;
56     ULONG         NumberOfLinks;
57     BOOLEAN       DeletePending;
58     BOOLEAN       Directory;
59 } FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;
60 
61 #define FileStandardInformation (FILE_INFORMATION_CLASS)5
62 
63 #endif
64 #endif
65 
66 HRESULT __stdcall BtrfsPropSheet::QueryInterface(REFIID riid, void **ppObj) {
67     if (riid == IID_IUnknown || riid == IID_IShellPropSheetExt) {
68         *ppObj = static_cast<IShellPropSheetExt*>(this);
69         AddRef();
70         return S_OK;
71     } else if (riid == IID_IShellExtInit) {
72         *ppObj = static_cast<IShellExtInit*>(this);
73         AddRef();
74         return S_OK;
75     }
76 
77     *ppObj = nullptr;
78     return E_NOINTERFACE;
79 }
80 
81 void BtrfsPropSheet::do_search(const wstring& fn) {
82     wstring ss;
83     WIN32_FIND_DATAW ffd;
84 
85 #ifndef __REACTOS__
86     ss = fn + L"\\*"s;
87 #else
88     ss = fn + wstring(L"\\*");
89 #endif
90 
91     fff_handle h = FindFirstFileW(ss.c_str(), &ffd);
92     if (h == INVALID_HANDLE_VALUE)
93         return;
94 
95     do {
96         if (ffd.cFileName[0] != '.' || ((ffd.cFileName[1] != 0) && (ffd.cFileName[1] != '.' || ffd.cFileName[2] != 0))) {
97             wstring fn2;
98 
99 #ifndef __REACTOS__
100             fn2 = fn + L"\\"s + ffd.cFileName;
101 #else
102             fn2 = fn + wstring(L"\\") + ffd.cFileName;
103 #endif
104 
105             if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
106                 search_list.push_back(fn2);
107             else {
108                 win_handle fh = CreateFileW(fn2.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
109                                             OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
110 
111                 if (fh != INVALID_HANDLE_VALUE) {
112                     NTSTATUS Status;
113                     IO_STATUS_BLOCK iosb;
114                     btrfs_inode_info bii2;
115 
116                     memset(&bii2, 0, sizeof(bii2));
117 
118                     Status = NtFsControlFile(fh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info));
119 
120                     if (NT_SUCCESS(Status)) {
121                         sizes[0] += bii2.inline_length;
122                         sizes[1] += bii2.disk_size_uncompressed;
123                         sizes[2] += bii2.disk_size_zlib;
124                         sizes[3] += bii2.disk_size_lzo;
125                         sizes[4] += bii2.disk_size_zstd;
126                         totalsize += bii2.inline_length + bii2.disk_size_uncompressed + bii2.disk_size_zlib + bii2.disk_size_lzo + bii2.disk_size_zstd;
127                         sparsesize += bii2.sparse_size;
128                     }
129 
130                     FILE_STANDARD_INFORMATION fsi;
131 
132                     Status = NtQueryInformationFile(fh, &iosb, &fsi, sizeof(fsi), FileStandardInformation);
133 
134                     if (NT_SUCCESS(Status)) {
135                         if (bii2.inline_length > 0)
136                             allocsize += fsi.EndOfFile.QuadPart;
137                         else
138                             allocsize += fsi.AllocationSize.QuadPart;
139                     }
140                 }
141             }
142         }
143     } while (FindNextFileW(h, &ffd));
144 }
145 
146 DWORD BtrfsPropSheet::search_list_thread() {
147     while (!search_list.empty()) {
148         do_search(search_list.front());
149 
150         search_list.pop_front();
151     }
152 
153     thread = nullptr;
154 
155     return 0;
156 }
157 
158 static DWORD WINAPI global_search_list_thread(LPVOID lpParameter) {
159     BtrfsPropSheet* bps = (BtrfsPropSheet*)lpParameter;
160 
161     return bps->search_list_thread();
162 }
163 
164 HRESULT BtrfsPropSheet::check_file(const wstring& fn, UINT i, UINT num_files, UINT* sv) {
165     win_handle h;
166     IO_STATUS_BLOCK iosb;
167     NTSTATUS Status;
168     FILE_ACCESS_INFORMATION fai;
169     BY_HANDLE_FILE_INFORMATION bhfi;
170     btrfs_inode_info bii2;
171 
172     h = CreateFileW(fn.c_str(), MAXIMUM_ALLOWED, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
173                     OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
174 
175     if (h == INVALID_HANDLE_VALUE)
176         return E_FAIL;
177 
178     Status = NtQueryInformationFile(h, &iosb, &fai, sizeof(FILE_ACCESS_INFORMATION), FileAccessInformation);
179     if (!NT_SUCCESS(Status))
180         return E_FAIL;
181 
182     if (fai.AccessFlags & FILE_READ_ATTRIBUTES)
183         can_change_perms = fai.AccessFlags & WRITE_DAC;
184 
185     readonly = !(fai.AccessFlags & FILE_WRITE_ATTRIBUTES);
186 
187     if (!readonly && num_files == 1 && !can_change_perms)
188         show_admin_button = true;
189 
190     if (GetFileInformationByHandle(h, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
191         search_list.push_back(fn);
192 
193     memset(&bii2, 0, sizeof(bii2));
194 
195     Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info));
196 
197     if (NT_SUCCESS(Status) && !bii2.top) {
198         LARGE_INTEGER filesize;
199 
200         if (i == 0) {
201             subvol = bii2.subvol;
202             inode = bii2.inode;
203             type = bii2.type;
204             uid = bii2.st_uid;
205             gid = bii2.st_gid;
206             rdev = bii2.st_rdev;
207         } else {
208             if (subvol != bii2.subvol)
209                 various_subvols = true;
210 
211             if (inode != bii2.inode)
212                 various_inodes = true;
213 
214             if (type != bii2.type)
215                 various_types = true;
216 
217             if (uid != bii2.st_uid)
218                 various_uids = true;
219 
220             if (gid != bii2.st_gid)
221                 various_gids = true;
222         }
223 
224         if (bii2.inline_length > 0) {
225             totalsize += bii2.inline_length;
226             sizes[0] += bii2.inline_length;
227         }
228 
229         if (bii2.disk_size_uncompressed > 0) {
230             totalsize += bii2.disk_size_uncompressed;
231             sizes[1] += bii2.disk_size_uncompressed;
232         }
233 
234         if (bii2.disk_size_zlib > 0) {
235             totalsize += bii2.disk_size_zlib;
236             sizes[2] += bii2.disk_size_zlib;
237         }
238 
239         if (bii2.disk_size_lzo > 0) {
240             totalsize += bii2.disk_size_lzo;
241             sizes[3] += bii2.disk_size_lzo;
242         }
243 
244         if (bii2.disk_size_zstd > 0) {
245             totalsize += bii2.disk_size_zstd;
246             sizes[4] += bii2.disk_size_zstd;
247         }
248 
249         sparsesize += bii2.sparse_size;
250 
251         FILE_STANDARD_INFORMATION fsi;
252 
253         Status = NtQueryInformationFile(h, &iosb, &fsi, sizeof(fsi), FileStandardInformation);
254 
255         if (!NT_SUCCESS(Status))
256             throw ntstatus_error(Status);
257 
258         if (bii2.inline_length > 0)
259             allocsize += fsi.EndOfFile.QuadPart;
260         else
261             allocsize += fsi.AllocationSize.QuadPart;
262 
263         min_mode |= ~bii2.st_mode;
264         max_mode |= bii2.st_mode;
265         min_flags |= ~bii2.flags;
266         max_flags |= bii2.flags;
267         min_compression_type = bii2.compression_type < min_compression_type ? bii2.compression_type : min_compression_type;
268         max_compression_type = bii2.compression_type > max_compression_type ? bii2.compression_type : max_compression_type;
269 
270         if (bii2.inode == SUBVOL_ROOT_INODE) {
271             bool ro = bhfi.dwFileAttributes & FILE_ATTRIBUTE_READONLY;
272 
273             has_subvols = true;
274 
275             if (*sv == 0)
276                 ro_subvol = ro;
277             else {
278                 if (ro_subvol != ro)
279                     various_ro = true;
280             }
281 
282             (*sv)++;
283         }
284 
285         ignore = false;
286 
287         if (bii2.type != BTRFS_TYPE_DIRECTORY && GetFileSizeEx(h, &filesize)) {
288             if (filesize.QuadPart != 0)
289                 can_change_nocow = false;
290         }
291     } else
292         return E_FAIL;
293 
294     return S_OK;
295 }
296 
297 HRESULT BtrfsPropSheet::load_file_list() {
298     UINT num_files, i, sv = 0;
299     WCHAR fn[MAX_PATH];
300 
301     num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);
302 
303     min_mode = 0;
304     max_mode = 0;
305     min_flags = 0;
306     max_flags = 0;
307     min_compression_type = 0xff;
308     max_compression_type = 0;
309     various_subvols = various_inodes = various_types = various_uids = various_gids = various_ro = false;
310 
311     can_change_perms = true;
312     can_change_nocow = true;
313 
314     sizes[0] = sizes[1] = sizes[2] = sizes[3] = sizes[4] = 0;
315     totalsize = allocsize = sparsesize = 0;
316 
317     for (i = 0; i < num_files; i++) {
318         if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(MAX_PATH))) {
319             HRESULT hr;
320 
321             hr = check_file(fn, i, num_files, &sv);
322             if (FAILED(hr))
323                 return hr;
324         } else
325             return E_FAIL;
326     }
327 
328     min_mode = ~min_mode;
329     min_flags = ~min_flags;
330 
331     mode = min_mode;
332     mode_set = ~(min_mode ^ max_mode);
333 
334     flags = min_flags;
335     flags_set = ~(min_flags ^ max_flags);
336 
337     return S_OK;
338 }
339 
340 HRESULT __stdcall BtrfsPropSheet::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) {
341     try {
342         FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
343         HDROP hdrop;
344         HRESULT hr;
345 
346         if (pidlFolder)
347             return E_FAIL;
348 
349         if (!pdtobj)
350             return E_FAIL;
351 
352         stgm.tymed = TYMED_HGLOBAL;
353 
354         if (FAILED(pdtobj->GetData(&format, &stgm)))
355             return E_INVALIDARG;
356 
357         stgm_set = true;
358 
359         hdrop = (HDROP)GlobalLock(stgm.hGlobal);
360 
361         if (!hdrop) {
362             ReleaseStgMedium(&stgm);
363             stgm_set = false;
364             return E_INVALIDARG;
365         }
366 
367         try {
368             hr = load_file_list();
369             if (FAILED(hr))
370                 return hr;
371 
372             if (search_list.size() > 0) {
373                 thread = CreateThread(nullptr, 0, global_search_list_thread, this, 0, nullptr);
374 
375                 if (!thread)
376                     throw last_error(GetLastError());
377             }
378         } catch (...) {
379             GlobalUnlock(hdrop);
380             throw;
381         }
382 
383         GlobalUnlock(hdrop);
384     } catch (const exception& e) {
385         error_message(nullptr, e.what());
386 
387         return E_FAIL;
388     }
389 
390     return S_OK;
391 }
392 
393 void BtrfsPropSheet::set_cmdline(const wstring& cmdline) {
394     win_handle h;
395     IO_STATUS_BLOCK iosb;
396     NTSTATUS Status;
397     UINT sv = 0;
398     BY_HANDLE_FILE_INFORMATION bhfi;
399     btrfs_inode_info bii2;
400     FILE_ACCESS_INFORMATION fai;
401 
402     min_mode = 0;
403     max_mode = 0;
404     min_flags = 0;
405     max_flags = 0;
406     min_compression_type = 0xff;
407     max_compression_type = 0;
408     various_subvols = various_inodes = various_types = various_uids = various_gids = various_ro = false;
409 
410     can_change_perms = true;
411     can_change_nocow = true;
412 
413     h = CreateFileW(cmdline.c_str(), MAXIMUM_ALLOWED, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
414                     OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
415 
416     if (h == INVALID_HANDLE_VALUE)
417         return;
418 
419     Status = NtQueryInformationFile(h, &iosb, &fai, sizeof(FILE_ACCESS_INFORMATION), FileAccessInformation);
420     if (!NT_SUCCESS(Status))
421         return;
422 
423     if (fai.AccessFlags & FILE_READ_ATTRIBUTES)
424         can_change_perms = fai.AccessFlags & WRITE_DAC;
425 
426     readonly = !(fai.AccessFlags & FILE_WRITE_ATTRIBUTES);
427 
428     if (GetFileInformationByHandle(h, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
429         search_list.push_back(cmdline);
430 
431     memset(&bii2, 0, sizeof(bii2));
432 
433     Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info));
434 
435     if (NT_SUCCESS(Status) && !bii2.top) {
436         LARGE_INTEGER filesize;
437 
438         subvol = bii2.subvol;
439         inode = bii2.inode;
440         type = bii2.type;
441         uid = bii2.st_uid;
442         gid = bii2.st_gid;
443         rdev = bii2.st_rdev;
444 
445         if (bii2.inline_length > 0) {
446             totalsize += bii2.inline_length;
447             sizes[0] += bii2.inline_length;
448         }
449 
450         if (bii2.disk_size_uncompressed > 0) {
451             totalsize += bii2.disk_size_uncompressed;
452             sizes[1] += bii2.disk_size_uncompressed;
453         }
454 
455         if (bii2.disk_size_zlib > 0) {
456             totalsize += bii2.disk_size_zlib;
457             sizes[2] += bii2.disk_size_zlib;
458         }
459 
460         if (bii2.disk_size_lzo > 0) {
461             totalsize += bii2.disk_size_lzo;
462             sizes[3] += bii2.disk_size_lzo;
463         }
464 
465         if (bii2.disk_size_zstd > 0) {
466             totalsize += bii2.disk_size_zstd;
467             sizes[4] += bii2.disk_size_zstd;
468         }
469 
470         sparsesize += bii2.sparse_size;
471 
472         FILE_STANDARD_INFORMATION fsi;
473 
474         Status = NtQueryInformationFile(h, &iosb, &fsi, sizeof(fsi), FileStandardInformation);
475 
476         if (!NT_SUCCESS(Status))
477             throw ntstatus_error(Status);
478 
479         if (bii2.inline_length > 0)
480             allocsize += fsi.EndOfFile.QuadPart;
481         else
482             allocsize += fsi.AllocationSize.QuadPart;
483 
484         min_mode |= ~bii2.st_mode;
485         max_mode |= bii2.st_mode;
486         min_flags |= ~bii2.flags;
487         max_flags |= bii2.flags;
488         min_compression_type = bii2.compression_type < min_compression_type ? bii2.compression_type : min_compression_type;
489         max_compression_type = bii2.compression_type > max_compression_type ? bii2.compression_type : max_compression_type;
490 
491         if (bii2.inode == SUBVOL_ROOT_INODE) {
492             bool ro = bhfi.dwFileAttributes & FILE_ATTRIBUTE_READONLY;
493 
494             has_subvols = true;
495 
496             if (sv == 0)
497                 ro_subvol = ro;
498             else {
499                 if (ro_subvol != ro)
500                     various_ro = true;
501             }
502 
503             sv++;
504         }
505 
506         ignore = false;
507 
508         if (bii2.type != BTRFS_TYPE_DIRECTORY && GetFileSizeEx(h, &filesize)) {
509             if (filesize.QuadPart != 0)
510                 can_change_nocow = false;
511         }
512     } else
513         return;
514 
515     min_mode = ~min_mode;
516     min_flags = ~min_flags;
517 
518     mode = min_mode;
519     mode_set = ~(min_mode ^ max_mode);
520 
521     flags = min_flags;
522     flags_set = ~(min_flags ^ max_flags);
523 
524     if (search_list.size() > 0) {
525         thread = CreateThread(nullptr, 0, global_search_list_thread, this, 0, nullptr);
526 
527         if (!thread)
528             throw last_error(GetLastError());
529     }
530 
531     this->filename = cmdline;
532 }
533 
534 static ULONG inode_type_to_string_ref(uint8_t type) {
535     switch (type) {
536         case BTRFS_TYPE_FILE:
537             return IDS_INODE_FILE;
538 
539         case BTRFS_TYPE_DIRECTORY:
540             return IDS_INODE_DIR;
541 
542         case BTRFS_TYPE_CHARDEV:
543             return IDS_INODE_CHAR;
544 
545         case BTRFS_TYPE_BLOCKDEV:
546             return IDS_INODE_BLOCK;
547 
548         case BTRFS_TYPE_FIFO:
549             return IDS_INODE_FIFO;
550 
551         case BTRFS_TYPE_SOCKET:
552             return IDS_INODE_SOCKET;
553 
554         case BTRFS_TYPE_SYMLINK:
555             return IDS_INODE_SYMLINK;
556 
557         default:
558             return IDS_INODE_UNKNOWN;
559     }
560 }
561 
562 void BtrfsPropSheet::change_inode_flag(HWND hDlg, uint64_t flag, UINT state) {
563     if (flag & BTRFS_INODE_NODATACOW)
564         flag |= BTRFS_INODE_NODATASUM;
565 
566     if (state == BST_CHECKED) {
567         flags |= flag;
568         flags_set |= flag;
569     } else if (state == BST_UNCHECKED) {
570         flags &= ~flag;
571         flags_set |= flag;
572     } else if (state == BST_INDETERMINATE) {
573         flags_set = ~flag;
574     }
575 
576     flags_changed = true;
577 
578     SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
579 }
580 
581 void BtrfsPropSheet::apply_changes_file(HWND hDlg, const wstring& fn) {
582     win_handle h;
583     IO_STATUS_BLOCK iosb;
584     NTSTATUS Status;
585     btrfs_set_inode_info bsii;
586     btrfs_inode_info bii2;
587     ULONG perms = FILE_TRAVERSE | FILE_READ_ATTRIBUTES;
588 
589     if (flags_changed || ro_changed)
590         perms |= FILE_WRITE_ATTRIBUTES;
591 
592     if (perms_changed || gid_changed || uid_changed)
593         perms |= WRITE_DAC;
594 
595     if (mode_set & S_ISUID && (((min_mode & S_ISUID) != (max_mode & S_ISUID)) || ((min_mode & S_ISUID) != (mode & S_ISUID))))
596         perms |= WRITE_OWNER;
597 
598     h = CreateFileW(fn.c_str(), perms, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
599                     OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
600 
601     if (h == INVALID_HANDLE_VALUE)
602         throw last_error(GetLastError());
603 
604     ZeroMemory(&bsii, sizeof(btrfs_set_inode_info));
605 
606     Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info));
607 
608     if (!NT_SUCCESS(Status))
609         throw ntstatus_error(Status);
610 
611     if (bii2.inode == SUBVOL_ROOT_INODE && ro_changed) {
612         BY_HANDLE_FILE_INFORMATION bhfi;
613         FILE_BASIC_INFO fbi;
614 
615         if (!GetFileInformationByHandle(h, &bhfi))
616             throw last_error(GetLastError());
617 
618         memset(&fbi, 0, sizeof(fbi));
619         fbi.FileAttributes = bhfi.dwFileAttributes;
620 
621         if (ro_subvol)
622             fbi.FileAttributes |= FILE_ATTRIBUTE_READONLY;
623         else
624             fbi.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
625 
626         if (!SetFileInformationByHandle(h, FileBasicInfo, &fbi, sizeof(fbi)))
627             throw last_error(GetLastError());
628     }
629 
630     if (flags_changed || perms_changed || uid_changed || gid_changed || compress_type_changed) {
631         if (flags_changed) {
632             bsii.flags_changed = true;
633             bsii.flags = (bii2.flags & ~flags_set) | (flags & flags_set);
634         }
635 
636         if (perms_changed) {
637             bsii.mode_changed = true;
638             bsii.st_mode = (bii2.st_mode & ~mode_set) | (mode & mode_set);
639         }
640 
641         if (uid_changed) {
642             bsii.uid_changed = true;
643             bsii.st_uid = uid;
644         }
645 
646         if (gid_changed) {
647             bsii.gid_changed = true;
648             bsii.st_gid = gid;
649         }
650 
651         if (compress_type_changed) {
652             bsii.compression_type_changed = true;
653             bsii.compression_type = compress_type;
654         }
655 
656         Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);
657 
658         if (!NT_SUCCESS(Status))
659             throw ntstatus_error(Status);
660     }
661 }
662 
663 void BtrfsPropSheet::apply_changes(HWND hDlg) {
664     UINT num_files, i;
665     WCHAR fn[MAX_PATH]; // FIXME - is this long enough?
666 
667     if (various_uids)
668         uid_changed = false;
669 
670     if (various_gids)
671         gid_changed = false;
672 
673     if (!flags_changed && !perms_changed && !uid_changed && !gid_changed && !compress_type_changed && !ro_changed)
674         return;
675 
676     if (filename[0] != 0)
677         apply_changes_file(hDlg, filename);
678     else {
679         num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);
680 
681         for (i = 0; i < num_files; i++) {
682             if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(MAX_PATH))) {
683                 apply_changes_file(hDlg, fn);
684             }
685         }
686     }
687 
688     flags_changed = false;
689     perms_changed = false;
690     uid_changed = false;
691     gid_changed = false;
692     ro_changed = false;
693 }
694 
695 void BtrfsPropSheet::set_size_on_disk(HWND hwndDlg) {
696     wstring s, size_on_disk, cr;
697     WCHAR old_text[1024];
698     float ratio;
699 
700     format_size(totalsize, size_on_disk, true);
701 
702     wstring_sprintf(s, size_format, size_on_disk.c_str());
703 
704     if (allocsize == sparsesize || totalsize == 0)
705         ratio = 0.0f;
706     else
707         ratio = 100.0f * (1.0f - ((float)totalsize / (float)(allocsize - sparsesize)));
708 
709     wstring_sprintf(cr, cr_format, ratio);
710 
711     GetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, old_text, sizeof(old_text) / sizeof(WCHAR));
712 
713     if (s != old_text)
714         SetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, s.c_str());
715 
716     GetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, old_text, sizeof(old_text) / sizeof(WCHAR));
717 
718     if (cr != old_text)
719         SetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, cr.c_str());
720 }
721 
722 void BtrfsPropSheet::change_perm_flag(HWND hDlg, ULONG flag, UINT state) {
723     if (state == BST_CHECKED) {
724         mode |= flag;
725         mode_set |= flag;
726     } else if (state == BST_UNCHECKED) {
727         mode &= ~flag;
728         mode_set |= flag;
729     } else if (state == BST_INDETERMINATE) {
730         mode_set = ~flag;
731     }
732 
733     perms_changed = true;
734 
735     SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
736 }
737 
738 void BtrfsPropSheet::change_uid(HWND hDlg, uint32_t uid) {
739     if (this->uid != uid) {
740         this->uid = uid;
741         uid_changed = true;
742 
743         SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
744     }
745 }
746 
747 void BtrfsPropSheet::change_gid(HWND hDlg, uint32_t gid) {
748     if (this->gid != gid) {
749         this->gid = gid;
750         gid_changed = true;
751 
752         SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
753     }
754 }
755 
756 void BtrfsPropSheet::update_size_details_dialog(HWND hDlg) {
757     wstring size;
758     WCHAR old_text[1024];
759     int i;
760     ULONG items[] = { IDC_SIZE_INLINE, IDC_SIZE_UNCOMPRESSED, IDC_SIZE_ZLIB, IDC_SIZE_LZO, IDC_SIZE_ZSTD };
761 
762     for (i = 0; i < 5; i++) {
763         format_size(sizes[i], size, true);
764 
765         GetDlgItemTextW(hDlg, items[i], old_text, sizeof(old_text) / sizeof(WCHAR));
766 
767         if (size != old_text)
768             SetDlgItemTextW(hDlg, items[i], size.c_str());
769     }
770 }
771 
772 static INT_PTR CALLBACK SizeDetailsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
773     try {
774         switch (uMsg) {
775             case WM_INITDIALOG:
776             {
777                 BtrfsPropSheet* bps = (BtrfsPropSheet*)lParam;
778 
779                 SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)bps);
780 
781                 bps->update_size_details_dialog(hwndDlg);
782 
783                 if (bps->thread)
784                     SetTimer(hwndDlg, 1, 250, nullptr);
785 
786                 return true;
787             }
788 
789             case WM_COMMAND:
790                 if (HIWORD(wParam) == BN_CLICKED && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {
791                     EndDialog(hwndDlg, 0);
792                     return true;
793                 }
794             break;
795 
796             case WM_TIMER:
797             {
798                 BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
799 
800                 if (bps) {
801                     bps->update_size_details_dialog(hwndDlg);
802 
803                     if (!bps->thread)
804                         KillTimer(hwndDlg, 1);
805                 }
806 
807                 break;
808             }
809         }
810     } catch (const exception& e) {
811         error_message(hwndDlg, e.what());
812     }
813 
814     return false;
815 }
816 
817 static void set_check_box(HWND hwndDlg, ULONG id, uint64_t min, uint64_t max) {
818     if (min && max) {
819         SendDlgItemMessage(hwndDlg, id, BM_SETCHECK, BST_CHECKED, 0);
820     } else if (!min && !max) {
821         SendDlgItemMessage(hwndDlg, id, BM_SETCHECK, BST_UNCHECKED, 0);
822     } else {
823         LONG_PTR style;
824 
825         style = GetWindowLongPtr(GetDlgItem(hwndDlg, id), GWL_STYLE);
826         style &= ~BS_AUTOCHECKBOX;
827         style |= BS_AUTO3STATE;
828         SetWindowLongPtr(GetDlgItem(hwndDlg, id), GWL_STYLE, style);
829 
830         SendDlgItemMessage(hwndDlg, id, BM_SETCHECK, BST_INDETERMINATE, 0);
831     }
832 }
833 
834 void BtrfsPropSheet::open_as_admin(HWND hwndDlg) {
835     ULONG num_files, i;
836     WCHAR fn[MAX_PATH], modfn[MAX_PATH];
837 
838     num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);
839 
840     if (num_files == 0)
841         return;
842 
843     GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));
844 
845     for (i = 0; i < num_files; i++) {
846         if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(MAX_PATH))) {
847             wstring t;
848             SHELLEXECUTEINFOW sei;
849 
850 #ifndef __REACTOS__
851             t = L"\""s + modfn + L"\",ShowPropSheet "s + fn;
852 #else
853             t = wstring(L"\"") + modfn + wstring(L"\",ShowPropSheet ") + fn;
854 #endif
855 
856             RtlZeroMemory(&sei, sizeof(sei));
857 
858             sei.cbSize = sizeof(sei);
859             sei.hwnd = hwndDlg;
860             sei.lpVerb = L"runas";
861             sei.lpFile = L"rundll32.exe";
862             sei.lpParameters = t.c_str();
863             sei.nShow = SW_SHOW;
864             sei.fMask = SEE_MASK_NOCLOSEPROCESS;
865 
866             if (!ShellExecuteExW(&sei))
867                 throw last_error(GetLastError());
868 
869             WaitForSingleObject(sei.hProcess, INFINITE);
870             CloseHandle(sei.hProcess);
871 
872             load_file_list();
873             init_propsheet(hwndDlg);
874         }
875     }
876 }
877 
878 // based on functions in sys/sysmacros.h
879 #define major(rdev) ((((rdev) >> 8) & 0xFFF) | ((uint32_t)((rdev) >> 32) & ~0xFFF))
880 #define minor(rdev) (((rdev) & 0xFF) | ((uint32_t)((rdev) >> 12) & ~0xFF))
881 
882 void BtrfsPropSheet::init_propsheet(HWND hwndDlg) {
883     wstring s;
884     ULONG sr;
885     int i;
886     HWND comptype;
887 
888     static ULONG perm_controls[] = { IDC_USERR, IDC_USERW, IDC_USERX, IDC_GROUPR, IDC_GROUPW, IDC_GROUPX, IDC_OTHERR, IDC_OTHERW, IDC_OTHERX,
889                                      IDC_SETUID, IDC_SETGID, IDC_STICKY, 0 };
890     static ULONG perms[] = { S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH, S_ISUID, S_ISGID, S_ISVTX, 0 };
891     static ULONG comp_types[] = { IDS_COMPRESS_ANY, IDS_COMPRESS_ZLIB, IDS_COMPRESS_LZO, IDS_COMPRESS_ZSTD, 0 };
892 
893     if (various_subvols) {
894         if (!load_string(module, IDS_VARIOUS, s))
895             throw last_error(GetLastError());
896     } else
897         wstring_sprintf(s, L"%llx", subvol);
898 
899     SetDlgItemTextW(hwndDlg, IDC_SUBVOL, s.c_str());
900 
901     if (various_inodes) {
902         if (!load_string(module, IDS_VARIOUS, s))
903             throw last_error(GetLastError());
904     } else
905         wstring_sprintf(s, L"%llx", inode);
906 
907     SetDlgItemTextW(hwndDlg, IDC_INODE, s.c_str());
908 
909     if (various_types)
910         sr = IDS_VARIOUS;
911     else
912         sr = inode_type_to_string_ref(type);
913 
914     if (various_inodes) {
915         if (sr == IDS_INODE_CHAR)
916             sr = IDS_INODE_CHAR_SIMPLE;
917         else if (sr == IDS_INODE_BLOCK)
918             sr = IDS_INODE_BLOCK_SIMPLE;
919     }
920 
921     if (sr == IDS_INODE_UNKNOWN) {
922         wstring t;
923 
924         if (!load_string(module, sr, t))
925             throw last_error(GetLastError());
926 
927         wstring_sprintf(s, t, type);
928     } else if (sr == IDS_INODE_CHAR || sr == IDS_INODE_BLOCK) {
929         wstring t;
930 
931         if (!load_string(module, sr, t))
932             throw last_error(GetLastError());
933 
934         wstring_sprintf(s, t, major(rdev), minor(rdev));
935     } else {
936         if (!load_string(module, sr, s))
937             throw last_error(GetLastError());
938     }
939 
940     SetDlgItemTextW(hwndDlg, IDC_TYPE, s.c_str());
941 
942     if (size_format[0] == 0)
943         GetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, size_format, sizeof(size_format) / sizeof(WCHAR));
944 
945     if (cr_format[0] == 0)
946         GetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, cr_format, sizeof(cr_format) / sizeof(WCHAR));
947 
948     set_size_on_disk(hwndDlg);
949 
950     if (thread)
951         SetTimer(hwndDlg, 1, 250, nullptr);
952 
953     set_check_box(hwndDlg, IDC_NODATACOW, min_flags & BTRFS_INODE_NODATACOW, max_flags & BTRFS_INODE_NODATACOW);
954     set_check_box(hwndDlg, IDC_COMPRESS, min_flags & BTRFS_INODE_COMPRESS, max_flags & BTRFS_INODE_COMPRESS);
955 
956     comptype = GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE);
957 
958     while (SendMessage(comptype, CB_GETCOUNT, 0, 0) > 0) {
959         SendMessage(comptype, CB_DELETESTRING, 0, 0);
960     }
961 
962     if (min_compression_type != max_compression_type) {
963         SendMessage(comptype, CB_ADDSTRING, 0, (LPARAM)L"");
964         SendMessage(comptype, CB_SETCURSEL, 0, 0);
965     }
966 
967     i = 0;
968     while (comp_types[i] != 0) {
969         wstring t;
970 
971         if (!load_string(module, comp_types[i], t))
972             throw last_error(GetLastError());
973 
974         SendMessage(comptype, CB_ADDSTRING, 0, (LPARAM)t.c_str());
975 
976         i++;
977     }
978 
979     if (min_compression_type == max_compression_type) {
980         SendMessage(comptype, CB_SETCURSEL, min_compression_type, 0);
981         compress_type = min_compression_type;
982     }
983 
984     EnableWindow(comptype, max_flags & BTRFS_INODE_COMPRESS);
985 
986     i = 0;
987     while (perm_controls[i] != 0) {
988         set_check_box(hwndDlg, perm_controls[i], min_mode & perms[i], max_mode & perms[i]);
989         i++;
990     }
991 
992     if (various_uids) {
993         if (!load_string(module, IDS_VARIOUS, s))
994             throw last_error(GetLastError());
995 
996         EnableWindow(GetDlgItem(hwndDlg, IDC_UID), 0);
997     } else
998         s = to_wstring(uid);
999 
1000     SetDlgItemTextW(hwndDlg, IDC_UID, s.c_str());
1001 
1002     if (various_gids) {
1003         if (!load_string(module, IDS_VARIOUS, s))
1004             throw last_error(GetLastError());
1005 
1006         EnableWindow(GetDlgItem(hwndDlg, IDC_GID), 0);
1007     } else
1008         s = to_wstring(gid);
1009 
1010     SetDlgItemTextW(hwndDlg, IDC_GID, s.c_str());
1011 
1012     ShowWindow(GetDlgItem(hwndDlg, IDC_SUBVOL_RO), has_subvols);
1013 
1014     if (has_subvols)
1015         set_check_box(hwndDlg, IDC_SUBVOL_RO, ro_subvol, various_ro ? (!ro_subvol) : ro_subvol);
1016 
1017     if (!can_change_nocow)
1018         EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), 0);
1019 
1020     if (!can_change_perms) {
1021         i = 0;
1022         while (perm_controls[i] != 0) {
1023             EnableWindow(GetDlgItem(hwndDlg, perm_controls[i]), 0);
1024             i++;
1025         }
1026 
1027         EnableWindow(GetDlgItem(hwndDlg, IDC_UID), 0);
1028         EnableWindow(GetDlgItem(hwndDlg, IDC_GID), 0);
1029         EnableWindow(GetDlgItem(hwndDlg, IDC_SETUID), 0);
1030     }
1031 
1032     if (readonly) {
1033         EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), 0);
1034         EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS), 0);
1035         EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE), 0);
1036     }
1037 
1038     if (show_admin_button) {
1039         SendMessageW(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), BCM_SETSHIELD, 0, true);
1040         ShowWindow(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), SW_SHOW);
1041     } else
1042         ShowWindow(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), SW_HIDE);
1043 }
1044 
1045 static INT_PTR CALLBACK PropSheetDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
1046     try {
1047         switch (uMsg) {
1048             case WM_INITDIALOG:
1049             {
1050                 PROPSHEETPAGE* psp = (PROPSHEETPAGE*)lParam;
1051                 BtrfsPropSheet* bps = (BtrfsPropSheet*)psp->lParam;
1052 
1053                 EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB);
1054 
1055                 SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)bps);
1056 
1057                 bps->init_propsheet(hwndDlg);
1058 
1059                 return false;
1060             }
1061 
1062             case WM_COMMAND:
1063             {
1064                 BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
1065 
1066                 if (bps && !bps->readonly) {
1067                     switch (HIWORD(wParam)) {
1068                         case BN_CLICKED: {
1069                             switch (LOWORD(wParam)) {
1070                                 case IDC_NODATACOW:
1071                                     bps->change_inode_flag(hwndDlg, BTRFS_INODE_NODATACOW, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1072                                 break;
1073 
1074                                 case IDC_COMPRESS:
1075                                     bps->change_inode_flag(hwndDlg, BTRFS_INODE_COMPRESS, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1076 
1077                                     EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE), IsDlgButtonChecked(hwndDlg, LOWORD(wParam)) != BST_UNCHECKED);
1078                                 break;
1079 
1080                                 case IDC_USERR:
1081                                     bps->change_perm_flag(hwndDlg, S_IRUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1082                                 break;
1083 
1084                                 case IDC_USERW:
1085                                     bps->change_perm_flag(hwndDlg, S_IWUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1086                                 break;
1087 
1088                                 case IDC_USERX:
1089                                     bps->change_perm_flag(hwndDlg, S_IXUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1090                                 break;
1091 
1092                                 case IDC_GROUPR:
1093                                     bps->change_perm_flag(hwndDlg, S_IRGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1094                                 break;
1095 
1096                                 case IDC_GROUPW:
1097                                     bps->change_perm_flag(hwndDlg, S_IWGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1098                                 break;
1099 
1100                                 case IDC_GROUPX:
1101                                     bps->change_perm_flag(hwndDlg, S_IXGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1102                                 break;
1103 
1104                                 case IDC_OTHERR:
1105                                     bps->change_perm_flag(hwndDlg, S_IROTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1106                                 break;
1107 
1108                                 case IDC_OTHERW:
1109                                     bps->change_perm_flag(hwndDlg, S_IWOTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1110                                 break;
1111 
1112                                 case IDC_OTHERX:
1113                                     bps->change_perm_flag(hwndDlg, S_IXOTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1114                                 break;
1115 
1116                                 case IDC_SETUID:
1117                                     bps->change_perm_flag(hwndDlg, S_ISUID, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1118                                 break;
1119 
1120                                 case IDC_SETGID:
1121                                     bps->change_perm_flag(hwndDlg, S_ISGID, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1122                                 break;
1123 
1124                                 case IDC_STICKY:
1125                                     bps->change_perm_flag(hwndDlg, S_ISVTX, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1126                                 break;
1127 
1128                                 case IDC_SUBVOL_RO:
1129                                     switch (IsDlgButtonChecked(hwndDlg, LOWORD(wParam))) {
1130                                         case BST_CHECKED:
1131                                             bps->ro_subvol = true;
1132                                             bps->ro_changed = true;
1133                                         break;
1134 
1135                                         case BST_UNCHECKED:
1136                                             bps->ro_subvol = false;
1137                                             bps->ro_changed = true;
1138                                         break;
1139 
1140                                         case BST_INDETERMINATE:
1141                                             bps->ro_changed = false;
1142                                         break;
1143                                     }
1144 
1145                                     SendMessageW(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0);
1146                                 break;
1147 
1148                                 case IDC_OPEN_ADMIN:
1149                                     bps->open_as_admin(hwndDlg);
1150                                 break;
1151                             }
1152 
1153                             break;
1154                         }
1155 
1156                         case EN_CHANGE: {
1157                             switch (LOWORD(wParam)) {
1158                                 case IDC_UID: {
1159                                     WCHAR s[255];
1160 
1161                                     GetDlgItemTextW(hwndDlg, LOWORD(wParam), s, sizeof(s) / sizeof(WCHAR));
1162 
1163                                     bps->change_uid(hwndDlg, _wtoi(s));
1164                                     break;
1165                                 }
1166 
1167                                 case IDC_GID: {
1168                                     WCHAR s[255];
1169 
1170                                     GetDlgItemTextW(hwndDlg, LOWORD(wParam), s, sizeof(s) / sizeof(WCHAR));
1171 
1172                                     bps->change_gid(hwndDlg, _wtoi(s));
1173                                     break;
1174                                 }
1175                             }
1176 
1177                             break;
1178                         }
1179 
1180                         case CBN_SELCHANGE: {
1181                             switch (LOWORD(wParam)) {
1182                                 case IDC_COMPRESS_TYPE: {
1183                                     int sel = SendMessageW(GetDlgItem(hwndDlg, LOWORD(wParam)), CB_GETCURSEL, 0, 0);
1184 
1185                                     if (bps->min_compression_type != bps->max_compression_type) {
1186                                         if (sel == 0)
1187                                             bps->compress_type_changed = false;
1188                                         else {
1189                                             bps->compress_type = (uint8_t)(sel - 1);
1190                                             bps->compress_type_changed = true;
1191                                         }
1192                                     } else {
1193                                         bps->compress_type = (uint8_t)sel;
1194                                         bps->compress_type_changed = true;
1195                                     }
1196 
1197                                     SendMessageW(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0);
1198 
1199                                     break;
1200                                 }
1201                             }
1202 
1203                             break;
1204                         }
1205                     }
1206                 }
1207 
1208                 break;
1209             }
1210 
1211             case WM_NOTIFY:
1212             {
1213                 switch (((LPNMHDR)lParam)->code) {
1214                     case PSN_KILLACTIVE:
1215                         SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, false);
1216                     break;
1217 
1218                     case PSN_APPLY: {
1219                         BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
1220 
1221                         bps->apply_changes(hwndDlg);
1222                         SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
1223                         break;
1224                     }
1225 
1226                     case NM_CLICK:
1227                     case NM_RETURN: {
1228                         if (((LPNMHDR)lParam)->hwndFrom == GetDlgItem(hwndDlg, IDC_SIZE_ON_DISK)) {
1229                             PNMLINK pNMLink = (PNMLINK)lParam;
1230 
1231                             if (pNMLink->item.iLink == 0)
1232                                 DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_SIZE_DETAILS), hwndDlg, SizeDetailsDlgProc, GetWindowLongPtr(hwndDlg, GWLP_USERDATA));
1233                         }
1234                         break;
1235                     }
1236                 }
1237             }
1238 
1239             case WM_TIMER:
1240             {
1241                 BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
1242 
1243                 if (bps) {
1244                     bps->set_size_on_disk(hwndDlg);
1245 
1246                     if (!bps->thread)
1247                         KillTimer(hwndDlg, 1);
1248                 }
1249 
1250                 break;
1251             }
1252         }
1253     } catch (const exception& e) {
1254         error_message(hwndDlg, e.what());
1255     }
1256 
1257     return false;
1258 }
1259 
1260 HRESULT __stdcall BtrfsPropSheet::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) {
1261     try {
1262         PROPSHEETPAGE psp;
1263         HPROPSHEETPAGE hPage;
1264         INITCOMMONCONTROLSEX icex;
1265 
1266         if (ignore)
1267             return S_OK;
1268 
1269         icex.dwSize = sizeof(icex);
1270         icex.dwICC = ICC_LINK_CLASS;
1271 
1272         if (!InitCommonControlsEx(&icex))
1273             throw string_error(IDS_INITCOMMONCONTROLSEX_FAILED);
1274 
1275         psp.dwSize = sizeof(psp);
1276         psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE;
1277         psp.hInstance = module;
1278         psp.pszTemplate = MAKEINTRESOURCE(IDD_PROP_SHEET);
1279         psp.hIcon = 0;
1280         psp.pszTitle = MAKEINTRESOURCE(IDS_PROP_SHEET_TITLE);
1281         psp.pfnDlgProc = (DLGPROC)PropSheetDlgProc;
1282         psp.pcRefParent = (UINT*)&objs_loaded;
1283         psp.pfnCallback = nullptr;
1284         psp.lParam = (LPARAM)this;
1285 
1286         hPage = CreatePropertySheetPage(&psp);
1287 
1288         if (hPage) {
1289             if (pfnAddPage(hPage, lParam)) {
1290                 this->AddRef();
1291                 return S_OK;
1292             } else
1293                 DestroyPropertySheetPage(hPage);
1294         } else
1295             return E_OUTOFMEMORY;
1296     } catch (const exception& e) {
1297         error_message(nullptr, e.what());
1298     }
1299 
1300     return E_FAIL;
1301 }
1302 
1303 HRESULT __stdcall BtrfsPropSheet::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam) {
1304     return S_OK;
1305 }
1306 
1307 #ifdef __cplusplus
1308 extern "C" {
1309 #endif
1310 
1311 void CALLBACK ShowPropSheetW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
1312     try {
1313         BtrfsPropSheet bps;
1314         PROPSHEETPAGEW psp;
1315         PROPSHEETHEADERW psh;
1316         wstring title;
1317 
1318         set_dpi_aware();
1319 
1320         load_string(module, IDS_STANDALONE_PROPSHEET_TITLE, title);
1321 
1322         bps.set_cmdline(lpszCmdLine);
1323 
1324         psp.dwSize = sizeof(psp);
1325         psp.dwFlags = PSP_USETITLE;
1326         psp.hInstance = module;
1327         psp.pszTemplate = MAKEINTRESOURCEW(IDD_PROP_SHEET);
1328         psp.hIcon = 0;
1329         psp.pszTitle = MAKEINTRESOURCEW(IDS_PROP_SHEET_TITLE);
1330         psp.pfnDlgProc = (DLGPROC)PropSheetDlgProc;
1331         psp.pfnCallback = nullptr;
1332         psp.lParam = (LPARAM)&bps;
1333 
1334         memset(&psh, 0, sizeof(PROPSHEETHEADERW));
1335 
1336         psh.dwSize = sizeof(PROPSHEETHEADERW);
1337         psh.dwFlags = PSH_PROPSHEETPAGE;
1338         psh.hwndParent = hwnd;
1339         psh.hInstance = psp.hInstance;
1340         psh.pszCaption = title.c_str();
1341         psh.nPages = 1;
1342         psh.ppsp = &psp;
1343 
1344         PropertySheetW(&psh);
1345     } catch (const exception& e) {
1346         error_message(hwnd, e.what());
1347     }
1348 }
1349 
1350 #ifdef __cplusplus
1351 }
1352 #endif
1353