diff options
-rw-r--r-- | src/dokan/ceph_dokan.cc | 40 | ||||
-rw-r--r-- | src/dokan/ceph_dokan.h | 8 | ||||
-rw-r--r-- | src/dokan/options.cc | 9 | ||||
-rw-r--r-- | src/test/dokan/dokan.cc | 86 |
4 files changed, 136 insertions, 7 deletions
diff --git a/src/dokan/ceph_dokan.cc b/src/dokan/ceph_dokan.cc index 9e115222cab..6459ea261bf 100644 --- a/src/dokan/ceph_dokan.cc +++ b/src/dokan/ceph_dokan.cc @@ -77,9 +77,26 @@ typedef struct { static_assert(sizeof(fd_context) <= 8, "fd_context exceeds DOKAN_FILE_INFO.Context size."); -string get_path(LPCWSTR path_w) { +string get_path(LPCWSTR path_w, bool normalize_case=true) { string path = to_string(path_w); replace(path.begin(), path.end(), '\\', '/'); + + if (normalize_case && !g_cfg->case_sensitive) { + if (g_cfg->convert_to_uppercase) { + std::transform( + path.begin(), path.end(), path.begin(), + [](unsigned char c){ + return std::toupper(c); + }); + } else { + std::transform( + path.begin(), path.end(), path.begin(), + [](unsigned char c){ + return std::tolower(c); + }); + } + } + return path; } @@ -543,6 +560,11 @@ static NTSTATUS WinCephFindFiles( return cephfs_errno_to_ntstatus_map(ret); } + // TODO: retrieve the original case (e.g. using xattr) if configured + // to do so. + // TODO: provide aliases when case insensitive mounts cause collisions. + // For example, when having test.txt and Test.txt, the latter becomes + // TEST~1.txt WIN32_FIND_DATAW findData; int count = 0; while (1) { @@ -794,14 +816,18 @@ static NTSTATUS WinCephGetVolumeInformation( { 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; + *FileSystemFlags = + FILE_SUPPORTS_REMOTE_STORAGE | + FILE_UNICODE_ON_DISK | + FILE_PERSISTENT_ACLS; + + if (g_cfg->case_sensitive) { + *FileSystemFlags |= + FILE_CASE_SENSITIVE_SEARCH | + FILE_CASE_PRESERVED_NAMES; + } wcscpy(FileSystemNameBuffer, L"Ceph"); return 0; diff --git a/src/dokan/ceph_dokan.h b/src/dokan/ceph_dokan.h index 5957d4dead1..fe48aa45814 100644 --- a/src/dokan/ceph_dokan.h +++ b/src/dokan/ceph_dokan.h @@ -36,6 +36,14 @@ struct Config { unsigned long max_path_len = 256; mode_t file_mode = 0755; mode_t dir_mode = 0755; + + bool case_sensitive = true; + // Convert new file paths to upper case in case of case insensitive mounts. + // Visual Studio recommends normalizing to uppercase in order to avoid + // locale issues (CA1308). + bool convert_to_uppercase = true; + // TODO: consider adding an option to preserve the original case. + // It could be stored using an extended attribute. }; extern Config *g_cfg; diff --git a/src/dokan/options.cc b/src/dokan/options.cc index 1ed90ef9d34..705e1117ca9 100644 --- a/src/dokan/options.cc +++ b/src/dokan/options.cc @@ -45,6 +45,11 @@ Map options: --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. + --case-insensitive Emulate a case insensitive filesystem by normalizing + paths. The original case is NOT preserved. Existing + paths with a different case cannot be accessed. + --force-lowercase Use lowercase when normalizing paths. Uppercase is + used by default. Unmap options: -l [ --mountpoint ] arg mountpoint (path or drive letter) (e.g -l x). @@ -196,6 +201,10 @@ int parse_args( *err_msg << "ceph-dokan: Invalid argument for operation-timeout"; return -EINVAL; } + } else if (ceph_argparse_flag(args, i, "--case-insensitive", (char *)NULL)) { + cfg->case_sensitive = false; + } else if (ceph_argparse_flag(args, i, "--force-lowercase", (char *)NULL)) { + cfg->convert_to_uppercase = false; } else { ++i; } diff --git a/src/test/dokan/dokan.cc b/src/test/dokan/dokan.cc index 18f206985e8..eaa26557fe8 100644 --- a/src/test/dokan/dokan.cc +++ b/src/test/dokan/dokan.cc @@ -39,6 +39,18 @@ std::string get_uuid() { return suffix.to_string(); } +std::string to_upper(std::string& in) { + std::string out = in; + + std::transform( + out.begin(), out.end(), out.begin(), + [](unsigned char c){ + return std::toupper(c); + }); + + return out; +} + bool move_eof(HANDLE handle, LARGE_INTEGER offset) { // Move file pointer to FILE_BEGIN + offset @@ -162,6 +174,22 @@ void map_dokan_with_maxpath( } } +void map_dokan_case_insensitive(SubProcess** mount, const char* mountpoint, + bool force_lowercase=false) { + SubProcess* new_mount = new SubProcess("ceph-dokan"); + + new_mount->add_cmd_args("map", "--win-vol-name", "TestCeph", + "--win-vol-serial", TEST_VOL_SERIAL, + "-l", mountpoint, "--case-insensitive", NULL); + if (force_lowercase) { + new_mount->add_cmd_args("--force-lowercase", NULL); + } + + *mount = new_mount; + ASSERT_EQ(new_mount->spawn(), 0); + ASSERT_EQ(wait_for_mount(mountpoint), 0); +} + void unmap_dokan(SubProcess* mount, const char* mountpoint) { std::string ret = run_cmd("ceph-dokan", "unmap", "-l", mountpoint, (char*)NULL); @@ -762,3 +790,61 @@ TEST_F(DokanTests, test_create_dispositions) { // clean-up ASSERT_TRUE(fs::remove(file_path)); } + +TEST_F(DokanTests, test_case_sensitive) { + std::string test_dir = DEFAULT_MOUNTPOINT"test_dir" + get_uuid() + "\\"; + std::string lower_file_path = test_dir + "file_" + get_uuid(); + std::string upper_file_path = to_upper(lower_file_path); + + ASSERT_TRUE(fs::create_directory(test_dir)); + std::ofstream{lower_file_path}; + + ASSERT_TRUE(fs::exists(lower_file_path)); + ASSERT_FALSE(fs::exists(upper_file_path)); + + // clean-up + fs::remove_all(test_dir); +} + +void test_case_insensitive(bool force_lowercase) { + std::string mountpoint = "Q:\\"; + std::string test_dir = mountpoint + "test_dir" + get_uuid() + "/"; + std::string file_name = "file_" + get_uuid(); + std::string lower_file_path = test_dir + file_name; + std::string upper_file_path = to_upper(lower_file_path); + + SubProcess* mount = nullptr; + map_dokan_case_insensitive(&mount, mountpoint.c_str(), force_lowercase); + + ASSERT_TRUE(fs::create_directory(test_dir)); + std::ofstream{upper_file_path}; + + ASSERT_TRUE(fs::exists(lower_file_path)); + ASSERT_TRUE(fs::exists(upper_file_path)); + + std::vector<std::string> paths; + for (const auto & entry : fs::recursive_directory_iterator(test_dir)) { + paths.push_back(entry.path().filename().generic_string()); + } + + bool found_lowercase = std::find( + begin(paths), end(paths), file_name) != end(paths); + bool found_uppercase = std::find( + begin(paths), end(paths), to_upper(file_name)) != end(paths); + + ASSERT_EQ(found_lowercase, force_lowercase); + ASSERT_NE(found_uppercase, force_lowercase); + + // clean-up + fs::remove_all(test_dir); + + unmap_dokan(mount, mountpoint.c_str()); +} + +TEST_F(DokanTests, test_case_insensitive_force_lower) { + test_case_insensitive(true); +} + +TEST_F(DokanTests, test_case_insensitive_force_upper) { + test_case_insensitive(false); +} |