Add utils_expand_path_keep_symlink fonction
authorFrancis Deslauriers <francis.deslauriers@efficios.com>
Wed, 27 Jun 2018 14:47:41 +0000 (10:47 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Fri, 24 Aug 2018 19:30:25 +0000 (15:30 -0400)
This function mimics the behaviour of utils_expand_path() but omits to
expand symbolic links. This is useful to keep the full path to a
user-provided path, which can contain a symlink.

We will use this of the lttng-save of sessions containing
userspace-probe.

Signed-off-by: Francis Deslauriers <francis.deslauriers@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
configure.ac
src/common/compat/string.h
src/common/utils.c
src/common/utils.h
tests/unit/test_utils_expand_path.c

index b7688eb7e8256b45454db8e99078ee81dfeddc90..d8625fc21ede93a5b5a2f56cef0eb20044dd4285 100644 (file)
@@ -194,7 +194,7 @@ AC_CHECK_HEADERS([ \
 # Basic functions check
 AC_CHECK_FUNCS([ \
        atexit bzero clock_gettime dup2 fdatasync fls ftruncate \
-       gethostbyname gethostname getpagesize localtime_r memchr memset \
+       gethostbyname gethostname getpagesize localtime_r memchr memrchr memset \
        mkdir munmap putenv realpath rmdir socket strchr strcspn strdup \
        strncasecmp strndup strnlen strpbrk strrchr strstr strtol strtoul \
        strtoull dirfd gethostbyname2 getipnodebyname epoll_create1 \
index db3db80522f5fe039f3f5f716f567f6676dfa87f..a7bcab0a8c345a6e34019b2419d424207372be10 100644 (file)
@@ -123,4 +123,25 @@ static inline int lttng_fls(int val)
 }
 #endif /* HAVE_FLS */
 
+#if HAVE_MEMRCHR
+static inline
+void *lttng_memrchr(const void *s, int c, size_t n)
+{
+       return memrchr(s, c, n);
+}
+#else
+static inline
+void *lttng_memrchr(const void *s, int c, size_t n)
+{
+       int i;
+       const char *str = s;
+       for (i = n-1; i >= 0; i--) {
+               if (str[i] == (char)c) {
+                       return (void *)(str+i);
+               }
+       }
+       return NULL;
+}
+#endif /* HAVE_MEMRCHR */
+
 #endif /* _COMPAT_STRING_H */
index e9fb552ac1011fc37569865baacb6fdbe29ca2de..84d42f4638e53f37fc139c4b1c6d5d6b1c30b375 100644 (file)
@@ -196,6 +196,107 @@ error:
        return NULL;
 }
 
+static
+char *expand_double_slashes_dot_and_dotdot(char *path)
+{
+       size_t expanded_path_len, path_len;
+       const char *curr_char, *path_last_char, *next_slash, *prev_slash;
+
+       path_len = strlen(path);
+       path_last_char = &path[path_len];
+
+       if (path_len == 0) {
+               path = NULL;
+               goto error;
+       }
+
+       expanded_path_len = 0;
+
+       /* We iterate over the provided path to expand the "//", "../" and "./" */
+       for (curr_char = path; curr_char <= path_last_char; curr_char = next_slash + 1) {
+               /* Find the next forward slash. */
+               size_t curr_token_len;
+
+               if (curr_char == path_last_char) {
+                       expanded_path_len++;
+                       break;
+               }
+
+               next_slash = memchr(curr_char, '/', path_last_char - curr_char);
+               if (next_slash == NULL) {
+                       /* Reached the end of the provided path. */
+                       next_slash = path_last_char;
+               }
+
+               /* Compute how long is the previous token. */
+               curr_token_len = next_slash - curr_char;
+               switch(curr_token_len) {
+               case 0:
+                       /*
+                        * The pointer has not move meaning that curr_char is
+                        * pointing to a slash. It that case there is no token
+                        * to copy, so continue the iteration to find the next
+                        * token
+                        */
+                       continue;
+               case 1:
+                       /*
+                        * The pointer moved 1 character. Check if that
+                        * character is a dot ('.'), if it is: omit it, else
+                        * copy the token to the normalized path.
+                        */
+                       if (curr_char[0] == '.') {
+                               continue;
+                       }
+                       break;
+               case 2:
+                       /*
+                        * The pointer moved 2 characters. Check if these
+                        * characters are double dots ('..'). If that is the
+                        * case, we need to remove the last token of the
+                        * normalized path.
+                        */
+                       if (curr_char[0] == '.' && curr_char[1] == '.') {
+                               /*
+                                * Find the previous path component by
+                                * using the memrchr function to find the
+                                * previous forward slash and substract that
+                                * len to the resulting path.
+                                */
+                               prev_slash = lttng_memrchr(path, '/', expanded_path_len);
+                               /*
+                                * If prev_slash is NULL, we reached the
+                                * beginning of the path. We can't go back any
+                                * further.
+                                */
+                               if (prev_slash != NULL) {
+                                       expanded_path_len = prev_slash - path;
+                               }
+                               continue;
+                       }
+                       break;
+               default:
+                       break;
+               }
+
+               /*
+                * Copy the current token which is neither a '.' nor a '..'.
+                */
+               path[expanded_path_len++] = '/';
+               memcpy(&path[expanded_path_len], curr_char, curr_token_len);
+               expanded_path_len += curr_token_len;
+       }
+
+       if (expanded_path_len == 0) {
+               path[expanded_path_len++] = '/';
+       }
+
+       path[expanded_path_len] = '\0';
+
+error:
+       return path;
+}
+
 /*
  * Make a full resolution of the given path even if it doesn't exist.
  * This function uses the utils_partial_realpath function to resolve
@@ -207,9 +308,9 @@ error:
  * the responsibility of the caller to free this memory.
  */
 LTTNG_HIDDEN
-char *utils_expand_path(const char *path)
+char *_utils_expand_path(const char *path, bool keep_symlink)
 {
-       char *next, *previous, *slash, *start_path, *absolute_path = NULL;
+       char *absolute_path = NULL;
        char *last_token;
        int is_dot, is_dotdot;
 
@@ -225,63 +326,38 @@ char *utils_expand_path(const char *path)
                goto error;
        }
 
-       /*
-        * If the path is not already absolute nor explicitly relative,
-        * consider we're in the current directory
-        */
-       if (*path != '/' && strncmp(path, "./", 2) != 0 &&
-                       strncmp(path, "../", 3) != 0) {
-               snprintf(absolute_path, PATH_MAX, "./%s", path);
-       /* Else, we just copy the path */
-       } else {
+       if (path[0] == '/') {
                strncpy(absolute_path, path, PATH_MAX);
-       }
-
-       /* Resolve partially our path */
-       absolute_path = utils_partial_realpath(absolute_path,
-                       absolute_path, PATH_MAX);
-
-       /* As long as we find '/./' in the working_path string */
-       while ((next = strstr(absolute_path, "/./"))) {
-
-               /* We prepare the start_path not containing it */
-               start_path = lttng_strndup(absolute_path, next - absolute_path);
-               if (!start_path) {
-                       PERROR("lttng_strndup");
+       } else {
+               /*
+                * This is a relative path. We need to get the present working
+                * directory and start the path walk from there.
+                */
+               char current_working_dir[PATH_MAX];
+               char *cwd_ret;
+               cwd_ret = getcwd(current_working_dir, sizeof(current_working_dir));
+               if (!cwd_ret) {
+                       absolute_path = NULL;
                        goto error;
                }
-               /* And we concatenate it with the part after this string */
-               snprintf(absolute_path, PATH_MAX, "%s%s", start_path, next + 2);
-
-               free(start_path);
+               /*
+                * Get the number of character in the CWD and allocate an array
+                * to can hold it and the path provided by the caller.
+                */
+               snprintf(absolute_path, PATH_MAX, "%s/%s", current_working_dir, path);
        }
 
-       /* As long as we find '/../' in the working_path string */
-       while ((next = strstr(absolute_path, "/../"))) {
-               /* We find the last level of directory */
-               previous = absolute_path;
-               while ((slash = strpbrk(previous, "/")) && slash != next) {
-                       previous = slash + 1;
-               }
-
-               /* Then we prepare the start_path not containing it */
-               start_path = lttng_strndup(absolute_path, previous - absolute_path);
-               if (!start_path) {
-                       PERROR("lttng_strndup");
-                       goto error;
-               }
-
-               /* And we concatenate it with the part after the '/../' */
-               snprintf(absolute_path, PATH_MAX, "%s%s", start_path, next + 4);
-
-               /* We can free the memory used for the start path*/
-               free(start_path);
-
-               /* Then we verify for symlinks using partial_realpath */
+       if (keep_symlink) {
+               /* Resolve partially our path */
                absolute_path = utils_partial_realpath(absolute_path,
                                absolute_path, PATH_MAX);
        }
 
+       absolute_path = expand_double_slashes_dot_and_dotdot(absolute_path);
+       if (!absolute_path) {
+               goto error;
+       }
+
        /* Identify the last token */
        last_token = strrchr(absolute_path, '/');
 
@@ -313,7 +389,17 @@ error:
        free(absolute_path);
        return NULL;
 }
+LTTNG_HIDDEN
+char *utils_expand_path(const char *path)
+{
+       return _utils_expand_path(path, true);
+}
 
+LTTNG_HIDDEN
+char *utils_expand_path_keep_symlink(const char *path)
+{
+       return _utils_expand_path(path, false);
+}
 /*
  * Create a pipe in dst.
  */
index 9293b1d0ac03d44bf6bc052cb95af1a8fdbf7641..1221f0eec7336ee4165fcfb7b3360916e7f61599 100644 (file)
@@ -30,6 +30,7 @@
 char *utils_partial_realpath(const char *path, char *resolved_path,
                size_t size);
 char *utils_expand_path(const char *path);
+char *utils_expand_path_keep_symlink(const char *path);
 int utils_create_pipe(int *dst);
 int utils_create_pipe_cloexec(int *dst);
 int utils_create_pipe_cloexec_nonblock(int *dst);
index aa7984b4026b38e4385f11d26b07545444c9bb1c..d5cab002e9096ade325d9cc820586aec96f75442 100644 (file)
@@ -53,6 +53,7 @@ struct symlink_test_input {
 /* Valid test cases */
 static struct valid_test_input valid_tests_inputs[] = {
        { "/a/b/c/d/e",                 "",             "/a/b/c/d/e"    },
+       { "/a//b//c/d/e",               "",             "/a/b/c/d/e"    },
        { "./a/b/c/d/e",                ".",            "/a/b/c/d/e"    },
        { "../a/b/c/d/../e",            "..",           "/a/b/c/e"      },
        { ".././a/b/c/d/./e",           "..",           "/a/b/c/d/e"    },
This page took 0.03119 seconds and 4 git commands to generate.