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;