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