Elijah Newren | 0b027f6 | 2023-03-21 06:25:58 +0000 | [diff] [blame] | 1 | #include "git-compat-util.h" |
| 2 | #include "abspath.h" |
| 3 | #include "strbuf.h" |
Dmitry Potapov | 5b8e6f8 | 2008-06-28 00:46:42 +0400 | [diff] [blame] | 4 | |
Junio C Hamano | 90b4a71 | 2008-09-09 01:27:07 -0700 | [diff] [blame] | 5 | /* |
| 6 | * Do not use this for inspecting *tracked* content. When path is a |
| 7 | * symlink to a directory, we do not want to say it is a directory when |
| 8 | * dealing with tracked content in the working tree. |
| 9 | */ |
| 10 | int is_directory(const char *path) |
| 11 | { |
| 12 | struct stat st; |
| 13 | return (!stat(path, &st) && S_ISDIR(st.st_mode)); |
| 14 | } |
| 15 | |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 16 | /* removes the last path component from 'path' except if 'path' is root */ |
| 17 | static void strip_last_component(struct strbuf *path) |
| 18 | { |
| 19 | size_t offset = offset_1st_component(path->buf); |
| 20 | size_t len = path->len; |
| 21 | |
| 22 | /* Find start of the last component */ |
| 23 | while (offset < len && !is_dir_sep(path->buf[len - 1])) |
| 24 | len--; |
| 25 | /* Skip sequences of multiple path-separators */ |
| 26 | while (offset < len && is_dir_sep(path->buf[len - 1])) |
| 27 | len--; |
| 28 | |
| 29 | strbuf_setlen(path, len); |
| 30 | } |
| 31 | |
| 32 | /* get (and remove) the next component in 'remaining' and place it in 'next' */ |
| 33 | static void get_next_component(struct strbuf *next, struct strbuf *remaining) |
| 34 | { |
| 35 | char *start = NULL; |
| 36 | char *end = NULL; |
| 37 | |
| 38 | strbuf_reset(next); |
| 39 | |
| 40 | /* look for the next component */ |
| 41 | /* Skip sequences of multiple path-separators */ |
| 42 | for (start = remaining->buf; is_dir_sep(*start); start++) |
| 43 | ; /* nothing */ |
| 44 | /* Find end of the path component */ |
| 45 | for (end = start; *end && !is_dir_sep(*end); end++) |
| 46 | ; /* nothing */ |
| 47 | |
| 48 | strbuf_add(next, start, end - start); |
| 49 | /* remove the component from 'remaining' */ |
| 50 | strbuf_remove(remaining, 0, end - remaining->buf); |
| 51 | } |
| 52 | |
Johannes Sixt | e9a379c | 2016-12-21 22:51:35 +0100 | [diff] [blame] | 53 | /* copies root part from remaining to resolved, canonicalizing it on the way */ |
| 54 | static void get_root_part(struct strbuf *resolved, struct strbuf *remaining) |
| 55 | { |
| 56 | int offset = offset_1st_component(remaining->buf); |
| 57 | |
| 58 | strbuf_reset(resolved); |
| 59 | strbuf_add(resolved, remaining->buf, offset); |
| 60 | #ifdef GIT_WINDOWS_NATIVE |
| 61 | convert_slashes(resolved->buf); |
| 62 | #endif |
| 63 | strbuf_remove(remaining, 0, offset); |
| 64 | } |
| 65 | |
Dmitry Potapov | 5b8e6f8 | 2008-06-28 00:46:42 +0400 | [diff] [blame] | 66 | /* We allow "recursive" symbolic links. Only within reason, though. */ |
Brandon Williams | 7aeb81f | 2017-01-09 10:50:23 -0800 | [diff] [blame] | 67 | #ifndef MAXSYMLINKS |
| 68 | #define MAXSYMLINKS 32 |
| 69 | #endif |
Dmitry Potapov | 5b8e6f8 | 2008-06-28 00:46:42 +0400 | [diff] [blame] | 70 | |
Carlos Martín Nieto | e2a57aa | 2011-03-17 12:26:46 +0100 | [diff] [blame] | 71 | /* |
brian m. carlson | be6e0da | 2020-12-13 00:25:28 +0000 | [diff] [blame] | 72 | * If set, any number of trailing components may be missing; otherwise, only one |
| 73 | * may be. |
Carlos Martín Nieto | e2a57aa | 2011-03-17 12:26:46 +0100 | [diff] [blame] | 74 | */ |
brian m. carlson | be6e0da | 2020-12-13 00:25:28 +0000 | [diff] [blame] | 75 | #define REALPATH_MANY_MISSING (1 << 0) |
| 76 | /* Should we die if there's an error? */ |
| 77 | #define REALPATH_DIE_ON_ERROR (1 << 1) |
| 78 | |
| 79 | static char *strbuf_realpath_1(struct strbuf *resolved, const char *path, |
| 80 | int flags) |
Dmitry Potapov | 5b8e6f8 | 2008-06-28 00:46:42 +0400 | [diff] [blame] | 81 | { |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 82 | struct strbuf remaining = STRBUF_INIT; |
| 83 | struct strbuf next = STRBUF_INIT; |
| 84 | struct strbuf symlink = STRBUF_INIT; |
Michael Haggerty | 038e55f | 2012-10-28 17:16:20 +0100 | [diff] [blame] | 85 | char *retval = NULL; |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 86 | int num_symlinks = 0; |
Dmitry Potapov | 5b8e6f8 | 2008-06-28 00:46:42 +0400 | [diff] [blame] | 87 | struct stat st; |
| 88 | |
Michael Haggerty | 038e55f | 2012-10-28 17:16:20 +0100 | [diff] [blame] | 89 | if (!*path) { |
brian m. carlson | be6e0da | 2020-12-13 00:25:28 +0000 | [diff] [blame] | 90 | if (flags & REALPATH_DIE_ON_ERROR) |
Michael Haggerty | 038e55f | 2012-10-28 17:16:20 +0100 | [diff] [blame] | 91 | die("The empty string is not a valid path"); |
| 92 | else |
| 93 | goto error_out; |
| 94 | } |
Michael Haggerty | 3efe5d1 | 2012-09-07 00:41:01 +0200 | [diff] [blame] | 95 | |
Johannes Sixt | e9a379c | 2016-12-21 22:51:35 +0100 | [diff] [blame] | 96 | strbuf_addstr(&remaining, path); |
| 97 | get_root_part(resolved, &remaining); |
Dmitry Potapov | 5b8e6f8 | 2008-06-28 00:46:42 +0400 | [diff] [blame] | 98 | |
Johannes Sixt | e9a379c | 2016-12-21 22:51:35 +0100 | [diff] [blame] | 99 | if (!resolved->len) { |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 100 | /* relative path; can use CWD as the initial resolved path */ |
Brandon Williams | a1ae484 | 2016-12-12 10:16:53 -0800 | [diff] [blame] | 101 | if (strbuf_getcwd(resolved)) { |
brian m. carlson | be6e0da | 2020-12-13 00:25:28 +0000 | [diff] [blame] | 102 | if (flags & REALPATH_DIE_ON_ERROR) |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 103 | die_errno("unable to get current working directory"); |
Michael Haggerty | 038e55f | 2012-10-28 17:16:20 +0100 | [diff] [blame] | 104 | else |
| 105 | goto error_out; |
| 106 | } |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 107 | } |
Dmitry Potapov | 5b8e6f8 | 2008-06-28 00:46:42 +0400 | [diff] [blame] | 108 | |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 109 | /* Iterate over the remaining path components */ |
| 110 | while (remaining.len > 0) { |
| 111 | get_next_component(&next, &remaining); |
| 112 | |
| 113 | if (next.len == 0) { |
| 114 | continue; /* empty component */ |
| 115 | } else if (next.len == 1 && !strcmp(next.buf, ".")) { |
| 116 | continue; /* '.' component */ |
| 117 | } else if (next.len == 2 && !strcmp(next.buf, "..")) { |
| 118 | /* '..' component; strip the last path component */ |
Brandon Williams | a1ae484 | 2016-12-12 10:16:53 -0800 | [diff] [blame] | 119 | strip_last_component(resolved); |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 120 | continue; |
Dmitry Potapov | 5b8e6f8 | 2008-06-28 00:46:42 +0400 | [diff] [blame] | 121 | } |
| 122 | |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 123 | /* append the next component and resolve resultant path */ |
Brandon Williams | a1ae484 | 2016-12-12 10:16:53 -0800 | [diff] [blame] | 124 | if (!is_dir_sep(resolved->buf[resolved->len - 1])) |
| 125 | strbuf_addch(resolved, '/'); |
| 126 | strbuf_addbuf(resolved, &next); |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 127 | |
Brandon Williams | a1ae484 | 2016-12-12 10:16:53 -0800 | [diff] [blame] | 128 | if (lstat(resolved->buf, &st)) { |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 129 | /* error out unless this was the last component */ |
brian m. carlson | be6e0da | 2020-12-13 00:25:28 +0000 | [diff] [blame] | 130 | if (errno != ENOENT || |
| 131 | (!(flags & REALPATH_MANY_MISSING) && remaining.len)) { |
| 132 | if (flags & REALPATH_DIE_ON_ERROR) |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 133 | die_errno("Invalid path '%s'", |
Brandon Williams | a1ae484 | 2016-12-12 10:16:53 -0800 | [diff] [blame] | 134 | resolved->buf); |
Michael Haggerty | 038e55f | 2012-10-28 17:16:20 +0100 | [diff] [blame] | 135 | else |
| 136 | goto error_out; |
| 137 | } |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 138 | } else if (S_ISLNK(st.st_mode)) { |
| 139 | ssize_t len; |
| 140 | strbuf_reset(&symlink); |
| 141 | |
| 142 | if (num_symlinks++ > MAXSYMLINKS) { |
Brandon Williams | 0b9864a | 2017-01-09 10:50:24 -0800 | [diff] [blame] | 143 | errno = ELOOP; |
| 144 | |
brian m. carlson | be6e0da | 2020-12-13 00:25:28 +0000 | [diff] [blame] | 145 | if (flags & REALPATH_DIE_ON_ERROR) |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 146 | die("More than %d nested symlinks " |
| 147 | "on path '%s'", MAXSYMLINKS, path); |
| 148 | else |
| 149 | goto error_out; |
| 150 | } |
| 151 | |
Brandon Williams | a1ae484 | 2016-12-12 10:16:53 -0800 | [diff] [blame] | 152 | len = strbuf_readlink(&symlink, resolved->buf, |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 153 | st.st_size); |
| 154 | if (len < 0) { |
brian m. carlson | be6e0da | 2020-12-13 00:25:28 +0000 | [diff] [blame] | 155 | if (flags & REALPATH_DIE_ON_ERROR) |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 156 | die_errno("Invalid symlink '%s'", |
Brandon Williams | a1ae484 | 2016-12-12 10:16:53 -0800 | [diff] [blame] | 157 | resolved->buf); |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 158 | else |
| 159 | goto error_out; |
| 160 | } |
| 161 | |
| 162 | if (is_absolute_path(symlink.buf)) { |
| 163 | /* absolute symlink; set resolved to root */ |
Johannes Sixt | e9a379c | 2016-12-21 22:51:35 +0100 | [diff] [blame] | 164 | get_root_part(resolved, &symlink); |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 165 | } else { |
| 166 | /* |
| 167 | * relative symlink |
| 168 | * strip off the last component since it will |
| 169 | * be replaced with the contents of the symlink |
| 170 | */ |
Brandon Williams | a1ae484 | 2016-12-12 10:16:53 -0800 | [diff] [blame] | 171 | strip_last_component(resolved); |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 172 | } |
| 173 | |
| 174 | /* |
| 175 | * if there are still remaining components to resolve |
| 176 | * then append them to symlink |
| 177 | */ |
| 178 | if (remaining.len) { |
| 179 | strbuf_addch(&symlink, '/'); |
| 180 | strbuf_addbuf(&symlink, &remaining); |
| 181 | } |
| 182 | |
| 183 | /* |
| 184 | * use the symlink as the remaining components that |
Ville Skyttä | 6412757 | 2017-06-25 13:20:41 +0300 | [diff] [blame] | 185 | * need to be resolved |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 186 | */ |
| 187 | strbuf_swap(&symlink, &remaining); |
| 188 | } |
Dmitry Potapov | 5b8e6f8 | 2008-06-28 00:46:42 +0400 | [diff] [blame] | 189 | } |
| 190 | |
Brandon Williams | a1ae484 | 2016-12-12 10:16:53 -0800 | [diff] [blame] | 191 | retval = resolved->buf; |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 192 | |
Michael Haggerty | 038e55f | 2012-10-28 17:16:20 +0100 | [diff] [blame] | 193 | error_out: |
Brandon Williams | 05b458c | 2016-12-12 10:16:52 -0800 | [diff] [blame] | 194 | strbuf_release(&remaining); |
| 195 | strbuf_release(&next); |
| 196 | strbuf_release(&symlink); |
Dmitry Potapov | 5b8e6f8 | 2008-06-28 00:46:42 +0400 | [diff] [blame] | 197 | |
Brandon Williams | a1ae484 | 2016-12-12 10:16:53 -0800 | [diff] [blame] | 198 | if (!retval) |
| 199 | strbuf_reset(resolved); |
| 200 | |
Michael Haggerty | 038e55f | 2012-10-28 17:16:20 +0100 | [diff] [blame] | 201 | return retval; |
| 202 | } |
| 203 | |
brian m. carlson | be6e0da | 2020-12-13 00:25:28 +0000 | [diff] [blame] | 204 | /* |
| 205 | * Return the real path (i.e., absolute path, with symlinks resolved |
| 206 | * and extra slashes removed) equivalent to the specified path. (If |
| 207 | * you want an absolute path but don't mind links, use |
| 208 | * absolute_path().) Places the resolved realpath in the provided strbuf. |
| 209 | * |
| 210 | * The directory part of path (i.e., everything up to the last |
| 211 | * dir_sep) must denote a valid, existing directory, but the last |
| 212 | * component need not exist. If die_on_error is set, then die with an |
| 213 | * informative error message if there is a problem. Otherwise, return |
| 214 | * NULL on errors (without generating any output). |
| 215 | */ |
| 216 | char *strbuf_realpath(struct strbuf *resolved, const char *path, |
| 217 | int die_on_error) |
| 218 | { |
| 219 | return strbuf_realpath_1(resolved, path, |
| 220 | die_on_error ? REALPATH_DIE_ON_ERROR : 0); |
| 221 | } |
| 222 | |
| 223 | /* |
| 224 | * Just like strbuf_realpath, but allows an arbitrary number of path |
| 225 | * components to be missing. |
| 226 | */ |
| 227 | char *strbuf_realpath_forgiving(struct strbuf *resolved, const char *path, |
| 228 | int die_on_error) |
| 229 | { |
| 230 | return strbuf_realpath_1(resolved, path, |
| 231 | ((die_on_error ? REALPATH_DIE_ON_ERROR : 0) | |
| 232 | REALPATH_MANY_MISSING)); |
| 233 | } |
| 234 | |
Johannes Schindelin | ce83ead | 2017-03-08 16:43:40 +0100 | [diff] [blame] | 235 | char *real_pathdup(const char *path, int die_on_error) |
Brandon Williams | 7241764 | 2016-12-12 10:16:54 -0800 | [diff] [blame] | 236 | { |
| 237 | struct strbuf realpath = STRBUF_INIT; |
| 238 | char *retval = NULL; |
| 239 | |
Johannes Schindelin | ce83ead | 2017-03-08 16:43:40 +0100 | [diff] [blame] | 240 | if (strbuf_realpath(&realpath, path, die_on_error)) |
Brandon Williams | 7241764 | 2016-12-12 10:16:54 -0800 | [diff] [blame] | 241 | retval = strbuf_detach(&realpath, NULL); |
| 242 | |
| 243 | strbuf_release(&realpath); |
| 244 | |
| 245 | return retval; |
| 246 | } |
| 247 | |
Carlos Martín Nieto | e2a57aa | 2011-03-17 12:26:46 +0100 | [diff] [blame] | 248 | /* |
| 249 | * Use this to get an absolute path from a relative one. If you want |
Alexandr Miloslavskiy | 3d7747e | 2020-03-10 13:11:22 +0000 | [diff] [blame] | 250 | * to resolve links, you should use strbuf_realpath. |
Carlos Martín Nieto | e2a57aa | 2011-03-17 12:26:46 +0100 | [diff] [blame] | 251 | */ |
| 252 | const char *absolute_path(const char *path) |
Johannes Sixt | 10c4c88 | 2008-07-21 21:19:55 +0200 | [diff] [blame] | 253 | { |
René Scharfe | 679eebe | 2014-07-28 20:33:55 +0200 | [diff] [blame] | 254 | static struct strbuf sb = STRBUF_INIT; |
| 255 | strbuf_reset(&sb); |
| 256 | strbuf_add_absolute_path(&sb, path); |
| 257 | return sb.buf; |
Johannes Sixt | 10c4c88 | 2008-07-21 21:19:55 +0200 | [diff] [blame] | 258 | } |
Dmitry Ivankov | 0687628 | 2011-08-11 15:15:38 +0600 | [diff] [blame] | 259 | |
René Scharfe | b1edb40 | 2017-01-26 18:47:45 +0100 | [diff] [blame] | 260 | char *absolute_pathdup(const char *path) |
| 261 | { |
| 262 | struct strbuf sb = STRBUF_INIT; |
| 263 | strbuf_add_absolute_path(&sb, path); |
| 264 | return strbuf_detach(&sb, NULL); |
| 265 | } |
| 266 | |
Jeff King | e4da43b | 2017-03-20 21:28:49 -0400 | [diff] [blame] | 267 | char *prefix_filename(const char *pfx, const char *arg) |
Dmitry Ivankov | 0687628 | 2011-08-11 15:15:38 +0600 | [diff] [blame] | 268 | { |
Jeff King | e4da43b | 2017-03-20 21:28:49 -0400 | [diff] [blame] | 269 | struct strbuf path = STRBUF_INIT; |
Jeff King | 116fb64 | 2017-03-20 21:22:28 -0400 | [diff] [blame] | 270 | size_t pfx_len = pfx ? strlen(pfx) : 0; |
| 271 | |
Jeff King | af10e8b | 2017-03-20 21:30:41 -0400 | [diff] [blame] | 272 | if (!pfx_len) |
| 273 | ; /* nothing to prefix */ |
| 274 | else if (is_absolute_path(arg)) |
Dmitry Ivankov | 0687628 | 2011-08-11 15:15:38 +0600 | [diff] [blame] | 275 | pfx_len = 0; |
Jeff King | af10e8b | 2017-03-20 21:30:41 -0400 | [diff] [blame] | 276 | else |
Antoine Pelisse | fc2b621 | 2013-12-14 12:31:16 +0100 | [diff] [blame] | 277 | strbuf_add(&path, pfx, pfx_len); |
Jeff King | af10e8b | 2017-03-20 21:30:41 -0400 | [diff] [blame] | 278 | |
Antoine Pelisse | fc2b621 | 2013-12-14 12:31:16 +0100 | [diff] [blame] | 279 | strbuf_addstr(&path, arg); |
Jeff King | af10e8b | 2017-03-20 21:30:41 -0400 | [diff] [blame] | 280 | #ifdef GIT_WINDOWS_NATIVE |
Johannes Sixt | 8e9b208 | 2016-04-02 21:03:14 +0200 | [diff] [blame] | 281 | convert_slashes(path.buf + pfx_len); |
Dmitry Ivankov | 0687628 | 2011-08-11 15:15:38 +0600 | [diff] [blame] | 282 | #endif |
Jeff King | e4da43b | 2017-03-20 21:28:49 -0400 | [diff] [blame] | 283 | return strbuf_detach(&path, NULL); |
Dmitry Ivankov | 0687628 | 2011-08-11 15:15:38 +0600 | [diff] [blame] | 284 | } |
Junio C Hamano | a8bfa99 | 2023-03-04 05:27:56 -0500 | [diff] [blame] | 285 | |
| 286 | char *prefix_filename_except_for_dash(const char *pfx, const char *arg) |
| 287 | { |
| 288 | if (!strcmp(arg, "-")) |
| 289 | return xstrdup(arg); |
| 290 | return prefix_filename(pfx, arg); |
| 291 | } |
Calvin Wan | 5d1344b | 2023-06-06 19:48:39 +0000 | [diff] [blame] | 292 | |
| 293 | void strbuf_add_absolute_path(struct strbuf *sb, const char *path) |
| 294 | { |
| 295 | if (!*path) |
| 296 | die("The empty string is not a valid path"); |
| 297 | if (!is_absolute_path(path)) { |
| 298 | struct stat cwd_stat, pwd_stat; |
| 299 | size_t orig_len = sb->len; |
| 300 | char *cwd = xgetcwd(); |
| 301 | char *pwd = getenv("PWD"); |
| 302 | if (pwd && strcmp(pwd, cwd) && |
| 303 | !stat(cwd, &cwd_stat) && |
| 304 | (cwd_stat.st_dev || cwd_stat.st_ino) && |
| 305 | !stat(pwd, &pwd_stat) && |
| 306 | pwd_stat.st_dev == cwd_stat.st_dev && |
| 307 | pwd_stat.st_ino == cwd_stat.st_ino) |
| 308 | strbuf_addstr(sb, pwd); |
| 309 | else |
| 310 | strbuf_addstr(sb, cwd); |
| 311 | if (sb->len > orig_len && !is_dir_sep(sb->buf[sb->len - 1])) |
| 312 | strbuf_addch(sb, '/'); |
| 313 | free(cwd); |
| 314 | } |
| 315 | strbuf_addstr(sb, path); |
| 316 | } |
| 317 | |
| 318 | void strbuf_add_real_path(struct strbuf *sb, const char *path) |
| 319 | { |
| 320 | if (sb->len) { |
| 321 | struct strbuf resolved = STRBUF_INIT; |
| 322 | strbuf_realpath(&resolved, path, 1); |
| 323 | strbuf_addbuf(sb, &resolved); |
| 324 | strbuf_release(&resolved); |
| 325 | } else |
| 326 | strbuf_realpath(sb, path, 1); |
| 327 | } |