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(MAX_PATH))) {
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         return;
439 
440     Status = NtQueryInformationFile(h, &iosb, &fai, sizeof(FILE_ACCESS_INFORMATION), FileAccessInformation);
441     if (!NT_SUCCESS(Status))
442         return;
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) && !bii2.top) {
457         LARGE_INTEGER filesize;
458 
459         subvol = bii2.subvol;
460         inode = bii2.inode;
461         type = bii2.type;
462         uid = bii2.st_uid;
463         gid = bii2.st_gid;
464         rdev = bii2.st_rdev;
465 
466         if (bii2.inline_length > 0) {
467             totalsize += bii2.inline_length;
468             sizes[0] += bii2.inline_length;
469         }
470 
471         if (bii2.disk_size_uncompressed > 0) {
472             totalsize += bii2.disk_size_uncompressed;
473             sizes[1] += bii2.disk_size_uncompressed;
474         }
475 
476         if (bii2.disk_size_zlib > 0) {
477             totalsize += bii2.disk_size_zlib;
478             sizes[2] += bii2.disk_size_zlib;
479         }
480 
481         if (bii2.disk_size_lzo > 0) {
482             totalsize += bii2.disk_size_lzo;
483             sizes[3] += bii2.disk_size_lzo;
484         }
485 
486         if (bii2.disk_size_zstd > 0) {
487             totalsize += bii2.disk_size_zstd;
488             sizes[4] += bii2.disk_size_zstd;
489         }
490 
491         sparsesize += bii2.sparse_size;
492 
493         FILE_STANDARD_INFORMATION fsi;
494 
495         Status = NtQueryInformationFile(h, &iosb, &fsi, sizeof(fsi), FileStandardInformation);
496 
497         if (!NT_SUCCESS(Status))
498             throw ntstatus_error(Status);
499 
500         if (bii2.inline_length > 0)
501             allocsize += fsi.EndOfFile.QuadPart;
502         else
503             allocsize += fsi.AllocationSize.QuadPart;
504 
505         min_mode |= ~bii2.st_mode;
506         max_mode |= bii2.st_mode;
507         min_flags |= ~bii2.flags;
508         max_flags |= bii2.flags;
509         min_compression_type = bii2.compression_type < min_compression_type ? bii2.compression_type : min_compression_type;
510         max_compression_type = bii2.compression_type > max_compression_type ? bii2.compression_type : max_compression_type;
511 
512         if (bii2.inode == SUBVOL_ROOT_INODE) {
513             bool ro = bhfi.dwFileAttributes & FILE_ATTRIBUTE_READONLY;
514 
515             has_subvols = true;
516 
517             if (sv == 0)
518                 ro_subvol = ro;
519             else {
520                 if (ro_subvol != ro)
521                     various_ro = true;
522             }
523 
524             sv++;
525         }
526 
527         ignore = false;
528 
529         if (bii2.type != BTRFS_TYPE_DIRECTORY && GetFileSizeEx(h, &filesize)) {
530             if (filesize.QuadPart != 0)
531                 can_change_nocow = false;
532         }
533     } else
534         return;
535 
536     min_mode = ~min_mode;
537     min_flags = ~min_flags;
538 
539     mode = min_mode;
540     mode_set = ~(min_mode ^ max_mode);
541 
542     flags = min_flags;
543     flags_set = ~(min_flags ^ max_flags);
544 
545     if (search_list.size() > 0) {
546         thread = CreateThread(nullptr, 0, global_search_list_thread, this, 0, nullptr);
547 
548         if (!thread)
549             throw last_error(GetLastError());
550     }
551 
552     this->filename = cmdline;
553 }
554 
555 static ULONG inode_type_to_string_ref(uint8_t type) {
556     switch (type) {
557         case BTRFS_TYPE_FILE:
558             return IDS_INODE_FILE;
559 
560         case BTRFS_TYPE_DIRECTORY:
561             return IDS_INODE_DIR;
562 
563         case BTRFS_TYPE_CHARDEV:
564             return IDS_INODE_CHAR;
565 
566         case BTRFS_TYPE_BLOCKDEV:
567             return IDS_INODE_BLOCK;
568 
569         case BTRFS_TYPE_FIFO:
570             return IDS_INODE_FIFO;
571 
572         case BTRFS_TYPE_SOCKET:
573             return IDS_INODE_SOCKET;
574 
575         case BTRFS_TYPE_SYMLINK:
576             return IDS_INODE_SYMLINK;
577 
578         default:
579             return IDS_INODE_UNKNOWN;
580     }
581 }
582 
583 void BtrfsPropSheet::change_inode_flag(HWND hDlg, uint64_t flag, UINT state) {
584     if (flag & BTRFS_INODE_NODATACOW)
585         flag |= BTRFS_INODE_NODATASUM;
586 
587     if (state == BST_CHECKED) {
588         flags |= flag;
589         flags_set |= flag;
590     } else if (state == BST_UNCHECKED) {
591         flags &= ~flag;
592         flags_set |= flag;
593     } else if (state == BST_INDETERMINATE) {
594         flags_set = ~flag;
595     }
596 
597     flags_changed = true;
598 
599     SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
600 }
601 
602 void BtrfsPropSheet::apply_changes_file(HWND hDlg, const wstring& fn) {
603     win_handle h;
604     IO_STATUS_BLOCK iosb;
605     NTSTATUS Status;
606     btrfs_set_inode_info bsii;
607     btrfs_inode_info bii2;
608     ULONG perms = FILE_TRAVERSE | FILE_READ_ATTRIBUTES;
609 
610     if (flags_changed || ro_changed)
611         perms |= FILE_WRITE_ATTRIBUTES;
612 
613     if (perms_changed || gid_changed || uid_changed)
614         perms |= WRITE_DAC;
615 
616     if (mode_set & S_ISUID && (((min_mode & S_ISUID) != (max_mode & S_ISUID)) || ((min_mode & S_ISUID) != (mode & S_ISUID))))
617         perms |= WRITE_OWNER;
618 
619     h = CreateFileW(fn.c_str(), perms, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
620                     OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
621 
622     if (h == INVALID_HANDLE_VALUE)
623         throw last_error(GetLastError());
624 
625     ZeroMemory(&bsii, sizeof(btrfs_set_inode_info));
626 
627     Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info));
628 
629     if (!NT_SUCCESS(Status))
630         throw ntstatus_error(Status);
631 
632     if (bii2.inode == SUBVOL_ROOT_INODE && ro_changed) {
633         BY_HANDLE_FILE_INFORMATION bhfi;
634         FILE_BASIC_INFO fbi;
635 
636         if (!GetFileInformationByHandle(h, &bhfi))
637             throw last_error(GetLastError());
638 
639         memset(&fbi, 0, sizeof(fbi));
640         fbi.FileAttributes = bhfi.dwFileAttributes;
641 
642         if (ro_subvol)
643             fbi.FileAttributes |= FILE_ATTRIBUTE_READONLY;
644         else
645             fbi.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
646 
647         Status = NtSetInformationFile(h, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation);
648         if (!NT_SUCCESS(Status))
649             throw ntstatus_error(Status);
650     }
651 
652     if (flags_changed || perms_changed || uid_changed || gid_changed || compress_type_changed) {
653         if (flags_changed) {
654             bsii.flags_changed = true;
655             bsii.flags = (bii2.flags & ~flags_set) | (flags & flags_set);
656         }
657 
658         if (perms_changed) {
659             bsii.mode_changed = true;
660             bsii.st_mode = (bii2.st_mode & ~mode_set) | (mode & mode_set);
661         }
662 
663         if (uid_changed) {
664             bsii.uid_changed = true;
665             bsii.st_uid = uid;
666         }
667 
668         if (gid_changed) {
669             bsii.gid_changed = true;
670             bsii.st_gid = gid;
671         }
672 
673         if (compress_type_changed) {
674             bsii.compression_type_changed = true;
675             bsii.compression_type = compress_type;
676         }
677 
678         Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);
679 
680         if (!NT_SUCCESS(Status))
681             throw ntstatus_error(Status);
682     }
683 }
684 
685 void BtrfsPropSheet::apply_changes(HWND hDlg) {
686     UINT num_files, i;
687     WCHAR fn[MAX_PATH]; // FIXME - is this long enough?
688 
689     if (various_uids)
690         uid_changed = false;
691 
692     if (various_gids)
693         gid_changed = false;
694 
695     if (!flags_changed && !perms_changed && !uid_changed && !gid_changed && !compress_type_changed && !ro_changed)
696         return;
697 
698     if (filename[0] != 0)
699         apply_changes_file(hDlg, filename);
700     else {
701         num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);
702 
703         for (i = 0; i < num_files; i++) {
704             if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(MAX_PATH))) {
705                 apply_changes_file(hDlg, fn);
706             }
707         }
708     }
709 
710     flags_changed = false;
711     perms_changed = false;
712     uid_changed = false;
713     gid_changed = false;
714     ro_changed = false;
715 }
716 
717 void BtrfsPropSheet::set_size_on_disk(HWND hwndDlg) {
718     wstring s, size_on_disk, cr, frag;
719     WCHAR old_text[1024];
720     float ratio;
721 
722     format_size(totalsize, size_on_disk, true);
723 
724     wstring_sprintf(s, size_format, size_on_disk.c_str());
725 
726     if (allocsize == sparsesize || totalsize == 0)
727         ratio = 0.0f;
728     else
729         ratio = 100.0f * (1.0f - ((float)totalsize / (float)(allocsize - sparsesize)));
730 
731     wstring_sprintf(cr, cr_format, ratio);
732 
733     GetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, old_text, sizeof(old_text) / sizeof(WCHAR));
734 
735     if (s != old_text)
736         SetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, s.c_str());
737 
738     GetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, old_text, sizeof(old_text) / sizeof(WCHAR));
739 
740     if (cr != old_text)
741         SetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, cr.c_str());
742 
743     uint64_t extent_size = (allocsize - sparsesize - sizes[0]) / sector_size;
744 
745     if (num_extents == 0 || extent_size <= 1)
746         ratio = 0.0f;
747     else
748         ratio = 100.0f * ((float)num_extents / (float)(extent_size - 1));
749 
750     wstring_sprintf(frag, frag_format, ratio);
751 
752     GetDlgItemTextW(hwndDlg, IDC_FRAGMENTATION, old_text, sizeof(old_text) / sizeof(WCHAR));
753 
754 
755     if (frag != old_text)
756         SetDlgItemTextW(hwndDlg, IDC_FRAGMENTATION, frag.c_str());
757 }
758 
759 void BtrfsPropSheet::change_perm_flag(HWND hDlg, ULONG flag, UINT state) {
760     if (state == BST_CHECKED) {
761         mode |= flag;
762         mode_set |= flag;
763     } else if (state == BST_UNCHECKED) {
764         mode &= ~flag;
765         mode_set |= flag;
766     } else if (state == BST_INDETERMINATE) {
767         mode_set = ~flag;
768     }
769 
770     perms_changed = true;
771 
772     SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
773 }
774 
775 void BtrfsPropSheet::change_uid(HWND hDlg, uint32_t uid) {
776     if (this->uid != uid) {
777         this->uid = uid;
778         uid_changed = true;
779 
780         SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
781     }
782 }
783 
784 void BtrfsPropSheet::change_gid(HWND hDlg, uint32_t gid) {
785     if (this->gid != gid) {
786         this->gid = gid;
787         gid_changed = true;
788 
789         SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
790     }
791 }
792 
793 void BtrfsPropSheet::update_size_details_dialog(HWND hDlg) {
794     wstring size;
795     WCHAR old_text[1024];
796     int i;
797     ULONG items[] = { IDC_SIZE_INLINE, IDC_SIZE_UNCOMPRESSED, IDC_SIZE_ZLIB, IDC_SIZE_LZO, IDC_SIZE_ZSTD };
798 
799     for (i = 0; i < 5; i++) {
800         format_size(sizes[i], size, true);
801 
802         GetDlgItemTextW(hDlg, items[i], old_text, sizeof(old_text) / sizeof(WCHAR));
803 
804         if (size != old_text)
805             SetDlgItemTextW(hDlg, items[i], size.c_str());
806     }
807 }
808 
809 static INT_PTR CALLBACK SizeDetailsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
810     try {
811         switch (uMsg) {
812             case WM_INITDIALOG:
813             {
814                 BtrfsPropSheet* bps = (BtrfsPropSheet*)lParam;
815 
816                 SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)bps);
817 
818                 bps->update_size_details_dialog(hwndDlg);
819 
820                 if (bps->thread)
821                     SetTimer(hwndDlg, 1, 250, nullptr);
822 
823                 return true;
824             }
825 
826             case WM_COMMAND:
827                 if (HIWORD(wParam) == BN_CLICKED && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {
828                     EndDialog(hwndDlg, 0);
829                     return true;
830                 }
831             break;
832 
833             case WM_TIMER:
834             {
835                 BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
836 
837                 if (bps) {
838                     bps->update_size_details_dialog(hwndDlg);
839 
840                     if (!bps->thread)
841                         KillTimer(hwndDlg, 1);
842                 }
843 
844                 break;
845             }
846         }
847     } catch (const exception& e) {
848         error_message(hwndDlg, e.what());
849     }
850 
851     return false;
852 }
853 
854 static void set_check_box(HWND hwndDlg, ULONG id, uint64_t min, uint64_t max) {
855     if (min && max) {
856         SendDlgItemMessage(hwndDlg, id, BM_SETCHECK, BST_CHECKED, 0);
857     } else if (!min && !max) {
858         SendDlgItemMessage(hwndDlg, id, BM_SETCHECK, BST_UNCHECKED, 0);
859     } else {
860         LONG_PTR style;
861 
862         style = GetWindowLongPtr(GetDlgItem(hwndDlg, id), GWL_STYLE);
863         style &= ~BS_AUTOCHECKBOX;
864         style |= BS_AUTO3STATE;
865         SetWindowLongPtr(GetDlgItem(hwndDlg, id), GWL_STYLE, style);
866 
867         SendDlgItemMessage(hwndDlg, id, BM_SETCHECK, BST_INDETERMINATE, 0);
868     }
869 }
870 
871 void BtrfsPropSheet::open_as_admin(HWND hwndDlg) {
872     ULONG num_files, i;
873     WCHAR fn[MAX_PATH], modfn[MAX_PATH];
874 
875     num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);
876 
877     if (num_files == 0)
878         return;
879 
880     GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));
881 
882     for (i = 0; i < num_files; i++) {
883         if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(MAX_PATH))) {
884             wstring t;
885             SHELLEXECUTEINFOW sei;
886 
887 #ifndef __REACTOS__
888             t = L"\""s + modfn + L"\",ShowPropSheet "s + fn;
889 #else
890             t = wstring(L"\"") + modfn + wstring(L"\",ShowPropSheet ") + fn;
891 #endif
892 
893             RtlZeroMemory(&sei, sizeof(sei));
894 
895             sei.cbSize = sizeof(sei);
896             sei.hwnd = hwndDlg;
897             sei.lpVerb = L"runas";
898             sei.lpFile = L"rundll32.exe";
899             sei.lpParameters = t.c_str();
900             sei.nShow = SW_SHOW;
901             sei.fMask = SEE_MASK_NOCLOSEPROCESS;
902 
903             if (!ShellExecuteExW(&sei))
904                 throw last_error(GetLastError());
905 
906             WaitForSingleObject(sei.hProcess, INFINITE);
907             CloseHandle(sei.hProcess);
908 
909             load_file_list();
910             init_propsheet(hwndDlg);
911         }
912     }
913 }
914 
915 // based on functions in sys/sysmacros.h
916 #define major(rdev) ((((rdev) >> 8) & 0xFFF) | ((uint32_t)((rdev) >> 32) & ~0xFFF))
917 #define minor(rdev) (((rdev) & 0xFF) | ((uint32_t)((rdev) >> 12) & ~0xFF))
918 
919 void BtrfsPropSheet::init_propsheet(HWND hwndDlg) {
920     wstring s;
921     ULONG sr;
922     int i;
923     HWND comptype;
924 
925     static ULONG perm_controls[] = { IDC_USERR, IDC_USERW, IDC_USERX, IDC_GROUPR, IDC_GROUPW, IDC_GROUPX, IDC_OTHERR, IDC_OTHERW, IDC_OTHERX,
926                                      IDC_SETUID, IDC_SETGID, IDC_STICKY, 0 };
927     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 };
928     static ULONG comp_types[] = { IDS_COMPRESS_ANY, IDS_COMPRESS_ZLIB, IDS_COMPRESS_LZO, IDS_COMPRESS_ZSTD, 0 };
929 
930     if (various_subvols) {
931         if (!load_string(module, IDS_VARIOUS, s))
932             throw last_error(GetLastError());
933     } else
934         wstring_sprintf(s, L"%llx", subvol);
935 
936     SetDlgItemTextW(hwndDlg, IDC_SUBVOL, s.c_str());
937 
938     if (various_inodes) {
939         if (!load_string(module, IDS_VARIOUS, s))
940             throw last_error(GetLastError());
941     } else
942         wstring_sprintf(s, L"%llx", inode);
943 
944     SetDlgItemTextW(hwndDlg, IDC_INODE, s.c_str());
945 
946     if (various_types)
947         sr = IDS_VARIOUS;
948     else
949         sr = inode_type_to_string_ref(type);
950 
951     if (various_inodes) {
952         if (sr == IDS_INODE_CHAR)
953             sr = IDS_INODE_CHAR_SIMPLE;
954         else if (sr == IDS_INODE_BLOCK)
955             sr = IDS_INODE_BLOCK_SIMPLE;
956     }
957 
958     if (sr == IDS_INODE_UNKNOWN) {
959         wstring t;
960 
961         if (!load_string(module, sr, t))
962             throw last_error(GetLastError());
963 
964         wstring_sprintf(s, t, type);
965     } else if (sr == IDS_INODE_CHAR || sr == IDS_INODE_BLOCK) {
966         wstring t;
967 
968         if (!load_string(module, sr, t))
969             throw last_error(GetLastError());
970 
971         wstring_sprintf(s, t, major(rdev), minor(rdev));
972     } else {
973         if (!load_string(module, sr, s))
974             throw last_error(GetLastError());
975     }
976 
977     SetDlgItemTextW(hwndDlg, IDC_TYPE, s.c_str());
978 
979     if (size_format[0] == 0)
980         GetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, size_format, sizeof(size_format) / sizeof(WCHAR));
981 
982     if (cr_format[0] == 0)
983         GetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, cr_format, sizeof(cr_format) / sizeof(WCHAR));
984 
985     if (frag_format[0] == 0)
986         GetDlgItemTextW(hwndDlg, IDC_FRAGMENTATION, frag_format, sizeof(frag_format) / sizeof(WCHAR));
987 
988     set_size_on_disk(hwndDlg);
989 
990     if (thread)
991         SetTimer(hwndDlg, 1, 250, nullptr);
992 
993     set_check_box(hwndDlg, IDC_NODATACOW, min_flags & BTRFS_INODE_NODATACOW, max_flags & BTRFS_INODE_NODATACOW);
994     set_check_box(hwndDlg, IDC_COMPRESS, min_flags & BTRFS_INODE_COMPRESS, max_flags & BTRFS_INODE_COMPRESS);
995 
996     comptype = GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE);
997 
998     while (SendMessage(comptype, CB_GETCOUNT, 0, 0) > 0) {
999         SendMessage(comptype, CB_DELETESTRING, 0, 0);
1000     }
1001 
1002     if (min_compression_type != max_compression_type) {
1003         SendMessage(comptype, CB_ADDSTRING, 0, (LPARAM)L"");
1004         SendMessage(comptype, CB_SETCURSEL, 0, 0);
1005     }
1006 
1007     i = 0;
1008     while (comp_types[i] != 0) {
1009         wstring t;
1010 
1011         if (!load_string(module, comp_types[i], t))
1012             throw last_error(GetLastError());
1013 
1014         SendMessage(comptype, CB_ADDSTRING, 0, (LPARAM)t.c_str());
1015 
1016         i++;
1017     }
1018 
1019     if (min_compression_type == max_compression_type) {
1020         SendMessage(comptype, CB_SETCURSEL, min_compression_type, 0);
1021         compress_type = min_compression_type;
1022     }
1023 
1024     EnableWindow(comptype, max_flags & BTRFS_INODE_COMPRESS);
1025 
1026     i = 0;
1027     while (perm_controls[i] != 0) {
1028         set_check_box(hwndDlg, perm_controls[i], min_mode & perms[i], max_mode & perms[i]);
1029         i++;
1030     }
1031 
1032     if (various_uids) {
1033         if (!load_string(module, IDS_VARIOUS, s))
1034             throw last_error(GetLastError());
1035 
1036         EnableWindow(GetDlgItem(hwndDlg, IDC_UID), 0);
1037     } else
1038         s = to_wstring(uid);
1039 
1040     SetDlgItemTextW(hwndDlg, IDC_UID, s.c_str());
1041 
1042     if (various_gids) {
1043         if (!load_string(module, IDS_VARIOUS, s))
1044             throw last_error(GetLastError());
1045 
1046         EnableWindow(GetDlgItem(hwndDlg, IDC_GID), 0);
1047     } else
1048         s = to_wstring(gid);
1049 
1050     SetDlgItemTextW(hwndDlg, IDC_GID, s.c_str());
1051 
1052     ShowWindow(GetDlgItem(hwndDlg, IDC_SUBVOL_RO), has_subvols);
1053 
1054     if (has_subvols)
1055         set_check_box(hwndDlg, IDC_SUBVOL_RO, ro_subvol, various_ro ? (!ro_subvol) : ro_subvol);
1056 
1057     if (!can_change_nocow)
1058         EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), 0);
1059 
1060     if (!can_change_perms) {
1061         i = 0;
1062         while (perm_controls[i] != 0) {
1063             EnableWindow(GetDlgItem(hwndDlg, perm_controls[i]), 0);
1064             i++;
1065         }
1066 
1067         EnableWindow(GetDlgItem(hwndDlg, IDC_UID), 0);
1068         EnableWindow(GetDlgItem(hwndDlg, IDC_GID), 0);
1069         EnableWindow(GetDlgItem(hwndDlg, IDC_SETUID), 0);
1070     }
1071 
1072     if (readonly) {
1073         EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), 0);
1074         EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS), 0);
1075         EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE), 0);
1076     }
1077 
1078     if (show_admin_button) {
1079         SendMessageW(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), BCM_SETSHIELD, 0, true);
1080         ShowWindow(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), SW_SHOW);
1081     } else
1082         ShowWindow(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), SW_HIDE);
1083 }
1084 
1085 static INT_PTR CALLBACK PropSheetDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
1086     try {
1087         switch (uMsg) {
1088             case WM_INITDIALOG:
1089             {
1090                 PROPSHEETPAGE* psp = (PROPSHEETPAGE*)lParam;
1091                 BtrfsPropSheet* bps = (BtrfsPropSheet*)psp->lParam;
1092 
1093                 EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB);
1094 
1095                 SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)bps);
1096 
1097                 bps->init_propsheet(hwndDlg);
1098 
1099                 return false;
1100             }
1101 
1102             case WM_COMMAND:
1103             {
1104                 BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
1105 
1106                 if (bps && !bps->readonly) {
1107                     switch (HIWORD(wParam)) {
1108                         case BN_CLICKED: {
1109                             switch (LOWORD(wParam)) {
1110                                 case IDC_NODATACOW:
1111                                     bps->change_inode_flag(hwndDlg, BTRFS_INODE_NODATACOW, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1112                                 break;
1113 
1114                                 case IDC_COMPRESS:
1115                                     bps->change_inode_flag(hwndDlg, BTRFS_INODE_COMPRESS, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1116 
1117                                     EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE), IsDlgButtonChecked(hwndDlg, LOWORD(wParam)) != BST_UNCHECKED);
1118                                 break;
1119 
1120                                 case IDC_USERR:
1121                                     bps->change_perm_flag(hwndDlg, S_IRUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1122                                 break;
1123 
1124                                 case IDC_USERW:
1125                                     bps->change_perm_flag(hwndDlg, S_IWUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1126                                 break;
1127 
1128                                 case IDC_USERX:
1129                                     bps->change_perm_flag(hwndDlg, S_IXUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1130                                 break;
1131 
1132                                 case IDC_GROUPR:
1133                                     bps->change_perm_flag(hwndDlg, S_IRGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1134                                 break;
1135 
1136                                 case IDC_GROUPW:
1137                                     bps->change_perm_flag(hwndDlg, S_IWGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1138                                 break;
1139 
1140                                 case IDC_GROUPX:
1141                                     bps->change_perm_flag(hwndDlg, S_IXGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1142                                 break;
1143 
1144                                 case IDC_OTHERR:
1145                                     bps->change_perm_flag(hwndDlg, S_IROTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1146                                 break;
1147 
1148                                 case IDC_OTHERW:
1149                                     bps->change_perm_flag(hwndDlg, S_IWOTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1150                                 break;
1151 
1152                                 case IDC_OTHERX:
1153                                     bps->change_perm_flag(hwndDlg, S_IXOTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1154                                 break;
1155 
1156                                 case IDC_SETUID:
1157                                     bps->change_perm_flag(hwndDlg, S_ISUID, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1158                                 break;
1159 
1160                                 case IDC_SETGID:
1161                                     bps->change_perm_flag(hwndDlg, S_ISGID, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1162                                 break;
1163 
1164                                 case IDC_STICKY:
1165                                     bps->change_perm_flag(hwndDlg, S_ISVTX, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));
1166                                 break;
1167 
1168                                 case IDC_SUBVOL_RO:
1169                                     switch (IsDlgButtonChecked(hwndDlg, LOWORD(wParam))) {
1170                                         case BST_CHECKED:
1171                                             bps->ro_subvol = true;
1172                                             bps->ro_changed = true;
1173                                         break;
1174 
1175                                         case BST_UNCHECKED:
1176                                             bps->ro_subvol = false;
1177                                             bps->ro_changed = true;
1178                                         break;
1179 
1180                                         case BST_INDETERMINATE:
1181                                             bps->ro_changed = false;
1182                                         break;
1183                                     }
1184 
1185                                     SendMessageW(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0);
1186                                 break;
1187 
1188                                 case IDC_OPEN_ADMIN:
1189                                     bps->open_as_admin(hwndDlg);
1190                                 break;
1191                             }
1192 
1193                             break;
1194                         }
1195 
1196                         case EN_CHANGE: {
1197                             switch (LOWORD(wParam)) {
1198                                 case IDC_UID: {
1199                                     WCHAR s[255];
1200 
1201                                     GetDlgItemTextW(hwndDlg, LOWORD(wParam), s, sizeof(s) / sizeof(WCHAR));
1202 
1203                                     bps->change_uid(hwndDlg, _wtoi(s));
1204                                     break;
1205                                 }
1206 
1207                                 case IDC_GID: {
1208                                     WCHAR s[255];
1209 
1210                                     GetDlgItemTextW(hwndDlg, LOWORD(wParam), s, sizeof(s) / sizeof(WCHAR));
1211 
1212                                     bps->change_gid(hwndDlg, _wtoi(s));
1213                                     break;
1214                                 }
1215                             }
1216 
1217                             break;
1218                         }
1219 
1220                         case CBN_SELCHANGE: {
1221                             switch (LOWORD(wParam)) {
1222                                 case IDC_COMPRESS_TYPE: {
1223                                     auto sel = SendMessageW(GetDlgItem(hwndDlg, LOWORD(wParam)), CB_GETCURSEL, 0, 0);
1224 
1225                                     if (bps->min_compression_type != bps->max_compression_type) {
1226                                         if (sel == 0)
1227                                             bps->compress_type_changed = false;
1228                                         else {
1229                                             bps->compress_type = (uint8_t)(sel - 1);
1230                                             bps->compress_type_changed = true;
1231                                         }
1232                                     } else {
1233                                         bps->compress_type = (uint8_t)sel;
1234                                         bps->compress_type_changed = true;
1235                                     }
1236 
1237                                     SendMessageW(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0);
1238 
1239                                     break;
1240                                 }
1241                             }
1242 
1243                             break;
1244                         }
1245                     }
1246                 }
1247 
1248                 break;
1249             }
1250 
1251             case WM_NOTIFY:
1252             {
1253                 switch (((LPNMHDR)lParam)->code) {
1254                     case PSN_KILLACTIVE:
1255                         SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, false);
1256                     break;
1257 
1258                     case PSN_APPLY: {
1259                         BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
1260 
1261                         bps->apply_changes(hwndDlg);
1262                         SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
1263                         break;
1264                     }
1265 
1266                     case NM_CLICK:
1267                     case NM_RETURN: {
1268                         if (((LPNMHDR)lParam)->hwndFrom == GetDlgItem(hwndDlg, IDC_SIZE_ON_DISK)) {
1269                             PNMLINK pNMLink = (PNMLINK)lParam;
1270 
1271                             if (pNMLink->item.iLink == 0)
1272                                 DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_SIZE_DETAILS), hwndDlg, SizeDetailsDlgProc, GetWindowLongPtr(hwndDlg, GWLP_USERDATA));
1273                         }
1274                         break;
1275                     }
1276                 }
1277             }
1278 
1279             case WM_TIMER:
1280             {
1281                 BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
1282 
1283                 if (bps) {
1284                     bps->set_size_on_disk(hwndDlg);
1285 
1286                     if (!bps->thread)
1287                         KillTimer(hwndDlg, 1);
1288                 }
1289 
1290                 break;
1291             }
1292         }
1293     } catch (const exception& e) {
1294         error_message(hwndDlg, e.what());
1295     }
1296 
1297     return false;
1298 }
1299 
1300 HRESULT __stdcall BtrfsPropSheet::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) {
1301     try {
1302         PROPSHEETPAGE psp;
1303         HPROPSHEETPAGE hPage;
1304         INITCOMMONCONTROLSEX icex;
1305 
1306         if (ignore)
1307             return S_OK;
1308 
1309         icex.dwSize = sizeof(icex);
1310         icex.dwICC = ICC_LINK_CLASS;
1311 
1312         if (!InitCommonControlsEx(&icex))
1313             throw string_error(IDS_INITCOMMONCONTROLSEX_FAILED);
1314 
1315         psp.dwSize = sizeof(psp);
1316         psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE;
1317         psp.hInstance = module;
1318         psp.pszTemplate = MAKEINTRESOURCE(IDD_PROP_SHEET);
1319         psp.hIcon = 0;
1320         psp.pszTitle = MAKEINTRESOURCE(IDS_PROP_SHEET_TITLE);
1321         psp.pfnDlgProc = (DLGPROC)PropSheetDlgProc;
1322         psp.pcRefParent = (UINT*)&objs_loaded;
1323         psp.pfnCallback = nullptr;
1324         psp.lParam = (LPARAM)this;
1325 
1326         hPage = CreatePropertySheetPage(&psp);
1327 
1328         if (hPage) {
1329             if (pfnAddPage(hPage, lParam)) {
1330                 this->AddRef();
1331                 return S_OK;
1332             } else
1333                 DestroyPropertySheetPage(hPage);
1334         } else
1335             return E_OUTOFMEMORY;
1336     } catch (const exception& e) {
1337         error_message(nullptr, e.what());
1338     }
1339 
1340     return E_FAIL;
1341 }
1342 
1343 HRESULT __stdcall BtrfsPropSheet::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam) {
1344     return S_OK;
1345 }
1346 
1347 #ifdef __cplusplus
1348 extern "C" {
1349 #endif
1350 
1351 void CALLBACK ShowPropSheetW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
1352     try {
1353         BtrfsPropSheet bps;
1354         PROPSHEETPAGEW psp;
1355         PROPSHEETHEADERW psh;
1356         wstring title;
1357 
1358         set_dpi_aware();
1359 
1360         load_string(module, IDS_STANDALONE_PROPSHEET_TITLE, title);
1361 
1362         bps.set_cmdline(lpszCmdLine);
1363 
1364         psp.dwSize = sizeof(psp);
1365         psp.dwFlags = PSP_USETITLE;
1366         psp.hInstance = module;
1367         psp.pszTemplate = MAKEINTRESOURCEW(IDD_PROP_SHEET);
1368         psp.hIcon = 0;
1369         psp.pszTitle = MAKEINTRESOURCEW(IDS_PROP_SHEET_TITLE);
1370         psp.pfnDlgProc = (DLGPROC)PropSheetDlgProc;
1371         psp.pfnCallback = nullptr;
1372         psp.lParam = (LPARAM)&bps;
1373 
1374         memset(&psh, 0, sizeof(PROPSHEETHEADERW));
1375 
1376         psh.dwSize = sizeof(PROPSHEETHEADERW);
1377         psh.dwFlags = PSH_PROPSHEETPAGE;
1378         psh.hwndParent = hwnd;
1379         psh.hInstance = psp.hInstance;
1380         psh.pszCaption = title.c_str();
1381         psh.nPages = 1;
1382         psh.ppsp = &psp;
1383 
1384         PropertySheetW(&psh);
1385     } catch (const exception& e) {
1386         error_message(hwnd, e.what());
1387     }
1388 }
1389 
1390 #ifdef __cplusplus
1391 }
1392 #endif
1393