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