Run clang-format on the whole tree
[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 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 24static char *utils_partial_realpath(const char *path)
4971b7f0
MD
25{
26 char *cut_path = NULL, *try_path = NULL, *try_path_prev = NULL;
27 const char *next, *prev, *end;
28 char *resolved_path = NULL;
29
30 /* Safety net */
31 if (path == NULL) {
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 */
53 while (try_path != NULL && next != end) {
54 char *try_path_buf = NULL;
55
56 /*
57 * If there is not any '/' left, we want to try with
58 * the full path
59 */
60 next = strpbrk(next + 1, "/");
61 if (next == NULL) {
62 next = end;
63 }
64
65 /* Cut the part we will be trying to resolve */
66 cut_path = lttng_strndup(path, next - path);
67 if (cut_path == NULL) {
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);
80 if (try_path == NULL) {
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 */
98 try_path_buf = NULL;
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);
106 cut_path = NULL;
107 }
108
109 /* Allocate memory for the resolved path. */
64803277 110 resolved_path = zmalloc<char>(LTTNG_PATH_MAX);
4971b7f0
MD
111 if (resolved_path == NULL) {
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 */
120 if (try_path_prev != NULL) {
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);
131 if (cut_path == NULL) {
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);
142 cut_path = NULL;
143 try_path_prev = NULL;
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
155error:
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 }
162 return NULL;
163}
164
28ab034a 165static 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);
190 if (next_slash == NULL) {
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 */
237 if (prev_slash != NULL) {
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;
261error:
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 275static char *_utils_expand_path(const char *path, bool keep_symlink)
4971b7f0
MD
276{
277 int ret;
278 char *absolute_path = NULL;
279 char *last_token;
280 bool is_dot, is_dotdot;
281
282 /* Safety net */
283 if (path == NULL) {
284 goto error;
285 }
286
287 /* Allocate memory for the absolute_path */
64803277 288 absolute_path = zmalloc<char>(LTTNG_PATH_MAX);
4971b7f0
MD
289 if (absolute_path == NULL) {
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
369error:
370 free(absolute_path);
371 return NULL;
372}
373char *utils_expand_path(const char *path)
374{
375 return _utils_expand_path(path, true);
376}
377
378char *utils_expand_path_keep_symlink(const char *path)
379{
380 return _utils_expand_path(path, false);
381}
This page took 0.045046 seconds and 4 git commands to generate.