summaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2018-03-29 13:47:12 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2018-07-11 09:58:38 +0200
commitb3fbfad7aeaf8ee36216e1d14b5d7be27ba612de (patch)
tree61299cecd2c49902ff831813daa81926bacd69db /fs
parent4c7b877a5c90bcf80d42086d246981f0686d1e27 (diff)
downloadbarebox-b3fbfad7aeaf8ee36216e1d14b5d7be27ba612de.tar.gz
barebox-b3fbfad7aeaf8ee36216e1d14b5d7be27ba612de.tar.xz
fs: dentry cache implementation
This adds the Linux dentry cache implementation to barebox. Until now every filesystem driver resolves the full path to a file for itself. This leads to code duplication and is error prone since resolving paths is a complicated task. Also it can narrow down the lookup performance since barebox only knows ASCII paths and has no way of caching lookups. With this patch we get the Linux dcache implementation. The path resolving code from fs/namei.c is nearly taken as-is, minus the RCU and locking code. Dcaching is made simple as of now: We simply cache everything and never release any dentries. Although we do reference counting for inodes and dentries it is effectively not used yet. We never free anything until a fs is unmounted in which case we free everything no matter if references are taken or not. This patch also contains a wrapper in fs/legacy.c to support filesystems with the old API. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'fs')
-rw-r--r--fs/Kconfig23
-rw-r--r--fs/Makefile3
-rw-r--r--fs/ext4/Kconfig1
-rw-r--r--fs/fat/Kconfig1
-rw-r--r--fs/fs.c3186
-rw-r--r--fs/legacy.c315
-rw-r--r--fs/libfs.c97
-rw-r--r--fs/pstore/Kconfig1
-rw-r--r--fs/squashfs/Kconfig1
-rw-r--r--fs/squashfs/super.c9
-rw-r--r--fs/ubifs/Kconfig3
11 files changed, 2635 insertions, 1005 deletions
diff --git a/fs/Kconfig b/fs/Kconfig
index 3512000556..b60314b1ec 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -6,12 +6,24 @@ config FS
default y
select FILETYPE
+config FS_LEGACY
+ bool
+ help
+ invisible option selected by filesystem drivers which haven't
+ been ported to dentry cache.
+
+if FS_LEGACY
+comment "Some selected filesystems still use the legacy FS API."
+comment "Consider updating them."
+endif
+
config FS_AUTOMOUNT
bool
config FS_CRAMFS
bool
select ZLIB
+ select FS_LEGACY
prompt "cramfs support"
source fs/ext4/Kconfig
@@ -19,15 +31,18 @@ source fs/ext4/Kconfig
config FS_RAMFS
bool
default y
+ select FS_LEGACY
prompt "ramfs support"
config FS_DEVFS
bool
default y
+ select FS_LEGACY
prompt "devfs support"
config FS_TFTP
bool
+ select FS_LEGACY
prompt "tftp support"
depends on NET
@@ -35,14 +50,17 @@ config FS_OMAP4_USBBOOT
bool
prompt "Filesystem over usb boot"
depends on OMAP4_USBBOOT
+ select FS_LEGACY
config FS_NFS
depends on NET
+ select FS_LEGACY
bool
prompt "nfs support"
config FS_EFI
depends on EFI_BOOTUP
+ select FS_LEGACY
bool
prompt "EFI filesystem support"
help
@@ -51,6 +69,7 @@ config FS_EFI
config FS_EFIVARFS
depends on EFI_BOOTUP
+ select FS_LEGACY
bool
prompt "EFI variable filesystem support (efivarfs)"
help
@@ -62,6 +81,7 @@ source fs/ubifs/Kconfig
config FS_BPKFS
bool
select CRC32
+ select FS_LEGACY
prompt "BPKFS support"
help
Simple update file format developed for Somfy, tools and library are
@@ -78,10 +98,12 @@ config FS_BPKFS
config FS_UIMAGEFS
bool
select CRC32
+ select FS_LEGACY
prompt "uImage FS support"
config FS_SMHFS
depends on ARM_SEMIHOSTING
+ select FS_LEGACY
bool
prompt "Semihosting FS support"
help
@@ -95,6 +117,7 @@ source fs/squashfs/Kconfig
config FS_RATP
bool
depends on RATP
+ select FS_LEGACY
prompt "RATP filesystem support"
help
This enables support for transferring files over RATP. A host can
diff --git a/fs/Makefile b/fs/Makefile
index 8e3fd78e92..ac3e6a03aa 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -2,9 +2,10 @@ obj-$(CONFIG_FS_CRAMFS) += cramfs/
obj-$(CONFIG_FS_EXT4) += ext4/
obj-$(CONFIG_FS_RAMFS) += ramfs.o
obj-y += devfs-core.o
+obj-$(CONFIG_FS_LEGACY) += legacy.o
obj-$(CONFIG_FS_DEVFS) += devfs.o
obj-$(CONFIG_FS_FAT) += fat/
-obj-y += fs.o
+obj-y += fs.o libfs.o
obj-$(CONFIG_FS_UBIFS) += ubifs/
obj-$(CONFIG_FS_TFTP) += tftp.o
obj-$(CONFIG_FS_OMAP4_USBBOOT) += omap4_usbbootfs.o
diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig
index f36043d9a7..8643e9d859 100644
--- a/fs/ext4/Kconfig
+++ b/fs/ext4/Kconfig
@@ -1,3 +1,4 @@
config FS_EXT4
bool
+ select FS_LEGACY
prompt "ext4 filesystem support"
diff --git a/fs/fat/Kconfig b/fs/fat/Kconfig
index 0699728494..b1def851cf 100644
--- a/fs/fat/Kconfig
+++ b/fs/fat/Kconfig
@@ -1,5 +1,6 @@
menuconfig FS_FAT
bool
+ select FS_LEGACY
prompt "FAT filesystem support"
if FS_FAT
diff --git a/fs/fs.c b/fs/fs.c
index 8a49e32b5c..41818ea811 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -36,6 +36,7 @@
#include <block.h>
#include <libfile.h>
#include <parseopt.h>
+#include <linux/namei.h>
char *mkmodestr(unsigned long mode, char *str)
{
@@ -69,8 +70,12 @@ char *mkmodestr(unsigned long mode, char *str)
EXPORT_SYMBOL(mkmodestr);
static char *cwd;
+static struct dentry *cwd_dentry;
+static struct vfsmount *cwd_mnt;
static FILE *files;
+static struct dentry *d_root;
+static struct vfsmount *mnt_root;
static int init_fs(void)
{
@@ -84,226 +89,40 @@ static int init_fs(void)
postcore_initcall(init_fs);
-char *normalise_path(const char *pathname)
-{
- char *path = xzalloc(strlen(pathname) + strlen(cwd) + 2);
- char *in, *out, *slashes[32];
- int sl = 0;
-
- debug("in: %s\n", pathname);
-
- if (*pathname != '/')
- strcpy(path, cwd);
- strcat(path, "/");
- strcat(path, pathname);
-
- slashes[0] = in = out = path;
-
- while (*in) {
- if(*in == '/') {
- slashes[sl++] = out;
- *out++ = *in++;
- while(*in == '/')
- in++;
- } else {
- if (*in == '.' && (*(in + 1) == '/' || !*(in + 1))) {
- sl--;
- if (sl < 0)
- sl = 0;
- out = slashes[sl];
- in++;
- continue;
- }
- if (*in == '.' && *(in + 1) == '.') {
- sl -= 2;
- if (sl < 0)
- sl = 0;
- out = slashes[sl];
- in += 2;
- continue;
- }
- *out++ = *in++;
- }
- }
-
- *out-- = 0;
-
- /*
- * Remove trailing slash
- */
- if (*out == '/')
- *out = 0;
-
- if (!*path) {
- *path = '/';
- *(path + 1) = 0;
- }
-
- return path;
-}
-EXPORT_SYMBOL(normalise_path);
-
-static int __lstat(const char *filename, struct stat *s);
static struct fs_device_d *get_fsdevice_by_path(const char *path);
-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 == '/' || !strcmp(cwd, "/"))
- outpath = xstrdup("");
- else
- outpath = __canonicalize_path(cwd, level + 1);
-
- while (1) {
- char *p = strsep(&path, "/");
- char *tmp;
- char link[PATH_MAX] = {};
- struct fs_device_d *fsdev;
-
- 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;
-
- /*
- * Don't bother filesystems without link support
- * with an additional stat() call.
- */
- fsdev = get_fsdevice_by_path(outpath);
- if (!fsdev || !fsdev->driver->readlink)
- continue;
-
- 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);
-
- if (!*outpath) {
- free(outpath);
- outpath = xstrdup("/");
- }
-
- return outpath;
-}
+LIST_HEAD(fs_device_list);
-/*
- * 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)
+struct vfsmount *mntget(struct vfsmount *mnt)
{
- char *r, *p = __canonicalize_path(pathname, 0);
-
- if (IS_ERR(p))
- return ERR_CAST(p);
+ if (!mnt)
+ return NULL;
- r = normalise_path(p);
- free(p);
+ mnt->ref++;
- return r;
+ return mnt;
}
-/*
- * 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.
- */
-static char *canonicalize_dir(const char *pathname)
+void mntput(struct vfsmount *mnt)
{
- 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);
+ if (!mnt)
+ return;
- return ret;
+ mnt->ref--;
}
-LIST_HEAD(fs_device_list);
-static struct fs_device_d *fs_dev_root;
-
-static struct fs_device_d *get_fsdevice_by_path(const char *path)
+struct vfsmount *lookup_mnt(struct path *path)
{
- struct fs_device_d *fsdev = NULL;
+ struct fs_device_d *fsdev;
for_each_fs_device(fsdev) {
- int len = strlen(fsdev->path);
- if (!strncmp(path, fsdev->path, len) &&
- (path[len] == '/' || path[len] == 0))
- return fsdev;
+ if (path->dentry == fsdev->vfsmount.mountpoint) {
+ mntget(&fsdev->vfsmount);
+ return &fsdev->vfsmount;
+ }
}
- return fs_dev_root;
+ return NULL;
}
/*
@@ -348,6 +167,8 @@ static void put_file(FILE *f)
free(f->path);
f->path = NULL;
f->in_use = 0;
+ iput(f->f_inode);
+ dput(f->dentry);
}
static int check_fd(int fd)
@@ -360,380 +181,20 @@ static int check_fd(int fd)
return 0;
}
-#ifdef CONFIG_FS_AUTOMOUNT
-
-#define AUTOMOUNT_IS_FILE (1 << 0)
-
-struct automount {
- char *path;
- char *cmd;
- struct list_head list;
- unsigned int flags;
-};
-
-static LIST_HEAD(automount_list);
-
-void automount_remove(const char *_path)
-{
- char *path = normalise_path(_path);
- struct automount *am;
-
- list_for_each_entry(am, &automount_list, list) {
- if (!strcmp(path, am->path))
- goto found;
- }
-
- return;
-found:
- list_del(&am->list);
- free(am->path);
- free(am->cmd);
- free(am);
-}
-EXPORT_SYMBOL(automount_remove);
-
-int automount_add(const char *path, const char *cmd)
-{
- struct automount *am = xzalloc(sizeof(*am));
- struct stat s;
- int ret;
-
- am->path = normalise_path(path);
- am->cmd = xstrdup(cmd);
-
- automount_remove(am->path);
-
- ret = stat(path, &s);
- if (!ret) {
- /*
- * If it exists it must be a directory
- */
- if (!S_ISDIR(s.st_mode))
- return -ENOTDIR;
- } else {
- am->flags |= AUTOMOUNT_IS_FILE;
- }
-
- list_add_tail(&am->list, &automount_list);
-
- return 0;
-}
-EXPORT_SYMBOL(automount_add);
-
-void cdev_create_default_automount(struct cdev *cdev)
-{
- char *path, *cmd;
-
- path = basprintf("/mnt/%s", cdev->name);
- cmd = basprintf("mount %s", cdev->name);
-
- make_directory(path);
- automount_add(path, cmd);
-
- free(cmd);
- free(path);
-}
-
-void automount_print(void)
-{
- struct automount *am;
-
- list_for_each_entry(am, &automount_list, list)
- printf("%-20s %s\n", am->path, am->cmd);
-}
-EXPORT_SYMBOL(automount_print);
-
-static void automount_mount(const char *path, int instat)
-{
- struct automount *am;
- int ret;
- static int in_automount;
-
- if (in_automount)
- return;
-
- in_automount++;
-
- if (fs_dev_root != get_fsdevice_by_path(path))
- goto out;
-
- list_for_each_entry(am, &automount_list, list) {
- int len_path = strlen(path);
- int len_am_path = strlen(am->path);
-
- /*
- * stat is a bit special. We do not want to trigger
- * automount when someone calls stat() on the automount
- * directory itself.
- */
- if (instat && !(am->flags & AUTOMOUNT_IS_FILE) &&
- len_path == len_am_path) {
- continue;
- }
-
- if (len_path < len_am_path)
- continue;
-
- if (strncmp(path, am->path, len_am_path))
- continue;
-
- if (*(path + len_am_path) != 0 && *(path + len_am_path) != '/')
- continue;
-
- setenv("automount_path", am->path);
- export("automount_path");
- ret = run_command(am->cmd);
- setenv("automount_path", NULL);
-
- if (ret)
- printf("running automount command '%s' failed\n",
- am->cmd);
-
- break;
- }
-out:
- in_automount--;
-}
-
-BAREBOX_MAGICVAR(automount_path, "mountpath passed to automount scripts");
-
-#else
-static void automount_mount(const char *path, int instat)
+int create(struct dentry *dir, struct dentry *dentry)
{
-}
-#endif /* CONFIG_FS_AUTOMOUNT */
+ struct inode *inode;
-static struct fs_device_d *get_fs_device_and_root_path(char **path)
-{
- struct fs_device_d *fsdev;
-
- automount_mount(*path, 0);
-
- fsdev = get_fsdevice_by_path(*path);
- if (!fsdev)
- return NULL;
- if (fsdev != fs_dev_root)
- *path += strlen(fsdev->path);
-
- return fsdev;
-}
-
-static int dir_is_empty(const char *pathname)
-{
- DIR *dir;
- struct dirent *d;
- int ret = 1;
-
- dir = opendir(pathname);
- if (!dir) {
- errno = ENOENT;
- return -ENOENT;
- }
-
- while ((d = readdir(dir))) {
- if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
- continue;
- ret = 0;
- break;
- }
-
- closedir(dir);
- return ret;
-}
-
-static int parent_check_directory(const char *path)
-{
- struct stat s;
- int ret;
- char *dir = dirname(xstrdup(path));
-
- ret = lstat(dir, &s);
-
- free(dir);
-
- if (ret)
+ if (d_is_negative(dir))
return -ENOENT;
- if (!S_ISDIR(s.st_mode))
- return -ENOTDIR;
-
- return 0;
-}
-
-const char *getcwd(void)
-{
- return cwd;
-}
-EXPORT_SYMBOL(getcwd);
-
-int chdir(const char *pathname)
-{
- char *p = normalise_path(pathname);
- int ret;
- struct stat s;
-
- 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);
-
-out:
- free(p);
-
- if (ret)
- errno = -ret;
-
- return ret;
-}
-EXPORT_SYMBOL(chdir);
-
-int unlink(const char *pathname)
-{
- struct fs_device_d *fsdev;
- struct fs_driver_d *fsdrv;
- char *p = canonicalize_dir(pathname);
- char *freep = p;
- int ret;
- struct stat s;
-
- ret = lstat(p, &s);
- if (ret)
- goto out;
-
- if (S_ISDIR(s.st_mode)) {
- ret = -EISDIR;
- goto out;
- }
-
- fsdev = get_fs_device_and_root_path(&p);
- if (!fsdev) {
- ret = -ENOENT;
- goto out;
- }
- fsdrv = fsdev->driver;
+ inode = d_inode(dir);
- if (!fsdrv->unlink) {
- ret = -ENOSYS;
- goto out;
- }
+ if (!inode->i_op->create)
+ return -EROFS;
- ret = fsdrv->unlink(&fsdev->dev, p);
- if (ret)
- errno = -ret;
-out:
- free(freep);
- if (ret)
- errno = -ret;
- return ret;
+ return inode->i_op->create(inode, dentry, S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO);
}
-EXPORT_SYMBOL(unlink);
-
-int open(const char *pathname, int flags, ...)
-{
- struct fs_device_d *fsdev;
- struct fs_driver_d *fsdrv;
- FILE *f;
- int exist_err = 0;
- struct stat s;
- char *path;
- char *freep;
- int ret;
-
- path = canonicalize_path(pathname);
- if (IS_ERR(path)) {
- ret = PTR_ERR(path);
- goto out2;
- }
-
- exist_err = stat(path, &s);
-
- freep = path;
-
- if (!exist_err && S_ISDIR(s.st_mode)) {
- ret = -EISDIR;
- goto out1;
- }
-
- if (exist_err && !(flags & O_CREAT)) {
- ret = exist_err;
- goto out1;
- }
-
- if (exist_err) {
- ret = parent_check_directory(path);
- if (ret)
- goto out1;
- }
-
- f = get_file();
- if (!f) {
- ret = -EMFILE;
- goto out1;
- }
-
- fsdev = get_fs_device_and_root_path(&path);
- if (!fsdev) {
- ret = -ENOENT;
- goto out;
- }
-
- fsdrv = fsdev->driver;
-
- f->fsdev = fsdev;
- f->flags = flags;
-
- if ((flags & O_ACCMODE) && !fsdrv->write) {
- ret = -EROFS;
- goto out;
- }
-
- if (exist_err) {
- if (NULL != fsdrv->create)
- ret = fsdrv->create(&fsdev->dev, path,
- S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO);
- else
- ret = -EROFS;
- if (ret)
- goto out;
- }
-
- f->path = xstrdup(path);
-
- ret = fsdrv->open(&fsdev->dev, f, path);
- if (ret)
- goto out;
-
- if (flags & O_TRUNC) {
- ret = fsdrv->truncate(&fsdev->dev, f, 0);
- f->size = 0;
- if (ret)
- goto out;
- }
-
- if (flags & O_APPEND)
- f->pos = f->size;
-
- free(freep);
- return f->no;
-
-out:
- put_file(f);
-out1:
- free(freep);
-out2:
- if (ret)
- errno = -ret;
- return ret;
-}
-EXPORT_SYMBOL(open);
int creat(const char *pathname, mode_t mode)
{
@@ -869,6 +330,7 @@ static ssize_t __write(FILE *f, const void *buf, size_t count)
goto out;
} else {
f->size = f->pos + count;
+ f->f_inode->i_size = f->size;
}
}
ret = fsdrv->write(&f->fsdev->dev, f, buf, count);
@@ -1097,7 +559,7 @@ int close(int fd)
{
struct fs_driver_d *fsdrv;
FILE *f;
- int ret;
+ int ret = 0;
if (check_fd(fd))
return -errno;
@@ -1105,7 +567,9 @@ int close(int fd)
f = &files[fd];
fsdrv = f->fsdev->driver;
- ret = fsdrv->close(&f->fsdev->dev, f);
+
+ if (fsdrv->close)
+ ret = fsdrv->close(&f->fsdev->dev, f);
put_file(f);
@@ -1116,91 +580,6 @@ int close(int fd)
}
EXPORT_SYMBOL(close);
-int readlink(const char *pathname, char *buf, size_t bufsiz)
-{
- struct fs_driver_d *fsdrv;
- struct fs_device_d *fsdev;
- char *p = canonicalize_dir(pathname);
- char *freep = p;
- int ret;
- struct stat s;
-
- 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;
- goto out;
- }
- fsdrv = fsdev->driver;
-
- if (fsdrv->readlink)
- ret = fsdrv->readlink(&fsdev->dev, p, buf, bufsiz);
- else
- ret = -ENOSYS;
-
- if (ret)
- goto out;
-
-out:
- free(freep);
-
- if (ret)
- errno = -ret;
-
- return ret;
-}
-EXPORT_SYMBOL(readlink);
-
-int symlink(const char *pathname, const char *newpath)
-{
- struct fs_driver_d *fsdrv;
- struct fs_device_d *fsdev;
- char *p;
- int ret;
- struct stat s;
-
- p = canonicalize_path(newpath);
- if (IS_ERR(p)) {
- ret = PTR_ERR(p);
- goto out;
- }
-
- ret = lstat(p, &s);
- if (!ret) {
- ret = -EEXIST;
- goto out;
- }
-
- fsdev = get_fs_device_and_root_path(&p);
- if (!fsdev) {
- ret = -ENODEV;
- goto out;
- }
- fsdrv = fsdev->driver;
-
- if (fsdrv->symlink) {
- ret = fsdrv->symlink(&fsdev->dev, pathname, p);
- } else {
- ret = -EPERM;
- }
-
-out:
- free(p);
- if (ret)
- errno = -ret;
-
- return ret;
-}
-EXPORT_SYMBOL(symlink);
-
static int fs_match(struct device_d *dev, struct driver_d *drv)
{
return strcmp(dev->name, drv->name) ? -1 : 0;
@@ -1221,15 +600,56 @@ static int fs_probe(struct device_d *dev)
list_add_tail(&fsdev->list, &fs_device_list);
- if (!fs_dev_root)
- fs_dev_root = fsdev;
+ if (IS_ENABLED(CONFIG_FS_LEGACY) && !fsdev->sb.s_root) {
+ ret = fs_init_legacy(fsdev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+void dentry_kill(struct dentry *dentry)
+{
+ if (dentry->d_inode)
+ iput(dentry->d_inode);
+
+ if (!IS_ROOT(dentry))
+ dput(dentry->d_parent);
+
+ list_del(&dentry->d_child);
+ free(dentry->name);
+ free(dentry);
+}
+
+int dentry_delete_subtree(struct super_block *sb, struct dentry *parent)
+{
+ struct dentry *dentry, *tmp;
+
+ if (!parent)
+ return 0;
+
+ list_for_each_entry_safe(dentry, tmp, &parent->d_subdirs, d_child)
+ dentry_delete_subtree(sb, dentry);
+
+ dentry_kill(parent);
return 0;
}
+static void destroy_inode(struct inode *inode)
+{
+ if (inode->i_sb->s_op->destroy_inode)
+ inode->i_sb->s_op->destroy_inode(inode);
+ else
+ free(inode);
+}
+
static void fs_remove(struct device_d *dev)
{
struct fs_device_d *fsdev = dev_to_fs_device(dev);
+ struct super_block *sb = &fsdev->sb;
+ struct inode *inode, *tmp;
if (fsdev->dev.driver) {
dev->driver->remove(dev);
@@ -1239,15 +659,23 @@ static void fs_remove(struct device_d *dev)
free(fsdev->path);
free(fsdev->options);
- if (fsdev == fs_dev_root)
- fs_dev_root = NULL;
-
if (fsdev->cdev)
cdev_close(fsdev->cdev);
- if (fsdev->loop)
+ if (fsdev->loop && fsdev->cdev)
cdev_remove_loop(fsdev->cdev);
+ dput(sb->s_root);
+ dentry_delete_subtree(sb, sb->s_root);
+
+ list_for_each_entry_safe(inode, tmp, &sb->s_inodes, i_sb_list)
+ destroy_inode(inode);
+
+ if (fsdev->vfsmount.mountpoint)
+ fsdev->vfsmount.mountpoint->d_flags &= ~DCACHE_MOUNTED;
+
+ mntput(fsdev->vfsmount.parent);
+
free(fsdev->backingstore);
free(fsdev);
}
@@ -1322,551 +750,2319 @@ int fsdev_open_cdev(struct fs_device_d *fsdev)
return 0;
}
+static void init_super(struct super_block *sb)
+{
+ INIT_LIST_HEAD(&sb->s_inodes);
+}
+
+static int fsdev_umount(struct fs_device_d *fsdev)
+{
+ if (fsdev->vfsmount.ref)
+ return -EBUSY;
+
+ return unregister_device(&fsdev->dev);
+}
+
+/**
+ * umount_by_cdev Use a cdev struct to umount all mounted filesystems
+ * @param cdev cdev to the according device
+ * @return 0 on success or if cdev was not mounted, -errno otherwise
+ */
+int umount_by_cdev(struct cdev *cdev)
+{
+ struct fs_device_d *fs;
+ struct fs_device_d *fs_tmp;
+ int first_error = 0;
+
+ for_each_fs_device_safe(fs_tmp, fs) {
+ int ret;
+
+ if (fs->cdev == cdev) {
+ ret = fsdev_umount(fs);
+ if (ret) {
+ pr_err("Failed umounting %s, %d, continuing anyway\n",
+ fs->path, ret);
+ if (!first_error)
+ first_error = ret;
+ }
+ }
+ }
+
+ return first_error;
+}
+EXPORT_SYMBOL(umount_by_cdev);
+
+struct readdir_entry {
+ struct dirent d;
+ struct list_head list;
+};
+
+struct readdir_callback {
+ struct dir_context ctx;
+ DIR *dir;
+};
+
+static int fillonedir(struct dir_context *ctx, const char *name, int namlen,
+ loff_t offset, u64 ino, unsigned int d_type)
+{
+ struct readdir_callback *rd = container_of(ctx, struct readdir_callback, ctx);
+ struct readdir_entry *entry;
+
+ entry = xzalloc(sizeof(*entry));
+ if (!entry)
+ return -ENOMEM;
+
+ memcpy(entry->d.d_name, name, namlen);
+ list_add_tail(&entry->list, &rd->dir->entries);
+
+ return 0;
+}
+
+struct dirent *readdir(DIR *dir)
+{
+ struct readdir_entry *entry;
+
+ if (!dir)
+ return NULL;
+
+ if (list_empty(&dir->entries))
+ return NULL;
+
+ entry = list_first_entry(&dir->entries, struct readdir_entry, list);
+
+ list_del(&entry->list);
+ strcpy(dir->d.d_name, entry->d.d_name);
+ free(entry);
+
+ return &dir->d;
+}
+EXPORT_SYMBOL(readdir);
+
+static void stat_inode(struct inode *inode, struct stat *s)
+{
+ s->st_dev = 0;
+ s->st_ino = inode->i_ino;
+ s->st_mode = inode->i_mode;
+ s->st_uid = inode->i_uid;
+ s->st_gid = inode->i_gid;
+ s->st_size = inode->i_size;
+}
+
+int fstat(int fd, struct stat *s)
+{
+ FILE *f;
+ struct fs_device_d *fsdev;
+
+ if (check_fd(fd))
+ return -errno;
+
+ f = &files[fd];
+
+ fsdev = f->fsdev;
+
+ stat_inode(f->f_inode, s);
+
+ return 0;
+}
+EXPORT_SYMBOL(fstat);
+
/*
- * Mount a device to a directory.
- * We do this by registering a new device on which the filesystem
- * driver will match.
+ * cdev_get_mount_path - return the path a cdev is mounted on
+ *
+ * If a cdev is mounted return the path it's mounted on, NULL
+ * otherwise.
*/
-int mount(const char *device, const char *fsname, const char *_path,
- const char *fsoptions)
+const char *cdev_get_mount_path(struct cdev *cdev)
{
struct fs_device_d *fsdev;
+
+ for_each_fs_device(fsdev) {
+ if (fsdev->cdev && fsdev->cdev == cdev)
+ return fsdev->path;
+ }
+
+ return NULL;
+}
+
+/*
+ * cdev_mount_default - mount a cdev to the default path
+ *
+ * If a cdev is already mounted return the path it's mounted on, otherwise
+ * mount it to /mnt/<cdevname> and return the path. Returns an error pointer
+ * on failure.
+ */
+const char *cdev_mount_default(struct cdev *cdev, const char *fsoptions)
+{
+ const char *path;
+ char *newpath, *devpath;
int ret;
- char *path = normalise_path(_path);
- if (!fsoptions)
- fsoptions = "";
+ /*
+ * If this cdev is already mounted somewhere use this path
+ * instead of mounting it again to avoid corruption on the
+ * filesystem. Note this ignores eventual fsoptions though.
+ */
+ path = cdev_get_mount_path(cdev);
+ if (path)
+ return path;
- debug("mount: %s on %s type %s, options=%s\n",
- device, path, fsname, fsoptions);
+ newpath = basprintf("/mnt/%s", cdev->name);
+ make_directory(newpath);
- if (fs_dev_root) {
- struct stat s;
+ devpath = basprintf("/dev/%s", cdev->name);
- fsdev = get_fsdevice_by_path(path);
- if (fsdev != fs_dev_root) {
- printf("sorry, no nested mounts\n");
- ret = -EBUSY;
- goto err_free_path;
- }
- ret = lstat(path, &s);
- if (ret)
- goto err_free_path;
- if (!S_ISDIR(s.st_mode)) {
- ret = -ENOTDIR;
- goto err_free_path;
+ ret = mount(devpath, NULL, newpath, fsoptions);
+
+ free(devpath);
+
+ if (ret) {
+ free(newpath);
+ return ERR_PTR(ret);
+ }
+
+ return cdev_get_mount_path(cdev);
+}
+
+/*
+ * mount_all - iterate over block devices and mount all devices we are able to
+ */
+void mount_all(void)
+{
+ struct device_d *dev;
+ struct block_device *bdev;
+
+ if (!IS_ENABLED(CONFIG_BLOCK))
+ return;
+
+ for_each_device(dev)
+ device_detect(dev);
+
+ for_each_block_device(bdev) {
+ struct cdev *cdev = &bdev->cdev;
+
+ list_for_each_entry(cdev, &bdev->dev->cdevs, devices_list)
+ cdev_mount_default(cdev, NULL);
+ }
+}
+
+void fsdev_set_linux_rootarg(struct fs_device_d *fsdev, const char *str)
+{
+ fsdev->linux_rootarg = xstrdup(str);
+
+ dev_add_param_fixed(&fsdev->dev, "linux.bootargs", fsdev->linux_rootarg);
+}
+
+/**
+ * path_get_linux_rootarg() - Given a path return a suitable root= option for
+ * Linux
+ * @path: The path
+ *
+ * Return: A string containing the root= option or an ERR_PTR. the returned
+ * string must be freed by the caller.
+ */
+char *path_get_linux_rootarg(const char *path)
+{
+ struct fs_device_d *fsdev;
+ const char *str;
+
+ fsdev = get_fsdevice_by_path(path);
+ if (!fsdev)
+ return ERR_PTR(-EINVAL);
+
+ str = dev_get_param(&fsdev->dev, "linux.bootargs");
+ if (!str)
+ return ERR_PTR(-ENOSYS);
+
+ return xstrdup(str);
+}
+
+/**
+ * __is_tftp_fs() - return true when path is mounted on TFTP
+ * @path: The path
+ *
+ * Do not use directly, use is_tftp_fs instead.
+ *
+ * Return: true when @path is on TFTP, false otherwise
+ */
+bool __is_tftp_fs(const char *path)
+{
+ struct fs_device_d *fsdev;
+
+ fsdev = get_fsdevice_by_path(path);
+ if (!fsdev)
+ return false;
+
+ if (strcmp(fsdev->driver->drv.name, "tftp"))
+ return false;
+
+ return true;
+}
+
+/* inode.c */
+unsigned int get_next_ino(void)
+{
+ static unsigned int ino;
+
+ return ++ino;
+}
+
+void drop_nlink(struct inode *inode)
+{
+ WARN_ON(inode->i_nlink == 0);
+ inode->__i_nlink--;
+}
+
+void inc_nlink(struct inode *inode)
+{
+ inode->__i_nlink++;
+}
+
+static struct inode *alloc_inode(struct super_block *sb)
+{
+ static const struct inode_operations empty_iops;
+ static const struct file_operations no_open_fops;
+ struct inode *inode;
+
+ if (sb->s_op->alloc_inode)
+ inode = sb->s_op->alloc_inode(sb);
+ else
+ inode = xzalloc(sizeof(*inode));
+
+ inode->i_op = &empty_iops;
+ inode->i_fop = &no_open_fops;
+ inode->__i_nlink = 1;
+ inode->i_count = 1;
+
+ return inode;
+}
+
+struct inode *new_inode(struct super_block *sb)
+{
+ struct inode *inode;
+
+ inode = alloc_inode(sb);
+ if (!inode)
+ return NULL;
+
+ inode->i_sb = sb;
+
+ list_add(&inode->i_sb_list, &sb->s_inodes);
+
+ return inode;
+}
+
+void iput(struct inode *inode)
+{
+ if (!inode->i_count)
+ return;
+
+ inode->i_count--;
+}
+
+struct inode *iget(struct inode *inode)
+{
+ inode->i_count++;
+
+ return inode;
+}
+
+/* dcache.c */
+
+/*
+ * refcounting is implemented but right now we do not do anything with
+ * the refcounting information. Dentries are never freed unless the
+ * filesystem they are on is unmounted. In this case we do not care
+ * about the refcounts so we may free up a dentry that is actually used
+ * (file is opened). This leaves room for improvements.
+ */
+void dput(struct dentry *dentry)
+{
+ if (!dentry)
+ return;
+
+ if (!dentry->d_count)
+ return;
+
+ dentry->d_count--;
+}
+
+struct dentry *dget(struct dentry *dentry)
+{
+ if (!dentry)
+ return NULL;
+
+ dentry->d_count++;
+
+ return dentry;
+}
+
+const struct qstr slash_name = QSTR_INIT("/", 1);
+
+void d_set_d_op(struct dentry *dentry, const struct dentry_operations *op)
+{
+ dentry->d_op = op;
+}
+
+/**
+ * __d_alloc - allocate a dcache entry
+ * @sb: filesystem it will belong to
+ * @name: qstr of the name
+ *
+ * Allocates a dentry. It returns %NULL if there is insufficient memory
+ * available. On a success the dentry is returned. The name passed in is
+ * copied and the copy passed in may be reused after this call.
+ */
+struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
+{
+ struct dentry *dentry;
+
+ dentry = xzalloc(sizeof(*dentry));
+ if (!dentry)
+ return NULL;
+
+ if (!name)
+ name = &slash_name;
+
+ dentry->name = malloc(name->len + 1);
+ if (!dentry->name)
+ return NULL;
+
+ memcpy(dentry->name, name->name, name->len);
+ dentry->name[name->len] = 0;
+
+ dentry->d_name.len = name->len;
+ dentry->d_name.name = dentry->name;
+
+ dentry->d_count = 1;
+ dentry->d_parent = dentry;
+ dentry->d_sb = sb;
+ INIT_LIST_HEAD(&dentry->d_subdirs);
+ INIT_LIST_HEAD(&dentry->d_child);
+ d_set_d_op(dentry, dentry->d_sb->s_d_op);
+
+ return dentry;
+}
+
+/**
+ * d_alloc - allocate a dcache entry
+ * @parent: parent of entry to allocate
+ * @name: qstr of the name
+ *
+ * Allocates a dentry. It returns %NULL if there is insufficient memory
+ * available. On a success the dentry is returned. The name passed in is
+ * copied and the copy passed in may be reused after this call.
+ */
+struct dentry *d_alloc(struct dentry *parent, const struct qstr *name)
+{
+ struct dentry *dentry = __d_alloc(parent->d_sb, name);
+ if (!dentry)
+ return NULL;
+
+ dget(parent);
+
+ dentry->d_parent = parent;
+ list_add(&dentry->d_child, &parent->d_subdirs);
+
+ return dentry;
+}
+
+struct dentry *d_alloc_anon(struct super_block *sb)
+{
+ return __d_alloc(sb, NULL);
+}
+
+static unsigned d_flags_for_inode(struct inode *inode)
+{
+ if (!inode)
+ return DCACHE_MISS_TYPE;
+
+ if (S_ISDIR(inode->i_mode))
+ return DCACHE_DIRECTORY_TYPE;
+
+ if (inode->i_op->get_link)
+ return DCACHE_SYMLINK_TYPE;
+
+ return DCACHE_REGULAR_TYPE;
+}
+
+void d_instantiate(struct dentry *dentry, struct inode *inode)
+{
+ dentry->d_inode = inode;
+ dentry->d_flags &= ~DCACHE_ENTRY_TYPE;
+ dentry->d_flags |= d_flags_for_inode(inode);
+}
+
+struct dentry *d_make_root(struct inode *inode)
+{
+ struct dentry *res;
+
+ if (!inode)
+ return NULL;
+
+ res = d_alloc_anon(inode->i_sb);
+ if (!res)
+ return NULL;
+
+ d_instantiate(res, inode);
+
+ return res;
+}
+
+void d_add(struct dentry *dentry, struct inode *inode)
+{
+ dentry->d_inode = inode;
+ dentry->d_flags &= ~DCACHE_ENTRY_TYPE;
+ dentry->d_flags |= d_flags_for_inode(inode);
+}
+
+static bool d_same_name(const struct dentry *dentry,
+ const struct dentry *parent,
+ const struct qstr *name)
+{
+ if (dentry->d_name.len != name->len)
+ return false;
+
+ return strncmp(dentry->d_name.name, name->name, name->len) == 0;
+}
+
+struct dentry *d_lookup(const struct dentry *parent, const struct qstr *name)
+{
+ struct dentry *dentry;
+
+ list_for_each_entry(dentry, &parent->d_subdirs, d_child) {
+ if (!d_same_name(dentry, parent, name))
+ continue;
+
+ dget(dentry);
+
+ return dentry;
+ }
+
+ return NULL;
+}
+
+void d_invalidate(struct dentry *dentry)
+{
+}
+
+static inline void __d_clear_type_and_inode(struct dentry *dentry)
+{
+ dentry->d_flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU);
+
+ dentry->d_inode = NULL;
+}
+
+/*
+ * Release the dentry's inode, using the filesystem
+ * d_iput() operation if defined.
+ */
+static void dentry_unlink_inode(struct dentry * dentry)
+{
+ struct inode *inode = dentry->d_inode;
+
+ __d_clear_type_and_inode(dentry);
+ iput(inode);
+}
+
+void d_delete(struct dentry * dentry)
+{
+ dentry_unlink_inode(dentry);
+}
+
+/*
+ * These are the Linux name resolve functions from fs/namei.c
+ *
+ * The implementation is more or less directly ported from the
+ * Linux Kernel (as of Linux-4.16) minus the RCU and locking code.
+ */
+
+enum {WALK_FOLLOW = 1, WALK_MORE = 2};
+
+/*
+ * Define EMBEDDED_LEVELS to MAXSYMLINKS so we do not have to
+ * dynamically allocate a path stack.
+ */
+#define EMBEDDED_LEVELS MAXSYMLINKS
+
+struct nameidata {
+ struct path path;
+ struct qstr last;
+ struct inode *inode; /* path.dentry.d_inode */
+ unsigned int flags;
+ unsigned seq, m_seq;
+ int last_type;
+ unsigned depth;
+ int total_link_count;
+ struct saved {
+ struct path link;
+ const char *name;
+ unsigned seq;
+ } *stack, internal[EMBEDDED_LEVELS];
+ struct filename *name;
+ struct nameidata *saved;
+ struct inode *link_inode;
+ unsigned root_seq;
+ int dfd;
+};
+
+struct filename {
+ char *name;
+ int refcnt;
+};
+
+static void set_nameidata(struct nameidata *p, int dfd, struct filename *name)
+{
+ p->stack = p->internal;
+ p->dfd = dfd;
+ p->name = name;
+ p->total_link_count = 0;
+}
+
+void path_get(const struct path *path)
+{
+ mntget(path->mnt);
+ dget(path->dentry);
+}
+
+void path_put(const struct path *path)
+{
+ dput(path->dentry);
+ mntput(path->mnt);
+}
+
+static inline void get_root(struct path *root)
+{
+ root->dentry = d_root;
+ root->mnt = mnt_root;
+
+ path_get(root);
+}
+
+static inline void get_pwd(struct path *pwd)
+{
+ if (!cwd_dentry) {
+ cwd_dentry = d_root;
+ cwd_mnt = mnt_root;
+ }
+
+ pwd->dentry = cwd_dentry;
+ pwd->mnt = cwd_mnt;
+
+ path_get(pwd);
+}
+
+static inline void put_link(struct nameidata *nd)
+{
+ struct saved *last = nd->stack + --nd->depth;
+ path_put(&last->link);
+}
+
+static int automount_mount(struct dentry *dentry);
+
+static void path_put_conditional(struct path *path, struct nameidata *nd)
+{
+ dput(path->dentry);
+ if (path->mnt != nd->path.mnt)
+ mntput(path->mnt);
+}
+
+static int follow_automount(struct path *path, struct nameidata *nd,
+ bool *need_mntput)
+{
+ /* We don't want to mount if someone's just doing a stat -
+ * unless they're stat'ing a directory and appended a '/' to
+ * the name.
+ *
+ * We do, however, want to mount if someone wants to open or
+ * create a file of any type under the mountpoint, wants to
+ * traverse through the mountpoint or wants to open the
+ * mounted directory. Also, autofs may mark negative dentries
+ * as being automount points. These will need the attentions
+ * of the daemon to instantiate them before they can be used.
+ */
+ if (!(nd->flags & (LOOKUP_PARENT | LOOKUP_DIRECTORY |
+ LOOKUP_OPEN | LOOKUP_CREATE | LOOKUP_AUTOMOUNT)) &&
+ path->dentry->d_inode)
+ return -EISDIR;
+
+ return automount_mount(path->dentry);
+}
+
+/*
+ * Handle a dentry that is managed in some way.
+ * - Flagged for transit management (autofs)
+ * - Flagged as mountpoint
+ * - Flagged as automount point
+ *
+ * This may only be called in refwalk mode.
+ *
+ * Serialization is taken care of in namespace.c
+ */
+static int follow_managed(struct path *path, struct nameidata *nd)
+{
+ struct vfsmount *mnt = path->mnt;
+ unsigned managed = path->dentry->d_flags;
+ bool need_mntput = false;
+ int ret = 0;
+
+ while (managed = path->dentry->d_flags,
+ managed &= DCACHE_MANAGED_DENTRY,
+ managed != 0) {
+
+ if (managed & DCACHE_MOUNTED) {
+ struct vfsmount *mounted = lookup_mnt(path);
+
+ if (mounted) {
+ dput(path->dentry);
+ if (need_mntput)
+ mntput(path->mnt);
+ path->mnt = mounted;
+ path->dentry = dget(mounted->mnt_root);
+ need_mntput = true;
+ continue;
+ }
}
- } else {
- /* no mtab, so we only allow to mount on '/' */
- if (*path != '/' || *(path + 1)) {
- ret = -ENOTDIR;
- goto err_free_path;
+
+ /* Handle an automount point */
+ if (managed & DCACHE_NEED_AUTOMOUNT) {
+ ret = follow_automount(path, nd, &need_mntput);
+ if (ret < 0)
+ break;
+ continue;
}
+
+ /* We didn't change the current path point */
+ break;
}
- if (!fsname)
- fsname = detect_fs(device, fsoptions);
+ if (need_mntput && path->mnt == mnt)
+ mntput(path->mnt);
+ if (ret == -EISDIR || !ret)
+ ret = 1;
+ if (need_mntput)
+ nd->flags |= LOOKUP_JUMPED;
+ if (ret < 0)
+ path_put_conditional(path, nd);
+ return ret;
+}
- if (!fsname)
+static struct dentry *__lookup_hash(const struct qstr *name,
+ struct dentry *base, unsigned int flags)
+{
+ struct dentry *dentry;
+ struct dentry *old;
+ struct inode *dir = base->d_inode;
+
+ if (!base)
+ return ERR_PTR(-ENOENT);
+
+ dentry = d_lookup(base, name);
+ if (dentry)
+ return dentry;
+
+ dentry = d_alloc(base, name);
+ if (unlikely(!dentry))
+ return ERR_PTR(-ENOMEM);
+
+ old = dir->i_op->lookup(dir, dentry, flags);
+ if (IS_ERR(old)) {
+ dput(dentry);
+ return old;
+ }
+
+ if (unlikely(old)) {
+ dput(dentry);
+ dentry = old;
+ }
+
+ return dentry;
+}
+
+static int lookup_fast(struct nameidata *nd, struct path *path)
+{
+ struct dentry *dentry, *parent = nd->path.dentry;
+
+ dentry = __lookup_hash(&nd->last, parent, 0);
+ if (IS_ERR(dentry))
+ return PTR_ERR(dentry);
+
+ if (d_is_negative(dentry)) {
+ dput(dentry);
return -ENOENT;
+ }
- fsdev = xzalloc(sizeof(struct fs_device_d));
- fsdev->backingstore = xstrdup(device);
- safe_strncpy(fsdev->dev.name, fsname, MAX_DRIVER_NAME);
- fsdev->dev.id = get_free_deviceid(fsdev->dev.name);
- fsdev->path = xstrdup(path);
- fsdev->dev.bus = &fs_bus;
- fsdev->options = xstrdup(fsoptions);
+ path->dentry = dentry;
+ path->mnt = nd->path.mnt;
- ret = register_device(&fsdev->dev);
- if (ret)
- goto err_register;
+ return follow_managed(path, nd);
+}
- if (!fsdev->dev.driver) {
- /*
- * Driver didn't accept the device or no driver for this
- * device. Bail out
- */
- ret = -EINVAL;
- goto err_no_driver;
+/*
+ * follow_up - Find the mountpoint of path's vfsmount
+ *
+ * Given a path, find the mountpoint of its source file system.
+ * Replace @path with the path of the mountpoint in the parent mount.
+ * Up is towards /.
+ *
+ * Return 1 if we went up a level and 0 if we were already at the
+ * root.
+ */
+int follow_up(struct path *path)
+{
+ struct vfsmount *parent, *mnt = path->mnt;
+ struct dentry *mountpoint;
+
+ parent = mnt->parent;
+ if (parent == mnt)
+ return 0;
+
+ mntget(parent);
+ mountpoint = dget(mnt->mountpoint);
+ dput(path->dentry);
+ path->dentry = mountpoint;
+ mntput(path->mnt);
+ path->mnt = mnt->parent;
+
+ return 1;
+}
+
+static void follow_mount(struct path *path)
+{
+ while (d_mountpoint(path->dentry)) {
+ struct vfsmount *mounted = lookup_mnt(path);
+ if (!mounted)
+ break;
+ dput(path->dentry);
+ path->mnt = mounted;
+ path->dentry = dget(mounted->mnt_root);
}
+}
- if (!fsdev->linux_rootarg && fsdev->cdev && fsdev->cdev->partuuid[0] != 0) {
- char *str = basprintf("root=PARTUUID=%s",
- fsdev->cdev->partuuid);
+static int path_parent_directory(struct path *path)
+{
+ struct dentry *old = path->dentry;
- fsdev_set_linux_rootarg(fsdev, str);
+ path->dentry = dget(path->dentry->d_parent);
+ dput(old);
+
+ return 0;
+}
+
+static int follow_dotdot(struct nameidata *nd)
+{
+ while (1) {
+ if (nd->path.dentry != nd->path.mnt->mnt_root) {
+ int ret = path_parent_directory(&nd->path);
+ if (ret)
+ return ret;
+ break;
+ }
+
+ if (!follow_up(&nd->path))
+ break;
}
- free(path);
+ follow_mount(&nd->path);
+
+ nd->inode = nd->path.dentry->d_inode;
return 0;
+}
-err_no_driver:
- unregister_device(&fsdev->dev);
-err_register:
- fs_remove(&fsdev->dev);
-err_free_path:
- free(path);
+static inline int handle_dots(struct nameidata *nd, int type)
+{
+ if (type == LAST_DOTDOT) {
+ return follow_dotdot(nd);
+ }
+ return 0;
+}
- errno = -ret;
+static inline void path_to_nameidata(const struct path *path,
+ struct nameidata *nd)
+{
+ dput(nd->path.dentry);
+ if (nd->path.mnt != path->mnt)
+ mntput(nd->path.mnt);
+ nd->path.mnt = path->mnt;
+ nd->path.dentry = path->dentry;
+}
- return ret;
+static const char *get_link(struct nameidata *nd)
+{
+ struct saved *last = nd->stack + nd->depth - 1;
+ struct dentry *dentry = last->link.dentry;
+ struct inode *inode = nd->link_inode;
+ const char *res;
+
+ nd->last_type = LAST_BIND;
+ res = inode->i_link;
+ if (!res) {
+ res = inode->i_op->get_link(dentry, inode);
+ if (IS_ERR_OR_NULL(res))
+ return res;
+ }
+ if (*res == '/') {
+ while (unlikely(*++res == '/'))
+ ;
+ }
+ if (!*res)
+ res = NULL;
+ return res;
}
-EXPORT_SYMBOL(mount);
-static int fsdev_umount(struct fs_device_d *fsdev)
+static int pick_link(struct nameidata *nd, struct path *link,
+ struct inode *inode)
{
- return unregister_device(&fsdev->dev);
+ struct saved *last;
+
+ if (unlikely(nd->total_link_count++ >= MAXSYMLINKS)) {
+ path_to_nameidata(link, nd);
+ return -ELOOP;
+ }
+
+ if (link->mnt == nd->path.mnt)
+ mntget(link->mnt);
+
+ last = nd->stack + nd->depth++;
+ last->link = *link;
+ nd->link_inode = inode;
+
+ return 1;
}
-/**
- * umount_by_cdev Use a cdev struct to umount all mounted filesystems
- * @param cdev cdev to the according device
- * @return 0 on success or if cdev was not mounted, -errno otherwise
+/*
+ * Do we need to follow links? We _really_ want to be able
+ * to do this check without having to look at inode->i_op,
+ * so we keep a cache of "no, this doesn't need follow_link"
+ * for the common case.
*/
-int umount_by_cdev(struct cdev *cdev)
+static inline int step_into(struct nameidata *nd, struct path *path,
+ int flags, struct inode *inode)
{
- struct fs_device_d *fs;
- struct fs_device_d *fs_tmp;
- int first_error = 0;
+ if (!(flags & WALK_MORE) && nd->depth)
+ put_link(nd);
- for_each_fs_device_safe(fs_tmp, fs) {
- int ret;
+ if (likely(!d_is_symlink(path->dentry)) ||
+ !(flags & WALK_FOLLOW || nd->flags & LOOKUP_FOLLOW)) {
+ /* not a symlink or should not follow */
+ path_to_nameidata(path, nd);
+ nd->inode = inode;
+ return 0;
+ }
- if (fs->cdev == cdev) {
- ret = fsdev_umount(fs);
- if (ret) {
- pr_err("Failed umounting %s, %d, continuing anyway\n",
- fs->path, ret);
- if (!first_error)
- first_error = ret;
- }
+ return pick_link(nd, path, inode);
+}
+
+static int walk_component(struct nameidata *nd, int flags)
+{
+ struct path path;
+ int err;
+
+ /*
+ * "." and ".." are special - ".." especially so because it has
+ * to be able to know about the current root directory and
+ * parent relationships.
+ */
+ if (nd->last_type != LAST_NORM) {
+ err = handle_dots(nd, nd->last_type);
+ if (!(flags & WALK_MORE) && nd->depth)
+ put_link(nd);
+ return err;
+ }
+
+ err = lookup_fast(nd, &path);
+ if (err < 0)
+ return err;
+
+ if (err == 0) {
+ path.mnt = nd->path.mnt;
+ err = follow_managed(&path, nd);
+ if (err < 0)
+ return err;
+
+ if (d_is_negative(path.dentry)) {
+ path_to_nameidata(&path, nd);
+ return -ENOENT;
}
}
- return first_error;
+ return step_into(nd, &path, flags, d_inode(path.dentry));
}
-EXPORT_SYMBOL(umount_by_cdev);
-int umount(const char *pathname)
+static int component_len(const char *name, char separator)
{
- struct fs_device_d *fsdev = NULL, *f;
- char *p = normalise_path(pathname);
+ int len = 0;
- for_each_fs_device(f) {
- if (!strcmp(p, f->path)) {
- fsdev = f;
- break;
- }
+ while (name[len] && name[len] != separator)
+ len++;
+
+ return len;
+}
+
+struct filename *getname(const char *filename)
+{
+ struct filename *result;
+
+ result = malloc(sizeof(*result));
+ if (!result)
+ return NULL;
+
+ result->name = strdup(filename);
+ if (!result->name) {
+ free(result);
+ return NULL;
}
- if (!fsdev) {
- struct cdev *cdev = cdev_open(p, O_RDWR);
+ result->refcnt = 1;
- if (cdev) {
- free(p);
- cdev_close(cdev);
- return umount_by_cdev(cdev);
+ return result;
+}
+
+void putname(struct filename *name)
+{
+ BUG_ON(name->refcnt <= 0);
+
+ if (--name->refcnt > 0)
+ return;
+
+ free(name->name);
+ free(name);
+}
+
+static struct fs_device_d *get_fsdevice_by_dentry(struct dentry *dentry)
+{
+ struct super_block *sb;
+
+ sb = dentry->d_sb;
+
+ return container_of(sb, struct fs_device_d, sb);
+}
+
+static bool dentry_is_tftp(struct dentry *dentry)
+{
+ struct fs_device_d *fsdev;
+
+ fsdev = get_fsdevice_by_dentry(dentry);
+ if (!fsdev)
+ return false;
+
+ if (strcmp(fsdev->driver->drv.name, "tftp"))
+ return false;
+
+ return true;
+}
+
+/*
+ * Name resolution.
+ * This is the basic name resolution function, turning a pathname into
+ * the final dentry. We expect 'base' to be positive and a directory.
+ *
+ * Returns 0 and nd will have valid dentry and mnt on success.
+ * Returns error and drops reference to input namei data on failure.
+ */
+static int link_path_walk(const char *name, struct nameidata *nd)
+{
+ int err;
+ char separator = '/';
+
+ while (*name=='/')
+ name++;
+ if (!*name)
+ return 0;
+
+ /* At this point we know we have a real path component. */
+ for(;;) {
+ int len;
+ int type;
+
+ len = component_len(name, separator);
+
+ type = LAST_NORM;
+ if (name[0] == '.') switch (len) {
+ case 2:
+ if (name[1] == '.') {
+ type = LAST_DOTDOT;
+ nd->flags |= LOOKUP_JUMPED;
+ }
+ break;
+ case 1:
+ type = LAST_DOT;
+ }
+ if (likely(type == LAST_NORM))
+ nd->flags &= ~LOOKUP_JUMPED;
+
+ nd->last.len = len;
+ nd->last.name = name;
+ nd->last_type = type;
+
+ name += len;
+ if (!*name)
+ goto OK;
+
+ /*
+ * If it wasn't NUL, we know it was '/'. Skip that
+ * slash, and continue until no more slashes.
+ */
+ do {
+ name++;
+ } while (unlikely(*name == separator));
+
+ if (unlikely(!*name)) {
+OK:
+ /* pathname body, done */
+ if (!nd->depth)
+ return 0;
+ name = nd->stack[nd->depth - 1].name;
+ /* trailing symlink, done */
+ if (!name)
+ return 0;
+ /* last component of nested symlink */
+ err = walk_component(nd, WALK_FOLLOW);
+ } else {
+ /* not the last component */
+ err = walk_component(nd, WALK_FOLLOW | WALK_MORE);
+ }
+
+ if (err < 0)
+ return err;
+
+ /*
+ * barebox specific hack for TFTP. TFTP does not support
+ * looking up directories, only the files in directories.
+ * Since the filename is not known at this point we replace
+ * the path separator with an invalid char so that TFTP will
+ * get the full remaining path including slashes.
+ */
+ if (dentry_is_tftp(nd->path.dentry))
+ separator = 0x1;
+
+ if (err) {
+ const char *s = get_link(nd);
+
+ if (IS_ERR(s))
+ return PTR_ERR(s);
+ err = 0;
+ if (unlikely(!s)) {
+ /* jumped */
+ put_link(nd);
+ } else {
+ nd->stack[nd->depth - 1].name = name;
+ name = s;
+ continue;
+ }
}
+ if (unlikely(!d_can_lookup(nd->path.dentry)))
+ return -ENOTDIR;
}
+}
- free(p);
+static const char *path_init(struct nameidata *nd, unsigned flags)
+{
+ const char *s = nd->name->name;
- if (f == fs_dev_root && !list_is_singular(&fs_device_list)) {
- errno = EBUSY;
- return -EBUSY;
+ nd->last_type = LAST_ROOT; /* if there are only slashes... */
+ nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
+ nd->depth = 0;
+
+ nd->path.mnt = NULL;
+ nd->path.dentry = NULL;
+
+ if (*s == '/') {
+ get_root(&nd->path);
+ return s;
+ } else if (nd->dfd == AT_FDCWD) {
+ get_pwd(&nd->path);
+ nd->inode = nd->path.dentry->d_inode;
+ return s;
}
- if (!fsdev) {
- errno = EFAULT;
- return -EFAULT;
+ return s;
+}
+
+static const char *trailing_symlink(struct nameidata *nd)
+{
+ const char *s;
+
+ nd->flags |= LOOKUP_PARENT;
+ nd->stack[0].name = NULL;
+ s = get_link(nd);
+
+ return s ? s : "";
+}
+
+static inline int lookup_last(struct nameidata *nd)
+{
+ if (nd->last_type == LAST_NORM && nd->last.name[nd->last.len])
+ nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
+
+ nd->flags &= ~LOOKUP_PARENT;
+ return walk_component(nd, 0);
+}
+
+static void terminate_walk(struct nameidata *nd)
+{
+ int i;
+
+ path_put(&nd->path);
+ for (i = 0; i < nd->depth; i++)
+ path_put(&nd->stack[i].link);
+
+ nd->depth = 0;
+}
+
+/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
+static int path_parentat(struct nameidata *nd, unsigned flags,
+ struct path *parent)
+{
+ const char *s = path_init(nd, flags);
+ int err;
+
+ if (IS_ERR(s))
+ return PTR_ERR(s);
+
+ err = link_path_walk(s, nd);
+ if (!err) {
+ *parent = nd->path;
+ nd->path.mnt = NULL;
+ nd->path.dentry = NULL;
}
+ terminate_walk(nd);
+ return err;
+}
- return fsdev_umount(fsdev);
+static struct filename *filename_parentat(int dfd, struct filename *name,
+ unsigned int flags, struct path *parent,
+ struct qstr *last, int *type)
+{
+ int retval;
+ struct nameidata nd;
+
+ if (IS_ERR(name))
+ return name;
+
+ set_nameidata(&nd, dfd, name);
+
+ retval = path_parentat(&nd, flags, parent);
+ if (likely(!retval)) {
+ *last = nd.last;
+ *type = nd.last_type;
+ } else {
+ putname(name);
+ name = ERR_PTR(retval);
+ }
+
+ return name;
}
-EXPORT_SYMBOL(umount);
-DIR *opendir(const char *pathname)
+static struct dentry *filename_create(int dfd, struct filename *name,
+ struct path *path, unsigned int lookup_flags)
+{
+ struct dentry *dentry = ERR_PTR(-EEXIST);
+ struct qstr last;
+ int type;
+ int error;
+ bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
+
+ /*
+ * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
+ * other flags passed in are ignored!
+ */
+ lookup_flags &= LOOKUP_REVAL;
+
+ name = filename_parentat(dfd, name, 0, path, &last, &type);
+ if (IS_ERR(name))
+ return ERR_CAST(name);
+
+ /*
+ * Yucky last component or no last component at all?
+ * (foo/., foo/.., /////)
+ */
+ if (unlikely(type != LAST_NORM))
+ goto out;
+
+ /*
+ * Do the final lookup.
+ */
+ lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
+ dentry = __lookup_hash(&last, path->dentry, lookup_flags);
+ if (IS_ERR(dentry))
+ goto unlock;
+
+ error = -EEXIST;
+ if (d_is_positive(dentry))
+ goto fail;
+
+ /*
+ * Special case - lookup gave negative, but... we had foo/bar/
+ * From the vfs_mknod() POV we just have a negative dentry -
+ * all is fine. Let's be bastards - you had / on the end, you've
+ * been asking for (non-existent) directory. -ENOENT for you.
+ */
+ if (unlikely(!is_dir && last.name[last.len])) {
+ error = -ENOENT;
+ goto fail;
+ }
+ putname(name);
+ return dentry;
+fail:
+ dput(dentry);
+ dentry = ERR_PTR(error);
+unlock:
+out:
+ path_put(path);
+ putname(name);
+ return dentry;
+}
+
+static int filename_lookup(int dfd, struct filename *name, unsigned flags,
+ struct path *path)
+{
+ int err;
+ struct nameidata nd;
+ const char *s;
+
+ set_nameidata(&nd, dfd, name);
+
+ s = path_init(&nd, flags);
+
+ while (!(err = link_path_walk(s, &nd)) && ((err = lookup_last(&nd)) > 0)) {
+ s = trailing_symlink(&nd);
+ if (IS_ERR(s)) {
+ err = PTR_ERR(s);
+ break;
+ }
+ }
+
+ if (!err && nd.flags & LOOKUP_DIRECTORY)
+ if (!d_can_lookup(nd.path.dentry))
+ err = -ENOTDIR;
+ if (!err) {
+ *path = nd.path;
+ nd.path.mnt = NULL;
+ nd.path.dentry = NULL;
+ }
+
+ terminate_walk(&nd);
+ putname(name);
+
+ return err;
+}
+
+static struct fs_device_d *get_fsdevice_by_path(const char *pathname)
{
- DIR *dir = NULL;
struct fs_device_d *fsdev;
- struct fs_driver_d *fsdrv;
- char *p = canonicalize_path(pathname);
- char *freep = p;
+ struct path path;
int ret;
- struct stat s;
- ret = stat(pathname, &s);
+ ret = filename_lookup(AT_FDCWD, getname(pathname), 0, &path);
if (ret)
+ return NULL;
+
+ fsdev = get_fsdevice_by_dentry(path.dentry);
+
+ path_put(&path);
+
+ return fsdev;
+}
+
+int vfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ int error;
+
+ if (!dir->i_op->rmdir)
+ return -EPERM;
+
+ dget(dentry);
+
+ error = dir->i_op->rmdir(dir, dentry);
+ if (error)
goto out;
- if (!S_ISDIR(s.st_mode)) {
- ret = -ENOTDIR;
+ dentry->d_inode->i_flags |= S_DEAD;
+
+out:
+ dput(dentry);
+
+ if (!error)
+ d_delete(dentry);
+
+ return error;
+}
+
+int vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
+{
+ int error;
+
+ if (!dir->i_op->mkdir)
+ return -EPERM;
+
+ mode &= (S_IRWXUGO|S_ISVTX);
+
+ error = dir->i_op->mkdir(dir, dentry, mode);
+
+ return error;
+}
+
+/* libfs.c */
+
+/* ---------------------------------------------------------------- */
+int mkdir (const char *pathname, mode_t mode)
+{
+ struct dentry *dentry;
+ struct path path;
+ int error;
+ unsigned int lookup_flags = LOOKUP_DIRECTORY;
+
+ dentry = filename_create(AT_FDCWD, getname(pathname), &path, lookup_flags);
+ if (IS_ERR(dentry)) {
+ error = PTR_ERR(dentry);
goto out;
}
- fsdev = get_fs_device_and_root_path(&p);
- if (!fsdev) {
- ret = -ENOENT;
+ error = vfs_mkdir(path.dentry->d_inode, dentry, mode);
+
+ dput(dentry);
+ path_put(&path);
+out:
+ if (error)
+ errno = -error;
+
+ return error;
+}
+EXPORT_SYMBOL(mkdir);
+
+int rmdir (const char *pathname)
+{
+ int error = 0;
+ struct filename *name;
+ struct dentry *dentry;
+ struct path path;
+ struct qstr last;
+ int type;
+
+ name = filename_parentat(AT_FDCWD, getname(pathname), 0,
+ &path, &last, &type);
+ if (IS_ERR(name))
+ return PTR_ERR(name);
+
+ switch (type) {
+ case LAST_DOTDOT:
+ error = -ENOTEMPTY;
+ goto out;
+ case LAST_DOT:
+ error = -EINVAL;
+ goto out;
+ case LAST_ROOT:
+ error = -EBUSY;
goto out;
}
- fsdrv = fsdev->driver;
- debug("opendir: fsdrv: %p\n",fsdrv);
+ dentry = __lookup_hash(&last, path.dentry, 0);
+ if (d_is_negative(dentry)) {
+ error = -ENOENT;
+ goto out;
+ }
+ if (d_mountpoint(dentry)) {
+ error = -EBUSY;
+ goto out;
+ }
+
+ if (!d_is_dir(dentry)) {
+ error = -ENOTDIR;
+ goto out;
+ }
- dir = fsdrv->opendir(&fsdev->dev, p);
- if (dir) {
- dir->dev = &fsdev->dev;
- dir->fsdrv = fsdrv;
+ error = vfs_rmdir(path.dentry->d_inode, dentry);
+
+ dput(dentry);
+out:
+ path_put(&path);
+ putname(name);
+
+ if (error)
+ errno = -error;
+
+ return error;
+}
+EXPORT_SYMBOL(rmdir);
+
+int open(const char *pathname, int flags, ...)
+{
+ struct fs_device_d *fsdev;
+ struct fs_driver_d *fsdrv;
+ struct super_block *sb;
+ FILE *f;
+ int error = 0;
+ struct inode *inode = NULL;
+ struct dentry *dentry = NULL;
+ struct nameidata nd;
+ const char *s;
+
+ set_nameidata(&nd, AT_FDCWD, getname(pathname));
+ s = path_init(&nd, LOOKUP_FOLLOW);
+
+ while (1) {
+ error = link_path_walk(s, &nd);
+ if (error)
+ break;
+
+ if (!d_is_dir(nd.path.dentry)) {
+ error = -ENOTDIR;
+ break;
+ }
+
+ dentry = __lookup_hash(&nd.last, nd.path.dentry, 0);
+ if (IS_ERR(dentry)) {
+ error = PTR_ERR(dentry);
+ break;
+ }
+
+ if (!d_is_symlink(dentry))
+ break;
+
+ dput(dentry);
+
+ error = lookup_last(&nd);
+ if (error <= 0)
+ break;
+
+ s = trailing_symlink(&nd);
+ if (IS_ERR(s)) {
+ error = PTR_ERR(s);
+ break;
+ }
+ }
+
+ terminate_walk(&nd);
+ putname(nd.name);
+
+ if (error)
+ return error;
+
+ if (d_is_negative(dentry)) {
+ if (flags & O_CREAT) {
+ error = create(nd.path.dentry, dentry);
+ if (error)
+ goto out1;
+ } else {
+ dput(dentry);
+ error = -ENOENT;
+ goto out1;
+ }
} else {
- /*
- * FIXME: The fs drivers should return ERR_PTR here so that
- * we are able to forward the error
- */
- ret = -EINVAL;
+ if (d_is_dir(dentry) && !dentry_is_tftp(dentry)) {
+ error = -EISDIR;
+ goto out1;
+ }
+ }
+
+ inode = d_inode(dentry);
+
+ f = get_file();
+ if (!f) {
+ error = -EMFILE;
+ goto out1;
}
+ f->path = xstrdup(pathname);
+ f->dentry = dentry;
+ f->f_inode = iget(inode);
+ f->flags = flags;
+ f->size = inode->i_size;
+
+ sb = inode->i_sb;
+ fsdev = container_of(sb, struct fs_device_d, sb);
+ fsdrv = fsdev->driver;
+
+ f->fsdev = fsdev;
+
+ if (fsdrv->open) {
+ char *pathname = dpath(dentry, fsdev->vfsmount.mnt_root);
+
+ error = fsdrv->open(&fsdev->dev, f, pathname);
+ free(pathname);
+ if (error)
+ goto out;
+ }
+
+ if (flags & O_TRUNC) {
+ error = fsdrv->truncate(&fsdev->dev, f, 0);
+ f->size = 0;
+ inode->i_size = 0;
+ if (error)
+ goto out;
+ }
+
+ if (flags & O_APPEND)
+ f->pos = f->size;
+
+ return f->no;
+
out:
- free(freep);
+ put_file(f);
+out1:
+
+ if (error)
+ errno = -error;
+ return error;
+}
+EXPORT_SYMBOL(open);
+int unlink(const char *pathname)
+{
+ int ret;
+ struct dentry *dentry;
+ struct inode *inode;
+ struct path path;
+
+ ret = filename_lookup(AT_FDCWD, getname(pathname), 0, &path);
+ if (ret)
+ goto out;
+
+ dentry = path.dentry;
+
+ if (d_is_dir(dentry)) {
+ ret = -EISDIR;
+ goto out_put;
+ }
+
+ inode = d_inode(dentry->d_parent);
+
+ if (!inode->i_op->unlink) {
+ ret = -EPERM;
+ goto out_put;
+ }
+
+ ret = inode->i_op->unlink(inode, dentry);
+ if (ret)
+ goto out_put;
+
+ d_delete(dentry);
+
+out_put:
+ path_put(&path);
+out:
if (ret)
errno = -ret;
+ return ret;
+}
+EXPORT_SYMBOL(unlink);
- return dir;
+int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
+{
+ if (!dir->i_op->symlink)
+ return -EPERM;
+
+ return dir->i_op->symlink(dir, dentry, oldname);
}
-EXPORT_SYMBOL(opendir);
-struct dirent *readdir(DIR *dir)
+int symlink(const char *pathname, const char *newpath)
{
- struct dirent *ent;
+ struct dentry *dentry;
+ struct path path;
+ int error;
+ unsigned int lookup_flags = LOOKUP_DIRECTORY;
- if (!dir)
- return NULL;
+ dentry = filename_create(AT_FDCWD, getname(newpath), &path, lookup_flags);
+ if (IS_ERR(dentry)) {
+ error = PTR_ERR(dentry);
+ goto out;
+ }
- ent = dir->fsdrv->readdir(dir->dev, dir);
+ error = vfs_symlink(path.dentry->d_inode, dentry, pathname);
+out:
+ if (error)
+ errno = -error;
- if (!ent)
- errno = EBADF;
+ return error;
+}
+EXPORT_SYMBOL(symlink);
+
+static void release_dir(DIR *d)
+{
+ struct readdir_entry *entry, *tmp;
+
+ list_for_each_entry_safe(entry, tmp, &d->entries, list) {
+ free(entry);
+ }
- return ent;
+ free(d);
}
-EXPORT_SYMBOL(readdir);
-int closedir(DIR *dir)
+DIR *opendir(const char *pathname)
{
int ret;
+ struct dentry *dir;
+ struct inode *inode;
+ struct file file = {};
+ DIR *d;
+ struct path path = {};
+ struct readdir_callback rd = {
+ .ctx = {
+ .actor = fillonedir,
+ },
+ };
+
+ ret = filename_lookup(AT_FDCWD, getname(pathname),
+ LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path);
+ if (ret)
+ goto out;
- if (!dir) {
- errno = EBADF;
- return -EBADF;
+ dir = path.dentry;
+
+ if (d_is_negative(dir)) {
+ ret = -ENOENT;
+ goto out_put;
}
- ret = dir->fsdrv->closedir(dir->dev, dir);
+ inode = d_inode(dir);
+
+ if (!S_ISDIR(inode->i_mode)) {
+ ret = -ENOTDIR;
+ goto out_put;
+ }
+
+ file.f_path.dentry = dir;
+ file.f_op = dir->d_inode->i_fop;
+
+ d = xzalloc(sizeof(*d));
+
+ INIT_LIST_HEAD(&d->entries);
+ rd.dir = d;
+
+ ret = file.f_op->iterate(&file, &rd.ctx);
if (ret)
- errno = -ret;
+ goto out_release;
- return ret;
+ path_put(&path);
+
+ return d;
+
+out_release:
+ release_dir(d);
+out_put:
+ path_put(&path);
+out:
+ errno = -ret;
+
+ return NULL;
}
-EXPORT_SYMBOL(closedir);
+EXPORT_SYMBOL(opendir);
-int stat(const char *filename, struct stat *s)
+int closedir(DIR *dir)
{
- char *path = canonicalize_path(filename);
int ret;
- if (IS_ERR(path))
- return PTR_ERR(path);
-
- ret = lstat(path, s);
+ if (!dir) {
+ errno = EBADF;
+ return -EBADF;
+ }
- free(path);
+ release_dir(dir);
return ret;
}
-EXPORT_SYMBOL(stat);
+EXPORT_SYMBOL(closedir);
-static int __lstat(const char *filename, struct stat *s)
+int readlink(const char *pathname, char *buf, size_t bufsiz)
{
- struct fs_driver_d *fsdrv;
- struct fs_device_d *fsdev;
- char *f = normalise_path(filename);
- char *freep = f;
int ret;
+ struct dentry *dentry;
+ struct inode *inode;
+ const char *link;
+ struct path path = {};
- automount_mount(f, 1);
+ ret = filename_lookup(AT_FDCWD, getname(pathname), 0, &path);
+ if (ret)
+ goto out;
- memset(s, 0, sizeof(struct stat));
+ dentry = path.dentry;
- fsdev = get_fsdevice_by_path(f);
- if (!fsdev) {
- ret = -ENOENT;
- goto out;
+ if (!d_is_symlink(dentry)) {
+ ret = -EINVAL;
+ goto out_put;
}
- if (fsdev != fs_dev_root && strcmp(f, fsdev->path))
- f += strlen(fsdev->path);
- else
- fsdev = fs_dev_root;
+ inode = d_inode(dentry);
- fsdrv = fsdev->driver;
+ if (!inode->i_op->get_link) {
+ ret = -EPERM;
+ goto out_put;
+ }
- if (*f == 0)
- f = "/";
+ link = inode->i_op->get_link(dentry, inode);
+ if (IS_ERR(link)) {
+ ret = PTR_ERR(link);
+ goto out_put;
+ }
- ret = fsdrv->stat(&fsdev->dev, f, s);
-out:
- free(freep);
+ strncpy(buf, link, bufsiz);
+ ret = 0;
+out_put:
+ path_put(&path);
+out:
if (ret)
errno = -ret;
return ret;
}
+EXPORT_SYMBOL(readlink);
-int lstat(const char *filename, struct stat *s)
+static int stat_filename(const char *filename, struct stat *s, unsigned int flags)
{
- char *f = canonicalize_dir(filename);
int ret;
+ struct dentry *dentry;
+ struct inode *inode;
+ struct path path = {};
+
+ ret = filename_lookup(AT_FDCWD, getname(filename), flags, &path);
+ if (ret)
+ goto out;
+
+ dentry = path.dentry;
- if (IS_ERR(f))
- return PTR_ERR(f);
+ if (d_is_negative(dentry)) {
+ ret = -ENOENT;
+ goto out_put;
+ }
- ret = __lstat(f, s);
+ inode = d_inode(dentry);
- free(f);
+ stat_inode(inode, s);
+ ret = 0;
+out_put:
+ path_put(&path);
+out:
return ret;
}
+
+int stat(const char *filename, struct stat *s)
+{
+ return stat_filename(filename, s, LOOKUP_FOLLOW);
+}
+EXPORT_SYMBOL(stat);
+
+int lstat(const char *filename, struct stat *s)
+{
+ return stat_filename(filename, s, 0);
+}
EXPORT_SYMBOL(lstat);
-int fstat(int fd, struct stat *s)
+static char *__dpath(struct dentry *dentry, struct dentry *root)
{
- FILE *f;
- struct fs_device_d *fsdev;
+ char *res, *ppath;
- if (check_fd(fd))
- return -errno;
+ if (dentry == root)
+ return NULL;
+ if (dentry == d_root)
+ return NULL;
- f = &files[fd];
+ while (IS_ROOT(dentry)) {
+ struct fs_device_d *fsdev;
- fsdev = f->fsdev;
+ for_each_fs_device(fsdev) {
+ if (dentry == fsdev->vfsmount.mnt_root) {
+ dentry = fsdev->vfsmount.mountpoint;
+ break;
+ }
+ }
+ }
+
+ ppath = __dpath(dentry->d_parent, root);
+ if (ppath)
+ res = basprintf("%s/%s", ppath, dentry->name);
+ else
+ res = basprintf("/%s", dentry->name);
+ free(ppath);
- return fsdev->driver->stat(&fsdev->dev, f->path, s);
+ return res;
}
-EXPORT_SYMBOL(fstat);
-int mkdir (const char *pathname, mode_t mode)
+/**
+ * dpath - return path of a dentry
+ * @dentry: The dentry to return the path from
+ * @root: The dentry up to which the path is followed
+ *
+ * Get the path of a dentry. The path is followed up to
+ * @root or the root ("/") dentry, whatever is found first.
+ *
+ * Return: Dynamically allocated string containing the path
+ */
+char *dpath(struct dentry *dentry, struct dentry *root)
{
- struct fs_driver_d *fsdrv;
- struct fs_device_d *fsdev;
- char *p = canonicalize_path(pathname);
- char *freep = p;
- int ret;
- struct stat s;
+ char *res;
- ret = parent_check_directory(p);
- if (ret)
- goto out;
+ if (dentry == root)
+ return strdup("/");
- ret = stat(pathname, &s);
- if (!ret) {
- ret = -EEXIST;
- goto out;
- }
+ res = __dpath(dentry, root);
- fsdev = get_fs_device_and_root_path(&p);
- if (!fsdev) {
- ret = -ENOENT;
+ return res;
+}
+
+/**
+ * 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 *res = NULL;
+ struct path path;
+ int ret;
+
+ ret = filename_lookup(AT_FDCWD, getname(pathname), LOOKUP_FOLLOW, &path);
+ if (ret)
goto out;
- }
- fsdrv = fsdev->driver;
- if (fsdrv->mkdir)
- ret = fsdrv->mkdir(&fsdev->dev, p);
- else
- ret = -EROFS;
+ res = dpath(path.dentry, d_root);
out:
- free(freep);
-
if (ret)
errno = -ret;
- return ret;
+ return res;
}
-EXPORT_SYMBOL(mkdir);
-int rmdir (const char *pathname)
+const char *getcwd(void)
{
- struct fs_driver_d *fsdrv;
- struct fs_device_d *fsdev;
- char *p = canonicalize_path(pathname);
- char *freep = p;
+ return cwd;
+}
+EXPORT_SYMBOL(getcwd);
+
+int chdir(const char *pathname)
+{
+ char *realpath;
+ struct path path;
int ret;
- struct stat s;
- ret = lstat(pathname, &s);
+ ret = filename_lookup(AT_FDCWD, getname(pathname), LOOKUP_FOLLOW, &path);
if (ret)
goto out;
- if (!S_ISDIR(s.st_mode)) {
+
+ if (!d_is_dir(path.dentry)) {
ret = -ENOTDIR;
goto out;
}
- if (!dir_is_empty(pathname)) {
- ret = -ENOTEMPTY;
- goto out;
- }
+ realpath = dpath(path.dentry, d_root);
+ strcpy(cwd, realpath);
+ free(realpath);
+ cwd_dentry = path.dentry;
+ cwd_mnt = path.mnt;
- fsdev = get_fs_device_and_root_path(&p);
- if (!fsdev) {
- ret = -ENODEV;
- goto out;
- }
- fsdrv = fsdev->driver;
+ ret = 0;
- if (fsdrv->rmdir)
- ret = fsdrv->rmdir(&fsdev->dev, p);
- else
- ret = -EROFS;
out:
- free(freep);
-
if (ret)
errno = -ret;
return ret;
}
-EXPORT_SYMBOL(rmdir);
+EXPORT_SYMBOL(chdir);
/*
- * cdev_get_mount_path - return the path a cdev is mounted on
- *
- * If a cdev is mounted return the path it's mounted on, NULL
- * otherwise.
+ * Mount a device to a directory.
+ * We do this by registering a new device on which the filesystem
+ * driver will match.
*/
-const char *cdev_get_mount_path(struct cdev *cdev)
+int mount(const char *device, const char *fsname, const char *pathname,
+ const char *fsoptions)
{
struct fs_device_d *fsdev;
+ int ret;
+ struct path path = {};
- for_each_fs_device(fsdev) {
- if (fsdev->cdev && fsdev->cdev == cdev)
- return fsdev->path;
+ if (d_root) {
+ ret = filename_lookup(AT_FDCWD, getname(pathname), LOOKUP_FOLLOW, &path);
+ if (ret)
+ goto out;
+
+ if (!d_is_dir(path.dentry)) {
+ ret = -ENOTDIR;
+ goto out;
+ }
+
+ if (IS_ROOT(path.dentry) || d_mountpoint(path.dentry)) {
+ ret = -EBUSY;
+ goto out;
+ }
+ } else {
+ if (pathname[0] != '/' || pathname[1])
+ return -EINVAL;
}
- return NULL;
+ if (!fsoptions)
+ fsoptions = "";
+
+ debug("mount: %s on %s type %s, options=%s\n",
+ device, pathname, fsname, fsoptions);
+
+ if (!fsname)
+ fsname = detect_fs(device, fsoptions);
+
+ if (!fsname) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ fsdev = xzalloc(sizeof(struct fs_device_d));
+ fsdev->backingstore = xstrdup(device);
+ safe_strncpy(fsdev->dev.name, fsname, MAX_DRIVER_NAME);
+ fsdev->dev.id = get_free_deviceid(fsdev->dev.name);
+ fsdev->dev.bus = &fs_bus;
+ fsdev->options = xstrdup(fsoptions);
+
+ init_super(&fsdev->sb);
+
+ if (path.mnt)
+ mntget(path.mnt);
+
+ if (d_root)
+ fsdev->path = dpath(path.dentry, d_root);
+
+ ret = register_device(&fsdev->dev);
+ if (ret)
+ goto err_register;
+
+ if (!fsdev->dev.driver) {
+ /*
+ * Driver didn't accept the device or no driver for this
+ * device. Bail out
+ */
+ ret = -EINVAL;
+ goto err_no_driver;
+ }
+
+ if (d_root) {
+ fsdev->vfsmount.mountpoint = path.dentry;
+ fsdev->vfsmount.parent = path.mnt;
+ fsdev->vfsmount.mountpoint->d_flags |= DCACHE_MOUNTED;
+ } else {
+ d_root = fsdev->sb.s_root;
+ path.dentry = d_root;
+ mnt_root = &fsdev->vfsmount;
+ fsdev->vfsmount.mountpoint = d_root;
+ fsdev->vfsmount.parent = &fsdev->vfsmount;
+ fsdev->path = xstrdup("/");
+ }
+
+ fsdev->vfsmount.mnt_root = fsdev->sb.s_root;
+
+ if (!fsdev->linux_rootarg && fsdev->cdev && fsdev->cdev->partuuid[0] != 0) {
+ char *str = basprintf("root=PARTUUID=%s",
+ fsdev->cdev->partuuid);
+
+ fsdev_set_linux_rootarg(fsdev, str);
+ }
+
+ path_put(&path);
+
+ return 0;
+
+err_no_driver:
+ unregister_device(&fsdev->dev);
+err_register:
+ fs_remove(&fsdev->dev);
+out:
+ path_put(&path);
+
+ errno = -ret;
+
+ return ret;
}
+EXPORT_SYMBOL(mount);
-/*
- * cdev_mount_default - mount a cdev to the default path
- *
- * If a cdev is already mounted return the path it's mounted on, otherwise
- * mount it to /mnt/<cdevname> and return the path. Returns an error pointer
- * on failure.
- */
-const char *cdev_mount_default(struct cdev *cdev, const char *fsoptions)
+int umount(const char *pathname)
{
- const char *path;
- char *newpath, *devpath;
+ struct fs_device_d *fsdev = NULL, *f;
+ struct path path = {};
int ret;
- /*
- * If this cdev is already mounted somewhere use this path
- * instead of mounting it again to avoid corruption on the
- * filesystem. Note this ignores eventual fsoptions though.
- */
- path = cdev_get_mount_path(cdev);
- if (path)
- return path;
+ ret = filename_lookup(AT_FDCWD, getname(pathname), LOOKUP_FOLLOW, &path);
+ if (ret)
+ return ret;
- newpath = basprintf("/mnt/%s", cdev->name);
- make_directory(newpath);
+ if (path.dentry == d_root) {
+ path_put(&path);
+ return -EBUSY;
+ }
- devpath = basprintf("/dev/%s", cdev->name);
+ for_each_fs_device(f) {
+ if (path.dentry == f->vfsmount.mnt_root) {
+ fsdev = f;
+ break;
+ }
+ }
- ret = mount(devpath, NULL, newpath, fsoptions);
+ path_put(&path);
- free(devpath);
+ if (!fsdev) {
+ struct cdev *cdev = cdev_open(pathname, O_RDWR);
- if (ret) {
- free(newpath);
- return ERR_PTR(ret);
+ if (cdev) {
+ cdev_close(cdev);
+ return umount_by_cdev(cdev);
+ }
}
- return cdev_get_mount_path(cdev);
+ if (!fsdev) {
+ errno = EFAULT;
+ return -EFAULT;
+ }
+
+ return fsdev_umount(fsdev);
}
+EXPORT_SYMBOL(umount);
-/*
- * mount_all - iterate over block devices and mount all devices we are able to
- */
-void mount_all(void)
+#ifdef CONFIG_FS_AUTOMOUNT
+
+#define AUTOMOUNT_IS_FILE (1 << 0)
+
+struct automount {
+ char *path;
+ struct dentry *dentry;
+ char *cmd;
+ struct list_head list;
+ unsigned int flags;
+};
+
+static LIST_HEAD(automount_list);
+
+static void automount_remove_dentry(struct dentry *dentry)
{
- struct device_d *dev;
- struct block_device *bdev;
+ struct automount *am;
- if (!IS_ENABLED(CONFIG_BLOCK))
+ list_for_each_entry(am, &automount_list, list) {
+ if (dentry == am->dentry)
+ goto found;
+ }
+
+ return;
+found:
+ list_del(&am->list);
+ dput(am->dentry);
+ free(am->path);
+ free(am->cmd);
+ free(am);
+}
+
+void automount_remove(const char *pathname)
+{
+ struct path path;
+ int ret;
+
+ ret = filename_lookup(AT_FDCWD, getname(pathname), LOOKUP_FOLLOW, &path);
+ if (ret)
return;
- for_each_device(dev)
- device_detect(dev);
+ automount_remove_dentry(path.dentry);
- for_each_block_device(bdev) {
- struct cdev *cdev = &bdev->cdev;
+ path_put(&path);
+}
+EXPORT_SYMBOL(automount_remove);
- list_for_each_entry(cdev, &bdev->dev->cdevs, devices_list)
- cdev_mount_default(cdev, NULL);
+int automount_add(const char *pathname, const char *cmd)
+{
+ struct automount *am = xzalloc(sizeof(*am));
+ struct path path;
+ int ret;
+
+ ret = filename_lookup(AT_FDCWD, getname(pathname), LOOKUP_FOLLOW, &path);
+ if (ret)
+ return ret;
+
+ if (!d_is_dir(path.dentry)) {
+ ret = -ENOTDIR;
+ goto out;
}
+
+ am->path = dpath(path.dentry, d_root);
+ am->dentry = dget(path.dentry);
+ am->dentry->d_flags |= DCACHE_NEED_AUTOMOUNT;
+ am->cmd = xstrdup(cmd);
+
+ automount_remove_dentry(am->dentry);
+
+ list_add_tail(&am->list, &automount_list);
+
+ ret = 0;
+out:
+ path_put(&path);
+
+ return ret;
}
+EXPORT_SYMBOL(automount_add);
-void fsdev_set_linux_rootarg(struct fs_device_d *fsdev, const char *str)
+void cdev_create_default_automount(struct cdev *cdev)
{
- fsdev->linux_rootarg = xstrdup(str);
+ char *path, *cmd;
- dev_add_param_fixed(&fsdev->dev, "linux.bootargs", fsdev->linux_rootarg);
+ path = basprintf("/mnt/%s", cdev->name);
+ cmd = basprintf("mount %s", cdev->name);
+
+ make_directory(path);
+ automount_add(path, cmd);
+
+ free(cmd);
+ free(path);
}
-/**
- * path_get_linux_rootarg() - Given a path return a suitable root= option for
- * Linux
- * @path: The path
- *
- * Return: A string containing the root= option or an ERR_PTR. the returned
- * string must be freed by the caller.
- */
-char *path_get_linux_rootarg(const char *path)
+void automount_print(void)
{
- struct fs_device_d *fsdev;
- const char *str;
+ struct automount *am;
- fsdev = get_fsdevice_by_path(path);
- if (!fsdev)
- return ERR_PTR(-EINVAL);
+ list_for_each_entry(am, &automount_list, list)
+ printf("%-20s %s\n", am->path, am->cmd);
+}
+EXPORT_SYMBOL(automount_print);
- str = dev_get_param(&fsdev->dev, "linux.bootargs");
- if (!str)
- return ERR_PTR(-ENOSYS);
+static int automount_mount(struct dentry *dentry)
+{
+ struct automount *am;
+ int ret = -ENOENT;
+ static int in_automount;
- return xstrdup(str);
+ if (in_automount)
+ return -EINVAL;
+
+ in_automount++;
+
+ list_for_each_entry(am, &automount_list, list) {
+ if (am->dentry != dentry)
+ continue;
+
+ setenv("automount_path", am->path);
+ export("automount_path");
+ ret = run_command(am->cmd);
+ setenv("automount_path", NULL);
+
+ if (ret) {
+ printf("running automount command '%s' failed\n",
+ am->cmd);
+ ret = -ENODEV;
+ }
+
+ break;
+ }
+
+ in_automount--;
+
+ return ret;
}
-/**
- * __is_tftp_fs() - return true when path is mounted on TFTP
- * @path: The path
- *
- * Do not use directly, use is_tftp_fs instead.
- *
- * Return: true when @path is on TFTP, false otherwise
+BAREBOX_MAGICVAR(automount_path, "mountpath passed to automount scripts");
+
+#else
+static int automount_mount(struct dentry *dentry)
+{
+ return 0;
+}
+#endif /* CONFIG_FS_AUTOMOUNT */
+
+#ifdef DEBUG
+
+/*
+ * Some debug commands, helpful to debug the dcache implementation
*/
-bool __is_tftp_fs(const char *path)
+#include <command.h>
+
+static int do_lookup_dentry(int argc, char *argv[])
+{
+ struct path path;
+ int ret;
+ char *canon;
+ char mode[16];
+
+ if (argc < 2)
+ return COMMAND_ERROR_USAGE;
+
+ ret = filename_lookup(AT_FDCWD, getname(argv[1]), 0, &path);
+ if (ret) {
+ printf("Cannot lookup path \"%s\": %s\n",
+ argv[1], strerror(-ret));
+ return 1;
+ }
+
+ canon = canonicalize_path(argv[1]);
+
+ printf("path \"%s\":\n", argv[1]);
+ printf("dentry: 0x%p\n", path.dentry);
+ printf("dentry refcnt: %d\n", path.dentry->d_count);
+ if (path.dentry->d_inode) {
+ struct inode *inode = path.dentry->d_inode;
+ printf("inode: 0x%p\n", inode);
+ printf("inode refcnt: %d\n", inode->i_count);
+ printf("Type: %s\n", mkmodestr(inode->i_mode, mode));
+ }
+ printf("canonical path: \"%s\"\n", canon);
+
+ path_put(&path);
+ free(canon);
+
+ return 0;
+}
+
+BAREBOX_CMD_START(lookup_dentry)
+ .cmd = do_lookup_dentry,
+BAREBOX_CMD_END
+
+static struct dentry *debug_follow_mount(struct dentry *dentry)
{
struct fs_device_d *fsdev;
+ unsigned managed = dentry->d_flags;
- fsdev = get_fsdevice_by_path(path);
- if (!fsdev)
- return false;
+ if (managed & DCACHE_MOUNTED) {
+ for_each_fs_device(fsdev) {
+ if (dentry == fsdev->vfsmount.mountpoint)
+ return fsdev->vfsmount.mnt_root;
+ }
+ return NULL;
+ } else {
+ return dentry;
+ }
+}
- if (strcmp(fsdev->driver->drv.name, "tftp"))
- return false;
+static void debug_dump_dentries(struct dentry *parent, int indent)
+{
+ int i;
+ struct dentry *dentry, *mp;
- return true;
+ for (i = 0; i < indent; i++)
+ printf("\t");
+again:
+ printf("%s d: %p refcnt: %d, inode %p refcnt %d\n",
+ parent->name, parent, parent->d_count, parent->d_inode,
+ parent->d_inode ? parent->d_inode->i_count : -1);
+
+ mp = debug_follow_mount(parent);
+ if (mp != parent) {
+ for (i = 0; i < indent; i++)
+ printf("\t");
+ printf("MOUNT: ");
+
+ parent = mp;
+
+ goto again;
+ }
+
+ list_for_each_entry(dentry, &parent->d_subdirs, d_child)
+ debug_dump_dentries(dentry, indent + 1);
+}
+
+static int do_debug_fs_dump(int argc, char *argv[])
+{
+ debug_dump_dentries(d_root, 0);
+
+ return 0;
}
+
+BAREBOX_CMD_START(debug_fs_dump)
+ .cmd = do_debug_fs_dump,
+BAREBOX_CMD_END
+#endif
diff --git a/fs/legacy.c b/fs/legacy.c
new file mode 100644
index 0000000000..fc6a18f408
--- /dev/null
+++ b/fs/legacy.c
@@ -0,0 +1,315 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+#include <common.h>
+#include <fs.h>
+
+static struct inode *legacy_get_inode(struct super_block *sb, const struct inode *dir,
+ umode_t mode);
+
+static int legacy_iterate(struct file *file, struct dir_context *ctx)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ struct inode *dir = d_inode(dentry);
+ struct super_block *sb = dir->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ struct dir *d;
+ struct dirent *dirent;
+ char *pathname;
+
+ dir_emit_dots(file, ctx);
+
+ pathname = dpath(dentry, fsdev->vfsmount.mnt_root);
+
+ d = fsdev->driver->opendir(&fsdev->dev, pathname);
+ while (1) {
+ dirent = fsdev->driver->readdir(&fsdev->dev, d);
+ if (!dirent)
+ break;
+
+ dir_emit(ctx, dirent->d_name, strlen(dirent->d_name), 0, DT_UNKNOWN);
+ }
+
+ fsdev->driver->closedir(&fsdev->dev, d);
+
+ free(pathname);
+
+ return 0;
+}
+
+static struct dentry *legacy_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ struct super_block *sb = dir->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ struct inode *inode;
+ char *pathname;
+ struct stat s;
+ int ret;
+
+ pathname = dpath(dentry, fsdev->vfsmount.mnt_root);
+
+ ret = fsdev->driver->stat(&fsdev->dev, pathname, &s);
+ if (!ret) {
+ inode = legacy_get_inode(sb, dir, s.st_mode);
+
+ inode->i_size = s.st_size;
+ inode->i_mode = s.st_mode;
+
+ d_add(dentry, inode);
+ }
+
+ return NULL;
+}
+
+static int legacy_create(struct inode *dir, struct dentry *dentry, umode_t mode)
+{
+ struct super_block *sb = dir->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ struct inode *inode;
+ char *pathname;
+ int ret;
+
+ if (!fsdev->driver->create)
+ return -EROFS;
+
+ pathname = dpath(dentry, fsdev->vfsmount.mnt_root);
+
+ ret = fsdev->driver->create(&fsdev->dev, pathname, mode | S_IFREG);
+
+ free(pathname);
+
+ if (ret)
+ return ret;
+
+ inode = legacy_get_inode(sb, dir, mode | S_IFREG);
+
+ d_instantiate(dentry, inode);
+
+ return 0;
+}
+
+static int legacy_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
+{
+ struct super_block *sb = dir->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ struct inode *inode;
+ char *pathname;
+ int ret;
+
+ if (!fsdev->driver->mkdir)
+ return -EROFS;
+
+ pathname = dpath(dentry, fsdev->vfsmount.mnt_root);
+
+ ret = fsdev->driver->mkdir(&fsdev->dev, pathname);
+
+ free(pathname);
+
+ if (ret)
+ return ret;
+
+ inode = legacy_get_inode(sb, dir, mode | S_IFDIR);
+
+ d_instantiate(dentry, inode);
+
+ return 0;
+}
+
+static int legacy_dir_is_empty(struct dentry *dentry)
+{
+ struct inode *dir = d_inode(dentry);
+ struct super_block *sb = dir->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ struct dir *d;
+ struct dirent *dirent;
+ char *pathname;
+
+ pathname = dpath(dentry, fsdev->vfsmount.mnt_root);
+
+ d = fsdev->driver->opendir(&fsdev->dev, pathname);
+ dirent = fsdev->driver->readdir(&fsdev->dev, d);
+
+ fsdev->driver->closedir(&fsdev->dev, d);
+
+ free(pathname);
+
+ return dirent ? 0 : 1;
+}
+
+static int legacy_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ struct super_block *sb = dir->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ char *pathname;
+ int ret;
+
+ if (!fsdev->driver->rmdir)
+ return -EROFS;
+
+ if (!legacy_dir_is_empty(dentry))
+ return -ENOTEMPTY;
+
+ pathname = dpath(dentry, fsdev->vfsmount.mnt_root);
+
+ ret = fsdev->driver->rmdir(&fsdev->dev, pathname);
+
+ free(pathname);
+
+ if (ret)
+ return ret;
+
+ drop_nlink(d_inode(dentry));
+ dput(dentry);
+ drop_nlink(dir);
+
+ return 0;
+}
+
+static int legacy_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct super_block *sb = dir->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ char *pathname;
+ int ret;
+
+ if (!fsdev->driver->unlink)
+ return -EROFS;
+
+ pathname = dpath(dentry, fsdev->vfsmount.mnt_root);
+
+ ret = fsdev->driver->unlink(&fsdev->dev, pathname);
+
+ free(pathname);
+
+ if (ret)
+ return ret;
+
+ drop_nlink(d_inode(dentry));
+ dput(dentry);
+
+ return 0;
+}
+
+static int legacy_symlink(struct inode *dir, struct dentry *dentry,
+ const char *dest)
+{
+ struct super_block *sb = dir->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ struct inode *inode;
+ char *pathname;
+ int ret;
+
+ if (!fsdev->driver->symlink)
+ return -ENOSYS;
+
+ pathname = dpath(dentry, fsdev->vfsmount.mnt_root);
+
+ ret = fsdev->driver->symlink(&fsdev->dev, dest, pathname);
+
+ free(pathname);
+
+ if (ret)
+ return ret;
+
+ inode = legacy_get_inode(sb, dir, S_IFLNK);
+ inode->i_link = xstrdup(dest);
+
+ d_instantiate(dentry, inode);
+
+ return 0;
+}
+
+static const char *legacy_get_link(struct dentry *dentry, struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+ struct fs_device_d *fsdev = container_of(sb, struct fs_device_d, sb);
+ char *pathname;
+ int ret;
+ char link[PATH_MAX] = {};
+
+ if (!fsdev->driver->readlink)
+ return ERR_PTR(-ENOSYS);
+
+ pathname = dpath(dentry, fsdev->vfsmount.mnt_root);
+
+ ret = fsdev->driver->readlink(&fsdev->dev, pathname, link, PATH_MAX - 1);
+
+ free(pathname);
+
+ if (ret)
+ return NULL;
+
+ inode->i_link = xstrdup(link);
+
+ return inode->i_link;
+}
+
+static const struct super_operations legacy_s_ops;
+static const struct inode_operations legacy_file_inode_operations;
+
+static const struct inode_operations legacy_dir_inode_operations = {
+ .lookup = legacy_lookup,
+ .create = legacy_create,
+ .mkdir = legacy_mkdir,
+ .rmdir = legacy_rmdir,
+ .unlink = legacy_unlink,
+ .symlink = legacy_symlink,
+};
+
+static const struct file_operations legacy_dir_operations = {
+ .iterate = legacy_iterate,
+};
+
+static const struct inode_operations legacy_symlink_inode_operations = {
+ .get_link = legacy_get_link,
+};
+
+static struct inode *legacy_get_inode(struct super_block *sb, const struct inode *dir,
+ umode_t mode)
+{
+ struct inode *inode = new_inode(sb);
+
+ if (!inode)
+ return NULL;
+
+ inode->i_ino = get_next_ino();
+ inode->i_mode = mode;
+
+ switch (mode & S_IFMT) {
+ default:
+ return NULL;
+ case S_IFREG:
+ case S_IFCHR:
+ inode->i_op = &legacy_file_inode_operations;
+ break;
+ case S_IFDIR:
+ inode->i_op = &legacy_dir_inode_operations;
+ inode->i_fop = &legacy_dir_operations;
+ inc_nlink(inode);
+ break;
+ case S_IFLNK:
+ inode->i_op = &legacy_symlink_inode_operations;
+ break;
+ }
+
+ return inode;
+}
+
+int fs_init_legacy(struct fs_device_d *fsdev)
+{
+ struct inode *inode;
+
+ fsdev->sb.s_op = &legacy_s_ops;
+ inode = legacy_get_inode(&fsdev->sb, NULL, S_IFDIR);
+ fsdev->sb.s_root = d_make_root(inode);
+
+ return 0;
+}
diff --git a/fs/libfs.c b/fs/libfs.c
new file mode 100644
index 0000000000..af8f0f7462
--- /dev/null
+++ b/fs/libfs.c
@@ -0,0 +1,97 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+#include <common.h>
+#include <fs.h>
+#include <linux/fs.h>
+
+/*
+ * Lookup the data. This is trivial - if the dentry didn't already
+ * exist, we know it is negative. Set d_op to delete negative dentries.
+ */
+struct dentry *simple_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
+{
+ return NULL;
+}
+
+/* Relationship between i_mode and the DT_xxx types */
+static inline unsigned char dt_type(struct inode *inode)
+{
+ return (inode->i_mode >> 12) & 15;
+}
+
+int dcache_readdir(struct file *file, struct dir_context *ctx)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ struct dentry *d;
+
+ dir_emit_dots(file, ctx);
+
+ list_for_each_entry(d, &dentry->d_subdirs, d_child) {
+ if (d_is_negative(d))
+ continue;
+ dir_emit(ctx, d->d_name.name, d->d_name.len,
+ d_inode(d)->i_ino, dt_type(d_inode(d)));
+ }
+
+ return 0;
+}
+
+const struct file_operations simple_dir_operations = {
+ .iterate = dcache_readdir,
+};
+
+int simple_empty(struct dentry *dentry)
+{
+ struct dentry *child;
+ int ret = 0;
+
+ list_for_each_entry(child, &dentry->d_subdirs, d_child) {
+ if (d_is_positive(child))
+ goto out;
+ }
+ ret = 1;
+out:
+ return ret;
+}
+
+int simple_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct inode *inode = d_inode(dentry);
+
+ drop_nlink(inode);
+ dput(dentry);
+
+ return 0;
+}
+
+int simple_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ if (IS_ROOT(dentry))
+ return -EBUSY;
+
+ if (!simple_empty(dentry))
+ return -ENOTEMPTY;
+
+ drop_nlink(d_inode(dentry));
+ simple_unlink(dir, dentry);
+ drop_nlink(dir);
+
+ return 0;
+}
+
+const char *simple_get_link(struct dentry *dentry, struct inode *inode)
+{
+ return inode->i_link;
+}
+
+const struct inode_operations simple_symlink_inode_operations = {
+ .get_link = simple_get_link,
+};
diff --git a/fs/pstore/Kconfig b/fs/pstore/Kconfig
index 0c6f136920..0e042cb162 100644
--- a/fs/pstore/Kconfig
+++ b/fs/pstore/Kconfig
@@ -1,4 +1,5 @@
menuconfig FS_PSTORE
+ select FS_LEGACY
bool
prompt "pstore fs support"
help
diff --git a/fs/squashfs/Kconfig b/fs/squashfs/Kconfig
index 19b8297af6..fce05c5730 100644
--- a/fs/squashfs/Kconfig
+++ b/fs/squashfs/Kconfig
@@ -1,6 +1,7 @@
menuconfig FS_SQUASHFS
bool
prompt "squashfs support"
+ select FS_LEGACY
help
Saying Y here includes support for SquashFS 4.0 (a Compressed
Read-Only File System). Squashfs is a highly compressed read-only
diff --git a/fs/squashfs/super.c b/fs/squashfs/super.c
index 4c730e09e4..34e92e3c1d 100644
--- a/fs/squashfs/super.c
+++ b/fs/squashfs/super.c
@@ -39,15 +39,6 @@
#include "squashfs.h"
#include "decompressor.h"
-static struct dentry *d_make_root(struct inode *inode)
-{
- struct dentry *de = malloc(sizeof(struct dentry));
- de->d_name.name = "/";
- de->d_name.len = strlen("/");
- de->d_inode = inode;
- return de;
-}
-
static const struct squashfs_decompressor *supported_squashfs_filesystem(short
major, short minor, short id)
{
diff --git a/fs/ubifs/Kconfig b/fs/ubifs/Kconfig
index 889a2be97a..9aa0172289 100644
--- a/fs/ubifs/Kconfig
+++ b/fs/ubifs/Kconfig
@@ -1,6 +1,9 @@
menuconfig FS_UBIFS
bool
depends on MTD_UBI
+ select FS_LEGACY
+# Due to duplicate definition of iput
+ depends on BROKEN
prompt "ubifs support"
if FS_UBIFS