1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "fs.h"
21 
22 #ifdef _WIN32
23 #include <windows.h>
24 #include <ntdef.h>
25 #include <winioctl.h>
26 
27 #include "utf8.h"
28 #endif
29 
30 #include <sys/stat.h> /* S_* statbuf */
31 #include <sys/types.h> /* size_t mode_t */
32 #include <unistd.h> /* pathconf() readlink() */
33 
34 #include <ctype.h> /* isalpha() */
35 #include <errno.h> /* errno */
36 #include <stddef.h> /* NULL */
37 #include <stdio.h> /* snprintf() remove() */
38 #include <stdlib.h> /* free() */
39 #include <string.h> /* strcpy() strdup() strlen() strncmp() strncpy() */
40 
41 #include "../compat/dtype.h"
42 #include "../compat/fs_limits.h"
43 #include "../compat/os.h"
44 #include "../io/iop.h"
45 #include "log.h"
46 #include "path.h"
47 #include "str.h"
48 #include "string_array.h"
49 #include "utils.h"
50 
51 static int is_dir_fast(const char path[]);
52 static int path_exists_internal(const char path[], const char filename[],
53 		int deref);
54 
55 #ifndef _WIN32
56 static int is_directory(const char path[], int dereference_links);
57 #endif
58 
59 int
is_dir(const char path[])60 is_dir(const char path[])
61 {
62 	if(is_dir_fast(path))
63 	{
64 		return 1;
65 	}
66 
67 #ifndef _WIN32
68 	return is_directory(path, 1);
69 #else
70 	{
71 		const DWORD attrs = win_get_file_attrs(path);
72 		return attrs != INVALID_FILE_ATTRIBUTES
73 		    && (attrs & FILE_ATTRIBUTE_DIRECTORY);
74 	}
75 #endif
76 }
77 
78 /* Checks if path is an existing directory faster than is_dir() does this at the
79  * cost of less accurate results (fails on non-sufficient rights).
80  * Automatically dereferences symbolic links.  Returns non-zero if path points
81  * to a directory, otherwise zero is returned. */
82 static int
is_dir_fast(const char path[])83 is_dir_fast(const char path[])
84 {
85 #if !defined(_WIN32) && !defined(__APPLE__) && !defined(BSD)
86 	/* Optimization idea: is_dir() ends up using stat() call, which in turn has
87 	 * to:
88 	 *  1) resolve path to an inode number;
89 	 *  2) find and load inode for the directory.
90 	 * Checking "path/." for existence is a "hack" to omit 2).
91 	 * Negative answer of this method doesn't guarantee directory absence, but
92 	 * positive answer provides correct answer faster than is_dir() would. */
93 
94 	const size_t len = strlen(path);
95 	char path_to_selfref[len + 1 + 1 + 1];
96 
97 	strcpy(path_to_selfref, path);
98 	path_to_selfref[len] = '/';
99 	path_to_selfref[len + 1] = '.';
100 	path_to_selfref[len + 2] = '\0';
101 
102 	return os_access(path_to_selfref, F_OK) == 0;
103 #else
104 	/* Some systems report that "/path/to/file/." is directory... */
105 	return 0;
106 #endif
107 }
108 
109 int
is_dir_empty(const char path[])110 is_dir_empty(const char path[])
111 {
112 	DIR *dir;
113 	struct dirent *d;
114 
115 	dir = os_opendir(path);
116 	if(dir == NULL)
117 	{
118 		return 1;
119 	}
120 
121 	while((d = os_readdir(dir)) != NULL)
122 	{
123 		if(!is_builtin_dir(d->d_name))
124 		{
125 			break;
126 		}
127 	}
128 	os_closedir(dir);
129 
130 	return d == NULL;
131 }
132 
133 int
is_valid_dir(const char * path)134 is_valid_dir(const char *path)
135 {
136 	return is_dir(path) || is_unc_root(path);
137 }
138 
139 int
path_exists(const char path[],int deref)140 path_exists(const char path[], int deref)
141 {
142 	if(!is_path_absolute(path))
143 	{
144 		LOG_ERROR_MSG("Passed relative path where absolute one is expected: %s",
145 				path);
146 	}
147 	return path_exists_internal(NULL, path, deref);
148 }
149 
150 int
path_exists_at(const char path[],const char filename[],int deref)151 path_exists_at(const char path[], const char filename[], int deref)
152 {
153 	if(is_path_absolute(filename))
154 	{
155 		LOG_ERROR_MSG("Passed absolute path where relative one is expected: %s",
156 				filename);
157 		path = NULL;
158 	}
159 	return path_exists_internal(path, filename, deref);
160 }
161 
162 /* Checks whether path/file exists. If path is NULL, filename is assumed to
163  * contain full path. */
164 static int
path_exists_internal(const char path[],const char filename[],int deref)165 path_exists_internal(const char path[], const char filename[], int deref)
166 {
167 	char full[PATH_MAX + 1];
168 	if(path == NULL)
169 	{
170 		copy_str(full, sizeof(full), filename);
171 	}
172 	else
173 	{
174 		snprintf(full, sizeof(full), "%s/%s", path, filename);
175 	}
176 
177 	/* At least on Windows extra trailing slash can mess up the check, so get rid
178 	 * of it. */
179 	if(!is_root_dir(full))
180 	{
181 		chosp(full);
182 	}
183 
184 	if(!deref)
185 	{
186 		struct stat st;
187 		return os_lstat(full, &st) == 0;
188 	}
189 	return os_access(full, F_OK) == 0;
190 }
191 
192 int
paths_are_same(const char s[],const char t[])193 paths_are_same(const char s[], const char t[])
194 {
195 	char s_real[PATH_MAX + 1];
196 	char t_real[PATH_MAX + 1];
197 
198 	if(os_realpath(s, s_real) != s_real || os_realpath(t, t_real) != t_real)
199 	{
200 		return (stroscmp(s, t) == 0);
201 	}
202 	return (stroscmp(s_real, t_real) == 0);
203 }
204 
205 int
is_symlink(const char path[])206 is_symlink(const char path[])
207 {
208 #ifndef _WIN32
209 	struct stat st;
210 	return os_lstat(path, &st) == 0 && S_ISLNK(st.st_mode);
211 #else
212 	return (win_get_reparse_point_type(path) == IO_REPARSE_TAG_SYMLINK);
213 #endif
214 }
215 
216 int
is_shortcut(const char path[])217 is_shortcut(const char path[])
218 {
219 #ifndef _WIN32
220 	return 0;
221 #else
222 	return (strcasecmp(get_ext(path), "lnk") == 0);
223 #endif
224 }
225 
226 SymLinkType
get_symlink_type(const char path[])227 get_symlink_type(const char path[])
228 {
229 	char cwd[PATH_MAX + 1];
230 	char linkto[PATH_MAX + NAME_MAX];
231 	int saved_errno;
232 	char *filename_copy;
233 	char *p;
234 
235 	if(get_cwd(cwd, sizeof(cwd)) == NULL)
236 	{
237 		/* getcwd() failed, just use "." rather than fail. */
238 		strcpy(cwd, ".");
239 	}
240 
241 	/* Use readlink() (in get_link_target_abs) before realpath() to check for
242 	 * target at slow file system.  realpath() doesn't fit in this case as it
243 	 * resolves chains of symbolic links and we want to try only the first one. */
244 	if(get_link_target_abs(path, cwd, linkto, sizeof(linkto)) != 0)
245 	{
246 		LOG_SERROR_MSG(errno, "Can't readlink \"%s\"", path);
247 		log_cwd();
248 		return SLT_UNKNOWN;
249 	}
250 	if(refers_to_slower_fs(path, linkto))
251 	{
252 		return SLT_SLOW;
253 	}
254 
255 	filename_copy = strdup(path);
256 	chosp(filename_copy);
257 
258 	p = os_realpath(filename_copy, linkto);
259 	saved_errno = errno;
260 
261 	free(filename_copy);
262 
263 	if(p == linkto)
264 	{
265 		return is_dir(linkto) ? SLT_DIR : SLT_UNKNOWN;
266 	}
267 
268 	LOG_SERROR_MSG(saved_errno, "Can't realpath \"%s\"", path);
269 	log_cwd();
270 	return SLT_UNKNOWN;
271 }
272 
273 int
get_link_target_abs(const char link[],const char cwd[],char buf[],size_t buf_len)274 get_link_target_abs(const char link[], const char cwd[], char buf[],
275 		size_t buf_len)
276 {
277 	char link_target[PATH_MAX + 1];
278 	if(get_link_target(link, link_target, sizeof(link_target)) != 0)
279 	{
280 		return 1;
281 	}
282 	if(is_path_absolute(link_target))
283 	{
284 		strncpy(buf, link_target, buf_len);
285 		buf[buf_len - 1] = '\0';
286 	}
287 	else
288 	{
289 		snprintf(buf, buf_len, "%s/%s", cwd, link_target);
290 	}
291 	return 0;
292 }
293 
294 int
get_link_target(const char link[],char buf[],size_t buf_len)295 get_link_target(const char link[], char buf[], size_t buf_len)
296 {
297 #ifndef _WIN32
298 	char *filename;
299 	ssize_t len;
300 
301 	if(buf_len == 0)
302 	{
303 		return -1;
304 	}
305 
306 	filename = strdup(link);
307 	chosp(filename);
308 
309 	len = readlink(filename, buf, buf_len - 1);
310 
311 	free(filename);
312 
313 	if(len == -1)
314 	{
315 		return -1;
316 	}
317 
318 	buf[len] = '\0';
319 	return 0;
320 #else
321 	if(win_symlink_read(link, buf, buf_len) == 0)
322 	{
323 		return 0;
324 	}
325 	return win_shortcut_read(link, buf, buf_len);
326 #endif
327 }
328 
329 int
make_path(const char dir_name[],mode_t mode)330 make_path(const char dir_name[], mode_t mode)
331 {
332 	io_args_t args = {
333 		.arg1.path = dir_name,
334 		.arg2.process_parents = 1,
335 		.arg3.mode = mode,
336 	};
337 
338 	return iop_mkdir(&args);
339 }
340 
341 int
create_path(const char dir_name[],mode_t mode)342 create_path(const char dir_name[], mode_t mode)
343 {
344 	return is_dir(dir_name) ? 1 : make_path(dir_name, mode);
345 }
346 
347 int
symlinks_available(void)348 symlinks_available(void)
349 {
350 #ifndef _WIN32
351 	return 1;
352 #else
353 	return is_vista_and_above();
354 #endif
355 }
356 
357 int
has_atomic_file_replace(void)358 has_atomic_file_replace(void)
359 {
360 #ifndef _WIN32
361 	return 1;
362 #else
363 	return 0;
364 #endif
365 }
366 
367 int
directory_accessible(const char path[])368 directory_accessible(const char path[])
369 {
370 	return os_access(path, X_OK) == 0 || is_unc_root(path);
371 }
372 
373 int
is_dir_writable(const char path[])374 is_dir_writable(const char path[])
375 {
376 	if(!is_unc_root(path))
377 	{
378 #ifdef _WIN32
379 		HANDLE hdir;
380 		wchar_t *utf16_path;
381 
382 		if(is_on_fat_volume(path))
383 		{
384 			return 1;
385 		}
386 
387 		utf16_path = utf8_to_utf16(path);
388 		hdir = CreateFileW(utf16_path, GENERIC_WRITE,
389 				FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
390 				FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
391 		free(utf16_path);
392 
393 		if(hdir != INVALID_HANDLE_VALUE)
394 		{
395 			CloseHandle(hdir);
396 			return 1;
397 		}
398 #else
399 	if(os_access(path, W_OK) == 0)
400 		return 1;
401 #endif
402 	}
403 
404 	return 0;
405 }
406 
407 uint64_t
get_file_size(const char path[])408 get_file_size(const char path[])
409 {
410 #ifndef _WIN32
411 	struct stat st;
412 	if(os_lstat(path, &st) == 0)
413 	{
414 		return (uint64_t)st.st_size;
415 	}
416 	return 0;
417 #else
418 	wchar_t *utf16_path;
419 	int ok;
420 	WIN32_FILE_ATTRIBUTE_DATA attrs;
421 	LARGE_INTEGER size;
422 
423 	utf16_path = utf8_to_utf16(path);
424 	ok = GetFileAttributesExW(utf16_path, GetFileExInfoStandard, &attrs);
425 	free(utf16_path);
426 	if(!ok)
427 	{
428 		LOG_WERROR(GetLastError());
429 		return 0;
430 	}
431 
432 	size.u.LowPart = attrs.nFileSizeLow;
433 	size.u.HighPart = attrs.nFileSizeHigh;
434 	return size.QuadPart;
435 #endif
436 }
437 
438 char **
list_regular_files(const char path[],char * list[],int * len)439 list_regular_files(const char path[], char *list[], int *len)
440 {
441 	DIR *dir;
442 	struct dirent *d;
443 
444 	dir = os_opendir(path);
445 	if(dir == NULL)
446 	{
447 		return list;
448 	}
449 
450 	while((d = os_readdir(dir)) != NULL)
451 	{
452 		char full_path[PATH_MAX + 1];
453 		snprintf(full_path, sizeof(full_path), "%s/%s", path, d->d_name);
454 
455 		if(is_regular_file(full_path))
456 		{
457 			*len = add_to_string_array(&list, *len, d->d_name);
458 		}
459 	}
460 	os_closedir(dir);
461 
462 	return list;
463 }
464 
465 char **
list_all_files(const char path[],int * len)466 list_all_files(const char path[], int *len)
467 {
468 	DIR *dir;
469 	struct dirent *d;
470 	char **list = NULL;
471 
472 	dir = os_opendir(path);
473 	if(dir == NULL)
474 	{
475 		*len = -1;
476 		return NULL;
477 	}
478 
479 	*len = 0;
480 	while((d = os_readdir(dir)) != NULL)
481 	{
482 		if(!is_builtin_dir(d->d_name))
483 		{
484 			*len = add_to_string_array(&list, *len, d->d_name);
485 		}
486 	}
487 	os_closedir(dir);
488 
489 	return list;
490 }
491 
492 char **
list_sorted_files(const char path[],int * len)493 list_sorted_files(const char path[], int *len)
494 {
495 	char **const list = list_all_files(path, len);
496 	if(*len > 0)
497 	{
498 		/* The check above is needed because *len might be negative. */
499 		safe_qsort(list, *len, sizeof(*list), &strossorter);
500 	}
501 	return list;
502 }
503 
504 int
is_regular_file(const char path[])505 is_regular_file(const char path[])
506 {
507 	char resolved_link[PATH_MAX + 1];
508 	if(is_symlink(path))
509 	{
510 		char *const symlink_base = strdup(path);
511 
512 		if(!is_root_dir(symlink_base))
513 		{
514 			remove_last_path_component(symlink_base);
515 		}
516 
517 		if(get_link_target_abs(path, symlink_base, resolved_link,
518 					sizeof(resolved_link)) != 0)
519 		{
520 			free(symlink_base);
521 			return 0;
522 		}
523 		free(symlink_base);
524 
525 		path = resolved_link;
526 	}
527 
528 	return is_regular_file_noderef(path);
529 }
530 
531 int
is_regular_file_noderef(const char path[])532 is_regular_file_noderef(const char path[])
533 {
534 #ifndef _WIN32
535 	struct stat s;
536 	return os_lstat(path, &s) == 0 && (s.st_mode & S_IFMT) == S_IFREG;
537 #else
538 	const DWORD attrs = win_get_file_attrs(path);
539 	if(attrs == INVALID_FILE_ATTRIBUTES)
540 	{
541 		return 0;
542 	}
543 	return (attrs & FILE_ATTRIBUTE_DIRECTORY) == 0UL;
544 #endif
545 }
546 
547 int
rename_file(const char src[],const char dst[])548 rename_file(const char src[], const char dst[])
549 {
550 	int error;
551 #ifdef _WIN32
552 	(void)remove(dst);
553 #endif
554 	if((error = os_rename(src, dst)))
555 	{
556 		LOG_SERROR_MSG(errno, "Rename operation failed: {%s => %s}", src, dst);
557 	}
558 	return error != 0;
559 }
560 
561 void
remove_dir_content(const char path[])562 remove_dir_content(const char path[])
563 {
564 	DIR *dir;
565 	struct dirent *d;
566 
567 	dir = os_opendir(path);
568 	if(dir == NULL)
569 	{
570 		return;
571 	}
572 
573 	while((d = os_readdir(dir)) != NULL)
574 	{
575 		if(!is_builtin_dir(d->d_name))
576 		{
577 			char *const full_path = format_str("%s/%s", path, d->d_name);
578 			if(entry_is_dir(full_path, d))
579 			{
580 				/* Attempt to make sure that we can change the directory we are
581 				 * descending into. */
582 				(void)os_chmod(full_path, 0777);
583 				remove_dir_content(full_path);
584 			}
585 			(void)remove(full_path);
586 			free(full_path);
587 		}
588 	}
589 	os_closedir(dir);
590 }
591 
592 int
entry_is_link(const char path[],const struct dirent * dentry)593 entry_is_link(const char path[], const struct dirent *dentry)
594 {
595 #ifndef _WIN32
596 	if(get_dirent_type(dentry, path) == DT_LNK)
597 	{
598 		return 1;
599 	}
600 #endif
601 	return is_symlink(path);
602 }
603 
604 int
entry_is_dir(const char full_path[],const struct dirent * dentry)605 entry_is_dir(const char full_path[], const struct dirent *dentry)
606 {
607 #ifndef _WIN32
608 	const unsigned char type = get_dirent_type(dentry, full_path);
609 	return (type == DT_UNKNOWN)
610 	     ? is_directory(full_path, 0)
611 	     : type == DT_DIR;
612 #else
613 	const DWORD MASK = FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY;
614 	const DWORD attrs = win_get_file_attrs(full_path);
615 	return attrs != INVALID_FILE_ATTRIBUTES
616 	    && (attrs & MASK) == FILE_ATTRIBUTE_DIRECTORY;
617 #endif
618 }
619 
620 int
is_dirent_targets_dir(const char full_path[],const struct dirent * d)621 is_dirent_targets_dir(const char full_path[], const struct dirent *d)
622 {
623 #ifdef _WIN32
624 	return is_dir(full_path);
625 #else
626 	const unsigned char type = get_dirent_type(d, full_path);
627 
628 	if(type == DT_UNKNOWN)
629 	{
630 		return is_dir(full_path);
631 	}
632 
633 	return  type == DT_DIR
634 	    || (type == DT_LNK && get_symlink_type(full_path) != SLT_UNKNOWN);
635 #endif
636 }
637 
638 int
is_in_subtree(const char path[],const char root[],int include_root)639 is_in_subtree(const char path[], const char root[], int include_root)
640 {
641 	/* This variable must remain on this level because it can be being pointed
642 	 * to. */
643 	char path_copy[PATH_MAX + 1];
644 	if(!include_root)
645 	{
646 		copy_str(path_copy, sizeof(path_copy), path);
647 		remove_last_path_component(path_copy);
648 		path = path_copy;
649 	}
650 
651 	char path_real[PATH_MAX + 1];
652 	if(os_realpath(path, path_real) != path_real)
653 	{
654 		return 0;
655 	}
656 
657 	char root_real[PATH_MAX + 1];
658 	if(os_realpath(root, root_real) != root_real)
659 	{
660 		return 0;
661 	}
662 
663 	return path_starts_with(path_real, root_real);
664 }
665 
666 int
are_on_the_same_fs(const char s[],const char t[])667 are_on_the_same_fs(const char s[], const char t[])
668 {
669 	struct stat s_stat, t_stat;
670 	if(os_lstat(s, &s_stat) != 0 || os_lstat(t, &t_stat) != 0)
671 	{
672 		return 0;
673 	}
674 
675 	return s_stat.st_dev == t_stat.st_dev;
676 }
677 
678 int
is_case_change(const char src[],const char dst[])679 is_case_change(const char src[], const char dst[])
680 {
681 	if(case_sensitive_paths(src))
682 	{
683 		return 0;
684 	}
685 
686 	return strcasecmp(src, dst) == 0 && strcmp(src, dst) != 0;
687 }
688 
689 int
case_sensitive_paths(const char at[])690 case_sensitive_paths(const char at[])
691 {
692 #if HAVE_DECL__PC_CASE_SENSITIVE
693 	return pathconf(at, _PC_CASE_SENSITIVE) != 0;
694 #elif !defined(_WIN32)
695 	return 1;
696 #else
697 	return 0;
698 #endif
699 }
700 
701 int
enum_dir_content(const char path[],dir_content_client_func client,void * param)702 enum_dir_content(const char path[], dir_content_client_func client, void *param)
703 {
704 #ifndef _WIN32
705 	DIR *dir;
706 	struct dirent *d;
707 
708 	if((dir = os_opendir(path)) == NULL)
709 	{
710 		return -1;
711 	}
712 
713 	while((d = os_readdir(dir)) != NULL)
714 	{
715 		if(client(d->d_name, d, param) != 0)
716 		{
717 			break;
718 		}
719 	}
720 	os_closedir(dir);
721 
722 	return 0;
723 #else
724 	char find_pat[PATH_MAX + 1];
725 	wchar_t *utf16_path;
726 	HANDLE hfind;
727 	WIN32_FIND_DATAW ffd;
728 
729 	snprintf(find_pat, sizeof(find_pat), "%s/*", path);
730 	utf16_path = utf8_to_utf16(find_pat);
731 	hfind = FindFirstFileW(utf16_path, &ffd);
732 	free(utf16_path);
733 
734 	if(hfind == INVALID_HANDLE_VALUE)
735 	{
736 		return -1;
737 	}
738 
739 	do
740 	{
741 		char *const utf8_name = utf8_from_utf16(ffd.cFileName);
742 		if(client(utf8_name, &ffd, param) != 0)
743 		{
744 			break;
745 		}
746 		free(utf8_name);
747 	}
748 	while(FindNextFileW(hfind, &ffd));
749 	FindClose(hfind);
750 
751 	return 0;
752 #endif
753 }
754 
755 int
count_dir_items(const char path[])756 count_dir_items(const char path[])
757 {
758 	DIR *dir;
759 	struct dirent *d;
760 	int count;
761 
762 	dir = os_opendir(path);
763 	if(dir == NULL)
764 	{
765 		return -1;
766 	}
767 
768 	count = 0;
769 	while((d = os_readdir(dir)) != NULL)
770 	{
771 		if(!is_builtin_dir(d->d_name))
772 		{
773 			++count;
774 		}
775 	}
776 	os_closedir(dir);
777 
778 	return count;
779 }
780 
781 char *
get_cwd(char buf[],size_t size)782 get_cwd(char buf[], size_t size)
783 {
784 	if(os_getcwd(buf, size) == NULL)
785 	{
786 		return NULL;
787 	}
788 	return buf;
789 }
790 
791 char *
save_cwd(void)792 save_cwd(void)
793 {
794 	char cwd[PATH_MAX + 1];
795 	if(os_getcwd(cwd, sizeof(cwd)) != cwd)
796 	{
797 		return NULL;
798 	}
799 	return strdup(cwd);
800 }
801 
802 void
restore_cwd(char saved_cwd[])803 restore_cwd(char saved_cwd[])
804 {
805 	if(saved_cwd != NULL)
806 	{
807 		(void)vifm_chdir(saved_cwd);
808 		free(saved_cwd);
809 	}
810 }
811 
812 #ifndef _WIN32
813 
814 /* Checks if path (dereferenced for a symbolic link) is an existing directory.
815  * Automatically dereferences symbolic links. */
816 static int
is_directory(const char path[],int dereference_links)817 is_directory(const char path[], int dereference_links)
818 {
819 	struct stat statbuf;
820 	if((dereference_links ? &os_stat : &os_lstat)(path, &statbuf) != 0)
821 	{
822 		LOG_SERROR_MSG(errno, "Can't stat \"%s\"", path);
823 		log_cwd();
824 		return 0;
825 	}
826 
827 	return S_ISDIR(statbuf.st_mode);
828 }
829 
830 #else
831 
832 int
S_ISLNK(mode_t mode)833 S_ISLNK(mode_t mode)
834 {
835 	return 0;
836 }
837 
838 int
readlink(const char * path,char * buf,size_t len)839 readlink(const char *path, char *buf, size_t len)
840 {
841 	return -1;
842 }
843 
844 int
is_on_fat_volume(const char * path)845 is_on_fat_volume(const char *path)
846 {
847 	char buf[NAME_MAX + 1];
848 	char fs[16];
849 	if(is_unc_path(path))
850 	{
851 		int i = 4, j = 0;
852 		copy_str(buf, sizeof(buf), path);
853 		while(i > 0 && buf[j] != '\0')
854 			if(buf[j++] == '/')
855 				i--;
856 		if(i == 0)
857 			buf[j - 1] = '\0';
858 	}
859 	else
860 	{
861 		strcpy(buf, "x:\\");
862 		buf[0] = path[0];
863 	}
864 	if(GetVolumeInformationA(buf, NULL, 0, NULL, NULL, NULL, fs, sizeof(fs)))
865 	{
866 		if(strncasecmp(fs, "fat", 3) == 0)
867 			return 1;
868 	}
869 	return 0;
870 }
871 
872 int
drive_exists(char letter)873 drive_exists(char letter)
874 {
875 	const wchar_t drive[] = { (wchar_t)letter, L':', L'\\', L'\0' };
876 	const int drive_type = GetDriveTypeW(drive);
877 
878 	switch(drive_type)
879 	{
880 		case DRIVE_CDROM:
881 		case DRIVE_REMOTE:
882 		case DRIVE_RAMDISK:
883 		case DRIVE_REMOVABLE:
884 		case DRIVE_FIXED:
885 			return 1;
886 
887 		case DRIVE_UNKNOWN:
888 		case DRIVE_NO_ROOT_DIR:
889 		default:
890 			return 0;
891 	}
892 }
893 
894 int
is_win_symlink(uint32_t attr,uint32_t tag)895 is_win_symlink(uint32_t attr, uint32_t tag)
896 {
897 	return (attr & FILE_ATTRIBUTE_REPARSE_POINT)
898 	    && (tag == IO_REPARSE_TAG_SYMLINK);
899 }
900 
901 #endif
902 
903 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
904 /* vim: set cinoptions+=t0 filetype=c : */
905