char * do_case_sensitive_path (const char *path) { char ret[PATH_MAX+1] = "/"; size_t next = 1; int fd_cwd; /* 'fd_cwd' here is a surrogate for the current working directory, so * that we don't have to actually call chdir(2). */ fd_cwd = open (sysroot, O_RDONLY | O_DIRECTORY); if (fd_cwd == -1) { reply_with_perror ("%s", sysroot); return NULL; } /* First character is a '/'. Take each subsequent path element * and follow it. */ while (*path) { size_t i = strcspn (path, "/"); if (i == 0) { path++; continue; } if ((i == 1 && path[0] == '.') || (i == 2 && path[0] == '.' && path[1] == '.')) { reply_with_error ("path contained . or .. elements"); goto error; } if (i > NAME_MAX) { reply_with_error ("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; /* Read the current directory looking (case insensitively) for * this element of the path. This replaces 'name' with the * correct case version. */ if (find_path_element (fd_cwd, name, &i) == -1) goto error; /* Add the real name of this path element to the return value. */ if (next > 1) ret[next++] = '/'; if (next + i >= PATH_MAX) { reply_with_error ("final path too long"); goto error; } strcpy (&ret[next], name); next += i; /* Is it a directory? Try going into it. */ int fd2 = openat (fd_cwd, name, O_RDONLY | O_DIRECTORY); int err = errno; close (fd_cwd); fd_cwd = fd2; errno = err; if (fd_cwd == -1) { /* ENOTDIR is OK provided we've reached the end of the path. */ if (errno != ENOTDIR) { reply_with_perror ("openat: %s", name); goto error; } if (*path) { reply_with_error ("%s: non-directory element in path", name); goto error; } } } if (fd_cwd >= 0) close (fd_cwd); ret[next] = '\0'; char *retp = strdup (ret); if (retp == NULL) { reply_with_perror ("strdup"); return NULL; } return retp; /* caller frees */ error: if (fd_cwd >= 0) close (fd_cwd); return NULL; }
char * do_case_sensitive_path (const char *path) { size_t next; int fd_cwd, fd2, err, is_end; char *ret; ret = strdup ("/"); if (ret == NULL) { reply_with_perror ("strdup"); return NULL; } next = 1; /* next position in 'ret' buffer */ /* 'fd_cwd' here is a surrogate for the current working directory, so * that we don't have to actually call chdir(2). */ fd_cwd = open (sysroot, O_RDONLY|O_DIRECTORY|O_CLOEXEC); if (fd_cwd == -1) { reply_with_perror ("%s", sysroot); goto error; } /* First character is a '/'. Take each subsequent path element * and follow it. */ while (*path) { char *t; size_t i, len; CLEANUP_FREE char *name_in = NULL, *name_out = NULL; i = strcspn (path, "/"); if (i == 0) { path++; continue; } if ((i == 1 && path[0] == '.') || (i == 2 && path[0] == '.' && path[1] == '.')) { reply_with_error ("path contained . or .. elements"); goto error; } name_in = strndup (path, i); if (name_in == NULL) { reply_with_perror ("strdup"); goto error; } /* Skip to next element in path (for the next loop iteration). */ path += i; is_end = *path == 0; /* Read the current directory looking (case insensitively) for * this element of the path. This replaces 'name' with the * correct case version. */ if (find_path_element (fd_cwd, is_end, name_in, &name_out) == -1) goto error; len = strlen (name_out); /* Add the real name of this path element to the return value. */ if (next > 1) ret[next++] = '/'; t = realloc (ret, next+len+1); if (t == NULL) { reply_with_perror ("realloc"); goto error; } ret = t; strcpy (&ret[next], name_out); next += len; /* Is it a directory? Try going into it. */ fd2 = openat (fd_cwd, name_out, O_RDONLY|O_DIRECTORY|O_CLOEXEC); err = errno; close (fd_cwd); fd_cwd = fd2; errno = err; if (fd_cwd == -1) { /* Some errors are OK provided we've reached the end of the path. */ if (is_end && (errno == ENOTDIR || errno == ENOENT)) break; reply_with_perror ("openat: %s", name_out); goto error; } } if (fd_cwd >= 0) close (fd_cwd); return ret; /* caller frees */ error: if (fd_cwd >= 0) close (fd_cwd); free (ret); return NULL; }