mirror of
https://github.com/a52xq-dev/kernel.git
synced 2026-10-18 21:58:11 +07:00
overlayfs update for 4.19
This contains two new features:
1) Stack file operations: this allows removal of several hacks from the
VFS, proper interaction of read-only open files with copy-up,
possibility to implement fs modifying ioctls properly, and others.
2) Metadata only copy-up: when file is on lower layer and only metadata is
modified (except size) then only copy up the metadata and continue to
use the data from the lower file.
-----BEGIN PGP SIGNATURE-----
iHUEABYIAB0WIQSQHSd0lITzzeNWNm3h3BK/laaZPAUCW3srhAAKCRDh3BK/laaZ
PC6tAQCP+KklcN+TvNp502f+O/kATahSpgnun4NY1/p4I8JV+AEAzdlkTN3+MiAO
fn9brN6mBK7h59DO3hqedPLJy2vrgwg=
=QDXH
-----END PGP SIGNATURE-----
Merge tag 'ovl-update-4.19' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
Pull overlayfs updates from Miklos Szeredi:
"This contains two new features:
- Stack file operations: this allows removal of several hacks from
the VFS, proper interaction of read-only open files with copy-up,
possibility to implement fs modifying ioctls properly, and others.
- Metadata only copy-up: when file is on lower layer and only
metadata is modified (except size) then only copy up the metadata
and continue to use the data from the lower file"
* tag 'ovl-update-4.19' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs: (66 commits)
ovl: Enable metadata only feature
ovl: Do not do metacopy only for ioctl modifying file attr
ovl: Do not do metadata only copy-up for truncate operation
ovl: add helper to force data copy-up
ovl: Check redirect on index as well
ovl: Set redirect on upper inode when it is linked
ovl: Set redirect on metacopy files upon rename
ovl: Do not set dentry type ORIGIN for broken hardlinks
ovl: Add an inode flag OVL_CONST_INO
ovl: Treat metacopy dentries as type OVL_PATH_MERGE
ovl: Check redirects for metacopy files
ovl: Move some dir related ovl_lookup_single() code in else block
ovl: Do not expose metacopy only dentry from d_real()
ovl: Open file with data except for the case of fsync
ovl: Add helper ovl_inode_realdata()
ovl: Store lower data inode in ovl_inode
ovl: Fix ovl_getattr() to get number of blocks from lower
ovl: Add helper ovl_dentry_lowerdata() to get lower data dentry
ovl: Copy up meta inode data from lowest data inode
ovl: Modify ovl_lookup() and friends to lookup metacopy dentry
...
This commit is contained in:
commit
d9a185f8b4
33 changed files with 1619 additions and 595 deletions
|
|
@ -21,8 +21,7 @@ prototypes:
|
|||
char *(*d_dname)((struct dentry *dentry, char *buffer, int buflen);
|
||||
struct vfsmount *(*d_automount)(struct path *path);
|
||||
int (*d_manage)(const struct path *, bool);
|
||||
struct dentry *(*d_real)(struct dentry *, const struct inode *,
|
||||
unsigned int, unsigned int);
|
||||
struct dentry *(*d_real)(struct dentry *, const struct inode *);
|
||||
|
||||
locking rules:
|
||||
rename_lock ->d_lock may block rcu-walk
|
||||
|
|
|
|||
|
|
@ -10,10 +10,6 @@ union-filesystems). An overlay-filesystem tries to present a
|
|||
filesystem which is the result over overlaying one filesystem on top
|
||||
of the other.
|
||||
|
||||
The result will inevitably fail to look exactly like a normal
|
||||
filesystem for various technical reasons. The expectation is that
|
||||
many use cases will be able to ignore these differences.
|
||||
|
||||
|
||||
Overlay objects
|
||||
---------------
|
||||
|
|
@ -266,6 +262,30 @@ rightmost one and going left. In the above example lower1 will be the
|
|||
top, lower2 the middle and lower3 the bottom layer.
|
||||
|
||||
|
||||
Metadata only copy up
|
||||
--------------------
|
||||
|
||||
When metadata only copy up feature is enabled, overlayfs will only copy
|
||||
up metadata (as opposed to whole file), when a metadata specific operation
|
||||
like chown/chmod is performed. Full file will be copied up later when
|
||||
file is opened for WRITE operation.
|
||||
|
||||
In other words, this is delayed data copy up operation and data is copied
|
||||
up when there is a need to actually modify data.
|
||||
|
||||
There are multiple ways to enable/disable this feature. A config option
|
||||
CONFIG_OVERLAY_FS_METACOPY can be set/unset to enable/disable this feature
|
||||
by default. Or one can enable/disable it at module load time with module
|
||||
parameter metacopy=on/off. Lastly, there is also a per mount option
|
||||
metacopy=on/off to enable/disable this feature per mount.
|
||||
|
||||
Do not use metacopy=on with untrusted upper/lower directories. Otherwise
|
||||
it is possible that an attacker can create a handcrafted file with
|
||||
appropriate REDIRECT and METACOPY xattrs, and gain access to file on lower
|
||||
pointed by REDIRECT. This should not be possible on local system as setting
|
||||
"trusted." xattrs will require CAP_SYS_ADMIN. But it should be possible
|
||||
for untrusted layers like from a pen drive.
|
||||
|
||||
Sharing and copying layers
|
||||
--------------------------
|
||||
|
||||
|
|
@ -284,7 +304,7 @@ though it will not result in a crash or deadlock.
|
|||
Mounting an overlay using an upper layer path, where the upper layer path
|
||||
was previously used by another mounted overlay in combination with a
|
||||
different lower layer path, is allowed, unless the "inodes index" feature
|
||||
is enabled.
|
||||
or "metadata only copy up" feature is enabled.
|
||||
|
||||
With the "inodes index" feature, on the first time mount, an NFS file
|
||||
handle of the lower layer root directory, along with the UUID of the lower
|
||||
|
|
@ -297,6 +317,10 @@ lower root origin, mount will fail with ESTALE. An overlayfs mount with
|
|||
does not support NFS export, lower filesystem does not have a valid UUID or
|
||||
if the upper filesystem does not support extended attributes.
|
||||
|
||||
For "metadata only copy up" feature there is no verification mechanism at
|
||||
mount time. So if same upper is mounted with different set of lower, mount
|
||||
probably will succeed but expect the unexpected later on. So don't do it.
|
||||
|
||||
It is quite a common practice to copy overlay layers to a different
|
||||
directory tree on the same or different underlying filesystem, and even
|
||||
to a different machine. With the "inodes index" feature, trying to mount
|
||||
|
|
@ -306,27 +330,40 @@ the copied layers will fail the verification of the lower root file handle.
|
|||
Non-standard behavior
|
||||
---------------------
|
||||
|
||||
The copy_up operation essentially creates a new, identical file and
|
||||
moves it over to the old name. Any open files referring to this inode
|
||||
will access the old data.
|
||||
Overlayfs can now act as a POSIX compliant filesystem with the following
|
||||
features turned on:
|
||||
|
||||
The new file may be on a different filesystem, so both st_dev and st_ino
|
||||
of the real file may change. The values of st_dev and st_ino returned by
|
||||
stat(2) on an overlay object are often not the same as the real file
|
||||
stat(2) values to prevent the values from changing on copy_up.
|
||||
1) "redirect_dir"
|
||||
|
||||
Unless "xino" feature is enabled, when overlay layers are not all on the
|
||||
same underlying filesystem, the value of st_dev may be different for two
|
||||
non-directory objects in the same overlay filesystem and the value of
|
||||
st_ino for directory objects may be non persistent and could change even
|
||||
while the overlay filesystem is still mounted.
|
||||
Enabled with the mount option or module option: "redirect_dir=on" or with
|
||||
the kernel config option CONFIG_OVERLAY_FS_REDIRECT_DIR=y.
|
||||
|
||||
Unless "inode index" feature is enabled, if a file with multiple hard
|
||||
links is copied up, then this will "break" the link. Changes will not be
|
||||
propagated to other names referring to the same inode.
|
||||
If this feature is disabled, then rename(2) on a lower or merged directory
|
||||
will fail with EXDEV ("Invalid cross-device link").
|
||||
|
||||
Unless "redirect_dir" feature is enabled, rename(2) on a lower or merged
|
||||
directory will fail with EXDEV.
|
||||
2) "inode index"
|
||||
|
||||
Enabled with the mount option or module option "index=on" or with the
|
||||
kernel config option CONFIG_OVERLAY_FS_INDEX=y.
|
||||
|
||||
If this feature is disabled and a file with multiple hard links is copied
|
||||
up, then this will "break" the link. Changes will not be propagated to
|
||||
other names referring to the same inode.
|
||||
|
||||
3) "xino"
|
||||
|
||||
Enabled with the mount option "xino=auto" or "xino=on", with the module
|
||||
option "xino_auto=on" or with the kernel config option
|
||||
CONFIG_OVERLAY_FS_XINO_AUTO=y. Also implicitly enabled by using the same
|
||||
underlying filesystem for all layers making up the overlay.
|
||||
|
||||
If this feature is disabled or the underlying filesystem doesn't have
|
||||
enough free bits in the inode number, then overlayfs will not be able to
|
||||
guarantee that the values of st_ino and st_dev returned by stat(2) and the
|
||||
value of d_ino returned by readdir(3) will act like on a normal filesystem.
|
||||
E.g. the value of st_dev may be different for two objects in the same
|
||||
overlay filesystem and the value of st_ino for directory objects may not be
|
||||
persistent and could change even while the overlay filesystem is mounted.
|
||||
|
||||
|
||||
Changes to underlying filesystems
|
||||
|
|
|
|||
|
|
@ -989,8 +989,7 @@ struct dentry_operations {
|
|||
char *(*d_dname)(struct dentry *, char *, int);
|
||||
struct vfsmount *(*d_automount)(struct path *);
|
||||
int (*d_manage)(const struct path *, bool);
|
||||
struct dentry *(*d_real)(struct dentry *, const struct inode *,
|
||||
unsigned int, unsigned int);
|
||||
struct dentry *(*d_real)(struct dentry *, const struct inode *);
|
||||
};
|
||||
|
||||
d_revalidate: called when the VFS needs to revalidate a dentry. This
|
||||
|
|
@ -1124,22 +1123,15 @@ struct dentry_operations {
|
|||
dentry being transited from.
|
||||
|
||||
d_real: overlay/union type filesystems implement this method to return one of
|
||||
the underlying dentries hidden by the overlay. It is used in three
|
||||
the underlying dentries hidden by the overlay. It is used in two
|
||||
different modes:
|
||||
|
||||
Called from open it may need to copy-up the file depending on the
|
||||
supplied open flags. This mode is selected with a non-zero flags
|
||||
argument. In this mode the d_real method can return an error.
|
||||
|
||||
Called from file_dentry() it returns the real dentry matching the inode
|
||||
argument. The real dentry may be from a lower layer already copied up,
|
||||
but still referenced from the file. This mode is selected with a
|
||||
non-NULL inode argument. This will always succeed.
|
||||
non-NULL inode argument.
|
||||
|
||||
With NULL inode and zero flags the topmost real underlying dentry is
|
||||
returned. This will always succeed.
|
||||
|
||||
This method is never called with both non-NULL inode and non-zero flags.
|
||||
With NULL inode the topmost real underlying dentry is returned.
|
||||
|
||||
Each dentry has a pointer to its parent dentry, as well as a hash list
|
||||
of child dentries. Child dentries are basically like files in a
|
||||
|
|
|
|||
|
|
@ -3217,8 +3217,9 @@ void btrfs_get_block_group_info(struct list_head *groups_list,
|
|||
struct btrfs_ioctl_space_info *space);
|
||||
void btrfs_update_ioctl_balance_args(struct btrfs_fs_info *fs_info,
|
||||
struct btrfs_ioctl_balance_args *bargs);
|
||||
ssize_t btrfs_dedupe_file_range(struct file *src_file, u64 loff, u64 olen,
|
||||
struct file *dst_file, u64 dst_loff);
|
||||
int btrfs_dedupe_file_range(struct file *src_file, loff_t src_loff,
|
||||
struct file *dst_file, loff_t dst_loff,
|
||||
u64 olen);
|
||||
|
||||
/* file.c */
|
||||
int __init btrfs_auto_defrag_init(void);
|
||||
|
|
|
|||
|
|
@ -3592,13 +3592,13 @@ out_unlock:
|
|||
return ret;
|
||||
}
|
||||
|
||||
ssize_t btrfs_dedupe_file_range(struct file *src_file, u64 loff, u64 olen,
|
||||
struct file *dst_file, u64 dst_loff)
|
||||
int btrfs_dedupe_file_range(struct file *src_file, loff_t src_loff,
|
||||
struct file *dst_file, loff_t dst_loff,
|
||||
u64 olen)
|
||||
{
|
||||
struct inode *src = file_inode(src_file);
|
||||
struct inode *dst = file_inode(dst_file);
|
||||
u64 bs = BTRFS_I(src)->root->fs_info->sb->s_blocksize;
|
||||
ssize_t res;
|
||||
|
||||
if (WARN_ON_ONCE(bs < PAGE_SIZE)) {
|
||||
/*
|
||||
|
|
@ -3609,10 +3609,7 @@ ssize_t btrfs_dedupe_file_range(struct file *src_file, u64 loff, u64 olen,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
res = btrfs_extent_same(src, loff, olen, dst, dst_loff);
|
||||
if (res)
|
||||
return res;
|
||||
return olen;
|
||||
return btrfs_extent_same(src, src_loff, olen, dst, dst_loff);
|
||||
}
|
||||
|
||||
static int clone_finish_inode_update(struct btrfs_trans_handle *trans,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ static void file_free_rcu(struct rcu_head *head)
|
|||
static inline void file_free(struct file *f)
|
||||
{
|
||||
security_file_free(f);
|
||||
percpu_counter_dec(&nr_files);
|
||||
if (!(f->f_mode & FMODE_NOACCOUNT))
|
||||
percpu_counter_dec(&nr_files);
|
||||
call_rcu(&f->f_u.fu_rcuhead, file_free_rcu);
|
||||
}
|
||||
|
||||
|
|
@ -91,34 +92,11 @@ int proc_nr_files(struct ctl_table *table, int write,
|
|||
}
|
||||
#endif
|
||||
|
||||
/* Find an unused file structure and return a pointer to it.
|
||||
* Returns an error pointer if some error happend e.g. we over file
|
||||
* structures limit, run out of memory or operation is not permitted.
|
||||
*
|
||||
* Be very careful using this. You are responsible for
|
||||
* getting write access to any mount that you might assign
|
||||
* to this filp, if it is opened for write. If this is not
|
||||
* done, you will imbalance int the mount's writer count
|
||||
* and a warning at __fput() time.
|
||||
*/
|
||||
struct file *alloc_empty_file(int flags, const struct cred *cred)
|
||||
static struct file *__alloc_file(int flags, const struct cred *cred)
|
||||
{
|
||||
static long old_max;
|
||||
struct file *f;
|
||||
int error;
|
||||
|
||||
/*
|
||||
* Privileged users can go above max_files
|
||||
*/
|
||||
if (get_nr_files() >= files_stat.max_files && !capable(CAP_SYS_ADMIN)) {
|
||||
/*
|
||||
* percpu_counters are inaccurate. Do an expensive check before
|
||||
* we go and fail.
|
||||
*/
|
||||
if (percpu_counter_sum_positive(&nr_files) >= files_stat.max_files)
|
||||
goto over;
|
||||
}
|
||||
|
||||
f = kmem_cache_zalloc(filp_cachep, GFP_KERNEL);
|
||||
if (unlikely(!f))
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
|
@ -138,7 +116,41 @@ struct file *alloc_empty_file(int flags, const struct cred *cred)
|
|||
f->f_flags = flags;
|
||||
f->f_mode = OPEN_FMODE(flags);
|
||||
/* f->f_version: 0 */
|
||||
percpu_counter_inc(&nr_files);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/* Find an unused file structure and return a pointer to it.
|
||||
* Returns an error pointer if some error happend e.g. we over file
|
||||
* structures limit, run out of memory or operation is not permitted.
|
||||
*
|
||||
* Be very careful using this. You are responsible for
|
||||
* getting write access to any mount that you might assign
|
||||
* to this filp, if it is opened for write. If this is not
|
||||
* done, you will imbalance int the mount's writer count
|
||||
* and a warning at __fput() time.
|
||||
*/
|
||||
struct file *alloc_empty_file(int flags, const struct cred *cred)
|
||||
{
|
||||
static long old_max;
|
||||
struct file *f;
|
||||
|
||||
/*
|
||||
* Privileged users can go above max_files
|
||||
*/
|
||||
if (get_nr_files() >= files_stat.max_files && !capable(CAP_SYS_ADMIN)) {
|
||||
/*
|
||||
* percpu_counters are inaccurate. Do an expensive check before
|
||||
* we go and fail.
|
||||
*/
|
||||
if (percpu_counter_sum_positive(&nr_files) >= files_stat.max_files)
|
||||
goto over;
|
||||
}
|
||||
|
||||
f = __alloc_file(flags, cred);
|
||||
if (!IS_ERR(f))
|
||||
percpu_counter_inc(&nr_files);
|
||||
|
||||
return f;
|
||||
|
||||
over:
|
||||
|
|
@ -150,6 +162,21 @@ over:
|
|||
return ERR_PTR(-ENFILE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Variant of alloc_empty_file() that doesn't check and modify nr_files.
|
||||
*
|
||||
* Should not be used unless there's a very good reason to do so.
|
||||
*/
|
||||
struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred)
|
||||
{
|
||||
struct file *f = __alloc_file(flags, cred);
|
||||
|
||||
if (!IS_ERR(f))
|
||||
f->f_mode |= FMODE_NOACCOUNT;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* alloc_file - allocate and initialize a 'struct file'
|
||||
*
|
||||
|
|
|
|||
46
fs/inode.c
46
fs/inode.c
|
|
@ -1595,50 +1595,17 @@ sector_t bmap(struct inode *inode, sector_t block)
|
|||
}
|
||||
EXPORT_SYMBOL(bmap);
|
||||
|
||||
/*
|
||||
* Update times in overlayed inode from underlying real inode
|
||||
*/
|
||||
static void update_ovl_inode_times(struct dentry *dentry, struct inode *inode,
|
||||
bool rcu)
|
||||
{
|
||||
struct dentry *upperdentry;
|
||||
|
||||
/*
|
||||
* Nothing to do if in rcu or if non-overlayfs
|
||||
*/
|
||||
if (rcu || likely(!(dentry->d_flags & DCACHE_OP_REAL)))
|
||||
return;
|
||||
|
||||
upperdentry = d_real(dentry, NULL, 0, D_REAL_UPPER);
|
||||
|
||||
/*
|
||||
* If file is on lower then we can't update atime, so no worries about
|
||||
* stale mtime/ctime.
|
||||
*/
|
||||
if (upperdentry) {
|
||||
struct inode *realinode = d_inode(upperdentry);
|
||||
|
||||
if ((!timespec64_equal(&inode->i_mtime, &realinode->i_mtime) ||
|
||||
!timespec64_equal(&inode->i_ctime, &realinode->i_ctime))) {
|
||||
inode->i_mtime = realinode->i_mtime;
|
||||
inode->i_ctime = realinode->i_ctime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* With relative atime, only update atime if the previous atime is
|
||||
* earlier than either the ctime or mtime or if at least a day has
|
||||
* passed since the last atime update.
|
||||
*/
|
||||
static int relatime_need_update(const struct path *path, struct inode *inode,
|
||||
struct timespec now, bool rcu)
|
||||
static int relatime_need_update(struct vfsmount *mnt, struct inode *inode,
|
||||
struct timespec now)
|
||||
{
|
||||
|
||||
if (!(path->mnt->mnt_flags & MNT_RELATIME))
|
||||
if (!(mnt->mnt_flags & MNT_RELATIME))
|
||||
return 1;
|
||||
|
||||
update_ovl_inode_times(path->dentry, inode, rcu);
|
||||
/*
|
||||
* Is mtime younger than atime? If yes, update atime:
|
||||
*/
|
||||
|
|
@ -1709,8 +1676,7 @@ static int update_time(struct inode *inode, struct timespec64 *time, int flags)
|
|||
* This function automatically handles read only file systems and media,
|
||||
* as well as the "noatime" flag and inode specific "noatime" markers.
|
||||
*/
|
||||
bool __atime_needs_update(const struct path *path, struct inode *inode,
|
||||
bool rcu)
|
||||
bool atime_needs_update(const struct path *path, struct inode *inode)
|
||||
{
|
||||
struct vfsmount *mnt = path->mnt;
|
||||
struct timespec64 now;
|
||||
|
|
@ -1736,7 +1702,7 @@ bool __atime_needs_update(const struct path *path, struct inode *inode,
|
|||
|
||||
now = current_time(inode);
|
||||
|
||||
if (!relatime_need_update(path, inode, timespec64_to_timespec(now), rcu))
|
||||
if (!relatime_need_update(mnt, inode, timespec64_to_timespec(now)))
|
||||
return false;
|
||||
|
||||
if (timespec64_equal(&inode->i_atime, &now))
|
||||
|
|
@ -1751,7 +1717,7 @@ void touch_atime(const struct path *path)
|
|||
struct inode *inode = d_inode(path->dentry);
|
||||
struct timespec64 now;
|
||||
|
||||
if (!__atime_needs_update(path, inode, false))
|
||||
if (!atime_needs_update(path, inode))
|
||||
return;
|
||||
|
||||
if (!sb_start_write_trylock(inode->i_sb))
|
||||
|
|
|
|||
|
|
@ -82,10 +82,8 @@ extern void __init mnt_init(void);
|
|||
|
||||
extern int __mnt_want_write(struct vfsmount *);
|
||||
extern int __mnt_want_write_file(struct file *);
|
||||
extern int mnt_want_write_file_path(struct file *);
|
||||
extern void __mnt_drop_write(struct vfsmount *);
|
||||
extern void __mnt_drop_write_file(struct file *);
|
||||
extern void mnt_drop_write_file_path(struct file *);
|
||||
|
||||
/*
|
||||
* fs_struct.c
|
||||
|
|
@ -96,6 +94,7 @@ extern void chroot_fs_refs(const struct path *, const struct path *);
|
|||
* file_table.c
|
||||
*/
|
||||
extern struct file *alloc_empty_file(int, const struct cred *);
|
||||
extern struct file *alloc_empty_file_noaccount(int, const struct cred *);
|
||||
|
||||
/*
|
||||
* super.c
|
||||
|
|
@ -136,13 +135,6 @@ extern long prune_icache_sb(struct super_block *sb, struct shrink_control *sc);
|
|||
extern void inode_add_lru(struct inode *inode);
|
||||
extern int dentry_needs_remove_privs(struct dentry *dentry);
|
||||
|
||||
extern bool __atime_needs_update(const struct path *, struct inode *, bool);
|
||||
static inline bool atime_needs_update_rcu(const struct path *path,
|
||||
struct inode *inode)
|
||||
{
|
||||
return __atime_needs_update(path, inode, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* fs-writeback.c
|
||||
*/
|
||||
|
|
@ -185,7 +177,6 @@ extern const struct dentry_operations ns_dentry_operations;
|
|||
*/
|
||||
extern int do_vfs_ioctl(struct file *file, unsigned int fd, unsigned int cmd,
|
||||
unsigned long arg);
|
||||
extern long vfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
|
||||
|
||||
/*
|
||||
* iomap support:
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ long vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|||
out:
|
||||
return error;
|
||||
}
|
||||
EXPORT_SYMBOL(vfs_ioctl);
|
||||
|
||||
static int ioctl_fibmap(struct file *filp, int __user *p)
|
||||
{
|
||||
|
|
|
|||
20
fs/locks.c
20
fs/locks.c
|
|
@ -139,11 +139,6 @@
|
|||
#define IS_OFDLCK(fl) (fl->fl_flags & FL_OFDLCK)
|
||||
#define IS_REMOTELCK(fl) (fl->fl_pid <= 0)
|
||||
|
||||
static inline bool is_remote_lock(struct file *filp)
|
||||
{
|
||||
return likely(!(filp->f_path.dentry->d_sb->s_flags & SB_NOREMOTELOCK));
|
||||
}
|
||||
|
||||
static bool lease_breaking(struct file_lock *fl)
|
||||
{
|
||||
return fl->fl_flags & (FL_UNLOCK_PENDING | FL_DOWNGRADE_PENDING);
|
||||
|
|
@ -1651,8 +1646,7 @@ check_conflicting_open(const struct dentry *dentry, const long arg, int flags)
|
|||
if (flags & FL_LAYOUT)
|
||||
return 0;
|
||||
|
||||
if ((arg == F_RDLCK) &&
|
||||
(atomic_read(&d_real_inode(dentry)->i_writecount) > 0))
|
||||
if ((arg == F_RDLCK) && (atomic_read(&inode->i_writecount) > 0))
|
||||
return -EAGAIN;
|
||||
|
||||
if ((arg == F_WRLCK) && ((d_count(dentry) > 1) ||
|
||||
|
|
@ -1873,7 +1867,7 @@ EXPORT_SYMBOL(generic_setlease);
|
|||
int
|
||||
vfs_setlease(struct file *filp, long arg, struct file_lock **lease, void **priv)
|
||||
{
|
||||
if (filp->f_op->setlease && is_remote_lock(filp))
|
||||
if (filp->f_op->setlease)
|
||||
return filp->f_op->setlease(filp, arg, lease, priv);
|
||||
else
|
||||
return generic_setlease(filp, arg, lease, priv);
|
||||
|
|
@ -2020,7 +2014,7 @@ SYSCALL_DEFINE2(flock, unsigned int, fd, unsigned int, cmd)
|
|||
if (error)
|
||||
goto out_free;
|
||||
|
||||
if (f.file->f_op->flock && is_remote_lock(f.file))
|
||||
if (f.file->f_op->flock)
|
||||
error = f.file->f_op->flock(f.file,
|
||||
(can_sleep) ? F_SETLKW : F_SETLK,
|
||||
lock);
|
||||
|
|
@ -2046,7 +2040,7 @@ SYSCALL_DEFINE2(flock, unsigned int, fd, unsigned int, cmd)
|
|||
*/
|
||||
int vfs_test_lock(struct file *filp, struct file_lock *fl)
|
||||
{
|
||||
if (filp->f_op->lock && is_remote_lock(filp))
|
||||
if (filp->f_op->lock)
|
||||
return filp->f_op->lock(filp, F_GETLK, fl);
|
||||
posix_test_lock(filp, fl);
|
||||
return 0;
|
||||
|
|
@ -2196,7 +2190,7 @@ out:
|
|||
*/
|
||||
int vfs_lock_file(struct file *filp, unsigned int cmd, struct file_lock *fl, struct file_lock *conf)
|
||||
{
|
||||
if (filp->f_op->lock && is_remote_lock(filp))
|
||||
if (filp->f_op->lock)
|
||||
return filp->f_op->lock(filp, cmd, fl);
|
||||
else
|
||||
return posix_lock_file(filp, fl, conf);
|
||||
|
|
@ -2518,7 +2512,7 @@ locks_remove_flock(struct file *filp, struct file_lock_context *flctx)
|
|||
if (list_empty(&flctx->flc_flock))
|
||||
return;
|
||||
|
||||
if (filp->f_op->flock && is_remote_lock(filp))
|
||||
if (filp->f_op->flock)
|
||||
filp->f_op->flock(filp, F_SETLKW, &fl);
|
||||
else
|
||||
flock_lock_inode(inode, &fl);
|
||||
|
|
@ -2605,7 +2599,7 @@ EXPORT_SYMBOL(posix_unblock_lock);
|
|||
*/
|
||||
int vfs_cancel_lock(struct file *filp, struct file_lock *fl)
|
||||
{
|
||||
if (filp->f_op->lock && is_remote_lock(filp))
|
||||
if (filp->f_op->lock)
|
||||
return filp->f_op->lock(filp, F_CANCELLK, fl);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1015,7 +1015,7 @@ const char *get_link(struct nameidata *nd)
|
|||
if (!(nd->flags & LOOKUP_RCU)) {
|
||||
touch_atime(&last->link);
|
||||
cond_resched();
|
||||
} else if (atime_needs_update_rcu(&last->link, inode)) {
|
||||
} else if (atime_needs_update(&last->link, inode)) {
|
||||
if (unlikely(unlazy_walk(nd)))
|
||||
return ERR_PTR(-ECHILD);
|
||||
touch_atime(&last->link);
|
||||
|
|
|
|||
|
|
@ -430,75 +430,21 @@ int __mnt_want_write_file(struct file *file)
|
|||
return mnt_clone_write(file->f_path.mnt);
|
||||
}
|
||||
|
||||
/**
|
||||
* mnt_want_write_file_path - get write access to a file's mount
|
||||
* @file: the file who's mount on which to take a write
|
||||
*
|
||||
* This is like mnt_want_write, but it takes a file and can
|
||||
* do some optimisations if the file is open for write already
|
||||
*
|
||||
* Called by the vfs for cases when we have an open file at hand, but will do an
|
||||
* inode operation on it (important distinction for files opened on overlayfs,
|
||||
* since the file operations will come from the real underlying file, while
|
||||
* inode operations come from the overlay).
|
||||
*/
|
||||
int mnt_want_write_file_path(struct file *file)
|
||||
{
|
||||
int ret;
|
||||
|
||||
sb_start_write(file->f_path.mnt->mnt_sb);
|
||||
ret = __mnt_want_write_file(file);
|
||||
if (ret)
|
||||
sb_end_write(file->f_path.mnt->mnt_sb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int may_write_real(struct file *file)
|
||||
{
|
||||
struct dentry *dentry = file->f_path.dentry;
|
||||
struct dentry *upperdentry;
|
||||
|
||||
/* Writable file? */
|
||||
if (file->f_mode & FMODE_WRITER)
|
||||
return 0;
|
||||
|
||||
/* Not overlayfs? */
|
||||
if (likely(!(dentry->d_flags & DCACHE_OP_REAL)))
|
||||
return 0;
|
||||
|
||||
/* File refers to upper, writable layer? */
|
||||
upperdentry = d_real(dentry, NULL, 0, D_REAL_UPPER);
|
||||
if (upperdentry &&
|
||||
(file_inode(file) == d_inode(upperdentry) ||
|
||||
file_inode(file) == d_inode(dentry)))
|
||||
return 0;
|
||||
|
||||
/* Lower layer: can't write to real file, sorry... */
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
/**
|
||||
* mnt_want_write_file - get write access to a file's mount
|
||||
* @file: the file who's mount on which to take a write
|
||||
*
|
||||
* This is like mnt_want_write, but it takes a file and can
|
||||
* do some optimisations if the file is open for write already
|
||||
*
|
||||
* Mostly called by filesystems from their ioctl operation before performing
|
||||
* modification. On overlayfs this needs to check if the file is on a read-only
|
||||
* lower layer and deny access in that case.
|
||||
*/
|
||||
int mnt_want_write_file(struct file *file)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = may_write_real(file);
|
||||
if (!ret) {
|
||||
sb_start_write(file_inode(file)->i_sb);
|
||||
ret = __mnt_want_write_file(file);
|
||||
if (ret)
|
||||
sb_end_write(file_inode(file)->i_sb);
|
||||
}
|
||||
sb_start_write(file_inode(file)->i_sb);
|
||||
ret = __mnt_want_write_file(file);
|
||||
if (ret)
|
||||
sb_end_write(file_inode(file)->i_sb);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mnt_want_write_file);
|
||||
|
|
@ -538,14 +484,9 @@ void __mnt_drop_write_file(struct file *file)
|
|||
__mnt_drop_write(file->f_path.mnt);
|
||||
}
|
||||
|
||||
void mnt_drop_write_file_path(struct file *file)
|
||||
{
|
||||
mnt_drop_write(file->f_path.mnt);
|
||||
}
|
||||
|
||||
void mnt_drop_write_file(struct file *file)
|
||||
{
|
||||
__mnt_drop_write(file->f_path.mnt);
|
||||
__mnt_drop_write_file(file);
|
||||
sb_end_write(file_inode(file)->i_sb);
|
||||
}
|
||||
EXPORT_SYMBOL(mnt_drop_write_file);
|
||||
|
|
|
|||
|
|
@ -2537,19 +2537,14 @@ static int ocfs2_file_clone_range(struct file *file_in,
|
|||
len, false);
|
||||
}
|
||||
|
||||
static ssize_t ocfs2_file_dedupe_range(struct file *src_file,
|
||||
u64 loff,
|
||||
u64 len,
|
||||
struct file *dst_file,
|
||||
u64 dst_loff)
|
||||
static int ocfs2_file_dedupe_range(struct file *file_in,
|
||||
loff_t pos_in,
|
||||
struct file *file_out,
|
||||
loff_t pos_out,
|
||||
u64 len)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = ocfs2_reflink_remap_range(src_file, loff, dst_file, dst_loff,
|
||||
return ocfs2_reflink_remap_range(file_in, pos_in, file_out, pos_out,
|
||||
len, true);
|
||||
if (error)
|
||||
return error;
|
||||
return len;
|
||||
}
|
||||
|
||||
const struct inode_operations ocfs2_file_iops = {
|
||||
|
|
|
|||
44
fs/open.c
44
fs/open.c
|
|
@ -68,7 +68,6 @@ int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs,
|
|||
long vfs_truncate(const struct path *path, loff_t length)
|
||||
{
|
||||
struct inode *inode;
|
||||
struct dentry *upperdentry;
|
||||
long error;
|
||||
|
||||
inode = path->dentry->d_inode;
|
||||
|
|
@ -91,17 +90,7 @@ long vfs_truncate(const struct path *path, loff_t length)
|
|||
if (IS_APPEND(inode))
|
||||
goto mnt_drop_write_and_out;
|
||||
|
||||
/*
|
||||
* If this is an overlayfs then do as if opening the file so we get
|
||||
* write access on the upper inode, not on the overlay inode. For
|
||||
* non-overlay filesystems d_real() is an identity function.
|
||||
*/
|
||||
upperdentry = d_real(path->dentry, NULL, O_WRONLY, 0);
|
||||
error = PTR_ERR(upperdentry);
|
||||
if (IS_ERR(upperdentry))
|
||||
goto mnt_drop_write_and_out;
|
||||
|
||||
error = get_write_access(upperdentry->d_inode);
|
||||
error = get_write_access(inode);
|
||||
if (error)
|
||||
goto mnt_drop_write_and_out;
|
||||
|
||||
|
|
@ -120,7 +109,7 @@ long vfs_truncate(const struct path *path, loff_t length)
|
|||
error = do_truncate(path->dentry, length, 0, NULL);
|
||||
|
||||
put_write_and_out:
|
||||
put_write_access(upperdentry->d_inode);
|
||||
put_write_access(inode);
|
||||
mnt_drop_write_and_out:
|
||||
mnt_drop_write(path->mnt);
|
||||
out:
|
||||
|
|
@ -707,12 +696,12 @@ int ksys_fchown(unsigned int fd, uid_t user, gid_t group)
|
|||
if (!f.file)
|
||||
goto out;
|
||||
|
||||
error = mnt_want_write_file_path(f.file);
|
||||
error = mnt_want_write_file(f.file);
|
||||
if (error)
|
||||
goto out_fput;
|
||||
audit_file(f.file);
|
||||
error = chown_common(&f.file->f_path, user, group);
|
||||
mnt_drop_write_file_path(f.file);
|
||||
mnt_drop_write_file(f.file);
|
||||
out_fput:
|
||||
fdput(f);
|
||||
out:
|
||||
|
|
@ -887,13 +876,8 @@ EXPORT_SYMBOL(file_path);
|
|||
*/
|
||||
int vfs_open(const struct path *path, struct file *file)
|
||||
{
|
||||
struct dentry *dentry = d_real(path->dentry, NULL, file->f_flags, 0);
|
||||
|
||||
if (IS_ERR(dentry))
|
||||
return PTR_ERR(dentry);
|
||||
|
||||
file->f_path = *path;
|
||||
return do_dentry_open(file, d_backing_inode(dentry), NULL);
|
||||
return do_dentry_open(file, d_backing_inode(path->dentry), NULL);
|
||||
}
|
||||
|
||||
struct file *dentry_open(const struct path *path, int flags,
|
||||
|
|
@ -919,6 +903,24 @@ struct file *dentry_open(const struct path *path, int flags,
|
|||
}
|
||||
EXPORT_SYMBOL(dentry_open);
|
||||
|
||||
struct file *open_with_fake_path(const struct path *path, int flags,
|
||||
struct inode *inode, const struct cred *cred)
|
||||
{
|
||||
struct file *f = alloc_empty_file_noaccount(flags, cred);
|
||||
if (!IS_ERR(f)) {
|
||||
int error;
|
||||
|
||||
f->f_path = *path;
|
||||
error = do_dentry_open(f, inode, NULL);
|
||||
if (error) {
|
||||
fput(f);
|
||||
f = ERR_PTR(error);
|
||||
}
|
||||
}
|
||||
return f;
|
||||
}
|
||||
EXPORT_SYMBOL(open_with_fake_path);
|
||||
|
||||
static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op)
|
||||
{
|
||||
int lookup_flags = 0;
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ config OVERLAY_FS_NFS_EXPORT
|
|||
bool "Overlayfs: turn on NFS export feature by default"
|
||||
depends on OVERLAY_FS
|
||||
depends on OVERLAY_FS_INDEX
|
||||
depends on !OVERLAY_FS_METACOPY
|
||||
help
|
||||
If this config option is enabled then overlay filesystems will use
|
||||
the index directory to decode overlay NFS file handles by default.
|
||||
|
|
@ -103,3 +104,21 @@ config OVERLAY_FS_XINO_AUTO
|
|||
For more information, see Documentation/filesystems/overlayfs.txt
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config OVERLAY_FS_METACOPY
|
||||
bool "Overlayfs: turn on metadata only copy up feature by default"
|
||||
depends on OVERLAY_FS
|
||||
select OVERLAY_FS_REDIRECT_DIR
|
||||
help
|
||||
If this config option is enabled then overlay filesystems will
|
||||
copy up only metadata where appropriate and data copy up will
|
||||
happen when a file is opened for WRITE operation. It is still
|
||||
possible to turn off this feature globally with the "metacopy=off"
|
||||
module option or on a filesystem instance basis with the
|
||||
"metacopy=off" mount option.
|
||||
|
||||
Note, that this feature is not backward compatible. That is,
|
||||
mounting an overlay which has metacopy only inodes on a kernel
|
||||
that doesn't support this feature will have unexpected results.
|
||||
|
||||
If unsure, say N.
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@
|
|||
|
||||
obj-$(CONFIG_OVERLAY_FS) += overlay.o
|
||||
|
||||
overlay-objs := super.o namei.o util.o inode.o dir.o readdir.o copy_up.o \
|
||||
export.o
|
||||
overlay-objs := super.o namei.o util.o inode.o file.o dir.o readdir.o \
|
||||
copy_up.o export.o
|
||||
|
|
|
|||
|
|
@ -25,35 +25,20 @@
|
|||
|
||||
#define OVL_COPY_UP_CHUNK_SIZE (1 << 20)
|
||||
|
||||
static bool __read_mostly ovl_check_copy_up;
|
||||
module_param_named(check_copy_up, ovl_check_copy_up, bool,
|
||||
S_IWUSR | S_IRUGO);
|
||||
MODULE_PARM_DESC(ovl_check_copy_up,
|
||||
"Warn on copy-up when causing process also has a R/O fd open");
|
||||
|
||||
static int ovl_check_fd(const void *data, struct file *f, unsigned int fd)
|
||||
static int ovl_ccup_set(const char *buf, const struct kernel_param *param)
|
||||
{
|
||||
const struct dentry *dentry = data;
|
||||
|
||||
if (file_inode(f) == d_inode(dentry))
|
||||
pr_warn_ratelimited("overlayfs: Warning: Copying up %pD, but open R/O on fd %u which will cease to be coherent [pid=%d %s]\n",
|
||||
f, fd, current->pid, current->comm);
|
||||
pr_warn("overlayfs: \"check_copy_up\" module option is obsolete\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the fds open by this process and warn if something like the following
|
||||
* scenario is about to occur:
|
||||
*
|
||||
* fd1 = open("foo", O_RDONLY);
|
||||
* fd2 = open("foo", O_RDWR);
|
||||
*/
|
||||
static void ovl_do_check_copy_up(struct dentry *dentry)
|
||||
static int ovl_ccup_get(char *buf, const struct kernel_param *param)
|
||||
{
|
||||
if (ovl_check_copy_up)
|
||||
iterate_fd(current->files, 0, ovl_check_fd, dentry);
|
||||
return sprintf(buf, "N\n");
|
||||
}
|
||||
|
||||
module_param_call(check_copy_up, ovl_ccup_set, ovl_ccup_get, NULL, 0644);
|
||||
MODULE_PARM_DESC(ovl_check_copy_up, "Obsolete; does nothing");
|
||||
|
||||
int ovl_copy_xattr(struct dentry *old, struct dentry *new)
|
||||
{
|
||||
ssize_t list_size, size, value_size = 0;
|
||||
|
|
@ -195,6 +180,16 @@ out_fput:
|
|||
return error;
|
||||
}
|
||||
|
||||
static int ovl_set_size(struct dentry *upperdentry, struct kstat *stat)
|
||||
{
|
||||
struct iattr attr = {
|
||||
.ia_valid = ATTR_SIZE,
|
||||
.ia_size = stat->size,
|
||||
};
|
||||
|
||||
return notify_change(upperdentry, &attr, NULL);
|
||||
}
|
||||
|
||||
static int ovl_set_timestamps(struct dentry *upperdentry, struct kstat *stat)
|
||||
{
|
||||
struct iattr attr = {
|
||||
|
|
@ -403,6 +398,7 @@ struct ovl_copy_up_ctx {
|
|||
bool tmpfile;
|
||||
bool origin;
|
||||
bool indexed;
|
||||
bool metacopy;
|
||||
};
|
||||
|
||||
static int ovl_link_up(struct ovl_copy_up_ctx *c)
|
||||
|
|
@ -505,28 +501,10 @@ static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp)
|
|||
{
|
||||
int err;
|
||||
|
||||
if (S_ISREG(c->stat.mode)) {
|
||||
struct path upperpath;
|
||||
|
||||
ovl_path_upper(c->dentry, &upperpath);
|
||||
BUG_ON(upperpath.dentry != NULL);
|
||||
upperpath.dentry = temp;
|
||||
|
||||
err = ovl_copy_up_data(&c->lowerpath, &upperpath, c->stat.size);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
err = ovl_copy_xattr(c->lowerpath.dentry, temp);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
inode_lock(temp->d_inode);
|
||||
err = ovl_set_attr(temp, &c->stat);
|
||||
inode_unlock(temp->d_inode);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Store identifier of lower inode in upper inode xattr to
|
||||
* allow lookup of the copy up origin inode.
|
||||
|
|
@ -540,7 +518,34 @@ static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp)
|
|||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (S_ISREG(c->stat.mode) && !c->metacopy) {
|
||||
struct path upperpath, datapath;
|
||||
|
||||
ovl_path_upper(c->dentry, &upperpath);
|
||||
BUG_ON(upperpath.dentry != NULL);
|
||||
upperpath.dentry = temp;
|
||||
|
||||
ovl_path_lowerdata(c->dentry, &datapath);
|
||||
err = ovl_copy_up_data(&datapath, &upperpath, c->stat.size);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (c->metacopy) {
|
||||
err = ovl_check_setxattr(c->dentry, temp, OVL_XATTR_METACOPY,
|
||||
NULL, 0, -EOPNOTSUPP);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
inode_lock(temp->d_inode);
|
||||
if (c->metacopy)
|
||||
err = ovl_set_size(temp, &c->stat);
|
||||
if (!err)
|
||||
err = ovl_set_attr(temp, &c->stat);
|
||||
inode_unlock(temp->d_inode);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)
|
||||
|
|
@ -575,6 +580,8 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c)
|
|||
if (err)
|
||||
goto out;
|
||||
|
||||
if (!c->metacopy)
|
||||
ovl_set_upperdata(d_inode(c->dentry));
|
||||
inode = d_inode(c->dentry);
|
||||
ovl_inode_update(inode, newdentry);
|
||||
if (S_ISDIR(inode->i_mode))
|
||||
|
|
@ -677,6 +684,49 @@ out:
|
|||
return err;
|
||||
}
|
||||
|
||||
static bool ovl_need_meta_copy_up(struct dentry *dentry, umode_t mode,
|
||||
int flags)
|
||||
{
|
||||
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
|
||||
|
||||
if (!ofs->config.metacopy)
|
||||
return false;
|
||||
|
||||
if (!S_ISREG(mode))
|
||||
return false;
|
||||
|
||||
if (flags && ((OPEN_FMODE(flags) & FMODE_WRITE) || (flags & O_TRUNC)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Copy up data of an inode which was copied up metadata only in the past. */
|
||||
static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c)
|
||||
{
|
||||
struct path upperpath, datapath;
|
||||
int err;
|
||||
|
||||
ovl_path_upper(c->dentry, &upperpath);
|
||||
if (WARN_ON(upperpath.dentry == NULL))
|
||||
return -EIO;
|
||||
|
||||
ovl_path_lowerdata(c->dentry, &datapath);
|
||||
if (WARN_ON(datapath.dentry == NULL))
|
||||
return -EIO;
|
||||
|
||||
err = ovl_copy_up_data(&datapath, &upperpath, c->stat.size);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = vfs_removexattr(upperpath.dentry, OVL_XATTR_METACOPY);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
ovl_set_upperdata(d_inode(c->dentry));
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
|
||||
int flags)
|
||||
{
|
||||
|
|
@ -698,6 +748,8 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
ctx.metacopy = ovl_need_meta_copy_up(dentry, ctx.stat.mode, flags);
|
||||
|
||||
if (parent) {
|
||||
ovl_path_upper(parent, &parentpath);
|
||||
ctx.destdir = parentpath.dentry;
|
||||
|
|
@ -719,9 +771,8 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
|
|||
if (IS_ERR(ctx.link))
|
||||
return PTR_ERR(ctx.link);
|
||||
}
|
||||
ovl_do_check_copy_up(ctx.lowerpath.dentry);
|
||||
|
||||
err = ovl_copy_up_start(dentry);
|
||||
err = ovl_copy_up_start(dentry, flags);
|
||||
/* err < 0: interrupted, err > 0: raced with another copy-up */
|
||||
if (unlikely(err)) {
|
||||
if (err > 0)
|
||||
|
|
@ -731,6 +782,8 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
|
|||
err = ovl_do_copy_up(&ctx);
|
||||
if (!err && parent && !ovl_dentry_has_upper_alias(dentry))
|
||||
err = ovl_link_up(&ctx);
|
||||
if (!err && ovl_dentry_needs_data_copy_up_locked(dentry, flags))
|
||||
err = ovl_copy_up_meta_inode_data(&ctx);
|
||||
ovl_copy_up_end(dentry);
|
||||
}
|
||||
do_delayed_call(&done);
|
||||
|
|
@ -756,21 +809,7 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags)
|
|||
struct dentry *next;
|
||||
struct dentry *parent = NULL;
|
||||
|
||||
/*
|
||||
* Check if copy-up has happened as well as for upper alias (in
|
||||
* case of hard links) is there.
|
||||
*
|
||||
* Both checks are lockless:
|
||||
* - false negatives: will recheck under oi->lock
|
||||
* - false positives:
|
||||
* + ovl_dentry_upper() uses memory barriers to ensure the
|
||||
* upper dentry is up-to-date
|
||||
* + ovl_dentry_has_upper_alias() relies on locking of
|
||||
* upper parent i_rwsem to prevent reordering copy-up
|
||||
* with rename.
|
||||
*/
|
||||
if (ovl_dentry_upper(dentry) &&
|
||||
(ovl_dentry_has_upper_alias(dentry) || disconnected))
|
||||
if (ovl_already_copied_up(dentry, flags))
|
||||
break;
|
||||
|
||||
next = dget(dentry);
|
||||
|
|
@ -795,6 +834,41 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags)
|
|||
return err;
|
||||
}
|
||||
|
||||
static bool ovl_open_need_copy_up(struct dentry *dentry, int flags)
|
||||
{
|
||||
/* Copy up of disconnected dentry does not set upper alias */
|
||||
if (ovl_already_copied_up(dentry, flags))
|
||||
return false;
|
||||
|
||||
if (special_file(d_inode(dentry)->i_mode))
|
||||
return false;
|
||||
|
||||
if (!ovl_open_flags_need_copy_up(flags))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
if (ovl_open_need_copy_up(dentry, file_flags)) {
|
||||
err = ovl_want_write(dentry);
|
||||
if (!err) {
|
||||
err = ovl_copy_up_flags(dentry, file_flags);
|
||||
ovl_drop_write(dentry);
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int ovl_copy_up_with_data(struct dentry *dentry)
|
||||
{
|
||||
return ovl_copy_up_flags(dentry, O_WRONLY);
|
||||
}
|
||||
|
||||
int ovl_copy_up(struct dentry *dentry)
|
||||
{
|
||||
return ovl_copy_up_flags(dentry, 0);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ module_param_named(redirect_max, ovl_redirect_max, ushort, 0644);
|
|||
MODULE_PARM_DESC(ovl_redirect_max,
|
||||
"Maximum length of absolute redirect xattr value");
|
||||
|
||||
static int ovl_set_redirect(struct dentry *dentry, bool samedir);
|
||||
|
||||
int ovl_cleanup(struct inode *wdir, struct dentry *wdentry)
|
||||
{
|
||||
int err;
|
||||
|
|
@ -242,7 +244,7 @@ static int ovl_instantiate(struct dentry *dentry, struct inode *inode,
|
|||
.newinode = inode,
|
||||
};
|
||||
|
||||
ovl_dentry_version_inc(dentry->d_parent, false);
|
||||
ovl_dir_modified(dentry->d_parent, false);
|
||||
ovl_dentry_set_upper_alias(dentry);
|
||||
if (!hardlink) {
|
||||
/*
|
||||
|
|
@ -657,6 +659,12 @@ static int ovl_link(struct dentry *old, struct inode *newdir,
|
|||
if (err)
|
||||
goto out_drop_write;
|
||||
|
||||
if (ovl_is_metacopy_dentry(old)) {
|
||||
err = ovl_set_redirect(old, false);
|
||||
if (err)
|
||||
goto out_drop_write;
|
||||
}
|
||||
|
||||
err = ovl_nlink_start(old, &locked);
|
||||
if (err)
|
||||
goto out_drop_write;
|
||||
|
|
@ -722,7 +730,7 @@ static int ovl_remove_and_whiteout(struct dentry *dentry,
|
|||
if (err)
|
||||
goto out_d_drop;
|
||||
|
||||
ovl_dentry_version_inc(dentry->d_parent, true);
|
||||
ovl_dir_modified(dentry->d_parent, true);
|
||||
out_d_drop:
|
||||
d_drop(dentry);
|
||||
out_dput_upper:
|
||||
|
|
@ -767,7 +775,7 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
|
|||
err = vfs_rmdir(dir, upper);
|
||||
else
|
||||
err = vfs_unlink(dir, upper, NULL);
|
||||
ovl_dentry_version_inc(dentry->d_parent, ovl_type_origin(dentry));
|
||||
ovl_dir_modified(dentry->d_parent, ovl_type_origin(dentry));
|
||||
|
||||
/*
|
||||
* Keeping this dentry hashed would mean having to release
|
||||
|
|
@ -797,6 +805,7 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir)
|
|||
int err;
|
||||
bool locked = false;
|
||||
const struct cred *old_cred;
|
||||
struct dentry *upperdentry;
|
||||
bool lower_positive = ovl_lower_positive(dentry);
|
||||
LIST_HEAD(list);
|
||||
|
||||
|
|
@ -832,6 +841,17 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir)
|
|||
drop_nlink(dentry->d_inode);
|
||||
}
|
||||
ovl_nlink_end(dentry, locked);
|
||||
|
||||
/*
|
||||
* Copy ctime
|
||||
*
|
||||
* Note: we fail to update ctime if there was no copy-up, only a
|
||||
* whiteout
|
||||
*/
|
||||
upperdentry = ovl_dentry_upper(dentry);
|
||||
if (upperdentry)
|
||||
ovl_copyattr(d_inode(upperdentry), d_inode(dentry));
|
||||
|
||||
out_drop_write:
|
||||
ovl_drop_write(dentry);
|
||||
out:
|
||||
|
|
@ -862,13 +882,13 @@ static bool ovl_can_move(struct dentry *dentry)
|
|||
!d_is_dir(dentry) || !ovl_type_merge_or_lower(dentry);
|
||||
}
|
||||
|
||||
static char *ovl_get_redirect(struct dentry *dentry, bool samedir)
|
||||
static char *ovl_get_redirect(struct dentry *dentry, bool abs_redirect)
|
||||
{
|
||||
char *buf, *ret;
|
||||
struct dentry *d, *tmp;
|
||||
int buflen = ovl_redirect_max + 1;
|
||||
|
||||
if (samedir) {
|
||||
if (!abs_redirect) {
|
||||
ret = kstrndup(dentry->d_name.name, dentry->d_name.len,
|
||||
GFP_KERNEL);
|
||||
goto out;
|
||||
|
|
@ -922,15 +942,43 @@ out:
|
|||
return ret ? ret : ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
static bool ovl_need_absolute_redirect(struct dentry *dentry, bool samedir)
|
||||
{
|
||||
struct dentry *lowerdentry;
|
||||
|
||||
if (!samedir)
|
||||
return true;
|
||||
|
||||
if (d_is_dir(dentry))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* For non-dir hardlinked files, we need absolute redirects
|
||||
* in general as two upper hardlinks could be in different
|
||||
* dirs. We could put a relative redirect now and convert
|
||||
* it to absolute redirect later. But when nlink > 1 and
|
||||
* indexing is on, that means relative redirect needs to be
|
||||
* converted to absolute during copy up of another lower
|
||||
* hardllink as well.
|
||||
*
|
||||
* So without optimizing too much, just check if lower is
|
||||
* a hard link or not. If lower is hard link, put absolute
|
||||
* redirect.
|
||||
*/
|
||||
lowerdentry = ovl_dentry_lower(dentry);
|
||||
return (d_inode(lowerdentry)->i_nlink > 1);
|
||||
}
|
||||
|
||||
static int ovl_set_redirect(struct dentry *dentry, bool samedir)
|
||||
{
|
||||
int err;
|
||||
const char *redirect = ovl_dentry_get_redirect(dentry);
|
||||
bool absolute_redirect = ovl_need_absolute_redirect(dentry, samedir);
|
||||
|
||||
if (redirect && (samedir || redirect[0] == '/'))
|
||||
if (redirect && (!absolute_redirect || redirect[0] == '/'))
|
||||
return 0;
|
||||
|
||||
redirect = ovl_get_redirect(dentry, samedir);
|
||||
redirect = ovl_get_redirect(dentry, absolute_redirect);
|
||||
if (IS_ERR(redirect))
|
||||
return PTR_ERR(redirect);
|
||||
|
||||
|
|
@ -1106,22 +1154,20 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
|
|||
goto out_dput;
|
||||
|
||||
err = 0;
|
||||
if (is_dir) {
|
||||
if (ovl_type_merge_or_lower(old))
|
||||
err = ovl_set_redirect(old, samedir);
|
||||
else if (!old_opaque && ovl_type_merge(new->d_parent))
|
||||
err = ovl_set_opaque_xerr(old, olddentry, -EXDEV);
|
||||
if (err)
|
||||
goto out_dput;
|
||||
}
|
||||
if (!overwrite && new_is_dir) {
|
||||
if (ovl_type_merge_or_lower(new))
|
||||
err = ovl_set_redirect(new, samedir);
|
||||
else if (!new_opaque && ovl_type_merge(old->d_parent))
|
||||
err = ovl_set_opaque_xerr(new, newdentry, -EXDEV);
|
||||
if (err)
|
||||
goto out_dput;
|
||||
}
|
||||
if (ovl_type_merge_or_lower(old))
|
||||
err = ovl_set_redirect(old, samedir);
|
||||
else if (is_dir && !old_opaque && ovl_type_merge(new->d_parent))
|
||||
err = ovl_set_opaque_xerr(old, olddentry, -EXDEV);
|
||||
if (err)
|
||||
goto out_dput;
|
||||
|
||||
if (!overwrite && ovl_type_merge_or_lower(new))
|
||||
err = ovl_set_redirect(new, samedir);
|
||||
else if (!overwrite && new_is_dir && !new_opaque &&
|
||||
ovl_type_merge(old->d_parent))
|
||||
err = ovl_set_opaque_xerr(new, newdentry, -EXDEV);
|
||||
if (err)
|
||||
goto out_dput;
|
||||
|
||||
err = ovl_do_rename(old_upperdir->d_inode, olddentry,
|
||||
new_upperdir->d_inode, newdentry, flags);
|
||||
|
|
@ -1138,10 +1184,15 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
|
|||
drop_nlink(d_inode(new));
|
||||
}
|
||||
|
||||
ovl_dentry_version_inc(old->d_parent, ovl_type_origin(old) ||
|
||||
(!overwrite && ovl_type_origin(new)));
|
||||
ovl_dentry_version_inc(new->d_parent, ovl_type_origin(old) ||
|
||||
(d_inode(new) && ovl_type_origin(new)));
|
||||
ovl_dir_modified(old->d_parent, ovl_type_origin(old) ||
|
||||
(!overwrite && ovl_type_origin(new)));
|
||||
ovl_dir_modified(new->d_parent, ovl_type_origin(old) ||
|
||||
(d_inode(new) && ovl_type_origin(new)));
|
||||
|
||||
/* copy ctime: */
|
||||
ovl_copyattr(d_inode(olddentry), d_inode(old));
|
||||
if (d_inode(new) && ovl_dentry_upper(new))
|
||||
ovl_copyattr(d_inode(newdentry), d_inode(new));
|
||||
|
||||
out_dput:
|
||||
dput(newdentry);
|
||||
|
|
|
|||
|
|
@ -317,6 +317,9 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb,
|
|||
return ERR_CAST(inode);
|
||||
}
|
||||
|
||||
if (upper)
|
||||
ovl_set_flag(OVL_UPPERDATA, inode);
|
||||
|
||||
dentry = d_find_any_alias(inode);
|
||||
if (!dentry) {
|
||||
dentry = d_alloc_anon(inode->i_sb);
|
||||
|
|
|
|||
511
fs/overlayfs/file.c
Normal file
511
fs/overlayfs/file.c
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Red Hat, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/cred.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/xattr.h>
|
||||
#include <linux/uio.h>
|
||||
#include "overlayfs.h"
|
||||
|
||||
static char ovl_whatisit(struct inode *inode, struct inode *realinode)
|
||||
{
|
||||
if (realinode != ovl_inode_upper(inode))
|
||||
return 'l';
|
||||
if (ovl_has_upperdata(inode))
|
||||
return 'u';
|
||||
else
|
||||
return 'm';
|
||||
}
|
||||
|
||||
static struct file *ovl_open_realfile(const struct file *file,
|
||||
struct inode *realinode)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
struct file *realfile;
|
||||
const struct cred *old_cred;
|
||||
|
||||
old_cred = ovl_override_creds(inode->i_sb);
|
||||
realfile = open_with_fake_path(&file->f_path, file->f_flags | O_NOATIME,
|
||||
realinode, current_cred());
|
||||
revert_creds(old_cred);
|
||||
|
||||
pr_debug("open(%p[%pD2/%c], 0%o) -> (%p, 0%o)\n",
|
||||
file, file, ovl_whatisit(inode, realinode), file->f_flags,
|
||||
realfile, IS_ERR(realfile) ? 0 : realfile->f_flags);
|
||||
|
||||
return realfile;
|
||||
}
|
||||
|
||||
#define OVL_SETFL_MASK (O_APPEND | O_NONBLOCK | O_NDELAY | O_DIRECT)
|
||||
|
||||
static int ovl_change_flags(struct file *file, unsigned int flags)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
int err;
|
||||
|
||||
/* No atime modificaton on underlying */
|
||||
flags |= O_NOATIME;
|
||||
|
||||
/* If some flag changed that cannot be changed then something's amiss */
|
||||
if (WARN_ON((file->f_flags ^ flags) & ~OVL_SETFL_MASK))
|
||||
return -EIO;
|
||||
|
||||
flags &= OVL_SETFL_MASK;
|
||||
|
||||
if (((flags ^ file->f_flags) & O_APPEND) && IS_APPEND(inode))
|
||||
return -EPERM;
|
||||
|
||||
if (flags & O_DIRECT) {
|
||||
if (!file->f_mapping->a_ops ||
|
||||
!file->f_mapping->a_ops->direct_IO)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (file->f_op->check_flags) {
|
||||
err = file->f_op->check_flags(flags);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
spin_lock(&file->f_lock);
|
||||
file->f_flags = (file->f_flags & ~OVL_SETFL_MASK) | flags;
|
||||
spin_unlock(&file->f_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ovl_real_fdget_meta(const struct file *file, struct fd *real,
|
||||
bool allow_meta)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
struct inode *realinode;
|
||||
|
||||
real->flags = 0;
|
||||
real->file = file->private_data;
|
||||
|
||||
if (allow_meta)
|
||||
realinode = ovl_inode_real(inode);
|
||||
else
|
||||
realinode = ovl_inode_realdata(inode);
|
||||
|
||||
/* Has it been copied up since we'd opened it? */
|
||||
if (unlikely(file_inode(real->file) != realinode)) {
|
||||
real->flags = FDPUT_FPUT;
|
||||
real->file = ovl_open_realfile(file, realinode);
|
||||
|
||||
return PTR_ERR_OR_ZERO(real->file);
|
||||
}
|
||||
|
||||
/* Did the flags change since open? */
|
||||
if (unlikely((file->f_flags ^ real->file->f_flags) & ~O_NOATIME))
|
||||
return ovl_change_flags(real->file, file->f_flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ovl_real_fdget(const struct file *file, struct fd *real)
|
||||
{
|
||||
return ovl_real_fdget_meta(file, real, false);
|
||||
}
|
||||
|
||||
static int ovl_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct dentry *dentry = file_dentry(file);
|
||||
struct file *realfile;
|
||||
int err;
|
||||
|
||||
err = ovl_open_maybe_copy_up(dentry, file->f_flags);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* No longer need these flags, so don't pass them on to underlying fs */
|
||||
file->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
|
||||
|
||||
realfile = ovl_open_realfile(file, ovl_inode_realdata(inode));
|
||||
if (IS_ERR(realfile))
|
||||
return PTR_ERR(realfile);
|
||||
|
||||
/* For O_DIRECT dentry_open() checks f_mapping->a_ops->direct_IO */
|
||||
file->f_mapping = realfile->f_mapping;
|
||||
|
||||
file->private_data = realfile;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ovl_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
fput(file->private_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static loff_t ovl_llseek(struct file *file, loff_t offset, int whence)
|
||||
{
|
||||
struct inode *realinode = ovl_inode_real(file_inode(file));
|
||||
|
||||
return generic_file_llseek_size(file, offset, whence,
|
||||
realinode->i_sb->s_maxbytes,
|
||||
i_size_read(realinode));
|
||||
}
|
||||
|
||||
static void ovl_file_accessed(struct file *file)
|
||||
{
|
||||
struct inode *inode, *upperinode;
|
||||
|
||||
if (file->f_flags & O_NOATIME)
|
||||
return;
|
||||
|
||||
inode = file_inode(file);
|
||||
upperinode = ovl_inode_upper(inode);
|
||||
|
||||
if (!upperinode)
|
||||
return;
|
||||
|
||||
if ((!timespec64_equal(&inode->i_mtime, &upperinode->i_mtime) ||
|
||||
!timespec64_equal(&inode->i_ctime, &upperinode->i_ctime))) {
|
||||
inode->i_mtime = upperinode->i_mtime;
|
||||
inode->i_ctime = upperinode->i_ctime;
|
||||
}
|
||||
|
||||
touch_atime(&file->f_path);
|
||||
}
|
||||
|
||||
static rwf_t ovl_iocb_to_rwf(struct kiocb *iocb)
|
||||
{
|
||||
int ifl = iocb->ki_flags;
|
||||
rwf_t flags = 0;
|
||||
|
||||
if (ifl & IOCB_NOWAIT)
|
||||
flags |= RWF_NOWAIT;
|
||||
if (ifl & IOCB_HIPRI)
|
||||
flags |= RWF_HIPRI;
|
||||
if (ifl & IOCB_DSYNC)
|
||||
flags |= RWF_DSYNC;
|
||||
if (ifl & IOCB_SYNC)
|
||||
flags |= RWF_SYNC;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
static ssize_t ovl_read_iter(struct kiocb *iocb, struct iov_iter *iter)
|
||||
{
|
||||
struct file *file = iocb->ki_filp;
|
||||
struct fd real;
|
||||
const struct cred *old_cred;
|
||||
ssize_t ret;
|
||||
|
||||
if (!iov_iter_count(iter))
|
||||
return 0;
|
||||
|
||||
ret = ovl_real_fdget(file, &real);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
old_cred = ovl_override_creds(file_inode(file)->i_sb);
|
||||
ret = vfs_iter_read(real.file, iter, &iocb->ki_pos,
|
||||
ovl_iocb_to_rwf(iocb));
|
||||
revert_creds(old_cred);
|
||||
|
||||
ovl_file_accessed(file);
|
||||
|
||||
fdput(real);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t ovl_write_iter(struct kiocb *iocb, struct iov_iter *iter)
|
||||
{
|
||||
struct file *file = iocb->ki_filp;
|
||||
struct inode *inode = file_inode(file);
|
||||
struct fd real;
|
||||
const struct cred *old_cred;
|
||||
ssize_t ret;
|
||||
|
||||
if (!iov_iter_count(iter))
|
||||
return 0;
|
||||
|
||||
inode_lock(inode);
|
||||
/* Update mode */
|
||||
ovl_copyattr(ovl_inode_real(inode), inode);
|
||||
ret = file_remove_privs(file);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
|
||||
ret = ovl_real_fdget(file, &real);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
|
||||
old_cred = ovl_override_creds(file_inode(file)->i_sb);
|
||||
ret = vfs_iter_write(real.file, iter, &iocb->ki_pos,
|
||||
ovl_iocb_to_rwf(iocb));
|
||||
revert_creds(old_cred);
|
||||
|
||||
/* Update size */
|
||||
ovl_copyattr(ovl_inode_real(inode), inode);
|
||||
|
||||
fdput(real);
|
||||
|
||||
out_unlock:
|
||||
inode_unlock(inode);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ovl_fsync(struct file *file, loff_t start, loff_t end, int datasync)
|
||||
{
|
||||
struct fd real;
|
||||
const struct cred *old_cred;
|
||||
int ret;
|
||||
|
||||
ret = ovl_real_fdget_meta(file, &real, !datasync);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Don't sync lower file for fear of receiving EROFS error */
|
||||
if (file_inode(real.file) == ovl_inode_upper(file_inode(file))) {
|
||||
old_cred = ovl_override_creds(file_inode(file)->i_sb);
|
||||
ret = vfs_fsync_range(real.file, start, end, datasync);
|
||||
revert_creds(old_cred);
|
||||
}
|
||||
|
||||
fdput(real);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ovl_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
{
|
||||
struct file *realfile = file->private_data;
|
||||
const struct cred *old_cred;
|
||||
int ret;
|
||||
|
||||
if (!realfile->f_op->mmap)
|
||||
return -ENODEV;
|
||||
|
||||
if (WARN_ON(file != vma->vm_file))
|
||||
return -EIO;
|
||||
|
||||
vma->vm_file = get_file(realfile);
|
||||
|
||||
old_cred = ovl_override_creds(file_inode(file)->i_sb);
|
||||
ret = call_mmap(vma->vm_file, vma);
|
||||
revert_creds(old_cred);
|
||||
|
||||
if (ret) {
|
||||
/* Drop reference count from new vm_file value */
|
||||
fput(realfile);
|
||||
} else {
|
||||
/* Drop reference count from previous vm_file value */
|
||||
fput(file);
|
||||
}
|
||||
|
||||
ovl_file_accessed(file);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long ovl_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
struct fd real;
|
||||
const struct cred *old_cred;
|
||||
int ret;
|
||||
|
||||
ret = ovl_real_fdget(file, &real);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
old_cred = ovl_override_creds(file_inode(file)->i_sb);
|
||||
ret = vfs_fallocate(real.file, mode, offset, len);
|
||||
revert_creds(old_cred);
|
||||
|
||||
/* Update size */
|
||||
ovl_copyattr(ovl_inode_real(inode), inode);
|
||||
|
||||
fdput(real);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long ovl_real_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct fd real;
|
||||
const struct cred *old_cred;
|
||||
long ret;
|
||||
|
||||
ret = ovl_real_fdget(file, &real);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
old_cred = ovl_override_creds(file_inode(file)->i_sb);
|
||||
ret = vfs_ioctl(real.file, cmd, arg);
|
||||
revert_creds(old_cred);
|
||||
|
||||
fdput(real);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long ovl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
long ret;
|
||||
struct inode *inode = file_inode(file);
|
||||
|
||||
switch (cmd) {
|
||||
case FS_IOC_GETFLAGS:
|
||||
ret = ovl_real_ioctl(file, cmd, arg);
|
||||
break;
|
||||
|
||||
case FS_IOC_SETFLAGS:
|
||||
if (!inode_owner_or_capable(inode))
|
||||
return -EACCES;
|
||||
|
||||
ret = mnt_want_write_file(file);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ovl_copy_up_with_data(file_dentry(file));
|
||||
if (!ret) {
|
||||
ret = ovl_real_ioctl(file, cmd, arg);
|
||||
|
||||
inode_lock(inode);
|
||||
ovl_copyflags(ovl_inode_real(inode), inode);
|
||||
inode_unlock(inode);
|
||||
}
|
||||
|
||||
mnt_drop_write_file(file);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -ENOTTY;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long ovl_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case FS_IOC32_GETFLAGS:
|
||||
cmd = FS_IOC_GETFLAGS;
|
||||
break;
|
||||
|
||||
case FS_IOC32_SETFLAGS:
|
||||
cmd = FS_IOC_SETFLAGS;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
return ovl_ioctl(file, cmd, arg);
|
||||
}
|
||||
|
||||
enum ovl_copyop {
|
||||
OVL_COPY,
|
||||
OVL_CLONE,
|
||||
OVL_DEDUPE,
|
||||
};
|
||||
|
||||
static ssize_t ovl_copyfile(struct file *file_in, loff_t pos_in,
|
||||
struct file *file_out, loff_t pos_out,
|
||||
u64 len, unsigned int flags, enum ovl_copyop op)
|
||||
{
|
||||
struct inode *inode_out = file_inode(file_out);
|
||||
struct fd real_in, real_out;
|
||||
const struct cred *old_cred;
|
||||
ssize_t ret;
|
||||
|
||||
ret = ovl_real_fdget(file_out, &real_out);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ovl_real_fdget(file_in, &real_in);
|
||||
if (ret) {
|
||||
fdput(real_out);
|
||||
return ret;
|
||||
}
|
||||
|
||||
old_cred = ovl_override_creds(file_inode(file_out)->i_sb);
|
||||
switch (op) {
|
||||
case OVL_COPY:
|
||||
ret = vfs_copy_file_range(real_in.file, pos_in,
|
||||
real_out.file, pos_out, len, flags);
|
||||
break;
|
||||
|
||||
case OVL_CLONE:
|
||||
ret = vfs_clone_file_range(real_in.file, pos_in,
|
||||
real_out.file, pos_out, len);
|
||||
break;
|
||||
|
||||
case OVL_DEDUPE:
|
||||
ret = vfs_dedupe_file_range_one(real_in.file, pos_in,
|
||||
real_out.file, pos_out, len);
|
||||
break;
|
||||
}
|
||||
revert_creds(old_cred);
|
||||
|
||||
/* Update size */
|
||||
ovl_copyattr(ovl_inode_real(inode_out), inode_out);
|
||||
|
||||
fdput(real_in);
|
||||
fdput(real_out);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t ovl_copy_file_range(struct file *file_in, loff_t pos_in,
|
||||
struct file *file_out, loff_t pos_out,
|
||||
size_t len, unsigned int flags)
|
||||
{
|
||||
return ovl_copyfile(file_in, pos_in, file_out, pos_out, len, flags,
|
||||
OVL_COPY);
|
||||
}
|
||||
|
||||
static int ovl_clone_file_range(struct file *file_in, loff_t pos_in,
|
||||
struct file *file_out, loff_t pos_out, u64 len)
|
||||
{
|
||||
return ovl_copyfile(file_in, pos_in, file_out, pos_out, len, 0,
|
||||
OVL_CLONE);
|
||||
}
|
||||
|
||||
static int ovl_dedupe_file_range(struct file *file_in, loff_t pos_in,
|
||||
struct file *file_out, loff_t pos_out, u64 len)
|
||||
{
|
||||
/*
|
||||
* Don't copy up because of a dedupe request, this wouldn't make sense
|
||||
* most of the time (data would be duplicated instead of deduplicated).
|
||||
*/
|
||||
if (!ovl_inode_upper(file_inode(file_in)) ||
|
||||
!ovl_inode_upper(file_inode(file_out)))
|
||||
return -EPERM;
|
||||
|
||||
return ovl_copyfile(file_in, pos_in, file_out, pos_out, len, 0,
|
||||
OVL_DEDUPE);
|
||||
}
|
||||
|
||||
const struct file_operations ovl_file_operations = {
|
||||
.open = ovl_open,
|
||||
.release = ovl_release,
|
||||
.llseek = ovl_llseek,
|
||||
.read_iter = ovl_read_iter,
|
||||
.write_iter = ovl_write_iter,
|
||||
.fsync = ovl_fsync,
|
||||
.mmap = ovl_mmap,
|
||||
.fallocate = ovl_fallocate,
|
||||
.unlocked_ioctl = ovl_ioctl,
|
||||
.compat_ioctl = ovl_compat_ioctl,
|
||||
|
||||
.copy_file_range = ovl_copy_file_range,
|
||||
.clone_file_range = ovl_clone_file_range,
|
||||
.dedupe_file_range = ovl_dedupe_file_range,
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue