[BTRFS] Upgrade to 1.4
[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 = 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 btrfs_create_snapshot* bcs;
466 ULONG namelen;
467 WIN32_FIND_DATAW wfd;
468 SYSTEMTIME time;
469
470 parpath = fn;
471 path_remove_file(parpath);
472
473 subvolname = fn;
474 path_strip_path(subvolname);
475
476 h2 = CreateFileW(parpath.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
477
478 if (h2 == INVALID_HANDLE_VALUE)
479 throw last_error(GetLastError());
480
481 if (!load_string(module, IDS_SNAPSHOT_FILENAME, temp1))
482 throw last_error(GetLastError());
483
484 GetLocalTime(&time);
485
486 wstring_sprintf(name, temp1, subvolname.c_str(), time.wYear, time.wMonth, time.wDay);
487 nameorig = name;
488
489 searchpath = parpath + L"\\" + name;
490
491 fff_handle fff = FindFirstFileW(searchpath.c_str(), &wfd);
492
493 if (fff != INVALID_HANDLE_VALUE) {
494 ULONG num = 2;
495
496 do {
497 #ifndef __REACTOS__
498 name = nameorig + L" (" + to_wstring(num) + L")";
499 #else
500 {
501 WCHAR buffer[32];
502
503 swprintf(buffer, L"%d", num);
504 name = nameorig + L" (" + buffer + L")";
505 }
506 #endif
507 searchpath = parpath + L"\\" + name;
508
509 fff = FindFirstFileW(searchpath.c_str(), &wfd);
510 num++;
511 } while (fff != INVALID_HANDLE_VALUE);
512 }
513
514 namelen = name.length() * sizeof(WCHAR);
515
516 bcs = (btrfs_create_snapshot*)malloc(sizeof(btrfs_create_snapshot) - 1 + namelen);
517 bcs->readonly = false;
518 bcs->posix = false;
519 bcs->subvol = h;
520 bcs->namelen = (uint16_t)namelen;
521 memcpy(bcs->name, name.c_str(), namelen);
522
523 Status = NtFsControlFile(h2, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, sizeof(btrfs_create_snapshot) - 1 + namelen, nullptr, 0);
524
525 if (!NT_SUCCESS(Status))
526 throw ntstatus_error(Status);
527 }
528 } else
529 throw last_error(GetLastError());
530 }
531
532 static uint64_t __inline sector_align(uint64_t n, uint64_t a) {
533 if (n & (a - 1))
534 n = (n + a) & ~(a - 1);
535
536 return n;
537 }
538
539 void BtrfsContextMenu::reflink_copy(HWND hwnd, const WCHAR* fn, const WCHAR* dir) {
540 win_handle source, dest;
541 WCHAR* name, volpath1[255], volpath2[255];
542 wstring dirw, newpath;
543 FILE_BASIC_INFO fbi;
544 FILETIME atime, mtime;
545 btrfs_inode_info bii;
546 btrfs_set_inode_info bsii;
547 ULONG bytesret;
548 NTSTATUS Status;
549 IO_STATUS_BLOCK iosb;
550 btrfs_set_xattr bsxa;
551
552 // Thanks to 0xbadfca11, whose version of reflink for Windows provided a few pointers on what
553 // to do here - https://github.com/0xbadfca11/reflink
554
555 name = PathFindFileNameW(fn);
556
557 dirw = dir;
558
559 if (dir[0] != 0 && dir[wcslen(dir) - 1] != '\\')
560 dirw += L"\\";
561
562 newpath = dirw;
563 newpath += name;
564
565 if (!get_volume_path_parent(fn, volpath1, sizeof(volpath1) / sizeof(WCHAR)))
566 throw last_error(GetLastError());
567
568 if (!GetVolumePathNameW(dir, volpath2, sizeof(volpath2) / sizeof(WCHAR)))
569 throw last_error(GetLastError());
570
571 if (wcscmp(volpath1, volpath2)) // different filesystems
572 throw string_error(IDS_CANT_REFLINK_DIFFERENT_FS);
573
574 source = CreateFileW(fn, GENERIC_READ | FILE_TRAVERSE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr);
575 if (source == INVALID_HANDLE_VALUE)
576 throw last_error(GetLastError());
577
578 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii, sizeof(btrfs_inode_info));
579 if (!NT_SUCCESS(Status))
580 throw ntstatus_error(Status);
581
582 // if subvol, do snapshot instead
583 if (bii.inode == SUBVOL_ROOT_INODE) {
584 btrfs_create_snapshot* bcs;
585 win_handle dirh;
586 wstring destname, search;
587 WIN32_FIND_DATAW wfd;
588 int num = 2;
589
590 dirh = CreateFileW(dir, FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
591 if (dirh == INVALID_HANDLE_VALUE)
592 throw last_error(GetLastError());
593
594 search = dirw;
595 search += name;
596 destname = name;
597
598 fff_handle fff = FindFirstFileW(search.c_str(), &wfd);
599
600 if (fff != INVALID_HANDLE_VALUE) {
601 do {
602 wstringstream ss;
603
604 ss << name;
605 ss << L" (";
606 ss << num;
607 ss << L")";
608 destname = ss.str();
609
610 search = dirw + destname;
611
612 fff = FindFirstFileW(search.c_str(), &wfd);
613 num++;
614 } while (fff != INVALID_HANDLE_VALUE);
615 }
616
617 bcs = (btrfs_create_snapshot*)malloc(sizeof(btrfs_create_snapshot) - sizeof(WCHAR) + (destname.length() * sizeof(WCHAR)));
618 bcs->subvol = source;
619 bcs->namelen = (uint16_t)(destname.length() * sizeof(WCHAR));
620 memcpy(bcs->name, destname.c_str(), destname.length() * sizeof(WCHAR));
621
622 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, 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 if (!GetFileInformationByHandleEx(source, FileBasicInfo, &fbi, sizeof(FILE_BASIC_INFO)))
633 throw last_error(GetLastError());
634
635 if (bii.type == BTRFS_TYPE_CHARDEV || bii.type == BTRFS_TYPE_BLOCKDEV || bii.type == BTRFS_TYPE_FIFO || bii.type == BTRFS_TYPE_SOCKET) {
636 win_handle dirh;
637 ULONG bmnsize;
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 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, 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 WIN32_FIND_STREAM_DATA fsd;
763
764 if (fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
765 reparse_header rh;
766 ULONG rplen;
767 uint8_t* rp;
768
769 if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, &rh, sizeof(reparse_header), &bytesret, nullptr)) {
770 if (GetLastError() != ERROR_MORE_DATA)
771 throw last_error(GetLastError());
772 }
773
774 rplen = sizeof(reparse_header) + rh.ReparseDataLength;
775 rp = (uint8_t*)malloc(rplen);
776
777 if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, rp, rplen, &bytesret, nullptr))
778 throw last_error(GetLastError());
779
780 if (!DeviceIoControl(dest, FSCTL_SET_REPARSE_POINT, rp, rplen, nullptr, 0, &bytesret, nullptr))
781 throw last_error(GetLastError());
782
783 free(rp);
784 } else {
785 FILE_STANDARD_INFO fsi;
786 FILE_END_OF_FILE_INFO feofi;
787 FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib;
788 FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib;
789 DUPLICATE_EXTENTS_DATA ded;
790 uint64_t offset, alloc_size;
791 ULONG maxdup;
792
793 if (!GetFileInformationByHandleEx(source, FileStandardInfo, &fsi, sizeof(FILE_STANDARD_INFO)))
794 throw last_error(GetLastError());
795
796 if (!DeviceIoControl(source, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &fgiib, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER), &bytesret, nullptr))
797 throw last_error(GetLastError());
798
799 if (fbi.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) {
800 if (!DeviceIoControl(dest, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesret, nullptr))
801 throw last_error(GetLastError());
802 }
803
804 fsiib.ChecksumAlgorithm = fgiib.ChecksumAlgorithm;
805 fsiib.Reserved = 0;
806 fsiib.Flags = fgiib.Flags;
807 if (!DeviceIoControl(dest, FSCTL_SET_INTEGRITY_INFORMATION, &fsiib, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER), nullptr, 0, &bytesret, nullptr))
808 throw last_error(GetLastError());
809
810 feofi.EndOfFile = fsi.EndOfFile;
811 if (!SetFileInformationByHandle(dest, FileEndOfFileInfo, &feofi, sizeof(FILE_END_OF_FILE_INFO)))
812 throw last_error(GetLastError());
813
814 ded.FileHandle = source;
815 maxdup = 0xffffffff - fgiib.ClusterSizeInBytes + 1;
816
817 alloc_size = sector_align(fsi.EndOfFile.QuadPart, fgiib.ClusterSizeInBytes);
818
819 offset = 0;
820 while (offset < alloc_size) {
821 ded.SourceFileOffset.QuadPart = ded.TargetFileOffset.QuadPart = offset;
822 ded.ByteCount.QuadPart = maxdup < (alloc_size - offset) ? maxdup : (alloc_size - offset);
823 if (!DeviceIoControl(dest, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA), nullptr, 0, &bytesret, nullptr))
824 throw last_error(GetLastError());
825
826 offset += ded.ByteCount.QuadPart;
827 }
828 }
829
830 fff_handle h = FindFirstStreamW(fn, FindStreamInfoStandard, &fsd, 0);
831 if (h != INVALID_HANDLE_VALUE) {
832 do {
833 wstring sn;
834
835 sn = fsd.cStreamName;
836
837 if (sn != L"::$DATA" && sn.length() > 6 && sn.substr(sn.length() - 6, 6) == L":$DATA") {
838 win_handle stream;
839 uint8_t* data = nullptr;
840 uint16_t stream_size = (uint16_t)fsd.StreamSize.QuadPart;
841
842 if (fsd.StreamSize.QuadPart > 0) {
843 wstring fn2;
844
845 fn2 = fn;
846 fn2 += sn;
847
848 stream = CreateFileW(fn2.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
849
850 if (stream == INVALID_HANDLE_VALUE)
851 throw last_error(GetLastError());
852
853 // We can get away with this because our streams are guaranteed to be below 64 KB -
854 // don't do this on NTFS!
855 data = (uint8_t*)malloc(stream_size);
856
857 if (!ReadFile(stream, data, stream_size, &bytesret, nullptr)) {
858 free(data);
859 throw last_error(GetLastError());
860 }
861 }
862
863 stream = CreateFileW((newpath + sn).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, nullptr);
864
865 if (stream == INVALID_HANDLE_VALUE) {
866 if (data) free(data);
867 throw last_error(GetLastError());
868 }
869
870 if (data) {
871 if (!WriteFile(stream, data, stream_size, &bytesret, nullptr)) {
872 free(data);
873 throw last_error(GetLastError());
874 }
875
876 free(data);
877 }
878 }
879 } while (FindNextStreamW(h, &fsd));
880 }
881 }
882
883 atime.dwLowDateTime = fbi.LastAccessTime.LowPart;
884 atime.dwHighDateTime = fbi.LastAccessTime.HighPart;
885 mtime.dwLowDateTime = fbi.LastWriteTime.LowPart;
886 mtime.dwHighDateTime = fbi.LastWriteTime.HighPart;
887 SetFileTime(dest, nullptr, &atime, &mtime);
888
889 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, &bsxa, sizeof(btrfs_set_xattr));
890
891 if (Status == STATUS_BUFFER_OVERFLOW || (NT_SUCCESS(Status) && bsxa.valuelen > 0)) {
892 ULONG xalen = 0;
893 btrfs_set_xattr *xa = nullptr, *xa2;
894
895 do {
896 xalen += 1024;
897
898 if (xa) free(xa);
899 xa = (btrfs_set_xattr*)malloc(xalen);
900
901 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, xa, xalen);
902 } while (Status == STATUS_BUFFER_OVERFLOW);
903
904 if (!NT_SUCCESS(Status)) {
905 free(xa);
906 throw ntstatus_error(Status);
907 }
908
909 xa2 = xa;
910 while (xa2->valuelen > 0) {
911 Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, xa2,
912 offsetof(btrfs_set_xattr, data[0]) + xa2->namelen + xa2->valuelen, nullptr, 0);
913 if (!NT_SUCCESS(Status)) {
914 free(xa);
915 throw ntstatus_error(Status);
916 }
917 xa2 = (btrfs_set_xattr*)&xa2->data[xa2->namelen + xa2->valuelen];
918 }
919
920 free(xa);
921 } else if (!NT_SUCCESS(Status))
922 throw ntstatus_error(Status);
923 } catch (...) {
924 FILE_DISPOSITION_INFO fdi;
925
926 fdi.DeleteFile = true;
927 if (!SetFileInformationByHandle(dest, FileDispositionInfo, &fdi, sizeof(FILE_DISPOSITION_INFO)))
928 throw last_error(GetLastError());
929
930 throw;
931 }
932 }
933
934 HRESULT __stdcall BtrfsContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO picia) {
935 LPCMINVOKECOMMANDINFOEX pici = (LPCMINVOKECOMMANDINFOEX)picia;
936
937 try {
938 if (ignore)
939 return E_INVALIDARG;
940
941 if (!bg) {
942 if ((IS_INTRESOURCE(pici->lpVerb) && allow_snapshot && pici->lpVerb == 0) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, SNAPSHOT_VERBA))) {
943 UINT num_files, i;
944 WCHAR fn[MAX_PATH];
945
946 if (!stgm_set)
947 return E_FAIL;
948
949 num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);
950
951 if (num_files == 0)
952 return E_FAIL;
953
954 for (i = 0; i < num_files; i++) {
955 if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) {
956 create_snapshot(pici->hwnd, fn);
957 }
958 }
959
960 return S_OK;
961 } else if ((IS_INTRESOURCE(pici->lpVerb) && ((allow_snapshot && (ULONG_PTR)pici->lpVerb == 1) || (!allow_snapshot && (ULONG_PTR)pici->lpVerb == 0))) ||
962 (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, SEND_VERBA))) {
963 UINT num_files, i;
964 WCHAR dll[MAX_PATH], fn[MAX_PATH];
965 wstring t;
966 SHELLEXECUTEINFOW sei;
967
968 GetModuleFileNameW(module, dll, sizeof(dll) / sizeof(WCHAR));
969
970 if (!stgm_set)
971 return E_FAIL;
972
973 num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);
974
975 if (num_files == 0)
976 return E_FAIL;
977
978 for (i = 0; i < num_files; i++) {
979 if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) {
980 t = L"\"";
981 t += dll;
982 t += L"\",SendSubvolGUI ";
983 t += fn;
984
985 RtlZeroMemory(&sei, sizeof(sei));
986
987 sei.cbSize = sizeof(sei);
988 sei.hwnd = pici->hwnd;
989 sei.lpVerb = L"runas";
990 sei.lpFile = L"rundll32.exe";
991 sei.lpParameters = t.c_str();
992 sei.nShow = SW_SHOW;
993 sei.fMask = SEE_MASK_NOCLOSEPROCESS;
994
995 if (!ShellExecuteExW(&sei))
996 throw last_error(GetLastError());
997
998 WaitForSingleObject(sei.hProcess, INFINITE);
999 CloseHandle(sei.hProcess);
1000 }
1001 }
1002
1003 return S_OK;
1004 }
1005 } else {
1006 if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 0) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, NEW_SUBVOL_VERBA))) {
1007 win_handle h;
1008 IO_STATUS_BLOCK iosb;
1009 NTSTATUS Status;
1010 ULONG bcslen;
1011 wstring name, nameorig, searchpath;
1012 btrfs_create_subvol* bcs;
1013 WIN32_FIND_DATAW wfd;
1014
1015 if (!load_string(module, IDS_NEW_SUBVOL_FILENAME, name))
1016 throw last_error(GetLastError());
1017
1018 h = CreateFileW(path.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
1019
1020 if (h == INVALID_HANDLE_VALUE)
1021 throw last_error(GetLastError());
1022
1023 searchpath = path + L"\\" + name;
1024 nameorig = name;
1025
1026 {
1027 fff_handle fff = FindFirstFileW(searchpath.c_str(), &wfd);
1028
1029 if (fff != INVALID_HANDLE_VALUE) {
1030 ULONG num = 2;
1031
1032 do {
1033 #ifndef __REACTOS__
1034 name = nameorig + L" (" + to_wstring(num) + L")";
1035 #else
1036 {
1037 WCHAR buffer[32];
1038
1039 swprintf(buffer, L"%d", num);
1040 name = nameorig + L" (" + buffer + L")";
1041 }
1042 #endif
1043 searchpath = path + L"\\" + name;
1044
1045 fff = FindFirstFileW(searchpath.c_str(), &wfd);
1046 num++;
1047 } while (fff != INVALID_HANDLE_VALUE);
1048 }
1049 }
1050
1051 bcslen = offsetof(btrfs_create_subvol, name[0]) + (name.length() * sizeof(WCHAR));
1052 bcs = (btrfs_create_subvol*)malloc(bcslen);
1053
1054 bcs->readonly = false;
1055 bcs->posix = false;
1056 bcs->namelen = (uint16_t)(name.length() * sizeof(WCHAR));
1057 memcpy(bcs->name, name.c_str(), name.length() * sizeof(WCHAR));
1058
1059 Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, bcs, bcslen, nullptr, 0);
1060
1061 free(bcs);
1062
1063 if (!NT_SUCCESS(Status))
1064 throw ntstatus_error(Status);
1065
1066 return S_OK;
1067 } else if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 1) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, RECV_VERBA))) {
1068 WCHAR dll[MAX_PATH];
1069 wstring t;
1070 SHELLEXECUTEINFOW sei;
1071
1072 GetModuleFileNameW(module, dll, sizeof(dll) / sizeof(WCHAR));
1073
1074 t = L"\"";
1075 t += dll;
1076 t += L"\",RecvSubvolGUI ";
1077 t += path;
1078
1079 RtlZeroMemory(&sei, sizeof(sei));
1080
1081 sei.cbSize = sizeof(sei);
1082 sei.hwnd = pici->hwnd;
1083 sei.lpVerb = L"runas";
1084 sei.lpFile = L"rundll32.exe";
1085 sei.lpParameters = t.c_str();
1086 sei.nShow = SW_SHOW;
1087 sei.fMask = SEE_MASK_NOCLOSEPROCESS;
1088
1089 if (!ShellExecuteExW(&sei))
1090 throw last_error(GetLastError());
1091
1092 WaitForSingleObject(sei.hProcess, INFINITE);
1093 CloseHandle(sei.hProcess);
1094
1095 return S_OK;
1096 } else if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 2) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, REFLINK_VERBA))) {
1097 HDROP hdrop;
1098
1099 if (!IsClipboardFormatAvailable(CF_HDROP))
1100 return S_OK;
1101
1102 if (!OpenClipboard(pici->hwnd))
1103 throw last_error(GetLastError());
1104
1105 try {
1106 hdrop = (HDROP)GetClipboardData(CF_HDROP);
1107
1108 if (hdrop) {
1109 HANDLE lh;
1110
1111 lh = GlobalLock(hdrop);
1112
1113 if (lh) {
1114 try {
1115 ULONG num_files, i;
1116 WCHAR fn[MAX_PATH];
1117
1118 num_files = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);
1119
1120 for (i = 0; i < num_files; i++) {
1121 if (DragQueryFileW(hdrop, i, fn, sizeof(fn) / sizeof(WCHAR))) {
1122 reflink_copy(pici->hwnd, fn, pici->lpDirectoryW);
1123 }
1124 }
1125 } catch (...) {
1126 GlobalUnlock(lh);
1127 throw;
1128 }
1129
1130 GlobalUnlock(lh);
1131 }
1132 }
1133 } catch (...) {
1134 CloseClipboard();
1135 throw;
1136 }
1137
1138 CloseClipboard();
1139
1140 return S_OK;
1141 }
1142 }
1143 } catch (const exception& e) {
1144 error_message(pici->hwnd, e.what());
1145 }
1146
1147 return E_FAIL;
1148 }
1149
1150 HRESULT __stdcall BtrfsContextMenu::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax) {
1151 if (ignore)
1152 return E_INVALIDARG;
1153
1154 if (idCmd != 0)
1155 return E_INVALIDARG;
1156
1157 if (!bg) {
1158 if (idCmd == 0) {
1159 switch (uFlags) {
1160 case GCS_HELPTEXTA:
1161 if (LoadStringA(module, IDS_CREATE_SNAPSHOT_HELP_TEXT, pszName, cchMax))
1162 return S_OK;
1163 else
1164 return E_FAIL;
1165
1166 case GCS_HELPTEXTW:
1167 if (LoadStringW(module, IDS_CREATE_SNAPSHOT_HELP_TEXT, (LPWSTR)pszName, cchMax))
1168 return S_OK;
1169 else
1170 return E_FAIL;
1171
1172 case GCS_VALIDATEA:
1173 case GCS_VALIDATEW:
1174 return S_OK;
1175
1176 case GCS_VERBA:
1177 return StringCchCopyA(pszName, cchMax, SNAPSHOT_VERBA);
1178
1179 case GCS_VERBW:
1180 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, SNAPSHOT_VERBW);
1181
1182 default:
1183 return E_INVALIDARG;
1184 }
1185 } else if (idCmd == 1) {
1186 switch (uFlags) {
1187 case GCS_HELPTEXTA:
1188 if (LoadStringA(module, IDS_SEND_SUBVOL_HELP, pszName, cchMax))
1189 return S_OK;
1190 else
1191 return E_FAIL;
1192
1193 case GCS_HELPTEXTW:
1194 if (LoadStringW(module, IDS_SEND_SUBVOL_HELP, (LPWSTR)pszName, cchMax))
1195 return S_OK;
1196 else
1197 return E_FAIL;
1198
1199 case GCS_VALIDATEA:
1200 case GCS_VALIDATEW:
1201 return S_OK;
1202
1203 case GCS_VERBA:
1204 return StringCchCopyA(pszName, cchMax, SEND_VERBA);
1205
1206 case GCS_VERBW:
1207 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, SEND_VERBW);
1208
1209 default:
1210 return E_INVALIDARG;
1211 }
1212 } else
1213 return E_INVALIDARG;
1214 } else {
1215 if (idCmd == 0) {
1216 switch (uFlags) {
1217 case GCS_HELPTEXTA:
1218 if (LoadStringA(module, IDS_NEW_SUBVOL_HELP_TEXT, pszName, cchMax))
1219 return S_OK;
1220 else
1221 return E_FAIL;
1222
1223 case GCS_HELPTEXTW:
1224 if (LoadStringW(module, IDS_NEW_SUBVOL_HELP_TEXT, (LPWSTR)pszName, cchMax))
1225 return S_OK;
1226 else
1227 return E_FAIL;
1228
1229 case GCS_VALIDATEA:
1230 case GCS_VALIDATEW:
1231 return S_OK;
1232
1233 case GCS_VERBA:
1234 return StringCchCopyA(pszName, cchMax, NEW_SUBVOL_VERBA);
1235
1236 case GCS_VERBW:
1237 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, NEW_SUBVOL_VERBW);
1238
1239 default:
1240 return E_INVALIDARG;
1241 }
1242 } else if (idCmd == 1) {
1243 switch (uFlags) {
1244 case GCS_HELPTEXTA:
1245 if (LoadStringA(module, IDS_RECV_SUBVOL_HELP, pszName, cchMax))
1246 return S_OK;
1247 else
1248 return E_FAIL;
1249
1250 case GCS_HELPTEXTW:
1251 if (LoadStringW(module, IDS_RECV_SUBVOL_HELP, (LPWSTR)pszName, cchMax))
1252 return S_OK;
1253 else
1254 return E_FAIL;
1255
1256 case GCS_VALIDATEA:
1257 case GCS_VALIDATEW:
1258 return S_OK;
1259
1260 case GCS_VERBA:
1261 return StringCchCopyA(pszName, cchMax, RECV_VERBA);
1262
1263 case GCS_VERBW:
1264 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, RECV_VERBW);
1265
1266 default:
1267 return E_INVALIDARG;
1268 }
1269 } else if (idCmd == 2) {
1270 switch (uFlags) {
1271 case GCS_HELPTEXTA:
1272 if (LoadStringA(module, IDS_REFLINK_PASTE_HELP, pszName, cchMax))
1273 return S_OK;
1274 else
1275 return E_FAIL;
1276
1277 case GCS_HELPTEXTW:
1278 if (LoadStringW(module, IDS_REFLINK_PASTE_HELP, (LPWSTR)pszName, cchMax))
1279 return S_OK;
1280 else
1281 return E_FAIL;
1282
1283 case GCS_VALIDATEA:
1284 case GCS_VALIDATEW:
1285 return S_OK;
1286
1287 case GCS_VERBA:
1288 return StringCchCopyA(pszName, cchMax, REFLINK_VERBA);
1289
1290 case GCS_VERBW:
1291 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, REFLINK_VERBW);
1292
1293 default:
1294 return E_INVALIDARG;
1295 }
1296 } else
1297 return E_INVALIDARG;
1298 }
1299 }
1300
1301 static void reflink_copy2(const wstring& srcfn, const wstring& destdir, const wstring& destname) {
1302 win_handle source, dest;
1303 FILE_BASIC_INFO fbi;
1304 FILETIME atime, mtime;
1305 btrfs_inode_info bii;
1306 btrfs_set_inode_info bsii;
1307 ULONG bytesret;
1308 NTSTATUS Status;
1309 IO_STATUS_BLOCK iosb;
1310 btrfs_set_xattr bsxa;
1311
1312 source = CreateFileW(srcfn.c_str(), GENERIC_READ | FILE_TRAVERSE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr);
1313 if (source == INVALID_HANDLE_VALUE)
1314 throw last_error(GetLastError());
1315
1316 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii, sizeof(btrfs_inode_info));
1317 if (!NT_SUCCESS(Status))
1318 throw ntstatus_error(Status);
1319
1320 // if subvol, do snapshot instead
1321 if (bii.inode == SUBVOL_ROOT_INODE) {
1322 ULONG bcslen;
1323 btrfs_create_snapshot* bcs;
1324 win_handle dirh;
1325
1326 dirh = CreateFileW(destdir.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
1327 if (dirh == INVALID_HANDLE_VALUE)
1328 throw last_error(GetLastError());
1329
1330 bcslen = offsetof(btrfs_create_snapshot, name[0]) + (destname.length() * sizeof(WCHAR));
1331 bcs = (btrfs_create_snapshot*)malloc(bcslen);
1332 bcs->subvol = source;
1333 bcs->namelen = (uint16_t)(destname.length() * sizeof(WCHAR));
1334 memcpy(bcs->name, destname.c_str(), destname.length() * sizeof(WCHAR));
1335
1336 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, bcslen, nullptr, 0);
1337 if (!NT_SUCCESS(Status)) {
1338 free(bcs);
1339 throw ntstatus_error(Status);
1340 }
1341
1342 free(bcs);
1343
1344 return;
1345 }
1346
1347 if (!GetFileInformationByHandleEx(source, FileBasicInfo, &fbi, sizeof(FILE_BASIC_INFO)))
1348 throw last_error(GetLastError());
1349
1350 if (bii.type == BTRFS_TYPE_CHARDEV || bii.type == BTRFS_TYPE_BLOCKDEV || bii.type == BTRFS_TYPE_FIFO || bii.type == BTRFS_TYPE_SOCKET) {
1351 win_handle dirh;
1352 ULONG bmnsize;
1353 btrfs_mknod* bmn;
1354
1355 dirh = CreateFileW(destdir.c_str(), FILE_ADD_FILE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
1356 if (dirh == INVALID_HANDLE_VALUE)
1357 throw last_error(GetLastError());
1358
1359 bmnsize = offsetof(btrfs_mknod, name[0]) + (destname.length() * sizeof(WCHAR));
1360 bmn = (btrfs_mknod*)malloc(bmnsize);
1361
1362 bmn->inode = 0;
1363 bmn->type = bii.type;
1364 bmn->st_rdev = bii.st_rdev;
1365 bmn->namelen = (uint16_t)(destname.length() * sizeof(WCHAR));
1366 memcpy(bmn->name, destname.c_str(), bmn->namelen);
1367
1368 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_MKNOD, bmn, bmnsize, nullptr, 0);
1369 if (!NT_SUCCESS(Status)) {
1370 free(bmn);
1371 throw ntstatus_error(Status);
1372 }
1373
1374 free(bmn);
1375
1376 dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
1377 } else if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1378 if (CreateDirectoryExW(srcfn.c_str(), (destdir + destname).c_str(), nullptr))
1379 dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1380 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
1381 else
1382 dest = INVALID_HANDLE_VALUE;
1383 } else
1384 dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source);
1385
1386 if (dest == INVALID_HANDLE_VALUE)
1387 throw last_error(GetLastError());
1388
1389 memset(&bsii, 0, sizeof(btrfs_set_inode_info));
1390
1391 bsii.flags_changed = true;
1392 bsii.flags = bii.flags;
1393
1394 if (bii.flags & BTRFS_INODE_COMPRESS) {
1395 bsii.compression_type_changed = true;
1396 bsii.compression_type = bii.compression_type;
1397 }
1398
1399 try {
1400 Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);
1401 if (!NT_SUCCESS(Status))
1402 throw ntstatus_error(Status);
1403
1404 if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1405 if (!(fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
1406 WIN32_FIND_DATAW fff;
1407 wstring qs;
1408
1409 qs = srcfn;
1410 qs += L"\\*";
1411
1412 fff_handle h = FindFirstFileW(qs.c_str(), &fff);
1413 if (h != INVALID_HANDLE_VALUE) {
1414 do {
1415 wstring fn2;
1416
1417 if (fff.cFileName[0] == '.' && (fff.cFileName[1] == 0 || (fff.cFileName[1] == '.' && fff.cFileName[2] == 0)))
1418 continue;
1419
1420 fn2 = srcfn;
1421 fn2 += L"\\";
1422 fn2 += fff.cFileName;
1423
1424 reflink_copy2(fn2, destdir + destname + L"\\", fff.cFileName);
1425 } while (FindNextFileW(h, &fff));
1426 }
1427 }
1428
1429 // CreateDirectoryExW also copies streams, no need to do it here
1430 } else {
1431 WIN32_FIND_STREAM_DATA fsd;
1432
1433 if (fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
1434 reparse_header rh;
1435 ULONG rplen;
1436 uint8_t* rp;
1437
1438 if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, &rh, sizeof(reparse_header), &bytesret, nullptr)) {
1439 if (GetLastError() != ERROR_MORE_DATA)
1440 throw last_error(GetLastError());
1441 }
1442
1443 rplen = sizeof(reparse_header) + rh.ReparseDataLength;
1444 rp = (uint8_t*)malloc(rplen);
1445
1446 try {
1447 if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, rp, rplen, &bytesret, nullptr))
1448 throw last_error(GetLastError());
1449
1450 if (!DeviceIoControl(dest, FSCTL_SET_REPARSE_POINT, rp, rplen, nullptr, 0, &bytesret, nullptr))
1451 throw last_error(GetLastError());
1452 } catch (...) {
1453 free(rp);
1454 throw;
1455 }
1456
1457 free(rp);
1458 } else {
1459 FILE_STANDARD_INFO fsi;
1460 FILE_END_OF_FILE_INFO feofi;
1461 FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib;
1462 FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib;
1463 DUPLICATE_EXTENTS_DATA ded;
1464 uint64_t offset, alloc_size;
1465 ULONG maxdup;
1466
1467 if (!GetFileInformationByHandleEx(source, FileStandardInfo, &fsi, sizeof(FILE_STANDARD_INFO)))
1468 throw last_error(GetLastError());
1469
1470 if (!DeviceIoControl(source, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &fgiib, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER), &bytesret, nullptr))
1471 throw last_error(GetLastError());
1472
1473 if (fbi.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) {
1474 if (!DeviceIoControl(dest, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesret, nullptr))
1475 throw last_error(GetLastError());
1476 }
1477
1478 fsiib.ChecksumAlgorithm = fgiib.ChecksumAlgorithm;
1479 fsiib.Reserved = 0;
1480 fsiib.Flags = fgiib.Flags;
1481 if (!DeviceIoControl(dest, FSCTL_SET_INTEGRITY_INFORMATION, &fsiib, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER), nullptr, 0, &bytesret, nullptr))
1482 throw last_error(GetLastError());
1483
1484 feofi.EndOfFile = fsi.EndOfFile;
1485 if (!SetFileInformationByHandle(dest, FileEndOfFileInfo, &feofi, sizeof(FILE_END_OF_FILE_INFO)))
1486 throw last_error(GetLastError());
1487
1488 ded.FileHandle = source;
1489 maxdup = 0xffffffff - fgiib.ClusterSizeInBytes + 1;
1490
1491 alloc_size = sector_align(fsi.EndOfFile.QuadPart, fgiib.ClusterSizeInBytes);
1492
1493 offset = 0;
1494 while (offset < alloc_size) {
1495 ded.SourceFileOffset.QuadPart = ded.TargetFileOffset.QuadPart = offset;
1496 ded.ByteCount.QuadPart = maxdup < (alloc_size - offset) ? maxdup : (alloc_size - offset);
1497 if (!DeviceIoControl(dest, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA), nullptr, 0, &bytesret, nullptr))
1498 throw last_error(GetLastError());
1499
1500 offset += ded.ByteCount.QuadPart;
1501 }
1502 }
1503
1504 fff_handle h = FindFirstStreamW(srcfn.c_str(), FindStreamInfoStandard, &fsd, 0);
1505 if (h != INVALID_HANDLE_VALUE) {
1506 do {
1507 wstring sn;
1508
1509 sn = fsd.cStreamName;
1510
1511 if (sn != L"::$DATA" && sn.length() > 6 && sn.substr(sn.length() - 6, 6) == L":$DATA") {
1512 win_handle stream;
1513 uint8_t* data = nullptr;
1514
1515 if (fsd.StreamSize.QuadPart > 0) {
1516 wstring fn2;
1517 uint16_t stream_size = (uint16_t)fsd.StreamSize.QuadPart;
1518
1519 fn2 = srcfn;
1520 fn2 += sn;
1521
1522 stream = CreateFileW(fn2.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
1523
1524 if (stream == INVALID_HANDLE_VALUE)
1525 throw last_error(GetLastError());
1526
1527 // We can get away with this because our streams are guaranteed to be below 64 KB -
1528 // don't do this on NTFS!
1529 data = (uint8_t*)malloc(stream_size);
1530
1531 if (!ReadFile(stream, data, stream_size, &bytesret, nullptr)) {
1532 free(data);
1533 throw last_error(GetLastError());
1534 }
1535 }
1536
1537 stream = CreateFileW((destdir + destname + sn).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, nullptr);
1538
1539 if (stream == INVALID_HANDLE_VALUE) {
1540 if (data) free(data);
1541 throw last_error(GetLastError());
1542 }
1543
1544 if (data) {
1545 if (!WriteFile(stream, data, (uint32_t)fsd.StreamSize.QuadPart, &bytesret, nullptr)) {
1546 free(data);
1547 throw last_error(GetLastError());
1548 }
1549
1550 free(data);
1551 }
1552 }
1553 } while (FindNextStreamW(h, &fsd));
1554 }
1555 }
1556
1557 atime.dwLowDateTime = fbi.LastAccessTime.LowPart;
1558 atime.dwHighDateTime = fbi.LastAccessTime.HighPart;
1559 mtime.dwLowDateTime = fbi.LastWriteTime.LowPart;
1560 mtime.dwHighDateTime = fbi.LastWriteTime.HighPart;
1561 SetFileTime(dest, nullptr, &atime, &mtime);
1562
1563 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, &bsxa, sizeof(btrfs_set_xattr));
1564
1565 if (Status == STATUS_BUFFER_OVERFLOW || (NT_SUCCESS(Status) && bsxa.valuelen > 0)) {
1566 ULONG xalen = 0;
1567 btrfs_set_xattr *xa = nullptr, *xa2;
1568
1569 do {
1570 xalen += 1024;
1571
1572 if (xa) free(xa);
1573 xa = (btrfs_set_xattr*)malloc(xalen);
1574
1575 Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, xa, xalen);
1576 } while (Status == STATUS_BUFFER_OVERFLOW);
1577
1578 if (!NT_SUCCESS(Status)) {
1579 free(xa);
1580 throw ntstatus_error(Status);
1581 }
1582
1583 xa2 = xa;
1584 while (xa2->valuelen > 0) {
1585 Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, xa2,
1586 offsetof(btrfs_set_xattr, data[0]) + xa2->namelen + xa2->valuelen, nullptr, 0);
1587 if (!NT_SUCCESS(Status)) {
1588 free(xa);
1589 throw ntstatus_error(Status);
1590 }
1591 xa2 = (btrfs_set_xattr*)&xa2->data[xa2->namelen + xa2->valuelen];
1592 }
1593
1594 free(xa);
1595 } else if (!NT_SUCCESS(Status))
1596 throw ntstatus_error(Status);
1597 } catch (...) {
1598 FILE_DISPOSITION_INFO fdi;
1599
1600 fdi.DeleteFile = true;
1601 SetFileInformationByHandle(dest, FileDispositionInfo, &fdi, sizeof(FILE_DISPOSITION_INFO));
1602
1603 throw;
1604 }
1605 }
1606
1607 #ifdef __REACTOS__
1608 extern "C" {
1609 #endif
1610
1611 void CALLBACK ReflinkCopyW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
1612 vector<wstring> args;
1613
1614 command_line_to_args(lpszCmdLine, args);
1615
1616 if (args.size() >= 2) {
1617 bool dest_is_dir = false;
1618 wstring dest = args[args.size() - 1], destdir, destname;
1619 WCHAR volpath2[MAX_PATH];
1620
1621 {
1622 win_handle destdirh = CreateFileW(dest.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1623 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
1624
1625 if (destdirh != INVALID_HANDLE_VALUE) {
1626 BY_HANDLE_FILE_INFORMATION bhfi;
1627
1628 if (GetFileInformationByHandle(destdirh, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1629 dest_is_dir = true;
1630
1631 destdir = dest;
1632 if (destdir.substr(destdir.length() - 1, 1) != L"\\")
1633 destdir += L"\\";
1634 }
1635 }
1636 }
1637
1638 if (!dest_is_dir) {
1639 size_t found = dest.rfind(L"\\");
1640
1641 if (found == wstring::npos) {
1642 destdir = L"";
1643 destname = dest;
1644 } else {
1645 destdir = dest.substr(0, found);
1646 destname = dest.substr(found + 1);
1647 }
1648 }
1649
1650 if (!GetVolumePathNameW(dest.c_str(), volpath2, sizeof(volpath2) / sizeof(WCHAR)))
1651 return;
1652
1653 for (unsigned int i = 0; i < args.size() - 1; i++) {
1654 WIN32_FIND_DATAW ffd;
1655
1656 fff_handle h = FindFirstFileW(args[i].c_str(), &ffd);
1657 if (h != INVALID_HANDLE_VALUE) {
1658 WCHAR volpath1[MAX_PATH];
1659 wstring path = args[i];
1660 size_t found = path.rfind(L"\\");
1661
1662 if (found == wstring::npos)
1663 path = L"";
1664 else
1665 path = path.substr(0, found);
1666
1667 path += L"\\";
1668
1669 if (get_volume_path_parent(path.c_str(), volpath1, sizeof(volpath1) / sizeof(WCHAR))) {
1670 if (!wcscmp(volpath1, volpath2)) {
1671 do {
1672 try {
1673 reflink_copy2(path + ffd.cFileName, destdir, dest_is_dir ? ffd.cFileName : destname);
1674 } catch (const exception& e) {
1675 cerr << "Error: " << e.what() << endl;
1676 }
1677 } while (FindNextFileW(h, &ffd));
1678 }
1679 }
1680 }
1681 }
1682 }
1683 }
1684
1685 #ifdef __REACTOS__
1686 } /* extern "C" */
1687 #endif