summaryrefslogtreecommitdiffstats
path: root/src/dokan/ceph_dokan.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/dokan/ceph_dokan.cc
parentInitial commit. (diff)
downloadceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz
ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/dokan/ceph_dokan.cc')
-rw-r--r--src/dokan/ceph_dokan.cc1086
1 files changed, 1086 insertions, 0 deletions
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;
+}