1 /* Copyright (c) Mark Harmstone 2016-17
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/>. */
25 #define WIN32_NO_STATUS
31 #include <ndk/iofuncs.h>
38 #define NO_SHLWAPI_STRFCNS
41 #include "contextmenu.h"
44 #include "../btrfsioctl.h"
46 #include "btrfsioctl.h"
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"
62 USHORT ReparseDataLength
;
66 static void path_remove_file(wstring
& path
);
68 // FIXME - don't assume subvol's top inode is 0x100
70 HRESULT __stdcall
BtrfsContextMenu::QueryInterface(REFIID riid
, void **ppObj
) {
71 if (riid
== IID_IUnknown
|| riid
== IID_IContextMenu
) {
72 *ppObj
= static_cast<IContextMenu
*>(this);
75 } else if (riid
== IID_IShellExtInit
) {
76 *ppObj
= static_cast<IShellExtInit
*>(this);
85 HRESULT __stdcall
BtrfsContextMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder
, IDataObject
* pdtobj
, HKEY hkeyProgID
) {
87 btrfs_get_file_ids bgfi
;
91 FORMATETC format
= { CF_HDROP
, nullptr, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
99 stgm
.tymed
= TYMED_HGLOBAL
;
101 if (FAILED(pdtobj
->GetData(&format
, &stgm
)))
106 hdrop
= (HDROP
)GlobalLock(stgm
.hGlobal
);
109 ReleaseStgMedium(&stgm
);
114 num_files
= DragQueryFileW((HDROP
)stgm
.hGlobal
, 0xFFFFFFFF, nullptr, 0);
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);
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
));
123 if (NT_SUCCESS(Status
) && bgfi
.inode
== 0x100 && !bgfi
.top
) {
130 path_remove_file(parpath
);
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);
135 if (h2
!= INVALID_HANDLE_VALUE
)
136 allow_snapshot
= true;
155 WCHAR pathbuf
[MAX_PATH
];
157 if (!SHGetPathFromIDListW(pidlFolder
, pathbuf
))
164 // check we have permissions to create new subdirectory
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);
168 if (h
== INVALID_HANDLE_VALUE
)
171 // check is Btrfs volume
173 Status
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_FILE_IDS
, nullptr, 0, &bgfi
, sizeof(btrfs_get_file_ids
));
175 if (!NT_SUCCESS(Status
))
185 static bool get_volume_path_parent(const WCHAR
* fn
, WCHAR
* volpath
, ULONG volpathlen
) {
189 f
= PathFindFileNameW(fn
);
192 return GetVolumePathNameW(fn
, volpath
, volpathlen
);
194 p
= (WCHAR
*)malloc((f
- fn
+ 1) * sizeof(WCHAR
));
195 memcpy(p
, fn
, (f
- fn
) * sizeof(WCHAR
));
198 b
= GetVolumePathNameW(p
, volpath
, volpathlen
);
205 static bool show_reflink_paste(const wstring
& path
) {
209 WCHAR fn
[MAX_PATH
], volpath1
[255], volpath2
[255];
211 if (!IsClipboardFormatAvailable(CF_HDROP
))
214 if (!GetVolumePathNameW(path
.c_str(), volpath1
, sizeof(volpath1
) / sizeof(WCHAR
)))
217 if (!OpenClipboard(nullptr))
220 hdrop
= (HDROP
)GetClipboardData(CF_HDROP
);
227 lh
= GlobalLock(hdrop
);
234 num_files
= DragQueryFileW(hdrop
, 0xFFFFFFFF, nullptr, 0);
236 if (num_files
== 0) {
242 if (!DragQueryFileW(hdrop
, 0, fn
, sizeof(fn
) / sizeof(WCHAR
))) {
248 if (!get_volume_path_parent(fn
, volpath2
, sizeof(volpath2
) / sizeof(WCHAR
))) {
258 return !wcscmp(volpath1
, volpath2
);
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
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
;
270 pbmi
->bmiHeader
.biWidth
= cx
;
271 pbmi
->bmiHeader
.biHeight
= cy
;
272 pbmi
->bmiHeader
.biBitCount
= bpp
;
275 static HRESULT
Create32BitHBITMAP(HDC hdc
, const SIZE
*psize
, void **ppvBits
, HBITMAP
* phBmp
) {
281 InitBitmapInfo(&bmi
, sizeof(bmi
), psize
->cx
, psize
->cy
, 32);
283 hdcUsed
= hdc
? hdc
: GetDC(nullptr);
286 *phBmp
= CreateDIBSection(hdcUsed
, &bmi
, DIB_RGB_COLORS
, ppvBits
, nullptr, 0);
288 ReleaseDC(nullptr, hdcUsed
);
291 return !*phBmp
? E_OUTOFMEMORY
: S_OK
;
294 void BtrfsContextMenu::get_uac_icon() {
295 IWICImagingFactory
* factory
= nullptr;
300 hr
= CoCreateInstance(CLSID_WICImagingFactory
, nullptr, CLSCTX_INPROC_SERVER
, IID_IWICImagingFactory
, (void **)&factory
);
302 hr
= CoCreateInstance(CLSID_WICImagingFactory
, nullptr, CLSCTX_INPROC_SERVER
, IID_PPV_ARGS(&factory
));
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
);
312 hr
= factory
->CreateBitmapFromHICON((HICON
)icon
, &bitmap
);
316 hr
= bitmap
->GetSize(&cx
, &cy
);
324 hr
= Create32BitHBITMAP(nullptr, &sz
, (void**)&buf
, &uacicon
);
326 UINT stride
= (UINT
)(cx
* sizeof(DWORD
));
327 UINT buflen
= cy
* stride
;
328 bitmap
->CopyPixels(nullptr, stride
, buflen
, buf
);
339 HRESULT __stdcall
BtrfsContextMenu::QueryContextMenu(HMENU hmenu
, UINT indexMenu
, UINT idCmdFirst
, UINT idCmdLast
, UINT uFlags
) {
346 if (uFlags
& CMF_DEFAULTONLY
)
350 if (allow_snapshot
) {
351 if (load_string(module
, IDS_CREATE_SNAPSHOT
, str
) == 0)
354 if (!InsertMenuW(hmenu
, indexMenu
, MF_BYPOSITION
, idCmdFirst
, str
.c_str()))
360 if (idCmdFirst
+ entries
<= idCmdLast
) {
363 if (load_string(module
, IDS_SEND_SUBVOL
, str
) == 0)
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
;
376 if (!InsertMenuItemW(hmenu
, indexMenu
+ entries
, true, &mii
))
382 if (load_string(module
, IDS_NEW_SUBVOL
, str
) == 0)
385 if (!InsertMenuW(hmenu
, indexMenu
, MF_BYPOSITION
, idCmdFirst
, str
.c_str()))
390 if (idCmdFirst
+ 1 <= idCmdLast
) {
393 if (load_string(module
, IDS_RECV_SUBVOL
, str
) == 0)
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
;
406 if (!InsertMenuItemW(hmenu
, indexMenu
+ 1, true, &mii
))
412 if (idCmdFirst
+ 2 <= idCmdLast
&& show_reflink_paste(path
)) {
413 if (load_string(module
, IDS_REFLINK_PASTE
, str
) == 0)
416 if (!InsertMenuW(hmenu
, indexMenu
+ 2, MF_BYPOSITION
, idCmdFirst
+ 2, str
.c_str()))
423 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, entries
);
426 static void path_remove_file(wstring
& path
) {
427 size_t bs
= path
.rfind(L
"\\");
429 if (bs
== string::npos
)
432 if (bs
== path
.find(L
"\\")) { // only one backslash
433 path
= path
.substr(0, bs
+ 1);
437 path
= path
.substr(0, bs
);
440 static void path_strip_path(wstring
& path
) {
441 size_t bs
= path
.rfind(L
"\\");
443 if (bs
== string::npos
) {
448 path
= path
.substr(bs
+ 1);
451 static void create_snapshot(HWND hwnd
, const wstring
& fn
) {
454 IO_STATUS_BLOCK iosb
;
455 btrfs_get_file_ids bgfi
;
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);
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
));
462 if (NT_SUCCESS(Status
) && bgfi
.inode
== 0x100 && !bgfi
.top
) {
463 wstring subvolname
, parpath
, searchpath
, temp1
, name
, nameorig
;
465 WIN32_FIND_DATAW wfd
;
469 path_remove_file(parpath
);
472 path_strip_path(subvolname
);
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);
476 if (h2
== INVALID_HANDLE_VALUE
)
477 throw last_error(GetLastError());
479 if (!load_string(module
, IDS_SNAPSHOT_FILENAME
, temp1
))
480 throw last_error(GetLastError());
484 wstring_sprintf(name
, temp1
, subvolname
.c_str(), time
.wYear
, time
.wMonth
, time
.wDay
);
487 searchpath
= parpath
+ L
"\\" + name
;
489 fff_handle fff
= FindFirstFileW(searchpath
.c_str(), &wfd
);
491 if (fff
!= INVALID_HANDLE_VALUE
) {
496 name
= nameorig
+ L
" (" + to_wstring(num
) + L
")";
501 swprintf(buffer
, L
"%d", num
);
502 name
= nameorig
+ L
" (" + buffer
+ L
")";
505 searchpath
= parpath
+ L
"\\" + name
;
507 fff
= FindFirstFileW(searchpath
.c_str(), &wfd
);
509 } while (fff
!= INVALID_HANDLE_VALUE
);
512 size_t namelen
= name
.length() * sizeof(WCHAR
);
514 auto bcs
= (btrfs_create_snapshot
*)malloc(sizeof(btrfs_create_snapshot
) - 1 + namelen
);
515 bcs
->readonly
= false;
518 bcs
->namelen
= (uint16_t)namelen
;
519 memcpy(bcs
->name
, name
.c_str(), namelen
);
521 Status
= NtFsControlFile(h2
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_CREATE_SNAPSHOT
, bcs
,
522 (ULONG
)(sizeof(btrfs_create_snapshot
) - 1 + namelen
), nullptr, 0);
524 if (!NT_SUCCESS(Status
))
525 throw ntstatus_error(Status
);
528 throw last_error(GetLastError());
531 static uint64_t __inline
sector_align(uint64_t n
, uint64_t a
) {
533 n
= (n
+ a
) & ~(a
- 1);
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
;
543 FILETIME atime
, mtime
;
544 btrfs_inode_info bii
;
545 btrfs_set_inode_info bsii
;
548 IO_STATUS_BLOCK iosb
;
549 btrfs_set_xattr bsxa
;
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
554 name
= PathFindFileNameW(fn
);
558 if (dir
[0] != 0 && dir
[wcslen(dir
) - 1] != '\\')
564 if (!get_volume_path_parent(fn
, volpath1
, sizeof(volpath1
) / sizeof(WCHAR
)))
565 throw last_error(GetLastError());
567 if (!GetVolumePathNameW(dir
, volpath2
, sizeof(volpath2
) / sizeof(WCHAR
)))
568 throw last_error(GetLastError());
570 if (wcscmp(volpath1
, volpath2
)) // different filesystems
571 throw string_error(IDS_CANT_REFLINK_DIFFERENT_FS
);
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());
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
);
581 // if subvol, do snapshot instead
582 if (bii
.inode
== SUBVOL_ROOT_INODE
) {
583 btrfs_create_snapshot
* bcs
;
585 wstring destname
, search
;
586 WIN32_FIND_DATAW wfd
;
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());
597 fff_handle fff
= FindFirstFileW(search
.c_str(), &wfd
);
599 if (fff
!= INVALID_HANDLE_VALUE
) {
609 search
= dirw
+ destname
;
611 fff
= FindFirstFileW(search
.c_str(), &wfd
);
613 } while (fff
!= INVALID_HANDLE_VALUE
);
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
));
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);
626 if (!NT_SUCCESS(Status
))
627 throw ntstatus_error(Status
);
632 Status
= NtQueryInformationFile(source
, &iosb
, &fbi
, sizeof(FILE_BASIC_INFO
), FileBasicInformation
);
633 if (!NT_SUCCESS(Status
))
634 throw ntstatus_error(Status
);
636 if (bii
.type
== BTRFS_TYPE_CHARDEV
|| bii
.type
== BTRFS_TYPE_BLOCKDEV
|| bii
.type
== BTRFS_TYPE_FIFO
|| bii
.type
== BTRFS_TYPE_SOCKET
) {
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());
644 size_t bmnsize
= offsetof(btrfs_mknod
, name
[0]) + (wcslen(name
) * sizeof(WCHAR
));
645 bmn
= (btrfs_mknod
*)malloc(bmnsize
);
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
);
653 Status
= NtFsControlFile(dirh
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_MKNOD
, bmn
, (ULONG
)bmnsize
, nullptr, 0);
654 if (!NT_SUCCESS(Status
)) {
656 throw ntstatus_error(Status
);
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);
667 dest
= INVALID_HANDLE_VALUE
;
669 dest
= CreateFileW(newpath
.c_str(), GENERIC_READ
| GENERIC_WRITE
| DELETE
, 0, nullptr, CREATE_NEW
, 0, source
);
671 if (dest
== INVALID_HANDLE_VALUE
) {
674 if (GetLastError() != ERROR_FILE_EXISTS
&& GetLastError() != ERROR_ALREADY_EXISTS
&& wcscmp(fn
, newpath
.c_str()))
675 throw last_error(GetLastError());
681 ext
= PathFindExtensionW(fn
);
691 wstring namew
= name
;
693 ss
<< namew
.substr(0, ext
- name
);
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);
705 dest
= INVALID_HANDLE_VALUE
;
707 dest
= CreateFileW(newpath
.c_str(), GENERIC_READ
| GENERIC_WRITE
| DELETE
, 0, nullptr, CREATE_NEW
, 0, source
);
709 if (dest
== INVALID_HANDLE_VALUE
) {
710 if (GetLastError() != ERROR_FILE_EXISTS
&& GetLastError() != ERROR_ALREADY_EXISTS
)
711 throw last_error(GetLastError());
720 memset(&bsii
, 0, sizeof(btrfs_set_inode_info
));
722 bsii
.flags_changed
= true;
723 bsii
.flags
= bii
.flags
;
725 if (bii
.flags
& BTRFS_INODE_COMPRESS
) {
726 bsii
.compression_type_changed
= true;
727 bsii
.compression_type
= bii
.compression_type
;
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
);
734 if (fbi
.FileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
735 if (!(fbi
.FileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
)) {
737 WIN32_FIND_DATAW fff
;
743 h
= FindFirstFileW(qs
.c_str(), &fff
);
744 if (h
!= INVALID_HANDLE_VALUE
) {
748 if (fff
.cFileName
[0] == '.' && (fff
.cFileName
[1] == 0 || (fff
.cFileName
[1] == '.' && fff
.cFileName
[2] == 0)))
753 fn2
+= fff
.cFileName
;
755 reflink_copy(hwnd
, fn2
.c_str(), newpath
.c_str());
756 } while (FindNextFileW(h
, &fff
));
760 // CreateDirectoryExW also copies streams, no need to do it here
762 if (fbi
.FileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) {
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());
771 size_t rplen
= sizeof(reparse_header
) + rh
.ReparseDataLength
;
772 rp
= (uint8_t*)malloc(rplen
);
774 if (!DeviceIoControl(source
, FSCTL_GET_REPARSE_POINT
, nullptr, 0, rp
, (ULONG
)rplen
, &bytesret
, nullptr))
775 throw last_error(GetLastError());
777 if (!DeviceIoControl(dest
, FSCTL_SET_REPARSE_POINT
, rp
, (ULONG
)rplen
, nullptr, 0, &bytesret
, nullptr))
778 throw last_error(GetLastError());
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
;
790 Status
= NtQueryInformationFile(source
, &iosb
, &fsi
, sizeof(FILE_STANDARD_INFO
), FileStandardInformation
);
791 if (!NT_SUCCESS(Status
))
792 throw ntstatus_error(Status
);
794 if (!DeviceIoControl(source
, FSCTL_GET_INTEGRITY_INFORMATION
, nullptr, 0, &fgiib
, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
), &bytesret
, nullptr))
795 throw last_error(GetLastError());
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());
802 fsiib
.ChecksumAlgorithm
= fgiib
.ChecksumAlgorithm
;
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());
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
);
813 ded
.FileHandle
= source
;
814 maxdup
= 0xffffffff - fgiib
.ClusterSizeInBytes
+ 1;
816 alloc_size
= sector_align(fsi
.EndOfFile
.QuadPart
, fgiib
.ClusterSizeInBytes
);
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());
825 offset
+= ded
.ByteCount
.QuadPart
;
829 ULONG streambufsize
= 0;
830 vector
<char> streambuf
;
833 streambufsize
+= 0x1000;
834 streambuf
.resize(streambufsize
);
836 memset(streambuf
.data(), 0, streambufsize
);
838 Status
= NtQueryInformationFile(source
, &iosb
, streambuf
.data(), streambufsize
, FileStreamInformation
);
839 } while (Status
== STATUS_BUFFER_OVERFLOW
);
841 if (!NT_SUCCESS(Status
))
842 throw ntstatus_error(Status
);
844 auto fsi
= reinterpret_cast<FILE_STREAM_INFORMATION
*>(streambuf
.data());
847 if (fsi
->StreamNameLength
> 0) {
848 wstring sn
= wstring(fsi
->StreamName
, fsi
->StreamNameLength
/ sizeof(WCHAR
));
850 if (sn
!= L
"::$DATA" && sn
.length() > 6 && sn
.substr(sn
.length() - 6, 6) == L
":$DATA") {
852 uint8_t* data
= nullptr;
853 auto stream_size
= (uint16_t)fsi
->StreamSize
.QuadPart
;
856 if (stream_size
> 0) {
862 stream
= CreateFileW(fn2
.c_str(), GENERIC_READ
, 0, nullptr, OPEN_EXISTING
, 0, nullptr);
864 if (stream
== INVALID_HANDLE_VALUE
)
865 throw last_error(GetLastError());
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
);
871 if (!ReadFile(stream
, data
, stream_size
, &bytesret
, nullptr)) {
873 throw last_error(GetLastError());
877 stream
= CreateFileW((newpath
+ sn
).c_str(), GENERIC_READ
| GENERIC_WRITE
| DELETE
, 0, nullptr, CREATE_NEW
, 0, nullptr);
879 if (stream
== INVALID_HANDLE_VALUE
) {
880 if (data
) free(data
);
881 throw last_error(GetLastError());
885 if (!WriteFile(stream
, data
, stream_size
, &bytesret
, nullptr)) {
887 throw last_error(GetLastError());
895 if (fsi
->NextEntryOffset
== 0)
898 fsi
= reinterpret_cast<FILE_STREAM_INFORMATION
*>(reinterpret_cast<char*>(fsi
) + fsi
->NextEntryOffset
);
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
);
908 Status
= NtFsControlFile(source
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_XATTRS
, nullptr, 0, &bsxa
, sizeof(btrfs_set_xattr
));
910 if (Status
== STATUS_BUFFER_OVERFLOW
|| (NT_SUCCESS(Status
) && bsxa
.valuelen
> 0)) {
912 btrfs_set_xattr
*xa
= nullptr, *xa2
;
918 xa
= (btrfs_set_xattr
*)malloc(xalen
);
920 Status
= NtFsControlFile(source
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_XATTRS
, nullptr, 0, xa
, xalen
);
921 } while (Status
== STATUS_BUFFER_OVERFLOW
);
923 if (!NT_SUCCESS(Status
)) {
925 throw ntstatus_error(Status
);
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
)) {
934 throw ntstatus_error(Status
);
936 xa2
= (btrfs_set_xattr
*)&xa2
->data
[xa2
->namelen
+ xa2
->valuelen
];
940 } else if (!NT_SUCCESS(Status
))
941 throw ntstatus_error(Status
);
943 FILE_DISPOSITION_INFO fdi
;
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
);
954 HRESULT __stdcall
BtrfsContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO picia
) {
955 LPCMINVOKECOMMANDINFOEX pici
= (LPCMINVOKECOMMANDINFOEX
)picia
;
962 if ((IS_INTRESOURCE(pici
->lpVerb
) && allow_snapshot
&& pici
->lpVerb
== 0) || (!IS_INTRESOURCE(pici
->lpVerb
) && !strcmp(pici
->lpVerb
, SNAPSHOT_VERBA
))) {
969 num_files
= DragQueryFileW((HDROP
)stgm
.hGlobal
, 0xFFFFFFFF, nullptr, 0);
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
);
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
))) {
984 WCHAR dll
[MAX_PATH
], fn
[MAX_PATH
];
986 SHELLEXECUTEINFOW sei
;
988 GetModuleFileNameW(module
, dll
, sizeof(dll
) / sizeof(WCHAR
));
993 num_files
= DragQueryFileW((HDROP
)stgm
.hGlobal
, 0xFFFFFFFF, nullptr, 0);
998 for (i
= 0; i
< num_files
; i
++) {
999 if (DragQueryFileW((HDROP
)stgm
.hGlobal
, i
, fn
, sizeof(fn
) / sizeof(WCHAR
))) {
1002 t
+= L
"\",SendSubvolGUI ";
1005 RtlZeroMemory(&sei
, sizeof(sei
));
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
;
1015 if (!ShellExecuteExW(&sei
))
1016 throw last_error(GetLastError());
1018 WaitForSingleObject(sei
.hProcess
, INFINITE
);
1019 CloseHandle(sei
.hProcess
);
1026 if ((IS_INTRESOURCE(pici
->lpVerb
) && (ULONG_PTR
)pici
->lpVerb
== 0) || (!IS_INTRESOURCE(pici
->lpVerb
) && !strcmp(pici
->lpVerb
, NEW_SUBVOL_VERBA
))) {
1028 IO_STATUS_BLOCK iosb
;
1030 wstring name
, nameorig
, searchpath
;
1031 btrfs_create_subvol
* bcs
;
1032 WIN32_FIND_DATAW wfd
;
1034 if (!load_string(module
, IDS_NEW_SUBVOL_FILENAME
, name
))
1035 throw last_error(GetLastError());
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);
1039 if (h
== INVALID_HANDLE_VALUE
)
1040 throw last_error(GetLastError());
1042 searchpath
= path
+ L
"\\" + name
;
1046 fff_handle fff
= FindFirstFileW(searchpath
.c_str(), &wfd
);
1048 if (fff
!= INVALID_HANDLE_VALUE
) {
1053 name
= nameorig
+ L
" (" + to_wstring(num
) + L
")";
1058 swprintf(buffer
, L
"%d", num
);
1059 name
= nameorig
+ L
" (" + buffer
+ L
")";
1062 searchpath
= path
+ L
"\\" + name
;
1064 fff
= FindFirstFileW(searchpath
.c_str(), &wfd
);
1066 } while (fff
!= INVALID_HANDLE_VALUE
);
1070 size_t bcslen
= offsetof(btrfs_create_subvol
, name
[0]) + (name
.length() * sizeof(WCHAR
));
1071 bcs
= (btrfs_create_subvol
*)malloc(bcslen
);
1073 bcs
->readonly
= false;
1075 bcs
->namelen
= (uint16_t)(name
.length() * sizeof(WCHAR
));
1076 memcpy(bcs
->name
, name
.c_str(), name
.length() * sizeof(WCHAR
));
1078 Status
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_CREATE_SUBVOL
, bcs
, (ULONG
)bcslen
, nullptr, 0);
1082 if (!NT_SUCCESS(Status
))
1083 throw ntstatus_error(Status
);
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
];
1089 SHELLEXECUTEINFOW sei
;
1091 GetModuleFileNameW(module
, dll
, sizeof(dll
) / sizeof(WCHAR
));
1095 t
+= L
"\",RecvSubvolGUI ";
1098 RtlZeroMemory(&sei
, sizeof(sei
));
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
;
1108 if (!ShellExecuteExW(&sei
))
1109 throw last_error(GetLastError());
1111 WaitForSingleObject(sei
.hProcess
, INFINITE
);
1112 CloseHandle(sei
.hProcess
);
1115 } else if ((IS_INTRESOURCE(pici
->lpVerb
) && (ULONG_PTR
)pici
->lpVerb
== 2) || (!IS_INTRESOURCE(pici
->lpVerb
) && !strcmp(pici
->lpVerb
, REFLINK_VERBA
))) {
1118 if (!IsClipboardFormatAvailable(CF_HDROP
))
1121 if (!OpenClipboard(pici
->hwnd
))
1122 throw last_error(GetLastError());
1125 hdrop
= (HDROP
)GetClipboardData(CF_HDROP
);
1130 lh
= GlobalLock(hdrop
);
1137 num_files
= DragQueryFileW(hdrop
, 0xFFFFFFFF, nullptr, 0);
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
);
1162 } catch (const exception
& e
) {
1163 error_message(pici
->hwnd
, e
.what());
1169 HRESULT __stdcall
BtrfsContextMenu::GetCommandString(UINT_PTR idCmd
, UINT uFlags
, UINT
* pwReserved
, LPSTR pszName
, UINT cchMax
) {
1171 return E_INVALIDARG
;
1174 return E_INVALIDARG
;
1180 if (LoadStringA(module
, IDS_CREATE_SNAPSHOT_HELP_TEXT
, pszName
, cchMax
))
1186 if (LoadStringW(module
, IDS_CREATE_SNAPSHOT_HELP_TEXT
, (LPWSTR
)pszName
, cchMax
))
1196 return StringCchCopyA(pszName
, cchMax
, SNAPSHOT_VERBA
);
1199 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, SNAPSHOT_VERBW
);
1202 return E_INVALIDARG
;
1204 } else if (idCmd
== 1) {
1207 if (LoadStringA(module
, IDS_SEND_SUBVOL_HELP
, pszName
, cchMax
))
1213 if (LoadStringW(module
, IDS_SEND_SUBVOL_HELP
, (LPWSTR
)pszName
, cchMax
))
1223 return StringCchCopyA(pszName
, cchMax
, SEND_VERBA
);
1226 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, SEND_VERBW
);
1229 return E_INVALIDARG
;
1232 return E_INVALIDARG
;
1237 if (LoadStringA(module
, IDS_NEW_SUBVOL_HELP_TEXT
, pszName
, cchMax
))
1243 if (LoadStringW(module
, IDS_NEW_SUBVOL_HELP_TEXT
, (LPWSTR
)pszName
, cchMax
))
1253 return StringCchCopyA(pszName
, cchMax
, NEW_SUBVOL_VERBA
);
1256 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, NEW_SUBVOL_VERBW
);
1259 return E_INVALIDARG
;
1261 } else if (idCmd
== 1) {
1264 if (LoadStringA(module
, IDS_RECV_SUBVOL_HELP
, pszName
, cchMax
))
1270 if (LoadStringW(module
, IDS_RECV_SUBVOL_HELP
, (LPWSTR
)pszName
, cchMax
))
1280 return StringCchCopyA(pszName
, cchMax
, RECV_VERBA
);
1283 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, RECV_VERBW
);
1286 return E_INVALIDARG
;
1288 } else if (idCmd
== 2) {
1291 if (LoadStringA(module
, IDS_REFLINK_PASTE_HELP
, pszName
, cchMax
))
1297 if (LoadStringW(module
, IDS_REFLINK_PASTE_HELP
, (LPWSTR
)pszName
, cchMax
))
1307 return StringCchCopyA(pszName
, cchMax
, REFLINK_VERBA
);
1310 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, REFLINK_VERBW
);
1313 return E_INVALIDARG
;
1316 return E_INVALIDARG
;
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
;
1328 IO_STATUS_BLOCK iosb
;
1329 btrfs_set_xattr bsxa
;
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());
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
);
1339 // if subvol, do snapshot instead
1340 if (bii
.inode
== SUBVOL_ROOT_INODE
) {
1341 btrfs_create_snapshot
* bcs
;
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());
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
));
1354 Status
= NtFsControlFile(dirh
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_CREATE_SNAPSHOT
, bcs
, (ULONG
)bcslen
, nullptr, 0);
1355 if (!NT_SUCCESS(Status
)) {
1357 throw ntstatus_error(Status
);
1365 Status
= NtQueryInformationFile(source
, &iosb
, &fbi
, sizeof(FILE_BASIC_INFO
), FileBasicInformation
);
1366 if (!NT_SUCCESS(Status
))
1367 throw ntstatus_error(Status
);
1369 if (bii
.type
== BTRFS_TYPE_CHARDEV
|| bii
.type
== BTRFS_TYPE_BLOCKDEV
|| bii
.type
== BTRFS_TYPE_FIFO
|| bii
.type
== BTRFS_TYPE_SOCKET
) {
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());
1377 size_t bmnsize
= offsetof(btrfs_mknod
, name
[0]) + (destname
.length() * sizeof(WCHAR
));
1378 bmn
= (btrfs_mknod
*)malloc(bmnsize
);
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
);
1386 Status
= NtFsControlFile(dirh
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_MKNOD
, bmn
, (ULONG
)bmnsize
, nullptr, 0);
1387 if (!NT_SUCCESS(Status
)) {
1389 throw ntstatus_error(Status
);
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);
1400 dest
= INVALID_HANDLE_VALUE
;
1402 dest
= CreateFileW((destdir
+ destname
).c_str(), GENERIC_READ
| GENERIC_WRITE
| DELETE
, 0, nullptr, CREATE_NEW
, 0, source
);
1404 if (dest
== INVALID_HANDLE_VALUE
)
1405 throw last_error(GetLastError());
1407 memset(&bsii
, 0, sizeof(btrfs_set_inode_info
));
1409 bsii
.flags_changed
= true;
1410 bsii
.flags
= bii
.flags
;
1412 if (bii
.flags
& BTRFS_INODE_COMPRESS
) {
1413 bsii
.compression_type_changed
= true;
1414 bsii
.compression_type
= bii
.compression_type
;
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
);
1422 if (fbi
.FileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
1423 if (!(fbi
.FileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
)) {
1424 WIN32_FIND_DATAW fff
;
1430 fff_handle h
= FindFirstFileW(qs
.c_str(), &fff
);
1431 if (h
!= INVALID_HANDLE_VALUE
) {
1435 if (fff
.cFileName
[0] == '.' && (fff
.cFileName
[1] == 0 || (fff
.cFileName
[1] == '.' && fff
.cFileName
[2] == 0)))
1440 fn2
+= fff
.cFileName
;
1442 reflink_copy2(fn2
, destdir
+ destname
+ L
"\\", fff
.cFileName
);
1443 } while (FindNextFileW(h
, &fff
));
1447 // CreateDirectoryExW also copies streams, no need to do it here
1449 if (fbi
.FileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) {
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());
1458 size_t rplen
= sizeof(reparse_header
) + rh
.ReparseDataLength
;
1459 rp
= (uint8_t*)malloc(rplen
);
1462 if (!DeviceIoControl(source
, FSCTL_GET_REPARSE_POINT
, nullptr, 0, rp
, (DWORD
)rplen
, &bytesret
, nullptr))
1463 throw last_error(GetLastError());
1465 if (!DeviceIoControl(dest
, FSCTL_SET_REPARSE_POINT
, rp
, (DWORD
)rplen
, nullptr, 0, &bytesret
, nullptr))
1466 throw last_error(GetLastError());
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
;
1482 Status
= NtQueryInformationFile(source
, &iosb
, &fsi
, sizeof(FILE_STANDARD_INFO
), FileStandardInformation
);
1483 if (!NT_SUCCESS(Status
))
1484 throw ntstatus_error(Status
);
1486 if (!DeviceIoControl(source
, FSCTL_GET_INTEGRITY_INFORMATION
, nullptr, 0, &fgiib
, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
), &bytesret
, nullptr))
1487 throw last_error(GetLastError());
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());
1494 fsiib
.ChecksumAlgorithm
= fgiib
.ChecksumAlgorithm
;
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());
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
);
1505 ded
.FileHandle
= source
;
1506 maxdup
= 0xffffffff - fgiib
.ClusterSizeInBytes
+ 1;
1508 alloc_size
= sector_align(fsi
.EndOfFile
.QuadPart
, fgiib
.ClusterSizeInBytes
);
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());
1517 offset
+= ded
.ByteCount
.QuadPart
;
1521 ULONG streambufsize
= 0;
1522 vector
<char> streambuf
;
1525 streambufsize
+= 0x1000;
1526 streambuf
.resize(streambufsize
);
1528 memset(streambuf
.data(), 0, streambufsize
);
1530 Status
= NtQueryInformationFile(source
, &iosb
, streambuf
.data(), streambufsize
, FileStreamInformation
);
1531 } while (Status
== STATUS_BUFFER_OVERFLOW
);
1533 if (!NT_SUCCESS(Status
))
1534 throw ntstatus_error(Status
);
1536 auto fsi
= reinterpret_cast<FILE_STREAM_INFORMATION
*>(streambuf
.data());
1539 if (fsi
->StreamNameLength
> 0) {
1540 wstring sn
= wstring(fsi
->StreamName
, fsi
->StreamNameLength
/ sizeof(WCHAR
));
1542 if (sn
!= L
"::$DATA" && sn
.length() > 6 && sn
.substr(sn
.length() - 6, 6) == L
":$DATA") {
1544 uint8_t* data
= nullptr;
1545 auto stream_size
= (uint16_t)fsi
->StreamSize
.QuadPart
;
1547 if (stream_size
> 0) {
1553 stream
= CreateFileW(fn2
.c_str(), GENERIC_READ
, 0, nullptr, OPEN_EXISTING
, 0, nullptr);
1555 if (stream
== INVALID_HANDLE_VALUE
)
1556 throw last_error(GetLastError());
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
);
1562 if (!ReadFile(stream
, data
, stream_size
, &bytesret
, nullptr)) {
1564 throw last_error(GetLastError());
1568 stream
= CreateFileW((destdir
+ destname
+ sn
).c_str(), GENERIC_READ
| GENERIC_WRITE
| DELETE
, 0, nullptr, CREATE_NEW
, 0, nullptr);
1570 if (stream
== INVALID_HANDLE_VALUE
) {
1571 if (data
) free(data
);
1572 throw last_error(GetLastError());
1576 if (!WriteFile(stream
, data
, stream_size
, &bytesret
, nullptr)) {
1578 throw last_error(GetLastError());
1587 if (fsi
->NextEntryOffset
== 0)
1590 fsi
= reinterpret_cast<FILE_STREAM_INFORMATION
*>(reinterpret_cast<char*>(fsi
) + fsi
->NextEntryOffset
);
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
);
1600 Status
= NtFsControlFile(source
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_XATTRS
, nullptr, 0, &bsxa
, sizeof(btrfs_set_xattr
));
1602 if (Status
== STATUS_BUFFER_OVERFLOW
|| (NT_SUCCESS(Status
) && bsxa
.valuelen
> 0)) {
1604 btrfs_set_xattr
*xa
= nullptr, *xa2
;
1610 xa
= (btrfs_set_xattr
*)malloc(xalen
);
1612 Status
= NtFsControlFile(source
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_XATTRS
, nullptr, 0, xa
, xalen
);
1613 } while (Status
== STATUS_BUFFER_OVERFLOW
);
1615 if (!NT_SUCCESS(Status
)) {
1617 throw ntstatus_error(Status
);
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
)) {
1626 throw ntstatus_error(Status
);
1628 xa2
= (btrfs_set_xattr
*)&xa2
->data
[xa2
->namelen
+ xa2
->valuelen
];
1632 } else if (!NT_SUCCESS(Status
))
1633 throw ntstatus_error(Status
);
1635 FILE_DISPOSITION_INFO fdi
;
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
);
1650 void CALLBACK
ReflinkCopyW(HWND hwnd
, HINSTANCE hinst
, LPWSTR lpszCmdLine
, int nCmdShow
) {
1651 vector
<wstring
> args
;
1653 command_line_to_args(lpszCmdLine
, args
);
1655 if (args
.size() >= 2) {
1656 bool dest_is_dir
= false;
1657 wstring dest
= args
[args
.size() - 1], destdir
, destname
;
1658 WCHAR volpath2
[MAX_PATH
];
1661 win_handle destdirh
= CreateFileW(dest
.c_str(), FILE_TRAVERSE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
1662 nullptr, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, nullptr);
1664 if (destdirh
!= INVALID_HANDLE_VALUE
) {
1665 BY_HANDLE_FILE_INFORMATION bhfi
;
1667 if (GetFileInformationByHandle(destdirh
, &bhfi
) && bhfi
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
1671 if (destdir
.substr(destdir
.length() - 1, 1) != L
"\\")
1678 size_t found
= dest
.rfind(L
"\\");
1680 if (found
== wstring::npos
) {
1684 destdir
= dest
.substr(0, found
);
1685 destname
= dest
.substr(found
+ 1);
1689 if (!GetVolumePathNameW(dest
.c_str(), volpath2
, sizeof(volpath2
) / sizeof(WCHAR
)))
1692 for (unsigned int i
= 0; i
< args
.size() - 1; i
++) {
1693 WIN32_FIND_DATAW ffd
;
1695 fff_handle h
= FindFirstFileW(args
[i
].c_str(), &ffd
);
1696 if (h
!= INVALID_HANDLE_VALUE
) {
1697 WCHAR volpath1
[MAX_PATH
];
1698 wstring path
= args
[i
];
1699 size_t found
= path
.rfind(L
"\\");
1701 if (found
== wstring::npos
)
1704 path
= path
.substr(0, found
);
1708 if (get_volume_path_parent(path
.c_str(), volpath1
, sizeof(volpath1
) / sizeof(WCHAR
))) {
1709 if (!wcscmp(volpath1
, volpath2
)) {
1712 reflink_copy2(path
+ ffd
.cFileName
, destdir
, dest_is_dir
? ffd
.cFileName
: destname
);
1713 } catch (const exception
& e
) {
1714 cerr
<< "Error: " << e
.what() << endl
;
1716 } while (FindNextFileW(h
, &ffd
));