2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
10 #include "git2_util.h"
14 #include "win32/posix.h"
15 #include "win32/w32_buffer.h"
16 #include "win32/w32_util.h"
17 #include "win32/version.h"
25 #define ensure_error_set(code) do { \
26 const git_error *e = git_error_last(); \
27 if (!e || !e->message) \
28 git_error_set(e ? e->klass : GIT_ERROR_CALLBACK, \
29 "filesystem callback returned %d", code); \
32 static int dos_drive_prefix_length(const char *path
)
37 * Does it start with an ASCII letter (i.e. highest bit not set),
38 * followed by a colon?
40 if (!(0x80 & (unsigned char)*path
))
41 return *path
&& path
[1] == ':' ? 2 : 0;
44 * While drive letters must be letters of the English alphabet, it is
45 * possible to assign virtually _any_ Unicode character via `subst` as
46 * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
49 * subst ֍: %USERPROFILE%\Desktop
51 for (i
= 1; i
< 4 && (0x80 & (unsigned char)path
[i
]); i
++)
52 ; /* skip first UTF-8 character */
53 return path
[i
] == ':' ? i
+ 1 : 0;
57 static bool looks_like_network_computer_name(const char *path
, int pos
)
62 if (path
[0] != '/' || path
[1] != '/')
75 * Based on the Android implementation, BSD licensed.
76 * http://android.git.kernel.org/
78 * Copyright (C) 2008 The Android Open Source Project
79 * All rights reserved.
81 * Redistribution and use in source and binary forms, with or without
82 * modification, are permitted provided that the following conditions
84 * * Redistributions of source code must retain the above copyright
85 * notice, this list of conditions and the following disclaimer.
86 * * Redistributions in binary form must reproduce the above copyright
87 * notice, this list of conditions and the following disclaimer in
88 * the documentation and/or other materials provided with the
91 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
92 * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
93 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
94 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
95 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
96 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
97 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
98 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
99 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
100 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
101 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
104 int git_fs_path_basename_r(git_str
*buffer
, const char *path
)
106 const char *endp
, *startp
;
109 /* Empty or NULL string gets treated as "." */
110 if (path
== NULL
|| *path
== '\0') {
116 /* Strip trailing slashes */
117 endp
= path
+ strlen(path
) - 1;
118 while (endp
> path
&& *endp
== '/')
121 /* All slashes becomes "/" */
122 if (endp
== path
&& *endp
== '/') {
128 /* Find the start of the base */
130 while (startp
> path
&& *(startp
- 1) != '/')
133 /* Cast is safe because max path < max int */
134 len
= (int)(endp
- startp
+ 1);
139 if (buffer
!= NULL
&& git_str_set(buffer
, startp
, len
) < 0)
146 * Determine if the path is a Windows prefix and, if so, returns
147 * its actual length. If it is not a prefix, returns -1.
149 static int win32_prefix_length(const char *path
, int len
)
156 * Mimic unix behavior where '/.git' returns '/': 'C:/.git'
157 * will return 'C:/' here
159 if (dos_drive_prefix_length(path
) == len
)
163 * Similarly checks if we're dealing with a network computer name
164 * '//computername/.git' will return '//computername/'
166 if (looks_like_network_computer_name(path
, len
))
174 * Based on the Android implementation, BSD licensed.
175 * Check http://android.git.kernel.org/
177 int git_fs_path_dirname_r(git_str
*buffer
, const char *path
)
180 int is_prefix
= 0, len
;
182 /* Empty or NULL string gets treated as "." */
183 if (path
== NULL
|| *path
== '\0') {
189 /* Strip trailing slashes */
190 endp
= path
+ strlen(path
) - 1;
191 while (endp
> path
&& *endp
== '/')
194 if (endp
- path
+ 1 > INT_MAX
) {
195 git_error_set(GIT_ERROR_INVALID
, "path too long");
199 if ((len
= win32_prefix_length(path
, (int)(endp
- path
+ 1))) > 0) {
204 /* Find the start of the dir */
205 while (endp
> path
&& *endp
!= '/')
208 /* Either the dir is "/" or there are no slashes */
210 path
= (*endp
== '/') ? "/" : ".";
217 } while (endp
> path
&& *endp
== '/');
219 if (endp
- path
+ 1 > INT_MAX
) {
220 git_error_set(GIT_ERROR_INVALID
, "path too long");
224 if ((len
= win32_prefix_length(path
, (int)(endp
- path
+ 1))) > 0) {
229 /* Cast is safe because max path < max int */
230 len
= (int)(endp
- path
+ 1);
234 if (git_str_set(buffer
, path
, len
) < 0)
236 if (is_prefix
&& git_str_putc(buffer
, '/') < 0)
244 char *git_fs_path_dirname(const char *path
)
246 git_str buf
= GIT_STR_INIT
;
249 git_fs_path_dirname_r(&buf
, path
);
250 dirname
= git_str_detach(&buf
);
251 git_str_dispose(&buf
); /* avoid memleak if error occurs */
256 char *git_fs_path_basename(const char *path
)
258 git_str buf
= GIT_STR_INIT
;
261 git_fs_path_basename_r(&buf
, path
);
262 basename
= git_str_detach(&buf
);
263 git_str_dispose(&buf
); /* avoid memleak if error occurs */
268 size_t git_fs_path_basename_offset(git_str
*buffer
)
272 if (!buffer
|| buffer
->size
<= 0)
275 slash
= git_str_rfind_next(buffer
, '/');
277 if (slash
>= 0 && buffer
->ptr
[slash
] == '/')
278 return (size_t)(slash
+ 1);
283 int git_fs_path_root(const char *path
)
285 int offset
= 0, prefix_len
;
287 /* Does the root of the path look like a windows drive ? */
288 if ((prefix_len
= dos_drive_prefix_length(path
)))
289 offset
+= prefix_len
;
292 /* Are we dealing with a windows network path? */
293 else if ((path
[0] == '/' && path
[1] == '/' && path
[2] != '/') ||
294 (path
[0] == '\\' && path
[1] == '\\' && path
[2] != '\\'))
298 /* Skip the computer name segment */
299 while (path
[offset
] && path
[offset
] != '/' && path
[offset
] != '\\')
303 if (path
[offset
] == '\\')
307 if (path
[offset
] == '/')
310 return -1; /* Not a real error - signals that path is not rooted */
313 static void path_trim_slashes(git_str
*path
)
315 int ceiling
= git_fs_path_root(path
->ptr
) + 1;
320 while (path
->size
> (size_t)ceiling
) {
321 if (path
->ptr
[path
->size
-1] != '/')
324 path
->ptr
[path
->size
-1] = '\0';
329 int git_fs_path_join_unrooted(
330 git_str
*path_out
, const char *path
, const char *base
, ssize_t
*root_at
)
334 GIT_ASSERT_ARG(path_out
);
335 GIT_ASSERT_ARG(path
);
337 root
= (ssize_t
)git_fs_path_root(path
);
339 if (base
!= NULL
&& root
< 0) {
340 if (git_str_joinpath(path_out
, base
, path
) < 0)
343 root
= (ssize_t
)strlen(base
);
345 if (git_str_sets(path_out
, path
) < 0)
351 git_fs_path_equal_or_prefixed(base
, path
, &root
);
360 void git_fs_path_squash_slashes(git_str
*path
)
367 for (p
= path
->ptr
, q
= path
->ptr
; *q
; p
++, q
++) {
370 while (*q
== '/' && *(q
+1) == '/') {
379 int git_fs_path_prettify(git_str
*path_out
, const char *path
, const char *base
)
381 char buf
[GIT_PATH_MAX
];
383 GIT_ASSERT_ARG(path_out
);
384 GIT_ASSERT_ARG(path
);
386 /* construct path if needed */
387 if (base
!= NULL
&& git_fs_path_root(path
) < 0) {
388 if (git_str_joinpath(path_out
, base
, path
) < 0)
390 path
= path_out
->ptr
;
393 if (p_realpath(path
, buf
) == NULL
) {
394 /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */
395 int error
= (errno
== ENOENT
|| errno
== ENOTDIR
) ? GIT_ENOTFOUND
: -1;
396 git_error_set(GIT_ERROR_OS
, "failed to resolve path '%s'", path
);
398 git_str_clear(path_out
);
403 return git_str_sets(path_out
, buf
);
406 int git_fs_path_prettify_dir(git_str
*path_out
, const char *path
, const char *base
)
408 int error
= git_fs_path_prettify(path_out
, path
, base
);
409 return (error
< 0) ? error
: git_fs_path_to_dir(path_out
);
412 int git_fs_path_to_dir(git_str
*path
)
414 if (path
->asize
> 0 &&
415 git_str_len(path
) > 0 &&
416 path
->ptr
[git_str_len(path
) - 1] != '/')
417 git_str_putc(path
, '/');
419 return git_str_oom(path
) ? -1 : 0;
422 void git_fs_path_string_to_dir(char *path
, size_t size
)
424 size_t end
= strlen(path
);
426 if (end
&& path
[end
- 1] != '/' && end
< size
) {
428 path
[end
+ 1] = '\0';
432 int git__percent_decode(git_str
*decoded_out
, const char *input
)
436 GIT_ASSERT_ARG(decoded_out
);
437 GIT_ASSERT_ARG(input
);
439 len
= (int)strlen(input
);
440 git_str_clear(decoded_out
);
442 for(i
= 0; i
< len
; i
++)
452 hi
= git__fromhex(input
[i
+ 1]);
453 lo
= git__fromhex(input
[i
+ 2]);
455 if (hi
< 0 || lo
< 0)
458 c
= (char)(hi
<< 4 | lo
);
462 if (git_str_putc(decoded_out
, c
) < 0)
469 static int error_invalid_local_file_uri(const char *uri
)
471 git_error_set(GIT_ERROR_CONFIG
, "'%s' is not a valid local file URI", uri
);
475 static int local_file_url_prefixlen(const char *file_url
)
479 if (git__prefixcmp(file_url
, "file://") == 0) {
480 if (file_url
[7] == '/')
482 else if (git__prefixcmp(file_url
+ 7, "localhost/") == 0)
489 bool git_fs_path_is_local_file_url(const char *file_url
)
491 return (local_file_url_prefixlen(file_url
) > 0);
494 int git_fs_path_fromurl(git_str
*local_path_out
, const char *file_url
)
498 GIT_ASSERT_ARG(local_path_out
);
499 GIT_ASSERT_ARG(file_url
);
501 if ((offset
= local_file_url_prefixlen(file_url
)) < 0 ||
502 file_url
[offset
] == '\0' || file_url
[offset
] == '/')
503 return error_invalid_local_file_uri(file_url
);
506 offset
--; /* A *nix absolute path starts with a forward slash */
509 git_str_clear(local_path_out
);
510 return git__percent_decode(local_path_out
, file_url
+ offset
);
513 int git_fs_path_walk_up(
516 int (*cb
)(void *data
, const char *),
521 ssize_t stop
= 0, scan
;
524 GIT_ASSERT_ARG(path
);
527 if (ceiling
!= NULL
) {
528 if (git__prefixcmp(path
->ptr
, ceiling
) == 0)
529 stop
= (ssize_t
)strlen(ceiling
);
531 stop
= git_str_len(path
);
533 scan
= git_str_len(path
);
535 /* empty path: yield only once */
537 error
= cb(data
, "");
539 ensure_error_set(error
);
543 iter
.ptr
= path
->ptr
;
544 iter
.size
= git_str_len(path
);
545 iter
.asize
= path
->asize
;
547 while (scan
>= stop
) {
548 error
= cb(data
, iter
.ptr
);
549 iter
.ptr
[scan
] = oldc
;
552 ensure_error_set(error
);
556 scan
= git_str_rfind_next(&iter
, '/');
559 oldc
= iter
.ptr
[scan
];
561 iter
.ptr
[scan
] = '\0';
566 iter
.ptr
[scan
] = oldc
;
568 /* relative path: yield for the last component */
569 if (!error
&& stop
== 0 && iter
.ptr
[0] != '/') {
570 error
= cb(data
, "");
572 ensure_error_set(error
);
578 bool git_fs_path_exists(const char *path
)
580 GIT_ASSERT_ARG_WITH_RETVAL(path
, false);
581 return p_access(path
, F_OK
) == 0;
584 bool git_fs_path_isdir(const char *path
)
587 if (p_stat(path
, &st
) < 0)
590 return S_ISDIR(st
.st_mode
) != 0;
593 bool git_fs_path_isfile(const char *path
)
597 GIT_ASSERT_ARG_WITH_RETVAL(path
, false);
598 if (p_stat(path
, &st
) < 0)
601 return S_ISREG(st
.st_mode
) != 0;
604 bool git_fs_path_islink(const char *path
)
608 GIT_ASSERT_ARG_WITH_RETVAL(path
, false);
609 if (p_lstat(path
, &st
) < 0)
612 return S_ISLNK(st
.st_mode
) != 0;
617 bool git_fs_path_is_empty_dir(const char *path
)
619 git_win32_path filter_w
;
622 if (git_win32__findfirstfile_filter(filter_w
, path
)) {
623 WIN32_FIND_DATAW findData
;
624 HANDLE hFind
= FindFirstFileW(filter_w
, &findData
);
626 /* FindFirstFile will fail if there are no children to the given
627 * path, which can happen if the given path is a file (and obviously
628 * has no children) or if the given path is an empty mount point.
629 * (Most directories have at least directory entries '.' and '..',
630 * but ridiculously another volume mounted in another drive letter's
631 * path space do not, and thus have nothing to enumerate.) If
632 * FindFirstFile fails, check if this is a directory-like thing
635 if (hFind
== INVALID_HANDLE_VALUE
)
636 return git_fs_path_isdir(path
);
638 /* If the find handle was created successfully, then it's a directory */
642 /* Allow the enumeration to return . and .. and still be considered
643 * empty. In the special case of drive roots (i.e. C:\) where . and
644 * .. do not occur, we can still consider the path to be an empty
645 * directory if there's nothing there. */
646 if (!git_fs_path_is_dot_or_dotdotW(findData
.cFileName
)) {
650 } while (FindNextFileW(hFind
, &findData
));
660 static int path_found_entry(void *payload
, git_str
*path
)
663 return !git_fs_path_is_dot_or_dotdot(path
->ptr
);
666 bool git_fs_path_is_empty_dir(const char *path
)
669 git_str dir
= GIT_STR_INIT
;
671 if (!git_fs_path_isdir(path
))
674 if ((error
= git_str_sets(&dir
, path
)) != 0)
677 error
= git_fs_path_direach(&dir
, 0, path_found_entry
, NULL
);
679 git_str_dispose(&dir
);
686 int git_fs_path_set_error(int errno_value
, const char *path
, const char *action
)
688 switch (errno_value
) {
691 git_error_set(GIT_ERROR_OS
, "could not find '%s' to %s", path
, action
);
692 return GIT_ENOTFOUND
;
696 git_error_set(GIT_ERROR_OS
, "invalid path for filesystem '%s'", path
);
697 return GIT_EINVALIDSPEC
;
700 git_error_set(GIT_ERROR_OS
, "failed %s - '%s' already exists", action
, path
);
704 git_error_set(GIT_ERROR_OS
, "failed %s - '%s' is locked", action
, path
);
708 git_error_set(GIT_ERROR_OS
, "could not %s '%s'", action
, path
);
713 int git_fs_path_lstat(const char *path
, struct stat
*st
)
715 if (p_lstat(path
, st
) == 0)
718 return git_fs_path_set_error(errno
, path
, "stat");
721 static bool _check_dir_contents(
724 bool (*predicate
)(const char *))
727 size_t dir_size
= git_str_len(dir
);
728 size_t sub_size
= strlen(sub
);
731 /* leave base valid even if we could not make space for subdir */
732 if (GIT_ADD_SIZET_OVERFLOW(&alloc_size
, dir_size
, sub_size
) ||
733 GIT_ADD_SIZET_OVERFLOW(&alloc_size
, alloc_size
, 2) ||
734 git_str_try_grow(dir
, alloc_size
, false) < 0)
738 if (git_str_joinpath(dir
, dir
->ptr
, sub
) < 0)
741 result
= predicate(dir
->ptr
);
744 git_str_truncate(dir
, dir_size
);
748 bool git_fs_path_contains(git_str
*dir
, const char *item
)
750 return _check_dir_contents(dir
, item
, &git_fs_path_exists
);
753 bool git_fs_path_contains_dir(git_str
*base
, const char *subdir
)
755 return _check_dir_contents(base
, subdir
, &git_fs_path_isdir
);
758 bool git_fs_path_contains_file(git_str
*base
, const char *file
)
760 return _check_dir_contents(base
, file
, &git_fs_path_isfile
);
763 int git_fs_path_find_dir(git_str
*dir
)
766 char buf
[GIT_PATH_MAX
];
768 if (p_realpath(dir
->ptr
, buf
) != NULL
)
769 error
= git_str_sets(dir
, buf
);
771 /* call dirname if this is not a directory */
772 if (!error
) /* && git_fs_path_isdir(dir->ptr) == false) */
773 error
= (git_fs_path_dirname_r(dir
, dir
->ptr
) < 0) ? -1 : 0;
776 error
= git_fs_path_to_dir(dir
);
781 int git_fs_path_resolve_relative(git_str
*path
, size_t ceiling
)
783 char *base
, *to
, *from
, *next
;
786 GIT_ERROR_CHECK_ALLOC_STR(path
);
788 if (ceiling
> path
->size
)
789 ceiling
= path
->size
;
791 /* recognize drive prefixes, etc. that should not be backed over */
793 ceiling
= git_fs_path_root(path
->ptr
) + 1;
795 /* recognize URL prefixes that should not be backed over */
797 for (next
= path
->ptr
; *next
&& git__isalpha(*next
); ++next
);
798 if (next
[0] == ':' && next
[1] == '/' && next
[2] == '/')
799 ceiling
= (next
+ 3) - path
->ptr
;
802 base
= to
= from
= path
->ptr
+ ceiling
;
805 for (next
= from
; *next
&& *next
!= '/'; ++next
);
809 if (len
== 1 && from
[0] == '.')
810 /* do nothing with singleton dot */;
812 else if (len
== 2 && from
[0] == '.' && from
[1] == '.') {
813 /* error out if trying to up one from a hard base */
814 if (to
== base
&& ceiling
!= 0) {
815 git_error_set(GIT_ERROR_INVALID
,
816 "cannot strip root component off url");
820 /* no more path segments to strip,
821 * use '../' as a new base path */
827 memmove(to
, from
, len
);
830 /* this is now the base, can't back up from a
834 /* back up a path segment */
835 while (to
> base
&& to
[-1] == '/') to
--;
836 while (to
> base
&& to
[-1] != '/') to
--;
839 if (*next
== '/' && *from
!= '/')
843 memmove(to
, from
, len
);
850 while (*from
== '/') from
++;
855 path
->size
= to
- path
->ptr
;
860 int git_fs_path_apply_relative(git_str
*target
, const char *relpath
)
862 return git_str_joinpath(target
, git_str_cstr(target
), relpath
) ||
863 git_fs_path_resolve_relative(target
, 0);
867 const char *name1
, size_t len1
, int isdir1
,
868 const char *name2
, size_t len2
, int isdir2
,
869 int (*compare
)(const char *, const char *, size_t))
871 unsigned char c1
, c2
;
872 size_t len
= len1
< len2
? len1
: len2
;
875 cmp
= compare(name1
, name2
, len
);
882 if (c1
== '\0' && isdir1
)
885 if (c2
== '\0' && isdir2
)
888 return (c1
< c2
) ? -1 : (c1
> c2
) ? 1 : 0;
891 size_t git_fs_path_common_dirlen(const char *one
, const char *two
)
893 const char *p
, *q
, *dirsep
= NULL
;
895 for (p
= one
, q
= two
; *p
&& *q
; p
++, q
++) {
896 if (*p
== '/' && *q
== '/')
902 return dirsep
? (dirsep
- one
) + 1 : 0;
905 int git_fs_path_make_relative(git_str
*path
, const char *parent
)
907 const char *p
, *q
, *p_dirsep
, *q_dirsep
;
908 size_t plen
= path
->size
, newlen
, alloclen
, depth
= 1, i
, offset
;
910 for (p_dirsep
= p
= path
->ptr
, q_dirsep
= q
= parent
; *p
&& *q
; p
++, q
++) {
911 if (*p
== '/' && *q
== '/') {
919 /* need at least 1 common path segment */
920 if ((p_dirsep
== path
->ptr
|| q_dirsep
== parent
) &&
921 (*p_dirsep
!= '/' || *q_dirsep
!= '/')) {
922 git_error_set(GIT_ERROR_INVALID
,
923 "%s is not a parent of %s", parent
, path
->ptr
);
924 return GIT_ENOTFOUND
;
927 if (*p
== '/' && !*q
)
929 else if (!*p
&& *q
== '/')
932 return git_str_clear(path
), 0;
938 plen
-= (p
- path
->ptr
);
941 return git_str_set(path
, p
, plen
);
943 for (; (q
= strchr(q
, '/')) && *(q
+ 1); q
++)
946 GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen
, depth
, 3);
947 GIT_ERROR_CHECK_ALLOC_ADD(&newlen
, newlen
, plen
);
949 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen
, newlen
, 1);
951 /* save the offset as we might realllocate the pointer */
952 offset
= p
- path
->ptr
;
953 if (git_str_try_grow(path
, alloclen
, 1) < 0)
955 p
= path
->ptr
+ offset
;
957 memmove(path
->ptr
+ (depth
* 3), p
, plen
+ 1);
959 for (i
= 0; i
< depth
; i
++)
960 memcpy(path
->ptr
+ (i
* 3), "../", 3);
966 bool git_fs_path_has_non_ascii(const char *path
, size_t pathlen
)
968 const uint8_t *scan
= (const uint8_t *)path
, *end
;
970 for (end
= scan
+ pathlen
; scan
< end
; ++scan
)
979 int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t
*ic
)
981 git_str_init(&ic
->buf
, 0);
982 ic
->map
= iconv_open(GIT_PATH_REPO_ENCODING
, GIT_PATH_NATIVE_ENCODING
);
986 void git_fs_path_iconv_clear(git_fs_path_iconv_t
*ic
)
989 if (ic
->map
!= (iconv_t
)-1)
990 iconv_close(ic
->map
);
991 git_str_dispose(&ic
->buf
);
995 int git_fs_path_iconv(git_fs_path_iconv_t
*ic
, const char **in
, size_t *inlen
)
997 char *nfd
= (char*)*in
, *nfc
;
998 size_t nfdlen
= *inlen
, nfclen
, wantlen
= nfdlen
, alloclen
, rv
;
1001 if (!ic
|| ic
->map
== (iconv_t
)-1 ||
1002 !git_fs_path_has_non_ascii(*in
, *inlen
))
1005 git_str_clear(&ic
->buf
);
1008 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen
, wantlen
, 1);
1009 if (git_str_grow(&ic
->buf
, alloclen
) < 0)
1012 nfc
= ic
->buf
.ptr
+ ic
->buf
.size
;
1013 nfclen
= ic
->buf
.asize
- ic
->buf
.size
;
1015 rv
= iconv(ic
->map
, &nfd
, &nfdlen
, &nfc
, &nfclen
);
1017 ic
->buf
.size
= (nfc
- ic
->buf
.ptr
);
1019 if (rv
!= (size_t)-1)
1022 /* if we cannot convert the data (probably because iconv thinks
1023 * it is not valid UTF-8 source data), then use original data
1028 /* make space for 2x the remaining data to be converted
1029 * (with per retry overhead to avoid infinite loops)
1031 wantlen
= ic
->buf
.size
+ max(nfclen
, nfdlen
) * 2 + (size_t)(retry
* 4);
1037 ic
->buf
.ptr
[ic
->buf
.size
] = '\0';
1040 *inlen
= ic
->buf
.size
;
1045 git_error_set(GIT_ERROR_OS
, "unable to convert unicode path data");
1049 static const char *nfc_file
= "\xC3\x85\x73\x74\x72\xC3\xB6\x6D";
1050 static const char *nfd_file
= "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D";
1052 /* Check if the platform is decomposing unicode data for us. We will
1053 * emulate core Git and prefer to use precomposed unicode data internally
1054 * on these platforms, composing the decomposed unicode on the fly.
1056 * This mainly happens on the Mac where HDFS stores filenames as
1057 * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
1058 * return decomposed unicode from readdir() even when the actual
1059 * filesystem is storing precomposed unicode.
1061 bool git_fs_path_does_decompose_unicode(const char *root
)
1063 git_str nfc_path
= GIT_STR_INIT
;
1064 git_str nfd_path
= GIT_STR_INIT
;
1066 bool found_decomposed
= false;
1068 const char *trailer
;
1070 /* Create a file using a precomposed path and then try to find it
1071 * using the decomposed name. If the lookup fails, then we will mark
1072 * that we should precompose unicode for this repository.
1074 if (git_str_joinpath(&nfc_path
, root
, nfc_file
) < 0)
1077 /* record original path length before trailer */
1078 orig_len
= nfc_path
.size
;
1080 if ((fd
= git_futils_mktmp(&nfc_path
, nfc_path
.ptr
, 0666)) < 0)
1084 trailer
= nfc_path
.ptr
+ orig_len
;
1086 /* try to look up as NFD path */
1087 if (git_str_joinpath(&nfd_path
, root
, nfd_file
) < 0 ||
1088 git_str_puts(&nfd_path
, trailer
) < 0)
1091 found_decomposed
= git_fs_path_exists(nfd_path
.ptr
);
1093 /* remove temporary file (using original precomposed path) */
1094 (void)p_unlink(nfc_path
.ptr
);
1097 git_str_dispose(&nfc_path
);
1098 git_str_dispose(&nfd_path
);
1099 return found_decomposed
;
1104 bool git_fs_path_does_decompose_unicode(const char *root
)
1112 #if defined(__sun) || defined(__GNU__)
1113 typedef char path_dirent_data
[sizeof(struct dirent
) + FILENAME_MAX
+ 1];
1115 typedef struct dirent path_dirent_data
;
1118 int git_fs_path_direach(
1121 int (*fn
)(void *, git_str
*),
1129 #ifdef GIT_USE_ICONV
1130 git_fs_path_iconv_t ic
= GIT_PATH_ICONV_INIT
;
1135 if (git_fs_path_to_dir(path
) < 0)
1138 wd_len
= git_str_len(path
);
1140 if ((dir
= opendir(path
->ptr
)) == NULL
) {
1141 git_error_set(GIT_ERROR_OS
, "failed to open directory '%s'", path
->ptr
);
1142 if (errno
== ENOENT
)
1143 return GIT_ENOTFOUND
;
1148 #ifdef GIT_USE_ICONV
1149 if ((flags
& GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE
) != 0)
1150 (void)git_fs_path_iconv_init_precompose(&ic
);
1153 while ((de
= readdir(dir
)) != NULL
) {
1154 const char *de_path
= de
->d_name
;
1155 size_t de_len
= strlen(de_path
);
1157 if (git_fs_path_is_dot_or_dotdot(de_path
))
1160 #ifdef GIT_USE_ICONV
1161 if ((error
= git_fs_path_iconv(&ic
, &de_path
, &de_len
)) < 0)
1165 if ((error
= git_str_put(path
, de_path
, de_len
)) < 0)
1169 error
= fn(arg
, path
);
1171 git_str_truncate(path
, wd_len
); /* restore path */
1173 /* Only set our own error if the callback did not set one already */
1175 if (!git_error_last())
1176 ensure_error_set(error
);
1184 #ifdef GIT_USE_ICONV
1185 git_fs_path_iconv_clear(&ic
);
1191 #if defined(GIT_WIN32) && !defined(__MINGW32__)
1193 /* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
1196 #ifndef FIND_FIRST_EX_LARGE_FETCH
1197 # define FIND_FIRST_EX_LARGE_FETCH 2
1200 int git_fs_path_diriter_init(
1201 git_fs_path_diriter
*diriter
,
1205 git_win32_path path_filter
;
1207 static int is_win7_or_later
= -1;
1208 if (is_win7_or_later
< 0)
1209 is_win7_or_later
= git_has_win32_version(6, 1, 0);
1211 GIT_ASSERT_ARG(diriter
);
1212 GIT_ASSERT_ARG(path
);
1214 memset(diriter
, 0, sizeof(git_fs_path_diriter
));
1215 diriter
->handle
= INVALID_HANDLE_VALUE
;
1217 if (git_str_puts(&diriter
->path_utf8
, path
) < 0)
1220 path_trim_slashes(&diriter
->path_utf8
);
1222 if (diriter
->path_utf8
.size
== 0) {
1223 git_error_set(GIT_ERROR_FILESYSTEM
, "could not open directory '%s'", path
);
1227 if ((diriter
->parent_len
= git_win32_path_from_utf8(diriter
->path
, diriter
->path_utf8
.ptr
)) < 0 ||
1228 !git_win32__findfirstfile_filter(path_filter
, diriter
->path_utf8
.ptr
)) {
1229 git_error_set(GIT_ERROR_OS
, "could not parse the directory path '%s'", path
);
1233 diriter
->handle
= FindFirstFileExW(
1235 is_win7_or_later
? FindExInfoBasic
: FindExInfoStandard
,
1237 FindExSearchNameMatch
,
1239 is_win7_or_later
? FIND_FIRST_EX_LARGE_FETCH
: 0);
1241 if (diriter
->handle
== INVALID_HANDLE_VALUE
) {
1242 git_error_set(GIT_ERROR_OS
, "could not open directory '%s'", path
);
1246 diriter
->parent_utf8_len
= diriter
->path_utf8
.size
;
1247 diriter
->flags
= flags
;
1251 static int diriter_update_paths(git_fs_path_diriter
*diriter
)
1253 size_t filename_len
, path_len
;
1255 filename_len
= wcslen(diriter
->current
.cFileName
);
1257 if (GIT_ADD_SIZET_OVERFLOW(&path_len
, diriter
->parent_len
, filename_len
) ||
1258 GIT_ADD_SIZET_OVERFLOW(&path_len
, path_len
, 2))
1261 if (path_len
> GIT_WIN_PATH_UTF16
) {
1262 git_error_set(GIT_ERROR_FILESYSTEM
,
1263 "invalid path '%.*ls\\%ls' (path too long)",
1264 diriter
->parent_len
, diriter
->path
, diriter
->current
.cFileName
);
1268 diriter
->path
[diriter
->parent_len
] = L
'\\';
1269 memcpy(&diriter
->path
[diriter
->parent_len
+1],
1270 diriter
->current
.cFileName
, filename_len
* sizeof(wchar_t));
1271 diriter
->path
[path_len
-1] = L
'\0';
1273 git_str_truncate(&diriter
->path_utf8
, diriter
->parent_utf8_len
);
1275 if (diriter
->parent_utf8_len
> 0 &&
1276 diriter
->path_utf8
.ptr
[diriter
->parent_utf8_len
-1] != '/')
1277 git_str_putc(&diriter
->path_utf8
, '/');
1279 git_str_put_w(&diriter
->path_utf8
, diriter
->current
.cFileName
, filename_len
);
1281 if (git_str_oom(&diriter
->path_utf8
))
1287 int git_fs_path_diriter_next(git_fs_path_diriter
*diriter
)
1289 bool skip_dot
= !(diriter
->flags
& GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT
);
1292 /* Our first time through, we already have the data from
1293 * FindFirstFileW. Use it, otherwise get the next file.
1295 if (!diriter
->needs_next
)
1296 diriter
->needs_next
= 1;
1297 else if (!FindNextFileW(diriter
->handle
, &diriter
->current
))
1298 return GIT_ITEROVER
;
1299 } while (skip_dot
&& git_fs_path_is_dot_or_dotdotW(diriter
->current
.cFileName
));
1301 if (diriter_update_paths(diriter
) < 0)
1307 int git_fs_path_diriter_filename(
1310 git_fs_path_diriter
*diriter
)
1312 GIT_ASSERT_ARG(out
);
1313 GIT_ASSERT_ARG(out_len
);
1314 GIT_ASSERT_ARG(diriter
);
1315 GIT_ASSERT(diriter
->path_utf8
.size
> diriter
->parent_utf8_len
);
1317 *out
= &diriter
->path_utf8
.ptr
[diriter
->parent_utf8_len
+1];
1318 *out_len
= diriter
->path_utf8
.size
- diriter
->parent_utf8_len
- 1;
1322 int git_fs_path_diriter_fullpath(
1325 git_fs_path_diriter
*diriter
)
1327 GIT_ASSERT_ARG(out
);
1328 GIT_ASSERT_ARG(out_len
);
1329 GIT_ASSERT_ARG(diriter
);
1331 *out
= diriter
->path_utf8
.ptr
;
1332 *out_len
= diriter
->path_utf8
.size
;
1336 int git_fs_path_diriter_stat(struct stat
*out
, git_fs_path_diriter
*diriter
)
1338 GIT_ASSERT_ARG(out
);
1339 GIT_ASSERT_ARG(diriter
);
1341 return git_win32__file_attribute_to_stat(out
,
1342 (WIN32_FILE_ATTRIBUTE_DATA
*)&diriter
->current
,
1346 void git_fs_path_diriter_free(git_fs_path_diriter
*diriter
)
1348 if (diriter
== NULL
)
1351 git_str_dispose(&diriter
->path_utf8
);
1353 if (diriter
->handle
!= INVALID_HANDLE_VALUE
) {
1354 FindClose(diriter
->handle
);
1355 diriter
->handle
= INVALID_HANDLE_VALUE
;
1361 int git_fs_path_diriter_init(
1362 git_fs_path_diriter
*diriter
,
1366 GIT_ASSERT_ARG(diriter
);
1367 GIT_ASSERT_ARG(path
);
1369 memset(diriter
, 0, sizeof(git_fs_path_diriter
));
1371 if (git_str_puts(&diriter
->path
, path
) < 0)
1374 path_trim_slashes(&diriter
->path
);
1376 if (diriter
->path
.size
== 0) {
1377 git_error_set(GIT_ERROR_FILESYSTEM
, "could not open directory '%s'", path
);
1381 if ((diriter
->dir
= opendir(diriter
->path
.ptr
)) == NULL
) {
1382 git_str_dispose(&diriter
->path
);
1384 git_error_set(GIT_ERROR_OS
, "failed to open directory '%s'", path
);
1388 #ifdef GIT_USE_ICONV
1389 if ((flags
& GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE
) != 0)
1390 (void)git_fs_path_iconv_init_precompose(&diriter
->ic
);
1393 diriter
->parent_len
= diriter
->path
.size
;
1394 diriter
->flags
= flags
;
1399 int git_fs_path_diriter_next(git_fs_path_diriter
*diriter
)
1402 const char *filename
;
1403 size_t filename_len
;
1404 bool skip_dot
= !(diriter
->flags
& GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT
);
1407 GIT_ASSERT_ARG(diriter
);
1412 if ((de
= readdir(diriter
->dir
)) == NULL
) {
1414 return GIT_ITEROVER
;
1416 git_error_set(GIT_ERROR_OS
,
1417 "could not read directory '%s'", diriter
->path
.ptr
);
1420 } while (skip_dot
&& git_fs_path_is_dot_or_dotdot(de
->d_name
));
1422 filename
= de
->d_name
;
1423 filename_len
= strlen(filename
);
1425 #ifdef GIT_USE_ICONV
1426 if ((diriter
->flags
& GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE
) != 0 &&
1427 (error
= git_fs_path_iconv(&diriter
->ic
, &filename
, &filename_len
)) < 0)
1431 git_str_truncate(&diriter
->path
, diriter
->parent_len
);
1433 if (diriter
->parent_len
> 0 &&
1434 diriter
->path
.ptr
[diriter
->parent_len
-1] != '/')
1435 git_str_putc(&diriter
->path
, '/');
1437 git_str_put(&diriter
->path
, filename
, filename_len
);
1439 if (git_str_oom(&diriter
->path
))
1445 int git_fs_path_diriter_filename(
1448 git_fs_path_diriter
*diriter
)
1450 GIT_ASSERT_ARG(out
);
1451 GIT_ASSERT_ARG(out_len
);
1452 GIT_ASSERT_ARG(diriter
);
1453 GIT_ASSERT(diriter
->path
.size
> diriter
->parent_len
);
1455 *out
= &diriter
->path
.ptr
[diriter
->parent_len
+1];
1456 *out_len
= diriter
->path
.size
- diriter
->parent_len
- 1;
1460 int git_fs_path_diriter_fullpath(
1463 git_fs_path_diriter
*diriter
)
1465 GIT_ASSERT_ARG(out
);
1466 GIT_ASSERT_ARG(out_len
);
1467 GIT_ASSERT_ARG(diriter
);
1469 *out
= diriter
->path
.ptr
;
1470 *out_len
= diriter
->path
.size
;
1474 int git_fs_path_diriter_stat(struct stat
*out
, git_fs_path_diriter
*diriter
)
1476 GIT_ASSERT_ARG(out
);
1477 GIT_ASSERT_ARG(diriter
);
1479 return git_fs_path_lstat(diriter
->path
.ptr
, out
);
1482 void git_fs_path_diriter_free(git_fs_path_diriter
*diriter
)
1484 if (diriter
== NULL
)
1488 closedir(diriter
->dir
);
1489 diriter
->dir
= NULL
;
1492 #ifdef GIT_USE_ICONV
1493 git_fs_path_iconv_clear(&diriter
->ic
);
1496 git_str_dispose(&diriter
->path
);
1501 int git_fs_path_dirload(
1502 git_vector
*contents
,
1507 git_fs_path_diriter iter
= GIT_FS_PATH_DIRITER_INIT
;
1513 GIT_ASSERT_ARG(contents
);
1514 GIT_ASSERT_ARG(path
);
1516 if ((error
= git_fs_path_diriter_init(&iter
, path
, flags
)) < 0)
1519 while ((error
= git_fs_path_diriter_next(&iter
)) == 0) {
1520 if ((error
= git_fs_path_diriter_fullpath(&name
, &name_len
, &iter
)) < 0)
1523 GIT_ASSERT(name_len
> prefix_len
);
1525 dup
= git__strndup(name
+ prefix_len
, name_len
- prefix_len
);
1526 GIT_ERROR_CHECK_ALLOC(dup
);
1528 if ((error
= git_vector_insert(contents
, dup
)) < 0)
1532 if (error
== GIT_ITEROVER
)
1535 git_fs_path_diriter_free(&iter
);
1539 int git_fs_path_from_url_or_path(git_str
*local_path_out
, const char *url_or_path
)
1541 if (git_fs_path_is_local_file_url(url_or_path
))
1542 return git_fs_path_fromurl(local_path_out
, url_or_path
);
1544 return git_str_sets(local_path_out
, url_or_path
);
1547 /* Reject paths like AUX or COM1, or those versions that end in a dot or
1548 * colon. ("AUX." or "AUX:")
1550 GIT_INLINE(bool) validate_dospath(
1551 const char *component
,
1553 const char dospath
[3],
1556 size_t last
= trailing_num
? 4 : 3;
1558 if (len
< last
|| git__strncasecmp(component
, dospath
, 3) != 0)
1561 if (trailing_num
&& (component
[3] < '1' || component
[3] > '9'))
1564 return (len
> last
&&
1565 component
[last
] != '.' &&
1566 component
[last
] != ':');
1569 GIT_INLINE(bool) validate_char(unsigned char c
, unsigned int flags
)
1571 if ((flags
& GIT_FS_PATH_REJECT_BACKSLASH
) && c
== '\\')
1574 if ((flags
& GIT_FS_PATH_REJECT_SLASH
) && c
== '/')
1577 if (flags
& GIT_FS_PATH_REJECT_NT_CHARS
) {
1597 * We fundamentally don't like some paths when dealing with user-inputted
1598 * strings (to avoid escaping a sandbox): we don't want dot or dot-dot
1599 * anywhere, we want to avoid writing weird paths on Windows that can't
1600 * be handled by tools that use the non-\\?\ APIs, we don't want slashes
1601 * or double slashes at the end of paths that can make them ambiguous.
1603 * For checkout, we don't want to recurse into ".git" either.
1605 static bool validate_component(
1606 const char *component
,
1611 return !(flags
& GIT_FS_PATH_REJECT_EMPTY_COMPONENT
);
1613 if ((flags
& GIT_FS_PATH_REJECT_TRAVERSAL
) &&
1614 len
== 1 && component
[0] == '.')
1617 if ((flags
& GIT_FS_PATH_REJECT_TRAVERSAL
) &&
1618 len
== 2 && component
[0] == '.' && component
[1] == '.')
1621 if ((flags
& GIT_FS_PATH_REJECT_TRAILING_DOT
) &&
1622 component
[len
- 1] == '.')
1625 if ((flags
& GIT_FS_PATH_REJECT_TRAILING_SPACE
) &&
1626 component
[len
- 1] == ' ')
1629 if ((flags
& GIT_FS_PATH_REJECT_TRAILING_COLON
) &&
1630 component
[len
- 1] == ':')
1633 if (flags
& GIT_FS_PATH_REJECT_DOS_PATHS
) {
1634 if (!validate_dospath(component
, len
, "CON", false) ||
1635 !validate_dospath(component
, len
, "PRN", false) ||
1636 !validate_dospath(component
, len
, "AUX", false) ||
1637 !validate_dospath(component
, len
, "NUL", false) ||
1638 !validate_dospath(component
, len
, "COM", true) ||
1639 !validate_dospath(component
, len
, "LPT", true))
1647 GIT_INLINE(bool) validate_length(
1650 size_t utf8_char_len
)
1655 return (utf8_char_len
<= MAX_PATH
);
1659 bool git_fs_path_str_is_valid_ext(
1660 const git_str
*path
,
1662 bool (*validate_char_cb
)(char ch
, void *payload
),
1663 bool (*validate_component_cb
)(const char *component
, size_t len
, void *payload
),
1664 bool (*validate_length_cb
)(const char *path
, size_t len
, size_t utf8_char_len
),
1667 const char *start
, *c
;
1673 for (start
= c
= path
->ptr
; *c
&& len
< path
->size
; c
++, len
++) {
1674 if (!validate_char(*c
, flags
))
1677 if (validate_char_cb
&& !validate_char_cb(*c
, payload
))
1683 if (!validate_component(start
, (c
- start
), flags
))
1686 if (validate_component_cb
&&
1687 !validate_component_cb(start
, (c
- start
), payload
))
1694 * We want to support paths specified as either `const char *`
1695 * or `git_str *`; we pass size as `SIZE_MAX` when we use a
1696 * `const char *` to avoid a `strlen`. Ensure that we didn't
1697 * have a NUL in the buffer if there was a non-SIZE_MAX length.
1699 if (path
->size
!= SIZE_MAX
&& len
!= path
->size
)
1702 if (!validate_component(start
, (c
- start
), flags
))
1705 if (validate_component_cb
&&
1706 !validate_component_cb(start
, (c
- start
), payload
))
1710 if ((flags
& GIT_FS_PATH_REJECT_LONG_PATHS
) != 0) {
1711 size_t utf8_len
= git_utf8_char_length(path
->ptr
, len
);
1713 if (!validate_length(path
->ptr
, len
, utf8_len
))
1716 if (validate_length_cb
&&
1717 !validate_length_cb(path
->ptr
, len
, utf8_len
))
1721 GIT_UNUSED(validate_length_cb
);
1727 int git_fs_path_validate_str_length_with_suffix(
1732 size_t utf8_len
= git_utf8_char_length(path
->ptr
, path
->size
);
1735 if (GIT_ADD_SIZET_OVERFLOW(&total_len
, utf8_len
, suffix_len
) ||
1736 total_len
> MAX_PATH
) {
1738 git_error_set(GIT_ERROR_FILESYSTEM
, "path too long: '%.*s'",
1739 (int)path
->size
, path
->ptr
);
1744 GIT_UNUSED(suffix_len
);
1750 int git_fs_path_normalize_slashes(git_str
*out
, const char *path
)
1755 if ((error
= git_str_puts(out
, path
)) < 0)
1758 for (p
= out
->ptr
; *p
; p
++) {
1766 bool git_fs_path_supports_symlinks(const char *dir
)
1768 git_str path
= GIT_STR_INIT
;
1769 bool supported
= false;
1773 if ((fd
= git_futils_mktmp(&path
, dir
, 0666)) < 0 ||
1775 p_unlink(path
.ptr
) < 0 ||
1776 p_symlink("testing", path
.ptr
) < 0 ||
1777 p_lstat(path
.ptr
, &st
) < 0)
1780 supported
= (S_ISLNK(st
.st_mode
) != 0);
1783 (void)p_unlink(path
.ptr
);
1784 git_str_dispose(&path
);
1788 static git_fs_path_owner_t mock_owner
= GIT_FS_PATH_OWNER_NONE
;
1790 void git_fs_path__set_owner(git_fs_path_owner_t owner
)
1796 static PSID
*sid_dup(PSID sid
)
1801 len
= GetLengthSid(sid
);
1803 if ((dup
= git__malloc(len
)) == NULL
)
1806 if (!CopySid(len
, dup
, sid
)) {
1807 git_error_set(GIT_ERROR_OS
, "could not duplicate sid");
1815 static int current_user_sid(PSID
*out
)
1817 TOKEN_USER
*info
= NULL
;
1818 HANDLE token
= NULL
;
1822 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY
, &token
)) {
1823 git_error_set(GIT_ERROR_OS
, "could not lookup process information");
1827 if (GetTokenInformation(token
, TokenUser
, NULL
, 0, &len
) ||
1828 GetLastError() != ERROR_INSUFFICIENT_BUFFER
) {
1829 git_error_set(GIT_ERROR_OS
, "could not lookup token metadata");
1833 info
= git__malloc(len
);
1834 GIT_ERROR_CHECK_ALLOC(info
);
1836 if (!GetTokenInformation(token
, TokenUser
, info
, len
, &len
)) {
1837 git_error_set(GIT_ERROR_OS
, "could not lookup current user");
1841 if ((*out
= sid_dup(info
->User
.Sid
)))
1852 static int file_owner_sid(PSID
*out
, const char *path
)
1854 git_win32_path path_w32
;
1855 PSECURITY_DESCRIPTOR descriptor
= NULL
;
1860 if (git_win32_path_from_utf8(path_w32
, path
) < 0)
1863 ret
= GetNamedSecurityInfoW(path_w32
, SE_FILE_OBJECT
,
1864 OWNER_SECURITY_INFORMATION
| DACL_SECURITY_INFORMATION
,
1865 &owner_sid
, NULL
, NULL
, NULL
, &descriptor
);
1867 if (ret
== ERROR_FILE_NOT_FOUND
|| ret
== ERROR_PATH_NOT_FOUND
)
1868 error
= GIT_ENOTFOUND
;
1869 else if (ret
!= ERROR_SUCCESS
)
1870 git_error_set(GIT_ERROR_OS
, "failed to get security information");
1871 else if (!IsValidSid(owner_sid
))
1872 git_error_set(GIT_ERROR_OS
, "file owner is not valid");
1873 else if ((*out
= sid_dup(owner_sid
)))
1877 LocalFree(descriptor
);
1882 int git_fs_path_owner_is(
1885 git_fs_path_owner_t owner_type
)
1887 PSID owner_sid
= NULL
, user_sid
= NULL
;
1888 BOOL is_admin
, admin_owned
;
1892 *out
= ((mock_owner
& owner_type
) != 0);
1896 if ((error
= file_owner_sid(&owner_sid
, path
)) < 0)
1899 if ((owner_type
& GIT_FS_PATH_OWNER_CURRENT_USER
) != 0) {
1900 if ((error
= current_user_sid(&user_sid
)) < 0)
1903 if (EqualSid(owner_sid
, user_sid
)) {
1910 IsWellKnownSid(owner_sid
, WinBuiltinAdministratorsSid
) ||
1911 IsWellKnownSid(owner_sid
, WinLocalSystemSid
);
1914 (owner_type
& GIT_FS_PATH_OWNER_ADMINISTRATOR
) != 0) {
1920 (owner_type
& GIT_FS_PATH_USER_IS_ADMINISTRATOR
) != 0 &&
1921 CheckTokenMembership(NULL
, owner_sid
, &is_admin
) &&
1930 git__free(owner_sid
);
1931 git__free(user_sid
);
1937 static int sudo_uid_lookup(uid_t
*out
)
1939 git_str uid_str
= GIT_STR_INIT
;
1943 if ((error
= git__getenv(&uid_str
, "SUDO_UID")) == 0 &&
1944 (error
= git__strntol64(&uid
, uid_str
.ptr
, uid_str
.size
, NULL
, 10)) == 0 &&
1945 uid
== (int64_t)((uid_t
)uid
)) {
1949 git_str_dispose(&uid_str
);
1953 int git_fs_path_owner_is(
1956 git_fs_path_owner_t owner_type
)
1959 uid_t euid
, sudo_uid
;
1962 *out
= ((mock_owner
& owner_type
) != 0);
1968 if (p_lstat(path
, &st
) != 0) {
1969 if (errno
== ENOENT
)
1970 return GIT_ENOTFOUND
;
1972 git_error_set(GIT_ERROR_OS
, "could not stat '%s'", path
);
1976 if ((owner_type
& GIT_FS_PATH_OWNER_CURRENT_USER
) != 0 &&
1977 st
.st_uid
== euid
) {
1982 if ((owner_type
& GIT_FS_PATH_OWNER_ADMINISTRATOR
) != 0 &&
1988 if ((owner_type
& GIT_FS_PATH_OWNER_RUNNING_SUDO
) != 0 &&
1990 sudo_uid_lookup(&sudo_uid
) == 0 &&
1991 st
.st_uid
== sudo_uid
) {
2002 int git_fs_path_owner_is_current_user(bool *out
, const char *path
)
2004 return git_fs_path_owner_is(out
, path
, GIT_FS_PATH_OWNER_CURRENT_USER
);
2007 int git_fs_path_owner_is_system(bool *out
, const char *path
)
2009 return git_fs_path_owner_is(out
, path
, GIT_FS_PATH_OWNER_ADMINISTRATOR
);
2012 int git_fs_path_find_executable(git_str
*fullpath
, const char *executable
)
2015 git_win32_path fullpath_w
, executable_w
;
2018 if (git__utf8_to_16(executable_w
, GIT_WIN_PATH_MAX
, executable
) < 0)
2021 error
= git_win32_path_find_executable(fullpath_w
, executable_w
);
2024 error
= git_str_put_w(fullpath
, fullpath_w
, wcslen(fullpath_w
));
2028 git_str path
= GIT_STR_INIT
;
2029 const char *current_dir
, *term
;
2032 if (git__getenv(&path
, "PATH") < 0)
2035 current_dir
= path
.ptr
;
2037 while (*current_dir
) {
2038 if (! (term
= strchr(current_dir
, GIT_PATH_LIST_SEPARATOR
)))
2039 term
= strchr(current_dir
, '\0');
2041 git_str_clear(fullpath
);
2042 if (git_str_put(fullpath
, current_dir
, (term
- current_dir
)) < 0 ||
2043 git_str_putc(fullpath
, '/') < 0 ||
2044 git_str_puts(fullpath
, executable
) < 0)
2047 if (git_fs_path_isfile(fullpath
->ptr
)) {
2054 while (*current_dir
== GIT_PATH_LIST_SEPARATOR
)
2058 git_str_dispose(&path
);
2063 git_str_clear(fullpath
);
2064 return GIT_ENOTFOUND
;