[SHELLBTRFS] Addendum to 1725ddf
[reactos.git] / dll / shellext / shellbtrfs / contextmenu.cpp
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 #ifndef __REACTOS__
837 memset(streambuf.data(), 0, streambufsize);
838
839 Status = NtQueryInformationFile(source, &iosb, streambuf.data(), streambufsize, FileStreamInformation);
840 #else
841 memset(&streambuf[0], 0, streambufsize);
842
843 Status = NtQueryInformationFile(source, &iosb, &streambuf[0], streambufsize, FileStreamInformation);
844 #endif
845 } while (Status == STATUS_BUFFER_OVERFLOW);
846
847 if (!NT_SUCCESS(Status))
848 throw ntstatus_error(Status);
849
850 #ifndef __REACTOS__
851 auto fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(streambuf.data());
852 #else
853 auto fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(&streambuf[0]);
854 #endif
855
856 while (true) {
857 if (fsi->StreamNameLength > 0) {
858 wstring sn = wstring(fsi->StreamName, fsi->StreamNameLength / sizeof(WCHAR));
859
860 if (sn != L"::$DATA" && sn.length() > 6 && sn.substr(sn.length() - 6, 6) == L":$DATA") {
861 win_handle stream;
862 uint8_t* data = nullptr;
863 auto stream_size = (uint16_t)fsi->StreamSize.QuadPart;
864
865
866 if (stream_size > 0) {
867 wstring fn2;
868
869 fn2 = fn;
870 fn2 += sn;
871
872 stream = CreateFileW(fn2.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
873
874 if (stream == INVALID_HANDLE_VALUE)
875 throw last_error(GetLastError());
876
877 // We can get away with this because our streams are guaranteed to be below 64 KB -
878 // don't do this on NTFS!
879 data = (uint8_t*)malloc(stream_size);
880
881 if (!ReadFile(stream, data, stream_size, &bytesret, nullptr)) {
882 free(data);
883 throw last_error(GetLastError());
884 }
885 }
886
887 stream = CreateFileW((newpath + sn).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, nullptr);
888
889 if (stream == INVALID_HANDLE_VALUE) {
890 if (data) free(data);
891 throw last_error(GetLastError());
892 }
893
894 if (data) {
895 if (!WriteFile(stream, data, stream_size, &bytesret, nullptr)) {
896 free(data);
897 throw last_error(GetLastError());
898 }
899
900 free(data);
901 }
902 }
903 }
904
905 if (fsi->NextEntryOffset == 0)
906 break;
907
908 fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(reinterpret_cast<char*>(fsi) + fsi->NextEntryOffset);
909 }
910 }
911
912 atime.dwLowDateTime = fbi.LastAccessTime.LowPart;
913 atime.dwHighDateTime = fbi.LastAccessTime.HighPart;
914 mtime.dwLowDateTime = fbi.LastWriteTime.LowPart;
915 mtime.dwHighDateTime = fbi.LastWriteTime.HighPart;
916 SetFileTime(dest, nullptr, &atime, &mtime);
917
918 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, &bsxa, sizeof(btrfs_set_xattr));
919
920 if (Status == STATUS_BUFFER_OVERFLOW || (NT_SUCCESS(Status) && bsxa.valuelen > 0)) {
921 ULONG xalen = 0;
922 btrfs_set_xattr *xa = nullptr, *xa2;
923
924 do {
925 xalen += 1024;
926
927 if (xa) free(xa);
928 xa = (btrfs_set_xattr*)malloc(xalen);
929
930 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, xa, xalen);
931 } while (Status == STATUS_BUFFER_OVERFLOW);
932
933 if (!NT_SUCCESS(Status)) {
934 free(xa);
935 throw ntstatus_error(Status);
936 }
937
938 xa2 = xa;
939 while (xa2->valuelen > 0) {
940 Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, xa2,
941 (ULONG)(offsetof(btrfs_set_xattr, data[0]) + xa2->namelen + xa2->valuelen), nullptr, 0);
942 if (!NT_SUCCESS(Status)) {
943 free(xa);
944 throw ntstatus_error(Status);
945 }
946 xa2 = (btrfs_set_xattr*)&xa2->data[xa2->namelen + xa2->valuelen];
947 }
948
949 free(xa);
950 } else if (!NT_SUCCESS(Status))
951 throw ntstatus_error(Status);
952 } catch (...) {
953 FILE_DISPOSITION_INFO fdi;
954
955 fdi.DeleteFile = true;
956 Status = NtSetInformationFile(dest, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);
957 if (!NT_SUCCESS(Status))
958 throw ntstatus_error(Status);
959
960 throw;
961 }
962 }
963
964 HRESULT __stdcall BtrfsContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO picia) {
965 LPCMINVOKECOMMANDINFOEX pici = (LPCMINVOKECOMMANDINFOEX)picia;
966
967 try {
968 if (ignore)
969 return E_INVALIDARG;
970
971 if (!bg) {
972 if ((IS_INTRESOURCE(pici->lpVerb) && allow_snapshot && pici->lpVerb == 0) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, SNAPSHOT_VERBA))) {
973 UINT num_files, i;
974 WCHAR fn[MAX_PATH];
975
976 if (!stgm_set)
977 return E_FAIL;
978
979 num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);
980
981 if (num_files == 0)
982 return E_FAIL;
983
984 for (i = 0; i < num_files; i++) {
985 if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) {
986 create_snapshot(pici->hwnd, fn);
987 }
988 }
989
990 return S_OK;
991 } else if ((IS_INTRESOURCE(pici->lpVerb) && ((allow_snapshot && (ULONG_PTR)pici->lpVerb == 1) || (!allow_snapshot && (ULONG_PTR)pici->lpVerb == 0))) ||
992 (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, SEND_VERBA))) {
993 UINT num_files, i;
994 WCHAR dll[MAX_PATH], fn[MAX_PATH];
995 wstring t;
996 SHELLEXECUTEINFOW sei;
997
998 GetModuleFileNameW(module, dll, sizeof(dll) / sizeof(WCHAR));
999
1000 if (!stgm_set)
1001 return E_FAIL;
1002
1003 num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);
1004
1005 if (num_files == 0)
1006 return E_FAIL;
1007
1008 for (i = 0; i < num_files; i++) {
1009 if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) {
1010 t = L"\"";
1011 t += dll;
1012 t += L"\",SendSubvolGUI ";
1013 t += fn;
1014
1015 RtlZeroMemory(&sei, sizeof(sei));
1016
1017 sei.cbSize = sizeof(sei);
1018 sei.hwnd = pici->hwnd;
1019 sei.lpVerb = L"runas";
1020 sei.lpFile = L"rundll32.exe";
1021 sei.lpParameters = t.c_str();
1022 sei.nShow = SW_SHOW;
1023 sei.fMask = SEE_MASK_NOCLOSEPROCESS;
1024
1025 if (!ShellExecuteExW(&sei))
1026 throw last_error(GetLastError());
1027
1028 WaitForSingleObject(sei.hProcess, INFINITE);
1029 CloseHandle(sei.hProcess);
1030 }
1031 }
1032
1033 return S_OK;
1034 }
1035 } else {
1036 if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 0) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, NEW_SUBVOL_VERBA))) {
1037 win_handle h;
1038 IO_STATUS_BLOCK iosb;
1039 NTSTATUS Status;
1040 wstring name, nameorig, searchpath;
1041 btrfs_create_subvol* bcs;
1042 WIN32_FIND_DATAW wfd;
1043
1044 if (!load_string(module, IDS_NEW_SUBVOL_FILENAME, name))
1045 throw last_error(GetLastError());
1046
1047 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);
1048
1049 if (h == INVALID_HANDLE_VALUE)
1050 throw last_error(GetLastError());
1051
1052 searchpath = path + L"\\" + name;
1053 nameorig = name;
1054
1055 {
1056 fff_handle fff = FindFirstFileW(searchpath.c_str(), &wfd);
1057
1058 if (fff != INVALID_HANDLE_VALUE) {
1059 ULONG num = 2;
1060
1061 do {
1062 #ifndef __REACTOS__
1063 name = nameorig + L" (" + to_wstring(num) + L")";
1064 #else
1065 {
1066 WCHAR buffer[32];
1067
1068 swprintf(buffer, L"%d", num);
1069 name = nameorig + L" (" + buffer + L")";
1070 }
1071 #endif
1072 searchpath = path + L"\\" + name;
1073
1074 fff = FindFirstFileW(searchpath.c_str(), &wfd);
1075 num++;
1076 } while (fff != INVALID_HANDLE_VALUE);
1077 }
1078 }
1079
1080 size_t bcslen = offsetof(btrfs_create_subvol, name[0]) + (name.length() * sizeof(WCHAR));
1081 bcs = (btrfs_create_subvol*)malloc(bcslen);
1082
1083 bcs->readonly = false;
1084 bcs->posix = false;
1085 bcs->namelen = (uint16_t)(name.length() * sizeof(WCHAR));
1086 memcpy(bcs->name, name.c_str(), name.length() * sizeof(WCHAR));
1087
1088 Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0);
1089
1090 free(bcs);
1091
1092 if (!NT_SUCCESS(Status))
1093 throw ntstatus_error(Status);
1094
1095 return S_OK;
1096 } else if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 1) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, RECV_VERBA))) {
1097 WCHAR dll[MAX_PATH];
1098 wstring t;
1099 SHELLEXECUTEINFOW sei;
1100
1101 GetModuleFileNameW(module, dll, sizeof(dll) / sizeof(WCHAR));
1102
1103 t = L"\"";
1104 t += dll;
1105 t += L"\",RecvSubvolGUI ";
1106 t += path;
1107
1108 RtlZeroMemory(&sei, sizeof(sei));
1109
1110 sei.cbSize = sizeof(sei);
1111 sei.hwnd = pici->hwnd;
1112 sei.lpVerb = L"runas";
1113 sei.lpFile = L"rundll32.exe";
1114 sei.lpParameters = t.c_str();
1115 sei.nShow = SW_SHOW;
1116 sei.fMask = SEE_MASK_NOCLOSEPROCESS;
1117
1118 if (!ShellExecuteExW(&sei))
1119 throw last_error(GetLastError());
1120
1121 WaitForSingleObject(sei.hProcess, INFINITE);
1122 CloseHandle(sei.hProcess);
1123
1124 return S_OK;
1125 } else if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 2) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, REFLINK_VERBA))) {
1126 HDROP hdrop;
1127
1128 if (!IsClipboardFormatAvailable(CF_HDROP))
1129 return S_OK;
1130
1131 if (!OpenClipboard(pici->hwnd))
1132 throw last_error(GetLastError());
1133
1134 try {
1135 hdrop = (HDROP)GetClipboardData(CF_HDROP);
1136
1137 if (hdrop) {
1138 HANDLE lh;
1139
1140 lh = GlobalLock(hdrop);
1141
1142 if (lh) {
1143 try {
1144 ULONG num_files, i;
1145 WCHAR fn[MAX_PATH];
1146
1147 num_files = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);
1148
1149 for (i = 0; i < num_files; i++) {
1150 if (DragQueryFileW(hdrop, i, fn, sizeof(fn) / sizeof(WCHAR))) {
1151 reflink_copy(pici->hwnd, fn, pici->lpDirectoryW);
1152 }
1153 }
1154 } catch (...) {
1155 GlobalUnlock(lh);
1156 throw;
1157 }
1158
1159 GlobalUnlock(lh);
1160 }
1161 }
1162 } catch (...) {
1163 CloseClipboard();
1164 throw;
1165 }
1166
1167 CloseClipboard();
1168
1169 return S_OK;
1170 }
1171 }
1172 } catch (const exception& e) {
1173 error_message(pici->hwnd, e.what());
1174 }
1175
1176 return E_FAIL;
1177 }
1178
1179 HRESULT __stdcall BtrfsContextMenu::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax) {
1180 if (ignore)
1181 return E_INVALIDARG;
1182
1183 if (idCmd != 0)
1184 return E_INVALIDARG;
1185
1186 if (!bg) {
1187 if (idCmd == 0) {
1188 switch (uFlags) {
1189 case GCS_HELPTEXTA:
1190 if (LoadStringA(module, IDS_CREATE_SNAPSHOT_HELP_TEXT, pszName, cchMax))
1191 return S_OK;
1192 else
1193 return E_FAIL;
1194
1195 case GCS_HELPTEXTW:
1196 if (LoadStringW(module, IDS_CREATE_SNAPSHOT_HELP_TEXT, (LPWSTR)pszName, cchMax))
1197 return S_OK;
1198 else
1199 return E_FAIL;
1200
1201 case GCS_VALIDATEA:
1202 case GCS_VALIDATEW:
1203 return S_OK;
1204
1205 case GCS_VERBA:
1206 return StringCchCopyA(pszName, cchMax, SNAPSHOT_VERBA);
1207
1208 case GCS_VERBW:
1209 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, SNAPSHOT_VERBW);
1210
1211 default:
1212 return E_INVALIDARG;
1213 }
1214 } else if (idCmd == 1) {
1215 switch (uFlags) {
1216 case GCS_HELPTEXTA:
1217 if (LoadStringA(module, IDS_SEND_SUBVOL_HELP, pszName, cchMax))
1218 return S_OK;
1219 else
1220 return E_FAIL;
1221
1222 case GCS_HELPTEXTW:
1223 if (LoadStringW(module, IDS_SEND_SUBVOL_HELP, (LPWSTR)pszName, cchMax))
1224 return S_OK;
1225 else
1226 return E_FAIL;
1227
1228 case GCS_VALIDATEA:
1229 case GCS_VALIDATEW:
1230 return S_OK;
1231
1232 case GCS_VERBA:
1233 return StringCchCopyA(pszName, cchMax, SEND_VERBA);
1234
1235 case GCS_VERBW:
1236 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, SEND_VERBW);
1237
1238 default:
1239 return E_INVALIDARG;
1240 }
1241 } else
1242 return E_INVALIDARG;
1243 } else {
1244 if (idCmd == 0) {
1245 switch (uFlags) {
1246 case GCS_HELPTEXTA:
1247 if (LoadStringA(module, IDS_NEW_SUBVOL_HELP_TEXT, pszName, cchMax))
1248 return S_OK;
1249 else
1250 return E_FAIL;
1251
1252 case GCS_HELPTEXTW:
1253 if (LoadStringW(module, IDS_NEW_SUBVOL_HELP_TEXT, (LPWSTR)pszName, cchMax))
1254 return S_OK;
1255 else
1256 return E_FAIL;
1257
1258 case GCS_VALIDATEA:
1259 case GCS_VALIDATEW:
1260 return S_OK;
1261
1262 case GCS_VERBA:
1263 return StringCchCopyA(pszName, cchMax, NEW_SUBVOL_VERBA);
1264
1265 case GCS_VERBW:
1266 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, NEW_SUBVOL_VERBW);
1267
1268 default:
1269 return E_INVALIDARG;
1270 }
1271 } else if (idCmd == 1) {
1272 switch (uFlags) {
1273 case GCS_HELPTEXTA:
1274 if (LoadStringA(module, IDS_RECV_SUBVOL_HELP, pszName, cchMax))
1275 return S_OK;
1276 else
1277 return E_FAIL;
1278
1279 case GCS_HELPTEXTW:
1280 if (LoadStringW(module, IDS_RECV_SUBVOL_HELP, (LPWSTR)pszName, cchMax))
1281 return S_OK;
1282 else
1283 return E_FAIL;
1284
1285 case GCS_VALIDATEA:
1286 case GCS_VALIDATEW:
1287 return S_OK;
1288
1289 case GCS_VERBA:
1290 return StringCchCopyA(pszName, cchMax, RECV_VERBA);
1291
1292 case GCS_VERBW:
1293 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, RECV_VERBW);
1294
1295 default:
1296 return E_INVALIDARG;
1297 }
1298 } else if (idCmd == 2) {
1299 switch (uFlags) {
1300 case GCS_HELPTEXTA:
1301 if (LoadStringA(module, IDS_REFLINK_PASTE_HELP, pszName, cchMax))
1302 return S_OK;
1303 else
1304 return E_FAIL;
1305
1306 case GCS_HELPTEXTW:
1307 if (LoadStringW(module, IDS_REFLINK_PASTE_HELP, (LPWSTR)pszName, cchMax))
1308 return S_OK;
1309 else
1310 return E_FAIL;
1311
1312 case GCS_VALIDATEA:
1313 case GCS_VALIDATEW:
1314 return S_OK;
1315
1316 case GCS_VERBA:
1317 return StringCchCopyA(pszName, cchMax, REFLINK_VERBA);
1318
1319 case GCS_VERBW:
1320 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, REFLINK_VERBW);
1321
1322 default:
1323 return E_INVALIDARG;
1324 }
1325 } else
1326 return E_INVALIDARG;
1327 }
1328 }
1329
1330 static void reflink_copy2(const wstring& srcfn, const wstring& destdir, const wstring& destname) {
1331 win_handle source, dest;
1332 FILE_BASIC_INFO fbi;
1333 FILETIME atime, mtime;
1334 btrfs_inode_info bii;
1335 btrfs_set_inode_info bsii;
1336 ULONG bytesret;
1337 NTSTATUS Status;
1338 IO_STATUS_BLOCK iosb;
1339 btrfs_set_xattr bsxa;
1340
1341 source = CreateFileW(srcfn.c_str(), GENERIC_READ | FILE_TRAVERSE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr);
1342 if (source == INVALID_HANDLE_VALUE)
1343 throw last_error(GetLastError());
1344
1345 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii, sizeof(btrfs_inode_info));
1346 if (!NT_SUCCESS(Status))
1347 throw ntstatus_error(Status);
1348
1349 // if subvol, do snapshot instead
1350 if (bii.inode == SUBVOL_ROOT_INODE) {
1351 btrfs_create_snapshot* bcs;
1352 win_handle dirh;
1353
1354 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);
1355 if (dirh == INVALID_HANDLE_VALUE)
1356 throw last_error(GetLastError());
1357
1358 size_t bcslen = offsetof(btrfs_create_snapshot, name[0]) + (destname.length() * sizeof(WCHAR));
1359 bcs = (btrfs_create_snapshot*)malloc(bcslen);
1360 bcs->subvol = source;
1361 bcs->namelen = (uint16_t)(destname.length() * sizeof(WCHAR));
1362 memcpy(bcs->name, destname.c_str(), destname.length() * sizeof(WCHAR));
1363
1364 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)bcslen, nullptr, 0);
1365 if (!NT_SUCCESS(Status)) {
1366 free(bcs);
1367 throw ntstatus_error(Status);
1368 }
1369
1370 free(bcs);
1371
1372 return;
1373 }
1374
1375 Status = NtQueryInformationFile(source, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation);
1376 if (!NT_SUCCESS(Status))
1377 throw ntstatus_error(Status);
1378
1379 if (bii.type == BTRFS_TYPE_CHARDEV || bii.type == BTRFS_TYPE_BLOCKDEV || bii.type == BTRFS_TYPE_FIFO || bii.type == BTRFS_TYPE_SOCKET) {
1380 win_handle dirh;
1381 btrfs_mknod* bmn;
1382
1383 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);
1384 if (dirh == INVALID_HANDLE_VALUE)
1385 throw last_error(GetLastError());
1386
1387 size_t bmnsize = offsetof(btrfs_mknod, name[0]) + (destname.length() * sizeof(WCHAR));
1388 bmn = (btrfs_mknod*)malloc(bmnsize);
1389
1390 bmn->inode = 0;
1391 bmn->type = bii.type;
1392 bmn->st_rdev = bii.st_rdev;
1393 bmn->namelen = (uint16_t)(destname.length() * sizeof(WCHAR));
1394 memcpy(bmn->name, destname.c_str(), bmn->namelen);
1395
1396 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_MKNOD, bmn, (ULONG)bmnsize, nullptr, 0);
1397 if (!NT_SUCCESS(Status)) {
1398 free(bmn);
1399 throw ntstatus_error(Status);
1400 }
1401
1402 free(bmn);
1403
1404 dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
1405 } else if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1406 if (CreateDirectoryExW(srcfn.c_str(), (destdir + destname).c_str(), nullptr))
1407 dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1408 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
1409 else
1410 dest = INVALID_HANDLE_VALUE;
1411 } else
1412 dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source);
1413
1414 if (dest == INVALID_HANDLE_VALUE)
1415 throw last_error(GetLastError());
1416
1417 memset(&bsii, 0, sizeof(btrfs_set_inode_info));
1418
1419 bsii.flags_changed = true;
1420 bsii.flags = bii.flags;
1421
1422 if (bii.flags & BTRFS_INODE_COMPRESS) {
1423 bsii.compression_type_changed = true;
1424 bsii.compression_type = bii.compression_type;
1425 }
1426
1427 try {
1428 Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);
1429 if (!NT_SUCCESS(Status))
1430 throw ntstatus_error(Status);
1431
1432 if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1433 if (!(fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
1434 WIN32_FIND_DATAW fff;
1435 wstring qs;
1436
1437 qs = srcfn;
1438 qs += L"\\*";
1439
1440 fff_handle h = FindFirstFileW(qs.c_str(), &fff);
1441 if (h != INVALID_HANDLE_VALUE) {
1442 do {
1443 wstring fn2;
1444
1445 if (fff.cFileName[0] == '.' && (fff.cFileName[1] == 0 || (fff.cFileName[1] == '.' && fff.cFileName[2] == 0)))
1446 continue;
1447
1448 fn2 = srcfn;
1449 fn2 += L"\\";
1450 fn2 += fff.cFileName;
1451
1452 reflink_copy2(fn2, destdir + destname + L"\\", fff.cFileName);
1453 } while (FindNextFileW(h, &fff));
1454 }
1455 }
1456
1457 // CreateDirectoryExW also copies streams, no need to do it here
1458 } else {
1459 if (fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
1460 reparse_header rh;
1461 uint8_t* rp;
1462
1463 if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, &rh, sizeof(reparse_header), &bytesret, nullptr)) {
1464 if (GetLastError() != ERROR_MORE_DATA)
1465 throw last_error(GetLastError());
1466 }
1467
1468 size_t rplen = sizeof(reparse_header) + rh.ReparseDataLength;
1469 rp = (uint8_t*)malloc(rplen);
1470
1471 try {
1472 if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, rp, (DWORD)rplen, &bytesret, nullptr))
1473 throw last_error(GetLastError());
1474
1475 if (!DeviceIoControl(dest, FSCTL_SET_REPARSE_POINT, rp, (DWORD)rplen, nullptr, 0, &bytesret, nullptr))
1476 throw last_error(GetLastError());
1477 } catch (...) {
1478 free(rp);
1479 throw;
1480 }
1481
1482 free(rp);
1483 } else {
1484 FILE_STANDARD_INFO fsi;
1485 FILE_END_OF_FILE_INFO feofi;
1486 FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib;
1487 FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib;
1488 DUPLICATE_EXTENTS_DATA ded;
1489 uint64_t offset, alloc_size;
1490 ULONG maxdup;
1491
1492 Status = NtQueryInformationFile(source, &iosb, &fsi, sizeof(FILE_STANDARD_INFO), FileStandardInformation);
1493 if (!NT_SUCCESS(Status))
1494 throw ntstatus_error(Status);
1495
1496 if (!DeviceIoControl(source, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &fgiib, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER), &bytesret, nullptr))
1497 throw last_error(GetLastError());
1498
1499 if (fbi.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) {
1500 if (!DeviceIoControl(dest, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesret, nullptr))
1501 throw last_error(GetLastError());
1502 }
1503
1504 fsiib.ChecksumAlgorithm = fgiib.ChecksumAlgorithm;
1505 fsiib.Reserved = 0;
1506 fsiib.Flags = fgiib.Flags;
1507 if (!DeviceIoControl(dest, FSCTL_SET_INTEGRITY_INFORMATION, &fsiib, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER), nullptr, 0, &bytesret, nullptr))
1508 throw last_error(GetLastError());
1509
1510 feofi.EndOfFile = fsi.EndOfFile;
1511 Status = NtSetInformationFile(dest, &iosb, &feofi, sizeof(FILE_END_OF_FILE_INFO), FileEndOfFileInformation);
1512 if (!NT_SUCCESS(Status))
1513 throw ntstatus_error(Status);
1514
1515 ded.FileHandle = source;
1516 maxdup = 0xffffffff - fgiib.ClusterSizeInBytes + 1;
1517
1518 alloc_size = sector_align(fsi.EndOfFile.QuadPart, fgiib.ClusterSizeInBytes);
1519
1520 offset = 0;
1521 while (offset < alloc_size) {
1522 ded.SourceFileOffset.QuadPart = ded.TargetFileOffset.QuadPart = offset;
1523 ded.ByteCount.QuadPart = maxdup < (alloc_size - offset) ? maxdup : (alloc_size - offset);
1524 if (!DeviceIoControl(dest, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA), nullptr, 0, &bytesret, nullptr))
1525 throw last_error(GetLastError());
1526
1527 offset += ded.ByteCount.QuadPart;
1528 }
1529 }
1530
1531 ULONG streambufsize = 0;
1532 vector<char> streambuf;
1533
1534 do {
1535 streambufsize += 0x1000;
1536 streambuf.resize(streambufsize);
1537
1538 #ifndef __REACTOS__
1539 memset(streambuf.data(), 0, streambufsize);
1540
1541 Status = NtQueryInformationFile(source, &iosb, streambuf.data(), streambufsize, FileStreamInformation);
1542 #else
1543 memset(&streambuf[0], 0, streambufsize);
1544
1545 Status = NtQueryInformationFile(source, &iosb, &streambuf[0], streambufsize, FileStreamInformation);
1546 #endif
1547 } while (Status == STATUS_BUFFER_OVERFLOW);
1548
1549 if (!NT_SUCCESS(Status))
1550 throw ntstatus_error(Status);
1551
1552 #ifndef __REACTOS__
1553 auto fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(streambuf.data());
1554 #else
1555 auto fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(&streambuf[0]);
1556 #endif
1557
1558 while (true) {
1559 if (fsi->StreamNameLength > 0) {
1560 wstring sn = wstring(fsi->StreamName, fsi->StreamNameLength / sizeof(WCHAR));
1561
1562 if (sn != L"::$DATA" && sn.length() > 6 && sn.substr(sn.length() - 6, 6) == L":$DATA") {
1563 win_handle stream;
1564 uint8_t* data = nullptr;
1565 auto stream_size = (uint16_t)fsi->StreamSize.QuadPart;
1566
1567 if (stream_size > 0) {
1568 wstring fn2;
1569
1570 fn2 = srcfn;
1571 fn2 += sn;
1572
1573 stream = CreateFileW(fn2.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
1574
1575 if (stream == INVALID_HANDLE_VALUE)
1576 throw last_error(GetLastError());
1577
1578 // We can get away with this because our streams are guaranteed to be below 64 KB -
1579 // don't do this on NTFS!
1580 data = (uint8_t*)malloc(stream_size);
1581
1582 if (!ReadFile(stream, data, stream_size, &bytesret, nullptr)) {
1583 free(data);
1584 throw last_error(GetLastError());
1585 }
1586 }
1587
1588 stream = CreateFileW((destdir + destname + sn).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, nullptr);
1589
1590 if (stream == INVALID_HANDLE_VALUE) {
1591 if (data) free(data);
1592 throw last_error(GetLastError());
1593 }
1594
1595 if (data) {
1596 if (!WriteFile(stream, data, stream_size, &bytesret, nullptr)) {
1597 free(data);
1598 throw last_error(GetLastError());
1599 }
1600
1601 free(data);
1602 }
1603 }
1604
1605 }
1606
1607 if (fsi->NextEntryOffset == 0)
1608 break;
1609
1610 fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(reinterpret_cast<char*>(fsi) + fsi->NextEntryOffset);
1611 }
1612 }
1613
1614 atime.dwLowDateTime = fbi.LastAccessTime.LowPart;
1615 atime.dwHighDateTime = fbi.LastAccessTime.HighPart;
1616 mtime.dwLowDateTime = fbi.LastWriteTime.LowPart;
1617 mtime.dwHighDateTime = fbi.LastWriteTime.HighPart;
1618 SetFileTime(dest, nullptr, &atime, &mtime);
1619
1620 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, &bsxa, sizeof(btrfs_set_xattr));
1621
1622 if (Status == STATUS_BUFFER_OVERFLOW || (NT_SUCCESS(Status) && bsxa.valuelen > 0)) {
1623 ULONG xalen = 0;
1624 btrfs_set_xattr *xa = nullptr, *xa2;
1625
1626 do {
1627 xalen += 1024;
1628
1629 if (xa) free(xa);
1630 xa = (btrfs_set_xattr*)malloc(xalen);
1631
1632 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, xa, xalen);
1633 } while (Status == STATUS_BUFFER_OVERFLOW);
1634
1635 if (!NT_SUCCESS(Status)) {
1636 free(xa);
1637 throw ntstatus_error(Status);
1638 }
1639
1640 xa2 = xa;
1641 while (xa2->valuelen > 0) {
1642 Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, xa2,
1643 (ULONG)(offsetof(btrfs_set_xattr, data[0]) + xa2->namelen + xa2->valuelen), nullptr, 0);
1644 if (!NT_SUCCESS(Status)) {
1645 free(xa);
1646 throw ntstatus_error(Status);
1647 }
1648 xa2 = (btrfs_set_xattr*)&xa2->data[xa2->namelen + xa2->valuelen];
1649 }
1650
1651 free(xa);
1652 } else if (!NT_SUCCESS(Status))
1653 throw ntstatus_error(Status);
1654 } catch (...) {
1655 FILE_DISPOSITION_INFO fdi;
1656
1657 fdi.DeleteFile = true;
1658 Status = NtSetInformationFile(dest, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);
1659 if (!NT_SUCCESS(Status))
1660 throw ntstatus_error(Status);
1661
1662 throw;
1663 }
1664 }
1665
1666 #ifdef __REACTOS__
1667 extern "C" {
1668 #endif
1669
1670 void CALLBACK ReflinkCopyW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
1671 vector<wstring> args;
1672
1673 command_line_to_args(lpszCmdLine, args);
1674
1675 if (args.size() >= 2) {
1676 bool dest_is_dir = false;
1677 wstring dest = args[args.size() - 1], destdir, destname;
1678 WCHAR volpath2[MAX_PATH];
1679
1680 {
1681 win_handle destdirh = CreateFileW(dest.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1682 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
1683
1684 if (destdirh != INVALID_HANDLE_VALUE) {
1685 BY_HANDLE_FILE_INFORMATION bhfi;
1686
1687 if (GetFileInformationByHandle(destdirh, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1688 dest_is_dir = true;
1689
1690 destdir = dest;
1691 if (destdir.substr(destdir.length() - 1, 1) != L"\\")
1692 destdir += L"\\";
1693 }
1694 }
1695 }
1696
1697 if (!dest_is_dir) {
1698 size_t found = dest.rfind(L"\\");
1699
1700 if (found == wstring::npos) {
1701 destdir = L"";
1702 destname = dest;
1703 } else {
1704 destdir = dest.substr(0, found);
1705 destname = dest.substr(found + 1);
1706 }
1707 }
1708
1709 if (!GetVolumePathNameW(dest.c_str(), volpath2, sizeof(volpath2) / sizeof(WCHAR)))
1710 return;
1711
1712 for (unsigned int i = 0; i < args.size() - 1; i++) {
1713 WIN32_FIND_DATAW ffd;
1714
1715 fff_handle h = FindFirstFileW(args[i].c_str(), &ffd);
1716 if (h != INVALID_HANDLE_VALUE) {
1717 WCHAR volpath1[MAX_PATH];
1718 wstring path = args[i];
1719 size_t found = path.rfind(L"\\");
1720
1721 if (found == wstring::npos)
1722 path = L"";
1723 else
1724 path = path.substr(0, found);
1725
1726 path += L"\\";
1727
1728 if (get_volume_path_parent(path.c_str(), volpath1, sizeof(volpath1) / sizeof(WCHAR))) {
1729 if (!wcscmp(volpath1, volpath2)) {
1730 do {
1731 try {
1732 reflink_copy2(path + ffd.cFileName, destdir, dest_is_dir ? ffd.cFileName : destname);
1733 } catch (const exception& e) {
1734 cerr << "Error: " << e.what() << endl;
1735 }
1736 } while (FindNextFileW(h, &ffd));
1737 }
1738 }
1739 }
1740 }
1741 }
1742 }
1743
1744 #ifdef __REACTOS__
1745 } /* extern "C" */
1746 #endif