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 #include "shellext.h" 19 #ifndef __REACTOS__ 20 #include <windows.h> 21 #include <strsafe.h> 22 #include <stddef.h> 23 #include <winternl.h> 24 #else 25 #define WIN32_NO_STATUS 26 #include <windef.h> 27 #include <winbase.h> 28 #include <strsafe.h> 29 #include <shellapi.h> 30 #include <winioctl.h> 31 #include <ndk/iofuncs.h> 32 #undef DeleteFile 33 #endif 34 #include <wincodec.h> 35 #include <sstream> 36 #include <iostream> 37 38 #define NO_SHLWAPI_STRFCNS 39 #include <shlwapi.h> 40 41 #include "contextmenu.h" 42 #include "resource.h" 43 #ifndef __REACTOS__ 44 #include "../btrfsioctl.h" 45 #else 46 #include "btrfsioctl.h" 47 #endif 48 49 #define NEW_SUBVOL_VERBA "newsubvol" 50 #define NEW_SUBVOL_VERBW L"newsubvol" 51 #define SNAPSHOT_VERBA "snapshot" 52 #define SNAPSHOT_VERBW L"snapshot" 53 #define REFLINK_VERBA "reflink" 54 #define REFLINK_VERBW L"reflink" 55 #define RECV_VERBA "recvsubvol" 56 #define RECV_VERBW L"recvsubvol" 57 #define SEND_VERBA "sendsubvol" 58 #define SEND_VERBW L"sendsubvol" 59 60 typedef struct { 61 ULONG ReparseTag; 62 USHORT ReparseDataLength; 63 USHORT Reserved; 64 } reparse_header; 65 66 static void path_remove_file(wstring& path); 67 68 // FIXME - don't assume subvol's top inode is 0x100 69 70 HRESULT __stdcall BtrfsContextMenu::QueryInterface(REFIID riid, void **ppObj) { 71 if (riid == IID_IUnknown || riid == IID_IContextMenu) { 72 *ppObj = static_cast<IContextMenu*>(this); 73 AddRef(); 74 return S_OK; 75 } else if (riid == IID_IShellExtInit) { 76 *ppObj = static_cast<IShellExtInit*>(this); 77 AddRef(); 78 return S_OK; 79 } 80 81 *ppObj = nullptr; 82 return E_NOINTERFACE; 83 } 84 85 HRESULT __stdcall BtrfsContextMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) { 86 IO_STATUS_BLOCK iosb; 87 btrfs_get_file_ids bgfi; 88 NTSTATUS Status; 89 90 if (!pidlFolder) { 91 FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; 92 UINT num_files, i; 93 WCHAR fn[MAX_PATH]; 94 HDROP hdrop; 95 96 if (!pdtobj) 97 return E_FAIL; 98 99 stgm.tymed = TYMED_HGLOBAL; 100 101 if (FAILED(pdtobj->GetData(&format, &stgm))) 102 return E_INVALIDARG; 103 104 stgm_set = true; 105 106 hdrop = (HDROP)GlobalLock(stgm.hGlobal); 107 108 if (!hdrop) { 109 ReleaseStgMedium(&stgm); 110 stgm_set = false; 111 return E_INVALIDARG; 112 } 113 114 num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0); 115 116 for (i = 0; i < num_files; i++) { 117 if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) { 118 win_handle h = CreateFileW(fn, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 119 120 if (h != INVALID_HANDLE_VALUE) { 121 Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids)); 122 123 if (NT_SUCCESS(Status) && bgfi.inode == 0x100 && !bgfi.top) { 124 wstring parpath; 125 126 { 127 win_handle h2; 128 129 parpath = fn; 130 path_remove_file(parpath); 131 132 h2 = CreateFileW(parpath.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, 133 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 134 135 if (h2 != INVALID_HANDLE_VALUE) 136 allow_snapshot = true; 137 } 138 139 ignore = false; 140 bg = false; 141 142 GlobalUnlock(hdrop); 143 return S_OK; 144 } 145 } 146 } 147 } 148 149 GlobalUnlock(hdrop); 150 151 return S_OK; 152 } 153 154 { 155 WCHAR pathbuf[MAX_PATH]; 156 157 if (!SHGetPathFromIDListW(pidlFolder, pathbuf)) 158 return E_FAIL; 159 160 path = pathbuf; 161 } 162 163 { 164 // check we have permissions to create new subdirectory 165 166 win_handle h = CreateFileW(path.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 167 168 if (h == INVALID_HANDLE_VALUE) 169 return E_FAIL; 170 171 // check is Btrfs volume 172 173 Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids)); 174 175 if (!NT_SUCCESS(Status)) 176 return E_FAIL; 177 } 178 179 ignore = false; 180 bg = true; 181 182 return S_OK; 183 } 184 185 static bool get_volume_path_parent(const WCHAR* fn, WCHAR* volpath, ULONG volpathlen) { 186 WCHAR *f, *p; 187 bool b; 188 189 f = PathFindFileNameW(fn); 190 191 if (f == fn) 192 return GetVolumePathNameW(fn, volpath, volpathlen); 193 194 p = (WCHAR*)malloc((f - fn + 1) * sizeof(WCHAR)); 195 memcpy(p, fn, (f - fn) * sizeof(WCHAR)); 196 p[f - fn] = 0; 197 198 b = GetVolumePathNameW(p, volpath, volpathlen); 199 200 free(p); 201 202 return b; 203 } 204 205 static bool show_reflink_paste(const wstring& path) { 206 HDROP hdrop; 207 HANDLE lh; 208 ULONG num_files; 209 WCHAR fn[MAX_PATH], volpath1[255], volpath2[255]; 210 211 if (!IsClipboardFormatAvailable(CF_HDROP)) 212 return false; 213 214 if (!GetVolumePathNameW(path.c_str(), volpath1, sizeof(volpath1) / sizeof(WCHAR))) 215 return false; 216 217 if (!OpenClipboard(nullptr)) 218 return false; 219 220 hdrop = (HDROP)GetClipboardData(CF_HDROP); 221 222 if (!hdrop) { 223 CloseClipboard(); 224 return false; 225 } 226 227 lh = GlobalLock(hdrop); 228 229 if (!lh) { 230 CloseClipboard(); 231 return false; 232 } 233 234 num_files = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0); 235 236 if (num_files == 0) { 237 GlobalUnlock(lh); 238 CloseClipboard(); 239 return false; 240 } 241 242 if (!DragQueryFileW(hdrop, 0, fn, sizeof(fn) / sizeof(WCHAR))) { 243 GlobalUnlock(lh); 244 CloseClipboard(); 245 return false; 246 } 247 248 if (!get_volume_path_parent(fn, volpath2, sizeof(volpath2) / sizeof(WCHAR))) { 249 GlobalUnlock(lh); 250 CloseClipboard(); 251 return false; 252 } 253 254 GlobalUnlock(lh); 255 256 CloseClipboard(); 257 258 return !wcscmp(volpath1, volpath2); 259 } 260 261 // The code for putting an icon against a menu item comes from: 262 // http://web.archive.org/web/20070208005514/http://shellrevealed.com/blogs/shellblog/archive/2007/02/06/Vista-Style-Menus_2C00_-Part-1-_2D00_-Adding-icons-to-standard-menus.aspx 263 264 static void InitBitmapInfo(BITMAPINFO* pbmi, ULONG cbInfo, LONG cx, LONG cy, WORD bpp) { 265 ZeroMemory(pbmi, cbInfo); 266 pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 267 pbmi->bmiHeader.biPlanes = 1; 268 pbmi->bmiHeader.biCompression = BI_RGB; 269 270 pbmi->bmiHeader.biWidth = cx; 271 pbmi->bmiHeader.biHeight = cy; 272 pbmi->bmiHeader.biBitCount = bpp; 273 } 274 275 static HRESULT Create32BitHBITMAP(HDC hdc, const SIZE *psize, void **ppvBits, HBITMAP* phBmp) { 276 BITMAPINFO bmi; 277 HDC hdcUsed; 278 279 *phBmp = nullptr; 280 281 InitBitmapInfo(&bmi, sizeof(bmi), psize->cx, psize->cy, 32); 282 283 hdcUsed = hdc ? hdc : GetDC(nullptr); 284 285 if (hdcUsed) { 286 *phBmp = CreateDIBSection(hdcUsed, &bmi, DIB_RGB_COLORS, ppvBits, nullptr, 0); 287 if (hdc != hdcUsed) 288 ReleaseDC(nullptr, hdcUsed); 289 } 290 291 return !*phBmp ? E_OUTOFMEMORY : S_OK; 292 } 293 294 void BtrfsContextMenu::get_uac_icon() { 295 IWICImagingFactory* factory = nullptr; 296 IWICBitmap* bitmap; 297 HRESULT hr; 298 299 #ifdef __REACTOS__ 300 hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, (void **)&factory); 301 #else 302 hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)); 303 #endif 304 305 if (SUCCEEDED(hr)) { 306 HANDLE icon; 307 308 // We can't use IDI_SHIELD, as that will only give us the full-size icon 309 icon = LoadImageW(GetModuleHandleW(L"user32.dll"), MAKEINTRESOURCEW(106)/* UAC shield */, IMAGE_ICON, 310 GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR); 311 312 hr = factory->CreateBitmapFromHICON((HICON)icon, &bitmap); 313 if (SUCCEEDED(hr)) { 314 UINT cx, cy; 315 316 hr = bitmap->GetSize(&cx, &cy); 317 if (SUCCEEDED(hr)) { 318 SIZE sz; 319 BYTE* buf; 320 321 sz.cx = (int)cx; 322 sz.cy = -(int)cy; 323 324 hr = Create32BitHBITMAP(nullptr, &sz, (void**)&buf, &uacicon); 325 if (SUCCEEDED(hr)) { 326 UINT stride = (UINT)(cx * sizeof(DWORD)); 327 UINT buflen = cy * stride; 328 bitmap->CopyPixels(nullptr, stride, buflen, buf); 329 } 330 } 331 332 bitmap->Release(); 333 } 334 335 factory->Release(); 336 } 337 } 338 339 HRESULT __stdcall BtrfsContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { 340 wstring str; 341 ULONG entries = 0; 342 343 if (ignore) 344 return E_INVALIDARG; 345 346 if (uFlags & CMF_DEFAULTONLY) 347 return S_OK; 348 349 if (!bg) { 350 if (allow_snapshot) { 351 if (load_string(module, IDS_CREATE_SNAPSHOT, str) == 0) 352 return E_FAIL; 353 354 if (!InsertMenuW(hmenu, indexMenu, MF_BYPOSITION, idCmdFirst, str.c_str())) 355 return E_FAIL; 356 357 entries = 1; 358 } 359 360 if (idCmdFirst + entries <= idCmdLast) { 361 MENUITEMINFOW mii; 362 363 if (load_string(module, IDS_SEND_SUBVOL, str) == 0) 364 return E_FAIL; 365 366 if (!uacicon) 367 get_uac_icon(); 368 369 memset(&mii, 0, sizeof(MENUITEMINFOW)); 370 mii.cbSize = sizeof(MENUITEMINFOW); 371 mii.fMask = MIIM_STRING | MIIM_ID | MIIM_BITMAP; 372 mii.dwTypeData = (WCHAR*)str.c_str(); 373 mii.wID = idCmdFirst + entries; 374 mii.hbmpItem = uacicon; 375 376 if (!InsertMenuItemW(hmenu, indexMenu + entries, true, &mii)) 377 return E_FAIL; 378 379 entries++; 380 } 381 } else { 382 if (load_string(module, IDS_NEW_SUBVOL, str) == 0) 383 return E_FAIL; 384 385 if (!InsertMenuW(hmenu, indexMenu, MF_BYPOSITION, idCmdFirst, str.c_str())) 386 return E_FAIL; 387 388 entries = 1; 389 390 if (idCmdFirst + 1 <= idCmdLast) { 391 MENUITEMINFOW mii; 392 393 if (load_string(module, IDS_RECV_SUBVOL, str) == 0) 394 return E_FAIL; 395 396 if (!uacicon) 397 get_uac_icon(); 398 399 memset(&mii, 0, sizeof(MENUITEMINFOW)); 400 mii.cbSize = sizeof(MENUITEMINFOW); 401 mii.fMask = MIIM_STRING | MIIM_ID | MIIM_BITMAP; 402 mii.dwTypeData = (WCHAR*)str.c_str(); 403 mii.wID = idCmdFirst + 1; 404 mii.hbmpItem = uacicon; 405 406 if (!InsertMenuItemW(hmenu, indexMenu + 1, true, &mii)) 407 return E_FAIL; 408 409 entries++; 410 } 411 412 if (idCmdFirst + 2 <= idCmdLast && show_reflink_paste(path)) { 413 if (load_string(module, IDS_REFLINK_PASTE, str) == 0) 414 return E_FAIL; 415 416 if (!InsertMenuW(hmenu, indexMenu + 2, MF_BYPOSITION, idCmdFirst + 2, str.c_str())) 417 return E_FAIL; 418 419 entries++; 420 } 421 } 422 423 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, entries); 424 } 425 426 static void path_remove_file(wstring& path) { 427 size_t bs = path.rfind(L"\\"); 428 429 if (bs == string::npos) 430 return; 431 432 if (bs == path.find(L"\\")) { // only one backslash 433 path = path.substr(0, bs + 1); 434 return; 435 } 436 437 path = path.substr(0, bs); 438 } 439 440 static void path_strip_path(wstring& path) { 441 size_t bs = path.rfind(L"\\"); 442 443 if (bs == string::npos) { 444 path = L""; 445 return; 446 } 447 448 path = path.substr(bs + 1); 449 } 450 451 static void create_snapshot(HWND hwnd, const wstring& fn) { 452 win_handle h; 453 NTSTATUS Status; 454 IO_STATUS_BLOCK iosb; 455 btrfs_get_file_ids bgfi; 456 457 h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 458 459 if (h != INVALID_HANDLE_VALUE) { 460 Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids)); 461 462 if (NT_SUCCESS(Status) && bgfi.inode == 0x100 && !bgfi.top) { 463 wstring subvolname, parpath, searchpath, temp1, name, nameorig; 464 win_handle h2; 465 WIN32_FIND_DATAW wfd; 466 SYSTEMTIME time; 467 468 parpath = fn; 469 path_remove_file(parpath); 470 471 subvolname = fn; 472 path_strip_path(subvolname); 473 474 h2 = CreateFileW(parpath.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 475 476 if (h2 == INVALID_HANDLE_VALUE) 477 throw last_error(GetLastError()); 478 479 if (!load_string(module, IDS_SNAPSHOT_FILENAME, temp1)) 480 throw last_error(GetLastError()); 481 482 GetLocalTime(&time); 483 484 wstring_sprintf(name, temp1, subvolname.c_str(), time.wYear, time.wMonth, time.wDay); 485 nameorig = name; 486 487 searchpath = parpath + L"\\" + name; 488 489 fff_handle fff = FindFirstFileW(searchpath.c_str(), &wfd); 490 491 if (fff != INVALID_HANDLE_VALUE) { 492 ULONG num = 2; 493 494 do { 495 #ifndef __REACTOS__ 496 name = nameorig + L" (" + to_wstring(num) + L")"; 497 #else 498 { 499 WCHAR buffer[32]; 500 501 swprintf(buffer, L"%d", num); 502 name = nameorig + L" (" + buffer + L")"; 503 } 504 #endif 505 searchpath = parpath + L"\\" + name; 506 507 fff = FindFirstFileW(searchpath.c_str(), &wfd); 508 num++; 509 } while (fff != INVALID_HANDLE_VALUE); 510 } 511 512 size_t namelen = name.length() * sizeof(WCHAR); 513 514 auto bcs = (btrfs_create_snapshot*)malloc(sizeof(btrfs_create_snapshot) - 1 + namelen); 515 bcs->readonly = false; 516 bcs->posix = false; 517 bcs->subvol = h; 518 bcs->namelen = (uint16_t)namelen; 519 memcpy(bcs->name, name.c_str(), namelen); 520 521 Status = NtFsControlFile(h2, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, 522 (ULONG)(sizeof(btrfs_create_snapshot) - 1 + namelen), nullptr, 0); 523 524 if (!NT_SUCCESS(Status)) 525 throw ntstatus_error(Status); 526 } 527 } else 528 throw last_error(GetLastError()); 529 } 530 531 static uint64_t __inline sector_align(uint64_t n, uint64_t a) { 532 if (n & (a - 1)) 533 n = (n + a) & ~(a - 1); 534 535 return n; 536 } 537 538 void BtrfsContextMenu::reflink_copy(HWND hwnd, const WCHAR* fn, const WCHAR* dir) { 539 win_handle source, dest; 540 WCHAR* name, volpath1[255], volpath2[255]; 541 wstring dirw, newpath; 542 FILE_BASIC_INFO fbi; 543 FILETIME atime, mtime; 544 btrfs_inode_info bii; 545 btrfs_set_inode_info bsii; 546 ULONG bytesret; 547 NTSTATUS Status; 548 IO_STATUS_BLOCK iosb; 549 btrfs_set_xattr bsxa; 550 551 // Thanks to 0xbadfca11, whose version of reflink for Windows provided a few pointers on what 552 // to do here - https://github.com/0xbadfca11/reflink 553 554 name = PathFindFileNameW(fn); 555 556 dirw = dir; 557 558 if (dir[0] != 0 && dir[wcslen(dir) - 1] != '\\') 559 dirw += L"\\"; 560 561 newpath = dirw; 562 newpath += name; 563 564 if (!get_volume_path_parent(fn, volpath1, sizeof(volpath1) / sizeof(WCHAR))) 565 throw last_error(GetLastError()); 566 567 if (!GetVolumePathNameW(dir, volpath2, sizeof(volpath2) / sizeof(WCHAR))) 568 throw last_error(GetLastError()); 569 570 if (wcscmp(volpath1, volpath2)) // different filesystems 571 throw string_error(IDS_CANT_REFLINK_DIFFERENT_FS); 572 573 source = CreateFileW(fn, GENERIC_READ | FILE_TRAVERSE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr); 574 if (source == INVALID_HANDLE_VALUE) 575 throw last_error(GetLastError()); 576 577 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii, sizeof(btrfs_inode_info)); 578 if (!NT_SUCCESS(Status)) 579 throw ntstatus_error(Status); 580 581 // if subvol, do snapshot instead 582 if (bii.inode == SUBVOL_ROOT_INODE) { 583 btrfs_create_snapshot* bcs; 584 win_handle dirh; 585 wstring destname, search; 586 WIN32_FIND_DATAW wfd; 587 int num = 2; 588 589 dirh = CreateFileW(dir, FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 590 if (dirh == INVALID_HANDLE_VALUE) 591 throw last_error(GetLastError()); 592 593 search = dirw; 594 search += name; 595 destname = name; 596 597 fff_handle fff = FindFirstFileW(search.c_str(), &wfd); 598 599 if (fff != INVALID_HANDLE_VALUE) { 600 do { 601 wstringstream ss; 602 603 ss << name; 604 ss << L" ("; 605 ss << num; 606 ss << L")"; 607 destname = ss.str(); 608 609 search = dirw + destname; 610 611 fff = FindFirstFileW(search.c_str(), &wfd); 612 num++; 613 } while (fff != INVALID_HANDLE_VALUE); 614 } 615 616 bcs = (btrfs_create_snapshot*)malloc(sizeof(btrfs_create_snapshot) - sizeof(WCHAR) + (destname.length() * sizeof(WCHAR))); 617 bcs->subvol = source; 618 bcs->namelen = (uint16_t)(destname.length() * sizeof(WCHAR)); 619 memcpy(bcs->name, destname.c_str(), destname.length() * sizeof(WCHAR)); 620 621 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, 622 (ULONG)(sizeof(btrfs_create_snapshot) - sizeof(WCHAR) + bcs->namelen), nullptr, 0); 623 624 free(bcs); 625 626 if (!NT_SUCCESS(Status)) 627 throw ntstatus_error(Status); 628 629 return; 630 } 631 632 Status = NtQueryInformationFile(source, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation); 633 if (!NT_SUCCESS(Status)) 634 throw ntstatus_error(Status); 635 636 if (bii.type == BTRFS_TYPE_CHARDEV || bii.type == BTRFS_TYPE_BLOCKDEV || bii.type == BTRFS_TYPE_FIFO || bii.type == BTRFS_TYPE_SOCKET) { 637 win_handle dirh; 638 btrfs_mknod* bmn; 639 640 dirh = CreateFileW(dir, FILE_ADD_FILE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 641 if (dirh == INVALID_HANDLE_VALUE) 642 throw last_error(GetLastError()); 643 644 size_t bmnsize = offsetof(btrfs_mknod, name[0]) + (wcslen(name) * sizeof(WCHAR)); 645 bmn = (btrfs_mknod*)malloc(bmnsize); 646 647 bmn->inode = 0; 648 bmn->type = bii.type; 649 bmn->st_rdev = bii.st_rdev; 650 bmn->namelen = (uint16_t)(wcslen(name) * sizeof(WCHAR)); 651 memcpy(bmn->name, name, bmn->namelen); 652 653 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_MKNOD, bmn, (ULONG)bmnsize, nullptr, 0); 654 if (!NT_SUCCESS(Status)) { 655 free(bmn); 656 throw ntstatus_error(Status); 657 } 658 659 free(bmn); 660 661 dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, 0, nullptr); 662 } else if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 663 if (CreateDirectoryExW(fn, newpath.c_str(), nullptr)) 664 dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 665 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 666 else 667 dest = INVALID_HANDLE_VALUE; 668 } else 669 dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source); 670 671 if (dest == INVALID_HANDLE_VALUE) { 672 int num = 2; 673 674 if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_ALREADY_EXISTS && wcscmp(fn, newpath.c_str())) 675 throw last_error(GetLastError()); 676 677 do { 678 WCHAR* ext; 679 wstringstream ss; 680 681 ext = PathFindExtensionW(fn); 682 683 ss << dirw; 684 685 if (*ext == 0) { 686 ss << name; 687 ss << L" ("; 688 ss << num; 689 ss << L")"; 690 } else { 691 wstring namew = name; 692 693 ss << namew.substr(0, ext - name); 694 ss << L" ("; 695 ss << num; 696 ss << L")"; 697 ss << ext; 698 } 699 700 newpath = ss.str(); 701 if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 702 if (CreateDirectoryExW(fn, newpath.c_str(), nullptr)) 703 dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 704 else 705 dest = INVALID_HANDLE_VALUE; 706 } else 707 dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source); 708 709 if (dest == INVALID_HANDLE_VALUE) { 710 if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_ALREADY_EXISTS) 711 throw last_error(GetLastError()); 712 713 num++; 714 } else 715 break; 716 } while (true); 717 } 718 719 try { 720 memset(&bsii, 0, sizeof(btrfs_set_inode_info)); 721 722 bsii.flags_changed = true; 723 bsii.flags = bii.flags; 724 725 if (bii.flags & BTRFS_INODE_COMPRESS) { 726 bsii.compression_type_changed = true; 727 bsii.compression_type = bii.compression_type; 728 } 729 730 Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0); 731 if (!NT_SUCCESS(Status)) 732 throw ntstatus_error(Status); 733 734 if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 735 if (!(fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { 736 fff_handle h; 737 WIN32_FIND_DATAW fff; 738 wstring qs; 739 740 qs = fn; 741 qs += L"\\*"; 742 743 h = FindFirstFileW(qs.c_str(), &fff); 744 if (h != INVALID_HANDLE_VALUE) { 745 do { 746 wstring fn2; 747 748 if (fff.cFileName[0] == '.' && (fff.cFileName[1] == 0 || (fff.cFileName[1] == '.' && fff.cFileName[2] == 0))) 749 continue; 750 751 fn2 = fn; 752 fn2 += L"\\"; 753 fn2 += fff.cFileName; 754 755 reflink_copy(hwnd, fn2.c_str(), newpath.c_str()); 756 } while (FindNextFileW(h, &fff)); 757 } 758 } 759 760 // CreateDirectoryExW also copies streams, no need to do it here 761 } else { 762 if (fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { 763 reparse_header rh; 764 uint8_t* rp; 765 766 if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, &rh, sizeof(reparse_header), &bytesret, nullptr)) { 767 if (GetLastError() != ERROR_MORE_DATA) 768 throw last_error(GetLastError()); 769 } 770 771 size_t rplen = sizeof(reparse_header) + rh.ReparseDataLength; 772 rp = (uint8_t*)malloc(rplen); 773 774 if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, rp, (ULONG)rplen, &bytesret, nullptr)) 775 throw last_error(GetLastError()); 776 777 if (!DeviceIoControl(dest, FSCTL_SET_REPARSE_POINT, rp, (ULONG)rplen, nullptr, 0, &bytesret, nullptr)) 778 throw last_error(GetLastError()); 779 780 free(rp); 781 } else { 782 FILE_STANDARD_INFO fsi; 783 FILE_END_OF_FILE_INFO feofi; 784 FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib; 785 FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib; 786 DUPLICATE_EXTENTS_DATA ded; 787 uint64_t offset, alloc_size; 788 ULONG maxdup; 789 790 Status = NtQueryInformationFile(source, &iosb, &fsi, sizeof(FILE_STANDARD_INFO), FileStandardInformation); 791 if (!NT_SUCCESS(Status)) 792 throw ntstatus_error(Status); 793 794 if (!DeviceIoControl(source, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &fgiib, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER), &bytesret, nullptr)) 795 throw last_error(GetLastError()); 796 797 if (fbi.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) { 798 if (!DeviceIoControl(dest, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesret, nullptr)) 799 throw last_error(GetLastError()); 800 } 801 802 fsiib.ChecksumAlgorithm = fgiib.ChecksumAlgorithm; 803 fsiib.Reserved = 0; 804 fsiib.Flags = fgiib.Flags; 805 if (!DeviceIoControl(dest, FSCTL_SET_INTEGRITY_INFORMATION, &fsiib, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER), nullptr, 0, &bytesret, nullptr)) 806 throw last_error(GetLastError()); 807 808 feofi.EndOfFile = fsi.EndOfFile; 809 Status = NtSetInformationFile(dest, &iosb, &feofi, sizeof(FILE_END_OF_FILE_INFO), FileEndOfFileInformation); 810 if (!NT_SUCCESS(Status)) 811 throw ntstatus_error(Status); 812 813 ded.FileHandle = source; 814 maxdup = 0xffffffff - fgiib.ClusterSizeInBytes + 1; 815 816 alloc_size = sector_align(fsi.EndOfFile.QuadPart, fgiib.ClusterSizeInBytes); 817 818 offset = 0; 819 while (offset < alloc_size) { 820 ded.SourceFileOffset.QuadPart = ded.TargetFileOffset.QuadPart = offset; 821 ded.ByteCount.QuadPart = maxdup < (alloc_size - offset) ? maxdup : (alloc_size - offset); 822 if (!DeviceIoControl(dest, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA), nullptr, 0, &bytesret, nullptr)) 823 throw last_error(GetLastError()); 824 825 offset += ded.ByteCount.QuadPart; 826 } 827 } 828 829 ULONG streambufsize = 0; 830 vector<char> streambuf; 831 832 do { 833 streambufsize += 0x1000; 834 streambuf.resize(streambufsize); 835 836 memset(streambuf.data(), 0, streambufsize); 837 838 Status = NtQueryInformationFile(source, &iosb, streambuf.data(), streambufsize, FileStreamInformation); 839 } while (Status == STATUS_BUFFER_OVERFLOW); 840 841 if (!NT_SUCCESS(Status)) 842 throw ntstatus_error(Status); 843 844 auto fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(streambuf.data()); 845 846 while (true) { 847 if (fsi->StreamNameLength > 0) { 848 wstring sn = wstring(fsi->StreamName, fsi->StreamNameLength / sizeof(WCHAR)); 849 850 if (sn != L"::$DATA" && sn.length() > 6 && sn.substr(sn.length() - 6, 6) == L":$DATA") { 851 win_handle stream; 852 uint8_t* data = nullptr; 853 auto stream_size = (uint16_t)fsi->StreamSize.QuadPart; 854 855 856 if (stream_size > 0) { 857 wstring fn2; 858 859 fn2 = fn; 860 fn2 += sn; 861 862 stream = CreateFileW(fn2.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr); 863 864 if (stream == INVALID_HANDLE_VALUE) 865 throw last_error(GetLastError()); 866 867 // We can get away with this because our streams are guaranteed to be below 64 KB - 868 // don't do this on NTFS! 869 data = (uint8_t*)malloc(stream_size); 870 871 if (!ReadFile(stream, data, stream_size, &bytesret, nullptr)) { 872 free(data); 873 throw last_error(GetLastError()); 874 } 875 } 876 877 stream = CreateFileW((newpath + sn).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, nullptr); 878 879 if (stream == INVALID_HANDLE_VALUE) { 880 if (data) free(data); 881 throw last_error(GetLastError()); 882 } 883 884 if (data) { 885 if (!WriteFile(stream, data, stream_size, &bytesret, nullptr)) { 886 free(data); 887 throw last_error(GetLastError()); 888 } 889 890 free(data); 891 } 892 } 893 } 894 895 if (fsi->NextEntryOffset == 0) 896 break; 897 898 fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(reinterpret_cast<char*>(fsi) + fsi->NextEntryOffset); 899 } 900 } 901 902 atime.dwLowDateTime = fbi.LastAccessTime.LowPart; 903 atime.dwHighDateTime = fbi.LastAccessTime.HighPart; 904 mtime.dwLowDateTime = fbi.LastWriteTime.LowPart; 905 mtime.dwHighDateTime = fbi.LastWriteTime.HighPart; 906 SetFileTime(dest, nullptr, &atime, &mtime); 907 908 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, &bsxa, sizeof(btrfs_set_xattr)); 909 910 if (Status == STATUS_BUFFER_OVERFLOW || (NT_SUCCESS(Status) && bsxa.valuelen > 0)) { 911 ULONG xalen = 0; 912 btrfs_set_xattr *xa = nullptr, *xa2; 913 914 do { 915 xalen += 1024; 916 917 if (xa) free(xa); 918 xa = (btrfs_set_xattr*)malloc(xalen); 919 920 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, xa, xalen); 921 } while (Status == STATUS_BUFFER_OVERFLOW); 922 923 if (!NT_SUCCESS(Status)) { 924 free(xa); 925 throw ntstatus_error(Status); 926 } 927 928 xa2 = xa; 929 while (xa2->valuelen > 0) { 930 Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, xa2, 931 (ULONG)(offsetof(btrfs_set_xattr, data[0]) + xa2->namelen + xa2->valuelen), nullptr, 0); 932 if (!NT_SUCCESS(Status)) { 933 free(xa); 934 throw ntstatus_error(Status); 935 } 936 xa2 = (btrfs_set_xattr*)&xa2->data[xa2->namelen + xa2->valuelen]; 937 } 938 939 free(xa); 940 } else if (!NT_SUCCESS(Status)) 941 throw ntstatus_error(Status); 942 } catch (...) { 943 FILE_DISPOSITION_INFO fdi; 944 945 fdi.DeleteFile = true; 946 Status = NtSetInformationFile(dest, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation); 947 if (!NT_SUCCESS(Status)) 948 throw ntstatus_error(Status); 949 950 throw; 951 } 952 } 953 954 HRESULT __stdcall BtrfsContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO picia) { 955 LPCMINVOKECOMMANDINFOEX pici = (LPCMINVOKECOMMANDINFOEX)picia; 956 957 try { 958 if (ignore) 959 return E_INVALIDARG; 960 961 if (!bg) { 962 if ((IS_INTRESOURCE(pici->lpVerb) && allow_snapshot && pici->lpVerb == 0) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, SNAPSHOT_VERBA))) { 963 UINT num_files, i; 964 WCHAR fn[MAX_PATH]; 965 966 if (!stgm_set) 967 return E_FAIL; 968 969 num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0); 970 971 if (num_files == 0) 972 return E_FAIL; 973 974 for (i = 0; i < num_files; i++) { 975 if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) { 976 create_snapshot(pici->hwnd, fn); 977 } 978 } 979 980 return S_OK; 981 } else if ((IS_INTRESOURCE(pici->lpVerb) && ((allow_snapshot && (ULONG_PTR)pici->lpVerb == 1) || (!allow_snapshot && (ULONG_PTR)pici->lpVerb == 0))) || 982 (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, SEND_VERBA))) { 983 UINT num_files, i; 984 WCHAR dll[MAX_PATH], fn[MAX_PATH]; 985 wstring t; 986 SHELLEXECUTEINFOW sei; 987 988 GetModuleFileNameW(module, dll, sizeof(dll) / sizeof(WCHAR)); 989 990 if (!stgm_set) 991 return E_FAIL; 992 993 num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0); 994 995 if (num_files == 0) 996 return E_FAIL; 997 998 for (i = 0; i < num_files; i++) { 999 if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) { 1000 t = L"\""; 1001 t += dll; 1002 t += L"\",SendSubvolGUI "; 1003 t += fn; 1004 1005 RtlZeroMemory(&sei, sizeof(sei)); 1006 1007 sei.cbSize = sizeof(sei); 1008 sei.hwnd = pici->hwnd; 1009 sei.lpVerb = L"runas"; 1010 sei.lpFile = L"rundll32.exe"; 1011 sei.lpParameters = t.c_str(); 1012 sei.nShow = SW_SHOW; 1013 sei.fMask = SEE_MASK_NOCLOSEPROCESS; 1014 1015 if (!ShellExecuteExW(&sei)) 1016 throw last_error(GetLastError()); 1017 1018 WaitForSingleObject(sei.hProcess, INFINITE); 1019 CloseHandle(sei.hProcess); 1020 } 1021 } 1022 1023 return S_OK; 1024 } 1025 } else { 1026 if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 0) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, NEW_SUBVOL_VERBA))) { 1027 win_handle h; 1028 IO_STATUS_BLOCK iosb; 1029 NTSTATUS Status; 1030 wstring name, nameorig, searchpath; 1031 btrfs_create_subvol* bcs; 1032 WIN32_FIND_DATAW wfd; 1033 1034 if (!load_string(module, IDS_NEW_SUBVOL_FILENAME, name)) 1035 throw last_error(GetLastError()); 1036 1037 h = CreateFileW(path.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 1038 1039 if (h == INVALID_HANDLE_VALUE) 1040 throw last_error(GetLastError()); 1041 1042 searchpath = path + L"\\" + name; 1043 nameorig = name; 1044 1045 { 1046 fff_handle fff = FindFirstFileW(searchpath.c_str(), &wfd); 1047 1048 if (fff != INVALID_HANDLE_VALUE) { 1049 ULONG num = 2; 1050 1051 do { 1052 #ifndef __REACTOS__ 1053 name = nameorig + L" (" + to_wstring(num) + L")"; 1054 #else 1055 { 1056 WCHAR buffer[32]; 1057 1058 swprintf(buffer, L"%d", num); 1059 name = nameorig + L" (" + buffer + L")"; 1060 } 1061 #endif 1062 searchpath = path + L"\\" + name; 1063 1064 fff = FindFirstFileW(searchpath.c_str(), &wfd); 1065 num++; 1066 } while (fff != INVALID_HANDLE_VALUE); 1067 } 1068 } 1069 1070 size_t bcslen = offsetof(btrfs_create_subvol, name[0]) + (name.length() * sizeof(WCHAR)); 1071 bcs = (btrfs_create_subvol*)malloc(bcslen); 1072 1073 bcs->readonly = false; 1074 bcs->posix = false; 1075 bcs->namelen = (uint16_t)(name.length() * sizeof(WCHAR)); 1076 memcpy(bcs->name, name.c_str(), name.length() * sizeof(WCHAR)); 1077 1078 Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0); 1079 1080 free(bcs); 1081 1082 if (!NT_SUCCESS(Status)) 1083 throw ntstatus_error(Status); 1084 1085 return S_OK; 1086 } else if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 1) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, RECV_VERBA))) { 1087 WCHAR dll[MAX_PATH]; 1088 wstring t; 1089 SHELLEXECUTEINFOW sei; 1090 1091 GetModuleFileNameW(module, dll, sizeof(dll) / sizeof(WCHAR)); 1092 1093 t = L"\""; 1094 t += dll; 1095 t += L"\",RecvSubvolGUI "; 1096 t += path; 1097 1098 RtlZeroMemory(&sei, sizeof(sei)); 1099 1100 sei.cbSize = sizeof(sei); 1101 sei.hwnd = pici->hwnd; 1102 sei.lpVerb = L"runas"; 1103 sei.lpFile = L"rundll32.exe"; 1104 sei.lpParameters = t.c_str(); 1105 sei.nShow = SW_SHOW; 1106 sei.fMask = SEE_MASK_NOCLOSEPROCESS; 1107 1108 if (!ShellExecuteExW(&sei)) 1109 throw last_error(GetLastError()); 1110 1111 WaitForSingleObject(sei.hProcess, INFINITE); 1112 CloseHandle(sei.hProcess); 1113 1114 return S_OK; 1115 } else if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 2) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, REFLINK_VERBA))) { 1116 HDROP hdrop; 1117 1118 if (!IsClipboardFormatAvailable(CF_HDROP)) 1119 return S_OK; 1120 1121 if (!OpenClipboard(pici->hwnd)) 1122 throw last_error(GetLastError()); 1123 1124 try { 1125 hdrop = (HDROP)GetClipboardData(CF_HDROP); 1126 1127 if (hdrop) { 1128 HANDLE lh; 1129 1130 lh = GlobalLock(hdrop); 1131 1132 if (lh) { 1133 try { 1134 ULONG num_files, i; 1135 WCHAR fn[MAX_PATH]; 1136 1137 num_files = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0); 1138 1139 for (i = 0; i < num_files; i++) { 1140 if (DragQueryFileW(hdrop, i, fn, sizeof(fn) / sizeof(WCHAR))) { 1141 reflink_copy(pici->hwnd, fn, pici->lpDirectoryW); 1142 } 1143 } 1144 } catch (...) { 1145 GlobalUnlock(lh); 1146 throw; 1147 } 1148 1149 GlobalUnlock(lh); 1150 } 1151 } 1152 } catch (...) { 1153 CloseClipboard(); 1154 throw; 1155 } 1156 1157 CloseClipboard(); 1158 1159 return S_OK; 1160 } 1161 } 1162 } catch (const exception& e) { 1163 error_message(pici->hwnd, e.what()); 1164 } 1165 1166 return E_FAIL; 1167 } 1168 1169 HRESULT __stdcall BtrfsContextMenu::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax) { 1170 if (ignore) 1171 return E_INVALIDARG; 1172 1173 if (idCmd != 0) 1174 return E_INVALIDARG; 1175 1176 if (!bg) { 1177 if (idCmd == 0) { 1178 switch (uFlags) { 1179 case GCS_HELPTEXTA: 1180 if (LoadStringA(module, IDS_CREATE_SNAPSHOT_HELP_TEXT, pszName, cchMax)) 1181 return S_OK; 1182 else 1183 return E_FAIL; 1184 1185 case GCS_HELPTEXTW: 1186 if (LoadStringW(module, IDS_CREATE_SNAPSHOT_HELP_TEXT, (LPWSTR)pszName, cchMax)) 1187 return S_OK; 1188 else 1189 return E_FAIL; 1190 1191 case GCS_VALIDATEA: 1192 case GCS_VALIDATEW: 1193 return S_OK; 1194 1195 case GCS_VERBA: 1196 return StringCchCopyA(pszName, cchMax, SNAPSHOT_VERBA); 1197 1198 case GCS_VERBW: 1199 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, SNAPSHOT_VERBW); 1200 1201 default: 1202 return E_INVALIDARG; 1203 } 1204 } else if (idCmd == 1) { 1205 switch (uFlags) { 1206 case GCS_HELPTEXTA: 1207 if (LoadStringA(module, IDS_SEND_SUBVOL_HELP, pszName, cchMax)) 1208 return S_OK; 1209 else 1210 return E_FAIL; 1211 1212 case GCS_HELPTEXTW: 1213 if (LoadStringW(module, IDS_SEND_SUBVOL_HELP, (LPWSTR)pszName, cchMax)) 1214 return S_OK; 1215 else 1216 return E_FAIL; 1217 1218 case GCS_VALIDATEA: 1219 case GCS_VALIDATEW: 1220 return S_OK; 1221 1222 case GCS_VERBA: 1223 return StringCchCopyA(pszName, cchMax, SEND_VERBA); 1224 1225 case GCS_VERBW: 1226 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, SEND_VERBW); 1227 1228 default: 1229 return E_INVALIDARG; 1230 } 1231 } else 1232 return E_INVALIDARG; 1233 } else { 1234 if (idCmd == 0) { 1235 switch (uFlags) { 1236 case GCS_HELPTEXTA: 1237 if (LoadStringA(module, IDS_NEW_SUBVOL_HELP_TEXT, pszName, cchMax)) 1238 return S_OK; 1239 else 1240 return E_FAIL; 1241 1242 case GCS_HELPTEXTW: 1243 if (LoadStringW(module, IDS_NEW_SUBVOL_HELP_TEXT, (LPWSTR)pszName, cchMax)) 1244 return S_OK; 1245 else 1246 return E_FAIL; 1247 1248 case GCS_VALIDATEA: 1249 case GCS_VALIDATEW: 1250 return S_OK; 1251 1252 case GCS_VERBA: 1253 return StringCchCopyA(pszName, cchMax, NEW_SUBVOL_VERBA); 1254 1255 case GCS_VERBW: 1256 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, NEW_SUBVOL_VERBW); 1257 1258 default: 1259 return E_INVALIDARG; 1260 } 1261 } else if (idCmd == 1) { 1262 switch (uFlags) { 1263 case GCS_HELPTEXTA: 1264 if (LoadStringA(module, IDS_RECV_SUBVOL_HELP, pszName, cchMax)) 1265 return S_OK; 1266 else 1267 return E_FAIL; 1268 1269 case GCS_HELPTEXTW: 1270 if (LoadStringW(module, IDS_RECV_SUBVOL_HELP, (LPWSTR)pszName, cchMax)) 1271 return S_OK; 1272 else 1273 return E_FAIL; 1274 1275 case GCS_VALIDATEA: 1276 case GCS_VALIDATEW: 1277 return S_OK; 1278 1279 case GCS_VERBA: 1280 return StringCchCopyA(pszName, cchMax, RECV_VERBA); 1281 1282 case GCS_VERBW: 1283 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, RECV_VERBW); 1284 1285 default: 1286 return E_INVALIDARG; 1287 } 1288 } else if (idCmd == 2) { 1289 switch (uFlags) { 1290 case GCS_HELPTEXTA: 1291 if (LoadStringA(module, IDS_REFLINK_PASTE_HELP, pszName, cchMax)) 1292 return S_OK; 1293 else 1294 return E_FAIL; 1295 1296 case GCS_HELPTEXTW: 1297 if (LoadStringW(module, IDS_REFLINK_PASTE_HELP, (LPWSTR)pszName, cchMax)) 1298 return S_OK; 1299 else 1300 return E_FAIL; 1301 1302 case GCS_VALIDATEA: 1303 case GCS_VALIDATEW: 1304 return S_OK; 1305 1306 case GCS_VERBA: 1307 return StringCchCopyA(pszName, cchMax, REFLINK_VERBA); 1308 1309 case GCS_VERBW: 1310 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, REFLINK_VERBW); 1311 1312 default: 1313 return E_INVALIDARG; 1314 } 1315 } else 1316 return E_INVALIDARG; 1317 } 1318 } 1319 1320 static void reflink_copy2(const wstring& srcfn, const wstring& destdir, const wstring& destname) { 1321 win_handle source, dest; 1322 FILE_BASIC_INFO fbi; 1323 FILETIME atime, mtime; 1324 btrfs_inode_info bii; 1325 btrfs_set_inode_info bsii; 1326 ULONG bytesret; 1327 NTSTATUS Status; 1328 IO_STATUS_BLOCK iosb; 1329 btrfs_set_xattr bsxa; 1330 1331 source = CreateFileW(srcfn.c_str(), GENERIC_READ | FILE_TRAVERSE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr); 1332 if (source == INVALID_HANDLE_VALUE) 1333 throw last_error(GetLastError()); 1334 1335 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii, sizeof(btrfs_inode_info)); 1336 if (!NT_SUCCESS(Status)) 1337 throw ntstatus_error(Status); 1338 1339 // if subvol, do snapshot instead 1340 if (bii.inode == SUBVOL_ROOT_INODE) { 1341 btrfs_create_snapshot* bcs; 1342 win_handle dirh; 1343 1344 dirh = CreateFileW(destdir.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 1345 if (dirh == INVALID_HANDLE_VALUE) 1346 throw last_error(GetLastError()); 1347 1348 size_t bcslen = offsetof(btrfs_create_snapshot, name[0]) + (destname.length() * sizeof(WCHAR)); 1349 bcs = (btrfs_create_snapshot*)malloc(bcslen); 1350 bcs->subvol = source; 1351 bcs->namelen = (uint16_t)(destname.length() * sizeof(WCHAR)); 1352 memcpy(bcs->name, destname.c_str(), destname.length() * sizeof(WCHAR)); 1353 1354 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)bcslen, nullptr, 0); 1355 if (!NT_SUCCESS(Status)) { 1356 free(bcs); 1357 throw ntstatus_error(Status); 1358 } 1359 1360 free(bcs); 1361 1362 return; 1363 } 1364 1365 Status = NtQueryInformationFile(source, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation); 1366 if (!NT_SUCCESS(Status)) 1367 throw ntstatus_error(Status); 1368 1369 if (bii.type == BTRFS_TYPE_CHARDEV || bii.type == BTRFS_TYPE_BLOCKDEV || bii.type == BTRFS_TYPE_FIFO || bii.type == BTRFS_TYPE_SOCKET) { 1370 win_handle dirh; 1371 btrfs_mknod* bmn; 1372 1373 dirh = CreateFileW(destdir.c_str(), FILE_ADD_FILE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 1374 if (dirh == INVALID_HANDLE_VALUE) 1375 throw last_error(GetLastError()); 1376 1377 size_t bmnsize = offsetof(btrfs_mknod, name[0]) + (destname.length() * sizeof(WCHAR)); 1378 bmn = (btrfs_mknod*)malloc(bmnsize); 1379 1380 bmn->inode = 0; 1381 bmn->type = bii.type; 1382 bmn->st_rdev = bii.st_rdev; 1383 bmn->namelen = (uint16_t)(destname.length() * sizeof(WCHAR)); 1384 memcpy(bmn->name, destname.c_str(), bmn->namelen); 1385 1386 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_MKNOD, bmn, (ULONG)bmnsize, nullptr, 0); 1387 if (!NT_SUCCESS(Status)) { 1388 free(bmn); 1389 throw ntstatus_error(Status); 1390 } 1391 1392 free(bmn); 1393 1394 dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, 0, nullptr); 1395 } else if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 1396 if (CreateDirectoryExW(srcfn.c_str(), (destdir + destname).c_str(), nullptr)) 1397 dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 1398 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 1399 else 1400 dest = INVALID_HANDLE_VALUE; 1401 } else 1402 dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source); 1403 1404 if (dest == INVALID_HANDLE_VALUE) 1405 throw last_error(GetLastError()); 1406 1407 memset(&bsii, 0, sizeof(btrfs_set_inode_info)); 1408 1409 bsii.flags_changed = true; 1410 bsii.flags = bii.flags; 1411 1412 if (bii.flags & BTRFS_INODE_COMPRESS) { 1413 bsii.compression_type_changed = true; 1414 bsii.compression_type = bii.compression_type; 1415 } 1416 1417 try { 1418 Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0); 1419 if (!NT_SUCCESS(Status)) 1420 throw ntstatus_error(Status); 1421 1422 if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 1423 if (!(fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { 1424 WIN32_FIND_DATAW fff; 1425 wstring qs; 1426 1427 qs = srcfn; 1428 qs += L"\\*"; 1429 1430 fff_handle h = FindFirstFileW(qs.c_str(), &fff); 1431 if (h != INVALID_HANDLE_VALUE) { 1432 do { 1433 wstring fn2; 1434 1435 if (fff.cFileName[0] == '.' && (fff.cFileName[1] == 0 || (fff.cFileName[1] == '.' && fff.cFileName[2] == 0))) 1436 continue; 1437 1438 fn2 = srcfn; 1439 fn2 += L"\\"; 1440 fn2 += fff.cFileName; 1441 1442 reflink_copy2(fn2, destdir + destname + L"\\", fff.cFileName); 1443 } while (FindNextFileW(h, &fff)); 1444 } 1445 } 1446 1447 // CreateDirectoryExW also copies streams, no need to do it here 1448 } else { 1449 if (fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { 1450 reparse_header rh; 1451 uint8_t* rp; 1452 1453 if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, &rh, sizeof(reparse_header), &bytesret, nullptr)) { 1454 if (GetLastError() != ERROR_MORE_DATA) 1455 throw last_error(GetLastError()); 1456 } 1457 1458 size_t rplen = sizeof(reparse_header) + rh.ReparseDataLength; 1459 rp = (uint8_t*)malloc(rplen); 1460 1461 try { 1462 if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, rp, (DWORD)rplen, &bytesret, nullptr)) 1463 throw last_error(GetLastError()); 1464 1465 if (!DeviceIoControl(dest, FSCTL_SET_REPARSE_POINT, rp, (DWORD)rplen, nullptr, 0, &bytesret, nullptr)) 1466 throw last_error(GetLastError()); 1467 } catch (...) { 1468 free(rp); 1469 throw; 1470 } 1471 1472 free(rp); 1473 } else { 1474 FILE_STANDARD_INFO fsi; 1475 FILE_END_OF_FILE_INFO feofi; 1476 FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib; 1477 FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib; 1478 DUPLICATE_EXTENTS_DATA ded; 1479 uint64_t offset, alloc_size; 1480 ULONG maxdup; 1481 1482 Status = NtQueryInformationFile(source, &iosb, &fsi, sizeof(FILE_STANDARD_INFO), FileStandardInformation); 1483 if (!NT_SUCCESS(Status)) 1484 throw ntstatus_error(Status); 1485 1486 if (!DeviceIoControl(source, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &fgiib, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER), &bytesret, nullptr)) 1487 throw last_error(GetLastError()); 1488 1489 if (fbi.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) { 1490 if (!DeviceIoControl(dest, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesret, nullptr)) 1491 throw last_error(GetLastError()); 1492 } 1493 1494 fsiib.ChecksumAlgorithm = fgiib.ChecksumAlgorithm; 1495 fsiib.Reserved = 0; 1496 fsiib.Flags = fgiib.Flags; 1497 if (!DeviceIoControl(dest, FSCTL_SET_INTEGRITY_INFORMATION, &fsiib, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER), nullptr, 0, &bytesret, nullptr)) 1498 throw last_error(GetLastError()); 1499 1500 feofi.EndOfFile = fsi.EndOfFile; 1501 Status = NtSetInformationFile(dest, &iosb, &feofi, sizeof(FILE_END_OF_FILE_INFO), FileEndOfFileInformation); 1502 if (!NT_SUCCESS(Status)) 1503 throw ntstatus_error(Status); 1504 1505 ded.FileHandle = source; 1506 maxdup = 0xffffffff - fgiib.ClusterSizeInBytes + 1; 1507 1508 alloc_size = sector_align(fsi.EndOfFile.QuadPart, fgiib.ClusterSizeInBytes); 1509 1510 offset = 0; 1511 while (offset < alloc_size) { 1512 ded.SourceFileOffset.QuadPart = ded.TargetFileOffset.QuadPart = offset; 1513 ded.ByteCount.QuadPart = maxdup < (alloc_size - offset) ? maxdup : (alloc_size - offset); 1514 if (!DeviceIoControl(dest, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA), nullptr, 0, &bytesret, nullptr)) 1515 throw last_error(GetLastError()); 1516 1517 offset += ded.ByteCount.QuadPart; 1518 } 1519 } 1520 1521 ULONG streambufsize = 0; 1522 vector<char> streambuf; 1523 1524 do { 1525 streambufsize += 0x1000; 1526 streambuf.resize(streambufsize); 1527 1528 memset(streambuf.data(), 0, streambufsize); 1529 1530 Status = NtQueryInformationFile(source, &iosb, streambuf.data(), streambufsize, FileStreamInformation); 1531 } while (Status == STATUS_BUFFER_OVERFLOW); 1532 1533 if (!NT_SUCCESS(Status)) 1534 throw ntstatus_error(Status); 1535 1536 auto fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(streambuf.data()); 1537 1538 while (true) { 1539 if (fsi->StreamNameLength > 0) { 1540 wstring sn = wstring(fsi->StreamName, fsi->StreamNameLength / sizeof(WCHAR)); 1541 1542 if (sn != L"::$DATA" && sn.length() > 6 && sn.substr(sn.length() - 6, 6) == L":$DATA") { 1543 win_handle stream; 1544 uint8_t* data = nullptr; 1545 auto stream_size = (uint16_t)fsi->StreamSize.QuadPart; 1546 1547 if (stream_size > 0) { 1548 wstring fn2; 1549 1550 fn2 = srcfn; 1551 fn2 += sn; 1552 1553 stream = CreateFileW(fn2.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr); 1554 1555 if (stream == INVALID_HANDLE_VALUE) 1556 throw last_error(GetLastError()); 1557 1558 // We can get away with this because our streams are guaranteed to be below 64 KB - 1559 // don't do this on NTFS! 1560 data = (uint8_t*)malloc(stream_size); 1561 1562 if (!ReadFile(stream, data, stream_size, &bytesret, nullptr)) { 1563 free(data); 1564 throw last_error(GetLastError()); 1565 } 1566 } 1567 1568 stream = CreateFileW((destdir + destname + sn).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, nullptr); 1569 1570 if (stream == INVALID_HANDLE_VALUE) { 1571 if (data) free(data); 1572 throw last_error(GetLastError()); 1573 } 1574 1575 if (data) { 1576 if (!WriteFile(stream, data, stream_size, &bytesret, nullptr)) { 1577 free(data); 1578 throw last_error(GetLastError()); 1579 } 1580 1581 free(data); 1582 } 1583 } 1584 1585 } 1586 1587 if (fsi->NextEntryOffset == 0) 1588 break; 1589 1590 fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(reinterpret_cast<char*>(fsi) + fsi->NextEntryOffset); 1591 } 1592 } 1593 1594 atime.dwLowDateTime = fbi.LastAccessTime.LowPart; 1595 atime.dwHighDateTime = fbi.LastAccessTime.HighPart; 1596 mtime.dwLowDateTime = fbi.LastWriteTime.LowPart; 1597 mtime.dwHighDateTime = fbi.LastWriteTime.HighPart; 1598 SetFileTime(dest, nullptr, &atime, &mtime); 1599 1600 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, &bsxa, sizeof(btrfs_set_xattr)); 1601 1602 if (Status == STATUS_BUFFER_OVERFLOW || (NT_SUCCESS(Status) && bsxa.valuelen > 0)) { 1603 ULONG xalen = 0; 1604 btrfs_set_xattr *xa = nullptr, *xa2; 1605 1606 do { 1607 xalen += 1024; 1608 1609 if (xa) free(xa); 1610 xa = (btrfs_set_xattr*)malloc(xalen); 1611 1612 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, xa, xalen); 1613 } while (Status == STATUS_BUFFER_OVERFLOW); 1614 1615 if (!NT_SUCCESS(Status)) { 1616 free(xa); 1617 throw ntstatus_error(Status); 1618 } 1619 1620 xa2 = xa; 1621 while (xa2->valuelen > 0) { 1622 Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, xa2, 1623 (ULONG)(offsetof(btrfs_set_xattr, data[0]) + xa2->namelen + xa2->valuelen), nullptr, 0); 1624 if (!NT_SUCCESS(Status)) { 1625 free(xa); 1626 throw ntstatus_error(Status); 1627 } 1628 xa2 = (btrfs_set_xattr*)&xa2->data[xa2->namelen + xa2->valuelen]; 1629 } 1630 1631 free(xa); 1632 } else if (!NT_SUCCESS(Status)) 1633 throw ntstatus_error(Status); 1634 } catch (...) { 1635 FILE_DISPOSITION_INFO fdi; 1636 1637 fdi.DeleteFile = true; 1638 Status = NtSetInformationFile(dest, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation); 1639 if (!NT_SUCCESS(Status)) 1640 throw ntstatus_error(Status); 1641 1642 throw; 1643 } 1644 } 1645 1646 extern "C" void CALLBACK ReflinkCopyW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) { 1647 vector<wstring> args; 1648 1649 command_line_to_args(lpszCmdLine, args); 1650 1651 if (args.size() >= 2) { 1652 bool dest_is_dir = false; 1653 wstring dest = args[args.size() - 1], destdir, destname; 1654 WCHAR volpath2[MAX_PATH]; 1655 1656 { 1657 win_handle destdirh = CreateFileW(dest.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 1658 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); 1659 1660 if (destdirh != INVALID_HANDLE_VALUE) { 1661 BY_HANDLE_FILE_INFORMATION bhfi; 1662 1663 if (GetFileInformationByHandle(destdirh, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 1664 dest_is_dir = true; 1665 1666 destdir = dest; 1667 if (destdir.substr(destdir.length() - 1, 1) != L"\\") 1668 destdir += L"\\"; 1669 } 1670 } 1671 } 1672 1673 if (!dest_is_dir) { 1674 size_t found = dest.rfind(L"\\"); 1675 1676 if (found == wstring::npos) { 1677 destdir = L""; 1678 destname = dest; 1679 } else { 1680 destdir = dest.substr(0, found); 1681 destname = dest.substr(found + 1); 1682 } 1683 } 1684 1685 if (!GetVolumePathNameW(dest.c_str(), volpath2, sizeof(volpath2) / sizeof(WCHAR))) 1686 return; 1687 1688 for (unsigned int i = 0; i < args.size() - 1; i++) { 1689 WIN32_FIND_DATAW ffd; 1690 1691 fff_handle h = FindFirstFileW(args[i].c_str(), &ffd); 1692 if (h != INVALID_HANDLE_VALUE) { 1693 WCHAR volpath1[MAX_PATH]; 1694 wstring path = args[i]; 1695 size_t found = path.rfind(L"\\"); 1696 1697 if (found == wstring::npos) 1698 path = L""; 1699 else 1700 path = path.substr(0, found); 1701 1702 path += L"\\"; 1703 1704 if (get_volume_path_parent(path.c_str(), volpath1, sizeof(volpath1) / sizeof(WCHAR))) { 1705 if (!wcscmp(volpath1, volpath2)) { 1706 do { 1707 try { 1708 reflink_copy2(path + ffd.cFileName, destdir, dest_is_dir ? ffd.cFileName : destname); 1709 } catch (const exception& e) { 1710 cerr << "Error: " << e.what() << endl; 1711 } 1712 } while (FindNextFileW(h, &ffd)); 1713 } 1714 } 1715 } 1716 } 1717 } 1718 }