Richard W.M. Jones wrote: ...
diff --git a/daemon/realpath.c b/daemon/realpath.c ...
Hi Rich,
@@ -42,3 +46,111 @@ do_realpath (const char *path)
return ret; /* caller frees */ } + +char * +do_case_sensitive_path (const char *path) +{ + char ret[PATH_MAX+1] = "/"; + size_t next = 1; + + /* MUST chdir ("/") before leaving this function. */ + if (chdir (sysroot) == -1) {
If this function might ever be used from a multi-threaded application, then you'll want to change it not to use chdir, since chdir changes the process-wide current directory. That can cause rare but particularly hard to debug problems. Another (albeit small) advantage of not performing the chdir/readdir is that the function will be able to handle those rare names with a directory component that grants "x" but not "r" access.
+ reply_with_perror ("%s", sysroot); + return NULL; + } + + /* First character is a '/'. Take each subsequent path element + * and follow it. + */ + path++; + while (*path) { + size_t i = strcspn (path, "/"); + if (i == 0) { /* "//" in path */ + path++; + continue; + } + if ((i == 1 && path[0] == '.') || + (i == 2 && path[0] == '.' && path[1] == '.')) { + reply_with_error ("case_sensitive_path: path contained . or .. elements"); + goto error; + } + if (i > NAME_MAX) { + reply_with_error ("case_sensitive_path: path element too long"); + goto error; + } + + char name[NAME_MAX+1]; + memcpy (name, path, i); + name[i] = '\0'; + + /* Skip to next element in path (for the next loop iteration). */ + path += i; + if (*path == '/') path++; + + /* Read the current directory looking (case insensitively) for + * this element of the path. + */ + DIR *dir = opendir ("."); + if (dir == NULL) { + reply_with_perror ("opendir"); + goto error; + } + + struct dirent *d = NULL; + + while ((d = readdir (dir)) != NULL) {
If you want to distinguish readdir "EOF" from failure (e.g., EIO), you'll have to set errno=0 before each call and test whether it's still 0 upon NULL return.
+ if (strcasecmp (d->d_name, name) == 0) + break; + } + + if (closedir (dir) == -1) { + reply_with_perror ("closedir"); + goto error; + } + + if (d == NULL) { + reply_with_error ("%s: no file or directory found with this name", name); + goto error; + } + + /* Add the real name of this path element to the return value. */ + if (next > 1) + ret[next++] = '/'; + + i = strlen (d->d_name); + if (next + i >= PATH_MAX) { + reply_with_error ("final path too long"); + goto error; + } + + strcpy (&ret[next], d->d_name); + next += i; + + /* Is it a directory? Try going into it. */ + if (chdir (d->d_name) == 0) + continue; + + /* This is OK provided we've reached the end of the path. */ + if (errno == ENOTDIR) { + if (*path) { + reply_with_error ("non-directory element in path"); + goto error; + } + break; + }
What about other errno values?
+ } + + ignore_value (chdir ("/")); + + ret[next] = '\0'; + char *retp = strdup (ret); + if (retp == NULL) { + reply_with_perror ("strdup"); + return NULL; + } + return retp; /* caller frees */ + + error: + ignore_value (chdir ("/")); + return NULL;