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