| /* |
| * Copyright (C) 2005-2013 Junjiro R. Okajima |
| * |
| * This program, aufs 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; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| /* |
| * external inode number translation table and bitmap |
| */ |
| |
| #include <linux/seq_file.h> |
| #include "aufs.h" |
| |
| /* todo: unnecessary to support mmap_sem since kernel-space? */ |
| ssize_t xino_fread(au_readf_t func, struct file *file, void *kbuf, size_t size, |
| loff_t *pos) |
| { |
| ssize_t err; |
| mm_segment_t oldfs; |
| union { |
| void *k; |
| char __user *u; |
| } buf; |
| |
| buf.k = kbuf; |
| oldfs = get_fs(); |
| set_fs(KERNEL_DS); |
| do { |
| /* todo: signal_pending? */ |
| err = func(file, buf.u, size, pos); |
| } while (err == -EAGAIN || err == -EINTR); |
| set_fs(oldfs); |
| |
| #if 0 /* reserved for future use */ |
| if (err > 0) |
| fsnotify_access(file->f_dentry); |
| #endif |
| |
| return err; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| static ssize_t do_xino_fwrite(au_writef_t func, struct file *file, void *kbuf, |
| size_t size, loff_t *pos) |
| { |
| ssize_t err; |
| mm_segment_t oldfs; |
| union { |
| void *k; |
| const char __user *u; |
| } buf; |
| |
| buf.k = kbuf; |
| oldfs = get_fs(); |
| set_fs(KERNEL_DS); |
| do { |
| /* todo: signal_pending? */ |
| err = func(file, buf.u, size, pos); |
| } while (err == -EAGAIN || err == -EINTR); |
| set_fs(oldfs); |
| |
| #if 0 /* reserved for future use */ |
| if (err > 0) |
| fsnotify_modify(file->f_dentry); |
| #endif |
| |
| return err; |
| } |
| |
| struct do_xino_fwrite_args { |
| ssize_t *errp; |
| au_writef_t func; |
| struct file *file; |
| void *buf; |
| size_t size; |
| loff_t *pos; |
| }; |
| |
| static void call_do_xino_fwrite(void *args) |
| { |
| struct do_xino_fwrite_args *a = args; |
| *a->errp = do_xino_fwrite(a->func, a->file, a->buf, a->size, a->pos); |
| } |
| |
| ssize_t xino_fwrite(au_writef_t func, struct file *file, void *buf, size_t size, |
| loff_t *pos) |
| { |
| ssize_t err; |
| |
| /* todo: signal block and no wkq? */ |
| if (rlimit(RLIMIT_FSIZE) == RLIM_INFINITY) { |
| lockdep_off(); |
| err = do_xino_fwrite(func, file, buf, size, pos); |
| lockdep_on(); |
| } else { |
| /* |
| * it breaks RLIMIT_FSIZE and normal user's limit, |
| * users should care about quota and real 'filesystem full.' |
| */ |
| int wkq_err; |
| struct do_xino_fwrite_args args = { |
| .errp = &err, |
| .func = func, |
| .file = file, |
| .buf = buf, |
| .size = size, |
| .pos = pos |
| }; |
| |
| wkq_err = au_wkq_wait(call_do_xino_fwrite, &args); |
| if (unlikely(wkq_err)) |
| err = wkq_err; |
| } |
| |
| return err; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| /* |
| * create a new xinofile at the same place/path as @base_file. |
| */ |
| struct file *au_xino_create2(struct file *base_file, struct file *copy_src) |
| { |
| struct file *file; |
| struct dentry *base, *parent; |
| struct inode *dir; |
| struct qstr *name; |
| struct path path; |
| int err; |
| |
| base = base_file->f_dentry; |
| parent = base->d_parent; /* dir inode is locked */ |
| dir = parent->d_inode; |
| IMustLock(dir); |
| |
| file = ERR_PTR(-EINVAL); |
| name = &base->d_name; |
| path.dentry = vfsub_lookup_one_len(name->name, parent, name->len); |
| if (IS_ERR(path.dentry)) { |
| file = (void *)path.dentry; |
| pr_err("%.*s lookup err %ld\n", |
| AuLNPair(name), PTR_ERR(path.dentry)); |
| goto out; |
| } |
| |
| /* no need to mnt_want_write() since we call dentry_open() later */ |
| err = vfs_create(dir, path.dentry, S_IRUGO | S_IWUGO, NULL); |
| if (unlikely(err)) { |
| file = ERR_PTR(err); |
| pr_err("%.*s create err %d\n", AuLNPair(name), err); |
| goto out_dput; |
| } |
| |
| path.mnt = base_file->f_path.mnt; |
| file = vfsub_dentry_open(&path, |
| O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE |
| /* | __FMODE_NONOTIFY */); |
| if (IS_ERR(file)) { |
| pr_err("%.*s open err %ld\n", AuLNPair(name), PTR_ERR(file)); |
| goto out_dput; |
| } |
| |
| err = vfsub_unlink(dir, &file->f_path, /*force*/0); |
| if (unlikely(err)) { |
| pr_err("%.*s unlink err %d\n", AuLNPair(name), err); |
| goto out_fput; |
| } |
| |
| if (copy_src) { |
| /* no one can touch copy_src xino */ |
| err = au_copy_file(file, copy_src, vfsub_f_size_read(copy_src)); |
| if (unlikely(err)) { |
| pr_err("%.*s copy err %d\n", AuLNPair(name), err); |
| goto out_fput; |
| } |
| } |
| goto out_dput; /* success */ |
| |
| out_fput: |
| fput(file); |
| file = ERR_PTR(err); |
| out_dput: |
| dput(path.dentry); |
| out: |
| return file; |
| } |
| |
| struct au_xino_lock_dir { |
| struct au_hinode *hdir; |
| struct dentry *parent; |
| struct mutex *mtx; |
| }; |
| |
| static void au_xino_lock_dir(struct super_block *sb, struct file *xino, |
| struct au_xino_lock_dir *ldir) |
| { |
| aufs_bindex_t brid, bindex; |
| |
| ldir->hdir = NULL; |
| bindex = -1; |
| brid = au_xino_brid(sb); |
| if (brid >= 0) |
| bindex = au_br_index(sb, brid); |
| if (bindex >= 0) { |
| ldir->hdir = au_hi(sb->s_root->d_inode, bindex); |
| au_hn_imtx_lock_nested(ldir->hdir, AuLsc_I_PARENT); |
| } else { |
| ldir->parent = dget_parent(xino->f_dentry); |
| ldir->mtx = &ldir->parent->d_inode->i_mutex; |
| mutex_lock_nested(ldir->mtx, AuLsc_I_PARENT); |
| } |
| } |
| |
| static void au_xino_unlock_dir(struct au_xino_lock_dir *ldir) |
| { |
| if (ldir->hdir) |
| au_hn_imtx_unlock(ldir->hdir); |
| else { |
| mutex_unlock(ldir->mtx); |
| dput(ldir->parent); |
| } |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| /* trucate xino files asynchronously */ |
| |
| int au_xino_trunc(struct super_block *sb, aufs_bindex_t bindex) |
| { |
| int err; |
| aufs_bindex_t bi, bend; |
| struct au_branch *br; |
| struct file *new_xino, *file; |
| struct super_block *h_sb; |
| struct au_xino_lock_dir ldir; |
| |
| err = -EINVAL; |
| bend = au_sbend(sb); |
| if (unlikely(bindex < 0 || bend < bindex)) |
| goto out; |
| br = au_sbr(sb, bindex); |
| file = br->br_xino.xi_file; |
| if (!file) |
| goto out; |
| |
| au_xino_lock_dir(sb, file, &ldir); |
| /* mnt_want_write() is unnecessary here */ |
| new_xino = au_xino_create2(file, file); |
| au_xino_unlock_dir(&ldir); |
| err = PTR_ERR(new_xino); |
| if (IS_ERR(new_xino)) |
| goto out; |
| err = 0; |
| fput(file); |
| br->br_xino.xi_file = new_xino; |
| |
| h_sb = au_br_sb(br); |
| for (bi = 0; bi <= bend; bi++) { |
| if (unlikely(bi == bindex)) |
| continue; |
| br = au_sbr(sb, bi); |
| if (au_br_sb(br) != h_sb) |
| continue; |
| |
| fput(br->br_xino.xi_file); |
| br->br_xino.xi_file = new_xino; |
| get_file(new_xino); |
| } |
| |
| out: |
| return err; |
| } |
| |
| struct xino_do_trunc_args { |
| struct super_block *sb; |
| struct au_branch *br; |
| }; |
| |
| static void xino_do_trunc(void *_args) |
| { |
| struct xino_do_trunc_args *args = _args; |
| struct super_block *sb; |
| struct au_branch *br; |
| struct inode *dir; |
| int err; |
| aufs_bindex_t bindex; |
| |
| err = 0; |
| sb = args->sb; |
| dir = sb->s_root->d_inode; |
| br = args->br; |
| |
| si_noflush_write_lock(sb); |
| ii_read_lock_parent(dir); |
| bindex = au_br_index(sb, br->br_id); |
| err = au_xino_trunc(sb, bindex); |
| if (!err |
| && file_inode(br->br_xino.xi_file)->i_blocks |
| >= br->br_xino_upper) |
| br->br_xino_upper += AUFS_XINO_TRUNC_STEP; |
| |
| ii_read_unlock(dir); |
| if (unlikely(err)) |
| pr_warn("err b%d, upper %llu, (%d)\n", |
| bindex, (unsigned long long)br->br_xino_upper, err); |
| atomic_dec(&br->br_xino_running); |
| atomic_dec(&br->br_count); |
| si_write_unlock(sb); |
| au_nwt_done(&au_sbi(sb)->si_nowait); |
| kfree(args); |
| } |
| |
| static void xino_try_trunc(struct super_block *sb, struct au_branch *br) |
| { |
| struct xino_do_trunc_args *args; |
| int wkq_err; |
| |
| if (file_inode(br->br_xino.xi_file)->i_blocks |
| < br->br_xino_upper) |
| return; |
| |
| if (atomic_inc_return(&br->br_xino_running) > 1) |
| goto out; |
| |
| /* lock and kfree() will be called in trunc_xino() */ |
| args = kmalloc(sizeof(*args), GFP_NOFS); |
| if (unlikely(!args)) { |
| AuErr1("no memory\n"); |
| goto out_args; |
| } |
| |
| atomic_inc(&br->br_count); |
| args->sb = sb; |
| args->br = br; |
| wkq_err = au_wkq_nowait(xino_do_trunc, args, sb, /*flags*/0); |
| if (!wkq_err) |
| return; /* success */ |
| |
| pr_err("wkq %d\n", wkq_err); |
| atomic_dec(&br->br_count); |
| |
| out_args: |
| kfree(args); |
| out: |
| atomic_dec(&br->br_xino_running); |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| static int au_xino_do_write(au_writef_t write, struct file *file, |
| ino_t h_ino, ino_t ino) |
| { |
| loff_t pos; |
| ssize_t sz; |
| |
| pos = h_ino; |
| if (unlikely(au_loff_max / sizeof(ino) - 1 < pos)) { |
| AuIOErr1("too large hi%lu\n", (unsigned long)h_ino); |
| return -EFBIG; |
| } |
| pos *= sizeof(ino); |
| sz = xino_fwrite(write, file, &ino, sizeof(ino), &pos); |
| if (sz == sizeof(ino)) |
| return 0; /* success */ |
| |
| AuIOErr("write failed (%zd)\n", sz); |
| return -EIO; |
| } |
| |
| /* |
| * write @ino to the xinofile for the specified branch{@sb, @bindex} |
| * at the position of @h_ino. |
| * even if @ino is zero, it is written to the xinofile and means no entry. |
| * if the size of the xino file on a specific filesystem exceeds the watermark, |
| * try truncating it. |
| */ |
| int au_xino_write(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, |
| ino_t ino) |
| { |
| int err; |
| unsigned int mnt_flags; |
| struct au_branch *br; |
| |
| BUILD_BUG_ON(sizeof(long long) != sizeof(au_loff_max) |
| || ((loff_t)-1) > 0); |
| SiMustAnyLock(sb); |
| |
| mnt_flags = au_mntflags(sb); |
| if (!au_opt_test(mnt_flags, XINO)) |
| return 0; |
| |
| br = au_sbr(sb, bindex); |
| err = au_xino_do_write(au_sbi(sb)->si_xwrite, br->br_xino.xi_file, |
| h_ino, ino); |
| if (!err) { |
| if (au_opt_test(mnt_flags, TRUNC_XINO) |
| && au_test_fs_trunc_xino(au_br_sb(br))) |
| xino_try_trunc(sb, br); |
| return 0; /* success */ |
| } |
| |
| AuIOErr("write failed (%d)\n", err); |
| return -EIO; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| /* aufs inode number bitmap */ |
| |
| static const int page_bits = (int)PAGE_SIZE * BITS_PER_BYTE; |
| static ino_t xib_calc_ino(unsigned long pindex, int bit) |
| { |
| ino_t ino; |
| |
| AuDebugOn(bit < 0 || page_bits <= bit); |
| ino = AUFS_FIRST_INO + pindex * page_bits + bit; |
| return ino; |
| } |
| |
| static void xib_calc_bit(ino_t ino, unsigned long *pindex, int *bit) |
| { |
| AuDebugOn(ino < AUFS_FIRST_INO); |
| ino -= AUFS_FIRST_INO; |
| *pindex = ino / page_bits; |
| *bit = ino % page_bits; |
| } |
| |
| static int xib_pindex(struct super_block *sb, unsigned long pindex) |
| { |
| int err; |
| loff_t pos; |
| ssize_t sz; |
| struct au_sbinfo *sbinfo; |
| struct file *xib; |
| unsigned long *p; |
| |
| sbinfo = au_sbi(sb); |
| MtxMustLock(&sbinfo->si_xib_mtx); |
| AuDebugOn(pindex > ULONG_MAX / PAGE_SIZE |
| || !au_opt_test(sbinfo->si_mntflags, XINO)); |
| |
| if (pindex == sbinfo->si_xib_last_pindex) |
| return 0; |
| |
| xib = sbinfo->si_xib; |
| p = sbinfo->si_xib_buf; |
| pos = sbinfo->si_xib_last_pindex; |
| pos *= PAGE_SIZE; |
| sz = xino_fwrite(sbinfo->si_xwrite, xib, p, PAGE_SIZE, &pos); |
| if (unlikely(sz != PAGE_SIZE)) |
| goto out; |
| |
| pos = pindex; |
| pos *= PAGE_SIZE; |
| if (vfsub_f_size_read(xib) >= pos + PAGE_SIZE) |
| sz = xino_fread(sbinfo->si_xread, xib, p, PAGE_SIZE, &pos); |
| else { |
| memset(p, 0, PAGE_SIZE); |
| sz = xino_fwrite(sbinfo->si_xwrite, xib, p, PAGE_SIZE, &pos); |
| } |
| if (sz == PAGE_SIZE) { |
| sbinfo->si_xib_last_pindex = pindex; |
| return 0; /* success */ |
| } |
| |
| out: |
| AuIOErr1("write failed (%zd)\n", sz); |
| err = sz; |
| if (sz >= 0) |
| err = -EIO; |
| return err; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| static void au_xib_clear_bit(struct inode *inode) |
| { |
| int err, bit; |
| unsigned long pindex; |
| struct super_block *sb; |
| struct au_sbinfo *sbinfo; |
| |
| AuDebugOn(inode->i_nlink); |
| |
| sb = inode->i_sb; |
| xib_calc_bit(inode->i_ino, &pindex, &bit); |
| AuDebugOn(page_bits <= bit); |
| sbinfo = au_sbi(sb); |
| mutex_lock(&sbinfo->si_xib_mtx); |
| err = xib_pindex(sb, pindex); |
| if (!err) { |
| clear_bit(bit, sbinfo->si_xib_buf); |
| sbinfo->si_xib_next_bit = bit; |
| } |
| mutex_unlock(&sbinfo->si_xib_mtx); |
| } |
| |
| /* for s_op->delete_inode() */ |
| void au_xino_delete_inode(struct inode *inode, const int unlinked) |
| { |
| int err; |
| unsigned int mnt_flags; |
| aufs_bindex_t bindex, bend, bi; |
| unsigned char try_trunc; |
| struct au_iinfo *iinfo; |
| struct super_block *sb; |
| struct au_hinode *hi; |
| struct inode *h_inode; |
| struct au_branch *br; |
| au_writef_t xwrite; |
| |
| sb = inode->i_sb; |
| mnt_flags = au_mntflags(sb); |
| if (!au_opt_test(mnt_flags, XINO) |
| || inode->i_ino == AUFS_ROOT_INO) |
| return; |
| |
| if (unlinked) { |
| au_xigen_inc(inode); |
| au_xib_clear_bit(inode); |
| } |
| |
| iinfo = au_ii(inode); |
| if (!iinfo) |
| return; |
| |
| bindex = iinfo->ii_bstart; |
| if (bindex < 0) |
| return; |
| |
| xwrite = au_sbi(sb)->si_xwrite; |
| try_trunc = !!au_opt_test(mnt_flags, TRUNC_XINO); |
| hi = iinfo->ii_hinode + bindex; |
| bend = iinfo->ii_bend; |
| for (; bindex <= bend; bindex++, hi++) { |
| h_inode = hi->hi_inode; |
| if (!h_inode |
| || (!unlinked && h_inode->i_nlink)) |
| continue; |
| |
| /* inode may not be revalidated */ |
| bi = au_br_index(sb, hi->hi_id); |
| if (bi < 0) |
| continue; |
| |
| br = au_sbr(sb, bi); |
| err = au_xino_do_write(xwrite, br->br_xino.xi_file, |
| h_inode->i_ino, /*ino*/0); |
| if (!err && try_trunc |
| && au_test_fs_trunc_xino(au_br_sb(br))) |
| xino_try_trunc(sb, br); |
| } |
| } |
| |
| /* get an unused inode number from bitmap */ |
| ino_t au_xino_new_ino(struct super_block *sb) |
| { |
| ino_t ino; |
| unsigned long *p, pindex, ul, pend; |
| struct au_sbinfo *sbinfo; |
| struct file *file; |
| int free_bit, err; |
| |
| if (!au_opt_test(au_mntflags(sb), XINO)) |
| return iunique(sb, AUFS_FIRST_INO); |
| |
| sbinfo = au_sbi(sb); |
| mutex_lock(&sbinfo->si_xib_mtx); |
| p = sbinfo->si_xib_buf; |
| free_bit = sbinfo->si_xib_next_bit; |
| if (free_bit < page_bits && !test_bit(free_bit, p)) |
| goto out; /* success */ |
| free_bit = find_first_zero_bit(p, page_bits); |
| if (free_bit < page_bits) |
| goto out; /* success */ |
| |
| pindex = sbinfo->si_xib_last_pindex; |
| for (ul = pindex - 1; ul < ULONG_MAX; ul--) { |
| err = xib_pindex(sb, ul); |
| if (unlikely(err)) |
| goto out_err; |
| free_bit = find_first_zero_bit(p, page_bits); |
| if (free_bit < page_bits) |
| goto out; /* success */ |
| } |
| |
| file = sbinfo->si_xib; |
| pend = vfsub_f_size_read(file) / PAGE_SIZE; |
| for (ul = pindex + 1; ul <= pend; ul++) { |
| err = xib_pindex(sb, ul); |
| if (unlikely(err)) |
| goto out_err; |
| free_bit = find_first_zero_bit(p, page_bits); |
| if (free_bit < page_bits) |
| goto out; /* success */ |
| } |
| BUG(); |
| |
| out: |
| set_bit(free_bit, p); |
| sbinfo->si_xib_next_bit = free_bit + 1; |
| pindex = sbinfo->si_xib_last_pindex; |
| mutex_unlock(&sbinfo->si_xib_mtx); |
| ino = xib_calc_ino(pindex, free_bit); |
| AuDbg("i%lu\n", (unsigned long)ino); |
| return ino; |
| out_err: |
| mutex_unlock(&sbinfo->si_xib_mtx); |
| AuDbg("i0\n"); |
| return 0; |
| } |
| |
| /* |
| * read @ino from xinofile for the specified branch{@sb, @bindex} |
| * at the position of @h_ino. |
| * if @ino does not exist and @do_new is true, get new one. |
| */ |
| int au_xino_read(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, |
| ino_t *ino) |
| { |
| int err; |
| ssize_t sz; |
| loff_t pos; |
| struct file *file; |
| struct au_sbinfo *sbinfo; |
| |
| *ino = 0; |
| if (!au_opt_test(au_mntflags(sb), XINO)) |
| return 0; /* no xino */ |
| |
| err = 0; |
| sbinfo = au_sbi(sb); |
| pos = h_ino; |
| if (unlikely(au_loff_max / sizeof(*ino) - 1 < pos)) { |
| AuIOErr1("too large hi%lu\n", (unsigned long)h_ino); |
| return -EFBIG; |
| } |
| pos *= sizeof(*ino); |
| |
| file = au_sbr(sb, bindex)->br_xino.xi_file; |
| if (vfsub_f_size_read(file) < pos + sizeof(*ino)) |
| return 0; /* no ino */ |
| |
| sz = xino_fread(sbinfo->si_xread, file, ino, sizeof(*ino), &pos); |
| if (sz == sizeof(*ino)) |
| return 0; /* success */ |
| |
| err = sz; |
| if (unlikely(sz >= 0)) { |
| err = -EIO; |
| AuIOErr("xino read error (%zd)\n", sz); |
| } |
| |
| return err; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| /* create and set a new xino file */ |
| |
| struct file *au_xino_create(struct super_block *sb, char *fname, int silent) |
| { |
| struct file *file; |
| struct dentry *h_parent, *d; |
| struct inode *h_dir; |
| int err; |
| |
| /* |
| * at mount-time, and the xino file is the default path, |
| * hnotify is disabled so we have no notify events to ignore. |
| * when a user specified the xino, we cannot get au_hdir to be ignored. |
| */ |
| file = vfsub_filp_open(fname, O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE |
| /* | __FMODE_NONOTIFY */, |
| S_IRUGO | S_IWUGO); |
| if (IS_ERR(file)) { |
| if (!silent) |
| pr_err("open %s(%ld)\n", fname, PTR_ERR(file)); |
| return file; |
| } |
| |
| /* keep file count */ |
| h_parent = dget_parent(file->f_dentry); |
| h_dir = h_parent->d_inode; |
| mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT); |
| /* mnt_want_write() is unnecessary here */ |
| err = vfsub_unlink(h_dir, &file->f_path, /*force*/0); |
| mutex_unlock(&h_dir->i_mutex); |
| dput(h_parent); |
| if (unlikely(err)) { |
| if (!silent) |
| pr_err("unlink %s(%d)\n", fname, err); |
| goto out; |
| } |
| |
| err = -EINVAL; |
| d = file->f_dentry; |
| if (unlikely(sb == d->d_sb)) { |
| if (!silent) |
| pr_err("%s must be outside\n", fname); |
| goto out; |
| } |
| if (unlikely(au_test_fs_bad_xino(d->d_sb))) { |
| if (!silent) |
| pr_err("xino doesn't support %s(%s)\n", |
| fname, au_sbtype(d->d_sb)); |
| goto out; |
| } |
| return file; /* success */ |
| |
| out: |
| fput(file); |
| file = ERR_PTR(err); |
| return file; |
| } |
| |
| /* |
| * find another branch who is on the same filesystem of the specified |
| * branch{@btgt}. search until @bend. |
| */ |
| static int is_sb_shared(struct super_block *sb, aufs_bindex_t btgt, |
| aufs_bindex_t bend) |
| { |
| aufs_bindex_t bindex; |
| struct super_block *tgt_sb = au_sbr_sb(sb, btgt); |
| |
| for (bindex = 0; bindex < btgt; bindex++) |
| if (unlikely(tgt_sb == au_sbr_sb(sb, bindex))) |
| return bindex; |
| for (bindex++; bindex <= bend; bindex++) |
| if (unlikely(tgt_sb == au_sbr_sb(sb, bindex))) |
| return bindex; |
| return -1; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| /* |
| * initialize the xinofile for the specified branch @br |
| * at the place/path where @base_file indicates. |
| * test whether another branch is on the same filesystem or not, |
| * if @do_test is true. |
| */ |
| int au_xino_br(struct super_block *sb, struct au_branch *br, ino_t h_ino, |
| struct file *base_file, int do_test) |
| { |
| int err; |
| ino_t ino; |
| aufs_bindex_t bend, bindex; |
| struct au_branch *shared_br, *b; |
| struct file *file; |
| struct super_block *tgt_sb; |
| |
| shared_br = NULL; |
| bend = au_sbend(sb); |
| if (do_test) { |
| tgt_sb = au_br_sb(br); |
| for (bindex = 0; bindex <= bend; bindex++) { |
| b = au_sbr(sb, bindex); |
| if (tgt_sb == au_br_sb(b)) { |
| shared_br = b; |
| break; |
| } |
| } |
| } |
| |
| if (!shared_br || !shared_br->br_xino.xi_file) { |
| struct au_xino_lock_dir ldir; |
| |
| au_xino_lock_dir(sb, base_file, &ldir); |
| /* mnt_want_write() is unnecessary here */ |
| file = au_xino_create2(base_file, NULL); |
| au_xino_unlock_dir(&ldir); |
| err = PTR_ERR(file); |
| if (IS_ERR(file)) |
| goto out; |
| br->br_xino.xi_file = file; |
| } else { |
| br->br_xino.xi_file = shared_br->br_xino.xi_file; |
| get_file(br->br_xino.xi_file); |
| } |
| |
| ino = AUFS_ROOT_INO; |
| err = au_xino_do_write(au_sbi(sb)->si_xwrite, br->br_xino.xi_file, |
| h_ino, ino); |
| if (unlikely(err)) { |
| fput(br->br_xino.xi_file); |
| br->br_xino.xi_file = NULL; |
| } |
| |
| out: |
| return err; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| /* trucate a xino bitmap file */ |
| |
| /* todo: slow */ |
| static int do_xib_restore(struct super_block *sb, struct file *file, void *page) |
| { |
| int err, bit; |
| ssize_t sz; |
| unsigned long pindex; |
| loff_t pos, pend; |
| struct au_sbinfo *sbinfo; |
| au_readf_t func; |
| ino_t *ino; |
| unsigned long *p; |
| |
| err = 0; |
| sbinfo = au_sbi(sb); |
| MtxMustLock(&sbinfo->si_xib_mtx); |
| p = sbinfo->si_xib_buf; |
| func = sbinfo->si_xread; |
| pend = vfsub_f_size_read(file); |
| pos = 0; |
| while (pos < pend) { |
| sz = xino_fread(func, file, page, PAGE_SIZE, &pos); |
| err = sz; |
| if (unlikely(sz <= 0)) |
| goto out; |
| |
| err = 0; |
| for (ino = page; sz > 0; ino++, sz -= sizeof(ino)) { |
| if (unlikely(*ino < AUFS_FIRST_INO)) |
| continue; |
| |
| xib_calc_bit(*ino, &pindex, &bit); |
| AuDebugOn(page_bits <= bit); |
| err = xib_pindex(sb, pindex); |
| if (!err) |
| set_bit(bit, p); |
| else |
| goto out; |
| } |
| } |
| |
| out: |
| return err; |
| } |
| |
| static int xib_restore(struct super_block *sb) |
| { |
| int err; |
| aufs_bindex_t bindex, bend; |
| void *page; |
| |
| err = -ENOMEM; |
| page = (void *)__get_free_page(GFP_NOFS); |
| if (unlikely(!page)) |
| goto out; |
| |
| err = 0; |
| bend = au_sbend(sb); |
| for (bindex = 0; !err && bindex <= bend; bindex++) |
| if (!bindex || is_sb_shared(sb, bindex, bindex - 1) < 0) |
| err = do_xib_restore |
| (sb, au_sbr(sb, bindex)->br_xino.xi_file, page); |
| else |
| AuDbg("b%d\n", bindex); |
| free_page((unsigned long)page); |
| |
| out: |
| return err; |
| } |
| |
| int au_xib_trunc(struct super_block *sb) |
| { |
| int err; |
| ssize_t sz; |
| loff_t pos; |
| struct au_xino_lock_dir ldir; |
| struct au_sbinfo *sbinfo; |
| unsigned long *p; |
| struct file *file; |
| |
| SiMustWriteLock(sb); |
| |
| err = 0; |
| sbinfo = au_sbi(sb); |
| if (!au_opt_test(sbinfo->si_mntflags, XINO)) |
| goto out; |
| |
| file = sbinfo->si_xib; |
| if (vfsub_f_size_read(file) <= PAGE_SIZE) |
| goto out; |
| |
| au_xino_lock_dir(sb, file, &ldir); |
| /* mnt_want_write() is unnecessary here */ |
| file = au_xino_create2(sbinfo->si_xib, NULL); |
| au_xino_unlock_dir(&ldir); |
| err = PTR_ERR(file); |
| if (IS_ERR(file)) |
| goto out; |
| fput(sbinfo->si_xib); |
| sbinfo->si_xib = file; |
| |
| p = sbinfo->si_xib_buf; |
| memset(p, 0, PAGE_SIZE); |
| pos = 0; |
| sz = xino_fwrite(sbinfo->si_xwrite, sbinfo->si_xib, p, PAGE_SIZE, &pos); |
| if (unlikely(sz != PAGE_SIZE)) { |
| err = sz; |
| AuIOErr("err %d\n", err); |
| if (sz >= 0) |
| err = -EIO; |
| goto out; |
| } |
| |
| mutex_lock(&sbinfo->si_xib_mtx); |
| /* mnt_want_write() is unnecessary here */ |
| err = xib_restore(sb); |
| mutex_unlock(&sbinfo->si_xib_mtx); |
| |
| out: |
| return err; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| /* |
| * xino mount option handlers |
| */ |
| static au_readf_t find_readf(struct file *h_file) |
| { |
| const struct file_operations *fop = h_file->f_op; |
| |
| if (fop) { |
| if (fop->read) |
| return fop->read; |
| if (fop->aio_read) |
| return do_sync_read; |
| } |
| return ERR_PTR(-ENOSYS); |
| } |
| |
| static au_writef_t find_writef(struct file *h_file) |
| { |
| const struct file_operations *fop = h_file->f_op; |
| |
| if (fop) { |
| if (fop->write) |
| return fop->write; |
| if (fop->aio_write) |
| return do_sync_write; |
| } |
| return ERR_PTR(-ENOSYS); |
| } |
| |
| /* xino bitmap */ |
| static void xino_clear_xib(struct super_block *sb) |
| { |
| struct au_sbinfo *sbinfo; |
| |
| SiMustWriteLock(sb); |
| |
| sbinfo = au_sbi(sb); |
| sbinfo->si_xread = NULL; |
| sbinfo->si_xwrite = NULL; |
| if (sbinfo->si_xib) |
| fput(sbinfo->si_xib); |
| sbinfo->si_xib = NULL; |
| free_page((unsigned long)sbinfo->si_xib_buf); |
| sbinfo->si_xib_buf = NULL; |
| } |
| |
| static int au_xino_set_xib(struct super_block *sb, struct file *base) |
| { |
| int err; |
| loff_t pos; |
| struct au_sbinfo *sbinfo; |
| struct file *file; |
| |
| SiMustWriteLock(sb); |
| |
| sbinfo = au_sbi(sb); |
| file = au_xino_create2(base, sbinfo->si_xib); |
| err = PTR_ERR(file); |
| if (IS_ERR(file)) |
| goto out; |
| if (sbinfo->si_xib) |
| fput(sbinfo->si_xib); |
| sbinfo->si_xib = file; |
| sbinfo->si_xread = find_readf(file); |
| sbinfo->si_xwrite = find_writef(file); |
| |
| err = -ENOMEM; |
| if (!sbinfo->si_xib_buf) |
| sbinfo->si_xib_buf = (void *)get_zeroed_page(GFP_NOFS); |
| if (unlikely(!sbinfo->si_xib_buf)) |
| goto out_unset; |
| |
| sbinfo->si_xib_last_pindex = 0; |
| sbinfo->si_xib_next_bit = 0; |
| if (vfsub_f_size_read(file) < PAGE_SIZE) { |
| pos = 0; |
| err = xino_fwrite(sbinfo->si_xwrite, file, sbinfo->si_xib_buf, |
| PAGE_SIZE, &pos); |
| if (unlikely(err != PAGE_SIZE)) |
| goto out_free; |
| } |
| err = 0; |
| goto out; /* success */ |
| |
| out_free: |
| free_page((unsigned long)sbinfo->si_xib_buf); |
| sbinfo->si_xib_buf = NULL; |
| if (err >= 0) |
| err = -EIO; |
| out_unset: |
| fput(sbinfo->si_xib); |
| sbinfo->si_xib = NULL; |
| sbinfo->si_xread = NULL; |
| sbinfo->si_xwrite = NULL; |
| out: |
| return err; |
| } |
| |
| /* xino for each branch */ |
| static void xino_clear_br(struct super_block *sb) |
| { |
| aufs_bindex_t bindex, bend; |
| struct au_branch *br; |
| |
| bend = au_sbend(sb); |
| for (bindex = 0; bindex <= bend; bindex++) { |
| br = au_sbr(sb, bindex); |
| if (!br || !br->br_xino.xi_file) |
| continue; |
| |
| fput(br->br_xino.xi_file); |
| br->br_xino.xi_file = NULL; |
| } |
| } |
| |
| static int au_xino_set_br(struct super_block *sb, struct file *base) |
| { |
| int err; |
| ino_t ino; |
| aufs_bindex_t bindex, bend, bshared; |
| struct { |
| struct file *old, *new; |
| } *fpair, *p; |
| struct au_branch *br; |
| struct inode *inode; |
| au_writef_t writef; |
| |
| SiMustWriteLock(sb); |
| |
| err = -ENOMEM; |
| bend = au_sbend(sb); |
| fpair = kcalloc(bend + 1, sizeof(*fpair), GFP_NOFS); |
| if (unlikely(!fpair)) |
| goto out; |
| |
| inode = sb->s_root->d_inode; |
| ino = AUFS_ROOT_INO; |
| writef = au_sbi(sb)->si_xwrite; |
| for (bindex = 0, p = fpair; bindex <= bend; bindex++, p++) { |
| br = au_sbr(sb, bindex); |
| bshared = is_sb_shared(sb, bindex, bindex - 1); |
| if (bshared >= 0) { |
| /* shared xino */ |
| *p = fpair[bshared]; |
| get_file(p->new); |
| } |
| |
| if (!p->new) { |
| /* new xino */ |
| p->old = br->br_xino.xi_file; |
| p->new = au_xino_create2(base, br->br_xino.xi_file); |
| err = PTR_ERR(p->new); |
| if (IS_ERR(p->new)) { |
| p->new = NULL; |
| goto out_pair; |
| } |
| } |
| |
| err = au_xino_do_write(writef, p->new, |
| au_h_iptr(inode, bindex)->i_ino, ino); |
| if (unlikely(err)) |
| goto out_pair; |
| } |
| |
| for (bindex = 0, p = fpair; bindex <= bend; bindex++, p++) { |
| br = au_sbr(sb, bindex); |
| if (br->br_xino.xi_file) |
| fput(br->br_xino.xi_file); |
| get_file(p->new); |
| br->br_xino.xi_file = p->new; |
| } |
| |
| out_pair: |
| for (bindex = 0, p = fpair; bindex <= bend; bindex++, p++) |
| if (p->new) |
| fput(p->new); |
| else |
| break; |
| kfree(fpair); |
| out: |
| return err; |
| } |
| |
| void au_xino_clr(struct super_block *sb) |
| { |
| struct au_sbinfo *sbinfo; |
| |
| au_xigen_clr(sb); |
| xino_clear_xib(sb); |
| xino_clear_br(sb); |
| sbinfo = au_sbi(sb); |
| /* lvalue, do not call au_mntflags() */ |
| au_opt_clr(sbinfo->si_mntflags, XINO); |
| } |
| |
| int au_xino_set(struct super_block *sb, struct au_opt_xino *xino, int remount) |
| { |
| int err, skip; |
| struct dentry *parent, *cur_parent; |
| struct qstr *dname, *cur_name; |
| struct file *cur_xino; |
| struct inode *dir; |
| struct au_sbinfo *sbinfo; |
| |
| SiMustWriteLock(sb); |
| |
| err = 0; |
| sbinfo = au_sbi(sb); |
| parent = dget_parent(xino->file->f_dentry); |
| if (remount) { |
| skip = 0; |
| dname = &xino->file->f_dentry->d_name; |
| cur_xino = sbinfo->si_xib; |
| if (cur_xino) { |
| cur_parent = dget_parent(cur_xino->f_dentry); |
| cur_name = &cur_xino->f_dentry->d_name; |
| skip = (cur_parent == parent |
| && dname->len == cur_name->len |
| && !memcmp(dname->name, cur_name->name, |
| dname->len)); |
| dput(cur_parent); |
| } |
| if (skip) |
| goto out; |
| } |
| |
| au_opt_set(sbinfo->si_mntflags, XINO); |
| dir = parent->d_inode; |
| mutex_lock_nested(&dir->i_mutex, AuLsc_I_PARENT); |
| /* mnt_want_write() is unnecessary here */ |
| err = au_xino_set_xib(sb, xino->file); |
| if (!err) |
| err = au_xigen_set(sb, xino->file); |
| if (!err) |
| err = au_xino_set_br(sb, xino->file); |
| mutex_unlock(&dir->i_mutex); |
| if (!err) |
| goto out; /* success */ |
| |
| /* reset all */ |
| AuIOErr("failed creating xino(%d).\n", err); |
| |
| out: |
| dput(parent); |
| return err; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| /* |
| * create a xinofile at the default place/path. |
| */ |
| struct file *au_xino_def(struct super_block *sb) |
| { |
| struct file *file; |
| char *page, *p; |
| struct au_branch *br; |
| struct super_block *h_sb; |
| struct path path; |
| aufs_bindex_t bend, bindex, bwr; |
| |
| br = NULL; |
| bend = au_sbend(sb); |
| bwr = -1; |
| for (bindex = 0; bindex <= bend; bindex++) { |
| br = au_sbr(sb, bindex); |
| if (au_br_writable(br->br_perm) |
| && !au_test_fs_bad_xino(au_br_sb(br))) { |
| bwr = bindex; |
| break; |
| } |
| } |
| |
| if (bwr >= 0) { |
| file = ERR_PTR(-ENOMEM); |
| page = (void *)__get_free_page(GFP_NOFS); |
| if (unlikely(!page)) |
| goto out; |
| path.mnt = au_br_mnt(br); |
| path.dentry = au_h_dptr(sb->s_root, bwr); |
| p = d_path(&path, page, PATH_MAX - sizeof(AUFS_XINO_FNAME)); |
| file = (void *)p; |
| if (!IS_ERR(p)) { |
| strcat(p, "/" AUFS_XINO_FNAME); |
| AuDbg("%s\n", p); |
| file = au_xino_create(sb, p, /*silent*/0); |
| if (!IS_ERR(file)) |
| au_xino_brid_set(sb, br->br_id); |
| } |
| free_page((unsigned long)page); |
| } else { |
| file = au_xino_create(sb, AUFS_XINO_DEFPATH, /*silent*/0); |
| if (IS_ERR(file)) |
| goto out; |
| h_sb = file->f_dentry->d_sb; |
| if (unlikely(au_test_fs_bad_xino(h_sb))) { |
| pr_err("xino doesn't support %s(%s)\n", |
| AUFS_XINO_DEFPATH, au_sbtype(h_sb)); |
| fput(file); |
| file = ERR_PTR(-EINVAL); |
| } |
| if (!IS_ERR(file)) |
| au_xino_brid_set(sb, -1); |
| } |
| |
| out: |
| return file; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| int au_xino_path(struct seq_file *seq, struct file *file) |
| { |
| int err; |
| |
| err = au_seq_path(seq, &file->f_path); |
| if (unlikely(err < 0)) |
| goto out; |
| |
| err = 0; |
| #define Deleted "\\040(deleted)" |
| seq->count -= sizeof(Deleted) - 1; |
| AuDebugOn(memcmp(seq->buf + seq->count, Deleted, |
| sizeof(Deleted) - 1)); |
| #undef Deleted |
| |
| out: |
| return err; |
| } |