File Coverage

deps/libgit2/src/util/fs_path.c
Criterion Covered Total %
statement 385 688 55.9
branch 235 632 37.1
condition n/a
subroutine n/a
pod n/a
total 620 1320 46.9


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
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.
6             */
7              
8             #include "fs_path.h"
9              
10             #include "git2_util.h"
11             #include "futils.h"
12             #include "posix.h"
13             #ifdef GIT_WIN32
14             #include "win32/posix.h"
15             #include "win32/w32_buffer.h"
16             #include "win32/w32_util.h"
17             #include "win32/version.h"
18             #include
19             #else
20             #include
21             #endif
22             #include
23             #include
24              
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); \
30             } while(0)
31              
32 9889           static int dos_drive_prefix_length(const char *path)
33             {
34             int i;
35              
36             /*
37             * Does it start with an ASCII letter (i.e. highest bit not set),
38             * followed by a colon?
39             */
40 9889 50         if (!(0x80 & (unsigned char)*path))
41 9889 100         return *path && path[1] == ':' ? 2 : 0;
    50          
42              
43             /*
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
47             * like this:
48             *
49             * subst ֍: %USERPROFILE%\Desktop
50             */
51 0 0         for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
    0          
52             ; /* skip first UTF-8 character */
53 0 0         return path[i] == ':' ? i + 1 : 0;
54             }
55              
56             #ifdef GIT_WIN32
57             static bool looks_like_network_computer_name(const char *path, int pos)
58             {
59             if (pos < 3)
60             return false;
61              
62             if (path[0] != '/' || path[1] != '/')
63             return false;
64              
65             while (pos-- > 2) {
66             if (path[pos] == '/')
67             return false;
68             }
69              
70             return true;
71             }
72             #endif
73              
74             /*
75             * Based on the Android implementation, BSD licensed.
76             * http://android.git.kernel.org/
77             *
78             * Copyright (C) 2008 The Android Open Source Project
79             * All rights reserved.
80             *
81             * Redistribution and use in source and binary forms, with or without
82             * modification, are permitted provided that the following conditions
83             * are met:
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
89             * distribution.
90             *
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
102             * SUCH DAMAGE.
103             */
104 0           int git_fs_path_basename_r(git_str *buffer, const char *path)
105             {
106             const char *endp, *startp;
107             int len, result;
108              
109             /* Empty or NULL string gets treated as "." */
110 0 0         if (path == NULL || *path == '\0') {
    0          
111 0           startp = ".";
112 0           len = 1;
113 0           goto Exit;
114             }
115              
116             /* Strip trailing slashes */
117 0           endp = path + strlen(path) - 1;
118 0 0         while (endp > path && *endp == '/')
    0          
119 0           endp--;
120              
121             /* All slashes becomes "/" */
122 0 0         if (endp == path && *endp == '/') {
    0          
123 0           startp = "/";
124 0           len = 1;
125 0           goto Exit;
126             }
127              
128             /* Find the start of the base */
129 0           startp = endp;
130 0 0         while (startp > path && *(startp - 1) != '/')
    0          
131 0           startp--;
132              
133             /* Cast is safe because max path < max int */
134 0           len = (int)(endp - startp + 1);
135              
136             Exit:
137 0           result = len;
138              
139 0 0         if (buffer != NULL && git_str_set(buffer, startp, len) < 0)
    0          
140 0           return -1;
141              
142 0           return result;
143             }
144              
145             /*
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.
148             */
149 2632           static int win32_prefix_length(const char *path, int len)
150             {
151             #ifndef GIT_WIN32
152 2632           GIT_UNUSED(path);
153 2632           GIT_UNUSED(len);
154             #else
155             /*
156             * Mimic unix behavior where '/.git' returns '/': 'C:/.git'
157             * will return 'C:/' here
158             */
159             if (dos_drive_prefix_length(path) == len)
160             return len;
161              
162             /*
163             * Similarly checks if we're dealing with a network computer name
164             * '//computername/.git' will return '//computername/'
165             */
166             if (looks_like_network_computer_name(path, len))
167             return len;
168             #endif
169              
170 2632           return -1;
171             }
172              
173             /*
174             * Based on the Android implementation, BSD licensed.
175             * Check http://android.git.kernel.org/
176             */
177 1407           int git_fs_path_dirname_r(git_str *buffer, const char *path)
178             {
179             const char *endp;
180 1407           int is_prefix = 0, len;
181              
182             /* Empty or NULL string gets treated as "." */
183 1407 50         if (path == NULL || *path == '\0') {
    50          
184 0           path = ".";
185 0           len = 1;
186 0           goto Exit;
187             }
188              
189             /* Strip trailing slashes */
190 1407           endp = path + strlen(path) - 1;
191 1468 50         while (endp > path && *endp == '/')
    100          
192 61           endp--;
193              
194 1407 50         if (endp - path + 1 > INT_MAX) {
195 0           git_error_set(GIT_ERROR_INVALID, "path too long");
196 0           return -1;
197             }
198              
199 1407 50         if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) {
200 0           is_prefix = 1;
201 0           goto Exit;
202             }
203              
204             /* Find the start of the dir */
205 16805 100         while (endp > path && *endp != '/')
    100          
206 15398           endp--;
207              
208             /* Either the dir is "/" or there are no slashes */
209 1407 100         if (endp == path) {
210 182 50         path = (*endp == '/') ? "/" : ".";
211 182           len = 1;
212 182           goto Exit;
213             }
214              
215             do {
216 1225           endp--;
217 1225 50         } while (endp > path && *endp == '/');
    50          
218              
219 1225 50         if (endp - path + 1 > INT_MAX) {
220 0           git_error_set(GIT_ERROR_INVALID, "path too long");
221 0           return -1;
222             }
223              
224 1225 50         if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) {
225 0           is_prefix = 1;
226 0           goto Exit;
227             }
228              
229             /* Cast is safe because max path < max int */
230 1225           len = (int)(endp - path + 1);
231              
232             Exit:
233 1407 50         if (buffer) {
234 1407 50         if (git_str_set(buffer, path, len) < 0)
235 0           return -1;
236 1407 50         if (is_prefix && git_str_putc(buffer, '/') < 0)
    0          
237 0           return -1;
238             }
239              
240 1407           return len;
241             }
242              
243              
244 6           char *git_fs_path_dirname(const char *path)
245             {
246 6           git_str buf = GIT_STR_INIT;
247             char *dirname;
248              
249 6           git_fs_path_dirname_r(&buf, path);
250 6           dirname = git_str_detach(&buf);
251 6           git_str_dispose(&buf); /* avoid memleak if error occurs */
252              
253 6           return dirname;
254             }
255              
256 0           char *git_fs_path_basename(const char *path)
257             {
258 0           git_str buf = GIT_STR_INIT;
259             char *basename;
260              
261 0           git_fs_path_basename_r(&buf, path);
262 0           basename = git_str_detach(&buf);
263 0           git_str_dispose(&buf); /* avoid memleak if error occurs */
264              
265 0           return basename;
266             }
267              
268 0           size_t git_fs_path_basename_offset(git_str *buffer)
269             {
270             ssize_t slash;
271              
272 0 0         if (!buffer || buffer->size <= 0)
    0          
273 0           return 0;
274              
275 0           slash = git_str_rfind_next(buffer, '/');
276              
277 0 0         if (slash >= 0 && buffer->ptr[slash] == '/')
    0          
278 0           return (size_t)(slash + 1);
279              
280 0           return 0;
281             }
282              
283 9889           int git_fs_path_root(const char *path)
284             {
285 9889           int offset = 0, prefix_len;
286              
287             /* Does the root of the path look like a windows drive ? */
288 9889 50         if ((prefix_len = dos_drive_prefix_length(path)))
289 0           offset += prefix_len;
290              
291             #ifdef GIT_WIN32
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] != '\\'))
295             {
296             offset += 2;
297              
298             /* Skip the computer name segment */
299             while (path[offset] && path[offset] != '/' && path[offset] != '\\')
300             offset++;
301             }
302              
303             if (path[offset] == '\\')
304             return offset;
305             #endif
306              
307 9889 100         if (path[offset] == '/')
308 1558           return offset;
309              
310 8331           return -1; /* Not a real error - signals that path is not rooted */
311             }
312              
313 448           static void path_trim_slashes(git_str *path)
314             {
315 448           int ceiling = git_fs_path_root(path->ptr) + 1;
316              
317 448 50         if (ceiling < 0)
318 0           return;
319              
320 896 50         while (path->size > (size_t)ceiling) {
321 896 100         if (path->ptr[path->size-1] != '/')
322 448           break;
323              
324 448           path->ptr[path->size-1] = '\0';
325 448           path->size--;
326             }
327             }
328              
329 1793           int git_fs_path_join_unrooted(
330             git_str *path_out, const char *path, const char *base, ssize_t *root_at)
331             {
332             ssize_t root;
333              
334 1793 50         GIT_ASSERT_ARG(path_out);
335 1793 50         GIT_ASSERT_ARG(path);
336              
337 1793           root = (ssize_t)git_fs_path_root(path);
338              
339 1793 100         if (base != NULL && root < 0) {
    100          
340 1731 50         if (git_str_joinpath(path_out, base, path) < 0)
341 0           return -1;
342              
343 1731           root = (ssize_t)strlen(base);
344             } else {
345 62 50         if (git_str_sets(path_out, path) < 0)
346 0           return -1;
347              
348 62 50         if (root < 0)
349 0           root = 0;
350 62 100         else if (base)
351 51           git_fs_path_equal_or_prefixed(base, path, &root);
352             }
353              
354 1793 100         if (root_at)
355 1629           *root_at = root;
356              
357 1793           return 0;
358             }
359              
360 19           void git_fs_path_squash_slashes(git_str *path)
361             {
362             char *p, *q;
363              
364 19 50         if (path->size == 0)
365 0           return;
366              
367 287 100         for (p = path->ptr, q = path->ptr; *q; p++, q++) {
368 268           *p = *q;
369              
370 268 100         while (*q == '/' && *(q+1) == '/') {
    50          
371 0           path->size--;
372 0           q++;
373             }
374             }
375              
376 19           *p = '\0';
377             }
378              
379 94           int git_fs_path_prettify(git_str *path_out, const char *path, const char *base)
380             {
381             char buf[GIT_PATH_MAX];
382              
383 94 50         GIT_ASSERT_ARG(path_out);
384 94 50         GIT_ASSERT_ARG(path);
385              
386             /* construct path if needed */
387 94 100         if (base != NULL && git_fs_path_root(path) < 0) {
    50          
388 0 0         if (git_str_joinpath(path_out, base, path) < 0)
389 0           return -1;
390 0           path = path_out->ptr;
391             }
392              
393 94 50         if (p_realpath(path, buf) == NULL) {
394             /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */
395 0 0         int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1;
    0          
396 0           git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path);
397              
398 0           git_str_clear(path_out);
399              
400 0           return error;
401             }
402              
403 94           return git_str_sets(path_out, buf);
404             }
405              
406 30           int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base)
407             {
408 30           int error = git_fs_path_prettify(path_out, path, base);
409 30 50         return (error < 0) ? error : git_fs_path_to_dir(path_out);
410             }
411              
412 4559           int git_fs_path_to_dir(git_str *path)
413             {
414 9118           if (path->asize > 0 &&
415 8936 100         git_str_len(path) > 0 &&
416 4377           path->ptr[git_str_len(path) - 1] != '/')
417 2709           git_str_putc(path, '/');
418              
419 4559 50         return git_str_oom(path) ? -1 : 0;
420             }
421              
422 0           void git_fs_path_string_to_dir(char *path, size_t size)
423             {
424 0           size_t end = strlen(path);
425              
426 0 0         if (end && path[end - 1] != '/' && end < size) {
    0          
    0          
427 0           path[end] = '/';
428 0           path[end + 1] = '\0';
429             }
430 0           }
431              
432 3           int git__percent_decode(git_str *decoded_out, const char *input)
433             {
434             int len, hi, lo, i;
435              
436 3 50         GIT_ASSERT_ARG(decoded_out);
437 3 50         GIT_ASSERT_ARG(input);
438              
439 3           len = (int)strlen(input);
440 3           git_str_clear(decoded_out);
441              
442 153 100         for(i = 0; i < len; i++)
443             {
444 150           char c = input[i];
445              
446 150 50         if (c != '%')
447 150           goto append;
448              
449 0 0         if (i >= len - 2)
450 0           goto append;
451              
452 0           hi = git__fromhex(input[i + 1]);
453 0           lo = git__fromhex(input[i + 2]);
454              
455 0 0         if (hi < 0 || lo < 0)
    0          
456             goto append;
457              
458 0           c = (char)(hi << 4 | lo);
459 0           i += 2;
460              
461             append:
462 150 50         if (git_str_putc(decoded_out, c) < 0)
463 0           return -1;
464             }
465              
466 3           return 0;
467             }
468              
469 0           static int error_invalid_local_file_uri(const char *uri)
470             {
471 0           git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri);
472 0           return -1;
473             }
474              
475 9           static int local_file_url_prefixlen(const char *file_url)
476             {
477 9           int len = -1;
478              
479 9 100         if (git__prefixcmp(file_url, "file://") == 0) {
480 6 50         if (file_url[7] == '/')
481 6           len = 8;
482 0 0         else if (git__prefixcmp(file_url + 7, "localhost/") == 0)
483 0           len = 17;
484             }
485              
486 9           return len;
487             }
488              
489 6           bool git_fs_path_is_local_file_url(const char *file_url)
490             {
491 6           return (local_file_url_prefixlen(file_url) > 0);
492             }
493              
494 3           int git_fs_path_fromurl(git_str *local_path_out, const char *file_url)
495             {
496             int offset;
497              
498 3 50         GIT_ASSERT_ARG(local_path_out);
499 3 50         GIT_ASSERT_ARG(file_url);
500              
501 3 50         if ((offset = local_file_url_prefixlen(file_url)) < 0 ||
    50          
502 3 50         file_url[offset] == '\0' || file_url[offset] == '/')
503 0           return error_invalid_local_file_uri(file_url);
504              
505             #ifndef GIT_WIN32
506 3           offset--; /* A *nix absolute path starts with a forward slash */
507             #endif
508              
509 3           git_str_clear(local_path_out);
510 3           return git__percent_decode(local_path_out, file_url + offset);
511             }
512              
513 839           int git_fs_path_walk_up(
514             git_str *path,
515             const char *ceiling,
516             int (*cb)(void *data, const char *),
517             void *data)
518             {
519 839           int error = 0;
520             git_str iter;
521 839           ssize_t stop = 0, scan;
522 839           char oldc = '\0';
523              
524 839 50         GIT_ASSERT_ARG(path);
525 839 50         GIT_ASSERT_ARG(cb);
526              
527 839 50         if (ceiling != NULL) {
528 839 50         if (git__prefixcmp(path->ptr, ceiling) == 0)
529 839           stop = (ssize_t)strlen(ceiling);
530             else
531 0           stop = git_str_len(path);
532             }
533 839           scan = git_str_len(path);
534              
535             /* empty path: yield only once */
536 839 50         if (!scan) {
537 0           error = cb(data, "");
538 0 0         if (error)
539 0 0         ensure_error_set(error);
    0          
    0          
540 0           return error;
541             }
542              
543 839           iter.ptr = path->ptr;
544 839           iter.size = git_str_len(path);
545 839           iter.asize = path->asize;
546              
547 1806 100         while (scan >= stop) {
548 993           error = cb(data, iter.ptr);
549 993           iter.ptr[scan] = oldc;
550              
551 993 100         if (error) {
552 26 100         ensure_error_set(error);
    50          
    50          
553 26           break;
554             }
555              
556 967           scan = git_str_rfind_next(&iter, '/');
557 967 50         if (scan >= 0) {
558 967           scan++;
559 967           oldc = iter.ptr[scan];
560 967           iter.size = scan;
561 967           iter.ptr[scan] = '\0';
562             }
563             }
564              
565 839 50         if (scan >= 0)
566 839           iter.ptr[scan] = oldc;
567              
568             /* relative path: yield for the last component */
569 839 100         if (!error && stop == 0 && iter.ptr[0] != '/') {
    50          
    0          
570 0           error = cb(data, "");
571 0 0         if (error)
572 0 0         ensure_error_set(error);
    0          
    0          
573             }
574              
575 839           return error;
576             }
577              
578 3137           bool git_fs_path_exists(const char *path)
579             {
580 3137 50         GIT_ASSERT_ARG_WITH_RETVAL(path, false);
581 3137           return p_access(path, F_OK) == 0;
582             }
583              
584 2032           bool git_fs_path_isdir(const char *path)
585             {
586             struct stat st;
587 2032 100         if (p_stat(path, &st) < 0)
588 338           return false;
589              
590 2032           return S_ISDIR(st.st_mode) != 0;
591             }
592              
593 690           bool git_fs_path_isfile(const char *path)
594             {
595             struct stat st;
596              
597 690 50         GIT_ASSERT_ARG_WITH_RETVAL(path, false);
598 690 100         if (p_stat(path, &st) < 0)
599 473           return false;
600              
601 690           return S_ISREG(st.st_mode) != 0;
602             }
603              
604 0           bool git_fs_path_islink(const char *path)
605             {
606             struct stat st;
607              
608 0 0         GIT_ASSERT_ARG_WITH_RETVAL(path, false);
609 0 0         if (p_lstat(path, &st) < 0)
610 0           return false;
611              
612 0           return S_ISLNK(st.st_mode) != 0;
613             }
614              
615             #ifdef GIT_WIN32
616              
617             bool git_fs_path_is_empty_dir(const char *path)
618             {
619             git_win32_path filter_w;
620             bool empty = false;
621              
622             if (git_win32__findfirstfile_filter(filter_w, path)) {
623             WIN32_FIND_DATAW findData;
624             HANDLE hFind = FindFirstFileW(filter_w, &findData);
625              
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
633             * (a mount point).
634             */
635             if (hFind == INVALID_HANDLE_VALUE)
636             return git_fs_path_isdir(path);
637              
638             /* If the find handle was created successfully, then it's a directory */
639             empty = true;
640              
641             do {
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)) {
647             empty = false;
648             break;
649             }
650             } while (FindNextFileW(hFind, &findData));
651              
652             FindClose(hFind);
653             }
654              
655             return empty;
656             }
657              
658             #else
659              
660 2           static int path_found_entry(void *payload, git_str *path)
661             {
662 2           GIT_UNUSED(payload);
663 2           return !git_fs_path_is_dot_or_dotdot(path->ptr);
664             }
665              
666 2           bool git_fs_path_is_empty_dir(const char *path)
667             {
668             int error;
669 2           git_str dir = GIT_STR_INIT;
670              
671 2 50         if (!git_fs_path_isdir(path))
672 0           return false;
673              
674 2 50         if ((error = git_str_sets(&dir, path)) != 0)
675 0           git_error_clear();
676             else
677 2           error = git_fs_path_direach(&dir, 0, path_found_entry, NULL);
678              
679 2           git_str_dispose(&dir);
680              
681 2           return !error;
682             }
683              
684             #endif
685              
686 783           int git_fs_path_set_error(int errno_value, const char *path, const char *action)
687             {
688 783           switch (errno_value) {
689             case ENOENT:
690             case ENOTDIR:
691 783           git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action);
692 783           return GIT_ENOTFOUND;
693              
694             case EINVAL:
695             case ENAMETOOLONG:
696 0           git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path);
697 0           return GIT_EINVALIDSPEC;
698              
699             case EEXIST:
700 0           git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path);
701 0           return GIT_EEXISTS;
702              
703             case EACCES:
704 0           git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path);
705 0           return GIT_ELOCKED;
706              
707             default:
708 0           git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path);
709 0           return -1;
710             }
711             }
712              
713 1208           int git_fs_path_lstat(const char *path, struct stat *st)
714             {
715 1208 50         if (p_lstat(path, st) == 0)
716 1208           return 0;
717              
718 0           return git_fs_path_set_error(errno, path, "stat");
719             }
720              
721 588           static bool _check_dir_contents(
722             git_str *dir,
723             const char *sub,
724             bool (*predicate)(const char *))
725             {
726             bool result;
727 588           size_t dir_size = git_str_len(dir);
728 588           size_t sub_size = strlen(sub);
729             size_t alloc_size;
730              
731             /* leave base valid even if we could not make space for subdir */
732 1176 50         if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) ||
    50          
733 1176 50         GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) ||
734 588           git_str_try_grow(dir, alloc_size, false) < 0)
735 0           return false;
736              
737             /* save excursion */
738 588 50         if (git_str_joinpath(dir, dir->ptr, sub) < 0)
739 0           return false;
740              
741 588           result = predicate(dir->ptr);
742              
743             /* restore path */
744 588           git_str_truncate(dir, dir_size);
745 588           return result;
746             }
747              
748 13           bool git_fs_path_contains(git_str *dir, const char *item)
749             {
750 13           return _check_dir_contents(dir, item, &git_fs_path_exists);
751             }
752              
753 195           bool git_fs_path_contains_dir(git_str *base, const char *subdir)
754             {
755 195           return _check_dir_contents(base, subdir, &git_fs_path_isdir);
756             }
757              
758 380           bool git_fs_path_contains_file(git_str *base, const char *file)
759             {
760 380           return _check_dir_contents(base, file, &git_fs_path_isfile);
761             }
762              
763 631           int git_fs_path_find_dir(git_str *dir)
764             {
765 631           int error = 0;
766             char buf[GIT_PATH_MAX];
767              
768 631 100         if (p_realpath(dir->ptr, buf) != NULL)
769 613           error = git_str_sets(dir, buf);
770              
771             /* call dirname if this is not a directory */
772 631 50         if (!error) /* && git_fs_path_isdir(dir->ptr) == false) */
773 631 50         error = (git_fs_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0;
774              
775 631 50         if (!error)
776 631           error = git_fs_path_to_dir(dir);
777              
778 631           return error;
779             }
780              
781 182           int git_fs_path_resolve_relative(git_str *path, size_t ceiling)
782             {
783             char *base, *to, *from, *next;
784             size_t len;
785              
786 182 50         GIT_ERROR_CHECK_ALLOC_STR(path);
    50          
787              
788 182 50         if (ceiling > path->size)
789 0           ceiling = path->size;
790              
791             /* recognize drive prefixes, etc. that should not be backed over */
792 182 50         if (ceiling == 0)
793 182           ceiling = git_fs_path_root(path->ptr) + 1;
794              
795             /* recognize URL prefixes that should not be backed over */
796 182 50         if (ceiling == 0) {
797 182 50         for (next = path->ptr; *next && git__isalpha(*next); ++next);
    50          
798 182 50         if (next[0] == ':' && next[1] == '/' && next[2] == '/')
    0          
    0          
799 0           ceiling = (next + 3) - path->ptr;
800             }
801              
802 182           base = to = from = path->ptr + ceiling;
803              
804 364 100         while (*from) {
805 364 100         for (next = from; *next && *next != '/'; ++next);
    50          
806              
807 182           len = next - from;
808              
809 182 50         if (len == 1 && from[0] == '.')
    50          
810             /* do nothing with singleton dot */;
811              
812 0 0         else if (len == 2 && from[0] == '.' && from[1] == '.') {
    0          
    0          
813             /* error out if trying to up one from a hard base */
814 0 0         if (to == base && ceiling != 0) {
    0          
815 0           git_error_set(GIT_ERROR_INVALID,
816             "cannot strip root component off url");
817 0           return -1;
818             }
819              
820             /* no more path segments to strip,
821             * use '../' as a new base path */
822 0 0         if (to == base) {
823 0 0         if (*next == '/')
824 0           len++;
825              
826 0 0         if (to != from)
827 0           memmove(to, from, len);
828              
829 0           to += len;
830             /* this is now the base, can't back up from a
831             * relative prefix */
832 0           base = to;
833             } else {
834             /* back up a path segment */
835 0 0         while (to > base && to[-1] == '/') to--;
    0          
836 0 0         while (to > base && to[-1] != '/') to--;
    0          
837             }
838             } else {
839 0 0         if (*next == '/' && *from != '/')
    0          
840 0           len++;
841              
842 0 0         if (to != from)
843 0           memmove(to, from, len);
844              
845 0           to += len;
846             }
847              
848 182           from += len;
849              
850 182 50         while (*from == '/') from++;
851             }
852              
853 182           *to = '\0';
854              
855 182           path->size = to - path->ptr;
856              
857 182           return 0;
858             }
859              
860 0           int git_fs_path_apply_relative(git_str *target, const char *relpath)
861             {
862 0           return git_str_joinpath(target, git_str_cstr(target), relpath) ||
863 0           git_fs_path_resolve_relative(target, 0);
864             }
865              
866 220           int git_fs_path_cmp(
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))
870             {
871             unsigned char c1, c2;
872 220           size_t len = len1 < len2 ? len1 : len2;
873             int cmp;
874              
875 220           cmp = compare(name1, name2, len);
876 220 100         if (cmp)
877 137           return cmp;
878              
879 83           c1 = name1[len];
880 83           c2 = name2[len];
881              
882 83 100         if (c1 == '\0' && isdir1)
    100          
883 1           c1 = '/';
884              
885 83 100         if (c2 == '\0' && isdir2)
    100          
886 2           c2 = '/';
887              
888 83 100         return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
889             }
890              
891 22           size_t git_fs_path_common_dirlen(const char *one, const char *two)
892             {
893 22           const char *p, *q, *dirsep = NULL;
894              
895 174 100         for (p = one, q = two; *p && *q; p++, q++) {
    50          
896 166 100         if (*p == '/' && *q == '/')
    50          
897 28           dirsep = p;
898 138 100         else if (*p != *q)
899 14           break;
900             }
901              
902 22 100         return dirsep ? (dirsep - one) + 1 : 0;
903             }
904              
905 0           int git_fs_path_make_relative(git_str *path, const char *parent)
906             {
907             const char *p, *q, *p_dirsep, *q_dirsep;
908 0           size_t plen = path->size, newlen, alloclen, depth = 1, i, offset;
909              
910 0 0         for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) {
    0          
911 0 0         if (*p == '/' && *q == '/') {
    0          
912 0           p_dirsep = p;
913 0           q_dirsep = q;
914             }
915 0 0         else if (*p != *q)
916 0           break;
917             }
918              
919             /* need at least 1 common path segment */
920 0 0         if ((p_dirsep == path->ptr || q_dirsep == parent) &&
    0          
    0          
921 0 0         (*p_dirsep != '/' || *q_dirsep != '/')) {
922 0           git_error_set(GIT_ERROR_INVALID,
923             "%s is not a parent of %s", parent, path->ptr);
924 0           return GIT_ENOTFOUND;
925             }
926              
927 0 0         if (*p == '/' && !*q)
    0          
928 0           p++;
929 0 0         else if (!*p && *q == '/')
    0          
930 0           q++;
931 0 0         else if (!*p && !*q)
    0          
932 0           return git_str_clear(path), 0;
933             else {
934 0           p = p_dirsep + 1;
935 0           q = q_dirsep + 1;
936             }
937              
938 0           plen -= (p - path->ptr);
939              
940 0 0         if (!*q)
941 0           return git_str_set(path, p, plen);
942              
943 0 0         for (; (q = strchr(q, '/')) && *(q + 1); q++)
    0          
944 0           depth++;
945              
946 0 0         GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3);
    0          
947 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen);
    0          
948              
949 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1);
    0          
950              
951             /* save the offset as we might realllocate the pointer */
952 0           offset = p - path->ptr;
953 0 0         if (git_str_try_grow(path, alloclen, 1) < 0)
954 0           return -1;
955 0           p = path->ptr + offset;
956              
957 0           memmove(path->ptr + (depth * 3), p, plen + 1);
958              
959 0 0         for (i = 0; i < depth; i++)
960 0           memcpy(path->ptr + (i * 3), "../", 3);
961              
962 0           path->size = newlen;
963 0           return 0;
964             }
965              
966 0           bool git_fs_path_has_non_ascii(const char *path, size_t pathlen)
967             {
968 0           const uint8_t *scan = (const uint8_t *)path, *end;
969              
970 0 0         for (end = scan + pathlen; scan < end; ++scan)
971 0 0         if (*scan & 0x80)
972 0           return true;
973              
974 0           return false;
975             }
976              
977             #ifdef GIT_USE_ICONV
978              
979             int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic)
980             {
981             git_str_init(&ic->buf, 0);
982             ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING);
983             return 0;
984             }
985              
986             void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic)
987             {
988             if (ic) {
989             if (ic->map != (iconv_t)-1)
990             iconv_close(ic->map);
991             git_str_dispose(&ic->buf);
992             }
993             }
994              
995             int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen)
996             {
997             char *nfd = (char*)*in, *nfc;
998             size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv;
999             int retry = 1;
1000              
1001             if (!ic || ic->map == (iconv_t)-1 ||
1002             !git_fs_path_has_non_ascii(*in, *inlen))
1003             return 0;
1004              
1005             git_str_clear(&ic->buf);
1006              
1007             while (1) {
1008             GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1);
1009             if (git_str_grow(&ic->buf, alloclen) < 0)
1010             return -1;
1011              
1012             nfc = ic->buf.ptr + ic->buf.size;
1013             nfclen = ic->buf.asize - ic->buf.size;
1014              
1015             rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen);
1016              
1017             ic->buf.size = (nfc - ic->buf.ptr);
1018              
1019             if (rv != (size_t)-1)
1020             break;
1021              
1022             /* if we cannot convert the data (probably because iconv thinks
1023             * it is not valid UTF-8 source data), then use original data
1024             */
1025             if (errno != E2BIG)
1026             return 0;
1027              
1028             /* make space for 2x the remaining data to be converted
1029             * (with per retry overhead to avoid infinite loops)
1030             */
1031             wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4);
1032              
1033             if (retry++ > 4)
1034             goto fail;
1035             }
1036              
1037             ic->buf.ptr[ic->buf.size] = '\0';
1038              
1039             *in = ic->buf.ptr;
1040             *inlen = ic->buf.size;
1041              
1042             return 0;
1043              
1044             fail:
1045             git_error_set(GIT_ERROR_OS, "unable to convert unicode path data");
1046             return -1;
1047             }
1048              
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";
1051              
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.
1055             *
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.
1060             */
1061             bool git_fs_path_does_decompose_unicode(const char *root)
1062             {
1063             git_str nfc_path = GIT_STR_INIT;
1064             git_str nfd_path = GIT_STR_INIT;
1065             int fd;
1066             bool found_decomposed = false;
1067             size_t orig_len;
1068             const char *trailer;
1069              
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.
1073             */
1074             if (git_str_joinpath(&nfc_path, root, nfc_file) < 0)
1075             goto done;
1076              
1077             /* record original path length before trailer */
1078             orig_len = nfc_path.size;
1079              
1080             if ((fd = git_futils_mktmp(&nfc_path, nfc_path.ptr, 0666)) < 0)
1081             goto done;
1082             p_close(fd);
1083              
1084             trailer = nfc_path.ptr + orig_len;
1085              
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)
1089             goto done;
1090              
1091             found_decomposed = git_fs_path_exists(nfd_path.ptr);
1092              
1093             /* remove temporary file (using original precomposed path) */
1094             (void)p_unlink(nfc_path.ptr);
1095              
1096             done:
1097             git_str_dispose(&nfc_path);
1098             git_str_dispose(&nfd_path);
1099             return found_decomposed;
1100             }
1101              
1102             #else
1103              
1104 0           bool git_fs_path_does_decompose_unicode(const char *root)
1105             {
1106 0           GIT_UNUSED(root);
1107 0           return false;
1108             }
1109              
1110             #endif
1111              
1112             #if defined(__sun) || defined(__GNU__)
1113             typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1];
1114             #else
1115             typedef struct dirent path_dirent_data;
1116             #endif
1117              
1118 336           int git_fs_path_direach(
1119             git_str *path,
1120             uint32_t flags,
1121             int (*fn)(void *, git_str *),
1122             void *arg)
1123             {
1124 336           int error = 0;
1125             ssize_t wd_len;
1126             DIR *dir;
1127             struct dirent *de;
1128              
1129             #ifdef GIT_USE_ICONV
1130             git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT;
1131             #endif
1132              
1133 336           GIT_UNUSED(flags);
1134              
1135 336 50         if (git_fs_path_to_dir(path) < 0)
1136 0           return -1;
1137              
1138 336           wd_len = git_str_len(path);
1139              
1140 336 50         if ((dir = opendir(path->ptr)) == NULL) {
1141 0           git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr);
1142 0 0         if (errno == ENOENT)
1143 0           return GIT_ENOTFOUND;
1144              
1145 0           return -1;
1146             }
1147              
1148             #ifdef GIT_USE_ICONV
1149             if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
1150             (void)git_fs_path_iconv_init_precompose(&ic);
1151             #endif
1152              
1153 1275 100         while ((de = readdir(dir)) != NULL) {
1154 941           const char *de_path = de->d_name;
1155 941           size_t de_len = strlen(de_path);
1156              
1157 941 100         if (git_fs_path_is_dot_or_dotdot(de_path))
1158 668           continue;
1159              
1160             #ifdef GIT_USE_ICONV
1161             if ((error = git_fs_path_iconv(&ic, &de_path, &de_len)) < 0)
1162             break;
1163             #endif
1164              
1165 273 50         if ((error = git_str_put(path, de_path, de_len)) < 0)
1166 0           break;
1167              
1168 273           git_error_clear();
1169 273           error = fn(arg, path);
1170              
1171 273           git_str_truncate(path, wd_len); /* restore path */
1172              
1173             /* Only set our own error if the callback did not set one already */
1174 273 100         if (error != 0) {
1175 2 50         if (!git_error_last())
1176 2 50         ensure_error_set(error);
    0          
    50          
1177              
1178 2           break;
1179             }
1180             }
1181              
1182 336           closedir(dir);
1183              
1184             #ifdef GIT_USE_ICONV
1185             git_fs_path_iconv_clear(&ic);
1186             #endif
1187              
1188 336           return error;
1189             }
1190              
1191             #if defined(GIT_WIN32) && !defined(__MINGW32__)
1192              
1193             /* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7
1194             * and better.
1195             */
1196             #ifndef FIND_FIRST_EX_LARGE_FETCH
1197             # define FIND_FIRST_EX_LARGE_FETCH 2
1198             #endif
1199              
1200             int git_fs_path_diriter_init(
1201             git_fs_path_diriter *diriter,
1202             const char *path,
1203             unsigned int flags)
1204             {
1205             git_win32_path path_filter;
1206              
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);
1210              
1211             GIT_ASSERT_ARG(diriter);
1212             GIT_ASSERT_ARG(path);
1213              
1214             memset(diriter, 0, sizeof(git_fs_path_diriter));
1215             diriter->handle = INVALID_HANDLE_VALUE;
1216              
1217             if (git_str_puts(&diriter->path_utf8, path) < 0)
1218             return -1;
1219              
1220             path_trim_slashes(&diriter->path_utf8);
1221              
1222             if (diriter->path_utf8.size == 0) {
1223             git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path);
1224             return -1;
1225             }
1226              
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);
1230             return -1;
1231             }
1232              
1233             diriter->handle = FindFirstFileExW(
1234             path_filter,
1235             is_win7_or_later ? FindExInfoBasic : FindExInfoStandard,
1236             &diriter->current,
1237             FindExSearchNameMatch,
1238             NULL,
1239             is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0);
1240              
1241             if (diriter->handle == INVALID_HANDLE_VALUE) {
1242             git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path);
1243             return -1;
1244             }
1245              
1246             diriter->parent_utf8_len = diriter->path_utf8.size;
1247             diriter->flags = flags;
1248             return 0;
1249             }
1250              
1251             static int diriter_update_paths(git_fs_path_diriter *diriter)
1252             {
1253             size_t filename_len, path_len;
1254              
1255             filename_len = wcslen(diriter->current.cFileName);
1256              
1257             if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) ||
1258             GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2))
1259             return -1;
1260              
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);
1265             return -1;
1266             }
1267              
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';
1272              
1273             git_str_truncate(&diriter->path_utf8, diriter->parent_utf8_len);
1274              
1275             if (diriter->parent_utf8_len > 0 &&
1276             diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/')
1277             git_str_putc(&diriter->path_utf8, '/');
1278              
1279             git_str_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len);
1280              
1281             if (git_str_oom(&diriter->path_utf8))
1282             return -1;
1283              
1284             return 0;
1285             }
1286              
1287             int git_fs_path_diriter_next(git_fs_path_diriter *diriter)
1288             {
1289             bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
1290              
1291             do {
1292             /* Our first time through, we already have the data from
1293             * FindFirstFileW. Use it, otherwise get the next file.
1294             */
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));
1300              
1301             if (diriter_update_paths(diriter) < 0)
1302             return -1;
1303              
1304             return 0;
1305             }
1306              
1307             int git_fs_path_diriter_filename(
1308             const char **out,
1309             size_t *out_len,
1310             git_fs_path_diriter *diriter)
1311             {
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);
1316              
1317             *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1];
1318             *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1;
1319             return 0;
1320             }
1321              
1322             int git_fs_path_diriter_fullpath(
1323             const char **out,
1324             size_t *out_len,
1325             git_fs_path_diriter *diriter)
1326             {
1327             GIT_ASSERT_ARG(out);
1328             GIT_ASSERT_ARG(out_len);
1329             GIT_ASSERT_ARG(diriter);
1330              
1331             *out = diriter->path_utf8.ptr;
1332             *out_len = diriter->path_utf8.size;
1333             return 0;
1334             }
1335              
1336             int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter)
1337             {
1338             GIT_ASSERT_ARG(out);
1339             GIT_ASSERT_ARG(diriter);
1340              
1341             return git_win32__file_attribute_to_stat(out,
1342             (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current,
1343             diriter->path);
1344             }
1345              
1346             void git_fs_path_diriter_free(git_fs_path_diriter *diriter)
1347             {
1348             if (diriter == NULL)
1349             return;
1350              
1351             git_str_dispose(&diriter->path_utf8);
1352              
1353             if (diriter->handle != INVALID_HANDLE_VALUE) {
1354             FindClose(diriter->handle);
1355             diriter->handle = INVALID_HANDLE_VALUE;
1356             }
1357             }
1358              
1359             #else
1360              
1361 448           int git_fs_path_diriter_init(
1362             git_fs_path_diriter *diriter,
1363             const char *path,
1364             unsigned int flags)
1365             {
1366 448 50         GIT_ASSERT_ARG(diriter);
1367 448 50         GIT_ASSERT_ARG(path);
1368              
1369 448           memset(diriter, 0, sizeof(git_fs_path_diriter));
1370              
1371 448 50         if (git_str_puts(&diriter->path, path) < 0)
1372 0           return -1;
1373              
1374 448           path_trim_slashes(&diriter->path);
1375              
1376 448 50         if (diriter->path.size == 0) {
1377 0           git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path);
1378 0           return -1;
1379             }
1380              
1381 448 100         if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
1382 2           git_str_dispose(&diriter->path);
1383              
1384 2           git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path);
1385 2           return -1;
1386             }
1387              
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);
1391             #endif
1392              
1393 446           diriter->parent_len = diriter->path.size;
1394 446           diriter->flags = flags;
1395              
1396 446           return 0;
1397             }
1398              
1399 1661           int git_fs_path_diriter_next(git_fs_path_diriter *diriter)
1400             {
1401             struct dirent *de;
1402             const char *filename;
1403             size_t filename_len;
1404 1661           bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
1405 1661           int error = 0;
1406              
1407 1661 50         GIT_ASSERT_ARG(diriter);
1408              
1409 1661           errno = 0;
1410              
1411             do {
1412 2553 100         if ((de = readdir(diriter->dir)) == NULL) {
1413 446 50         if (!errno)
1414 446           return GIT_ITEROVER;
1415              
1416 0           git_error_set(GIT_ERROR_OS,
1417             "could not read directory '%s'", diriter->path.ptr);
1418 0           return -1;
1419             }
1420 2107 50         } while (skip_dot && git_fs_path_is_dot_or_dotdot(de->d_name));
    100          
1421              
1422 1215           filename = de->d_name;
1423 1215           filename_len = strlen(filename);
1424              
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)
1428             return error;
1429             #endif
1430              
1431 1215           git_str_truncate(&diriter->path, diriter->parent_len);
1432              
1433 1215 50         if (diriter->parent_len > 0 &&
    50          
1434 1215           diriter->path.ptr[diriter->parent_len-1] != '/')
1435 1215           git_str_putc(&diriter->path, '/');
1436              
1437 1215           git_str_put(&diriter->path, filename, filename_len);
1438              
1439 1215 50         if (git_str_oom(&diriter->path))
1440 0           return -1;
1441              
1442 1215           return error;
1443             }
1444              
1445 0           int git_fs_path_diriter_filename(
1446             const char **out,
1447             size_t *out_len,
1448             git_fs_path_diriter *diriter)
1449             {
1450 0 0         GIT_ASSERT_ARG(out);
1451 0 0         GIT_ASSERT_ARG(out_len);
1452 0 0         GIT_ASSERT_ARG(diriter);
1453 0 0         GIT_ASSERT(diriter->path.size > diriter->parent_len);
1454              
1455 0           *out = &diriter->path.ptr[diriter->parent_len+1];
1456 0           *out_len = diriter->path.size - diriter->parent_len - 1;
1457 0           return 0;
1458             }
1459              
1460 1215           int git_fs_path_diriter_fullpath(
1461             const char **out,
1462             size_t *out_len,
1463             git_fs_path_diriter *diriter)
1464             {
1465 1215 50         GIT_ASSERT_ARG(out);
1466 1215 50         GIT_ASSERT_ARG(out_len);
1467 1215 50         GIT_ASSERT_ARG(diriter);
1468              
1469 1215           *out = diriter->path.ptr;
1470 1215           *out_len = diriter->path.size;
1471 1215           return 0;
1472             }
1473              
1474 1092           int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter)
1475             {
1476 1092 50         GIT_ASSERT_ARG(out);
1477 1092 50         GIT_ASSERT_ARG(diriter);
1478              
1479 1092           return git_fs_path_lstat(diriter->path.ptr, out);
1480             }
1481              
1482 448           void git_fs_path_diriter_free(git_fs_path_diriter *diriter)
1483             {
1484 448 50         if (diriter == NULL)
1485 0           return;
1486              
1487 448 100         if (diriter->dir) {
1488 446           closedir(diriter->dir);
1489 446           diriter->dir = NULL;
1490             }
1491              
1492             #ifdef GIT_USE_ICONV
1493             git_fs_path_iconv_clear(&diriter->ic);
1494             #endif
1495              
1496 448           git_str_dispose(&diriter->path);
1497             }
1498              
1499             #endif
1500              
1501 2           int git_fs_path_dirload(
1502             git_vector *contents,
1503             const char *path,
1504             size_t prefix_len,
1505             uint32_t flags)
1506             {
1507 2           git_fs_path_diriter iter = GIT_FS_PATH_DIRITER_INIT;
1508             const char *name;
1509             size_t name_len;
1510             char *dup;
1511             int error;
1512              
1513 2 50         GIT_ASSERT_ARG(contents);
1514 2 50         GIT_ASSERT_ARG(path);
1515              
1516 2 50         if ((error = git_fs_path_diriter_init(&iter, path, flags)) < 0)
1517 0           return error;
1518              
1519 6 100         while ((error = git_fs_path_diriter_next(&iter)) == 0) {
1520 4 50         if ((error = git_fs_path_diriter_fullpath(&name, &name_len, &iter)) < 0)
1521 0           break;
1522              
1523 4 50         GIT_ASSERT(name_len > prefix_len);
1524              
1525 4           dup = git__strndup(name + prefix_len, name_len - prefix_len);
1526 4 50         GIT_ERROR_CHECK_ALLOC(dup);
1527              
1528 4 50         if ((error = git_vector_insert(contents, dup)) < 0)
1529 0           break;
1530             }
1531              
1532 2 50         if (error == GIT_ITEROVER)
1533 2           error = 0;
1534              
1535 2           git_fs_path_diriter_free(&iter);
1536 2           return error;
1537             }
1538              
1539 6           int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path)
1540             {
1541 6 100         if (git_fs_path_is_local_file_url(url_or_path))
1542 3           return git_fs_path_fromurl(local_path_out, url_or_path);
1543             else
1544 3           return git_str_sets(local_path_out, url_or_path);
1545             }
1546              
1547             /* Reject paths like AUX or COM1, or those versions that end in a dot or
1548             * colon. ("AUX." or "AUX:")
1549             */
1550 0           GIT_INLINE(bool) validate_dospath(
1551             const char *component,
1552             size_t len,
1553             const char dospath[3],
1554             bool trailing_num)
1555             {
1556 0 0         size_t last = trailing_num ? 4 : 3;
1557              
1558 0 0         if (len < last || git__strncasecmp(component, dospath, 3) != 0)
    0          
1559 0           return true;
1560              
1561 0 0         if (trailing_num && (component[3] < '1' || component[3] > '9'))
    0          
    0          
1562 0           return true;
1563              
1564 0 0         return (len > last &&
1565 0 0         component[last] != '.' &&
    0          
1566 0           component[last] != ':');
1567             }
1568              
1569 4887           GIT_INLINE(bool) validate_char(unsigned char c, unsigned int flags)
1570             {
1571 4887 50         if ((flags & GIT_FS_PATH_REJECT_BACKSLASH) && c == '\\')
    0          
1572 0           return false;
1573              
1574 4887 100         if ((flags & GIT_FS_PATH_REJECT_SLASH) && c == '/')
    100          
1575 1           return false;
1576              
1577 4886 50         if (flags & GIT_FS_PATH_REJECT_NT_CHARS) {
1578 0 0         if (c < 32)
1579 0           return false;
1580              
1581 0 0         switch (c) {
1582             case '<':
1583             case '>':
1584             case ':':
1585             case '"':
1586             case '|':
1587             case '?':
1588             case '*':
1589 0           return false;
1590             }
1591             }
1592              
1593 4886           return true;
1594             }
1595              
1596             /*
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.
1602             *
1603             * For checkout, we don't want to recurse into ".git" either.
1604             */
1605 803           static bool validate_component(
1606             const char *component,
1607             size_t len,
1608             unsigned int flags)
1609             {
1610 803 50         if (len == 0)
1611 0           return !(flags & GIT_FS_PATH_REJECT_EMPTY_COMPONENT);
1612              
1613 803 50         if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) &&
    100          
1614 7 50         len == 1 && component[0] == '.')
1615 0           return false;
1616              
1617 803 50         if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) &&
    50          
1618 0 0         len == 2 && component[0] == '.' && component[1] == '.')
    0          
1619 0           return false;
1620              
1621 803 50         if ((flags & GIT_FS_PATH_REJECT_TRAILING_DOT) &&
    0          
1622 0           component[len - 1] == '.')
1623 0           return false;
1624              
1625 803 50         if ((flags & GIT_FS_PATH_REJECT_TRAILING_SPACE) &&
    0          
1626 0           component[len - 1] == ' ')
1627 0           return false;
1628              
1629 803 50         if ((flags & GIT_FS_PATH_REJECT_TRAILING_COLON) &&
    0          
1630 0           component[len - 1] == ':')
1631 0           return false;
1632              
1633 803 50         if (flags & GIT_FS_PATH_REJECT_DOS_PATHS) {
1634 0           if (!validate_dospath(component, len, "CON", false) ||
1635 0 0         !validate_dospath(component, len, "PRN", false) ||
1636 0 0         !validate_dospath(component, len, "AUX", false) ||
1637 0 0         !validate_dospath(component, len, "NUL", false) ||
1638 0 0         !validate_dospath(component, len, "COM", true) ||
1639 0           !validate_dospath(component, len, "LPT", true))
1640 0           return false;
1641             }
1642              
1643 803           return true;
1644             }
1645              
1646             #ifdef GIT_WIN32
1647             GIT_INLINE(bool) validate_length(
1648             const char *path,
1649             size_t len,
1650             size_t utf8_char_len)
1651             {
1652             GIT_UNUSED(path);
1653             GIT_UNUSED(len);
1654              
1655             return (utf8_char_len <= MAX_PATH);
1656             }
1657             #endif
1658              
1659 7229           bool git_fs_path_str_is_valid_ext(
1660             const git_str *path,
1661             unsigned int flags,
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),
1665             void *payload)
1666             {
1667             const char *start, *c;
1668 7229           size_t len = 0;
1669              
1670 7229 100         if (!flags)
1671 6651           return true;
1672              
1673 5464 100         for (start = c = path->ptr; *c && len < path->size; c++, len++) {
    50          
1674 4887 100         if (!validate_char(*c, flags))
1675 1           return false;
1676              
1677 4886 50         if (validate_char_cb && !validate_char_cb(*c, payload))
    0          
1678 0           return false;
1679              
1680 4886 100         if (*c != '/')
1681 4660           continue;
1682              
1683 226 50         if (!validate_component(start, (c - start), flags))
1684 0           return false;
1685              
1686 452           if (validate_component_cb &&
1687 226           !validate_component_cb(start, (c - start), payload))
1688 0           return false;
1689              
1690 226           start = c + 1;
1691             }
1692              
1693             /*
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.
1698             */
1699 577 50         if (path->size != SIZE_MAX && len != path->size)
    0          
1700 0           return false;
1701              
1702 577 50         if (!validate_component(start, (c - start), flags))
1703 0           return false;
1704              
1705 1154           if (validate_component_cb &&
1706 577           !validate_component_cb(start, (c - start), payload))
1707 0           return false;
1708              
1709             #ifdef GIT_WIN32
1710             if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS) != 0) {
1711             size_t utf8_len = git_utf8_char_length(path->ptr, len);
1712              
1713             if (!validate_length(path->ptr, len, utf8_len))
1714             return false;
1715              
1716             if (validate_length_cb &&
1717             !validate_length_cb(path->ptr, len, utf8_len))
1718             return false;
1719             }
1720             #else
1721 577           GIT_UNUSED(validate_length_cb);
1722             #endif
1723              
1724 577           return true;
1725             }
1726              
1727 1894           int git_fs_path_validate_str_length_with_suffix(
1728             git_str *path,
1729             size_t suffix_len)
1730             {
1731             #ifdef GIT_WIN32
1732             size_t utf8_len = git_utf8_char_length(path->ptr, path->size);
1733             size_t total_len;
1734              
1735             if (GIT_ADD_SIZET_OVERFLOW(&total_len, utf8_len, suffix_len) ||
1736             total_len > MAX_PATH) {
1737              
1738             git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'",
1739             (int)path->size, path->ptr);
1740             return -1;
1741             }
1742             #else
1743 1894           GIT_UNUSED(path);
1744 1894           GIT_UNUSED(suffix_len);
1745             #endif
1746              
1747 1894           return 0;
1748             }
1749              
1750 0           int git_fs_path_normalize_slashes(git_str *out, const char *path)
1751             {
1752             int error;
1753             char *p;
1754              
1755 0 0         if ((error = git_str_puts(out, path)) < 0)
1756 0           return error;
1757              
1758 0 0         for (p = out->ptr; *p; p++) {
1759 0 0         if (*p == '\\')
1760 0           *p = '/';
1761             }
1762              
1763 0           return 0;
1764             }
1765              
1766 5           bool git_fs_path_supports_symlinks(const char *dir)
1767             {
1768 5           git_str path = GIT_STR_INIT;
1769 5           bool supported = false;
1770             struct stat st;
1771             int fd;
1772              
1773 10           if ((fd = git_futils_mktmp(&path, dir, 0666)) < 0 ||
1774 10 50         p_close(fd) < 0 ||
1775 10 50         p_unlink(path.ptr) < 0 ||
1776 10 50         p_symlink("testing", path.ptr) < 0 ||
1777 5           p_lstat(path.ptr, &st) < 0)
1778             goto done;
1779              
1780 5           supported = (S_ISLNK(st.st_mode) != 0);
1781             done:
1782 5 50         if (path.size)
1783 5           (void)p_unlink(path.ptr);
1784 5           git_str_dispose(&path);
1785 5           return supported;
1786             }
1787              
1788             static git_fs_path_owner_t mock_owner = GIT_FS_PATH_OWNER_NONE;
1789              
1790 0           void git_fs_path__set_owner(git_fs_path_owner_t owner)
1791             {
1792 0           mock_owner = owner;
1793 0           }
1794              
1795             #ifdef GIT_WIN32
1796             static PSID *sid_dup(PSID sid)
1797             {
1798             DWORD len;
1799             PSID dup;
1800              
1801             len = GetLengthSid(sid);
1802              
1803             if ((dup = git__malloc(len)) == NULL)
1804             return NULL;
1805              
1806             if (!CopySid(len, dup, sid)) {
1807             git_error_set(GIT_ERROR_OS, "could not duplicate sid");
1808             git__free(dup);
1809             return NULL;
1810             }
1811              
1812             return dup;
1813             }
1814              
1815             static int current_user_sid(PSID *out)
1816             {
1817             TOKEN_USER *info = NULL;
1818             HANDLE token = NULL;
1819             DWORD len = 0;
1820             int error = -1;
1821              
1822             if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
1823             git_error_set(GIT_ERROR_OS, "could not lookup process information");
1824             goto done;
1825             }
1826              
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");
1830             goto done;
1831             }
1832              
1833             info = git__malloc(len);
1834             GIT_ERROR_CHECK_ALLOC(info);
1835              
1836             if (!GetTokenInformation(token, TokenUser, info, len, &len)) {
1837             git_error_set(GIT_ERROR_OS, "could not lookup current user");
1838             goto done;
1839             }
1840              
1841             if ((*out = sid_dup(info->User.Sid)))
1842             error = 0;
1843              
1844             done:
1845             if (token)
1846             CloseHandle(token);
1847              
1848             git__free(info);
1849             return error;
1850             }
1851              
1852             static int file_owner_sid(PSID *out, const char *path)
1853             {
1854             git_win32_path path_w32;
1855             PSECURITY_DESCRIPTOR descriptor = NULL;
1856             PSID owner_sid;
1857             DWORD ret;
1858             int error = -1;
1859              
1860             if (git_win32_path_from_utf8(path_w32, path) < 0)
1861             return -1;
1862              
1863             ret = GetNamedSecurityInfoW(path_w32, SE_FILE_OBJECT,
1864             OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
1865             &owner_sid, NULL, NULL, NULL, &descriptor);
1866              
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)))
1874             error = 0;
1875              
1876             if (descriptor)
1877             LocalFree(descriptor);
1878              
1879             return error;
1880             }
1881              
1882             int git_fs_path_owner_is(
1883             bool *out,
1884             const char *path,
1885             git_fs_path_owner_t owner_type)
1886             {
1887             PSID owner_sid = NULL, user_sid = NULL;
1888             BOOL is_admin, admin_owned;
1889             int error;
1890              
1891             if (mock_owner) {
1892             *out = ((mock_owner & owner_type) != 0);
1893             return 0;
1894             }
1895              
1896             if ((error = file_owner_sid(&owner_sid, path)) < 0)
1897             goto done;
1898              
1899             if ((owner_type & GIT_FS_PATH_OWNER_CURRENT_USER) != 0) {
1900             if ((error = current_user_sid(&user_sid)) < 0)
1901             goto done;
1902              
1903             if (EqualSid(owner_sid, user_sid)) {
1904             *out = true;
1905             goto done;
1906             }
1907             }
1908              
1909             admin_owned =
1910             IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) ||
1911             IsWellKnownSid(owner_sid, WinLocalSystemSid);
1912              
1913             if (admin_owned &&
1914             (owner_type & GIT_FS_PATH_OWNER_ADMINISTRATOR) != 0) {
1915             *out = true;
1916             goto done;
1917             }
1918              
1919             if (admin_owned &&
1920             (owner_type & GIT_FS_PATH_USER_IS_ADMINISTRATOR) != 0 &&
1921             CheckTokenMembership(NULL, owner_sid, &is_admin) &&
1922             is_admin) {
1923             *out = true;
1924             goto done;
1925             }
1926              
1927             *out = false;
1928              
1929             done:
1930             git__free(owner_sid);
1931             git__free(user_sid);
1932             return error;
1933             }
1934              
1935             #else
1936              
1937 0           static int sudo_uid_lookup(uid_t *out)
1938             {
1939 0           git_str uid_str = GIT_STR_INIT;
1940             int64_t uid;
1941             int error;
1942              
1943 0 0         if ((error = git__getenv(&uid_str, "SUDO_UID")) == 0 &&
    0          
1944 0 0         (error = git__strntol64(&uid, uid_str.ptr, uid_str.size, NULL, 10)) == 0 &&
1945 0           uid == (int64_t)((uid_t)uid)) {
1946 0           *out = (uid_t)uid;
1947             }
1948              
1949 0           git_str_dispose(&uid_str);
1950 0           return error;
1951             }
1952              
1953 123           int git_fs_path_owner_is(
1954             bool *out,
1955             const char *path,
1956             git_fs_path_owner_t owner_type)
1957             {
1958             struct stat st;
1959             uid_t euid, sudo_uid;
1960              
1961 123 50         if (mock_owner) {
1962 0           *out = ((mock_owner & owner_type) != 0);
1963 0           return 0;
1964             }
1965              
1966 123           euid = geteuid();
1967              
1968 123 50         if (p_lstat(path, &st) != 0) {
1969 0 0         if (errno == ENOENT)
1970 0           return GIT_ENOTFOUND;
1971              
1972 0           git_error_set(GIT_ERROR_OS, "could not stat '%s'", path);
1973 0           return -1;
1974             }
1975              
1976 123 50         if ((owner_type & GIT_FS_PATH_OWNER_CURRENT_USER) != 0 &&
    50          
1977 123           st.st_uid == euid) {
1978 123           *out = true;
1979 123           return 0;
1980             }
1981              
1982 0 0         if ((owner_type & GIT_FS_PATH_OWNER_ADMINISTRATOR) != 0 &&
    0          
1983 0           st.st_uid == 0) {
1984 0           *out = true;
1985 0           return 0;
1986             }
1987              
1988 0 0         if ((owner_type & GIT_FS_PATH_OWNER_RUNNING_SUDO) != 0 &&
    0          
1989 0 0         euid == 0 &&
1990 0 0         sudo_uid_lookup(&sudo_uid) == 0 &&
1991 0           st.st_uid == sudo_uid) {
1992 0           *out = true;
1993 0           return 0;
1994             }
1995              
1996 0           *out = false;
1997 123           return 0;
1998             }
1999              
2000             #endif
2001              
2002 0           int git_fs_path_owner_is_current_user(bool *out, const char *path)
2003             {
2004 0           return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_CURRENT_USER);
2005             }
2006              
2007 0           int git_fs_path_owner_is_system(bool *out, const char *path)
2008             {
2009 0           return git_fs_path_owner_is(out, path, GIT_FS_PATH_OWNER_ADMINISTRATOR);
2010             }
2011              
2012 0           int git_fs_path_find_executable(git_str *fullpath, const char *executable)
2013             {
2014             #ifdef GIT_WIN32
2015             git_win32_path fullpath_w, executable_w;
2016             int error;
2017              
2018             if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0)
2019             return -1;
2020              
2021             error = git_win32_path_find_executable(fullpath_w, executable_w);
2022              
2023             if (error == 0)
2024             error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w));
2025              
2026             return error;
2027             #else
2028 0           git_str path = GIT_STR_INIT;
2029             const char *current_dir, *term;
2030 0           bool found = false;
2031              
2032 0 0         if (git__getenv(&path, "PATH") < 0)
2033 0           return -1;
2034              
2035 0           current_dir = path.ptr;
2036              
2037 0 0         while (*current_dir) {
2038 0 0         if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR)))
2039 0           term = strchr(current_dir, '\0');
2040              
2041 0           git_str_clear(fullpath);
2042 0           if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 ||
2043 0 0         git_str_putc(fullpath, '/') < 0 ||
2044 0           git_str_puts(fullpath, executable) < 0)
2045 0           return -1;
2046              
2047 0 0         if (git_fs_path_isfile(fullpath->ptr)) {
2048 0           found = true;
2049 0           break;
2050             }
2051              
2052 0           current_dir = term;
2053              
2054 0 0         while (*current_dir == GIT_PATH_LIST_SEPARATOR)
2055 0           current_dir++;
2056             }
2057              
2058 0           git_str_dispose(&path);
2059              
2060 0 0         if (found)
2061 0           return 0;
2062              
2063 0           git_str_clear(fullpath);
2064 0           return GIT_ENOTFOUND;
2065             #endif
2066             }