[NETAPI32]
[reactos.git] / reactos / dll / shellext / shellbtrfs / contextmenu.cpp
1 /* Copyright (c) Mark Harmstone 2016
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 #ifndef __REACTOS__
19 #include <windows.h>
20 #include <winternl.h>
21 #else
22 #define WIN32_NO_STATUS
23 #include <windef.h>
24 #include <winbase.h>
25 #include <strsafe.h>
26 #include <ndk/iofuncs.h>
27 #endif
28
29 #define NO_SHLWAPI_STRFCNS
30 #include <shlwapi.h>
31
32 #include "contextmenu.h"
33 #include "resource.h"
34 #ifndef __REACTOS__
35 #include "../btrfsioctl.h"
36 #else
37 #include "../../drivers/filesystems/btrfs/btrfsioctl.h"
38 #endif
39
40 #define NEW_SUBVOL_VERBA "newsubvol"
41 #define NEW_SUBVOL_VERBW L"newsubvol"
42 #define SNAPSHOT_VERBA "snapshot"
43 #define SNAPSHOT_VERBW L"snapshot"
44
45 #ifndef __REACTOS__
46 // FIXME - is there a way to link to the proper header files without breaking everything?
47 #ifdef __cplusplus
48 extern "C" {
49 #endif
50 NTSYSCALLAPI NTSTATUS NTAPI NtFsControlFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, ULONG FsControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength);
51 #ifdef __cplusplus
52 }
53 #endif
54
55 #define STATUS_SUCCESS (NTSTATUS)0x00000000
56 #endif
57
58 typedef struct _KEY_NAME_INFORMATION {
59 ULONG NameLength;
60 WCHAR Name[1];
61 } KEY_NAME_INFORMATION;
62
63 typedef ULONG (WINAPI *_RtlNtStatusToDosError)(NTSTATUS Status);
64
65 extern HMODULE module;
66
67 // FIXME - don't assume subvol's top inode is 0x100
68
69 HRESULT __stdcall BtrfsContextMenu::QueryInterface(REFIID riid, void **ppObj) {
70 if (riid == IID_IUnknown || riid == IID_IContextMenu) {
71 *ppObj = static_cast<IContextMenu*>(this);
72 AddRef();
73 return S_OK;
74 } else if (riid == IID_IShellExtInit) {
75 *ppObj = static_cast<IShellExtInit*>(this);
76 AddRef();
77 return S_OK;
78 }
79
80 *ppObj = NULL;
81 return E_NOINTERFACE;
82 }
83
84 HRESULT __stdcall BtrfsContextMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) {
85 HANDLE h;
86 IO_STATUS_BLOCK iosb;
87 btrfs_get_file_ids bgfi;
88 NTSTATUS Status;
89
90 if (!pidlFolder) {
91 FORMATETC format = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
92 UINT num_files, i;
93 WCHAR fn[MAX_PATH];
94 HDROP hdrop;
95
96 if (!pdtobj)
97 return E_FAIL;
98
99 stgm.tymed = TYMED_HGLOBAL;
100
101 if (FAILED(pdtobj->GetData(&format, &stgm)))
102 return E_INVALIDARG;
103
104 stgm_set = TRUE;
105
106 hdrop = (HDROP)GlobalLock(stgm.hGlobal);
107
108 if (!hdrop) {
109 ReleaseStgMedium(&stgm);
110 stgm_set = FALSE;
111 return E_INVALIDARG;
112 }
113
114 num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, NULL, 0);
115
116 for (i = 0; i < num_files; i++) {
117 if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(MAX_PATH))) {
118 h = CreateFileW(fn, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
119
120 if (h != INVALID_HANDLE_VALUE) {
121 Status = NtFsControlFile(h, NULL, NULL, NULL, &iosb, FSCTL_BTRFS_GET_FILE_IDS, NULL, 0, &bgfi, sizeof(btrfs_get_file_ids));
122
123 if (Status == STATUS_SUCCESS && bgfi.inode == 0x100 && !bgfi.top) {
124 WCHAR parpath[MAX_PATH];
125 HANDLE h2;
126
127 StringCchCopyW(parpath, sizeof(parpath) / sizeof(WCHAR), fn);
128
129 PathRemoveFileSpecW(parpath);
130
131 h2 = CreateFileW(parpath, FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
132
133 if (h2 == INVALID_HANDLE_VALUE) {
134 CloseHandle(h);
135 return E_FAIL;
136 }
137
138 ignore = FALSE;
139 bg = FALSE;
140
141 CloseHandle(h2);
142 CloseHandle(h);
143 return S_OK;
144 }
145
146 CloseHandle(h);
147 }
148 }
149 }
150
151 return S_OK;
152 }
153
154 if (!SHGetPathFromIDListW(pidlFolder, path))
155 return E_FAIL;
156
157 // check we have permissions to create new subdirectory
158
159 h = CreateFileW(path, FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
160
161 if (h == INVALID_HANDLE_VALUE)
162 return E_FAIL;
163
164 // check is Btrfs volume
165
166 Status = NtFsControlFile(h, NULL, NULL, NULL, &iosb, FSCTL_BTRFS_GET_FILE_IDS, NULL, 0, &bgfi, sizeof(btrfs_get_file_ids));
167
168 if (Status != STATUS_SUCCESS) {
169 CloseHandle(h);
170 return E_FAIL;
171 }
172
173 CloseHandle(h);
174
175 ignore = FALSE;
176 bg = TRUE;
177
178 return S_OK;
179 }
180
181 HRESULT __stdcall BtrfsContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) {
182 WCHAR str[256];
183
184 if (ignore)
185 return E_INVALIDARG;
186
187 if (uFlags & CMF_DEFAULTONLY)
188 return S_OK;
189
190 if (!bg) {
191 if (LoadStringW(module, IDS_CREATE_SNAPSHOT, str, sizeof(str) / sizeof(WCHAR)) == 0)
192 return E_FAIL;
193
194 if (!InsertMenuW(hmenu, indexMenu, MF_BYPOSITION, idCmdFirst, str))
195 return E_FAIL;
196
197 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 1);
198 }
199
200 if (LoadStringW(module, IDS_NEW_SUBVOL, str, sizeof(str) / sizeof(WCHAR)) == 0)
201 return E_FAIL;
202
203 if (!InsertMenuW(hmenu, indexMenu, MF_BYPOSITION, idCmdFirst, str))
204 return E_FAIL;
205
206 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 1);
207 }
208
209 static void ShowError(HWND hwnd, ULONG err) {
210 WCHAR* buf;
211
212 if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
213 err, 0, (WCHAR*)&buf, 0, NULL) == 0) {
214 MessageBoxW(hwnd, L"FormatMessage failed", L"Error", MB_ICONERROR);
215 return;
216 }
217
218 MessageBoxW(hwnd, buf, L"Error", MB_ICONERROR);
219
220 LocalFree(buf);
221 }
222
223 void ShowNtStatusError(HWND hwnd, NTSTATUS Status) {
224 _RtlNtStatusToDosError RtlNtStatusToDosError;
225 HMODULE ntdll = LoadLibraryW(L"ntdll.dll");
226
227 if (!ntdll) {
228 MessageBoxW(hwnd, L"Error loading ntdll.dll", L"Error", MB_ICONERROR);
229 return;
230 }
231
232 RtlNtStatusToDosError = (_RtlNtStatusToDosError)GetProcAddress(ntdll, "RtlNtStatusToDosError");
233
234 if (!ntdll) {
235 MessageBoxW(hwnd, L"Error loading RtlNtStatusToDosError in ntdll.dll", L"Error", MB_ICONERROR);
236 FreeLibrary(ntdll);
237 return;
238 }
239
240 ShowError(hwnd, RtlNtStatusToDosError(Status));
241
242 FreeLibrary(ntdll);
243 }
244
245 static void create_snapshot(HWND hwnd, WCHAR* fn) {
246 HANDLE h;
247 NTSTATUS Status;
248 IO_STATUS_BLOCK iosb;
249 btrfs_get_file_ids bgfi;
250
251 h = CreateFileW(fn, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
252
253 if (h != INVALID_HANDLE_VALUE) {
254 Status = NtFsControlFile(h, NULL, NULL, NULL, &iosb, FSCTL_BTRFS_GET_FILE_IDS, NULL, 0, &bgfi, sizeof(btrfs_get_file_ids));
255
256 if (Status == STATUS_SUCCESS && bgfi.inode == 0x100 && !bgfi.top) {
257 WCHAR parpath[MAX_PATH], subvolname[MAX_PATH], templ[MAX_PATH], name[MAX_PATH], searchpath[MAX_PATH];
258 HANDLE h2, fff;
259 btrfs_create_snapshot* bcs;
260 ULONG namelen, pathend;
261 WIN32_FIND_DATAW wfd;
262 SYSTEMTIME time;
263
264 StringCchCopyW(parpath, sizeof(parpath) / sizeof(WCHAR), fn);
265 PathRemoveFileSpecW(parpath);
266
267 StringCchCopyW(subvolname, sizeof(subvolname) / sizeof(WCHAR), fn);
268 PathStripPathW(subvolname);
269
270 h2 = CreateFileW(parpath, FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
271
272 if (h2 == INVALID_HANDLE_VALUE) {
273 ShowError(hwnd, GetLastError());
274 CloseHandle(h);
275 return;
276 }
277
278 if (!LoadStringW(module, IDS_SNAPSHOT_FILENAME, templ, MAX_PATH)) {
279 ShowError(hwnd, GetLastError());
280 CloseHandle(h);
281 CloseHandle(h2);
282 return;
283 }
284
285 GetLocalTime(&time);
286
287 if (StringCchPrintfW(name, sizeof(name) / sizeof(WCHAR), templ, subvolname, time.wYear, time.wMonth, time.wDay) == STRSAFE_E_INSUFFICIENT_BUFFER) {
288 MessageBoxW(hwnd, L"Filename too long.\n", L"Error", MB_ICONERROR);
289 CloseHandle(h);
290 CloseHandle(h2);
291 return;
292 }
293
294 StringCchCopyW(searchpath, sizeof(searchpath) / sizeof(WCHAR), parpath);
295 StringCchCatW(searchpath, sizeof(searchpath) / sizeof(WCHAR), L"\\");
296 pathend = wcslen(searchpath);
297
298 StringCchCatW(searchpath, sizeof(searchpath) / sizeof(WCHAR), name);
299
300 fff = FindFirstFileW(searchpath, &wfd);
301
302 if (fff != INVALID_HANDLE_VALUE) {
303 ULONG i = wcslen(searchpath), num = 2;
304
305 do {
306 FindClose(fff);
307
308 searchpath[i] = 0;
309 if (StringCchPrintfW(searchpath, sizeof(searchpath) / sizeof(WCHAR), L"%s (%u)", searchpath, num) == STRSAFE_E_INSUFFICIENT_BUFFER) {
310 MessageBoxW(hwnd, L"Filename too long.\n", L"Error", MB_ICONERROR);
311 CloseHandle(h);
312 CloseHandle(h2);
313 return;
314 }
315
316 fff = FindFirstFileW(searchpath, &wfd);
317 num++;
318 } while (fff != INVALID_HANDLE_VALUE);
319 }
320
321 namelen = wcslen(&searchpath[pathend]) * sizeof(WCHAR);
322
323 bcs = (btrfs_create_snapshot*)malloc(sizeof(btrfs_create_snapshot) - 1 + namelen);
324 bcs->subvol = h;
325 bcs->namelen = namelen;
326 memcpy(bcs->name, &searchpath[pathend], namelen);
327
328 Status = NtFsControlFile(h2, NULL, NULL, NULL, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, NULL, 0, bcs, sizeof(btrfs_create_snapshot) - 1 + namelen);
329
330 if (Status != STATUS_SUCCESS)
331 ShowNtStatusError(hwnd, Status);
332
333 CloseHandle(h2);
334 }
335
336 CloseHandle(h);
337 } else
338 ShowError(hwnd, GetLastError());
339 }
340
341 HRESULT __stdcall BtrfsContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) {
342 if (ignore)
343 return E_INVALIDARG;
344
345 if (!bg) {
346 if ((IS_INTRESOURCE(pici->lpVerb) && pici->lpVerb == 0) || !strcmp(pici->lpVerb, SNAPSHOT_VERBA)) {
347 UINT num_files, i;
348 WCHAR fn[MAX_PATH];
349
350 if (!stgm_set)
351 return E_FAIL;
352
353 num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, NULL, 0);
354
355 if (num_files == 0)
356 return E_FAIL;
357
358 for (i = 0; i < num_files; i++) {
359 if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(MAX_PATH))) {
360 create_snapshot(pici->hwnd, fn);
361 }
362 }
363
364 return S_OK;
365 }
366
367 return E_FAIL;
368 }
369
370 if ((IS_INTRESOURCE(pici->lpVerb) && pici->lpVerb == 0) || !strcmp(pici->lpVerb, NEW_SUBVOL_VERBA)) {
371 HANDLE h;
372 IO_STATUS_BLOCK iosb;
373 NTSTATUS Status;
374 ULONG pathlen, searchpathlen, pathend;
375 WCHAR name[MAX_PATH], *searchpath;
376 HANDLE fff;
377 WIN32_FIND_DATAW wfd;
378
379 if (!LoadStringW(module, IDS_NEW_SUBVOL_FILENAME, name, MAX_PATH)) {
380 ShowError(pici->hwnd, GetLastError());
381 return E_FAIL;
382 }
383
384 h = CreateFileW(path, FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
385
386 if (h == INVALID_HANDLE_VALUE) {
387 ShowError(pici->hwnd, GetLastError());
388 return E_FAIL;
389 }
390
391 pathlen = wcslen(path);
392
393 searchpathlen = pathlen + wcslen(name) + 10;
394 searchpath = (WCHAR*)malloc(searchpathlen * sizeof(WCHAR));
395
396 StringCchCopyW(searchpath, searchpathlen, path);
397 StringCchCatW(searchpath, searchpathlen, L"\\");
398 pathend = wcslen(searchpath);
399
400 StringCchCatW(searchpath, searchpathlen, name);
401
402 fff = FindFirstFileW(searchpath, &wfd);
403
404 if (fff != INVALID_HANDLE_VALUE) {
405 ULONG i = wcslen(searchpath), num = 2;
406
407 do {
408 FindClose(fff);
409
410 searchpath[i] = 0;
411 if (StringCchPrintfW(searchpath, searchpathlen, L"%s (%u)", searchpath, num) == STRSAFE_E_INSUFFICIENT_BUFFER) {
412 MessageBoxW(pici->hwnd, L"Filename too long.\n", L"Error", MB_ICONERROR);
413 CloseHandle(h);
414 return E_FAIL;
415 }
416
417 fff = FindFirstFileW(searchpath, &wfd);
418 num++;
419 } while (fff != INVALID_HANDLE_VALUE);
420 }
421
422 Status = NtFsControlFile(h, NULL, NULL, NULL, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, NULL, 0, &searchpath[pathend], wcslen(&searchpath[pathend]) * sizeof(WCHAR));
423
424 free(searchpath);
425
426 if (Status != STATUS_SUCCESS) {
427 CloseHandle(h);
428 ShowNtStatusError(pici->hwnd, Status);
429 return E_FAIL;
430 }
431
432 CloseHandle(h);
433
434 return S_OK;
435 }
436
437 return E_FAIL;
438 }
439
440 HRESULT __stdcall BtrfsContextMenu::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax) {
441 if (ignore)
442 return E_INVALIDARG;
443
444 if (idCmd != 0)
445 return E_INVALIDARG;
446
447 switch (uFlags) {
448 case GCS_HELPTEXTA:
449 if (LoadStringA(module, bg ? IDS_NEW_SUBVOL_HELP_TEXT : IDS_CREATE_SNAPSHOT_HELP_TEXT, pszName, cchMax))
450 return S_OK;
451 else
452 return E_FAIL;
453
454 case GCS_HELPTEXTW:
455 if (LoadStringW(module, bg ? IDS_NEW_SUBVOL_HELP_TEXT : IDS_CREATE_SNAPSHOT_HELP_TEXT, (LPWSTR)pszName, cchMax))
456 return S_OK;
457 else
458 return E_FAIL;
459
460 case GCS_VALIDATEA:
461 case GCS_VALIDATEW:
462 return S_OK;
463
464 case GCS_VERBA:
465 return StringCchCopyA(pszName, cchMax, bg ? NEW_SUBVOL_VERBA : SNAPSHOT_VERBA);
466
467 case GCS_VERBW:
468 return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, bg ? NEW_SUBVOL_VERBW : SNAPSHOT_VERBW);
469
470 default:
471 return E_INVALIDARG;
472 }
473 }