diff --git a/fs/Kconfig b/fs/Kconfig index 3512000..b60314b 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -6,12 +6,24 @@ 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 @@ 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 @@ 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_EFIVARFS depends on EFI_BOOTUP + select FS_LEGACY bool prompt "EFI variable filesystem support (efivarfs)" help @@ -62,6 +81,7 @@ 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_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 @@ 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 8e3fd78..ac3e6a0 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -2,9 +2,10 @@ 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 f36043d..8643e9d 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 0699728..b1def85 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 8a49e32..41818ea 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -36,6 +36,7 @@ #include #include #include +#include char *mkmodestr(unsigned long mode, char *str) { @@ -69,8 +70,12 @@ 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 @@ 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; -} - -/* - * canonicalize_path - resolve links in path - * @pathname: The input path - * - * This function resolves all links in @pathname and returns - * a path without links in it. - * - * Return: Path with links resolved. Allocated, must be freed after use. - */ -char *canonicalize_path(const char *pathname) -{ - char *r, *p = __canonicalize_path(pathname, 0); - - if (IS_ERR(p)) - return ERR_CAST(p); - - r = normalise_path(p); - free(p); - - return r; -} - -/* - * canonicalize_dir - resolve links in path - * @pathname: The input path - * - * This function resolves all links except the last one. Needed to give - * access to the link itself. - * - * Return: Path with links resolved. Allocated, must be freed after use. - */ -static char *canonicalize_dir(const char *pathname) -{ - char *f, *d, *r, *ret, *p; - char *freep1, *freep2; - - freep1 = xstrdup(pathname); - freep2 = xstrdup(pathname); - f = basename(freep1); - d = dirname(freep2); - - p = __canonicalize_path(d, 0); - if (IS_ERR(p)) { - ret = ERR_CAST(p); - goto out; - } - - r = basprintf("%s/%s", p, f); - - ret = normalise_path(r); - - free(r); - free(p); -out: - free(freep1); - free(freep2); - - return ret; -} - LIST_HEAD(fs_device_list); -static struct fs_device_d *fs_dev_root; -static struct fs_device_d *get_fsdevice_by_path(const char *path) +struct vfsmount *mntget(struct vfsmount *mnt) { - struct fs_device_d *fsdev = NULL; + if (!mnt) + return NULL; + + mnt->ref++; + + return mnt; +} + +void mntput(struct vfsmount *mnt) +{ + if (!mnt) + return; + + mnt->ref--; +} + +struct vfsmount *lookup_mnt(struct path *path) +{ + 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 @@ free(f->path); f->path = NULL; f->in_use = 0; + iput(f->f_inode); + dput(f->dentry); } static int check_fd(int fd) @@ -360,381 +181,21 @@ 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) +int create(struct dentry *dir, struct dentry *dentry) { - char *path = normalise_path(_path); - struct automount *am; + struct inode *inode; - 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) -{ -} -#endif /* CONFIG_FS_AUTOMOUNT */ - -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; + inode = d_inode(dir); - return 0; + if (!inode->i_op->create) + return -EROFS; + + return inode->i_op->create(inode, dentry, S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO); } -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; - - if (!fsdrv->unlink) { - ret = -ENOSYS; - goto out; - } - - ret = fsdrv->unlink(&fsdev->dev, p); - if (ret) - errno = -ret; -out: - free(freep); - if (ret) - errno = -ret; - return ret; -} -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) { return open(pathname, O_CREAT | O_WRONLY | O_TRUNC); @@ -869,6 +330,7 @@ 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 @@ { struct fs_driver_d *fsdrv; FILE *f; - int ret; + int ret = 0; if (check_fd(fd)) return -errno; @@ -1105,7 +567,9 @@ 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 @@ } 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 @@ 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 @@ 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,101 +750,16 @@ return 0; } -/* - * Mount a device to a directory. - * We do this by registering a new device on which the filesystem - * driver will match. - */ -int mount(const char *device, const char *fsname, const char *_path, - const char *fsoptions) +static void init_super(struct super_block *sb) { - struct fs_device_d *fsdev; - int ret; - char *path = normalise_path(_path); - - if (!fsoptions) - fsoptions = ""; - - debug("mount: %s on %s type %s, options=%s\n", - device, path, fsname, fsoptions); - - if (fs_dev_root) { - struct stat s; - - fsdev = get_fsdevice_by_path(path); - if (fsdev != fs_dev_root) { - printf("sorry, no nested mounts\n"); - ret = -EBUSY; - goto err_free_path; - } - ret = lstat(path, &s); - if (ret) - goto err_free_path; - if (!S_ISDIR(s.st_mode)) { - ret = -ENOTDIR; - goto err_free_path; - } - } else { - /* no mtab, so we only allow to mount on '/' */ - if (*path != '/' || *(path + 1)) { - ret = -ENOTDIR; - goto err_free_path; - } - } - - if (!fsname) - fsname = detect_fs(device, fsoptions); - - if (!fsname) - 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); - - 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 (!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); - } - - free(path); - - return 0; - -err_no_driver: - unregister_device(&fsdev->dev); -err_register: - fs_remove(&fsdev->dev); -err_free_path: - free(path); - - errno = -ret; - - return ret; + INIT_LIST_HEAD(&sb->s_inodes); } -EXPORT_SYMBOL(mount); static int fsdev_umount(struct fs_device_d *fsdev) { + if (fsdev->vfsmount.ref) + return -EBUSY; + return unregister_device(&fsdev->dev); } @@ -1449,196 +792,61 @@ } EXPORT_SYMBOL(umount_by_cdev); -int umount(const char *pathname) +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 fs_device_d *fsdev = NULL, *f; - char *p = normalise_path(pathname); + struct readdir_callback *rd = container_of(ctx, struct readdir_callback, ctx); + struct readdir_entry *entry; - for_each_fs_device(f) { - if (!strcmp(p, f->path)) { - fsdev = f; - break; - } - } + entry = xzalloc(sizeof(*entry)); + if (!entry) + return -ENOMEM; - if (!fsdev) { - struct cdev *cdev = cdev_open(p, O_RDWR); + memcpy(entry->d.d_name, name, namlen); + list_add_tail(&entry->list, &rd->dir->entries); - if (cdev) { - free(p); - cdev_close(cdev); - return umount_by_cdev(cdev); - } - } - - free(p); - - if (f == fs_dev_root && !list_is_singular(&fs_device_list)) { - errno = EBUSY; - return -EBUSY; - } - - if (!fsdev) { - errno = EFAULT; - return -EFAULT; - } - - return fsdev_umount(fsdev); + return 0; } -EXPORT_SYMBOL(umount); - -DIR *opendir(const char *pathname) -{ - DIR *dir = NULL; - struct fs_device_d *fsdev; - struct fs_driver_d *fsdrv; - char *p = canonicalize_path(pathname); - char *freep = p; - int ret; - struct stat s; - - ret = stat(pathname, &s); - if (ret) - goto out; - - if (!S_ISDIR(s.st_mode)) { - ret = -ENOTDIR; - goto out; - } - - fsdev = get_fs_device_and_root_path(&p); - if (!fsdev) { - ret = -ENOENT; - goto out; - } - fsdrv = fsdev->driver; - - debug("opendir: fsdrv: %p\n",fsdrv); - - dir = fsdrv->opendir(&fsdev->dev, p); - if (dir) { - dir->dev = &fsdev->dev; - dir->fsdrv = fsdrv; - } else { - /* - * FIXME: The fs drivers should return ERR_PTR here so that - * we are able to forward the error - */ - ret = -EINVAL; - } - -out: - free(freep); - - if (ret) - errno = -ret; - - return dir; -} -EXPORT_SYMBOL(opendir); struct dirent *readdir(DIR *dir) { - struct dirent *ent; + struct readdir_entry *entry; if (!dir) return NULL; - ent = dir->fsdrv->readdir(dir->dev, dir); + if (list_empty(&dir->entries)) + return NULL; - if (!ent) - errno = EBADF; + entry = list_first_entry(&dir->entries, struct readdir_entry, list); - return ent; + list_del(&entry->list); + strcpy(dir->d.d_name, entry->d.d_name); + free(entry); + + return &dir->d; } EXPORT_SYMBOL(readdir); -int closedir(DIR *dir) +static void stat_inode(struct inode *inode, struct stat *s) { - int ret; - - if (!dir) { - errno = EBADF; - return -EBADF; - } - - ret = dir->fsdrv->closedir(dir->dev, dir); - if (ret) - errno = -ret; - - return ret; + 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; } -EXPORT_SYMBOL(closedir); - -int stat(const char *filename, struct stat *s) -{ - char *path = canonicalize_path(filename); - int ret; - - if (IS_ERR(path)) - return PTR_ERR(path); - - ret = lstat(path, s); - - free(path); - - return ret; -} -EXPORT_SYMBOL(stat); - -static int __lstat(const char *filename, struct stat *s) -{ - struct fs_driver_d *fsdrv; - struct fs_device_d *fsdev; - char *f = normalise_path(filename); - char *freep = f; - int ret; - - automount_mount(f, 1); - - memset(s, 0, sizeof(struct stat)); - - fsdev = get_fsdevice_by_path(f); - if (!fsdev) { - ret = -ENOENT; - goto out; - } - - if (fsdev != fs_dev_root && strcmp(f, fsdev->path)) - f += strlen(fsdev->path); - else - fsdev = fs_dev_root; - - fsdrv = fsdev->driver; - - if (*f == 0) - f = "/"; - - ret = fsdrv->stat(&fsdev->dev, f, s); -out: - free(freep); - - if (ret) - errno = -ret; - - return ret; -} - -int lstat(const char *filename, struct stat *s) -{ - char *f = canonicalize_dir(filename); - int ret; - - if (IS_ERR(f)) - return PTR_ERR(f); - - ret = __lstat(f, s); - - free(f); - - return ret; -} -EXPORT_SYMBOL(lstat); int fstat(int fd, struct stat *s) { @@ -1652,93 +860,12 @@ fsdev = f->fsdev; - return fsdev->driver->stat(&fsdev->dev, f->path, s); + stat_inode(f->f_inode, s); + + return 0; } EXPORT_SYMBOL(fstat); -int mkdir (const char *pathname, mode_t mode) -{ - struct fs_driver_d *fsdrv; - struct fs_device_d *fsdev; - char *p = canonicalize_path(pathname); - char *freep = p; - int ret; - struct stat s; - - ret = parent_check_directory(p); - if (ret) - goto out; - - ret = stat(pathname, &s); - if (!ret) { - ret = -EEXIST; - goto out; - } - - fsdev = get_fs_device_and_root_path(&p); - if (!fsdev) { - ret = -ENOENT; - goto out; - } - fsdrv = fsdev->driver; - - if (fsdrv->mkdir) - ret = fsdrv->mkdir(&fsdev->dev, p); - else - ret = -EROFS; -out: - free(freep); - - if (ret) - errno = -ret; - - return ret; -} -EXPORT_SYMBOL(mkdir); - -int rmdir (const char *pathname) -{ - struct fs_driver_d *fsdrv; - struct fs_device_d *fsdev; - char *p = canonicalize_path(pathname); - char *freep = p; - int ret; - struct stat s; - - ret = lstat(pathname, &s); - if (ret) - goto out; - if (!S_ISDIR(s.st_mode)) { - ret = -ENOTDIR; - goto out; - } - - if (!dir_is_empty(pathname)) { - ret = -ENOTEMPTY; - goto out; - } - - fsdev = get_fs_device_and_root_path(&p); - if (!fsdev) { - ret = -ENODEV; - goto out; - } - fsdrv = fsdev->driver; - - if (fsdrv->rmdir) - ret = fsdrv->rmdir(&fsdev->dev, p); - else - ret = -EROFS; -out: - free(freep); - - if (ret) - errno = -ret; - - return ret; -} -EXPORT_SYMBOL(rmdir); - /* * cdev_get_mount_path - return the path a cdev is mounted on * @@ -1870,3 +997,2072 @@ 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; + } + } + + /* 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 (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; +} + +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; + } + + path->dentry = dentry; + path->mnt = nd->path.mnt; + + return follow_managed(path, nd); +} + +/* + * 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); + } +} + +static int path_parent_directory(struct path *path) +{ + struct dentry *old = path->dentry; + + 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; + } + + follow_mount(&nd->path); + + nd->inode = nd->path.dentry->d_inode; + + return 0; +} + +static inline int handle_dots(struct nameidata *nd, int type) +{ + if (type == LAST_DOTDOT) { + return follow_dotdot(nd); + } + return 0; +} + +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; +} + +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; +} + +static int pick_link(struct nameidata *nd, struct path *link, + struct inode *inode) +{ + 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; +} + +/* + * 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. + */ +static inline int step_into(struct nameidata *nd, struct path *path, + int flags, struct inode *inode) +{ + if (!(flags & WALK_MORE) && nd->depth) + put_link(nd); + + 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; + } + + 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 step_into(nd, &path, flags, d_inode(path.dentry)); +} + +static int component_len(const char *name, char separator) +{ + int len = 0; + + 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; + } + + result->refcnt = 1; + + 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; + } +} + +static const char *path_init(struct nameidata *nd, unsigned flags) +{ + const char *s = nd->name->name; + + 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; + } + + 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; +} + +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; +} + +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) +{ + struct fs_device_d *fsdev; + struct path path; + int ret; + + 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; + + 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; + } + + 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; + } + + 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; + } + + 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 { + 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: + 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); + +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); +} + +int symlink(const char *pathname, const char *newpath) +{ + struct dentry *dentry; + struct path path; + int error; + unsigned int lookup_flags = LOOKUP_DIRECTORY; + + dentry = filename_create(AT_FDCWD, getname(newpath), &path, lookup_flags); + if (IS_ERR(dentry)) { + error = PTR_ERR(dentry); + goto out; + } + + error = vfs_symlink(path.dentry->d_inode, dentry, pathname); +out: + if (error) + errno = -error; + + 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); + } + + free(d); +} + +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; + + dir = path.dentry; + + if (d_is_negative(dir)) { + ret = -ENOENT; + goto out_put; + } + + 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) + goto out_release; + + path_put(&path); + + return d; + +out_release: + release_dir(d); +out_put: + path_put(&path); +out: + errno = -ret; + + return NULL; +} +EXPORT_SYMBOL(opendir); + +int closedir(DIR *dir) +{ + int ret; + + if (!dir) { + errno = EBADF; + return -EBADF; + } + + release_dir(dir); + + return ret; +} +EXPORT_SYMBOL(closedir); + +int readlink(const char *pathname, char *buf, size_t bufsiz) +{ + int ret; + struct dentry *dentry; + struct inode *inode; + const char *link; + struct path path = {}; + + ret = filename_lookup(AT_FDCWD, getname(pathname), 0, &path); + if (ret) + goto out; + + dentry = path.dentry; + + if (!d_is_symlink(dentry)) { + ret = -EINVAL; + goto out_put; + } + + inode = d_inode(dentry); + + if (!inode->i_op->get_link) { + ret = -EPERM; + goto out_put; + } + + link = inode->i_op->get_link(dentry, inode); + if (IS_ERR(link)) { + ret = PTR_ERR(link); + goto out_put; + } + + strncpy(buf, link, bufsiz); + + ret = 0; +out_put: + path_put(&path); +out: + if (ret) + errno = -ret; + + return ret; +} +EXPORT_SYMBOL(readlink); + +static int stat_filename(const char *filename, struct stat *s, unsigned int flags) +{ + 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 (d_is_negative(dentry)) { + ret = -ENOENT; + goto out_put; + } + + inode = d_inode(dentry); + + 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); + +static char *__dpath(struct dentry *dentry, struct dentry *root) +{ + char *res, *ppath; + + if (dentry == root) + return NULL; + if (dentry == d_root) + return NULL; + + while (IS_ROOT(dentry)) { + struct fs_device_d *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 res; +} + +/** + * 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) +{ + char *res; + + if (dentry == root) + return strdup("/"); + + res = __dpath(dentry, root); + + 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; + + res = dpath(path.dentry, d_root); +out: + if (ret) + errno = -ret; + + return res; +} + +const char *getcwd(void) +{ + return cwd; +} +EXPORT_SYMBOL(getcwd); + +int chdir(const char *pathname) +{ + char *realpath; + struct path path; + int ret; + + ret = filename_lookup(AT_FDCWD, getname(pathname), LOOKUP_FOLLOW, &path); + if (ret) + goto out; + + if (!d_is_dir(path.dentry)) { + ret = -ENOTDIR; + goto out; + } + + realpath = dpath(path.dentry, d_root); + strcpy(cwd, realpath); + free(realpath); + cwd_dentry = path.dentry; + cwd_mnt = path.mnt; + + ret = 0; + +out: + if (ret) + errno = -ret; + + return ret; +} +EXPORT_SYMBOL(chdir); + +/* + * Mount a device to a directory. + * We do this by registering a new device on which the filesystem + * driver will match. + */ +int mount(const char *device, const char *fsname, const char *pathname, + const char *fsoptions) +{ + struct fs_device_d *fsdev; + int ret; + struct path 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; + } + + 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); + +int umount(const char *pathname) +{ + struct fs_device_d *fsdev = NULL, *f; + struct path path = {}; + int ret; + + ret = filename_lookup(AT_FDCWD, getname(pathname), LOOKUP_FOLLOW, &path); + if (ret) + return ret; + + if (path.dentry == d_root) { + path_put(&path); + return -EBUSY; + } + + for_each_fs_device(f) { + if (path.dentry == f->vfsmount.mnt_root) { + fsdev = f; + break; + } + } + + path_put(&path); + + if (!fsdev) { + struct cdev *cdev = cdev_open(pathname, O_RDWR); + + if (cdev) { + cdev_close(cdev); + return umount_by_cdev(cdev); + } + } + + if (!fsdev) { + errno = EFAULT; + return -EFAULT; + } + + return fsdev_umount(fsdev); +} +EXPORT_SYMBOL(umount); + +#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 automount *am; + + 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; + + automount_remove_dentry(path.dentry); + + path_put(&path); +} +EXPORT_SYMBOL(automount_remove); + +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 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 int automount_mount(struct dentry *dentry) +{ + struct automount *am; + int ret = -ENOENT; + static int in_automount; + + 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; +} + +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 + */ +#include + +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; + + if (managed & DCACHE_MOUNTED) { + for_each_fs_device(fsdev) { + if (dentry == fsdev->vfsmount.mountpoint) + return fsdev->vfsmount.mnt_root; + } + return NULL; + } else { + return dentry; + } +} + +static void debug_dump_dentries(struct dentry *parent, int indent) +{ + int i; + struct dentry *dentry, *mp; + + 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 0000000..fc6a18f --- /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 +#include + +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 0000000..af8f0f7 --- /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 +#include +#include + +/* + * 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 0c6f136..0e042cb 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 19b8297..fce05c5 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 4c730e0..34e92e3 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 889a2be..9aa0172 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 diff --git a/include/dirent.h b/include/dirent.h index 5ee4c20..1df5d90 100644 --- a/include/dirent.h +++ b/include/dirent.h @@ -1,6 +1,8 @@ #ifndef __DIRENT_H #define __DIRENT_H +#include + struct dirent { char d_name[256]; }; @@ -11,6 +13,7 @@ struct node_d *node; struct dirent d; void *priv; /* private data for the fs driver */ + struct list_head entries; } DIR; DIR *opendir(const char *pathname); diff --git a/include/fs.h b/include/fs.h index e6fcd04..181318f 100644 --- a/include/fs.h +++ b/include/fs.h @@ -31,14 +31,15 @@ /* private fields. Mapping between FILE and filedescriptor number */ int no; char in_use; + + struct inode *f_inode; + struct dentry *dentry; } FILE; #define FS_DRIVER_NO_DEV 1 struct fs_driver_d { int (*probe) (struct device_d *dev); - int (*mkdir)(struct device_d *dev, const char *pathname); - int (*rmdir)(struct device_d *dev, const char *pathname); /* create a file. The file is guaranteed to not exist */ int (*create)(struct device_d *dev, const char *pathname, mode_t mode); @@ -47,11 +48,6 @@ /* Truncate a file to given size */ int (*truncate)(struct device_d *dev, FILE *f, ulong size); - int (*symlink)(struct device_d *dev, const char *pathname, - const char *newpath); - int (*readlink)(struct device_d *dev, const char *pathname, char *name, - size_t size); - int (*open)(struct device_d *dev, FILE *f, const char *pathname); int (*close)(struct device_d *dev, FILE *f); int (*read)(struct device_d *dev, FILE *f, void *buf, size_t size); @@ -59,11 +55,6 @@ int (*flush)(struct device_d *dev, FILE *f); loff_t (*lseek)(struct device_d *dev, FILE *f, loff_t pos); - struct dir* (*opendir)(struct device_d *dev, const char *pathname); - struct dirent* (*readdir)(struct device_d *dev, struct dir *dir); - int (*closedir)(struct device_d *dev, DIR *dir); - int (*stat)(struct device_d *dev, const char *file, struct stat *stat); - int (*ioctl)(struct device_d *dev, FILE *f, int request, void *buf); int (*erase)(struct device_d *dev, FILE *f, loff_t count, loff_t offset); @@ -72,6 +63,18 @@ int (*memmap)(struct device_d *dev, FILE *f, void **map, int flags); + /* legacy */ + int (*mkdir)(struct device_d *dev, const char *pathname); + int (*rmdir)(struct device_d *dev, const char *pathname); + int (*symlink)(struct device_d *dev, const char *pathname, + const char *newpath); + int (*readlink)(struct device_d *dev, const char *pathname, char *name, + size_t size); + struct dir* (*opendir)(struct device_d *dev, const char *pathname); + struct dirent* (*readdir)(struct device_d *dev, struct dir *dir); + int (*closedir)(struct device_d *dev, DIR *dir); + int (*stat)(struct device_d *dev, const char *file, struct stat *stat); + struct driver_d drv; enum filetype type; @@ -99,6 +102,10 @@ struct list_head list; char *options; char *linux_rootarg; + + struct super_block sb; + + struct vfsmount vfsmount; }; bool __is_tftp_fs(const char *path); @@ -135,12 +142,6 @@ char *mkmodestr(unsigned long mode, char *str); -/* - * This function turns 'path' into an absolute path and removes all occurrences - * of "..", "." and double slashes. The returned string must be freed wit free(). - */ -char *normalise_path(const char *path); - char *canonicalize_path(const char *pathname); char *get_mounted_path(const char *path); @@ -154,6 +155,7 @@ int automount_add(const char *path, const char *cmd); void automount_print(void); +int fs_init_legacy(struct fs_device_d *fsdev); int fsdev_open_cdev(struct fs_device_d *fsdev); const char *cdev_get_mount_path(struct cdev *cdev); const char *cdev_mount_default(struct cdev *cdev, const char *fsoptions); diff --git a/include/linux/dcache.h b/include/linux/dcache.h index dfb4667..1624412 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -53,6 +53,10 @@ spinlock_t d_lock; /* per dentry lock */ struct inode *d_inode; /* Where the name belongs to - NULL is * negative */ + + unsigned int d_count; + const struct dentry_operations *d_op; + /* * The next three fields are touched by __d_lookup. Place them here * so they all fit in a cache line. @@ -65,8 +69,8 @@ /* * d_child and d_rcu can share memory */ + struct list_head d_child; /* child of parent list */ struct list_head d_subdirs; /* our children */ - struct list_head d_alias; /* inode alias list */ unsigned long d_time; /* used by d_revalidate */ struct super_block *d_sb; /* The root of the dentry tree */ void *d_fsdata; /* fs-specific data */ @@ -74,7 +78,108 @@ struct dcookie_struct *d_cookie; /* cookie, if any */ #endif int d_mounted; - unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */ + unsigned char *name; /* all names */ }; +struct dentry_operations { +}; + +struct dentry * d_make_root(struct inode *); +void d_add(struct dentry *, struct inode *); +struct dentry * d_alloc_anon(struct super_block *); +void d_set_d_op(struct dentry *dentry, const struct dentry_operations *op); +void d_instantiate(struct dentry *dentry, struct inode *inode); +void d_delete(struct dentry *); +struct dentry *dget(struct dentry *); +void dput(struct dentry *); + +#define DCACHE_ENTRY_TYPE 0x00700000 +#define DCACHE_MISS_TYPE 0x00000000 /* Negative dentry (maybe fallthru to nowhere) */ +#define DCACHE_WHITEOUT_TYPE 0x00100000 /* Whiteout dentry (stop pathwalk) */ +#define DCACHE_DIRECTORY_TYPE 0x00200000 /* Normal directory */ +#define DCACHE_AUTODIR_TYPE 0x00300000 /* Lookupless directory (presumed automount) */ +#define DCACHE_REGULAR_TYPE 0x00400000 /* Regular file type (or fallthru to such) */ +#define DCACHE_SPECIAL_TYPE 0x00500000 /* Other file type (or fallthru to such) */ +#define DCACHE_SYMLINK_TYPE 0x00600000 /* Symlink (or fallthru to such) */ + +#define DCACHE_FALLTHRU 0x01000000 /* Fall through to lower layer */ +#define DCACHE_CANT_MOUNT 0x00000100 +#define DCACHE_MOUNTED 0x00010000 /* is a mountpoint */ +#define DCACHE_NEED_AUTOMOUNT 0x00020000 /* handle automount on this dir */ +#define DCACHE_MANAGED_DENTRY \ + (DCACHE_MOUNTED|DCACHE_NEED_AUTOMOUNT) + +static inline bool d_mountpoint(const struct dentry *dentry) +{ + return dentry->d_flags & DCACHE_MOUNTED; +} + +/* + * Directory cache entry type accessor functions. + */ +static inline unsigned __d_entry_type(const struct dentry *dentry) +{ + return dentry->d_flags & DCACHE_ENTRY_TYPE; +} + +static inline bool d_is_miss(const struct dentry *dentry) +{ + return __d_entry_type(dentry) == DCACHE_MISS_TYPE; +} + +static inline bool d_can_lookup(const struct dentry *dentry) +{ + return __d_entry_type(dentry) == DCACHE_DIRECTORY_TYPE; +} + +static inline bool d_is_autodir(const struct dentry *dentry) +{ + return __d_entry_type(dentry) == DCACHE_AUTODIR_TYPE; +} + +static inline bool d_is_dir(const struct dentry *dentry) +{ + return d_can_lookup(dentry) || d_is_autodir(dentry); +} + +static inline bool d_is_symlink(const struct dentry *dentry) +{ + return __d_entry_type(dentry) == DCACHE_SYMLINK_TYPE; +} + +static inline bool d_is_reg(const struct dentry *dentry) +{ + return __d_entry_type(dentry) == DCACHE_REGULAR_TYPE; +} + +static inline bool d_is_special(const struct dentry *dentry) +{ + return __d_entry_type(dentry) == DCACHE_SPECIAL_TYPE; +} + +static inline bool d_is_file(const struct dentry *dentry) +{ + return d_is_reg(dentry) || d_is_special(dentry); +} + +static inline bool d_is_negative(const struct dentry *dentry) +{ + // TODO: check d_is_whiteout(dentry) also. + return d_is_miss(dentry); +} + +static inline bool d_is_positive(const struct dentry *dentry) +{ + return !d_is_negative(dentry); +} + +static inline struct inode *d_inode(const struct dentry *dentry) +{ + return dentry->d_inode; +} + +#define IS_ROOT(x) ((x) == (x)->d_parent) + +char *dpath(struct dentry *dentry, struct dentry *root); + #endif /* __LINUX_DCACHE_H */ diff --git a/include/linux/fs.h b/include/linux/fs.h index 153c464..4550e8f 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -34,7 +34,18 @@ #define DT_SOCK 12 #define DT_WHT 14 +/* + * This is the "filldir" function type, used by readdir() to let + * the kernel specify what kind of dirent layout it wants to have. + * This allows the kernel to read directories into kernel space or + * to have different dirent layouts depending on the binary type. + */ +struct dir_context; +typedef int (*filldir_t)(struct dir_context *, const char *, int, loff_t, u64, + unsigned); + struct dir_context { + const filldir_t actor; loff_t pos; }; @@ -94,12 +105,8 @@ }; uid_t i_uid; gid_t i_gid; - dev_t i_rdev; u64 i_version; loff_t i_size; -#ifdef __NEED_I_SIZE_ORDERED - seqcount_t i_size_seqcount; -#endif struct timespec i_atime; struct timespec i_mtime; struct timespec i_ctime; @@ -107,39 +114,19 @@ blkcnt_t i_blocks; unsigned short i_bytes; umode_t i_mode; - spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */ - struct mutex i_mutex; - struct rw_semaphore i_alloc_sem; const struct inode_operations *i_op; const struct file_operations *i_fop; /* former ->i_op->default_file_ops */ struct super_block *i_sb; - struct file_lock *i_flock; -#ifdef CONFIG_QUOTA - struct dquot *i_dquot[MAXQUOTAS]; -#endif - struct list_head i_devices; - int i_cindex; __u32 i_generation; -#ifdef CONFIG_DNOTIFY - unsigned long i_dnotify_mask; /* Directory notify events */ - struct dnotify_struct *i_dnotify; /* for directory notifications */ -#endif - -#ifdef CONFIG_INOTIFY - struct list_head inotify_watches; /* watches on this inode */ - struct mutex inotify_mutex; /* protects the watches list */ -#endif - unsigned long i_state; - unsigned long dirtied_when; /* jiffies of first dirtying */ unsigned int i_flags; + unsigned int i_count; -#ifdef CONFIG_SECURITY - void *i_security; -#endif + char *i_link; + void *i_private; /* fs or device private pointer */ }; @@ -199,19 +186,9 @@ Cannot be worse than a second */ u32 s_time_gran; - /* - * Filesystem subtype. If non-empty the filesystem type field - * in /proc/mounts will be "type.subtype" - */ - char *s_subtype; - - /* - * Saved mount options for lazy filesystems using - * generic_show_options() - */ - char *s_options; - /* Number of inodes with nlink == 0 but still referenced */ + + const struct dentry_operations *s_d_op; /* default d_op for dentries */ }; struct file_system_type { @@ -405,4 +382,80 @@ return inode->i_size; } +struct inode *new_inode(struct super_block *sb); +unsigned int get_next_ino(void); +void iput(struct inode *); +struct inode *iget(struct inode *); +void inc_nlink(struct inode *inode); + +struct inode_operations { + struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); + + const char *(*get_link) (struct dentry *dentry, struct inode *inode); + + int (*create) (struct inode *,struct dentry *, umode_t); + int (*link) (struct dentry *,struct inode *,struct dentry *); + int (*unlink) (struct inode *,struct dentry *); + int (*symlink) (struct inode *,struct dentry *,const char *); + int (*mkdir) (struct inode *,struct dentry *,umode_t); + int (*rmdir) (struct inode *,struct dentry *); + int (*rename) (struct inode *, struct dentry *, + struct inode *, struct dentry *, unsigned int); +}; + +static inline ino_t parent_ino(struct dentry *dentry) +{ + return dentry->d_parent->d_inode->i_ino; +} + +static inline bool dir_emit(struct dir_context *ctx, + const char *name, int namelen, + u64 ino, unsigned type) +{ + return ctx->actor(ctx, name, namelen, ctx->pos, ino, type) == 0; +} + +static inline bool dir_emit_dot(struct file *file, struct dir_context *ctx) +{ + return ctx->actor(ctx, ".", 1, ctx->pos, + file->f_path.dentry->d_inode->i_ino, DT_DIR) == 0; +} +static inline bool dir_emit_dotdot(struct file *file, struct dir_context *ctx) +{ + return ctx->actor(ctx, "..", 2, ctx->pos, + parent_ino(file->f_path.dentry), DT_DIR) == 0; +} + +static inline void dir_emit_dots(struct file *file, struct dir_context *ctx) +{ + if (ctx->pos == 0) { + dir_emit_dot(file, ctx); + ctx->pos = 1; + } + if (ctx->pos == 1) { + dir_emit_dotdot(file, ctx); + ctx->pos = 2; + } +} + +struct file_operations { + int (*iterate) (struct file *, struct dir_context *); + ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); + ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); + int (*ioctl) (struct file *, int request, void *buf); + int (*truncate) (struct file *, loff_t); +}; + +void drop_nlink(struct inode *inode); + +extern const struct file_operations simple_dir_operations; +extern const struct inode_operations simple_symlink_inode_operations; + +int simple_empty(struct dentry *dentry); +int simple_unlink(struct inode *dir, struct dentry *dentry); +int simple_rmdir(struct inode *dir, struct dentry *dentry); +struct dentry *simple_lookup(struct inode *, struct dentry *, unsigned int flags); +int dcache_readdir(struct file *, struct dir_context *); +const char *simple_get_link(struct dentry *dentry, struct inode *inode); + #endif /* _LINUX_FS_H */ diff --git a/include/linux/mount.h b/include/linux/mount.h index 57d5ba9..9557365 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -14,8 +14,11 @@ struct vfsmount { struct dentry *mnt_root; /* root of the mounted tree */ + struct dentry *mountpoint; /* where it's mounted (barebox specific, no support */ + struct vfsmount *parent; /* for bind mounts and the like) */ struct super_block *mnt_sb; /* pointer to superblock */ int mnt_flags; + int ref; }; #endif /* _LINUX_MOUNT_H */ diff --git a/include/linux/namei.h b/include/linux/namei.h new file mode 100644 index 0000000..8ed7f8a --- /dev/null +++ b/include/linux/namei.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_NAMEI_H +#define _LINUX_NAMEI_H + +#include +#include + +enum { MAX_NESTED_LINKS = 8 }; + +#define MAXSYMLINKS 40 + +/* + * Type of the last component on LOOKUP_PARENT + */ +enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND}; + +/* + * The bitmask for a lookup event: + * - follow links at the end + * - require a directory + * - ending slashes ok even for nonexistent files + * - internal "there are more path components" flag + * - dentry cache is untrusted; force a real lookup + * - suppress terminal automount + */ +#define LOOKUP_FOLLOW 0x0001 +#define LOOKUP_DIRECTORY 0x0002 +#define LOOKUP_AUTOMOUNT 0x0004 + +#define LOOKUP_PARENT 0x0010 +#define LOOKUP_REVAL 0x0020 +#define LOOKUP_RCU 0x0040 +#define LOOKUP_NO_REVAL 0x0080 + +/* + * Intent data + */ +#define LOOKUP_OPEN 0x0100 +#define LOOKUP_CREATE 0x0200 +#define LOOKUP_EXCL 0x0400 +#define LOOKUP_RENAME_TARGET 0x0800 + +#define LOOKUP_JUMPED 0x1000 +#define LOOKUP_ROOT 0x2000 +#define LOOKUP_EMPTY 0x4000 +#define LOOKUP_DOWN 0x8000 + +#define AT_FDCWD -100 /* Special value used to indicate + openat should use the current + working directory. */ + +#endif /* _LINUX_NAMEI_H */ diff --git a/include/linux/stat.h b/include/linux/stat.h index af022c5..87fe068 100644 --- a/include/linux/stat.h +++ b/include/linux/stat.h @@ -42,6 +42,8 @@ #define S_IWOTH 00002 /* read permission for other */ #define S_IXOTH 00001 /* execute/search permission for other */ +#define S_IRWXUGO (S_IRWXU|S_IRWXG|S_IRWXO) + struct stat { unsigned short st_dev; unsigned short __pad1;