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 }