summaryrefslogtreecommitdiff
path: root/fs/aufs/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/aufs/file.c')
-rw-r--r--fs/aufs/file.c841
1 files changed, 841 insertions, 0 deletions
diff --git a/fs/aufs/file.c b/fs/aufs/file.c
new file mode 100644
index 000000000..d3a566e4f
--- /dev/null
+++ b/fs/aufs/file.c
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2005-2015 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * handling file/dir, and address_space operation
+ */
+
+#ifdef CONFIG_AUFS_DEBUG
+#include <linux/migrate.h>
+#endif
+#include <linux/pagemap.h>
+#include "aufs.h"
+
+/* drop flags for writing */
+unsigned int au_file_roflags(unsigned int flags)
+{
+ flags &= ~(O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC);
+ flags |= O_RDONLY | O_NOATIME;
+ return flags;
+}
+
+/* common functions to regular file and dir */
+struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags,
+ struct file *file, int force_wr)
+{
+ struct file *h_file;
+ struct dentry *h_dentry;
+ struct inode *h_inode;
+ struct super_block *sb;
+ struct au_branch *br;
+ struct path h_path;
+ int err;
+
+ /* a race condition can happen between open and unlink/rmdir */
+ h_file = ERR_PTR(-ENOENT);
+ h_dentry = au_h_dptr(dentry, bindex);
+ if (au_test_nfsd() && (!h_dentry || d_is_negative(h_dentry)))
+ goto out;
+ h_inode = d_inode(h_dentry);
+ spin_lock(&h_dentry->d_lock);
+ err = (!d_unhashed(dentry) && d_unlinked(h_dentry))
+ /* || !d_inode(dentry)->i_nlink */
+ ;
+ spin_unlock(&h_dentry->d_lock);
+ if (unlikely(err))
+ goto out;
+
+ sb = dentry->d_sb;
+ br = au_sbr(sb, bindex);
+ err = au_br_test_oflag(flags, br);
+ h_file = ERR_PTR(err);
+ if (unlikely(err))
+ goto out;
+
+ /* drop flags for writing */
+ if (au_test_ro(sb, bindex, d_inode(dentry))) {
+ if (force_wr && !(flags & O_WRONLY))
+ force_wr = 0;
+ flags = au_file_roflags(flags);
+ if (force_wr) {
+ h_file = ERR_PTR(-EROFS);
+ flags = au_file_roflags(flags);
+ if (unlikely(vfsub_native_ro(h_inode)
+ || IS_APPEND(h_inode)))
+ goto out;
+ flags &= ~O_ACCMODE;
+ flags |= O_WRONLY;
+ }
+ }
+ flags &= ~O_CREAT;
+ atomic_inc(&br->br_count);
+ h_path.dentry = h_dentry;
+ h_path.mnt = au_br_mnt(br);
+ h_file = vfsub_dentry_open(&h_path, flags);
+ if (IS_ERR(h_file))
+ goto out_br;
+
+ if (flags & __FMODE_EXEC) {
+ err = deny_write_access(h_file);
+ if (unlikely(err)) {
+ fput(h_file);
+ h_file = ERR_PTR(err);
+ goto out_br;
+ }
+ }
+ fsnotify_open(h_file);
+ goto out; /* success */
+
+out_br:
+ atomic_dec(&br->br_count);
+out:
+ return h_file;
+}
+
+static int au_cmoo(struct dentry *dentry)
+{
+ int err, cmoo;
+ unsigned int udba;
+ struct path h_path;
+ struct au_pin pin;
+ struct au_cp_generic cpg = {
+ .dentry = dentry,
+ .bdst = -1,
+ .bsrc = -1,
+ .len = -1,
+ .pin = &pin,
+ .flags = AuCpup_DTIME | AuCpup_HOPEN
+ };
+ struct inode *delegated;
+ struct super_block *sb;
+ struct au_sbinfo *sbinfo;
+ struct au_fhsm *fhsm;
+ pid_t pid;
+ struct au_branch *br;
+ struct dentry *parent;
+ struct au_hinode *hdir;
+
+ DiMustWriteLock(dentry);
+ IiMustWriteLock(d_inode(dentry));
+
+ err = 0;
+ if (IS_ROOT(dentry))
+ goto out;
+ cpg.bsrc = au_dbstart(dentry);
+ if (!cpg.bsrc)
+ goto out;
+
+ sb = dentry->d_sb;
+ sbinfo = au_sbi(sb);
+ fhsm = &sbinfo->si_fhsm;
+ pid = au_fhsm_pid(fhsm);
+ if (pid
+ && (current->pid == pid
+ || current->real_parent->pid == pid))
+ goto out;
+
+ br = au_sbr(sb, cpg.bsrc);
+ cmoo = au_br_cmoo(br->br_perm);
+ if (!cmoo)
+ goto out;
+ if (!d_is_reg(dentry))
+ cmoo &= AuBrAttr_COO_ALL;
+ if (!cmoo)
+ goto out;
+
+ parent = dget_parent(dentry);
+ di_write_lock_parent(parent);
+ err = au_wbr_do_copyup_bu(dentry, cpg.bsrc - 1);
+ cpg.bdst = err;
+ if (unlikely(err < 0)) {
+ err = 0; /* there is no upper writable branch */
+ goto out_dgrade;
+ }
+ AuDbg("bsrc %d, bdst %d\n", cpg.bsrc, cpg.bdst);
+
+ /* do not respect the coo attrib for the target branch */
+ err = au_cpup_dirs(dentry, cpg.bdst);
+ if (unlikely(err))
+ goto out_dgrade;
+
+ di_downgrade_lock(parent, AuLock_IR);
+ udba = au_opt_udba(sb);
+ err = au_pin(&pin, dentry, cpg.bdst, udba,
+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
+ if (unlikely(err))
+ goto out_parent;
+
+ err = au_sio_cpup_simple(&cpg);
+ au_unpin(&pin);
+ if (unlikely(err))
+ goto out_parent;
+ if (!(cmoo & AuBrWAttr_MOO))
+ goto out_parent; /* success */
+
+ err = au_pin(&pin, dentry, cpg.bsrc, udba,
+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
+ if (unlikely(err))
+ goto out_parent;
+
+ h_path.mnt = au_br_mnt(br);
+ h_path.dentry = au_h_dptr(dentry, cpg.bsrc);
+ hdir = au_hi(d_inode(parent), cpg.bsrc);
+ delegated = NULL;
+ err = vfsub_unlink(hdir->hi_inode, &h_path, &delegated, /*force*/1);
+ au_unpin(&pin);
+ /* todo: keep h_dentry or not? */
+ if (unlikely(err == -EWOULDBLOCK)) {
+ pr_warn("cannot retry for NFSv4 delegation"
+ " for an internal unlink\n");
+ iput(delegated);
+ }
+ if (unlikely(err)) {
+ pr_err("unlink %pd after coo failed (%d), ignored\n",
+ dentry, err);
+ err = 0;
+ }
+ goto out_parent; /* success */
+
+out_dgrade:
+ di_downgrade_lock(parent, AuLock_IR);
+out_parent:
+ di_read_unlock(parent, AuLock_IR);
+ dput(parent);
+out:
+ AuTraceErr(err);
+ return err;
+}
+
+int au_do_open(struct file *file, struct au_do_open_args *args)
+{
+ int err, no_lock = args->no_lock;
+ struct dentry *dentry;
+ struct au_finfo *finfo;
+
+ if (!no_lock)
+ err = au_finfo_init(file, args->fidir);
+ else {
+ lockdep_off();
+ err = au_finfo_init(file, args->fidir);
+ lockdep_on();
+ }
+ if (unlikely(err))
+ goto out;
+
+ dentry = file->f_path.dentry;
+ AuDebugOn(IS_ERR_OR_NULL(dentry));
+ if (!no_lock) {
+ di_write_lock_child(dentry);
+ err = au_cmoo(dentry);
+ di_downgrade_lock(dentry, AuLock_IR);
+ if (!err)
+ err = args->open(file, vfsub_file_flags(file), NULL);
+ di_read_unlock(dentry, AuLock_IR);
+ } else {
+ err = au_cmoo(dentry);
+ if (!err)
+ err = args->open(file, vfsub_file_flags(file),
+ args->h_file);
+ if (!err && au_fbstart(file) != au_dbstart(dentry))
+ /*
+ * cmoo happens after h_file was opened.
+ * need to refresh file later.
+ */
+ atomic_dec(&au_fi(file)->fi_generation);
+ }
+
+ finfo = au_fi(file);
+ if (!err) {
+ finfo->fi_file = file;
+ au_sphl_add(&finfo->fi_hlist,
+ &au_sbi(file->f_path.dentry->d_sb)->si_files);
+ }
+ if (!no_lock)
+ fi_write_unlock(file);
+ else {
+ lockdep_off();
+ fi_write_unlock(file);
+ lockdep_on();
+ }
+ if (unlikely(err)) {
+ finfo->fi_hdir = NULL;
+ au_finfo_fin(file);
+ }
+
+out:
+ return err;
+}
+
+int au_reopen_nondir(struct file *file)
+{
+ int err;
+ aufs_bindex_t bstart;
+ struct dentry *dentry;
+ struct file *h_file, *h_file_tmp;
+
+ dentry = file->f_path.dentry;
+ bstart = au_dbstart(dentry);
+ h_file_tmp = NULL;
+ if (au_fbstart(file) == bstart) {
+ h_file = au_hf_top(file);
+ if (file->f_mode == h_file->f_mode)
+ return 0; /* success */
+ h_file_tmp = h_file;
+ get_file(h_file_tmp);
+ au_set_h_fptr(file, bstart, NULL);
+ }
+ AuDebugOn(au_fi(file)->fi_hdir);
+ /*
+ * it can happen
+ * file exists on both of rw and ro
+ * open --> dbstart and fbstart are both 0
+ * prepend a branch as rw, "rw" become ro
+ * remove rw/file
+ * delete the top branch, "rw" becomes rw again
+ * --> dbstart is 1, fbstart is still 0
+ * write --> fbstart is 0 but dbstart is 1
+ */
+ /* AuDebugOn(au_fbstart(file) < bstart); */
+
+ h_file = au_h_open(dentry, bstart, vfsub_file_flags(file) & ~O_TRUNC,
+ file, /*force_wr*/0);
+ err = PTR_ERR(h_file);
+ if (IS_ERR(h_file)) {
+ if (h_file_tmp) {
+ atomic_inc(&au_sbr(dentry->d_sb, bstart)->br_count);
+ au_set_h_fptr(file, bstart, h_file_tmp);
+ h_file_tmp = NULL;
+ }
+ goto out; /* todo: close all? */
+ }
+
+ err = 0;
+ au_set_fbstart(file, bstart);
+ au_set_h_fptr(file, bstart, h_file);
+ au_update_figen(file);
+ /* todo: necessary? */
+ /* file->f_ra = h_file->f_ra; */
+
+out:
+ if (h_file_tmp)
+ fput(h_file_tmp);
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int au_reopen_wh(struct file *file, aufs_bindex_t btgt,
+ struct dentry *hi_wh)
+{
+ int err;
+ aufs_bindex_t bstart;
+ struct au_dinfo *dinfo;
+ struct dentry *h_dentry;
+ struct au_hdentry *hdp;
+
+ dinfo = au_di(file->f_path.dentry);
+ AuRwMustWriteLock(&dinfo->di_rwsem);
+
+ bstart = dinfo->di_bstart;
+ dinfo->di_bstart = btgt;
+ hdp = dinfo->di_hdentry;
+ h_dentry = hdp[0 + btgt].hd_dentry;
+ hdp[0 + btgt].hd_dentry = hi_wh;
+ err = au_reopen_nondir(file);
+ hdp[0 + btgt].hd_dentry = h_dentry;
+ dinfo->di_bstart = bstart;
+
+ return err;
+}
+
+static int au_ready_to_write_wh(struct file *file, loff_t len,
+ aufs_bindex_t bcpup, struct au_pin *pin)
+{
+ int err;
+ struct inode *inode, *h_inode;
+ struct dentry *h_dentry, *hi_wh;
+ struct au_cp_generic cpg = {
+ .dentry = file->f_path.dentry,
+ .bdst = bcpup,
+ .bsrc = -1,
+ .len = len,
+ .pin = pin
+ };
+
+ au_update_dbstart(cpg.dentry);
+ inode = d_inode(cpg.dentry);
+ h_inode = NULL;
+ if (au_dbstart(cpg.dentry) <= bcpup
+ && au_dbend(cpg.dentry) >= bcpup) {
+ h_dentry = au_h_dptr(cpg.dentry, bcpup);
+ if (h_dentry && d_is_positive(h_dentry))
+ h_inode = d_inode(h_dentry);
+ }
+ hi_wh = au_hi_wh(inode, bcpup);
+ if (!hi_wh && !h_inode)
+ err = au_sio_cpup_wh(&cpg, file);
+ else
+ /* already copied-up after unlink */
+ err = au_reopen_wh(file, bcpup, hi_wh);
+
+ if (!err
+ && (inode->i_nlink > 1
+ || (inode->i_state & I_LINKABLE))
+ && au_opt_test(au_mntflags(cpg.dentry->d_sb), PLINK))
+ au_plink_append(inode, bcpup, au_h_dptr(cpg.dentry, bcpup));
+
+ return err;
+}
+
+/*
+ * prepare the @file for writing.
+ */
+int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin)
+{
+ int err;
+ aufs_bindex_t dbstart;
+ struct dentry *parent;
+ struct inode *inode;
+ struct super_block *sb;
+ struct file *h_file;
+ struct au_cp_generic cpg = {
+ .dentry = file->f_path.dentry,
+ .bdst = -1,
+ .bsrc = -1,
+ .len = len,
+ .pin = pin,
+ .flags = AuCpup_DTIME
+ };
+
+ sb = cpg.dentry->d_sb;
+ inode = d_inode(cpg.dentry);
+ cpg.bsrc = au_fbstart(file);
+ err = au_test_ro(sb, cpg.bsrc, inode);
+ if (!err && (au_hf_top(file)->f_mode & FMODE_WRITE)) {
+ err = au_pin(pin, cpg.dentry, cpg.bsrc, AuOpt_UDBA_NONE,
+ /*flags*/0);
+ goto out;
+ }
+
+ /* need to cpup or reopen */
+ parent = dget_parent(cpg.dentry);
+ di_write_lock_parent(parent);
+ err = AuWbrCopyup(au_sbi(sb), cpg.dentry);
+ cpg.bdst = err;
+ if (unlikely(err < 0))
+ goto out_dgrade;
+ err = 0;
+
+ if (!d_unhashed(cpg.dentry) && !au_h_dptr(parent, cpg.bdst)) {
+ err = au_cpup_dirs(cpg.dentry, cpg.bdst);
+ if (unlikely(err))
+ goto out_dgrade;
+ }
+
+ err = au_pin(pin, cpg.dentry, cpg.bdst, AuOpt_UDBA_NONE,
+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
+ if (unlikely(err))
+ goto out_dgrade;
+
+ dbstart = au_dbstart(cpg.dentry);
+ if (dbstart <= cpg.bdst)
+ cpg.bsrc = cpg.bdst;
+
+ if (dbstart <= cpg.bdst /* just reopen */
+ || !d_unhashed(cpg.dentry) /* copyup and reopen */
+ ) {
+ h_file = au_h_open_pre(cpg.dentry, cpg.bsrc, /*force_wr*/0);
+ if (IS_ERR(h_file))
+ err = PTR_ERR(h_file);
+ else {
+ di_downgrade_lock(parent, AuLock_IR);
+ if (dbstart > cpg.bdst)
+ err = au_sio_cpup_simple(&cpg);
+ if (!err)
+ err = au_reopen_nondir(file);
+ au_h_open_post(cpg.dentry, cpg.bsrc, h_file);
+ }
+ } else { /* copyup as wh and reopen */
+ /*
+ * since writable hfsplus branch is not supported,
+ * h_open_pre/post() are unnecessary.
+ */
+ err = au_ready_to_write_wh(file, len, cpg.bdst, pin);
+ di_downgrade_lock(parent, AuLock_IR);
+ }
+
+ if (!err) {
+ au_pin_set_parent_lflag(pin, /*lflag*/0);
+ goto out_dput; /* success */
+ }
+ au_unpin(pin);
+ goto out_unlock;
+
+out_dgrade:
+ di_downgrade_lock(parent, AuLock_IR);
+out_unlock:
+ di_read_unlock(parent, AuLock_IR);
+out_dput:
+ dput(parent);
+out:
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+int au_do_flush(struct file *file, fl_owner_t id,
+ int (*flush)(struct file *file, fl_owner_t id))
+{
+ int err;
+ struct super_block *sb;
+ struct inode *inode;
+
+ inode = file_inode(file);
+ sb = inode->i_sb;
+ si_noflush_read_lock(sb);
+ fi_read_lock(file);
+ ii_read_lock_child(inode);
+
+ err = flush(file, id);
+ au_cpup_attr_timesizes(inode);
+
+ ii_read_unlock(inode);
+ fi_read_unlock(file);
+ si_read_unlock(sb);
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int au_file_refresh_by_inode(struct file *file, int *need_reopen)
+{
+ int err;
+ struct au_pin pin;
+ struct au_finfo *finfo;
+ struct dentry *parent, *hi_wh;
+ struct inode *inode;
+ struct super_block *sb;
+ struct au_cp_generic cpg = {
+ .dentry = file->f_path.dentry,
+ .bdst = -1,
+ .bsrc = -1,
+ .len = -1,
+ .pin = &pin,
+ .flags = AuCpup_DTIME
+ };
+
+ FiMustWriteLock(file);
+
+ err = 0;
+ finfo = au_fi(file);
+ sb = cpg.dentry->d_sb;
+ inode = d_inode(cpg.dentry);
+ cpg.bdst = au_ibstart(inode);
+ if (cpg.bdst == finfo->fi_btop || IS_ROOT(cpg.dentry))
+ goto out;
+
+ parent = dget_parent(cpg.dentry);
+ if (au_test_ro(sb, cpg.bdst, inode)) {
+ di_read_lock_parent(parent, !AuLock_IR);
+ err = AuWbrCopyup(au_sbi(sb), cpg.dentry);
+ cpg.bdst = err;
+ di_read_unlock(parent, !AuLock_IR);
+ if (unlikely(err < 0))
+ goto out_parent;
+ err = 0;
+ }
+
+ di_read_lock_parent(parent, AuLock_IR);
+ hi_wh = au_hi_wh(inode, cpg.bdst);
+ if (!S_ISDIR(inode->i_mode)
+ && au_opt_test(au_mntflags(sb), PLINK)
+ && au_plink_test(inode)
+ && !d_unhashed(cpg.dentry)
+ && cpg.bdst < au_dbstart(cpg.dentry)) {
+ err = au_test_and_cpup_dirs(cpg.dentry, cpg.bdst);
+ if (unlikely(err))
+ goto out_unlock;
+
+ /* always superio. */
+ err = au_pin(&pin, cpg.dentry, cpg.bdst, AuOpt_UDBA_NONE,
+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
+ if (!err) {
+ err = au_sio_cpup_simple(&cpg);
+ au_unpin(&pin);
+ }
+ } else if (hi_wh) {
+ /* already copied-up after unlink */
+ err = au_reopen_wh(file, cpg.bdst, hi_wh);
+ *need_reopen = 0;
+ }
+
+out_unlock:
+ di_read_unlock(parent, AuLock_IR);
+out_parent:
+ dput(parent);
+out:
+ return err;
+}
+
+static void au_do_refresh_dir(struct file *file)
+{
+ aufs_bindex_t bindex, bend, new_bindex, brid;
+ struct au_hfile *p, tmp, *q;
+ struct au_finfo *finfo;
+ struct super_block *sb;
+ struct au_fidir *fidir;
+
+ FiMustWriteLock(file);
+
+ sb = file->f_path.dentry->d_sb;
+ finfo = au_fi(file);
+ fidir = finfo->fi_hdir;
+ AuDebugOn(!fidir);
+ p = fidir->fd_hfile + finfo->fi_btop;
+ brid = p->hf_br->br_id;
+ bend = fidir->fd_bbot;
+ for (bindex = finfo->fi_btop; bindex <= bend; bindex++, p++) {
+ if (!p->hf_file)
+ continue;
+
+ new_bindex = au_br_index(sb, p->hf_br->br_id);
+ if (new_bindex == bindex)
+ continue;
+ if (new_bindex < 0) {
+ au_set_h_fptr(file, bindex, NULL);
+ continue;
+ }
+
+ /* swap two lower inode, and loop again */
+ q = fidir->fd_hfile + new_bindex;
+ tmp = *q;
+ *q = *p;
+ *p = tmp;
+ if (tmp.hf_file) {
+ bindex--;
+ p--;
+ }
+ }
+
+ p = fidir->fd_hfile;
+ if (!au_test_mmapped(file) && !d_unlinked(file->f_path.dentry)) {
+ bend = au_sbend(sb);
+ for (finfo->fi_btop = 0; finfo->fi_btop <= bend;
+ finfo->fi_btop++, p++)
+ if (p->hf_file) {
+ if (file_inode(p->hf_file))
+ break;
+ au_hfput(p, file);
+ }
+ } else {
+ bend = au_br_index(sb, brid);
+ for (finfo->fi_btop = 0; finfo->fi_btop < bend;
+ finfo->fi_btop++, p++)
+ if (p->hf_file)
+ au_hfput(p, file);
+ bend = au_sbend(sb);
+ }
+
+ p = fidir->fd_hfile + bend;
+ for (fidir->fd_bbot = bend; fidir->fd_bbot >= finfo->fi_btop;
+ fidir->fd_bbot--, p--)
+ if (p->hf_file) {
+ if (file_inode(p->hf_file))
+ break;
+ au_hfput(p, file);
+ }
+ AuDebugOn(fidir->fd_bbot < finfo->fi_btop);
+}
+
+/*
+ * after branch manipulating, refresh the file.
+ */
+static int refresh_file(struct file *file, int (*reopen)(struct file *file))
+{
+ int err, need_reopen;
+ aufs_bindex_t bend, bindex;
+ struct dentry *dentry;
+ struct au_finfo *finfo;
+ struct au_hfile *hfile;
+
+ dentry = file->f_path.dentry;
+ finfo = au_fi(file);
+ if (!finfo->fi_hdir) {
+ hfile = &finfo->fi_htop;
+ AuDebugOn(!hfile->hf_file);
+ bindex = au_br_index(dentry->d_sb, hfile->hf_br->br_id);
+ AuDebugOn(bindex < 0);
+ if (bindex != finfo->fi_btop)
+ au_set_fbstart(file, bindex);
+ } else {
+ err = au_fidir_realloc(finfo, au_sbend(dentry->d_sb) + 1);
+ if (unlikely(err))
+ goto out;
+ au_do_refresh_dir(file);
+ }
+
+ err = 0;
+ need_reopen = 1;
+ if (!au_test_mmapped(file))
+ err = au_file_refresh_by_inode(file, &need_reopen);
+ if (!err && need_reopen && !d_unlinked(dentry))
+ err = reopen(file);
+ if (!err) {
+ au_update_figen(file);
+ goto out; /* success */
+ }
+
+ /* error, close all lower files */
+ if (finfo->fi_hdir) {
+ bend = au_fbend_dir(file);
+ for (bindex = au_fbstart(file); bindex <= bend; bindex++)
+ au_set_h_fptr(file, bindex, NULL);
+ }
+
+out:
+ return err;
+}
+
+/* common function to regular file and dir */
+int au_reval_and_lock_fdi(struct file *file, int (*reopen)(struct file *file),
+ int wlock)
+{
+ int err;
+ unsigned int sigen, figen;
+ aufs_bindex_t bstart;
+ unsigned char pseudo_link;
+ struct dentry *dentry;
+ struct inode *inode;
+
+ err = 0;
+ dentry = file->f_path.dentry;
+ inode = d_inode(dentry);
+ sigen = au_sigen(dentry->d_sb);
+ fi_write_lock(file);
+ figen = au_figen(file);
+ di_write_lock_child(dentry);
+ bstart = au_dbstart(dentry);
+ pseudo_link = (bstart != au_ibstart(inode));
+ if (sigen == figen && !pseudo_link && au_fbstart(file) == bstart) {
+ if (!wlock) {
+ di_downgrade_lock(dentry, AuLock_IR);
+ fi_downgrade_lock(file);
+ }
+ goto out; /* success */
+ }
+
+ AuDbg("sigen %d, figen %d\n", sigen, figen);
+ if (au_digen_test(dentry, sigen)) {
+ err = au_reval_dpath(dentry, sigen);
+ AuDebugOn(!err && au_digen_test(dentry, sigen));
+ }
+
+ if (!err)
+ err = refresh_file(file, reopen);
+ if (!err) {
+ if (!wlock) {
+ di_downgrade_lock(dentry, AuLock_IR);
+ fi_downgrade_lock(file);
+ }
+ } else {
+ di_write_unlock(dentry);
+ fi_write_unlock(file);
+ }
+
+out:
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* cf. aufs_nopage() */
+/* for madvise(2) */
+static int aufs_readpage(struct file *file __maybe_unused, struct page *page)
+{
+ unlock_page(page);
+ return 0;
+}
+
+/* it will never be called, but necessary to support O_DIRECT */
+static ssize_t aufs_direct_IO(struct kiocb *iocb, struct iov_iter *iter,
+ loff_t offset)
+{ BUG(); return 0; }
+
+/* they will never be called. */
+#ifdef CONFIG_AUFS_DEBUG
+static int aufs_write_begin(struct file *file, struct address_space *mapping,
+ loff_t pos, unsigned len, unsigned flags,
+ struct page **pagep, void **fsdata)
+{ AuUnsupport(); return 0; }
+static int aufs_write_end(struct file *file, struct address_space *mapping,
+ loff_t pos, unsigned len, unsigned copied,
+ struct page *page, void *fsdata)
+{ AuUnsupport(); return 0; }
+static int aufs_writepage(struct page *page, struct writeback_control *wbc)
+{ AuUnsupport(); return 0; }
+
+static int aufs_set_page_dirty(struct page *page)
+{ AuUnsupport(); return 0; }
+static void aufs_invalidatepage(struct page *page, unsigned int offset,
+ unsigned int length)
+{ AuUnsupport(); }
+static int aufs_releasepage(struct page *page, gfp_t gfp)
+{ AuUnsupport(); return 0; }
+static int aufs_migratepage(struct address_space *mapping, struct page *newpage,
+ struct page *page, enum migrate_mode mode)
+{ AuUnsupport(); return 0; }
+static int aufs_launder_page(struct page *page)
+{ AuUnsupport(); return 0; }
+static int aufs_is_partially_uptodate(struct page *page,
+ unsigned long from,
+ unsigned long count)
+{ AuUnsupport(); return 0; }
+static void aufs_is_dirty_writeback(struct page *page, bool *dirty,
+ bool *writeback)
+{ AuUnsupport(); }
+static int aufs_error_remove_page(struct address_space *mapping,
+ struct page *page)
+{ AuUnsupport(); return 0; }
+static int aufs_swap_activate(struct swap_info_struct *sis, struct file *file,
+ sector_t *span)
+{ AuUnsupport(); return 0; }
+static void aufs_swap_deactivate(struct file *file)
+{ AuUnsupport(); }
+#endif /* CONFIG_AUFS_DEBUG */
+
+const struct address_space_operations aufs_aop = {
+ .readpage = aufs_readpage,
+ .direct_IO = aufs_direct_IO,
+#ifdef CONFIG_AUFS_DEBUG
+ .writepage = aufs_writepage,
+ /* no writepages, because of writepage */
+ .set_page_dirty = aufs_set_page_dirty,
+ /* no readpages, because of readpage */
+ .write_begin = aufs_write_begin,
+ .write_end = aufs_write_end,
+ /* no bmap, no block device */
+ .invalidatepage = aufs_invalidatepage,
+ .releasepage = aufs_releasepage,
+ .migratepage = aufs_migratepage,
+ .launder_page = aufs_launder_page,
+ .is_partially_uptodate = aufs_is_partially_uptodate,
+ .is_dirty_writeback = aufs_is_dirty_writeback,
+ .error_remove_page = aufs_error_remove_page,
+ .swap_activate = aufs_swap_activate,
+ .swap_deactivate = aufs_swap_deactivate
+#endif /* CONFIG_AUFS_DEBUG */
+};