Attached patch fixes the linux-user path mangling code for use with
real target root filesystems that have nasty symlinks and lots of
files. The old code is terribly slow and can easily end up going
through the entire host system /usr hierarchy in a recursive loop.

Compared to the previous version of this patch, fixes an issue with
attempting to free() a pointer returned by GNU basename().

/lauri
Index: linux-user/path.c
===================================================================
RCS file: /sources/qemu/qemu/linux-user/path.c,v
retrieving revision 1.2
diff -u -r1.2 path.c
--- linux-user/path.c	13 Sep 2004 21:39:32 -0000	1.2
+++ linux-user/path.c	21 May 2007 23:06:54 -0000
@@ -3,145 +3,201 @@
 
    The assumption is that this area does not change.
 */
+
+#define _GNU_SOURCE
+
 #include <sys/types.h>
 #include <dirent.h>
 #include <unistd.h>
 #include <stdlib.h>
-#include <string.h>
 #include <errno.h>
 #include <stdio.h>
+#include <string.h>
 #include "qemu.h"
 
-struct pathelem
-{
-    /* Name of this, eg. lib */
-    char *name;
-    /* Full path name, eg. /usr/gnemul/x86-linux/lib. */
-    char *pathname;
-    struct pathelem *parent;
-    /* Children */
-    unsigned int num_entries;
-    struct pathelem *entries[0];
-};
-
-static struct pathelem *base;
-
-/* First N chars of S1 match S2, and S2 is N chars long. */
-static int strneq(const char *s1, unsigned int n, const char *s2)
-{
-    unsigned int i;
-
-    for (i = 0; i < n; i++)
-	if (s1[i] != s2[i])
-	    return 0;
-    return s2[i] == 0;
-}
+char *base = NULL;
 
-static struct pathelem *add_entry(struct pathelem *root, const char *name);
+struct path_entry {
+    struct path_entry *prev;
+    struct path_entry *next;
+    char name[PATH_MAX];
+};
 
-static struct pathelem *new_entry(const char *root,
-				  struct pathelem *parent,
-				  const char *name)
+char *decolonize_path(const char *path)
 {
-    struct pathelem *new = malloc(sizeof(*new));
-    new->name = strdup(name);
-    asprintf(&new->pathname, "%s/%s", root, name);
-    new->num_entries = 0;
-    return new;
-}
-
-#define streq(a,b) (strcmp((a), (b)) == 0)
+    char *cpath, *index, *start;
+    char cwd[PATH_MAX];
+    struct path_entry list;
+    struct path_entry *work;
+    struct path_entry *new;
+    char *buf = NULL;
 
-static struct pathelem *add_dir_maybe(struct pathelem *path)
-{
-    DIR *dir;
+    if (!path) {
+        return NULL;
+    }
 
-    if ((dir = opendir(path->pathname)) != NULL) {
-	struct dirent *dirent;
+    buf = malloc((PATH_MAX + 1) * sizeof(char));
+    memset(buf, '\0', PATH_MAX + 1);
 
-	while ((dirent = readdir(dir)) != NULL) {
-	    if (!streq(dirent->d_name,".") && !streq(dirent->d_name,"..")){
-		path = add_entry(path, dirent->d_name);
-	    }
-	}
-        closedir(dir);
+    list.next = NULL;
+    list.prev = NULL;
+    work = &list;
+
+    if (path[0] != '/') {
+        /* not an absolute path */
+        memset(cwd, '\0', PATH_MAX);
+        if (getcwd(cwd, PATH_MAX) < 0) {
+            perror("error getting current work dir\n");
+            return NULL;
+        }
+        unsigned int l = (strlen(cwd) + 1 + strlen(path) + 1);
+        cpath = malloc((strlen(cwd) + 1
+                        + strlen(path) + 1) * sizeof(char));
+        memset(cpath, '\0', l);
+        strcpy(cpath, cwd);
+        strcat(cpath, "/");
+        strcat(cpath, path);
+    } else {
+        cpath = strdup(path);
     }
-    return path;
-}
 
-static struct pathelem *add_entry(struct pathelem *root, const char *name)
-{
-    root->num_entries++;
-
-    root = realloc(root, sizeof(*root)
-		   + sizeof(root->entries[0])*root->num_entries);
+    start = cpath + 1;          /* ignore leading '/' */
+    while (1) {
+        index = strstr(start, "/");
+        if (!index) {
+            /* add the last item */
+            new = malloc(sizeof(struct path_entry));
+            memset(new->name, '\0', PATH_MAX);
+            new->prev = work;
+            work->next = new;
+            new->next = NULL;
+            strcpy(new->name, start);
+            work = new;
+            break;
+        }
+        *index = '\0';
+        if (index == (start)) {
+            goto proceed;       /* skip over empty strings 
+                                   resulting from // */
+        }
+
+        if (strcmp(start, "..") == 0) {
+            /* travel up one */
+            if (!work->prev)
+                goto proceed;
+            work = work->prev;
+            free(work->next);
+            work->next = NULL;
+        } else if (strcmp(start, ".") == 0) {
+            /* ignore */
+            goto proceed;
+        } else {
+            /* add an entry to our path_entry list */
+            new = malloc(sizeof(struct path_entry));
+            memset(new->name, '\0', PATH_MAX);
+            new->prev = work;
+            work->next = new;
+            new->next = NULL;
+            strcpy(new->name, start);
+            work = new;
+        }
+
+      proceed:
+        *index = '/';
+        start = index + 1;
+    }
 
-    root->entries[root->num_entries-1] = new_entry(root->pathname, root, name);
-    root->entries[root->num_entries-1]
-	= add_dir_maybe(root->entries[root->num_entries-1]);
-    return root;
+    work = list.next;
+    while (work) {
+        struct path_entry *tmp;
+        strcat(buf, "/");
+        strcat(buf, work->name);
+        tmp = work;
+        work = work->next;
+        free(tmp);
+    }
+    return buf;
 }
 
-/* This needs to be done after tree is stabalized (ie. no more reallocs!). */
-static void set_parents(struct pathelem *child, struct pathelem *parent)
-{
-    unsigned int i;
-
-    child->parent = parent;
-    for (i = 0; i < child->num_entries; i++)
-	set_parents(child->entries[i], child);
-}
 
 void init_paths(const char *prefix)
 {
-    if (prefix[0] != '/' ||
-        prefix[0] == '\0' ||
-        !strcmp(prefix, "/"))
-        return;
-
-    base = new_entry("", NULL, prefix+1);
-    base = add_dir_maybe(base);
-    if (base->num_entries == 0) {
-        free (base);
-        base = NULL;
-    } else {
-        set_parents(base, base);
-    }
+    base = strdup(prefix);
 }
 
-/* FIXME: Doesn't handle DIR/.. where DIR is not in emulated dir. */
-static const char *
-follow_path(const struct pathelem *cursor, const char *name)
+
+
+char *adjust_for_leakage(char *path)
 {
-    unsigned int i, namelen;
+    char tmp[PATH_MAX + 1];
+    char tmp2[PATH_MAX + 1];
+    char *buf;
+    char *bname = NULL, *dname = NULL;
+    int i;
 
-    name += strspn(name, "/");
-    namelen = strcspn(name, "/");
+    if (!path)
+        return NULL;
 
-    if (namelen == 0)
-	return cursor->pathname;
+    memset(tmp, '\0', PATH_MAX + 1);
+    memset(tmp2, '\0', PATH_MAX + 1);
+    if ((i = readlink(path, tmp, PATH_MAX) < 0)) {
+        /* not a symlink */
+        return strdup(path);
+    }
 
-    if (strneq(name, namelen, ".."))
-	return follow_path(cursor->parent, name + namelen);
+    bname = basename(path); /* relying on GNU version of basename */
+    dname = dirname(strdup(path));
 
-    if (strneq(name, namelen, "."))
-	return follow_path(cursor, name + namelen);
+    /* check if the symlink refers to itself */
+    if (strcmp(tmp, bname) == 0) {
+        /* symlink refers to itself */
+        return strdup(path);
+    }
 
-    for (i = 0; i < cursor->num_entries; i++)
-	if (strneq(name, namelen, cursor->entries[i]->name))
-	    return follow_path(cursor->entries[i], name + namelen);
+    /* make tmp absolute if it's not */
+    if (tmp[0] != '/') {
+        strcpy(tmp2, dname);
+        strcat(tmp2, "/");
+        strcat(tmp2, tmp);
+    }
 
-    /* Not found */
-    return NULL;
+    /* remove "." and ".." entries from tmp2 */
+    buf = decolonize_path(tmp2);
+    strcpy(tmp2, buf);
+
+    free(buf);
+    /* bname is not free'd, because GNU basename returns a pointer
+     * to its own buffer */
+    free(dname);
+
+    if (strncmp(tmp2, base, strlen(base)) != 0) {
+        /* tried to leak out, fix it */
+        strcpy(tmp, base);
+        strcpy(tmp, "/");
+        strcpy(tmp, tmp2);
+        return adjust_for_leakage(tmp);
+    } else {
+        return adjust_for_leakage(tmp2);
+    }
 }
 
 /* Look for path in emulation dir, otherwise return name. */
 const char *path(const char *name)
 {
-    /* Only do absolute paths: quick and dirty, but should mostly be OK.
-       Could do relative by tracking cwd. */
-    if (!base || name[0] != '/')
-	return name;
+    char *tmp, *ret;
 
-    return follow_path(base, name) ?: name;
+    if (strncmp(name, base, strlen(base)) == 0) {
+        /* name is within emulation dir */
+        ret = adjust_for_leakage(name);
+    } else {
+        /* name is elsewhere, use base + "/" + name */
+        tmp = malloc(sizeof(char) * strlen(base) + strlen(name) + 2);
+        strcpy(tmp, base);
+        if (name[0] != '/')
+            strcat(tmp, "/");
+        strcat(tmp, name);
+        ret = adjust_for_leakage(tmp);
+        free(tmp);
+    }
+    return ret;
 }

Reply via email to