diff options
Diffstat (limited to 'src/dokan')
-rw-r--r-- | src/dokan/CMakeLists.txt | 12 | ||||
-rw-r--r-- | src/dokan/ceph_dokan.cc | 1086 | ||||
-rw-r--r-- | src/dokan/ceph_dokan.h | 57 | ||||
-rw-r--r-- | src/dokan/dbg.cc | 171 | ||||
-rw-r--r-- | src/dokan/dbg.h | 26 | ||||
-rw-r--r-- | src/dokan/options.cc | 248 | ||||
-rw-r--r-- | src/dokan/utils.cc | 30 | ||||
-rw-r--r-- | src/dokan/utils.h | 18 |
8 files changed, 1648 insertions, 0 deletions
diff --git a/src/dokan/CMakeLists.txt b/src/dokan/CMakeLists.txt new file mode 100644 index 000000000..cc05a0f29 --- /dev/null +++ b/src/dokan/CMakeLists.txt @@ -0,0 +1,12 @@ +set(ceph_dokan_srcs + ceph_dokan.cc + dbg.cc + utils.cc + options.cc) +add_executable(ceph-dokan ${ceph_dokan_srcs}) +target_link_libraries(ceph-dokan ${DOKAN_LIBRARIES} + ${GSSAPI_LIBRARIES} + cephfs ceph-common global ${EXTRALIBS}) +set_target_properties(ceph-dokan PROPERTIES + COMPILE_FLAGS "-I${DOKAN_INCLUDE_DIRS}") +install(TARGETS ceph-dokan DESTINATION bin) diff --git a/src/dokan/ceph_dokan.cc b/src/dokan/ceph_dokan.cc new file mode 100644 index 000000000..9e115222c --- /dev/null +++ b/src/dokan/ceph_dokan.cc @@ -0,0 +1,1086 @@ +/* + * ceph-dokan - Win32 CephFS client based on Dokan + * + * Copyright (C) 2021 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * +*/ + +#define UNICODE +#define _UNICODE + +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" + +#include "ceph_dokan.h" + +#include <algorithm> +#include <stdlib.h> +#include <fileinfo.h> +#include <dirent.h> +#include <fcntl.h> +#include <signal.h> +#include <sddl.h> +#include <accctrl.h> +#include <aclapi.h> +#include <ntstatus.h> + +#include "common/ceph_argparse.h" +#include "common/config.h" +#include "common/debug.h" +#include "common/dout.h" +#include "common/errno.h" +#include "common/version.h" +#include "common/win32/wstring.h" + +#include "global/global_init.h" + +#include "include/uuid.h" + +#include "dbg.h" +#include "utils.h" + +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_client +#undef dout_prefix +#define dout_prefix *_dout << "ceph-dokan: " + +using namespace std; + +#define READ_ACCESS_REQUESTED(access_mode) \ + (access_mode & GENERIC_READ || \ + access_mode & FILE_SHARE_READ || \ + access_mode & STANDARD_RIGHTS_READ || \ + access_mode & FILE_SHARE_READ) +#define WRITE_ACCESS_REQUESTED(access_mode) \ + (access_mode & GENERIC_WRITE || \ + access_mode & FILE_SHARE_WRITE || \ + access_mode & STANDARD_RIGHTS_WRITE || \ + access_mode & FILE_SHARE_WRITE) + +// TODO: check if those dokan limits still stand. +#define CEPH_DOKAN_MAX_FILE_SZ (1LL << 40) // 1TB +#define CEPH_DOKAN_MAX_IO_SZ (128 * 1024 * 1024) // 128MB + +struct ceph_mount_info *cmount; +Config *g_cfg; + +// Used as part of DOKAN_FILE_INFO.Context, must fit within 8B. +typedef struct { + int fd; + short read_only; +} fd_context, *pfd_context; +static_assert(sizeof(fd_context) <= 8, + "fd_context exceeds DOKAN_FILE_INFO.Context size."); + +string get_path(LPCWSTR path_w) { + string path = to_string(path_w); + replace(path.begin(), path.end(), '\\', '/'); + return path; +} + +static NTSTATUS do_open_file( + string path, + int flags, + mode_t mode, + fd_context* fdc) +{ + dout(20) << __func__ << " " << path << dendl; + int fd = ceph_open(cmount, path.c_str(), flags, mode); + if (fd < 0) { + dout(2) << __func__ << " " << path + << ": ceph_open failed. Error: " << fd << dendl; + return cephfs_errno_to_ntstatus_map(fd); + } + + fdc->fd = fd; + dout(20) << __func__ << " " << path << " - fd: " << fd << dendl; + return 0; +} + +static NTSTATUS WinCephCreateDirectory( + LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo) +{ + string path = get_path(FileName); + dout(20) << __func__ << " " << path << dendl; + if (path == "/") { + return 0; + } + + int ret = ceph_mkdir(cmount, path.c_str(), g_cfg->dir_mode); + if (ret < 0) { + dout(2) << __func__ << " " << path + << ": ceph_mkdir failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + return 0; +} + +static NTSTATUS WinCephCreateFile( + LPCWSTR FileName, + PDOKAN_IO_SECURITY_CONTEXT SecurityContext, + ACCESS_MASK DesiredAccess, + ULONG FileAttributes, + ULONG ShareMode, + ULONG CreateDisposition, + ULONG CreateOptions, + PDOKAN_FILE_INFO DokanFileInfo) +{ + // TODO: use ZwCreateFile args by default and avoid conversions. + ACCESS_MASK AccessMode; + DWORD FlagsAndAttributes, CreationDisposition; + DokanMapKernelToUserCreateFileFlags( + DesiredAccess, FileAttributes, CreateOptions, CreateDisposition, + &AccessMode, &FlagsAndAttributes, &CreationDisposition); + + string path = get_path(FileName); + dout(20) << __func__ << " " << path + << ". CreationDisposition: " << CreationDisposition << dendl; + + if (g_cfg->debug) { + print_open_params( + path.c_str(), AccessMode, FlagsAndAttributes, ShareMode, + CreationDisposition, CreateOptions, DokanFileInfo); + } + + pfd_context fdc = (pfd_context) &(DokanFileInfo->Context); + *fdc = { 0 }; + NTSTATUS st = 0; + + struct ceph_statx stbuf; + unsigned int requested_attrs = CEPH_STATX_BASIC_STATS; + int ret = ceph_statx(cmount, path.c_str(), &stbuf, requested_attrs, 0); + if (!ret) { /* File Exists */ + if (S_ISREG(stbuf.stx_mode)) { + dout(20) << __func__ << " " << path << ". File exists." << dendl; + if (CreateOptions & FILE_DIRECTORY_FILE) { + dout(2) << __func__ << " " << path << ". Not a directory." << dendl; + return STATUS_NOT_A_DIRECTORY; + } + switch (CreationDisposition) { + case CREATE_NEW: + return STATUS_OBJECT_NAME_COLLISION; + case TRUNCATE_EXISTING: + // open O_TRUNC & return 0 + return do_open_file(path, O_CREAT | O_TRUNC | O_RDWR, + g_cfg->file_mode, fdc); + case OPEN_ALWAYS: + // open & return STATUS_OBJECT_NAME_COLLISION + if (!WRITE_ACCESS_REQUESTED(AccessMode)) + fdc->read_only = 1; + if ((st = do_open_file(path, fdc->read_only ? O_RDONLY : O_RDWR, + g_cfg->file_mode, fdc))) + return st; + return STATUS_OBJECT_NAME_COLLISION; + case OPEN_EXISTING: + // open & return 0 + if (!WRITE_ACCESS_REQUESTED(AccessMode)) + fdc->read_only = 1; + if ((st = do_open_file(path, fdc->read_only ? O_RDONLY : O_RDWR, + g_cfg->file_mode, fdc))) + return st; + return 0; + case CREATE_ALWAYS: + // open O_TRUNC & return STATUS_OBJECT_NAME_COLLISION + if ((st = do_open_file(path, O_CREAT | O_TRUNC | O_RDWR, + g_cfg->file_mode, fdc))) + return st; + return STATUS_OBJECT_NAME_COLLISION; + } + } else if (S_ISDIR(stbuf.stx_mode)) { + dout(20) << __func__ << " " << path << ". Directory exists." << dendl; + DokanFileInfo->IsDirectory = TRUE; + if (CreateOptions & FILE_NON_DIRECTORY_FILE) { + dout(2) << __func__ << " " << path << ". File is a directory." << dendl; + return STATUS_FILE_IS_A_DIRECTORY; + } + + switch (CreationDisposition) { + case CREATE_NEW: + return STATUS_OBJECT_NAME_COLLISION; + case TRUNCATE_EXISTING: + return 0; + case OPEN_ALWAYS: + case OPEN_EXISTING: + return do_open_file(path, O_RDONLY, g_cfg->file_mode, fdc); + case CREATE_ALWAYS: + return STATUS_OBJECT_NAME_COLLISION; + } + } else { + derr << __func__ << " " << path + << ": Unsupported st_mode: " << stbuf.stx_mode << dendl; + return STATUS_BAD_FILE_TYPE; + } + } else { // The file doens't exist. + if (DokanFileInfo->IsDirectory) { + // TODO: check create disposition. + dout(20) << __func__ << " " << path << ". New directory." << dendl; + if ((st = WinCephCreateDirectory(FileName, DokanFileInfo))) + return st; + // Dokan expects a file handle even when creating new directories. + return do_open_file(path, O_RDONLY, g_cfg->file_mode, fdc); + } + dout(20) << __func__ << " " << path << ". New file." << dendl; + switch (CreationDisposition) { + case CREATE_NEW: + // create & return 0 + return do_open_file(path, O_CREAT | O_RDWR | O_EXCL, + g_cfg->file_mode, fdc); + case CREATE_ALWAYS: + // create & return 0 + return do_open_file(path, O_CREAT | O_TRUNC | O_RDWR, + g_cfg->file_mode, fdc); + case OPEN_ALWAYS: + return do_open_file(path, O_CREAT | O_RDWR, + g_cfg->file_mode, fdc); + case OPEN_EXISTING: + case TRUNCATE_EXISTING: + dout(2) << __func__ << " " << path << ": Not found." << dendl; + return STATUS_OBJECT_NAME_NOT_FOUND; + default: + derr << __func__ << " " << path + << ": Unsupported create disposition: " + << CreationDisposition << dendl; + return STATUS_INVALID_PARAMETER; + } + } + + // We shouldn't get here. + derr << __func__ << ": unknown error while opening: " << path << dendl; + return STATUS_INTERNAL_ERROR; +} + +static void WinCephCloseFile( + LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo) +{ + string path = get_path(FileName); + + pfd_context fdc = (pfd_context) &(DokanFileInfo->Context); + if (!fdc) { + derr << __func__ << ": missing context: " << path << dendl; + return; + } + + dout(20) << __func__ << " " << path << " fd: " << fdc->fd << dendl; + int ret = ceph_close(cmount, fdc->fd); + if (ret) { + dout(2) << __func__ << " " << path + << " failed. fd: " << fdc->fd + << ". Error: " << ret << dendl; + } + + DokanFileInfo->Context = 0; +} + +static void WinCephCleanup( + LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo) +{ + string path = get_path(FileName); + + if (!DokanFileInfo->Context) { + dout(10) << __func__ << ": missing context: " << path << dendl; + return; + } + + if (DokanFileInfo->DeleteOnClose) { + dout(20) << __func__ << " DeleteOnClose: " << path << dendl; + if (DokanFileInfo->IsDirectory) { + int ret = ceph_rmdir(cmount, path.c_str()); + if (ret) + derr << __func__ << " " << path + << ": ceph_rmdir failed. Error: " << ret << dendl; + } else { + int ret = ceph_unlink(cmount, path.c_str()); + if (ret != 0) { + derr << __func__ << " " << path + << ": ceph_unlink failed. Error: " << ret << dendl; + } + } + } +} + +static NTSTATUS WinCephReadFile( + LPCWSTR FileName, + LPVOID Buffer, + DWORD BufferLength, + LPDWORD ReadLength, + LONGLONG Offset, + PDOKAN_FILE_INFO DokanFileInfo) +{ + if (!BufferLength) { + *ReadLength = 0; + return 0; + } + if (Offset < 0) { + dout(2) << __func__ << " " << get_path(FileName) + << ": Invalid offset: " << Offset << dendl; + return STATUS_INVALID_PARAMETER; + } + if (Offset > CEPH_DOKAN_MAX_FILE_SZ || + BufferLength > CEPH_DOKAN_MAX_IO_SZ) { + dout(2) << "File read too large: " << get_path(FileName) + << ". Offset: " << Offset + << ". Buffer length: " << BufferLength << dendl; + return STATUS_FILE_TOO_LARGE; + } + + pfd_context fdc = (pfd_context) &(DokanFileInfo->Context); + if (!fdc->fd) { + dout(15) << __func__ << " " << get_path(FileName) + << ". Missing context, using temporary handle." << dendl; + + string path = get_path(FileName); + int fd_new = ceph_open(cmount, path.c_str(), O_RDONLY, 0); + if (fd_new < 0) { + dout(2) << __func__ << " " << path + << ": ceph_open failed. Error: " << fd_new << dendl; + return cephfs_errno_to_ntstatus_map(fd_new); + } + + int ret = ceph_read(cmount, fd_new, (char*) Buffer, BufferLength, Offset); + if (ret < 0) { + dout(2) << __func__ << " " << path + << ": ceph_read failed. Error: " << ret + << ". Offset: " << Offset + << "Buffer length: " << BufferLength << dendl; + ceph_close(cmount, fd_new); + return cephfs_errno_to_ntstatus_map(ret); + } + *ReadLength = ret; + ceph_close(cmount, fd_new); + return 0; + } else { + int ret = ceph_read(cmount, fdc->fd, (char*) Buffer, BufferLength, Offset); + if (ret < 0) { + dout(2) << __func__ << " " << get_path(FileName) + << ": ceph_read failed. Error: " << ret + << ". Offset: " << Offset + << "Buffer length: " << BufferLength << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + *ReadLength = ret; + return 0; + } +} + +static NTSTATUS WinCephWriteFile( + LPCWSTR FileName, + LPCVOID Buffer, + DWORD NumberOfBytesToWrite, + LPDWORD NumberOfBytesWritten, + LONGLONG Offset, + PDOKAN_FILE_INFO DokanFileInfo) +{ + if (!NumberOfBytesToWrite) { + *NumberOfBytesWritten = 0; + return 0; + } + if (Offset < 0) { + if (DokanFileInfo->WriteToEndOfFile) { + string path = get_path(FileName); + struct ceph_statx stbuf; + unsigned int requested_attrs = CEPH_STATX_BASIC_STATS; + + int ret = ceph_statx(cmount, path.c_str(), &stbuf, requested_attrs, 0); + if (ret) { + dout(2) << __func__ << " " << path + << ": ceph_statx failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + + Offset = stbuf.stx_size; + } else { + dout(2) << __func__ << " " << get_path(FileName) + << ": Invalid offset: " << Offset << dendl; + return STATUS_INVALID_PARAMETER; + } + } + + if (Offset > CEPH_DOKAN_MAX_FILE_SZ || + NumberOfBytesToWrite > CEPH_DOKAN_MAX_IO_SZ) { + dout(2) << "File write too large: " << get_path(FileName) + << ". Offset: " << Offset + << ". Buffer length: " << NumberOfBytesToWrite + << ". WriteToEndOfFile: " << (bool) DokanFileInfo->WriteToEndOfFile + << dendl; + return STATUS_FILE_TOO_LARGE; + } + pfd_context fdc = (pfd_context) &(DokanFileInfo->Context); + if (fdc->read_only) + return STATUS_ACCESS_DENIED; + + // TODO: check if we still have to support missing handles. + // According to Dokan docs, it might be related to memory mapped files, in + // which case reads/writes can be performed between the Close/Cleanup calls. + if (!fdc->fd) { + string path = get_path(FileName); + dout(15) << __func__ << " " << path + << ". Missing context, using temporary handle." << dendl; + + int fd_new = ceph_open(cmount, path.c_str(), O_RDWR, 0); + if (fd_new < 0) { + dout(2) << __func__ << " " << path + << ": ceph_open failed. Error: " << fd_new << dendl; + return cephfs_errno_to_ntstatus_map(fd_new); + } + + int ret = ceph_write(cmount, fd_new, (char*) Buffer, + NumberOfBytesToWrite, Offset); + if (ret < 0) { + dout(2) << __func__ << " " << path + << ": ceph_write failed. Error: " << ret + << ". Offset: " << Offset + << "Buffer length: " << NumberOfBytesToWrite << dendl; + ceph_close(cmount, fd_new); + return cephfs_errno_to_ntstatus_map(ret); + } + *NumberOfBytesWritten = ret; + ceph_close(cmount, fd_new); + return 0; + } else { + int ret = ceph_write(cmount, fdc->fd, (char*) Buffer, + NumberOfBytesToWrite, Offset); + if (ret < 0) { + dout(2) << __func__ << " " << get_path(FileName) + << ": ceph_write failed. Error: " << ret + << ". Offset: " << Offset + << "Buffer length: " << NumberOfBytesToWrite << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + *NumberOfBytesWritten = ret; + return 0; + } +} + +static NTSTATUS WinCephFlushFileBuffers( + LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo) +{ + pfd_context fdc = (pfd_context) &(DokanFileInfo->Context); + if (!fdc->fd) { + derr << __func__ << ": missing context: " << get_path(FileName) << dendl; + return STATUS_INVALID_HANDLE; + } + + int ret = ceph_fsync(cmount, fdc->fd, 0); + if (ret) { + dout(2) << __func__ << " " << get_path(FileName) + << ": ceph_sync failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + return 0; +} + +static NTSTATUS WinCephGetFileInformation( + LPCWSTR FileName, + LPBY_HANDLE_FILE_INFORMATION HandleFileInformation, + PDOKAN_FILE_INFO DokanFileInfo) +{ + string path = get_path(FileName); + dout(20) << __func__ << " " << path << dendl; + + memset(HandleFileInformation, 0, sizeof(BY_HANDLE_FILE_INFORMATION)); + + struct ceph_statx stbuf; + unsigned int requested_attrs = CEPH_STATX_BASIC_STATS; + pfd_context fdc = (pfd_context) &(DokanFileInfo->Context); + if (!fdc->fd) { + int ret = ceph_statx(cmount, path.c_str(), &stbuf, requested_attrs, 0); + if (ret) { + dout(2) << __func__ << " " << path + << ": ceph_statx failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + } else { + int ret = ceph_fstatx(cmount, fdc->fd, &stbuf, requested_attrs, 0); + if (ret) { + dout(2) << __func__ << " " << path + << ": ceph_fstatx failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + } + + HandleFileInformation->nFileSizeLow = (stbuf.stx_size << 32) >> 32; + HandleFileInformation->nFileSizeHigh = stbuf.stx_size >> 32; + + to_filetime(stbuf.stx_ctime.tv_sec, &HandleFileInformation->ftCreationTime); + to_filetime(stbuf.stx_atime.tv_sec, &HandleFileInformation->ftLastAccessTime); + to_filetime(stbuf.stx_mtime.tv_sec, &HandleFileInformation->ftLastWriteTime); + + if (S_ISDIR(stbuf.stx_mode)) { + HandleFileInformation->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY; + } else if (S_ISREG(stbuf.stx_mode)) { + HandleFileInformation->dwFileAttributes |= FILE_ATTRIBUTE_NORMAL; + } + + HandleFileInformation->nFileIndexLow = (stbuf.stx_ino << 32) >> 32; + HandleFileInformation->nFileIndexHigh = stbuf.stx_ino >> 32; + + HandleFileInformation->nNumberOfLinks = stbuf.stx_nlink; + return 0; +} + +static NTSTATUS WinCephFindFiles( + LPCWSTR FileName, + PFillFindData FillFindData, // function pointer + PDOKAN_FILE_INFO DokanFileInfo) +{ + string path = get_path(FileName); + dout(20) << __func__ << " " << path << dendl; + + struct ceph_dir_result *dirp; + int ret = ceph_opendir(cmount, path.c_str(), &dirp); + if (ret != 0) { + dout(2) << __func__ << " " << path + << ": ceph_mkdir failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + + WIN32_FIND_DATAW findData; + int count = 0; + while (1) { + memset(&findData, 0, sizeof(findData)); + struct dirent result; + struct ceph_statx stbuf; + + unsigned int requested_attrs = CEPH_STATX_BASIC_STATS; + ret = ceph_readdirplus_r(cmount, dirp, &result, &stbuf, + requested_attrs, + 0, // no special flags used when filling attrs + NULL); // we're not using inodes. + if (!ret) + break; + if (ret < 0) { + dout(2) << __func__ << " " << path + << ": ceph_readdirplus_r failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + + to_wstring(result.d_name).copy(findData.cFileName, MAX_PATH); + + findData.nFileSizeLow = (stbuf.stx_size << 32) >> 32; + findData.nFileSizeHigh = stbuf.stx_size >> 32; + + to_filetime(stbuf.stx_ctime.tv_sec, &findData.ftCreationTime); + to_filetime(stbuf.stx_atime.tv_sec, &findData.ftLastAccessTime); + to_filetime(stbuf.stx_mtime.tv_sec, &findData.ftLastWriteTime); + + if (S_ISDIR(stbuf.stx_mode)) { + findData.dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY; + } else if (S_ISREG(stbuf.stx_mode)) { + findData.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL; + } + + FillFindData(&findData, DokanFileInfo); + count++; + } + + ceph_closedir(cmount, dirp); + + dout(20) << __func__ << " " << path + << " found " << count << " entries." << dendl; + return 0; +} + +/** + * This callback is only supposed to check if deleting a file is + * allowed. The actual file deletion will be performed by WinCephCleanup + */ +static NTSTATUS WinCephDeleteFile( + LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo) +{ + string path = get_path(FileName); + dout(20) << __func__ << " " << path << dendl; + + if (ceph_may_delete(cmount, path.c_str()) < 0) { + return STATUS_ACCESS_DENIED; + } + + return 0; +} + +static NTSTATUS WinCephDeleteDirectory( + LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo) +{ + string path = get_path(FileName); + dout(20) << __func__ << " " << path << dendl; + + if (ceph_may_delete(cmount, path.c_str()) < 0) { + return STATUS_ACCESS_DENIED; + } + + struct ceph_dir_result *dirp; + int ret = ceph_opendir(cmount, path.c_str(), &dirp); + if (ret != 0) { + dout(2) << __func__ << " " << path + << ": ceph_opendir failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + + WIN32_FIND_DATAW findData; + while (1) { + memset(&findData, 0, sizeof(findData)); + struct dirent *result = ceph_readdir(cmount, dirp); + if (result) { + if (strcmp(result->d_name, ".") && strcmp(result->d_name, "..")) { + ceph_closedir(cmount, dirp); + dout(2) << __func__ << " " << path + << ": directory is not empty. " << dendl; + return STATUS_DIRECTORY_NOT_EMPTY; + } + } else break; + } + + ceph_closedir(cmount, dirp); + return 0; +} + +static NTSTATUS WinCephMoveFile( + LPCWSTR FileName, // existing file name + LPCWSTR NewFileName, + BOOL ReplaceIfExisting, + PDOKAN_FILE_INFO DokanFileInfo) +{ + string path = get_path(FileName); + string new_path = get_path(NewFileName); + dout(20) << __func__ << " " << path << " -> " << new_path << dendl; + + int ret = ceph_rename(cmount, path.c_str(), new_path.c_str()); + if (ret) { + dout(2) << __func__ << " " << path << " -> " << new_path + << ": ceph_rename failed. Error: " << ret << dendl; + } + + return cephfs_errno_to_ntstatus_map(ret); +} + +static NTSTATUS WinCephSetEndOfFile( + LPCWSTR FileName, + LONGLONG ByteOffset, + PDOKAN_FILE_INFO DokanFileInfo) +{ + pfd_context fdc = (pfd_context) &(DokanFileInfo->Context); + if (!fdc->fd) { + derr << __func__ << ": missing context: " << get_path(FileName) << dendl; + return STATUS_INVALID_HANDLE; + } + + int ret = ceph_ftruncate(cmount, fdc->fd, ByteOffset); + if (ret) { + dout(2) << __func__ << " " << get_path(FileName) + << ": ceph_ftruncate failed. Error: " << ret + << " Offset: " << ByteOffset << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + + return 0; +} + +static NTSTATUS WinCephSetAllocationSize( + LPCWSTR FileName, + LONGLONG AllocSize, + PDOKAN_FILE_INFO DokanFileInfo) +{ + pfd_context fdc = (pfd_context) &(DokanFileInfo->Context); + if (!fdc->fd) { + derr << __func__ << ": missing context: " << get_path(FileName) << dendl; + return STATUS_INVALID_HANDLE; + } + + struct ceph_statx stbuf; + unsigned int requested_attrs = CEPH_STATX_BASIC_STATS; + int ret = ceph_fstatx(cmount, fdc->fd, &stbuf, requested_attrs, 0); + if (ret) { + dout(2) << __func__ << " " << get_path(FileName) + << ": ceph_fstatx failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + + if ((unsigned long long) AllocSize < stbuf.stx_size) { + int ret = ceph_ftruncate(cmount, fdc->fd, AllocSize); + if (ret) { + dout(2) << __func__ << " " << get_path(FileName) + << ": ceph_ftruncate failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + return 0; + } + return 0; +} + +static NTSTATUS WinCephSetFileAttributes( + LPCWSTR FileName, + DWORD FileAttributes, + PDOKAN_FILE_INFO DokanFileInfo) +{ + string path = get_path(FileName); + dout(20) << __func__ << " (stubbed) " << path << dendl; + return 0; +} + +static NTSTATUS WinCephSetFileTime( + LPCWSTR FileName, + CONST FILETIME* CreationTime, + CONST FILETIME* LastAccessTime, + CONST FILETIME* LastWriteTime, + PDOKAN_FILE_INFO DokanFileInfo) +{ + // TODO: as per a previous inline comment, this might cause problems + // with some apps such as MS Office (different error code than expected + // or ctime issues probably). We might allow disabling it. + string path = get_path(FileName); + dout(20) << __func__ << " " << path << dendl; + + struct ceph_statx stbuf = { 0 }; + int mask = 0; + if (CreationTime) { + mask |= CEPH_SETATTR_CTIME; + // On Windows, st_ctime is the creation time while on Linux it's the time + // of the last metadata change. We'll try to stick with the Windows + // semantics, although this might be overridden by Linux hosts. + to_unix_time(*CreationTime, &stbuf.stx_ctime.tv_sec); + } + if (LastAccessTime) { + mask |= CEPH_SETATTR_ATIME; + to_unix_time(*LastAccessTime, &stbuf.stx_atime.tv_sec); + } + if (LastWriteTime) { + mask |= CEPH_SETATTR_MTIME; + to_unix_time(*LastWriteTime, &stbuf.stx_mtime.tv_sec); + } + + int ret = ceph_setattrx(cmount, path.c_str(), &stbuf, mask, 0); + if (ret) { + dout(2) << __func__ << " " << path + << ": ceph_setattrx failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + return 0; +} + +static NTSTATUS WinCephSetFileSecurity( + LPCWSTR FileName, + PSECURITY_INFORMATION SecurityInformation, + PSECURITY_DESCRIPTOR SecurityDescriptor, + ULONG SecurityDescriptorLength, + PDOKAN_FILE_INFO DokanFileInfo) +{ + string path = get_path(FileName); + dout(20) << __func__ << " (stubbed) " << path << dendl; + // TODO: Windows ACLs are ignored. At the moment, we're reporting this + // operation as successful to avoid breaking applications. We might consider + // making this behavior configurable. + return 0; +} + +static NTSTATUS WinCephGetVolumeInformation( + LPWSTR VolumeNameBuffer, + DWORD VolumeNameSize, + LPDWORD VolumeSerialNumber, + LPDWORD MaximumComponentLength, + LPDWORD FileSystemFlags, + LPWSTR FileSystemNameBuffer, + DWORD FileSystemNameSize, + PDOKAN_FILE_INFO DokanFileInfo) +{ + g_cfg->win_vol_name.copy(VolumeNameBuffer, VolumeNameSize); + *VolumeSerialNumber = g_cfg->win_vol_serial; + + *MaximumComponentLength = g_cfg->max_path_len; + + *FileSystemFlags = FILE_CASE_SENSITIVE_SEARCH | + FILE_CASE_PRESERVED_NAMES | + FILE_SUPPORTS_REMOTE_STORAGE | + FILE_UNICODE_ON_DISK | + FILE_PERSISTENT_ACLS; + + wcscpy(FileSystemNameBuffer, L"Ceph"); + return 0; +} + +static NTSTATUS WinCephGetDiskFreeSpace( + PULONGLONG FreeBytesAvailable, + PULONGLONG TotalNumberOfBytes, + PULONGLONG TotalNumberOfFreeBytes, + PDOKAN_FILE_INFO DokanFileInfo) +{ + struct statvfs vfsbuf; + int ret = ceph_statfs(cmount, "/", &vfsbuf); + if (ret) { + derr << "ceph_statfs failed. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret);; + } + + *FreeBytesAvailable = vfsbuf.f_bsize * vfsbuf.f_bfree; + *TotalNumberOfBytes = vfsbuf.f_bsize * vfsbuf.f_blocks; + *TotalNumberOfFreeBytes = vfsbuf.f_bsize * vfsbuf.f_bfree; + + return 0; +} + +int do_unmap(wstring& mountpoint) { + if (!DokanRemoveMountPoint(mountpoint.c_str())) { + wcerr << "Couldn't remove the specified CephFS mount: " + << mountpoint << std::endl; + return -EINVAL; + } + return 0; +} + +int cleanup_mount() { + int ret = ceph_unmount(cmount); + if (ret) + derr << "Couldn't perform clean unmount. Error: " << ret << dendl; + else + dout(0) << "Unmounted." << dendl; + return ret; +} + +static NTSTATUS WinCephUnmount( + PDOKAN_FILE_INFO DokanFileInfo) +{ + cleanup_mount(); + // TODO: consider propagating unmount errors to Dokan. + return 0; +} + +BOOL WINAPI ConsoleHandler(DWORD dwType) +{ + switch(dwType) { + case CTRL_C_EVENT: + dout(0) << "Received ctrl-c." << dendl; + exit(0); + case CTRL_BREAK_EVENT: + dout(0) << "Received break event." << dendl; + break; + default: + dout(0) << "Received console event: " << dwType << dendl; + } + return TRUE; +} + +static void unmount_atexit(void) +{ + cleanup_mount(); +} + +NTSTATUS get_volume_serial(PDWORD serial) { + int64_t fs_cid = ceph_get_fs_cid(cmount); + + char fsid_str[64] = { 0 }; + int ret = ceph_getxattr(cmount, "/", "ceph.cluster_fsid", + fsid_str, sizeof(fsid_str)); + if (ret < 0) { + dout(2) << "Coudln't retrieve the cluster fsid. Error: " << ret << dendl; + return cephfs_errno_to_ntstatus_map(ret); + } + + uuid_d fsid; + if (!fsid.parse(fsid_str)) { + dout(2) << "Couldn't parse cluster fsid" << dendl; + return STATUS_INTERNAL_ERROR; + } + + // We're generating a volume serial number by concatenating the last 16 bits + // of the filesystem id and the cluster fsid. + *serial = ((*(uint16_t*) fsid.bytes() & 0xffff) << 16) | (fs_cid & 0xffff); + + return 0; +} + +int do_map() { + PDOKAN_OPERATIONS dokan_operations = + (PDOKAN_OPERATIONS) malloc(sizeof(DOKAN_OPERATIONS)); + PDOKAN_OPTIONS dokan_options = + (PDOKAN_OPTIONS) malloc(sizeof(DOKAN_OPTIONS)); + if (!dokan_operations || !dokan_options) { + derr << "Not enough memory" << dendl; + return -ENOMEM; + } + + int r = set_dokan_options(g_cfg, dokan_options); + if (r) { + return r; + } + + ZeroMemory(dokan_operations, sizeof(DOKAN_OPERATIONS)); + dokan_operations->ZwCreateFile = WinCephCreateFile; + dokan_operations->Cleanup = WinCephCleanup; + dokan_operations->CloseFile = WinCephCloseFile; + dokan_operations->ReadFile = WinCephReadFile; + dokan_operations->WriteFile = WinCephWriteFile; + dokan_operations->FlushFileBuffers = WinCephFlushFileBuffers; + dokan_operations->GetFileInformation = WinCephGetFileInformation; + dokan_operations->FindFiles = WinCephFindFiles; + dokan_operations->SetFileAttributes = WinCephSetFileAttributes; + dokan_operations->SetFileTime = WinCephSetFileTime; + dokan_operations->DeleteFile = WinCephDeleteFile; + dokan_operations->DeleteDirectory = WinCephDeleteDirectory; + dokan_operations->MoveFile = WinCephMoveFile; + dokan_operations->SetEndOfFile = WinCephSetEndOfFile; + dokan_operations->SetAllocationSize = WinCephSetAllocationSize; + dokan_operations->SetFileSecurity = WinCephSetFileSecurity; + dokan_operations->GetDiskFreeSpace = WinCephGetDiskFreeSpace; + dokan_operations->GetVolumeInformation = WinCephGetVolumeInformation; + dokan_operations->Unmounted = WinCephUnmount; + + ceph_create_with_context(&cmount, g_ceph_context); + + r = ceph_mount(cmount, g_cfg->root_path.c_str()); + if (r) { + derr << "ceph_mount failed. Error: " << r << dendl; + return cephfs_errno_to_ntstatus_map(r); + } + + if (g_cfg->win_vol_name.empty()) { + string ceph_fs_name = g_conf().get_val<string>("client_fs"); + + g_cfg->win_vol_name = L"Ceph"; + if (!ceph_fs_name.empty()) { + g_cfg->win_vol_name += L" - " + to_wstring(ceph_fs_name); + } + } + + if (!g_cfg->win_vol_serial) { + if (get_volume_serial(&g_cfg->win_vol_serial)) { + return -EINVAL; + } + } + + if (g_cfg->max_path_len > 260) { + dout(0) << "maximum path length set to " << g_cfg->max_path_len + << ". Some Windows utilities may not be able to handle " + << "paths that exceed MAX_PATH (260) characters. " + << "CreateDirectoryW, used by Powershell, has also been " + << "observed to fail when paths exceed 16384 characters." + << dendl; + } + + atexit(unmount_atexit); + dout(0) << "Mounted cephfs directory: " << g_cfg->root_path.c_str() + <<". Mountpoint: " << to_string(g_cfg->mountpoint) << dendl; + + DokanInit(); + + DWORD status = DokanMain(dokan_options, dokan_operations); + switch (static_cast<int>(status)) { + case DOKAN_SUCCESS: + dout(2) << "Dokan has returned successfully" << dendl; + break; + case DOKAN_ERROR: + derr << "Received generic dokan error." << dendl; + break; + case DOKAN_DRIVE_LETTER_ERROR: + derr << "Invalid drive letter or mountpoint." << dendl; + break; + case DOKAN_DRIVER_INSTALL_ERROR: + derr << "Can't initialize Dokan driver." << dendl; + break; + case DOKAN_START_ERROR: + derr << "Dokan failed to start" << dendl; + break; + case DOKAN_MOUNT_ERROR: + derr << "Dokan mount error." << dendl; + break; + case DOKAN_MOUNT_POINT_ERROR: + derr << "Invalid mountpoint." << dendl; + break; + default: + derr << "Unknown Dokan error: " << status << dendl; + break; + } + + DokanShutdown(); + + free(dokan_options); + free(dokan_operations); + return 0; +} + +boost::intrusive_ptr<CephContext> do_global_init( + int argc, const char **argv, Command cmd) +{ + auto args = argv_to_vec(argc, argv); + + code_environment_t code_env; + int flags; + + switch (cmd) { + case Command::Map: + code_env = CODE_ENVIRONMENT_DAEMON; + flags = CINIT_FLAG_UNPRIVILEGED_DAEMON_DEFAULTS; + break; + default: + code_env = CODE_ENVIRONMENT_UTILITY; + flags = CINIT_FLAG_NO_MON_CONFIG; + break; + } + + global_pre_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, code_env, flags); + // Avoid cluttering the console when spawning a mapping that will run + // in the background. + if (g_conf()->daemonize) { + flags |= CINIT_FLAG_NO_DAEMON_ACTIONS; + } + auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, + code_env, flags, FALSE); + + // There's no fork on Windows, we should be safe calling this anytime. + common_init_finish(g_ceph_context); + global_init_chdir(g_ceph_context); + + return cct; +} + +int main(int argc, const char** argv) +{ + if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)ConsoleHandler, TRUE)) { + cerr << "Couldn't initialize console event handler." << std::endl; + return -EINVAL; + } + + g_cfg = new Config; + + Command cmd = Command::None; + auto args = argv_to_vec(argc, argv); + std::ostringstream err_msg; + int r = parse_args(args, &err_msg, &cmd, g_cfg); + if (r) { + std::cerr << err_msg.str() << std::endl; + return r; + } + + switch (cmd) { + case Command::Version: + std::cout << pretty_version_to_str() << std::endl; + return 0; + case Command::Help: + print_usage(); + return 0; + default: + break; + } + + auto cct = do_global_init(argc, argv, cmd); + + switch (cmd) { + case Command::Map: + return do_map(); + case Command::Unmap: + return do_unmap(g_cfg->mountpoint); + default: + print_usage(); + break; + } + + return 0; +} diff --git a/src/dokan/ceph_dokan.h b/src/dokan/ceph_dokan.h new file mode 100644 index 000000000..5957d4dea --- /dev/null +++ b/src/dokan/ceph_dokan.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * +*/ + +#pragma once + +#define CEPH_DOKAN_IO_DEFAULT_TIMEOUT 60 * 5 // Seconds + +// Avoid conflicting COM types, exposed when using C++. +#define _OLE2_H_ + +#include <bcrypt.h> // for typedef of NTSTATUS +#include <dokan.h> + +struct Config { + bool removable = false; + bool readonly = false; + bool use_win_mount_mgr = false; + bool current_session_only = false; + bool debug = false; + bool dokan_stderr = false; + + int operation_timeout = CEPH_DOKAN_IO_DEFAULT_TIMEOUT; + + std::wstring mountpoint = L""; + std::string root_path = "/"; + + std::wstring win_vol_name = L""; + unsigned long win_vol_serial = 0; + unsigned long max_path_len = 256; + mode_t file_mode = 0755; + mode_t dir_mode = 0755; +}; + +extern Config *g_cfg; + +// TODO: list and service commands. +enum class Command { + None, + Version, + Help, + Map, + Unmap, +}; + +void print_usage(); +int parse_args( + std::vector<const char*>& args, + std::ostream *err_msg, + Command *command, Config *cfg); +int set_dokan_options(Config *cfg, PDOKAN_OPTIONS dokan_options); diff --git a/src/dokan/dbg.cc b/src/dokan/dbg.cc new file mode 100644 index 000000000..4584178e0 --- /dev/null +++ b/src/dokan/dbg.cc @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2021 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * +*/ + +#include "ceph_dokan.h" +#include "utils.h" +#include "dbg.h" + +#include "common/debug.h" +#include "common/dout.h" + +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_client +#undef dout_prefix +#define dout_prefix *_dout << "ceph-dokan: " + +#define check_flag(stream, val, flag) if (val & flag) { stream << "[" #flag "]"; } +#define check_flag_eq(stream, val, flag) if (val == flag) { stream << "[" #flag "]"; } + +using namespace std; + +void print_credentials(ostringstream& Stream, PDOKAN_FILE_INFO DokanFileInfo) +{ + UCHAR buffer[1024]; + DWORD returnLength; + CHAR accountName[256]; + CHAR domainName[256]; + DWORD accountLength = sizeof(accountName) / sizeof(WCHAR); + DWORD domainLength = sizeof(domainName) / sizeof(WCHAR); + SID_NAME_USE snu; + + int err = 0; + HANDLE handle = DokanOpenRequestorToken(DokanFileInfo); + if (handle == INVALID_HANDLE_VALUE) { + err = GetLastError(); + derr << "DokanOpenRequestorToken failed. Error: " << err << dendl; + return; + } + + if (!GetTokenInformation(handle, TokenUser, buffer, + sizeof(buffer), &returnLength)) { + err = GetLastError(); + derr << "GetTokenInformation failed. Error: " << err << dendl; + CloseHandle(handle); + return; + } + + CloseHandle(handle); + + PTOKEN_USER tokenUser = (PTOKEN_USER)buffer; + if (!LookupAccountSidA(NULL, tokenUser->User.Sid, accountName, + &accountLength, domainName, &domainLength, &snu)) { + err = GetLastError(); + derr << "LookupAccountSid failed. Error: " << err << dendl; + return; + } + + Stream << "\n\tAccountName: " << accountName << ", DomainName: " << domainName; +} + +void print_open_params( + LPCSTR FilePath, + ACCESS_MASK AccessMode, + DWORD FlagsAndAttributes, + ULONG ShareMode, + DWORD CreationDisposition, + ULONG CreateOptions, + PDOKAN_FILE_INFO DokanFileInfo) +{ + ostringstream o; + o << "CreateFile: " << FilePath << ". "; + print_credentials(o, DokanFileInfo); + + o << "\n\tCreateDisposition: " << hex << CreationDisposition << " "; + check_flag_eq(o, CreationDisposition, CREATE_NEW); + check_flag_eq(o, CreationDisposition, OPEN_ALWAYS); + check_flag_eq(o, CreationDisposition, CREATE_ALWAYS); + check_flag_eq(o, CreationDisposition, OPEN_EXISTING); + check_flag_eq(o, CreationDisposition, TRUNCATE_EXISTING); + + o << "\n\tShareMode: " << hex << ShareMode << " "; + check_flag(o, ShareMode, FILE_SHARE_READ); + check_flag(o, ShareMode, FILE_SHARE_WRITE); + check_flag(o, ShareMode, FILE_SHARE_DELETE); + + o << "\n\tAccessMode: " << hex << AccessMode << " "; + check_flag(o, AccessMode, GENERIC_READ); + check_flag(o, AccessMode, GENERIC_WRITE); + check_flag(o, AccessMode, GENERIC_EXECUTE); + + check_flag(o, AccessMode, WIN32_DELETE); + check_flag(o, AccessMode, FILE_READ_DATA); + check_flag(o, AccessMode, FILE_READ_ATTRIBUTES); + check_flag(o, AccessMode, FILE_READ_EA); + check_flag(o, AccessMode, READ_CONTROL); + check_flag(o, AccessMode, FILE_WRITE_DATA); + check_flag(o, AccessMode, FILE_WRITE_ATTRIBUTES); + check_flag(o, AccessMode, FILE_WRITE_EA); + check_flag(o, AccessMode, FILE_APPEND_DATA); + check_flag(o, AccessMode, WRITE_DAC); + check_flag(o, AccessMode, WRITE_OWNER); + check_flag(o, AccessMode, SYNCHRONIZE); + check_flag(o, AccessMode, FILE_EXECUTE); + check_flag(o, AccessMode, STANDARD_RIGHTS_READ); + check_flag(o, AccessMode, STANDARD_RIGHTS_WRITE); + check_flag(o, AccessMode, STANDARD_RIGHTS_EXECUTE); + + o << "\n\tFlagsAndAttributes: " << hex << FlagsAndAttributes << " "; + check_flag(o, FlagsAndAttributes, FILE_ATTRIBUTE_ARCHIVE); + check_flag(o, FlagsAndAttributes, FILE_ATTRIBUTE_ENCRYPTED); + check_flag(o, FlagsAndAttributes, FILE_ATTRIBUTE_HIDDEN); + check_flag(o, FlagsAndAttributes, FILE_ATTRIBUTE_NORMAL); + check_flag(o, FlagsAndAttributes, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); + check_flag(o, FlagsAndAttributes, FILE_ATTRIBUTE_OFFLINE); + check_flag(o, FlagsAndAttributes, FILE_ATTRIBUTE_READONLY); + check_flag(o, FlagsAndAttributes, FILE_ATTRIBUTE_SYSTEM); + check_flag(o, FlagsAndAttributes, FILE_ATTRIBUTE_TEMPORARY); + check_flag(o, FlagsAndAttributes, FILE_FLAG_WRITE_THROUGH); + check_flag(o, FlagsAndAttributes, FILE_FLAG_OVERLAPPED); + check_flag(o, FlagsAndAttributes, FILE_FLAG_NO_BUFFERING); + check_flag(o, FlagsAndAttributes, FILE_FLAG_RANDOM_ACCESS); + check_flag(o, FlagsAndAttributes, FILE_FLAG_SEQUENTIAL_SCAN); + check_flag(o, FlagsAndAttributes, FILE_FLAG_DELETE_ON_CLOSE); + check_flag(o, FlagsAndAttributes, FILE_FLAG_BACKUP_SEMANTICS); + check_flag(o, FlagsAndAttributes, FILE_FLAG_POSIX_SEMANTICS); + check_flag(o, FlagsAndAttributes, FILE_FLAG_OPEN_REPARSE_POINT); + check_flag(o, FlagsAndAttributes, FILE_FLAG_OPEN_NO_RECALL); + check_flag(o, FlagsAndAttributes, SECURITY_ANONYMOUS); + check_flag(o, FlagsAndAttributes, SECURITY_IDENTIFICATION); + check_flag(o, FlagsAndAttributes, SECURITY_IMPERSONATION); + check_flag(o, FlagsAndAttributes, SECURITY_DELEGATION); + check_flag(o, FlagsAndAttributes, SECURITY_CONTEXT_TRACKING); + check_flag(o, FlagsAndAttributes, SECURITY_EFFECTIVE_ONLY); + check_flag(o, FlagsAndAttributes, SECURITY_SQOS_PRESENT); + + o << "\n\tIsDirectory: " << static_cast<bool>(DokanFileInfo->IsDirectory); + + o << "\n\tCreateOptions: " << hex << CreateOptions << " "; + check_flag(o, CreateOptions, FILE_DIRECTORY_FILE); + check_flag(o, CreateOptions, FILE_WRITE_THROUGH); + check_flag(o, CreateOptions, FILE_SEQUENTIAL_ONLY); + check_flag(o, CreateOptions, FILE_NO_INTERMEDIATE_BUFFERING); + check_flag(o, CreateOptions, FILE_SYNCHRONOUS_IO_ALERT); + check_flag(o, CreateOptions, FILE_SYNCHRONOUS_IO_NONALERT); + check_flag(o, CreateOptions, FILE_NON_DIRECTORY_FILE); + check_flag(o, CreateOptions, FILE_CREATE_TREE_CONNECTION); + check_flag(o, CreateOptions, FILE_COMPLETE_IF_OPLOCKED); + check_flag(o, CreateOptions, FILE_NO_EA_KNOWLEDGE); + check_flag(o, CreateOptions, FILE_OPEN_REMOTE_INSTANCE); + check_flag(o, CreateOptions, FILE_RANDOM_ACCESS); + check_flag(o, CreateOptions, FILE_DELETE_ON_CLOSE); + check_flag(o, CreateOptions, FILE_OPEN_BY_FILE_ID); + check_flag(o, CreateOptions, FILE_OPEN_FOR_BACKUP_INTENT); + check_flag(o, CreateOptions, FILE_NO_COMPRESSION); + check_flag(o, CreateOptions, FILE_OPEN_REQUIRING_OPLOCK); + check_flag(o, CreateOptions, FILE_DISALLOW_EXCLUSIVE); + check_flag(o, CreateOptions, FILE_RESERVE_OPFILTER); + check_flag(o, CreateOptions, FILE_OPEN_REPARSE_POINT); + check_flag(o, CreateOptions, FILE_OPEN_NO_RECALL); + check_flag(o, CreateOptions, FILE_OPEN_FOR_FREE_SPACE_QUERY); + + // We're using a high log level since this will only be enabled with the + // explicit debug flag. + dout(0) << o.str() << dendl; +} diff --git a/src/dokan/dbg.h b/src/dokan/dbg.h new file mode 100644 index 000000000..71e767ce2 --- /dev/null +++ b/src/dokan/dbg.h @@ -0,0 +1,26 @@ +// Various helpers used for debugging purposes, such as functions +// logging certain flags. Since those can be rather verbose, it's +// better if we keep them separate. + +#ifndef CEPH_DOKAN_DBG_H +#define CEPH_DOKAN_DBG_H + +#include "include/compat.h" + +#include <sstream> + +#include "ceph_dokan.h" + +void print_credentials( + std::ostringstream& Stream, + PDOKAN_FILE_INFO DokanFileInfo); +void print_open_params( + LPCSTR FilePath, + ACCESS_MASK AccessMode, + DWORD FlagsAndAttributes, + ULONG ShareMode, + DWORD CreationDisposition, + ULONG CreateOptions, + PDOKAN_FILE_INFO DokanFileInfo); + +#endif // CEPH_DOKAN_DBG_H diff --git a/src/dokan/options.cc b/src/dokan/options.cc new file mode 100644 index 000000000..4c968e13a --- /dev/null +++ b/src/dokan/options.cc @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2021 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * +*/ +#include <regex> + +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" + +#include "ceph_dokan.h" +#include "utils.h" + +#include "common/ceph_argparse.h" +#include "common/config.h" +#include "common/win32/wstring.h" + +#include "global/global_init.h" + +void print_usage() { + const char* usage_str = R"( +Usage: ceph-dokan.exe -l <mountpoint> + map -l <mountpoint> Map a CephFS filesystem + unmap -l <mountpoint> Unmap a CephFS filesystem + +Map options: + -l [ --mountpoint ] arg mountpoint (path or drive letter) (e.g -l x) + -x [ --root-path ] arg mount a Ceph filesystem subdirectory + + --operation-timeout arg Dokan operation timeout. Default: 120s. + + --debug enable debug output + --dokan-stderr enable stderr Dokan logging + + --read-only read-only mount + -o [ --win-mount-mgr] use the Windows mount manager + --current-session-only expose the mount only to the current user session + --removable use a removable drive + --win-vol-name arg The Windows volume name. Default: Ceph - <fs_name>. + --win-vol-serial arg The Windows volume serial number. Default: <fs_id>. + --max-path-len The value of the maximum path length. Default: 256. + --file-mode The access mode to be used when creating files. + --dir-mode The access mode to be used when creating directories. + +Unmap options: + -l [ --mountpoint ] arg mountpoint (path or drive letter) (e.g -l x). + It has to be the exact same mountpoint that was + used when the mapping was created. + +Common Options: +)"; + + std::cout << usage_str; + generic_client_usage(); +} + + +int parse_args( + std::vector<const char*>& args, + std::ostream *err_msg, + Command *command, Config *cfg) +{ + if (args.empty()) { + std::cout << "ceph-dokan: -h or --help for usage" << std::endl; + return -EINVAL; + } + + std::string conf_file_list; + std::string cluster; + CephInitParameters iparams = ceph_argparse_early_args( + args, CEPH_ENTITY_TYPE_CLIENT, &cluster, &conf_file_list); + + ConfigProxy config{false}; + config->name = iparams.name; + config->cluster = cluster; + if (!conf_file_list.empty()) { + config.parse_config_files(conf_file_list.c_str(), nullptr, 0); + } else { + config.parse_config_files(nullptr, nullptr, 0); + } + config.parse_env(CEPH_ENTITY_TYPE_CLIENT); + config.parse_argv(args); + + std::vector<const char*>::iterator i; + std::ostringstream err; + std::string mountpoint; + std::string win_vol_name; + std::string win_vol_serial; + std::string max_path_len; + std::string file_mode; + std::string dir_mode; + + int thread_count; + + for (i = args.begin(); i != args.end(); ) { + if (ceph_argparse_flag(args, i, "-h", "--help", (char*)NULL)) { + *command = Command::Help; + return 0; + } else if (ceph_argparse_flag(args, i, "-v", "--version", (char*)NULL)) { + *command = Command::Version; + } else if (ceph_argparse_witharg(args, i, &mountpoint, + "--mountpoint", "-l", (char *)NULL)) { + cfg->mountpoint = to_wstring(mountpoint); + } else if (ceph_argparse_witharg(args, i, &cfg->root_path, + "--root-path", "-x", (char *)NULL)) { + } else if (ceph_argparse_flag(args, i, "--debug", (char *)NULL)) { + cfg->debug = true; + } else if (ceph_argparse_flag(args, i, "--dokan-stderr", (char *)NULL)) { + cfg->dokan_stderr = true; + } else if (ceph_argparse_flag(args, i, "--read-only", (char *)NULL)) { + cfg->readonly = true; + } else if (ceph_argparse_flag(args, i, "--removable", (char *)NULL)) { + cfg->removable = true; + } else if (ceph_argparse_flag(args, i, "--win-mount-mgr", "-o", (char *)NULL)) { + cfg->use_win_mount_mgr = true; + } else if (ceph_argparse_witharg(args, i, &win_vol_name, + "--win-vol-name", (char *)NULL)) { + cfg->win_vol_name = to_wstring(win_vol_name); + } else if (ceph_argparse_witharg(args, i, &win_vol_serial, + "--win-vol-serial", (char *)NULL)) { + cfg->win_vol_serial = std::stoul(win_vol_serial); + } else if (ceph_argparse_witharg(args, i, &max_path_len, + "--max-path-len", (char*)NULL)) { + unsigned long max_path_length = std::stoul(max_path_len); + + if (max_path_length > 32767) { + *err_msg << "ceph-dokan: maximum path length should not " + << "exceed " << 32767; + return -EINVAL; + } + + if (max_path_length < 256) { + *err_msg << "ceph-dokan: maximum path length should not " + << "have a value lower than 256"; + return -EINVAL; + } + + cfg->max_path_len = max_path_length; + } else if (ceph_argparse_witharg(args, i, &file_mode, "--file-mode", (char *)NULL)) { + mode_t mode = strtol(file_mode.c_str(), NULL, 8); + if (!std::regex_match(file_mode, std::regex("^[0-7]{3}$")) + || mode < 01 || mode > 0777) { + *err_msg << "ceph-dokan: invalid file access mode"; + return -EINVAL; + } + cfg->file_mode = mode; + } else if (ceph_argparse_witharg(args, i, &dir_mode, "--dir-mode", (char *)NULL)) { + mode_t mode = strtol(dir_mode.c_str(), NULL, 8); + if (!std::regex_match(dir_mode, std::regex("^[0-7]{3}$")) + || mode < 01 || mode > 0777) { + *err_msg << "ceph-dokan: invalid directory access mode"; + return -EINVAL; + } + cfg->dir_mode = mode; + } else if (ceph_argparse_flag(args, i, "--current-session-only", (char *)NULL)) { + cfg->current_session_only = true; + } else if (ceph_argparse_witharg(args, i, &thread_count, + err, "--thread-count", "-t", (char *)NULL)) { + std::cerr << "ceph-dokan: the thread count parameter is not supported by Dokany v2 " + << "and has been deprecated." << std::endl; + } else if (ceph_argparse_witharg(args, i, (int*)&cfg->operation_timeout, + err, "--operation-timeout", (char *)NULL)) { + if (!err.str().empty()) { + *err_msg << "ceph-dokan: " << err.str(); + return -EINVAL; + } + if (cfg->operation_timeout < 0) { + *err_msg << "ceph-dokan: Invalid argument for operation-timeout"; + return -EINVAL; + } + } else { + ++i; + } + } + + if (cfg->use_win_mount_mgr && cfg->current_session_only) { + *err_msg << "ceph-dokan: The mount manager always mounts the drive " + << "for all user sessions."; + return -EINVAL; + } + + Command cmd = Command::None; + if (args.begin() != args.end()) { + if (strcmp(*args.begin(), "help") == 0) { + cmd = Command::Help; + } else if (strcmp(*args.begin(), "version") == 0) { + cmd = Command::Version; + } else if (strcmp(*args.begin(), "map") == 0) { + cmd = Command::Map; + } else if (strcmp(*args.begin(), "unmap") == 0) { + cmd = Command::Unmap; + } else { + *err_msg << "ceph-dokan: unknown command: " << *args.begin(); + return -EINVAL; + } + args.erase(args.begin()); + } + if (cmd == Command::None) { + // The default command. + cmd = Command::Map; + } + + switch (cmd) { + case Command::Map: + case Command::Unmap: + if (cfg->mountpoint.empty()) { + *err_msg << "ceph-dokan: missing mountpoint."; + return -EINVAL; + } + break; + default: + break; + } + + if (args.begin() != args.end()) { + *err_msg << "ceph-dokan: unknown args: " << *args.begin(); + return -EINVAL; + } + + *command = cmd; + return 0; +} + +int set_dokan_options(Config *cfg, PDOKAN_OPTIONS dokan_options) { + ZeroMemory(dokan_options, sizeof(DOKAN_OPTIONS)); + dokan_options->Version = DOKAN_VERSION; + dokan_options->MountPoint = cfg->mountpoint.c_str(); + dokan_options->Timeout = cfg->operation_timeout * 1000; + + if (cfg->removable) + dokan_options->Options |= DOKAN_OPTION_REMOVABLE; + if (cfg->use_win_mount_mgr) + dokan_options->Options |= DOKAN_OPTION_MOUNT_MANAGER; + if (cfg->current_session_only) + dokan_options->Options |= DOKAN_OPTION_CURRENT_SESSION; + if (cfg->readonly) + dokan_options->Options |= DOKAN_OPTION_WRITE_PROTECT; + if (cfg->debug) + dokan_options->Options |= DOKAN_OPTION_DEBUG; + if (cfg->dokan_stderr) + dokan_options->Options |= DOKAN_OPTION_STDERR; + + return 0; +} diff --git a/src/dokan/utils.cc b/src/dokan/utils.cc new file mode 100644 index 000000000..576cceb99 --- /dev/null +++ b/src/dokan/utils.cc @@ -0,0 +1,30 @@ +/* + * ceph-dokan - Win32 CephFS client based on Dokan + * + * Copyright (C) 2021 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * +*/ + +#include "utils.h" + +void to_filetime(time_t t, LPFILETIME pft) +{ + // Note that LONGLONG is a 64-bit value + LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000; + pft->dwLowDateTime = (DWORD)ll; + pft->dwHighDateTime = ll >> 32; +} + +void to_unix_time(FILETIME ft, time_t *t) +{ + ULARGE_INTEGER ui; + ui.LowPart = ft.dwLowDateTime; + ui.HighPart = ft.dwHighDateTime; + + *t = (LONGLONG)(ui.QuadPart / 10000000ULL - 11644473600ULL); +} diff --git a/src/dokan/utils.h b/src/dokan/utils.h new file mode 100644 index 000000000..0fb27818b --- /dev/null +++ b/src/dokan/utils.h @@ -0,0 +1,18 @@ +/* + * ceph-dokan - Win32 CephFS client based on Dokan + * + * Copyright (C) 2021 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * +*/ + +#pragma once + +#include "include/compat.h" + +void to_filetime(time_t t, LPFILETIME pft); +void to_unix_time(FILETIME ft, time_t *t); |