tree-walk: learn get_tree_entry_follow_symlinks
Add a new function, get_tree_entry_follow_symlinks, to tree-walk.[ch]. The function is not yet used. It will be used to implement git cat-file --batch --follow-symlinks. The function locates an object by path, following symlinks in the repository. If the symlinks lead outside the repository, the function reports this to the caller. Signed-off-by: David Turner <dturner@twopensource.com> Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>
David Turner committed
May 20, 2015 at 13:03 UTC
275721c2671800a905e873e58be2f1c2f313bf88
2 files changed
+224
tree-walk.c
+206
@@ -415,6 +415,12 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
415
return error;
416
}
417
418
+struct dir_state {
419
+ void *tree;
420
+ unsigned long size;
421
+ unsigned char sha1[20];
422
+};
423
+
424
static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
425
{
426
int namelen = strlen(name);
@@ -478,6 +484,206 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
484
return retval;
485
}
486
487
+/*
488
+ * This is Linux's built-in max for the number of symlinks to follow.
489
+ * That limit, of course, does not affect git, but it's a reasonable
490
+ * choice.
491
+ */
492
+#define GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS 40
493
+
494
+/**
495
+ * Find a tree entry by following symlinks in tree_sha (which is
496
+ * assumed to be the root of the repository). In the event that a
497
+ * symlink points outside the repository (e.g. a link to /foo or a
498
+ * root-level link to ../foo), the portion of the link which is
499
+ * outside the repository will be returned in result_path, and *mode
500
+ * will be set to 0. It is assumed that result_path is uninitialized.
501
+ * If there are no symlinks, or the end result of the symlink chain
502
+ * points to an object inside the repository, result will be filled in
503
+ * with the sha1 of the found object, and *mode will hold the mode of
504
+ * the object.
505
+ *
506
+ * See the code for enum follow_symlink_result for a description of
507
+ * the return values.
508
+ */
509
+enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode)
510
+{
511
+ int retval = MISSING_OBJECT;
512
+ struct dir_state *parents = NULL;
513
+ size_t parents_alloc = 0;
514
+ ssize_t parents_nr = 0;
515
+ unsigned char current_tree_sha1[20];
516
+ struct strbuf namebuf = STRBUF_INIT;
517
+ struct tree_desc t;
518
+ int follows_remaining = GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS;
519
+ int i;
520
+
521
+ init_tree_desc(&t, NULL, 0UL);
522
+ strbuf_init(result_path, 0);
523
+ strbuf_addstr(&namebuf, name);
524
+ hashcpy(current_tree_sha1, tree_sha1);
525
+
526
+ while (1) {
527
+ int find_result;
528
+ char *first_slash;
529
+ char *remainder = NULL;
530
+
531
+ if (!t.buffer) {
532
+ void *tree;
533
+ unsigned char root[20];
534
+ unsigned long size;
535
+ tree = read_object_with_reference(current_tree_sha1,
536
+ tree_type, &size,
537
+ root);
538
+ if (!tree)
539
+ goto done;
540
+
541
+ ALLOC_GROW(parents, parents_nr + 1, parents_alloc);
542
+ parents[parents_nr].tree = tree;
543
+ parents[parents_nr].size = size;
544
+ hashcpy(parents[parents_nr].sha1, root);
545
+ parents_nr++;
546
+
547
+ if (namebuf.buf[0] == '\0') {
548
+ hashcpy(result, root);
549
+ retval = FOUND;
550
+ goto done;
551
+ }
552
+
553
+ if (!size)
554
+ goto done;
555
+
556
+ /* descend */
557
+ init_tree_desc(&t, tree, size);
558
+ }
559
+
560
+ /* Handle symlinks to e.g. a//b by removing leading slashes */
561
+ while (namebuf.buf[0] == '/') {
562
+ strbuf_remove(&namebuf, 0, 1);
563
+ }
564
+
565
+ /* Split namebuf into a first component and a remainder */
566
+ if ((first_slash = strchr(namebuf.buf, '/'))) {
567
+ *first_slash = 0;
568
+ remainder = first_slash + 1;
569
+ }
570
+
571
+ if (!strcmp(namebuf.buf, "..")) {
572
+ struct dir_state *parent;
573
+ /*
574
+ * We could end up with .. in the namebuf if it
575
+ * appears in a symlink.
576
+ */
577
+
578
+ if (parents_nr == 1) {
579
+ if (remainder)
580
+ *first_slash = '/';
581
+ strbuf_add(result_path, namebuf.buf,
582
+ namebuf.len);
583
+ *mode = 0;
584
+ retval = FOUND;
585
+ goto done;
586
+ }
587
+ parent = &parents[parents_nr - 1];
588
+ free(parent->tree);
589
+ parents_nr--;
590
+ parent = &parents[parents_nr - 1];
591
+ init_tree_desc(&t, parent->tree, parent->size);
592
+ strbuf_remove(&namebuf, 0, remainder ? 3 : 2);
593
+ continue;
594
+ }
595
+
596
+ /* We could end up here via a symlink to dir/.. */
597
+ if (namebuf.buf[0] == '\0') {
598
+ hashcpy(result, parents[parents_nr - 1].sha1);
599
+ retval = FOUND;
600
+ goto done;
601
+ }
602
+
603
+ /* Look up the first (or only) path component in the tree. */
604
+ find_result = find_tree_entry(&t, namebuf.buf,
605
+ current_tree_sha1, mode);
606
+ if (find_result) {
607
+ goto done;
608
+ }
609
+
610
+ if (S_ISDIR(*mode)) {
611
+ if (!remainder) {
612
+ hashcpy(result, current_tree_sha1);
613
+ retval = FOUND;
614
+ goto done;
615
+ }
616
+ /* Descend the tree */
617
+ t.buffer = NULL;
618
+ strbuf_remove(&namebuf, 0,
619
+ 1 + first_slash - namebuf.buf);
620
+ } else if (S_ISREG(*mode)) {
621
+ if (!remainder) {
622
+ hashcpy(result, current_tree_sha1);
623
+ retval = FOUND;
624
+ } else {
625
+ retval = NOT_DIR;
626
+ }
627
+ goto done;
628
+ } else if (S_ISLNK(*mode)) {
629
+ /* Follow a symlink */
630
+ unsigned long link_len;
631
+ size_t len;
632
+ char *contents, *contents_start;
633
+ struct dir_state *parent;
634
+ enum object_type type;
635
+
636
+ if (follows_remaining-- == 0) {
637
+ /* Too many symlinks followed */
638
+ retval = SYMLINK_LOOP;
639
+ goto done;
640
+ }
641
+
642
+ /*
643
+ * At this point, we have followed at a least
644
+ * one symlink, so on error we need to report this.
645
+ */
646
+ retval = DANGLING_SYMLINK;
647
+
648
+ contents = read_sha1_file(current_tree_sha1, &type,
649
+ &link_len);
650
+
651
+ if (!contents)
652
+ goto done;
653
+
654
+ if (contents[0] == '/') {
655
+ strbuf_addstr(result_path, contents);
656
+ free(contents);
657
+ *mode = 0;
658
+ retval = FOUND;
659
+ goto done;
660
+ }
661
+
662
+ if (remainder)
663
+ len = first_slash - namebuf.buf;
664
+ else
665
+ len = namebuf.len;
666
+
667
+ contents_start = contents;
668
+
669
+ parent = &parents[parents_nr - 1];
670
+ init_tree_desc(&t, parent->tree, parent->size);
671
+ strbuf_splice(&namebuf, 0, len,
672
+ contents_start, link_len);
673
+ if (remainder)
674
+ namebuf.buf[link_len] = '/';
675
+ free(contents);
676
+ }
677
+ }
678
+done:
679
+ for (i = 0; i < parents_nr; i++)
680
+ free(parents[i].tree);
681
+ free(parents);
682
+
683
+ strbuf_release(&namebuf);
684
+ return retval;
685
+}
686
+
687
static int match_entry(const struct pathspec_item *item,
688
const struct name_entry *entry, int pathlen,
689
const char *match, int matchlen,
tree-walk.h
+18
@@ -40,6 +40,24 @@ struct traverse_info;
40
typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *);
41
int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info);
42
43
+enum follow_symlinks_result {
44
+ FOUND = 0, /* This includes out-of-tree links */
45
+ MISSING_OBJECT = -1, /* The initial symlink is missing */
46
+ DANGLING_SYMLINK = -2, /*
47
+ * The initial symlink is there, but
48
+ * (transitively) points to a missing
49
+ * in-tree file
50
+ */
51
+ SYMLINK_LOOP = -3,
52
+ NOT_DIR = -4, /*
53
+ * Somewhere along the symlink chain, a path is
54
+ * requested which contains a file as a
55
+ * non-final element.
56
+ */
57
+};
58
+
59
+enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode);
60
+
61
struct traverse_info {
62
struct traverse_info *prev;
63
struct name_entry name;