xref: /reactos/dll/shellext/shellbtrfs/send.cpp (revision 0c2cdcae)
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 
Thread()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 
send_thread(LPVOID lpParameter)227 static DWORD WINAPI send_thread(LPVOID lpParameter) {
228     BtrfsSend* bs = (BtrfsSend*)lpParameter;
229 
230     return bs->Thread();
231 }
232 
StartSend(HWND hwnd)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 
Browse(HWND hwnd)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 
BrowseParent(HWND hwnd)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 
AddClone(HWND hwnd)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 
RemoveClone(HWND hwnd)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 
SendDlgProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)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 
stub_SendDlgProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)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         SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);
518         bs = (BtrfsSend*)lParam;
519     } else
520         bs = (BtrfsSend*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);
521 
522     if (bs)
523         return bs->SendDlgProc(hwndDlg, uMsg, wParam, lParam);
524     else
525         return false;
526 }
527 
Open(HWND hwnd,LPWSTR path)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 
SendSubvolGUIW(HWND hwnd,HINSTANCE hinst,LPWSTR lpszCmdLine,int nCmdShow)535 extern "C" void CALLBACK SendSubvolGUIW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
536     try {
537         win_handle token;
538         TOKEN_PRIVILEGES tp;
539         LUID luid;
540 
541         set_dpi_aware();
542 
543         if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
544             throw last_error(GetLastError());
545 
546         if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid))
547             throw last_error(GetLastError());
548 
549         tp.PrivilegeCount = 1;
550         tp.Privileges[0].Luid = luid;
551         tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
552 
553         if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))
554             throw last_error(GetLastError());
555 
556         BtrfsSend bs;
557 
558         bs.Open(hwnd, lpszCmdLine);
559     } catch (const exception& e) {
560         error_message(hwnd, e.what());
561     }
562 }
563 
send_subvol(const wstring & subvol,const wstring & file,const wstring & parent,const vector<wstring> & clones)564 static void send_subvol(const wstring& subvol, const wstring& file, const wstring& parent, const vector<wstring>& clones) {
565     char* buf;
566     win_handle dirh, stream;
567     ULONG i;
568     btrfs_send_subvol* bss;
569     IO_STATUS_BLOCK iosb;
570     NTSTATUS Status;
571     btrfs_send_header header;
572     btrfs_send_command end;
573 
574     buf = (char*)malloc(SEND_BUFFER_LEN);
575 
576     try {
577         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);
578         if (dirh == INVALID_HANDLE_VALUE)
579             throw last_error(GetLastError());
580 
581         stream = CreateFileW(file.c_str(), FILE_WRITE_DATA | DELETE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
582         if (stream == INVALID_HANDLE_VALUE)
583             throw last_error(GetLastError());
584 
585         try {
586             size_t bss_size = offsetof(btrfs_send_subvol, clones[0]) + (clones.size() * sizeof(HANDLE));
587             bss = (btrfs_send_subvol*)malloc(bss_size);
588             memset(bss, 0, bss_size);
589 
590             if (parent != L"") {
591                 HANDLE parenth;
592 
593                 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);
594                 if (parenth == INVALID_HANDLE_VALUE)
595                     throw last_error(GetLastError());
596 
597                 bss->parent = parenth;
598             } else
599                 bss->parent = nullptr;
600 
601             bss->num_clones = (ULONG)clones.size();
602 
603             for (i = 0; i < bss->num_clones; i++) {
604                 HANDLE h;
605 
606                 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);
607                 if (h == INVALID_HANDLE_VALUE) {
608                     auto le = GetLastError();
609                     ULONG j;
610 
611                     for (j = 0; j < i; j++) {
612                         CloseHandle(bss->clones[j]);
613                     }
614 
615                     if (bss->parent) CloseHandle(bss->parent);
616 
617                     throw last_error(le);
618                 }
619 
620                 bss->clones[i] = h;
621             }
622 
623             Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SEND_SUBVOL, bss, (ULONG)bss_size, nullptr, 0);
624 
625             for (i = 0; i < bss->num_clones; i++) {
626                 CloseHandle(bss->clones[i]);
627             }
628 
629             if (bss->parent) CloseHandle(bss->parent);
630 
631             if (!NT_SUCCESS(Status))
632                 throw ntstatus_error(Status);
633 
634             memcpy(header.magic, BTRFS_SEND_MAGIC, sizeof(header.magic));
635             header.version = 1;
636 
637             if (!WriteFile(stream, &header, sizeof(header), nullptr, nullptr))
638                 throw last_error(GetLastError());
639 
640             do {
641                 Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_READ_SEND_BUFFER, nullptr, 0, buf, SEND_BUFFER_LEN);
642 
643                 if (NT_SUCCESS(Status))
644                     WriteFile(stream, buf, (DWORD)iosb.Information, nullptr, nullptr);
645             } while (NT_SUCCESS(Status));
646 
647             if (Status != STATUS_END_OF_FILE)
648                 throw ntstatus_error(Status);
649 
650             end.length = 0;
651             end.cmd = BTRFS_SEND_CMD_END;
652             end.csum = 0x9dc96c50;
653 
654             if (!WriteFile(stream, &end, sizeof(end), nullptr, nullptr))
655                 throw last_error(GetLastError());
656 
657             SetEndOfFile(stream);
658         } catch (...) {
659             FILE_DISPOSITION_INFO fdi;
660 
661             fdi.DeleteFile = true;
662 
663             Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);
664             if (!NT_SUCCESS(Status))
665                 throw ntstatus_error(Status);
666 
667             throw;
668         }
669     } catch (...) {
670         free(buf);
671         throw;
672     }
673 
674     free(buf);
675 }
676 
SendSubvolW(HWND hwnd,HINSTANCE hinst,LPWSTR lpszCmdLine,int nCmdShow)677 extern "C" void CALLBACK SendSubvolW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
678     vector<wstring> args;
679     wstring subvol = L"", parent = L"", file = L"";
680     vector<wstring> clones;
681 
682     command_line_to_args(lpszCmdLine, args);
683 
684     if (args.size() >= 2) {
685         TOKEN_PRIVILEGES tp;
686         LUID luid;
687 
688         {
689             win_handle token;
690 
691             if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
692                 return;
693 
694             if (!LookupPrivilegeValueW(nullptr, L"SeManageVolumePrivilege", &luid))
695                 return;
696 
697             tp.PrivilegeCount = 1;
698             tp.Privileges[0].Luid = luid;
699             tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
700 
701             if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))
702                 return;
703         }
704 
705         for (unsigned int i = 0; i < args.size(); i++) {
706             if (args[i][0] == '-') {
707                 if (args[i][2] == 0 && i < args.size() - 1) {
708                     if (args[i][1] == 'p') {
709                         parent = args[i+1];
710                         i++;
711                     } else if (args[i][1] == 'c') {
712                         clones.push_back(args[i+1]);
713                         i++;
714                     }
715                 }
716             } else {
717                 if (subvol == L"")
718                     subvol = args[i];
719                 else if (file == L"")
720                     file = args[i];
721             }
722         }
723 
724         if (subvol != L"" && file != L"") {
725             try {
726                 send_subvol(subvol, file, parent, clones);
727             } catch (const exception& e) {
728                 cerr << "Error: " << e.what() << endl;
729             }
730         }
731     }
732 }
733