1 /* Copyright (c) Mark Harmstone 2017
3 * This file is part of WinBtrfs.
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.
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.
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/>. */
22 #include "../btrfsioctl.h"
24 #include "btrfsioctl.h"
33 #define WIN32_NO_STATUS
37 #include <ndk/iofuncs.h>
38 #include <ndk/iotypes.h>
41 #define NO_SHLWAPI_STRFCNS
45 void BtrfsScrub::UpdateTextBox(HWND hwndDlg
, btrfs_query_scrub
* bqs
) {
46 btrfs_query_scrub
* bqs2
= nullptr;
47 bool alloc_bqs2
= false;
50 WCHAR dt
[255], tm
[255];
53 uint64_t recoverable_errors
= 0, unrecoverable_errors
= 0;
56 if (bqs
->num_errors
> 0) {
61 h
= CreateFileW(fn
.c_str(), FILE_TRAVERSE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, nullptr,
62 OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OPEN_REPARSE_POINT
, nullptr);
63 if (h
== INVALID_HANDLE_VALUE
)
64 throw last_error(GetLastError());
77 bqs2
= (btrfs_query_scrub
*)malloc(len
);
79 Status
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_QUERY_SCRUB
, nullptr, 0, bqs2
, len
);
81 if (!NT_SUCCESS(Status
) && Status
!= STATUS_BUFFER_OVERFLOW
)
82 throw ntstatus_error(Status
);
83 } while (Status
== STATUS_BUFFER_OVERFLOW
);
96 if (bqs2
->start_time
.QuadPart
> 0) {
97 filetime
.dwLowDateTime
= bqs2
->start_time
.LowPart
;
98 filetime
.dwHighDateTime
= bqs2
->start_time
.HighPart
;
100 if (!FileTimeToSystemTime(&filetime
, &systime
))
101 throw last_error(GetLastError());
103 if (!SystemTimeToTzSpecificLocalTime(nullptr, &systime
, &systime
))
104 throw last_error(GetLastError());
106 if (!load_string(module
, IDS_SCRUB_MSG_STARTED
, t
))
107 throw last_error(GetLastError());
109 if (!GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, &systime
, nullptr, dt
, sizeof(dt
) / sizeof(WCHAR
)))
110 throw last_error(GetLastError());
112 if (!GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &systime
, nullptr, tm
, sizeof(tm
) / sizeof(WCHAR
)))
113 throw last_error(GetLastError());
115 wstring_sprintf(u
, t
, dt
, tm
);
122 if (bqs2
->num_errors
> 0) {
123 btrfs_scrub_error
* bse
= &bqs2
->errors
;
127 recoverable_errors
++;
129 unrecoverable_errors
++;
132 if (!load_string(module
, IDS_SCRUB_MSG_RECOVERABLE_PARITY
, t
))
133 throw last_error(GetLastError());
135 wstring_sprintf(u
, t
, bse
->address
, bse
->device
);
136 } else if (bse
->is_metadata
) {
140 message
= IDS_SCRUB_MSG_RECOVERABLE_METADATA
;
141 else if (bse
->metadata
.firstitem
.obj_id
== 0 && bse
->metadata
.firstitem
.obj_type
== 0 && bse
->metadata
.firstitem
.offset
== 0)
142 message
= IDS_SCRUB_MSG_UNRECOVERABLE_METADATA
;
144 message
= IDS_SCRUB_MSG_UNRECOVERABLE_METADATA_FIRSTITEM
;
146 if (!load_string(module
, message
, t
))
147 throw last_error(GetLastError());
150 wstring_sprintf(u
, t
, bse
->address
, bse
->device
);
151 else if (bse
->metadata
.firstitem
.obj_id
== 0 && bse
->metadata
.firstitem
.obj_type
== 0 && bse
->metadata
.firstitem
.offset
== 0)
152 wstring_sprintf(u
, t
, bse
->address
, bse
->device
, bse
->metadata
.root
, bse
->metadata
.level
);
154 wstring_sprintf(u
, t
, bse
->address
, bse
->device
, bse
->metadata
.root
, bse
->metadata
.level
, bse
->metadata
.firstitem
.obj_id
,
155 bse
->metadata
.firstitem
.obj_type
, bse
->metadata
.firstitem
.offset
);
160 message
= IDS_SCRUB_MSG_RECOVERABLE_DATA
;
161 else if (bse
->data
.subvol
!= 0)
162 message
= IDS_SCRUB_MSG_UNRECOVERABLE_DATA_SUBVOL
;
164 message
= IDS_SCRUB_MSG_UNRECOVERABLE_DATA
;
166 if (!load_string(module
, message
, t
))
167 throw last_error(GetLastError());
170 wstring_sprintf(u
, t
, bse
->address
, bse
->device
);
171 else if (bse
->data
.subvol
!= 0)
172 wstring_sprintf(u
, t
, bse
->address
, bse
->device
, bse
->data
.subvol
,
173 bse
->data
.filename_length
/ sizeof(WCHAR
), bse
->data
.filename
, bse
->data
.offset
);
175 wstring_sprintf(u
, t
, bse
->address
, bse
->device
, bse
->data
.filename_length
/ sizeof(WCHAR
),
176 bse
->data
.filename
, bse
->data
.offset
);
182 if (bse
->next_entry
== 0)
185 bse
= (btrfs_scrub_error
*)((uint8_t*)bse
+ bse
->next_entry
);
189 if (bqs2
->finish_time
.QuadPart
> 0) {
195 filetime
.dwLowDateTime
= bqs2
->finish_time
.LowPart
;
196 filetime
.dwHighDateTime
= bqs2
->finish_time
.HighPart
;
198 if (!FileTimeToSystemTime(&filetime
, &systime
))
199 throw last_error(GetLastError());
201 if (!SystemTimeToTzSpecificLocalTime(nullptr, &systime
, &systime
))
202 throw last_error(GetLastError());
204 if (!load_string(module
, IDS_SCRUB_MSG_FINISHED
, t
))
205 throw last_error(GetLastError());
207 if (!GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, &systime
, nullptr, dt
, sizeof(dt
) / sizeof(WCHAR
)))
208 throw last_error(GetLastError());
210 if (!GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &systime
, nullptr, tm
, sizeof(tm
) / sizeof(WCHAR
)))
211 throw last_error(GetLastError());
213 wstring_sprintf(u
, t
, dt
, tm
);
220 if (!load_string(module
, IDS_SCRUB_MSG_SUMMARY
, t
))
221 throw last_error(GetLastError());
223 format_size(bqs2
->data_scrubbed
, d1
, false);
225 speed
= (float)bqs2
->data_scrubbed
/ ((float)bqs2
->duration
/ 10000000.0f
);
227 format_size((uint64_t)speed
, d2
, false);
229 wstring_sprintf(u
, t
, d1
.c_str(), bqs2
->duration
/ 10000000, d2
.c_str());
234 // recoverable errors
236 if (!load_string(module
, IDS_SCRUB_MSG_SUMMARY_ERRORS_RECOVERABLE
, t
))
237 throw last_error(GetLastError());
239 wstring_sprintf(u
, t
, recoverable_errors
);
244 // unrecoverable errors
246 if (!load_string(module
, IDS_SCRUB_MSG_SUMMARY_ERRORS_UNRECOVERABLE
, t
))
247 throw last_error(GetLastError());
249 wstring_sprintf(u
, t
, unrecoverable_errors
);
255 SetWindowTextW(GetDlgItem(hwndDlg
, IDC_SCRUB_INFO
), s
.c_str());
267 void BtrfsScrub::RefreshScrubDlg(HWND hwndDlg
, bool first_time
) {
268 btrfs_query_scrub bqs
;
271 win_handle h
= CreateFileW(fn
.c_str(), FILE_TRAVERSE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, nullptr,
272 OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OPEN_REPARSE_POINT
, nullptr);
273 if (h
!= INVALID_HANDLE_VALUE
) {
275 IO_STATUS_BLOCK iosb
;
277 Status
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_QUERY_SCRUB
, nullptr, 0, &bqs
, sizeof(btrfs_query_scrub
));
279 if (!NT_SUCCESS(Status
) && Status
!= STATUS_BUFFER_OVERFLOW
)
280 throw ntstatus_error(Status
);
282 throw last_error(GetLastError());
285 if (first_time
|| status
!= bqs
.status
|| chunks_left
!= bqs
.chunks_left
) {
288 if (bqs
.status
== BTRFS_SCRUB_STOPPED
) {
289 EnableWindow(GetDlgItem(hwndDlg
, IDC_START_SCRUB
), true);
290 EnableWindow(GetDlgItem(hwndDlg
, IDC_PAUSE_SCRUB
), false);
291 EnableWindow(GetDlgItem(hwndDlg
, IDC_CANCEL_SCRUB
), false);
293 if (bqs
.error
!= STATUS_SUCCESS
) {
296 if (!load_string(module
, IDS_SCRUB_FAILED
, t
))
297 throw last_error(GetLastError());
299 wstring_sprintf(s
, t
, bqs
.error
);
301 if (!load_string(module
, bqs
.total_chunks
== 0 ? IDS_NO_SCRUB
: IDS_SCRUB_FINISHED
, s
))
302 throw last_error(GetLastError());
308 EnableWindow(GetDlgItem(hwndDlg
, IDC_START_SCRUB
), false);
309 EnableWindow(GetDlgItem(hwndDlg
, IDC_PAUSE_SCRUB
), true);
310 EnableWindow(GetDlgItem(hwndDlg
, IDC_CANCEL_SCRUB
), true);
312 if (!load_string(module
, bqs
.status
== BTRFS_SCRUB_PAUSED
? IDS_SCRUB_PAUSED
: IDS_SCRUB_RUNNING
, t
))
313 throw last_error(GetLastError());
315 pc
= ((float)(bqs
.total_chunks
- bqs
.chunks_left
) / (float)bqs
.total_chunks
) * 100.0f
;
317 wstring_sprintf(s
, t
, bqs
.total_chunks
- bqs
.chunks_left
, bqs
.total_chunks
, pc
);
320 SetDlgItemTextW(hwndDlg
, IDC_SCRUB_STATUS
, s
.c_str());
322 if (first_time
|| status
!= bqs
.status
) {
323 EnableWindow(GetDlgItem(hwndDlg
, IDC_SCRUB_PROGRESS
), bqs
.status
!= BTRFS_SCRUB_STOPPED
);
325 if (bqs
.status
!= BTRFS_SCRUB_STOPPED
) {
326 SendMessageW(GetDlgItem(hwndDlg
, IDC_SCRUB_PROGRESS
), PBM_SETRANGE32
, 0, (LPARAM
)bqs
.total_chunks
);
327 SendMessageW(GetDlgItem(hwndDlg
, IDC_SCRUB_PROGRESS
), PBM_SETPOS
, (WPARAM
)(bqs
.total_chunks
- bqs
.chunks_left
), 0);
329 if (bqs
.status
== BTRFS_SCRUB_PAUSED
)
330 SendMessageW(GetDlgItem(hwndDlg
, IDC_SCRUB_PROGRESS
), PBM_SETSTATE
, PBST_PAUSED
, 0);
332 SendMessageW(GetDlgItem(hwndDlg
, IDC_SCRUB_PROGRESS
), PBM_SETSTATE
, PBST_NORMAL
, 0);
334 SendMessageW(GetDlgItem(hwndDlg
, IDC_SCRUB_PROGRESS
), PBM_SETRANGE32
, 0, 0);
335 SendMessageW(GetDlgItem(hwndDlg
, IDC_SCRUB_PROGRESS
), PBM_SETPOS
, 0, 0);
338 chunks_left
= bqs
.chunks_left
;
342 if (bqs
.status
!= BTRFS_SCRUB_STOPPED
&& chunks_left
!= bqs
.chunks_left
) {
343 SendMessageW(GetDlgItem(hwndDlg
, IDC_SCRUB_PROGRESS
), PBM_SETPOS
, (WPARAM
)(bqs
.total_chunks
- bqs
.chunks_left
), 0);
344 chunks_left
= bqs
.chunks_left
;
347 if (first_time
|| status
!= bqs
.status
|| num_errors
!= bqs
.num_errors
) {
348 UpdateTextBox(hwndDlg
, &bqs
);
350 num_errors
= bqs
.num_errors
;
356 void BtrfsScrub::StartScrub(HWND hwndDlg
) {
357 win_handle h
= CreateFileW(fn
.c_str(), FILE_TRAVERSE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, nullptr,
358 OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OPEN_REPARSE_POINT
, nullptr);
360 if (h
!= INVALID_HANDLE_VALUE
) {
362 IO_STATUS_BLOCK iosb
;
364 Status
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_START_SCRUB
, nullptr, 0, nullptr, 0);
366 if (Status
== STATUS_DEVICE_NOT_READY
) {
367 btrfs_query_balance bqb
;
370 Status2
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_QUERY_BALANCE
, nullptr, 0, &bqb
, sizeof(btrfs_query_balance
));
372 if (NT_SUCCESS(Status2
) && bqb
.status
& (BTRFS_BALANCE_RUNNING
| BTRFS_BALANCE_PAUSED
))
373 throw string_error(IDS_SCRUB_BALANCE_RUNNING
);
376 if (!NT_SUCCESS(Status
))
377 throw ntstatus_error(Status
);
379 RefreshScrubDlg(hwndDlg
, true);
381 throw last_error(GetLastError());
384 void BtrfsScrub::PauseScrub(HWND hwndDlg
) {
385 btrfs_query_scrub bqs
;
387 win_handle h
= CreateFileW(fn
.c_str(), FILE_TRAVERSE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, nullptr,
388 OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OPEN_REPARSE_POINT
, nullptr);
390 if (h
!= INVALID_HANDLE_VALUE
) {
392 IO_STATUS_BLOCK iosb
;
394 Status
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_QUERY_SCRUB
, nullptr, 0, &bqs
, sizeof(btrfs_query_scrub
));
396 if (!NT_SUCCESS(Status
) && Status
!= STATUS_BUFFER_OVERFLOW
)
397 throw ntstatus_error(Status
);
399 if (bqs
.status
== BTRFS_SCRUB_PAUSED
)
400 Status
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_RESUME_SCRUB
, nullptr, 0, nullptr, 0);
402 Status
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_PAUSE_SCRUB
, nullptr, 0, nullptr, 0);
404 if (!NT_SUCCESS(Status
))
405 throw ntstatus_error(Status
);
407 throw last_error(GetLastError());
410 void BtrfsScrub::StopScrub(HWND hwndDlg
) {
411 win_handle h
= CreateFileW(fn
.c_str(), FILE_TRAVERSE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, nullptr,
412 OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OPEN_REPARSE_POINT
, nullptr);
414 if (h
!= INVALID_HANDLE_VALUE
) {
416 IO_STATUS_BLOCK iosb
;
418 Status
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_STOP_SCRUB
, nullptr, 0, nullptr, 0);
420 if (!NT_SUCCESS(Status
))
421 throw ntstatus_error(Status
);
423 throw last_error(GetLastError());
426 INT_PTR CALLBACK
BtrfsScrub::ScrubDlgProc(HWND hwndDlg
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
) {
430 RefreshScrubDlg(hwndDlg
, true);
431 SetTimer(hwndDlg
, 1, 1000, nullptr);
435 switch (HIWORD(wParam
)) {
437 switch (LOWORD(wParam
)) {
440 EndDialog(hwndDlg
, 0);
443 case IDC_START_SCRUB
:
447 case IDC_PAUSE_SCRUB
:
451 case IDC_CANCEL_SCRUB
:
460 RefreshScrubDlg(hwndDlg
, false);
463 } catch (const exception
& e
) {
464 error_message(hwndDlg
, e
.what());
470 static INT_PTR CALLBACK
stub_ScrubDlgProc(HWND hwndDlg
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
) {
473 if (uMsg
== WM_INITDIALOG
) {
474 SetWindowLongPtr(hwndDlg
, GWLP_USERDATA
, (LONG_PTR
)lParam
);
475 bs
= (BtrfsScrub
*)lParam
;
477 bs
= (BtrfsScrub
*)GetWindowLongPtr(hwndDlg
, GWLP_USERDATA
);
481 return bs
->ScrubDlgProc(hwndDlg
, uMsg
, wParam
, lParam
);
490 void CALLBACK
ShowScrubW(HWND hwnd
, HINSTANCE hinst
, LPWSTR lpszCmdLine
, int nCmdShow
) {
496 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES
| TOKEN_QUERY
, &token
))
497 throw last_error(GetLastError());
499 if (!LookupPrivilegeValueW(nullptr, L
"SeManageVolumePrivilege", &luid
))
500 throw last_error(GetLastError());
502 tp
.PrivilegeCount
= 1;
503 tp
.Privileges
[0].Luid
= luid
;
504 tp
.Privileges
[0].Attributes
= SE_PRIVILEGE_ENABLED
;
506 if (!AdjustTokenPrivileges(token
, false, &tp
, sizeof(TOKEN_PRIVILEGES
), nullptr, nullptr))
507 throw last_error(GetLastError());
511 BtrfsScrub
scrub(lpszCmdLine
);
513 DialogBoxParamW(module
, MAKEINTRESOURCEW(IDD_SCRUB
), hwnd
, stub_ScrubDlgProc
, (LPARAM
)&scrub
);
514 } catch (const exception
& e
) {
515 error_message(hwnd
, e
.what());
519 void CALLBACK
StartScrubW(HWND hwnd
, HINSTANCE hinst
, LPWSTR lpszCmdLine
, int nCmdShow
) {
520 vector
<wstring
> args
;
522 command_line_to_args(lpszCmdLine
, args
);
524 if (args
.size() >= 1) {
531 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES
| TOKEN_QUERY
, &token
))
534 if (!LookupPrivilegeValueW(nullptr, L
"SeManageVolumePrivilege", &luid
))
537 tp
.PrivilegeCount
= 1;
538 tp
.Privileges
[0].Luid
= luid
;
539 tp
.Privileges
[0].Attributes
= SE_PRIVILEGE_ENABLED
;
541 if (!AdjustTokenPrivileges(token
, false, &tp
, sizeof(TOKEN_PRIVILEGES
), nullptr, nullptr))
545 win_handle h
= CreateFileW(args
[0].c_str(), FILE_TRAVERSE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, nullptr,
546 OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OPEN_REPARSE_POINT
, nullptr);
547 if (h
!= INVALID_HANDLE_VALUE
) {
548 IO_STATUS_BLOCK iosb
;
550 NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_START_SCRUB
, nullptr, 0, nullptr, 0);
555 void CALLBACK
StopScrubW(HWND hwnd
, HINSTANCE hinst
, LPWSTR lpszCmdLine
, int nCmdShow
) {
556 vector
<wstring
> args
;
558 command_line_to_args(lpszCmdLine
, args
);
560 if (args
.size() >= 1) {
567 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES
| TOKEN_QUERY
, &token
))
570 if (!LookupPrivilegeValueW(nullptr, L
"SeManageVolumePrivilege", &luid
))
573 tp
.PrivilegeCount
= 1;
574 tp
.Privileges
[0].Luid
= luid
;
575 tp
.Privileges
[0].Attributes
= SE_PRIVILEGE_ENABLED
;
577 if (!AdjustTokenPrivileges(token
, false, &tp
, sizeof(TOKEN_PRIVILEGES
), nullptr, nullptr))
581 win_handle h
= CreateFileW(args
[0].c_str(), FILE_TRAVERSE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, nullptr,
582 OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OPEN_REPARSE_POINT
, nullptr);
583 if (h
!= INVALID_HANDLE_VALUE
) {
584 IO_STATUS_BLOCK iosb
;
586 NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_STOP_SCRUB
, nullptr, 0, nullptr, 0);