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