summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/dokan/ceph_dokan.cc40
-rw-r--r--src/dokan/ceph_dokan.h8
-rw-r--r--src/dokan/options.cc9
-rw-r--r--src/test/dokan/dokan.cc86
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);
+}