1 /* Copyright (c) Mark Harmstone 2016
3 * This file is part of WinBtrfs.
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.
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.
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/>. */
22 #define WIN32_NO_STATUS
26 #include <ndk/iofuncs.h>
29 #define NO_SHLWAPI_STRFCNS
32 #include "contextmenu.h"
35 #include "../btrfsioctl.h"
37 #include "../../drivers/filesystems/btrfs/btrfsioctl.h"
40 #define NEW_SUBVOL_VERBA "newsubvol"
41 #define NEW_SUBVOL_VERBW L"newsubvol"
42 #define SNAPSHOT_VERBA "snapshot"
43 #define SNAPSHOT_VERBW L"snapshot"
46 // FIXME - is there a way to link to the proper header files without breaking everything?
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
);
55 #define STATUS_SUCCESS (NTSTATUS)0x00000000
58 typedef struct _KEY_NAME_INFORMATION
{
61 } KEY_NAME_INFORMATION
;
63 typedef ULONG (WINAPI
*_RtlNtStatusToDosError
)(NTSTATUS Status
);
65 extern HMODULE module
;
67 // FIXME - don't assume subvol's top inode is 0x100
69 HRESULT __stdcall
BtrfsContextMenu::QueryInterface(REFIID riid
, void **ppObj
) {
70 if (riid
== IID_IUnknown
|| riid
== IID_IContextMenu
) {
71 *ppObj
= static_cast<IContextMenu
*>(this);
74 } else if (riid
== IID_IShellExtInit
) {
75 *ppObj
= static_cast<IShellExtInit
*>(this);
84 HRESULT __stdcall
BtrfsContextMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder
, IDataObject
* pdtobj
, HKEY hkeyProgID
) {
87 btrfs_get_file_ids bgfi
;
91 FORMATETC format
= { CF_HDROP
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
99 stgm
.tymed
= TYMED_HGLOBAL
;
101 if (FAILED(pdtobj
->GetData(&format
, &stgm
)))
106 hdrop
= (HDROP
)GlobalLock(stgm
.hGlobal
);
109 ReleaseStgMedium(&stgm
);
114 num_files
= DragQueryFileW((HDROP
)stgm
.hGlobal
, 0xFFFFFFFF, NULL
, 0);
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
);
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
));
123 if (Status
== STATUS_SUCCESS
&& bgfi
.inode
== 0x100 && !bgfi
.top
) {
124 WCHAR parpath
[MAX_PATH
];
127 StringCchCopyW(parpath
, sizeof(parpath
) / sizeof(WCHAR
), fn
);
129 PathRemoveFileSpecW(parpath
);
131 h2
= CreateFileW(parpath
, FILE_ADD_SUBDIRECTORY
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, NULL
, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
133 if (h2
== INVALID_HANDLE_VALUE
) {
154 if (!SHGetPathFromIDListW(pidlFolder
, path
))
157 // check we have permissions to create new subdirectory
159 h
= CreateFileW(path
, FILE_ADD_SUBDIRECTORY
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, NULL
, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
161 if (h
== INVALID_HANDLE_VALUE
)
164 // check is Btrfs volume
166 Status
= NtFsControlFile(h
, NULL
, NULL
, NULL
, &iosb
, FSCTL_BTRFS_GET_FILE_IDS
, NULL
, 0, &bgfi
, sizeof(btrfs_get_file_ids
));
168 if (Status
!= STATUS_SUCCESS
) {
181 HRESULT __stdcall
BtrfsContextMenu::QueryContextMenu(HMENU hmenu
, UINT indexMenu
, UINT idCmdFirst
, UINT idCmdLast
, UINT uFlags
) {
187 if (uFlags
& CMF_DEFAULTONLY
)
191 if (LoadStringW(module
, IDS_CREATE_SNAPSHOT
, str
, sizeof(str
) / sizeof(WCHAR
)) == 0)
194 if (!InsertMenuW(hmenu
, indexMenu
, MF_BYPOSITION
, idCmdFirst
, str
))
197 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, 1);
200 if (LoadStringW(module
, IDS_NEW_SUBVOL
, str
, sizeof(str
) / sizeof(WCHAR
)) == 0)
203 if (!InsertMenuW(hmenu
, indexMenu
, MF_BYPOSITION
, idCmdFirst
, str
))
206 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, 1);
209 static void ShowError(HWND hwnd
, ULONG err
) {
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
);
218 MessageBoxW(hwnd
, buf
, L
"Error", MB_ICONERROR
);
223 void ShowNtStatusError(HWND hwnd
, NTSTATUS Status
) {
224 _RtlNtStatusToDosError RtlNtStatusToDosError
;
225 HMODULE ntdll
= LoadLibraryW(L
"ntdll.dll");
228 MessageBoxW(hwnd
, L
"Error loading ntdll.dll", L
"Error", MB_ICONERROR
);
232 RtlNtStatusToDosError
= (_RtlNtStatusToDosError
)GetProcAddress(ntdll
, "RtlNtStatusToDosError");
235 MessageBoxW(hwnd
, L
"Error loading RtlNtStatusToDosError in ntdll.dll", L
"Error", MB_ICONERROR
);
240 ShowError(hwnd
, RtlNtStatusToDosError(Status
));
245 static void create_snapshot(HWND hwnd
, WCHAR
* fn
) {
248 IO_STATUS_BLOCK iosb
;
249 btrfs_get_file_ids bgfi
;
251 h
= CreateFileW(fn
, FILE_TRAVERSE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, NULL
, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
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
));
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
];
259 btrfs_create_snapshot
* bcs
;
260 ULONG namelen
, pathend
;
261 WIN32_FIND_DATAW wfd
;
264 StringCchCopyW(parpath
, sizeof(parpath
) / sizeof(WCHAR
), fn
);
265 PathRemoveFileSpecW(parpath
);
267 StringCchCopyW(subvolname
, sizeof(subvolname
) / sizeof(WCHAR
), fn
);
268 PathStripPathW(subvolname
);
270 h2
= CreateFileW(parpath
, FILE_ADD_SUBDIRECTORY
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, NULL
, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
272 if (h2
== INVALID_HANDLE_VALUE
) {
273 ShowError(hwnd
, GetLastError());
278 if (!LoadStringW(module
, IDS_SNAPSHOT_FILENAME
, templ
, MAX_PATH
)) {
279 ShowError(hwnd
, GetLastError());
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
);
294 StringCchCopyW(searchpath
, sizeof(searchpath
) / sizeof(WCHAR
), parpath
);
295 StringCchCatW(searchpath
, sizeof(searchpath
) / sizeof(WCHAR
), L
"\\");
296 pathend
= wcslen(searchpath
);
298 StringCchCatW(searchpath
, sizeof(searchpath
) / sizeof(WCHAR
), name
);
300 fff
= FindFirstFileW(searchpath
, &wfd
);
302 if (fff
!= INVALID_HANDLE_VALUE
) {
303 ULONG i
= wcslen(searchpath
), num
= 2;
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
);
316 fff
= FindFirstFileW(searchpath
, &wfd
);
318 } while (fff
!= INVALID_HANDLE_VALUE
);
321 namelen
= wcslen(&searchpath
[pathend
]) * sizeof(WCHAR
);
323 bcs
= (btrfs_create_snapshot
*)malloc(sizeof(btrfs_create_snapshot
) - 1 + namelen
);
325 bcs
->namelen
= namelen
;
326 memcpy(bcs
->name
, &searchpath
[pathend
], namelen
);
328 Status
= NtFsControlFile(h2
, NULL
, NULL
, NULL
, &iosb
, FSCTL_BTRFS_CREATE_SNAPSHOT
, NULL
, 0, bcs
, sizeof(btrfs_create_snapshot
) - 1 + namelen
);
330 if (Status
!= STATUS_SUCCESS
)
331 ShowNtStatusError(hwnd
, Status
);
338 ShowError(hwnd
, GetLastError());
341 HRESULT __stdcall
BtrfsContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici
) {
346 if ((IS_INTRESOURCE(pici
->lpVerb
) && pici
->lpVerb
== 0) || !strcmp(pici
->lpVerb
, SNAPSHOT_VERBA
)) {
353 num_files
= DragQueryFileW((HDROP
)stgm
.hGlobal
, 0xFFFFFFFF, NULL
, 0);
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
);
370 if ((IS_INTRESOURCE(pici
->lpVerb
) && pici
->lpVerb
== 0) || !strcmp(pici
->lpVerb
, NEW_SUBVOL_VERBA
)) {
372 IO_STATUS_BLOCK iosb
;
374 ULONG pathlen
, searchpathlen
, pathend
;
375 WCHAR name
[MAX_PATH
], *searchpath
;
377 WIN32_FIND_DATAW wfd
;
379 if (!LoadStringW(module
, IDS_NEW_SUBVOL_FILENAME
, name
, MAX_PATH
)) {
380 ShowError(pici
->hwnd
, GetLastError());
384 h
= CreateFileW(path
, FILE_ADD_SUBDIRECTORY
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, NULL
, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
386 if (h
== INVALID_HANDLE_VALUE
) {
387 ShowError(pici
->hwnd
, GetLastError());
391 pathlen
= wcslen(path
);
393 searchpathlen
= pathlen
+ wcslen(name
) + 10;
394 searchpath
= (WCHAR
*)malloc(searchpathlen
* sizeof(WCHAR
));
396 StringCchCopyW(searchpath
, searchpathlen
, path
);
397 StringCchCatW(searchpath
, searchpathlen
, L
"\\");
398 pathend
= wcslen(searchpath
);
400 StringCchCatW(searchpath
, searchpathlen
, name
);
402 fff
= FindFirstFileW(searchpath
, &wfd
);
404 if (fff
!= INVALID_HANDLE_VALUE
) {
405 ULONG i
= wcslen(searchpath
), num
= 2;
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
);
417 fff
= FindFirstFileW(searchpath
, &wfd
);
419 } while (fff
!= INVALID_HANDLE_VALUE
);
422 Status
= NtFsControlFile(h
, NULL
, NULL
, NULL
, &iosb
, FSCTL_BTRFS_CREATE_SUBVOL
, NULL
, 0, &searchpath
[pathend
], wcslen(&searchpath
[pathend
]) * sizeof(WCHAR
));
426 if (Status
!= STATUS_SUCCESS
) {
428 ShowNtStatusError(pici
->hwnd
, Status
);
440 HRESULT __stdcall
BtrfsContextMenu::GetCommandString(UINT_PTR idCmd
, UINT uFlags
, UINT
* pwReserved
, LPSTR pszName
, UINT cchMax
) {
449 if (LoadStringA(module
, bg
? IDS_NEW_SUBVOL_HELP_TEXT
: IDS_CREATE_SNAPSHOT_HELP_TEXT
, pszName
, cchMax
))
455 if (LoadStringW(module
, bg
? IDS_NEW_SUBVOL_HELP_TEXT
: IDS_CREATE_SNAPSHOT_HELP_TEXT
, (LPWSTR
)pszName
, cchMax
))
465 return StringCchCopyA(pszName
, cchMax
, bg
? NEW_SUBVOL_VERBA
: SNAPSHOT_VERBA
);
468 return StringCchCopyW((STRSAFE_LPWSTR
)pszName
, cchMax
, bg
? NEW_SUBVOL_VERBW
: SNAPSHOT_VERBW
);