abspath: add a function to resolve paths with missing components

Currently, we have a function to resolve paths, strbuf_realpath. This function canonicalizes paths like realpath(3), but permits a trailing component to be absent from the file system. In other words, this is the behavior of the GNU realpath(1) without any arguments. In the future, we'll need this same behavior, except that we want to allow for any number of missing trailing components, which is the behavior of GNU realpath(1) with the -m option. This is useful because we'll want to canonicalize a path that may point to a not yet present path under the .git directory. For example, a user may want to know where an arbitrary ref would be stored if it existed in the file system. Let's refactor strbuf_realpath to move most of the code to an internal function and then pass it two flags to control its behavior. We'll add a strbuf_realpath_forgiving function that has our new behavior, and leave strbuf_realpath with the older, stricter behavior. Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

brian m. carlson committed Dec 13, 2020 at 00:25 UTC be6e0daee700b2ee28846b54ddfefe08cd4ec506
2 files changed +48 -18
abspath.c
+46 -18
@@ -67,19 +67,15 @@ static void get_root_part(struct strbuf *resolved, struct strbuf *remaining)
67 #endif
68
69 /*
70 - * Return the real path (i.e., absolute path, with symlinks resolved
71 - * and extra slashes removed) equivalent to the specified path. (If
72 - * you want an absolute path but don't mind links, use
73 - * absolute_path().) Places the resolved realpath in the provided strbuf.
74 - *
75 - * The directory part of path (i.e., everything up to the last
76 - * dir_sep) must denote a valid, existing directory, but the last
77 - * component need not exist. If die_on_error is set, then die with an
78 - * informative error message if there is a problem. Otherwise, return
79 - * NULL on errors (without generating any output).
70 + * If set, any number of trailing components may be missing; otherwise, only one
71 + * may be.
72 */
81 -char *strbuf_realpath(struct strbuf *resolved, const char *path,
82 - int die_on_error)
73 +#define REALPATH_MANY_MISSING (1 << 0)
74 +/* Should we die if there's an error? */
75 +#define REALPATH_DIE_ON_ERROR (1 << 1)
76 +
77 +static char *strbuf_realpath_1(struct strbuf *resolved, const char *path,
78 + int flags)
79 {
80 struct strbuf remaining = STRBUF_INIT;
81 struct strbuf next = STRBUF_INIT;
@@ -89,7 +85,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
85 struct stat st;
86
87 if (!*path) {
92 - if (die_on_error)
88 + if (flags & REALPATH_DIE_ON_ERROR)
89 die("The empty string is not a valid path");
90 else
91 goto error_out;
@@ -101,7 +97,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
97 if (!resolved->len) {
98 /* relative path; can use CWD as the initial resolved path */
99 if (strbuf_getcwd(resolved)) {
104 - if (die_on_error)
100 + if (flags & REALPATH_DIE_ON_ERROR)
101 die_errno("unable to get current working directory");
102 else
103 goto error_out;
@@ -129,8 +125,9 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
125
126 if (lstat(resolved->buf, &st)) {
127 /* error out unless this was the last component */
132 - if (errno != ENOENT || remaining.len) {
133 - if (die_on_error)
128 + if (errno != ENOENT ||
129 + (!(flags & REALPATH_MANY_MISSING) && remaining.len)) {
130 + if (flags & REALPATH_DIE_ON_ERROR)
131 die_errno("Invalid path '%s'",
132 resolved->buf);
133 else
@@ -143,7 +140,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
140 if (num_symlinks++ > MAXSYMLINKS) {
141 errno = ELOOP;
142
146 - if (die_on_error)
143 + if (flags & REALPATH_DIE_ON_ERROR)
144 die("More than %d nested symlinks "
145 "on path '%s'", MAXSYMLINKS, path);
146 else
@@ -153,7 +150,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
150 len = strbuf_readlink(&symlink, resolved->buf,
151 st.st_size);
152 if (len < 0) {
156 - if (die_on_error)
153 + if (flags & REALPATH_DIE_ON_ERROR)
154 die_errno("Invalid symlink '%s'",
155 resolved->buf);
156 else
@@ -202,6 +199,37 @@ error_out:
199 return retval;
200 }
201
202 +/*
203 + * Return the real path (i.e., absolute path, with symlinks resolved
204 + * and extra slashes removed) equivalent to the specified path. (If
205 + * you want an absolute path but don't mind links, use
206 + * absolute_path().) Places the resolved realpath in the provided strbuf.
207 + *
208 + * The directory part of path (i.e., everything up to the last
209 + * dir_sep) must denote a valid, existing directory, but the last
210 + * component need not exist. If die_on_error is set, then die with an
211 + * informative error message if there is a problem. Otherwise, return
212 + * NULL on errors (without generating any output).
213 + */
214 +char *strbuf_realpath(struct strbuf *resolved, const char *path,
215 + int die_on_error)
216 +{
217 + return strbuf_realpath_1(resolved, path,
218 + die_on_error ? REALPATH_DIE_ON_ERROR : 0);
219 +}
220 +
221 +/*
222 + * Just like strbuf_realpath, but allows an arbitrary number of path
223 + * components to be missing.
224 + */
225 +char *strbuf_realpath_forgiving(struct strbuf *resolved, const char *path,
226 + int die_on_error)
227 +{
228 + return strbuf_realpath_1(resolved, path,
229 + ((die_on_error ? REALPATH_DIE_ON_ERROR : 0) |
230 + REALPATH_MANY_MISSING));
231 +}
232 +
233 char *real_pathdup(const char *path, int die_on_error)
234 {
235 struct strbuf realpath = STRBUF_INIT;
cache.h
+2
@@ -1325,6 +1325,8 @@ static inline int is_absolute_path(const char *path)
1325 int is_directory(const char *);
1326 char *strbuf_realpath(struct strbuf *resolved, const char *path,
1327 int die_on_error);
1328 +char *strbuf_realpath_forgiving(struct strbuf *resolved, const char *path,
1329 + int die_on_error);
1330 char *real_pathdup(const char *path, int die_on_error);
1331 const char *absolute_path(const char *path);
1332 char *absolute_pathdup(const char *path);