| /* |
| * Copyright (C) 2010-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 |
| */ |
| |
| /* |
| * dynamically customizable operations for regular files |
| */ |
| |
| #include "aufs.h" |
| |
| #define DyPrSym(key) AuDbgSym(key->dk_op.dy_hop) |
| |
| /* |
| * How large will these lists be? |
| * Usually just a few elements, 20-30 at most for each, I guess. |
| */ |
| static struct au_splhead dynop[AuDyLast]; |
| |
| static struct au_dykey *dy_gfind_get(struct au_splhead *spl, const void *h_op) |
| { |
| struct au_dykey *key, *tmp; |
| struct list_head *head; |
| |
| key = NULL; |
| head = &spl->head; |
| rcu_read_lock(); |
| list_for_each_entry_rcu(tmp, head, dk_list) |
| if (tmp->dk_op.dy_hop == h_op) { |
| key = tmp; |
| kref_get(&key->dk_kref); |
| break; |
| } |
| rcu_read_unlock(); |
| |
| return key; |
| } |
| |
| static struct au_dykey *dy_bradd(struct au_branch *br, struct au_dykey *key) |
| { |
| struct au_dykey **k, *found; |
| const void *h_op = key->dk_op.dy_hop; |
| int i; |
| |
| found = NULL; |
| k = br->br_dykey; |
| for (i = 0; i < AuBrDynOp; i++) |
| if (k[i]) { |
| if (k[i]->dk_op.dy_hop == h_op) { |
| found = k[i]; |
| break; |
| } |
| } else |
| break; |
| if (!found) { |
| spin_lock(&br->br_dykey_lock); |
| for (; i < AuBrDynOp; i++) |
| if (k[i]) { |
| if (k[i]->dk_op.dy_hop == h_op) { |
| found = k[i]; |
| break; |
| } |
| } else { |
| k[i] = key; |
| break; |
| } |
| spin_unlock(&br->br_dykey_lock); |
| BUG_ON(i == AuBrDynOp); /* expand the array */ |
| } |
| |
| return found; |
| } |
| |
| /* kref_get() if @key is already added */ |
| static struct au_dykey *dy_gadd(struct au_splhead *spl, struct au_dykey *key) |
| { |
| struct au_dykey *tmp, *found; |
| struct list_head *head; |
| const void *h_op = key->dk_op.dy_hop; |
| |
| found = NULL; |
| head = &spl->head; |
| spin_lock(&spl->spin); |
| list_for_each_entry(tmp, head, dk_list) |
| if (tmp->dk_op.dy_hop == h_op) { |
| kref_get(&tmp->dk_kref); |
| found = tmp; |
| break; |
| } |
| if (!found) |
| list_add_rcu(&key->dk_list, head); |
| spin_unlock(&spl->spin); |
| |
| if (!found) |
| DyPrSym(key); |
| return found; |
| } |
| |
| static void dy_free_rcu(struct rcu_head *rcu) |
| { |
| struct au_dykey *key; |
| |
| key = container_of(rcu, struct au_dykey, dk_rcu); |
| DyPrSym(key); |
| kfree(key); |
| } |
| |
| static void dy_free(struct kref *kref) |
| { |
| struct au_dykey *key; |
| struct au_splhead *spl; |
| |
| key = container_of(kref, struct au_dykey, dk_kref); |
| spl = dynop + key->dk_op.dy_type; |
| au_spl_del_rcu(&key->dk_list, spl); |
| call_rcu(&key->dk_rcu, dy_free_rcu); |
| } |
| |
| void au_dy_put(struct au_dykey *key) |
| { |
| kref_put(&key->dk_kref, dy_free); |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| #define DyDbgSize(cnt, op) AuDebugOn(cnt != sizeof(op)/sizeof(void *)) |
| |
| #ifdef CONFIG_AUFS_DEBUG |
| #define DyDbgDeclare(cnt) unsigned int cnt = 0 |
| #define DyDbgInc(cnt) do { cnt++; } while (0) |
| #else |
| #define DyDbgDeclare(cnt) do {} while (0) |
| #define DyDbgInc(cnt) do {} while (0) |
| #endif |
| |
| #define DySet(func, dst, src, h_op, h_sb) do { \ |
| DyDbgInc(cnt); \ |
| if (h_op->func) { \ |
| if (src.func) \ |
| dst.func = src.func; \ |
| else \ |
| AuDbg("%s %s\n", au_sbtype(h_sb), #func); \ |
| } \ |
| } while (0) |
| |
| #define DySetForce(func, dst, src) do { \ |
| AuDebugOn(!src.func); \ |
| DyDbgInc(cnt); \ |
| dst.func = src.func; \ |
| } while (0) |
| |
| #define DySetAop(func) \ |
| DySet(func, dyaop->da_op, aufs_aop, h_aop, h_sb) |
| #define DySetAopForce(func) \ |
| DySetForce(func, dyaop->da_op, aufs_aop) |
| |
| static void dy_aop(struct au_dykey *key, const void *h_op, |
| struct super_block *h_sb __maybe_unused) |
| { |
| struct au_dyaop *dyaop = (void *)key; |
| const struct address_space_operations *h_aop = h_op; |
| DyDbgDeclare(cnt); |
| |
| AuDbg("%s\n", au_sbtype(h_sb)); |
| |
| DySetAop(writepage); |
| DySetAopForce(readpage); /* force */ |
| DySetAop(writepages); |
| DySetAop(set_page_dirty); |
| DySetAop(readpages); |
| DySetAop(write_begin); |
| DySetAop(write_end); |
| DySetAop(bmap); |
| DySetAop(invalidatepage); |
| DySetAop(releasepage); |
| DySetAop(freepage); |
| /* these two will be changed according to an aufs mount option */ |
| DySetAop(direct_IO); |
| DySetAop(get_xip_mem); |
| DySetAop(migratepage); |
| DySetAop(launder_page); |
| DySetAop(is_partially_uptodate); |
| DySetAop(error_remove_page); |
| DySetAop(swap_activate); |
| DySetAop(swap_deactivate); |
| |
| DyDbgSize(cnt, *h_aop); |
| dyaop->da_get_xip_mem = h_aop->get_xip_mem; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| static void dy_bug(struct kref *kref) |
| { |
| BUG(); |
| } |
| |
| static struct au_dykey *dy_get(struct au_dynop *op, struct au_branch *br) |
| { |
| struct au_dykey *key, *old; |
| struct au_splhead *spl; |
| struct op { |
| unsigned int sz; |
| void (*set)(struct au_dykey *key, const void *h_op, |
| struct super_block *h_sb __maybe_unused); |
| }; |
| static const struct op a[] = { |
| [AuDy_AOP] = { |
| .sz = sizeof(struct au_dyaop), |
| .set = dy_aop |
| } |
| }; |
| const struct op *p; |
| |
| spl = dynop + op->dy_type; |
| key = dy_gfind_get(spl, op->dy_hop); |
| if (key) |
| goto out_add; /* success */ |
| |
| p = a + op->dy_type; |
| key = kzalloc(p->sz, GFP_NOFS); |
| if (unlikely(!key)) { |
| key = ERR_PTR(-ENOMEM); |
| goto out; |
| } |
| |
| key->dk_op.dy_hop = op->dy_hop; |
| kref_init(&key->dk_kref); |
| p->set(key, op->dy_hop, au_br_sb(br)); |
| old = dy_gadd(spl, key); |
| if (old) { |
| kfree(key); |
| key = old; |
| } |
| |
| out_add: |
| old = dy_bradd(br, key); |
| if (old) |
| /* its ref-count should never be zero here */ |
| kref_put(&key->dk_kref, dy_bug); |
| out: |
| return key; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| /* |
| * Aufs prohibits O_DIRECT by defaut even if the branch supports it. |
| * This behaviour is neccessary to return an error from open(O_DIRECT) instead |
| * of the succeeding I/O. The dio mount option enables O_DIRECT and makes |
| * open(O_DIRECT) always succeed, but the succeeding I/O may return an error. |
| * See the aufs manual in detail. |
| * |
| * To keep this behaviour, aufs has to set NULL to ->get_xip_mem too, and the |
| * performance of fadvise() and madvise() may be affected. |
| */ |
| static void dy_adx(struct au_dyaop *dyaop, int do_dx) |
| { |
| if (!do_dx) { |
| dyaop->da_op.direct_IO = NULL; |
| dyaop->da_op.get_xip_mem = NULL; |
| } else { |
| dyaop->da_op.direct_IO = aufs_aop.direct_IO; |
| dyaop->da_op.get_xip_mem = aufs_aop.get_xip_mem; |
| if (!dyaop->da_get_xip_mem) |
| dyaop->da_op.get_xip_mem = NULL; |
| } |
| } |
| |
| static struct au_dyaop *dy_aget(struct au_branch *br, |
| const struct address_space_operations *h_aop, |
| int do_dx) |
| { |
| struct au_dyaop *dyaop; |
| struct au_dynop op; |
| |
| op.dy_type = AuDy_AOP; |
| op.dy_haop = h_aop; |
| dyaop = (void *)dy_get(&op, br); |
| if (IS_ERR(dyaop)) |
| goto out; |
| dy_adx(dyaop, do_dx); |
| |
| out: |
| return dyaop; |
| } |
| |
| int au_dy_iaop(struct inode *inode, aufs_bindex_t bindex, |
| struct inode *h_inode) |
| { |
| int err, do_dx; |
| struct super_block *sb; |
| struct au_branch *br; |
| struct au_dyaop *dyaop; |
| |
| AuDebugOn(!S_ISREG(h_inode->i_mode)); |
| IiMustWriteLock(inode); |
| |
| sb = inode->i_sb; |
| br = au_sbr(sb, bindex); |
| do_dx = !!au_opt_test(au_mntflags(sb), DIO); |
| dyaop = dy_aget(br, h_inode->i_mapping->a_ops, do_dx); |
| err = PTR_ERR(dyaop); |
| if (IS_ERR(dyaop)) |
| /* unnecessary to call dy_fput() */ |
| goto out; |
| |
| err = 0; |
| inode->i_mapping->a_ops = &dyaop->da_op; |
| |
| out: |
| return err; |
| } |
| |
| /* |
| * Is it safe to replace a_ops during the inode/file is in operation? |
| * Yes, I hope so. |
| */ |
| int au_dy_irefresh(struct inode *inode) |
| { |
| int err; |
| aufs_bindex_t bstart; |
| struct inode *h_inode; |
| |
| err = 0; |
| if (S_ISREG(inode->i_mode)) { |
| bstart = au_ibstart(inode); |
| h_inode = au_h_iptr(inode, bstart); |
| err = au_dy_iaop(inode, bstart, h_inode); |
| } |
| return err; |
| } |
| |
| void au_dy_arefresh(int do_dx) |
| { |
| struct au_splhead *spl; |
| struct list_head *head; |
| struct au_dykey *key; |
| |
| spl = dynop + AuDy_AOP; |
| head = &spl->head; |
| spin_lock(&spl->spin); |
| list_for_each_entry(key, head, dk_list) |
| dy_adx((void *)key, do_dx); |
| spin_unlock(&spl->spin); |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| void __init au_dy_init(void) |
| { |
| int i; |
| |
| /* make sure that 'struct au_dykey *' can be any type */ |
| BUILD_BUG_ON(offsetof(struct au_dyaop, da_key)); |
| |
| for (i = 0; i < AuDyLast; i++) |
| au_spl_init(dynop + i); |
| } |
| |
| void au_dy_fin(void) |
| { |
| int i; |
| |
| for (i = 0; i < AuDyLast; i++) |
| WARN_ON(!list_empty(&dynop[i].head)); |
| } |