| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include <errno.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include <unistd.h> | ||
| 4 | #include "path.h" | ||
| 5 | #include "str-util.h" | ||
| 6 | |||
| 7 | 64 | char *path_absolute(const char *path) | |
| 8 | { | ||
| 9 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 63 times.
|
64 | if (unlikely(path[0] == '\0')) { |
| 10 | 1 | errno = EINVAL; | |
| 11 | 1 | return NULL; | |
| 12 | } | ||
| 13 | |||
| 14 | 63 | char *abs = realpath(path, NULL); | |
| 15 |
3/4✓ Branch 5 → 6 taken 29 times.
✓ Branch 5 → 17 taken 34 times.
✓ Branch 6 → 7 taken 29 times.
✗ Branch 6 → 17 not taken.
|
63 | if (abs || errno != ENOENT) { |
| 16 | return abs; | ||
| 17 | } | ||
| 18 | |||
| 19 | 29 | char target[8192]; | |
| 20 | 29 | ssize_t tlen = readlink(path, target, sizeof(target) - 1); | |
| 21 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 29 times.
|
29 | if (tlen >= 0) { |
| 22 | // Dangling symlink. Use the link target instead of the original | ||
| 23 | // `path` for the special case below. This avoids the path of the | ||
| 24 | // link itself being returned and other functions then opening | ||
| 25 | // (and potentially replacing) it, as if nonexistent. | ||
| 26 | ✗ | target[tlen] = '\0'; | |
| 27 | ✗ | path = target; | |
| 28 |
1/2✓ Branch 10 → 11 taken 29 times.
✗ Branch 10 → 17 not taken.
|
29 | } else if (errno != ENOENT) { |
| 29 | return NULL; | ||
| 30 | } | ||
| 31 | |||
| 32 | 29 | const char *base = path_basename(path); | |
| 33 | 29 | char *dir_relative = path_dirname(path); | |
| 34 | 29 | char *dir = realpath(dir_relative, NULL); | |
| 35 | 29 | free(dir_relative); | |
| 36 |
2/2✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 27 times.
|
29 | if (!dir) { |
| 37 | 2 | errno = ENOENT; | |
| 38 | 2 | return NULL; | |
| 39 | } | ||
| 40 | |||
| 41 | // If the full path doesn't exist but the directory part does, return | ||
| 42 | // the concatenation of the real directory and the non-existent filename | ||
| 43 | 27 | abs = path_join(dir, base); | |
| 44 | 27 | free(dir); | |
| 45 | 27 | return abs; | |
| 46 | } | ||
| 47 | |||
| 48 | 115 | static bool path_component(const char *path, size_t pos) | |
| 49 | { | ||
| 50 |
5/6✓ Branch 2 → 3 taken 114 times.
✓ Branch 2 → 6 taken 1 time.
✓ Branch 3 → 4 taken 114 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 110 times.
|
115 | return path[pos] == '\0' || pos == 0 || path[pos - 1] == '/'; |
| 51 | } | ||
| 52 | |||
| 53 | // Like path_relative(), but always returning either a "borrowed" pointer | ||
| 54 | // into `abs` or the static string ".", thus avoiding the need to allocate. | ||
| 55 | // This most notably means that "../" path components are never used, even | ||
| 56 | // if doing so would produce a path shorter than `abs`. | ||
| 57 | 8 | const char *path_slice_relative(const char *abs, const char *cwd) | |
| 58 | { | ||
| 59 | 8 | BUG_ON(!path_is_absolute(cwd)); | |
| 60 | 8 | BUG_ON(!path_is_absolute(abs)); | |
| 61 | |||
| 62 | // Special case | ||
| 63 |
2/2✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 10 taken 6 times.
|
8 | if (cwd[1] == '\0') { |
| 64 |
2/2✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 1 time.
|
2 | return abs[1] == '\0' ? abs : abs + 1; |
| 65 | } | ||
| 66 | |||
| 67 | 6 | size_t clen = str_common_prefix_length(cwd, abs); | |
| 68 |
2/2✓ Branch 10 → 11 taken 4 times.
✓ Branch 10 → 14 taken 2 times.
|
6 | if (cwd[clen] == '\0') { |
| 69 |
3/3✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 2 times.
✓ Branch 11 → 14 taken 1 time.
|
4 | switch (abs[clen]) { |
| 70 | 1 | case '\0': | |
| 71 | // Identical strings; abs is current directory | ||
| 72 | 1 | return "."; | |
| 73 | 2 | case '/': | |
| 74 | 2 | return abs + clen + 1; | |
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | return abs; | ||
| 79 | } | ||
| 80 | |||
| 81 | 139 | char *path_relative(const char *abs, const char *cwd) | |
| 82 | { | ||
| 83 | 139 | BUG_ON(!path_is_absolute(cwd)); | |
| 84 | 139 | BUG_ON(!path_is_absolute(abs)); | |
| 85 | |||
| 86 | // Special case | ||
| 87 |
2/2✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 10 taken 137 times.
|
139 | if (cwd[1] == '\0') { |
| 88 |
2/2✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 1 time.
|
2 | return xstrdup(abs[1] == '\0' ? abs : abs + 1); |
| 89 | } | ||
| 90 | |||
| 91 | 137 | size_t clen = str_common_prefix_length(cwd, abs); | |
| 92 |
2/2✓ Branch 10 → 11 taken 79 times.
✓ Branch 10 → 14 taken 58 times.
|
137 | if (cwd[clen] == '\0') { |
| 93 |
3/3✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 13 taken 76 times.
✓ Branch 11 → 14 taken 1 time.
|
79 | switch (abs[clen]) { |
| 94 | 2 | case '\0': | |
| 95 | // Identical strings; abs is current directory | ||
| 96 | 2 | return xstrdup("."); | |
| 97 | 76 | case '/': | |
| 98 | // cwd = /home/user | ||
| 99 | // abs = /home/user/project-a/file.c | ||
| 100 | // common = /home/user | ||
| 101 | 76 | return xstrdup(abs + clen + 1); | |
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | // Common path components | ||
| 106 |
4/4✓ Branch 14 → 15 taken 56 times.
✓ Branch 14 → 16 taken 3 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 19 taken 55 times.
|
59 | if (!path_component(cwd, clen) || !path_component(abs, clen)) { |
| 107 |
3/4✓ Branch 17 → 18 taken 17 times.
✗ Branch 17 → 19 not taken.
✓ Branch 18 → 17 taken 13 times.
✓ Branch 18 → 19 taken 4 times.
|
17 | while (clen > 0 && abs[clen - 1] != '/') { |
| 108 | clen--; | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | // Number of "../" needed | ||
| 113 | 59 | size_t dotdot = 1; | |
| 114 | 59 | const char *slash = strchr(cwd + clen + 1, '/'); | |
| 115 |
2/2✓ Branch 19 → 20 taken 54 times.
✓ Branch 19 → 22 taken 5 times.
|
59 | if (slash) { |
| 116 | 54 | dotdot++; | |
| 117 |
2/2✓ Branch 20 → 21 taken 4 times.
✓ Branch 20 → 22 taken 50 times.
|
54 | if (strchr(slash + 1, '/')) { |
| 118 | // Just use absolute path if `dotdot` would be > 2 | ||
| 119 | 4 | return xstrdup(abs); | |
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | 55 | const char *tail = abs + clen; | |
| 124 | 55 | return xmemjoin("../../", 3 * dotdot, tail, strlen(tail) + 1); | |
| 125 | } | ||
| 126 | |||
| 127 | 129 | static bool path_has_dir_prefix(StringView path, StringView dir) | |
| 128 | { | ||
| 129 |
4/4✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 126 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 2 times.
|
129 | return strview_has_sv_prefix(path, dir) && path.data[dir.length] == '/'; |
| 130 | } | ||
| 131 | |||
| 132 | 129 | char *short_filename_cwd(const char *abs, const char *cwd, StringView home) | |
| 133 | { | ||
| 134 | 129 | StringView abs_sv = strview(abs); | |
| 135 | 129 | char *rel = path_relative(abs, cwd); | |
| 136 | 129 | size_t abs_len = abs_sv.length; | |
| 137 | 129 | size_t rel_len = strlen(rel); | |
| 138 | 129 | size_t suffix_len = (abs_len - home.length) + 1; | |
| 139 | |||
| 140 |
3/4✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 7 taken 127 times.
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 7 not taken.
|
129 | if (path_has_dir_prefix(abs_sv, home) && suffix_len < rel_len) { |
| 141 | // Prefer absolute in tilde notation (e.g. "~/abs/path"), if applicable | ||
| 142 | // and shorter than relative | ||
| 143 | 2 | rel[0] = '~'; | |
| 144 | 2 | memcpy(rel + 1, abs + home.length, suffix_len); | |
| 145 |
2/2✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 126 times.
|
127 | } else if (abs_len < rel_len) { |
| 146 | // Prefer absolute, if shorter than relative | ||
| 147 | 1 | memcpy(rel, abs, abs_len + 1); | |
| 148 | } | ||
| 149 | |||
| 150 | 129 | return rel; | |
| 151 | } | ||
| 152 | |||
| 153 | 51 | char *short_filename(const char *abs, StringView home) | |
| 154 | { | ||
| 155 | 51 | char buf[8192]; | |
| 156 | 51 | const char *cwd = getcwd(buf, sizeof buf); | |
| 157 |
1/2✓ Branch 3 → 4 taken 51 times.
✗ Branch 3 → 5 not taken.
|
51 | return likely(cwd) ? short_filename_cwd(abs, cwd, home) : xstrdup(abs); |
| 158 | } | ||
| 159 |