aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2012-11-22 21:18:09 -0600
committerRob Landley <rob@landley.net>2012-11-22 21:18:09 -0600
commitfe91e68e8d1e6a2974b57a9855032ad94d137e8e (patch)
treec6a347d54dae31c9572008133d6610df8c8dc475 /lib
parentbd2e2279d2b8f491ff9a56f6bd9000b0215778f8 (diff)
downloadtoybox-fe91e68e8d1e6a2974b57a9855032ad94d137e8e.tar.gz
Remove readlink -m for being poorly defined ("readlink -m /dev/null/and/more" answers what question, exactly?), rewrite xabspath() to work right and not depend on realpath, fix subtle longstanding bug in llist_traverse().
Diffstat (limited to 'lib')
-rw-r--r--lib/lib.c143
-rw-r--r--lib/lib.h2
-rw-r--r--lib/llist.c4
3 files changed, 124 insertions, 25 deletions
diff --git a/lib/lib.c b/lib/lib.c
index 83b9bb2e..57000ac7 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -323,37 +323,134 @@ void xstat(char *path, struct stat *st)
if(stat(path, st)) perror_exit("Can't stat %s", path);
}
-// Cannonicalize path, even to file with one or more missing components at end
-char *xabspath(char *path, unsigned missing)
+// Split a path into linked list of components, tracking head and tail of list.
+// Filters out // entries with no contents.
+struct string_list **splitpath(char *path, struct string_list **list)
{
- char *apath, *temp, *slash;
- int i=0;
+ char *new = path;
- // If this isn't an absolute path, make it one with cwd.
- if (path[0]!='/') {
- char *temp=xgetcwd();
- apath = xmsprintf("%s/%s", temp, path);
- free(temp);
- } else apath = path;
- slash = apath+strlen(apath);
+ *list = 0;
+ do {
+ int len;
+
+ if (*path && *path != '/') continue;
+ len = path-new;
+ if (len > 0) {
+ *list = xmalloc(sizeof(struct string_list) + len + 1);
+ (*list)->next = 0;
+ strncpy((*list)->str, new, len);
+ (*list)->str[len] = 0;
+ list = &(*list)->next;
+ }
+ new = path+1;
+ } while (*path++);
- for (;;) {
- temp = realpath(apath, NULL);
- if (i) *slash = '/';
- if (temp || ++i > missing) break;
- while (slash>apath) if (*--slash == '/') break;
- *slash=0;
+ return list;
+}
+
+// Cannonicalize path, even to file with one or more missing components at end.
+// if exact, require last path component to exist
+char *xabspath(char *path, int exact)
+{
+ struct string_list *todo, *done = 0;
+ int try = 9999, dirfd = open("/", 0);;
+ char buf[4096], *ret;
+
+ // If this isn't an absolute path, start with cwd.
+ if (*path != '/') {
+ char *temp = xgetcwd();
+
+ splitpath(path, splitpath(temp, &todo));
free(temp);
+ } else splitpath(path, &todo);
+
+ // Iterate through path components
+ while (todo) {
+ struct string_list *new = llist_pop(&todo), **tail;
+ ssize_t len;
+
+ if (!try--) {
+ errno = ELOOP;
+ goto error;
+ }
+
+ // Removable path componenents.
+ if (!strcmp(new->str, ".") || !strcmp(new->str, "..")) {
+ if (new->str[1] && done) free(llist_pop(&done));
+ free(new);
+ continue;
+ }
+
+ // Is this a symlink?
+ len=readlinkat(dirfd, new->str, buf, 4096);
+ if (len>4095) goto error;
+ if (len<1) {
+ int fd;
+
+ // Not a symlink: add to linked list, move dirfd, fail if error
+ if ((exact || todo) && errno != EINVAL) goto error;
+ new->next = done;
+ done = new;
+ fd = openat(dirfd, new->str, O_DIRECTORY);
+ if (fd == -1 && (exact || todo)) goto error;
+ close(dirfd);
+ dirfd = fd;
+ continue;
+ }
+
+ // If this symlink is to an absolute path, discard existing resolved path
+ buf[len] = 0;
+ if (*buf == '/') {
+ llist_traverse(done, free);
+ done=0;
+ close(dirfd);
+ dirfd = open("/", 0);
+ }
+ free(new);
+
+ // prepend components of new path. Note symlink to "/" will leave new NULL
+ tail = splitpath(buf, &new);
+
+ // symlink to "/" will return null and leave tail alone
+ if (new) {
+ *tail = todo;
+ todo = new;
+ }
}
+ close(dirfd);
- if (i && temp) {
- slash = xmsprintf("%s%s", temp, slash);
- free(temp);
- temp = slash;
+ // At this point done has the path, in reverse order. Reverse list while
+ // calculating buffer length.
+
+ try = 2;
+ while (done) {
+ struct string_list *temp = llist_pop(&done);;
+
+ if (todo) try++;
+ try += strlen(temp->str);
+ temp->next = todo;
+ todo = temp;
+ }
+
+ // Assemble return buffer
+
+ ret = xmalloc(try);
+ *ret = '/';
+ ret [try = 1] = 0;
+ while (todo) {
+ if (try>1) ret[try++] = '/';
+ try = stpcpy(ret+try, todo->str) - ret;
+ free(llist_pop(&todo));
}
- if (path != apath) free(apath);
- return temp;
+ return ret;
+
+error:
+ close(dirfd);
+ llist_traverse(todo, free);
+ llist_traverse(done, free);
+
+ return NULL;
}
// Resolve all symlinks, returning malloc() memory.
diff --git a/lib/lib.h b/lib/lib.h
index 34ded000..5c4cc46f 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -116,7 +116,7 @@ char *readfile(char *name);
char *xreadfile(char *name);
char *xgetcwd(void);
void xstat(char *path, struct stat *st);
-char *xabspath(char *path, unsigned missing);
+char *xabspath(char *path, int exact);
char *xrealpath(char *path);
void xchdir(char *path);
void xmkpath(char *path, int mode);
diff --git a/lib/llist.c b/lib/llist.c
index 9b6c2950..7b41fd08 100644
--- a/lib/llist.c
+++ b/lib/llist.c
@@ -8,12 +8,14 @@
// Call a function (such as free()) on each element of a linked list.
void llist_traverse(void *list, void (*using)(void *data))
{
+ void *old = list;
+
while (list) {
void *pop = llist_pop(&list);
using(pop);
// End doubly linked list too.
- if (list==pop) break;
+ if (old == list) break;
}
}