summaryrefslogtreecommitdiffstats
path: root/src/test/dokan
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/dokan')
-rw-r--r--src/test/dokan/CMakeLists.txt13
-rw-r--r--src/test/dokan/dokan.cc771
2 files changed, 784 insertions, 0 deletions
diff --git a/src/test/dokan/CMakeLists.txt b/src/test/dokan/CMakeLists.txt
new file mode 100644
index 000000000..71029d386
--- /dev/null
+++ b/src/test/dokan/CMakeLists.txt
@@ -0,0 +1,13 @@
+if(WITH_DOKAN)
+ add_executable(ceph_test_dokan
+ dokan.cc
+ )
+ target_link_libraries(ceph_test_dokan
+ ceph-common
+ ${UNITTEST_LIBS}
+ ${EXTRALIBS}
+ ${CMAKE_DL_LIBS}
+ )
+ install(TARGETS ceph_test_dokan
+ DESTINATION ${CMAKE_INSTALL_BINDIR})
+endif(WITH_DOKAN)
diff --git a/src/test/dokan/dokan.cc b/src/test/dokan/dokan.cc
new file mode 100644
index 000000000..1a1d39580
--- /dev/null
+++ b/src/test/dokan/dokan.cc
@@ -0,0 +1,771 @@
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2022 Cloudbase Solutions
+ *
+ * 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 <windows.h>
+#include <iostream>
+#include <fstream>
+#include <filesystem>
+#include <sys/socket.h>
+#include <direct.h>
+
+#include "gtest/gtest.h"
+
+#include "common/SubProcess.h"
+#include "common/run_cmd.h"
+#include "include/uuid.h"
+
+#define DEFAULT_MOUNTPOINT "X:\\"
+#define MOUNT_POLL_ATTEMPT 10
+#define MOUNT_POLL_INTERVAL_MS 1000
+#define TEST_VOL_SERIAL "1234567890"
+#define MByte 1048576
+
+namespace fs = std::filesystem;
+using namespace std::chrono_literals;
+
+std::string get_uuid() {
+ uuid_d suffix;
+ suffix.generate_random();
+
+ return suffix.to_string();
+}
+
+bool move_eof(HANDLE handle, LARGE_INTEGER offset) {
+
+ // Move file pointer to FILE_BEGIN + offset
+ if (!SetFilePointerEx(handle, offset, NULL, FILE_BEGIN)) {
+ std::cerr << "Setting file pointer failed. err: "
+ << GetLastError() << std::endl;
+ return false;
+ }
+
+ if (!SetEndOfFile(handle)) {
+ std::cerr << "Setting EOF failed. err: " << GetLastError() << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+void write_file(std::string file_path, std::string data) {
+ std::ofstream file;
+ file.open(file_path);
+
+ ASSERT_TRUE(file.is_open())
+ << "Failed to open file: " << file_path;
+ file << data;
+ file.flush();
+
+ file.close();
+}
+
+void expect_write_failure(std::string file_path) {
+ std::ofstream file;
+ file.open(file_path);
+
+ ASSERT_FALSE(file.is_open());
+}
+
+std::string read_file(std::string file_path) {
+ std::ifstream file;
+ file.open(file_path);
+ std::string content((std::istreambuf_iterator<char>(file)),
+ std::istreambuf_iterator<char>());
+ file.close();
+
+ return content;
+}
+
+void check_write_file(std::string file_path, std::string data) {
+ write_file(file_path, data);
+ ASSERT_EQ(read_file(file_path), data);
+}
+
+int wait_for_mount(std::string mount_path) {
+ std::cerr << "Waiting for mount: " << mount_path << std::endl;
+
+ int attempts = 0;
+ do {
+ attempts++;
+ if (attempts < MOUNT_POLL_ATTEMPT)
+ Sleep(MOUNT_POLL_INTERVAL_MS);
+ } while (!fs::exists(mount_path)
+ && attempts < MOUNT_POLL_ATTEMPT);
+
+ if (!fs::exists(mount_path)) {
+ std::cerr << "Timed out waiting for ceph-dokan mount: "
+ << mount_path << std::endl;
+ return -ETIMEDOUT;
+ }
+
+ std::cerr << "Successfully mounted: " << mount_path << std::endl;
+
+ return 0;
+}
+
+void map_dokan(SubProcess** mount, const char* mountpoint) {
+ 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, NULL);
+
+ *mount = new_mount;
+ ASSERT_EQ(new_mount->spawn(), 0);
+ ASSERT_EQ(wait_for_mount(mountpoint), 0);
+}
+
+void map_dokan_read_only(
+ SubProcess** mount,
+ const char* mountpoint
+) {
+ SubProcess* new_mount = new SubProcess("ceph-dokan");
+ new_mount->add_cmd_args("map", "--win-vol-name", "TestCeph",
+ "--win-vol-serial", TEST_VOL_SERIAL,
+ "--read-only", "-l", mountpoint, NULL);
+
+ *mount = new_mount;
+ ASSERT_EQ(new_mount->spawn(), 0);
+ ASSERT_EQ(wait_for_mount(mountpoint), 0);
+ std::cerr << mountpoint << " mounted in read-only mode"
+ << std::endl;
+}
+
+void map_dokan_with_maxpath(
+ SubProcess** mount,
+ const char* mountpoint,
+ uint64_t max_path_len)
+{
+ SubProcess* new_mount = new SubProcess("ceph-dokan");
+ new_mount->add_cmd_args("map", "--debug", "--dokan-stderr",
+ "--win-vol-name", "TestCeph",
+ "--win-vol-serial", TEST_VOL_SERIAL,
+ "--max-path-len",
+ (std::to_string(max_path_len)).c_str(),
+ "-l", mountpoint, NULL);
+
+ *mount = new_mount;
+ ASSERT_EQ(new_mount->spawn(), 0);
+ if (256 <= max_path_len && max_path_len <= 4096) {
+ ASSERT_EQ(wait_for_mount(mountpoint), 0);
+ } else {
+ ASSERT_NE(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);
+
+ ASSERT_EQ(ret, "") << "Failed unmapping: " << mountpoint;
+ std::cerr<< "Unmounted: " << mountpoint << std::endl;
+
+ ASSERT_EQ(mount->join(), 0);
+}
+
+int get_volume_max_path(std::string mountpoint){
+ char volume_name[MAX_PATH + 1] = { 0 };
+ char file_system_name[MAX_PATH + 1] = { 0 };
+ DWORD serial_number = 0;
+ DWORD max_component_len = 0;
+ DWORD file_system_flags = 0;
+ if (GetVolumeInformation(
+ mountpoint.c_str(),
+ volume_name,
+ sizeof(volume_name),
+ &serial_number,
+ &max_component_len,
+ &file_system_flags,
+ file_system_name,
+ sizeof(file_system_name)) != TRUE) {
+ std::cerr << "GetVolumeInformation() failed, error: "
+ << GetLastError() << std::endl;
+ }
+
+ return max_component_len;
+}
+
+static SubProcess* shared_mount = nullptr;
+
+class DokanTests : public testing::Test
+{
+protected:
+
+ static void SetUpTestSuite() {
+ map_dokan(&shared_mount, DEFAULT_MOUNTPOINT);
+ }
+
+ static void TearDownTestSuite() {
+ if (shared_mount) {
+ unmap_dokan(shared_mount, DEFAULT_MOUNTPOINT);
+ }
+ shared_mount = nullptr;
+ }
+};
+
+TEST_F(DokanTests, test_mount) {
+ std::string mountpoint = "Y:\\";
+ SubProcess* mount = nullptr;
+ map_dokan(&mount, mountpoint.c_str());
+ unmap_dokan(mount, mountpoint.c_str());
+}
+
+TEST_F(DokanTests, test_mount_read_only) {
+ std::string mountpoint = "Z:\\";
+ std::string data = "abc123";
+ std::string success_file_path = "ro_success_" + get_uuid();
+ std::string failed_file_path = "ro_fail_" + get_uuid();
+
+ SubProcess* mount = nullptr;
+ map_dokan(&mount, mountpoint.c_str());
+
+ check_write_file(mountpoint + success_file_path, data);
+ ASSERT_TRUE(fs::exists(mountpoint + success_file_path));
+
+ unmap_dokan(mount, mountpoint.c_str());
+
+ mount = nullptr;
+ map_dokan_read_only(&mount, mountpoint.c_str());
+
+ expect_write_failure(mountpoint + failed_file_path);
+ ASSERT_FALSE(fs::exists(mountpoint + failed_file_path));
+
+ ASSERT_TRUE(fs::exists(mountpoint + success_file_path));
+ ASSERT_EQ(read_file(mountpoint + success_file_path), data);
+
+ std::string exception_msg(
+ "filesystem error: cannot remove: No such device ["
+ + mountpoint + success_file_path + "]");
+ EXPECT_THROW({
+ try {
+ fs::remove(mountpoint + success_file_path);
+ } catch(const fs::filesystem_error &e) {
+ EXPECT_STREQ(e.what(), exception_msg.c_str());
+ throw;
+ }
+ }, fs::filesystem_error);
+ unmap_dokan(mount, mountpoint.c_str());
+
+ map_dokan(&mount, mountpoint.c_str());
+ ASSERT_TRUE(fs::exists(mountpoint + success_file_path));
+ ASSERT_TRUE(fs::remove(mountpoint + success_file_path));
+ unmap_dokan(mount, mountpoint.c_str());
+}
+
+TEST_F(DokanTests, test_delete_on_close) {
+ std::string file_path = DEFAULT_MOUNTPOINT"file_" + get_uuid();
+ HANDLE hFile = CreateFile(
+ file_path.c_str(),
+ GENERIC_WRITE, // open for writing
+ 0, // sharing mode, none in this case
+ 0, // use default security descriptor
+ CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
+ 0);
+
+ ASSERT_NE(hFile, INVALID_HANDLE_VALUE)
+ << "Could not open file: "
+ << DEFAULT_MOUNTPOINT"test_create.txt "
+ << "err: " << GetLastError() << std::endl;
+
+ ASSERT_NE(CloseHandle(hFile), 0);
+
+ // FILE_FLAG_DELETE_ON_CLOSE is used
+ ASSERT_FALSE(fs::exists(file_path));
+}
+
+TEST_F(DokanTests, test_io) {
+ std::string data = "abcdef";
+ std::string file_path = "test_io_" + get_uuid();
+
+ std::string mountpoint = "I:\\";
+ SubProcess* mount = nullptr;
+ map_dokan(&mount, mountpoint.c_str());
+
+ check_write_file(mountpoint + file_path, data);
+ ASSERT_TRUE(fs::exists(mountpoint + file_path));
+ unmap_dokan(mount, mountpoint.c_str());
+
+ mountpoint = "O:\\";
+ mount = nullptr;
+ map_dokan(&mount, mountpoint.c_str());
+
+ ASSERT_TRUE(fs::exists(mountpoint + file_path));
+ EXPECT_EQ(data, read_file(mountpoint + file_path));
+ ASSERT_TRUE(fs::remove((mountpoint + file_path).c_str()));
+ ASSERT_FALSE(fs::exists(mountpoint + file_path));
+ unmap_dokan(mount, mountpoint.c_str());
+}
+
+TEST_F(DokanTests, test_subfolders) {
+ std::string base_dir_path = DEFAULT_MOUNTPOINT"base_dir_"
+ + get_uuid() + "\\";
+ std::string sub_dir_path = base_dir_path
+ + "test_sub_dir" + get_uuid();
+ std::string base_dir_file = base_dir_path
+ + "file_" + get_uuid();
+ std::string sub_dir_file = sub_dir_path
+ + "file_" + get_uuid();
+
+ std::string data = "abc";
+
+ ASSERT_EQ(fs::create_directory(base_dir_path), true);
+ ASSERT_TRUE(fs::exists(base_dir_path));
+
+ ASSERT_EQ(fs::create_directory(sub_dir_path), true);
+ ASSERT_TRUE(fs::exists(sub_dir_path));
+
+ check_write_file(base_dir_file, data);
+ ASSERT_TRUE(fs::exists(base_dir_file));
+
+ check_write_file(sub_dir_file, data);
+ ASSERT_TRUE(fs::exists(sub_dir_file));
+
+ ASSERT_TRUE(fs::remove((sub_dir_file).c_str()))
+ << "Failed to remove file: " << sub_dir_file;
+ ASSERT_FALSE(fs::exists(sub_dir_file));
+
+ // Remove empty dir
+ ASSERT_TRUE(fs::remove((sub_dir_path).c_str()))
+ << "Failed to remove directory: " << sub_dir_path;
+ ASSERT_FALSE(fs::exists(sub_dir_file));
+
+ ASSERT_NE(fs::remove_all((base_dir_path).c_str()), 0)
+ << "Failed to remove directory: " << base_dir_path;
+ ASSERT_FALSE(fs::exists(sub_dir_file));
+}
+
+TEST_F(DokanTests, test_find_files) {
+ std::string basedir_path = "X:/find_" + get_uuid();
+ std::string subdir_path = basedir_path + "/dir_" + get_uuid();
+ std::string file1_path = basedir_path + "/file1_" + get_uuid();
+ std::string file2_path = subdir_path + "/file2_" + get_uuid();
+
+ ASSERT_TRUE(
+ fs::create_directories(subdir_path)
+ );
+
+ std::ofstream{file1_path};
+ std::ofstream{file2_path};
+
+ std::vector<std::string> paths;
+
+ for (const auto & entry :
+ fs::recursive_directory_iterator(basedir_path)
+ ) {
+ paths.push_back(entry.path().generic_string());
+ }
+
+ ASSERT_NE(std::find(begin(paths), end(paths), subdir_path), end(paths));
+ ASSERT_NE(std::find(begin(paths), end(paths), file1_path), end(paths));
+ ASSERT_NE(std::find(begin(paths), end(paths), file2_path), end(paths));
+
+ // clean-up
+ ASSERT_NE(fs::remove_all(basedir_path), 0);
+}
+
+TEST_F(DokanTests, test_move_file) {
+ std::string dir1_path = DEFAULT_MOUNTPOINT
+ "test_mv_1_" + get_uuid() + "\\";
+ std::string dir2_path = DEFAULT_MOUNTPOINT
+ "test_mv_2_" + get_uuid() + "\\";
+ std::string file_name = "mv_file_" + get_uuid();
+ std::string data = "abcd";
+
+ ASSERT_TRUE(fs::create_directory(dir1_path));
+ ASSERT_TRUE(fs::create_directory(dir2_path));
+
+ check_write_file(dir1_path + file_name, data);
+
+ fs::rename(dir1_path + file_name, dir2_path + file_name);
+
+ ASSERT_TRUE(fs::exists(dir2_path + file_name));
+ ASSERT_FALSE(fs::exists(dir1_path + file_name));
+
+ ASSERT_EQ(data, read_file(dir2_path + file_name));
+
+ // clean-up
+ ASSERT_NE(fs::remove_all(dir1_path),0);
+ ASSERT_NE(fs::remove_all(dir2_path),0);
+}
+
+TEST_F(DokanTests, test_max_path) {
+ std::string mountpoint = "P:\\";
+ std::string extended_mountpoint = "\\\\?\\" + mountpoint;
+ SubProcess* mount = nullptr;
+ char dir[200] = { 0 };
+ char file[200] = { 0 };
+ std::string data = "abcd1234";
+
+ memset(dir, 'd', sizeof(dir) - 1);
+ memset(file, 'f', sizeof(file) - 1);
+
+ uint64_t max_path_len = 4096;
+
+ map_dokan_with_maxpath(&mount,
+ mountpoint.c_str(),
+ max_path_len);
+ EXPECT_EQ(get_volume_max_path(extended_mountpoint),
+ max_path_len);
+
+ std::string long_dir_path = extended_mountpoint;
+
+ std::string dir_names[15];
+
+ for (int i = 0; i < 15; i++) {
+ std::string crt_dir = std::string(dir) + "_"
+ + get_uuid() + "\\";
+ long_dir_path.append(crt_dir);
+ int stat = _mkdir(long_dir_path.c_str());
+ ASSERT_EQ(stat, 0) << "Error creating directory " << i
+ << ": " << GetLastError() << std::endl;
+ dir_names[i] = crt_dir;
+ }
+ std::string file_path = long_dir_path + "\\" + std::string(file)
+ + "_" + get_uuid();
+
+ check_write_file(file_path, data);
+
+ // clean-up
+ // fs::remove is unable to handle long Windows paths
+ EXPECT_NE(DeleteFileA(file_path.c_str()), 0);
+
+ for (int i = 14; i >= 0; i--) {
+ std::string remove_dir = extended_mountpoint;
+ for (int j = 0; j <= i; j++) {
+ remove_dir.append(dir_names[j]);
+ }
+
+ EXPECT_NE(RemoveDirectoryA(remove_dir.c_str()), 0);
+ }
+
+ unmap_dokan(mount, mountpoint.c_str());
+
+ // value exceeds 32767, so a failure is expected
+ max_path_len = 32770;
+ map_dokan_with_maxpath(&mount,
+ mountpoint.c_str(),
+ max_path_len);
+ ASSERT_FALSE(fs::exists(mountpoint));
+
+ // value is below 256, so a failure is expected
+ max_path_len = 150;
+ map_dokan_with_maxpath(&mount,
+ mountpoint.c_str(),
+ max_path_len);
+ ASSERT_FALSE(fs::exists(mountpoint));
+
+ // default value
+ map_dokan(&mount, mountpoint.c_str());
+ EXPECT_EQ(get_volume_max_path(mountpoint.c_str()), 256);
+
+ unmap_dokan(mount, mountpoint.c_str());
+}
+
+TEST_F(DokanTests, test_set_eof) {
+ std::string file_path = DEFAULT_MOUNTPOINT"test_eof_"
+ + get_uuid();
+ HANDLE hFile = CreateFile(
+ file_path.c_str(),
+ GENERIC_WRITE, // open for writing
+ 0, // sharing mode, none in this case
+ 0, // use default security descriptor
+ CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
+ 0);
+
+ ASSERT_NE(hFile, INVALID_HANDLE_VALUE)
+ << "Could not open file: "
+ << DEFAULT_MOUNTPOINT"test_create.txt "
+ << "err: " << GetLastError() << std::endl;
+
+ LARGE_INTEGER offset;
+ offset.QuadPart = 2 * MByte; // 2MB
+
+ LARGE_INTEGER file_size;
+
+ ASSERT_TRUE(move_eof(hFile, offset));
+ ASSERT_NE(GetFileSizeEx(hFile, &file_size), 0);
+ EXPECT_EQ(file_size.QuadPart, offset.QuadPart);
+
+ offset.QuadPart = MByte; // 1MB
+
+ ASSERT_TRUE(move_eof(hFile, offset));
+ ASSERT_NE(GetFileSizeEx(hFile, &file_size), 0);
+ EXPECT_EQ(file_size.QuadPart, offset.QuadPart);
+
+ ASSERT_NE(CloseHandle(hFile), 0);
+
+ // FILE_FLAG_DELETE_ON_CLOSE is used
+ ASSERT_FALSE(fs::exists(file_path));
+}
+
+TEST_F(DokanTests, test_set_alloc_size) {
+ std::string file_path = DEFAULT_MOUNTPOINT"test_alloc_size_"
+ + get_uuid();
+ HANDLE hFile = CreateFile(
+ file_path.c_str(),
+ GENERIC_WRITE, // open for writing
+ 0, // sharing mode, none in this case
+ 0, // use default security descriptor
+ CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
+ 0);
+
+ ASSERT_NE(hFile, INVALID_HANDLE_VALUE)
+ << "Could not open file: "
+ << DEFAULT_MOUNTPOINT"test_create.txt "
+ << "err: " << GetLastError() << std::endl;
+
+ LARGE_INTEGER li;
+ li.QuadPart = MByte;
+ FILE_ALLOCATION_INFO fai;
+ fai.AllocationSize = li;
+
+ ASSERT_NE(SetFileInformationByHandle(
+ hFile,
+ FileAllocationInfo,
+ &fai,
+ sizeof(FILE_ALLOCATION_INFO)
+ ),0) << "Error: " << GetLastError();
+
+ LARGE_INTEGER offset;
+ offset.QuadPart = 2 * MByte;
+ LARGE_INTEGER file_size;
+
+ ASSERT_TRUE(move_eof(hFile, offset));
+ ASSERT_NE(GetFileSizeEx(hFile, &file_size), 0);
+ EXPECT_EQ(file_size.QuadPart, offset.QuadPart);
+
+ offset.QuadPart = MByte;
+
+ ASSERT_TRUE(move_eof(hFile, offset));
+ ASSERT_NE(GetFileSizeEx(hFile, &file_size), 0);
+ EXPECT_EQ(file_size.QuadPart, offset.QuadPart);
+
+ ASSERT_NE(CloseHandle(hFile), 0);
+
+ // FILE_FLAG_DELETE_ON_CLOSE is used
+ ASSERT_FALSE(fs::exists(file_path));
+}
+
+TEST_F(DokanTests, test_file_type) {
+ std::string test_dir = DEFAULT_MOUNTPOINT"test_info_"
+ + get_uuid() + "\\";
+ std::string file_path = test_dir + "file_"
+ + get_uuid();
+ std::string dir_path = test_dir + "dir_"
+ + get_uuid() + "\\";
+
+ ASSERT_TRUE(fs::create_directory(test_dir));
+
+ std::ofstream{file_path};
+ ASSERT_TRUE(fs::create_directory(dir_path));
+
+ ASSERT_TRUE(fs::is_regular_file(fs::status(file_path)));
+ ASSERT_TRUE(fs::is_directory(fs::status(dir_path)));
+
+ // clean-up
+ fs::remove_all(test_dir);
+
+}
+
+TEST_F(DokanTests, test_volume_info) {
+ char volume_name[MAX_PATH + 1] = { 0 };
+ char file_system_name[MAX_PATH + 1] = { 0 };
+ DWORD serial_number = 0;
+ DWORD max_component_len = 0;
+ DWORD file_system_flags = 0;
+
+ ASSERT_EQ(
+ GetVolumeInformation(
+ DEFAULT_MOUNTPOINT,
+ volume_name,
+ sizeof(volume_name),
+ &serial_number,
+ &max_component_len,
+ &file_system_flags,
+ file_system_name,
+ sizeof(file_system_name)),TRUE)
+ << "GetVolumeInformation() failed, error: "
+ << GetLastError() << std::endl;
+
+ ASSERT_STREQ(volume_name, "TestCeph")
+ << "Received: " << volume_name << std::endl;
+ ASSERT_STREQ(file_system_name, "Ceph")
+ << "Received: " << file_system_name << std::endl;
+ ASSERT_EQ(max_component_len, 256);
+ ASSERT_EQ(serial_number, std::stoi(TEST_VOL_SERIAL))
+ << "Received: " << serial_number << std::endl;
+
+ // Consider adding specific flags
+ // and check for them
+ // ASSERT_EQ(file_system_flags, 271);
+}
+
+TEST_F(DokanTests, test_get_free_space) {
+ std::error_code ec;
+ const std::filesystem::space_info si =
+ std::filesystem::space(DEFAULT_MOUNTPOINT, ec);
+ ASSERT_EQ(ec.value(), 0);
+
+ ASSERT_NE(static_cast<std::intmax_t>(si.capacity), 0);
+ ASSERT_NE(static_cast<std::intmax_t>(si.free), 0);
+ ASSERT_NE(static_cast<std::intmax_t>(si.available), 0);
+}
+
+TEST_F(DokanTests, test_file_timestamp) {
+ std::string file1 = DEFAULT_MOUNTPOINT"test_time1_"
+ + get_uuid();
+ std::string file2 = DEFAULT_MOUNTPOINT"test_time2_"
+ + get_uuid();
+ std::string file3 = DEFAULT_MOUNTPOINT"test_time3_"
+ + get_uuid();
+
+ std::ofstream{file1};
+ Sleep(1000);
+ std::ofstream{file2};
+ Sleep(1000);
+ std::ofstream{file3};
+
+ int64_t file1_creation = fs::last_write_time(file1)
+ .time_since_epoch().count();
+ int64_t file2_creation = fs::last_write_time(file2)
+ .time_since_epoch().count();
+ int64_t file3_creation = fs::last_write_time(file3)
+ .time_since_epoch().count();
+
+ EXPECT_LT(file1_creation, file2_creation);
+ EXPECT_LT(file2_creation, file3_creation);
+
+ // add 1h to file 1 creation time
+ fs::file_time_type file1_time = fs::last_write_time(file1);
+
+ fs::last_write_time(file1, file1_time + 1h);
+
+ int64_t file1_new_time = fs::last_write_time(file1)
+ .time_since_epoch().count();
+
+ EXPECT_EQ((file1_time + 1h).time_since_epoch().count(),
+ file1_new_time);
+ EXPECT_GT(file1_new_time, file2_creation);
+ EXPECT_GT(file1_new_time, file3_creation);
+
+ ASSERT_TRUE(fs::remove(file1));
+ ASSERT_TRUE(fs::remove(file2));
+ ASSERT_TRUE(fs::remove(file3));
+}
+
+TEST_F(DokanTests, test_delete_disposition) {
+ std::string file_path = DEFAULT_MOUNTPOINT"test_disp_"
+ + get_uuid();
+ HANDLE hFile = CreateFile(file_path.c_str(),
+ GENERIC_ALL, // required for delete
+ 0, // exclusive access
+ NULL,
+ CREATE_ALWAYS,
+ 0,
+ NULL);
+
+ ASSERT_NE(hFile, INVALID_HANDLE_VALUE)
+ << "Could not open file: " << file_path
+ << "err: " << GetLastError() << std::endl;
+
+ FILE_DISPOSITION_INFO fdi;
+ fdi.DeleteFile = TRUE; // marking for deletion
+
+ ASSERT_NE(
+ SetFileInformationByHandle(
+ hFile,
+ FileDispositionInfo,
+ &fdi,
+ sizeof(FILE_DISPOSITION_INFO)), 0);
+
+ ASSERT_NE(CloseHandle(hFile), 0);
+ ASSERT_FALSE(fs::exists(file_path));
+}
+
+bool check_create_disposition(std::string path, DWORD disposition) {
+ HANDLE hFile = CreateFile(path.c_str(),
+ GENERIC_WRITE,
+ 0, // exclusive access
+ NULL,
+ disposition,
+ 0,
+ NULL);
+
+ if(hFile == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ if(CloseHandle(hFile) == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+TEST_F(DokanTests, test_create_dispositions) {
+ std::string file_path = DEFAULT_MOUNTPOINT"test_create_"
+ + get_uuid();
+ std::string non_existant_file = DEFAULT_MOUNTPOINT
+ "test_create_" + get_uuid();
+
+ EXPECT_TRUE(
+ check_create_disposition(file_path, CREATE_NEW));
+
+ // CREATE_ALWAYS with existing file
+ EXPECT_TRUE(
+ check_create_disposition(file_path, CREATE_ALWAYS));
+ EXPECT_EQ(GetLastError(), ERROR_ALREADY_EXISTS);
+
+ // CREATE_NEW with existing file
+ EXPECT_FALSE(
+ check_create_disposition(file_path, CREATE_NEW));
+ EXPECT_EQ(GetLastError(), ERROR_FILE_EXISTS);
+
+ // OPEN_EXISTING with existing file
+ EXPECT_TRUE(
+ check_create_disposition(file_path, OPEN_EXISTING));
+
+ ASSERT_FALSE(fs::exists(non_existant_file));
+ // OPEN_EXISTING with non-existant file
+ EXPECT_FALSE(
+ check_create_disposition(non_existant_file, OPEN_EXISTING));
+ EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND);
+
+ // OPEN_ALWAYS with existing file
+ EXPECT_TRUE(
+ check_create_disposition(file_path, OPEN_ALWAYS));
+ EXPECT_EQ(GetLastError(), ERROR_ALREADY_EXISTS);
+
+ ASSERT_FALSE(fs::exists(non_existant_file));
+ // OPEN_ALWAYS with non-existant file
+ EXPECT_TRUE(
+ check_create_disposition(non_existant_file, OPEN_ALWAYS));
+ EXPECT_EQ(GetLastError(), 0);
+
+ ASSERT_TRUE(fs::remove(non_existant_file));
+
+ // TRUNCATE_EXISTING with existing file
+ EXPECT_TRUE(
+ check_create_disposition(file_path, TRUNCATE_EXISTING));
+
+ // TRUNCATE_EXISTING with non-existant file
+ EXPECT_FALSE(
+ check_create_disposition(non_existant_file,
+ TRUNCATE_EXISTING));
+ EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND);
+
+ // clean-up
+ ASSERT_TRUE(fs::remove(file_path));
+}