mirror of
https://abf.rosa.ru/djam/kernel-5.15.git
synced 2025-02-23 18:42:55 +00:00

- rediffed most of patches, renamed files to easify further rediffs by git format-patch - thanks to abf.io/kernels_stable for some patches for kernel 5.9 - using unofficial patch for AUFS for kernel 5.10, there may be issues with stability, try to avoid using AUFS (I would have dropped it, but MagOS wants it very much) - pulled updates of AltHa from http://git.altlinux.org/gears/k/kernel-image-un-def.git?p=kernel-image-un-def.git;a=history;f=security/altha;hb=HEAD - dropped patch adding sysctl to disable disk-based swap because it has not found any usage - bpf is now in the list of LSM modules (ability to write LSM modules as BPF programs), enable it, it is potentially useful and does not seem to be harmful - keeping kernel libc headers in older kernels for now - dropped building external virtualbox guest modules because they are now included into the mainline kernel - offed building VirtualBox host modules, I do not know how to keep these binary modules in sync with userspace part of VirtualBox, users can continue using dkms - offed building kernel-shredder because it is not buildable on kernel 5.10 - updated rtl8821ce and added a dependency from its "blacklist" subpackage here (see https://github.com/tomaspinho/rtl8821ce/commit/14b536f0) It is not clear if kernel 5.10 will receive an LTS support longer than 5.4 or not. Support until Dec, 2022 is declared right now at https://www.kernel.org/category/releases.html for 5.10 and until Dec, 2025 - for 5.4. TODO: update kernel configs TODO: solve problems with version of virtualbox host modules not matching version of the virtualbox package after virtualbox is updated but kernel is not rebuilt
39578 lines
1,015 KiB
Diff
39578 lines
1,015 KiB
Diff
From 71f7f6257f0bd1985837051f528c489e047b5678 Mon Sep 17 00:00:00 2001
|
|
From: Mikhail Novosyolov <m.novosyolov@rosalinux.ru>
|
|
Date: Sun, 20 Dec 2020 18:28:09 +0300
|
|
Subject: [PATCH] Apply AUFS 5
|
|
|
|
Rediffed from https://abf.io/kernels_stable/kernel-5.9/raw/22b55ffc8e/fs-aufs.patch
|
|
|
|
Signed-off-by: Mikhail Novosyolov <m.novosyolov@rosalinux.ru>
|
|
---
|
|
Documentation/ABI/testing/debugfs-aufs | 55 +
|
|
Documentation/ABI/testing/sysfs-aufs | 31 +
|
|
Documentation/filesystems/aufs/README | 401 ++++
|
|
.../filesystems/aufs/design/01intro.txt | 171 ++
|
|
.../filesystems/aufs/design/02struct.txt | 258 +++
|
|
.../filesystems/aufs/design/03atomic_open.txt | 85 +
|
|
.../filesystems/aufs/design/03lookup.txt | 113 +
|
|
.../filesystems/aufs/design/04branch.txt | 74 +
|
|
.../filesystems/aufs/design/05wbr_policy.txt | 64 +
|
|
.../filesystems/aufs/design/06dirren.dot | 31 +
|
|
.../filesystems/aufs/design/06dirren.txt | 102 +
|
|
.../filesystems/aufs/design/06fhsm.txt | 120 +
|
|
.../filesystems/aufs/design/06mmap.txt | 72 +
|
|
.../filesystems/aufs/design/06xattr.txt | 96 +
|
|
.../filesystems/aufs/design/07export.txt | 58 +
|
|
.../filesystems/aufs/design/08shwh.txt | 52 +
|
|
.../filesystems/aufs/design/10dynop.txt | 47 +
|
|
MAINTAINERS | 13 +
|
|
drivers/block/loop.c | 18 +
|
|
fs/Kconfig | 1 +
|
|
fs/Makefile | 1 +
|
|
fs/aufs/Kconfig | 199 ++
|
|
fs/aufs/Makefile | 46 +
|
|
fs/aufs/aufs.h | 62 +
|
|
fs/aufs/branch.c | 1427 ++++++++++++
|
|
fs/aufs/branch.h | 366 +++
|
|
fs/aufs/conf.mk | 40 +
|
|
fs/aufs/cpup.c | 1458 ++++++++++++
|
|
fs/aufs/cpup.h | 100 +
|
|
fs/aufs/dbgaufs.c | 526 +++++
|
|
fs/aufs/dbgaufs.h | 53 +
|
|
fs/aufs/dcsub.c | 225 ++
|
|
fs/aufs/dcsub.h | 137 ++
|
|
fs/aufs/debug.c | 441 ++++
|
|
fs/aufs/debug.h | 226 ++
|
|
fs/aufs/dentry.c | 1154 ++++++++++
|
|
fs/aufs/dentry.h | 268 +++
|
|
fs/aufs/dinfo.c | 554 +++++
|
|
fs/aufs/dir.c | 763 +++++++
|
|
fs/aufs/dir.h | 134 ++
|
|
fs/aufs/dirren.c | 1316 +++++++++++
|
|
fs/aufs/dirren.h | 140 ++
|
|
fs/aufs/dynop.c | 367 +++
|
|
fs/aufs/dynop.h | 77 +
|
|
fs/aufs/export.c | 838 +++++++
|
|
fs/aufs/f_op.c | 819 +++++++
|
|
fs/aufs/fhsm.c | 427 ++++
|
|
fs/aufs/file.c | 863 ++++++++
|
|
fs/aufs/file.h | 342 +++
|
|
fs/aufs/finfo.c | 149 ++
|
|
fs/aufs/fstype.h | 401 ++++
|
|
fs/aufs/hbl.h | 65 +
|
|
fs/aufs/hfsnotify.c | 288 +++
|
|
fs/aufs/hfsplus.c | 60 +
|
|
fs/aufs/hnotify.c | 715 ++++++
|
|
fs/aufs/i_op.c | 1502 +++++++++++++
|
|
fs/aufs/i_op_add.c | 936 ++++++++
|
|
fs/aufs/i_op_del.c | 513 +++++
|
|
fs/aufs/i_op_ren.c | 1250 +++++++++++
|
|
fs/aufs/iinfo.c | 286 +++
|
|
fs/aufs/inode.c | 529 +++++
|
|
fs/aufs/inode.h | 698 ++++++
|
|
fs/aufs/ioctl.c | 220 ++
|
|
fs/aufs/lcnt.h | 186 ++
|
|
fs/aufs/loop.c | 148 ++
|
|
fs/aufs/loop.h | 55 +
|
|
fs/aufs/magic.mk | 31 +
|
|
fs/aufs/module.c | 273 +++
|
|
fs/aufs/module.h | 166 ++
|
|
fs/aufs/mvdown.c | 706 ++++++
|
|
fs/aufs/opts.c | 1880 ++++++++++++++++
|
|
fs/aufs/opts.h | 225 ++
|
|
fs/aufs/plink.c | 516 +++++
|
|
fs/aufs/poll.c | 51 +
|
|
fs/aufs/posix_acl.c | 105 +
|
|
fs/aufs/procfs.c | 170 ++
|
|
fs/aufs/rdu.c | 384 ++++
|
|
fs/aufs/rwsem.h | 73 +
|
|
fs/aufs/sbinfo.c | 314 +++
|
|
fs/aufs/super.c | 1047 +++++++++
|
|
fs/aufs/super.h | 589 +++++
|
|
fs/aufs/sysaufs.c | 93 +
|
|
fs/aufs/sysaufs.h | 102 +
|
|
fs/aufs/sysfs.c | 374 ++++
|
|
fs/aufs/sysrq.c | 149 ++
|
|
fs/aufs/vdir.c | 896 ++++++++
|
|
fs/aufs/vfsub.c | 895 ++++++++
|
|
fs/aufs/vfsub.h | 354 +++
|
|
fs/aufs/wbr_policy.c | 830 +++++++
|
|
fs/aufs/whout.c | 1062 +++++++++
|
|
fs/aufs/whout.h | 86 +
|
|
fs/aufs/wkq.c | 372 ++++
|
|
fs/aufs/wkq.h | 89 +
|
|
fs/aufs/xattr.c | 356 +++
|
|
fs/aufs/xino.c | 1966 +++++++++++++++++
|
|
fs/dcache.c | 4 +-
|
|
fs/exec.c | 1 +
|
|
fs/fcntl.c | 5 +-
|
|
fs/file_table.c | 2 +
|
|
fs/inode.c | 7 +-
|
|
fs/namespace.c | 9 +
|
|
fs/notify/group.c | 1 +
|
|
fs/open.c | 1 +
|
|
fs/proc/base.c | 2 +-
|
|
fs/proc/nommu.c | 5 +-
|
|
fs/proc/task_mmu.c | 7 +-
|
|
fs/proc/task_nommu.c | 5 +-
|
|
fs/read_write.c | 26 +
|
|
fs/splice.c | 12 +-
|
|
fs/sync.c | 3 +-
|
|
fs/xattr.c | 1 +
|
|
include/linux/fs.h | 10 +
|
|
include/linux/lockdep.h | 3 +
|
|
include/linux/mm.h | 22 +
|
|
include/linux/mm_types.h | 2 +
|
|
include/linux/mnt_namespace.h | 3 +
|
|
include/linux/splice.h | 6 +
|
|
include/uapi/linux/aufs_type.h | 452 ++++
|
|
kernel/fork.c | 2 +-
|
|
kernel/locking/lockdep.c | 4 +-
|
|
kernel/task_work.c | 1 +
|
|
mm/Makefile | 2 +-
|
|
mm/filemap.c | 2 +-
|
|
mm/mmap.c | 33 +-
|
|
mm/nommu.c | 10 +-
|
|
mm/prfile.c | 86 +
|
|
security/security.c | 8 +
|
|
127 files changed, 38091 insertions(+), 32 deletions(-)
|
|
create mode 100644 Documentation/ABI/testing/debugfs-aufs
|
|
create mode 100644 Documentation/ABI/testing/sysfs-aufs
|
|
create mode 100644 Documentation/filesystems/aufs/README
|
|
create mode 100644 Documentation/filesystems/aufs/design/01intro.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/02struct.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/03atomic_open.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/03lookup.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/04branch.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/05wbr_policy.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/06dirren.dot
|
|
create mode 100644 Documentation/filesystems/aufs/design/06dirren.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/06fhsm.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/06mmap.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/06xattr.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/07export.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/08shwh.txt
|
|
create mode 100644 Documentation/filesystems/aufs/design/10dynop.txt
|
|
create mode 100644 fs/aufs/Kconfig
|
|
create mode 100644 fs/aufs/Makefile
|
|
create mode 100644 fs/aufs/aufs.h
|
|
create mode 100644 fs/aufs/branch.c
|
|
create mode 100644 fs/aufs/branch.h
|
|
create mode 100644 fs/aufs/conf.mk
|
|
create mode 100644 fs/aufs/cpup.c
|
|
create mode 100644 fs/aufs/cpup.h
|
|
create mode 100644 fs/aufs/dbgaufs.c
|
|
create mode 100644 fs/aufs/dbgaufs.h
|
|
create mode 100644 fs/aufs/dcsub.c
|
|
create mode 100644 fs/aufs/dcsub.h
|
|
create mode 100644 fs/aufs/debug.c
|
|
create mode 100644 fs/aufs/debug.h
|
|
create mode 100644 fs/aufs/dentry.c
|
|
create mode 100644 fs/aufs/dentry.h
|
|
create mode 100644 fs/aufs/dinfo.c
|
|
create mode 100644 fs/aufs/dir.c
|
|
create mode 100644 fs/aufs/dir.h
|
|
create mode 100644 fs/aufs/dirren.c
|
|
create mode 100644 fs/aufs/dirren.h
|
|
create mode 100644 fs/aufs/dynop.c
|
|
create mode 100644 fs/aufs/dynop.h
|
|
create mode 100644 fs/aufs/export.c
|
|
create mode 100644 fs/aufs/f_op.c
|
|
create mode 100644 fs/aufs/fhsm.c
|
|
create mode 100644 fs/aufs/file.c
|
|
create mode 100644 fs/aufs/file.h
|
|
create mode 100644 fs/aufs/finfo.c
|
|
create mode 100644 fs/aufs/fstype.h
|
|
create mode 100644 fs/aufs/hbl.h
|
|
create mode 100644 fs/aufs/hfsnotify.c
|
|
create mode 100644 fs/aufs/hfsplus.c
|
|
create mode 100644 fs/aufs/hnotify.c
|
|
create mode 100644 fs/aufs/i_op.c
|
|
create mode 100644 fs/aufs/i_op_add.c
|
|
create mode 100644 fs/aufs/i_op_del.c
|
|
create mode 100644 fs/aufs/i_op_ren.c
|
|
create mode 100644 fs/aufs/iinfo.c
|
|
create mode 100644 fs/aufs/inode.c
|
|
create mode 100644 fs/aufs/inode.h
|
|
create mode 100644 fs/aufs/ioctl.c
|
|
create mode 100644 fs/aufs/lcnt.h
|
|
create mode 100644 fs/aufs/loop.c
|
|
create mode 100644 fs/aufs/loop.h
|
|
create mode 100644 fs/aufs/magic.mk
|
|
create mode 100644 fs/aufs/module.c
|
|
create mode 100644 fs/aufs/module.h
|
|
create mode 100644 fs/aufs/mvdown.c
|
|
create mode 100644 fs/aufs/opts.c
|
|
create mode 100644 fs/aufs/opts.h
|
|
create mode 100644 fs/aufs/plink.c
|
|
create mode 100644 fs/aufs/poll.c
|
|
create mode 100644 fs/aufs/posix_acl.c
|
|
create mode 100644 fs/aufs/procfs.c
|
|
create mode 100644 fs/aufs/rdu.c
|
|
create mode 100644 fs/aufs/rwsem.h
|
|
create mode 100644 fs/aufs/sbinfo.c
|
|
create mode 100644 fs/aufs/super.c
|
|
create mode 100644 fs/aufs/super.h
|
|
create mode 100644 fs/aufs/sysaufs.c
|
|
create mode 100644 fs/aufs/sysaufs.h
|
|
create mode 100644 fs/aufs/sysfs.c
|
|
create mode 100644 fs/aufs/sysrq.c
|
|
create mode 100644 fs/aufs/vdir.c
|
|
create mode 100644 fs/aufs/vfsub.c
|
|
create mode 100644 fs/aufs/vfsub.h
|
|
create mode 100644 fs/aufs/wbr_policy.c
|
|
create mode 100644 fs/aufs/whout.c
|
|
create mode 100644 fs/aufs/whout.h
|
|
create mode 100644 fs/aufs/wkq.c
|
|
create mode 100644 fs/aufs/wkq.h
|
|
create mode 100644 fs/aufs/xattr.c
|
|
create mode 100644 fs/aufs/xino.c
|
|
create mode 100644 include/uapi/linux/aufs_type.h
|
|
create mode 100644 mm/prfile.c
|
|
|
|
diff --git a/Documentation/ABI/testing/debugfs-aufs b/Documentation/ABI/testing/debugfs-aufs
|
|
new file mode 100644
|
|
index 000000000000..4a6694194ba6
|
|
--- /dev/null
|
|
+++ b/Documentation/ABI/testing/debugfs-aufs
|
|
@@ -0,0 +1,55 @@
|
|
+What: /debug/aufs/si_<id>/
|
|
+Date: March 2009
|
|
+Contact: J. R. Okajima <hooanon05g@gmail.com>
|
|
+Description:
|
|
+ Under /debug/aufs, a directory named si_<id> is created
|
|
+ per aufs mount, where <id> is a unique id generated
|
|
+ internally.
|
|
+
|
|
+What: /debug/aufs/si_<id>/plink
|
|
+Date: Apr 2013
|
|
+Contact: J. R. Okajima <hooanon05g@gmail.com>
|
|
+Description:
|
|
+ It has three lines and shows the information about the
|
|
+ pseudo-link. The first line is a single number
|
|
+ representing a number of buckets. The second line is a
|
|
+ number of pseudo-links per buckets (separated by a
|
|
+ blank). The last line is a single number representing a
|
|
+ total number of psedo-links.
|
|
+ When the aufs mount option 'noplink' is specified, it
|
|
+ will show "1\n0\n0\n".
|
|
+
|
|
+What: /debug/aufs/si_<id>/xib
|
|
+Date: March 2009
|
|
+Contact: J. R. Okajima <hooanon05g@gmail.com>
|
|
+Description:
|
|
+ It shows the consumed blocks by xib (External Inode Number
|
|
+ Bitmap), its block size and file size.
|
|
+ When the aufs mount option 'noxino' is specified, it
|
|
+ will be empty. About XINO files, see the aufs manual.
|
|
+
|
|
+What: /debug/aufs/si_<id>/xi0, xi1 ... xiN and xiN-N
|
|
+Date: March 2009
|
|
+Contact: J. R. Okajima <hooanon05g@gmail.com>
|
|
+Description:
|
|
+ It shows the consumed blocks by xino (External Inode Number
|
|
+ Translation Table), its link count, block size and file
|
|
+ size.
|
|
+ Due to the file size limit, there may exist multiple
|
|
+ xino files per branch. In this case, "-N" is added to
|
|
+ the filename and it corresponds to the index of the
|
|
+ internal xino array. "-0" is omitted.
|
|
+ When the aufs mount option 'noxino' is specified, Those
|
|
+ entries won't exist. About XINO files, see the aufs
|
|
+ manual.
|
|
+
|
|
+What: /debug/aufs/si_<id>/xigen
|
|
+Date: March 2009
|
|
+Contact: J. R. Okajima <hooanon05g@gmail.com>
|
|
+Description:
|
|
+ It shows the consumed blocks by xigen (External Inode
|
|
+ Generation Table), its block size and file size.
|
|
+ If CONFIG_AUFS_EXPORT is disabled, this entry will not
|
|
+ be created.
|
|
+ When the aufs mount option 'noxino' is specified, it
|
|
+ will be empty. About XINO files, see the aufs manual.
|
|
diff --git a/Documentation/ABI/testing/sysfs-aufs b/Documentation/ABI/testing/sysfs-aufs
|
|
new file mode 100644
|
|
index 000000000000..82f9518495ea
|
|
--- /dev/null
|
|
+++ b/Documentation/ABI/testing/sysfs-aufs
|
|
@@ -0,0 +1,31 @@
|
|
+What: /sys/fs/aufs/si_<id>/
|
|
+Date: March 2009
|
|
+Contact: J. R. Okajima <hooanon05g@gmail.com>
|
|
+Description:
|
|
+ Under /sys/fs/aufs, a directory named si_<id> is created
|
|
+ per aufs mount, where <id> is a unique id generated
|
|
+ internally.
|
|
+
|
|
+What: /sys/fs/aufs/si_<id>/br0, br1 ... brN
|
|
+Date: March 2009
|
|
+Contact: J. R. Okajima <hooanon05g@gmail.com>
|
|
+Description:
|
|
+ It shows the abolute path of a member directory (which
|
|
+ is called branch) in aufs, and its permission.
|
|
+
|
|
+What: /sys/fs/aufs/si_<id>/brid0, brid1 ... bridN
|
|
+Date: July 2013
|
|
+Contact: J. R. Okajima <hooanon05g@gmail.com>
|
|
+Description:
|
|
+ It shows the id of a member directory (which is called
|
|
+ branch) in aufs.
|
|
+
|
|
+What: /sys/fs/aufs/si_<id>/xi_path
|
|
+Date: March 2009
|
|
+Contact: J. R. Okajima <hooanon05g@gmail.com>
|
|
+Description:
|
|
+ It shows the abolute path of XINO (External Inode Number
|
|
+ Bitmap, Translation Table and Generation Table) file
|
|
+ even if it is the default path.
|
|
+ When the aufs mount option 'noxino' is specified, it
|
|
+ will be empty. About XINO files, see the aufs manual.
|
|
diff --git a/Documentation/filesystems/aufs/README b/Documentation/filesystems/aufs/README
|
|
new file mode 100644
|
|
index 000000000000..3e655d357134
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/README
|
|
@@ -0,0 +1,401 @@
|
|
+
|
|
+Aufs5 -- advanced multi layered unification filesystem version 5.x
|
|
+http://aufs.sf.net
|
|
+Junjiro R. Okajima
|
|
+
|
|
+
|
|
+0. Introduction
|
|
+----------------------------------------
|
|
+In the early days, aufs was entirely re-designed and re-implemented
|
|
+Unionfs Version 1.x series. Adding many original ideas, approaches,
|
|
+improvements and implementations, it became totally different from
|
|
+Unionfs while keeping the basic features.
|
|
+Later, Unionfs Version 2.x series began taking some of the same
|
|
+approaches to aufs1's.
|
|
+Unionfs was being developed by Professor Erez Zadok at Stony Brook
|
|
+University and his team.
|
|
+
|
|
+Aufs5 supports linux-v5.0 and later, If you want older kernel version
|
|
+support,
|
|
+- for linux-v4.x series, try aufs4-linux.git or aufs4-standalone.git
|
|
+- for linux-v3.x series, try aufs3-linux.git or aufs3-standalone.git
|
|
+- for linux-v2.6.16 and later, try aufs2-2.6.git, aufs2-standalone.git
|
|
+ or aufs1 from CVS on SourceForge.
|
|
+
|
|
+Note: it becomes clear that "Aufs was rejected. Let's give it up."
|
|
+ According to Christoph Hellwig, linux rejects all union-type
|
|
+ filesystems but UnionMount.
|
|
+<http://marc.info/?l=linux-kernel&m=123938533724484&w=2>
|
|
+
|
|
+PS. Al Viro seems have a plan to merge aufs as well as overlayfs and
|
|
+ UnionMount, and he pointed out an issue around a directory mutex
|
|
+ lock and aufs addressed it. But it is still unsure whether aufs will
|
|
+ be merged (or any other union solution).
|
|
+<http://marc.info/?l=linux-kernel&m=136312705029295&w=1>
|
|
+
|
|
+
|
|
+1. Features
|
|
+----------------------------------------
|
|
+- unite several directories into a single virtual filesystem. The member
|
|
+ directory is called as a branch.
|
|
+- you can specify the permission flags to the branch, which are 'readonly',
|
|
+ 'readwrite' and 'whiteout-able.'
|
|
+- by upper writable branch, internal copyup and whiteout, files/dirs on
|
|
+ readonly branch are modifiable logically.
|
|
+- dynamic branch manipulation, add, del.
|
|
+- etc...
|
|
+
|
|
+Also there are many enhancements in aufs, such as:
|
|
+- test only the highest one for the directory permission (dirperm1)
|
|
+- copyup on open (coo=)
|
|
+- 'move' policy for copy-up between two writable branches, after
|
|
+ checking free space.
|
|
+- xattr, acl
|
|
+- readdir(3) in userspace.
|
|
+- keep inode number by external inode number table
|
|
+- keep the timestamps of file/dir in internal copyup operation
|
|
+- seekable directory, supporting NFS readdir.
|
|
+- whiteout is hardlinked in order to reduce the consumption of inodes
|
|
+ on branch
|
|
+- do not copyup, nor create a whiteout when it is unnecessary
|
|
+- revert a single systemcall when an error occurs in aufs
|
|
+- remount interface instead of ioctl
|
|
+- maintain /etc/mtab by an external command, /sbin/mount.aufs.
|
|
+- loopback mounted filesystem as a branch
|
|
+- kernel thread for removing the dir who has a plenty of whiteouts
|
|
+- support copyup sparse file (a file which has a 'hole' in it)
|
|
+- default permission flags for branches
|
|
+- selectable permission flags for ro branch, whether whiteout can
|
|
+ exist or not
|
|
+- export via NFS.
|
|
+- support <sysfs>/fs/aufs and <debugfs>/aufs.
|
|
+- support multiple writable branches, some policies to select one
|
|
+ among multiple writable branches.
|
|
+- a new semantics for link(2) and rename(2) to support multiple
|
|
+ writable branches.
|
|
+- no glibc changes are required.
|
|
+- pseudo hardlink (hardlink over branches)
|
|
+- allow a direct access manually to a file on branch, e.g. bypassing aufs.
|
|
+ including NFS or remote filesystem branch.
|
|
+- userspace wrapper for pathconf(3)/fpathconf(3) with _PC_LINK_MAX.
|
|
+- and more...
|
|
+
|
|
+Currently these features are dropped temporary from aufs5.
|
|
+See design/08plan.txt in detail.
|
|
+- nested mount, i.e. aufs as readonly no-whiteout branch of another aufs
|
|
+ (robr)
|
|
+- statistics of aufs thread (/sys/fs/aufs/stat)
|
|
+
|
|
+Features or just an idea in the future (see also design/*.txt),
|
|
+- reorder the branch index without del/re-add.
|
|
+- permanent xino files for NFSD
|
|
+- an option for refreshing the opened files after add/del branches
|
|
+- light version, without branch manipulation. (unnecessary?)
|
|
+- copyup in userspace
|
|
+- inotify in userspace
|
|
+- readv/writev
|
|
+
|
|
+
|
|
+2. Download
|
|
+----------------------------------------
|
|
+There are three GIT trees for aufs5, aufs5-linux.git,
|
|
+aufs5-standalone.git, and aufs-util.git. Note that there is no "5" in
|
|
+"aufs-util.git."
|
|
+While the aufs-util is always necessary, you need either of aufs5-linux
|
|
+or aufs5-standalone.
|
|
+
|
|
+The aufs5-linux tree includes the whole linux mainline GIT tree,
|
|
+git://git.kernel.org/.../torvalds/linux.git.
|
|
+And you cannot select CONFIG_AUFS_FS=m for this version, eg. you cannot
|
|
+build aufs5 as an external kernel module.
|
|
+Several extra patches are not included in this tree. Only
|
|
+aufs5-standalone tree contains them. They are described in the later
|
|
+section "Configuration and Compilation."
|
|
+
|
|
+On the other hand, the aufs5-standalone tree has only aufs source files
|
|
+and necessary patches, and you can select CONFIG_AUFS_FS=m.
|
|
+But you need to apply all aufs patches manually.
|
|
+
|
|
+You will find GIT branches whose name is in form of "aufs5.x" where "x"
|
|
+represents the linux kernel version, "linux-5.x". For instance,
|
|
+"aufs5.0" is for linux-5.0. For latest "linux-5.x-rcN", use
|
|
+"aufs5.x-rcN" branch.
|
|
+
|
|
+o aufs5-linux tree
|
|
+$ git clone --reference /your/linux/git/tree \
|
|
+ git://github.com/sfjro/aufs5-linux.git aufs5-linux.git
|
|
+- if you don't have linux GIT tree, then remove "--reference ..."
|
|
+$ cd aufs5-linux.git
|
|
+$ git checkout origin/aufs5.0
|
|
+
|
|
+Or You may want to directly git-pull aufs into your linux GIT tree, and
|
|
+leave the patch-work to GIT.
|
|
+$ cd /your/linux/git/tree
|
|
+$ git remote add aufs5 git://github.com/sfjro/aufs5-linux.git
|
|
+$ git fetch aufs5
|
|
+$ git checkout -b my5.0 v5.0
|
|
+$ (add your local change...)
|
|
+$ git pull aufs5 aufs5.0
|
|
+- now you have v5.0 + your_changes + aufs5.0 in you my5.0 branch.
|
|
+- you may need to solve some conflicts between your_changes and
|
|
+ aufs5.0. in this case, git-rerere is recommended so that you can
|
|
+ solve the similar conflicts automatically when you upgrade to 5.1 or
|
|
+ later in the future.
|
|
+
|
|
+o aufs5-standalone tree
|
|
+$ git clone git://github.com/sfjro/aufs5-standalone.git aufs5-standalone.git
|
|
+$ cd aufs5-standalone.git
|
|
+$ git checkout origin/aufs5.0
|
|
+
|
|
+o aufs-util tree
|
|
+$ git clone git://git.code.sf.net/p/aufs/aufs-util aufs-util.git
|
|
+- note that the public aufs-util.git is on SourceForge instead of
|
|
+ GitHUB.
|
|
+$ cd aufs-util.git
|
|
+$ git checkout origin/aufs5.0
|
|
+
|
|
+Note: The 5.x-rcN branch is to be used with `rc' kernel versions ONLY.
|
|
+The minor version number, 'x' in '5.x', of aufs may not always
|
|
+follow the minor version number of the kernel.
|
|
+Because changes in the kernel that cause the use of a new
|
|
+minor version number do not always require changes to aufs-util.
|
|
+
|
|
+Since aufs-util has its own minor version number, you may not be
|
|
+able to find a GIT branch in aufs-util for your kernel's
|
|
+exact minor version number.
|
|
+In this case, you should git-checkout the branch for the
|
|
+nearest lower number.
|
|
+
|
|
+For (an unreleased) example:
|
|
+If you are using "linux-5.10" and the "aufs5.10" branch
|
|
+does not exist in aufs-util repository, then "aufs5.9", "aufs5.8"
|
|
+or something numerically smaller is the branch for your kernel.
|
|
+
|
|
+Also you can view all branches by
|
|
+ $ git branch -a
|
|
+
|
|
+
|
|
+3. Configuration and Compilation
|
|
+----------------------------------------
|
|
+Make sure you have git-checkout'ed the correct branch.
|
|
+
|
|
+For aufs5-linux tree,
|
|
+- enable CONFIG_AUFS_FS.
|
|
+- set other aufs configurations if necessary.
|
|
+
|
|
+For aufs5-standalone tree,
|
|
+There are several ways to build.
|
|
+
|
|
+1.
|
|
+- apply ./aufs5-kbuild.patch to your kernel source files.
|
|
+- apply ./aufs5-base.patch too.
|
|
+- apply ./aufs5-mmap.patch too.
|
|
+- apply ./aufs5-standalone.patch too, if you have a plan to set
|
|
+ CONFIG_AUFS_FS=m. otherwise you don't need ./aufs5-standalone.patch.
|
|
+- copy ./{Documentation,fs,include/uapi/linux/aufs_type.h} files to your
|
|
+ kernel source tree. Never copy $PWD/include/uapi/linux/Kbuild.
|
|
+- enable CONFIG_AUFS_FS, you can select either
|
|
+ =m or =y.
|
|
+- and build your kernel as usual.
|
|
+- install the built kernel.
|
|
+- install the header files too by "make headers_install" to the
|
|
+ directory where you specify. By default, it is $PWD/usr.
|
|
+ "make help" shows a brief note for headers_install.
|
|
+- and reboot your system.
|
|
+
|
|
+2.
|
|
+- module only (CONFIG_AUFS_FS=m).
|
|
+- apply ./aufs5-base.patch to your kernel source files.
|
|
+- apply ./aufs5-mmap.patch too.
|
|
+- apply ./aufs5-standalone.patch too.
|
|
+- build your kernel, don't forget "make headers_install", and reboot.
|
|
+- edit ./config.mk and set other aufs configurations if necessary.
|
|
+ Note: You should read $PWD/fs/aufs/Kconfig carefully which describes
|
|
+ every aufs configurations.
|
|
+- build the module by simple "make".
|
|
+- you can specify ${KDIR} make variable which points to your kernel
|
|
+ source tree.
|
|
+- install the files
|
|
+ + run "make install" to install the aufs module, or copy the built
|
|
+ $PWD/aufs.ko to /lib/modules/... and run depmod -a (or reboot simply).
|
|
+ + run "make install_headers" (instead of headers_install) to install
|
|
+ the modified aufs header file (you can specify DESTDIR which is
|
|
+ available in aufs standalone version's Makefile only), or copy
|
|
+ $PWD/usr/include/linux/aufs_type.h to /usr/include/linux or wherever
|
|
+ you like manually. By default, the target directory is $PWD/usr.
|
|
+- no need to apply aufs5-kbuild.patch, nor copying source files to your
|
|
+ kernel source tree.
|
|
+
|
|
+Note: The header file aufs_type.h is necessary to build aufs-util
|
|
+ as well as "make headers_install" in the kernel source tree.
|
|
+ headers_install is subject to be forgotten, but it is essentially
|
|
+ necessary, not only for building aufs-util.
|
|
+ You may not meet problems without headers_install in some older
|
|
+ version though.
|
|
+
|
|
+And then,
|
|
+- read README in aufs-util, build and install it
|
|
+- note that your distribution may contain an obsoleted version of
|
|
+ aufs_type.h in /usr/include/linux or something. When you build aufs
|
|
+ utilities, make sure that your compiler refers the correct aufs header
|
|
+ file which is built by "make headers_install."
|
|
+- if you want to use readdir(3) in userspace or pathconf(3) wrapper,
|
|
+ then run "make install_ulib" too. And refer to the aufs manual in
|
|
+ detail.
|
|
+
|
|
+There several other patches in aufs5-standalone.git. They are all
|
|
+optional. When you meet some problems, they will help you.
|
|
+- aufs5-loopback.patch
|
|
+ Supports a nested loopback mount in a branch-fs. This patch is
|
|
+ unnecessary until aufs produces a message like "you may want to try
|
|
+ another patch for loopback file".
|
|
+- proc_mounts.patch
|
|
+ When there are many mountpoints and many mount(2)/umount(2) are
|
|
+ running, then /proc/mounts may not show the all mountpoints. This
|
|
+ patch makes /proc/mounts always show the full mountpoints list.
|
|
+ If you don't want to apply this patch and meet such problem, then you
|
|
+ need to increase the value of 'ProcMounts_Times' make-variable in
|
|
+ aufs-util.git as a second best solution.
|
|
+- vfs-ino.patch
|
|
+ Modifies a system global kernel internal function get_next_ino() in
|
|
+ order to stop assigning 0 for an inode-number. Not directly related to
|
|
+ aufs, but recommended generally.
|
|
+- tmpfs-idr.patch
|
|
+ Keeps the tmpfs inode number as the lowest value. Effective to reduce
|
|
+ the size of aufs XINO files for tmpfs branch. Also it prevents the
|
|
+ duplication of inode number, which is important for backup tools and
|
|
+ other utilities. When you find aufs XINO files for tmpfs branch
|
|
+ growing too much, try this patch.
|
|
+- lockdep-debug.patch
|
|
+ Because aufs is not only an ordinary filesystem (callee of VFS), but
|
|
+ also a caller of VFS functions for branch filesystems, subclassing of
|
|
+ the internal locks for LOCKDEP is necessary. LOCKDEP is a debugging
|
|
+ feature of linux kernel. If you enable CONFIG_LOCKDEP, then you will
|
|
+ need to apply this debug patch to expand several constant values.
|
|
+ If you don't know what LOCKDEP is, then you don't have apply this
|
|
+ patch.
|
|
+
|
|
+
|
|
+4. Usage
|
|
+----------------------------------------
|
|
+At first, make sure aufs-util are installed, and please read the aufs
|
|
+manual, aufs.5 in aufs-util.git tree.
|
|
+$ man -l aufs.5
|
|
+
|
|
+And then,
|
|
+$ mkdir /tmp/rw /tmp/aufs
|
|
+# mount -t aufs -o br=/tmp/rw:${HOME} none /tmp/aufs
|
|
+
|
|
+Here is another example. The result is equivalent.
|
|
+# mount -t aufs -o br=/tmp/rw=rw:${HOME}=ro none /tmp/aufs
|
|
+ Or
|
|
+# mount -t aufs -o br:/tmp/rw none /tmp/aufs
|
|
+# mount -o remount,append:${HOME} /tmp/aufs
|
|
+
|
|
+Then, you can see whole tree of your home dir through /tmp/aufs. If
|
|
+you modify a file under /tmp/aufs, the one on your home directory is
|
|
+not affected, instead the same named file will be newly created under
|
|
+/tmp/rw. And all of your modification to a file will be applied to
|
|
+the one under /tmp/rw. This is called the file based Copy on Write
|
|
+(COW) method.
|
|
+Aufs mount options are described in aufs.5.
|
|
+If you run chroot or something and make your aufs as a root directory,
|
|
+then you need to customize the shutdown script. See the aufs manual in
|
|
+detail.
|
|
+
|
|
+Additionally, there are some sample usages of aufs which are a
|
|
+diskless system with network booting, and LiveCD over NFS.
|
|
+See sample dir in CVS tree on SourceForge.
|
|
+
|
|
+
|
|
+5. Contact
|
|
+----------------------------------------
|
|
+When you have any problems or strange behaviour in aufs, please let me
|
|
+know with:
|
|
+- /proc/mounts (instead of the output of mount(8))
|
|
+- /sys/module/aufs/*
|
|
+- /sys/fs/aufs/* (if you have them)
|
|
+- /debug/aufs/* (if you have them)
|
|
+- linux kernel version
|
|
+ if your kernel is not plain, for example modified by distributor,
|
|
+ the url where i can download its source is necessary too.
|
|
+- aufs version which was printed at loading the module or booting the
|
|
+ system, instead of the date you downloaded.
|
|
+- configuration (define/undefine CONFIG_AUFS_xxx)
|
|
+- kernel configuration or /proc/config.gz (if you have it)
|
|
+- LSM (linux security module, if you are using)
|
|
+- behaviour which you think to be incorrect
|
|
+- actual operation, reproducible one is better
|
|
+- mailto: aufs-users at lists.sourceforge.net
|
|
+
|
|
+Usually, I don't watch the Public Areas(Bugs, Support Requests, Patches,
|
|
+and Feature Requests) on SourceForge. Please join and write to
|
|
+aufs-users ML.
|
|
+
|
|
+
|
|
+6. Acknowledgements
|
|
+----------------------------------------
|
|
+Thanks to everyone who have tried and are using aufs, whoever
|
|
+have reported a bug or any feedback.
|
|
+
|
|
+Especially donators:
|
|
+Tomas Matejicek(slax.org) made a donation (much more than once).
|
|
+ Since Apr 2010, Tomas M (the author of Slax and Linux Live
|
|
+ scripts) is making "doubling" donations.
|
|
+ Unfortunately I cannot list all of the donators, but I really
|
|
+ appreciate.
|
|
+ It ends Aug 2010, but the ordinary donation URL is still available.
|
|
+ <http://sourceforge.net/donate/index.php?group_id=167503>
|
|
+Dai Itasaka made a donation (2007/8).
|
|
+Chuck Smith made a donation (2008/4, 10 and 12).
|
|
+Henk Schoneveld made a donation (2008/9).
|
|
+Chih-Wei Huang, ASUS, CTC donated Eee PC 4G (2008/10).
|
|
+Francois Dupoux made a donation (2008/11).
|
|
+Bruno Cesar Ribas and Luis Carlos Erpen de Bona, C3SL serves public
|
|
+ aufs2 GIT tree (2009/2).
|
|
+William Grant made a donation (2009/3).
|
|
+Patrick Lane made a donation (2009/4).
|
|
+The Mail Archive (mail-archive.com) made donations (2009/5).
|
|
+Nippy Networks (Ed Wildgoose) made a donation (2009/7).
|
|
+New Dream Network, LLC (www.dreamhost.com) made a donation (2009/11).
|
|
+Pavel Pronskiy made a donation (2011/2).
|
|
+Iridium and Inmarsat satellite phone retailer (www.mailasail.com), Nippy
|
|
+ Networks (Ed Wildgoose) made a donation for hardware (2011/3).
|
|
+Max Lekomcev (DOM-TV project) made a donation (2011/7, 12, 2012/3, 6 and
|
|
+11).
|
|
+Sam Liddicott made a donation (2011/9).
|
|
+Era Scarecrow made a donation (2013/4).
|
|
+Bor Ratajc made a donation (2013/4).
|
|
+Alessandro Gorreta made a donation (2013/4).
|
|
+POIRETTE Marc made a donation (2013/4).
|
|
+Alessandro Gorreta made a donation (2013/4).
|
|
+lauri kasvandik made a donation (2013/5).
|
|
+"pemasu from Finland" made a donation (2013/7).
|
|
+The Parted Magic Project made a donation (2013/9 and 11).
|
|
+Pavel Barta made a donation (2013/10).
|
|
+Nikolay Pertsev made a donation (2014/5).
|
|
+James B made a donation (2014/7 and 2015/7).
|
|
+Stefano Di Biase made a donation (2014/8).
|
|
+Daniel Epellei made a donation (2015/1).
|
|
+OmegaPhil made a donation (2016/1, 2018/4).
|
|
+Tomasz Szewczyk made a donation (2016/4).
|
|
+James Burry made a donation (2016/12).
|
|
+Carsten Rose made a donation (2018/9).
|
|
+Porteus Kiosk made a donation (2018/10).
|
|
+
|
|
+Thank you very much.
|
|
+Donations are always, including future donations, very important and
|
|
+helpful for me to keep on developing aufs.
|
|
+
|
|
+
|
|
+7.
|
|
+----------------------------------------
|
|
+If you are an experienced user, no explanation is needed. Aufs is
|
|
+just a linux filesystem.
|
|
+
|
|
+
|
|
+Enjoy!
|
|
+
|
|
+# Local variables: ;
|
|
+# mode: text;
|
|
+# End: ;
|
|
diff --git a/Documentation/filesystems/aufs/design/01intro.txt b/Documentation/filesystems/aufs/design/01intro.txt
|
|
new file mode 100644
|
|
index 000000000000..47e0a01a8af2
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/01intro.txt
|
|
@@ -0,0 +1,171 @@
|
|
+
|
|
+# Copyright (C) 2005-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+Introduction
|
|
+----------------------------------------
|
|
+
|
|
+aufs [ei ju: ef es] | /ey-yoo-ef-es/ | [a u f s]
|
|
+1. abbrev. for "advanced multi-layered unification filesystem".
|
|
+2. abbrev. for "another unionfs".
|
|
+3. abbrev. for "auf das" in German which means "on the" in English.
|
|
+ Ex. "Butter aufs Brot"(G) means "butter onto bread"(E).
|
|
+ But "Filesystem aufs Filesystem" is hard to understand.
|
|
+4. abbrev. for "African Urban Fashion Show".
|
|
+
|
|
+AUFS is a filesystem with features:
|
|
+- multi layered stackable unification filesystem, the member directory
|
|
+ is called as a branch.
|
|
+- branch permission and attribute, 'readonly', 'real-readonly',
|
|
+ 'readwrite', 'whiteout-able', 'link-able whiteout', etc. and their
|
|
+ combination.
|
|
+- internal "file copy-on-write".
|
|
+- logical deletion, whiteout.
|
|
+- dynamic branch manipulation, adding, deleting and changing permission.
|
|
+- allow bypassing aufs, user's direct branch access.
|
|
+- external inode number translation table and bitmap which maintains the
|
|
+ persistent aufs inode number.
|
|
+- seekable directory, including NFS readdir.
|
|
+- file mapping, mmap and sharing pages.
|
|
+- pseudo-link, hardlink over branches.
|
|
+- loopback mounted filesystem as a branch.
|
|
+- several policies to select one among multiple writable branches.
|
|
+- revert a single systemcall when an error occurs in aufs.
|
|
+- and more...
|
|
+
|
|
+
|
|
+Multi Layered Stackable Unification Filesystem
|
|
+----------------------------------------------------------------------
|
|
+Most people already knows what it is.
|
|
+It is a filesystem which unifies several directories and provides a
|
|
+merged single directory. When users access a file, the access will be
|
|
+passed/re-directed/converted (sorry, I am not sure which English word is
|
|
+correct) to the real file on the member filesystem. The member
|
|
+filesystem is called 'lower filesystem' or 'branch' and has a mode
|
|
+'readonly' and 'readwrite.' And the deletion for a file on the lower
|
|
+readonly branch is handled by creating 'whiteout' on the upper writable
|
|
+branch.
|
|
+
|
|
+On LKML, there have been discussions about UnionMount (Jan Blunck,
|
|
+Bharata B Rao and Valerie Aurora) and Unionfs (Erez Zadok). They took
|
|
+different approaches to implement the merged-view.
|
|
+The former tries putting it into VFS, and the latter implements as a
|
|
+separate filesystem.
|
|
+(If I misunderstand about these implementations, please let me know and
|
|
+I shall correct it. Because it is a long time ago when I read their
|
|
+source files last time).
|
|
+
|
|
+UnionMount's approach will be able to small, but may be hard to share
|
|
+branches between several UnionMount since the whiteout in it is
|
|
+implemented in the inode on branch filesystem and always
|
|
+shared. According to Bharata's post, readdir does not seems to be
|
|
+finished yet.
|
|
+There are several missing features known in this implementations such as
|
|
+- for users, the inode number may change silently. eg. copy-up.
|
|
+- link(2) may break by copy-up.
|
|
+- read(2) may get an obsoleted filedata (fstat(2) too).
|
|
+- fcntl(F_SETLK) may be broken by copy-up.
|
|
+- unnecessary copy-up may happen, for example mmap(MAP_PRIVATE) after
|
|
+ open(O_RDWR).
|
|
+
|
|
+In linux-3.18, "overlay" filesystem (formerly known as "overlayfs") was
|
|
+merged into mainline. This is another implementation of UnionMount as a
|
|
+separated filesystem. All the limitations and known problems which
|
|
+UnionMount are equally inherited to "overlay" filesystem.
|
|
+
|
|
+Unionfs has a longer history. When I started implementing a stackable
|
|
+filesystem (Aug 2005), it already existed. It has virtual super_block,
|
|
+inode, dentry and file objects and they have an array pointing lower
|
|
+same kind objects. After contributing many patches for Unionfs, I
|
|
+re-started my project AUFS (Jun 2006).
|
|
+
|
|
+In AUFS, the structure of filesystem resembles to Unionfs, but I
|
|
+implemented my own ideas, approaches and enhancements and it became
|
|
+totally different one.
|
|
+
|
|
+Comparing DM snapshot and fs based implementation
|
|
+- the number of bytes to be copied between devices is much smaller.
|
|
+- the type of filesystem must be one and only.
|
|
+- the fs must be writable, no readonly fs, even for the lower original
|
|
+ device. so the compression fs will not be usable. but if we use
|
|
+ loopback mount, we may address this issue.
|
|
+ for instance,
|
|
+ mount /cdrom/squashfs.img /sq
|
|
+ losetup /sq/ext2.img
|
|
+ losetup /somewhere/cow
|
|
+ dmsetup "snapshot /dev/loop0 /dev/loop1 ..."
|
|
+- it will be difficult (or needs more operations) to extract the
|
|
+ difference between the original device and COW.
|
|
+- DM snapshot-merge may help a lot when users try merging. in the
|
|
+ fs-layer union, users will use rsync(1).
|
|
+
|
|
+You may want to read my old paper "Filesystems in LiveCD"
|
|
+(http://aufs.sourceforge.net/aufs2/report/sq/sq.pdf).
|
|
+
|
|
+
|
|
+Several characters/aspects/persona of aufs
|
|
+----------------------------------------------------------------------
|
|
+
|
|
+Aufs has several characters, aspects or persona.
|
|
+1. a filesystem, callee of VFS helper
|
|
+2. sub-VFS, caller of VFS helper for branches
|
|
+3. a virtual filesystem which maintains persistent inode number
|
|
+4. reader/writer of files on branches such like an application
|
|
+
|
|
+1. Callee of VFS Helper
|
|
+As an ordinary linux filesystem, aufs is a callee of VFS. For instance,
|
|
+unlink(2) from an application reaches sys_unlink() kernel function and
|
|
+then vfs_unlink() is called. vfs_unlink() is one of VFS helper and it
|
|
+calls filesystem specific unlink operation. Actually aufs implements the
|
|
+unlink operation but it behaves like a redirector.
|
|
+
|
|
+2. Caller of VFS Helper for Branches
|
|
+aufs_unlink() passes the unlink request to the branch filesystem as if
|
|
+it were called from VFS. So the called unlink operation of the branch
|
|
+filesystem acts as usual. As a caller of VFS helper, aufs should handle
|
|
+every necessary pre/post operation for the branch filesystem.
|
|
+- acquire the lock for the parent dir on a branch
|
|
+- lookup in a branch
|
|
+- revalidate dentry on a branch
|
|
+- mnt_want_write() for a branch
|
|
+- vfs_unlink() for a branch
|
|
+- mnt_drop_write() for a branch
|
|
+- release the lock on a branch
|
|
+
|
|
+3. Persistent Inode Number
|
|
+One of the most important issue for a filesystem is to maintain inode
|
|
+numbers. This is particularly important to support exporting a
|
|
+filesystem via NFS. Aufs is a virtual filesystem which doesn't have a
|
|
+backend block device for its own. But some storage is necessary to
|
|
+keep and maintain the inode numbers. It may be a large space and may not
|
|
+suit to keep in memory. Aufs rents some space from its first writable
|
|
+branch filesystem (by default) and creates file(s) on it. These files
|
|
+are created by aufs internally and removed soon (currently) keeping
|
|
+opened.
|
|
+Note: Because these files are removed, they are totally gone after
|
|
+ unmounting aufs. It means the inode numbers are not persistent
|
|
+ across unmount or reboot. I have a plan to make them really
|
|
+ persistent which will be important for aufs on NFS server.
|
|
+
|
|
+4. Read/Write Files Internally (copy-on-write)
|
|
+Because a branch can be readonly, when you write a file on it, aufs will
|
|
+"copy-up" it to the upper writable branch internally. And then write the
|
|
+originally requested thing to the file. Generally kernel doesn't
|
|
+open/read/write file actively. In aufs, even a single write may cause a
|
|
+internal "file copy". This behaviour is very similar to cp(1) command.
|
|
+
|
|
+Some people may think it is better to pass such work to user space
|
|
+helper, instead of doing in kernel space. Actually I am still thinking
|
|
+about it. But currently I have implemented it in kernel space.
|
|
diff --git a/Documentation/filesystems/aufs/design/02struct.txt b/Documentation/filesystems/aufs/design/02struct.txt
|
|
new file mode 100644
|
|
index 000000000000..0092b3dcfbe0
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/02struct.txt
|
|
@@ -0,0 +1,258 @@
|
|
+
|
|
+# Copyright (C) 2005-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+Basic Aufs Internal Structure
|
|
+
|
|
+Superblock/Inode/Dentry/File Objects
|
|
+----------------------------------------------------------------------
|
|
+As like an ordinary filesystem, aufs has its own
|
|
+superblock/inode/dentry/file objects. All these objects have a
|
|
+dynamically allocated array and store the same kind of pointers to the
|
|
+lower filesystem, branch.
|
|
+For example, when you build a union with one readwrite branch and one
|
|
+readonly, mounted /au, /rw and /ro respectively.
|
|
+- /au = /rw + /ro
|
|
+- /ro/fileA exists but /rw/fileA
|
|
+
|
|
+Aufs lookup operation finds /ro/fileA and gets dentry for that. These
|
|
+pointers are stored in a aufs dentry. The array in aufs dentry will be,
|
|
+- [0] = NULL (because /rw/fileA doesn't exist)
|
|
+- [1] = /ro/fileA
|
|
+
|
|
+This style of an array is essentially same to the aufs
|
|
+superblock/inode/dentry/file objects.
|
|
+
|
|
+Because aufs supports manipulating branches, ie. add/delete/change
|
|
+branches dynamically, these objects has its own generation. When
|
|
+branches are changed, the generation in aufs superblock is
|
|
+incremented. And a generation in other object are compared when it is
|
|
+accessed. When a generation in other objects are obsoleted, aufs
|
|
+refreshes the internal array.
|
|
+
|
|
+
|
|
+Superblock
|
|
+----------------------------------------------------------------------
|
|
+Additionally aufs superblock has some data for policies to select one
|
|
+among multiple writable branches, XIB files, pseudo-links and kobject.
|
|
+See below in detail.
|
|
+About the policies which supports copy-down a directory, see
|
|
+wbr_policy.txt too.
|
|
+
|
|
+
|
|
+Branch and XINO(External Inode Number Translation Table)
|
|
+----------------------------------------------------------------------
|
|
+Every branch has its own xino (external inode number translation table)
|
|
+file. The xino file is created and unlinked by aufs internally. When two
|
|
+members of a union exist on the same filesystem, they share the single
|
|
+xino file.
|
|
+The struct of a xino file is simple, just a sequence of aufs inode
|
|
+numbers which is indexed by the lower inode number.
|
|
+In the above sample, assume the inode number of /ro/fileA is i111 and
|
|
+aufs assigns the inode number i999 for fileA. Then aufs writes 999 as
|
|
+4(8) bytes at 111 * 4(8) bytes offset in the xino file.
|
|
+
|
|
+When the inode numbers are not contiguous, the xino file will be sparse
|
|
+which has a hole in it and doesn't consume as much disk space as it
|
|
+might appear. If your branch filesystem consumes disk space for such
|
|
+holes, then you should specify 'xino=' option at mounting aufs.
|
|
+
|
|
+Aufs has a mount option to free the disk blocks for such holes in XINO
|
|
+files on tmpfs or ramdisk. But it is not so effective actually. If you
|
|
+meet a problem of disk shortage due to XINO files, then you should try
|
|
+"tmpfs-ino.patch" (and "vfs-ino.patch" too) in aufs4-standalone.git.
|
|
+The patch localizes the assignment inumbers per tmpfs-mount and avoid
|
|
+the holes in XINO files.
|
|
+
|
|
+Also a writable branch has three kinds of "whiteout bases". All these
|
|
+are existed when the branch is joined to aufs, and their names are
|
|
+whiteout-ed doubly, so that users will never see their names in aufs
|
|
+hierarchy.
|
|
+1. a regular file which will be hardlinked to all whiteouts.
|
|
+2. a directory to store a pseudo-link.
|
|
+3. a directory to store an "orphan"-ed file temporary.
|
|
+
|
|
+1. Whiteout Base
|
|
+ When you remove a file on a readonly branch, aufs handles it as a
|
|
+ logical deletion and creates a whiteout on the upper writable branch
|
|
+ as a hardlink of this file in order not to consume inode on the
|
|
+ writable branch.
|
|
+2. Pseudo-link Dir
|
|
+ See below, Pseudo-link.
|
|
+3. Step-Parent Dir
|
|
+ When "fileC" exists on the lower readonly branch only and it is
|
|
+ opened and removed with its parent dir, and then user writes
|
|
+ something into it, then aufs copies-up fileC to this
|
|
+ directory. Because there is no other dir to store fileC. After
|
|
+ creating a file under this dir, the file is unlinked.
|
|
+
|
|
+Because aufs supports manipulating branches, ie. add/delete/change
|
|
+dynamically, a branch has its own id. When the branch order changes,
|
|
+aufs finds the new index by searching the branch id.
|
|
+
|
|
+
|
|
+Pseudo-link
|
|
+----------------------------------------------------------------------
|
|
+Assume "fileA" exists on the lower readonly branch only and it is
|
|
+hardlinked to "fileB" on the branch. When you write something to fileA,
|
|
+aufs copies-up it to the upper writable branch. Additionally aufs
|
|
+creates a hardlink under the Pseudo-link Directory of the writable
|
|
+branch. The inode of a pseudo-link is kept in aufs super_block as a
|
|
+simple list. If fileB is read after unlinking fileA, aufs returns
|
|
+filedata from the pseudo-link instead of the lower readonly
|
|
+branch. Because the pseudo-link is based upon the inode, to keep the
|
|
+inode number by xino (see above) is essentially necessary.
|
|
+
|
|
+All the hardlinks under the Pseudo-link Directory of the writable branch
|
|
+should be restored in a proper location later. Aufs provides a utility
|
|
+to do this. The userspace helpers executed at remounting and unmounting
|
|
+aufs by default.
|
|
+During this utility is running, it puts aufs into the pseudo-link
|
|
+maintenance mode. In this mode, only the process which began the
|
|
+maintenance mode (and its child processes) is allowed to operate in
|
|
+aufs. Some other processes which are not related to the pseudo-link will
|
|
+be allowed to run too, but the rest have to return an error or wait
|
|
+until the maintenance mode ends. If a process already acquires an inode
|
|
+mutex (in VFS), it has to return an error.
|
|
+
|
|
+
|
|
+XIB(external inode number bitmap)
|
|
+----------------------------------------------------------------------
|
|
+Addition to the xino file per a branch, aufs has an external inode number
|
|
+bitmap in a superblock object. It is also an internal file such like a
|
|
+xino file.
|
|
+It is a simple bitmap to mark whether the aufs inode number is in-use or
|
|
+not.
|
|
+To reduce the file I/O, aufs prepares a single memory page to cache xib.
|
|
+
|
|
+As well as XINO files, aufs has a feature to truncate/refresh XIB to
|
|
+reduce the number of consumed disk blocks for these files.
|
|
+
|
|
+
|
|
+Virtual or Vertical Dir, and Readdir in Userspace
|
|
+----------------------------------------------------------------------
|
|
+In order to support multiple layers (branches), aufs readdir operation
|
|
+constructs a virtual dir block on memory. For readdir, aufs calls
|
|
+vfs_readdir() internally for each dir on branches, merges their entries
|
|
+with eliminating the whiteout-ed ones, and sets it to file (dir)
|
|
+object. So the file object has its entry list until it is closed. The
|
|
+entry list will be updated when the file position is zero and becomes
|
|
+obsoleted. This decision is made in aufs automatically.
|
|
+
|
|
+The dynamically allocated memory block for the name of entries has a
|
|
+unit of 512 bytes (by default) and stores the names contiguously (no
|
|
+padding). Another block for each entry is handled by kmem_cache too.
|
|
+During building dir blocks, aufs creates hash list and judging whether
|
|
+the entry is whiteouted by its upper branch or already listed.
|
|
+The merged result is cached in the corresponding inode object and
|
|
+maintained by a customizable life-time option.
|
|
+
|
|
+Some people may call it can be a security hole or invite DoS attack
|
|
+since the opened and once readdir-ed dir (file object) holds its entry
|
|
+list and becomes a pressure for system memory. But I'd say it is similar
|
|
+to files under /proc or /sys. The virtual files in them also holds a
|
|
+memory page (generally) while they are opened. When an idea to reduce
|
|
+memory for them is introduced, it will be applied to aufs too.
|
|
+For those who really hate this situation, I've developed readdir(3)
|
|
+library which operates this merging in userspace. You just need to set
|
|
+LD_PRELOAD environment variable, and aufs will not consume no memory in
|
|
+kernel space for readdir(3).
|
|
+
|
|
+
|
|
+Workqueue
|
|
+----------------------------------------------------------------------
|
|
+Aufs sometimes requires privilege access to a branch. For instance,
|
|
+in copy-up/down operation. When a user process is going to make changes
|
|
+to a file which exists in the lower readonly branch only, and the mode
|
|
+of one of ancestor directories may not be writable by a user
|
|
+process. Here aufs copy-up the file with its ancestors and they may
|
|
+require privilege to set its owner/group/mode/etc.
|
|
+This is a typical case of a application character of aufs (see
|
|
+Introduction).
|
|
+
|
|
+Aufs uses workqueue synchronously for this case. It creates its own
|
|
+workqueue. The workqueue is a kernel thread and has privilege. Aufs
|
|
+passes the request to call mkdir or write (for example), and wait for
|
|
+its completion. This approach solves a problem of a signal handler
|
|
+simply.
|
|
+If aufs didn't adopt the workqueue and changed the privilege of the
|
|
+process, then the process may receive the unexpected SIGXFSZ or other
|
|
+signals.
|
|
+
|
|
+Also aufs uses the system global workqueue ("events" kernel thread) too
|
|
+for asynchronous tasks, such like handling inotify/fsnotify, re-creating a
|
|
+whiteout base and etc. This is unrelated to a privilege.
|
|
+Most of aufs operation tries acquiring a rw_semaphore for aufs
|
|
+superblock at the beginning, at the same time waits for the completion
|
|
+of all queued asynchronous tasks.
|
|
+
|
|
+
|
|
+Whiteout
|
|
+----------------------------------------------------------------------
|
|
+The whiteout in aufs is very similar to Unionfs's. That is represented
|
|
+by its filename. UnionMount takes an approach of a file mode, but I am
|
|
+afraid several utilities (find(1) or something) will have to support it.
|
|
+
|
|
+Basically the whiteout represents "logical deletion" which stops aufs to
|
|
+lookup further, but also it represents "dir is opaque" which also stop
|
|
+further lookup.
|
|
+
|
|
+In aufs, rmdir(2) and rename(2) for dir uses whiteout alternatively.
|
|
+In order to make several functions in a single systemcall to be
|
|
+revertible, aufs adopts an approach to rename a directory to a temporary
|
|
+unique whiteouted name.
|
|
+For example, in rename(2) dir where the target dir already existed, aufs
|
|
+renames the target dir to a temporary unique whiteouted name before the
|
|
+actual rename on a branch, and then handles other actions (make it opaque,
|
|
+update the attributes, etc). If an error happens in these actions, aufs
|
|
+simply renames the whiteouted name back and returns an error. If all are
|
|
+succeeded, aufs registers a function to remove the whiteouted unique
|
|
+temporary name completely and asynchronously to the system global
|
|
+workqueue.
|
|
+
|
|
+
|
|
+Copy-up
|
|
+----------------------------------------------------------------------
|
|
+It is a well-known feature or concept.
|
|
+When user modifies a file on a readonly branch, aufs operate "copy-up"
|
|
+internally and makes change to the new file on the upper writable branch.
|
|
+When the trigger systemcall does not update the timestamps of the parent
|
|
+dir, aufs reverts it after copy-up.
|
|
+
|
|
+
|
|
+Move-down (aufs3.9 and later)
|
|
+----------------------------------------------------------------------
|
|
+"Copy-up" is one of the essential feature in aufs. It copies a file from
|
|
+the lower readonly branch to the upper writable branch when a user
|
|
+changes something about the file.
|
|
+"Move-down" is an opposite action of copy-up. Basically this action is
|
|
+ran manually instead of automatically and internally.
|
|
+For desgin and implementation, aufs has to consider these issues.
|
|
+- whiteout for the file may exist on the lower branch.
|
|
+- ancestor directories may not exist on the lower branch.
|
|
+- diropq for the ancestor directories may exist on the upper branch.
|
|
+- free space on the lower branch will reduce.
|
|
+- another access to the file may happen during moving-down, including
|
|
+ UDBA (see "Revalidate Dentry and UDBA").
|
|
+- the file should not be hard-linked nor pseudo-linked. they should be
|
|
+ handled by auplink utility later.
|
|
+
|
|
+Sometimes users want to move-down a file from the upper writable branch
|
|
+to the lower readonly or writable branch. For instance,
|
|
+- the free space of the upper writable branch is going to run out.
|
|
+- create a new intermediate branch between the upper and lower branch.
|
|
+- etc.
|
|
+
|
|
+For this purpose, use "aumvdown" command in aufs-util.git.
|
|
diff --git a/Documentation/filesystems/aufs/design/03atomic_open.txt b/Documentation/filesystems/aufs/design/03atomic_open.txt
|
|
new file mode 100644
|
|
index 000000000000..fb8cf0bc8c72
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/03atomic_open.txt
|
|
@@ -0,0 +1,85 @@
|
|
+
|
|
+# Copyright (C) 2015-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+Support for a branch who has its ->atomic_open()
|
|
+----------------------------------------------------------------------
|
|
+The filesystems who implement its ->atomic_open() are not majority. For
|
|
+example NFSv4 does, and aufs should call NFSv4 ->atomic_open,
|
|
+particularly for open(O_CREAT|O_EXCL, 0400) case. Other than
|
|
+->atomic_open(), NFSv4 returns an error for this open(2). While I am not
|
|
+sure whether all filesystems who have ->atomic_open() behave like this,
|
|
+but NFSv4 surely returns the error.
|
|
+
|
|
+In order to support ->atomic_open() for aufs, there are a few
|
|
+approaches.
|
|
+
|
|
+A. Introduce aufs_atomic_open()
|
|
+ - calls one of VFS:do_last(), lookup_open() or atomic_open() for
|
|
+ branch fs.
|
|
+B. Introduce aufs_atomic_open() calling create, open and chmod. this is
|
|
+ an aufs user Pip Cet's approach
|
|
+ - calls aufs_create(), VFS finish_open() and notify_change().
|
|
+ - pass fake-mode to finish_open(), and then correct the mode by
|
|
+ notify_change().
|
|
+C. Extend aufs_open() to call branch fs's ->atomic_open()
|
|
+ - no aufs_atomic_open().
|
|
+ - aufs_lookup() registers the TID to an aufs internal object.
|
|
+ - aufs_create() does nothing when the matching TID is registered, but
|
|
+ registers the mode.
|
|
+ - aufs_open() calls branch fs's ->atomic_open() when the matching
|
|
+ TID is registered.
|
|
+D. Extend aufs_open() to re-try branch fs's ->open() with superuser's
|
|
+ credential
|
|
+ - no aufs_atomic_open().
|
|
+ - aufs_create() registers the TID to an internal object. this info
|
|
+ represents "this process created this file just now."
|
|
+ - when aufs gets EACCES from branch fs's ->open(), then confirm the
|
|
+ registered TID and re-try open() with superuser's credential.
|
|
+
|
|
+Pros and cons for each approach.
|
|
+
|
|
+A.
|
|
+ - straightforward but highly depends upon VFS internal.
|
|
+ - the atomic behavaiour is kept.
|
|
+ - some of parameters such as nameidata are hard to reproduce for
|
|
+ branch fs.
|
|
+ - large overhead.
|
|
+B.
|
|
+ - easy to implement.
|
|
+ - the atomic behavaiour is lost.
|
|
+C.
|
|
+ - the atomic behavaiour is kept.
|
|
+ - dirty and tricky.
|
|
+ - VFS checks whether the file is created correctly after calling
|
|
+ ->create(), which means this approach doesn't work.
|
|
+D.
|
|
+ - easy to implement.
|
|
+ - the atomic behavaiour is lost.
|
|
+ - to open a file with superuser's credential and give it to a user
|
|
+ process is a bad idea, since the file object keeps the credential
|
|
+ in it. It may affect LSM or something. This approach doesn't work
|
|
+ either.
|
|
+
|
|
+The approach A is ideal, but it hard to implement. So here is a
|
|
+variation of A, which is to be implemented.
|
|
+
|
|
+A-1. Introduce aufs_atomic_open()
|
|
+ - calls branch fs ->atomic_open() if exists. otherwise calls
|
|
+ vfs_create() and finish_open().
|
|
+ - the demerit is that the several checks after branch fs
|
|
+ ->atomic_open() are lost. in the ordinary case, the checks are
|
|
+ done by VFS:do_last(), lookup_open() and atomic_open(). some can
|
|
+ be implemented in aufs, but not all I am afraid.
|
|
diff --git a/Documentation/filesystems/aufs/design/03lookup.txt b/Documentation/filesystems/aufs/design/03lookup.txt
|
|
new file mode 100644
|
|
index 000000000000..5c3c97f11c57
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/03lookup.txt
|
|
@@ -0,0 +1,113 @@
|
|
+
|
|
+# Copyright (C) 2005-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+Lookup in a Branch
|
|
+----------------------------------------------------------------------
|
|
+Since aufs has a character of sub-VFS (see Introduction), it operates
|
|
+lookup for branches as VFS does. It may be a heavy work. But almost all
|
|
+lookup operation in aufs is the simplest case, ie. lookup only an entry
|
|
+directly connected to its parent. Digging down the directory hierarchy
|
|
+is unnecessary. VFS has a function lookup_one_len() for that use, and
|
|
+aufs calls it.
|
|
+
|
|
+When a branch is a remote filesystem, aufs basically relies upon its
|
|
+->d_revalidate(), also aufs forces the hardest revalidate tests for
|
|
+them.
|
|
+For d_revalidate, aufs implements three levels of revalidate tests. See
|
|
+"Revalidate Dentry and UDBA" in detail.
|
|
+
|
|
+
|
|
+Test Only the Highest One for the Directory Permission (dirperm1 option)
|
|
+----------------------------------------------------------------------
|
|
+Let's try case study.
|
|
+- aufs has two branches, upper readwrite and lower readonly.
|
|
+ /au = /rw + /ro
|
|
+- "dirA" exists under /ro, but /rw. and its mode is 0700.
|
|
+- user invoked "chmod a+rx /au/dirA"
|
|
+- the internal copy-up is activated and "/rw/dirA" is created and its
|
|
+ permission bits are set to world readable.
|
|
+- then "/au/dirA" becomes world readable?
|
|
+
|
|
+In this case, /ro/dirA is still 0700 since it exists in readonly branch,
|
|
+or it may be a natively readonly filesystem. If aufs respects the lower
|
|
+branch, it should not respond readdir request from other users. But user
|
|
+allowed it by chmod. Should really aufs rejects showing the entries
|
|
+under /ro/dirA?
|
|
+
|
|
+To be honest, I don't have a good solution for this case. So aufs
|
|
+implements 'dirperm1' and 'nodirperm1' mount options, and leave it to
|
|
+users.
|
|
+When dirperm1 is specified, aufs checks only the highest one for the
|
|
+directory permission, and shows the entries. Otherwise, as usual, checks
|
|
+every dir existing on all branches and rejects the request.
|
|
+
|
|
+As a side effect, dirperm1 option improves the performance of aufs
|
|
+because the number of permission check is reduced when the number of
|
|
+branch is many.
|
|
+
|
|
+
|
|
+Revalidate Dentry and UDBA (User's Direct Branch Access)
|
|
+----------------------------------------------------------------------
|
|
+Generally VFS helpers re-validate a dentry as a part of lookup.
|
|
+0. digging down the directory hierarchy.
|
|
+1. lock the parent dir by its i_mutex.
|
|
+2. lookup the final (child) entry.
|
|
+3. revalidate it.
|
|
+4. call the actual operation (create, unlink, etc.)
|
|
+5. unlock the parent dir
|
|
+
|
|
+If the filesystem implements its ->d_revalidate() (step 3), then it is
|
|
+called. Actually aufs implements it and checks the dentry on a branch is
|
|
+still valid.
|
|
+But it is not enough. Because aufs has to release the lock for the
|
|
+parent dir on a branch at the end of ->lookup() (step 2) and
|
|
+->d_revalidate() (step 3) while the i_mutex of the aufs dir is still
|
|
+held by VFS.
|
|
+If the file on a branch is changed directly, eg. bypassing aufs, after
|
|
+aufs released the lock, then the subsequent operation may cause
|
|
+something unpleasant result.
|
|
+
|
|
+This situation is a result of VFS architecture, ->lookup() and
|
|
+->d_revalidate() is separated. But I never say it is wrong. It is a good
|
|
+design from VFS's point of view. It is just not suitable for sub-VFS
|
|
+character in aufs.
|
|
+
|
|
+Aufs supports such case by three level of revalidation which is
|
|
+selectable by user.
|
|
+1. Simple Revalidate
|
|
+ Addition to the native flow in VFS's, confirm the child-parent
|
|
+ relationship on the branch just after locking the parent dir on the
|
|
+ branch in the "actual operation" (step 4). When this validation
|
|
+ fails, aufs returns EBUSY. ->d_revalidate() (step 3) in aufs still
|
|
+ checks the validation of the dentry on branches.
|
|
+2. Monitor Changes Internally by Inotify/Fsnotify
|
|
+ Addition to above, in the "actual operation" (step 4) aufs re-lookup
|
|
+ the dentry on the branch, and returns EBUSY if it finds different
|
|
+ dentry.
|
|
+ Additionally, aufs sets the inotify/fsnotify watch for every dir on branches
|
|
+ during it is in cache. When the event is notified, aufs registers a
|
|
+ function to kernel 'events' thread by schedule_work(). And the
|
|
+ function sets some special status to the cached aufs dentry and inode
|
|
+ private data. If they are not cached, then aufs has nothing to
|
|
+ do. When the same file is accessed through aufs (step 0-3) later,
|
|
+ aufs will detect the status and refresh all necessary data.
|
|
+ In this mode, aufs has to ignore the event which is fired by aufs
|
|
+ itself.
|
|
+3. No Extra Validation
|
|
+ This is the simplest test and doesn't add any additional revalidation
|
|
+ test, and skip the revalidation in step 4. It is useful and improves
|
|
+ aufs performance when system surely hide the aufs branches from user,
|
|
+ by over-mounting something (or another method).
|
|
diff --git a/Documentation/filesystems/aufs/design/04branch.txt b/Documentation/filesystems/aufs/design/04branch.txt
|
|
new file mode 100644
|
|
index 000000000000..da5200be41a9
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/04branch.txt
|
|
@@ -0,0 +1,74 @@
|
|
+
|
|
+# Copyright (C) 2005-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+Branch Manipulation
|
|
+
|
|
+Since aufs supports dynamic branch manipulation, ie. add/remove a branch
|
|
+and changing its permission/attribute, there are a lot of works to do.
|
|
+
|
|
+
|
|
+Add a Branch
|
|
+----------------------------------------------------------------------
|
|
+o Confirm the adding dir exists outside of aufs, including loopback
|
|
+ mount, and its various attributes.
|
|
+o Initialize the xino file and whiteout bases if necessary.
|
|
+ See struct.txt.
|
|
+
|
|
+o Check the owner/group/mode of the directory
|
|
+ When the owner/group/mode of the adding directory differs from the
|
|
+ existing branch, aufs issues a warning because it may impose a
|
|
+ security risk.
|
|
+ For example, when a upper writable branch has a world writable empty
|
|
+ top directory, a malicious user can create any files on the writable
|
|
+ branch directly, like copy-up and modify manually. If something like
|
|
+ /etc/{passwd,shadow} exists on the lower readonly branch but the upper
|
|
+ writable branch, and the writable branch is world-writable, then a
|
|
+ malicious guy may create /etc/passwd on the writable branch directly
|
|
+ and the infected file will be valid in aufs.
|
|
+ I am afraid it can be a security issue, but aufs can do nothing except
|
|
+ producing a warning.
|
|
+
|
|
+
|
|
+Delete a Branch
|
|
+----------------------------------------------------------------------
|
|
+o Confirm the deleting branch is not busy
|
|
+ To be general, there is one merit to adopt "remount" interface to
|
|
+ manipulate branches. It is to discard caches. At deleting a branch,
|
|
+ aufs checks the still cached (and connected) dentries and inodes. If
|
|
+ there are any, then they are all in-use. An inode without its
|
|
+ corresponding dentry can be alive alone (for example, inotify/fsnotify case).
|
|
+
|
|
+ For the cached one, aufs checks whether the same named entry exists on
|
|
+ other branches.
|
|
+ If the cached one is a directory, because aufs provides a merged view
|
|
+ to users, as long as one dir is left on any branch aufs can show the
|
|
+ dir to users. In this case, the branch can be removed from aufs.
|
|
+ Otherwise aufs rejects deleting the branch.
|
|
+
|
|
+ If any file on the deleting branch is opened by aufs, then aufs
|
|
+ rejects deleting.
|
|
+
|
|
+
|
|
+Modify the Permission of a Branch
|
|
+----------------------------------------------------------------------
|
|
+o Re-initialize or remove the xino file and whiteout bases if necessary.
|
|
+ See struct.txt.
|
|
+
|
|
+o rw --> ro: Confirm the modifying branch is not busy
|
|
+ Aufs rejects the request if any of these conditions are true.
|
|
+ - a file on the branch is mmap-ed.
|
|
+ - a regular file on the branch is opened for write and there is no
|
|
+ same named entry on the upper branch.
|
|
diff --git a/Documentation/filesystems/aufs/design/05wbr_policy.txt b/Documentation/filesystems/aufs/design/05wbr_policy.txt
|
|
new file mode 100644
|
|
index 000000000000..0262084bf634
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/05wbr_policy.txt
|
|
@@ -0,0 +1,64 @@
|
|
+
|
|
+# Copyright (C) 2005-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+Policies to Select One among Multiple Writable Branches
|
|
+----------------------------------------------------------------------
|
|
+When the number of writable branch is more than one, aufs has to decide
|
|
+the target branch for file creation or copy-up. By default, the highest
|
|
+writable branch which has the parent (or ancestor) dir of the target
|
|
+file is chosen (top-down-parent policy).
|
|
+By user's request, aufs implements some other policies to select the
|
|
+writable branch, for file creation several policies, round-robin,
|
|
+most-free-space, and other policies. For copy-up, top-down-parent,
|
|
+bottom-up-parent, bottom-up and others.
|
|
+
|
|
+As expected, the round-robin policy selects the branch in circular. When
|
|
+you have two writable branches and creates 10 new files, 5 files will be
|
|
+created for each branch. mkdir(2) systemcall is an exception. When you
|
|
+create 10 new directories, all will be created on the same branch.
|
|
+And the most-free-space policy selects the one which has most free
|
|
+space among the writable branches. The amount of free space will be
|
|
+checked by aufs internally, and users can specify its time interval.
|
|
+
|
|
+The policies for copy-up is more simple,
|
|
+top-down-parent is equivalent to the same named on in create policy,
|
|
+bottom-up-parent selects the writable branch where the parent dir
|
|
+exists and the nearest upper one from the copyup-source,
|
|
+bottom-up selects the nearest upper writable branch from the
|
|
+copyup-source, regardless the existence of the parent dir.
|
|
+
|
|
+There are some rules or exceptions to apply these policies.
|
|
+- If there is a readonly branch above the policy-selected branch and
|
|
+ the parent dir is marked as opaque (a variation of whiteout), or the
|
|
+ target (creating) file is whiteout-ed on the upper readonly branch,
|
|
+ then the result of the policy is ignored and the target file will be
|
|
+ created on the nearest upper writable branch than the readonly branch.
|
|
+- If there is a writable branch above the policy-selected branch and
|
|
+ the parent dir is marked as opaque or the target file is whiteouted
|
|
+ on the branch, then the result of the policy is ignored and the target
|
|
+ file will be created on the highest one among the upper writable
|
|
+ branches who has diropq or whiteout. In case of whiteout, aufs removes
|
|
+ it as usual.
|
|
+- link(2) and rename(2) systemcalls are exceptions in every policy.
|
|
+ They try selecting the branch where the source exists as possible
|
|
+ since copyup a large file will take long time. If it can't be,
|
|
+ ie. the branch where the source exists is readonly, then they will
|
|
+ follow the copyup policy.
|
|
+- There is an exception for rename(2) when the target exists.
|
|
+ If the rename target exists, aufs compares the index of the branches
|
|
+ where the source and the target exists and selects the higher
|
|
+ one. If the selected branch is readonly, then aufs follows the
|
|
+ copyup policy.
|
|
diff --git a/Documentation/filesystems/aufs/design/06dirren.dot b/Documentation/filesystems/aufs/design/06dirren.dot
|
|
new file mode 100644
|
|
index 000000000000..2d62bb6dd55f
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/06dirren.dot
|
|
@@ -0,0 +1,31 @@
|
|
+
|
|
+// to view this graph, run dot(1) command in GRAPHVIZ.
|
|
+
|
|
+digraph G {
|
|
+node [shape=box];
|
|
+whinfo [label="detailed info file\n(lower_brid_root-hinum, h_inum, namelen, old name)"];
|
|
+
|
|
+node [shape=oval];
|
|
+
|
|
+aufs_rename -> whinfo [label="store/remove"];
|
|
+
|
|
+node [shape=oval];
|
|
+inode_list [label="h_inum list in branch\ncache"];
|
|
+
|
|
+node [shape=box];
|
|
+whinode [label="h_inum list file"];
|
|
+
|
|
+node [shape=oval];
|
|
+brmgmt [label="br_add/del/mod/umount"];
|
|
+
|
|
+brmgmt -> inode_list [label="create/remove"];
|
|
+brmgmt -> whinode [label="load/store"];
|
|
+
|
|
+inode_list -> whinode [style=dashed,dir=both];
|
|
+
|
|
+aufs_rename -> inode_list [label="add/del"];
|
|
+
|
|
+aufs_lookup -> inode_list [label="search"];
|
|
+
|
|
+aufs_lookup -> whinfo [label="load/remove"];
|
|
+}
|
|
diff --git a/Documentation/filesystems/aufs/design/06dirren.txt b/Documentation/filesystems/aufs/design/06dirren.txt
|
|
new file mode 100644
|
|
index 000000000000..38ae77b2c842
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/06dirren.txt
|
|
@@ -0,0 +1,102 @@
|
|
+
|
|
+# Copyright (C) 2017-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+Special handling for renaming a directory (DIRREN)
|
|
+----------------------------------------------------------------------
|
|
+First, let's assume we have a simple usecase.
|
|
+
|
|
+- /u = /rw + /ro
|
|
+- /rw/dirA exists
|
|
+- /ro/dirA and /ro/dirA/file exist too
|
|
+- there is no dirB on both branches
|
|
+- a user issues rename("dirA", "dirB")
|
|
+
|
|
+Now, what should aufs behave against this rename(2)?
|
|
+There are a few possible cases.
|
|
+
|
|
+A. returns EROFS.
|
|
+ since dirA exists on a readonly branch which cannot be renamed.
|
|
+B. returns EXDEV.
|
|
+ it is possible to copy-up dirA (only the dir itself), but the child
|
|
+ entries ("file" in this case) should not be. it must be a bad
|
|
+ approach to copy-up recursively.
|
|
+C. returns a success.
|
|
+ even the branch /ro is readonly, aufs tries renaming it. Obviously it
|
|
+ is a violation of aufs' policy.
|
|
+D. construct an extra information which indicates that /ro/dirA should
|
|
+ be handled as the name of dirB.
|
|
+ overlayfs has a similar feature called REDIRECT.
|
|
+
|
|
+Until now, aufs implements the case B only which returns EXDEV, and
|
|
+expects the userspace application behaves like mv(1) which tries
|
|
+issueing rename(2) recursively.
|
|
+
|
|
+A new aufs feature called DIRREN is introduced which implements the case
|
|
+D. There are several "extra information" added.
|
|
+
|
|
+1. detailed info per renamed directory
|
|
+ path: /rw/dirB/$AUFS_WH_DR_INFO_PFX.<lower branch-id>
|
|
+2. the inode-number list of directories on a branch
|
|
+ path: /rw/dirB/$AUFS_WH_DR_BRHINO
|
|
+
|
|
+The filename of "detailed info per directory" represents the lower
|
|
+branch, and its format is
|
|
+- a type of the branch id
|
|
+ one of these.
|
|
+ + uuid (not implemented yet)
|
|
+ + fsid
|
|
+ + dev
|
|
+- the inode-number of the branch root dir
|
|
+
|
|
+And it contains these info in a single regular file.
|
|
+- magic number
|
|
+- branch's inode-number of the logically renamed dir
|
|
+- the name of the before-renamed dir
|
|
+
|
|
+The "detailed info per directory" file is created in aufs rename(2), and
|
|
+loaded in any lookup.
|
|
+The info is considered in lookup for the matching case only. Here
|
|
+"matching" means that the root of branch (in the info filename) is same
|
|
+to the current looking-up branch. After looking-up the before-renamed
|
|
+name, the inode-number is compared. And the matched dentry is used.
|
|
+
|
|
+The "inode-number list of directories" is a regular file which contains
|
|
+simply the inode-numbers on the branch. The file is created or updated
|
|
+in removing the branch, and loaded in adding the branch. Its lifetime is
|
|
+equal to the branch.
|
|
+The list is refered in lookup, and when the current target inode is
|
|
+found in the list, the aufs tries loading the "detailed info per
|
|
+directory" and get the changed and valid name of the dir.
|
|
+
|
|
+Theoretically these "extra informaiton" may be able to be put into XATTR
|
|
+in the dir inode. But aufs doesn't choose this way because
|
|
+1. XATTR may not be supported by the branch (or its configuration)
|
|
+2. XATTR may have its size limit.
|
|
+3. XATTR may be less easy to convert than a regular file, when the
|
|
+ format of the info is changed in the future.
|
|
+At the same time, I agree that the regular file approach is much slower
|
|
+than XATTR approach. So, in the future, aufs may take the XATTR or other
|
|
+better approach.
|
|
+
|
|
+This DIRREN feature is enabled by aufs configuration, and is activated
|
|
+by a new mount option.
|
|
+
|
|
+For the more complicated case, there is a work with UDBA option, which
|
|
+is to dected the direct access to the branches (by-passing aufs) and to
|
|
+maintain the cashes in aufs. Since a single cached aufs dentry may
|
|
+contains two names, before- and after-rename, the name comparision in
|
|
+UDBA handler may not work correctly. In this case, the behaviour will be
|
|
+equivalen to udba=reval case.
|
|
diff --git a/Documentation/filesystems/aufs/design/06fhsm.txt b/Documentation/filesystems/aufs/design/06fhsm.txt
|
|
new file mode 100644
|
|
index 000000000000..df985662befb
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/06fhsm.txt
|
|
@@ -0,0 +1,120 @@
|
|
+
|
|
+# Copyright (C) 2011-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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
|
|
+
|
|
+
|
|
+File-based Hierarchical Storage Management (FHSM)
|
|
+----------------------------------------------------------------------
|
|
+Hierarchical Storage Management (or HSM) is a well-known feature in the
|
|
+storage world. Aufs provides this feature as file-based with multiple
|
|
+writable branches, based upon the principle of "Colder, the Lower".
|
|
+Here the word "colder" means that the less used files, and "lower" means
|
|
+that the position in the order of the stacked branches vertically.
|
|
+These multiple writable branches are prioritized, ie. the topmost one
|
|
+should be the fastest drive and be used heavily.
|
|
+
|
|
+o Characters in aufs FHSM story
|
|
+- aufs itself and a new branch attribute.
|
|
+- a new ioctl interface to move-down and to establish a connection with
|
|
+ the daemon ("move-down" is a converse of "copy-up").
|
|
+- userspace tool and daemon.
|
|
+
|
|
+The userspace daemon establishes a connection with aufs and waits for
|
|
+the notification. The notified information is very similar to struct
|
|
+statfs containing the number of consumed blocks and inodes.
|
|
+When the consumed blocks/inodes of a branch exceeds the user-specified
|
|
+upper watermark, the daemon activates its move-down process until the
|
|
+consumed blocks/inodes reaches the user-specified lower watermark.
|
|
+
|
|
+The actual move-down is done by aufs based upon the request from
|
|
+user-space since we need to maintain the inode number and the internal
|
|
+pointer arrays in aufs.
|
|
+
|
|
+Currently aufs FHSM handles the regular files only. Additionally they
|
|
+must not be hard-linked nor pseudo-linked.
|
|
+
|
|
+
|
|
+o Cowork of aufs and the user-space daemon
|
|
+ During the userspace daemon established the connection, aufs sends a
|
|
+ small notification to it whenever aufs writes something into the
|
|
+ writable branch. But it may cost high since aufs issues statfs(2)
|
|
+ internally. So user can specify a new option to cache the
|
|
+ info. Actually the notification is controlled by these factors.
|
|
+ + the specified cache time.
|
|
+ + classified as "force" by aufs internally.
|
|
+ Until the specified time expires, aufs doesn't send the info
|
|
+ except the forced cases. When aufs decide forcing, the info is always
|
|
+ notified to userspace.
|
|
+ For example, the number of free inodes is generally large enough and
|
|
+ the shortage of it happens rarely. So aufs doesn't force the
|
|
+ notification when creating a new file, directory and others. This is
|
|
+ the typical case which aufs doesn't force.
|
|
+ When aufs writes the actual filedata and the files consumes any of new
|
|
+ blocks, the aufs forces notifying.
|
|
+
|
|
+
|
|
+o Interfaces in aufs
|
|
+- New branch attribute.
|
|
+ + fhsm
|
|
+ Specifies that the branch is managed by FHSM feature. In other word,
|
|
+ participant in the FHSM.
|
|
+ When nofhsm is set to the branch, it will not be the source/target
|
|
+ branch of the move-down operation. This attribute is set
|
|
+ independently from coo and moo attributes, and if you want full
|
|
+ FHSM, you should specify them as well.
|
|
+- New mount option.
|
|
+ + fhsm_sec
|
|
+ Specifies a second to suppress many less important info to be
|
|
+ notified.
|
|
+- New ioctl.
|
|
+ + AUFS_CTL_FHSM_FD
|
|
+ create a new file descriptor which userspace can read the notification
|
|
+ (a subset of struct statfs) from aufs.
|
|
+- Module parameter 'brs'
|
|
+ It has to be set to 1. Otherwise the new mount option 'fhsm' will not
|
|
+ be set.
|
|
+- mount helpers /sbin/mount.aufs and /sbin/umount.aufs
|
|
+ When there are two or more branches with fhsm attributes,
|
|
+ /sbin/mount.aufs invokes the user-space daemon and /sbin/umount.aufs
|
|
+ terminates it. As a result of remounting and branch-manipulation, the
|
|
+ number of branches with fhsm attribute can be one. In this case,
|
|
+ /sbin/mount.aufs will terminate the user-space daemon.
|
|
+
|
|
+
|
|
+Finally the operation is done as these steps in kernel-space.
|
|
+- make sure that,
|
|
+ + no one else is using the file.
|
|
+ + the file is not hard-linked.
|
|
+ + the file is not pseudo-linked.
|
|
+ + the file is a regular file.
|
|
+ + the parent dir is not opaqued.
|
|
+- find the target writable branch.
|
|
+- make sure the file is not whiteout-ed by the upper (than the target)
|
|
+ branch.
|
|
+- make the parent dir on the target branch.
|
|
+- mutex lock the inode on the branch.
|
|
+- unlink the whiteout on the target branch (if exists).
|
|
+- lookup and create the whiteout-ed temporary name on the target branch.
|
|
+- copy the file as the whiteout-ed temporary name on the target branch.
|
|
+- rename the whiteout-ed temporary name to the original name.
|
|
+- unlink the file on the source branch.
|
|
+- maintain the internal pointer array and the external inode number
|
|
+ table (XINO).
|
|
+- maintain the timestamps and other attributes of the parent dir and the
|
|
+ file.
|
|
+
|
|
+And of course, in every step, an error may happen. So the operation
|
|
+should restore the original file state after an error happens.
|
|
diff --git a/Documentation/filesystems/aufs/design/06mmap.txt b/Documentation/filesystems/aufs/design/06mmap.txt
|
|
new file mode 100644
|
|
index 000000000000..9184f6710437
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/06mmap.txt
|
|
@@ -0,0 +1,72 @@
|
|
+
|
|
+# Copyright (C) 2005-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+mmap(2) -- File Memory Mapping
|
|
+----------------------------------------------------------------------
|
|
+In aufs, the file-mapped pages are handled by a branch fs directly, no
|
|
+interaction with aufs. It means aufs_mmap() calls the branch fs's
|
|
+->mmap().
|
|
+This approach is simple and good, but there is one problem.
|
|
+Under /proc, several entries show the mmapped files by its path (with
|
|
+device and inode number), and the printed path will be the path on the
|
|
+branch fs's instead of virtual aufs's.
|
|
+This is not a problem in most cases, but some utilities lsof(1) (and its
|
|
+user) may expect the path on aufs.
|
|
+
|
|
+To address this issue, aufs adds a new member called vm_prfile in struct
|
|
+vm_area_struct (and struct vm_region). The original vm_file points to
|
|
+the file on the branch fs in order to handle everything correctly as
|
|
+usual. The new vm_prfile points to a virtual file in aufs, and the
|
|
+show-functions in procfs refers to vm_prfile if it is set.
|
|
+Also we need to maintain several other places where touching vm_file
|
|
+such like
|
|
+- fork()/clone() copies vma and the reference count of vm_file is
|
|
+ incremented.
|
|
+- merging vma maintains the ref count too.
|
|
+
|
|
+This is not a good approach. It just fakes the printed path. But it
|
|
+leaves all behaviour around f_mapping unchanged. This is surely an
|
|
+advantage.
|
|
+Actually aufs had adopted another complicated approach which calls
|
|
+generic_file_mmap() and handles struct vm_operations_struct. In this
|
|
+approach, aufs met a hard problem and I could not solve it without
|
|
+switching the approach.
|
|
+
|
|
+There may be one more another approach which is
|
|
+- bind-mount the branch-root onto the aufs-root internally
|
|
+- grab the new vfsmount (ie. struct mount)
|
|
+- lazy-umount the branch-root internally
|
|
+- in open(2) the aufs-file, open the branch-file with the hidden
|
|
+ vfsmount (instead of the original branch's vfsmount)
|
|
+- ideally this "bind-mount and lazy-umount" should be done atomically,
|
|
+ but it may be possible from userspace by the mount helper.
|
|
+
|
|
+Adding the internal hidden vfsmount and using it in opening a file, the
|
|
+file path under /proc will be printed correctly. This approach looks
|
|
+smarter, but is not possible I am afraid.
|
|
+- aufs-root may be bind-mount later. when it happens, another hidden
|
|
+ vfsmount will be required.
|
|
+- it is hard to get the chance to bind-mount and lazy-umount
|
|
+ + in kernel-space, FS can have vfsmount in open(2) via
|
|
+ file->f_path, and aufs can know its vfsmount. But several locks are
|
|
+ already acquired, and if aufs tries to bind-mount and lazy-umount
|
|
+ here, then it may cause a deadlock.
|
|
+ + in user-space, bind-mount doesn't invoke the mount helper.
|
|
+- since /proc shows dev and ino, aufs has to give vma these info. it
|
|
+ means a new member vm_prinode will be necessary. this is essentially
|
|
+ equivalent to vm_prfile described above.
|
|
+
|
|
+I have to give up this "looks-smater" approach.
|
|
diff --git a/Documentation/filesystems/aufs/design/06xattr.txt b/Documentation/filesystems/aufs/design/06xattr.txt
|
|
new file mode 100644
|
|
index 000000000000..d0f6aedfe2d0
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/06xattr.txt
|
|
@@ -0,0 +1,96 @@
|
|
+
|
|
+# Copyright (C) 2014-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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
|
|
+
|
|
+
|
|
+Listing XATTR/EA and getting the value
|
|
+----------------------------------------------------------------------
|
|
+For the inode standard attributes (owner, group, timestamps, etc.), aufs
|
|
+shows the values from the topmost existing file. This behaviour is good
|
|
+for the non-dir entries since the bahaviour exactly matches the shown
|
|
+information. But for the directories, aufs considers all the same named
|
|
+entries on the lower branches. Which means, if one of the lower entry
|
|
+rejects readdir call, then aufs returns an error even if the topmost
|
|
+entry allows it. This behaviour is necessary to respect the branch fs's
|
|
+security, but can make users confused since the user-visible standard
|
|
+attributes don't match the behaviour.
|
|
+To address this issue, aufs has a mount option called dirperm1 which
|
|
+checks the permission for the topmost entry only, and ignores the lower
|
|
+entry's permission.
|
|
+
|
|
+A similar issue can happen around XATTR.
|
|
+getxattr(2) and listxattr(2) families behave as if dirperm1 option is
|
|
+always set. Otherwise these very unpleasant situation would happen.
|
|
+- listxattr(2) may return the duplicated entries.
|
|
+- users may not be able to remove or reset the XATTR forever,
|
|
+
|
|
+
|
|
+XATTR/EA support in the internal (copy,move)-(up,down)
|
|
+----------------------------------------------------------------------
|
|
+Generally the extended attributes of inode are categorized as these.
|
|
+- "security" for LSM and capability.
|
|
+- "system" for posix ACL, 'acl' mount option is required for the branch
|
|
+ fs generally.
|
|
+- "trusted" for userspace, CAP_SYS_ADMIN is required.
|
|
+- "user" for userspace, 'user_xattr' mount option is required for the
|
|
+ branch fs generally.
|
|
+
|
|
+Moreover there are some other categories. Aufs handles these rather
|
|
+unpopular categories as the ordinary ones, ie. there is no special
|
|
+condition nor exception.
|
|
+
|
|
+In copy-up, the support for XATTR on the dst branch may differ from the
|
|
+src branch. In this case, the copy-up operation will get an error and
|
|
+the original user operation which triggered the copy-up will fail. It
|
|
+can happen that even all copy-up will fail.
|
|
+When both of src and dst branches support XATTR and if an error occurs
|
|
+during copying XATTR, then the copy-up should fail obviously. That is a
|
|
+good reason and aufs should return an error to userspace. But when only
|
|
+the src branch support that XATTR, aufs should not return an error.
|
|
+For example, the src branch supports ACL but the dst branch doesn't
|
|
+because the dst branch may natively un-support it or temporary
|
|
+un-support it due to "noacl" mount option. Of course, the dst branch fs
|
|
+may NOT return an error even if the XATTR is not supported. It is
|
|
+totally up to the branch fs.
|
|
+
|
|
+Anyway when the aufs internal copy-up gets an error from the dst branch
|
|
+fs, then aufs tries removing the just copied entry and returns the error
|
|
+to the userspace. The worst case of this situation will be all copy-up
|
|
+will fail.
|
|
+
|
|
+For the copy-up operation, there two basic approaches.
|
|
+- copy the specified XATTR only (by category above), and return the
|
|
+ error unconditionally if it happens.
|
|
+- copy all XATTR, and ignore the error on the specified category only.
|
|
+
|
|
+In order to support XATTR and to implement the correct behaviour, aufs
|
|
+chooses the latter approach and introduces some new branch attributes,
|
|
+"icexsec", "icexsys", "icextr", "icexusr", and "icexoth".
|
|
+They correspond to the XATTR namespaces (see above). Additionally, to be
|
|
+convenient, "icex" is also provided which means all "icex*" attributes
|
|
+are set (here the word "icex" stands for "ignore copy-error on XATTR").
|
|
+
|
|
+The meaning of these attributes is to ignore the error from setting
|
|
+XATTR on that branch.
|
|
+Note that aufs tries copying all XATTR unconditionally, and ignores the
|
|
+error from the dst branch according to the specified attributes.
|
|
+
|
|
+Some XATTR may have its default value. The default value may come from
|
|
+the parent dir or the environment. If the default value is set at the
|
|
+file creating-time, it will be overwritten by copy-up.
|
|
+Some contradiction may happen I am afraid.
|
|
+Do we need another attribute to stop copying XATTR? I am unsure. For
|
|
+now, aufs implements the branch attributes to ignore the error.
|
|
diff --git a/Documentation/filesystems/aufs/design/07export.txt b/Documentation/filesystems/aufs/design/07export.txt
|
|
new file mode 100644
|
|
index 000000000000..6fcb00d7dbdb
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/07export.txt
|
|
@@ -0,0 +1,58 @@
|
|
+
|
|
+# Copyright (C) 2005-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+Export Aufs via NFS
|
|
+----------------------------------------------------------------------
|
|
+Here is an approach.
|
|
+- like xino/xib, add a new file 'xigen' which stores aufs inode
|
|
+ generation.
|
|
+- iget_locked(): initialize aufs inode generation for a new inode, and
|
|
+ store it in xigen file.
|
|
+- destroy_inode(): increment aufs inode generation and store it in xigen
|
|
+ file. it is necessary even if it is not unlinked, because any data of
|
|
+ inode may be changed by UDBA.
|
|
+- encode_fh(): for a root dir, simply return FILEID_ROOT. otherwise
|
|
+ build file handle by
|
|
+ + branch id (4 bytes)
|
|
+ + superblock generation (4 bytes)
|
|
+ + inode number (4 or 8 bytes)
|
|
+ + parent dir inode number (4 or 8 bytes)
|
|
+ + inode generation (4 bytes))
|
|
+ + return value of exportfs_encode_fh() for the parent on a branch (4
|
|
+ bytes)
|
|
+ + file handle for a branch (by exportfs_encode_fh())
|
|
+- fh_to_dentry():
|
|
+ + find the index of a branch from its id in handle, and check it is
|
|
+ still exist in aufs.
|
|
+ + 1st level: get the inode number from handle and search it in cache.
|
|
+ + 2nd level: if not found in cache, get the parent inode number from
|
|
+ the handle and search it in cache. and then open the found parent
|
|
+ dir, find the matching inode number by vfs_readdir() and get its
|
|
+ name, and call lookup_one_len() for the target dentry.
|
|
+ + 3rd level: if the parent dir is not cached, call
|
|
+ exportfs_decode_fh() for a branch and get the parent on a branch,
|
|
+ build a pathname of it, convert it a pathname in aufs, call
|
|
+ path_lookup(). now aufs gets a parent dir dentry, then handle it as
|
|
+ the 2nd level.
|
|
+ + to open the dir, aufs needs struct vfsmount. aufs keeps vfsmount
|
|
+ for every branch, but not itself. to get this, (currently) aufs
|
|
+ searches in current->nsproxy->mnt_ns list. it may not be a good
|
|
+ idea, but I didn't get other approach.
|
|
+ + test the generation of the gotten inode.
|
|
+- every inode operation: they may get EBUSY due to UDBA. in this case,
|
|
+ convert it into ESTALE for NFSD.
|
|
+- readdir(): call lockdep_on/off() because filldir in NFSD calls
|
|
+ lookup_one_len(), vfs_getattr(), encode_fh() and others.
|
|
diff --git a/Documentation/filesystems/aufs/design/08shwh.txt b/Documentation/filesystems/aufs/design/08shwh.txt
|
|
new file mode 100644
|
|
index 000000000000..d7e58319086b
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/08shwh.txt
|
|
@@ -0,0 +1,52 @@
|
|
+
|
|
+# Copyright (C) 2005-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+Show Whiteout Mode (shwh)
|
|
+----------------------------------------------------------------------
|
|
+Generally aufs hides the name of whiteouts. But in some cases, to show
|
|
+them is very useful for users. For instance, creating a new middle layer
|
|
+(branch) by merging existing layers.
|
|
+
|
|
+(borrowing aufs1 HOW-TO from a user, Michael Towers)
|
|
+When you have three branches,
|
|
+- Bottom: 'system', squashfs (underlying base system), read-only
|
|
+- Middle: 'mods', squashfs, read-only
|
|
+- Top: 'overlay', ram (tmpfs), read-write
|
|
+
|
|
+The top layer is loaded at boot time and saved at shutdown, to preserve
|
|
+the changes made to the system during the session.
|
|
+When larger changes have been made, or smaller changes have accumulated,
|
|
+the size of the saved top layer data grows. At this point, it would be
|
|
+nice to be able to merge the two overlay branches ('mods' and 'overlay')
|
|
+and rewrite the 'mods' squashfs, clearing the top layer and thus
|
|
+restoring save and load speed.
|
|
+
|
|
+This merging is simplified by the use of another aufs mount, of just the
|
|
+two overlay branches using the 'shwh' option.
|
|
+# mount -t aufs -o ro,shwh,br:/livesys/overlay=ro+wh:/livesys/mods=rr+wh \
|
|
+ aufs /livesys/merge_union
|
|
+
|
|
+A merged view of these two branches is then available at
|
|
+/livesys/merge_union, and the new feature is that the whiteouts are
|
|
+visible!
|
|
+Note that in 'shwh' mode the aufs mount must be 'ro', which will disable
|
|
+writing to all branches. Also the default mode for all branches is 'ro'.
|
|
+It is now possible to save the combined contents of the two overlay
|
|
+branches to a new squashfs, e.g.:
|
|
+# mksquashfs /livesys/merge_union /path/to/newmods.squash
|
|
+
|
|
+This new squashfs archive can be stored on the boot device and the
|
|
+initramfs will use it to replace the old one at the next boot.
|
|
diff --git a/Documentation/filesystems/aufs/design/10dynop.txt b/Documentation/filesystems/aufs/design/10dynop.txt
|
|
new file mode 100644
|
|
index 000000000000..d55cae285dff
|
|
--- /dev/null
|
|
+++ b/Documentation/filesystems/aufs/design/10dynop.txt
|
|
@@ -0,0 +1,47 @@
|
|
+
|
|
+# Copyright (C) 2010-2020 Junjiro R. Okajima
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; 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/>.
|
|
+
|
|
+Dynamically customizable FS operations
|
|
+----------------------------------------------------------------------
|
|
+Generally FS operations (struct inode_operations, struct
|
|
+address_space_operations, struct file_operations, etc.) are defined as
|
|
+"static const", but it never means that FS have only one set of
|
|
+operation. Some FS have multiple sets of them. For instance, ext2 has
|
|
+three sets, one for XIP, for NOBH, and for normal.
|
|
+Since aufs overrides and redirects these operations, sometimes aufs has
|
|
+to change its behaviour according to the branch FS type. More importantly
|
|
+VFS acts differently if a function (member in the struct) is set or
|
|
+not. It means aufs should have several sets of operations and select one
|
|
+among them according to the branch FS definition.
|
|
+
|
|
+In order to solve this problem and not to affect the behaviour of VFS,
|
|
+aufs defines these operations dynamically. For instance, aufs defines
|
|
+dummy direct_IO function for struct address_space_operations, but it may
|
|
+not be set to the address_space_operations actually. When the branch FS
|
|
+doesn't have it, aufs doesn't set it to its address_space_operations
|
|
+while the function definition itself is still alive. So the behaviour
|
|
+itself will not change, and it will return an error when direct_IO is
|
|
+not set.
|
|
+
|
|
+The lifetime of these dynamically generated operation object is
|
|
+maintained by aufs branch object. When the branch is removed from aufs,
|
|
+the reference counter of the object is decremented. When it reaches
|
|
+zero, the dynamically generated operation object will be freed.
|
|
+
|
|
+This approach is designed to support AIO (io_submit), Direct I/O and
|
|
+XIP (DAX) mainly.
|
|
+Currently this approach is applied to address_space_operations for
|
|
+regular files only.
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index 281de213ef47..407ae5c24566 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -3009,6 +3009,19 @@ F: include/linux/audit.h
|
|
F: include/uapi/linux/audit.h
|
|
F: kernel/audit*
|
|
|
|
+AUFS (advanced multi layered unification filesystem) FILESYSTEM
|
|
+M: "J. R. Okajima" <hooanon05g@gmail.com>
|
|
+L: aufs-users@lists.sourceforge.net (members only)
|
|
+L: linux-unionfs@vger.kernel.org
|
|
+W: http://aufs.sourceforge.net
|
|
+T: git://github.com/sfjro/aufs4-linux.git
|
|
+S: Supported
|
|
+F: Documentation/filesystems/aufs/
|
|
+F: Documentation/ABI/testing/debugfs-aufs
|
|
+F: Documentation/ABI/testing/sysfs-aufs
|
|
+F: fs/aufs/
|
|
+F: include/uapi/linux/aufs_type.h
|
|
+
|
|
AUXILIARY DISPLAY DRIVERS
|
|
M: Miguel Ojeda Sandonis <miguel.ojeda.sandonis@gmail.com>
|
|
S: Maintained
|
|
diff --git a/drivers/block/loop.c b/drivers/block/loop.c
|
|
index a58084c2ed7c..7be7ca3f5454 100644
|
|
--- a/drivers/block/loop.c
|
|
+++ b/drivers/block/loop.c
|
|
@@ -765,6 +765,24 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev,
|
|
return error;
|
|
}
|
|
|
|
+/*
|
|
+ * for AUFS
|
|
+ * no get/put for file.
|
|
+ */
|
|
+struct file *loop_backing_file(struct super_block *sb)
|
|
+{
|
|
+ struct file *ret;
|
|
+ struct loop_device *l;
|
|
+
|
|
+ ret = NULL;
|
|
+ if (MAJOR(sb->s_dev) == LOOP_MAJOR) {
|
|
+ l = sb->s_bdev->bd_disk->private_data;
|
|
+ ret = l->lo_backing_file;
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(loop_backing_file);
|
|
+
|
|
/* loop sysfs attributes */
|
|
|
|
static ssize_t loop_attr_show(struct device *dev, char *page,
|
|
diff --git a/fs/Kconfig b/fs/Kconfig
|
|
index aa4c12282301..b29bad13b249 100644
|
|
--- a/fs/Kconfig
|
|
+++ b/fs/Kconfig
|
|
@@ -288,6 +288,7 @@ source "fs/sysv/Kconfig"
|
|
source "fs/ufs/Kconfig"
|
|
source "fs/erofs/Kconfig"
|
|
source "fs/vboxsf/Kconfig"
|
|
+source "fs/aufs/Kconfig"
|
|
|
|
endif # MISC_FILESYSTEMS
|
|
|
|
diff --git a/fs/Makefile b/fs/Makefile
|
|
index 999d1a23f036..0cd76857ca76 100644
|
|
--- a/fs/Makefile
|
|
+++ b/fs/Makefile
|
|
@@ -136,3 +136,4 @@ obj-$(CONFIG_EFIVAR_FS) += efivarfs/
|
|
obj-$(CONFIG_EROFS_FS) += erofs/
|
|
obj-$(CONFIG_VBOXSF_FS) += vboxsf/
|
|
obj-$(CONFIG_ZONEFS_FS) += zonefs/
|
|
+obj-$(CONFIG_AUFS_FS) += aufs/
|
|
diff --git a/fs/aufs/Kconfig b/fs/aufs/Kconfig
|
|
new file mode 100644
|
|
index 000000000000..9f436425716a
|
|
--- /dev/null
|
|
+++ b/fs/aufs/Kconfig
|
|
@@ -0,0 +1,199 @@
|
|
+# SPDX-License-Identifier: GPL-2.0
|
|
+config AUFS_FS
|
|
+ tristate "Aufs (Advanced multi layered unification filesystem) support"
|
|
+ help
|
|
+ Aufs is a stackable unification filesystem such as Unionfs,
|
|
+ which unifies several directories and provides a merged single
|
|
+ directory.
|
|
+ In the early days, aufs was entirely re-designed and
|
|
+ re-implemented Unionfs Version 1.x series. Introducing many
|
|
+ original ideas, approaches and improvements, it becomes totally
|
|
+ different from Unionfs while keeping the basic features.
|
|
+
|
|
+if AUFS_FS
|
|
+choice
|
|
+ prompt "Maximum number of branches"
|
|
+ default AUFS_BRANCH_MAX_127
|
|
+ help
|
|
+ Specifies the maximum number of branches (or member directories)
|
|
+ in a single aufs. The larger value consumes more system
|
|
+ resources and has a minor impact to performance.
|
|
+config AUFS_BRANCH_MAX_127
|
|
+ bool "127"
|
|
+ help
|
|
+ Specifies the maximum number of branches (or member directories)
|
|
+ in a single aufs. The larger value consumes more system
|
|
+ resources and has a minor impact to performance.
|
|
+config AUFS_BRANCH_MAX_511
|
|
+ bool "511"
|
|
+ help
|
|
+ Specifies the maximum number of branches (or member directories)
|
|
+ in a single aufs. The larger value consumes more system
|
|
+ resources and has a minor impact to performance.
|
|
+config AUFS_BRANCH_MAX_1023
|
|
+ bool "1023"
|
|
+ help
|
|
+ Specifies the maximum number of branches (or member directories)
|
|
+ in a single aufs. The larger value consumes more system
|
|
+ resources and has a minor impact to performance.
|
|
+config AUFS_BRANCH_MAX_32767
|
|
+ bool "32767"
|
|
+ help
|
|
+ Specifies the maximum number of branches (or member directories)
|
|
+ in a single aufs. The larger value consumes more system
|
|
+ resources and has a minor impact to performance.
|
|
+endchoice
|
|
+
|
|
+config AUFS_SBILIST
|
|
+ bool
|
|
+ depends on AUFS_MAGIC_SYSRQ || PROC_FS
|
|
+ default y
|
|
+ help
|
|
+ Automatic configuration for internal use.
|
|
+ When aufs supports Magic SysRq or /proc, enabled automatically.
|
|
+
|
|
+config AUFS_HNOTIFY
|
|
+ bool "Detect direct branch access (bypassing aufs)"
|
|
+ help
|
|
+ If you want to modify files on branches directly, eg. bypassing aufs,
|
|
+ and want aufs to detect the changes of them fully, then enable this
|
|
+ option and use 'udba=notify' mount option.
|
|
+ Currently there is only one available configuration, "fsnotify".
|
|
+ It will have a negative impact to the performance.
|
|
+ See detail in aufs.5.
|
|
+
|
|
+choice
|
|
+ prompt "method" if AUFS_HNOTIFY
|
|
+ default AUFS_HFSNOTIFY
|
|
+config AUFS_HFSNOTIFY
|
|
+ bool "fsnotify"
|
|
+ select FSNOTIFY
|
|
+endchoice
|
|
+
|
|
+config AUFS_EXPORT
|
|
+ bool "NFS-exportable aufs"
|
|
+ depends on EXPORTFS
|
|
+ help
|
|
+ If you want to export your mounted aufs via NFS, then enable this
|
|
+ option. There are several requirements for this configuration.
|
|
+ See detail in aufs.5.
|
|
+
|
|
+config AUFS_INO_T_64
|
|
+ bool
|
|
+ depends on AUFS_EXPORT
|
|
+ depends on 64BIT && !(ALPHA || S390)
|
|
+ default y
|
|
+ help
|
|
+ Automatic configuration for internal use.
|
|
+ /* typedef unsigned long/int __kernel_ino_t */
|
|
+ /* alpha and s390x are int */
|
|
+
|
|
+config AUFS_XATTR
|
|
+ bool "support for XATTR/EA (including Security Labels)"
|
|
+ help
|
|
+ If your branch fs supports XATTR/EA and you want to make them
|
|
+ available in aufs too, then enable this opsion and specify the
|
|
+ branch attributes for EA.
|
|
+ See detail in aufs.5.
|
|
+
|
|
+config AUFS_FHSM
|
|
+ bool "File-based Hierarchical Storage Management"
|
|
+ help
|
|
+ Hierarchical Storage Management (or HSM) is a well-known feature
|
|
+ in the storage world. Aufs provides this feature as file-based.
|
|
+ with multiple branches.
|
|
+ These multiple branches are prioritized, ie. the topmost one
|
|
+ should be the fastest drive and be used heavily.
|
|
+
|
|
+config AUFS_RDU
|
|
+ bool "Readdir in userspace"
|
|
+ help
|
|
+ Aufs has two methods to provide a merged view for a directory,
|
|
+ by a user-space library and by kernel-space natively. The latter
|
|
+ is always enabled but sometimes large and slow.
|
|
+ If you enable this option, install the library in aufs2-util
|
|
+ package, and set some environment variables for your readdir(3),
|
|
+ then the work will be handled in user-space which generally
|
|
+ shows better performance in most cases.
|
|
+ See detail in aufs.5.
|
|
+
|
|
+config AUFS_DIRREN
|
|
+ bool "Workaround for rename(2)-ing a directory"
|
|
+ help
|
|
+ By default, aufs returns EXDEV error in renameing a dir who has
|
|
+ his child on the lower branch, since it is a bad idea to issue
|
|
+ rename(2) internally for every lower branch. But user may not
|
|
+ accept this behaviour. So here is a workaround to allow such
|
|
+ rename(2) and store some extra infromation on the writable
|
|
+ branch. Obviously this costs high (and I don't like it).
|
|
+ To use this feature, you need to enable this configuration AND
|
|
+ to specify the mount option `dirren.'
|
|
+ See details in aufs.5 and the design documents.
|
|
+
|
|
+config AUFS_SHWH
|
|
+ bool "Show whiteouts"
|
|
+ help
|
|
+ If you want to make the whiteouts in aufs visible, then enable
|
|
+ this option and specify 'shwh' mount option. Although it may
|
|
+ sounds like philosophy or something, but in technically it
|
|
+ simply shows the name of whiteout with keeping its behaviour.
|
|
+
|
|
+config AUFS_BR_RAMFS
|
|
+ bool "Ramfs (initramfs/rootfs) as an aufs branch"
|
|
+ help
|
|
+ If you want to use ramfs as an aufs branch fs, then enable this
|
|
+ option. Generally tmpfs is recommended.
|
|
+ Aufs prohibited them to be a branch fs by default, because
|
|
+ initramfs becomes unusable after switch_root or something
|
|
+ generally. If you sets initramfs as an aufs branch and boot your
|
|
+ system by switch_root, you will meet a problem easily since the
|
|
+ files in initramfs may be inaccessible.
|
|
+ Unless you are going to use ramfs as an aufs branch fs without
|
|
+ switch_root or something, leave it N.
|
|
+
|
|
+config AUFS_BR_FUSE
|
|
+ bool "Fuse fs as an aufs branch"
|
|
+ depends on FUSE_FS
|
|
+ select AUFS_POLL
|
|
+ help
|
|
+ If you want to use fuse-based userspace filesystem as an aufs
|
|
+ branch fs, then enable this option.
|
|
+ It implements the internal poll(2) operation which is
|
|
+ implemented by fuse only (curretnly).
|
|
+
|
|
+config AUFS_POLL
|
|
+ bool
|
|
+ help
|
|
+ Automatic configuration for internal use.
|
|
+
|
|
+config AUFS_BR_HFSPLUS
|
|
+ bool "Hfsplus as an aufs branch"
|
|
+ depends on HFSPLUS_FS
|
|
+ default y
|
|
+ help
|
|
+ If you want to use hfsplus fs as an aufs branch fs, then enable
|
|
+ this option. This option introduces a small overhead at
|
|
+ copying-up a file on hfsplus.
|
|
+
|
|
+config AUFS_BDEV_LOOP
|
|
+ bool
|
|
+ depends on BLK_DEV_LOOP
|
|
+ default y
|
|
+ help
|
|
+ Automatic configuration for internal use.
|
|
+ Convert =[ym] into =y.
|
|
+
|
|
+config AUFS_DEBUG
|
|
+ bool "Debug aufs"
|
|
+ help
|
|
+ Enable this to compile aufs internal debug code.
|
|
+ It will have a negative impact to the performance.
|
|
+
|
|
+config AUFS_MAGIC_SYSRQ
|
|
+ bool
|
|
+ depends on AUFS_DEBUG && MAGIC_SYSRQ
|
|
+ default y
|
|
+ help
|
|
+ Automatic configuration for internal use.
|
|
+ When aufs supports Magic SysRq, enabled automatically.
|
|
+endif
|
|
diff --git a/fs/aufs/Makefile b/fs/aufs/Makefile
|
|
new file mode 100644
|
|
index 000000000000..2c819a64935e
|
|
--- /dev/null
|
|
+++ b/fs/aufs/Makefile
|
|
@@ -0,0 +1,46 @@
|
|
+# SPDX-License-Identifier: GPL-2.0
|
|
+
|
|
+include ${src}/magic.mk
|
|
+ifeq (${CONFIG_AUFS_FS},m)
|
|
+include ${src}/conf.mk
|
|
+endif
|
|
+-include ${src}/priv_def.mk
|
|
+
|
|
+# cf. include/linux/kernel.h
|
|
+# enable pr_debug
|
|
+ccflags-y += -DDEBUG
|
|
+# sparse requires the full pathname
|
|
+ifdef M
|
|
+ccflags-y += -include ${M}/../../include/uapi/linux/aufs_type.h
|
|
+else
|
|
+ccflags-y += -include ${srctree}/include/uapi/linux/aufs_type.h
|
|
+endif
|
|
+
|
|
+obj-$(CONFIG_AUFS_FS) += aufs.o
|
|
+aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \
|
|
+ wkq.o vfsub.o dcsub.o \
|
|
+ cpup.o whout.o wbr_policy.o \
|
|
+ dinfo.o dentry.o \
|
|
+ dynop.o \
|
|
+ finfo.o file.o f_op.o \
|
|
+ dir.o vdir.o \
|
|
+ iinfo.o inode.o i_op.o i_op_add.o i_op_del.o i_op_ren.o \
|
|
+ mvdown.o ioctl.o
|
|
+
|
|
+# all are boolean
|
|
+aufs-$(CONFIG_PROC_FS) += procfs.o plink.o
|
|
+aufs-$(CONFIG_SYSFS) += sysfs.o
|
|
+aufs-$(CONFIG_DEBUG_FS) += dbgaufs.o
|
|
+aufs-$(CONFIG_AUFS_BDEV_LOOP) += loop.o
|
|
+aufs-$(CONFIG_AUFS_HNOTIFY) += hnotify.o
|
|
+aufs-$(CONFIG_AUFS_HFSNOTIFY) += hfsnotify.o
|
|
+aufs-$(CONFIG_AUFS_EXPORT) += export.o
|
|
+aufs-$(CONFIG_AUFS_XATTR) += xattr.o
|
|
+aufs-$(CONFIG_FS_POSIX_ACL) += posix_acl.o
|
|
+aufs-$(CONFIG_AUFS_DIRREN) += dirren.o
|
|
+aufs-$(CONFIG_AUFS_FHSM) += fhsm.o
|
|
+aufs-$(CONFIG_AUFS_POLL) += poll.o
|
|
+aufs-$(CONFIG_AUFS_RDU) += rdu.o
|
|
+aufs-$(CONFIG_AUFS_BR_HFSPLUS) += hfsplus.o
|
|
+aufs-$(CONFIG_AUFS_DEBUG) += debug.o
|
|
+aufs-$(CONFIG_AUFS_MAGIC_SYSRQ) += sysrq.o
|
|
diff --git a/fs/aufs/aufs.h b/fs/aufs/aufs.h
|
|
new file mode 100644
|
|
index 000000000000..9a573445876f
|
|
--- /dev/null
|
|
+++ b/fs/aufs/aufs.h
|
|
@@ -0,0 +1,62 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * all header files
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_H__
|
|
+#define __AUFS_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#define AuStub(type, name, body, ...) \
|
|
+ static inline type name(__VA_ARGS__) { body; }
|
|
+
|
|
+#define AuStubVoid(name, ...) \
|
|
+ AuStub(void, name, , __VA_ARGS__)
|
|
+#define AuStubInt0(name, ...) \
|
|
+ AuStub(int, name, return 0, __VA_ARGS__)
|
|
+
|
|
+#include "debug.h"
|
|
+
|
|
+#include "branch.h"
|
|
+#include "cpup.h"
|
|
+#include "dcsub.h"
|
|
+#include "dbgaufs.h"
|
|
+#include "dentry.h"
|
|
+#include "dir.h"
|
|
+#include "dirren.h"
|
|
+#include "dynop.h"
|
|
+#include "file.h"
|
|
+#include "fstype.h"
|
|
+#include "hbl.h"
|
|
+#include "inode.h"
|
|
+#include "lcnt.h"
|
|
+#include "loop.h"
|
|
+#include "module.h"
|
|
+#include "opts.h"
|
|
+#include "rwsem.h"
|
|
+#include "super.h"
|
|
+#include "sysaufs.h"
|
|
+#include "vfsub.h"
|
|
+#include "whout.h"
|
|
+#include "wkq.h"
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_H__ */
|
|
diff --git a/fs/aufs/branch.c b/fs/aufs/branch.c
|
|
new file mode 100644
|
|
index 000000000000..26d41e1bc0c3
|
|
--- /dev/null
|
|
+++ b/fs/aufs/branch.c
|
|
@@ -0,0 +1,1427 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * branch management
|
|
+ */
|
|
+
|
|
+#include <linux/compat.h>
|
|
+#include <linux/statfs.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/*
|
|
+ * free a single branch
|
|
+ */
|
|
+static void au_br_do_free(struct au_branch *br)
|
|
+{
|
|
+ int i;
|
|
+ struct au_wbr *wbr;
|
|
+ struct au_dykey **key;
|
|
+
|
|
+ au_hnotify_fin_br(br);
|
|
+ /* always, regardless the mount option */
|
|
+ au_dr_hino_free(&br->br_dirren);
|
|
+ au_xino_put(br);
|
|
+
|
|
+ AuLCntZero(au_lcnt_read(&br->br_nfiles, /*do_rev*/0));
|
|
+ au_lcnt_fin(&br->br_nfiles, /*do_sync*/0);
|
|
+ AuLCntZero(au_lcnt_read(&br->br_count, /*do_rev*/0));
|
|
+ au_lcnt_fin(&br->br_count, /*do_sync*/0);
|
|
+
|
|
+ wbr = br->br_wbr;
|
|
+ if (wbr) {
|
|
+ for (i = 0; i < AuBrWh_Last; i++)
|
|
+ dput(wbr->wbr_wh[i]);
|
|
+ AuDebugOn(atomic_read(&wbr->wbr_wh_running));
|
|
+ AuRwDestroy(&wbr->wbr_wh_rwsem);
|
|
+ }
|
|
+
|
|
+ if (br->br_fhsm) {
|
|
+ au_br_fhsm_fin(br->br_fhsm);
|
|
+ au_kfree_try_rcu(br->br_fhsm);
|
|
+ }
|
|
+
|
|
+ key = br->br_dykey;
|
|
+ for (i = 0; i < AuBrDynOp; i++, key++)
|
|
+ if (*key)
|
|
+ au_dy_put(*key);
|
|
+ else
|
|
+ break;
|
|
+
|
|
+ /* recursive lock, s_umount of branch's */
|
|
+ /* synchronize_rcu(); */ /* why? */
|
|
+ lockdep_off();
|
|
+ path_put(&br->br_path);
|
|
+ lockdep_on();
|
|
+ au_kfree_rcu(wbr);
|
|
+ au_lcnt_wait_for_fin(&br->br_nfiles);
|
|
+ au_lcnt_wait_for_fin(&br->br_count);
|
|
+ /* I don't know why, but percpu_refcount requires this */
|
|
+ /* synchronize_rcu(); */
|
|
+ au_kfree_rcu(br);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * frees all branches
|
|
+ */
|
|
+void au_br_free(struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ aufs_bindex_t bmax;
|
|
+ struct au_branch **br;
|
|
+
|
|
+ AuRwMustWriteLock(&sbinfo->si_rwsem);
|
|
+
|
|
+ bmax = sbinfo->si_bbot + 1;
|
|
+ br = sbinfo->si_branch;
|
|
+ while (bmax--)
|
|
+ au_br_do_free(*br++);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * find the index of a branch which is specified by @br_id.
|
|
+ */
|
|
+int au_br_index(struct super_block *sb, aufs_bindex_t br_id)
|
|
+{
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++)
|
|
+ if (au_sbr_id(sb, bindex) == br_id)
|
|
+ return bindex;
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * add a branch
|
|
+ */
|
|
+
|
|
+static int test_overlap(struct super_block *sb, struct dentry *h_adding,
|
|
+ struct dentry *h_root)
|
|
+{
|
|
+ if (unlikely(h_adding == h_root
|
|
+ || au_test_loopback_overlap(sb, h_adding)))
|
|
+ return 1;
|
|
+ if (h_adding->d_sb != h_root->d_sb)
|
|
+ return 0;
|
|
+ return au_test_subdir(h_adding, h_root)
|
|
+ || au_test_subdir(h_root, h_adding);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * returns a newly allocated branch. @new_nbranch is a number of branches
|
|
+ * after adding a branch.
|
|
+ */
|
|
+static struct au_branch *au_br_alloc(struct super_block *sb, int new_nbranch,
|
|
+ int perm)
|
|
+{
|
|
+ struct au_branch *add_branch;
|
|
+ struct dentry *root;
|
|
+ struct inode *inode;
|
|
+ int err;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ add_branch = kzalloc(sizeof(*add_branch), GFP_NOFS);
|
|
+ if (unlikely(!add_branch))
|
|
+ goto out;
|
|
+ add_branch->br_xino = au_xino_alloc(/*nfile*/1);
|
|
+ if (unlikely(!add_branch->br_xino))
|
|
+ goto out_br;
|
|
+ err = au_hnotify_init_br(add_branch, perm);
|
|
+ if (unlikely(err))
|
|
+ goto out_xino;
|
|
+
|
|
+ if (au_br_writable(perm)) {
|
|
+ /* may be freed separately at changing the branch permission */
|
|
+ add_branch->br_wbr = kzalloc(sizeof(*add_branch->br_wbr),
|
|
+ GFP_NOFS);
|
|
+ if (unlikely(!add_branch->br_wbr))
|
|
+ goto out_hnotify;
|
|
+ }
|
|
+
|
|
+ if (au_br_fhsm(perm)) {
|
|
+ err = au_fhsm_br_alloc(add_branch);
|
|
+ if (unlikely(err))
|
|
+ goto out_wbr;
|
|
+ }
|
|
+
|
|
+ root = sb->s_root;
|
|
+ err = au_sbr_realloc(au_sbi(sb), new_nbranch, /*may_shrink*/0);
|
|
+ if (!err)
|
|
+ err = au_di_realloc(au_di(root), new_nbranch, /*may_shrink*/0);
|
|
+ if (!err) {
|
|
+ inode = d_inode(root);
|
|
+ err = au_hinode_realloc(au_ii(inode), new_nbranch,
|
|
+ /*may_shrink*/0);
|
|
+ }
|
|
+ if (!err)
|
|
+ return add_branch; /* success */
|
|
+
|
|
+out_wbr:
|
|
+ au_kfree_rcu(add_branch->br_wbr);
|
|
+out_hnotify:
|
|
+ au_hnotify_fin_br(add_branch);
|
|
+out_xino:
|
|
+ au_xino_put(add_branch);
|
|
+out_br:
|
|
+ au_kfree_rcu(add_branch);
|
|
+out:
|
|
+ return ERR_PTR(err);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * test if the branch permission is legal or not.
|
|
+ */
|
|
+static int test_br(struct inode *inode, int brperm, char *path)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = (au_br_writable(brperm) && IS_RDONLY(inode));
|
|
+ if (!err)
|
|
+ goto out;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ pr_err("write permission for readonly mount or inode, %s\n", path);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * returns:
|
|
+ * 0: success, the caller will add it
|
|
+ * plus: success, it is already unified, the caller should ignore it
|
|
+ * minus: error
|
|
+ */
|
|
+static int test_add(struct super_block *sb, struct au_opt_add *add, int remount)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bbot, bindex;
|
|
+ struct dentry *root, *h_dentry;
|
|
+ struct inode *inode, *h_inode;
|
|
+
|
|
+ root = sb->s_root;
|
|
+ bbot = au_sbbot(sb);
|
|
+ if (unlikely(bbot >= 0
|
|
+ && au_find_dbindex(root, add->path.dentry) >= 0)) {
|
|
+ err = 1;
|
|
+ if (!remount) {
|
|
+ err = -EINVAL;
|
|
+ pr_err("%s duplicated\n", add->pathname);
|
|
+ }
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = -ENOSPC; /* -E2BIG; */
|
|
+ if (unlikely(AUFS_BRANCH_MAX <= add->bindex
|
|
+ || AUFS_BRANCH_MAX - 1 <= bbot)) {
|
|
+ pr_err("number of branches exceeded %s\n", add->pathname);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = -EDOM;
|
|
+ if (unlikely(add->bindex < 0 || bbot + 1 < add->bindex)) {
|
|
+ pr_err("bad index %d\n", add->bindex);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ inode = d_inode(add->path.dentry);
|
|
+ err = -ENOENT;
|
|
+ if (unlikely(!inode->i_nlink)) {
|
|
+ pr_err("no existence %s\n", add->pathname);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (unlikely(inode->i_sb == sb)) {
|
|
+ pr_err("%s must be outside\n", add->pathname);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (unlikely(au_test_fs_unsuppoted(inode->i_sb))) {
|
|
+ pr_err("unsupported filesystem, %s (%s)\n",
|
|
+ add->pathname, au_sbtype(inode->i_sb));
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (unlikely(inode->i_sb->s_stack_depth)) {
|
|
+ pr_err("already stacked, %s (%s)\n",
|
|
+ add->pathname, au_sbtype(inode->i_sb));
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = test_br(d_inode(add->path.dentry), add->perm, add->pathname);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ if (bbot < 0)
|
|
+ return 0; /* success */
|
|
+
|
|
+ err = -EINVAL;
|
|
+ for (bindex = 0; bindex <= bbot; bindex++)
|
|
+ if (unlikely(test_overlap(sb, add->path.dentry,
|
|
+ au_h_dptr(root, bindex)))) {
|
|
+ pr_err("%s is overlapped\n", add->pathname);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ if (au_opt_test(au_mntflags(sb), WARN_PERM)) {
|
|
+ h_dentry = au_h_dptr(root, 0);
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ if ((h_inode->i_mode & S_IALLUGO) != (inode->i_mode & S_IALLUGO)
|
|
+ || !uid_eq(h_inode->i_uid, inode->i_uid)
|
|
+ || !gid_eq(h_inode->i_gid, inode->i_gid))
|
|
+ pr_warn("uid/gid/perm %s %u/%u/0%o, %u/%u/0%o\n",
|
|
+ add->pathname,
|
|
+ i_uid_read(inode), i_gid_read(inode),
|
|
+ (inode->i_mode & S_IALLUGO),
|
|
+ i_uid_read(h_inode), i_gid_read(h_inode),
|
|
+ (h_inode->i_mode & S_IALLUGO));
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * initialize or clean the whiteouts for an adding branch
|
|
+ */
|
|
+static int au_br_init_wh(struct super_block *sb, struct au_branch *br,
|
|
+ int new_perm)
|
|
+{
|
|
+ int err, old_perm;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct inode *h_inode;
|
|
+ struct au_wbr *wbr;
|
|
+ struct au_hinode *hdir;
|
|
+ struct dentry *h_dentry;
|
|
+
|
|
+ err = vfsub_mnt_want_write(au_br_mnt(br));
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ wbr = br->br_wbr;
|
|
+ old_perm = br->br_perm;
|
|
+ br->br_perm = new_perm;
|
|
+ hdir = NULL;
|
|
+ h_inode = NULL;
|
|
+ bindex = au_br_index(sb, br->br_id);
|
|
+ if (0 <= bindex) {
|
|
+ hdir = au_hi(d_inode(sb->s_root), bindex);
|
|
+ au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT);
|
|
+ } else {
|
|
+ h_dentry = au_br_dentry(br);
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ inode_lock_nested(h_inode, AuLsc_I_PARENT);
|
|
+ }
|
|
+ if (!wbr)
|
|
+ err = au_wh_init(br, sb);
|
|
+ else {
|
|
+ wbr_wh_write_lock(wbr);
|
|
+ err = au_wh_init(br, sb);
|
|
+ wbr_wh_write_unlock(wbr);
|
|
+ }
|
|
+ if (hdir)
|
|
+ au_hn_inode_unlock(hdir);
|
|
+ else
|
|
+ inode_unlock(h_inode);
|
|
+ vfsub_mnt_drop_write(au_br_mnt(br));
|
|
+ br->br_perm = old_perm;
|
|
+
|
|
+ if (!err && wbr && !au_br_writable(new_perm)) {
|
|
+ au_kfree_rcu(wbr);
|
|
+ br->br_wbr = NULL;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_wbr_init(struct au_branch *br, struct super_block *sb,
|
|
+ int perm)
|
|
+{
|
|
+ int err;
|
|
+ struct kstatfs kst;
|
|
+ struct au_wbr *wbr;
|
|
+
|
|
+ wbr = br->br_wbr;
|
|
+ au_rw_init(&wbr->wbr_wh_rwsem);
|
|
+ atomic_set(&wbr->wbr_wh_running, 0);
|
|
+
|
|
+ /*
|
|
+ * a limit for rmdir/rename a dir
|
|
+ * cf. AUFS_MAX_NAMELEN in include/uapi/linux/aufs_type.h
|
|
+ */
|
|
+ err = vfs_statfs(&br->br_path, &kst);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = -EINVAL;
|
|
+ if (kst.f_namelen >= NAME_MAX)
|
|
+ err = au_br_init_wh(sb, br, perm);
|
|
+ else
|
|
+ pr_err("%pd(%s), unsupported namelen %ld\n",
|
|
+ au_br_dentry(br),
|
|
+ au_sbtype(au_br_dentry(br)->d_sb), kst.f_namelen);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* initialize a new branch */
|
|
+static int au_br_init(struct au_branch *br, struct super_block *sb,
|
|
+ struct au_opt_add *add)
|
|
+{
|
|
+ int err;
|
|
+ struct au_branch *brbase;
|
|
+ struct file *xf;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ err = 0;
|
|
+ br->br_perm = add->perm;
|
|
+ br->br_path = add->path; /* set first, path_get() later */
|
|
+ spin_lock_init(&br->br_dykey_lock);
|
|
+ au_lcnt_init(&br->br_nfiles, /*release*/NULL);
|
|
+ au_lcnt_init(&br->br_count, /*release*/NULL);
|
|
+ br->br_id = au_new_br_id(sb);
|
|
+ AuDebugOn(br->br_id < 0);
|
|
+
|
|
+ /* always, regardless the given option */
|
|
+ err = au_dr_br_init(sb, br, &add->path);
|
|
+ if (unlikely(err))
|
|
+ goto out_err;
|
|
+
|
|
+ if (au_br_writable(add->perm)) {
|
|
+ err = au_wbr_init(br, sb, add->perm);
|
|
+ if (unlikely(err))
|
|
+ goto out_err;
|
|
+ }
|
|
+
|
|
+ if (au_opt_test(au_mntflags(sb), XINO)) {
|
|
+ brbase = au_sbr(sb, 0);
|
|
+ xf = au_xino_file(brbase->br_xino, /*idx*/-1);
|
|
+ AuDebugOn(!xf);
|
|
+ h_inode = d_inode(add->path.dentry);
|
|
+ err = au_xino_init_br(sb, br, h_inode->i_ino, &xf->f_path);
|
|
+ if (unlikely(err)) {
|
|
+ AuDebugOn(au_xino_file(br->br_xino, /*idx*/-1));
|
|
+ goto out_err;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ sysaufs_br_init(br);
|
|
+ path_get(&br->br_path);
|
|
+ goto out; /* success */
|
|
+
|
|
+out_err:
|
|
+ memset(&br->br_path, 0, sizeof(br->br_path));
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_br_do_add_brp(struct au_sbinfo *sbinfo, aufs_bindex_t bindex,
|
|
+ struct au_branch *br, aufs_bindex_t bbot,
|
|
+ aufs_bindex_t amount)
|
|
+{
|
|
+ struct au_branch **brp;
|
|
+
|
|
+ AuRwMustWriteLock(&sbinfo->si_rwsem);
|
|
+
|
|
+ brp = sbinfo->si_branch + bindex;
|
|
+ memmove(brp + 1, brp, sizeof(*brp) * amount);
|
|
+ *brp = br;
|
|
+ sbinfo->si_bbot++;
|
|
+ if (unlikely(bbot < 0))
|
|
+ sbinfo->si_bbot = 0;
|
|
+}
|
|
+
|
|
+static void au_br_do_add_hdp(struct au_dinfo *dinfo, aufs_bindex_t bindex,
|
|
+ aufs_bindex_t bbot, aufs_bindex_t amount)
|
|
+{
|
|
+ struct au_hdentry *hdp;
|
|
+
|
|
+ AuRwMustWriteLock(&dinfo->di_rwsem);
|
|
+
|
|
+ hdp = au_hdentry(dinfo, bindex);
|
|
+ memmove(hdp + 1, hdp, sizeof(*hdp) * amount);
|
|
+ au_h_dentry_init(hdp);
|
|
+ dinfo->di_bbot++;
|
|
+ if (unlikely(bbot < 0))
|
|
+ dinfo->di_btop = 0;
|
|
+}
|
|
+
|
|
+static void au_br_do_add_hip(struct au_iinfo *iinfo, aufs_bindex_t bindex,
|
|
+ aufs_bindex_t bbot, aufs_bindex_t amount)
|
|
+{
|
|
+ struct au_hinode *hip;
|
|
+
|
|
+ AuRwMustWriteLock(&iinfo->ii_rwsem);
|
|
+
|
|
+ hip = au_hinode(iinfo, bindex);
|
|
+ memmove(hip + 1, hip, sizeof(*hip) * amount);
|
|
+ au_hinode_init(hip);
|
|
+ iinfo->ii_bbot++;
|
|
+ if (unlikely(bbot < 0))
|
|
+ iinfo->ii_btop = 0;
|
|
+}
|
|
+
|
|
+static void au_br_do_add(struct super_block *sb, struct au_branch *br,
|
|
+ aufs_bindex_t bindex)
|
|
+{
|
|
+ struct dentry *root, *h_dentry;
|
|
+ struct inode *root_inode, *h_inode;
|
|
+ aufs_bindex_t bbot, amount;
|
|
+
|
|
+ root = sb->s_root;
|
|
+ root_inode = d_inode(root);
|
|
+ bbot = au_sbbot(sb);
|
|
+ amount = bbot + 1 - bindex;
|
|
+ h_dentry = au_br_dentry(br);
|
|
+ au_sbilist_lock();
|
|
+ au_br_do_add_brp(au_sbi(sb), bindex, br, bbot, amount);
|
|
+ au_br_do_add_hdp(au_di(root), bindex, bbot, amount);
|
|
+ au_br_do_add_hip(au_ii(root_inode), bindex, bbot, amount);
|
|
+ au_set_h_dptr(root, bindex, dget(h_dentry));
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ au_set_h_iptr(root_inode, bindex, au_igrab(h_inode), /*flags*/0);
|
|
+ au_sbilist_unlock();
|
|
+}
|
|
+
|
|
+int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bbot, add_bindex;
|
|
+ struct dentry *root, *h_dentry;
|
|
+ struct inode *root_inode;
|
|
+ struct au_branch *add_branch;
|
|
+
|
|
+ root = sb->s_root;
|
|
+ root_inode = d_inode(root);
|
|
+ IMustLock(root_inode);
|
|
+ IiMustWriteLock(root_inode);
|
|
+ err = test_add(sb, add, remount);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out;
|
|
+ if (err) {
|
|
+ err = 0;
|
|
+ goto out; /* success */
|
|
+ }
|
|
+
|
|
+ bbot = au_sbbot(sb);
|
|
+ add_branch = au_br_alloc(sb, bbot + 2, add->perm);
|
|
+ err = PTR_ERR(add_branch);
|
|
+ if (IS_ERR(add_branch))
|
|
+ goto out;
|
|
+
|
|
+ err = au_br_init(add_branch, sb, add);
|
|
+ if (unlikely(err)) {
|
|
+ au_br_do_free(add_branch);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ add_bindex = add->bindex;
|
|
+ sysaufs_brs_del(sb, add_bindex); /* remove successors */
|
|
+ au_br_do_add(sb, add_branch, add_bindex);
|
|
+ sysaufs_brs_add(sb, add_bindex); /* append successors */
|
|
+ dbgaufs_brs_add(sb, add_bindex, /*topdown*/0); /* rename successors */
|
|
+
|
|
+ h_dentry = add->path.dentry;
|
|
+ if (!add_bindex) {
|
|
+ au_cpup_attr_all(root_inode, /*force*/1);
|
|
+ sb->s_maxbytes = h_dentry->d_sb->s_maxbytes;
|
|
+ } else
|
|
+ au_add_nlink(root_inode, d_inode(h_dentry));
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static unsigned long long au_farray_cb(struct super_block *sb, void *a,
|
|
+ unsigned long long max __maybe_unused,
|
|
+ void *arg)
|
|
+{
|
|
+ unsigned long long n;
|
|
+ struct file **p, *f;
|
|
+ struct hlist_bl_head *files;
|
|
+ struct hlist_bl_node *pos;
|
|
+ struct au_finfo *finfo;
|
|
+
|
|
+ n = 0;
|
|
+ p = a;
|
|
+ files = &au_sbi(sb)->si_files;
|
|
+ hlist_bl_lock(files);
|
|
+ hlist_bl_for_each_entry(finfo, pos, files, fi_hlist) {
|
|
+ f = finfo->fi_file;
|
|
+ if (file_count(f)
|
|
+ && !special_file(file_inode(f)->i_mode)) {
|
|
+ get_file(f);
|
|
+ *p++ = f;
|
|
+ n++;
|
|
+ AuDebugOn(n > max);
|
|
+ }
|
|
+ }
|
|
+ hlist_bl_unlock(files);
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static struct file **au_farray_alloc(struct super_block *sb,
|
|
+ unsigned long long *max)
|
|
+{
|
|
+ struct au_sbinfo *sbi;
|
|
+
|
|
+ sbi = au_sbi(sb);
|
|
+ *max = au_lcnt_read(&sbi->si_nfiles, /*do_rev*/1);
|
|
+ return au_array_alloc(max, au_farray_cb, sb, /*arg*/NULL);
|
|
+}
|
|
+
|
|
+static void au_farray_free(struct file **a, unsigned long long max)
|
|
+{
|
|
+ unsigned long long ull;
|
|
+
|
|
+ for (ull = 0; ull < max; ull++)
|
|
+ if (a[ull])
|
|
+ fput(a[ull]);
|
|
+ kvfree(a);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * delete a branch
|
|
+ */
|
|
+
|
|
+/* to show the line number, do not make it inlined function */
|
|
+#define AuVerbose(do_info, fmt, ...) do { \
|
|
+ if (do_info) \
|
|
+ pr_info(fmt, ##__VA_ARGS__); \
|
|
+} while (0)
|
|
+
|
|
+static int au_test_ibusy(struct inode *inode, aufs_bindex_t btop,
|
|
+ aufs_bindex_t bbot)
|
|
+{
|
|
+ return (inode && !S_ISDIR(inode->i_mode)) || btop == bbot;
|
|
+}
|
|
+
|
|
+static int au_test_dbusy(struct dentry *dentry, aufs_bindex_t btop,
|
|
+ aufs_bindex_t bbot)
|
|
+{
|
|
+ return au_test_ibusy(d_inode(dentry), btop, bbot);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * test if the branch is deletable or not.
|
|
+ */
|
|
+static int test_dentry_busy(struct dentry *root, aufs_bindex_t bindex,
|
|
+ unsigned int sigen, const unsigned int verbose)
|
|
+{
|
|
+ int err, i, j, ndentry;
|
|
+ aufs_bindex_t btop, bbot;
|
|
+ struct au_dcsub_pages dpages;
|
|
+ struct au_dpage *dpage;
|
|
+ struct dentry *d;
|
|
+
|
|
+ err = au_dpages_init(&dpages, GFP_NOFS);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = au_dcsub_pages(&dpages, root, NULL, NULL);
|
|
+ if (unlikely(err))
|
|
+ goto out_dpages;
|
|
+
|
|
+ for (i = 0; !err && i < dpages.ndpage; i++) {
|
|
+ dpage = dpages.dpages + i;
|
|
+ ndentry = dpage->ndentry;
|
|
+ for (j = 0; !err && j < ndentry; j++) {
|
|
+ d = dpage->dentries[j];
|
|
+ AuDebugOn(au_dcount(d) <= 0);
|
|
+ if (!au_digen_test(d, sigen)) {
|
|
+ di_read_lock_child(d, AuLock_IR);
|
|
+ if (unlikely(au_dbrange_test(d))) {
|
|
+ di_read_unlock(d, AuLock_IR);
|
|
+ continue;
|
|
+ }
|
|
+ } else {
|
|
+ di_write_lock_child(d);
|
|
+ if (unlikely(au_dbrange_test(d))) {
|
|
+ di_write_unlock(d);
|
|
+ continue;
|
|
+ }
|
|
+ err = au_reval_dpath(d, sigen);
|
|
+ if (!err)
|
|
+ di_downgrade_lock(d, AuLock_IR);
|
|
+ else {
|
|
+ di_write_unlock(d);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* AuDbgDentry(d); */
|
|
+ btop = au_dbtop(d);
|
|
+ bbot = au_dbbot(d);
|
|
+ if (btop <= bindex
|
|
+ && bindex <= bbot
|
|
+ && au_h_dptr(d, bindex)
|
|
+ && au_test_dbusy(d, btop, bbot)) {
|
|
+ err = -EBUSY;
|
|
+ AuVerbose(verbose, "busy %pd\n", d);
|
|
+ AuDbgDentry(d);
|
|
+ }
|
|
+ di_read_unlock(d, AuLock_IR);
|
|
+ }
|
|
+ }
|
|
+
|
|
+out_dpages:
|
|
+ au_dpages_free(&dpages);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int test_inode_busy(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ unsigned int sigen, const unsigned int verbose)
|
|
+{
|
|
+ int err;
|
|
+ unsigned long long max, ull;
|
|
+ struct inode *i, **array;
|
|
+ aufs_bindex_t btop, bbot;
|
|
+
|
|
+ array = au_iarray_alloc(sb, &max);
|
|
+ err = PTR_ERR(array);
|
|
+ if (IS_ERR(array))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ AuDbg("b%d\n", bindex);
|
|
+ for (ull = 0; !err && ull < max; ull++) {
|
|
+ i = array[ull];
|
|
+ if (unlikely(!i))
|
|
+ break;
|
|
+ if (i->i_ino == AUFS_ROOT_INO)
|
|
+ continue;
|
|
+
|
|
+ /* AuDbgInode(i); */
|
|
+ if (au_iigen(i, NULL) == sigen)
|
|
+ ii_read_lock_child(i);
|
|
+ else {
|
|
+ ii_write_lock_child(i);
|
|
+ err = au_refresh_hinode_self(i);
|
|
+ au_iigen_dec(i);
|
|
+ if (!err)
|
|
+ ii_downgrade_lock(i);
|
|
+ else {
|
|
+ ii_write_unlock(i);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ btop = au_ibtop(i);
|
|
+ bbot = au_ibbot(i);
|
|
+ if (btop <= bindex
|
|
+ && bindex <= bbot
|
|
+ && au_h_iptr(i, bindex)
|
|
+ && au_test_ibusy(i, btop, bbot)) {
|
|
+ err = -EBUSY;
|
|
+ AuVerbose(verbose, "busy i%lu\n", i->i_ino);
|
|
+ AuDbgInode(i);
|
|
+ }
|
|
+ ii_read_unlock(i);
|
|
+ }
|
|
+ au_iarray_free(array, max);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int test_children_busy(struct dentry *root, aufs_bindex_t bindex,
|
|
+ const unsigned int verbose)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int sigen;
|
|
+
|
|
+ sigen = au_sigen(root->d_sb);
|
|
+ DiMustNoWaiters(root);
|
|
+ IiMustNoWaiters(d_inode(root));
|
|
+ di_write_unlock(root);
|
|
+ err = test_dentry_busy(root, bindex, sigen, verbose);
|
|
+ if (!err)
|
|
+ err = test_inode_busy(root->d_sb, bindex, sigen, verbose);
|
|
+ di_write_lock_child(root); /* aufs_write_lock() calls ..._child() */
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int test_dir_busy(struct file *file, aufs_bindex_t br_id,
|
|
+ struct file **to_free, int *idx)
|
|
+{
|
|
+ int err;
|
|
+ unsigned char matched, root;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_fidir *fidir;
|
|
+ struct au_hfile *hfile;
|
|
+
|
|
+ err = 0;
|
|
+ root = IS_ROOT(file->f_path.dentry);
|
|
+ if (root) {
|
|
+ get_file(file);
|
|
+ to_free[*idx] = file;
|
|
+ (*idx)++;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ matched = 0;
|
|
+ fidir = au_fi(file)->fi_hdir;
|
|
+ AuDebugOn(!fidir);
|
|
+ bbot = au_fbbot_dir(file);
|
|
+ for (bindex = au_fbtop(file); bindex <= bbot; bindex++) {
|
|
+ hfile = fidir->fd_hfile + bindex;
|
|
+ if (!hfile->hf_file)
|
|
+ continue;
|
|
+
|
|
+ if (hfile->hf_br->br_id == br_id) {
|
|
+ matched = 1;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (matched)
|
|
+ err = -EBUSY;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int test_file_busy(struct super_block *sb, aufs_bindex_t br_id,
|
|
+ struct file **to_free, int opened)
|
|
+{
|
|
+ int err, idx;
|
|
+ unsigned long long ull, max;
|
|
+ aufs_bindex_t btop;
|
|
+ struct file *file, **array;
|
|
+ struct dentry *root;
|
|
+ struct au_hfile *hfile;
|
|
+
|
|
+ array = au_farray_alloc(sb, &max);
|
|
+ err = PTR_ERR(array);
|
|
+ if (IS_ERR(array))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ idx = 0;
|
|
+ root = sb->s_root;
|
|
+ di_write_unlock(root);
|
|
+ for (ull = 0; ull < max; ull++) {
|
|
+ file = array[ull];
|
|
+ if (unlikely(!file))
|
|
+ break;
|
|
+
|
|
+ /* AuDbg("%pD\n", file); */
|
|
+ fi_read_lock(file);
|
|
+ btop = au_fbtop(file);
|
|
+ if (!d_is_dir(file->f_path.dentry)) {
|
|
+ hfile = &au_fi(file)->fi_htop;
|
|
+ if (hfile->hf_br->br_id == br_id)
|
|
+ err = -EBUSY;
|
|
+ } else
|
|
+ err = test_dir_busy(file, br_id, to_free, &idx);
|
|
+ fi_read_unlock(file);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ }
|
|
+ di_write_lock_child(root);
|
|
+ au_farray_free(array, max);
|
|
+ AuDebugOn(idx > opened);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void br_del_file(struct file **to_free, unsigned long long opened,
|
|
+ aufs_bindex_t br_id)
|
|
+{
|
|
+ unsigned long long ull;
|
|
+ aufs_bindex_t bindex, btop, bbot, bfound;
|
|
+ struct file *file;
|
|
+ struct au_fidir *fidir;
|
|
+ struct au_hfile *hfile;
|
|
+
|
|
+ for (ull = 0; ull < opened; ull++) {
|
|
+ file = to_free[ull];
|
|
+ if (unlikely(!file))
|
|
+ break;
|
|
+
|
|
+ /* AuDbg("%pD\n", file); */
|
|
+ AuDebugOn(!d_is_dir(file->f_path.dentry));
|
|
+ bfound = -1;
|
|
+ fidir = au_fi(file)->fi_hdir;
|
|
+ AuDebugOn(!fidir);
|
|
+ fi_write_lock(file);
|
|
+ btop = au_fbtop(file);
|
|
+ bbot = au_fbbot_dir(file);
|
|
+ for (bindex = btop; bindex <= bbot; bindex++) {
|
|
+ hfile = fidir->fd_hfile + bindex;
|
|
+ if (!hfile->hf_file)
|
|
+ continue;
|
|
+
|
|
+ if (hfile->hf_br->br_id == br_id) {
|
|
+ bfound = bindex;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ AuDebugOn(bfound < 0);
|
|
+ au_set_h_fptr(file, bfound, NULL);
|
|
+ if (bfound == btop) {
|
|
+ for (btop++; btop <= bbot; btop++)
|
|
+ if (au_hf_dir(file, btop)) {
|
|
+ au_set_fbtop(file, btop);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ fi_write_unlock(file);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void au_br_do_del_brp(struct au_sbinfo *sbinfo,
|
|
+ const aufs_bindex_t bindex,
|
|
+ const aufs_bindex_t bbot)
|
|
+{
|
|
+ struct au_branch **brp, **p;
|
|
+
|
|
+ AuRwMustWriteLock(&sbinfo->si_rwsem);
|
|
+
|
|
+ brp = sbinfo->si_branch + bindex;
|
|
+ if (bindex < bbot)
|
|
+ memmove(brp, brp + 1, sizeof(*brp) * (bbot - bindex));
|
|
+ sbinfo->si_branch[0 + bbot] = NULL;
|
|
+ sbinfo->si_bbot--;
|
|
+
|
|
+ p = au_krealloc(sbinfo->si_branch, sizeof(*p) * bbot, AuGFP_SBILIST,
|
|
+ /*may_shrink*/1);
|
|
+ if (p)
|
|
+ sbinfo->si_branch = p;
|
|
+ /* harmless error */
|
|
+}
|
|
+
|
|
+static void au_br_do_del_hdp(struct au_dinfo *dinfo, const aufs_bindex_t bindex,
|
|
+ const aufs_bindex_t bbot)
|
|
+{
|
|
+ struct au_hdentry *hdp, *p;
|
|
+
|
|
+ AuRwMustWriteLock(&dinfo->di_rwsem);
|
|
+
|
|
+ hdp = au_hdentry(dinfo, bindex);
|
|
+ if (bindex < bbot)
|
|
+ memmove(hdp, hdp + 1, sizeof(*hdp) * (bbot - bindex));
|
|
+ /* au_h_dentry_init(au_hdentry(dinfo, bbot); */
|
|
+ dinfo->di_bbot--;
|
|
+
|
|
+ p = au_krealloc(dinfo->di_hdentry, sizeof(*p) * bbot, AuGFP_SBILIST,
|
|
+ /*may_shrink*/1);
|
|
+ if (p)
|
|
+ dinfo->di_hdentry = p;
|
|
+ /* harmless error */
|
|
+}
|
|
+
|
|
+static void au_br_do_del_hip(struct au_iinfo *iinfo, const aufs_bindex_t bindex,
|
|
+ const aufs_bindex_t bbot)
|
|
+{
|
|
+ struct au_hinode *hip, *p;
|
|
+
|
|
+ AuRwMustWriteLock(&iinfo->ii_rwsem);
|
|
+
|
|
+ hip = au_hinode(iinfo, bindex);
|
|
+ if (bindex < bbot)
|
|
+ memmove(hip, hip + 1, sizeof(*hip) * (bbot - bindex));
|
|
+ /* au_hinode_init(au_hinode(iinfo, bbot)); */
|
|
+ iinfo->ii_bbot--;
|
|
+
|
|
+ p = au_krealloc(iinfo->ii_hinode, sizeof(*p) * bbot, AuGFP_SBILIST,
|
|
+ /*may_shrink*/1);
|
|
+ if (p)
|
|
+ iinfo->ii_hinode = p;
|
|
+ /* harmless error */
|
|
+}
|
|
+
|
|
+static void au_br_do_del(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ struct au_branch *br)
|
|
+{
|
|
+ aufs_bindex_t bbot;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct dentry *root, *h_root;
|
|
+ struct inode *inode, *h_inode;
|
|
+ struct au_hinode *hinode;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ root = sb->s_root;
|
|
+ inode = d_inode(root);
|
|
+ sbinfo = au_sbi(sb);
|
|
+ bbot = sbinfo->si_bbot;
|
|
+
|
|
+ h_root = au_h_dptr(root, bindex);
|
|
+ hinode = au_hi(inode, bindex);
|
|
+ h_inode = au_igrab(hinode->hi_inode);
|
|
+ au_hiput(hinode);
|
|
+
|
|
+ au_sbilist_lock();
|
|
+ au_br_do_del_brp(sbinfo, bindex, bbot);
|
|
+ au_br_do_del_hdp(au_di(root), bindex, bbot);
|
|
+ au_br_do_del_hip(au_ii(inode), bindex, bbot);
|
|
+ au_sbilist_unlock();
|
|
+
|
|
+ /* ignore an error */
|
|
+ au_dr_br_fin(sb, br); /* always, regardless the mount option */
|
|
+
|
|
+ dput(h_root);
|
|
+ iput(h_inode);
|
|
+ au_br_do_free(br);
|
|
+}
|
|
+
|
|
+static unsigned long long empty_cb(struct super_block *sb, void *array,
|
|
+ unsigned long long max, void *arg)
|
|
+{
|
|
+ return max;
|
|
+}
|
|
+
|
|
+int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount)
|
|
+{
|
|
+ int err, rerr, i;
|
|
+ unsigned long long opened;
|
|
+ unsigned int mnt_flags;
|
|
+ aufs_bindex_t bindex, bbot, br_id;
|
|
+ unsigned char do_wh, verbose;
|
|
+ struct au_branch *br;
|
|
+ struct au_wbr *wbr;
|
|
+ struct dentry *root;
|
|
+ struct file **to_free;
|
|
+
|
|
+ err = 0;
|
|
+ opened = 0;
|
|
+ to_free = NULL;
|
|
+ root = sb->s_root;
|
|
+ bindex = au_find_dbindex(root, del->h_path.dentry);
|
|
+ if (bindex < 0) {
|
|
+ if (remount)
|
|
+ goto out; /* success */
|
|
+ err = -ENOENT;
|
|
+ pr_err("%s no such branch\n", del->pathname);
|
|
+ goto out;
|
|
+ }
|
|
+ AuDbg("bindex b%d\n", bindex);
|
|
+
|
|
+ err = -EBUSY;
|
|
+ mnt_flags = au_mntflags(sb);
|
|
+ verbose = !!au_opt_test(mnt_flags, VERBOSE);
|
|
+ bbot = au_sbbot(sb);
|
|
+ if (unlikely(!bbot)) {
|
|
+ AuVerbose(verbose, "no more branches left\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ br = au_sbr(sb, bindex);
|
|
+ AuDebugOn(!path_equal(&br->br_path, &del->h_path));
|
|
+ if (unlikely(au_lcnt_read(&br->br_count, /*do_rev*/1))) {
|
|
+ AuVerbose(verbose, "br %pd2 is busy now\n", del->h_path.dentry);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ br_id = br->br_id;
|
|
+ opened = au_lcnt_read(&br->br_nfiles, /*do_rev*/1);
|
|
+ if (unlikely(opened)) {
|
|
+ to_free = au_array_alloc(&opened, empty_cb, sb, NULL);
|
|
+ err = PTR_ERR(to_free);
|
|
+ if (IS_ERR(to_free))
|
|
+ goto out;
|
|
+
|
|
+ err = test_file_busy(sb, br_id, to_free, opened);
|
|
+ if (unlikely(err)) {
|
|
+ AuVerbose(verbose, "%llu file(s) opened\n", opened);
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ wbr = br->br_wbr;
|
|
+ do_wh = wbr && (wbr->wbr_whbase || wbr->wbr_plink || wbr->wbr_orph);
|
|
+ if (do_wh) {
|
|
+ /* instead of WbrWhMustWriteLock(wbr) */
|
|
+ SiMustWriteLock(sb);
|
|
+ for (i = 0; i < AuBrWh_Last; i++) {
|
|
+ dput(wbr->wbr_wh[i]);
|
|
+ wbr->wbr_wh[i] = NULL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = test_children_busy(root, bindex, verbose);
|
|
+ if (unlikely(err)) {
|
|
+ if (do_wh)
|
|
+ goto out_wh;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ if (to_free) {
|
|
+ /*
|
|
+ * now we confirmed the branch is deletable.
|
|
+ * let's free the remaining opened dirs on the branch.
|
|
+ */
|
|
+ di_write_unlock(root);
|
|
+ br_del_file(to_free, opened, br_id);
|
|
+ di_write_lock_child(root);
|
|
+ }
|
|
+
|
|
+ sysaufs_brs_del(sb, bindex); /* remove successors */
|
|
+ dbgaufs_xino_del(br); /* remove one */
|
|
+ au_br_do_del(sb, bindex, br);
|
|
+ sysaufs_brs_add(sb, bindex); /* append successors */
|
|
+ dbgaufs_brs_add(sb, bindex, /*topdown*/1); /* rename successors */
|
|
+
|
|
+ if (!bindex) {
|
|
+ au_cpup_attr_all(d_inode(root), /*force*/1);
|
|
+ sb->s_maxbytes = au_sbr_sb(sb, 0)->s_maxbytes;
|
|
+ } else
|
|
+ au_sub_nlink(d_inode(root), d_inode(del->h_path.dentry));
|
|
+ if (au_opt_test(mnt_flags, PLINK))
|
|
+ au_plink_half_refresh(sb, br_id);
|
|
+
|
|
+ goto out; /* success */
|
|
+
|
|
+out_wh:
|
|
+ /* revert */
|
|
+ rerr = au_br_init_wh(sb, br, br->br_perm);
|
|
+ if (rerr)
|
|
+ pr_warn("failed re-creating base whiteout, %s. (%d)\n",
|
|
+ del->pathname, rerr);
|
|
+out:
|
|
+ if (to_free)
|
|
+ au_farray_free(to_free, opened);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_ibusy(struct super_block *sb, struct aufs_ibusy __user *arg)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t btop, bbot;
|
|
+ struct aufs_ibusy ibusy;
|
|
+ struct inode *inode, *h_inode;
|
|
+
|
|
+ err = -EPERM;
|
|
+ if (unlikely(!capable(CAP_SYS_ADMIN)))
|
|
+ goto out;
|
|
+
|
|
+ err = copy_from_user(&ibusy, arg, sizeof(ibusy));
|
|
+ if (!err)
|
|
+ /* VERIFY_WRITE */
|
|
+ err = !access_ok(&arg->h_ino, sizeof(arg->h_ino));
|
|
+ if (unlikely(err)) {
|
|
+ err = -EFAULT;
|
|
+ AuTraceErr(err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = -EINVAL;
|
|
+ si_read_lock(sb, AuLock_FLUSH);
|
|
+ if (unlikely(ibusy.bindex < 0 || ibusy.bindex > au_sbbot(sb)))
|
|
+ goto out_unlock;
|
|
+
|
|
+ err = 0;
|
|
+ ibusy.h_ino = 0; /* invalid */
|
|
+ inode = ilookup(sb, ibusy.ino);
|
|
+ if (!inode
|
|
+ || inode->i_ino == AUFS_ROOT_INO
|
|
+ || au_is_bad_inode(inode))
|
|
+ goto out_unlock;
|
|
+
|
|
+ ii_read_lock_child(inode);
|
|
+ btop = au_ibtop(inode);
|
|
+ bbot = au_ibbot(inode);
|
|
+ if (btop <= ibusy.bindex && ibusy.bindex <= bbot) {
|
|
+ h_inode = au_h_iptr(inode, ibusy.bindex);
|
|
+ if (h_inode && au_test_ibusy(inode, btop, bbot))
|
|
+ ibusy.h_ino = h_inode->i_ino;
|
|
+ }
|
|
+ ii_read_unlock(inode);
|
|
+ iput(inode);
|
|
+
|
|
+out_unlock:
|
|
+ si_read_unlock(sb);
|
|
+ if (!err) {
|
|
+ err = __put_user(ibusy.h_ino, &arg->h_ino);
|
|
+ if (unlikely(err)) {
|
|
+ err = -EFAULT;
|
|
+ AuTraceErr(err);
|
|
+ }
|
|
+ }
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+long au_ibusy_ioctl(struct file *file, unsigned long arg)
|
|
+{
|
|
+ return au_ibusy(file->f_path.dentry->d_sb, (void __user *)arg);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_COMPAT
|
|
+long au_ibusy_compat_ioctl(struct file *file, unsigned long arg)
|
|
+{
|
|
+ return au_ibusy(file->f_path.dentry->d_sb, compat_ptr(arg));
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * change a branch permission
|
|
+ */
|
|
+
|
|
+static void au_warn_ima(void)
|
|
+{
|
|
+#ifdef CONFIG_IMA
|
|
+ /* since it doesn't support mark_files_ro() */
|
|
+ AuWarn1("RW -> RO makes IMA to produce wrong message\n");
|
|
+#endif
|
|
+}
|
|
+
|
|
+static int do_need_sigen_inc(int a, int b)
|
|
+{
|
|
+ return au_br_whable(a) && !au_br_whable(b);
|
|
+}
|
|
+
|
|
+static int need_sigen_inc(int old, int new)
|
|
+{
|
|
+ return do_need_sigen_inc(old, new)
|
|
+ || do_need_sigen_inc(new, old);
|
|
+}
|
|
+
|
|
+static int au_br_mod_files_ro(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ int err, do_warn;
|
|
+ unsigned int mnt_flags;
|
|
+ unsigned long long ull, max;
|
|
+ aufs_bindex_t br_id;
|
|
+ unsigned char verbose, writer;
|
|
+ struct file *file, *hf, **array;
|
|
+ struct au_hfile *hfile;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ mnt_flags = au_mntflags(sb);
|
|
+ verbose = !!au_opt_test(mnt_flags, VERBOSE);
|
|
+
|
|
+ array = au_farray_alloc(sb, &max);
|
|
+ err = PTR_ERR(array);
|
|
+ if (IS_ERR(array))
|
|
+ goto out;
|
|
+
|
|
+ do_warn = 0;
|
|
+ br_id = au_sbr_id(sb, bindex);
|
|
+ for (ull = 0; ull < max; ull++) {
|
|
+ file = array[ull];
|
|
+ if (unlikely(!file))
|
|
+ break;
|
|
+
|
|
+ /* AuDbg("%pD\n", file); */
|
|
+ fi_read_lock(file);
|
|
+ if (unlikely(au_test_mmapped(file))) {
|
|
+ err = -EBUSY;
|
|
+ AuVerbose(verbose, "mmapped %pD\n", file);
|
|
+ AuDbgFile(file);
|
|
+ FiMustNoWaiters(file);
|
|
+ fi_read_unlock(file);
|
|
+ goto out_array;
|
|
+ }
|
|
+
|
|
+ hfile = &au_fi(file)->fi_htop;
|
|
+ hf = hfile->hf_file;
|
|
+ if (!d_is_reg(file->f_path.dentry)
|
|
+ || !(file->f_mode & FMODE_WRITE)
|
|
+ || hfile->hf_br->br_id != br_id
|
|
+ || !(hf->f_mode & FMODE_WRITE))
|
|
+ array[ull] = NULL;
|
|
+ else {
|
|
+ do_warn = 1;
|
|
+ get_file(file);
|
|
+ }
|
|
+
|
|
+ FiMustNoWaiters(file);
|
|
+ fi_read_unlock(file);
|
|
+ fput(file);
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ if (do_warn)
|
|
+ au_warn_ima();
|
|
+
|
|
+ for (ull = 0; ull < max; ull++) {
|
|
+ file = array[ull];
|
|
+ if (!file)
|
|
+ continue;
|
|
+
|
|
+ /* todo: already flushed? */
|
|
+ /*
|
|
+ * fs/super.c:mark_files_ro() is gone, but aufs keeps its
|
|
+ * approach which resets f_mode and calls mnt_drop_write() and
|
|
+ * file_release_write() for each file, because the branch
|
|
+ * attribute in aufs world is totally different from the native
|
|
+ * fs rw/ro mode.
|
|
+ */
|
|
+ /* fi_read_lock(file); */
|
|
+ hfile = &au_fi(file)->fi_htop;
|
|
+ hf = hfile->hf_file;
|
|
+ /* fi_read_unlock(file); */
|
|
+ spin_lock(&hf->f_lock);
|
|
+ writer = !!(hf->f_mode & FMODE_WRITER);
|
|
+ hf->f_mode &= ~(FMODE_WRITE | FMODE_WRITER);
|
|
+ spin_unlock(&hf->f_lock);
|
|
+ if (writer) {
|
|
+ h_inode = file_inode(hf);
|
|
+ if (hf->f_mode & FMODE_READ)
|
|
+ i_readcount_inc(h_inode);
|
|
+ put_write_access(h_inode);
|
|
+ __mnt_drop_write(hf->f_path.mnt);
|
|
+ }
|
|
+ }
|
|
+
|
|
+out_array:
|
|
+ au_farray_free(array, max);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount,
|
|
+ int *do_refresh)
|
|
+{
|
|
+ int err, rerr;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct dentry *root;
|
|
+ struct au_branch *br;
|
|
+ struct au_br_fhsm *bf;
|
|
+
|
|
+ root = sb->s_root;
|
|
+ bindex = au_find_dbindex(root, mod->h_root);
|
|
+ if (bindex < 0) {
|
|
+ if (remount)
|
|
+ return 0; /* success */
|
|
+ err = -ENOENT;
|
|
+ pr_err("%s no such branch\n", mod->path);
|
|
+ goto out;
|
|
+ }
|
|
+ AuDbg("bindex b%d\n", bindex);
|
|
+
|
|
+ err = test_br(d_inode(mod->h_root), mod->perm, mod->path);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ br = au_sbr(sb, bindex);
|
|
+ AuDebugOn(mod->h_root != au_br_dentry(br));
|
|
+ if (br->br_perm == mod->perm)
|
|
+ return 0; /* success */
|
|
+
|
|
+ /* pre-allocate for non-fhsm --> fhsm */
|
|
+ bf = NULL;
|
|
+ if (!au_br_fhsm(br->br_perm) && au_br_fhsm(mod->perm)) {
|
|
+ err = au_fhsm_br_alloc(br);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ bf = br->br_fhsm;
|
|
+ br->br_fhsm = NULL;
|
|
+ }
|
|
+
|
|
+ if (au_br_writable(br->br_perm)) {
|
|
+ /* remove whiteout base */
|
|
+ err = au_br_init_wh(sb, br, mod->perm);
|
|
+ if (unlikely(err))
|
|
+ goto out_bf;
|
|
+
|
|
+ if (!au_br_writable(mod->perm)) {
|
|
+ /* rw --> ro, file might be mmapped */
|
|
+ DiMustNoWaiters(root);
|
|
+ IiMustNoWaiters(d_inode(root));
|
|
+ di_write_unlock(root);
|
|
+ err = au_br_mod_files_ro(sb, bindex);
|
|
+ /* aufs_write_lock() calls ..._child() */
|
|
+ di_write_lock_child(root);
|
|
+
|
|
+ if (unlikely(err)) {
|
|
+ rerr = -ENOMEM;
|
|
+ br->br_wbr = kzalloc(sizeof(*br->br_wbr),
|
|
+ GFP_NOFS);
|
|
+ if (br->br_wbr)
|
|
+ rerr = au_wbr_init(br, sb, br->br_perm);
|
|
+ if (unlikely(rerr)) {
|
|
+ AuIOErr("nested error %d (%d)\n",
|
|
+ rerr, err);
|
|
+ br->br_perm = mod->perm;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ } else if (au_br_writable(mod->perm)) {
|
|
+ /* ro --> rw */
|
|
+ err = -ENOMEM;
|
|
+ br->br_wbr = kzalloc(sizeof(*br->br_wbr), GFP_NOFS);
|
|
+ if (br->br_wbr) {
|
|
+ err = au_wbr_init(br, sb, mod->perm);
|
|
+ if (unlikely(err)) {
|
|
+ au_kfree_rcu(br->br_wbr);
|
|
+ br->br_wbr = NULL;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ goto out_bf;
|
|
+
|
|
+ if (au_br_fhsm(br->br_perm)) {
|
|
+ if (!au_br_fhsm(mod->perm)) {
|
|
+ /* fhsm --> non-fhsm */
|
|
+ au_br_fhsm_fin(br->br_fhsm);
|
|
+ au_kfree_rcu(br->br_fhsm);
|
|
+ br->br_fhsm = NULL;
|
|
+ }
|
|
+ } else if (au_br_fhsm(mod->perm))
|
|
+ /* non-fhsm --> fhsm */
|
|
+ br->br_fhsm = bf;
|
|
+
|
|
+ *do_refresh |= need_sigen_inc(br->br_perm, mod->perm);
|
|
+ br->br_perm = mod->perm;
|
|
+ goto out; /* success */
|
|
+
|
|
+out_bf:
|
|
+ au_kfree_try_rcu(bf);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_br_stfs(struct au_branch *br, struct aufs_stfs *stfs)
|
|
+{
|
|
+ int err;
|
|
+ struct kstatfs kstfs;
|
|
+
|
|
+ err = vfs_statfs(&br->br_path, &kstfs);
|
|
+ if (!err) {
|
|
+ stfs->f_blocks = kstfs.f_blocks;
|
|
+ stfs->f_bavail = kstfs.f_bavail;
|
|
+ stfs->f_files = kstfs.f_files;
|
|
+ stfs->f_ffree = kstfs.f_ffree;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/branch.h b/fs/aufs/branch.h
|
|
new file mode 100644
|
|
index 000000000000..594c8bd674b2
|
|
--- /dev/null
|
|
+++ b/fs/aufs/branch.h
|
|
@@ -0,0 +1,366 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * branch filesystems and xino for them
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_BRANCH_H__
|
|
+#define __AUFS_BRANCH_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/mount.h>
|
|
+#include "dirren.h"
|
|
+#include "dynop.h"
|
|
+#include "lcnt.h"
|
|
+#include "rwsem.h"
|
|
+#include "super.h"
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* a xino file */
|
|
+struct au_xino {
|
|
+ struct file **xi_file;
|
|
+ unsigned int xi_nfile;
|
|
+
|
|
+ struct {
|
|
+ spinlock_t spin;
|
|
+ ino_t *array;
|
|
+ int total;
|
|
+ /* reserved for future use */
|
|
+ /* unsigned long *bitmap; */
|
|
+ wait_queue_head_t wqh;
|
|
+ } xi_nondir;
|
|
+
|
|
+ struct mutex xi_mtx; /* protects xi_file array */
|
|
+ struct hlist_bl_head xi_writing;
|
|
+
|
|
+ atomic_t xi_truncating;
|
|
+
|
|
+ struct kref xi_kref;
|
|
+};
|
|
+
|
|
+/* File-based Hierarchical Storage Management */
|
|
+struct au_br_fhsm {
|
|
+#ifdef CONFIG_AUFS_FHSM
|
|
+ struct mutex bf_lock;
|
|
+ unsigned long bf_jiffy;
|
|
+ struct aufs_stfs bf_stfs;
|
|
+ int bf_readable;
|
|
+#endif
|
|
+};
|
|
+
|
|
+/* members for writable branch only */
|
|
+enum {AuBrWh_BASE, AuBrWh_PLINK, AuBrWh_ORPH, AuBrWh_Last};
|
|
+struct au_wbr {
|
|
+ struct au_rwsem wbr_wh_rwsem;
|
|
+ struct dentry *wbr_wh[AuBrWh_Last];
|
|
+ atomic_t wbr_wh_running;
|
|
+#define wbr_whbase wbr_wh[AuBrWh_BASE] /* whiteout base */
|
|
+#define wbr_plink wbr_wh[AuBrWh_PLINK] /* pseudo-link dir */
|
|
+#define wbr_orph wbr_wh[AuBrWh_ORPH] /* dir for orphans */
|
|
+
|
|
+ /* mfs mode */
|
|
+ unsigned long long wbr_bytes;
|
|
+};
|
|
+
|
|
+/* ext2 has 3 types of operations at least, ext3 has 4 */
|
|
+#define AuBrDynOp (AuDyLast * 4)
|
|
+
|
|
+#ifdef CONFIG_AUFS_HFSNOTIFY
|
|
+/* support for asynchronous destruction */
|
|
+struct au_br_hfsnotify {
|
|
+ struct fsnotify_group *hfsn_group;
|
|
+};
|
|
+#endif
|
|
+
|
|
+/* sysfs entries */
|
|
+struct au_brsysfs {
|
|
+ char name[16];
|
|
+ struct attribute attr;
|
|
+};
|
|
+
|
|
+enum {
|
|
+ AuBrSysfs_BR,
|
|
+ AuBrSysfs_BRID,
|
|
+ AuBrSysfs_Last
|
|
+};
|
|
+
|
|
+/* protected by superblock rwsem */
|
|
+struct au_branch {
|
|
+ struct au_xino *br_xino;
|
|
+
|
|
+ aufs_bindex_t br_id;
|
|
+
|
|
+ int br_perm;
|
|
+ struct path br_path;
|
|
+ spinlock_t br_dykey_lock;
|
|
+ struct au_dykey *br_dykey[AuBrDynOp];
|
|
+ au_lcnt_t br_nfiles; /* opened files */
|
|
+ au_lcnt_t br_count; /* in-use for other */
|
|
+
|
|
+ struct au_wbr *br_wbr;
|
|
+ struct au_br_fhsm *br_fhsm;
|
|
+
|
|
+#ifdef CONFIG_AUFS_HFSNOTIFY
|
|
+ struct au_br_hfsnotify *br_hfsn;
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_SYSFS
|
|
+ /* entries under sysfs per mount-point */
|
|
+ struct au_brsysfs br_sysfs[AuBrSysfs_Last];
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+ struct dentry *br_dbgaufs; /* xino */
|
|
+#endif
|
|
+
|
|
+ struct au_dr_br br_dirren;
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline struct vfsmount *au_br_mnt(struct au_branch *br)
|
|
+{
|
|
+ return br->br_path.mnt;
|
|
+}
|
|
+
|
|
+static inline struct dentry *au_br_dentry(struct au_branch *br)
|
|
+{
|
|
+ return br->br_path.dentry;
|
|
+}
|
|
+
|
|
+static inline struct super_block *au_br_sb(struct au_branch *br)
|
|
+{
|
|
+ return au_br_mnt(br)->mnt_sb;
|
|
+}
|
|
+
|
|
+static inline int au_br_rdonly(struct au_branch *br)
|
|
+{
|
|
+ return (sb_rdonly(au_br_sb(br))
|
|
+ || !au_br_writable(br->br_perm))
|
|
+ ? -EROFS : 0;
|
|
+}
|
|
+
|
|
+static inline int au_br_hnotifyable(int brperm __maybe_unused)
|
|
+{
|
|
+#ifdef CONFIG_AUFS_HNOTIFY
|
|
+ return !(brperm & AuBrPerm_RR);
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_br_test_oflag(int oflag, struct au_branch *br)
|
|
+{
|
|
+ int err, exec_flag;
|
|
+
|
|
+ err = 0;
|
|
+ exec_flag = oflag & __FMODE_EXEC;
|
|
+ if (unlikely(exec_flag && path_noexec(&br->br_path)))
|
|
+ err = -EACCES;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static inline void au_xino_get(struct au_branch *br)
|
|
+{
|
|
+ struct au_xino *xi;
|
|
+
|
|
+ xi = br->br_xino;
|
|
+ if (xi)
|
|
+ kref_get(&xi->xi_kref);
|
|
+}
|
|
+
|
|
+static inline int au_xino_count(struct au_branch *br)
|
|
+{
|
|
+ int v;
|
|
+ struct au_xino *xi;
|
|
+
|
|
+ v = 0;
|
|
+ xi = br->br_xino;
|
|
+ if (xi)
|
|
+ v = kref_read(&xi->xi_kref);
|
|
+
|
|
+ return v;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* branch.c */
|
|
+struct au_sbinfo;
|
|
+void au_br_free(struct au_sbinfo *sinfo);
|
|
+int au_br_index(struct super_block *sb, aufs_bindex_t br_id);
|
|
+struct au_opt_add;
|
|
+int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount);
|
|
+struct au_opt_del;
|
|
+int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount);
|
|
+long au_ibusy_ioctl(struct file *file, unsigned long arg);
|
|
+#ifdef CONFIG_COMPAT
|
|
+long au_ibusy_compat_ioctl(struct file *file, unsigned long arg);
|
|
+#endif
|
|
+struct au_opt_mod;
|
|
+int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount,
|
|
+ int *do_refresh);
|
|
+struct aufs_stfs;
|
|
+int au_br_stfs(struct au_branch *br, struct aufs_stfs *stfs);
|
|
+
|
|
+/* xino.c */
|
|
+static const loff_t au_loff_max = LLONG_MAX;
|
|
+
|
|
+aufs_bindex_t au_xi_root(struct super_block *sb, struct dentry *dentry);
|
|
+struct file *au_xino_create(struct super_block *sb, char *fpath, int silent,
|
|
+ int wbrtop);
|
|
+struct file *au_xino_create2(struct super_block *sb, struct path *base,
|
|
+ struct file *copy_src);
|
|
+struct au_xi_new {
|
|
+ struct au_xino *xi; /* switch between xino and xigen */
|
|
+ int idx;
|
|
+ struct path *base;
|
|
+ struct file *copy_src;
|
|
+};
|
|
+struct file *au_xi_new(struct super_block *sb, struct au_xi_new *xinew);
|
|
+
|
|
+int au_xino_read(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
|
|
+ ino_t *ino);
|
|
+int au_xino_write(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
|
|
+ ino_t ino);
|
|
+ssize_t xino_fread(vfs_readf_t func, struct file *file, void *buf, size_t size,
|
|
+ loff_t *pos);
|
|
+ssize_t xino_fwrite(vfs_writef_t func, struct file *file, void *buf,
|
|
+ size_t size, loff_t *pos);
|
|
+
|
|
+int au_xib_trunc(struct super_block *sb);
|
|
+int au_xino_trunc(struct super_block *sb, aufs_bindex_t bindex, int idx_begin);
|
|
+
|
|
+struct au_xino *au_xino_alloc(unsigned int nfile);
|
|
+int au_xino_put(struct au_branch *br);
|
|
+struct file *au_xino_file1(struct au_xino *xi);
|
|
+
|
|
+struct au_opt_xino;
|
|
+void au_xino_clr(struct super_block *sb);
|
|
+int au_xino_set(struct super_block *sb, struct au_opt_xino *xiopt, int remount);
|
|
+struct file *au_xino_def(struct super_block *sb);
|
|
+int au_xino_init_br(struct super_block *sb, struct au_branch *br, ino_t hino,
|
|
+ struct path *base);
|
|
+
|
|
+ino_t au_xino_new_ino(struct super_block *sb);
|
|
+void au_xino_delete_inode(struct inode *inode, const int unlinked);
|
|
+
|
|
+void au_xinondir_leave(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ ino_t h_ino, int idx);
|
|
+int au_xinondir_enter(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
|
|
+ int *idx);
|
|
+
|
|
+int au_xino_path(struct seq_file *seq, struct file *file);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* @idx is signed to accept -1 meaning the first file */
|
|
+static inline struct file *au_xino_file(struct au_xino *xi, int idx)
|
|
+{
|
|
+ struct file *file;
|
|
+
|
|
+ file = NULL;
|
|
+ if (!xi)
|
|
+ goto out;
|
|
+
|
|
+ if (idx >= 0) {
|
|
+ if (idx < xi->xi_nfile)
|
|
+ file = xi->xi_file[idx];
|
|
+ } else
|
|
+ file = au_xino_file1(xi);
|
|
+
|
|
+out:
|
|
+ return file;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* Superblock to branch */
|
|
+static inline
|
|
+aufs_bindex_t au_sbr_id(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ return au_sbr(sb, bindex)->br_id;
|
|
+}
|
|
+
|
|
+static inline
|
|
+struct vfsmount *au_sbr_mnt(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ return au_br_mnt(au_sbr(sb, bindex));
|
|
+}
|
|
+
|
|
+static inline
|
|
+struct super_block *au_sbr_sb(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ return au_br_sb(au_sbr(sb, bindex));
|
|
+}
|
|
+
|
|
+static inline int au_sbr_perm(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ return au_sbr(sb, bindex)->br_perm;
|
|
+}
|
|
+
|
|
+static inline int au_sbr_whable(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ return au_br_whable(au_sbr_perm(sb, bindex));
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#define wbr_wh_read_lock(wbr) au_rw_read_lock(&(wbr)->wbr_wh_rwsem)
|
|
+#define wbr_wh_write_lock(wbr) au_rw_write_lock(&(wbr)->wbr_wh_rwsem)
|
|
+#define wbr_wh_read_trylock(wbr) au_rw_read_trylock(&(wbr)->wbr_wh_rwsem)
|
|
+#define wbr_wh_write_trylock(wbr) au_rw_write_trylock(&(wbr)->wbr_wh_rwsem)
|
|
+/*
|
|
+#define wbr_wh_read_trylock_nested(wbr) \
|
|
+ au_rw_read_trylock_nested(&(wbr)->wbr_wh_rwsem)
|
|
+#define wbr_wh_write_trylock_nested(wbr) \
|
|
+ au_rw_write_trylock_nested(&(wbr)->wbr_wh_rwsem)
|
|
+*/
|
|
+
|
|
+#define wbr_wh_read_unlock(wbr) au_rw_read_unlock(&(wbr)->wbr_wh_rwsem)
|
|
+#define wbr_wh_write_unlock(wbr) au_rw_write_unlock(&(wbr)->wbr_wh_rwsem)
|
|
+#define wbr_wh_downgrade_lock(wbr) au_rw_dgrade_lock(&(wbr)->wbr_wh_rwsem)
|
|
+
|
|
+#define WbrWhMustNoWaiters(wbr) AuRwMustNoWaiters(&(wbr)->wbr_wh_rwsem)
|
|
+#define WbrWhMustAnyLock(wbr) AuRwMustAnyLock(&(wbr)->wbr_wh_rwsem)
|
|
+#define WbrWhMustWriteLock(wbr) AuRwMustWriteLock(&(wbr)->wbr_wh_rwsem)
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_AUFS_FHSM
|
|
+static inline void au_br_fhsm_init(struct au_br_fhsm *brfhsm)
|
|
+{
|
|
+ mutex_init(&brfhsm->bf_lock);
|
|
+ brfhsm->bf_jiffy = 0;
|
|
+ brfhsm->bf_readable = 0;
|
|
+}
|
|
+
|
|
+static inline void au_br_fhsm_fin(struct au_br_fhsm *brfhsm)
|
|
+{
|
|
+ mutex_destroy(&brfhsm->bf_lock);
|
|
+}
|
|
+#else
|
|
+AuStubVoid(au_br_fhsm_init, struct au_br_fhsm *brfhsm)
|
|
+AuStubVoid(au_br_fhsm_fin, struct au_br_fhsm *brfhsm)
|
|
+#endif
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_BRANCH_H__ */
|
|
diff --git a/fs/aufs/conf.mk b/fs/aufs/conf.mk
|
|
new file mode 100644
|
|
index 000000000000..12782f8e0f38
|
|
--- /dev/null
|
|
+++ b/fs/aufs/conf.mk
|
|
@@ -0,0 +1,40 @@
|
|
+# SPDX-License-Identifier: GPL-2.0
|
|
+
|
|
+AuConfStr = CONFIG_AUFS_FS=${CONFIG_AUFS_FS}
|
|
+
|
|
+define AuConf
|
|
+ifdef ${1}
|
|
+AuConfStr += ${1}=${${1}}
|
|
+endif
|
|
+endef
|
|
+
|
|
+AuConfAll = BRANCH_MAX_127 BRANCH_MAX_511 BRANCH_MAX_1023 BRANCH_MAX_32767 \
|
|
+ SBILIST \
|
|
+ HNOTIFY HFSNOTIFY \
|
|
+ EXPORT INO_T_64 \
|
|
+ XATTR \
|
|
+ FHSM \
|
|
+ RDU \
|
|
+ DIRREN \
|
|
+ SHWH \
|
|
+ BR_RAMFS \
|
|
+ BR_FUSE POLL \
|
|
+ BR_HFSPLUS \
|
|
+ BDEV_LOOP \
|
|
+ DEBUG MAGIC_SYSRQ
|
|
+$(foreach i, ${AuConfAll}, \
|
|
+ $(eval $(call AuConf,CONFIG_AUFS_${i})))
|
|
+
|
|
+AuConfName = ${obj}/conf.str
|
|
+${AuConfName}.tmp: FORCE
|
|
+ @echo ${AuConfStr} | tr ' ' '\n' | sed -e 's/^/"/' -e 's/$$/\\n"/' > $@
|
|
+${AuConfName}: ${AuConfName}.tmp
|
|
+ @diff -q $< $@ > /dev/null 2>&1 || { \
|
|
+ echo ' GEN ' $@; \
|
|
+ cp -p $< $@; \
|
|
+ }
|
|
+FORCE:
|
|
+clean-files += ${AuConfName} ${AuConfName}.tmp
|
|
+${obj}/sysfs.o: ${AuConfName}
|
|
+
|
|
+-include ${srctree}/${src}/conf_priv.mk
|
|
diff --git a/fs/aufs/cpup.c b/fs/aufs/cpup.c
|
|
new file mode 100644
|
|
index 000000000000..492442339b6c
|
|
--- /dev/null
|
|
+++ b/fs/aufs/cpup.c
|
|
@@ -0,0 +1,1458 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * copy-up functions, see wbr_policy.c for copy-down
|
|
+ */
|
|
+
|
|
+#include <linux/fs_stack.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/task_work.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+void au_cpup_attr_flags(struct inode *dst, unsigned int iflags)
|
|
+{
|
|
+ const unsigned int mask = S_DEAD | S_SWAPFILE | S_PRIVATE
|
|
+ | S_NOATIME | S_NOCMTIME | S_AUTOMOUNT;
|
|
+
|
|
+ BUILD_BUG_ON(sizeof(iflags) != sizeof(dst->i_flags));
|
|
+
|
|
+ dst->i_flags |= iflags & ~mask;
|
|
+ if (au_test_fs_notime(dst->i_sb))
|
|
+ dst->i_flags |= S_NOATIME | S_NOCMTIME;
|
|
+}
|
|
+
|
|
+void au_cpup_attr_timesizes(struct inode *inode)
|
|
+{
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ h_inode = au_h_iptr(inode, au_ibtop(inode));
|
|
+ fsstack_copy_attr_times(inode, h_inode);
|
|
+ fsstack_copy_inode_size(inode, h_inode);
|
|
+}
|
|
+
|
|
+void au_cpup_attr_nlink(struct inode *inode, int force)
|
|
+{
|
|
+ struct inode *h_inode;
|
|
+ struct super_block *sb;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+
|
|
+ sb = inode->i_sb;
|
|
+ bindex = au_ibtop(inode);
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+ if (!force
|
|
+ && !S_ISDIR(h_inode->i_mode)
|
|
+ && au_opt_test(au_mntflags(sb), PLINK)
|
|
+ && au_plink_test(inode))
|
|
+ return;
|
|
+
|
|
+ /*
|
|
+ * 0 can happen in revalidating.
|
|
+ * h_inode->i_mutex may not be held here, but it is harmless since once
|
|
+ * i_nlink reaches 0, it will never become positive except O_TMPFILE
|
|
+ * case.
|
|
+ * todo: O_TMPFILE+linkat(AT_SYMLINK_FOLLOW) bypassing aufs may cause
|
|
+ * the incorrect link count.
|
|
+ */
|
|
+ set_nlink(inode, h_inode->i_nlink);
|
|
+
|
|
+ /*
|
|
+ * fewer nlink makes find(1) noisy, but larger nlink doesn't.
|
|
+ * it may includes whplink directory.
|
|
+ */
|
|
+ if (S_ISDIR(h_inode->i_mode)) {
|
|
+ bbot = au_ibbot(inode);
|
|
+ for (bindex++; bindex <= bbot; bindex++) {
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+ if (h_inode)
|
|
+ au_add_nlink(inode, h_inode);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+void au_cpup_attr_changeable(struct inode *inode)
|
|
+{
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ h_inode = au_h_iptr(inode, au_ibtop(inode));
|
|
+ inode->i_mode = h_inode->i_mode;
|
|
+ inode->i_uid = h_inode->i_uid;
|
|
+ inode->i_gid = h_inode->i_gid;
|
|
+ au_cpup_attr_timesizes(inode);
|
|
+ au_cpup_attr_flags(inode, h_inode->i_flags);
|
|
+}
|
|
+
|
|
+void au_cpup_igen(struct inode *inode, struct inode *h_inode)
|
|
+{
|
|
+ struct au_iinfo *iinfo = au_ii(inode);
|
|
+
|
|
+ IiMustWriteLock(inode);
|
|
+
|
|
+ iinfo->ii_higen = h_inode->i_generation;
|
|
+ iinfo->ii_hsb1 = h_inode->i_sb;
|
|
+}
|
|
+
|
|
+void au_cpup_attr_all(struct inode *inode, int force)
|
|
+{
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ h_inode = au_h_iptr(inode, au_ibtop(inode));
|
|
+ au_cpup_attr_changeable(inode);
|
|
+ if (inode->i_nlink > 0)
|
|
+ au_cpup_attr_nlink(inode, force);
|
|
+ inode->i_rdev = h_inode->i_rdev;
|
|
+ inode->i_blkbits = h_inode->i_blkbits;
|
|
+ au_cpup_igen(inode, h_inode);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* Note: dt_dentry and dt_h_dentry are not dget/dput-ed */
|
|
+
|
|
+/* keep the timestamps of the parent dir when cpup */
|
|
+void au_dtime_store(struct au_dtime *dt, struct dentry *dentry,
|
|
+ struct path *h_path)
|
|
+{
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ dt->dt_dentry = dentry;
|
|
+ dt->dt_h_path = *h_path;
|
|
+ h_inode = d_inode(h_path->dentry);
|
|
+ dt->dt_atime = h_inode->i_atime;
|
|
+ dt->dt_mtime = h_inode->i_mtime;
|
|
+ /* smp_mb(); */
|
|
+}
|
|
+
|
|
+void au_dtime_revert(struct au_dtime *dt)
|
|
+{
|
|
+ struct iattr attr;
|
|
+ int err;
|
|
+
|
|
+ attr.ia_atime = dt->dt_atime;
|
|
+ attr.ia_mtime = dt->dt_mtime;
|
|
+ attr.ia_valid = ATTR_FORCE | ATTR_MTIME | ATTR_MTIME_SET
|
|
+ | ATTR_ATIME | ATTR_ATIME_SET;
|
|
+
|
|
+ /* no delegation since this is a directory */
|
|
+ err = vfsub_notify_change(&dt->dt_h_path, &attr, /*delegated*/NULL);
|
|
+ if (unlikely(err))
|
|
+ pr_warn("restoring timestamps failed(%d). ignored\n", err);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* internal use only */
|
|
+struct au_cpup_reg_attr {
|
|
+ int valid;
|
|
+ struct kstat st;
|
|
+ unsigned int iflags; /* inode->i_flags */
|
|
+};
|
|
+
|
|
+static noinline_for_stack
|
|
+int cpup_iattr(struct dentry *dst, aufs_bindex_t bindex, struct dentry *h_src,
|
|
+ struct au_cpup_reg_attr *h_src_attr)
|
|
+{
|
|
+ int err, sbits, icex;
|
|
+ unsigned int mnt_flags;
|
|
+ unsigned char verbose;
|
|
+ struct iattr ia;
|
|
+ struct path h_path;
|
|
+ struct inode *h_isrc, *h_idst;
|
|
+ struct kstat *h_st;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ h_path.dentry = au_h_dptr(dst, bindex);
|
|
+ h_idst = d_inode(h_path.dentry);
|
|
+ br = au_sbr(dst->d_sb, bindex);
|
|
+ h_path.mnt = au_br_mnt(br);
|
|
+ h_isrc = d_inode(h_src);
|
|
+ ia.ia_valid = ATTR_FORCE | ATTR_UID | ATTR_GID
|
|
+ | ATTR_ATIME | ATTR_MTIME
|
|
+ | ATTR_ATIME_SET | ATTR_MTIME_SET;
|
|
+ if (h_src_attr && h_src_attr->valid) {
|
|
+ h_st = &h_src_attr->st;
|
|
+ ia.ia_uid = h_st->uid;
|
|
+ ia.ia_gid = h_st->gid;
|
|
+ ia.ia_atime = h_st->atime;
|
|
+ ia.ia_mtime = h_st->mtime;
|
|
+ if (h_idst->i_mode != h_st->mode
|
|
+ && !S_ISLNK(h_idst->i_mode)) {
|
|
+ ia.ia_valid |= ATTR_MODE;
|
|
+ ia.ia_mode = h_st->mode;
|
|
+ }
|
|
+ sbits = !!(h_st->mode & (S_ISUID | S_ISGID));
|
|
+ au_cpup_attr_flags(h_idst, h_src_attr->iflags);
|
|
+ } else {
|
|
+ ia.ia_uid = h_isrc->i_uid;
|
|
+ ia.ia_gid = h_isrc->i_gid;
|
|
+ ia.ia_atime = h_isrc->i_atime;
|
|
+ ia.ia_mtime = h_isrc->i_mtime;
|
|
+ if (h_idst->i_mode != h_isrc->i_mode
|
|
+ && !S_ISLNK(h_idst->i_mode)) {
|
|
+ ia.ia_valid |= ATTR_MODE;
|
|
+ ia.ia_mode = h_isrc->i_mode;
|
|
+ }
|
|
+ sbits = !!(h_isrc->i_mode & (S_ISUID | S_ISGID));
|
|
+ au_cpup_attr_flags(h_idst, h_isrc->i_flags);
|
|
+ }
|
|
+ /* no delegation since it is just created */
|
|
+ err = vfsub_notify_change(&h_path, &ia, /*delegated*/NULL);
|
|
+
|
|
+ /* is this nfs only? */
|
|
+ if (!err && sbits && au_test_nfs(h_path.dentry->d_sb)) {
|
|
+ ia.ia_valid = ATTR_FORCE | ATTR_MODE;
|
|
+ ia.ia_mode = h_isrc->i_mode;
|
|
+ err = vfsub_notify_change(&h_path, &ia, /*delegated*/NULL);
|
|
+ }
|
|
+
|
|
+ icex = br->br_perm & AuBrAttr_ICEX;
|
|
+ if (!err) {
|
|
+ mnt_flags = au_mntflags(dst->d_sb);
|
|
+ verbose = !!au_opt_test(mnt_flags, VERBOSE);
|
|
+ err = au_cpup_xattr(h_path.dentry, h_src, icex, verbose);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_do_copy_file(struct file *dst, struct file *src, loff_t len,
|
|
+ char *buf, unsigned long blksize)
|
|
+{
|
|
+ int err;
|
|
+ size_t sz, rbytes, wbytes;
|
|
+ unsigned char all_zero;
|
|
+ char *p, *zp;
|
|
+ struct inode *h_inode;
|
|
+ /* reduce stack usage */
|
|
+ struct iattr *ia;
|
|
+
|
|
+ zp = page_address(ZERO_PAGE(0));
|
|
+ if (unlikely(!zp))
|
|
+ return -ENOMEM; /* possible? */
|
|
+
|
|
+ err = 0;
|
|
+ all_zero = 0;
|
|
+ while (len) {
|
|
+ AuDbg("len %lld\n", len);
|
|
+ sz = blksize;
|
|
+ if (len < blksize)
|
|
+ sz = len;
|
|
+
|
|
+ rbytes = 0;
|
|
+ /* todo: signal_pending? */
|
|
+ while (!rbytes || err == -EAGAIN || err == -EINTR) {
|
|
+ rbytes = vfsub_read_k(src, buf, sz, &src->f_pos);
|
|
+ err = rbytes;
|
|
+ }
|
|
+ if (unlikely(err < 0))
|
|
+ break;
|
|
+
|
|
+ all_zero = 0;
|
|
+ if (len >= rbytes && rbytes == blksize)
|
|
+ all_zero = !memcmp(buf, zp, rbytes);
|
|
+ if (!all_zero) {
|
|
+ wbytes = rbytes;
|
|
+ p = buf;
|
|
+ while (wbytes) {
|
|
+ size_t b;
|
|
+
|
|
+ b = vfsub_write_k(dst, p, wbytes, &dst->f_pos);
|
|
+ err = b;
|
|
+ /* todo: signal_pending? */
|
|
+ if (unlikely(err == -EAGAIN || err == -EINTR))
|
|
+ continue;
|
|
+ if (unlikely(err < 0))
|
|
+ break;
|
|
+ wbytes -= b;
|
|
+ p += b;
|
|
+ }
|
|
+ if (unlikely(err < 0))
|
|
+ break;
|
|
+ } else {
|
|
+ loff_t res;
|
|
+
|
|
+ AuLabel(hole);
|
|
+ res = vfsub_llseek(dst, rbytes, SEEK_CUR);
|
|
+ err = res;
|
|
+ if (unlikely(res < 0))
|
|
+ break;
|
|
+ }
|
|
+ len -= rbytes;
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+ /* the last block may be a hole */
|
|
+ if (!err && all_zero) {
|
|
+ AuLabel(last hole);
|
|
+
|
|
+ err = 1;
|
|
+ if (au_test_nfs(dst->f_path.dentry->d_sb)) {
|
|
+ /* nfs requires this step to make last hole */
|
|
+ /* is this only nfs? */
|
|
+ do {
|
|
+ /* todo: signal_pending? */
|
|
+ err = vfsub_write_k(dst, "\0", 1, &dst->f_pos);
|
|
+ } while (err == -EAGAIN || err == -EINTR);
|
|
+ if (err == 1)
|
|
+ dst->f_pos--;
|
|
+ }
|
|
+
|
|
+ if (err == 1) {
|
|
+ ia = (void *)buf;
|
|
+ ia->ia_size = dst->f_pos;
|
|
+ ia->ia_valid = ATTR_SIZE | ATTR_FILE;
|
|
+ ia->ia_file = dst;
|
|
+ h_inode = file_inode(dst);
|
|
+ inode_lock_nested(h_inode, AuLsc_I_CHILD2);
|
|
+ /* no delegation since it is just created */
|
|
+ err = vfsub_notify_change(&dst->f_path, ia,
|
|
+ /*delegated*/NULL);
|
|
+ inode_unlock(h_inode);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_copy_file(struct file *dst, struct file *src, loff_t len)
|
|
+{
|
|
+ int err;
|
|
+ unsigned long blksize;
|
|
+ unsigned char do_kfree;
|
|
+ char *buf;
|
|
+ struct super_block *h_sb;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ h_sb = file_inode(dst)->i_sb;
|
|
+ blksize = h_sb->s_blocksize;
|
|
+ if (!blksize || PAGE_SIZE < blksize)
|
|
+ blksize = PAGE_SIZE;
|
|
+ AuDbg("blksize %lu\n", blksize);
|
|
+ do_kfree = (blksize != PAGE_SIZE && blksize >= sizeof(struct iattr *));
|
|
+ if (do_kfree)
|
|
+ buf = kmalloc(blksize, GFP_NOFS);
|
|
+ else
|
|
+ buf = (void *)__get_free_page(GFP_NOFS);
|
|
+ if (unlikely(!buf))
|
|
+ goto out;
|
|
+
|
|
+ if (len > (1 << 22))
|
|
+ AuDbg("copying a large file %lld\n", (long long)len);
|
|
+
|
|
+ src->f_pos = 0;
|
|
+ dst->f_pos = 0;
|
|
+ err = au_do_copy_file(dst, src, len, buf, blksize);
|
|
+ if (do_kfree) {
|
|
+ AuDebugOn(!au_kfree_do_sz_test(blksize));
|
|
+ au_kfree_do_rcu(buf);
|
|
+ } else
|
|
+ free_page((unsigned long)buf);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_do_copy(struct file *dst, struct file *src, loff_t len)
|
|
+{
|
|
+ int err;
|
|
+ struct super_block *h_src_sb;
|
|
+ struct inode *h_src_inode;
|
|
+
|
|
+ h_src_inode = file_inode(src);
|
|
+ h_src_sb = h_src_inode->i_sb;
|
|
+
|
|
+ /* XFS acquires inode_lock */
|
|
+ if (!au_test_xfs(h_src_sb))
|
|
+ err = au_copy_file(dst, src, len);
|
|
+ else {
|
|
+ inode_unlock_shared(h_src_inode);
|
|
+ err = au_copy_file(dst, src, len);
|
|
+ inode_lock_shared_nested(h_src_inode, AuLsc_I_CHILD);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_clone_or_copy(struct file *dst, struct file *src, loff_t len)
|
|
+{
|
|
+ int err;
|
|
+ loff_t lo;
|
|
+ struct super_block *h_src_sb;
|
|
+ struct inode *h_src_inode;
|
|
+
|
|
+ h_src_inode = file_inode(src);
|
|
+ h_src_sb = h_src_inode->i_sb;
|
|
+ if (h_src_sb != file_inode(dst)->i_sb
|
|
+ || !dst->f_op->remap_file_range) {
|
|
+ err = au_do_copy(dst, src, len);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (!au_test_nfs(h_src_sb)) {
|
|
+ inode_unlock_shared(h_src_inode);
|
|
+ lo = vfsub_clone_file_range(src, dst, len);
|
|
+ inode_lock_shared_nested(h_src_inode, AuLsc_I_CHILD);
|
|
+ } else
|
|
+ lo = vfsub_clone_file_range(src, dst, len);
|
|
+ if (lo == len) {
|
|
+ err = 0;
|
|
+ goto out; /* success */
|
|
+ } else if (lo >= 0)
|
|
+ /* todo: possible? */
|
|
+ /* paritially succeeded */
|
|
+ AuDbg("lo %lld, len %lld. Retrying.\n", lo, len);
|
|
+ else if (lo != -EOPNOTSUPP) {
|
|
+ /* older XFS has a condition in cloning */
|
|
+ err = lo;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* the backend fs on NFS may not support cloning */
|
|
+ err = au_do_copy(dst, src, len);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * to support a sparse file which is opened with O_APPEND,
|
|
+ * we need to close the file.
|
|
+ */
|
|
+static int au_cp_regular(struct au_cp_generic *cpg)
|
|
+{
|
|
+ int err, i;
|
|
+ enum { SRC, DST };
|
|
+ struct {
|
|
+ aufs_bindex_t bindex;
|
|
+ unsigned int flags;
|
|
+ struct dentry *dentry;
|
|
+ int force_wr;
|
|
+ struct file *file;
|
|
+ } *f, file[] = {
|
|
+ {
|
|
+ .bindex = cpg->bsrc,
|
|
+ .flags = O_RDONLY | O_NOATIME | O_LARGEFILE,
|
|
+ },
|
|
+ {
|
|
+ .bindex = cpg->bdst,
|
|
+ .flags = O_WRONLY | O_NOATIME | O_LARGEFILE,
|
|
+ .force_wr = !!au_ftest_cpup(cpg->flags, RWDST),
|
|
+ }
|
|
+ };
|
|
+ struct au_branch *br;
|
|
+ struct super_block *sb, *h_src_sb;
|
|
+ struct inode *h_src_inode;
|
|
+ struct task_struct *tsk = current;
|
|
+
|
|
+ /* bsrc branch can be ro/rw. */
|
|
+ sb = cpg->dentry->d_sb;
|
|
+ f = file;
|
|
+ for (i = 0; i < 2; i++, f++) {
|
|
+ f->dentry = au_h_dptr(cpg->dentry, f->bindex);
|
|
+ f->file = au_h_open(cpg->dentry, f->bindex, f->flags,
|
|
+ /*file*/NULL, f->force_wr);
|
|
+ if (IS_ERR(f->file)) {
|
|
+ err = PTR_ERR(f->file);
|
|
+ if (i == SRC)
|
|
+ goto out;
|
|
+ else
|
|
+ goto out_src;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* try stopping to update while we copyup */
|
|
+ h_src_inode = d_inode(file[SRC].dentry);
|
|
+ h_src_sb = h_src_inode->i_sb;
|
|
+ if (!au_test_nfs(h_src_sb))
|
|
+ IMustLock(h_src_inode);
|
|
+ err = au_clone_or_copy(file[DST].file, file[SRC].file, cpg->len);
|
|
+
|
|
+ /* i wonder if we had O_NO_DELAY_FPUT flag */
|
|
+ if (tsk->flags & PF_KTHREAD)
|
|
+ __fput_sync(file[DST].file);
|
|
+ else {
|
|
+ /* it happened actually */
|
|
+ fput(file[DST].file);
|
|
+ /*
|
|
+ * too bad.
|
|
+ * we have to call both since we don't know which place the file
|
|
+ * was added to.
|
|
+ */
|
|
+ task_work_run();
|
|
+ flush_delayed_fput();
|
|
+ }
|
|
+ br = au_sbr(sb, file[DST].bindex);
|
|
+ au_lcnt_dec(&br->br_nfiles);
|
|
+
|
|
+out_src:
|
|
+ fput(file[SRC].file);
|
|
+ br = au_sbr(sb, file[SRC].bindex);
|
|
+ au_lcnt_dec(&br->br_nfiles);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_do_cpup_regular(struct au_cp_generic *cpg,
|
|
+ struct au_cpup_reg_attr *h_src_attr)
|
|
+{
|
|
+ int err, rerr;
|
|
+ loff_t l;
|
|
+ struct path h_path;
|
|
+ struct inode *h_src_inode, *h_dst_inode;
|
|
+
|
|
+ err = 0;
|
|
+ h_src_inode = au_h_iptr(d_inode(cpg->dentry), cpg->bsrc);
|
|
+ l = i_size_read(h_src_inode);
|
|
+ if (cpg->len == -1 || l < cpg->len)
|
|
+ cpg->len = l;
|
|
+ if (cpg->len) {
|
|
+ /* try stopping to update while we are referencing */
|
|
+ inode_lock_shared_nested(h_src_inode, AuLsc_I_CHILD);
|
|
+ au_pin_hdir_unlock(cpg->pin);
|
|
+
|
|
+ h_path.dentry = au_h_dptr(cpg->dentry, cpg->bsrc);
|
|
+ h_path.mnt = au_sbr_mnt(cpg->dentry->d_sb, cpg->bsrc);
|
|
+ h_src_attr->iflags = h_src_inode->i_flags;
|
|
+ if (!au_test_nfs(h_src_inode->i_sb))
|
|
+ err = vfsub_getattr(&h_path, &h_src_attr->st);
|
|
+ else {
|
|
+ inode_unlock_shared(h_src_inode);
|
|
+ err = vfsub_getattr(&h_path, &h_src_attr->st);
|
|
+ inode_lock_shared_nested(h_src_inode, AuLsc_I_CHILD);
|
|
+ }
|
|
+ if (unlikely(err)) {
|
|
+ inode_unlock_shared(h_src_inode);
|
|
+ goto out;
|
|
+ }
|
|
+ h_src_attr->valid = 1;
|
|
+ if (!au_test_nfs(h_src_inode->i_sb)) {
|
|
+ err = au_cp_regular(cpg);
|
|
+ inode_unlock_shared(h_src_inode);
|
|
+ } else {
|
|
+ inode_unlock_shared(h_src_inode);
|
|
+ err = au_cp_regular(cpg);
|
|
+ }
|
|
+ rerr = au_pin_hdir_relock(cpg->pin);
|
|
+ if (!err && rerr)
|
|
+ err = rerr;
|
|
+ }
|
|
+ if (!err && (h_src_inode->i_state & I_LINKABLE)) {
|
|
+ h_path.dentry = au_h_dptr(cpg->dentry, cpg->bdst);
|
|
+ h_dst_inode = d_inode(h_path.dentry);
|
|
+ spin_lock(&h_dst_inode->i_lock);
|
|
+ h_dst_inode->i_state |= I_LINKABLE;
|
|
+ spin_unlock(&h_dst_inode->i_lock);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_do_cpup_symlink(struct path *h_path, struct dentry *h_src,
|
|
+ struct inode *h_dir)
|
|
+{
|
|
+ int err, symlen;
|
|
+ mm_segment_t old_fs;
|
|
+ union {
|
|
+ char *k;
|
|
+ char __user *u;
|
|
+ } sym;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ sym.k = (void *)__get_free_page(GFP_NOFS);
|
|
+ if (unlikely(!sym.k))
|
|
+ goto out;
|
|
+
|
|
+ /* unnecessary to support mmap_sem since symlink is not mmap-able */
|
|
+ old_fs = get_fs();
|
|
+ set_fs(KERNEL_DS);
|
|
+ symlen = vfs_readlink(h_src, sym.u, PATH_MAX);
|
|
+ err = symlen;
|
|
+ set_fs(old_fs);
|
|
+
|
|
+ if (symlen > 0) {
|
|
+ sym.k[symlen] = 0;
|
|
+ err = vfsub_symlink(h_dir, h_path, sym.k);
|
|
+ }
|
|
+ free_page((unsigned long)sym.k);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * regardless 'acl' option, reset all ACL.
|
|
+ * All ACL will be copied up later from the original entry on the lower branch.
|
|
+ */
|
|
+static int au_reset_acl(struct inode *h_dir, struct path *h_path, umode_t mode)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *h_dentry;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ h_dentry = h_path->dentry;
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ /* forget_all_cached_acls(h_inode)); */
|
|
+ err = vfsub_removexattr(h_dentry, XATTR_NAME_POSIX_ACL_ACCESS);
|
|
+ AuTraceErr(err);
|
|
+ if (err == -EOPNOTSUPP)
|
|
+ err = 0;
|
|
+ if (!err)
|
|
+ err = vfsub_acl_chmod(h_inode, mode);
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_do_cpup_dir(struct au_cp_generic *cpg, struct dentry *dst_parent,
|
|
+ struct inode *h_dir, struct path *h_path)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *dir, *inode;
|
|
+
|
|
+ err = vfsub_removexattr(h_path->dentry, XATTR_NAME_POSIX_ACL_DEFAULT);
|
|
+ AuTraceErr(err);
|
|
+ if (err == -EOPNOTSUPP)
|
|
+ err = 0;
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ /*
|
|
+ * strange behaviour from the users view,
|
|
+ * particularly setattr case
|
|
+ */
|
|
+ dir = d_inode(dst_parent);
|
|
+ if (au_ibtop(dir) == cpg->bdst)
|
|
+ au_cpup_attr_nlink(dir, /*force*/1);
|
|
+ inode = d_inode(cpg->dentry);
|
|
+ au_cpup_attr_nlink(inode, /*force*/1);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static noinline_for_stack
|
|
+int cpup_entry(struct au_cp_generic *cpg, struct dentry *dst_parent,
|
|
+ struct au_cpup_reg_attr *h_src_attr)
|
|
+{
|
|
+ int err;
|
|
+ umode_t mode;
|
|
+ unsigned int mnt_flags;
|
|
+ unsigned char isdir, isreg, force;
|
|
+ const unsigned char do_dt = !!au_ftest_cpup(cpg->flags, DTIME);
|
|
+ struct au_dtime dt;
|
|
+ struct path h_path;
|
|
+ struct dentry *h_src, *h_dst, *h_parent;
|
|
+ struct inode *h_inode, *h_dir;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ /* bsrc branch can be ro/rw. */
|
|
+ h_src = au_h_dptr(cpg->dentry, cpg->bsrc);
|
|
+ h_inode = d_inode(h_src);
|
|
+ AuDebugOn(h_inode != au_h_iptr(d_inode(cpg->dentry), cpg->bsrc));
|
|
+
|
|
+ /* try stopping to be referenced while we are creating */
|
|
+ h_dst = au_h_dptr(cpg->dentry, cpg->bdst);
|
|
+ if (au_ftest_cpup(cpg->flags, RENAME))
|
|
+ AuDebugOn(strncmp(h_dst->d_name.name, AUFS_WH_PFX,
|
|
+ AUFS_WH_PFX_LEN));
|
|
+ h_parent = h_dst->d_parent; /* dir inode is locked */
|
|
+ h_dir = d_inode(h_parent);
|
|
+ IMustLock(h_dir);
|
|
+ AuDebugOn(h_parent != h_dst->d_parent);
|
|
+
|
|
+ sb = cpg->dentry->d_sb;
|
|
+ h_path.mnt = au_sbr_mnt(sb, cpg->bdst);
|
|
+ if (do_dt) {
|
|
+ h_path.dentry = h_parent;
|
|
+ au_dtime_store(&dt, dst_parent, &h_path);
|
|
+ }
|
|
+ h_path.dentry = h_dst;
|
|
+
|
|
+ isreg = 0;
|
|
+ isdir = 0;
|
|
+ mode = h_inode->i_mode;
|
|
+ switch (mode & S_IFMT) {
|
|
+ case S_IFREG:
|
|
+ isreg = 1;
|
|
+ err = vfsub_create(h_dir, &h_path, 0600, /*want_excl*/true);
|
|
+ if (!err)
|
|
+ err = au_do_cpup_regular(cpg, h_src_attr);
|
|
+ break;
|
|
+ case S_IFDIR:
|
|
+ isdir = 1;
|
|
+ err = vfsub_mkdir(h_dir, &h_path, mode);
|
|
+ if (!err)
|
|
+ err = au_do_cpup_dir(cpg, dst_parent, h_dir, &h_path);
|
|
+ break;
|
|
+ case S_IFLNK:
|
|
+ err = au_do_cpup_symlink(&h_path, h_src, h_dir);
|
|
+ break;
|
|
+ case S_IFCHR:
|
|
+ case S_IFBLK:
|
|
+ AuDebugOn(!capable(CAP_MKNOD));
|
|
+ /*FALLTHROUGH*/
|
|
+ case S_IFIFO:
|
|
+ case S_IFSOCK:
|
|
+ err = vfsub_mknod(h_dir, &h_path, mode, h_inode->i_rdev);
|
|
+ break;
|
|
+ default:
|
|
+ AuIOErr("Unknown inode type 0%o\n", mode);
|
|
+ err = -EIO;
|
|
+ }
|
|
+ if (!err)
|
|
+ err = au_reset_acl(h_dir, &h_path, mode);
|
|
+
|
|
+ mnt_flags = au_mntflags(sb);
|
|
+ if (!au_opt_test(mnt_flags, UDBA_NONE)
|
|
+ && !isdir
|
|
+ && au_opt_test(mnt_flags, XINO)
|
|
+ && (h_inode->i_nlink == 1
|
|
+ || (h_inode->i_state & I_LINKABLE))
|
|
+ /* todo: unnecessary? */
|
|
+ /* && d_inode(cpg->dentry)->i_nlink == 1 */
|
|
+ && cpg->bdst < cpg->bsrc
|
|
+ && !au_ftest_cpup(cpg->flags, KEEPLINO))
|
|
+ au_xino_write(sb, cpg->bsrc, h_inode->i_ino, /*ino*/0);
|
|
+ /* ignore this error */
|
|
+
|
|
+ if (!err) {
|
|
+ force = 0;
|
|
+ if (isreg) {
|
|
+ force = !!cpg->len;
|
|
+ if (cpg->len == -1)
|
|
+ force = !!i_size_read(h_inode);
|
|
+ }
|
|
+ au_fhsm_wrote(sb, cpg->bdst, force);
|
|
+ }
|
|
+
|
|
+ if (do_dt)
|
|
+ au_dtime_revert(&dt);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_do_ren_after_cpup(struct au_cp_generic *cpg, struct path *h_path)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *dentry, *h_dentry, *h_parent, *parent;
|
|
+ struct inode *h_dir;
|
|
+ aufs_bindex_t bdst;
|
|
+
|
|
+ dentry = cpg->dentry;
|
|
+ bdst = cpg->bdst;
|
|
+ h_dentry = au_h_dptr(dentry, bdst);
|
|
+ if (!au_ftest_cpup(cpg->flags, OVERWRITE)) {
|
|
+ dget(h_dentry);
|
|
+ au_set_h_dptr(dentry, bdst, NULL);
|
|
+ err = au_lkup_neg(dentry, bdst, /*wh*/0);
|
|
+ if (!err)
|
|
+ h_path->dentry = dget(au_h_dptr(dentry, bdst));
|
|
+ au_set_h_dptr(dentry, bdst, h_dentry);
|
|
+ } else {
|
|
+ err = 0;
|
|
+ parent = dget_parent(dentry);
|
|
+ h_parent = au_h_dptr(parent, bdst);
|
|
+ dput(parent);
|
|
+ h_path->dentry = vfsub_lkup_one(&dentry->d_name, h_parent);
|
|
+ if (IS_ERR(h_path->dentry))
|
|
+ err = PTR_ERR(h_path->dentry);
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ h_parent = h_dentry->d_parent; /* dir inode is locked */
|
|
+ h_dir = d_inode(h_parent);
|
|
+ IMustLock(h_dir);
|
|
+ AuDbg("%pd %pd\n", h_dentry, h_path->dentry);
|
|
+ /* no delegation since it is just created */
|
|
+ err = vfsub_rename(h_dir, h_dentry, h_dir, h_path, /*delegated*/NULL,
|
|
+ /*flags*/0);
|
|
+ dput(h_path->dentry);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * copyup the @dentry from @bsrc to @bdst.
|
|
+ * the caller must set the both of lower dentries.
|
|
+ * @len is for truncating when it is -1 copyup the entire file.
|
|
+ * in link/rename cases, @dst_parent may be different from the real one.
|
|
+ * basic->bsrc can be larger than basic->bdst.
|
|
+ * aufs doesn't touch the credential so
|
|
+ * security_inode_copy_up{,_xattr}() are unnecessary.
|
|
+ */
|
|
+static int au_cpup_single(struct au_cp_generic *cpg, struct dentry *dst_parent)
|
|
+{
|
|
+ int err, rerr;
|
|
+ aufs_bindex_t old_ibtop;
|
|
+ unsigned char isdir, plink;
|
|
+ struct dentry *h_src, *h_dst, *h_parent;
|
|
+ struct inode *dst_inode, *h_dir, *inode, *delegated, *src_inode;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ /* to reduce stack size */
|
|
+ struct {
|
|
+ struct au_dtime dt;
|
|
+ struct path h_path;
|
|
+ struct au_cpup_reg_attr h_src_attr;
|
|
+ } *a;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ a = kmalloc(sizeof(*a), GFP_NOFS);
|
|
+ if (unlikely(!a))
|
|
+ goto out;
|
|
+ a->h_src_attr.valid = 0;
|
|
+
|
|
+ sb = cpg->dentry->d_sb;
|
|
+ br = au_sbr(sb, cpg->bdst);
|
|
+ a->h_path.mnt = au_br_mnt(br);
|
|
+ h_dst = au_h_dptr(cpg->dentry, cpg->bdst);
|
|
+ h_parent = h_dst->d_parent; /* dir inode is locked */
|
|
+ h_dir = d_inode(h_parent);
|
|
+ IMustLock(h_dir);
|
|
+
|
|
+ h_src = au_h_dptr(cpg->dentry, cpg->bsrc);
|
|
+ inode = d_inode(cpg->dentry);
|
|
+
|
|
+ if (!dst_parent)
|
|
+ dst_parent = dget_parent(cpg->dentry);
|
|
+ else
|
|
+ dget(dst_parent);
|
|
+
|
|
+ plink = !!au_opt_test(au_mntflags(sb), PLINK);
|
|
+ dst_inode = au_h_iptr(inode, cpg->bdst);
|
|
+ if (dst_inode) {
|
|
+ if (unlikely(!plink)) {
|
|
+ err = -EIO;
|
|
+ AuIOErr("hi%lu(i%lu) exists on b%d "
|
|
+ "but plink is disabled\n",
|
|
+ dst_inode->i_ino, inode->i_ino, cpg->bdst);
|
|
+ goto out_parent;
|
|
+ }
|
|
+
|
|
+ if (dst_inode->i_nlink) {
|
|
+ const int do_dt = au_ftest_cpup(cpg->flags, DTIME);
|
|
+
|
|
+ h_src = au_plink_lkup(inode, cpg->bdst);
|
|
+ err = PTR_ERR(h_src);
|
|
+ if (IS_ERR(h_src))
|
|
+ goto out_parent;
|
|
+ if (unlikely(d_is_negative(h_src))) {
|
|
+ err = -EIO;
|
|
+ AuIOErr("i%lu exists on b%d "
|
|
+ "but not pseudo-linked\n",
|
|
+ inode->i_ino, cpg->bdst);
|
|
+ dput(h_src);
|
|
+ goto out_parent;
|
|
+ }
|
|
+
|
|
+ if (do_dt) {
|
|
+ a->h_path.dentry = h_parent;
|
|
+ au_dtime_store(&a->dt, dst_parent, &a->h_path);
|
|
+ }
|
|
+
|
|
+ a->h_path.dentry = h_dst;
|
|
+ delegated = NULL;
|
|
+ err = vfsub_link(h_src, h_dir, &a->h_path, &delegated);
|
|
+ if (!err && au_ftest_cpup(cpg->flags, RENAME))
|
|
+ err = au_do_ren_after_cpup(cpg, &a->h_path);
|
|
+ if (do_dt)
|
|
+ au_dtime_revert(&a->dt);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal link\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ dput(h_src);
|
|
+ goto out_parent;
|
|
+ } else
|
|
+ /* todo: cpup_wh_file? */
|
|
+ /* udba work */
|
|
+ au_update_ibrange(inode, /*do_put_zero*/1);
|
|
+ }
|
|
+
|
|
+ isdir = S_ISDIR(inode->i_mode);
|
|
+ old_ibtop = au_ibtop(inode);
|
|
+ err = cpup_entry(cpg, dst_parent, &a->h_src_attr);
|
|
+ if (unlikely(err))
|
|
+ goto out_rev;
|
|
+ dst_inode = d_inode(h_dst);
|
|
+ inode_lock_nested(dst_inode, AuLsc_I_CHILD2);
|
|
+ /* todo: necessary? */
|
|
+ /* au_pin_hdir_unlock(cpg->pin); */
|
|
+
|
|
+ err = cpup_iattr(cpg->dentry, cpg->bdst, h_src, &a->h_src_attr);
|
|
+ if (unlikely(err)) {
|
|
+ /* todo: necessary? */
|
|
+ /* au_pin_hdir_relock(cpg->pin); */ /* ignore an error */
|
|
+ inode_unlock(dst_inode);
|
|
+ goto out_rev;
|
|
+ }
|
|
+
|
|
+ if (cpg->bdst < old_ibtop) {
|
|
+ if (S_ISREG(inode->i_mode)) {
|
|
+ err = au_dy_iaop(inode, cpg->bdst, dst_inode);
|
|
+ if (unlikely(err)) {
|
|
+ /* ignore an error */
|
|
+ /* au_pin_hdir_relock(cpg->pin); */
|
|
+ inode_unlock(dst_inode);
|
|
+ goto out_rev;
|
|
+ }
|
|
+ }
|
|
+ au_set_ibtop(inode, cpg->bdst);
|
|
+ } else
|
|
+ au_set_ibbot(inode, cpg->bdst);
|
|
+ au_set_h_iptr(inode, cpg->bdst, au_igrab(dst_inode),
|
|
+ au_hi_flags(inode, isdir));
|
|
+
|
|
+ /* todo: necessary? */
|
|
+ /* err = au_pin_hdir_relock(cpg->pin); */
|
|
+ inode_unlock(dst_inode);
|
|
+ if (unlikely(err))
|
|
+ goto out_rev;
|
|
+
|
|
+ src_inode = d_inode(h_src);
|
|
+ if (!isdir
|
|
+ && (src_inode->i_nlink > 1
|
|
+ || src_inode->i_state & I_LINKABLE)
|
|
+ && plink)
|
|
+ au_plink_append(inode, cpg->bdst, h_dst);
|
|
+
|
|
+ if (au_ftest_cpup(cpg->flags, RENAME)) {
|
|
+ a->h_path.dentry = h_dst;
|
|
+ err = au_do_ren_after_cpup(cpg, &a->h_path);
|
|
+ }
|
|
+ if (!err)
|
|
+ goto out_parent; /* success */
|
|
+
|
|
+ /* revert */
|
|
+out_rev:
|
|
+ a->h_path.dentry = h_parent;
|
|
+ au_dtime_store(&a->dt, dst_parent, &a->h_path);
|
|
+ a->h_path.dentry = h_dst;
|
|
+ rerr = 0;
|
|
+ if (d_is_positive(h_dst)) {
|
|
+ if (!isdir) {
|
|
+ /* no delegation since it is just created */
|
|
+ rerr = vfsub_unlink(h_dir, &a->h_path,
|
|
+ /*delegated*/NULL, /*force*/0);
|
|
+ } else
|
|
+ rerr = vfsub_rmdir(h_dir, &a->h_path);
|
|
+ }
|
|
+ au_dtime_revert(&a->dt);
|
|
+ if (rerr) {
|
|
+ AuIOErr("failed removing broken entry(%d, %d)\n", err, rerr);
|
|
+ err = -EIO;
|
|
+ }
|
|
+out_parent:
|
|
+ dput(dst_parent);
|
|
+ au_kfree_rcu(a);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+#if 0 /* reserved */
|
|
+struct au_cpup_single_args {
|
|
+ int *errp;
|
|
+ struct au_cp_generic *cpg;
|
|
+ struct dentry *dst_parent;
|
|
+};
|
|
+
|
|
+static void au_call_cpup_single(void *args)
|
|
+{
|
|
+ struct au_cpup_single_args *a = args;
|
|
+
|
|
+ au_pin_hdir_acquire_nest(a->cpg->pin);
|
|
+ *a->errp = au_cpup_single(a->cpg, a->dst_parent);
|
|
+ au_pin_hdir_release(a->cpg->pin);
|
|
+}
|
|
+#endif
|
|
+
|
|
+/*
|
|
+ * prevent SIGXFSZ in copy-up.
|
|
+ * testing CAP_MKNOD is for generic fs,
|
|
+ * but CAP_FSETID is for xfs only, currently.
|
|
+ */
|
|
+static int au_cpup_sio_test(struct au_pin *pin, umode_t mode)
|
|
+{
|
|
+ int do_sio;
|
|
+ struct super_block *sb;
|
|
+ struct inode *h_dir;
|
|
+
|
|
+ do_sio = 0;
|
|
+ sb = au_pinned_parent(pin)->d_sb;
|
|
+ if (!au_wkq_test()
|
|
+ && (!au_sbi(sb)->si_plink_maint_pid
|
|
+ || au_plink_maint(sb, AuLock_NOPLM))) {
|
|
+ switch (mode & S_IFMT) {
|
|
+ case S_IFREG:
|
|
+ /* no condition about RLIMIT_FSIZE and the file size */
|
|
+ do_sio = 1;
|
|
+ break;
|
|
+ case S_IFCHR:
|
|
+ case S_IFBLK:
|
|
+ do_sio = !capable(CAP_MKNOD);
|
|
+ break;
|
|
+ }
|
|
+ if (!do_sio)
|
|
+ do_sio = ((mode & (S_ISUID | S_ISGID))
|
|
+ && !capable(CAP_FSETID));
|
|
+ /* this workaround may be removed in the future */
|
|
+ if (!do_sio) {
|
|
+ h_dir = au_pinned_h_dir(pin);
|
|
+ do_sio = h_dir->i_mode & S_ISVTX;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return do_sio;
|
|
+}
|
|
+
|
|
+#if 0 /* reserved */
|
|
+int au_sio_cpup_single(struct au_cp_generic *cpg, struct dentry *dst_parent)
|
|
+{
|
|
+ int err, wkq_err;
|
|
+ struct dentry *h_dentry;
|
|
+
|
|
+ h_dentry = au_h_dptr(cpg->dentry, cpg->bsrc);
|
|
+ if (!au_cpup_sio_test(pin, d_inode(h_dentry)->i_mode))
|
|
+ err = au_cpup_single(cpg, dst_parent);
|
|
+ else {
|
|
+ struct au_cpup_single_args args = {
|
|
+ .errp = &err,
|
|
+ .cpg = cpg,
|
|
+ .dst_parent = dst_parent
|
|
+ };
|
|
+ wkq_err = au_wkq_wait(au_call_cpup_single, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+#endif
|
|
+
|
|
+/*
|
|
+ * copyup the @dentry from the first active lower branch to @bdst,
|
|
+ * using au_cpup_single().
|
|
+ */
|
|
+static int au_cpup_simple(struct au_cp_generic *cpg)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int flags_orig;
|
|
+ struct dentry *dentry;
|
|
+
|
|
+ AuDebugOn(cpg->bsrc < 0);
|
|
+
|
|
+ dentry = cpg->dentry;
|
|
+ DiMustWriteLock(dentry);
|
|
+
|
|
+ err = au_lkup_neg(dentry, cpg->bdst, /*wh*/1);
|
|
+ if (!err) {
|
|
+ flags_orig = cpg->flags;
|
|
+ au_fset_cpup(cpg->flags, RENAME);
|
|
+ err = au_cpup_single(cpg, NULL);
|
|
+ cpg->flags = flags_orig;
|
|
+ if (!err)
|
|
+ return 0; /* success */
|
|
+
|
|
+ /* revert */
|
|
+ au_set_h_dptr(dentry, cpg->bdst, NULL);
|
|
+ au_set_dbtop(dentry, cpg->bsrc);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct au_cpup_simple_args {
|
|
+ int *errp;
|
|
+ struct au_cp_generic *cpg;
|
|
+};
|
|
+
|
|
+static void au_call_cpup_simple(void *args)
|
|
+{
|
|
+ struct au_cpup_simple_args *a = args;
|
|
+
|
|
+ au_pin_hdir_acquire_nest(a->cpg->pin);
|
|
+ *a->errp = au_cpup_simple(a->cpg);
|
|
+ au_pin_hdir_release(a->cpg->pin);
|
|
+}
|
|
+
|
|
+static int au_do_sio_cpup_simple(struct au_cp_generic *cpg)
|
|
+{
|
|
+ int err, wkq_err;
|
|
+ struct dentry *dentry, *parent;
|
|
+ struct file *h_file;
|
|
+ struct inode *h_dir;
|
|
+
|
|
+ dentry = cpg->dentry;
|
|
+ h_file = NULL;
|
|
+ if (au_ftest_cpup(cpg->flags, HOPEN)) {
|
|
+ AuDebugOn(cpg->bsrc < 0);
|
|
+ h_file = au_h_open_pre(dentry, cpg->bsrc, /*force_wr*/0);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ parent = dget_parent(dentry);
|
|
+ h_dir = au_h_iptr(d_inode(parent), cpg->bdst);
|
|
+ if (!au_test_h_perm_sio(h_dir, MAY_EXEC | MAY_WRITE)
|
|
+ && !au_cpup_sio_test(cpg->pin, d_inode(dentry)->i_mode))
|
|
+ err = au_cpup_simple(cpg);
|
|
+ else {
|
|
+ struct au_cpup_simple_args args = {
|
|
+ .errp = &err,
|
|
+ .cpg = cpg
|
|
+ };
|
|
+ wkq_err = au_wkq_wait(au_call_cpup_simple, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+ }
|
|
+
|
|
+ dput(parent);
|
|
+ if (h_file)
|
|
+ au_h_open_post(dentry, cpg->bsrc, h_file);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_sio_cpup_simple(struct au_cp_generic *cpg)
|
|
+{
|
|
+ aufs_bindex_t bsrc, bbot;
|
|
+ struct dentry *dentry, *h_dentry;
|
|
+
|
|
+ if (cpg->bsrc < 0) {
|
|
+ dentry = cpg->dentry;
|
|
+ bbot = au_dbbot(dentry);
|
|
+ for (bsrc = cpg->bdst + 1; bsrc <= bbot; bsrc++) {
|
|
+ h_dentry = au_h_dptr(dentry, bsrc);
|
|
+ if (h_dentry) {
|
|
+ AuDebugOn(d_is_negative(h_dentry));
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ AuDebugOn(bsrc > bbot);
|
|
+ cpg->bsrc = bsrc;
|
|
+ }
|
|
+ AuDebugOn(cpg->bsrc <= cpg->bdst);
|
|
+ return au_do_sio_cpup_simple(cpg);
|
|
+}
|
|
+
|
|
+int au_sio_cpdown_simple(struct au_cp_generic *cpg)
|
|
+{
|
|
+ AuDebugOn(cpg->bdst <= cpg->bsrc);
|
|
+ return au_do_sio_cpup_simple(cpg);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * copyup the deleted file for writing.
|
|
+ */
|
|
+static int au_do_cpup_wh(struct au_cp_generic *cpg, struct dentry *wh_dentry,
|
|
+ struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int flags_orig;
|
|
+ aufs_bindex_t bsrc_orig;
|
|
+ struct au_dinfo *dinfo;
|
|
+ struct {
|
|
+ struct au_hdentry *hd;
|
|
+ struct dentry *h_dentry;
|
|
+ } hdst, hsrc;
|
|
+
|
|
+ dinfo = au_di(cpg->dentry);
|
|
+ AuRwMustWriteLock(&dinfo->di_rwsem);
|
|
+
|
|
+ bsrc_orig = cpg->bsrc;
|
|
+ cpg->bsrc = dinfo->di_btop;
|
|
+ hdst.hd = au_hdentry(dinfo, cpg->bdst);
|
|
+ hdst.h_dentry = hdst.hd->hd_dentry;
|
|
+ hdst.hd->hd_dentry = wh_dentry;
|
|
+ dinfo->di_btop = cpg->bdst;
|
|
+
|
|
+ hsrc.h_dentry = NULL;
|
|
+ if (file) {
|
|
+ hsrc.hd = au_hdentry(dinfo, cpg->bsrc);
|
|
+ hsrc.h_dentry = hsrc.hd->hd_dentry;
|
|
+ hsrc.hd->hd_dentry = au_hf_top(file)->f_path.dentry;
|
|
+ }
|
|
+ flags_orig = cpg->flags;
|
|
+ cpg->flags = !AuCpup_DTIME;
|
|
+ err = au_cpup_single(cpg, /*h_parent*/NULL);
|
|
+ cpg->flags = flags_orig;
|
|
+ if (file) {
|
|
+ if (!err)
|
|
+ err = au_reopen_nondir(file);
|
|
+ hsrc.hd->hd_dentry = hsrc.h_dentry;
|
|
+ }
|
|
+ hdst.hd->hd_dentry = hdst.h_dentry;
|
|
+ dinfo->di_btop = cpg->bsrc;
|
|
+ cpg->bsrc = bsrc_orig;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_cpup_wh(struct au_cp_generic *cpg, struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bdst;
|
|
+ struct au_dtime dt;
|
|
+ struct dentry *dentry, *parent, *h_parent, *wh_dentry;
|
|
+ struct au_branch *br;
|
|
+ struct path h_path;
|
|
+
|
|
+ dentry = cpg->dentry;
|
|
+ bdst = cpg->bdst;
|
|
+ br = au_sbr(dentry->d_sb, bdst);
|
|
+ parent = dget_parent(dentry);
|
|
+ h_parent = au_h_dptr(parent, bdst);
|
|
+ wh_dentry = au_whtmp_lkup(h_parent, br, &dentry->d_name);
|
|
+ err = PTR_ERR(wh_dentry);
|
|
+ if (IS_ERR(wh_dentry))
|
|
+ goto out;
|
|
+
|
|
+ h_path.dentry = h_parent;
|
|
+ h_path.mnt = au_br_mnt(br);
|
|
+ au_dtime_store(&dt, parent, &h_path);
|
|
+ err = au_do_cpup_wh(cpg, wh_dentry, file);
|
|
+ if (unlikely(err))
|
|
+ goto out_wh;
|
|
+
|
|
+ dget(wh_dentry);
|
|
+ h_path.dentry = wh_dentry;
|
|
+ if (!d_is_dir(wh_dentry)) {
|
|
+ /* no delegation since it is just created */
|
|
+ err = vfsub_unlink(d_inode(h_parent), &h_path,
|
|
+ /*delegated*/NULL, /*force*/0);
|
|
+ } else
|
|
+ err = vfsub_rmdir(d_inode(h_parent), &h_path);
|
|
+ if (unlikely(err)) {
|
|
+ AuIOErr("failed remove copied-up tmp file %pd(%d)\n",
|
|
+ wh_dentry, err);
|
|
+ err = -EIO;
|
|
+ }
|
|
+ au_dtime_revert(&dt);
|
|
+ au_set_hi_wh(d_inode(dentry), bdst, wh_dentry);
|
|
+
|
|
+out_wh:
|
|
+ dput(wh_dentry);
|
|
+out:
|
|
+ dput(parent);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct au_cpup_wh_args {
|
|
+ int *errp;
|
|
+ struct au_cp_generic *cpg;
|
|
+ struct file *file;
|
|
+};
|
|
+
|
|
+static void au_call_cpup_wh(void *args)
|
|
+{
|
|
+ struct au_cpup_wh_args *a = args;
|
|
+
|
|
+ au_pin_hdir_acquire_nest(a->cpg->pin);
|
|
+ *a->errp = au_cpup_wh(a->cpg, a->file);
|
|
+ au_pin_hdir_release(a->cpg->pin);
|
|
+}
|
|
+
|
|
+int au_sio_cpup_wh(struct au_cp_generic *cpg, struct file *file)
|
|
+{
|
|
+ int err, wkq_err;
|
|
+ aufs_bindex_t bdst;
|
|
+ struct dentry *dentry, *parent, *h_orph, *h_parent;
|
|
+ struct inode *dir, *h_dir, *h_tmpdir;
|
|
+ struct au_wbr *wbr;
|
|
+ struct au_pin wh_pin, *pin_orig;
|
|
+
|
|
+ dentry = cpg->dentry;
|
|
+ bdst = cpg->bdst;
|
|
+ parent = dget_parent(dentry);
|
|
+ dir = d_inode(parent);
|
|
+ h_orph = NULL;
|
|
+ h_parent = NULL;
|
|
+ h_dir = au_igrab(au_h_iptr(dir, bdst));
|
|
+ h_tmpdir = h_dir;
|
|
+ pin_orig = NULL;
|
|
+ if (!h_dir->i_nlink) {
|
|
+ wbr = au_sbr(dentry->d_sb, bdst)->br_wbr;
|
|
+ h_orph = wbr->wbr_orph;
|
|
+
|
|
+ h_parent = dget(au_h_dptr(parent, bdst));
|
|
+ au_set_h_dptr(parent, bdst, dget(h_orph));
|
|
+ h_tmpdir = d_inode(h_orph);
|
|
+ au_set_h_iptr(dir, bdst, au_igrab(h_tmpdir), /*flags*/0);
|
|
+
|
|
+ inode_lock_nested(h_tmpdir, AuLsc_I_PARENT3);
|
|
+ /* todo: au_h_open_pre()? */
|
|
+
|
|
+ pin_orig = cpg->pin;
|
|
+ au_pin_init(&wh_pin, dentry, bdst, AuLsc_DI_PARENT,
|
|
+ AuLsc_I_PARENT3, cpg->pin->udba, AuPin_DI_LOCKED);
|
|
+ cpg->pin = &wh_pin;
|
|
+ }
|
|
+
|
|
+ if (!au_test_h_perm_sio(h_tmpdir, MAY_EXEC | MAY_WRITE)
|
|
+ && !au_cpup_sio_test(cpg->pin, d_inode(dentry)->i_mode))
|
|
+ err = au_cpup_wh(cpg, file);
|
|
+ else {
|
|
+ struct au_cpup_wh_args args = {
|
|
+ .errp = &err,
|
|
+ .cpg = cpg,
|
|
+ .file = file
|
|
+ };
|
|
+ wkq_err = au_wkq_wait(au_call_cpup_wh, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+ }
|
|
+
|
|
+ if (h_orph) {
|
|
+ inode_unlock(h_tmpdir);
|
|
+ /* todo: au_h_open_post()? */
|
|
+ au_set_h_iptr(dir, bdst, au_igrab(h_dir), /*flags*/0);
|
|
+ au_set_h_dptr(parent, bdst, h_parent);
|
|
+ AuDebugOn(!pin_orig);
|
|
+ cpg->pin = pin_orig;
|
|
+ }
|
|
+ iput(h_dir);
|
|
+ dput(parent);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * generic routine for both of copy-up and copy-down.
|
|
+ */
|
|
+/* cf. revalidate function in file.c */
|
|
+int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst,
|
|
+ int (*cp)(struct dentry *dentry, aufs_bindex_t bdst,
|
|
+ struct au_pin *pin,
|
|
+ struct dentry *h_parent, void *arg),
|
|
+ void *arg)
|
|
+{
|
|
+ int err;
|
|
+ struct au_pin pin;
|
|
+ struct dentry *d, *parent, *h_parent, *real_parent, *h_dentry;
|
|
+
|
|
+ err = 0;
|
|
+ parent = dget_parent(dentry);
|
|
+ if (IS_ROOT(parent))
|
|
+ goto out;
|
|
+
|
|
+ au_pin_init(&pin, dentry, bdst, AuLsc_DI_PARENT2, AuLsc_I_PARENT2,
|
|
+ au_opt_udba(dentry->d_sb), AuPin_MNT_WRITE);
|
|
+
|
|
+ /* do not use au_dpage */
|
|
+ real_parent = parent;
|
|
+ while (1) {
|
|
+ dput(parent);
|
|
+ parent = dget_parent(dentry);
|
|
+ h_parent = au_h_dptr(parent, bdst);
|
|
+ if (h_parent)
|
|
+ goto out; /* success */
|
|
+
|
|
+ /* find top dir which is necessary to cpup */
|
|
+ do {
|
|
+ d = parent;
|
|
+ dput(parent);
|
|
+ parent = dget_parent(d);
|
|
+ di_read_lock_parent3(parent, !AuLock_IR);
|
|
+ h_parent = au_h_dptr(parent, bdst);
|
|
+ di_read_unlock(parent, !AuLock_IR);
|
|
+ } while (!h_parent);
|
|
+
|
|
+ if (d != real_parent)
|
|
+ di_write_lock_child3(d);
|
|
+
|
|
+ /* somebody else might create while we were sleeping */
|
|
+ h_dentry = au_h_dptr(d, bdst);
|
|
+ if (!h_dentry || d_is_negative(h_dentry)) {
|
|
+ if (h_dentry)
|
|
+ au_update_dbtop(d);
|
|
+
|
|
+ au_pin_set_dentry(&pin, d);
|
|
+ err = au_do_pin(&pin);
|
|
+ if (!err) {
|
|
+ err = cp(d, bdst, &pin, h_parent, arg);
|
|
+ au_unpin(&pin);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (d != real_parent)
|
|
+ di_write_unlock(d);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ dput(parent);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_cpup_dir(struct dentry *dentry, aufs_bindex_t bdst,
|
|
+ struct au_pin *pin,
|
|
+ struct dentry *h_parent __maybe_unused,
|
|
+ void *arg __maybe_unused)
|
|
+{
|
|
+ struct au_cp_generic cpg = {
|
|
+ .dentry = dentry,
|
|
+ .bdst = bdst,
|
|
+ .bsrc = -1,
|
|
+ .len = 0,
|
|
+ .pin = pin,
|
|
+ .flags = AuCpup_DTIME
|
|
+ };
|
|
+ return au_sio_cpup_simple(&cpg);
|
|
+}
|
|
+
|
|
+int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst)
|
|
+{
|
|
+ return au_cp_dirs(dentry, bdst, au_cpup_dir, NULL);
|
|
+}
|
|
+
|
|
+int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *parent;
|
|
+ struct inode *dir;
|
|
+
|
|
+ parent = dget_parent(dentry);
|
|
+ dir = d_inode(parent);
|
|
+ err = 0;
|
|
+ if (au_h_iptr(dir, bdst))
|
|
+ goto out;
|
|
+
|
|
+ di_read_unlock(parent, AuLock_IR);
|
|
+ di_write_lock_parent(parent);
|
|
+ /* someone else might change our inode while we were sleeping */
|
|
+ if (!au_h_iptr(dir, bdst))
|
|
+ err = au_cpup_dirs(dentry, bdst);
|
|
+ di_downgrade_lock(parent, AuLock_IR);
|
|
+
|
|
+out:
|
|
+ dput(parent);
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/cpup.h b/fs/aufs/cpup.h
|
|
new file mode 100644
|
|
index 000000000000..d02f8150fa05
|
|
--- /dev/null
|
|
+++ b/fs/aufs/cpup.h
|
|
@@ -0,0 +1,100 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * copy-up/down functions
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_CPUP_H__
|
|
+#define __AUFS_CPUP_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/path.h>
|
|
+
|
|
+struct inode;
|
|
+struct file;
|
|
+struct au_pin;
|
|
+
|
|
+void au_cpup_attr_flags(struct inode *dst, unsigned int iflags);
|
|
+void au_cpup_attr_timesizes(struct inode *inode);
|
|
+void au_cpup_attr_nlink(struct inode *inode, int force);
|
|
+void au_cpup_attr_changeable(struct inode *inode);
|
|
+void au_cpup_igen(struct inode *inode, struct inode *h_inode);
|
|
+void au_cpup_attr_all(struct inode *inode, int force);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_cp_generic {
|
|
+ struct dentry *dentry;
|
|
+ aufs_bindex_t bdst, bsrc;
|
|
+ loff_t len;
|
|
+ struct au_pin *pin;
|
|
+ unsigned int flags;
|
|
+};
|
|
+
|
|
+/* cpup flags */
|
|
+#define AuCpup_DTIME 1 /* do dtime_store/revert */
|
|
+#define AuCpup_KEEPLINO (1 << 1) /* do not clear the lower xino,
|
|
+ for link(2) */
|
|
+#define AuCpup_RENAME (1 << 2) /* rename after cpup */
|
|
+#define AuCpup_HOPEN (1 << 3) /* call h_open_pre/post() in
|
|
+ cpup */
|
|
+#define AuCpup_OVERWRITE (1 << 4) /* allow overwriting the
|
|
+ existing entry */
|
|
+#define AuCpup_RWDST (1 << 5) /* force write target even if
|
|
+ the branch is marked as RO */
|
|
+
|
|
+#ifndef CONFIG_AUFS_BR_HFSPLUS
|
|
+#undef AuCpup_HOPEN
|
|
+#define AuCpup_HOPEN 0
|
|
+#endif
|
|
+
|
|
+#define au_ftest_cpup(flags, name) ((flags) & AuCpup_##name)
|
|
+#define au_fset_cpup(flags, name) \
|
|
+ do { (flags) |= AuCpup_##name; } while (0)
|
|
+#define au_fclr_cpup(flags, name) \
|
|
+ do { (flags) &= ~AuCpup_##name; } while (0)
|
|
+
|
|
+int au_copy_file(struct file *dst, struct file *src, loff_t len);
|
|
+int au_sio_cpup_simple(struct au_cp_generic *cpg);
|
|
+int au_sio_cpdown_simple(struct au_cp_generic *cpg);
|
|
+int au_sio_cpup_wh(struct au_cp_generic *cpg, struct file *file);
|
|
+
|
|
+int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst,
|
|
+ int (*cp)(struct dentry *dentry, aufs_bindex_t bdst,
|
|
+ struct au_pin *pin,
|
|
+ struct dentry *h_parent, void *arg),
|
|
+ void *arg);
|
|
+int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst);
|
|
+int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* keep timestamps when copyup */
|
|
+struct au_dtime {
|
|
+ struct dentry *dt_dentry;
|
|
+ struct path dt_h_path;
|
|
+ struct timespec64 dt_atime, dt_mtime;
|
|
+};
|
|
+void au_dtime_store(struct au_dtime *dt, struct dentry *dentry,
|
|
+ struct path *h_path);
|
|
+void au_dtime_revert(struct au_dtime *dt);
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_CPUP_H__ */
|
|
diff --git a/fs/aufs/dbgaufs.c b/fs/aufs/dbgaufs.c
|
|
new file mode 100644
|
|
index 000000000000..80266f4fcf7d
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dbgaufs.c
|
|
@@ -0,0 +1,526 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * debugfs interface
|
|
+ */
|
|
+
|
|
+#include <linux/debugfs.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+#ifndef CONFIG_SYSFS
|
|
+#error DEBUG_FS depends upon SYSFS
|
|
+#endif
|
|
+
|
|
+static struct dentry *dbgaufs;
|
|
+static const mode_t dbgaufs_mode = 0444;
|
|
+
|
|
+/* 20 is max digits length of ulong 64 */
|
|
+struct dbgaufs_arg {
|
|
+ int n;
|
|
+ char a[20 * 4];
|
|
+};
|
|
+
|
|
+/*
|
|
+ * common function for all XINO files
|
|
+ */
|
|
+static int dbgaufs_xi_release(struct inode *inode __maybe_unused,
|
|
+ struct file *file)
|
|
+{
|
|
+ void *p;
|
|
+
|
|
+ p = file->private_data;
|
|
+ if (p) {
|
|
+ /* this is struct dbgaufs_arg */
|
|
+ AuDebugOn(!au_kfree_sz_test(p));
|
|
+ au_kfree_do_rcu(p);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dbgaufs_xi_open(struct file *xf, struct file *file, int do_fcnt,
|
|
+ int cnt)
|
|
+{
|
|
+ int err;
|
|
+ struct kstat st;
|
|
+ struct dbgaufs_arg *p;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ p = kmalloc(sizeof(*p), GFP_NOFS);
|
|
+ if (unlikely(!p))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ p->n = 0;
|
|
+ file->private_data = p;
|
|
+ if (!xf)
|
|
+ goto out;
|
|
+
|
|
+ err = vfsub_getattr(&xf->f_path, &st);
|
|
+ if (!err) {
|
|
+ if (do_fcnt)
|
|
+ p->n = snprintf
|
|
+ (p->a, sizeof(p->a), "%d, %llux%u %lld\n",
|
|
+ cnt, st.blocks, st.blksize,
|
|
+ (long long)st.size);
|
|
+ else
|
|
+ p->n = snprintf(p->a, sizeof(p->a), "%llux%u %lld\n",
|
|
+ st.blocks, st.blksize,
|
|
+ (long long)st.size);
|
|
+ AuDebugOn(p->n >= sizeof(p->a));
|
|
+ } else {
|
|
+ p->n = snprintf(p->a, sizeof(p->a), "err %d\n", err);
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t dbgaufs_xi_read(struct file *file, char __user *buf,
|
|
+ size_t count, loff_t *ppos)
|
|
+{
|
|
+ struct dbgaufs_arg *p;
|
|
+
|
|
+ p = file->private_data;
|
|
+ return simple_read_from_buffer(buf, count, ppos, p->a, p->n);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct dbgaufs_plink_arg {
|
|
+ int n;
|
|
+ char a[];
|
|
+};
|
|
+
|
|
+static int dbgaufs_plink_release(struct inode *inode __maybe_unused,
|
|
+ struct file *file)
|
|
+{
|
|
+ free_page((unsigned long)file->private_data);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dbgaufs_plink_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ int err, i, limit;
|
|
+ unsigned long n, sum;
|
|
+ struct dbgaufs_plink_arg *p;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct super_block *sb;
|
|
+ struct hlist_bl_head *hbl;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ p = (void *)get_zeroed_page(GFP_NOFS);
|
|
+ if (unlikely(!p))
|
|
+ goto out;
|
|
+
|
|
+ err = -EFBIG;
|
|
+ sbinfo = inode->i_private;
|
|
+ sb = sbinfo->si_sb;
|
|
+ si_noflush_read_lock(sb);
|
|
+ if (au_opt_test(au_mntflags(sb), PLINK)) {
|
|
+ limit = PAGE_SIZE - sizeof(p->n);
|
|
+
|
|
+ /* the number of buckets */
|
|
+ n = snprintf(p->a + p->n, limit, "%d\n", AuPlink_NHASH);
|
|
+ p->n += n;
|
|
+ limit -= n;
|
|
+
|
|
+ sum = 0;
|
|
+ for (i = 0, hbl = sbinfo->si_plink; i < AuPlink_NHASH;
|
|
+ i++, hbl++) {
|
|
+ n = au_hbl_count(hbl);
|
|
+ sum += n;
|
|
+
|
|
+ n = snprintf(p->a + p->n, limit, "%lu ", n);
|
|
+ p->n += n;
|
|
+ limit -= n;
|
|
+ if (unlikely(limit <= 0))
|
|
+ goto out_free;
|
|
+ }
|
|
+ p->a[p->n - 1] = '\n';
|
|
+
|
|
+ /* the sum of plinks */
|
|
+ n = snprintf(p->a + p->n, limit, "%lu\n", sum);
|
|
+ p->n += n;
|
|
+ limit -= n;
|
|
+ if (unlikely(limit <= 0))
|
|
+ goto out_free;
|
|
+ } else {
|
|
+#define str "1\n0\n0\n"
|
|
+ p->n = sizeof(str) - 1;
|
|
+ strcpy(p->a, str);
|
|
+#undef str
|
|
+ }
|
|
+ si_read_unlock(sb);
|
|
+
|
|
+ err = 0;
|
|
+ file->private_data = p;
|
|
+ goto out; /* success */
|
|
+
|
|
+out_free:
|
|
+ free_page((unsigned long)p);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t dbgaufs_plink_read(struct file *file, char __user *buf,
|
|
+ size_t count, loff_t *ppos)
|
|
+{
|
|
+ struct dbgaufs_plink_arg *p;
|
|
+
|
|
+ p = file->private_data;
|
|
+ return simple_read_from_buffer(buf, count, ppos, p->a, p->n);
|
|
+}
|
|
+
|
|
+static const struct file_operations dbgaufs_plink_fop = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = dbgaufs_plink_open,
|
|
+ .release = dbgaufs_plink_release,
|
|
+ .read = dbgaufs_plink_read
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int dbgaufs_xib_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ sbinfo = inode->i_private;
|
|
+ sb = sbinfo->si_sb;
|
|
+ si_noflush_read_lock(sb);
|
|
+ err = dbgaufs_xi_open(sbinfo->si_xib, file, /*do_fcnt*/0, /*cnt*/0);
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct file_operations dbgaufs_xib_fop = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = dbgaufs_xib_open,
|
|
+ .release = dbgaufs_xi_release,
|
|
+ .read = dbgaufs_xi_read
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#define DbgaufsXi_PREFIX "xi"
|
|
+
|
|
+static int dbgaufs_xino_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ int err, idx;
|
|
+ long l;
|
|
+ aufs_bindex_t bindex;
|
|
+ char *p, a[sizeof(DbgaufsXi_PREFIX) + 8];
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct super_block *sb;
|
|
+ struct au_xino *xi;
|
|
+ struct file *xf;
|
|
+ struct qstr *name;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ err = -ENOENT;
|
|
+ name = &file->f_path.dentry->d_name;
|
|
+ if (unlikely(name->len < sizeof(DbgaufsXi_PREFIX)
|
|
+ || memcmp(name->name, DbgaufsXi_PREFIX,
|
|
+ sizeof(DbgaufsXi_PREFIX) - 1)))
|
|
+ goto out;
|
|
+
|
|
+ AuDebugOn(name->len >= sizeof(a));
|
|
+ memcpy(a, name->name, name->len);
|
|
+ a[name->len] = '\0';
|
|
+ p = strchr(a, '-');
|
|
+ if (p)
|
|
+ *p = '\0';
|
|
+ err = kstrtol(a + sizeof(DbgaufsXi_PREFIX) - 1, 10, &l);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ bindex = l;
|
|
+ idx = 0;
|
|
+ if (p) {
|
|
+ err = kstrtol(p + 1, 10, &l);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ idx = l;
|
|
+ }
|
|
+
|
|
+ err = -ENOENT;
|
|
+ sbinfo = inode->i_private;
|
|
+ sb = sbinfo->si_sb;
|
|
+ si_noflush_read_lock(sb);
|
|
+ if (unlikely(bindex < 0 || bindex > au_sbbot(sb)))
|
|
+ goto out_si;
|
|
+ br = au_sbr(sb, bindex);
|
|
+ xi = br->br_xino;
|
|
+ if (unlikely(idx >= xi->xi_nfile))
|
|
+ goto out_si;
|
|
+ xf = au_xino_file(xi, idx);
|
|
+ if (xf)
|
|
+ err = dbgaufs_xi_open(xf, file, /*do_fcnt*/1,
|
|
+ au_xino_count(br));
|
|
+
|
|
+out_si:
|
|
+ si_read_unlock(sb);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct file_operations dbgaufs_xino_fop = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = dbgaufs_xino_open,
|
|
+ .release = dbgaufs_xi_release,
|
|
+ .read = dbgaufs_xi_read
|
|
+};
|
|
+
|
|
+void dbgaufs_xino_del(struct au_branch *br)
|
|
+{
|
|
+ struct dentry *dbgaufs;
|
|
+
|
|
+ dbgaufs = br->br_dbgaufs;
|
|
+ if (!dbgaufs)
|
|
+ return;
|
|
+
|
|
+ br->br_dbgaufs = NULL;
|
|
+ /* debugfs acquires the parent i_mutex */
|
|
+ lockdep_off();
|
|
+ debugfs_remove(dbgaufs);
|
|
+ lockdep_on();
|
|
+}
|
|
+
|
|
+void dbgaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ aufs_bindex_t bbot;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ if (!au_sbi(sb)->si_dbgaufs)
|
|
+ return;
|
|
+
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ dbgaufs_xino_del(br);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void dbgaufs_br_do_add(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ unsigned int idx, struct dentry *parent,
|
|
+ struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ struct au_branch *br;
|
|
+ struct dentry *d;
|
|
+ /* "xi" bindex(5) "-" idx(2) NULL */
|
|
+ char name[sizeof(DbgaufsXi_PREFIX) + 8];
|
|
+
|
|
+ if (!idx)
|
|
+ snprintf(name, sizeof(name), DbgaufsXi_PREFIX "%d", bindex);
|
|
+ else
|
|
+ snprintf(name, sizeof(name), DbgaufsXi_PREFIX "%d-%u",
|
|
+ bindex, idx);
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (br->br_dbgaufs) {
|
|
+ struct qstr qstr = QSTR_INIT(name, strlen(name));
|
|
+
|
|
+ if (!au_qstreq(&br->br_dbgaufs->d_name, &qstr)) {
|
|
+ /* debugfs acquires the parent i_mutex */
|
|
+ lockdep_off();
|
|
+ d = debugfs_rename(parent, br->br_dbgaufs, parent,
|
|
+ name);
|
|
+ lockdep_on();
|
|
+ if (unlikely(!d))
|
|
+ pr_warn("failed renaming %pd/%s, ignored.\n",
|
|
+ parent, name);
|
|
+ }
|
|
+ } else {
|
|
+ lockdep_off();
|
|
+ br->br_dbgaufs = debugfs_create_file(name, dbgaufs_mode, parent,
|
|
+ sbinfo, &dbgaufs_xino_fop);
|
|
+ lockdep_on();
|
|
+ if (unlikely(!br->br_dbgaufs))
|
|
+ pr_warn("failed creating %pd/%s, ignored.\n",
|
|
+ parent, name);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void dbgaufs_br_add(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ struct dentry *parent, struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ struct au_branch *br;
|
|
+ struct au_xino *xi;
|
|
+ unsigned int u;
|
|
+
|
|
+ br = au_sbr(sb, bindex);
|
|
+ xi = br->br_xino;
|
|
+ for (u = 0; u < xi->xi_nfile; u++)
|
|
+ dbgaufs_br_do_add(sb, bindex, u, parent, sbinfo);
|
|
+}
|
|
+
|
|
+void dbgaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex, int topdown)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct dentry *parent;
|
|
+ aufs_bindex_t bbot;
|
|
+
|
|
+ if (!au_opt_test(au_mntflags(sb), XINO))
|
|
+ return;
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ parent = sbinfo->si_dbgaufs;
|
|
+ if (!parent)
|
|
+ return;
|
|
+
|
|
+ bbot = au_sbbot(sb);
|
|
+ if (topdown)
|
|
+ for (; bindex <= bbot; bindex++)
|
|
+ dbgaufs_br_add(sb, bindex, parent, sbinfo);
|
|
+ else
|
|
+ for (; bbot >= bindex; bbot--)
|
|
+ dbgaufs_br_add(sb, bbot, parent, sbinfo);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_AUFS_EXPORT
|
|
+static int dbgaufs_xigen_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ sbinfo = inode->i_private;
|
|
+ sb = sbinfo->si_sb;
|
|
+ si_noflush_read_lock(sb);
|
|
+ err = dbgaufs_xi_open(sbinfo->si_xigen, file, /*do_fcnt*/0, /*cnt*/0);
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct file_operations dbgaufs_xigen_fop = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = dbgaufs_xigen_open,
|
|
+ .release = dbgaufs_xi_release,
|
|
+ .read = dbgaufs_xi_read
|
|
+};
|
|
+
|
|
+static int dbgaufs_xigen_init(struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ /*
|
|
+ * This function is a dynamic '__init' function actually,
|
|
+ * so the tiny check for si_rwsem is unnecessary.
|
|
+ */
|
|
+ /* AuRwMustWriteLock(&sbinfo->si_rwsem); */
|
|
+
|
|
+ err = -EIO;
|
|
+ sbinfo->si_dbgaufs_xigen = debugfs_create_file
|
|
+ ("xigen", dbgaufs_mode, sbinfo->si_dbgaufs, sbinfo,
|
|
+ &dbgaufs_xigen_fop);
|
|
+ if (sbinfo->si_dbgaufs_xigen)
|
|
+ err = 0;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+#else
|
|
+static int dbgaufs_xigen_init(struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+#endif /* CONFIG_AUFS_EXPORT */
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void dbgaufs_si_fin(struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ /*
|
|
+ * This function is a dynamic '__fin' function actually,
|
|
+ * so the tiny check for si_rwsem is unnecessary.
|
|
+ */
|
|
+ /* AuRwMustWriteLock(&sbinfo->si_rwsem); */
|
|
+
|
|
+ debugfs_remove_recursive(sbinfo->si_dbgaufs);
|
|
+ sbinfo->si_dbgaufs = NULL;
|
|
+}
|
|
+
|
|
+int dbgaufs_si_init(struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ int err;
|
|
+ char name[SysaufsSiNameLen];
|
|
+
|
|
+ /*
|
|
+ * This function is a dynamic '__init' function actually,
|
|
+ * so the tiny check for si_rwsem is unnecessary.
|
|
+ */
|
|
+ /* AuRwMustWriteLock(&sbinfo->si_rwsem); */
|
|
+
|
|
+ err = -ENOENT;
|
|
+ if (!dbgaufs) {
|
|
+ AuErr1("/debug/aufs is uninitialized\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = -EIO;
|
|
+ sysaufs_name(sbinfo, name);
|
|
+ sbinfo->si_dbgaufs = debugfs_create_dir(name, dbgaufs);
|
|
+ if (unlikely(!sbinfo->si_dbgaufs))
|
|
+ goto out;
|
|
+
|
|
+ /* regardless plink/noplink option */
|
|
+ sbinfo->si_dbgaufs_plink = debugfs_create_file
|
|
+ ("plink", dbgaufs_mode, sbinfo->si_dbgaufs, sbinfo,
|
|
+ &dbgaufs_plink_fop);
|
|
+ if (unlikely(!sbinfo->si_dbgaufs_plink))
|
|
+ goto out_dir;
|
|
+
|
|
+ /* regardless xino/noxino option */
|
|
+ sbinfo->si_dbgaufs_xib = debugfs_create_file
|
|
+ ("xib", dbgaufs_mode, sbinfo->si_dbgaufs, sbinfo,
|
|
+ &dbgaufs_xib_fop);
|
|
+ if (unlikely(!sbinfo->si_dbgaufs_xib))
|
|
+ goto out_dir;
|
|
+
|
|
+ err = dbgaufs_xigen_init(sbinfo);
|
|
+ if (!err)
|
|
+ goto out; /* success */
|
|
+
|
|
+out_dir:
|
|
+ dbgaufs_si_fin(sbinfo);
|
|
+out:
|
|
+ if (unlikely(err))
|
|
+ pr_err("debugfs/aufs failed\n");
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void dbgaufs_fin(void)
|
|
+{
|
|
+ debugfs_remove(dbgaufs);
|
|
+}
|
|
+
|
|
+int __init dbgaufs_init(void)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = -EIO;
|
|
+ dbgaufs = debugfs_create_dir(AUFS_NAME, NULL);
|
|
+ if (dbgaufs)
|
|
+ err = 0;
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/dbgaufs.h b/fs/aufs/dbgaufs.h
|
|
new file mode 100644
|
|
index 000000000000..7b4ccdebb678
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dbgaufs.h
|
|
@@ -0,0 +1,53 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * debugfs interface
|
|
+ */
|
|
+
|
|
+#ifndef __DBGAUFS_H__
|
|
+#define __DBGAUFS_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+struct super_block;
|
|
+struct au_sbinfo;
|
|
+struct au_branch;
|
|
+
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+/* dbgaufs.c */
|
|
+void dbgaufs_xino_del(struct au_branch *br);
|
|
+void dbgaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex);
|
|
+void dbgaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex, int topdown);
|
|
+void dbgaufs_si_fin(struct au_sbinfo *sbinfo);
|
|
+int dbgaufs_si_init(struct au_sbinfo *sbinfo);
|
|
+void dbgaufs_fin(void);
|
|
+int __init dbgaufs_init(void);
|
|
+#else
|
|
+AuStubVoid(dbgaufs_xino_del, struct au_branch *br)
|
|
+AuStubVoid(dbgaufs_brs_del, struct super_block *sb, aufs_bindex_t bindex)
|
|
+AuStubVoid(dbgaufs_brs_add, struct super_block *sb, aufs_bindex_t bindex,
|
|
+ int topdown)
|
|
+AuStubVoid(dbgaufs_si_fin, struct au_sbinfo *sbinfo)
|
|
+AuStubInt0(dbgaufs_si_init, struct au_sbinfo *sbinfo)
|
|
+AuStubVoid(dbgaufs_fin, void)
|
|
+AuStubInt0(__init dbgaufs_init, void)
|
|
+#endif /* CONFIG_DEBUG_FS */
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __DBGAUFS_H__ */
|
|
diff --git a/fs/aufs/dcsub.c b/fs/aufs/dcsub.c
|
|
new file mode 100644
|
|
index 000000000000..0b9b1862b563
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dcsub.c
|
|
@@ -0,0 +1,225 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * sub-routines for dentry cache
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+static void au_dpage_free(struct au_dpage *dpage)
|
|
+{
|
|
+ int i;
|
|
+ struct dentry **p;
|
|
+
|
|
+ p = dpage->dentries;
|
|
+ for (i = 0; i < dpage->ndentry; i++)
|
|
+ dput(*p++);
|
|
+ free_page((unsigned long)dpage->dentries);
|
|
+}
|
|
+
|
|
+int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp)
|
|
+{
|
|
+ int err;
|
|
+ void *p;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ dpages->dpages = kmalloc(sizeof(*dpages->dpages), gfp);
|
|
+ if (unlikely(!dpages->dpages))
|
|
+ goto out;
|
|
+
|
|
+ p = (void *)__get_free_page(gfp);
|
|
+ if (unlikely(!p))
|
|
+ goto out_dpages;
|
|
+
|
|
+ dpages->dpages[0].ndentry = 0;
|
|
+ dpages->dpages[0].dentries = p;
|
|
+ dpages->ndpage = 1;
|
|
+ return 0; /* success */
|
|
+
|
|
+out_dpages:
|
|
+ au_kfree_try_rcu(dpages->dpages);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_dpages_free(struct au_dcsub_pages *dpages)
|
|
+{
|
|
+ int i;
|
|
+ struct au_dpage *p;
|
|
+
|
|
+ p = dpages->dpages;
|
|
+ for (i = 0; i < dpages->ndpage; i++)
|
|
+ au_dpage_free(p++);
|
|
+ au_kfree_try_rcu(dpages->dpages);
|
|
+}
|
|
+
|
|
+static int au_dpages_append(struct au_dcsub_pages *dpages,
|
|
+ struct dentry *dentry, gfp_t gfp)
|
|
+{
|
|
+ int err, sz;
|
|
+ struct au_dpage *dpage;
|
|
+ void *p;
|
|
+
|
|
+ dpage = dpages->dpages + dpages->ndpage - 1;
|
|
+ sz = PAGE_SIZE / sizeof(dentry);
|
|
+ if (unlikely(dpage->ndentry >= sz)) {
|
|
+ AuLabel(new dpage);
|
|
+ err = -ENOMEM;
|
|
+ sz = dpages->ndpage * sizeof(*dpages->dpages);
|
|
+ p = au_kzrealloc(dpages->dpages, sz,
|
|
+ sz + sizeof(*dpages->dpages), gfp,
|
|
+ /*may_shrink*/0);
|
|
+ if (unlikely(!p))
|
|
+ goto out;
|
|
+
|
|
+ dpages->dpages = p;
|
|
+ dpage = dpages->dpages + dpages->ndpage;
|
|
+ p = (void *)__get_free_page(gfp);
|
|
+ if (unlikely(!p))
|
|
+ goto out;
|
|
+
|
|
+ dpage->ndentry = 0;
|
|
+ dpage->dentries = p;
|
|
+ dpages->ndpage++;
|
|
+ }
|
|
+
|
|
+ AuDebugOn(au_dcount(dentry) <= 0);
|
|
+ dpage->dentries[dpage->ndentry++] = dget_dlock(dentry);
|
|
+ return 0; /* success */
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* todo: BAD approach */
|
|
+/* copied from linux/fs/dcache.c */
|
|
+enum d_walk_ret {
|
|
+ D_WALK_CONTINUE,
|
|
+ D_WALK_QUIT,
|
|
+ D_WALK_NORETRY,
|
|
+ D_WALK_SKIP,
|
|
+};
|
|
+
|
|
+extern void d_walk(struct dentry *parent, void *data,
|
|
+ enum d_walk_ret (*enter)(void *, struct dentry *));
|
|
+
|
|
+struct ac_dpages_arg {
|
|
+ int err;
|
|
+ struct au_dcsub_pages *dpages;
|
|
+ struct super_block *sb;
|
|
+ au_dpages_test test;
|
|
+ void *arg;
|
|
+};
|
|
+
|
|
+static enum d_walk_ret au_call_dpages_append(void *_arg, struct dentry *dentry)
|
|
+{
|
|
+ enum d_walk_ret ret;
|
|
+ struct ac_dpages_arg *arg = _arg;
|
|
+
|
|
+ ret = D_WALK_CONTINUE;
|
|
+ if (dentry->d_sb == arg->sb
|
|
+ && !IS_ROOT(dentry)
|
|
+ && au_dcount(dentry) > 0
|
|
+ && au_di(dentry)
|
|
+ && (!arg->test || arg->test(dentry, arg->arg))) {
|
|
+ arg->err = au_dpages_append(arg->dpages, dentry, GFP_ATOMIC);
|
|
+ if (unlikely(arg->err))
|
|
+ ret = D_WALK_QUIT;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+int au_dcsub_pages(struct au_dcsub_pages *dpages, struct dentry *root,
|
|
+ au_dpages_test test, void *arg)
|
|
+{
|
|
+ struct ac_dpages_arg args = {
|
|
+ .err = 0,
|
|
+ .dpages = dpages,
|
|
+ .sb = root->d_sb,
|
|
+ .test = test,
|
|
+ .arg = arg
|
|
+ };
|
|
+
|
|
+ d_walk(root, &args, au_call_dpages_append);
|
|
+
|
|
+ return args.err;
|
|
+}
|
|
+
|
|
+int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry,
|
|
+ int do_include, au_dpages_test test, void *arg)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ write_seqlock(&rename_lock);
|
|
+ spin_lock(&dentry->d_lock);
|
|
+ if (do_include
|
|
+ && au_dcount(dentry) > 0
|
|
+ && (!test || test(dentry, arg)))
|
|
+ err = au_dpages_append(dpages, dentry, GFP_ATOMIC);
|
|
+ spin_unlock(&dentry->d_lock);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ /*
|
|
+ * RCU for vfsmount is unnecessary since this is a traverse in a single
|
|
+ * mount
|
|
+ */
|
|
+ while (!IS_ROOT(dentry)) {
|
|
+ dentry = dentry->d_parent; /* rename_lock is locked */
|
|
+ spin_lock(&dentry->d_lock);
|
|
+ if (au_dcount(dentry) > 0
|
|
+ && (!test || test(dentry, arg)))
|
|
+ err = au_dpages_append(dpages, dentry, GFP_ATOMIC);
|
|
+ spin_unlock(&dentry->d_lock);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ write_sequnlock(&rename_lock);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static inline int au_dcsub_dpages_aufs(struct dentry *dentry, void *arg)
|
|
+{
|
|
+ return au_di(dentry) && dentry->d_sb == arg;
|
|
+}
|
|
+
|
|
+int au_dcsub_pages_rev_aufs(struct au_dcsub_pages *dpages,
|
|
+ struct dentry *dentry, int do_include)
|
|
+{
|
|
+ return au_dcsub_pages_rev(dpages, dentry, do_include,
|
|
+ au_dcsub_dpages_aufs, dentry->d_sb);
|
|
+}
|
|
+
|
|
+int au_test_subdir(struct dentry *d1, struct dentry *d2)
|
|
+{
|
|
+ struct path path[2] = {
|
|
+ {
|
|
+ .dentry = d1
|
|
+ },
|
|
+ {
|
|
+ .dentry = d2
|
|
+ }
|
|
+ };
|
|
+
|
|
+ return path_is_under(path + 0, path + 1);
|
|
+}
|
|
diff --git a/fs/aufs/dcsub.h b/fs/aufs/dcsub.h
|
|
new file mode 100644
|
|
index 000000000000..36f7fcdd7f23
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dcsub.h
|
|
@@ -0,0 +1,137 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * sub-routines for dentry cache
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_DCSUB_H__
|
|
+#define __AUFS_DCSUB_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/dcache.h>
|
|
+#include <linux/fs.h>
|
|
+
|
|
+struct au_dpage {
|
|
+ int ndentry;
|
|
+ struct dentry **dentries;
|
|
+};
|
|
+
|
|
+struct au_dcsub_pages {
|
|
+ int ndpage;
|
|
+ struct au_dpage *dpages;
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* dcsub.c */
|
|
+int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp);
|
|
+void au_dpages_free(struct au_dcsub_pages *dpages);
|
|
+typedef int (*au_dpages_test)(struct dentry *dentry, void *arg);
|
|
+int au_dcsub_pages(struct au_dcsub_pages *dpages, struct dentry *root,
|
|
+ au_dpages_test test, void *arg);
|
|
+int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry,
|
|
+ int do_include, au_dpages_test test, void *arg);
|
|
+int au_dcsub_pages_rev_aufs(struct au_dcsub_pages *dpages,
|
|
+ struct dentry *dentry, int do_include);
|
|
+int au_test_subdir(struct dentry *d1, struct dentry *d2);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * todo: in linux-3.13, several similar (but faster) helpers are added to
|
|
+ * include/linux/dcache.h. Try them (in the future).
|
|
+ */
|
|
+
|
|
+static inline int au_d_hashed_positive(struct dentry *d)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *inode = d_inode(d);
|
|
+
|
|
+ err = 0;
|
|
+ if (unlikely(d_unhashed(d)
|
|
+ || d_is_negative(d)
|
|
+ || !inode->i_nlink))
|
|
+ err = -ENOENT;
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static inline int au_d_linkable(struct dentry *d)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *inode = d_inode(d);
|
|
+
|
|
+ err = au_d_hashed_positive(d);
|
|
+ if (err
|
|
+ && d_is_positive(d)
|
|
+ && (inode->i_state & I_LINKABLE))
|
|
+ err = 0;
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static inline int au_d_alive(struct dentry *d)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *inode;
|
|
+
|
|
+ err = 0;
|
|
+ if (!IS_ROOT(d))
|
|
+ err = au_d_hashed_positive(d);
|
|
+ else {
|
|
+ inode = d_inode(d);
|
|
+ if (unlikely(d_unlinked(d)
|
|
+ || d_is_negative(d)
|
|
+ || !inode->i_nlink))
|
|
+ err = -ENOENT;
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static inline int au_alive_dir(struct dentry *d)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = au_d_alive(d);
|
|
+ if (unlikely(err || IS_DEADDIR(d_inode(d))))
|
|
+ err = -ENOENT;
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static inline int au_qstreq(struct qstr *a, struct qstr *b)
|
|
+{
|
|
+ return a->len == b->len
|
|
+ && !memcmp(a->name, b->name, a->len);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * by the commit
|
|
+ * 360f547 2015-01-25 dcache: let the dentry count go down to zero without
|
|
+ * taking d_lock
|
|
+ * the type of d_lockref.count became int, but the inlined function d_count()
|
|
+ * still returns unsigned int.
|
|
+ * I don't know why. Maybe it is for every d_count() users?
|
|
+ * Anyway au_dcount() lives on.
|
|
+ */
|
|
+static inline int au_dcount(struct dentry *d)
|
|
+{
|
|
+ return (int)d_count(d);
|
|
+}
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_DCSUB_H__ */
|
|
diff --git a/fs/aufs/debug.c b/fs/aufs/debug.c
|
|
new file mode 100644
|
|
index 000000000000..f0c076c61252
|
|
--- /dev/null
|
|
+++ b/fs/aufs/debug.c
|
|
@@ -0,0 +1,441 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * debug print functions
|
|
+ */
|
|
+
|
|
+#include <linux/iversion.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/* Returns 0, or -errno. arg is in kp->arg. */
|
|
+static int param_atomic_t_set(const char *val, const struct kernel_param *kp)
|
|
+{
|
|
+ int err, n;
|
|
+
|
|
+ err = kstrtoint(val, 0, &n);
|
|
+ if (!err) {
|
|
+ if (n > 0)
|
|
+ au_debug_on();
|
|
+ else
|
|
+ au_debug_off();
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* Returns length written or -errno. Buffer is 4k (ie. be short!) */
|
|
+static int param_atomic_t_get(char *buffer, const struct kernel_param *kp)
|
|
+{
|
|
+ atomic_t *a;
|
|
+
|
|
+ a = kp->arg;
|
|
+ return sprintf(buffer, "%d", atomic_read(a));
|
|
+}
|
|
+
|
|
+static struct kernel_param_ops param_ops_atomic_t = {
|
|
+ .set = param_atomic_t_set,
|
|
+ .get = param_atomic_t_get
|
|
+ /* void (*free)(void *arg) */
|
|
+};
|
|
+
|
|
+atomic_t aufs_debug = ATOMIC_INIT(0);
|
|
+MODULE_PARM_DESC(debug, "debug print");
|
|
+module_param_named(debug, aufs_debug, atomic_t, 0664);
|
|
+
|
|
+DEFINE_MUTEX(au_dbg_mtx); /* just to serialize the dbg msgs */
|
|
+char *au_plevel = KERN_DEBUG;
|
|
+#define dpri(fmt, ...) do { \
|
|
+ if ((au_plevel \
|
|
+ && strcmp(au_plevel, KERN_DEBUG)) \
|
|
+ || au_debug_test()) \
|
|
+ printk("%s" fmt, au_plevel, ##__VA_ARGS__); \
|
|
+} while (0)
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void au_dpri_whlist(struct au_nhash *whlist)
|
|
+{
|
|
+ unsigned long ul, n;
|
|
+ struct hlist_head *head;
|
|
+ struct au_vdir_wh *pos;
|
|
+
|
|
+ n = whlist->nh_num;
|
|
+ head = whlist->nh_head;
|
|
+ for (ul = 0; ul < n; ul++) {
|
|
+ hlist_for_each_entry(pos, head, wh_hash)
|
|
+ dpri("b%d, %.*s, %d\n",
|
|
+ pos->wh_bindex,
|
|
+ pos->wh_str.len, pos->wh_str.name,
|
|
+ pos->wh_str.len);
|
|
+ head++;
|
|
+ }
|
|
+}
|
|
+
|
|
+void au_dpri_vdir(struct au_vdir *vdir)
|
|
+{
|
|
+ unsigned long ul;
|
|
+ union au_vdir_deblk_p p;
|
|
+ unsigned char *o;
|
|
+
|
|
+ if (!vdir || IS_ERR(vdir)) {
|
|
+ dpri("err %ld\n", PTR_ERR(vdir));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ dpri("deblk %u, nblk %lu, deblk %p, last{%lu, %p}, ver %llu\n",
|
|
+ vdir->vd_deblk_sz, vdir->vd_nblk, vdir->vd_deblk,
|
|
+ vdir->vd_last.ul, vdir->vd_last.p.deblk, vdir->vd_version);
|
|
+ for (ul = 0; ul < vdir->vd_nblk; ul++) {
|
|
+ p.deblk = vdir->vd_deblk[ul];
|
|
+ o = p.deblk;
|
|
+ dpri("[%lu]: %p\n", ul, o);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int do_pri_inode(aufs_bindex_t bindex, struct inode *inode, int hn,
|
|
+ struct dentry *wh)
|
|
+{
|
|
+ char *n = NULL;
|
|
+ int l = 0;
|
|
+
|
|
+ if (!inode || IS_ERR(inode)) {
|
|
+ dpri("i%d: err %ld\n", bindex, PTR_ERR(inode));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* the type of i_blocks depends upon CONFIG_LBDAF */
|
|
+ BUILD_BUG_ON(sizeof(inode->i_blocks) != sizeof(unsigned long)
|
|
+ && sizeof(inode->i_blocks) != sizeof(u64));
|
|
+ if (wh) {
|
|
+ n = (void *)wh->d_name.name;
|
|
+ l = wh->d_name.len;
|
|
+ }
|
|
+
|
|
+ dpri("i%d: %p, i%lu, %s, cnt %d, nl %u, 0%o, sz %llu, blk %llu,"
|
|
+ " hn %d, ct %lld, np %lu, st 0x%lx, f 0x%x, v %llu, g %x%s%.*s\n",
|
|
+ bindex, inode,
|
|
+ inode->i_ino, inode->i_sb ? au_sbtype(inode->i_sb) : "??",
|
|
+ atomic_read(&inode->i_count), inode->i_nlink, inode->i_mode,
|
|
+ i_size_read(inode), (unsigned long long)inode->i_blocks,
|
|
+ hn, (long long)timespec64_to_ns(&inode->i_ctime) & 0x0ffff,
|
|
+ inode->i_mapping ? inode->i_mapping->nrpages : 0,
|
|
+ inode->i_state, inode->i_flags, inode_peek_iversion(inode),
|
|
+ inode->i_generation,
|
|
+ l ? ", wh " : "", l, n);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void au_dpri_inode(struct inode *inode)
|
|
+{
|
|
+ struct au_iinfo *iinfo;
|
|
+ struct au_hinode *hi;
|
|
+ aufs_bindex_t bindex;
|
|
+ int err, hn;
|
|
+
|
|
+ err = do_pri_inode(-1, inode, -1, NULL);
|
|
+ if (err || !au_test_aufs(inode->i_sb) || au_is_bad_inode(inode))
|
|
+ return;
|
|
+
|
|
+ iinfo = au_ii(inode);
|
|
+ dpri("i-1: btop %d, bbot %d, gen %d\n",
|
|
+ iinfo->ii_btop, iinfo->ii_bbot, au_iigen(inode, NULL));
|
|
+ if (iinfo->ii_btop < 0)
|
|
+ return;
|
|
+ hn = 0;
|
|
+ for (bindex = iinfo->ii_btop; bindex <= iinfo->ii_bbot; bindex++) {
|
|
+ hi = au_hinode(iinfo, bindex);
|
|
+ hn = !!au_hn(hi);
|
|
+ do_pri_inode(bindex, hi->hi_inode, hn, hi->hi_whdentry);
|
|
+ }
|
|
+}
|
|
+
|
|
+void au_dpri_dalias(struct inode *inode)
|
|
+{
|
|
+ struct dentry *d;
|
|
+
|
|
+ spin_lock(&inode->i_lock);
|
|
+ hlist_for_each_entry(d, &inode->i_dentry, d_u.d_alias)
|
|
+ au_dpri_dentry(d);
|
|
+ spin_unlock(&inode->i_lock);
|
|
+}
|
|
+
|
|
+static int do_pri_dentry(aufs_bindex_t bindex, struct dentry *dentry)
|
|
+{
|
|
+ struct dentry *wh = NULL;
|
|
+ int hn;
|
|
+ struct inode *inode;
|
|
+ struct au_iinfo *iinfo;
|
|
+ struct au_hinode *hi;
|
|
+
|
|
+ if (!dentry || IS_ERR(dentry)) {
|
|
+ dpri("d%d: err %ld\n", bindex, PTR_ERR(dentry));
|
|
+ return -1;
|
|
+ }
|
|
+ /* do not call dget_parent() here */
|
|
+ /* note: access d_xxx without d_lock */
|
|
+ dpri("d%d: %p, %pd2?, %s, cnt %d, flags 0x%x, %shashed\n",
|
|
+ bindex, dentry, dentry,
|
|
+ dentry->d_sb ? au_sbtype(dentry->d_sb) : "??",
|
|
+ au_dcount(dentry), dentry->d_flags,
|
|
+ d_unhashed(dentry) ? "un" : "");
|
|
+ hn = -1;
|
|
+ inode = NULL;
|
|
+ if (d_is_positive(dentry))
|
|
+ inode = d_inode(dentry);
|
|
+ if (inode
|
|
+ && au_test_aufs(dentry->d_sb)
|
|
+ && bindex >= 0
|
|
+ && !au_is_bad_inode(inode)) {
|
|
+ iinfo = au_ii(inode);
|
|
+ hi = au_hinode(iinfo, bindex);
|
|
+ hn = !!au_hn(hi);
|
|
+ wh = hi->hi_whdentry;
|
|
+ }
|
|
+ do_pri_inode(bindex, inode, hn, wh);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void au_dpri_dentry(struct dentry *dentry)
|
|
+{
|
|
+ struct au_dinfo *dinfo;
|
|
+ aufs_bindex_t bindex;
|
|
+ int err;
|
|
+
|
|
+ err = do_pri_dentry(-1, dentry);
|
|
+ if (err || !au_test_aufs(dentry->d_sb))
|
|
+ return;
|
|
+
|
|
+ dinfo = au_di(dentry);
|
|
+ if (!dinfo)
|
|
+ return;
|
|
+ dpri("d-1: btop %d, bbot %d, bwh %d, bdiropq %d, gen %d, tmp %d\n",
|
|
+ dinfo->di_btop, dinfo->di_bbot,
|
|
+ dinfo->di_bwh, dinfo->di_bdiropq, au_digen(dentry),
|
|
+ dinfo->di_tmpfile);
|
|
+ if (dinfo->di_btop < 0)
|
|
+ return;
|
|
+ for (bindex = dinfo->di_btop; bindex <= dinfo->di_bbot; bindex++)
|
|
+ do_pri_dentry(bindex, au_hdentry(dinfo, bindex)->hd_dentry);
|
|
+}
|
|
+
|
|
+static int do_pri_file(aufs_bindex_t bindex, struct file *file)
|
|
+{
|
|
+ char a[32];
|
|
+
|
|
+ if (!file || IS_ERR(file)) {
|
|
+ dpri("f%d: err %ld\n", bindex, PTR_ERR(file));
|
|
+ return -1;
|
|
+ }
|
|
+ a[0] = 0;
|
|
+ if (bindex < 0
|
|
+ && !IS_ERR_OR_NULL(file->f_path.dentry)
|
|
+ && au_test_aufs(file->f_path.dentry->d_sb)
|
|
+ && au_fi(file))
|
|
+ snprintf(a, sizeof(a), ", gen %d, mmapped %d",
|
|
+ au_figen(file), atomic_read(&au_fi(file)->fi_mmapped));
|
|
+ dpri("f%d: mode 0x%x, flags 0%o, cnt %ld, v %llu, pos %llu%s\n",
|
|
+ bindex, file->f_mode, file->f_flags, (long)file_count(file),
|
|
+ file->f_version, file->f_pos, a);
|
|
+ if (!IS_ERR_OR_NULL(file->f_path.dentry))
|
|
+ do_pri_dentry(bindex, file->f_path.dentry);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void au_dpri_file(struct file *file)
|
|
+{
|
|
+ struct au_finfo *finfo;
|
|
+ struct au_fidir *fidir;
|
|
+ struct au_hfile *hfile;
|
|
+ aufs_bindex_t bindex;
|
|
+ int err;
|
|
+
|
|
+ err = do_pri_file(-1, file);
|
|
+ if (err
|
|
+ || IS_ERR_OR_NULL(file->f_path.dentry)
|
|
+ || !au_test_aufs(file->f_path.dentry->d_sb))
|
|
+ return;
|
|
+
|
|
+ finfo = au_fi(file);
|
|
+ if (!finfo)
|
|
+ return;
|
|
+ if (finfo->fi_btop < 0)
|
|
+ return;
|
|
+ fidir = finfo->fi_hdir;
|
|
+ if (!fidir)
|
|
+ do_pri_file(finfo->fi_btop, finfo->fi_htop.hf_file);
|
|
+ else
|
|
+ for (bindex = finfo->fi_btop;
|
|
+ bindex >= 0 && bindex <= fidir->fd_bbot;
|
|
+ bindex++) {
|
|
+ hfile = fidir->fd_hfile + bindex;
|
|
+ do_pri_file(bindex, hfile ? hfile->hf_file : NULL);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int do_pri_br(aufs_bindex_t bindex, struct au_branch *br)
|
|
+{
|
|
+ struct vfsmount *mnt;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ if (!br || IS_ERR(br))
|
|
+ goto out;
|
|
+ mnt = au_br_mnt(br);
|
|
+ if (!mnt || IS_ERR(mnt))
|
|
+ goto out;
|
|
+ sb = mnt->mnt_sb;
|
|
+ if (!sb || IS_ERR(sb))
|
|
+ goto out;
|
|
+
|
|
+ dpri("s%d: {perm 0x%x, id %d, wbr %p}, "
|
|
+ "%s, dev 0x%02x%02x, flags 0x%lx, cnt %d, active %d, "
|
|
+ "xino %d\n",
|
|
+ bindex, br->br_perm, br->br_id, br->br_wbr,
|
|
+ au_sbtype(sb), MAJOR(sb->s_dev), MINOR(sb->s_dev),
|
|
+ sb->s_flags, sb->s_count,
|
|
+ atomic_read(&sb->s_active),
|
|
+ !!au_xino_file(br->br_xino, /*idx*/-1));
|
|
+ return 0;
|
|
+
|
|
+out:
|
|
+ dpri("s%d: err %ld\n", bindex, PTR_ERR(br));
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+void au_dpri_sb(struct super_block *sb)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ aufs_bindex_t bindex;
|
|
+ int err;
|
|
+ /* to reduce stack size */
|
|
+ struct {
|
|
+ struct vfsmount mnt;
|
|
+ struct au_branch fake;
|
|
+ } *a;
|
|
+
|
|
+ /* this function can be called from magic sysrq */
|
|
+ a = kzalloc(sizeof(*a), GFP_ATOMIC);
|
|
+ if (unlikely(!a)) {
|
|
+ dpri("no memory\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ a->mnt.mnt_sb = sb;
|
|
+ a->fake.br_path.mnt = &a->mnt;
|
|
+ err = do_pri_br(-1, &a->fake);
|
|
+ au_kfree_rcu(a);
|
|
+ dpri("dev 0x%x\n", sb->s_dev);
|
|
+ if (err || !au_test_aufs(sb))
|
|
+ return;
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ if (!sbinfo)
|
|
+ return;
|
|
+ dpri("nw %d, gen %u, kobj %d\n",
|
|
+ atomic_read(&sbinfo->si_nowait.nw_len), sbinfo->si_generation,
|
|
+ kref_read(&sbinfo->si_kobj.kref));
|
|
+ for (bindex = 0; bindex <= sbinfo->si_bbot; bindex++)
|
|
+ do_pri_br(bindex, sbinfo->si_branch[0 + bindex]);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void __au_dbg_verify_dinode(struct dentry *dentry, const char *func, int line)
|
|
+{
|
|
+ struct inode *h_inode, *inode = d_inode(dentry);
|
|
+ struct dentry *h_dentry;
|
|
+ aufs_bindex_t bindex, bbot, bi;
|
|
+
|
|
+ if (!inode /* || au_di(dentry)->di_lsc == AuLsc_DI_TMP */)
|
|
+ return;
|
|
+
|
|
+ bbot = au_dbbot(dentry);
|
|
+ bi = au_ibbot(inode);
|
|
+ if (bi < bbot)
|
|
+ bbot = bi;
|
|
+ bindex = au_dbtop(dentry);
|
|
+ bi = au_ibtop(inode);
|
|
+ if (bi > bindex)
|
|
+ bindex = bi;
|
|
+
|
|
+ for (; bindex <= bbot; bindex++) {
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (!h_dentry)
|
|
+ continue;
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+ if (unlikely(h_inode != d_inode(h_dentry))) {
|
|
+ au_debug_on();
|
|
+ AuDbg("b%d, %s:%d\n", bindex, func, line);
|
|
+ AuDbgDentry(dentry);
|
|
+ AuDbgInode(inode);
|
|
+ au_debug_off();
|
|
+ BUG();
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+void au_dbg_verify_gen(struct dentry *parent, unsigned int sigen)
|
|
+{
|
|
+ int err, i, j;
|
|
+ struct au_dcsub_pages dpages;
|
|
+ struct au_dpage *dpage;
|
|
+ struct dentry **dentries;
|
|
+
|
|
+ err = au_dpages_init(&dpages, GFP_NOFS);
|
|
+ AuDebugOn(err);
|
|
+ err = au_dcsub_pages_rev_aufs(&dpages, parent, /*do_include*/1);
|
|
+ AuDebugOn(err);
|
|
+ for (i = dpages.ndpage - 1; !err && i >= 0; i--) {
|
|
+ dpage = dpages.dpages + i;
|
|
+ dentries = dpage->dentries;
|
|
+ for (j = dpage->ndentry - 1; !err && j >= 0; j--)
|
|
+ AuDebugOn(au_digen_test(dentries[j], sigen));
|
|
+ }
|
|
+ au_dpages_free(&dpages);
|
|
+}
|
|
+
|
|
+void au_dbg_verify_kthread(void)
|
|
+{
|
|
+ if (au_wkq_test()) {
|
|
+ au_dbg_blocked();
|
|
+ /*
|
|
+ * It may be recursive, but udba=notify between two aufs mounts,
|
|
+ * where a single ro branch is shared, is not a problem.
|
|
+ */
|
|
+ /* WARN_ON(1); */
|
|
+ }
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int __init au_debug_init(void)
|
|
+{
|
|
+ aufs_bindex_t bindex;
|
|
+ struct au_vdir_destr destr;
|
|
+
|
|
+ bindex = -1;
|
|
+ AuDebugOn(bindex >= 0);
|
|
+
|
|
+ destr.len = -1;
|
|
+ AuDebugOn(destr.len < NAME_MAX);
|
|
+
|
|
+#ifdef CONFIG_4KSTACKS
|
|
+ pr_warn("CONFIG_4KSTACKS is defined.\n");
|
|
+#endif
|
|
+
|
|
+ return 0;
|
|
+}
|
|
diff --git a/fs/aufs/debug.h b/fs/aufs/debug.h
|
|
new file mode 100644
|
|
index 000000000000..7e46953513f2
|
|
--- /dev/null
|
|
+++ b/fs/aufs/debug.h
|
|
@@ -0,0 +1,226 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * debug print functions
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_DEBUG_H__
|
|
+#define __AUFS_DEBUG_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/atomic.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/kallsyms.h>
|
|
+#include <linux/sysrq.h>
|
|
+
|
|
+#ifdef CONFIG_AUFS_DEBUG
|
|
+#define AuDebugOn(a) BUG_ON(a)
|
|
+
|
|
+/* module parameter */
|
|
+extern atomic_t aufs_debug;
|
|
+static inline void au_debug_on(void)
|
|
+{
|
|
+ atomic_inc(&aufs_debug);
|
|
+}
|
|
+static inline void au_debug_off(void)
|
|
+{
|
|
+ atomic_dec_if_positive(&aufs_debug);
|
|
+}
|
|
+
|
|
+static inline int au_debug_test(void)
|
|
+{
|
|
+ return atomic_read(&aufs_debug) > 0;
|
|
+}
|
|
+#else
|
|
+#define AuDebugOn(a) do {} while (0)
|
|
+AuStubVoid(au_debug_on, void)
|
|
+AuStubVoid(au_debug_off, void)
|
|
+AuStubInt0(au_debug_test, void)
|
|
+#endif /* CONFIG_AUFS_DEBUG */
|
|
+
|
|
+#define param_check_atomic_t(name, p) __param_check(name, p, atomic_t)
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* debug print */
|
|
+
|
|
+#define AuDbg(fmt, ...) do { \
|
|
+ if (au_debug_test()) \
|
|
+ pr_debug("DEBUG: " fmt, ##__VA_ARGS__); \
|
|
+} while (0)
|
|
+#define AuLabel(l) AuDbg(#l "\n")
|
|
+#define AuIOErr(fmt, ...) pr_err("I/O Error, " fmt, ##__VA_ARGS__)
|
|
+#define AuWarn1(fmt, ...) do { \
|
|
+ static unsigned char _c; \
|
|
+ if (!_c++) \
|
|
+ pr_warn(fmt, ##__VA_ARGS__); \
|
|
+} while (0)
|
|
+
|
|
+#define AuErr1(fmt, ...) do { \
|
|
+ static unsigned char _c; \
|
|
+ if (!_c++) \
|
|
+ pr_err(fmt, ##__VA_ARGS__); \
|
|
+} while (0)
|
|
+
|
|
+#define AuIOErr1(fmt, ...) do { \
|
|
+ static unsigned char _c; \
|
|
+ if (!_c++) \
|
|
+ AuIOErr(fmt, ##__VA_ARGS__); \
|
|
+} while (0)
|
|
+
|
|
+#define AuUnsupportMsg "This operation is not supported." \
|
|
+ " Please report this application to aufs-users ML."
|
|
+#define AuUnsupport(fmt, ...) do { \
|
|
+ pr_err(AuUnsupportMsg "\n" fmt, ##__VA_ARGS__); \
|
|
+ dump_stack(); \
|
|
+} while (0)
|
|
+
|
|
+#define AuTraceErr(e) do { \
|
|
+ if (unlikely((e) < 0)) \
|
|
+ AuDbg("err %d\n", (int)(e)); \
|
|
+} while (0)
|
|
+
|
|
+#define AuTraceErrPtr(p) do { \
|
|
+ if (IS_ERR(p)) \
|
|
+ AuDbg("err %ld\n", PTR_ERR(p)); \
|
|
+} while (0)
|
|
+
|
|
+/* dirty macros for debug print, use with "%.*s" and caution */
|
|
+#define AuLNPair(qstr) (qstr)->len, (qstr)->name
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct dentry;
|
|
+#ifdef CONFIG_AUFS_DEBUG
|
|
+extern struct mutex au_dbg_mtx;
|
|
+extern char *au_plevel;
|
|
+struct au_nhash;
|
|
+void au_dpri_whlist(struct au_nhash *whlist);
|
|
+struct au_vdir;
|
|
+void au_dpri_vdir(struct au_vdir *vdir);
|
|
+struct inode;
|
|
+void au_dpri_inode(struct inode *inode);
|
|
+void au_dpri_dalias(struct inode *inode);
|
|
+void au_dpri_dentry(struct dentry *dentry);
|
|
+struct file;
|
|
+void au_dpri_file(struct file *filp);
|
|
+struct super_block;
|
|
+void au_dpri_sb(struct super_block *sb);
|
|
+
|
|
+#define au_dbg_verify_dinode(d) __au_dbg_verify_dinode(d, __func__, __LINE__)
|
|
+void __au_dbg_verify_dinode(struct dentry *dentry, const char *func, int line);
|
|
+void au_dbg_verify_gen(struct dentry *parent, unsigned int sigen);
|
|
+void au_dbg_verify_kthread(void);
|
|
+
|
|
+int __init au_debug_init(void);
|
|
+
|
|
+#define AuDbgWhlist(w) do { \
|
|
+ mutex_lock(&au_dbg_mtx); \
|
|
+ AuDbg(#w "\n"); \
|
|
+ au_dpri_whlist(w); \
|
|
+ mutex_unlock(&au_dbg_mtx); \
|
|
+} while (0)
|
|
+
|
|
+#define AuDbgVdir(v) do { \
|
|
+ mutex_lock(&au_dbg_mtx); \
|
|
+ AuDbg(#v "\n"); \
|
|
+ au_dpri_vdir(v); \
|
|
+ mutex_unlock(&au_dbg_mtx); \
|
|
+} while (0)
|
|
+
|
|
+#define AuDbgInode(i) do { \
|
|
+ mutex_lock(&au_dbg_mtx); \
|
|
+ AuDbg(#i "\n"); \
|
|
+ au_dpri_inode(i); \
|
|
+ mutex_unlock(&au_dbg_mtx); \
|
|
+} while (0)
|
|
+
|
|
+#define AuDbgDAlias(i) do { \
|
|
+ mutex_lock(&au_dbg_mtx); \
|
|
+ AuDbg(#i "\n"); \
|
|
+ au_dpri_dalias(i); \
|
|
+ mutex_unlock(&au_dbg_mtx); \
|
|
+} while (0)
|
|
+
|
|
+#define AuDbgDentry(d) do { \
|
|
+ mutex_lock(&au_dbg_mtx); \
|
|
+ AuDbg(#d "\n"); \
|
|
+ au_dpri_dentry(d); \
|
|
+ mutex_unlock(&au_dbg_mtx); \
|
|
+} while (0)
|
|
+
|
|
+#define AuDbgFile(f) do { \
|
|
+ mutex_lock(&au_dbg_mtx); \
|
|
+ AuDbg(#f "\n"); \
|
|
+ au_dpri_file(f); \
|
|
+ mutex_unlock(&au_dbg_mtx); \
|
|
+} while (0)
|
|
+
|
|
+#define AuDbgSb(sb) do { \
|
|
+ mutex_lock(&au_dbg_mtx); \
|
|
+ AuDbg(#sb "\n"); \
|
|
+ au_dpri_sb(sb); \
|
|
+ mutex_unlock(&au_dbg_mtx); \
|
|
+} while (0)
|
|
+
|
|
+#define AuDbgSym(addr) do { \
|
|
+ char sym[KSYM_SYMBOL_LEN]; \
|
|
+ sprint_symbol(sym, (unsigned long)addr); \
|
|
+ AuDbg("%s\n", sym); \
|
|
+} while (0)
|
|
+#else
|
|
+AuStubVoid(au_dbg_verify_dinode, struct dentry *dentry)
|
|
+AuStubVoid(au_dbg_verify_gen, struct dentry *parent, unsigned int sigen)
|
|
+AuStubVoid(au_dbg_verify_kthread, void)
|
|
+AuStubInt0(__init au_debug_init, void)
|
|
+
|
|
+#define AuDbgWhlist(w) do {} while (0)
|
|
+#define AuDbgVdir(v) do {} while (0)
|
|
+#define AuDbgInode(i) do {} while (0)
|
|
+#define AuDbgDAlias(i) do {} while (0)
|
|
+#define AuDbgDentry(d) do {} while (0)
|
|
+#define AuDbgFile(f) do {} while (0)
|
|
+#define AuDbgSb(sb) do {} while (0)
|
|
+#define AuDbgSym(addr) do {} while (0)
|
|
+#endif /* CONFIG_AUFS_DEBUG */
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_AUFS_MAGIC_SYSRQ
|
|
+int __init au_sysrq_init(void);
|
|
+void au_sysrq_fin(void);
|
|
+
|
|
+#ifdef CONFIG_HW_CONSOLE
|
|
+#define au_dbg_blocked() do { \
|
|
+ WARN_ON(1); \
|
|
+ handle_sysrq('w'); \
|
|
+} while (0)
|
|
+#else
|
|
+AuStubVoid(au_dbg_blocked, void)
|
|
+#endif
|
|
+
|
|
+#else
|
|
+AuStubInt0(__init au_sysrq_init, void)
|
|
+AuStubVoid(au_sysrq_fin, void)
|
|
+AuStubVoid(au_dbg_blocked, void)
|
|
+#endif /* CONFIG_AUFS_MAGIC_SYSRQ */
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_DEBUG_H__ */
|
|
diff --git a/fs/aufs/dentry.c b/fs/aufs/dentry.c
|
|
new file mode 100644
|
|
index 000000000000..50aaaa21d089
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dentry.c
|
|
@@ -0,0 +1,1154 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * lookup and dentry operations
|
|
+ */
|
|
+
|
|
+#include <linux/iversion.h>
|
|
+#include <linux/namei.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/*
|
|
+ * returns positive/negative dentry, NULL or an error.
|
|
+ * NULL means whiteout-ed or not-found.
|
|
+ */
|
|
+static struct dentry*
|
|
+au_do_lookup(struct dentry *h_parent, struct dentry *dentry,
|
|
+ aufs_bindex_t bindex, struct au_do_lookup_args *args)
|
|
+{
|
|
+ struct dentry *h_dentry;
|
|
+ struct inode *h_inode;
|
|
+ struct au_branch *br;
|
|
+ int wh_found, opq;
|
|
+ unsigned char wh_able;
|
|
+ const unsigned char allow_neg = !!au_ftest_lkup(args->flags, ALLOW_NEG);
|
|
+ const unsigned char ignore_perm = !!au_ftest_lkup(args->flags,
|
|
+ IGNORE_PERM);
|
|
+
|
|
+ wh_found = 0;
|
|
+ br = au_sbr(dentry->d_sb, bindex);
|
|
+ wh_able = !!au_br_whable(br->br_perm);
|
|
+ if (wh_able)
|
|
+ wh_found = au_wh_test(h_parent, &args->whname, ignore_perm);
|
|
+ h_dentry = ERR_PTR(wh_found);
|
|
+ if (!wh_found)
|
|
+ goto real_lookup;
|
|
+ if (unlikely(wh_found < 0))
|
|
+ goto out;
|
|
+
|
|
+ /* We found a whiteout */
|
|
+ /* au_set_dbbot(dentry, bindex); */
|
|
+ au_set_dbwh(dentry, bindex);
|
|
+ if (!allow_neg)
|
|
+ return NULL; /* success */
|
|
+
|
|
+real_lookup:
|
|
+ if (!ignore_perm)
|
|
+ h_dentry = vfsub_lkup_one(args->name, h_parent);
|
|
+ else
|
|
+ h_dentry = au_sio_lkup_one(args->name, h_parent);
|
|
+ if (IS_ERR(h_dentry)) {
|
|
+ if (PTR_ERR(h_dentry) == -ENAMETOOLONG
|
|
+ && !allow_neg)
|
|
+ h_dentry = NULL;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ if (d_is_negative(h_dentry)) {
|
|
+ if (!allow_neg)
|
|
+ goto out_neg;
|
|
+ } else if (wh_found
|
|
+ || (args->type && args->type != (h_inode->i_mode & S_IFMT)))
|
|
+ goto out_neg;
|
|
+ else if (au_ftest_lkup(args->flags, DIRREN)
|
|
+ /* && h_inode */
|
|
+ && !au_dr_lkup_h_ino(args, bindex, h_inode->i_ino)) {
|
|
+ AuDbg("b%d %pd ignored hi%llu\n", bindex, h_dentry,
|
|
+ (unsigned long long)h_inode->i_ino);
|
|
+ goto out_neg;
|
|
+ }
|
|
+
|
|
+ if (au_dbbot(dentry) <= bindex)
|
|
+ au_set_dbbot(dentry, bindex);
|
|
+ if (au_dbtop(dentry) < 0 || bindex < au_dbtop(dentry))
|
|
+ au_set_dbtop(dentry, bindex);
|
|
+ au_set_h_dptr(dentry, bindex, h_dentry);
|
|
+
|
|
+ if (!d_is_dir(h_dentry)
|
|
+ || !wh_able
|
|
+ || (d_really_is_positive(dentry) && !d_is_dir(dentry)))
|
|
+ goto out; /* success */
|
|
+
|
|
+ inode_lock_shared_nested(h_inode, AuLsc_I_CHILD);
|
|
+ opq = au_diropq_test(h_dentry);
|
|
+ inode_unlock_shared(h_inode);
|
|
+ if (opq > 0)
|
|
+ au_set_dbdiropq(dentry, bindex);
|
|
+ else if (unlikely(opq < 0)) {
|
|
+ au_set_h_dptr(dentry, bindex, NULL);
|
|
+ h_dentry = ERR_PTR(opq);
|
|
+ }
|
|
+ goto out;
|
|
+
|
|
+out_neg:
|
|
+ dput(h_dentry);
|
|
+ h_dentry = NULL;
|
|
+out:
|
|
+ return h_dentry;
|
|
+}
|
|
+
|
|
+static int au_test_shwh(struct super_block *sb, const struct qstr *name)
|
|
+{
|
|
+ if (unlikely(!au_opt_test(au_mntflags(sb), SHWH)
|
|
+ && !strncmp(name->name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)))
|
|
+ return -EPERM;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * returns the number of lower positive dentries,
|
|
+ * otherwise an error.
|
|
+ * can be called at unlinking with @type is zero.
|
|
+ */
|
|
+int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t btop,
|
|
+ unsigned int flags)
|
|
+{
|
|
+ int npositive, err;
|
|
+ aufs_bindex_t bindex, btail, bdiropq;
|
|
+ unsigned char isdir, dirperm1, dirren;
|
|
+ struct au_do_lookup_args args = {
|
|
+ .flags = flags,
|
|
+ .name = &dentry->d_name
|
|
+ };
|
|
+ struct dentry *parent;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ err = au_test_shwh(sb, args.name);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ err = au_wh_name_alloc(&args.whname, args.name);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ isdir = !!d_is_dir(dentry);
|
|
+ dirperm1 = !!au_opt_test(au_mntflags(sb), DIRPERM1);
|
|
+ dirren = !!au_opt_test(au_mntflags(sb), DIRREN);
|
|
+ if (dirren)
|
|
+ au_fset_lkup(args.flags, DIRREN);
|
|
+
|
|
+ npositive = 0;
|
|
+ parent = dget_parent(dentry);
|
|
+ btail = au_dbtaildir(parent);
|
|
+ for (bindex = btop; bindex <= btail; bindex++) {
|
|
+ struct dentry *h_parent, *h_dentry;
|
|
+ struct inode *h_inode, *h_dir;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (h_dentry) {
|
|
+ if (d_is_positive(h_dentry))
|
|
+ npositive++;
|
|
+ break;
|
|
+ }
|
|
+ h_parent = au_h_dptr(parent, bindex);
|
|
+ if (!h_parent || !d_is_dir(h_parent))
|
|
+ continue;
|
|
+
|
|
+ if (dirren) {
|
|
+ /* if the inum matches, then use the prepared name */
|
|
+ err = au_dr_lkup_name(&args, bindex);
|
|
+ if (unlikely(err))
|
|
+ goto out_parent;
|
|
+ }
|
|
+
|
|
+ h_dir = d_inode(h_parent);
|
|
+ inode_lock_shared_nested(h_dir, AuLsc_I_PARENT);
|
|
+ h_dentry = au_do_lookup(h_parent, dentry, bindex, &args);
|
|
+ inode_unlock_shared(h_dir);
|
|
+ err = PTR_ERR(h_dentry);
|
|
+ if (IS_ERR(h_dentry))
|
|
+ goto out_parent;
|
|
+ if (h_dentry)
|
|
+ au_fclr_lkup(args.flags, ALLOW_NEG);
|
|
+ if (dirperm1)
|
|
+ au_fset_lkup(args.flags, IGNORE_PERM);
|
|
+
|
|
+ if (au_dbwh(dentry) == bindex)
|
|
+ break;
|
|
+ if (!h_dentry)
|
|
+ continue;
|
|
+ if (d_is_negative(h_dentry))
|
|
+ continue;
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ npositive++;
|
|
+ if (!args.type)
|
|
+ args.type = h_inode->i_mode & S_IFMT;
|
|
+ if (args.type != S_IFDIR)
|
|
+ break;
|
|
+ else if (isdir) {
|
|
+ /* the type of lower may be different */
|
|
+ bdiropq = au_dbdiropq(dentry);
|
|
+ if (bdiropq >= 0 && bdiropq <= bindex)
|
|
+ break;
|
|
+ }
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (dirren
|
|
+ && au_dr_hino_test_add(&br->br_dirren, h_inode->i_ino,
|
|
+ /*add_ent*/NULL)) {
|
|
+ /* prepare next name to lookup */
|
|
+ err = au_dr_lkup(&args, dentry, bindex);
|
|
+ if (unlikely(err))
|
|
+ goto out_parent;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (npositive) {
|
|
+ AuLabel(positive);
|
|
+ au_update_dbtop(dentry);
|
|
+ }
|
|
+ err = npositive;
|
|
+ if (unlikely(!au_opt_test(au_mntflags(sb), UDBA_NONE)
|
|
+ && au_dbtop(dentry) < 0)) {
|
|
+ err = -EIO;
|
|
+ AuIOErr("both of real entry and whiteout found, %pd, err %d\n",
|
|
+ dentry, err);
|
|
+ }
|
|
+
|
|
+out_parent:
|
|
+ dput(parent);
|
|
+ au_kfree_try_rcu(args.whname.name);
|
|
+ if (dirren)
|
|
+ au_dr_lkup_fin(&args);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct dentry *au_sio_lkup_one(struct qstr *name, struct dentry *parent)
|
|
+{
|
|
+ struct dentry *dentry;
|
|
+ int wkq_err;
|
|
+
|
|
+ if (!au_test_h_perm_sio(d_inode(parent), MAY_EXEC))
|
|
+ dentry = vfsub_lkup_one(name, parent);
|
|
+ else {
|
|
+ struct vfsub_lkup_one_args args = {
|
|
+ .errp = &dentry,
|
|
+ .name = name,
|
|
+ .parent = parent
|
|
+ };
|
|
+
|
|
+ wkq_err = au_wkq_wait(vfsub_call_lkup_one, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ dentry = ERR_PTR(wkq_err);
|
|
+ }
|
|
+
|
|
+ return dentry;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * lookup @dentry on @bindex which should be negative.
|
|
+ */
|
|
+int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex, int wh)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *parent, *h_parent, *h_dentry;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ parent = dget_parent(dentry);
|
|
+ h_parent = au_h_dptr(parent, bindex);
|
|
+ br = au_sbr(dentry->d_sb, bindex);
|
|
+ if (wh)
|
|
+ h_dentry = au_whtmp_lkup(h_parent, br, &dentry->d_name);
|
|
+ else
|
|
+ h_dentry = au_sio_lkup_one(&dentry->d_name, h_parent);
|
|
+ err = PTR_ERR(h_dentry);
|
|
+ if (IS_ERR(h_dentry))
|
|
+ goto out;
|
|
+ if (unlikely(d_is_positive(h_dentry))) {
|
|
+ err = -EIO;
|
|
+ AuIOErr("%pd should be negative on b%d.\n", h_dentry, bindex);
|
|
+ dput(h_dentry);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ if (bindex < au_dbtop(dentry))
|
|
+ au_set_dbtop(dentry, bindex);
|
|
+ if (au_dbbot(dentry) < bindex)
|
|
+ au_set_dbbot(dentry, bindex);
|
|
+ au_set_h_dptr(dentry, bindex, h_dentry);
|
|
+
|
|
+out:
|
|
+ dput(parent);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* subset of struct inode */
|
|
+struct au_iattr {
|
|
+ unsigned long i_ino;
|
|
+ /* unsigned int i_nlink; */
|
|
+ kuid_t i_uid;
|
|
+ kgid_t i_gid;
|
|
+ u64 i_version;
|
|
+/*
|
|
+ loff_t i_size;
|
|
+ blkcnt_t i_blocks;
|
|
+*/
|
|
+ umode_t i_mode;
|
|
+};
|
|
+
|
|
+static void au_iattr_save(struct au_iattr *ia, struct inode *h_inode)
|
|
+{
|
|
+ ia->i_ino = h_inode->i_ino;
|
|
+ /* ia->i_nlink = h_inode->i_nlink; */
|
|
+ ia->i_uid = h_inode->i_uid;
|
|
+ ia->i_gid = h_inode->i_gid;
|
|
+ ia->i_version = inode_query_iversion(h_inode);
|
|
+/*
|
|
+ ia->i_size = h_inode->i_size;
|
|
+ ia->i_blocks = h_inode->i_blocks;
|
|
+*/
|
|
+ ia->i_mode = (h_inode->i_mode & S_IFMT);
|
|
+}
|
|
+
|
|
+static int au_iattr_test(struct au_iattr *ia, struct inode *h_inode)
|
|
+{
|
|
+ return ia->i_ino != h_inode->i_ino
|
|
+ /* || ia->i_nlink != h_inode->i_nlink */
|
|
+ || !uid_eq(ia->i_uid, h_inode->i_uid)
|
|
+ || !gid_eq(ia->i_gid, h_inode->i_gid)
|
|
+ || !inode_eq_iversion(h_inode, ia->i_version)
|
|
+/*
|
|
+ || ia->i_size != h_inode->i_size
|
|
+ || ia->i_blocks != h_inode->i_blocks
|
|
+*/
|
|
+ || ia->i_mode != (h_inode->i_mode & S_IFMT);
|
|
+}
|
|
+
|
|
+static int au_h_verify_dentry(struct dentry *h_dentry, struct dentry *h_parent,
|
|
+ struct au_branch *br)
|
|
+{
|
|
+ int err;
|
|
+ struct au_iattr ia;
|
|
+ struct inode *h_inode;
|
|
+ struct dentry *h_d;
|
|
+ struct super_block *h_sb;
|
|
+
|
|
+ err = 0;
|
|
+ memset(&ia, -1, sizeof(ia));
|
|
+ h_sb = h_dentry->d_sb;
|
|
+ h_inode = NULL;
|
|
+ if (d_is_positive(h_dentry)) {
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ au_iattr_save(&ia, h_inode);
|
|
+ } else if (au_test_nfs(h_sb) || au_test_fuse(h_sb))
|
|
+ /* nfs d_revalidate may return 0 for negative dentry */
|
|
+ /* fuse d_revalidate always return 0 for negative dentry */
|
|
+ goto out;
|
|
+
|
|
+ /* main purpose is namei.c:cached_lookup() and d_revalidate */
|
|
+ h_d = vfsub_lkup_one(&h_dentry->d_name, h_parent);
|
|
+ err = PTR_ERR(h_d);
|
|
+ if (IS_ERR(h_d))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ if (unlikely(h_d != h_dentry
|
|
+ || d_inode(h_d) != h_inode
|
|
+ || (h_inode && au_iattr_test(&ia, h_inode))))
|
|
+ err = au_busy_or_stale();
|
|
+ dput(h_d);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_h_verify(struct dentry *h_dentry, unsigned int udba, struct inode *h_dir,
|
|
+ struct dentry *h_parent, struct au_branch *br)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (udba == AuOpt_UDBA_REVAL
|
|
+ && !au_test_fs_remote(h_dentry->d_sb)) {
|
|
+ IMustLock(h_dir);
|
|
+ err = (d_inode(h_dentry->d_parent) != h_dir);
|
|
+ } else if (udba != AuOpt_UDBA_NONE)
|
|
+ err = au_h_verify_dentry(h_dentry, h_parent, br);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_do_refresh_hdentry(struct dentry *dentry, struct dentry *parent)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t new_bindex, bindex, bbot, bwh, bdiropq;
|
|
+ struct au_hdentry tmp, *p, *q;
|
|
+ struct au_dinfo *dinfo;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ DiMustWriteLock(dentry);
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ dinfo = au_di(dentry);
|
|
+ bbot = dinfo->di_bbot;
|
|
+ bwh = dinfo->di_bwh;
|
|
+ bdiropq = dinfo->di_bdiropq;
|
|
+ bindex = dinfo->di_btop;
|
|
+ p = au_hdentry(dinfo, bindex);
|
|
+ for (; bindex <= bbot; bindex++, p++) {
|
|
+ if (!p->hd_dentry)
|
|
+ continue;
|
|
+
|
|
+ new_bindex = au_br_index(sb, p->hd_id);
|
|
+ if (new_bindex == bindex)
|
|
+ continue;
|
|
+
|
|
+ if (dinfo->di_bwh == bindex)
|
|
+ bwh = new_bindex;
|
|
+ if (dinfo->di_bdiropq == bindex)
|
|
+ bdiropq = new_bindex;
|
|
+ if (new_bindex < 0) {
|
|
+ au_hdput(p);
|
|
+ p->hd_dentry = NULL;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* swap two lower dentries, and loop again */
|
|
+ q = au_hdentry(dinfo, new_bindex);
|
|
+ tmp = *q;
|
|
+ *q = *p;
|
|
+ *p = tmp;
|
|
+ if (tmp.hd_dentry) {
|
|
+ bindex--;
|
|
+ p--;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dinfo->di_bwh = -1;
|
|
+ if (bwh >= 0 && bwh <= au_sbbot(sb) && au_sbr_whable(sb, bwh))
|
|
+ dinfo->di_bwh = bwh;
|
|
+
|
|
+ dinfo->di_bdiropq = -1;
|
|
+ if (bdiropq >= 0
|
|
+ && bdiropq <= au_sbbot(sb)
|
|
+ && au_sbr_whable(sb, bdiropq))
|
|
+ dinfo->di_bdiropq = bdiropq;
|
|
+
|
|
+ err = -EIO;
|
|
+ dinfo->di_btop = -1;
|
|
+ dinfo->di_bbot = -1;
|
|
+ bbot = au_dbbot(parent);
|
|
+ bindex = 0;
|
|
+ p = au_hdentry(dinfo, bindex);
|
|
+ for (; bindex <= bbot; bindex++, p++)
|
|
+ if (p->hd_dentry) {
|
|
+ dinfo->di_btop = bindex;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (dinfo->di_btop >= 0) {
|
|
+ bindex = bbot;
|
|
+ p = au_hdentry(dinfo, bindex);
|
|
+ for (; bindex >= 0; bindex--, p--)
|
|
+ if (p->hd_dentry) {
|
|
+ dinfo->di_bbot = bindex;
|
|
+ err = 0;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_do_hide(struct dentry *dentry)
|
|
+{
|
|
+ struct inode *inode;
|
|
+
|
|
+ if (d_really_is_positive(dentry)) {
|
|
+ inode = d_inode(dentry);
|
|
+ if (!d_is_dir(dentry)) {
|
|
+ if (inode->i_nlink && !d_unhashed(dentry))
|
|
+ drop_nlink(inode);
|
|
+ } else {
|
|
+ clear_nlink(inode);
|
|
+ /* stop next lookup */
|
|
+ inode->i_flags |= S_DEAD;
|
|
+ }
|
|
+ smp_mb(); /* necessary? */
|
|
+ }
|
|
+ d_drop(dentry);
|
|
+}
|
|
+
|
|
+static int au_hide_children(struct dentry *parent)
|
|
+{
|
|
+ int err, i, j, ndentry;
|
|
+ struct au_dcsub_pages dpages;
|
|
+ struct au_dpage *dpage;
|
|
+ struct dentry *dentry;
|
|
+
|
|
+ err = au_dpages_init(&dpages, GFP_NOFS);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = au_dcsub_pages(&dpages, parent, NULL, NULL);
|
|
+ if (unlikely(err))
|
|
+ goto out_dpages;
|
|
+
|
|
+ /* in reverse order */
|
|
+ for (i = dpages.ndpage - 1; i >= 0; i--) {
|
|
+ dpage = dpages.dpages + i;
|
|
+ ndentry = dpage->ndentry;
|
|
+ for (j = ndentry - 1; j >= 0; j--) {
|
|
+ dentry = dpage->dentries[j];
|
|
+ if (dentry != parent)
|
|
+ au_do_hide(dentry);
|
|
+ }
|
|
+ }
|
|
+
|
|
+out_dpages:
|
|
+ au_dpages_free(&dpages);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_hide(struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ AuDbgDentry(dentry);
|
|
+ if (d_is_dir(dentry)) {
|
|
+ /* shrink_dcache_parent(dentry); */
|
|
+ err = au_hide_children(dentry);
|
|
+ if (unlikely(err))
|
|
+ AuIOErr("%pd, failed hiding children, ignored %d\n",
|
|
+ dentry, err);
|
|
+ }
|
|
+ au_do_hide(dentry);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * By adding a dirty branch, a cached dentry may be affected in various ways.
|
|
+ *
|
|
+ * a dirty branch is added
|
|
+ * - on the top of layers
|
|
+ * - in the middle of layers
|
|
+ * - to the bottom of layers
|
|
+ *
|
|
+ * on the added branch there exists
|
|
+ * - a whiteout
|
|
+ * - a diropq
|
|
+ * - a same named entry
|
|
+ * + exist
|
|
+ * * negative --> positive
|
|
+ * * positive --> positive
|
|
+ * - type is unchanged
|
|
+ * - type is changed
|
|
+ * + doesn't exist
|
|
+ * * negative --> negative
|
|
+ * * positive --> negative (rejected by au_br_del() for non-dir case)
|
|
+ * - none
|
|
+ */
|
|
+static int au_refresh_by_dinfo(struct dentry *dentry, struct au_dinfo *dinfo,
|
|
+ struct au_dinfo *tmp)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct {
|
|
+ struct dentry *dentry;
|
|
+ struct inode *inode;
|
|
+ mode_t mode;
|
|
+ } orig_h, tmp_h = {
|
|
+ .dentry = NULL
|
|
+ };
|
|
+ struct au_hdentry *hd;
|
|
+ struct inode *inode, *h_inode;
|
|
+ struct dentry *h_dentry;
|
|
+
|
|
+ err = 0;
|
|
+ AuDebugOn(dinfo->di_btop < 0);
|
|
+ orig_h.mode = 0;
|
|
+ orig_h.dentry = au_hdentry(dinfo, dinfo->di_btop)->hd_dentry;
|
|
+ orig_h.inode = NULL;
|
|
+ if (d_is_positive(orig_h.dentry)) {
|
|
+ orig_h.inode = d_inode(orig_h.dentry);
|
|
+ orig_h.mode = orig_h.inode->i_mode & S_IFMT;
|
|
+ }
|
|
+ if (tmp->di_btop >= 0) {
|
|
+ tmp_h.dentry = au_hdentry(tmp, tmp->di_btop)->hd_dentry;
|
|
+ if (d_is_positive(tmp_h.dentry)) {
|
|
+ tmp_h.inode = d_inode(tmp_h.dentry);
|
|
+ tmp_h.mode = tmp_h.inode->i_mode & S_IFMT;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ inode = NULL;
|
|
+ if (d_really_is_positive(dentry))
|
|
+ inode = d_inode(dentry);
|
|
+ if (!orig_h.inode) {
|
|
+ AuDbg("negative originally\n");
|
|
+ if (inode) {
|
|
+ au_hide(dentry);
|
|
+ goto out;
|
|
+ }
|
|
+ AuDebugOn(inode);
|
|
+ AuDebugOn(dinfo->di_btop != dinfo->di_bbot);
|
|
+ AuDebugOn(dinfo->di_bdiropq != -1);
|
|
+
|
|
+ if (!tmp_h.inode) {
|
|
+ AuDbg("negative --> negative\n");
|
|
+ /* should have only one negative lower */
|
|
+ if (tmp->di_btop >= 0
|
|
+ && tmp->di_btop < dinfo->di_btop) {
|
|
+ AuDebugOn(tmp->di_btop != tmp->di_bbot);
|
|
+ AuDebugOn(dinfo->di_btop != dinfo->di_bbot);
|
|
+ au_set_h_dptr(dentry, dinfo->di_btop, NULL);
|
|
+ au_di_cp(dinfo, tmp);
|
|
+ hd = au_hdentry(tmp, tmp->di_btop);
|
|
+ au_set_h_dptr(dentry, tmp->di_btop,
|
|
+ dget(hd->hd_dentry));
|
|
+ }
|
|
+ au_dbg_verify_dinode(dentry);
|
|
+ } else {
|
|
+ AuDbg("negative --> positive\n");
|
|
+ /*
|
|
+ * similar to the behaviour of creating with bypassing
|
|
+ * aufs.
|
|
+ * unhash it in order to force an error in the
|
|
+ * succeeding create operation.
|
|
+ * we should not set S_DEAD here.
|
|
+ */
|
|
+ d_drop(dentry);
|
|
+ /* au_di_swap(tmp, dinfo); */
|
|
+ au_dbg_verify_dinode(dentry);
|
|
+ }
|
|
+ } else {
|
|
+ AuDbg("positive originally\n");
|
|
+ /* inode may be NULL */
|
|
+ AuDebugOn(inode && (inode->i_mode & S_IFMT) != orig_h.mode);
|
|
+ if (!tmp_h.inode) {
|
|
+ AuDbg("positive --> negative\n");
|
|
+ /* or bypassing aufs */
|
|
+ au_hide(dentry);
|
|
+ if (tmp->di_bwh >= 0 && tmp->di_bwh <= dinfo->di_btop)
|
|
+ dinfo->di_bwh = tmp->di_bwh;
|
|
+ if (inode)
|
|
+ err = au_refresh_hinode_self(inode);
|
|
+ au_dbg_verify_dinode(dentry);
|
|
+ } else if (orig_h.mode == tmp_h.mode) {
|
|
+ AuDbg("positive --> positive, same type\n");
|
|
+ if (!S_ISDIR(orig_h.mode)
|
|
+ && dinfo->di_btop > tmp->di_btop) {
|
|
+ /*
|
|
+ * similar to the behaviour of removing and
|
|
+ * creating.
|
|
+ */
|
|
+ au_hide(dentry);
|
|
+ if (inode)
|
|
+ err = au_refresh_hinode_self(inode);
|
|
+ au_dbg_verify_dinode(dentry);
|
|
+ } else {
|
|
+ /* fill empty slots */
|
|
+ if (dinfo->di_btop > tmp->di_btop)
|
|
+ dinfo->di_btop = tmp->di_btop;
|
|
+ if (dinfo->di_bbot < tmp->di_bbot)
|
|
+ dinfo->di_bbot = tmp->di_bbot;
|
|
+ dinfo->di_bwh = tmp->di_bwh;
|
|
+ dinfo->di_bdiropq = tmp->di_bdiropq;
|
|
+ bbot = dinfo->di_bbot;
|
|
+ bindex = tmp->di_btop;
|
|
+ hd = au_hdentry(tmp, bindex);
|
|
+ for (; bindex <= bbot; bindex++, hd++) {
|
|
+ if (au_h_dptr(dentry, bindex))
|
|
+ continue;
|
|
+ h_dentry = hd->hd_dentry;
|
|
+ if (!h_dentry)
|
|
+ continue;
|
|
+ AuDebugOn(d_is_negative(h_dentry));
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ AuDebugOn(orig_h.mode
|
|
+ != (h_inode->i_mode
|
|
+ & S_IFMT));
|
|
+ au_set_h_dptr(dentry, bindex,
|
|
+ dget(h_dentry));
|
|
+ }
|
|
+ if (inode)
|
|
+ err = au_refresh_hinode(inode, dentry);
|
|
+ au_dbg_verify_dinode(dentry);
|
|
+ }
|
|
+ } else {
|
|
+ AuDbg("positive --> positive, different type\n");
|
|
+ /* similar to the behaviour of removing and creating */
|
|
+ au_hide(dentry);
|
|
+ if (inode)
|
|
+ err = au_refresh_hinode_self(inode);
|
|
+ au_dbg_verify_dinode(dentry);
|
|
+ }
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_refresh_dop(struct dentry *dentry, int force_reval)
|
|
+{
|
|
+ const struct dentry_operations *dop
|
|
+ = force_reval ? &aufs_dop : dentry->d_sb->s_d_op;
|
|
+ static const unsigned int mask
|
|
+ = DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE;
|
|
+
|
|
+ BUILD_BUG_ON(sizeof(mask) != sizeof(dentry->d_flags));
|
|
+
|
|
+ if (dentry->d_op == dop)
|
|
+ return;
|
|
+
|
|
+ AuDbg("%pd\n", dentry);
|
|
+ spin_lock(&dentry->d_lock);
|
|
+ if (dop == &aufs_dop)
|
|
+ dentry->d_flags |= mask;
|
|
+ else
|
|
+ dentry->d_flags &= ~mask;
|
|
+ dentry->d_op = dop;
|
|
+ spin_unlock(&dentry->d_lock);
|
|
+}
|
|
+
|
|
+int au_refresh_dentry(struct dentry *dentry, struct dentry *parent)
|
|
+{
|
|
+ int err, ebrange, nbr;
|
|
+ unsigned int sigen;
|
|
+ struct au_dinfo *dinfo, *tmp;
|
|
+ struct super_block *sb;
|
|
+ struct inode *inode;
|
|
+
|
|
+ DiMustWriteLock(dentry);
|
|
+ AuDebugOn(IS_ROOT(dentry));
|
|
+ AuDebugOn(d_really_is_negative(parent));
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ sigen = au_sigen(sb);
|
|
+ err = au_digen_test(parent, sigen);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ nbr = au_sbbot(sb) + 1;
|
|
+ dinfo = au_di(dentry);
|
|
+ err = au_di_realloc(dinfo, nbr, /*may_shrink*/0);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ ebrange = au_dbrange_test(dentry);
|
|
+ if (!ebrange)
|
|
+ ebrange = au_do_refresh_hdentry(dentry, parent);
|
|
+
|
|
+ if (d_unhashed(dentry) || ebrange /* || dinfo->di_tmpfile */) {
|
|
+ AuDebugOn(au_dbtop(dentry) < 0 && au_dbbot(dentry) >= 0);
|
|
+ if (d_really_is_positive(dentry)) {
|
|
+ inode = d_inode(dentry);
|
|
+ err = au_refresh_hinode_self(inode);
|
|
+ }
|
|
+ au_dbg_verify_dinode(dentry);
|
|
+ if (!err)
|
|
+ goto out_dgen; /* success */
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* temporary dinfo */
|
|
+ AuDbgDentry(dentry);
|
|
+ err = -ENOMEM;
|
|
+ tmp = au_di_alloc(sb, AuLsc_DI_TMP);
|
|
+ if (unlikely(!tmp))
|
|
+ goto out;
|
|
+ au_di_swap(tmp, dinfo);
|
|
+ /* returns the number of positive dentries */
|
|
+ /*
|
|
+ * if current working dir is removed, it returns an error.
|
|
+ * but the dentry is legal.
|
|
+ */
|
|
+ err = au_lkup_dentry(dentry, /*btop*/0, AuLkup_ALLOW_NEG);
|
|
+ AuDbgDentry(dentry);
|
|
+ au_di_swap(tmp, dinfo);
|
|
+ if (err == -ENOENT)
|
|
+ err = 0;
|
|
+ if (err >= 0) {
|
|
+ /* compare/refresh by dinfo */
|
|
+ AuDbgDentry(dentry);
|
|
+ err = au_refresh_by_dinfo(dentry, dinfo, tmp);
|
|
+ au_dbg_verify_dinode(dentry);
|
|
+ AuTraceErr(err);
|
|
+ }
|
|
+ au_di_realloc(dinfo, nbr, /*may_shrink*/1); /* harmless if err */
|
|
+ au_rw_write_unlock(&tmp->di_rwsem);
|
|
+ au_di_free(tmp);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+out_dgen:
|
|
+ au_update_digen(dentry);
|
|
+out:
|
|
+ if (unlikely(err && !(dentry->d_flags & DCACHE_NFSFS_RENAMED))) {
|
|
+ AuIOErr("failed refreshing %pd, %d\n", dentry, err);
|
|
+ AuDbgDentry(dentry);
|
|
+ }
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_do_h_d_reval(struct dentry *h_dentry, unsigned int flags,
|
|
+ struct dentry *dentry, aufs_bindex_t bindex)
|
|
+{
|
|
+ int err, valid;
|
|
+
|
|
+ err = 0;
|
|
+ if (!(h_dentry->d_flags & DCACHE_OP_REVALIDATE))
|
|
+ goto out;
|
|
+
|
|
+ AuDbg("b%d\n", bindex);
|
|
+ /*
|
|
+ * gave up supporting LOOKUP_CREATE/OPEN for lower fs,
|
|
+ * due to whiteout and branch permission.
|
|
+ */
|
|
+ flags &= ~(/*LOOKUP_PARENT |*/ LOOKUP_OPEN | LOOKUP_CREATE
|
|
+ | LOOKUP_FOLLOW | LOOKUP_EXCL);
|
|
+ /* it may return tri-state */
|
|
+ valid = h_dentry->d_op->d_revalidate(h_dentry, flags);
|
|
+
|
|
+ if (unlikely(valid < 0))
|
|
+ err = valid;
|
|
+ else if (!valid)
|
|
+ err = -EINVAL;
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* todo: remove this */
|
|
+static int h_d_revalidate(struct dentry *dentry, struct inode *inode,
|
|
+ unsigned int flags, int do_udba, int dirren)
|
|
+{
|
|
+ int err;
|
|
+ umode_t mode, h_mode;
|
|
+ aufs_bindex_t bindex, btail, btop, ibs, ibe;
|
|
+ unsigned char plus, unhashed, is_root, h_plus, h_nfs, tmpfile;
|
|
+ struct inode *h_inode, *h_cached_inode;
|
|
+ struct dentry *h_dentry;
|
|
+ struct qstr *name, *h_name;
|
|
+
|
|
+ err = 0;
|
|
+ plus = 0;
|
|
+ mode = 0;
|
|
+ ibs = -1;
|
|
+ ibe = -1;
|
|
+ unhashed = !!d_unhashed(dentry);
|
|
+ is_root = !!IS_ROOT(dentry);
|
|
+ name = &dentry->d_name;
|
|
+ tmpfile = au_di(dentry)->di_tmpfile;
|
|
+
|
|
+ /*
|
|
+ * Theoretically, REVAL test should be unnecessary in case of
|
|
+ * {FS,I}NOTIFY.
|
|
+ * But {fs,i}notify doesn't fire some necessary events,
|
|
+ * IN_ATTRIB for atime/nlink/pageio
|
|
+ * Let's do REVAL test too.
|
|
+ */
|
|
+ if (do_udba && inode) {
|
|
+ mode = (inode->i_mode & S_IFMT);
|
|
+ plus = (inode->i_nlink > 0);
|
|
+ ibs = au_ibtop(inode);
|
|
+ ibe = au_ibbot(inode);
|
|
+ }
|
|
+
|
|
+ btop = au_dbtop(dentry);
|
|
+ btail = btop;
|
|
+ if (inode && S_ISDIR(inode->i_mode))
|
|
+ btail = au_dbtaildir(dentry);
|
|
+ for (bindex = btop; bindex <= btail; bindex++) {
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (!h_dentry)
|
|
+ continue;
|
|
+
|
|
+ AuDbg("b%d, %pd\n", bindex, h_dentry);
|
|
+ h_nfs = !!au_test_nfs(h_dentry->d_sb);
|
|
+ spin_lock(&h_dentry->d_lock);
|
|
+ h_name = &h_dentry->d_name;
|
|
+ if (unlikely(do_udba
|
|
+ && !is_root
|
|
+ && ((!h_nfs
|
|
+ && (unhashed != !!d_unhashed(h_dentry)
|
|
+ || (!tmpfile && !dirren
|
|
+ && !au_qstreq(name, h_name))
|
|
+ ))
|
|
+ || (h_nfs
|
|
+ && !(flags & LOOKUP_OPEN)
|
|
+ && (h_dentry->d_flags
|
|
+ & DCACHE_NFSFS_RENAMED)))
|
|
+ )) {
|
|
+ int h_unhashed;
|
|
+
|
|
+ h_unhashed = d_unhashed(h_dentry);
|
|
+ spin_unlock(&h_dentry->d_lock);
|
|
+ AuDbg("unhash 0x%x 0x%x, %pd %pd\n",
|
|
+ unhashed, h_unhashed, dentry, h_dentry);
|
|
+ goto err;
|
|
+ }
|
|
+ spin_unlock(&h_dentry->d_lock);
|
|
+
|
|
+ err = au_do_h_d_reval(h_dentry, flags, dentry, bindex);
|
|
+ if (unlikely(err))
|
|
+ /* do not goto err, to keep the errno */
|
|
+ break;
|
|
+
|
|
+ /* todo: plink too? */
|
|
+ if (!do_udba)
|
|
+ continue;
|
|
+
|
|
+ /* UDBA tests */
|
|
+ if (unlikely(!!inode != d_is_positive(h_dentry)))
|
|
+ goto err;
|
|
+
|
|
+ h_inode = NULL;
|
|
+ if (d_is_positive(h_dentry))
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ h_plus = plus;
|
|
+ h_mode = mode;
|
|
+ h_cached_inode = h_inode;
|
|
+ if (h_inode) {
|
|
+ h_mode = (h_inode->i_mode & S_IFMT);
|
|
+ h_plus = (h_inode->i_nlink > 0);
|
|
+ }
|
|
+ if (inode && ibs <= bindex && bindex <= ibe)
|
|
+ h_cached_inode = au_h_iptr(inode, bindex);
|
|
+
|
|
+ if (!h_nfs) {
|
|
+ if (unlikely(plus != h_plus && !tmpfile))
|
|
+ goto err;
|
|
+ } else {
|
|
+ if (unlikely(!(h_dentry->d_flags & DCACHE_NFSFS_RENAMED)
|
|
+ && !is_root
|
|
+ && !IS_ROOT(h_dentry)
|
|
+ && unhashed != d_unhashed(h_dentry)))
|
|
+ goto err;
|
|
+ }
|
|
+ if (unlikely(mode != h_mode
|
|
+ || h_cached_inode != h_inode))
|
|
+ goto err;
|
|
+ continue;
|
|
+
|
|
+err:
|
|
+ err = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* todo: consolidate with do_refresh() and au_reval_for_attr() */
|
|
+static int simple_reval_dpath(struct dentry *dentry, unsigned int sigen)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *parent;
|
|
+
|
|
+ if (!au_digen_test(dentry, sigen))
|
|
+ return 0;
|
|
+
|
|
+ parent = dget_parent(dentry);
|
|
+ di_read_lock_parent(parent, AuLock_IR);
|
|
+ AuDebugOn(au_digen_test(parent, sigen));
|
|
+ au_dbg_verify_gen(parent, sigen);
|
|
+ err = au_refresh_dentry(dentry, parent);
|
|
+ di_read_unlock(parent, AuLock_IR);
|
|
+ dput(parent);
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_reval_dpath(struct dentry *dentry, unsigned int sigen)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d, *parent;
|
|
+
|
|
+ if (!au_ftest_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIR))
|
|
+ return simple_reval_dpath(dentry, sigen);
|
|
+
|
|
+ /* slow loop, keep it simple and stupid */
|
|
+ /* cf: au_cpup_dirs() */
|
|
+ err = 0;
|
|
+ parent = NULL;
|
|
+ while (au_digen_test(dentry, sigen)) {
|
|
+ d = dentry;
|
|
+ while (1) {
|
|
+ dput(parent);
|
|
+ parent = dget_parent(d);
|
|
+ if (!au_digen_test(parent, sigen))
|
|
+ break;
|
|
+ d = parent;
|
|
+ }
|
|
+
|
|
+ if (d != dentry)
|
|
+ di_write_lock_child2(d);
|
|
+
|
|
+ /* someone might update our dentry while we were sleeping */
|
|
+ if (au_digen_test(d, sigen)) {
|
|
+ /*
|
|
+ * todo: consolidate with simple_reval_dpath(),
|
|
+ * do_refresh() and au_reval_for_attr().
|
|
+ */
|
|
+ di_read_lock_parent(parent, AuLock_IR);
|
|
+ err = au_refresh_dentry(d, parent);
|
|
+ di_read_unlock(parent, AuLock_IR);
|
|
+ }
|
|
+
|
|
+ if (d != dentry)
|
|
+ di_write_unlock(d);
|
|
+ dput(parent);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * if valid returns 1, otherwise 0.
|
|
+ */
|
|
+static int aufs_d_revalidate(struct dentry *dentry, unsigned int flags)
|
|
+{
|
|
+ int valid, err;
|
|
+ unsigned int sigen;
|
|
+ unsigned char do_udba, dirren;
|
|
+ struct super_block *sb;
|
|
+ struct inode *inode;
|
|
+
|
|
+ /* todo: support rcu-walk? */
|
|
+ if (flags & LOOKUP_RCU)
|
|
+ return -ECHILD;
|
|
+
|
|
+ valid = 0;
|
|
+ if (unlikely(!au_di(dentry)))
|
|
+ goto out;
|
|
+
|
|
+ valid = 1;
|
|
+ sb = dentry->d_sb;
|
|
+ /*
|
|
+ * todo: very ugly
|
|
+ * i_mutex of parent dir may be held,
|
|
+ * but we should not return 'invalid' due to busy.
|
|
+ */
|
|
+ err = aufs_read_lock(dentry, AuLock_FLUSH | AuLock_DW | AuLock_NOPLM);
|
|
+ if (unlikely(err)) {
|
|
+ valid = err;
|
|
+ AuTraceErr(err);
|
|
+ goto out;
|
|
+ }
|
|
+ inode = NULL;
|
|
+ if (d_really_is_positive(dentry))
|
|
+ inode = d_inode(dentry);
|
|
+ if (unlikely(inode && au_is_bad_inode(inode))) {
|
|
+ err = -EINVAL;
|
|
+ AuTraceErr(err);
|
|
+ goto out_dgrade;
|
|
+ }
|
|
+ if (unlikely(au_dbrange_test(dentry))) {
|
|
+ err = -EINVAL;
|
|
+ AuTraceErr(err);
|
|
+ goto out_dgrade;
|
|
+ }
|
|
+
|
|
+ sigen = au_sigen(sb);
|
|
+ if (au_digen_test(dentry, sigen)) {
|
|
+ AuDebugOn(IS_ROOT(dentry));
|
|
+ err = au_reval_dpath(dentry, sigen);
|
|
+ if (unlikely(err)) {
|
|
+ AuTraceErr(err);
|
|
+ goto out_dgrade;
|
|
+ }
|
|
+ }
|
|
+ di_downgrade_lock(dentry, AuLock_IR);
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (!(flags & (LOOKUP_OPEN | LOOKUP_EMPTY))
|
|
+ && inode
|
|
+ && !(inode->i_state && I_LINKABLE)
|
|
+ && (IS_DEADDIR(inode) || !inode->i_nlink)) {
|
|
+ AuTraceErr(err);
|
|
+ goto out_inval;
|
|
+ }
|
|
+
|
|
+ do_udba = !au_opt_test(au_mntflags(sb), UDBA_NONE);
|
|
+ if (do_udba && inode) {
|
|
+ aufs_bindex_t btop = au_ibtop(inode);
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ if (btop >= 0) {
|
|
+ h_inode = au_h_iptr(inode, btop);
|
|
+ if (h_inode && au_test_higen(inode, h_inode)) {
|
|
+ AuTraceErr(err);
|
|
+ goto out_inval;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dirren = !!au_opt_test(au_mntflags(sb), DIRREN);
|
|
+ err = h_d_revalidate(dentry, inode, flags, do_udba, dirren);
|
|
+ if (unlikely(!err && do_udba && au_dbtop(dentry) < 0)) {
|
|
+ err = -EIO;
|
|
+ AuDbg("both of real entry and whiteout found, %p, err %d\n",
|
|
+ dentry, err);
|
|
+ }
|
|
+ goto out_inval;
|
|
+
|
|
+out_dgrade:
|
|
+ di_downgrade_lock(dentry, AuLock_IR);
|
|
+out_inval:
|
|
+ aufs_read_unlock(dentry, AuLock_IR);
|
|
+ AuTraceErr(err);
|
|
+ valid = !err;
|
|
+out:
|
|
+ if (!valid) {
|
|
+ AuDbg("%pd invalid, %d\n", dentry, valid);
|
|
+ d_drop(dentry);
|
|
+ }
|
|
+ return valid;
|
|
+}
|
|
+
|
|
+static void aufs_d_release(struct dentry *dentry)
|
|
+{
|
|
+ if (au_di(dentry)) {
|
|
+ au_di_fin(dentry);
|
|
+ au_hn_di_reinit(dentry);
|
|
+ }
|
|
+}
|
|
+
|
|
+const struct dentry_operations aufs_dop = {
|
|
+ .d_revalidate = aufs_d_revalidate,
|
|
+ .d_weak_revalidate = aufs_d_revalidate,
|
|
+ .d_release = aufs_d_release
|
|
+};
|
|
+
|
|
+/* aufs_dop without d_revalidate */
|
|
+const struct dentry_operations aufs_dop_noreval = {
|
|
+ .d_release = aufs_d_release
|
|
+};
|
|
diff --git a/fs/aufs/dentry.h b/fs/aufs/dentry.h
|
|
new file mode 100644
|
|
index 000000000000..c9f9c704da0a
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dentry.h
|
|
@@ -0,0 +1,268 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * lookup and dentry operations
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_DENTRY_H__
|
|
+#define __AUFS_DENTRY_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/dcache.h>
|
|
+#include "dirren.h"
|
|
+#include "rwsem.h"
|
|
+
|
|
+struct au_hdentry {
|
|
+ struct dentry *hd_dentry;
|
|
+ aufs_bindex_t hd_id;
|
|
+};
|
|
+
|
|
+struct au_dinfo {
|
|
+ atomic_t di_generation;
|
|
+
|
|
+ struct au_rwsem di_rwsem;
|
|
+ aufs_bindex_t di_btop, di_bbot, di_bwh, di_bdiropq;
|
|
+ unsigned char di_tmpfile; /* to allow the different name */
|
|
+ struct au_hdentry *di_hdentry;
|
|
+ struct rcu_head rcu;
|
|
+} ____cacheline_aligned_in_smp;
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* flags for au_lkup_dentry() */
|
|
+#define AuLkup_ALLOW_NEG 1
|
|
+#define AuLkup_IGNORE_PERM (1 << 1)
|
|
+#define AuLkup_DIRREN (1 << 2)
|
|
+#define au_ftest_lkup(flags, name) ((flags) & AuLkup_##name)
|
|
+#define au_fset_lkup(flags, name) \
|
|
+ do { (flags) |= AuLkup_##name; } while (0)
|
|
+#define au_fclr_lkup(flags, name) \
|
|
+ do { (flags) &= ~AuLkup_##name; } while (0)
|
|
+
|
|
+#ifndef CONFIG_AUFS_DIRREN
|
|
+#undef AuLkup_DIRREN
|
|
+#define AuLkup_DIRREN 0
|
|
+#endif
|
|
+
|
|
+struct au_do_lookup_args {
|
|
+ unsigned int flags;
|
|
+ mode_t type;
|
|
+ struct qstr whname, *name;
|
|
+ struct au_dr_lookup dirren;
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* dentry.c */
|
|
+extern const struct dentry_operations aufs_dop, aufs_dop_noreval;
|
|
+struct au_branch;
|
|
+struct dentry *au_sio_lkup_one(struct qstr *name, struct dentry *parent);
|
|
+int au_h_verify(struct dentry *h_dentry, unsigned int udba, struct inode *h_dir,
|
|
+ struct dentry *h_parent, struct au_branch *br);
|
|
+
|
|
+int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t btop,
|
|
+ unsigned int flags);
|
|
+int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex, int wh);
|
|
+int au_refresh_dentry(struct dentry *dentry, struct dentry *parent);
|
|
+int au_reval_dpath(struct dentry *dentry, unsigned int sigen);
|
|
+void au_refresh_dop(struct dentry *dentry, int force_reval);
|
|
+
|
|
+/* dinfo.c */
|
|
+void au_di_init_once(void *_di);
|
|
+struct au_dinfo *au_di_alloc(struct super_block *sb, unsigned int lsc);
|
|
+void au_di_free(struct au_dinfo *dinfo);
|
|
+void au_di_swap(struct au_dinfo *a, struct au_dinfo *b);
|
|
+void au_di_cp(struct au_dinfo *dst, struct au_dinfo *src);
|
|
+int au_di_init(struct dentry *dentry);
|
|
+void au_di_fin(struct dentry *dentry);
|
|
+int au_di_realloc(struct au_dinfo *dinfo, int nbr, int may_shrink);
|
|
+
|
|
+void di_read_lock(struct dentry *d, int flags, unsigned int lsc);
|
|
+void di_read_unlock(struct dentry *d, int flags);
|
|
+void di_downgrade_lock(struct dentry *d, int flags);
|
|
+void di_write_lock(struct dentry *d, unsigned int lsc);
|
|
+void di_write_unlock(struct dentry *d);
|
|
+void di_write_lock2_child(struct dentry *d1, struct dentry *d2, int isdir);
|
|
+void di_write_lock2_parent(struct dentry *d1, struct dentry *d2, int isdir);
|
|
+void di_write_unlock2(struct dentry *d1, struct dentry *d2);
|
|
+
|
|
+struct dentry *au_h_dptr(struct dentry *dentry, aufs_bindex_t bindex);
|
|
+struct dentry *au_h_d_alias(struct dentry *dentry, aufs_bindex_t bindex);
|
|
+aufs_bindex_t au_dbtail(struct dentry *dentry);
|
|
+aufs_bindex_t au_dbtaildir(struct dentry *dentry);
|
|
+
|
|
+void au_set_h_dptr(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct dentry *h_dentry);
|
|
+int au_digen_test(struct dentry *dentry, unsigned int sigen);
|
|
+int au_dbrange_test(struct dentry *dentry);
|
|
+void au_update_digen(struct dentry *dentry);
|
|
+void au_update_dbrange(struct dentry *dentry, int do_put_zero);
|
|
+void au_update_dbtop(struct dentry *dentry);
|
|
+void au_update_dbbot(struct dentry *dentry);
|
|
+int au_find_dbindex(struct dentry *dentry, struct dentry *h_dentry);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline struct au_dinfo *au_di(struct dentry *dentry)
|
|
+{
|
|
+ return dentry->d_fsdata;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* lock subclass for dinfo */
|
|
+enum {
|
|
+ AuLsc_DI_CHILD, /* child first */
|
|
+ AuLsc_DI_CHILD2, /* rename(2), link(2), and cpup at hnotify */
|
|
+ AuLsc_DI_CHILD3, /* copyup dirs */
|
|
+ AuLsc_DI_PARENT,
|
|
+ AuLsc_DI_PARENT2,
|
|
+ AuLsc_DI_PARENT3,
|
|
+ AuLsc_DI_TMP /* temp for replacing dinfo */
|
|
+};
|
|
+
|
|
+/*
|
|
+ * di_read_lock_child, di_write_lock_child,
|
|
+ * di_read_lock_child2, di_write_lock_child2,
|
|
+ * di_read_lock_child3, di_write_lock_child3,
|
|
+ * di_read_lock_parent, di_write_lock_parent,
|
|
+ * di_read_lock_parent2, di_write_lock_parent2,
|
|
+ * di_read_lock_parent3, di_write_lock_parent3,
|
|
+ */
|
|
+#define AuReadLockFunc(name, lsc) \
|
|
+static inline void di_read_lock_##name(struct dentry *d, int flags) \
|
|
+{ di_read_lock(d, flags, AuLsc_DI_##lsc); }
|
|
+
|
|
+#define AuWriteLockFunc(name, lsc) \
|
|
+static inline void di_write_lock_##name(struct dentry *d) \
|
|
+{ di_write_lock(d, AuLsc_DI_##lsc); }
|
|
+
|
|
+#define AuRWLockFuncs(name, lsc) \
|
|
+ AuReadLockFunc(name, lsc) \
|
|
+ AuWriteLockFunc(name, lsc)
|
|
+
|
|
+AuRWLockFuncs(child, CHILD);
|
|
+AuRWLockFuncs(child2, CHILD2);
|
|
+AuRWLockFuncs(child3, CHILD3);
|
|
+AuRWLockFuncs(parent, PARENT);
|
|
+AuRWLockFuncs(parent2, PARENT2);
|
|
+AuRWLockFuncs(parent3, PARENT3);
|
|
+
|
|
+#undef AuReadLockFunc
|
|
+#undef AuWriteLockFunc
|
|
+#undef AuRWLockFuncs
|
|
+
|
|
+#define DiMustNoWaiters(d) AuRwMustNoWaiters(&au_di(d)->di_rwsem)
|
|
+#define DiMustAnyLock(d) AuRwMustAnyLock(&au_di(d)->di_rwsem)
|
|
+#define DiMustWriteLock(d) AuRwMustWriteLock(&au_di(d)->di_rwsem)
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* todo: memory barrier? */
|
|
+static inline unsigned int au_digen(struct dentry *d)
|
|
+{
|
|
+ return atomic_read(&au_di(d)->di_generation);
|
|
+}
|
|
+
|
|
+static inline void au_h_dentry_init(struct au_hdentry *hdentry)
|
|
+{
|
|
+ hdentry->hd_dentry = NULL;
|
|
+}
|
|
+
|
|
+static inline struct au_hdentry *au_hdentry(struct au_dinfo *di,
|
|
+ aufs_bindex_t bindex)
|
|
+{
|
|
+ return di->di_hdentry + bindex;
|
|
+}
|
|
+
|
|
+static inline void au_hdput(struct au_hdentry *hd)
|
|
+{
|
|
+ if (hd)
|
|
+ dput(hd->hd_dentry);
|
|
+}
|
|
+
|
|
+static inline aufs_bindex_t au_dbtop(struct dentry *dentry)
|
|
+{
|
|
+ DiMustAnyLock(dentry);
|
|
+ return au_di(dentry)->di_btop;
|
|
+}
|
|
+
|
|
+static inline aufs_bindex_t au_dbbot(struct dentry *dentry)
|
|
+{
|
|
+ DiMustAnyLock(dentry);
|
|
+ return au_di(dentry)->di_bbot;
|
|
+}
|
|
+
|
|
+static inline aufs_bindex_t au_dbwh(struct dentry *dentry)
|
|
+{
|
|
+ DiMustAnyLock(dentry);
|
|
+ return au_di(dentry)->di_bwh;
|
|
+}
|
|
+
|
|
+static inline aufs_bindex_t au_dbdiropq(struct dentry *dentry)
|
|
+{
|
|
+ DiMustAnyLock(dentry);
|
|
+ return au_di(dentry)->di_bdiropq;
|
|
+}
|
|
+
|
|
+/* todo: hard/soft set? */
|
|
+static inline void au_set_dbtop(struct dentry *dentry, aufs_bindex_t bindex)
|
|
+{
|
|
+ DiMustWriteLock(dentry);
|
|
+ au_di(dentry)->di_btop = bindex;
|
|
+}
|
|
+
|
|
+static inline void au_set_dbbot(struct dentry *dentry, aufs_bindex_t bindex)
|
|
+{
|
|
+ DiMustWriteLock(dentry);
|
|
+ au_di(dentry)->di_bbot = bindex;
|
|
+}
|
|
+
|
|
+static inline void au_set_dbwh(struct dentry *dentry, aufs_bindex_t bindex)
|
|
+{
|
|
+ DiMustWriteLock(dentry);
|
|
+ /* dbwh can be outside of btop - bbot range */
|
|
+ au_di(dentry)->di_bwh = bindex;
|
|
+}
|
|
+
|
|
+static inline void au_set_dbdiropq(struct dentry *dentry, aufs_bindex_t bindex)
|
|
+{
|
|
+ DiMustWriteLock(dentry);
|
|
+ au_di(dentry)->di_bdiropq = bindex;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_AUFS_HNOTIFY
|
|
+static inline void au_digen_dec(struct dentry *d)
|
|
+{
|
|
+ atomic_dec(&au_di(d)->di_generation);
|
|
+}
|
|
+
|
|
+static inline void au_hn_di_reinit(struct dentry *dentry)
|
|
+{
|
|
+ dentry->d_fsdata = NULL;
|
|
+}
|
|
+#else
|
|
+AuStubVoid(au_hn_di_reinit, struct dentry *dentry __maybe_unused)
|
|
+#endif /* CONFIG_AUFS_HNOTIFY */
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_DENTRY_H__ */
|
|
diff --git a/fs/aufs/dinfo.c b/fs/aufs/dinfo.c
|
|
new file mode 100644
|
|
index 000000000000..be959106d980
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dinfo.c
|
|
@@ -0,0 +1,554 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * dentry private data
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+void au_di_init_once(void *_dinfo)
|
|
+{
|
|
+ struct au_dinfo *dinfo = _dinfo;
|
|
+
|
|
+ au_rw_init(&dinfo->di_rwsem);
|
|
+}
|
|
+
|
|
+struct au_dinfo *au_di_alloc(struct super_block *sb, unsigned int lsc)
|
|
+{
|
|
+ struct au_dinfo *dinfo;
|
|
+ int nbr, i;
|
|
+
|
|
+ dinfo = au_cache_alloc_dinfo();
|
|
+ if (unlikely(!dinfo))
|
|
+ goto out;
|
|
+
|
|
+ nbr = au_sbbot(sb) + 1;
|
|
+ if (nbr <= 0)
|
|
+ nbr = 1;
|
|
+ dinfo->di_hdentry = kcalloc(nbr, sizeof(*dinfo->di_hdentry), GFP_NOFS);
|
|
+ if (dinfo->di_hdentry) {
|
|
+ au_rw_write_lock_nested(&dinfo->di_rwsem, lsc);
|
|
+ dinfo->di_btop = -1;
|
|
+ dinfo->di_bbot = -1;
|
|
+ dinfo->di_bwh = -1;
|
|
+ dinfo->di_bdiropq = -1;
|
|
+ dinfo->di_tmpfile = 0;
|
|
+ for (i = 0; i < nbr; i++)
|
|
+ dinfo->di_hdentry[i].hd_id = -1;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ au_cache_free_dinfo(dinfo);
|
|
+ dinfo = NULL;
|
|
+
|
|
+out:
|
|
+ return dinfo;
|
|
+}
|
|
+
|
|
+void au_di_free(struct au_dinfo *dinfo)
|
|
+{
|
|
+ struct au_hdentry *p;
|
|
+ aufs_bindex_t bbot, bindex;
|
|
+
|
|
+ /* dentry may not be revalidated */
|
|
+ bindex = dinfo->di_btop;
|
|
+ if (bindex >= 0) {
|
|
+ bbot = dinfo->di_bbot;
|
|
+ p = au_hdentry(dinfo, bindex);
|
|
+ while (bindex++ <= bbot)
|
|
+ au_hdput(p++);
|
|
+ }
|
|
+ au_kfree_try_rcu(dinfo->di_hdentry);
|
|
+ au_cache_free_dinfo(dinfo);
|
|
+}
|
|
+
|
|
+void au_di_swap(struct au_dinfo *a, struct au_dinfo *b)
|
|
+{
|
|
+ struct au_hdentry *p;
|
|
+ aufs_bindex_t bi;
|
|
+
|
|
+ AuRwMustWriteLock(&a->di_rwsem);
|
|
+ AuRwMustWriteLock(&b->di_rwsem);
|
|
+
|
|
+#define DiSwap(v, name) \
|
|
+ do { \
|
|
+ v = a->di_##name; \
|
|
+ a->di_##name = b->di_##name; \
|
|
+ b->di_##name = v; \
|
|
+ } while (0)
|
|
+
|
|
+ DiSwap(p, hdentry);
|
|
+ DiSwap(bi, btop);
|
|
+ DiSwap(bi, bbot);
|
|
+ DiSwap(bi, bwh);
|
|
+ DiSwap(bi, bdiropq);
|
|
+ /* smp_mb(); */
|
|
+
|
|
+#undef DiSwap
|
|
+}
|
|
+
|
|
+void au_di_cp(struct au_dinfo *dst, struct au_dinfo *src)
|
|
+{
|
|
+ AuRwMustWriteLock(&dst->di_rwsem);
|
|
+ AuRwMustWriteLock(&src->di_rwsem);
|
|
+
|
|
+ dst->di_btop = src->di_btop;
|
|
+ dst->di_bbot = src->di_bbot;
|
|
+ dst->di_bwh = src->di_bwh;
|
|
+ dst->di_bdiropq = src->di_bdiropq;
|
|
+ /* smp_mb(); */
|
|
+}
|
|
+
|
|
+int au_di_init(struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+ struct super_block *sb;
|
|
+ struct au_dinfo *dinfo;
|
|
+
|
|
+ err = 0;
|
|
+ sb = dentry->d_sb;
|
|
+ dinfo = au_di_alloc(sb, AuLsc_DI_CHILD);
|
|
+ if (dinfo) {
|
|
+ atomic_set(&dinfo->di_generation, au_sigen(sb));
|
|
+ /* smp_mb(); */ /* atomic_set */
|
|
+ dentry->d_fsdata = dinfo;
|
|
+ } else
|
|
+ err = -ENOMEM;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_di_fin(struct dentry *dentry)
|
|
+{
|
|
+ struct au_dinfo *dinfo;
|
|
+
|
|
+ dinfo = au_di(dentry);
|
|
+ AuRwDestroy(&dinfo->di_rwsem);
|
|
+ au_di_free(dinfo);
|
|
+}
|
|
+
|
|
+int au_di_realloc(struct au_dinfo *dinfo, int nbr, int may_shrink)
|
|
+{
|
|
+ int err, sz;
|
|
+ struct au_hdentry *hdp;
|
|
+
|
|
+ AuRwMustWriteLock(&dinfo->di_rwsem);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ sz = sizeof(*hdp) * (dinfo->di_bbot + 1);
|
|
+ if (!sz)
|
|
+ sz = sizeof(*hdp);
|
|
+ hdp = au_kzrealloc(dinfo->di_hdentry, sz, sizeof(*hdp) * nbr, GFP_NOFS,
|
|
+ may_shrink);
|
|
+ if (hdp) {
|
|
+ dinfo->di_hdentry = hdp;
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static void do_ii_write_lock(struct inode *inode, unsigned int lsc)
|
|
+{
|
|
+ switch (lsc) {
|
|
+ case AuLsc_DI_CHILD:
|
|
+ ii_write_lock_child(inode);
|
|
+ break;
|
|
+ case AuLsc_DI_CHILD2:
|
|
+ ii_write_lock_child2(inode);
|
|
+ break;
|
|
+ case AuLsc_DI_CHILD3:
|
|
+ ii_write_lock_child3(inode);
|
|
+ break;
|
|
+ case AuLsc_DI_PARENT:
|
|
+ ii_write_lock_parent(inode);
|
|
+ break;
|
|
+ case AuLsc_DI_PARENT2:
|
|
+ ii_write_lock_parent2(inode);
|
|
+ break;
|
|
+ case AuLsc_DI_PARENT3:
|
|
+ ii_write_lock_parent3(inode);
|
|
+ break;
|
|
+ default:
|
|
+ BUG();
|
|
+ }
|
|
+}
|
|
+
|
|
+static void do_ii_read_lock(struct inode *inode, unsigned int lsc)
|
|
+{
|
|
+ switch (lsc) {
|
|
+ case AuLsc_DI_CHILD:
|
|
+ ii_read_lock_child(inode);
|
|
+ break;
|
|
+ case AuLsc_DI_CHILD2:
|
|
+ ii_read_lock_child2(inode);
|
|
+ break;
|
|
+ case AuLsc_DI_CHILD3:
|
|
+ ii_read_lock_child3(inode);
|
|
+ break;
|
|
+ case AuLsc_DI_PARENT:
|
|
+ ii_read_lock_parent(inode);
|
|
+ break;
|
|
+ case AuLsc_DI_PARENT2:
|
|
+ ii_read_lock_parent2(inode);
|
|
+ break;
|
|
+ case AuLsc_DI_PARENT3:
|
|
+ ii_read_lock_parent3(inode);
|
|
+ break;
|
|
+ default:
|
|
+ BUG();
|
|
+ }
|
|
+}
|
|
+
|
|
+void di_read_lock(struct dentry *d, int flags, unsigned int lsc)
|
|
+{
|
|
+ struct inode *inode;
|
|
+
|
|
+ au_rw_read_lock_nested(&au_di(d)->di_rwsem, lsc);
|
|
+ if (d_really_is_positive(d)) {
|
|
+ inode = d_inode(d);
|
|
+ if (au_ftest_lock(flags, IW))
|
|
+ do_ii_write_lock(inode, lsc);
|
|
+ else if (au_ftest_lock(flags, IR))
|
|
+ do_ii_read_lock(inode, lsc);
|
|
+ }
|
|
+}
|
|
+
|
|
+void di_read_unlock(struct dentry *d, int flags)
|
|
+{
|
|
+ struct inode *inode;
|
|
+
|
|
+ if (d_really_is_positive(d)) {
|
|
+ inode = d_inode(d);
|
|
+ if (au_ftest_lock(flags, IW)) {
|
|
+ au_dbg_verify_dinode(d);
|
|
+ ii_write_unlock(inode);
|
|
+ } else if (au_ftest_lock(flags, IR)) {
|
|
+ au_dbg_verify_dinode(d);
|
|
+ ii_read_unlock(inode);
|
|
+ }
|
|
+ }
|
|
+ au_rw_read_unlock(&au_di(d)->di_rwsem);
|
|
+}
|
|
+
|
|
+void di_downgrade_lock(struct dentry *d, int flags)
|
|
+{
|
|
+ if (d_really_is_positive(d) && au_ftest_lock(flags, IR))
|
|
+ ii_downgrade_lock(d_inode(d));
|
|
+ au_rw_dgrade_lock(&au_di(d)->di_rwsem);
|
|
+}
|
|
+
|
|
+void di_write_lock(struct dentry *d, unsigned int lsc)
|
|
+{
|
|
+ au_rw_write_lock_nested(&au_di(d)->di_rwsem, lsc);
|
|
+ if (d_really_is_positive(d))
|
|
+ do_ii_write_lock(d_inode(d), lsc);
|
|
+}
|
|
+
|
|
+void di_write_unlock(struct dentry *d)
|
|
+{
|
|
+ au_dbg_verify_dinode(d);
|
|
+ if (d_really_is_positive(d))
|
|
+ ii_write_unlock(d_inode(d));
|
|
+ au_rw_write_unlock(&au_di(d)->di_rwsem);
|
|
+}
|
|
+
|
|
+void di_write_lock2_child(struct dentry *d1, struct dentry *d2, int isdir)
|
|
+{
|
|
+ AuDebugOn(d1 == d2
|
|
+ || d_inode(d1) == d_inode(d2)
|
|
+ || d1->d_sb != d2->d_sb);
|
|
+
|
|
+ if ((isdir && au_test_subdir(d1, d2))
|
|
+ || d1 < d2) {
|
|
+ di_write_lock_child(d1);
|
|
+ di_write_lock_child2(d2);
|
|
+ } else {
|
|
+ di_write_lock_child(d2);
|
|
+ di_write_lock_child2(d1);
|
|
+ }
|
|
+}
|
|
+
|
|
+void di_write_lock2_parent(struct dentry *d1, struct dentry *d2, int isdir)
|
|
+{
|
|
+ AuDebugOn(d1 == d2
|
|
+ || d_inode(d1) == d_inode(d2)
|
|
+ || d1->d_sb != d2->d_sb);
|
|
+
|
|
+ if ((isdir && au_test_subdir(d1, d2))
|
|
+ || d1 < d2) {
|
|
+ di_write_lock_parent(d1);
|
|
+ di_write_lock_parent2(d2);
|
|
+ } else {
|
|
+ di_write_lock_parent(d2);
|
|
+ di_write_lock_parent2(d1);
|
|
+ }
|
|
+}
|
|
+
|
|
+void di_write_unlock2(struct dentry *d1, struct dentry *d2)
|
|
+{
|
|
+ di_write_unlock(d1);
|
|
+ if (d_inode(d1) == d_inode(d2))
|
|
+ au_rw_write_unlock(&au_di(d2)->di_rwsem);
|
|
+ else
|
|
+ di_write_unlock(d2);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct dentry *au_h_dptr(struct dentry *dentry, aufs_bindex_t bindex)
|
|
+{
|
|
+ struct dentry *d;
|
|
+
|
|
+ DiMustAnyLock(dentry);
|
|
+
|
|
+ if (au_dbtop(dentry) < 0 || bindex < au_dbtop(dentry))
|
|
+ return NULL;
|
|
+ AuDebugOn(bindex < 0);
|
|
+ d = au_hdentry(au_di(dentry), bindex)->hd_dentry;
|
|
+ AuDebugOn(d && au_dcount(d) <= 0);
|
|
+ return d;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * extended version of au_h_dptr().
|
|
+ * returns a hashed and positive (or linkable) h_dentry in bindex, NULL, or
|
|
+ * error.
|
|
+ */
|
|
+struct dentry *au_h_d_alias(struct dentry *dentry, aufs_bindex_t bindex)
|
|
+{
|
|
+ struct dentry *h_dentry;
|
|
+ struct inode *inode, *h_inode;
|
|
+
|
|
+ AuDebugOn(d_really_is_negative(dentry));
|
|
+
|
|
+ h_dentry = NULL;
|
|
+ if (au_dbtop(dentry) <= bindex
|
|
+ && bindex <= au_dbbot(dentry))
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (h_dentry && !au_d_linkable(h_dentry)) {
|
|
+ dget(h_dentry);
|
|
+ goto out; /* success */
|
|
+ }
|
|
+
|
|
+ inode = d_inode(dentry);
|
|
+ AuDebugOn(bindex < au_ibtop(inode));
|
|
+ AuDebugOn(au_ibbot(inode) < bindex);
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+ h_dentry = d_find_alias(h_inode);
|
|
+ if (h_dentry) {
|
|
+ if (!IS_ERR(h_dentry)) {
|
|
+ if (!au_d_linkable(h_dentry))
|
|
+ goto out; /* success */
|
|
+ dput(h_dentry);
|
|
+ } else
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (au_opt_test(au_mntflags(dentry->d_sb), PLINK)) {
|
|
+ h_dentry = au_plink_lkup(inode, bindex);
|
|
+ AuDebugOn(!h_dentry);
|
|
+ if (!IS_ERR(h_dentry)) {
|
|
+ if (!au_d_hashed_positive(h_dentry))
|
|
+ goto out; /* success */
|
|
+ dput(h_dentry);
|
|
+ h_dentry = NULL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+out:
|
|
+ AuDbgDentry(h_dentry);
|
|
+ return h_dentry;
|
|
+}
|
|
+
|
|
+aufs_bindex_t au_dbtail(struct dentry *dentry)
|
|
+{
|
|
+ aufs_bindex_t bbot, bwh;
|
|
+
|
|
+ bbot = au_dbbot(dentry);
|
|
+ if (0 <= bbot) {
|
|
+ bwh = au_dbwh(dentry);
|
|
+ if (!bwh)
|
|
+ return bwh;
|
|
+ if (0 < bwh && bwh < bbot)
|
|
+ return bwh - 1;
|
|
+ }
|
|
+ return bbot;
|
|
+}
|
|
+
|
|
+aufs_bindex_t au_dbtaildir(struct dentry *dentry)
|
|
+{
|
|
+ aufs_bindex_t bbot, bopq;
|
|
+
|
|
+ bbot = au_dbtail(dentry);
|
|
+ if (0 <= bbot) {
|
|
+ bopq = au_dbdiropq(dentry);
|
|
+ if (0 <= bopq && bopq < bbot)
|
|
+ bbot = bopq;
|
|
+ }
|
|
+ return bbot;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void au_set_h_dptr(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct dentry *h_dentry)
|
|
+{
|
|
+ struct au_dinfo *dinfo;
|
|
+ struct au_hdentry *hd;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ DiMustWriteLock(dentry);
|
|
+
|
|
+ dinfo = au_di(dentry);
|
|
+ hd = au_hdentry(dinfo, bindex);
|
|
+ au_hdput(hd);
|
|
+ hd->hd_dentry = h_dentry;
|
|
+ if (h_dentry) {
|
|
+ br = au_sbr(dentry->d_sb, bindex);
|
|
+ hd->hd_id = br->br_id;
|
|
+ }
|
|
+}
|
|
+
|
|
+int au_dbrange_test(struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t btop, bbot;
|
|
+
|
|
+ err = 0;
|
|
+ btop = au_dbtop(dentry);
|
|
+ bbot = au_dbbot(dentry);
|
|
+ if (btop >= 0)
|
|
+ AuDebugOn(bbot < 0 && btop > bbot);
|
|
+ else {
|
|
+ err = -EIO;
|
|
+ AuDebugOn(bbot >= 0);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_digen_test(struct dentry *dentry, unsigned int sigen)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (unlikely(au_digen(dentry) != sigen
|
|
+ || au_iigen_test(d_inode(dentry), sigen)))
|
|
+ err = -EIO;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_update_digen(struct dentry *dentry)
|
|
+{
|
|
+ atomic_set(&au_di(dentry)->di_generation, au_sigen(dentry->d_sb));
|
|
+ /* smp_mb(); */ /* atomic_set */
|
|
+}
|
|
+
|
|
+void au_update_dbrange(struct dentry *dentry, int do_put_zero)
|
|
+{
|
|
+ struct au_dinfo *dinfo;
|
|
+ struct dentry *h_d;
|
|
+ struct au_hdentry *hdp;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+
|
|
+ DiMustWriteLock(dentry);
|
|
+
|
|
+ dinfo = au_di(dentry);
|
|
+ if (!dinfo || dinfo->di_btop < 0)
|
|
+ return;
|
|
+
|
|
+ if (do_put_zero) {
|
|
+ bbot = dinfo->di_bbot;
|
|
+ bindex = dinfo->di_btop;
|
|
+ hdp = au_hdentry(dinfo, bindex);
|
|
+ for (; bindex <= bbot; bindex++, hdp++) {
|
|
+ h_d = hdp->hd_dentry;
|
|
+ if (h_d && d_is_negative(h_d))
|
|
+ au_set_h_dptr(dentry, bindex, NULL);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dinfo->di_btop = 0;
|
|
+ hdp = au_hdentry(dinfo, dinfo->di_btop);
|
|
+ for (; dinfo->di_btop <= dinfo->di_bbot; dinfo->di_btop++, hdp++)
|
|
+ if (hdp->hd_dentry)
|
|
+ break;
|
|
+ if (dinfo->di_btop > dinfo->di_bbot) {
|
|
+ dinfo->di_btop = -1;
|
|
+ dinfo->di_bbot = -1;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ hdp = au_hdentry(dinfo, dinfo->di_bbot);
|
|
+ for (; dinfo->di_bbot >= 0; dinfo->di_bbot--, hdp--)
|
|
+ if (hdp->hd_dentry)
|
|
+ break;
|
|
+ AuDebugOn(dinfo->di_btop > dinfo->di_bbot || dinfo->di_bbot < 0);
|
|
+}
|
|
+
|
|
+void au_update_dbtop(struct dentry *dentry)
|
|
+{
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct dentry *h_dentry;
|
|
+
|
|
+ bbot = au_dbbot(dentry);
|
|
+ for (bindex = au_dbtop(dentry); bindex <= bbot; bindex++) {
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (!h_dentry)
|
|
+ continue;
|
|
+ if (d_is_positive(h_dentry)) {
|
|
+ au_set_dbtop(dentry, bindex);
|
|
+ return;
|
|
+ }
|
|
+ au_set_h_dptr(dentry, bindex, NULL);
|
|
+ }
|
|
+}
|
|
+
|
|
+void au_update_dbbot(struct dentry *dentry)
|
|
+{
|
|
+ aufs_bindex_t bindex, btop;
|
|
+ struct dentry *h_dentry;
|
|
+
|
|
+ btop = au_dbtop(dentry);
|
|
+ for (bindex = au_dbbot(dentry); bindex >= btop; bindex--) {
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (!h_dentry)
|
|
+ continue;
|
|
+ if (d_is_positive(h_dentry)) {
|
|
+ au_set_dbbot(dentry, bindex);
|
|
+ return;
|
|
+ }
|
|
+ au_set_h_dptr(dentry, bindex, NULL);
|
|
+ }
|
|
+}
|
|
+
|
|
+int au_find_dbindex(struct dentry *dentry, struct dentry *h_dentry)
|
|
+{
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+
|
|
+ bbot = au_dbbot(dentry);
|
|
+ for (bindex = au_dbtop(dentry); bindex <= bbot; bindex++)
|
|
+ if (au_h_dptr(dentry, bindex) == h_dentry)
|
|
+ return bindex;
|
|
+ return -1;
|
|
+}
|
|
diff --git a/fs/aufs/dir.c b/fs/aufs/dir.c
|
|
new file mode 100644
|
|
index 000000000000..0bcb39ee7255
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dir.c
|
|
@@ -0,0 +1,763 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * directory operations
|
|
+ */
|
|
+
|
|
+#include <linux/fs_stack.h>
|
|
+#include <linux/iversion.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+void au_add_nlink(struct inode *dir, struct inode *h_dir)
|
|
+{
|
|
+ unsigned int nlink;
|
|
+
|
|
+ AuDebugOn(!S_ISDIR(dir->i_mode) || !S_ISDIR(h_dir->i_mode));
|
|
+
|
|
+ nlink = dir->i_nlink;
|
|
+ nlink += h_dir->i_nlink - 2;
|
|
+ if (h_dir->i_nlink < 2)
|
|
+ nlink += 2;
|
|
+ smp_mb(); /* for i_nlink */
|
|
+ /* 0 can happen in revaliding */
|
|
+ set_nlink(dir, nlink);
|
|
+}
|
|
+
|
|
+void au_sub_nlink(struct inode *dir, struct inode *h_dir)
|
|
+{
|
|
+ unsigned int nlink;
|
|
+
|
|
+ AuDebugOn(!S_ISDIR(dir->i_mode) || !S_ISDIR(h_dir->i_mode));
|
|
+
|
|
+ nlink = dir->i_nlink;
|
|
+ nlink -= h_dir->i_nlink - 2;
|
|
+ if (h_dir->i_nlink < 2)
|
|
+ nlink -= 2;
|
|
+ smp_mb(); /* for i_nlink */
|
|
+ /* nlink == 0 means the branch-fs is broken */
|
|
+ set_nlink(dir, nlink);
|
|
+}
|
|
+
|
|
+loff_t au_dir_size(struct file *file, struct dentry *dentry)
|
|
+{
|
|
+ loff_t sz;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct file *h_file;
|
|
+ struct dentry *h_dentry;
|
|
+
|
|
+ sz = 0;
|
|
+ if (file) {
|
|
+ AuDebugOn(!d_is_dir(file->f_path.dentry));
|
|
+
|
|
+ bbot = au_fbbot_dir(file);
|
|
+ for (bindex = au_fbtop(file);
|
|
+ bindex <= bbot && sz < KMALLOC_MAX_SIZE;
|
|
+ bindex++) {
|
|
+ h_file = au_hf_dir(file, bindex);
|
|
+ if (h_file && file_inode(h_file))
|
|
+ sz += vfsub_f_size_read(h_file);
|
|
+ }
|
|
+ } else {
|
|
+ AuDebugOn(!dentry);
|
|
+ AuDebugOn(!d_is_dir(dentry));
|
|
+
|
|
+ bbot = au_dbtaildir(dentry);
|
|
+ for (bindex = au_dbtop(dentry);
|
|
+ bindex <= bbot && sz < KMALLOC_MAX_SIZE;
|
|
+ bindex++) {
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (h_dentry && d_is_positive(h_dentry))
|
|
+ sz += i_size_read(d_inode(h_dentry));
|
|
+ }
|
|
+ }
|
|
+ if (sz < KMALLOC_MAX_SIZE)
|
|
+ sz = roundup_pow_of_two(sz);
|
|
+ if (sz > KMALLOC_MAX_SIZE)
|
|
+ sz = KMALLOC_MAX_SIZE;
|
|
+ else if (sz < NAME_MAX) {
|
|
+ BUILD_BUG_ON(AUFS_RDBLK_DEF < NAME_MAX);
|
|
+ sz = AUFS_RDBLK_DEF;
|
|
+ }
|
|
+ return sz;
|
|
+}
|
|
+
|
|
+struct au_dir_ts_arg {
|
|
+ struct dentry *dentry;
|
|
+ aufs_bindex_t brid;
|
|
+};
|
|
+
|
|
+static void au_do_dir_ts(void *arg)
|
|
+{
|
|
+ struct au_dir_ts_arg *a = arg;
|
|
+ struct au_dtime dt;
|
|
+ struct path h_path;
|
|
+ struct inode *dir, *h_dir;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ struct au_hinode *hdir;
|
|
+ int err;
|
|
+ aufs_bindex_t btop, bindex;
|
|
+
|
|
+ sb = a->dentry->d_sb;
|
|
+ if (d_really_is_negative(a->dentry))
|
|
+ goto out;
|
|
+ /* no dir->i_mutex lock */
|
|
+ aufs_read_lock(a->dentry, AuLock_DW); /* noflush */
|
|
+
|
|
+ dir = d_inode(a->dentry);
|
|
+ btop = au_ibtop(dir);
|
|
+ bindex = au_br_index(sb, a->brid);
|
|
+ if (bindex < btop)
|
|
+ goto out_unlock;
|
|
+
|
|
+ br = au_sbr(sb, bindex);
|
|
+ h_path.dentry = au_h_dptr(a->dentry, bindex);
|
|
+ if (!h_path.dentry)
|
|
+ goto out_unlock;
|
|
+ h_path.mnt = au_br_mnt(br);
|
|
+ au_dtime_store(&dt, a->dentry, &h_path);
|
|
+
|
|
+ br = au_sbr(sb, btop);
|
|
+ if (!au_br_writable(br->br_perm))
|
|
+ goto out_unlock;
|
|
+ h_path.dentry = au_h_dptr(a->dentry, btop);
|
|
+ h_path.mnt = au_br_mnt(br);
|
|
+ err = vfsub_mnt_want_write(h_path.mnt);
|
|
+ if (err)
|
|
+ goto out_unlock;
|
|
+ hdir = au_hi(dir, btop);
|
|
+ au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT);
|
|
+ h_dir = au_h_iptr(dir, btop);
|
|
+ if (h_dir->i_nlink
|
|
+ && timespec64_compare(&h_dir->i_mtime, &dt.dt_mtime) < 0) {
|
|
+ dt.dt_h_path = h_path;
|
|
+ au_dtime_revert(&dt);
|
|
+ }
|
|
+ au_hn_inode_unlock(hdir);
|
|
+ vfsub_mnt_drop_write(h_path.mnt);
|
|
+ au_cpup_attr_timesizes(dir);
|
|
+
|
|
+out_unlock:
|
|
+ aufs_read_unlock(a->dentry, AuLock_DW);
|
|
+out:
|
|
+ dput(a->dentry);
|
|
+ au_nwt_done(&au_sbi(sb)->si_nowait);
|
|
+ au_kfree_try_rcu(arg);
|
|
+}
|
|
+
|
|
+void au_dir_ts(struct inode *dir, aufs_bindex_t bindex)
|
|
+{
|
|
+ int perm, wkq_err;
|
|
+ aufs_bindex_t btop;
|
|
+ struct au_dir_ts_arg *arg;
|
|
+ struct dentry *dentry;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ dentry = d_find_any_alias(dir);
|
|
+ AuDebugOn(!dentry);
|
|
+ sb = dentry->d_sb;
|
|
+ btop = au_ibtop(dir);
|
|
+ if (btop == bindex) {
|
|
+ au_cpup_attr_timesizes(dir);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ perm = au_sbr_perm(sb, btop);
|
|
+ if (!au_br_writable(perm))
|
|
+ goto out;
|
|
+
|
|
+ arg = kmalloc(sizeof(*arg), GFP_NOFS);
|
|
+ if (!arg)
|
|
+ goto out;
|
|
+
|
|
+ arg->dentry = dget(dentry); /* will be dput-ted by au_do_dir_ts() */
|
|
+ arg->brid = au_sbr_id(sb, bindex);
|
|
+ wkq_err = au_wkq_nowait(au_do_dir_ts, arg, sb, /*flags*/0);
|
|
+ if (unlikely(wkq_err)) {
|
|
+ pr_err("wkq %d\n", wkq_err);
|
|
+ dput(dentry);
|
|
+ au_kfree_try_rcu(arg);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ dput(dentry);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int reopen_dir(struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int flags;
|
|
+ aufs_bindex_t bindex, btail, btop;
|
|
+ struct dentry *dentry, *h_dentry;
|
|
+ struct file *h_file;
|
|
+
|
|
+ /* open all lower dirs */
|
|
+ dentry = file->f_path.dentry;
|
|
+ btop = au_dbtop(dentry);
|
|
+ for (bindex = au_fbtop(file); bindex < btop; bindex++)
|
|
+ au_set_h_fptr(file, bindex, NULL);
|
|
+ au_set_fbtop(file, btop);
|
|
+
|
|
+ btail = au_dbtaildir(dentry);
|
|
+ for (bindex = au_fbbot_dir(file); btail < bindex; bindex--)
|
|
+ au_set_h_fptr(file, bindex, NULL);
|
|
+ au_set_fbbot_dir(file, btail);
|
|
+
|
|
+ flags = vfsub_file_flags(file);
|
|
+ for (bindex = btop; bindex <= btail; bindex++) {
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (!h_dentry)
|
|
+ continue;
|
|
+ h_file = au_hf_dir(file, bindex);
|
|
+ if (h_file)
|
|
+ continue;
|
|
+
|
|
+ h_file = au_h_open(dentry, bindex, flags, file, /*force_wr*/0);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out; /* close all? */
|
|
+ au_set_h_fptr(file, bindex, h_file);
|
|
+ }
|
|
+ au_update_figen(file);
|
|
+ /* todo: necessary? */
|
|
+ /* file->f_ra = h_file->f_ra; */
|
|
+ err = 0;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int do_open_dir(struct file *file, int flags, struct file *h_file)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, btail;
|
|
+ struct dentry *dentry, *h_dentry;
|
|
+ struct vfsmount *mnt;
|
|
+
|
|
+ FiMustWriteLock(file);
|
|
+ AuDebugOn(h_file);
|
|
+
|
|
+ err = 0;
|
|
+ mnt = file->f_path.mnt;
|
|
+ dentry = file->f_path.dentry;
|
|
+ file->f_version = inode_query_iversion(d_inode(dentry));
|
|
+ bindex = au_dbtop(dentry);
|
|
+ au_set_fbtop(file, bindex);
|
|
+ btail = au_dbtaildir(dentry);
|
|
+ au_set_fbbot_dir(file, btail);
|
|
+ for (; !err && bindex <= btail; bindex++) {
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (!h_dentry)
|
|
+ continue;
|
|
+
|
|
+ err = vfsub_test_mntns(mnt, h_dentry->d_sb);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ h_file = au_h_open(dentry, bindex, flags, file, /*force_wr*/0);
|
|
+ if (IS_ERR(h_file)) {
|
|
+ err = PTR_ERR(h_file);
|
|
+ break;
|
|
+ }
|
|
+ au_set_h_fptr(file, bindex, h_file);
|
|
+ }
|
|
+ au_update_figen(file);
|
|
+ /* todo: necessary? */
|
|
+ /* file->f_ra = h_file->f_ra; */
|
|
+ if (!err)
|
|
+ return 0; /* success */
|
|
+
|
|
+ /* close all */
|
|
+ for (bindex = au_fbtop(file); bindex <= btail; bindex++)
|
|
+ au_set_h_fptr(file, bindex, NULL);
|
|
+ au_set_fbtop(file, -1);
|
|
+ au_set_fbbot_dir(file, -1);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_open_dir(struct inode *inode __maybe_unused,
|
|
+ struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ struct super_block *sb;
|
|
+ struct au_fidir *fidir;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ sb = file->f_path.dentry->d_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH);
|
|
+ fidir = au_fidir_alloc(sb);
|
|
+ if (fidir) {
|
|
+ struct au_do_open_args args = {
|
|
+ .open = do_open_dir,
|
|
+ .fidir = fidir
|
|
+ };
|
|
+ err = au_do_open(file, &args);
|
|
+ if (unlikely(err))
|
|
+ au_kfree_rcu(fidir);
|
|
+ }
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_release_dir(struct inode *inode __maybe_unused,
|
|
+ struct file *file)
|
|
+{
|
|
+ struct au_vdir *vdir_cache;
|
|
+ struct au_finfo *finfo;
|
|
+ struct au_fidir *fidir;
|
|
+ struct au_hfile *hf;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+
|
|
+ finfo = au_fi(file);
|
|
+ fidir = finfo->fi_hdir;
|
|
+ if (fidir) {
|
|
+ au_hbl_del(&finfo->fi_hlist,
|
|
+ &au_sbi(file->f_path.dentry->d_sb)->si_files);
|
|
+ vdir_cache = fidir->fd_vdir_cache; /* lock-free */
|
|
+ if (vdir_cache)
|
|
+ au_vdir_free(vdir_cache);
|
|
+
|
|
+ bindex = finfo->fi_btop;
|
|
+ if (bindex >= 0) {
|
|
+ hf = fidir->fd_hfile + bindex;
|
|
+ /*
|
|
+ * calls fput() instead of filp_close(),
|
|
+ * since no dnotify or lock for the lower file.
|
|
+ */
|
|
+ bbot = fidir->fd_bbot;
|
|
+ for (; bindex <= bbot; bindex++, hf++)
|
|
+ if (hf->hf_file)
|
|
+ au_hfput(hf, /*execed*/0);
|
|
+ }
|
|
+ au_kfree_rcu(fidir);
|
|
+ finfo->fi_hdir = NULL;
|
|
+ }
|
|
+ au_finfo_fin(file);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_do_flush_dir(struct file *file, fl_owner_t id)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct file *h_file;
|
|
+
|
|
+ err = 0;
|
|
+ bbot = au_fbbot_dir(file);
|
|
+ for (bindex = au_fbtop(file); !err && bindex <= bbot; bindex++) {
|
|
+ h_file = au_hf_dir(file, bindex);
|
|
+ if (h_file)
|
|
+ err = vfsub_flush(h_file, id);
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_flush_dir(struct file *file, fl_owner_t id)
|
|
+{
|
|
+ return au_do_flush(file, id, au_do_flush_dir);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_do_fsync_dir_no_file(struct dentry *dentry, int datasync)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bbot, bindex;
|
|
+ struct inode *inode;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ err = 0;
|
|
+ sb = dentry->d_sb;
|
|
+ inode = d_inode(dentry);
|
|
+ IMustLock(inode);
|
|
+ bbot = au_dbbot(dentry);
|
|
+ for (bindex = au_dbtop(dentry); !err && bindex <= bbot; bindex++) {
|
|
+ struct path h_path;
|
|
+
|
|
+ if (au_test_ro(sb, bindex, inode))
|
|
+ continue;
|
|
+ h_path.dentry = au_h_dptr(dentry, bindex);
|
|
+ if (!h_path.dentry)
|
|
+ continue;
|
|
+
|
|
+ h_path.mnt = au_sbr_mnt(sb, bindex);
|
|
+ err = vfsub_fsync(NULL, &h_path, datasync);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_do_fsync_dir(struct file *file, int datasync)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bbot, bindex;
|
|
+ struct file *h_file;
|
|
+ struct super_block *sb;
|
|
+ struct inode *inode;
|
|
+
|
|
+ err = au_reval_and_lock_fdi(file, reopen_dir, /*wlock*/1, /*fi_lsc*/0);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ inode = file_inode(file);
|
|
+ sb = inode->i_sb;
|
|
+ bbot = au_fbbot_dir(file);
|
|
+ for (bindex = au_fbtop(file); !err && bindex <= bbot; bindex++) {
|
|
+ h_file = au_hf_dir(file, bindex);
|
|
+ if (!h_file || au_test_ro(sb, bindex, inode))
|
|
+ continue;
|
|
+
|
|
+ err = vfsub_fsync(h_file, &h_file->f_path, datasync);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * @file may be NULL
|
|
+ */
|
|
+static int aufs_fsync_dir(struct file *file, loff_t start, loff_t end,
|
|
+ int datasync)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *dentry;
|
|
+ struct inode *inode;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ err = 0;
|
|
+ dentry = file->f_path.dentry;
|
|
+ inode = d_inode(dentry);
|
|
+ inode_lock(inode);
|
|
+ sb = dentry->d_sb;
|
|
+ si_noflush_read_lock(sb);
|
|
+ if (file)
|
|
+ err = au_do_fsync_dir(file, datasync);
|
|
+ else {
|
|
+ di_write_lock_child(dentry);
|
|
+ err = au_do_fsync_dir_no_file(dentry, datasync);
|
|
+ }
|
|
+ au_cpup_attr_timesizes(inode);
|
|
+ di_write_unlock(dentry);
|
|
+ if (file)
|
|
+ fi_write_unlock(file);
|
|
+
|
|
+ si_read_unlock(sb);
|
|
+ inode_unlock(inode);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int aufs_iterate_shared(struct file *file, struct dir_context *ctx)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *dentry;
|
|
+ struct inode *inode, *h_inode;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ AuDbg("%pD, ctx{%ps, %llu}\n", file, ctx->actor, ctx->pos);
|
|
+
|
|
+ dentry = file->f_path.dentry;
|
|
+ inode = d_inode(dentry);
|
|
+ IMustLock(inode);
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH);
|
|
+ err = au_reval_and_lock_fdi(file, reopen_dir, /*wlock*/1, /*fi_lsc*/0);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = au_alive_dir(dentry);
|
|
+ if (!err)
|
|
+ err = au_vdir_init(file);
|
|
+ di_downgrade_lock(dentry, AuLock_IR);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+
|
|
+ h_inode = au_h_iptr(inode, au_ibtop(inode));
|
|
+ if (!au_test_nfsd()) {
|
|
+ err = au_vdir_fill_de(file, ctx);
|
|
+ fsstack_copy_attr_atime(inode, h_inode);
|
|
+ } else {
|
|
+ /*
|
|
+ * nfsd filldir may call lookup_one_len(), vfs_getattr(),
|
|
+ * encode_fh() and others.
|
|
+ */
|
|
+ atomic_inc(&h_inode->i_count);
|
|
+ di_read_unlock(dentry, AuLock_IR);
|
|
+ si_read_unlock(sb);
|
|
+ err = au_vdir_fill_de(file, ctx);
|
|
+ fsstack_copy_attr_atime(inode, h_inode);
|
|
+ fi_write_unlock(file);
|
|
+ iput(h_inode);
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+out_unlock:
|
|
+ di_read_unlock(dentry, AuLock_IR);
|
|
+ fi_write_unlock(file);
|
|
+out:
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#define AuTestEmpty_WHONLY 1
|
|
+#define AuTestEmpty_CALLED (1 << 1)
|
|
+#define AuTestEmpty_SHWH (1 << 2)
|
|
+#define au_ftest_testempty(flags, name) ((flags) & AuTestEmpty_##name)
|
|
+#define au_fset_testempty(flags, name) \
|
|
+ do { (flags) |= AuTestEmpty_##name; } while (0)
|
|
+#define au_fclr_testempty(flags, name) \
|
|
+ do { (flags) &= ~AuTestEmpty_##name; } while (0)
|
|
+
|
|
+#ifndef CONFIG_AUFS_SHWH
|
|
+#undef AuTestEmpty_SHWH
|
|
+#define AuTestEmpty_SHWH 0
|
|
+#endif
|
|
+
|
|
+struct test_empty_arg {
|
|
+ struct dir_context ctx;
|
|
+ struct au_nhash *whlist;
|
|
+ unsigned int flags;
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+};
|
|
+
|
|
+static int test_empty_cb(struct dir_context *ctx, const char *__name,
|
|
+ int namelen, loff_t offset __maybe_unused, u64 ino,
|
|
+ unsigned int d_type)
|
|
+{
|
|
+ struct test_empty_arg *arg = container_of(ctx, struct test_empty_arg,
|
|
+ ctx);
|
|
+ char *name = (void *)__name;
|
|
+
|
|
+ arg->err = 0;
|
|
+ au_fset_testempty(arg->flags, CALLED);
|
|
+ /* smp_mb(); */
|
|
+ if (name[0] == '.'
|
|
+ && (namelen == 1 || (name[1] == '.' && namelen == 2)))
|
|
+ goto out; /* success */
|
|
+
|
|
+ if (namelen <= AUFS_WH_PFX_LEN
|
|
+ || memcmp(name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) {
|
|
+ if (au_ftest_testempty(arg->flags, WHONLY)
|
|
+ && !au_nhash_test_known_wh(arg->whlist, name, namelen))
|
|
+ arg->err = -ENOTEMPTY;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ name += AUFS_WH_PFX_LEN;
|
|
+ namelen -= AUFS_WH_PFX_LEN;
|
|
+ if (!au_nhash_test_known_wh(arg->whlist, name, namelen))
|
|
+ arg->err = au_nhash_append_wh
|
|
+ (arg->whlist, name, namelen, ino, d_type, arg->bindex,
|
|
+ au_ftest_testempty(arg->flags, SHWH));
|
|
+
|
|
+out:
|
|
+ /* smp_mb(); */
|
|
+ AuTraceErr(arg->err);
|
|
+ return arg->err;
|
|
+}
|
|
+
|
|
+static int do_test_empty(struct dentry *dentry, struct test_empty_arg *arg)
|
|
+{
|
|
+ int err;
|
|
+ struct file *h_file;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ h_file = au_h_open(dentry, arg->bindex,
|
|
+ O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_LARGEFILE,
|
|
+ /*file*/NULL, /*force_wr*/0);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ if (!au_opt_test(au_mntflags(dentry->d_sb), UDBA_NONE)
|
|
+ && !file_inode(h_file)->i_nlink)
|
|
+ goto out_put;
|
|
+
|
|
+ do {
|
|
+ arg->err = 0;
|
|
+ au_fclr_testempty(arg->flags, CALLED);
|
|
+ /* smp_mb(); */
|
|
+ err = vfsub_iterate_dir(h_file, &arg->ctx);
|
|
+ if (err >= 0)
|
|
+ err = arg->err;
|
|
+ } while (!err && au_ftest_testempty(arg->flags, CALLED));
|
|
+
|
|
+out_put:
|
|
+ fput(h_file);
|
|
+ br = au_sbr(dentry->d_sb, arg->bindex);
|
|
+ au_lcnt_dec(&br->br_nfiles);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct do_test_empty_args {
|
|
+ int *errp;
|
|
+ struct dentry *dentry;
|
|
+ struct test_empty_arg *arg;
|
|
+};
|
|
+
|
|
+static void call_do_test_empty(void *args)
|
|
+{
|
|
+ struct do_test_empty_args *a = args;
|
|
+ *a->errp = do_test_empty(a->dentry, a->arg);
|
|
+}
|
|
+
|
|
+static int sio_test_empty(struct dentry *dentry, struct test_empty_arg *arg)
|
|
+{
|
|
+ int err, wkq_err;
|
|
+ struct dentry *h_dentry;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ h_dentry = au_h_dptr(dentry, arg->bindex);
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ /* todo: i_mode changes anytime? */
|
|
+ inode_lock_shared_nested(h_inode, AuLsc_I_CHILD);
|
|
+ err = au_test_h_perm_sio(h_inode, MAY_EXEC | MAY_READ);
|
|
+ inode_unlock_shared(h_inode);
|
|
+ if (!err)
|
|
+ err = do_test_empty(dentry, arg);
|
|
+ else {
|
|
+ struct do_test_empty_args args = {
|
|
+ .errp = &err,
|
|
+ .dentry = dentry,
|
|
+ .arg = arg
|
|
+ };
|
|
+ unsigned int flags = arg->flags;
|
|
+
|
|
+ wkq_err = au_wkq_wait(call_do_test_empty, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+ arg->flags = flags;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_test_empty_lower(struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int rdhash;
|
|
+ aufs_bindex_t bindex, btop, btail;
|
|
+ struct au_nhash whlist;
|
|
+ struct test_empty_arg arg = {
|
|
+ .ctx = {
|
|
+ .actor = test_empty_cb
|
|
+ }
|
|
+ };
|
|
+ int (*test_empty)(struct dentry *dentry, struct test_empty_arg *arg);
|
|
+
|
|
+ SiMustAnyLock(dentry->d_sb);
|
|
+
|
|
+ rdhash = au_sbi(dentry->d_sb)->si_rdhash;
|
|
+ if (!rdhash)
|
|
+ rdhash = au_rdhash_est(au_dir_size(/*file*/NULL, dentry));
|
|
+ err = au_nhash_alloc(&whlist, rdhash, GFP_NOFS);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ arg.flags = 0;
|
|
+ arg.whlist = &whlist;
|
|
+ btop = au_dbtop(dentry);
|
|
+ if (au_opt_test(au_mntflags(dentry->d_sb), SHWH))
|
|
+ au_fset_testempty(arg.flags, SHWH);
|
|
+ test_empty = do_test_empty;
|
|
+ if (au_opt_test(au_mntflags(dentry->d_sb), DIRPERM1))
|
|
+ test_empty = sio_test_empty;
|
|
+ arg.bindex = btop;
|
|
+ err = test_empty(dentry, &arg);
|
|
+ if (unlikely(err))
|
|
+ goto out_whlist;
|
|
+
|
|
+ au_fset_testempty(arg.flags, WHONLY);
|
|
+ btail = au_dbtaildir(dentry);
|
|
+ for (bindex = btop + 1; !err && bindex <= btail; bindex++) {
|
|
+ struct dentry *h_dentry;
|
|
+
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (h_dentry && d_is_positive(h_dentry)) {
|
|
+ arg.bindex = bindex;
|
|
+ err = test_empty(dentry, &arg);
|
|
+ }
|
|
+ }
|
|
+
|
|
+out_whlist:
|
|
+ au_nhash_wh_free(&whlist);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_test_empty(struct dentry *dentry, struct au_nhash *whlist)
|
|
+{
|
|
+ int err;
|
|
+ struct test_empty_arg arg = {
|
|
+ .ctx = {
|
|
+ .actor = test_empty_cb
|
|
+ }
|
|
+ };
|
|
+ aufs_bindex_t bindex, btail;
|
|
+
|
|
+ err = 0;
|
|
+ arg.whlist = whlist;
|
|
+ arg.flags = AuTestEmpty_WHONLY;
|
|
+ if (au_opt_test(au_mntflags(dentry->d_sb), SHWH))
|
|
+ au_fset_testempty(arg.flags, SHWH);
|
|
+ btail = au_dbtaildir(dentry);
|
|
+ for (bindex = au_dbtop(dentry); !err && bindex <= btail; bindex++) {
|
|
+ struct dentry *h_dentry;
|
|
+
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (h_dentry && d_is_positive(h_dentry)) {
|
|
+ arg.bindex = bindex;
|
|
+ err = sio_test_empty(dentry, &arg);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+const struct file_operations aufs_dir_fop = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .llseek = default_llseek,
|
|
+ .read = generic_read_dir,
|
|
+ .iterate_shared = aufs_iterate_shared,
|
|
+ .unlocked_ioctl = aufs_ioctl_dir,
|
|
+#ifdef CONFIG_COMPAT
|
|
+ .compat_ioctl = aufs_compat_ioctl_dir,
|
|
+#endif
|
|
+ .open = aufs_open_dir,
|
|
+ .release = aufs_release_dir,
|
|
+ .flush = aufs_flush_dir,
|
|
+ .fsync = aufs_fsync_dir
|
|
+};
|
|
diff --git a/fs/aufs/dir.h b/fs/aufs/dir.h
|
|
new file mode 100644
|
|
index 000000000000..e44c2a1a849b
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dir.h
|
|
@@ -0,0 +1,134 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * directory operations
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_DIR_H__
|
|
+#define __AUFS_DIR_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/fs.h>
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* need to be faster and smaller */
|
|
+
|
|
+struct au_nhash {
|
|
+ unsigned int nh_num;
|
|
+ struct hlist_head *nh_head;
|
|
+};
|
|
+
|
|
+struct au_vdir_destr {
|
|
+ unsigned char len;
|
|
+ unsigned char name[];
|
|
+} __packed;
|
|
+
|
|
+struct au_vdir_dehstr {
|
|
+ struct hlist_node hash;
|
|
+ struct au_vdir_destr *str;
|
|
+ struct rcu_head rcu;
|
|
+} ____cacheline_aligned_in_smp;
|
|
+
|
|
+struct au_vdir_de {
|
|
+ ino_t de_ino;
|
|
+ unsigned char de_type;
|
|
+ /* caution: packed */
|
|
+ struct au_vdir_destr de_str;
|
|
+} __packed;
|
|
+
|
|
+struct au_vdir_wh {
|
|
+ struct hlist_node wh_hash;
|
|
+#ifdef CONFIG_AUFS_SHWH
|
|
+ ino_t wh_ino;
|
|
+ aufs_bindex_t wh_bindex;
|
|
+ unsigned char wh_type;
|
|
+#else
|
|
+ aufs_bindex_t wh_bindex;
|
|
+#endif
|
|
+ /* caution: packed */
|
|
+ struct au_vdir_destr wh_str;
|
|
+} __packed;
|
|
+
|
|
+union au_vdir_deblk_p {
|
|
+ unsigned char *deblk;
|
|
+ struct au_vdir_de *de;
|
|
+};
|
|
+
|
|
+struct au_vdir {
|
|
+ unsigned char **vd_deblk;
|
|
+ unsigned long vd_nblk;
|
|
+ struct {
|
|
+ unsigned long ul;
|
|
+ union au_vdir_deblk_p p;
|
|
+ } vd_last;
|
|
+
|
|
+ u64 vd_version;
|
|
+ unsigned int vd_deblk_sz;
|
|
+ unsigned long vd_jiffy;
|
|
+ struct rcu_head rcu;
|
|
+} ____cacheline_aligned_in_smp;
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* dir.c */
|
|
+extern const struct file_operations aufs_dir_fop;
|
|
+void au_add_nlink(struct inode *dir, struct inode *h_dir);
|
|
+void au_sub_nlink(struct inode *dir, struct inode *h_dir);
|
|
+loff_t au_dir_size(struct file *file, struct dentry *dentry);
|
|
+void au_dir_ts(struct inode *dir, aufs_bindex_t bsrc);
|
|
+int au_test_empty_lower(struct dentry *dentry);
|
|
+int au_test_empty(struct dentry *dentry, struct au_nhash *whlist);
|
|
+
|
|
+/* vdir.c */
|
|
+unsigned int au_rdhash_est(loff_t sz);
|
|
+int au_nhash_alloc(struct au_nhash *nhash, unsigned int num_hash, gfp_t gfp);
|
|
+void au_nhash_wh_free(struct au_nhash *whlist);
|
|
+int au_nhash_test_longer_wh(struct au_nhash *whlist, aufs_bindex_t btgt,
|
|
+ int limit);
|
|
+int au_nhash_test_known_wh(struct au_nhash *whlist, char *name, int nlen);
|
|
+int au_nhash_append_wh(struct au_nhash *whlist, char *name, int nlen, ino_t ino,
|
|
+ unsigned int d_type, aufs_bindex_t bindex,
|
|
+ unsigned char shwh);
|
|
+void au_vdir_free(struct au_vdir *vdir);
|
|
+int au_vdir_init(struct file *file);
|
|
+int au_vdir_fill_de(struct file *file, struct dir_context *ctx);
|
|
+
|
|
+/* ioctl.c */
|
|
+long aufs_ioctl_dir(struct file *file, unsigned int cmd, unsigned long arg);
|
|
+
|
|
+#ifdef CONFIG_AUFS_RDU
|
|
+/* rdu.c */
|
|
+long au_rdu_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
|
|
+#ifdef CONFIG_COMPAT
|
|
+long au_rdu_compat_ioctl(struct file *file, unsigned int cmd,
|
|
+ unsigned long arg);
|
|
+#endif
|
|
+#else
|
|
+AuStub(long, au_rdu_ioctl, return -EINVAL, struct file *file,
|
|
+ unsigned int cmd, unsigned long arg)
|
|
+#ifdef CONFIG_COMPAT
|
|
+AuStub(long, au_rdu_compat_ioctl, return -EINVAL, struct file *file,
|
|
+ unsigned int cmd, unsigned long arg)
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_DIR_H__ */
|
|
diff --git a/fs/aufs/dirren.c b/fs/aufs/dirren.c
|
|
new file mode 100644
|
|
index 000000000000..ba4bf56cfb87
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dirren.c
|
|
@@ -0,0 +1,1316 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2017-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * special handling in renaming a directory
|
|
+ * in order to support looking-up the before-renamed name on the lower readonly
|
|
+ * branches
|
|
+ */
|
|
+
|
|
+#include <linux/byteorder/generic.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+static void au_dr_hino_del(struct au_dr_br *dr, struct au_dr_hino *ent)
|
|
+{
|
|
+ int idx;
|
|
+
|
|
+ idx = au_dr_ihash(ent->dr_h_ino);
|
|
+ au_hbl_del(&ent->dr_hnode, dr->dr_h_ino + idx);
|
|
+}
|
|
+
|
|
+static int au_dr_hino_test_empty(struct au_dr_br *dr)
|
|
+{
|
|
+ int ret, i;
|
|
+ struct hlist_bl_head *hbl;
|
|
+
|
|
+ ret = 1;
|
|
+ for (i = 0; ret && i < AuDirren_NHASH; i++) {
|
|
+ hbl = dr->dr_h_ino + i;
|
|
+ hlist_bl_lock(hbl);
|
|
+ ret &= hlist_bl_empty(hbl);
|
|
+ hlist_bl_unlock(hbl);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static struct au_dr_hino *au_dr_hino_find(struct au_dr_br *dr, ino_t ino)
|
|
+{
|
|
+ struct au_dr_hino *found, *ent;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos;
|
|
+ int idx;
|
|
+
|
|
+ found = NULL;
|
|
+ idx = au_dr_ihash(ino);
|
|
+ hbl = dr->dr_h_ino + idx;
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_for_each_entry(ent, pos, hbl, dr_hnode)
|
|
+ if (ent->dr_h_ino == ino) {
|
|
+ found = ent;
|
|
+ break;
|
|
+ }
|
|
+ hlist_bl_unlock(hbl);
|
|
+
|
|
+ return found;
|
|
+}
|
|
+
|
|
+int au_dr_hino_test_add(struct au_dr_br *dr, ino_t ino,
|
|
+ struct au_dr_hino *add_ent)
|
|
+{
|
|
+ int found, idx;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos;
|
|
+ struct au_dr_hino *ent;
|
|
+
|
|
+ found = 0;
|
|
+ idx = au_dr_ihash(ino);
|
|
+ hbl = dr->dr_h_ino + idx;
|
|
+#if 0 /* debug print */
|
|
+ {
|
|
+ struct hlist_bl_node *tmp;
|
|
+
|
|
+ hlist_bl_for_each_entry_safe(ent, pos, tmp, hbl, dr_hnode)
|
|
+ AuDbg("hi%llu\n", (unsigned long long)ent->dr_h_ino);
|
|
+ }
|
|
+#endif
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_for_each_entry(ent, pos, hbl, dr_hnode)
|
|
+ if (ent->dr_h_ino == ino) {
|
|
+ found = 1;
|
|
+ break;
|
|
+ }
|
|
+ if (!found && add_ent)
|
|
+ hlist_bl_add_head(&add_ent->dr_hnode, hbl);
|
|
+ hlist_bl_unlock(hbl);
|
|
+
|
|
+ if (!found && add_ent)
|
|
+ AuDbg("i%llu added\n", (unsigned long long)add_ent->dr_h_ino);
|
|
+
|
|
+ return found;
|
|
+}
|
|
+
|
|
+void au_dr_hino_free(struct au_dr_br *dr)
|
|
+{
|
|
+ int i;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos, *tmp;
|
|
+ struct au_dr_hino *ent;
|
|
+
|
|
+ /* SiMustWriteLock(sb); */
|
|
+
|
|
+ for (i = 0; i < AuDirren_NHASH; i++) {
|
|
+ hbl = dr->dr_h_ino + i;
|
|
+ /* no spinlock since sbinfo must be write-locked */
|
|
+ hlist_bl_for_each_entry_safe(ent, pos, tmp, hbl, dr_hnode)
|
|
+ au_kfree_rcu(ent);
|
|
+ INIT_HLIST_BL_HEAD(hbl);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* returns the number of inodes or an error */
|
|
+static int au_dr_hino_store(struct super_block *sb, struct au_branch *br,
|
|
+ struct file *hinofile)
|
|
+{
|
|
+ int err, i;
|
|
+ ssize_t ssz;
|
|
+ loff_t pos, oldsize;
|
|
+ __be64 u64;
|
|
+ struct inode *hinoinode;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *n1, *n2;
|
|
+ struct au_dr_hino *ent;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+ AuDebugOn(!au_br_writable(br->br_perm));
|
|
+
|
|
+ hinoinode = file_inode(hinofile);
|
|
+ oldsize = i_size_read(hinoinode);
|
|
+
|
|
+ err = 0;
|
|
+ pos = 0;
|
|
+ hbl = br->br_dirren.dr_h_ino;
|
|
+ for (i = 0; !err && i < AuDirren_NHASH; i++, hbl++) {
|
|
+ /* no bit-lock since sbinfo must be write-locked */
|
|
+ hlist_bl_for_each_entry_safe(ent, n1, n2, hbl, dr_hnode) {
|
|
+ AuDbg("hi%llu, %pD2\n",
|
|
+ (unsigned long long)ent->dr_h_ino, hinofile);
|
|
+ u64 = cpu_to_be64(ent->dr_h_ino);
|
|
+ ssz = vfsub_write_k(hinofile, &u64, sizeof(u64), &pos);
|
|
+ if (ssz == sizeof(u64))
|
|
+ continue;
|
|
+
|
|
+ /* write error */
|
|
+ pr_err("ssz %zd, %pD2\n", ssz, hinofile);
|
|
+ err = -ENOSPC;
|
|
+ if (ssz < 0)
|
|
+ err = ssz;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ /* regardless the error */
|
|
+ if (pos < oldsize) {
|
|
+ err = vfsub_trunc(&hinofile->f_path, pos, /*attr*/0, hinofile);
|
|
+ AuTraceErr(err);
|
|
+ }
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_dr_hino_load(struct au_dr_br *dr, struct file *hinofile)
|
|
+{
|
|
+ int err, hidx;
|
|
+ ssize_t ssz;
|
|
+ size_t sz, n;
|
|
+ loff_t pos;
|
|
+ uint64_t u64;
|
|
+ struct au_dr_hino *ent;
|
|
+ struct inode *hinoinode;
|
|
+ struct hlist_bl_head *hbl;
|
|
+
|
|
+ err = 0;
|
|
+ pos = 0;
|
|
+ hbl = dr->dr_h_ino;
|
|
+ hinoinode = file_inode(hinofile);
|
|
+ sz = i_size_read(hinoinode);
|
|
+ AuDebugOn(sz % sizeof(u64));
|
|
+ n = sz / sizeof(u64);
|
|
+ while (n--) {
|
|
+ ssz = vfsub_read_k(hinofile, &u64, sizeof(u64), &pos);
|
|
+ if (unlikely(ssz != sizeof(u64))) {
|
|
+ pr_err("ssz %zd, %pD2\n", ssz, hinofile);
|
|
+ err = -EINVAL;
|
|
+ if (ssz < 0)
|
|
+ err = ssz;
|
|
+ goto out_free;
|
|
+ }
|
|
+
|
|
+ ent = kmalloc(sizeof(*ent), GFP_NOFS);
|
|
+ if (!ent) {
|
|
+ err = -ENOMEM;
|
|
+ AuTraceErr(err);
|
|
+ goto out_free;
|
|
+ }
|
|
+ ent->dr_h_ino = be64_to_cpu((__force __be64)u64);
|
|
+ AuDbg("hi%llu, %pD2\n",
|
|
+ (unsigned long long)ent->dr_h_ino, hinofile);
|
|
+ hidx = au_dr_ihash(ent->dr_h_ino);
|
|
+ au_hbl_add(&ent->dr_hnode, hbl + hidx);
|
|
+ }
|
|
+ goto out; /* success */
|
|
+
|
|
+out_free:
|
|
+ au_dr_hino_free(dr);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * @bindex/@br is a switch to distinguish whether suspending hnotify or not.
|
|
+ * @path is a switch to distinguish load and store.
|
|
+ */
|
|
+static int au_dr_hino(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ struct au_branch *br, const struct path *path)
|
|
+{
|
|
+ int err, flags;
|
|
+ unsigned char load, suspend;
|
|
+ struct file *hinofile;
|
|
+ struct au_hinode *hdir;
|
|
+ struct inode *dir, *delegated;
|
|
+ struct path hinopath;
|
|
+ struct qstr hinoname = QSTR_INIT(AUFS_WH_DR_BRHINO,
|
|
+ sizeof(AUFS_WH_DR_BRHINO) - 1);
|
|
+
|
|
+ AuDebugOn(bindex < 0 && !br);
|
|
+ AuDebugOn(bindex >= 0 && br);
|
|
+
|
|
+ err = -EINVAL;
|
|
+ suspend = !br;
|
|
+ if (suspend)
|
|
+ br = au_sbr(sb, bindex);
|
|
+ load = !!path;
|
|
+ if (!load) {
|
|
+ path = &br->br_path;
|
|
+ AuDebugOn(!au_br_writable(br->br_perm));
|
|
+ if (unlikely(!au_br_writable(br->br_perm)))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ hdir = NULL;
|
|
+ if (suspend) {
|
|
+ dir = d_inode(sb->s_root);
|
|
+ hdir = au_hinode(au_ii(dir), bindex);
|
|
+ dir = hdir->hi_inode;
|
|
+ au_hn_inode_lock_nested(hdir, AuLsc_I_CHILD);
|
|
+ } else {
|
|
+ dir = d_inode(path->dentry);
|
|
+ inode_lock_nested(dir, AuLsc_I_CHILD);
|
|
+ }
|
|
+ hinopath.dentry = vfsub_lkup_one(&hinoname, path->dentry);
|
|
+ err = PTR_ERR(hinopath.dentry);
|
|
+ if (IS_ERR(hinopath.dentry))
|
|
+ goto out_unlock;
|
|
+
|
|
+ err = 0;
|
|
+ flags = O_RDONLY;
|
|
+ if (load) {
|
|
+ if (d_is_negative(hinopath.dentry))
|
|
+ goto out_dput; /* success */
|
|
+ } else {
|
|
+ if (au_dr_hino_test_empty(&br->br_dirren)) {
|
|
+ if (d_is_positive(hinopath.dentry)) {
|
|
+ delegated = NULL;
|
|
+ err = vfsub_unlink(dir, &hinopath, &delegated,
|
|
+ /*force*/0);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err))
|
|
+ pr_err("ignored err %d, %pd2\n",
|
|
+ err, hinopath.dentry);
|
|
+ if (unlikely(err == -EWOULDBLOCK))
|
|
+ iput(delegated);
|
|
+ err = 0;
|
|
+ }
|
|
+ goto out_dput;
|
|
+ } else if (!d_is_positive(hinopath.dentry)) {
|
|
+ err = vfsub_create(dir, &hinopath, 0600,
|
|
+ /*want_excl*/false);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err))
|
|
+ goto out_dput;
|
|
+ }
|
|
+ flags = O_WRONLY;
|
|
+ }
|
|
+ hinopath.mnt = path->mnt;
|
|
+ hinofile = vfsub_dentry_open(&hinopath, flags);
|
|
+ if (suspend)
|
|
+ au_hn_inode_unlock(hdir);
|
|
+ else
|
|
+ inode_unlock(dir);
|
|
+ dput(hinopath.dentry);
|
|
+ AuTraceErrPtr(hinofile);
|
|
+ if (IS_ERR(hinofile)) {
|
|
+ err = PTR_ERR(hinofile);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (load)
|
|
+ err = au_dr_hino_load(&br->br_dirren, hinofile);
|
|
+ else
|
|
+ err = au_dr_hino_store(sb, br, hinofile);
|
|
+ fput(hinofile);
|
|
+ goto out;
|
|
+
|
|
+out_dput:
|
|
+ dput(hinopath.dentry);
|
|
+out_unlock:
|
|
+ if (suspend)
|
|
+ au_hn_inode_unlock(hdir);
|
|
+ else
|
|
+ inode_unlock(dir);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_dr_brid_init(struct au_dr_brid *brid, const struct path *path)
|
|
+{
|
|
+ int err;
|
|
+ struct kstatfs kstfs;
|
|
+ dev_t dev;
|
|
+ struct dentry *dentry;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ err = vfs_statfs((void *)path, &kstfs);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ /* todo: support for UUID */
|
|
+
|
|
+ if (kstfs.f_fsid.val[0] || kstfs.f_fsid.val[1]) {
|
|
+ brid->type = AuBrid_FSID;
|
|
+ brid->fsid = kstfs.f_fsid;
|
|
+ } else {
|
|
+ dentry = path->dentry;
|
|
+ sb = dentry->d_sb;
|
|
+ dev = sb->s_dev;
|
|
+ if (dev) {
|
|
+ brid->type = AuBrid_DEV;
|
|
+ brid->dev = dev;
|
|
+ }
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_dr_br_init(struct super_block *sb, struct au_branch *br,
|
|
+ const struct path *path)
|
|
+{
|
|
+ int err, i;
|
|
+ struct au_dr_br *dr;
|
|
+ struct hlist_bl_head *hbl;
|
|
+
|
|
+ dr = &br->br_dirren;
|
|
+ hbl = dr->dr_h_ino;
|
|
+ for (i = 0; i < AuDirren_NHASH; i++, hbl++)
|
|
+ INIT_HLIST_BL_HEAD(hbl);
|
|
+
|
|
+ err = au_dr_brid_init(&dr->dr_brid, path);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ if (au_opt_test(au_mntflags(sb), DIRREN))
|
|
+ err = au_dr_hino(sb, /*bindex*/-1, br, path);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_dr_br_fin(struct super_block *sb, struct au_branch *br)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (au_br_writable(br->br_perm))
|
|
+ err = au_dr_hino(sb, /*bindex*/-1, br, /*path*/NULL);
|
|
+ if (!err)
|
|
+ au_dr_hino_free(&br->br_dirren);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_brid_str(struct au_dr_brid *brid, struct inode *h_inode,
|
|
+ char *buf, size_t sz)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int major, minor;
|
|
+ char *p;
|
|
+
|
|
+ p = buf;
|
|
+ err = snprintf(p, sz, "%d_", brid->type);
|
|
+ AuDebugOn(err > sz);
|
|
+ p += err;
|
|
+ sz -= err;
|
|
+ switch (brid->type) {
|
|
+ case AuBrid_Unset:
|
|
+ return -EINVAL;
|
|
+ case AuBrid_UUID:
|
|
+ err = snprintf(p, sz, "%pU", brid->uuid.b);
|
|
+ break;
|
|
+ case AuBrid_FSID:
|
|
+ err = snprintf(p, sz, "%08x-%08x",
|
|
+ brid->fsid.val[0], brid->fsid.val[1]);
|
|
+ break;
|
|
+ case AuBrid_DEV:
|
|
+ major = MAJOR(brid->dev);
|
|
+ minor = MINOR(brid->dev);
|
|
+ if (major <= 0xff && minor <= 0xff)
|
|
+ err = snprintf(p, sz, "%02x%02x", major, minor);
|
|
+ else
|
|
+ err = snprintf(p, sz, "%03x:%05x", major, minor);
|
|
+ break;
|
|
+ }
|
|
+ AuDebugOn(err > sz);
|
|
+ p += err;
|
|
+ sz -= err;
|
|
+ err = snprintf(p, sz, "_%llu", (unsigned long long)h_inode->i_ino);
|
|
+ AuDebugOn(err > sz);
|
|
+ p += err;
|
|
+ sz -= err;
|
|
+
|
|
+ return p - buf;
|
|
+}
|
|
+
|
|
+static int au_drinfo_name(struct au_branch *br, char *name, int len)
|
|
+{
|
|
+ int rlen;
|
|
+ struct dentry *br_dentry;
|
|
+ struct inode *br_inode;
|
|
+
|
|
+ br_dentry = au_br_dentry(br);
|
|
+ br_inode = d_inode(br_dentry);
|
|
+ rlen = au_brid_str(&br->br_dirren.dr_brid, br_inode, name, len);
|
|
+ AuDebugOn(rlen >= AUFS_DIRREN_ENV_VAL_SZ);
|
|
+ AuDebugOn(rlen > len);
|
|
+
|
|
+ return rlen;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * from the given @h_dentry, construct drinfo at @*fdata.
|
|
+ * when the size of @*fdata is not enough, reallocate and return new @fdata and
|
|
+ * @allocated.
|
|
+ */
|
|
+static int au_drinfo_construct(struct au_drinfo_fdata **fdata,
|
|
+ struct dentry *h_dentry,
|
|
+ unsigned char *allocated)
|
|
+{
|
|
+ int err, v;
|
|
+ struct au_drinfo_fdata *f, *p;
|
|
+ struct au_drinfo *drinfo;
|
|
+ struct inode *h_inode;
|
|
+ struct qstr *qname;
|
|
+
|
|
+ err = 0;
|
|
+ f = *fdata;
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ qname = &h_dentry->d_name;
|
|
+ drinfo = &f->drinfo;
|
|
+ drinfo->ino = (__force uint64_t)cpu_to_be64(h_inode->i_ino);
|
|
+ drinfo->oldnamelen = qname->len;
|
|
+ if (*allocated < sizeof(*f) + qname->len) {
|
|
+ v = roundup_pow_of_two(*allocated + qname->len);
|
|
+ p = au_krealloc(f, v, GFP_NOFS, /*may_shrink*/0);
|
|
+ if (unlikely(!p)) {
|
|
+ err = -ENOMEM;
|
|
+ AuTraceErr(err);
|
|
+ goto out;
|
|
+ }
|
|
+ f = p;
|
|
+ *fdata = f;
|
|
+ *allocated = v;
|
|
+ drinfo = &f->drinfo;
|
|
+ }
|
|
+ memcpy(drinfo->oldname, qname->name, qname->len);
|
|
+ AuDbg("i%llu, %.*s\n",
|
|
+ be64_to_cpu((__force __be64)drinfo->ino), drinfo->oldnamelen,
|
|
+ drinfo->oldname);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* callers have to free the return value */
|
|
+static struct au_drinfo *au_drinfo_read_k(struct file *file, ino_t h_ino)
|
|
+{
|
|
+ struct au_drinfo *ret, *drinfo;
|
|
+ struct au_drinfo_fdata fdata;
|
|
+ int len;
|
|
+ loff_t pos;
|
|
+ ssize_t ssz;
|
|
+
|
|
+ ret = ERR_PTR(-EIO);
|
|
+ pos = 0;
|
|
+ ssz = vfsub_read_k(file, &fdata, sizeof(fdata), &pos);
|
|
+ if (unlikely(ssz != sizeof(fdata))) {
|
|
+ AuIOErr("ssz %zd, %u, %pD2\n",
|
|
+ ssz, (unsigned int)sizeof(fdata), file);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ fdata.magic = ntohl((__force __be32)fdata.magic);
|
|
+ switch (fdata.magic) {
|
|
+ case AUFS_DRINFO_MAGIC_V1:
|
|
+ break;
|
|
+ default:
|
|
+ AuIOErr("magic-num 0x%x, 0x%x, %pD2\n",
|
|
+ fdata.magic, AUFS_DRINFO_MAGIC_V1, file);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ drinfo = &fdata.drinfo;
|
|
+ len = drinfo->oldnamelen;
|
|
+ if (!len) {
|
|
+ AuIOErr("broken drinfo %pD2\n", file);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ ret = NULL;
|
|
+ drinfo->ino = be64_to_cpu((__force __be64)drinfo->ino);
|
|
+ if (unlikely(h_ino && drinfo->ino != h_ino)) {
|
|
+ AuDbg("ignored i%llu, i%llu, %pD2\n",
|
|
+ (unsigned long long)drinfo->ino,
|
|
+ (unsigned long long)h_ino, file);
|
|
+ goto out; /* success */
|
|
+ }
|
|
+
|
|
+ ret = kmalloc(sizeof(*ret) + len, GFP_NOFS);
|
|
+ if (unlikely(!ret)) {
|
|
+ ret = ERR_PTR(-ENOMEM);
|
|
+ AuTraceErrPtr(ret);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ *ret = *drinfo;
|
|
+ ssz = vfsub_read_k(file, (void *)ret->oldname, len, &pos);
|
|
+ if (unlikely(ssz != len)) {
|
|
+ au_kfree_rcu(ret);
|
|
+ ret = ERR_PTR(-EIO);
|
|
+ AuIOErr("ssz %zd, %u, %pD2\n", ssz, len, file);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ AuDbg("oldname %.*s\n", ret->oldnamelen, ret->oldname);
|
|
+
|
|
+out:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* in order to be revertible */
|
|
+struct au_drinfo_rev_elm {
|
|
+ int created;
|
|
+ struct dentry *info_dentry;
|
|
+ struct au_drinfo *info_last;
|
|
+};
|
|
+
|
|
+struct au_drinfo_rev {
|
|
+ unsigned char already;
|
|
+ aufs_bindex_t nelm;
|
|
+ struct au_drinfo_rev_elm elm[];
|
|
+};
|
|
+
|
|
+/* todo: isn't it too large? */
|
|
+struct au_drinfo_store {
|
|
+ struct path h_ppath;
|
|
+ struct dentry *h_dentry;
|
|
+ struct au_drinfo_fdata *fdata;
|
|
+ char *infoname; /* inside of whname, just after PFX */
|
|
+ char whname[sizeof(AUFS_WH_DR_INFO_PFX) + AUFS_DIRREN_ENV_VAL_SZ];
|
|
+ aufs_bindex_t btgt, btail;
|
|
+ unsigned char no_sio,
|
|
+ allocated, /* current size of *fdata */
|
|
+ infonamelen, /* room size for p */
|
|
+ whnamelen, /* length of the generated name */
|
|
+ renameback; /* renamed back */
|
|
+};
|
|
+
|
|
+/* on rename(2) error, the caller should revert it using @elm */
|
|
+static int au_drinfo_do_store(struct au_drinfo_store *w,
|
|
+ struct au_drinfo_rev_elm *elm)
|
|
+{
|
|
+ int err, len;
|
|
+ ssize_t ssz;
|
|
+ loff_t pos;
|
|
+ struct path infopath = {
|
|
+ .mnt = w->h_ppath.mnt
|
|
+ };
|
|
+ struct inode *h_dir, *h_inode, *delegated;
|
|
+ struct file *infofile;
|
|
+ struct qstr *qname;
|
|
+
|
|
+ AuDebugOn(elm
|
|
+ && memcmp(elm, page_address(ZERO_PAGE(0)), sizeof(*elm)));
|
|
+
|
|
+ infopath.dentry = vfsub_lookup_one_len(w->whname, w->h_ppath.dentry,
|
|
+ w->whnamelen);
|
|
+ AuTraceErrPtr(infopath.dentry);
|
|
+ if (IS_ERR(infopath.dentry)) {
|
|
+ err = PTR_ERR(infopath.dentry);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ h_dir = d_inode(w->h_ppath.dentry);
|
|
+ if (elm && d_is_negative(infopath.dentry)) {
|
|
+ err = vfsub_create(h_dir, &infopath, 0600, /*want_excl*/true);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err))
|
|
+ goto out_dput;
|
|
+ elm->created = 1;
|
|
+ elm->info_dentry = dget(infopath.dentry);
|
|
+ }
|
|
+
|
|
+ infofile = vfsub_dentry_open(&infopath, O_RDWR);
|
|
+ AuTraceErrPtr(infofile);
|
|
+ if (IS_ERR(infofile)) {
|
|
+ err = PTR_ERR(infofile);
|
|
+ goto out_dput;
|
|
+ }
|
|
+
|
|
+ h_inode = d_inode(infopath.dentry);
|
|
+ if (elm && i_size_read(h_inode)) {
|
|
+ h_inode = d_inode(w->h_dentry);
|
|
+ elm->info_last = au_drinfo_read_k(infofile, h_inode->i_ino);
|
|
+ AuTraceErrPtr(elm->info_last);
|
|
+ if (IS_ERR(elm->info_last)) {
|
|
+ err = PTR_ERR(elm->info_last);
|
|
+ elm->info_last = NULL;
|
|
+ AuDebugOn(elm->info_dentry);
|
|
+ goto out_fput;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (elm && w->renameback) {
|
|
+ delegated = NULL;
|
|
+ err = vfsub_unlink(h_dir, &infopath, &delegated, /*force*/0);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err == -EWOULDBLOCK))
|
|
+ iput(delegated);
|
|
+ goto out_fput;
|
|
+ }
|
|
+
|
|
+ pos = 0;
|
|
+ qname = &w->h_dentry->d_name;
|
|
+ len = sizeof(*w->fdata) + qname->len;
|
|
+ if (!elm)
|
|
+ len = sizeof(*w->fdata) + w->fdata->drinfo.oldnamelen;
|
|
+ ssz = vfsub_write_k(infofile, w->fdata, len, &pos);
|
|
+ if (ssz == len) {
|
|
+ AuDbg("hi%llu, %.*s\n", w->fdata->drinfo.ino,
|
|
+ w->fdata->drinfo.oldnamelen, w->fdata->drinfo.oldname);
|
|
+ goto out_fput; /* success */
|
|
+ } else {
|
|
+ err = -EIO;
|
|
+ if (ssz < 0)
|
|
+ err = ssz;
|
|
+ /* the caller should revert it using @elm */
|
|
+ }
|
|
+
|
|
+out_fput:
|
|
+ fput(infofile);
|
|
+out_dput:
|
|
+ dput(infopath.dentry);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct au_call_drinfo_do_store_args {
|
|
+ int *errp;
|
|
+ struct au_drinfo_store *w;
|
|
+ struct au_drinfo_rev_elm *elm;
|
|
+};
|
|
+
|
|
+static void au_call_drinfo_do_store(void *args)
|
|
+{
|
|
+ struct au_call_drinfo_do_store_args *a = args;
|
|
+
|
|
+ *a->errp = au_drinfo_do_store(a->w, a->elm);
|
|
+}
|
|
+
|
|
+static int au_drinfo_store_sio(struct au_drinfo_store *w,
|
|
+ struct au_drinfo_rev_elm *elm)
|
|
+{
|
|
+ int err, wkq_err;
|
|
+
|
|
+ if (w->no_sio)
|
|
+ err = au_drinfo_do_store(w, elm);
|
|
+ else {
|
|
+ struct au_call_drinfo_do_store_args a = {
|
|
+ .errp = &err,
|
|
+ .w = w,
|
|
+ .elm = elm
|
|
+ };
|
|
+ wkq_err = au_wkq_wait(au_call_drinfo_do_store, &a);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+ }
|
|
+ AuTraceErr(err);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_drinfo_store_work_init(struct au_drinfo_store *w,
|
|
+ aufs_bindex_t btgt)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ memset(w, 0, sizeof(*w));
|
|
+ w->allocated = roundup_pow_of_two(sizeof(*w->fdata) + 40);
|
|
+ strcpy(w->whname, AUFS_WH_DR_INFO_PFX);
|
|
+ w->infoname = w->whname + sizeof(AUFS_WH_DR_INFO_PFX) - 1;
|
|
+ w->infonamelen = sizeof(w->whname) - sizeof(AUFS_WH_DR_INFO_PFX);
|
|
+ w->btgt = btgt;
|
|
+ w->no_sio = !!uid_eq(current_fsuid(), GLOBAL_ROOT_UID);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ w->fdata = kcalloc(1, w->allocated, GFP_NOFS);
|
|
+ if (unlikely(!w->fdata)) {
|
|
+ AuTraceErr(err);
|
|
+ goto out;
|
|
+ }
|
|
+ w->fdata->magic = (__force uint32_t)htonl(AUFS_DRINFO_MAGIC_V1);
|
|
+ err = 0;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_drinfo_store_work_fin(struct au_drinfo_store *w)
|
|
+{
|
|
+ au_kfree_rcu(w->fdata);
|
|
+}
|
|
+
|
|
+static void au_drinfo_store_rev(struct au_drinfo_rev *rev,
|
|
+ struct au_drinfo_store *w)
|
|
+{
|
|
+ struct au_drinfo_rev_elm *elm;
|
|
+ struct inode *h_dir, *delegated;
|
|
+ int err, nelm;
|
|
+ struct path infopath = {
|
|
+ .mnt = w->h_ppath.mnt
|
|
+ };
|
|
+
|
|
+ h_dir = d_inode(w->h_ppath.dentry);
|
|
+ IMustLock(h_dir);
|
|
+
|
|
+ err = 0;
|
|
+ elm = rev->elm;
|
|
+ for (nelm = rev->nelm; nelm > 0; nelm--, elm++) {
|
|
+ AuDebugOn(elm->created && elm->info_last);
|
|
+ if (elm->created) {
|
|
+ AuDbg("here\n");
|
|
+ delegated = NULL;
|
|
+ infopath.dentry = elm->info_dentry;
|
|
+ err = vfsub_unlink(h_dir, &infopath, &delegated,
|
|
+ !w->no_sio);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err == -EWOULDBLOCK))
|
|
+ iput(delegated);
|
|
+ dput(elm->info_dentry);
|
|
+ } else if (elm->info_last) {
|
|
+ AuDbg("here\n");
|
|
+ w->fdata->drinfo = *elm->info_last;
|
|
+ memcpy(w->fdata->drinfo.oldname,
|
|
+ elm->info_last->oldname,
|
|
+ elm->info_last->oldnamelen);
|
|
+ err = au_drinfo_store_sio(w, /*elm*/NULL);
|
|
+ au_kfree_rcu(elm->info_last);
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ AuIOErr("%d, %s\n", err, w->whname);
|
|
+ /* go on even if err */
|
|
+ }
|
|
+}
|
|
+
|
|
+/* caller has to call au_dr_rename_fin() later */
|
|
+static int au_drinfo_store(struct dentry *dentry, aufs_bindex_t btgt,
|
|
+ struct qstr *dst_name, void *_rev)
|
|
+{
|
|
+ int err, sz, nelm;
|
|
+ aufs_bindex_t bindex, btail;
|
|
+ struct au_drinfo_store work;
|
|
+ struct au_drinfo_rev *rev, **p;
|
|
+ struct au_drinfo_rev_elm *elm;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ struct au_hinode *hdir;
|
|
+
|
|
+ err = au_drinfo_store_work_init(&work, btgt);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ btail = au_dbtaildir(dentry);
|
|
+ nelm = btail - btgt;
|
|
+ sz = sizeof(*rev) + sizeof(*elm) * nelm;
|
|
+ rev = kcalloc(1, sz, GFP_NOFS);
|
|
+ if (unlikely(!rev)) {
|
|
+ AuTraceErr(err);
|
|
+ goto out_args;
|
|
+ }
|
|
+ rev->nelm = nelm;
|
|
+ elm = rev->elm;
|
|
+ p = _rev;
|
|
+ *p = rev;
|
|
+
|
|
+ err = 0;
|
|
+ sb = dentry->d_sb;
|
|
+ work.h_ppath.dentry = au_h_dptr(dentry, btgt);
|
|
+ work.h_ppath.mnt = au_sbr_mnt(sb, btgt);
|
|
+ hdir = au_hi(d_inode(dentry), btgt);
|
|
+ au_hn_inode_lock_nested(hdir, AuLsc_I_CHILD);
|
|
+ for (bindex = btgt + 1; bindex <= btail; bindex++, elm++) {
|
|
+ work.h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (!work.h_dentry)
|
|
+ continue;
|
|
+
|
|
+ err = au_drinfo_construct(&work.fdata, work.h_dentry,
|
|
+ &work.allocated);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+
|
|
+ work.renameback = au_qstreq(&work.h_dentry->d_name, dst_name);
|
|
+ br = au_sbr(sb, bindex);
|
|
+ work.whnamelen = sizeof(AUFS_WH_DR_INFO_PFX) - 1;
|
|
+ work.whnamelen += au_drinfo_name(br, work.infoname,
|
|
+ work.infonamelen);
|
|
+ AuDbg("whname %.*s, i%llu, %.*s\n",
|
|
+ work.whnamelen, work.whname,
|
|
+ be64_to_cpu((__force __be64)work.fdata->drinfo.ino),
|
|
+ work.fdata->drinfo.oldnamelen,
|
|
+ work.fdata->drinfo.oldname);
|
|
+
|
|
+ err = au_drinfo_store_sio(&work, elm);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ }
|
|
+ if (unlikely(err)) {
|
|
+ /* revert all drinfo */
|
|
+ au_drinfo_store_rev(rev, &work);
|
|
+ au_kfree_try_rcu(rev);
|
|
+ *p = NULL;
|
|
+ }
|
|
+ au_hn_inode_unlock(hdir);
|
|
+
|
|
+out_args:
|
|
+ au_drinfo_store_work_fin(&work);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_dr_rename(struct dentry *src, aufs_bindex_t bindex,
|
|
+ struct qstr *dst_name, void *_rev)
|
|
+{
|
|
+ int err, already;
|
|
+ ino_t ino;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ struct au_dr_br *dr;
|
|
+ struct dentry *h_dentry;
|
|
+ struct inode *h_inode;
|
|
+ struct au_dr_hino *ent;
|
|
+ struct au_drinfo_rev *rev, **p;
|
|
+
|
|
+ AuDbg("bindex %d\n", bindex);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ ent = kmalloc(sizeof(*ent), GFP_NOFS);
|
|
+ if (unlikely(!ent))
|
|
+ goto out;
|
|
+
|
|
+ sb = src->d_sb;
|
|
+ br = au_sbr(sb, bindex);
|
|
+ dr = &br->br_dirren;
|
|
+ h_dentry = au_h_dptr(src, bindex);
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ ino = h_inode->i_ino;
|
|
+ ent->dr_h_ino = ino;
|
|
+ already = au_dr_hino_test_add(dr, ino, ent);
|
|
+ AuDbg("b%d, hi%llu, already %d\n",
|
|
+ bindex, (unsigned long long)ino, already);
|
|
+
|
|
+ err = au_drinfo_store(src, bindex, dst_name, _rev);
|
|
+ AuTraceErr(err);
|
|
+ if (!err) {
|
|
+ p = _rev;
|
|
+ rev = *p;
|
|
+ rev->already = already;
|
|
+ goto out; /* success */
|
|
+ }
|
|
+
|
|
+ /* revert */
|
|
+ if (!already)
|
|
+ au_dr_hino_del(dr, ent);
|
|
+ au_kfree_rcu(ent);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_dr_rename_fin(struct dentry *src, aufs_bindex_t btgt, void *_rev)
|
|
+{
|
|
+ struct au_drinfo_rev *rev;
|
|
+ struct au_drinfo_rev_elm *elm;
|
|
+ int nelm;
|
|
+
|
|
+ rev = _rev;
|
|
+ elm = rev->elm;
|
|
+ for (nelm = rev->nelm; nelm > 0; nelm--, elm++) {
|
|
+ dput(elm->info_dentry);
|
|
+ au_kfree_rcu(elm->info_last);
|
|
+ }
|
|
+ au_kfree_try_rcu(rev);
|
|
+}
|
|
+
|
|
+void au_dr_rename_rev(struct dentry *src, aufs_bindex_t btgt, void *_rev)
|
|
+{
|
|
+ int err;
|
|
+ struct au_drinfo_store work;
|
|
+ struct au_drinfo_rev *rev = _rev;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ struct inode *h_inode;
|
|
+ struct au_dr_br *dr;
|
|
+ struct au_dr_hino *ent;
|
|
+
|
|
+ err = au_drinfo_store_work_init(&work, btgt);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ sb = src->d_sb;
|
|
+ br = au_sbr(sb, btgt);
|
|
+ work.h_ppath.dentry = au_h_dptr(src, btgt);
|
|
+ work.h_ppath.mnt = au_br_mnt(br);
|
|
+ au_drinfo_store_rev(rev, &work);
|
|
+ au_drinfo_store_work_fin(&work);
|
|
+ if (rev->already)
|
|
+ goto out;
|
|
+
|
|
+ dr = &br->br_dirren;
|
|
+ h_inode = d_inode(work.h_ppath.dentry);
|
|
+ ent = au_dr_hino_find(dr, h_inode->i_ino);
|
|
+ BUG_ON(!ent);
|
|
+ au_dr_hino_del(dr, ent);
|
|
+ au_kfree_rcu(ent);
|
|
+
|
|
+out:
|
|
+ au_kfree_try_rcu(rev);
|
|
+ if (unlikely(err))
|
|
+ pr_err("failed to remove dirren info\n");
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static struct au_drinfo *au_drinfo_do_load(struct path *h_ppath,
|
|
+ char *whname, int whnamelen,
|
|
+ struct dentry **info_dentry)
|
|
+{
|
|
+ struct au_drinfo *drinfo;
|
|
+ struct file *f;
|
|
+ struct inode *h_dir;
|
|
+ struct path infopath;
|
|
+ int unlocked;
|
|
+
|
|
+ AuDbg("%pd/%.*s\n", h_ppath->dentry, whnamelen, whname);
|
|
+
|
|
+ *info_dentry = NULL;
|
|
+ drinfo = NULL;
|
|
+ unlocked = 0;
|
|
+ h_dir = d_inode(h_ppath->dentry);
|
|
+ inode_lock_shared_nested(h_dir, AuLsc_I_PARENT);
|
|
+ infopath.dentry = vfsub_lookup_one_len(whname, h_ppath->dentry,
|
|
+ whnamelen);
|
|
+ if (IS_ERR(infopath.dentry)) {
|
|
+ drinfo = (void *)infopath.dentry;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (d_is_negative(infopath.dentry))
|
|
+ goto out_dput; /* success */
|
|
+
|
|
+ infopath.mnt = h_ppath->mnt;
|
|
+ f = vfsub_dentry_open(&infopath, O_RDONLY);
|
|
+ inode_unlock_shared(h_dir);
|
|
+ unlocked = 1;
|
|
+ if (IS_ERR(f)) {
|
|
+ drinfo = (void *)f;
|
|
+ goto out_dput;
|
|
+ }
|
|
+
|
|
+ drinfo = au_drinfo_read_k(f, /*h_ino*/0);
|
|
+ if (IS_ERR_OR_NULL(drinfo))
|
|
+ goto out_fput;
|
|
+
|
|
+ AuDbg("oldname %.*s\n", drinfo->oldnamelen, drinfo->oldname);
|
|
+ *info_dentry = dget(infopath.dentry); /* keep it alive */
|
|
+
|
|
+out_fput:
|
|
+ fput(f);
|
|
+out_dput:
|
|
+ dput(infopath.dentry);
|
|
+out:
|
|
+ if (!unlocked)
|
|
+ inode_unlock_shared(h_dir);
|
|
+ AuTraceErrPtr(drinfo);
|
|
+ return drinfo;
|
|
+}
|
|
+
|
|
+struct au_drinfo_do_load_args {
|
|
+ struct au_drinfo **drinfop;
|
|
+ struct path *h_ppath;
|
|
+ char *whname;
|
|
+ int whnamelen;
|
|
+ struct dentry **info_dentry;
|
|
+};
|
|
+
|
|
+static void au_call_drinfo_do_load(void *args)
|
|
+{
|
|
+ struct au_drinfo_do_load_args *a = args;
|
|
+
|
|
+ *a->drinfop = au_drinfo_do_load(a->h_ppath, a->whname, a->whnamelen,
|
|
+ a->info_dentry);
|
|
+}
|
|
+
|
|
+struct au_drinfo_load {
|
|
+ struct path h_ppath;
|
|
+ struct qstr *qname;
|
|
+ unsigned char no_sio;
|
|
+
|
|
+ aufs_bindex_t ninfo;
|
|
+ struct au_drinfo **drinfo;
|
|
+};
|
|
+
|
|
+static int au_drinfo_load(struct au_drinfo_load *w, aufs_bindex_t bindex,
|
|
+ struct au_branch *br)
|
|
+{
|
|
+ int err, wkq_err, whnamelen, e;
|
|
+ char whname[sizeof(AUFS_WH_DR_INFO_PFX) + AUFS_DIRREN_ENV_VAL_SZ]
|
|
+ = AUFS_WH_DR_INFO_PFX;
|
|
+ struct au_drinfo *drinfo;
|
|
+ struct qstr oldname;
|
|
+ struct inode *h_dir, *delegated;
|
|
+ struct dentry *info_dentry;
|
|
+ struct path infopath;
|
|
+
|
|
+ whnamelen = sizeof(AUFS_WH_DR_INFO_PFX) - 1;
|
|
+ whnamelen += au_drinfo_name(br, whname + whnamelen,
|
|
+ sizeof(whname) - whnamelen);
|
|
+ if (w->no_sio)
|
|
+ drinfo = au_drinfo_do_load(&w->h_ppath, whname, whnamelen,
|
|
+ &info_dentry);
|
|
+ else {
|
|
+ struct au_drinfo_do_load_args args = {
|
|
+ .drinfop = &drinfo,
|
|
+ .h_ppath = &w->h_ppath,
|
|
+ .whname = whname,
|
|
+ .whnamelen = whnamelen,
|
|
+ .info_dentry = &info_dentry
|
|
+ };
|
|
+ wkq_err = au_wkq_wait(au_call_drinfo_do_load, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ drinfo = ERR_PTR(wkq_err);
|
|
+ }
|
|
+ err = PTR_ERR(drinfo);
|
|
+ if (IS_ERR_OR_NULL(drinfo))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ oldname.len = drinfo->oldnamelen;
|
|
+ oldname.name = drinfo->oldname;
|
|
+ if (au_qstreq(w->qname, &oldname)) {
|
|
+ /* the name is renamed back */
|
|
+ au_kfree_rcu(drinfo);
|
|
+ drinfo = NULL;
|
|
+
|
|
+ infopath.dentry = info_dentry;
|
|
+ infopath.mnt = w->h_ppath.mnt;
|
|
+ h_dir = d_inode(w->h_ppath.dentry);
|
|
+ delegated = NULL;
|
|
+ inode_lock_nested(h_dir, AuLsc_I_PARENT);
|
|
+ e = vfsub_unlink(h_dir, &infopath, &delegated, !w->no_sio);
|
|
+ inode_unlock(h_dir);
|
|
+ if (unlikely(e))
|
|
+ AuIOErr("ignored %d, %pd2\n", e, &infopath.dentry);
|
|
+ if (unlikely(e == -EWOULDBLOCK))
|
|
+ iput(delegated);
|
|
+ }
|
|
+ au_kfree_rcu(w->drinfo[bindex]);
|
|
+ w->drinfo[bindex] = drinfo;
|
|
+ dput(info_dentry);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static void au_dr_lkup_free(struct au_drinfo **drinfo, int n)
|
|
+{
|
|
+ struct au_drinfo **p = drinfo;
|
|
+
|
|
+ while (n-- > 0)
|
|
+ au_kfree_rcu(*drinfo++);
|
|
+ au_kfree_try_rcu(p);
|
|
+}
|
|
+
|
|
+int au_dr_lkup(struct au_do_lookup_args *lkup, struct dentry *dentry,
|
|
+ aufs_bindex_t btgt)
|
|
+{
|
|
+ int err, ninfo;
|
|
+ struct au_drinfo_load w;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_branch *br;
|
|
+ struct inode *h_dir;
|
|
+ struct au_dr_hino *ent;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ AuDbg("%.*s, name %.*s, whname %.*s, b%d\n",
|
|
+ AuLNPair(&dentry->d_name), AuLNPair(&lkup->dirren.dr_name),
|
|
+ AuLNPair(&lkup->whname), btgt);
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ bbot = au_sbbot(sb);
|
|
+ w.ninfo = bbot + 1;
|
|
+ if (!lkup->dirren.drinfo) {
|
|
+ lkup->dirren.drinfo = kcalloc(w.ninfo,
|
|
+ sizeof(*lkup->dirren.drinfo),
|
|
+ GFP_NOFS);
|
|
+ if (unlikely(!lkup->dirren.drinfo)) {
|
|
+ err = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+ lkup->dirren.ninfo = w.ninfo;
|
|
+ }
|
|
+ w.drinfo = lkup->dirren.drinfo;
|
|
+ w.no_sio = !!uid_eq(current_fsuid(), GLOBAL_ROOT_UID);
|
|
+ w.h_ppath.dentry = au_h_dptr(dentry, btgt);
|
|
+ AuDebugOn(!w.h_ppath.dentry);
|
|
+ w.h_ppath.mnt = au_sbr_mnt(sb, btgt);
|
|
+ w.qname = &dentry->d_name;
|
|
+
|
|
+ ninfo = 0;
|
|
+ for (bindex = btgt + 1; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ err = au_drinfo_load(&w, bindex, br);
|
|
+ if (unlikely(err))
|
|
+ goto out_free;
|
|
+ if (w.drinfo[bindex])
|
|
+ ninfo++;
|
|
+ }
|
|
+ if (!ninfo) {
|
|
+ br = au_sbr(sb, btgt);
|
|
+ h_dir = d_inode(w.h_ppath.dentry);
|
|
+ ent = au_dr_hino_find(&br->br_dirren, h_dir->i_ino);
|
|
+ AuDebugOn(!ent);
|
|
+ au_dr_hino_del(&br->br_dirren, ent);
|
|
+ au_kfree_rcu(ent);
|
|
+ }
|
|
+ goto out; /* success */
|
|
+
|
|
+out_free:
|
|
+ au_dr_lkup_free(lkup->dirren.drinfo, lkup->dirren.ninfo);
|
|
+ lkup->dirren.ninfo = 0;
|
|
+ lkup->dirren.drinfo = NULL;
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_dr_lkup_fin(struct au_do_lookup_args *lkup)
|
|
+{
|
|
+ au_dr_lkup_free(lkup->dirren.drinfo, lkup->dirren.ninfo);
|
|
+}
|
|
+
|
|
+int au_dr_lkup_name(struct au_do_lookup_args *lkup, aufs_bindex_t btgt)
|
|
+{
|
|
+ int err;
|
|
+ struct au_drinfo *drinfo;
|
|
+
|
|
+ err = 0;
|
|
+ if (!lkup->dirren.drinfo)
|
|
+ goto out;
|
|
+ AuDebugOn(lkup->dirren.ninfo <= btgt);
|
|
+ drinfo = lkup->dirren.drinfo[btgt];
|
|
+ if (!drinfo)
|
|
+ goto out;
|
|
+
|
|
+ au_kfree_try_rcu(lkup->whname.name);
|
|
+ lkup->whname.name = NULL;
|
|
+ lkup->dirren.dr_name.len = drinfo->oldnamelen;
|
|
+ lkup->dirren.dr_name.name = drinfo->oldname;
|
|
+ lkup->name = &lkup->dirren.dr_name;
|
|
+ err = au_wh_name_alloc(&lkup->whname, lkup->name);
|
|
+ if (!err)
|
|
+ AuDbg("name %.*s, whname %.*s, b%d\n",
|
|
+ AuLNPair(lkup->name), AuLNPair(&lkup->whname),
|
|
+ btgt);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_dr_lkup_h_ino(struct au_do_lookup_args *lkup, aufs_bindex_t bindex,
|
|
+ ino_t h_ino)
|
|
+{
|
|
+ int match;
|
|
+ struct au_drinfo *drinfo;
|
|
+
|
|
+ match = 1;
|
|
+ if (!lkup->dirren.drinfo)
|
|
+ goto out;
|
|
+ AuDebugOn(lkup->dirren.ninfo <= bindex);
|
|
+ drinfo = lkup->dirren.drinfo[bindex];
|
|
+ if (!drinfo)
|
|
+ goto out;
|
|
+
|
|
+ match = (drinfo->ino == h_ino);
|
|
+ AuDbg("match %d\n", match);
|
|
+
|
|
+out:
|
|
+ return match;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_dr_opt_set(struct super_block *sb)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ err = 0;
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; !err && bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ err = au_dr_hino(sb, bindex, /*br*/NULL, &br->br_path);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_dr_opt_flush(struct super_block *sb)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ err = 0;
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; !err && bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (au_br_writable(br->br_perm))
|
|
+ err = au_dr_hino(sb, bindex, /*br*/NULL, /*path*/NULL);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_dr_opt_clr(struct super_block *sb, int no_flush)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ err = 0;
|
|
+ if (!no_flush) {
|
|
+ err = au_dr_opt_flush(sb);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ au_dr_hino_free(&br->br_dirren);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/dirren.h b/fs/aufs/dirren.h
|
|
new file mode 100644
|
|
index 000000000000..1fbc8fb20def
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dirren.h
|
|
@@ -0,0 +1,140 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2017-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * renamed dir info
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_DIRREN_H__
|
|
+#define __AUFS_DIRREN_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/dcache.h>
|
|
+#include <linux/statfs.h>
|
|
+#include <linux/uuid.h>
|
|
+#include "hbl.h"
|
|
+
|
|
+#define AuDirren_NHASH 100
|
|
+
|
|
+#ifdef CONFIG_AUFS_DIRREN
|
|
+enum au_brid_type {
|
|
+ AuBrid_Unset,
|
|
+ AuBrid_UUID,
|
|
+ AuBrid_FSID,
|
|
+ AuBrid_DEV
|
|
+};
|
|
+
|
|
+struct au_dr_brid {
|
|
+ enum au_brid_type type;
|
|
+ union {
|
|
+ uuid_t uuid; /* unimplemented yet */
|
|
+ fsid_t fsid;
|
|
+ dev_t dev;
|
|
+ };
|
|
+};
|
|
+
|
|
+/* 20 is the max digits length of ulong 64 */
|
|
+/* brid-type "_" uuid "_" inum */
|
|
+#define AUFS_DIRREN_FNAME_SZ (1 + 1 + UUID_STRING_LEN + 20)
|
|
+#define AUFS_DIRREN_ENV_VAL_SZ (AUFS_DIRREN_FNAME_SZ + 1 + 20)
|
|
+
|
|
+struct au_dr_hino {
|
|
+ struct hlist_bl_node dr_hnode;
|
|
+ ino_t dr_h_ino;
|
|
+};
|
|
+
|
|
+struct au_dr_br {
|
|
+ struct hlist_bl_head dr_h_ino[AuDirren_NHASH];
|
|
+ struct au_dr_brid dr_brid;
|
|
+};
|
|
+
|
|
+struct au_dr_lookup {
|
|
+ /* dr_name is pointed by struct au_do_lookup_args.name */
|
|
+ struct qstr dr_name; /* subset of dr_info */
|
|
+ aufs_bindex_t ninfo;
|
|
+ struct au_drinfo **drinfo;
|
|
+};
|
|
+#else
|
|
+struct au_dr_hino;
|
|
+/* empty */
|
|
+struct au_dr_br { };
|
|
+struct au_dr_lookup { };
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_branch;
|
|
+struct au_do_lookup_args;
|
|
+struct au_hinode;
|
|
+#ifdef CONFIG_AUFS_DIRREN
|
|
+int au_dr_hino_test_add(struct au_dr_br *dr, ino_t h_ino,
|
|
+ struct au_dr_hino *add_ent);
|
|
+void au_dr_hino_free(struct au_dr_br *dr);
|
|
+int au_dr_br_init(struct super_block *sb, struct au_branch *br,
|
|
+ const struct path *path);
|
|
+int au_dr_br_fin(struct super_block *sb, struct au_branch *br);
|
|
+int au_dr_rename(struct dentry *src, aufs_bindex_t bindex,
|
|
+ struct qstr *dst_name, void *_rev);
|
|
+void au_dr_rename_fin(struct dentry *src, aufs_bindex_t btgt, void *rev);
|
|
+void au_dr_rename_rev(struct dentry *src, aufs_bindex_t bindex, void *rev);
|
|
+int au_dr_lkup(struct au_do_lookup_args *lkup, struct dentry *dentry,
|
|
+ aufs_bindex_t bindex);
|
|
+int au_dr_lkup_name(struct au_do_lookup_args *lkup, aufs_bindex_t btgt);
|
|
+int au_dr_lkup_h_ino(struct au_do_lookup_args *lkup, aufs_bindex_t bindex,
|
|
+ ino_t h_ino);
|
|
+void au_dr_lkup_fin(struct au_do_lookup_args *lkup);
|
|
+int au_dr_opt_set(struct super_block *sb);
|
|
+int au_dr_opt_flush(struct super_block *sb);
|
|
+int au_dr_opt_clr(struct super_block *sb, int no_flush);
|
|
+#else
|
|
+AuStubInt0(au_dr_hino_test_add, struct au_dr_br *dr, ino_t h_ino,
|
|
+ struct au_dr_hino *add_ent);
|
|
+AuStubVoid(au_dr_hino_free, struct au_dr_br *dr);
|
|
+AuStubInt0(au_dr_br_init, struct super_block *sb, struct au_branch *br,
|
|
+ const struct path *path);
|
|
+AuStubInt0(au_dr_br_fin, struct super_block *sb, struct au_branch *br);
|
|
+AuStubInt0(au_dr_rename, struct dentry *src, aufs_bindex_t bindex,
|
|
+ struct qstr *dst_name, void *_rev);
|
|
+AuStubVoid(au_dr_rename_fin, struct dentry *src, aufs_bindex_t btgt, void *rev);
|
|
+AuStubVoid(au_dr_rename_rev, struct dentry *src, aufs_bindex_t bindex,
|
|
+ void *rev);
|
|
+AuStubInt0(au_dr_lkup, struct au_do_lookup_args *lkup, struct dentry *dentry,
|
|
+ aufs_bindex_t bindex);
|
|
+AuStubInt0(au_dr_lkup_name, struct au_do_lookup_args *lkup, aufs_bindex_t btgt);
|
|
+AuStubInt0(au_dr_lkup_h_ino, struct au_do_lookup_args *lkup,
|
|
+ aufs_bindex_t bindex, ino_t h_ino);
|
|
+AuStubVoid(au_dr_lkup_fin, struct au_do_lookup_args *lkup);
|
|
+AuStubInt0(au_dr_opt_set, struct super_block *sb);
|
|
+AuStubInt0(au_dr_opt_flush, struct super_block *sb);
|
|
+AuStubInt0(au_dr_opt_clr, struct super_block *sb, int no_flush);
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_AUFS_DIRREN
|
|
+static inline int au_dr_ihash(ino_t h_ino)
|
|
+{
|
|
+ return h_ino % AuDirren_NHASH;
|
|
+}
|
|
+#else
|
|
+AuStubInt0(au_dr_ihash, ino_t h_ino);
|
|
+#endif
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_DIRREN_H__ */
|
|
diff --git a/fs/aufs/dynop.c b/fs/aufs/dynop.c
|
|
new file mode 100644
|
|
index 000000000000..837f94d49f74
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dynop.c
|
|
@@ -0,0 +1,367 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2010-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * 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 hlist_bl_head dynop[AuDyLast];
|
|
+
|
|
+static struct au_dykey *dy_gfind_get(struct hlist_bl_head *hbl,
|
|
+ const void *h_op)
|
|
+{
|
|
+ struct au_dykey *key, *tmp;
|
|
+ struct hlist_bl_node *pos;
|
|
+
|
|
+ key = NULL;
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_for_each_entry(tmp, pos, hbl, dk_hnode)
|
|
+ if (tmp->dk_op.dy_hop == h_op) {
|
|
+ if (kref_get_unless_zero(&tmp->dk_kref))
|
|
+ key = tmp;
|
|
+ break;
|
|
+ }
|
|
+ hlist_bl_unlock(hbl);
|
|
+
|
|
+ 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 hlist_bl_head *hbl, struct au_dykey *key)
|
|
+{
|
|
+ struct au_dykey *tmp, *found;
|
|
+ struct hlist_bl_node *pos;
|
|
+ const void *h_op = key->dk_op.dy_hop;
|
|
+
|
|
+ found = NULL;
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_for_each_entry(tmp, pos, hbl, dk_hnode)
|
|
+ if (tmp->dk_op.dy_hop == h_op) {
|
|
+ if (kref_get_unless_zero(&tmp->dk_kref))
|
|
+ found = tmp;
|
|
+ break;
|
|
+ }
|
|
+ if (!found)
|
|
+ hlist_bl_add_head(&key->dk_hnode, hbl);
|
|
+ hlist_bl_unlock(hbl);
|
|
+
|
|
+ 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 hlist_bl_head *hbl;
|
|
+
|
|
+ key = container_of(kref, struct au_dykey, dk_kref);
|
|
+ hbl = dynop + key->dk_op.dy_type;
|
|
+ au_hbl_del(&key->dk_hnode, hbl);
|
|
+ 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);
|
|
+ /* this one will be changed according to an aufs mount option */
|
|
+ DySetAop(direct_IO);
|
|
+ DySetAop(migratepage);
|
|
+ DySetAop(isolate_page);
|
|
+ DySetAop(putback_page);
|
|
+ DySetAop(launder_page);
|
|
+ DySetAop(is_partially_uptodate);
|
|
+ DySetAop(is_dirty_writeback);
|
|
+ DySetAop(error_remove_page);
|
|
+ DySetAop(swap_activate);
|
|
+ DySetAop(swap_deactivate);
|
|
+
|
|
+ DyDbgSize(cnt, *h_aop);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+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 hlist_bl_head *hbl;
|
|
+ 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;
|
|
+
|
|
+ hbl = dynop + op->dy_type;
|
|
+ key = dy_gfind_get(hbl, 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(hbl, key);
|
|
+ if (old) {
|
|
+ au_kfree_rcu(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 default even if the branch supports it.
|
|
+ * This behaviour is necessary 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.
|
|
+ */
|
|
+static void dy_adx(struct au_dyaop *dyaop, int do_dx)
|
|
+{
|
|
+ if (!do_dx)
|
|
+ dyaop->da_op.direct_IO = NULL;
|
|
+ else
|
|
+ dyaop->da_op.direct_IO = aufs_aop.direct_IO;
|
|
+}
|
|
+
|
|
+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 btop;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ err = 0;
|
|
+ if (S_ISREG(inode->i_mode)) {
|
|
+ btop = au_ibtop(inode);
|
|
+ h_inode = au_h_iptr(inode, btop);
|
|
+ err = au_dy_iaop(inode, btop, h_inode);
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_dy_arefresh(int do_dx)
|
|
+{
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos;
|
|
+ struct au_dykey *key;
|
|
+
|
|
+ hbl = dynop + AuDy_AOP;
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_for_each_entry(key, pos, hbl, dk_hnode)
|
|
+ dy_adx((void *)key, do_dx);
|
|
+ hlist_bl_unlock(hbl);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void __init au_dy_init(void)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < AuDyLast; i++)
|
|
+ INIT_HLIST_BL_HEAD(dynop + i);
|
|
+}
|
|
+
|
|
+void au_dy_fin(void)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < AuDyLast; i++)
|
|
+ WARN_ON(!hlist_bl_empty(dynop + i));
|
|
+}
|
|
diff --git a/fs/aufs/dynop.h b/fs/aufs/dynop.h
|
|
new file mode 100644
|
|
index 000000000000..c0c7a5485ad7
|
|
--- /dev/null
|
|
+++ b/fs/aufs/dynop.h
|
|
@@ -0,0 +1,77 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2010-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * dynamically customizable operations (for regular files only)
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_DYNOP_H__
|
|
+#define __AUFS_DYNOP_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/fs.h>
|
|
+#include <linux/kref.h>
|
|
+
|
|
+enum {AuDy_AOP, AuDyLast};
|
|
+
|
|
+struct au_dynop {
|
|
+ int dy_type;
|
|
+ union {
|
|
+ const void *dy_hop;
|
|
+ const struct address_space_operations *dy_haop;
|
|
+ };
|
|
+};
|
|
+
|
|
+struct au_dykey {
|
|
+ union {
|
|
+ struct hlist_bl_node dk_hnode;
|
|
+ struct rcu_head dk_rcu;
|
|
+ };
|
|
+ struct au_dynop dk_op;
|
|
+
|
|
+ /*
|
|
+ * during I am in the branch local array, kref is gotten. when the
|
|
+ * branch is removed, kref is put.
|
|
+ */
|
|
+ struct kref dk_kref;
|
|
+};
|
|
+
|
|
+/* stop unioning since their sizes are very different from each other */
|
|
+struct au_dyaop {
|
|
+ struct au_dykey da_key;
|
|
+ struct address_space_operations da_op; /* not const */
|
|
+};
|
|
+/* make sure that 'struct au_dykey *' can be any type */
|
|
+static_assert(!offsetof(struct au_dyaop, da_key));
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* dynop.c */
|
|
+struct au_branch;
|
|
+void au_dy_put(struct au_dykey *key);
|
|
+int au_dy_iaop(struct inode *inode, aufs_bindex_t bindex,
|
|
+ struct inode *h_inode);
|
|
+int au_dy_irefresh(struct inode *inode);
|
|
+void au_dy_arefresh(int do_dio);
|
|
+
|
|
+void __init au_dy_init(void);
|
|
+void au_dy_fin(void);
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_DYNOP_H__ */
|
|
diff --git a/fs/aufs/export.c b/fs/aufs/export.c
|
|
new file mode 100644
|
|
index 000000000000..842df6f05517
|
|
--- /dev/null
|
|
+++ b/fs/aufs/export.c
|
|
@@ -0,0 +1,838 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * export via nfs
|
|
+ */
|
|
+
|
|
+#include <linux/exportfs.h>
|
|
+#include <linux/fs_struct.h>
|
|
+#include <linux/namei.h>
|
|
+#include <linux/nsproxy.h>
|
|
+#include <linux/random.h>
|
|
+#include <linux/writeback.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+union conv {
|
|
+#ifdef CONFIG_AUFS_INO_T_64
|
|
+ __u32 a[2];
|
|
+#else
|
|
+ __u32 a[1];
|
|
+#endif
|
|
+ ino_t ino;
|
|
+};
|
|
+
|
|
+static ino_t decode_ino(__u32 *a)
|
|
+{
|
|
+ union conv u;
|
|
+
|
|
+ BUILD_BUG_ON(sizeof(u.ino) != sizeof(u.a));
|
|
+ u.a[0] = a[0];
|
|
+#ifdef CONFIG_AUFS_INO_T_64
|
|
+ u.a[1] = a[1];
|
|
+#endif
|
|
+ return u.ino;
|
|
+}
|
|
+
|
|
+static void encode_ino(__u32 *a, ino_t ino)
|
|
+{
|
|
+ union conv u;
|
|
+
|
|
+ u.ino = ino;
|
|
+ a[0] = u.a[0];
|
|
+#ifdef CONFIG_AUFS_INO_T_64
|
|
+ a[1] = u.a[1];
|
|
+#endif
|
|
+}
|
|
+
|
|
+/* NFS file handle */
|
|
+enum {
|
|
+ Fh_br_id,
|
|
+ Fh_sigen,
|
|
+#ifdef CONFIG_AUFS_INO_T_64
|
|
+ /* support 64bit inode number */
|
|
+ Fh_ino1,
|
|
+ Fh_ino2,
|
|
+ Fh_dir_ino1,
|
|
+ Fh_dir_ino2,
|
|
+#else
|
|
+ Fh_ino1,
|
|
+ Fh_dir_ino1,
|
|
+#endif
|
|
+ Fh_igen,
|
|
+ Fh_h_type,
|
|
+ Fh_tail,
|
|
+
|
|
+ Fh_ino = Fh_ino1,
|
|
+ Fh_dir_ino = Fh_dir_ino1
|
|
+};
|
|
+
|
|
+static int au_test_anon(struct dentry *dentry)
|
|
+{
|
|
+ /* note: read d_flags without d_lock */
|
|
+ return !!(dentry->d_flags & DCACHE_DISCONNECTED);
|
|
+}
|
|
+
|
|
+int au_test_nfsd(void)
|
|
+{
|
|
+ int ret;
|
|
+ struct task_struct *tsk = current;
|
|
+ char comm[sizeof(tsk->comm)];
|
|
+
|
|
+ ret = 0;
|
|
+ if (tsk->flags & PF_KTHREAD) {
|
|
+ get_task_comm(comm, tsk);
|
|
+ ret = !strcmp(comm, "nfsd");
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+/* inode generation external table */
|
|
+
|
|
+void au_xigen_inc(struct inode *inode)
|
|
+{
|
|
+ loff_t pos;
|
|
+ ssize_t sz;
|
|
+ __u32 igen;
|
|
+ struct super_block *sb;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ sb = inode->i_sb;
|
|
+ AuDebugOn(!au_opt_test(au_mntflags(sb), XINO));
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ pos = inode->i_ino;
|
|
+ pos *= sizeof(igen);
|
|
+ igen = inode->i_generation + 1;
|
|
+ sz = xino_fwrite(sbinfo->si_xwrite, sbinfo->si_xigen, &igen,
|
|
+ sizeof(igen), &pos);
|
|
+ if (sz == sizeof(igen))
|
|
+ return; /* success */
|
|
+
|
|
+ if (unlikely(sz >= 0))
|
|
+ AuIOErr("xigen error (%zd)\n", sz);
|
|
+}
|
|
+
|
|
+int au_xigen_new(struct inode *inode)
|
|
+{
|
|
+ int err;
|
|
+ loff_t pos;
|
|
+ ssize_t sz;
|
|
+ struct super_block *sb;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct file *file;
|
|
+
|
|
+ err = 0;
|
|
+ /* todo: dirty, at mount time */
|
|
+ if (inode->i_ino == AUFS_ROOT_INO)
|
|
+ goto out;
|
|
+ sb = inode->i_sb;
|
|
+ SiMustAnyLock(sb);
|
|
+ if (unlikely(!au_opt_test(au_mntflags(sb), XINO)))
|
|
+ goto out;
|
|
+
|
|
+ err = -EFBIG;
|
|
+ pos = inode->i_ino;
|
|
+ if (unlikely(au_loff_max / sizeof(inode->i_generation) - 1 < pos)) {
|
|
+ AuIOErr1("too large i%lld\n", pos);
|
|
+ goto out;
|
|
+ }
|
|
+ pos *= sizeof(inode->i_generation);
|
|
+
|
|
+ err = 0;
|
|
+ sbinfo = au_sbi(sb);
|
|
+ file = sbinfo->si_xigen;
|
|
+ BUG_ON(!file);
|
|
+
|
|
+ if (vfsub_f_size_read(file)
|
|
+ < pos + sizeof(inode->i_generation)) {
|
|
+ inode->i_generation = atomic_inc_return(&sbinfo->si_xigen_next);
|
|
+ sz = xino_fwrite(sbinfo->si_xwrite, file, &inode->i_generation,
|
|
+ sizeof(inode->i_generation), &pos);
|
|
+ } else
|
|
+ sz = xino_fread(sbinfo->si_xread, file, &inode->i_generation,
|
|
+ sizeof(inode->i_generation), &pos);
|
|
+ if (sz == sizeof(inode->i_generation))
|
|
+ goto out; /* success */
|
|
+
|
|
+ err = sz;
|
|
+ if (unlikely(sz >= 0)) {
|
|
+ err = -EIO;
|
|
+ AuIOErr("xigen error (%zd)\n", sz);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_xigen_set(struct super_block *sb, struct path *path)
|
|
+{
|
|
+ int err;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct file *file;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ file = au_xino_create2(sb, path, sbinfo->si_xigen);
|
|
+ err = PTR_ERR(file);
|
|
+ if (IS_ERR(file))
|
|
+ goto out;
|
|
+ err = 0;
|
|
+ if (sbinfo->si_xigen)
|
|
+ fput(sbinfo->si_xigen);
|
|
+ sbinfo->si_xigen = file;
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_xigen_clr(struct super_block *sb)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ if (sbinfo->si_xigen) {
|
|
+ fput(sbinfo->si_xigen);
|
|
+ sbinfo->si_xigen = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static struct dentry *decode_by_ino(struct super_block *sb, ino_t ino,
|
|
+ ino_t dir_ino)
|
|
+{
|
|
+ struct dentry *dentry, *d;
|
|
+ struct inode *inode;
|
|
+ unsigned int sigen;
|
|
+
|
|
+ dentry = NULL;
|
|
+ inode = ilookup(sb, ino);
|
|
+ if (!inode)
|
|
+ goto out;
|
|
+
|
|
+ dentry = ERR_PTR(-ESTALE);
|
|
+ sigen = au_sigen(sb);
|
|
+ if (unlikely(au_is_bad_inode(inode)
|
|
+ || IS_DEADDIR(inode)
|
|
+ || sigen != au_iigen(inode, NULL)))
|
|
+ goto out_iput;
|
|
+
|
|
+ dentry = NULL;
|
|
+ if (!dir_ino || S_ISDIR(inode->i_mode))
|
|
+ dentry = d_find_alias(inode);
|
|
+ else {
|
|
+ spin_lock(&inode->i_lock);
|
|
+ hlist_for_each_entry(d, &inode->i_dentry, d_u.d_alias) {
|
|
+ spin_lock(&d->d_lock);
|
|
+ if (!au_test_anon(d)
|
|
+ && d_inode(d->d_parent)->i_ino == dir_ino) {
|
|
+ dentry = dget_dlock(d);
|
|
+ spin_unlock(&d->d_lock);
|
|
+ break;
|
|
+ }
|
|
+ spin_unlock(&d->d_lock);
|
|
+ }
|
|
+ spin_unlock(&inode->i_lock);
|
|
+ }
|
|
+ if (unlikely(dentry && au_digen_test(dentry, sigen))) {
|
|
+ /* need to refresh */
|
|
+ dput(dentry);
|
|
+ dentry = NULL;
|
|
+ }
|
|
+
|
|
+out_iput:
|
|
+ iput(inode);
|
|
+out:
|
|
+ AuTraceErrPtr(dentry);
|
|
+ return dentry;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* todo: dirty? */
|
|
+/* if exportfs_decode_fh() passed vfsmount*, we could be happy */
|
|
+
|
|
+struct au_compare_mnt_args {
|
|
+ /* input */
|
|
+ struct super_block *sb;
|
|
+
|
|
+ /* output */
|
|
+ struct vfsmount *mnt;
|
|
+};
|
|
+
|
|
+static int au_compare_mnt(struct vfsmount *mnt, void *arg)
|
|
+{
|
|
+ struct au_compare_mnt_args *a = arg;
|
|
+
|
|
+ if (mnt->mnt_sb != a->sb)
|
|
+ return 0;
|
|
+ a->mnt = mntget(mnt);
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static struct vfsmount *au_mnt_get(struct super_block *sb)
|
|
+{
|
|
+ int err;
|
|
+ struct path root;
|
|
+ struct au_compare_mnt_args args = {
|
|
+ .sb = sb
|
|
+ };
|
|
+
|
|
+ get_fs_root(current->fs, &root);
|
|
+ rcu_read_lock();
|
|
+ err = iterate_mounts(au_compare_mnt, &args, root.mnt);
|
|
+ rcu_read_unlock();
|
|
+ path_put(&root);
|
|
+ AuDebugOn(!err);
|
|
+ AuDebugOn(!args.mnt);
|
|
+ return args.mnt;
|
|
+}
|
|
+
|
|
+struct au_nfsd_si_lock {
|
|
+ unsigned int sigen;
|
|
+ aufs_bindex_t bindex, br_id;
|
|
+ unsigned char force_lock;
|
|
+};
|
|
+
|
|
+static int si_nfsd_read_lock(struct super_block *sb,
|
|
+ struct au_nfsd_si_lock *nsi_lock)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+
|
|
+ si_read_lock(sb, AuLock_FLUSH);
|
|
+
|
|
+ /* branch id may be wrapped around */
|
|
+ err = 0;
|
|
+ bindex = au_br_index(sb, nsi_lock->br_id);
|
|
+ if (bindex >= 0 && nsi_lock->sigen + AUFS_BRANCH_MAX > au_sigen(sb))
|
|
+ goto out; /* success */
|
|
+
|
|
+ err = -ESTALE;
|
|
+ bindex = -1;
|
|
+ if (!nsi_lock->force_lock)
|
|
+ si_read_unlock(sb);
|
|
+
|
|
+out:
|
|
+ nsi_lock->bindex = bindex;
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct find_name_by_ino {
|
|
+ struct dir_context ctx;
|
|
+ int called, found;
|
|
+ ino_t ino;
|
|
+ char *name;
|
|
+ int namelen;
|
|
+};
|
|
+
|
|
+static int
|
|
+find_name_by_ino(struct dir_context *ctx, const char *name, int namelen,
|
|
+ loff_t offset, u64 ino, unsigned int d_type)
|
|
+{
|
|
+ struct find_name_by_ino *a = container_of(ctx, struct find_name_by_ino,
|
|
+ ctx);
|
|
+
|
|
+ a->called++;
|
|
+ if (a->ino != ino)
|
|
+ return 0;
|
|
+
|
|
+ memcpy(a->name, name, namelen);
|
|
+ a->namelen = namelen;
|
|
+ a->found = 1;
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static struct dentry *au_lkup_by_ino(struct path *path, ino_t ino,
|
|
+ struct au_nfsd_si_lock *nsi_lock)
|
|
+{
|
|
+ struct dentry *dentry, *parent;
|
|
+ struct file *file;
|
|
+ struct inode *dir;
|
|
+ struct find_name_by_ino arg = {
|
|
+ .ctx = {
|
|
+ .actor = find_name_by_ino
|
|
+ }
|
|
+ };
|
|
+ int err;
|
|
+
|
|
+ parent = path->dentry;
|
|
+ if (nsi_lock)
|
|
+ si_read_unlock(parent->d_sb);
|
|
+ file = vfsub_dentry_open(path, au_dir_roflags);
|
|
+ dentry = (void *)file;
|
|
+ if (IS_ERR(file))
|
|
+ goto out;
|
|
+
|
|
+ dentry = ERR_PTR(-ENOMEM);
|
|
+ arg.name = (void *)__get_free_page(GFP_NOFS);
|
|
+ if (unlikely(!arg.name))
|
|
+ goto out_file;
|
|
+ arg.ino = ino;
|
|
+ arg.found = 0;
|
|
+ do {
|
|
+ arg.called = 0;
|
|
+ /* smp_mb(); */
|
|
+ err = vfsub_iterate_dir(file, &arg.ctx);
|
|
+ } while (!err && !arg.found && arg.called);
|
|
+ dentry = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out_name;
|
|
+ /* instead of ENOENT */
|
|
+ dentry = ERR_PTR(-ESTALE);
|
|
+ if (!arg.found)
|
|
+ goto out_name;
|
|
+
|
|
+ /* do not call vfsub_lkup_one() */
|
|
+ dir = d_inode(parent);
|
|
+ dentry = vfsub_lookup_one_len_unlocked(arg.name, parent, arg.namelen);
|
|
+ AuTraceErrPtr(dentry);
|
|
+ if (IS_ERR(dentry))
|
|
+ goto out_name;
|
|
+ AuDebugOn(au_test_anon(dentry));
|
|
+ if (unlikely(d_really_is_negative(dentry))) {
|
|
+ dput(dentry);
|
|
+ dentry = ERR_PTR(-ENOENT);
|
|
+ }
|
|
+
|
|
+out_name:
|
|
+ free_page((unsigned long)arg.name);
|
|
+out_file:
|
|
+ fput(file);
|
|
+out:
|
|
+ if (unlikely(nsi_lock
|
|
+ && si_nfsd_read_lock(parent->d_sb, nsi_lock) < 0))
|
|
+ if (!IS_ERR(dentry)) {
|
|
+ dput(dentry);
|
|
+ dentry = ERR_PTR(-ESTALE);
|
|
+ }
|
|
+ AuTraceErrPtr(dentry);
|
|
+ return dentry;
|
|
+}
|
|
+
|
|
+static struct dentry *decode_by_dir_ino(struct super_block *sb, ino_t ino,
|
|
+ ino_t dir_ino,
|
|
+ struct au_nfsd_si_lock *nsi_lock)
|
|
+{
|
|
+ struct dentry *dentry;
|
|
+ struct path path;
|
|
+
|
|
+ if (dir_ino != AUFS_ROOT_INO) {
|
|
+ path.dentry = decode_by_ino(sb, dir_ino, 0);
|
|
+ dentry = path.dentry;
|
|
+ if (!path.dentry || IS_ERR(path.dentry))
|
|
+ goto out;
|
|
+ AuDebugOn(au_test_anon(path.dentry));
|
|
+ } else
|
|
+ path.dentry = dget(sb->s_root);
|
|
+
|
|
+ path.mnt = au_mnt_get(sb);
|
|
+ dentry = au_lkup_by_ino(&path, ino, nsi_lock);
|
|
+ path_put(&path);
|
|
+
|
|
+out:
|
|
+ AuTraceErrPtr(dentry);
|
|
+ return dentry;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int h_acceptable(void *expv, struct dentry *dentry)
|
|
+{
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static char *au_build_path(struct dentry *h_parent, struct path *h_rootpath,
|
|
+ char *buf, int len, struct super_block *sb)
|
|
+{
|
|
+ char *p;
|
|
+ int n;
|
|
+ struct path path;
|
|
+
|
|
+ p = d_path(h_rootpath, buf, len);
|
|
+ if (IS_ERR(p))
|
|
+ goto out;
|
|
+ n = strlen(p);
|
|
+
|
|
+ path.mnt = h_rootpath->mnt;
|
|
+ path.dentry = h_parent;
|
|
+ p = d_path(&path, buf, len);
|
|
+ if (IS_ERR(p))
|
|
+ goto out;
|
|
+ if (n != 1)
|
|
+ p += n;
|
|
+
|
|
+ path.mnt = au_mnt_get(sb);
|
|
+ path.dentry = sb->s_root;
|
|
+ p = d_path(&path, buf, len - strlen(p));
|
|
+ mntput(path.mnt);
|
|
+ if (IS_ERR(p))
|
|
+ goto out;
|
|
+ if (n != 1)
|
|
+ p[strlen(p)] = '/';
|
|
+
|
|
+out:
|
|
+ AuTraceErrPtr(p);
|
|
+ return p;
|
|
+}
|
|
+
|
|
+static
|
|
+struct dentry *decode_by_path(struct super_block *sb, ino_t ino, __u32 *fh,
|
|
+ int fh_len, struct au_nfsd_si_lock *nsi_lock)
|
|
+{
|
|
+ struct dentry *dentry, *h_parent, *root;
|
|
+ struct super_block *h_sb;
|
|
+ char *pathname, *p;
|
|
+ struct vfsmount *h_mnt;
|
|
+ struct au_branch *br;
|
|
+ int err;
|
|
+ struct path path;
|
|
+
|
|
+ br = au_sbr(sb, nsi_lock->bindex);
|
|
+ h_mnt = au_br_mnt(br);
|
|
+ h_sb = h_mnt->mnt_sb;
|
|
+ /* todo: call lower fh_to_dentry()? fh_to_parent()? */
|
|
+ lockdep_off();
|
|
+ h_parent = exportfs_decode_fh(h_mnt, (void *)(fh + Fh_tail),
|
|
+ fh_len - Fh_tail, fh[Fh_h_type],
|
|
+ h_acceptable, /*context*/NULL);
|
|
+ lockdep_on();
|
|
+ dentry = h_parent;
|
|
+ if (unlikely(!h_parent || IS_ERR(h_parent))) {
|
|
+ AuWarn1("%s decode_fh failed, %ld\n",
|
|
+ au_sbtype(h_sb), PTR_ERR(h_parent));
|
|
+ goto out;
|
|
+ }
|
|
+ dentry = NULL;
|
|
+ if (unlikely(au_test_anon(h_parent))) {
|
|
+ AuWarn1("%s decode_fh returned a disconnected dentry\n",
|
|
+ au_sbtype(h_sb));
|
|
+ goto out_h_parent;
|
|
+ }
|
|
+
|
|
+ dentry = ERR_PTR(-ENOMEM);
|
|
+ pathname = (void *)__get_free_page(GFP_NOFS);
|
|
+ if (unlikely(!pathname))
|
|
+ goto out_h_parent;
|
|
+
|
|
+ root = sb->s_root;
|
|
+ path.mnt = h_mnt;
|
|
+ di_read_lock_parent(root, !AuLock_IR);
|
|
+ path.dentry = au_h_dptr(root, nsi_lock->bindex);
|
|
+ di_read_unlock(root, !AuLock_IR);
|
|
+ p = au_build_path(h_parent, &path, pathname, PAGE_SIZE, sb);
|
|
+ dentry = (void *)p;
|
|
+ if (IS_ERR(p))
|
|
+ goto out_pathname;
|
|
+
|
|
+ si_read_unlock(sb);
|
|
+ err = vfsub_kern_path(p, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path);
|
|
+ dentry = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out_relock;
|
|
+
|
|
+ dentry = ERR_PTR(-ENOENT);
|
|
+ AuDebugOn(au_test_anon(path.dentry));
|
|
+ if (unlikely(d_really_is_negative(path.dentry)))
|
|
+ goto out_path;
|
|
+
|
|
+ if (ino != d_inode(path.dentry)->i_ino)
|
|
+ dentry = au_lkup_by_ino(&path, ino, /*nsi_lock*/NULL);
|
|
+ else
|
|
+ dentry = dget(path.dentry);
|
|
+
|
|
+out_path:
|
|
+ path_put(&path);
|
|
+out_relock:
|
|
+ if (unlikely(si_nfsd_read_lock(sb, nsi_lock) < 0))
|
|
+ if (!IS_ERR(dentry)) {
|
|
+ dput(dentry);
|
|
+ dentry = ERR_PTR(-ESTALE);
|
|
+ }
|
|
+out_pathname:
|
|
+ free_page((unsigned long)pathname);
|
|
+out_h_parent:
|
|
+ dput(h_parent);
|
|
+out:
|
|
+ AuTraceErrPtr(dentry);
|
|
+ return dentry;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static struct dentry *
|
|
+aufs_fh_to_dentry(struct super_block *sb, struct fid *fid, int fh_len,
|
|
+ int fh_type)
|
|
+{
|
|
+ struct dentry *dentry;
|
|
+ __u32 *fh = fid->raw;
|
|
+ struct au_branch *br;
|
|
+ ino_t ino, dir_ino;
|
|
+ struct au_nfsd_si_lock nsi_lock = {
|
|
+ .force_lock = 0
|
|
+ };
|
|
+
|
|
+ dentry = ERR_PTR(-ESTALE);
|
|
+ /* it should never happen, but the file handle is unreliable */
|
|
+ if (unlikely(fh_len < Fh_tail))
|
|
+ goto out;
|
|
+ nsi_lock.sigen = fh[Fh_sigen];
|
|
+ nsi_lock.br_id = fh[Fh_br_id];
|
|
+
|
|
+ /* branch id may be wrapped around */
|
|
+ br = NULL;
|
|
+ if (unlikely(si_nfsd_read_lock(sb, &nsi_lock)))
|
|
+ goto out;
|
|
+ nsi_lock.force_lock = 1;
|
|
+
|
|
+ /* is this inode still cached? */
|
|
+ ino = decode_ino(fh + Fh_ino);
|
|
+ /* it should never happen */
|
|
+ if (unlikely(ino == AUFS_ROOT_INO))
|
|
+ goto out_unlock;
|
|
+
|
|
+ dir_ino = decode_ino(fh + Fh_dir_ino);
|
|
+ dentry = decode_by_ino(sb, ino, dir_ino);
|
|
+ if (IS_ERR(dentry))
|
|
+ goto out_unlock;
|
|
+ if (dentry)
|
|
+ goto accept;
|
|
+
|
|
+ /* is the parent dir cached? */
|
|
+ br = au_sbr(sb, nsi_lock.bindex);
|
|
+ au_lcnt_inc(&br->br_nfiles);
|
|
+ dentry = decode_by_dir_ino(sb, ino, dir_ino, &nsi_lock);
|
|
+ if (IS_ERR(dentry))
|
|
+ goto out_unlock;
|
|
+ if (dentry)
|
|
+ goto accept;
|
|
+
|
|
+ /* lookup path */
|
|
+ dentry = decode_by_path(sb, ino, fh, fh_len, &nsi_lock);
|
|
+ if (IS_ERR(dentry))
|
|
+ goto out_unlock;
|
|
+ if (unlikely(!dentry))
|
|
+ /* todo?: make it ESTALE */
|
|
+ goto out_unlock;
|
|
+
|
|
+accept:
|
|
+ if (!au_digen_test(dentry, au_sigen(sb))
|
|
+ && d_inode(dentry)->i_generation == fh[Fh_igen])
|
|
+ goto out_unlock; /* success */
|
|
+
|
|
+ dput(dentry);
|
|
+ dentry = ERR_PTR(-ESTALE);
|
|
+out_unlock:
|
|
+ if (br)
|
|
+ au_lcnt_dec(&br->br_nfiles);
|
|
+ si_read_unlock(sb);
|
|
+out:
|
|
+ AuTraceErrPtr(dentry);
|
|
+ return dentry;
|
|
+}
|
|
+
|
|
+#if 0 /* reserved for future use */
|
|
+/* support subtreecheck option */
|
|
+static struct dentry *aufs_fh_to_parent(struct super_block *sb, struct fid *fid,
|
|
+ int fh_len, int fh_type)
|
|
+{
|
|
+ struct dentry *parent;
|
|
+ __u32 *fh = fid->raw;
|
|
+ ino_t dir_ino;
|
|
+
|
|
+ dir_ino = decode_ino(fh + Fh_dir_ino);
|
|
+ parent = decode_by_ino(sb, dir_ino, 0);
|
|
+ if (IS_ERR(parent))
|
|
+ goto out;
|
|
+ if (!parent)
|
|
+ parent = decode_by_path(sb, au_br_index(sb, fh[Fh_br_id]),
|
|
+ dir_ino, fh, fh_len);
|
|
+
|
|
+out:
|
|
+ AuTraceErrPtr(parent);
|
|
+ return parent;
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int aufs_encode_fh(struct inode *inode, __u32 *fh, int *max_len,
|
|
+ struct inode *dir)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct super_block *sb, *h_sb;
|
|
+ struct dentry *dentry, *parent, *h_parent;
|
|
+ struct inode *h_dir;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ err = -ENOSPC;
|
|
+ if (unlikely(*max_len <= Fh_tail)) {
|
|
+ AuWarn1("NFSv2 client (max_len %d)?\n", *max_len);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = FILEID_ROOT;
|
|
+ if (inode->i_ino == AUFS_ROOT_INO) {
|
|
+ AuDebugOn(inode->i_ino != AUFS_ROOT_INO);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ h_parent = NULL;
|
|
+ sb = inode->i_sb;
|
|
+ err = si_read_lock(sb, AuLock_FLUSH);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+#ifdef CONFIG_AUFS_DEBUG
|
|
+ if (unlikely(!au_opt_test(au_mntflags(sb), XINO)))
|
|
+ AuWarn1("NFS-exporting requires xino\n");
|
|
+#endif
|
|
+ err = -EIO;
|
|
+ parent = NULL;
|
|
+ ii_read_lock_child(inode);
|
|
+ bindex = au_ibtop(inode);
|
|
+ if (!dir) {
|
|
+ dentry = d_find_any_alias(inode);
|
|
+ if (unlikely(!dentry))
|
|
+ goto out_unlock;
|
|
+ AuDebugOn(au_test_anon(dentry));
|
|
+ parent = dget_parent(dentry);
|
|
+ dput(dentry);
|
|
+ if (unlikely(!parent))
|
|
+ goto out_unlock;
|
|
+ if (d_really_is_positive(parent))
|
|
+ dir = d_inode(parent);
|
|
+ }
|
|
+
|
|
+ ii_read_lock_parent(dir);
|
|
+ h_dir = au_h_iptr(dir, bindex);
|
|
+ ii_read_unlock(dir);
|
|
+ if (unlikely(!h_dir))
|
|
+ goto out_parent;
|
|
+ h_parent = d_find_any_alias(h_dir);
|
|
+ if (unlikely(!h_parent))
|
|
+ goto out_hparent;
|
|
+
|
|
+ err = -EPERM;
|
|
+ br = au_sbr(sb, bindex);
|
|
+ h_sb = au_br_sb(br);
|
|
+ if (unlikely(!h_sb->s_export_op)) {
|
|
+ AuErr1("%s branch is not exportable\n", au_sbtype(h_sb));
|
|
+ goto out_hparent;
|
|
+ }
|
|
+
|
|
+ fh[Fh_br_id] = br->br_id;
|
|
+ fh[Fh_sigen] = au_sigen(sb);
|
|
+ encode_ino(fh + Fh_ino, inode->i_ino);
|
|
+ encode_ino(fh + Fh_dir_ino, dir->i_ino);
|
|
+ fh[Fh_igen] = inode->i_generation;
|
|
+
|
|
+ *max_len -= Fh_tail;
|
|
+ fh[Fh_h_type] = exportfs_encode_fh(h_parent, (void *)(fh + Fh_tail),
|
|
+ max_len,
|
|
+ /*connectable or subtreecheck*/0);
|
|
+ err = fh[Fh_h_type];
|
|
+ *max_len += Fh_tail;
|
|
+ /* todo: macros? */
|
|
+ if (err != FILEID_INVALID)
|
|
+ err = 99;
|
|
+ else
|
|
+ AuWarn1("%s encode_fh failed\n", au_sbtype(h_sb));
|
|
+
|
|
+out_hparent:
|
|
+ dput(h_parent);
|
|
+out_parent:
|
|
+ dput(parent);
|
|
+out_unlock:
|
|
+ ii_read_unlock(inode);
|
|
+ si_read_unlock(sb);
|
|
+out:
|
|
+ if (unlikely(err < 0))
|
|
+ err = FILEID_INVALID;
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int aufs_commit_metadata(struct inode *inode)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct super_block *sb;
|
|
+ struct inode *h_inode;
|
|
+ int (*f)(struct inode *inode);
|
|
+
|
|
+ sb = inode->i_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW);
|
|
+ ii_write_lock_child(inode);
|
|
+ bindex = au_ibtop(inode);
|
|
+ AuDebugOn(bindex < 0);
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+
|
|
+ f = h_inode->i_sb->s_export_op->commit_metadata;
|
|
+ if (f)
|
|
+ err = f(h_inode);
|
|
+ else {
|
|
+ struct writeback_control wbc = {
|
|
+ .sync_mode = WB_SYNC_ALL,
|
|
+ .nr_to_write = 0 /* metadata only */
|
|
+ };
|
|
+
|
|
+ err = sync_inode(h_inode, &wbc);
|
|
+ }
|
|
+
|
|
+ au_cpup_attr_timesizes(inode);
|
|
+ ii_write_unlock(inode);
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static struct export_operations aufs_export_op = {
|
|
+ .fh_to_dentry = aufs_fh_to_dentry,
|
|
+ /* .fh_to_parent = aufs_fh_to_parent, */
|
|
+ .encode_fh = aufs_encode_fh,
|
|
+ .commit_metadata = aufs_commit_metadata
|
|
+};
|
|
+
|
|
+void au_export_init(struct super_block *sb)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ __u32 u;
|
|
+
|
|
+ BUILD_BUG_ON_MSG(IS_BUILTIN(CONFIG_AUFS_FS)
|
|
+ && IS_MODULE(CONFIG_EXPORTFS),
|
|
+ AUFS_NAME ": unsupported configuration "
|
|
+ "CONFIG_EXPORTFS=m and CONFIG_AUFS_FS=y");
|
|
+
|
|
+ sb->s_export_op = &aufs_export_op;
|
|
+ sbinfo = au_sbi(sb);
|
|
+ sbinfo->si_xigen = NULL;
|
|
+ get_random_bytes(&u, sizeof(u));
|
|
+ BUILD_BUG_ON(sizeof(u) != sizeof(int));
|
|
+ atomic_set(&sbinfo->si_xigen_next, u);
|
|
+}
|
|
diff --git a/fs/aufs/f_op.c b/fs/aufs/f_op.c
|
|
new file mode 100644
|
|
index 000000000000..9894e2124bd5
|
|
--- /dev/null
|
|
+++ b/fs/aufs/f_op.c
|
|
@@ -0,0 +1,819 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * file and vm operations
|
|
+ */
|
|
+
|
|
+#include <linux/aio.h>
|
|
+#include <linux/fs_stack.h>
|
|
+#include <linux/mman.h>
|
|
+#include <linux/security.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+int au_do_open_nondir(struct file *file, int flags, struct file *h_file)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct dentry *dentry, *h_dentry;
|
|
+ struct au_finfo *finfo;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ FiMustWriteLock(file);
|
|
+
|
|
+ err = 0;
|
|
+ dentry = file->f_path.dentry;
|
|
+ AuDebugOn(IS_ERR_OR_NULL(dentry));
|
|
+ finfo = au_fi(file);
|
|
+ memset(&finfo->fi_htop, 0, sizeof(finfo->fi_htop));
|
|
+ atomic_set(&finfo->fi_mmapped, 0);
|
|
+ bindex = au_dbtop(dentry);
|
|
+ if (!h_file) {
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ err = vfsub_test_mntns(file->f_path.mnt, h_dentry->d_sb);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ h_file = au_h_open(dentry, bindex, flags, file, /*force_wr*/0);
|
|
+ if (IS_ERR(h_file)) {
|
|
+ err = PTR_ERR(h_file);
|
|
+ goto out;
|
|
+ }
|
|
+ } else {
|
|
+ h_dentry = h_file->f_path.dentry;
|
|
+ err = vfsub_test_mntns(file->f_path.mnt, h_dentry->d_sb);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ /* br ref is already inc-ed */
|
|
+ }
|
|
+
|
|
+ if ((flags & __O_TMPFILE)
|
|
+ && !(flags & O_EXCL)) {
|
|
+ h_inode = file_inode(h_file);
|
|
+ spin_lock(&h_inode->i_lock);
|
|
+ h_inode->i_state |= I_LINKABLE;
|
|
+ spin_unlock(&h_inode->i_lock);
|
|
+ }
|
|
+ au_set_fbtop(file, bindex);
|
|
+ au_set_h_fptr(file, bindex, h_file);
|
|
+ au_update_figen(file);
|
|
+ /* todo: necessary? */
|
|
+ /* file->f_ra = h_file->f_ra; */
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_open_nondir(struct inode *inode __maybe_unused,
|
|
+ struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ struct super_block *sb;
|
|
+ struct au_do_open_args args = {
|
|
+ .open = au_do_open_nondir
|
|
+ };
|
|
+
|
|
+ AuDbg("%pD, f_flags 0x%x, f_mode 0x%x\n",
|
|
+ file, vfsub_file_flags(file), file->f_mode);
|
|
+
|
|
+ sb = file->f_path.dentry->d_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH);
|
|
+ err = au_do_open(file, &args);
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int aufs_release_nondir(struct inode *inode __maybe_unused, struct file *file)
|
|
+{
|
|
+ struct au_finfo *finfo;
|
|
+ aufs_bindex_t bindex;
|
|
+
|
|
+ finfo = au_fi(file);
|
|
+ au_hbl_del(&finfo->fi_hlist,
|
|
+ &au_sbi(file->f_path.dentry->d_sb)->si_files);
|
|
+ bindex = finfo->fi_btop;
|
|
+ if (bindex >= 0)
|
|
+ au_set_h_fptr(file, bindex, NULL);
|
|
+
|
|
+ au_finfo_fin(file);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_do_flush_nondir(struct file *file, fl_owner_t id)
|
|
+{
|
|
+ int err;
|
|
+ struct file *h_file;
|
|
+
|
|
+ err = 0;
|
|
+ h_file = au_hf_top(file);
|
|
+ if (h_file)
|
|
+ err = vfsub_flush(h_file, id);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_flush_nondir(struct file *file, fl_owner_t id)
|
|
+{
|
|
+ return au_do_flush(file, id, au_do_flush_nondir);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+/*
|
|
+ * read and write functions acquire [fdi]_rwsem once, but release before
|
|
+ * mmap_sem. This is because to stop a race condition between mmap(2).
|
|
+ * Releasing these aufs-rwsem should be safe, no branch-management (by keeping
|
|
+ * si_rwsem), no harmful copy-up should happen. Actually copy-up may happen in
|
|
+ * read functions after [fdi]_rwsem are released, but it should be harmless.
|
|
+ */
|
|
+
|
|
+/* Callers should call au_read_post() or fput() in the end */
|
|
+struct file *au_read_pre(struct file *file, int keep_fi, unsigned int lsc)
|
|
+{
|
|
+ struct file *h_file;
|
|
+ int err;
|
|
+
|
|
+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0, lsc);
|
|
+ if (!err) {
|
|
+ di_read_unlock(file->f_path.dentry, AuLock_IR);
|
|
+ h_file = au_hf_top(file);
|
|
+ get_file(h_file);
|
|
+ if (!keep_fi)
|
|
+ fi_read_unlock(file);
|
|
+ } else
|
|
+ h_file = ERR_PTR(err);
|
|
+
|
|
+ return h_file;
|
|
+}
|
|
+
|
|
+static void au_read_post(struct inode *inode, struct file *h_file)
|
|
+{
|
|
+ /* update without lock, I don't think it a problem */
|
|
+ fsstack_copy_attr_atime(inode, file_inode(h_file));
|
|
+ fput(h_file);
|
|
+}
|
|
+
|
|
+struct au_write_pre {
|
|
+ /* input */
|
|
+ unsigned int lsc;
|
|
+
|
|
+ /* output */
|
|
+ blkcnt_t blks;
|
|
+ aufs_bindex_t btop;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * return with iinfo is write-locked
|
|
+ * callers should call au_write_post() or iinfo_write_unlock() + fput() in the
|
|
+ * end
|
|
+ */
|
|
+static struct file *au_write_pre(struct file *file, int do_ready,
|
|
+ struct au_write_pre *wpre)
|
|
+{
|
|
+ struct file *h_file;
|
|
+ struct dentry *dentry;
|
|
+ int err;
|
|
+ unsigned int lsc;
|
|
+ struct au_pin pin;
|
|
+
|
|
+ lsc = 0;
|
|
+ if (wpre)
|
|
+ lsc = wpre->lsc;
|
|
+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1, lsc);
|
|
+ h_file = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ dentry = file->f_path.dentry;
|
|
+ if (do_ready) {
|
|
+ err = au_ready_to_write(file, -1, &pin);
|
|
+ if (unlikely(err)) {
|
|
+ h_file = ERR_PTR(err);
|
|
+ di_write_unlock(dentry);
|
|
+ goto out_fi;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ di_downgrade_lock(dentry, /*flags*/0);
|
|
+ if (wpre)
|
|
+ wpre->btop = au_fbtop(file);
|
|
+ h_file = au_hf_top(file);
|
|
+ get_file(h_file);
|
|
+ if (wpre)
|
|
+ wpre->blks = file_inode(h_file)->i_blocks;
|
|
+ if (do_ready)
|
|
+ au_unpin(&pin);
|
|
+ di_read_unlock(dentry, /*flags*/0);
|
|
+
|
|
+out_fi:
|
|
+ fi_write_unlock(file);
|
|
+out:
|
|
+ return h_file;
|
|
+}
|
|
+
|
|
+static void au_write_post(struct inode *inode, struct file *h_file,
|
|
+ struct au_write_pre *wpre, ssize_t written)
|
|
+{
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ au_cpup_attr_timesizes(inode);
|
|
+ AuDebugOn(au_ibtop(inode) != wpre->btop);
|
|
+ h_inode = file_inode(h_file);
|
|
+ inode->i_mode = h_inode->i_mode;
|
|
+ ii_write_unlock(inode);
|
|
+ /* AuDbg("blks %llu, %llu\n", (u64)blks, (u64)h_inode->i_blocks); */
|
|
+ if (written > 0)
|
|
+ au_fhsm_wrote(inode->i_sb, wpre->btop,
|
|
+ /*force*/h_inode->i_blocks > wpre->blks);
|
|
+ fput(h_file);
|
|
+}
|
|
+
|
|
+static ssize_t aufs_read(struct file *file, char __user *buf, size_t count,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ ssize_t err;
|
|
+ struct inode *inode;
|
|
+ struct file *h_file;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ inode = file_inode(file);
|
|
+ sb = inode->i_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW);
|
|
+
|
|
+ h_file = au_read_pre(file, /*keep_fi*/0, /*lsc*/0);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ /* filedata may be obsoleted by concurrent copyup, but no problem */
|
|
+ err = vfsub_read_u(h_file, buf, count, ppos);
|
|
+ /* todo: necessary? */
|
|
+ /* file->f_ra = h_file->f_ra; */
|
|
+ au_read_post(inode, h_file);
|
|
+
|
|
+out:
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * todo: very ugly
|
|
+ * it locks both of i_mutex and si_rwsem for read in safe.
|
|
+ * if the plink maintenance mode continues forever (that is the problem),
|
|
+ * may loop forever.
|
|
+ */
|
|
+static void au_mtx_and_read_lock(struct inode *inode)
|
|
+{
|
|
+ int err;
|
|
+ struct super_block *sb = inode->i_sb;
|
|
+
|
|
+ while (1) {
|
|
+ inode_lock(inode);
|
|
+ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
|
|
+ if (!err)
|
|
+ break;
|
|
+ inode_unlock(inode);
|
|
+ si_read_lock(sb, AuLock_NOPLMW);
|
|
+ si_read_unlock(sb);
|
|
+ }
|
|
+}
|
|
+
|
|
+static ssize_t aufs_write(struct file *file, const char __user *ubuf,
|
|
+ size_t count, loff_t *ppos)
|
|
+{
|
|
+ ssize_t err;
|
|
+ struct au_write_pre wpre;
|
|
+ struct inode *inode;
|
|
+ struct file *h_file;
|
|
+ char __user *buf = (char __user *)ubuf;
|
|
+
|
|
+ inode = file_inode(file);
|
|
+ au_mtx_and_read_lock(inode);
|
|
+
|
|
+ wpre.lsc = 0;
|
|
+ h_file = au_write_pre(file, /*do_ready*/1, &wpre);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ err = vfsub_write_u(h_file, buf, count, ppos);
|
|
+ au_write_post(inode, h_file, &wpre, err);
|
|
+
|
|
+out:
|
|
+ si_read_unlock(inode->i_sb);
|
|
+ inode_unlock(inode);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t au_do_iter(struct file *h_file, int rw, struct kiocb *kio,
|
|
+ struct iov_iter *iov_iter)
|
|
+{
|
|
+ ssize_t err;
|
|
+ struct file *file;
|
|
+ ssize_t (*iter)(struct kiocb *, struct iov_iter *);
|
|
+
|
|
+ err = security_file_permission(h_file, rw);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ err = -ENOSYS; /* the branch doesn't have its ->(read|write)_iter() */
|
|
+ iter = NULL;
|
|
+ if (rw == MAY_READ)
|
|
+ iter = h_file->f_op->read_iter;
|
|
+ else if (rw == MAY_WRITE)
|
|
+ iter = h_file->f_op->write_iter;
|
|
+
|
|
+ file = kio->ki_filp;
|
|
+ kio->ki_filp = h_file;
|
|
+ if (iter) {
|
|
+ lockdep_off();
|
|
+ err = iter(kio, iov_iter);
|
|
+ lockdep_on();
|
|
+ } else
|
|
+ /* currently there is no such fs */
|
|
+ WARN_ON_ONCE(1);
|
|
+ kio->ki_filp = file;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t aufs_read_iter(struct kiocb *kio, struct iov_iter *iov_iter)
|
|
+{
|
|
+ ssize_t err;
|
|
+ struct file *file, *h_file;
|
|
+ struct inode *inode;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ file = kio->ki_filp;
|
|
+ inode = file_inode(file);
|
|
+ sb = inode->i_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW);
|
|
+
|
|
+ h_file = au_read_pre(file, /*keep_fi*/1, /*lsc*/0);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ if (au_test_loopback_kthread()) {
|
|
+ au_warn_loopback(h_file->f_path.dentry->d_sb);
|
|
+ if (file->f_mapping != h_file->f_mapping) {
|
|
+ file->f_mapping = h_file->f_mapping;
|
|
+ smp_mb(); /* unnecessary? */
|
|
+ }
|
|
+ }
|
|
+ fi_read_unlock(file);
|
|
+
|
|
+ err = au_do_iter(h_file, MAY_READ, kio, iov_iter);
|
|
+ /* todo: necessary? */
|
|
+ /* file->f_ra = h_file->f_ra; */
|
|
+ au_read_post(inode, h_file);
|
|
+
|
|
+out:
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t aufs_write_iter(struct kiocb *kio, struct iov_iter *iov_iter)
|
|
+{
|
|
+ ssize_t err;
|
|
+ struct au_write_pre wpre;
|
|
+ struct inode *inode;
|
|
+ struct file *file, *h_file;
|
|
+
|
|
+ file = kio->ki_filp;
|
|
+ inode = file_inode(file);
|
|
+ au_mtx_and_read_lock(inode);
|
|
+
|
|
+ wpre.lsc = 0;
|
|
+ h_file = au_write_pre(file, /*do_ready*/1, &wpre);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ err = au_do_iter(h_file, MAY_WRITE, kio, iov_iter);
|
|
+ au_write_post(inode, h_file, &wpre, err);
|
|
+
|
|
+out:
|
|
+ si_read_unlock(inode->i_sb);
|
|
+ inode_unlock(inode);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t aufs_splice_read(struct file *file, loff_t *ppos,
|
|
+ struct pipe_inode_info *pipe, size_t len,
|
|
+ unsigned int flags)
|
|
+{
|
|
+ ssize_t err;
|
|
+ struct file *h_file;
|
|
+ struct inode *inode;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ inode = file_inode(file);
|
|
+ sb = inode->i_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW);
|
|
+
|
|
+ h_file = au_read_pre(file, /*keep_fi*/0, /*lsc*/0);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ err = vfsub_splice_to(h_file, ppos, pipe, len, flags);
|
|
+ /* todo: necessary? */
|
|
+ /* file->f_ra = h_file->f_ra; */
|
|
+ au_read_post(inode, h_file);
|
|
+
|
|
+out:
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t
|
|
+aufs_splice_write(struct pipe_inode_info *pipe, struct file *file, loff_t *ppos,
|
|
+ size_t len, unsigned int flags)
|
|
+{
|
|
+ ssize_t err;
|
|
+ struct au_write_pre wpre;
|
|
+ struct inode *inode;
|
|
+ struct file *h_file;
|
|
+
|
|
+ inode = file_inode(file);
|
|
+ au_mtx_and_read_lock(inode);
|
|
+
|
|
+ wpre.lsc = 0;
|
|
+ h_file = au_write_pre(file, /*do_ready*/1, &wpre);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ err = vfsub_splice_from(pipe, h_file, ppos, len, flags);
|
|
+ au_write_post(inode, h_file, &wpre, err);
|
|
+
|
|
+out:
|
|
+ si_read_unlock(inode->i_sb);
|
|
+ inode_unlock(inode);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static long aufs_fallocate(struct file *file, int mode, loff_t offset,
|
|
+ loff_t len)
|
|
+{
|
|
+ long err;
|
|
+ struct au_write_pre wpre;
|
|
+ struct inode *inode;
|
|
+ struct file *h_file;
|
|
+
|
|
+ inode = file_inode(file);
|
|
+ au_mtx_and_read_lock(inode);
|
|
+
|
|
+ wpre.lsc = 0;
|
|
+ h_file = au_write_pre(file, /*do_ready*/1, &wpre);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_fallocate(h_file, mode, offset, len);
|
|
+ lockdep_on();
|
|
+ au_write_post(inode, h_file, &wpre, /*written*/1);
|
|
+
|
|
+out:
|
|
+ si_read_unlock(inode->i_sb);
|
|
+ inode_unlock(inode);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t aufs_copy_file_range(struct file *src, loff_t src_pos,
|
|
+ struct file *dst, loff_t dst_pos,
|
|
+ size_t len, unsigned int flags)
|
|
+{
|
|
+ ssize_t err;
|
|
+ struct au_write_pre wpre;
|
|
+ enum { SRC, DST };
|
|
+ struct {
|
|
+ struct inode *inode;
|
|
+ struct file *h_file;
|
|
+ struct super_block *h_sb;
|
|
+ } a[2];
|
|
+#define a_src a[SRC]
|
|
+#define a_dst a[DST]
|
|
+
|
|
+ err = -EINVAL;
|
|
+ a_src.inode = file_inode(src);
|
|
+ if (unlikely(!S_ISREG(a_src.inode->i_mode)))
|
|
+ goto out;
|
|
+ a_dst.inode = file_inode(dst);
|
|
+ if (unlikely(!S_ISREG(a_dst.inode->i_mode)))
|
|
+ goto out;
|
|
+
|
|
+ au_mtx_and_read_lock(a_dst.inode);
|
|
+ /*
|
|
+ * in order to match the order in di_write_lock2_{child,parent}(),
|
|
+ * use f_path.dentry for this comparison.
|
|
+ */
|
|
+ if (src->f_path.dentry < dst->f_path.dentry) {
|
|
+ a_src.h_file = au_read_pre(src, /*keep_fi*/1, AuLsc_FI_1);
|
|
+ err = PTR_ERR(a_src.h_file);
|
|
+ if (IS_ERR(a_src.h_file))
|
|
+ goto out_si;
|
|
+
|
|
+ wpre.lsc = AuLsc_FI_2;
|
|
+ a_dst.h_file = au_write_pre(dst, /*do_ready*/1, &wpre);
|
|
+ err = PTR_ERR(a_dst.h_file);
|
|
+ if (IS_ERR(a_dst.h_file)) {
|
|
+ au_read_post(a_src.inode, a_src.h_file);
|
|
+ goto out_si;
|
|
+ }
|
|
+ } else {
|
|
+ wpre.lsc = AuLsc_FI_1;
|
|
+ a_dst.h_file = au_write_pre(dst, /*do_ready*/1, &wpre);
|
|
+ err = PTR_ERR(a_dst.h_file);
|
|
+ if (IS_ERR(a_dst.h_file))
|
|
+ goto out_si;
|
|
+
|
|
+ a_src.h_file = au_read_pre(src, /*keep_fi*/1, AuLsc_FI_2);
|
|
+ err = PTR_ERR(a_src.h_file);
|
|
+ if (IS_ERR(a_src.h_file)) {
|
|
+ au_write_post(a_dst.inode, a_dst.h_file, &wpre,
|
|
+ /*written*/0);
|
|
+ goto out_si;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = -EXDEV;
|
|
+ a_src.h_sb = file_inode(a_src.h_file)->i_sb;
|
|
+ a_dst.h_sb = file_inode(a_dst.h_file)->i_sb;
|
|
+ if (unlikely(a_src.h_sb != a_dst.h_sb)) {
|
|
+ AuDbgFile(src);
|
|
+ AuDbgFile(dst);
|
|
+ goto out_file;
|
|
+ }
|
|
+
|
|
+ err = vfsub_copy_file_range(a_src.h_file, src_pos, a_dst.h_file,
|
|
+ dst_pos, len, flags);
|
|
+
|
|
+out_file:
|
|
+ au_write_post(a_dst.inode, a_dst.h_file, &wpre, err);
|
|
+ fi_read_unlock(src);
|
|
+ au_read_post(a_src.inode, a_src.h_file);
|
|
+out_si:
|
|
+ si_read_unlock(a_dst.inode->i_sb);
|
|
+ inode_unlock(a_dst.inode);
|
|
+out:
|
|
+ return err;
|
|
+#undef a_src
|
|
+#undef a_dst
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * The locking order around current->mmap_sem.
|
|
+ * - in most and regular cases
|
|
+ * file I/O syscall -- aufs_read() or something
|
|
+ * -- si_rwsem for read -- mmap_sem
|
|
+ * (Note that [fdi]i_rwsem are released before mmap_sem).
|
|
+ * - in mmap case
|
|
+ * mmap(2) -- mmap_sem -- aufs_mmap() -- si_rwsem for read -- [fdi]i_rwsem
|
|
+ * This AB-BA order is definitely bad, but is not a problem since "si_rwsem for
|
|
+ * read" allows multiple processes to acquire it and [fdi]i_rwsem are not held
|
|
+ * in file I/O. Aufs needs to stop lockdep in aufs_mmap() though.
|
|
+ * It means that when aufs acquires si_rwsem for write, the process should never
|
|
+ * acquire mmap_sem.
|
|
+ *
|
|
+ * Actually aufs_iterate() holds [fdi]i_rwsem before mmap_sem, but this is not a
|
|
+ * problem either since any directory is not able to be mmap-ed.
|
|
+ * The similar scenario is applied to aufs_readlink() too.
|
|
+ */
|
|
+
|
|
+#if 0 /* stop calling security_file_mmap() */
|
|
+/* cf. linux/include/linux/mman.h: calc_vm_prot_bits() */
|
|
+#define AuConv_VM_PROT(f, b) _calc_vm_trans(f, VM_##b, PROT_##b)
|
|
+
|
|
+static unsigned long au_arch_prot_conv(unsigned long flags)
|
|
+{
|
|
+ /* currently ppc64 only */
|
|
+#ifdef CONFIG_PPC64
|
|
+ /* cf. linux/arch/powerpc/include/asm/mman.h */
|
|
+ AuDebugOn(arch_calc_vm_prot_bits(-1) != VM_SAO);
|
|
+ return AuConv_VM_PROT(flags, SAO);
|
|
+#else
|
|
+ AuDebugOn(arch_calc_vm_prot_bits(-1));
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static unsigned long au_prot_conv(unsigned long flags)
|
|
+{
|
|
+ return AuConv_VM_PROT(flags, READ)
|
|
+ | AuConv_VM_PROT(flags, WRITE)
|
|
+ | AuConv_VM_PROT(flags, EXEC)
|
|
+ | au_arch_prot_conv(flags);
|
|
+}
|
|
+
|
|
+/* cf. linux/include/linux/mman.h: calc_vm_flag_bits() */
|
|
+#define AuConv_VM_MAP(f, b) _calc_vm_trans(f, VM_##b, MAP_##b)
|
|
+
|
|
+static unsigned long au_flag_conv(unsigned long flags)
|
|
+{
|
|
+ return AuConv_VM_MAP(flags, GROWSDOWN)
|
|
+ | AuConv_VM_MAP(flags, DENYWRITE)
|
|
+ | AuConv_VM_MAP(flags, LOCKED);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int aufs_mmap(struct file *file, struct vm_area_struct *vma)
|
|
+{
|
|
+ int err;
|
|
+ const unsigned char wlock
|
|
+ = (file->f_mode & FMODE_WRITE) && (vma->vm_flags & VM_SHARED);
|
|
+ struct super_block *sb;
|
|
+ struct file *h_file;
|
|
+ struct inode *inode;
|
|
+
|
|
+ AuDbgVmRegion(file, vma);
|
|
+
|
|
+ inode = file_inode(file);
|
|
+ sb = inode->i_sb;
|
|
+ lockdep_off();
|
|
+ si_read_lock(sb, AuLock_NOPLMW);
|
|
+
|
|
+ h_file = au_write_pre(file, wlock, /*wpre*/NULL);
|
|
+ lockdep_on();
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ au_set_mmapped(file);
|
|
+ au_vm_file_reset(vma, h_file);
|
|
+ /*
|
|
+ * we cannot call security_mmap_file() here since it may acquire
|
|
+ * mmap_sem or i_mutex.
|
|
+ *
|
|
+ * err = security_mmap_file(h_file, au_prot_conv(vma->vm_flags),
|
|
+ * au_flag_conv(vma->vm_flags));
|
|
+ */
|
|
+ if (!err)
|
|
+ err = call_mmap(h_file, vma);
|
|
+ if (!err) {
|
|
+ au_vm_prfile_set(vma, file);
|
|
+ fsstack_copy_attr_atime(inode, file_inode(h_file));
|
|
+ goto out_fput; /* success */
|
|
+ }
|
|
+ au_unset_mmapped(file);
|
|
+ au_vm_file_reset(vma, file);
|
|
+
|
|
+out_fput:
|
|
+ lockdep_off();
|
|
+ ii_write_unlock(inode);
|
|
+ lockdep_on();
|
|
+ fput(h_file);
|
|
+out:
|
|
+ lockdep_off();
|
|
+ si_read_unlock(sb);
|
|
+ lockdep_on();
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int aufs_fsync_nondir(struct file *file, loff_t start, loff_t end,
|
|
+ int datasync)
|
|
+{
|
|
+ int err;
|
|
+ struct au_write_pre wpre;
|
|
+ struct inode *inode;
|
|
+ struct file *h_file;
|
|
+
|
|
+ err = 0; /* -EBADF; */ /* posix? */
|
|
+ if (unlikely(!(file->f_mode & FMODE_WRITE)))
|
|
+ goto out;
|
|
+
|
|
+ inode = file_inode(file);
|
|
+ au_mtx_and_read_lock(inode);
|
|
+
|
|
+ wpre.lsc = 0;
|
|
+ h_file = au_write_pre(file, /*do_ready*/1, &wpre);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out_unlock;
|
|
+
|
|
+ err = vfsub_fsync(h_file, &h_file->f_path, datasync);
|
|
+ au_write_post(inode, h_file, &wpre, /*written*/0);
|
|
+
|
|
+out_unlock:
|
|
+ si_read_unlock(inode->i_sb);
|
|
+ inode_unlock(inode);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_fasync(int fd, struct file *file, int flag)
|
|
+{
|
|
+ int err;
|
|
+ struct file *h_file;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ sb = file->f_path.dentry->d_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW);
|
|
+
|
|
+ h_file = au_read_pre(file, /*keep_fi*/0, /*lsc*/0);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ if (h_file->f_op->fasync)
|
|
+ err = h_file->f_op->fasync(fd, h_file, flag);
|
|
+ fput(h_file); /* instead of au_read_post() */
|
|
+
|
|
+out:
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_setfl(struct file *file, unsigned long arg)
|
|
+{
|
|
+ int err;
|
|
+ struct file *h_file;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ sb = file->f_path.dentry->d_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW);
|
|
+
|
|
+ h_file = au_read_pre(file, /*keep_fi*/0, /*lsc*/0);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out;
|
|
+
|
|
+ /* stop calling h_file->fasync */
|
|
+ arg |= vfsub_file_flags(file) & FASYNC;
|
|
+ err = setfl(/*unused fd*/-1, h_file, arg);
|
|
+ fput(h_file); /* instead of au_read_post() */
|
|
+
|
|
+out:
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* no one supports this operation, currently */
|
|
+#if 0 /* reserved for future use */
|
|
+static ssize_t aufs_sendpage(struct file *file, struct page *page, int offset,
|
|
+ size_t len, loff_t *pos, int more)
|
|
+{
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+const struct file_operations aufs_file_fop = {
|
|
+ .owner = THIS_MODULE,
|
|
+
|
|
+ .llseek = default_llseek,
|
|
+
|
|
+ .read = aufs_read,
|
|
+ .write = aufs_write,
|
|
+ .read_iter = aufs_read_iter,
|
|
+ .write_iter = aufs_write_iter,
|
|
+
|
|
+#ifdef CONFIG_AUFS_POLL
|
|
+ .poll = aufs_poll,
|
|
+#endif
|
|
+ .unlocked_ioctl = aufs_ioctl_nondir,
|
|
+#ifdef CONFIG_COMPAT
|
|
+ .compat_ioctl = aufs_compat_ioctl_nondir,
|
|
+#endif
|
|
+ .mmap = aufs_mmap,
|
|
+ .open = aufs_open_nondir,
|
|
+ .flush = aufs_flush_nondir,
|
|
+ .release = aufs_release_nondir,
|
|
+ .fsync = aufs_fsync_nondir,
|
|
+ .fasync = aufs_fasync,
|
|
+ /* .sendpage = aufs_sendpage, */
|
|
+ .setfl = aufs_setfl,
|
|
+ .splice_write = aufs_splice_write,
|
|
+ .splice_read = aufs_splice_read,
|
|
+#if 0 /* reserved for future use */
|
|
+ .aio_splice_write = aufs_aio_splice_write,
|
|
+ .aio_splice_read = aufs_aio_splice_read,
|
|
+#endif
|
|
+ .fallocate = aufs_fallocate,
|
|
+ .copy_file_range = aufs_copy_file_range
|
|
+};
|
|
diff --git a/fs/aufs/fhsm.c b/fs/aufs/fhsm.c
|
|
new file mode 100644
|
|
index 000000000000..9cef93b42993
|
|
--- /dev/null
|
|
+++ b/fs/aufs/fhsm.c
|
|
@@ -0,0 +1,427 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2011-2020 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
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * File-based Hierarchy Storage Management
|
|
+ */
|
|
+
|
|
+#include <linux/anon_inodes.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/seq_file.h>
|
|
+#include <linux/statfs.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+static aufs_bindex_t au_fhsm_bottom(struct super_block *sb)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_fhsm *fhsm;
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ fhsm = &sbinfo->si_fhsm;
|
|
+ AuDebugOn(!fhsm);
|
|
+ return fhsm->fhsm_bottom;
|
|
+}
|
|
+
|
|
+void au_fhsm_set_bottom(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_fhsm *fhsm;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ fhsm = &sbinfo->si_fhsm;
|
|
+ AuDebugOn(!fhsm);
|
|
+ fhsm->fhsm_bottom = bindex;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_fhsm_test_jiffy(struct au_sbinfo *sbinfo, struct au_branch *br)
|
|
+{
|
|
+ struct au_br_fhsm *bf;
|
|
+
|
|
+ bf = br->br_fhsm;
|
|
+ MtxMustLock(&bf->bf_lock);
|
|
+
|
|
+ return !bf->bf_readable
|
|
+ || time_after(jiffies,
|
|
+ bf->bf_jiffy + sbinfo->si_fhsm.fhsm_expire);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static void au_fhsm_notify(struct super_block *sb, int val)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_fhsm *fhsm;
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ fhsm = &sbinfo->si_fhsm;
|
|
+ if (au_fhsm_pid(fhsm)
|
|
+ && atomic_read(&fhsm->fhsm_readable) != -1) {
|
|
+ atomic_set(&fhsm->fhsm_readable, val);
|
|
+ if (val)
|
|
+ wake_up(&fhsm->fhsm_wqh);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int au_fhsm_stfs(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ struct aufs_stfs *rstfs, int do_lock, int do_notify)
|
|
+{
|
|
+ int err;
|
|
+ struct au_branch *br;
|
|
+ struct au_br_fhsm *bf;
|
|
+
|
|
+ br = au_sbr(sb, bindex);
|
|
+ AuDebugOn(au_br_rdonly(br));
|
|
+ bf = br->br_fhsm;
|
|
+ AuDebugOn(!bf);
|
|
+
|
|
+ if (do_lock)
|
|
+ mutex_lock(&bf->bf_lock);
|
|
+ else
|
|
+ MtxMustLock(&bf->bf_lock);
|
|
+
|
|
+ /* sb->s_root for NFS is unreliable */
|
|
+ err = au_br_stfs(br, &bf->bf_stfs);
|
|
+ if (unlikely(err)) {
|
|
+ AuErr1("FHSM failed (%d), b%d, ignored.\n", bindex, err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ bf->bf_jiffy = jiffies;
|
|
+ bf->bf_readable = 1;
|
|
+ if (do_notify)
|
|
+ au_fhsm_notify(sb, /*val*/1);
|
|
+ if (rstfs)
|
|
+ *rstfs = bf->bf_stfs;
|
|
+
|
|
+out:
|
|
+ if (do_lock)
|
|
+ mutex_unlock(&bf->bf_lock);
|
|
+ au_fhsm_notify(sb, /*val*/1);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_fhsm_wrote(struct super_block *sb, aufs_bindex_t bindex, int force)
|
|
+{
|
|
+ int err;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_fhsm *fhsm;
|
|
+ struct au_branch *br;
|
|
+ struct au_br_fhsm *bf;
|
|
+
|
|
+ AuDbg("b%d, force %d\n", bindex, force);
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ fhsm = &sbinfo->si_fhsm;
|
|
+ if (!au_ftest_si(sbinfo, FHSM)
|
|
+ || fhsm->fhsm_bottom == bindex)
|
|
+ return;
|
|
+
|
|
+ br = au_sbr(sb, bindex);
|
|
+ bf = br->br_fhsm;
|
|
+ AuDebugOn(!bf);
|
|
+ mutex_lock(&bf->bf_lock);
|
|
+ if (force
|
|
+ || au_fhsm_pid(fhsm)
|
|
+ || au_fhsm_test_jiffy(sbinfo, br))
|
|
+ err = au_fhsm_stfs(sb, bindex, /*rstfs*/NULL, /*do_lock*/0,
|
|
+ /*do_notify*/1);
|
|
+ mutex_unlock(&bf->bf_lock);
|
|
+}
|
|
+
|
|
+void au_fhsm_wrote_all(struct super_block *sb, int force)
|
|
+{
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ /* exclude the bottom */
|
|
+ bbot = au_fhsm_bottom(sb);
|
|
+ for (bindex = 0; bindex < bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (au_br_fhsm(br->br_perm))
|
|
+ au_fhsm_wrote(sb, bindex, force);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static __poll_t au_fhsm_poll(struct file *file, struct poll_table_struct *wait)
|
|
+{
|
|
+ __poll_t mask;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_fhsm *fhsm;
|
|
+
|
|
+ mask = 0;
|
|
+ sbinfo = file->private_data;
|
|
+ fhsm = &sbinfo->si_fhsm;
|
|
+ poll_wait(file, &fhsm->fhsm_wqh, wait);
|
|
+ if (atomic_read(&fhsm->fhsm_readable))
|
|
+ mask = EPOLLIN /* | EPOLLRDNORM */;
|
|
+
|
|
+ if (!mask)
|
|
+ AuDbg("mask 0x%x\n", mask);
|
|
+ return mask;
|
|
+}
|
|
+
|
|
+static int au_fhsm_do_read_one(struct aufs_stbr __user *stbr,
|
|
+ struct aufs_stfs *stfs, __s16 brid)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = copy_to_user(&stbr->stfs, stfs, sizeof(*stfs));
|
|
+ if (!err)
|
|
+ err = __put_user(brid, &stbr->brid);
|
|
+ if (unlikely(err))
|
|
+ err = -EFAULT;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t au_fhsm_do_read(struct super_block *sb,
|
|
+ struct aufs_stbr __user *stbr, size_t count)
|
|
+{
|
|
+ ssize_t err;
|
|
+ int nstbr;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_branch *br;
|
|
+ struct au_br_fhsm *bf;
|
|
+
|
|
+ /* except the bottom branch */
|
|
+ err = 0;
|
|
+ nstbr = 0;
|
|
+ bbot = au_fhsm_bottom(sb);
|
|
+ for (bindex = 0; !err && bindex < bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (!au_br_fhsm(br->br_perm))
|
|
+ continue;
|
|
+
|
|
+ bf = br->br_fhsm;
|
|
+ mutex_lock(&bf->bf_lock);
|
|
+ if (bf->bf_readable) {
|
|
+ err = -EFAULT;
|
|
+ if (count >= sizeof(*stbr))
|
|
+ err = au_fhsm_do_read_one(stbr++, &bf->bf_stfs,
|
|
+ br->br_id);
|
|
+ if (!err) {
|
|
+ bf->bf_readable = 0;
|
|
+ count -= sizeof(*stbr);
|
|
+ nstbr++;
|
|
+ }
|
|
+ }
|
|
+ mutex_unlock(&bf->bf_lock);
|
|
+ }
|
|
+ if (!err)
|
|
+ err = sizeof(*stbr) * nstbr;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t au_fhsm_read(struct file *file, char __user *buf, size_t count,
|
|
+ loff_t *pos)
|
|
+{
|
|
+ ssize_t err;
|
|
+ int readable;
|
|
+ aufs_bindex_t nfhsm, bindex, bbot;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_fhsm *fhsm;
|
|
+ struct au_branch *br;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ err = 0;
|
|
+ sbinfo = file->private_data;
|
|
+ fhsm = &sbinfo->si_fhsm;
|
|
+need_data:
|
|
+ spin_lock_irq(&fhsm->fhsm_wqh.lock);
|
|
+ if (!atomic_read(&fhsm->fhsm_readable)) {
|
|
+ if (vfsub_file_flags(file) & O_NONBLOCK)
|
|
+ err = -EAGAIN;
|
|
+ else
|
|
+ err = wait_event_interruptible_locked_irq
|
|
+ (fhsm->fhsm_wqh,
|
|
+ atomic_read(&fhsm->fhsm_readable));
|
|
+ }
|
|
+ spin_unlock_irq(&fhsm->fhsm_wqh.lock);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ /* sb may already be dead */
|
|
+ au_rw_read_lock(&sbinfo->si_rwsem);
|
|
+ readable = atomic_read(&fhsm->fhsm_readable);
|
|
+ if (readable > 0) {
|
|
+ sb = sbinfo->si_sb;
|
|
+ AuDebugOn(!sb);
|
|
+ /* exclude the bottom branch */
|
|
+ nfhsm = 0;
|
|
+ bbot = au_fhsm_bottom(sb);
|
|
+ for (bindex = 0; bindex < bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (au_br_fhsm(br->br_perm))
|
|
+ nfhsm++;
|
|
+ }
|
|
+ err = -EMSGSIZE;
|
|
+ if (nfhsm * sizeof(struct aufs_stbr) <= count) {
|
|
+ atomic_set(&fhsm->fhsm_readable, 0);
|
|
+ err = au_fhsm_do_read(sbinfo->si_sb, (void __user *)buf,
|
|
+ count);
|
|
+ }
|
|
+ }
|
|
+ au_rw_read_unlock(&sbinfo->si_rwsem);
|
|
+ if (!readable)
|
|
+ goto need_data;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_fhsm_release(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_fhsm *fhsm;
|
|
+
|
|
+ /* sb may already be dead */
|
|
+ sbinfo = file->private_data;
|
|
+ fhsm = &sbinfo->si_fhsm;
|
|
+ spin_lock(&fhsm->fhsm_spin);
|
|
+ fhsm->fhsm_pid = 0;
|
|
+ spin_unlock(&fhsm->fhsm_spin);
|
|
+ kobject_put(&sbinfo->si_kobj);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct file_operations au_fhsm_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .llseek = noop_llseek,
|
|
+ .read = au_fhsm_read,
|
|
+ .poll = au_fhsm_poll,
|
|
+ .release = au_fhsm_release
|
|
+};
|
|
+
|
|
+int au_fhsm_fd(struct super_block *sb, int oflags)
|
|
+{
|
|
+ int err, fd;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_fhsm *fhsm;
|
|
+
|
|
+ err = -EPERM;
|
|
+ if (unlikely(!capable(CAP_SYS_ADMIN)))
|
|
+ goto out;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (unlikely(oflags & ~(O_CLOEXEC | O_NONBLOCK)))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ sbinfo = au_sbi(sb);
|
|
+ fhsm = &sbinfo->si_fhsm;
|
|
+ spin_lock(&fhsm->fhsm_spin);
|
|
+ if (!fhsm->fhsm_pid)
|
|
+ fhsm->fhsm_pid = current->pid;
|
|
+ else
|
|
+ err = -EBUSY;
|
|
+ spin_unlock(&fhsm->fhsm_spin);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ oflags |= O_RDONLY;
|
|
+ /* oflags |= FMODE_NONOTIFY; */
|
|
+ fd = anon_inode_getfd("[aufs_fhsm]", &au_fhsm_fops, sbinfo, oflags);
|
|
+ err = fd;
|
|
+ if (unlikely(fd < 0))
|
|
+ goto out_pid;
|
|
+
|
|
+ /* succeed regardless 'fhsm' status */
|
|
+ kobject_get(&sbinfo->si_kobj);
|
|
+ si_noflush_read_lock(sb);
|
|
+ if (au_ftest_si(sbinfo, FHSM))
|
|
+ au_fhsm_wrote_all(sb, /*force*/0);
|
|
+ si_read_unlock(sb);
|
|
+ goto out; /* success */
|
|
+
|
|
+out_pid:
|
|
+ spin_lock(&fhsm->fhsm_spin);
|
|
+ fhsm->fhsm_pid = 0;
|
|
+ spin_unlock(&fhsm->fhsm_spin);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_fhsm_br_alloc(struct au_branch *br)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ br->br_fhsm = kmalloc(sizeof(*br->br_fhsm), GFP_NOFS);
|
|
+ if (br->br_fhsm)
|
|
+ au_br_fhsm_init(br->br_fhsm);
|
|
+ else
|
|
+ err = -ENOMEM;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void au_fhsm_fin(struct super_block *sb)
|
|
+{
|
|
+ au_fhsm_notify(sb, /*val*/-1);
|
|
+}
|
|
+
|
|
+void au_fhsm_init(struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ struct au_fhsm *fhsm;
|
|
+
|
|
+ fhsm = &sbinfo->si_fhsm;
|
|
+ spin_lock_init(&fhsm->fhsm_spin);
|
|
+ init_waitqueue_head(&fhsm->fhsm_wqh);
|
|
+ atomic_set(&fhsm->fhsm_readable, 0);
|
|
+ fhsm->fhsm_expire
|
|
+ = msecs_to_jiffies(AUFS_FHSM_CACHE_DEF_SEC * MSEC_PER_SEC);
|
|
+ fhsm->fhsm_bottom = -1;
|
|
+}
|
|
+
|
|
+void au_fhsm_set(struct au_sbinfo *sbinfo, unsigned int sec)
|
|
+{
|
|
+ sbinfo->si_fhsm.fhsm_expire
|
|
+ = msecs_to_jiffies(sec * MSEC_PER_SEC);
|
|
+}
|
|
+
|
|
+void au_fhsm_show(struct seq_file *seq, struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ unsigned int u;
|
|
+
|
|
+ if (!au_ftest_si(sbinfo, FHSM))
|
|
+ return;
|
|
+
|
|
+ u = jiffies_to_msecs(sbinfo->si_fhsm.fhsm_expire) / MSEC_PER_SEC;
|
|
+ if (u != AUFS_FHSM_CACHE_DEF_SEC)
|
|
+ seq_printf(seq, ",fhsm_sec=%u", u);
|
|
+}
|
|
diff --git a/fs/aufs/file.c b/fs/aufs/file.c
|
|
new file mode 100644
|
|
index 000000000000..b0075b57d8bc
|
|
--- /dev/null
|
|
+++ b/fs/aufs/file.c
|
|
@@ -0,0 +1,863 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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;
|
|
+ au_lcnt_inc(&br->br_nfiles);
|
|
+ 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:
|
|
+ au_lcnt_dec(&br->br_nfiles);
|
|
+out:
|
|
+ return h_file;
|
|
+}
|
|
+
|
|
+static int au_cmoo(struct dentry *dentry)
|
|
+{
|
|
+ int err, cmoo, matched;
|
|
+ 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_dbtop(dentry);
|
|
+ if (!cpg.bsrc)
|
|
+ goto out;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ sbinfo = au_sbi(sb);
|
|
+ fhsm = &sbinfo->si_fhsm;
|
|
+ pid = au_fhsm_pid(fhsm);
|
|
+ rcu_read_lock();
|
|
+ matched = (pid
|
|
+ && (current->pid == pid
|
|
+ || rcu_dereference(current->real_parent)->pid == pid));
|
|
+ rcu_read_unlock();
|
|
+ if (matched)
|
|
+ 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, aopen = args->aopen;
|
|
+ struct dentry *dentry;
|
|
+ struct au_finfo *finfo;
|
|
+
|
|
+ if (!aopen)
|
|
+ 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));
|
|
+ di_write_lock_child(dentry);
|
|
+ err = au_cmoo(dentry);
|
|
+ di_downgrade_lock(dentry, AuLock_IR);
|
|
+ if (!err) {
|
|
+ if (!aopen)
|
|
+ err = args->open(file, vfsub_file_flags(file), NULL);
|
|
+ else {
|
|
+ lockdep_off();
|
|
+ err = args->open(file, vfsub_file_flags(file),
|
|
+ args->h_file);
|
|
+ lockdep_on();
|
|
+ }
|
|
+ }
|
|
+ di_read_unlock(dentry, AuLock_IR);
|
|
+
|
|
+ finfo = au_fi(file);
|
|
+ if (!err) {
|
|
+ finfo->fi_file = file;
|
|
+ au_hbl_add(&finfo->fi_hlist,
|
|
+ &au_sbi(file->f_path.dentry->d_sb)->si_files);
|
|
+ }
|
|
+ if (!aopen)
|
|
+ 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:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_reopen_nondir(struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t btop;
|
|
+ struct dentry *dentry;
|
|
+ struct au_branch *br;
|
|
+ struct file *h_file, *h_file_tmp;
|
|
+
|
|
+ dentry = file->f_path.dentry;
|
|
+ btop = au_dbtop(dentry);
|
|
+ br = au_sbr(dentry->d_sb, btop);
|
|
+ h_file_tmp = NULL;
|
|
+ if (au_fbtop(file) == btop) {
|
|
+ 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_lcnt_inc(&br->br_nfiles);
|
|
+ au_set_h_fptr(file, btop, NULL);
|
|
+ }
|
|
+ AuDebugOn(au_fi(file)->fi_hdir);
|
|
+ /*
|
|
+ * it can happen
|
|
+ * file exists on both of rw and ro
|
|
+ * open --> dbtop and fbtop are both 0
|
|
+ * prepend a branch as rw, "rw" become ro
|
|
+ * remove rw/file
|
|
+ * delete the top branch, "rw" becomes rw again
|
|
+ * --> dbtop is 1, fbtop is still 0
|
|
+ * write --> fbtop is 0 but dbtop is 1
|
|
+ */
|
|
+ /* AuDebugOn(au_fbtop(file) < btop); */
|
|
+
|
|
+ h_file = au_h_open(dentry, btop, vfsub_file_flags(file) & ~O_TRUNC,
|
|
+ file, /*force_wr*/0);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file)) {
|
|
+ if (h_file_tmp) {
|
|
+ /* revert */
|
|
+ au_set_h_fptr(file, btop, h_file_tmp);
|
|
+ h_file_tmp = NULL;
|
|
+ }
|
|
+ goto out; /* todo: close all? */
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ au_set_fbtop(file, btop);
|
|
+ au_set_h_fptr(file, btop, h_file);
|
|
+ au_update_figen(file);
|
|
+ /* todo: necessary? */
|
|
+ /* file->f_ra = h_file->f_ra; */
|
|
+
|
|
+out:
|
|
+ if (h_file_tmp) {
|
|
+ fput(h_file_tmp);
|
|
+ au_lcnt_dec(&br->br_nfiles);
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_reopen_wh(struct file *file, aufs_bindex_t btgt,
|
|
+ struct dentry *hi_wh)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t btop;
|
|
+ struct au_dinfo *dinfo;
|
|
+ struct dentry *h_dentry;
|
|
+ struct au_hdentry *hdp;
|
|
+
|
|
+ dinfo = au_di(file->f_path.dentry);
|
|
+ AuRwMustWriteLock(&dinfo->di_rwsem);
|
|
+
|
|
+ btop = dinfo->di_btop;
|
|
+ dinfo->di_btop = btgt;
|
|
+ hdp = au_hdentry(dinfo, btgt);
|
|
+ h_dentry = hdp->hd_dentry;
|
|
+ hdp->hd_dentry = hi_wh;
|
|
+ err = au_reopen_nondir(file);
|
|
+ hdp->hd_dentry = h_dentry;
|
|
+ dinfo->di_btop = btop;
|
|
+
|
|
+ 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_dbtop(cpg.dentry);
|
|
+ inode = d_inode(cpg.dentry);
|
|
+ h_inode = NULL;
|
|
+ if (au_dbtop(cpg.dentry) <= bcpup
|
|
+ && au_dbbot(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 dbtop;
|
|
+ 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_fbtop(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;
|
|
+
|
|
+ dbtop = au_dbtop(cpg.dentry);
|
|
+ if (dbtop <= cpg.bdst)
|
|
+ cpg.bsrc = cpg.bdst;
|
|
+
|
|
+ if (dbtop <= 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 (dbtop > 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_ibtop(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_dbtop(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, bbot, 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;
|
|
+ bbot = fidir->fd_bbot;
|
|
+ for (bindex = finfo->fi_btop; bindex <= bbot; 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)) {
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (finfo->fi_btop = 0; finfo->fi_btop <= bbot;
|
|
+ finfo->fi_btop++, p++)
|
|
+ if (p->hf_file) {
|
|
+ if (file_inode(p->hf_file))
|
|
+ break;
|
|
+ au_hfput(p, /*execed*/0);
|
|
+ }
|
|
+ } else {
|
|
+ bbot = au_br_index(sb, brid);
|
|
+ for (finfo->fi_btop = 0; finfo->fi_btop < bbot;
|
|
+ finfo->fi_btop++, p++)
|
|
+ if (p->hf_file)
|
|
+ au_hfput(p, /*execed*/0);
|
|
+ bbot = au_sbbot(sb);
|
|
+ }
|
|
+
|
|
+ p = fidir->fd_hfile + bbot;
|
|
+ for (fidir->fd_bbot = bbot; fidir->fd_bbot >= finfo->fi_btop;
|
|
+ fidir->fd_bbot--, p--)
|
|
+ if (p->hf_file) {
|
|
+ if (file_inode(p->hf_file))
|
|
+ break;
|
|
+ au_hfput(p, /*execed*/0);
|
|
+ }
|
|
+ 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, nbr;
|
|
+ aufs_bindex_t bbot, bindex;
|
|
+ struct dentry *dentry;
|
|
+ struct super_block *sb;
|
|
+ struct au_finfo *finfo;
|
|
+ struct au_hfile *hfile;
|
|
+
|
|
+ dentry = file->f_path.dentry;
|
|
+ sb = dentry->d_sb;
|
|
+ nbr = au_sbbot(sb) + 1;
|
|
+ finfo = au_fi(file);
|
|
+ if (!finfo->fi_hdir) {
|
|
+ hfile = &finfo->fi_htop;
|
|
+ AuDebugOn(!hfile->hf_file);
|
|
+ bindex = au_br_index(sb, hfile->hf_br->br_id);
|
|
+ AuDebugOn(bindex < 0);
|
|
+ if (bindex != finfo->fi_btop)
|
|
+ au_set_fbtop(file, bindex);
|
|
+ } else {
|
|
+ err = au_fidir_realloc(finfo, nbr, /*may_shrink*/0);
|
|
+ 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 (finfo->fi_hdir)
|
|
+ /* harmless if err */
|
|
+ au_fidir_realloc(finfo, nbr, /*may_shrink*/1);
|
|
+ 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) {
|
|
+ bbot = au_fbbot_dir(file);
|
|
+ for (bindex = au_fbtop(file); bindex <= bbot; 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, unsigned int fi_lsc)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int sigen, figen;
|
|
+ aufs_bindex_t btop;
|
|
+ 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_nested(file, fi_lsc);
|
|
+ figen = au_figen(file);
|
|
+ if (!fi_lsc)
|
|
+ di_write_lock_child(dentry);
|
|
+ else
|
|
+ di_write_lock_child2(dentry);
|
|
+ btop = au_dbtop(dentry);
|
|
+ pseudo_link = (btop != au_ibtop(inode));
|
|
+ if (sigen == figen && !pseudo_link && au_fbtop(file) == btop) {
|
|
+ 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)
|
|
+{ 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; }
|
|
+#if 0 /* called by memory compaction regardless file */
|
|
+static int aufs_migratepage(struct address_space *mapping, struct page *newpage,
|
|
+ struct page *page, enum migrate_mode mode)
|
|
+{ AuUnsupport(); return 0; }
|
|
+#endif
|
|
+static bool aufs_isolate_page(struct page *page, isolate_mode_t mode)
|
|
+{ AuUnsupport(); return true; }
|
|
+static void aufs_putback_page(struct page *page)
|
|
+{ AuUnsupport(); }
|
|
+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,
|
|
+ /* is fallback_migrate_page ok? */
|
|
+ /* .migratepage = aufs_migratepage, */
|
|
+ .isolate_page = aufs_isolate_page,
|
|
+ .putback_page = aufs_putback_page,
|
|
+ .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 */
|
|
+};
|
|
diff --git a/fs/aufs/file.h b/fs/aufs/file.h
|
|
new file mode 100644
|
|
index 000000000000..d124d9c8216d
|
|
--- /dev/null
|
|
+++ b/fs/aufs/file.h
|
|
@@ -0,0 +1,342 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * file operations
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_FILE_H__
|
|
+#define __AUFS_FILE_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/file.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/mm_types.h>
|
|
+#include <linux/poll.h>
|
|
+#include "rwsem.h"
|
|
+
|
|
+struct au_branch;
|
|
+struct au_hfile {
|
|
+ struct file *hf_file;
|
|
+ struct au_branch *hf_br;
|
|
+};
|
|
+
|
|
+struct au_vdir;
|
|
+struct au_fidir {
|
|
+ aufs_bindex_t fd_bbot;
|
|
+ aufs_bindex_t fd_nent;
|
|
+ struct au_vdir *fd_vdir_cache;
|
|
+ struct au_hfile fd_hfile[];
|
|
+};
|
|
+
|
|
+static inline int au_fidir_sz(int nent)
|
|
+{
|
|
+ AuDebugOn(nent < 0);
|
|
+ return sizeof(struct au_fidir) + sizeof(struct au_hfile) * nent;
|
|
+}
|
|
+
|
|
+struct au_finfo {
|
|
+ atomic_t fi_generation;
|
|
+
|
|
+ struct au_rwsem fi_rwsem;
|
|
+ aufs_bindex_t fi_btop;
|
|
+
|
|
+ /* do not union them */
|
|
+ struct { /* for non-dir */
|
|
+ struct au_hfile fi_htop;
|
|
+ atomic_t fi_mmapped;
|
|
+ };
|
|
+ struct au_fidir *fi_hdir; /* for dir only */
|
|
+
|
|
+ struct hlist_bl_node fi_hlist;
|
|
+ struct file *fi_file; /* very ugly */
|
|
+ struct rcu_head rcu;
|
|
+} ____cacheline_aligned_in_smp;
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* file.c */
|
|
+extern const struct address_space_operations aufs_aop;
|
|
+unsigned int au_file_roflags(unsigned int flags);
|
|
+struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags,
|
|
+ struct file *file, int force_wr);
|
|
+struct au_do_open_args {
|
|
+ int aopen;
|
|
+ int (*open)(struct file *file, int flags,
|
|
+ struct file *h_file);
|
|
+ struct au_fidir *fidir;
|
|
+ struct file *h_file;
|
|
+};
|
|
+int au_do_open(struct file *file, struct au_do_open_args *args);
|
|
+int au_reopen_nondir(struct file *file);
|
|
+struct au_pin;
|
|
+int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin);
|
|
+int au_reval_and_lock_fdi(struct file *file, int (*reopen)(struct file *file),
|
|
+ int wlock, unsigned int fi_lsc);
|
|
+int au_do_flush(struct file *file, fl_owner_t id,
|
|
+ int (*flush)(struct file *file, fl_owner_t id));
|
|
+
|
|
+/* poll.c */
|
|
+#ifdef CONFIG_AUFS_POLL
|
|
+__poll_t aufs_poll(struct file *file, struct poll_table_struct *pt);
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_AUFS_BR_HFSPLUS
|
|
+/* hfsplus.c */
|
|
+struct file *au_h_open_pre(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ int force_wr);
|
|
+void au_h_open_post(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct file *h_file);
|
|
+#else
|
|
+AuStub(struct file *, au_h_open_pre, return NULL, struct dentry *dentry,
|
|
+ aufs_bindex_t bindex, int force_wr)
|
|
+AuStubVoid(au_h_open_post, struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct file *h_file);
|
|
+#endif
|
|
+
|
|
+/* f_op.c */
|
|
+extern const struct file_operations aufs_file_fop;
|
|
+int au_do_open_nondir(struct file *file, int flags, struct file *h_file);
|
|
+int aufs_release_nondir(struct inode *inode __maybe_unused, struct file *file);
|
|
+struct file *au_read_pre(struct file *file, int keep_fi, unsigned int lsc);
|
|
+
|
|
+/* finfo.c */
|
|
+void au_hfput(struct au_hfile *hf, int execed);
|
|
+void au_set_h_fptr(struct file *file, aufs_bindex_t bindex,
|
|
+ struct file *h_file);
|
|
+
|
|
+void au_update_figen(struct file *file);
|
|
+struct au_fidir *au_fidir_alloc(struct super_block *sb);
|
|
+int au_fidir_realloc(struct au_finfo *finfo, int nbr, int may_shrink);
|
|
+
|
|
+void au_fi_init_once(void *_fi);
|
|
+void au_finfo_fin(struct file *file);
|
|
+int au_finfo_init(struct file *file, struct au_fidir *fidir);
|
|
+
|
|
+/* ioctl.c */
|
|
+long aufs_ioctl_nondir(struct file *file, unsigned int cmd, unsigned long arg);
|
|
+#ifdef CONFIG_COMPAT
|
|
+long aufs_compat_ioctl_dir(struct file *file, unsigned int cmd,
|
|
+ unsigned long arg);
|
|
+long aufs_compat_ioctl_nondir(struct file *file, unsigned int cmd,
|
|
+ unsigned long arg);
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline struct au_finfo *au_fi(struct file *file)
|
|
+{
|
|
+ return file->private_data;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#define fi_read_lock(f) au_rw_read_lock(&au_fi(f)->fi_rwsem)
|
|
+#define fi_write_lock(f) au_rw_write_lock(&au_fi(f)->fi_rwsem)
|
|
+#define fi_read_trylock(f) au_rw_read_trylock(&au_fi(f)->fi_rwsem)
|
|
+#define fi_write_trylock(f) au_rw_write_trylock(&au_fi(f)->fi_rwsem)
|
|
+/*
|
|
+#define fi_read_trylock_nested(f) \
|
|
+ au_rw_read_trylock_nested(&au_fi(f)->fi_rwsem)
|
|
+#define fi_write_trylock_nested(f) \
|
|
+ au_rw_write_trylock_nested(&au_fi(f)->fi_rwsem)
|
|
+*/
|
|
+
|
|
+#define fi_read_unlock(f) au_rw_read_unlock(&au_fi(f)->fi_rwsem)
|
|
+#define fi_write_unlock(f) au_rw_write_unlock(&au_fi(f)->fi_rwsem)
|
|
+#define fi_downgrade_lock(f) au_rw_dgrade_lock(&au_fi(f)->fi_rwsem)
|
|
+
|
|
+/* lock subclass for finfo */
|
|
+enum {
|
|
+ AuLsc_FI_1,
|
|
+ AuLsc_FI_2
|
|
+};
|
|
+
|
|
+static inline void fi_read_lock_nested(struct file *f, unsigned int lsc)
|
|
+{
|
|
+ au_rw_read_lock_nested(&au_fi(f)->fi_rwsem, lsc);
|
|
+}
|
|
+
|
|
+static inline void fi_write_lock_nested(struct file *f, unsigned int lsc)
|
|
+{
|
|
+ au_rw_write_lock_nested(&au_fi(f)->fi_rwsem, lsc);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * fi_read_lock_1, fi_write_lock_1,
|
|
+ * fi_read_lock_2, fi_write_lock_2
|
|
+ */
|
|
+#define AuReadLockFunc(name) \
|
|
+static inline void fi_read_lock_##name(struct file *f) \
|
|
+{ fi_read_lock_nested(f, AuLsc_FI_##name); }
|
|
+
|
|
+#define AuWriteLockFunc(name) \
|
|
+static inline void fi_write_lock_##name(struct file *f) \
|
|
+{ fi_write_lock_nested(f, AuLsc_FI_##name); }
|
|
+
|
|
+#define AuRWLockFuncs(name) \
|
|
+ AuReadLockFunc(name) \
|
|
+ AuWriteLockFunc(name)
|
|
+
|
|
+AuRWLockFuncs(1);
|
|
+AuRWLockFuncs(2);
|
|
+
|
|
+#undef AuReadLockFunc
|
|
+#undef AuWriteLockFunc
|
|
+#undef AuRWLockFuncs
|
|
+
|
|
+#define FiMustNoWaiters(f) AuRwMustNoWaiters(&au_fi(f)->fi_rwsem)
|
|
+#define FiMustAnyLock(f) AuRwMustAnyLock(&au_fi(f)->fi_rwsem)
|
|
+#define FiMustWriteLock(f) AuRwMustWriteLock(&au_fi(f)->fi_rwsem)
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* todo: hard/soft set? */
|
|
+static inline aufs_bindex_t au_fbtop(struct file *file)
|
|
+{
|
|
+ FiMustAnyLock(file);
|
|
+ return au_fi(file)->fi_btop;
|
|
+}
|
|
+
|
|
+static inline aufs_bindex_t au_fbbot_dir(struct file *file)
|
|
+{
|
|
+ FiMustAnyLock(file);
|
|
+ AuDebugOn(!au_fi(file)->fi_hdir);
|
|
+ return au_fi(file)->fi_hdir->fd_bbot;
|
|
+}
|
|
+
|
|
+static inline struct au_vdir *au_fvdir_cache(struct file *file)
|
|
+{
|
|
+ FiMustAnyLock(file);
|
|
+ AuDebugOn(!au_fi(file)->fi_hdir);
|
|
+ return au_fi(file)->fi_hdir->fd_vdir_cache;
|
|
+}
|
|
+
|
|
+static inline void au_set_fbtop(struct file *file, aufs_bindex_t bindex)
|
|
+{
|
|
+ FiMustWriteLock(file);
|
|
+ au_fi(file)->fi_btop = bindex;
|
|
+}
|
|
+
|
|
+static inline void au_set_fbbot_dir(struct file *file, aufs_bindex_t bindex)
|
|
+{
|
|
+ FiMustWriteLock(file);
|
|
+ AuDebugOn(!au_fi(file)->fi_hdir);
|
|
+ au_fi(file)->fi_hdir->fd_bbot = bindex;
|
|
+}
|
|
+
|
|
+static inline void au_set_fvdir_cache(struct file *file,
|
|
+ struct au_vdir *vdir_cache)
|
|
+{
|
|
+ FiMustWriteLock(file);
|
|
+ AuDebugOn(!au_fi(file)->fi_hdir);
|
|
+ au_fi(file)->fi_hdir->fd_vdir_cache = vdir_cache;
|
|
+}
|
|
+
|
|
+static inline struct file *au_hf_top(struct file *file)
|
|
+{
|
|
+ FiMustAnyLock(file);
|
|
+ AuDebugOn(au_fi(file)->fi_hdir);
|
|
+ return au_fi(file)->fi_htop.hf_file;
|
|
+}
|
|
+
|
|
+static inline struct file *au_hf_dir(struct file *file, aufs_bindex_t bindex)
|
|
+{
|
|
+ FiMustAnyLock(file);
|
|
+ AuDebugOn(!au_fi(file)->fi_hdir);
|
|
+ return au_fi(file)->fi_hdir->fd_hfile[0 + bindex].hf_file;
|
|
+}
|
|
+
|
|
+/* todo: memory barrier? */
|
|
+static inline unsigned int au_figen(struct file *f)
|
|
+{
|
|
+ return atomic_read(&au_fi(f)->fi_generation);
|
|
+}
|
|
+
|
|
+static inline void au_set_mmapped(struct file *f)
|
|
+{
|
|
+ if (atomic_inc_return(&au_fi(f)->fi_mmapped))
|
|
+ return;
|
|
+ pr_warn("fi_mmapped wrapped around\n");
|
|
+ while (!atomic_inc_return(&au_fi(f)->fi_mmapped))
|
|
+ ;
|
|
+}
|
|
+
|
|
+static inline void au_unset_mmapped(struct file *f)
|
|
+{
|
|
+ atomic_dec(&au_fi(f)->fi_mmapped);
|
|
+}
|
|
+
|
|
+static inline int au_test_mmapped(struct file *f)
|
|
+{
|
|
+ return atomic_read(&au_fi(f)->fi_mmapped);
|
|
+}
|
|
+
|
|
+/* customize vma->vm_file */
|
|
+
|
|
+static inline void au_do_vm_file_reset(struct vm_area_struct *vma,
|
|
+ struct file *file)
|
|
+{
|
|
+ struct file *f;
|
|
+
|
|
+ f = vma->vm_file;
|
|
+ get_file(file);
|
|
+ vma->vm_file = file;
|
|
+ fput(f);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_MMU
|
|
+#define AuDbgVmRegion(file, vma) do {} while (0)
|
|
+
|
|
+static inline void au_vm_file_reset(struct vm_area_struct *vma,
|
|
+ struct file *file)
|
|
+{
|
|
+ au_do_vm_file_reset(vma, file);
|
|
+}
|
|
+#else
|
|
+#define AuDbgVmRegion(file, vma) \
|
|
+ AuDebugOn((vma)->vm_region && (vma)->vm_region->vm_file != (file))
|
|
+
|
|
+static inline void au_vm_file_reset(struct vm_area_struct *vma,
|
|
+ struct file *file)
|
|
+{
|
|
+ struct file *f;
|
|
+
|
|
+ au_do_vm_file_reset(vma, file);
|
|
+ f = vma->vm_region->vm_file;
|
|
+ get_file(file);
|
|
+ vma->vm_region->vm_file = file;
|
|
+ fput(f);
|
|
+}
|
|
+#endif /* CONFIG_MMU */
|
|
+
|
|
+/* handle vma->vm_prfile */
|
|
+static inline void au_vm_prfile_set(struct vm_area_struct *vma,
|
|
+ struct file *file)
|
|
+{
|
|
+ get_file(file);
|
|
+ vma->vm_prfile = file;
|
|
+#ifndef CONFIG_MMU
|
|
+ get_file(file);
|
|
+ vma->vm_region->vm_prfile = file;
|
|
+#endif
|
|
+}
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_FILE_H__ */
|
|
diff --git a/fs/aufs/finfo.c b/fs/aufs/finfo.c
|
|
new file mode 100644
|
|
index 000000000000..25077670a507
|
|
--- /dev/null
|
|
+++ b/fs/aufs/finfo.c
|
|
@@ -0,0 +1,149 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * file private data
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+void au_hfput(struct au_hfile *hf, int execed)
|
|
+{
|
|
+ if (execed)
|
|
+ allow_write_access(hf->hf_file);
|
|
+ fput(hf->hf_file);
|
|
+ hf->hf_file = NULL;
|
|
+ au_lcnt_dec(&hf->hf_br->br_nfiles);
|
|
+ hf->hf_br = NULL;
|
|
+}
|
|
+
|
|
+void au_set_h_fptr(struct file *file, aufs_bindex_t bindex, struct file *val)
|
|
+{
|
|
+ struct au_finfo *finfo = au_fi(file);
|
|
+ struct au_hfile *hf;
|
|
+ struct au_fidir *fidir;
|
|
+
|
|
+ fidir = finfo->fi_hdir;
|
|
+ if (!fidir) {
|
|
+ AuDebugOn(finfo->fi_btop != bindex);
|
|
+ hf = &finfo->fi_htop;
|
|
+ } else
|
|
+ hf = fidir->fd_hfile + bindex;
|
|
+
|
|
+ if (hf && hf->hf_file)
|
|
+ au_hfput(hf, vfsub_file_execed(file));
|
|
+ if (val) {
|
|
+ FiMustWriteLock(file);
|
|
+ AuDebugOn(IS_ERR_OR_NULL(file->f_path.dentry));
|
|
+ hf->hf_file = val;
|
|
+ hf->hf_br = au_sbr(file->f_path.dentry->d_sb, bindex);
|
|
+ }
|
|
+}
|
|
+
|
|
+void au_update_figen(struct file *file)
|
|
+{
|
|
+ atomic_set(&au_fi(file)->fi_generation, au_digen(file->f_path.dentry));
|
|
+ /* smp_mb(); */ /* atomic_set */
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_fidir *au_fidir_alloc(struct super_block *sb)
|
|
+{
|
|
+ struct au_fidir *fidir;
|
|
+ int nbr;
|
|
+
|
|
+ nbr = au_sbbot(sb) + 1;
|
|
+ if (nbr < 2)
|
|
+ nbr = 2; /* initial allocate for 2 branches */
|
|
+ fidir = kzalloc(au_fidir_sz(nbr), GFP_NOFS);
|
|
+ if (fidir) {
|
|
+ fidir->fd_bbot = -1;
|
|
+ fidir->fd_nent = nbr;
|
|
+ }
|
|
+
|
|
+ return fidir;
|
|
+}
|
|
+
|
|
+int au_fidir_realloc(struct au_finfo *finfo, int nbr, int may_shrink)
|
|
+{
|
|
+ int err;
|
|
+ struct au_fidir *fidir, *p;
|
|
+
|
|
+ AuRwMustWriteLock(&finfo->fi_rwsem);
|
|
+ fidir = finfo->fi_hdir;
|
|
+ AuDebugOn(!fidir);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ p = au_kzrealloc(fidir, au_fidir_sz(fidir->fd_nent), au_fidir_sz(nbr),
|
|
+ GFP_NOFS, may_shrink);
|
|
+ if (p) {
|
|
+ p->fd_nent = nbr;
|
|
+ finfo->fi_hdir = p;
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void au_finfo_fin(struct file *file)
|
|
+{
|
|
+ struct au_finfo *finfo;
|
|
+
|
|
+ au_lcnt_dec(&au_sbi(file->f_path.dentry->d_sb)->si_nfiles);
|
|
+
|
|
+ finfo = au_fi(file);
|
|
+ AuDebugOn(finfo->fi_hdir);
|
|
+ AuRwDestroy(&finfo->fi_rwsem);
|
|
+ au_cache_free_finfo(finfo);
|
|
+}
|
|
+
|
|
+void au_fi_init_once(void *_finfo)
|
|
+{
|
|
+ struct au_finfo *finfo = _finfo;
|
|
+
|
|
+ au_rw_init(&finfo->fi_rwsem);
|
|
+}
|
|
+
|
|
+int au_finfo_init(struct file *file, struct au_fidir *fidir)
|
|
+{
|
|
+ int err;
|
|
+ struct au_finfo *finfo;
|
|
+ struct dentry *dentry;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ dentry = file->f_path.dentry;
|
|
+ finfo = au_cache_alloc_finfo();
|
|
+ if (unlikely(!finfo))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ au_lcnt_inc(&au_sbi(dentry->d_sb)->si_nfiles);
|
|
+ au_rw_write_lock(&finfo->fi_rwsem);
|
|
+ finfo->fi_btop = -1;
|
|
+ finfo->fi_hdir = fidir;
|
|
+ atomic_set(&finfo->fi_generation, au_digen(dentry));
|
|
+ /* smp_mb(); */ /* atomic_set */
|
|
+
|
|
+ file->private_data = finfo;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/fstype.h b/fs/aufs/fstype.h
|
|
new file mode 100644
|
|
index 000000000000..af4bc6c0dd42
|
|
--- /dev/null
|
|
+++ b/fs/aufs/fstype.h
|
|
@@ -0,0 +1,401 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * judging filesystem type
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_FSTYPE_H__
|
|
+#define __AUFS_FSTYPE_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/fs.h>
|
|
+#include <linux/magic.h>
|
|
+#include <linux/nfs_fs.h>
|
|
+#include <linux/romfs_fs.h>
|
|
+
|
|
+static inline int au_test_aufs(struct super_block *sb)
|
|
+{
|
|
+ return sb->s_magic == AUFS_SUPER_MAGIC;
|
|
+}
|
|
+
|
|
+static inline const char *au_sbtype(struct super_block *sb)
|
|
+{
|
|
+ return sb->s_type->name;
|
|
+}
|
|
+
|
|
+static inline int au_test_iso9660(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_ISO9660_FS)
|
|
+ return sb->s_magic == ISOFS_SUPER_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_romfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_ROMFS_FS)
|
|
+ return sb->s_magic == ROMFS_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_cramfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_CRAMFS)
|
|
+ return sb->s_magic == CRAMFS_MAGIC;
|
|
+#endif
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline int au_test_nfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_NFS_FS)
|
|
+ return sb->s_magic == NFS_SUPER_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_fuse(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_FUSE_FS)
|
|
+ return sb->s_magic == FUSE_SUPER_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_xfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_XFS_FS)
|
|
+ return sb->s_magic == XFS_SB_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_tmpfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#ifdef CONFIG_TMPFS
|
|
+ return sb->s_magic == TMPFS_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_ecryptfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_ECRYPT_FS)
|
|
+ return !strcmp(au_sbtype(sb), "ecryptfs");
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_ramfs(struct super_block *sb)
|
|
+{
|
|
+ return sb->s_magic == RAMFS_MAGIC;
|
|
+}
|
|
+
|
|
+static inline int au_test_ubifs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_UBIFS_FS)
|
|
+ return sb->s_magic == UBIFS_SUPER_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_procfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#ifdef CONFIG_PROC_FS
|
|
+ return sb->s_magic == PROC_SUPER_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_sysfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#ifdef CONFIG_SYSFS
|
|
+ return sb->s_magic == SYSFS_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_configfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_CONFIGFS_FS)
|
|
+ return sb->s_magic == CONFIGFS_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_minix(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_MINIX_FS)
|
|
+ return sb->s_magic == MINIX3_SUPER_MAGIC
|
|
+ || sb->s_magic == MINIX2_SUPER_MAGIC
|
|
+ || sb->s_magic == MINIX2_SUPER_MAGIC2
|
|
+ || sb->s_magic == MINIX_SUPER_MAGIC
|
|
+ || sb->s_magic == MINIX_SUPER_MAGIC2;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_fat(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_FAT_FS)
|
|
+ return sb->s_magic == MSDOS_SUPER_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_msdos(struct super_block *sb)
|
|
+{
|
|
+ return au_test_fat(sb);
|
|
+}
|
|
+
|
|
+static inline int au_test_vfat(struct super_block *sb)
|
|
+{
|
|
+ return au_test_fat(sb);
|
|
+}
|
|
+
|
|
+static inline int au_test_securityfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#ifdef CONFIG_SECURITYFS
|
|
+ return sb->s_magic == SECURITYFS_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_squashfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_SQUASHFS)
|
|
+ return sb->s_magic == SQUASHFS_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_btrfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_BTRFS_FS)
|
|
+ return sb->s_magic == BTRFS_SUPER_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_xenfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_XENFS)
|
|
+ return sb->s_magic == XENFS_SUPER_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_debugfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+ return sb->s_magic == DEBUGFS_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_nilfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_NILFS)
|
|
+ return sb->s_magic == NILFS_SUPER_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline int au_test_hfsplus(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_HFSPLUS_FS)
|
|
+ return sb->s_magic == HFSPLUS_SUPER_MAGIC;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+/*
|
|
+ * they can't be an aufs branch.
|
|
+ */
|
|
+static inline int au_test_fs_unsuppoted(struct super_block *sb)
|
|
+{
|
|
+ return
|
|
+#ifndef CONFIG_AUFS_BR_RAMFS
|
|
+ au_test_ramfs(sb) ||
|
|
+#endif
|
|
+ au_test_procfs(sb)
|
|
+ || au_test_sysfs(sb)
|
|
+ || au_test_configfs(sb)
|
|
+ || au_test_debugfs(sb)
|
|
+ || au_test_securityfs(sb)
|
|
+ || au_test_xenfs(sb)
|
|
+ || au_test_ecryptfs(sb)
|
|
+ /* || !strcmp(au_sbtype(sb), "unionfs") */
|
|
+ || au_test_aufs(sb); /* will be supported in next version */
|
|
+}
|
|
+
|
|
+static inline int au_test_fs_remote(struct super_block *sb)
|
|
+{
|
|
+ return !au_test_tmpfs(sb)
|
|
+#ifdef CONFIG_AUFS_BR_RAMFS
|
|
+ && !au_test_ramfs(sb)
|
|
+#endif
|
|
+ && !(sb->s_type->fs_flags & FS_REQUIRES_DEV);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * Note: these functions (below) are created after reading ->getattr() in all
|
|
+ * filesystems under linux/fs. it means we have to do so in every update...
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * some filesystems require getattr to refresh the inode attributes before
|
|
+ * referencing.
|
|
+ * in most cases, we can rely on the inode attribute in NFS (or every remote fs)
|
|
+ * and leave the work for d_revalidate()
|
|
+ */
|
|
+static inline int au_test_fs_refresh_iattr(struct super_block *sb)
|
|
+{
|
|
+ return au_test_nfs(sb)
|
|
+ || au_test_fuse(sb)
|
|
+ /* || au_test_btrfs(sb) */ /* untested */
|
|
+ ;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * filesystems which don't maintain i_size or i_blocks.
|
|
+ */
|
|
+static inline int au_test_fs_bad_iattr_size(struct super_block *sb)
|
|
+{
|
|
+ return au_test_xfs(sb)
|
|
+ || au_test_btrfs(sb)
|
|
+ || au_test_ubifs(sb)
|
|
+ || au_test_hfsplus(sb) /* maintained, but incorrect */
|
|
+ /* || au_test_minix(sb) */ /* untested */
|
|
+ ;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * filesystems which don't store the correct value in some of their inode
|
|
+ * attributes.
|
|
+ */
|
|
+static inline int au_test_fs_bad_iattr(struct super_block *sb)
|
|
+{
|
|
+ return au_test_fs_bad_iattr_size(sb)
|
|
+ || au_test_fat(sb)
|
|
+ || au_test_msdos(sb)
|
|
+ || au_test_vfat(sb);
|
|
+}
|
|
+
|
|
+/* they don't check i_nlink in link(2) */
|
|
+static inline int au_test_fs_no_limit_nlink(struct super_block *sb)
|
|
+{
|
|
+ return au_test_tmpfs(sb)
|
|
+#ifdef CONFIG_AUFS_BR_RAMFS
|
|
+ || au_test_ramfs(sb)
|
|
+#endif
|
|
+ || au_test_ubifs(sb)
|
|
+ || au_test_hfsplus(sb);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * filesystems which sets S_NOATIME and S_NOCMTIME.
|
|
+ */
|
|
+static inline int au_test_fs_notime(struct super_block *sb)
|
|
+{
|
|
+ return au_test_nfs(sb)
|
|
+ || au_test_fuse(sb)
|
|
+ || au_test_ubifs(sb)
|
|
+ ;
|
|
+}
|
|
+
|
|
+/* temporary support for i#1 in cramfs */
|
|
+static inline int au_test_fs_unique_ino(struct inode *inode)
|
|
+{
|
|
+ if (au_test_cramfs(inode->i_sb))
|
|
+ return inode->i_ino != 1;
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * the filesystem where the xino files placed must support i/o after unlink and
|
|
+ * maintain i_size and i_blocks.
|
|
+ */
|
|
+static inline int au_test_fs_bad_xino(struct super_block *sb)
|
|
+{
|
|
+ return au_test_fs_remote(sb)
|
|
+ || au_test_fs_bad_iattr_size(sb)
|
|
+ /* don't want unnecessary work for xino */
|
|
+ || au_test_aufs(sb)
|
|
+ || au_test_ecryptfs(sb)
|
|
+ || au_test_nilfs(sb);
|
|
+}
|
|
+
|
|
+static inline int au_test_fs_trunc_xino(struct super_block *sb)
|
|
+{
|
|
+ return au_test_tmpfs(sb)
|
|
+ || au_test_ramfs(sb);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * test if the @sb is real-readonly.
|
|
+ */
|
|
+static inline int au_test_fs_rr(struct super_block *sb)
|
|
+{
|
|
+ return au_test_squashfs(sb)
|
|
+ || au_test_iso9660(sb)
|
|
+ || au_test_cramfs(sb)
|
|
+ || au_test_romfs(sb);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * test if the @inode is nfs with 'noacl' option
|
|
+ * NFS always sets SB_POSIXACL regardless its mount option 'noacl.'
|
|
+ */
|
|
+static inline int au_test_nfs_noacl(struct inode *inode)
|
|
+{
|
|
+ return au_test_nfs(inode->i_sb)
|
|
+ /* && IS_POSIXACL(inode) */
|
|
+ && !nfs_server_capable(inode, NFS_CAP_ACLS);
|
|
+}
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_FSTYPE_H__ */
|
|
diff --git a/fs/aufs/hbl.h b/fs/aufs/hbl.h
|
|
new file mode 100644
|
|
index 000000000000..4d5016bccc51
|
|
--- /dev/null
|
|
+++ b/fs/aufs/hbl.h
|
|
@@ -0,0 +1,65 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2017-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * helpers for hlist_bl.h
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_HBL_H__
|
|
+#define __AUFS_HBL_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/list_bl.h>
|
|
+
|
|
+static inline void au_hbl_add(struct hlist_bl_node *node,
|
|
+ struct hlist_bl_head *hbl)
|
|
+{
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_add_head(node, hbl);
|
|
+ hlist_bl_unlock(hbl);
|
|
+}
|
|
+
|
|
+static inline void au_hbl_del(struct hlist_bl_node *node,
|
|
+ struct hlist_bl_head *hbl)
|
|
+{
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_del(node);
|
|
+ hlist_bl_unlock(hbl);
|
|
+}
|
|
+
|
|
+#define au_hbl_for_each(pos, head) \
|
|
+ for (pos = hlist_bl_first(head); \
|
|
+ pos; \
|
|
+ pos = pos->next)
|
|
+
|
|
+static inline unsigned long au_hbl_count(struct hlist_bl_head *hbl)
|
|
+{
|
|
+ unsigned long cnt;
|
|
+ struct hlist_bl_node *pos;
|
|
+
|
|
+ cnt = 0;
|
|
+ hlist_bl_lock(hbl);
|
|
+ au_hbl_for_each(pos, hbl)
|
|
+ cnt++;
|
|
+ hlist_bl_unlock(hbl);
|
|
+ return cnt;
|
|
+}
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_HBL_H__ */
|
|
diff --git a/fs/aufs/hfsnotify.c b/fs/aufs/hfsnotify.c
|
|
new file mode 100644
|
|
index 000000000000..cb4eeb1e6069
|
|
--- /dev/null
|
|
+++ b/fs/aufs/hfsnotify.c
|
|
@@ -0,0 +1,288 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * fsnotify for the lower directories
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+/* FS_IN_IGNORED is unnecessary */
|
|
+static const __u32 AuHfsnMask = (FS_MOVED_TO | FS_MOVED_FROM | FS_DELETE
|
|
+ | FS_CREATE | FS_EVENT_ON_CHILD);
|
|
+static DECLARE_WAIT_QUEUE_HEAD(au_hfsn_wq);
|
|
+static __cacheline_aligned_in_smp atomic64_t au_hfsn_ifree = ATOMIC64_INIT(0);
|
|
+
|
|
+static void au_hfsn_free_mark(struct fsnotify_mark *mark)
|
|
+{
|
|
+ struct au_hnotify *hn = container_of(mark, struct au_hnotify,
|
|
+ hn_mark);
|
|
+ /* AuDbg("here\n"); */
|
|
+ au_cache_free_hnotify(hn);
|
|
+ smp_mb__before_atomic(); /* for atomic64_dec */
|
|
+ if (atomic64_dec_and_test(&au_hfsn_ifree))
|
|
+ wake_up(&au_hfsn_wq);
|
|
+}
|
|
+
|
|
+static int au_hfsn_alloc(struct au_hinode *hinode)
|
|
+{
|
|
+ int err;
|
|
+ struct au_hnotify *hn;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ struct fsnotify_mark *mark;
|
|
+ aufs_bindex_t bindex;
|
|
+
|
|
+ hn = hinode->hi_notify;
|
|
+ sb = hn->hn_aufs_inode->i_sb;
|
|
+ bindex = au_br_index(sb, hinode->hi_id);
|
|
+ br = au_sbr(sb, bindex);
|
|
+ AuDebugOn(!br->br_hfsn);
|
|
+
|
|
+ mark = &hn->hn_mark;
|
|
+ fsnotify_init_mark(mark, br->br_hfsn->hfsn_group);
|
|
+ mark->mask = AuHfsnMask;
|
|
+ /*
|
|
+ * by udba rename or rmdir, aufs assign a new inode to the known
|
|
+ * h_inode, so specify 1 to allow dups.
|
|
+ */
|
|
+ lockdep_off();
|
|
+ err = fsnotify_add_inode_mark(mark, hinode->hi_inode, /*allow_dups*/1);
|
|
+ lockdep_on();
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_hfsn_free(struct au_hinode *hinode, struct au_hnotify *hn)
|
|
+{
|
|
+ struct fsnotify_mark *mark;
|
|
+ unsigned long long ull;
|
|
+ struct fsnotify_group *group;
|
|
+
|
|
+ ull = atomic64_inc_return(&au_hfsn_ifree);
|
|
+ BUG_ON(!ull);
|
|
+
|
|
+ mark = &hn->hn_mark;
|
|
+ spin_lock(&mark->lock);
|
|
+ group = mark->group;
|
|
+ fsnotify_get_group(group);
|
|
+ spin_unlock(&mark->lock);
|
|
+ lockdep_off();
|
|
+ fsnotify_destroy_mark(mark, group);
|
|
+ fsnotify_put_mark(mark);
|
|
+ fsnotify_put_group(group);
|
|
+ lockdep_on();
|
|
+
|
|
+ /* free hn by myself */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static void au_hfsn_ctl(struct au_hinode *hinode, int do_set)
|
|
+{
|
|
+ struct fsnotify_mark *mark;
|
|
+
|
|
+ mark = &hinode->hi_notify->hn_mark;
|
|
+ spin_lock(&mark->lock);
|
|
+ if (do_set) {
|
|
+ AuDebugOn(mark->mask & AuHfsnMask);
|
|
+ mark->mask |= AuHfsnMask;
|
|
+ } else {
|
|
+ AuDebugOn(!(mark->mask & AuHfsnMask));
|
|
+ mark->mask &= ~AuHfsnMask;
|
|
+ }
|
|
+ spin_unlock(&mark->lock);
|
|
+ /* fsnotify_recalc_inode_mask(hinode->hi_inode); */
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* #define AuDbgHnotify */
|
|
+#ifdef AuDbgHnotify
|
|
+static char *au_hfsn_name(u32 mask)
|
|
+{
|
|
+#ifdef CONFIG_AUFS_DEBUG
|
|
+#define test_ret(flag) \
|
|
+ do { \
|
|
+ if (mask & flag) \
|
|
+ return #flag; \
|
|
+ } while (0)
|
|
+ test_ret(FS_ACCESS);
|
|
+ test_ret(FS_MODIFY);
|
|
+ test_ret(FS_ATTRIB);
|
|
+ test_ret(FS_CLOSE_WRITE);
|
|
+ test_ret(FS_CLOSE_NOWRITE);
|
|
+ test_ret(FS_OPEN);
|
|
+ test_ret(FS_MOVED_FROM);
|
|
+ test_ret(FS_MOVED_TO);
|
|
+ test_ret(FS_CREATE);
|
|
+ test_ret(FS_DELETE);
|
|
+ test_ret(FS_DELETE_SELF);
|
|
+ test_ret(FS_MOVE_SELF);
|
|
+ test_ret(FS_UNMOUNT);
|
|
+ test_ret(FS_Q_OVERFLOW);
|
|
+ test_ret(FS_IN_IGNORED);
|
|
+ test_ret(FS_ISDIR);
|
|
+ test_ret(FS_IN_ONESHOT);
|
|
+ test_ret(FS_EVENT_ON_CHILD);
|
|
+ return "";
|
|
+#undef test_ret
|
|
+#else
|
|
+ return "??";
|
|
+#endif
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static void au_hfsn_free_group(struct fsnotify_group *group)
|
|
+{
|
|
+ struct au_br_hfsnotify *hfsn = group->private;
|
|
+
|
|
+ /* AuDbg("here\n"); */
|
|
+ au_kfree_try_rcu(hfsn);
|
|
+}
|
|
+
|
|
+static int au_hfsn_handle_event(struct fsnotify_group *group,
|
|
+ struct inode *inode,
|
|
+ u32 mask, const void *data, int data_type,
|
|
+ const struct qstr *file_name, u32 cookie,
|
|
+ struct fsnotify_iter_info *iter_info)
|
|
+{
|
|
+ int err;
|
|
+ struct au_hnotify *hnotify;
|
|
+ struct inode *h_dir, *h_inode;
|
|
+ struct fsnotify_mark *inode_mark;
|
|
+
|
|
+ AuDebugOn(data_type != FSNOTIFY_EVENT_INODE);
|
|
+
|
|
+ err = 0;
|
|
+ /* if FS_UNMOUNT happens, there must be another bug */
|
|
+ AuDebugOn(mask & FS_UNMOUNT);
|
|
+ if (mask & (FS_IN_IGNORED | FS_UNMOUNT))
|
|
+ goto out;
|
|
+
|
|
+ h_dir = inode;
|
|
+ h_inode = NULL;
|
|
+#ifdef AuDbgHnotify
|
|
+ au_debug_on();
|
|
+ if (1 || h_child_qstr.len != sizeof(AUFS_XINO_FNAME) - 1
|
|
+ || strncmp(h_child_qstr.name, AUFS_XINO_FNAME, h_child_qstr.len)) {
|
|
+ AuDbg("i%lu, mask 0x%x %s, hcname %.*s, hi%lu\n",
|
|
+ h_dir->i_ino, mask, au_hfsn_name(mask),
|
|
+ AuLNPair(&h_child_qstr), h_inode ? h_inode->i_ino : 0);
|
|
+ /* WARN_ON(1); */
|
|
+ }
|
|
+ au_debug_off();
|
|
+#endif
|
|
+
|
|
+ inode_mark = fsnotify_iter_inode_mark(iter_info);
|
|
+ AuDebugOn(!inode_mark);
|
|
+ hnotify = container_of(inode_mark, struct au_hnotify, hn_mark);
|
|
+ err = au_hnotify(h_dir, hnotify, mask, file_name, h_inode);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static struct fsnotify_ops au_hfsn_ops = {
|
|
+ .handle_event = au_hfsn_handle_event,
|
|
+ .free_group_priv = au_hfsn_free_group,
|
|
+ .free_mark = au_hfsn_free_mark
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static void au_hfsn_fin_br(struct au_branch *br)
|
|
+{
|
|
+ struct au_br_hfsnotify *hfsn;
|
|
+
|
|
+ hfsn = br->br_hfsn;
|
|
+ if (hfsn) {
|
|
+ lockdep_off();
|
|
+ fsnotify_put_group(hfsn->hfsn_group);
|
|
+ lockdep_on();
|
|
+ }
|
|
+}
|
|
+
|
|
+static int au_hfsn_init_br(struct au_branch *br, int perm)
|
|
+{
|
|
+ int err;
|
|
+ struct fsnotify_group *group;
|
|
+ struct au_br_hfsnotify *hfsn;
|
|
+
|
|
+ err = 0;
|
|
+ br->br_hfsn = NULL;
|
|
+ if (!au_br_hnotifyable(perm))
|
|
+ goto out;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ hfsn = kmalloc(sizeof(*hfsn), GFP_NOFS);
|
|
+ if (unlikely(!hfsn))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ group = fsnotify_alloc_group(&au_hfsn_ops);
|
|
+ if (IS_ERR(group)) {
|
|
+ err = PTR_ERR(group);
|
|
+ pr_err("fsnotify_alloc_group() failed, %d\n", err);
|
|
+ goto out_hfsn;
|
|
+ }
|
|
+
|
|
+ group->private = hfsn;
|
|
+ hfsn->hfsn_group = group;
|
|
+ br->br_hfsn = hfsn;
|
|
+ goto out; /* success */
|
|
+
|
|
+out_hfsn:
|
|
+ au_kfree_try_rcu(hfsn);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_hfsn_reset_br(unsigned int udba, struct au_branch *br, int perm)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (!br->br_hfsn)
|
|
+ err = au_hfsn_init_br(br, perm);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static void au_hfsn_fin(void)
|
|
+{
|
|
+ AuDbg("au_hfsn_ifree %lld\n", (long long)atomic64_read(&au_hfsn_ifree));
|
|
+ wait_event(au_hfsn_wq, !atomic64_read(&au_hfsn_ifree));
|
|
+}
|
|
+
|
|
+const struct au_hnotify_op au_hnotify_op = {
|
|
+ .ctl = au_hfsn_ctl,
|
|
+ .alloc = au_hfsn_alloc,
|
|
+ .free = au_hfsn_free,
|
|
+
|
|
+ .fin = au_hfsn_fin,
|
|
+
|
|
+ .reset_br = au_hfsn_reset_br,
|
|
+ .fin_br = au_hfsn_fin_br,
|
|
+ .init_br = au_hfsn_init_br
|
|
+};
|
|
diff --git a/fs/aufs/hfsplus.c b/fs/aufs/hfsplus.c
|
|
new file mode 100644
|
|
index 000000000000..d250f019bedf
|
|
--- /dev/null
|
|
+++ b/fs/aufs/hfsplus.c
|
|
@@ -0,0 +1,60 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2010-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * special support for filesystems which acquires an inode mutex
|
|
+ * at final closing a file, eg, hfsplus.
|
|
+ *
|
|
+ * This trick is very simple and stupid, just to open the file before really
|
|
+ * necessary open to tell hfsplus that this is not the final closing.
|
|
+ * The caller should call au_h_open_pre() after acquiring the inode mutex,
|
|
+ * and au_h_open_post() after releasing it.
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+struct file *au_h_open_pre(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ int force_wr)
|
|
+{
|
|
+ struct file *h_file;
|
|
+ struct dentry *h_dentry;
|
|
+
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ AuDebugOn(!h_dentry);
|
|
+ AuDebugOn(d_is_negative(h_dentry));
|
|
+
|
|
+ h_file = NULL;
|
|
+ if (au_test_hfsplus(h_dentry->d_sb)
|
|
+ && d_is_reg(h_dentry))
|
|
+ h_file = au_h_open(dentry, bindex,
|
|
+ O_RDONLY | O_NOATIME | O_LARGEFILE,
|
|
+ /*file*/NULL, force_wr);
|
|
+ return h_file;
|
|
+}
|
|
+
|
|
+void au_h_open_post(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct file *h_file)
|
|
+{
|
|
+ struct au_branch *br;
|
|
+
|
|
+ if (h_file) {
|
|
+ fput(h_file);
|
|
+ br = au_sbr(dentry->d_sb, bindex);
|
|
+ au_lcnt_dec(&br->br_nfiles);
|
|
+ }
|
|
+}
|
|
diff --git a/fs/aufs/hnotify.c b/fs/aufs/hnotify.c
|
|
new file mode 100644
|
|
index 000000000000..231edd1b4753
|
|
--- /dev/null
|
|
+++ b/fs/aufs/hnotify.c
|
|
@@ -0,0 +1,715 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * abstraction to notify the direct changes on lower directories
|
|
+ */
|
|
+
|
|
+/* #include <linux/iversion.h> */
|
|
+#include "aufs.h"
|
|
+
|
|
+int au_hn_alloc(struct au_hinode *hinode, struct inode *inode)
|
|
+{
|
|
+ int err;
|
|
+ struct au_hnotify *hn;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ hn = au_cache_alloc_hnotify();
|
|
+ if (hn) {
|
|
+ hn->hn_aufs_inode = inode;
|
|
+ hinode->hi_notify = hn;
|
|
+ err = au_hnotify_op.alloc(hinode);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err)) {
|
|
+ hinode->hi_notify = NULL;
|
|
+ au_cache_free_hnotify(hn);
|
|
+ /*
|
|
+ * The upper dir was removed by udba, but the same named
|
|
+ * dir left. In this case, aufs assigns a new inode
|
|
+ * number and set the monitor again.
|
|
+ * For the lower dir, the old monitor is still left.
|
|
+ */
|
|
+ if (err == -EEXIST)
|
|
+ err = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_hn_free(struct au_hinode *hinode)
|
|
+{
|
|
+ struct au_hnotify *hn;
|
|
+
|
|
+ hn = hinode->hi_notify;
|
|
+ if (hn) {
|
|
+ hinode->hi_notify = NULL;
|
|
+ if (au_hnotify_op.free(hinode, hn))
|
|
+ au_cache_free_hnotify(hn);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void au_hn_ctl(struct au_hinode *hinode, int do_set)
|
|
+{
|
|
+ if (hinode->hi_notify)
|
|
+ au_hnotify_op.ctl(hinode, do_set);
|
|
+}
|
|
+
|
|
+void au_hn_reset(struct inode *inode, unsigned int flags)
|
|
+{
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct inode *hi;
|
|
+ struct dentry *iwhdentry;
|
|
+
|
|
+ bbot = au_ibbot(inode);
|
|
+ for (bindex = au_ibtop(inode); bindex <= bbot; bindex++) {
|
|
+ hi = au_h_iptr(inode, bindex);
|
|
+ if (!hi)
|
|
+ continue;
|
|
+
|
|
+ /* inode_lock_nested(hi, AuLsc_I_CHILD); */
|
|
+ iwhdentry = au_hi_wh(inode, bindex);
|
|
+ if (iwhdentry)
|
|
+ dget(iwhdentry);
|
|
+ au_igrab(hi);
|
|
+ au_set_h_iptr(inode, bindex, NULL, 0);
|
|
+ au_set_h_iptr(inode, bindex, au_igrab(hi),
|
|
+ flags & ~AuHi_XINO);
|
|
+ iput(hi);
|
|
+ dput(iwhdentry);
|
|
+ /* inode_unlock(hi); */
|
|
+ }
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int hn_xino(struct inode *inode, struct inode *h_inode)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, bbot, bfound, btop;
|
|
+ struct inode *h_i;
|
|
+
|
|
+ err = 0;
|
|
+ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
|
|
+ pr_warn("branch root dir was changed\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ bfound = -1;
|
|
+ bbot = au_ibbot(inode);
|
|
+ btop = au_ibtop(inode);
|
|
+#if 0 /* reserved for future use */
|
|
+ if (bindex == bbot) {
|
|
+ /* keep this ino in rename case */
|
|
+ goto out;
|
|
+ }
|
|
+#endif
|
|
+ for (bindex = btop; bindex <= bbot; bindex++)
|
|
+ if (au_h_iptr(inode, bindex) == h_inode) {
|
|
+ bfound = bindex;
|
|
+ break;
|
|
+ }
|
|
+ if (bfound < 0)
|
|
+ goto out;
|
|
+
|
|
+ for (bindex = btop; bindex <= bbot; bindex++) {
|
|
+ h_i = au_h_iptr(inode, bindex);
|
|
+ if (!h_i)
|
|
+ continue;
|
|
+
|
|
+ err = au_xino_write(inode->i_sb, bindex, h_i->i_ino, /*ino*/0);
|
|
+ /* ignore this error */
|
|
+ /* bad action? */
|
|
+ }
|
|
+
|
|
+ /* children inode number will be broken */
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int hn_gen_tree(struct dentry *dentry)
|
|
+{
|
|
+ int err, i, j, ndentry;
|
|
+ struct au_dcsub_pages dpages;
|
|
+ struct au_dpage *dpage;
|
|
+ struct dentry **dentries;
|
|
+
|
|
+ err = au_dpages_init(&dpages, GFP_NOFS);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = au_dcsub_pages(&dpages, dentry, NULL, NULL);
|
|
+ if (unlikely(err))
|
|
+ goto out_dpages;
|
|
+
|
|
+ for (i = 0; i < dpages.ndpage; i++) {
|
|
+ dpage = dpages.dpages + i;
|
|
+ dentries = dpage->dentries;
|
|
+ ndentry = dpage->ndentry;
|
|
+ for (j = 0; j < ndentry; j++) {
|
|
+ struct dentry *d;
|
|
+
|
|
+ d = dentries[j];
|
|
+ if (IS_ROOT(d))
|
|
+ continue;
|
|
+
|
|
+ au_digen_dec(d);
|
|
+ if (d_really_is_positive(d))
|
|
+ /* todo: reset children xino?
|
|
+ cached children only? */
|
|
+ au_iigen_dec(d_inode(d));
|
|
+ }
|
|
+ }
|
|
+
|
|
+out_dpages:
|
|
+ au_dpages_free(&dpages);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * return 0 if processed.
|
|
+ */
|
|
+static int hn_gen_by_inode(char *name, unsigned int nlen, struct inode *inode,
|
|
+ const unsigned int isdir)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d;
|
|
+ struct qstr *dname;
|
|
+
|
|
+ err = 1;
|
|
+ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
|
|
+ pr_warn("branch root dir was changed\n");
|
|
+ err = 0;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (!isdir) {
|
|
+ AuDebugOn(!name);
|
|
+ au_iigen_dec(inode);
|
|
+ spin_lock(&inode->i_lock);
|
|
+ hlist_for_each_entry(d, &inode->i_dentry, d_u.d_alias) {
|
|
+ spin_lock(&d->d_lock);
|
|
+ dname = &d->d_name;
|
|
+ if (dname->len != nlen
|
|
+ && memcmp(dname->name, name, nlen)) {
|
|
+ spin_unlock(&d->d_lock);
|
|
+ continue;
|
|
+ }
|
|
+ err = 0;
|
|
+ au_digen_dec(d);
|
|
+ spin_unlock(&d->d_lock);
|
|
+ break;
|
|
+ }
|
|
+ spin_unlock(&inode->i_lock);
|
|
+ } else {
|
|
+ au_fset_si(au_sbi(inode->i_sb), FAILED_REFRESH_DIR);
|
|
+ d = d_find_any_alias(inode);
|
|
+ if (!d) {
|
|
+ au_iigen_dec(inode);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ spin_lock(&d->d_lock);
|
|
+ dname = &d->d_name;
|
|
+ if (dname->len == nlen && !memcmp(dname->name, name, nlen)) {
|
|
+ spin_unlock(&d->d_lock);
|
|
+ err = hn_gen_tree(d);
|
|
+ spin_lock(&d->d_lock);
|
|
+ }
|
|
+ spin_unlock(&d->d_lock);
|
|
+ dput(d);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int hn_gen_by_name(struct dentry *dentry, const unsigned int isdir)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ if (IS_ROOT(dentry)) {
|
|
+ pr_warn("branch root dir was changed\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ if (!isdir) {
|
|
+ au_digen_dec(dentry);
|
|
+ if (d_really_is_positive(dentry))
|
|
+ au_iigen_dec(d_inode(dentry));
|
|
+ } else {
|
|
+ au_fset_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIR);
|
|
+ if (d_really_is_positive(dentry))
|
|
+ err = hn_gen_tree(dentry);
|
|
+ }
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* hnotify job flags */
|
|
+#define AuHnJob_XINO0 1
|
|
+#define AuHnJob_GEN (1 << 1)
|
|
+#define AuHnJob_DIRENT (1 << 2)
|
|
+#define AuHnJob_ISDIR (1 << 3)
|
|
+#define AuHnJob_TRYXINO0 (1 << 4)
|
|
+#define AuHnJob_MNTPNT (1 << 5)
|
|
+#define au_ftest_hnjob(flags, name) ((flags) & AuHnJob_##name)
|
|
+#define au_fset_hnjob(flags, name) \
|
|
+ do { (flags) |= AuHnJob_##name; } while (0)
|
|
+#define au_fclr_hnjob(flags, name) \
|
|
+ do { (flags) &= ~AuHnJob_##name; } while (0)
|
|
+
|
|
+enum {
|
|
+ AuHn_CHILD,
|
|
+ AuHn_PARENT,
|
|
+ AuHnLast
|
|
+};
|
|
+
|
|
+struct au_hnotify_args {
|
|
+ struct inode *h_dir, *dir, *h_child_inode;
|
|
+ u32 mask;
|
|
+ unsigned int flags[AuHnLast];
|
|
+ unsigned int h_child_nlen;
|
|
+ char h_child_name[];
|
|
+};
|
|
+
|
|
+struct hn_job_args {
|
|
+ unsigned int flags;
|
|
+ struct inode *inode, *h_inode, *dir, *h_dir;
|
|
+ struct dentry *dentry;
|
|
+ char *h_name;
|
|
+ int h_nlen;
|
|
+};
|
|
+
|
|
+static int hn_job(struct hn_job_args *a)
|
|
+{
|
|
+ const unsigned int isdir = au_ftest_hnjob(a->flags, ISDIR);
|
|
+ int e;
|
|
+
|
|
+ /* reset xino */
|
|
+ if (au_ftest_hnjob(a->flags, XINO0) && a->inode)
|
|
+ hn_xino(a->inode, a->h_inode); /* ignore this error */
|
|
+
|
|
+ if (au_ftest_hnjob(a->flags, TRYXINO0)
|
|
+ && a->inode
|
|
+ && a->h_inode) {
|
|
+ inode_lock_shared_nested(a->h_inode, AuLsc_I_CHILD);
|
|
+ if (!a->h_inode->i_nlink
|
|
+ && !(a->h_inode->i_state & I_LINKABLE))
|
|
+ hn_xino(a->inode, a->h_inode); /* ignore this error */
|
|
+ inode_unlock_shared(a->h_inode);
|
|
+ }
|
|
+
|
|
+ /* make the generation obsolete */
|
|
+ if (au_ftest_hnjob(a->flags, GEN)) {
|
|
+ e = -1;
|
|
+ if (a->inode)
|
|
+ e = hn_gen_by_inode(a->h_name, a->h_nlen, a->inode,
|
|
+ isdir);
|
|
+ if (e && a->dentry)
|
|
+ hn_gen_by_name(a->dentry, isdir);
|
|
+ /* ignore this error */
|
|
+ }
|
|
+
|
|
+ /* make dir entries obsolete */
|
|
+ if (au_ftest_hnjob(a->flags, DIRENT) && a->inode) {
|
|
+ struct au_vdir *vdir;
|
|
+
|
|
+ vdir = au_ivdir(a->inode);
|
|
+ if (vdir)
|
|
+ vdir->vd_jiffy = 0;
|
|
+ /* IMustLock(a->inode); */
|
|
+ /* inode_inc_iversion(a->inode); */
|
|
+ }
|
|
+
|
|
+ /* can do nothing but warn */
|
|
+ if (au_ftest_hnjob(a->flags, MNTPNT)
|
|
+ && a->dentry
|
|
+ && d_mountpoint(a->dentry))
|
|
+ pr_warn("mount-point %pd is removed or renamed\n", a->dentry);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static struct dentry *lookup_wlock_by_name(char *name, unsigned int nlen,
|
|
+ struct inode *dir)
|
|
+{
|
|
+ struct dentry *dentry, *d, *parent;
|
|
+ struct qstr *dname;
|
|
+
|
|
+ parent = d_find_any_alias(dir);
|
|
+ if (!parent)
|
|
+ return NULL;
|
|
+
|
|
+ dentry = NULL;
|
|
+ spin_lock(&parent->d_lock);
|
|
+ list_for_each_entry(d, &parent->d_subdirs, d_child) {
|
|
+ /* AuDbg("%pd\n", d); */
|
|
+ spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED);
|
|
+ dname = &d->d_name;
|
|
+ if (dname->len != nlen || memcmp(dname->name, name, nlen))
|
|
+ goto cont_unlock;
|
|
+ if (au_di(d))
|
|
+ au_digen_dec(d);
|
|
+ else
|
|
+ goto cont_unlock;
|
|
+ if (au_dcount(d) > 0) {
|
|
+ dentry = dget_dlock(d);
|
|
+ spin_unlock(&d->d_lock);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+cont_unlock:
|
|
+ spin_unlock(&d->d_lock);
|
|
+ }
|
|
+ spin_unlock(&parent->d_lock);
|
|
+ dput(parent);
|
|
+
|
|
+ if (dentry)
|
|
+ di_write_lock_child(dentry);
|
|
+
|
|
+ return dentry;
|
|
+}
|
|
+
|
|
+static struct inode *lookup_wlock_by_ino(struct super_block *sb,
|
|
+ aufs_bindex_t bindex, ino_t h_ino)
|
|
+{
|
|
+ struct inode *inode;
|
|
+ ino_t ino;
|
|
+ int err;
|
|
+
|
|
+ inode = NULL;
|
|
+ err = au_xino_read(sb, bindex, h_ino, &ino);
|
|
+ if (!err && ino)
|
|
+ inode = ilookup(sb, ino);
|
|
+ if (!inode)
|
|
+ goto out;
|
|
+
|
|
+ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
|
|
+ pr_warn("wrong root branch\n");
|
|
+ iput(inode);
|
|
+ inode = NULL;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ ii_write_lock_child(inode);
|
|
+
|
|
+out:
|
|
+ return inode;
|
|
+}
|
|
+
|
|
+static void au_hn_bh(void *_args)
|
|
+{
|
|
+ struct au_hnotify_args *a = _args;
|
|
+ struct super_block *sb;
|
|
+ aufs_bindex_t bindex, bbot, bfound;
|
|
+ unsigned char xino, try_iput;
|
|
+ int err;
|
|
+ struct inode *inode;
|
|
+ ino_t h_ino;
|
|
+ struct hn_job_args args;
|
|
+ struct dentry *dentry;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ AuDebugOn(!_args);
|
|
+ AuDebugOn(!a->h_dir);
|
|
+ AuDebugOn(!a->dir);
|
|
+ AuDebugOn(!a->mask);
|
|
+ AuDbg("mask 0x%x, i%lu, hi%lu, hci%lu\n",
|
|
+ a->mask, a->dir->i_ino, a->h_dir->i_ino,
|
|
+ a->h_child_inode ? a->h_child_inode->i_ino : 0);
|
|
+
|
|
+ inode = NULL;
|
|
+ dentry = NULL;
|
|
+ /*
|
|
+ * do not lock a->dir->i_mutex here
|
|
+ * because of d_revalidate() may cause a deadlock.
|
|
+ */
|
|
+ sb = a->dir->i_sb;
|
|
+ AuDebugOn(!sb);
|
|
+ sbinfo = au_sbi(sb);
|
|
+ AuDebugOn(!sbinfo);
|
|
+ si_write_lock(sb, AuLock_NOPLMW);
|
|
+
|
|
+ if (au_opt_test(sbinfo->si_mntflags, DIRREN))
|
|
+ switch (a->mask & FS_EVENTS_POSS_ON_CHILD) {
|
|
+ case FS_MOVED_FROM:
|
|
+ case FS_MOVED_TO:
|
|
+ AuWarn1("DIRREN with UDBA may not work correctly "
|
|
+ "for the direct rename(2)\n");
|
|
+ }
|
|
+
|
|
+ ii_read_lock_parent(a->dir);
|
|
+ bfound = -1;
|
|
+ bbot = au_ibbot(a->dir);
|
|
+ for (bindex = au_ibtop(a->dir); bindex <= bbot; bindex++)
|
|
+ if (au_h_iptr(a->dir, bindex) == a->h_dir) {
|
|
+ bfound = bindex;
|
|
+ break;
|
|
+ }
|
|
+ ii_read_unlock(a->dir);
|
|
+ if (unlikely(bfound < 0))
|
|
+ goto out;
|
|
+
|
|
+ xino = !!au_opt_test(au_mntflags(sb), XINO);
|
|
+ h_ino = 0;
|
|
+ if (a->h_child_inode)
|
|
+ h_ino = a->h_child_inode->i_ino;
|
|
+
|
|
+ if (a->h_child_nlen
|
|
+ && (au_ftest_hnjob(a->flags[AuHn_CHILD], GEN)
|
|
+ || au_ftest_hnjob(a->flags[AuHn_CHILD], MNTPNT)))
|
|
+ dentry = lookup_wlock_by_name(a->h_child_name, a->h_child_nlen,
|
|
+ a->dir);
|
|
+ try_iput = 0;
|
|
+ if (dentry && d_really_is_positive(dentry))
|
|
+ inode = d_inode(dentry);
|
|
+ if (xino && !inode && h_ino
|
|
+ && (au_ftest_hnjob(a->flags[AuHn_CHILD], XINO0)
|
|
+ || au_ftest_hnjob(a->flags[AuHn_CHILD], TRYXINO0)
|
|
+ || au_ftest_hnjob(a->flags[AuHn_CHILD], GEN))) {
|
|
+ inode = lookup_wlock_by_ino(sb, bfound, h_ino);
|
|
+ try_iput = 1;
|
|
+ }
|
|
+
|
|
+ args.flags = a->flags[AuHn_CHILD];
|
|
+ args.dentry = dentry;
|
|
+ args.inode = inode;
|
|
+ args.h_inode = a->h_child_inode;
|
|
+ args.dir = a->dir;
|
|
+ args.h_dir = a->h_dir;
|
|
+ args.h_name = a->h_child_name;
|
|
+ args.h_nlen = a->h_child_nlen;
|
|
+ err = hn_job(&args);
|
|
+ if (dentry) {
|
|
+ if (au_di(dentry))
|
|
+ di_write_unlock(dentry);
|
|
+ dput(dentry);
|
|
+ }
|
|
+ if (inode && try_iput) {
|
|
+ ii_write_unlock(inode);
|
|
+ iput(inode);
|
|
+ }
|
|
+
|
|
+ ii_write_lock_parent(a->dir);
|
|
+ args.flags = a->flags[AuHn_PARENT];
|
|
+ args.dentry = NULL;
|
|
+ args.inode = a->dir;
|
|
+ args.h_inode = a->h_dir;
|
|
+ args.dir = NULL;
|
|
+ args.h_dir = NULL;
|
|
+ args.h_name = NULL;
|
|
+ args.h_nlen = 0;
|
|
+ err = hn_job(&args);
|
|
+ ii_write_unlock(a->dir);
|
|
+
|
|
+out:
|
|
+ iput(a->h_child_inode);
|
|
+ iput(a->h_dir);
|
|
+ iput(a->dir);
|
|
+ si_write_unlock(sb);
|
|
+ au_nwt_done(&sbinfo->si_nowait);
|
|
+ au_kfree_rcu(a);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_hnotify(struct inode *h_dir, struct au_hnotify *hnotify, u32 mask,
|
|
+ const struct qstr *h_child_qstr, struct inode *h_child_inode)
|
|
+{
|
|
+ int err, len;
|
|
+ unsigned int flags[AuHnLast], f;
|
|
+ unsigned char isdir, isroot, wh;
|
|
+ struct inode *dir;
|
|
+ struct au_hnotify_args *args;
|
|
+ char *p, *h_child_name;
|
|
+
|
|
+ err = 0;
|
|
+ AuDebugOn(!hnotify || !hnotify->hn_aufs_inode);
|
|
+ dir = igrab(hnotify->hn_aufs_inode);
|
|
+ if (!dir)
|
|
+ goto out;
|
|
+
|
|
+ isroot = (dir->i_ino == AUFS_ROOT_INO);
|
|
+ wh = 0;
|
|
+ h_child_name = (void *)h_child_qstr->name;
|
|
+ len = h_child_qstr->len;
|
|
+ if (h_child_name) {
|
|
+ if (len > AUFS_WH_PFX_LEN
|
|
+ && !memcmp(h_child_name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) {
|
|
+ h_child_name += AUFS_WH_PFX_LEN;
|
|
+ len -= AUFS_WH_PFX_LEN;
|
|
+ wh = 1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ isdir = 0;
|
|
+ if (h_child_inode)
|
|
+ isdir = !!S_ISDIR(h_child_inode->i_mode);
|
|
+ flags[AuHn_PARENT] = AuHnJob_ISDIR;
|
|
+ flags[AuHn_CHILD] = 0;
|
|
+ if (isdir)
|
|
+ flags[AuHn_CHILD] = AuHnJob_ISDIR;
|
|
+ au_fset_hnjob(flags[AuHn_PARENT], DIRENT);
|
|
+ au_fset_hnjob(flags[AuHn_CHILD], GEN);
|
|
+ switch (mask & ALL_FSNOTIFY_DIRENT_EVENTS) {
|
|
+ case FS_MOVED_FROM:
|
|
+ case FS_MOVED_TO:
|
|
+ au_fset_hnjob(flags[AuHn_CHILD], XINO0);
|
|
+ au_fset_hnjob(flags[AuHn_CHILD], MNTPNT);
|
|
+ /*FALLTHROUGH*/
|
|
+ case FS_CREATE:
|
|
+ AuDebugOn(!h_child_name);
|
|
+ break;
|
|
+
|
|
+ case FS_DELETE:
|
|
+ /*
|
|
+ * aufs never be able to get this child inode.
|
|
+ * revalidation should be in d_revalidate()
|
|
+ * by checking i_nlink, i_generation or d_unhashed().
|
|
+ */
|
|
+ AuDebugOn(!h_child_name);
|
|
+ au_fset_hnjob(flags[AuHn_CHILD], TRYXINO0);
|
|
+ au_fset_hnjob(flags[AuHn_CHILD], MNTPNT);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ AuDebugOn(1);
|
|
+ }
|
|
+
|
|
+ if (wh)
|
|
+ h_child_inode = NULL;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ /* iput() and kfree() will be called in au_hnotify() */
|
|
+ args = kmalloc(sizeof(*args) + len + 1, GFP_NOFS);
|
|
+ if (unlikely(!args)) {
|
|
+ AuErr1("no memory\n");
|
|
+ iput(dir);
|
|
+ goto out;
|
|
+ }
|
|
+ args->flags[AuHn_PARENT] = flags[AuHn_PARENT];
|
|
+ args->flags[AuHn_CHILD] = flags[AuHn_CHILD];
|
|
+ args->mask = mask;
|
|
+ args->dir = dir;
|
|
+ args->h_dir = igrab(h_dir);
|
|
+ if (h_child_inode)
|
|
+ h_child_inode = igrab(h_child_inode); /* can be NULL */
|
|
+ args->h_child_inode = h_child_inode;
|
|
+ args->h_child_nlen = len;
|
|
+ if (len) {
|
|
+ p = (void *)args;
|
|
+ p += sizeof(*args);
|
|
+ memcpy(p, h_child_name, len);
|
|
+ p[len] = 0;
|
|
+ }
|
|
+
|
|
+ /* NFS fires the event for silly-renamed one from kworker */
|
|
+ f = 0;
|
|
+ if (!dir->i_nlink
|
|
+ || (au_test_nfs(h_dir->i_sb) && (mask & FS_DELETE)))
|
|
+ f = AuWkq_NEST;
|
|
+ err = au_wkq_nowait(au_hn_bh, args, dir->i_sb, f);
|
|
+ if (unlikely(err)) {
|
|
+ pr_err("wkq %d\n", err);
|
|
+ iput(args->h_child_inode);
|
|
+ iput(args->h_dir);
|
|
+ iput(args->dir);
|
|
+ au_kfree_rcu(args);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_hnotify_reset_br(unsigned int udba, struct au_branch *br, int perm)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ AuDebugOn(!(udba & AuOptMask_UDBA));
|
|
+
|
|
+ err = 0;
|
|
+ if (au_hnotify_op.reset_br)
|
|
+ err = au_hnotify_op.reset_br(udba, br, perm);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_hnotify_init_br(struct au_branch *br, int perm)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (au_hnotify_op.init_br)
|
|
+ err = au_hnotify_op.init_br(br, perm);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_hnotify_fin_br(struct au_branch *br)
|
|
+{
|
|
+ if (au_hnotify_op.fin_br)
|
|
+ au_hnotify_op.fin_br(br);
|
|
+}
|
|
+
|
|
+static void au_hn_destroy_cache(void)
|
|
+{
|
|
+ kmem_cache_destroy(au_cache[AuCache_HNOTIFY]);
|
|
+ au_cache[AuCache_HNOTIFY] = NULL;
|
|
+}
|
|
+
|
|
+int __init au_hnotify_init(void)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ au_cache[AuCache_HNOTIFY] = AuCache(au_hnotify);
|
|
+ if (au_cache[AuCache_HNOTIFY]) {
|
|
+ err = 0;
|
|
+ if (au_hnotify_op.init)
|
|
+ err = au_hnotify_op.init();
|
|
+ if (unlikely(err))
|
|
+ au_hn_destroy_cache();
|
|
+ }
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_hnotify_fin(void)
|
|
+{
|
|
+ if (au_hnotify_op.fin)
|
|
+ au_hnotify_op.fin();
|
|
+
|
|
+ /* cf. au_cache_fin() */
|
|
+ if (au_cache[AuCache_HNOTIFY])
|
|
+ au_hn_destroy_cache();
|
|
+}
|
|
diff --git a/fs/aufs/i_op.c b/fs/aufs/i_op.c
|
|
new file mode 100644
|
|
index 000000000000..2d09f80153b2
|
|
--- /dev/null
|
|
+++ b/fs/aufs/i_op.c
|
|
@@ -0,0 +1,1502 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * inode operations (except add/del/rename)
|
|
+ */
|
|
+
|
|
+#include <linux/device_cgroup.h>
|
|
+#include <linux/fs_stack.h>
|
|
+#include <linux/iversion.h>
|
|
+#include <linux/namei.h>
|
|
+#include <linux/security.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+static int h_permission(struct inode *h_inode, int mask,
|
|
+ struct path *h_path, int brperm)
|
|
+{
|
|
+ int err;
|
|
+ const unsigned char write_mask = !!(mask & (MAY_WRITE | MAY_APPEND));
|
|
+
|
|
+ err = -EPERM;
|
|
+ if (write_mask && IS_IMMUTABLE(h_inode))
|
|
+ goto out;
|
|
+
|
|
+ err = -EACCES;
|
|
+ if (((mask & MAY_EXEC)
|
|
+ && S_ISREG(h_inode->i_mode)
|
|
+ && (path_noexec(h_path)
|
|
+ || !(h_inode->i_mode & 0111))))
|
|
+ goto out;
|
|
+
|
|
+ /*
|
|
+ * - skip the lower fs test in the case of write to ro branch.
|
|
+ * - nfs dir permission write check is optimized, but a policy for
|
|
+ * link/rename requires a real check.
|
|
+ * - nfs always sets SB_POSIXACL regardless its mount option 'noacl.'
|
|
+ * in this case, generic_permission() returns -EOPNOTSUPP.
|
|
+ */
|
|
+ if ((write_mask && !au_br_writable(brperm))
|
|
+ || (au_test_nfs(h_inode->i_sb) && S_ISDIR(h_inode->i_mode)
|
|
+ && write_mask && !(mask & MAY_READ))
|
|
+ || !h_inode->i_op->permission) {
|
|
+ /* AuLabel(generic_permission); */
|
|
+ /* AuDbg("get_acl %ps\n", h_inode->i_op->get_acl); */
|
|
+ err = generic_permission(h_inode, mask);
|
|
+ if (err == -EOPNOTSUPP && au_test_nfs_noacl(h_inode))
|
|
+ err = h_inode->i_op->permission(h_inode, mask);
|
|
+ AuTraceErr(err);
|
|
+ } else {
|
|
+ /* AuLabel(h_inode->permission); */
|
|
+ err = h_inode->i_op->permission(h_inode, mask);
|
|
+ AuTraceErr(err);
|
|
+ }
|
|
+
|
|
+ if (!err)
|
|
+ err = devcgroup_inode_permission(h_inode, mask);
|
|
+ if (!err)
|
|
+ err = security_inode_permission(h_inode, mask);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_permission(struct inode *inode, int mask)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ const unsigned char isdir = !!S_ISDIR(inode->i_mode),
|
|
+ write_mask = !!(mask & (MAY_WRITE | MAY_APPEND));
|
|
+ struct inode *h_inode;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ /* todo: support rcu-walk? */
|
|
+ if (mask & MAY_NOT_BLOCK)
|
|
+ return -ECHILD;
|
|
+
|
|
+ sb = inode->i_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH);
|
|
+ ii_read_lock_child(inode);
|
|
+#if 0 /* reserved for future use */
|
|
+ /*
|
|
+ * This test may be rather 'too much' since the test is essentially done
|
|
+ * in the aufs_lookup(). Theoretically it is possible that the inode
|
|
+ * generation doesn't match to the superblock's here. But it isn't a
|
|
+ * big deal I suppose.
|
|
+ */
|
|
+ err = au_iigen_test(inode, au_sigen(sb));
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+#endif
|
|
+
|
|
+ if (!isdir
|
|
+ || write_mask
|
|
+ || au_opt_test(au_mntflags(sb), DIRPERM1)) {
|
|
+ err = au_busy_or_stale();
|
|
+ h_inode = au_h_iptr(inode, au_ibtop(inode));
|
|
+ if (unlikely(!h_inode
|
|
+ || (h_inode->i_mode & S_IFMT)
|
|
+ != (inode->i_mode & S_IFMT)))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ bindex = au_ibtop(inode);
|
|
+ br = au_sbr(sb, bindex);
|
|
+ err = h_permission(h_inode, mask, &br->br_path, br->br_perm);
|
|
+ if (write_mask
|
|
+ && !err
|
|
+ && !special_file(h_inode->i_mode)) {
|
|
+ /* test whether the upper writable branch exists */
|
|
+ err = -EROFS;
|
|
+ for (; bindex >= 0; bindex--)
|
|
+ if (!au_br_rdonly(au_sbr(sb, bindex))) {
|
|
+ err = 0;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* non-write to dir */
|
|
+ err = 0;
|
|
+ bbot = au_ibbot(inode);
|
|
+ for (bindex = au_ibtop(inode); !err && bindex <= bbot; bindex++) {
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+ if (h_inode) {
|
|
+ err = au_busy_or_stale();
|
|
+ if (unlikely(!S_ISDIR(h_inode->i_mode)))
|
|
+ break;
|
|
+
|
|
+ br = au_sbr(sb, bindex);
|
|
+ err = h_permission(h_inode, mask, &br->br_path,
|
|
+ br->br_perm);
|
|
+ }
|
|
+ }
|
|
+
|
|
+out:
|
|
+ ii_read_unlock(inode);
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static struct dentry *aufs_lookup(struct inode *dir, struct dentry *dentry,
|
|
+ unsigned int flags)
|
|
+{
|
|
+ struct dentry *ret, *parent;
|
|
+ struct inode *inode;
|
|
+ struct super_block *sb;
|
|
+ int err, npositive;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ /* todo: support rcu-walk? */
|
|
+ ret = ERR_PTR(-ECHILD);
|
|
+ if (flags & LOOKUP_RCU)
|
|
+ goto out;
|
|
+
|
|
+ ret = ERR_PTR(-ENAMETOOLONG);
|
|
+ if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN))
|
|
+ goto out;
|
|
+
|
|
+ sb = dir->i_sb;
|
|
+ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
|
|
+ ret = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ err = au_di_init(dentry);
|
|
+ ret = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out_si;
|
|
+
|
|
+ inode = NULL;
|
|
+ npositive = 0; /* suppress a warning */
|
|
+ parent = dentry->d_parent; /* dir inode is locked */
|
|
+ di_read_lock_parent(parent, AuLock_IR);
|
|
+ err = au_alive_dir(parent);
|
|
+ if (!err)
|
|
+ err = au_digen_test(parent, au_sigen(sb));
|
|
+ if (!err) {
|
|
+ /* regardless LOOKUP_CREATE, always ALLOW_NEG */
|
|
+ npositive = au_lkup_dentry(dentry, au_dbtop(parent),
|
|
+ AuLkup_ALLOW_NEG);
|
|
+ err = npositive;
|
|
+ }
|
|
+ di_read_unlock(parent, AuLock_IR);
|
|
+ ret = ERR_PTR(err);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out_unlock;
|
|
+
|
|
+ if (npositive) {
|
|
+ inode = au_new_inode(dentry, /*must_new*/0);
|
|
+ if (IS_ERR(inode)) {
|
|
+ ret = (void *)inode;
|
|
+ inode = NULL;
|
|
+ goto out_unlock;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (inode)
|
|
+ atomic_inc(&inode->i_count);
|
|
+ ret = d_splice_alias(inode, dentry);
|
|
+#if 0 /* reserved for future use */
|
|
+ if (unlikely(d_need_lookup(dentry))) {
|
|
+ spin_lock(&dentry->d_lock);
|
|
+ dentry->d_flags &= ~DCACHE_NEED_LOOKUP;
|
|
+ spin_unlock(&dentry->d_lock);
|
|
+ } else
|
|
+#endif
|
|
+ if (inode) {
|
|
+ if (!IS_ERR(ret)) {
|
|
+ iput(inode);
|
|
+ if (ret && ret != dentry)
|
|
+ ii_write_unlock(inode);
|
|
+ } else {
|
|
+ ii_write_unlock(inode);
|
|
+ iput(inode);
|
|
+ inode = NULL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+out_unlock:
|
|
+ di_write_unlock(dentry);
|
|
+out_si:
|
|
+ si_read_unlock(sb);
|
|
+out:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * very dirty and complicated aufs ->atomic_open().
|
|
+ * aufs_atomic_open()
|
|
+ * + au_aopen_or_create()
|
|
+ * + add_simple()
|
|
+ * + vfsub_atomic_open()
|
|
+ * + branch fs ->atomic_open()
|
|
+ * may call the actual 'open' for h_file
|
|
+ * + inc br_nfiles only if opened
|
|
+ * + au_aopen_no_open() or au_aopen_do_open()
|
|
+ *
|
|
+ * au_aopen_do_open()
|
|
+ * + finish_open()
|
|
+ * + au_do_aopen()
|
|
+ * + au_do_open() the body of all 'open'
|
|
+ * + au_do_open_nondir()
|
|
+ * set the passed h_file
|
|
+ *
|
|
+ * au_aopen_no_open()
|
|
+ * + finish_no_open()
|
|
+ */
|
|
+
|
|
+struct aopen_node {
|
|
+ struct hlist_bl_node hblist;
|
|
+ struct file *file, *h_file;
|
|
+};
|
|
+
|
|
+static int au_do_aopen(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct hlist_bl_head *aopen;
|
|
+ struct hlist_bl_node *pos;
|
|
+ struct aopen_node *node;
|
|
+ struct au_do_open_args args = {
|
|
+ .aopen = 1,
|
|
+ .open = au_do_open_nondir
|
|
+ };
|
|
+
|
|
+ aopen = &au_sbi(inode->i_sb)->si_aopen;
|
|
+ hlist_bl_lock(aopen);
|
|
+ hlist_bl_for_each_entry(node, pos, aopen, hblist)
|
|
+ if (node->file == file) {
|
|
+ args.h_file = node->h_file;
|
|
+ break;
|
|
+ }
|
|
+ hlist_bl_unlock(aopen);
|
|
+ /* AuDebugOn(!args.h_file); */
|
|
+
|
|
+ return au_do_open(file, &args);
|
|
+}
|
|
+
|
|
+static int au_aopen_do_open(struct file *file, struct dentry *dentry,
|
|
+ struct aopen_node *aopen_node)
|
|
+{
|
|
+ int err;
|
|
+ struct hlist_bl_head *aopen;
|
|
+
|
|
+ AuLabel(here);
|
|
+ aopen = &au_sbi(dentry->d_sb)->si_aopen;
|
|
+ au_hbl_add(&aopen_node->hblist, aopen);
|
|
+ err = finish_open(file, dentry, au_do_aopen);
|
|
+ au_hbl_del(&aopen_node->hblist, aopen);
|
|
+ /* AuDbgFile(file); */
|
|
+ AuDbg("%pd%s%s\n", dentry,
|
|
+ (file->f_mode & FMODE_CREATED) ? " created" : "",
|
|
+ (file->f_mode & FMODE_OPENED) ? " opened" : "");
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_aopen_no_open(struct file *file, struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ AuLabel(here);
|
|
+ dget(dentry);
|
|
+ err = finish_no_open(file, dentry);
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_atomic_open(struct inode *dir, struct dentry *dentry,
|
|
+ struct file *file, unsigned int open_flag,
|
|
+ umode_t create_mode)
|
|
+{
|
|
+ int err, did_open;
|
|
+ unsigned int lkup_flags;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct super_block *sb;
|
|
+ struct dentry *parent, *d;
|
|
+ struct vfsub_aopen_args args = {
|
|
+ .open_flag = open_flag,
|
|
+ .create_mode = create_mode
|
|
+ };
|
|
+ struct aopen_node aopen_node = {
|
|
+ .file = file
|
|
+ };
|
|
+
|
|
+ IMustLock(dir);
|
|
+ AuDbg("open_flag 0%o\n", open_flag);
|
|
+ AuDbgDentry(dentry);
|
|
+
|
|
+ err = 0;
|
|
+ if (!au_di(dentry)) {
|
|
+ lkup_flags = LOOKUP_OPEN;
|
|
+ if (open_flag & O_CREAT)
|
|
+ lkup_flags |= LOOKUP_CREATE;
|
|
+ d = aufs_lookup(dir, dentry, lkup_flags);
|
|
+ if (IS_ERR(d)) {
|
|
+ err = PTR_ERR(d);
|
|
+ AuTraceErr(err);
|
|
+ goto out;
|
|
+ } else if (d) {
|
|
+ /*
|
|
+ * obsoleted dentry found.
|
|
+ * another error will be returned later.
|
|
+ */
|
|
+ d_drop(d);
|
|
+ AuDbgDentry(d);
|
|
+ dput(d);
|
|
+ }
|
|
+ AuDbgDentry(dentry);
|
|
+ }
|
|
+
|
|
+ if (d_is_positive(dentry)
|
|
+ || d_unhashed(dentry)
|
|
+ || d_unlinked(dentry)
|
|
+ || !(open_flag & O_CREAT)) {
|
|
+ err = au_aopen_no_open(file, dentry);
|
|
+ goto out; /* success */
|
|
+ }
|
|
+
|
|
+ err = aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH | AuLock_GEN);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ parent = dentry->d_parent; /* dir is locked */
|
|
+ di_write_lock_parent(parent);
|
|
+ err = au_lkup_dentry(dentry, /*btop*/0, AuLkup_ALLOW_NEG);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out_parent;
|
|
+
|
|
+ AuDbgDentry(dentry);
|
|
+ if (d_is_positive(dentry)) {
|
|
+ err = au_aopen_no_open(file, dentry);
|
|
+ goto out_parent; /* success */
|
|
+ }
|
|
+
|
|
+ args.file = alloc_empty_file(file->f_flags, current_cred());
|
|
+ err = PTR_ERR(args.file);
|
|
+ if (IS_ERR(args.file))
|
|
+ goto out_parent;
|
|
+
|
|
+ bindex = au_dbtop(dentry);
|
|
+ err = au_aopen_or_create(dir, dentry, &args);
|
|
+ AuTraceErr(err);
|
|
+ AuDbgFile(args.file);
|
|
+ file->f_mode = args.file->f_mode & ~FMODE_OPENED;
|
|
+ did_open = !!(args.file->f_mode & FMODE_OPENED);
|
|
+ if (!did_open) {
|
|
+ fput(args.file);
|
|
+ args.file = NULL;
|
|
+ }
|
|
+ di_write_unlock(parent);
|
|
+ di_write_unlock(dentry);
|
|
+ if (unlikely(err < 0)) {
|
|
+ if (args.file)
|
|
+ fput(args.file);
|
|
+ goto out_sb;
|
|
+ }
|
|
+
|
|
+ if (!did_open)
|
|
+ err = au_aopen_no_open(file, dentry);
|
|
+ else {
|
|
+ aopen_node.h_file = args.file;
|
|
+ err = au_aopen_do_open(file, dentry, &aopen_node);
|
|
+ }
|
|
+ if (unlikely(err < 0)) {
|
|
+ if (args.file)
|
|
+ fput(args.file);
|
|
+ if (did_open)
|
|
+ au_lcnt_dec(&args.br->br_nfiles);
|
|
+ }
|
|
+ goto out_sb; /* success */
|
|
+
|
|
+out_parent:
|
|
+ di_write_unlock(parent);
|
|
+ di_write_unlock(dentry);
|
|
+out_sb:
|
|
+ si_read_unlock(sb);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ AuDbgFile(file);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_wr_dir_cpup(struct dentry *dentry, struct dentry *parent,
|
|
+ const unsigned char add_entry, aufs_bindex_t bcpup,
|
|
+ aufs_bindex_t btop)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *h_parent;
|
|
+ struct inode *h_dir;
|
|
+
|
|
+ if (add_entry)
|
|
+ IMustLock(d_inode(parent));
|
|
+ else
|
|
+ di_write_lock_parent(parent);
|
|
+
|
|
+ err = 0;
|
|
+ if (!au_h_dptr(parent, bcpup)) {
|
|
+ if (btop > bcpup)
|
|
+ err = au_cpup_dirs(dentry, bcpup);
|
|
+ else if (btop < bcpup)
|
|
+ err = au_cpdown_dirs(dentry, bcpup);
|
|
+ else
|
|
+ BUG();
|
|
+ }
|
|
+ if (!err && add_entry && !au_ftest_wrdir(add_entry, TMPFILE)) {
|
|
+ h_parent = au_h_dptr(parent, bcpup);
|
|
+ h_dir = d_inode(h_parent);
|
|
+ inode_lock_shared_nested(h_dir, AuLsc_I_PARENT);
|
|
+ err = au_lkup_neg(dentry, bcpup, /*wh*/0);
|
|
+ /* todo: no unlock here */
|
|
+ inode_unlock_shared(h_dir);
|
|
+
|
|
+ AuDbg("bcpup %d\n", bcpup);
|
|
+ if (!err) {
|
|
+ if (d_really_is_negative(dentry))
|
|
+ au_set_h_dptr(dentry, btop, NULL);
|
|
+ au_update_dbrange(dentry, /*do_put_zero*/0);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!add_entry)
|
|
+ di_write_unlock(parent);
|
|
+ if (!err)
|
|
+ err = bcpup; /* success */
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * decide the branch and the parent dir where we will create a new entry.
|
|
+ * returns new bindex or an error.
|
|
+ * copyup the parent dir if needed.
|
|
+ */
|
|
+int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
|
|
+ struct au_wr_dir_args *args)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int flags;
|
|
+ aufs_bindex_t bcpup, btop, src_btop;
|
|
+ const unsigned char add_entry
|
|
+ = au_ftest_wrdir(args->flags, ADD_ENTRY)
|
|
+ | au_ftest_wrdir(args->flags, TMPFILE);
|
|
+ struct super_block *sb;
|
|
+ struct dentry *parent;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ sbinfo = au_sbi(sb);
|
|
+ parent = dget_parent(dentry);
|
|
+ btop = au_dbtop(dentry);
|
|
+ bcpup = btop;
|
|
+ if (args->force_btgt < 0) {
|
|
+ if (src_dentry) {
|
|
+ src_btop = au_dbtop(src_dentry);
|
|
+ if (src_btop < btop)
|
|
+ bcpup = src_btop;
|
|
+ } else if (add_entry) {
|
|
+ flags = 0;
|
|
+ if (au_ftest_wrdir(args->flags, ISDIR))
|
|
+ au_fset_wbr(flags, DIR);
|
|
+ err = AuWbrCreate(sbinfo, dentry, flags);
|
|
+ bcpup = err;
|
|
+ }
|
|
+
|
|
+ if (bcpup < 0 || au_test_ro(sb, bcpup, d_inode(dentry))) {
|
|
+ if (add_entry)
|
|
+ err = AuWbrCopyup(sbinfo, dentry);
|
|
+ else {
|
|
+ if (!IS_ROOT(dentry)) {
|
|
+ di_read_lock_parent(parent, !AuLock_IR);
|
|
+ err = AuWbrCopyup(sbinfo, dentry);
|
|
+ di_read_unlock(parent, !AuLock_IR);
|
|
+ } else
|
|
+ err = AuWbrCopyup(sbinfo, dentry);
|
|
+ }
|
|
+ bcpup = err;
|
|
+ if (unlikely(err < 0))
|
|
+ goto out;
|
|
+ }
|
|
+ } else {
|
|
+ bcpup = args->force_btgt;
|
|
+ AuDebugOn(au_test_ro(sb, bcpup, d_inode(dentry)));
|
|
+ }
|
|
+
|
|
+ AuDbg("btop %d, bcpup %d\n", btop, bcpup);
|
|
+ err = bcpup;
|
|
+ if (bcpup == btop)
|
|
+ goto out; /* success */
|
|
+
|
|
+ /* copyup the new parent into the branch we process */
|
|
+ err = au_wr_dir_cpup(dentry, parent, add_entry, bcpup, btop);
|
|
+ if (err >= 0) {
|
|
+ if (d_really_is_negative(dentry)) {
|
|
+ au_set_h_dptr(dentry, btop, NULL);
|
|
+ au_set_dbtop(dentry, bcpup);
|
|
+ au_set_dbbot(dentry, bcpup);
|
|
+ }
|
|
+ AuDebugOn(add_entry
|
|
+ && !au_ftest_wrdir(args->flags, TMPFILE)
|
|
+ && !au_h_dptr(dentry, bcpup));
|
|
+ }
|
|
+
|
|
+out:
|
|
+ dput(parent);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void au_pin_hdir_unlock(struct au_pin *p)
|
|
+{
|
|
+ if (p->hdir)
|
|
+ au_hn_inode_unlock(p->hdir);
|
|
+}
|
|
+
|
|
+int au_pin_hdir_lock(struct au_pin *p)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (!p->hdir)
|
|
+ goto out;
|
|
+
|
|
+ /* even if an error happens later, keep this lock */
|
|
+ au_hn_inode_lock_nested(p->hdir, p->lsc_hi);
|
|
+
|
|
+ err = -EBUSY;
|
|
+ if (unlikely(p->hdir->hi_inode != d_inode(p->h_parent)))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ if (p->h_dentry)
|
|
+ err = au_h_verify(p->h_dentry, p->udba, p->hdir->hi_inode,
|
|
+ p->h_parent, p->br);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_pin_hdir_relock(struct au_pin *p)
|
|
+{
|
|
+ int err, i;
|
|
+ struct inode *h_i;
|
|
+ struct dentry *h_d[] = {
|
|
+ p->h_dentry,
|
|
+ p->h_parent
|
|
+ };
|
|
+
|
|
+ err = au_pin_hdir_lock(p);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ for (i = 0; !err && i < sizeof(h_d)/sizeof(*h_d); i++) {
|
|
+ if (!h_d[i])
|
|
+ continue;
|
|
+ if (d_is_positive(h_d[i])) {
|
|
+ h_i = d_inode(h_d[i]);
|
|
+ err = !h_i->i_nlink;
|
|
+ }
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_pin_hdir_set_owner(struct au_pin *p, struct task_struct *task)
|
|
+{
|
|
+ atomic_long_set(&p->hdir->hi_inode->i_rwsem.owner, (long)task);
|
|
+}
|
|
+
|
|
+void au_pin_hdir_acquire_nest(struct au_pin *p)
|
|
+{
|
|
+ if (p->hdir) {
|
|
+ rwsem_acquire_nest(&p->hdir->hi_inode->i_rwsem.dep_map,
|
|
+ p->lsc_hi, 0, NULL, _RET_IP_);
|
|
+ au_pin_hdir_set_owner(p, current);
|
|
+ }
|
|
+}
|
|
+
|
|
+void au_pin_hdir_release(struct au_pin *p)
|
|
+{
|
|
+ if (p->hdir) {
|
|
+ au_pin_hdir_set_owner(p, p->task);
|
|
+ rwsem_release(&p->hdir->hi_inode->i_rwsem.dep_map, _RET_IP_);
|
|
+ }
|
|
+}
|
|
+
|
|
+struct dentry *au_pinned_h_parent(struct au_pin *pin)
|
|
+{
|
|
+ if (pin && pin->parent)
|
|
+ return au_h_dptr(pin->parent, pin->bindex);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+void au_unpin(struct au_pin *p)
|
|
+{
|
|
+ if (p->hdir)
|
|
+ au_pin_hdir_unlock(p);
|
|
+ if (p->h_mnt && au_ftest_pin(p->flags, MNT_WRITE))
|
|
+ vfsub_mnt_drop_write(p->h_mnt);
|
|
+ if (!p->hdir)
|
|
+ return;
|
|
+
|
|
+ if (!au_ftest_pin(p->flags, DI_LOCKED))
|
|
+ di_read_unlock(p->parent, AuLock_IR);
|
|
+ iput(p->hdir->hi_inode);
|
|
+ dput(p->parent);
|
|
+ p->parent = NULL;
|
|
+ p->hdir = NULL;
|
|
+ p->h_mnt = NULL;
|
|
+ /* do not clear p->task */
|
|
+}
|
|
+
|
|
+int au_do_pin(struct au_pin *p)
|
|
+{
|
|
+ int err;
|
|
+ struct super_block *sb;
|
|
+ struct inode *h_dir;
|
|
+
|
|
+ err = 0;
|
|
+ sb = p->dentry->d_sb;
|
|
+ p->br = au_sbr(sb, p->bindex);
|
|
+ if (IS_ROOT(p->dentry)) {
|
|
+ if (au_ftest_pin(p->flags, MNT_WRITE)) {
|
|
+ p->h_mnt = au_br_mnt(p->br);
|
|
+ err = vfsub_mnt_want_write(p->h_mnt);
|
|
+ if (unlikely(err)) {
|
|
+ au_fclr_pin(p->flags, MNT_WRITE);
|
|
+ goto out_err;
|
|
+ }
|
|
+ }
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ p->h_dentry = NULL;
|
|
+ if (p->bindex <= au_dbbot(p->dentry))
|
|
+ p->h_dentry = au_h_dptr(p->dentry, p->bindex);
|
|
+
|
|
+ p->parent = dget_parent(p->dentry);
|
|
+ if (!au_ftest_pin(p->flags, DI_LOCKED))
|
|
+ di_read_lock(p->parent, AuLock_IR, p->lsc_di);
|
|
+
|
|
+ h_dir = NULL;
|
|
+ p->h_parent = au_h_dptr(p->parent, p->bindex);
|
|
+ p->hdir = au_hi(d_inode(p->parent), p->bindex);
|
|
+ if (p->hdir)
|
|
+ h_dir = p->hdir->hi_inode;
|
|
+
|
|
+ /*
|
|
+ * udba case, or
|
|
+ * if DI_LOCKED is not set, then p->parent may be different
|
|
+ * and h_parent can be NULL.
|
|
+ */
|
|
+ if (unlikely(!p->hdir || !h_dir || !p->h_parent)) {
|
|
+ err = -EBUSY;
|
|
+ if (!au_ftest_pin(p->flags, DI_LOCKED))
|
|
+ di_read_unlock(p->parent, AuLock_IR);
|
|
+ dput(p->parent);
|
|
+ p->parent = NULL;
|
|
+ goto out_err;
|
|
+ }
|
|
+
|
|
+ if (au_ftest_pin(p->flags, MNT_WRITE)) {
|
|
+ p->h_mnt = au_br_mnt(p->br);
|
|
+ err = vfsub_mnt_want_write(p->h_mnt);
|
|
+ if (unlikely(err)) {
|
|
+ au_fclr_pin(p->flags, MNT_WRITE);
|
|
+ if (!au_ftest_pin(p->flags, DI_LOCKED))
|
|
+ di_read_unlock(p->parent, AuLock_IR);
|
|
+ dput(p->parent);
|
|
+ p->parent = NULL;
|
|
+ goto out_err;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ au_igrab(h_dir);
|
|
+ err = au_pin_hdir_lock(p);
|
|
+ if (!err)
|
|
+ goto out; /* success */
|
|
+
|
|
+ au_unpin(p);
|
|
+
|
|
+out_err:
|
|
+ pr_err("err %d\n", err);
|
|
+ err = au_busy_or_stale();
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_pin_init(struct au_pin *p, struct dentry *dentry,
|
|
+ aufs_bindex_t bindex, int lsc_di, int lsc_hi,
|
|
+ unsigned int udba, unsigned char flags)
|
|
+{
|
|
+ p->dentry = dentry;
|
|
+ p->udba = udba;
|
|
+ p->lsc_di = lsc_di;
|
|
+ p->lsc_hi = lsc_hi;
|
|
+ p->flags = flags;
|
|
+ p->bindex = bindex;
|
|
+
|
|
+ p->parent = NULL;
|
|
+ p->hdir = NULL;
|
|
+ p->h_mnt = NULL;
|
|
+
|
|
+ p->h_dentry = NULL;
|
|
+ p->h_parent = NULL;
|
|
+ p->br = NULL;
|
|
+ p->task = current;
|
|
+}
|
|
+
|
|
+int au_pin(struct au_pin *pin, struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ unsigned int udba, unsigned char flags)
|
|
+{
|
|
+ au_pin_init(pin, dentry, bindex, AuLsc_DI_PARENT, AuLsc_I_PARENT2,
|
|
+ udba, flags);
|
|
+ return au_do_pin(pin);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * ->setattr() and ->getattr() are called in various cases.
|
|
+ * chmod, stat: dentry is revalidated.
|
|
+ * fchmod, fstat: file and dentry are not revalidated, additionally they may be
|
|
+ * unhashed.
|
|
+ * for ->setattr(), ia->ia_file is passed from ftruncate only.
|
|
+ */
|
|
+/* todo: consolidate with do_refresh() and simple_reval_dpath() */
|
|
+int au_reval_for_attr(struct dentry *dentry, unsigned int sigen)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *parent;
|
|
+
|
|
+ err = 0;
|
|
+ if (au_digen_test(dentry, sigen)) {
|
|
+ parent = dget_parent(dentry);
|
|
+ di_read_lock_parent(parent, AuLock_IR);
|
|
+ err = au_refresh_dentry(dentry, parent);
|
|
+ di_read_unlock(parent, AuLock_IR);
|
|
+ dput(parent);
|
|
+ }
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_pin_and_icpup(struct dentry *dentry, struct iattr *ia,
|
|
+ struct au_icpup_args *a)
|
|
+{
|
|
+ int err;
|
|
+ loff_t sz;
|
|
+ aufs_bindex_t btop, ibtop;
|
|
+ struct dentry *hi_wh, *parent;
|
|
+ struct inode *inode;
|
|
+ struct au_wr_dir_args wr_dir_args = {
|
|
+ .force_btgt = -1,
|
|
+ .flags = 0
|
|
+ };
|
|
+
|
|
+ if (d_is_dir(dentry))
|
|
+ au_fset_wrdir(wr_dir_args.flags, ISDIR);
|
|
+ /* plink or hi_wh() case */
|
|
+ btop = au_dbtop(dentry);
|
|
+ inode = d_inode(dentry);
|
|
+ ibtop = au_ibtop(inode);
|
|
+ if (btop != ibtop && !au_test_ro(inode->i_sb, ibtop, inode))
|
|
+ wr_dir_args.force_btgt = ibtop;
|
|
+ err = au_wr_dir(dentry, /*src_dentry*/NULL, &wr_dir_args);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out;
|
|
+ a->btgt = err;
|
|
+ if (err != btop)
|
|
+ au_fset_icpup(a->flags, DID_CPUP);
|
|
+
|
|
+ err = 0;
|
|
+ a->pin_flags = AuPin_MNT_WRITE;
|
|
+ parent = NULL;
|
|
+ if (!IS_ROOT(dentry)) {
|
|
+ au_fset_pin(a->pin_flags, DI_LOCKED);
|
|
+ parent = dget_parent(dentry);
|
|
+ di_write_lock_parent(parent);
|
|
+ }
|
|
+
|
|
+ err = au_pin(&a->pin, dentry, a->btgt, a->udba, a->pin_flags);
|
|
+ if (unlikely(err))
|
|
+ goto out_parent;
|
|
+
|
|
+ sz = -1;
|
|
+ a->h_path.dentry = au_h_dptr(dentry, btop);
|
|
+ a->h_inode = d_inode(a->h_path.dentry);
|
|
+ if (ia && (ia->ia_valid & ATTR_SIZE)) {
|
|
+ inode_lock_shared_nested(a->h_inode, AuLsc_I_CHILD);
|
|
+ if (ia->ia_size < i_size_read(a->h_inode))
|
|
+ sz = ia->ia_size;
|
|
+ inode_unlock_shared(a->h_inode);
|
|
+ }
|
|
+
|
|
+ hi_wh = NULL;
|
|
+ if (au_ftest_icpup(a->flags, DID_CPUP) && d_unlinked(dentry)) {
|
|
+ hi_wh = au_hi_wh(inode, a->btgt);
|
|
+ if (!hi_wh) {
|
|
+ struct au_cp_generic cpg = {
|
|
+ .dentry = dentry,
|
|
+ .bdst = a->btgt,
|
|
+ .bsrc = -1,
|
|
+ .len = sz,
|
|
+ .pin = &a->pin
|
|
+ };
|
|
+ err = au_sio_cpup_wh(&cpg, /*file*/NULL);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+ hi_wh = au_hi_wh(inode, a->btgt);
|
|
+ /* todo: revalidate hi_wh? */
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (parent) {
|
|
+ au_pin_set_parent_lflag(&a->pin, /*lflag*/0);
|
|
+ di_downgrade_lock(parent, AuLock_IR);
|
|
+ dput(parent);
|
|
+ parent = NULL;
|
|
+ }
|
|
+ if (!au_ftest_icpup(a->flags, DID_CPUP))
|
|
+ goto out; /* success */
|
|
+
|
|
+ if (!d_unhashed(dentry)) {
|
|
+ struct au_cp_generic cpg = {
|
|
+ .dentry = dentry,
|
|
+ .bdst = a->btgt,
|
|
+ .bsrc = btop,
|
|
+ .len = sz,
|
|
+ .pin = &a->pin,
|
|
+ .flags = AuCpup_DTIME | AuCpup_HOPEN
|
|
+ };
|
|
+ err = au_sio_cpup_simple(&cpg);
|
|
+ if (!err)
|
|
+ a->h_path.dentry = au_h_dptr(dentry, a->btgt);
|
|
+ } else if (!hi_wh)
|
|
+ a->h_path.dentry = au_h_dptr(dentry, a->btgt);
|
|
+ else
|
|
+ a->h_path.dentry = hi_wh; /* do not dget here */
|
|
+
|
|
+out_unlock:
|
|
+ a->h_inode = d_inode(a->h_path.dentry);
|
|
+ if (!err)
|
|
+ goto out; /* success */
|
|
+ au_unpin(&a->pin);
|
|
+out_parent:
|
|
+ if (parent) {
|
|
+ di_write_unlock(parent);
|
|
+ dput(parent);
|
|
+ }
|
|
+out:
|
|
+ if (!err)
|
|
+ inode_lock_nested(a->h_inode, AuLsc_I_CHILD);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_setattr(struct dentry *dentry, struct iattr *ia)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *inode, *delegated;
|
|
+ struct super_block *sb;
|
|
+ struct file *file;
|
|
+ struct au_icpup_args *a;
|
|
+
|
|
+ inode = d_inode(dentry);
|
|
+ IMustLock(inode);
|
|
+
|
|
+ err = setattr_prepare(dentry, ia);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ a = kzalloc(sizeof(*a), GFP_NOFS);
|
|
+ if (unlikely(!a))
|
|
+ goto out;
|
|
+
|
|
+ if (ia->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID))
|
|
+ ia->ia_valid &= ~ATTR_MODE;
|
|
+
|
|
+ file = NULL;
|
|
+ sb = dentry->d_sb;
|
|
+ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
|
|
+ if (unlikely(err))
|
|
+ goto out_kfree;
|
|
+
|
|
+ if (ia->ia_valid & ATTR_FILE) {
|
|
+ /* currently ftruncate(2) only */
|
|
+ AuDebugOn(!d_is_reg(dentry));
|
|
+ file = ia->ia_file;
|
|
+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1,
|
|
+ /*fi_lsc*/0);
|
|
+ if (unlikely(err))
|
|
+ goto out_si;
|
|
+ ia->ia_file = au_hf_top(file);
|
|
+ a->udba = AuOpt_UDBA_NONE;
|
|
+ } else {
|
|
+ /* fchmod() doesn't pass ia_file */
|
|
+ a->udba = au_opt_udba(sb);
|
|
+ di_write_lock_child(dentry);
|
|
+ /* no d_unlinked(), to set UDBA_NONE for root */
|
|
+ if (d_unhashed(dentry))
|
|
+ a->udba = AuOpt_UDBA_NONE;
|
|
+ if (a->udba != AuOpt_UDBA_NONE) {
|
|
+ AuDebugOn(IS_ROOT(dentry));
|
|
+ err = au_reval_for_attr(dentry, au_sigen(sb));
|
|
+ if (unlikely(err))
|
|
+ goto out_dentry;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = au_pin_and_icpup(dentry, ia, a);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out_dentry;
|
|
+ if (au_ftest_icpup(a->flags, DID_CPUP)) {
|
|
+ ia->ia_file = NULL;
|
|
+ ia->ia_valid &= ~ATTR_FILE;
|
|
+ }
|
|
+
|
|
+ a->h_path.mnt = au_sbr_mnt(sb, a->btgt);
|
|
+ if ((ia->ia_valid & (ATTR_MODE | ATTR_CTIME))
|
|
+ == (ATTR_MODE | ATTR_CTIME)) {
|
|
+ err = security_path_chmod(&a->h_path, ia->ia_mode);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+ } else if ((ia->ia_valid & (ATTR_UID | ATTR_GID))
|
|
+ && (ia->ia_valid & ATTR_CTIME)) {
|
|
+ err = security_path_chown(&a->h_path, ia->ia_uid, ia->ia_gid);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+ }
|
|
+
|
|
+ if (ia->ia_valid & ATTR_SIZE) {
|
|
+ struct file *f;
|
|
+
|
|
+ if (ia->ia_size < i_size_read(inode))
|
|
+ /* unmap only */
|
|
+ truncate_setsize(inode, ia->ia_size);
|
|
+
|
|
+ f = NULL;
|
|
+ if (ia->ia_valid & ATTR_FILE)
|
|
+ f = ia->ia_file;
|
|
+ inode_unlock(a->h_inode);
|
|
+ err = vfsub_trunc(&a->h_path, ia->ia_size, ia->ia_valid, f);
|
|
+ inode_lock_nested(a->h_inode, AuLsc_I_CHILD);
|
|
+ } else {
|
|
+ delegated = NULL;
|
|
+ while (1) {
|
|
+ err = vfsub_notify_change(&a->h_path, ia, &delegated);
|
|
+ if (delegated) {
|
|
+ err = break_deleg_wait(&delegated);
|
|
+ if (!err)
|
|
+ continue;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ /*
|
|
+ * regardless aufs 'acl' option setting.
|
|
+ * why don't all acl-aware fs call this func from their ->setattr()?
|
|
+ */
|
|
+ if (!err && (ia->ia_valid & ATTR_MODE))
|
|
+ err = vfsub_acl_chmod(a->h_inode, ia->ia_mode);
|
|
+ if (!err)
|
|
+ au_cpup_attr_changeable(inode);
|
|
+
|
|
+out_unlock:
|
|
+ inode_unlock(a->h_inode);
|
|
+ au_unpin(&a->pin);
|
|
+ if (unlikely(err))
|
|
+ au_update_dbtop(dentry);
|
|
+out_dentry:
|
|
+ di_write_unlock(dentry);
|
|
+ if (file) {
|
|
+ fi_write_unlock(file);
|
|
+ ia->ia_file = file;
|
|
+ ia->ia_valid |= ATTR_FILE;
|
|
+ }
|
|
+out_si:
|
|
+ si_read_unlock(sb);
|
|
+out_kfree:
|
|
+ au_kfree_rcu(a);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+#if IS_ENABLED(CONFIG_AUFS_XATTR) || IS_ENABLED(CONFIG_FS_POSIX_ACL)
|
|
+static int au_h_path_to_set_attr(struct dentry *dentry,
|
|
+ struct au_icpup_args *a, struct path *h_path)
|
|
+{
|
|
+ int err;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ a->udba = au_opt_udba(sb);
|
|
+ /* no d_unlinked(), to set UDBA_NONE for root */
|
|
+ if (d_unhashed(dentry))
|
|
+ a->udba = AuOpt_UDBA_NONE;
|
|
+ if (a->udba != AuOpt_UDBA_NONE) {
|
|
+ AuDebugOn(IS_ROOT(dentry));
|
|
+ err = au_reval_for_attr(dentry, au_sigen(sb));
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ }
|
|
+ err = au_pin_and_icpup(dentry, /*ia*/NULL, a);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out;
|
|
+
|
|
+ h_path->dentry = a->h_path.dentry;
|
|
+ h_path->mnt = au_sbr_mnt(sb, a->btgt);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+ssize_t au_sxattr(struct dentry *dentry, struct inode *inode,
|
|
+ struct au_sxattr *arg)
|
|
+{
|
|
+ int err;
|
|
+ struct path h_path;
|
|
+ struct super_block *sb;
|
|
+ struct au_icpup_args *a;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ IMustLock(inode);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ a = kzalloc(sizeof(*a), GFP_NOFS);
|
|
+ if (unlikely(!a))
|
|
+ goto out;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
|
|
+ if (unlikely(err))
|
|
+ goto out_kfree;
|
|
+
|
|
+ h_path.dentry = NULL; /* silence gcc */
|
|
+ di_write_lock_child(dentry);
|
|
+ err = au_h_path_to_set_attr(dentry, a, &h_path);
|
|
+ if (unlikely(err))
|
|
+ goto out_di;
|
|
+
|
|
+ inode_unlock(a->h_inode);
|
|
+ switch (arg->type) {
|
|
+ case AU_XATTR_SET:
|
|
+ AuDebugOn(d_is_negative(h_path.dentry));
|
|
+ err = vfsub_setxattr(h_path.dentry,
|
|
+ arg->u.set.name, arg->u.set.value,
|
|
+ arg->u.set.size, arg->u.set.flags);
|
|
+ break;
|
|
+ case AU_ACL_SET:
|
|
+ err = -EOPNOTSUPP;
|
|
+ h_inode = d_inode(h_path.dentry);
|
|
+ if (h_inode->i_op->set_acl)
|
|
+ /* this will call posix_acl_update_mode */
|
|
+ err = h_inode->i_op->set_acl(h_inode,
|
|
+ arg->u.acl_set.acl,
|
|
+ arg->u.acl_set.type);
|
|
+ break;
|
|
+ }
|
|
+ if (!err)
|
|
+ au_cpup_attr_timesizes(inode);
|
|
+
|
|
+ au_unpin(&a->pin);
|
|
+ if (unlikely(err))
|
|
+ au_update_dbtop(dentry);
|
|
+
|
|
+out_di:
|
|
+ di_write_unlock(dentry);
|
|
+ si_read_unlock(sb);
|
|
+out_kfree:
|
|
+ au_kfree_rcu(a);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void au_refresh_iattr(struct inode *inode, struct kstat *st,
|
|
+ unsigned int nlink)
|
|
+{
|
|
+ unsigned int n;
|
|
+
|
|
+ inode->i_mode = st->mode;
|
|
+ /* don't i_[ug]id_write() here */
|
|
+ inode->i_uid = st->uid;
|
|
+ inode->i_gid = st->gid;
|
|
+ inode->i_atime = st->atime;
|
|
+ inode->i_mtime = st->mtime;
|
|
+ inode->i_ctime = st->ctime;
|
|
+
|
|
+ au_cpup_attr_nlink(inode, /*force*/0);
|
|
+ if (S_ISDIR(inode->i_mode)) {
|
|
+ n = inode->i_nlink;
|
|
+ n -= nlink;
|
|
+ n += st->nlink;
|
|
+ smp_mb(); /* for i_nlink */
|
|
+ /* 0 can happen */
|
|
+ set_nlink(inode, n);
|
|
+ }
|
|
+
|
|
+ spin_lock(&inode->i_lock);
|
|
+ inode->i_blocks = st->blocks;
|
|
+ i_size_write(inode, st->size);
|
|
+ spin_unlock(&inode->i_lock);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * common routine for aufs_getattr() and au_getxattr().
|
|
+ * returns zero or negative (an error).
|
|
+ * @dentry will be read-locked in success.
|
|
+ */
|
|
+int au_h_path_getattr(struct dentry *dentry, struct inode *inode, int force,
|
|
+ struct path *h_path, int locked)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int mnt_flags, sigen;
|
|
+ unsigned char udba_none;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct super_block *sb, *h_sb;
|
|
+
|
|
+ h_path->mnt = NULL;
|
|
+ h_path->dentry = NULL;
|
|
+
|
|
+ err = 0;
|
|
+ sb = dentry->d_sb;
|
|
+ mnt_flags = au_mntflags(sb);
|
|
+ udba_none = !!au_opt_test(mnt_flags, UDBA_NONE);
|
|
+
|
|
+ if (unlikely(locked))
|
|
+ goto body; /* skip locking dinfo */
|
|
+
|
|
+ /* support fstat(2) */
|
|
+ if (!d_unlinked(dentry) && !udba_none) {
|
|
+ sigen = au_sigen(sb);
|
|
+ err = au_digen_test(dentry, sigen);
|
|
+ if (!err) {
|
|
+ di_read_lock_child(dentry, AuLock_IR);
|
|
+ err = au_dbrange_test(dentry);
|
|
+ if (unlikely(err)) {
|
|
+ di_read_unlock(dentry, AuLock_IR);
|
|
+ goto out;
|
|
+ }
|
|
+ } else {
|
|
+ AuDebugOn(IS_ROOT(dentry));
|
|
+ di_write_lock_child(dentry);
|
|
+ err = au_dbrange_test(dentry);
|
|
+ if (!err)
|
|
+ err = au_reval_for_attr(dentry, sigen);
|
|
+ if (!err)
|
|
+ di_downgrade_lock(dentry, AuLock_IR);
|
|
+ else {
|
|
+ di_write_unlock(dentry);
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+ } else
|
|
+ di_read_lock_child(dentry, AuLock_IR);
|
|
+
|
|
+body:
|
|
+ if (!inode) {
|
|
+ inode = d_inode(dentry);
|
|
+ if (unlikely(!inode))
|
|
+ goto out;
|
|
+ }
|
|
+ bindex = au_ibtop(inode);
|
|
+ h_path->mnt = au_sbr_mnt(sb, bindex);
|
|
+ h_sb = h_path->mnt->mnt_sb;
|
|
+ if (!force
|
|
+ && !au_test_fs_bad_iattr(h_sb)
|
|
+ && udba_none)
|
|
+ goto out; /* success */
|
|
+
|
|
+ if (au_dbtop(dentry) == bindex)
|
|
+ h_path->dentry = au_h_dptr(dentry, bindex);
|
|
+ else if (au_opt_test(mnt_flags, PLINK) && au_plink_test(inode)) {
|
|
+ h_path->dentry = au_plink_lkup(inode, bindex);
|
|
+ if (IS_ERR(h_path->dentry))
|
|
+ /* pretending success */
|
|
+ h_path->dentry = NULL;
|
|
+ else
|
|
+ dput(h_path->dentry);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_getattr(const struct path *path, struct kstat *st,
|
|
+ u32 request, unsigned int query)
|
|
+{
|
|
+ int err;
|
|
+ unsigned char positive;
|
|
+ struct path h_path;
|
|
+ struct dentry *dentry;
|
|
+ struct inode *inode;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ dentry = path->dentry;
|
|
+ inode = d_inode(dentry);
|
|
+ sb = dentry->d_sb;
|
|
+ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = au_h_path_getattr(dentry, /*inode*/NULL, /*force*/0, &h_path,
|
|
+ /*locked*/0);
|
|
+ if (unlikely(err))
|
|
+ goto out_si;
|
|
+ if (unlikely(!h_path.dentry))
|
|
+ /* illegally overlapped or something */
|
|
+ goto out_fill; /* pretending success */
|
|
+
|
|
+ positive = d_is_positive(h_path.dentry);
|
|
+ if (positive)
|
|
+ /* no vfsub version */
|
|
+ err = vfs_getattr(&h_path, st, request, query);
|
|
+ if (!err) {
|
|
+ if (positive)
|
|
+ au_refresh_iattr(inode, st,
|
|
+ d_inode(h_path.dentry)->i_nlink);
|
|
+ goto out_fill; /* success */
|
|
+ }
|
|
+ AuTraceErr(err);
|
|
+ goto out_di;
|
|
+
|
|
+out_fill:
|
|
+ generic_fillattr(inode, st);
|
|
+out_di:
|
|
+ di_read_unlock(dentry, AuLock_IR);
|
|
+out_si:
|
|
+ si_read_unlock(sb);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static const char *aufs_get_link(struct dentry *dentry, struct inode *inode,
|
|
+ struct delayed_call *done)
|
|
+{
|
|
+ const char *ret;
|
|
+ struct dentry *h_dentry;
|
|
+ struct inode *h_inode;
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+
|
|
+ ret = NULL; /* suppress a warning */
|
|
+ err = -ECHILD;
|
|
+ if (!dentry)
|
|
+ goto out;
|
|
+
|
|
+ err = aufs_read_lock(dentry, AuLock_IR | AuLock_GEN);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ err = au_d_hashed_positive(dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ inode = d_inode(dentry);
|
|
+ bindex = au_ibtop(inode);
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+ if (unlikely(!h_inode->i_op->get_link))
|
|
+ goto out_unlock;
|
|
+
|
|
+ err = -EBUSY;
|
|
+ h_dentry = NULL;
|
|
+ if (au_dbtop(dentry) <= bindex) {
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (h_dentry)
|
|
+ dget(h_dentry);
|
|
+ }
|
|
+ if (!h_dentry) {
|
|
+ h_dentry = d_find_any_alias(h_inode);
|
|
+ if (IS_ERR(h_dentry)) {
|
|
+ err = PTR_ERR(h_dentry);
|
|
+ goto out_unlock;
|
|
+ }
|
|
+ }
|
|
+ if (unlikely(!h_dentry))
|
|
+ goto out_unlock;
|
|
+
|
|
+ err = 0;
|
|
+ AuDbg("%ps\n", h_inode->i_op->get_link);
|
|
+ AuDbgDentry(h_dentry);
|
|
+ ret = vfs_get_link(h_dentry, done);
|
|
+ dput(h_dentry);
|
|
+ if (IS_ERR(ret))
|
|
+ err = PTR_ERR(ret);
|
|
+
|
|
+out_unlock:
|
|
+ aufs_read_unlock(dentry, AuLock_IR);
|
|
+out:
|
|
+ if (unlikely(err))
|
|
+ ret = ERR_PTR(err);
|
|
+ AuTraceErrPtr(ret);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_is_special(struct inode *inode)
|
|
+{
|
|
+ return (inode->i_mode & (S_IFBLK | S_IFCHR | S_IFIFO | S_IFSOCK));
|
|
+}
|
|
+
|
|
+static int aufs_update_time(struct inode *inode, struct timespec64 *ts,
|
|
+ int flags)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct super_block *sb;
|
|
+ struct inode *h_inode;
|
|
+ struct vfsmount *h_mnt;
|
|
+
|
|
+ sb = inode->i_sb;
|
|
+ WARN_ONCE((flags & S_ATIME) && !IS_NOATIME(inode),
|
|
+ "unexpected s_flags 0x%lx", sb->s_flags);
|
|
+
|
|
+ /* mmap_sem might be acquired already, cf. aufs_mmap() */
|
|
+ lockdep_off();
|
|
+ si_read_lock(sb, AuLock_FLUSH);
|
|
+ ii_write_lock_child(inode);
|
|
+
|
|
+ err = 0;
|
|
+ bindex = au_ibtop(inode);
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+ if (!au_test_ro(sb, bindex, inode)) {
|
|
+ h_mnt = au_sbr_mnt(sb, bindex);
|
|
+ err = vfsub_mnt_want_write(h_mnt);
|
|
+ if (!err) {
|
|
+ err = vfsub_update_time(h_inode, ts, flags);
|
|
+ vfsub_mnt_drop_write(h_mnt);
|
|
+ }
|
|
+ } else if (au_is_special(h_inode)) {
|
|
+ /*
|
|
+ * Never copy-up here.
|
|
+ * These special files may already be opened and used for
|
|
+ * communicating. If we copied it up, then the communication
|
|
+ * would be corrupted.
|
|
+ */
|
|
+ AuWarn1("timestamps for i%lu are ignored "
|
|
+ "since it is on readonly branch (hi%lu).\n",
|
|
+ inode->i_ino, h_inode->i_ino);
|
|
+ } else if (flags & ~S_ATIME) {
|
|
+ err = -EIO;
|
|
+ AuIOErr1("unexpected flags 0x%x\n", flags);
|
|
+ AuDebugOn(1);
|
|
+ }
|
|
+
|
|
+ if (!err)
|
|
+ au_cpup_attr_timesizes(inode);
|
|
+ ii_write_unlock(inode);
|
|
+ si_read_unlock(sb);
|
|
+ lockdep_on();
|
|
+
|
|
+ if (!err && (flags & S_VERSION))
|
|
+ inode_inc_iversion(inode);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* no getattr version will be set by module.c:aufs_init() */
|
|
+struct inode_operations aufs_iop_nogetattr[AuIop_Last],
|
|
+ aufs_iop[] = {
|
|
+ [AuIop_SYMLINK] = {
|
|
+ .permission = aufs_permission,
|
|
+#ifdef CONFIG_FS_POSIX_ACL
|
|
+ .get_acl = aufs_get_acl,
|
|
+ .set_acl = aufs_set_acl, /* unsupport for symlink? */
|
|
+#endif
|
|
+
|
|
+ .setattr = aufs_setattr,
|
|
+ .getattr = aufs_getattr,
|
|
+
|
|
+#ifdef CONFIG_AUFS_XATTR
|
|
+ .listxattr = aufs_listxattr,
|
|
+#endif
|
|
+
|
|
+ .get_link = aufs_get_link,
|
|
+
|
|
+ /* .update_time = aufs_update_time */
|
|
+ },
|
|
+ [AuIop_DIR] = {
|
|
+ .create = aufs_create,
|
|
+ .lookup = aufs_lookup,
|
|
+ .link = aufs_link,
|
|
+ .unlink = aufs_unlink,
|
|
+ .symlink = aufs_symlink,
|
|
+ .mkdir = aufs_mkdir,
|
|
+ .rmdir = aufs_rmdir,
|
|
+ .mknod = aufs_mknod,
|
|
+ .rename = aufs_rename,
|
|
+
|
|
+ .permission = aufs_permission,
|
|
+#ifdef CONFIG_FS_POSIX_ACL
|
|
+ .get_acl = aufs_get_acl,
|
|
+ .set_acl = aufs_set_acl,
|
|
+#endif
|
|
+
|
|
+ .setattr = aufs_setattr,
|
|
+ .getattr = aufs_getattr,
|
|
+
|
|
+#ifdef CONFIG_AUFS_XATTR
|
|
+ .listxattr = aufs_listxattr,
|
|
+#endif
|
|
+
|
|
+ .update_time = aufs_update_time,
|
|
+ .atomic_open = aufs_atomic_open,
|
|
+ .tmpfile = aufs_tmpfile
|
|
+ },
|
|
+ [AuIop_OTHER] = {
|
|
+ .permission = aufs_permission,
|
|
+#ifdef CONFIG_FS_POSIX_ACL
|
|
+ .get_acl = aufs_get_acl,
|
|
+ .set_acl = aufs_set_acl,
|
|
+#endif
|
|
+
|
|
+ .setattr = aufs_setattr,
|
|
+ .getattr = aufs_getattr,
|
|
+
|
|
+#ifdef CONFIG_AUFS_XATTR
|
|
+ .listxattr = aufs_listxattr,
|
|
+#endif
|
|
+
|
|
+ .update_time = aufs_update_time
|
|
+ }
|
|
+};
|
|
diff --git a/fs/aufs/i_op_add.c b/fs/aufs/i_op_add.c
|
|
new file mode 100644
|
|
index 000000000000..80f2cfeeaaa0
|
|
--- /dev/null
|
|
+++ b/fs/aufs/i_op_add.c
|
|
@@ -0,0 +1,936 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * inode operations (add entry)
|
|
+ */
|
|
+
|
|
+#include <linux/iversion.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/*
|
|
+ * final procedure of adding a new entry, except link(2).
|
|
+ * remove whiteout, instantiate, copyup the parent dir's times and size
|
|
+ * and update version.
|
|
+ * if it failed, re-create the removed whiteout.
|
|
+ */
|
|
+static int epilog(struct inode *dir, aufs_bindex_t bindex,
|
|
+ struct dentry *wh_dentry, struct dentry *dentry)
|
|
+{
|
|
+ int err, rerr;
|
|
+ aufs_bindex_t bwh;
|
|
+ struct path h_path;
|
|
+ struct super_block *sb;
|
|
+ struct inode *inode, *h_dir;
|
|
+ struct dentry *wh;
|
|
+
|
|
+ bwh = -1;
|
|
+ sb = dir->i_sb;
|
|
+ if (wh_dentry) {
|
|
+ h_dir = d_inode(wh_dentry->d_parent); /* dir inode is locked */
|
|
+ IMustLock(h_dir);
|
|
+ AuDebugOn(au_h_iptr(dir, bindex) != h_dir);
|
|
+ bwh = au_dbwh(dentry);
|
|
+ h_path.dentry = wh_dentry;
|
|
+ h_path.mnt = au_sbr_mnt(sb, bindex);
|
|
+ err = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path,
|
|
+ dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ inode = au_new_inode(dentry, /*must_new*/1);
|
|
+ if (!IS_ERR(inode)) {
|
|
+ d_instantiate(dentry, inode);
|
|
+ dir = d_inode(dentry->d_parent); /* dir inode is locked */
|
|
+ IMustLock(dir);
|
|
+ au_dir_ts(dir, bindex);
|
|
+ inode_inc_iversion(dir);
|
|
+ au_fhsm_wrote(sb, bindex, /*force*/0);
|
|
+ return 0; /* success */
|
|
+ }
|
|
+
|
|
+ err = PTR_ERR(inode);
|
|
+ if (!wh_dentry)
|
|
+ goto out;
|
|
+
|
|
+ /* revert */
|
|
+ /* dir inode is locked */
|
|
+ wh = au_wh_create(dentry, bwh, wh_dentry->d_parent);
|
|
+ rerr = PTR_ERR(wh);
|
|
+ if (IS_ERR(wh)) {
|
|
+ AuIOErr("%pd reverting whiteout failed(%d, %d)\n",
|
|
+ dentry, err, rerr);
|
|
+ err = -EIO;
|
|
+ } else
|
|
+ dput(wh);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_d_may_add(struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (unlikely(d_unhashed(dentry)))
|
|
+ err = -ENOENT;
|
|
+ if (unlikely(d_really_is_positive(dentry)))
|
|
+ err = -EEXIST;
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * simple tests for the adding inode operations.
|
|
+ * following the checks in vfs, plus the parent-child relationship.
|
|
+ */
|
|
+int au_may_add(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct dentry *h_parent, int isdir)
|
|
+{
|
|
+ int err;
|
|
+ umode_t h_mode;
|
|
+ struct dentry *h_dentry;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ err = -ENAMETOOLONG;
|
|
+ if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN))
|
|
+ goto out;
|
|
+
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (d_really_is_negative(dentry)) {
|
|
+ err = -EEXIST;
|
|
+ if (unlikely(d_is_positive(h_dentry)))
|
|
+ goto out;
|
|
+ } else {
|
|
+ /* rename(2) case */
|
|
+ err = -EIO;
|
|
+ if (unlikely(d_is_negative(h_dentry)))
|
|
+ goto out;
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ if (unlikely(!h_inode->i_nlink))
|
|
+ goto out;
|
|
+
|
|
+ h_mode = h_inode->i_mode;
|
|
+ if (!isdir) {
|
|
+ err = -EISDIR;
|
|
+ if (unlikely(S_ISDIR(h_mode)))
|
|
+ goto out;
|
|
+ } else if (unlikely(!S_ISDIR(h_mode))) {
|
|
+ err = -ENOTDIR;
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ /* expected parent dir is locked */
|
|
+ if (unlikely(h_parent != h_dentry->d_parent))
|
|
+ err = -EIO;
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * initial procedure of adding a new entry.
|
|
+ * prepare writable branch and the parent dir, lock it,
|
|
+ * and lookup whiteout for the new entry.
|
|
+ */
|
|
+static struct dentry*
|
|
+lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt,
|
|
+ struct dentry *src_dentry, struct au_pin *pin,
|
|
+ struct au_wr_dir_args *wr_dir_args)
|
|
+{
|
|
+ struct dentry *wh_dentry, *h_parent;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ int err;
|
|
+ unsigned int udba;
|
|
+ aufs_bindex_t bcpup;
|
|
+
|
|
+ AuDbg("%pd\n", dentry);
|
|
+
|
|
+ err = au_wr_dir(dentry, src_dentry, wr_dir_args);
|
|
+ bcpup = err;
|
|
+ wh_dentry = ERR_PTR(err);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ udba = au_opt_udba(sb);
|
|
+ err = au_pin(pin, dentry, bcpup, udba,
|
|
+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
|
|
+ wh_dentry = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ h_parent = au_pinned_h_parent(pin);
|
|
+ if (udba != AuOpt_UDBA_NONE
|
|
+ && au_dbtop(dentry) == bcpup)
|
|
+ err = au_may_add(dentry, bcpup, h_parent,
|
|
+ au_ftest_wrdir(wr_dir_args->flags, ISDIR));
|
|
+ else if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN))
|
|
+ err = -ENAMETOOLONG;
|
|
+ wh_dentry = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out_unpin;
|
|
+
|
|
+ br = au_sbr(sb, bcpup);
|
|
+ if (dt) {
|
|
+ struct path tmp = {
|
|
+ .dentry = h_parent,
|
|
+ .mnt = au_br_mnt(br)
|
|
+ };
|
|
+ au_dtime_store(dt, au_pinned_parent(pin), &tmp);
|
|
+ }
|
|
+
|
|
+ wh_dentry = NULL;
|
|
+ if (bcpup != au_dbwh(dentry))
|
|
+ goto out; /* success */
|
|
+
|
|
+ /*
|
|
+ * ENAMETOOLONG here means that if we allowed create such name, then it
|
|
+ * would not be able to removed in the future. So we don't allow such
|
|
+ * name here and we don't handle ENAMETOOLONG differently here.
|
|
+ */
|
|
+ wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, br);
|
|
+
|
|
+out_unpin:
|
|
+ if (IS_ERR(wh_dentry))
|
|
+ au_unpin(pin);
|
|
+out:
|
|
+ return wh_dentry;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+enum { Mknod, Symlink, Creat };
|
|
+struct simple_arg {
|
|
+ int type;
|
|
+ union {
|
|
+ struct {
|
|
+ umode_t mode;
|
|
+ bool want_excl;
|
|
+ bool try_aopen;
|
|
+ struct vfsub_aopen_args *aopen;
|
|
+ } c;
|
|
+ struct {
|
|
+ const char *symname;
|
|
+ } s;
|
|
+ struct {
|
|
+ umode_t mode;
|
|
+ dev_t dev;
|
|
+ } m;
|
|
+ } u;
|
|
+};
|
|
+
|
|
+static int add_simple(struct inode *dir, struct dentry *dentry,
|
|
+ struct simple_arg *arg)
|
|
+{
|
|
+ int err, rerr;
|
|
+ aufs_bindex_t btop;
|
|
+ unsigned char created;
|
|
+ const unsigned char try_aopen
|
|
+ = (arg->type == Creat && arg->u.c.try_aopen);
|
|
+ struct vfsub_aopen_args *aopen = arg->u.c.aopen;
|
|
+ struct dentry *wh_dentry, *parent;
|
|
+ struct inode *h_dir;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ /* to reduce stack size */
|
|
+ struct {
|
|
+ struct au_dtime dt;
|
|
+ struct au_pin pin;
|
|
+ struct path h_path;
|
|
+ struct au_wr_dir_args wr_dir_args;
|
|
+ } *a;
|
|
+
|
|
+ AuDbg("%pd\n", dentry);
|
|
+ IMustLock(dir);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ a = kmalloc(sizeof(*a), GFP_NOFS);
|
|
+ if (unlikely(!a))
|
|
+ goto out;
|
|
+ a->wr_dir_args.force_btgt = -1;
|
|
+ a->wr_dir_args.flags = AuWrDir_ADD_ENTRY;
|
|
+
|
|
+ parent = dentry->d_parent; /* dir inode is locked */
|
|
+ if (!try_aopen) {
|
|
+ err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN);
|
|
+ if (unlikely(err))
|
|
+ goto out_free;
|
|
+ }
|
|
+ err = au_d_may_add(dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+ if (!try_aopen)
|
|
+ di_write_lock_parent(parent);
|
|
+ wh_dentry = lock_hdir_lkup_wh(dentry, &a->dt, /*src_dentry*/NULL,
|
|
+ &a->pin, &a->wr_dir_args);
|
|
+ err = PTR_ERR(wh_dentry);
|
|
+ if (IS_ERR(wh_dentry))
|
|
+ goto out_parent;
|
|
+
|
|
+ btop = au_dbtop(dentry);
|
|
+ sb = dentry->d_sb;
|
|
+ br = au_sbr(sb, btop);
|
|
+ a->h_path.dentry = au_h_dptr(dentry, btop);
|
|
+ a->h_path.mnt = au_br_mnt(br);
|
|
+ h_dir = au_pinned_h_dir(&a->pin);
|
|
+ switch (arg->type) {
|
|
+ case Creat:
|
|
+ if (!try_aopen || !h_dir->i_op->atomic_open) {
|
|
+ err = vfsub_create(h_dir, &a->h_path, arg->u.c.mode,
|
|
+ arg->u.c.want_excl);
|
|
+ created = !err;
|
|
+ if (!err && try_aopen)
|
|
+ aopen->file->f_mode |= FMODE_CREATED;
|
|
+ } else {
|
|
+ aopen->br = br;
|
|
+ err = vfsub_atomic_open(h_dir, a->h_path.dentry, aopen);
|
|
+ AuDbg("err %d\n", err);
|
|
+ AuDbgFile(aopen->file);
|
|
+ created = err >= 0
|
|
+ && !!(aopen->file->f_mode & FMODE_CREATED);
|
|
+ }
|
|
+ break;
|
|
+ case Symlink:
|
|
+ err = vfsub_symlink(h_dir, &a->h_path, arg->u.s.symname);
|
|
+ created = !err;
|
|
+ break;
|
|
+ case Mknod:
|
|
+ err = vfsub_mknod(h_dir, &a->h_path, arg->u.m.mode,
|
|
+ arg->u.m.dev);
|
|
+ created = !err;
|
|
+ break;
|
|
+ default:
|
|
+ BUG();
|
|
+ }
|
|
+ if (unlikely(err < 0))
|
|
+ goto out_unpin;
|
|
+
|
|
+ err = epilog(dir, btop, wh_dentry, dentry);
|
|
+ if (!err)
|
|
+ goto out_unpin; /* success */
|
|
+
|
|
+ /* revert */
|
|
+ if (created /* && d_is_positive(a->h_path.dentry) */) {
|
|
+ /* no delegation since it is just created */
|
|
+ rerr = vfsub_unlink(h_dir, &a->h_path, /*delegated*/NULL,
|
|
+ /*force*/0);
|
|
+ if (rerr) {
|
|
+ AuIOErr("%pd revert failure(%d, %d)\n",
|
|
+ dentry, err, rerr);
|
|
+ err = -EIO;
|
|
+ }
|
|
+ au_dtime_revert(&a->dt);
|
|
+ }
|
|
+ if (try_aopen && h_dir->i_op->atomic_open
|
|
+ && (aopen->file->f_mode & FMODE_OPENED))
|
|
+ /* aopen->file is still opened */
|
|
+ au_lcnt_dec(&aopen->br->br_nfiles);
|
|
+
|
|
+out_unpin:
|
|
+ au_unpin(&a->pin);
|
|
+ dput(wh_dentry);
|
|
+out_parent:
|
|
+ if (!try_aopen)
|
|
+ di_write_unlock(parent);
|
|
+out_unlock:
|
|
+ if (unlikely(err)) {
|
|
+ au_update_dbtop(dentry);
|
|
+ d_drop(dentry);
|
|
+ }
|
|
+ if (!try_aopen)
|
|
+ aufs_read_unlock(dentry, AuLock_DW);
|
|
+out_free:
|
|
+ au_kfree_rcu(a);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int aufs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
|
|
+ dev_t dev)
|
|
+{
|
|
+ struct simple_arg arg = {
|
|
+ .type = Mknod,
|
|
+ .u.m = {
|
|
+ .mode = mode,
|
|
+ .dev = dev
|
|
+ }
|
|
+ };
|
|
+ return add_simple(dir, dentry, &arg);
|
|
+}
|
|
+
|
|
+int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
|
|
+{
|
|
+ struct simple_arg arg = {
|
|
+ .type = Symlink,
|
|
+ .u.s.symname = symname
|
|
+ };
|
|
+ return add_simple(dir, dentry, &arg);
|
|
+}
|
|
+
|
|
+int aufs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
|
|
+ bool want_excl)
|
|
+{
|
|
+ struct simple_arg arg = {
|
|
+ .type = Creat,
|
|
+ .u.c = {
|
|
+ .mode = mode,
|
|
+ .want_excl = want_excl
|
|
+ }
|
|
+ };
|
|
+ return add_simple(dir, dentry, &arg);
|
|
+}
|
|
+
|
|
+int au_aopen_or_create(struct inode *dir, struct dentry *dentry,
|
|
+ struct vfsub_aopen_args *aopen_args)
|
|
+{
|
|
+ struct simple_arg arg = {
|
|
+ .type = Creat,
|
|
+ .u.c = {
|
|
+ .mode = aopen_args->create_mode,
|
|
+ .want_excl = aopen_args->open_flag & O_EXCL,
|
|
+ .try_aopen = true,
|
|
+ .aopen = aopen_args
|
|
+ }
|
|
+ };
|
|
+ return add_simple(dir, dentry, &arg);
|
|
+}
|
|
+
|
|
+int aufs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct super_block *sb;
|
|
+ struct dentry *parent, *h_parent, *h_dentry;
|
|
+ struct inode *h_dir, *inode;
|
|
+ struct vfsmount *h_mnt;
|
|
+ struct au_wr_dir_args wr_dir_args = {
|
|
+ .force_btgt = -1,
|
|
+ .flags = AuWrDir_TMPFILE
|
|
+ };
|
|
+
|
|
+ /* copy-up may happen */
|
|
+ inode_lock(dir);
|
|
+
|
|
+ sb = dir->i_sb;
|
|
+ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ err = au_di_init(dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_si;
|
|
+
|
|
+ err = -EBUSY;
|
|
+ parent = d_find_any_alias(dir);
|
|
+ AuDebugOn(!parent);
|
|
+ di_write_lock_parent(parent);
|
|
+ if (unlikely(d_inode(parent) != dir))
|
|
+ goto out_parent;
|
|
+
|
|
+ err = au_digen_test(parent, au_sigen(sb));
|
|
+ if (unlikely(err))
|
|
+ goto out_parent;
|
|
+
|
|
+ bindex = au_dbtop(parent);
|
|
+ au_set_dbtop(dentry, bindex);
|
|
+ au_set_dbbot(dentry, bindex);
|
|
+ err = au_wr_dir(dentry, /*src_dentry*/NULL, &wr_dir_args);
|
|
+ bindex = err;
|
|
+ if (unlikely(err < 0))
|
|
+ goto out_parent;
|
|
+
|
|
+ err = -EOPNOTSUPP;
|
|
+ h_dir = au_h_iptr(dir, bindex);
|
|
+ if (unlikely(!h_dir->i_op->tmpfile))
|
|
+ goto out_parent;
|
|
+
|
|
+ h_mnt = au_sbr_mnt(sb, bindex);
|
|
+ err = vfsub_mnt_want_write(h_mnt);
|
|
+ if (unlikely(err))
|
|
+ goto out_parent;
|
|
+
|
|
+ h_parent = au_h_dptr(parent, bindex);
|
|
+ h_dentry = vfs_tmpfile(h_parent, mode, /*open_flag*/0);
|
|
+ if (IS_ERR(h_dentry)) {
|
|
+ err = PTR_ERR(h_dentry);
|
|
+ goto out_mnt;
|
|
+ }
|
|
+
|
|
+ au_set_dbtop(dentry, bindex);
|
|
+ au_set_dbbot(dentry, bindex);
|
|
+ au_set_h_dptr(dentry, bindex, dget(h_dentry));
|
|
+ inode = au_new_inode(dentry, /*must_new*/1);
|
|
+ if (IS_ERR(inode)) {
|
|
+ err = PTR_ERR(inode);
|
|
+ au_set_h_dptr(dentry, bindex, NULL);
|
|
+ au_set_dbtop(dentry, -1);
|
|
+ au_set_dbbot(dentry, -1);
|
|
+ } else {
|
|
+ if (!inode->i_nlink)
|
|
+ set_nlink(inode, 1);
|
|
+ d_tmpfile(dentry, inode);
|
|
+ au_di(dentry)->di_tmpfile = 1;
|
|
+
|
|
+ /* update without i_mutex */
|
|
+ if (au_ibtop(dir) == au_dbtop(dentry))
|
|
+ au_cpup_attr_timesizes(dir);
|
|
+ }
|
|
+ dput(h_dentry);
|
|
+
|
|
+out_mnt:
|
|
+ vfsub_mnt_drop_write(h_mnt);
|
|
+out_parent:
|
|
+ di_write_unlock(parent);
|
|
+ dput(parent);
|
|
+ di_write_unlock(dentry);
|
|
+ if (unlikely(err)) {
|
|
+ au_di_fin(dentry);
|
|
+ dentry->d_fsdata = NULL;
|
|
+ }
|
|
+out_si:
|
|
+ si_read_unlock(sb);
|
|
+out:
|
|
+ inode_unlock(dir);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_link_args {
|
|
+ aufs_bindex_t bdst, bsrc;
|
|
+ struct au_pin pin;
|
|
+ struct path h_path;
|
|
+ struct dentry *src_parent, *parent;
|
|
+};
|
|
+
|
|
+static int au_cpup_before_link(struct dentry *src_dentry,
|
|
+ struct au_link_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *h_src_dentry;
|
|
+ struct au_cp_generic cpg = {
|
|
+ .dentry = src_dentry,
|
|
+ .bdst = a->bdst,
|
|
+ .bsrc = a->bsrc,
|
|
+ .len = -1,
|
|
+ .pin = &a->pin,
|
|
+ .flags = AuCpup_DTIME | AuCpup_HOPEN /* | AuCpup_KEEPLINO */
|
|
+ };
|
|
+
|
|
+ di_read_lock_parent(a->src_parent, AuLock_IR);
|
|
+ err = au_test_and_cpup_dirs(src_dentry, a->bdst);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ h_src_dentry = au_h_dptr(src_dentry, a->bsrc);
|
|
+ err = au_pin(&a->pin, src_dentry, a->bdst,
|
|
+ au_opt_udba(src_dentry->d_sb),
|
|
+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ err = au_sio_cpup_simple(&cpg);
|
|
+ au_unpin(&a->pin);
|
|
+
|
|
+out:
|
|
+ di_read_unlock(a->src_parent, AuLock_IR);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_cpup_or_link(struct dentry *src_dentry, struct dentry *dentry,
|
|
+ struct au_link_args *a)
|
|
+{
|
|
+ int err;
|
|
+ unsigned char plink;
|
|
+ aufs_bindex_t bbot;
|
|
+ struct dentry *h_src_dentry;
|
|
+ struct inode *h_inode, *inode, *delegated;
|
|
+ struct super_block *sb;
|
|
+ struct file *h_file;
|
|
+
|
|
+ plink = 0;
|
|
+ h_inode = NULL;
|
|
+ sb = src_dentry->d_sb;
|
|
+ inode = d_inode(src_dentry);
|
|
+ if (au_ibtop(inode) <= a->bdst)
|
|
+ h_inode = au_h_iptr(inode, a->bdst);
|
|
+ if (!h_inode || !h_inode->i_nlink) {
|
|
+ /* copyup src_dentry as the name of dentry. */
|
|
+ bbot = au_dbbot(dentry);
|
|
+ if (bbot < a->bsrc)
|
|
+ au_set_dbbot(dentry, a->bsrc);
|
|
+ au_set_h_dptr(dentry, a->bsrc,
|
|
+ dget(au_h_dptr(src_dentry, a->bsrc)));
|
|
+ dget(a->h_path.dentry);
|
|
+ au_set_h_dptr(dentry, a->bdst, NULL);
|
|
+ AuDbg("temporary d_inode...\n");
|
|
+ spin_lock(&dentry->d_lock);
|
|
+ dentry->d_inode = d_inode(src_dentry); /* tmp */
|
|
+ spin_unlock(&dentry->d_lock);
|
|
+ h_file = au_h_open_pre(dentry, a->bsrc, /*force_wr*/0);
|
|
+ if (IS_ERR(h_file))
|
|
+ err = PTR_ERR(h_file);
|
|
+ else {
|
|
+ struct au_cp_generic cpg = {
|
|
+ .dentry = dentry,
|
|
+ .bdst = a->bdst,
|
|
+ .bsrc = -1,
|
|
+ .len = -1,
|
|
+ .pin = &a->pin,
|
|
+ .flags = AuCpup_KEEPLINO
|
|
+ };
|
|
+ err = au_sio_cpup_simple(&cpg);
|
|
+ au_h_open_post(dentry, a->bsrc, h_file);
|
|
+ if (!err) {
|
|
+ dput(a->h_path.dentry);
|
|
+ a->h_path.dentry = au_h_dptr(dentry, a->bdst);
|
|
+ } else
|
|
+ au_set_h_dptr(dentry, a->bdst,
|
|
+ a->h_path.dentry);
|
|
+ }
|
|
+ spin_lock(&dentry->d_lock);
|
|
+ dentry->d_inode = NULL; /* restore */
|
|
+ spin_unlock(&dentry->d_lock);
|
|
+ AuDbg("temporary d_inode...done\n");
|
|
+ au_set_h_dptr(dentry, a->bsrc, NULL);
|
|
+ au_set_dbbot(dentry, bbot);
|
|
+ } else {
|
|
+ /* the inode of src_dentry already exists on a.bdst branch */
|
|
+ h_src_dentry = d_find_alias(h_inode);
|
|
+ if (!h_src_dentry && au_plink_test(inode)) {
|
|
+ plink = 1;
|
|
+ h_src_dentry = au_plink_lkup(inode, a->bdst);
|
|
+ err = PTR_ERR(h_src_dentry);
|
|
+ if (IS_ERR(h_src_dentry))
|
|
+ goto out;
|
|
+
|
|
+ if (unlikely(d_is_negative(h_src_dentry))) {
|
|
+ dput(h_src_dentry);
|
|
+ h_src_dentry = NULL;
|
|
+ }
|
|
+
|
|
+ }
|
|
+ if (h_src_dentry) {
|
|
+ delegated = NULL;
|
|
+ err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
|
|
+ &a->h_path, &delegated);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal link\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ dput(h_src_dentry);
|
|
+ } else {
|
|
+ AuIOErr("no dentry found for hi%lu on b%d\n",
|
|
+ h_inode->i_ino, a->bdst);
|
|
+ err = -EIO;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!err && !plink)
|
|
+ au_plink_append(inode, a->bdst, a->h_path.dentry);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int aufs_link(struct dentry *src_dentry, struct inode *dir,
|
|
+ struct dentry *dentry)
|
|
+{
|
|
+ int err, rerr;
|
|
+ struct au_dtime dt;
|
|
+ struct au_link_args *a;
|
|
+ struct dentry *wh_dentry, *h_src_dentry;
|
|
+ struct inode *inode, *delegated;
|
|
+ struct super_block *sb;
|
|
+ struct au_wr_dir_args wr_dir_args = {
|
|
+ /* .force_btgt = -1, */
|
|
+ .flags = AuWrDir_ADD_ENTRY
|
|
+ };
|
|
+
|
|
+ IMustLock(dir);
|
|
+ inode = d_inode(src_dentry);
|
|
+ IMustLock(inode);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ a = kzalloc(sizeof(*a), GFP_NOFS);
|
|
+ if (unlikely(!a))
|
|
+ goto out;
|
|
+
|
|
+ a->parent = dentry->d_parent; /* dir inode is locked */
|
|
+ err = aufs_read_and_write_lock2(dentry, src_dentry,
|
|
+ AuLock_NOPLM | AuLock_GEN);
|
|
+ if (unlikely(err))
|
|
+ goto out_kfree;
|
|
+ err = au_d_linkable(src_dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+ err = au_d_may_add(dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+
|
|
+ a->src_parent = dget_parent(src_dentry);
|
|
+ wr_dir_args.force_btgt = au_ibtop(inode);
|
|
+
|
|
+ di_write_lock_parent(a->parent);
|
|
+ wr_dir_args.force_btgt = au_wbr(dentry, wr_dir_args.force_btgt);
|
|
+ wh_dentry = lock_hdir_lkup_wh(dentry, &dt, src_dentry, &a->pin,
|
|
+ &wr_dir_args);
|
|
+ err = PTR_ERR(wh_dentry);
|
|
+ if (IS_ERR(wh_dentry))
|
|
+ goto out_parent;
|
|
+
|
|
+ err = 0;
|
|
+ sb = dentry->d_sb;
|
|
+ a->bdst = au_dbtop(dentry);
|
|
+ a->h_path.dentry = au_h_dptr(dentry, a->bdst);
|
|
+ a->h_path.mnt = au_sbr_mnt(sb, a->bdst);
|
|
+ a->bsrc = au_ibtop(inode);
|
|
+ h_src_dentry = au_h_d_alias(src_dentry, a->bsrc);
|
|
+ if (!h_src_dentry && au_di(src_dentry)->di_tmpfile)
|
|
+ h_src_dentry = dget(au_hi_wh(inode, a->bsrc));
|
|
+ if (!h_src_dentry) {
|
|
+ a->bsrc = au_dbtop(src_dentry);
|
|
+ h_src_dentry = au_h_d_alias(src_dentry, a->bsrc);
|
|
+ AuDebugOn(!h_src_dentry);
|
|
+ } else if (IS_ERR(h_src_dentry)) {
|
|
+ err = PTR_ERR(h_src_dentry);
|
|
+ goto out_parent;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * aufs doesn't touch the credential so
|
|
+ * security_dentry_create_files_as() is unnecessary.
|
|
+ */
|
|
+ if (au_opt_test(au_mntflags(sb), PLINK)) {
|
|
+ if (a->bdst < a->bsrc
|
|
+ /* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */)
|
|
+ err = au_cpup_or_link(src_dentry, dentry, a);
|
|
+ else {
|
|
+ delegated = NULL;
|
|
+ err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
|
|
+ &a->h_path, &delegated);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal link\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ }
|
|
+ dput(h_src_dentry);
|
|
+ } else {
|
|
+ /*
|
|
+ * copyup src_dentry to the branch we process,
|
|
+ * and then link(2) to it.
|
|
+ */
|
|
+ dput(h_src_dentry);
|
|
+ if (a->bdst < a->bsrc
|
|
+ /* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */) {
|
|
+ au_unpin(&a->pin);
|
|
+ di_write_unlock(a->parent);
|
|
+ err = au_cpup_before_link(src_dentry, a);
|
|
+ di_write_lock_parent(a->parent);
|
|
+ if (!err)
|
|
+ err = au_pin(&a->pin, dentry, a->bdst,
|
|
+ au_opt_udba(sb),
|
|
+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
|
|
+ if (unlikely(err))
|
|
+ goto out_wh;
|
|
+ }
|
|
+ if (!err) {
|
|
+ h_src_dentry = au_h_dptr(src_dentry, a->bdst);
|
|
+ err = -ENOENT;
|
|
+ if (h_src_dentry && d_is_positive(h_src_dentry)) {
|
|
+ delegated = NULL;
|
|
+ err = vfsub_link(h_src_dentry,
|
|
+ au_pinned_h_dir(&a->pin),
|
|
+ &a->h_path, &delegated);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry"
|
|
+ " for NFSv4 delegation"
|
|
+ " for an internal link\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ goto out_unpin;
|
|
+
|
|
+ if (wh_dentry) {
|
|
+ a->h_path.dentry = wh_dentry;
|
|
+ err = au_wh_unlink_dentry(au_pinned_h_dir(&a->pin), &a->h_path,
|
|
+ dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_revert;
|
|
+ }
|
|
+
|
|
+ au_dir_ts(dir, a->bdst);
|
|
+ inode_inc_iversion(dir);
|
|
+ inc_nlink(inode);
|
|
+ inode->i_ctime = dir->i_ctime;
|
|
+ d_instantiate(dentry, au_igrab(inode));
|
|
+ if (d_unhashed(a->h_path.dentry))
|
|
+ /* some filesystem calls d_drop() */
|
|
+ d_drop(dentry);
|
|
+ /* some filesystems consume an inode even hardlink */
|
|
+ au_fhsm_wrote(sb, a->bdst, /*force*/0);
|
|
+ goto out_unpin; /* success */
|
|
+
|
|
+out_revert:
|
|
+ /* no delegation since it is just created */
|
|
+ rerr = vfsub_unlink(au_pinned_h_dir(&a->pin), &a->h_path,
|
|
+ /*delegated*/NULL, /*force*/0);
|
|
+ if (unlikely(rerr)) {
|
|
+ AuIOErr("%pd reverting failed(%d, %d)\n", dentry, err, rerr);
|
|
+ err = -EIO;
|
|
+ }
|
|
+ au_dtime_revert(&dt);
|
|
+out_unpin:
|
|
+ au_unpin(&a->pin);
|
|
+out_wh:
|
|
+ dput(wh_dentry);
|
|
+out_parent:
|
|
+ di_write_unlock(a->parent);
|
|
+ dput(a->src_parent);
|
|
+out_unlock:
|
|
+ if (unlikely(err)) {
|
|
+ au_update_dbtop(dentry);
|
|
+ d_drop(dentry);
|
|
+ }
|
|
+ aufs_read_and_write_unlock2(dentry, src_dentry);
|
|
+out_kfree:
|
|
+ au_kfree_rcu(a);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int aufs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
|
|
+{
|
|
+ int err, rerr;
|
|
+ aufs_bindex_t bindex;
|
|
+ unsigned char diropq;
|
|
+ struct path h_path;
|
|
+ struct dentry *wh_dentry, *parent, *opq_dentry;
|
|
+ struct inode *h_inode;
|
|
+ struct super_block *sb;
|
|
+ struct {
|
|
+ struct au_pin pin;
|
|
+ struct au_dtime dt;
|
|
+ } *a; /* reduce the stack usage */
|
|
+ struct au_wr_dir_args wr_dir_args = {
|
|
+ .force_btgt = -1,
|
|
+ .flags = AuWrDir_ADD_ENTRY | AuWrDir_ISDIR
|
|
+ };
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ a = kmalloc(sizeof(*a), GFP_NOFS);
|
|
+ if (unlikely(!a))
|
|
+ goto out;
|
|
+
|
|
+ err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN);
|
|
+ if (unlikely(err))
|
|
+ goto out_free;
|
|
+ err = au_d_may_add(dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+
|
|
+ parent = dentry->d_parent; /* dir inode is locked */
|
|
+ di_write_lock_parent(parent);
|
|
+ wh_dentry = lock_hdir_lkup_wh(dentry, &a->dt, /*src_dentry*/NULL,
|
|
+ &a->pin, &wr_dir_args);
|
|
+ err = PTR_ERR(wh_dentry);
|
|
+ if (IS_ERR(wh_dentry))
|
|
+ goto out_parent;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ bindex = au_dbtop(dentry);
|
|
+ h_path.dentry = au_h_dptr(dentry, bindex);
|
|
+ h_path.mnt = au_sbr_mnt(sb, bindex);
|
|
+ err = vfsub_mkdir(au_pinned_h_dir(&a->pin), &h_path, mode);
|
|
+ if (unlikely(err))
|
|
+ goto out_unpin;
|
|
+
|
|
+ /* make the dir opaque */
|
|
+ diropq = 0;
|
|
+ h_inode = d_inode(h_path.dentry);
|
|
+ if (wh_dentry
|
|
+ || au_opt_test(au_mntflags(sb), ALWAYS_DIROPQ)) {
|
|
+ inode_lock_nested(h_inode, AuLsc_I_CHILD);
|
|
+ opq_dentry = au_diropq_create(dentry, bindex);
|
|
+ inode_unlock(h_inode);
|
|
+ err = PTR_ERR(opq_dentry);
|
|
+ if (IS_ERR(opq_dentry))
|
|
+ goto out_dir;
|
|
+ dput(opq_dentry);
|
|
+ diropq = 1;
|
|
+ }
|
|
+
|
|
+ err = epilog(dir, bindex, wh_dentry, dentry);
|
|
+ if (!err) {
|
|
+ inc_nlink(dir);
|
|
+ goto out_unpin; /* success */
|
|
+ }
|
|
+
|
|
+ /* revert */
|
|
+ if (diropq) {
|
|
+ AuLabel(revert opq);
|
|
+ inode_lock_nested(h_inode, AuLsc_I_CHILD);
|
|
+ rerr = au_diropq_remove(dentry, bindex);
|
|
+ inode_unlock(h_inode);
|
|
+ if (rerr) {
|
|
+ AuIOErr("%pd reverting diropq failed(%d, %d)\n",
|
|
+ dentry, err, rerr);
|
|
+ err = -EIO;
|
|
+ }
|
|
+ }
|
|
+
|
|
+out_dir:
|
|
+ AuLabel(revert dir);
|
|
+ rerr = vfsub_rmdir(au_pinned_h_dir(&a->pin), &h_path);
|
|
+ if (rerr) {
|
|
+ AuIOErr("%pd reverting dir failed(%d, %d)\n",
|
|
+ dentry, err, rerr);
|
|
+ err = -EIO;
|
|
+ }
|
|
+ au_dtime_revert(&a->dt);
|
|
+out_unpin:
|
|
+ au_unpin(&a->pin);
|
|
+ dput(wh_dentry);
|
|
+out_parent:
|
|
+ di_write_unlock(parent);
|
|
+out_unlock:
|
|
+ if (unlikely(err)) {
|
|
+ au_update_dbtop(dentry);
|
|
+ d_drop(dentry);
|
|
+ }
|
|
+ aufs_read_unlock(dentry, AuLock_DW);
|
|
+out_free:
|
|
+ au_kfree_rcu(a);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/i_op_del.c b/fs/aufs/i_op_del.c
|
|
new file mode 100644
|
|
index 000000000000..9beba2ba8db0
|
|
--- /dev/null
|
|
+++ b/fs/aufs/i_op_del.c
|
|
@@ -0,0 +1,513 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * inode operations (del entry)
|
|
+ */
|
|
+
|
|
+#include <linux/iversion.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/*
|
|
+ * decide if a new whiteout for @dentry is necessary or not.
|
|
+ * when it is necessary, prepare the parent dir for the upper branch whose
|
|
+ * branch index is @bcpup for creation. the actual creation of the whiteout will
|
|
+ * be done by caller.
|
|
+ * return value:
|
|
+ * 0: wh is unnecessary
|
|
+ * plus: wh is necessary
|
|
+ * minus: error
|
|
+ */
|
|
+int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup)
|
|
+{
|
|
+ int need_wh, err;
|
|
+ aufs_bindex_t btop;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ btop = au_dbtop(dentry);
|
|
+ if (*bcpup < 0) {
|
|
+ *bcpup = btop;
|
|
+ if (au_test_ro(sb, btop, d_inode(dentry))) {
|
|
+ err = AuWbrCopyup(au_sbi(sb), dentry);
|
|
+ *bcpup = err;
|
|
+ if (unlikely(err < 0))
|
|
+ goto out;
|
|
+ }
|
|
+ } else
|
|
+ AuDebugOn(btop < *bcpup
|
|
+ || au_test_ro(sb, *bcpup, d_inode(dentry)));
|
|
+ AuDbg("bcpup %d, btop %d\n", *bcpup, btop);
|
|
+
|
|
+ if (*bcpup != btop) {
|
|
+ err = au_cpup_dirs(dentry, *bcpup);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ need_wh = 1;
|
|
+ } else {
|
|
+ struct au_dinfo *dinfo, *tmp;
|
|
+
|
|
+ need_wh = -ENOMEM;
|
|
+ dinfo = au_di(dentry);
|
|
+ tmp = au_di_alloc(sb, AuLsc_DI_TMP);
|
|
+ if (tmp) {
|
|
+ au_di_cp(tmp, dinfo);
|
|
+ au_di_swap(tmp, dinfo);
|
|
+ /* returns the number of positive dentries */
|
|
+ need_wh = au_lkup_dentry(dentry, btop + 1,
|
|
+ /* AuLkup_IGNORE_PERM */ 0);
|
|
+ au_di_swap(tmp, dinfo);
|
|
+ au_rw_write_unlock(&tmp->di_rwsem);
|
|
+ au_di_free(tmp);
|
|
+ }
|
|
+ }
|
|
+ AuDbg("need_wh %d\n", need_wh);
|
|
+ err = need_wh;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * simple tests for the del-entry operations.
|
|
+ * following the checks in vfs, plus the parent-child relationship.
|
|
+ */
|
|
+int au_may_del(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct dentry *h_parent, int isdir)
|
|
+{
|
|
+ int err;
|
|
+ umode_t h_mode;
|
|
+ struct dentry *h_dentry, *h_latest;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (d_really_is_positive(dentry)) {
|
|
+ err = -ENOENT;
|
|
+ if (unlikely(d_is_negative(h_dentry)))
|
|
+ goto out;
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ if (unlikely(!h_inode->i_nlink))
|
|
+ goto out;
|
|
+
|
|
+ h_mode = h_inode->i_mode;
|
|
+ if (!isdir) {
|
|
+ err = -EISDIR;
|
|
+ if (unlikely(S_ISDIR(h_mode)))
|
|
+ goto out;
|
|
+ } else if (unlikely(!S_ISDIR(h_mode))) {
|
|
+ err = -ENOTDIR;
|
|
+ goto out;
|
|
+ }
|
|
+ } else {
|
|
+ /* rename(2) case */
|
|
+ err = -EIO;
|
|
+ if (unlikely(d_is_positive(h_dentry)))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = -ENOENT;
|
|
+ /* expected parent dir is locked */
|
|
+ if (unlikely(h_parent != h_dentry->d_parent))
|
|
+ goto out;
|
|
+ err = 0;
|
|
+
|
|
+ /*
|
|
+ * rmdir a dir may break the consistency on some filesystem.
|
|
+ * let's try heavy test.
|
|
+ */
|
|
+ err = -EACCES;
|
|
+ if (unlikely(!au_opt_test(au_mntflags(dentry->d_sb), DIRPERM1)
|
|
+ && au_test_h_perm(d_inode(h_parent),
|
|
+ MAY_EXEC | MAY_WRITE)))
|
|
+ goto out;
|
|
+
|
|
+ h_latest = au_sio_lkup_one(&dentry->d_name, h_parent);
|
|
+ err = -EIO;
|
|
+ if (IS_ERR(h_latest))
|
|
+ goto out;
|
|
+ if (h_latest == h_dentry)
|
|
+ err = 0;
|
|
+ dput(h_latest);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * decide the branch where we operate for @dentry. the branch index will be set
|
|
+ * @rbcpup. after deciding it, 'pin' it and store the timestamps of the parent
|
|
+ * dir for reverting.
|
|
+ * when a new whiteout is necessary, create it.
|
|
+ */
|
|
+static struct dentry*
|
|
+lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *rbcpup,
|
|
+ struct au_dtime *dt, struct au_pin *pin)
|
|
+{
|
|
+ struct dentry *wh_dentry;
|
|
+ struct super_block *sb;
|
|
+ struct path h_path;
|
|
+ int err, need_wh;
|
|
+ unsigned int udba;
|
|
+ aufs_bindex_t bcpup;
|
|
+
|
|
+ need_wh = au_wr_dir_need_wh(dentry, isdir, rbcpup);
|
|
+ wh_dentry = ERR_PTR(need_wh);
|
|
+ if (unlikely(need_wh < 0))
|
|
+ goto out;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ udba = au_opt_udba(sb);
|
|
+ bcpup = *rbcpup;
|
|
+ err = au_pin(pin, dentry, bcpup, udba,
|
|
+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
|
|
+ wh_dentry = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ h_path.dentry = au_pinned_h_parent(pin);
|
|
+ if (udba != AuOpt_UDBA_NONE
|
|
+ && au_dbtop(dentry) == bcpup) {
|
|
+ err = au_may_del(dentry, bcpup, h_path.dentry, isdir);
|
|
+ wh_dentry = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out_unpin;
|
|
+ }
|
|
+
|
|
+ h_path.mnt = au_sbr_mnt(sb, bcpup);
|
|
+ au_dtime_store(dt, au_pinned_parent(pin), &h_path);
|
|
+ wh_dentry = NULL;
|
|
+ if (!need_wh)
|
|
+ goto out; /* success, no need to create whiteout */
|
|
+
|
|
+ wh_dentry = au_wh_create(dentry, bcpup, h_path.dentry);
|
|
+ if (IS_ERR(wh_dentry))
|
|
+ goto out_unpin;
|
|
+
|
|
+ /* returns with the parent is locked and wh_dentry is dget-ed */
|
|
+ goto out; /* success */
|
|
+
|
|
+out_unpin:
|
|
+ au_unpin(pin);
|
|
+out:
|
|
+ return wh_dentry;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * when removing a dir, rename it to a unique temporary whiteout-ed name first
|
|
+ * in order to be revertible and save time for removing many child whiteouts
|
|
+ * under the dir.
|
|
+ * returns 1 when there are too many child whiteout and caller should remove
|
|
+ * them asynchronously. returns 0 when the number of children is enough small to
|
|
+ * remove now or the branch fs is a remote fs.
|
|
+ * otherwise return an error.
|
|
+ */
|
|
+static int renwh_and_rmdir(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct au_nhash *whlist, struct inode *dir)
|
|
+{
|
|
+ int rmdir_later, err, dirwh;
|
|
+ struct dentry *h_dentry;
|
|
+ struct super_block *sb;
|
|
+ struct inode *inode;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ SiMustAnyLock(sb);
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ err = au_whtmp_ren(h_dentry, au_sbr(sb, bindex));
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ /* stop monitoring */
|
|
+ inode = d_inode(dentry);
|
|
+ au_hn_free(au_hi(inode, bindex));
|
|
+
|
|
+ if (!au_test_fs_remote(h_dentry->d_sb)) {
|
|
+ dirwh = au_sbi(sb)->si_dirwh;
|
|
+ rmdir_later = (dirwh <= 1);
|
|
+ if (!rmdir_later)
|
|
+ rmdir_later = au_nhash_test_longer_wh(whlist, bindex,
|
|
+ dirwh);
|
|
+ if (rmdir_later)
|
|
+ return rmdir_later;
|
|
+ }
|
|
+
|
|
+ err = au_whtmp_rmdir(dir, bindex, h_dentry, whlist);
|
|
+ if (unlikely(err)) {
|
|
+ AuIOErr("rmdir %pd, b%d failed, %d. ignored\n",
|
|
+ h_dentry, bindex, err);
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * final procedure for deleting a entry.
|
|
+ * maintain dentry and iattr.
|
|
+ */
|
|
+static void epilog(struct inode *dir, struct dentry *dentry,
|
|
+ aufs_bindex_t bindex)
|
|
+{
|
|
+ struct inode *inode;
|
|
+
|
|
+ inode = d_inode(dentry);
|
|
+ d_drop(dentry);
|
|
+ inode->i_ctime = dir->i_ctime;
|
|
+
|
|
+ au_dir_ts(dir, bindex);
|
|
+ inode_inc_iversion(dir);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * when an error happened, remove the created whiteout and revert everything.
|
|
+ */
|
|
+static int do_revert(int err, struct inode *dir, aufs_bindex_t bindex,
|
|
+ aufs_bindex_t bwh, struct dentry *wh_dentry,
|
|
+ struct dentry *dentry, struct au_dtime *dt)
|
|
+{
|
|
+ int rerr;
|
|
+ struct path h_path = {
|
|
+ .dentry = wh_dentry,
|
|
+ .mnt = au_sbr_mnt(dir->i_sb, bindex)
|
|
+ };
|
|
+
|
|
+ rerr = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path, dentry);
|
|
+ if (!rerr) {
|
|
+ au_set_dbwh(dentry, bwh);
|
|
+ au_dtime_revert(dt);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ AuIOErr("%pd reverting whiteout failed(%d, %d)\n", dentry, err, rerr);
|
|
+ return -EIO;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int aufs_unlink(struct inode *dir, struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bwh, bindex, btop;
|
|
+ struct inode *inode, *h_dir, *delegated;
|
|
+ struct dentry *parent, *wh_dentry;
|
|
+ /* to reduce stack size */
|
|
+ struct {
|
|
+ struct au_dtime dt;
|
|
+ struct au_pin pin;
|
|
+ struct path h_path;
|
|
+ } *a;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ a = kmalloc(sizeof(*a), GFP_NOFS);
|
|
+ if (unlikely(!a))
|
|
+ goto out;
|
|
+
|
|
+ err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN);
|
|
+ if (unlikely(err))
|
|
+ goto out_free;
|
|
+ err = au_d_hashed_positive(dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+ inode = d_inode(dentry);
|
|
+ IMustLock(inode);
|
|
+ err = -EISDIR;
|
|
+ if (unlikely(d_is_dir(dentry)))
|
|
+ goto out_unlock; /* possible? */
|
|
+
|
|
+ btop = au_dbtop(dentry);
|
|
+ bwh = au_dbwh(dentry);
|
|
+ bindex = -1;
|
|
+ parent = dentry->d_parent; /* dir inode is locked */
|
|
+ di_write_lock_parent(parent);
|
|
+ wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &a->dt,
|
|
+ &a->pin);
|
|
+ err = PTR_ERR(wh_dentry);
|
|
+ if (IS_ERR(wh_dentry))
|
|
+ goto out_parent;
|
|
+
|
|
+ a->h_path.mnt = au_sbr_mnt(dentry->d_sb, btop);
|
|
+ a->h_path.dentry = au_h_dptr(dentry, btop);
|
|
+ dget(a->h_path.dentry);
|
|
+ if (bindex == btop) {
|
|
+ h_dir = au_pinned_h_dir(&a->pin);
|
|
+ delegated = NULL;
|
|
+ err = vfsub_unlink(h_dir, &a->h_path, &delegated, /*force*/0);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal unlink\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ } else {
|
|
+ /* dir inode is locked */
|
|
+ h_dir = d_inode(wh_dentry->d_parent);
|
|
+ IMustLock(h_dir);
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+ if (!err) {
|
|
+ vfsub_drop_nlink(inode);
|
|
+ epilog(dir, dentry, bindex);
|
|
+
|
|
+ /* update target timestamps */
|
|
+ if (bindex == btop) {
|
|
+ vfsub_update_h_iattr(&a->h_path, /*did*/NULL);
|
|
+ /*ignore*/
|
|
+ inode->i_ctime = d_inode(a->h_path.dentry)->i_ctime;
|
|
+ } else
|
|
+ /* todo: this timestamp may be reverted later */
|
|
+ inode->i_ctime = h_dir->i_ctime;
|
|
+ goto out_unpin; /* success */
|
|
+ }
|
|
+
|
|
+ /* revert */
|
|
+ if (wh_dentry) {
|
|
+ int rerr;
|
|
+
|
|
+ rerr = do_revert(err, dir, bindex, bwh, wh_dentry, dentry,
|
|
+ &a->dt);
|
|
+ if (rerr)
|
|
+ err = rerr;
|
|
+ }
|
|
+
|
|
+out_unpin:
|
|
+ au_unpin(&a->pin);
|
|
+ dput(wh_dentry);
|
|
+ dput(a->h_path.dentry);
|
|
+out_parent:
|
|
+ di_write_unlock(parent);
|
|
+out_unlock:
|
|
+ aufs_read_unlock(dentry, AuLock_DW);
|
|
+out_free:
|
|
+ au_kfree_rcu(a);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int aufs_rmdir(struct inode *dir, struct dentry *dentry)
|
|
+{
|
|
+ int err, rmdir_later;
|
|
+ aufs_bindex_t bwh, bindex, btop;
|
|
+ struct inode *inode;
|
|
+ struct dentry *parent, *wh_dentry, *h_dentry;
|
|
+ struct au_whtmp_rmdir *args;
|
|
+ /* to reduce stack size */
|
|
+ struct {
|
|
+ struct au_dtime dt;
|
|
+ struct au_pin pin;
|
|
+ } *a;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ a = kmalloc(sizeof(*a), GFP_NOFS);
|
|
+ if (unlikely(!a))
|
|
+ goto out;
|
|
+
|
|
+ err = aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH | AuLock_GEN);
|
|
+ if (unlikely(err))
|
|
+ goto out_free;
|
|
+ err = au_alive_dir(dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+ inode = d_inode(dentry);
|
|
+ IMustLock(inode);
|
|
+ err = -ENOTDIR;
|
|
+ if (unlikely(!d_is_dir(dentry)))
|
|
+ goto out_unlock; /* possible? */
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ args = au_whtmp_rmdir_alloc(dir->i_sb, GFP_NOFS);
|
|
+ if (unlikely(!args))
|
|
+ goto out_unlock;
|
|
+
|
|
+ parent = dentry->d_parent; /* dir inode is locked */
|
|
+ di_write_lock_parent(parent);
|
|
+ err = au_test_empty(dentry, &args->whlist);
|
|
+ if (unlikely(err))
|
|
+ goto out_parent;
|
|
+
|
|
+ btop = au_dbtop(dentry);
|
|
+ bwh = au_dbwh(dentry);
|
|
+ bindex = -1;
|
|
+ wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/1, &bindex, &a->dt,
|
|
+ &a->pin);
|
|
+ err = PTR_ERR(wh_dentry);
|
|
+ if (IS_ERR(wh_dentry))
|
|
+ goto out_parent;
|
|
+
|
|
+ h_dentry = au_h_dptr(dentry, btop);
|
|
+ dget(h_dentry);
|
|
+ rmdir_later = 0;
|
|
+ if (bindex == btop) {
|
|
+ err = renwh_and_rmdir(dentry, btop, &args->whlist, dir);
|
|
+ if (err > 0) {
|
|
+ rmdir_later = err;
|
|
+ err = 0;
|
|
+ }
|
|
+ } else {
|
|
+ /* stop monitoring */
|
|
+ au_hn_free(au_hi(inode, btop));
|
|
+
|
|
+ /* dir inode is locked */
|
|
+ IMustLock(d_inode(wh_dentry->d_parent));
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+ if (!err) {
|
|
+ vfsub_dead_dir(inode);
|
|
+ au_set_dbdiropq(dentry, -1);
|
|
+ epilog(dir, dentry, bindex);
|
|
+
|
|
+ if (rmdir_later) {
|
|
+ au_whtmp_kick_rmdir(dir, btop, h_dentry, args);
|
|
+ args = NULL;
|
|
+ }
|
|
+
|
|
+ goto out_unpin; /* success */
|
|
+ }
|
|
+
|
|
+ /* revert */
|
|
+ AuLabel(revert);
|
|
+ if (wh_dentry) {
|
|
+ int rerr;
|
|
+
|
|
+ rerr = do_revert(err, dir, bindex, bwh, wh_dentry, dentry,
|
|
+ &a->dt);
|
|
+ if (rerr)
|
|
+ err = rerr;
|
|
+ }
|
|
+
|
|
+out_unpin:
|
|
+ au_unpin(&a->pin);
|
|
+ dput(wh_dentry);
|
|
+ dput(h_dentry);
|
|
+out_parent:
|
|
+ di_write_unlock(parent);
|
|
+ if (args)
|
|
+ au_whtmp_rmdir_free(args);
|
|
+out_unlock:
|
|
+ aufs_read_unlock(dentry, AuLock_DW);
|
|
+out_free:
|
|
+ au_kfree_rcu(a);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/i_op_ren.c b/fs/aufs/i_op_ren.c
|
|
new file mode 100644
|
|
index 000000000000..435fa50811ff
|
|
--- /dev/null
|
|
+++ b/fs/aufs/i_op_ren.c
|
|
@@ -0,0 +1,1250 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * inode operation (rename entry)
|
|
+ * todo: this is crazy monster
|
|
+ */
|
|
+
|
|
+#include <linux/iversion.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+enum { AuSRC, AuDST, AuSrcDst };
|
|
+enum { AuPARENT, AuCHILD, AuParentChild };
|
|
+
|
|
+#define AuRen_ISDIR_SRC 1
|
|
+#define AuRen_ISDIR_DST (1 << 1)
|
|
+#define AuRen_ISSAMEDIR (1 << 2)
|
|
+#define AuRen_WHSRC (1 << 3)
|
|
+#define AuRen_WHDST (1 << 4)
|
|
+#define AuRen_MNT_WRITE (1 << 5)
|
|
+#define AuRen_DT_DSTDIR (1 << 6)
|
|
+#define AuRen_DIROPQ_SRC (1 << 7)
|
|
+#define AuRen_DIROPQ_DST (1 << 8)
|
|
+#define AuRen_DIRREN (1 << 9)
|
|
+#define AuRen_DROPPED_SRC (1 << 10)
|
|
+#define AuRen_DROPPED_DST (1 << 11)
|
|
+#define au_ftest_ren(flags, name) ((flags) & AuRen_##name)
|
|
+#define au_fset_ren(flags, name) \
|
|
+ do { (flags) |= AuRen_##name; } while (0)
|
|
+#define au_fclr_ren(flags, name) \
|
|
+ do { (flags) &= ~AuRen_##name; } while (0)
|
|
+
|
|
+#ifndef CONFIG_AUFS_DIRREN
|
|
+#undef AuRen_DIRREN
|
|
+#define AuRen_DIRREN 0
|
|
+#endif
|
|
+
|
|
+struct au_ren_args {
|
|
+ struct {
|
|
+ struct dentry *dentry, *h_dentry, *parent, *h_parent,
|
|
+ *wh_dentry;
|
|
+ struct inode *dir, *inode;
|
|
+ struct au_hinode *hdir, *hinode;
|
|
+ struct au_dtime dt[AuParentChild];
|
|
+ aufs_bindex_t btop, bdiropq;
|
|
+ } sd[AuSrcDst];
|
|
+
|
|
+#define src_dentry sd[AuSRC].dentry
|
|
+#define src_dir sd[AuSRC].dir
|
|
+#define src_inode sd[AuSRC].inode
|
|
+#define src_h_dentry sd[AuSRC].h_dentry
|
|
+#define src_parent sd[AuSRC].parent
|
|
+#define src_h_parent sd[AuSRC].h_parent
|
|
+#define src_wh_dentry sd[AuSRC].wh_dentry
|
|
+#define src_hdir sd[AuSRC].hdir
|
|
+#define src_hinode sd[AuSRC].hinode
|
|
+#define src_h_dir sd[AuSRC].hdir->hi_inode
|
|
+#define src_dt sd[AuSRC].dt
|
|
+#define src_btop sd[AuSRC].btop
|
|
+#define src_bdiropq sd[AuSRC].bdiropq
|
|
+
|
|
+#define dst_dentry sd[AuDST].dentry
|
|
+#define dst_dir sd[AuDST].dir
|
|
+#define dst_inode sd[AuDST].inode
|
|
+#define dst_h_dentry sd[AuDST].h_dentry
|
|
+#define dst_parent sd[AuDST].parent
|
|
+#define dst_h_parent sd[AuDST].h_parent
|
|
+#define dst_wh_dentry sd[AuDST].wh_dentry
|
|
+#define dst_hdir sd[AuDST].hdir
|
|
+#define dst_hinode sd[AuDST].hinode
|
|
+#define dst_h_dir sd[AuDST].hdir->hi_inode
|
|
+#define dst_dt sd[AuDST].dt
|
|
+#define dst_btop sd[AuDST].btop
|
|
+#define dst_bdiropq sd[AuDST].bdiropq
|
|
+
|
|
+ struct dentry *h_trap;
|
|
+ struct au_branch *br;
|
|
+ struct path h_path;
|
|
+ struct au_nhash whlist;
|
|
+ aufs_bindex_t btgt, src_bwh;
|
|
+
|
|
+ struct {
|
|
+ unsigned short auren_flags;
|
|
+ unsigned char flags; /* syscall parameter */
|
|
+ unsigned char exchange;
|
|
+ } __packed;
|
|
+
|
|
+ struct au_whtmp_rmdir *thargs;
|
|
+ struct dentry *h_dst;
|
|
+ struct au_hinode *h_root;
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * functions for reverting.
|
|
+ * when an error happened in a single rename systemcall, we should revert
|
|
+ * everything as if nothing happened.
|
|
+ * we don't need to revert the copied-up/down the parent dir since they are
|
|
+ * harmless.
|
|
+ */
|
|
+
|
|
+#define RevertFailure(fmt, ...) do { \
|
|
+ AuIOErr("revert failure: " fmt " (%d, %d)\n", \
|
|
+ ##__VA_ARGS__, err, rerr); \
|
|
+ err = -EIO; \
|
|
+} while (0)
|
|
+
|
|
+static void au_ren_do_rev_diropq(int err, struct au_ren_args *a, int idx)
|
|
+{
|
|
+ int rerr;
|
|
+ struct dentry *d;
|
|
+#define src_or_dst(member) a->sd[idx].member
|
|
+
|
|
+ d = src_or_dst(dentry); /* {src,dst}_dentry */
|
|
+ au_hn_inode_lock_nested(src_or_dst(hinode), AuLsc_I_CHILD);
|
|
+ rerr = au_diropq_remove(d, a->btgt);
|
|
+ au_hn_inode_unlock(src_or_dst(hinode));
|
|
+ au_set_dbdiropq(d, src_or_dst(bdiropq));
|
|
+ if (rerr)
|
|
+ RevertFailure("remove diropq %pd", d);
|
|
+
|
|
+#undef src_or_dst_
|
|
+}
|
|
+
|
|
+static void au_ren_rev_diropq(int err, struct au_ren_args *a)
|
|
+{
|
|
+ if (au_ftest_ren(a->auren_flags, DIROPQ_SRC))
|
|
+ au_ren_do_rev_diropq(err, a, AuSRC);
|
|
+ if (au_ftest_ren(a->auren_flags, DIROPQ_DST))
|
|
+ au_ren_do_rev_diropq(err, a, AuDST);
|
|
+}
|
|
+
|
|
+static void au_ren_rev_rename(int err, struct au_ren_args *a)
|
|
+{
|
|
+ int rerr;
|
|
+ struct inode *delegated;
|
|
+
|
|
+ a->h_path.dentry = vfsub_lkup_one(&a->src_dentry->d_name,
|
|
+ a->src_h_parent);
|
|
+ rerr = PTR_ERR(a->h_path.dentry);
|
|
+ if (IS_ERR(a->h_path.dentry)) {
|
|
+ RevertFailure("lkup one %pd", a->src_dentry);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ delegated = NULL;
|
|
+ rerr = vfsub_rename(a->dst_h_dir,
|
|
+ au_h_dptr(a->src_dentry, a->btgt),
|
|
+ a->src_h_dir, &a->h_path, &delegated, a->flags);
|
|
+ if (unlikely(rerr == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal rename\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ d_drop(a->h_path.dentry);
|
|
+ dput(a->h_path.dentry);
|
|
+ /* au_set_h_dptr(a->src_dentry, a->btgt, NULL); */
|
|
+ if (rerr)
|
|
+ RevertFailure("rename %pd", a->src_dentry);
|
|
+}
|
|
+
|
|
+static void au_ren_rev_whtmp(int err, struct au_ren_args *a)
|
|
+{
|
|
+ int rerr;
|
|
+ struct inode *delegated;
|
|
+
|
|
+ a->h_path.dentry = vfsub_lkup_one(&a->dst_dentry->d_name,
|
|
+ a->dst_h_parent);
|
|
+ rerr = PTR_ERR(a->h_path.dentry);
|
|
+ if (IS_ERR(a->h_path.dentry)) {
|
|
+ RevertFailure("lkup one %pd", a->dst_dentry);
|
|
+ return;
|
|
+ }
|
|
+ if (d_is_positive(a->h_path.dentry)) {
|
|
+ d_drop(a->h_path.dentry);
|
|
+ dput(a->h_path.dentry);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ delegated = NULL;
|
|
+ rerr = vfsub_rename(a->dst_h_dir, a->h_dst, a->dst_h_dir, &a->h_path,
|
|
+ &delegated, a->flags);
|
|
+ if (unlikely(rerr == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal rename\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ d_drop(a->h_path.dentry);
|
|
+ dput(a->h_path.dentry);
|
|
+ if (!rerr)
|
|
+ au_set_h_dptr(a->dst_dentry, a->btgt, dget(a->h_dst));
|
|
+ else
|
|
+ RevertFailure("rename %pd", a->h_dst);
|
|
+}
|
|
+
|
|
+static void au_ren_rev_whsrc(int err, struct au_ren_args *a)
|
|
+{
|
|
+ int rerr;
|
|
+
|
|
+ a->h_path.dentry = a->src_wh_dentry;
|
|
+ rerr = au_wh_unlink_dentry(a->src_h_dir, &a->h_path, a->src_dentry);
|
|
+ au_set_dbwh(a->src_dentry, a->src_bwh);
|
|
+ if (rerr)
|
|
+ RevertFailure("unlink %pd", a->src_wh_dentry);
|
|
+}
|
|
+#undef RevertFailure
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * when we have to copyup the renaming entry, do it with the rename-target name
|
|
+ * in order to minimize the cost (the later actual rename is unnecessary).
|
|
+ * otherwise rename it on the target branch.
|
|
+ */
|
|
+static int au_ren_or_cpup(struct au_ren_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d;
|
|
+ struct inode *delegated;
|
|
+
|
|
+ d = a->src_dentry;
|
|
+ if (au_dbtop(d) == a->btgt) {
|
|
+ a->h_path.dentry = a->dst_h_dentry;
|
|
+ AuDebugOn(au_dbtop(d) != a->btgt);
|
|
+ delegated = NULL;
|
|
+ err = vfsub_rename(a->src_h_dir, au_h_dptr(d, a->btgt),
|
|
+ a->dst_h_dir, &a->h_path, &delegated,
|
|
+ a->flags);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal rename\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ } else
|
|
+ BUG();
|
|
+
|
|
+ if (!err && a->h_dst)
|
|
+ /* it will be set to dinfo later */
|
|
+ dget(a->h_dst);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* cf. aufs_rmdir() */
|
|
+static int au_ren_del_whtmp(struct au_ren_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *dir;
|
|
+
|
|
+ dir = a->dst_dir;
|
|
+ SiMustAnyLock(dir->i_sb);
|
|
+ if (!au_nhash_test_longer_wh(&a->whlist, a->btgt,
|
|
+ au_sbi(dir->i_sb)->si_dirwh)
|
|
+ || au_test_fs_remote(a->h_dst->d_sb)) {
|
|
+ err = au_whtmp_rmdir(dir, a->btgt, a->h_dst, &a->whlist);
|
|
+ if (unlikely(err))
|
|
+ pr_warn("failed removing whtmp dir %pd (%d), "
|
|
+ "ignored.\n", a->h_dst, err);
|
|
+ } else {
|
|
+ au_nhash_wh_free(&a->thargs->whlist);
|
|
+ a->thargs->whlist = a->whlist;
|
|
+ a->whlist.nh_num = 0;
|
|
+ au_whtmp_kick_rmdir(dir, a->btgt, a->h_dst, a->thargs);
|
|
+ dput(a->h_dst);
|
|
+ a->thargs = NULL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* make it 'opaque' dir. */
|
|
+static int au_ren_do_diropq(struct au_ren_args *a, int idx)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d, *diropq;
|
|
+#define src_or_dst(member) a->sd[idx].member
|
|
+
|
|
+ err = 0;
|
|
+ d = src_or_dst(dentry); /* {src,dst}_dentry */
|
|
+ src_or_dst(bdiropq) = au_dbdiropq(d);
|
|
+ src_or_dst(hinode) = au_hi(src_or_dst(inode), a->btgt);
|
|
+ au_hn_inode_lock_nested(src_or_dst(hinode), AuLsc_I_CHILD);
|
|
+ diropq = au_diropq_create(d, a->btgt);
|
|
+ au_hn_inode_unlock(src_or_dst(hinode));
|
|
+ if (IS_ERR(diropq))
|
|
+ err = PTR_ERR(diropq);
|
|
+ else
|
|
+ dput(diropq);
|
|
+
|
|
+#undef src_or_dst_
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_ren_diropq(struct au_ren_args *a)
|
|
+{
|
|
+ int err;
|
|
+ unsigned char always;
|
|
+ struct dentry *d;
|
|
+
|
|
+ err = 0;
|
|
+ d = a->dst_dentry; /* already renamed on the branch */
|
|
+ always = !!au_opt_test(au_mntflags(d->d_sb), ALWAYS_DIROPQ);
|
|
+ if (au_ftest_ren(a->auren_flags, ISDIR_SRC)
|
|
+ && !au_ftest_ren(a->auren_flags, DIRREN)
|
|
+ && a->btgt != au_dbdiropq(a->src_dentry)
|
|
+ && (a->dst_wh_dentry
|
|
+ || a->btgt <= au_dbdiropq(d)
|
|
+ /* hide the lower to keep xino */
|
|
+ /* the lowers may not be a dir, but we hide them anyway */
|
|
+ || a->btgt < au_dbbot(d)
|
|
+ || always)) {
|
|
+ AuDbg("here\n");
|
|
+ err = au_ren_do_diropq(a, AuSRC);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ au_fset_ren(a->auren_flags, DIROPQ_SRC);
|
|
+ }
|
|
+ if (!a->exchange)
|
|
+ goto out; /* success */
|
|
+
|
|
+ d = a->src_dentry; /* already renamed on the branch */
|
|
+ if (au_ftest_ren(a->auren_flags, ISDIR_DST)
|
|
+ && a->btgt != au_dbdiropq(a->dst_dentry)
|
|
+ && (a->btgt < au_dbdiropq(d)
|
|
+ || a->btgt < au_dbbot(d)
|
|
+ || always)) {
|
|
+ AuDbgDentry(a->src_dentry);
|
|
+ AuDbgDentry(a->dst_dentry);
|
|
+ err = au_ren_do_diropq(a, AuDST);
|
|
+ if (unlikely(err))
|
|
+ goto out_rev_src;
|
|
+ au_fset_ren(a->auren_flags, DIROPQ_DST);
|
|
+ }
|
|
+ goto out; /* success */
|
|
+
|
|
+out_rev_src:
|
|
+ AuDbg("err %d, reverting src\n", err);
|
|
+ au_ren_rev_diropq(err, a);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int do_rename(struct au_ren_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d, *h_d;
|
|
+
|
|
+ if (!a->exchange) {
|
|
+ /* prepare workqueue args for asynchronous rmdir */
|
|
+ h_d = a->dst_h_dentry;
|
|
+ if (au_ftest_ren(a->auren_flags, ISDIR_DST)
|
|
+ /* && !au_ftest_ren(a->auren_flags, DIRREN) */
|
|
+ && d_is_positive(h_d)) {
|
|
+ err = -ENOMEM;
|
|
+ a->thargs = au_whtmp_rmdir_alloc(a->src_dentry->d_sb,
|
|
+ GFP_NOFS);
|
|
+ if (unlikely(!a->thargs))
|
|
+ goto out;
|
|
+ a->h_dst = dget(h_d);
|
|
+ }
|
|
+
|
|
+ /* create whiteout for src_dentry */
|
|
+ if (au_ftest_ren(a->auren_flags, WHSRC)) {
|
|
+ a->src_bwh = au_dbwh(a->src_dentry);
|
|
+ AuDebugOn(a->src_bwh >= 0);
|
|
+ a->src_wh_dentry = au_wh_create(a->src_dentry, a->btgt,
|
|
+ a->src_h_parent);
|
|
+ err = PTR_ERR(a->src_wh_dentry);
|
|
+ if (IS_ERR(a->src_wh_dentry))
|
|
+ goto out_thargs;
|
|
+ }
|
|
+
|
|
+ /* lookup whiteout for dentry */
|
|
+ if (au_ftest_ren(a->auren_flags, WHDST)) {
|
|
+ h_d = au_wh_lkup(a->dst_h_parent,
|
|
+ &a->dst_dentry->d_name, a->br);
|
|
+ err = PTR_ERR(h_d);
|
|
+ if (IS_ERR(h_d))
|
|
+ goto out_whsrc;
|
|
+ if (d_is_negative(h_d))
|
|
+ dput(h_d);
|
|
+ else
|
|
+ a->dst_wh_dentry = h_d;
|
|
+ }
|
|
+
|
|
+ /* rename dentry to tmpwh */
|
|
+ if (a->thargs) {
|
|
+ err = au_whtmp_ren(a->dst_h_dentry, a->br);
|
|
+ if (unlikely(err))
|
|
+ goto out_whdst;
|
|
+
|
|
+ d = a->dst_dentry;
|
|
+ au_set_h_dptr(d, a->btgt, NULL);
|
|
+ err = au_lkup_neg(d, a->btgt, /*wh*/0);
|
|
+ if (unlikely(err))
|
|
+ goto out_whtmp;
|
|
+ a->dst_h_dentry = au_h_dptr(d, a->btgt);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ BUG_ON(d_is_positive(a->dst_h_dentry) && a->src_btop != a->btgt);
|
|
+#if 0 /* debugging */
|
|
+ BUG_ON(!au_ftest_ren(a->auren_flags, DIRREN)
|
|
+ && d_is_positive(a->dst_h_dentry)
|
|
+ && a->src_btop != a->btgt);
|
|
+#endif
|
|
+
|
|
+ /* rename by vfs_rename or cpup */
|
|
+ err = au_ren_or_cpup(a);
|
|
+ if (unlikely(err))
|
|
+ /* leave the copied-up one */
|
|
+ goto out_whtmp;
|
|
+
|
|
+ /* make dir opaque */
|
|
+ err = au_ren_diropq(a);
|
|
+ if (unlikely(err))
|
|
+ goto out_rename;
|
|
+
|
|
+ /* update target timestamps */
|
|
+ if (a->exchange) {
|
|
+ AuDebugOn(au_dbtop(a->dst_dentry) != a->btgt);
|
|
+ a->h_path.dentry = au_h_dptr(a->dst_dentry, a->btgt);
|
|
+ vfsub_update_h_iattr(&a->h_path, /*did*/NULL); /*ignore*/
|
|
+ a->dst_inode->i_ctime = d_inode(a->h_path.dentry)->i_ctime;
|
|
+ }
|
|
+ AuDebugOn(au_dbtop(a->src_dentry) != a->btgt);
|
|
+ a->h_path.dentry = au_h_dptr(a->src_dentry, a->btgt);
|
|
+ vfsub_update_h_iattr(&a->h_path, /*did*/NULL); /*ignore*/
|
|
+ a->src_inode->i_ctime = d_inode(a->h_path.dentry)->i_ctime;
|
|
+
|
|
+ if (!a->exchange) {
|
|
+ /* remove whiteout for dentry */
|
|
+ if (a->dst_wh_dentry) {
|
|
+ a->h_path.dentry = a->dst_wh_dentry;
|
|
+ err = au_wh_unlink_dentry(a->dst_h_dir, &a->h_path,
|
|
+ a->dst_dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_diropq;
|
|
+ }
|
|
+
|
|
+ /* remove whtmp */
|
|
+ if (a->thargs)
|
|
+ au_ren_del_whtmp(a); /* ignore this error */
|
|
+
|
|
+ au_fhsm_wrote(a->src_dentry->d_sb, a->btgt, /*force*/0);
|
|
+ }
|
|
+ err = 0;
|
|
+ goto out_success;
|
|
+
|
|
+out_diropq:
|
|
+ au_ren_rev_diropq(err, a);
|
|
+out_rename:
|
|
+ au_ren_rev_rename(err, a);
|
|
+ dput(a->h_dst);
|
|
+out_whtmp:
|
|
+ if (a->thargs)
|
|
+ au_ren_rev_whtmp(err, a);
|
|
+out_whdst:
|
|
+ dput(a->dst_wh_dentry);
|
|
+ a->dst_wh_dentry = NULL;
|
|
+out_whsrc:
|
|
+ if (a->src_wh_dentry)
|
|
+ au_ren_rev_whsrc(err, a);
|
|
+out_success:
|
|
+ dput(a->src_wh_dentry);
|
|
+ dput(a->dst_wh_dentry);
|
|
+out_thargs:
|
|
+ if (a->thargs) {
|
|
+ dput(a->h_dst);
|
|
+ au_whtmp_rmdir_free(a->thargs);
|
|
+ a->thargs = NULL;
|
|
+ }
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * test if @dentry dir can be rename destination or not.
|
|
+ * success means, it is a logically empty dir.
|
|
+ */
|
|
+static int may_rename_dstdir(struct dentry *dentry, struct au_nhash *whlist)
|
|
+{
|
|
+ return au_test_empty(dentry, whlist);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * test if @a->src_dentry dir can be rename source or not.
|
|
+ * if it can, return 0.
|
|
+ * success means,
|
|
+ * - it is a logically empty dir.
|
|
+ * - or, it exists on writable branch and has no children including whiteouts
|
|
+ * on the lower branch unless DIRREN is on.
|
|
+ */
|
|
+static int may_rename_srcdir(struct au_ren_args *a)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int rdhash;
|
|
+ aufs_bindex_t btop, btgt;
|
|
+ struct dentry *dentry;
|
|
+ struct super_block *sb;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ dentry = a->src_dentry;
|
|
+ sb = dentry->d_sb;
|
|
+ sbinfo = au_sbi(sb);
|
|
+ if (au_opt_test(sbinfo->si_mntflags, DIRREN))
|
|
+ au_fset_ren(a->auren_flags, DIRREN);
|
|
+
|
|
+ btgt = a->btgt;
|
|
+ btop = au_dbtop(dentry);
|
|
+ if (btop != btgt) {
|
|
+ struct au_nhash whlist;
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+ rdhash = sbinfo->si_rdhash;
|
|
+ if (!rdhash)
|
|
+ rdhash = au_rdhash_est(au_dir_size(/*file*/NULL,
|
|
+ dentry));
|
|
+ err = au_nhash_alloc(&whlist, rdhash, GFP_NOFS);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = au_test_empty(dentry, &whlist);
|
|
+ au_nhash_wh_free(&whlist);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (btop == au_dbtaildir(dentry))
|
|
+ return 0; /* success */
|
|
+
|
|
+ err = au_test_empty_lower(dentry);
|
|
+
|
|
+out:
|
|
+ if (err == -ENOTEMPTY) {
|
|
+ if (au_ftest_ren(a->auren_flags, DIRREN)) {
|
|
+ err = 0;
|
|
+ } else {
|
|
+ AuWarn1("renaming dir who has child(ren) on multiple "
|
|
+ "branches, is not supported\n");
|
|
+ err = -EXDEV;
|
|
+ }
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* side effect: sets whlist and h_dentry */
|
|
+static int au_ren_may_dir(struct au_ren_args *a)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int rdhash;
|
|
+ struct dentry *d;
|
|
+
|
|
+ d = a->dst_dentry;
|
|
+ SiMustAnyLock(d->d_sb);
|
|
+
|
|
+ err = 0;
|
|
+ if (au_ftest_ren(a->auren_flags, ISDIR_DST) && a->dst_inode) {
|
|
+ rdhash = au_sbi(d->d_sb)->si_rdhash;
|
|
+ if (!rdhash)
|
|
+ rdhash = au_rdhash_est(au_dir_size(/*file*/NULL, d));
|
|
+ err = au_nhash_alloc(&a->whlist, rdhash, GFP_NOFS);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ if (!a->exchange) {
|
|
+ au_set_dbtop(d, a->dst_btop);
|
|
+ err = may_rename_dstdir(d, &a->whlist);
|
|
+ au_set_dbtop(d, a->btgt);
|
|
+ } else
|
|
+ err = may_rename_srcdir(a);
|
|
+ }
|
|
+ a->dst_h_dentry = au_h_dptr(d, au_dbtop(d));
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ d = a->src_dentry;
|
|
+ a->src_h_dentry = au_h_dptr(d, au_dbtop(d));
|
|
+ if (au_ftest_ren(a->auren_flags, ISDIR_SRC)) {
|
|
+ err = may_rename_srcdir(a);
|
|
+ if (unlikely(err)) {
|
|
+ au_nhash_wh_free(&a->whlist);
|
|
+ a->whlist.nh_num = 0;
|
|
+ }
|
|
+ }
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * simple tests for rename.
|
|
+ * following the checks in vfs, plus the parent-child relationship.
|
|
+ */
|
|
+static int au_may_ren(struct au_ren_args *a)
|
|
+{
|
|
+ int err, isdir;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ if (a->src_btop == a->btgt) {
|
|
+ err = au_may_del(a->src_dentry, a->btgt, a->src_h_parent,
|
|
+ au_ftest_ren(a->auren_flags, ISDIR_SRC));
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = -EINVAL;
|
|
+ if (unlikely(a->src_h_dentry == a->h_trap))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ if (a->dst_btop != a->btgt)
|
|
+ goto out;
|
|
+
|
|
+ err = -ENOTEMPTY;
|
|
+ if (unlikely(a->dst_h_dentry == a->h_trap))
|
|
+ goto out;
|
|
+
|
|
+ err = -EIO;
|
|
+ isdir = !!au_ftest_ren(a->auren_flags, ISDIR_DST);
|
|
+ if (d_really_is_negative(a->dst_dentry)) {
|
|
+ if (d_is_negative(a->dst_h_dentry))
|
|
+ err = au_may_add(a->dst_dentry, a->btgt,
|
|
+ a->dst_h_parent, isdir);
|
|
+ } else {
|
|
+ if (unlikely(d_is_negative(a->dst_h_dentry)))
|
|
+ goto out;
|
|
+ h_inode = d_inode(a->dst_h_dentry);
|
|
+ if (h_inode->i_nlink)
|
|
+ err = au_may_del(a->dst_dentry, a->btgt,
|
|
+ a->dst_h_parent, isdir);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ if (unlikely(err == -ENOENT || err == -EEXIST))
|
|
+ err = -EIO;
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * locking order
|
|
+ * (VFS)
|
|
+ * - src_dir and dir by lock_rename()
|
|
+ * - inode if exists
|
|
+ * (aufs)
|
|
+ * - lock all
|
|
+ * + src_dentry and dentry by aufs_read_and_write_lock2() which calls,
|
|
+ * + si_read_lock
|
|
+ * + di_write_lock2_child()
|
|
+ * + di_write_lock_child()
|
|
+ * + ii_write_lock_child()
|
|
+ * + di_write_lock_child2()
|
|
+ * + ii_write_lock_child2()
|
|
+ * + src_parent and parent
|
|
+ * + di_write_lock_parent()
|
|
+ * + ii_write_lock_parent()
|
|
+ * + di_write_lock_parent2()
|
|
+ * + ii_write_lock_parent2()
|
|
+ * + lower src_dir and dir by vfsub_lock_rename()
|
|
+ * + verify the every relationships between child and parent. if any
|
|
+ * of them failed, unlock all and return -EBUSY.
|
|
+ */
|
|
+static void au_ren_unlock(struct au_ren_args *a)
|
|
+{
|
|
+ vfsub_unlock_rename(a->src_h_parent, a->src_hdir,
|
|
+ a->dst_h_parent, a->dst_hdir);
|
|
+ if (au_ftest_ren(a->auren_flags, DIRREN)
|
|
+ && a->h_root)
|
|
+ au_hn_inode_unlock(a->h_root);
|
|
+ if (au_ftest_ren(a->auren_flags, MNT_WRITE))
|
|
+ vfsub_mnt_drop_write(au_br_mnt(a->br));
|
|
+}
|
|
+
|
|
+static int au_ren_lock(struct au_ren_args *a)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int udba;
|
|
+
|
|
+ err = 0;
|
|
+ a->src_h_parent = au_h_dptr(a->src_parent, a->btgt);
|
|
+ a->src_hdir = au_hi(a->src_dir, a->btgt);
|
|
+ a->dst_h_parent = au_h_dptr(a->dst_parent, a->btgt);
|
|
+ a->dst_hdir = au_hi(a->dst_dir, a->btgt);
|
|
+
|
|
+ err = vfsub_mnt_want_write(au_br_mnt(a->br));
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ au_fset_ren(a->auren_flags, MNT_WRITE);
|
|
+ if (au_ftest_ren(a->auren_flags, DIRREN)) {
|
|
+ struct dentry *root;
|
|
+ struct inode *dir;
|
|
+
|
|
+ /*
|
|
+ * sbinfo is already locked, so this ii_read_lock is
|
|
+ * unnecessary. but our debugging feature checks it.
|
|
+ */
|
|
+ root = a->src_inode->i_sb->s_root;
|
|
+ if (root != a->src_parent && root != a->dst_parent) {
|
|
+ dir = d_inode(root);
|
|
+ ii_read_lock_parent3(dir);
|
|
+ a->h_root = au_hi(dir, a->btgt);
|
|
+ ii_read_unlock(dir);
|
|
+ au_hn_inode_lock_nested(a->h_root, AuLsc_I_PARENT3);
|
|
+ }
|
|
+ }
|
|
+ a->h_trap = vfsub_lock_rename(a->src_h_parent, a->src_hdir,
|
|
+ a->dst_h_parent, a->dst_hdir);
|
|
+ udba = au_opt_udba(a->src_dentry->d_sb);
|
|
+ if (unlikely(a->src_hdir->hi_inode != d_inode(a->src_h_parent)
|
|
+ || a->dst_hdir->hi_inode != d_inode(a->dst_h_parent)))
|
|
+ err = au_busy_or_stale();
|
|
+ if (!err && au_dbtop(a->src_dentry) == a->btgt)
|
|
+ err = au_h_verify(a->src_h_dentry, udba,
|
|
+ d_inode(a->src_h_parent), a->src_h_parent,
|
|
+ a->br);
|
|
+ if (!err && au_dbtop(a->dst_dentry) == a->btgt)
|
|
+ err = au_h_verify(a->dst_h_dentry, udba,
|
|
+ d_inode(a->dst_h_parent), a->dst_h_parent,
|
|
+ a->br);
|
|
+ if (!err)
|
|
+ goto out; /* success */
|
|
+
|
|
+ err = au_busy_or_stale();
|
|
+ au_ren_unlock(a);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static void au_ren_refresh_dir(struct au_ren_args *a)
|
|
+{
|
|
+ struct inode *dir;
|
|
+
|
|
+ dir = a->dst_dir;
|
|
+ inode_inc_iversion(dir);
|
|
+ if (au_ftest_ren(a->auren_flags, ISDIR_SRC)) {
|
|
+ /* is this updating defined in POSIX? */
|
|
+ au_cpup_attr_timesizes(a->src_inode);
|
|
+ au_cpup_attr_nlink(dir, /*force*/1);
|
|
+ }
|
|
+ au_dir_ts(dir, a->btgt);
|
|
+
|
|
+ if (a->exchange) {
|
|
+ dir = a->src_dir;
|
|
+ inode_inc_iversion(dir);
|
|
+ if (au_ftest_ren(a->auren_flags, ISDIR_DST)) {
|
|
+ /* is this updating defined in POSIX? */
|
|
+ au_cpup_attr_timesizes(a->dst_inode);
|
|
+ au_cpup_attr_nlink(dir, /*force*/1);
|
|
+ }
|
|
+ au_dir_ts(dir, a->btgt);
|
|
+ }
|
|
+
|
|
+ if (au_ftest_ren(a->auren_flags, ISSAMEDIR))
|
|
+ return;
|
|
+
|
|
+ dir = a->src_dir;
|
|
+ inode_inc_iversion(dir);
|
|
+ if (au_ftest_ren(a->auren_flags, ISDIR_SRC))
|
|
+ au_cpup_attr_nlink(dir, /*force*/1);
|
|
+ au_dir_ts(dir, a->btgt);
|
|
+}
|
|
+
|
|
+static void au_ren_refresh(struct au_ren_args *a)
|
|
+{
|
|
+ aufs_bindex_t bbot, bindex;
|
|
+ struct dentry *d, *h_d;
|
|
+ struct inode *i, *h_i;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ d = a->dst_dentry;
|
|
+ d_drop(d);
|
|
+ if (a->h_dst)
|
|
+ /* already dget-ed by au_ren_or_cpup() */
|
|
+ au_set_h_dptr(d, a->btgt, a->h_dst);
|
|
+
|
|
+ i = a->dst_inode;
|
|
+ if (i) {
|
|
+ if (!a->exchange) {
|
|
+ if (!au_ftest_ren(a->auren_flags, ISDIR_DST))
|
|
+ vfsub_drop_nlink(i);
|
|
+ else {
|
|
+ vfsub_dead_dir(i);
|
|
+ au_cpup_attr_timesizes(i);
|
|
+ }
|
|
+ au_update_dbrange(d, /*do_put_zero*/1);
|
|
+ } else
|
|
+ au_cpup_attr_nlink(i, /*force*/1);
|
|
+ } else {
|
|
+ bbot = a->btgt;
|
|
+ for (bindex = au_dbtop(d); bindex < bbot; bindex++)
|
|
+ au_set_h_dptr(d, bindex, NULL);
|
|
+ bbot = au_dbbot(d);
|
|
+ for (bindex = a->btgt + 1; bindex <= bbot; bindex++)
|
|
+ au_set_h_dptr(d, bindex, NULL);
|
|
+ au_update_dbrange(d, /*do_put_zero*/0);
|
|
+ }
|
|
+
|
|
+ if (a->exchange
|
|
+ || au_ftest_ren(a->auren_flags, DIRREN)) {
|
|
+ d_drop(a->src_dentry);
|
|
+ if (au_ftest_ren(a->auren_flags, DIRREN))
|
|
+ au_set_dbwh(a->src_dentry, -1);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ d = a->src_dentry;
|
|
+ au_set_dbwh(d, -1);
|
|
+ bbot = au_dbbot(d);
|
|
+ for (bindex = a->btgt + 1; bindex <= bbot; bindex++) {
|
|
+ h_d = au_h_dptr(d, bindex);
|
|
+ if (h_d)
|
|
+ au_set_h_dptr(d, bindex, NULL);
|
|
+ }
|
|
+ au_set_dbbot(d, a->btgt);
|
|
+
|
|
+ sb = d->d_sb;
|
|
+ i = a->src_inode;
|
|
+ if (au_opt_test(au_mntflags(sb), PLINK) && au_plink_test(i))
|
|
+ return; /* success */
|
|
+
|
|
+ bbot = au_ibbot(i);
|
|
+ for (bindex = a->btgt + 1; bindex <= bbot; bindex++) {
|
|
+ h_i = au_h_iptr(i, bindex);
|
|
+ if (h_i) {
|
|
+ au_xino_write(sb, bindex, h_i->i_ino, /*ino*/0);
|
|
+ /* ignore this error */
|
|
+ au_set_h_iptr(i, bindex, NULL, 0);
|
|
+ }
|
|
+ }
|
|
+ au_set_ibbot(i, a->btgt);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* mainly for link(2) and rename(2) */
|
|
+int au_wbr(struct dentry *dentry, aufs_bindex_t btgt)
|
|
+{
|
|
+ aufs_bindex_t bdiropq, bwh;
|
|
+ struct dentry *parent;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ parent = dentry->d_parent;
|
|
+ IMustLock(d_inode(parent)); /* dir is locked */
|
|
+
|
|
+ bdiropq = au_dbdiropq(parent);
|
|
+ bwh = au_dbwh(dentry);
|
|
+ br = au_sbr(dentry->d_sb, btgt);
|
|
+ if (au_br_rdonly(br)
|
|
+ || (0 <= bdiropq && bdiropq < btgt)
|
|
+ || (0 <= bwh && bwh < btgt))
|
|
+ btgt = -1;
|
|
+
|
|
+ AuDbg("btgt %d\n", btgt);
|
|
+ return btgt;
|
|
+}
|
|
+
|
|
+/* sets src_btop, dst_btop and btgt */
|
|
+static int au_ren_wbr(struct au_ren_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct au_wr_dir_args wr_dir_args = {
|
|
+ /* .force_btgt = -1, */
|
|
+ .flags = AuWrDir_ADD_ENTRY
|
|
+ };
|
|
+
|
|
+ a->src_btop = au_dbtop(a->src_dentry);
|
|
+ a->dst_btop = au_dbtop(a->dst_dentry);
|
|
+ if (au_ftest_ren(a->auren_flags, ISDIR_SRC)
|
|
+ || au_ftest_ren(a->auren_flags, ISDIR_DST))
|
|
+ au_fset_wrdir(wr_dir_args.flags, ISDIR);
|
|
+ wr_dir_args.force_btgt = a->src_btop;
|
|
+ if (a->dst_inode && a->dst_btop < a->src_btop)
|
|
+ wr_dir_args.force_btgt = a->dst_btop;
|
|
+ wr_dir_args.force_btgt = au_wbr(a->dst_dentry, wr_dir_args.force_btgt);
|
|
+ err = au_wr_dir(a->dst_dentry, a->src_dentry, &wr_dir_args);
|
|
+ a->btgt = err;
|
|
+ if (a->exchange)
|
|
+ au_update_dbtop(a->dst_dentry);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_ren_dt(struct au_ren_args *a)
|
|
+{
|
|
+ a->h_path.dentry = a->src_h_parent;
|
|
+ au_dtime_store(a->src_dt + AuPARENT, a->src_parent, &a->h_path);
|
|
+ if (!au_ftest_ren(a->auren_flags, ISSAMEDIR)) {
|
|
+ a->h_path.dentry = a->dst_h_parent;
|
|
+ au_dtime_store(a->dst_dt + AuPARENT, a->dst_parent, &a->h_path);
|
|
+ }
|
|
+
|
|
+ au_fclr_ren(a->auren_flags, DT_DSTDIR);
|
|
+ if (!au_ftest_ren(a->auren_flags, ISDIR_SRC)
|
|
+ && !a->exchange)
|
|
+ return;
|
|
+
|
|
+ a->h_path.dentry = a->src_h_dentry;
|
|
+ au_dtime_store(a->src_dt + AuCHILD, a->src_dentry, &a->h_path);
|
|
+ if (d_is_positive(a->dst_h_dentry)) {
|
|
+ au_fset_ren(a->auren_flags, DT_DSTDIR);
|
|
+ a->h_path.dentry = a->dst_h_dentry;
|
|
+ au_dtime_store(a->dst_dt + AuCHILD, a->dst_dentry, &a->h_path);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void au_ren_rev_dt(int err, struct au_ren_args *a)
|
|
+{
|
|
+ struct dentry *h_d;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ au_dtime_revert(a->src_dt + AuPARENT);
|
|
+ if (!au_ftest_ren(a->auren_flags, ISSAMEDIR))
|
|
+ au_dtime_revert(a->dst_dt + AuPARENT);
|
|
+
|
|
+ if (au_ftest_ren(a->auren_flags, ISDIR_SRC) && err != -EIO) {
|
|
+ h_d = a->src_dt[AuCHILD].dt_h_path.dentry;
|
|
+ h_inode = d_inode(h_d);
|
|
+ inode_lock_nested(h_inode, AuLsc_I_CHILD);
|
|
+ au_dtime_revert(a->src_dt + AuCHILD);
|
|
+ inode_unlock(h_inode);
|
|
+
|
|
+ if (au_ftest_ren(a->auren_flags, DT_DSTDIR)) {
|
|
+ h_d = a->dst_dt[AuCHILD].dt_h_path.dentry;
|
|
+ h_inode = d_inode(h_d);
|
|
+ inode_lock_nested(h_inode, AuLsc_I_CHILD);
|
|
+ au_dtime_revert(a->dst_dt + AuCHILD);
|
|
+ inode_unlock(h_inode);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int aufs_rename(struct inode *_src_dir, struct dentry *_src_dentry,
|
|
+ struct inode *_dst_dir, struct dentry *_dst_dentry,
|
|
+ unsigned int _flags)
|
|
+{
|
|
+ int err, lock_flags;
|
|
+ void *rev;
|
|
+ /* reduce stack space */
|
|
+ struct au_ren_args *a;
|
|
+ struct au_pin pin;
|
|
+
|
|
+ AuDbg("%pd, %pd, 0x%x\n", _src_dentry, _dst_dentry, _flags);
|
|
+ IMustLock(_src_dir);
|
|
+ IMustLock(_dst_dir);
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (unlikely(_flags & RENAME_WHITEOUT))
|
|
+ goto out;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ BUILD_BUG_ON(sizeof(*a) > PAGE_SIZE);
|
|
+ a = kzalloc(sizeof(*a), GFP_NOFS);
|
|
+ if (unlikely(!a))
|
|
+ goto out;
|
|
+
|
|
+ a->flags = _flags;
|
|
+ BUILD_BUG_ON(sizeof(a->exchange) == sizeof(u8)
|
|
+ && RENAME_EXCHANGE > U8_MAX);
|
|
+ a->exchange = _flags & RENAME_EXCHANGE;
|
|
+ a->src_dir = _src_dir;
|
|
+ a->src_dentry = _src_dentry;
|
|
+ a->src_inode = NULL;
|
|
+ if (d_really_is_positive(a->src_dentry))
|
|
+ a->src_inode = d_inode(a->src_dentry);
|
|
+ a->src_parent = a->src_dentry->d_parent; /* dir inode is locked */
|
|
+ a->dst_dir = _dst_dir;
|
|
+ a->dst_dentry = _dst_dentry;
|
|
+ a->dst_inode = NULL;
|
|
+ if (d_really_is_positive(a->dst_dentry))
|
|
+ a->dst_inode = d_inode(a->dst_dentry);
|
|
+ a->dst_parent = a->dst_dentry->d_parent; /* dir inode is locked */
|
|
+ if (a->dst_inode) {
|
|
+ /*
|
|
+ * if EXCHANGE && src is non-dir && dst is dir,
|
|
+ * dst is not locked.
|
|
+ */
|
|
+ /* IMustLock(a->dst_inode); */
|
|
+ au_igrab(a->dst_inode);
|
|
+ }
|
|
+
|
|
+ err = -ENOTDIR;
|
|
+ lock_flags = AuLock_FLUSH | AuLock_NOPLM | AuLock_GEN;
|
|
+ if (d_is_dir(a->src_dentry)) {
|
|
+ au_fset_ren(a->auren_flags, ISDIR_SRC);
|
|
+ if (unlikely(!a->exchange
|
|
+ && d_really_is_positive(a->dst_dentry)
|
|
+ && !d_is_dir(a->dst_dentry)))
|
|
+ goto out_free;
|
|
+ lock_flags |= AuLock_DIRS;
|
|
+ }
|
|
+ if (a->dst_inode && d_is_dir(a->dst_dentry)) {
|
|
+ au_fset_ren(a->auren_flags, ISDIR_DST);
|
|
+ if (unlikely(!a->exchange
|
|
+ && d_really_is_positive(a->src_dentry)
|
|
+ && !d_is_dir(a->src_dentry)))
|
|
+ goto out_free;
|
|
+ lock_flags |= AuLock_DIRS;
|
|
+ }
|
|
+ err = aufs_read_and_write_lock2(a->dst_dentry, a->src_dentry,
|
|
+ lock_flags);
|
|
+ if (unlikely(err))
|
|
+ goto out_free;
|
|
+
|
|
+ err = au_d_hashed_positive(a->src_dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+ err = -ENOENT;
|
|
+ if (a->dst_inode) {
|
|
+ /*
|
|
+ * If it is a dir, VFS unhash it before this
|
|
+ * function. It means we cannot rely upon d_unhashed().
|
|
+ */
|
|
+ if (unlikely(!a->dst_inode->i_nlink))
|
|
+ goto out_unlock;
|
|
+ if (!au_ftest_ren(a->auren_flags, ISDIR_DST)) {
|
|
+ err = au_d_hashed_positive(a->dst_dentry);
|
|
+ if (unlikely(err && !a->exchange))
|
|
+ goto out_unlock;
|
|
+ } else if (unlikely(IS_DEADDIR(a->dst_inode)))
|
|
+ goto out_unlock;
|
|
+ } else if (unlikely(d_unhashed(a->dst_dentry)))
|
|
+ goto out_unlock;
|
|
+
|
|
+ /*
|
|
+ * is it possible?
|
|
+ * yes, it happened (in linux-3.3-rcN) but I don't know why.
|
|
+ * there may exist a problem somewhere else.
|
|
+ */
|
|
+ err = -EINVAL;
|
|
+ if (unlikely(d_inode(a->dst_parent) == d_inode(a->src_dentry)))
|
|
+ goto out_unlock;
|
|
+
|
|
+ au_fset_ren(a->auren_flags, ISSAMEDIR); /* temporary */
|
|
+ di_write_lock_parent(a->dst_parent);
|
|
+
|
|
+ /* which branch we process */
|
|
+ err = au_ren_wbr(a);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out_parent;
|
|
+ a->br = au_sbr(a->dst_dentry->d_sb, a->btgt);
|
|
+ a->h_path.mnt = au_br_mnt(a->br);
|
|
+
|
|
+ /* are they available to be renamed */
|
|
+ err = au_ren_may_dir(a);
|
|
+ if (unlikely(err))
|
|
+ goto out_children;
|
|
+
|
|
+ /* prepare the writable parent dir on the same branch */
|
|
+ if (a->dst_btop == a->btgt) {
|
|
+ au_fset_ren(a->auren_flags, WHDST);
|
|
+ } else {
|
|
+ err = au_cpup_dirs(a->dst_dentry, a->btgt);
|
|
+ if (unlikely(err))
|
|
+ goto out_children;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ if (!a->exchange) {
|
|
+ if (a->src_dir != a->dst_dir) {
|
|
+ /*
|
|
+ * this temporary unlock is safe,
|
|
+ * because both dir->i_mutex are locked.
|
|
+ */
|
|
+ di_write_unlock(a->dst_parent);
|
|
+ di_write_lock_parent(a->src_parent);
|
|
+ err = au_wr_dir_need_wh(a->src_dentry,
|
|
+ au_ftest_ren(a->auren_flags,
|
|
+ ISDIR_SRC),
|
|
+ &a->btgt);
|
|
+ di_write_unlock(a->src_parent);
|
|
+ di_write_lock2_parent(a->src_parent, a->dst_parent,
|
|
+ /*isdir*/1);
|
|
+ au_fclr_ren(a->auren_flags, ISSAMEDIR);
|
|
+ } else
|
|
+ err = au_wr_dir_need_wh(a->src_dentry,
|
|
+ au_ftest_ren(a->auren_flags,
|
|
+ ISDIR_SRC),
|
|
+ &a->btgt);
|
|
+ }
|
|
+ if (unlikely(err < 0))
|
|
+ goto out_children;
|
|
+ if (err)
|
|
+ au_fset_ren(a->auren_flags, WHSRC);
|
|
+
|
|
+ /* cpup src */
|
|
+ if (a->src_btop != a->btgt) {
|
|
+ err = au_pin(&pin, a->src_dentry, a->btgt,
|
|
+ au_opt_udba(a->src_dentry->d_sb),
|
|
+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
|
|
+ if (!err) {
|
|
+ struct au_cp_generic cpg = {
|
|
+ .dentry = a->src_dentry,
|
|
+ .bdst = a->btgt,
|
|
+ .bsrc = a->src_btop,
|
|
+ .len = -1,
|
|
+ .pin = &pin,
|
|
+ .flags = AuCpup_DTIME | AuCpup_HOPEN
|
|
+ };
|
|
+ AuDebugOn(au_dbtop(a->src_dentry) != a->src_btop);
|
|
+ err = au_sio_cpup_simple(&cpg);
|
|
+ au_unpin(&pin);
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ goto out_children;
|
|
+ a->src_btop = a->btgt;
|
|
+ a->src_h_dentry = au_h_dptr(a->src_dentry, a->btgt);
|
|
+ if (!a->exchange)
|
|
+ au_fset_ren(a->auren_flags, WHSRC);
|
|
+ }
|
|
+
|
|
+ /* cpup dst */
|
|
+ if (a->exchange && a->dst_inode
|
|
+ && a->dst_btop != a->btgt) {
|
|
+ err = au_pin(&pin, a->dst_dentry, a->btgt,
|
|
+ au_opt_udba(a->dst_dentry->d_sb),
|
|
+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
|
|
+ if (!err) {
|
|
+ struct au_cp_generic cpg = {
|
|
+ .dentry = a->dst_dentry,
|
|
+ .bdst = a->btgt,
|
|
+ .bsrc = a->dst_btop,
|
|
+ .len = -1,
|
|
+ .pin = &pin,
|
|
+ .flags = AuCpup_DTIME | AuCpup_HOPEN
|
|
+ };
|
|
+ err = au_sio_cpup_simple(&cpg);
|
|
+ au_unpin(&pin);
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ goto out_children;
|
|
+ a->dst_btop = a->btgt;
|
|
+ a->dst_h_dentry = au_h_dptr(a->dst_dentry, a->btgt);
|
|
+ }
|
|
+
|
|
+ /* lock them all */
|
|
+ err = au_ren_lock(a);
|
|
+ if (unlikely(err))
|
|
+ /* leave the copied-up one */
|
|
+ goto out_children;
|
|
+
|
|
+ if (!a->exchange) {
|
|
+ if (!au_opt_test(au_mntflags(a->dst_dir->i_sb), UDBA_NONE))
|
|
+ err = au_may_ren(a);
|
|
+ else if (unlikely(a->dst_dentry->d_name.len > AUFS_MAX_NAMELEN))
|
|
+ err = -ENAMETOOLONG;
|
|
+ if (unlikely(err))
|
|
+ goto out_hdir;
|
|
+ }
|
|
+
|
|
+ /* store timestamps to be revertible */
|
|
+ au_ren_dt(a);
|
|
+
|
|
+ /* store dirren info */
|
|
+ if (au_ftest_ren(a->auren_flags, DIRREN)) {
|
|
+ err = au_dr_rename(a->src_dentry, a->btgt,
|
|
+ &a->dst_dentry->d_name, &rev);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err))
|
|
+ goto out_dt;
|
|
+ }
|
|
+
|
|
+ /* here we go */
|
|
+ err = do_rename(a);
|
|
+ if (unlikely(err))
|
|
+ goto out_dirren;
|
|
+
|
|
+ if (au_ftest_ren(a->auren_flags, DIRREN))
|
|
+ au_dr_rename_fin(a->src_dentry, a->btgt, rev);
|
|
+
|
|
+ /* update dir attributes */
|
|
+ au_ren_refresh_dir(a);
|
|
+
|
|
+ /* dput/iput all lower dentries */
|
|
+ au_ren_refresh(a);
|
|
+
|
|
+ goto out_hdir; /* success */
|
|
+
|
|
+out_dirren:
|
|
+ if (au_ftest_ren(a->auren_flags, DIRREN))
|
|
+ au_dr_rename_rev(a->src_dentry, a->btgt, rev);
|
|
+out_dt:
|
|
+ au_ren_rev_dt(err, a);
|
|
+out_hdir:
|
|
+ au_ren_unlock(a);
|
|
+out_children:
|
|
+ au_nhash_wh_free(&a->whlist);
|
|
+ if (err && a->dst_inode && a->dst_btop != a->btgt) {
|
|
+ AuDbg("btop %d, btgt %d\n", a->dst_btop, a->btgt);
|
|
+ au_set_h_dptr(a->dst_dentry, a->btgt, NULL);
|
|
+ au_set_dbtop(a->dst_dentry, a->dst_btop);
|
|
+ }
|
|
+out_parent:
|
|
+ if (!err) {
|
|
+ if (d_unhashed(a->src_dentry))
|
|
+ au_fset_ren(a->auren_flags, DROPPED_SRC);
|
|
+ if (d_unhashed(a->dst_dentry))
|
|
+ au_fset_ren(a->auren_flags, DROPPED_DST);
|
|
+ if (!a->exchange)
|
|
+ d_move(a->src_dentry, a->dst_dentry);
|
|
+ else {
|
|
+ d_exchange(a->src_dentry, a->dst_dentry);
|
|
+ if (au_ftest_ren(a->auren_flags, DROPPED_DST))
|
|
+ d_drop(a->dst_dentry);
|
|
+ }
|
|
+ if (au_ftest_ren(a->auren_flags, DROPPED_SRC))
|
|
+ d_drop(a->src_dentry);
|
|
+ } else {
|
|
+ au_update_dbtop(a->dst_dentry);
|
|
+ if (!a->dst_inode)
|
|
+ d_drop(a->dst_dentry);
|
|
+ }
|
|
+ if (au_ftest_ren(a->auren_flags, ISSAMEDIR))
|
|
+ di_write_unlock(a->dst_parent);
|
|
+ else
|
|
+ di_write_unlock2(a->src_parent, a->dst_parent);
|
|
+out_unlock:
|
|
+ aufs_read_and_write_unlock2(a->dst_dentry, a->src_dentry);
|
|
+out_free:
|
|
+ iput(a->dst_inode);
|
|
+ if (a->thargs)
|
|
+ au_whtmp_rmdir_free(a->thargs);
|
|
+ au_kfree_rcu(a);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/iinfo.c b/fs/aufs/iinfo.c
|
|
new file mode 100644
|
|
index 000000000000..016db39451cd
|
|
--- /dev/null
|
|
+++ b/fs/aufs/iinfo.c
|
|
@@ -0,0 +1,286 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * inode private data
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex)
|
|
+{
|
|
+ struct inode *h_inode;
|
|
+ struct au_hinode *hinode;
|
|
+
|
|
+ IiMustAnyLock(inode);
|
|
+
|
|
+ hinode = au_hinode(au_ii(inode), bindex);
|
|
+ h_inode = hinode->hi_inode;
|
|
+ AuDebugOn(h_inode && atomic_read(&h_inode->i_count) <= 0);
|
|
+ return h_inode;
|
|
+}
|
|
+
|
|
+/* todo: hard/soft set? */
|
|
+void au_hiput(struct au_hinode *hinode)
|
|
+{
|
|
+ au_hn_free(hinode);
|
|
+ dput(hinode->hi_whdentry);
|
|
+ iput(hinode->hi_inode);
|
|
+}
|
|
+
|
|
+unsigned int au_hi_flags(struct inode *inode, int isdir)
|
|
+{
|
|
+ unsigned int flags;
|
|
+ const unsigned int mnt_flags = au_mntflags(inode->i_sb);
|
|
+
|
|
+ flags = 0;
|
|
+ if (au_opt_test(mnt_flags, XINO))
|
|
+ au_fset_hi(flags, XINO);
|
|
+ if (isdir && au_opt_test(mnt_flags, UDBA_HNOTIFY))
|
|
+ au_fset_hi(flags, HNOTIFY);
|
|
+ return flags;
|
|
+}
|
|
+
|
|
+void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex,
|
|
+ struct inode *h_inode, unsigned int flags)
|
|
+{
|
|
+ struct au_hinode *hinode;
|
|
+ struct inode *hi;
|
|
+ struct au_iinfo *iinfo = au_ii(inode);
|
|
+
|
|
+ IiMustWriteLock(inode);
|
|
+
|
|
+ hinode = au_hinode(iinfo, bindex);
|
|
+ hi = hinode->hi_inode;
|
|
+ AuDebugOn(h_inode && atomic_read(&h_inode->i_count) <= 0);
|
|
+
|
|
+ if (hi)
|
|
+ au_hiput(hinode);
|
|
+ hinode->hi_inode = h_inode;
|
|
+ if (h_inode) {
|
|
+ int err;
|
|
+ struct super_block *sb = inode->i_sb;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ AuDebugOn(inode->i_mode
|
|
+ && (h_inode->i_mode & S_IFMT)
|
|
+ != (inode->i_mode & S_IFMT));
|
|
+ if (bindex == iinfo->ii_btop)
|
|
+ au_cpup_igen(inode, h_inode);
|
|
+ br = au_sbr(sb, bindex);
|
|
+ hinode->hi_id = br->br_id;
|
|
+ if (au_ftest_hi(flags, XINO)) {
|
|
+ err = au_xino_write(sb, bindex, h_inode->i_ino,
|
|
+ inode->i_ino);
|
|
+ if (unlikely(err))
|
|
+ AuIOErr1("failed au_xino_write() %d\n", err);
|
|
+ }
|
|
+
|
|
+ if (au_ftest_hi(flags, HNOTIFY)
|
|
+ && au_br_hnotifyable(br->br_perm)) {
|
|
+ err = au_hn_alloc(hinode, inode);
|
|
+ if (unlikely(err))
|
|
+ AuIOErr1("au_hn_alloc() %d\n", err);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+void au_set_hi_wh(struct inode *inode, aufs_bindex_t bindex,
|
|
+ struct dentry *h_wh)
|
|
+{
|
|
+ struct au_hinode *hinode;
|
|
+
|
|
+ IiMustWriteLock(inode);
|
|
+
|
|
+ hinode = au_hinode(au_ii(inode), bindex);
|
|
+ AuDebugOn(hinode->hi_whdentry);
|
|
+ hinode->hi_whdentry = h_wh;
|
|
+}
|
|
+
|
|
+void au_update_iigen(struct inode *inode, int half)
|
|
+{
|
|
+ struct au_iinfo *iinfo;
|
|
+ struct au_iigen *iigen;
|
|
+ unsigned int sigen;
|
|
+
|
|
+ sigen = au_sigen(inode->i_sb);
|
|
+ iinfo = au_ii(inode);
|
|
+ iigen = &iinfo->ii_generation;
|
|
+ spin_lock(&iigen->ig_spin);
|
|
+ iigen->ig_generation = sigen;
|
|
+ if (half)
|
|
+ au_ig_fset(iigen->ig_flags, HALF_REFRESHED);
|
|
+ else
|
|
+ au_ig_fclr(iigen->ig_flags, HALF_REFRESHED);
|
|
+ spin_unlock(&iigen->ig_spin);
|
|
+}
|
|
+
|
|
+/* it may be called at remount time, too */
|
|
+void au_update_ibrange(struct inode *inode, int do_put_zero)
|
|
+{
|
|
+ struct au_iinfo *iinfo;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+
|
|
+ AuDebugOn(au_is_bad_inode(inode));
|
|
+ IiMustWriteLock(inode);
|
|
+
|
|
+ iinfo = au_ii(inode);
|
|
+ if (do_put_zero && iinfo->ii_btop >= 0) {
|
|
+ for (bindex = iinfo->ii_btop; bindex <= iinfo->ii_bbot;
|
|
+ bindex++) {
|
|
+ struct inode *h_i;
|
|
+
|
|
+ h_i = au_hinode(iinfo, bindex)->hi_inode;
|
|
+ if (h_i
|
|
+ && !h_i->i_nlink
|
|
+ && !(h_i->i_state & I_LINKABLE))
|
|
+ au_set_h_iptr(inode, bindex, NULL, 0);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ iinfo->ii_btop = -1;
|
|
+ iinfo->ii_bbot = -1;
|
|
+ bbot = au_sbbot(inode->i_sb);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++)
|
|
+ if (au_hinode(iinfo, bindex)->hi_inode) {
|
|
+ iinfo->ii_btop = bindex;
|
|
+ break;
|
|
+ }
|
|
+ if (iinfo->ii_btop >= 0)
|
|
+ for (bindex = bbot; bindex >= iinfo->ii_btop; bindex--)
|
|
+ if (au_hinode(iinfo, bindex)->hi_inode) {
|
|
+ iinfo->ii_bbot = bindex;
|
|
+ break;
|
|
+ }
|
|
+ AuDebugOn(iinfo->ii_btop > iinfo->ii_bbot);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void au_icntnr_init_once(void *_c)
|
|
+{
|
|
+ struct au_icntnr *c = _c;
|
|
+ struct au_iinfo *iinfo = &c->iinfo;
|
|
+
|
|
+ spin_lock_init(&iinfo->ii_generation.ig_spin);
|
|
+ au_rw_init(&iinfo->ii_rwsem);
|
|
+ inode_init_once(&c->vfs_inode);
|
|
+}
|
|
+
|
|
+void au_hinode_init(struct au_hinode *hinode)
|
|
+{
|
|
+ hinode->hi_inode = NULL;
|
|
+ hinode->hi_id = -1;
|
|
+ au_hn_init(hinode);
|
|
+ hinode->hi_whdentry = NULL;
|
|
+}
|
|
+
|
|
+int au_iinfo_init(struct inode *inode)
|
|
+{
|
|
+ struct au_iinfo *iinfo;
|
|
+ struct super_block *sb;
|
|
+ struct au_hinode *hi;
|
|
+ int nbr, i;
|
|
+
|
|
+ sb = inode->i_sb;
|
|
+ iinfo = &(container_of(inode, struct au_icntnr, vfs_inode)->iinfo);
|
|
+ nbr = au_sbbot(sb) + 1;
|
|
+ if (unlikely(nbr <= 0))
|
|
+ nbr = 1;
|
|
+ hi = kmalloc_array(nbr, sizeof(*iinfo->ii_hinode), GFP_NOFS);
|
|
+ if (hi) {
|
|
+ au_lcnt_inc(&au_sbi(sb)->si_ninodes);
|
|
+
|
|
+ iinfo->ii_hinode = hi;
|
|
+ for (i = 0; i < nbr; i++, hi++)
|
|
+ au_hinode_init(hi);
|
|
+
|
|
+ iinfo->ii_generation.ig_generation = au_sigen(sb);
|
|
+ iinfo->ii_btop = -1;
|
|
+ iinfo->ii_bbot = -1;
|
|
+ iinfo->ii_vdir = NULL;
|
|
+ return 0;
|
|
+ }
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+int au_hinode_realloc(struct au_iinfo *iinfo, int nbr, int may_shrink)
|
|
+{
|
|
+ int err, i;
|
|
+ struct au_hinode *hip;
|
|
+
|
|
+ AuRwMustWriteLock(&iinfo->ii_rwsem);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ hip = au_krealloc(iinfo->ii_hinode, sizeof(*hip) * nbr, GFP_NOFS,
|
|
+ may_shrink);
|
|
+ if (hip) {
|
|
+ iinfo->ii_hinode = hip;
|
|
+ i = iinfo->ii_bbot + 1;
|
|
+ hip += i;
|
|
+ for (; i < nbr; i++, hip++)
|
|
+ au_hinode_init(hip);
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_iinfo_fin(struct inode *inode)
|
|
+{
|
|
+ struct au_iinfo *iinfo;
|
|
+ struct au_hinode *hi;
|
|
+ struct super_block *sb;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ const unsigned char unlinked = !inode->i_nlink;
|
|
+
|
|
+ AuDebugOn(au_is_bad_inode(inode));
|
|
+
|
|
+ sb = inode->i_sb;
|
|
+ au_lcnt_dec(&au_sbi(sb)->si_ninodes);
|
|
+ if (si_pid_test(sb))
|
|
+ au_xino_delete_inode(inode, unlinked);
|
|
+ else {
|
|
+ /*
|
|
+ * it is safe to hide the dependency between sbinfo and
|
|
+ * sb->s_umount.
|
|
+ */
|
|
+ lockdep_off();
|
|
+ si_noflush_read_lock(sb);
|
|
+ au_xino_delete_inode(inode, unlinked);
|
|
+ si_read_unlock(sb);
|
|
+ lockdep_on();
|
|
+ }
|
|
+
|
|
+ iinfo = au_ii(inode);
|
|
+ if (iinfo->ii_vdir)
|
|
+ au_vdir_free(iinfo->ii_vdir);
|
|
+
|
|
+ bindex = iinfo->ii_btop;
|
|
+ if (bindex >= 0) {
|
|
+ hi = au_hinode(iinfo, bindex);
|
|
+ bbot = iinfo->ii_bbot;
|
|
+ while (bindex++ <= bbot) {
|
|
+ if (hi->hi_inode)
|
|
+ au_hiput(hi);
|
|
+ hi++;
|
|
+ }
|
|
+ }
|
|
+ au_kfree_rcu(iinfo->ii_hinode);
|
|
+ AuRwDestroy(&iinfo->ii_rwsem);
|
|
+}
|
|
diff --git a/fs/aufs/inode.c b/fs/aufs/inode.c
|
|
new file mode 100644
|
|
index 000000000000..4de1b7507c5b
|
|
--- /dev/null
|
|
+++ b/fs/aufs/inode.c
|
|
@@ -0,0 +1,529 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * inode functions
|
|
+ */
|
|
+
|
|
+#include <linux/iversion.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+struct inode *au_igrab(struct inode *inode)
|
|
+{
|
|
+ if (inode) {
|
|
+ AuDebugOn(!atomic_read(&inode->i_count));
|
|
+ ihold(inode);
|
|
+ }
|
|
+ return inode;
|
|
+}
|
|
+
|
|
+static void au_refresh_hinode_attr(struct inode *inode, int do_version)
|
|
+{
|
|
+ au_cpup_attr_all(inode, /*force*/0);
|
|
+ au_update_iigen(inode, /*half*/1);
|
|
+ if (do_version)
|
|
+ inode_inc_iversion(inode);
|
|
+}
|
|
+
|
|
+static int au_ii_refresh(struct inode *inode, int *update)
|
|
+{
|
|
+ int err, e, nbr;
|
|
+ umode_t type;
|
|
+ aufs_bindex_t bindex, new_bindex;
|
|
+ struct super_block *sb;
|
|
+ struct au_iinfo *iinfo;
|
|
+ struct au_hinode *p, *q, tmp;
|
|
+
|
|
+ AuDebugOn(au_is_bad_inode(inode));
|
|
+ IiMustWriteLock(inode);
|
|
+
|
|
+ *update = 0;
|
|
+ sb = inode->i_sb;
|
|
+ nbr = au_sbbot(sb) + 1;
|
|
+ type = inode->i_mode & S_IFMT;
|
|
+ iinfo = au_ii(inode);
|
|
+ err = au_hinode_realloc(iinfo, nbr, /*may_shrink*/0);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ AuDebugOn(iinfo->ii_btop < 0);
|
|
+ p = au_hinode(iinfo, iinfo->ii_btop);
|
|
+ for (bindex = iinfo->ii_btop; bindex <= iinfo->ii_bbot;
|
|
+ bindex++, p++) {
|
|
+ if (!p->hi_inode)
|
|
+ continue;
|
|
+
|
|
+ AuDebugOn(type != (p->hi_inode->i_mode & S_IFMT));
|
|
+ new_bindex = au_br_index(sb, p->hi_id);
|
|
+ if (new_bindex == bindex)
|
|
+ continue;
|
|
+
|
|
+ if (new_bindex < 0) {
|
|
+ *update = 1;
|
|
+ au_hiput(p);
|
|
+ p->hi_inode = NULL;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (new_bindex < iinfo->ii_btop)
|
|
+ iinfo->ii_btop = new_bindex;
|
|
+ if (iinfo->ii_bbot < new_bindex)
|
|
+ iinfo->ii_bbot = new_bindex;
|
|
+ /* swap two lower inode, and loop again */
|
|
+ q = au_hinode(iinfo, new_bindex);
|
|
+ tmp = *q;
|
|
+ *q = *p;
|
|
+ *p = tmp;
|
|
+ if (tmp.hi_inode) {
|
|
+ bindex--;
|
|
+ p--;
|
|
+ }
|
|
+ }
|
|
+ au_update_ibrange(inode, /*do_put_zero*/0);
|
|
+ au_hinode_realloc(iinfo, nbr, /*may_shrink*/1); /* harmless if err */
|
|
+ e = au_dy_irefresh(inode);
|
|
+ if (unlikely(e && !err))
|
|
+ err = e;
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_refresh_iop(struct inode *inode, int force_getattr)
|
|
+{
|
|
+ int type;
|
|
+ struct au_sbinfo *sbi = au_sbi(inode->i_sb);
|
|
+ const struct inode_operations *iop
|
|
+ = force_getattr ? aufs_iop : sbi->si_iop_array;
|
|
+
|
|
+ if (inode->i_op == iop)
|
|
+ return;
|
|
+
|
|
+ switch (inode->i_mode & S_IFMT) {
|
|
+ case S_IFDIR:
|
|
+ type = AuIop_DIR;
|
|
+ break;
|
|
+ case S_IFLNK:
|
|
+ type = AuIop_SYMLINK;
|
|
+ break;
|
|
+ default:
|
|
+ type = AuIop_OTHER;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ inode->i_op = iop + type;
|
|
+ /* unnecessary smp_wmb() */
|
|
+}
|
|
+
|
|
+int au_refresh_hinode_self(struct inode *inode)
|
|
+{
|
|
+ int err, update;
|
|
+
|
|
+ err = au_ii_refresh(inode, &update);
|
|
+ if (!err)
|
|
+ au_refresh_hinode_attr(inode, update && S_ISDIR(inode->i_mode));
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_refresh_hinode(struct inode *inode, struct dentry *dentry)
|
|
+{
|
|
+ int err, e, update;
|
|
+ unsigned int flags;
|
|
+ umode_t mode;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ unsigned char isdir;
|
|
+ struct au_hinode *p;
|
|
+ struct au_iinfo *iinfo;
|
|
+
|
|
+ err = au_ii_refresh(inode, &update);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ update = 0;
|
|
+ iinfo = au_ii(inode);
|
|
+ p = au_hinode(iinfo, iinfo->ii_btop);
|
|
+ mode = (inode->i_mode & S_IFMT);
|
|
+ isdir = S_ISDIR(mode);
|
|
+ flags = au_hi_flags(inode, isdir);
|
|
+ bbot = au_dbbot(dentry);
|
|
+ for (bindex = au_dbtop(dentry); bindex <= bbot; bindex++) {
|
|
+ struct inode *h_i, *h_inode;
|
|
+ struct dentry *h_d;
|
|
+
|
|
+ h_d = au_h_dptr(dentry, bindex);
|
|
+ if (!h_d || d_is_negative(h_d))
|
|
+ continue;
|
|
+
|
|
+ h_inode = d_inode(h_d);
|
|
+ AuDebugOn(mode != (h_inode->i_mode & S_IFMT));
|
|
+ if (iinfo->ii_btop <= bindex && bindex <= iinfo->ii_bbot) {
|
|
+ h_i = au_h_iptr(inode, bindex);
|
|
+ if (h_i) {
|
|
+ if (h_i == h_inode)
|
|
+ continue;
|
|
+ err = -EIO;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (bindex < iinfo->ii_btop)
|
|
+ iinfo->ii_btop = bindex;
|
|
+ if (iinfo->ii_bbot < bindex)
|
|
+ iinfo->ii_bbot = bindex;
|
|
+ au_set_h_iptr(inode, bindex, au_igrab(h_inode), flags);
|
|
+ update = 1;
|
|
+ }
|
|
+ au_update_ibrange(inode, /*do_put_zero*/0);
|
|
+ e = au_dy_irefresh(inode);
|
|
+ if (unlikely(e && !err))
|
|
+ err = e;
|
|
+ if (!err)
|
|
+ au_refresh_hinode_attr(inode, update && isdir);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int set_inode(struct inode *inode, struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int flags;
|
|
+ umode_t mode;
|
|
+ aufs_bindex_t bindex, btop, btail;
|
|
+ unsigned char isdir;
|
|
+ struct dentry *h_dentry;
|
|
+ struct inode *h_inode;
|
|
+ struct au_iinfo *iinfo;
|
|
+ const struct inode_operations *iop;
|
|
+
|
|
+ IiMustWriteLock(inode);
|
|
+
|
|
+ err = 0;
|
|
+ isdir = 0;
|
|
+ iop = au_sbi(inode->i_sb)->si_iop_array;
|
|
+ btop = au_dbtop(dentry);
|
|
+ h_dentry = au_h_dptr(dentry, btop);
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ mode = h_inode->i_mode;
|
|
+ switch (mode & S_IFMT) {
|
|
+ case S_IFREG:
|
|
+ btail = au_dbtail(dentry);
|
|
+ inode->i_op = iop + AuIop_OTHER;
|
|
+ inode->i_fop = &aufs_file_fop;
|
|
+ err = au_dy_iaop(inode, btop, h_inode);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ break;
|
|
+ case S_IFDIR:
|
|
+ isdir = 1;
|
|
+ btail = au_dbtaildir(dentry);
|
|
+ inode->i_op = iop + AuIop_DIR;
|
|
+ inode->i_fop = &aufs_dir_fop;
|
|
+ break;
|
|
+ case S_IFLNK:
|
|
+ btail = au_dbtail(dentry);
|
|
+ inode->i_op = iop + AuIop_SYMLINK;
|
|
+ break;
|
|
+ case S_IFBLK:
|
|
+ case S_IFCHR:
|
|
+ case S_IFIFO:
|
|
+ case S_IFSOCK:
|
|
+ btail = au_dbtail(dentry);
|
|
+ inode->i_op = iop + AuIop_OTHER;
|
|
+ init_special_inode(inode, mode, h_inode->i_rdev);
|
|
+ break;
|
|
+ default:
|
|
+ AuIOErr("Unknown file type 0%o\n", mode);
|
|
+ err = -EIO;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* do not set hnotify for whiteouted dirs (SHWH mode) */
|
|
+ flags = au_hi_flags(inode, isdir);
|
|
+ if (au_opt_test(au_mntflags(dentry->d_sb), SHWH)
|
|
+ && au_ftest_hi(flags, HNOTIFY)
|
|
+ && dentry->d_name.len > AUFS_WH_PFX_LEN
|
|
+ && !memcmp(dentry->d_name.name, AUFS_WH_PFX, AUFS_WH_PFX_LEN))
|
|
+ au_fclr_hi(flags, HNOTIFY);
|
|
+ iinfo = au_ii(inode);
|
|
+ iinfo->ii_btop = btop;
|
|
+ iinfo->ii_bbot = btail;
|
|
+ for (bindex = btop; bindex <= btail; bindex++) {
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (h_dentry)
|
|
+ au_set_h_iptr(inode, bindex,
|
|
+ au_igrab(d_inode(h_dentry)), flags);
|
|
+ }
|
|
+ au_cpup_attr_all(inode, /*force*/1);
|
|
+ /*
|
|
+ * to force calling aufs_get_acl() every time,
|
|
+ * do not call cache_no_acl() for aufs inode.
|
|
+ */
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * successful returns with iinfo write_locked
|
|
+ * minus: errno
|
|
+ * zero: success, matched
|
|
+ * plus: no error, but unmatched
|
|
+ */
|
|
+static int reval_inode(struct inode *inode, struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int gen, igflags;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct inode *h_inode, *h_dinode;
|
|
+ struct dentry *h_dentry;
|
|
+
|
|
+ /*
|
|
+ * before this function, if aufs got any iinfo lock, it must be only
|
|
+ * one, the parent dir.
|
|
+ * it can happen by UDBA and the obsoleted inode number.
|
|
+ */
|
|
+ err = -EIO;
|
|
+ if (unlikely(inode->i_ino == parent_ino(dentry)))
|
|
+ goto out;
|
|
+
|
|
+ err = 1;
|
|
+ ii_write_lock_new_child(inode);
|
|
+ h_dentry = au_h_dptr(dentry, au_dbtop(dentry));
|
|
+ h_dinode = d_inode(h_dentry);
|
|
+ bbot = au_ibbot(inode);
|
|
+ for (bindex = au_ibtop(inode); bindex <= bbot; bindex++) {
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+ if (!h_inode || h_inode != h_dinode)
|
|
+ continue;
|
|
+
|
|
+ err = 0;
|
|
+ gen = au_iigen(inode, &igflags);
|
|
+ if (gen == au_digen(dentry)
|
|
+ && !au_ig_ftest(igflags, HALF_REFRESHED))
|
|
+ break;
|
|
+
|
|
+ /* fully refresh inode using dentry */
|
|
+ err = au_refresh_hinode(inode, dentry);
|
|
+ if (!err)
|
|
+ au_update_iigen(inode, /*half*/0);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (unlikely(err))
|
|
+ ii_write_unlock(inode);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
|
|
+ unsigned int d_type, ino_t *ino)
|
|
+{
|
|
+ int err, idx;
|
|
+ const int isnondir = d_type != DT_DIR;
|
|
+
|
|
+ /* prevent hardlinked inode number from race condition */
|
|
+ if (isnondir) {
|
|
+ err = au_xinondir_enter(sb, bindex, h_ino, &idx);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = au_xino_read(sb, bindex, h_ino, ino);
|
|
+ if (unlikely(err))
|
|
+ goto out_xinondir;
|
|
+
|
|
+ if (!*ino) {
|
|
+ err = -EIO;
|
|
+ *ino = au_xino_new_ino(sb);
|
|
+ if (unlikely(!*ino))
|
|
+ goto out_xinondir;
|
|
+ err = au_xino_write(sb, bindex, h_ino, *ino);
|
|
+ if (unlikely(err))
|
|
+ goto out_xinondir;
|
|
+ }
|
|
+
|
|
+out_xinondir:
|
|
+ if (isnondir && idx >= 0)
|
|
+ au_xinondir_leave(sb, bindex, h_ino, idx);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* successful returns with iinfo write_locked */
|
|
+/* todo: return with unlocked? */
|
|
+struct inode *au_new_inode(struct dentry *dentry, int must_new)
|
|
+{
|
|
+ struct inode *inode, *h_inode;
|
|
+ struct dentry *h_dentry;
|
|
+ struct super_block *sb;
|
|
+ ino_t h_ino, ino;
|
|
+ int err, idx, hlinked;
|
|
+ aufs_bindex_t btop;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ btop = au_dbtop(dentry);
|
|
+ h_dentry = au_h_dptr(dentry, btop);
|
|
+ h_inode = d_inode(h_dentry);
|
|
+ h_ino = h_inode->i_ino;
|
|
+ hlinked = !d_is_dir(h_dentry) && h_inode->i_nlink > 1;
|
|
+
|
|
+new_ino:
|
|
+ /*
|
|
+ * stop 'race'-ing between hardlinks under different
|
|
+ * parents.
|
|
+ */
|
|
+ if (hlinked) {
|
|
+ err = au_xinondir_enter(sb, btop, h_ino, &idx);
|
|
+ inode = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = au_xino_read(sb, btop, h_ino, &ino);
|
|
+ inode = ERR_PTR(err);
|
|
+ if (unlikely(err))
|
|
+ goto out_xinondir;
|
|
+
|
|
+ if (!ino) {
|
|
+ ino = au_xino_new_ino(sb);
|
|
+ if (unlikely(!ino)) {
|
|
+ inode = ERR_PTR(-EIO);
|
|
+ goto out_xinondir;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ AuDbg("i%lu\n", (unsigned long)ino);
|
|
+ inode = au_iget_locked(sb, ino);
|
|
+ err = PTR_ERR(inode);
|
|
+ if (IS_ERR(inode))
|
|
+ goto out_xinondir;
|
|
+
|
|
+ AuDbg("%lx, new %d\n", inode->i_state, !!(inode->i_state & I_NEW));
|
|
+ if (inode->i_state & I_NEW) {
|
|
+ ii_write_lock_new_child(inode);
|
|
+ err = set_inode(inode, dentry);
|
|
+ if (!err) {
|
|
+ unlock_new_inode(inode);
|
|
+ goto out_xinondir; /* success */
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * iget_failed() calls iput(), but we need to call
|
|
+ * ii_write_unlock() after iget_failed(). so dirty hack for
|
|
+ * i_count.
|
|
+ */
|
|
+ atomic_inc(&inode->i_count);
|
|
+ iget_failed(inode);
|
|
+ ii_write_unlock(inode);
|
|
+ au_xino_write(sb, btop, h_ino, /*ino*/0);
|
|
+ /* ignore this error */
|
|
+ goto out_iput;
|
|
+ } else if (!must_new && !IS_DEADDIR(inode) && inode->i_nlink) {
|
|
+ /*
|
|
+ * horrible race condition between lookup, readdir and copyup
|
|
+ * (or something).
|
|
+ */
|
|
+ if (hlinked && idx >= 0)
|
|
+ au_xinondir_leave(sb, btop, h_ino, idx);
|
|
+ err = reval_inode(inode, dentry);
|
|
+ if (unlikely(err < 0)) {
|
|
+ hlinked = 0;
|
|
+ goto out_iput;
|
|
+ }
|
|
+ if (!err)
|
|
+ goto out; /* success */
|
|
+ else if (hlinked && idx >= 0) {
|
|
+ err = au_xinondir_enter(sb, btop, h_ino, &idx);
|
|
+ if (unlikely(err)) {
|
|
+ iput(inode);
|
|
+ inode = ERR_PTR(err);
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (unlikely(au_test_fs_unique_ino(h_inode)))
|
|
+ AuWarn1("Warning: Un-notified UDBA or repeatedly renamed dir,"
|
|
+ " b%d, %s, %pd, hi%lu, i%lu.\n",
|
|
+ btop, au_sbtype(h_dentry->d_sb), dentry,
|
|
+ (unsigned long)h_ino, (unsigned long)ino);
|
|
+ ino = 0;
|
|
+ err = au_xino_write(sb, btop, h_ino, /*ino*/0);
|
|
+ if (!err) {
|
|
+ iput(inode);
|
|
+ if (hlinked && idx >= 0)
|
|
+ au_xinondir_leave(sb, btop, h_ino, idx);
|
|
+ goto new_ino;
|
|
+ }
|
|
+
|
|
+out_iput:
|
|
+ iput(inode);
|
|
+ inode = ERR_PTR(err);
|
|
+out_xinondir:
|
|
+ if (hlinked && idx >= 0)
|
|
+ au_xinondir_leave(sb, btop, h_ino, idx);
|
|
+out:
|
|
+ return inode;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_test_ro(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ struct inode *inode)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *hi;
|
|
+
|
|
+ err = au_br_rdonly(au_sbr(sb, bindex));
|
|
+
|
|
+ /* pseudo-link after flushed may happen out of bounds */
|
|
+ if (!err
|
|
+ && inode
|
|
+ && au_ibtop(inode) <= bindex
|
|
+ && bindex <= au_ibbot(inode)) {
|
|
+ /*
|
|
+ * permission check is unnecessary since vfsub routine
|
|
+ * will be called later
|
|
+ */
|
|
+ hi = au_h_iptr(inode, bindex);
|
|
+ if (hi)
|
|
+ err = IS_IMMUTABLE(hi) ? -EROFS : 0;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_test_h_perm(struct inode *h_inode, int mask)
|
|
+{
|
|
+ if (uid_eq(current_fsuid(), GLOBAL_ROOT_UID))
|
|
+ return 0;
|
|
+ return inode_permission(h_inode, mask);
|
|
+}
|
|
+
|
|
+int au_test_h_perm_sio(struct inode *h_inode, int mask)
|
|
+{
|
|
+ if (au_test_nfs(h_inode->i_sb)
|
|
+ && (mask & MAY_WRITE)
|
|
+ && S_ISDIR(h_inode->i_mode))
|
|
+ mask |= MAY_READ; /* force permission check */
|
|
+ return au_test_h_perm(h_inode, mask);
|
|
+}
|
|
diff --git a/fs/aufs/inode.h b/fs/aufs/inode.h
|
|
new file mode 100644
|
|
index 000000000000..af32a233200e
|
|
--- /dev/null
|
|
+++ b/fs/aufs/inode.h
|
|
@@ -0,0 +1,698 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * inode operations
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_INODE_H__
|
|
+#define __AUFS_INODE_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/fsnotify.h>
|
|
+#include "rwsem.h"
|
|
+
|
|
+struct vfsmount;
|
|
+
|
|
+struct au_hnotify {
|
|
+#ifdef CONFIG_AUFS_HNOTIFY
|
|
+#ifdef CONFIG_AUFS_HFSNOTIFY
|
|
+ /* never use fsnotify_add_vfsmount_mark() */
|
|
+ struct fsnotify_mark hn_mark;
|
|
+#endif
|
|
+ struct inode *hn_aufs_inode; /* no get/put */
|
|
+ struct rcu_head rcu;
|
|
+#endif
|
|
+} ____cacheline_aligned_in_smp;
|
|
+
|
|
+struct au_hinode {
|
|
+ struct inode *hi_inode;
|
|
+ aufs_bindex_t hi_id;
|
|
+#ifdef CONFIG_AUFS_HNOTIFY
|
|
+ struct au_hnotify *hi_notify;
|
|
+#endif
|
|
+
|
|
+ /* reference to the copied-up whiteout with get/put */
|
|
+ struct dentry *hi_whdentry;
|
|
+};
|
|
+
|
|
+/* ig_flags */
|
|
+#define AuIG_HALF_REFRESHED 1
|
|
+#define au_ig_ftest(flags, name) ((flags) & AuIG_##name)
|
|
+#define au_ig_fset(flags, name) \
|
|
+ do { (flags) |= AuIG_##name; } while (0)
|
|
+#define au_ig_fclr(flags, name) \
|
|
+ do { (flags) &= ~AuIG_##name; } while (0)
|
|
+
|
|
+struct au_iigen {
|
|
+ spinlock_t ig_spin;
|
|
+ __u32 ig_generation, ig_flags;
|
|
+};
|
|
+
|
|
+struct au_vdir;
|
|
+struct au_iinfo {
|
|
+ struct au_iigen ii_generation;
|
|
+ struct super_block *ii_hsb1; /* no get/put */
|
|
+
|
|
+ struct au_rwsem ii_rwsem;
|
|
+ aufs_bindex_t ii_btop, ii_bbot;
|
|
+ __u32 ii_higen;
|
|
+ struct au_hinode *ii_hinode;
|
|
+ struct au_vdir *ii_vdir;
|
|
+};
|
|
+
|
|
+struct au_icntnr {
|
|
+ struct au_iinfo iinfo;
|
|
+ struct inode vfs_inode;
|
|
+ struct hlist_bl_node plink;
|
|
+ struct rcu_head rcu;
|
|
+} ____cacheline_aligned_in_smp;
|
|
+
|
|
+/* au_pin flags */
|
|
+#define AuPin_DI_LOCKED 1
|
|
+#define AuPin_MNT_WRITE (1 << 1)
|
|
+#define au_ftest_pin(flags, name) ((flags) & AuPin_##name)
|
|
+#define au_fset_pin(flags, name) \
|
|
+ do { (flags) |= AuPin_##name; } while (0)
|
|
+#define au_fclr_pin(flags, name) \
|
|
+ do { (flags) &= ~AuPin_##name; } while (0)
|
|
+
|
|
+struct au_pin {
|
|
+ /* input */
|
|
+ struct dentry *dentry;
|
|
+ unsigned int udba;
|
|
+ unsigned char lsc_di, lsc_hi, flags;
|
|
+ aufs_bindex_t bindex;
|
|
+
|
|
+ /* output */
|
|
+ struct dentry *parent;
|
|
+ struct au_hinode *hdir;
|
|
+ struct vfsmount *h_mnt;
|
|
+
|
|
+ /* temporary unlock/relock for copyup */
|
|
+ struct dentry *h_dentry, *h_parent;
|
|
+ struct au_branch *br;
|
|
+ struct task_struct *task;
|
|
+};
|
|
+
|
|
+void au_pin_hdir_unlock(struct au_pin *p);
|
|
+int au_pin_hdir_lock(struct au_pin *p);
|
|
+int au_pin_hdir_relock(struct au_pin *p);
|
|
+void au_pin_hdir_acquire_nest(struct au_pin *p);
|
|
+void au_pin_hdir_release(struct au_pin *p);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline struct au_iinfo *au_ii(struct inode *inode)
|
|
+{
|
|
+ BUG_ON(is_bad_inode(inode));
|
|
+ return &(container_of(inode, struct au_icntnr, vfs_inode)->iinfo);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* inode.c */
|
|
+struct inode *au_igrab(struct inode *inode);
|
|
+void au_refresh_iop(struct inode *inode, int force_getattr);
|
|
+int au_refresh_hinode_self(struct inode *inode);
|
|
+int au_refresh_hinode(struct inode *inode, struct dentry *dentry);
|
|
+int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
|
|
+ unsigned int d_type, ino_t *ino);
|
|
+struct inode *au_new_inode(struct dentry *dentry, int must_new);
|
|
+int au_test_ro(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ struct inode *inode);
|
|
+int au_test_h_perm(struct inode *h_inode, int mask);
|
|
+int au_test_h_perm_sio(struct inode *h_inode, int mask);
|
|
+
|
|
+static inline int au_wh_ino(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ ino_t h_ino, unsigned int d_type, ino_t *ino)
|
|
+{
|
|
+#ifdef CONFIG_AUFS_SHWH
|
|
+ return au_ino(sb, bindex, h_ino, d_type, ino);
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+/* i_op.c */
|
|
+enum {
|
|
+ AuIop_SYMLINK,
|
|
+ AuIop_DIR,
|
|
+ AuIop_OTHER,
|
|
+ AuIop_Last
|
|
+};
|
|
+extern struct inode_operations aufs_iop[AuIop_Last], /* not const */
|
|
+ aufs_iop_nogetattr[AuIop_Last];
|
|
+
|
|
+/* au_wr_dir flags */
|
|
+#define AuWrDir_ADD_ENTRY 1
|
|
+#define AuWrDir_ISDIR (1 << 1)
|
|
+#define AuWrDir_TMPFILE (1 << 2)
|
|
+#define au_ftest_wrdir(flags, name) ((flags) & AuWrDir_##name)
|
|
+#define au_fset_wrdir(flags, name) \
|
|
+ do { (flags) |= AuWrDir_##name; } while (0)
|
|
+#define au_fclr_wrdir(flags, name) \
|
|
+ do { (flags) &= ~AuWrDir_##name; } while (0)
|
|
+
|
|
+struct au_wr_dir_args {
|
|
+ aufs_bindex_t force_btgt;
|
|
+ unsigned char flags;
|
|
+};
|
|
+int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
|
|
+ struct au_wr_dir_args *args);
|
|
+
|
|
+struct dentry *au_pinned_h_parent(struct au_pin *pin);
|
|
+void au_pin_init(struct au_pin *pin, struct dentry *dentry,
|
|
+ aufs_bindex_t bindex, int lsc_di, int lsc_hi,
|
|
+ unsigned int udba, unsigned char flags);
|
|
+int au_pin(struct au_pin *pin, struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ unsigned int udba, unsigned char flags) __must_check;
|
|
+int au_do_pin(struct au_pin *pin) __must_check;
|
|
+void au_unpin(struct au_pin *pin);
|
|
+int au_reval_for_attr(struct dentry *dentry, unsigned int sigen);
|
|
+
|
|
+#define AuIcpup_DID_CPUP 1
|
|
+#define au_ftest_icpup(flags, name) ((flags) & AuIcpup_##name)
|
|
+#define au_fset_icpup(flags, name) \
|
|
+ do { (flags) |= AuIcpup_##name; } while (0)
|
|
+#define au_fclr_icpup(flags, name) \
|
|
+ do { (flags) &= ~AuIcpup_##name; } while (0)
|
|
+
|
|
+struct au_icpup_args {
|
|
+ unsigned char flags;
|
|
+ unsigned char pin_flags;
|
|
+ aufs_bindex_t btgt;
|
|
+ unsigned int udba;
|
|
+ struct au_pin pin;
|
|
+ struct path h_path;
|
|
+ struct inode *h_inode;
|
|
+};
|
|
+
|
|
+int au_pin_and_icpup(struct dentry *dentry, struct iattr *ia,
|
|
+ struct au_icpup_args *a);
|
|
+
|
|
+int au_h_path_getattr(struct dentry *dentry, struct inode *inode, int force,
|
|
+ struct path *h_path, int locked);
|
|
+
|
|
+/* i_op_add.c */
|
|
+int au_may_add(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct dentry *h_parent, int isdir);
|
|
+int aufs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
|
|
+ dev_t dev);
|
|
+int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname);
|
|
+int aufs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
|
|
+ bool want_excl);
|
|
+struct vfsub_aopen_args;
|
|
+int au_aopen_or_create(struct inode *dir, struct dentry *dentry,
|
|
+ struct vfsub_aopen_args *args);
|
|
+int aufs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode);
|
|
+int aufs_link(struct dentry *src_dentry, struct inode *dir,
|
|
+ struct dentry *dentry);
|
|
+int aufs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode);
|
|
+
|
|
+/* i_op_del.c */
|
|
+int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup);
|
|
+int au_may_del(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct dentry *h_parent, int isdir);
|
|
+int aufs_unlink(struct inode *dir, struct dentry *dentry);
|
|
+int aufs_rmdir(struct inode *dir, struct dentry *dentry);
|
|
+
|
|
+/* i_op_ren.c */
|
|
+int au_wbr(struct dentry *dentry, aufs_bindex_t btgt);
|
|
+int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
|
|
+ struct inode *dir, struct dentry *dentry,
|
|
+ unsigned int flags);
|
|
+
|
|
+/* iinfo.c */
|
|
+struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex);
|
|
+void au_hiput(struct au_hinode *hinode);
|
|
+void au_set_hi_wh(struct inode *inode, aufs_bindex_t bindex,
|
|
+ struct dentry *h_wh);
|
|
+unsigned int au_hi_flags(struct inode *inode, int isdir);
|
|
+
|
|
+/* hinode flags */
|
|
+#define AuHi_XINO 1
|
|
+#define AuHi_HNOTIFY (1 << 1)
|
|
+#define au_ftest_hi(flags, name) ((flags) & AuHi_##name)
|
|
+#define au_fset_hi(flags, name) \
|
|
+ do { (flags) |= AuHi_##name; } while (0)
|
|
+#define au_fclr_hi(flags, name) \
|
|
+ do { (flags) &= ~AuHi_##name; } while (0)
|
|
+
|
|
+#ifndef CONFIG_AUFS_HNOTIFY
|
|
+#undef AuHi_HNOTIFY
|
|
+#define AuHi_HNOTIFY 0
|
|
+#endif
|
|
+
|
|
+void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex,
|
|
+ struct inode *h_inode, unsigned int flags);
|
|
+
|
|
+void au_update_iigen(struct inode *inode, int half);
|
|
+void au_update_ibrange(struct inode *inode, int do_put_zero);
|
|
+
|
|
+void au_icntnr_init_once(void *_c);
|
|
+void au_hinode_init(struct au_hinode *hinode);
|
|
+int au_iinfo_init(struct inode *inode);
|
|
+void au_iinfo_fin(struct inode *inode);
|
|
+int au_hinode_realloc(struct au_iinfo *iinfo, int nbr, int may_shrink);
|
|
+
|
|
+#ifdef CONFIG_PROC_FS
|
|
+/* plink.c */
|
|
+int au_plink_maint(struct super_block *sb, int flags);
|
|
+struct au_sbinfo;
|
|
+void au_plink_maint_leave(struct au_sbinfo *sbinfo);
|
|
+int au_plink_maint_enter(struct super_block *sb);
|
|
+#ifdef CONFIG_AUFS_DEBUG
|
|
+void au_plink_list(struct super_block *sb);
|
|
+#else
|
|
+AuStubVoid(au_plink_list, struct super_block *sb)
|
|
+#endif
|
|
+int au_plink_test(struct inode *inode);
|
|
+struct dentry *au_plink_lkup(struct inode *inode, aufs_bindex_t bindex);
|
|
+void au_plink_append(struct inode *inode, aufs_bindex_t bindex,
|
|
+ struct dentry *h_dentry);
|
|
+void au_plink_put(struct super_block *sb, int verbose);
|
|
+void au_plink_clean(struct super_block *sb, int verbose);
|
|
+void au_plink_half_refresh(struct super_block *sb, aufs_bindex_t br_id);
|
|
+#else
|
|
+AuStubInt0(au_plink_maint, struct super_block *sb, int flags);
|
|
+AuStubVoid(au_plink_maint_leave, struct au_sbinfo *sbinfo);
|
|
+AuStubInt0(au_plink_maint_enter, struct super_block *sb);
|
|
+AuStubVoid(au_plink_list, struct super_block *sb);
|
|
+AuStubInt0(au_plink_test, struct inode *inode);
|
|
+AuStub(struct dentry *, au_plink_lkup, return NULL,
|
|
+ struct inode *inode, aufs_bindex_t bindex);
|
|
+AuStubVoid(au_plink_append, struct inode *inode, aufs_bindex_t bindex,
|
|
+ struct dentry *h_dentry);
|
|
+AuStubVoid(au_plink_put, struct super_block *sb, int verbose);
|
|
+AuStubVoid(au_plink_clean, struct super_block *sb, int verbose);
|
|
+AuStubVoid(au_plink_half_refresh, struct super_block *sb, aufs_bindex_t br_id);
|
|
+#endif /* CONFIG_PROC_FS */
|
|
+
|
|
+#ifdef CONFIG_AUFS_XATTR
|
|
+/* xattr.c */
|
|
+int au_cpup_xattr(struct dentry *h_dst, struct dentry *h_src, int ignore_flags,
|
|
+ unsigned int verbose);
|
|
+ssize_t aufs_listxattr(struct dentry *dentry, char *list, size_t size);
|
|
+void au_xattr_init(struct super_block *sb);
|
|
+#else
|
|
+AuStubInt0(au_cpup_xattr, struct dentry *h_dst, struct dentry *h_src,
|
|
+ int ignore_flags, unsigned int verbose);
|
|
+AuStubVoid(au_xattr_init, struct super_block *sb);
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_FS_POSIX_ACL
|
|
+struct posix_acl *aufs_get_acl(struct inode *inode, int type);
|
|
+int aufs_set_acl(struct inode *inode, struct posix_acl *acl, int type);
|
|
+#endif
|
|
+
|
|
+#if IS_ENABLED(CONFIG_AUFS_XATTR) || IS_ENABLED(CONFIG_FS_POSIX_ACL)
|
|
+enum {
|
|
+ AU_XATTR_SET,
|
|
+ AU_ACL_SET
|
|
+};
|
|
+
|
|
+struct au_sxattr {
|
|
+ int type;
|
|
+ union {
|
|
+ struct {
|
|
+ const char *name;
|
|
+ const void *value;
|
|
+ size_t size;
|
|
+ int flags;
|
|
+ } set;
|
|
+ struct {
|
|
+ struct posix_acl *acl;
|
|
+ int type;
|
|
+ } acl_set;
|
|
+ } u;
|
|
+};
|
|
+ssize_t au_sxattr(struct dentry *dentry, struct inode *inode,
|
|
+ struct au_sxattr *arg);
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* lock subclass for iinfo */
|
|
+enum {
|
|
+ AuLsc_II_CHILD, /* child first */
|
|
+ AuLsc_II_CHILD2, /* rename(2), link(2), and cpup at hnotify */
|
|
+ AuLsc_II_CHILD3, /* copyup dirs */
|
|
+ AuLsc_II_PARENT, /* see AuLsc_I_PARENT in vfsub.h */
|
|
+ AuLsc_II_PARENT2,
|
|
+ AuLsc_II_PARENT3, /* copyup dirs */
|
|
+ AuLsc_II_NEW_CHILD
|
|
+};
|
|
+
|
|
+/*
|
|
+ * ii_read_lock_child, ii_write_lock_child,
|
|
+ * ii_read_lock_child2, ii_write_lock_child2,
|
|
+ * ii_read_lock_child3, ii_write_lock_child3,
|
|
+ * ii_read_lock_parent, ii_write_lock_parent,
|
|
+ * ii_read_lock_parent2, ii_write_lock_parent2,
|
|
+ * ii_read_lock_parent3, ii_write_lock_parent3,
|
|
+ * ii_read_lock_new_child, ii_write_lock_new_child,
|
|
+ */
|
|
+#define AuReadLockFunc(name, lsc) \
|
|
+static inline void ii_read_lock_##name(struct inode *i) \
|
|
+{ \
|
|
+ au_rw_read_lock_nested(&au_ii(i)->ii_rwsem, AuLsc_II_##lsc); \
|
|
+}
|
|
+
|
|
+#define AuWriteLockFunc(name, lsc) \
|
|
+static inline void ii_write_lock_##name(struct inode *i) \
|
|
+{ \
|
|
+ au_rw_write_lock_nested(&au_ii(i)->ii_rwsem, AuLsc_II_##lsc); \
|
|
+}
|
|
+
|
|
+#define AuRWLockFuncs(name, lsc) \
|
|
+ AuReadLockFunc(name, lsc) \
|
|
+ AuWriteLockFunc(name, lsc)
|
|
+
|
|
+AuRWLockFuncs(child, CHILD);
|
|
+AuRWLockFuncs(child2, CHILD2);
|
|
+AuRWLockFuncs(child3, CHILD3);
|
|
+AuRWLockFuncs(parent, PARENT);
|
|
+AuRWLockFuncs(parent2, PARENT2);
|
|
+AuRWLockFuncs(parent3, PARENT3);
|
|
+AuRWLockFuncs(new_child, NEW_CHILD);
|
|
+
|
|
+#undef AuReadLockFunc
|
|
+#undef AuWriteLockFunc
|
|
+#undef AuRWLockFuncs
|
|
+
|
|
+#define ii_read_unlock(i) au_rw_read_unlock(&au_ii(i)->ii_rwsem)
|
|
+#define ii_write_unlock(i) au_rw_write_unlock(&au_ii(i)->ii_rwsem)
|
|
+#define ii_downgrade_lock(i) au_rw_dgrade_lock(&au_ii(i)->ii_rwsem)
|
|
+
|
|
+#define IiMustNoWaiters(i) AuRwMustNoWaiters(&au_ii(i)->ii_rwsem)
|
|
+#define IiMustAnyLock(i) AuRwMustAnyLock(&au_ii(i)->ii_rwsem)
|
|
+#define IiMustWriteLock(i) AuRwMustWriteLock(&au_ii(i)->ii_rwsem)
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline void au_icntnr_init(struct au_icntnr *c)
|
|
+{
|
|
+#ifdef CONFIG_AUFS_DEBUG
|
|
+ c->vfs_inode.i_mode = 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline unsigned int au_iigen(struct inode *inode, unsigned int *igflags)
|
|
+{
|
|
+ unsigned int gen;
|
|
+ struct au_iinfo *iinfo;
|
|
+ struct au_iigen *iigen;
|
|
+
|
|
+ iinfo = au_ii(inode);
|
|
+ iigen = &iinfo->ii_generation;
|
|
+ spin_lock(&iigen->ig_spin);
|
|
+ if (igflags)
|
|
+ *igflags = iigen->ig_flags;
|
|
+ gen = iigen->ig_generation;
|
|
+ spin_unlock(&iigen->ig_spin);
|
|
+
|
|
+ return gen;
|
|
+}
|
|
+
|
|
+/* tiny test for inode number */
|
|
+/* tmpfs generation is too rough */
|
|
+static inline int au_test_higen(struct inode *inode, struct inode *h_inode)
|
|
+{
|
|
+ struct au_iinfo *iinfo;
|
|
+
|
|
+ iinfo = au_ii(inode);
|
|
+ AuRwMustAnyLock(&iinfo->ii_rwsem);
|
|
+ return !(iinfo->ii_hsb1 == h_inode->i_sb
|
|
+ && iinfo->ii_higen == h_inode->i_generation);
|
|
+}
|
|
+
|
|
+static inline void au_iigen_dec(struct inode *inode)
|
|
+{
|
|
+ struct au_iinfo *iinfo;
|
|
+ struct au_iigen *iigen;
|
|
+
|
|
+ iinfo = au_ii(inode);
|
|
+ iigen = &iinfo->ii_generation;
|
|
+ spin_lock(&iigen->ig_spin);
|
|
+ iigen->ig_generation--;
|
|
+ spin_unlock(&iigen->ig_spin);
|
|
+}
|
|
+
|
|
+static inline int au_iigen_test(struct inode *inode, unsigned int sigen)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (unlikely(inode && au_iigen(inode, NULL) != sigen))
|
|
+ err = -EIO;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline struct au_hinode *au_hinode(struct au_iinfo *iinfo,
|
|
+ aufs_bindex_t bindex)
|
|
+{
|
|
+ return iinfo->ii_hinode + bindex;
|
|
+}
|
|
+
|
|
+static inline int au_is_bad_inode(struct inode *inode)
|
|
+{
|
|
+ return !!(is_bad_inode(inode) || !au_hinode(au_ii(inode), 0));
|
|
+}
|
|
+
|
|
+static inline aufs_bindex_t au_ii_br_id(struct inode *inode,
|
|
+ aufs_bindex_t bindex)
|
|
+{
|
|
+ IiMustAnyLock(inode);
|
|
+ return au_hinode(au_ii(inode), bindex)->hi_id;
|
|
+}
|
|
+
|
|
+static inline aufs_bindex_t au_ibtop(struct inode *inode)
|
|
+{
|
|
+ IiMustAnyLock(inode);
|
|
+ return au_ii(inode)->ii_btop;
|
|
+}
|
|
+
|
|
+static inline aufs_bindex_t au_ibbot(struct inode *inode)
|
|
+{
|
|
+ IiMustAnyLock(inode);
|
|
+ return au_ii(inode)->ii_bbot;
|
|
+}
|
|
+
|
|
+static inline struct au_vdir *au_ivdir(struct inode *inode)
|
|
+{
|
|
+ IiMustAnyLock(inode);
|
|
+ return au_ii(inode)->ii_vdir;
|
|
+}
|
|
+
|
|
+static inline struct dentry *au_hi_wh(struct inode *inode, aufs_bindex_t bindex)
|
|
+{
|
|
+ IiMustAnyLock(inode);
|
|
+ return au_hinode(au_ii(inode), bindex)->hi_whdentry;
|
|
+}
|
|
+
|
|
+static inline void au_set_ibtop(struct inode *inode, aufs_bindex_t bindex)
|
|
+{
|
|
+ IiMustWriteLock(inode);
|
|
+ au_ii(inode)->ii_btop = bindex;
|
|
+}
|
|
+
|
|
+static inline void au_set_ibbot(struct inode *inode, aufs_bindex_t bindex)
|
|
+{
|
|
+ IiMustWriteLock(inode);
|
|
+ au_ii(inode)->ii_bbot = bindex;
|
|
+}
|
|
+
|
|
+static inline void au_set_ivdir(struct inode *inode, struct au_vdir *vdir)
|
|
+{
|
|
+ IiMustWriteLock(inode);
|
|
+ au_ii(inode)->ii_vdir = vdir;
|
|
+}
|
|
+
|
|
+static inline struct au_hinode *au_hi(struct inode *inode, aufs_bindex_t bindex)
|
|
+{
|
|
+ IiMustAnyLock(inode);
|
|
+ return au_hinode(au_ii(inode), bindex);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline struct dentry *au_pinned_parent(struct au_pin *pin)
|
|
+{
|
|
+ if (pin)
|
|
+ return pin->parent;
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static inline struct inode *au_pinned_h_dir(struct au_pin *pin)
|
|
+{
|
|
+ if (pin && pin->hdir)
|
|
+ return pin->hdir->hi_inode;
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static inline struct au_hinode *au_pinned_hdir(struct au_pin *pin)
|
|
+{
|
|
+ if (pin)
|
|
+ return pin->hdir;
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static inline void au_pin_set_dentry(struct au_pin *pin, struct dentry *dentry)
|
|
+{
|
|
+ if (pin)
|
|
+ pin->dentry = dentry;
|
|
+}
|
|
+
|
|
+static inline void au_pin_set_parent_lflag(struct au_pin *pin,
|
|
+ unsigned char lflag)
|
|
+{
|
|
+ if (pin) {
|
|
+ if (lflag)
|
|
+ au_fset_pin(pin->flags, DI_LOCKED);
|
|
+ else
|
|
+ au_fclr_pin(pin->flags, DI_LOCKED);
|
|
+ }
|
|
+}
|
|
+
|
|
+#if 0 /* reserved */
|
|
+static inline void au_pin_set_parent(struct au_pin *pin, struct dentry *parent)
|
|
+{
|
|
+ if (pin) {
|
|
+ dput(pin->parent);
|
|
+ pin->parent = dget(parent);
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_branch;
|
|
+#ifdef CONFIG_AUFS_HNOTIFY
|
|
+struct au_hnotify_op {
|
|
+ void (*ctl)(struct au_hinode *hinode, int do_set);
|
|
+ int (*alloc)(struct au_hinode *hinode);
|
|
+
|
|
+ /*
|
|
+ * if it returns true, the the caller should free hinode->hi_notify,
|
|
+ * otherwise ->free() frees it.
|
|
+ */
|
|
+ int (*free)(struct au_hinode *hinode,
|
|
+ struct au_hnotify *hn) __must_check;
|
|
+
|
|
+ void (*fin)(void);
|
|
+ int (*init)(void);
|
|
+
|
|
+ int (*reset_br)(unsigned int udba, struct au_branch *br, int perm);
|
|
+ void (*fin_br)(struct au_branch *br);
|
|
+ int (*init_br)(struct au_branch *br, int perm);
|
|
+};
|
|
+
|
|
+/* hnotify.c */
|
|
+int au_hn_alloc(struct au_hinode *hinode, struct inode *inode);
|
|
+void au_hn_free(struct au_hinode *hinode);
|
|
+void au_hn_ctl(struct au_hinode *hinode, int do_set);
|
|
+void au_hn_reset(struct inode *inode, unsigned int flags);
|
|
+int au_hnotify(struct inode *h_dir, struct au_hnotify *hnotify, u32 mask,
|
|
+ const struct qstr *h_child_qstr, struct inode *h_child_inode);
|
|
+int au_hnotify_reset_br(unsigned int udba, struct au_branch *br, int perm);
|
|
+int au_hnotify_init_br(struct au_branch *br, int perm);
|
|
+void au_hnotify_fin_br(struct au_branch *br);
|
|
+int __init au_hnotify_init(void);
|
|
+void au_hnotify_fin(void);
|
|
+
|
|
+/* hfsnotify.c */
|
|
+extern const struct au_hnotify_op au_hnotify_op;
|
|
+
|
|
+static inline
|
|
+void au_hn_init(struct au_hinode *hinode)
|
|
+{
|
|
+ hinode->hi_notify = NULL;
|
|
+}
|
|
+
|
|
+static inline struct au_hnotify *au_hn(struct au_hinode *hinode)
|
|
+{
|
|
+ return hinode->hi_notify;
|
|
+}
|
|
+
|
|
+#else
|
|
+AuStub(int, au_hn_alloc, return -EOPNOTSUPP,
|
|
+ struct au_hinode *hinode __maybe_unused,
|
|
+ struct inode *inode __maybe_unused)
|
|
+AuStub(struct au_hnotify *, au_hn, return NULL, struct au_hinode *hinode)
|
|
+AuStubVoid(au_hn_free, struct au_hinode *hinode __maybe_unused)
|
|
+AuStubVoid(au_hn_ctl, struct au_hinode *hinode __maybe_unused,
|
|
+ int do_set __maybe_unused)
|
|
+AuStubVoid(au_hn_reset, struct inode *inode __maybe_unused,
|
|
+ unsigned int flags __maybe_unused)
|
|
+AuStubInt0(au_hnotify_reset_br, unsigned int udba __maybe_unused,
|
|
+ struct au_branch *br __maybe_unused,
|
|
+ int perm __maybe_unused)
|
|
+AuStubInt0(au_hnotify_init_br, struct au_branch *br __maybe_unused,
|
|
+ int perm __maybe_unused)
|
|
+AuStubVoid(au_hnotify_fin_br, struct au_branch *br __maybe_unused)
|
|
+AuStubInt0(__init au_hnotify_init, void)
|
|
+AuStubVoid(au_hnotify_fin, void)
|
|
+AuStubVoid(au_hn_init, struct au_hinode *hinode __maybe_unused)
|
|
+#endif /* CONFIG_AUFS_HNOTIFY */
|
|
+
|
|
+static inline void au_hn_suspend(struct au_hinode *hdir)
|
|
+{
|
|
+ au_hn_ctl(hdir, /*do_set*/0);
|
|
+}
|
|
+
|
|
+static inline void au_hn_resume(struct au_hinode *hdir)
|
|
+{
|
|
+ au_hn_ctl(hdir, /*do_set*/1);
|
|
+}
|
|
+
|
|
+static inline void au_hn_inode_lock(struct au_hinode *hdir)
|
|
+{
|
|
+ inode_lock(hdir->hi_inode);
|
|
+ au_hn_suspend(hdir);
|
|
+}
|
|
+
|
|
+static inline void au_hn_inode_lock_nested(struct au_hinode *hdir,
|
|
+ unsigned int sc __maybe_unused)
|
|
+{
|
|
+ inode_lock_nested(hdir->hi_inode, sc);
|
|
+ au_hn_suspend(hdir);
|
|
+}
|
|
+
|
|
+#if 0 /* unused */
|
|
+#include "vfsub.h"
|
|
+static inline void au_hn_inode_lock_shared_nested(struct au_hinode *hdir,
|
|
+ unsigned int sc)
|
|
+{
|
|
+ inode_lock_shared_nested(hdir->hi_inode, sc);
|
|
+ au_hn_suspend(hdir);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static inline void au_hn_inode_unlock(struct au_hinode *hdir)
|
|
+{
|
|
+ au_hn_resume(hdir);
|
|
+ inode_unlock(hdir->hi_inode);
|
|
+}
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_INODE_H__ */
|
|
diff --git a/fs/aufs/ioctl.c b/fs/aufs/ioctl.c
|
|
new file mode 100644
|
|
index 000000000000..ae4f73386b90
|
|
--- /dev/null
|
|
+++ b/fs/aufs/ioctl.c
|
|
@@ -0,0 +1,220 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * ioctl
|
|
+ * plink-management and readdir in userspace.
|
|
+ * assist the pathconf(3) wrapper library.
|
|
+ * move-down
|
|
+ * File-based Hierarchical Storage Management.
|
|
+ */
|
|
+
|
|
+#include <linux/compat.h>
|
|
+#include <linux/file.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+static int au_wbr_fd(struct path *path, struct aufs_wbr_fd __user *arg)
|
|
+{
|
|
+ int err, fd;
|
|
+ aufs_bindex_t wbi, bindex, bbot;
|
|
+ struct file *h_file;
|
|
+ struct super_block *sb;
|
|
+ struct dentry *root;
|
|
+ struct au_branch *br;
|
|
+ struct aufs_wbr_fd wbrfd = {
|
|
+ .oflags = au_dir_roflags,
|
|
+ .brid = -1
|
|
+ };
|
|
+ const int valid = O_RDONLY | O_NONBLOCK | O_LARGEFILE | O_DIRECTORY
|
|
+ | O_NOATIME | O_CLOEXEC;
|
|
+
|
|
+ AuDebugOn(wbrfd.oflags & ~valid);
|
|
+
|
|
+ if (arg) {
|
|
+ err = copy_from_user(&wbrfd, arg, sizeof(wbrfd));
|
|
+ if (unlikely(err)) {
|
|
+ err = -EFAULT;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = -EINVAL;
|
|
+ AuDbg("wbrfd{0%o, %d}\n", wbrfd.oflags, wbrfd.brid);
|
|
+ wbrfd.oflags |= au_dir_roflags;
|
|
+ AuDbg("0%o\n", wbrfd.oflags);
|
|
+ if (unlikely(wbrfd.oflags & ~valid))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ fd = get_unused_fd_flags(0);
|
|
+ err = fd;
|
|
+ if (unlikely(fd < 0))
|
|
+ goto out;
|
|
+
|
|
+ h_file = ERR_PTR(-EINVAL);
|
|
+ wbi = 0;
|
|
+ br = NULL;
|
|
+ sb = path->dentry->d_sb;
|
|
+ root = sb->s_root;
|
|
+ aufs_read_lock(root, AuLock_IR);
|
|
+ bbot = au_sbbot(sb);
|
|
+ if (wbrfd.brid >= 0) {
|
|
+ wbi = au_br_index(sb, wbrfd.brid);
|
|
+ if (unlikely(wbi < 0 || wbi > bbot))
|
|
+ goto out_unlock;
|
|
+ }
|
|
+
|
|
+ h_file = ERR_PTR(-ENOENT);
|
|
+ br = au_sbr(sb, wbi);
|
|
+ if (!au_br_writable(br->br_perm)) {
|
|
+ if (arg)
|
|
+ goto out_unlock;
|
|
+
|
|
+ bindex = wbi + 1;
|
|
+ wbi = -1;
|
|
+ for (; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (au_br_writable(br->br_perm)) {
|
|
+ wbi = bindex;
|
|
+ br = au_sbr(sb, wbi);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ AuDbg("wbi %d\n", wbi);
|
|
+ if (wbi >= 0)
|
|
+ h_file = au_h_open(root, wbi, wbrfd.oflags, NULL,
|
|
+ /*force_wr*/0);
|
|
+
|
|
+out_unlock:
|
|
+ aufs_read_unlock(root, AuLock_IR);
|
|
+ err = PTR_ERR(h_file);
|
|
+ if (IS_ERR(h_file))
|
|
+ goto out_fd;
|
|
+
|
|
+ au_lcnt_dec(&br->br_nfiles); /* cf. au_h_open() */
|
|
+ fd_install(fd, h_file);
|
|
+ err = fd;
|
|
+ goto out; /* success */
|
|
+
|
|
+out_fd:
|
|
+ put_unused_fd(fd);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+long aufs_ioctl_dir(struct file *file, unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ long err;
|
|
+ struct dentry *dentry;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case AUFS_CTL_RDU:
|
|
+ case AUFS_CTL_RDU_INO:
|
|
+ err = au_rdu_ioctl(file, cmd, arg);
|
|
+ break;
|
|
+
|
|
+ case AUFS_CTL_WBR_FD:
|
|
+ err = au_wbr_fd(&file->f_path, (void __user *)arg);
|
|
+ break;
|
|
+
|
|
+ case AUFS_CTL_IBUSY:
|
|
+ err = au_ibusy_ioctl(file, arg);
|
|
+ break;
|
|
+
|
|
+ case AUFS_CTL_BRINFO:
|
|
+ err = au_brinfo_ioctl(file, arg);
|
|
+ break;
|
|
+
|
|
+ case AUFS_CTL_FHSM_FD:
|
|
+ dentry = file->f_path.dentry;
|
|
+ if (IS_ROOT(dentry))
|
|
+ err = au_fhsm_fd(dentry->d_sb, arg);
|
|
+ else
|
|
+ err = -ENOTTY;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ /* do not call the lower */
|
|
+ AuDbg("0x%x\n", cmd);
|
|
+ err = -ENOTTY;
|
|
+ }
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+long aufs_ioctl_nondir(struct file *file, unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ long err;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case AUFS_CTL_MVDOWN:
|
|
+ err = au_mvdown(file->f_path.dentry, (void __user *)arg);
|
|
+ break;
|
|
+
|
|
+ case AUFS_CTL_WBR_FD:
|
|
+ err = au_wbr_fd(&file->f_path, (void __user *)arg);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ /* do not call the lower */
|
|
+ AuDbg("0x%x\n", cmd);
|
|
+ err = -ENOTTY;
|
|
+ }
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_COMPAT
|
|
+long aufs_compat_ioctl_dir(struct file *file, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ long err;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case AUFS_CTL_RDU:
|
|
+ case AUFS_CTL_RDU_INO:
|
|
+ err = au_rdu_compat_ioctl(file, cmd, arg);
|
|
+ break;
|
|
+
|
|
+ case AUFS_CTL_IBUSY:
|
|
+ err = au_ibusy_compat_ioctl(file, arg);
|
|
+ break;
|
|
+
|
|
+ case AUFS_CTL_BRINFO:
|
|
+ err = au_brinfo_compat_ioctl(file, arg);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ err = aufs_ioctl_dir(file, cmd, arg);
|
|
+ }
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+long aufs_compat_ioctl_nondir(struct file *file, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ return aufs_ioctl_nondir(file, cmd, (unsigned long)compat_ptr(arg));
|
|
+}
|
|
+#endif
|
|
diff --git a/fs/aufs/lcnt.h b/fs/aufs/lcnt.h
|
|
new file mode 100644
|
|
index 000000000000..8afcabe55826
|
|
--- /dev/null
|
|
+++ b/fs/aufs/lcnt.h
|
|
@@ -0,0 +1,186 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2018-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * simple long counter wrapper
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_LCNT_H__
|
|
+#define __AUFS_LCNT_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include "debug.h"
|
|
+
|
|
+#define AuLCntATOMIC 1
|
|
+#define AuLCntPCPUCNT 2
|
|
+/*
|
|
+ * why does percpu_refcount require extra synchronize_rcu()s in
|
|
+ * au_br_do_free()
|
|
+ */
|
|
+#define AuLCntPCPUREF 3
|
|
+
|
|
+/* #define AuLCntChosen AuLCntATOMIC */
|
|
+#define AuLCntChosen AuLCntPCPUCNT
|
|
+/* #define AuLCntChosen AuLCntPCPUREF */
|
|
+
|
|
+#if AuLCntChosen == AuLCntATOMIC
|
|
+#include <linux/atomic.h>
|
|
+
|
|
+typedef atomic_long_t au_lcnt_t;
|
|
+
|
|
+static inline int au_lcnt_init(au_lcnt_t *cnt, void *release __maybe_unused)
|
|
+{
|
|
+ atomic_long_set(cnt, 0);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_wait_for_fin(au_lcnt_t *cnt __maybe_unused)
|
|
+{
|
|
+ /* empty */
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_fin(au_lcnt_t *cnt __maybe_unused,
|
|
+ int do_sync __maybe_unused)
|
|
+{
|
|
+ /* empty */
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_inc(au_lcnt_t *cnt)
|
|
+{
|
|
+ atomic_long_inc(cnt);
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_dec(au_lcnt_t *cnt)
|
|
+{
|
|
+ atomic_long_dec(cnt);
|
|
+}
|
|
+
|
|
+static inline long au_lcnt_read(au_lcnt_t *cnt, int do_rev __maybe_unused)
|
|
+{
|
|
+ return atomic_long_read(cnt);
|
|
+}
|
|
+#endif
|
|
+
|
|
+#if AuLCntChosen == AuLCntPCPUCNT
|
|
+#include <linux/percpu_counter.h>
|
|
+
|
|
+typedef struct percpu_counter au_lcnt_t;
|
|
+
|
|
+static inline int au_lcnt_init(au_lcnt_t *cnt, void *release __maybe_unused)
|
|
+{
|
|
+ return percpu_counter_init(cnt, 0, GFP_NOFS);
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_wait_for_fin(au_lcnt_t *cnt __maybe_unused)
|
|
+{
|
|
+ /* empty */
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_fin(au_lcnt_t *cnt, int do_sync __maybe_unused)
|
|
+{
|
|
+ percpu_counter_destroy(cnt);
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_inc(au_lcnt_t *cnt)
|
|
+{
|
|
+ percpu_counter_inc(cnt);
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_dec(au_lcnt_t *cnt)
|
|
+{
|
|
+ percpu_counter_dec(cnt);
|
|
+}
|
|
+
|
|
+static inline long au_lcnt_read(au_lcnt_t *cnt, int do_rev __maybe_unused)
|
|
+{
|
|
+ s64 n;
|
|
+
|
|
+ n = percpu_counter_sum(cnt);
|
|
+ BUG_ON(n < 0);
|
|
+ if (LONG_MAX != LLONG_MAX
|
|
+ && n > LONG_MAX)
|
|
+ AuWarn1("%s\n", "wrap-around");
|
|
+
|
|
+ return n;
|
|
+}
|
|
+#endif
|
|
+
|
|
+#if AuLCntChosen == AuLCntPCPUREF
|
|
+#include <linux/percpu-refcount.h>
|
|
+
|
|
+typedef struct percpu_ref au_lcnt_t;
|
|
+
|
|
+static inline int au_lcnt_init(au_lcnt_t *cnt, percpu_ref_func_t *release)
|
|
+{
|
|
+ if (!release)
|
|
+ release = percpu_ref_exit;
|
|
+ return percpu_ref_init(cnt, release, /*percpu mode*/0, GFP_NOFS);
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_wait_for_fin(au_lcnt_t *cnt __maybe_unused)
|
|
+{
|
|
+ synchronize_rcu();
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_fin(au_lcnt_t *cnt, int do_sync)
|
|
+{
|
|
+ percpu_ref_kill(cnt);
|
|
+ if (do_sync)
|
|
+ au_lcnt_wait_for_fin(cnt);
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_inc(au_lcnt_t *cnt)
|
|
+{
|
|
+ percpu_ref_get(cnt);
|
|
+}
|
|
+
|
|
+static inline void au_lcnt_dec(au_lcnt_t *cnt)
|
|
+{
|
|
+ percpu_ref_put(cnt);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * avoid calling this func as possible.
|
|
+ */
|
|
+static inline long au_lcnt_read(au_lcnt_t *cnt, int do_rev)
|
|
+{
|
|
+ long l;
|
|
+
|
|
+ percpu_ref_switch_to_atomic_sync(cnt);
|
|
+ l = atomic_long_read(&cnt->count);
|
|
+ if (do_rev)
|
|
+ percpu_ref_switch_to_percpu(cnt);
|
|
+
|
|
+ /* percpu_ref is initialized by 1 instead of 0 */
|
|
+ return l - 1;
|
|
+}
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_AUFS_DEBUG
|
|
+#define AuLCntZero(val) do { \
|
|
+ long l = val; \
|
|
+ if (l) \
|
|
+ AuDbg("%s = %ld\n", #val, l); \
|
|
+} while (0)
|
|
+#else
|
|
+#define AuLCntZero(val) do {} while (0)
|
|
+#endif
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_LCNT_H__ */
|
|
diff --git a/fs/aufs/loop.c b/fs/aufs/loop.c
|
|
new file mode 100644
|
|
index 000000000000..63732e938684
|
|
--- /dev/null
|
|
+++ b/fs/aufs/loop.c
|
|
@@ -0,0 +1,148 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * support for loopback block device as a branch
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+/* added into drivers/block/loop.c */
|
|
+static struct file *(*backing_file_func)(struct super_block *sb);
|
|
+
|
|
+/*
|
|
+ * test if two lower dentries have overlapping branches.
|
|
+ */
|
|
+int au_test_loopback_overlap(struct super_block *sb, struct dentry *h_adding)
|
|
+{
|
|
+ struct super_block *h_sb;
|
|
+ struct file *backing_file;
|
|
+
|
|
+ if (unlikely(!backing_file_func)) {
|
|
+ /* don't load "loop" module here */
|
|
+ backing_file_func = symbol_get(loop_backing_file);
|
|
+ if (unlikely(!backing_file_func))
|
|
+ /* "loop" module is not loaded */
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ h_sb = h_adding->d_sb;
|
|
+ backing_file = backing_file_func(h_sb);
|
|
+ if (!backing_file)
|
|
+ return 0;
|
|
+
|
|
+ h_adding = backing_file->f_path.dentry;
|
|
+ /*
|
|
+ * h_adding can be local NFS.
|
|
+ * in this case aufs cannot detect the loop.
|
|
+ */
|
|
+ if (unlikely(h_adding->d_sb == sb))
|
|
+ return 1;
|
|
+ return !!au_test_subdir(h_adding, sb->s_root);
|
|
+}
|
|
+
|
|
+/* true if a kernel thread named 'loop[0-9].*' accesses a file */
|
|
+int au_test_loopback_kthread(void)
|
|
+{
|
|
+ int ret;
|
|
+ struct task_struct *tsk = current;
|
|
+ char c, comm[sizeof(tsk->comm)];
|
|
+
|
|
+ ret = 0;
|
|
+ if (tsk->flags & PF_KTHREAD) {
|
|
+ get_task_comm(comm, tsk);
|
|
+ c = comm[4];
|
|
+ ret = ('0' <= c && c <= '9'
|
|
+ && !strncmp(comm, "loop", 4));
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#define au_warn_loopback_step 16
|
|
+static int au_warn_loopback_nelem = au_warn_loopback_step;
|
|
+static unsigned long *au_warn_loopback_array;
|
|
+
|
|
+void au_warn_loopback(struct super_block *h_sb)
|
|
+{
|
|
+ int i, new_nelem;
|
|
+ unsigned long *a, magic;
|
|
+ static DEFINE_SPINLOCK(spin);
|
|
+
|
|
+ magic = h_sb->s_magic;
|
|
+ spin_lock(&spin);
|
|
+ a = au_warn_loopback_array;
|
|
+ for (i = 0; i < au_warn_loopback_nelem && *a; i++)
|
|
+ if (a[i] == magic) {
|
|
+ spin_unlock(&spin);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* h_sb is new to us, print it */
|
|
+ if (i < au_warn_loopback_nelem) {
|
|
+ a[i] = magic;
|
|
+ goto pr;
|
|
+ }
|
|
+
|
|
+ /* expand the array */
|
|
+ new_nelem = au_warn_loopback_nelem + au_warn_loopback_step;
|
|
+ a = au_kzrealloc(au_warn_loopback_array,
|
|
+ au_warn_loopback_nelem * sizeof(unsigned long),
|
|
+ new_nelem * sizeof(unsigned long), GFP_ATOMIC,
|
|
+ /*may_shrink*/0);
|
|
+ if (a) {
|
|
+ au_warn_loopback_nelem = new_nelem;
|
|
+ au_warn_loopback_array = a;
|
|
+ a[i] = magic;
|
|
+ goto pr;
|
|
+ }
|
|
+
|
|
+ spin_unlock(&spin);
|
|
+ AuWarn1("realloc failed, ignored\n");
|
|
+ return;
|
|
+
|
|
+pr:
|
|
+ spin_unlock(&spin);
|
|
+ pr_warn("you may want to try another patch for loopback file "
|
|
+ "on %s(0x%lx) branch\n", au_sbtype(h_sb), magic);
|
|
+}
|
|
+
|
|
+int au_loopback_init(void)
|
|
+{
|
|
+ int err;
|
|
+ struct super_block *sb __maybe_unused;
|
|
+
|
|
+ BUILD_BUG_ON(sizeof(sb->s_magic) != sizeof(*au_warn_loopback_array));
|
|
+
|
|
+ err = 0;
|
|
+ au_warn_loopback_array = kcalloc(au_warn_loopback_step,
|
|
+ sizeof(unsigned long), GFP_NOFS);
|
|
+ if (unlikely(!au_warn_loopback_array))
|
|
+ err = -ENOMEM;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_loopback_fin(void)
|
|
+{
|
|
+ if (backing_file_func)
|
|
+ symbol_put(loop_backing_file);
|
|
+ au_kfree_try_rcu(au_warn_loopback_array);
|
|
+}
|
|
diff --git a/fs/aufs/loop.h b/fs/aufs/loop.h
|
|
new file mode 100644
|
|
index 000000000000..65c38bbb5d56
|
|
--- /dev/null
|
|
+++ b/fs/aufs/loop.h
|
|
@@ -0,0 +1,55 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * support for loopback mount as a branch
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_LOOP_H__
|
|
+#define __AUFS_LOOP_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+struct dentry;
|
|
+struct super_block;
|
|
+
|
|
+#ifdef CONFIG_AUFS_BDEV_LOOP
|
|
+/* drivers/block/loop.c */
|
|
+struct file *loop_backing_file(struct super_block *sb);
|
|
+
|
|
+/* loop.c */
|
|
+int au_test_loopback_overlap(struct super_block *sb, struct dentry *h_adding);
|
|
+int au_test_loopback_kthread(void);
|
|
+void au_warn_loopback(struct super_block *h_sb);
|
|
+
|
|
+int au_loopback_init(void);
|
|
+void au_loopback_fin(void);
|
|
+#else
|
|
+AuStub(struct file *, loop_backing_file, return NULL, struct super_block *sb)
|
|
+
|
|
+AuStubInt0(au_test_loopback_overlap, struct super_block *sb,
|
|
+ struct dentry *h_adding)
|
|
+AuStubInt0(au_test_loopback_kthread, void)
|
|
+AuStubVoid(au_warn_loopback, struct super_block *h_sb)
|
|
+
|
|
+AuStubInt0(au_loopback_init, void)
|
|
+AuStubVoid(au_loopback_fin, void)
|
|
+#endif /* BLK_DEV_LOOP */
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_LOOP_H__ */
|
|
diff --git a/fs/aufs/magic.mk b/fs/aufs/magic.mk
|
|
new file mode 100644
|
|
index 000000000000..7bc9eef3ffec
|
|
--- /dev/null
|
|
+++ b/fs/aufs/magic.mk
|
|
@@ -0,0 +1,31 @@
|
|
+# SPDX-License-Identifier: GPL-2.0
|
|
+
|
|
+# defined in ${srctree}/fs/fuse/inode.c
|
|
+# tristate
|
|
+ifdef CONFIG_FUSE_FS
|
|
+ccflags-y += -DFUSE_SUPER_MAGIC=0x65735546
|
|
+endif
|
|
+
|
|
+# defined in ${srctree}/fs/xfs/xfs_sb.h
|
|
+# tristate
|
|
+ifdef CONFIG_XFS_FS
|
|
+ccflags-y += -DXFS_SB_MAGIC=0x58465342
|
|
+endif
|
|
+
|
|
+# defined in ${srctree}/fs/configfs/mount.c
|
|
+# tristate
|
|
+ifdef CONFIG_CONFIGFS_FS
|
|
+ccflags-y += -DCONFIGFS_MAGIC=0x62656570
|
|
+endif
|
|
+
|
|
+# defined in ${srctree}/fs/ubifs/ubifs.h
|
|
+# tristate
|
|
+ifdef CONFIG_UBIFS_FS
|
|
+ccflags-y += -DUBIFS_SUPER_MAGIC=0x24051905
|
|
+endif
|
|
+
|
|
+# defined in ${srctree}/fs/hfsplus/hfsplus_raw.h
|
|
+# tristate
|
|
+ifdef CONFIG_HFSPLUS_FS
|
|
+ccflags-y += -DHFSPLUS_SUPER_MAGIC=0x482b
|
|
+endif
|
|
diff --git a/fs/aufs/module.c b/fs/aufs/module.c
|
|
new file mode 100644
|
|
index 000000000000..7245197a6cbf
|
|
--- /dev/null
|
|
+++ b/fs/aufs/module.c
|
|
@@ -0,0 +1,273 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * module global variables and operations
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/seq_file.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/* shrinkable realloc */
|
|
+void *au_krealloc(void *p, unsigned int new_sz, gfp_t gfp, int may_shrink)
|
|
+{
|
|
+ size_t sz;
|
|
+ int diff;
|
|
+
|
|
+ sz = 0;
|
|
+ diff = -1;
|
|
+ if (p) {
|
|
+#if 0 /* unused */
|
|
+ if (!new_sz) {
|
|
+ au_kfree_rcu(p);
|
|
+ p = NULL;
|
|
+ goto out;
|
|
+ }
|
|
+#else
|
|
+ AuDebugOn(!new_sz);
|
|
+#endif
|
|
+ sz = ksize(p);
|
|
+ diff = au_kmidx_sub(sz, new_sz);
|
|
+ }
|
|
+ if (sz && !diff)
|
|
+ goto out;
|
|
+
|
|
+ if (sz < new_sz)
|
|
+ /* expand or SLOB */
|
|
+ p = krealloc(p, new_sz, gfp);
|
|
+ else if (new_sz < sz && may_shrink) {
|
|
+ /* shrink */
|
|
+ void *q;
|
|
+
|
|
+ q = kmalloc(new_sz, gfp);
|
|
+ if (q) {
|
|
+ if (p) {
|
|
+ memcpy(q, p, new_sz);
|
|
+ au_kfree_try_rcu(p);
|
|
+ }
|
|
+ p = q;
|
|
+ } else
|
|
+ p = NULL;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return p;
|
|
+}
|
|
+
|
|
+void *au_kzrealloc(void *p, unsigned int nused, unsigned int new_sz, gfp_t gfp,
|
|
+ int may_shrink)
|
|
+{
|
|
+ p = au_krealloc(p, new_sz, gfp, may_shrink);
|
|
+ if (p && new_sz > nused)
|
|
+ memset(p + nused, 0, new_sz - nused);
|
|
+ return p;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+/*
|
|
+ * aufs caches
|
|
+ */
|
|
+struct kmem_cache *au_cache[AuCache_Last];
|
|
+
|
|
+static void au_cache_fin(void)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ /*
|
|
+ * Make sure all delayed rcu free inodes are flushed before we
|
|
+ * destroy cache.
|
|
+ */
|
|
+ rcu_barrier();
|
|
+
|
|
+ /* excluding AuCache_HNOTIFY */
|
|
+ BUILD_BUG_ON(AuCache_HNOTIFY + 1 != AuCache_Last);
|
|
+ for (i = 0; i < AuCache_HNOTIFY; i++) {
|
|
+ kmem_cache_destroy(au_cache[i]);
|
|
+ au_cache[i] = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int __init au_cache_init(void)
|
|
+{
|
|
+ au_cache[AuCache_DINFO] = AuCacheCtor(au_dinfo, au_di_init_once);
|
|
+ if (au_cache[AuCache_DINFO])
|
|
+ /* SLAB_DESTROY_BY_RCU */
|
|
+ au_cache[AuCache_ICNTNR] = AuCacheCtor(au_icntnr,
|
|
+ au_icntnr_init_once);
|
|
+ if (au_cache[AuCache_ICNTNR])
|
|
+ au_cache[AuCache_FINFO] = AuCacheCtor(au_finfo,
|
|
+ au_fi_init_once);
|
|
+ if (au_cache[AuCache_FINFO])
|
|
+ au_cache[AuCache_VDIR] = AuCache(au_vdir);
|
|
+ if (au_cache[AuCache_VDIR])
|
|
+ au_cache[AuCache_DEHSTR] = AuCache(au_vdir_dehstr);
|
|
+ if (au_cache[AuCache_DEHSTR])
|
|
+ return 0;
|
|
+
|
|
+ au_cache_fin();
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_dir_roflags;
|
|
+
|
|
+#ifdef CONFIG_AUFS_SBILIST
|
|
+/*
|
|
+ * iterate_supers_type() doesn't protect us from
|
|
+ * remounting (branch management)
|
|
+ */
|
|
+struct hlist_bl_head au_sbilist;
|
|
+#endif
|
|
+
|
|
+/*
|
|
+ * functions for module interface.
|
|
+ */
|
|
+MODULE_LICENSE("GPL");
|
|
+/* MODULE_LICENSE("GPL v2"); */
|
|
+MODULE_AUTHOR("Junjiro R. Okajima <aufs-users@lists.sourceforge.net>");
|
|
+MODULE_DESCRIPTION(AUFS_NAME
|
|
+ " -- Advanced multi layered unification filesystem");
|
|
+MODULE_VERSION(AUFS_VERSION);
|
|
+MODULE_ALIAS_FS(AUFS_NAME);
|
|
+
|
|
+/* this module parameter has no meaning when SYSFS is disabled */
|
|
+int sysaufs_brs = 1;
|
|
+MODULE_PARM_DESC(brs, "use <sysfs>/fs/aufs/si_*/brN");
|
|
+module_param_named(brs, sysaufs_brs, int, 0444);
|
|
+
|
|
+/* this module parameter has no meaning when USER_NS is disabled */
|
|
+bool au_userns;
|
|
+MODULE_PARM_DESC(allow_userns, "allow unprivileged to mount under userns");
|
|
+module_param_named(allow_userns, au_userns, bool, 0444);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static char au_esc_chars[0x20 + 3]; /* 0x01-0x20, backslash, del, and NULL */
|
|
+
|
|
+int au_seq_path(struct seq_file *seq, struct path *path)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = seq_path(seq, path, au_esc_chars);
|
|
+ if (err >= 0)
|
|
+ err = 0;
|
|
+ else
|
|
+ err = -ENOMEM;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int __init aufs_init(void)
|
|
+{
|
|
+ int err, i;
|
|
+ char *p;
|
|
+
|
|
+ p = au_esc_chars;
|
|
+ for (i = 1; i <= ' '; i++)
|
|
+ *p++ = i;
|
|
+ *p++ = '\\';
|
|
+ *p++ = '\x7f';
|
|
+ *p = 0;
|
|
+
|
|
+ au_dir_roflags = au_file_roflags(O_DIRECTORY | O_LARGEFILE);
|
|
+
|
|
+ memcpy(aufs_iop_nogetattr, aufs_iop, sizeof(aufs_iop));
|
|
+ for (i = 0; i < AuIop_Last; i++)
|
|
+ aufs_iop_nogetattr[i].getattr = NULL;
|
|
+
|
|
+ memset(au_cache, 0, sizeof(au_cache)); /* including hnotify */
|
|
+
|
|
+ au_sbilist_init();
|
|
+ sysaufs_brs_init();
|
|
+ au_debug_init();
|
|
+ au_dy_init();
|
|
+ err = sysaufs_init();
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = dbgaufs_init();
|
|
+ if (unlikely(err))
|
|
+ goto out_sysaufs;
|
|
+ err = au_procfs_init();
|
|
+ if (unlikely(err))
|
|
+ goto out_dbgaufs;
|
|
+ err = au_wkq_init();
|
|
+ if (unlikely(err))
|
|
+ goto out_procfs;
|
|
+ err = au_loopback_init();
|
|
+ if (unlikely(err))
|
|
+ goto out_wkq;
|
|
+ err = au_hnotify_init();
|
|
+ if (unlikely(err))
|
|
+ goto out_loopback;
|
|
+ err = au_sysrq_init();
|
|
+ if (unlikely(err))
|
|
+ goto out_hin;
|
|
+ err = au_cache_init();
|
|
+ if (unlikely(err))
|
|
+ goto out_sysrq;
|
|
+
|
|
+ aufs_fs_type.fs_flags |= au_userns ? FS_USERNS_MOUNT : 0;
|
|
+ err = register_filesystem(&aufs_fs_type);
|
|
+ if (unlikely(err))
|
|
+ goto out_cache;
|
|
+
|
|
+ /* since we define pr_fmt, call printk directly */
|
|
+ printk(KERN_INFO AUFS_NAME " " AUFS_VERSION "\n");
|
|
+ goto out; /* success */
|
|
+
|
|
+out_cache:
|
|
+ au_cache_fin();
|
|
+out_sysrq:
|
|
+ au_sysrq_fin();
|
|
+out_hin:
|
|
+ au_hnotify_fin();
|
|
+out_loopback:
|
|
+ au_loopback_fin();
|
|
+out_wkq:
|
|
+ au_wkq_fin();
|
|
+out_procfs:
|
|
+ au_procfs_fin();
|
|
+out_dbgaufs:
|
|
+ dbgaufs_fin();
|
|
+out_sysaufs:
|
|
+ sysaufs_fin();
|
|
+ au_dy_fin();
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void __exit aufs_exit(void)
|
|
+{
|
|
+ unregister_filesystem(&aufs_fs_type);
|
|
+ au_cache_fin();
|
|
+ au_sysrq_fin();
|
|
+ au_hnotify_fin();
|
|
+ au_loopback_fin();
|
|
+ au_wkq_fin();
|
|
+ au_procfs_fin();
|
|
+ dbgaufs_fin();
|
|
+ sysaufs_fin();
|
|
+ au_dy_fin();
|
|
+}
|
|
+
|
|
+module_init(aufs_init);
|
|
+module_exit(aufs_exit);
|
|
diff --git a/fs/aufs/module.h b/fs/aufs/module.h
|
|
new file mode 100644
|
|
index 000000000000..d6a76788d2ff
|
|
--- /dev/null
|
|
+++ b/fs/aufs/module.h
|
|
@@ -0,0 +1,166 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * module initialization and module-global
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_MODULE_H__
|
|
+#define __AUFS_MODULE_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/slab.h>
|
|
+#include "debug.h"
|
|
+#include "dentry.h"
|
|
+#include "dir.h"
|
|
+#include "file.h"
|
|
+#include "inode.h"
|
|
+
|
|
+struct path;
|
|
+struct seq_file;
|
|
+
|
|
+/* module parameters */
|
|
+extern int sysaufs_brs;
|
|
+extern bool au_userns;
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+extern int au_dir_roflags;
|
|
+
|
|
+void *au_krealloc(void *p, unsigned int new_sz, gfp_t gfp, int may_shrink);
|
|
+void *au_kzrealloc(void *p, unsigned int nused, unsigned int new_sz, gfp_t gfp,
|
|
+ int may_shrink);
|
|
+
|
|
+/*
|
|
+ * Comparing the size of the object with sizeof(struct rcu_head)
|
|
+ * case 1: object is always larger
|
|
+ * --> au_kfree_rcu() or au_kfree_do_rcu()
|
|
+ * case 2: object is always smaller
|
|
+ * --> au_kfree_small()
|
|
+ * case 3: object can be any size
|
|
+ * --> au_kfree_try_rcu()
|
|
+ */
|
|
+
|
|
+static inline void au_kfree_do_rcu(const void *p)
|
|
+{
|
|
+ struct {
|
|
+ struct rcu_head rcu;
|
|
+ } *a = (void *)p;
|
|
+
|
|
+ kfree_rcu(a, rcu);
|
|
+}
|
|
+
|
|
+#define au_kfree_rcu(_p) do { \
|
|
+ typeof(_p) p = (_p); \
|
|
+ BUILD_BUG_ON(sizeof(*p) < sizeof(struct rcu_head)); \
|
|
+ if (p) \
|
|
+ au_kfree_do_rcu(p); \
|
|
+ } while (0)
|
|
+
|
|
+#define au_kfree_do_sz_test(sz) (sz >= sizeof(struct rcu_head))
|
|
+#define au_kfree_sz_test(p) (p && au_kfree_do_sz_test(ksize(p)))
|
|
+
|
|
+static inline void au_kfree_try_rcu(const void *p)
|
|
+{
|
|
+ if (!p)
|
|
+ return;
|
|
+ if (au_kfree_sz_test(p))
|
|
+ au_kfree_do_rcu(p);
|
|
+ else
|
|
+ kfree(p);
|
|
+}
|
|
+
|
|
+static inline void au_kfree_small(const void *p)
|
|
+{
|
|
+ if (!p)
|
|
+ return;
|
|
+ AuDebugOn(au_kfree_sz_test(p));
|
|
+ kfree(p);
|
|
+}
|
|
+
|
|
+static inline int au_kmidx_sub(size_t sz, size_t new_sz)
|
|
+{
|
|
+#ifndef CONFIG_SLOB
|
|
+ return kmalloc_index(sz) - kmalloc_index(new_sz);
|
|
+#else
|
|
+ return -1; /* SLOB is untested */
|
|
+#endif
|
|
+}
|
|
+
|
|
+int au_seq_path(struct seq_file *seq, struct path *path);
|
|
+
|
|
+#ifdef CONFIG_PROC_FS
|
|
+/* procfs.c */
|
|
+int __init au_procfs_init(void);
|
|
+void au_procfs_fin(void);
|
|
+#else
|
|
+AuStubInt0(au_procfs_init, void);
|
|
+AuStubVoid(au_procfs_fin, void);
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* kmem cache */
|
|
+enum {
|
|
+ AuCache_DINFO,
|
|
+ AuCache_ICNTNR,
|
|
+ AuCache_FINFO,
|
|
+ AuCache_VDIR,
|
|
+ AuCache_DEHSTR,
|
|
+ AuCache_HNOTIFY, /* must be last */
|
|
+ AuCache_Last
|
|
+};
|
|
+
|
|
+extern struct kmem_cache *au_cache[AuCache_Last];
|
|
+
|
|
+#define AuCacheFlags (SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD)
|
|
+#define AuCache(type) KMEM_CACHE(type, AuCacheFlags)
|
|
+#define AuCacheCtor(type, ctor) \
|
|
+ kmem_cache_create(#type, sizeof(struct type), \
|
|
+ __alignof__(struct type), AuCacheFlags, ctor)
|
|
+
|
|
+#define AuCacheFuncs(name, index) \
|
|
+ static inline struct au_##name *au_cache_alloc_##name(void) \
|
|
+ { return kmem_cache_alloc(au_cache[AuCache_##index], GFP_NOFS); } \
|
|
+ static inline void au_cache_free_##name##_norcu(struct au_##name *p) \
|
|
+ { kmem_cache_free(au_cache[AuCache_##index], p); } \
|
|
+ \
|
|
+ static inline void au_cache_free_##name##_rcu_cb(struct rcu_head *rcu) \
|
|
+ { void *p = rcu; \
|
|
+ p -= offsetof(struct au_##name, rcu); \
|
|
+ kmem_cache_free(au_cache[AuCache_##index], p); } \
|
|
+ static inline void au_cache_free_##name##_rcu(struct au_##name *p) \
|
|
+ { BUILD_BUG_ON(sizeof(struct au_##name) < sizeof(struct rcu_head)); \
|
|
+ call_rcu(&p->rcu, au_cache_free_##name##_rcu_cb); } \
|
|
+ \
|
|
+ static inline void au_cache_free_##name(struct au_##name *p) \
|
|
+ { /* au_cache_free_##name##_norcu(p); */ \
|
|
+ au_cache_free_##name##_rcu(p); }
|
|
+
|
|
+AuCacheFuncs(dinfo, DINFO);
|
|
+AuCacheFuncs(icntnr, ICNTNR);
|
|
+AuCacheFuncs(finfo, FINFO);
|
|
+AuCacheFuncs(vdir, VDIR);
|
|
+AuCacheFuncs(vdir_dehstr, DEHSTR);
|
|
+#ifdef CONFIG_AUFS_HNOTIFY
|
|
+AuCacheFuncs(hnotify, HNOTIFY);
|
|
+#endif
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_MODULE_H__ */
|
|
diff --git a/fs/aufs/mvdown.c b/fs/aufs/mvdown.c
|
|
new file mode 100644
|
|
index 000000000000..c39a39e52ebc
|
|
--- /dev/null
|
|
+++ b/fs/aufs/mvdown.c
|
|
@@ -0,0 +1,706 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2011-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * move-down, opposite of copy-up
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+struct au_mvd_args {
|
|
+ struct {
|
|
+ struct super_block *h_sb;
|
|
+ struct dentry *h_parent;
|
|
+ struct au_hinode *hdir;
|
|
+ struct inode *h_dir, *h_inode;
|
|
+ struct au_pin pin;
|
|
+ } info[AUFS_MVDOWN_NARRAY];
|
|
+
|
|
+ struct aufs_mvdown mvdown;
|
|
+ struct dentry *dentry, *parent;
|
|
+ struct inode *inode, *dir;
|
|
+ struct super_block *sb;
|
|
+ aufs_bindex_t bopq, bwh, bfound;
|
|
+ unsigned char rename_lock;
|
|
+};
|
|
+
|
|
+#define mvd_errno mvdown.au_errno
|
|
+#define mvd_bsrc mvdown.stbr[AUFS_MVDOWN_UPPER].bindex
|
|
+#define mvd_src_brid mvdown.stbr[AUFS_MVDOWN_UPPER].brid
|
|
+#define mvd_bdst mvdown.stbr[AUFS_MVDOWN_LOWER].bindex
|
|
+#define mvd_dst_brid mvdown.stbr[AUFS_MVDOWN_LOWER].brid
|
|
+
|
|
+#define mvd_h_src_sb info[AUFS_MVDOWN_UPPER].h_sb
|
|
+#define mvd_h_src_parent info[AUFS_MVDOWN_UPPER].h_parent
|
|
+#define mvd_hdir_src info[AUFS_MVDOWN_UPPER].hdir
|
|
+#define mvd_h_src_dir info[AUFS_MVDOWN_UPPER].h_dir
|
|
+#define mvd_h_src_inode info[AUFS_MVDOWN_UPPER].h_inode
|
|
+#define mvd_pin_src info[AUFS_MVDOWN_UPPER].pin
|
|
+
|
|
+#define mvd_h_dst_sb info[AUFS_MVDOWN_LOWER].h_sb
|
|
+#define mvd_h_dst_parent info[AUFS_MVDOWN_LOWER].h_parent
|
|
+#define mvd_hdir_dst info[AUFS_MVDOWN_LOWER].hdir
|
|
+#define mvd_h_dst_dir info[AUFS_MVDOWN_LOWER].h_dir
|
|
+#define mvd_h_dst_inode info[AUFS_MVDOWN_LOWER].h_inode
|
|
+#define mvd_pin_dst info[AUFS_MVDOWN_LOWER].pin
|
|
+
|
|
+#define AU_MVD_PR(flag, ...) do { \
|
|
+ if (flag) \
|
|
+ pr_err(__VA_ARGS__); \
|
|
+ } while (0)
|
|
+
|
|
+static int find_lower_writable(struct au_mvd_args *a)
|
|
+{
|
|
+ struct super_block *sb;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ sb = a->sb;
|
|
+ bindex = a->mvd_bsrc;
|
|
+ bbot = au_sbbot(sb);
|
|
+ if (a->mvdown.flags & AUFS_MVDOWN_FHSM_LOWER)
|
|
+ for (bindex++; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (au_br_fhsm(br->br_perm)
|
|
+ && !sb_rdonly(au_br_sb(br)))
|
|
+ return bindex;
|
|
+ }
|
|
+ else if (!(a->mvdown.flags & AUFS_MVDOWN_ROLOWER))
|
|
+ for (bindex++; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (!au_br_rdonly(br))
|
|
+ return bindex;
|
|
+ }
|
|
+ else
|
|
+ for (bindex++; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (!sb_rdonly(au_br_sb(br))) {
|
|
+ if (au_br_rdonly(br))
|
|
+ a->mvdown.flags
|
|
+ |= AUFS_MVDOWN_ROLOWER_R;
|
|
+ return bindex;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+/* make the parent dir on bdst */
|
|
+static int au_do_mkdir(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ a->mvd_hdir_src = au_hi(a->dir, a->mvd_bsrc);
|
|
+ a->mvd_hdir_dst = au_hi(a->dir, a->mvd_bdst);
|
|
+ a->mvd_h_src_parent = au_h_dptr(a->parent, a->mvd_bsrc);
|
|
+ a->mvd_h_dst_parent = NULL;
|
|
+ if (au_dbbot(a->parent) >= a->mvd_bdst)
|
|
+ a->mvd_h_dst_parent = au_h_dptr(a->parent, a->mvd_bdst);
|
|
+ if (!a->mvd_h_dst_parent) {
|
|
+ err = au_cpdown_dirs(a->dentry, a->mvd_bdst);
|
|
+ if (unlikely(err)) {
|
|
+ AU_MVD_PR(dmsg, "cpdown_dirs failed\n");
|
|
+ goto out;
|
|
+ }
|
|
+ a->mvd_h_dst_parent = au_h_dptr(a->parent, a->mvd_bdst);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* lock them all */
|
|
+static int au_do_lock(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *h_trap;
|
|
+
|
|
+ a->mvd_h_src_sb = au_sbr_sb(a->sb, a->mvd_bsrc);
|
|
+ a->mvd_h_dst_sb = au_sbr_sb(a->sb, a->mvd_bdst);
|
|
+ err = au_pin(&a->mvd_pin_dst, a->dentry, a->mvd_bdst,
|
|
+ au_opt_udba(a->sb),
|
|
+ AuPin_MNT_WRITE | AuPin_DI_LOCKED);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err)) {
|
|
+ AU_MVD_PR(dmsg, "pin_dst failed\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (a->mvd_h_src_sb != a->mvd_h_dst_sb) {
|
|
+ a->rename_lock = 0;
|
|
+ au_pin_init(&a->mvd_pin_src, a->dentry, a->mvd_bsrc,
|
|
+ AuLsc_DI_PARENT, AuLsc_I_PARENT3,
|
|
+ au_opt_udba(a->sb),
|
|
+ AuPin_MNT_WRITE | AuPin_DI_LOCKED);
|
|
+ err = au_do_pin(&a->mvd_pin_src);
|
|
+ AuTraceErr(err);
|
|
+ a->mvd_h_src_dir = d_inode(a->mvd_h_src_parent);
|
|
+ if (unlikely(err)) {
|
|
+ AU_MVD_PR(dmsg, "pin_src failed\n");
|
|
+ goto out_dst;
|
|
+ }
|
|
+ goto out; /* success */
|
|
+ }
|
|
+
|
|
+ a->rename_lock = 1;
|
|
+ au_pin_hdir_unlock(&a->mvd_pin_dst);
|
|
+ err = au_pin(&a->mvd_pin_src, a->dentry, a->mvd_bsrc,
|
|
+ au_opt_udba(a->sb),
|
|
+ AuPin_MNT_WRITE | AuPin_DI_LOCKED);
|
|
+ AuTraceErr(err);
|
|
+ a->mvd_h_src_dir = d_inode(a->mvd_h_src_parent);
|
|
+ if (unlikely(err)) {
|
|
+ AU_MVD_PR(dmsg, "pin_src failed\n");
|
|
+ au_pin_hdir_lock(&a->mvd_pin_dst);
|
|
+ goto out_dst;
|
|
+ }
|
|
+ au_pin_hdir_unlock(&a->mvd_pin_src);
|
|
+ h_trap = vfsub_lock_rename(a->mvd_h_src_parent, a->mvd_hdir_src,
|
|
+ a->mvd_h_dst_parent, a->mvd_hdir_dst);
|
|
+ if (h_trap) {
|
|
+ err = (h_trap != a->mvd_h_src_parent);
|
|
+ if (err)
|
|
+ err = (h_trap != a->mvd_h_dst_parent);
|
|
+ }
|
|
+ BUG_ON(err); /* it should never happen */
|
|
+ if (unlikely(a->mvd_h_src_dir != au_pinned_h_dir(&a->mvd_pin_src))) {
|
|
+ err = -EBUSY;
|
|
+ AuTraceErr(err);
|
|
+ vfsub_unlock_rename(a->mvd_h_src_parent, a->mvd_hdir_src,
|
|
+ a->mvd_h_dst_parent, a->mvd_hdir_dst);
|
|
+ au_pin_hdir_lock(&a->mvd_pin_src);
|
|
+ au_unpin(&a->mvd_pin_src);
|
|
+ au_pin_hdir_lock(&a->mvd_pin_dst);
|
|
+ goto out_dst;
|
|
+ }
|
|
+ goto out; /* success */
|
|
+
|
|
+out_dst:
|
|
+ au_unpin(&a->mvd_pin_dst);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_do_unlock(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ if (!a->rename_lock)
|
|
+ au_unpin(&a->mvd_pin_src);
|
|
+ else {
|
|
+ vfsub_unlock_rename(a->mvd_h_src_parent, a->mvd_hdir_src,
|
|
+ a->mvd_h_dst_parent, a->mvd_hdir_dst);
|
|
+ au_pin_hdir_lock(&a->mvd_pin_src);
|
|
+ au_unpin(&a->mvd_pin_src);
|
|
+ au_pin_hdir_lock(&a->mvd_pin_dst);
|
|
+ }
|
|
+ au_unpin(&a->mvd_pin_dst);
|
|
+}
|
|
+
|
|
+/* copy-down the file */
|
|
+static int au_do_cpdown(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct au_cp_generic cpg = {
|
|
+ .dentry = a->dentry,
|
|
+ .bdst = a->mvd_bdst,
|
|
+ .bsrc = a->mvd_bsrc,
|
|
+ .len = -1,
|
|
+ .pin = &a->mvd_pin_dst,
|
|
+ .flags = AuCpup_DTIME | AuCpup_HOPEN
|
|
+ };
|
|
+
|
|
+ AuDbg("b%d, b%d\n", cpg.bsrc, cpg.bdst);
|
|
+ if (a->mvdown.flags & AUFS_MVDOWN_OWLOWER)
|
|
+ au_fset_cpup(cpg.flags, OVERWRITE);
|
|
+ if (a->mvdown.flags & AUFS_MVDOWN_ROLOWER)
|
|
+ au_fset_cpup(cpg.flags, RWDST);
|
|
+ err = au_sio_cpdown_simple(&cpg);
|
|
+ if (unlikely(err))
|
|
+ AU_MVD_PR(dmsg, "cpdown failed\n");
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * unlink the whiteout on bdst if exist which may be created by UDBA while we
|
|
+ * were sleeping
|
|
+ */
|
|
+static int au_do_unlink_wh(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct path h_path;
|
|
+ struct au_branch *br;
|
|
+ struct inode *delegated;
|
|
+
|
|
+ br = au_sbr(a->sb, a->mvd_bdst);
|
|
+ h_path.dentry = au_wh_lkup(a->mvd_h_dst_parent, &a->dentry->d_name, br);
|
|
+ err = PTR_ERR(h_path.dentry);
|
|
+ if (IS_ERR(h_path.dentry)) {
|
|
+ AU_MVD_PR(dmsg, "wh_lkup failed\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ if (d_is_positive(h_path.dentry)) {
|
|
+ h_path.mnt = au_br_mnt(br);
|
|
+ delegated = NULL;
|
|
+ err = vfsub_unlink(d_inode(a->mvd_h_dst_parent), &h_path,
|
|
+ &delegated, /*force*/0);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal unlink\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ AU_MVD_PR(dmsg, "wh_unlink failed\n");
|
|
+ }
|
|
+ dput(h_path.dentry);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * unlink the topmost h_dentry
|
|
+ */
|
|
+static int au_do_unlink(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct path h_path;
|
|
+ struct inode *delegated;
|
|
+
|
|
+ h_path.mnt = au_sbr_mnt(a->sb, a->mvd_bsrc);
|
|
+ h_path.dentry = au_h_dptr(a->dentry, a->mvd_bsrc);
|
|
+ delegated = NULL;
|
|
+ err = vfsub_unlink(a->mvd_h_src_dir, &h_path, &delegated, /*force*/0);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal unlink\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ AU_MVD_PR(dmsg, "unlink failed\n");
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* Since mvdown succeeded, we ignore an error of this function */
|
|
+static void au_do_stfs(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ a->mvdown.flags |= AUFS_MVDOWN_STFS_FAILED;
|
|
+ br = au_sbr(a->sb, a->mvd_bsrc);
|
|
+ err = au_br_stfs(br, &a->mvdown.stbr[AUFS_MVDOWN_UPPER].stfs);
|
|
+ if (!err) {
|
|
+ br = au_sbr(a->sb, a->mvd_bdst);
|
|
+ a->mvdown.stbr[AUFS_MVDOWN_LOWER].brid = br->br_id;
|
|
+ err = au_br_stfs(br, &a->mvdown.stbr[AUFS_MVDOWN_LOWER].stfs);
|
|
+ }
|
|
+ if (!err)
|
|
+ a->mvdown.flags &= ~AUFS_MVDOWN_STFS_FAILED;
|
|
+ else
|
|
+ AU_MVD_PR(dmsg, "statfs failed (%d), ignored\n", err);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * copy-down the file and unlink the bsrc file.
|
|
+ * - unlink the bdst whout if exist
|
|
+ * - copy-down the file (with whtmp name and rename)
|
|
+ * - unlink the bsrc file
|
|
+ */
|
|
+static int au_do_mvdown(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = au_do_mkdir(dmsg, a);
|
|
+ if (!err)
|
|
+ err = au_do_lock(dmsg, a);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ /*
|
|
+ * do not revert the activities we made on bdst since they should be
|
|
+ * harmless in aufs.
|
|
+ */
|
|
+
|
|
+ err = au_do_cpdown(dmsg, a);
|
|
+ if (!err)
|
|
+ err = au_do_unlink_wh(dmsg, a);
|
|
+ if (!err && !(a->mvdown.flags & AUFS_MVDOWN_KUPPER))
|
|
+ err = au_do_unlink(dmsg, a);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+
|
|
+ AuDbg("%pd2, 0x%x, %d --> %d\n",
|
|
+ a->dentry, a->mvdown.flags, a->mvd_bsrc, a->mvd_bdst);
|
|
+ if (find_lower_writable(a) < 0)
|
|
+ a->mvdown.flags |= AUFS_MVDOWN_BOTTOM;
|
|
+
|
|
+ if (a->mvdown.flags & AUFS_MVDOWN_STFS)
|
|
+ au_do_stfs(dmsg, a);
|
|
+
|
|
+ /* maintain internal array */
|
|
+ if (!(a->mvdown.flags & AUFS_MVDOWN_KUPPER)) {
|
|
+ au_set_h_dptr(a->dentry, a->mvd_bsrc, NULL);
|
|
+ au_set_dbtop(a->dentry, a->mvd_bdst);
|
|
+ au_set_h_iptr(a->inode, a->mvd_bsrc, NULL, /*flags*/0);
|
|
+ au_set_ibtop(a->inode, a->mvd_bdst);
|
|
+ } else {
|
|
+ /* hide the lower */
|
|
+ au_set_h_dptr(a->dentry, a->mvd_bdst, NULL);
|
|
+ au_set_dbbot(a->dentry, a->mvd_bsrc);
|
|
+ au_set_h_iptr(a->inode, a->mvd_bdst, NULL, /*flags*/0);
|
|
+ au_set_ibbot(a->inode, a->mvd_bsrc);
|
|
+ }
|
|
+ if (au_dbbot(a->dentry) < a->mvd_bdst)
|
|
+ au_set_dbbot(a->dentry, a->mvd_bdst);
|
|
+ if (au_ibbot(a->inode) < a->mvd_bdst)
|
|
+ au_set_ibbot(a->inode, a->mvd_bdst);
|
|
+
|
|
+out_unlock:
|
|
+ au_do_unlock(dmsg, a);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* make sure the file is idle */
|
|
+static int au_mvd_args_busy(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ int err, plinked;
|
|
+
|
|
+ err = 0;
|
|
+ plinked = !!au_opt_test(au_mntflags(a->sb), PLINK);
|
|
+ if (au_dbtop(a->dentry) == a->mvd_bsrc
|
|
+ && au_dcount(a->dentry) == 1
|
|
+ && atomic_read(&a->inode->i_count) == 1
|
|
+ /* && a->mvd_h_src_inode->i_nlink == 1 */
|
|
+ && (!plinked || !au_plink_test(a->inode))
|
|
+ && a->inode->i_nlink == 1)
|
|
+ goto out;
|
|
+
|
|
+ err = -EBUSY;
|
|
+ AU_MVD_PR(dmsg,
|
|
+ "b%d, d{b%d, c%d?}, i{c%d?, l%u}, hi{l%u}, p{%d, %d}\n",
|
|
+ a->mvd_bsrc, au_dbtop(a->dentry), au_dcount(a->dentry),
|
|
+ atomic_read(&a->inode->i_count), a->inode->i_nlink,
|
|
+ a->mvd_h_src_inode->i_nlink,
|
|
+ plinked, plinked ? au_plink_test(a->inode) : 0);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* make sure the parent dir is fine */
|
|
+static int au_mvd_args_parent(const unsigned char dmsg,
|
|
+ struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+
|
|
+ err = 0;
|
|
+ if (unlikely(au_alive_dir(a->parent))) {
|
|
+ err = -ENOENT;
|
|
+ AU_MVD_PR(dmsg, "parent dir is dead\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ a->bopq = au_dbdiropq(a->parent);
|
|
+ bindex = au_wbr_nonopq(a->dentry, a->mvd_bdst);
|
|
+ AuDbg("b%d\n", bindex);
|
|
+ if (unlikely((bindex >= 0 && bindex < a->mvd_bdst)
|
|
+ || (a->bopq != -1 && a->bopq < a->mvd_bdst))) {
|
|
+ err = -EINVAL;
|
|
+ a->mvd_errno = EAU_MVDOWN_OPAQUE;
|
|
+ AU_MVD_PR(dmsg, "ancestor is opaque b%d, b%d\n",
|
|
+ a->bopq, a->mvd_bdst);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_mvd_args_intermediate(const unsigned char dmsg,
|
|
+ struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct au_dinfo *dinfo, *tmp;
|
|
+
|
|
+ /* lookup the next lower positive entry */
|
|
+ err = -ENOMEM;
|
|
+ tmp = au_di_alloc(a->sb, AuLsc_DI_TMP);
|
|
+ if (unlikely(!tmp))
|
|
+ goto out;
|
|
+
|
|
+ a->bfound = -1;
|
|
+ a->bwh = -1;
|
|
+ dinfo = au_di(a->dentry);
|
|
+ au_di_cp(tmp, dinfo);
|
|
+ au_di_swap(tmp, dinfo);
|
|
+
|
|
+ /* returns the number of positive dentries */
|
|
+ err = au_lkup_dentry(a->dentry, a->mvd_bsrc + 1,
|
|
+ /* AuLkup_IGNORE_PERM */ 0);
|
|
+ if (!err)
|
|
+ a->bwh = au_dbwh(a->dentry);
|
|
+ else if (err > 0)
|
|
+ a->bfound = au_dbtop(a->dentry);
|
|
+
|
|
+ au_di_swap(tmp, dinfo);
|
|
+ au_rw_write_unlock(&tmp->di_rwsem);
|
|
+ au_di_free(tmp);
|
|
+ if (unlikely(err < 0))
|
|
+ AU_MVD_PR(dmsg, "failed look-up lower\n");
|
|
+
|
|
+ /*
|
|
+ * here, we have these cases.
|
|
+ * bfound == -1
|
|
+ * no positive dentry under bsrc. there are more sub-cases.
|
|
+ * bwh < 0
|
|
+ * there no whiteout, we can safely move-down.
|
|
+ * bwh <= bsrc
|
|
+ * impossible
|
|
+ * bsrc < bwh && bwh < bdst
|
|
+ * there is a whiteout on RO branch. cannot proceed.
|
|
+ * bwh == bdst
|
|
+ * there is a whiteout on the RW target branch. it should
|
|
+ * be removed.
|
|
+ * bdst < bwh
|
|
+ * there is a whiteout somewhere unrelated branch.
|
|
+ * -1 < bfound && bfound <= bsrc
|
|
+ * impossible.
|
|
+ * bfound < bdst
|
|
+ * found, but it is on RO branch between bsrc and bdst. cannot
|
|
+ * proceed.
|
|
+ * bfound == bdst
|
|
+ * found, replace it if AUFS_MVDOWN_FORCE is set. otherwise return
|
|
+ * error.
|
|
+ * bdst < bfound
|
|
+ * found, after we create the file on bdst, it will be hidden.
|
|
+ */
|
|
+
|
|
+ AuDebugOn(a->bfound == -1
|
|
+ && a->bwh != -1
|
|
+ && a->bwh <= a->mvd_bsrc);
|
|
+ AuDebugOn(-1 < a->bfound
|
|
+ && a->bfound <= a->mvd_bsrc);
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (a->bfound == -1
|
|
+ && a->mvd_bsrc < a->bwh
|
|
+ && a->bwh != -1
|
|
+ && a->bwh < a->mvd_bdst) {
|
|
+ a->mvd_errno = EAU_MVDOWN_WHITEOUT;
|
|
+ AU_MVD_PR(dmsg, "bsrc %d, bdst %d, bfound %d, bwh %d\n",
|
|
+ a->mvd_bsrc, a->mvd_bdst, a->bfound, a->bwh);
|
|
+ goto out;
|
|
+ } else if (a->bfound != -1 && a->bfound < a->mvd_bdst) {
|
|
+ a->mvd_errno = EAU_MVDOWN_UPPER;
|
|
+ AU_MVD_PR(dmsg, "bdst %d, bfound %d\n",
|
|
+ a->mvd_bdst, a->bfound);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0; /* success */
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_mvd_args_exist(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (!(a->mvdown.flags & AUFS_MVDOWN_OWLOWER)
|
|
+ && a->bfound == a->mvd_bdst)
|
|
+ err = -EEXIST;
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_mvd_args(const unsigned char dmsg, struct au_mvd_args *a)
|
|
+{
|
|
+ int err;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ err = -EISDIR;
|
|
+ if (unlikely(S_ISDIR(a->inode->i_mode)))
|
|
+ goto out;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (!(a->mvdown.flags & AUFS_MVDOWN_BRID_UPPER))
|
|
+ a->mvd_bsrc = au_ibtop(a->inode);
|
|
+ else {
|
|
+ a->mvd_bsrc = au_br_index(a->sb, a->mvd_src_brid);
|
|
+ if (unlikely(a->mvd_bsrc < 0
|
|
+ || (a->mvd_bsrc < au_dbtop(a->dentry)
|
|
+ || au_dbbot(a->dentry) < a->mvd_bsrc
|
|
+ || !au_h_dptr(a->dentry, a->mvd_bsrc))
|
|
+ || (a->mvd_bsrc < au_ibtop(a->inode)
|
|
+ || au_ibbot(a->inode) < a->mvd_bsrc
|
|
+ || !au_h_iptr(a->inode, a->mvd_bsrc)))) {
|
|
+ a->mvd_errno = EAU_MVDOWN_NOUPPER;
|
|
+ AU_MVD_PR(dmsg, "no upper\n");
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+ if (unlikely(a->mvd_bsrc == au_sbbot(a->sb))) {
|
|
+ a->mvd_errno = EAU_MVDOWN_BOTTOM;
|
|
+ AU_MVD_PR(dmsg, "on the bottom\n");
|
|
+ goto out;
|
|
+ }
|
|
+ a->mvd_h_src_inode = au_h_iptr(a->inode, a->mvd_bsrc);
|
|
+ br = au_sbr(a->sb, a->mvd_bsrc);
|
|
+ err = au_br_rdonly(br);
|
|
+ if (!(a->mvdown.flags & AUFS_MVDOWN_ROUPPER)) {
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ } else if (!(vfsub_native_ro(a->mvd_h_src_inode)
|
|
+ || IS_APPEND(a->mvd_h_src_inode))) {
|
|
+ if (err)
|
|
+ a->mvdown.flags |= AUFS_MVDOWN_ROUPPER_R;
|
|
+ /* go on */
|
|
+ } else
|
|
+ goto out;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (!(a->mvdown.flags & AUFS_MVDOWN_BRID_LOWER)) {
|
|
+ a->mvd_bdst = find_lower_writable(a);
|
|
+ if (unlikely(a->mvd_bdst < 0)) {
|
|
+ a->mvd_errno = EAU_MVDOWN_BOTTOM;
|
|
+ AU_MVD_PR(dmsg, "no writable lower branch\n");
|
|
+ goto out;
|
|
+ }
|
|
+ } else {
|
|
+ a->mvd_bdst = au_br_index(a->sb, a->mvd_dst_brid);
|
|
+ if (unlikely(a->mvd_bdst < 0
|
|
+ || au_sbbot(a->sb) < a->mvd_bdst)) {
|
|
+ a->mvd_errno = EAU_MVDOWN_NOLOWERBR;
|
|
+ AU_MVD_PR(dmsg, "no lower brid\n");
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = au_mvd_args_busy(dmsg, a);
|
|
+ if (!err)
|
|
+ err = au_mvd_args_parent(dmsg, a);
|
|
+ if (!err)
|
|
+ err = au_mvd_args_intermediate(dmsg, a);
|
|
+ if (!err)
|
|
+ err = au_mvd_args_exist(dmsg, a);
|
|
+ if (!err)
|
|
+ AuDbg("b%d, b%d\n", a->mvd_bsrc, a->mvd_bdst);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_mvdown(struct dentry *dentry, struct aufs_mvdown __user *uarg)
|
|
+{
|
|
+ int err, e;
|
|
+ unsigned char dmsg;
|
|
+ struct au_mvd_args *args;
|
|
+ struct inode *inode;
|
|
+
|
|
+ inode = d_inode(dentry);
|
|
+ err = -EPERM;
|
|
+ if (unlikely(!capable(CAP_SYS_ADMIN)))
|
|
+ goto out;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ args = kmalloc(sizeof(*args), GFP_NOFS);
|
|
+ if (unlikely(!args))
|
|
+ goto out;
|
|
+
|
|
+ err = copy_from_user(&args->mvdown, uarg, sizeof(args->mvdown));
|
|
+ if (!err)
|
|
+ /* VERIFY_WRITE */
|
|
+ err = !access_ok(uarg, sizeof(*uarg));
|
|
+ if (unlikely(err)) {
|
|
+ err = -EFAULT;
|
|
+ AuTraceErr(err);
|
|
+ goto out_free;
|
|
+ }
|
|
+ AuDbg("flags 0x%x\n", args->mvdown.flags);
|
|
+ args->mvdown.flags &= ~(AUFS_MVDOWN_ROLOWER_R | AUFS_MVDOWN_ROUPPER_R);
|
|
+ args->mvdown.au_errno = 0;
|
|
+ args->dentry = dentry;
|
|
+ args->inode = inode;
|
|
+ args->sb = dentry->d_sb;
|
|
+
|
|
+ err = -ENOENT;
|
|
+ dmsg = !!(args->mvdown.flags & AUFS_MVDOWN_DMSG);
|
|
+ args->parent = dget_parent(dentry);
|
|
+ args->dir = d_inode(args->parent);
|
|
+ inode_lock_nested(args->dir, I_MUTEX_PARENT);
|
|
+ dput(args->parent);
|
|
+ if (unlikely(args->parent != dentry->d_parent)) {
|
|
+ AU_MVD_PR(dmsg, "parent dir is moved\n");
|
|
+ goto out_dir;
|
|
+ }
|
|
+
|
|
+ inode_lock_nested(inode, I_MUTEX_CHILD);
|
|
+ err = aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH | AuLock_NOPLMW);
|
|
+ if (unlikely(err))
|
|
+ goto out_inode;
|
|
+
|
|
+ di_write_lock_parent(args->parent);
|
|
+ err = au_mvd_args(dmsg, args);
|
|
+ if (unlikely(err))
|
|
+ goto out_parent;
|
|
+
|
|
+ err = au_do_mvdown(dmsg, args);
|
|
+ if (unlikely(err))
|
|
+ goto out_parent;
|
|
+
|
|
+ au_cpup_attr_timesizes(args->dir);
|
|
+ au_cpup_attr_timesizes(inode);
|
|
+ if (!(args->mvdown.flags & AUFS_MVDOWN_KUPPER))
|
|
+ au_cpup_igen(inode, au_h_iptr(inode, args->mvd_bdst));
|
|
+ /* au_digen_dec(dentry); */
|
|
+
|
|
+out_parent:
|
|
+ di_write_unlock(args->parent);
|
|
+ aufs_read_unlock(dentry, AuLock_DW);
|
|
+out_inode:
|
|
+ inode_unlock(inode);
|
|
+out_dir:
|
|
+ inode_unlock(args->dir);
|
|
+out_free:
|
|
+ e = copy_to_user(uarg, &args->mvdown, sizeof(args->mvdown));
|
|
+ if (unlikely(e))
|
|
+ err = -EFAULT;
|
|
+ au_kfree_rcu(args);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/opts.c b/fs/aufs/opts.c
|
|
new file mode 100644
|
|
index 000000000000..16e785facf9b
|
|
--- /dev/null
|
|
+++ b/fs/aufs/opts.c
|
|
@@ -0,0 +1,1880 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * mount options/flags
|
|
+ */
|
|
+
|
|
+#include <linux/namei.h>
|
|
+#include <linux/types.h> /* a distribution requires */
|
|
+#include <linux/parser.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+enum {
|
|
+ Opt_br,
|
|
+ Opt_add, Opt_del, Opt_mod, Opt_append, Opt_prepend,
|
|
+ Opt_idel, Opt_imod,
|
|
+ Opt_dirwh, Opt_rdcache, Opt_rdblk, Opt_rdhash,
|
|
+ Opt_rdblk_def, Opt_rdhash_def,
|
|
+ Opt_xino, Opt_noxino,
|
|
+ Opt_trunc_xino, Opt_trunc_xino_v, Opt_notrunc_xino,
|
|
+ Opt_trunc_xino_path, Opt_itrunc_xino,
|
|
+ Opt_trunc_xib, Opt_notrunc_xib,
|
|
+ Opt_shwh, Opt_noshwh,
|
|
+ Opt_plink, Opt_noplink, Opt_list_plink,
|
|
+ Opt_udba,
|
|
+ Opt_dio, Opt_nodio,
|
|
+ Opt_diropq_a, Opt_diropq_w,
|
|
+ Opt_warn_perm, Opt_nowarn_perm,
|
|
+ Opt_wbr_copyup, Opt_wbr_create,
|
|
+ Opt_fhsm_sec,
|
|
+ Opt_verbose, Opt_noverbose,
|
|
+ Opt_sum, Opt_nosum, Opt_wsum,
|
|
+ Opt_dirperm1, Opt_nodirperm1,
|
|
+ Opt_dirren, Opt_nodirren,
|
|
+ Opt_acl, Opt_noacl,
|
|
+ Opt_tail, Opt_ignore, Opt_ignore_silent, Opt_err
|
|
+};
|
|
+
|
|
+static match_table_t options = {
|
|
+ {Opt_br, "br=%s"},
|
|
+ {Opt_br, "br:%s"},
|
|
+
|
|
+ {Opt_add, "add=%d:%s"},
|
|
+ {Opt_add, "add:%d:%s"},
|
|
+ {Opt_add, "ins=%d:%s"},
|
|
+ {Opt_add, "ins:%d:%s"},
|
|
+ {Opt_append, "append=%s"},
|
|
+ {Opt_append, "append:%s"},
|
|
+ {Opt_prepend, "prepend=%s"},
|
|
+ {Opt_prepend, "prepend:%s"},
|
|
+
|
|
+ {Opt_del, "del=%s"},
|
|
+ {Opt_del, "del:%s"},
|
|
+ /* {Opt_idel, "idel:%d"}, */
|
|
+ {Opt_mod, "mod=%s"},
|
|
+ {Opt_mod, "mod:%s"},
|
|
+ /* {Opt_imod, "imod:%d:%s"}, */
|
|
+
|
|
+ {Opt_dirwh, "dirwh=%d"},
|
|
+
|
|
+ {Opt_xino, "xino=%s"},
|
|
+ {Opt_noxino, "noxino"},
|
|
+ {Opt_trunc_xino, "trunc_xino"},
|
|
+ {Opt_trunc_xino_v, "trunc_xino_v=%d:%d"},
|
|
+ {Opt_notrunc_xino, "notrunc_xino"},
|
|
+ {Opt_trunc_xino_path, "trunc_xino=%s"},
|
|
+ {Opt_itrunc_xino, "itrunc_xino=%d"},
|
|
+ /* {Opt_zxino, "zxino=%s"}, */
|
|
+ {Opt_trunc_xib, "trunc_xib"},
|
|
+ {Opt_notrunc_xib, "notrunc_xib"},
|
|
+
|
|
+#ifdef CONFIG_PROC_FS
|
|
+ {Opt_plink, "plink"},
|
|
+#else
|
|
+ {Opt_ignore_silent, "plink"},
|
|
+#endif
|
|
+
|
|
+ {Opt_noplink, "noplink"},
|
|
+
|
|
+#ifdef CONFIG_AUFS_DEBUG
|
|
+ {Opt_list_plink, "list_plink"},
|
|
+#endif
|
|
+
|
|
+ {Opt_udba, "udba=%s"},
|
|
+
|
|
+ {Opt_dio, "dio"},
|
|
+ {Opt_nodio, "nodio"},
|
|
+
|
|
+#ifdef CONFIG_AUFS_DIRREN
|
|
+ {Opt_dirren, "dirren"},
|
|
+ {Opt_nodirren, "nodirren"},
|
|
+#else
|
|
+ {Opt_ignore, "dirren"},
|
|
+ {Opt_ignore_silent, "nodirren"},
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_AUFS_FHSM
|
|
+ {Opt_fhsm_sec, "fhsm_sec=%d"},
|
|
+#else
|
|
+ {Opt_ignore, "fhsm_sec=%d"},
|
|
+#endif
|
|
+
|
|
+ {Opt_diropq_a, "diropq=always"},
|
|
+ {Opt_diropq_a, "diropq=a"},
|
|
+ {Opt_diropq_w, "diropq=whiteouted"},
|
|
+ {Opt_diropq_w, "diropq=w"},
|
|
+
|
|
+ {Opt_warn_perm, "warn_perm"},
|
|
+ {Opt_nowarn_perm, "nowarn_perm"},
|
|
+
|
|
+ /* keep them temporary */
|
|
+ {Opt_ignore_silent, "nodlgt"},
|
|
+ {Opt_ignore, "clean_plink"},
|
|
+
|
|
+#ifdef CONFIG_AUFS_SHWH
|
|
+ {Opt_shwh, "shwh"},
|
|
+#endif
|
|
+ {Opt_noshwh, "noshwh"},
|
|
+
|
|
+ {Opt_dirperm1, "dirperm1"},
|
|
+ {Opt_nodirperm1, "nodirperm1"},
|
|
+
|
|
+ {Opt_verbose, "verbose"},
|
|
+ {Opt_verbose, "v"},
|
|
+ {Opt_noverbose, "noverbose"},
|
|
+ {Opt_noverbose, "quiet"},
|
|
+ {Opt_noverbose, "q"},
|
|
+ {Opt_noverbose, "silent"},
|
|
+
|
|
+ {Opt_sum, "sum"},
|
|
+ {Opt_nosum, "nosum"},
|
|
+ {Opt_wsum, "wsum"},
|
|
+
|
|
+ {Opt_rdcache, "rdcache=%d"},
|
|
+ {Opt_rdblk, "rdblk=%d"},
|
|
+ {Opt_rdblk_def, "rdblk=def"},
|
|
+ {Opt_rdhash, "rdhash=%d"},
|
|
+ {Opt_rdhash_def, "rdhash=def"},
|
|
+
|
|
+ {Opt_wbr_create, "create=%s"},
|
|
+ {Opt_wbr_create, "create_policy=%s"},
|
|
+ {Opt_wbr_copyup, "cpup=%s"},
|
|
+ {Opt_wbr_copyup, "copyup=%s"},
|
|
+ {Opt_wbr_copyup, "copyup_policy=%s"},
|
|
+
|
|
+ /* generic VFS flag */
|
|
+#ifdef CONFIG_FS_POSIX_ACL
|
|
+ {Opt_acl, "acl"},
|
|
+ {Opt_noacl, "noacl"},
|
|
+#else
|
|
+ {Opt_ignore, "acl"},
|
|
+ {Opt_ignore_silent, "noacl"},
|
|
+#endif
|
|
+
|
|
+ /* internal use for the scripts */
|
|
+ {Opt_ignore_silent, "si=%s"},
|
|
+
|
|
+ {Opt_br, "dirs=%s"},
|
|
+ {Opt_ignore, "debug=%d"},
|
|
+ {Opt_ignore, "delete=whiteout"},
|
|
+ {Opt_ignore, "delete=all"},
|
|
+ {Opt_ignore, "imap=%s"},
|
|
+
|
|
+ /* temporary workaround, due to old mount(8)? */
|
|
+ {Opt_ignore_silent, "relatime"},
|
|
+
|
|
+ {Opt_err, NULL}
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static const char *au_parser_pattern(int val, match_table_t tbl)
|
|
+{
|
|
+ struct match_token *p;
|
|
+
|
|
+ p = tbl;
|
|
+ while (p->pattern) {
|
|
+ if (p->token == val)
|
|
+ return p->pattern;
|
|
+ p++;
|
|
+ }
|
|
+ BUG();
|
|
+ return "??";
|
|
+}
|
|
+
|
|
+static const char *au_optstr(int *val, match_table_t tbl)
|
|
+{
|
|
+ struct match_token *p;
|
|
+ int v;
|
|
+
|
|
+ v = *val;
|
|
+ if (!v)
|
|
+ goto out;
|
|
+ p = tbl;
|
|
+ while (p->pattern) {
|
|
+ if (p->token
|
|
+ && (v & p->token) == p->token) {
|
|
+ *val &= ~p->token;
|
|
+ return p->pattern;
|
|
+ }
|
|
+ p++;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static match_table_t brperm = {
|
|
+ {AuBrPerm_RO, AUFS_BRPERM_RO},
|
|
+ {AuBrPerm_RR, AUFS_BRPERM_RR},
|
|
+ {AuBrPerm_RW, AUFS_BRPERM_RW},
|
|
+ {0, NULL}
|
|
+};
|
|
+
|
|
+static match_table_t brattr = {
|
|
+ /* general */
|
|
+ {AuBrAttr_COO_REG, AUFS_BRATTR_COO_REG},
|
|
+ {AuBrAttr_COO_ALL, AUFS_BRATTR_COO_ALL},
|
|
+ /* 'unpin' attrib is meaningless since linux-3.18-rc1 */
|
|
+ {AuBrAttr_UNPIN, AUFS_BRATTR_UNPIN},
|
|
+#ifdef CONFIG_AUFS_FHSM
|
|
+ {AuBrAttr_FHSM, AUFS_BRATTR_FHSM},
|
|
+#endif
|
|
+#ifdef CONFIG_AUFS_XATTR
|
|
+ {AuBrAttr_ICEX, AUFS_BRATTR_ICEX},
|
|
+ {AuBrAttr_ICEX_SEC, AUFS_BRATTR_ICEX_SEC},
|
|
+ {AuBrAttr_ICEX_SYS, AUFS_BRATTR_ICEX_SYS},
|
|
+ {AuBrAttr_ICEX_TR, AUFS_BRATTR_ICEX_TR},
|
|
+ {AuBrAttr_ICEX_USR, AUFS_BRATTR_ICEX_USR},
|
|
+ {AuBrAttr_ICEX_OTH, AUFS_BRATTR_ICEX_OTH},
|
|
+#endif
|
|
+
|
|
+ /* ro/rr branch */
|
|
+ {AuBrRAttr_WH, AUFS_BRRATTR_WH},
|
|
+
|
|
+ /* rw branch */
|
|
+ {AuBrWAttr_MOO, AUFS_BRWATTR_MOO},
|
|
+ {AuBrWAttr_NoLinkWH, AUFS_BRWATTR_NLWH},
|
|
+
|
|
+ {0, NULL}
|
|
+};
|
|
+
|
|
+static int br_attr_val(char *str, match_table_t table, substring_t args[])
|
|
+{
|
|
+ int attr, v;
|
|
+ char *p;
|
|
+
|
|
+ attr = 0;
|
|
+ do {
|
|
+ p = strchr(str, '+');
|
|
+ if (p)
|
|
+ *p = 0;
|
|
+ v = match_token(str, table, args);
|
|
+ if (v) {
|
|
+ if (v & AuBrAttr_CMOO_Mask)
|
|
+ attr &= ~AuBrAttr_CMOO_Mask;
|
|
+ attr |= v;
|
|
+ } else {
|
|
+ if (p)
|
|
+ *p = '+';
|
|
+ pr_warn("ignored branch attribute %s\n", str);
|
|
+ break;
|
|
+ }
|
|
+ if (p)
|
|
+ str = p + 1;
|
|
+ } while (p);
|
|
+
|
|
+ return attr;
|
|
+}
|
|
+
|
|
+static int au_do_optstr_br_attr(au_br_perm_str_t *str, int perm)
|
|
+{
|
|
+ int sz;
|
|
+ const char *p;
|
|
+ char *q;
|
|
+
|
|
+ q = str->a;
|
|
+ *q = 0;
|
|
+ p = au_optstr(&perm, brattr);
|
|
+ if (p) {
|
|
+ sz = strlen(p);
|
|
+ memcpy(q, p, sz + 1);
|
|
+ q += sz;
|
|
+ } else
|
|
+ goto out;
|
|
+
|
|
+ do {
|
|
+ p = au_optstr(&perm, brattr);
|
|
+ if (p) {
|
|
+ *q++ = '+';
|
|
+ sz = strlen(p);
|
|
+ memcpy(q, p, sz + 1);
|
|
+ q += sz;
|
|
+ }
|
|
+ } while (p);
|
|
+
|
|
+out:
|
|
+ return q - str->a;
|
|
+}
|
|
+
|
|
+static int noinline_for_stack br_perm_val(char *perm)
|
|
+{
|
|
+ int val, bad, sz;
|
|
+ char *p;
|
|
+ substring_t args[MAX_OPT_ARGS];
|
|
+ au_br_perm_str_t attr;
|
|
+
|
|
+ p = strchr(perm, '+');
|
|
+ if (p)
|
|
+ *p = 0;
|
|
+ val = match_token(perm, brperm, args);
|
|
+ if (!val) {
|
|
+ if (p)
|
|
+ *p = '+';
|
|
+ pr_warn("ignored branch permission %s\n", perm);
|
|
+ val = AuBrPerm_RO;
|
|
+ goto out;
|
|
+ }
|
|
+ if (!p)
|
|
+ goto out;
|
|
+
|
|
+ val |= br_attr_val(p + 1, brattr, args);
|
|
+
|
|
+ bad = 0;
|
|
+ switch (val & AuBrPerm_Mask) {
|
|
+ case AuBrPerm_RO:
|
|
+ case AuBrPerm_RR:
|
|
+ bad = val & AuBrWAttr_Mask;
|
|
+ val &= ~AuBrWAttr_Mask;
|
|
+ break;
|
|
+ case AuBrPerm_RW:
|
|
+ bad = val & AuBrRAttr_Mask;
|
|
+ val &= ~AuBrRAttr_Mask;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * 'unpin' attrib becomes meaningless since linux-3.18-rc1, but aufs
|
|
+ * does not treat it as an error, just warning.
|
|
+ * this is a tiny guard for the user operation.
|
|
+ */
|
|
+ if (val & AuBrAttr_UNPIN) {
|
|
+ bad |= AuBrAttr_UNPIN;
|
|
+ val &= ~AuBrAttr_UNPIN;
|
|
+ }
|
|
+
|
|
+ if (unlikely(bad)) {
|
|
+ sz = au_do_optstr_br_attr(&attr, bad);
|
|
+ AuDebugOn(!sz);
|
|
+ pr_warn("ignored branch attribute %s\n", attr.a);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return val;
|
|
+}
|
|
+
|
|
+void au_optstr_br_perm(au_br_perm_str_t *str, int perm)
|
|
+{
|
|
+ au_br_perm_str_t attr;
|
|
+ const char *p;
|
|
+ char *q;
|
|
+ int sz;
|
|
+
|
|
+ q = str->a;
|
|
+ p = au_optstr(&perm, brperm);
|
|
+ AuDebugOn(!p || !*p);
|
|
+ sz = strlen(p);
|
|
+ memcpy(q, p, sz + 1);
|
|
+ q += sz;
|
|
+
|
|
+ sz = au_do_optstr_br_attr(&attr, perm);
|
|
+ if (sz) {
|
|
+ *q++ = '+';
|
|
+ memcpy(q, attr.a, sz + 1);
|
|
+ }
|
|
+
|
|
+ AuDebugOn(strlen(str->a) >= sizeof(str->a));
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static match_table_t udbalevel = {
|
|
+ {AuOpt_UDBA_REVAL, "reval"},
|
|
+ {AuOpt_UDBA_NONE, "none"},
|
|
+#ifdef CONFIG_AUFS_HNOTIFY
|
|
+ {AuOpt_UDBA_HNOTIFY, "notify"}, /* abstraction */
|
|
+#ifdef CONFIG_AUFS_HFSNOTIFY
|
|
+ {AuOpt_UDBA_HNOTIFY, "fsnotify"},
|
|
+#endif
|
|
+#endif
|
|
+ {-1, NULL}
|
|
+};
|
|
+
|
|
+static int noinline_for_stack udba_val(char *str)
|
|
+{
|
|
+ substring_t args[MAX_OPT_ARGS];
|
|
+
|
|
+ return match_token(str, udbalevel, args);
|
|
+}
|
|
+
|
|
+const char *au_optstr_udba(int udba)
|
|
+{
|
|
+ return au_parser_pattern(udba, udbalevel);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static match_table_t au_wbr_create_policy = {
|
|
+ {AuWbrCreate_TDP, "tdp"},
|
|
+ {AuWbrCreate_TDP, "top-down-parent"},
|
|
+ {AuWbrCreate_RR, "rr"},
|
|
+ {AuWbrCreate_RR, "round-robin"},
|
|
+ {AuWbrCreate_MFS, "mfs"},
|
|
+ {AuWbrCreate_MFS, "most-free-space"},
|
|
+ {AuWbrCreate_MFSV, "mfs:%d"},
|
|
+ {AuWbrCreate_MFSV, "most-free-space:%d"},
|
|
+
|
|
+ /* top-down regardless the parent, and then mfs */
|
|
+ {AuWbrCreate_TDMFS, "tdmfs:%d"},
|
|
+ {AuWbrCreate_TDMFSV, "tdmfs:%d:%d"},
|
|
+
|
|
+ {AuWbrCreate_MFSRR, "mfsrr:%d"},
|
|
+ {AuWbrCreate_MFSRRV, "mfsrr:%d:%d"},
|
|
+ {AuWbrCreate_PMFS, "pmfs"},
|
|
+ {AuWbrCreate_PMFSV, "pmfs:%d"},
|
|
+ {AuWbrCreate_PMFSRR, "pmfsrr:%d"},
|
|
+ {AuWbrCreate_PMFSRRV, "pmfsrr:%d:%d"},
|
|
+
|
|
+ {-1, NULL}
|
|
+};
|
|
+
|
|
+static int au_wbr_mfs_wmark(substring_t *arg, char *str,
|
|
+ struct au_opt_wbr_create *create)
|
|
+{
|
|
+ int err;
|
|
+ unsigned long long ull;
|
|
+
|
|
+ err = 0;
|
|
+ if (!match_u64(arg, &ull))
|
|
+ create->mfsrr_watermark = ull;
|
|
+ else {
|
|
+ pr_err("bad integer in %s\n", str);
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_wbr_mfs_sec(substring_t *arg, char *str,
|
|
+ struct au_opt_wbr_create *create)
|
|
+{
|
|
+ int n, err;
|
|
+
|
|
+ err = 0;
|
|
+ if (!match_int(arg, &n) && 0 <= n && n <= AUFS_MFS_MAX_SEC)
|
|
+ create->mfs_second = n;
|
|
+ else {
|
|
+ pr_err("bad integer in %s\n", str);
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int noinline_for_stack
|
|
+au_wbr_create_val(char *str, struct au_opt_wbr_create *create)
|
|
+{
|
|
+ int err, e;
|
|
+ substring_t args[MAX_OPT_ARGS];
|
|
+
|
|
+ err = match_token(str, au_wbr_create_policy, args);
|
|
+ create->wbr_create = err;
|
|
+ switch (err) {
|
|
+ case AuWbrCreate_MFSRRV:
|
|
+ case AuWbrCreate_TDMFSV:
|
|
+ case AuWbrCreate_PMFSRRV:
|
|
+ e = au_wbr_mfs_wmark(&args[0], str, create);
|
|
+ if (!e)
|
|
+ e = au_wbr_mfs_sec(&args[1], str, create);
|
|
+ if (unlikely(e))
|
|
+ err = e;
|
|
+ break;
|
|
+ case AuWbrCreate_MFSRR:
|
|
+ case AuWbrCreate_TDMFS:
|
|
+ case AuWbrCreate_PMFSRR:
|
|
+ e = au_wbr_mfs_wmark(&args[0], str, create);
|
|
+ if (unlikely(e)) {
|
|
+ err = e;
|
|
+ break;
|
|
+ }
|
|
+ /*FALLTHROUGH*/
|
|
+ case AuWbrCreate_MFS:
|
|
+ case AuWbrCreate_PMFS:
|
|
+ create->mfs_second = AUFS_MFS_DEF_SEC;
|
|
+ break;
|
|
+ case AuWbrCreate_MFSV:
|
|
+ case AuWbrCreate_PMFSV:
|
|
+ e = au_wbr_mfs_sec(&args[0], str, create);
|
|
+ if (unlikely(e))
|
|
+ err = e;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+const char *au_optstr_wbr_create(int wbr_create)
|
|
+{
|
|
+ return au_parser_pattern(wbr_create, au_wbr_create_policy);
|
|
+}
|
|
+
|
|
+static match_table_t au_wbr_copyup_policy = {
|
|
+ {AuWbrCopyup_TDP, "tdp"},
|
|
+ {AuWbrCopyup_TDP, "top-down-parent"},
|
|
+ {AuWbrCopyup_BUP, "bup"},
|
|
+ {AuWbrCopyup_BUP, "bottom-up-parent"},
|
|
+ {AuWbrCopyup_BU, "bu"},
|
|
+ {AuWbrCopyup_BU, "bottom-up"},
|
|
+ {-1, NULL}
|
|
+};
|
|
+
|
|
+static int noinline_for_stack au_wbr_copyup_val(char *str)
|
|
+{
|
|
+ substring_t args[MAX_OPT_ARGS];
|
|
+
|
|
+ return match_token(str, au_wbr_copyup_policy, args);
|
|
+}
|
|
+
|
|
+const char *au_optstr_wbr_copyup(int wbr_copyup)
|
|
+{
|
|
+ return au_parser_pattern(wbr_copyup, au_wbr_copyup_policy);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static const int lkup_dirflags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
|
|
+
|
|
+static void dump_opts(struct au_opts *opts)
|
|
+{
|
|
+#ifdef CONFIG_AUFS_DEBUG
|
|
+ /* reduce stack space */
|
|
+ union {
|
|
+ struct au_opt_add *add;
|
|
+ struct au_opt_del *del;
|
|
+ struct au_opt_mod *mod;
|
|
+ struct au_opt_xino *xino;
|
|
+ struct au_opt_xino_itrunc *xino_itrunc;
|
|
+ struct au_opt_wbr_create *create;
|
|
+ } u;
|
|
+ struct au_opt *opt;
|
|
+
|
|
+ opt = opts->opt;
|
|
+ while (opt->type != Opt_tail) {
|
|
+ switch (opt->type) {
|
|
+ case Opt_add:
|
|
+ u.add = &opt->add;
|
|
+ AuDbg("add {b%d, %s, 0x%x, %p}\n",
|
|
+ u.add->bindex, u.add->pathname, u.add->perm,
|
|
+ u.add->path.dentry);
|
|
+ break;
|
|
+ case Opt_del:
|
|
+ case Opt_idel:
|
|
+ u.del = &opt->del;
|
|
+ AuDbg("del {%s, %p}\n",
|
|
+ u.del->pathname, u.del->h_path.dentry);
|
|
+ break;
|
|
+ case Opt_mod:
|
|
+ case Opt_imod:
|
|
+ u.mod = &opt->mod;
|
|
+ AuDbg("mod {%s, 0x%x, %p}\n",
|
|
+ u.mod->path, u.mod->perm, u.mod->h_root);
|
|
+ break;
|
|
+ case Opt_append:
|
|
+ u.add = &opt->add;
|
|
+ AuDbg("append {b%d, %s, 0x%x, %p}\n",
|
|
+ u.add->bindex, u.add->pathname, u.add->perm,
|
|
+ u.add->path.dentry);
|
|
+ break;
|
|
+ case Opt_prepend:
|
|
+ u.add = &opt->add;
|
|
+ AuDbg("prepend {b%d, %s, 0x%x, %p}\n",
|
|
+ u.add->bindex, u.add->pathname, u.add->perm,
|
|
+ u.add->path.dentry);
|
|
+ break;
|
|
+ case Opt_dirwh:
|
|
+ AuDbg("dirwh %d\n", opt->dirwh);
|
|
+ break;
|
|
+ case Opt_rdcache:
|
|
+ AuDbg("rdcache %d\n", opt->rdcache);
|
|
+ break;
|
|
+ case Opt_rdblk:
|
|
+ AuDbg("rdblk %u\n", opt->rdblk);
|
|
+ break;
|
|
+ case Opt_rdblk_def:
|
|
+ AuDbg("rdblk_def\n");
|
|
+ break;
|
|
+ case Opt_rdhash:
|
|
+ AuDbg("rdhash %u\n", opt->rdhash);
|
|
+ break;
|
|
+ case Opt_rdhash_def:
|
|
+ AuDbg("rdhash_def\n");
|
|
+ break;
|
|
+ case Opt_xino:
|
|
+ u.xino = &opt->xino;
|
|
+ AuDbg("xino {%s %pD}\n", u.xino->path, u.xino->file);
|
|
+ break;
|
|
+ case Opt_trunc_xino:
|
|
+ AuLabel(trunc_xino);
|
|
+ break;
|
|
+ case Opt_notrunc_xino:
|
|
+ AuLabel(notrunc_xino);
|
|
+ break;
|
|
+ case Opt_trunc_xino_path:
|
|
+ case Opt_itrunc_xino:
|
|
+ u.xino_itrunc = &opt->xino_itrunc;
|
|
+ AuDbg("trunc_xino %d\n", u.xino_itrunc->bindex);
|
|
+ break;
|
|
+ case Opt_noxino:
|
|
+ AuLabel(noxino);
|
|
+ break;
|
|
+ case Opt_trunc_xib:
|
|
+ AuLabel(trunc_xib);
|
|
+ break;
|
|
+ case Opt_notrunc_xib:
|
|
+ AuLabel(notrunc_xib);
|
|
+ break;
|
|
+ case Opt_shwh:
|
|
+ AuLabel(shwh);
|
|
+ break;
|
|
+ case Opt_noshwh:
|
|
+ AuLabel(noshwh);
|
|
+ break;
|
|
+ case Opt_dirperm1:
|
|
+ AuLabel(dirperm1);
|
|
+ break;
|
|
+ case Opt_nodirperm1:
|
|
+ AuLabel(nodirperm1);
|
|
+ break;
|
|
+ case Opt_plink:
|
|
+ AuLabel(plink);
|
|
+ break;
|
|
+ case Opt_noplink:
|
|
+ AuLabel(noplink);
|
|
+ break;
|
|
+ case Opt_list_plink:
|
|
+ AuLabel(list_plink);
|
|
+ break;
|
|
+ case Opt_udba:
|
|
+ AuDbg("udba %d, %s\n",
|
|
+ opt->udba, au_optstr_udba(opt->udba));
|
|
+ break;
|
|
+ case Opt_dio:
|
|
+ AuLabel(dio);
|
|
+ break;
|
|
+ case Opt_nodio:
|
|
+ AuLabel(nodio);
|
|
+ break;
|
|
+ case Opt_diropq_a:
|
|
+ AuLabel(diropq_a);
|
|
+ break;
|
|
+ case Opt_diropq_w:
|
|
+ AuLabel(diropq_w);
|
|
+ break;
|
|
+ case Opt_warn_perm:
|
|
+ AuLabel(warn_perm);
|
|
+ break;
|
|
+ case Opt_nowarn_perm:
|
|
+ AuLabel(nowarn_perm);
|
|
+ break;
|
|
+ case Opt_verbose:
|
|
+ AuLabel(verbose);
|
|
+ break;
|
|
+ case Opt_noverbose:
|
|
+ AuLabel(noverbose);
|
|
+ break;
|
|
+ case Opt_sum:
|
|
+ AuLabel(sum);
|
|
+ break;
|
|
+ case Opt_nosum:
|
|
+ AuLabel(nosum);
|
|
+ break;
|
|
+ case Opt_wsum:
|
|
+ AuLabel(wsum);
|
|
+ break;
|
|
+ case Opt_wbr_create:
|
|
+ u.create = &opt->wbr_create;
|
|
+ AuDbg("create %d, %s\n", u.create->wbr_create,
|
|
+ au_optstr_wbr_create(u.create->wbr_create));
|
|
+ switch (u.create->wbr_create) {
|
|
+ case AuWbrCreate_MFSV:
|
|
+ case AuWbrCreate_PMFSV:
|
|
+ AuDbg("%d sec\n", u.create->mfs_second);
|
|
+ break;
|
|
+ case AuWbrCreate_MFSRR:
|
|
+ case AuWbrCreate_TDMFS:
|
|
+ AuDbg("%llu watermark\n",
|
|
+ u.create->mfsrr_watermark);
|
|
+ break;
|
|
+ case AuWbrCreate_MFSRRV:
|
|
+ case AuWbrCreate_TDMFSV:
|
|
+ case AuWbrCreate_PMFSRRV:
|
|
+ AuDbg("%llu watermark, %d sec\n",
|
|
+ u.create->mfsrr_watermark,
|
|
+ u.create->mfs_second);
|
|
+ break;
|
|
+ }
|
|
+ break;
|
|
+ case Opt_wbr_copyup:
|
|
+ AuDbg("copyup %d, %s\n", opt->wbr_copyup,
|
|
+ au_optstr_wbr_copyup(opt->wbr_copyup));
|
|
+ break;
|
|
+ case Opt_fhsm_sec:
|
|
+ AuDbg("fhsm_sec %u\n", opt->fhsm_second);
|
|
+ break;
|
|
+ case Opt_dirren:
|
|
+ AuLabel(dirren);
|
|
+ break;
|
|
+ case Opt_nodirren:
|
|
+ AuLabel(nodirren);
|
|
+ break;
|
|
+ case Opt_acl:
|
|
+ AuLabel(acl);
|
|
+ break;
|
|
+ case Opt_noacl:
|
|
+ AuLabel(noacl);
|
|
+ break;
|
|
+ default:
|
|
+ BUG();
|
|
+ }
|
|
+ opt++;
|
|
+ }
|
|
+#endif
|
|
+}
|
|
+
|
|
+void au_opts_free(struct au_opts *opts)
|
|
+{
|
|
+ struct au_opt *opt;
|
|
+
|
|
+ opt = opts->opt;
|
|
+ while (opt->type != Opt_tail) {
|
|
+ switch (opt->type) {
|
|
+ case Opt_add:
|
|
+ case Opt_append:
|
|
+ case Opt_prepend:
|
|
+ path_put(&opt->add.path);
|
|
+ break;
|
|
+ case Opt_del:
|
|
+ case Opt_idel:
|
|
+ path_put(&opt->del.h_path);
|
|
+ break;
|
|
+ case Opt_mod:
|
|
+ case Opt_imod:
|
|
+ dput(opt->mod.h_root);
|
|
+ break;
|
|
+ case Opt_xino:
|
|
+ fput(opt->xino.file);
|
|
+ break;
|
|
+ }
|
|
+ opt++;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int opt_add(struct au_opt *opt, char *opt_str, unsigned long sb_flags,
|
|
+ aufs_bindex_t bindex)
|
|
+{
|
|
+ int err;
|
|
+ struct au_opt_add *add = &opt->add;
|
|
+ char *p;
|
|
+
|
|
+ add->bindex = bindex;
|
|
+ add->perm = AuBrPerm_RO;
|
|
+ add->pathname = opt_str;
|
|
+ p = strchr(opt_str, '=');
|
|
+ if (p) {
|
|
+ *p++ = 0;
|
|
+ if (*p)
|
|
+ add->perm = br_perm_val(p);
|
|
+ }
|
|
+
|
|
+ err = vfsub_kern_path(add->pathname, lkup_dirflags, &add->path);
|
|
+ if (!err) {
|
|
+ if (!p) {
|
|
+ add->perm = AuBrPerm_RO;
|
|
+ if (au_test_fs_rr(add->path.dentry->d_sb))
|
|
+ add->perm = AuBrPerm_RR;
|
|
+ else if (!bindex && !(sb_flags & SB_RDONLY))
|
|
+ add->perm = AuBrPerm_RW;
|
|
+ }
|
|
+ opt->type = Opt_add;
|
|
+ goto out;
|
|
+ }
|
|
+ pr_err("lookup failed %s (%d)\n", add->pathname, err);
|
|
+ err = -EINVAL;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_opts_parse_del(struct au_opt_del *del, substring_t args[])
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ del->pathname = args[0].from;
|
|
+ AuDbg("del path %s\n", del->pathname);
|
|
+
|
|
+ err = vfsub_kern_path(del->pathname, lkup_dirflags, &del->h_path);
|
|
+ if (unlikely(err))
|
|
+ pr_err("lookup failed %s (%d)\n", del->pathname, err);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+#if 0 /* reserved for future use */
|
|
+static int au_opts_parse_idel(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ struct au_opt_del *del, substring_t args[])
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *root;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ root = sb->s_root;
|
|
+ aufs_read_lock(root, AuLock_FLUSH);
|
|
+ if (bindex < 0 || au_sbbot(sb) < bindex) {
|
|
+ pr_err("out of bounds, %d\n", bindex);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ del->h_path.dentry = dget(au_h_dptr(root, bindex));
|
|
+ del->h_path.mnt = mntget(au_sbr_mnt(sb, bindex));
|
|
+
|
|
+out:
|
|
+ aufs_read_unlock(root, !AuLock_IR);
|
|
+ return err;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int noinline_for_stack
|
|
+au_opts_parse_mod(struct au_opt_mod *mod, substring_t args[])
|
|
+{
|
|
+ int err;
|
|
+ struct path path;
|
|
+ char *p;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ mod->path = args[0].from;
|
|
+ p = strchr(mod->path, '=');
|
|
+ if (unlikely(!p)) {
|
|
+ pr_err("no permission %s\n", args[0].from);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ *p++ = 0;
|
|
+ err = vfsub_kern_path(mod->path, lkup_dirflags, &path);
|
|
+ if (unlikely(err)) {
|
|
+ pr_err("lookup failed %s (%d)\n", mod->path, err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ mod->perm = br_perm_val(p);
|
|
+ AuDbg("mod path %s, perm 0x%x, %s\n", mod->path, mod->perm, p);
|
|
+ mod->h_root = dget(path.dentry);
|
|
+ path_put(&path);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+#if 0 /* reserved for future use */
|
|
+static int au_opts_parse_imod(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ struct au_opt_mod *mod, substring_t args[])
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *root;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ root = sb->s_root;
|
|
+ aufs_read_lock(root, AuLock_FLUSH);
|
|
+ if (bindex < 0 || au_sbbot(sb) < bindex) {
|
|
+ pr_err("out of bounds, %d\n", bindex);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ mod->perm = br_perm_val(args[1].from);
|
|
+ AuDbg("mod path %s, perm 0x%x, %s\n",
|
|
+ mod->path, mod->perm, args[1].from);
|
|
+ mod->h_root = dget(au_h_dptr(root, bindex));
|
|
+
|
|
+out:
|
|
+ aufs_read_unlock(root, !AuLock_IR);
|
|
+ return err;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int au_opts_parse_xino(struct super_block *sb, struct au_opt_xino *xino,
|
|
+ substring_t args[])
|
|
+{
|
|
+ int err;
|
|
+ struct file *file;
|
|
+
|
|
+ file = au_xino_create(sb, args[0].from, /*silent*/0, /*wbrtop*/0);
|
|
+ err = PTR_ERR(file);
|
|
+ if (IS_ERR(file))
|
|
+ goto out;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (unlikely(file->f_path.dentry->d_sb == sb)) {
|
|
+ fput(file);
|
|
+ pr_err("%s must be outside\n", args[0].from);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ xino->file = file;
|
|
+ xino->path = args[0].from;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int noinline_for_stack
|
|
+au_opts_parse_xino_itrunc_path(struct super_block *sb,
|
|
+ struct au_opt_xino_itrunc *xino_itrunc,
|
|
+ substring_t args[])
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bbot, bindex;
|
|
+ struct path path;
|
|
+ struct dentry *root;
|
|
+
|
|
+ err = vfsub_kern_path(args[0].from, lkup_dirflags, &path);
|
|
+ if (unlikely(err)) {
|
|
+ pr_err("lookup failed %s (%d)\n", args[0].from, err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ xino_itrunc->bindex = -1;
|
|
+ root = sb->s_root;
|
|
+ aufs_read_lock(root, AuLock_FLUSH);
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++) {
|
|
+ if (au_h_dptr(root, bindex) == path.dentry) {
|
|
+ xino_itrunc->bindex = bindex;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ aufs_read_unlock(root, !AuLock_IR);
|
|
+ path_put(&path);
|
|
+
|
|
+ if (unlikely(xino_itrunc->bindex < 0)) {
|
|
+ pr_err("no such branch %s\n", args[0].from);
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* called without aufs lock */
|
|
+int au_opts_parse(struct super_block *sb, char *str, struct au_opts *opts)
|
|
+{
|
|
+ int err, n, token;
|
|
+ aufs_bindex_t bindex;
|
|
+ unsigned char skipped;
|
|
+ struct dentry *root;
|
|
+ struct au_opt *opt, *opt_tail;
|
|
+ char *opt_str;
|
|
+ /* reduce the stack space */
|
|
+ union {
|
|
+ struct au_opt_xino_itrunc *xino_itrunc;
|
|
+ struct au_opt_wbr_create *create;
|
|
+ } u;
|
|
+ struct {
|
|
+ substring_t args[MAX_OPT_ARGS];
|
|
+ } *a;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ a = kmalloc(sizeof(*a), GFP_NOFS);
|
|
+ if (unlikely(!a))
|
|
+ goto out;
|
|
+
|
|
+ root = sb->s_root;
|
|
+ err = 0;
|
|
+ bindex = 0;
|
|
+ opt = opts->opt;
|
|
+ opt_tail = opt + opts->max_opt - 1;
|
|
+ opt->type = Opt_tail;
|
|
+ while (!err && (opt_str = strsep(&str, ",")) && *opt_str) {
|
|
+ err = -EINVAL;
|
|
+ skipped = 0;
|
|
+ token = match_token(opt_str, options, a->args);
|
|
+ switch (token) {
|
|
+ case Opt_br:
|
|
+ err = 0;
|
|
+ while (!err && (opt_str = strsep(&a->args[0].from, ":"))
|
|
+ && *opt_str) {
|
|
+ err = opt_add(opt, opt_str, opts->sb_flags,
|
|
+ bindex++);
|
|
+ if (unlikely(!err && ++opt > opt_tail)) {
|
|
+ err = -E2BIG;
|
|
+ break;
|
|
+ }
|
|
+ opt->type = Opt_tail;
|
|
+ skipped = 1;
|
|
+ }
|
|
+ break;
|
|
+ case Opt_add:
|
|
+ if (unlikely(match_int(&a->args[0], &n))) {
|
|
+ pr_err("bad integer in %s\n", opt_str);
|
|
+ break;
|
|
+ }
|
|
+ bindex = n;
|
|
+ err = opt_add(opt, a->args[1].from, opts->sb_flags,
|
|
+ bindex);
|
|
+ if (!err)
|
|
+ opt->type = token;
|
|
+ break;
|
|
+ case Opt_append:
|
|
+ err = opt_add(opt, a->args[0].from, opts->sb_flags,
|
|
+ /*dummy bindex*/1);
|
|
+ if (!err)
|
|
+ opt->type = token;
|
|
+ break;
|
|
+ case Opt_prepend:
|
|
+ err = opt_add(opt, a->args[0].from, opts->sb_flags,
|
|
+ /*bindex*/0);
|
|
+ if (!err)
|
|
+ opt->type = token;
|
|
+ break;
|
|
+ case Opt_del:
|
|
+ err = au_opts_parse_del(&opt->del, a->args);
|
|
+ if (!err)
|
|
+ opt->type = token;
|
|
+ break;
|
|
+#if 0 /* reserved for future use */
|
|
+ case Opt_idel:
|
|
+ del->pathname = "(indexed)";
|
|
+ if (unlikely(match_int(&args[0], &n))) {
|
|
+ pr_err("bad integer in %s\n", opt_str);
|
|
+ break;
|
|
+ }
|
|
+ err = au_opts_parse_idel(sb, n, &opt->del, a->args);
|
|
+ if (!err)
|
|
+ opt->type = token;
|
|
+ break;
|
|
+#endif
|
|
+ case Opt_mod:
|
|
+ err = au_opts_parse_mod(&opt->mod, a->args);
|
|
+ if (!err)
|
|
+ opt->type = token;
|
|
+ break;
|
|
+#ifdef IMOD /* reserved for future use */
|
|
+ case Opt_imod:
|
|
+ u.mod->path = "(indexed)";
|
|
+ if (unlikely(match_int(&a->args[0], &n))) {
|
|
+ pr_err("bad integer in %s\n", opt_str);
|
|
+ break;
|
|
+ }
|
|
+ err = au_opts_parse_imod(sb, n, &opt->mod, a->args);
|
|
+ if (!err)
|
|
+ opt->type = token;
|
|
+ break;
|
|
+#endif
|
|
+ case Opt_xino:
|
|
+ err = au_opts_parse_xino(sb, &opt->xino, a->args);
|
|
+ if (!err)
|
|
+ opt->type = token;
|
|
+ break;
|
|
+
|
|
+ case Opt_trunc_xino_path:
|
|
+ err = au_opts_parse_xino_itrunc_path
|
|
+ (sb, &opt->xino_itrunc, a->args);
|
|
+ if (!err)
|
|
+ opt->type = token;
|
|
+ break;
|
|
+
|
|
+ case Opt_itrunc_xino:
|
|
+ u.xino_itrunc = &opt->xino_itrunc;
|
|
+ if (unlikely(match_int(&a->args[0], &n))) {
|
|
+ pr_err("bad integer in %s\n", opt_str);
|
|
+ break;
|
|
+ }
|
|
+ u.xino_itrunc->bindex = n;
|
|
+ aufs_read_lock(root, AuLock_FLUSH);
|
|
+ if (n < 0 || au_sbbot(sb) < n) {
|
|
+ pr_err("out of bounds, %d\n", n);
|
|
+ aufs_read_unlock(root, !AuLock_IR);
|
|
+ break;
|
|
+ }
|
|
+ aufs_read_unlock(root, !AuLock_IR);
|
|
+ err = 0;
|
|
+ opt->type = token;
|
|
+ break;
|
|
+
|
|
+ case Opt_dirwh:
|
|
+ if (unlikely(match_int(&a->args[0], &opt->dirwh)))
|
|
+ break;
|
|
+ err = 0;
|
|
+ opt->type = token;
|
|
+ break;
|
|
+
|
|
+ case Opt_rdcache:
|
|
+ if (unlikely(match_int(&a->args[0], &n))) {
|
|
+ pr_err("bad integer in %s\n", opt_str);
|
|
+ break;
|
|
+ }
|
|
+ if (unlikely(n > AUFS_RDCACHE_MAX)) {
|
|
+ pr_err("rdcache must be smaller than %d\n",
|
|
+ AUFS_RDCACHE_MAX);
|
|
+ break;
|
|
+ }
|
|
+ opt->rdcache = n;
|
|
+ err = 0;
|
|
+ opt->type = token;
|
|
+ break;
|
|
+ case Opt_rdblk:
|
|
+ if (unlikely(match_int(&a->args[0], &n)
|
|
+ || n < 0
|
|
+ || n > KMALLOC_MAX_SIZE)) {
|
|
+ pr_err("bad integer in %s\n", opt_str);
|
|
+ break;
|
|
+ }
|
|
+ if (unlikely(n && n < NAME_MAX)) {
|
|
+ pr_err("rdblk must be larger than %d\n",
|
|
+ NAME_MAX);
|
|
+ break;
|
|
+ }
|
|
+ opt->rdblk = n;
|
|
+ err = 0;
|
|
+ opt->type = token;
|
|
+ break;
|
|
+ case Opt_rdhash:
|
|
+ if (unlikely(match_int(&a->args[0], &n)
|
|
+ || n < 0
|
|
+ || n * sizeof(struct hlist_head)
|
|
+ > KMALLOC_MAX_SIZE)) {
|
|
+ pr_err("bad integer in %s\n", opt_str);
|
|
+ break;
|
|
+ }
|
|
+ opt->rdhash = n;
|
|
+ err = 0;
|
|
+ opt->type = token;
|
|
+ break;
|
|
+
|
|
+ case Opt_trunc_xino:
|
|
+ case Opt_notrunc_xino:
|
|
+ case Opt_noxino:
|
|
+ case Opt_trunc_xib:
|
|
+ case Opt_notrunc_xib:
|
|
+ case Opt_shwh:
|
|
+ case Opt_noshwh:
|
|
+ case Opt_dirperm1:
|
|
+ case Opt_nodirperm1:
|
|
+ case Opt_plink:
|
|
+ case Opt_noplink:
|
|
+ case Opt_list_plink:
|
|
+ case Opt_dio:
|
|
+ case Opt_nodio:
|
|
+ case Opt_diropq_a:
|
|
+ case Opt_diropq_w:
|
|
+ case Opt_warn_perm:
|
|
+ case Opt_nowarn_perm:
|
|
+ case Opt_verbose:
|
|
+ case Opt_noverbose:
|
|
+ case Opt_sum:
|
|
+ case Opt_nosum:
|
|
+ case Opt_wsum:
|
|
+ case Opt_rdblk_def:
|
|
+ case Opt_rdhash_def:
|
|
+ case Opt_dirren:
|
|
+ case Opt_nodirren:
|
|
+ case Opt_acl:
|
|
+ case Opt_noacl:
|
|
+ err = 0;
|
|
+ opt->type = token;
|
|
+ break;
|
|
+
|
|
+ case Opt_udba:
|
|
+ opt->udba = udba_val(a->args[0].from);
|
|
+ if (opt->udba >= 0) {
|
|
+ err = 0;
|
|
+ opt->type = token;
|
|
+ } else
|
|
+ pr_err("wrong value, %s\n", opt_str);
|
|
+ break;
|
|
+
|
|
+ case Opt_wbr_create:
|
|
+ u.create = &opt->wbr_create;
|
|
+ u.create->wbr_create
|
|
+ = au_wbr_create_val(a->args[0].from, u.create);
|
|
+ if (u.create->wbr_create >= 0) {
|
|
+ err = 0;
|
|
+ opt->type = token;
|
|
+ } else
|
|
+ pr_err("wrong value, %s\n", opt_str);
|
|
+ break;
|
|
+ case Opt_wbr_copyup:
|
|
+ opt->wbr_copyup = au_wbr_copyup_val(a->args[0].from);
|
|
+ if (opt->wbr_copyup >= 0) {
|
|
+ err = 0;
|
|
+ opt->type = token;
|
|
+ } else
|
|
+ pr_err("wrong value, %s\n", opt_str);
|
|
+ break;
|
|
+
|
|
+ case Opt_fhsm_sec:
|
|
+ if (unlikely(match_int(&a->args[0], &n)
|
|
+ || n < 0)) {
|
|
+ pr_err("bad integer in %s\n", opt_str);
|
|
+ break;
|
|
+ }
|
|
+ if (sysaufs_brs) {
|
|
+ opt->fhsm_second = n;
|
|
+ opt->type = token;
|
|
+ } else
|
|
+ pr_warn("ignored %s\n", opt_str);
|
|
+ err = 0;
|
|
+ break;
|
|
+
|
|
+ case Opt_ignore:
|
|
+ pr_warn("ignored %s\n", opt_str);
|
|
+ /*FALLTHROUGH*/
|
|
+ case Opt_ignore_silent:
|
|
+ skipped = 1;
|
|
+ err = 0;
|
|
+ break;
|
|
+ case Opt_err:
|
|
+ pr_err("unknown option %s\n", opt_str);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!err && !skipped) {
|
|
+ if (unlikely(++opt > opt_tail)) {
|
|
+ err = -E2BIG;
|
|
+ opt--;
|
|
+ opt->type = Opt_tail;
|
|
+ break;
|
|
+ }
|
|
+ opt->type = Opt_tail;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ au_kfree_rcu(a);
|
|
+ dump_opts(opts);
|
|
+ if (unlikely(err))
|
|
+ au_opts_free(opts);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_opt_wbr_create(struct super_block *sb,
|
|
+ struct au_opt_wbr_create *create)
|
|
+{
|
|
+ int err;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ err = 1; /* handled */
|
|
+ sbinfo = au_sbi(sb);
|
|
+ if (sbinfo->si_wbr_create_ops->fin) {
|
|
+ err = sbinfo->si_wbr_create_ops->fin(sb);
|
|
+ if (!err)
|
|
+ err = 1;
|
|
+ }
|
|
+
|
|
+ sbinfo->si_wbr_create = create->wbr_create;
|
|
+ sbinfo->si_wbr_create_ops = au_wbr_create_ops + create->wbr_create;
|
|
+ switch (create->wbr_create) {
|
|
+ case AuWbrCreate_MFSRRV:
|
|
+ case AuWbrCreate_MFSRR:
|
|
+ case AuWbrCreate_TDMFS:
|
|
+ case AuWbrCreate_TDMFSV:
|
|
+ case AuWbrCreate_PMFSRR:
|
|
+ case AuWbrCreate_PMFSRRV:
|
|
+ sbinfo->si_wbr_mfs.mfsrr_watermark = create->mfsrr_watermark;
|
|
+ /*FALLTHROUGH*/
|
|
+ case AuWbrCreate_MFS:
|
|
+ case AuWbrCreate_MFSV:
|
|
+ case AuWbrCreate_PMFS:
|
|
+ case AuWbrCreate_PMFSV:
|
|
+ sbinfo->si_wbr_mfs.mfs_expire
|
|
+ = msecs_to_jiffies(create->mfs_second * MSEC_PER_SEC);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (sbinfo->si_wbr_create_ops->init)
|
|
+ sbinfo->si_wbr_create_ops->init(sb); /* ignore */
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * returns,
|
|
+ * plus: processed without an error
|
|
+ * zero: unprocessed
|
|
+ */
|
|
+static int au_opt_simple(struct super_block *sb, struct au_opt *opt,
|
|
+ struct au_opts *opts)
|
|
+{
|
|
+ int err;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ err = 1; /* handled */
|
|
+ sbinfo = au_sbi(sb);
|
|
+ switch (opt->type) {
|
|
+ case Opt_udba:
|
|
+ sbinfo->si_mntflags &= ~AuOptMask_UDBA;
|
|
+ sbinfo->si_mntflags |= opt->udba;
|
|
+ opts->given_udba |= opt->udba;
|
|
+ break;
|
|
+
|
|
+ case Opt_plink:
|
|
+ au_opt_set(sbinfo->si_mntflags, PLINK);
|
|
+ break;
|
|
+ case Opt_noplink:
|
|
+ if (au_opt_test(sbinfo->si_mntflags, PLINK))
|
|
+ au_plink_put(sb, /*verbose*/1);
|
|
+ au_opt_clr(sbinfo->si_mntflags, PLINK);
|
|
+ break;
|
|
+ case Opt_list_plink:
|
|
+ if (au_opt_test(sbinfo->si_mntflags, PLINK))
|
|
+ au_plink_list(sb);
|
|
+ break;
|
|
+
|
|
+ case Opt_dio:
|
|
+ au_opt_set(sbinfo->si_mntflags, DIO);
|
|
+ au_fset_opts(opts->flags, REFRESH_DYAOP);
|
|
+ break;
|
|
+ case Opt_nodio:
|
|
+ au_opt_clr(sbinfo->si_mntflags, DIO);
|
|
+ au_fset_opts(opts->flags, REFRESH_DYAOP);
|
|
+ break;
|
|
+
|
|
+ case Opt_fhsm_sec:
|
|
+ au_fhsm_set(sbinfo, opt->fhsm_second);
|
|
+ break;
|
|
+
|
|
+ case Opt_diropq_a:
|
|
+ au_opt_set(sbinfo->si_mntflags, ALWAYS_DIROPQ);
|
|
+ break;
|
|
+ case Opt_diropq_w:
|
|
+ au_opt_clr(sbinfo->si_mntflags, ALWAYS_DIROPQ);
|
|
+ break;
|
|
+
|
|
+ case Opt_warn_perm:
|
|
+ au_opt_set(sbinfo->si_mntflags, WARN_PERM);
|
|
+ break;
|
|
+ case Opt_nowarn_perm:
|
|
+ au_opt_clr(sbinfo->si_mntflags, WARN_PERM);
|
|
+ break;
|
|
+
|
|
+ case Opt_verbose:
|
|
+ au_opt_set(sbinfo->si_mntflags, VERBOSE);
|
|
+ break;
|
|
+ case Opt_noverbose:
|
|
+ au_opt_clr(sbinfo->si_mntflags, VERBOSE);
|
|
+ break;
|
|
+
|
|
+ case Opt_sum:
|
|
+ au_opt_set(sbinfo->si_mntflags, SUM);
|
|
+ break;
|
|
+ case Opt_wsum:
|
|
+ au_opt_clr(sbinfo->si_mntflags, SUM);
|
|
+ au_opt_set(sbinfo->si_mntflags, SUM_W);
|
|
+ break;
|
|
+ case Opt_nosum:
|
|
+ au_opt_clr(sbinfo->si_mntflags, SUM);
|
|
+ au_opt_clr(sbinfo->si_mntflags, SUM_W);
|
|
+ break;
|
|
+
|
|
+ case Opt_wbr_create:
|
|
+ err = au_opt_wbr_create(sb, &opt->wbr_create);
|
|
+ break;
|
|
+ case Opt_wbr_copyup:
|
|
+ sbinfo->si_wbr_copyup = opt->wbr_copyup;
|
|
+ sbinfo->si_wbr_copyup_ops = au_wbr_copyup_ops + opt->wbr_copyup;
|
|
+ break;
|
|
+
|
|
+ case Opt_dirwh:
|
|
+ sbinfo->si_dirwh = opt->dirwh;
|
|
+ break;
|
|
+
|
|
+ case Opt_rdcache:
|
|
+ sbinfo->si_rdcache
|
|
+ = msecs_to_jiffies(opt->rdcache * MSEC_PER_SEC);
|
|
+ break;
|
|
+ case Opt_rdblk:
|
|
+ sbinfo->si_rdblk = opt->rdblk;
|
|
+ break;
|
|
+ case Opt_rdblk_def:
|
|
+ sbinfo->si_rdblk = AUFS_RDBLK_DEF;
|
|
+ break;
|
|
+ case Opt_rdhash:
|
|
+ sbinfo->si_rdhash = opt->rdhash;
|
|
+ break;
|
|
+ case Opt_rdhash_def:
|
|
+ sbinfo->si_rdhash = AUFS_RDHASH_DEF;
|
|
+ break;
|
|
+
|
|
+ case Opt_shwh:
|
|
+ au_opt_set(sbinfo->si_mntflags, SHWH);
|
|
+ break;
|
|
+ case Opt_noshwh:
|
|
+ au_opt_clr(sbinfo->si_mntflags, SHWH);
|
|
+ break;
|
|
+
|
|
+ case Opt_dirperm1:
|
|
+ au_opt_set(sbinfo->si_mntflags, DIRPERM1);
|
|
+ break;
|
|
+ case Opt_nodirperm1:
|
|
+ au_opt_clr(sbinfo->si_mntflags, DIRPERM1);
|
|
+ break;
|
|
+
|
|
+ case Opt_trunc_xino:
|
|
+ au_opt_set(sbinfo->si_mntflags, TRUNC_XINO);
|
|
+ break;
|
|
+ case Opt_notrunc_xino:
|
|
+ au_opt_clr(sbinfo->si_mntflags, TRUNC_XINO);
|
|
+ break;
|
|
+
|
|
+ case Opt_trunc_xino_path:
|
|
+ case Opt_itrunc_xino:
|
|
+ err = au_xino_trunc(sb, opt->xino_itrunc.bindex,
|
|
+ /*idx_begin*/0);
|
|
+ if (!err)
|
|
+ err = 1;
|
|
+ break;
|
|
+
|
|
+ case Opt_trunc_xib:
|
|
+ au_fset_opts(opts->flags, TRUNC_XIB);
|
|
+ break;
|
|
+ case Opt_notrunc_xib:
|
|
+ au_fclr_opts(opts->flags, TRUNC_XIB);
|
|
+ break;
|
|
+
|
|
+ case Opt_dirren:
|
|
+ err = 1;
|
|
+ if (!au_opt_test(sbinfo->si_mntflags, DIRREN)) {
|
|
+ err = au_dr_opt_set(sb);
|
|
+ if (!err)
|
|
+ err = 1;
|
|
+ }
|
|
+ if (err == 1)
|
|
+ au_opt_set(sbinfo->si_mntflags, DIRREN);
|
|
+ break;
|
|
+ case Opt_nodirren:
|
|
+ err = 1;
|
|
+ if (au_opt_test(sbinfo->si_mntflags, DIRREN)) {
|
|
+ err = au_dr_opt_clr(sb, au_ftest_opts(opts->flags,
|
|
+ DR_FLUSHED));
|
|
+ if (!err)
|
|
+ err = 1;
|
|
+ }
|
|
+ if (err == 1)
|
|
+ au_opt_clr(sbinfo->si_mntflags, DIRREN);
|
|
+ break;
|
|
+
|
|
+ case Opt_acl:
|
|
+ sb->s_flags |= SB_POSIXACL;
|
|
+ break;
|
|
+ case Opt_noacl:
|
|
+ sb->s_flags &= ~SB_POSIXACL;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ err = 0;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * returns tri-state.
|
|
+ * plus: processed without an error
|
|
+ * zero: unprocessed
|
|
+ * minus: error
|
|
+ */
|
|
+static int au_opt_br(struct super_block *sb, struct au_opt *opt,
|
|
+ struct au_opts *opts)
|
|
+{
|
|
+ int err, do_refresh;
|
|
+
|
|
+ err = 0;
|
|
+ switch (opt->type) {
|
|
+ case Opt_append:
|
|
+ opt->add.bindex = au_sbbot(sb) + 1;
|
|
+ if (opt->add.bindex < 0)
|
|
+ opt->add.bindex = 0;
|
|
+ goto add;
|
|
+ /* Always goto add, not fallthrough */
|
|
+ case Opt_prepend:
|
|
+ opt->add.bindex = 0;
|
|
+ /* fallthrough */
|
|
+ add: /* indented label */
|
|
+ case Opt_add:
|
|
+ err = au_br_add(sb, &opt->add,
|
|
+ au_ftest_opts(opts->flags, REMOUNT));
|
|
+ if (!err) {
|
|
+ err = 1;
|
|
+ au_fset_opts(opts->flags, REFRESH);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case Opt_del:
|
|
+ case Opt_idel:
|
|
+ err = au_br_del(sb, &opt->del,
|
|
+ au_ftest_opts(opts->flags, REMOUNT));
|
|
+ if (!err) {
|
|
+ err = 1;
|
|
+ au_fset_opts(opts->flags, TRUNC_XIB);
|
|
+ au_fset_opts(opts->flags, REFRESH);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case Opt_mod:
|
|
+ case Opt_imod:
|
|
+ err = au_br_mod(sb, &opt->mod,
|
|
+ au_ftest_opts(opts->flags, REMOUNT),
|
|
+ &do_refresh);
|
|
+ if (!err) {
|
|
+ err = 1;
|
|
+ if (do_refresh)
|
|
+ au_fset_opts(opts->flags, REFRESH);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_opt_xino(struct super_block *sb, struct au_opt *opt,
|
|
+ struct au_opt_xino **opt_xino,
|
|
+ struct au_opts *opts)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ switch (opt->type) {
|
|
+ case Opt_xino:
|
|
+ err = au_xino_set(sb, &opt->xino,
|
|
+ !!au_ftest_opts(opts->flags, REMOUNT));
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+
|
|
+ *opt_xino = &opt->xino;
|
|
+ break;
|
|
+
|
|
+ case Opt_noxino:
|
|
+ au_xino_clr(sb);
|
|
+ *opt_xino = (void *)-1;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_opts_verify(struct super_block *sb, unsigned long sb_flags,
|
|
+ unsigned int pending)
|
|
+{
|
|
+ int err, fhsm;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ unsigned char do_plink, skip, do_free, can_no_dreval;
|
|
+ struct au_branch *br;
|
|
+ struct au_wbr *wbr;
|
|
+ struct dentry *root, *dentry;
|
|
+ struct inode *dir, *h_dir;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_hinode *hdir;
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ AuDebugOn(!(sbinfo->si_mntflags & AuOptMask_UDBA));
|
|
+
|
|
+ if (!(sb_flags & SB_RDONLY)) {
|
|
+ if (unlikely(!au_br_writable(au_sbr_perm(sb, 0))))
|
|
+ pr_warn("first branch should be rw\n");
|
|
+ if (unlikely(au_opt_test(sbinfo->si_mntflags, SHWH)))
|
|
+ pr_warn_once("shwh should be used with ro\n");
|
|
+ }
|
|
+
|
|
+ if (au_opt_test((sbinfo->si_mntflags | pending), UDBA_HNOTIFY)
|
|
+ && !au_opt_test(sbinfo->si_mntflags, XINO))
|
|
+ pr_warn_once("udba=*notify requires xino\n");
|
|
+
|
|
+ if (au_opt_test(sbinfo->si_mntflags, DIRPERM1))
|
|
+ pr_warn_once("dirperm1 breaks the protection"
|
|
+ " by the permission bits on the lower branch\n");
|
|
+
|
|
+ err = 0;
|
|
+ fhsm = 0;
|
|
+ root = sb->s_root;
|
|
+ dir = d_inode(root);
|
|
+ do_plink = !!au_opt_test(sbinfo->si_mntflags, PLINK);
|
|
+ can_no_dreval = !!au_opt_test((sbinfo->si_mntflags | pending),
|
|
+ UDBA_NONE);
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; !err && bindex <= bbot; bindex++) {
|
|
+ skip = 0;
|
|
+ h_dir = au_h_iptr(dir, bindex);
|
|
+ br = au_sbr(sb, bindex);
|
|
+
|
|
+ if ((br->br_perm & AuBrAttr_ICEX)
|
|
+ && !h_dir->i_op->listxattr)
|
|
+ br->br_perm &= ~AuBrAttr_ICEX;
|
|
+#if 0 /* untested */
|
|
+ if ((br->br_perm & AuBrAttr_ICEX_SEC)
|
|
+ && (au_br_sb(br)->s_flags & SB_NOSEC))
|
|
+ br->br_perm &= ~AuBrAttr_ICEX_SEC;
|
|
+#endif
|
|
+
|
|
+ do_free = 0;
|
|
+ wbr = br->br_wbr;
|
|
+ if (wbr)
|
|
+ wbr_wh_read_lock(wbr);
|
|
+
|
|
+ if (!au_br_writable(br->br_perm)) {
|
|
+ do_free = !!wbr;
|
|
+ skip = (!wbr
|
|
+ || (!wbr->wbr_whbase
|
|
+ && !wbr->wbr_plink
|
|
+ && !wbr->wbr_orph));
|
|
+ } else if (!au_br_wh_linkable(br->br_perm)) {
|
|
+ /* skip = (!br->br_whbase && !br->br_orph); */
|
|
+ skip = (!wbr || !wbr->wbr_whbase);
|
|
+ if (skip && wbr) {
|
|
+ if (do_plink)
|
|
+ skip = !!wbr->wbr_plink;
|
|
+ else
|
|
+ skip = !wbr->wbr_plink;
|
|
+ }
|
|
+ } else {
|
|
+ /* skip = (br->br_whbase && br->br_ohph); */
|
|
+ skip = (wbr && wbr->wbr_whbase);
|
|
+ if (skip) {
|
|
+ if (do_plink)
|
|
+ skip = !!wbr->wbr_plink;
|
|
+ else
|
|
+ skip = !wbr->wbr_plink;
|
|
+ }
|
|
+ }
|
|
+ if (wbr)
|
|
+ wbr_wh_read_unlock(wbr);
|
|
+
|
|
+ if (can_no_dreval) {
|
|
+ dentry = br->br_path.dentry;
|
|
+ spin_lock(&dentry->d_lock);
|
|
+ if (dentry->d_flags &
|
|
+ (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE))
|
|
+ can_no_dreval = 0;
|
|
+ spin_unlock(&dentry->d_lock);
|
|
+ }
|
|
+
|
|
+ if (au_br_fhsm(br->br_perm)) {
|
|
+ fhsm++;
|
|
+ AuDebugOn(!br->br_fhsm);
|
|
+ }
|
|
+
|
|
+ if (skip)
|
|
+ continue;
|
|
+
|
|
+ hdir = au_hi(dir, bindex);
|
|
+ au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT);
|
|
+ if (wbr)
|
|
+ wbr_wh_write_lock(wbr);
|
|
+ err = au_wh_init(br, sb);
|
|
+ if (wbr)
|
|
+ wbr_wh_write_unlock(wbr);
|
|
+ au_hn_inode_unlock(hdir);
|
|
+
|
|
+ if (!err && do_free) {
|
|
+ au_kfree_rcu(wbr);
|
|
+ br->br_wbr = NULL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (can_no_dreval)
|
|
+ au_fset_si(sbinfo, NO_DREVAL);
|
|
+ else
|
|
+ au_fclr_si(sbinfo, NO_DREVAL);
|
|
+
|
|
+ if (fhsm >= 2) {
|
|
+ au_fset_si(sbinfo, FHSM);
|
|
+ for (bindex = bbot; bindex >= 0; bindex--) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (au_br_fhsm(br->br_perm)) {
|
|
+ au_fhsm_set_bottom(sb, bindex);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ au_fclr_si(sbinfo, FHSM);
|
|
+ au_fhsm_set_bottom(sb, -1);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_opts_mount(struct super_block *sb, struct au_opts *opts)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int tmp;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_opt *opt;
|
|
+ struct au_opt_xino *opt_xino, xino;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_branch *br;
|
|
+ struct inode *dir;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ err = 0;
|
|
+ opt_xino = NULL;
|
|
+ opt = opts->opt;
|
|
+ while (err >= 0 && opt->type != Opt_tail)
|
|
+ err = au_opt_simple(sb, opt++, opts);
|
|
+ if (err > 0)
|
|
+ err = 0;
|
|
+ else if (unlikely(err < 0))
|
|
+ goto out;
|
|
+
|
|
+ /* disable xino and udba temporary */
|
|
+ sbinfo = au_sbi(sb);
|
|
+ tmp = sbinfo->si_mntflags;
|
|
+ au_opt_clr(sbinfo->si_mntflags, XINO);
|
|
+ au_opt_set_udba(sbinfo->si_mntflags, UDBA_REVAL);
|
|
+
|
|
+ opt = opts->opt;
|
|
+ while (err >= 0 && opt->type != Opt_tail)
|
|
+ err = au_opt_br(sb, opt++, opts);
|
|
+ if (err > 0)
|
|
+ err = 0;
|
|
+ else if (unlikely(err < 0))
|
|
+ goto out;
|
|
+
|
|
+ bbot = au_sbbot(sb);
|
|
+ if (unlikely(bbot < 0)) {
|
|
+ err = -EINVAL;
|
|
+ pr_err("no branches\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (au_opt_test(tmp, XINO))
|
|
+ au_opt_set(sbinfo->si_mntflags, XINO);
|
|
+ opt = opts->opt;
|
|
+ while (!err && opt->type != Opt_tail)
|
|
+ err = au_opt_xino(sb, opt++, &opt_xino, opts);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ err = au_opts_verify(sb, sb->s_flags, tmp);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ /* restore xino */
|
|
+ if (au_opt_test(tmp, XINO) && !opt_xino) {
|
|
+ xino.file = au_xino_def(sb);
|
|
+ err = PTR_ERR(xino.file);
|
|
+ if (IS_ERR(xino.file))
|
|
+ goto out;
|
|
+
|
|
+ err = au_xino_set(sb, &xino, /*remount*/0);
|
|
+ fput(xino.file);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* restore udba */
|
|
+ tmp &= AuOptMask_UDBA;
|
|
+ sbinfo->si_mntflags &= ~AuOptMask_UDBA;
|
|
+ sbinfo->si_mntflags |= tmp;
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ err = au_hnotify_reset_br(tmp, br, br->br_perm);
|
|
+ if (unlikely(err))
|
|
+ AuIOErr("hnotify failed on br %d, %d, ignored\n",
|
|
+ bindex, err);
|
|
+ /* go on even if err */
|
|
+ }
|
|
+ if (au_opt_test(tmp, UDBA_HNOTIFY)) {
|
|
+ dir = d_inode(sb->s_root);
|
|
+ au_hn_reset(dir, au_hi_flags(dir, /*isdir*/1) & ~AuHi_XINO);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_opts_remount(struct super_block *sb, struct au_opts *opts)
|
|
+{
|
|
+ int err, rerr;
|
|
+ unsigned char no_dreval;
|
|
+ struct inode *dir;
|
|
+ struct au_opt_xino *opt_xino;
|
|
+ struct au_opt *opt;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ err = au_dr_opt_flush(sb);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ au_fset_opts(opts->flags, DR_FLUSHED);
|
|
+
|
|
+ dir = d_inode(sb->s_root);
|
|
+ sbinfo = au_sbi(sb);
|
|
+ opt_xino = NULL;
|
|
+ opt = opts->opt;
|
|
+ while (err >= 0 && opt->type != Opt_tail) {
|
|
+ err = au_opt_simple(sb, opt, opts);
|
|
+ if (!err)
|
|
+ err = au_opt_br(sb, opt, opts);
|
|
+ if (!err)
|
|
+ err = au_opt_xino(sb, opt, &opt_xino, opts);
|
|
+ opt++;
|
|
+ }
|
|
+ if (err > 0)
|
|
+ err = 0;
|
|
+ AuTraceErr(err);
|
|
+ /* go on even err */
|
|
+
|
|
+ no_dreval = !!au_ftest_si(sbinfo, NO_DREVAL);
|
|
+ rerr = au_opts_verify(sb, opts->sb_flags, /*pending*/0);
|
|
+ if (unlikely(rerr && !err))
|
|
+ err = rerr;
|
|
+
|
|
+ if (no_dreval != !!au_ftest_si(sbinfo, NO_DREVAL))
|
|
+ au_fset_opts(opts->flags, REFRESH_IDOP);
|
|
+
|
|
+ if (au_ftest_opts(opts->flags, TRUNC_XIB)) {
|
|
+ rerr = au_xib_trunc(sb);
|
|
+ if (unlikely(rerr && !err))
|
|
+ err = rerr;
|
|
+ }
|
|
+
|
|
+ /* will be handled by the caller */
|
|
+ if (!au_ftest_opts(opts->flags, REFRESH)
|
|
+ && (opts->given_udba
|
|
+ || au_opt_test(sbinfo->si_mntflags, XINO)
|
|
+ || au_ftest_opts(opts->flags, REFRESH_IDOP)
|
|
+ ))
|
|
+ au_fset_opts(opts->flags, REFRESH);
|
|
+
|
|
+ AuDbg("status 0x%x\n", opts->flags);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+unsigned int au_opt_udba(struct super_block *sb)
|
|
+{
|
|
+ return au_mntflags(sb) & AuOptMask_UDBA;
|
|
+}
|
|
diff --git a/fs/aufs/opts.h b/fs/aufs/opts.h
|
|
new file mode 100644
|
|
index 000000000000..79f3ea56f8bb
|
|
--- /dev/null
|
|
+++ b/fs/aufs/opts.h
|
|
@@ -0,0 +1,225 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * mount options/flags
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_OPTS_H__
|
|
+#define __AUFS_OPTS_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/path.h>
|
|
+
|
|
+struct file;
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* mount flags */
|
|
+#define AuOpt_XINO 1 /* external inode number bitmap
|
|
+ and translation table */
|
|
+#define AuOpt_TRUNC_XINO (1 << 1) /* truncate xino files */
|
|
+#define AuOpt_UDBA_NONE (1 << 2) /* users direct branch access */
|
|
+#define AuOpt_UDBA_REVAL (1 << 3)
|
|
+#define AuOpt_UDBA_HNOTIFY (1 << 4)
|
|
+#define AuOpt_SHWH (1 << 5) /* show whiteout */
|
|
+#define AuOpt_PLINK (1 << 6) /* pseudo-link */
|
|
+#define AuOpt_DIRPERM1 (1 << 7) /* ignore the lower dir's perm
|
|
+ bits */
|
|
+#define AuOpt_ALWAYS_DIROPQ (1 << 9) /* policy to creating diropq */
|
|
+#define AuOpt_SUM (1 << 10) /* summation for statfs(2) */
|
|
+#define AuOpt_SUM_W (1 << 11) /* unimplemented */
|
|
+#define AuOpt_WARN_PERM (1 << 12) /* warn when add-branch */
|
|
+#define AuOpt_VERBOSE (1 << 13) /* print the cause of error */
|
|
+#define AuOpt_DIO (1 << 14) /* direct io */
|
|
+#define AuOpt_DIRREN (1 << 15) /* directory rename */
|
|
+
|
|
+#ifndef CONFIG_AUFS_HNOTIFY
|
|
+#undef AuOpt_UDBA_HNOTIFY
|
|
+#define AuOpt_UDBA_HNOTIFY 0
|
|
+#endif
|
|
+#ifndef CONFIG_AUFS_DIRREN
|
|
+#undef AuOpt_DIRREN
|
|
+#define AuOpt_DIRREN 0
|
|
+#endif
|
|
+#ifndef CONFIG_AUFS_SHWH
|
|
+#undef AuOpt_SHWH
|
|
+#define AuOpt_SHWH 0
|
|
+#endif
|
|
+
|
|
+#define AuOpt_Def (AuOpt_XINO \
|
|
+ | AuOpt_UDBA_REVAL \
|
|
+ | AuOpt_PLINK \
|
|
+ /* | AuOpt_DIRPERM1 */ \
|
|
+ | AuOpt_WARN_PERM)
|
|
+#define AuOptMask_UDBA (AuOpt_UDBA_NONE \
|
|
+ | AuOpt_UDBA_REVAL \
|
|
+ | AuOpt_UDBA_HNOTIFY)
|
|
+
|
|
+#define au_opt_test(flags, name) (flags & AuOpt_##name)
|
|
+#define au_opt_set(flags, name) do { \
|
|
+ BUILD_BUG_ON(AuOpt_##name & AuOptMask_UDBA); \
|
|
+ ((flags) |= AuOpt_##name); \
|
|
+} while (0)
|
|
+#define au_opt_set_udba(flags, name) do { \
|
|
+ (flags) &= ~AuOptMask_UDBA; \
|
|
+ ((flags) |= AuOpt_##name); \
|
|
+} while (0)
|
|
+#define au_opt_clr(flags, name) do { \
|
|
+ ((flags) &= ~AuOpt_##name); \
|
|
+} while (0)
|
|
+
|
|
+static inline unsigned int au_opts_plink(unsigned int mntflags)
|
|
+{
|
|
+#ifdef CONFIG_PROC_FS
|
|
+ return mntflags;
|
|
+#else
|
|
+ return mntflags & ~AuOpt_PLINK;
|
|
+#endif
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* policies to select one among multiple writable branches */
|
|
+enum {
|
|
+ AuWbrCreate_TDP, /* top down parent */
|
|
+ AuWbrCreate_RR, /* round robin */
|
|
+ AuWbrCreate_MFS, /* most free space */
|
|
+ AuWbrCreate_MFSV, /* mfs with seconds */
|
|
+ AuWbrCreate_MFSRR, /* mfs then rr */
|
|
+ AuWbrCreate_MFSRRV, /* mfs then rr with seconds */
|
|
+ AuWbrCreate_TDMFS, /* top down regardless parent and mfs */
|
|
+ AuWbrCreate_TDMFSV, /* top down regardless parent and mfs */
|
|
+ AuWbrCreate_PMFS, /* parent and mfs */
|
|
+ AuWbrCreate_PMFSV, /* parent and mfs with seconds */
|
|
+ AuWbrCreate_PMFSRR, /* parent, mfs and round-robin */
|
|
+ AuWbrCreate_PMFSRRV, /* plus seconds */
|
|
+
|
|
+ AuWbrCreate_Def = AuWbrCreate_TDP
|
|
+};
|
|
+
|
|
+enum {
|
|
+ AuWbrCopyup_TDP, /* top down parent */
|
|
+ AuWbrCopyup_BUP, /* bottom up parent */
|
|
+ AuWbrCopyup_BU, /* bottom up */
|
|
+
|
|
+ AuWbrCopyup_Def = AuWbrCopyup_TDP
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_opt_add {
|
|
+ aufs_bindex_t bindex;
|
|
+ char *pathname;
|
|
+ int perm;
|
|
+ struct path path;
|
|
+};
|
|
+
|
|
+struct au_opt_del {
|
|
+ char *pathname;
|
|
+ struct path h_path;
|
|
+};
|
|
+
|
|
+struct au_opt_mod {
|
|
+ char *path;
|
|
+ int perm;
|
|
+ struct dentry *h_root;
|
|
+};
|
|
+
|
|
+struct au_opt_xino {
|
|
+ char *path;
|
|
+ struct file *file;
|
|
+};
|
|
+
|
|
+struct au_opt_xino_itrunc {
|
|
+ aufs_bindex_t bindex;
|
|
+};
|
|
+
|
|
+struct au_opt_wbr_create {
|
|
+ int wbr_create;
|
|
+ int mfs_second;
|
|
+ unsigned long long mfsrr_watermark;
|
|
+};
|
|
+
|
|
+struct au_opt {
|
|
+ int type;
|
|
+ union {
|
|
+ struct au_opt_xino xino;
|
|
+ struct au_opt_xino_itrunc xino_itrunc;
|
|
+ struct au_opt_add add;
|
|
+ struct au_opt_del del;
|
|
+ struct au_opt_mod mod;
|
|
+ int dirwh;
|
|
+ int rdcache;
|
|
+ unsigned int rdblk;
|
|
+ unsigned int rdhash;
|
|
+ int udba;
|
|
+ struct au_opt_wbr_create wbr_create;
|
|
+ int wbr_copyup;
|
|
+ unsigned int fhsm_second;
|
|
+ };
|
|
+};
|
|
+
|
|
+/* opts flags */
|
|
+#define AuOpts_REMOUNT 1
|
|
+#define AuOpts_REFRESH (1 << 1)
|
|
+#define AuOpts_TRUNC_XIB (1 << 2)
|
|
+#define AuOpts_REFRESH_DYAOP (1 << 3)
|
|
+#define AuOpts_REFRESH_IDOP (1 << 4)
|
|
+#define AuOpts_DR_FLUSHED (1 << 5)
|
|
+#define au_ftest_opts(flags, name) ((flags) & AuOpts_##name)
|
|
+#define au_fset_opts(flags, name) \
|
|
+ do { (flags) |= AuOpts_##name; } while (0)
|
|
+#define au_fclr_opts(flags, name) \
|
|
+ do { (flags) &= ~AuOpts_##name; } while (0)
|
|
+
|
|
+#ifndef CONFIG_AUFS_DIRREN
|
|
+#undef AuOpts_DR_FLUSHED
|
|
+#define AuOpts_DR_FLUSHED 0
|
|
+#endif
|
|
+
|
|
+struct au_opts {
|
|
+ struct au_opt *opt;
|
|
+ int max_opt;
|
|
+
|
|
+ unsigned int given_udba;
|
|
+ unsigned int flags;
|
|
+ unsigned long sb_flags;
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* opts.c */
|
|
+void au_optstr_br_perm(au_br_perm_str_t *str, int perm);
|
|
+const char *au_optstr_udba(int udba);
|
|
+const char *au_optstr_wbr_copyup(int wbr_copyup);
|
|
+const char *au_optstr_wbr_create(int wbr_create);
|
|
+
|
|
+void au_opts_free(struct au_opts *opts);
|
|
+struct super_block;
|
|
+int au_opts_parse(struct super_block *sb, char *str, struct au_opts *opts);
|
|
+int au_opts_verify(struct super_block *sb, unsigned long sb_flags,
|
|
+ unsigned int pending);
|
|
+int au_opts_mount(struct super_block *sb, struct au_opts *opts);
|
|
+int au_opts_remount(struct super_block *sb, struct au_opts *opts);
|
|
+
|
|
+unsigned int au_opt_udba(struct super_block *sb);
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_OPTS_H__ */
|
|
diff --git a/fs/aufs/plink.c b/fs/aufs/plink.c
|
|
new file mode 100644
|
|
index 000000000000..850829b21888
|
|
--- /dev/null
|
|
+++ b/fs/aufs/plink.c
|
|
@@ -0,0 +1,516 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * pseudo-link
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+/*
|
|
+ * the pseudo-link maintenance mode.
|
|
+ * during a user process maintains the pseudo-links,
|
|
+ * prohibit adding a new plink and branch manipulation.
|
|
+ *
|
|
+ * Flags
|
|
+ * NOPLM:
|
|
+ * For entry functions which will handle plink, and i_mutex is already held
|
|
+ * in VFS.
|
|
+ * They cannot wait and should return an error at once.
|
|
+ * Callers has to check the error.
|
|
+ * NOPLMW:
|
|
+ * For entry functions which will handle plink, but i_mutex is not held
|
|
+ * in VFS.
|
|
+ * They can wait the plink maintenance mode to finish.
|
|
+ *
|
|
+ * They behave like F_SETLK and F_SETLKW.
|
|
+ * If the caller never handle plink, then both flags are unnecessary.
|
|
+ */
|
|
+
|
|
+int au_plink_maint(struct super_block *sb, int flags)
|
|
+{
|
|
+ int err;
|
|
+ pid_t pid, ppid;
|
|
+ struct task_struct *parent, *prev;
|
|
+ struct au_sbinfo *sbi;
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ err = 0;
|
|
+ if (!au_opt_test(au_mntflags(sb), PLINK))
|
|
+ goto out;
|
|
+
|
|
+ sbi = au_sbi(sb);
|
|
+ pid = sbi->si_plink_maint_pid;
|
|
+ if (!pid || pid == current->pid)
|
|
+ goto out;
|
|
+
|
|
+ /* todo: it highly depends upon /sbin/mount.aufs */
|
|
+ prev = NULL;
|
|
+ parent = current;
|
|
+ ppid = 0;
|
|
+ rcu_read_lock();
|
|
+ while (1) {
|
|
+ parent = rcu_dereference(parent->real_parent);
|
|
+ if (parent == prev)
|
|
+ break;
|
|
+ ppid = task_pid_vnr(parent);
|
|
+ if (pid == ppid) {
|
|
+ rcu_read_unlock();
|
|
+ goto out;
|
|
+ }
|
|
+ prev = parent;
|
|
+ }
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ if (au_ftest_lock(flags, NOPLMW)) {
|
|
+ /* if there is no i_mutex lock in VFS, we don't need to wait */
|
|
+ /* AuDebugOn(!lockdep_depth(current)); */
|
|
+ while (sbi->si_plink_maint_pid) {
|
|
+ si_read_unlock(sb);
|
|
+ /* gave up wake_up_bit() */
|
|
+ wait_event(sbi->si_plink_wq, !sbi->si_plink_maint_pid);
|
|
+
|
|
+ if (au_ftest_lock(flags, FLUSH))
|
|
+ au_nwt_flush(&sbi->si_nowait);
|
|
+ si_noflush_read_lock(sb);
|
|
+ }
|
|
+ } else if (au_ftest_lock(flags, NOPLM)) {
|
|
+ AuDbg("ppid %d, pid %d\n", ppid, pid);
|
|
+ err = -EAGAIN;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_plink_maint_leave(struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ spin_lock(&sbinfo->si_plink_maint_lock);
|
|
+ sbinfo->si_plink_maint_pid = 0;
|
|
+ spin_unlock(&sbinfo->si_plink_maint_lock);
|
|
+ wake_up_all(&sbinfo->si_plink_wq);
|
|
+}
|
|
+
|
|
+int au_plink_maint_enter(struct super_block *sb)
|
|
+{
|
|
+ int err;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ err = 0;
|
|
+ sbinfo = au_sbi(sb);
|
|
+ /* make sure i am the only one in this fs */
|
|
+ si_write_lock(sb, AuLock_FLUSH);
|
|
+ if (au_opt_test(au_mntflags(sb), PLINK)) {
|
|
+ spin_lock(&sbinfo->si_plink_maint_lock);
|
|
+ if (!sbinfo->si_plink_maint_pid)
|
|
+ sbinfo->si_plink_maint_pid = current->pid;
|
|
+ else
|
|
+ err = -EBUSY;
|
|
+ spin_unlock(&sbinfo->si_plink_maint_lock);
|
|
+ }
|
|
+ si_write_unlock(sb);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_AUFS_DEBUG
|
|
+void au_plink_list(struct super_block *sb)
|
|
+{
|
|
+ int i;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos;
|
|
+ struct au_icntnr *icntnr;
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK));
|
|
+ AuDebugOn(au_plink_maint(sb, AuLock_NOPLM));
|
|
+
|
|
+ for (i = 0; i < AuPlink_NHASH; i++) {
|
|
+ hbl = sbinfo->si_plink + i;
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_for_each_entry(icntnr, pos, hbl, plink)
|
|
+ AuDbg("%lu\n", icntnr->vfs_inode.i_ino);
|
|
+ hlist_bl_unlock(hbl);
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* is the inode pseudo-linked? */
|
|
+int au_plink_test(struct inode *inode)
|
|
+{
|
|
+ int found, i;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos;
|
|
+ struct au_icntnr *icntnr;
|
|
+
|
|
+ sbinfo = au_sbi(inode->i_sb);
|
|
+ AuRwMustAnyLock(&sbinfo->si_rwsem);
|
|
+ AuDebugOn(!au_opt_test(au_mntflags(inode->i_sb), PLINK));
|
|
+ AuDebugOn(au_plink_maint(inode->i_sb, AuLock_NOPLM));
|
|
+
|
|
+ found = 0;
|
|
+ i = au_plink_hash(inode->i_ino);
|
|
+ hbl = sbinfo->si_plink + i;
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_for_each_entry(icntnr, pos, hbl, plink)
|
|
+ if (&icntnr->vfs_inode == inode) {
|
|
+ found = 1;
|
|
+ break;
|
|
+ }
|
|
+ hlist_bl_unlock(hbl);
|
|
+ return found;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * generate a name for plink.
|
|
+ * the file will be stored under AUFS_WH_PLINKDIR.
|
|
+ */
|
|
+/* 20 is max digits length of ulong 64 */
|
|
+#define PLINK_NAME_LEN ((20 + 1) * 2)
|
|
+
|
|
+static int plink_name(char *name, int len, struct inode *inode,
|
|
+ aufs_bindex_t bindex)
|
|
+{
|
|
+ int rlen;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+ rlen = snprintf(name, len, "%lu.%lu", inode->i_ino, h_inode->i_ino);
|
|
+ return rlen;
|
|
+}
|
|
+
|
|
+struct au_do_plink_lkup_args {
|
|
+ struct dentry **errp;
|
|
+ struct qstr *tgtname;
|
|
+ struct dentry *h_parent;
|
|
+ struct au_branch *br;
|
|
+};
|
|
+
|
|
+static struct dentry *au_do_plink_lkup(struct qstr *tgtname,
|
|
+ struct dentry *h_parent,
|
|
+ struct au_branch *br)
|
|
+{
|
|
+ struct dentry *h_dentry;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ h_inode = d_inode(h_parent);
|
|
+ inode_lock_shared_nested(h_inode, AuLsc_I_CHILD2);
|
|
+ h_dentry = vfsub_lkup_one(tgtname, h_parent);
|
|
+ inode_unlock_shared(h_inode);
|
|
+ return h_dentry;
|
|
+}
|
|
+
|
|
+static void au_call_do_plink_lkup(void *args)
|
|
+{
|
|
+ struct au_do_plink_lkup_args *a = args;
|
|
+ *a->errp = au_do_plink_lkup(a->tgtname, a->h_parent, a->br);
|
|
+}
|
|
+
|
|
+/* lookup the plink-ed @inode under the branch at @bindex */
|
|
+struct dentry *au_plink_lkup(struct inode *inode, aufs_bindex_t bindex)
|
|
+{
|
|
+ struct dentry *h_dentry, *h_parent;
|
|
+ struct au_branch *br;
|
|
+ int wkq_err;
|
|
+ char a[PLINK_NAME_LEN];
|
|
+ struct qstr tgtname = QSTR_INIT(a, 0);
|
|
+
|
|
+ AuDebugOn(au_plink_maint(inode->i_sb, AuLock_NOPLM));
|
|
+
|
|
+ br = au_sbr(inode->i_sb, bindex);
|
|
+ h_parent = br->br_wbr->wbr_plink;
|
|
+ tgtname.len = plink_name(a, sizeof(a), inode, bindex);
|
|
+
|
|
+ if (!uid_eq(current_fsuid(), GLOBAL_ROOT_UID)) {
|
|
+ struct au_do_plink_lkup_args args = {
|
|
+ .errp = &h_dentry,
|
|
+ .tgtname = &tgtname,
|
|
+ .h_parent = h_parent,
|
|
+ .br = br
|
|
+ };
|
|
+
|
|
+ wkq_err = au_wkq_wait(au_call_do_plink_lkup, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ h_dentry = ERR_PTR(wkq_err);
|
|
+ } else
|
|
+ h_dentry = au_do_plink_lkup(&tgtname, h_parent, br);
|
|
+
|
|
+ return h_dentry;
|
|
+}
|
|
+
|
|
+/* create a pseudo-link */
|
|
+static int do_whplink(struct qstr *tgt, struct dentry *h_parent,
|
|
+ struct dentry *h_dentry, struct au_branch *br)
|
|
+{
|
|
+ int err;
|
|
+ struct path h_path = {
|
|
+ .mnt = au_br_mnt(br)
|
|
+ };
|
|
+ struct inode *h_dir, *delegated;
|
|
+
|
|
+ h_dir = d_inode(h_parent);
|
|
+ inode_lock_nested(h_dir, AuLsc_I_CHILD2);
|
|
+again:
|
|
+ h_path.dentry = vfsub_lkup_one(tgt, h_parent);
|
|
+ err = PTR_ERR(h_path.dentry);
|
|
+ if (IS_ERR(h_path.dentry))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ /* wh.plink dir is not monitored */
|
|
+ /* todo: is it really safe? */
|
|
+ if (d_is_positive(h_path.dentry)
|
|
+ && d_inode(h_path.dentry) != d_inode(h_dentry)) {
|
|
+ delegated = NULL;
|
|
+ err = vfsub_unlink(h_dir, &h_path, &delegated, /*force*/0);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal unlink\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ dput(h_path.dentry);
|
|
+ h_path.dentry = NULL;
|
|
+ if (!err)
|
|
+ goto again;
|
|
+ }
|
|
+ if (!err && d_is_negative(h_path.dentry)) {
|
|
+ delegated = NULL;
|
|
+ err = vfsub_link(h_dentry, h_dir, &h_path, &delegated);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal link\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ }
|
|
+ dput(h_path.dentry);
|
|
+
|
|
+out:
|
|
+ inode_unlock(h_dir);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct do_whplink_args {
|
|
+ int *errp;
|
|
+ struct qstr *tgt;
|
|
+ struct dentry *h_parent;
|
|
+ struct dentry *h_dentry;
|
|
+ struct au_branch *br;
|
|
+};
|
|
+
|
|
+static void call_do_whplink(void *args)
|
|
+{
|
|
+ struct do_whplink_args *a = args;
|
|
+ *a->errp = do_whplink(a->tgt, a->h_parent, a->h_dentry, a->br);
|
|
+}
|
|
+
|
|
+static int whplink(struct dentry *h_dentry, struct inode *inode,
|
|
+ aufs_bindex_t bindex, struct au_branch *br)
|
|
+{
|
|
+ int err, wkq_err;
|
|
+ struct au_wbr *wbr;
|
|
+ struct dentry *h_parent;
|
|
+ char a[PLINK_NAME_LEN];
|
|
+ struct qstr tgtname = QSTR_INIT(a, 0);
|
|
+
|
|
+ wbr = au_sbr(inode->i_sb, bindex)->br_wbr;
|
|
+ h_parent = wbr->wbr_plink;
|
|
+ tgtname.len = plink_name(a, sizeof(a), inode, bindex);
|
|
+
|
|
+ /* always superio. */
|
|
+ if (!uid_eq(current_fsuid(), GLOBAL_ROOT_UID)) {
|
|
+ struct do_whplink_args args = {
|
|
+ .errp = &err,
|
|
+ .tgt = &tgtname,
|
|
+ .h_parent = h_parent,
|
|
+ .h_dentry = h_dentry,
|
|
+ .br = br
|
|
+ };
|
|
+ wkq_err = au_wkq_wait(call_do_whplink, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+ } else
|
|
+ err = do_whplink(&tgtname, h_parent, h_dentry, br);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * create a new pseudo-link for @h_dentry on @bindex.
|
|
+ * the linked inode is held in aufs @inode.
|
|
+ */
|
|
+void au_plink_append(struct inode *inode, aufs_bindex_t bindex,
|
|
+ struct dentry *h_dentry)
|
|
+{
|
|
+ struct super_block *sb;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos;
|
|
+ struct au_icntnr *icntnr;
|
|
+ int found, err, cnt, i;
|
|
+
|
|
+ sb = inode->i_sb;
|
|
+ sbinfo = au_sbi(sb);
|
|
+ AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK));
|
|
+ AuDebugOn(au_plink_maint(sb, AuLock_NOPLM));
|
|
+
|
|
+ found = au_plink_test(inode);
|
|
+ if (found)
|
|
+ return;
|
|
+
|
|
+ i = au_plink_hash(inode->i_ino);
|
|
+ hbl = sbinfo->si_plink + i;
|
|
+ au_igrab(inode);
|
|
+
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_for_each_entry(icntnr, pos, hbl, plink) {
|
|
+ if (&icntnr->vfs_inode == inode) {
|
|
+ found = 1;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (!found) {
|
|
+ icntnr = container_of(inode, struct au_icntnr, vfs_inode);
|
|
+ hlist_bl_add_head(&icntnr->plink, hbl);
|
|
+ }
|
|
+ hlist_bl_unlock(hbl);
|
|
+ if (!found) {
|
|
+ cnt = au_hbl_count(hbl);
|
|
+#define msg "unexpectedly unbalanced or too many pseudo-links"
|
|
+ if (cnt > AUFS_PLINK_WARN)
|
|
+ AuWarn1(msg ", %d\n", cnt);
|
|
+#undef msg
|
|
+ err = whplink(h_dentry, inode, bindex, au_sbr(sb, bindex));
|
|
+ if (unlikely(err)) {
|
|
+ pr_warn("err %d, damaged pseudo link.\n", err);
|
|
+ au_hbl_del(&icntnr->plink, hbl);
|
|
+ iput(&icntnr->vfs_inode);
|
|
+ }
|
|
+ } else
|
|
+ iput(&icntnr->vfs_inode);
|
|
+}
|
|
+
|
|
+/* free all plinks */
|
|
+void au_plink_put(struct super_block *sb, int verbose)
|
|
+{
|
|
+ int i, warned;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos, *tmp;
|
|
+ struct au_icntnr *icntnr;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK));
|
|
+ AuDebugOn(au_plink_maint(sb, AuLock_NOPLM));
|
|
+
|
|
+ /* no spin_lock since sbinfo is write-locked */
|
|
+ warned = 0;
|
|
+ for (i = 0; i < AuPlink_NHASH; i++) {
|
|
+ hbl = sbinfo->si_plink + i;
|
|
+ if (!warned && verbose && !hlist_bl_empty(hbl)) {
|
|
+ pr_warn("pseudo-link is not flushed");
|
|
+ warned = 1;
|
|
+ }
|
|
+ hlist_bl_for_each_entry_safe(icntnr, pos, tmp, hbl, plink)
|
|
+ iput(&icntnr->vfs_inode);
|
|
+ INIT_HLIST_BL_HEAD(hbl);
|
|
+ }
|
|
+}
|
|
+
|
|
+void au_plink_clean(struct super_block *sb, int verbose)
|
|
+{
|
|
+ struct dentry *root;
|
|
+
|
|
+ root = sb->s_root;
|
|
+ aufs_write_lock(root);
|
|
+ if (au_opt_test(au_mntflags(sb), PLINK))
|
|
+ au_plink_put(sb, verbose);
|
|
+ aufs_write_unlock(root);
|
|
+}
|
|
+
|
|
+static int au_plink_do_half_refresh(struct inode *inode, aufs_bindex_t br_id)
|
|
+{
|
|
+ int do_put;
|
|
+ aufs_bindex_t btop, bbot, bindex;
|
|
+
|
|
+ do_put = 0;
|
|
+ btop = au_ibtop(inode);
|
|
+ bbot = au_ibbot(inode);
|
|
+ if (btop >= 0) {
|
|
+ for (bindex = btop; bindex <= bbot; bindex++) {
|
|
+ if (!au_h_iptr(inode, bindex)
|
|
+ || au_ii_br_id(inode, bindex) != br_id)
|
|
+ continue;
|
|
+ au_set_h_iptr(inode, bindex, NULL, 0);
|
|
+ do_put = 1;
|
|
+ break;
|
|
+ }
|
|
+ if (do_put)
|
|
+ for (bindex = btop; bindex <= bbot; bindex++)
|
|
+ if (au_h_iptr(inode, bindex)) {
|
|
+ do_put = 0;
|
|
+ break;
|
|
+ }
|
|
+ } else
|
|
+ do_put = 1;
|
|
+
|
|
+ return do_put;
|
|
+}
|
|
+
|
|
+/* free the plinks on a branch specified by @br_id */
|
|
+void au_plink_half_refresh(struct super_block *sb, aufs_bindex_t br_id)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos, *tmp;
|
|
+ struct au_icntnr *icntnr;
|
|
+ struct inode *inode;
|
|
+ int i, do_put;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK));
|
|
+ AuDebugOn(au_plink_maint(sb, AuLock_NOPLM));
|
|
+
|
|
+ /* no bit_lock since sbinfo is write-locked */
|
|
+ for (i = 0; i < AuPlink_NHASH; i++) {
|
|
+ hbl = sbinfo->si_plink + i;
|
|
+ hlist_bl_for_each_entry_safe(icntnr, pos, tmp, hbl, plink) {
|
|
+ inode = au_igrab(&icntnr->vfs_inode);
|
|
+ ii_write_lock_child(inode);
|
|
+ do_put = au_plink_do_half_refresh(inode, br_id);
|
|
+ if (do_put) {
|
|
+ hlist_bl_del(&icntnr->plink);
|
|
+ iput(inode);
|
|
+ }
|
|
+ ii_write_unlock(inode);
|
|
+ iput(inode);
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/fs/aufs/poll.c b/fs/aufs/poll.c
|
|
new file mode 100644
|
|
index 000000000000..6975d3ab2d29
|
|
--- /dev/null
|
|
+++ b/fs/aufs/poll.c
|
|
@@ -0,0 +1,51 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * poll operation
|
|
+ * There is only one filesystem which implements ->poll operation, currently.
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+__poll_t aufs_poll(struct file *file, struct poll_table_struct *pt)
|
|
+{
|
|
+ __poll_t mask;
|
|
+ struct file *h_file;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ /* We should pretend an error happened. */
|
|
+ mask = EPOLLERR /* | EPOLLIN | EPOLLOUT */;
|
|
+ sb = file->f_path.dentry->d_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLMW);
|
|
+
|
|
+ h_file = au_read_pre(file, /*keep_fi*/0, /*lsc*/0);
|
|
+ if (IS_ERR(h_file)) {
|
|
+ AuDbg("h_file %ld\n", PTR_ERR(h_file));
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ mask = vfs_poll(h_file, pt);
|
|
+ fput(h_file); /* instead of au_read_post() */
|
|
+
|
|
+out:
|
|
+ si_read_unlock(sb);
|
|
+ if (mask & EPOLLERR)
|
|
+ AuDbg("mask 0x%x\n", mask);
|
|
+ return mask;
|
|
+}
|
|
diff --git a/fs/aufs/posix_acl.c b/fs/aufs/posix_acl.c
|
|
new file mode 100644
|
|
index 000000000000..0789335650de
|
|
--- /dev/null
|
|
+++ b/fs/aufs/posix_acl.c
|
|
@@ -0,0 +1,105 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2014-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * posix acl operations
|
|
+ */
|
|
+
|
|
+#include <linux/fs.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+struct posix_acl *aufs_get_acl(struct inode *inode, int type)
|
|
+{
|
|
+ struct posix_acl *acl;
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct inode *h_inode;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ acl = NULL;
|
|
+ sb = inode->i_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH);
|
|
+ ii_read_lock_child(inode);
|
|
+ if (!(sb->s_flags & SB_POSIXACL))
|
|
+ goto out;
|
|
+
|
|
+ bindex = au_ibtop(inode);
|
|
+ h_inode = au_h_iptr(inode, bindex);
|
|
+ if (unlikely(!h_inode
|
|
+ || ((h_inode->i_mode & S_IFMT)
|
|
+ != (inode->i_mode & S_IFMT)))) {
|
|
+ err = au_busy_or_stale();
|
|
+ acl = ERR_PTR(err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* always topmost only */
|
|
+ acl = get_acl(h_inode, type);
|
|
+ if (IS_ERR(acl))
|
|
+ forget_cached_acl(inode, type);
|
|
+ else
|
|
+ set_cached_acl(inode, type, acl);
|
|
+
|
|
+out:
|
|
+ ii_read_unlock(inode);
|
|
+ si_read_unlock(sb);
|
|
+
|
|
+ AuTraceErrPtr(acl);
|
|
+ return acl;
|
|
+}
|
|
+
|
|
+int aufs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
|
|
+{
|
|
+ int err;
|
|
+ ssize_t ssz;
|
|
+ struct dentry *dentry;
|
|
+ struct au_sxattr arg = {
|
|
+ .type = AU_ACL_SET,
|
|
+ .u.acl_set = {
|
|
+ .acl = acl,
|
|
+ .type = type
|
|
+ },
|
|
+ };
|
|
+
|
|
+ IMustLock(inode);
|
|
+
|
|
+ if (inode->i_ino == AUFS_ROOT_INO)
|
|
+ dentry = dget(inode->i_sb->s_root);
|
|
+ else {
|
|
+ dentry = d_find_alias(inode);
|
|
+ if (!dentry)
|
|
+ dentry = d_find_any_alias(inode);
|
|
+ if (!dentry) {
|
|
+ pr_warn("cannot handle this inode, "
|
|
+ "please report to aufs-users ML\n");
|
|
+ err = -ENOENT;
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ssz = au_sxattr(dentry, inode, &arg);
|
|
+ /* forget even it if succeeds since the branch might set differently */
|
|
+ forget_cached_acl(inode, type);
|
|
+ dput(dentry);
|
|
+ err = ssz;
|
|
+ if (ssz >= 0)
|
|
+ err = 0;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/procfs.c b/fs/aufs/procfs.c
|
|
new file mode 100644
|
|
index 000000000000..50ee0a9c1bcb
|
|
--- /dev/null
|
|
+++ b/fs/aufs/procfs.c
|
|
@@ -0,0 +1,170 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2010-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * procfs interfaces
|
|
+ */
|
|
+
|
|
+#include <linux/proc_fs.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+static int au_procfs_plm_release(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ sbinfo = file->private_data;
|
|
+ if (sbinfo) {
|
|
+ au_plink_maint_leave(sbinfo);
|
|
+ kobject_put(&sbinfo->si_kobj);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void au_procfs_plm_write_clean(struct file *file)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ sbinfo = file->private_data;
|
|
+ if (sbinfo)
|
|
+ au_plink_clean(sbinfo->si_sb, /*verbose*/0);
|
|
+}
|
|
+
|
|
+static int au_procfs_plm_write_si(struct file *file, unsigned long id)
|
|
+{
|
|
+ int err;
|
|
+ struct super_block *sb;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct hlist_bl_node *pos;
|
|
+
|
|
+ err = -EBUSY;
|
|
+ if (unlikely(file->private_data))
|
|
+ goto out;
|
|
+
|
|
+ sb = NULL;
|
|
+ /* don't use au_sbilist_lock() here */
|
|
+ hlist_bl_lock(&au_sbilist);
|
|
+ hlist_bl_for_each_entry(sbinfo, pos, &au_sbilist, si_list)
|
|
+ if (id == sysaufs_si_id(sbinfo)) {
|
|
+ if (kobject_get_unless_zero(&sbinfo->si_kobj))
|
|
+ sb = sbinfo->si_sb;
|
|
+ break;
|
|
+ }
|
|
+ hlist_bl_unlock(&au_sbilist);
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (unlikely(!sb))
|
|
+ goto out;
|
|
+
|
|
+ err = au_plink_maint_enter(sb);
|
|
+ if (!err)
|
|
+ /* keep kobject_get() */
|
|
+ file->private_data = sbinfo;
|
|
+ else
|
|
+ kobject_put(&sbinfo->si_kobj);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Accept a valid "si=xxxx" only.
|
|
+ * Once it is accepted successfully, accept "clean" too.
|
|
+ */
|
|
+static ssize_t au_procfs_plm_write(struct file *file, const char __user *ubuf,
|
|
+ size_t count, loff_t *ppos)
|
|
+{
|
|
+ ssize_t err;
|
|
+ unsigned long id;
|
|
+ /* last newline is allowed */
|
|
+ char buf[3 + sizeof(unsigned long) * 2 + 1];
|
|
+
|
|
+ err = -EACCES;
|
|
+ if (unlikely(!capable(CAP_SYS_ADMIN)))
|
|
+ goto out;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (unlikely(count > sizeof(buf)))
|
|
+ goto out;
|
|
+
|
|
+ err = copy_from_user(buf, ubuf, count);
|
|
+ if (unlikely(err)) {
|
|
+ err = -EFAULT;
|
|
+ goto out;
|
|
+ }
|
|
+ buf[count] = 0;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (!strcmp("clean", buf)) {
|
|
+ au_procfs_plm_write_clean(file);
|
|
+ goto out_success;
|
|
+ } else if (unlikely(strncmp("si=", buf, 3)))
|
|
+ goto out;
|
|
+
|
|
+ err = kstrtoul(buf + 3, 16, &id);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ err = au_procfs_plm_write_si(file, id);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+out_success:
|
|
+ err = count; /* success */
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct proc_ops au_procfs_plm_op = {
|
|
+ .proc_write = au_procfs_plm_write,
|
|
+ .proc_release = au_procfs_plm_release
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static struct proc_dir_entry *au_procfs_dir;
|
|
+
|
|
+void au_procfs_fin(void)
|
|
+{
|
|
+ remove_proc_entry(AUFS_PLINK_MAINT_NAME, au_procfs_dir);
|
|
+ remove_proc_entry(AUFS_PLINK_MAINT_DIR, NULL);
|
|
+}
|
|
+
|
|
+int __init au_procfs_init(void)
|
|
+{
|
|
+ int err;
|
|
+ struct proc_dir_entry *entry;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ au_procfs_dir = proc_mkdir(AUFS_PLINK_MAINT_DIR, NULL);
|
|
+ if (unlikely(!au_procfs_dir))
|
|
+ goto out;
|
|
+
|
|
+ entry = proc_create(AUFS_PLINK_MAINT_NAME, S_IFREG | 0200,
|
|
+ au_procfs_dir, &au_procfs_plm_op);
|
|
+ if (unlikely(!entry))
|
|
+ goto out_dir;
|
|
+
|
|
+ err = 0;
|
|
+ goto out; /* success */
|
|
+
|
|
+
|
|
+out_dir:
|
|
+ remove_proc_entry(AUFS_PLINK_MAINT_DIR, NULL);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/rdu.c b/fs/aufs/rdu.c
|
|
new file mode 100644
|
|
index 000000000000..afd77564f3dc
|
|
--- /dev/null
|
|
+++ b/fs/aufs/rdu.c
|
|
@@ -0,0 +1,384 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * readdir in userspace.
|
|
+ */
|
|
+
|
|
+#include <linux/compat.h>
|
|
+#include <linux/fs_stack.h>
|
|
+#include <linux/security.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/* bits for struct aufs_rdu.flags */
|
|
+#define AuRdu_CALLED 1
|
|
+#define AuRdu_CONT (1 << 1)
|
|
+#define AuRdu_FULL (1 << 2)
|
|
+#define au_ftest_rdu(flags, name) ((flags) & AuRdu_##name)
|
|
+#define au_fset_rdu(flags, name) \
|
|
+ do { (flags) |= AuRdu_##name; } while (0)
|
|
+#define au_fclr_rdu(flags, name) \
|
|
+ do { (flags) &= ~AuRdu_##name; } while (0)
|
|
+
|
|
+struct au_rdu_arg {
|
|
+ struct dir_context ctx;
|
|
+ struct aufs_rdu *rdu;
|
|
+ union au_rdu_ent_ul ent;
|
|
+ unsigned long end;
|
|
+
|
|
+ struct super_block *sb;
|
|
+ int err;
|
|
+};
|
|
+
|
|
+static int au_rdu_fill(struct dir_context *ctx, const char *name, int nlen,
|
|
+ loff_t offset, u64 h_ino, unsigned int d_type)
|
|
+{
|
|
+ int err, len;
|
|
+ struct au_rdu_arg *arg = container_of(ctx, struct au_rdu_arg, ctx);
|
|
+ struct aufs_rdu *rdu = arg->rdu;
|
|
+ struct au_rdu_ent ent;
|
|
+
|
|
+ err = 0;
|
|
+ arg->err = 0;
|
|
+ au_fset_rdu(rdu->cookie.flags, CALLED);
|
|
+ len = au_rdu_len(nlen);
|
|
+ if (arg->ent.ul + len < arg->end) {
|
|
+ ent.ino = h_ino;
|
|
+ ent.bindex = rdu->cookie.bindex;
|
|
+ ent.type = d_type;
|
|
+ ent.nlen = nlen;
|
|
+ if (unlikely(nlen > AUFS_MAX_NAMELEN))
|
|
+ ent.type = DT_UNKNOWN;
|
|
+
|
|
+ /* unnecessary to support mmap_sem since this is a dir */
|
|
+ err = -EFAULT;
|
|
+ if (copy_to_user(arg->ent.e, &ent, sizeof(ent)))
|
|
+ goto out;
|
|
+ if (copy_to_user(arg->ent.e->name, name, nlen))
|
|
+ goto out;
|
|
+ /* the terminating NULL */
|
|
+ if (__put_user(0, arg->ent.e->name + nlen))
|
|
+ goto out;
|
|
+ err = 0;
|
|
+ /* AuDbg("%p, %.*s\n", arg->ent.p, nlen, name); */
|
|
+ arg->ent.ul += len;
|
|
+ rdu->rent++;
|
|
+ } else {
|
|
+ err = -EFAULT;
|
|
+ au_fset_rdu(rdu->cookie.flags, FULL);
|
|
+ rdu->full = 1;
|
|
+ rdu->tail = arg->ent;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ /* AuTraceErr(err); */
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_rdu_do(struct file *h_file, struct au_rdu_arg *arg)
|
|
+{
|
|
+ int err;
|
|
+ loff_t offset;
|
|
+ struct au_rdu_cookie *cookie = &arg->rdu->cookie;
|
|
+
|
|
+ /* we don't have to care (FMODE_32BITHASH | FMODE_64BITHASH) for ext4 */
|
|
+ offset = vfsub_llseek(h_file, cookie->h_pos, SEEK_SET);
|
|
+ err = offset;
|
|
+ if (unlikely(offset != cookie->h_pos))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ do {
|
|
+ arg->err = 0;
|
|
+ au_fclr_rdu(cookie->flags, CALLED);
|
|
+ /* smp_mb(); */
|
|
+ err = vfsub_iterate_dir(h_file, &arg->ctx);
|
|
+ if (err >= 0)
|
|
+ err = arg->err;
|
|
+ } while (!err
|
|
+ && au_ftest_rdu(cookie->flags, CALLED)
|
|
+ && !au_ftest_rdu(cookie->flags, FULL));
|
|
+ cookie->h_pos = h_file->f_pos;
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_rdu(struct file *file, struct aufs_rdu *rdu)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bbot;
|
|
+ struct au_rdu_arg arg = {
|
|
+ .ctx = {
|
|
+ .actor = au_rdu_fill
|
|
+ }
|
|
+ };
|
|
+ struct dentry *dentry;
|
|
+ struct inode *inode;
|
|
+ struct file *h_file;
|
|
+ struct au_rdu_cookie *cookie = &rdu->cookie;
|
|
+
|
|
+ /* VERIFY_WRITE */
|
|
+ err = !access_ok(rdu->ent.e, rdu->sz);
|
|
+ if (unlikely(err)) {
|
|
+ err = -EFAULT;
|
|
+ AuTraceErr(err);
|
|
+ goto out;
|
|
+ }
|
|
+ rdu->rent = 0;
|
|
+ rdu->tail = rdu->ent;
|
|
+ rdu->full = 0;
|
|
+ arg.rdu = rdu;
|
|
+ arg.ent = rdu->ent;
|
|
+ arg.end = arg.ent.ul;
|
|
+ arg.end += rdu->sz;
|
|
+
|
|
+ err = -ENOTDIR;
|
|
+ if (unlikely(!file->f_op->iterate && !file->f_op->iterate_shared))
|
|
+ goto out;
|
|
+
|
|
+ err = security_file_permission(file, MAY_READ);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ dentry = file->f_path.dentry;
|
|
+ inode = d_inode(dentry);
|
|
+ inode_lock_shared(inode);
|
|
+
|
|
+ arg.sb = inode->i_sb;
|
|
+ err = si_read_lock(arg.sb, AuLock_FLUSH | AuLock_NOPLM);
|
|
+ if (unlikely(err))
|
|
+ goto out_mtx;
|
|
+ err = au_alive_dir(dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out_si;
|
|
+ /* todo: reval? */
|
|
+ fi_read_lock(file);
|
|
+
|
|
+ err = -EAGAIN;
|
|
+ if (unlikely(au_ftest_rdu(cookie->flags, CONT)
|
|
+ && cookie->generation != au_figen(file)))
|
|
+ goto out_unlock;
|
|
+
|
|
+ err = 0;
|
|
+ if (!rdu->blk) {
|
|
+ rdu->blk = au_sbi(arg.sb)->si_rdblk;
|
|
+ if (!rdu->blk)
|
|
+ rdu->blk = au_dir_size(file, /*dentry*/NULL);
|
|
+ }
|
|
+ bbot = au_fbtop(file);
|
|
+ if (cookie->bindex < bbot)
|
|
+ cookie->bindex = bbot;
|
|
+ bbot = au_fbbot_dir(file);
|
|
+ /* AuDbg("b%d, b%d\n", cookie->bindex, bbot); */
|
|
+ for (; !err && cookie->bindex <= bbot;
|
|
+ cookie->bindex++, cookie->h_pos = 0) {
|
|
+ h_file = au_hf_dir(file, cookie->bindex);
|
|
+ if (!h_file)
|
|
+ continue;
|
|
+
|
|
+ au_fclr_rdu(cookie->flags, FULL);
|
|
+ err = au_rdu_do(h_file, &arg);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(au_ftest_rdu(cookie->flags, FULL) || err))
|
|
+ break;
|
|
+ }
|
|
+ AuDbg("rent %llu\n", rdu->rent);
|
|
+
|
|
+ if (!err && !au_ftest_rdu(cookie->flags, CONT)) {
|
|
+ rdu->shwh = !!au_opt_test(au_sbi(arg.sb)->si_mntflags, SHWH);
|
|
+ au_fset_rdu(cookie->flags, CONT);
|
|
+ cookie->generation = au_figen(file);
|
|
+ }
|
|
+
|
|
+ ii_read_lock_child(inode);
|
|
+ fsstack_copy_attr_atime(inode, au_h_iptr(inode, au_ibtop(inode)));
|
|
+ ii_read_unlock(inode);
|
|
+
|
|
+out_unlock:
|
|
+ fi_read_unlock(file);
|
|
+out_si:
|
|
+ si_read_unlock(arg.sb);
|
|
+out_mtx:
|
|
+ inode_unlock_shared(inode);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_rdu_ino(struct file *file, struct aufs_rdu *rdu)
|
|
+{
|
|
+ int err;
|
|
+ ino_t ino;
|
|
+ unsigned long long nent;
|
|
+ union au_rdu_ent_ul *u;
|
|
+ struct au_rdu_ent ent;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ err = 0;
|
|
+ nent = rdu->nent;
|
|
+ u = &rdu->ent;
|
|
+ sb = file->f_path.dentry->d_sb;
|
|
+ si_read_lock(sb, AuLock_FLUSH);
|
|
+ while (nent-- > 0) {
|
|
+ /* unnecessary to support mmap_sem since this is a dir */
|
|
+ err = copy_from_user(&ent, u->e, sizeof(ent));
|
|
+ if (!err)
|
|
+ /* VERIFY_WRITE */
|
|
+ err = !access_ok(&u->e->ino, sizeof(ino));
|
|
+ if (unlikely(err)) {
|
|
+ err = -EFAULT;
|
|
+ AuTraceErr(err);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* AuDbg("b%d, i%llu\n", ent.bindex, ent.ino); */
|
|
+ if (!ent.wh)
|
|
+ err = au_ino(sb, ent.bindex, ent.ino, ent.type, &ino);
|
|
+ else
|
|
+ err = au_wh_ino(sb, ent.bindex, ent.ino, ent.type,
|
|
+ &ino);
|
|
+ if (unlikely(err)) {
|
|
+ AuTraceErr(err);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ err = __put_user(ino, &u->e->ino);
|
|
+ if (unlikely(err)) {
|
|
+ err = -EFAULT;
|
|
+ AuTraceErr(err);
|
|
+ break;
|
|
+ }
|
|
+ u->ul += au_rdu_len(ent.nlen);
|
|
+ }
|
|
+ si_read_unlock(sb);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_rdu_verify(struct aufs_rdu *rdu)
|
|
+{
|
|
+ AuDbg("rdu{%llu, %p, %u | %u | %llu, %u, %u | "
|
|
+ "%llu, b%d, 0x%x, g%u}\n",
|
|
+ rdu->sz, rdu->ent.e, rdu->verify[AufsCtlRduV_SZ],
|
|
+ rdu->blk,
|
|
+ rdu->rent, rdu->shwh, rdu->full,
|
|
+ rdu->cookie.h_pos, rdu->cookie.bindex, rdu->cookie.flags,
|
|
+ rdu->cookie.generation);
|
|
+
|
|
+ if (rdu->verify[AufsCtlRduV_SZ] == sizeof(*rdu))
|
|
+ return 0;
|
|
+
|
|
+ AuDbg("%u:%u\n",
|
|
+ rdu->verify[AufsCtlRduV_SZ], (unsigned int)sizeof(*rdu));
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+long au_rdu_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ long err, e;
|
|
+ struct aufs_rdu rdu;
|
|
+ void __user *p = (void __user *)arg;
|
|
+
|
|
+ err = copy_from_user(&rdu, p, sizeof(rdu));
|
|
+ if (unlikely(err)) {
|
|
+ err = -EFAULT;
|
|
+ AuTraceErr(err);
|
|
+ goto out;
|
|
+ }
|
|
+ err = au_rdu_verify(&rdu);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case AUFS_CTL_RDU:
|
|
+ err = au_rdu(file, &rdu);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+
|
|
+ e = copy_to_user(p, &rdu, sizeof(rdu));
|
|
+ if (unlikely(e)) {
|
|
+ err = -EFAULT;
|
|
+ AuTraceErr(err);
|
|
+ }
|
|
+ break;
|
|
+ case AUFS_CTL_RDU_INO:
|
|
+ err = au_rdu_ino(file, &rdu);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ /* err = -ENOTTY; */
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_COMPAT
|
|
+long au_rdu_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ long err, e;
|
|
+ struct aufs_rdu rdu;
|
|
+ void __user *p = compat_ptr(arg);
|
|
+
|
|
+ /* todo: get_user()? */
|
|
+ err = copy_from_user(&rdu, p, sizeof(rdu));
|
|
+ if (unlikely(err)) {
|
|
+ err = -EFAULT;
|
|
+ AuTraceErr(err);
|
|
+ goto out;
|
|
+ }
|
|
+ rdu.ent.e = compat_ptr(rdu.ent.ul);
|
|
+ err = au_rdu_verify(&rdu);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case AUFS_CTL_RDU:
|
|
+ err = au_rdu(file, &rdu);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+
|
|
+ rdu.ent.ul = ptr_to_compat(rdu.ent.e);
|
|
+ rdu.tail.ul = ptr_to_compat(rdu.tail.e);
|
|
+ e = copy_to_user(p, &rdu, sizeof(rdu));
|
|
+ if (unlikely(e)) {
|
|
+ err = -EFAULT;
|
|
+ AuTraceErr(err);
|
|
+ }
|
|
+ break;
|
|
+ case AUFS_CTL_RDU_INO:
|
|
+ err = au_rdu_ino(file, &rdu);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ /* err = -ENOTTY; */
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+#endif
|
|
diff --git a/fs/aufs/rwsem.h b/fs/aufs/rwsem.h
|
|
new file mode 100644
|
|
index 000000000000..370eae159348
|
|
--- /dev/null
|
|
+++ b/fs/aufs/rwsem.h
|
|
@@ -0,0 +1,73 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * simple read-write semaphore wrappers
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_RWSEM_H__
|
|
+#define __AUFS_RWSEM_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include "debug.h"
|
|
+
|
|
+/* in the future, the name 'au_rwsem' will be totally gone */
|
|
+#define au_rwsem rw_semaphore
|
|
+
|
|
+/* to debug easier, do not make them inlined functions */
|
|
+#define AuRwMustNoWaiters(rw) AuDebugOn(rwsem_is_contended(rw))
|
|
+/* rwsem_is_locked() is unusable */
|
|
+#define AuRwMustReadLock(rw) AuDebugOn(!lockdep_recursing(current) \
|
|
+ && debug_locks \
|
|
+ && !lockdep_is_held_type(rw, 1))
|
|
+#define AuRwMustWriteLock(rw) AuDebugOn(!lockdep_recursing(current) \
|
|
+ && debug_locks \
|
|
+ && !lockdep_is_held_type(rw, 0))
|
|
+#define AuRwMustAnyLock(rw) AuDebugOn(!lockdep_recursing(current) \
|
|
+ && debug_locks \
|
|
+ && !lockdep_is_held(rw))
|
|
+#define AuRwDestroy(rw) AuDebugOn(!lockdep_recursing(current) \
|
|
+ && debug_locks \
|
|
+ && lockdep_is_held(rw))
|
|
+
|
|
+#define au_rw_init(rw) init_rwsem(rw)
|
|
+
|
|
+#define au_rw_init_wlock(rw) do { \
|
|
+ au_rw_init(rw); \
|
|
+ down_write(rw); \
|
|
+ } while (0)
|
|
+
|
|
+#define au_rw_init_wlock_nested(rw, lsc) do { \
|
|
+ au_rw_init(rw); \
|
|
+ down_write_nested(rw, lsc); \
|
|
+ } while (0)
|
|
+
|
|
+#define au_rw_read_lock(rw) down_read(rw)
|
|
+#define au_rw_read_lock_nested(rw, lsc) down_read_nested(rw, lsc)
|
|
+#define au_rw_read_unlock(rw) up_read(rw)
|
|
+#define au_rw_dgrade_lock(rw) downgrade_write(rw)
|
|
+#define au_rw_write_lock(rw) down_write(rw)
|
|
+#define au_rw_write_lock_nested(rw, lsc) down_write_nested(rw, lsc)
|
|
+#define au_rw_write_unlock(rw) up_write(rw)
|
|
+/* why is not _nested version defined? */
|
|
+#define au_rw_read_trylock(rw) down_read_trylock(rw)
|
|
+#define au_rw_write_trylock(rw) down_write_trylock(rw)
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_RWSEM_H__ */
|
|
diff --git a/fs/aufs/sbinfo.c b/fs/aufs/sbinfo.c
|
|
new file mode 100644
|
|
index 000000000000..3835be61d978
|
|
--- /dev/null
|
|
+++ b/fs/aufs/sbinfo.c
|
|
@@ -0,0 +1,314 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * superblock private data
|
|
+ */
|
|
+
|
|
+#include <linux/iversion.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/*
|
|
+ * they are necessary regardless sysfs is disabled.
|
|
+ */
|
|
+void au_si_free(struct kobject *kobj)
|
|
+{
|
|
+ int i;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ char *locked __maybe_unused; /* debug only */
|
|
+
|
|
+ sbinfo = container_of(kobj, struct au_sbinfo, si_kobj);
|
|
+ for (i = 0; i < AuPlink_NHASH; i++)
|
|
+ AuDebugOn(!hlist_bl_empty(sbinfo->si_plink + i));
|
|
+ AuDebugOn(atomic_read(&sbinfo->si_nowait.nw_len));
|
|
+
|
|
+ AuLCntZero(au_lcnt_read(&sbinfo->si_ninodes, /*do_rev*/0));
|
|
+ au_lcnt_fin(&sbinfo->si_ninodes, /*do_sync*/0);
|
|
+ AuLCntZero(au_lcnt_read(&sbinfo->si_nfiles, /*do_rev*/0));
|
|
+ au_lcnt_fin(&sbinfo->si_nfiles, /*do_sync*/0);
|
|
+
|
|
+ dbgaufs_si_fin(sbinfo);
|
|
+ au_rw_write_lock(&sbinfo->si_rwsem);
|
|
+ au_br_free(sbinfo);
|
|
+ au_rw_write_unlock(&sbinfo->si_rwsem);
|
|
+
|
|
+ au_kfree_try_rcu(sbinfo->si_branch);
|
|
+ mutex_destroy(&sbinfo->si_xib_mtx);
|
|
+ AuRwDestroy(&sbinfo->si_rwsem);
|
|
+
|
|
+ au_lcnt_wait_for_fin(&sbinfo->si_ninodes);
|
|
+ /* si_nfiles is waited too */
|
|
+ au_kfree_rcu(sbinfo);
|
|
+}
|
|
+
|
|
+int au_si_alloc(struct super_block *sb)
|
|
+{
|
|
+ int err, i;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ sbinfo = kzalloc(sizeof(*sbinfo), GFP_NOFS);
|
|
+ if (unlikely(!sbinfo))
|
|
+ goto out;
|
|
+
|
|
+ /* will be reallocated separately */
|
|
+ sbinfo->si_branch = kzalloc(sizeof(*sbinfo->si_branch), GFP_NOFS);
|
|
+ if (unlikely(!sbinfo->si_branch))
|
|
+ goto out_sbinfo;
|
|
+
|
|
+ err = sysaufs_si_init(sbinfo);
|
|
+ if (!err) {
|
|
+ dbgaufs_si_null(sbinfo);
|
|
+ err = dbgaufs_si_init(sbinfo);
|
|
+ if (unlikely(err))
|
|
+ kobject_put(&sbinfo->si_kobj);
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ goto out_br;
|
|
+
|
|
+ au_nwt_init(&sbinfo->si_nowait);
|
|
+ au_rw_init_wlock(&sbinfo->si_rwsem);
|
|
+
|
|
+ au_lcnt_init(&sbinfo->si_ninodes, /*release*/NULL);
|
|
+ au_lcnt_init(&sbinfo->si_nfiles, /*release*/NULL);
|
|
+
|
|
+ sbinfo->si_bbot = -1;
|
|
+ sbinfo->si_last_br_id = AUFS_BRANCH_MAX / 2;
|
|
+
|
|
+ sbinfo->si_wbr_copyup = AuWbrCopyup_Def;
|
|
+ sbinfo->si_wbr_create = AuWbrCreate_Def;
|
|
+ sbinfo->si_wbr_copyup_ops = au_wbr_copyup_ops + sbinfo->si_wbr_copyup;
|
|
+ sbinfo->si_wbr_create_ops = au_wbr_create_ops + sbinfo->si_wbr_create;
|
|
+
|
|
+ au_fhsm_init(sbinfo);
|
|
+
|
|
+ sbinfo->si_mntflags = au_opts_plink(AuOpt_Def);
|
|
+
|
|
+ sbinfo->si_xino_jiffy = jiffies;
|
|
+ sbinfo->si_xino_expire
|
|
+ = msecs_to_jiffies(AUFS_XINO_DEF_SEC * MSEC_PER_SEC);
|
|
+ mutex_init(&sbinfo->si_xib_mtx);
|
|
+ /* leave si_xib_last_pindex and si_xib_next_bit */
|
|
+
|
|
+ INIT_HLIST_BL_HEAD(&sbinfo->si_aopen);
|
|
+
|
|
+ sbinfo->si_rdcache = msecs_to_jiffies(AUFS_RDCACHE_DEF * MSEC_PER_SEC);
|
|
+ sbinfo->si_rdblk = AUFS_RDBLK_DEF;
|
|
+ sbinfo->si_rdhash = AUFS_RDHASH_DEF;
|
|
+ sbinfo->si_dirwh = AUFS_DIRWH_DEF;
|
|
+
|
|
+ for (i = 0; i < AuPlink_NHASH; i++)
|
|
+ INIT_HLIST_BL_HEAD(sbinfo->si_plink + i);
|
|
+ init_waitqueue_head(&sbinfo->si_plink_wq);
|
|
+ spin_lock_init(&sbinfo->si_plink_maint_lock);
|
|
+
|
|
+ INIT_HLIST_BL_HEAD(&sbinfo->si_files);
|
|
+
|
|
+ /* with getattr by default */
|
|
+ sbinfo->si_iop_array = aufs_iop;
|
|
+
|
|
+ /* leave other members for sysaufs and si_mnt. */
|
|
+ sbinfo->si_sb = sb;
|
|
+ sb->s_fs_info = sbinfo;
|
|
+ si_pid_set(sb);
|
|
+ return 0; /* success */
|
|
+
|
|
+out_br:
|
|
+ au_kfree_try_rcu(sbinfo->si_branch);
|
|
+out_sbinfo:
|
|
+ au_kfree_rcu(sbinfo);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_sbr_realloc(struct au_sbinfo *sbinfo, int nbr, int may_shrink)
|
|
+{
|
|
+ int err, sz;
|
|
+ struct au_branch **brp;
|
|
+
|
|
+ AuRwMustWriteLock(&sbinfo->si_rwsem);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ sz = sizeof(*brp) * (sbinfo->si_bbot + 1);
|
|
+ if (unlikely(!sz))
|
|
+ sz = sizeof(*brp);
|
|
+ brp = au_kzrealloc(sbinfo->si_branch, sz, sizeof(*brp) * nbr, GFP_NOFS,
|
|
+ may_shrink);
|
|
+ if (brp) {
|
|
+ sbinfo->si_branch = brp;
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+unsigned int au_sigen_inc(struct super_block *sb)
|
|
+{
|
|
+ unsigned int gen;
|
|
+ struct inode *inode;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ gen = ++au_sbi(sb)->si_generation;
|
|
+ au_update_digen(sb->s_root);
|
|
+ inode = d_inode(sb->s_root);
|
|
+ au_update_iigen(inode, /*half*/0);
|
|
+ inode_inc_iversion(inode);
|
|
+ return gen;
|
|
+}
|
|
+
|
|
+aufs_bindex_t au_new_br_id(struct super_block *sb)
|
|
+{
|
|
+ aufs_bindex_t br_id;
|
|
+ int i;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ for (i = 0; i <= AUFS_BRANCH_MAX; i++) {
|
|
+ br_id = ++sbinfo->si_last_br_id;
|
|
+ AuDebugOn(br_id < 0);
|
|
+ if (br_id && au_br_index(sb, br_id) < 0)
|
|
+ return br_id;
|
|
+ }
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* it is ok that new 'nwt' tasks are appended while we are sleeping */
|
|
+int si_read_lock(struct super_block *sb, int flags)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (au_ftest_lock(flags, FLUSH))
|
|
+ au_nwt_flush(&au_sbi(sb)->si_nowait);
|
|
+
|
|
+ si_noflush_read_lock(sb);
|
|
+ err = au_plink_maint(sb, flags);
|
|
+ if (unlikely(err))
|
|
+ si_read_unlock(sb);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int si_write_lock(struct super_block *sb, int flags)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ if (au_ftest_lock(flags, FLUSH))
|
|
+ au_nwt_flush(&au_sbi(sb)->si_nowait);
|
|
+
|
|
+ si_noflush_write_lock(sb);
|
|
+ err = au_plink_maint(sb, flags);
|
|
+ if (unlikely(err))
|
|
+ si_write_unlock(sb);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* dentry and super_block lock. call at entry point */
|
|
+int aufs_read_lock(struct dentry *dentry, int flags)
|
|
+{
|
|
+ int err;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ err = si_read_lock(sb, flags);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ if (au_ftest_lock(flags, DW))
|
|
+ di_write_lock_child(dentry);
|
|
+ else
|
|
+ di_read_lock_child(dentry, flags);
|
|
+
|
|
+ if (au_ftest_lock(flags, GEN)) {
|
|
+ err = au_digen_test(dentry, au_sigen(sb));
|
|
+ if (!au_opt_test(au_mntflags(sb), UDBA_NONE))
|
|
+ AuDebugOn(!err && au_dbrange_test(dentry));
|
|
+ else if (!err)
|
|
+ err = au_dbrange_test(dentry);
|
|
+ if (unlikely(err))
|
|
+ aufs_read_unlock(dentry, flags);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void aufs_read_unlock(struct dentry *dentry, int flags)
|
|
+{
|
|
+ if (au_ftest_lock(flags, DW))
|
|
+ di_write_unlock(dentry);
|
|
+ else
|
|
+ di_read_unlock(dentry, flags);
|
|
+ si_read_unlock(dentry->d_sb);
|
|
+}
|
|
+
|
|
+void aufs_write_lock(struct dentry *dentry)
|
|
+{
|
|
+ si_write_lock(dentry->d_sb, AuLock_FLUSH | AuLock_NOPLMW);
|
|
+ di_write_lock_child(dentry);
|
|
+}
|
|
+
|
|
+void aufs_write_unlock(struct dentry *dentry)
|
|
+{
|
|
+ di_write_unlock(dentry);
|
|
+ si_write_unlock(dentry->d_sb);
|
|
+}
|
|
+
|
|
+int aufs_read_and_write_lock2(struct dentry *d1, struct dentry *d2, int flags)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int sigen;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ sb = d1->d_sb;
|
|
+ err = si_read_lock(sb, flags);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ di_write_lock2_child(d1, d2, au_ftest_lock(flags, DIRS));
|
|
+
|
|
+ if (au_ftest_lock(flags, GEN)) {
|
|
+ sigen = au_sigen(sb);
|
|
+ err = au_digen_test(d1, sigen);
|
|
+ AuDebugOn(!err && au_dbrange_test(d1));
|
|
+ if (!err) {
|
|
+ err = au_digen_test(d2, sigen);
|
|
+ AuDebugOn(!err && au_dbrange_test(d2));
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ aufs_read_and_write_unlock2(d1, d2);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void aufs_read_and_write_unlock2(struct dentry *d1, struct dentry *d2)
|
|
+{
|
|
+ di_write_unlock2(d1, d2);
|
|
+ si_read_unlock(d1->d_sb);
|
|
+}
|
|
diff --git a/fs/aufs/super.c b/fs/aufs/super.c
|
|
new file mode 100644
|
|
index 000000000000..aa7f210e5493
|
|
--- /dev/null
|
|
+++ b/fs/aufs/super.c
|
|
@@ -0,0 +1,1047 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * mount and super_block operations
|
|
+ */
|
|
+
|
|
+#include <linux/iversion.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/seq_file.h>
|
|
+#include <linux/statfs.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/*
|
|
+ * super_operations
|
|
+ */
|
|
+static struct inode *aufs_alloc_inode(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+ struct au_icntnr *c;
|
|
+
|
|
+ c = au_cache_alloc_icntnr();
|
|
+ if (c) {
|
|
+ au_icntnr_init(c);
|
|
+ inode_set_iversion(&c->vfs_inode, 1); /* sigen(sb); */
|
|
+ c->iinfo.ii_hinode = NULL;
|
|
+ return &c->vfs_inode;
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void aufs_destroy_inode(struct inode *inode)
|
|
+{
|
|
+ if (!au_is_bad_inode(inode))
|
|
+ au_iinfo_fin(inode);
|
|
+}
|
|
+
|
|
+static void aufs_free_inode(struct inode *inode)
|
|
+{
|
|
+ au_cache_free_icntnr(container_of(inode, struct au_icntnr, vfs_inode));
|
|
+}
|
|
+
|
|
+struct inode *au_iget_locked(struct super_block *sb, ino_t ino)
|
|
+{
|
|
+ struct inode *inode;
|
|
+ int err;
|
|
+
|
|
+ inode = iget_locked(sb, ino);
|
|
+ if (unlikely(!inode)) {
|
|
+ inode = ERR_PTR(-ENOMEM);
|
|
+ goto out;
|
|
+ }
|
|
+ if (!(inode->i_state & I_NEW))
|
|
+ goto out;
|
|
+
|
|
+ err = au_xigen_new(inode);
|
|
+ if (!err)
|
|
+ err = au_iinfo_init(inode);
|
|
+ if (!err)
|
|
+ inode_inc_iversion(inode);
|
|
+ else {
|
|
+ iget_failed(inode);
|
|
+ inode = ERR_PTR(err);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ /* never return NULL */
|
|
+ AuDebugOn(!inode);
|
|
+ AuTraceErrPtr(inode);
|
|
+ return inode;
|
|
+}
|
|
+
|
|
+/* lock free root dinfo */
|
|
+static int au_show_brs(struct seq_file *seq, struct super_block *sb)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct path path;
|
|
+ struct au_hdentry *hdp;
|
|
+ struct au_branch *br;
|
|
+ au_br_perm_str_t perm;
|
|
+
|
|
+ err = 0;
|
|
+ bbot = au_sbbot(sb);
|
|
+ bindex = 0;
|
|
+ hdp = au_hdentry(au_di(sb->s_root), bindex);
|
|
+ for (; !err && bindex <= bbot; bindex++, hdp++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ path.mnt = au_br_mnt(br);
|
|
+ path.dentry = hdp->hd_dentry;
|
|
+ err = au_seq_path(seq, &path);
|
|
+ if (!err) {
|
|
+ au_optstr_br_perm(&perm, br->br_perm);
|
|
+ seq_printf(seq, "=%s", perm.a);
|
|
+ if (bindex != bbot)
|
|
+ seq_putc(seq, ':');
|
|
+ }
|
|
+ }
|
|
+ if (unlikely(err || seq_has_overflowed(seq)))
|
|
+ err = -E2BIG;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_gen_fmt(char *fmt, int len __maybe_unused, const char *pat,
|
|
+ const char *append)
|
|
+{
|
|
+ char *p;
|
|
+
|
|
+ p = fmt;
|
|
+ while (*pat != ':')
|
|
+ *p++ = *pat++;
|
|
+ *p++ = *pat++;
|
|
+ strcpy(p, append);
|
|
+ AuDebugOn(strlen(fmt) >= len);
|
|
+}
|
|
+
|
|
+static void au_show_wbr_create(struct seq_file *m, int v,
|
|
+ struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ const char *pat;
|
|
+ char fmt[32];
|
|
+ struct au_wbr_mfs *mfs;
|
|
+
|
|
+ AuRwMustAnyLock(&sbinfo->si_rwsem);
|
|
+
|
|
+ seq_puts(m, ",create=");
|
|
+ pat = au_optstr_wbr_create(v);
|
|
+ mfs = &sbinfo->si_wbr_mfs;
|
|
+ switch (v) {
|
|
+ case AuWbrCreate_TDP:
|
|
+ case AuWbrCreate_RR:
|
|
+ case AuWbrCreate_MFS:
|
|
+ case AuWbrCreate_PMFS:
|
|
+ seq_puts(m, pat);
|
|
+ break;
|
|
+ case AuWbrCreate_MFSRR:
|
|
+ case AuWbrCreate_TDMFS:
|
|
+ case AuWbrCreate_PMFSRR:
|
|
+ au_gen_fmt(fmt, sizeof(fmt), pat, "%llu");
|
|
+ seq_printf(m, fmt, mfs->mfsrr_watermark);
|
|
+ break;
|
|
+ case AuWbrCreate_MFSV:
|
|
+ case AuWbrCreate_PMFSV:
|
|
+ au_gen_fmt(fmt, sizeof(fmt), pat, "%lu");
|
|
+ seq_printf(m, fmt,
|
|
+ jiffies_to_msecs(mfs->mfs_expire)
|
|
+ / MSEC_PER_SEC);
|
|
+ break;
|
|
+ case AuWbrCreate_MFSRRV:
|
|
+ case AuWbrCreate_TDMFSV:
|
|
+ case AuWbrCreate_PMFSRRV:
|
|
+ au_gen_fmt(fmt, sizeof(fmt), pat, "%llu:%lu");
|
|
+ seq_printf(m, fmt, mfs->mfsrr_watermark,
|
|
+ jiffies_to_msecs(mfs->mfs_expire) / MSEC_PER_SEC);
|
|
+ break;
|
|
+ default:
|
|
+ BUG();
|
|
+ }
|
|
+}
|
|
+
|
|
+static int au_show_xino(struct seq_file *seq, struct super_block *sb)
|
|
+{
|
|
+#ifdef CONFIG_SYSFS
|
|
+ return 0;
|
|
+#else
|
|
+ int err;
|
|
+ const int len = sizeof(AUFS_XINO_FNAME) - 1;
|
|
+ aufs_bindex_t bindex, brid;
|
|
+ struct qstr *name;
|
|
+ struct file *f;
|
|
+ struct dentry *d, *h_root;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ AuRwMustAnyLock(&sbinfo->si_rwsem);
|
|
+
|
|
+ err = 0;
|
|
+ f = au_sbi(sb)->si_xib;
|
|
+ if (!f)
|
|
+ goto out;
|
|
+
|
|
+ /* stop printing the default xino path on the first writable branch */
|
|
+ h_root = NULL;
|
|
+ bindex = au_xi_root(sb, f->f_path.dentry);
|
|
+ if (bindex >= 0) {
|
|
+ br = au_sbr_sb(sb, bindex);
|
|
+ h_root = au_br_dentry(br);
|
|
+ }
|
|
+
|
|
+ d = f->f_path.dentry;
|
|
+ name = &d->d_name;
|
|
+ /* safe ->d_parent because the file is unlinked */
|
|
+ if (d->d_parent == h_root
|
|
+ && name->len == len
|
|
+ && !memcmp(name->name, AUFS_XINO_FNAME, len))
|
|
+ goto out;
|
|
+
|
|
+ seq_puts(seq, ",xino=");
|
|
+ err = au_xino_path(seq, f);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+#endif
|
|
+}
|
|
+
|
|
+/* seq_file will re-call me in case of too long string */
|
|
+static int aufs_show_options(struct seq_file *m, struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int mnt_flags, v;
|
|
+ struct super_block *sb;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+#define AuBool(name, str) do { \
|
|
+ v = au_opt_test(mnt_flags, name); \
|
|
+ if (v != au_opt_test(AuOpt_Def, name)) \
|
|
+ seq_printf(m, ",%s" #str, v ? "" : "no"); \
|
|
+} while (0)
|
|
+
|
|
+#define AuStr(name, str) do { \
|
|
+ v = mnt_flags & AuOptMask_##name; \
|
|
+ if (v != (AuOpt_Def & AuOptMask_##name)) \
|
|
+ seq_printf(m, "," #str "=%s", au_optstr_##str(v)); \
|
|
+} while (0)
|
|
+
|
|
+#define AuUInt(name, str, val) do { \
|
|
+ if (val != AUFS_##name##_DEF) \
|
|
+ seq_printf(m, "," #str "=%u", val); \
|
|
+} while (0)
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ if (sb->s_flags & SB_POSIXACL)
|
|
+ seq_puts(m, ",acl");
|
|
+#if 0 /* reserved for future use */
|
|
+ if (sb->s_flags & SB_I_VERSION)
|
|
+ seq_puts(m, ",i_version");
|
|
+#endif
|
|
+
|
|
+ /* lock free root dinfo */
|
|
+ si_noflush_read_lock(sb);
|
|
+ sbinfo = au_sbi(sb);
|
|
+ seq_printf(m, ",si=%lx", sysaufs_si_id(sbinfo));
|
|
+
|
|
+ mnt_flags = au_mntflags(sb);
|
|
+ if (au_opt_test(mnt_flags, XINO)) {
|
|
+ err = au_show_xino(m, sb);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ } else
|
|
+ seq_puts(m, ",noxino");
|
|
+
|
|
+ AuBool(TRUNC_XINO, trunc_xino);
|
|
+ AuStr(UDBA, udba);
|
|
+ AuBool(SHWH, shwh);
|
|
+ AuBool(PLINK, plink);
|
|
+ AuBool(DIO, dio);
|
|
+ AuBool(DIRPERM1, dirperm1);
|
|
+
|
|
+ v = sbinfo->si_wbr_create;
|
|
+ if (v != AuWbrCreate_Def)
|
|
+ au_show_wbr_create(m, v, sbinfo);
|
|
+
|
|
+ v = sbinfo->si_wbr_copyup;
|
|
+ if (v != AuWbrCopyup_Def)
|
|
+ seq_printf(m, ",cpup=%s", au_optstr_wbr_copyup(v));
|
|
+
|
|
+ v = au_opt_test(mnt_flags, ALWAYS_DIROPQ);
|
|
+ if (v != au_opt_test(AuOpt_Def, ALWAYS_DIROPQ))
|
|
+ seq_printf(m, ",diropq=%c", v ? 'a' : 'w');
|
|
+
|
|
+ AuUInt(DIRWH, dirwh, sbinfo->si_dirwh);
|
|
+
|
|
+ v = jiffies_to_msecs(sbinfo->si_rdcache) / MSEC_PER_SEC;
|
|
+ AuUInt(RDCACHE, rdcache, v);
|
|
+
|
|
+ AuUInt(RDBLK, rdblk, sbinfo->si_rdblk);
|
|
+ AuUInt(RDHASH, rdhash, sbinfo->si_rdhash);
|
|
+
|
|
+ au_fhsm_show(m, sbinfo);
|
|
+
|
|
+ AuBool(DIRREN, dirren);
|
|
+ AuBool(SUM, sum);
|
|
+ /* AuBool(SUM_W, wsum); */
|
|
+ AuBool(WARN_PERM, warn_perm);
|
|
+ AuBool(VERBOSE, verbose);
|
|
+
|
|
+out:
|
|
+ /* be sure to print "br:" last */
|
|
+ if (!sysaufs_brs) {
|
|
+ seq_puts(m, ",br:");
|
|
+ au_show_brs(m, sb);
|
|
+ }
|
|
+ si_read_unlock(sb);
|
|
+ return 0;
|
|
+
|
|
+#undef AuBool
|
|
+#undef AuStr
|
|
+#undef AuUInt
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* sum mode which returns the summation for statfs(2) */
|
|
+
|
|
+static u64 au_add_till_max(u64 a, u64 b)
|
|
+{
|
|
+ u64 old;
|
|
+
|
|
+ old = a;
|
|
+ a += b;
|
|
+ if (old <= a)
|
|
+ return a;
|
|
+ return ULLONG_MAX;
|
|
+}
|
|
+
|
|
+static u64 au_mul_till_max(u64 a, long mul)
|
|
+{
|
|
+ u64 old;
|
|
+
|
|
+ old = a;
|
|
+ a *= mul;
|
|
+ if (old <= a)
|
|
+ return a;
|
|
+ return ULLONG_MAX;
|
|
+}
|
|
+
|
|
+static int au_statfs_sum(struct super_block *sb, struct kstatfs *buf)
|
|
+{
|
|
+ int err;
|
|
+ long bsize, factor;
|
|
+ u64 blocks, bfree, bavail, files, ffree;
|
|
+ aufs_bindex_t bbot, bindex, i;
|
|
+ unsigned char shared;
|
|
+ struct path h_path;
|
|
+ struct super_block *h_sb;
|
|
+
|
|
+ err = 0;
|
|
+ bsize = LONG_MAX;
|
|
+ files = 0;
|
|
+ ffree = 0;
|
|
+ blocks = 0;
|
|
+ bfree = 0;
|
|
+ bavail = 0;
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++) {
|
|
+ h_path.mnt = au_sbr_mnt(sb, bindex);
|
|
+ h_sb = h_path.mnt->mnt_sb;
|
|
+ shared = 0;
|
|
+ for (i = 0; !shared && i < bindex; i++)
|
|
+ shared = (au_sbr_sb(sb, i) == h_sb);
|
|
+ if (shared)
|
|
+ continue;
|
|
+
|
|
+ /* sb->s_root for NFS is unreliable */
|
|
+ h_path.dentry = h_path.mnt->mnt_root;
|
|
+ err = vfs_statfs(&h_path, buf);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ if (bsize > buf->f_bsize) {
|
|
+ /*
|
|
+ * we will reduce bsize, so we have to expand blocks
|
|
+ * etc. to match them again
|
|
+ */
|
|
+ factor = (bsize / buf->f_bsize);
|
|
+ blocks = au_mul_till_max(blocks, factor);
|
|
+ bfree = au_mul_till_max(bfree, factor);
|
|
+ bavail = au_mul_till_max(bavail, factor);
|
|
+ bsize = buf->f_bsize;
|
|
+ }
|
|
+
|
|
+ factor = (buf->f_bsize / bsize);
|
|
+ blocks = au_add_till_max(blocks,
|
|
+ au_mul_till_max(buf->f_blocks, factor));
|
|
+ bfree = au_add_till_max(bfree,
|
|
+ au_mul_till_max(buf->f_bfree, factor));
|
|
+ bavail = au_add_till_max(bavail,
|
|
+ au_mul_till_max(buf->f_bavail, factor));
|
|
+ files = au_add_till_max(files, buf->f_files);
|
|
+ ffree = au_add_till_max(ffree, buf->f_ffree);
|
|
+ }
|
|
+
|
|
+ buf->f_bsize = bsize;
|
|
+ buf->f_blocks = blocks;
|
|
+ buf->f_bfree = bfree;
|
|
+ buf->f_bavail = bavail;
|
|
+ buf->f_files = files;
|
|
+ buf->f_ffree = ffree;
|
|
+ buf->f_frsize = 0;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_statfs(struct dentry *dentry, struct kstatfs *buf)
|
|
+{
|
|
+ int err;
|
|
+ struct path h_path;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ /* lock free root dinfo */
|
|
+ sb = dentry->d_sb;
|
|
+ si_noflush_read_lock(sb);
|
|
+ if (!au_opt_test(au_mntflags(sb), SUM)) {
|
|
+ /* sb->s_root for NFS is unreliable */
|
|
+ h_path.mnt = au_sbr_mnt(sb, 0);
|
|
+ h_path.dentry = h_path.mnt->mnt_root;
|
|
+ err = vfs_statfs(&h_path, buf);
|
|
+ } else
|
|
+ err = au_statfs_sum(sb, buf);
|
|
+ si_read_unlock(sb);
|
|
+
|
|
+ if (!err) {
|
|
+ buf->f_type = AUFS_SUPER_MAGIC;
|
|
+ buf->f_namelen = AUFS_MAX_NAMELEN;
|
|
+ memset(&buf->f_fsid, 0, sizeof(buf->f_fsid));
|
|
+ }
|
|
+ /* buf->f_bsize = buf->f_blocks = buf->f_bfree = buf->f_bavail = -1; */
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int aufs_sync_fs(struct super_block *sb, int wait)
|
|
+{
|
|
+ int err, e;
|
|
+ aufs_bindex_t bbot, bindex;
|
|
+ struct au_branch *br;
|
|
+ struct super_block *h_sb;
|
|
+
|
|
+ err = 0;
|
|
+ si_noflush_read_lock(sb);
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (!au_br_writable(br->br_perm))
|
|
+ continue;
|
|
+
|
|
+ h_sb = au_sbr_sb(sb, bindex);
|
|
+ e = vfsub_sync_filesystem(h_sb, wait);
|
|
+ if (unlikely(e && !err))
|
|
+ err = e;
|
|
+ /* go on even if an error happens */
|
|
+ }
|
|
+ si_read_unlock(sb);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* final actions when unmounting a file system */
|
|
+static void aufs_put_super(struct super_block *sb)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ if (sbinfo)
|
|
+ kobject_put(&sbinfo->si_kobj);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void *au_array_alloc(unsigned long long *hint, au_arraycb_t cb,
|
|
+ struct super_block *sb, void *arg)
|
|
+{
|
|
+ void *array;
|
|
+ unsigned long long n, sz;
|
|
+
|
|
+ array = NULL;
|
|
+ n = 0;
|
|
+ if (!*hint)
|
|
+ goto out;
|
|
+
|
|
+ if (*hint > ULLONG_MAX / sizeof(array)) {
|
|
+ array = ERR_PTR(-EMFILE);
|
|
+ pr_err("hint %llu\n", *hint);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ sz = sizeof(array) * *hint;
|
|
+ array = kzalloc(sz, GFP_NOFS);
|
|
+ if (unlikely(!array))
|
|
+ array = vzalloc(sz);
|
|
+ if (unlikely(!array)) {
|
|
+ array = ERR_PTR(-ENOMEM);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ n = cb(sb, array, *hint, arg);
|
|
+ AuDebugOn(n > *hint);
|
|
+
|
|
+out:
|
|
+ *hint = n;
|
|
+ return array;
|
|
+}
|
|
+
|
|
+static unsigned long long au_iarray_cb(struct super_block *sb, void *a,
|
|
+ unsigned long long max __maybe_unused,
|
|
+ void *arg)
|
|
+{
|
|
+ unsigned long long n;
|
|
+ struct inode **p, *inode;
|
|
+ struct list_head *head;
|
|
+
|
|
+ n = 0;
|
|
+ p = a;
|
|
+ head = arg;
|
|
+ spin_lock(&sb->s_inode_list_lock);
|
|
+ list_for_each_entry(inode, head, i_sb_list) {
|
|
+ if (!au_is_bad_inode(inode)
|
|
+ && au_ii(inode)->ii_btop >= 0) {
|
|
+ spin_lock(&inode->i_lock);
|
|
+ if (atomic_read(&inode->i_count)) {
|
|
+ au_igrab(inode);
|
|
+ *p++ = inode;
|
|
+ n++;
|
|
+ AuDebugOn(n > max);
|
|
+ }
|
|
+ spin_unlock(&inode->i_lock);
|
|
+ }
|
|
+ }
|
|
+ spin_unlock(&sb->s_inode_list_lock);
|
|
+
|
|
+ return n;
|
|
+}
|
|
+
|
|
+struct inode **au_iarray_alloc(struct super_block *sb, unsigned long long *max)
|
|
+{
|
|
+ struct au_sbinfo *sbi;
|
|
+
|
|
+ sbi = au_sbi(sb);
|
|
+ *max = au_lcnt_read(&sbi->si_ninodes, /*do_rev*/1);
|
|
+ return au_array_alloc(max, au_iarray_cb, sb, &sb->s_inodes);
|
|
+}
|
|
+
|
|
+void au_iarray_free(struct inode **a, unsigned long long max)
|
|
+{
|
|
+ unsigned long long ull;
|
|
+
|
|
+ for (ull = 0; ull < max; ull++)
|
|
+ iput(a[ull]);
|
|
+ kvfree(a);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * refresh dentry and inode at remount time.
|
|
+ */
|
|
+/* todo: consolidate with simple_reval_dpath() and au_reval_for_attr() */
|
|
+static int au_do_refresh(struct dentry *dentry, unsigned int dir_flags,
|
|
+ struct dentry *parent)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ di_write_lock_child(dentry);
|
|
+ di_read_lock_parent(parent, AuLock_IR);
|
|
+ err = au_refresh_dentry(dentry, parent);
|
|
+ if (!err && dir_flags)
|
|
+ au_hn_reset(d_inode(dentry), dir_flags);
|
|
+ di_read_unlock(parent, AuLock_IR);
|
|
+ di_write_unlock(dentry);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_do_refresh_d(struct dentry *dentry, unsigned int sigen,
|
|
+ struct au_sbinfo *sbinfo,
|
|
+ const unsigned int dir_flags, unsigned int do_idop)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *parent;
|
|
+
|
|
+ err = 0;
|
|
+ parent = dget_parent(dentry);
|
|
+ if (!au_digen_test(parent, sigen) && au_digen_test(dentry, sigen)) {
|
|
+ if (d_really_is_positive(dentry)) {
|
|
+ if (!d_is_dir(dentry))
|
|
+ err = au_do_refresh(dentry, /*dir_flags*/0,
|
|
+ parent);
|
|
+ else {
|
|
+ err = au_do_refresh(dentry, dir_flags, parent);
|
|
+ if (unlikely(err))
|
|
+ au_fset_si(sbinfo, FAILED_REFRESH_DIR);
|
|
+ }
|
|
+ } else
|
|
+ err = au_do_refresh(dentry, /*dir_flags*/0, parent);
|
|
+ AuDbgDentry(dentry);
|
|
+ }
|
|
+ dput(parent);
|
|
+
|
|
+ if (!err) {
|
|
+ if (do_idop)
|
|
+ au_refresh_dop(dentry, /*force_reval*/0);
|
|
+ } else
|
|
+ au_refresh_dop(dentry, /*force_reval*/1);
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_refresh_d(struct super_block *sb, unsigned int do_idop)
|
|
+{
|
|
+ int err, i, j, ndentry, e;
|
|
+ unsigned int sigen;
|
|
+ struct au_dcsub_pages dpages;
|
|
+ struct au_dpage *dpage;
|
|
+ struct dentry **dentries, *d;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct dentry *root = sb->s_root;
|
|
+ const unsigned int dir_flags = au_hi_flags(d_inode(root), /*isdir*/1);
|
|
+
|
|
+ if (do_idop)
|
|
+ au_refresh_dop(root, /*force_reval*/0);
|
|
+
|
|
+ err = au_dpages_init(&dpages, GFP_NOFS);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = au_dcsub_pages(&dpages, root, NULL, NULL);
|
|
+ if (unlikely(err))
|
|
+ goto out_dpages;
|
|
+
|
|
+ sigen = au_sigen(sb);
|
|
+ sbinfo = au_sbi(sb);
|
|
+ for (i = 0; i < dpages.ndpage; i++) {
|
|
+ dpage = dpages.dpages + i;
|
|
+ dentries = dpage->dentries;
|
|
+ ndentry = dpage->ndentry;
|
|
+ for (j = 0; j < ndentry; j++) {
|
|
+ d = dentries[j];
|
|
+ e = au_do_refresh_d(d, sigen, sbinfo, dir_flags,
|
|
+ do_idop);
|
|
+ if (unlikely(e && !err))
|
|
+ err = e;
|
|
+ /* go on even err */
|
|
+ }
|
|
+ }
|
|
+
|
|
+out_dpages:
|
|
+ au_dpages_free(&dpages);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_refresh_i(struct super_block *sb, unsigned int do_idop)
|
|
+{
|
|
+ int err, e;
|
|
+ unsigned int sigen;
|
|
+ unsigned long long max, ull;
|
|
+ struct inode *inode, **array;
|
|
+
|
|
+ array = au_iarray_alloc(sb, &max);
|
|
+ err = PTR_ERR(array);
|
|
+ if (IS_ERR(array))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ sigen = au_sigen(sb);
|
|
+ for (ull = 0; ull < max; ull++) {
|
|
+ inode = array[ull];
|
|
+ if (unlikely(!inode))
|
|
+ break;
|
|
+
|
|
+ e = 0;
|
|
+ ii_write_lock_child(inode);
|
|
+ if (au_iigen(inode, NULL) != sigen) {
|
|
+ e = au_refresh_hinode_self(inode);
|
|
+ if (unlikely(e)) {
|
|
+ au_refresh_iop(inode, /*force_getattr*/1);
|
|
+ pr_err("error %d, i%lu\n", e, inode->i_ino);
|
|
+ if (!err)
|
|
+ err = e;
|
|
+ /* go on even if err */
|
|
+ }
|
|
+ }
|
|
+ if (!e && do_idop)
|
|
+ au_refresh_iop(inode, /*force_getattr*/0);
|
|
+ ii_write_unlock(inode);
|
|
+ }
|
|
+
|
|
+ au_iarray_free(array, max);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_remount_refresh(struct super_block *sb, unsigned int do_idop)
|
|
+{
|
|
+ int err, e;
|
|
+ unsigned int udba;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct dentry *root;
|
|
+ struct inode *inode;
|
|
+ struct au_branch *br;
|
|
+ struct au_sbinfo *sbi;
|
|
+
|
|
+ au_sigen_inc(sb);
|
|
+ sbi = au_sbi(sb);
|
|
+ au_fclr_si(sbi, FAILED_REFRESH_DIR);
|
|
+
|
|
+ root = sb->s_root;
|
|
+ DiMustNoWaiters(root);
|
|
+ inode = d_inode(root);
|
|
+ IiMustNoWaiters(inode);
|
|
+
|
|
+ udba = au_opt_udba(sb);
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ err = au_hnotify_reset_br(udba, br, br->br_perm);
|
|
+ if (unlikely(err))
|
|
+ AuIOErr("hnotify failed on br %d, %d, ignored\n",
|
|
+ bindex, err);
|
|
+ /* go on even if err */
|
|
+ }
|
|
+ au_hn_reset(inode, au_hi_flags(inode, /*isdir*/1));
|
|
+
|
|
+ if (do_idop) {
|
|
+ if (au_ftest_si(sbi, NO_DREVAL)) {
|
|
+ AuDebugOn(sb->s_d_op == &aufs_dop_noreval);
|
|
+ sb->s_d_op = &aufs_dop_noreval;
|
|
+ AuDebugOn(sbi->si_iop_array == aufs_iop_nogetattr);
|
|
+ sbi->si_iop_array = aufs_iop_nogetattr;
|
|
+ } else {
|
|
+ AuDebugOn(sb->s_d_op == &aufs_dop);
|
|
+ sb->s_d_op = &aufs_dop;
|
|
+ AuDebugOn(sbi->si_iop_array == aufs_iop);
|
|
+ sbi->si_iop_array = aufs_iop;
|
|
+ }
|
|
+ pr_info("reset to %ps and %ps\n",
|
|
+ sb->s_d_op, sbi->si_iop_array);
|
|
+ }
|
|
+
|
|
+ di_write_unlock(root);
|
|
+ err = au_refresh_d(sb, do_idop);
|
|
+ e = au_refresh_i(sb, do_idop);
|
|
+ if (unlikely(e && !err))
|
|
+ err = e;
|
|
+ /* aufs_write_lock() calls ..._child() */
|
|
+ di_write_lock_child(root);
|
|
+
|
|
+ au_cpup_attr_all(inode, /*force*/1);
|
|
+
|
|
+ if (unlikely(err))
|
|
+ AuIOErr("refresh failed, ignored, %d\n", err);
|
|
+}
|
|
+
|
|
+/* stop extra interpretation of errno in mount(8), and strange error messages */
|
|
+static int cvt_err(int err)
|
|
+{
|
|
+ AuTraceErr(err);
|
|
+
|
|
+ switch (err) {
|
|
+ case -ENOENT:
|
|
+ case -ENOTDIR:
|
|
+ case -EEXIST:
|
|
+ case -EIO:
|
|
+ err = -EINVAL;
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_remount_fs(struct super_block *sb, int *flags, char *data)
|
|
+{
|
|
+ int err, do_dx;
|
|
+ unsigned int mntflags;
|
|
+ struct au_opts opts = {
|
|
+ .opt = NULL
|
|
+ };
|
|
+ struct dentry *root;
|
|
+ struct inode *inode;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ err = 0;
|
|
+ root = sb->s_root;
|
|
+ if (!data || !*data) {
|
|
+ err = si_write_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
|
|
+ if (!err) {
|
|
+ di_write_lock_child(root);
|
|
+ err = au_opts_verify(sb, *flags, /*pending*/0);
|
|
+ aufs_write_unlock(root);
|
|
+ }
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ opts.opt = (void *)__get_free_page(GFP_NOFS);
|
|
+ if (unlikely(!opts.opt))
|
|
+ goto out;
|
|
+ opts.max_opt = PAGE_SIZE / sizeof(*opts.opt);
|
|
+ opts.flags = AuOpts_REMOUNT;
|
|
+ opts.sb_flags = *flags;
|
|
+
|
|
+ /* parse it before aufs lock */
|
|
+ err = au_opts_parse(sb, data, &opts);
|
|
+ if (unlikely(err))
|
|
+ goto out_opts;
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ inode = d_inode(root);
|
|
+ inode_lock(inode);
|
|
+ err = si_write_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
|
|
+ if (unlikely(err))
|
|
+ goto out_mtx;
|
|
+ di_write_lock_child(root);
|
|
+
|
|
+ /* au_opts_remount() may return an error */
|
|
+ err = au_opts_remount(sb, &opts);
|
|
+ au_opts_free(&opts);
|
|
+
|
|
+ if (au_ftest_opts(opts.flags, REFRESH))
|
|
+ au_remount_refresh(sb, au_ftest_opts(opts.flags, REFRESH_IDOP));
|
|
+
|
|
+ if (au_ftest_opts(opts.flags, REFRESH_DYAOP)) {
|
|
+ mntflags = au_mntflags(sb);
|
|
+ do_dx = !!au_opt_test(mntflags, DIO);
|
|
+ au_dy_arefresh(do_dx);
|
|
+ }
|
|
+
|
|
+ au_fhsm_wrote_all(sb, /*force*/1); /* ?? */
|
|
+ aufs_write_unlock(root);
|
|
+
|
|
+out_mtx:
|
|
+ inode_unlock(inode);
|
|
+out_opts:
|
|
+ free_page((unsigned long)opts.opt);
|
|
+out:
|
|
+ err = cvt_err(err);
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct super_operations aufs_sop = {
|
|
+ .alloc_inode = aufs_alloc_inode,
|
|
+ .destroy_inode = aufs_destroy_inode,
|
|
+ .free_inode = aufs_free_inode,
|
|
+ /* always deleting, no clearing */
|
|
+ .drop_inode = generic_delete_inode,
|
|
+ .show_options = aufs_show_options,
|
|
+ .statfs = aufs_statfs,
|
|
+ .put_super = aufs_put_super,
|
|
+ .sync_fs = aufs_sync_fs,
|
|
+ .remount_fs = aufs_remount_fs
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int alloc_root(struct super_block *sb)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *inode;
|
|
+ struct dentry *root;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ inode = au_iget_locked(sb, AUFS_ROOT_INO);
|
|
+ err = PTR_ERR(inode);
|
|
+ if (IS_ERR(inode))
|
|
+ goto out;
|
|
+
|
|
+ inode->i_op = aufs_iop + AuIop_DIR; /* with getattr by default */
|
|
+ inode->i_fop = &aufs_dir_fop;
|
|
+ inode->i_mode = S_IFDIR;
|
|
+ set_nlink(inode, 2);
|
|
+ unlock_new_inode(inode);
|
|
+
|
|
+ root = d_make_root(inode);
|
|
+ if (unlikely(!root))
|
|
+ goto out;
|
|
+ err = PTR_ERR(root);
|
|
+ if (IS_ERR(root))
|
|
+ goto out;
|
|
+
|
|
+ err = au_di_init(root);
|
|
+ if (!err) {
|
|
+ sb->s_root = root;
|
|
+ return 0; /* success */
|
|
+ }
|
|
+ dput(root);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int aufs_fill_super(struct super_block *sb, void *raw_data,
|
|
+ int silent __maybe_unused)
|
|
+{
|
|
+ int err;
|
|
+ struct au_opts opts = {
|
|
+ .opt = NULL
|
|
+ };
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct dentry *root;
|
|
+ struct inode *inode;
|
|
+ char *arg = raw_data;
|
|
+
|
|
+ if (unlikely(!arg || !*arg)) {
|
|
+ err = -EINVAL;
|
|
+ pr_err("no arg\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ opts.opt = (void *)__get_free_page(GFP_NOFS);
|
|
+ if (unlikely(!opts.opt))
|
|
+ goto out;
|
|
+ opts.max_opt = PAGE_SIZE / sizeof(*opts.opt);
|
|
+ opts.sb_flags = sb->s_flags;
|
|
+
|
|
+ err = au_si_alloc(sb);
|
|
+ if (unlikely(err))
|
|
+ goto out_opts;
|
|
+ sbinfo = au_sbi(sb);
|
|
+
|
|
+ /* all timestamps always follow the ones on the branch */
|
|
+ sb->s_flags |= SB_NOATIME | SB_NODIRATIME;
|
|
+ sb->s_flags |= SB_I_VERSION; /* do we really need this? */
|
|
+ sb->s_op = &aufs_sop;
|
|
+ sb->s_d_op = &aufs_dop;
|
|
+ sb->s_magic = AUFS_SUPER_MAGIC;
|
|
+ sb->s_maxbytes = 0;
|
|
+ sb->s_stack_depth = 1;
|
|
+ au_export_init(sb);
|
|
+ au_xattr_init(sb);
|
|
+
|
|
+ err = alloc_root(sb);
|
|
+ if (unlikely(err)) {
|
|
+ si_write_unlock(sb);
|
|
+ goto out_info;
|
|
+ }
|
|
+ root = sb->s_root;
|
|
+ inode = d_inode(root);
|
|
+
|
|
+ /*
|
|
+ * actually we can parse options regardless aufs lock here.
|
|
+ * but at remount time, parsing must be done before aufs lock.
|
|
+ * so we follow the same rule.
|
|
+ */
|
|
+ ii_write_lock_parent(inode);
|
|
+ aufs_write_unlock(root);
|
|
+ err = au_opts_parse(sb, arg, &opts);
|
|
+ if (unlikely(err))
|
|
+ goto out_root;
|
|
+
|
|
+ /* lock vfs_inode first, then aufs. */
|
|
+ inode_lock(inode);
|
|
+ aufs_write_lock(root);
|
|
+ err = au_opts_mount(sb, &opts);
|
|
+ au_opts_free(&opts);
|
|
+ if (!err && au_ftest_si(sbinfo, NO_DREVAL)) {
|
|
+ sb->s_d_op = &aufs_dop_noreval;
|
|
+ pr_info("%ps\n", sb->s_d_op);
|
|
+ au_refresh_dop(root, /*force_reval*/0);
|
|
+ sbinfo->si_iop_array = aufs_iop_nogetattr;
|
|
+ au_refresh_iop(inode, /*force_getattr*/0);
|
|
+ }
|
|
+ aufs_write_unlock(root);
|
|
+ inode_unlock(inode);
|
|
+ if (!err)
|
|
+ goto out_opts; /* success */
|
|
+
|
|
+out_root:
|
|
+ dput(root);
|
|
+ sb->s_root = NULL;
|
|
+out_info:
|
|
+ kobject_put(&sbinfo->si_kobj);
|
|
+ sb->s_fs_info = NULL;
|
|
+out_opts:
|
|
+ free_page((unsigned long)opts.opt);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ err = cvt_err(err);
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static struct dentry *aufs_mount(struct file_system_type *fs_type, int flags,
|
|
+ const char *dev_name __maybe_unused,
|
|
+ void *raw_data)
|
|
+{
|
|
+ struct dentry *root;
|
|
+
|
|
+ /* all timestamps always follow the ones on the branch */
|
|
+ /* mnt->mnt_flags |= MNT_NOATIME | MNT_NODIRATIME; */
|
|
+ root = mount_nodev(fs_type, flags, raw_data, aufs_fill_super);
|
|
+ if (IS_ERR(root))
|
|
+ goto out;
|
|
+
|
|
+ au_sbilist_add(root->d_sb);
|
|
+
|
|
+out:
|
|
+ return root;
|
|
+}
|
|
+
|
|
+static void aufs_kill_sb(struct super_block *sb)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ if (sbinfo) {
|
|
+ au_sbilist_del(sb);
|
|
+ aufs_write_lock(sb->s_root);
|
|
+ au_fhsm_fin(sb);
|
|
+ if (sbinfo->si_wbr_create_ops->fin)
|
|
+ sbinfo->si_wbr_create_ops->fin(sb);
|
|
+ if (au_opt_test(sbinfo->si_mntflags, UDBA_HNOTIFY)) {
|
|
+ au_opt_set_udba(sbinfo->si_mntflags, UDBA_NONE);
|
|
+ au_remount_refresh(sb, /*do_idop*/0);
|
|
+ }
|
|
+ if (au_opt_test(sbinfo->si_mntflags, PLINK))
|
|
+ au_plink_put(sb, /*verbose*/1);
|
|
+ au_xino_clr(sb);
|
|
+ au_dr_opt_flush(sb);
|
|
+ sbinfo->si_sb = NULL;
|
|
+ aufs_write_unlock(sb->s_root);
|
|
+ au_nwt_flush(&sbinfo->si_nowait);
|
|
+ }
|
|
+ kill_anon_super(sb);
|
|
+}
|
|
+
|
|
+struct file_system_type aufs_fs_type = {
|
|
+ .name = AUFS_FSTYPE,
|
|
+ /* a race between rename and others */
|
|
+ .fs_flags = FS_RENAME_DOES_D_MOVE,
|
|
+ .mount = aufs_mount,
|
|
+ .kill_sb = aufs_kill_sb,
|
|
+ /* no need to __module_get() and module_put(). */
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
diff --git a/fs/aufs/super.h b/fs/aufs/super.h
|
|
new file mode 100644
|
|
index 000000000000..c0cb0051242c
|
|
--- /dev/null
|
|
+++ b/fs/aufs/super.h
|
|
@@ -0,0 +1,589 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * super_block operations
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_SUPER_H__
|
|
+#define __AUFS_SUPER_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/fs.h>
|
|
+#include <linux/kobject.h>
|
|
+#include "hbl.h"
|
|
+#include "lcnt.h"
|
|
+#include "rwsem.h"
|
|
+#include "wkq.h"
|
|
+
|
|
+/* policies to select one among multiple writable branches */
|
|
+struct au_wbr_copyup_operations {
|
|
+ int (*copyup)(struct dentry *dentry);
|
|
+};
|
|
+
|
|
+#define AuWbr_DIR 1 /* target is a dir */
|
|
+#define AuWbr_PARENT (1 << 1) /* always require a parent */
|
|
+
|
|
+#define au_ftest_wbr(flags, name) ((flags) & AuWbr_##name)
|
|
+#define au_fset_wbr(flags, name) { (flags) |= AuWbr_##name; }
|
|
+#define au_fclr_wbr(flags, name) { (flags) &= ~AuWbr_##name; }
|
|
+
|
|
+struct au_wbr_create_operations {
|
|
+ int (*create)(struct dentry *dentry, unsigned int flags);
|
|
+ int (*init)(struct super_block *sb);
|
|
+ int (*fin)(struct super_block *sb);
|
|
+};
|
|
+
|
|
+struct au_wbr_mfs {
|
|
+ struct mutex mfs_lock; /* protect this structure */
|
|
+ unsigned long mfs_jiffy;
|
|
+ unsigned long mfs_expire;
|
|
+ aufs_bindex_t mfs_bindex;
|
|
+
|
|
+ unsigned long long mfsrr_bytes;
|
|
+ unsigned long long mfsrr_watermark;
|
|
+};
|
|
+
|
|
+#define AuPlink_NHASH 100
|
|
+static inline int au_plink_hash(ino_t ino)
|
|
+{
|
|
+ return ino % AuPlink_NHASH;
|
|
+}
|
|
+
|
|
+/* File-based Hierarchical Storage Management */
|
|
+struct au_fhsm {
|
|
+#ifdef CONFIG_AUFS_FHSM
|
|
+ /* allow only one process who can receive the notification */
|
|
+ spinlock_t fhsm_spin;
|
|
+ pid_t fhsm_pid;
|
|
+ wait_queue_head_t fhsm_wqh;
|
|
+ atomic_t fhsm_readable;
|
|
+
|
|
+ /* these are protected by si_rwsem */
|
|
+ unsigned long fhsm_expire;
|
|
+ aufs_bindex_t fhsm_bottom;
|
|
+#endif
|
|
+};
|
|
+
|
|
+struct au_branch;
|
|
+struct au_sbinfo {
|
|
+ /* nowait tasks in the system-wide workqueue */
|
|
+ struct au_nowait_tasks si_nowait;
|
|
+
|
|
+ /*
|
|
+ * tried sb->s_umount, but failed due to the dependency between i_mutex.
|
|
+ * rwsem for au_sbinfo is necessary.
|
|
+ */
|
|
+ struct au_rwsem si_rwsem;
|
|
+
|
|
+ /*
|
|
+ * dirty approach to protect sb->sb_inodes and ->s_files (gone) from
|
|
+ * remount.
|
|
+ */
|
|
+ au_lcnt_t si_ninodes, si_nfiles;
|
|
+
|
|
+ /* branch management */
|
|
+ unsigned int si_generation;
|
|
+
|
|
+ /* see AuSi_ flags */
|
|
+ unsigned char au_si_status;
|
|
+
|
|
+ aufs_bindex_t si_bbot;
|
|
+
|
|
+ /* dirty trick to keep br_id plus */
|
|
+ unsigned int si_last_br_id :
|
|
+ sizeof(aufs_bindex_t) * BITS_PER_BYTE - 1;
|
|
+ struct au_branch **si_branch;
|
|
+
|
|
+ /* policy to select a writable branch */
|
|
+ unsigned char si_wbr_copyup;
|
|
+ unsigned char si_wbr_create;
|
|
+ struct au_wbr_copyup_operations *si_wbr_copyup_ops;
|
|
+ struct au_wbr_create_operations *si_wbr_create_ops;
|
|
+
|
|
+ /* round robin */
|
|
+ atomic_t si_wbr_rr_next;
|
|
+
|
|
+ /* most free space */
|
|
+ struct au_wbr_mfs si_wbr_mfs;
|
|
+
|
|
+ /* File-based Hierarchical Storage Management */
|
|
+ struct au_fhsm si_fhsm;
|
|
+
|
|
+ /* mount flags */
|
|
+ /* include/asm-ia64/siginfo.h defines a macro named si_flags */
|
|
+ unsigned int si_mntflags;
|
|
+
|
|
+ /* external inode number (bitmap and translation table) */
|
|
+ vfs_readf_t si_xread;
|
|
+ vfs_writef_t si_xwrite;
|
|
+ loff_t si_ximaxent; /* max entries in a xino */
|
|
+
|
|
+ struct file *si_xib;
|
|
+ struct mutex si_xib_mtx; /* protect xib members */
|
|
+ unsigned long *si_xib_buf;
|
|
+ unsigned long si_xib_last_pindex;
|
|
+ int si_xib_next_bit;
|
|
+
|
|
+ unsigned long si_xino_jiffy;
|
|
+ unsigned long si_xino_expire;
|
|
+ /* reserved for future use */
|
|
+ /* unsigned long long si_xib_limit; */ /* Max xib file size */
|
|
+
|
|
+#ifdef CONFIG_AUFS_EXPORT
|
|
+ /* i_generation */
|
|
+ /* todo: make xigen file an array to support many inode numbers */
|
|
+ struct file *si_xigen;
|
|
+ atomic_t si_xigen_next;
|
|
+#endif
|
|
+
|
|
+ /* dirty trick to support atomic_open */
|
|
+ struct hlist_bl_head si_aopen;
|
|
+
|
|
+ /* vdir parameters */
|
|
+ unsigned long si_rdcache; /* max cache time in jiffies */
|
|
+ unsigned int si_rdblk; /* deblk size */
|
|
+ unsigned int si_rdhash; /* hash size */
|
|
+
|
|
+ /*
|
|
+ * If the number of whiteouts are larger than si_dirwh, leave all of
|
|
+ * them after au_whtmp_ren to reduce the cost of rmdir(2).
|
|
+ * future fsck.aufs or kernel thread will remove them later.
|
|
+ * Otherwise, remove all whiteouts and the dir in rmdir(2).
|
|
+ */
|
|
+ unsigned int si_dirwh;
|
|
+
|
|
+ /* pseudo_link list */
|
|
+ struct hlist_bl_head si_plink[AuPlink_NHASH];
|
|
+ wait_queue_head_t si_plink_wq;
|
|
+ spinlock_t si_plink_maint_lock;
|
|
+ pid_t si_plink_maint_pid;
|
|
+
|
|
+ /* file list */
|
|
+ struct hlist_bl_head si_files;
|
|
+
|
|
+ /* with/without getattr, brother of sb->s_d_op */
|
|
+ const struct inode_operations *si_iop_array;
|
|
+
|
|
+ /*
|
|
+ * sysfs and lifetime management.
|
|
+ * this is not a small structure and it may be a waste of memory in case
|
|
+ * of sysfs is disabled, particularly when many aufs-es are mounted.
|
|
+ * but using sysfs is majority.
|
|
+ */
|
|
+ struct kobject si_kobj;
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+ struct dentry *si_dbgaufs;
|
|
+ struct dentry *si_dbgaufs_plink;
|
|
+ struct dentry *si_dbgaufs_xib;
|
|
+#ifdef CONFIG_AUFS_EXPORT
|
|
+ struct dentry *si_dbgaufs_xigen;
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+#ifdef CONFIG_AUFS_SBILIST
|
|
+ struct hlist_bl_node si_list;
|
|
+#endif
|
|
+
|
|
+ /* dirty, necessary for unmounting, sysfs and sysrq */
|
|
+ struct super_block *si_sb;
|
|
+};
|
|
+
|
|
+/* sbinfo status flags */
|
|
+/*
|
|
+ * set true when refresh_dirs() failed at remount time.
|
|
+ * then try refreshing dirs at access time again.
|
|
+ * if it is false, refreshing dirs at access time is unnecessary
|
|
+ */
|
|
+#define AuSi_FAILED_REFRESH_DIR 1
|
|
+#define AuSi_FHSM (1 << 1) /* fhsm is active now */
|
|
+#define AuSi_NO_DREVAL (1 << 2) /* disable all d_revalidate */
|
|
+
|
|
+#ifndef CONFIG_AUFS_FHSM
|
|
+#undef AuSi_FHSM
|
|
+#define AuSi_FHSM 0
|
|
+#endif
|
|
+
|
|
+static inline unsigned char au_do_ftest_si(struct au_sbinfo *sbi,
|
|
+ unsigned int flag)
|
|
+{
|
|
+ AuRwMustAnyLock(&sbi->si_rwsem);
|
|
+ return sbi->au_si_status & flag;
|
|
+}
|
|
+#define au_ftest_si(sbinfo, name) au_do_ftest_si(sbinfo, AuSi_##name)
|
|
+#define au_fset_si(sbinfo, name) do { \
|
|
+ AuRwMustWriteLock(&(sbinfo)->si_rwsem); \
|
|
+ (sbinfo)->au_si_status |= AuSi_##name; \
|
|
+} while (0)
|
|
+#define au_fclr_si(sbinfo, name) do { \
|
|
+ AuRwMustWriteLock(&(sbinfo)->si_rwsem); \
|
|
+ (sbinfo)->au_si_status &= ~AuSi_##name; \
|
|
+} while (0)
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* policy to select one among writable branches */
|
|
+#define AuWbrCopyup(sbinfo, ...) \
|
|
+ ((sbinfo)->si_wbr_copyup_ops->copyup(__VA_ARGS__))
|
|
+#define AuWbrCreate(sbinfo, ...) \
|
|
+ ((sbinfo)->si_wbr_create_ops->create(__VA_ARGS__))
|
|
+
|
|
+/* flags for si_read_lock()/aufs_read_lock()/di_read_lock() */
|
|
+#define AuLock_DW 1 /* write-lock dentry */
|
|
+#define AuLock_IR (1 << 1) /* read-lock inode */
|
|
+#define AuLock_IW (1 << 2) /* write-lock inode */
|
|
+#define AuLock_FLUSH (1 << 3) /* wait for 'nowait' tasks */
|
|
+#define AuLock_DIRS (1 << 4) /* target is a pair of dirs */
|
|
+ /* except RENAME_EXCHANGE */
|
|
+#define AuLock_NOPLM (1 << 5) /* return err in plm mode */
|
|
+#define AuLock_NOPLMW (1 << 6) /* wait for plm mode ends */
|
|
+#define AuLock_GEN (1 << 7) /* test digen/iigen */
|
|
+#define au_ftest_lock(flags, name) ((flags) & AuLock_##name)
|
|
+#define au_fset_lock(flags, name) \
|
|
+ do { (flags) |= AuLock_##name; } while (0)
|
|
+#define au_fclr_lock(flags, name) \
|
|
+ do { (flags) &= ~AuLock_##name; } while (0)
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* super.c */
|
|
+extern struct file_system_type aufs_fs_type;
|
|
+struct inode *au_iget_locked(struct super_block *sb, ino_t ino);
|
|
+typedef unsigned long long (*au_arraycb_t)(struct super_block *sb, void *array,
|
|
+ unsigned long long max, void *arg);
|
|
+void *au_array_alloc(unsigned long long *hint, au_arraycb_t cb,
|
|
+ struct super_block *sb, void *arg);
|
|
+struct inode **au_iarray_alloc(struct super_block *sb, unsigned long long *max);
|
|
+void au_iarray_free(struct inode **a, unsigned long long max);
|
|
+
|
|
+/* sbinfo.c */
|
|
+void au_si_free(struct kobject *kobj);
|
|
+int au_si_alloc(struct super_block *sb);
|
|
+int au_sbr_realloc(struct au_sbinfo *sbinfo, int nbr, int may_shrink);
|
|
+
|
|
+unsigned int au_sigen_inc(struct super_block *sb);
|
|
+aufs_bindex_t au_new_br_id(struct super_block *sb);
|
|
+
|
|
+int si_read_lock(struct super_block *sb, int flags);
|
|
+int si_write_lock(struct super_block *sb, int flags);
|
|
+int aufs_read_lock(struct dentry *dentry, int flags);
|
|
+void aufs_read_unlock(struct dentry *dentry, int flags);
|
|
+void aufs_write_lock(struct dentry *dentry);
|
|
+void aufs_write_unlock(struct dentry *dentry);
|
|
+int aufs_read_and_write_lock2(struct dentry *d1, struct dentry *d2, int flags);
|
|
+void aufs_read_and_write_unlock2(struct dentry *d1, struct dentry *d2);
|
|
+
|
|
+/* wbr_policy.c */
|
|
+extern struct au_wbr_copyup_operations au_wbr_copyup_ops[];
|
|
+extern struct au_wbr_create_operations au_wbr_create_ops[];
|
|
+int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst);
|
|
+int au_wbr_nonopq(struct dentry *dentry, aufs_bindex_t bindex);
|
|
+int au_wbr_do_copyup_bu(struct dentry *dentry, aufs_bindex_t btop);
|
|
+
|
|
+/* mvdown.c */
|
|
+int au_mvdown(struct dentry *dentry, struct aufs_mvdown __user *arg);
|
|
+
|
|
+#ifdef CONFIG_AUFS_FHSM
|
|
+/* fhsm.c */
|
|
+
|
|
+static inline pid_t au_fhsm_pid(struct au_fhsm *fhsm)
|
|
+{
|
|
+ pid_t pid;
|
|
+
|
|
+ spin_lock(&fhsm->fhsm_spin);
|
|
+ pid = fhsm->fhsm_pid;
|
|
+ spin_unlock(&fhsm->fhsm_spin);
|
|
+
|
|
+ return pid;
|
|
+}
|
|
+
|
|
+void au_fhsm_wrote(struct super_block *sb, aufs_bindex_t bindex, int force);
|
|
+void au_fhsm_wrote_all(struct super_block *sb, int force);
|
|
+int au_fhsm_fd(struct super_block *sb, int oflags);
|
|
+int au_fhsm_br_alloc(struct au_branch *br);
|
|
+void au_fhsm_set_bottom(struct super_block *sb, aufs_bindex_t bindex);
|
|
+void au_fhsm_fin(struct super_block *sb);
|
|
+void au_fhsm_init(struct au_sbinfo *sbinfo);
|
|
+void au_fhsm_set(struct au_sbinfo *sbinfo, unsigned int sec);
|
|
+void au_fhsm_show(struct seq_file *seq, struct au_sbinfo *sbinfo);
|
|
+#else
|
|
+AuStubVoid(au_fhsm_wrote, struct super_block *sb, aufs_bindex_t bindex,
|
|
+ int force)
|
|
+AuStubVoid(au_fhsm_wrote_all, struct super_block *sb, int force)
|
|
+AuStub(int, au_fhsm_fd, return -EOPNOTSUPP, struct super_block *sb, int oflags)
|
|
+AuStub(pid_t, au_fhsm_pid, return 0, struct au_fhsm *fhsm)
|
|
+AuStubInt0(au_fhsm_br_alloc, struct au_branch *br)
|
|
+AuStubVoid(au_fhsm_set_bottom, struct super_block *sb, aufs_bindex_t bindex)
|
|
+AuStubVoid(au_fhsm_fin, struct super_block *sb)
|
|
+AuStubVoid(au_fhsm_init, struct au_sbinfo *sbinfo)
|
|
+AuStubVoid(au_fhsm_set, struct au_sbinfo *sbinfo, unsigned int sec)
|
|
+AuStubVoid(au_fhsm_show, struct seq_file *seq, struct au_sbinfo *sbinfo)
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline struct au_sbinfo *au_sbi(struct super_block *sb)
|
|
+{
|
|
+ return sb->s_fs_info;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_AUFS_EXPORT
|
|
+int au_test_nfsd(void);
|
|
+void au_export_init(struct super_block *sb);
|
|
+void au_xigen_inc(struct inode *inode);
|
|
+int au_xigen_new(struct inode *inode);
|
|
+int au_xigen_set(struct super_block *sb, struct path *path);
|
|
+void au_xigen_clr(struct super_block *sb);
|
|
+
|
|
+static inline int au_busy_or_stale(void)
|
|
+{
|
|
+ if (!au_test_nfsd())
|
|
+ return -EBUSY;
|
|
+ return -ESTALE;
|
|
+}
|
|
+#else
|
|
+AuStubInt0(au_test_nfsd, void)
|
|
+AuStubVoid(au_export_init, struct super_block *sb)
|
|
+AuStubVoid(au_xigen_inc, struct inode *inode)
|
|
+AuStubInt0(au_xigen_new, struct inode *inode)
|
|
+AuStubInt0(au_xigen_set, struct super_block *sb, struct path *path)
|
|
+AuStubVoid(au_xigen_clr, struct super_block *sb)
|
|
+AuStub(int, au_busy_or_stale, return -EBUSY, void)
|
|
+#endif /* CONFIG_AUFS_EXPORT */
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_AUFS_SBILIST
|
|
+/* module.c */
|
|
+extern struct hlist_bl_head au_sbilist;
|
|
+
|
|
+static inline void au_sbilist_init(void)
|
|
+{
|
|
+ INIT_HLIST_BL_HEAD(&au_sbilist);
|
|
+}
|
|
+
|
|
+static inline void au_sbilist_add(struct super_block *sb)
|
|
+{
|
|
+ au_hbl_add(&au_sbi(sb)->si_list, &au_sbilist);
|
|
+}
|
|
+
|
|
+static inline void au_sbilist_del(struct super_block *sb)
|
|
+{
|
|
+ au_hbl_del(&au_sbi(sb)->si_list, &au_sbilist);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_AUFS_MAGIC_SYSRQ
|
|
+static inline void au_sbilist_lock(void)
|
|
+{
|
|
+ hlist_bl_lock(&au_sbilist);
|
|
+}
|
|
+
|
|
+static inline void au_sbilist_unlock(void)
|
|
+{
|
|
+ hlist_bl_unlock(&au_sbilist);
|
|
+}
|
|
+#define AuGFP_SBILIST GFP_ATOMIC
|
|
+#else
|
|
+AuStubVoid(au_sbilist_lock, void)
|
|
+AuStubVoid(au_sbilist_unlock, void)
|
|
+#define AuGFP_SBILIST GFP_NOFS
|
|
+#endif /* CONFIG_AUFS_MAGIC_SYSRQ */
|
|
+#else
|
|
+AuStubVoid(au_sbilist_init, void)
|
|
+AuStubVoid(au_sbilist_add, struct super_block *sb)
|
|
+AuStubVoid(au_sbilist_del, struct super_block *sb)
|
|
+AuStubVoid(au_sbilist_lock, void)
|
|
+AuStubVoid(au_sbilist_unlock, void)
|
|
+#define AuGFP_SBILIST GFP_NOFS
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline void dbgaufs_si_null(struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ /*
|
|
+ * This function is a dynamic '__init' function actually,
|
|
+ * so the tiny check for si_rwsem is unnecessary.
|
|
+ */
|
|
+ /* AuRwMustWriteLock(&sbinfo->si_rwsem); */
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+ sbinfo->si_dbgaufs = NULL;
|
|
+ sbinfo->si_dbgaufs_plink = NULL;
|
|
+ sbinfo->si_dbgaufs_xib = NULL;
|
|
+#ifdef CONFIG_AUFS_EXPORT
|
|
+ sbinfo->si_dbgaufs_xigen = NULL;
|
|
+#endif
|
|
+#endif
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* current->atomic_flags */
|
|
+/* this value should never corrupt the ones defined in linux/sched.h */
|
|
+#define PFA_AUFS 0x10
|
|
+
|
|
+TASK_PFA_TEST(AUFS, test_aufs) /* task_test_aufs */
|
|
+TASK_PFA_SET(AUFS, aufs) /* task_set_aufs */
|
|
+TASK_PFA_CLEAR(AUFS, aufs) /* task_clear_aufs */
|
|
+
|
|
+static inline int si_pid_test(struct super_block *sb)
|
|
+{
|
|
+ return !!task_test_aufs(current);
|
|
+}
|
|
+
|
|
+static inline void si_pid_clr(struct super_block *sb)
|
|
+{
|
|
+ AuDebugOn(!task_test_aufs(current));
|
|
+ task_clear_aufs(current);
|
|
+}
|
|
+
|
|
+static inline void si_pid_set(struct super_block *sb)
|
|
+{
|
|
+ AuDebugOn(task_test_aufs(current));
|
|
+ task_set_aufs(current);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* lock superblock. mainly for entry point functions */
|
|
+#define __si_read_lock(sb) au_rw_read_lock(&au_sbi(sb)->si_rwsem)
|
|
+#define __si_write_lock(sb) au_rw_write_lock(&au_sbi(sb)->si_rwsem)
|
|
+#define __si_read_trylock(sb) au_rw_read_trylock(&au_sbi(sb)->si_rwsem)
|
|
+#define __si_write_trylock(sb) au_rw_write_trylock(&au_sbi(sb)->si_rwsem)
|
|
+/*
|
|
+#define __si_read_trylock_nested(sb) \
|
|
+ au_rw_read_trylock_nested(&au_sbi(sb)->si_rwsem)
|
|
+#define __si_write_trylock_nested(sb) \
|
|
+ au_rw_write_trylock_nested(&au_sbi(sb)->si_rwsem)
|
|
+*/
|
|
+
|
|
+#define __si_read_unlock(sb) au_rw_read_unlock(&au_sbi(sb)->si_rwsem)
|
|
+#define __si_write_unlock(sb) au_rw_write_unlock(&au_sbi(sb)->si_rwsem)
|
|
+#define __si_downgrade_lock(sb) au_rw_dgrade_lock(&au_sbi(sb)->si_rwsem)
|
|
+
|
|
+#define SiMustNoWaiters(sb) AuRwMustNoWaiters(&au_sbi(sb)->si_rwsem)
|
|
+#define SiMustAnyLock(sb) AuRwMustAnyLock(&au_sbi(sb)->si_rwsem)
|
|
+#define SiMustWriteLock(sb) AuRwMustWriteLock(&au_sbi(sb)->si_rwsem)
|
|
+
|
|
+static inline void si_noflush_read_lock(struct super_block *sb)
|
|
+{
|
|
+ __si_read_lock(sb);
|
|
+ si_pid_set(sb);
|
|
+}
|
|
+
|
|
+static inline int si_noflush_read_trylock(struct super_block *sb)
|
|
+{
|
|
+ int locked;
|
|
+
|
|
+ locked = __si_read_trylock(sb);
|
|
+ if (locked)
|
|
+ si_pid_set(sb);
|
|
+ return locked;
|
|
+}
|
|
+
|
|
+static inline void si_noflush_write_lock(struct super_block *sb)
|
|
+{
|
|
+ __si_write_lock(sb);
|
|
+ si_pid_set(sb);
|
|
+}
|
|
+
|
|
+static inline int si_noflush_write_trylock(struct super_block *sb)
|
|
+{
|
|
+ int locked;
|
|
+
|
|
+ locked = __si_write_trylock(sb);
|
|
+ if (locked)
|
|
+ si_pid_set(sb);
|
|
+ return locked;
|
|
+}
|
|
+
|
|
+#if 0 /* reserved */
|
|
+static inline int si_read_trylock(struct super_block *sb, int flags)
|
|
+{
|
|
+ if (au_ftest_lock(flags, FLUSH))
|
|
+ au_nwt_flush(&au_sbi(sb)->si_nowait);
|
|
+ return si_noflush_read_trylock(sb);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static inline void si_read_unlock(struct super_block *sb)
|
|
+{
|
|
+ si_pid_clr(sb);
|
|
+ __si_read_unlock(sb);
|
|
+}
|
|
+
|
|
+#if 0 /* reserved */
|
|
+static inline int si_write_trylock(struct super_block *sb, int flags)
|
|
+{
|
|
+ if (au_ftest_lock(flags, FLUSH))
|
|
+ au_nwt_flush(&au_sbi(sb)->si_nowait);
|
|
+ return si_noflush_write_trylock(sb);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static inline void si_write_unlock(struct super_block *sb)
|
|
+{
|
|
+ si_pid_clr(sb);
|
|
+ __si_write_unlock(sb);
|
|
+}
|
|
+
|
|
+#if 0 /* reserved */
|
|
+static inline void si_downgrade_lock(struct super_block *sb)
|
|
+{
|
|
+ __si_downgrade_lock(sb);
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline aufs_bindex_t au_sbbot(struct super_block *sb)
|
|
+{
|
|
+ SiMustAnyLock(sb);
|
|
+ return au_sbi(sb)->si_bbot;
|
|
+}
|
|
+
|
|
+static inline unsigned int au_mntflags(struct super_block *sb)
|
|
+{
|
|
+ SiMustAnyLock(sb);
|
|
+ return au_sbi(sb)->si_mntflags;
|
|
+}
|
|
+
|
|
+static inline unsigned int au_sigen(struct super_block *sb)
|
|
+{
|
|
+ SiMustAnyLock(sb);
|
|
+ return au_sbi(sb)->si_generation;
|
|
+}
|
|
+
|
|
+static inline struct au_branch *au_sbr(struct super_block *sb,
|
|
+ aufs_bindex_t bindex)
|
|
+{
|
|
+ SiMustAnyLock(sb);
|
|
+ return au_sbi(sb)->si_branch[0 + bindex];
|
|
+}
|
|
+
|
|
+static inline loff_t au_xi_maxent(struct super_block *sb)
|
|
+{
|
|
+ SiMustAnyLock(sb);
|
|
+ return au_sbi(sb)->si_ximaxent;
|
|
+}
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_SUPER_H__ */
|
|
diff --git a/fs/aufs/sysaufs.c b/fs/aufs/sysaufs.c
|
|
new file mode 100644
|
|
index 000000000000..f615471231b5
|
|
--- /dev/null
|
|
+++ b/fs/aufs/sysaufs.c
|
|
@@ -0,0 +1,93 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * sysfs interface and lifetime management
|
|
+ * they are necessary regardless sysfs is disabled.
|
|
+ */
|
|
+
|
|
+#include <linux/random.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+unsigned long sysaufs_si_mask;
|
|
+struct kset *sysaufs_kset;
|
|
+
|
|
+#define AuSiAttr(_name) { \
|
|
+ .attr = { .name = __stringify(_name), .mode = 0444 }, \
|
|
+ .show = sysaufs_si_##_name, \
|
|
+}
|
|
+
|
|
+static struct sysaufs_si_attr sysaufs_si_attr_xi_path = AuSiAttr(xi_path);
|
|
+struct attribute *sysaufs_si_attrs[] = {
|
|
+ &sysaufs_si_attr_xi_path.attr,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+static const struct sysfs_ops au_sbi_ops = {
|
|
+ .show = sysaufs_si_show
|
|
+};
|
|
+
|
|
+static struct kobj_type au_sbi_ktype = {
|
|
+ .release = au_si_free,
|
|
+ .sysfs_ops = &au_sbi_ops,
|
|
+ .default_attrs = sysaufs_si_attrs
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int sysaufs_si_init(struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ sbinfo->si_kobj.kset = sysaufs_kset;
|
|
+ /* cf. sysaufs_name() */
|
|
+ err = kobject_init_and_add
|
|
+ (&sbinfo->si_kobj, &au_sbi_ktype, /*&sysaufs_kset->kobj*/NULL,
|
|
+ SysaufsSiNamePrefix "%lx", sysaufs_si_id(sbinfo));
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void sysaufs_fin(void)
|
|
+{
|
|
+ sysfs_remove_group(&sysaufs_kset->kobj, sysaufs_attr_group);
|
|
+ kset_unregister(sysaufs_kset);
|
|
+}
|
|
+
|
|
+int __init sysaufs_init(void)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ do {
|
|
+ get_random_bytes(&sysaufs_si_mask, sizeof(sysaufs_si_mask));
|
|
+ } while (!sysaufs_si_mask);
|
|
+
|
|
+ err = -EINVAL;
|
|
+ sysaufs_kset = kset_create_and_add(AUFS_NAME, NULL, fs_kobj);
|
|
+ if (unlikely(!sysaufs_kset))
|
|
+ goto out;
|
|
+ err = PTR_ERR(sysaufs_kset);
|
|
+ if (IS_ERR(sysaufs_kset))
|
|
+ goto out;
|
|
+ err = sysfs_create_group(&sysaufs_kset->kobj, sysaufs_attr_group);
|
|
+ if (unlikely(err))
|
|
+ kset_unregister(sysaufs_kset);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/sysaufs.h b/fs/aufs/sysaufs.h
|
|
new file mode 100644
|
|
index 000000000000..55c5c9868f3b
|
|
--- /dev/null
|
|
+++ b/fs/aufs/sysaufs.h
|
|
@@ -0,0 +1,102 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * sysfs interface and mount lifetime management
|
|
+ */
|
|
+
|
|
+#ifndef __SYSAUFS_H__
|
|
+#define __SYSAUFS_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/sysfs.h>
|
|
+#include "module.h"
|
|
+
|
|
+struct super_block;
|
|
+struct au_sbinfo;
|
|
+
|
|
+struct sysaufs_si_attr {
|
|
+ struct attribute attr;
|
|
+ int (*show)(struct seq_file *seq, struct super_block *sb);
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* sysaufs.c */
|
|
+extern unsigned long sysaufs_si_mask;
|
|
+extern struct kset *sysaufs_kset;
|
|
+extern struct attribute *sysaufs_si_attrs[];
|
|
+int sysaufs_si_init(struct au_sbinfo *sbinfo);
|
|
+int __init sysaufs_init(void);
|
|
+void sysaufs_fin(void);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* some people doesn't like to show a pointer in kernel */
|
|
+static inline unsigned long sysaufs_si_id(struct au_sbinfo *sbinfo)
|
|
+{
|
|
+ return sysaufs_si_mask ^ (unsigned long)sbinfo;
|
|
+}
|
|
+
|
|
+#define SysaufsSiNamePrefix "si_"
|
|
+#define SysaufsSiNameLen (sizeof(SysaufsSiNamePrefix) + 16)
|
|
+static inline void sysaufs_name(struct au_sbinfo *sbinfo, char *name)
|
|
+{
|
|
+ snprintf(name, SysaufsSiNameLen, SysaufsSiNamePrefix "%lx",
|
|
+ sysaufs_si_id(sbinfo));
|
|
+}
|
|
+
|
|
+struct au_branch;
|
|
+#ifdef CONFIG_SYSFS
|
|
+/* sysfs.c */
|
|
+extern struct attribute_group *sysaufs_attr_group;
|
|
+
|
|
+int sysaufs_si_xi_path(struct seq_file *seq, struct super_block *sb);
|
|
+ssize_t sysaufs_si_show(struct kobject *kobj, struct attribute *attr,
|
|
+ char *buf);
|
|
+long au_brinfo_ioctl(struct file *file, unsigned long arg);
|
|
+#ifdef CONFIG_COMPAT
|
|
+long au_brinfo_compat_ioctl(struct file *file, unsigned long arg);
|
|
+#endif
|
|
+
|
|
+void sysaufs_br_init(struct au_branch *br);
|
|
+void sysaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex);
|
|
+void sysaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex);
|
|
+
|
|
+#define sysaufs_brs_init() do {} while (0)
|
|
+
|
|
+#else
|
|
+#define sysaufs_attr_group NULL
|
|
+
|
|
+AuStubInt0(sysaufs_si_xi_path, struct seq_file *seq, struct super_block *sb)
|
|
+AuStub(ssize_t, sysaufs_si_show, return 0, struct kobject *kobj,
|
|
+ struct attribute *attr, char *buf)
|
|
+AuStubVoid(sysaufs_br_init, struct au_branch *br)
|
|
+AuStubVoid(sysaufs_brs_add, struct super_block *sb, aufs_bindex_t bindex)
|
|
+AuStubVoid(sysaufs_brs_del, struct super_block *sb, aufs_bindex_t bindex)
|
|
+
|
|
+static inline void sysaufs_brs_init(void)
|
|
+{
|
|
+ sysaufs_brs = 0;
|
|
+}
|
|
+
|
|
+#endif /* CONFIG_SYSFS */
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __SYSAUFS_H__ */
|
|
diff --git a/fs/aufs/sysfs.c b/fs/aufs/sysfs.c
|
|
new file mode 100644
|
|
index 000000000000..46638d88ef21
|
|
--- /dev/null
|
|
+++ b/fs/aufs/sysfs.c
|
|
@@ -0,0 +1,374 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * sysfs interface
|
|
+ */
|
|
+
|
|
+#include <linux/compat.h>
|
|
+#include <linux/seq_file.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+#ifdef CONFIG_AUFS_FS_MODULE
|
|
+/* this entry violates the "one line per file" policy of sysfs */
|
|
+static ssize_t config_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ ssize_t err;
|
|
+ static char *conf =
|
|
+/* this file is generated at compiling */
|
|
+#include "conf.str"
|
|
+ ;
|
|
+
|
|
+ err = snprintf(buf, PAGE_SIZE, conf);
|
|
+ if (unlikely(err >= PAGE_SIZE))
|
|
+ err = -EFBIG;
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static struct kobj_attribute au_config_attr = __ATTR_RO(config);
|
|
+#endif
|
|
+
|
|
+static struct attribute *au_attr[] = {
|
|
+#ifdef CONFIG_AUFS_FS_MODULE
|
|
+ &au_config_attr.attr,
|
|
+#endif
|
|
+ NULL, /* need to NULL terminate the list of attributes */
|
|
+};
|
|
+
|
|
+static struct attribute_group sysaufs_attr_group_body = {
|
|
+ .attrs = au_attr
|
|
+};
|
|
+
|
|
+struct attribute_group *sysaufs_attr_group = &sysaufs_attr_group_body;
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int sysaufs_si_xi_path(struct seq_file *seq, struct super_block *sb)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ err = 0;
|
|
+ if (au_opt_test(au_mntflags(sb), XINO)) {
|
|
+ err = au_xino_path(seq, au_sbi(sb)->si_xib);
|
|
+ seq_putc(seq, '\n');
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * the lifetime of branch is independent from the entry under sysfs.
|
|
+ * sysfs handles the lifetime of the entry, and never call ->show() after it is
|
|
+ * unlinked.
|
|
+ */
|
|
+static int sysaufs_si_br(struct seq_file *seq, struct super_block *sb,
|
|
+ aufs_bindex_t bindex, int idx)
|
|
+{
|
|
+ int err;
|
|
+ struct path path;
|
|
+ struct dentry *root;
|
|
+ struct au_branch *br;
|
|
+ au_br_perm_str_t perm;
|
|
+
|
|
+ AuDbg("b%d\n", bindex);
|
|
+
|
|
+ err = 0;
|
|
+ root = sb->s_root;
|
|
+ di_read_lock_parent(root, !AuLock_IR);
|
|
+ br = au_sbr(sb, bindex);
|
|
+
|
|
+ switch (idx) {
|
|
+ case AuBrSysfs_BR:
|
|
+ path.mnt = au_br_mnt(br);
|
|
+ path.dentry = au_h_dptr(root, bindex);
|
|
+ err = au_seq_path(seq, &path);
|
|
+ if (!err) {
|
|
+ au_optstr_br_perm(&perm, br->br_perm);
|
|
+ seq_printf(seq, "=%s\n", perm.a);
|
|
+ }
|
|
+ break;
|
|
+ case AuBrSysfs_BRID:
|
|
+ seq_printf(seq, "%d\n", br->br_id);
|
|
+ break;
|
|
+ }
|
|
+ di_read_unlock(root, !AuLock_IR);
|
|
+ if (unlikely(err || seq_has_overflowed(seq)))
|
|
+ err = -E2BIG;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static struct seq_file *au_seq(char *p, ssize_t len)
|
|
+{
|
|
+ struct seq_file *seq;
|
|
+
|
|
+ seq = kzalloc(sizeof(*seq), GFP_NOFS);
|
|
+ if (seq) {
|
|
+ /* mutex_init(&seq.lock); */
|
|
+ seq->buf = p;
|
|
+ seq->size = len;
|
|
+ return seq; /* success */
|
|
+ }
|
|
+
|
|
+ seq = ERR_PTR(-ENOMEM);
|
|
+ return seq;
|
|
+}
|
|
+
|
|
+#define SysaufsBr_PREFIX "br"
|
|
+#define SysaufsBrid_PREFIX "brid"
|
|
+
|
|
+/* todo: file size may exceed PAGE_SIZE */
|
|
+ssize_t sysaufs_si_show(struct kobject *kobj, struct attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ ssize_t err;
|
|
+ int idx;
|
|
+ long l;
|
|
+ aufs_bindex_t bbot;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct super_block *sb;
|
|
+ struct seq_file *seq;
|
|
+ char *name;
|
|
+ struct attribute **cattr;
|
|
+
|
|
+ sbinfo = container_of(kobj, struct au_sbinfo, si_kobj);
|
|
+ sb = sbinfo->si_sb;
|
|
+
|
|
+ /*
|
|
+ * prevent a race condition between sysfs and aufs.
|
|
+ * for instance, sysfs_file_read() calls sysfs_get_active_two() which
|
|
+ * prohibits maintaining the sysfs entries.
|
|
+ * hew we acquire read lock after sysfs_get_active_two().
|
|
+ * on the other hand, the remount process may maintain the sysfs/aufs
|
|
+ * entries after acquiring write lock.
|
|
+ * it can cause a deadlock.
|
|
+ * simply we gave up processing read here.
|
|
+ */
|
|
+ err = -EBUSY;
|
|
+ if (unlikely(!si_noflush_read_trylock(sb)))
|
|
+ goto out;
|
|
+
|
|
+ seq = au_seq(buf, PAGE_SIZE);
|
|
+ err = PTR_ERR(seq);
|
|
+ if (IS_ERR(seq))
|
|
+ goto out_unlock;
|
|
+
|
|
+ name = (void *)attr->name;
|
|
+ cattr = sysaufs_si_attrs;
|
|
+ while (*cattr) {
|
|
+ if (!strcmp(name, (*cattr)->name)) {
|
|
+ err = container_of(*cattr, struct sysaufs_si_attr, attr)
|
|
+ ->show(seq, sb);
|
|
+ goto out_seq;
|
|
+ }
|
|
+ cattr++;
|
|
+ }
|
|
+
|
|
+ if (!strncmp(name, SysaufsBrid_PREFIX,
|
|
+ sizeof(SysaufsBrid_PREFIX) - 1)) {
|
|
+ idx = AuBrSysfs_BRID;
|
|
+ name += sizeof(SysaufsBrid_PREFIX) - 1;
|
|
+ } else if (!strncmp(name, SysaufsBr_PREFIX,
|
|
+ sizeof(SysaufsBr_PREFIX) - 1)) {
|
|
+ idx = AuBrSysfs_BR;
|
|
+ name += sizeof(SysaufsBr_PREFIX) - 1;
|
|
+ } else
|
|
+ BUG();
|
|
+
|
|
+ err = kstrtol(name, 10, &l);
|
|
+ if (!err) {
|
|
+ bbot = au_sbbot(sb);
|
|
+ if (l <= bbot)
|
|
+ err = sysaufs_si_br(seq, sb, (aufs_bindex_t)l, idx);
|
|
+ else
|
|
+ err = -ENOENT;
|
|
+ }
|
|
+
|
|
+out_seq:
|
|
+ if (!err) {
|
|
+ err = seq->count;
|
|
+ /* sysfs limit */
|
|
+ if (unlikely(err == PAGE_SIZE))
|
|
+ err = -EFBIG;
|
|
+ }
|
|
+ au_kfree_rcu(seq);
|
|
+out_unlock:
|
|
+ si_read_unlock(sb);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_brinfo(struct super_block *sb, union aufs_brinfo __user *arg)
|
|
+{
|
|
+ int err;
|
|
+ int16_t brid;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ size_t sz;
|
|
+ char *buf;
|
|
+ struct seq_file *seq;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ si_read_lock(sb, AuLock_FLUSH);
|
|
+ bbot = au_sbbot(sb);
|
|
+ err = bbot + 1;
|
|
+ if (!arg)
|
|
+ goto out;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ buf = (void *)__get_free_page(GFP_NOFS);
|
|
+ if (unlikely(!buf))
|
|
+ goto out;
|
|
+
|
|
+ seq = au_seq(buf, PAGE_SIZE);
|
|
+ err = PTR_ERR(seq);
|
|
+ if (IS_ERR(seq))
|
|
+ goto out_buf;
|
|
+
|
|
+ sz = sizeof(*arg) - offsetof(union aufs_brinfo, path);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++, arg++) {
|
|
+ /* VERIFY_WRITE */
|
|
+ err = !access_ok(arg, sizeof(*arg));
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+
|
|
+ br = au_sbr(sb, bindex);
|
|
+ brid = br->br_id;
|
|
+ BUILD_BUG_ON(sizeof(brid) != sizeof(arg->id));
|
|
+ err = __put_user(brid, &arg->id);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+
|
|
+ BUILD_BUG_ON(sizeof(br->br_perm) != sizeof(arg->perm));
|
|
+ err = __put_user(br->br_perm, &arg->perm);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+
|
|
+ err = au_seq_path(seq, &br->br_path);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ seq_putc(seq, '\0');
|
|
+ if (!seq_has_overflowed(seq)) {
|
|
+ err = copy_to_user(arg->path, seq->buf, seq->count);
|
|
+ seq->count = 0;
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ } else {
|
|
+ err = -E2BIG;
|
|
+ goto out_seq;
|
|
+ }
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ err = -EFAULT;
|
|
+
|
|
+out_seq:
|
|
+ au_kfree_rcu(seq);
|
|
+out_buf:
|
|
+ free_page((unsigned long)buf);
|
|
+out:
|
|
+ si_read_unlock(sb);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+long au_brinfo_ioctl(struct file *file, unsigned long arg)
|
|
+{
|
|
+ return au_brinfo(file->f_path.dentry->d_sb, (void __user *)arg);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_COMPAT
|
|
+long au_brinfo_compat_ioctl(struct file *file, unsigned long arg)
|
|
+{
|
|
+ return au_brinfo(file->f_path.dentry->d_sb, compat_ptr(arg));
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void sysaufs_br_init(struct au_branch *br)
|
|
+{
|
|
+ int i;
|
|
+ struct au_brsysfs *br_sysfs;
|
|
+ struct attribute *attr;
|
|
+
|
|
+ br_sysfs = br->br_sysfs;
|
|
+ for (i = 0; i < ARRAY_SIZE(br->br_sysfs); i++) {
|
|
+ attr = &br_sysfs->attr;
|
|
+ sysfs_attr_init(attr);
|
|
+ attr->name = br_sysfs->name;
|
|
+ attr->mode = 0444;
|
|
+ br_sysfs++;
|
|
+ }
|
|
+}
|
|
+
|
|
+void sysaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ struct au_branch *br;
|
|
+ struct kobject *kobj;
|
|
+ struct au_brsysfs *br_sysfs;
|
|
+ int i;
|
|
+ aufs_bindex_t bbot;
|
|
+
|
|
+ if (!sysaufs_brs)
|
|
+ return;
|
|
+
|
|
+ kobj = &au_sbi(sb)->si_kobj;
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ br_sysfs = br->br_sysfs;
|
|
+ for (i = 0; i < ARRAY_SIZE(br->br_sysfs); i++) {
|
|
+ sysfs_remove_file(kobj, &br_sysfs->attr);
|
|
+ br_sysfs++;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+void sysaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ int err, i;
|
|
+ aufs_bindex_t bbot;
|
|
+ struct kobject *kobj;
|
|
+ struct au_branch *br;
|
|
+ struct au_brsysfs *br_sysfs;
|
|
+
|
|
+ if (!sysaufs_brs)
|
|
+ return;
|
|
+
|
|
+ kobj = &au_sbi(sb)->si_kobj;
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ br_sysfs = br->br_sysfs;
|
|
+ snprintf(br_sysfs[AuBrSysfs_BR].name, sizeof(br_sysfs->name),
|
|
+ SysaufsBr_PREFIX "%d", bindex);
|
|
+ snprintf(br_sysfs[AuBrSysfs_BRID].name, sizeof(br_sysfs->name),
|
|
+ SysaufsBrid_PREFIX "%d", bindex);
|
|
+ for (i = 0; i < ARRAY_SIZE(br->br_sysfs); i++) {
|
|
+ err = sysfs_create_file(kobj, &br_sysfs->attr);
|
|
+ if (unlikely(err))
|
|
+ pr_warn("failed %s under sysfs(%d)\n",
|
|
+ br_sysfs->name, err);
|
|
+ br_sysfs++;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/fs/aufs/sysrq.c b/fs/aufs/sysrq.c
|
|
new file mode 100644
|
|
index 000000000000..fc8aa4a2839f
|
|
--- /dev/null
|
|
+++ b/fs/aufs/sysrq.c
|
|
@@ -0,0 +1,149 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * magic sysrq handler
|
|
+ */
|
|
+
|
|
+/* #include <linux/sysrq.h> */
|
|
+#include <linux/writeback.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static void sysrq_sb(struct super_block *sb)
|
|
+{
|
|
+ char *plevel;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct file *file;
|
|
+ struct hlist_bl_head *files;
|
|
+ struct hlist_bl_node *pos;
|
|
+ struct au_finfo *finfo;
|
|
+ struct inode *i;
|
|
+
|
|
+ plevel = au_plevel;
|
|
+ au_plevel = KERN_WARNING;
|
|
+
|
|
+ /* since we define pr_fmt, call printk directly */
|
|
+#define pr(str) printk(KERN_WARNING AUFS_NAME ": " str)
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ printk(KERN_WARNING "si=%lx\n", sysaufs_si_id(sbinfo));
|
|
+ pr("superblock\n");
|
|
+ au_dpri_sb(sb);
|
|
+
|
|
+#if 0 /* reserved */
|
|
+ do {
|
|
+ int err, i, j, ndentry;
|
|
+ struct au_dcsub_pages dpages;
|
|
+ struct au_dpage *dpage;
|
|
+
|
|
+ err = au_dpages_init(&dpages, GFP_ATOMIC);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ err = au_dcsub_pages(&dpages, sb->s_root, NULL, NULL);
|
|
+ if (!err)
|
|
+ for (i = 0; i < dpages.ndpage; i++) {
|
|
+ dpage = dpages.dpages + i;
|
|
+ ndentry = dpage->ndentry;
|
|
+ for (j = 0; j < ndentry; j++)
|
|
+ au_dpri_dentry(dpage->dentries[j]);
|
|
+ }
|
|
+ au_dpages_free(&dpages);
|
|
+ } while (0);
|
|
+#endif
|
|
+
|
|
+ pr("isolated inode\n");
|
|
+ spin_lock(&sb->s_inode_list_lock);
|
|
+ list_for_each_entry(i, &sb->s_inodes, i_sb_list) {
|
|
+ spin_lock(&i->i_lock);
|
|
+ if (hlist_empty(&i->i_dentry))
|
|
+ au_dpri_inode(i);
|
|
+ spin_unlock(&i->i_lock);
|
|
+ }
|
|
+ spin_unlock(&sb->s_inode_list_lock);
|
|
+
|
|
+ pr("files\n");
|
|
+ files = &au_sbi(sb)->si_files;
|
|
+ hlist_bl_lock(files);
|
|
+ hlist_bl_for_each_entry(finfo, pos, files, fi_hlist) {
|
|
+ umode_t mode;
|
|
+
|
|
+ file = finfo->fi_file;
|
|
+ mode = file_inode(file)->i_mode;
|
|
+ if (!special_file(mode))
|
|
+ au_dpri_file(file);
|
|
+ }
|
|
+ hlist_bl_unlock(files);
|
|
+ pr("done\n");
|
|
+
|
|
+#undef pr
|
|
+ au_plevel = plevel;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* module parameter */
|
|
+static char *aufs_sysrq_key = "a";
|
|
+module_param_named(sysrq, aufs_sysrq_key, charp, 0444);
|
|
+MODULE_PARM_DESC(sysrq, "MagicSysRq key for " AUFS_NAME);
|
|
+
|
|
+static void au_sysrq(int key __maybe_unused)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct hlist_bl_node *pos;
|
|
+
|
|
+ lockdep_off();
|
|
+ au_sbilist_lock();
|
|
+ hlist_bl_for_each_entry(sbinfo, pos, &au_sbilist, si_list)
|
|
+ sysrq_sb(sbinfo->si_sb);
|
|
+ au_sbilist_unlock();
|
|
+ lockdep_on();
|
|
+}
|
|
+
|
|
+static struct sysrq_key_op au_sysrq_op = {
|
|
+ .handler = au_sysrq,
|
|
+ .help_msg = "Aufs",
|
|
+ .action_msg = "Aufs",
|
|
+ .enable_mask = SYSRQ_ENABLE_DUMP
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int __init au_sysrq_init(void)
|
|
+{
|
|
+ int err;
|
|
+ char key;
|
|
+
|
|
+ err = -1;
|
|
+ key = *aufs_sysrq_key;
|
|
+ if ('a' <= key && key <= 'z')
|
|
+ err = register_sysrq_key(key, &au_sysrq_op);
|
|
+ if (unlikely(err))
|
|
+ pr_err("err %d, sysrq=%c\n", err, key);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_sysrq_fin(void)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = unregister_sysrq_key(*aufs_sysrq_key, &au_sysrq_op);
|
|
+ if (unlikely(err))
|
|
+ pr_err("err %d (ignored)\n", err);
|
|
+}
|
|
diff --git a/fs/aufs/vdir.c b/fs/aufs/vdir.c
|
|
new file mode 100644
|
|
index 000000000000..5ba006b80724
|
|
--- /dev/null
|
|
+++ b/fs/aufs/vdir.c
|
|
@@ -0,0 +1,896 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * virtual or vertical directory
|
|
+ */
|
|
+
|
|
+#include <linux/iversion.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+static unsigned int calc_size(int nlen)
|
|
+{
|
|
+ return ALIGN(sizeof(struct au_vdir_de) + nlen, sizeof(ino_t));
|
|
+}
|
|
+
|
|
+static int set_deblk_end(union au_vdir_deblk_p *p,
|
|
+ union au_vdir_deblk_p *deblk_end)
|
|
+{
|
|
+ if (calc_size(0) <= deblk_end->deblk - p->deblk) {
|
|
+ p->de->de_str.len = 0;
|
|
+ /* smp_mb(); */
|
|
+ return 0;
|
|
+ }
|
|
+ return -1; /* error */
|
|
+}
|
|
+
|
|
+/* returns true or false */
|
|
+static int is_deblk_end(union au_vdir_deblk_p *p,
|
|
+ union au_vdir_deblk_p *deblk_end)
|
|
+{
|
|
+ if (calc_size(0) <= deblk_end->deblk - p->deblk)
|
|
+ return !p->de->de_str.len;
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static unsigned char *last_deblk(struct au_vdir *vdir)
|
|
+{
|
|
+ return vdir->vd_deblk[vdir->vd_nblk - 1];
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* estimate the appropriate size for name hash table */
|
|
+unsigned int au_rdhash_est(loff_t sz)
|
|
+{
|
|
+ unsigned int n;
|
|
+
|
|
+ n = UINT_MAX;
|
|
+ sz >>= 10;
|
|
+ if (sz < n)
|
|
+ n = sz;
|
|
+ if (sz < AUFS_RDHASH_DEF)
|
|
+ n = AUFS_RDHASH_DEF;
|
|
+ /* pr_info("n %u\n", n); */
|
|
+ return n;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * the allocated memory has to be freed by
|
|
+ * au_nhash_wh_free() or au_nhash_de_free().
|
|
+ */
|
|
+int au_nhash_alloc(struct au_nhash *nhash, unsigned int num_hash, gfp_t gfp)
|
|
+{
|
|
+ struct hlist_head *head;
|
|
+ unsigned int u;
|
|
+ size_t sz;
|
|
+
|
|
+ sz = sizeof(*nhash->nh_head) * num_hash;
|
|
+ head = kmalloc(sz, gfp);
|
|
+ if (head) {
|
|
+ nhash->nh_num = num_hash;
|
|
+ nhash->nh_head = head;
|
|
+ for (u = 0; u < num_hash; u++)
|
|
+ INIT_HLIST_HEAD(head++);
|
|
+ return 0; /* success */
|
|
+ }
|
|
+
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+static void nhash_count(struct hlist_head *head)
|
|
+{
|
|
+#if 0 /* debugging */
|
|
+ unsigned long n;
|
|
+ struct hlist_node *pos;
|
|
+
|
|
+ n = 0;
|
|
+ hlist_for_each(pos, head)
|
|
+ n++;
|
|
+ pr_info("%lu\n", n);
|
|
+#endif
|
|
+}
|
|
+
|
|
+static void au_nhash_wh_do_free(struct hlist_head *head)
|
|
+{
|
|
+ struct au_vdir_wh *pos;
|
|
+ struct hlist_node *node;
|
|
+
|
|
+ hlist_for_each_entry_safe(pos, node, head, wh_hash)
|
|
+ au_kfree_rcu(pos);
|
|
+}
|
|
+
|
|
+static void au_nhash_de_do_free(struct hlist_head *head)
|
|
+{
|
|
+ struct au_vdir_dehstr *pos;
|
|
+ struct hlist_node *node;
|
|
+
|
|
+ hlist_for_each_entry_safe(pos, node, head, hash)
|
|
+ au_cache_free_vdir_dehstr(pos);
|
|
+}
|
|
+
|
|
+static void au_nhash_do_free(struct au_nhash *nhash,
|
|
+ void (*free)(struct hlist_head *head))
|
|
+{
|
|
+ unsigned int n;
|
|
+ struct hlist_head *head;
|
|
+
|
|
+ n = nhash->nh_num;
|
|
+ if (!n)
|
|
+ return;
|
|
+
|
|
+ head = nhash->nh_head;
|
|
+ while (n-- > 0) {
|
|
+ nhash_count(head);
|
|
+ free(head++);
|
|
+ }
|
|
+ au_kfree_try_rcu(nhash->nh_head);
|
|
+}
|
|
+
|
|
+void au_nhash_wh_free(struct au_nhash *whlist)
|
|
+{
|
|
+ au_nhash_do_free(whlist, au_nhash_wh_do_free);
|
|
+}
|
|
+
|
|
+static void au_nhash_de_free(struct au_nhash *delist)
|
|
+{
|
|
+ au_nhash_do_free(delist, au_nhash_de_do_free);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_nhash_test_longer_wh(struct au_nhash *whlist, aufs_bindex_t btgt,
|
|
+ int limit)
|
|
+{
|
|
+ int num;
|
|
+ unsigned int u, n;
|
|
+ struct hlist_head *head;
|
|
+ struct au_vdir_wh *pos;
|
|
+
|
|
+ num = 0;
|
|
+ n = whlist->nh_num;
|
|
+ head = whlist->nh_head;
|
|
+ for (u = 0; u < n; u++, head++)
|
|
+ hlist_for_each_entry(pos, head, wh_hash)
|
|
+ if (pos->wh_bindex == btgt && ++num > limit)
|
|
+ return 1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct hlist_head *au_name_hash(struct au_nhash *nhash,
|
|
+ unsigned char *name,
|
|
+ unsigned int len)
|
|
+{
|
|
+ unsigned int v;
|
|
+ /* const unsigned int magic_bit = 12; */
|
|
+
|
|
+ AuDebugOn(!nhash->nh_num || !nhash->nh_head);
|
|
+
|
|
+ v = 0;
|
|
+ if (len > 8)
|
|
+ len = 8;
|
|
+ while (len--)
|
|
+ v += *name++;
|
|
+ /* v = hash_long(v, magic_bit); */
|
|
+ v %= nhash->nh_num;
|
|
+ return nhash->nh_head + v;
|
|
+}
|
|
+
|
|
+static int au_nhash_test_name(struct au_vdir_destr *str, const char *name,
|
|
+ int nlen)
|
|
+{
|
|
+ return str->len == nlen && !memcmp(str->name, name, nlen);
|
|
+}
|
|
+
|
|
+/* returns found or not */
|
|
+int au_nhash_test_known_wh(struct au_nhash *whlist, char *name, int nlen)
|
|
+{
|
|
+ struct hlist_head *head;
|
|
+ struct au_vdir_wh *pos;
|
|
+ struct au_vdir_destr *str;
|
|
+
|
|
+ head = au_name_hash(whlist, name, nlen);
|
|
+ hlist_for_each_entry(pos, head, wh_hash) {
|
|
+ str = &pos->wh_str;
|
|
+ AuDbg("%.*s\n", str->len, str->name);
|
|
+ if (au_nhash_test_name(str, name, nlen))
|
|
+ return 1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* returns found(true) or not */
|
|
+static int test_known(struct au_nhash *delist, char *name, int nlen)
|
|
+{
|
|
+ struct hlist_head *head;
|
|
+ struct au_vdir_dehstr *pos;
|
|
+ struct au_vdir_destr *str;
|
|
+
|
|
+ head = au_name_hash(delist, name, nlen);
|
|
+ hlist_for_each_entry(pos, head, hash) {
|
|
+ str = pos->str;
|
|
+ AuDbg("%.*s\n", str->len, str->name);
|
|
+ if (au_nhash_test_name(str, name, nlen))
|
|
+ return 1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void au_shwh_init_wh(struct au_vdir_wh *wh, ino_t ino,
|
|
+ unsigned char d_type)
|
|
+{
|
|
+#ifdef CONFIG_AUFS_SHWH
|
|
+ wh->wh_ino = ino;
|
|
+ wh->wh_type = d_type;
|
|
+#endif
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_nhash_append_wh(struct au_nhash *whlist, char *name, int nlen, ino_t ino,
|
|
+ unsigned int d_type, aufs_bindex_t bindex,
|
|
+ unsigned char shwh)
|
|
+{
|
|
+ int err;
|
|
+ struct au_vdir_destr *str;
|
|
+ struct au_vdir_wh *wh;
|
|
+
|
|
+ AuDbg("%.*s\n", nlen, name);
|
|
+ AuDebugOn(!whlist->nh_num || !whlist->nh_head);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ wh = kmalloc(sizeof(*wh) + nlen, GFP_NOFS);
|
|
+ if (unlikely(!wh))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ wh->wh_bindex = bindex;
|
|
+ if (shwh)
|
|
+ au_shwh_init_wh(wh, ino, d_type);
|
|
+ str = &wh->wh_str;
|
|
+ str->len = nlen;
|
|
+ memcpy(str->name, name, nlen);
|
|
+ hlist_add_head(&wh->wh_hash, au_name_hash(whlist, name, nlen));
|
|
+ /* smp_mb(); */
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int append_deblk(struct au_vdir *vdir)
|
|
+{
|
|
+ int err;
|
|
+ unsigned long ul;
|
|
+ const unsigned int deblk_sz = vdir->vd_deblk_sz;
|
|
+ union au_vdir_deblk_p p, deblk_end;
|
|
+ unsigned char **o;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ o = au_krealloc(vdir->vd_deblk, sizeof(*o) * (vdir->vd_nblk + 1),
|
|
+ GFP_NOFS, /*may_shrink*/0);
|
|
+ if (unlikely(!o))
|
|
+ goto out;
|
|
+
|
|
+ vdir->vd_deblk = o;
|
|
+ p.deblk = kmalloc(deblk_sz, GFP_NOFS);
|
|
+ if (p.deblk) {
|
|
+ ul = vdir->vd_nblk++;
|
|
+ vdir->vd_deblk[ul] = p.deblk;
|
|
+ vdir->vd_last.ul = ul;
|
|
+ vdir->vd_last.p.deblk = p.deblk;
|
|
+ deblk_end.deblk = p.deblk + deblk_sz;
|
|
+ err = set_deblk_end(&p, &deblk_end);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int append_de(struct au_vdir *vdir, char *name, int nlen, ino_t ino,
|
|
+ unsigned int d_type, struct au_nhash *delist)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int sz;
|
|
+ const unsigned int deblk_sz = vdir->vd_deblk_sz;
|
|
+ union au_vdir_deblk_p p, *room, deblk_end;
|
|
+ struct au_vdir_dehstr *dehstr;
|
|
+
|
|
+ p.deblk = last_deblk(vdir);
|
|
+ deblk_end.deblk = p.deblk + deblk_sz;
|
|
+ room = &vdir->vd_last.p;
|
|
+ AuDebugOn(room->deblk < p.deblk || deblk_end.deblk <= room->deblk
|
|
+ || !is_deblk_end(room, &deblk_end));
|
|
+
|
|
+ sz = calc_size(nlen);
|
|
+ if (unlikely(sz > deblk_end.deblk - room->deblk)) {
|
|
+ err = append_deblk(vdir);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ p.deblk = last_deblk(vdir);
|
|
+ deblk_end.deblk = p.deblk + deblk_sz;
|
|
+ /* smp_mb(); */
|
|
+ AuDebugOn(room->deblk != p.deblk);
|
|
+ }
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ dehstr = au_cache_alloc_vdir_dehstr();
|
|
+ if (unlikely(!dehstr))
|
|
+ goto out;
|
|
+
|
|
+ dehstr->str = &room->de->de_str;
|
|
+ hlist_add_head(&dehstr->hash, au_name_hash(delist, name, nlen));
|
|
+ room->de->de_ino = ino;
|
|
+ room->de->de_type = d_type;
|
|
+ room->de->de_str.len = nlen;
|
|
+ memcpy(room->de->de_str.name, name, nlen);
|
|
+
|
|
+ err = 0;
|
|
+ room->deblk += sz;
|
|
+ if (unlikely(set_deblk_end(room, &deblk_end)))
|
|
+ err = append_deblk(vdir);
|
|
+ /* smp_mb(); */
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void au_vdir_free(struct au_vdir *vdir)
|
|
+{
|
|
+ unsigned char **deblk;
|
|
+
|
|
+ deblk = vdir->vd_deblk;
|
|
+ while (vdir->vd_nblk--)
|
|
+ au_kfree_try_rcu(*deblk++);
|
|
+ au_kfree_try_rcu(vdir->vd_deblk);
|
|
+ au_cache_free_vdir(vdir);
|
|
+}
|
|
+
|
|
+static struct au_vdir *alloc_vdir(struct file *file)
|
|
+{
|
|
+ struct au_vdir *vdir;
|
|
+ struct super_block *sb;
|
|
+ int err;
|
|
+
|
|
+ sb = file->f_path.dentry->d_sb;
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ vdir = au_cache_alloc_vdir();
|
|
+ if (unlikely(!vdir))
|
|
+ goto out;
|
|
+
|
|
+ vdir->vd_deblk = kzalloc(sizeof(*vdir->vd_deblk), GFP_NOFS);
|
|
+ if (unlikely(!vdir->vd_deblk))
|
|
+ goto out_free;
|
|
+
|
|
+ vdir->vd_deblk_sz = au_sbi(sb)->si_rdblk;
|
|
+ if (!vdir->vd_deblk_sz) {
|
|
+ /* estimate the appropriate size for deblk */
|
|
+ vdir->vd_deblk_sz = au_dir_size(file, /*dentry*/NULL);
|
|
+ /* pr_info("vd_deblk_sz %u\n", vdir->vd_deblk_sz); */
|
|
+ }
|
|
+ vdir->vd_nblk = 0;
|
|
+ vdir->vd_version = 0;
|
|
+ vdir->vd_jiffy = 0;
|
|
+ err = append_deblk(vdir);
|
|
+ if (!err)
|
|
+ return vdir; /* success */
|
|
+
|
|
+ au_kfree_try_rcu(vdir->vd_deblk);
|
|
+
|
|
+out_free:
|
|
+ au_cache_free_vdir(vdir);
|
|
+out:
|
|
+ vdir = ERR_PTR(err);
|
|
+ return vdir;
|
|
+}
|
|
+
|
|
+static int reinit_vdir(struct au_vdir *vdir)
|
|
+{
|
|
+ int err;
|
|
+ union au_vdir_deblk_p p, deblk_end;
|
|
+
|
|
+ while (vdir->vd_nblk > 1) {
|
|
+ au_kfree_try_rcu(vdir->vd_deblk[vdir->vd_nblk - 1]);
|
|
+ /* vdir->vd_deblk[vdir->vd_nblk - 1] = NULL; */
|
|
+ vdir->vd_nblk--;
|
|
+ }
|
|
+ p.deblk = vdir->vd_deblk[0];
|
|
+ deblk_end.deblk = p.deblk + vdir->vd_deblk_sz;
|
|
+ err = set_deblk_end(&p, &deblk_end);
|
|
+ /* keep vd_dblk_sz */
|
|
+ vdir->vd_last.ul = 0;
|
|
+ vdir->vd_last.p.deblk = vdir->vd_deblk[0];
|
|
+ vdir->vd_version = 0;
|
|
+ vdir->vd_jiffy = 0;
|
|
+ /* smp_mb(); */
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#define AuFillVdir_CALLED 1
|
|
+#define AuFillVdir_WHABLE (1 << 1)
|
|
+#define AuFillVdir_SHWH (1 << 2)
|
|
+#define au_ftest_fillvdir(flags, name) ((flags) & AuFillVdir_##name)
|
|
+#define au_fset_fillvdir(flags, name) \
|
|
+ do { (flags) |= AuFillVdir_##name; } while (0)
|
|
+#define au_fclr_fillvdir(flags, name) \
|
|
+ do { (flags) &= ~AuFillVdir_##name; } while (0)
|
|
+
|
|
+#ifndef CONFIG_AUFS_SHWH
|
|
+#undef AuFillVdir_SHWH
|
|
+#define AuFillVdir_SHWH 0
|
|
+#endif
|
|
+
|
|
+struct fillvdir_arg {
|
|
+ struct dir_context ctx;
|
|
+ struct file *file;
|
|
+ struct au_vdir *vdir;
|
|
+ struct au_nhash delist;
|
|
+ struct au_nhash whlist;
|
|
+ aufs_bindex_t bindex;
|
|
+ unsigned int flags;
|
|
+ int err;
|
|
+};
|
|
+
|
|
+static int fillvdir(struct dir_context *ctx, const char *__name, int nlen,
|
|
+ loff_t offset __maybe_unused, u64 h_ino,
|
|
+ unsigned int d_type)
|
|
+{
|
|
+ struct fillvdir_arg *arg = container_of(ctx, struct fillvdir_arg, ctx);
|
|
+ char *name = (void *)__name;
|
|
+ struct super_block *sb;
|
|
+ ino_t ino;
|
|
+ const unsigned char shwh = !!au_ftest_fillvdir(arg->flags, SHWH);
|
|
+
|
|
+ arg->err = 0;
|
|
+ sb = arg->file->f_path.dentry->d_sb;
|
|
+ au_fset_fillvdir(arg->flags, CALLED);
|
|
+ /* smp_mb(); */
|
|
+ if (nlen <= AUFS_WH_PFX_LEN
|
|
+ || memcmp(name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) {
|
|
+ if (test_known(&arg->delist, name, nlen)
|
|
+ || au_nhash_test_known_wh(&arg->whlist, name, nlen))
|
|
+ goto out; /* already exists or whiteouted */
|
|
+
|
|
+ arg->err = au_ino(sb, arg->bindex, h_ino, d_type, &ino);
|
|
+ if (!arg->err) {
|
|
+ if (unlikely(nlen > AUFS_MAX_NAMELEN))
|
|
+ d_type = DT_UNKNOWN;
|
|
+ arg->err = append_de(arg->vdir, name, nlen, ino,
|
|
+ d_type, &arg->delist);
|
|
+ }
|
|
+ } else if (au_ftest_fillvdir(arg->flags, WHABLE)) {
|
|
+ name += AUFS_WH_PFX_LEN;
|
|
+ nlen -= AUFS_WH_PFX_LEN;
|
|
+ if (au_nhash_test_known_wh(&arg->whlist, name, nlen))
|
|
+ goto out; /* already whiteouted */
|
|
+
|
|
+ ino = 0; /* just to suppress a warning */
|
|
+ if (shwh)
|
|
+ arg->err = au_wh_ino(sb, arg->bindex, h_ino, d_type,
|
|
+ &ino);
|
|
+ if (!arg->err) {
|
|
+ if (nlen <= AUFS_MAX_NAMELEN + AUFS_WH_PFX_LEN)
|
|
+ d_type = DT_UNKNOWN;
|
|
+ arg->err = au_nhash_append_wh
|
|
+ (&arg->whlist, name, nlen, ino, d_type,
|
|
+ arg->bindex, shwh);
|
|
+ }
|
|
+ }
|
|
+
|
|
+out:
|
|
+ if (!arg->err)
|
|
+ arg->vdir->vd_jiffy = jiffies;
|
|
+ /* smp_mb(); */
|
|
+ AuTraceErr(arg->err);
|
|
+ return arg->err;
|
|
+}
|
|
+
|
|
+static int au_handle_shwh(struct super_block *sb, struct au_vdir *vdir,
|
|
+ struct au_nhash *whlist, struct au_nhash *delist)
|
|
+{
|
|
+#ifdef CONFIG_AUFS_SHWH
|
|
+ int err;
|
|
+ unsigned int nh, u;
|
|
+ struct hlist_head *head;
|
|
+ struct au_vdir_wh *pos;
|
|
+ struct hlist_node *n;
|
|
+ char *p, *o;
|
|
+ struct au_vdir_destr *destr;
|
|
+
|
|
+ AuDebugOn(!au_opt_test(au_mntflags(sb), SHWH));
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ o = p = (void *)__get_free_page(GFP_NOFS);
|
|
+ if (unlikely(!p))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ nh = whlist->nh_num;
|
|
+ memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
|
|
+ p += AUFS_WH_PFX_LEN;
|
|
+ for (u = 0; u < nh; u++) {
|
|
+ head = whlist->nh_head + u;
|
|
+ hlist_for_each_entry_safe(pos, n, head, wh_hash) {
|
|
+ destr = &pos->wh_str;
|
|
+ memcpy(p, destr->name, destr->len);
|
|
+ err = append_de(vdir, o, destr->len + AUFS_WH_PFX_LEN,
|
|
+ pos->wh_ino, pos->wh_type, delist);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ free_page((unsigned long)o);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+static int au_do_read_vdir(struct fillvdir_arg *arg)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int rdhash;
|
|
+ loff_t offset;
|
|
+ aufs_bindex_t bbot, bindex, btop;
|
|
+ unsigned char shwh;
|
|
+ struct file *hf, *file;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ file = arg->file;
|
|
+ sb = file->f_path.dentry->d_sb;
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ rdhash = au_sbi(sb)->si_rdhash;
|
|
+ if (!rdhash)
|
|
+ rdhash = au_rdhash_est(au_dir_size(file, /*dentry*/NULL));
|
|
+ err = au_nhash_alloc(&arg->delist, rdhash, GFP_NOFS);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = au_nhash_alloc(&arg->whlist, rdhash, GFP_NOFS);
|
|
+ if (unlikely(err))
|
|
+ goto out_delist;
|
|
+
|
|
+ err = 0;
|
|
+ arg->flags = 0;
|
|
+ shwh = 0;
|
|
+ if (au_opt_test(au_mntflags(sb), SHWH)) {
|
|
+ shwh = 1;
|
|
+ au_fset_fillvdir(arg->flags, SHWH);
|
|
+ }
|
|
+ btop = au_fbtop(file);
|
|
+ bbot = au_fbbot_dir(file);
|
|
+ for (bindex = btop; !err && bindex <= bbot; bindex++) {
|
|
+ hf = au_hf_dir(file, bindex);
|
|
+ if (!hf)
|
|
+ continue;
|
|
+
|
|
+ offset = vfsub_llseek(hf, 0, SEEK_SET);
|
|
+ err = offset;
|
|
+ if (unlikely(offset))
|
|
+ break;
|
|
+
|
|
+ arg->bindex = bindex;
|
|
+ au_fclr_fillvdir(arg->flags, WHABLE);
|
|
+ if (shwh
|
|
+ || (bindex != bbot
|
|
+ && au_br_whable(au_sbr_perm(sb, bindex))))
|
|
+ au_fset_fillvdir(arg->flags, WHABLE);
|
|
+ do {
|
|
+ arg->err = 0;
|
|
+ au_fclr_fillvdir(arg->flags, CALLED);
|
|
+ /* smp_mb(); */
|
|
+ err = vfsub_iterate_dir(hf, &arg->ctx);
|
|
+ if (err >= 0)
|
|
+ err = arg->err;
|
|
+ } while (!err && au_ftest_fillvdir(arg->flags, CALLED));
|
|
+
|
|
+ /*
|
|
+ * dir_relax() may be good for concurrency, but aufs should not
|
|
+ * use it since it will cause a lockdep problem.
|
|
+ */
|
|
+ }
|
|
+
|
|
+ if (!err && shwh)
|
|
+ err = au_handle_shwh(sb, arg->vdir, &arg->whlist, &arg->delist);
|
|
+
|
|
+ au_nhash_wh_free(&arg->whlist);
|
|
+
|
|
+out_delist:
|
|
+ au_nhash_de_free(&arg->delist);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int read_vdir(struct file *file, int may_read)
|
|
+{
|
|
+ int err;
|
|
+ unsigned long expire;
|
|
+ unsigned char do_read;
|
|
+ struct fillvdir_arg arg = {
|
|
+ .ctx = {
|
|
+ .actor = fillvdir
|
|
+ }
|
|
+ };
|
|
+ struct inode *inode;
|
|
+ struct au_vdir *vdir, *allocated;
|
|
+
|
|
+ err = 0;
|
|
+ inode = file_inode(file);
|
|
+ IMustLock(inode);
|
|
+ IiMustWriteLock(inode);
|
|
+ SiMustAnyLock(inode->i_sb);
|
|
+
|
|
+ allocated = NULL;
|
|
+ do_read = 0;
|
|
+ expire = au_sbi(inode->i_sb)->si_rdcache;
|
|
+ vdir = au_ivdir(inode);
|
|
+ if (!vdir) {
|
|
+ do_read = 1;
|
|
+ vdir = alloc_vdir(file);
|
|
+ err = PTR_ERR(vdir);
|
|
+ if (IS_ERR(vdir))
|
|
+ goto out;
|
|
+ err = 0;
|
|
+ allocated = vdir;
|
|
+ } else if (may_read
|
|
+ && (!inode_eq_iversion(inode, vdir->vd_version)
|
|
+ || time_after(jiffies, vdir->vd_jiffy + expire))) {
|
|
+ do_read = 1;
|
|
+ err = reinit_vdir(vdir);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (!do_read)
|
|
+ return 0; /* success */
|
|
+
|
|
+ arg.file = file;
|
|
+ arg.vdir = vdir;
|
|
+ err = au_do_read_vdir(&arg);
|
|
+ if (!err) {
|
|
+ /* file->f_pos = 0; */ /* todo: ctx->pos? */
|
|
+ vdir->vd_version = inode_query_iversion(inode);
|
|
+ vdir->vd_last.ul = 0;
|
|
+ vdir->vd_last.p.deblk = vdir->vd_deblk[0];
|
|
+ if (allocated)
|
|
+ au_set_ivdir(inode, allocated);
|
|
+ } else if (allocated)
|
|
+ au_vdir_free(allocated);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int copy_vdir(struct au_vdir *tgt, struct au_vdir *src)
|
|
+{
|
|
+ int err, rerr;
|
|
+ unsigned long ul, n;
|
|
+ const unsigned int deblk_sz = src->vd_deblk_sz;
|
|
+
|
|
+ AuDebugOn(tgt->vd_nblk != 1);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ if (tgt->vd_nblk < src->vd_nblk) {
|
|
+ unsigned char **p;
|
|
+
|
|
+ p = au_krealloc(tgt->vd_deblk, sizeof(*p) * src->vd_nblk,
|
|
+ GFP_NOFS, /*may_shrink*/0);
|
|
+ if (unlikely(!p))
|
|
+ goto out;
|
|
+ tgt->vd_deblk = p;
|
|
+ }
|
|
+
|
|
+ if (tgt->vd_deblk_sz != deblk_sz) {
|
|
+ unsigned char *p;
|
|
+
|
|
+ tgt->vd_deblk_sz = deblk_sz;
|
|
+ p = au_krealloc(tgt->vd_deblk[0], deblk_sz, GFP_NOFS,
|
|
+ /*may_shrink*/1);
|
|
+ if (unlikely(!p))
|
|
+ goto out;
|
|
+ tgt->vd_deblk[0] = p;
|
|
+ }
|
|
+ memcpy(tgt->vd_deblk[0], src->vd_deblk[0], deblk_sz);
|
|
+ tgt->vd_version = src->vd_version;
|
|
+ tgt->vd_jiffy = src->vd_jiffy;
|
|
+
|
|
+ n = src->vd_nblk;
|
|
+ for (ul = 1; ul < n; ul++) {
|
|
+ tgt->vd_deblk[ul] = kmemdup(src->vd_deblk[ul], deblk_sz,
|
|
+ GFP_NOFS);
|
|
+ if (unlikely(!tgt->vd_deblk[ul]))
|
|
+ goto out;
|
|
+ tgt->vd_nblk++;
|
|
+ }
|
|
+ tgt->vd_nblk = n;
|
|
+ tgt->vd_last.ul = tgt->vd_last.ul;
|
|
+ tgt->vd_last.p.deblk = tgt->vd_deblk[tgt->vd_last.ul];
|
|
+ tgt->vd_last.p.deblk += src->vd_last.p.deblk
|
|
+ - src->vd_deblk[src->vd_last.ul];
|
|
+ /* smp_mb(); */
|
|
+ return 0; /* success */
|
|
+
|
|
+out:
|
|
+ rerr = reinit_vdir(tgt);
|
|
+ BUG_ON(rerr);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_vdir_init(struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *inode;
|
|
+ struct au_vdir *vdir_cache, *allocated;
|
|
+
|
|
+ /* test file->f_pos here instead of ctx->pos */
|
|
+ err = read_vdir(file, !file->f_pos);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ allocated = NULL;
|
|
+ vdir_cache = au_fvdir_cache(file);
|
|
+ if (!vdir_cache) {
|
|
+ vdir_cache = alloc_vdir(file);
|
|
+ err = PTR_ERR(vdir_cache);
|
|
+ if (IS_ERR(vdir_cache))
|
|
+ goto out;
|
|
+ allocated = vdir_cache;
|
|
+ } else if (!file->f_pos && vdir_cache->vd_version != file->f_version) {
|
|
+ /* test file->f_pos here instead of ctx->pos */
|
|
+ err = reinit_vdir(vdir_cache);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ } else
|
|
+ return 0; /* success */
|
|
+
|
|
+ inode = file_inode(file);
|
|
+ err = copy_vdir(vdir_cache, au_ivdir(inode));
|
|
+ if (!err) {
|
|
+ file->f_version = inode_query_iversion(inode);
|
|
+ if (allocated)
|
|
+ au_set_fvdir_cache(file, allocated);
|
|
+ } else if (allocated)
|
|
+ au_vdir_free(allocated);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static loff_t calc_offset(struct au_vdir *vdir)
|
|
+{
|
|
+ loff_t offset;
|
|
+ union au_vdir_deblk_p p;
|
|
+
|
|
+ p.deblk = vdir->vd_deblk[vdir->vd_last.ul];
|
|
+ offset = vdir->vd_last.p.deblk - p.deblk;
|
|
+ offset += vdir->vd_deblk_sz * vdir->vd_last.ul;
|
|
+ return offset;
|
|
+}
|
|
+
|
|
+/* returns true or false */
|
|
+static int seek_vdir(struct file *file, struct dir_context *ctx)
|
|
+{
|
|
+ int valid;
|
|
+ unsigned int deblk_sz;
|
|
+ unsigned long ul, n;
|
|
+ loff_t offset;
|
|
+ union au_vdir_deblk_p p, deblk_end;
|
|
+ struct au_vdir *vdir_cache;
|
|
+
|
|
+ valid = 1;
|
|
+ vdir_cache = au_fvdir_cache(file);
|
|
+ offset = calc_offset(vdir_cache);
|
|
+ AuDbg("offset %lld\n", offset);
|
|
+ if (ctx->pos == offset)
|
|
+ goto out;
|
|
+
|
|
+ vdir_cache->vd_last.ul = 0;
|
|
+ vdir_cache->vd_last.p.deblk = vdir_cache->vd_deblk[0];
|
|
+ if (!ctx->pos)
|
|
+ goto out;
|
|
+
|
|
+ valid = 0;
|
|
+ deblk_sz = vdir_cache->vd_deblk_sz;
|
|
+ ul = div64_u64(ctx->pos, deblk_sz);
|
|
+ AuDbg("ul %lu\n", ul);
|
|
+ if (ul >= vdir_cache->vd_nblk)
|
|
+ goto out;
|
|
+
|
|
+ n = vdir_cache->vd_nblk;
|
|
+ for (; ul < n; ul++) {
|
|
+ p.deblk = vdir_cache->vd_deblk[ul];
|
|
+ deblk_end.deblk = p.deblk + deblk_sz;
|
|
+ offset = ul;
|
|
+ offset *= deblk_sz;
|
|
+ while (!is_deblk_end(&p, &deblk_end) && offset < ctx->pos) {
|
|
+ unsigned int l;
|
|
+
|
|
+ l = calc_size(p.de->de_str.len);
|
|
+ offset += l;
|
|
+ p.deblk += l;
|
|
+ }
|
|
+ if (!is_deblk_end(&p, &deblk_end)) {
|
|
+ valid = 1;
|
|
+ vdir_cache->vd_last.ul = ul;
|
|
+ vdir_cache->vd_last.p = p;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+out:
|
|
+ /* smp_mb(); */
|
|
+ if (!valid)
|
|
+ AuDbg("valid %d\n", !valid);
|
|
+ return valid;
|
|
+}
|
|
+
|
|
+int au_vdir_fill_de(struct file *file, struct dir_context *ctx)
|
|
+{
|
|
+ unsigned int l, deblk_sz;
|
|
+ union au_vdir_deblk_p deblk_end;
|
|
+ struct au_vdir *vdir_cache;
|
|
+ struct au_vdir_de *de;
|
|
+
|
|
+ if (!seek_vdir(file, ctx))
|
|
+ return 0;
|
|
+
|
|
+ vdir_cache = au_fvdir_cache(file);
|
|
+ deblk_sz = vdir_cache->vd_deblk_sz;
|
|
+ while (1) {
|
|
+ deblk_end.deblk = vdir_cache->vd_deblk[vdir_cache->vd_last.ul];
|
|
+ deblk_end.deblk += deblk_sz;
|
|
+ while (!is_deblk_end(&vdir_cache->vd_last.p, &deblk_end)) {
|
|
+ de = vdir_cache->vd_last.p.de;
|
|
+ AuDbg("%.*s, off%lld, i%lu, dt%d\n",
|
|
+ de->de_str.len, de->de_str.name, ctx->pos,
|
|
+ (unsigned long)de->de_ino, de->de_type);
|
|
+ if (unlikely(!dir_emit(ctx, de->de_str.name,
|
|
+ de->de_str.len, de->de_ino,
|
|
+ de->de_type))) {
|
|
+ /* todo: ignore the error caused by udba? */
|
|
+ /* return err; */
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ l = calc_size(de->de_str.len);
|
|
+ vdir_cache->vd_last.p.deblk += l;
|
|
+ ctx->pos += l;
|
|
+ }
|
|
+ if (vdir_cache->vd_last.ul < vdir_cache->vd_nblk - 1) {
|
|
+ vdir_cache->vd_last.ul++;
|
|
+ vdir_cache->vd_last.p.deblk
|
|
+ = vdir_cache->vd_deblk[vdir_cache->vd_last.ul];
|
|
+ ctx->pos = deblk_sz * vdir_cache->vd_last.ul;
|
|
+ continue;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* smp_mb(); */
|
|
+ return 0;
|
|
+}
|
|
diff --git a/fs/aufs/vfsub.c b/fs/aufs/vfsub.c
|
|
new file mode 100644
|
|
index 000000000000..a5e10c5c004f
|
|
--- /dev/null
|
|
+++ b/fs/aufs/vfsub.c
|
|
@@ -0,0 +1,895 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * sub-routines for VFS
|
|
+ */
|
|
+
|
|
+#include <linux/mnt_namespace.h>
|
|
+#include <linux/namei.h>
|
|
+#include <linux/nsproxy.h>
|
|
+#include <linux/security.h>
|
|
+#include <linux/splice.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+#ifdef CONFIG_AUFS_BR_FUSE
|
|
+int vfsub_test_mntns(struct vfsmount *mnt, struct super_block *h_sb)
|
|
+{
|
|
+ if (!au_test_fuse(h_sb) || !au_userns)
|
|
+ return 0;
|
|
+
|
|
+ return is_current_mnt_ns(mnt) ? 0 : -EACCES;
|
|
+}
|
|
+#endif
|
|
+
|
|
+int vfsub_sync_filesystem(struct super_block *h_sb, int wait)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ lockdep_off();
|
|
+ down_read(&h_sb->s_umount);
|
|
+ err = __sync_filesystem(h_sb, wait);
|
|
+ up_read(&h_sb->s_umount);
|
|
+ lockdep_on();
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int vfsub_update_h_iattr(struct path *h_path, int *did)
|
|
+{
|
|
+ int err;
|
|
+ struct kstat st;
|
|
+ struct super_block *h_sb;
|
|
+
|
|
+ /* for remote fs, leave work for its getattr or d_revalidate */
|
|
+ /* for bad i_attr fs, handle them in aufs_getattr() */
|
|
+ /* still some fs may acquire i_mutex. we need to skip them */
|
|
+ err = 0;
|
|
+ if (!did)
|
|
+ did = &err;
|
|
+ h_sb = h_path->dentry->d_sb;
|
|
+ *did = (!au_test_fs_remote(h_sb) && au_test_fs_refresh_iattr(h_sb));
|
|
+ if (*did)
|
|
+ err = vfsub_getattr(h_path, &st);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct file *vfsub_dentry_open(struct path *path, int flags)
|
|
+{
|
|
+ return dentry_open(path, flags /* | __FMODE_NONOTIFY */,
|
|
+ current_cred());
|
|
+}
|
|
+
|
|
+struct file *vfsub_filp_open(const char *path, int oflags, int mode)
|
|
+{
|
|
+ struct file *file;
|
|
+
|
|
+ lockdep_off();
|
|
+ file = filp_open(path,
|
|
+ oflags /* | __FMODE_NONOTIFY */,
|
|
+ mode);
|
|
+ lockdep_on();
|
|
+ if (IS_ERR(file))
|
|
+ goto out;
|
|
+ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
|
|
+
|
|
+out:
|
|
+ return file;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Ideally this function should call VFS:do_last() in order to keep all its
|
|
+ * checkings. But it is very hard for aufs to regenerate several VFS internal
|
|
+ * structure such as nameidata. This is a second (or third) best approach.
|
|
+ * cf. linux/fs/namei.c:do_last(), lookup_open() and atomic_open().
|
|
+ */
|
|
+int vfsub_atomic_open(struct inode *dir, struct dentry *dentry,
|
|
+ struct vfsub_aopen_args *args)
|
|
+{
|
|
+ int err;
|
|
+ struct au_branch *br = args->br;
|
|
+ struct file *file = args->file;
|
|
+ /* copied from linux/fs/namei.c:atomic_open() */
|
|
+ struct dentry *const DENTRY_NOT_SET = (void *)-1UL;
|
|
+
|
|
+ IMustLock(dir);
|
|
+ AuDebugOn(!dir->i_op->atomic_open);
|
|
+
|
|
+ err = au_br_test_oflag(args->open_flag, br);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ au_lcnt_inc(&br->br_nfiles);
|
|
+ file->f_path.dentry = DENTRY_NOT_SET;
|
|
+ file->f_path.mnt = au_br_mnt(br);
|
|
+ AuDbg("%ps\n", dir->i_op->atomic_open);
|
|
+ err = dir->i_op->atomic_open(dir, dentry, file, args->open_flag,
|
|
+ args->create_mode);
|
|
+ if (unlikely(err < 0)) {
|
|
+ au_lcnt_dec(&br->br_nfiles);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* temporary workaround for nfsv4 branch */
|
|
+ if (au_test_nfs(dir->i_sb))
|
|
+ nfs_mark_for_revalidate(dir);
|
|
+
|
|
+ if (file->f_mode & FMODE_CREATED)
|
|
+ fsnotify_create(dir, dentry);
|
|
+ if (!(file->f_mode & FMODE_OPENED)) {
|
|
+ au_lcnt_dec(&br->br_nfiles);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* todo: call VFS:may_open() here */
|
|
+ /* todo: ima_file_check() too? */
|
|
+ if (!err && (args->open_flag & __FMODE_EXEC))
|
|
+ err = deny_write_access(file);
|
|
+ if (!err)
|
|
+ fsnotify_open(file);
|
|
+ else
|
|
+ au_lcnt_dec(&br->br_nfiles);
|
|
+ /* note that the file is created and still opened */
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_kern_path(const char *name, unsigned int flags, struct path *path)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = kern_path(name, flags, path);
|
|
+ if (!err && d_is_positive(path->dentry))
|
|
+ vfsub_update_h_iattr(path, /*did*/NULL); /*ignore*/
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct dentry *vfsub_lookup_one_len_unlocked(const char *name,
|
|
+ struct dentry *parent, int len)
|
|
+{
|
|
+ struct path path = {
|
|
+ .mnt = NULL
|
|
+ };
|
|
+
|
|
+ path.dentry = lookup_one_len_unlocked(name, parent, len);
|
|
+ if (IS_ERR(path.dentry))
|
|
+ goto out;
|
|
+ if (d_is_positive(path.dentry))
|
|
+ vfsub_update_h_iattr(&path, /*did*/NULL); /*ignore*/
|
|
+
|
|
+out:
|
|
+ AuTraceErrPtr(path.dentry);
|
|
+ return path.dentry;
|
|
+}
|
|
+
|
|
+struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent,
|
|
+ int len)
|
|
+{
|
|
+ struct path path = {
|
|
+ .mnt = NULL
|
|
+ };
|
|
+
|
|
+ /* VFS checks it too, but by WARN_ON_ONCE() */
|
|
+ IMustLock(d_inode(parent));
|
|
+
|
|
+ path.dentry = lookup_one_len(name, parent, len);
|
|
+ if (IS_ERR(path.dentry))
|
|
+ goto out;
|
|
+ if (d_is_positive(path.dentry))
|
|
+ vfsub_update_h_iattr(&path, /*did*/NULL); /*ignore*/
|
|
+
|
|
+out:
|
|
+ AuTraceErrPtr(path.dentry);
|
|
+ return path.dentry;
|
|
+}
|
|
+
|
|
+void vfsub_call_lkup_one(void *args)
|
|
+{
|
|
+ struct vfsub_lkup_one_args *a = args;
|
|
+ *a->errp = vfsub_lkup_one(a->name, a->parent);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct dentry *vfsub_lock_rename(struct dentry *d1, struct au_hinode *hdir1,
|
|
+ struct dentry *d2, struct au_hinode *hdir2)
|
|
+{
|
|
+ struct dentry *d;
|
|
+
|
|
+ lockdep_off();
|
|
+ d = lock_rename(d1, d2);
|
|
+ lockdep_on();
|
|
+ au_hn_suspend(hdir1);
|
|
+ if (hdir1 != hdir2)
|
|
+ au_hn_suspend(hdir2);
|
|
+
|
|
+ return d;
|
|
+}
|
|
+
|
|
+void vfsub_unlock_rename(struct dentry *d1, struct au_hinode *hdir1,
|
|
+ struct dentry *d2, struct au_hinode *hdir2)
|
|
+{
|
|
+ au_hn_resume(hdir1);
|
|
+ if (hdir1 != hdir2)
|
|
+ au_hn_resume(hdir2);
|
|
+ lockdep_off();
|
|
+ unlock_rename(d1, d2);
|
|
+ lockdep_on();
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int vfsub_create(struct inode *dir, struct path *path, int mode, bool want_excl)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ d = path->dentry;
|
|
+ path->dentry = d->d_parent;
|
|
+ err = security_path_mknod(path, d, mode, 0);
|
|
+ path->dentry = d;
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_create(dir, path->dentry, mode, want_excl);
|
|
+ lockdep_on();
|
|
+ if (!err) {
|
|
+ struct path tmp = *path;
|
|
+ int did;
|
|
+
|
|
+ vfsub_update_h_iattr(&tmp, &did);
|
|
+ if (did) {
|
|
+ tmp.dentry = path->dentry->d_parent;
|
|
+ vfsub_update_h_iattr(&tmp, /*did*/NULL);
|
|
+ }
|
|
+ /*ignore*/
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_symlink(struct inode *dir, struct path *path, const char *symname)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ d = path->dentry;
|
|
+ path->dentry = d->d_parent;
|
|
+ err = security_path_symlink(path, d, symname);
|
|
+ path->dentry = d;
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_symlink(dir, path->dentry, symname);
|
|
+ lockdep_on();
|
|
+ if (!err) {
|
|
+ struct path tmp = *path;
|
|
+ int did;
|
|
+
|
|
+ vfsub_update_h_iattr(&tmp, &did);
|
|
+ if (did) {
|
|
+ tmp.dentry = path->dentry->d_parent;
|
|
+ vfsub_update_h_iattr(&tmp, /*did*/NULL);
|
|
+ }
|
|
+ /*ignore*/
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_mknod(struct inode *dir, struct path *path, int mode, dev_t dev)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ d = path->dentry;
|
|
+ path->dentry = d->d_parent;
|
|
+ err = security_path_mknod(path, d, mode, new_encode_dev(dev));
|
|
+ path->dentry = d;
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_mknod(dir, path->dentry, mode, dev);
|
|
+ lockdep_on();
|
|
+ if (!err) {
|
|
+ struct path tmp = *path;
|
|
+ int did;
|
|
+
|
|
+ vfsub_update_h_iattr(&tmp, &did);
|
|
+ if (did) {
|
|
+ tmp.dentry = path->dentry->d_parent;
|
|
+ vfsub_update_h_iattr(&tmp, /*did*/NULL);
|
|
+ }
|
|
+ /*ignore*/
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_test_nlink(struct inode *inode)
|
|
+{
|
|
+ const unsigned int link_max = UINT_MAX >> 1; /* rough margin */
|
|
+
|
|
+ if (!au_test_fs_no_limit_nlink(inode->i_sb)
|
|
+ || inode->i_nlink < link_max)
|
|
+ return 0;
|
|
+ return -EMLINK;
|
|
+}
|
|
+
|
|
+int vfsub_link(struct dentry *src_dentry, struct inode *dir, struct path *path,
|
|
+ struct inode **delegated_inode)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ err = au_test_nlink(d_inode(src_dentry));
|
|
+ if (unlikely(err))
|
|
+ return err;
|
|
+
|
|
+ /* we don't call may_linkat() */
|
|
+ d = path->dentry;
|
|
+ path->dentry = d->d_parent;
|
|
+ err = security_path_link(src_dentry, path, d);
|
|
+ path->dentry = d;
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_link(src_dentry, dir, path->dentry, delegated_inode);
|
|
+ lockdep_on();
|
|
+ if (!err) {
|
|
+ struct path tmp = *path;
|
|
+ int did;
|
|
+
|
|
+ /* fuse has different memory inode for the same inumber */
|
|
+ vfsub_update_h_iattr(&tmp, &did);
|
|
+ if (did) {
|
|
+ tmp.dentry = path->dentry->d_parent;
|
|
+ vfsub_update_h_iattr(&tmp, /*did*/NULL);
|
|
+ tmp.dentry = src_dentry;
|
|
+ vfsub_update_h_iattr(&tmp, /*did*/NULL);
|
|
+ }
|
|
+ /*ignore*/
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry,
|
|
+ struct inode *dir, struct path *path,
|
|
+ struct inode **delegated_inode, unsigned int flags)
|
|
+{
|
|
+ int err;
|
|
+ struct path tmp = {
|
|
+ .mnt = path->mnt
|
|
+ };
|
|
+ struct dentry *d;
|
|
+
|
|
+ IMustLock(dir);
|
|
+ IMustLock(src_dir);
|
|
+
|
|
+ d = path->dentry;
|
|
+ path->dentry = d->d_parent;
|
|
+ tmp.dentry = src_dentry->d_parent;
|
|
+ err = security_path_rename(&tmp, src_dentry, path, d, /*flags*/0);
|
|
+ path->dentry = d;
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_rename(src_dir, src_dentry, dir, path->dentry,
|
|
+ delegated_inode, flags);
|
|
+ lockdep_on();
|
|
+ if (!err) {
|
|
+ int did;
|
|
+
|
|
+ tmp.dentry = d->d_parent;
|
|
+ vfsub_update_h_iattr(&tmp, &did);
|
|
+ if (did) {
|
|
+ tmp.dentry = src_dentry;
|
|
+ vfsub_update_h_iattr(&tmp, /*did*/NULL);
|
|
+ tmp.dentry = src_dentry->d_parent;
|
|
+ vfsub_update_h_iattr(&tmp, /*did*/NULL);
|
|
+ }
|
|
+ /*ignore*/
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_mkdir(struct inode *dir, struct path *path, int mode)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ d = path->dentry;
|
|
+ path->dentry = d->d_parent;
|
|
+ err = security_path_mkdir(path, d, mode);
|
|
+ path->dentry = d;
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_mkdir(dir, path->dentry, mode);
|
|
+ lockdep_on();
|
|
+ if (!err) {
|
|
+ struct path tmp = *path;
|
|
+ int did;
|
|
+
|
|
+ vfsub_update_h_iattr(&tmp, &did);
|
|
+ if (did) {
|
|
+ tmp.dentry = path->dentry->d_parent;
|
|
+ vfsub_update_h_iattr(&tmp, /*did*/NULL);
|
|
+ }
|
|
+ /*ignore*/
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_rmdir(struct inode *dir, struct path *path)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *d;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ d = path->dentry;
|
|
+ path->dentry = d->d_parent;
|
|
+ err = security_path_rmdir(path, d);
|
|
+ path->dentry = d;
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_rmdir(dir, path->dentry);
|
|
+ lockdep_on();
|
|
+ if (!err) {
|
|
+ struct path tmp = {
|
|
+ .dentry = path->dentry->d_parent,
|
|
+ .mnt = path->mnt
|
|
+ };
|
|
+
|
|
+ vfsub_update_h_iattr(&tmp, /*did*/NULL); /*ignore*/
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* todo: support mmap_sem? */
|
|
+ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ ssize_t err;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_read(file, ubuf, count, ppos);
|
|
+ lockdep_on();
|
|
+ if (err >= 0)
|
|
+ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* todo: kernel_read()? */
|
|
+ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ ssize_t err;
|
|
+ mm_segment_t oldfs;
|
|
+ union {
|
|
+ void *k;
|
|
+ char __user *u;
|
|
+ } buf;
|
|
+
|
|
+ buf.k = kbuf;
|
|
+ oldfs = get_fs();
|
|
+ set_fs(KERNEL_DS);
|
|
+ err = vfsub_read_u(file, buf.u, count, ppos);
|
|
+ set_fs(oldfs);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ ssize_t err;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_write(file, ubuf, count, ppos);
|
|
+ lockdep_on();
|
|
+ if (err >= 0)
|
|
+ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
|
|
+ return err;
|
|
+}
|
|
+
|
|
+ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count, loff_t *ppos)
|
|
+{
|
|
+ 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);
|
|
+ err = vfsub_write_u(file, buf.u, count, ppos);
|
|
+ set_fs(oldfs);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_flush(struct file *file, fl_owner_t id)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ if (file->f_op->flush) {
|
|
+ if (!au_test_nfs(file->f_path.dentry->d_sb))
|
|
+ err = file->f_op->flush(file, id);
|
|
+ else {
|
|
+ lockdep_off();
|
|
+ err = file->f_op->flush(file, id);
|
|
+ lockdep_on();
|
|
+ }
|
|
+ if (!err)
|
|
+ vfsub_update_h_iattr(&file->f_path, /*did*/NULL);
|
|
+ /*ignore*/
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_iterate_dir(struct file *file, struct dir_context *ctx)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ AuDbg("%pD, ctx{%ps, %llu}\n", file, ctx->actor, ctx->pos);
|
|
+
|
|
+ lockdep_off();
|
|
+ err = iterate_dir(file, ctx);
|
|
+ lockdep_on();
|
|
+ if (err >= 0)
|
|
+ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+long vfsub_splice_to(struct file *in, loff_t *ppos,
|
|
+ struct pipe_inode_info *pipe, size_t len,
|
|
+ unsigned int flags)
|
|
+{
|
|
+ long err;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = do_splice_to(in, ppos, pipe, len, flags);
|
|
+ lockdep_on();
|
|
+ file_accessed(in);
|
|
+ if (err >= 0)
|
|
+ vfsub_update_h_iattr(&in->f_path, /*did*/NULL); /*ignore*/
|
|
+ return err;
|
|
+}
|
|
+
|
|
+long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out,
|
|
+ loff_t *ppos, size_t len, unsigned int flags)
|
|
+{
|
|
+ long err;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = do_splice_from(pipe, out, ppos, len, flags);
|
|
+ lockdep_on();
|
|
+ if (err >= 0)
|
|
+ vfsub_update_h_iattr(&out->f_path, /*did*/NULL); /*ignore*/
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_fsync(struct file *file, struct path *path, int datasync)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ /* file can be NULL */
|
|
+ lockdep_off();
|
|
+ err = vfs_fsync(file, datasync);
|
|
+ lockdep_on();
|
|
+ if (!err) {
|
|
+ if (!path) {
|
|
+ AuDebugOn(!file);
|
|
+ path = &file->f_path;
|
|
+ }
|
|
+ vfsub_update_h_iattr(path, /*did*/NULL); /*ignore*/
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* cf. open.c:do_sys_truncate() and do_sys_ftruncate() */
|
|
+int vfsub_trunc(struct path *h_path, loff_t length, unsigned int attr,
|
|
+ struct file *h_file)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *h_inode;
|
|
+ struct super_block *h_sb;
|
|
+
|
|
+ if (!h_file) {
|
|
+ err = vfsub_truncate(h_path, length);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ h_inode = d_inode(h_path->dentry);
|
|
+ h_sb = h_inode->i_sb;
|
|
+ lockdep_off();
|
|
+ sb_start_write(h_sb);
|
|
+ lockdep_on();
|
|
+ err = locks_verify_truncate(h_inode, h_file, length);
|
|
+ if (!err)
|
|
+ err = security_path_truncate(h_path);
|
|
+ if (!err) {
|
|
+ lockdep_off();
|
|
+ err = do_truncate(h_path->dentry, length, attr, h_file);
|
|
+ lockdep_on();
|
|
+ }
|
|
+ lockdep_off();
|
|
+ sb_end_write(h_sb);
|
|
+ lockdep_on();
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_vfsub_mkdir_args {
|
|
+ int *errp;
|
|
+ struct inode *dir;
|
|
+ struct path *path;
|
|
+ int mode;
|
|
+};
|
|
+
|
|
+static void au_call_vfsub_mkdir(void *args)
|
|
+{
|
|
+ struct au_vfsub_mkdir_args *a = args;
|
|
+ *a->errp = vfsub_mkdir(a->dir, a->path, a->mode);
|
|
+}
|
|
+
|
|
+int vfsub_sio_mkdir(struct inode *dir, struct path *path, int mode)
|
|
+{
|
|
+ int err, do_sio, wkq_err;
|
|
+
|
|
+ do_sio = au_test_h_perm_sio(dir, MAY_EXEC | MAY_WRITE);
|
|
+ if (!do_sio) {
|
|
+ lockdep_off();
|
|
+ err = vfsub_mkdir(dir, path, mode);
|
|
+ lockdep_on();
|
|
+ } else {
|
|
+ struct au_vfsub_mkdir_args args = {
|
|
+ .errp = &err,
|
|
+ .dir = dir,
|
|
+ .path = path,
|
|
+ .mode = mode
|
|
+ };
|
|
+ wkq_err = au_wkq_wait(au_call_vfsub_mkdir, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct au_vfsub_rmdir_args {
|
|
+ int *errp;
|
|
+ struct inode *dir;
|
|
+ struct path *path;
|
|
+};
|
|
+
|
|
+static void au_call_vfsub_rmdir(void *args)
|
|
+{
|
|
+ struct au_vfsub_rmdir_args *a = args;
|
|
+ *a->errp = vfsub_rmdir(a->dir, a->path);
|
|
+}
|
|
+
|
|
+int vfsub_sio_rmdir(struct inode *dir, struct path *path)
|
|
+{
|
|
+ int err, do_sio, wkq_err;
|
|
+
|
|
+ do_sio = au_test_h_perm_sio(dir, MAY_EXEC | MAY_WRITE);
|
|
+ if (!do_sio) {
|
|
+ lockdep_off();
|
|
+ err = vfsub_rmdir(dir, path);
|
|
+ lockdep_on();
|
|
+ } else {
|
|
+ struct au_vfsub_rmdir_args args = {
|
|
+ .errp = &err,
|
|
+ .dir = dir,
|
|
+ .path = path
|
|
+ };
|
|
+ wkq_err = au_wkq_wait(au_call_vfsub_rmdir, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct notify_change_args {
|
|
+ int *errp;
|
|
+ struct path *path;
|
|
+ struct iattr *ia;
|
|
+ struct inode **delegated_inode;
|
|
+};
|
|
+
|
|
+static void call_notify_change(void *args)
|
|
+{
|
|
+ struct notify_change_args *a = args;
|
|
+ struct inode *h_inode;
|
|
+
|
|
+ h_inode = d_inode(a->path->dentry);
|
|
+ IMustLock(h_inode);
|
|
+
|
|
+ *a->errp = -EPERM;
|
|
+ if (!IS_IMMUTABLE(h_inode) && !IS_APPEND(h_inode)) {
|
|
+ lockdep_off();
|
|
+ *a->errp = notify_change(a->path->dentry, a->ia,
|
|
+ a->delegated_inode);
|
|
+ lockdep_on();
|
|
+ if (!*a->errp)
|
|
+ vfsub_update_h_iattr(a->path, /*did*/NULL); /*ignore*/
|
|
+ }
|
|
+ AuTraceErr(*a->errp);
|
|
+}
|
|
+
|
|
+int vfsub_notify_change(struct path *path, struct iattr *ia,
|
|
+ struct inode **delegated_inode)
|
|
+{
|
|
+ int err;
|
|
+ struct notify_change_args args = {
|
|
+ .errp = &err,
|
|
+ .path = path,
|
|
+ .ia = ia,
|
|
+ .delegated_inode = delegated_inode
|
|
+ };
|
|
+
|
|
+ call_notify_change(&args);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_sio_notify_change(struct path *path, struct iattr *ia,
|
|
+ struct inode **delegated_inode)
|
|
+{
|
|
+ int err, wkq_err;
|
|
+ struct notify_change_args args = {
|
|
+ .errp = &err,
|
|
+ .path = path,
|
|
+ .ia = ia,
|
|
+ .delegated_inode = delegated_inode
|
|
+ };
|
|
+
|
|
+ wkq_err = au_wkq_wait(call_notify_change, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct unlink_args {
|
|
+ int *errp;
|
|
+ struct inode *dir;
|
|
+ struct path *path;
|
|
+ struct inode **delegated_inode;
|
|
+};
|
|
+
|
|
+static void call_unlink(void *args)
|
|
+{
|
|
+ struct unlink_args *a = args;
|
|
+ struct dentry *d = a->path->dentry;
|
|
+ struct inode *h_inode;
|
|
+ const int stop_sillyrename = (au_test_nfs(d->d_sb)
|
|
+ && au_dcount(d) == 1);
|
|
+
|
|
+ IMustLock(a->dir);
|
|
+
|
|
+ a->path->dentry = d->d_parent;
|
|
+ *a->errp = security_path_unlink(a->path, d);
|
|
+ a->path->dentry = d;
|
|
+ if (unlikely(*a->errp))
|
|
+ return;
|
|
+
|
|
+ if (!stop_sillyrename)
|
|
+ dget(d);
|
|
+ h_inode = NULL;
|
|
+ if (d_is_positive(d)) {
|
|
+ h_inode = d_inode(d);
|
|
+ ihold(h_inode);
|
|
+ }
|
|
+
|
|
+ lockdep_off();
|
|
+ *a->errp = vfs_unlink(a->dir, d, a->delegated_inode);
|
|
+ lockdep_on();
|
|
+ if (!*a->errp) {
|
|
+ struct path tmp = {
|
|
+ .dentry = d->d_parent,
|
|
+ .mnt = a->path->mnt
|
|
+ };
|
|
+ vfsub_update_h_iattr(&tmp, /*did*/NULL); /*ignore*/
|
|
+ }
|
|
+
|
|
+ if (!stop_sillyrename)
|
|
+ dput(d);
|
|
+ if (h_inode)
|
|
+ iput(h_inode);
|
|
+
|
|
+ AuTraceErr(*a->errp);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * @dir: must be locked.
|
|
+ * @dentry: target dentry.
|
|
+ */
|
|
+int vfsub_unlink(struct inode *dir, struct path *path,
|
|
+ struct inode **delegated_inode, int force)
|
|
+{
|
|
+ int err;
|
|
+ struct unlink_args args = {
|
|
+ .errp = &err,
|
|
+ .dir = dir,
|
|
+ .path = path,
|
|
+ .delegated_inode = delegated_inode
|
|
+ };
|
|
+
|
|
+ if (!force)
|
|
+ call_unlink(&args);
|
|
+ else {
|
|
+ int wkq_err;
|
|
+
|
|
+ wkq_err = au_wkq_wait(call_unlink, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/vfsub.h b/fs/aufs/vfsub.h
|
|
new file mode 100644
|
|
index 000000000000..705f033ba2c3
|
|
--- /dev/null
|
|
+++ b/fs/aufs/vfsub.h
|
|
@@ -0,0 +1,354 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * sub-routines for VFS
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_VFSUB_H__
|
|
+#define __AUFS_VFSUB_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/fs.h>
|
|
+#include <linux/mount.h>
|
|
+#include <linux/posix_acl.h>
|
|
+#include <linux/xattr.h>
|
|
+#include "debug.h"
|
|
+
|
|
+/* copied from linux/fs/internal.h */
|
|
+/* todo: BAD approach!! */
|
|
+extern void __mnt_drop_write(struct vfsmount *);
|
|
+extern struct file *alloc_empty_file(int, const struct cred *);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* lock subclass for lower inode */
|
|
+/* default MAX_LOCKDEP_SUBCLASSES(8) is not enough */
|
|
+/* reduce? gave up. */
|
|
+enum {
|
|
+ AuLsc_I_Begin = I_MUTEX_PARENT2, /* 5 */
|
|
+ AuLsc_I_PARENT, /* lower inode, parent first */
|
|
+ AuLsc_I_PARENT2, /* copyup dirs */
|
|
+ AuLsc_I_PARENT3, /* copyup wh */
|
|
+ AuLsc_I_CHILD,
|
|
+ AuLsc_I_CHILD2,
|
|
+ AuLsc_I_End
|
|
+};
|
|
+
|
|
+/* to debug easier, do not make them inlined functions */
|
|
+#define MtxMustLock(mtx) AuDebugOn(!mutex_is_locked(mtx))
|
|
+#define IMustLock(i) AuDebugOn(!inode_is_locked(i))
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline void vfsub_drop_nlink(struct inode *inode)
|
|
+{
|
|
+ AuDebugOn(!inode->i_nlink);
|
|
+ drop_nlink(inode);
|
|
+}
|
|
+
|
|
+static inline void vfsub_dead_dir(struct inode *inode)
|
|
+{
|
|
+ AuDebugOn(!S_ISDIR(inode->i_mode));
|
|
+ inode->i_flags |= S_DEAD;
|
|
+ clear_nlink(inode);
|
|
+}
|
|
+
|
|
+static inline int vfsub_native_ro(struct inode *inode)
|
|
+{
|
|
+ return sb_rdonly(inode->i_sb)
|
|
+ || IS_RDONLY(inode)
|
|
+ /* || IS_APPEND(inode) */
|
|
+ || IS_IMMUTABLE(inode);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_AUFS_BR_FUSE
|
|
+int vfsub_test_mntns(struct vfsmount *mnt, struct super_block *h_sb);
|
|
+#else
|
|
+AuStubInt0(vfsub_test_mntns, struct vfsmount *mnt, struct super_block *h_sb);
|
|
+#endif
|
|
+
|
|
+int vfsub_sync_filesystem(struct super_block *h_sb, int wait);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int vfsub_update_h_iattr(struct path *h_path, int *did);
|
|
+struct file *vfsub_dentry_open(struct path *path, int flags);
|
|
+struct file *vfsub_filp_open(const char *path, int oflags, int mode);
|
|
+struct au_branch;
|
|
+struct vfsub_aopen_args {
|
|
+ struct file *file;
|
|
+ unsigned int open_flag;
|
|
+ umode_t create_mode;
|
|
+ struct au_branch *br;
|
|
+};
|
|
+int vfsub_atomic_open(struct inode *dir, struct dentry *dentry,
|
|
+ struct vfsub_aopen_args *args);
|
|
+int vfsub_kern_path(const char *name, unsigned int flags, struct path *path);
|
|
+
|
|
+struct dentry *vfsub_lookup_one_len_unlocked(const char *name,
|
|
+ struct dentry *parent, int len);
|
|
+struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent,
|
|
+ int len);
|
|
+
|
|
+struct vfsub_lkup_one_args {
|
|
+ struct dentry **errp;
|
|
+ struct qstr *name;
|
|
+ struct dentry *parent;
|
|
+};
|
|
+
|
|
+static inline struct dentry *vfsub_lkup_one(struct qstr *name,
|
|
+ struct dentry *parent)
|
|
+{
|
|
+ return vfsub_lookup_one_len(name->name, parent, name->len);
|
|
+}
|
|
+
|
|
+void vfsub_call_lkup_one(void *args);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline int vfsub_mnt_want_write(struct vfsmount *mnt)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = mnt_want_write(mnt);
|
|
+ lockdep_on();
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static inline void vfsub_mnt_drop_write(struct vfsmount *mnt)
|
|
+{
|
|
+ lockdep_off();
|
|
+ mnt_drop_write(mnt);
|
|
+ lockdep_on();
|
|
+}
|
|
+
|
|
+#if 0 /* reserved */
|
|
+static inline void vfsub_mnt_drop_write_file(struct file *file)
|
|
+{
|
|
+ lockdep_off();
|
|
+ mnt_drop_write_file(file);
|
|
+ lockdep_on();
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_hinode;
|
|
+struct dentry *vfsub_lock_rename(struct dentry *d1, struct au_hinode *hdir1,
|
|
+ struct dentry *d2, struct au_hinode *hdir2);
|
|
+void vfsub_unlock_rename(struct dentry *d1, struct au_hinode *hdir1,
|
|
+ struct dentry *d2, struct au_hinode *hdir2);
|
|
+
|
|
+int vfsub_create(struct inode *dir, struct path *path, int mode,
|
|
+ bool want_excl);
|
|
+int vfsub_symlink(struct inode *dir, struct path *path,
|
|
+ const char *symname);
|
|
+int vfsub_mknod(struct inode *dir, struct path *path, int mode, dev_t dev);
|
|
+int vfsub_link(struct dentry *src_dentry, struct inode *dir,
|
|
+ struct path *path, struct inode **delegated_inode);
|
|
+int vfsub_rename(struct inode *src_hdir, struct dentry *src_dentry,
|
|
+ struct inode *hdir, struct path *path,
|
|
+ struct inode **delegated_inode, unsigned int flags);
|
|
+int vfsub_mkdir(struct inode *dir, struct path *path, int mode);
|
|
+int vfsub_rmdir(struct inode *dir, struct path *path);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count,
|
|
+ loff_t *ppos);
|
|
+ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count,
|
|
+ loff_t *ppos);
|
|
+ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count,
|
|
+ loff_t *ppos);
|
|
+ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count,
|
|
+ loff_t *ppos);
|
|
+int vfsub_flush(struct file *file, fl_owner_t id);
|
|
+int vfsub_iterate_dir(struct file *file, struct dir_context *ctx);
|
|
+
|
|
+static inline loff_t vfsub_f_size_read(struct file *file)
|
|
+{
|
|
+ return i_size_read(file_inode(file));
|
|
+}
|
|
+
|
|
+static inline unsigned int vfsub_file_flags(struct file *file)
|
|
+{
|
|
+ unsigned int flags;
|
|
+
|
|
+ spin_lock(&file->f_lock);
|
|
+ flags = file->f_flags;
|
|
+ spin_unlock(&file->f_lock);
|
|
+
|
|
+ return flags;
|
|
+}
|
|
+
|
|
+static inline int vfsub_file_execed(struct file *file)
|
|
+{
|
|
+ /* todo: direct access f_flags */
|
|
+ return !!(vfsub_file_flags(file) & __FMODE_EXEC);
|
|
+}
|
|
+
|
|
+#if 0 /* reserved */
|
|
+static inline void vfsub_file_accessed(struct file *h_file)
|
|
+{
|
|
+ file_accessed(h_file);
|
|
+ vfsub_update_h_iattr(&h_file->f_path, /*did*/NULL); /*ignore*/
|
|
+}
|
|
+#endif
|
|
+
|
|
+#if 0 /* reserved */
|
|
+static inline void vfsub_touch_atime(struct vfsmount *h_mnt,
|
|
+ struct dentry *h_dentry)
|
|
+{
|
|
+ struct path h_path = {
|
|
+ .dentry = h_dentry,
|
|
+ .mnt = h_mnt
|
|
+ };
|
|
+ touch_atime(&h_path);
|
|
+ vfsub_update_h_iattr(&h_path, /*did*/NULL); /*ignore*/
|
|
+}
|
|
+#endif
|
|
+
|
|
+static inline int vfsub_update_time(struct inode *h_inode,
|
|
+ struct timespec64 *ts, int flags)
|
|
+{
|
|
+ return update_time(h_inode, ts, flags);
|
|
+ /* no vfsub_update_h_iattr() since we don't have struct path */
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_FS_POSIX_ACL
|
|
+static inline int vfsub_acl_chmod(struct inode *h_inode, umode_t h_mode)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = posix_acl_chmod(h_inode, h_mode);
|
|
+ if (err == -EOPNOTSUPP)
|
|
+ err = 0;
|
|
+ return err;
|
|
+}
|
|
+#else
|
|
+AuStubInt0(vfsub_acl_chmod, struct inode *h_inode, umode_t h_mode);
|
|
+#endif
|
|
+
|
|
+long vfsub_splice_to(struct file *in, loff_t *ppos,
|
|
+ struct pipe_inode_info *pipe, size_t len,
|
|
+ unsigned int flags);
|
|
+long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out,
|
|
+ loff_t *ppos, size_t len, unsigned int flags);
|
|
+
|
|
+static inline long vfsub_truncate(struct path *path, loff_t length)
|
|
+{
|
|
+ long err;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_truncate(path, length);
|
|
+ lockdep_on();
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int vfsub_trunc(struct path *h_path, loff_t length, unsigned int attr,
|
|
+ struct file *h_file);
|
|
+int vfsub_fsync(struct file *file, struct path *path, int datasync);
|
|
+
|
|
+/*
|
|
+ * re-use branch fs's ioctl(FICLONE) while aufs itself doesn't support such
|
|
+ * ioctl.
|
|
+ */
|
|
+static inline loff_t vfsub_clone_file_range(struct file *src, struct file *dst,
|
|
+ loff_t len)
|
|
+{
|
|
+ loff_t err;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_clone_file_range(src, 0, dst, 0, len, /*remap_flags*/0);
|
|
+ lockdep_on();
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* copy_file_range(2) is a systemcall */
|
|
+static inline ssize_t vfsub_copy_file_range(struct file *src, loff_t src_pos,
|
|
+ struct file *dst, loff_t dst_pos,
|
|
+ size_t len, unsigned int flags)
|
|
+{
|
|
+ ssize_t ssz;
|
|
+
|
|
+ lockdep_off();
|
|
+ ssz = vfs_copy_file_range(src, src_pos, dst, dst_pos, len, flags);
|
|
+ lockdep_on();
|
|
+
|
|
+ return ssz;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline loff_t vfsub_llseek(struct file *file, loff_t offset, int origin)
|
|
+{
|
|
+ loff_t err;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_llseek(file, offset, origin);
|
|
+ lockdep_on();
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int vfsub_sio_mkdir(struct inode *dir, struct path *path, int mode);
|
|
+int vfsub_sio_rmdir(struct inode *dir, struct path *path);
|
|
+int vfsub_sio_notify_change(struct path *path, struct iattr *ia,
|
|
+ struct inode **delegated_inode);
|
|
+int vfsub_notify_change(struct path *path, struct iattr *ia,
|
|
+ struct inode **delegated_inode);
|
|
+int vfsub_unlink(struct inode *dir, struct path *path,
|
|
+ struct inode **delegated_inode, int force);
|
|
+
|
|
+static inline int vfsub_getattr(const struct path *path, struct kstat *st)
|
|
+{
|
|
+ return vfs_getattr(path, st, STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline int vfsub_setxattr(struct dentry *dentry, const char *name,
|
|
+ const void *value, size_t size, int flags)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_setxattr(dentry, name, value, size, flags);
|
|
+ lockdep_on();
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static inline int vfsub_removexattr(struct dentry *dentry, const char *name)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ lockdep_off();
|
|
+ err = vfs_removexattr(dentry, name);
|
|
+ lockdep_on();
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_VFSUB_H__ */
|
|
diff --git a/fs/aufs/wbr_policy.c b/fs/aufs/wbr_policy.c
|
|
new file mode 100644
|
|
index 000000000000..a7cc1109f69d
|
|
--- /dev/null
|
|
+++ b/fs/aufs/wbr_policy.c
|
|
@@ -0,0 +1,830 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * policies for selecting one among multiple writable branches
|
|
+ */
|
|
+
|
|
+#include <linux/statfs.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/* subset of cpup_attr() */
|
|
+static noinline_for_stack
|
|
+int au_cpdown_attr(struct path *h_path, struct dentry *h_src)
|
|
+{
|
|
+ int err, sbits;
|
|
+ struct iattr ia;
|
|
+ struct inode *h_isrc;
|
|
+
|
|
+ h_isrc = d_inode(h_src);
|
|
+ ia.ia_valid = ATTR_FORCE | ATTR_MODE | ATTR_UID | ATTR_GID;
|
|
+ ia.ia_mode = h_isrc->i_mode;
|
|
+ ia.ia_uid = h_isrc->i_uid;
|
|
+ ia.ia_gid = h_isrc->i_gid;
|
|
+ sbits = !!(ia.ia_mode & (S_ISUID | S_ISGID));
|
|
+ au_cpup_attr_flags(d_inode(h_path->dentry), h_isrc->i_flags);
|
|
+ /* no delegation since it is just created */
|
|
+ err = vfsub_sio_notify_change(h_path, &ia, /*delegated*/NULL);
|
|
+
|
|
+ /* is this nfs only? */
|
|
+ if (!err && sbits && au_test_nfs(h_path->dentry->d_sb)) {
|
|
+ ia.ia_valid = ATTR_FORCE | ATTR_MODE;
|
|
+ ia.ia_mode = h_isrc->i_mode;
|
|
+ err = vfsub_sio_notify_change(h_path, &ia, /*delegated*/NULL);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+#define AuCpdown_PARENT_OPQ 1
|
|
+#define AuCpdown_WHED (1 << 1)
|
|
+#define AuCpdown_MADE_DIR (1 << 2)
|
|
+#define AuCpdown_DIROPQ (1 << 3)
|
|
+#define au_ftest_cpdown(flags, name) ((flags) & AuCpdown_##name)
|
|
+#define au_fset_cpdown(flags, name) \
|
|
+ do { (flags) |= AuCpdown_##name; } while (0)
|
|
+#define au_fclr_cpdown(flags, name) \
|
|
+ do { (flags) &= ~AuCpdown_##name; } while (0)
|
|
+
|
|
+static int au_cpdown_dir_opq(struct dentry *dentry, aufs_bindex_t bdst,
|
|
+ unsigned int *flags)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *opq_dentry;
|
|
+
|
|
+ opq_dentry = au_diropq_create(dentry, bdst);
|
|
+ err = PTR_ERR(opq_dentry);
|
|
+ if (IS_ERR(opq_dentry))
|
|
+ goto out;
|
|
+ dput(opq_dentry);
|
|
+ au_fset_cpdown(*flags, DIROPQ);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_cpdown_dir_wh(struct dentry *dentry, struct dentry *h_parent,
|
|
+ struct inode *dir, aufs_bindex_t bdst)
|
|
+{
|
|
+ int err;
|
|
+ struct path h_path;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ br = au_sbr(dentry->d_sb, bdst);
|
|
+ h_path.dentry = au_wh_lkup(h_parent, &dentry->d_name, br);
|
|
+ err = PTR_ERR(h_path.dentry);
|
|
+ if (IS_ERR(h_path.dentry))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ if (d_is_positive(h_path.dentry)) {
|
|
+ h_path.mnt = au_br_mnt(br);
|
|
+ err = au_wh_unlink_dentry(au_h_iptr(dir, bdst), &h_path,
|
|
+ dentry);
|
|
+ }
|
|
+ dput(h_path.dentry);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst,
|
|
+ struct au_pin *pin,
|
|
+ struct dentry *h_parent, void *arg)
|
|
+{
|
|
+ int err, rerr;
|
|
+ aufs_bindex_t bopq, btop;
|
|
+ struct path h_path;
|
|
+ struct dentry *parent;
|
|
+ struct inode *h_dir, *h_inode, *inode, *dir;
|
|
+ unsigned int *flags = arg;
|
|
+
|
|
+ btop = au_dbtop(dentry);
|
|
+ /* dentry is di-locked */
|
|
+ parent = dget_parent(dentry);
|
|
+ dir = d_inode(parent);
|
|
+ h_dir = d_inode(h_parent);
|
|
+ AuDebugOn(h_dir != au_h_iptr(dir, bdst));
|
|
+ IMustLock(h_dir);
|
|
+
|
|
+ err = au_lkup_neg(dentry, bdst, /*wh*/0);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out;
|
|
+ h_path.dentry = au_h_dptr(dentry, bdst);
|
|
+ h_path.mnt = au_sbr_mnt(dentry->d_sb, bdst);
|
|
+ err = vfsub_sio_mkdir(au_h_iptr(dir, bdst), &h_path, 0755);
|
|
+ if (unlikely(err))
|
|
+ goto out_put;
|
|
+ au_fset_cpdown(*flags, MADE_DIR);
|
|
+
|
|
+ bopq = au_dbdiropq(dentry);
|
|
+ au_fclr_cpdown(*flags, WHED);
|
|
+ au_fclr_cpdown(*flags, DIROPQ);
|
|
+ if (au_dbwh(dentry) == bdst)
|
|
+ au_fset_cpdown(*flags, WHED);
|
|
+ if (!au_ftest_cpdown(*flags, PARENT_OPQ) && bopq <= bdst)
|
|
+ au_fset_cpdown(*flags, PARENT_OPQ);
|
|
+ h_inode = d_inode(h_path.dentry);
|
|
+ inode_lock_nested(h_inode, AuLsc_I_CHILD);
|
|
+ if (au_ftest_cpdown(*flags, WHED)) {
|
|
+ err = au_cpdown_dir_opq(dentry, bdst, flags);
|
|
+ if (unlikely(err)) {
|
|
+ inode_unlock(h_inode);
|
|
+ goto out_dir;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = au_cpdown_attr(&h_path, au_h_dptr(dentry, btop));
|
|
+ inode_unlock(h_inode);
|
|
+ if (unlikely(err))
|
|
+ goto out_opq;
|
|
+
|
|
+ if (au_ftest_cpdown(*flags, WHED)) {
|
|
+ err = au_cpdown_dir_wh(dentry, h_parent, dir, bdst);
|
|
+ if (unlikely(err))
|
|
+ goto out_opq;
|
|
+ }
|
|
+
|
|
+ inode = d_inode(dentry);
|
|
+ if (au_ibbot(inode) < bdst)
|
|
+ au_set_ibbot(inode, bdst);
|
|
+ au_set_h_iptr(inode, bdst, au_igrab(h_inode),
|
|
+ au_hi_flags(inode, /*isdir*/1));
|
|
+ au_fhsm_wrote(dentry->d_sb, bdst, /*force*/0);
|
|
+ goto out; /* success */
|
|
+
|
|
+ /* revert */
|
|
+out_opq:
|
|
+ if (au_ftest_cpdown(*flags, DIROPQ)) {
|
|
+ inode_lock_nested(h_inode, AuLsc_I_CHILD);
|
|
+ rerr = au_diropq_remove(dentry, bdst);
|
|
+ inode_unlock(h_inode);
|
|
+ if (unlikely(rerr)) {
|
|
+ AuIOErr("failed removing diropq for %pd b%d (%d)\n",
|
|
+ dentry, bdst, rerr);
|
|
+ err = -EIO;
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+out_dir:
|
|
+ if (au_ftest_cpdown(*flags, MADE_DIR)) {
|
|
+ rerr = vfsub_sio_rmdir(au_h_iptr(dir, bdst), &h_path);
|
|
+ if (unlikely(rerr)) {
|
|
+ AuIOErr("failed removing %pd b%d (%d)\n",
|
|
+ dentry, bdst, rerr);
|
|
+ err = -EIO;
|
|
+ }
|
|
+ }
|
|
+out_put:
|
|
+ au_set_h_dptr(dentry, bdst, NULL);
|
|
+ if (au_dbbot(dentry) == bdst)
|
|
+ au_update_dbbot(dentry);
|
|
+out:
|
|
+ dput(parent);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int flags;
|
|
+
|
|
+ flags = 0;
|
|
+ err = au_cp_dirs(dentry, bdst, au_cpdown_dir, &flags);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* policies for create */
|
|
+
|
|
+int au_wbr_nonopq(struct dentry *dentry, aufs_bindex_t bindex)
|
|
+{
|
|
+ int err, i, j, ndentry;
|
|
+ aufs_bindex_t bopq;
|
|
+ struct au_dcsub_pages dpages;
|
|
+ struct au_dpage *dpage;
|
|
+ struct dentry **dentries, *parent, *d;
|
|
+
|
|
+ err = au_dpages_init(&dpages, GFP_NOFS);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ parent = dget_parent(dentry);
|
|
+ err = au_dcsub_pages_rev_aufs(&dpages, parent, /*do_include*/0);
|
|
+ if (unlikely(err))
|
|
+ goto out_free;
|
|
+
|
|
+ err = bindex;
|
|
+ for (i = 0; i < dpages.ndpage; i++) {
|
|
+ dpage = dpages.dpages + i;
|
|
+ dentries = dpage->dentries;
|
|
+ ndentry = dpage->ndentry;
|
|
+ for (j = 0; j < ndentry; j++) {
|
|
+ d = dentries[j];
|
|
+ di_read_lock_parent2(d, !AuLock_IR);
|
|
+ bopq = au_dbdiropq(d);
|
|
+ di_read_unlock(d, !AuLock_IR);
|
|
+ if (bopq >= 0 && bopq < err)
|
|
+ err = bopq;
|
|
+ }
|
|
+ }
|
|
+
|
|
+out_free:
|
|
+ dput(parent);
|
|
+ au_dpages_free(&dpages);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_wbr_bu(struct super_block *sb, aufs_bindex_t bindex)
|
|
+{
|
|
+ for (; bindex >= 0; bindex--)
|
|
+ if (!au_br_rdonly(au_sbr(sb, bindex)))
|
|
+ return bindex;
|
|
+ return -EROFS;
|
|
+}
|
|
+
|
|
+/* top down parent */
|
|
+static int au_wbr_create_tdp(struct dentry *dentry,
|
|
+ unsigned int flags __maybe_unused)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t btop, bindex;
|
|
+ struct super_block *sb;
|
|
+ struct dentry *parent, *h_parent;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ btop = au_dbtop(dentry);
|
|
+ err = btop;
|
|
+ if (!au_br_rdonly(au_sbr(sb, btop)))
|
|
+ goto out;
|
|
+
|
|
+ err = -EROFS;
|
|
+ parent = dget_parent(dentry);
|
|
+ for (bindex = au_dbtop(parent); bindex < btop; bindex++) {
|
|
+ h_parent = au_h_dptr(parent, bindex);
|
|
+ if (!h_parent || d_is_negative(h_parent))
|
|
+ continue;
|
|
+
|
|
+ if (!au_br_rdonly(au_sbr(sb, bindex))) {
|
|
+ err = bindex;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ dput(parent);
|
|
+
|
|
+ /* bottom up here */
|
|
+ if (unlikely(err < 0)) {
|
|
+ err = au_wbr_bu(sb, btop - 1);
|
|
+ if (err >= 0)
|
|
+ err = au_wbr_nonopq(dentry, err);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ AuDbg("b%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* an exception for the policy other than tdp */
|
|
+static int au_wbr_create_exp(struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bwh, bdiropq;
|
|
+ struct dentry *parent;
|
|
+
|
|
+ err = -1;
|
|
+ bwh = au_dbwh(dentry);
|
|
+ parent = dget_parent(dentry);
|
|
+ bdiropq = au_dbdiropq(parent);
|
|
+ if (bwh >= 0) {
|
|
+ if (bdiropq >= 0)
|
|
+ err = min(bdiropq, bwh);
|
|
+ else
|
|
+ err = bwh;
|
|
+ AuDbg("%d\n", err);
|
|
+ } else if (bdiropq >= 0) {
|
|
+ err = bdiropq;
|
|
+ AuDbg("%d\n", err);
|
|
+ }
|
|
+ dput(parent);
|
|
+
|
|
+ if (err >= 0)
|
|
+ err = au_wbr_nonopq(dentry, err);
|
|
+
|
|
+ if (err >= 0 && au_br_rdonly(au_sbr(dentry->d_sb, err)))
|
|
+ err = -1;
|
|
+
|
|
+ AuDbg("%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* round robin */
|
|
+static int au_wbr_create_init_rr(struct super_block *sb)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = au_wbr_bu(sb, au_sbbot(sb));
|
|
+ atomic_set(&au_sbi(sb)->si_wbr_rr_next, -err); /* less important */
|
|
+ /* smp_mb(); */
|
|
+
|
|
+ AuDbg("b%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_wbr_create_rr(struct dentry *dentry, unsigned int flags)
|
|
+{
|
|
+ int err, nbr;
|
|
+ unsigned int u;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct super_block *sb;
|
|
+ atomic_t *next;
|
|
+
|
|
+ err = au_wbr_create_exp(dentry);
|
|
+ if (err >= 0)
|
|
+ goto out;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ next = &au_sbi(sb)->si_wbr_rr_next;
|
|
+ bbot = au_sbbot(sb);
|
|
+ nbr = bbot + 1;
|
|
+ for (bindex = 0; bindex <= bbot; bindex++) {
|
|
+ if (!au_ftest_wbr(flags, DIR)) {
|
|
+ err = atomic_dec_return(next) + 1;
|
|
+ /* modulo for 0 is meaningless */
|
|
+ if (unlikely(!err))
|
|
+ err = atomic_dec_return(next) + 1;
|
|
+ } else
|
|
+ err = atomic_read(next);
|
|
+ AuDbg("%d\n", err);
|
|
+ u = err;
|
|
+ err = u % nbr;
|
|
+ AuDbg("%d\n", err);
|
|
+ if (!au_br_rdonly(au_sbr(sb, err)))
|
|
+ break;
|
|
+ err = -EROFS;
|
|
+ }
|
|
+
|
|
+ if (err >= 0)
|
|
+ err = au_wbr_nonopq(dentry, err);
|
|
+
|
|
+out:
|
|
+ AuDbg("%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* most free space */
|
|
+static void au_mfs(struct dentry *dentry, struct dentry *parent)
|
|
+{
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ struct au_wbr_mfs *mfs;
|
|
+ struct dentry *h_parent;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ int err;
|
|
+ unsigned long long b, bavail;
|
|
+ struct path h_path;
|
|
+ /* reduce the stack usage */
|
|
+ struct kstatfs *st;
|
|
+
|
|
+ st = kmalloc(sizeof(*st), GFP_NOFS);
|
|
+ if (unlikely(!st)) {
|
|
+ AuWarn1("failed updating mfs(%d), ignored\n", -ENOMEM);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ bavail = 0;
|
|
+ sb = dentry->d_sb;
|
|
+ mfs = &au_sbi(sb)->si_wbr_mfs;
|
|
+ MtxMustLock(&mfs->mfs_lock);
|
|
+ mfs->mfs_bindex = -EROFS;
|
|
+ mfs->mfsrr_bytes = 0;
|
|
+ if (!parent) {
|
|
+ bindex = 0;
|
|
+ bbot = au_sbbot(sb);
|
|
+ } else {
|
|
+ bindex = au_dbtop(parent);
|
|
+ bbot = au_dbtaildir(parent);
|
|
+ }
|
|
+
|
|
+ for (; bindex <= bbot; bindex++) {
|
|
+ if (parent) {
|
|
+ h_parent = au_h_dptr(parent, bindex);
|
|
+ if (!h_parent || d_is_negative(h_parent))
|
|
+ continue;
|
|
+ }
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (au_br_rdonly(br))
|
|
+ continue;
|
|
+
|
|
+ /* sb->s_root for NFS is unreliable */
|
|
+ h_path.mnt = au_br_mnt(br);
|
|
+ h_path.dentry = h_path.mnt->mnt_root;
|
|
+ err = vfs_statfs(&h_path, st);
|
|
+ if (unlikely(err)) {
|
|
+ AuWarn1("failed statfs, b%d, %d\n", bindex, err);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* when the available size is equal, select the lower one */
|
|
+ BUILD_BUG_ON(sizeof(b) < sizeof(st->f_bavail)
|
|
+ || sizeof(b) < sizeof(st->f_bsize));
|
|
+ b = st->f_bavail * st->f_bsize;
|
|
+ br->br_wbr->wbr_bytes = b;
|
|
+ if (b >= bavail) {
|
|
+ bavail = b;
|
|
+ mfs->mfs_bindex = bindex;
|
|
+ mfs->mfs_jiffy = jiffies;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mfs->mfsrr_bytes = bavail;
|
|
+ AuDbg("b%d\n", mfs->mfs_bindex);
|
|
+ au_kfree_rcu(st);
|
|
+}
|
|
+
|
|
+static int au_wbr_create_mfs(struct dentry *dentry, unsigned int flags)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *parent;
|
|
+ struct super_block *sb;
|
|
+ struct au_wbr_mfs *mfs;
|
|
+
|
|
+ err = au_wbr_create_exp(dentry);
|
|
+ if (err >= 0)
|
|
+ goto out;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ parent = NULL;
|
|
+ if (au_ftest_wbr(flags, PARENT))
|
|
+ parent = dget_parent(dentry);
|
|
+ mfs = &au_sbi(sb)->si_wbr_mfs;
|
|
+ mutex_lock(&mfs->mfs_lock);
|
|
+ if (time_after(jiffies, mfs->mfs_jiffy + mfs->mfs_expire)
|
|
+ || mfs->mfs_bindex < 0
|
|
+ || au_br_rdonly(au_sbr(sb, mfs->mfs_bindex)))
|
|
+ au_mfs(dentry, parent);
|
|
+ mutex_unlock(&mfs->mfs_lock);
|
|
+ err = mfs->mfs_bindex;
|
|
+ dput(parent);
|
|
+
|
|
+ if (err >= 0)
|
|
+ err = au_wbr_nonopq(dentry, err);
|
|
+
|
|
+out:
|
|
+ AuDbg("b%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_wbr_create_init_mfs(struct super_block *sb)
|
|
+{
|
|
+ struct au_wbr_mfs *mfs;
|
|
+
|
|
+ mfs = &au_sbi(sb)->si_wbr_mfs;
|
|
+ mutex_init(&mfs->mfs_lock);
|
|
+ mfs->mfs_jiffy = 0;
|
|
+ mfs->mfs_bindex = -EROFS;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int au_wbr_create_fin_mfs(struct super_block *sb __maybe_unused)
|
|
+{
|
|
+ mutex_destroy(&au_sbi(sb)->si_wbr_mfs.mfs_lock);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* top down regardless parent, and then mfs */
|
|
+static int au_wbr_create_tdmfs(struct dentry *dentry,
|
|
+ unsigned int flags __maybe_unused)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bwh, btail, bindex, bfound, bmfs;
|
|
+ unsigned long long watermark;
|
|
+ struct super_block *sb;
|
|
+ struct au_wbr_mfs *mfs;
|
|
+ struct au_branch *br;
|
|
+ struct dentry *parent;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ mfs = &au_sbi(sb)->si_wbr_mfs;
|
|
+ mutex_lock(&mfs->mfs_lock);
|
|
+ if (time_after(jiffies, mfs->mfs_jiffy + mfs->mfs_expire)
|
|
+ || mfs->mfs_bindex < 0)
|
|
+ au_mfs(dentry, /*parent*/NULL);
|
|
+ watermark = mfs->mfsrr_watermark;
|
|
+ bmfs = mfs->mfs_bindex;
|
|
+ mutex_unlock(&mfs->mfs_lock);
|
|
+
|
|
+ /* another style of au_wbr_create_exp() */
|
|
+ bwh = au_dbwh(dentry);
|
|
+ parent = dget_parent(dentry);
|
|
+ btail = au_dbtaildir(parent);
|
|
+ if (bwh >= 0 && bwh < btail)
|
|
+ btail = bwh;
|
|
+
|
|
+ err = au_wbr_nonopq(dentry, btail);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out;
|
|
+ btail = err;
|
|
+ bfound = -1;
|
|
+ for (bindex = 0; bindex <= btail; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (au_br_rdonly(br))
|
|
+ continue;
|
|
+ if (br->br_wbr->wbr_bytes > watermark) {
|
|
+ bfound = bindex;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ err = bfound;
|
|
+ if (err < 0)
|
|
+ err = bmfs;
|
|
+
|
|
+out:
|
|
+ dput(parent);
|
|
+ AuDbg("b%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* most free space and then round robin */
|
|
+static int au_wbr_create_mfsrr(struct dentry *dentry, unsigned int flags)
|
|
+{
|
|
+ int err;
|
|
+ struct au_wbr_mfs *mfs;
|
|
+
|
|
+ err = au_wbr_create_mfs(dentry, flags);
|
|
+ if (err >= 0) {
|
|
+ mfs = &au_sbi(dentry->d_sb)->si_wbr_mfs;
|
|
+ mutex_lock(&mfs->mfs_lock);
|
|
+ if (mfs->mfsrr_bytes < mfs->mfsrr_watermark)
|
|
+ err = au_wbr_create_rr(dentry, flags);
|
|
+ mutex_unlock(&mfs->mfs_lock);
|
|
+ }
|
|
+
|
|
+ AuDbg("b%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_wbr_create_init_mfsrr(struct super_block *sb)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ au_wbr_create_init_mfs(sb); /* ignore */
|
|
+ err = au_wbr_create_init_rr(sb);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* top down parent and most free space */
|
|
+static int au_wbr_create_pmfs(struct dentry *dentry, unsigned int flags)
|
|
+{
|
|
+ int err, e2;
|
|
+ unsigned long long b;
|
|
+ aufs_bindex_t bindex, btop, bbot;
|
|
+ struct super_block *sb;
|
|
+ struct dentry *parent, *h_parent;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ err = au_wbr_create_tdp(dentry, flags);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out;
|
|
+ parent = dget_parent(dentry);
|
|
+ btop = au_dbtop(parent);
|
|
+ bbot = au_dbtaildir(parent);
|
|
+ if (btop == bbot)
|
|
+ goto out_parent; /* success */
|
|
+
|
|
+ e2 = au_wbr_create_mfs(dentry, flags);
|
|
+ if (e2 < 0)
|
|
+ goto out_parent; /* success */
|
|
+
|
|
+ /* when the available size is equal, select upper one */
|
|
+ sb = dentry->d_sb;
|
|
+ br = au_sbr(sb, err);
|
|
+ b = br->br_wbr->wbr_bytes;
|
|
+ AuDbg("b%d, %llu\n", err, b);
|
|
+
|
|
+ for (bindex = btop; bindex <= bbot; bindex++) {
|
|
+ h_parent = au_h_dptr(parent, bindex);
|
|
+ if (!h_parent || d_is_negative(h_parent))
|
|
+ continue;
|
|
+
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (!au_br_rdonly(br) && br->br_wbr->wbr_bytes > b) {
|
|
+ b = br->br_wbr->wbr_bytes;
|
|
+ err = bindex;
|
|
+ AuDbg("b%d, %llu\n", err, b);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (err >= 0)
|
|
+ err = au_wbr_nonopq(dentry, err);
|
|
+
|
|
+out_parent:
|
|
+ dput(parent);
|
|
+out:
|
|
+ AuDbg("b%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * - top down parent
|
|
+ * - most free space with parent
|
|
+ * - most free space round-robin regardless parent
|
|
+ */
|
|
+static int au_wbr_create_pmfsrr(struct dentry *dentry, unsigned int flags)
|
|
+{
|
|
+ int err;
|
|
+ unsigned long long watermark;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ struct au_wbr_mfs *mfs;
|
|
+
|
|
+ err = au_wbr_create_pmfs(dentry, flags | AuWbr_PARENT);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ br = au_sbr(sb, err);
|
|
+ mfs = &au_sbi(sb)->si_wbr_mfs;
|
|
+ mutex_lock(&mfs->mfs_lock);
|
|
+ watermark = mfs->mfsrr_watermark;
|
|
+ mutex_unlock(&mfs->mfs_lock);
|
|
+ if (br->br_wbr->wbr_bytes < watermark)
|
|
+ /* regardless the parent dir */
|
|
+ err = au_wbr_create_mfsrr(dentry, flags);
|
|
+
|
|
+out:
|
|
+ AuDbg("b%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* policies for copyup */
|
|
+
|
|
+/* top down parent */
|
|
+static int au_wbr_copyup_tdp(struct dentry *dentry)
|
|
+{
|
|
+ return au_wbr_create_tdp(dentry, /*flags, anything is ok*/0);
|
|
+}
|
|
+
|
|
+/* bottom up parent */
|
|
+static int au_wbr_copyup_bup(struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, btop;
|
|
+ struct dentry *parent, *h_parent;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ err = -EROFS;
|
|
+ sb = dentry->d_sb;
|
|
+ parent = dget_parent(dentry);
|
|
+ btop = au_dbtop(parent);
|
|
+ for (bindex = au_dbtop(dentry); bindex >= btop; bindex--) {
|
|
+ h_parent = au_h_dptr(parent, bindex);
|
|
+ if (!h_parent || d_is_negative(h_parent))
|
|
+ continue;
|
|
+
|
|
+ if (!au_br_rdonly(au_sbr(sb, bindex))) {
|
|
+ err = bindex;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ dput(parent);
|
|
+
|
|
+ /* bottom up here */
|
|
+ if (unlikely(err < 0))
|
|
+ err = au_wbr_bu(sb, btop - 1);
|
|
+
|
|
+ AuDbg("b%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* bottom up */
|
|
+int au_wbr_do_copyup_bu(struct dentry *dentry, aufs_bindex_t btop)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = au_wbr_bu(dentry->d_sb, btop);
|
|
+ AuDbg("b%d\n", err);
|
|
+ if (err > btop)
|
|
+ err = au_wbr_nonopq(dentry, err);
|
|
+
|
|
+ AuDbg("b%d\n", err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_wbr_copyup_bu(struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t btop;
|
|
+
|
|
+ btop = au_dbtop(dentry);
|
|
+ err = au_wbr_do_copyup_bu(dentry, btop);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_wbr_copyup_operations au_wbr_copyup_ops[] = {
|
|
+ [AuWbrCopyup_TDP] = {
|
|
+ .copyup = au_wbr_copyup_tdp
|
|
+ },
|
|
+ [AuWbrCopyup_BUP] = {
|
|
+ .copyup = au_wbr_copyup_bup
|
|
+ },
|
|
+ [AuWbrCopyup_BU] = {
|
|
+ .copyup = au_wbr_copyup_bu
|
|
+ }
|
|
+};
|
|
+
|
|
+struct au_wbr_create_operations au_wbr_create_ops[] = {
|
|
+ [AuWbrCreate_TDP] = {
|
|
+ .create = au_wbr_create_tdp
|
|
+ },
|
|
+ [AuWbrCreate_RR] = {
|
|
+ .create = au_wbr_create_rr,
|
|
+ .init = au_wbr_create_init_rr
|
|
+ },
|
|
+ [AuWbrCreate_MFS] = {
|
|
+ .create = au_wbr_create_mfs,
|
|
+ .init = au_wbr_create_init_mfs,
|
|
+ .fin = au_wbr_create_fin_mfs
|
|
+ },
|
|
+ [AuWbrCreate_MFSV] = {
|
|
+ .create = au_wbr_create_mfs,
|
|
+ .init = au_wbr_create_init_mfs,
|
|
+ .fin = au_wbr_create_fin_mfs
|
|
+ },
|
|
+ [AuWbrCreate_MFSRR] = {
|
|
+ .create = au_wbr_create_mfsrr,
|
|
+ .init = au_wbr_create_init_mfsrr,
|
|
+ .fin = au_wbr_create_fin_mfs
|
|
+ },
|
|
+ [AuWbrCreate_MFSRRV] = {
|
|
+ .create = au_wbr_create_mfsrr,
|
|
+ .init = au_wbr_create_init_mfsrr,
|
|
+ .fin = au_wbr_create_fin_mfs
|
|
+ },
|
|
+ [AuWbrCreate_TDMFS] = {
|
|
+ .create = au_wbr_create_tdmfs,
|
|
+ .init = au_wbr_create_init_mfs,
|
|
+ .fin = au_wbr_create_fin_mfs
|
|
+ },
|
|
+ [AuWbrCreate_TDMFSV] = {
|
|
+ .create = au_wbr_create_tdmfs,
|
|
+ .init = au_wbr_create_init_mfs,
|
|
+ .fin = au_wbr_create_fin_mfs
|
|
+ },
|
|
+ [AuWbrCreate_PMFS] = {
|
|
+ .create = au_wbr_create_pmfs,
|
|
+ .init = au_wbr_create_init_mfs,
|
|
+ .fin = au_wbr_create_fin_mfs
|
|
+ },
|
|
+ [AuWbrCreate_PMFSV] = {
|
|
+ .create = au_wbr_create_pmfs,
|
|
+ .init = au_wbr_create_init_mfs,
|
|
+ .fin = au_wbr_create_fin_mfs
|
|
+ },
|
|
+ [AuWbrCreate_PMFSRR] = {
|
|
+ .create = au_wbr_create_pmfsrr,
|
|
+ .init = au_wbr_create_init_mfsrr,
|
|
+ .fin = au_wbr_create_fin_mfs
|
|
+ },
|
|
+ [AuWbrCreate_PMFSRRV] = {
|
|
+ .create = au_wbr_create_pmfsrr,
|
|
+ .init = au_wbr_create_init_mfsrr,
|
|
+ .fin = au_wbr_create_fin_mfs
|
|
+ }
|
|
+};
|
|
diff --git a/fs/aufs/whout.c b/fs/aufs/whout.c
|
|
new file mode 100644
|
|
index 000000000000..efe0c16508db
|
|
--- /dev/null
|
|
+++ b/fs/aufs/whout.c
|
|
@@ -0,0 +1,1062 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * whiteout for logical deletion and opaque directory
|
|
+ */
|
|
+
|
|
+#include "aufs.h"
|
|
+
|
|
+#define WH_MASK 0444
|
|
+
|
|
+/*
|
|
+ * If a directory contains this file, then it is opaque. We start with the
|
|
+ * .wh. flag so that it is blocked by lookup.
|
|
+ */
|
|
+static struct qstr diropq_name = QSTR_INIT(AUFS_WH_DIROPQ,
|
|
+ sizeof(AUFS_WH_DIROPQ) - 1);
|
|
+
|
|
+/*
|
|
+ * generate whiteout name, which is NOT terminated by NULL.
|
|
+ * @name: original d_name.name
|
|
+ * @len: original d_name.len
|
|
+ * @wh: whiteout qstr
|
|
+ * returns zero when succeeds, otherwise error.
|
|
+ * succeeded value as wh->name should be freed by kfree().
|
|
+ */
|
|
+int au_wh_name_alloc(struct qstr *wh, const struct qstr *name)
|
|
+{
|
|
+ char *p;
|
|
+
|
|
+ if (unlikely(name->len > PATH_MAX - AUFS_WH_PFX_LEN))
|
|
+ return -ENAMETOOLONG;
|
|
+
|
|
+ wh->len = name->len + AUFS_WH_PFX_LEN;
|
|
+ p = kmalloc(wh->len, GFP_NOFS);
|
|
+ wh->name = p;
|
|
+ if (p) {
|
|
+ memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
|
|
+ memcpy(p + AUFS_WH_PFX_LEN, name->name, name->len);
|
|
+ /* smp_mb(); */
|
|
+ return 0;
|
|
+ }
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * test if the @wh_name exists under @h_parent.
|
|
+ * @try_sio specifies the necessary of super-io.
|
|
+ */
|
|
+int au_wh_test(struct dentry *h_parent, struct qstr *wh_name, int try_sio)
|
|
+{
|
|
+ int err;
|
|
+ struct dentry *wh_dentry;
|
|
+
|
|
+ if (!try_sio)
|
|
+ wh_dentry = vfsub_lkup_one(wh_name, h_parent);
|
|
+ else
|
|
+ wh_dentry = au_sio_lkup_one(wh_name, h_parent);
|
|
+ err = PTR_ERR(wh_dentry);
|
|
+ if (IS_ERR(wh_dentry)) {
|
|
+ if (err == -ENAMETOOLONG)
|
|
+ err = 0;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ if (d_is_negative(wh_dentry))
|
|
+ goto out_wh; /* success */
|
|
+
|
|
+ err = 1;
|
|
+ if (d_is_reg(wh_dentry))
|
|
+ goto out_wh; /* success */
|
|
+
|
|
+ err = -EIO;
|
|
+ AuIOErr("%pd Invalid whiteout entry type 0%o.\n",
|
|
+ wh_dentry, d_inode(wh_dentry)->i_mode);
|
|
+
|
|
+out_wh:
|
|
+ dput(wh_dentry);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * test if the @h_dentry sets opaque or not.
|
|
+ */
|
|
+int au_diropq_test(struct dentry *h_dentry)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *h_dir;
|
|
+
|
|
+ h_dir = d_inode(h_dentry);
|
|
+ err = au_wh_test(h_dentry, &diropq_name,
|
|
+ au_test_h_perm_sio(h_dir, MAY_EXEC));
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * returns a negative dentry whose name is unique and temporary.
|
|
+ */
|
|
+struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct au_branch *br,
|
|
+ struct qstr *prefix)
|
|
+{
|
|
+ struct dentry *dentry;
|
|
+ int i;
|
|
+ char defname[NAME_MAX - AUFS_MAX_NAMELEN + DNAME_INLINE_LEN + 1],
|
|
+ *name, *p;
|
|
+ /* strict atomic_t is unnecessary here */
|
|
+ static unsigned short cnt;
|
|
+ struct qstr qs;
|
|
+
|
|
+ BUILD_BUG_ON(sizeof(cnt) * 2 > AUFS_WH_TMP_LEN);
|
|
+
|
|
+ name = defname;
|
|
+ qs.len = sizeof(defname) - DNAME_INLINE_LEN + prefix->len - 1;
|
|
+ if (unlikely(prefix->len > DNAME_INLINE_LEN)) {
|
|
+ dentry = ERR_PTR(-ENAMETOOLONG);
|
|
+ if (unlikely(qs.len > NAME_MAX))
|
|
+ goto out;
|
|
+ dentry = ERR_PTR(-ENOMEM);
|
|
+ name = kmalloc(qs.len + 1, GFP_NOFS);
|
|
+ if (unlikely(!name))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* doubly whiteout-ed */
|
|
+ memcpy(name, AUFS_WH_PFX AUFS_WH_PFX, AUFS_WH_PFX_LEN * 2);
|
|
+ p = name + AUFS_WH_PFX_LEN * 2;
|
|
+ memcpy(p, prefix->name, prefix->len);
|
|
+ p += prefix->len;
|
|
+ *p++ = '.';
|
|
+ AuDebugOn(name + qs.len + 1 - p <= AUFS_WH_TMP_LEN);
|
|
+
|
|
+ qs.name = name;
|
|
+ for (i = 0; i < 3; i++) {
|
|
+ sprintf(p, "%.*x", AUFS_WH_TMP_LEN, cnt++);
|
|
+ dentry = au_sio_lkup_one(&qs, h_parent);
|
|
+ if (IS_ERR(dentry) || d_is_negative(dentry))
|
|
+ goto out_name;
|
|
+ dput(dentry);
|
|
+ }
|
|
+ /* pr_warn("could not get random name\n"); */
|
|
+ dentry = ERR_PTR(-EEXIST);
|
|
+ AuDbg("%.*s\n", AuLNPair(&qs));
|
|
+ BUG();
|
|
+
|
|
+out_name:
|
|
+ if (name != defname)
|
|
+ au_kfree_try_rcu(name);
|
|
+out:
|
|
+ AuTraceErrPtr(dentry);
|
|
+ return dentry;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * rename the @h_dentry on @br to the whiteouted temporary name.
|
|
+ */
|
|
+int au_whtmp_ren(struct dentry *h_dentry, struct au_branch *br)
|
|
+{
|
|
+ int err;
|
|
+ struct path h_path = {
|
|
+ .mnt = au_br_mnt(br)
|
|
+ };
|
|
+ struct inode *h_dir, *delegated;
|
|
+ struct dentry *h_parent;
|
|
+
|
|
+ h_parent = h_dentry->d_parent; /* dir inode is locked */
|
|
+ h_dir = d_inode(h_parent);
|
|
+ IMustLock(h_dir);
|
|
+
|
|
+ h_path.dentry = au_whtmp_lkup(h_parent, br, &h_dentry->d_name);
|
|
+ err = PTR_ERR(h_path.dentry);
|
|
+ if (IS_ERR(h_path.dentry))
|
|
+ goto out;
|
|
+
|
|
+ /* under the same dir, no need to lock_rename() */
|
|
+ delegated = NULL;
|
|
+ err = vfsub_rename(h_dir, h_dentry, h_dir, &h_path, &delegated,
|
|
+ /*flags*/0);
|
|
+ AuTraceErr(err);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal rename\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ dput(h_path.dentry);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+/*
|
|
+ * functions for removing a whiteout
|
|
+ */
|
|
+
|
|
+static int do_unlink_wh(struct inode *h_dir, struct path *h_path)
|
|
+{
|
|
+ int err, force;
|
|
+ struct inode *delegated;
|
|
+
|
|
+ /*
|
|
+ * forces superio when the dir has a sticky bit.
|
|
+ * this may be a violation of unix fs semantics.
|
|
+ */
|
|
+ force = (h_dir->i_mode & S_ISVTX)
|
|
+ && !uid_eq(current_fsuid(), d_inode(h_path->dentry)->i_uid);
|
|
+ delegated = NULL;
|
|
+ err = vfsub_unlink(h_dir, h_path, &delegated, force);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal unlink\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_wh_unlink_dentry(struct inode *h_dir, struct path *h_path,
|
|
+ struct dentry *dentry)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = do_unlink_wh(h_dir, h_path);
|
|
+ if (!err && dentry)
|
|
+ au_set_dbwh(dentry, -1);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int unlink_wh_name(struct dentry *h_parent, struct qstr *wh,
|
|
+ struct au_branch *br)
|
|
+{
|
|
+ int err;
|
|
+ struct path h_path = {
|
|
+ .mnt = au_br_mnt(br)
|
|
+ };
|
|
+
|
|
+ err = 0;
|
|
+ h_path.dentry = vfsub_lkup_one(wh, h_parent);
|
|
+ if (IS_ERR(h_path.dentry))
|
|
+ err = PTR_ERR(h_path.dentry);
|
|
+ else {
|
|
+ if (d_is_reg(h_path.dentry))
|
|
+ err = do_unlink_wh(d_inode(h_parent), &h_path);
|
|
+ dput(h_path.dentry);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+/*
|
|
+ * initialize/clean whiteout for a branch
|
|
+ */
|
|
+
|
|
+static void au_wh_clean(struct inode *h_dir, struct path *whpath,
|
|
+ const int isdir)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *delegated;
|
|
+
|
|
+ if (d_is_negative(whpath->dentry))
|
|
+ return;
|
|
+
|
|
+ if (isdir)
|
|
+ err = vfsub_rmdir(h_dir, whpath);
|
|
+ else {
|
|
+ delegated = NULL;
|
|
+ err = vfsub_unlink(h_dir, whpath, &delegated, /*force*/0);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal unlink\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ }
|
|
+ if (unlikely(err))
|
|
+ pr_warn("failed removing %pd (%d), ignored.\n",
|
|
+ whpath->dentry, err);
|
|
+}
|
|
+
|
|
+static int test_linkable(struct dentry *h_root)
|
|
+{
|
|
+ struct inode *h_dir = d_inode(h_root);
|
|
+
|
|
+ if (h_dir->i_op->link)
|
|
+ return 0;
|
|
+
|
|
+ pr_err("%pd (%s) doesn't support link(2), use noplink and rw+nolwh\n",
|
|
+ h_root, au_sbtype(h_root->d_sb));
|
|
+ return -ENOSYS; /* the branch doesn't have its ->link() */
|
|
+}
|
|
+
|
|
+/* todo: should this mkdir be done in /sbin/mount.aufs helper? */
|
|
+static int au_whdir(struct inode *h_dir, struct path *path)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = -EEXIST;
|
|
+ if (d_is_negative(path->dentry)) {
|
|
+ int mode = 0700;
|
|
+
|
|
+ if (au_test_nfs(path->dentry->d_sb))
|
|
+ mode |= 0111;
|
|
+ err = vfsub_mkdir(h_dir, path, mode);
|
|
+ } else if (d_is_dir(path->dentry))
|
|
+ err = 0;
|
|
+ else
|
|
+ pr_err("unknown %pd exists\n", path->dentry);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct au_wh_base {
|
|
+ const struct qstr *name;
|
|
+ struct dentry *dentry;
|
|
+};
|
|
+
|
|
+static void au_wh_init_ro(struct inode *h_dir, struct au_wh_base base[],
|
|
+ struct path *h_path)
|
|
+{
|
|
+ h_path->dentry = base[AuBrWh_BASE].dentry;
|
|
+ au_wh_clean(h_dir, h_path, /*isdir*/0);
|
|
+ h_path->dentry = base[AuBrWh_PLINK].dentry;
|
|
+ au_wh_clean(h_dir, h_path, /*isdir*/1);
|
|
+ h_path->dentry = base[AuBrWh_ORPH].dentry;
|
|
+ au_wh_clean(h_dir, h_path, /*isdir*/1);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * returns tri-state,
|
|
+ * minus: error, caller should print the message
|
|
+ * zero: success
|
|
+ * plus: error, caller should NOT print the message
|
|
+ */
|
|
+static int au_wh_init_rw_nolink(struct dentry *h_root, struct au_wbr *wbr,
|
|
+ int do_plink, struct au_wh_base base[],
|
|
+ struct path *h_path)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *h_dir;
|
|
+
|
|
+ h_dir = d_inode(h_root);
|
|
+ h_path->dentry = base[AuBrWh_BASE].dentry;
|
|
+ au_wh_clean(h_dir, h_path, /*isdir*/0);
|
|
+ h_path->dentry = base[AuBrWh_PLINK].dentry;
|
|
+ if (do_plink) {
|
|
+ err = test_linkable(h_root);
|
|
+ if (unlikely(err)) {
|
|
+ err = 1;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = au_whdir(h_dir, h_path);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry);
|
|
+ } else
|
|
+ au_wh_clean(h_dir, h_path, /*isdir*/1);
|
|
+ h_path->dentry = base[AuBrWh_ORPH].dentry;
|
|
+ err = au_whdir(h_dir, h_path);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ wbr->wbr_orph = dget(base[AuBrWh_ORPH].dentry);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * for the moment, aufs supports the branch filesystem which does not support
|
|
+ * link(2). testing on FAT which does not support i_op->setattr() fully either,
|
|
+ * copyup failed. finally, such filesystem will not be used as the writable
|
|
+ * branch.
|
|
+ *
|
|
+ * returns tri-state, see above.
|
|
+ */
|
|
+static int au_wh_init_rw(struct dentry *h_root, struct au_wbr *wbr,
|
|
+ int do_plink, struct au_wh_base base[],
|
|
+ struct path *h_path)
|
|
+{
|
|
+ int err;
|
|
+ struct inode *h_dir;
|
|
+
|
|
+ WbrWhMustWriteLock(wbr);
|
|
+
|
|
+ err = test_linkable(h_root);
|
|
+ if (unlikely(err)) {
|
|
+ err = 1;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * todo: should this create be done in /sbin/mount.aufs helper?
|
|
+ */
|
|
+ err = -EEXIST;
|
|
+ h_dir = d_inode(h_root);
|
|
+ if (d_is_negative(base[AuBrWh_BASE].dentry)) {
|
|
+ h_path->dentry = base[AuBrWh_BASE].dentry;
|
|
+ err = vfsub_create(h_dir, h_path, WH_MASK, /*want_excl*/true);
|
|
+ } else if (d_is_reg(base[AuBrWh_BASE].dentry))
|
|
+ err = 0;
|
|
+ else
|
|
+ pr_err("unknown %pd2 exists\n", base[AuBrWh_BASE].dentry);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+ h_path->dentry = base[AuBrWh_PLINK].dentry;
|
|
+ if (do_plink) {
|
|
+ err = au_whdir(h_dir, h_path);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry);
|
|
+ } else
|
|
+ au_wh_clean(h_dir, h_path, /*isdir*/1);
|
|
+ wbr->wbr_whbase = dget(base[AuBrWh_BASE].dentry);
|
|
+
|
|
+ h_path->dentry = base[AuBrWh_ORPH].dentry;
|
|
+ err = au_whdir(h_dir, h_path);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ wbr->wbr_orph = dget(base[AuBrWh_ORPH].dentry);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * initialize the whiteout base file/dir for @br.
|
|
+ */
|
|
+int au_wh_init(struct au_branch *br, struct super_block *sb)
|
|
+{
|
|
+ int err, i;
|
|
+ const unsigned char do_plink
|
|
+ = !!au_opt_test(au_mntflags(sb), PLINK);
|
|
+ struct inode *h_dir;
|
|
+ struct path path = br->br_path;
|
|
+ struct dentry *h_root = path.dentry;
|
|
+ struct au_wbr *wbr = br->br_wbr;
|
|
+ static const struct qstr base_name[] = {
|
|
+ [AuBrWh_BASE] = QSTR_INIT(AUFS_BASE_NAME,
|
|
+ sizeof(AUFS_BASE_NAME) - 1),
|
|
+ [AuBrWh_PLINK] = QSTR_INIT(AUFS_PLINKDIR_NAME,
|
|
+ sizeof(AUFS_PLINKDIR_NAME) - 1),
|
|
+ [AuBrWh_ORPH] = QSTR_INIT(AUFS_ORPHDIR_NAME,
|
|
+ sizeof(AUFS_ORPHDIR_NAME) - 1)
|
|
+ };
|
|
+ struct au_wh_base base[] = {
|
|
+ [AuBrWh_BASE] = {
|
|
+ .name = base_name + AuBrWh_BASE,
|
|
+ .dentry = NULL
|
|
+ },
|
|
+ [AuBrWh_PLINK] = {
|
|
+ .name = base_name + AuBrWh_PLINK,
|
|
+ .dentry = NULL
|
|
+ },
|
|
+ [AuBrWh_ORPH] = {
|
|
+ .name = base_name + AuBrWh_ORPH,
|
|
+ .dentry = NULL
|
|
+ }
|
|
+ };
|
|
+
|
|
+ if (wbr)
|
|
+ WbrWhMustWriteLock(wbr);
|
|
+
|
|
+ for (i = 0; i < AuBrWh_Last; i++) {
|
|
+ /* doubly whiteouted */
|
|
+ struct dentry *d;
|
|
+
|
|
+ d = au_wh_lkup(h_root, (void *)base[i].name, br);
|
|
+ err = PTR_ERR(d);
|
|
+ if (IS_ERR(d))
|
|
+ goto out;
|
|
+
|
|
+ base[i].dentry = d;
|
|
+ AuDebugOn(wbr
|
|
+ && wbr->wbr_wh[i]
|
|
+ && wbr->wbr_wh[i] != base[i].dentry);
|
|
+ }
|
|
+
|
|
+ if (wbr)
|
|
+ for (i = 0; i < AuBrWh_Last; i++) {
|
|
+ dput(wbr->wbr_wh[i]);
|
|
+ wbr->wbr_wh[i] = NULL;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ if (!au_br_writable(br->br_perm)) {
|
|
+ h_dir = d_inode(h_root);
|
|
+ au_wh_init_ro(h_dir, base, &path);
|
|
+ } else if (!au_br_wh_linkable(br->br_perm)) {
|
|
+ err = au_wh_init_rw_nolink(h_root, wbr, do_plink, base, &path);
|
|
+ if (err > 0)
|
|
+ goto out;
|
|
+ else if (err)
|
|
+ goto out_err;
|
|
+ } else {
|
|
+ err = au_wh_init_rw(h_root, wbr, do_plink, base, &path);
|
|
+ if (err > 0)
|
|
+ goto out;
|
|
+ else if (err)
|
|
+ goto out_err;
|
|
+ }
|
|
+ goto out; /* success */
|
|
+
|
|
+out_err:
|
|
+ pr_err("an error(%d) on the writable branch %pd(%s)\n",
|
|
+ err, h_root, au_sbtype(h_root->d_sb));
|
|
+out:
|
|
+ for (i = 0; i < AuBrWh_Last; i++)
|
|
+ dput(base[i].dentry);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+/*
|
|
+ * whiteouts are all hard-linked usually.
|
|
+ * when its link count reaches a ceiling, we create a new whiteout base
|
|
+ * asynchronously.
|
|
+ */
|
|
+
|
|
+struct reinit_br_wh {
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+};
|
|
+
|
|
+static void reinit_br_wh(void *arg)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct path h_path;
|
|
+ struct reinit_br_wh *a = arg;
|
|
+ struct au_wbr *wbr;
|
|
+ struct inode *dir, *delegated;
|
|
+ struct dentry *h_root;
|
|
+ struct au_hinode *hdir;
|
|
+
|
|
+ err = 0;
|
|
+ wbr = a->br->br_wbr;
|
|
+ /* big aufs lock */
|
|
+ si_noflush_write_lock(a->sb);
|
|
+ if (!au_br_writable(a->br->br_perm))
|
|
+ goto out;
|
|
+ bindex = au_br_index(a->sb, a->br->br_id);
|
|
+ if (unlikely(bindex < 0))
|
|
+ goto out;
|
|
+
|
|
+ di_read_lock_parent(a->sb->s_root, AuLock_IR);
|
|
+ dir = d_inode(a->sb->s_root);
|
|
+ hdir = au_hi(dir, bindex);
|
|
+ h_root = au_h_dptr(a->sb->s_root, bindex);
|
|
+ AuDebugOn(h_root != au_br_dentry(a->br));
|
|
+
|
|
+ au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT);
|
|
+ wbr_wh_write_lock(wbr);
|
|
+ err = au_h_verify(wbr->wbr_whbase, au_opt_udba(a->sb), hdir->hi_inode,
|
|
+ h_root, a->br);
|
|
+ if (!err) {
|
|
+ h_path.dentry = wbr->wbr_whbase;
|
|
+ h_path.mnt = au_br_mnt(a->br);
|
|
+ delegated = NULL;
|
|
+ err = vfsub_unlink(hdir->hi_inode, &h_path, &delegated,
|
|
+ /*force*/0);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal unlink\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ } else {
|
|
+ pr_warn("%pd is moved, ignored\n", wbr->wbr_whbase);
|
|
+ err = 0;
|
|
+ }
|
|
+ dput(wbr->wbr_whbase);
|
|
+ wbr->wbr_whbase = NULL;
|
|
+ if (!err)
|
|
+ err = au_wh_init(a->br, a->sb);
|
|
+ wbr_wh_write_unlock(wbr);
|
|
+ au_hn_inode_unlock(hdir);
|
|
+ di_read_unlock(a->sb->s_root, AuLock_IR);
|
|
+ if (!err)
|
|
+ au_fhsm_wrote(a->sb, bindex, /*force*/0);
|
|
+
|
|
+out:
|
|
+ if (wbr)
|
|
+ atomic_dec(&wbr->wbr_wh_running);
|
|
+ au_lcnt_dec(&a->br->br_count);
|
|
+ si_write_unlock(a->sb);
|
|
+ au_nwt_done(&au_sbi(a->sb)->si_nowait);
|
|
+ au_kfree_rcu(a);
|
|
+ if (unlikely(err))
|
|
+ AuIOErr("err %d\n", err);
|
|
+}
|
|
+
|
|
+static void kick_reinit_br_wh(struct super_block *sb, struct au_branch *br)
|
|
+{
|
|
+ int do_dec, wkq_err;
|
|
+ struct reinit_br_wh *arg;
|
|
+
|
|
+ do_dec = 1;
|
|
+ if (atomic_inc_return(&br->br_wbr->wbr_wh_running) != 1)
|
|
+ goto out;
|
|
+
|
|
+ /* ignore ENOMEM */
|
|
+ arg = kmalloc(sizeof(*arg), GFP_NOFS);
|
|
+ if (arg) {
|
|
+ /*
|
|
+ * dec(wh_running), kfree(arg) and dec(br_count)
|
|
+ * in reinit function
|
|
+ */
|
|
+ arg->sb = sb;
|
|
+ arg->br = br;
|
|
+ au_lcnt_inc(&br->br_count);
|
|
+ wkq_err = au_wkq_nowait(reinit_br_wh, arg, sb, /*flags*/0);
|
|
+ if (unlikely(wkq_err)) {
|
|
+ atomic_dec(&br->br_wbr->wbr_wh_running);
|
|
+ au_lcnt_dec(&br->br_count);
|
|
+ au_kfree_rcu(arg);
|
|
+ }
|
|
+ do_dec = 0;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ if (do_dec)
|
|
+ atomic_dec(&br->br_wbr->wbr_wh_running);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * create the whiteout @wh.
|
|
+ */
|
|
+static int link_or_create_wh(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ struct dentry *wh)
|
|
+{
|
|
+ int err;
|
|
+ struct path h_path = {
|
|
+ .dentry = wh
|
|
+ };
|
|
+ struct au_branch *br;
|
|
+ struct au_wbr *wbr;
|
|
+ struct dentry *h_parent;
|
|
+ struct inode *h_dir, *delegated;
|
|
+
|
|
+ h_parent = wh->d_parent; /* dir inode is locked */
|
|
+ h_dir = d_inode(h_parent);
|
|
+ IMustLock(h_dir);
|
|
+
|
|
+ br = au_sbr(sb, bindex);
|
|
+ h_path.mnt = au_br_mnt(br);
|
|
+ wbr = br->br_wbr;
|
|
+ wbr_wh_read_lock(wbr);
|
|
+ if (wbr->wbr_whbase) {
|
|
+ delegated = NULL;
|
|
+ err = vfsub_link(wbr->wbr_whbase, h_dir, &h_path, &delegated);
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal link\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ if (!err || err != -EMLINK)
|
|
+ goto out;
|
|
+
|
|
+ /* link count full. re-initialize br_whbase. */
|
|
+ kick_reinit_br_wh(sb, br);
|
|
+ }
|
|
+
|
|
+ /* return this error in this context */
|
|
+ err = vfsub_create(h_dir, &h_path, WH_MASK, /*want_excl*/true);
|
|
+ if (!err)
|
|
+ au_fhsm_wrote(sb, bindex, /*force*/0);
|
|
+
|
|
+out:
|
|
+ wbr_wh_read_unlock(wbr);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * create or remove the diropq.
|
|
+ */
|
|
+static struct dentry *do_diropq(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ unsigned int flags)
|
|
+{
|
|
+ struct dentry *opq_dentry, *h_dentry;
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ int err;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ br = au_sbr(sb, bindex);
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ opq_dentry = vfsub_lkup_one(&diropq_name, h_dentry);
|
|
+ if (IS_ERR(opq_dentry))
|
|
+ goto out;
|
|
+
|
|
+ if (au_ftest_diropq(flags, CREATE)) {
|
|
+ err = link_or_create_wh(sb, bindex, opq_dentry);
|
|
+ if (!err) {
|
|
+ au_set_dbdiropq(dentry, bindex);
|
|
+ goto out; /* success */
|
|
+ }
|
|
+ } else {
|
|
+ struct path tmp = {
|
|
+ .dentry = opq_dentry,
|
|
+ .mnt = au_br_mnt(br)
|
|
+ };
|
|
+ err = do_unlink_wh(au_h_iptr(d_inode(dentry), bindex), &tmp);
|
|
+ if (!err)
|
|
+ au_set_dbdiropq(dentry, -1);
|
|
+ }
|
|
+ dput(opq_dentry);
|
|
+ opq_dentry = ERR_PTR(err);
|
|
+
|
|
+out:
|
|
+ return opq_dentry;
|
|
+}
|
|
+
|
|
+struct do_diropq_args {
|
|
+ struct dentry **errp;
|
|
+ struct dentry *dentry;
|
|
+ aufs_bindex_t bindex;
|
|
+ unsigned int flags;
|
|
+};
|
|
+
|
|
+static void call_do_diropq(void *args)
|
|
+{
|
|
+ struct do_diropq_args *a = args;
|
|
+ *a->errp = do_diropq(a->dentry, a->bindex, a->flags);
|
|
+}
|
|
+
|
|
+struct dentry *au_diropq_sio(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ unsigned int flags)
|
|
+{
|
|
+ struct dentry *diropq, *h_dentry;
|
|
+
|
|
+ h_dentry = au_h_dptr(dentry, bindex);
|
|
+ if (!au_test_h_perm_sio(d_inode(h_dentry), MAY_EXEC | MAY_WRITE))
|
|
+ diropq = do_diropq(dentry, bindex, flags);
|
|
+ else {
|
|
+ int wkq_err;
|
|
+ struct do_diropq_args args = {
|
|
+ .errp = &diropq,
|
|
+ .dentry = dentry,
|
|
+ .bindex = bindex,
|
|
+ .flags = flags
|
|
+ };
|
|
+
|
|
+ wkq_err = au_wkq_wait(call_do_diropq, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ diropq = ERR_PTR(wkq_err);
|
|
+ }
|
|
+
|
|
+ return diropq;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * lookup whiteout dentry.
|
|
+ * @h_parent: lower parent dentry which must exist and be locked
|
|
+ * @base_name: name of dentry which will be whiteouted
|
|
+ * returns dentry for whiteout.
|
|
+ */
|
|
+struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name,
|
|
+ struct au_branch *br)
|
|
+{
|
|
+ int err;
|
|
+ struct qstr wh_name;
|
|
+ struct dentry *wh_dentry;
|
|
+
|
|
+ err = au_wh_name_alloc(&wh_name, base_name);
|
|
+ wh_dentry = ERR_PTR(err);
|
|
+ if (!err) {
|
|
+ wh_dentry = vfsub_lkup_one(&wh_name, h_parent);
|
|
+ au_kfree_try_rcu(wh_name.name);
|
|
+ }
|
|
+ return wh_dentry;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * link/create a whiteout for @dentry on @bindex.
|
|
+ */
|
|
+struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct dentry *h_parent)
|
|
+{
|
|
+ struct dentry *wh_dentry;
|
|
+ struct super_block *sb;
|
|
+ int err;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, au_sbr(sb, bindex));
|
|
+ if (!IS_ERR(wh_dentry) && d_is_negative(wh_dentry)) {
|
|
+ err = link_or_create_wh(sb, bindex, wh_dentry);
|
|
+ if (!err) {
|
|
+ au_set_dbwh(dentry, bindex);
|
|
+ au_fhsm_wrote(sb, bindex, /*force*/0);
|
|
+ } else {
|
|
+ dput(wh_dentry);
|
|
+ wh_dentry = ERR_PTR(err);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return wh_dentry;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* Delete all whiteouts in this directory on branch bindex. */
|
|
+static int del_wh_children(struct dentry *h_dentry, struct au_nhash *whlist,
|
|
+ aufs_bindex_t bindex, struct au_branch *br)
|
|
+{
|
|
+ int err;
|
|
+ unsigned long ul, n;
|
|
+ struct qstr wh_name;
|
|
+ char *p;
|
|
+ struct hlist_head *head;
|
|
+ struct au_vdir_wh *pos;
|
|
+ struct au_vdir_destr *str;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ p = (void *)__get_free_page(GFP_NOFS);
|
|
+ wh_name.name = p;
|
|
+ if (unlikely(!wh_name.name))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
|
|
+ p += AUFS_WH_PFX_LEN;
|
|
+ n = whlist->nh_num;
|
|
+ head = whlist->nh_head;
|
|
+ for (ul = 0; !err && ul < n; ul++, head++) {
|
|
+ hlist_for_each_entry(pos, head, wh_hash) {
|
|
+ if (pos->wh_bindex != bindex)
|
|
+ continue;
|
|
+
|
|
+ str = &pos->wh_str;
|
|
+ if (str->len + AUFS_WH_PFX_LEN <= PATH_MAX) {
|
|
+ memcpy(p, str->name, str->len);
|
|
+ wh_name.len = AUFS_WH_PFX_LEN + str->len;
|
|
+ err = unlink_wh_name(h_dentry, &wh_name, br);
|
|
+ if (!err)
|
|
+ continue;
|
|
+ break;
|
|
+ }
|
|
+ AuIOErr("whiteout name too long %.*s\n",
|
|
+ str->len, str->name);
|
|
+ err = -EIO;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ free_page((unsigned long)wh_name.name);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct del_wh_children_args {
|
|
+ int *errp;
|
|
+ struct dentry *h_dentry;
|
|
+ struct au_nhash *whlist;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct au_branch *br;
|
|
+};
|
|
+
|
|
+static void call_del_wh_children(void *args)
|
|
+{
|
|
+ struct del_wh_children_args *a = args;
|
|
+ *a->errp = del_wh_children(a->h_dentry, a->whlist, a->bindex, a->br);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_whtmp_rmdir *au_whtmp_rmdir_alloc(struct super_block *sb, gfp_t gfp)
|
|
+{
|
|
+ struct au_whtmp_rmdir *whtmp;
|
|
+ int err;
|
|
+ unsigned int rdhash;
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ whtmp = kzalloc(sizeof(*whtmp), gfp);
|
|
+ if (unlikely(!whtmp)) {
|
|
+ whtmp = ERR_PTR(-ENOMEM);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* no estimation for dir size */
|
|
+ rdhash = au_sbi(sb)->si_rdhash;
|
|
+ if (!rdhash)
|
|
+ rdhash = AUFS_RDHASH_DEF;
|
|
+ err = au_nhash_alloc(&whtmp->whlist, rdhash, gfp);
|
|
+ if (unlikely(err)) {
|
|
+ au_kfree_rcu(whtmp);
|
|
+ whtmp = ERR_PTR(err);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return whtmp;
|
|
+}
|
|
+
|
|
+void au_whtmp_rmdir_free(struct au_whtmp_rmdir *whtmp)
|
|
+{
|
|
+ if (whtmp->br)
|
|
+ au_lcnt_dec(&whtmp->br->br_count);
|
|
+ dput(whtmp->wh_dentry);
|
|
+ iput(whtmp->dir);
|
|
+ au_nhash_wh_free(&whtmp->whlist);
|
|
+ au_kfree_rcu(whtmp);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * rmdir the whiteouted temporary named dir @h_dentry.
|
|
+ * @whlist: whiteouted children.
|
|
+ */
|
|
+int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex,
|
|
+ struct dentry *wh_dentry, struct au_nhash *whlist)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int h_nlink;
|
|
+ struct path h_tmp;
|
|
+ struct inode *wh_inode, *h_dir;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ h_dir = d_inode(wh_dentry->d_parent); /* dir inode is locked */
|
|
+ IMustLock(h_dir);
|
|
+
|
|
+ br = au_sbr(dir->i_sb, bindex);
|
|
+ wh_inode = d_inode(wh_dentry);
|
|
+ inode_lock_nested(wh_inode, AuLsc_I_CHILD);
|
|
+
|
|
+ /*
|
|
+ * someone else might change some whiteouts while we were sleeping.
|
|
+ * it means this whlist may have an obsoleted entry.
|
|
+ */
|
|
+ if (!au_test_h_perm_sio(wh_inode, MAY_EXEC | MAY_WRITE))
|
|
+ err = del_wh_children(wh_dentry, whlist, bindex, br);
|
|
+ else {
|
|
+ int wkq_err;
|
|
+ struct del_wh_children_args args = {
|
|
+ .errp = &err,
|
|
+ .h_dentry = wh_dentry,
|
|
+ .whlist = whlist,
|
|
+ .bindex = bindex,
|
|
+ .br = br
|
|
+ };
|
|
+
|
|
+ wkq_err = au_wkq_wait(call_del_wh_children, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+ }
|
|
+ inode_unlock(wh_inode);
|
|
+
|
|
+ if (!err) {
|
|
+ h_tmp.dentry = wh_dentry;
|
|
+ h_tmp.mnt = au_br_mnt(br);
|
|
+ h_nlink = h_dir->i_nlink;
|
|
+ err = vfsub_rmdir(h_dir, &h_tmp);
|
|
+ /* some fs doesn't change the parent nlink in some cases */
|
|
+ h_nlink -= h_dir->i_nlink;
|
|
+ }
|
|
+
|
|
+ if (!err) {
|
|
+ if (au_ibtop(dir) == bindex) {
|
|
+ /* todo: dir->i_mutex is necessary */
|
|
+ au_cpup_attr_timesizes(dir);
|
|
+ if (h_nlink)
|
|
+ vfsub_drop_nlink(dir);
|
|
+ }
|
|
+ return 0; /* success */
|
|
+ }
|
|
+
|
|
+ pr_warn("failed removing %pd(%d), ignored\n", wh_dentry, err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void call_rmdir_whtmp(void *args)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex;
|
|
+ struct au_whtmp_rmdir *a = args;
|
|
+ struct super_block *sb;
|
|
+ struct dentry *h_parent;
|
|
+ struct inode *h_dir;
|
|
+ struct au_hinode *hdir;
|
|
+
|
|
+ /* rmdir by nfsd may cause deadlock with this i_mutex */
|
|
+ /* inode_lock(a->dir); */
|
|
+ err = -EROFS;
|
|
+ sb = a->dir->i_sb;
|
|
+ si_read_lock(sb, !AuLock_FLUSH);
|
|
+ if (!au_br_writable(a->br->br_perm))
|
|
+ goto out;
|
|
+ bindex = au_br_index(sb, a->br->br_id);
|
|
+ if (unlikely(bindex < 0))
|
|
+ goto out;
|
|
+
|
|
+ err = -EIO;
|
|
+ ii_write_lock_parent(a->dir);
|
|
+ h_parent = dget_parent(a->wh_dentry);
|
|
+ h_dir = d_inode(h_parent);
|
|
+ hdir = au_hi(a->dir, bindex);
|
|
+ err = vfsub_mnt_want_write(au_br_mnt(a->br));
|
|
+ if (unlikely(err))
|
|
+ goto out_mnt;
|
|
+ au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT);
|
|
+ err = au_h_verify(a->wh_dentry, au_opt_udba(sb), h_dir, h_parent,
|
|
+ a->br);
|
|
+ if (!err)
|
|
+ err = au_whtmp_rmdir(a->dir, bindex, a->wh_dentry, &a->whlist);
|
|
+ au_hn_inode_unlock(hdir);
|
|
+ vfsub_mnt_drop_write(au_br_mnt(a->br));
|
|
+
|
|
+out_mnt:
|
|
+ dput(h_parent);
|
|
+ ii_write_unlock(a->dir);
|
|
+out:
|
|
+ /* inode_unlock(a->dir); */
|
|
+ au_whtmp_rmdir_free(a);
|
|
+ si_read_unlock(sb);
|
|
+ au_nwt_done(&au_sbi(sb)->si_nowait);
|
|
+ if (unlikely(err))
|
|
+ AuIOErr("err %d\n", err);
|
|
+}
|
|
+
|
|
+void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex,
|
|
+ struct dentry *wh_dentry, struct au_whtmp_rmdir *args)
|
|
+{
|
|
+ int wkq_err;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ IMustLock(dir);
|
|
+
|
|
+ /* all post-process will be done in do_rmdir_whtmp(). */
|
|
+ sb = dir->i_sb;
|
|
+ args->dir = au_igrab(dir);
|
|
+ args->br = au_sbr(sb, bindex);
|
|
+ au_lcnt_inc(&args->br->br_count);
|
|
+ args->wh_dentry = dget(wh_dentry);
|
|
+ wkq_err = au_wkq_nowait(call_rmdir_whtmp, args, sb, /*flags*/0);
|
|
+ if (unlikely(wkq_err)) {
|
|
+ pr_warn("rmdir error %pd (%d), ignored\n", wh_dentry, wkq_err);
|
|
+ au_whtmp_rmdir_free(args);
|
|
+ }
|
|
+}
|
|
diff --git a/fs/aufs/whout.h b/fs/aufs/whout.h
|
|
new file mode 100644
|
|
index 000000000000..65459ba0e790
|
|
--- /dev/null
|
|
+++ b/fs/aufs/whout.h
|
|
@@ -0,0 +1,86 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * whiteout for logical deletion and opaque directory
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_WHOUT_H__
|
|
+#define __AUFS_WHOUT_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include "dir.h"
|
|
+
|
|
+/* whout.c */
|
|
+int au_wh_name_alloc(struct qstr *wh, const struct qstr *name);
|
|
+int au_wh_test(struct dentry *h_parent, struct qstr *wh_name, int try_sio);
|
|
+int au_diropq_test(struct dentry *h_dentry);
|
|
+struct au_branch;
|
|
+struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct au_branch *br,
|
|
+ struct qstr *prefix);
|
|
+int au_whtmp_ren(struct dentry *h_dentry, struct au_branch *br);
|
|
+int au_wh_unlink_dentry(struct inode *h_dir, struct path *h_path,
|
|
+ struct dentry *dentry);
|
|
+int au_wh_init(struct au_branch *br, struct super_block *sb);
|
|
+
|
|
+/* diropq flags */
|
|
+#define AuDiropq_CREATE 1
|
|
+#define au_ftest_diropq(flags, name) ((flags) & AuDiropq_##name)
|
|
+#define au_fset_diropq(flags, name) \
|
|
+ do { (flags) |= AuDiropq_##name; } while (0)
|
|
+#define au_fclr_diropq(flags, name) \
|
|
+ do { (flags) &= ~AuDiropq_##name; } while (0)
|
|
+
|
|
+struct dentry *au_diropq_sio(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ unsigned int flags);
|
|
+struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name,
|
|
+ struct au_branch *br);
|
|
+struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex,
|
|
+ struct dentry *h_parent);
|
|
+
|
|
+/* real rmdir for the whiteout-ed dir */
|
|
+struct au_whtmp_rmdir {
|
|
+ struct inode *dir;
|
|
+ struct au_branch *br;
|
|
+ struct dentry *wh_dentry;
|
|
+ struct au_nhash whlist;
|
|
+};
|
|
+
|
|
+struct au_whtmp_rmdir *au_whtmp_rmdir_alloc(struct super_block *sb, gfp_t gfp);
|
|
+void au_whtmp_rmdir_free(struct au_whtmp_rmdir *whtmp);
|
|
+int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex,
|
|
+ struct dentry *wh_dentry, struct au_nhash *whlist);
|
|
+void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex,
|
|
+ struct dentry *wh_dentry, struct au_whtmp_rmdir *args);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline struct dentry *au_diropq_create(struct dentry *dentry,
|
|
+ aufs_bindex_t bindex)
|
|
+{
|
|
+ return au_diropq_sio(dentry, bindex, AuDiropq_CREATE);
|
|
+}
|
|
+
|
|
+static inline int au_diropq_remove(struct dentry *dentry, aufs_bindex_t bindex)
|
|
+{
|
|
+ return PTR_ERR(au_diropq_sio(dentry, bindex, !AuDiropq_CREATE));
|
|
+}
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_WHOUT_H__ */
|
|
diff --git a/fs/aufs/wkq.c b/fs/aufs/wkq.c
|
|
new file mode 100644
|
|
index 000000000000..4d66bb2dc657
|
|
--- /dev/null
|
|
+++ b/fs/aufs/wkq.c
|
|
@@ -0,0 +1,372 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * workqueue for asynchronous/super-io operations
|
|
+ * todo: try new credential scheme
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+/* internal workqueue named AUFS_WKQ_NAME */
|
|
+
|
|
+static struct workqueue_struct *au_wkq;
|
|
+
|
|
+struct au_wkinfo {
|
|
+ struct work_struct wk;
|
|
+ struct kobject *kobj;
|
|
+
|
|
+ unsigned int flags; /* see wkq.h */
|
|
+
|
|
+ au_wkq_func_t func;
|
|
+ void *args;
|
|
+
|
|
+#ifdef CONFIG_LOCKDEP
|
|
+ int dont_check;
|
|
+ struct held_lock **hlock;
|
|
+#endif
|
|
+
|
|
+ struct completion *comp;
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+/*
|
|
+ * Aufs passes some operations to the workqueue such as the internal copyup.
|
|
+ * This scheme looks rather unnatural for LOCKDEP debugging feature, since the
|
|
+ * job run by workqueue depends upon the locks acquired in the other task.
|
|
+ * Delegating a small operation to the workqueue, aufs passes its lockdep
|
|
+ * information too. And the job in the workqueue restores the info in order to
|
|
+ * pretend as if it acquired those locks. This is just to make LOCKDEP work
|
|
+ * correctly and expectedly.
|
|
+ */
|
|
+
|
|
+#ifndef CONFIG_LOCKDEP
|
|
+AuStubInt0(au_wkq_lockdep_alloc, struct au_wkinfo *wkinfo);
|
|
+AuStubVoid(au_wkq_lockdep_free, struct au_wkinfo *wkinfo);
|
|
+AuStubVoid(au_wkq_lockdep_pre, struct au_wkinfo *wkinfo);
|
|
+AuStubVoid(au_wkq_lockdep_post, struct au_wkinfo *wkinfo);
|
|
+AuStubVoid(au_wkq_lockdep_init, struct au_wkinfo *wkinfo);
|
|
+#else
|
|
+static void au_wkq_lockdep_init(struct au_wkinfo *wkinfo)
|
|
+{
|
|
+ wkinfo->hlock = NULL;
|
|
+ wkinfo->dont_check = 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * 1: matched
|
|
+ * 0: unmatched
|
|
+ */
|
|
+static int au_wkq_lockdep_test(struct lock_class_key *key, const char *name)
|
|
+{
|
|
+ static DEFINE_SPINLOCK(spin);
|
|
+ static struct {
|
|
+ char *name;
|
|
+ struct lock_class_key *key;
|
|
+ } a[] = {
|
|
+ { .name = "&sbinfo->si_rwsem" },
|
|
+ { .name = "&finfo->fi_rwsem" },
|
|
+ { .name = "&dinfo->di_rwsem" },
|
|
+ { .name = "&iinfo->ii_rwsem" }
|
|
+ };
|
|
+ static int set;
|
|
+ int i;
|
|
+
|
|
+ /* lockless read from 'set.' see below */
|
|
+ if (set == ARRAY_SIZE(a)) {
|
|
+ for (i = 0; i < ARRAY_SIZE(a); i++)
|
|
+ if (a[i].key == key)
|
|
+ goto match;
|
|
+ goto unmatch;
|
|
+ }
|
|
+
|
|
+ spin_lock(&spin);
|
|
+ if (set)
|
|
+ for (i = 0; i < ARRAY_SIZE(a); i++)
|
|
+ if (a[i].key == key) {
|
|
+ spin_unlock(&spin);
|
|
+ goto match;
|
|
+ }
|
|
+ for (i = 0; i < ARRAY_SIZE(a); i++) {
|
|
+ if (a[i].key) {
|
|
+ if (unlikely(a[i].key == key)) { /* rare but possible */
|
|
+ spin_unlock(&spin);
|
|
+ goto match;
|
|
+ } else
|
|
+ continue;
|
|
+ }
|
|
+ if (strstr(a[i].name, name)) {
|
|
+ /*
|
|
+ * the order of these three lines is important for the
|
|
+ * lockless read above.
|
|
+ */
|
|
+ a[i].key = key;
|
|
+ spin_unlock(&spin);
|
|
+ set++;
|
|
+ /* AuDbg("%d, %s\n", set, name); */
|
|
+ goto match;
|
|
+ }
|
|
+ }
|
|
+ spin_unlock(&spin);
|
|
+ goto unmatch;
|
|
+
|
|
+match:
|
|
+ return 1;
|
|
+unmatch:
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int au_wkq_lockdep_alloc(struct au_wkinfo *wkinfo)
|
|
+{
|
|
+ int err, n;
|
|
+ struct task_struct *curr;
|
|
+ struct held_lock **hl, *held_locks, *p;
|
|
+
|
|
+ err = 0;
|
|
+ curr = current;
|
|
+ wkinfo->dont_check = lockdep_recursing(curr);
|
|
+ if (wkinfo->dont_check)
|
|
+ goto out;
|
|
+ n = curr->lockdep_depth;
|
|
+ if (!n)
|
|
+ goto out;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ wkinfo->hlock = kmalloc_array(n + 1, sizeof(*wkinfo->hlock), GFP_NOFS);
|
|
+ if (unlikely(!wkinfo->hlock))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+#if 0 /* left for debugging */
|
|
+ if (0 && au_debug_test())
|
|
+ lockdep_print_held_locks(curr);
|
|
+#endif
|
|
+ held_locks = curr->held_locks;
|
|
+ hl = wkinfo->hlock;
|
|
+ while (n--) {
|
|
+ p = held_locks++;
|
|
+ if (au_wkq_lockdep_test(p->instance->key, p->instance->name))
|
|
+ *hl++ = p;
|
|
+ }
|
|
+ *hl = NULL;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_wkq_lockdep_free(struct au_wkinfo *wkinfo)
|
|
+{
|
|
+ au_kfree_try_rcu(wkinfo->hlock);
|
|
+}
|
|
+
|
|
+static void au_wkq_lockdep_pre(struct au_wkinfo *wkinfo)
|
|
+{
|
|
+ struct held_lock *p, **hl = wkinfo->hlock;
|
|
+ int subclass;
|
|
+
|
|
+ if (wkinfo->dont_check)
|
|
+ lockdep_off();
|
|
+ if (!hl)
|
|
+ return;
|
|
+ while ((p = *hl++)) { /* assignment */
|
|
+ subclass = lockdep_hlock_class(p)->subclass;
|
|
+ /* AuDbg("%s, %d\n", p->instance->name, subclass); */
|
|
+ if (p->read)
|
|
+ rwsem_acquire_read(p->instance, subclass, 0,
|
|
+ /*p->acquire_ip*/_RET_IP_);
|
|
+ else
|
|
+ rwsem_acquire(p->instance, subclass, 0,
|
|
+ /*p->acquire_ip*/_RET_IP_);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void au_wkq_lockdep_post(struct au_wkinfo *wkinfo)
|
|
+{
|
|
+ struct held_lock *p, **hl = wkinfo->hlock;
|
|
+
|
|
+ if (wkinfo->dont_check)
|
|
+ lockdep_on();
|
|
+ if (!hl)
|
|
+ return;
|
|
+ while ((p = *hl++)) /* assignment */
|
|
+ rwsem_release(p->instance, /*p->acquire_ip*/_RET_IP_);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void wkq_func(struct work_struct *wk)
|
|
+{
|
|
+ struct au_wkinfo *wkinfo = container_of(wk, struct au_wkinfo, wk);
|
|
+
|
|
+ AuDebugOn(!uid_eq(current_fsuid(), GLOBAL_ROOT_UID));
|
|
+ AuDebugOn(rlimit(RLIMIT_FSIZE) != RLIM_INFINITY);
|
|
+
|
|
+ au_wkq_lockdep_pre(wkinfo);
|
|
+ wkinfo->func(wkinfo->args);
|
|
+ au_wkq_lockdep_post(wkinfo);
|
|
+ if (au_ftest_wkq(wkinfo->flags, WAIT))
|
|
+ complete(wkinfo->comp);
|
|
+ else {
|
|
+ kobject_put(wkinfo->kobj);
|
|
+ module_put(THIS_MODULE); /* todo: ?? */
|
|
+ au_kfree_rcu(wkinfo);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Since struct completion is large, try allocating it dynamically.
|
|
+ */
|
|
+#define AuWkqCompDeclare(name) struct completion *comp = NULL
|
|
+
|
|
+static int au_wkq_comp_alloc(struct au_wkinfo *wkinfo, struct completion **comp)
|
|
+{
|
|
+ *comp = kmalloc(sizeof(**comp), GFP_NOFS);
|
|
+ if (*comp) {
|
|
+ init_completion(*comp);
|
|
+ wkinfo->comp = *comp;
|
|
+ return 0;
|
|
+ }
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+static void au_wkq_comp_free(struct completion *comp)
|
|
+{
|
|
+ au_kfree_rcu(comp);
|
|
+}
|
|
+
|
|
+static void au_wkq_run(struct au_wkinfo *wkinfo)
|
|
+{
|
|
+ if (au_ftest_wkq(wkinfo->flags, NEST)) {
|
|
+ if (au_wkq_test()) {
|
|
+ AuWarn1("wkq from wkq, unless silly-rename on NFS,"
|
|
+ " due to a dead dir by UDBA,"
|
|
+ " or async xino write?\n");
|
|
+ AuDebugOn(au_ftest_wkq(wkinfo->flags, WAIT));
|
|
+ }
|
|
+ } else
|
|
+ au_dbg_verify_kthread();
|
|
+
|
|
+ if (au_ftest_wkq(wkinfo->flags, WAIT)) {
|
|
+ INIT_WORK_ONSTACK(&wkinfo->wk, wkq_func);
|
|
+ queue_work(au_wkq, &wkinfo->wk);
|
|
+ } else {
|
|
+ INIT_WORK(&wkinfo->wk, wkq_func);
|
|
+ schedule_work(&wkinfo->wk);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Be careful. It is easy to make deadlock happen.
|
|
+ * processA: lock, wkq and wait
|
|
+ * processB: wkq and wait, lock in wkq
|
|
+ * --> deadlock
|
|
+ */
|
|
+int au_wkq_do_wait(unsigned int flags, au_wkq_func_t func, void *args)
|
|
+{
|
|
+ int err;
|
|
+ AuWkqCompDeclare(comp);
|
|
+ struct au_wkinfo wkinfo = {
|
|
+ .flags = flags,
|
|
+ .func = func,
|
|
+ .args = args
|
|
+ };
|
|
+
|
|
+ err = au_wkq_comp_alloc(&wkinfo, &comp);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ err = au_wkq_lockdep_alloc(&wkinfo);
|
|
+ if (unlikely(err))
|
|
+ goto out_comp;
|
|
+ if (!err) {
|
|
+ au_wkq_run(&wkinfo);
|
|
+ /* no timeout, no interrupt */
|
|
+ wait_for_completion(wkinfo.comp);
|
|
+ }
|
|
+ au_wkq_lockdep_free(&wkinfo);
|
|
+
|
|
+out_comp:
|
|
+ au_wkq_comp_free(comp);
|
|
+out:
|
|
+ destroy_work_on_stack(&wkinfo.wk);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Note: dget/dput() in func for aufs dentries are not supported. It will be a
|
|
+ * problem in a concurrent umounting.
|
|
+ */
|
|
+int au_wkq_nowait(au_wkq_func_t func, void *args, struct super_block *sb,
|
|
+ unsigned int flags)
|
|
+{
|
|
+ int err;
|
|
+ struct au_wkinfo *wkinfo;
|
|
+
|
|
+ atomic_inc(&au_sbi(sb)->si_nowait.nw_len);
|
|
+
|
|
+ /*
|
|
+ * wkq_func() must free this wkinfo.
|
|
+ * it highly depends upon the implementation of workqueue.
|
|
+ */
|
|
+ err = 0;
|
|
+ wkinfo = kmalloc(sizeof(*wkinfo), GFP_NOFS);
|
|
+ if (wkinfo) {
|
|
+ wkinfo->kobj = &au_sbi(sb)->si_kobj;
|
|
+ wkinfo->flags = flags & ~AuWkq_WAIT;
|
|
+ wkinfo->func = func;
|
|
+ wkinfo->args = args;
|
|
+ wkinfo->comp = NULL;
|
|
+ au_wkq_lockdep_init(wkinfo);
|
|
+ kobject_get(wkinfo->kobj);
|
|
+ __module_get(THIS_MODULE); /* todo: ?? */
|
|
+
|
|
+ au_wkq_run(wkinfo);
|
|
+ } else {
|
|
+ err = -ENOMEM;
|
|
+ au_nwt_done(&au_sbi(sb)->si_nowait);
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+void au_nwt_init(struct au_nowait_tasks *nwt)
|
|
+{
|
|
+ atomic_set(&nwt->nw_len, 0);
|
|
+ /* smp_mb(); */ /* atomic_set */
|
|
+ init_waitqueue_head(&nwt->nw_wq);
|
|
+}
|
|
+
|
|
+void au_wkq_fin(void)
|
|
+{
|
|
+ destroy_workqueue(au_wkq);
|
|
+}
|
|
+
|
|
+int __init au_wkq_init(void)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = 0;
|
|
+ au_wkq = alloc_workqueue(AUFS_WKQ_NAME, 0, WQ_DFL_ACTIVE);
|
|
+ if (IS_ERR(au_wkq))
|
|
+ err = PTR_ERR(au_wkq);
|
|
+ else if (!au_wkq)
|
|
+ err = -ENOMEM;
|
|
+
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/aufs/wkq.h b/fs/aufs/wkq.h
|
|
new file mode 100644
|
|
index 000000000000..5be76b69d8ff
|
|
--- /dev/null
|
|
+++ b/fs/aufs/wkq.h
|
|
@@ -0,0 +1,89 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * workqueue for asynchronous/super-io operations
|
|
+ * todo: try new credentials management scheme
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_WKQ_H__
|
|
+#define __AUFS_WKQ_H__
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+
|
|
+#include <linux/wait.h>
|
|
+
|
|
+struct super_block;
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * in the next operation, wait for the 'nowait' tasks in system-wide workqueue
|
|
+ */
|
|
+struct au_nowait_tasks {
|
|
+ atomic_t nw_len;
|
|
+ wait_queue_head_t nw_wq;
|
|
+};
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+typedef void (*au_wkq_func_t)(void *args);
|
|
+
|
|
+/* wkq flags */
|
|
+#define AuWkq_WAIT 1
|
|
+#define AuWkq_NEST (1 << 1)
|
|
+#define au_ftest_wkq(flags, name) ((flags) & AuWkq_##name)
|
|
+#define au_fset_wkq(flags, name) \
|
|
+ do { (flags) |= AuWkq_##name; } while (0)
|
|
+#define au_fclr_wkq(flags, name) \
|
|
+ do { (flags) &= ~AuWkq_##name; } while (0)
|
|
+
|
|
+/* wkq.c */
|
|
+int au_wkq_do_wait(unsigned int flags, au_wkq_func_t func, void *args);
|
|
+int au_wkq_nowait(au_wkq_func_t func, void *args, struct super_block *sb,
|
|
+ unsigned int flags);
|
|
+void au_nwt_init(struct au_nowait_tasks *nwt);
|
|
+int __init au_wkq_init(void);
|
|
+void au_wkq_fin(void);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static inline int au_wkq_test(void)
|
|
+{
|
|
+ return current->flags & PF_WQ_WORKER;
|
|
+}
|
|
+
|
|
+static inline int au_wkq_wait(au_wkq_func_t func, void *args)
|
|
+{
|
|
+ return au_wkq_do_wait(AuWkq_WAIT, func, args);
|
|
+}
|
|
+
|
|
+static inline void au_nwt_done(struct au_nowait_tasks *nwt)
|
|
+{
|
|
+ if (atomic_dec_and_test(&nwt->nw_len))
|
|
+ wake_up_all(&nwt->nw_wq);
|
|
+}
|
|
+
|
|
+static inline int au_nwt_flush(struct au_nowait_tasks *nwt)
|
|
+{
|
|
+ wait_event(nwt->nw_wq, !atomic_read(&nwt->nw_len));
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#endif /* __KERNEL__ */
|
|
+#endif /* __AUFS_WKQ_H__ */
|
|
diff --git a/fs/aufs/xattr.c b/fs/aufs/xattr.c
|
|
new file mode 100644
|
|
index 000000000000..103c4275e5fc
|
|
--- /dev/null
|
|
+++ b/fs/aufs/xattr.c
|
|
@@ -0,0 +1,356 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2014-2020 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 xattr functions
|
|
+ */
|
|
+
|
|
+#include <linux/fs.h>
|
|
+#include <linux/posix_acl_xattr.h>
|
|
+#include <linux/xattr.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+static int au_xattr_ignore(int err, char *name, unsigned int ignore_flags)
|
|
+{
|
|
+ if (!ignore_flags)
|
|
+ goto out;
|
|
+ switch (err) {
|
|
+ case -ENOMEM:
|
|
+ case -EDQUOT:
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if ((ignore_flags & AuBrAttr_ICEX) == AuBrAttr_ICEX) {
|
|
+ err = 0;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+#define cmp(brattr, prefix) do { \
|
|
+ if (!strncmp(name, XATTR_##prefix##_PREFIX, \
|
|
+ XATTR_##prefix##_PREFIX_LEN)) { \
|
|
+ if (ignore_flags & AuBrAttr_ICEX_##brattr) \
|
|
+ err = 0; \
|
|
+ goto out; \
|
|
+ } \
|
|
+ } while (0)
|
|
+
|
|
+ cmp(SEC, SECURITY);
|
|
+ cmp(SYS, SYSTEM);
|
|
+ cmp(TR, TRUSTED);
|
|
+ cmp(USR, USER);
|
|
+#undef cmp
|
|
+
|
|
+ if (ignore_flags & AuBrAttr_ICEX_OTH)
|
|
+ err = 0;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const int au_xattr_out_of_list = AuBrAttr_ICEX_OTH << 1;
|
|
+
|
|
+static int au_do_cpup_xattr(struct dentry *h_dst, struct dentry *h_src,
|
|
+ char *name, char **buf, unsigned int ignore_flags,
|
|
+ unsigned int verbose)
|
|
+{
|
|
+ int err;
|
|
+ ssize_t ssz;
|
|
+ struct inode *h_idst;
|
|
+
|
|
+ ssz = vfs_getxattr_alloc(h_src, name, buf, 0, GFP_NOFS);
|
|
+ err = ssz;
|
|
+ if (unlikely(err <= 0)) {
|
|
+ if (err == -ENODATA
|
|
+ || (err == -EOPNOTSUPP
|
|
+ && ((ignore_flags & au_xattr_out_of_list)
|
|
+ || (au_test_nfs_noacl(d_inode(h_src))
|
|
+ && (!strcmp(name, XATTR_NAME_POSIX_ACL_ACCESS)
|
|
+ || !strcmp(name,
|
|
+ XATTR_NAME_POSIX_ACL_DEFAULT))))
|
|
+ ))
|
|
+ err = 0;
|
|
+ if (err && (verbose || au_debug_test()))
|
|
+ pr_err("%s, err %d\n", name, err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* unlock it temporary */
|
|
+ h_idst = d_inode(h_dst);
|
|
+ inode_unlock(h_idst);
|
|
+ err = vfsub_setxattr(h_dst, name, *buf, ssz, /*flags*/0);
|
|
+ inode_lock_nested(h_idst, AuLsc_I_CHILD2);
|
|
+ if (unlikely(err)) {
|
|
+ if (verbose || au_debug_test())
|
|
+ pr_err("%s, err %d\n", name, err);
|
|
+ err = au_xattr_ignore(err, name, ignore_flags);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_cpup_xattr(struct dentry *h_dst, struct dentry *h_src, int ignore_flags,
|
|
+ unsigned int verbose)
|
|
+{
|
|
+ int err, unlocked, acl_access, acl_default;
|
|
+ ssize_t ssz;
|
|
+ struct inode *h_isrc, *h_idst;
|
|
+ char *value, *p, *o, *e;
|
|
+
|
|
+ /* try stopping to update the source inode while we are referencing */
|
|
+ /* there should not be the parent-child relationship between them */
|
|
+ h_isrc = d_inode(h_src);
|
|
+ h_idst = d_inode(h_dst);
|
|
+ inode_unlock(h_idst);
|
|
+ inode_lock_shared_nested(h_isrc, AuLsc_I_CHILD);
|
|
+ inode_lock_nested(h_idst, AuLsc_I_CHILD2);
|
|
+ unlocked = 0;
|
|
+
|
|
+ /* some filesystems don't list POSIX ACL, for example tmpfs */
|
|
+ ssz = vfs_listxattr(h_src, NULL, 0);
|
|
+ err = ssz;
|
|
+ if (unlikely(err < 0)) {
|
|
+ AuTraceErr(err);
|
|
+ if (err == -ENODATA
|
|
+ || err == -EOPNOTSUPP)
|
|
+ err = 0; /* ignore */
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = 0;
|
|
+ p = NULL;
|
|
+ o = NULL;
|
|
+ if (ssz) {
|
|
+ err = -ENOMEM;
|
|
+ p = kmalloc(ssz, GFP_NOFS);
|
|
+ o = p;
|
|
+ if (unlikely(!p))
|
|
+ goto out;
|
|
+ err = vfs_listxattr(h_src, p, ssz);
|
|
+ }
|
|
+ inode_unlock_shared(h_isrc);
|
|
+ unlocked = 1;
|
|
+ AuDbg("err %d, ssz %zd\n", err, ssz);
|
|
+ if (unlikely(err < 0))
|
|
+ goto out_free;
|
|
+
|
|
+ err = 0;
|
|
+ e = p + ssz;
|
|
+ value = NULL;
|
|
+ acl_access = 0;
|
|
+ acl_default = 0;
|
|
+ while (!err && p < e) {
|
|
+ acl_access |= !strncmp(p, XATTR_NAME_POSIX_ACL_ACCESS,
|
|
+ sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1);
|
|
+ acl_default |= !strncmp(p, XATTR_NAME_POSIX_ACL_DEFAULT,
|
|
+ sizeof(XATTR_NAME_POSIX_ACL_DEFAULT)
|
|
+ - 1);
|
|
+ err = au_do_cpup_xattr(h_dst, h_src, p, &value, ignore_flags,
|
|
+ verbose);
|
|
+ p += strlen(p) + 1;
|
|
+ }
|
|
+ AuTraceErr(err);
|
|
+ ignore_flags |= au_xattr_out_of_list;
|
|
+ if (!err && !acl_access) {
|
|
+ err = au_do_cpup_xattr(h_dst, h_src,
|
|
+ XATTR_NAME_POSIX_ACL_ACCESS, &value,
|
|
+ ignore_flags, verbose);
|
|
+ AuTraceErr(err);
|
|
+ }
|
|
+ if (!err && !acl_default) {
|
|
+ err = au_do_cpup_xattr(h_dst, h_src,
|
|
+ XATTR_NAME_POSIX_ACL_DEFAULT, &value,
|
|
+ ignore_flags, verbose);
|
|
+ AuTraceErr(err);
|
|
+ }
|
|
+
|
|
+ au_kfree_try_rcu(value);
|
|
+
|
|
+out_free:
|
|
+ au_kfree_try_rcu(o);
|
|
+out:
|
|
+ if (!unlocked)
|
|
+ inode_unlock_shared(h_isrc);
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_smack_reentering(struct super_block *sb)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_SECURITY_SMACK) || IS_ENABLED(CONFIG_SECURITY_SELINUX)
|
|
+ /*
|
|
+ * as a part of lookup, smack_d_instantiate() is called, and it calls
|
|
+ * i_op->getxattr(). ouch.
|
|
+ */
|
|
+ return si_pid_test(sb);
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+enum {
|
|
+ AU_XATTR_LIST,
|
|
+ AU_XATTR_GET
|
|
+};
|
|
+
|
|
+struct au_lgxattr {
|
|
+ int type;
|
|
+ union {
|
|
+ struct {
|
|
+ char *list;
|
|
+ size_t size;
|
|
+ } list;
|
|
+ struct {
|
|
+ const char *name;
|
|
+ void *value;
|
|
+ size_t size;
|
|
+ } get;
|
|
+ } u;
|
|
+};
|
|
+
|
|
+static ssize_t au_lgxattr(struct dentry *dentry, struct inode *inode,
|
|
+ struct au_lgxattr *arg)
|
|
+{
|
|
+ ssize_t err;
|
|
+ int reenter;
|
|
+ struct path h_path;
|
|
+ struct super_block *sb;
|
|
+
|
|
+ sb = dentry->d_sb;
|
|
+ reenter = au_smack_reentering(sb);
|
|
+ if (!reenter) {
|
|
+ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ }
|
|
+ err = au_h_path_getattr(dentry, inode, /*force*/1, &h_path, reenter);
|
|
+ if (unlikely(err))
|
|
+ goto out_si;
|
|
+ if (unlikely(!h_path.dentry))
|
|
+ /* illegally overlapped or something */
|
|
+ goto out_di; /* pretending success */
|
|
+
|
|
+ /* always topmost entry only */
|
|
+ switch (arg->type) {
|
|
+ case AU_XATTR_LIST:
|
|
+ err = vfs_listxattr(h_path.dentry,
|
|
+ arg->u.list.list, arg->u.list.size);
|
|
+ break;
|
|
+ case AU_XATTR_GET:
|
|
+ AuDebugOn(d_is_negative(h_path.dentry));
|
|
+ err = vfs_getxattr(h_path.dentry,
|
|
+ arg->u.get.name, arg->u.get.value,
|
|
+ arg->u.get.size);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+out_di:
|
|
+ if (!reenter)
|
|
+ di_read_unlock(dentry, AuLock_IR);
|
|
+out_si:
|
|
+ if (!reenter)
|
|
+ si_read_unlock(sb);
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+ssize_t aufs_listxattr(struct dentry *dentry, char *list, size_t size)
|
|
+{
|
|
+ struct au_lgxattr arg = {
|
|
+ .type = AU_XATTR_LIST,
|
|
+ .u.list = {
|
|
+ .list = list,
|
|
+ .size = size
|
|
+ },
|
|
+ };
|
|
+
|
|
+ return au_lgxattr(dentry, /*inode*/NULL, &arg);
|
|
+}
|
|
+
|
|
+static ssize_t au_getxattr(struct dentry *dentry, struct inode *inode,
|
|
+ const char *name, void *value, size_t size)
|
|
+{
|
|
+ struct au_lgxattr arg = {
|
|
+ .type = AU_XATTR_GET,
|
|
+ .u.get = {
|
|
+ .name = name,
|
|
+ .value = value,
|
|
+ .size = size
|
|
+ },
|
|
+ };
|
|
+
|
|
+ return au_lgxattr(dentry, inode, &arg);
|
|
+}
|
|
+
|
|
+static int au_setxattr(struct dentry *dentry, struct inode *inode,
|
|
+ const char *name, const void *value, size_t size,
|
|
+ int flags)
|
|
+{
|
|
+ struct au_sxattr arg = {
|
|
+ .type = AU_XATTR_SET,
|
|
+ .u.set = {
|
|
+ .name = name,
|
|
+ .value = value,
|
|
+ .size = size,
|
|
+ .flags = flags
|
|
+ },
|
|
+ };
|
|
+
|
|
+ return au_sxattr(dentry, inode, &arg);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_xattr_get(const struct xattr_handler *handler,
|
|
+ struct dentry *dentry, struct inode *inode,
|
|
+ const char *name, void *buffer, size_t size)
|
|
+{
|
|
+ return au_getxattr(dentry, inode, name, buffer, size);
|
|
+}
|
|
+
|
|
+static int au_xattr_set(const struct xattr_handler *handler,
|
|
+ struct dentry *dentry, struct inode *inode,
|
|
+ const char *name, const void *value, size_t size,
|
|
+ int flags)
|
|
+{
|
|
+ return au_setxattr(dentry, inode, name, value, size, flags);
|
|
+}
|
|
+
|
|
+static const struct xattr_handler au_xattr_handler = {
|
|
+ .name = "",
|
|
+ .prefix = "",
|
|
+ .get = au_xattr_get,
|
|
+ .set = au_xattr_set
|
|
+};
|
|
+
|
|
+static const struct xattr_handler *au_xattr_handlers[] = {
|
|
+#ifdef CONFIG_FS_POSIX_ACL
|
|
+ &posix_acl_access_xattr_handler,
|
|
+ &posix_acl_default_xattr_handler,
|
|
+#endif
|
|
+ &au_xattr_handler, /* must be last */
|
|
+ NULL
|
|
+};
|
|
+
|
|
+void au_xattr_init(struct super_block *sb)
|
|
+{
|
|
+ sb->s_xattr = au_xattr_handlers;
|
|
+}
|
|
diff --git a/fs/aufs/xino.c b/fs/aufs/xino.c
|
|
new file mode 100644
|
|
index 000000000000..b3152c0ce0b5
|
|
--- /dev/null
|
|
+++ b/fs/aufs/xino.c
|
|
@@ -0,0 +1,1966 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * external inode number translation table and bitmap
|
|
+ *
|
|
+ * things to consider
|
|
+ * - the lifetime
|
|
+ * + au_xino object
|
|
+ * + XINO files (xino, xib, xigen)
|
|
+ * + dynamic debugfs entries (xiN)
|
|
+ * + static debugfs entries (xib, xigen)
|
|
+ * + static sysfs entry (xi_path)
|
|
+ * - several entry points to handle them.
|
|
+ * + mount(2) without xino option (default)
|
|
+ * + mount(2) with xino option
|
|
+ * + mount(2) with noxino option
|
|
+ * + umount(2)
|
|
+ * + remount with add/del branches
|
|
+ * + remount with xino/noxino options
|
|
+ */
|
|
+
|
|
+#include <linux/seq_file.h>
|
|
+#include <linux/statfs.h>
|
|
+#include "aufs.h"
|
|
+
|
|
+static aufs_bindex_t sbr_find_shared(struct super_block *sb, aufs_bindex_t btop,
|
|
+ aufs_bindex_t bbot,
|
|
+ struct super_block *h_sb)
|
|
+{
|
|
+ /* todo: try binary-search if the branches are many */
|
|
+ for (; btop <= bbot; btop++)
|
|
+ if (h_sb == au_sbr_sb(sb, btop))
|
|
+ return btop;
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * find another branch who is on the same filesystem of the specified
|
|
+ * branch{@btgt}. search until @bbot.
|
|
+ */
|
|
+static aufs_bindex_t is_sb_shared(struct super_block *sb, aufs_bindex_t btgt,
|
|
+ aufs_bindex_t bbot)
|
|
+{
|
|
+ aufs_bindex_t bindex;
|
|
+ struct super_block *tgt_sb;
|
|
+
|
|
+ tgt_sb = au_sbr_sb(sb, btgt);
|
|
+ bindex = sbr_find_shared(sb, /*btop*/0, btgt - 1, tgt_sb);
|
|
+ if (bindex < 0)
|
|
+ bindex = sbr_find_shared(sb, btgt + 1, bbot, tgt_sb);
|
|
+
|
|
+ return bindex;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * stop unnecessary notify events at creating xino files
|
|
+ */
|
|
+
|
|
+aufs_bindex_t au_xi_root(struct super_block *sb, struct dentry *dentry)
|
|
+{
|
|
+ aufs_bindex_t bfound, bindex, bbot;
|
|
+ struct dentry *parent;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ bfound = -1;
|
|
+ parent = dentry->d_parent; /* safe d_parent access */
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ if (au_br_dentry(br) == parent) {
|
|
+ bfound = bindex;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ AuDbg("bfound b%d\n", bfound);
|
|
+ return bfound;
|
|
+}
|
|
+
|
|
+struct au_xino_lock_dir {
|
|
+ struct au_hinode *hdir;
|
|
+ struct dentry *parent;
|
|
+ struct inode *dir;
|
|
+};
|
|
+
|
|
+static struct dentry *au_dget_parent_lock(struct dentry *dentry,
|
|
+ unsigned int lsc)
|
|
+{
|
|
+ struct dentry *parent;
|
|
+ struct inode *dir;
|
|
+
|
|
+ parent = dget_parent(dentry);
|
|
+ dir = d_inode(parent);
|
|
+ inode_lock_nested(dir, lsc);
|
|
+#if 0 /* it should not happen */
|
|
+ spin_lock(&dentry->d_lock);
|
|
+ if (unlikely(dentry->d_parent != parent)) {
|
|
+ spin_unlock(&dentry->d_lock);
|
|
+ inode_unlock(dir);
|
|
+ dput(parent);
|
|
+ parent = NULL;
|
|
+ goto out;
|
|
+ }
|
|
+ spin_unlock(&dentry->d_lock);
|
|
+
|
|
+out:
|
|
+#endif
|
|
+ return parent;
|
|
+}
|
|
+
|
|
+static void au_xino_lock_dir(struct super_block *sb, struct path *xipath,
|
|
+ struct au_xino_lock_dir *ldir)
|
|
+{
|
|
+ aufs_bindex_t bindex;
|
|
+
|
|
+ ldir->hdir = NULL;
|
|
+ bindex = au_xi_root(sb, xipath->dentry);
|
|
+ if (bindex >= 0) {
|
|
+ /* rw branch root */
|
|
+ ldir->hdir = au_hi(d_inode(sb->s_root), bindex);
|
|
+ au_hn_inode_lock_nested(ldir->hdir, AuLsc_I_PARENT);
|
|
+ } else {
|
|
+ /* other */
|
|
+ ldir->parent = au_dget_parent_lock(xipath->dentry,
|
|
+ AuLsc_I_PARENT);
|
|
+ ldir->dir = d_inode(ldir->parent);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void au_xino_unlock_dir(struct au_xino_lock_dir *ldir)
|
|
+{
|
|
+ if (ldir->hdir)
|
|
+ au_hn_inode_unlock(ldir->hdir);
|
|
+ else {
|
|
+ inode_unlock(ldir->dir);
|
|
+ dput(ldir->parent);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * create and set a new xino file
|
|
+ */
|
|
+struct file *au_xino_create(struct super_block *sb, char *fpath, int silent,
|
|
+ int wbrtop)
|
|
+{
|
|
+ struct file *file;
|
|
+ struct dentry *h_parent, *d;
|
|
+ struct inode *h_dir, *inode;
|
|
+ int err;
|
|
+ static DEFINE_MUTEX(mtx);
|
|
+
|
|
+ /*
|
|
+ * 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.
|
|
+ */
|
|
+ if (!wbrtop)
|
|
+ mutex_lock(&mtx);
|
|
+ file = vfsub_filp_open(fpath, O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE
|
|
+ /* | __FMODE_NONOTIFY */,
|
|
+ 0666);
|
|
+ if (IS_ERR(file)) {
|
|
+ if (!wbrtop)
|
|
+ mutex_unlock(&mtx);
|
|
+ if (!silent)
|
|
+ pr_err("open %s(%ld)\n", fpath, PTR_ERR(file));
|
|
+ return file;
|
|
+ }
|
|
+
|
|
+ /* keep file count */
|
|
+ err = 0;
|
|
+ d = file->f_path.dentry;
|
|
+ h_parent = au_dget_parent_lock(d, AuLsc_I_PARENT);
|
|
+ if (!wbrtop)
|
|
+ mutex_unlock(&mtx);
|
|
+ /* mnt_want_write() is unnecessary here */
|
|
+ h_dir = d_inode(h_parent);
|
|
+ inode = file_inode(file);
|
|
+ /* no delegation since it is just created */
|
|
+ if (inode->i_nlink)
|
|
+ err = vfsub_unlink(h_dir, &file->f_path, /*delegated*/NULL,
|
|
+ /*force*/0);
|
|
+ inode_unlock(h_dir);
|
|
+ dput(h_parent);
|
|
+ if (unlikely(err)) {
|
|
+ if (!silent)
|
|
+ pr_err("unlink %s(%d)\n", fpath, err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = -EINVAL;
|
|
+ if (unlikely(sb == d->d_sb)) {
|
|
+ if (!silent)
|
|
+ pr_err("%s must be outside\n", fpath);
|
|
+ goto out;
|
|
+ }
|
|
+ if (unlikely(au_test_fs_bad_xino(d->d_sb))) {
|
|
+ if (!silent)
|
|
+ pr_err("xino doesn't support %s(%s)\n",
|
|
+ fpath, au_sbtype(d->d_sb));
|
|
+ goto out;
|
|
+ }
|
|
+ return file; /* success */
|
|
+
|
|
+out:
|
|
+ fput(file);
|
|
+ file = ERR_PTR(err);
|
|
+ return file;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * create a new xinofile at the same place/path as @base.
|
|
+ */
|
|
+struct file *au_xino_create2(struct super_block *sb, struct path *base,
|
|
+ struct file *copy_src)
|
|
+{
|
|
+ struct file *file;
|
|
+ struct dentry *dentry, *parent;
|
|
+ struct inode *dir, *delegated;
|
|
+ struct qstr *name;
|
|
+ struct path path;
|
|
+ int err, do_unlock;
|
|
+ struct au_xino_lock_dir ldir;
|
|
+
|
|
+ do_unlock = 1;
|
|
+ au_xino_lock_dir(sb, base, &ldir);
|
|
+ dentry = base->dentry;
|
|
+ parent = dentry->d_parent; /* dir inode is locked */
|
|
+ dir = d_inode(parent);
|
|
+ IMustLock(dir);
|
|
+
|
|
+ name = &dentry->d_name;
|
|
+ path.dentry = vfsub_lookup_one_len(name->name, parent, name->len);
|
|
+ if (IS_ERR(path.dentry)) {
|
|
+ file = (void *)path.dentry;
|
|
+ pr_err("%pd lookup err %ld\n", dentry, PTR_ERR(path.dentry));
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* no need to mnt_want_write() since we call dentry_open() later */
|
|
+ err = vfs_create(dir, path.dentry, 0666, NULL);
|
|
+ if (unlikely(err)) {
|
|
+ file = ERR_PTR(err);
|
|
+ pr_err("%pd create err %d\n", dentry, err);
|
|
+ goto out_dput;
|
|
+ }
|
|
+
|
|
+ path.mnt = base->mnt;
|
|
+ file = vfsub_dentry_open(&path,
|
|
+ O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE
|
|
+ /* | __FMODE_NONOTIFY */);
|
|
+ if (IS_ERR(file)) {
|
|
+ pr_err("%pd open err %ld\n", dentry, PTR_ERR(file));
|
|
+ goto out_dput;
|
|
+ }
|
|
+
|
|
+ delegated = NULL;
|
|
+ err = vfsub_unlink(dir, &file->f_path, &delegated, /*force*/0);
|
|
+ au_xino_unlock_dir(&ldir);
|
|
+ do_unlock = 0;
|
|
+ if (unlikely(err == -EWOULDBLOCK)) {
|
|
+ pr_warn("cannot retry for NFSv4 delegation"
|
|
+ " for an internal unlink\n");
|
|
+ iput(delegated);
|
|
+ }
|
|
+ if (unlikely(err)) {
|
|
+ pr_err("%pd unlink err %d\n", dentry, 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("%pd copy err %d\n", dentry, err);
|
|
+ goto out_fput;
|
|
+ }
|
|
+ }
|
|
+ goto out_dput; /* success */
|
|
+
|
|
+out_fput:
|
|
+ fput(file);
|
|
+ file = ERR_PTR(err);
|
|
+out_dput:
|
|
+ dput(path.dentry);
|
|
+out:
|
|
+ if (do_unlock)
|
|
+ au_xino_unlock_dir(&ldir);
|
|
+ return file;
|
|
+}
|
|
+
|
|
+struct file *au_xino_file1(struct au_xino *xi)
|
|
+{
|
|
+ struct file *file;
|
|
+ unsigned int u, nfile;
|
|
+
|
|
+ file = NULL;
|
|
+ nfile = xi->xi_nfile;
|
|
+ for (u = 0; u < nfile; u++) {
|
|
+ file = xi->xi_file[u];
|
|
+ if (file)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return file;
|
|
+}
|
|
+
|
|
+static int au_xino_file_set(struct au_xino *xi, int idx, struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ struct file *f;
|
|
+ void *p;
|
|
+
|
|
+ if (file)
|
|
+ get_file(file);
|
|
+
|
|
+ err = 0;
|
|
+ f = NULL;
|
|
+ if (idx < xi->xi_nfile) {
|
|
+ f = xi->xi_file[idx];
|
|
+ if (f)
|
|
+ fput(f);
|
|
+ } else {
|
|
+ p = au_kzrealloc(xi->xi_file,
|
|
+ sizeof(*xi->xi_file) * xi->xi_nfile,
|
|
+ sizeof(*xi->xi_file) * (idx + 1),
|
|
+ GFP_NOFS, /*may_shrink*/0);
|
|
+ if (p) {
|
|
+ MtxMustLock(&xi->xi_mtx);
|
|
+ xi->xi_file = p;
|
|
+ xi->xi_nfile = idx + 1;
|
|
+ } else {
|
|
+ err = -ENOMEM;
|
|
+ if (file)
|
|
+ fput(file);
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+ xi->xi_file[idx] = file;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * if @xinew->xi is not set, then create new xigen file.
|
|
+ */
|
|
+struct file *au_xi_new(struct super_block *sb, struct au_xi_new *xinew)
|
|
+{
|
|
+ struct file *file;
|
|
+ int err;
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ file = au_xino_create2(sb, xinew->base, xinew->copy_src);
|
|
+ if (IS_ERR(file)) {
|
|
+ err = PTR_ERR(file);
|
|
+ pr_err("%s[%d], err %d\n",
|
|
+ xinew->xi ? "xino" : "xigen",
|
|
+ xinew->idx, err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (xinew->xi)
|
|
+ err = au_xino_file_set(xinew->xi, xinew->idx, file);
|
|
+ else {
|
|
+ BUG();
|
|
+ /* todo: make xigen file an array */
|
|
+ /* err = au_xigen_file_set(sb, xinew->idx, file); */
|
|
+ }
|
|
+ fput(file);
|
|
+ if (unlikely(err))
|
|
+ file = ERR_PTR(err);
|
|
+
|
|
+out:
|
|
+ return file;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * truncate xino files
|
|
+ */
|
|
+static int au_xino_do_trunc(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ int idx, struct kstatfs *st)
|
|
+{
|
|
+ int err;
|
|
+ blkcnt_t blocks;
|
|
+ struct file *file, *new_xino;
|
|
+ struct au_xi_new xinew = {
|
|
+ .idx = idx
|
|
+ };
|
|
+
|
|
+ err = 0;
|
|
+ xinew.xi = au_sbr(sb, bindex)->br_xino;
|
|
+ file = au_xino_file(xinew.xi, idx);
|
|
+ if (!file)
|
|
+ goto out;
|
|
+
|
|
+ xinew.base = &file->f_path;
|
|
+ err = vfs_statfs(xinew.base, st);
|
|
+ if (unlikely(err)) {
|
|
+ AuErr1("statfs err %d, ignored\n", err);
|
|
+ err = 0;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ blocks = file_inode(file)->i_blocks;
|
|
+ pr_info("begin truncating xino(b%d-%d), ib%llu, %llu/%llu free blks\n",
|
|
+ bindex, idx, (u64)blocks, st->f_bfree, st->f_blocks);
|
|
+
|
|
+ xinew.copy_src = file;
|
|
+ new_xino = au_xi_new(sb, &xinew);
|
|
+ if (IS_ERR(new_xino)) {
|
|
+ err = PTR_ERR(new_xino);
|
|
+ pr_err("xino(b%d-%d), err %d, ignored\n", bindex, idx, err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = vfs_statfs(&new_xino->f_path, st);
|
|
+ if (!err)
|
|
+ pr_info("end truncating xino(b%d-%d), ib%llu, %llu/%llu free blks\n",
|
|
+ bindex, idx, (u64)file_inode(new_xino)->i_blocks,
|
|
+ st->f_bfree, st->f_blocks);
|
|
+ else {
|
|
+ AuErr1("statfs err %d, ignored\n", err);
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+int au_xino_trunc(struct super_block *sb, aufs_bindex_t bindex, int idx_begin)
|
|
+{
|
|
+ int err, i;
|
|
+ unsigned long jiffy;
|
|
+ aufs_bindex_t bbot;
|
|
+ struct kstatfs *st;
|
|
+ struct au_branch *br;
|
|
+ struct au_xino *xi;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ st = kmalloc(sizeof(*st), GFP_NOFS);
|
|
+ if (unlikely(!st))
|
|
+ goto out;
|
|
+
|
|
+ err = -EINVAL;
|
|
+ bbot = au_sbbot(sb);
|
|
+ if (unlikely(bindex < 0 || bbot < bindex))
|
|
+ goto out_st;
|
|
+
|
|
+ err = 0;
|
|
+ jiffy = jiffies;
|
|
+ br = au_sbr(sb, bindex);
|
|
+ xi = br->br_xino;
|
|
+ for (i = idx_begin; !err && i < xi->xi_nfile; i++)
|
|
+ err = au_xino_do_trunc(sb, bindex, i, st);
|
|
+ if (!err)
|
|
+ au_sbi(sb)->si_xino_jiffy = jiffy;
|
|
+
|
|
+out_st:
|
|
+ au_kfree_rcu(st);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct xino_do_trunc_args {
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ int idx;
|
|
+};
|
|
+
|
|
+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, idx;
|
|
+ aufs_bindex_t bindex;
|
|
+
|
|
+ err = 0;
|
|
+ sb = args->sb;
|
|
+ dir = d_inode(sb->s_root);
|
|
+ br = args->br;
|
|
+ idx = args->idx;
|
|
+
|
|
+ si_noflush_write_lock(sb);
|
|
+ ii_read_lock_parent(dir);
|
|
+ bindex = au_br_index(sb, br->br_id);
|
|
+ err = au_xino_trunc(sb, bindex, idx);
|
|
+ ii_read_unlock(dir);
|
|
+ if (unlikely(err))
|
|
+ pr_warn("err b%d, (%d)\n", bindex, err);
|
|
+ atomic_dec(&br->br_xino->xi_truncating);
|
|
+ au_lcnt_dec(&br->br_count);
|
|
+ si_write_unlock(sb);
|
|
+ au_nwt_done(&au_sbi(sb)->si_nowait);
|
|
+ au_kfree_rcu(args);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * returns the index in the xi_file array whose corresponding file is necessary
|
|
+ * to truncate, or -1 which means no need to truncate.
|
|
+ */
|
|
+static int xino_trunc_test(struct super_block *sb, struct au_branch *br)
|
|
+{
|
|
+ int err;
|
|
+ unsigned int u;
|
|
+ struct kstatfs st;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct au_xino *xi;
|
|
+ struct file *file;
|
|
+
|
|
+ /* todo: si_xino_expire and the ratio should be customizable */
|
|
+ sbinfo = au_sbi(sb);
|
|
+ if (time_before(jiffies,
|
|
+ sbinfo->si_xino_jiffy + sbinfo->si_xino_expire))
|
|
+ return -1;
|
|
+
|
|
+ /* truncation border */
|
|
+ xi = br->br_xino;
|
|
+ for (u = 0; u < xi->xi_nfile; u++) {
|
|
+ file = au_xino_file(xi, u);
|
|
+ if (!file)
|
|
+ continue;
|
|
+
|
|
+ err = vfs_statfs(&file->f_path, &st);
|
|
+ if (unlikely(err)) {
|
|
+ AuErr1("statfs err %d, ignored\n", err);
|
|
+ return -1;
|
|
+ }
|
|
+ if (div64_u64(st.f_bfree * 100, st.f_blocks)
|
|
+ >= AUFS_XINO_DEF_TRUNC)
|
|
+ return u;
|
|
+ }
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static void xino_try_trunc(struct super_block *sb, struct au_branch *br)
|
|
+{
|
|
+ int idx;
|
|
+ struct xino_do_trunc_args *args;
|
|
+ int wkq_err;
|
|
+
|
|
+ idx = xino_trunc_test(sb, br);
|
|
+ if (idx < 0)
|
|
+ return;
|
|
+
|
|
+ if (atomic_inc_return(&br->br_xino->xi_truncating) > 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;
|
|
+ }
|
|
+
|
|
+ au_lcnt_inc(&br->br_count);
|
|
+ args->sb = sb;
|
|
+ args->br = br;
|
|
+ args->idx = idx;
|
|
+ wkq_err = au_wkq_nowait(xino_do_trunc, args, sb, /*flags*/0);
|
|
+ if (!wkq_err)
|
|
+ return; /* success */
|
|
+
|
|
+ pr_err("wkq %d\n", wkq_err);
|
|
+ au_lcnt_dec(&br->br_count);
|
|
+ au_kfree_rcu(args);
|
|
+
|
|
+out:
|
|
+ atomic_dec(&br->br_xino->xi_truncating);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_xi_calc {
|
|
+ int idx;
|
|
+ loff_t pos;
|
|
+};
|
|
+
|
|
+static void au_xi_calc(struct super_block *sb, ino_t h_ino,
|
|
+ struct au_xi_calc *calc)
|
|
+{
|
|
+ loff_t maxent;
|
|
+
|
|
+ maxent = au_xi_maxent(sb);
|
|
+ calc->idx = div64_u64_rem(h_ino, maxent, &calc->pos);
|
|
+ calc->pos *= sizeof(ino_t);
|
|
+}
|
|
+
|
|
+static int au_xino_do_new_async(struct super_block *sb, struct au_branch *br,
|
|
+ struct au_xi_calc *calc)
|
|
+{
|
|
+ int err;
|
|
+ struct file *file;
|
|
+ struct au_xino *xi = br->br_xino;
|
|
+ struct au_xi_new xinew = {
|
|
+ .xi = xi
|
|
+ };
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ err = 0;
|
|
+ if (!xi)
|
|
+ goto out;
|
|
+
|
|
+ mutex_lock(&xi->xi_mtx);
|
|
+ file = au_xino_file(xi, calc->idx);
|
|
+ if (file)
|
|
+ goto out_mtx;
|
|
+
|
|
+ file = au_xino_file(xi, /*idx*/-1);
|
|
+ AuDebugOn(!file);
|
|
+ xinew.idx = calc->idx;
|
|
+ xinew.base = &file->f_path;
|
|
+ /* xinew.copy_src = NULL; */
|
|
+ file = au_xi_new(sb, &xinew);
|
|
+ if (IS_ERR(file))
|
|
+ err = PTR_ERR(file);
|
|
+
|
|
+out_mtx:
|
|
+ mutex_unlock(&xi->xi_mtx);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct au_xino_do_new_async_args {
|
|
+ struct super_block *sb;
|
|
+ struct au_branch *br;
|
|
+ struct au_xi_calc calc;
|
|
+ ino_t ino;
|
|
+};
|
|
+
|
|
+struct au_xi_writing {
|
|
+ struct hlist_bl_node node;
|
|
+ ino_t h_ino, ino;
|
|
+};
|
|
+
|
|
+static int au_xino_do_write(vfs_writef_t write, struct file *file,
|
|
+ struct au_xi_calc *calc, ino_t ino);
|
|
+
|
|
+static void au_xino_call_do_new_async(void *args)
|
|
+{
|
|
+ struct au_xino_do_new_async_args *a = args;
|
|
+ struct au_branch *br;
|
|
+ struct super_block *sb;
|
|
+ struct au_sbinfo *sbi;
|
|
+ struct inode *root;
|
|
+ struct file *file;
|
|
+ struct au_xi_writing *del, *p;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos;
|
|
+ int err;
|
|
+
|
|
+ br = a->br;
|
|
+ sb = a->sb;
|
|
+ sbi = au_sbi(sb);
|
|
+ si_noflush_read_lock(sb);
|
|
+ root = d_inode(sb->s_root);
|
|
+ ii_read_lock_child(root);
|
|
+ err = au_xino_do_new_async(sb, br, &a->calc);
|
|
+ if (unlikely(err)) {
|
|
+ AuIOErr("err %d\n", err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ file = au_xino_file(br->br_xino, a->calc.idx);
|
|
+ AuDebugOn(!file);
|
|
+ err = au_xino_do_write(sbi->si_xwrite, file, &a->calc, a->ino);
|
|
+ if (unlikely(err)) {
|
|
+ AuIOErr("err %d\n", err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ del = NULL;
|
|
+ hbl = &br->br_xino->xi_writing;
|
|
+ hlist_bl_lock(hbl);
|
|
+ au_hbl_for_each(pos, hbl) {
|
|
+ p = container_of(pos, struct au_xi_writing, node);
|
|
+ if (p->ino == a->ino) {
|
|
+ del = p;
|
|
+ hlist_bl_del(&p->node);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ hlist_bl_unlock(hbl);
|
|
+ au_kfree_rcu(del);
|
|
+
|
|
+out:
|
|
+ au_lcnt_dec(&br->br_count);
|
|
+ ii_read_unlock(root);
|
|
+ si_read_unlock(sb);
|
|
+ au_nwt_done(&sbi->si_nowait);
|
|
+ au_kfree_rcu(a);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * create a new xino file asynchronously
|
|
+ */
|
|
+static int au_xino_new_async(struct super_block *sb, struct au_branch *br,
|
|
+ struct au_xi_calc *calc, ino_t ino)
|
|
+{
|
|
+ int err;
|
|
+ struct au_xino_do_new_async_args *arg;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ arg = kmalloc(sizeof(*arg), GFP_NOFS);
|
|
+ if (unlikely(!arg))
|
|
+ goto out;
|
|
+
|
|
+ arg->sb = sb;
|
|
+ arg->br = br;
|
|
+ arg->calc = *calc;
|
|
+ arg->ino = ino;
|
|
+ au_lcnt_inc(&br->br_count);
|
|
+ err = au_wkq_nowait(au_xino_call_do_new_async, arg, sb, AuWkq_NEST);
|
|
+ if (unlikely(err)) {
|
|
+ pr_err("wkq %d\n", err);
|
|
+ au_lcnt_dec(&br->br_count);
|
|
+ au_kfree_rcu(arg);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * read @ino from xinofile for the specified branch{@sb, @bindex}
|
|
+ * at the position of @h_ino.
|
|
+ */
|
|
+int au_xino_read(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
|
|
+ ino_t *ino)
|
|
+{
|
|
+ int err;
|
|
+ ssize_t sz;
|
|
+ struct au_xi_calc calc;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct file *file;
|
|
+ struct au_xino *xi;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos;
|
|
+ struct au_xi_writing *p;
|
|
+
|
|
+ *ino = 0;
|
|
+ if (!au_opt_test(au_mntflags(sb), XINO))
|
|
+ return 0; /* no xino */
|
|
+
|
|
+ err = 0;
|
|
+ au_xi_calc(sb, h_ino, &calc);
|
|
+ xi = au_sbr(sb, bindex)->br_xino;
|
|
+ file = au_xino_file(xi, calc.idx);
|
|
+ if (!file) {
|
|
+ hbl = &xi->xi_writing;
|
|
+ hlist_bl_lock(hbl);
|
|
+ au_hbl_for_each(pos, hbl) {
|
|
+ p = container_of(pos, struct au_xi_writing, node);
|
|
+ if (p->h_ino == h_ino) {
|
|
+ AuDbg("hi%llu, i%llu, found\n",
|
|
+ (u64)p->h_ino, (u64)p->ino);
|
|
+ *ino = p->ino;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ hlist_bl_unlock(hbl);
|
|
+ return 0;
|
|
+ } else if (vfsub_f_size_read(file) < calc.pos + sizeof(*ino))
|
|
+ return 0; /* no xino */
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ sz = xino_fread(sbinfo->si_xread, file, ino, sizeof(*ino), &calc.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;
|
|
+}
|
|
+
|
|
+static int au_xino_do_write(vfs_writef_t write, struct file *file,
|
|
+ struct au_xi_calc *calc, ino_t ino)
|
|
+{
|
|
+ ssize_t sz;
|
|
+
|
|
+ sz = xino_fwrite(write, file, &ino, sizeof(ino), &calc->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_xi_calc calc;
|
|
+ struct file *file;
|
|
+ struct au_branch *br;
|
|
+ struct au_xino *xi;
|
|
+ struct au_xi_writing *p;
|
|
+
|
|
+ SiMustAnyLock(sb);
|
|
+
|
|
+ mnt_flags = au_mntflags(sb);
|
|
+ if (!au_opt_test(mnt_flags, XINO))
|
|
+ return 0;
|
|
+
|
|
+ au_xi_calc(sb, h_ino, &calc);
|
|
+ br = au_sbr(sb, bindex);
|
|
+ xi = br->br_xino;
|
|
+ file = au_xino_file(xi, calc.idx);
|
|
+ if (!file) {
|
|
+ /* store the inum pair into the list */
|
|
+ p = kmalloc(sizeof(*p), GFP_NOFS | __GFP_NOFAIL);
|
|
+ p->h_ino = h_ino;
|
|
+ p->ino = ino;
|
|
+ au_hbl_add(&p->node, &xi->xi_writing);
|
|
+
|
|
+ /* create and write a new xino file asynchronously */
|
|
+ err = au_xino_new_async(sb, br, &calc, ino);
|
|
+ if (!err)
|
|
+ return 0; /* success */
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = au_xino_do_write(au_sbi(sb)->si_xwrite, file, &calc, ino);
|
|
+ if (!err) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ 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 */
|
|
+ }
|
|
+
|
|
+out:
|
|
+ AuIOErr("write failed (%d)\n", err);
|
|
+ return -EIO;
|
|
+}
|
|
+
|
|
+static ssize_t xino_fread_wkq(vfs_readf_t func, struct file *file, void *buf,
|
|
+ size_t size, loff_t *pos);
|
|
+
|
|
+/* todo: unnecessary to support mmap_sem since kernel-space? */
|
|
+ssize_t xino_fread(vfs_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;
|
|
+ int i;
|
|
+ const int prevent_endless = 10;
|
|
+
|
|
+ i = 0;
|
|
+ buf.k = kbuf;
|
|
+ oldfs = get_fs();
|
|
+ set_fs(KERNEL_DS);
|
|
+ do {
|
|
+ err = func(file, buf.u, size, pos);
|
|
+ if (err == -EINTR
|
|
+ && !au_wkq_test()
|
|
+ && fatal_signal_pending(current)) {
|
|
+ set_fs(oldfs);
|
|
+ err = xino_fread_wkq(func, file, kbuf, size, pos);
|
|
+ BUG_ON(err == -EINTR);
|
|
+ oldfs = get_fs();
|
|
+ set_fs(KERNEL_DS);
|
|
+ }
|
|
+ } while (i++ < prevent_endless
|
|
+ && (err == -EAGAIN || err == -EINTR));
|
|
+ set_fs(oldfs);
|
|
+
|
|
+#if 0 /* reserved for future use */
|
|
+ if (err > 0)
|
|
+ fsnotify_access(file->f_path.dentry);
|
|
+#endif
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct xino_fread_args {
|
|
+ ssize_t *errp;
|
|
+ vfs_readf_t func;
|
|
+ struct file *file;
|
|
+ void *buf;
|
|
+ size_t size;
|
|
+ loff_t *pos;
|
|
+};
|
|
+
|
|
+static void call_xino_fread(void *args)
|
|
+{
|
|
+ struct xino_fread_args *a = args;
|
|
+ *a->errp = xino_fread(a->func, a->file, a->buf, a->size, a->pos);
|
|
+}
|
|
+
|
|
+static ssize_t xino_fread_wkq(vfs_readf_t func, struct file *file, void *buf,
|
|
+ size_t size, loff_t *pos)
|
|
+{
|
|
+ ssize_t err;
|
|
+ int wkq_err;
|
|
+ struct xino_fread_args args = {
|
|
+ .errp = &err,
|
|
+ .func = func,
|
|
+ .file = file,
|
|
+ .buf = buf,
|
|
+ .size = size,
|
|
+ .pos = pos
|
|
+ };
|
|
+
|
|
+ wkq_err = au_wkq_wait(call_xino_fread, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static ssize_t xino_fwrite_wkq(vfs_writef_t func, struct file *file, void *buf,
|
|
+ size_t size, loff_t *pos);
|
|
+
|
|
+static ssize_t do_xino_fwrite(vfs_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;
|
|
+ int i;
|
|
+ const int prevent_endless = 10;
|
|
+
|
|
+ i = 0;
|
|
+ buf.k = kbuf;
|
|
+ oldfs = get_fs();
|
|
+ set_fs(KERNEL_DS);
|
|
+ do {
|
|
+ err = func(file, buf.u, size, pos);
|
|
+ if (err == -EINTR
|
|
+ && !au_wkq_test()
|
|
+ && fatal_signal_pending(current)) {
|
|
+ set_fs(oldfs);
|
|
+ err = xino_fwrite_wkq(func, file, kbuf, size, pos);
|
|
+ BUG_ON(err == -EINTR);
|
|
+ oldfs = get_fs();
|
|
+ set_fs(KERNEL_DS);
|
|
+ }
|
|
+ } while (i++ < prevent_endless
|
|
+ && (err == -EAGAIN || err == -EINTR));
|
|
+ set_fs(oldfs);
|
|
+
|
|
+#if 0 /* reserved for future use */
|
|
+ if (err > 0)
|
|
+ fsnotify_modify(file->f_path.dentry);
|
|
+#endif
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+struct do_xino_fwrite_args {
|
|
+ ssize_t *errp;
|
|
+ vfs_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);
|
|
+}
|
|
+
|
|
+static ssize_t xino_fwrite_wkq(vfs_writef_t func, struct file *file, void *buf,
|
|
+ size_t size, loff_t *pos)
|
|
+{
|
|
+ ssize_t err;
|
|
+ int wkq_err;
|
|
+ struct do_xino_fwrite_args args = {
|
|
+ .errp = &err,
|
|
+ .func = func,
|
|
+ .file = file,
|
|
+ .buf = buf,
|
|
+ .size = size,
|
|
+ .pos = pos
|
|
+ };
|
|
+
|
|
+ /*
|
|
+ * it breaks RLIMIT_FSIZE and normal user's limit,
|
|
+ * users should care about quota and real 'filesystem full.'
|
|
+ */
|
|
+ wkq_err = au_wkq_wait(call_do_xino_fwrite, &args);
|
|
+ if (unlikely(wkq_err))
|
|
+ err = wkq_err;
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+ssize_t xino_fwrite(vfs_writef_t func, struct file *file, void *buf,
|
|
+ size_t size, loff_t *pos)
|
|
+{
|
|
+ ssize_t err;
|
|
+
|
|
+ if (rlimit(RLIMIT_FSIZE) == RLIM_INFINITY) {
|
|
+ lockdep_off();
|
|
+ err = do_xino_fwrite(func, file, buf, size, pos);
|
|
+ lockdep_on();
|
|
+ } else {
|
|
+ lockdep_off();
|
|
+ err = xino_fwrite_wkq(func, file, buf, size, pos);
|
|
+ lockdep_on();
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * 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);
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * truncate 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;
|
|
+ vfs_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, i;
|
|
+ unsigned int nfile;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ void *page;
|
|
+ struct au_branch *br;
|
|
+ struct au_xino *xi;
|
|
+ struct file *file;
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ page = (void *)__get_free_page(GFP_NOFS);
|
|
+ if (unlikely(!page))
|
|
+ goto out;
|
|
+
|
|
+ err = 0;
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; !err && bindex <= bbot; bindex++)
|
|
+ if (!bindex || is_sb_shared(sb, bindex, bindex - 1) < 0) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ xi = br->br_xino;
|
|
+ nfile = xi->xi_nfile;
|
|
+ for (i = 0; i < nfile; i++) {
|
|
+ file = au_xino_file(xi, i);
|
|
+ if (file)
|
|
+ err = do_xib_restore(sb, file, page);
|
|
+ }
|
|
+ } else
|
|
+ AuDbg("skip shared 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_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;
|
|
+
|
|
+ file = au_xino_create2(sb, &sbinfo->si_xib->f_path, NULL);
|
|
+ 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;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct au_xino *au_xino_alloc(unsigned int nfile)
|
|
+{
|
|
+ struct au_xino *xi;
|
|
+
|
|
+ xi = kzalloc(sizeof(*xi), GFP_NOFS);
|
|
+ if (unlikely(!xi))
|
|
+ goto out;
|
|
+ xi->xi_nfile = nfile;
|
|
+ xi->xi_file = kcalloc(nfile, sizeof(*xi->xi_file), GFP_NOFS);
|
|
+ if (unlikely(!xi->xi_file))
|
|
+ goto out_free;
|
|
+
|
|
+ xi->xi_nondir.total = 8; /* initial size */
|
|
+ xi->xi_nondir.array = kcalloc(xi->xi_nondir.total, sizeof(ino_t),
|
|
+ GFP_NOFS);
|
|
+ if (unlikely(!xi->xi_nondir.array))
|
|
+ goto out_file;
|
|
+
|
|
+ spin_lock_init(&xi->xi_nondir.spin);
|
|
+ init_waitqueue_head(&xi->xi_nondir.wqh);
|
|
+ mutex_init(&xi->xi_mtx);
|
|
+ INIT_HLIST_BL_HEAD(&xi->xi_writing);
|
|
+ atomic_set(&xi->xi_truncating, 0);
|
|
+ kref_init(&xi->xi_kref);
|
|
+ goto out; /* success */
|
|
+
|
|
+out_file:
|
|
+ au_kfree_try_rcu(xi->xi_file);
|
|
+out_free:
|
|
+ au_kfree_rcu(xi);
|
|
+ xi = NULL;
|
|
+out:
|
|
+ return xi;
|
|
+}
|
|
+
|
|
+static int au_xino_init(struct au_branch *br, int idx, struct file *file)
|
|
+{
|
|
+ int err;
|
|
+ struct au_xino *xi;
|
|
+
|
|
+ err = 0;
|
|
+ xi = au_xino_alloc(idx + 1);
|
|
+ if (unlikely(!xi)) {
|
|
+ err = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (file)
|
|
+ get_file(file);
|
|
+ xi->xi_file[idx] = file;
|
|
+ AuDebugOn(br->br_xino);
|
|
+ br->br_xino = xi;
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void au_xino_release(struct kref *kref)
|
|
+{
|
|
+ struct au_xino *xi;
|
|
+ int i;
|
|
+ unsigned long ul;
|
|
+ struct hlist_bl_head *hbl;
|
|
+ struct hlist_bl_node *pos, *n;
|
|
+ struct au_xi_writing *p;
|
|
+
|
|
+ xi = container_of(kref, struct au_xino, xi_kref);
|
|
+ for (i = 0; i < xi->xi_nfile; i++)
|
|
+ if (xi->xi_file[i])
|
|
+ fput(xi->xi_file[i]);
|
|
+ for (i = xi->xi_nondir.total - 1; i >= 0; i--)
|
|
+ AuDebugOn(xi->xi_nondir.array[i]);
|
|
+ mutex_destroy(&xi->xi_mtx);
|
|
+ hbl = &xi->xi_writing;
|
|
+ ul = au_hbl_count(hbl);
|
|
+ if (unlikely(ul)) {
|
|
+ pr_warn("xi_writing %lu\n", ul);
|
|
+ hlist_bl_lock(hbl);
|
|
+ hlist_bl_for_each_entry_safe(p, pos, n, hbl, node) {
|
|
+ hlist_bl_del(&p->node);
|
|
+ /* kmemleak reported au_kfree_rcu() doesn't free it */
|
|
+ kfree(p);
|
|
+ }
|
|
+ hlist_bl_unlock(hbl);
|
|
+ }
|
|
+ au_kfree_try_rcu(xi->xi_file);
|
|
+ au_kfree_try_rcu(xi->xi_nondir.array);
|
|
+ au_kfree_rcu(xi);
|
|
+}
|
|
+
|
|
+int au_xino_put(struct au_branch *br)
|
|
+{
|
|
+ int ret;
|
|
+ struct au_xino *xi;
|
|
+
|
|
+ ret = 0;
|
|
+ xi = br->br_xino;
|
|
+ if (xi) {
|
|
+ br->br_xino = NULL;
|
|
+ ret = kref_put(&xi->xi_kref, au_xino_release);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * xino mount option handlers
|
|
+ */
|
|
+
|
|
+/* xino bitmap */
|
|
+static void xino_clear_xib(struct super_block *sb)
|
|
+{
|
|
+ struct au_sbinfo *sbinfo;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ /* unnecessary to clear sbinfo->si_xread and ->si_xwrite */
|
|
+ if (sbinfo->si_xib)
|
|
+ fput(sbinfo->si_xib);
|
|
+ sbinfo->si_xib = NULL;
|
|
+ if (sbinfo->si_xib_buf)
|
|
+ free_page((unsigned long)sbinfo->si_xib_buf);
|
|
+ sbinfo->si_xib_buf = NULL;
|
|
+}
|
|
+
|
|
+static int au_xino_set_xib(struct super_block *sb, struct path *path)
|
|
+{
|
|
+ int err;
|
|
+ loff_t pos;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct file *file;
|
|
+ struct super_block *xi_sb;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ sbinfo = au_sbi(sb);
|
|
+ file = au_xino_create2(sb, path, 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 = vfs_readf(file);
|
|
+ sbinfo->si_xwrite = vfs_writef(file);
|
|
+ xi_sb = file_inode(file)->i_sb;
|
|
+ sbinfo->si_ximaxent = xi_sb->s_maxbytes;
|
|
+ if (unlikely(sbinfo->si_ximaxent < PAGE_SIZE)) {
|
|
+ err = -EIO;
|
|
+ pr_err("s_maxbytes(%llu) on %s is too small\n",
|
|
+ (u64)sbinfo->si_ximaxent, au_sbtype(xi_sb));
|
|
+ goto out_unset;
|
|
+ }
|
|
+ sbinfo->si_ximaxent /= sizeof(ino_t);
|
|
+
|
|
+ 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:
|
|
+ if (sbinfo->si_xib_buf)
|
|
+ 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;
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* xino for each branch */
|
|
+static void xino_clear_br(struct super_block *sb)
|
|
+{
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_branch *br;
|
|
+
|
|
+ bbot = au_sbbot(sb);
|
|
+ for (bindex = 0; bindex <= bbot; bindex++) {
|
|
+ br = au_sbr(sb, bindex);
|
|
+ AuDebugOn(!br);
|
|
+ au_xino_put(br);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void au_xino_set_br_shared(struct super_block *sb, struct au_branch *br,
|
|
+ aufs_bindex_t bshared)
|
|
+{
|
|
+ struct au_branch *brshared;
|
|
+
|
|
+ brshared = au_sbr(sb, bshared);
|
|
+ AuDebugOn(!brshared->br_xino);
|
|
+ AuDebugOn(!brshared->br_xino->xi_file);
|
|
+ if (br->br_xino != brshared->br_xino) {
|
|
+ au_xino_get(brshared);
|
|
+ au_xino_put(br);
|
|
+ br->br_xino = brshared->br_xino;
|
|
+ }
|
|
+}
|
|
+
|
|
+struct au_xino_do_set_br {
|
|
+ vfs_writef_t writef;
|
|
+ struct au_branch *br;
|
|
+ ino_t h_ino;
|
|
+ aufs_bindex_t bshared;
|
|
+};
|
|
+
|
|
+static int au_xino_do_set_br(struct super_block *sb, struct path *path,
|
|
+ struct au_xino_do_set_br *args)
|
|
+{
|
|
+ int err;
|
|
+ struct au_xi_calc calc;
|
|
+ struct file *file;
|
|
+ struct au_branch *br;
|
|
+ struct au_xi_new xinew = {
|
|
+ .base = path
|
|
+ };
|
|
+
|
|
+ br = args->br;
|
|
+ xinew.xi = br->br_xino;
|
|
+ au_xi_calc(sb, args->h_ino, &calc);
|
|
+ xinew.copy_src = au_xino_file(xinew.xi, calc.idx);
|
|
+ if (args->bshared >= 0)
|
|
+ /* shared xino */
|
|
+ au_xino_set_br_shared(sb, br, args->bshared);
|
|
+ else if (!xinew.xi) {
|
|
+ /* new xino */
|
|
+ err = au_xino_init(br, calc.idx, xinew.copy_src);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* force re-creating */
|
|
+ xinew.xi = br->br_xino;
|
|
+ xinew.idx = calc.idx;
|
|
+ mutex_lock(&xinew.xi->xi_mtx);
|
|
+ file = au_xi_new(sb, &xinew);
|
|
+ mutex_unlock(&xinew.xi->xi_mtx);
|
|
+ err = PTR_ERR(file);
|
|
+ if (IS_ERR(file))
|
|
+ goto out;
|
|
+ AuDebugOn(!file);
|
|
+
|
|
+ err = au_xino_do_write(args->writef, file, &calc, AUFS_ROOT_INO);
|
|
+ if (unlikely(err))
|
|
+ au_xino_put(br);
|
|
+
|
|
+out:
|
|
+ AuTraceErr(err);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int au_xino_set_br(struct super_block *sb, struct path *path)
|
|
+{
|
|
+ int err;
|
|
+ aufs_bindex_t bindex, bbot;
|
|
+ struct au_xino_do_set_br args;
|
|
+ struct inode *inode;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ bbot = au_sbbot(sb);
|
|
+ inode = d_inode(sb->s_root);
|
|
+ args.writef = au_sbi(sb)->si_xwrite;
|
|
+ for (bindex = 0; bindex <= bbot; bindex++) {
|
|
+ args.h_ino = au_h_iptr(inode, bindex)->i_ino;
|
|
+ args.br = au_sbr(sb, bindex);
|
|
+ args.bshared = is_sb_shared(sb, bindex, bindex - 1);
|
|
+ err = au_xino_do_set_br(sb, path, &args);
|
|
+ if (unlikely(err))
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ AuTraceErr(err);
|
|
+ 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);
|
|
+ dbgaufs_brs_del(sb, 0);
|
|
+ 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 *xiopt, int remount)
|
|
+{
|
|
+ int err, skip;
|
|
+ struct dentry *dentry, *parent, *cur_dentry, *cur_parent;
|
|
+ struct qstr *dname, *cur_name;
|
|
+ struct file *cur_xino;
|
|
+ struct au_sbinfo *sbinfo;
|
|
+ struct path *path, *cur_path;
|
|
+
|
|
+ SiMustWriteLock(sb);
|
|
+
|
|
+ err = 0;
|
|
+ sbinfo = au_sbi(sb);
|
|
+ path = &xiopt->file->f_path;
|
|
+ dentry = path->dentry;
|
|
+ parent = dget_parent(dentry);
|
|
+ if (remount) {
|
|
+ skip = 0;
|
|
+ cur_xino = sbinfo->si_xib;
|
|
+ if (cur_xino) {
|
|
+ cur_path = &cur_xino->f_path;
|
|
+ cur_dentry = cur_path->dentry;
|
|
+ cur_parent = dget_parent(cur_dentry);
|
|
+ cur_name = &cur_dentry->d_name;
|
|
+ dname = &dentry->d_name;
|
|
+ skip = (cur_parent == parent
|
|
+ && au_qstreq(dname, cur_name));
|
|
+ dput(cur_parent);
|
|
+ }
|
|
+ if (skip)
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ au_opt_set(sbinfo->si_mntflags, XINO);
|
|
+ err = au_xino_set_xib(sb, path);
|
|
+ /* si_x{read,write} are set */
|
|
+ if (!err)
|
|
+ err = au_xigen_set(sb, path);
|
|
+ if (!err)
|
|
+ err = au_xino_set_br(sb, path);
|
|
+ if (!err) {
|
|
+ dbgaufs_brs_add(sb, 0, /*topdown*/1);
|
|
+ goto out; /* success */
|
|
+ }
|
|
+
|
|
+ /* reset all */
|
|
+ AuIOErr("failed setting xino(%d).\n", err);
|
|
+ au_xino_clr(sb);
|
|
+
|
|
+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 bbot, bindex, bwr;
|
|
+
|
|
+ br = NULL;
|
|
+ bbot = au_sbbot(sb);
|
|
+ bwr = -1;
|
|
+ for (bindex = 0; bindex <= bbot; 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, /*wbrtop*/1);
|
|
+ }
|
|
+ free_page((unsigned long)page);
|
|
+ } else {
|
|
+ file = au_xino_create(sb, AUFS_XINO_DEFPATH, /*silent*/0,
|
|
+ /*wbrtop*/0);
|
|
+ if (IS_ERR(file))
|
|
+ goto out;
|
|
+ h_sb = file->f_path.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);
|
|
+ }
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return file;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * 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 found then share the xinofile with another branch.
|
|
+ */
|
|
+int au_xino_init_br(struct super_block *sb, struct au_branch *br, ino_t h_ino,
|
|
+ struct path *base)
|
|
+{
|
|
+ int err;
|
|
+ struct au_xino_do_set_br args = {
|
|
+ .h_ino = h_ino,
|
|
+ .br = br
|
|
+ };
|
|
+
|
|
+ args.writef = au_sbi(sb)->si_xwrite;
|
|
+ args.bshared = sbr_find_shared(sb, /*btop*/0, au_sbbot(sb),
|
|
+ au_br_sb(br));
|
|
+ err = au_xino_do_set_br(sb, base, &args);
|
|
+ if (unlikely(err))
|
|
+ au_xino_put(br);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * 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;
|
|
+}
|
|
+
|
|
+/* 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, bbot, 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;
|
|
+ vfs_writef_t xwrite;
|
|
+ struct au_xi_calc calc;
|
|
+ struct file *file;
|
|
+
|
|
+ AuDebugOn(au_is_bad_inode(inode));
|
|
+
|
|
+ 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);
|
|
+ bindex = iinfo->ii_btop;
|
|
+ if (bindex < 0)
|
|
+ return;
|
|
+
|
|
+ xwrite = au_sbi(sb)->si_xwrite;
|
|
+ try_trunc = !!au_opt_test(mnt_flags, TRUNC_XINO);
|
|
+ hi = au_hinode(iinfo, bindex);
|
|
+ bbot = iinfo->ii_bbot;
|
|
+ for (; bindex <= bbot; 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);
|
|
+ au_xi_calc(sb, h_inode->i_ino, &calc);
|
|
+ file = au_xino_file(br->br_xino, calc.idx);
|
|
+ if (IS_ERR_OR_NULL(file))
|
|
+ continue;
|
|
+
|
|
+ err = au_xino_do_write(xwrite, file, &calc, /*ino*/0);
|
|
+ if (!err && try_trunc
|
|
+ && au_test_fs_trunc_xino(au_br_sb(br)))
|
|
+ xino_try_trunc(sb, br);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+static int au_xinondir_find(struct au_xino *xi, ino_t h_ino)
|
|
+{
|
|
+ int found, total, i;
|
|
+
|
|
+ found = -1;
|
|
+ total = xi->xi_nondir.total;
|
|
+ for (i = 0; i < total; i++) {
|
|
+ if (xi->xi_nondir.array[i] != h_ino)
|
|
+ continue;
|
|
+ found = i;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return found;
|
|
+}
|
|
+
|
|
+static int au_xinondir_expand(struct au_xino *xi)
|
|
+{
|
|
+ int err, sz;
|
|
+ ino_t *p;
|
|
+
|
|
+ BUILD_BUG_ON(KMALLOC_MAX_SIZE > INT_MAX);
|
|
+
|
|
+ err = -ENOMEM;
|
|
+ sz = xi->xi_nondir.total * sizeof(ino_t);
|
|
+ if (unlikely(sz > KMALLOC_MAX_SIZE / 2))
|
|
+ goto out;
|
|
+ p = au_kzrealloc(xi->xi_nondir.array, sz, sz << 1, GFP_ATOMIC,
|
|
+ /*may_shrink*/0);
|
|
+ if (p) {
|
|
+ xi->xi_nondir.array = p;
|
|
+ xi->xi_nondir.total <<= 1;
|
|
+ AuDbg("xi_nondir.total %d\n", xi->xi_nondir.total);
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void au_xinondir_leave(struct super_block *sb, aufs_bindex_t bindex,
|
|
+ ino_t h_ino, int idx)
|
|
+{
|
|
+ struct au_xino *xi;
|
|
+
|
|
+ AuDebugOn(!au_opt_test(au_mntflags(sb), XINO));
|
|
+ xi = au_sbr(sb, bindex)->br_xino;
|
|
+ AuDebugOn(idx < 0 || xi->xi_nondir.total <= idx);
|
|
+
|
|
+ spin_lock(&xi->xi_nondir.spin);
|
|
+ AuDebugOn(xi->xi_nondir.array[idx] != h_ino);
|
|
+ xi->xi_nondir.array[idx] = 0;
|
|
+ spin_unlock(&xi->xi_nondir.spin);
|
|
+ wake_up_all(&xi->xi_nondir.wqh);
|
|
+}
|
|
+
|
|
+int au_xinondir_enter(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
|
|
+ int *idx)
|
|
+{
|
|
+ int err, found, empty;
|
|
+ struct au_xino *xi;
|
|
+
|
|
+ err = 0;
|
|
+ *idx = -1;
|
|
+ if (!au_opt_test(au_mntflags(sb), XINO))
|
|
+ goto out; /* no xino */
|
|
+
|
|
+ xi = au_sbr(sb, bindex)->br_xino;
|
|
+
|
|
+again:
|
|
+ spin_lock(&xi->xi_nondir.spin);
|
|
+ found = au_xinondir_find(xi, h_ino);
|
|
+ if (found == -1) {
|
|
+ empty = au_xinondir_find(xi, /*h_ino*/0);
|
|
+ if (empty == -1) {
|
|
+ empty = xi->xi_nondir.total;
|
|
+ err = au_xinondir_expand(xi);
|
|
+ if (unlikely(err))
|
|
+ goto out_unlock;
|
|
+ }
|
|
+ xi->xi_nondir.array[empty] = h_ino;
|
|
+ *idx = empty;
|
|
+ } else {
|
|
+ spin_unlock(&xi->xi_nondir.spin);
|
|
+ wait_event(xi->xi_nondir.wqh,
|
|
+ xi->xi_nondir.array[found] != h_ino);
|
|
+ goto again;
|
|
+ }
|
|
+
|
|
+out_unlock:
|
|
+ spin_unlock(&xi->xi_nondir.spin);
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+int au_xino_path(struct seq_file *seq, struct file *file)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = au_seq_path(seq, &file->f_path);
|
|
+ if (unlikely(err))
|
|
+ goto out;
|
|
+
|
|
+#define Deleted "\\040(deleted)"
|
|
+ seq->count -= sizeof(Deleted) - 1;
|
|
+ AuDebugOn(memcmp(seq->buf + seq->count, Deleted,
|
|
+ sizeof(Deleted) - 1));
|
|
+#undef Deleted
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
diff --git a/fs/dcache.c b/fs/dcache.c
|
|
index ea0485861d93..30dec552278d 100644
|
|
--- a/fs/dcache.c
|
|
+++ b/fs/dcache.c
|
|
@@ -1285,7 +1285,7 @@ enum d_walk_ret {
|
|
*
|
|
* The @enter() callbacks are called with d_lock held.
|
|
*/
|
|
-static void d_walk(struct dentry *parent, void *data,
|
|
+void d_walk(struct dentry *parent, void *data,
|
|
enum d_walk_ret (*enter)(void *, struct dentry *))
|
|
{
|
|
struct dentry *this_parent;
|
|
@@ -1390,6 +1390,7 @@ static void d_walk(struct dentry *parent, void *data,
|
|
seq = 1;
|
|
goto again;
|
|
}
|
|
+EXPORT_SYMBOL_GPL(d_walk);
|
|
|
|
struct check_mount {
|
|
struct vfsmount *mnt;
|
|
@@ -2935,6 +2936,7 @@ void d_exchange(struct dentry *dentry1, struct dentry *dentry2)
|
|
|
|
write_sequnlock(&rename_lock);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(d_exchange);
|
|
|
|
/**
|
|
* d_ancestor - search for an ancestor
|
|
diff --git a/fs/exec.c b/fs/exec.c
|
|
index 547a2390baf5..18d51d0face6 100644
|
|
--- a/fs/exec.c
|
|
+++ b/fs/exec.c
|
|
@@ -113,6 +113,7 @@ bool path_noexec(const struct path *path)
|
|
return (path->mnt->mnt_flags & MNT_NOEXEC) ||
|
|
(path->mnt->mnt_sb->s_iflags & SB_I_NOEXEC);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(path_noexec);
|
|
|
|
#ifdef CONFIG_USELIB
|
|
/*
|
|
diff --git a/fs/fcntl.c b/fs/fcntl.c
|
|
index 19ac5baad50f..b18afdf81e76 100644
|
|
--- a/fs/fcntl.c
|
|
+++ b/fs/fcntl.c
|
|
@@ -32,7 +32,7 @@
|
|
|
|
#define SETFL_MASK (O_APPEND | O_NONBLOCK | O_NDELAY | O_DIRECT | O_NOATIME)
|
|
|
|
-static int setfl(int fd, struct file * filp, unsigned long arg)
|
|
+int setfl(int fd, struct file *filp, unsigned long arg)
|
|
{
|
|
struct inode * inode = file_inode(filp);
|
|
int error = 0;
|
|
@@ -63,6 +63,8 @@ static int setfl(int fd, struct file * filp, unsigned long arg)
|
|
|
|
if (filp->f_op->check_flags)
|
|
error = filp->f_op->check_flags(arg);
|
|
+ if (!error && filp->f_op->setfl)
|
|
+ error = filp->f_op->setfl(filp, arg);
|
|
if (error)
|
|
return error;
|
|
|
|
@@ -83,6 +85,7 @@ static int setfl(int fd, struct file * filp, unsigned long arg)
|
|
out:
|
|
return error;
|
|
}
|
|
+EXPORT_SYMBOL_GPL(setfl);
|
|
|
|
static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,
|
|
int force)
|
|
diff --git a/fs/file_table.c b/fs/file_table.c
|
|
index 709ada3151da..27a3e3c9f2a8 100644
|
|
--- a/fs/file_table.c
|
|
+++ b/fs/file_table.c
|
|
@@ -162,6 +162,7 @@ struct file *alloc_empty_file(int flags, const struct cred *cred)
|
|
}
|
|
return ERR_PTR(-ENFILE);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(alloc_empty_file);
|
|
|
|
/*
|
|
* Variant of alloc_empty_file() that doesn't check and modify nr_files.
|
|
@@ -376,6 +377,7 @@ void __fput_sync(struct file *file)
|
|
}
|
|
|
|
EXPORT_SYMBOL(fput);
|
|
+EXPORT_SYMBOL_GPL(__fput_sync);
|
|
|
|
void __init files_init(void)
|
|
{
|
|
diff --git a/fs/inode.c b/fs/inode.c
|
|
index 9d78c37b00b8..9f23bc13f59f 100644
|
|
--- a/fs/inode.c
|
|
+++ b/fs/inode.c
|
|
@@ -896,6 +896,8 @@ unsigned int get_next_ino(void)
|
|
unsigned int *p = &get_cpu_var(last_ino);
|
|
unsigned int res = *p;
|
|
|
|
+start:
|
|
+
|
|
#ifdef CONFIG_SMP
|
|
if (unlikely((res & (LAST_INO_BATCH-1)) == 0)) {
|
|
static atomic_t shared_last_ino;
|
|
@@ -908,7 +910,7 @@ unsigned int get_next_ino(void)
|
|
res++;
|
|
/* get_next_ino should not provide a 0 inode number */
|
|
if (unlikely(!res))
|
|
- res++;
|
|
+ goto start;
|
|
*p = res;
|
|
put_cpu_var(last_ino);
|
|
return res;
|
|
@@ -1770,12 +1772,13 @@ EXPORT_SYMBOL(generic_update_time);
|
|
* This does the actual work of updating an inodes time or version. Must have
|
|
* had called mnt_want_write() before calling this.
|
|
*/
|
|
-static int update_time(struct inode *inode, struct timespec64 *time, int flags)
|
|
+int update_time(struct inode *inode, struct timespec64 *time, int flags)
|
|
{
|
|
if (inode->i_op->update_time)
|
|
return inode->i_op->update_time(inode, time, flags);
|
|
return generic_update_time(inode, time, flags);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(update_time);
|
|
|
|
/**
|
|
* touch_atime - update the access time
|
|
diff --git a/fs/namespace.c b/fs/namespace.c
|
|
index cebaa3e81794..38078cbede43 100644
|
|
--- a/fs/namespace.c
|
|
+++ b/fs/namespace.c
|
|
@@ -431,6 +431,7 @@ void __mnt_drop_write(struct vfsmount *mnt)
|
|
mnt_dec_writers(real_mount(mnt));
|
|
preempt_enable();
|
|
}
|
|
+EXPORT_SYMBOL_GPL(__mnt_drop_write);
|
|
|
|
/**
|
|
* mnt_drop_write - give up write access to a mount
|
|
@@ -792,6 +793,13 @@ static inline int check_mnt(struct mount *mnt)
|
|
return mnt->mnt_ns == current->nsproxy->mnt_ns;
|
|
}
|
|
|
|
+/* for aufs, CONFIG_AUFS_BR_FUSE */
|
|
+int is_current_mnt_ns(struct vfsmount *mnt)
|
|
+{
|
|
+ return check_mnt(real_mount(mnt));
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(is_current_mnt_ns);
|
|
+
|
|
/*
|
|
* vfsmount lock must be held for write
|
|
*/
|
|
@@ -1955,6 +1963,7 @@ int iterate_mounts(int (*f)(struct vfsmount *, void *), void *arg,
|
|
}
|
|
return 0;
|
|
}
|
|
+EXPORT_SYMBOL_GPL(iterate_mounts);
|
|
|
|
static void lock_mnt_tree(struct mount *mnt)
|
|
{
|
|
diff --git a/fs/notify/group.c b/fs/notify/group.c
|
|
index a4a4b1c64d32..86dc2efb1850 100644
|
|
--- a/fs/notify/group.c
|
|
+++ b/fs/notify/group.c
|
|
@@ -100,6 +100,7 @@ void fsnotify_get_group(struct fsnotify_group *group)
|
|
{
|
|
refcount_inc(&group->refcnt);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(fsnotify_get_group);
|
|
|
|
/*
|
|
* Drop a reference to a group. Free it if it's through.
|
|
diff --git a/fs/open.c b/fs/open.c
|
|
index 9af548fb841b..2ff09b709f7b 100644
|
|
--- a/fs/open.c
|
|
+++ b/fs/open.c
|
|
@@ -65,6 +65,7 @@ int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs,
|
|
inode_unlock(dentry->d_inode);
|
|
return ret;
|
|
}
|
|
+EXPORT_SYMBOL_GPL(do_truncate);
|
|
|
|
long vfs_truncate(const struct path *path, loff_t length)
|
|
{
|
|
diff --git a/fs/proc/base.c b/fs/proc/base.c
|
|
index b362523a9829..669448bb8a73 100644
|
|
--- a/fs/proc/base.c
|
|
+++ b/fs/proc/base.c
|
|
@@ -2184,7 +2184,7 @@ static int map_files_get_link(struct dentry *dentry, struct path *path)
|
|
rc = -ENOENT;
|
|
vma = find_exact_vma(mm, vm_start, vm_end);
|
|
if (vma && vma->vm_file) {
|
|
- *path = vma->vm_file->f_path;
|
|
+ *path = vma_pr_or_file(vma)->f_path;
|
|
path_get(path);
|
|
rc = 0;
|
|
}
|
|
diff --git a/fs/proc/nommu.c b/fs/proc/nommu.c
|
|
index 13452b32e2bd..38acccfef9d4 100644
|
|
--- a/fs/proc/nommu.c
|
|
+++ b/fs/proc/nommu.c
|
|
@@ -40,7 +40,10 @@ static int nommu_region_show(struct seq_file *m, struct vm_region *region)
|
|
file = region->vm_file;
|
|
|
|
if (file) {
|
|
- struct inode *inode = file_inode(region->vm_file);
|
|
+ struct inode *inode;
|
|
+
|
|
+ file = vmr_pr_or_file(region);
|
|
+ inode = file_inode(file);
|
|
dev = inode->i_sb->s_dev;
|
|
ino = inode->i_ino;
|
|
}
|
|
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
|
|
index ee5a235b3056..80a46dcede7b 100644
|
|
--- a/fs/proc/task_mmu.c
|
|
+++ b/fs/proc/task_mmu.c
|
|
@@ -280,7 +280,10 @@ show_map_vma(struct seq_file *m, struct vm_area_struct *vma)
|
|
const char *name = NULL;
|
|
|
|
if (file) {
|
|
- struct inode *inode = file_inode(vma->vm_file);
|
|
+ struct inode *inode;
|
|
+
|
|
+ file = vma_pr_or_file(vma);
|
|
+ inode = file_inode(file);
|
|
dev = inode->i_sb->s_dev;
|
|
ino = inode->i_ino;
|
|
pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT;
|
|
@@ -1855,7 +1858,7 @@ static int show_numa_map(struct seq_file *m, void *v)
|
|
struct proc_maps_private *proc_priv = &numa_priv->proc_maps;
|
|
struct vm_area_struct *vma = v;
|
|
struct numa_maps *md = &numa_priv->md;
|
|
- struct file *file = vma->vm_file;
|
|
+ struct file *file = vma_pr_or_file(vma);
|
|
struct mm_struct *mm = vma->vm_mm;
|
|
struct mempolicy *pol;
|
|
char buffer[64];
|
|
diff --git a/fs/proc/task_nommu.c b/fs/proc/task_nommu.c
|
|
index a6d21fc0033c..02c2de31196e 100644
|
|
--- a/fs/proc/task_nommu.c
|
|
+++ b/fs/proc/task_nommu.c
|
|
@@ -155,7 +155,10 @@ static int nommu_vma_show(struct seq_file *m, struct vm_area_struct *vma)
|
|
file = vma->vm_file;
|
|
|
|
if (file) {
|
|
- struct inode *inode = file_inode(vma->vm_file);
|
|
+ struct inode *inode;
|
|
+
|
|
+ file = vma_pr_or_file(vma);
|
|
+ inode = file_inode(file);
|
|
dev = inode->i_sb->s_dev;
|
|
ino = inode->i_ino;
|
|
pgoff = (loff_t)vma->vm_pgoff << PAGE_SHIFT;
|
|
diff --git a/fs/read_write.c b/fs/read_write.c
|
|
index 75f764b43418..4ba9dca3af5b 100644
|
|
--- a/fs/read_write.c
|
|
+++ b/fs/read_write.c
|
|
@@ -503,6 +503,7 @@ ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
|
|
inc_syscr(current);
|
|
return ret;
|
|
}
|
|
+EXPORT_SYMBOL_GPL(vfs_read);
|
|
|
|
static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
|
|
{
|
|
@@ -522,6 +523,30 @@ static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t
|
|
return ret;
|
|
}
|
|
|
|
+vfs_readf_t vfs_readf(struct file *file)
|
|
+{
|
|
+ const struct file_operations *fop = file->f_op;
|
|
+
|
|
+ if (fop->read)
|
|
+ return fop->read;
|
|
+ if (fop->read_iter)
|
|
+ return new_sync_read;
|
|
+ return ERR_PTR(-ENOSYS); /* doesn't have ->read(|_iter)() op */
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(vfs_readf);
|
|
+
|
|
+vfs_writef_t vfs_writef(struct file *file)
|
|
+{
|
|
+ const struct file_operations *fop = file->f_op;
|
|
+
|
|
+ if (fop->write)
|
|
+ return fop->write;
|
|
+ if (fop->write_iter)
|
|
+ return new_sync_write;
|
|
+ return ERR_PTR(-ENOSYS); /* doesn't have ->write(|_iter)() op */
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(vfs_writef);
|
|
+
|
|
/* caller is responsible for file_start_write/file_end_write */
|
|
ssize_t __kernel_write(struct file *file, const void *buf, size_t count, loff_t *pos)
|
|
{
|
|
@@ -613,6 +638,7 @@ ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_
|
|
file_end_write(file);
|
|
return ret;
|
|
}
|
|
+EXPORT_SYMBOL_GPL(vfs_write);
|
|
|
|
/* file_ppos returns &file->f_pos or NULL if file is stream */
|
|
static inline loff_t *file_ppos(struct file *file)
|
|
diff --git a/fs/splice.c b/fs/splice.c
|
|
index 866d5c2367b2..3e1787cae69a 100644
|
|
--- a/fs/splice.c
|
|
+++ b/fs/splice.c
|
|
@@ -756,8 +756,8 @@ static int warn_unsupported(struct file *file, const char *op)
|
|
/*
|
|
* Attempt to initiate a splice from pipe to file.
|
|
*/
|
|
-static long do_splice_from(struct pipe_inode_info *pipe, struct file *out,
|
|
- loff_t *ppos, size_t len, unsigned int flags)
|
|
+long do_splice_from(struct pipe_inode_info *pipe, struct file *out,
|
|
+ loff_t *ppos, size_t len, unsigned int flags)
|
|
{
|
|
if (unlikely(!out->f_op->splice_write))
|
|
return warn_unsupported(out, "write");
|
|
@@ -767,9 +767,9 @@ static long do_splice_from(struct pipe_inode_info *pipe, struct file *out,
|
|
/*
|
|
* Attempt to initiate a splice from a file to a pipe.
|
|
*/
|
|
-static long do_splice_to(struct file *in, loff_t *ppos,
|
|
- struct pipe_inode_info *pipe, size_t len,
|
|
- unsigned int flags)
|
|
+long do_splice_to(struct file *in, loff_t *ppos,
|
|
+ struct pipe_inode_info *pipe, size_t len,
|
|
+ unsigned int flags)
|
|
{
|
|
int ret;
|
|
|
|
@@ -787,6 +787,7 @@ static long do_splice_to(struct file *in, loff_t *ppos,
|
|
return warn_unsupported(in, "read");
|
|
return in->f_op->splice_read(in, ppos, pipe, len, flags);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(do_splice_from);
|
|
|
|
/**
|
|
* splice_direct_to_actor - splices data directly between two non-pipes
|
|
@@ -933,6 +934,7 @@ static int direct_splice_actor(struct pipe_inode_info *pipe,
|
|
return do_splice_from(pipe, file, sd->opos, sd->total_len,
|
|
sd->flags);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(do_splice_to);
|
|
|
|
/**
|
|
* do_splice_direct - splices data directly between two files
|
|
diff --git a/fs/sync.c b/fs/sync.c
|
|
index 1373a610dc78..fa5c7fba7f1b 100644
|
|
--- a/fs/sync.c
|
|
+++ b/fs/sync.c
|
|
@@ -28,7 +28,7 @@
|
|
* wait == 1 case since in that case write_inode() functions do
|
|
* sync_dirty_buffer() and thus effectively write one block at a time.
|
|
*/
|
|
-static int __sync_filesystem(struct super_block *sb, int wait)
|
|
+int __sync_filesystem(struct super_block *sb, int wait)
|
|
{
|
|
if (wait)
|
|
sync_inodes_sb(sb);
|
|
@@ -39,6 +39,7 @@ static int __sync_filesystem(struct super_block *sb, int wait)
|
|
sb->s_op->sync_fs(sb, wait);
|
|
return __sync_blockdev(sb->s_bdev, wait);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(__sync_filesystem);
|
|
|
|
/*
|
|
* Write out and wait upon all dirty data associated with this
|
|
diff --git a/fs/xattr.c b/fs/xattr.c
|
|
index cd7a563e8bcd..7d989d57b0f0 100644
|
|
--- a/fs/xattr.c
|
|
+++ b/fs/xattr.c
|
|
@@ -360,6 +360,7 @@ vfs_getxattr_alloc(struct dentry *dentry, const char *name, char **xattr_value,
|
|
*xattr_value = value;
|
|
return error;
|
|
}
|
|
+EXPORT_SYMBOL_GPL(vfs_getxattr_alloc);
|
|
|
|
ssize_t
|
|
__vfs_getxattr(struct dentry *dentry, struct inode *inode, const char *name,
|
|
diff --git a/include/linux/fs.h b/include/linux/fs.h
|
|
index 8667d0cdc71e..097457929cdc 100644
|
|
--- a/include/linux/fs.h
|
|
+++ b/include/linux/fs.h
|
|
@@ -1332,6 +1332,7 @@ extern void fasync_free(struct fasync_struct *);
|
|
/* can be called from interrupts */
|
|
extern void kill_fasync(struct fasync_struct **, int, int);
|
|
|
|
+extern int setfl(int fd, struct file *filp, unsigned long arg);
|
|
extern void __f_setown(struct file *filp, struct pid *, enum pid_type, int force);
|
|
extern int f_setown(struct file *filp, unsigned long arg, int force);
|
|
extern void f_delown(struct file *filp);
|
|
@@ -1843,6 +1844,7 @@ struct file_operations {
|
|
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
|
|
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
|
|
int (*check_flags)(int);
|
|
+ int (*setfl)(struct file *, unsigned long);
|
|
int (*flock) (struct file *, int, struct file_lock *);
|
|
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
|
|
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
|
|
@@ -1908,6 +1910,12 @@ static inline int call_mmap(struct file *file, struct vm_area_struct *vma)
|
|
return file->f_op->mmap(file, vma);
|
|
}
|
|
|
|
+typedef ssize_t (*vfs_readf_t)(struct file *, char __user *, size_t, loff_t *);
|
|
+typedef ssize_t (*vfs_writef_t)(struct file *, const char __user *, size_t,
|
|
+ loff_t *);
|
|
+vfs_readf_t vfs_readf(struct file *file);
|
|
+vfs_writef_t vfs_writef(struct file *file);
|
|
+
|
|
extern ssize_t vfs_read(struct file *, char __user *, size_t, loff_t *);
|
|
extern ssize_t vfs_write(struct file *, const char __user *, size_t, loff_t *);
|
|
extern ssize_t vfs_copy_file_range(struct file *, loff_t , struct file *,
|
|
@@ -2328,6 +2336,7 @@ extern int current_umask(void);
|
|
extern void ihold(struct inode * inode);
|
|
extern void iput(struct inode *);
|
|
extern int generic_update_time(struct inode *, struct timespec64 *, int);
|
|
+extern int update_time(struct inode *, struct timespec64 *, int);
|
|
|
|
/* /sys/fs */
|
|
extern struct kobject *fs_kobj;
|
|
@@ -2564,6 +2573,7 @@ static inline bool sb_is_blkdev_sb(struct super_block *sb)
|
|
}
|
|
|
|
void emergency_thaw_all(void);
|
|
+extern int __sync_filesystem(struct super_block *, int);
|
|
extern int sync_filesystem(struct super_block *);
|
|
extern const struct file_operations def_blk_fops;
|
|
extern const struct file_operations def_chr_fops;
|
|
diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h
|
|
index f5594879175a..93bb86198167 100644
|
|
--- a/include/linux/lockdep.h
|
|
+++ b/include/linux/lockdep.h
|
|
@@ -241,6 +241,8 @@ static inline int lockdep_match_key(struct lockdep_map *lock,
|
|
return lock->key == key;
|
|
}
|
|
|
|
+struct lock_class *lockdep_hlock_class(struct held_lock *hlock);
|
|
+
|
|
/*
|
|
* Acquire a lock.
|
|
*
|
|
@@ -375,6 +377,7 @@ static inline void lockdep_unregister_key(struct lock_class_key *key)
|
|
|
|
#define lockdep_depth(tsk) (0)
|
|
|
|
+#define lockdep_is_held(lock) (1)
|
|
#define lockdep_is_held_type(l, r) (1)
|
|
|
|
#define lockdep_assert_held(l) do { (void)(l); } while (0)
|
|
diff --git a/include/linux/mm.h b/include/linux/mm.h
|
|
index db6ae4d3fb4e..1a632192d9d9 100644
|
|
--- a/include/linux/mm.h
|
|
+++ b/include/linux/mm.h
|
|
@@ -1712,6 +1712,28 @@ static inline void unmap_shared_mapping_range(struct address_space *mapping,
|
|
unmap_mapping_range(mapping, holebegin, holelen, 0);
|
|
}
|
|
|
|
+extern void vma_do_file_update_time(struct vm_area_struct *, const char[], int);
|
|
+extern struct file *vma_do_pr_or_file(struct vm_area_struct *, const char[],
|
|
+ int);
|
|
+extern void vma_do_get_file(struct vm_area_struct *, const char[], int);
|
|
+extern void vma_do_fput(struct vm_area_struct *, const char[], int);
|
|
+
|
|
+#define vma_file_update_time(vma) vma_do_file_update_time(vma, __func__, \
|
|
+ __LINE__)
|
|
+#define vma_pr_or_file(vma) vma_do_pr_or_file(vma, __func__, \
|
|
+ __LINE__)
|
|
+#define vma_get_file(vma) vma_do_get_file(vma, __func__, __LINE__)
|
|
+#define vma_fput(vma) vma_do_fput(vma, __func__, __LINE__)
|
|
+
|
|
+#ifndef CONFIG_MMU
|
|
+extern struct file *vmr_do_pr_or_file(struct vm_region *, const char[], int);
|
|
+extern void vmr_do_fput(struct vm_region *, const char[], int);
|
|
+
|
|
+#define vmr_pr_or_file(region) vmr_do_pr_or_file(region, __func__, \
|
|
+ __LINE__)
|
|
+#define vmr_fput(region) vmr_do_fput(region, __func__, __LINE__)
|
|
+#endif /* !CONFIG_MMU */
|
|
+
|
|
extern int access_process_vm(struct task_struct *tsk, unsigned long addr,
|
|
void *buf, int len, unsigned int gup_flags);
|
|
extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
|
|
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
|
|
index 5a9238f6caad..ad387c3cb14f 100644
|
|
--- a/include/linux/mm_types.h
|
|
+++ b/include/linux/mm_types.h
|
|
@@ -280,6 +280,7 @@ struct vm_region {
|
|
unsigned long vm_top; /* region allocated to here */
|
|
unsigned long vm_pgoff; /* the offset in vm_file corresponding to vm_start */
|
|
struct file *vm_file; /* the backing file or NULL */
|
|
+ struct file *vm_prfile; /* the virtual backing file or NULL */
|
|
|
|
int vm_usage; /* region usage count (access under nommu_region_sem) */
|
|
bool vm_icache_flushed : 1; /* true if the icache has been flushed for
|
|
@@ -359,6 +360,7 @@ struct vm_area_struct {
|
|
unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE
|
|
units */
|
|
struct file * vm_file; /* File we map to (can be NULL). */
|
|
+ struct file *vm_prfile; /* shadow of vm_file */
|
|
void * vm_private_data; /* was vm_pte (shared mem) */
|
|
|
|
#ifdef CONFIG_SWAP
|
|
diff --git a/include/linux/mnt_namespace.h b/include/linux/mnt_namespace.h
|
|
index 8f882f5881e8..6b9808f09843 100644
|
|
--- a/include/linux/mnt_namespace.h
|
|
+++ b/include/linux/mnt_namespace.h
|
|
@@ -7,12 +7,15 @@ struct mnt_namespace;
|
|
struct fs_struct;
|
|
struct user_namespace;
|
|
struct ns_common;
|
|
+struct vfsmount;
|
|
|
|
extern struct mnt_namespace *copy_mnt_ns(unsigned long, struct mnt_namespace *,
|
|
struct user_namespace *, struct fs_struct *);
|
|
extern void put_mnt_ns(struct mnt_namespace *ns);
|
|
extern struct ns_common *from_mnt_ns(struct mnt_namespace *);
|
|
|
|
+extern int is_current_mnt_ns(struct vfsmount *mnt);
|
|
+
|
|
extern const struct file_operations proc_mounts_operations;
|
|
extern const struct file_operations proc_mountinfo_operations;
|
|
extern const struct file_operations proc_mountstats_operations;
|
|
diff --git a/include/linux/splice.h b/include/linux/splice.h
|
|
index a55179fd60fc..8e21c53cf883 100644
|
|
--- a/include/linux/splice.h
|
|
+++ b/include/linux/splice.h
|
|
@@ -93,4 +93,10 @@ extern void splice_shrink_spd(struct splice_pipe_desc *);
|
|
|
|
extern const struct pipe_buf_operations page_cache_pipe_buf_ops;
|
|
extern const struct pipe_buf_operations default_pipe_buf_ops;
|
|
+
|
|
+extern long do_splice_from(struct pipe_inode_info *pipe, struct file *out,
|
|
+ loff_t *ppos, size_t len, unsigned int flags);
|
|
+extern long do_splice_to(struct file *in, loff_t *ppos,
|
|
+ struct pipe_inode_info *pipe, size_t len,
|
|
+ unsigned int flags);
|
|
#endif
|
|
diff --git a/include/uapi/linux/aufs_type.h b/include/uapi/linux/aufs_type.h
|
|
new file mode 100644
|
|
index 000000000000..34738b8cf349
|
|
--- /dev/null
|
|
+++ b/include/uapi/linux/aufs_type.h
|
|
@@ -0,0 +1,452 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
|
+/*
|
|
+ * Copyright (C) 2005-2020 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/>.
|
|
+ */
|
|
+
|
|
+#ifndef __AUFS_TYPE_H__
|
|
+#define __AUFS_TYPE_H__
|
|
+
|
|
+#define AUFS_NAME "aufs"
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+/*
|
|
+ * define it before including all other headers.
|
|
+ * sched.h may use pr_* macros before defining "current", so define the
|
|
+ * no-current version first, and re-define later.
|
|
+ */
|
|
+#define pr_fmt(fmt) AUFS_NAME " %s:%d: " fmt, __func__, __LINE__
|
|
+#include <linux/sched.h>
|
|
+#undef pr_fmt
|
|
+#define pr_fmt(fmt) \
|
|
+ AUFS_NAME " %s:%d:%.*s[%d]: " fmt, __func__, __LINE__, \
|
|
+ (int)sizeof(current->comm), current->comm, current->pid
|
|
+#include <linux/limits.h>
|
|
+#else
|
|
+#include <stdint.h>
|
|
+#include <sys/types.h>
|
|
+#include <limits.h>
|
|
+#endif /* __KERNEL__ */
|
|
+
|
|
+#define AUFS_VERSION "5.7-20200622"
|
|
+
|
|
+/* todo? move this to linux-2.6.19/include/magic.h */
|
|
+#define AUFS_SUPER_MAGIC ('a' << 24 | 'u' << 16 | 'f' << 8 | 's')
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+#ifdef CONFIG_AUFS_BRANCH_MAX_127
|
|
+typedef int8_t aufs_bindex_t;
|
|
+#define AUFS_BRANCH_MAX 127
|
|
+#else
|
|
+typedef int16_t aufs_bindex_t;
|
|
+#ifdef CONFIG_AUFS_BRANCH_MAX_511
|
|
+#define AUFS_BRANCH_MAX 511
|
|
+#elif defined(CONFIG_AUFS_BRANCH_MAX_1023)
|
|
+#define AUFS_BRANCH_MAX 1023
|
|
+#elif defined(CONFIG_AUFS_BRANCH_MAX_32767)
|
|
+#define AUFS_BRANCH_MAX 32767
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+#ifndef AUFS_BRANCH_MAX
|
|
+#error unknown CONFIG_AUFS_BRANCH_MAX value
|
|
+#endif
|
|
+#endif /* __KERNEL__ */
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#define AUFS_FSTYPE AUFS_NAME
|
|
+
|
|
+#define AUFS_ROOT_INO 2
|
|
+#define AUFS_FIRST_INO 11
|
|
+
|
|
+#define AUFS_WH_PFX ".wh."
|
|
+#define AUFS_WH_PFX_LEN ((int)sizeof(AUFS_WH_PFX) - 1)
|
|
+#define AUFS_WH_TMP_LEN 4
|
|
+/* a limit for rmdir/rename a dir and copyup */
|
|
+#define AUFS_MAX_NAMELEN (NAME_MAX \
|
|
+ - AUFS_WH_PFX_LEN * 2 /* doubly whiteouted */\
|
|
+ - 1 /* dot */\
|
|
+ - AUFS_WH_TMP_LEN) /* hex */
|
|
+#define AUFS_XINO_FNAME "." AUFS_NAME ".xino"
|
|
+#define AUFS_XINO_DEFPATH "/tmp/" AUFS_XINO_FNAME
|
|
+#define AUFS_XINO_DEF_SEC 30 /* seconds */
|
|
+#define AUFS_XINO_DEF_TRUNC 45 /* percentage */
|
|
+#define AUFS_DIRWH_DEF 3
|
|
+#define AUFS_RDCACHE_DEF 10 /* seconds */
|
|
+#define AUFS_RDCACHE_MAX 3600 /* seconds */
|
|
+#define AUFS_RDBLK_DEF 512 /* bytes */
|
|
+#define AUFS_RDHASH_DEF 32
|
|
+#define AUFS_WKQ_NAME AUFS_NAME "d"
|
|
+#define AUFS_MFS_DEF_SEC 30 /* seconds */
|
|
+#define AUFS_MFS_MAX_SEC 3600 /* seconds */
|
|
+#define AUFS_FHSM_CACHE_DEF_SEC 30 /* seconds */
|
|
+#define AUFS_PLINK_WARN 50 /* number of plinks in a single bucket */
|
|
+
|
|
+/* pseudo-link maintenace under /proc */
|
|
+#define AUFS_PLINK_MAINT_NAME "plink_maint"
|
|
+#define AUFS_PLINK_MAINT_DIR "fs/" AUFS_NAME
|
|
+#define AUFS_PLINK_MAINT_PATH AUFS_PLINK_MAINT_DIR "/" AUFS_PLINK_MAINT_NAME
|
|
+
|
|
+/* dirren, renamed dir */
|
|
+#define AUFS_DR_INFO_PFX AUFS_WH_PFX ".dr."
|
|
+#define AUFS_DR_BRHINO_NAME AUFS_WH_PFX "hino"
|
|
+/* whiteouted doubly */
|
|
+#define AUFS_WH_DR_INFO_PFX AUFS_WH_PFX AUFS_DR_INFO_PFX
|
|
+#define AUFS_WH_DR_BRHINO AUFS_WH_PFX AUFS_DR_BRHINO_NAME
|
|
+
|
|
+#define AUFS_DIROPQ_NAME AUFS_WH_PFX ".opq" /* whiteouted doubly */
|
|
+#define AUFS_WH_DIROPQ AUFS_WH_PFX AUFS_DIROPQ_NAME
|
|
+
|
|
+#define AUFS_BASE_NAME AUFS_WH_PFX AUFS_NAME
|
|
+#define AUFS_PLINKDIR_NAME AUFS_WH_PFX "plnk"
|
|
+#define AUFS_ORPHDIR_NAME AUFS_WH_PFX "orph"
|
|
+
|
|
+/* doubly whiteouted */
|
|
+#define AUFS_WH_BASE AUFS_WH_PFX AUFS_BASE_NAME
|
|
+#define AUFS_WH_PLINKDIR AUFS_WH_PFX AUFS_PLINKDIR_NAME
|
|
+#define AUFS_WH_ORPHDIR AUFS_WH_PFX AUFS_ORPHDIR_NAME
|
|
+
|
|
+/* branch permissions and attributes */
|
|
+#define AUFS_BRPERM_RW "rw"
|
|
+#define AUFS_BRPERM_RO "ro"
|
|
+#define AUFS_BRPERM_RR "rr"
|
|
+#define AUFS_BRATTR_COO_REG "coo_reg"
|
|
+#define AUFS_BRATTR_COO_ALL "coo_all"
|
|
+#define AUFS_BRATTR_FHSM "fhsm"
|
|
+#define AUFS_BRATTR_UNPIN "unpin"
|
|
+#define AUFS_BRATTR_ICEX "icex"
|
|
+#define AUFS_BRATTR_ICEX_SEC "icexsec"
|
|
+#define AUFS_BRATTR_ICEX_SYS "icexsys"
|
|
+#define AUFS_BRATTR_ICEX_TR "icextr"
|
|
+#define AUFS_BRATTR_ICEX_USR "icexusr"
|
|
+#define AUFS_BRATTR_ICEX_OTH "icexoth"
|
|
+#define AUFS_BRRATTR_WH "wh"
|
|
+#define AUFS_BRWATTR_NLWH "nolwh"
|
|
+#define AUFS_BRWATTR_MOO "moo"
|
|
+
|
|
+#define AuBrPerm_RW 1 /* writable, hardlinkable wh */
|
|
+#define AuBrPerm_RO (1 << 1) /* readonly */
|
|
+#define AuBrPerm_RR (1 << 2) /* natively readonly */
|
|
+#define AuBrPerm_Mask (AuBrPerm_RW | AuBrPerm_RO | AuBrPerm_RR)
|
|
+
|
|
+#define AuBrAttr_COO_REG (1 << 3) /* copy-up on open */
|
|
+#define AuBrAttr_COO_ALL (1 << 4)
|
|
+#define AuBrAttr_COO_Mask (AuBrAttr_COO_REG | AuBrAttr_COO_ALL)
|
|
+
|
|
+#define AuBrAttr_FHSM (1 << 5) /* file-based hsm */
|
|
+#define AuBrAttr_UNPIN (1 << 6) /* rename-able top dir of
|
|
+ branch. meaningless since
|
|
+ linux-3.18-rc1 */
|
|
+
|
|
+/* ignore error in copying XATTR */
|
|
+#define AuBrAttr_ICEX_SEC (1 << 7)
|
|
+#define AuBrAttr_ICEX_SYS (1 << 8)
|
|
+#define AuBrAttr_ICEX_TR (1 << 9)
|
|
+#define AuBrAttr_ICEX_USR (1 << 10)
|
|
+#define AuBrAttr_ICEX_OTH (1 << 11)
|
|
+#define AuBrAttr_ICEX (AuBrAttr_ICEX_SEC \
|
|
+ | AuBrAttr_ICEX_SYS \
|
|
+ | AuBrAttr_ICEX_TR \
|
|
+ | AuBrAttr_ICEX_USR \
|
|
+ | AuBrAttr_ICEX_OTH)
|
|
+
|
|
+#define AuBrRAttr_WH (1 << 12) /* whiteout-able */
|
|
+#define AuBrRAttr_Mask AuBrRAttr_WH
|
|
+
|
|
+#define AuBrWAttr_NoLinkWH (1 << 13) /* un-hardlinkable whiteouts */
|
|
+#define AuBrWAttr_MOO (1 << 14) /* move-up on open */
|
|
+#define AuBrWAttr_Mask (AuBrWAttr_NoLinkWH | AuBrWAttr_MOO)
|
|
+
|
|
+#define AuBrAttr_CMOO_Mask (AuBrAttr_COO_Mask | AuBrWAttr_MOO)
|
|
+
|
|
+/* #warning test userspace */
|
|
+#ifdef __KERNEL__
|
|
+#ifndef CONFIG_AUFS_FHSM
|
|
+#undef AuBrAttr_FHSM
|
|
+#define AuBrAttr_FHSM 0
|
|
+#endif
|
|
+#ifndef CONFIG_AUFS_XATTR
|
|
+#undef AuBrAttr_ICEX
|
|
+#define AuBrAttr_ICEX 0
|
|
+#undef AuBrAttr_ICEX_SEC
|
|
+#define AuBrAttr_ICEX_SEC 0
|
|
+#undef AuBrAttr_ICEX_SYS
|
|
+#define AuBrAttr_ICEX_SYS 0
|
|
+#undef AuBrAttr_ICEX_TR
|
|
+#define AuBrAttr_ICEX_TR 0
|
|
+#undef AuBrAttr_ICEX_USR
|
|
+#define AuBrAttr_ICEX_USR 0
|
|
+#undef AuBrAttr_ICEX_OTH
|
|
+#define AuBrAttr_ICEX_OTH 0
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+/* the longest combination */
|
|
+/* AUFS_BRATTR_ICEX and AUFS_BRATTR_ICEX_TR don't affect here */
|
|
+#define AuBrPermStrSz sizeof(AUFS_BRPERM_RW \
|
|
+ "+" AUFS_BRATTR_COO_REG \
|
|
+ "+" AUFS_BRATTR_FHSM \
|
|
+ "+" AUFS_BRATTR_UNPIN \
|
|
+ "+" AUFS_BRATTR_ICEX_SEC \
|
|
+ "+" AUFS_BRATTR_ICEX_SYS \
|
|
+ "+" AUFS_BRATTR_ICEX_USR \
|
|
+ "+" AUFS_BRATTR_ICEX_OTH \
|
|
+ "+" AUFS_BRWATTR_NLWH)
|
|
+
|
|
+typedef struct {
|
|
+ char a[AuBrPermStrSz];
|
|
+} au_br_perm_str_t;
|
|
+
|
|
+static inline int au_br_writable(int brperm)
|
|
+{
|
|
+ return brperm & AuBrPerm_RW;
|
|
+}
|
|
+
|
|
+static inline int au_br_whable(int brperm)
|
|
+{
|
|
+ return brperm & (AuBrPerm_RW | AuBrRAttr_WH);
|
|
+}
|
|
+
|
|
+static inline int au_br_wh_linkable(int brperm)
|
|
+{
|
|
+ return !(brperm & AuBrWAttr_NoLinkWH);
|
|
+}
|
|
+
|
|
+static inline int au_br_cmoo(int brperm)
|
|
+{
|
|
+ return brperm & AuBrAttr_CMOO_Mask;
|
|
+}
|
|
+
|
|
+static inline int au_br_fhsm(int brperm)
|
|
+{
|
|
+ return brperm & AuBrAttr_FHSM;
|
|
+}
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* ioctl */
|
|
+enum {
|
|
+ /* readdir in userspace */
|
|
+ AuCtl_RDU,
|
|
+ AuCtl_RDU_INO,
|
|
+
|
|
+ AuCtl_WBR_FD, /* pathconf wrapper */
|
|
+ AuCtl_IBUSY, /* busy inode */
|
|
+ AuCtl_MVDOWN, /* move-down */
|
|
+ AuCtl_BR, /* info about branches */
|
|
+ AuCtl_FHSM_FD /* connection for fhsm */
|
|
+};
|
|
+
|
|
+/* borrowed from linux/include/linux/kernel.h */
|
|
+#ifndef ALIGN
|
|
+#ifdef _GNU_SOURCE
|
|
+#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a)-1)
|
|
+#else
|
|
+#define ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1))
|
|
+#endif
|
|
+#define __ALIGN_MASK(x, mask) (((x)+(mask))&~(mask))
|
|
+#endif
|
|
+
|
|
+/* borrowed from linux/include/linux/compiler-gcc3.h */
|
|
+#ifndef __aligned
|
|
+#define __aligned(x) __attribute__((aligned(x)))
|
|
+#endif
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+#ifndef __packed
|
|
+#define __packed __attribute__((packed))
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+struct au_rdu_cookie {
|
|
+ uint64_t h_pos;
|
|
+ int16_t bindex;
|
|
+ uint8_t flags;
|
|
+ uint8_t pad;
|
|
+ uint32_t generation;
|
|
+} __aligned(8);
|
|
+
|
|
+struct au_rdu_ent {
|
|
+ uint64_t ino;
|
|
+ int16_t bindex;
|
|
+ uint8_t type;
|
|
+ uint8_t nlen;
|
|
+ uint8_t wh;
|
|
+ char name[];
|
|
+} __aligned(8);
|
|
+
|
|
+static inline int au_rdu_len(int nlen)
|
|
+{
|
|
+ /* include the terminating NULL */
|
|
+ return ALIGN(sizeof(struct au_rdu_ent) + nlen + 1,
|
|
+ sizeof(uint64_t));
|
|
+}
|
|
+
|
|
+union au_rdu_ent_ul {
|
|
+ struct au_rdu_ent __user *e;
|
|
+ uint64_t ul;
|
|
+};
|
|
+
|
|
+enum {
|
|
+ AufsCtlRduV_SZ,
|
|
+ AufsCtlRduV_End
|
|
+};
|
|
+
|
|
+struct aufs_rdu {
|
|
+ /* input */
|
|
+ union {
|
|
+ uint64_t sz; /* AuCtl_RDU */
|
|
+ uint64_t nent; /* AuCtl_RDU_INO */
|
|
+ };
|
|
+ union au_rdu_ent_ul ent;
|
|
+ uint16_t verify[AufsCtlRduV_End];
|
|
+
|
|
+ /* input/output */
|
|
+ uint32_t blk;
|
|
+
|
|
+ /* output */
|
|
+ union au_rdu_ent_ul tail;
|
|
+ /* number of entries which were added in a single call */
|
|
+ uint64_t rent;
|
|
+ uint8_t full;
|
|
+ uint8_t shwh;
|
|
+
|
|
+ struct au_rdu_cookie cookie;
|
|
+} __aligned(8);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* dirren. the branch is identified by the filename who contains this */
|
|
+struct au_drinfo {
|
|
+ uint64_t ino;
|
|
+ union {
|
|
+ uint8_t oldnamelen;
|
|
+ uint64_t _padding;
|
|
+ };
|
|
+ uint8_t oldname[];
|
|
+} __aligned(8);
|
|
+
|
|
+struct au_drinfo_fdata {
|
|
+ uint32_t magic;
|
|
+ struct au_drinfo drinfo;
|
|
+} __aligned(8);
|
|
+
|
|
+#define AUFS_DRINFO_MAGIC_V1 ('a' << 24 | 'd' << 16 | 'r' << 8 | 0x01)
|
|
+/* future */
|
|
+#define AUFS_DRINFO_MAGIC_V2 ('a' << 24 | 'd' << 16 | 'r' << 8 | 0x02)
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct aufs_wbr_fd {
|
|
+ uint32_t oflags;
|
|
+ int16_t brid;
|
|
+} __aligned(8);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+struct aufs_ibusy {
|
|
+ uint64_t ino, h_ino;
|
|
+ int16_t bindex;
|
|
+} __aligned(8);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+/* error code for move-down */
|
|
+/* the actual message strings are implemented in aufs-util.git */
|
|
+enum {
|
|
+ EAU_MVDOWN_OPAQUE = 1,
|
|
+ EAU_MVDOWN_WHITEOUT,
|
|
+ EAU_MVDOWN_UPPER,
|
|
+ EAU_MVDOWN_BOTTOM,
|
|
+ EAU_MVDOWN_NOUPPER,
|
|
+ EAU_MVDOWN_NOLOWERBR,
|
|
+ EAU_Last
|
|
+};
|
|
+
|
|
+/* flags for move-down */
|
|
+#define AUFS_MVDOWN_DMSG 1
|
|
+#define AUFS_MVDOWN_OWLOWER (1 << 1) /* overwrite lower */
|
|
+#define AUFS_MVDOWN_KUPPER (1 << 2) /* keep upper */
|
|
+#define AUFS_MVDOWN_ROLOWER (1 << 3) /* do even if lower is RO */
|
|
+#define AUFS_MVDOWN_ROLOWER_R (1 << 4) /* did on lower RO */
|
|
+#define AUFS_MVDOWN_ROUPPER (1 << 5) /* do even if upper is RO */
|
|
+#define AUFS_MVDOWN_ROUPPER_R (1 << 6) /* did on upper RO */
|
|
+#define AUFS_MVDOWN_BRID_UPPER (1 << 7) /* upper brid */
|
|
+#define AUFS_MVDOWN_BRID_LOWER (1 << 8) /* lower brid */
|
|
+#define AUFS_MVDOWN_FHSM_LOWER (1 << 9) /* find fhsm attr for lower */
|
|
+#define AUFS_MVDOWN_STFS (1 << 10) /* req. stfs */
|
|
+#define AUFS_MVDOWN_STFS_FAILED (1 << 11) /* output: stfs is unusable */
|
|
+#define AUFS_MVDOWN_BOTTOM (1 << 12) /* output: no more lowers */
|
|
+
|
|
+/* index for move-down */
|
|
+enum {
|
|
+ AUFS_MVDOWN_UPPER,
|
|
+ AUFS_MVDOWN_LOWER,
|
|
+ AUFS_MVDOWN_NARRAY
|
|
+};
|
|
+
|
|
+/*
|
|
+ * additional info of move-down
|
|
+ * number of free blocks and inodes.
|
|
+ * subset of struct kstatfs, but smaller and always 64bit.
|
|
+ */
|
|
+struct aufs_stfs {
|
|
+ uint64_t f_blocks;
|
|
+ uint64_t f_bavail;
|
|
+ uint64_t f_files;
|
|
+ uint64_t f_ffree;
|
|
+};
|
|
+
|
|
+struct aufs_stbr {
|
|
+ int16_t brid; /* optional input */
|
|
+ int16_t bindex; /* output */
|
|
+ struct aufs_stfs stfs; /* output when AUFS_MVDOWN_STFS set */
|
|
+} __aligned(8);
|
|
+
|
|
+struct aufs_mvdown {
|
|
+ uint32_t flags; /* input/output */
|
|
+ struct aufs_stbr stbr[AUFS_MVDOWN_NARRAY]; /* input/output */
|
|
+ int8_t au_errno; /* output */
|
|
+} __aligned(8);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+union aufs_brinfo {
|
|
+ /* PATH_MAX may differ between kernel-space and user-space */
|
|
+ char _spacer[4096];
|
|
+ struct {
|
|
+ int16_t id;
|
|
+ int perm;
|
|
+ char path[];
|
|
+ };
|
|
+} __aligned(8);
|
|
+
|
|
+/* ---------------------------------------------------------------------- */
|
|
+
|
|
+#define AuCtlType 'A'
|
|
+#define AUFS_CTL_RDU _IOWR(AuCtlType, AuCtl_RDU, struct aufs_rdu)
|
|
+#define AUFS_CTL_RDU_INO _IOWR(AuCtlType, AuCtl_RDU_INO, struct aufs_rdu)
|
|
+#define AUFS_CTL_WBR_FD _IOW(AuCtlType, AuCtl_WBR_FD, \
|
|
+ struct aufs_wbr_fd)
|
|
+#define AUFS_CTL_IBUSY _IOWR(AuCtlType, AuCtl_IBUSY, struct aufs_ibusy)
|
|
+#define AUFS_CTL_MVDOWN _IOWR(AuCtlType, AuCtl_MVDOWN, \
|
|
+ struct aufs_mvdown)
|
|
+#define AUFS_CTL_BRINFO _IOW(AuCtlType, AuCtl_BR, union aufs_brinfo)
|
|
+#define AUFS_CTL_FHSM_FD _IOW(AuCtlType, AuCtl_FHSM_FD, int)
|
|
+
|
|
+#endif /* __AUFS_TYPE_H__ */
|
|
diff --git a/kernel/fork.c b/kernel/fork.c
|
|
index 6d266388d380..dd7f13c3bee7 100644
|
|
--- a/kernel/fork.c
|
|
+++ b/kernel/fork.c
|
|
@@ -554,7 +554,7 @@ static __latent_entropy int dup_mmap(struct mm_struct *mm,
|
|
struct inode *inode = file_inode(file);
|
|
struct address_space *mapping = file->f_mapping;
|
|
|
|
- get_file(file);
|
|
+ vma_get_file(tmp);
|
|
if (tmp->vm_flags & VM_DENYWRITE)
|
|
put_write_access(inode);
|
|
i_mmap_lock_write(mapping);
|
|
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
|
|
index c1418b47f625..be002c3a3083 100644
|
|
--- a/kernel/locking/lockdep.c
|
|
+++ b/kernel/locking/lockdep.c
|
|
@@ -188,7 +188,7 @@ static
|
|
struct lock_class lock_classes[MAX_LOCKDEP_KEYS];
|
|
static DECLARE_BITMAP(lock_classes_in_use, MAX_LOCKDEP_KEYS);
|
|
|
|
-static inline struct lock_class *hlock_class(struct held_lock *hlock)
|
|
+inline struct lock_class *lockdep_hlock_class(struct held_lock *hlock)
|
|
{
|
|
unsigned int class_idx = hlock->class_idx;
|
|
|
|
@@ -209,6 +209,8 @@ static inline struct lock_class *hlock_class(struct held_lock *hlock)
|
|
*/
|
|
return lock_classes + class_idx;
|
|
}
|
|
+EXPORT_SYMBOL_GPL(lockdep_hlock_class);
|
|
+#define hlock_class(hlock) lockdep_hlock_class(hlock)
|
|
|
|
#ifdef CONFIG_LOCK_STAT
|
|
static DEFINE_PER_CPU(struct lock_class_stats[MAX_LOCKDEP_KEYS], cpu_lock_stats);
|
|
diff --git a/kernel/task_work.c b/kernel/task_work.c
|
|
index 8d6e1217c451..0e73637adda8 100644
|
|
--- a/kernel/task_work.c
|
|
+++ b/kernel/task_work.c
|
|
@@ -154,3 +154,4 @@ void task_work_run(void)
|
|
} while (work);
|
|
}
|
|
}
|
|
+EXPORT_SYMBOL_GPL(task_work_run);
|
|
diff --git a/mm/Makefile b/mm/Makefile
|
|
index d73aed0fc99c..93076a66ad6a 100644
|
|
--- a/mm/Makefile
|
|
+++ b/mm/Makefile
|
|
@@ -52,7 +52,7 @@ obj-y := filemap.o mempool.o oom_kill.o fadvise.o \
|
|
mm_init.o percpu.o slab_common.o \
|
|
compaction.o vmacache.o \
|
|
interval_tree.o list_lru.o workingset.o \
|
|
- debug.o gup.o $(mmu-y)
|
|
+ prfile.o debug.o gup.o $(mmu-y)
|
|
|
|
# Give 'page_alloc' its own module-parameter namespace
|
|
page-alloc-y := page_alloc.o
|
|
diff --git a/mm/filemap.c b/mm/filemap.c
|
|
index 0b2067b3c328..3334207431c7 100644
|
|
--- a/mm/filemap.c
|
|
+++ b/mm/filemap.c
|
|
@@ -2909,7 +2909,7 @@ vm_fault_t filemap_page_mkwrite(struct vm_fault *vmf)
|
|
vm_fault_t ret = VM_FAULT_LOCKED;
|
|
|
|
sb_start_pagefault(inode->i_sb);
|
|
- file_update_time(vmf->vma->vm_file);
|
|
+ vma_file_update_time(vmf->vma);
|
|
lock_page(page);
|
|
if (page->mapping != inode->i_mapping) {
|
|
unlock_page(page);
|
|
diff --git a/mm/mmap.c b/mm/mmap.c
|
|
index 5c8b4485860d..59f2f758d77e 100644
|
|
--- a/mm/mmap.c
|
|
+++ b/mm/mmap.c
|
|
@@ -179,7 +179,7 @@ static struct vm_area_struct *remove_vma(struct vm_area_struct *vma)
|
|
if (vma->vm_ops && vma->vm_ops->close)
|
|
vma->vm_ops->close(vma);
|
|
if (vma->vm_file)
|
|
- fput(vma->vm_file);
|
|
+ vma_fput(vma);
|
|
mpol_put(vma_policy(vma));
|
|
vm_area_free(vma);
|
|
return next;
|
|
@@ -951,7 +951,7 @@ int __vma_adjust(struct vm_area_struct *vma, unsigned long start,
|
|
if (remove_next) {
|
|
if (file) {
|
|
uprobe_munmap(next, next->vm_start, next->vm_end);
|
|
- fput(file);
|
|
+ vma_fput(vma);
|
|
}
|
|
if (next->anon_vma)
|
|
anon_vma_merge(vma, next);
|
|
@@ -1897,8 +1897,8 @@ unsigned long mmap_region(struct file *file, unsigned long addr,
|
|
return addr;
|
|
|
|
unmap_and_free_vma:
|
|
+ vma_fput(vma);
|
|
vma->vm_file = NULL;
|
|
- fput(file);
|
|
|
|
/* Undo any partial mapping done by a device driver. */
|
|
unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end);
|
|
@@ -2757,7 +2757,7 @@ int __split_vma(struct mm_struct *mm, struct vm_area_struct *vma,
|
|
goto out_free_mpol;
|
|
|
|
if (new->vm_file)
|
|
- get_file(new->vm_file);
|
|
+ vma_get_file(new);
|
|
|
|
if (new->vm_ops && new->vm_ops->open)
|
|
new->vm_ops->open(new);
|
|
@@ -2776,7 +2776,7 @@ int __split_vma(struct mm_struct *mm, struct vm_area_struct *vma,
|
|
if (new->vm_ops && new->vm_ops->close)
|
|
new->vm_ops->close(new);
|
|
if (new->vm_file)
|
|
- fput(new->vm_file);
|
|
+ vma_fput(new);
|
|
unlink_anon_vmas(new);
|
|
out_free_mpol:
|
|
mpol_put(vma_policy(new));
|
|
@@ -2969,7 +2969,7 @@ SYSCALL_DEFINE5(remap_file_pages, unsigned long, start, unsigned long, size,
|
|
struct vm_area_struct *vma;
|
|
unsigned long populate = 0;
|
|
unsigned long ret = -EINVAL;
|
|
- struct file *file;
|
|
+ struct file *file, *prfile;
|
|
|
|
pr_warn_once("%s (%d) uses deprecated remap_file_pages() syscall. See Documentation/vm/remap_file_pages.rst.\n",
|
|
current->comm, current->pid);
|
|
@@ -3044,10 +3044,27 @@ SYSCALL_DEFINE5(remap_file_pages, unsigned long, start, unsigned long, size,
|
|
}
|
|
}
|
|
|
|
- file = get_file(vma->vm_file);
|
|
+ vma_get_file(vma);
|
|
+ file = vma->vm_file;
|
|
+ prfile = vma->vm_prfile;
|
|
ret = do_mmap(vma->vm_file, start, size,
|
|
prot, flags, pgoff, &populate, NULL);
|
|
+ if (!IS_ERR_VALUE(ret) && file && prfile) {
|
|
+ struct vm_area_struct *new_vma;
|
|
+
|
|
+ new_vma = find_vma(mm, ret);
|
|
+ if (!new_vma->vm_prfile)
|
|
+ new_vma->vm_prfile = prfile;
|
|
+ if (new_vma != vma)
|
|
+ get_file(prfile);
|
|
+ }
|
|
+ /*
|
|
+ * two fput()s instead of vma_fput(vma),
|
|
+ * coz vma may not be available anymore.
|
|
+ */
|
|
fput(file);
|
|
+ if (prfile)
|
|
+ fput(prfile);
|
|
out:
|
|
mmap_write_unlock(mm);
|
|
if (populate)
|
|
@@ -3334,7 +3351,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
|
|
if (anon_vma_clone(new_vma, vma))
|
|
goto out_free_mempol;
|
|
if (new_vma->vm_file)
|
|
- get_file(new_vma->vm_file);
|
|
+ vma_get_file(new_vma);
|
|
if (new_vma->vm_ops && new_vma->vm_ops->open)
|
|
new_vma->vm_ops->open(new_vma);
|
|
vma_link(mm, new_vma, prev, rb_link, rb_parent);
|
|
diff --git a/mm/nommu.c b/mm/nommu.c
|
|
index 0faf39b32cdb..78ecad7204c8 100644
|
|
--- a/mm/nommu.c
|
|
+++ b/mm/nommu.c
|
|
@@ -533,7 +533,7 @@ static void __put_nommu_region(struct vm_region *region)
|
|
up_write(&nommu_region_sem);
|
|
|
|
if (region->vm_file)
|
|
- fput(region->vm_file);
|
|
+ vmr_fput(region);
|
|
|
|
/* IO memory and memory shared directly out of the pagecache
|
|
* from ramfs/tmpfs mustn't be released here */
|
|
@@ -665,7 +665,7 @@ static void delete_vma(struct mm_struct *mm, struct vm_area_struct *vma)
|
|
if (vma->vm_ops && vma->vm_ops->close)
|
|
vma->vm_ops->close(vma);
|
|
if (vma->vm_file)
|
|
- fput(vma->vm_file);
|
|
+ vma_fput(vma);
|
|
put_nommu_region(vma->vm_region);
|
|
vm_area_free(vma);
|
|
}
|
|
@@ -1188,7 +1188,7 @@ unsigned long do_mmap(struct file *file,
|
|
goto error_just_free;
|
|
}
|
|
}
|
|
- fput(region->vm_file);
|
|
+ vmr_fput(region);
|
|
kmem_cache_free(vm_region_jar, region);
|
|
region = pregion;
|
|
result = start;
|
|
@@ -1265,10 +1265,10 @@ unsigned long do_mmap(struct file *file,
|
|
up_write(&nommu_region_sem);
|
|
error:
|
|
if (region->vm_file)
|
|
- fput(region->vm_file);
|
|
+ vmr_fput(region);
|
|
kmem_cache_free(vm_region_jar, region);
|
|
if (vma->vm_file)
|
|
- fput(vma->vm_file);
|
|
+ vma_fput(vma);
|
|
vm_area_free(vma);
|
|
return ret;
|
|
|
|
diff --git a/mm/prfile.c b/mm/prfile.c
|
|
new file mode 100644
|
|
index 000000000000..00d51187c325
|
|
--- /dev/null
|
|
+++ b/mm/prfile.c
|
|
@@ -0,0 +1,86 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Mainly for aufs which mmap(2) different file and wants to print different
|
|
+ * path in /proc/PID/maps.
|
|
+ * Call these functions via macros defined in linux/mm.h.
|
|
+ *
|
|
+ * See Documentation/filesystems/aufs/design/06mmap.txt
|
|
+ *
|
|
+ * Copyright (c) 2014-2020 Junjro R. Okajima
|
|
+ * Copyright (c) 2014 Ian Campbell
|
|
+ */
|
|
+
|
|
+#include <linux/mm.h>
|
|
+#include <linux/file.h>
|
|
+#include <linux/fs.h>
|
|
+
|
|
+/* #define PRFILE_TRACE */
|
|
+static inline void prfile_trace(struct file *f, struct file *pr,
|
|
+ const char func[], int line, const char func2[])
|
|
+{
|
|
+#ifdef PRFILE_TRACE
|
|
+ if (pr)
|
|
+ pr_info("%s:%d: %s, %pD2\n", func, line, func2, f);
|
|
+#endif
|
|
+}
|
|
+
|
|
+void vma_do_file_update_time(struct vm_area_struct *vma, const char func[],
|
|
+ int line)
|
|
+{
|
|
+ struct file *f = vma->vm_file, *pr = vma->vm_prfile;
|
|
+
|
|
+ prfile_trace(f, pr, func, line, __func__);
|
|
+ file_update_time(f);
|
|
+ if (f && pr)
|
|
+ file_update_time(pr);
|
|
+}
|
|
+
|
|
+struct file *vma_do_pr_or_file(struct vm_area_struct *vma, const char func[],
|
|
+ int line)
|
|
+{
|
|
+ struct file *f = vma->vm_file, *pr = vma->vm_prfile;
|
|
+
|
|
+ prfile_trace(f, pr, func, line, __func__);
|
|
+ return (f && pr) ? pr : f;
|
|
+}
|
|
+
|
|
+void vma_do_get_file(struct vm_area_struct *vma, const char func[], int line)
|
|
+{
|
|
+ struct file *f = vma->vm_file, *pr = vma->vm_prfile;
|
|
+
|
|
+ prfile_trace(f, pr, func, line, __func__);
|
|
+ get_file(f);
|
|
+ if (f && pr)
|
|
+ get_file(pr);
|
|
+}
|
|
+
|
|
+void vma_do_fput(struct vm_area_struct *vma, const char func[], int line)
|
|
+{
|
|
+ struct file *f = vma->vm_file, *pr = vma->vm_prfile;
|
|
+
|
|
+ prfile_trace(f, pr, func, line, __func__);
|
|
+ fput(f);
|
|
+ if (f && pr)
|
|
+ fput(pr);
|
|
+}
|
|
+
|
|
+#ifndef CONFIG_MMU
|
|
+struct file *vmr_do_pr_or_file(struct vm_region *region, const char func[],
|
|
+ int line)
|
|
+{
|
|
+ struct file *f = region->vm_file, *pr = region->vm_prfile;
|
|
+
|
|
+ prfile_trace(f, pr, func, line, __func__);
|
|
+ return (f && pr) ? pr : f;
|
|
+}
|
|
+
|
|
+void vmr_do_fput(struct vm_region *region, const char func[], int line)
|
|
+{
|
|
+ struct file *f = region->vm_file, *pr = region->vm_prfile;
|
|
+
|
|
+ prfile_trace(f, pr, func, line, __func__);
|
|
+ fput(f);
|
|
+ if (f && pr)
|
|
+ fput(pr);
|
|
+}
|
|
+#endif /* !CONFIG_MMU */
|
|
diff --git a/security/security.c b/security/security.c
|
|
index a28045dc9e7f..310cf38efeec 100644
|
|
--- a/security/security.c
|
|
+++ b/security/security.c
|
|
@@ -1093,6 +1093,7 @@ int security_path_rmdir(const struct path *dir, struct dentry *dentry)
|
|
return 0;
|
|
return call_int_hook(path_rmdir, 0, dir, dentry);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(security_path_rmdir);
|
|
|
|
int security_path_unlink(const struct path *dir, struct dentry *dentry)
|
|
{
|
|
@@ -1109,6 +1110,7 @@ int security_path_symlink(const struct path *dir, struct dentry *dentry,
|
|
return 0;
|
|
return call_int_hook(path_symlink, 0, dir, dentry, old_name);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(security_path_symlink);
|
|
|
|
int security_path_link(struct dentry *old_dentry, const struct path *new_dir,
|
|
struct dentry *new_dentry)
|
|
@@ -1117,6 +1119,7 @@ int security_path_link(struct dentry *old_dentry, const struct path *new_dir,
|
|
return 0;
|
|
return call_int_hook(path_link, 0, old_dentry, new_dir, new_dentry);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(security_path_link);
|
|
|
|
int security_path_rename(const struct path *old_dir, struct dentry *old_dentry,
|
|
const struct path *new_dir, struct dentry *new_dentry,
|
|
@@ -1144,6 +1147,7 @@ int security_path_truncate(const struct path *path)
|
|
return 0;
|
|
return call_int_hook(path_truncate, 0, path);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(security_path_truncate);
|
|
|
|
int security_path_chmod(const struct path *path, umode_t mode)
|
|
{
|
|
@@ -1151,6 +1155,7 @@ int security_path_chmod(const struct path *path, umode_t mode)
|
|
return 0;
|
|
return call_int_hook(path_chmod, 0, path, mode);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(security_path_chmod);
|
|
|
|
int security_path_chown(const struct path *path, kuid_t uid, kgid_t gid)
|
|
{
|
|
@@ -1158,6 +1163,7 @@ int security_path_chown(const struct path *path, kuid_t uid, kgid_t gid)
|
|
return 0;
|
|
return call_int_hook(path_chown, 0, path, uid, gid);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(security_path_chown);
|
|
|
|
int security_path_chroot(const struct path *path)
|
|
{
|
|
@@ -1258,6 +1264,7 @@ int security_inode_permission(struct inode *inode, int mask)
|
|
return 0;
|
|
return call_int_hook(inode_permission, 0, inode, mask);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(security_inode_permission);
|
|
|
|
int security_inode_setattr(struct dentry *dentry, struct iattr *attr)
|
|
{
|
|
@@ -1450,6 +1457,7 @@ int security_file_permission(struct file *file, int mask)
|
|
|
|
return fsnotify_perm(file, mask);
|
|
}
|
|
+EXPORT_SYMBOL_GPL(security_file_permission);
|
|
|
|
int security_file_alloc(struct file *file)
|
|
{
|
|
--
|
|
2.25.1
|
|
|