Commit | Line | Data |
---|---|---|
4971b7f0 MD |
1 | /* |
2 | * Copyright (C) 2012 David Goulet <dgoulet@efficios.com> | |
3 | * Copyright (C) 2013 Raphaël Beamonte <raphael.beamonte@gmail.com> | |
4 | * Copyright (C) 2013 Jérémie Galarneau <jeremie.galarneau@efficios.com> | |
5 | * Copyright (C) 2021 Mathieu Desnoyers <mathieu.desnoyers@efficios.com> | |
6 | * | |
7 | * SPDX-License-Identifier: GPL-2.0-only | |
8 | */ | |
9 | ||
10 | #define _LGPL_SOURCE | |
c9e313bc | 11 | #include <common/common.hpp> |
28ab034a | 12 | #include <common/macros.hpp> |
c9e313bc | 13 | #include <common/path.hpp> |
4971b7f0 MD |
14 | |
15 | /* | |
16 | * Return a partial realpath(3) of the path even if the full path does not | |
17 | * exist. For instance, with /tmp/test1/test2/test3, if test2/ does not exist | |
18 | * but the /tmp/test1 does, the real path for /tmp/test1 is concatened with | |
19 | * /test2/test3 then returned. In normal time, realpath(3) fails if the end | |
20 | * point directory does not exist. | |
21 | * | |
22 | * Return a newly-allocated string. | |
23 | */ | |
28ab034a | 24 | static char *utils_partial_realpath(const char *path) |
4971b7f0 | 25 | { |
cd9adb8b | 26 | char *cut_path = nullptr, *try_path = nullptr, *try_path_prev = nullptr; |
4971b7f0 | 27 | const char *next, *prev, *end; |
cd9adb8b | 28 | char *resolved_path = nullptr; |
4971b7f0 MD |
29 | |
30 | /* Safety net */ | |
cd9adb8b | 31 | if (path == nullptr) { |
4971b7f0 MD |
32 | goto error; |
33 | } | |
34 | ||
35 | /* | |
36 | * Identify the end of the path, we don't want to treat the | |
37 | * last char if it is a '/', we will just keep it on the side | |
38 | * to be added at the end, and return a value coherent with | |
39 | * the path given as argument | |
40 | */ | |
41 | end = path + strlen(path); | |
28ab034a | 42 | if (*(end - 1) == '/') { |
4971b7f0 MD |
43 | end--; |
44 | } | |
45 | ||
46 | /* Initiate the values of the pointers before looping */ | |
47 | next = path; | |
48 | prev = next; | |
49 | /* Only to ensure try_path is not NULL to enter the while */ | |
28ab034a | 50 | try_path = (char *) next; |
4971b7f0 MD |
51 | |
52 | /* Resolve the canonical path of the first part of the path */ | |
cd9adb8b JG |
53 | while (try_path != nullptr && next != end) { |
54 | char *try_path_buf = nullptr; | |
4971b7f0 MD |
55 | |
56 | /* | |
57 | * If there is not any '/' left, we want to try with | |
58 | * the full path | |
59 | */ | |
60 | next = strpbrk(next + 1, "/"); | |
cd9adb8b | 61 | if (next == nullptr) { |
4971b7f0 MD |
62 | next = end; |
63 | } | |
64 | ||
65 | /* Cut the part we will be trying to resolve */ | |
66 | cut_path = lttng_strndup(path, next - path); | |
cd9adb8b | 67 | if (cut_path == nullptr) { |
4971b7f0 MD |
68 | PERROR("lttng_strndup"); |
69 | goto error; | |
70 | } | |
71 | ||
64803277 | 72 | try_path_buf = zmalloc<char>(LTTNG_PATH_MAX); |
4971b7f0 MD |
73 | if (!try_path_buf) { |
74 | PERROR("zmalloc"); | |
75 | goto error; | |
76 | } | |
77 | ||
78 | /* Try to resolve this part */ | |
79 | try_path = realpath((char *) cut_path, try_path_buf); | |
cd9adb8b | 80 | if (try_path == nullptr) { |
4971b7f0 MD |
81 | free(try_path_buf); |
82 | /* | |
83 | * There was an error, we just want to be assured it | |
84 | * is linked to an unexistent directory, if it's another | |
85 | * reason, we spawn an error | |
86 | */ | |
87 | switch (errno) { | |
88 | case ENOENT: | |
89 | /* Ignore the error */ | |
90 | break; | |
91 | default: | |
92 | PERROR("realpath (partial_realpath)"); | |
93 | goto error; | |
94 | break; | |
95 | } | |
96 | } else { | |
97 | /* Save the place we are before trying the next step */ | |
cd9adb8b | 98 | try_path_buf = nullptr; |
4971b7f0 MD |
99 | free(try_path_prev); |
100 | try_path_prev = try_path; | |
101 | prev = next; | |
102 | } | |
103 | ||
104 | /* Free the allocated memory */ | |
105 | free(cut_path); | |
cd9adb8b | 106 | cut_path = nullptr; |
4971b7f0 MD |
107 | } |
108 | ||
109 | /* Allocate memory for the resolved path. */ | |
64803277 | 110 | resolved_path = zmalloc<char>(LTTNG_PATH_MAX); |
cd9adb8b | 111 | if (resolved_path == nullptr) { |
4971b7f0 MD |
112 | PERROR("zmalloc resolved path"); |
113 | goto error; | |
114 | } | |
115 | ||
116 | /* | |
117 | * If we were able to solve at least partially the path, we can concatenate | |
118 | * what worked and what didn't work | |
119 | */ | |
cd9adb8b | 120 | if (try_path_prev != nullptr) { |
4971b7f0 MD |
121 | /* If we risk to concatenate two '/', we remove one of them */ |
122 | if (try_path_prev[strlen(try_path_prev) - 1] == '/' && prev[0] == '/') { | |
123 | try_path_prev[strlen(try_path_prev) - 1] = '\0'; | |
124 | } | |
125 | ||
126 | /* | |
127 | * Duplicate the memory used by prev in case resolved_path and | |
128 | * path are pointers for the same memory space | |
129 | */ | |
130 | cut_path = strdup(prev); | |
cd9adb8b | 131 | if (cut_path == nullptr) { |
4971b7f0 MD |
132 | PERROR("strdup"); |
133 | goto error; | |
134 | } | |
135 | ||
136 | /* Concatenate the strings */ | |
28ab034a | 137 | snprintf(resolved_path, LTTNG_PATH_MAX, "%s%s", try_path_prev, cut_path); |
4971b7f0 MD |
138 | |
139 | /* Free the allocated memory */ | |
140 | free(cut_path); | |
141 | free(try_path_prev); | |
cd9adb8b JG |
142 | cut_path = nullptr; |
143 | try_path_prev = nullptr; | |
28ab034a JG |
144 | /* |
145 | * Else, we just copy the path in our resolved_path to | |
146 | * return it as is | |
147 | */ | |
4971b7f0 MD |
148 | } else { |
149 | strncpy(resolved_path, path, LTTNG_PATH_MAX); | |
150 | } | |
151 | ||
152 | /* Then we return the 'partially' resolved path */ | |
153 | return resolved_path; | |
154 | ||
155 | error: | |
156 | free(resolved_path); | |
157 | free(cut_path); | |
158 | free(try_path); | |
159 | if (try_path_prev != try_path) { | |
160 | free(try_path_prev); | |
161 | } | |
cd9adb8b | 162 | return nullptr; |
4971b7f0 MD |
163 | } |
164 | ||
28ab034a | 165 | static int expand_double_slashes_dot_and_dotdot(char *path) |
4971b7f0 MD |
166 | { |
167 | size_t expanded_path_len, path_len; | |
168 | const char *curr_char, *path_last_char, *next_slash, *prev_slash; | |
169 | ||
170 | path_len = strlen(path); | |
171 | path_last_char = &path[path_len]; | |
172 | ||
173 | if (path_len == 0) { | |
174 | goto error; | |
175 | } | |
176 | ||
177 | expanded_path_len = 0; | |
178 | ||
179 | /* We iterate over the provided path to expand the "//", "../" and "./" */ | |
180 | for (curr_char = path; curr_char <= path_last_char; curr_char = next_slash + 1) { | |
181 | /* Find the next forward slash. */ | |
182 | size_t curr_token_len; | |
183 | ||
184 | if (curr_char == path_last_char) { | |
185 | expanded_path_len++; | |
186 | break; | |
187 | } | |
188 | ||
189 | next_slash = (const char *) memchr(curr_char, '/', path_last_char - curr_char); | |
cd9adb8b | 190 | if (next_slash == nullptr) { |
4971b7f0 MD |
191 | /* Reached the end of the provided path. */ |
192 | next_slash = path_last_char; | |
193 | } | |
194 | ||
195 | /* Compute how long is the previous token. */ | |
196 | curr_token_len = next_slash - curr_char; | |
28ab034a | 197 | switch (curr_token_len) { |
4971b7f0 MD |
198 | case 0: |
199 | /* | |
200 | * The pointer has not move meaning that curr_char is | |
201 | * pointing to a slash. It that case there is no token | |
202 | * to copy, so continue the iteration to find the next | |
203 | * token | |
204 | */ | |
205 | continue; | |
206 | case 1: | |
207 | /* | |
208 | * The pointer moved 1 character. Check if that | |
209 | * character is a dot ('.'), if it is: omit it, else | |
210 | * copy the token to the normalized path. | |
211 | */ | |
212 | if (curr_char[0] == '.') { | |
213 | continue; | |
214 | } | |
215 | break; | |
216 | case 2: | |
217 | /* | |
218 | * The pointer moved 2 characters. Check if these | |
219 | * characters are double dots ('..'). If that is the | |
220 | * case, we need to remove the last token of the | |
221 | * normalized path. | |
222 | */ | |
223 | if (curr_char[0] == '.' && curr_char[1] == '.') { | |
224 | /* | |
225 | * Find the previous path component by | |
226 | * using the memrchr function to find the | |
227 | * previous forward slash and substract that | |
228 | * len to the resulting path. | |
229 | */ | |
28ab034a JG |
230 | prev_slash = |
231 | (const char *) lttng_memrchr(path, '/', expanded_path_len); | |
4971b7f0 MD |
232 | /* |
233 | * If prev_slash is NULL, we reached the | |
234 | * beginning of the path. We can't go back any | |
235 | * further. | |
236 | */ | |
cd9adb8b | 237 | if (prev_slash != nullptr) { |
4971b7f0 MD |
238 | expanded_path_len = prev_slash - path; |
239 | } | |
240 | continue; | |
241 | } | |
242 | break; | |
243 | default: | |
244 | break; | |
245 | } | |
246 | ||
247 | /* | |
248 | * Copy the current token which is neither a '.' nor a '..'. | |
249 | */ | |
250 | path[expanded_path_len++] = '/'; | |
251 | memmove(&path[expanded_path_len], curr_char, curr_token_len); | |
252 | expanded_path_len += curr_token_len; | |
253 | } | |
254 | ||
255 | if (expanded_path_len == 0) { | |
256 | path[expanded_path_len++] = '/'; | |
257 | } | |
258 | ||
259 | path[expanded_path_len] = '\0'; | |
260 | return 0; | |
261 | error: | |
262 | return -1; | |
263 | } | |
264 | ||
265 | /* | |
266 | * Make a full resolution of the given path even if it doesn't exist. | |
267 | * This function uses the utils_partial_realpath function to resolve | |
268 | * symlinks and relatives paths at the start of the string, and | |
269 | * implements functionnalities to resolve the './' and '../' strings | |
270 | * in the middle of a path. This function is only necessary because | |
271 | * realpath(3) does not accept to resolve unexistent paths. | |
272 | * The returned string was allocated in the function, it is thus of | |
273 | * the responsibility of the caller to free this memory. | |
274 | */ | |
28ab034a | 275 | static char *_utils_expand_path(const char *path, bool keep_symlink) |
4971b7f0 MD |
276 | { |
277 | int ret; | |
cd9adb8b | 278 | char *absolute_path = nullptr; |
4971b7f0 MD |
279 | char *last_token; |
280 | bool is_dot, is_dotdot; | |
281 | ||
282 | /* Safety net */ | |
cd9adb8b | 283 | if (path == nullptr) { |
4971b7f0 MD |
284 | goto error; |
285 | } | |
286 | ||
287 | /* Allocate memory for the absolute_path */ | |
64803277 | 288 | absolute_path = zmalloc<char>(LTTNG_PATH_MAX); |
cd9adb8b | 289 | if (absolute_path == nullptr) { |
4971b7f0 MD |
290 | PERROR("zmalloc expand path"); |
291 | goto error; | |
292 | } | |
293 | ||
294 | if (path[0] == '/') { | |
295 | ret = lttng_strncpy(absolute_path, path, LTTNG_PATH_MAX); | |
296 | if (ret) { | |
297 | ERR("Path exceeds maximal size of %i bytes", LTTNG_PATH_MAX); | |
298 | goto error; | |
299 | } | |
300 | } else { | |
301 | /* | |
302 | * This is a relative path. We need to get the present working | |
303 | * directory and start the path walk from there. | |
304 | */ | |
305 | char current_working_dir[LTTNG_PATH_MAX]; | |
306 | char *cwd_ret; | |
307 | ||
308 | cwd_ret = getcwd(current_working_dir, sizeof(current_working_dir)); | |
309 | if (!cwd_ret) { | |
310 | goto error; | |
311 | } | |
312 | /* | |
313 | * Get the number of character in the CWD and allocate an array | |
314 | * to can hold it and the path provided by the caller. | |
315 | */ | |
28ab034a | 316 | ret = snprintf(absolute_path, LTTNG_PATH_MAX, "%s/%s", current_working_dir, path); |
4971b7f0 MD |
317 | if (ret >= LTTNG_PATH_MAX) { |
318 | ERR("Concatenating current working directory %s and path %s exceeds maximal size of %i bytes", | |
28ab034a JG |
319 | current_working_dir, |
320 | path, | |
321 | LTTNG_PATH_MAX); | |
4971b7f0 MD |
322 | goto error; |
323 | } | |
324 | } | |
325 | ||
326 | if (keep_symlink) { | |
327 | /* Resolve partially our path */ | |
328 | char *new_absolute_path = utils_partial_realpath(absolute_path); | |
329 | if (!new_absolute_path) { | |
330 | goto error; | |
331 | } | |
332 | ||
333 | free(absolute_path); | |
334 | absolute_path = new_absolute_path; | |
335 | } | |
336 | ||
337 | ret = expand_double_slashes_dot_and_dotdot(absolute_path); | |
338 | if (ret) { | |
339 | goto error; | |
340 | } | |
341 | ||
342 | /* Identify the last token */ | |
343 | last_token = strrchr(absolute_path, '/'); | |
344 | ||
345 | /* Verify that this token is not a relative path */ | |
346 | is_dotdot = (strcmp(last_token, "/..") == 0); | |
347 | is_dot = (strcmp(last_token, "/.") == 0); | |
348 | ||
349 | /* If it is, take action */ | |
350 | if (is_dot || is_dotdot) { | |
351 | /* For both, remove this token */ | |
352 | *last_token = '\0'; | |
353 | ||
354 | /* If it was a reference to parent directory, go back one more time */ | |
355 | if (is_dotdot) { | |
356 | last_token = strrchr(absolute_path, '/'); | |
357 | ||
358 | /* If there was only one level left, we keep the first '/' */ | |
359 | if (last_token == absolute_path) { | |
360 | last_token++; | |
361 | } | |
362 | ||
363 | *last_token = '\0'; | |
364 | } | |
365 | } | |
366 | ||
367 | return absolute_path; | |
368 | ||
369 | error: | |
370 | free(absolute_path); | |
cd9adb8b | 371 | return nullptr; |
4971b7f0 MD |
372 | } |
373 | char *utils_expand_path(const char *path) | |
374 | { | |
375 | return _utils_expand_path(path, true); | |
376 | } | |
377 | ||
378 | char *utils_expand_path_keep_symlink(const char *path) | |
379 | { | |
380 | return _utils_expand_path(path, false); | |
381 | } |