real_path: resolve symlinks by hand

The current implementation of real_path uses chdir() in order to resolve symlinks. Unfortunately this isn't thread-safe as chdir() affects a process as a whole and not just an individual thread. Instead perform the symlink resolution by hand so that the calls to chdir() can be removed, making real_path one step closer to being reentrant. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brandon Williams committed Dec 12, 2016 at 10:16 UTC 05b458c104708141d2fad211d79703b3b99cc5a8
1 file changed +129 -61
abspath.c
+129 -61
@@ -11,8 +11,45 @@ int is_directory(const char *path)
11 return (!stat(path, &st) && S_ISDIR(st.st_mode));
12 }
13
14 +/* removes the last path component from 'path' except if 'path' is root */
15 +static void strip_last_component(struct strbuf *path)
16 +{
17 + size_t offset = offset_1st_component(path->buf);
18 + size_t len = path->len;
19 +
20 + /* Find start of the last component */
21 + while (offset < len && !is_dir_sep(path->buf[len - 1]))
22 + len--;
23 + /* Skip sequences of multiple path-separators */
24 + while (offset < len && is_dir_sep(path->buf[len - 1]))
25 + len--;
26 +
27 + strbuf_setlen(path, len);
28 +}
29 +
30 +/* get (and remove) the next component in 'remaining' and place it in 'next' */
31 +static void get_next_component(struct strbuf *next, struct strbuf *remaining)
32 +{
33 + char *start = NULL;
34 + char *end = NULL;
35 +
36 + strbuf_reset(next);
37 +
38 + /* look for the next component */
39 + /* Skip sequences of multiple path-separators */
40 + for (start = remaining->buf; is_dir_sep(*start); start++)
41 + ; /* nothing */
42 + /* Find end of the path component */
43 + for (end = start; *end && !is_dir_sep(*end); end++)
44 + ; /* nothing */
45 +
46 + strbuf_add(next, start, end - start);
47 + /* remove the component from 'remaining' */
48 + strbuf_remove(remaining, 0, end - remaining->buf);
49 +}
50 +
51 /* We allow "recursive" symbolic links. Only within reason, though. */
15 -#define MAXDEPTH 5
52 +#define MAXSYMLINKS 5
53
54 /*
55 * Return the real path (i.e., absolute path, with symlinks resolved
@@ -21,7 +58,6 @@ int is_directory(const char *path)
58 * absolute_path().) The return value is a pointer to a static
59 * buffer.
60 *
24 - * The input and all intermediate paths must be shorter than MAX_PATH.
61 * The directory part of path (i.e., everything up to the last
62 * dir_sep) must denote a valid, existing directory, but the last
63 * component need not exist. If die_on_error is set, then die with an
@@ -33,22 +69,16 @@ int is_directory(const char *path)
69 */
70 static const char *real_path_internal(const char *path, int die_on_error)
71 {
36 - static struct strbuf sb = STRBUF_INIT;
72 + static struct strbuf resolved = STRBUF_INIT;
73 + struct strbuf remaining = STRBUF_INIT;
74 + struct strbuf next = STRBUF_INIT;
75 + struct strbuf symlink = STRBUF_INIT;
76 char *retval = NULL;
38 -
39 - /*
40 - * If we have to temporarily chdir(), store the original CWD
41 - * here so that we can chdir() back to it at the end of the
42 - * function:
43 - */
44 - struct strbuf cwd = STRBUF_INIT;
45 -
46 - int depth = MAXDEPTH;
47 - char *last_elem = NULL;
77 + int num_symlinks = 0;
78 struct stat st;
79
80 /* We've already done it */
51 - if (path == sb.buf)
81 + if (path == resolved.buf)
82 return path;
83
84 if (!*path) {
@@ -58,74 +88,112 @@ static const char *real_path_internal(const char *path, int die_on_error)
88 goto error_out;
89 }
90
61 - strbuf_reset(&sb);
62 - strbuf_addstr(&sb, path);
63 -
64 - while (depth--) {
65 - if (!is_directory(sb.buf)) {
66 - char *last_slash = find_last_dir_sep(sb.buf);
67 - if (last_slash) {
68 - last_elem = xstrdup(last_slash + 1);
69 - strbuf_setlen(&sb, last_slash - sb.buf + 1);
70 - } else {
71 - last_elem = xmemdupz(sb.buf, sb.len);
72 - strbuf_reset(&sb);
73 - }
91 + strbuf_reset(&resolved);
92 +
93 + if (is_absolute_path(path)) {
94 + /* absolute path; start with only root as being resolved */
95 + int offset = offset_1st_component(path);
96 + strbuf_add(&resolved, path, offset);
97 + strbuf_addstr(&remaining, path + offset);
98 + } else {
99 + /* relative path; can use CWD as the initial resolved path */
100 + if (strbuf_getcwd(&resolved)) {
101 + if (die_on_error)
102 + die_errno("unable to get current working directory");
103 + else
104 + goto error_out;
105 }
106 + strbuf_addstr(&remaining, path);
107 + }
108
76 - if (sb.len) {
77 - if (!cwd.len && strbuf_getcwd(&cwd)) {
109 + /* Iterate over the remaining path components */
110 + while (remaining.len > 0) {
111 + get_next_component(&next, &remaining);
112 +
113 + if (next.len == 0) {
114 + continue; /* empty component */
115 + } else if (next.len == 1 && !strcmp(next.buf, ".")) {
116 + continue; /* '.' component */
117 + } else if (next.len == 2 && !strcmp(next.buf, "..")) {
118 + /* '..' component; strip the last path component */
119 + strip_last_component(&resolved);
120 + continue;
121 + }
122 +
123 + /* append the next component and resolve resultant path */
124 + if (!is_dir_sep(resolved.buf[resolved.len - 1]))
125 + strbuf_addch(&resolved, '/');
126 + strbuf_addbuf(&resolved, &next);
127 +
128 + if (lstat(resolved.buf, &st)) {
129 + /* error out unless this was the last component */
130 + if (errno != ENOENT || remaining.len) {
131 if (die_on_error)
79 - die_errno("Could not get current working directory");
132 + die_errno("Invalid path '%s'",
133 + resolved.buf);
134 else
135 goto error_out;
136 }
137 + } else if (S_ISLNK(st.st_mode)) {
138 + ssize_t len;
139 + strbuf_reset(&symlink);
140
84 - if (chdir(sb.buf)) {
141 + if (num_symlinks++ > MAXSYMLINKS) {
142 if (die_on_error)
86 - die_errno("Could not switch to '%s'",
87 - sb.buf);
143 + die("More than %d nested symlinks "
144 + "on path '%s'", MAXSYMLINKS, path);
145 else
146 goto error_out;
147 }
91 - }
92 - if (strbuf_getcwd(&sb)) {
93 - if (die_on_error)
94 - die_errno("Could not get current working directory");
95 - else
96 - goto error_out;
97 - }
98 -
99 - if (last_elem) {
100 - if (sb.len && !is_dir_sep(sb.buf[sb.len - 1]))
101 - strbuf_addch(&sb, '/');
102 - strbuf_addstr(&sb, last_elem);
103 - free(last_elem);
104 - last_elem = NULL;
105 - }
148
107 - if (!lstat(sb.buf, &st) && S_ISLNK(st.st_mode)) {
108 - struct strbuf next_sb = STRBUF_INIT;
109 - ssize_t len = strbuf_readlink(&next_sb, sb.buf, 0);
149 + len = strbuf_readlink(&symlink, resolved.buf,
150 + st.st_size);
151 if (len < 0) {
152 if (die_on_error)
153 die_errno("Invalid symlink '%s'",
113 - sb.buf);
154 + resolved.buf);
155 else
156 goto error_out;
157 }
117 - strbuf_swap(&sb, &next_sb);
118 - strbuf_release(&next_sb);
119 - } else
120 - break;
158 +
159 + if (is_absolute_path(symlink.buf)) {
160 + /* absolute symlink; set resolved to root */
161 + int offset = offset_1st_component(symlink.buf);
162 + strbuf_reset(&resolved);
163 + strbuf_add(&resolved, symlink.buf, offset);
164 + strbuf_remove(&symlink, 0, offset);
165 + } else {
166 + /*
167 + * relative symlink
168 + * strip off the last component since it will
169 + * be replaced with the contents of the symlink
170 + */
171 + strip_last_component(&resolved);
172 + }
173 +
174 + /*
175 + * if there are still remaining components to resolve
176 + * then append them to symlink
177 + */
178 + if (remaining.len) {
179 + strbuf_addch(&symlink, '/');
180 + strbuf_addbuf(&symlink, &remaining);
181 + }
182 +
183 + /*
184 + * use the symlink as the remaining components that
185 + * need to be resloved
186 + */
187 + strbuf_swap(&symlink, &remaining);
188 + }
189 }
190
123 - retval = sb.buf;
191 + retval = resolved.buf;
192 +
193 error_out:
125 - free(last_elem);
126 - if (cwd.len && chdir(cwd.buf))
127 - die_errno("Could not change back to '%s'", cwd.buf);
128 - strbuf_release(&cwd);
194 + strbuf_release(&remaining);
195 + strbuf_release(&next);
196 + strbuf_release(&symlink);
197
198 return retval;
199 }