summaryrefslogtreecommitdiffstats
path: root/src/dokan
diff options
context:
space:
mode:
authorLucian Petrut <lpetrut@cloudbasesolutions.com>2020-08-07 14:07:20 +0200
committerLucian Petrut <lpetrut@cloudbasesolutions.com>2021-03-05 14:13:31 +0100
commitaf6b37364b75faae73695bd77538d52ec9cd657a (patch)
treed40a02d8540f631a3c163b8e2a00eef18a33594b /src/dokan
parentcephfs: add ceph_may_delete function (diff)
downloadceph-af6b37364b75faae73695bd77538d52ec9cd657a.tar.xz
ceph-af6b37364b75faae73695bd77538d52ec9cd657a.zip
cephfs: Add ceph-dokan, providing Windows support
In order to expose ceph filesystems to Windows hosts, we propose including ceph-dokan[1][2] in the Ceph tree, while updating it to work with the latest CephFS and Dokany APIs. Dokany is a well maintained project (fork of the original Dokan project), allowing filesystems to be implemented in userspace, even providing a Fuse compatibility layer. One reason for not using the FUSE compatibility layer is that it's only covering the high level API while Ceph is using the low level FUSE API, which among other things is inode centric. Changes made by this patch compared to the upstream ceph-dokan: * support latest stable Dokany API. The upstream version relies on the legacy unmaintained Dokan API * return proper error codes, converting standard errno.h values to NTSTATUS * minor changes to support latest cephfs API * drop duplicated ceph code, no longer needed if we're to include it in tree. This makes it much easier to maintain. * drop redundant permission checks, leaving it up to libcephfs * use ceph argparse helpers * use ceph logging and daemon initialization * fixed unicode handling * switched to ceph coding style * made ceph.conf param optional, using the default path if available * enabled setting file timestamps * append support * configurable timeouts set once per mount * ensure that the error code is always logged * various cleanups (removed unused entry points, checks that have been moved to dokany, simplified conditional statements, unnecessary conversions in the hot path, etc). [1] https://github.com/ketor/ceph-dokan [2] https://github.com/ceph/ceph-dokan Signed-off-by: Lucian Petrut <lpetrut@cloudbasesolutions.com>
Diffstat (limited to 'src/dokan')
-rw-r--r--src/dokan/CMakeLists.txt12
-rw-r--r--src/dokan/ceph_dokan.cc1017
-rw-r--r--src/dokan/ceph_dokan.h52
-rw-r--r--src/dokan/dbg.cc170
-rw-r--r--src/dokan/dbg.h26
-rw-r--r--src/dokan/options.cc193
-rw-r--r--src/dokan/utils.cc44
-rw-r--r--src/dokan/utils.h19
8 files changed, 1533 insertions, 0 deletions
diff --git a/src/dokan/CMakeLists.txt b/src/dokan/CMakeLists.txt
new file mode 100644
index 00000000000..cc05a0f29f6
--- /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 00000000000..a9bd3323da3
--- /dev/null
+++ b/src/dokan/ceph_dokan.cc
@@ -0,0 +1,1017 @@
+/*
+ * 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 "global/global_init.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_ntsatus(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(), 0755);
+ if (ret < 0) {
+ dout(2) << __func__ << " " << path
+ << ": ceph_mkdir failed. Error: " << ret << dendl;
+ return cephfs_errno_to_ntsatus(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, 0755, 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,
+ 0755, 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,
+ 0755, 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, 0755, 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, 0755, 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, 0755, 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, 0755, fdc);
+ case CREATE_ALWAYS:
+ // create & return 0
+ return do_open_file(path, O_CREAT | O_TRUNC | O_RDWR, 0755, fdc);
+ case OPEN_ALWAYS:
+ return do_open_file(path, O_CREAT | O_RDWR, 0755, 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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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_ntsatus(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)
+{
+ // TODO: configurable volume name and serial number.
+ // We should also support having multiple mounts.
+ wcscpy(VolumeNameBuffer, L"Ceph");
+ *VolumeSerialNumber = 0x19831116;
+ *MaximumComponentLength = 256;
+ *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_ntsatus(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_unmount() {
+ 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)
+{
+ do_unmount();
+ // 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)
+{
+ do_unmount();
+}
+
+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_ntsatus(r);
+ }
+
+ atexit(unmount_atexit);
+ dout(0) << "Mounted cephfs directory: " << ceph_getcwd(cmount)
+ <<". Mountpoint: " << to_string(g_cfg->mountpoint) << dendl;
+
+ DWORD status = DokanMain(dokan_options, dokan_operations);
+ switch (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;
+ }
+
+ free(dokan_options);
+ free(dokan_operations);
+ return 0;
+}
+
+boost::intrusive_ptr<CephContext> do_global_init(
+ int argc, const char **argv, Command cmd)
+{
+ std::vector<const char*> args;
+ argv_to_vec(argc, argv, args);
+
+ 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;
+ std::vector<const char*> args;
+ argv_to_vec(argc, argv, args);
+ 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();
+ 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 00000000000..452e5171e73
--- /dev/null
+++ b/src/dokan/ceph_dokan.h
@@ -0,0 +1,52 @@
+/*
+ * 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
+#define CEPH_DOKAN_DEFAULT_THREAD_COUNT 10
+
+typedef DWORD NTSTATUS;
+// Avoid conflicting COM types, exposed when using C++.
+#define _OLE2_H_
+
+#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;
+ int thread_count = CEPH_DOKAN_DEFAULT_THREAD_COUNT;
+
+ std::wstring mountpoint = L"";
+ std::string root_path = "";
+};
+
+extern Config *g_cfg;
+
+// TODO: list and unmap commands.
+enum class Command {
+ None,
+ Version,
+ Help,
+ Map,
+};
+
+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 00000000000..1fb208f0917
--- /dev/null
+++ b/src/dokan/dbg.cc
@@ -0,0 +1,170 @@
+/*
+ * 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_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "rbd-wnbd: "
+
+#define check_flag(stream, val, flag) if (val & flag) { stream << "[" #flag "]"; }
+#define check_flag_eq(stream, val, flag) if (val == flag) { stream << "[" #flag "]"; }
+
+
+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: " << (DokanFileInfo->IsDirectory != NULL);
+
+ 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 00000000000..71e767ce2d3
--- /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 00000000000..0f3637b14b8
--- /dev/null
+++ b/src/dokan/options.cc
@@ -0,0 +1,193 @@
+/*
+ * 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 "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 "global/global_init.h"
+
+void print_usage() {
+ const char* usage_str = R"(
+Usage: ceph-dokan.exe -l <drive_letter>
+
+Map options:
+ -l [ --mountpoint ] arg mountpoint (path or drive letter) (e.g -l x)
+ -x [ --root-path ] arg mount a Ceph filesystem subdirectory
+
+ -t [ --thread-count] arg thread count
+ --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
+ -m [ --removable ] use a removable drive
+
+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;
+
+ 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", "-m", (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_flag(args, i, "--current-session-only", (char *)NULL)) {
+ cfg->current_session_only = true;
+ } else if (ceph_argparse_witharg(args, i, (int*)&cfg->thread_count,
+ err, "--thread-count", "-t", (char *)NULL)) {
+ if (!err.str().empty()) {
+ *err_msg << "ceph-dokan: " << err.str();
+ return -EINVAL;
+ }
+ if (cfg->thread_count < 0) {
+ *err_msg << "ceph-dokan: Invalid argument for thread-count";
+ return -EINVAL;
+ }
+ } 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 {
+ *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:
+ 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->ThreadCount = cfg->thread_count;
+ 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 00000000000..4705359c4d0
--- /dev/null
+++ b/src/dokan/utils.cc
@@ -0,0 +1,44 @@
+/*
+ * 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"
+
+#include <boost/locale/encoding_utf.hpp>
+
+using boost::locale::conv::utf_to_utf;
+
+std::wstring to_wstring(const std::string& str)
+{
+ return utf_to_utf<wchar_t>(str.c_str(), str.c_str() + str.size());
+}
+
+std::string to_string(const std::wstring& str)
+{
+ return utf_to_utf<char>(str.c_str(), str.c_str() + str.size());
+}
+
+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 00000000000..aa52b4d3a3c
--- /dev/null
+++ b/src/dokan/utils.h
@@ -0,0 +1,19 @@
+/*
+ * 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 "include/compat.h"
+
+std::wstring to_wstring(const std::string& str);
+std::string to_string(const std::wstring& str);
+
+void to_filetime(time_t t, LPFILETIME pft);
+void to_unix_time(FILETIME ft, time_t *t);