[PATCH 3/4] fs: Implement links to directories

Sascha Hauer s.hauer at pengutronix.de
Thu May 11 02:11:00 PDT 2017


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 <s.hauer at pengutronix.de>
---
 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




More information about the barebox mailing list