xref: /reactos/dll/shellext/shellbtrfs/send.cpp (revision 53221834)
1 /* Copyright (c) Mark Harmstone 2017
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 #include "send.h"
20 #include "resource.h"
21 #include <stddef.h>
22 #include <shlobj.h>
23 #ifdef __REACTOS__
24 #undef DeleteFile
25 #endif
26 #include <iostream>
27 
28 #define SEND_BUFFER_LEN 1048576
29 
30 DWORD BtrfsSend::Thread() {
31     try {
32         NTSTATUS Status;
33         IO_STATUS_BLOCK iosb;
34         btrfs_send_subvol* bss;
35         btrfs_send_header header;
36         btrfs_send_command end;
37         ULONG i;
38 
39         buf = (char*)malloc(SEND_BUFFER_LEN);
40 
41         try {
42             dirh = CreateFileW(subvol.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
43             if (dirh == INVALID_HANDLE_VALUE)
44                 throw string_error(IDS_SEND_CANT_OPEN_DIR, subvol.c_str(), GetLastError(), format_message(GetLastError()).c_str());
45 
46             try {
47                 size_t bss_size = offsetof(btrfs_send_subvol, clones[0]) + (clones.size() * sizeof(HANDLE));
48                 bss = (btrfs_send_subvol*)malloc(bss_size);
49                 memset(bss, 0, bss_size);
50 
51                 if (incremental) {
52                     WCHAR parent[MAX_PATH];
53                     HANDLE parenth;
54 
55                     parent[0] = 0;
56 
57                     GetDlgItemTextW(hwnd, IDC_PARENT_SUBVOL, parent, sizeof(parent) / sizeof(WCHAR));
58 
59                     parenth = CreateFileW(parent, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
60                     if (parenth == INVALID_HANDLE_VALUE)
61                         throw string_error(IDS_SEND_CANT_OPEN_DIR, parent, GetLastError(), format_message(GetLastError()).c_str());
62 
63                     bss->parent = parenth;
64                 } else
65                     bss->parent = nullptr;
66 
67                 bss->num_clones = (ULONG)clones.size();
68 
69                 for (i = 0; i < bss->num_clones; i++) {
70                     HANDLE h;
71 
72                     h = CreateFileW(clones[i].c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
73                     if (h == INVALID_HANDLE_VALUE) {
74                         auto le = GetLastError();
75                         ULONG j;
76 
77                         for (j = 0; j < i; j++) {
78                             CloseHandle(bss->clones[j]);
79                         }
80 
81                         if (bss->parent) CloseHandle(bss->parent);
82 
83                         throw string_error(IDS_SEND_CANT_OPEN_DIR, clones[i].c_str(), le, format_message(le).c_str());
84                     }
85 
86                     bss->clones[i] = h;
87                 }
88 
89                 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SEND_SUBVOL, bss, (ULONG)bss_size, nullptr, 0);
90 
91                 for (i = 0; i < bss->num_clones; i++) {
92                     CloseHandle(bss->clones[i]);
93                 }
94 
95                 if (!NT_SUCCESS(Status)) {
96                     if (Status == (NTSTATUS)STATUS_INVALID_PARAMETER) {
97                         BY_HANDLE_FILE_INFORMATION fileinfo;
98                         if (!GetFileInformationByHandle(dirh, &fileinfo)) {
99                             auto le = GetLastError();
100                             if (bss->parent) CloseHandle(bss->parent);
101                             throw string_error(IDS_SEND_GET_FILE_INFO_FAILED, le, format_message(le).c_str());
102                         }
103 
104                         if (!(fileinfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {
105                             if (bss->parent) CloseHandle(bss->parent);
106                             throw string_error(IDS_SEND_NOT_READONLY);
107                         }
108 
109                         if (bss->parent) {
110                             if (!GetFileInformationByHandle(bss->parent, &fileinfo)) {
111                                 auto le = GetLastError();
112                                 CloseHandle(bss->parent);
113                                 throw string_error(IDS_SEND_GET_FILE_INFO_FAILED, le, format_message(le).c_str());
114                             }
115 
116                             if (!(fileinfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {
117                                 CloseHandle(bss->parent);
118                                 throw string_error(IDS_SEND_PARENT_NOT_READONLY);
119                             }
120                         }
121                     }
122 
123                     if (bss->parent) CloseHandle(bss->parent);
124                     throw string_error(IDS_SEND_FSCTL_BTRFS_SEND_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str());
125                 }
126 
127                 if (bss->parent) CloseHandle(bss->parent);
128 
129                 stream = CreateFileW(file, FILE_WRITE_DATA | DELETE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
130                 if (stream == INVALID_HANDLE_VALUE)
131                     throw string_error(IDS_SEND_CANT_OPEN_FILE, file, GetLastError(), format_message(GetLastError()).c_str());
132 
133                 try {
134                     memcpy(header.magic, BTRFS_SEND_MAGIC, sizeof(header.magic));
135                     header.version = 1;
136 
137                     if (!WriteFile(stream, &header, sizeof(header), nullptr, nullptr))
138                         throw string_error(IDS_SEND_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());
139 
140                     do {
141                         Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_READ_SEND_BUFFER, nullptr, 0, buf, SEND_BUFFER_LEN);
142 
143                         if (NT_SUCCESS(Status)) {
144                             if (!WriteFile(stream, buf, (DWORD)iosb.Information, nullptr, nullptr))
145                                 throw string_error(IDS_SEND_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());
146                         }
147                     } while (NT_SUCCESS(Status));
148 
149                     if (Status != STATUS_END_OF_FILE)
150                         throw string_error(IDS_SEND_FSCTL_BTRFS_READ_SEND_BUFFER_FAILED, Status, format_ntstatus(Status).c_str());
151 
152                     end.length = 0;
153                     end.cmd = BTRFS_SEND_CMD_END;
154                     end.csum = 0x9dc96c50;
155 
156                     if (!WriteFile(stream, &end, sizeof(end), nullptr, nullptr))
157                         throw string_error(IDS_SEND_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());
158 
159                     SetEndOfFile(stream);
160                 } catch (...) {
161                     FILE_DISPOSITION_INFO fdi;
162 
163                     fdi.DeleteFile = true;
164 
165                     Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);
166 
167                     CloseHandle(stream);
168                     stream = INVALID_HANDLE_VALUE;
169 
170                     if (!NT_SUCCESS(Status))
171                         throw ntstatus_error(Status);
172 
173                     throw;
174                 }
175 
176                 CloseHandle(stream);
177                 stream = INVALID_HANDLE_VALUE;
178             } catch (...) {
179                 CloseHandle(dirh);
180                 dirh = INVALID_HANDLE_VALUE;
181 
182                 throw;
183             }
184 
185             CloseHandle(dirh);
186             dirh = INVALID_HANDLE_VALUE;
187         } catch (...) {
188             free(buf);
189             buf = nullptr;
190 
191             started = false;
192 
193             SetDlgItemTextW(hwnd, IDCANCEL, closetext);
194             EnableWindow(GetDlgItem(hwnd, IDOK), true);
195             EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), true);
196             EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true);
197 
198             throw;
199         }
200     } catch (const exception& e) {
201         auto msg = utf8_to_utf16(e.what());
202 
203         SetDlgItemTextW(hwnd, IDC_SEND_STATUS, msg.c_str());
204         return 0;
205     }
206 
207 
208     free(buf);
209     buf = nullptr;
210 
211     started = false;
212 
213     SetDlgItemTextW(hwnd, IDCANCEL, closetext);
214     EnableWindow(GetDlgItem(hwnd, IDOK), true);
215     EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), true);
216     EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true);
217 
218     wstring success;
219 
220     load_string(module, IDS_SEND_SUCCESS, success);
221 
222     SetDlgItemTextW(hwnd, IDC_SEND_STATUS, success.c_str());
223 
224     return 0;
225 }
226 
227 static DWORD WINAPI send_thread(LPVOID lpParameter) {
228     BtrfsSend* bs = (BtrfsSend*)lpParameter;
229 
230     return bs->Thread();
231 }
232 
233 void BtrfsSend::StartSend(HWND hwnd) {
234     wstring s;
235     HWND cl;
236 
237     if (started)
238         return;
239 
240     GetDlgItemTextW(hwnd, IDC_STREAM_DEST, file, sizeof(file) / sizeof(WCHAR));
241 
242     if (file[0] == 0)
243         return;
244 
245     if (incremental) {
246         WCHAR parent[MAX_PATH];
247 
248         GetDlgItemTextW(hwnd, IDC_PARENT_SUBVOL, parent, sizeof(parent) / sizeof(WCHAR));
249 
250         if (parent[0] == 0)
251             return;
252     }
253 
254     started = true;
255 
256     wstring writing;
257 
258     load_string(module, IDS_SEND_WRITING, writing);
259 
260     SetDlgItemTextW(hwnd, IDC_SEND_STATUS, writing.c_str());
261 
262     load_string(module, IDS_SEND_CANCEL, s);
263     SetDlgItemTextW(hwnd, IDCANCEL, s.c_str());
264 
265     EnableWindow(GetDlgItem(hwnd, IDOK), false);
266     EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), false);
267     EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), false);
268 
269     clones.clear();
270 
271     cl = GetDlgItem(hwnd, IDC_CLONE_LIST);
272     auto num_clones = SendMessageW(cl, LB_GETCOUNT, 0, 0);
273 
274     if (num_clones != LB_ERR) {
275         for (unsigned int i = 0; i < (unsigned int)num_clones; i++) {
276             WCHAR* t;
277 
278             auto len = SendMessageW(cl, LB_GETTEXTLEN, i, 0);
279             t = (WCHAR*)malloc((len + 1) * sizeof(WCHAR));
280 
281             SendMessageW(cl, LB_GETTEXT, i, (LPARAM)t);
282 
283             clones.push_back(t);
284 
285             free(t);
286         }
287     }
288 
289     thread = CreateThread(nullptr, 0, send_thread, this, 0, nullptr);
290 
291     if (!thread)
292         throw last_error(GetLastError());
293 }
294 
295 void BtrfsSend::Browse(HWND hwnd) {
296     OPENFILENAMEW ofn;
297 
298     file[0] = 0;
299 
300     memset(&ofn, 0, sizeof(OPENFILENAMEW));
301     ofn.lStructSize = sizeof(OPENFILENAMEW);
302     ofn.hwndOwner = hwnd;
303     ofn.hInstance = module;
304     ofn.lpstrFile = file;
305     ofn.nMaxFile = sizeof(file) / sizeof(WCHAR);
306     ofn.Flags = OFN_EXPLORER;
307 
308     if (!GetSaveFileNameW(&ofn))
309         return;
310 
311     SetDlgItemTextW(hwnd, IDC_STREAM_DEST, file);
312 }
313 
314 void BtrfsSend::BrowseParent(HWND hwnd) {
315     BROWSEINFOW bi;
316     PIDLIST_ABSOLUTE root, pidl;
317     HRESULT hr;
318     WCHAR parent[MAX_PATH], volpathw[MAX_PATH];
319     NTSTATUS Status;
320     IO_STATUS_BLOCK iosb;
321     btrfs_get_file_ids bgfi;
322 
323     if (!GetVolumePathNameW(subvol.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1))
324         throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str());
325 
326     hr = SHParseDisplayName(volpathw, 0, &root, 0, 0);
327     if (FAILED(hr))
328         throw string_error(IDS_SHPARSEDISPLAYNAME_FAILED);
329 
330     memset(&bi, 0, sizeof(BROWSEINFOW));
331 
332     bi.hwndOwner = hwnd;
333     bi.pidlRoot = root;
334     bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON;
335 
336     pidl = SHBrowseForFolderW(&bi);
337 
338     if (!pidl)
339         return;
340 
341     if (!SHGetPathFromIDListW(pidl, parent))
342         throw string_error(IDS_SHGETPATHFROMIDLIST_FAILED);
343 
344     {
345         win_handle h = CreateFileW(parent, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
346         if (h == INVALID_HANDLE_VALUE)
347             throw string_error(IDS_SEND_CANT_OPEN_DIR, parent, GetLastError(), format_message(GetLastError()).c_str());
348 
349         Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids));
350         if (!NT_SUCCESS(Status))
351             throw string_error(IDS_GET_FILE_IDS_FAILED, Status, format_ntstatus(Status).c_str());
352     }
353 
354     if (bgfi.inode != 0x100 || bgfi.top)
355         throw string_error(IDS_NOT_SUBVOL);
356 
357     SetDlgItemTextW(hwnd, IDC_PARENT_SUBVOL, parent);
358 }
359 
360 void BtrfsSend::AddClone(HWND hwnd) {
361     BROWSEINFOW bi;
362     PIDLIST_ABSOLUTE root, pidl;
363     HRESULT hr;
364     WCHAR path[MAX_PATH], volpathw[MAX_PATH];
365     NTSTATUS Status;
366     IO_STATUS_BLOCK iosb;
367     btrfs_get_file_ids bgfi;
368 
369     if (!GetVolumePathNameW(subvol.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1))
370         throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str());
371 
372     hr = SHParseDisplayName(volpathw, 0, &root, 0, 0);
373     if (FAILED(hr))
374         throw string_error(IDS_SHPARSEDISPLAYNAME_FAILED);
375 
376     memset(&bi, 0, sizeof(BROWSEINFOW));
377 
378     bi.hwndOwner = hwnd;
379     bi.pidlRoot = root;
380     bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON;
381 
382     pidl = SHBrowseForFolderW(&bi);
383 
384     if (!pidl)
385         return;
386 
387     if (!SHGetPathFromIDListW(pidl, path))
388         throw string_error(IDS_SHGETPATHFROMIDLIST_FAILED);
389 
390     {
391         win_handle h = CreateFileW(path, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
392         if (h == INVALID_HANDLE_VALUE)
393             throw string_error(IDS_SEND_CANT_OPEN_DIR, path, GetLastError(), format_message(GetLastError()).c_str());
394 
395         Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids));
396         if (!NT_SUCCESS(Status))
397             throw string_error(IDS_GET_FILE_IDS_FAILED, Status, format_ntstatus(Status).c_str());
398     }
399 
400     if (bgfi.inode != 0x100 || bgfi.top)
401         throw string_error(IDS_NOT_SUBVOL);
402 
403     SendMessageW(GetDlgItem(hwnd, IDC_CLONE_LIST), LB_ADDSTRING, 0, (LPARAM)path);
404 }
405 
406 void BtrfsSend::RemoveClone(HWND hwnd) {
407     LRESULT sel;
408     HWND cl = GetDlgItem(hwnd, IDC_CLONE_LIST);
409 
410     sel = SendMessageW(cl, LB_GETCURSEL, 0, 0);
411 
412     if (sel == LB_ERR)
413         return;
414 
415     SendMessageW(cl, LB_DELETESTRING, sel, 0);
416 
417     if (SendMessageW(cl, LB_GETCURSEL, 0, 0) == LB_ERR)
418         EnableWindow(GetDlgItem(hwnd, IDC_CLONE_REMOVE), false);
419 }
420 
421 INT_PTR BtrfsSend::SendDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
422     try {
423         switch (uMsg) {
424             case WM_INITDIALOG:
425                 this->hwnd = hwndDlg;
426 
427                 GetDlgItemTextW(hwndDlg, IDCANCEL, closetext, sizeof(closetext) / sizeof(WCHAR));
428             break;
429 
430             case WM_COMMAND:
431                 switch (HIWORD(wParam)) {
432                     case BN_CLICKED:
433                         switch (LOWORD(wParam)) {
434                             case IDOK:
435                                 StartSend(hwndDlg);
436                             return true;
437 
438                             case IDCANCEL:
439                                 if (started) {
440                                     TerminateThread(thread, 0);
441 
442                                     if (stream != INVALID_HANDLE_VALUE) {
443                                         NTSTATUS Status;
444                                         FILE_DISPOSITION_INFO fdi;
445                                         IO_STATUS_BLOCK iosb;
446 
447                                         fdi.DeleteFile = true;
448 
449                                         Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);
450 
451                                         CloseHandle(stream);
452 
453                                         if (!NT_SUCCESS(Status))
454                                             throw ntstatus_error(Status);
455                                     }
456 
457                                     if (dirh != INVALID_HANDLE_VALUE)
458                                         CloseHandle(dirh);
459 
460                                     started = false;
461 
462                                     SetDlgItemTextW(hwndDlg, IDCANCEL, closetext);
463 
464                                     EnableWindow(GetDlgItem(hwnd, IDOK), true);
465                                     EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), true);
466                                     EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true);
467                                 } else
468                                     EndDialog(hwndDlg, 1);
469                             return true;
470 
471                             case IDC_BROWSE:
472                                 Browse(hwndDlg);
473                             return true;
474 
475                             case IDC_INCREMENTAL:
476                                 incremental = IsDlgButtonChecked(hwndDlg, LOWORD(wParam));
477 
478                                 EnableWindow(GetDlgItem(hwnd, IDC_PARENT_SUBVOL), incremental);
479                                 EnableWindow(GetDlgItem(hwnd, IDC_PARENT_BROWSE), incremental);
480                             return true;
481 
482                             case IDC_PARENT_BROWSE:
483                                 BrowseParent(hwndDlg);
484                             return true;
485 
486                             case IDC_CLONE_ADD:
487                                 AddClone(hwndDlg);
488                             return true;
489 
490                             case IDC_CLONE_REMOVE:
491                                 RemoveClone(hwndDlg);
492                             return true;
493                         }
494                     break;
495 
496                     case LBN_SELCHANGE:
497                         switch (LOWORD(wParam)) {
498                             case IDC_CLONE_LIST:
499                                 EnableWindow(GetDlgItem(hwnd, IDC_CLONE_REMOVE), true);
500                             return true;
501                         }
502                     break;
503                 }
504             break;
505         }
506     } catch (const exception& e) {
507         error_message(hwnd, e.what());
508     }
509 
510     return false;
511 }
512 
513 static INT_PTR CALLBACK stub_SendDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
514     BtrfsSend* bs;
515 
516     if (uMsg == WM_INITDIALOG) {
517         SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);
518         bs = (BtrfsSend*)lParam;
519     } else
520         bs = (BtrfsSend*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
521 
522     if (bs)
523         return bs->SendDlgProc(hwndDlg, uMsg, wParam, lParam);
524     else
525         return false;
526 }
527 
528 void BtrfsSend::Open(HWND hwnd, LPWSTR path) {
529     subvol = path;
530 
531     if (DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_SEND_SUBVOL), hwnd, stub_SendDlgProc, (LPARAM)this) <= 0)
532         throw last_error(GetLastError());
533 }
534 
535 #ifdef __REACTOS__
536 extern "C" {
537 #endif
538 
539 void CALLBACK SendSubvolGUIW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
540     try {
541         win_handle token;
542         TOKEN_PRIVILEGES tp;
543         LUID luid;
544 
545         set_dpi_aware();
546 
547         if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
548             throw last_error(GetLastError());
549 
550         if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid))
551             throw last_error(GetLastError());
552 
553         tp.PrivilegeCount = 1;
554         tp.Privileges[0].Luid = luid;
555         tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
556 
557         if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))
558             throw last_error(GetLastError());
559 
560         BtrfsSend bs;
561 
562         bs.Open(hwnd, lpszCmdLine);
563     } catch (const exception& e) {
564         error_message(hwnd, e.what());
565     }
566 }
567 
568 #ifdef __REACTOS__
569 } /* extern "C" */
570 #endif
571 
572 static void send_subvol(const wstring& subvol, const wstring& file, const wstring& parent, const vector<wstring>& clones) {
573     char* buf;
574     win_handle dirh, stream;
575     ULONG i;
576     btrfs_send_subvol* bss;
577     IO_STATUS_BLOCK iosb;
578     NTSTATUS Status;
579     btrfs_send_header header;
580     btrfs_send_command end;
581 
582     buf = (char*)malloc(SEND_BUFFER_LEN);
583 
584     try {
585         dirh = CreateFileW(subvol.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
586         if (dirh == INVALID_HANDLE_VALUE)
587             throw last_error(GetLastError());
588 
589         stream = CreateFileW(file.c_str(), FILE_WRITE_DATA | DELETE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
590         if (stream == INVALID_HANDLE_VALUE)
591             throw last_error(GetLastError());
592 
593         try {
594             size_t bss_size = offsetof(btrfs_send_subvol, clones[0]) + (clones.size() * sizeof(HANDLE));
595             bss = (btrfs_send_subvol*)malloc(bss_size);
596             memset(bss, 0, bss_size);
597 
598             if (parent != L"") {
599                 HANDLE parenth;
600 
601                 parenth = CreateFileW(parent.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
602                 if (parenth == INVALID_HANDLE_VALUE)
603                     throw last_error(GetLastError());
604 
605                 bss->parent = parenth;
606             } else
607                 bss->parent = nullptr;
608 
609             bss->num_clones = (ULONG)clones.size();
610 
611             for (i = 0; i < bss->num_clones; i++) {
612                 HANDLE h;
613 
614                 h = CreateFileW(clones[i].c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
615                 if (h == INVALID_HANDLE_VALUE) {
616                     auto le = GetLastError();
617                     ULONG j;
618 
619                     for (j = 0; j < i; j++) {
620                         CloseHandle(bss->clones[j]);
621                     }
622 
623                     if (bss->parent) CloseHandle(bss->parent);
624 
625                     throw last_error(le);
626                 }
627 
628                 bss->clones[i] = h;
629             }
630 
631             Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SEND_SUBVOL, bss, (ULONG)bss_size, nullptr, 0);
632 
633             for (i = 0; i < bss->num_clones; i++) {
634                 CloseHandle(bss->clones[i]);
635             }
636 
637             if (bss->parent) CloseHandle(bss->parent);
638 
639             if (!NT_SUCCESS(Status))
640                 throw ntstatus_error(Status);
641 
642             memcpy(header.magic, BTRFS_SEND_MAGIC, sizeof(header.magic));
643             header.version = 1;
644 
645             if (!WriteFile(stream, &header, sizeof(header), nullptr, nullptr))
646                 throw last_error(GetLastError());
647 
648             do {
649                 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_READ_SEND_BUFFER, nullptr, 0, buf, SEND_BUFFER_LEN);
650 
651                 if (NT_SUCCESS(Status))
652                     WriteFile(stream, buf, (DWORD)iosb.Information, nullptr, nullptr);
653             } while (NT_SUCCESS(Status));
654 
655             if (Status != STATUS_END_OF_FILE)
656                 throw ntstatus_error(Status);
657 
658             end.length = 0;
659             end.cmd = BTRFS_SEND_CMD_END;
660             end.csum = 0x9dc96c50;
661 
662             if (!WriteFile(stream, &end, sizeof(end), nullptr, nullptr))
663                 throw last_error(GetLastError());
664 
665             SetEndOfFile(stream);
666         } catch (...) {
667             FILE_DISPOSITION_INFO fdi;
668 
669             fdi.DeleteFile = true;
670 
671             Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);
672             if (!NT_SUCCESS(Status))
673                 throw ntstatus_error(Status);
674 
675             throw;
676         }
677     } catch (...) {
678         free(buf);
679         throw;
680     }
681 
682     free(buf);
683 }
684 
685 #ifdef __REACTOS__
686 extern "C" {
687 #endif
688 
689 void CALLBACK SendSubvolW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
690     vector<wstring> args;
691     wstring subvol = L"", parent = L"", file = L"";
692     vector<wstring> clones;
693 
694     command_line_to_args(lpszCmdLine, args);
695 
696     if (args.size() >= 2) {
697         TOKEN_PRIVILEGES tp;
698         LUID luid;
699 
700         {
701             win_handle token;
702 
703             if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
704                 return;
705 
706             if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid))
707                 return;
708 
709             tp.PrivilegeCount = 1;
710             tp.Privileges[0].Luid = luid;
711             tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
712 
713             if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))
714                 return;
715         }
716 
717         for (unsigned int i = 0; i < args.size(); i++) {
718             if (args[i][0] == '-') {
719                 if (args[i][2] == 0 && i < args.size() - 1) {
720                     if (args[i][1] == 'p') {
721                         parent = args[i+1];
722                         i++;
723                     } else if (args[i][1] == 'c') {
724                         clones.push_back(args[i+1]);
725                         i++;
726                     }
727                 }
728             } else {
729                 if (subvol == L"")
730                     subvol = args[i];
731                 else if (file == L"")
732                     file = args[i];
733             }
734         }
735 
736         if (subvol != L"" && file != L"") {
737             try {
738                 send_subvol(subvol, file, parent, clones);
739             } catch (const exception& e) {
740                 cerr << "Error: " << e.what() << endl;
741             }
742         }
743     }
744 }
745 
746 #ifdef __REACTOS__
747 } /* extern "C" */
748 #endif
749