diff options
Diffstat (limited to 'fs/hostfs/hostfs_kern.c')
| -rw-r--r-- | fs/hostfs/hostfs_kern.c | 780 |
1 files changed, 437 insertions, 343 deletions
diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index cddb05217512..51d26aa2b93e 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -14,16 +14,26 @@ #include <linux/statfs.h> #include <linux/slab.h> #include <linux/seq_file.h> +#include <linux/writeback.h> #include <linux/mount.h> +#include <linux/fs_context.h> +#include <linux/fs_parser.h> #include <linux/namei.h> #include "hostfs.h" #include <init.h> #include <kern.h> +struct hostfs_fs_info { + char *host_root_path; +}; + struct hostfs_inode_info { int fd; fmode_t mode; struct inode vfs_inode; + struct mutex open_mutex; + dev_t dev; + struct hostfs_timespec btime; }; static inline struct hostfs_inode_info *HOSTFS_I(struct inode *inode) @@ -33,14 +43,7 @@ static inline struct hostfs_inode_info *HOSTFS_I(struct inode *inode) #define FILE_HOSTFS_I(file) HOSTFS_I(file_inode(file)) -static int hostfs_d_delete(const struct dentry *dentry) -{ - return 1; -} - -static const struct dentry_operations hostfs_dentry_ops = { - .d_delete = hostfs_d_delete, -}; +static struct kmem_cache *hostfs_inode_cache; /* Changed in hostfs_args before the kernel starts running */ static char *root_ino = ""; @@ -55,6 +58,7 @@ static int __init hostfs_args(char *options, int *add) { char *ptr; + *add = 0; ptr = strchr(options, ','); if (ptr != NULL) *ptr++ = '\0'; @@ -92,25 +96,18 @@ __uml_setup("hostfs=", hostfs_args, static char *__dentry_name(struct dentry *dentry, char *name) { char *p = dentry_path_raw(dentry, name, PATH_MAX); - char *root; - size_t len; + struct hostfs_fs_info *fsi = dentry->d_sb->s_fs_info; + char *root = fsi->host_root_path; + size_t len = strlen(root); - root = dentry->d_sb->s_fs_info; - len = strlen(root); - if (IS_ERR(p)) { + if (IS_ERR(p) || len > p - name) { __putname(name); return NULL; } - strlcpy(name, root, PATH_MAX); - if (len > p - name) { - __putname(name); - return NULL; - } - if (p > name + len) { - char *s = name + len; - while ((*s++ = *p++) != '\0') - ; - } + + memcpy(name, root, len); + memmove(name + len, p, name + PATH_MAX - p); + return name; } @@ -141,24 +138,22 @@ static char *inode_name(struct inode *ino) static char *follow_link(char *link) { - int len, n; char *name, *resolved, *end; + int n; - len = 64; - while (1) { + name = kmalloc(PATH_MAX, GFP_KERNEL); + if (!name) { n = -ENOMEM; - name = kmalloc(len, GFP_KERNEL); - if (name == NULL) - goto out; - - n = hostfs_do_readlink(link, name, len); - if (n < len) - break; - len *= 2; - kfree(name); + goto out_free; } + + n = hostfs_do_readlink(link, name, PATH_MAX); if (n < 0) goto out_free; + else if (n == PATH_MAX) { + n = -E2BIG; + goto out_free; + } if (*name == '/') return name; @@ -168,34 +163,22 @@ static char *follow_link(char *link) return name; *(end + 1) = '\0'; - len = strlen(link) + strlen(name) + 1; - resolved = kmalloc(len, GFP_KERNEL); + resolved = kasprintf(GFP_KERNEL, "%s%s", link, name); if (resolved == NULL) { n = -ENOMEM; goto out_free; } - sprintf(resolved, "%s%s", link, name); kfree(name); - kfree(link); return resolved; out_free: kfree(name); - out: return ERR_PTR(n); } -static struct inode *hostfs_iget(struct super_block *sb) -{ - struct inode *inode = new_inode(sb); - if (!inode) - return ERR_PTR(-ENOMEM); - return inode; -} - -int hostfs_statfs(struct dentry *dentry, struct kstatfs *sf) +static int hostfs_statfs(struct dentry *dentry, struct kstatfs *sf) { /* * do_statfs uses struct statfs64 internally, but the linux kernel @@ -208,8 +191,10 @@ int hostfs_statfs(struct dentry *dentry, struct kstatfs *sf) long long f_bavail; long long f_files; long long f_ffree; + struct hostfs_fs_info *fsi; - err = do_statfs(dentry->d_sb->s_fs_info, + fsi = dentry->d_sb->s_fs_info; + err = do_statfs(fsi->host_root_path, &sf->f_bsize, &f_blocks, &f_bfree, &f_bavail, &f_files, &f_ffree, &sf->f_fsid, sizeof(sf->f_fsid), &sf->f_namelen); @@ -228,56 +213,61 @@ static struct inode *hostfs_alloc_inode(struct super_block *sb) { struct hostfs_inode_info *hi; - hi = kmalloc(sizeof(*hi), GFP_KERNEL); + hi = alloc_inode_sb(sb, hostfs_inode_cache, GFP_KERNEL_ACCOUNT); if (hi == NULL) return NULL; hi->fd = -1; hi->mode = 0; + hi->dev = 0; inode_init_once(&hi->vfs_inode); + mutex_init(&hi->open_mutex); return &hi->vfs_inode; } static void hostfs_evict_inode(struct inode *inode) { - truncate_inode_pages(&inode->i_data, 0); + truncate_inode_pages_final(&inode->i_data); clear_inode(inode); if (HOSTFS_I(inode)->fd != -1) { close_file(&HOSTFS_I(inode)->fd); HOSTFS_I(inode)->fd = -1; + HOSTFS_I(inode)->dev = 0; } } -static void hostfs_i_callback(struct rcu_head *head) -{ - struct inode *inode = container_of(head, struct inode, i_rcu); - kfree(HOSTFS_I(inode)); -} - -static void hostfs_destroy_inode(struct inode *inode) +static void hostfs_free_inode(struct inode *inode) { - call_rcu(&inode->i_rcu, hostfs_i_callback); + kmem_cache_free(hostfs_inode_cache, HOSTFS_I(inode)); } static int hostfs_show_options(struct seq_file *seq, struct dentry *root) { - const char *root_path = root->d_sb->s_fs_info; + struct hostfs_fs_info *fsi; + const char *root_path; + + fsi = root->d_sb->s_fs_info; + root_path = fsi->host_root_path; size_t offset = strlen(root_ino) + 1; if (strlen(root_path) > offset) - seq_printf(seq, ",%s", root_path + offset); + seq_show_option(seq, root_path + offset, NULL); + + if (append) + seq_puts(seq, ",append"); return 0; } static const struct super_operations hostfs_sbops = { .alloc_inode = hostfs_alloc_inode, - .destroy_inode = hostfs_destroy_inode, + .free_inode = hostfs_free_inode, + .drop_inode = inode_just_drop, .evict_inode = hostfs_evict_inode, .statfs = hostfs_statfs, .show_options = hostfs_show_options, }; -int hostfs_readdir(struct file *file, struct dir_context *ctx) +static int hostfs_readdir(struct file *file, struct dir_context *ctx) { void *dir; char *name; @@ -293,6 +283,7 @@ int hostfs_readdir(struct file *file, struct dir_context *ctx) if (dir == NULL) return -error; next = ctx->pos; + seek_dir(dir, next); while ((name = read_dir(dir, &next, &ino, &len, &type)) != NULL) { if (!dir_emit(ctx, name, len, ino, type)) break; @@ -302,13 +293,12 @@ int hostfs_readdir(struct file *file, struct dir_context *ctx) return 0; } -int hostfs_file_open(struct inode *ino, struct file *file) +static int hostfs_open(struct inode *ino, struct file *file) { - static DEFINE_MUTEX(open_mutex); char *name; - fmode_t mode = 0; + fmode_t mode; int err; - int r = 0, w = 0, fd; + int r, w, fd; mode = file->f_mode & (FMODE_READ | FMODE_WRITE); if ((mode & HOSTFS_I(ino)->mode) == mode) @@ -317,14 +307,14 @@ int hostfs_file_open(struct inode *ino, struct file *file) mode |= HOSTFS_I(ino)->mode; retry: + r = w = 0; + if (mode & FMODE_READ) r = 1; if (mode & FMODE_WRITE) - w = 1; - if (w) - r = 1; + r = w = 1; - name = dentry_name(file->f_path.dentry); + name = dentry_name(file_dentry(file)); if (name == NULL) return -ENOMEM; @@ -333,15 +323,16 @@ retry: if (fd < 0) return fd; - mutex_lock(&open_mutex); + mutex_lock(&HOSTFS_I(ino)->open_mutex); /* somebody else had handled it first? */ if ((mode & HOSTFS_I(ino)->mode) == mode) { - mutex_unlock(&open_mutex); + mutex_unlock(&HOSTFS_I(ino)->open_mutex); + close_file(&fd); return 0; } if ((mode | HOSTFS_I(ino)->mode) != mode) { mode |= HOSTFS_I(ino)->mode; - mutex_unlock(&open_mutex); + mutex_unlock(&HOSTFS_I(ino)->open_mutex); close_file(&fd); goto retry; } @@ -351,139 +342,139 @@ retry: err = replace_file(fd, HOSTFS_I(ino)->fd); close_file(&fd); if (err < 0) { - mutex_unlock(&open_mutex); + mutex_unlock(&HOSTFS_I(ino)->open_mutex); return err; } } HOSTFS_I(ino)->mode = mode; - mutex_unlock(&open_mutex); + mutex_unlock(&HOSTFS_I(ino)->open_mutex); return 0; } -int hostfs_fsync(struct file *file, loff_t start, loff_t end, int datasync) +static int hostfs_file_release(struct inode *inode, struct file *file) +{ + filemap_write_and_wait(inode->i_mapping); + + return 0; +} + +static int hostfs_fsync(struct file *file, loff_t start, loff_t end, + int datasync) { struct inode *inode = file->f_mapping->host; int ret; - ret = filemap_write_and_wait_range(inode->i_mapping, start, end); + ret = file_write_and_wait_range(file, start, end); if (ret) return ret; - mutex_lock(&inode->i_mutex); + inode_lock(inode); ret = fsync_file(HOSTFS_I(inode)->fd, datasync); - mutex_unlock(&inode->i_mutex); + inode_unlock(inode); return ret; } static const struct file_operations hostfs_file_fops = { .llseek = generic_file_llseek, - .read = do_sync_read, - .splice_read = generic_file_splice_read, - .aio_read = generic_file_aio_read, - .aio_write = generic_file_aio_write, - .write = do_sync_write, - .mmap = generic_file_mmap, - .open = hostfs_file_open, - .release = NULL, + .splice_read = filemap_splice_read, + .splice_write = iter_file_splice_write, + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, + .mmap_prepare = generic_file_mmap_prepare, + .open = hostfs_open, + .release = hostfs_file_release, .fsync = hostfs_fsync, }; static const struct file_operations hostfs_dir_fops = { .llseek = generic_file_llseek, - .iterate = hostfs_readdir, + .iterate_shared = hostfs_readdir, .read = generic_read_dir, + .open = hostfs_open, + .fsync = hostfs_fsync, }; -int hostfs_writepage(struct page *page, struct writeback_control *wbc) +static int hostfs_writepages(struct address_space *mapping, + struct writeback_control *wbc) { - struct address_space *mapping = page->mapping; struct inode *inode = mapping->host; - char *buffer; - unsigned long long base; - int count = PAGE_CACHE_SIZE; - int end_index = inode->i_size >> PAGE_CACHE_SHIFT; - int err; - - if (page->index >= end_index) - count = inode->i_size & (PAGE_CACHE_SIZE-1); - - buffer = kmap(page); - base = ((unsigned long long) page->index) << PAGE_CACHE_SHIFT; + struct folio *folio = NULL; + loff_t i_size = i_size_read(inode); + int err = 0; - err = write_file(HOSTFS_I(inode)->fd, &base, buffer, count); - if (err != count) { - ClearPageUptodate(page); - goto out; + while ((folio = writeback_iter(mapping, wbc, folio, &err))) { + loff_t pos = folio_pos(folio); + size_t count = folio_size(folio); + char *buffer; + int ret; + + if (count > i_size - pos) + count = i_size - pos; + + buffer = kmap_local_folio(folio, 0); + ret = write_file(HOSTFS_I(inode)->fd, &pos, buffer, count); + kunmap_local(buffer); + folio_unlock(folio); + if (ret != count) { + err = ret < 0 ? ret : -EIO; + mapping_set_error(mapping, err); + } } - if (base > inode->i_size) - inode->i_size = base; - - if (PageError(page)) - ClearPageError(page); - err = 0; - - out: - kunmap(page); - - unlock_page(page); return err; } -int hostfs_readpage(struct file *file, struct page *page) +static int hostfs_read_folio(struct file *file, struct folio *folio) { char *buffer; - long long start; - int err = 0; + loff_t start = folio_pos(folio); + int bytes_read, ret = 0; + + buffer = kmap_local_folio(folio, 0); + bytes_read = read_file(FILE_HOSTFS_I(file)->fd, &start, buffer, + PAGE_SIZE); + if (bytes_read < 0) + ret = bytes_read; + else + buffer = folio_zero_tail(folio, bytes_read, buffer + bytes_read); + kunmap_local(buffer); - start = (long long) page->index << PAGE_CACHE_SHIFT; - buffer = kmap(page); - err = read_file(FILE_HOSTFS_I(file)->fd, &start, buffer, - PAGE_CACHE_SIZE); - if (err < 0) - goto out; - - memset(&buffer[err], 0, PAGE_CACHE_SIZE - err); - - flush_dcache_page(page); - SetPageUptodate(page); - if (PageError(page)) ClearPageError(page); - err = 0; - out: - kunmap(page); - unlock_page(page); - return err; + folio_end_read(folio, ret == 0); + return ret; } -int hostfs_write_begin(struct file *file, struct address_space *mapping, - loff_t pos, unsigned len, unsigned flags, - struct page **pagep, void **fsdata) +static int hostfs_write_begin(const struct kiocb *iocb, + struct address_space *mapping, + loff_t pos, unsigned len, + struct folio **foliop, void **fsdata) { - pgoff_t index = pos >> PAGE_CACHE_SHIFT; + pgoff_t index = pos >> PAGE_SHIFT; - *pagep = grab_cache_page_write_begin(mapping, index, flags); - if (!*pagep) - return -ENOMEM; + *foliop = __filemap_get_folio(mapping, index, FGP_WRITEBEGIN, + mapping_gfp_mask(mapping)); + if (IS_ERR(*foliop)) + return PTR_ERR(*foliop); return 0; } -int hostfs_write_end(struct file *file, struct address_space *mapping, - loff_t pos, unsigned len, unsigned copied, - struct page *page, void *fsdata) +static int hostfs_write_end(const struct kiocb *iocb, + struct address_space *mapping, + loff_t pos, unsigned len, unsigned copied, + struct folio *folio, void *fsdata) { struct inode *inode = mapping->host; void *buffer; - unsigned from = pos & (PAGE_CACHE_SIZE - 1); + size_t from = offset_in_folio(folio, pos); int err; - buffer = kmap(page); - err = write_file(FILE_HOSTFS_I(file)->fd, &pos, buffer + from, copied); - kunmap(page); + buffer = kmap_local_folio(folio, from); + err = write_file(FILE_HOSTFS_I(iocb->ki_filp)->fd, &pos, buffer, copied); + kunmap_local(buffer); - if (!PageUptodate(page) && err == PAGE_CACHE_SIZE) - SetPageUptodate(page); + if (!folio_test_uptodate(folio) && err == folio_size(folio)) + folio_mark_uptodate(folio); /* * If err > 0, write_file has added err to pos, so we are comparing @@ -491,32 +482,50 @@ int hostfs_write_end(struct file *file, struct address_space *mapping, */ if (err > 0 && (pos > inode->i_size)) inode->i_size = pos; - unlock_page(page); - page_cache_release(page); + folio_unlock(folio); + folio_put(folio); return err; } static const struct address_space_operations hostfs_aops = { - .writepage = hostfs_writepage, - .readpage = hostfs_readpage, - .set_page_dirty = __set_page_dirty_nobuffers, + .writepages = hostfs_writepages, + .read_folio = hostfs_read_folio, + .dirty_folio = filemap_dirty_folio, .write_begin = hostfs_write_begin, .write_end = hostfs_write_end, + .migrate_folio = filemap_migrate_folio, }; -static int read_name(struct inode *ino, char *name) +static int hostfs_inode_update(struct inode *ino, const struct hostfs_stat *st) { - dev_t rdev; - struct hostfs_stat st; - int err = stat_file(name, &st, -1); - if (err) - return err; + set_nlink(ino, st->nlink); + i_uid_write(ino, st->uid); + i_gid_write(ino, st->gid); + inode_set_atime_to_ts(ino, (struct timespec64){ + st->atime.tv_sec, + st->atime.tv_nsec, + }); + inode_set_mtime_to_ts(ino, (struct timespec64){ + st->mtime.tv_sec, + st->mtime.tv_nsec, + }); + inode_set_ctime(ino, st->ctime.tv_sec, st->ctime.tv_nsec); + ino->i_size = st->size; + ino->i_blocks = st->blocks; + return 0; +} + +static int hostfs_inode_set(struct inode *ino, void *data) +{ + struct hostfs_stat *st = data; + dev_t dev, rdev; /* Reencode maj and min with the kernel encoding.*/ - rdev = MKDEV(st.maj, st.min); + rdev = MKDEV(st->rdev.maj, st->rdev.min); + dev = MKDEV(st->dev.maj, st->dev.min); - switch (st.mode & S_IFMT) { + switch (st->mode & S_IFMT) { case S_IFLNK: ino->i_op = &hostfs_link_iops; break; @@ -528,109 +537,109 @@ static int read_name(struct inode *ino, char *name) case S_IFBLK: case S_IFIFO: case S_IFSOCK: - init_special_inode(ino, st.mode & S_IFMT, rdev); + init_special_inode(ino, st->mode & S_IFMT, rdev); ino->i_op = &hostfs_iops; break; - - default: + case S_IFREG: ino->i_op = &hostfs_iops; ino->i_fop = &hostfs_file_fops; ino->i_mapping->a_ops = &hostfs_aops; + break; + default: + return -EIO; } - ino->i_ino = st.ino; - ino->i_mode = st.mode; - set_nlink(ino, st.nlink); - i_uid_write(ino, st.uid); - i_gid_write(ino, st.gid); - ino->i_atime = st.atime; - ino->i_mtime = st.mtime; - ino->i_ctime = st.ctime; - ino->i_size = st.size; - ino->i_blocks = st.blocks; - return 0; + HOSTFS_I(ino)->dev = dev; + HOSTFS_I(ino)->btime = st->btime; + ino->i_ino = st->ino; + ino->i_mode = st->mode; + return hostfs_inode_update(ino, st); +} + +static int hostfs_inode_test(struct inode *inode, void *data) +{ + const struct hostfs_stat *st = data; + dev_t dev = MKDEV(st->dev.maj, st->dev.min); + + return inode->i_ino == st->ino && HOSTFS_I(inode)->dev == dev && + (inode->i_mode & S_IFMT) == (st->mode & S_IFMT) && + HOSTFS_I(inode)->btime.tv_sec == st->btime.tv_sec && + HOSTFS_I(inode)->btime.tv_nsec == st->btime.tv_nsec; } -int hostfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, - bool excl) +static struct inode *hostfs_iget(struct super_block *sb, char *name) { struct inode *inode; - char *name; - int error, fd; + struct hostfs_stat st; + int err = stat_file(name, &st, -1); - inode = hostfs_iget(dir->i_sb); - if (IS_ERR(inode)) { - error = PTR_ERR(inode); - goto out; + if (err) + return ERR_PTR(err); + + inode = iget5_locked(sb, st.ino, hostfs_inode_test, hostfs_inode_set, + &st); + if (!inode) + return ERR_PTR(-ENOMEM); + + if (inode_state_read_once(inode) & I_NEW) { + unlock_new_inode(inode); + } else { + spin_lock(&inode->i_lock); + hostfs_inode_update(inode, &st); + spin_unlock(&inode->i_lock); } - error = -ENOMEM; + return inode; +} + +static int hostfs_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) +{ + struct inode *inode; + char *name; + int fd; + name = dentry_name(dentry); if (name == NULL) - goto out_put; + return -ENOMEM; - fd = file_create(name, - mode & S_IRUSR, mode & S_IWUSR, mode & S_IXUSR, - mode & S_IRGRP, mode & S_IWGRP, mode & S_IXGRP, - mode & S_IROTH, mode & S_IWOTH, mode & S_IXOTH); - if (fd < 0) - error = fd; - else - error = read_name(inode, name); + fd = file_create(name, mode & 0777); + if (fd < 0) { + __putname(name); + return fd; + } + inode = hostfs_iget(dir->i_sb, name); __putname(name); - if (error) - goto out_put; + if (IS_ERR(inode)) + return PTR_ERR(inode); HOSTFS_I(inode)->fd = fd; HOSTFS_I(inode)->mode = FMODE_READ | FMODE_WRITE; d_instantiate(dentry, inode); return 0; - - out_put: - iput(inode); - out: - return error; } -struct dentry *hostfs_lookup(struct inode *ino, struct dentry *dentry, - unsigned int flags) +static struct dentry *hostfs_lookup(struct inode *ino, struct dentry *dentry, + unsigned int flags) { - struct inode *inode; + struct inode *inode = NULL; char *name; - int err; - inode = hostfs_iget(ino->i_sb); - if (IS_ERR(inode)) { - err = PTR_ERR(inode); - goto out; - } - - err = -ENOMEM; name = dentry_name(dentry); if (name == NULL) - goto out_put; - - err = read_name(inode, name); + return ERR_PTR(-ENOMEM); + inode = hostfs_iget(ino->i_sb, name); __putname(name); - if (err == -ENOENT) { - iput(inode); + if (inode == ERR_PTR(-ENOENT)) inode = NULL; - } - else if (err) - goto out_put; - - d_add(dentry, inode); - return NULL; - out_put: - iput(inode); - out: - return ERR_PTR(err); + return d_splice_alias(inode, dentry); } -int hostfs_link(struct dentry *to, struct inode *ino, struct dentry *from) +static int hostfs_link(struct dentry *to, struct inode *ino, + struct dentry *from) { char *from_name, *to_name; int err; @@ -648,7 +657,7 @@ int hostfs_link(struct dentry *to, struct inode *ino, struct dentry *from) return err; } -int hostfs_unlink(struct inode *ino, struct dentry *dentry) +static int hostfs_unlink(struct inode *ino, struct dentry *dentry) { char *file; int err; @@ -664,7 +673,8 @@ int hostfs_unlink(struct inode *ino, struct dentry *dentry) return err; } -int hostfs_symlink(struct inode *ino, struct dentry *dentry, const char *to) +static int hostfs_symlink(struct mnt_idmap *idmap, struct inode *ino, + struct dentry *dentry, const char *to) { char *file; int err; @@ -676,89 +686,96 @@ int hostfs_symlink(struct inode *ino, struct dentry *dentry, const char *to) return err; } -int hostfs_mkdir(struct inode *ino, struct dentry *dentry, umode_t mode) +static struct dentry *hostfs_mkdir(struct mnt_idmap *idmap, struct inode *ino, + struct dentry *dentry, umode_t mode) { + struct inode *inode; char *file; int err; if ((file = dentry_name(dentry)) == NULL) - return -ENOMEM; + return ERR_PTR(-ENOMEM); err = do_mkdir(file, mode); + if (err) { + dentry = ERR_PTR(err); + } else { + inode = hostfs_iget(dentry->d_sb, file); + d_drop(dentry); + dentry = d_splice_alias(inode, dentry); + } __putname(file); - return err; + return dentry; } -int hostfs_rmdir(struct inode *ino, struct dentry *dentry) +static int hostfs_rmdir(struct inode *ino, struct dentry *dentry) { char *file; int err; if ((file = dentry_name(dentry)) == NULL) return -ENOMEM; - err = do_rmdir(file); + err = hostfs_do_rmdir(file); __putname(file); return err; } -static int hostfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) +static int hostfs_mknod(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev) { struct inode *inode; char *name; int err; - inode = hostfs_iget(dir->i_sb); - if (IS_ERR(inode)) { - err = PTR_ERR(inode); - goto out; - } - - err = -ENOMEM; name = dentry_name(dentry); if (name == NULL) - goto out_put; + return -ENOMEM; - init_special_inode(inode, mode, dev); err = do_mknod(name, mode, MAJOR(dev), MINOR(dev)); - if (!err) - goto out_free; + if (err) { + __putname(name); + return err; + } - err = read_name(inode, name); + inode = hostfs_iget(dir->i_sb, name); __putname(name); - if (err) - goto out_put; - if (err) - goto out_put; + if (IS_ERR(inode)) + return PTR_ERR(inode); d_instantiate(dentry, inode); return 0; - - out_free: - __putname(name); - out_put: - iput(inode); - out: - return err; } -int hostfs_rename(struct inode *from_ino, struct dentry *from, - struct inode *to_ino, struct dentry *to) +static int hostfs_rename2(struct mnt_idmap *idmap, + struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { - char *from_name, *to_name; + char *old_name, *new_name; int err; - if ((from_name = dentry_name(from)) == NULL) + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE)) + return -EINVAL; + + old_name = dentry_name(old_dentry); + if (old_name == NULL) return -ENOMEM; - if ((to_name = dentry_name(to)) == NULL) { - __putname(from_name); + new_name = dentry_name(new_dentry); + if (new_name == NULL) { + __putname(old_name); return -ENOMEM; } - err = rename_file(from_name, to_name); - __putname(from_name); - __putname(to_name); + if (!flags) + err = rename_file(old_name, new_name); + else + err = rename2_file(old_name, new_name, flags); + + __putname(old_name); + __putname(new_name); return err; } -int hostfs_permission(struct inode *ino, int desired) +static int hostfs_permission(struct mnt_idmap *idmap, + struct inode *ino, int desired) { char *name; int r = 0, w = 0, x = 0, err; @@ -780,20 +797,21 @@ int hostfs_permission(struct inode *ino, int desired) err = access_file(name, r, w, x); __putname(name); if (!err) - err = generic_permission(ino, desired); + err = generic_permission(&nop_mnt_idmap, ino, desired); return err; } -int hostfs_setattr(struct dentry *dentry, struct iattr *attr) +static int hostfs_setattr(struct mnt_idmap *idmap, + struct dentry *dentry, struct iattr *attr) { - struct inode *inode = dentry->d_inode; + struct inode *inode = d_inode(dentry); struct hostfs_iattr attrs; char *name; int err; int fd = HOSTFS_I(inode)->fd; - err = inode_change_ok(inode, attr); + err = setattr_prepare(&nop_mnt_idmap, dentry, attr); if (err) return err; @@ -819,15 +837,18 @@ int hostfs_setattr(struct dentry *dentry, struct iattr *attr) } if (attr->ia_valid & ATTR_ATIME) { attrs.ia_valid |= HOSTFS_ATTR_ATIME; - attrs.ia_atime = attr->ia_atime; + attrs.ia_atime = (struct hostfs_timespec) + { attr->ia_atime.tv_sec, attr->ia_atime.tv_nsec }; } if (attr->ia_valid & ATTR_MTIME) { attrs.ia_valid |= HOSTFS_ATTR_MTIME; - attrs.ia_mtime = attr->ia_mtime; + attrs.ia_mtime = (struct hostfs_timespec) + { attr->ia_mtime.tv_sec, attr->ia_mtime.tv_nsec }; } if (attr->ia_valid & ATTR_CTIME) { attrs.ia_valid |= HOSTFS_ATTR_CTIME; - attrs.ia_ctime = attr->ia_ctime; + attrs.ia_ctime = (struct hostfs_timespec) + { attr->ia_ctime.tv_sec, attr->ia_ctime.tv_nsec }; } if (attr->ia_valid & ATTR_ATIME_SET) { attrs.ia_valid |= HOSTFS_ATTR_ATIME_SET; @@ -847,7 +868,7 @@ int hostfs_setattr(struct dentry *dentry, struct iattr *attr) attr->ia_size != i_size_read(inode)) truncate_setsize(inode, attr->ia_size); - setattr_copy(inode, attr); + setattr_copy(&nop_mnt_idmap, inode, attr); mark_inode_dirty(inode); return 0; } @@ -866,14 +887,19 @@ static const struct inode_operations hostfs_dir_iops = { .mkdir = hostfs_mkdir, .rmdir = hostfs_rmdir, .mknod = hostfs_mknod, - .rename = hostfs_rename, + .rename = hostfs_rename2, .permission = hostfs_permission, .setattr = hostfs_setattr, }; -static void *hostfs_follow_link(struct dentry *dentry, struct nameidata *nd) +static const char *hostfs_get_link(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) { - char *link = __getname(); + char *link; + if (!dentry) + return ERR_PTR(-ECHILD); + link = kmalloc(PATH_MAX, GFP_KERNEL); if (link) { char *path = dentry_name(dentry); int err = -ENOMEM; @@ -884,92 +910,155 @@ static void *hostfs_follow_link(struct dentry *dentry, struct nameidata *nd) __putname(path); } if (err < 0) { - __putname(link); - link = ERR_PTR(err); + kfree(link); + return ERR_PTR(err); } } else { - link = ERR_PTR(-ENOMEM); + return ERR_PTR(-ENOMEM); } - nd_set_link(nd, link); - return NULL; -} - -static void hostfs_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) -{ - char *s = nd_get_link(nd); - if (!IS_ERR(s)) - __putname(s); + set_delayed_call(done, kfree_link, link); + return link; } static const struct inode_operations hostfs_link_iops = { - .readlink = generic_readlink, - .follow_link = hostfs_follow_link, - .put_link = hostfs_put_link, + .get_link = hostfs_get_link, }; -static int hostfs_fill_sb_common(struct super_block *sb, void *d, int silent) +static int hostfs_fill_super(struct super_block *sb, struct fs_context *fc) { + struct hostfs_fs_info *fsi = sb->s_fs_info; struct inode *root_inode; - char *host_root_path, *req_root = d; int err; sb->s_blocksize = 1024; sb->s_blocksize_bits = 10; sb->s_magic = HOSTFS_SUPER_MAGIC; sb->s_op = &hostfs_sbops; - sb->s_d_op = &hostfs_dentry_ops; + sb->s_d_flags = DCACHE_DONTCACHE; sb->s_maxbytes = MAX_LFS_FILESIZE; - - /* NULL is printed as <NULL> by sprintf: avoid that. */ - if (req_root == NULL) - req_root = ""; - - err = -ENOMEM; - sb->s_fs_info = host_root_path = - kmalloc(strlen(root_ino) + strlen(req_root) + 2, GFP_KERNEL); - if (host_root_path == NULL) - goto out; - - sprintf(host_root_path, "%s/%s", root_ino, req_root); - - root_inode = new_inode(sb); - if (!root_inode) - goto out; - - err = read_name(root_inode, host_root_path); + err = super_setup_bdi(sb); if (err) - goto out_put; + return err; + + root_inode = hostfs_iget(sb, fsi->host_root_path); + if (IS_ERR(root_inode)) + return PTR_ERR(root_inode); if (S_ISLNK(root_inode->i_mode)) { - char *name = follow_link(host_root_path); + char *name; + + iput(root_inode); + name = follow_link(fsi->host_root_path); if (IS_ERR(name)) - err = PTR_ERR(name); - else - err = read_name(root_inode, name); + return PTR_ERR(name); + + root_inode = hostfs_iget(sb, name); kfree(name); - if (err) - goto out_put; + if (IS_ERR(root_inode)) + return PTR_ERR(root_inode); } - err = -ENOMEM; sb->s_root = d_make_root(root_inode); if (sb->s_root == NULL) - goto out; + return -ENOMEM; return 0; +} -out_put: - iput(root_inode); -out: - return err; +enum hostfs_parma { + Opt_hostfs, +}; + +static const struct fs_parameter_spec hostfs_param_specs[] = { + fsparam_string_empty("hostfs", Opt_hostfs), + {} +}; + +static int hostfs_parse_param(struct fs_context *fc, struct fs_parameter *param) +{ + struct hostfs_fs_info *fsi = fc->s_fs_info; + struct fs_parse_result result; + char *host_root, *tmp_root; + int opt; + + opt = fs_parse(fc, hostfs_param_specs, param, &result); + if (opt < 0) + return opt; + + switch (opt) { + case Opt_hostfs: + host_root = param->string; + if (!*host_root) + break; + tmp_root = kasprintf(GFP_KERNEL, "%s%s", + fsi->host_root_path, host_root); + if (!tmp_root) + return -ENOMEM; + kfree(fsi->host_root_path); + fsi->host_root_path = tmp_root; + break; + } + + return 0; +} + +static int hostfs_parse_monolithic(struct fs_context *fc, void *data) +{ + struct hostfs_fs_info *fsi = fc->s_fs_info; + char *tmp_root, *host_root = (char *)data; + + /* NULL is printed as '(null)' by printf(): avoid that. */ + if (host_root == NULL) + return 0; + + tmp_root = kasprintf(GFP_KERNEL, "%s%s", fsi->host_root_path, host_root); + if (!tmp_root) + return -ENOMEM; + kfree(fsi->host_root_path); + fsi->host_root_path = tmp_root; + return 0; } -static struct dentry *hostfs_read_sb(struct file_system_type *type, - int flags, const char *dev_name, - void *data) +static int hostfs_fc_get_tree(struct fs_context *fc) { - return mount_nodev(type, flags, data, hostfs_fill_sb_common); + return get_tree_nodev(fc, hostfs_fill_super); +} + +static void hostfs_fc_free(struct fs_context *fc) +{ + struct hostfs_fs_info *fsi = fc->s_fs_info; + + if (!fsi) + return; + + kfree(fsi->host_root_path); + kfree(fsi); +} + +static const struct fs_context_operations hostfs_context_ops = { + .parse_monolithic = hostfs_parse_monolithic, + .parse_param = hostfs_parse_param, + .get_tree = hostfs_fc_get_tree, + .free = hostfs_fc_free, +}; + +static int hostfs_init_fs_context(struct fs_context *fc) +{ + struct hostfs_fs_info *fsi; + + fsi = kzalloc(sizeof(*fsi), GFP_KERNEL); + if (!fsi) + return -ENOMEM; + + fsi->host_root_path = kasprintf(GFP_KERNEL, "%s/", root_ino); + if (!fsi->host_root_path) { + kfree(fsi); + return -ENOMEM; + } + fc->s_fs_info = fsi; + fc->ops = &hostfs_context_ops; + return 0; } static void hostfs_kill_sb(struct super_block *s) @@ -979,24 +1068,29 @@ static void hostfs_kill_sb(struct super_block *s) } static struct file_system_type hostfs_type = { - .owner = THIS_MODULE, - .name = "hostfs", - .mount = hostfs_read_sb, - .kill_sb = hostfs_kill_sb, - .fs_flags = 0, + .owner = THIS_MODULE, + .name = "hostfs", + .init_fs_context = hostfs_init_fs_context, + .kill_sb = hostfs_kill_sb, + .fs_flags = 0, }; MODULE_ALIAS_FS("hostfs"); static int __init init_hostfs(void) { + hostfs_inode_cache = KMEM_CACHE(hostfs_inode_info, 0); + if (!hostfs_inode_cache) + return -ENOMEM; return register_filesystem(&hostfs_type); } static void __exit exit_hostfs(void) { unregister_filesystem(&hostfs_type); + kmem_cache_destroy(hostfs_inode_cache); } module_init(init_hostfs) module_exit(exit_hostfs) +MODULE_DESCRIPTION("User-Mode Linux Host filesystem"); MODULE_LICENSE("GPL"); |
