diff options
Diffstat (limited to 'fs')
-rw-r--r-- | fs/Kconfig | 23 | ||||
-rw-r--r-- | fs/Makefile | 3 | ||||
-rw-r--r-- | fs/ext4/Kconfig | 1 | ||||
-rw-r--r-- | fs/fat/Kconfig | 1 | ||||
-rw-r--r-- | fs/fs.c | 3186 | ||||
-rw-r--r-- | fs/legacy.c | 315 | ||||
-rw-r--r-- | fs/libfs.c | 97 | ||||
-rw-r--r-- | fs/pstore/Kconfig | 1 | ||||
-rw-r--r-- | fs/squashfs/Kconfig | 1 | ||||
-rw-r--r-- | fs/squashfs/super.c | 9 | ||||
-rw-r--r-- | fs/ubifs/Kconfig | 3 |
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 @@ -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 |