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