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 #include #include #include -#include #include #include +#include #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; }