Coverage Report

Created: 2025-06-09 06:06

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