xref: /reactos/dll/shellext/shellbtrfs/send.cpp (revision f986527d)
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 
307     if (!GetSaveFileNameW(&ofn))
308         return;
309 
310     SetDlgItemTextW(hwnd, IDC_STREAM_DEST, file);
311 }
312 
313 void BtrfsSend::BrowseParent(HWND hwnd) {
314     BROWSEINFOW bi;
315     PIDLIST_ABSOLUTE root, pidl;
316     HRESULT hr;
317     WCHAR parent[MAX_PATH], volpathw[MAX_PATH];
318     NTSTATUS Status;
319     IO_STATUS_BLOCK iosb;
320     btrfs_get_file_ids bgfi;
321 
322     if (!GetVolumePathNameW(subvol.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1))
323         throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str());
324 
325     hr = SHParseDisplayName(volpathw, 0, &root, 0, 0);
326     if (FAILED(hr))
327         throw string_error(IDS_SHPARSEDISPLAYNAME_FAILED);
328 
329     memset(&bi, 0, sizeof(BROWSEINFOW));
330 
331     bi.hwndOwner = hwnd;
332     bi.pidlRoot = root;
333     bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON;
334 
335     pidl = SHBrowseForFolderW(&bi);
336 
337     if (!pidl)
338         return;
339 
340     if (!SHGetPathFromIDListW(pidl, parent))
341         throw string_error(IDS_SHGETPATHFROMIDLIST_FAILED);
342 
343     {
344         win_handle h = CreateFileW(parent, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
345         if (h == INVALID_HANDLE_VALUE)
346             throw string_error(IDS_SEND_CANT_OPEN_DIR, parent, GetLastError(), format_message(GetLastError()).c_str());
347 
348         Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids));
349         if (!NT_SUCCESS(Status))
350             throw string_error(IDS_GET_FILE_IDS_FAILED, Status, format_ntstatus(Status).c_str());
351     }
352 
353     if (bgfi.inode != 0x100 || bgfi.top)
354         throw string_error(IDS_NOT_SUBVOL);
355 
356     SetDlgItemTextW(hwnd, IDC_PARENT_SUBVOL, parent);
357 }
358 
359 void BtrfsSend::AddClone(HWND hwnd) {
360     BROWSEINFOW bi;
361     PIDLIST_ABSOLUTE root, pidl;
362     HRESULT hr;
363     WCHAR path[MAX_PATH], volpathw[MAX_PATH];
364     NTSTATUS Status;
365     IO_STATUS_BLOCK iosb;
366     btrfs_get_file_ids bgfi;
367 
368     if (!GetVolumePathNameW(subvol.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1))
369         throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str());
370 
371     hr = SHParseDisplayName(volpathw, 0, &root, 0, 0);
372     if (FAILED(hr))
373         throw string_error(IDS_SHPARSEDISPLAYNAME_FAILED);
374 
375     memset(&bi, 0, sizeof(BROWSEINFOW));
376 
377     bi.hwndOwner = hwnd;
378     bi.pidlRoot = root;
379     bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON;
380 
381     pidl = SHBrowseForFolderW(&bi);
382 
383     if (!pidl)
384         return;
385 
386     if (!SHGetPathFromIDListW(pidl, path))
387         throw string_error(IDS_SHGETPATHFROMIDLIST_FAILED);
388 
389     {
390         win_handle h = CreateFileW(path, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
391         if (h == INVALID_HANDLE_VALUE)
392             throw string_error(IDS_SEND_CANT_OPEN_DIR, path, GetLastError(), format_message(GetLastError()).c_str());
393 
394         Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids));
395         if (!NT_SUCCESS(Status))
396             throw string_error(IDS_GET_FILE_IDS_FAILED, Status, format_ntstatus(Status).c_str());
397     }
398 
399     if (bgfi.inode != 0x100 || bgfi.top)
400         throw string_error(IDS_NOT_SUBVOL);
401 
402     SendMessageW(GetDlgItem(hwnd, IDC_CLONE_LIST), LB_ADDSTRING, 0, (LPARAM)path);
403 }
404 
405 void BtrfsSend::RemoveClone(HWND hwnd) {
406     LRESULT sel;
407     HWND cl = GetDlgItem(hwnd, IDC_CLONE_LIST);
408 
409     sel = SendMessageW(cl, LB_GETCURSEL, 0, 0);
410 
411     if (sel == LB_ERR)
412         return;
413 
414     SendMessageW(cl, LB_DELETESTRING, sel, 0);
415 
416     if (SendMessageW(cl, LB_GETCURSEL, 0, 0) == LB_ERR)
417         EnableWindow(GetDlgItem(hwnd, IDC_CLONE_REMOVE), false);
418 }
419 
420 INT_PTR BtrfsSend::SendDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
421     try {
422         switch (uMsg) {
423             case WM_INITDIALOG:
424                 this->hwnd = hwndDlg;
425 
426                 GetDlgItemTextW(hwndDlg, IDCANCEL, closetext, sizeof(closetext) / sizeof(WCHAR));
427             break;
428 
429             case WM_COMMAND:
430                 switch (HIWORD(wParam)) {
431                     case BN_CLICKED:
432                         switch (LOWORD(wParam)) {
433                             case IDOK:
434                                 StartSend(hwndDlg);
435                             return true;
436 
437                             case IDCANCEL:
438                                 if (started) {
439                                     TerminateThread(thread, 0);
440 
441                                     if (stream != INVALID_HANDLE_VALUE) {
442                                         NTSTATUS Status;
443                                         FILE_DISPOSITION_INFO fdi;
444                                         IO_STATUS_BLOCK iosb;
445 
446                                         fdi.DeleteFile = true;
447 
448                                         Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);
449 
450                                         CloseHandle(stream);
451 
452                                         if (!NT_SUCCESS(Status))
453                                             throw ntstatus_error(Status);
454                                     }
455 
456                                     if (dirh != INVALID_HANDLE_VALUE)
457                                         CloseHandle(dirh);
458 
459                                     started = false;
460 
461                                     SetDlgItemTextW(hwndDlg, IDCANCEL, closetext);
462 
463                                     EnableWindow(GetDlgItem(hwnd, IDOK), true);
464                                     EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), true);
465                                     EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true);
466                                 } else
467                                     EndDialog(hwndDlg, 1);
468                             return true;
469 
470                             case IDC_BROWSE:
471                                 Browse(hwndDlg);
472                             return true;
473 
474                             case IDC_INCREMENTAL:
475                                 incremental = IsDlgButtonChecked(hwndDlg, LOWORD(wParam));
476 
477                                 EnableWindow(GetDlgItem(hwnd, IDC_PARENT_SUBVOL), incremental);
478                                 EnableWindow(GetDlgItem(hwnd, IDC_PARENT_BROWSE), incremental);
479                             return true;
480 
481                             case IDC_PARENT_BROWSE:
482                                 BrowseParent(hwndDlg);
483                             return true;
484 
485                             case IDC_CLONE_ADD:
486                                 AddClone(hwndDlg);
487                             return true;
488 
489                             case IDC_CLONE_REMOVE:
490                                 RemoveClone(hwndDlg);
491                             return true;
492                         }
493                     break;
494 
495                     case LBN_SELCHANGE:
496                         switch (LOWORD(wParam)) {
497                             case IDC_CLONE_LIST:
498                                 EnableWindow(GetDlgItem(hwnd, IDC_CLONE_REMOVE), true);
499                             return true;
500                         }
501                     break;
502                 }
503             break;
504         }
505     } catch (const exception& e) {
506         error_message(hwnd, e.what());
507     }
508 
509     return false;
510 }
511 
512 static INT_PTR CALLBACK stub_SendDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
513     BtrfsSend* bs;
514 
515     if (uMsg == WM_INITDIALOG) {
516         SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);
517         bs = (BtrfsSend*)lParam;
518     } else
519         bs = (BtrfsSend*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
520 
521     if (bs)
522         return bs->SendDlgProc(hwndDlg, uMsg, wParam, lParam);
523     else
524         return false;
525 }
526 
527 void BtrfsSend::Open(HWND hwnd, LPWSTR path) {
528     subvol = path;
529 
530     if (DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_SEND_SUBVOL), hwnd, stub_SendDlgProc, (LPARAM)this) <= 0)
531         throw last_error(GetLastError());
532 }
533 
534 #ifdef __REACTOS__
535 extern "C" {
536 #endif
537 
538 void CALLBACK SendSubvolGUIW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
539     try {
540         win_handle token;
541         TOKEN_PRIVILEGES tp;
542         LUID luid;
543 
544         set_dpi_aware();
545 
546         if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
547             throw last_error(GetLastError());
548 
549         if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid))
550             throw last_error(GetLastError());
551 
552         tp.PrivilegeCount = 1;
553         tp.Privileges[0].Luid = luid;
554         tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
555 
556         if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))
557             throw last_error(GetLastError());
558 
559         BtrfsSend bs;
560 
561         bs.Open(hwnd, lpszCmdLine);
562     } catch (const exception& e) {
563         error_message(hwnd, e.what());
564     }
565 }
566 
567 #ifdef __REACTOS__
568 } /* extern "C" */
569 #endif
570 
571 static void send_subvol(const wstring& subvol, const wstring& file, const wstring& parent, const vector<wstring>& clones) {
572     char* buf;
573     win_handle dirh, stream;
574     ULONG i;
575     btrfs_send_subvol* bss;
576     IO_STATUS_BLOCK iosb;
577     NTSTATUS Status;
578     btrfs_send_header header;
579     btrfs_send_command end;
580 
581     buf = (char*)malloc(SEND_BUFFER_LEN);
582 
583     try {
584         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);
585         if (dirh == INVALID_HANDLE_VALUE)
586             throw last_error(GetLastError());
587 
588         stream = CreateFileW(file.c_str(), FILE_WRITE_DATA | DELETE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
589         if (stream == INVALID_HANDLE_VALUE)
590             throw last_error(GetLastError());
591 
592         try {
593             size_t bss_size = offsetof(btrfs_send_subvol, clones[0]) + (clones.size() * sizeof(HANDLE));
594             bss = (btrfs_send_subvol*)malloc(bss_size);
595             memset(bss, 0, bss_size);
596 
597             if (parent != L"") {
598                 HANDLE parenth;
599 
600                 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);
601                 if (parenth == INVALID_HANDLE_VALUE)
602                     throw last_error(GetLastError());
603 
604                 bss->parent = parenth;
605             } else
606                 bss->parent = nullptr;
607 
608             bss->num_clones = (ULONG)clones.size();
609 
610             for (i = 0; i < bss->num_clones; i++) {
611                 HANDLE h;
612 
613                 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);
614                 if (h == INVALID_HANDLE_VALUE) {
615                     auto le = GetLastError();
616                     ULONG j;
617 
618                     for (j = 0; j < i; j++) {
619                         CloseHandle(bss->clones[j]);
620                     }
621 
622                     if (bss->parent) CloseHandle(bss->parent);
623 
624                     throw last_error(le);
625                 }
626 
627                 bss->clones[i] = h;
628             }
629 
630             Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SEND_SUBVOL, bss, (ULONG)bss_size, nullptr, 0);
631 
632             for (i = 0; i < bss->num_clones; i++) {
633                 CloseHandle(bss->clones[i]);
634             }
635 
636             if (bss->parent) CloseHandle(bss->parent);
637 
638             if (!NT_SUCCESS(Status))
639                 throw ntstatus_error(Status);
640 
641             memcpy(header.magic, BTRFS_SEND_MAGIC, sizeof(header.magic));
642             header.version = 1;
643 
644             if (!WriteFile(stream, &header, sizeof(header), nullptr, nullptr))
645                 throw last_error(GetLastError());
646 
647             do {
648                 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_READ_SEND_BUFFER, nullptr, 0, buf, SEND_BUFFER_LEN);
649 
650                 if (NT_SUCCESS(Status))
651                     WriteFile(stream, buf, (DWORD)iosb.Information, nullptr, nullptr);
652             } while (NT_SUCCESS(Status));
653 
654             if (Status != STATUS_END_OF_FILE)
655                 throw ntstatus_error(Status);
656 
657             end.length = 0;
658             end.cmd = BTRFS_SEND_CMD_END;
659             end.csum = 0x9dc96c50;
660 
661             if (!WriteFile(stream, &end, sizeof(end), nullptr, nullptr))
662                 throw last_error(GetLastError());
663 
664             SetEndOfFile(stream);
665         } catch (...) {
666             FILE_DISPOSITION_INFO fdi;
667 
668             fdi.DeleteFile = true;
669 
670             Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);
671             if (!NT_SUCCESS(Status))
672                 throw ntstatus_error(Status);
673 
674             throw;
675         }
676     } catch (...) {
677         free(buf);
678         throw;
679     }
680 
681     free(buf);
682 }
683 
684 #ifdef __REACTOS__
685 extern "C" {
686 #endif
687 
688 void CALLBACK SendSubvolW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
689     vector<wstring> args;
690     wstring subvol = L"", parent = L"", file = L"";
691     vector<wstring> clones;
692 
693     command_line_to_args(lpszCmdLine, args);
694 
695     if (args.size() >= 2) {
696         TOKEN_PRIVILEGES tp;
697         LUID luid;
698 
699         {
700             win_handle token;
701 
702             if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
703                 return;
704 
705             if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid))
706                 return;
707 
708             tp.PrivilegeCount = 1;
709             tp.Privileges[0].Luid = luid;
710             tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
711 
712             if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))
713                 return;
714         }
715 
716         for (unsigned int i = 0; i < args.size(); i++) {
717             if (args[i][0] == '-') {
718                 if (args[i][2] == 0 && i < args.size() - 1) {
719                     if (args[i][1] == 'p') {
720                         parent = args[i+1];
721                         i++;
722                     } else if (args[i][1] == 'c') {
723                         clones.push_back(args[i+1]);
724                         i++;
725                     }
726                 }
727             } else {
728                 if (subvol == L"")
729                     subvol = args[i];
730                 else if (file == L"")
731                     file = args[i];
732             }
733         }
734 
735         if (subvol != L"" && file != L"") {
736             try {
737                 send_subvol(subvol, file, parent, clones);
738             } catch (const exception& e) {
739                 cerr << "Error: " << e.what() << endl;
740             }
741         }
742     }
743 }
744 
745 #ifdef __REACTOS__
746 } /* extern "C" */
747 #endif
748