core, chdir: collapse slashes, avoid copy-to-self syslinux-4.00-pre64
authorH. Peter Anvin <hpa@zytor.com>
Mon, 28 Jun 2010 01:46:36 +0000 (18:46 -0700)
committerH. Peter Anvin <hpa@zytor.com>
Mon, 28 Jun 2010 01:46:36 +0000 (18:46 -0700)
Collapse multiple slashes into one (this still doesn't resolve . and
.. in the path, since that requires awareness of symlinks.)

This code also avoids a copy-over-self bug by introducing a temporary
buffer.

Reported-by: Gene Cumm <gene.cumm@gmail.com>
Signed-off-by: H. Peter Anvin <hpa@zytor.com>
core/fs/chdir.c

index bfce9bc..9e8dfd2 100644 (file)
@@ -16,15 +16,56 @@ void pm_realpath(com32sys_t *regs)
     realpath(dst, src, FILENAME_MAX);
 }
 
+#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)
+{
+    const char *list[2];
+    int i;
+    char c;
+    const char *p;
+    char *q  = dst;
+    size_t n = 0;
+    bool slash = false;
+    
+    list[0] = s1;
+    list[1] = s2;
+
+    for (i = 0; i < 2; i++) {
+       p = list[i];
+
+       while ((c = *p++)) {
+           if (c == '/') {
+               if (!slash)
+                   EMIT(c);
+               slash = true;
+           } else {
+               EMIT(c);
+               slash = false;
+           }
+       }
+    }
+
+    if (bufsize)
+       *q = '\0';
+
+    return n;
+}
+
 size_t realpath(char *dst, const char *src, size_t bufsize)
 {
     if (this_fs->fs_ops->realpath) {
        return this_fs->fs_ops->realpath(this_fs, dst, src, bufsize);
     } else {
        /* Filesystems with "common" pathname resolution */
-       return snprintf(dst, bufsize, "%s%s",
-                       src[0] == '/' ? "" : this_fs->cwd_name,
-                       src);
+       return join_paths(dst, bufsize, 
+                         src[0] == '/' ? "" : this_fs->cwd_name,
+                         src);
     }
 }
 
@@ -32,7 +73,7 @@ int chdir(const char *src)
 {
     int rv;
     struct file *file;
-    char *p;
+    char cwd_buf[CURRENTDIR_MAX];
 
     if (this_fs->fs_ops->chdir)
        return this_fs->fs_ops->chdir(this_fs, src);
@@ -53,14 +94,10 @@ int chdir(const char *src)
     _close_file(file);
 
     /* Save the current working directory */
-    realpath(this_fs->cwd_name, src, CURRENTDIR_MAX);
-    p = strchr(this_fs->cwd_name, '\0');
+    realpath(cwd_buf, src, CURRENTDIR_MAX);
 
     /* Make sure the cwd_name ends in a slash, it's supposed to be a prefix */
-    if (p < this_fs->cwd_name + CURRENTDIR_MAX - 1 &&
-       (p == this_fs->cwd_name || p[1] != '/')) {
-       p[0] = '/';
-       p[1] = '\0';
-    }
+    join_paths(this_fs->cwd_name, CURRENTDIR_MAX, cwd_buf, "/");
+
     return 0;
 }