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