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