So far links can only point to files. Implement links to
directories. With this all kinds of links are supported:

- relative links
- absolute links
- links including ".."
- link loops (are detected, return -EMLINK)

Signed-off-by: Sascha Hauer <[email protected]>
---
 commands/readlink.c |   2 +-
 fs/fs.c             | 272 +++++++++++++++++++++++++++++-----------------------
 include/fs.h        |   3 +-
 3 files changed, 157 insertions(+), 120 deletions(-)

diff --git a/commands/readlink.c b/commands/readlink.c
index 4ac576f16f..a19c8e0041 100644
--- a/commands/readlink.c
+++ b/commands/readlink.c
@@ -48,7 +48,7 @@ static int do_readlink(int argc, char *argv[])
                goto err;
 
        if (canonicalize) {
-               char *buf = normalise_link(argv[optind], realname);
+               char *buf = canonicalize_path(realname);
 
                if (!buf)
                        goto err;
diff --git a/fs/fs.c b/fs/fs.c
index 9cb15738b0..aaae5bbdd8 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -83,61 +83,6 @@ static int init_fs(void)
 
 postcore_initcall(init_fs);
 
-char *normalise_link(const char *pathname, const char *symlink)
-{
-       const char *buf = symlink;
-       char *path_free, *path;
-       char *absolute_path;
-       int point = 0;
-       int dir = 1;
-       int len;
-
-       if (symlink[0] == '/')
-               return strdup(symlink);
-
-       while (*buf == '.' || *buf == '/') {
-               if (*buf == '.') {
-                       point++;
-               } else if (*buf == '/') {
-                       point = 0;
-                       dir++;
-               }
-               if (point > 2) {
-                       buf -= 2;
-                       break;
-               }
-               buf++;
-       }
-
-       path = path_free = strdup(pathname);
-       if (!path)
-               return NULL;
-
-       while(dir) {
-               path = dirname(path);
-               dir--;
-       }
-
-       len = strlen(buf) + strlen(path) + 1;
-       if (buf[0] != '/')
-               len++;
-
-       absolute_path = calloc(sizeof(char), len);
-
-       if (!absolute_path)
-               goto out;
-
-       strcat(absolute_path, path);
-       if (buf[0] != '/')
-               strcat(absolute_path, "/");
-       strcat(absolute_path, buf);
-
-out:
-       free(path_free);
-
-       return absolute_path;
-}
-
 char *normalise_path(const char *pathname)
 {
        char *path = xzalloc(strlen(pathname) + strlen(cwd) + 2);
@@ -197,6 +142,137 @@ char *normalise_path(const char *pathname)
 }
 EXPORT_SYMBOL(normalise_path);
 
+static int __lstat(const char *filename, struct stat *s);
+
+static char *__canonicalize_path(const char *_pathname, int level)
+{
+       char *path, *freep;
+       char *outpath;
+       int ret;
+       struct stat s;
+
+       if (level > 10)
+               return ERR_PTR(-ELOOP);
+
+       path = freep = xstrdup(_pathname);
+
+       if (*path == '/')
+               outpath = xstrdup("/");
+       else
+               outpath = __canonicalize_path(cwd, level + 1);
+
+       while (1) {
+               char *p = strsep(&path, "/");
+               char *tmp;
+               char link[PATH_MAX] = {};
+
+               if (!p)
+                       break;
+               if (p[0] == '\0')
+                       continue;
+               if (!strcmp(p, "."))
+                       continue;
+               if (!strcmp(p, "..")) {
+                       tmp = xstrdup(dirname(outpath));
+                       free(outpath);
+                       outpath = tmp;
+                       continue;
+               }
+
+               tmp = basprintf("%s/%s", outpath, p);
+               free(outpath);
+               outpath = tmp;
+
+               ret = __lstat(outpath, &s);
+               if (ret)
+                       goto out;
+
+               if (!S_ISLNK(s.st_mode))
+                       continue;
+
+               ret = readlink(outpath, link, PATH_MAX - 1);
+               if (ret < 0)
+                       goto out;
+
+               if (link[0] == '/') {
+                       free(outpath);
+                       outpath = __canonicalize_path(link, level + 1);
+               } else {
+                       tmp = basprintf("%s/%s", dirname(outpath), link);
+                       free(outpath);
+                       outpath = __canonicalize_path(tmp, level + 1);
+                       free(tmp);
+               }
+
+               if (IS_ERR(outpath))
+                       goto out;
+       }
+out:
+       free(freep);
+
+       return outpath;
+}
+
+/*
+ * canonicalize_path - resolve links in path
+ * @pathname: The input path
+ *
+ * This function resolves all links in @pathname and returns
+ * a path without links in it.
+ *
+ * Return: Path with links resolved. Allocated, must be freed after use.
+ */
+char *canonicalize_path(const char *pathname)
+{
+       char *r, *p = __canonicalize_path(pathname, 0);
+
+       if (IS_ERR(p))
+               return ERR_CAST(p);
+
+       r = normalise_path(p);
+       free(p);
+
+       return r;
+}
+
+/*
+ * canonicalize_dir - resolve links in path
+ * @pathname: The input path
+ *
+ * This function resolves all links except the last one. Needed to give
+ * access to the link itself.
+ *
+ * Return: Path with links resolved. Allocated, must be freed after use.
+ */
+char *canonicalize_dir(const char *pathname)
+{
+       char *f, *d, *r, *ret, *p;
+       char *freep1, *freep2;
+
+       freep1 = xstrdup(pathname);
+       freep2 = xstrdup(pathname);
+       f = basename(freep1);
+       d = dirname(freep2);
+
+       p = __canonicalize_path(d, 0);
+       if (IS_ERR(p)) {
+               ret = ERR_CAST(p);
+               goto out;
+       }
+
+       r = basprintf("%s/%s", p, f);
+
+       ret = normalise_path(r);
+
+       free(r);
+       free(p);
+out:
+       free(freep1);
+       free(freep2);
+
+       return ret;
+}
+
 LIST_HEAD(fs_device_list);
 static struct fs_device_d *fs_dev_root;
 
@@ -493,7 +569,7 @@ int unlink(const char *pathname)
 {
        struct fs_device_d *fsdev;
        struct fs_driver_d *fsdrv;
-       char *p = normalise_path(pathname);
+       char *p = canonicalize_path(pathname);
        char *freep = p;
        int ret;
        struct stat s;
@@ -530,42 +606,6 @@ out:
 }
 EXPORT_SYMBOL(unlink);
 
-static char *realfile(const char *pathname, struct stat *s)
-{
-       char *path = normalise_path(pathname);
-       int ret;
-
-       ret = lstat(path, s);
-       if (ret)
-               goto out;
-
-       if (S_ISLNK(s->st_mode)) {
-               char tmp[PATH_MAX];
-               char *new_path;
-
-               memset(tmp, 0, PATH_MAX);
-
-               ret = readlink(path, tmp, PATH_MAX - 1);
-               if (ret < 0)
-                       goto out;
-
-               new_path = normalise_link(path, tmp);
-               free(path);
-               if (!new_path)
-                       return ERR_PTR(-ENOMEM);
-               path = new_path;
-
-               ret = lstat(path, s);
-       }
-
-       if (!ret)
-               return path;
-
-out:
-       free(path);
-       return ERR_PTR(ret);
-}
-
 int open(const char *pathname, int flags, ...)
 {
        struct fs_device_d *fsdev;
@@ -577,13 +617,14 @@ int open(const char *pathname, int flags, ...)
        char *freep;
        int ret;
 
-       path = realfile(pathname, &s);
-
+       path = canonicalize_path(pathname);
        if (IS_ERR(path)) {
-               exist_err = PTR_ERR(path);
-               path = normalise_path(pathname);
+               ret = PTR_ERR(path);
+               goto out2;
        }
 
+       exist_err = stat(path, &s);
+
        freep = path;
 
        if (!exist_err && S_ISDIR(s.st_mode)) {
@@ -657,6 +698,7 @@ out:
        put_file(f);
 out1:
        free(freep);
+out2:
        if (ret)
                errno = -ret;
        return ret;
@@ -1026,7 +1068,7 @@ int readlink(const char *pathname, char *buf, size_t 
bufsiz)
 {
        struct fs_driver_d *fsdrv;
        struct fs_device_d *fsdev;
-       char *p = normalise_path(pathname);
+       char *p = canonicalize_dir(pathname);
        char *freep = p;
        int ret;
        struct stat s;
@@ -1070,24 +1112,15 @@ int symlink(const char *pathname, const char *newpath)
        struct fs_driver_d *fsdrv;
        struct fs_device_d *fsdev;
        char *p;
-       char *freep = normalise_path(pathname);
        int ret;
        struct stat s;
 
-       if (!freep)
-               return -ENOMEM;
-
-       if (!stat(freep, &s) && S_ISDIR(s.st_mode)) {
-               ret = -ENOSYS;
+       p = canonicalize_path(newpath);
+       if (IS_ERR(p)) {
+               ret = PTR_ERR(p);
                goto out;
        }
 
-       free(freep);
-       freep = p = normalise_path(newpath);
-
-       if (!p)
-               return -ENOMEM;
-
        ret = lstat(p, &s);
        if (!ret) {
                ret = -EEXIST;
@@ -1108,7 +1141,7 @@ int symlink(const char *pathname, const char *newpath)
        }
 
 out:
-       free(freep);
+       free(p);
        if (ret)
                errno = -ret;
 
@@ -1387,7 +1420,7 @@ DIR *opendir(const char *pathname)
        DIR *dir = NULL;
        struct fs_device_d *fsdev;
        struct fs_driver_d *fsdrv;
-       char *p = normalise_path(pathname);
+       char *p = canonicalize_path(pathname);
        char *freep = p;
        int ret;
        struct stat s;
@@ -1467,18 +1500,21 @@ EXPORT_SYMBOL(closedir);
 
 int stat(const char *filename, struct stat *s)
 {
-       char *f;
+       char *path = canonicalize_path(filename);
+       int ret;
 
-       f = realfile(filename, s);
-       if (IS_ERR(f))
-               return PTR_ERR(f);
+       if (IS_ERR(path))
+               return PTR_ERR(path);
 
-       free(f);
-       return 0;
+       ret = lstat(path, s);
+
+       free(path);
+
+       return ret;
 }
 EXPORT_SYMBOL(stat);
 
-int lstat(const char *filename, struct stat *s)
+static int __lstat(const char *filename, struct stat *s)
 {
        struct fs_driver_d *fsdrv;
        struct fs_device_d *fsdev;
diff --git a/include/fs.h b/include/fs.h
index 6a592893a9..71edb22f26 100644
--- a/include/fs.h
+++ b/include/fs.h
@@ -128,7 +128,8 @@ char *mkmodestr(unsigned long mode, char *str);
  * of "..", "." and double slashes. The returned string must be freed wit 
free().
  */
 char *normalise_path(const char *path);
-char *normalise_link(const char *pathname, const char* symlink);
+
+char *canonicalize_path(const char *pathname);
 
 char *get_mounted_path(const char *path);
 
-- 
2.11.0


_______________________________________________
barebox mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/barebox

Reply via email to