aboutsummaryrefslogtreecommitdiffstats
path: root/pseudo_util.c
diff options
context:
space:
mode:
authorSeebs <seebs@seebs.net>2018-03-06 17:01:28 -0600
committerSeebs <seebs@seebs.net>2018-03-06 17:01:28 -0600
commit32c308ee14bbe2f68cefd12cf1f40275fd216a49 (patch)
tree726c48486d893b8c216991f319cb4aadd6b5af85 /pseudo_util.c
parentdfc21be28185956f61bd904022312f781cbe1572 (diff)
downloadpseudo-32c308ee14bbe2f68cefd12cf1f40275fd216a49.tar.gz
pseudo-32c308ee14bbe2f68cefd12cf1f40275fd216a49.tar.bz2
pseudo-32c308ee14bbe2f68cefd12cf1f40275fd216a49.zip
Improve path handling by not dereferencing files
The path handling was stripping /., or moving back up a level for /.., even when the current thing was a plain file, resulting in the surprising behavior that "a/." was a valid name for a file called a, which canonicalized without the extra directory. This breaks some stuff occasionally. (Related to, but not the same as, the previous issues with the use of trailing slashes in names to match only directories.) This is not yet well-tested. Signed-off-by: Seebs <seebs@seebs.net>
Diffstat (limited to 'pseudo_util.c')
-rw-r--r--pseudo_util.c106
1 files changed, 66 insertions, 40 deletions
diff --git a/pseudo_util.c b/pseudo_util.c
index 6a1fac2..addf503 100644
--- a/pseudo_util.c
+++ b/pseudo_util.c
@@ -265,7 +265,7 @@ int pseudo_util_evlog_fd = 2;
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, int leave_this);
+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);
extern char **environ;
static ssize_t pseudo_max_pathlen = -1;
@@ -618,11 +618,11 @@ pseudo_new_pid() {
* the symlink, appending each element in turn the same way.
*/
static int
-pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurrent, const char *element, size_t elen, int leave_this) {
+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);
char *current;
- PSEUDO_STATBUF buf;
if (!newpath ||
!pcurrent || !*pcurrent ||
!root || !element) {
@@ -630,25 +630,31 @@ pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurre
return -1;
}
current = *pcurrent;
- /* sanity-check: ignore // or /./ */
- if (elen == 0 || (elen == 1 && *element == '.')) {
- return 1;
- }
- /* backtrack for .. */
- if (elen == 2 && element[0] == '.' && element[1] == '.') {
- /* if newpath's whole contents are '/', do nothing */
- if (current <= root + 1)
+ /* the special cases here to skip empty paths, or ./.., should not
+ * apply to plain files, which should just get bogus
+ * paths.
+ */
+ if (!is_reg) {
+ /* sanity-check: ignore // or /./ */
+ if (elen == 0 || (elen == 1 && *element == '.')) {
+ return 1;
+ }
+ /* 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;
+ /* now find the previous slash */
+ while (current > root && *current != '/') {
+ --current;
+ }
+ /* and point to the nul just past it */
+ *(++current) = '\0';
+ *pcurrent = current;
return 1;
- /* backtrack to the character before the / */
- current -= 2;
- /* now find the previous slash */
- while (current > root && *current != '/') {
- --current;
}
- /* and point to the nul just past it */
- *(++current) = '\0';
- *pcurrent = current;
- return 1;
}
curlen = current - newpath;
/* current length, plus / <element> / \0 */
@@ -659,13 +665,20 @@ pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurre
}
memcpy(current, element, elen);
current += elen;
- /* nul-terminate, and we now point to the nul after the slash */
+ /* 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 */
- if (!leave_this) {
- int is_link;
- is_link = (pseudo_real_lstat) && (pseudo_real_lstat(newpath, &buf) != -1) && S_ISLNK(buf.st_mode);
+ /* 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 (!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);
+ }
+ 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);
is_link = 0;
@@ -699,9 +712,9 @@ pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurre
return retval;
}
}
- /* okay, not a symlink, go ahead and append a slash */
- *(current++) = '/';
- *current = '\0';
+ /* we used to always append a slash here. now we don't; append_elements
+ * handles slashes, so just update the pointer.
+ */
*pcurrent = current;
return 1;
}
@@ -709,6 +722,8 @@ 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) {
int retval = 1;
+ PSEUDO_STATBUF buf;
+ buf.st_mode = 0;
const char * start = element;
if (!newpath || !root ||
!current || !*current ||
@@ -716,30 +731,41 @@ pseudo_append_elements(char *newpath, char *root, size_t allocated, char **curre
pseudo_diag("pseudo_append_elements: invalid arguments.");
return -1;
}
+ /* 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) {
size_t this_elen;
int leave_this = 0;
+ int is_reg = S_ISREG(buf.st_mode);
char *next = strchr(element, '/');
if (!next) {
next = strchr(element, '\0');
leave_this = leave_last;
}
this_elen = next - element;
- switch (this_elen) {
- case 0: /* path => '/' */
- break;
- case 1: /* path => '?/' */
- if (*element != '.') {
- if (pseudo_append_element(newpath, root, allocated, current, element, this_elen, leave_this) == -1) {
- retval = -1;
- }
- }
- break;
- default:
- if (pseudo_append_element(newpath, root, allocated, current, element, this_elen, leave_this) == -1) {
+ /* for an empty path, or a "/.", we skip the append, but not for regular
+ * files; regular files get it appended so they can fail properly
+ * later for being invalid paths.
+ */
+ if (is_reg || (this_elen > 1) || ((this_elen == 1) && (*element != '.'))) {
+ if (pseudo_append_element(newpath, root, allocated, current, element, this_elen, &buf, leave_this) == -1) {
retval = -1;
+ break;
}
- break;
+ /* 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;
+ }
+ /* having grabbed a stat for the path, we append a slash so we can append to it again
+ * if needed.
+ */
+ *(*current)++ = '/';
+ *(*current) = '\0';
}
/* and now move past the separator */
element += this_elen + 1;