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