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
= 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 btrfs_create_snapshot
* bcs
;
467 WIN32_FIND_DATAW wfd
;
471 path_remove_file(parpath
);
474 path_strip_path(subvolname
);
476 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);
478 if (h2
== INVALID_HANDLE_VALUE
)
479 throw last_error(GetLastError());
481 if (!load_string(module
, IDS_SNAPSHOT_FILENAME
, temp1
))
482 throw last_error(GetLastError());
486 wstring_sprintf(name
, temp1
, subvolname
.c_str(), time
.wYear
, time
.wMonth
, time
.wDay
);
489 searchpath
= parpath
+ L
"\\" + name
;
491 fff_handle fff
= FindFirstFileW(searchpath
.c_str(), &wfd
);
493 if (fff
!= INVALID_HANDLE_VALUE
) {
498 name
= nameorig
+ L
" (" + to_wstring(num
) + L
")";
503 swprintf(buffer
, L
"%d", num
);
504 name
= nameorig
+ L
" (" + buffer
+ L
")";
507 searchpath
= parpath
+ L
"\\" + name
;
509 fff
= FindFirstFileW(searchpath
.c_str(), &wfd
);
511 } while (fff
!= INVALID_HANDLE_VALUE
);
514 namelen
= name
.length() * sizeof(WCHAR
);
516 bcs
= (btrfs_create_snapshot
*)malloc(sizeof(btrfs_create_snapshot
) - 1 + namelen
);
517 bcs
->readonly
= false;
520 bcs
->namelen
= (uint16_t)namelen
;
521 memcpy(bcs
->name
, name
.c_str(), namelen
);
523 Status
= NtFsControlFile(h2
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_CREATE_SNAPSHOT
, bcs
, sizeof(btrfs_create_snapshot
) - 1 + namelen
, nullptr, 0);
525 if (!NT_SUCCESS(Status
))
526 throw ntstatus_error(Status
);
529 throw last_error(GetLastError());
532 static uint64_t __inline
sector_align(uint64_t n
, uint64_t a
) {
534 n
= (n
+ a
) & ~(a
- 1);
539 void BtrfsContextMenu::reflink_copy(HWND hwnd
, const WCHAR
* fn
, const WCHAR
* dir
) {
540 win_handle source
, dest
;
541 WCHAR
* name
, volpath1
[255], volpath2
[255];
542 wstring dirw
, newpath
;
544 FILETIME atime
, mtime
;
545 btrfs_inode_info2 bii
;
546 btrfs_set_inode_info bsii
;
549 IO_STATUS_BLOCK iosb
;
550 btrfs_set_xattr bsxa
;
552 // Thanks to 0xbadfca11, whose version of reflink for Windows provided a few pointers on what
553 // to do here - https://github.com/0xbadfca11/reflink
555 name
= PathFindFileNameW(fn
);
559 if (dir
[0] != 0 && dir
[wcslen(dir
) - 1] != '\\')
565 if (!get_volume_path_parent(fn
, volpath1
, sizeof(volpath1
) / sizeof(WCHAR
)))
566 throw last_error(GetLastError());
568 if (!GetVolumePathNameW(dir
, volpath2
, sizeof(volpath2
) / sizeof(WCHAR
)))
569 throw last_error(GetLastError());
571 if (wcscmp(volpath1
, volpath2
)) // different filesystems
572 throw string_error(IDS_CANT_REFLINK_DIFFERENT_FS
);
574 source
= CreateFileW(fn
, GENERIC_READ
| FILE_TRAVERSE
, FILE_SHARE_READ
, nullptr, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
| FILE_OPEN_REPARSE_POINT
, nullptr);
575 if (source
== INVALID_HANDLE_VALUE
)
576 throw last_error(GetLastError());
578 Status
= NtFsControlFile(source
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_INODE_INFO
, nullptr, 0, &bii
, sizeof(btrfs_inode_info2
));
579 if (!NT_SUCCESS(Status
))
580 throw ntstatus_error(Status
);
582 // if subvol, do snapshot instead
583 if (bii
.inode
== SUBVOL_ROOT_INODE
) {
584 btrfs_create_snapshot
* bcs
;
586 wstring destname
, search
;
587 WIN32_FIND_DATAW wfd
;
590 dirh
= CreateFileW(dir
, FILE_ADD_SUBDIRECTORY
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, nullptr, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, nullptr);
591 if (dirh
== INVALID_HANDLE_VALUE
)
592 throw last_error(GetLastError());
598 fff_handle fff
= FindFirstFileW(search
.c_str(), &wfd
);
600 if (fff
!= INVALID_HANDLE_VALUE
) {
610 search
= dirw
+ destname
;
612 fff
= FindFirstFileW(search
.c_str(), &wfd
);
614 } while (fff
!= INVALID_HANDLE_VALUE
);
617 bcs
= (btrfs_create_snapshot
*)malloc(sizeof(btrfs_create_snapshot
) - sizeof(WCHAR
) + (destname
.length() * sizeof(WCHAR
)));
618 bcs
->subvol
= source
;
619 bcs
->namelen
= (uint16_t)(destname
.length() * sizeof(WCHAR
));
620 memcpy(bcs
->name
, destname
.c_str(), destname
.length() * sizeof(WCHAR
));
622 Status
= NtFsControlFile(dirh
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_CREATE_SNAPSHOT
, bcs
, sizeof(btrfs_create_snapshot
) - sizeof(WCHAR
) + bcs
->namelen
, nullptr, 0);
626 if (!NT_SUCCESS(Status
))
627 throw ntstatus_error(Status
);
632 if (!GetFileInformationByHandleEx(source
, FileBasicInfo
, &fbi
, sizeof(FILE_BASIC_INFO
)))
633 throw last_error(GetLastError());
635 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 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
, 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 WIN32_FIND_STREAM_DATA fsd
;
764 if (fbi
.FileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) {
769 if (!DeviceIoControl(source
, FSCTL_GET_REPARSE_POINT
, nullptr, 0, &rh
, sizeof(reparse_header
), &bytesret
, nullptr)) {
770 if (GetLastError() != ERROR_MORE_DATA
)
771 throw last_error(GetLastError());
774 rplen
= sizeof(reparse_header
) + rh
.ReparseDataLength
;
775 rp
= (uint8_t*)malloc(rplen
);
777 if (!DeviceIoControl(source
, FSCTL_GET_REPARSE_POINT
, nullptr, 0, rp
, rplen
, &bytesret
, nullptr))
778 throw last_error(GetLastError());
780 if (!DeviceIoControl(dest
, FSCTL_SET_REPARSE_POINT
, rp
, rplen
, nullptr, 0, &bytesret
, nullptr))
781 throw last_error(GetLastError());
785 FILE_STANDARD_INFO fsi
;
786 FILE_END_OF_FILE_INFO feofi
;
787 FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib
;
788 FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib
;
789 DUPLICATE_EXTENTS_DATA ded
;
790 uint64_t offset
, alloc_size
;
793 if (!GetFileInformationByHandleEx(source
, FileStandardInfo
, &fsi
, sizeof(FILE_STANDARD_INFO
)))
794 throw last_error(GetLastError());
796 if (!DeviceIoControl(source
, FSCTL_GET_INTEGRITY_INFORMATION
, nullptr, 0, &fgiib
, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
), &bytesret
, nullptr))
797 throw last_error(GetLastError());
799 if (fbi
.FileAttributes
& FILE_ATTRIBUTE_SPARSE_FILE
) {
800 if (!DeviceIoControl(dest
, FSCTL_SET_SPARSE
, nullptr, 0, nullptr, 0, &bytesret
, nullptr))
801 throw last_error(GetLastError());
804 fsiib
.ChecksumAlgorithm
= fgiib
.ChecksumAlgorithm
;
806 fsiib
.Flags
= fgiib
.Flags
;
807 if (!DeviceIoControl(dest
, FSCTL_SET_INTEGRITY_INFORMATION
, &fsiib
, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER
), nullptr, 0, &bytesret
, nullptr))
808 throw last_error(GetLastError());
810 feofi
.EndOfFile
= fsi
.EndOfFile
;
811 if (!SetFileInformationByHandle(dest
, FileEndOfFileInfo
, &feofi
, sizeof(FILE_END_OF_FILE_INFO
)))
812 throw last_error(GetLastError());
814 ded
.FileHandle
= source
;
815 maxdup
= 0xffffffff - fgiib
.ClusterSizeInBytes
+ 1;
817 alloc_size
= sector_align(fsi
.EndOfFile
.QuadPart
, fgiib
.ClusterSizeInBytes
);
820 while (offset
< alloc_size
) {
821 ded
.SourceFileOffset
.QuadPart
= ded
.TargetFileOffset
.QuadPart
= offset
;
822 ded
.ByteCount
.QuadPart
= maxdup
< (alloc_size
- offset
) ? maxdup
: (alloc_size
- offset
);
823 if (!DeviceIoControl(dest
, FSCTL_DUPLICATE_EXTENTS_TO_FILE
, &ded
, sizeof(DUPLICATE_EXTENTS_DATA
), nullptr, 0, &bytesret
, nullptr))
824 throw last_error(GetLastError());
826 offset
+= ded
.ByteCount
.QuadPart
;
830 fff_handle h
= FindFirstStreamW(fn
, FindStreamInfoStandard
, &fsd
, 0);
831 if (h
!= INVALID_HANDLE_VALUE
) {
835 sn
= fsd
.cStreamName
;
837 if (sn
!= L
"::$DATA" && sn
.length() > 6 && sn
.substr(sn
.length() - 6, 6) == L
":$DATA") {
839 uint8_t* data
= nullptr;
840 uint16_t stream_size
= (uint16_t)fsd
.StreamSize
.QuadPart
;
842 if (fsd
.StreamSize
.QuadPart
> 0) {
848 stream
= CreateFileW(fn2
.c_str(), GENERIC_READ
, 0, nullptr, OPEN_EXISTING
, 0, nullptr);
850 if (stream
== INVALID_HANDLE_VALUE
)
851 throw last_error(GetLastError());
853 // We can get away with this because our streams are guaranteed to be below 64 KB -
854 // don't do this on NTFS!
855 data
= (uint8_t*)malloc(stream_size
);
857 if (!ReadFile(stream
, data
, stream_size
, &bytesret
, nullptr)) {
859 throw last_error(GetLastError());
863 stream
= CreateFileW((newpath
+ sn
).c_str(), GENERIC_READ
| GENERIC_WRITE
| DELETE
, 0, nullptr, CREATE_NEW
, 0, nullptr);
865 if (stream
== INVALID_HANDLE_VALUE
) {
866 if (data
) free(data
);
867 throw last_error(GetLastError());
871 if (!WriteFile(stream
, data
, stream_size
, &bytesret
, nullptr)) {
873 throw last_error(GetLastError());
879 } while (FindNextStreamW(h
, &fsd
));
883 atime
.dwLowDateTime
= fbi
.LastAccessTime
.LowPart
;
884 atime
.dwHighDateTime
= fbi
.LastAccessTime
.HighPart
;
885 mtime
.dwLowDateTime
= fbi
.LastWriteTime
.LowPart
;
886 mtime
.dwHighDateTime
= fbi
.LastWriteTime
.HighPart
;
887 SetFileTime(dest
, nullptr, &atime
, &mtime
);
889 Status
= NtFsControlFile(source
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_XATTRS
, nullptr, 0, &bsxa
, sizeof(btrfs_set_xattr
));
891 if (Status
== STATUS_BUFFER_OVERFLOW
|| (NT_SUCCESS(Status
) && bsxa
.valuelen
> 0)) {
893 btrfs_set_xattr
*xa
= nullptr, *xa2
;
899 xa
= (btrfs_set_xattr
*)malloc(xalen
);
901 Status
= NtFsControlFile(source
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_XATTRS
, nullptr, 0, xa
, xalen
);
902 } while (Status
== STATUS_BUFFER_OVERFLOW
);
904 if (!NT_SUCCESS(Status
)) {
906 throw ntstatus_error(Status
);
910 while (xa2
->valuelen
> 0) {
911 Status
= NtFsControlFile(dest
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_SET_XATTR
, xa2
,
912 offsetof(btrfs_set_xattr
, data
[0]) + xa2
->namelen
+ xa2
->valuelen
, nullptr, 0);
913 if (!NT_SUCCESS(Status
)) {
915 throw ntstatus_error(Status
);
917 xa2
= (btrfs_set_xattr
*)&xa2
->data
[xa2
->namelen
+ xa2
->valuelen
];
921 } else if (!NT_SUCCESS(Status
))
922 throw ntstatus_error(Status
);
924 FILE_DISPOSITION_INFO fdi
;
926 fdi
.DeleteFile
= true;
927 if (!SetFileInformationByHandle(dest
, FileDispositionInfo
, &fdi
, sizeof(FILE_DISPOSITION_INFO
)))
928 throw last_error(GetLastError());
934 HRESULT __stdcall
BtrfsContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO picia
) {
935 LPCMINVOKECOMMANDINFOEX pici
= (LPCMINVOKECOMMANDINFOEX
)picia
;
942 if ((IS_INTRESOURCE(pici
->lpVerb
) && allow_snapshot
&& pici
->lpVerb
== 0) || (!IS_INTRESOURCE(pici
->lpVerb
) && !strcmp(pici
->lpVerb
, SNAPSHOT_VERBA
))) {
949 num_files
= DragQueryFileW((HDROP
)stgm
.hGlobal
, 0xFFFFFFFF, nullptr, 0);
954 for (i
= 0; i
< num_files
; i
++) {
955 if (DragQueryFileW((HDROP
)stgm
.hGlobal
, i
, fn
, sizeof(fn
) / sizeof(WCHAR
))) {
956 create_snapshot(pici
->hwnd
, fn
);
961 } else if ((IS_INTRESOURCE(pici
->lpVerb
) && ((allow_snapshot
&& (ULONG_PTR
)pici
->lpVerb
== 1) || (!allow_snapshot
&& (ULONG_PTR
)pici
->lpVerb
== 0))) ||
962 (!IS_INTRESOURCE(pici
->lpVerb
) && !strcmp(pici
->lpVerb
, SEND_VERBA
))) {
964 WCHAR dll
[MAX_PATH
], fn
[MAX_PATH
];
966 SHELLEXECUTEINFOW sei
;
968 GetModuleFileNameW(module
, dll
, sizeof(dll
) / sizeof(WCHAR
));
973 num_files
= DragQueryFileW((HDROP
)stgm
.hGlobal
, 0xFFFFFFFF, nullptr, 0);
978 for (i
= 0; i
< num_files
; i
++) {
979 if (DragQueryFileW((HDROP
)stgm
.hGlobal
, i
, fn
, sizeof(fn
) / sizeof(WCHAR
))) {
982 t
+= L
"\",SendSubvolGUI ";
985 RtlZeroMemory(&sei
, sizeof(sei
));
987 sei
.cbSize
= sizeof(sei
);
988 sei
.hwnd
= pici
->hwnd
;
989 sei
.lpVerb
= L
"runas";
990 sei
.lpFile
= L
"rundll32.exe";
991 sei
.lpParameters
= t
.c_str();
993 sei
.fMask
= SEE_MASK_NOCLOSEPROCESS
;
995 if (!ShellExecuteExW(&sei
))
996 throw last_error(GetLastError());
998 WaitForSingleObject(sei
.hProcess
, INFINITE
);
999 CloseHandle(sei
.hProcess
);
1006 if ((IS_INTRESOURCE(pici
->lpVerb
) && (ULONG_PTR
)pici
->lpVerb
== 0) || (!IS_INTRESOURCE(pici
->lpVerb
) && !strcmp(pici
->lpVerb
, NEW_SUBVOL_VERBA
))) {
1008 IO_STATUS_BLOCK iosb
;
1011 wstring name
, nameorig
, searchpath
;
1012 btrfs_create_subvol
* bcs
;
1013 WIN32_FIND_DATAW wfd
;
1015 if (!load_string(module
, IDS_NEW_SUBVOL_FILENAME
, name
))
1016 throw last_error(GetLastError());
1018 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);
1020 if (h
== INVALID_HANDLE_VALUE
)
1021 throw last_error(GetLastError());
1023 searchpath
= path
+ L
"\\" + name
;
1027 fff_handle fff
= FindFirstFileW(searchpath
.c_str(), &wfd
);
1029 if (fff
!= INVALID_HANDLE_VALUE
) {
1034 name
= nameorig
+ L
" (" + to_wstring(num
) + L
")";
1039 swprintf(buffer
, L
"%d", num
);
1040 name
= nameorig
+ L
" (" + buffer
+ L
")";
1043 searchpath
= path
+ L
"\\" + name
;
1045 fff
= FindFirstFileW(searchpath
.c_str(), &wfd
);
1047 } while (fff
!= INVALID_HANDLE_VALUE
);
1051 bcslen
= offsetof(btrfs_create_subvol
, name
[0]) + (name
.length() * sizeof(WCHAR
));
1052 bcs
= (btrfs_create_subvol
*)malloc(bcslen
);
1054 bcs
->readonly
= false;
1056 bcs
->namelen
= (uint16_t)(name
.length() * sizeof(WCHAR
));
1057 memcpy(bcs
->name
, name
.c_str(), name
.length() * sizeof(WCHAR
));
1059 Status
= NtFsControlFile(h
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_CREATE_SUBVOL
, bcs
, bcslen
, nullptr, 0);
1063 if (!NT_SUCCESS(Status
))
1064 throw ntstatus_error(Status
);
1067 } else if ((IS_INTRESOURCE(pici
->lpVerb
) && (ULONG_PTR
)pici
->lpVerb
== 1) || (!IS_INTRESOURCE(pici
->lpVerb
) && !strcmp(pici
->lpVerb
, RECV_VERBA
))) {
1068 WCHAR dll
[MAX_PATH
];
1070 SHELLEXECUTEINFOW sei
;
1072 GetModuleFileNameW(module
, dll
, sizeof(dll
) / sizeof(WCHAR
));
1076 t
+= L
"\",RecvSubvolGUI ";
1079 RtlZeroMemory(&sei
, sizeof(sei
));
1081 sei
.cbSize
= sizeof(sei
);
1082 sei
.hwnd
= pici
->hwnd
;
1083 sei
.lpVerb
= L
"runas";
1084 sei
.lpFile
= L
"rundll32.exe";
1085 sei
.lpParameters
= t
.c_str();
1086 sei
.nShow
= SW_SHOW
;
1087 sei
.fMask
= SEE_MASK_NOCLOSEPROCESS
;
1089 if (!ShellExecuteExW(&sei
))
1090 throw last_error(GetLastError());
1092 WaitForSingleObject(sei
.hProcess
, INFINITE
);
1093 CloseHandle(sei
.hProcess
);
1096 } else if ((IS_INTRESOURCE(pici
->lpVerb
) && (ULONG_PTR
)pici
->lpVerb
== 2) || (!IS_INTRESOURCE(pici
->lpVerb
) && !strcmp(pici
->lpVerb
, REFLINK_VERBA
))) {
1099 if (!IsClipboardFormatAvailable(CF_HDROP
))
1102 if (!OpenClipboard(pici
->hwnd
))
1103 throw last_error(GetLastError());
1106 hdrop
= (HDROP
)GetClipboardData(CF_HDROP
);
1111 lh
= GlobalLock(hdrop
);
1118 num_files
= DragQueryFileW(hdrop
, 0xFFFFFFFF, nullptr, 0);
1120 for (i
= 0; i
< num_files
; i
++) {
1121 if (DragQueryFileW(hdrop
, i
, fn
, sizeof(fn
) / sizeof(WCHAR
))) {
1122 reflink_copy(pici
->hwnd
, fn
, pici
->lpDirectoryW
);
1143 } catch (const exception
& e
) {
1144 error_message(pici
->hwnd
, e
.what());
1150 HRESULT __stdcall
BtrfsContextMenu::GetCommandString(UINT_PTR idCmd
, UINT uFlags
, UINT
* pwReserved
, LPSTR pszName
, UINT cchMax
) {
1152 return E_INVALIDARG
;
1155 return E_INVALIDARG
;
1161 if (LoadStringA(module
, IDS_CREATE_SNAPSHOT_HELP_TEXT
, pszName
, cchMax
))
1167 if (LoadStringW(module
, IDS_CREATE_SNAPSHOT_HELP_TEXT
, (LPWSTR
)pszName
, cchMax
))
1177 return StringCchCopyA(pszName
, cchMax
, SNAPSHOT_VERBA
);
1180 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, SNAPSHOT_VERBW
);
1183 return E_INVALIDARG
;
1185 } else if (idCmd
== 1) {
1188 if (LoadStringA(module
, IDS_SEND_SUBVOL_HELP
, pszName
, cchMax
))
1194 if (LoadStringW(module
, IDS_SEND_SUBVOL_HELP
, (LPWSTR
)pszName
, cchMax
))
1204 return StringCchCopyA(pszName
, cchMax
, SEND_VERBA
);
1207 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, SEND_VERBW
);
1210 return E_INVALIDARG
;
1213 return E_INVALIDARG
;
1218 if (LoadStringA(module
, IDS_NEW_SUBVOL_HELP_TEXT
, pszName
, cchMax
))
1224 if (LoadStringW(module
, IDS_NEW_SUBVOL_HELP_TEXT
, (LPWSTR
)pszName
, cchMax
))
1234 return StringCchCopyA(pszName
, cchMax
, NEW_SUBVOL_VERBA
);
1237 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, NEW_SUBVOL_VERBW
);
1240 return E_INVALIDARG
;
1242 } else if (idCmd
== 1) {
1245 if (LoadStringA(module
, IDS_RECV_SUBVOL_HELP
, pszName
, cchMax
))
1251 if (LoadStringW(module
, IDS_RECV_SUBVOL_HELP
, (LPWSTR
)pszName
, cchMax
))
1261 return StringCchCopyA(pszName
, cchMax
, RECV_VERBA
);
1264 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, RECV_VERBW
);
1267 return E_INVALIDARG
;
1269 } else if (idCmd
== 2) {
1272 if (LoadStringA(module
, IDS_REFLINK_PASTE_HELP
, pszName
, cchMax
))
1278 if (LoadStringW(module
, IDS_REFLINK_PASTE_HELP
, (LPWSTR
)pszName
, cchMax
))
1288 return StringCchCopyA(pszName
, cchMax
, REFLINK_VERBA
);
1291 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, REFLINK_VERBW
);
1294 return E_INVALIDARG
;
1297 return E_INVALIDARG
;
1301 static void reflink_copy2(const wstring
& srcfn
, const wstring
& destdir
, const wstring
& destname
) {
1302 win_handle source
, dest
;
1303 FILE_BASIC_INFO fbi
;
1304 FILETIME atime
, mtime
;
1305 btrfs_inode_info2 bii
;
1306 btrfs_set_inode_info bsii
;
1309 IO_STATUS_BLOCK iosb
;
1310 btrfs_set_xattr bsxa
;
1312 source
= CreateFileW(srcfn
.c_str(), GENERIC_READ
| FILE_TRAVERSE
, FILE_SHARE_READ
, nullptr, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
| FILE_OPEN_REPARSE_POINT
, nullptr);
1313 if (source
== INVALID_HANDLE_VALUE
)
1314 throw last_error(GetLastError());
1316 Status
= NtFsControlFile(source
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_INODE_INFO
, nullptr, 0, &bii
, sizeof(btrfs_inode_info2
));
1317 if (!NT_SUCCESS(Status
))
1318 throw ntstatus_error(Status
);
1320 // if subvol, do snapshot instead
1321 if (bii
.inode
== SUBVOL_ROOT_INODE
) {
1323 btrfs_create_snapshot
* bcs
;
1326 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);
1327 if (dirh
== INVALID_HANDLE_VALUE
)
1328 throw last_error(GetLastError());
1330 bcslen
= offsetof(btrfs_create_snapshot
, name
[0]) + (destname
.length() * sizeof(WCHAR
));
1331 bcs
= (btrfs_create_snapshot
*)malloc(bcslen
);
1332 bcs
->subvol
= source
;
1333 bcs
->namelen
= (uint16_t)(destname
.length() * sizeof(WCHAR
));
1334 memcpy(bcs
->name
, destname
.c_str(), destname
.length() * sizeof(WCHAR
));
1336 Status
= NtFsControlFile(dirh
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_CREATE_SNAPSHOT
, bcs
, bcslen
, nullptr, 0);
1337 if (!NT_SUCCESS(Status
)) {
1339 throw ntstatus_error(Status
);
1347 if (!GetFileInformationByHandleEx(source
, FileBasicInfo
, &fbi
, sizeof(FILE_BASIC_INFO
)))
1348 throw last_error(GetLastError());
1350 if (bii
.type
== BTRFS_TYPE_CHARDEV
|| bii
.type
== BTRFS_TYPE_BLOCKDEV
|| bii
.type
== BTRFS_TYPE_FIFO
|| bii
.type
== BTRFS_TYPE_SOCKET
) {
1355 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);
1356 if (dirh
== INVALID_HANDLE_VALUE
)
1357 throw last_error(GetLastError());
1359 bmnsize
= offsetof(btrfs_mknod
, name
[0]) + (destname
.length() * sizeof(WCHAR
));
1360 bmn
= (btrfs_mknod
*)malloc(bmnsize
);
1363 bmn
->type
= bii
.type
;
1364 bmn
->st_rdev
= bii
.st_rdev
;
1365 bmn
->namelen
= (uint16_t)(destname
.length() * sizeof(WCHAR
));
1366 memcpy(bmn
->name
, destname
.c_str(), bmn
->namelen
);
1368 Status
= NtFsControlFile(dirh
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_MKNOD
, bmn
, bmnsize
, nullptr, 0);
1369 if (!NT_SUCCESS(Status
)) {
1371 throw ntstatus_error(Status
);
1376 dest
= CreateFileW((destdir
+ destname
).c_str(), GENERIC_READ
| GENERIC_WRITE
| DELETE
, 0, nullptr, OPEN_EXISTING
, 0, nullptr);
1377 } else if (fbi
.FileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
1378 if (CreateDirectoryExW(srcfn
.c_str(), (destdir
+ destname
).c_str(), nullptr))
1379 dest
= CreateFileW((destdir
+ destname
).c_str(), GENERIC_READ
| GENERIC_WRITE
| DELETE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
1380 nullptr, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, nullptr);
1382 dest
= INVALID_HANDLE_VALUE
;
1384 dest
= CreateFileW((destdir
+ destname
).c_str(), GENERIC_READ
| GENERIC_WRITE
| DELETE
, 0, nullptr, CREATE_NEW
, 0, source
);
1386 if (dest
== INVALID_HANDLE_VALUE
)
1387 throw last_error(GetLastError());
1389 memset(&bsii
, 0, sizeof(btrfs_set_inode_info
));
1391 bsii
.flags_changed
= true;
1392 bsii
.flags
= bii
.flags
;
1394 if (bii
.flags
& BTRFS_INODE_COMPRESS
) {
1395 bsii
.compression_type_changed
= true;
1396 bsii
.compression_type
= bii
.compression_type
;
1400 Status
= NtFsControlFile(dest
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_SET_INODE_INFO
, &bsii
, sizeof(btrfs_set_inode_info
), nullptr, 0);
1401 if (!NT_SUCCESS(Status
))
1402 throw ntstatus_error(Status
);
1404 if (fbi
.FileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
1405 if (!(fbi
.FileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
)) {
1406 WIN32_FIND_DATAW fff
;
1412 fff_handle h
= FindFirstFileW(qs
.c_str(), &fff
);
1413 if (h
!= INVALID_HANDLE_VALUE
) {
1417 if (fff
.cFileName
[0] == '.' && (fff
.cFileName
[1] == 0 || (fff
.cFileName
[1] == '.' && fff
.cFileName
[2] == 0)))
1422 fn2
+= fff
.cFileName
;
1424 reflink_copy2(fn2
, destdir
+ destname
+ L
"\\", fff
.cFileName
);
1425 } while (FindNextFileW(h
, &fff
));
1429 // CreateDirectoryExW also copies streams, no need to do it here
1431 WIN32_FIND_STREAM_DATA fsd
;
1433 if (fbi
.FileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) {
1438 if (!DeviceIoControl(source
, FSCTL_GET_REPARSE_POINT
, nullptr, 0, &rh
, sizeof(reparse_header
), &bytesret
, nullptr)) {
1439 if (GetLastError() != ERROR_MORE_DATA
)
1440 throw last_error(GetLastError());
1443 rplen
= sizeof(reparse_header
) + rh
.ReparseDataLength
;
1444 rp
= (uint8_t*)malloc(rplen
);
1447 if (!DeviceIoControl(source
, FSCTL_GET_REPARSE_POINT
, nullptr, 0, rp
, rplen
, &bytesret
, nullptr))
1448 throw last_error(GetLastError());
1450 if (!DeviceIoControl(dest
, FSCTL_SET_REPARSE_POINT
, rp
, rplen
, nullptr, 0, &bytesret
, nullptr))
1451 throw last_error(GetLastError());
1459 FILE_STANDARD_INFO fsi
;
1460 FILE_END_OF_FILE_INFO feofi
;
1461 FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib
;
1462 FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib
;
1463 DUPLICATE_EXTENTS_DATA ded
;
1464 uint64_t offset
, alloc_size
;
1467 if (!GetFileInformationByHandleEx(source
, FileStandardInfo
, &fsi
, sizeof(FILE_STANDARD_INFO
)))
1468 throw last_error(GetLastError());
1470 if (!DeviceIoControl(source
, FSCTL_GET_INTEGRITY_INFORMATION
, nullptr, 0, &fgiib
, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
), &bytesret
, nullptr))
1471 throw last_error(GetLastError());
1473 if (fbi
.FileAttributes
& FILE_ATTRIBUTE_SPARSE_FILE
) {
1474 if (!DeviceIoControl(dest
, FSCTL_SET_SPARSE
, nullptr, 0, nullptr, 0, &bytesret
, nullptr))
1475 throw last_error(GetLastError());
1478 fsiib
.ChecksumAlgorithm
= fgiib
.ChecksumAlgorithm
;
1480 fsiib
.Flags
= fgiib
.Flags
;
1481 if (!DeviceIoControl(dest
, FSCTL_SET_INTEGRITY_INFORMATION
, &fsiib
, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER
), nullptr, 0, &bytesret
, nullptr))
1482 throw last_error(GetLastError());
1484 feofi
.EndOfFile
= fsi
.EndOfFile
;
1485 if (!SetFileInformationByHandle(dest
, FileEndOfFileInfo
, &feofi
, sizeof(FILE_END_OF_FILE_INFO
)))
1486 throw last_error(GetLastError());
1488 ded
.FileHandle
= source
;
1489 maxdup
= 0xffffffff - fgiib
.ClusterSizeInBytes
+ 1;
1491 alloc_size
= sector_align(fsi
.EndOfFile
.QuadPart
, fgiib
.ClusterSizeInBytes
);
1494 while (offset
< alloc_size
) {
1495 ded
.SourceFileOffset
.QuadPart
= ded
.TargetFileOffset
.QuadPart
= offset
;
1496 ded
.ByteCount
.QuadPart
= maxdup
< (alloc_size
- offset
) ? maxdup
: (alloc_size
- offset
);
1497 if (!DeviceIoControl(dest
, FSCTL_DUPLICATE_EXTENTS_TO_FILE
, &ded
, sizeof(DUPLICATE_EXTENTS_DATA
), nullptr, 0, &bytesret
, nullptr))
1498 throw last_error(GetLastError());
1500 offset
+= ded
.ByteCount
.QuadPart
;
1504 fff_handle h
= FindFirstStreamW(srcfn
.c_str(), FindStreamInfoStandard
, &fsd
, 0);
1505 if (h
!= INVALID_HANDLE_VALUE
) {
1509 sn
= fsd
.cStreamName
;
1511 if (sn
!= L
"::$DATA" && sn
.length() > 6 && sn
.substr(sn
.length() - 6, 6) == L
":$DATA") {
1513 uint8_t* data
= nullptr;
1515 if (fsd
.StreamSize
.QuadPart
> 0) {
1517 uint16_t stream_size
= (uint16_t)fsd
.StreamSize
.QuadPart
;
1522 stream
= CreateFileW(fn2
.c_str(), GENERIC_READ
, 0, nullptr, OPEN_EXISTING
, 0, nullptr);
1524 if (stream
== INVALID_HANDLE_VALUE
)
1525 throw last_error(GetLastError());
1527 // We can get away with this because our streams are guaranteed to be below 64 KB -
1528 // don't do this on NTFS!
1529 data
= (uint8_t*)malloc(stream_size
);
1531 if (!ReadFile(stream
, data
, stream_size
, &bytesret
, nullptr)) {
1533 throw last_error(GetLastError());
1537 stream
= CreateFileW((destdir
+ destname
+ sn
).c_str(), GENERIC_READ
| GENERIC_WRITE
| DELETE
, 0, nullptr, CREATE_NEW
, 0, nullptr);
1539 if (stream
== INVALID_HANDLE_VALUE
) {
1540 if (data
) free(data
);
1541 throw last_error(GetLastError());
1545 if (!WriteFile(stream
, data
, (uint32_t)fsd
.StreamSize
.QuadPart
, &bytesret
, nullptr)) {
1547 throw last_error(GetLastError());
1553 } while (FindNextStreamW(h
, &fsd
));
1557 atime
.dwLowDateTime
= fbi
.LastAccessTime
.LowPart
;
1558 atime
.dwHighDateTime
= fbi
.LastAccessTime
.HighPart
;
1559 mtime
.dwLowDateTime
= fbi
.LastWriteTime
.LowPart
;
1560 mtime
.dwHighDateTime
= fbi
.LastWriteTime
.HighPart
;
1561 SetFileTime(dest
, nullptr, &atime
, &mtime
);
1563 Status
= NtFsControlFile(source
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_XATTRS
, nullptr, 0, &bsxa
, sizeof(btrfs_set_xattr
));
1565 if (Status
== STATUS_BUFFER_OVERFLOW
|| (NT_SUCCESS(Status
) && bsxa
.valuelen
> 0)) {
1567 btrfs_set_xattr
*xa
= nullptr, *xa2
;
1573 xa
= (btrfs_set_xattr
*)malloc(xalen
);
1575 Status
= NtFsControlFile(source
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_GET_XATTRS
, nullptr, 0, xa
, xalen
);
1576 } while (Status
== STATUS_BUFFER_OVERFLOW
);
1578 if (!NT_SUCCESS(Status
)) {
1580 throw ntstatus_error(Status
);
1584 while (xa2
->valuelen
> 0) {
1585 Status
= NtFsControlFile(dest
, nullptr, nullptr, nullptr, &iosb
, FSCTL_BTRFS_SET_XATTR
, xa2
,
1586 offsetof(btrfs_set_xattr
, data
[0]) + xa2
->namelen
+ xa2
->valuelen
, nullptr, 0);
1587 if (!NT_SUCCESS(Status
)) {
1589 throw ntstatus_error(Status
);
1591 xa2
= (btrfs_set_xattr
*)&xa2
->data
[xa2
->namelen
+ xa2
->valuelen
];
1595 } else if (!NT_SUCCESS(Status
))
1596 throw ntstatus_error(Status
);
1598 FILE_DISPOSITION_INFO fdi
;
1600 fdi
.DeleteFile
= true;
1601 SetFileInformationByHandle(dest
, FileDispositionInfo
, &fdi
, sizeof(FILE_DISPOSITION_INFO
));
1611 void CALLBACK
ReflinkCopyW(HWND hwnd
, HINSTANCE hinst
, LPWSTR lpszCmdLine
, int nCmdShow
) {
1612 vector
<wstring
> args
;
1614 command_line_to_args(lpszCmdLine
, args
);
1616 if (args
.size() >= 2) {
1617 bool dest_is_dir
= false;
1618 wstring dest
= args
[args
.size() - 1], destdir
, destname
;
1619 WCHAR volpath2
[MAX_PATH
];
1622 win_handle destdirh
= CreateFileW(dest
.c_str(), FILE_TRAVERSE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
1623 nullptr, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, nullptr);
1625 if (destdirh
!= INVALID_HANDLE_VALUE
) {
1626 BY_HANDLE_FILE_INFORMATION bhfi
;
1628 if (GetFileInformationByHandle(destdirh
, &bhfi
) && bhfi
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
1632 if (destdir
.substr(destdir
.length() - 1, 1) != L
"\\")
1639 size_t found
= dest
.rfind(L
"\\");
1641 if (found
== wstring::npos
) {
1645 destdir
= dest
.substr(0, found
);
1646 destname
= dest
.substr(found
+ 1);
1650 if (!GetVolumePathNameW(dest
.c_str(), volpath2
, sizeof(volpath2
) / sizeof(WCHAR
)))
1653 for (unsigned int i
= 0; i
< args
.size() - 1; i
++) {
1654 WIN32_FIND_DATAW ffd
;
1656 fff_handle h
= FindFirstFileW(args
[i
].c_str(), &ffd
);
1657 if (h
!= INVALID_HANDLE_VALUE
) {
1658 WCHAR volpath1
[MAX_PATH
];
1659 wstring path
= args
[i
];
1660 size_t found
= path
.rfind(L
"\\");
1662 if (found
== wstring::npos
)
1665 path
= path
.substr(0, found
);
1669 if (get_volume_path_parent(path
.c_str(), volpath1
, sizeof(volpath1
) / sizeof(WCHAR
))) {
1670 if (!wcscmp(volpath1
, volpath2
)) {
1673 reflink_copy2(path
+ ffd
.cFileName
, destdir
, dest_is_dir
? ffd
.cFileName
: destname
);
1674 } catch (const exception
& e
) {
1675 cerr
<< "Error: " << e
.what() << endl
;
1677 } while (FindNextFileW(h
, &ffd
));