1 #include "libfilezilla/local_filesys.hpp"
2
3 #include "libfilezilla/buffer.hpp"
4 #include "libfilezilla/file.hpp"
5
6 #ifdef FZ_WINDOWS
7 #include "windows/security_descriptor_builder.hpp"
8 #else
9 #include <errno.h>
10 #include <sys/fcntl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <unistd.h>
14 #include <string.h>
15 #include <utime.h>
16 #endif
17
18 namespace fz {
19
20 namespace {
21 template<typename T>
make_int64fzT(T hi,T lo)22 int64_t make_int64fzT(T hi, T lo)
23 {
24 return (static_cast<int64_t>(hi) << 32) + static_cast<int64_t>(lo);
25 }
26 }
27
28 #ifdef FZ_WINDOWS
29 char const local_filesys::path_separator = '\\';
30 #else
31 char const local_filesys::path_separator = '/';
32 #endif
33
34
~local_filesys()35 local_filesys::~local_filesys()
36 {
37 end_find_files();
38 }
39
40 namespace {
41 #ifdef FZ_WINDOWS
IsNameSurrogateReparsePoint(std::wstring const & file)42 bool IsNameSurrogateReparsePoint(std::wstring const& file)
43 {
44 WIN32_FIND_DATA data;
45 HANDLE hFind = FindFirstFile(file.c_str(), &data);
46 if (hFind != INVALID_HANDLE_VALUE) {
47 FindClose(hFind);
48 return IsReparseTagNameSurrogate(data.dwReserved0);
49 }
50 return false;
51 }
52
is_drive_letter(wchar_t c)53 bool is_drive_letter(wchar_t c)
54 {
55 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
56 }
57 #endif
58
do_get_file_type(native_string const & path,bool follow_links)59 local_filesys::type do_get_file_type(native_string const& path, bool follow_links)
60 {
61
62 #ifdef FZ_WINDOWS
63 DWORD attributes = GetFileAttributes(path.c_str());
64 if (attributes == INVALID_FILE_ATTRIBUTES) {
65 return local_filesys::unknown;
66 }
67
68 bool is_dir = (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
69
70 if (attributes & FILE_ATTRIBUTE_REPARSE_POINT && IsNameSurrogateReparsePoint(path)) {
71 if (!follow_links) {
72 return local_filesys::link;
73 }
74
75 // Follow the reparse point
76 HANDLE hFile = CreateFile(path.c_str(), FILE_READ_ATTRIBUTES | FILE_READ_EA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
77 if (hFile == INVALID_HANDLE_VALUE) {
78 return local_filesys::unknown;
79 }
80
81 BY_HANDLE_FILE_INFORMATION info{};
82 int ret = GetFileInformationByHandle(hFile, &info);
83 CloseHandle(hFile);
84 if (!ret) {
85 return local_filesys::unknown;
86 }
87
88 is_dir = (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
89 }
90
91 return is_dir ? local_filesys::dir : local_filesys::file;
92 #else
93 struct stat buf;
94 int result = lstat(path.c_str(), &buf);
95 if (result) {
96 return local_filesys::unknown;
97 }
98
99 #ifdef S_ISLNK
100 if (S_ISLNK(buf.st_mode)) {
101 if (!follow_links) {
102 return local_filesys::link;
103 }
104
105 result = stat(path.c_str(), &buf);
106 if (result) {
107 return local_filesys::unknown;
108 }
109 }
110 #endif
111
112 if (S_ISDIR(buf.st_mode)) {
113 return local_filesys::dir;
114 }
115
116 return local_filesys::file;
117 #endif
118 }
119 }
120
get_file_type(native_string const & path,bool follow_links)121 local_filesys::type local_filesys::get_file_type(native_string const& path, bool follow_links)
122 {
123 #ifdef FZ_WINDOWS
124 if (path.size() == 6 && path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\' && is_drive_letter(path[4]) && path[5] == ':') {
125 return do_get_file_type(path + L"\\", follow_links);
126 }
127 if (path.size() == 7 && path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\' && is_drive_letter(path[4]) && path[5] == ':' && is_separator(path[6])) {
128 return do_get_file_type(path, follow_links);
129 }
130 #endif
131 if (path.size() > 1 && is_separator(path.back())) {
132 return do_get_file_type(path.substr(0, path.size() - 1), follow_links);
133 }
134
135 return do_get_file_type(path, follow_links);
136 }
137
138 namespace {
139 #ifndef FZ_WINDOWS
get_file_info_impl(int (* do_stat)(struct stat & buf,char const * path,DIR * dir,bool follow),char const * path,DIR * dir,bool & is_link,int64_t * size,datetime * modification_time,int * mode,bool follow_links)140 local_filesys::type get_file_info_impl(int(*do_stat)(struct stat & buf, char const* path, DIR* dir, bool follow), char const* path, DIR* dir, bool &is_link, int64_t* size, datetime* modification_time, int *mode, bool follow_links)
141 {
142 struct stat buf{};
143 static_assert(sizeof(buf.st_size) >= 8, "The st_size member of struct stat must be 8 bytes or larger.");
144
145 int result = do_stat(buf, path, dir, false);
146 if (result) {
147 is_link = false;
148 if (size) {
149 *size = -1;
150 }
151 if (mode) {
152 *mode = -1;
153 }
154 if (modification_time) {
155 *modification_time = datetime();
156 }
157 return local_filesys::unknown;
158 }
159
160 #ifdef S_ISLNK
161 if (S_ISLNK(buf.st_mode)) {
162 is_link = true;
163
164 if (follow_links) {
165 result = do_stat(buf, path, dir, true);
166 if (result) {
167 if (size) {
168 *size = -1;
169 }
170 if (mode) {
171 *mode = -1;
172 }
173 if (modification_time) {
174 *modification_time = datetime();
175 }
176 return local_filesys::unknown;
177 }
178 }
179 else {
180 if (modification_time) {
181 *modification_time = datetime(buf.st_mtime, datetime::seconds);
182 }
183
184 if (mode) {
185 *mode = buf.st_mode & 0777;
186 }
187 if (size) {
188 *size = -1;
189 }
190 return local_filesys::link;
191 }
192 }
193 else
194 #endif
195 is_link = false;
196
197 if (modification_time) {
198 *modification_time = datetime(buf.st_mtime, datetime::seconds);
199 }
200
201 if (mode) {
202 *mode = buf.st_mode & 0777;
203 }
204
205 if (S_ISDIR(buf.st_mode)) {
206 if (size) {
207 *size = -1;
208 }
209 return local_filesys::dir;
210 }
211
212 if (size) {
213 *size = buf.st_size;
214 }
215
216 return local_filesys::file;
217 }
218
get_file_info_at(char const * path,DIR * dir,bool & is_link,int64_t * size,datetime * modification_time,int * mode)219 local_filesys::type get_file_info_at(char const* path, DIR* dir, bool &is_link, int64_t* size, datetime* modification_time, int *mode)
220 {
221 auto do_stat = [](struct stat & buf, char const* path, DIR * dir, bool follow)
222 {
223 return fstatat(dirfd(dir), path, &buf, follow ? 0 : AT_SYMLINK_NOFOLLOW);
224 };
225 return get_file_info_impl(do_stat, path, dir, is_link, size, modification_time, mode, true);
226 }
227 #endif
228
do_get_file_info(native_string const & path,bool & is_link,int64_t * size,datetime * modification_time,int * mode,bool follow_links)229 local_filesys::type do_get_file_info(native_string const& path, bool& is_link, int64_t* size, datetime* modification_time, int* mode, bool follow_links)
230 {
231 #ifdef FZ_WINDOWS
232 is_link = false;
233
234 WIN32_FILE_ATTRIBUTE_DATA data;
235 if (!GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, &data)) {
236 if (size) {
237 *size = -1;
238 }
239 if (mode) {
240 *mode = 0;
241 }
242 if (modification_time) {
243 *modification_time = datetime();
244 }
245 return local_filesys::unknown;
246 }
247
248 bool is_dir = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
249
250 if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && IsNameSurrogateReparsePoint(path)) {
251 is_link = true;
252
253 if (!follow_links) {
254 if (modification_time) {
255 *modification_time = datetime(data.ftLastWriteTime, datetime::milliseconds);
256 if (modification_time->empty()) {
257 *modification_time = datetime(data.ftCreationTime, datetime::milliseconds);
258 }
259 }
260
261 if (mode) {
262 *mode = (int)data.dwFileAttributes;
263 }
264
265 if (size) {
266 *size = -1;
267 }
268
269 return local_filesys::link;
270 }
271
272 HANDLE hFile = is_dir ? INVALID_HANDLE_VALUE : CreateFile(path.c_str(), FILE_READ_ATTRIBUTES | FILE_READ_EA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
273 if (hFile != INVALID_HANDLE_VALUE) {
274 BY_HANDLE_FILE_INFORMATION info{};
275 int ret = GetFileInformationByHandle(hFile, &info);
276 CloseHandle(hFile);
277 if (ret != 0) {
278 if (modification_time) {
279 if (!modification_time->set(info.ftLastWriteTime, datetime::milliseconds)) {
280 modification_time->set(info.ftCreationTime, datetime::milliseconds);
281 }
282 }
283
284 if (mode) {
285 *mode = (int)info.dwFileAttributes;
286 }
287
288 if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
289 if (size) {
290 *size = -1;
291 }
292 return local_filesys::dir;
293 }
294
295 if (size) {
296 *size = make_int64fzT(info.nFileSizeHigh, info.nFileSizeLow);
297 }
298
299 return local_filesys::file;
300 }
301 }
302
303 if (size) {
304 *size = -1;
305 }
306 if (mode) {
307 *mode = 0;
308 }
309 if (modification_time) {
310 *modification_time = datetime();
311 }
312 return is_dir ? local_filesys::dir : local_filesys::unknown;
313 }
314
315 if (modification_time) {
316 *modification_time = datetime(data.ftLastWriteTime, datetime::milliseconds);
317 if (modification_time->empty()) {
318 *modification_time = datetime(data.ftCreationTime, datetime::milliseconds);
319 }
320 }
321
322 if (mode) {
323 *mode = (int)data.dwFileAttributes;
324 }
325
326 if (is_dir) {
327 if (size) {
328 *size = -1;
329 }
330 return local_filesys::dir;
331 }
332 else {
333 if (size) {
334 *size = make_int64fzT(data.nFileSizeHigh, data.nFileSizeLow);
335 }
336 return local_filesys::file;
337 }
338 #else
339 auto do_stat = [](struct stat& buf, char const* path, DIR*, bool follow)
340 {
341 if (follow) {
342 return stat(path, &buf);
343 }
344 else {
345 return lstat(path, &buf);
346 }
347 };
348 return get_file_info_impl(do_stat, path.c_str(), nullptr, is_link, size, modification_time, mode, follow_links);
349 #endif
350 }
351 }
352
get_file_info(native_string const & path,bool & is_link,int64_t * size,datetime * modification_time,int * mode,bool follow_links)353 local_filesys::type local_filesys::get_file_info(native_string const& path, bool &is_link, int64_t* size, datetime* modification_time, int *mode, bool follow_links)
354 {
355 #ifdef FZ_WINDOWS
356 if (path.size() == 6 && path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\' && is_drive_letter(path[4]) && path[5] == ':') {
357 return do_get_file_info(path + L"\\", is_link, size, modification_time, mode, follow_links);
358 }
359 if (path.size() == 7 && path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\' && is_drive_letter(path[4]) && path[5] == ':' && is_separator(path[6])) {
360 return do_get_file_info(path, is_link, size, modification_time, mode, follow_links);
361 }
362 #endif
363 if (path.size() > 1 && is_separator(path.back())) {
364 return do_get_file_info(path.substr(0, path.size() - 1), is_link, size, modification_time, mode, follow_links);
365 }
366
367 return do_get_file_info(path, is_link, size, modification_time, mode, follow_links);
368 }
369
begin_find_files(native_string path,bool dirs_only)370 result local_filesys::begin_find_files(native_string path, bool dirs_only)
371 {
372 if (path.empty()) {
373 return result{result::nodir};
374 }
375
376 end_find_files();
377
378 dirs_only_ = dirs_only;
379 #ifdef FZ_WINDOWS
380 if (is_separator(path.back())) {
381 m_find_path = path;
382 path += '*';
383 }
384 else {
385 m_find_path = path + fzT("\\");
386 path += fzT("\\*");
387 }
388
389 m_hFind = FindFirstFileEx(path.c_str(), FindExInfoStandard, &m_find_data, dirs_only ? FindExSearchLimitToDirectories : FindExSearchNameMatch, nullptr, 0);
390 if (m_hFind == INVALID_HANDLE_VALUE) {
391 has_next_ = false;
392 switch (GetLastError()) {
393 case ERROR_ACCESS_DENIED:
394 return result{result::noperm};
395 default:
396 return result{result::other};
397 }
398 }
399
400 has_next_ = true;
401 return result{result::ok};
402 #else
403 if (path.size() > 1 && path.back() == '/') {
404 path.pop_back();
405 }
406
407 dir_ = opendir(path.c_str());
408 if (!dir_) {
409 switch (errno) {
410 case EACCES:
411 case EPERM:
412 return result{result::noperm};
413 case ENOTDIR:
414 case ENOENT:
415 return result{result::nodir};
416 default:
417 return result{result::other};
418 }
419 }
420
421 return result{result::ok};
422 #endif
423 }
424
end_find_files()425 void local_filesys::end_find_files()
426 {
427 #ifdef FZ_WINDOWS
428 has_next_ = false;
429 if (m_hFind != INVALID_HANDLE_VALUE) {
430 FindClose(m_hFind);
431 m_hFind = INVALID_HANDLE_VALUE;
432 }
433 #else
434 if (dir_) {
435 closedir(dir_);
436 dir_ = nullptr;
437 }
438 #endif
439 }
440
get_next_file(native_string & name)441 bool local_filesys::get_next_file(native_string& name)
442 {
443 #ifdef FZ_WINDOWS
444 if (!has_next_) {
445 return false;
446 }
447 do {
448 name = m_find_data.cFileName;
449 if (name.empty()) {
450 has_next_ = FindNextFile(m_hFind, &m_find_data) != 0;
451 return true;
452 }
453 if (name == fzT(".") || name == fzT("..")) {
454 continue;
455 }
456
457 if (dirs_only_ && !(m_find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
458 continue;
459 }
460
461 has_next_ = FindNextFile(m_hFind, &m_find_data) != 0;
462 return true;
463 } while ((has_next_ = FindNextFile(m_hFind, &m_find_data) != 0));
464
465 return false;
466 #else
467 if (!dir_) {
468 return false;
469 }
470
471 struct dirent* entry;
472 while ((entry = readdir(dir_))) {
473 if (!entry->d_name[0] ||
474 !strcmp(entry->d_name, ".") ||
475 !strcmp(entry->d_name, ".."))
476 continue;
477
478 if (dirs_only_) {
479 #if HAVE_STRUCT_DIRENT_D_TYPE
480 if (entry->d_type == DT_LNK) {
481 bool wasLink{};
482 if (get_file_info_at(entry->d_name, dir_, wasLink, nullptr, nullptr, nullptr) != dir) {
483 continue;
484 }
485 }
486 else if (entry->d_type != DT_DIR) {
487 continue;
488 }
489 #else
490 // Solaris doesn't have d_type
491 bool wasLink{};
492 if (get_file_info_at(entry->d_name, dir_, wasLink, nullptr, nullptr, nullptr) != dir) {
493 continue;
494 }
495 #endif
496 }
497
498 name = entry->d_name;
499
500 return true;
501 }
502
503 return false;
504 #endif
505 }
506
get_next_file(native_string & name,bool & is_link,local_filesys::type & t,int64_t * size,datetime * modification_time,int * mode)507 bool local_filesys::get_next_file(native_string& name, bool &is_link, local_filesys::type & t, int64_t* size, datetime* modification_time, int* mode)
508 {
509 #ifdef FZ_WINDOWS
510 if (!has_next_) {
511 return false;
512 }
513 do {
514 if (!m_find_data.cFileName[0]) {
515 continue;
516 }
517 if (dirs_only_ && !(m_find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
518 continue;
519 }
520
521 if (m_find_data.cFileName[0] == '.' && (!m_find_data.cFileName[1] || (m_find_data.cFileName[1] == '.' && !m_find_data.cFileName[2]))) {
522 continue;
523 }
524 name = m_find_data.cFileName;
525
526 t = (m_find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? dir : file;
527
528 is_link = (m_find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && IsReparseTagNameSurrogate(m_find_data.dwReserved0);
529 if (is_link) {
530 // Follow the reparse point
531 HANDLE hFile = CreateFile((m_find_path + name).c_str(), FILE_READ_ATTRIBUTES | FILE_READ_EA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
532 if (hFile != INVALID_HANDLE_VALUE) {
533 BY_HANDLE_FILE_INFORMATION info{};
534 int ret = GetFileInformationByHandle(hFile, &info);
535 CloseHandle(hFile);
536 if (ret != 0) {
537
538 if (modification_time) {
539 *modification_time = datetime(info.ftLastWriteTime, datetime::milliseconds);
540 if (modification_time->empty()) {
541 *modification_time = datetime(info.ftCreationTime, datetime::milliseconds);
542 }
543 }
544
545 if (mode) {
546 *mode = (int)info.dwFileAttributes;
547 }
548
549 t = (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? dir : file;
550 if (size) {
551 if (t == dir) {
552 *size = -1;
553 }
554 else {
555 *size = make_int64fzT(info.nFileSizeHigh, info.nFileSizeLow);
556 }
557 }
558
559 has_next_ = FindNextFile(m_hFind, &m_find_data) != 0;
560 return true;
561 }
562 }
563
564 if (dirs_only_ && t != dir) {
565 continue;
566 }
567
568 if (size) {
569 *size = -1;
570 }
571 if (mode) {
572 *mode = 0;
573 }
574 if (modification_time) {
575 *modification_time = datetime();
576 }
577 }
578 else {
579 if (modification_time) {
580 *modification_time = datetime(m_find_data.ftLastWriteTime, datetime::milliseconds);
581 if (modification_time->empty()) {
582 *modification_time = datetime(m_find_data.ftLastWriteTime, datetime::milliseconds);
583 }
584 }
585
586 if (mode) {
587 *mode = (int)m_find_data.dwFileAttributes;
588 }
589
590 if (size) {
591 if (t == dir) {
592 *size = -1;
593 }
594 else {
595 *size = make_int64fzT(m_find_data.nFileSizeHigh, m_find_data.nFileSizeLow);
596 }
597 }
598 }
599 has_next_ = FindNextFile(m_hFind, &m_find_data) != 0;
600 return true;
601 } while ((has_next_ = FindNextFile(m_hFind, &m_find_data) != 0));
602
603 return false;
604 #else
605 if (!dir_) {
606 return false;
607 }
608
609 struct dirent* entry;
610 while ((entry = readdir(dir_))) {
611 if (!entry->d_name[0] ||
612 !strcmp(entry->d_name, ".") ||
613 !strcmp(entry->d_name, ".."))
614 continue;
615
616 #if HAVE_STRUCT_DIRENT_D_TYPE
617 if (dirs_only_) {
618 if (entry->d_type == DT_LNK) {
619 if (get_file_info_at(entry->d_name, dir_, is_link, size, modification_time, mode) != dir) {
620 continue;
621 }
622
623 name = entry->d_name;
624 t = dir;
625 return true;
626 }
627 else if (entry->d_type != DT_DIR) {
628 continue;
629 }
630 }
631 #endif
632
633 t = get_file_info_at(entry->d_name, dir_, is_link, size, modification_time, mode);
634 if (t == unknown) { // Happens for example in case of permission denied
635 #if HAVE_STRUCT_DIRENT_D_TYPE
636 t = (entry->d_type == DT_DIR) ? dir : file;
637 #endif
638 is_link = false;
639 if (size) {
640 *size = -1;
641 }
642 if (modification_time) {
643 *modification_time = datetime();
644 }
645 if (mode) {
646 *mode = 0;
647 }
648 }
649 if (dirs_only_ && t != dir) {
650 continue;
651 }
652
653 name = entry->d_name;
654
655 return true;
656 }
657
658 return false;
659 #endif
660 }
661
get_modification_time(native_string const & path)662 datetime local_filesys::get_modification_time(native_string const& path)
663 {
664 datetime mtime;
665
666 bool tmp;
667 if (get_file_info(path, tmp, nullptr, &mtime, nullptr) == unknown) {
668 mtime = datetime();
669 }
670
671 return mtime;
672 }
673
set_modification_time(native_string const & path,datetime const & t)674 bool local_filesys::set_modification_time(native_string const& path, datetime const& t)
675 {
676 if (t.empty()) {
677 return false;
678 }
679
680 #ifdef FZ_WINDOWS
681 FILETIME ft = t.get_filetime();
682 if (!ft.dwHighDateTime) {
683 return false;
684 }
685
686 HANDLE h = CreateFile(path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
687 if (h == INVALID_HANDLE_VALUE) {
688 return false;
689 }
690
691 bool ret = SetFileTime(h, nullptr, &ft, &ft) == TRUE;
692 CloseHandle(h);
693 return ret;
694 #else
695 utimbuf utm{};
696 utm.actime = t.get_time_t();
697 utm.modtime = utm.actime;
698 return utime(path.c_str(), &utm) == 0;
699 #endif
700 }
701
get_size(native_string const & path,bool * is_link)702 int64_t local_filesys::get_size(native_string const& path, bool* is_link)
703 {
704 int64_t ret = -1;
705 bool tmp{};
706 type t = get_file_info(path, is_link ? *is_link : tmp, &ret, nullptr, nullptr);
707 if (t != file) {
708 ret = -1;
709 }
710
711 return ret;
712 }
713
get_link_target(native_string const & path)714 native_string local_filesys::get_link_target(native_string const& path)
715 {
716 native_string target;
717
718 #ifdef FZ_WINDOWS
719 HANDLE hFile = CreateFile(path.c_str(), FILE_READ_ATTRIBUTES | FILE_READ_EA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
720 if (hFile != INVALID_HANDLE_VALUE) {
721 DWORD const size = 1024;
722 native_string::value_type out[size];
723 DWORD ret = GetFinalPathNameByHandle(hFile, out, size, 0);
724 if (ret > 0 && ret < size) {
725 target = out;
726 }
727 CloseHandle(hFile);
728 }
729 #else
730 size_t const size = 1024;
731 char out[size];
732
733 ssize_t res = readlink(path.c_str(), out, size);
734 if (res > 0 && static_cast<size_t>(res) < size) {
735 out[res] = 0;
736 target = out;
737 }
738 #endif
739 return target;
740 }
741
742 namespace {
do_mkdir(native_string const & path,mkdir_permissions permissions)743 result do_mkdir(native_string const& path, mkdir_permissions permissions)
744 {
745 result ret{result::other};
746 #ifdef FZ_WINDOWS
747
748 SECURITY_ATTRIBUTES attr{};
749 attr.bInheritHandle = false;
750 attr.nLength = sizeof(SECURITY_ATTRIBUTES);
751
752 security_descriptor_builder sdb;
753 if (permissions != mkdir_permissions::normal) {
754 sdb.add(security_descriptor_builder::self);
755 if (permissions == mkdir_permissions::cur_user_and_admins) {
756 sdb.add(security_descriptor_builder::administrators);
757 }
758 auto sd = sdb.get_sd();
759 if (!sd) {
760 return {result::other};
761 }
762 attr.lpSecurityDescriptor = sd;
763 }
764
765 if (CreateDirectory(path.c_str(), &attr)) {
766 ret = {result::ok};
767 }
768 else if (GetLastError() == ERROR_ACCESS_DENIED) {
769 ret = {result::noperm};
770 }
771 #else
772 int res = ::mkdir(path.c_str(), (permissions == mkdir_permissions::normal) ? 0777 : 0700);
773 if (!res) {
774 ret = {result::ok};
775 }
776 else if (errno == EACCES || errno == EPERM) {
777 ret = {result::noperm};
778 }
779 #endif
780
781 return ret;
782 }
783 }
784
mkdir(native_string const & absolute_path,bool recurse,mkdir_permissions permissions,native_string * last_created)785 result mkdir(native_string const& absolute_path, bool recurse, mkdir_permissions permissions, native_string* last_created)
786 {
787 // Step 0: Require an absolute path
788 #ifdef FZ_WINDOWS
789 bool unc{};
790 size_t offset{};
791 size_t min_len{};
792 if (starts_with(absolute_path, std::wstring(L"\\\\?\\"))) {
793 offset = 4;
794 }
795 else if (starts_with(absolute_path, std::wstring(L"\\\\"))) {
796 unc = true;
797 size_t pos = absolute_path.find_first_of(L"\\/", 2);
798 if (pos == std::wstring::npos || pos == 2) {
799 return result{result::other};
800 }
801 size_t pos2 = absolute_path.find_first_of(L"\\/", pos + 1);
802 if (pos2 == pos + 1) {
803 return result{result::other};
804 }
805 min_len = (pos2 == std::wstring::npos) ? absolute_path.size() : (pos2 - 1);
806 }
807
808 if (!unc) {
809 auto c = absolute_path[offset];
810 if (absolute_path.size() < offset + 2 || absolute_path[offset + 1] != ':' || !is_drive_letter(c)) {
811 return result{result::other};
812 }
813 size_t pos = absolute_path.find_first_of(L"\\/", offset + 2);
814 if (pos != std::wstring::npos && pos != offset + 2) {
815 return result{result::other};
816 }
817 min_len = offset + 2;
818 }
819 #else
820 if (absolute_path.empty() || absolute_path[0] != '/') {
821 return result{result::other};
822 }
823 size_t const min_len = 1;
824 #endif
825
826 // Step 1: Check if directory already exists
827 auto t = fz::local_filesys::get_file_type(absolute_path, true);
828 if (t == fz::local_filesys::dir) {
829 return result{result::ok};
830 }
831 else if (t != fz::local_filesys::unknown) {
832 return result{result::nodir};
833 }
834
835 if (recurse) {
836 // Step 2: Find a parent that exist
837 native_string work = absolute_path;
838 while (!work.empty() && local_filesys::is_separator(work.back())) {
839 work.pop_back();
840 }
841
842 bool found{};
843 std::vector<native_string> segments;
844 while (work.size() > min_len && !found) {
845 #ifdef FZ_WINDOWS
846 size_t pos = work.find_last_of(L"\\/");
847 #else
848 size_t pos = work.rfind('/');
849 #endif
850 if (pos == native_string::npos) {
851 work.clear();
852 }
853
854 if (pos + 1 < work.size()) {
855 segments.push_back(work.substr(pos + 1));
856 work = work.substr(0, pos);
857 auto t = fz::local_filesys::get_file_type(work.empty() ? fzT("/") : work, true);
858
859 if (t == fz::local_filesys::dir) {
860 found = true;
861 break;
862 }
863 else if (t != fz::local_filesys::unknown) {
864 return result{result::nodir};
865 }
866 }
867 else {
868 work = work.substr(0, pos);
869 }
870 }
871 if (!found) {
872 return result{result::other};
873 }
874
875 // Step 3: Create the segments
876 for (size_t i = 0; i < segments.size(); ++i) {
877 work += local_filesys::path_separator;
878 work += segments[segments.size() - i - 1];
879 result r = do_mkdir(work, ((i + 1) == segments.size()) ? permissions : mkdir_permissions::normal);
880 if (!r) {
881 return r;
882 }
883 if (last_created) {
884 *last_created = work;
885 }
886 }
887 }
888 else {
889 result r = do_mkdir(absolute_path, permissions);
890 if (!r) {
891 return r;
892 }
893 if (last_created) {
894 *last_created = absolute_path;
895 }
896 }
897
898 return result{result::ok};
899 }
900
901 #ifndef FZ_WINDOWS
902 namespace {
do_copy(native_string const & source,native_string const & dest,bool & dest_opened)903 static result do_copy(native_string const& source, native_string const& dest, bool & dest_opened)
904 {
905 fz::file in(source, fz::file::reading, fz::file::existing);
906
907 if (!in.opened()) {
908 // TODO error codes
909 return {result::other};
910 }
911
912 fz::file out(dest, fz::file::writing, fz::file::empty);
913 if (!out.opened()) {
914 // TODO error codes
915 return {result::other};
916 }
917
918 dest_opened = true;
919
920 fz::buffer buffer;
921 while (true) {
922 if (buffer.empty()) {
923 auto read = in.read(buffer.get(64 * 1024), 64 * 1024);
924 if (read < 0) {
925 return {result::other};
926 }
927 else if (read) {
928 buffer.add(read);
929 }
930 else {
931 if (buffer.empty()) {
932 return {result::ok};
933 }
934 }
935 }
936 auto written = out.write(buffer.get(), buffer.size());
937 if (written <= 0) {
938 return {result::other};
939 }
940
941 buffer.consume(written);
942 }
943 if (!out.fsync()) {
944 return {result::other};
945 }
946
947 return {result::ok};
948 }
949 }
950 #endif
951
rename_file(native_string const & source,native_string const & dest,bool allow_copy)952 result rename_file(native_string const& source, native_string const& dest, bool allow_copy)
953 {
954 #ifdef FZ_WINDOWS
955 DWORD flags = MOVEFILE_REPLACE_EXISTING;
956 if (allow_copy) {
957 flags |= MOVEFILE_COPY_ALLOWED;
958 }
959 DWORD res = MoveFileExW(source.c_str(), dest.c_str(), flags);
960 if (res) {
961 return {result::ok};
962 }
963
964 DWORD err = GetLastError();
965 switch (err) {
966 case ERROR_FILE_NOT_FOUND:
967 return {result::nofile};
968 case ERROR_PATH_NOT_FOUND:
969 return {result::nodir};
970 case ERROR_ACCESS_DENIED:
971 return {result::noperm};
972 case ERROR_DISK_FULL:
973 return {result::nospace};
974 default:
975 return {result::other};
976 }
977 #else
978 int res = rename(source.c_str(), dest.c_str());
979 if (!res) {
980 return {result::ok};
981 }
982
983 switch (errno) {
984 case EPERM:
985 case EACCES:
986 return {result::noperm};
987 case ENOSPC:
988 return {result::nospace};
989 case ENOTDIR:
990 return {result::nodir};
991 case ENOENT:
992 case EISDIR:
993 return {result::nofile};
994 case EXDEV:
995 break;
996 default:
997 return {result::other};
998 }
999
1000 if (!allow_copy) {
1001 return {result::other};
1002 }
1003
1004 bool dest_opened{};
1005 auto ret = do_copy(source, dest, dest_opened);
1006 if (!ret) {
1007 if (dest_opened) {
1008 unlink(dest.c_str());
1009 }
1010 return ret;
1011 }
1012
1013 res = unlink(source.c_str());
1014 if (res != 0) {
1015 switch (errno) {
1016 case EPERM:
1017 case EACCES:
1018 return {result::noperm};
1019 case ENOTDIR:
1020 return {result::nodir};
1021 case ENOENT:
1022 case EISDIR:
1023 return {result::nofile};
1024 case EXDEV:
1025 break;
1026 default:
1027 return {result::other};
1028 }
1029 }
1030
1031 return result{result::ok};
1032 #endif
1033 }
1034
1035 }
1036