aboutsummaryrefslogtreecommitdiffstats
path: root/pseudo_util.c
diff options
context:
space:
mode:
authorSeebs <seebs@seebs.net>2018-03-30 13:00:02 -0500
committerSeebs <seebs@seebs.net>2018-03-30 13:15:13 -0500
commitc7b8f88edea0195f0ef7b24dcdd974783d2d0750 (patch)
tree652cf3a1155e1b8202f3ad92d71532c1ac651d64 /pseudo_util.c
parent3e7a4eaa97abbdbd5362486898f1ba1e96774333 (diff)
downloadpseudo-c7b8f88edea0195f0ef7b24dcdd974783d2d0750.tar.gz
pseudo-c7b8f88edea0195f0ef7b24dcdd974783d2d0750.tar.bz2
pseudo-c7b8f88edea0195f0ef7b24dcdd974783d2d0750.zip
rework path computations
The path logic had gotten gradually more baroque, originally from the need to handle trailing slashes in paths (because we need "*/" to be a glob that matches only directories), then from the need not to handle "regularfile/." and the like. The revised solution is to move the invariant from "partial path always ends with slash" to "appending always adds a slash". Because this implies wanting to know the type of the current partial path, and we also check the type of the new path whenever we append an element, we now have a shared stat buf for those. (With some manual tweaking, such as an empty path is implicitly root and thus a directory, and anything in which we want to expand a symlink is a directory.) Signed-off-by: Seebs <seebs@seebs.net>
Diffstat (limited to 'pseudo_util.c')
-rw-r--r--pseudo_util.c168
1 files changed, 86 insertions, 82 deletions
diff --git a/pseudo_util.c b/pseudo_util.c
index 7859ad9..e01dfc2 100644
--- a/pseudo_util.c
+++ b/pseudo_util.c
@@ -266,7 +266,7 @@ static int debugged_newline = 1;
static char pid_text[32];
static size_t pid_len;
static int pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurrent, const char *element, size_t elen, PSEUDO_STATBUF *buf, int leave_this);
-static int pseudo_append_elements(char *newpath, char *root, size_t allocated, char **current, const char *elements, size_t elen, int leave_last);
+static int pseudo_append_elements(char *newpath, char *root, size_t allocated, char **current, const char *elements, size_t elen, int leave_last, PSEUDO_STATBUF *sbuf);
extern char **environ;
static ssize_t pseudo_max_pathlen = -1;
static ssize_t pseudo_sys_max_pathlen = -1;
@@ -621,7 +621,7 @@ static int
pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurrent, const char *element, size_t elen, PSEUDO_STATBUF *buf, int leave_this) {
static int link_recursion = 0;
size_t curlen;
- int is_reg = S_ISREG(buf->st_mode);
+ int is_dir = S_ISDIR(buf->st_mode);
char *current;
if (!newpath ||
!pcurrent || !*pcurrent ||
@@ -630,30 +630,31 @@ pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurre
return -1;
}
current = *pcurrent;
- pseudo_debug(PDBGF_PATH | PDBGF_VERBOSE, "pae: %s, + %.*s\n",
- newpath, (int) elen, element);
- /* the special cases here to skip empty paths, or ./.., should not
- * apply to plain files, which should just get bogus
- * paths.
+ pseudo_debug(PDBGF_PATH | PDBGF_VERBOSE, "pae: '%s', + '%.*s', is_dir %d\n",
+ newpath, (int) elen, element, is_dir);
+ /* the special cases here to skip empty paths, or ./.., should apply
+ * only to directories; plain files, nodes, etcetera should just get
+ * bogus paths.
*/
- if (!is_reg) {
+ if (is_dir) {
/* sanity-check: ignore // or /./ */
if (elen == 0 || (elen == 1 && *element == '.')) {
return 0;
}
/* backtrack for .. */
if (elen == 2 && element[0] == '.' && element[1] == '.') {
- /* if newpath's whole contents are '/', do nothing */
- if (current <= root + 1)
- return 1;
- /* backtrack to the character before the / */
- current -= 2;
+ /* if newpath is empty, do nothing. */
+ if (current <= root)
+ return 0;
/* now find the previous slash */
while (current > root && *current != '/') {
--current;
}
- /* and point to the nul just past it */
- *(++current) = '\0';
+ /* either we're at root, or we're at a slash.
+ * either way, nul that out, leaving us with a
+ * possibly-empty path which is not slash-terminated.
+ */
+ *current = '\0';
*pcurrent = current;
return 1;
}
@@ -665,21 +666,27 @@ pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurre
pseudo_diag("pseudo_append_element: path too long (wanted %lu bytes).\n", (unsigned long) curlen + elen + 3);
return -1;
}
+ /* append a slash */
+ *current++ = '/';
memcpy(current, element, elen);
current += elen;
/* nul-terminate, and we now point to the nul after the element just added. */
*current = '\0';
- /* now, the moment of truth... is that a symlink? */
- /* if lstat fails, that's fine -- nonexistent files aren't symlinks */
- /* don't do this if the parent was a regular file. it shouldn't matter,
- * because readlink can't succeed in that case anyway.
+ /* if we are not on the last element of a path and supposed to leave
+ * it alone (for SYMLINK_NOFOLLOW type cases), and we are not trying to
+ * go further under a regular file, we want to know whether this is a symlink.
+ * either way, we want to update buf to show the correct state of the file.
*/
- if (!leave_this && !is_reg) {
- /* if this fails, just use the parent's mode. which shouldn't have
- * been a symlink, I hope? */
- if (pseudo_real_lstat) {
- pseudo_real_lstat(newpath, buf);
- }
+ if (!pseudo_real_lstat || (pseudo_real_lstat(newpath, buf) == -1)) {
+ // if we can't stat it, zero mode so we don't think it's
+ // known to be a link or a regular file.
+ buf->st_mode = 0;
+ }
+ /* it is intentional that this uses the "stale" is_dir for the file we
+ * were appending to. we don't want to actually try to do this when
+ * we're appending names to a regular file.
+ */
+ if (!leave_this && is_dir) {
int is_link = S_ISLNK(buf->st_mode);
if (link_recursion >= PSEUDO_MAX_LINK_RECURSION && is_link) {
pseudo_diag("link recursion too deep, not expanding path '%s'.\n", newpath);
@@ -702,14 +709,20 @@ pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurre
current = newpath + 1;
} else {
/* point back at the end of the previous path... */
- current -= elen;
+ current -= (elen + 1);
}
/* null terminate at the new pointer */
*current = '\0';
- /* append all the elements in series */
*pcurrent = current;
+ /* we know that we're now pointing either at a directory we
+ * already decided was safe to go into, or root. either way,
+ * the parent item mode should reflect it being a directory.
+ * we don't need to call stat for that.
+ */
+ buf->st_mode = S_IFDIR;
+ /* append all the elements in series */
++link_recursion;
- retval = pseudo_append_elements(newpath, root, allocated, pcurrent, linkbuf, linklen, 0);
+ retval = pseudo_append_elements(newpath, root, allocated, pcurrent, linkbuf, linklen, 0, buf);
--link_recursion;
return retval;
}
@@ -722,69 +735,55 @@ pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurre
}
static int
-pseudo_append_elements(char *newpath, char *root, size_t allocated, char **current, const char *element, size_t elen, int leave_last) {
+pseudo_append_elements(char *newpath, char *root, size_t allocated, char **current, const char *path, size_t elen, int leave_last, PSEUDO_STATBUF *sbuf) {
int retval = 1;
+ /* a shareable buffer so we can cache stat results while walking the path */
PSEUDO_STATBUF buf;
buf.st_mode = 0;
- int add_slash = 0;
- const char * start = element;
+ const char * start = path;
if (!newpath || !root ||
!current || !*current ||
- !element) {
+ !path) {
pseudo_diag("pseudo_append_elements: invalid arguments.");
return -1;
}
+ if (!sbuf) {
+ /* we will use this buffer to hold "the current state of newpath".
+ * append_element will update that whenever it appends an element,
+ * and any calls back here from there will pass in the same buffer.
+ * if we didn't get one, we start using this local one, which will
+ * then get shared by anything we call.
+ */
+ sbuf = &buf;
+ if (!pseudo_real_lstat || (pseudo_real_lstat(newpath, sbuf) == -1)) {
+ sbuf->st_mode = (*current > root) ? 0 : S_IFDIR;
+ }
+ }
pseudo_debug(PDBGF_PATH | PDBGF_VERBOSE, "paes: newpath %s, element list <%.*s>\n",
- newpath, (int) elen, element);
- /* coming into append_elements, we should always have a trailing slash on
- * the path. append_element won't provide one, though.
- */
- while (element < (start + elen) && *element) {
+ newpath, (int) elen, path);
+ while (path < (start + elen) && *path) {
size_t this_elen;
int leave_this = 0;
- int is_reg = S_ISREG(buf.st_mode);
- char *next = strchr(element, '/');
+ char *next = strchr(path, '/');
if (!next) {
- next = strchr(element, '\0');
+ next = strchr(path, '\0');
leave_this = leave_last;
}
- this_elen = next - element;
+ this_elen = next - path;
/* for a directory, we skip the append for empty path or ".";
* regular files get it appended so they can fail properly
* later for being invalid paths.
*/
pseudo_debug(PDBGF_PATH | PDBGF_VERBOSE, "element to add: '%.*s'\n",
- (int) this_elen, element);
- /* we initially had a trailing slash. we don't
- * want append_element to append slashes, so every time through
- * this loop after the first, we want to add a slash.
- */
- if (add_slash) {
- *(*current)++ = '/';
- *(*current) = '\0';
- }
- if (is_reg || (this_elen > 1) || ((this_elen == 1) && (*element != '.'))) {
- int appended = pseudo_append_element(newpath, root, allocated, current, element, this_elen, &buf, leave_this);
- if (appended == -1) {
- retval = -1;
- break;
- }
- if (appended == 1) {
- add_slash = 1;
- }
- pseudo_debug(PDBGF_FILE | PDBGF_VERBOSE, "paes: append_element gave us '%s', current '%s'\n",
- newpath, *current);
- /* if a path element was appended, we want to know whether the resulting
- * thing is an existing regular file; regular files can't be further
- * explored, which actually means we *do* append path things to them
- * that would otherwise be skipped.
- */
- if (!pseudo_real_lstat || (pseudo_real_lstat(newpath, &buf) == -1)) {
- buf.st_mode = 0;
- }
+ (int) this_elen, path);
+ if (pseudo_append_element(newpath, root, allocated, current, path, this_elen, sbuf, leave_this) == -1) {
+ retval = -1;
+ break;
}
+ pseudo_debug(PDBGF_FILE | PDBGF_VERBOSE, "paes: append_element gave us '%s', current '%s'\n",
+ newpath, *current);
/* and now move past the separator */
- element += this_elen + 1;
+ path += this_elen + 1;
}
return retval;
}
@@ -820,9 +819,12 @@ pseudo_fix_path(const char *base, const char *path, size_t rootlen, size_t basel
newpath = pathbufs[pathbuf];
pathbuf = (pathbuf + 1) % PATHBUFS;
pathlen = strlen(path);
- /* a trailing slash has special meaning */
- if (pathlen > 0 && path[pathlen - 1] == '/') {
+ /* a trailing slash has special meaning, but processing
+ * trailing slashes is expensive.
+ */
+ while (pathlen > 0 && path[pathlen - 1] == '/') {
trailing_slash = 1;
+ --pathlen;
}
/* allow a bit of slush. overallocating a bit won't
* hurt. rounding to 256's in the hopes that it makes life
@@ -842,22 +844,24 @@ pseudo_fix_path(const char *base, const char *path, size_t rootlen, size_t basel
* part of the string; you can't back up over it.
*/
effective_root = newpath + rootlen;
- *current++ = '/';
*current = '\0';
/* at any given point:
- * current points to just after the last / of newpath
+ * path is not slash-terminated
+ * current points to the null byte immediately after the path
* path points to the next element of path
* newpathlen is the total allocated length of newpath
* (current - newpath) is the used length of newpath
- *
- * ... the above is now slightly wrong, we start append_elements
- * and append_element with a trailing slash on the path, but
- * end them without.
*/
- if (pseudo_append_elements(newpath, effective_root, newpathlen, &current, path, pathlen, leave_last) != -1) {
- if (current > effective_root && trailing_slash) {
- *current++ = '/';
- *current++ = '\0';
+ if (pseudo_append_elements(newpath, effective_root, newpathlen, &current, path, pathlen, leave_last, 0) != -1) {
+ /* if we are expecting a trailing slash, or the path ended up being completely
+ * empty (meaning it's pointing at either effective_root or the beginning of
+ * the path), we need a slash here.
+ */
+ if ((current == effective_root) || trailing_slash) {
+ if ((current - newpath) < (int) newpathlen) {
+ *current++ = '/';
+ *current = '\0';
+ }
}
pseudo_debug(PDBGF_PATH, "%s + %s => <%s>\n",
base ? base : "<nil>",