Skip to content

Commit 275721c

Browse files
dturner-twgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Ramsay Jones <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 8440f74 commit 275721c

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed

tree-walk.c

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,12 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
415415
return error;
416416
}
417417

418+
struct dir_state {
419+
void *tree;
420+
unsigned long size;
421+
unsigned char sha1[20];
422+
};
423+
418424
static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
419425
{
420426
int namelen = strlen(name);
@@ -478,6 +484,206 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
478484
return retval;
479485
}
480486

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+
481687
static int match_entry(const struct pathspec_item *item,
482688
const struct name_entry *entry, int pathlen,
483689
const char *match, int matchlen,

tree-walk.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ struct traverse_info;
4040
typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *);
4141
int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info);
4242

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+
4361
struct traverse_info {
4462
struct traverse_info *prev;
4563
struct name_entry name;

0 commit comments

Comments
 (0)