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