summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--commands/ls.c26
-rw-r--r--commands/readlink.c2
-rw-r--r--common/misc.c1
-rw-r--r--fs/fs.c400
-rw-r--r--include/fs.h3
5 files changed, 237 insertions, 195 deletions
diff --git a/commands/ls.c b/commands/ls.c
index 331a4d2015..771477b6e0 100644
--- a/commands/ls.c
+++ b/commands/ls.c
@@ -26,15 +26,21 @@
#include <getopt.h>
#include <stringlist.h>
-static void ls_one(const char *path, const char* fullname, struct stat *s)
+static void ls_one(const char *path, const char* fullname)
{
char modestr[11];
unsigned int namelen = strlen(path);
+ struct stat s;
+ int ret;
+
+ ret = lstat(fullname, &s);
+ if (ret)
+ return;
- mkmodestr(s->st_mode, modestr);
- printf("%s %14llu %*.*s", modestr, s->st_size, namelen, namelen, path);
+ mkmodestr(s.st_mode, modestr);
+ printf("%s %14llu %*.*s", modestr, s.st_size, namelen, namelen, path);
- if (S_ISLNK(s->st_mode)) {
+ if (S_ISLNK(s.st_mode)) {
char realname[PATH_MAX];
memset(realname, 0, PATH_MAX);
@@ -58,14 +64,14 @@ int ls(const char *path, ulong flags)
string_list_init(&sl);
- if (lstat(path, &s))
+ if (stat(path, &s))
return -errno;
if (flags & LS_SHOWARG && s.st_mode & S_IFDIR)
printf("%s:\n", path);
if (!(s.st_mode & S_IFDIR)) {
- ls_one(path, path, &s);
+ ls_one(path, path);
return 0;
}
@@ -89,7 +95,7 @@ int ls(const char *path, ulong flags)
continue;
}
- ls_one(entry->str, tmp, &s);
+ ls_one(entry->str, tmp);
}
}
@@ -162,7 +168,7 @@ static int do_ls(int argc, char *argv[])
/* first pass: all files */
while (o < argc) {
- ret = lstat(argv[o], &s);
+ ret = stat(argv[o], &s);
if (ret) {
printf("%s: %s: %s\n", argv[0],
argv[o], errno_str());
@@ -175,7 +181,7 @@ static int do_ls(int argc, char *argv[])
if (flags & LS_COLUMN)
string_list_add_sorted(&sl, argv[o]);
else
- ls_one(argv[o], argv[o], &s);
+ ls_one(argv[o], argv[o]);
}
o++;
@@ -190,7 +196,7 @@ static int do_ls(int argc, char *argv[])
/* second pass: directories */
while (o < argc) {
- ret = lstat(argv[o], &s);
+ ret = stat(argv[o], &s);
if (ret) {
o++;
exitcode = COMMAND_ERROR;
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/common/misc.c b/common/misc.c
index f0f0b808b7..c5d3704c82 100644
--- a/common/misc.c
+++ b/common/misc.c
@@ -66,6 +66,7 @@ const char *strerror(int errnum)
case ENETDOWN : str = "Network is down"; break;
case ETIMEDOUT : str = "Connection timed out"; break;
case EPROBE_DEFER : str = "Requested probe deferral"; break;
+ case ELOOP : str = "Too many symbolic links encountered"; break;
#if 0 /* These are probably not needed */
case ENOTBLK : str = "Block device required"; break;
case EFBIG : str = "File too large"; break;
diff --git a/fs/fs.c b/fs/fs.c
index 1901c94ad1..1da080502c 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;
@@ -435,57 +511,6 @@ static int dir_is_empty(const char *pathname)
return ret;
}
-#define S_UB_IS_EMPTY (1 << 31)
-#define S_UB_EXISTS (1 << 30)
-#define S_UB_DOES_NOT_EXIST (1 << 29)
-
-/*
- * Helper function to check the prerequisites of a path given
- * to fs functions. Besides the flags above S_IFREG and S_IFDIR
- * can be passed in.
- */
-static int path_check_prereq(const char *path, unsigned int flags)
-{
- struct stat s;
- unsigned int m;
- int ret = 0;
-
- if (lstat(path, &s)) {
- if (flags & S_UB_DOES_NOT_EXIST)
- goto out;
- ret = -ENOENT;
- goto out;
- }
-
- if (flags & S_UB_DOES_NOT_EXIST) {
- ret = -EEXIST;
- goto out;
- }
-
- if (flags == S_UB_EXISTS)
- goto out;
-
- m = s.st_mode;
-
- if (S_ISDIR(m)) {
- if (flags & S_IFREG) {
- ret = -EISDIR;
- goto out;
- }
- if ((flags & S_UB_IS_EMPTY) && !dir_is_empty(path)) {
- ret = -ENOTEMPTY;
- goto out;
- }
- }
- if ((flags & S_IFDIR) && S_ISREG(m)) {
- ret = -ENOTDIR;
- goto out;
- }
-
-out:
- return ret;
-}
-
static int parent_check_directory(const char *path)
{
struct stat s;
@@ -515,12 +540,17 @@ int chdir(const char *pathname)
{
char *p = normalise_path(pathname);
int ret;
+ struct stat s;
-
- ret = path_check_prereq(p, S_IFDIR);
+ ret = stat(p, &s);
if (ret)
goto out;
+ if (!S_ISDIR(s.st_mode)) {
+ ret = -ENOTDIR;
+ goto out;
+ }
+
automount_mount(p, 0);
strcpy(cwd, p);
@@ -539,13 +569,17 @@ int unlink(const char *pathname)
{
struct fs_device_d *fsdev;
struct fs_driver_d *fsdrv;
- char *p = normalise_path(pathname);
+ char *p = canonicalize_dir(pathname);
char *freep = p;
int ret;
+ struct stat s;
- ret = path_check_prereq(pathname, S_IFREG);
- if (ret) {
- ret = -EINVAL;
+ ret = lstat(p, &s);
+ if (ret)
+ goto out;
+
+ if (S_ISDIR(s.st_mode)) {
+ ret = -EISDIR;
goto out;
}
@@ -572,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;
@@ -619,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)) {
@@ -699,6 +698,7 @@ out:
put_file(f);
out1:
free(freep);
+out2:
if (ret)
errno = -ret;
return ret;
@@ -1068,14 +1068,20 @@ 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;
- ret = path_check_prereq(pathname, S_IFLNK);
+ ret = lstat(pathname, &s);
if (ret)
goto out;
+ if (!S_ISLNK(s.st_mode)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
fsdev = get_fs_device_and_root_path(&p);
if (!fsdev) {
ret = -ENODEV;
@@ -1106,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;
@@ -1144,7 +1141,7 @@ int symlink(const char *pathname, const char *newpath)
}
out:
- free(freep);
+ free(p);
if (ret)
errno = -ret;
@@ -1274,15 +1271,21 @@ int mount(const char *device, const char *fsname, const char *_path,
device, path, fsname, fsoptions);
if (fs_dev_root) {
+ struct stat s;
+
fsdev = get_fsdevice_by_path(path);
if (fsdev != fs_dev_root) {
printf("sorry, no nested mounts\n");
ret = -EBUSY;
goto err_free_path;
}
- ret = path_check_prereq(path, S_IFDIR);
+ ret = lstat(path, &s);
if (ret)
goto err_free_path;
+ if (!S_ISDIR(s.st_mode)) {
+ ret = -ENOTDIR;
+ goto err_free_path;
+ }
} else {
/* no mtab, so we only allow to mount on '/' */
if (*path != '/' || *(path + 1)) {
@@ -1417,14 +1420,20 @@ 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;
- ret = path_check_prereq(pathname, S_IFDIR);
+ ret = stat(pathname, &s);
if (ret)
goto out;
+ if (!S_ISDIR(s.st_mode)) {
+ ret = -ENOTDIR;
+ goto out;
+ }
+
fsdev = get_fs_device_and_root_path(&p);
if (!fsdev) {
ret = -ENOENT;
@@ -1491,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;
@@ -1539,6 +1551,21 @@ out:
return ret;
}
+
+int lstat(const char *filename, struct stat *s)
+{
+ char *f = canonicalize_dir(filename);
+ int ret;
+
+ if (IS_ERR(f))
+ return PTR_ERR(f);
+
+ ret = __lstat(f, s);
+
+ free(f);
+
+ return ret;
+}
EXPORT_SYMBOL(lstat);
int fstat(int fd, struct stat *s)
@@ -1564,14 +1591,17 @@ int mkdir (const char *pathname, mode_t mode)
char *p = normalise_path(pathname);
char *freep = p;
int ret;
+ struct stat s;
ret = parent_check_directory(p);
if (ret)
goto out;
- ret = path_check_prereq(pathname, S_UB_DOES_NOT_EXIST);
- if (ret)
+ ret = stat(pathname, &s);
+ if (!ret) {
+ ret = -EEXIST;
goto out;
+ }
fsdev = get_fs_device_and_root_path(&p);
if (!fsdev) {
@@ -1601,16 +1631,20 @@ int rmdir (const char *pathname)
char *p = normalise_path(pathname);
char *freep = p;
int ret;
+ struct stat s;
- ret = path_check_prereq(pathname, S_IFLNK);
- if (!ret) {
+ ret = lstat(pathname, &s);
+ if (ret)
+ goto out;
+ if (!S_ISDIR(s.st_mode)) {
ret = -ENOTDIR;
goto out;
}
- ret = path_check_prereq(pathname, S_IFDIR | S_UB_IS_EMPTY);
- if (ret)
+ if (!dir_is_empty(pathname)) {
+ ret = -ENOTEMPTY;
goto out;
+ }
fsdev = get_fs_device_and_root_path(&p);
if (!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);