}
/*
- * path = absolute path
- * buf = buffer of at least max(2, strlen(path)+1) bytes
- * It is okay if buf == path, but they should not overlap otherwise.
+ * It is okay if dst == src, but they should not overlap otherwise.
*
- * Performs the following normalizations on path, storing the result in buf:
- * - Removes trailing slashes.
- * - Removes empty components.
+ * Performs the following normalizations on src, storing the result in dst:
+ * - Ensures that components are separated by '/' (Windows only)
+ * - Squashes sequences of '/'.
* - Removes "." components.
* - Removes ".." components, and the components the precede them.
- * "" and paths that contain only slashes are normalized to "/".
- * Returns the length of the output.
+ * Returns failure (non-zero) if a ".." component appears as first path
+ * component anytime during the normalization. Otherwise, returns success (0).
*
* Note that this function is purely textual. It does not follow symlinks,
* verify the existence of the path, or make any system calls.
*/
-int normalize_absolute_path(char *buf, const char *path)
-{
- const char *comp_start = path, *comp_end = path;
- char *dst = buf;
- int comp_len;
- assert(buf);
- assert(path);
-
- while (*comp_start) {
- assert(*comp_start == '/');
- while (*++comp_end && *comp_end != '/')
- ; /* nothing */
- comp_len = comp_end - comp_start;
-
- if (!strncmp("/", comp_start, comp_len) ||
- !strncmp("/.", comp_start, comp_len))
- goto next;
-
- if (!strncmp("/..", comp_start, comp_len)) {
- while (dst > buf && *--dst != '/')
- ; /* nothing */
- goto next;
- }
-
- memmove(dst, comp_start, comp_len);
- dst += comp_len;
- next:
- comp_start = comp_end;
- }
-
- if (dst == buf)
- *dst++ = '/';
-
- *dst = '\0';
- return dst - buf;
-}
-
int normalize_path_copy(char *dst, const char *src)
{
char *dst0;
* dst0..dst is prefix portion, and dst[-1] is '/';
* go up one level.
*/
- dst -= 2; /* go past trailing '/' if any */
- if (dst < dst0)
+ dst--; /* go to trailing '/' */
+ if (dst <= dst0)
return -1;
- while (1) {
- if (dst <= dst0)
- break;
- c = *dst--;
- if (c == '/') { /* MinGW: cannot be '\\' anymore */
- dst += 2;
- break;
- }
- }
+ /* Windows: dst[-1] cannot be backslash anymore */
+ while (dst0 < dst && dst[-1] != '/')
+ dst--;
}
*dst = '\0';
return 0;
return -1;
for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
- for (colon = ceil; *colon && *colon != ':'; colon++);
+ for (colon = ceil; *colon && *colon != PATH_SEP; colon++);
len = colon - ceil;
if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
continue;
strlcpy(buf, ceil, len+1);
- len = normalize_absolute_path(buf, buf);
- /* Strip "trailing slashes" from "/". */
- if (len == 1)
- len = 0;
+ if (normalize_path_copy(buf, buf) < 0)
+ continue;
+ len = strlen(buf);
+ if (len > 0 && buf[len-1] == '/')
+ buf[--len] = '\0';
if (!strncmp(path, buf, len) &&
path[len] == '/' &&
return max_len;
}
+
+/* strip arbitrary amount of directory separators at end of path */
+static inline int chomp_trailing_dir_sep(const char *path, int len)
+{
+ while (len && is_dir_sep(path[len - 1]))
+ len--;
+ return len;
+}
+
+/*
+ * If path ends with suffix (complete path components), returns the
+ * part before suffix (sans trailing directory separators).
+ * Otherwise returns NULL.
+ */
+char *strip_path_suffix(const char *path, const char *suffix)
+{
+ int path_len = strlen(path), suffix_len = strlen(suffix);
+
+ while (suffix_len) {
+ if (!path_len)
+ return NULL;
+
+ if (is_dir_sep(path[path_len - 1])) {
+ if (!is_dir_sep(suffix[suffix_len - 1]))
+ return NULL;
+ path_len = chomp_trailing_dir_sep(path, path_len);
+ suffix_len = chomp_trailing_dir_sep(suffix, suffix_len);
+ }
+ else if (path[--path_len] != suffix[--suffix_len])
+ return NULL;
+ }
+
+ if (path_len && !is_dir_sep(path[path_len - 1]))
+ return NULL;
+ return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
+}