]> git.proxmox.com Git - ceph.git/blob - ceph/src/test/dokan/dokan.cc
1a1d39580e6dd0b195d94700a78fc492e0fe6696
[ceph.git] / ceph / src / test / dokan / dokan.cc
1 /*
2 * Ceph - scalable distributed file system
3 *
4 * Copyright (C) 2022 Cloudbase Solutions
5 *
6 * This is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License version 2.1, as published by the Free Software
9 * Foundation. See file COPYING.
10 *
11 */
12
13 #include <windows.h>
14 #include <iostream>
15 #include <fstream>
16 #include <filesystem>
17 #include <sys/socket.h>
18 #include <direct.h>
19
20 #include "gtest/gtest.h"
21
22 #include "common/SubProcess.h"
23 #include "common/run_cmd.h"
24 #include "include/uuid.h"
25
26 #define DEFAULT_MOUNTPOINT "X:\\"
27 #define MOUNT_POLL_ATTEMPT 10
28 #define MOUNT_POLL_INTERVAL_MS 1000
29 #define TEST_VOL_SERIAL "1234567890"
30 #define MByte 1048576
31
32 namespace fs = std::filesystem;
33 using namespace std::chrono_literals;
34
35 std::string get_uuid() {
36 uuid_d suffix;
37 suffix.generate_random();
38
39 return suffix.to_string();
40 }
41
42 bool move_eof(HANDLE handle, LARGE_INTEGER offset) {
43
44 // Move file pointer to FILE_BEGIN + offset
45 if (!SetFilePointerEx(handle, offset, NULL, FILE_BEGIN)) {
46 std::cerr << "Setting file pointer failed. err: "
47 << GetLastError() << std::endl;
48 return false;
49 }
50
51 if (!SetEndOfFile(handle)) {
52 std::cerr << "Setting EOF failed. err: " << GetLastError() << std::endl;
53 return false;
54 }
55
56 return true;
57 }
58
59 void write_file(std::string file_path, std::string data) {
60 std::ofstream file;
61 file.open(file_path);
62
63 ASSERT_TRUE(file.is_open())
64 << "Failed to open file: " << file_path;
65 file << data;
66 file.flush();
67
68 file.close();
69 }
70
71 void expect_write_failure(std::string file_path) {
72 std::ofstream file;
73 file.open(file_path);
74
75 ASSERT_FALSE(file.is_open());
76 }
77
78 std::string read_file(std::string file_path) {
79 std::ifstream file;
80 file.open(file_path);
81 std::string content((std::istreambuf_iterator<char>(file)),
82 std::istreambuf_iterator<char>());
83 file.close();
84
85 return content;
86 }
87
88 void check_write_file(std::string file_path, std::string data) {
89 write_file(file_path, data);
90 ASSERT_EQ(read_file(file_path), data);
91 }
92
93 int wait_for_mount(std::string mount_path) {
94 std::cerr << "Waiting for mount: " << mount_path << std::endl;
95
96 int attempts = 0;
97 do {
98 attempts++;
99 if (attempts < MOUNT_POLL_ATTEMPT)
100 Sleep(MOUNT_POLL_INTERVAL_MS);
101 } while (!fs::exists(mount_path)
102 && attempts < MOUNT_POLL_ATTEMPT);
103
104 if (!fs::exists(mount_path)) {
105 std::cerr << "Timed out waiting for ceph-dokan mount: "
106 << mount_path << std::endl;
107 return -ETIMEDOUT;
108 }
109
110 std::cerr << "Successfully mounted: " << mount_path << std::endl;
111
112 return 0;
113 }
114
115 void map_dokan(SubProcess** mount, const char* mountpoint) {
116 SubProcess* new_mount = new SubProcess("ceph-dokan");
117
118 new_mount->add_cmd_args("map", "--win-vol-name", "TestCeph",
119 "--win-vol-serial", TEST_VOL_SERIAL,
120 "-l", mountpoint, NULL);
121
122 *mount = new_mount;
123 ASSERT_EQ(new_mount->spawn(), 0);
124 ASSERT_EQ(wait_for_mount(mountpoint), 0);
125 }
126
127 void map_dokan_read_only(
128 SubProcess** mount,
129 const char* mountpoint
130 ) {
131 SubProcess* new_mount = new SubProcess("ceph-dokan");
132 new_mount->add_cmd_args("map", "--win-vol-name", "TestCeph",
133 "--win-vol-serial", TEST_VOL_SERIAL,
134 "--read-only", "-l", mountpoint, NULL);
135
136 *mount = new_mount;
137 ASSERT_EQ(new_mount->spawn(), 0);
138 ASSERT_EQ(wait_for_mount(mountpoint), 0);
139 std::cerr << mountpoint << " mounted in read-only mode"
140 << std::endl;
141 }
142
143 void map_dokan_with_maxpath(
144 SubProcess** mount,
145 const char* mountpoint,
146 uint64_t max_path_len)
147 {
148 SubProcess* new_mount = new SubProcess("ceph-dokan");
149 new_mount->add_cmd_args("map", "--debug", "--dokan-stderr",
150 "--win-vol-name", "TestCeph",
151 "--win-vol-serial", TEST_VOL_SERIAL,
152 "--max-path-len",
153 (std::to_string(max_path_len)).c_str(),
154 "-l", mountpoint, NULL);
155
156 *mount = new_mount;
157 ASSERT_EQ(new_mount->spawn(), 0);
158 if (256 <= max_path_len && max_path_len <= 4096) {
159 ASSERT_EQ(wait_for_mount(mountpoint), 0);
160 } else {
161 ASSERT_NE(wait_for_mount(mountpoint), 0);
162 }
163 }
164
165 void unmap_dokan(SubProcess* mount, const char* mountpoint) {
166 std::string ret = run_cmd("ceph-dokan", "unmap", "-l",
167 mountpoint, (char*)NULL);
168
169 ASSERT_EQ(ret, "") << "Failed unmapping: " << mountpoint;
170 std::cerr<< "Unmounted: " << mountpoint << std::endl;
171
172 ASSERT_EQ(mount->join(), 0);
173 }
174
175 int get_volume_max_path(std::string mountpoint){
176 char volume_name[MAX_PATH + 1] = { 0 };
177 char file_system_name[MAX_PATH + 1] = { 0 };
178 DWORD serial_number = 0;
179 DWORD max_component_len = 0;
180 DWORD file_system_flags = 0;
181 if (GetVolumeInformation(
182 mountpoint.c_str(),
183 volume_name,
184 sizeof(volume_name),
185 &serial_number,
186 &max_component_len,
187 &file_system_flags,
188 file_system_name,
189 sizeof(file_system_name)) != TRUE) {
190 std::cerr << "GetVolumeInformation() failed, error: "
191 << GetLastError() << std::endl;
192 }
193
194 return max_component_len;
195 }
196
197 static SubProcess* shared_mount = nullptr;
198
199 class DokanTests : public testing::Test
200 {
201 protected:
202
203 static void SetUpTestSuite() {
204 map_dokan(&shared_mount, DEFAULT_MOUNTPOINT);
205 }
206
207 static void TearDownTestSuite() {
208 if (shared_mount) {
209 unmap_dokan(shared_mount, DEFAULT_MOUNTPOINT);
210 }
211 shared_mount = nullptr;
212 }
213 };
214
215 TEST_F(DokanTests, test_mount) {
216 std::string mountpoint = "Y:\\";
217 SubProcess* mount = nullptr;
218 map_dokan(&mount, mountpoint.c_str());
219 unmap_dokan(mount, mountpoint.c_str());
220 }
221
222 TEST_F(DokanTests, test_mount_read_only) {
223 std::string mountpoint = "Z:\\";
224 std::string data = "abc123";
225 std::string success_file_path = "ro_success_" + get_uuid();
226 std::string failed_file_path = "ro_fail_" + get_uuid();
227
228 SubProcess* mount = nullptr;
229 map_dokan(&mount, mountpoint.c_str());
230
231 check_write_file(mountpoint + success_file_path, data);
232 ASSERT_TRUE(fs::exists(mountpoint + success_file_path));
233
234 unmap_dokan(mount, mountpoint.c_str());
235
236 mount = nullptr;
237 map_dokan_read_only(&mount, mountpoint.c_str());
238
239 expect_write_failure(mountpoint + failed_file_path);
240 ASSERT_FALSE(fs::exists(mountpoint + failed_file_path));
241
242 ASSERT_TRUE(fs::exists(mountpoint + success_file_path));
243 ASSERT_EQ(read_file(mountpoint + success_file_path), data);
244
245 std::string exception_msg(
246 "filesystem error: cannot remove: No such device ["
247 + mountpoint + success_file_path + "]");
248 EXPECT_THROW({
249 try {
250 fs::remove(mountpoint + success_file_path);
251 } catch(const fs::filesystem_error &e) {
252 EXPECT_STREQ(e.what(), exception_msg.c_str());
253 throw;
254 }
255 }, fs::filesystem_error);
256 unmap_dokan(mount, mountpoint.c_str());
257
258 map_dokan(&mount, mountpoint.c_str());
259 ASSERT_TRUE(fs::exists(mountpoint + success_file_path));
260 ASSERT_TRUE(fs::remove(mountpoint + success_file_path));
261 unmap_dokan(mount, mountpoint.c_str());
262 }
263
264 TEST_F(DokanTests, test_delete_on_close) {
265 std::string file_path = DEFAULT_MOUNTPOINT"file_" + get_uuid();
266 HANDLE hFile = CreateFile(
267 file_path.c_str(),
268 GENERIC_WRITE, // open for writing
269 0, // sharing mode, none in this case
270 0, // use default security descriptor
271 CREATE_NEW,
272 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
273 0);
274
275 ASSERT_NE(hFile, INVALID_HANDLE_VALUE)
276 << "Could not open file: "
277 << DEFAULT_MOUNTPOINT"test_create.txt "
278 << "err: " << GetLastError() << std::endl;
279
280 ASSERT_NE(CloseHandle(hFile), 0);
281
282 // FILE_FLAG_DELETE_ON_CLOSE is used
283 ASSERT_FALSE(fs::exists(file_path));
284 }
285
286 TEST_F(DokanTests, test_io) {
287 std::string data = "abcdef";
288 std::string file_path = "test_io_" + get_uuid();
289
290 std::string mountpoint = "I:\\";
291 SubProcess* mount = nullptr;
292 map_dokan(&mount, mountpoint.c_str());
293
294 check_write_file(mountpoint + file_path, data);
295 ASSERT_TRUE(fs::exists(mountpoint + file_path));
296 unmap_dokan(mount, mountpoint.c_str());
297
298 mountpoint = "O:\\";
299 mount = nullptr;
300 map_dokan(&mount, mountpoint.c_str());
301
302 ASSERT_TRUE(fs::exists(mountpoint + file_path));
303 EXPECT_EQ(data, read_file(mountpoint + file_path));
304 ASSERT_TRUE(fs::remove((mountpoint + file_path).c_str()));
305 ASSERT_FALSE(fs::exists(mountpoint + file_path));
306 unmap_dokan(mount, mountpoint.c_str());
307 }
308
309 TEST_F(DokanTests, test_subfolders) {
310 std::string base_dir_path = DEFAULT_MOUNTPOINT"base_dir_"
311 + get_uuid() + "\\";
312 std::string sub_dir_path = base_dir_path
313 + "test_sub_dir" + get_uuid();
314 std::string base_dir_file = base_dir_path
315 + "file_" + get_uuid();
316 std::string sub_dir_file = sub_dir_path
317 + "file_" + get_uuid();
318
319 std::string data = "abc";
320
321 ASSERT_EQ(fs::create_directory(base_dir_path), true);
322 ASSERT_TRUE(fs::exists(base_dir_path));
323
324 ASSERT_EQ(fs::create_directory(sub_dir_path), true);
325 ASSERT_TRUE(fs::exists(sub_dir_path));
326
327 check_write_file(base_dir_file, data);
328 ASSERT_TRUE(fs::exists(base_dir_file));
329
330 check_write_file(sub_dir_file, data);
331 ASSERT_TRUE(fs::exists(sub_dir_file));
332
333 ASSERT_TRUE(fs::remove((sub_dir_file).c_str()))
334 << "Failed to remove file: " << sub_dir_file;
335 ASSERT_FALSE(fs::exists(sub_dir_file));
336
337 // Remove empty dir
338 ASSERT_TRUE(fs::remove((sub_dir_path).c_str()))
339 << "Failed to remove directory: " << sub_dir_path;
340 ASSERT_FALSE(fs::exists(sub_dir_file));
341
342 ASSERT_NE(fs::remove_all((base_dir_path).c_str()), 0)
343 << "Failed to remove directory: " << base_dir_path;
344 ASSERT_FALSE(fs::exists(sub_dir_file));
345 }
346
347 TEST_F(DokanTests, test_find_files) {
348 std::string basedir_path = "X:/find_" + get_uuid();
349 std::string subdir_path = basedir_path + "/dir_" + get_uuid();
350 std::string file1_path = basedir_path + "/file1_" + get_uuid();
351 std::string file2_path = subdir_path + "/file2_" + get_uuid();
352
353 ASSERT_TRUE(
354 fs::create_directories(subdir_path)
355 );
356
357 std::ofstream{file1_path};
358 std::ofstream{file2_path};
359
360 std::vector<std::string> paths;
361
362 for (const auto & entry :
363 fs::recursive_directory_iterator(basedir_path)
364 ) {
365 paths.push_back(entry.path().generic_string());
366 }
367
368 ASSERT_NE(std::find(begin(paths), end(paths), subdir_path), end(paths));
369 ASSERT_NE(std::find(begin(paths), end(paths), file1_path), end(paths));
370 ASSERT_NE(std::find(begin(paths), end(paths), file2_path), end(paths));
371
372 // clean-up
373 ASSERT_NE(fs::remove_all(basedir_path), 0);
374 }
375
376 TEST_F(DokanTests, test_move_file) {
377 std::string dir1_path = DEFAULT_MOUNTPOINT
378 "test_mv_1_" + get_uuid() + "\\";
379 std::string dir2_path = DEFAULT_MOUNTPOINT
380 "test_mv_2_" + get_uuid() + "\\";
381 std::string file_name = "mv_file_" + get_uuid();
382 std::string data = "abcd";
383
384 ASSERT_TRUE(fs::create_directory(dir1_path));
385 ASSERT_TRUE(fs::create_directory(dir2_path));
386
387 check_write_file(dir1_path + file_name, data);
388
389 fs::rename(dir1_path + file_name, dir2_path + file_name);
390
391 ASSERT_TRUE(fs::exists(dir2_path + file_name));
392 ASSERT_FALSE(fs::exists(dir1_path + file_name));
393
394 ASSERT_EQ(data, read_file(dir2_path + file_name));
395
396 // clean-up
397 ASSERT_NE(fs::remove_all(dir1_path),0);
398 ASSERT_NE(fs::remove_all(dir2_path),0);
399 }
400
401 TEST_F(DokanTests, test_max_path) {
402 std::string mountpoint = "P:\\";
403 std::string extended_mountpoint = "\\\\?\\" + mountpoint;
404 SubProcess* mount = nullptr;
405 char dir[200] = { 0 };
406 char file[200] = { 0 };
407 std::string data = "abcd1234";
408
409 memset(dir, 'd', sizeof(dir) - 1);
410 memset(file, 'f', sizeof(file) - 1);
411
412 uint64_t max_path_len = 4096;
413
414 map_dokan_with_maxpath(&mount,
415 mountpoint.c_str(),
416 max_path_len);
417 EXPECT_EQ(get_volume_max_path(extended_mountpoint),
418 max_path_len);
419
420 std::string long_dir_path = extended_mountpoint;
421
422 std::string dir_names[15];
423
424 for (int i = 0; i < 15; i++) {
425 std::string crt_dir = std::string(dir) + "_"
426 + get_uuid() + "\\";
427 long_dir_path.append(crt_dir);
428 int stat = _mkdir(long_dir_path.c_str());
429 ASSERT_EQ(stat, 0) << "Error creating directory " << i
430 << ": " << GetLastError() << std::endl;
431 dir_names[i] = crt_dir;
432 }
433 std::string file_path = long_dir_path + "\\" + std::string(file)
434 + "_" + get_uuid();
435
436 check_write_file(file_path, data);
437
438 // clean-up
439 // fs::remove is unable to handle long Windows paths
440 EXPECT_NE(DeleteFileA(file_path.c_str()), 0);
441
442 for (int i = 14; i >= 0; i--) {
443 std::string remove_dir = extended_mountpoint;
444 for (int j = 0; j <= i; j++) {
445 remove_dir.append(dir_names[j]);
446 }
447
448 EXPECT_NE(RemoveDirectoryA(remove_dir.c_str()), 0);
449 }
450
451 unmap_dokan(mount, mountpoint.c_str());
452
453 // value exceeds 32767, so a failure is expected
454 max_path_len = 32770;
455 map_dokan_with_maxpath(&mount,
456 mountpoint.c_str(),
457 max_path_len);
458 ASSERT_FALSE(fs::exists(mountpoint));
459
460 // value is below 256, so a failure is expected
461 max_path_len = 150;
462 map_dokan_with_maxpath(&mount,
463 mountpoint.c_str(),
464 max_path_len);
465 ASSERT_FALSE(fs::exists(mountpoint));
466
467 // default value
468 map_dokan(&mount, mountpoint.c_str());
469 EXPECT_EQ(get_volume_max_path(mountpoint.c_str()), 256);
470
471 unmap_dokan(mount, mountpoint.c_str());
472 }
473
474 TEST_F(DokanTests, test_set_eof) {
475 std::string file_path = DEFAULT_MOUNTPOINT"test_eof_"
476 + get_uuid();
477 HANDLE hFile = CreateFile(
478 file_path.c_str(),
479 GENERIC_WRITE, // open for writing
480 0, // sharing mode, none in this case
481 0, // use default security descriptor
482 CREATE_NEW,
483 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
484 0);
485
486 ASSERT_NE(hFile, INVALID_HANDLE_VALUE)
487 << "Could not open file: "
488 << DEFAULT_MOUNTPOINT"test_create.txt "
489 << "err: " << GetLastError() << std::endl;
490
491 LARGE_INTEGER offset;
492 offset.QuadPart = 2 * MByte; // 2MB
493
494 LARGE_INTEGER file_size;
495
496 ASSERT_TRUE(move_eof(hFile, offset));
497 ASSERT_NE(GetFileSizeEx(hFile, &file_size), 0);
498 EXPECT_EQ(file_size.QuadPart, offset.QuadPart);
499
500 offset.QuadPart = MByte; // 1MB
501
502 ASSERT_TRUE(move_eof(hFile, offset));
503 ASSERT_NE(GetFileSizeEx(hFile, &file_size), 0);
504 EXPECT_EQ(file_size.QuadPart, offset.QuadPart);
505
506 ASSERT_NE(CloseHandle(hFile), 0);
507
508 // FILE_FLAG_DELETE_ON_CLOSE is used
509 ASSERT_FALSE(fs::exists(file_path));
510 }
511
512 TEST_F(DokanTests, test_set_alloc_size) {
513 std::string file_path = DEFAULT_MOUNTPOINT"test_alloc_size_"
514 + get_uuid();
515 HANDLE hFile = CreateFile(
516 file_path.c_str(),
517 GENERIC_WRITE, // open for writing
518 0, // sharing mode, none in this case
519 0, // use default security descriptor
520 CREATE_NEW,
521 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
522 0);
523
524 ASSERT_NE(hFile, INVALID_HANDLE_VALUE)
525 << "Could not open file: "
526 << DEFAULT_MOUNTPOINT"test_create.txt "
527 << "err: " << GetLastError() << std::endl;
528
529 LARGE_INTEGER li;
530 li.QuadPart = MByte;
531 FILE_ALLOCATION_INFO fai;
532 fai.AllocationSize = li;
533
534 ASSERT_NE(SetFileInformationByHandle(
535 hFile,
536 FileAllocationInfo,
537 &fai,
538 sizeof(FILE_ALLOCATION_INFO)
539 ),0) << "Error: " << GetLastError();
540
541 LARGE_INTEGER offset;
542 offset.QuadPart = 2 * MByte;
543 LARGE_INTEGER file_size;
544
545 ASSERT_TRUE(move_eof(hFile, offset));
546 ASSERT_NE(GetFileSizeEx(hFile, &file_size), 0);
547 EXPECT_EQ(file_size.QuadPart, offset.QuadPart);
548
549 offset.QuadPart = MByte;
550
551 ASSERT_TRUE(move_eof(hFile, offset));
552 ASSERT_NE(GetFileSizeEx(hFile, &file_size), 0);
553 EXPECT_EQ(file_size.QuadPart, offset.QuadPart);
554
555 ASSERT_NE(CloseHandle(hFile), 0);
556
557 // FILE_FLAG_DELETE_ON_CLOSE is used
558 ASSERT_FALSE(fs::exists(file_path));
559 }
560
561 TEST_F(DokanTests, test_file_type) {
562 std::string test_dir = DEFAULT_MOUNTPOINT"test_info_"
563 + get_uuid() + "\\";
564 std::string file_path = test_dir + "file_"
565 + get_uuid();
566 std::string dir_path = test_dir + "dir_"
567 + get_uuid() + "\\";
568
569 ASSERT_TRUE(fs::create_directory(test_dir));
570
571 std::ofstream{file_path};
572 ASSERT_TRUE(fs::create_directory(dir_path));
573
574 ASSERT_TRUE(fs::is_regular_file(fs::status(file_path)));
575 ASSERT_TRUE(fs::is_directory(fs::status(dir_path)));
576
577 // clean-up
578 fs::remove_all(test_dir);
579
580 }
581
582 TEST_F(DokanTests, test_volume_info) {
583 char volume_name[MAX_PATH + 1] = { 0 };
584 char file_system_name[MAX_PATH + 1] = { 0 };
585 DWORD serial_number = 0;
586 DWORD max_component_len = 0;
587 DWORD file_system_flags = 0;
588
589 ASSERT_EQ(
590 GetVolumeInformation(
591 DEFAULT_MOUNTPOINT,
592 volume_name,
593 sizeof(volume_name),
594 &serial_number,
595 &max_component_len,
596 &file_system_flags,
597 file_system_name,
598 sizeof(file_system_name)),TRUE)
599 << "GetVolumeInformation() failed, error: "
600 << GetLastError() << std::endl;
601
602 ASSERT_STREQ(volume_name, "TestCeph")
603 << "Received: " << volume_name << std::endl;
604 ASSERT_STREQ(file_system_name, "Ceph")
605 << "Received: " << file_system_name << std::endl;
606 ASSERT_EQ(max_component_len, 256);
607 ASSERT_EQ(serial_number, std::stoi(TEST_VOL_SERIAL))
608 << "Received: " << serial_number << std::endl;
609
610 // Consider adding specific flags
611 // and check for them
612 // ASSERT_EQ(file_system_flags, 271);
613 }
614
615 TEST_F(DokanTests, test_get_free_space) {
616 std::error_code ec;
617 const std::filesystem::space_info si =
618 std::filesystem::space(DEFAULT_MOUNTPOINT, ec);
619 ASSERT_EQ(ec.value(), 0);
620
621 ASSERT_NE(static_cast<std::intmax_t>(si.capacity), 0);
622 ASSERT_NE(static_cast<std::intmax_t>(si.free), 0);
623 ASSERT_NE(static_cast<std::intmax_t>(si.available), 0);
624 }
625
626 TEST_F(DokanTests, test_file_timestamp) {
627 std::string file1 = DEFAULT_MOUNTPOINT"test_time1_"
628 + get_uuid();
629 std::string file2 = DEFAULT_MOUNTPOINT"test_time2_"
630 + get_uuid();
631 std::string file3 = DEFAULT_MOUNTPOINT"test_time3_"
632 + get_uuid();
633
634 std::ofstream{file1};
635 Sleep(1000);
636 std::ofstream{file2};
637 Sleep(1000);
638 std::ofstream{file3};
639
640 int64_t file1_creation = fs::last_write_time(file1)
641 .time_since_epoch().count();
642 int64_t file2_creation = fs::last_write_time(file2)
643 .time_since_epoch().count();
644 int64_t file3_creation = fs::last_write_time(file3)
645 .time_since_epoch().count();
646
647 EXPECT_LT(file1_creation, file2_creation);
648 EXPECT_LT(file2_creation, file3_creation);
649
650 // add 1h to file 1 creation time
651 fs::file_time_type file1_time = fs::last_write_time(file1);
652
653 fs::last_write_time(file1, file1_time + 1h);
654
655 int64_t file1_new_time = fs::last_write_time(file1)
656 .time_since_epoch().count();
657
658 EXPECT_EQ((file1_time + 1h).time_since_epoch().count(),
659 file1_new_time);
660 EXPECT_GT(file1_new_time, file2_creation);
661 EXPECT_GT(file1_new_time, file3_creation);
662
663 ASSERT_TRUE(fs::remove(file1));
664 ASSERT_TRUE(fs::remove(file2));
665 ASSERT_TRUE(fs::remove(file3));
666 }
667
668 TEST_F(DokanTests, test_delete_disposition) {
669 std::string file_path = DEFAULT_MOUNTPOINT"test_disp_"
670 + get_uuid();
671 HANDLE hFile = CreateFile(file_path.c_str(),
672 GENERIC_ALL, // required for delete
673 0, // exclusive access
674 NULL,
675 CREATE_ALWAYS,
676 0,
677 NULL);
678
679 ASSERT_NE(hFile, INVALID_HANDLE_VALUE)
680 << "Could not open file: " << file_path
681 << "err: " << GetLastError() << std::endl;
682
683 FILE_DISPOSITION_INFO fdi;
684 fdi.DeleteFile = TRUE; // marking for deletion
685
686 ASSERT_NE(
687 SetFileInformationByHandle(
688 hFile,
689 FileDispositionInfo,
690 &fdi,
691 sizeof(FILE_DISPOSITION_INFO)), 0);
692
693 ASSERT_NE(CloseHandle(hFile), 0);
694 ASSERT_FALSE(fs::exists(file_path));
695 }
696
697 bool check_create_disposition(std::string path, DWORD disposition) {
698 HANDLE hFile = CreateFile(path.c_str(),
699 GENERIC_WRITE,
700 0, // exclusive access
701 NULL,
702 disposition,
703 0,
704 NULL);
705
706 if(hFile == INVALID_HANDLE_VALUE) {
707 return false;
708 }
709
710 if(CloseHandle(hFile) == 0) {
711 return false;
712 }
713
714 return true;
715 }
716
717 TEST_F(DokanTests, test_create_dispositions) {
718 std::string file_path = DEFAULT_MOUNTPOINT"test_create_"
719 + get_uuid();
720 std::string non_existant_file = DEFAULT_MOUNTPOINT
721 "test_create_" + get_uuid();
722
723 EXPECT_TRUE(
724 check_create_disposition(file_path, CREATE_NEW));
725
726 // CREATE_ALWAYS with existing file
727 EXPECT_TRUE(
728 check_create_disposition(file_path, CREATE_ALWAYS));
729 EXPECT_EQ(GetLastError(), ERROR_ALREADY_EXISTS);
730
731 // CREATE_NEW with existing file
732 EXPECT_FALSE(
733 check_create_disposition(file_path, CREATE_NEW));
734 EXPECT_EQ(GetLastError(), ERROR_FILE_EXISTS);
735
736 // OPEN_EXISTING with existing file
737 EXPECT_TRUE(
738 check_create_disposition(file_path, OPEN_EXISTING));
739
740 ASSERT_FALSE(fs::exists(non_existant_file));
741 // OPEN_EXISTING with non-existant file
742 EXPECT_FALSE(
743 check_create_disposition(non_existant_file, OPEN_EXISTING));
744 EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND);
745
746 // OPEN_ALWAYS with existing file
747 EXPECT_TRUE(
748 check_create_disposition(file_path, OPEN_ALWAYS));
749 EXPECT_EQ(GetLastError(), ERROR_ALREADY_EXISTS);
750
751 ASSERT_FALSE(fs::exists(non_existant_file));
752 // OPEN_ALWAYS with non-existant file
753 EXPECT_TRUE(
754 check_create_disposition(non_existant_file, OPEN_ALWAYS));
755 EXPECT_EQ(GetLastError(), 0);
756
757 ASSERT_TRUE(fs::remove(non_existant_file));
758
759 // TRUNCATE_EXISTING with existing file
760 EXPECT_TRUE(
761 check_create_disposition(file_path, TRUNCATE_EXISTING));
762
763 // TRUNCATE_EXISTING with non-existant file
764 EXPECT_FALSE(
765 check_create_disposition(non_existant_file,
766 TRUNCATE_EXISTING));
767 EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND);
768
769 // clean-up
770 ASSERT_TRUE(fs::remove(file_path));
771 }