chdir: collapse /./ and /../ in path for conventional filesystems syslinux-4.06-pre8
authorH. Peter Anvin <hpa@zytor.com>
Fri, 29 Jun 2012 00:26:19 +0000 (17:26 -0700)
committerH. Peter Anvin <hpa@zytor.com>
Fri, 29 Jun 2012 00:26:49 +0000 (17:26 -0700)
For conventional filesystems (i.e. not PXE), collapse /./ and /../ in
the path when doing chdir.

Signed-off-by: H. Peter Anvin <hpa@zytor.com>
core/fs/chdir.c

index 9e8dfd2..c40e91b 100644 (file)
@@ -1,6 +1,7 @@
 #include <stdio.h>
 #include <stdbool.h>
 #include <string.h>
+#include <dprintf.h>
 #include "fs.h"
 #include "cache.h"
 
@@ -16,12 +17,13 @@ void pm_realpath(com32sys_t *regs)
     realpath(dst, src, FILENAME_MAX);
 }
 
-#define EMIT(x)                                \
-do {                                   \
-    if (++n < bufsize)                 \
-       *q++ = (x);                     \
+#define EMIT(x)                                \
+do {                                   \
+    if (++n < bufsize)                 \
+       *q++ = (x);                     \
 } while (0)
 
+
 static size_t join_paths(char *dst, size_t bufsize,
                         const char *s1, const char *s2)
 {
@@ -31,8 +33,17 @@ static size_t join_paths(char *dst, size_t bufsize,
     const char *p;
     char *q  = dst;
     size_t n = 0;
-    bool slash = false;
+    bool slash;
+    struct bufstat {
+       struct bufstat *prev;
+       size_t n;
+       char *q;
+    };
+    struct bufstat *stk = NULL;
+    struct bufstat *bp;
     
+    slash = false;
+
     list[0] = s1;
     list[1] = s2;
 
@@ -41,9 +52,33 @@ static size_t join_paths(char *dst, size_t bufsize,
 
        while ((c = *p++)) {
            if (c == '/') {
-               if (!slash)
+               if (!slash) {
                    EMIT(c);
+                   bp = malloc(sizeof *bp);
+                   bp->n = n;
+                   bp->q = q;
+                   bp->prev = stk;
+                   stk = bp;
+               }
                slash = true;
+           } else if (c == '.' && slash) {
+               if (!*p || *p == '/') {
+                   continue;   /* Single dot */
+               }
+               if (*p == '.' && (!p[1] || p[1] == '/')) {
+                   /* Double dot; unwind one level */
+                   p++;
+                   if (stk) {
+                       bp = stk;
+                       stk = stk->prev;
+                       free(bp);
+                   }
+                   if (stk) {
+                       n = stk->n;
+                       q = stk->q;
+                   }
+                   continue;
+               }
            } else {
                EMIT(c);
                slash = false;
@@ -54,6 +89,11 @@ static size_t join_paths(char *dst, size_t bufsize,
     if (bufsize)
        *q = '\0';
 
+    while ((bp = stk)) {
+       stk = stk->prev;
+       free(bp);
+    }
+
     return n;
 }
 
@@ -75,6 +115,8 @@ int chdir(const char *src)
     struct file *file;
     char cwd_buf[CURRENTDIR_MAX];
 
+    dprintf("chdir: from %s add %s\n", this_fs->cwd_name, src);
+
     if (this_fs->fs_ops->chdir)
        return this_fs->fs_ops->chdir(this_fs, src);
 
@@ -99,5 +141,7 @@ int chdir(const char *src)
     /* Make sure the cwd_name ends in a slash, it's supposed to be a prefix */
     join_paths(this_fs->cwd_name, CURRENTDIR_MAX, cwd_buf, "/");
 
+    dprintf("chdir: final: %s\n", this_fs->cwd_name);
+
     return 0;
 }