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