summaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
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