2014-01-08 22:09:43

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 00/11] cross rename v3

[Only cosmetic changes from v2 based on Jan Kara's review]

This series adds a new syscall, renameat2(), which is the same as renameat() but
with a flags argument.

The purpose of extending rename is to add cross-rename, a symmetric variant of
rename, which exchanges the two files. This allows interesting things, which
were not possible before, for example atomically replacing a directory tree with
a symlink, etc... This also allows overlayfs and friends to operate on
whiteouts atomically.

Andy Lutomirski also suggested a "noreplace" flag, which disables the
overwriting behavior of rename.

These two flags, RENAME_EXCHANGE and RENAME_NOREPLACE are only implemented for
ext4 as an example and for testing.

Implementing RENAME_NOREPLACE for other local (disk or ram based) filesystems is
trivial: just don't fail with -EOPNOTSUPP, the rest is done by the VFS. Network
filesystems need special treatment to avoid creation races.


Please consider for -next (3.14).

Git tree is here:

git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git cross-rename

Thanks,
Miklos

---
Miklos Szeredi (11):
vfs: add d_is_dir()
vfs: rename: move d_move() up
vfs: rename: use common code for dir and non-dir
vfs: add renameat2 syscall
vfs: add RENAME_NOREPLACE flag
security: add flags to rename hooks
vfs: add cross-rename
ext4: rename: create ext4_renament structure for local vars
ext4: rename: move EMLINK check up
ext4: rename: split out helper functions
ext4: add cross rename support

---
Documentation/filesystems/Locking | 2 +-
Documentation/filesystems/vfs.txt | 4 +-
arch/x86/syscalls/syscall_64.tbl | 1 +
.../lustre/lustre/include/linux/lustre_compat25.h | 4 +-
drivers/staging/lustre/lustre/llite/namei.c | 7 +-
drivers/staging/lustre/lustre/lvfs/lvfs_linux.c | 2 +-
fs/9p/v9fs.h | 3 +-
fs/9p/vfs_inode.c | 7 +-
fs/affs/affs.h | 3 +-
fs/affs/namei.c | 6 +-
fs/afs/dir.c | 9 +-
fs/bad_inode.c | 3 +-
fs/bfs/dir.c | 6 +-
fs/btrfs/inode.c | 6 +-
fs/cachefiles/namei.c | 4 +-
fs/ceph/dir.c | 6 +-
fs/cifs/cifsfs.h | 2 +-
fs/cifs/inode.c | 6 +-
fs/coda/dir.c | 11 +-
fs/dcache.c | 46 ++-
fs/debugfs/inode.c | 2 +-
fs/ecryptfs/inode.c | 8 +-
fs/exofs/namei.c | 6 +-
fs/ext2/namei.c | 8 +-
fs/ext3/namei.c | 8 +-
fs/ext4/namei.c | 395 ++++++++++++++-------
fs/f2fs/namei.c | 6 +-
fs/fat/namei_msdos.c | 6 +-
fs/fat/namei_vfat.c | 6 +-
fs/fuse/dir.c | 9 +-
fs/gfs2/inode.c | 6 +-
fs/hfs/dir.c | 6 +-
fs/hfsplus/dir.c | 6 +-
fs/hostfs/hostfs_kern.c | 8 +-
fs/hpfs/namei.c | 6 +-
fs/jffs2/dir.c | 8 +-
fs/jfs/namei.c | 5 +-
fs/libfs.c | 6 +-
fs/logfs/dir.c | 6 +-
fs/minix/namei.c | 8 +-
fs/namei.c | 316 +++++++++--------
fs/ncpfs/dir.c | 8 +-
fs/nfs/dir.c | 6 +-
fs/nfs/internal.h | 3 +-
fs/nfsd/vfs.c | 2 +-
fs/nilfs2/namei.c | 6 +-
fs/ocfs2/namei.c | 6 +-
fs/omfs/dir.c | 6 +-
fs/reiserfs/namei.c | 6 +-
fs/sysv/namei.c | 8 +-
fs/ubifs/dir.c | 6 +-
fs/udf/namei.c | 6 +-
fs/ufs/namei.c | 6 +-
fs/xfs/xfs_iops.c | 6 +-
include/linux/dcache.h | 8 +-
include/linux/fs.h | 6 +-
include/linux/security.h | 12 +-
include/uapi/linux/fs.h | 3 +
kernel/cgroup.c | 8 +-
mm/shmem.c | 5 +-
security/security.c | 22 +-
61 files changed, 746 insertions(+), 366 deletions(-)


2014-01-08 22:10:00

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 04/11] vfs: add renameat2 syscall

From: Miklos Szeredi <[email protected]>

Add new renameat2 syscall, which is the same as renameat with an added
flags argument.

Pass flags to vfs_rename() and to i_op->rename() as well.

All filesystems check flags and return -EOPNOTSUPP for unsupported flags.

Signed-off-by: Miklos Szeredi <[email protected]>
---
Documentation/filesystems/Locking | 2 +-
Documentation/filesystems/vfs.txt | 4 +++-
arch/x86/syscalls/syscall_64.tbl | 1 +
.../lustre/lustre/include/linux/lustre_compat25.h | 4 ++--
drivers/staging/lustre/lustre/llite/namei.c | 7 +++++-
drivers/staging/lustre/lustre/lvfs/lvfs_linux.c | 2 +-
fs/9p/v9fs.h | 3 ++-
fs/9p/vfs_inode.c | 7 +++++-
fs/affs/affs.h | 3 ++-
fs/affs/namei.c | 6 ++++-
fs/afs/dir.c | 9 ++++++--
fs/bad_inode.c | 3 ++-
fs/bfs/dir.c | 6 ++++-
fs/btrfs/inode.c | 6 ++++-
fs/cachefiles/namei.c | 2 +-
fs/ceph/dir.c | 6 ++++-
fs/cifs/cifsfs.h | 2 +-
fs/cifs/inode.c | 6 ++++-
fs/coda/dir.c | 11 ++++++---
fs/debugfs/inode.c | 2 +-
fs/ecryptfs/inode.c | 8 +++++--
fs/exofs/namei.c | 6 ++++-
fs/ext2/namei.c | 8 +++++--
fs/ext3/namei.c | 8 +++++--
fs/ext4/namei.c | 6 ++++-
fs/f2fs/namei.c | 6 ++++-
fs/fat/namei_msdos.c | 6 ++++-
fs/fat/namei_vfat.c | 6 ++++-
fs/fuse/dir.c | 9 ++++++--
fs/gfs2/inode.c | 6 ++++-
fs/hfs/dir.c | 6 ++++-
fs/hfsplus/dir.c | 6 ++++-
fs/hostfs/hostfs_kern.c | 8 +++++--
fs/hpfs/namei.c | 6 ++++-
fs/jffs2/dir.c | 8 +++++--
fs/jfs/namei.c | 5 ++++-
fs/libfs.c | 6 ++++-
fs/logfs/dir.c | 6 ++++-
fs/minix/namei.c | 8 +++++--
fs/namei.c | 26 ++++++++++++++++------
fs/ncpfs/dir.c | 8 +++++--
fs/nfs/dir.c | 6 ++++-
fs/nfs/internal.h | 3 ++-
fs/nfsd/vfs.c | 2 +-
fs/nilfs2/namei.c | 6 ++++-
fs/ocfs2/namei.c | 6 ++++-
fs/omfs/dir.c | 6 ++++-
fs/reiserfs/namei.c | 6 ++++-
fs/sysv/namei.c | 8 +++++--
fs/ubifs/dir.c | 6 ++++-
fs/udf/namei.c | 6 ++++-
fs/ufs/namei.c | 6 ++++-
fs/xfs/xfs_iops.c | 6 ++++-
include/linux/fs.h | 6 ++---
kernel/cgroup.c | 8 +++++--
mm/shmem.c | 5 ++++-
56 files changed, 263 insertions(+), 77 deletions(-)

diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking
index fe7afe225381..70e800c3d54b 100644
--- a/Documentation/filesystems/Locking
+++ b/Documentation/filesystems/Locking
@@ -46,7 +46,7 @@ prototypes:
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct inode *, struct dentry *,
- struct inode *, struct dentry *);
+ struct inode *, struct dentry *, unsigned int);
int (*readlink) (struct dentry *, char __user *,int);
void * (*follow_link) (struct dentry *, struct nameidata *);
void (*put_link) (struct dentry *, struct nameidata *, void *);
diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt
index deb48b5fd883..96ac6ab6357c 100644
--- a/Documentation/filesystems/vfs.txt
+++ b/Documentation/filesystems/vfs.txt
@@ -346,7 +346,7 @@ struct inode_operations {
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct inode *, struct dentry *,
- struct inode *, struct dentry *);
+ struct inode *, struct dentry *, unsigned int);
int (*readlink) (struct dentry *, char __user *,int);
void * (*follow_link) (struct dentry *, struct nameidata *);
void (*put_link) (struct dentry *, struct nameidata *, void *);
@@ -413,6 +413,8 @@ otherwise noted.

rename: called by the rename(2) system call to rename the object to
have the parent and name given by the second inode and dentry.
+ If a flag (passed as the fifth argument) is not supported by
+ the filesystem, the rename method should return -EOPNOTSUPP.

readlink: called by the readlink(2) system call. Only required if
you want to support reading symbolic links
diff --git a/arch/x86/syscalls/syscall_64.tbl b/arch/x86/syscalls/syscall_64.tbl
index 38ae65dfd14f..fafd73440655 100644
--- a/arch/x86/syscalls/syscall_64.tbl
+++ b/arch/x86/syscalls/syscall_64.tbl
@@ -320,6 +320,7 @@
311 64 process_vm_writev sys_process_vm_writev
312 common kcmp sys_kcmp
313 common finit_module sys_finit_module
+314 common renameat2 sys_renameat2

#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h b/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h
index eefdb8d061b1..81cc7a0134bb 100644
--- a/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h
+++ b/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h
@@ -105,8 +105,8 @@ static inline void ll_set_fs_pwd(struct fs_struct *fs, struct vfsmount *mnt,
#define ll_vfs_unlink(inode,entry,mnt) vfs_unlink(inode,entry)
#define ll_vfs_mknod(dir,entry,mnt,mode,dev) vfs_mknod(dir,entry,mode,dev)
#define ll_security_inode_unlink(dir,entry,mnt) security_inode_unlink(dir,entry)
-#define ll_vfs_rename(old,old_dir,mnt,new,new_dir,mnt1,delegated_inode) \
- vfs_rename(old,old_dir,new,new_dir,delegated_inode)
+#define ll_vfs_rename(old, old_dir, mnt, new, new_dir, mnt1) \
+ vfs_rename(old, old_dir, new, new_dir, NULL, 0)

#define cfs_bio_io_error(a,b) bio_io_error((a))
#define cfs_bio_endio(a,b,c) bio_endio((a),(c))
diff --git a/drivers/staging/lustre/lustre/llite/namei.c b/drivers/staging/lustre/lustre/llite/namei.c
index 90bbdae824ac..cd24e469cc9a 100644
--- a/drivers/staging/lustre/lustre/llite/namei.c
+++ b/drivers/staging/lustre/lustre/llite/namei.c
@@ -1215,9 +1215,14 @@ static int ll_link(struct dentry *old_dentry, struct inode *dir,
}

static int ll_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
int err;
+
+ if (flags)
+ return -EOPNOTSUPP;
+
err = ll_rename_generic(old_dir, NULL,
old_dentry, &old_dentry->d_name,
new_dir, NULL, new_dentry,
diff --git a/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c b/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c
index 09474e7553dd..1ef06fea793b 100644
--- a/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c
+++ b/drivers/staging/lustre/lustre/lvfs/lvfs_linux.c
@@ -224,7 +224,7 @@ int lustre_rename(struct dentry *dir, struct vfsmount *mnt,
GOTO(put_old, err = PTR_ERR(dchild_new));

err = ll_vfs_rename(dir->d_inode, dchild_old, mnt,
- dir->d_inode, dchild_new, mnt, NULL);
+ dir->d_inode, dchild_new, mnt);

dput(dchild_new);
put_old:
diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h
index a8e127c89627..5d347d10cf55 100644
--- a/fs/9p/v9fs.h
+++ b/fs/9p/v9fs.h
@@ -148,7 +148,8 @@ extern struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
extern int v9fs_vfs_unlink(struct inode *i, struct dentry *d);
extern int v9fs_vfs_rmdir(struct inode *i, struct dentry *d);
extern int v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry);
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags);
extern void v9fs_vfs_put_link(struct dentry *dentry, struct nameidata *nd,
void *p);
extern struct inode *v9fs_inode_from_fid(struct v9fs_session_info *v9ses,
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index 4e65aa903345..6a75edabc645 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -945,12 +945,14 @@ int v9fs_vfs_rmdir(struct inode *i, struct dentry *d)
* @old_dentry: old dentry
* @new_dir: new dir inode
* @new_dentry: new dentry
+ * @flags: rename flags
*
*/

int
v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
int retval;
struct inode *old_inode;
@@ -961,6 +963,9 @@ v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct p9_fid *newdirfid;
struct p9_wstat wstat;

+ if (flags)
+ return -EOPNOTSUPP;
+
p9_debug(P9_DEBUG_VFS, "\n");
retval = 0;
old_inode = old_dentry->d_inode;
diff --git a/fs/affs/affs.h b/fs/affs/affs.h
index 3952121f2f28..badf97e81250 100644
--- a/fs/affs/affs.h
+++ b/fs/affs/affs.h
@@ -163,7 +163,8 @@ extern int affs_link(struct dentry *olddentry, struct inode *dir,
extern int affs_symlink(struct inode *dir, struct dentry *dentry,
const char *symname);
extern int affs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry);
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags);

/* inode.c */

diff --git a/fs/affs/namei.c b/fs/affs/namei.c
index c36cbb4537a2..4f958db92333 100644
--- a/fs/affs/namei.c
+++ b/fs/affs/namei.c
@@ -401,12 +401,16 @@ affs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)

int
affs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct super_block *sb = old_dir->i_sb;
struct buffer_head *bh = NULL;
int retval;

+ if (flags)
+ return -EOPNOTSUPP;
+
pr_debug("AFFS: rename(old=%u,\"%*s\" to new=%u,\"%*s\")\n",
(u32)old_dir->i_ino, (int)old_dentry->d_name.len, old_dentry->d_name.name,
(u32)new_dir->i_ino, (int)new_dentry->d_name.len, new_dentry->d_name.name);
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 529300327f45..ccdd551a0766 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -38,7 +38,8 @@ static int afs_link(struct dentry *from, struct inode *dir,
static int afs_symlink(struct inode *dir, struct dentry *dentry,
const char *content);
static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry);
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags);

const struct file_operations afs_dir_file_operations = {
.open = afs_dir_open,
@@ -1088,12 +1089,16 @@ error:
* rename a file in an AFS filesystem and/or move it between directories
*/
static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct afs_vnode *orig_dvnode, *new_dvnode, *vnode;
struct key *key;
int ret;

+ if (flags)
+ return -EOPNOTSUPP;
+
vnode = AFS_FS_I(old_dentry->d_inode);
orig_dvnode = AFS_FS_I(old_dir);
new_dvnode = AFS_FS_I(new_dir);
diff --git a/fs/bad_inode.c b/fs/bad_inode.c
index 7c93953030fb..02673d57ceda 100644
--- a/fs/bad_inode.c
+++ b/fs/bad_inode.c
@@ -219,7 +219,8 @@ static int bad_inode_mknod (struct inode *dir, struct dentry *dentry,
}

static int bad_inode_rename (struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
return -EIO;
}
diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c
index a399e6d9dc74..41529673fd42 100644
--- a/fs/bfs/dir.c
+++ b/fs/bfs/dir.c
@@ -209,7 +209,8 @@ out_brelse:
}

static int bfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode *old_inode, *new_inode;
struct buffer_head *old_bh, *new_bh;
@@ -217,6 +218,9 @@ static int bfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct bfs_sb_info *info;
int error = -ENOENT;

+ if (flags)
+ return -EOPNOTSUPP;
+
old_bh = new_bh = NULL;
old_inode = old_dentry->d_inode;
if (S_ISDIR(old_inode->i_mode))
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index f1a77449d032..884cf012da09 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -7982,7 +7982,8 @@ static int btrfs_getattr(struct vfsmount *mnt,
}

static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct btrfs_trans_handle *trans;
struct btrfs_root *root = BTRFS_I(old_dir)->root;
@@ -7995,6 +7996,9 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry,
int ret;
u64 old_ino = btrfs_ino(old_inode);

+ if (flags)
+ return -EOPNOTSUPP;
+
if (btrfs_ino(new_dir) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID)
return -EPERM;

diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
index ca65f39dc8dc..31088a969351 100644
--- a/fs/cachefiles/namei.c
+++ b/fs/cachefiles/namei.c
@@ -396,7 +396,7 @@ try_again:
cachefiles_io_error(cache, "Rename security error %d", ret);
} else {
ret = vfs_rename(dir->d_inode, rep,
- cache->graveyard->d_inode, grave, NULL);
+ cache->graveyard->d_inode, grave, NULL, 0);
if (ret != 0 && ret != -ENOMEM)
cachefiles_io_error(cache,
"Rename failed with error %d", ret);
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index 2a0bcaeb189a..84bf7c757823 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -879,13 +879,17 @@ out:
}

static int ceph_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct ceph_fs_client *fsc = ceph_sb_to_client(old_dir->i_sb);
struct ceph_mds_client *mdsc = fsc->mdsc;
struct ceph_mds_request *req;
int err;

+ if (flags)
+ return -EOPNOTSUPP;
+
if (ceph_snap(old_dir) != ceph_snap(new_dir))
return -EXDEV;
if (ceph_snap(old_dir) != CEPH_NOSNAP ||
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index 26a754f49ba1..b9e81af57e46 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -61,7 +61,7 @@ extern int cifs_mknod(struct inode *, struct dentry *, umode_t, dev_t);
extern int cifs_mkdir(struct inode *, struct dentry *, umode_t);
extern int cifs_rmdir(struct inode *, struct dentry *);
extern int cifs_rename(struct inode *, struct dentry *, struct inode *,
- struct dentry *);
+ struct dentry *, unsigned int);
extern int cifs_revalidate_file_attr(struct file *filp);
extern int cifs_revalidate_dentry_attr(struct dentry *);
extern int cifs_revalidate_file(struct file *filp);
diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
index 49719b8228e5..84202980a032 100644
--- a/fs/cifs/inode.c
+++ b/fs/cifs/inode.c
@@ -1595,7 +1595,8 @@ do_rename_exit:

int
cifs_rename(struct inode *source_dir, struct dentry *source_dentry,
- struct inode *target_dir, struct dentry *target_dentry)
+ struct inode *target_dir, struct dentry *target_dentry,
+ unsigned int flags)
{
char *from_name = NULL;
char *to_name = NULL;
@@ -1607,6 +1608,9 @@ cifs_rename(struct inode *source_dir, struct dentry *source_dentry,
unsigned int xid;
int rc, tmprc;

+ if (flags)
+ return -EOPNOTSUPP;
+
cifs_sb = CIFS_SB(source_dir->i_sb);
tlink = cifs_sb_tlink(cifs_sb);
if (IS_ERR(tlink))
diff --git a/fs/coda/dir.c b/fs/coda/dir.c
index 5efbb5ee0adc..f94838543f2d 100644
--- a/fs/coda/dir.c
+++ b/fs/coda/dir.c
@@ -39,8 +39,9 @@ static int coda_symlink(struct inode *dir_inode, struct dentry *entry,
const char *symname);
static int coda_mkdir(struct inode *dir_inode, struct dentry *entry, umode_t mode);
static int coda_rmdir(struct inode *dir_inode, struct dentry *entry);
-static int coda_rename(struct inode *old_inode, struct dentry *old_dentry,
- struct inode *new_inode, struct dentry *new_dentry);
+static int coda_rename(struct inode *old_inode, struct dentry *old_dentry,
+ struct inode *new_inode, struct dentry *new_dentry,
+ unsigned int flags);

/* dir file-ops */
static int coda_readdir(struct file *file, struct dir_context *ctx);
@@ -347,7 +348,8 @@ static int coda_rmdir(struct inode *dir, struct dentry *de)

/* rename */
static int coda_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
const char *old_name = old_dentry->d_name.name;
const char *new_name = new_dentry->d_name.name;
@@ -355,6 +357,9 @@ static int coda_rename(struct inode *old_dir, struct dentry *old_dentry,
int new_length = new_dentry->d_name.len;
int error;

+ if (flags)
+ return -EOPNOTSUPP;
+
error = venus_rename(old_dir->i_sb, coda_i2f(old_dir),
coda_i2f(new_dir), old_length, new_length,
(const char *) old_name, (const char *)new_name);
diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c
index 9c0444cccbe1..70fc09be0ffd 100644
--- a/fs/debugfs/inode.c
+++ b/fs/debugfs/inode.c
@@ -618,7 +618,7 @@ struct dentry *debugfs_rename(struct dentry *old_dir, struct dentry *old_dentry,
old_name = fsnotify_oldname_init(old_dentry->d_name.name);

error = simple_rename(old_dir->d_inode, old_dentry, new_dir->d_inode,
- dentry);
+ dentry, 0);
if (error) {
fsnotify_oldname_free(old_name);
goto exit;
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index c36c44824471..0ce5a6b2ea9d 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -611,7 +611,8 @@ out:

static int
ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
int rc;
struct dentry *lower_old_dentry;
@@ -621,6 +622,9 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct dentry *trap = NULL;
struct inode *target_inode;

+ if (flags)
+ return -EOPNOTSUPP;
+
lower_old_dentry = ecryptfs_dentry_to_lower(old_dentry);
lower_new_dentry = ecryptfs_dentry_to_lower(new_dentry);
dget(lower_old_dentry);
@@ -641,7 +645,7 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry,
}
rc = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry,
lower_new_dir_dentry->d_inode, lower_new_dentry,
- NULL);
+ NULL, 0);
if (rc)
goto out_lock;
if (target_inode)
diff --git a/fs/exofs/namei.c b/fs/exofs/namei.c
index 4731fd991efe..54db7e2192ca 100644
--- a/fs/exofs/namei.c
+++ b/fs/exofs/namei.c
@@ -228,7 +228,8 @@ static int exofs_rmdir(struct inode *dir, struct dentry *dentry)
}

static int exofs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode *old_inode = old_dentry->d_inode;
struct inode *new_inode = new_dentry->d_inode;
@@ -238,6 +239,9 @@ static int exofs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct exofs_dir_entry *old_de;
int err = -ENOENT;

+ if (flags)
+ return -EOPNOTSUPP;
+
old_de = exofs_find_entry(old_dir, old_dentry, &old_page);
if (!old_de)
goto out;
diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c
index 256dd5f4c1c4..d7da1dad9149 100644
--- a/fs/ext2/namei.c
+++ b/fs/ext2/namei.c
@@ -320,8 +320,9 @@ static int ext2_rmdir (struct inode * dir, struct dentry *dentry)
return err;
}

-static int ext2_rename (struct inode * old_dir, struct dentry * old_dentry,
- struct inode * new_dir, struct dentry * new_dentry )
+static int ext2_rename(struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode * old_inode = old_dentry->d_inode;
struct inode * new_inode = new_dentry->d_inode;
@@ -331,6 +332,9 @@ static int ext2_rename (struct inode * old_dir, struct dentry * old_dentry,
struct ext2_dir_entry_2 * old_de;
int err = -ENOENT;

+ if (flags)
+ return -EOPNOTSUPP;
+
dquot_initialize(old_dir);
dquot_initialize(new_dir);

diff --git a/fs/ext3/namei.c b/fs/ext3/namei.c
index f8cde46de9cd..08ca534c2de4 100644
--- a/fs/ext3/namei.c
+++ b/fs/ext3/namei.c
@@ -2375,8 +2375,9 @@ retry:
* Anybody can rename anything with this: the permission checks are left to the
* higher-level routines.
*/
-static int ext3_rename (struct inode * old_dir, struct dentry *old_dentry,
- struct inode * new_dir,struct dentry *new_dentry)
+static int ext3_rename(struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
handle_t *handle;
struct inode * old_inode, * new_inode;
@@ -2384,6 +2385,9 @@ static int ext3_rename (struct inode * old_dir, struct dentry *old_dentry,
struct ext3_dir_entry_2 * old_de, * new_de;
int retval, flush_file = 0;

+ if (flags)
+ return -EOPNOTSUPP;
+
dquot_initialize(old_dir);
dquot_initialize(new_dir);

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 5a0408d7b114..08c40f4e7eed 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3010,7 +3010,8 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle,
* This comes from rename(const char *oldpath, const char *newpath)
*/
static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
handle_t *handle = NULL;
struct inode *old_inode, *new_inode;
@@ -3020,6 +3021,9 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
int inlined = 0, new_inlined = 0;
struct ext4_dir_entry_2 *parent_de;

+ if (flags)
+ return -EOPNOTSUPP;
+
dquot_initialize(old_dir);
dquot_initialize(new_dir);

diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index 575adac17f8b..13913085927c 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -374,7 +374,8 @@ out:
}

static int f2fs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct super_block *sb = old_dir->i_sb;
struct f2fs_sb_info *sbi = F2FS_SB(sb);
@@ -387,6 +388,9 @@ static int f2fs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct f2fs_dir_entry *new_entry;
int err = -ENOENT;

+ if (flags)
+ return -EOPNOTSUPP;
+
f2fs_balance_fs(sbi);

old_entry = f2fs_find_entry(old_dir, &old_dentry->d_name, &old_page);
diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c
index a783b0e1272a..73db3d039ba7 100644
--- a/fs/fat/namei_msdos.c
+++ b/fs/fat/namei_msdos.c
@@ -598,12 +598,16 @@ error_inode:

/***** Rename, a wrapper for rename_same_dir & rename_diff_dir */
static int msdos_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct super_block *sb = old_dir->i_sb;
unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME];
int err, is_hid;

+ if (flags)
+ return -EOPNOTSUPP;
+
mutex_lock(&MSDOS_SB(sb)->s_lock);

err = msdos_format_name(old_dentry->d_name.name,
diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c
index 6df8d3d885e5..ef0fd0703c33 100644
--- a/fs/fat/namei_vfat.c
+++ b/fs/fat/namei_vfat.c
@@ -903,7 +903,8 @@ out:
}

static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct buffer_head *dotdot_bh;
struct msdos_dir_entry *dotdot_de;
@@ -914,6 +915,9 @@ static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry,
int err, is_dir, update_dotdot, corrupt = 0;
struct super_block *sb = old_dir->i_sb;

+ if (flags)
+ return -EOPNOTSUPP;
+
old_sinfo.bh = sinfo.bh = dotdot_bh = NULL;
old_inode = old_dentry->d_inode;
new_inode = new_dentry->d_inode;
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index c3eb2c46c8f1..ffe2f46aa53e 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -734,13 +734,18 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry)
}

static int fuse_rename(struct inode *olddir, struct dentry *oldent,
- struct inode *newdir, struct dentry *newent)
+ struct inode *newdir, struct dentry *newent,
+ unsigned int flags)
{
int err;
struct fuse_rename_in inarg;
struct fuse_conn *fc = get_fuse_conn(olddir);
- struct fuse_req *req = fuse_get_req_nopages(fc);
+ struct fuse_req *req;

+ if (flags)
+ return -EOPNOTSUPP;
+
+ req = fuse_get_req_nopages(fc);
if (IS_ERR(req))
return PTR_ERR(req);

diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 7119504159f1..6613e214ad72 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -1243,7 +1243,8 @@ static int gfs2_ok_to_move(struct gfs2_inode *this, struct gfs2_inode *to)
*/

static int gfs2_rename(struct inode *odir, struct dentry *odentry,
- struct inode *ndir, struct dentry *ndentry)
+ struct inode *ndir, struct dentry *ndentry,
+ unsigned int flags)
{
struct gfs2_inode *odip = GFS2_I(odir);
struct gfs2_inode *ndip = GFS2_I(ndir);
@@ -1258,6 +1259,9 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry,
unsigned int x;
int error;

+ if (flags)
+ return -EOPNOTSUPP;
+
if (ndentry->d_inode) {
nip = GFS2_I(ndentry->d_inode);
if (ip == nip)
diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c
index 145566851e7a..540e8bcd331e 100644
--- a/fs/hfs/dir.c
+++ b/fs/hfs/dir.c
@@ -280,10 +280,14 @@ static int hfs_remove(struct inode *dir, struct dentry *dentry)
* XXX: how do you handle must_be dir?
*/
static int hfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
int res;

+ if (flags)
+ return -EOPNOTSUPP;
+
/* Unlink destination if it already exists */
if (new_dentry->d_inode) {
res = hfs_remove(new_dir, new_dentry);
diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c
index 4a4fea002673..802dce49af35 100644
--- a/fs/hfsplus/dir.c
+++ b/fs/hfsplus/dir.c
@@ -494,10 +494,14 @@ static int hfsplus_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
}

static int hfsplus_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
int res;

+ if (flags)
+ return -EOPNOTSUPP;
+
/* Unlink destination if it already exists */
if (new_dentry->d_inode) {
if (S_ISDIR(new_dentry->d_inode->i_mode))
diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c
index db23ce1bd903..eeeef55fdd56 100644
--- a/fs/hostfs/hostfs_kern.c
+++ b/fs/hostfs/hostfs_kern.c
@@ -738,12 +738,16 @@ static int hostfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
return err;
}

-int hostfs_rename(struct inode *from_ino, struct dentry *from,
- struct inode *to_ino, struct dentry *to)
+static int hostfs_rename(struct inode *from_ino, struct dentry *from,
+ struct inode *to_ino, struct dentry *to,
+ unsigned int flags)
{
char *from_name, *to_name;
int err;

+ if (flags)
+ return -EOPNOTSUPP;
+
if ((from_name = dentry_name(from)) == NULL)
return -ENOMEM;
if ((to_name = dentry_name(to)) == NULL) {
diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c
index 1b39afdd86fd..1bec8e2b4c74 100644
--- a/fs/hpfs/namei.c
+++ b/fs/hpfs/namei.c
@@ -516,7 +516,8 @@ const struct address_space_operations hpfs_symlink_aops = {
};

static int hpfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
const unsigned char *old_name = old_dentry->d_name.name;
unsigned old_len = old_dentry->d_name.len;
@@ -533,6 +534,9 @@ static int hpfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct fnode *fnode;
int err;

+ if (flags)
+ return -EOPNOTSUPP;
+
if ((err = hpfs_chk_name(new_name, &new_len))) return err;
err = 0;
hpfs_adjust_length(old_name, &old_len);
diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c
index e3aac222472e..a23da79c1ed0 100644
--- a/fs/jffs2/dir.c
+++ b/fs/jffs2/dir.c
@@ -35,7 +35,7 @@ static int jffs2_mkdir (struct inode *,struct dentry *,umode_t);
static int jffs2_rmdir (struct inode *,struct dentry *);
static int jffs2_mknod (struct inode *,struct dentry *,umode_t,dev_t);
static int jffs2_rename (struct inode *, struct dentry *,
- struct inode *, struct dentry *);
+ struct inode *, struct dentry *, unsigned int);

const struct file_operations jffs2_dir_operations =
{
@@ -756,7 +756,8 @@ static int jffs2_mknod (struct inode *dir_i, struct dentry *dentry, umode_t mode
}

static int jffs2_rename (struct inode *old_dir_i, struct dentry *old_dentry,
- struct inode *new_dir_i, struct dentry *new_dentry)
+ struct inode *new_dir_i, struct dentry *new_dentry,
+ unsigned int flags)
{
int ret;
struct jffs2_sb_info *c = JFFS2_SB_INFO(old_dir_i->i_sb);
@@ -764,6 +765,9 @@ static int jffs2_rename (struct inode *old_dir_i, struct dentry *old_dentry,
uint8_t type;
uint32_t now;

+ if (flags)
+ return -EOPNOTSUPP;
+
/* The VFS will check for us and prevent trying to rename a
* file over a directory and vice versa, but if it's a directory,
* the VFS can't check whether the victim is empty. The filesystem
diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c
index aa8a3370631b..06841bcaad79 100644
--- a/fs/jfs/namei.c
+++ b/fs/jfs/namei.c
@@ -1062,7 +1062,8 @@ static int jfs_symlink(struct inode *dip, struct dentry *dentry,
* FUNCTION: rename a file or directory
*/
static int jfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct btstack btstack;
ino_t ino;
@@ -1081,6 +1082,8 @@ static int jfs_rename(struct inode *old_dir, struct dentry *old_dentry,
s64 new_size = 0;
int commit_flag;

+ if (flags)
+ return -EOPNOTSUPP;

jfs_info("jfs_rename: %s %s", old_dentry->d_name.name,
new_dentry->d_name.name);
diff --git a/fs/libfs.c b/fs/libfs.c
index a1844244246f..5297352d9b73 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -325,11 +325,15 @@ int simple_rmdir(struct inode *dir, struct dentry *dentry)
EXPORT_SYMBOL(simple_rmdir);

int simple_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode *inode = old_dentry->d_inode;
int they_are_dirs = S_ISDIR(old_dentry->d_inode->i_mode);

+ if (flags)
+ return -EOPNOTSUPP;
+
if (!simple_empty(new_dentry))
return -ENOTEMPTY;

diff --git a/fs/logfs/dir.c b/fs/logfs/dir.c
index 6bdc347008f5..43b32e7004ee 100644
--- a/fs/logfs/dir.c
+++ b/fs/logfs/dir.c
@@ -717,8 +717,12 @@ out:
}

static int logfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
+ if (flags)
+ return -EOPNOTSUPP;
+
if (new_dentry->d_inode)
return logfs_rename_target(old_dir, old_dentry,
new_dir, new_dentry);
diff --git a/fs/minix/namei.c b/fs/minix/namei.c
index cd950e2331b6..19eb25202c0e 100644
--- a/fs/minix/namei.c
+++ b/fs/minix/namei.c
@@ -184,8 +184,9 @@ static int minix_rmdir(struct inode * dir, struct dentry *dentry)
return err;
}

-static int minix_rename(struct inode * old_dir, struct dentry *old_dentry,
- struct inode * new_dir, struct dentry *new_dentry)
+static int minix_rename(struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode * old_inode = old_dentry->d_inode;
struct inode * new_inode = new_dentry->d_inode;
@@ -195,6 +196,9 @@ static int minix_rename(struct inode * old_dir, struct dentry *old_dentry,
struct minix_dir_entry * old_de;
int err = -ENOENT;

+ if (flags)
+ return -EOPNOTSUPP;
+
old_de = minix_find_entry(old_dentry, &old_page);
if (!old_de)
goto out;
diff --git a/fs/namei.c b/fs/namei.c
index f32a899fc039..593673fcbfef 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3969,6 +3969,7 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
* @new_dir: parent of destination
* @new_dentry: destination
* @delegated_inode: returns an inode needing a delegation break
+ * @flags: rename flags
*
* The caller must hold multiple mutexes--see lock_rename()).
*
@@ -4012,7 +4013,7 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
*/
int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry,
- struct inode **delegated_inode)
+ struct inode **delegated_inode, unsigned int flags)
{
int error;
bool is_dir = d_is_dir(old_dentry);
@@ -4082,7 +4083,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
goto out;
}
}
- error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
+ error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry,
+ flags);
if (error)
goto out;

@@ -4107,8 +4109,8 @@ out:
return error;
}

-SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
- int, newdfd, const char __user *, newname)
+SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
+ int, newdfd, const char __user *, newname, unsigned int, flags)
{
struct dentry *old_dir, *new_dir;
struct dentry *old_dentry, *new_dentry;
@@ -4120,6 +4122,10 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
unsigned int lookup_flags = 0;
bool should_retry = false;
int error;
+
+ if (flags)
+ return -EOPNOTSUPP;
+
retry:
from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags);
if (IS_ERR(from)) {
@@ -4191,8 +4197,8 @@ retry_deleg:
if (error)
goto exit5;
error = vfs_rename(old_dir->d_inode, old_dentry,
- new_dir->d_inode, new_dentry,
- &delegated_inode);
+ new_dir->d_inode, new_dentry,
+ &delegated_inode, flags);
exit5:
dput(new_dentry);
exit4:
@@ -4222,9 +4228,15 @@ exit:
return error;
}

+SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
+ int, newdfd, const char __user *, newname)
+{
+ return sys_renameat2(olddfd, oldname, newdfd, newname, 0);
+}
+
SYSCALL_DEFINE2(rename, const char __user *, oldname, const char __user *, newname)
{
- return sys_renameat(AT_FDCWD, oldname, AT_FDCWD, newname);
+ return sys_renameat2(AT_FDCWD, oldname, AT_FDCWD, newname, 0);
}

int vfs_readlink(struct dentry *dentry, char __user *buffer, int buflen, const char *link)
diff --git a/fs/ncpfs/dir.c b/fs/ncpfs/dir.c
index c320ac52353e..d4bf4f42d156 100644
--- a/fs/ncpfs/dir.c
+++ b/fs/ncpfs/dir.c
@@ -36,7 +36,7 @@ static int ncp_unlink(struct inode *, struct dentry *);
static int ncp_mkdir(struct inode *, struct dentry *, umode_t);
static int ncp_rmdir(struct inode *, struct dentry *);
static int ncp_rename(struct inode *, struct dentry *,
- struct inode *, struct dentry *);
+ struct inode *, struct dentry *, unsigned int);
static int ncp_mknod(struct inode * dir, struct dentry *dentry,
umode_t mode, dev_t rdev);
#if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
@@ -1113,13 +1113,17 @@ static int ncp_unlink(struct inode *dir, struct dentry *dentry)
}

static int ncp_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct ncp_server *server = NCP_SERVER(old_dir);
int error;
int old_len, new_len;
__u8 __old_name[NCP_MAXPATHLEN + 1], __new_name[NCP_MAXPATHLEN + 1];

+ if (flags)
+ return -EOPNOTSUPP;
+
DPRINTK("ncp_rename: %pd2 to %pd2\n", old_dentry, new_dentry);

ncp_age_dentry(server, old_dentry);
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 812154aff981..09881925d0e6 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1892,13 +1892,17 @@ EXPORT_SYMBOL_GPL(nfs_link);
* the rename.
*/
int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode *old_inode = old_dentry->d_inode;
struct inode *new_inode = new_dentry->d_inode;
struct dentry *dentry = NULL, *rehash = NULL;
int error = -EBUSY;

+ if (flags)
+ return -EOPNOTSUPP;
+
dfprintk(VFS, "NFS: rename(%pd2 -> %pd2, ct=%d)\n",
old_dentry, new_dentry,
d_count(new_dentry));
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 8b5cc04a8611..a2e287eb6b7d 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -303,7 +303,8 @@ int nfs_unlink(struct inode *, struct dentry *);
int nfs_symlink(struct inode *, struct dentry *, const char *);
int nfs_link(struct dentry *, struct inode *, struct dentry *);
int nfs_mknod(struct inode *, struct dentry *, umode_t, dev_t);
-int nfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *);
+int nfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *,
+ unsigned int);

/* file.c */
int nfs_file_fsync_commit(struct file *, loff_t, loff_t, int);
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 7eea63cada1d..ad63df8c1807 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1866,7 +1866,7 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
if (host_err)
goto out_dput_new;
}
- host_err = vfs_rename(fdir, odentry, tdir, ndentry, NULL);
+ host_err = vfs_rename(fdir, odentry, tdir, ndentry, NULL, 0);
if (!host_err) {
host_err = commit_metadata(tfhp);
if (!host_err)
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
index 9de78f08989e..6f49b9bcefc4 100644
--- a/fs/nilfs2/namei.c
+++ b/fs/nilfs2/namei.c
@@ -347,7 +347,8 @@ static int nilfs_rmdir(struct inode *dir, struct dentry *dentry)
}

static int nilfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode *old_inode = old_dentry->d_inode;
struct inode *new_inode = new_dentry->d_inode;
@@ -358,6 +359,9 @@ static int nilfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct nilfs_transaction_info ti;
int err;

+ if (flags)
+ return -EOPNOTSUPP;
+
err = nilfs_transaction_begin(old_dir->i_sb, &ti, 1);
if (unlikely(err))
return err;
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index 4f791f6d27d0..d117184c1eea 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -1038,7 +1038,8 @@ static void ocfs2_double_unlock(struct inode *inode1, struct inode *inode2)
static int ocfs2_rename(struct inode *old_dir,
struct dentry *old_dentry,
struct inode *new_dir,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ unsigned int flags)
{
int status = 0, rename_lock = 0, parents_locked = 0, target_exists = 0;
int old_child_locked = 0, new_child_locked = 0, update_dot_dot = 0;
@@ -1062,6 +1063,9 @@ static int ocfs2_rename(struct inode *old_dir,
struct ocfs2_dir_lookup_result orphan_insert = { NULL, };
struct ocfs2_dir_lookup_result target_insert = { NULL, };

+ if (flags)
+ return -EOPNOTSUPP;
+
/* At some point it might be nice to break this function up a
* bit. */

diff --git a/fs/omfs/dir.c b/fs/omfs/dir.c
index 1b8e9e8405b2..15e821af0c80 100644
--- a/fs/omfs/dir.c
+++ b/fs/omfs/dir.c
@@ -371,12 +371,16 @@ static bool omfs_fill_chain(struct inode *dir, struct dir_context *ctx,
}

static int omfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode *new_inode = new_dentry->d_inode;
struct inode *old_inode = old_dentry->d_inode;
int err;

+ if (flags)
+ return -EOPNOTSUPP;
+
if (new_inode) {
/* overwriting existing file/dir */
err = omfs_remove(new_dir, new_dentry);
diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
index dc5236f6de1b..e6d6a9cbb171 100644
--- a/fs/reiserfs/namei.c
+++ b/fs/reiserfs/namei.c
@@ -1202,7 +1202,8 @@ static void set_ino_in_dir_entry(struct reiserfs_dir_entry *de,
* get_empty_nodes or its clones
*/
static int reiserfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
int retval;
INITIALIZE_PATH(old_entry_path);
@@ -1217,6 +1218,9 @@ static int reiserfs_rename(struct inode *old_dir, struct dentry *old_dentry,
unsigned long savelink = 1;
struct timespec ctime;

+ if (flags)
+ return -EOPNOTSUPP;
+
/* three balancings: (1) old name removal, (2) new name insertion
and (3) maybe "save" link insertion
stat data updates: (1) old directory,
diff --git a/fs/sysv/namei.c b/fs/sysv/namei.c
index 731b2bbcaab3..b8811197c529 100644
--- a/fs/sysv/namei.c
+++ b/fs/sysv/namei.c
@@ -205,8 +205,9 @@ static int sysv_rmdir(struct inode * dir, struct dentry * dentry)
* Anybody can rename anything with this: the permission checks are left to the
* higher-level routines.
*/
-static int sysv_rename(struct inode * old_dir, struct dentry * old_dentry,
- struct inode * new_dir, struct dentry * new_dentry)
+static int sysv_rename(struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode * old_inode = old_dentry->d_inode;
struct inode * new_inode = new_dentry->d_inode;
@@ -216,6 +217,9 @@ static int sysv_rename(struct inode * old_dir, struct dentry * old_dentry,
struct sysv_dir_entry * old_de;
int err = -ENOENT;

+ if (flags)
+ return -EOPNOTSUPP;
+
old_de = sysv_find_entry(old_dentry, &old_page);
if (!old_de)
goto out;
diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c
index ea41649e4ca5..49d815460bb9 100644
--- a/fs/ubifs/dir.c
+++ b/fs/ubifs/dir.c
@@ -950,7 +950,8 @@ static void unlock_3_inodes(struct inode *inode1, struct inode *inode2,
}

static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct ubifs_info *c = old_dir->i_sb->s_fs_info;
struct inode *old_inode = old_dentry->d_inode;
@@ -968,6 +969,9 @@ static int ubifs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct timespec time;
unsigned int uninitialized_var(saved_nlink);

+ if (flags)
+ return -EOPNOTSUPP;
+
/*
* Budget request settings: deletion direntry, new direntry, removing
* the old inode, and changing old and new parent directory inodes.
diff --git a/fs/udf/namei.c b/fs/udf/namei.c
index 5f6fc17d6bc5..fd33cc096fda 100644
--- a/fs/udf/namei.c
+++ b/fs/udf/namei.c
@@ -1079,7 +1079,8 @@ static int udf_link(struct dentry *old_dentry, struct inode *dir,
* higher-level routines.
*/
static int udf_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode *old_inode = old_dentry->d_inode;
struct inode *new_inode = new_dentry->d_inode;
@@ -1091,6 +1092,9 @@ static int udf_rename(struct inode *old_dir, struct dentry *old_dentry,
struct kernel_lb_addr tloc;
struct udf_inode_info *old_iinfo = UDF_I(old_inode);

+ if (flags)
+ return -EOPNOTSUPP;
+
ofi = udf_find_entry(old_dir, &old_dentry->d_name, &ofibh, &ocfi);
if (ofi) {
if (ofibh.sbh != ofibh.ebh)
diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c
index 90d74b8f8eba..73ce50ee9e85 100644
--- a/fs/ufs/namei.c
+++ b/fs/ufs/namei.c
@@ -259,7 +259,8 @@ static int ufs_rmdir (struct inode * dir, struct dentry *dentry)
}

static int ufs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct inode *old_inode = old_dentry->d_inode;
struct inode *new_inode = new_dentry->d_inode;
@@ -269,6 +270,9 @@ static int ufs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct ufs_dir_entry *old_de;
int err = -ENOENT;

+ if (flags)
+ return -EOPNOTSUPP;
+
old_de = ufs_find_entry(old_dir, &old_dentry->d_name, &old_page);
if (!old_de)
goto out;
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 104455b8046c..9a6f9666bf25 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -346,12 +346,16 @@ xfs_vn_rename(
struct inode *odir,
struct dentry *odentry,
struct inode *ndir,
- struct dentry *ndentry)
+ struct dentry *ndentry,
+ unsigned int flags)
{
struct inode *new_inode = ndentry->d_inode;
struct xfs_name oname;
struct xfs_name nname;

+ if (flags)
+ return -EOPNOTSUPP;
+
xfs_dentry_to_name(&oname, odentry, 0);
xfs_dentry_to_name(&nname, ndentry, odentry->d_inode->i_mode);

diff --git a/include/linux/fs.h b/include/linux/fs.h
index 121f11f001c0..6a1cee720e54 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1456,7 +1456,7 @@ extern int vfs_symlink(struct inode *, struct dentry *, const char *);
extern int vfs_link(struct dentry *, struct inode *, struct dentry *, struct inode **);
extern int vfs_rmdir(struct inode *, struct dentry *);
extern int vfs_unlink(struct inode *, struct dentry *, struct inode **);
-extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **);
+extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **, unsigned int);

/*
* VFS dentry helper functions.
@@ -1566,7 +1566,7 @@ struct inode_operations {
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct inode *, struct dentry *,
- struct inode *, struct dentry *);
+ struct inode *, struct dentry *, unsigned int);
int (*setattr) (struct dentry *, struct iattr *);
int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
@@ -2612,7 +2612,7 @@ extern int simple_open(struct inode *inode, struct file *file);
extern int simple_link(struct dentry *, struct inode *, struct dentry *);
extern int simple_unlink(struct inode *, struct dentry *);
extern int simple_rmdir(struct inode *, struct dentry *);
-extern int simple_rename(struct inode *, struct dentry *, struct inode *, struct dentry *);
+extern int simple_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int);
extern int noop_fsync(struct file *, loff_t, loff_t, int);
extern int simple_empty(struct dentry *);
extern int simple_readpage(struct file *file, struct page *page);
diff --git a/kernel/cgroup.c b/kernel/cgroup.c
index bc1dcabe9217..383ad2248d6c 100644
--- a/kernel/cgroup.c
+++ b/kernel/cgroup.c
@@ -2510,12 +2510,16 @@ static int cgroup_file_release(struct inode *inode, struct file *file)
* cgroup_rename - Only allow simple rename of directories in place.
*/
static int cgroup_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
int ret;
struct cgroup_name *name, *old_name;
struct cgroup *cgrp;

+ if (flags)
+ return -EOPNOTSUPP;
+
/*
* It's convinient to use parent dir's i_mutex to protected
* cgrp->name.
@@ -2542,7 +2546,7 @@ static int cgroup_rename(struct inode *old_dir, struct dentry *old_dentry,
if (!name)
return -ENOMEM;

- ret = simple_rename(old_dir, old_dentry, new_dir, new_dentry);
+ ret = simple_rename(old_dir, old_dentry, new_dir, new_dentry, 0);
if (ret) {
kfree(name);
return ret;
diff --git a/mm/shmem.c b/mm/shmem.c
index 902a14842b74..094f47f5c014 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2067,11 +2067,14 @@ static int shmem_rmdir(struct inode *dir, struct dentry *dentry)
* it exists so that the VFS layer correctly free's it when it
* gets overwritten.
*/
-static int shmem_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry)
+static int shmem_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags)
{
struct inode *inode = old_dentry->d_inode;
int they_are_dirs = S_ISDIR(inode->i_mode);

+ if (flags)
+ return -EOPNOTSUPP;
+
if (!simple_empty(new_dentry))
return -ENOTEMPTY;

--
1.8.1.4

2014-01-08 22:09:49

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 02/11] vfs: rename: move d_move() up

From: Miklos Szeredi <[email protected]>

Move the d_move() in vfs_rename_dir() up, similarly to how it's done in
vfs_rename_other(). The next patch will consolidate these two functions
and this is the only structural difference between them.

I'm not sure if doing the d_move() after the dput is even valid. But there
may be a logical explanation for that. But moving the d_move() before the
dput() (and the mutex_unlock()) should definitely not hurt.

Signed-off-by: Miklos Szeredi <[email protected]>
---
fs/namei.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 601459c0b9b4..d848b31a646e 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4034,13 +4034,12 @@ static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry,
target->i_flags |= S_DEAD;
dont_mount(new_dentry);
}
+ if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE))
+ d_move(old_dentry, new_dentry);
out:
if (target)
mutex_unlock(&target->i_mutex);
dput(new_dentry);
- if (!error)
- if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE))
- d_move(old_dentry,new_dentry);
return error;
}

--
1.8.1.4

2014-01-08 22:10:07

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag

From: Miklos Szeredi <[email protected]>

If this flag is specified and the target of the rename exists then the
rename syscall fails with EEXIST.

The VFS does the existence checking, so it is trivial to enable for most
local filesystems. This patch only enables it in ext4.

For network filesystems the VFS check is not enough as there may be a race
between a remote create and the rename, so these filesystems need to handle
this flag in their ->rename() implementations to ensure atomicity.

Suggested-by: Andy Lutomirski <[email protected]>
Signed-off-by: Miklos Szeredi <[email protected]>
---
fs/ext4/namei.c | 2 +-
fs/namei.c | 21 +++++++++++++--------
include/uapi/linux/fs.h | 2 ++
3 files changed, 16 insertions(+), 9 deletions(-)

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 08c40f4e7eed..e0129b6e74cf 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3021,7 +3021,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
int inlined = 0, new_inlined = 0;
struct ext4_dir_entry_2 *parent_de;

- if (flags)
+ if (flags & ~RENAME_NOREPLACE)
return -EOPNOTSUPP;

dquot_initialize(old_dir);
diff --git a/fs/namei.c b/fs/namei.c
index 593673fcbfef..f9cf3020394c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4123,7 +4123,7 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
bool should_retry = false;
int error;

- if (flags)
+ if (flags & ~RENAME_NOREPLACE)
return -EOPNOTSUPP;

retry:
@@ -4149,6 +4149,8 @@ retry:
goto exit2;

new_dir = newnd.path.dentry;
+ if (flags & RENAME_NOREPLACE)
+ error = -EEXIST;
if (newnd.last_type != LAST_NORM)
goto exit2;

@@ -4171,22 +4173,25 @@ retry_deleg:
error = -ENOENT;
if (d_is_negative(old_dentry))
goto exit4;
+ new_dentry = lookup_hash(&newnd);
+ error = PTR_ERR(new_dentry);
+ if (IS_ERR(new_dentry))
+ goto exit4;
+ error = -EEXIST;
+ if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
+ goto exit5;
/* unless the source is a directory trailing slashes give -ENOTDIR */
if (!d_is_dir(old_dentry)) {
error = -ENOTDIR;
if (oldnd.last.name[oldnd.last.len])
- goto exit4;
+ goto exit5;
if (newnd.last.name[newnd.last.len])
- goto exit4;
+ goto exit5;
}
/* source should not be ancestor of target */
error = -EINVAL;
if (old_dentry == trap)
- goto exit4;
- new_dentry = lookup_hash(&newnd);
- error = PTR_ERR(new_dentry);
- if (IS_ERR(new_dentry))
- goto exit4;
+ goto exit5;
/* target should not be an ancestor of source */
error = -ENOTEMPTY;
if (new_dentry == trap)
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 6c28b61bb690..9250f4dd7d96 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -35,6 +35,8 @@
#define SEEK_HOLE 4 /* seek to the next hole */
#define SEEK_MAX SEEK_HOLE

+#define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */
+
struct fstrim_range {
__u64 start;
__u64 len;
--
1.8.1.4

2014-01-08 22:10:04

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 06/11] security: add flags to rename hooks

From: Miklos Szeredi <[email protected]>

Add flags to security_path_rename() and security_inode_rename() hooks.

Signed-off-by: Miklos Szeredi <[email protected]>
---
fs/cachefiles/namei.c | 2 +-
fs/namei.c | 5 +++--
include/linux/security.h | 12 ++++++++----
security/security.c | 6 ++++--
4 files changed, 16 insertions(+), 9 deletions(-)

diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
index 31088a969351..6494d9f673aa 100644
--- a/fs/cachefiles/namei.c
+++ b/fs/cachefiles/namei.c
@@ -391,7 +391,7 @@ try_again:
path.dentry = dir;
path_to_graveyard.mnt = cache->mnt;
path_to_graveyard.dentry = cache->graveyard;
- ret = security_path_rename(&path, rep, &path_to_graveyard, grave);
+ ret = security_path_rename(&path, rep, &path_to_graveyard, grave, 0);
if (ret < 0) {
cachefiles_io_error(cache, "Rename security error %d", ret);
} else {
diff --git a/fs/namei.c b/fs/namei.c
index f9cf3020394c..3fbc95c72e31 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4048,7 +4048,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
return error;
}

- error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry);
+ error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry,
+ flags);
if (error)
return error;

@@ -4198,7 +4199,7 @@ retry_deleg:
goto exit5;

error = security_path_rename(&oldnd.path, old_dentry,
- &newnd.path, new_dentry);
+ &newnd.path, new_dentry, flags);
if (error)
goto exit5;
error = vfs_rename(old_dir->d_inode, old_dentry,
diff --git a/include/linux/security.h b/include/linux/security.h
index 5623a7f965b7..95cfccc213fb 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -1792,7 +1792,8 @@ int security_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
int security_inode_rmdir(struct inode *dir, struct dentry *dentry);
int security_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev);
int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry);
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags);
int security_inode_readlink(struct dentry *dentry);
int security_inode_follow_link(struct dentry *dentry, struct nameidata *nd);
int security_inode_permission(struct inode *inode, int mask);
@@ -2160,7 +2161,8 @@ static inline int security_inode_mknod(struct inode *dir,
static inline int security_inode_rename(struct inode *old_dir,
struct dentry *old_dentry,
struct inode *new_dir,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ unsigned int flags)
{
return 0;
}
@@ -2951,7 +2953,8 @@ int security_path_symlink(struct path *dir, struct dentry *dentry,
int security_path_link(struct dentry *old_dentry, struct path *new_dir,
struct dentry *new_dentry);
int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry);
+ struct path *new_dir, struct dentry *new_dentry,
+ unsigned int flags);
int security_path_chmod(struct path *path, umode_t mode);
int security_path_chown(struct path *path, kuid_t uid, kgid_t gid);
int security_path_chroot(struct path *path);
@@ -2999,7 +3002,8 @@ static inline int security_path_link(struct dentry *old_dentry,
static inline int security_path_rename(struct path *old_dir,
struct dentry *old_dentry,
struct path *new_dir,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ unsigned int flags)
{
return 0;
}
diff --git a/security/security.c b/security/security.c
index 15b6928592ef..edc179f1ade0 100644
--- a/security/security.c
+++ b/security/security.c
@@ -433,7 +433,8 @@ int security_path_link(struct dentry *old_dentry, struct path *new_dir,
}

int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry)
+ struct path *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
@@ -524,7 +525,8 @@ int security_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
}

int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
--
1.8.1.4

2014-01-08 22:10:50

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 10/11] ext4: rename: split out helper functions

From: Miklos Szeredi <[email protected]>

Cross rename (exchange source and dest) will need to call some of these
helpers for both source and dest, while overwriting rename currently only
calls them for one or the other. This also makes the code easier to
follow.

Signed-off-by: Miklos Szeredi <[email protected]>
Reviewed-by: Jan Kara <[email protected]>
---
fs/ext4/namei.c | 199 +++++++++++++++++++++++++++++++++++---------------------
1 file changed, 126 insertions(+), 73 deletions(-)

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 7f40f2ea2f4a..7147d08a43a2 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3017,6 +3017,125 @@ struct ext4_renament {
int dir_inlined;
};

+static int ext4_rename_dir_prepare(handle_t *handle, struct ext4_renament *ent)
+{
+ int retval;
+
+ ent->dir_bh = ext4_get_first_dir_block(handle, ent->inode,
+ &retval, &ent->parent_de,
+ &ent->dir_inlined);
+ if (!ent->dir_bh)
+ return retval;
+ if (le32_to_cpu(ent->parent_de->inode) != ent->dir->i_ino)
+ return -EIO;
+ BUFFER_TRACE(ent->dir_bh, "get_write_access");
+ return ext4_journal_get_write_access(handle, ent->dir_bh);
+}
+
+static int ext4_rename_dir_finish(handle_t *handle, struct ext4_renament *ent,
+ unsigned dir_ino)
+{
+ int retval;
+
+ ent->parent_de->inode = cpu_to_le32(dir_ino);
+ BUFFER_TRACE(ent->dir_bh, "call ext4_handle_dirty_metadata");
+ if (!ent->dir_inlined) {
+ if (is_dx(ent->inode)) {
+ retval = ext4_handle_dirty_dx_node(handle,
+ ent->inode,
+ ent->dir_bh);
+ } else {
+ retval = ext4_handle_dirty_dirent_node(handle,
+ ent->inode,
+ ent->dir_bh);
+ }
+ } else {
+ retval = ext4_mark_inode_dirty(handle, ent->inode);
+ }
+ if (retval) {
+ ext4_std_error(ent->dir->i_sb, retval);
+ return retval;
+ }
+ return 0;
+}
+
+static int ext4_setent(handle_t *handle, struct ext4_renament *ent,
+ unsigned ino, unsigned file_type)
+{
+ int retval;
+
+ BUFFER_TRACE(ent->bh, "get write access");
+ retval = ext4_journal_get_write_access(handle, ent->bh);
+ if (retval)
+ return retval;
+ ent->de->inode = cpu_to_le32(ino);
+ if (EXT4_HAS_INCOMPAT_FEATURE(ent->dir->i_sb,
+ EXT4_FEATURE_INCOMPAT_FILETYPE))
+ ent->de->file_type = file_type;
+ ent->dir->i_version++;
+ ent->dir->i_ctime = ent->dir->i_mtime =
+ ext4_current_time(ent->dir);
+ ext4_mark_inode_dirty(handle, ent->dir);
+ BUFFER_TRACE(ent->bh, "call ext4_handle_dirty_metadata");
+ if (!ent->inlined) {
+ retval = ext4_handle_dirty_dirent_node(handle,
+ ent->dir, ent->bh);
+ if (unlikely(retval)) {
+ ext4_std_error(ent->dir->i_sb, retval);
+ return retval;
+ }
+ }
+ brelse(ent->bh);
+ ent->bh = NULL;
+
+ return 0;
+}
+
+static int ext4_find_delete_entry(handle_t *handle, struct inode *dir,
+ const struct qstr *d_name)
+{
+ int retval = -ENOENT;
+ struct buffer_head *bh;
+ struct ext4_dir_entry_2 *de;
+
+ bh = ext4_find_entry(dir, d_name, &de, NULL);
+ if (bh) {
+ retval = ext4_delete_entry(handle, dir, de, bh);
+ brelse(bh);
+ }
+ return retval;
+}
+
+static void ext4_rename_delete(handle_t *handle, struct ext4_renament *ent)
+{
+ int retval;
+ /*
+ * ent->de could have moved from under us during htree split, so make
+ * sure that we are deleting the right entry. We might also be pointing
+ * to a stale entry in the unused part of ent->bh so just checking inum
+ * and the name isn't enough.
+ */
+ if (le32_to_cpu(ent->de->inode) != ent->inode->i_ino ||
+ ent->de->name_len != ent->dentry->d_name.len ||
+ strncmp(ent->de->name, ent->dentry->d_name.name,
+ ent->de->name_len)) {
+ retval = ext4_find_delete_entry(handle, ent->dir,
+ &ent->dentry->d_name);
+ } else {
+ retval = ext4_delete_entry(handle, ent->dir, ent->de, ent->bh);
+ if (retval == -ENOENT) {
+ retval = ext4_find_delete_entry(handle, ent->dir,
+ &ent->dentry->d_name);
+ }
+ }
+
+ if (retval) {
+ ext4_warning(ent->dir->i_sb,
+ "Deleting old file (%lu), %d, error=%d",
+ ent->dir->i_ino, ent->dir->i_nlink, retval);
+ }
+}
+
/*
* Anybody can rename anything with this: the permission checks are left to the
* higher-level routines.
@@ -3094,16 +3213,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir))
goto end_rename;
}
- retval = -EIO;
- old.dir_bh = ext4_get_first_dir_block(handle, old.inode,
- &retval, &old.parent_de,
- &old.dir_inlined);
- if (!old.dir_bh)
- goto end_rename;
- if (le32_to_cpu(old.parent_de->inode) != old.dir->i_ino)
- goto end_rename;
- BUFFER_TRACE(old.dir_bh, "get_write_access");
- retval = ext4_journal_get_write_access(handle, old.dir_bh);
+ retval = ext4_rename_dir_prepare(handle, &old);
if (retval)
goto end_rename;
}
@@ -3112,29 +3222,10 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
if (retval)
goto end_rename;
} else {
- BUFFER_TRACE(new.bh, "get write access");
- retval = ext4_journal_get_write_access(handle, new.bh);
+ retval = ext4_setent(handle, &new,
+ old.inode->i_ino, old.de->file_type);
if (retval)
goto end_rename;
- new.de->inode = cpu_to_le32(old.inode->i_ino);
- if (EXT4_HAS_INCOMPAT_FEATURE(new.dir->i_sb,
- EXT4_FEATURE_INCOMPAT_FILETYPE))
- new.de->file_type = old.de->file_type;
- new.dir->i_version++;
- new.dir->i_ctime = new.dir->i_mtime =
- ext4_current_time(new.dir);
- ext4_mark_inode_dirty(handle, new.dir);
- BUFFER_TRACE(new.bh, "call ext4_handle_dirty_metadata");
- if (!new.inlined) {
- retval = ext4_handle_dirty_dirent_node(handle,
- new.dir, new.bh);
- if (unlikely(retval)) {
- ext4_std_error(new.dir->i_sb, retval);
- goto end_rename;
- }
- }
- brelse(new.bh);
- new.bh = NULL;
}

/*
@@ -3147,31 +3238,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
/*
* ok, that's it
*/
- if (le32_to_cpu(old.de->inode) != old.inode->i_ino ||
- old.de->name_len != old.dentry->d_name.len ||
- strncmp(old.de->name, old.dentry->d_name.name, old.de->name_len) ||
- (retval = ext4_delete_entry(handle, old.dir,
- old.de, old.bh)) == -ENOENT) {
- /* old.de could have moved from under us during htree split, so
- * make sure that we are deleting the right entry. We might
- * also be pointing to a stale entry in the unused part of
- * old.bh so just checking inum and the name isn't enough. */
- struct buffer_head *old_bh2;
- struct ext4_dir_entry_2 *old_de2;
-
- old_bh2 = ext4_find_entry(old.dir, &old.dentry->d_name,
- &old_de2, NULL);
- if (old_bh2) {
- retval = ext4_delete_entry(handle, old.dir,
- old_de2, old_bh2);
- brelse(old_bh2);
- }
- }
- if (retval) {
- ext4_warning(old.dir->i_sb,
- "Deleting old file (%lu), %d, error=%d",
- old.dir->i_ino, old.dir->i_nlink, retval);
- }
+ ext4_rename_delete(handle, &old);

if (new.inode) {
ext4_dec_count(handle, new.inode);
@@ -3180,24 +3247,10 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir);
ext4_update_dx_flag(old.dir);
if (old.dir_bh) {
- old.parent_de->inode = cpu_to_le32(new.dir->i_ino);
- BUFFER_TRACE(old.dir_bh, "call ext4_handle_dirty_metadata");
- if (!old.dir_inlined) {
- if (is_dx(old.inode)) {
- retval = ext4_handle_dirty_dx_node(handle,
- old.inode,
- old.dir_bh);
- } else {
- retval = ext4_handle_dirty_dirent_node(handle,
- old.inode, old.dir_bh);
- }
- } else {
- retval = ext4_mark_inode_dirty(handle, old.inode);
- }
- if (retval) {
- ext4_std_error(old.dir->i_sb, retval);
+ retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino);
+ if (retval)
goto end_rename;
- }
+
ext4_dec_count(handle, old.dir);
if (new.inode) {
/* checked empty_dir above, can't have another parent,
--
1.8.1.4

2014-01-08 22:10:55

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 09/11] ext4: rename: move EMLINK check up

From: Miklos Szeredi <[email protected]>

Move checking i_nlink from after ext4_get_first_dir_block() to before. The
check doesn't rely on the result of that function and the function only
fails on fs corruption, so the order shouldn't matter.

Signed-off-by: Miklos Szeredi <[email protected]>
Reviewed-by: Jan Kara <[email protected]>
---
fs/ext4/namei.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index bb3e2b4614dc..7f40f2ea2f4a 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3089,6 +3089,10 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
retval = -ENOTEMPTY;
if (!empty_dir(new.inode))
goto end_rename;
+ } else {
+ retval = -EMLINK;
+ if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir))
+ goto end_rename;
}
retval = -EIO;
old.dir_bh = ext4_get_first_dir_block(handle, old.inode,
@@ -3098,10 +3102,6 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
goto end_rename;
if (le32_to_cpu(old.parent_de->inode) != old.dir->i_ino)
goto end_rename;
- retval = -EMLINK;
- if (!new.inode && new.dir != old.dir &&
- EXT4_DIR_LINK_MAX(new.dir))
- goto end_rename;
BUFFER_TRACE(old.dir_bh, "get_write_access");
retval = ext4_journal_get_write_access(handle, old.dir_bh);
if (retval)
--
1.8.1.4

2014-01-08 22:10:47

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 11/11] ext4: add cross rename support

From: Miklos Szeredi <[email protected]>

Implement RENAME_EXCHANGE flag in renameat2 syscall.

Signed-off-by: Miklos Szeredi <[email protected]>
---
fs/ext4/namei.c | 121 ++++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 87 insertions(+), 34 deletions(-)

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 7147d08a43a2..e4513ba7ed99 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3005,6 +3005,8 @@ struct ext4_renament {
struct inode *dir;
struct dentry *dentry;
struct inode *inode;
+ bool is_dir;
+ int dir_nlink_delta;

/* entry for "dentry" */
struct buffer_head *bh;
@@ -3136,6 +3138,14 @@ static void ext4_rename_delete(handle_t *handle, struct ext4_renament *ent)
}
}

+static void ext4_update_dir_count(handle_t *handle, struct ext4_renament *ent)
+{
+ if (ent->dir_nlink_delta == -1)
+ ext4_dec_count(handle, ent->dir);
+ else if (ent->dir_nlink_delta == 1)
+ ext4_inc_count(handle, ent->dir);
+}
+
/*
* Anybody can rename anything with this: the permission checks are left to the
* higher-level routines.
@@ -3161,7 +3171,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
};
int retval;

- if (flags & ~RENAME_NOREPLACE)
+ if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
return -EOPNOTSUPP;

dquot_initialize(old.dir);
@@ -3169,10 +3179,11 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,

/* Initialize quotas before so that eventual writes go
* in separate transaction */
- if (new.inode)
+ if (!(flags & RENAME_EXCHANGE) && new.inode)
dquot_initialize(new.inode);

- old.bh = ext4_find_entry(old.dir, &old.dentry->d_name, &old.de, NULL);
+ old.bh = ext4_find_entry(old.dir, &old.dentry->d_name,
+ &old.de, &old.inlined);
/*
* Check for inode number is _not_ due to possible IO errors.
* We might rmdir the source, keep it as pwd of some process
@@ -3185,18 +3196,22 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,

new.bh = ext4_find_entry(new.dir, &new.dentry->d_name,
&new.de, &new.inlined);
- if (new.bh) {
- if (!new.inode) {
- brelse(new.bh);
- new.bh = NULL;
+ if (!(flags & RENAME_EXCHANGE)) {
+ if (new.bh) {
+ if (!new.inode) {
+ brelse(new.bh);
+ new.bh = NULL;
+ }
}
+ if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC))
+ ext4_alloc_da_blocks(old.inode);
+ } else if (!new.bh || le32_to_cpu(new.de->inode) != new.inode->i_ino) {
+ goto end_rename;
}
- if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC))
- ext4_alloc_da_blocks(old.inode);

handle = ext4_journal_start(old.dir, EXT4_HT_DIR,
(2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) +
- EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2));
+ 2 * EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2));
if (IS_ERR(handle))
return PTR_ERR(handle);

@@ -3204,28 +3219,61 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
ext4_handle_sync(handle);

if (S_ISDIR(old.inode->i_mode)) {
- if (new.inode) {
+ old.is_dir = true;
+ if (!(flags & RENAME_EXCHANGE) && new.inode) {
retval = -ENOTEMPTY;
if (!empty_dir(new.inode))
goto end_rename;
- } else {
- retval = -EMLINK;
- if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir))
- goto end_rename;
+
+ /*
+ * Overwriting a directory needs to decrement nlink of
+ * old parent (even if not cross directory rename).
+ */
+ old.dir_nlink_delta = -1;
}
retval = ext4_rename_dir_prepare(handle, &old);
if (retval)
goto end_rename;
}
+ if (new.inode && S_ISDIR(new.inode->i_mode)) {
+ new.is_dir = true;
+ if (flags & RENAME_EXCHANGE) {
+ retval = ext4_rename_dir_prepare(handle, &new);
+ if (retval)
+ goto end_rename;
+ }
+ }
+
+ /*
+ * Other than the special case of overwring a directory, parents' nlink
+ * only needs to be modified if this is a cross directory rename.
+ */
+ if (old.dir != new.dir && old.is_dir != new.is_dir) {
+ old.dir_nlink_delta = old.is_dir ? -1 : 1;
+ new.dir_nlink_delta = -old.dir_nlink_delta;
+ retval = -EMLINK;
+ if ((old.dir_nlink_delta > 0 && EXT4_DIR_LINK_MAX(old.dir)) ||
+ (new.dir_nlink_delta > 0 && EXT4_DIR_LINK_MAX(new.dir)))
+ goto end_rename;
+ }
+
if (!new.bh) {
retval = ext4_add_entry(handle, new.dentry, old.inode);
if (retval)
goto end_rename;
} else {
+ u8 new_file_type = new.de->file_type;
retval = ext4_setent(handle, &new,
old.inode->i_ino, old.de->file_type);
if (retval)
goto end_rename;
+
+ if (flags & RENAME_EXCHANGE) {
+ retval = ext4_setent(handle, &old,
+ new.inode->i_ino, new_file_type);
+ if (retval)
+ goto end_rename;
+ }
}

/*
@@ -3235,35 +3283,40 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
old.inode->i_ctime = ext4_current_time(old.inode);
ext4_mark_inode_dirty(handle, old.inode);

- /*
- * ok, that's it
- */
- ext4_rename_delete(handle, &old);
+ if (!(flags & RENAME_EXCHANGE)) {
+ /*
+ * ok, that's it
+ */
+ ext4_rename_delete(handle, &old);

- if (new.inode) {
- ext4_dec_count(handle, new.inode);
- new.inode->i_ctime = ext4_current_time(new.inode);
+ old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir);
+ ext4_update_dx_flag(old.dir);
}
- old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir);
- ext4_update_dx_flag(old.dir);
if (old.dir_bh) {
retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino);
if (retval)
goto end_rename;
-
- ext4_dec_count(handle, old.dir);
- if (new.inode) {
+ }
+ if (new.dir_bh) {
+ retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino);
+ if (retval)
+ goto end_rename;
+ }
+ ext4_update_dir_count(handle, &old);
+ ext4_update_dir_count(handle, &new);
+ if (!new.inode)
+ ext4_update_dx_flag(new.dir);
+ if (new.dir_nlink_delta || !new.inode)
+ ext4_mark_inode_dirty(handle, new.dir);
+ ext4_mark_inode_dirty(handle, old.dir);
+ if (!(flags & RENAME_EXCHANGE) && new.inode) {
+ ext4_dec_count(handle, new.inode);
+ new.inode->i_ctime = ext4_current_time(new.inode);
+ if (S_ISDIR(old.inode->i_mode)) {
/* checked empty_dir above, can't have another parent,
* ext4_dec_count() won't work for many-linked dirs */
clear_nlink(new.inode);
- } else {
- ext4_inc_count(handle, new.dir);
- ext4_update_dx_flag(new.dir);
- ext4_mark_inode_dirty(handle, new.dir);
}
- }
- ext4_mark_inode_dirty(handle, old.dir);
- if (new.inode) {
ext4_mark_inode_dirty(handle, new.inode);
if (!new.inode->i_nlink)
ext4_orphan_add(handle, new.inode);
--
1.8.1.4

2014-01-08 22:11:57

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 07/11] vfs: add cross-rename

From: Miklos Szeredi <[email protected]>

If flags contain RENAME_EXCHANGE then exchange source and destination files.
There's no restriction on the type of the files; e.g. a directory can be
exchanged with a symlink.

Signed-off-by: Miklos Szeredi <[email protected]>
---
fs/dcache.c | 46 +++++++++++++++++----
fs/namei.c | 107 +++++++++++++++++++++++++++++++++---------------
include/linux/dcache.h | 1 +
include/uapi/linux/fs.h | 1 +
security/security.c | 16 ++++++++
5 files changed, 131 insertions(+), 40 deletions(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index 6055d61811d3..094ec9c4cb64 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -2483,12 +2483,14 @@ static void switch_names(struct dentry *dentry, struct dentry *target)
dentry->d_name.name = dentry->d_iname;
} else {
/*
- * Both are internal. Just copy target to dentry
+ * Both are internal.
*/
- memcpy(dentry->d_iname, target->d_name.name,
- target->d_name.len + 1);
- dentry->d_name.len = target->d_name.len;
- return;
+ unsigned int i;
+ BUILD_BUG_ON(!IS_ALIGNED(DNAME_INLINE_LEN, sizeof(long)));
+ for (i = 0; i < DNAME_INLINE_LEN / sizeof(long); i++) {
+ swap(((long *) &dentry->d_iname)[i],
+ ((long *) &target->d_iname)[i]);
+ }
}
}
swap(dentry->d_name.len, target->d_name.len);
@@ -2545,13 +2547,15 @@ static void dentry_unlock_parents_for_move(struct dentry *dentry,
* __d_move - move a dentry
* @dentry: entry to move
* @target: new dentry
+ * @exchange: exchange the two dentries
*
* Update the dcache to reflect the move of a file name. Negative
* dcache entries should not be moved in this way. Caller must hold
* rename_lock, the i_mutex of the source and target directories,
* and the sb->s_vfs_rename_mutex if they differ. See lock_rename().
*/
-static void __d_move(struct dentry * dentry, struct dentry * target)
+static void __d_move(struct dentry *dentry, struct dentry *target,
+ bool exchange)
{
if (!dentry->d_inode)
printk(KERN_WARNING "VFS: moving negative dcache entry\n");
@@ -2575,6 +2579,10 @@ static void __d_move(struct dentry * dentry, struct dentry * target)

/* Unhash the target: dput() will then get rid of it */
__d_drop(target);
+ if (exchange) {
+ __d_rehash(target,
+ d_hash(dentry->d_parent, dentry->d_name.hash));
+ }

list_del(&dentry->d_u.d_child);
list_del(&target->d_u.d_child);
@@ -2601,6 +2609,8 @@ static void __d_move(struct dentry * dentry, struct dentry * target)
write_seqcount_end(&dentry->d_seq);

dentry_unlock_parents_for_move(dentry, target);
+ if (exchange)
+ fsnotify_d_move(target);
spin_unlock(&target->d_lock);
fsnotify_d_move(dentry);
spin_unlock(&dentry->d_lock);
@@ -2618,11 +2628,31 @@ static void __d_move(struct dentry * dentry, struct dentry * target)
void d_move(struct dentry *dentry, struct dentry *target)
{
write_seqlock(&rename_lock);
- __d_move(dentry, target);
+ __d_move(dentry, target, false);
write_sequnlock(&rename_lock);
}
EXPORT_SYMBOL(d_move);

+/*
+ * d_exchange - exchange two dentries
+ * @dentry1: first dentry
+ * @dentry2: second dentry
+ */
+void d_exchange(struct dentry *dentry1, struct dentry *dentry2)
+{
+ write_seqlock(&rename_lock);
+
+ WARN_ON(!dentry1->d_inode);
+ WARN_ON(!dentry2->d_inode);
+ WARN_ON(IS_ROOT(dentry1));
+ WARN_ON(IS_ROOT(dentry2));
+
+ __d_move(dentry1, dentry2, true);
+
+ write_sequnlock(&rename_lock);
+}
+
+
/**
* d_ancestor - search for an ancestor
* @p1: ancestor dentry
@@ -2670,7 +2700,7 @@ static struct dentry *__d_unalias(struct inode *inode,
m2 = &alias->d_parent->d_inode->i_mutex;
out_unalias:
if (likely(!d_mountpoint(alias))) {
- __d_move(alias, dentry);
+ __d_move(alias, dentry, false);
ret = alias;
}
out_err:
diff --git a/fs/namei.c b/fs/namei.c
index 3fbc95c72e31..97c6dbb47eca 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4020,6 +4020,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
const unsigned char *old_name;
struct inode *source = old_dentry->d_inode;
struct inode *target = new_dentry->d_inode;
+ bool new_is_dir = false;
+ unsigned max_links = new_dir->i_sb->s_max_links;

if (source == target)
return 0;
@@ -4028,10 +4030,16 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
if (error)
return error;

- if (!target)
+ if (!target) {
error = may_create(new_dir, new_dentry);
- else
- error = may_delete(new_dir, new_dentry, is_dir);
+ } else {
+ new_is_dir = d_is_dir(new_dentry);
+
+ if (!(flags & RENAME_EXCHANGE))
+ error = may_delete(new_dir, new_dentry, is_dir);
+ else
+ error = may_delete(new_dir, new_dentry, new_is_dir);
+ }
if (error)
return error;

@@ -4042,10 +4050,17 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
* If we are going to change the parent - check write permissions,
* we'll need to flip '..'.
*/
- if (is_dir && new_dir != old_dir) {
- error = inode_permission(source, MAY_WRITE);
- if (error)
- return error;
+ if (new_dir != old_dir) {
+ if (is_dir) {
+ error = inode_permission(source, MAY_WRITE);
+ if (error)
+ return error;
+ }
+ if ((flags & RENAME_EXCHANGE) && new_is_dir) {
+ error = inode_permission(target, MAY_WRITE);
+ if (error)
+ return error;
+ }
}

error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry,
@@ -4055,25 +4070,24 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,

old_name = fsnotify_oldname_init(old_dentry->d_name.name);
dget(new_dentry);
- if (!is_dir)
- lock_two_nondirectories(source, target);
- else if (target)
- mutex_lock(&target->i_mutex);
+ if (!(flags & RENAME_EXCHANGE)) {
+ if (!is_dir)
+ lock_two_nondirectories(source, target);
+ else if (target)
+ mutex_lock(&target->i_mutex);
+ }

error = -EBUSY;
if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry))
goto out;

- if (is_dir) {
- unsigned max_links = new_dir->i_sb->s_max_links;
-
+ if (max_links && new_dir != old_dir) {
error = -EMLINK;
- if (max_links && !target && new_dir != old_dir &&
- new_dir->i_nlink >= max_links)
+ if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links)
+ goto out;
+ if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir &&
+ old_dir->i_nlink > max_links)
goto out;
-
- if (target)
- shrink_dcache_parent(new_dentry);
} else {
error = try_break_deleg(source, delegated_inode);
if (error)
@@ -4084,27 +4098,40 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
goto out;
}
}
+ if (is_dir && !(flags & RENAME_EXCHANGE) && target)
+ shrink_dcache_parent(new_dentry);
error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry,
flags);
if (error)
goto out;

- if (target) {
+ if (!(flags & RENAME_EXCHANGE) && target) {
if (is_dir)
target->i_flags |= S_DEAD;
dont_mount(new_dentry);
}
- if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE))
- d_move(old_dentry, new_dentry);
+ if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) {
+ if (!(flags & RENAME_EXCHANGE))
+ d_move(old_dentry, new_dentry);
+ else
+ d_exchange(old_dentry, new_dentry);
+ }
out:
- if (!is_dir)
- unlock_two_nondirectories(source, target);
- else if (target)
- mutex_unlock(&target->i_mutex);
+ if (!(flags & RENAME_EXCHANGE)) {
+ if (!is_dir)
+ unlock_two_nondirectories(source, target);
+ else if (target)
+ mutex_unlock(&target->i_mutex);
+ }
dput(new_dentry);
- if (!error)
+ if (!error) {
fsnotify_move(old_dir, new_dir, old_name, is_dir,
- target, old_dentry);
+ !(flags & RENAME_EXCHANGE) ? target : NULL, old_dentry);
+ if (flags & RENAME_EXCHANGE) {
+ fsnotify_move(new_dir, old_dir, old_dentry->d_name.name,
+ new_is_dir, NULL, new_dentry);
+ }
+ }
fsnotify_oldname_free(old_name);

return error;
@@ -4124,9 +4151,12 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
bool should_retry = false;
int error;

- if (flags & ~RENAME_NOREPLACE)
+ if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
return -EOPNOTSUPP;

+ if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE))
+ return -EINVAL;
+
retry:
from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags);
if (IS_ERR(from)) {
@@ -4161,7 +4191,8 @@ retry:

oldnd.flags &= ~LOOKUP_PARENT;
newnd.flags &= ~LOOKUP_PARENT;
- newnd.flags |= LOOKUP_RENAME_TARGET;
+ if (!(flags & RENAME_EXCHANGE))
+ newnd.flags |= LOOKUP_RENAME_TARGET;

retry_deleg:
trap = lock_rename(new_dir, old_dir);
@@ -4181,12 +4212,23 @@ retry_deleg:
error = -EEXIST;
if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
goto exit5;
+ if (flags & RENAME_EXCHANGE) {
+ error = -ENOENT;
+ if (!new_dentry->d_inode)
+ goto exit5;
+
+ if (!d_is_dir(new_dentry)) {
+ error = -ENOTDIR;
+ if (newnd.last.name[newnd.last.len])
+ goto exit5;
+ }
+ }
/* unless the source is a directory trailing slashes give -ENOTDIR */
if (!d_is_dir(old_dentry)) {
error = -ENOTDIR;
if (oldnd.last.name[oldnd.last.len])
goto exit5;
- if (newnd.last.name[newnd.last.len])
+ if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len])
goto exit5;
}
/* source should not be ancestor of target */
@@ -4194,7 +4236,8 @@ retry_deleg:
if (old_dentry == trap)
goto exit5;
/* target should not be an ancestor of source */
- error = -ENOTEMPTY;
+ if (!(flags & RENAME_EXCHANGE))
+ error = -ENOTEMPTY;
if (new_dentry == trap)
goto exit5;

diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 3b50cac7ccb3..3b9bfdb83ba6 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -308,6 +308,7 @@ extern void dentry_update_name_case(struct dentry *, struct qstr *);

/* used for rename() and baskets */
extern void d_move(struct dentry *, struct dentry *);
+extern void d_exchange(struct dentry *, struct dentry *);
extern struct dentry *d_ancestor(struct dentry *, struct dentry *);

/* appendix may either be NULL or be used for transname suffixes */
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 9250f4dd7d96..ca1a11bb4443 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -36,6 +36,7 @@
#define SEEK_MAX SEEK_HOLE

#define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */
+#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */

struct fstrim_range {
__u64 start;
diff --git a/security/security.c b/security/security.c
index edc179f1ade0..3dd2258b7a38 100644
--- a/security/security.c
+++ b/security/security.c
@@ -439,6 +439,14 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;
+
+ if (flags & RENAME_EXCHANGE) {
+ int err = security_ops->path_rename(new_dir, new_dentry,
+ old_dir, old_dentry);
+ if (err)
+ return err;
+ }
+
return security_ops->path_rename(old_dir, old_dentry, new_dir,
new_dentry);
}
@@ -531,6 +539,14 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;
+
+ if (flags & RENAME_EXCHANGE) {
+ int err = security_ops->inode_rename(new_dir, new_dentry,
+ old_dir, old_dentry);
+ if (err)
+ return err;
+ }
+
return security_ops->inode_rename(old_dir, old_dentry,
new_dir, new_dentry);
}
--
1.8.1.4

2014-01-08 22:11:53

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars

From: Miklos Szeredi <[email protected]>

Need to split up ext4_rename() into helpers but there are too many local
variables involved, so create a new structure. This also, apparently,
makes the generated code size slightly smaller.

Signed-off-by: Miklos Szeredi <[email protected]>
Reviewed-by: Jan Kara <[email protected]>
---
fs/ext4/namei.c | 211 ++++++++++++++++++++++++++++++--------------------------
1 file changed, 114 insertions(+), 97 deletions(-)

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index e0129b6e74cf..bb3e2b4614dc 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3001,6 +3001,22 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle,
return ext4_get_first_inline_block(inode, parent_de, retval);
}

+struct ext4_renament {
+ struct inode *dir;
+ struct dentry *dentry;
+ struct inode *inode;
+
+ /* entry for "dentry" */
+ struct buffer_head *bh;
+ struct ext4_dir_entry_2 *de;
+ int inlined;
+
+ /* entry for ".." in inode if it's a directory */
+ struct buffer_head *dir_bh;
+ struct ext4_dir_entry_2 *parent_de;
+ int dir_inlined;
+};
+
/*
* Anybody can rename anything with this: the permission checks are left to the
* higher-level routines.
@@ -3014,196 +3030,197 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
unsigned int flags)
{
handle_t *handle = NULL;
- struct inode *old_inode, *new_inode;
- struct buffer_head *old_bh, *new_bh, *dir_bh;
- struct ext4_dir_entry_2 *old_de, *new_de;
+ struct ext4_renament old = {
+ .dir = old_dir,
+ .dentry = old_dentry,
+ .inode = old_dentry->d_inode,
+ };
+ struct ext4_renament new = {
+ .dir = new_dir,
+ .dentry = new_dentry,
+ .inode = new_dentry->d_inode,
+ };
int retval;
- int inlined = 0, new_inlined = 0;
- struct ext4_dir_entry_2 *parent_de;

if (flags & ~RENAME_NOREPLACE)
return -EOPNOTSUPP;

- dquot_initialize(old_dir);
- dquot_initialize(new_dir);
-
- old_bh = new_bh = dir_bh = NULL;
+ dquot_initialize(old.dir);
+ dquot_initialize(new.dir);

/* Initialize quotas before so that eventual writes go
* in separate transaction */
- if (new_dentry->d_inode)
- dquot_initialize(new_dentry->d_inode);
+ if (new.inode)
+ dquot_initialize(new.inode);

- old_bh = ext4_find_entry(old_dir, &old_dentry->d_name, &old_de, NULL);
+ old.bh = ext4_find_entry(old.dir, &old.dentry->d_name, &old.de, NULL);
/*
* Check for inode number is _not_ due to possible IO errors.
* We might rmdir the source, keep it as pwd of some process
* and merrily kill the link to whatever was created under the
* same name. Goodbye sticky bit ;-<
*/
- old_inode = old_dentry->d_inode;
retval = -ENOENT;
- if (!old_bh || le32_to_cpu(old_de->inode) != old_inode->i_ino)
+ if (!old.bh || le32_to_cpu(old.de->inode) != old.inode->i_ino)
goto end_rename;

- new_inode = new_dentry->d_inode;
- new_bh = ext4_find_entry(new_dir, &new_dentry->d_name,
- &new_de, &new_inlined);
- if (new_bh) {
- if (!new_inode) {
- brelse(new_bh);
- new_bh = NULL;
+ new.bh = ext4_find_entry(new.dir, &new.dentry->d_name,
+ &new.de, &new.inlined);
+ if (new.bh) {
+ if (!new.inode) {
+ brelse(new.bh);
+ new.bh = NULL;
}
}
- if (new_inode && !test_opt(new_dir->i_sb, NO_AUTO_DA_ALLOC))
- ext4_alloc_da_blocks(old_inode);
+ if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC))
+ ext4_alloc_da_blocks(old.inode);

- handle = ext4_journal_start(old_dir, EXT4_HT_DIR,
- (2 * EXT4_DATA_TRANS_BLOCKS(old_dir->i_sb) +
+ handle = ext4_journal_start(old.dir, EXT4_HT_DIR,
+ (2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) +
EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2));
if (IS_ERR(handle))
return PTR_ERR(handle);

- if (IS_DIRSYNC(old_dir) || IS_DIRSYNC(new_dir))
+ if (IS_DIRSYNC(old.dir) || IS_DIRSYNC(new.dir))
ext4_handle_sync(handle);

- if (S_ISDIR(old_inode->i_mode)) {
- if (new_inode) {
+ if (S_ISDIR(old.inode->i_mode)) {
+ if (new.inode) {
retval = -ENOTEMPTY;
- if (!empty_dir(new_inode))
+ if (!empty_dir(new.inode))
goto end_rename;
}
retval = -EIO;
- dir_bh = ext4_get_first_dir_block(handle, old_inode,
- &retval, &parent_de,
- &inlined);
- if (!dir_bh)
+ old.dir_bh = ext4_get_first_dir_block(handle, old.inode,
+ &retval, &old.parent_de,
+ &old.dir_inlined);
+ if (!old.dir_bh)
goto end_rename;
- if (le32_to_cpu(parent_de->inode) != old_dir->i_ino)
+ if (le32_to_cpu(old.parent_de->inode) != old.dir->i_ino)
goto end_rename;
retval = -EMLINK;
- if (!new_inode && new_dir != old_dir &&
- EXT4_DIR_LINK_MAX(new_dir))
+ if (!new.inode && new.dir != old.dir &&
+ EXT4_DIR_LINK_MAX(new.dir))
goto end_rename;
- BUFFER_TRACE(dir_bh, "get_write_access");
- retval = ext4_journal_get_write_access(handle, dir_bh);
+ BUFFER_TRACE(old.dir_bh, "get_write_access");
+ retval = ext4_journal_get_write_access(handle, old.dir_bh);
if (retval)
goto end_rename;
}
- if (!new_bh) {
- retval = ext4_add_entry(handle, new_dentry, old_inode);
+ if (!new.bh) {
+ retval = ext4_add_entry(handle, new.dentry, old.inode);
if (retval)
goto end_rename;
} else {
- BUFFER_TRACE(new_bh, "get write access");
- retval = ext4_journal_get_write_access(handle, new_bh);
+ BUFFER_TRACE(new.bh, "get write access");
+ retval = ext4_journal_get_write_access(handle, new.bh);
if (retval)
goto end_rename;
- new_de->inode = cpu_to_le32(old_inode->i_ino);
- if (EXT4_HAS_INCOMPAT_FEATURE(new_dir->i_sb,
+ new.de->inode = cpu_to_le32(old.inode->i_ino);
+ if (EXT4_HAS_INCOMPAT_FEATURE(new.dir->i_sb,
EXT4_FEATURE_INCOMPAT_FILETYPE))
- new_de->file_type = old_de->file_type;
- new_dir->i_version++;
- new_dir->i_ctime = new_dir->i_mtime =
- ext4_current_time(new_dir);
- ext4_mark_inode_dirty(handle, new_dir);
- BUFFER_TRACE(new_bh, "call ext4_handle_dirty_metadata");
- if (!new_inlined) {
+ new.de->file_type = old.de->file_type;
+ new.dir->i_version++;
+ new.dir->i_ctime = new.dir->i_mtime =
+ ext4_current_time(new.dir);
+ ext4_mark_inode_dirty(handle, new.dir);
+ BUFFER_TRACE(new.bh, "call ext4_handle_dirty_metadata");
+ if (!new.inlined) {
retval = ext4_handle_dirty_dirent_node(handle,
- new_dir, new_bh);
+ new.dir, new.bh);
if (unlikely(retval)) {
- ext4_std_error(new_dir->i_sb, retval);
+ ext4_std_error(new.dir->i_sb, retval);
goto end_rename;
}
}
- brelse(new_bh);
- new_bh = NULL;
+ brelse(new.bh);
+ new.bh = NULL;
}

/*
* Like most other Unix systems, set the ctime for inodes on a
* rename.
*/
- old_inode->i_ctime = ext4_current_time(old_inode);
- ext4_mark_inode_dirty(handle, old_inode);
+ old.inode->i_ctime = ext4_current_time(old.inode);
+ ext4_mark_inode_dirty(handle, old.inode);

/*
* ok, that's it
*/
- if (le32_to_cpu(old_de->inode) != old_inode->i_ino ||
- old_de->name_len != old_dentry->d_name.len ||
- strncmp(old_de->name, old_dentry->d_name.name, old_de->name_len) ||
- (retval = ext4_delete_entry(handle, old_dir,
- old_de, old_bh)) == -ENOENT) {
- /* old_de could have moved from under us during htree split, so
+ if (le32_to_cpu(old.de->inode) != old.inode->i_ino ||
+ old.de->name_len != old.dentry->d_name.len ||
+ strncmp(old.de->name, old.dentry->d_name.name, old.de->name_len) ||
+ (retval = ext4_delete_entry(handle, old.dir,
+ old.de, old.bh)) == -ENOENT) {
+ /* old.de could have moved from under us during htree split, so
* make sure that we are deleting the right entry. We might
* also be pointing to a stale entry in the unused part of
- * old_bh so just checking inum and the name isn't enough. */
+ * old.bh so just checking inum and the name isn't enough. */
struct buffer_head *old_bh2;
struct ext4_dir_entry_2 *old_de2;

- old_bh2 = ext4_find_entry(old_dir, &old_dentry->d_name,
+ old_bh2 = ext4_find_entry(old.dir, &old.dentry->d_name,
&old_de2, NULL);
if (old_bh2) {
- retval = ext4_delete_entry(handle, old_dir,
+ retval = ext4_delete_entry(handle, old.dir,
old_de2, old_bh2);
brelse(old_bh2);
}
}
if (retval) {
- ext4_warning(old_dir->i_sb,
+ ext4_warning(old.dir->i_sb,
"Deleting old file (%lu), %d, error=%d",
- old_dir->i_ino, old_dir->i_nlink, retval);
+ old.dir->i_ino, old.dir->i_nlink, retval);
}

- if (new_inode) {
- ext4_dec_count(handle, new_inode);
- new_inode->i_ctime = ext4_current_time(new_inode);
+ if (new.inode) {
+ ext4_dec_count(handle, new.inode);
+ new.inode->i_ctime = ext4_current_time(new.inode);
}
- old_dir->i_ctime = old_dir->i_mtime = ext4_current_time(old_dir);
- ext4_update_dx_flag(old_dir);
- if (dir_bh) {
- parent_de->inode = cpu_to_le32(new_dir->i_ino);
- BUFFER_TRACE(dir_bh, "call ext4_handle_dirty_metadata");
- if (!inlined) {
- if (is_dx(old_inode)) {
+ old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir);
+ ext4_update_dx_flag(old.dir);
+ if (old.dir_bh) {
+ old.parent_de->inode = cpu_to_le32(new.dir->i_ino);
+ BUFFER_TRACE(old.dir_bh, "call ext4_handle_dirty_metadata");
+ if (!old.dir_inlined) {
+ if (is_dx(old.inode)) {
retval = ext4_handle_dirty_dx_node(handle,
- old_inode,
- dir_bh);
+ old.inode,
+ old.dir_bh);
} else {
retval = ext4_handle_dirty_dirent_node(handle,
- old_inode, dir_bh);
+ old.inode, old.dir_bh);
}
} else {
- retval = ext4_mark_inode_dirty(handle, old_inode);
+ retval = ext4_mark_inode_dirty(handle, old.inode);
}
if (retval) {
- ext4_std_error(old_dir->i_sb, retval);
+ ext4_std_error(old.dir->i_sb, retval);
goto end_rename;
}
- ext4_dec_count(handle, old_dir);
- if (new_inode) {
+ ext4_dec_count(handle, old.dir);
+ if (new.inode) {
/* checked empty_dir above, can't have another parent,
* ext4_dec_count() won't work for many-linked dirs */
- clear_nlink(new_inode);
+ clear_nlink(new.inode);
} else {
- ext4_inc_count(handle, new_dir);
- ext4_update_dx_flag(new_dir);
- ext4_mark_inode_dirty(handle, new_dir);
+ ext4_inc_count(handle, new.dir);
+ ext4_update_dx_flag(new.dir);
+ ext4_mark_inode_dirty(handle, new.dir);
}
}
- ext4_mark_inode_dirty(handle, old_dir);
- if (new_inode) {
- ext4_mark_inode_dirty(handle, new_inode);
- if (!new_inode->i_nlink)
- ext4_orphan_add(handle, new_inode);
+ ext4_mark_inode_dirty(handle, old.dir);
+ if (new.inode) {
+ ext4_mark_inode_dirty(handle, new.inode);
+ if (!new.inode->i_nlink)
+ ext4_orphan_add(handle, new.inode);
}
retval = 0;

end_rename:
- brelse(dir_bh);
- brelse(old_bh);
- brelse(new_bh);
+ brelse(old.dir_bh);
+ brelse(old.bh);
+ brelse(new.bh);
if (handle)
ext4_journal_stop(handle);
return retval;
--
1.8.1.4

2014-01-08 22:13:42

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 03/11] vfs: rename: use common code for dir and non-dir

From: Miklos Szeredi <[email protected]>

There's actually very little difference between vfs_rename_dir() and
vfs_rename_other() so move both inline into vfs_rename() which still stays
reasonably readable.

Signed-off-by: Miklos Szeredi <[email protected]>
---
fs/namei.c | 187 +++++++++++++++++++++++++------------------------------------
1 file changed, 75 insertions(+), 112 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index d848b31a646e..f32a899fc039 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3962,7 +3962,27 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
return sys_linkat(AT_FDCWD, oldname, AT_FDCWD, newname, 0);
}

-/*
+/**
+ * vfs_rename - rename a filesystem object
+ * @old_dir: parent of source
+ * @old_dentry: source
+ * @new_dir: parent of destination
+ * @new_dentry: destination
+ * @delegated_inode: returns an inode needing a delegation break
+ *
+ * The caller must hold multiple mutexes--see lock_rename()).
+ *
+ * If vfs_rename discovers a delegation in need of breaking at either
+ * the source or destination, it will return -EWOULDBLOCK and return a
+ * reference to the inode in delegated_inode. The caller should then
+ * break the delegation and retry. Because breaking a delegation may
+ * take a long time, the caller should drop all locks before doing
+ * so.
+ *
+ * Alternatively, a caller may pass NULL for delegated_inode. This may
+ * be appropriate for callers that expect the underlying filesystem not
+ * to be NFS exported.
+ *
* The worst of all namespace operations - renaming directory. "Perverted"
* doesn't even start to describe it. Somebody in UCB had a heck of a trip...
* Problems:
@@ -3990,19 +4010,39 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
* ->i_mutex on parents, which works but leads to some truly excessive
* locking].
*/
-static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry)
+int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry,
+ struct inode **delegated_inode)
{
- int error = 0;
+ int error;
+ bool is_dir = d_is_dir(old_dentry);
+ const unsigned char *old_name;
+ struct inode *source = old_dentry->d_inode;
struct inode *target = new_dentry->d_inode;
- unsigned max_links = new_dir->i_sb->s_max_links;
+
+ if (source == target)
+ return 0;
+
+ error = may_delete(old_dir, old_dentry, is_dir);
+ if (error)
+ return error;
+
+ if (!target)
+ error = may_create(new_dir, new_dentry);
+ else
+ error = may_delete(new_dir, new_dentry, is_dir);
+ if (error)
+ return error;
+
+ if (!old_dir->i_op->rename)
+ return -EPERM;

/*
* If we are going to change the parent - check write permissions,
* we'll need to flip '..'.
*/
- if (new_dir != old_dir) {
- error = inode_permission(old_dentry->d_inode, MAY_WRITE);
+ if (is_dir && new_dir != old_dir) {
+ error = inode_permission(source, MAY_WRITE);
if (error)
return error;
}
@@ -4011,134 +4051,57 @@ static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry,
if (error)
return error;

+ old_name = fsnotify_oldname_init(old_dentry->d_name.name);
dget(new_dentry);
- if (target)
+ if (!is_dir)
+ lock_two_nondirectories(source, target);
+ else if (target)
mutex_lock(&target->i_mutex);

error = -EBUSY;
if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry))
goto out;

- error = -EMLINK;
- if (max_links && !target && new_dir != old_dir &&
- new_dir->i_nlink >= max_links)
- goto out;
-
- if (target)
- shrink_dcache_parent(new_dentry);
- error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
- if (error)
- goto out;
-
- if (target) {
- target->i_flags |= S_DEAD;
- dont_mount(new_dentry);
- }
- if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE))
- d_move(old_dentry, new_dentry);
-out:
- if (target)
- mutex_unlock(&target->i_mutex);
- dput(new_dentry);
- return error;
-}
-
-static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry,
- struct inode **delegated_inode)
-{
- struct inode *target = new_dentry->d_inode;
- struct inode *source = old_dentry->d_inode;
- int error;
-
- error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry);
- if (error)
- return error;
-
- dget(new_dentry);
- lock_two_nondirectories(source, target);
+ if (is_dir) {
+ unsigned max_links = new_dir->i_sb->s_max_links;

- error = -EBUSY;
- if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
- goto out;
+ error = -EMLINK;
+ if (max_links && !target && new_dir != old_dir &&
+ new_dir->i_nlink >= max_links)
+ goto out;

- error = try_break_deleg(source, delegated_inode);
- if (error)
- goto out;
- if (target) {
- error = try_break_deleg(target, delegated_inode);
+ if (target)
+ shrink_dcache_parent(new_dentry);
+ } else {
+ error = try_break_deleg(source, delegated_inode);
if (error)
goto out;
+ if (target) {
+ error = try_break_deleg(target, delegated_inode);
+ if (error)
+ goto out;
+ }
}
error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
if (error)
goto out;

- if (target)
+ if (target) {
+ if (is_dir)
+ target->i_flags |= S_DEAD;
dont_mount(new_dentry);
+ }
if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE))
d_move(old_dentry, new_dentry);
out:
- unlock_two_nondirectories(source, target);
+ if (!is_dir)
+ unlock_two_nondirectories(source, target);
+ else if (target)
+ mutex_unlock(&target->i_mutex);
dput(new_dentry);
- return error;
-}
-
-/**
- * vfs_rename - rename a filesystem object
- * @old_dir: parent of source
- * @old_dentry: source
- * @new_dir: parent of destination
- * @new_dentry: destination
- * @delegated_inode: returns an inode needing a delegation break
- *
- * The caller must hold multiple mutexes--see lock_rename()).
- *
- * If vfs_rename discovers a delegation in need of breaking at either
- * the source or destination, it will return -EWOULDBLOCK and return a
- * reference to the inode in delegated_inode. The caller should then
- * break the delegation and retry. Because breaking a delegation may
- * take a long time, the caller should drop all locks before doing
- * so.
- *
- * Alternatively, a caller may pass NULL for delegated_inode. This may
- * be appropriate for callers that expect the underlying filesystem not
- * to be NFS exported.
- */
-int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry,
- struct inode **delegated_inode)
-{
- int error;
- int is_dir = d_is_dir(old_dentry);
- const unsigned char *old_name;
-
- if (old_dentry->d_inode == new_dentry->d_inode)
- return 0;
-
- error = may_delete(old_dir, old_dentry, is_dir);
- if (error)
- return error;
-
- if (!new_dentry->d_inode)
- error = may_create(new_dir, new_dentry);
- else
- error = may_delete(new_dir, new_dentry, is_dir);
- if (error)
- return error;
-
- if (!old_dir->i_op->rename)
- return -EPERM;
-
- old_name = fsnotify_oldname_init(old_dentry->d_name.name);
-
- if (is_dir)
- error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry);
- else
- error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry,delegated_inode);
if (!error)
fsnotify_move(old_dir, new_dir, old_name, is_dir,
- new_dentry->d_inode, old_dentry);
+ target, old_dentry);
fsnotify_oldname_free(old_name);

return error;
--
1.8.1.4

2014-01-08 22:14:08

by Miklos Szeredi

[permalink] [raw]
Subject: [PATCH 01/11] vfs: add d_is_dir()

From: Miklos Szeredi <[email protected]>

Add d_is_dir(dentry) helper which is analogous to S_ISDIR().

To avoid confusion, rename d_is_directory() to d_can_lookup().

Signed-off-by: Miklos Szeredi <[email protected]>
---
fs/namei.c | 23 +++++++++++------------
include/linux/dcache.h | 7 ++++++-
2 files changed, 17 insertions(+), 13 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 3531deebad30..601459c0b9b4 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1788,7 +1788,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
if (err)
return err;
}
- if (!d_is_directory(nd->path.dentry)) {
+ if (!d_can_lookup(nd->path.dentry)) {
err = -ENOTDIR;
break;
}
@@ -1809,7 +1809,7 @@ static int path_init(int dfd, const char *name, unsigned int flags,
struct dentry *root = nd->root.dentry;
struct inode *inode = root->d_inode;
if (*name) {
- if (!d_is_directory(root))
+ if (!d_can_lookup(root))
return -ENOTDIR;
retval = inode_permission(inode, MAY_EXEC);
if (retval)
@@ -1865,7 +1865,7 @@ static int path_init(int dfd, const char *name, unsigned int flags,
dentry = f.file->f_path.dentry;

if (*name) {
- if (!d_is_directory(dentry)) {
+ if (!d_can_lookup(dentry)) {
fdput(f);
return -ENOTDIR;
}
@@ -1947,7 +1947,7 @@ static int path_lookupat(int dfd, const char *name,
err = complete_walk(nd);

if (!err && nd->flags & LOOKUP_DIRECTORY) {
- if (!d_is_directory(nd->path.dentry)) {
+ if (!d_can_lookup(nd->path.dentry)) {
path_put(&nd->path);
err = -ENOTDIR;
}
@@ -2406,11 +2406,11 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
IS_IMMUTABLE(inode) || IS_SWAPFILE(inode))
return -EPERM;
if (isdir) {
- if (!d_is_directory(victim) && !d_is_autodir(victim))
+ if (!d_is_dir(victim))
return -ENOTDIR;
if (IS_ROOT(victim))
return -EBUSY;
- } else if (d_is_directory(victim) || d_is_autodir(victim))
+ } else if (d_is_dir(victim))
return -EISDIR;
if (IS_DEADDIR(dir))
return -ENOENT;
@@ -3008,11 +3008,10 @@ finish_open:
}
audit_inode(name, nd->path.dentry, 0);
error = -EISDIR;
- if ((open_flag & O_CREAT) &&
- (d_is_directory(nd->path.dentry) || d_is_autodir(nd->path.dentry)))
+ if ((open_flag & O_CREAT) && d_is_dir(nd->path.dentry))
goto out;
error = -ENOTDIR;
- if ((nd->flags & LOOKUP_DIRECTORY) && !d_is_directory(nd->path.dentry))
+ if ((nd->flags & LOOKUP_DIRECTORY) && !d_can_lookup(nd->path.dentry))
goto out;
if (!S_ISREG(nd->inode->i_mode))
will_truncate = false;
@@ -3736,7 +3735,7 @@ exit1:
slashes:
if (d_is_negative(dentry))
error = -ENOENT;
- else if (d_is_directory(dentry) || d_is_autodir(dentry))
+ else if (d_is_dir(dentry))
error = -EISDIR;
else
error = -ENOTDIR;
@@ -4112,7 +4111,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode **delegated_inode)
{
int error;
- int is_dir = d_is_directory(old_dentry) || d_is_autodir(old_dentry);
+ int is_dir = d_is_dir(old_dentry);
const unsigned char *old_name;

if (old_dentry->d_inode == new_dentry->d_inode)
@@ -4205,7 +4204,7 @@ retry_deleg:
if (d_is_negative(old_dentry))
goto exit4;
/* unless the source is a directory trailing slashes give -ENOTDIR */
- if (!d_is_directory(old_dentry) && !d_is_autodir(old_dentry)) {
+ if (!d_is_dir(old_dentry)) {
error = -ENOTDIR;
if (oldnd.last.name[oldnd.last.len])
goto exit4;
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index bf72e9ac6de0..3b50cac7ccb3 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -429,7 +429,7 @@ static inline unsigned __d_entry_type(const struct dentry *dentry)
return dentry->d_flags & DCACHE_ENTRY_TYPE;
}

-static inline bool d_is_directory(const struct dentry *dentry)
+static inline bool d_can_lookup(const struct dentry *dentry)
{
return __d_entry_type(dentry) == DCACHE_DIRECTORY_TYPE;
}
@@ -439,6 +439,11 @@ static inline bool d_is_autodir(const struct dentry *dentry)
return __d_entry_type(dentry) == DCACHE_AUTODIR_TYPE;
}

+static inline bool d_is_dir(const struct dentry *dentry)
+{
+ return d_can_lookup(dentry) || d_is_autodir(dentry);
+}
+
static inline bool d_is_symlink(const struct dentry *dentry)
{
return __d_entry_type(dentry) == DCACHE_SYMLINK_TYPE;
--
1.8.1.4

2014-01-13 07:52:33

by Jan Kara

[permalink] [raw]
Subject: Re: [PATCH 07/11] vfs: add cross-rename

On Wed 08-01-14 23:10:11, Miklos Szeredi wrote:
> From: Miklos Szeredi <[email protected]>
>
> If flags contain RENAME_EXCHANGE then exchange source and destination files.
> There's no restriction on the type of the files; e.g. a directory can be
> exchanged with a symlink.
>
> Signed-off-by: Miklos Szeredi <[email protected]>
> ---
> fs/dcache.c | 46 +++++++++++++++++----
> fs/namei.c | 107 +++++++++++++++++++++++++++++++++---------------
> include/linux/dcache.h | 1 +
> include/uapi/linux/fs.h | 1 +
> security/security.c | 16 ++++++++
> 5 files changed, 131 insertions(+), 40 deletions(-)
>
> diff --git a/fs/namei.c b/fs/namei.c
> index 3fbc95c72e31..97c6dbb47eca 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -4020,6 +4020,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
> const unsigned char *old_name;
> struct inode *source = old_dentry->d_inode;
> struct inode *target = new_dentry->d_inode;
> + bool new_is_dir = false;
> + unsigned max_links = new_dir->i_sb->s_max_links;
>
> if (source == target)
> return 0;
> @@ -4028,10 +4030,16 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
> if (error)
> return error;
>
> - if (!target)
> + if (!target) {
> error = may_create(new_dir, new_dentry);
> - else
> - error = may_delete(new_dir, new_dentry, is_dir);
> + } else {
> + new_is_dir = d_is_dir(new_dentry);
> +
> + if (!(flags & RENAME_EXCHANGE))
> + error = may_delete(new_dir, new_dentry, is_dir);
> + else
> + error = may_delete(new_dir, new_dentry, new_is_dir);
> + }
> if (error)
> return error;
>
> @@ -4042,10 +4050,17 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
> * If we are going to change the parent - check write permissions,
> * we'll need to flip '..'.
> */
> - if (is_dir && new_dir != old_dir) {
> - error = inode_permission(source, MAY_WRITE);
> - if (error)
> - return error;
> + if (new_dir != old_dir) {
> + if (is_dir) {
> + error = inode_permission(source, MAY_WRITE);
> + if (error)
> + return error;
> + }
> + if ((flags & RENAME_EXCHANGE) && new_is_dir) {
> + error = inode_permission(target, MAY_WRITE);
> + if (error)
> + return error;
> + }
> }
>
> error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry,
> @@ -4055,25 +4070,24 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
>
> old_name = fsnotify_oldname_init(old_dentry->d_name.name);
> dget(new_dentry);
> - if (!is_dir)
> - lock_two_nondirectories(source, target);
> - else if (target)
> - mutex_lock(&target->i_mutex);
> + if (!(flags & RENAME_EXCHANGE)) {
> + if (!is_dir)
> + lock_two_nondirectories(source, target);
> + else if (target)
> + mutex_lock(&target->i_mutex);
> + }
>
> error = -EBUSY;
> if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry))
> goto out;
>
> - if (is_dir) {
> - unsigned max_links = new_dir->i_sb->s_max_links;
> -
> + if (max_links && new_dir != old_dir) {
> error = -EMLINK;
> - if (max_links && !target && new_dir != old_dir &&
> - new_dir->i_nlink >= max_links)
> + if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links)
> + goto out;
> + if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir &&
> + old_dir->i_nlink > max_links)
This should be >=, shouldn't it?

> goto out;
> -
> - if (target)
> - shrink_dcache_parent(new_dentry);
> } else {
> error = try_break_deleg(source, delegated_inode);
> if (error)
> @@ -4084,27 +4098,40 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
> goto out;
> }
> }
> + if (is_dir && !(flags & RENAME_EXCHANGE) && target)
> + shrink_dcache_parent(new_dentry);
> error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry,
> flags);
> if (error)
> goto out;
>
> - if (target) {
> + if (!(flags & RENAME_EXCHANGE) && target) {
> if (is_dir)
> target->i_flags |= S_DEAD;
> dont_mount(new_dentry);
> }
> - if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE))
> - d_move(old_dentry, new_dentry);
> + if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) {
> + if (!(flags & RENAME_EXCHANGE))
> + d_move(old_dentry, new_dentry);
> + else
> + d_exchange(old_dentry, new_dentry);
> + }
> out:
> - if (!is_dir)
> - unlock_two_nondirectories(source, target);
> - else if (target)
> - mutex_unlock(&target->i_mutex);
> + if (!(flags & RENAME_EXCHANGE)) {
> + if (!is_dir)
> + unlock_two_nondirectories(source, target);
> + else if (target)
> + mutex_unlock(&target->i_mutex);
> + }
> dput(new_dentry);
> - if (!error)
> + if (!error) {
> fsnotify_move(old_dir, new_dir, old_name, is_dir,
> - target, old_dentry);
> + !(flags & RENAME_EXCHANGE) ? target : NULL, old_dentry);
> + if (flags & RENAME_EXCHANGE) {
> + fsnotify_move(new_dir, old_dir, old_dentry->d_name.name,
> + new_is_dir, NULL, new_dentry);
> + }
> + }
> fsnotify_oldname_free(old_name);
>
> return error;
> @@ -4124,9 +4151,12 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
> bool should_retry = false;
> int error;
>
> - if (flags & ~RENAME_NOREPLACE)
> + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
> return -EOPNOTSUPP;
>
> + if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE))
> + return -EINVAL;
> +
> retry:
> from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags);
> if (IS_ERR(from)) {
> @@ -4161,7 +4191,8 @@ retry:
>
> oldnd.flags &= ~LOOKUP_PARENT;
> newnd.flags &= ~LOOKUP_PARENT;
> - newnd.flags |= LOOKUP_RENAME_TARGET;
> + if (!(flags & RENAME_EXCHANGE))
> + newnd.flags |= LOOKUP_RENAME_TARGET;
>
> retry_deleg:
> trap = lock_rename(new_dir, old_dir);
> @@ -4181,12 +4212,23 @@ retry_deleg:
> error = -EEXIST;
> if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
> goto exit5;
> + if (flags & RENAME_EXCHANGE) {
> + error = -ENOENT;
> + if (!new_dentry->d_inode)
> + goto exit5;
Should this be d_is_positive()?

Honza
--
Jan Kara <[email protected]>
SUSE Labs, CR

2014-01-13 12:25:24

by Jan Kara

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Wed 08-01-14 23:10:15, Miklos Szeredi wrote:
> From: Miklos Szeredi <[email protected]>
>
> Implement RENAME_EXCHANGE flag in renameat2 syscall.
Yes, this is much better than last time. Thanks for the work. You can add
Reviewed-by: Jan Kara <[email protected]>

One nitpick below...

> Signed-off-by: Miklos Szeredi <[email protected]>
> ---
> fs/ext4/namei.c | 121 ++++++++++++++++++++++++++++++++++++++++----------------
> 1 file changed, 87 insertions(+), 34 deletions(-)
>
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index 7147d08a43a2..e4513ba7ed99 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
> @@ -3005,6 +3005,8 @@ struct ext4_renament {
> struct inode *dir;
> struct dentry *dentry;
> struct inode *inode;
> + bool is_dir;
> + int dir_nlink_delta;
>
> /* entry for "dentry" */
> struct buffer_head *bh;
> @@ -3136,6 +3138,14 @@ static void ext4_rename_delete(handle_t *handle, struct ext4_renament *ent)
> }
> }
>
> +static void ext4_update_dir_count(handle_t *handle, struct ext4_renament *ent)
> +{
> + if (ent->dir_nlink_delta == -1)
> + ext4_dec_count(handle, ent->dir);
> + else if (ent->dir_nlink_delta == 1)
> + ext4_inc_count(handle, ent->dir);
> +}
> +
> /*
> * Anybody can rename anything with this: the permission checks are left to the
> * higher-level routines.
> @@ -3161,7 +3171,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
> };
> int retval;
>
> - if (flags & ~RENAME_NOREPLACE)
> + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
> return -EOPNOTSUPP;
>
> dquot_initialize(old.dir);
> @@ -3169,10 +3179,11 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
>
> /* Initialize quotas before so that eventual writes go
> * in separate transaction */
> - if (new.inode)
> + if (!(flags & RENAME_EXCHANGE) && new.inode)
> dquot_initialize(new.inode);
>
> - old.bh = ext4_find_entry(old.dir, &old.dentry->d_name, &old.de, NULL);
> + old.bh = ext4_find_entry(old.dir, &old.dentry->d_name,
> + &old.de, &old.inlined);
> /*
> * Check for inode number is _not_ due to possible IO errors.
> * We might rmdir the source, keep it as pwd of some process
> @@ -3185,18 +3196,22 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
>
> new.bh = ext4_find_entry(new.dir, &new.dentry->d_name,
> &new.de, &new.inlined);
> - if (new.bh) {
> - if (!new.inode) {
> - brelse(new.bh);
> - new.bh = NULL;
> + if (!(flags & RENAME_EXCHANGE)) {
> + if (new.bh) {
> + if (!new.inode) {
> + brelse(new.bh);
> + new.bh = NULL;
> + }
> }
> + if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC))
> + ext4_alloc_da_blocks(old.inode);
> + } else if (!new.bh || le32_to_cpu(new.de->inode) != new.inode->i_ino) {
> + goto end_rename;
> }
I think this deserves a comment that RENAME_EXCHANGE expects both source
and target to exist... I'm always pondering about this condition before I
realize that.

Honza
--
Jan Kara <[email protected]>
SUSE Labs, CR

2014-01-13 12:47:31

by Tetsuo Handa

[permalink] [raw]
Subject: Re: [PATCH 00/11] cross rename v3

Miklos Szeredi wrote:
> Please consider for -next (3.14).

Excuse me, but did you explain to CONFIG_SECURITY_PATH=y users?
I don't see changes in TOMOYO and AppArmor directories.
TOMOYO might want to use new keyword like "file swapname"
rather than using "file rename" for cross rename operation.

2014-01-13 17:08:07

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 00/11] cross rename v3

On Mon, 2014-01-13 at 21:46 +0900, Tetsuo Handa wrote:
> Miklos Szeredi wrote:
> > Please consider for -next (3.14).
>
> Excuse me, but did you explain to CONFIG_SECURITY_PATH=y users?

No, sorry.

> I don't see changes in TOMOYO and AppArmor directories.
> TOMOYO might want to use new keyword like "file swapname"
> rather than using "file rename" for cross rename operation.

Cross rename (A, B) is equivalent to plain rename(A, B) + plain rename
(B, A) done as a single atomic operation. If security module allows
both then cross rename is allowed. If at least one is denied then the
cross rename is denied.

This is prepared for in "[PATCH 06/11] security: add flags to rename
hooks" and actually done in "[PATCH 07/11] vfs: add cross-rename".

Security people are free to implement a explicit security check for
cross rename, but I don't think that is in the scope of this patchset.

Thanks,
Miklos

2014-01-13 22:05:14

by Tetsuo Handa

[permalink] [raw]
Subject: Re: [PATCH 00/11] cross rename v3

Miklos Szeredi wrote:
> Cross rename (A, B) is equivalent to plain rename(A, B) + plain rename
> (B, A) done as a single atomic operation. If security module allows
> both then cross rename is allowed. If at least one is denied then the
> cross rename is denied.

Yes, the functionality itself is fine. The problem is how LSM users check
their permissions for the functionality.

>
> This is prepared for in "[PATCH 06/11] security: add flags to rename
> hooks" and actually done in "[PATCH 07/11] vfs: add cross-rename".
>
> Security people are free to implement a explicit security check for
> cross rename, but I don't think that is in the scope of this patchset.
>
I don't know how their permissions are checked, but I think that
swapping /A/B and /C/D should check not only

Remove a name from directory A
Add a name to directory C

but also

Add a name to directory A
Remove a name from directory C

using their security labels.

Without making changes to security/*/ directory, SELinux/SMACK/TOMOYO/AppArmor
might fail to check the latter permissions. Please get confirmation from LSM
people before you merge this change to linux-next tree.

2014-01-14 09:58:56

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 00/11] cross rename v3

On Mon, Jan 13, 2014 at 11:03 PM, Tetsuo Handa
<[email protected]> wrote:
> Miklos Szeredi wrote:
>> Cross rename (A, B) is equivalent to plain rename(A, B) + plain rename
>> (B, A) done as a single atomic operation. If security module allows
>> both then cross rename is allowed. If at least one is denied then the
>> cross rename is denied.
>
> Yes, the functionality itself is fine. The problem is how LSM users check
> their permissions for the functionality.
>
>>
>> This is prepared for in "[PATCH 06/11] security: add flags to rename
>> hooks" and actually done in "[PATCH 07/11] vfs: add cross-rename".
>>
>> Security people are free to implement a explicit security check for
>> cross rename, but I don't think that is in the scope of this patchset.
>>
> I don't know how their permissions are checked, but I think that
> swapping /A/B and /C/D should check not only
>
> Remove a name from directory A
> Add a name to directory C
>
> but also
>
> Add a name to directory A
> Remove a name from directory C
>
> using their security labels.
>
> Without making changes to security/*/ directory, SELinux/SMACK/TOMOYO/AppArmor
> might fail to check the latter permissions.

Those permissions will be checked. Please see security/security.c in
patch 07/11 of the series.

Of course, review is appreciated.

Thanks,
Miklos

2014-01-14 10:31:19

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 07/11] vfs: add cross-rename

On Mon, Jan 13, 2014 at 08:52:27AM +0100, Jan Kara wrote:
> On Wed 08-01-14 23:10:11, Miklos Szeredi wrote:

> > + if (max_links && new_dir != old_dir) {
> > error = -EMLINK;
> > - if (max_links && !target && new_dir != old_dir &&
> > - new_dir->i_nlink >= max_links)
> > + if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links)
> > + goto out;
> > + if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir &&
> > + old_dir->i_nlink > max_links)
> This should be >=, shouldn't it?

Yes, good catch, thanks.


> > @@ -4181,12 +4212,23 @@ retry_deleg:
> > error = -EEXIST;
> > if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
> > goto exit5;
> > + if (flags & RENAME_EXCHANGE) {
> > + error = -ENOENT;
> > + if (!new_dentry->d_inode)
> > + goto exit5;
> Should this be d_is_positive()?

Yes, well, actually d_is_negative().

Thanks a lot for the review. Updated patch below.

Miklos
----

Subject: vfs: add cross-rename
From: Miklos Szeredi <[email protected]>

If flags contain RENAME_EXCHANGE then exchange source and destination files.
There's no restriction on the type of the files; e.g. a directory can be
exchanged with a symlink.

Signed-off-by: Miklos Szeredi <[email protected]>
---
fs/dcache.c | 46 +++++++++++++++++---
fs/namei.c | 107 +++++++++++++++++++++++++++++++++---------------
include/linux/dcache.h | 1
include/uapi/linux/fs.h | 1
security/security.c | 16 +++++++
5 files changed, 131 insertions(+), 40 deletions(-)

--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -36,6 +36,7 @@
#define SEEK_MAX SEEK_HOLE

#define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */
+#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */

struct fstrim_range {
__u64 start;
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4020,6 +4020,8 @@ int vfs_rename(struct inode *old_dir, st
const unsigned char *old_name;
struct inode *source = old_dentry->d_inode;
struct inode *target = new_dentry->d_inode;
+ bool new_is_dir = false;
+ unsigned max_links = new_dir->i_sb->s_max_links;

if (source == target)
return 0;
@@ -4028,10 +4030,16 @@ int vfs_rename(struct inode *old_dir, st
if (error)
return error;

- if (!target)
+ if (!target) {
error = may_create(new_dir, new_dentry);
- else
- error = may_delete(new_dir, new_dentry, is_dir);
+ } else {
+ new_is_dir = d_is_dir(new_dentry);
+
+ if (!(flags & RENAME_EXCHANGE))
+ error = may_delete(new_dir, new_dentry, is_dir);
+ else
+ error = may_delete(new_dir, new_dentry, new_is_dir);
+ }
if (error)
return error;

@@ -4042,10 +4050,17 @@ int vfs_rename(struct inode *old_dir, st
* If we are going to change the parent - check write permissions,
* we'll need to flip '..'.
*/
- if (is_dir && new_dir != old_dir) {
- error = inode_permission(source, MAY_WRITE);
- if (error)
- return error;
+ if (new_dir != old_dir) {
+ if (is_dir) {
+ error = inode_permission(source, MAY_WRITE);
+ if (error)
+ return error;
+ }
+ if ((flags & RENAME_EXCHANGE) && new_is_dir) {
+ error = inode_permission(target, MAY_WRITE);
+ if (error)
+ return error;
+ }
}

error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry,
@@ -4055,25 +4070,24 @@ int vfs_rename(struct inode *old_dir, st

old_name = fsnotify_oldname_init(old_dentry->d_name.name);
dget(new_dentry);
- if (!is_dir)
- lock_two_nondirectories(source, target);
- else if (target)
- mutex_lock(&target->i_mutex);
+ if (!(flags & RENAME_EXCHANGE)) {
+ if (!is_dir)
+ lock_two_nondirectories(source, target);
+ else if (target)
+ mutex_lock(&target->i_mutex);
+ }

error = -EBUSY;
if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry))
goto out;

- if (is_dir) {
- unsigned max_links = new_dir->i_sb->s_max_links;
-
+ if (max_links && new_dir != old_dir) {
error = -EMLINK;
- if (max_links && !target && new_dir != old_dir &&
- new_dir->i_nlink >= max_links)
+ if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links)
+ goto out;
+ if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir &&
+ old_dir->i_nlink >= max_links)
goto out;
-
- if (target)
- shrink_dcache_parent(new_dentry);
} else {
error = try_break_deleg(source, delegated_inode);
if (error)
@@ -4084,27 +4098,40 @@ int vfs_rename(struct inode *old_dir, st
goto out;
}
}
+ if (is_dir && !(flags & RENAME_EXCHANGE) && target)
+ shrink_dcache_parent(new_dentry);
error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry,
flags);
if (error)
goto out;

- if (target) {
+ if (!(flags & RENAME_EXCHANGE) && target) {
if (is_dir)
target->i_flags |= S_DEAD;
dont_mount(new_dentry);
}
- if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE))
- d_move(old_dentry, new_dentry);
+ if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) {
+ if (!(flags & RENAME_EXCHANGE))
+ d_move(old_dentry, new_dentry);
+ else
+ d_exchange(old_dentry, new_dentry);
+ }
out:
- if (!is_dir)
- unlock_two_nondirectories(source, target);
- else if (target)
- mutex_unlock(&target->i_mutex);
+ if (!(flags & RENAME_EXCHANGE)) {
+ if (!is_dir)
+ unlock_two_nondirectories(source, target);
+ else if (target)
+ mutex_unlock(&target->i_mutex);
+ }
dput(new_dentry);
- if (!error)
+ if (!error) {
fsnotify_move(old_dir, new_dir, old_name, is_dir,
- target, old_dentry);
+ !(flags & RENAME_EXCHANGE) ? target : NULL, old_dentry);
+ if (flags & RENAME_EXCHANGE) {
+ fsnotify_move(new_dir, old_dir, old_dentry->d_name.name,
+ new_is_dir, NULL, new_dentry);
+ }
+ }
fsnotify_oldname_free(old_name);

return error;
@@ -4124,9 +4151,12 @@ SYSCALL_DEFINE5(renameat2, int, olddfd,
bool should_retry = false;
int error;

- if (flags & ~RENAME_NOREPLACE)
+ if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
return -EOPNOTSUPP;

+ if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE))
+ return -EINVAL;
+
retry:
from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags);
if (IS_ERR(from)) {
@@ -4161,7 +4191,8 @@ SYSCALL_DEFINE5(renameat2, int, olddfd,

oldnd.flags &= ~LOOKUP_PARENT;
newnd.flags &= ~LOOKUP_PARENT;
- newnd.flags |= LOOKUP_RENAME_TARGET;
+ if (!(flags & RENAME_EXCHANGE))
+ newnd.flags |= LOOKUP_RENAME_TARGET;

retry_deleg:
trap = lock_rename(new_dir, old_dir);
@@ -4181,12 +4212,23 @@ SYSCALL_DEFINE5(renameat2, int, olddfd,
error = -EEXIST;
if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
goto exit5;
+ if (flags & RENAME_EXCHANGE) {
+ error = -ENOENT;
+ if (d_is_negative(new_dentry))
+ goto exit5;
+
+ if (!d_is_dir(new_dentry)) {
+ error = -ENOTDIR;
+ if (newnd.last.name[newnd.last.len])
+ goto exit5;
+ }
+ }
/* unless the source is a directory trailing slashes give -ENOTDIR */
if (!d_is_dir(old_dentry)) {
error = -ENOTDIR;
if (oldnd.last.name[oldnd.last.len])
goto exit5;
- if (newnd.last.name[newnd.last.len])
+ if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len])
goto exit5;
}
/* source should not be ancestor of target */
@@ -4194,7 +4236,8 @@ SYSCALL_DEFINE5(renameat2, int, olddfd,
if (old_dentry == trap)
goto exit5;
/* target should not be an ancestor of source */
- error = -ENOTEMPTY;
+ if (!(flags & RENAME_EXCHANGE))
+ error = -ENOTEMPTY;
if (new_dentry == trap)
goto exit5;

--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -2483,12 +2483,14 @@ static void switch_names(struct dentry *
dentry->d_name.name = dentry->d_iname;
} else {
/*
- * Both are internal. Just copy target to dentry
+ * Both are internal.
*/
- memcpy(dentry->d_iname, target->d_name.name,
- target->d_name.len + 1);
- dentry->d_name.len = target->d_name.len;
- return;
+ unsigned int i;
+ BUILD_BUG_ON(!IS_ALIGNED(DNAME_INLINE_LEN, sizeof(long)));
+ for (i = 0; i < DNAME_INLINE_LEN / sizeof(long); i++) {
+ swap(((long *) &dentry->d_iname)[i],
+ ((long *) &target->d_iname)[i]);
+ }
}
}
swap(dentry->d_name.len, target->d_name.len);
@@ -2545,13 +2547,15 @@ static void dentry_unlock_parents_for_mo
* __d_move - move a dentry
* @dentry: entry to move
* @target: new dentry
+ * @exchange: exchange the two dentries
*
* Update the dcache to reflect the move of a file name. Negative
* dcache entries should not be moved in this way. Caller must hold
* rename_lock, the i_mutex of the source and target directories,
* and the sb->s_vfs_rename_mutex if they differ. See lock_rename().
*/
-static void __d_move(struct dentry * dentry, struct dentry * target)
+static void __d_move(struct dentry *dentry, struct dentry *target,
+ bool exchange)
{
if (!dentry->d_inode)
printk(KERN_WARNING "VFS: moving negative dcache entry\n");
@@ -2575,6 +2579,10 @@ static void __d_move(struct dentry * den

/* Unhash the target: dput() will then get rid of it */
__d_drop(target);
+ if (exchange) {
+ __d_rehash(target,
+ d_hash(dentry->d_parent, dentry->d_name.hash));
+ }

list_del(&dentry->d_u.d_child);
list_del(&target->d_u.d_child);
@@ -2601,6 +2609,8 @@ static void __d_move(struct dentry * den
write_seqcount_end(&dentry->d_seq);

dentry_unlock_parents_for_move(dentry, target);
+ if (exchange)
+ fsnotify_d_move(target);
spin_unlock(&target->d_lock);
fsnotify_d_move(dentry);
spin_unlock(&dentry->d_lock);
@@ -2618,11 +2628,31 @@ static void __d_move(struct dentry * den
void d_move(struct dentry *dentry, struct dentry *target)
{
write_seqlock(&rename_lock);
- __d_move(dentry, target);
+ __d_move(dentry, target, false);
write_sequnlock(&rename_lock);
}
EXPORT_SYMBOL(d_move);

+/*
+ * d_exchange - exchange two dentries
+ * @dentry1: first dentry
+ * @dentry2: second dentry
+ */
+void d_exchange(struct dentry *dentry1, struct dentry *dentry2)
+{
+ write_seqlock(&rename_lock);
+
+ WARN_ON(!dentry1->d_inode);
+ WARN_ON(!dentry2->d_inode);
+ WARN_ON(IS_ROOT(dentry1));
+ WARN_ON(IS_ROOT(dentry2));
+
+ __d_move(dentry1, dentry2, true);
+
+ write_sequnlock(&rename_lock);
+}
+
+
/**
* d_ancestor - search for an ancestor
* @p1: ancestor dentry
@@ -2670,7 +2700,7 @@ static struct dentry *__d_unalias(struct
m2 = &alias->d_parent->d_inode->i_mutex;
out_unalias:
if (likely(!d_mountpoint(alias))) {
- __d_move(alias, dentry);
+ __d_move(alias, dentry, false);
ret = alias;
}
out_err:
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -308,6 +308,7 @@ extern void dentry_update_name_case(stru

/* used for rename() and baskets */
extern void d_move(struct dentry *, struct dentry *);
+extern void d_exchange(struct dentry *, struct dentry *);
extern struct dentry *d_ancestor(struct dentry *, struct dentry *);

/* appendix may either be NULL or be used for transname suffixes */
--- a/security/security.c
+++ b/security/security.c
@@ -439,6 +439,14 @@ int security_path_rename(struct path *ol
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;
+
+ if (flags & RENAME_EXCHANGE) {
+ int err = security_ops->path_rename(new_dir, new_dentry,
+ old_dir, old_dentry);
+ if (err)
+ return err;
+ }
+
return security_ops->path_rename(old_dir, old_dentry, new_dir,
new_dentry);
}
@@ -531,6 +539,14 @@ int security_inode_rename(struct inode *
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;
+
+ if (flags & RENAME_EXCHANGE) {
+ int err = security_ops->inode_rename(new_dir, new_dentry,
+ old_dir, old_dentry);
+ if (err)
+ return err;
+ }
+
return security_ops->inode_rename(old_dir, old_dentry,
new_dir, new_dentry);
}

2014-01-14 10:34:21

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Mon, Jan 13, 2014 at 01:25:17PM +0100, Jan Kara wrote:
> On Wed 08-01-14 23:10:15, Miklos Szeredi wrote:
> > From: Miklos Szeredi <[email protected]>
> >
> > Implement RENAME_EXCHANGE flag in renameat2 syscall.
> Yes, this is much better than last time. Thanks for the work. You can add
> Reviewed-by: Jan Kara <[email protected]>
>
> One nitpick below...

> > @@ -3185,18 +3196,22 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
> >
> > new.bh = ext4_find_entry(new.dir, &new.dentry->d_name,
> > &new.de, &new.inlined);
> > - if (new.bh) {
> > - if (!new.inode) {
> > - brelse(new.bh);
> > - new.bh = NULL;
> > + if (!(flags & RENAME_EXCHANGE)) {
> > + if (new.bh) {
> > + if (!new.inode) {
> > + brelse(new.bh);
> > + new.bh = NULL;
> > + }
> > }
> > + if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC))
> > + ext4_alloc_da_blocks(old.inode);
> > + } else if (!new.bh || le32_to_cpu(new.de->inode) != new.inode->i_ino) {
> > + goto end_rename;
> > }
> I think this deserves a comment that RENAME_EXCHANGE expects both source
> and target to exist... I'm always pondering about this condition before I
> realize that.

Okay, added.

Updated patch follows.

Thanks,
Miklos
----

Subject: ext4: add cross rename support
From: Miklos Szeredi <[email protected]>

Implement RENAME_EXCHANGE flag in renameat2 syscall.

Signed-off-by: Miklos Szeredi <[email protected]>
Reviewed-by: Jan Kara <[email protected]>
---
fs/ext4/namei.c | 122 ++++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 88 insertions(+), 34 deletions(-)

--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3005,6 +3005,8 @@ struct ext4_renament {
struct inode *dir;
struct dentry *dentry;
struct inode *inode;
+ bool is_dir;
+ int dir_nlink_delta;

/* entry for "dentry" */
struct buffer_head *bh;
@@ -3136,6 +3138,14 @@ static void ext4_rename_delete(handle_t
}
}

+static void ext4_update_dir_count(handle_t *handle, struct ext4_renament *ent)
+{
+ if (ent->dir_nlink_delta == -1)
+ ext4_dec_count(handle, ent->dir);
+ else if (ent->dir_nlink_delta == 1)
+ ext4_inc_count(handle, ent->dir);
+}
+
/*
* Anybody can rename anything with this: the permission checks are left to the
* higher-level routines.
@@ -3161,7 +3171,7 @@ static int ext4_rename(struct inode *old
};
int retval;

- if (flags & ~RENAME_NOREPLACE)
+ if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
return -EOPNOTSUPP;

dquot_initialize(old.dir);
@@ -3169,10 +3179,11 @@ static int ext4_rename(struct inode *old

/* Initialize quotas before so that eventual writes go
* in separate transaction */
- if (new.inode)
+ if (!(flags & RENAME_EXCHANGE) && new.inode)
dquot_initialize(new.inode);

- old.bh = ext4_find_entry(old.dir, &old.dentry->d_name, &old.de, NULL);
+ old.bh = ext4_find_entry(old.dir, &old.dentry->d_name,
+ &old.de, &old.inlined);
/*
* Check for inode number is _not_ due to possible IO errors.
* We might rmdir the source, keep it as pwd of some process
@@ -3185,18 +3196,23 @@ static int ext4_rename(struct inode *old

new.bh = ext4_find_entry(new.dir, &new.dentry->d_name,
&new.de, &new.inlined);
- if (new.bh) {
- if (!new.inode) {
- brelse(new.bh);
- new.bh = NULL;
+ if (!(flags & RENAME_EXCHANGE)) {
+ if (new.bh) {
+ if (!new.inode) {
+ brelse(new.bh);
+ new.bh = NULL;
+ }
}
+ if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC))
+ ext4_alloc_da_blocks(old.inode);
+ } else if (!new.bh || le32_to_cpu(new.de->inode) != new.inode->i_ino) {
+ /* RENAME_EXCHANGE case: old *and* new must both exist */
+ goto end_rename;
}
- if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC))
- ext4_alloc_da_blocks(old.inode);

handle = ext4_journal_start(old.dir, EXT4_HT_DIR,
(2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) +
- EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2));
+ 2 * EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2));
if (IS_ERR(handle))
return PTR_ERR(handle);

@@ -3204,28 +3220,61 @@ static int ext4_rename(struct inode *old
ext4_handle_sync(handle);

if (S_ISDIR(old.inode->i_mode)) {
- if (new.inode) {
+ old.is_dir = true;
+ if (!(flags & RENAME_EXCHANGE) && new.inode) {
retval = -ENOTEMPTY;
if (!empty_dir(new.inode))
goto end_rename;
- } else {
- retval = -EMLINK;
- if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir))
- goto end_rename;
+
+ /*
+ * Overwriting a directory needs to decrement nlink of
+ * old parent (even if not cross directory rename).
+ */
+ old.dir_nlink_delta = -1;
}
retval = ext4_rename_dir_prepare(handle, &old);
if (retval)
goto end_rename;
}
+ if (new.inode && S_ISDIR(new.inode->i_mode)) {
+ new.is_dir = true;
+ if (flags & RENAME_EXCHANGE) {
+ retval = ext4_rename_dir_prepare(handle, &new);
+ if (retval)
+ goto end_rename;
+ }
+ }
+
+ /*
+ * Other than the special case of overwring a directory, parents' nlink
+ * only needs to be modified if this is a cross directory rename.
+ */
+ if (old.dir != new.dir && old.is_dir != new.is_dir) {
+ old.dir_nlink_delta = old.is_dir ? -1 : 1;
+ new.dir_nlink_delta = -old.dir_nlink_delta;
+ retval = -EMLINK;
+ if ((old.dir_nlink_delta > 0 && EXT4_DIR_LINK_MAX(old.dir)) ||
+ (new.dir_nlink_delta > 0 && EXT4_DIR_LINK_MAX(new.dir)))
+ goto end_rename;
+ }
+
if (!new.bh) {
retval = ext4_add_entry(handle, new.dentry, old.inode);
if (retval)
goto end_rename;
} else {
+ u8 new_file_type = new.de->file_type;
retval = ext4_setent(handle, &new,
old.inode->i_ino, old.de->file_type);
if (retval)
goto end_rename;
+
+ if (flags & RENAME_EXCHANGE) {
+ retval = ext4_setent(handle, &old,
+ new.inode->i_ino, new_file_type);
+ if (retval)
+ goto end_rename;
+ }
}

/*
@@ -3235,35 +3284,40 @@ static int ext4_rename(struct inode *old
old.inode->i_ctime = ext4_current_time(old.inode);
ext4_mark_inode_dirty(handle, old.inode);

- /*
- * ok, that's it
- */
- ext4_rename_delete(handle, &old);
+ if (!(flags & RENAME_EXCHANGE)) {
+ /*
+ * ok, that's it
+ */
+ ext4_rename_delete(handle, &old);

- if (new.inode) {
- ext4_dec_count(handle, new.inode);
- new.inode->i_ctime = ext4_current_time(new.inode);
+ old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir);
+ ext4_update_dx_flag(old.dir);
}
- old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir);
- ext4_update_dx_flag(old.dir);
if (old.dir_bh) {
retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino);
if (retval)
goto end_rename;
-
- ext4_dec_count(handle, old.dir);
- if (new.inode) {
+ }
+ if (new.dir_bh) {
+ retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino);
+ if (retval)
+ goto end_rename;
+ }
+ ext4_update_dir_count(handle, &old);
+ ext4_update_dir_count(handle, &new);
+ if (!new.inode)
+ ext4_update_dx_flag(new.dir);
+ if (new.dir_nlink_delta || !new.inode)
+ ext4_mark_inode_dirty(handle, new.dir);
+ ext4_mark_inode_dirty(handle, old.dir);
+ if (!(flags & RENAME_EXCHANGE) && new.inode) {
+ ext4_dec_count(handle, new.inode);
+ new.inode->i_ctime = ext4_current_time(new.inode);
+ if (S_ISDIR(old.inode->i_mode)) {
/* checked empty_dir above, can't have another parent,
* ext4_dec_count() won't work for many-linked dirs */
clear_nlink(new.inode);
- } else {
- ext4_inc_count(handle, new.dir);
- ext4_update_dx_flag(new.dir);
- ext4_mark_inode_dirty(handle, new.dir);
}
- }
- ext4_mark_inode_dirty(handle, old.dir);
- if (new.inode) {
ext4_mark_inode_dirty(handle, new.inode);
if (!new.inode->i_nlink)
ext4_orphan_add(handle, new.inode);

2014-01-14 12:47:27

by Jan Kara

[permalink] [raw]
Subject: Re: [PATCH 07/11] vfs: add cross-rename

On Tue 14-01-14 11:31:59, Miklos Szeredi wrote:
> On Mon, Jan 13, 2014 at 08:52:27AM +0100, Jan Kara wrote:
> > On Wed 08-01-14 23:10:11, Miklos Szeredi wrote:
>
> > > + if (max_links && new_dir != old_dir) {
> > > error = -EMLINK;
> > > - if (max_links && !target && new_dir != old_dir &&
> > > - new_dir->i_nlink >= max_links)
> > > + if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links)
> > > + goto out;
> > > + if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir &&
> > > + old_dir->i_nlink > max_links)
> > This should be >=, shouldn't it?
>
> Yes, good catch, thanks.
>
>
> > > @@ -4181,12 +4212,23 @@ retry_deleg:
> > > error = -EEXIST;
> > > if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
> > > goto exit5;
> > > + if (flags & RENAME_EXCHANGE) {
> > > + error = -ENOENT;
> > > + if (!new_dentry->d_inode)
> > > + goto exit5;
> > Should this be d_is_positive()?
>
> Yes, well, actually d_is_negative().
>
> Thanks a lot for the review. Updated patch below.
>
> Miklos
> ----
>
> Subject: vfs: add cross-rename
> From: Miklos Szeredi <[email protected]>
>
> If flags contain RENAME_EXCHANGE then exchange source and destination files.
> There's no restriction on the type of the files; e.g. a directory can be
> exchanged with a symlink.
The patch looks good. You can add:
Reviewed-by: Jan Kara <[email protected]>

Honza

>
> Signed-off-by: Miklos Szeredi <[email protected]>
> ---
> fs/dcache.c | 46 +++++++++++++++++---
> fs/namei.c | 107 +++++++++++++++++++++++++++++++++---------------
> include/linux/dcache.h | 1
> include/uapi/linux/fs.h | 1
> security/security.c | 16 +++++++
> 5 files changed, 131 insertions(+), 40 deletions(-)
>
> --- a/include/uapi/linux/fs.h
> +++ b/include/uapi/linux/fs.h
> @@ -36,6 +36,7 @@
> #define SEEK_MAX SEEK_HOLE
>
> #define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */
> +#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
>
> struct fstrim_range {
> __u64 start;
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -4020,6 +4020,8 @@ int vfs_rename(struct inode *old_dir, st
> const unsigned char *old_name;
> struct inode *source = old_dentry->d_inode;
> struct inode *target = new_dentry->d_inode;
> + bool new_is_dir = false;
> + unsigned max_links = new_dir->i_sb->s_max_links;
>
> if (source == target)
> return 0;
> @@ -4028,10 +4030,16 @@ int vfs_rename(struct inode *old_dir, st
> if (error)
> return error;
>
> - if (!target)
> + if (!target) {
> error = may_create(new_dir, new_dentry);
> - else
> - error = may_delete(new_dir, new_dentry, is_dir);
> + } else {
> + new_is_dir = d_is_dir(new_dentry);
> +
> + if (!(flags & RENAME_EXCHANGE))
> + error = may_delete(new_dir, new_dentry, is_dir);
> + else
> + error = may_delete(new_dir, new_dentry, new_is_dir);
> + }
> if (error)
> return error;
>
> @@ -4042,10 +4050,17 @@ int vfs_rename(struct inode *old_dir, st
> * If we are going to change the parent - check write permissions,
> * we'll need to flip '..'.
> */
> - if (is_dir && new_dir != old_dir) {
> - error = inode_permission(source, MAY_WRITE);
> - if (error)
> - return error;
> + if (new_dir != old_dir) {
> + if (is_dir) {
> + error = inode_permission(source, MAY_WRITE);
> + if (error)
> + return error;
> + }
> + if ((flags & RENAME_EXCHANGE) && new_is_dir) {
> + error = inode_permission(target, MAY_WRITE);
> + if (error)
> + return error;
> + }
> }
>
> error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry,
> @@ -4055,25 +4070,24 @@ int vfs_rename(struct inode *old_dir, st
>
> old_name = fsnotify_oldname_init(old_dentry->d_name.name);
> dget(new_dentry);
> - if (!is_dir)
> - lock_two_nondirectories(source, target);
> - else if (target)
> - mutex_lock(&target->i_mutex);
> + if (!(flags & RENAME_EXCHANGE)) {
> + if (!is_dir)
> + lock_two_nondirectories(source, target);
> + else if (target)
> + mutex_lock(&target->i_mutex);
> + }
>
> error = -EBUSY;
> if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry))
> goto out;
>
> - if (is_dir) {
> - unsigned max_links = new_dir->i_sb->s_max_links;
> -
> + if (max_links && new_dir != old_dir) {
> error = -EMLINK;
> - if (max_links && !target && new_dir != old_dir &&
> - new_dir->i_nlink >= max_links)
> + if (is_dir && !new_is_dir && new_dir->i_nlink >= max_links)
> + goto out;
> + if ((flags & RENAME_EXCHANGE) && !is_dir && new_is_dir &&
> + old_dir->i_nlink >= max_links)
> goto out;
> -
> - if (target)
> - shrink_dcache_parent(new_dentry);
> } else {
> error = try_break_deleg(source, delegated_inode);
> if (error)
> @@ -4084,27 +4098,40 @@ int vfs_rename(struct inode *old_dir, st
> goto out;
> }
> }
> + if (is_dir && !(flags & RENAME_EXCHANGE) && target)
> + shrink_dcache_parent(new_dentry);
> error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry,
> flags);
> if (error)
> goto out;
>
> - if (target) {
> + if (!(flags & RENAME_EXCHANGE) && target) {
> if (is_dir)
> target->i_flags |= S_DEAD;
> dont_mount(new_dentry);
> }
> - if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE))
> - d_move(old_dentry, new_dentry);
> + if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) {
> + if (!(flags & RENAME_EXCHANGE))
> + d_move(old_dentry, new_dentry);
> + else
> + d_exchange(old_dentry, new_dentry);
> + }
> out:
> - if (!is_dir)
> - unlock_two_nondirectories(source, target);
> - else if (target)
> - mutex_unlock(&target->i_mutex);
> + if (!(flags & RENAME_EXCHANGE)) {
> + if (!is_dir)
> + unlock_two_nondirectories(source, target);
> + else if (target)
> + mutex_unlock(&target->i_mutex);
> + }
> dput(new_dentry);
> - if (!error)
> + if (!error) {
> fsnotify_move(old_dir, new_dir, old_name, is_dir,
> - target, old_dentry);
> + !(flags & RENAME_EXCHANGE) ? target : NULL, old_dentry);
> + if (flags & RENAME_EXCHANGE) {
> + fsnotify_move(new_dir, old_dir, old_dentry->d_name.name,
> + new_is_dir, NULL, new_dentry);
> + }
> + }
> fsnotify_oldname_free(old_name);
>
> return error;
> @@ -4124,9 +4151,12 @@ SYSCALL_DEFINE5(renameat2, int, olddfd,
> bool should_retry = false;
> int error;
>
> - if (flags & ~RENAME_NOREPLACE)
> + if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
> return -EOPNOTSUPP;
>
> + if ((flags & RENAME_NOREPLACE) && (flags & RENAME_EXCHANGE))
> + return -EINVAL;
> +
> retry:
> from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags);
> if (IS_ERR(from)) {
> @@ -4161,7 +4191,8 @@ SYSCALL_DEFINE5(renameat2, int, olddfd,
>
> oldnd.flags &= ~LOOKUP_PARENT;
> newnd.flags &= ~LOOKUP_PARENT;
> - newnd.flags |= LOOKUP_RENAME_TARGET;
> + if (!(flags & RENAME_EXCHANGE))
> + newnd.flags |= LOOKUP_RENAME_TARGET;
>
> retry_deleg:
> trap = lock_rename(new_dir, old_dir);
> @@ -4181,12 +4212,23 @@ SYSCALL_DEFINE5(renameat2, int, olddfd,
> error = -EEXIST;
> if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
> goto exit5;
> + if (flags & RENAME_EXCHANGE) {
> + error = -ENOENT;
> + if (d_is_negative(new_dentry))
> + goto exit5;
> +
> + if (!d_is_dir(new_dentry)) {
> + error = -ENOTDIR;
> + if (newnd.last.name[newnd.last.len])
> + goto exit5;
> + }
> + }
> /* unless the source is a directory trailing slashes give -ENOTDIR */
> if (!d_is_dir(old_dentry)) {
> error = -ENOTDIR;
> if (oldnd.last.name[oldnd.last.len])
> goto exit5;
> - if (newnd.last.name[newnd.last.len])
> + if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len])
> goto exit5;
> }
> /* source should not be ancestor of target */
> @@ -4194,7 +4236,8 @@ SYSCALL_DEFINE5(renameat2, int, olddfd,
> if (old_dentry == trap)
> goto exit5;
> /* target should not be an ancestor of source */
> - error = -ENOTEMPTY;
> + if (!(flags & RENAME_EXCHANGE))
> + error = -ENOTEMPTY;
> if (new_dentry == trap)
> goto exit5;
>
> --- a/fs/dcache.c
> +++ b/fs/dcache.c
> @@ -2483,12 +2483,14 @@ static void switch_names(struct dentry *
> dentry->d_name.name = dentry->d_iname;
> } else {
> /*
> - * Both are internal. Just copy target to dentry
> + * Both are internal.
> */
> - memcpy(dentry->d_iname, target->d_name.name,
> - target->d_name.len + 1);
> - dentry->d_name.len = target->d_name.len;
> - return;
> + unsigned int i;
> + BUILD_BUG_ON(!IS_ALIGNED(DNAME_INLINE_LEN, sizeof(long)));
> + for (i = 0; i < DNAME_INLINE_LEN / sizeof(long); i++) {
> + swap(((long *) &dentry->d_iname)[i],
> + ((long *) &target->d_iname)[i]);
> + }
> }
> }
> swap(dentry->d_name.len, target->d_name.len);
> @@ -2545,13 +2547,15 @@ static void dentry_unlock_parents_for_mo
> * __d_move - move a dentry
> * @dentry: entry to move
> * @target: new dentry
> + * @exchange: exchange the two dentries
> *
> * Update the dcache to reflect the move of a file name. Negative
> * dcache entries should not be moved in this way. Caller must hold
> * rename_lock, the i_mutex of the source and target directories,
> * and the sb->s_vfs_rename_mutex if they differ. See lock_rename().
> */
> -static void __d_move(struct dentry * dentry, struct dentry * target)
> +static void __d_move(struct dentry *dentry, struct dentry *target,
> + bool exchange)
> {
> if (!dentry->d_inode)
> printk(KERN_WARNING "VFS: moving negative dcache entry\n");
> @@ -2575,6 +2579,10 @@ static void __d_move(struct dentry * den
>
> /* Unhash the target: dput() will then get rid of it */
> __d_drop(target);
> + if (exchange) {
> + __d_rehash(target,
> + d_hash(dentry->d_parent, dentry->d_name.hash));
> + }
>
> list_del(&dentry->d_u.d_child);
> list_del(&target->d_u.d_child);
> @@ -2601,6 +2609,8 @@ static void __d_move(struct dentry * den
> write_seqcount_end(&dentry->d_seq);
>
> dentry_unlock_parents_for_move(dentry, target);
> + if (exchange)
> + fsnotify_d_move(target);
> spin_unlock(&target->d_lock);
> fsnotify_d_move(dentry);
> spin_unlock(&dentry->d_lock);
> @@ -2618,11 +2628,31 @@ static void __d_move(struct dentry * den
> void d_move(struct dentry *dentry, struct dentry *target)
> {
> write_seqlock(&rename_lock);
> - __d_move(dentry, target);
> + __d_move(dentry, target, false);
> write_sequnlock(&rename_lock);
> }
> EXPORT_SYMBOL(d_move);
>
> +/*
> + * d_exchange - exchange two dentries
> + * @dentry1: first dentry
> + * @dentry2: second dentry
> + */
> +void d_exchange(struct dentry *dentry1, struct dentry *dentry2)
> +{
> + write_seqlock(&rename_lock);
> +
> + WARN_ON(!dentry1->d_inode);
> + WARN_ON(!dentry2->d_inode);
> + WARN_ON(IS_ROOT(dentry1));
> + WARN_ON(IS_ROOT(dentry2));
> +
> + __d_move(dentry1, dentry2, true);
> +
> + write_sequnlock(&rename_lock);
> +}
> +
> +
> /**
> * d_ancestor - search for an ancestor
> * @p1: ancestor dentry
> @@ -2670,7 +2700,7 @@ static struct dentry *__d_unalias(struct
> m2 = &alias->d_parent->d_inode->i_mutex;
> out_unalias:
> if (likely(!d_mountpoint(alias))) {
> - __d_move(alias, dentry);
> + __d_move(alias, dentry, false);
> ret = alias;
> }
> out_err:
> --- a/include/linux/dcache.h
> +++ b/include/linux/dcache.h
> @@ -308,6 +308,7 @@ extern void dentry_update_name_case(stru
>
> /* used for rename() and baskets */
> extern void d_move(struct dentry *, struct dentry *);
> +extern void d_exchange(struct dentry *, struct dentry *);
> extern struct dentry *d_ancestor(struct dentry *, struct dentry *);
>
> /* appendix may either be NULL or be used for transname suffixes */
> --- a/security/security.c
> +++ b/security/security.c
> @@ -439,6 +439,14 @@ int security_path_rename(struct path *ol
> if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
> (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
> return 0;
> +
> + if (flags & RENAME_EXCHANGE) {
> + int err = security_ops->path_rename(new_dir, new_dentry,
> + old_dir, old_dentry);
> + if (err)
> + return err;
> + }
> +
> return security_ops->path_rename(old_dir, old_dentry, new_dir,
> new_dentry);
> }
> @@ -531,6 +539,14 @@ int security_inode_rename(struct inode *
> if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
> (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
> return 0;
> +
> + if (flags & RENAME_EXCHANGE) {
> + int err = security_ops->inode_rename(new_dir, new_dentry,
> + old_dir, old_dentry);
> + if (err)
> + return err;
> + }
> +
> return security_ops->inode_rename(old_dir, old_dentry,
> new_dir, new_dentry);
> }
--
Jan Kara <[email protected]>
SUSE Labs, CR

2014-01-14 13:04:53

by Tetsuo Handa

[permalink] [raw]
Subject: Re: [PATCH 00/11] cross rename v3

Miklos Szeredi wrote:
> On Mon, Jan 13, 2014 at 11:03 PM, Tetsuo Handa
> <[email protected]> wrote:
> > Miklos Szeredi wrote:
> >> Cross rename (A, B) is equivalent to plain rename(A, B) + plain rename
> >> (B, A) done as a single atomic operation. If security module allows
> >> both then cross rename is allowed. If at least one is denied then the
> >> cross rename is denied.
> >
> > Yes, the functionality itself is fine. The problem is how LSM users check
> > their permissions for the functionality.
> >
> >>
> >> This is prepared for in "[PATCH 06/11] security: add flags to rename
> >> hooks" and actually done in "[PATCH 07/11] vfs: add cross-rename".
> >>
> >> Security people are free to implement a explicit security check for
> >> cross rename, but I don't think that is in the scope of this patchset.
> >>
> > I don't know how their permissions are checked, but I think that
> > swapping /A/B and /C/D should check not only
> >
> > Remove a name from directory A
> > Add a name to directory C
> >
> > but also
> >
> > Add a name to directory A
> > Remove a name from directory C
> >
> > using their security labels.
> >
> > Without making changes to security/*/ directory, SELinux/SMACK/TOMOYO/AppArmor
> > might fail to check the latter permissions.
>
> Those permissions will be checked. Please see security/security.c in
> patch 07/11 of the series.
>
Oh, I see. But I think that 07/11 is wasteful for security_path_rename() users.
Why bother to re-calculate /A/B and /C/D using d_absolute_path()?

I prefer flags argument passed to tomoyo_path_rename(). Untested patch follows.
John, what about AppArmor?
----------
>From 4344f31e40b908ab1a6dba9121018d7f37130393 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Tue, 14 Jan 2014 21:55:48 +0900
Subject: [PATCH] LSM: Pass flags argument to security_path_rename hook users.

Passing flags argument can save TOMOYO from recalculating pathnames
when cross rename operation is requested.

Signed-off-by: Tetsuo Handa <[email protected]>
---
include/linux/security.h | 4 +++-
security/apparmor/lsm.c | 17 ++++++++++++++---
security/capability.c | 3 ++-
security/security.c | 9 +--------
security/tomoyo/common.c | 1 +
security/tomoyo/common.h | 5 ++++-
security/tomoyo/file.c | 10 +++++++++-
security/tomoyo/tomoyo.c | 8 ++++++--
security/tomoyo/util.c | 1 +
9 files changed, 41 insertions(+), 17 deletions(-)

diff --git a/include/linux/security.h b/include/linux/security.h
index 95cfccc..ba8ee7a 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -453,6 +453,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
* @old_dentry contains the dentry structure of the old link.
* @new_dir contains the path structure for parent of the new link.
* @new_dentry contains the dentry structure of the new link.
+ * @flags contains rename flags.
* Return 0 if permission is granted.
* @path_chmod:
* Check for permission to change DAC's permission of a file or directory.
@@ -1491,7 +1492,8 @@ struct security_operations {
int (*path_link) (struct dentry *old_dentry, struct path *new_dir,
struct dentry *new_dentry);
int (*path_rename) (struct path *old_dir, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry);
+ struct path *new_dir, struct dentry *new_dentry,
+ unsigned int flags);
int (*path_chmod) (struct path *path, umode_t mode);
int (*path_chown) (struct path *path, kuid_t uid, kgid_t gid);
int (*path_chroot) (struct path *path);
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 4257b7e..f5d4704 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -315,7 +315,8 @@ static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
}

static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry)
+ struct path *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct aa_profile *profile;
int error = 0;
@@ -330,7 +331,7 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
struct path_cond cond = { old_dentry->d_inode->i_uid,
old_dentry->d_inode->i_mode
};
-
+retry:
error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0,
MAY_READ | AA_MAY_META_READ | MAY_WRITE |
AA_MAY_META_WRITE | AA_MAY_DELETE,
@@ -339,7 +340,17 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
0, MAY_WRITE | AA_MAY_META_WRITE |
AA_MAY_CREATE, &cond);
-
+ if (!error && (flags & RENAME_EXCHANGE)) {
+ /* Cross rename requires both inodes to exist. */
+ old_path.mnt = new_dir->mnt;
+ old_path.dentry = new_dentry;
+ new_path.mnt = old_dir->mnt;
+ new_path.dentry = old_dentry;
+ cond.uid = new_dentry->d_inode->i_uid;
+ cond.mode = new_dentry->d_inode->i_mode;
+ flags = 0;
+ goto retry;
+ }
}
return error;
}
diff --git a/security/capability.c b/security/capability.c
index 8b4f24a..cb67fe2 100644
--- a/security/capability.c
+++ b/security/capability.c
@@ -280,7 +280,8 @@ static int cap_path_link(struct dentry *old_dentry, struct path *new_dir,
}

static int cap_path_rename(struct path *old_path, struct dentry *old_dentry,
- struct path *new_path, struct dentry *new_dentry)
+ struct path *new_path, struct dentry *new_dentry,
+ unsigned int flags)
{
return 0;
}
diff --git a/security/security.c b/security/security.c
index 3dd2258..b14574e 100644
--- a/security/security.c
+++ b/security/security.c
@@ -440,15 +440,8 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;

- if (flags & RENAME_EXCHANGE) {
- int err = security_ops->path_rename(new_dir, new_dentry,
- old_dir, old_dentry);
- if (err)
- return err;
- }
-
return security_ops->path_rename(old_dir, old_dentry, new_dir,
- new_dentry);
+ new_dentry, flags);
}
EXPORT_SYMBOL(security_path_rename);

diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c
index 283862a..86747a7 100644
--- a/security/tomoyo/common.c
+++ b/security/tomoyo/common.c
@@ -36,6 +36,7 @@ const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX
[TOMOYO_MAC_FILE_MKCHAR] = "mkchar",
[TOMOYO_MAC_FILE_LINK] = "link",
[TOMOYO_MAC_FILE_RENAME] = "rename",
+ [TOMOYO_MAC_FILE_SWAPNAME] = "swapname",
[TOMOYO_MAC_FILE_CHMOD] = "chmod",
[TOMOYO_MAC_FILE_CHOWN] = "chown",
[TOMOYO_MAC_FILE_CHGRP] = "chgrp",
diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h
index b897d48..0349ae9 100644
--- a/security/tomoyo/common.h
+++ b/security/tomoyo/common.h
@@ -276,6 +276,7 @@ enum tomoyo_network_acl_index {
enum tomoyo_path2_acl_index {
TOMOYO_TYPE_LINK,
TOMOYO_TYPE_RENAME,
+ TOMOYO_TYPE_SWAPNAME,
TOMOYO_TYPE_PIVOT_ROOT,
TOMOYO_MAX_PATH2_OPERATION
};
@@ -335,6 +336,7 @@ enum tomoyo_mac_index {
TOMOYO_MAC_FILE_MKCHAR,
TOMOYO_MAC_FILE_LINK,
TOMOYO_MAC_FILE_RENAME,
+ TOMOYO_MAC_FILE_SWAPNAME,
TOMOYO_MAC_FILE_CHMOD,
TOMOYO_MAC_FILE_CHOWN,
TOMOYO_MAC_FILE_CHGRP,
@@ -730,7 +732,8 @@ struct tomoyo_mkdev_acl {
};

/*
- * Structure for "file rename", "file link" and "file pivot_root" directive.
+ * Structure for "file rename", "file swapname", "file link" and
+ * "file pivot_root" directive.
*/
struct tomoyo_path2_acl {
struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH2_ACL */
diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c
index 4003907..c7d9546 100644
--- a/security/tomoyo/file.c
+++ b/security/tomoyo/file.c
@@ -38,6 +38,7 @@ const u8 tomoyo_pnnn2mac[TOMOYO_MAX_MKDEV_OPERATION] = {
const u8 tomoyo_pp2mac[TOMOYO_MAX_PATH2_OPERATION] = {
[TOMOYO_TYPE_LINK] = TOMOYO_MAC_FILE_LINK,
[TOMOYO_TYPE_RENAME] = TOMOYO_MAC_FILE_RENAME,
+ [TOMOYO_TYPE_SWAPNAME] = TOMOYO_MAC_FILE_SWAPNAME,
[TOMOYO_TYPE_PIVOT_ROOT] = TOMOYO_MAC_FILE_PIVOT_ROOT,
};

@@ -874,7 +875,7 @@ int tomoyo_mkdev_perm(const u8 operation, struct path *path,
}

/**
- * tomoyo_path2_perm - Check permission for "rename", "link" and "pivot_root".
+ * tomoyo_path2_perm - Check permission for "rename", "swapname", "link" and "pivot_root".
*
* @operation: Type of operation.
* @path1: Pointer to "struct path".
@@ -916,6 +917,13 @@ int tomoyo_path2_perm(const u8 operation, struct path *path1,
tomoyo_add_slash(&buf1);
tomoyo_add_slash(&buf2);
break;
+ case TOMOYO_TYPE_SWAPNAME:
+ /* Cross rename requires both inodes to exist. */
+ if (S_ISDIR(path1->dentry->d_inode->i_mode))
+ tomoyo_add_slash(&buf1);
+ if (S_ISDIR(path2->dentry->d_inode->i_mode))
+ tomoyo_add_slash(&buf2);
+ break;
}
r.obj = &obj;
r.param_type = TOMOYO_TYPE_PATH2_ACL;
diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
index f0b756e..8e9fb4a 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -287,17 +287,21 @@ static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
* @old_dentry: Pointer to "struct dentry".
* @new_parent: Pointer to "struct path".
* @new_dentry: Pointer to "struct dentry".
+ * @flags: Rename flags.
*
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_path_rename(struct path *old_parent,
struct dentry *old_dentry,
struct path *new_parent,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ unsigned int flags)
{
struct path path1 = { old_parent->mnt, old_dentry };
struct path path2 = { new_parent->mnt, new_dentry };
- return tomoyo_path2_perm(TOMOYO_TYPE_RENAME, &path1, &path2);
+ return tomoyo_path2_perm((flags & RENAME_EXCHANGE) ?
+ TOMOYO_TYPE_SWAPNAME : TOMOYO_TYPE_RENAME,
+ &path1, &path2);
}

/**
diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c
index 2952ba5..f0ac0be 100644
--- a/security/tomoyo/util.c
+++ b/security/tomoyo/util.c
@@ -34,6 +34,7 @@ const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX] = {
[TOMOYO_MAC_FILE_MKCHAR] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_LINK] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_RENAME] = TOMOYO_MAC_CATEGORY_FILE,
+ [TOMOYO_MAC_FILE_SWAPNAME] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_CHMOD] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_CHOWN] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_CHGRP] = TOMOYO_MAC_CATEGORY_FILE,
--
1.7.1

2014-01-14 20:11:08

by John Johansen

[permalink] [raw]
Subject: Re: [PATCH 00/11] cross rename v3

On 01/14/2014 05:03 AM, Tetsuo Handa wrote:
> Miklos Szeredi wrote:
>> On Mon, Jan 13, 2014 at 11:03 PM, Tetsuo Handa
>> <[email protected]> wrote:
>>> Miklos Szeredi wrote:
>>>> Cross rename (A, B) is equivalent to plain rename(A, B) + plain rename
>>>> (B, A) done as a single atomic operation. If security module allows
>>>> both then cross rename is allowed. If at least one is denied then the
>>>> cross rename is denied.
>>>
>>> Yes, the functionality itself is fine. The problem is how LSM users check
>>> their permissions for the functionality.
>>>
>>>>
>>>> This is prepared for in "[PATCH 06/11] security: add flags to rename
>>>> hooks" and actually done in "[PATCH 07/11] vfs: add cross-rename".
>>>>
>>>> Security people are free to implement a explicit security check for
>>>> cross rename, but I don't think that is in the scope of this patchset.
>>>>
>>> I don't know how their permissions are checked, but I think that
>>> swapping /A/B and /C/D should check not only
>>>
>>> Remove a name from directory A
>>> Add a name to directory C
>>>
>>> but also
>>>
>>> Add a name to directory A
>>> Remove a name from directory C
>>>
>>> using their security labels.
>>>
>>> Without making changes to security/*/ directory, SELinux/SMACK/TOMOYO/AppArmor
>>> might fail to check the latter permissions.
>>
>> Those permissions will be checked. Please see security/security.c in
>> patch 07/11 of the series.
>>
> Oh, I see. But I think that 07/11 is wasteful for security_path_rename() users.
> Why bother to re-calculate /A/B and /C/D using d_absolute_path()?
>
> I prefer flags argument passed to tomoyo_path_rename(). Untested patch follows.
> John, what about AppArmor?

Right policy wise it doesn't make a difference but not having to re-calculate
the paths would be more efficient.

I'd re-factor the apparmor bit of the patch differently so that the paths aren't
recomputed, what is in the patch looks like it should work. In fact I would want
to do the apparmor refactor as a separate patch so that the internal changes
needed to take advantage of the LSM change are separate from the LSM change
it self.

I've only given the patch a quick once over and not tested it yet, but it looks
good, so far.



> ----------
>>From 4344f31e40b908ab1a6dba9121018d7f37130393 Mon Sep 17 00:00:00 2001
> From: Tetsuo Handa <[email protected]>
> Date: Tue, 14 Jan 2014 21:55:48 +0900
> Subject: [PATCH] LSM: Pass flags argument to security_path_rename hook users.
>
> Passing flags argument can save TOMOYO from recalculating pathnames
> when cross rename operation is requested.
>
> Signed-off-by: Tetsuo Handa <[email protected]>
> ---
> include/linux/security.h | 4 +++-
> security/apparmor/lsm.c | 17 ++++++++++++++---
> security/capability.c | 3 ++-
> security/security.c | 9 +--------
> security/tomoyo/common.c | 1 +
> security/tomoyo/common.h | 5 ++++-
> security/tomoyo/file.c | 10 +++++++++-
> security/tomoyo/tomoyo.c | 8 ++++++--
> security/tomoyo/util.c | 1 +
> 9 files changed, 41 insertions(+), 17 deletions(-)
>
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 95cfccc..ba8ee7a 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -453,6 +453,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
> * @old_dentry contains the dentry structure of the old link.
> * @new_dir contains the path structure for parent of the new link.
> * @new_dentry contains the dentry structure of the new link.
> + * @flags contains rename flags.
> * Return 0 if permission is granted.
> * @path_chmod:
> * Check for permission to change DAC's permission of a file or directory.
> @@ -1491,7 +1492,8 @@ struct security_operations {
> int (*path_link) (struct dentry *old_dentry, struct path *new_dir,
> struct dentry *new_dentry);
> int (*path_rename) (struct path *old_dir, struct dentry *old_dentry,
> - struct path *new_dir, struct dentry *new_dentry);
> + struct path *new_dir, struct dentry *new_dentry,
> + unsigned int flags);
> int (*path_chmod) (struct path *path, umode_t mode);
> int (*path_chown) (struct path *path, kuid_t uid, kgid_t gid);
> int (*path_chroot) (struct path *path);
> diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
> index 4257b7e..f5d4704 100644
> --- a/security/apparmor/lsm.c
> +++ b/security/apparmor/lsm.c
> @@ -315,7 +315,8 @@ static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
> }
>
> static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
> - struct path *new_dir, struct dentry *new_dentry)
> + struct path *new_dir, struct dentry *new_dentry,
> + unsigned int flags)
> {
> struct aa_profile *profile;
> int error = 0;
> @@ -330,7 +331,7 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
> struct path_cond cond = { old_dentry->d_inode->i_uid,
> old_dentry->d_inode->i_mode
> };
> -
> +retry:
> error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0,
> MAY_READ | AA_MAY_META_READ | MAY_WRITE |
> AA_MAY_META_WRITE | AA_MAY_DELETE,
> @@ -339,7 +340,17 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
> error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
> 0, MAY_WRITE | AA_MAY_META_WRITE |
> AA_MAY_CREATE, &cond);
> -
> + if (!error && (flags & RENAME_EXCHANGE)) {
> + /* Cross rename requires both inodes to exist. */
> + old_path.mnt = new_dir->mnt;
> + old_path.dentry = new_dentry;
> + new_path.mnt = old_dir->mnt;
> + new_path.dentry = old_dentry;
> + cond.uid = new_dentry->d_inode->i_uid;
> + cond.mode = new_dentry->d_inode->i_mode;
> + flags = 0;
> + goto retry;
> + }
> }
> return error;
> }
> diff --git a/security/capability.c b/security/capability.c
> index 8b4f24a..cb67fe2 100644
> --- a/security/capability.c
> +++ b/security/capability.c
> @@ -280,7 +280,8 @@ static int cap_path_link(struct dentry *old_dentry, struct path *new_dir,
> }
>
> static int cap_path_rename(struct path *old_path, struct dentry *old_dentry,
> - struct path *new_path, struct dentry *new_dentry)
> + struct path *new_path, struct dentry *new_dentry,
> + unsigned int flags)
> {
> return 0;
> }
> diff --git a/security/security.c b/security/security.c
> index 3dd2258..b14574e 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -440,15 +440,8 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
> (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
> return 0;
>
> - if (flags & RENAME_EXCHANGE) {
> - int err = security_ops->path_rename(new_dir, new_dentry,
> - old_dir, old_dentry);
> - if (err)
> - return err;
> - }
> -
> return security_ops->path_rename(old_dir, old_dentry, new_dir,
> - new_dentry);
> + new_dentry, flags);
> }
> EXPORT_SYMBOL(security_path_rename);
>
> diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c
> index 283862a..86747a7 100644
> --- a/security/tomoyo/common.c
> +++ b/security/tomoyo/common.c
> @@ -36,6 +36,7 @@ const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX
> [TOMOYO_MAC_FILE_MKCHAR] = "mkchar",
> [TOMOYO_MAC_FILE_LINK] = "link",
> [TOMOYO_MAC_FILE_RENAME] = "rename",
> + [TOMOYO_MAC_FILE_SWAPNAME] = "swapname",
> [TOMOYO_MAC_FILE_CHMOD] = "chmod",
> [TOMOYO_MAC_FILE_CHOWN] = "chown",
> [TOMOYO_MAC_FILE_CHGRP] = "chgrp",
> diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h
> index b897d48..0349ae9 100644
> --- a/security/tomoyo/common.h
> +++ b/security/tomoyo/common.h
> @@ -276,6 +276,7 @@ enum tomoyo_network_acl_index {
> enum tomoyo_path2_acl_index {
> TOMOYO_TYPE_LINK,
> TOMOYO_TYPE_RENAME,
> + TOMOYO_TYPE_SWAPNAME,
> TOMOYO_TYPE_PIVOT_ROOT,
> TOMOYO_MAX_PATH2_OPERATION
> };
> @@ -335,6 +336,7 @@ enum tomoyo_mac_index {
> TOMOYO_MAC_FILE_MKCHAR,
> TOMOYO_MAC_FILE_LINK,
> TOMOYO_MAC_FILE_RENAME,
> + TOMOYO_MAC_FILE_SWAPNAME,
> TOMOYO_MAC_FILE_CHMOD,
> TOMOYO_MAC_FILE_CHOWN,
> TOMOYO_MAC_FILE_CHGRP,
> @@ -730,7 +732,8 @@ struct tomoyo_mkdev_acl {
> };
>
> /*
> - * Structure for "file rename", "file link" and "file pivot_root" directive.
> + * Structure for "file rename", "file swapname", "file link" and
> + * "file pivot_root" directive.
> */
> struct tomoyo_path2_acl {
> struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH2_ACL */
> diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c
> index 4003907..c7d9546 100644
> --- a/security/tomoyo/file.c
> +++ b/security/tomoyo/file.c
> @@ -38,6 +38,7 @@ const u8 tomoyo_pnnn2mac[TOMOYO_MAX_MKDEV_OPERATION] = {
> const u8 tomoyo_pp2mac[TOMOYO_MAX_PATH2_OPERATION] = {
> [TOMOYO_TYPE_LINK] = TOMOYO_MAC_FILE_LINK,
> [TOMOYO_TYPE_RENAME] = TOMOYO_MAC_FILE_RENAME,
> + [TOMOYO_TYPE_SWAPNAME] = TOMOYO_MAC_FILE_SWAPNAME,
> [TOMOYO_TYPE_PIVOT_ROOT] = TOMOYO_MAC_FILE_PIVOT_ROOT,
> };
>
> @@ -874,7 +875,7 @@ int tomoyo_mkdev_perm(const u8 operation, struct path *path,
> }
>
> /**
> - * tomoyo_path2_perm - Check permission for "rename", "link" and "pivot_root".
> + * tomoyo_path2_perm - Check permission for "rename", "swapname", "link" and "pivot_root".
> *
> * @operation: Type of operation.
> * @path1: Pointer to "struct path".
> @@ -916,6 +917,13 @@ int tomoyo_path2_perm(const u8 operation, struct path *path1,
> tomoyo_add_slash(&buf1);
> tomoyo_add_slash(&buf2);
> break;
> + case TOMOYO_TYPE_SWAPNAME:
> + /* Cross rename requires both inodes to exist. */
> + if (S_ISDIR(path1->dentry->d_inode->i_mode))
> + tomoyo_add_slash(&buf1);
> + if (S_ISDIR(path2->dentry->d_inode->i_mode))
> + tomoyo_add_slash(&buf2);
> + break;
> }
> r.obj = &obj;
> r.param_type = TOMOYO_TYPE_PATH2_ACL;
> diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
> index f0b756e..8e9fb4a 100644
> --- a/security/tomoyo/tomoyo.c
> +++ b/security/tomoyo/tomoyo.c
> @@ -287,17 +287,21 @@ static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
> * @old_dentry: Pointer to "struct dentry".
> * @new_parent: Pointer to "struct path".
> * @new_dentry: Pointer to "struct dentry".
> + * @flags: Rename flags.
> *
> * Returns 0 on success, negative value otherwise.
> */
> static int tomoyo_path_rename(struct path *old_parent,
> struct dentry *old_dentry,
> struct path *new_parent,
> - struct dentry *new_dentry)
> + struct dentry *new_dentry,
> + unsigned int flags)
> {
> struct path path1 = { old_parent->mnt, old_dentry };
> struct path path2 = { new_parent->mnt, new_dentry };
> - return tomoyo_path2_perm(TOMOYO_TYPE_RENAME, &path1, &path2);
> + return tomoyo_path2_perm((flags & RENAME_EXCHANGE) ?
> + TOMOYO_TYPE_SWAPNAME : TOMOYO_TYPE_RENAME,
> + &path1, &path2);
> }
>
> /**
> diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c
> index 2952ba5..f0ac0be 100644
> --- a/security/tomoyo/util.c
> +++ b/security/tomoyo/util.c
> @@ -34,6 +34,7 @@ const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX] = {
> [TOMOYO_MAC_FILE_MKCHAR] = TOMOYO_MAC_CATEGORY_FILE,
> [TOMOYO_MAC_FILE_LINK] = TOMOYO_MAC_CATEGORY_FILE,
> [TOMOYO_MAC_FILE_RENAME] = TOMOYO_MAC_CATEGORY_FILE,
> + [TOMOYO_MAC_FILE_SWAPNAME] = TOMOYO_MAC_CATEGORY_FILE,
> [TOMOYO_MAC_FILE_CHMOD] = TOMOYO_MAC_CATEGORY_FILE,
> [TOMOYO_MAC_FILE_CHOWN] = TOMOYO_MAC_CATEGORY_FILE,
> [TOMOYO_MAC_FILE_CHGRP] = TOMOYO_MAC_CATEGORY_FILE,
>

2014-01-14 20:54:55

by Tetsuo Handa

[permalink] [raw]
Subject: Re: [PATCH 00/11] cross rename v3

John Johansen wrote:
> On 01/14/2014 05:03 AM, Tetsuo Handa wrote:
> > Miklos Szeredi wrote:
> >> On Mon, Jan 13, 2014 at 11:03 PM, Tetsuo Handa
> >> <[email protected]> wrote:
> >>> Miklos Szeredi wrote:
> >>>> Cross rename (A, B) is equivalent to plain rename(A, B) + plain rename
> >>>> (B, A) done as a single atomic operation. If security module allows
> >>>> both then cross rename is allowed. If at least one is denied then the
> >>>> cross rename is denied.
> >>>
> >>> Yes, the functionality itself is fine. The problem is how LSM users check
> >>> their permissions for the functionality.
> >>>
> >>>>
> >>>> This is prepared for in "[PATCH 06/11] security: add flags to rename
> >>>> hooks" and actually done in "[PATCH 07/11] vfs: add cross-rename".
> >>>>
> >>>> Security people are free to implement a explicit security check for
> >>>> cross rename, but I don't think that is in the scope of this patchset.
> >>>>
> >>> I don't know how their permissions are checked, but I think that
> >>> swapping /A/B and /C/D should check not only
> >>>
> >>> Remove a name from directory A
> >>> Add a name to directory C
> >>>
> >>> but also
> >>>
> >>> Add a name to directory A
> >>> Remove a name from directory C
> >>>
> >>> using their security labels.
> >>>
> >>> Without making changes to security/*/ directory, SELinux/SMACK/TOMOYO/AppArmor
> >>> might fail to check the latter permissions.
> >>
> >> Those permissions will be checked. Please see security/security.c in
> >> patch 07/11 of the series.
> >>
> > Oh, I see. But I think that 07/11 is wasteful for security_path_rename() users.
> > Why bother to re-calculate /A/B and /C/D using d_absolute_path()?
> >
> > I prefer flags argument passed to tomoyo_path_rename(). Untested patch follows.
> > John, what about AppArmor?
>
> Right policy wise it doesn't make a difference but not having to re-calculate
> the paths would be more efficient.
>
> I'd re-factor the apparmor bit of the patch differently so that the paths aren't
> recomputed, what is in the patch looks like it should work. In fact I would want
> to do the apparmor refactor as a separate patch so that the internal changes
> needed to take advantage of the LSM change are separate from the LSM change
> it self.
>
> I've only given the patch a quick once over and not tested it yet, but it looks
> good, so far.

I see. And security_inode_rename() should also receive the flags argument, for
smack_inode_rename() needs no change as it checks MAY_READWRITE permission on
both inodes.

I think below change is fine for SELinux/SMACK/Capability. TOMOYO and AppArmor
want separate patch for RENAME_EXCHANGE handling.

Miklos, can you insert below change between a patch which defines
RENAME_EXCHANGE and a patch which implements RENAME_EXCHANGE functionality?

diff --git a/include/linux/security.h b/include/linux/security.h
index 95cfccc..dbd05ca 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -446,6 +446,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
* @old_dentry contains the dentry structure of the old link.
* @new_dir contains the inode structure for parent of the new link.
* @new_dentry contains the dentry structure of the new link.
+ * @flags contains rename flags.
* Return 0 if permission is granted.
* @path_rename:
* Check for permission to rename a file or directory.
@@ -453,6 +454,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
* @old_dentry contains the dentry structure of the old link.
* @new_dir contains the path structure for parent of the new link.
* @new_dentry contains the dentry structure of the new link.
+ * @flags contains rename flags.
* Return 0 if permission is granted.
* @path_chmod:
* Check for permission to change DAC's permission of a file or directory.
@@ -1491,7 +1493,8 @@ struct security_operations {
int (*path_link) (struct dentry *old_dentry, struct path *new_dir,
struct dentry *new_dentry);
int (*path_rename) (struct path *old_dir, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry);
+ struct path *new_dir, struct dentry *new_dentry,
+ unsigned int flags);
int (*path_chmod) (struct path *path, umode_t mode);
int (*path_chown) (struct path *path, kuid_t uid, kgid_t gid);
int (*path_chroot) (struct path *path);
@@ -1514,7 +1517,8 @@ struct security_operations {
int (*inode_mknod) (struct inode *dir, struct dentry *dentry,
umode_t mode, dev_t dev);
int (*inode_rename) (struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry);
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags);
int (*inode_readlink) (struct dentry *dentry);
int (*inode_follow_link) (struct dentry *dentry, struct nameidata *nd);
int (*inode_permission) (struct inode *inode, int mask);
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 4257b7e..2afa7c5 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -315,7 +315,8 @@ static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
}

static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry)
+ struct path *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct aa_profile *profile;
int error = 0;
diff --git a/security/capability.c b/security/capability.c
index 8b4f24a..ab2f231 100644
--- a/security/capability.c
+++ b/security/capability.c
@@ -176,7 +176,8 @@ static int cap_inode_mknod(struct inode *inode, struct dentry *dentry,
}

static int cap_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
- struct inode *new_inode, struct dentry *new_dentry)
+ struct inode *new_inode, struct dentry *new_dentry,
+ unsigned int flags)
{
return 0;
}
@@ -280,7 +281,8 @@ static int cap_path_link(struct dentry *old_dentry, struct path *new_dir,
}

static int cap_path_rename(struct path *old_path, struct dentry *old_dentry,
- struct path *new_path, struct dentry *new_dentry)
+ struct path *new_path, struct dentry *new_dentry,
+ unsigned int flags)
{
return 0;
}
diff --git a/security/security.c b/security/security.c
index 3dd2258..f90ac9b 100644
--- a/security/security.c
+++ b/security/security.c
@@ -440,15 +440,8 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;

- if (flags & RENAME_EXCHANGE) {
- int err = security_ops->path_rename(new_dir, new_dentry,
- old_dir, old_dentry);
- if (err)
- return err;
- }
-
return security_ops->path_rename(old_dir, old_dentry, new_dir,
- new_dentry);
+ new_dentry, flags);
}
EXPORT_SYMBOL(security_path_rename);

@@ -540,15 +533,8 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;

- if (flags & RENAME_EXCHANGE) {
- int err = security_ops->inode_rename(new_dir, new_dentry,
- old_dir, old_dentry);
- if (err)
- return err;
- }
-
return security_ops->inode_rename(old_dir, old_dentry,
- new_dir, new_dentry);
+ new_dir, new_dentry, flags);
}

int security_inode_readlink(struct dentry *dentry)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 3219560..fffd458 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2714,9 +2714,13 @@ static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t
}

static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
- struct inode *new_inode, struct dentry *new_dentry)
+ struct inode *new_inode, struct dentry *new_dentry,
+ unsigned int flags)
{
- return may_rename(old_inode, old_dentry, new_inode, new_dentry);
+ int err = may_rename(old_inode, old_dentry, new_inode, new_dentry);
+ if (!err && (flags & RENAME_EXCHANGE))
+ err = may_rename(new_inode, new_dentry, old_inode, old_dentry);
+ return err;
}

static int selinux_inode_readlink(struct dentry *dentry)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index d814e35..623fce6 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -697,6 +697,7 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry)
* @old_dentry: unused
* @new_inode: the new directory
* @new_dentry: unused
+ * @flags: rename flags
*
* Read and write access is required on both the old and
* new directories.
@@ -706,7 +707,8 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry)
static int smack_inode_rename(struct inode *old_inode,
struct dentry *old_dentry,
struct inode *new_inode,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ unsigned int flags)
{
int rc;
char *isp;
diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
index f0b756e..ac7dd97 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -287,13 +287,15 @@ static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
* @old_dentry: Pointer to "struct dentry".
* @new_parent: Pointer to "struct path".
* @new_dentry: Pointer to "struct dentry".
+ * @flags: Rename flags.
*
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_path_rename(struct path *old_parent,
struct dentry *old_dentry,
struct path *new_parent,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ unsigned int flags)
{
struct path path1 = { old_parent->mnt, old_dentry };
struct path path2 = { new_parent->mnt, new_dentry };

2014-01-14 22:12:01

by Tetsuo Handa

[permalink] [raw]
Subject: Re: [PATCH 04/11] vfs: add renameat2 syscall

Miklos Szeredi wrote:
> +
> + if (flags)
> + return -EOPNOTSUPP;
> +

If (at least for now) only ext4 interprets renameat2() flags,
I think adding a new member to "struct inode_operations" and
check it like

if (!old_dir->i_op->swapname)
return -EOPNOTSUPP;

or

if (!(old_dir->i_op->supported_features & flags))
return -EOPNOTSUPP;

earlier is smarter than scattering


if (flags)
return -EOPNOTSUPP;

into individual rename function.
If we do above change, LSM can omit checking permission for cross rename
operation which after all fails with -EOPNOTSUPP.

2014-01-15 10:10:16

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 00/11] cross rename v3

On Wed, Jan 15, 2014 at 05:53:36AM +0900, Tetsuo Handa wrote:
> Miklos, can you insert below change between a patch which defines
> RENAME_EXCHANGE and a patch which implements RENAME_EXCHANGE functionality?

I don't really want to add this to the series. This patch actually *breaks*
tomoyo and apparmor, AFAICS, so it makes little sense on its own.

Could you please prepare a separate series, and you can submit that right after
the cross rename patches go in.

Thanks,
Miklos

>
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 95cfccc..dbd05ca 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -446,6 +446,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
> * @old_dentry contains the dentry structure of the old link.
> * @new_dir contains the inode structure for parent of the new link.
> * @new_dentry contains the dentry structure of the new link.
> + * @flags contains rename flags.
> * Return 0 if permission is granted.
> * @path_rename:
> * Check for permission to rename a file or directory.
> @@ -453,6 +454,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
> * @old_dentry contains the dentry structure of the old link.
> * @new_dir contains the path structure for parent of the new link.
> * @new_dentry contains the dentry structure of the new link.
> + * @flags contains rename flags.
> * Return 0 if permission is granted.
> * @path_chmod:
> * Check for permission to change DAC's permission of a file or directory.
> @@ -1491,7 +1493,8 @@ struct security_operations {
> int (*path_link) (struct dentry *old_dentry, struct path *new_dir,
> struct dentry *new_dentry);
> int (*path_rename) (struct path *old_dir, struct dentry *old_dentry,
> - struct path *new_dir, struct dentry *new_dentry);
> + struct path *new_dir, struct dentry *new_dentry,
> + unsigned int flags);
> int (*path_chmod) (struct path *path, umode_t mode);
> int (*path_chown) (struct path *path, kuid_t uid, kgid_t gid);
> int (*path_chroot) (struct path *path);
> @@ -1514,7 +1517,8 @@ struct security_operations {
> int (*inode_mknod) (struct inode *dir, struct dentry *dentry,
> umode_t mode, dev_t dev);
> int (*inode_rename) (struct inode *old_dir, struct dentry *old_dentry,
> - struct inode *new_dir, struct dentry *new_dentry);
> + struct inode *new_dir, struct dentry *new_dentry,
> + unsigned int flags);
> int (*inode_readlink) (struct dentry *dentry);
> int (*inode_follow_link) (struct dentry *dentry, struct nameidata *nd);
> int (*inode_permission) (struct inode *inode, int mask);
> diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
> index 4257b7e..2afa7c5 100644
> --- a/security/apparmor/lsm.c
> +++ b/security/apparmor/lsm.c
> @@ -315,7 +315,8 @@ static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
> }
>
> static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
> - struct path *new_dir, struct dentry *new_dentry)
> + struct path *new_dir, struct dentry *new_dentry,
> + unsigned int flags)
> {
> struct aa_profile *profile;
> int error = 0;
> diff --git a/security/capability.c b/security/capability.c
> index 8b4f24a..ab2f231 100644
> --- a/security/capability.c
> +++ b/security/capability.c
> @@ -176,7 +176,8 @@ static int cap_inode_mknod(struct inode *inode, struct dentry *dentry,
> }
>
> static int cap_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
> - struct inode *new_inode, struct dentry *new_dentry)
> + struct inode *new_inode, struct dentry *new_dentry,
> + unsigned int flags)
> {
> return 0;
> }
> @@ -280,7 +281,8 @@ static int cap_path_link(struct dentry *old_dentry, struct path *new_dir,
> }
>
> static int cap_path_rename(struct path *old_path, struct dentry *old_dentry,
> - struct path *new_path, struct dentry *new_dentry)
> + struct path *new_path, struct dentry *new_dentry,
> + unsigned int flags)
> {
> return 0;
> }
> diff --git a/security/security.c b/security/security.c
> index 3dd2258..f90ac9b 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -440,15 +440,8 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
> (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
> return 0;
>
> - if (flags & RENAME_EXCHANGE) {
> - int err = security_ops->path_rename(new_dir, new_dentry,
> - old_dir, old_dentry);
> - if (err)
> - return err;
> - }
> -
> return security_ops->path_rename(old_dir, old_dentry, new_dir,
> - new_dentry);
> + new_dentry, flags);
> }
> EXPORT_SYMBOL(security_path_rename);
>
> @@ -540,15 +533,8 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
> (new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
> return 0;
>
> - if (flags & RENAME_EXCHANGE) {
> - int err = security_ops->inode_rename(new_dir, new_dentry,
> - old_dir, old_dentry);
> - if (err)
> - return err;
> - }
> -
> return security_ops->inode_rename(old_dir, old_dentry,
> - new_dir, new_dentry);
> + new_dir, new_dentry, flags);
> }
>
> int security_inode_readlink(struct dentry *dentry)
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 3219560..fffd458 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -2714,9 +2714,13 @@ static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t
> }
>
> static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
> - struct inode *new_inode, struct dentry *new_dentry)
> + struct inode *new_inode, struct dentry *new_dentry,
> + unsigned int flags)
> {
> - return may_rename(old_inode, old_dentry, new_inode, new_dentry);
> + int err = may_rename(old_inode, old_dentry, new_inode, new_dentry);
> + if (!err && (flags & RENAME_EXCHANGE))
> + err = may_rename(new_inode, new_dentry, old_inode, old_dentry);
> + return err;
> }
>
> static int selinux_inode_readlink(struct dentry *dentry)
> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
> index d814e35..623fce6 100644
> --- a/security/smack/smack_lsm.c
> +++ b/security/smack/smack_lsm.c
> @@ -697,6 +697,7 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry)
> * @old_dentry: unused
> * @new_inode: the new directory
> * @new_dentry: unused
> + * @flags: rename flags
> *
> * Read and write access is required on both the old and
> * new directories.
> @@ -706,7 +707,8 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry)
> static int smack_inode_rename(struct inode *old_inode,
> struct dentry *old_dentry,
> struct inode *new_inode,
> - struct dentry *new_dentry)
> + struct dentry *new_dentry,
> + unsigned int flags)
> {
> int rc;
> char *isp;
> diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
> index f0b756e..ac7dd97 100644
> --- a/security/tomoyo/tomoyo.c
> +++ b/security/tomoyo/tomoyo.c
> @@ -287,13 +287,15 @@ static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
> * @old_dentry: Pointer to "struct dentry".
> * @new_parent: Pointer to "struct path".
> * @new_dentry: Pointer to "struct dentry".
> + * @flags: Rename flags.
> *
> * Returns 0 on success, negative value otherwise.
> */
> static int tomoyo_path_rename(struct path *old_parent,
> struct dentry *old_dentry,
> struct path *new_parent,
> - struct dentry *new_dentry)
> + struct dentry *new_dentry,
> + unsigned int flags)
> {
> struct path path1 = { old_parent->mnt, old_dentry };
> struct path path2 = { new_parent->mnt, new_dentry };

2014-01-15 10:29:44

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 04/11] vfs: add renameat2 syscall

On Wed, Jan 15, 2014 at 07:11:16AM +0900, Tetsuo Handa wrote:
> Miklos Szeredi wrote:
> > +
> > + if (flags)
> > + return -EOPNOTSUPP;
> > +
>
> If (at least for now) only ext4 interprets renameat2() flags,
> I think adding a new member to "struct inode_operations" and
> check it like
>
> if (!old_dir->i_op->swapname)
> return -EOPNOTSUPP;
>
> or

This is not just about swapname, but other rename variants too (non-overwriting
rename is also included in the patchset).

>
> if (!(old_dir->i_op->supported_features & flags))
> return -EOPNOTSUPP;

Or rather old_dir->i_sb->s_type->fs_flags. We access it due to
FS_RENAME_DOES_D_MOVE anyway.

I like this variant, because now filesystems need to explicitly add a flag to
*enable* the functionality and not a check to disable it.

Objections?

Thanks,
Miklos

2014-01-15 13:50:24

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 04/11] vfs: add renameat2 syscall

On Wed, Jan 15, 2014 at 11:30:32AM +0100, Miklos Szeredi wrote:
> On Wed, Jan 15, 2014 at 07:11:16AM +0900, Tetsuo Handa wrote:

> >
> > if (!(old_dir->i_op->supported_features & flags))
> > return -EOPNOTSUPP;
>
> Or rather old_dir->i_sb->s_type->fs_flags. We access it due to
> FS_RENAME_DOES_D_MOVE anyway.
>
> I like this variant, because now filesystems need to explicitly add a flag to
> *enable* the functionality and not a check to disable it.

Since there were a few changes to the series since posting, I pushed the updated
patchset to

git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git cross-rename

Thanks for the reviews.

Thanks,
Miklos
----

Miklos Szeredi (11):
vfs: add d_is_dir()
vfs: rename: move d_move() up
vfs: rename: use common code for dir and non-dir
vfs: add renameat2 syscall
vfs: add RENAME_NOREPLACE flag
security: add flags to rename hooks
vfs: add cross-rename
ext4: rename: create ext4_renament structure for local vars
ext4: rename: move EMLINK check up
ext4: rename: split out helper functions
ext4: add cross rename support

---
Documentation/filesystems/Locking | 2 +-
Documentation/filesystems/vfs.txt | 4 +-
arch/x86/syscalls/syscall_64.tbl | 1 +
.../lustre/lustre/include/linux/lustre_compat25.h | 4 +-
drivers/staging/lustre/lustre/llite/namei.c | 3 +-
drivers/staging/lustre/lustre/lvfs/lvfs_linux.c | 2 +-
fs/9p/v9fs.h | 3 +-
fs/9p/vfs_inode.c | 4 +-
fs/affs/affs.h | 3 +-
fs/affs/namei.c | 3 +-
fs/afs/dir.c | 6 +-
fs/bad_inode.c | 3 +-
fs/bfs/dir.c | 3 +-
fs/btrfs/inode.c | 3 +-
fs/cachefiles/namei.c | 4 +-
fs/ceph/dir.c | 3 +-
fs/cifs/cifsfs.h | 2 +-
fs/cifs/inode.c | 3 +-
fs/coda/dir.c | 8 +-
fs/dcache.c | 46 ++-
fs/debugfs/inode.c | 2 +-
fs/ecryptfs/inode.c | 5 +-
fs/exofs/namei.c | 3 +-
fs/ext2/namei.c | 5 +-
fs/ext3/namei.c | 5 +-
fs/ext4/namei.c | 396 ++++++++++++++-------
fs/ext4/super.c | 6 +-
fs/f2fs/namei.c | 3 +-
fs/fat/namei_msdos.c | 3 +-
fs/fat/namei_vfat.c | 3 +-
fs/fuse/dir.c | 3 +-
fs/gfs2/inode.c | 3 +-
fs/hfs/dir.c | 3 +-
fs/hfsplus/dir.c | 3 +-
fs/hostfs/hostfs_kern.c | 5 +-
fs/hpfs/namei.c | 3 +-
fs/jffs2/dir.c | 5 +-
fs/jfs/namei.c | 3 +-
fs/libfs.c | 3 +-
fs/logfs/dir.c | 3 +-
fs/minix/namei.c | 5 +-
fs/namei.c | 317 +++++++++--------
fs/ncpfs/dir.c | 5 +-
fs/nfs/dir.c | 3 +-
fs/nfs/internal.h | 3 +-
fs/nfsd/vfs.c | 2 +-
fs/nilfs2/namei.c | 3 +-
fs/ocfs2/namei.c | 3 +-
fs/omfs/dir.c | 3 +-
fs/reiserfs/namei.c | 3 +-
fs/sysv/namei.c | 5 +-
fs/ubifs/dir.c | 3 +-
fs/udf/namei.c | 3 +-
fs/ufs/namei.c | 3 +-
fs/xfs/xfs_iops.c | 3 +-
include/linux/dcache.h | 8 +-
include/linux/fs.h | 7 +-
include/linux/security.h | 12 +-
include/uapi/linux/fs.h | 3 +
kernel/cgroup.c | 5 +-
mm/shmem.c | 2 +-
security/security.c | 22 +-
62 files changed, 631 insertions(+), 367 deletions(-)

2014-01-15 18:20:07

by J. Bruce Fields

[permalink] [raw]
Subject: Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag

On Wed, Jan 08, 2014 at 11:10:09PM +0100, Miklos Szeredi wrote:
> From: Miklos Szeredi <[email protected]>
>
> If this flag is specified and the target of the rename exists then the
> rename syscall fails with EEXIST.

Why is this useful?

(I'm sure it is, it'd just be useful to have the reasons recorded
someplace.)

> The VFS does the existence checking, so it is trivial to enable for most
> local filesystems. This patch only enables it in ext4.
>
> For network filesystems the VFS check is not enough as there may be a race
> between a remote create and the rename, so these filesystems need to handle
> this flag in their ->rename() implementations to ensure atomicity.

Till that's done this should probably result in -EOPNOTSUPP on those
filesystems?

I think this would need new protocol in the NFS case.

--b.

>
> Suggested-by: Andy Lutomirski <[email protected]>
> Signed-off-by: Miklos Szeredi <[email protected]>
> ---
> fs/ext4/namei.c | 2 +-
> fs/namei.c | 21 +++++++++++++--------
> include/uapi/linux/fs.h | 2 ++
> 3 files changed, 16 insertions(+), 9 deletions(-)
>
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index 08c40f4e7eed..e0129b6e74cf 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
> @@ -3021,7 +3021,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
> int inlined = 0, new_inlined = 0;
> struct ext4_dir_entry_2 *parent_de;
>
> - if (flags)
> + if (flags & ~RENAME_NOREPLACE)
> return -EOPNOTSUPP;
>
> dquot_initialize(old_dir);
> diff --git a/fs/namei.c b/fs/namei.c
> index 593673fcbfef..f9cf3020394c 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -4123,7 +4123,7 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
> bool should_retry = false;
> int error;
>
> - if (flags)
> + if (flags & ~RENAME_NOREPLACE)
> return -EOPNOTSUPP;
>
> retry:
> @@ -4149,6 +4149,8 @@ retry:
> goto exit2;
>
> new_dir = newnd.path.dentry;
> + if (flags & RENAME_NOREPLACE)
> + error = -EEXIST;
> if (newnd.last_type != LAST_NORM)
> goto exit2;
>
> @@ -4171,22 +4173,25 @@ retry_deleg:
> error = -ENOENT;
> if (d_is_negative(old_dentry))
> goto exit4;
> + new_dentry = lookup_hash(&newnd);
> + error = PTR_ERR(new_dentry);
> + if (IS_ERR(new_dentry))
> + goto exit4;
> + error = -EEXIST;
> + if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry))
> + goto exit5;
> /* unless the source is a directory trailing slashes give -ENOTDIR */
> if (!d_is_dir(old_dentry)) {
> error = -ENOTDIR;
> if (oldnd.last.name[oldnd.last.len])
> - goto exit4;
> + goto exit5;
> if (newnd.last.name[newnd.last.len])
> - goto exit4;
> + goto exit5;
> }
> /* source should not be ancestor of target */
> error = -EINVAL;
> if (old_dentry == trap)
> - goto exit4;
> - new_dentry = lookup_hash(&newnd);
> - error = PTR_ERR(new_dentry);
> - if (IS_ERR(new_dentry))
> - goto exit4;
> + goto exit5;
> /* target should not be an ancestor of source */
> error = -ENOTEMPTY;
> if (new_dentry == trap)
> diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
> index 6c28b61bb690..9250f4dd7d96 100644
> --- a/include/uapi/linux/fs.h
> +++ b/include/uapi/linux/fs.h
> @@ -35,6 +35,8 @@
> #define SEEK_HOLE 4 /* seek to the next hole */
> #define SEEK_MAX SEEK_HOLE
>
> +#define RENAME_NOREPLACE (1 << 0) /* Don't overwrite target */
> +
> struct fstrim_range {
> __u64 start;
> __u64 len;
> --
> 1.8.1.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html

2014-01-15 18:23:58

by J. Bruce Fields

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Mon, Jan 13, 2014 at 01:25:17PM +0100, Jan Kara wrote:
> I think this deserves a comment that RENAME_EXCHANGE expects both source
> and target to exist... I'm always pondering about this condition before I
> realize that.

Do you have a man page update somewhere for the two new flags?

--b.

2014-01-15 18:26:47

by Andy Lutomirski

[permalink] [raw]
Subject: Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag

On Wed, Jan 15, 2014 at 10:19 AM, J. Bruce Fields <[email protected]> wrote:
> On Wed, Jan 08, 2014 at 11:10:09PM +0100, Miklos Szeredi wrote:
>> From: Miklos Szeredi <[email protected]>
>>
>> If this flag is specified and the target of the rename exists then the
>> rename syscall fails with EEXIST.
>
> Why is this useful?

The trivial answer: to eliminate the race condition from 'mv -i'.

Another answer: there's a common pattern to atomically create a file
with contents: open a temporary file, write to it, optionally fsync
it, close it, then link(2) it to the final name, then unlink the
temporary file.

The reason to use link(2) is because it won't silently clobber the destination.

This is annoying:
- It requires an extra system call that shouldn't be necessary.
- It doesn't work on (IMO sensible) filesystems that don't support
hard links (e.g. vfat).
- It's not atomic -- there's an intermediate state where both files exist.
- It's ugly.

The new rename flag will make this totally sensible.

To be fair, on new enough kernels, you can also use O_TMPFILE and
linkat to achieve the same thing even more cleanly.

--Andy

2014-01-15 18:31:36

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Wed, Jan 15, 2014 at 7:23 PM, J. Bruce Fields <[email protected]> wrote:
> On Mon, Jan 13, 2014 at 01:25:17PM +0100, Jan Kara wrote:
>> I think this deserves a comment that RENAME_EXCHANGE expects both source
>> and target to exist... I'm always pondering about this condition before I
>> realize that.
>
> Do you have a man page update somewhere for the two new flags?

Not yet. I'll work on that tomorrow.

Thanks,
Miklos

2014-01-15 18:35:08

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag

On Wed, Jan 15, 2014 at 7:19 PM, J. Bruce Fields <[email protected]> wrote:
> On Wed, Jan 08, 2014 at 11:10:09PM +0100, Miklos Szeredi wrote:
>> From: Miklos Szeredi <[email protected]>
>>
>> If this flag is specified and the target of the rename exists then the
>> rename syscall fails with EEXIST.
>
> Why is this useful?
>
> (I'm sure it is, it'd just be useful to have the reasons recorded
> someplace.)
>
>> The VFS does the existence checking, so it is trivial to enable for most
>> local filesystems. This patch only enables it in ext4.
>>
>> For network filesystems the VFS check is not enough as there may be a race
>> between a remote create and the rename, so these filesystems need to handle
>> this flag in their ->rename() implementations to ensure atomicity.
>
> Till that's done this should probably result in -EOPNOTSUPP on those
> filesystems?
>
> I think this would need new protocol in the NFS case.

Yes, it needs to be enabled on a case-by-case basis. Only enabled for
ext4 now, but trivial to do for most fs.

Thanks,
Miklos

2014-01-15 23:31:57

by J. Bruce Fields

[permalink] [raw]
Subject: Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag

On Wed, Jan 15, 2014 at 07:35:04PM +0100, Miklos Szeredi wrote:
> On Wed, Jan 15, 2014 at 7:19 PM, J. Bruce Fields <[email protected]> wrote:
> > On Wed, Jan 08, 2014 at 11:10:09PM +0100, Miklos Szeredi wrote:
> >> From: Miklos Szeredi <[email protected]>
> >>
> >> If this flag is specified and the target of the rename exists then the
> >> rename syscall fails with EEXIST.
> >
> > Why is this useful?
> >
> > (I'm sure it is, it'd just be useful to have the reasons recorded
> > someplace.)
> >
> >> The VFS does the existence checking, so it is trivial to enable for most
> >> local filesystems. This patch only enables it in ext4.
> >>
> >> For network filesystems the VFS check is not enough as there may be a race
> >> between a remote create and the rename, so these filesystems need to handle
> >> this flag in their ->rename() implementations to ensure atomicity.
> >
> > Till that's done this should probably result in -EOPNOTSUPP on those
> > filesystems?
> >
> > I think this would need new protocol in the NFS case.
>
> Yes, it needs to be enabled on a case-by-case basis.

Oh, right, I missed all those

if (flags)
return -EOPNOTSUPP;

added with "vfs: add renameat2 syscall". Apologies.

> Only enabled for
> ext4 now, but trivial to do for most fs.

Got it, thanks!

--b.

2014-01-15 23:33:18

by J. Bruce Fields

[permalink] [raw]
Subject: Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag

On Wed, Jan 15, 2014 at 10:26:23AM -0800, Andy Lutomirski wrote:
> On Wed, Jan 15, 2014 at 10:19 AM, J. Bruce Fields <[email protected]> wrote:
> > On Wed, Jan 08, 2014 at 11:10:09PM +0100, Miklos Szeredi wrote:
> >> From: Miklos Szeredi <[email protected]>
> >>
> >> If this flag is specified and the target of the rename exists then the
> >> rename syscall fails with EEXIST.
> >
> > Why is this useful?
>
> The trivial answer: to eliminate the race condition from 'mv -i'.
>
> Another answer: there's a common pattern to atomically create a file
> with contents: open a temporary file, write to it, optionally fsync
> it, close it, then link(2) it to the final name, then unlink the
> temporary file.
>
> The reason to use link(2) is because it won't silently clobber the destination.
>
> This is annoying:
> - It requires an extra system call that shouldn't be necessary.
> - It doesn't work on (IMO sensible) filesystems that don't support
> hard links (e.g. vfat).
> - It's not atomic -- there's an intermediate state where both files exist.
> - It's ugly.
>
> The new rename flag will make this totally sensible.

Makes sense, thanks! Could that just get cut-n-pasted into the
changelog? (Unless there's some obvious other place to put it.)

--b.

>
> To be fair, on new enough kernels, you can also use O_TMPFILE and
> linkat to achieve the same thing even more cleanly.

2014-01-16 10:45:16

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag

On Thu, Jan 16, 2014 at 12:33 AM, J. Bruce Fields <[email protected]> wrote:

> Makes sense, thanks! Could that just get cut-n-pasted into the
> changelog? (Unless there's some obvious other place to put it.)

Done and pushed to the git tree.

Thanks,
Miklos

2014-01-16 10:53:24

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

> On Wed, Jan 15, 2014 at 7:23 PM, J. Bruce Fields <[email protected]> wrote:
> > Do you have a man page update somewhere for the two new flags?

Here's the updated man page (and attached the patch)

Michael, could you please review the interface?

I forgot to CC you when posing the patch series. I can resend it if you want,
or you can fetch the latest version of the cross-rename series from:

git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git cross-rename

Thanks,
Miklos
----

RENAMEAT(2) Linux Programmer's Manual RENAMEAT(2)



NAME
renameat, renameat2 - rename a file relative to directory file descrip-
tors

SYNOPSIS
#include <fcntl.h> /* Definition of AT_* constants */
#include <stdio.h>

int renameat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath);

int renameat2(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath, unsigned int flags);

Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

renameat():
Since glibc 2.10:
_XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L
Before glibc 2.10:
_ATFILE_SOURCE

DESCRIPTION
The renameat() and renameat2() system calls operate in exactly the same
way as rename(2), except for the differences described in this manual
page.

If the pathname given in oldpath is relative, then it is interpreted
relative to the directory referred to by the file descriptor olddirfd
(rather than relative to the current working directory of the calling
process, as is done by rename(2) for a relative pathname).

If oldpath is relative and olddirfd is the special value AT_FDCWD, then
oldpath is interpreted relative to the current working directory of the
calling process (like rename(2)).

If oldpath is absolute, then olddirfd is ignored.

The interpretation of newpath is as for oldpath, except that a relative
pathname is interpreted relative to the directory referred to by the
file descriptor newdirfd.

renameat2() has an additional flags argument. renameat2() call with a
zero flags argument is equivalent to renameat().

The flags argument is a bitfield consisting of zero or more of the fol-
lowing constants defined in <linux/fs.h>:

RENAME_NOREPLACE
Don't overwrite the target of the rename. Return an error if
the target would be overwritten.

RENAME_EXCHANGE
Atomically exchange the source and destination. Both must exist
but may be of a different type (e.g. one a non-empty directory
and the other a symbolic link).

RETURN VALUE
On success, renameat() and renameat2() return 0. On error, -1 is
returned and errno is set to indicate the error.

ERRORS
The same errors that occur for rename(2) can also occur for renameat()
and renameat2(). The following additional errors can occur for
renameat() and renameat2():

EBADF olddirfd or newdirfd is not a valid file descriptor.

ENOTDIR
oldpath is relative and olddirfd is a file descriptor referring
to a file other than a directory; or similar for newpath and
newdirfd

The following additional errors are defined for renameat2():

EOPNOTSUPP
The filesystem does not support a flag in flags

EINVAL Invalid combination of flags

EEXIST flags contain RENAME_NOREPLACE and the target of the rename
exists

ENOENT flags contain RENAME_EXCHANGE and the target of the rename does
not exist

VERSIONS
renameat() was added to Linux in kernel 2.6.16; library support was
added to glibc in version 2.4.

CONFORMING TO
renameat() is specified in POSIX.1-2008.

NOTES
See openat(2) for an explanation of the need for renameat().

SEE ALSO
openat(2), rename(2), path_resolution(7)



Linux 2012-05-04 RENAMEAT(2)


Attachments:
(No filename) (4.17 kB)
renameat.2.patch (2.95 kB)
Download all attachments

2014-01-16 14:49:05

by J. Bruce Fields

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Thu, Jan 16, 2014 at 11:54:06AM +0100, Miklos Szeredi wrote:
> > On Wed, Jan 15, 2014 at 7:23 PM, J. Bruce Fields <[email protected]> wrote:
> > > Do you have a man page update somewhere for the two new flags?
>
> Here's the updated man page (and attached the patch)

Looks good to me. Total nits:

> RENAME_NOREPLACE
> Don't overwrite the target of the rename. Return an error if
> the target would be overwritten.

s/Return an error/Fail with EEXIST/?

(Since it doesn't literally return the error.)

> ENOENT flags contain RENAME_EXCHANGE and the target of the rename does
> not exist

s/the target of the rename/either of the two files/

--b.

Subject: Re: [PATCH 11/11] ext4: add cross rename support

Hi Miklos,

A few comments below, including one piece in the code that really must be fixed.

On 01/16/2014 11:54 PM, Miklos Szeredi wrote:
>> On Wed, Jan 15, 2014 at 7:23 PM, J. Bruce Fields <[email protected]> wrote:
>>> Do you have a man page update somewhere for the two new flags?
>
> Here's the updated man page (and attached the patch)
>
> Michael, could you please review the interface?
>
> I forgot to CC you when posing the patch series. I can resend it if you want,
> or you can fetch the latest version of the cross-rename series from:
>
> git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git cross-rename

[...]

> renameat2() has an additional flags argument. renameat2() call with a
> zero flags argument is equivalent to renameat().
>
> The flags argument is a bitfield consisting of zero or more of the fol-
> lowing constants defined in <linux/fs.h>:
>
> RENAME_NOREPLACE
> Don't overwrite the target of the rename. Return an error if
> the target would be overwritten.
>
> RENAME_EXCHANGE
> Atomically exchange the source and destination. Both must exist
> but may be of a different type (e.g. one a non-empty directory
> and the other a symbolic link).

Somewhere here it would be good to explain the consequences if

(flags & (RENAME_NOREPLACE | RENAME_EXCHANGE)) ==
(RENAME_NOREPLACE | RENAME_EXCHANGE)

Okay -- it's EINVAL, but here the man page text should say something like
"these two flags can't be specified together", right?

> RETURN VALUE
> On success, renameat() and renameat2() return 0. On error, -1 is
> returned and errno is set to indicate the error.
>
> ERRORS
> The same errors that occur for rename(2) can also occur for renameat()
> and renameat2(). The following additional errors can occur for
> renameat() and renameat2():
>
> EBADF olddirfd or newdirfd is not a valid file descriptor.
>
> ENOTDIR
> oldpath is relative and olddirfd is a file descriptor referring
> to a file other than a directory; or similar for newpath and
> newdirfd
>
> The following additional errors are defined for renameat2():
>
> EOPNOTSUPP
> The filesystem does not support a flag in flags

This is not the usual error for an invalid bit flag. Please make it EINVAL.
(See the man pages for the *at() calls that have a 'flags" argument.)

> EINVAL Invalid combination of flags

(This is okay.)

Looks otherwise okay to me (and I agree with Bruce's comments).

Cheers,

Michael



--
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/

2014-01-17 14:40:39

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

Hi Michael,

Thanks for the review. I updated the code and man page based on your and
Bruce's comments.

The code changes are pushed to the git tree and the updated man page is below.

Thanks,
Miklos
----


RENAMEAT(2) Linux Programmer's Manual RENAMEAT(2)



NAME
renameat, renameat2 - rename a file relative to directory file descrip-
tors

SYNOPSIS
#include <fcntl.h> /* Definition of AT_* constants */
#include <stdio.h>

int renameat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath);

int renameat2(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath, unsigned int flags);

Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

renameat():
Since glibc 2.10:
_XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L
Before glibc 2.10:
_ATFILE_SOURCE

DESCRIPTION
The renameat() and renameat2() system calls operate in exactly the same
way as rename(2), except for the differences described in this manual
page.

If the pathname given in oldpath is relative, then it is interpreted
relative to the directory referred to by the file descriptor olddirfd
(rather than relative to the current working directory of the calling
process, as is done by rename(2) for a relative pathname).

If oldpath is relative and olddirfd is the special value AT_FDCWD, then
oldpath is interpreted relative to the current working directory of the
calling process (like rename(2)).

If oldpath is absolute, then olddirfd is ignored.

The interpretation of newpath is as for oldpath, except that a relative
pathname is interpreted relative to the directory referred to by the
file descriptor newdirfd.

renameat2() has an additional flags argument. renameat2() call with a
zero flags argument is equivalent to renameat().

The flags argument is a bitfield consisting of zero or more of the fol-
lowing constants defined in <linux/fs.h>:

RENAME_NOREPLACE
Don't overwrite the target of the rename. Fail if the target
would be overwritten.

RENAME_EXCHANGE
Atomically exchange the source and destination. Both must exist
but may be of a different type (e.g. one a non-empty directory
and the other a symbolic link). Specifying this flag together
with RENAME_NOREPLACE is invalid.

RETURN VALUE
On success, renameat() and renameat2() return 0. On error, -1 is
returned and errno is set to indicate the error.

ERRORS
The same errors that occur for rename(2) can also occur for renameat()
and renameat2(). The following additional errors can occur for
renameat() and renameat2():

EBADF olddirfd or newdirfd is not a valid file descriptor.

ENOTDIR
oldpath is relative and olddirfd is a file descriptor referring
to a file other than a directory; or similar for newpath and
newdirfd

The following additional errors are defined for renameat2():

EINVAL The filesystem does not support a flag in flags

EINVAL Invalid combination of flags

EEXIST flags contain RENAME_NOREPLACE and the target of the rename
exists

ENOENT flags contain RENAME_EXCHANGE and either of the two files does
not exist

VERSIONS
renameat() was added to Linux in kernel 2.6.16; library support was
added to glibc in version 2.4.

CONFORMING TO
renameat() is specified in POSIX.1-2008.

NOTES
See openat(2) for an explanation of the need for renameat().

SEE ALSO
openat(2), rename(2), path_resolution(7)



Linux 2012-05-04 RENAMEAT(2)

2014-01-17 22:09:07

by J. Bruce Fields

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Fri, Jan 17, 2014 at 11:53:07PM +1300, Michael Kerrisk (man-pages) wrote:
> Hi Miklos,
>
> A few comments below, including one piece in the code that really must be fixed.
>
> On 01/16/2014 11:54 PM, Miklos Szeredi wrote:
> >> On Wed, Jan 15, 2014 at 7:23 PM, J. Bruce Fields <[email protected]> wrote:
> >>> Do you have a man page update somewhere for the two new flags?
> >
> > Here's the updated man page (and attached the patch)
> >
> > Michael, could you please review the interface?
> >
> > I forgot to CC you when posing the patch series. I can resend it if you want,
> > or you can fetch the latest version of the cross-rename series from:
> >
> > git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git cross-rename
>
> [...]
>
> > renameat2() has an additional flags argument. renameat2() call with a
> > zero flags argument is equivalent to renameat().
> >
> > The flags argument is a bitfield consisting of zero or more of the fol-
> > lowing constants defined in <linux/fs.h>:
> >
> > RENAME_NOREPLACE
> > Don't overwrite the target of the rename. Return an error if
> > the target would be overwritten.
> >
> > RENAME_EXCHANGE
> > Atomically exchange the source and destination. Both must exist
> > but may be of a different type (e.g. one a non-empty directory
> > and the other a symbolic link).
>
> Somewhere here it would be good to explain the consequences if
>
> (flags & (RENAME_NOREPLACE | RENAME_EXCHANGE)) ==
> (RENAME_NOREPLACE | RENAME_EXCHANGE)
>
> Okay -- it's EINVAL, but here the man page text should say something like
> "these two flags can't be specified together", right?
>
> > RETURN VALUE
> > On success, renameat() and renameat2() return 0. On error, -1 is
> > returned and errno is set to indicate the error.
> >
> > ERRORS
> > The same errors that occur for rename(2) can also occur for renameat()
> > and renameat2(). The following additional errors can occur for
> > renameat() and renameat2():
> >
> > EBADF olddirfd or newdirfd is not a valid file descriptor.
> >
> > ENOTDIR
> > oldpath is relative and olddirfd is a file descriptor referring
> > to a file other than a directory; or similar for newpath and
> > newdirfd
> >
> > The following additional errors are defined for renameat2():
> >
> > EOPNOTSUPP
> > The filesystem does not support a flag in flags
>
> This is not the usual error for an invalid bit flag. Please make it EINVAL.

I agree that EINVAL makes sense for an invalid bit flag.

But renameat2() can also fail when the caller passes a perfectly valid
flags field but the paths resolve to a filesystem that doesn't support
the RENAME_EXCHANGE operation. EOPNOTSUPP looks more appropriate in
that case.

> (See the man pages for the *at() calls that have a 'flags" argument.)

Aren't those flags mostly handled in the vfs? In which case they work
everywhere, so there isn't the same distinction between "flag is
defined" and "behavior requested by flag is unsupported for the given
objects".

--b.

> > EINVAL Invalid combination of flags
>
> (This is okay.)
>
> Looks otherwise okay to me (and I agree with Bruce's comments).
>
> Cheers,
>
> Michael
>
>
>
> --
> Michael Kerrisk
> Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
> Linux/UNIX System Programming Training: http://man7.org/training/
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html

2014-01-18 06:50:00

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Fri, Jan 17, 2014 at 11:08 PM, J. Bruce Fields <[email protected]> wrote:
> On Fri, Jan 17, 2014 at 11:53:07PM +1300, Michael Kerrisk (man-pages) wrote:
>> > The following additional errors are defined for renameat2():
>> >
>> > EOPNOTSUPP
>> > The filesystem does not support a flag in flags
>>
>> This is not the usual error for an invalid bit flag. Please make it EINVAL.
>
> I agree that EINVAL makes sense for an invalid bit flag.
>
> But renameat2() can also fail when the caller passes a perfectly valid
> flags field but the paths resolve to a filesystem that doesn't support
> the RENAME_EXCHANGE operation. EOPNOTSUPP looks more appropriate in
> that case.

OTOH, from the app's perspective, it makes little difference whether a
particular kernel doesn't support the reanameat2 syscall, or it
doesn't support RENAME_FOO flag or if it does support RENAME_FOO but
not in all filesystems. In all those cases it has to just fall back
to something supported and it doesn't matter *why* it wasn't
supported.

Thanks,
Miklos

2014-01-18 10:41:03

by Tetsuo Handa

[permalink] [raw]
Subject: Re: [PATCH 04/11] vfs: add renameat2 syscall

Miklos Szeredi wrote:
> Since there were a few changes to the series since posting, I pushed the updated
> patchset to
>
> git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git cross-rename
>
> Thanks for the reviews.
>
> Thanks,
> Miklos
> ----
>
> Miklos Szeredi (11):
> vfs: add d_is_dir()
> vfs: rename: move d_move() up
> vfs: rename: use common code for dir and non-dir
> vfs: add renameat2 syscall
> vfs: add RENAME_NOREPLACE flag
> security: add flags to rename hooks
> vfs: add cross-rename
> ext4: rename: create ext4_renament structure for local vars
> ext4: rename: move EMLINK check up
> ext4: rename: split out helper functions
> ext4: add cross rename support

Here is a patchset refreshed using the abovementioned tree
for handling the rename flags in each LSM module.

SELinux: Added a few lines.
SMACK: No changes needed.
TOMOYO: Added some lines in order to check swapname permission once
rather than checking rename permission twice.
AppArmor: Using a temporary solution. John Johansen will overwrite this
change in order to avoid re-calculation of pathnames.

LSM users, please review your relevant part in the following changes.
----------------------------------------
>From 15850773c7b6336f1d884c2daa314f4c408d355e Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Sat, 18 Jan 2014 14:01:12 +0900
Subject: [PATCH 1/5] LSM: Pass the rename flags to each LSM module.

For now, security_inode_rename() and security_path_rename() are calling each
LSM module with reversed arguments if the rename flags contain RENAME_EXCHANGE
in order to avoid bypassing LSM module's permission checks. But since LSM
modules can avoid re-calculation if the arguments are known to be reversed,
it is better to pass the rename flags anyway.

security_inode_rename() and security_path_rename() will stop calling each LSM
module with reversed arguments after all LSM modules became ready to handle
the rename flags.

Signed-off-by: Tetsuo Handa <[email protected]>
---
include/linux/security.h | 8 ++++++--
security/apparmor/lsm.c | 3 ++-
security/capability.c | 6 ++++--
security/security.c | 10 ++++++----
security/selinux/hooks.c | 3 ++-
security/smack/smack_lsm.c | 4 +++-
security/tomoyo/tomoyo.c | 4 +++-
7 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/include/linux/security.h b/include/linux/security.h
index 95cfccc..dbd05ca 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -446,6 +446,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
* @old_dentry contains the dentry structure of the old link.
* @new_dir contains the inode structure for parent of the new link.
* @new_dentry contains the dentry structure of the new link.
+ * @flags contains rename flags.
* Return 0 if permission is granted.
* @path_rename:
* Check for permission to rename a file or directory.
@@ -453,6 +454,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
* @old_dentry contains the dentry structure of the old link.
* @new_dir contains the path structure for parent of the new link.
* @new_dentry contains the dentry structure of the new link.
+ * @flags contains rename flags.
* Return 0 if permission is granted.
* @path_chmod:
* Check for permission to change DAC's permission of a file or directory.
@@ -1491,7 +1493,8 @@ struct security_operations {
int (*path_link) (struct dentry *old_dentry, struct path *new_dir,
struct dentry *new_dentry);
int (*path_rename) (struct path *old_dir, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry);
+ struct path *new_dir, struct dentry *new_dentry,
+ unsigned int flags);
int (*path_chmod) (struct path *path, umode_t mode);
int (*path_chown) (struct path *path, kuid_t uid, kgid_t gid);
int (*path_chroot) (struct path *path);
@@ -1514,7 +1517,8 @@ struct security_operations {
int (*inode_mknod) (struct inode *dir, struct dentry *dentry,
umode_t mode, dev_t dev);
int (*inode_rename) (struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry);
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags);
int (*inode_readlink) (struct dentry *dentry);
int (*inode_follow_link) (struct dentry *dentry, struct nameidata *nd);
int (*inode_permission) (struct inode *inode, int mask);
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 4257b7e..2afa7c5 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -315,7 +315,8 @@ static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
}

static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry)
+ struct path *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct aa_profile *profile;
int error = 0;
diff --git a/security/capability.c b/security/capability.c
index 8b4f24a..ab2f231 100644
--- a/security/capability.c
+++ b/security/capability.c
@@ -176,7 +176,8 @@ static int cap_inode_mknod(struct inode *inode, struct dentry *dentry,
}

static int cap_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
- struct inode *new_inode, struct dentry *new_dentry)
+ struct inode *new_inode, struct dentry *new_dentry,
+ unsigned int flags)
{
return 0;
}
@@ -280,7 +281,8 @@ static int cap_path_link(struct dentry *old_dentry, struct path *new_dir,
}

static int cap_path_rename(struct path *old_path, struct dentry *old_dentry,
- struct path *new_path, struct dentry *new_dentry)
+ struct path *new_path, struct dentry *new_dentry,
+ unsigned int flags)
{
return 0;
}
diff --git a/security/security.c b/security/security.c
index 3dd2258..d720afc 100644
--- a/security/security.c
+++ b/security/security.c
@@ -442,13 +442,14 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,

if (flags & RENAME_EXCHANGE) {
int err = security_ops->path_rename(new_dir, new_dentry,
- old_dir, old_dentry);
+ old_dir, old_dentry,
+ flags);
if (err)
return err;
}

return security_ops->path_rename(old_dir, old_dentry, new_dir,
- new_dentry);
+ new_dentry, flags);
}
EXPORT_SYMBOL(security_path_rename);

@@ -542,13 +543,14 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,

if (flags & RENAME_EXCHANGE) {
int err = security_ops->inode_rename(new_dir, new_dentry,
- old_dir, old_dentry);
+ old_dir, old_dentry,
+ flags);
if (err)
return err;
}

return security_ops->inode_rename(old_dir, old_dentry,
- new_dir, new_dentry);
+ new_dir, new_dentry, flags);
}

int security_inode_readlink(struct dentry *dentry)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 57b0b49..c139369 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2731,7 +2731,8 @@ static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t
}

static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
- struct inode *new_inode, struct dentry *new_dentry)
+ struct inode *new_inode, struct dentry *new_dentry,
+ unsigned int flags)
{
return may_rename(old_inode, old_dentry, new_inode, new_dentry);
}
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index b0be893..1cbb73b 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -722,6 +722,7 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry)
* @old_dentry: unused
* @new_inode: the new directory
* @new_dentry: unused
+ * @flags: unused
*
* Read and write access is required on both the old and
* new directories.
@@ -731,7 +732,8 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry)
static int smack_inode_rename(struct inode *old_inode,
struct dentry *old_dentry,
struct inode *new_inode,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ unsigned int flags)
{
int rc;
char *isp;
diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
index f0b756e..ac7dd97 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -287,13 +287,15 @@ static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
* @old_dentry: Pointer to "struct dentry".
* @new_parent: Pointer to "struct path".
* @new_dentry: Pointer to "struct dentry".
+ * @flags: Rename flags.
*
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_path_rename(struct path *old_parent,
struct dentry *old_dentry,
struct path *new_parent,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ unsigned int flags)
{
struct path path1 = { old_parent->mnt, old_dentry };
struct path path2 = { new_parent->mnt, new_dentry };
--
1.7.1
----------------------------------------
>From 3b109949884ef534cbe0f8ddb9115422fd75b67d Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Sat, 18 Jan 2014 14:25:39 +0900
Subject: [PATCH 2/5] SELinux: Handle the rename flags.

For SELinux, the RENAME_EXCHANGE flag means "check permissions with reversed
arguments".

Signed-off-by: Tetsuo Handa <[email protected]>
---
security/selinux/hooks.c | 5 ++++-
1 files changed, 4 insertions(+), 1 deletions(-)

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index c139369..a6ef610 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2734,7 +2734,10 @@ static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dent
struct inode *new_inode, struct dentry *new_dentry,
unsigned int flags)
{
- return may_rename(old_inode, old_dentry, new_inode, new_dentry);
+ int err = may_rename(old_inode, old_dentry, new_inode, new_dentry);
+ if (!err && (flags & RENAME_EXCHANGE))
+ err = may_rename(new_inode, new_dentry, old_inode, old_dentry);
+ return err;
}

static int selinux_inode_readlink(struct dentry *dentry)
--
1.7.1
----------------------------------------
>From d0e400cf5e134cf8b6d06dc6b9c15fa6e6ca007e Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Sat, 18 Jan 2014 14:30:51 +0900
Subject: [PATCH 3/5] AppArmor: Handle the rename flags.

For AppArmor, the RENAME_EXCHANGE flag means "check permissions with reversed
arguments". Future patches will stop re-calculating pathnames.

Signed-off-by: Tetsuo Handa <[email protected]>
---
security/apparmor/lsm.c | 12 ++++++++++++
1 files changed, 12 insertions(+), 0 deletions(-)

diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 2afa7c5..8b04056 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -332,6 +332,7 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
old_dentry->d_inode->i_mode
};

+retry:
error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0,
MAY_READ | AA_MAY_META_READ | MAY_WRITE |
AA_MAY_META_WRITE | AA_MAY_DELETE,
@@ -340,6 +341,17 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
0, MAY_WRITE | AA_MAY_META_WRITE |
AA_MAY_CREATE, &cond);
+ if (!error && (flags & RENAME_EXCHANGE)) {
+ /* Cross rename requires both inodes to exist. */
+ old_path.mnt = new_dir->mnt;
+ old_path.dentry = new_dentry;
+ new_path.mnt = old_dir->mnt;
+ new_path.dentry = old_dentry;
+ cond.uid = new_dentry->d_inode->i_uid;
+ cond.mode = new_dentry->d_inode->i_mode;
+ flags = 0;
+ goto retry;
+ }

}
return error;
--
1.7.1
----------------------------------------
>From 76fbf677b730070ee7eb2296ef4c38fa288b1bbd Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Sat, 18 Jan 2014 16:45:53 +0900
Subject: [PATCH 4/5] TOMOYO: Handle the rename flags.

For TOMOYO, the RENAME_EXCHANGE flag means "check swapname permission once
rather than checking rename permission twice". This patch adds swapname
permission to TOMOYO.

Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/common.c | 1 +
security/tomoyo/common.h | 5 ++++-
security/tomoyo/file.c | 10 +++++++++-
security/tomoyo/tomoyo.c | 4 +++-
security/tomoyo/util.c | 1 +
5 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c
index 283862a..86747a7 100644
--- a/security/tomoyo/common.c
+++ b/security/tomoyo/common.c
@@ -36,6 +36,7 @@ const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX
[TOMOYO_MAC_FILE_MKCHAR] = "mkchar",
[TOMOYO_MAC_FILE_LINK] = "link",
[TOMOYO_MAC_FILE_RENAME] = "rename",
+ [TOMOYO_MAC_FILE_SWAPNAME] = "swapname",
[TOMOYO_MAC_FILE_CHMOD] = "chmod",
[TOMOYO_MAC_FILE_CHOWN] = "chown",
[TOMOYO_MAC_FILE_CHGRP] = "chgrp",
diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h
index b897d48..0349ae9 100644
--- a/security/tomoyo/common.h
+++ b/security/tomoyo/common.h
@@ -276,6 +276,7 @@ enum tomoyo_network_acl_index {
enum tomoyo_path2_acl_index {
TOMOYO_TYPE_LINK,
TOMOYO_TYPE_RENAME,
+ TOMOYO_TYPE_SWAPNAME,
TOMOYO_TYPE_PIVOT_ROOT,
TOMOYO_MAX_PATH2_OPERATION
};
@@ -335,6 +336,7 @@ enum tomoyo_mac_index {
TOMOYO_MAC_FILE_MKCHAR,
TOMOYO_MAC_FILE_LINK,
TOMOYO_MAC_FILE_RENAME,
+ TOMOYO_MAC_FILE_SWAPNAME,
TOMOYO_MAC_FILE_CHMOD,
TOMOYO_MAC_FILE_CHOWN,
TOMOYO_MAC_FILE_CHGRP,
@@ -730,7 +732,8 @@ struct tomoyo_mkdev_acl {
};

/*
- * Structure for "file rename", "file link" and "file pivot_root" directive.
+ * Structure for "file rename", "file swapname", "file link" and
+ * "file pivot_root" directive.
*/
struct tomoyo_path2_acl {
struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH2_ACL */
diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c
index 4003907..c7d9546 100644
--- a/security/tomoyo/file.c
+++ b/security/tomoyo/file.c
@@ -38,6 +38,7 @@ const u8 tomoyo_pnnn2mac[TOMOYO_MAX_MKDEV_OPERATION] = {
const u8 tomoyo_pp2mac[TOMOYO_MAX_PATH2_OPERATION] = {
[TOMOYO_TYPE_LINK] = TOMOYO_MAC_FILE_LINK,
[TOMOYO_TYPE_RENAME] = TOMOYO_MAC_FILE_RENAME,
+ [TOMOYO_TYPE_SWAPNAME] = TOMOYO_MAC_FILE_SWAPNAME,
[TOMOYO_TYPE_PIVOT_ROOT] = TOMOYO_MAC_FILE_PIVOT_ROOT,
};

@@ -874,7 +875,7 @@ int tomoyo_mkdev_perm(const u8 operation, struct path *path,
}

/**
- * tomoyo_path2_perm - Check permission for "rename", "link" and "pivot_root".
+ * tomoyo_path2_perm - Check permission for "rename", "swapname", "link" and "pivot_root".
*
* @operation: Type of operation.
* @path1: Pointer to "struct path".
@@ -916,6 +917,13 @@ int tomoyo_path2_perm(const u8 operation, struct path *path1,
tomoyo_add_slash(&buf1);
tomoyo_add_slash(&buf2);
break;
+ case TOMOYO_TYPE_SWAPNAME:
+ /* Cross rename requires both inodes to exist. */
+ if (S_ISDIR(path1->dentry->d_inode->i_mode))
+ tomoyo_add_slash(&buf1);
+ if (S_ISDIR(path2->dentry->d_inode->i_mode))
+ tomoyo_add_slash(&buf2);
+ break;
}
r.obj = &obj;
r.param_type = TOMOYO_TYPE_PATH2_ACL;
diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
index ac7dd97..8e9fb4a 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -299,7 +299,9 @@ static int tomoyo_path_rename(struct path *old_parent,
{
struct path path1 = { old_parent->mnt, old_dentry };
struct path path2 = { new_parent->mnt, new_dentry };
- return tomoyo_path2_perm(TOMOYO_TYPE_RENAME, &path1, &path2);
+ return tomoyo_path2_perm((flags & RENAME_EXCHANGE) ?
+ TOMOYO_TYPE_SWAPNAME : TOMOYO_TYPE_RENAME,
+ &path1, &path2);
}

/**
diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c
index 2952ba5..f0ac0be 100644
--- a/security/tomoyo/util.c
+++ b/security/tomoyo/util.c
@@ -34,6 +34,7 @@ const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX] = {
[TOMOYO_MAC_FILE_MKCHAR] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_LINK] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_RENAME] = TOMOYO_MAC_CATEGORY_FILE,
+ [TOMOYO_MAC_FILE_SWAPNAME] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_CHMOD] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_CHOWN] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_CHGRP] = TOMOYO_MAC_CATEGORY_FILE,
--
1.7.1
----------------------------------------
>From 9b135d4223002f9d193066b1908a6de976238e6d Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Sat, 18 Jan 2014 16:52:06 +0900
Subject: [PATCH 5/5] LSM: Remove duplicated rename handling.

Since all LSM modules are now ready to handle the rename flags,
security_inode_rename() and security_path_rename() no longer need to call
each LSM module with reversed arguments.

Signed-off-by: Tetsuo Handa <[email protected]>
---
security/security.c | 18 ------------------
1 files changed, 0 insertions(+), 18 deletions(-)

diff --git a/security/security.c b/security/security.c
index d720afc..9258353 100644
--- a/security/security.c
+++ b/security/security.c
@@ -439,15 +439,6 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;
-
- if (flags & RENAME_EXCHANGE) {
- int err = security_ops->path_rename(new_dir, new_dentry,
- old_dir, old_dentry,
- flags);
- if (err)
- return err;
- }
-
return security_ops->path_rename(old_dir, old_dentry, new_dir,
new_dentry, flags);
}
@@ -540,15 +531,6 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;
-
- if (flags & RENAME_EXCHANGE) {
- int err = security_ops->inode_rename(new_dir, new_dentry,
- old_dir, old_dentry,
- flags);
- if (err)
- return err;
- }
-
return security_ops->inode_rename(old_dir, old_dentry,
new_dir, new_dentry, flags);
}
--
1.7.1
----------------------------------------

2014-01-18 16:28:11

by J. Bruce Fields

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Sat, Jan 18, 2014 at 07:49:29AM +0100, Miklos Szeredi wrote:
> On Fri, Jan 17, 2014 at 11:08 PM, J. Bruce Fields <[email protected]> wrote:
> > On Fri, Jan 17, 2014 at 11:53:07PM +1300, Michael Kerrisk (man-pages) wrote:
> >> > The following additional errors are defined for renameat2():
> >> >
> >> > EOPNOTSUPP
> >> > The filesystem does not support a flag in flags
> >>
> >> This is not the usual error for an invalid bit flag. Please make it EINVAL.
> >
> > I agree that EINVAL makes sense for an invalid bit flag.
> >
> > But renameat2() can also fail when the caller passes a perfectly valid
> > flags field but the paths resolve to a filesystem that doesn't support
> > the RENAME_EXCHANGE operation. EOPNOTSUPP looks more appropriate in
> > that case.
>
> OTOH, from the app's perspective, it makes little difference whether a
> particular kernel doesn't support the reanameat2 syscall, or it
> doesn't support RENAME_FOO flag or if it does support RENAME_FOO but
> not in all filesystems. In all those cases it has to just fall back
> to something supported and it doesn't matter *why* it wasn't
> supported.

Well, in theory it could allow an optimization:

if (kernel_has_foo) {
ret = rename(.,.,.,.,RENAME_FOO);
if (ret && errno == EINVAL)
kernel_has_foo = 0;
}
if (!kernel_has_foo)
fallback...

or maybe even:

if (kernel_has_foo && fs_has_foo[fsid])
ret = rename(.,.,.,.,RENAME_FOO);
if (ret && errno == EINVAL)
kernel_has_foo = 0;
if (ret && errno == EOPNOTSUPP)
fs_has_foo[fsid] = 0;
}
if (!kernel_has_foo || !fs_has_foo[fsid])
fallback...

which may both be of dubious value--unless, say, you're implementing a
network protocol and making this distinction to your client allows it to
save server round trips.

That may not be *totally* farfetched--if for example we added rename2 to
the nfs protocol then servers probably will be required to make this
sort of distinction per filesystem, exactly to allow client logic like
the above.

And as long as we can, I'd just rather give the caller more information
than less.

As for precedent for EOPNOTSUPP: grepping through man-pages the one
documented use of EOPNOTSUPP I see for filesystems is fallocate, for a
similar "filesystem doesn't support this operation" case. "git grep
EOPNOTSUPP fs/" in the kernel repo suggests there are many more such,
but I haven't tried to figure out what any of them are.

--b.

2014-01-20 11:40:06

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Sat, Jan 18, 2014 at 5:27 PM, J. Bruce Fields <[email protected]> wrote:
> On Sat, Jan 18, 2014 at 07:49:29AM +0100, Miklos Szeredi wrote:
>> On Fri, Jan 17, 2014 at 11:08 PM, J. Bruce Fields <[email protected]> wrote:
>> > On Fri, Jan 17, 2014 at 11:53:07PM +1300, Michael Kerrisk (man-pages) wrote:
>> >> > The following additional errors are defined for renameat2():
>> >> >
>> >> > EOPNOTSUPP
>> >> > The filesystem does not support a flag in flags
>> >>
>> >> This is not the usual error for an invalid bit flag. Please make it EINVAL.
>> >
>> > I agree that EINVAL makes sense for an invalid bit flag.
>> >
>> > But renameat2() can also fail when the caller passes a perfectly valid
>> > flags field but the paths resolve to a filesystem that doesn't support
>> > the RENAME_EXCHANGE operation. EOPNOTSUPP looks more appropriate in
>> > that case.
>>
>> OTOH, from the app's perspective, it makes little difference whether a
>> particular kernel doesn't support the reanameat2 syscall, or it
>> doesn't support RENAME_FOO flag or if it does support RENAME_FOO but
>> not in all filesystems. In all those cases it has to just fall back
>> to something supported and it doesn't matter *why* it wasn't
>> supported.
>
> Well, in theory it could allow an optimization:
>
> if (kernel_has_foo) {
> ret = rename(.,.,.,.,RENAME_FOO);
> if (ret && errno == EINVAL)
> kernel_has_foo = 0;
> }
> if (!kernel_has_foo)
> fallback...
>
> or maybe even:
>
> if (kernel_has_foo && fs_has_foo[fsid])
> ret = rename(.,.,.,.,RENAME_FOO);
> if (ret && errno == EINVAL)
> kernel_has_foo = 0;
> if (ret && errno == EOPNOTSUPP)
> fs_has_foo[fsid] = 0;
> }
> if (!kernel_has_foo || !fs_has_foo[fsid])
> fallback...
>
> which may both be of dubious value--unless, say, you're implementing a
> network protocol and making this distinction to your client allows it to
> save server round trips.
>
> That may not be *totally* farfetched--if for example we added rename2 to
> the nfs protocol then servers probably will be required to make this
> sort of distinction per filesystem, exactly to allow client logic like
> the above.

I understand, but that's a protocol issue, not a filesystem issue.
The server will need to determine per-filesystem if the operation is
supported or not, but that doesn't depend on the error value returned
by the filesystem.

> And as long as we can, I'd just rather give the caller more information
> than less.
>
> As for precedent for EOPNOTSUPP: grepping through man-pages the one
> documented use of EOPNOTSUPP I see for filesystems is fallocate, for a
> similar "filesystem doesn't support this operation" case. "git grep
> EOPNOTSUPP fs/" in the kernel repo suggests there are many more such,
> but I haven't tried to figure out what any of them are.

The reason I chose EOPNOTSUPP is because it has the specific meaning:
"this operation is not supported, try to fall back to something else".
EINVAL just means "something" is invalid. That would most likely be
the "flags" argument in this specific case, and hence it works for
renameat2().

And differentiating between the "per-filesystem supported" and the
"per kernel supported" thing based on the error value would also work.
I don't really have a preference and I don't think it's a big deal.

Michael?

Thanks,
Miklos

Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Mon, Jan 20, 2014 at 12:39 PM, Miklos Szeredi <[email protected]> wrote:
> On Sat, Jan 18, 2014 at 5:27 PM, J. Bruce Fields <[email protected]> wrote:
>> On Sat, Jan 18, 2014 at 07:49:29AM +0100, Miklos Szeredi wrote:
>>> On Fri, Jan 17, 2014 at 11:08 PM, J. Bruce Fields <[email protected]> wrote:
>>> > On Fri, Jan 17, 2014 at 11:53:07PM +1300, Michael Kerrisk (man-pages) wrote:
>>> >> > The following additional errors are defined for renameat2():
>>> >> >
>>> >> > EOPNOTSUPP
>>> >> > The filesystem does not support a flag in flags
>>> >>
>>> >> This is not the usual error for an invalid bit flag. Please make it EINVAL.
>>> >
>>> > I agree that EINVAL makes sense for an invalid bit flag.
>>> >
>>> > But renameat2() can also fail when the caller passes a perfectly valid
>>> > flags field but the paths resolve to a filesystem that doesn't support
>>> > the RENAME_EXCHANGE operation. EOPNOTSUPP looks more appropriate in
>>> > that case.
>>>
>>> OTOH, from the app's perspective, it makes little difference whether a
>>> particular kernel doesn't support the reanameat2 syscall, or it
>>> doesn't support RENAME_FOO flag or if it does support RENAME_FOO but
>>> not in all filesystems. In all those cases it has to just fall back
>>> to something supported and it doesn't matter *why* it wasn't
>>> supported.
>>
>> Well, in theory it could allow an optimization:
>>
>> if (kernel_has_foo) {
>> ret = rename(.,.,.,.,RENAME_FOO);
>> if (ret && errno == EINVAL)
>> kernel_has_foo = 0;
>> }
>> if (!kernel_has_foo)
>> fallback...
>>
>> or maybe even:
>>
>> if (kernel_has_foo && fs_has_foo[fsid])
>> ret = rename(.,.,.,.,RENAME_FOO);
>> if (ret && errno == EINVAL)
>> kernel_has_foo = 0;
>> if (ret && errno == EOPNOTSUPP)
>> fs_has_foo[fsid] = 0;
>> }
>> if (!kernel_has_foo || !fs_has_foo[fsid])
>> fallback...
>>
>> which may both be of dubious value--unless, say, you're implementing a
>> network protocol and making this distinction to your client allows it to
>> save server round trips.
>>
>> That may not be *totally* farfetched--if for example we added rename2 to
>> the nfs protocol then servers probably will be required to make this
>> sort of distinction per filesystem, exactly to allow client logic like
>> the above.
>
> I understand, but that's a protocol issue, not a filesystem issue.
> The server will need to determine per-filesystem if the operation is
> supported or not, but that doesn't depend on the error value returned
> by the filesystem.
>
>> And as long as we can, I'd just rather give the caller more information
>> than less.
>>
>> As for precedent for EOPNOTSUPP: grepping through man-pages the one
>> documented use of EOPNOTSUPP I see for filesystems is fallocate, for a
>> similar "filesystem doesn't support this operation" case. "git grep
>> EOPNOTSUPP fs/" in the kernel repo suggests there are many more such,
>> but I haven't tried to figure out what any of them are.
>
> The reason I chose EOPNOTSUPP is because it has the specific meaning:
> "this operation is not supported, try to fall back to something else".
> EINVAL just means "something" is invalid. That would most likely be
> the "flags" argument in this specific case, and hence it works for
> renameat2().
>
> And differentiating between the "per-filesystem supported" and the
> "per kernel supported" thing based on the error value would also work.
> I don't really have a preference and I don't think it's a big deal.
>
> Michael?

I don't really have enough knowledge to know if EOPNOTSUPP would be
appropriate for "per-filesystem supported". I called the invalid
'flags' out, because EINVAL is the standard error for invalid flags.

Cheers,

Michael

--
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/

Subject: Re: [PATCH 11/11] ext4: add cross rename support

Hi Miklos,

> The code changes are pushed to the git tree and the updated man page is below.

Now that renameat2() is in 3.15, I've taken these changes. This had to be
manually does, because I'd done some major reworking on the pages since you
originally wrote your text. So, I may have injected some errors.

I did a little light reworking of the text and added VERSIONS and
CONFORMING TO pieces. I also added a couple of FIXMEs, to note points
that will need to be updated once glibc support is (presumably) added.
(Carlos, what's the usual process for triggering that sort of thing?)

Could you please review the diff below (now against rename(2)).

Cheers,

Michael

diff --git a/man2/rename.2 b/man2/rename.2
index 9f9eda4..73b00ff 100644
--- a/man2/rename.2
+++ b/man2/rename.2
@@ -30,9 +30,9 @@
.\" Modified Thu Mar 3 09:49:35 2005 by Michael Haardt <[email protected]>
.\" 2007-03-25, mtk, added various text to DESCRIPTION.
.\"
-.TH RENAME 2 2014-02-21 "Linux" "Linux Programmer's Manual"
+.TH RENAME 2 2014-04-19 "Linux" "Linux Programmer's Manual"
.SH NAME
-rename, renameat \- change the name or location of a file
+rename, renameat, renameat2 \- change the name or location of a file
.SH SYNOPSIS
.nf
.B #include <stdio.h>
@@ -44,6 +44,10 @@ rename, renameat \- change the name or location of a file
.sp
.BI "int renameat(int " olddirfd ", const char *" oldpath ,
.BI " int " newdirfd ", const char *" newpath );
+
+.BI "int renameat2(int " olddirfd ", const char *" oldpath ,
+.BI " int " newdirfd ", const char *" newpath \
+", unsigned int " flags );
.fi
.sp
.in -4n
@@ -61,6 +65,7 @@ _XOPEN_SOURCE\ >=\ 700 || _POSIX_C_SOURCE\ >=\ 200809L
.TP
Before glibc 2.10:
_ATFILE_SOURCE
+.\" FIXME need to define FTMs for renameat2(), once it hits glibc
.RE
.ad
.PD
@@ -163,6 +168,38 @@ See
.BR openat (2)
for an explanation of the need for
.BR renameat ().
+.SS renameat2()
+.BR renameat2 ()
+has an additional
+.I flags
+argument.
+A
+.BR renameat2 ()
+call with a zero
+.I flags
+argument is equivalent to
+.BR renameat ().
+
+The
+.I flags
+argument is a bit mask consisting of zero or more of the following flags:
+.TP
+.B RENAME_NOREPLACE
+Don't overwrite
+.IR newpath .
+of the rename.
+Return an error if
+.IR newpath
+already exists.
+.TP
+.B RENAME_EXCHANGE
+Atomically exchange
+.IR oldpath
+and
+.IR newpath .
+Both pathnames must exist
+but may be of different types (e.g., one could be a non-empty directory
+and the other a symbolic link).
.SH RETURN VALUE
On success, zero is returned.
On error, \-1 is returned, and
@@ -306,7 +343,9 @@ does not work across different mount points,
even if the same filesystem is mounted on both.)
.PP
The following additional errors can occur for
-.BR renameat ():
+.BR renameat ()
+and
+.BR renameat2 ():
.TP
.B EBADF
.I olddirfd
@@ -323,16 +362,55 @@ or similar for
.I newpath
and
.I newdirfd
+.PP
+The following additional errors can occur for
+.BR renameat2 ():
+.TP
+.B EEXIST
+.I flags
+contains
+.B RENAME_NOREPLACE
+and
+.I newpath
+already exists.
+.TP
+.B EINVAL
+An invalid flag was specified in
+.IR flags ,
+or both
+.B RENAME_NOREPLACE
+and
+.B RENAME_EXCHANGE
+were specified.
+.TP
+.B EINVAL
+The filesystem does not support one of the flags in
+.IR flags .
+.TP
+.B ENOENT
+.I flags
+contains
+.B RENAME_EXCHANGE
+and
+.IR newpath
+does not exist.
.SH VERSIONS
.BR renameat ()
was added to Linux in kernel 2.6.16;
library support was added to glibc in version 2.4.
+
+.BR renameat2 ()
+was added to Linux in kernel 3.15.
+.\" FIXME glibc support is pending.
.SH CONFORMING TO
.BR rename ():
4.3BSD, C89, C99, POSIX.1-2001, POSIX.1-2008.

.BR renameat ():
POSIX.1-2008.
+
+.BR renameat2()
+is Linux-specific.
.SH BUGS
On NFS filesystems, you can not assume that if the operation
failed, the file was not renamed.


--
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/

2014-04-19 12:08:23

by Tetsuo Handa

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

Michael Kerrisk (man-pages) wrote:
> Now that renameat2() is in 3.15, I've taken these changes.

What!? I didn't know renameat2() goes to 3.15.

But I assume that renameat2() is not accessible in 3.15, for I can see
"asmlinkage long sys_renameat2(" but don't see "#define __NR_renameat2".

$ grep -Fr renameat include/
include/linux/syscalls.h:asmlinkage long sys_renameat(int olddfd, const char __user * oldname,
include/linux/syscalls.h:asmlinkage long sys_renameat2(int olddfd, const char __user *oldname,
include/uapi/asm-generic/unistd.h:#define __NR_renameat 38
include/uapi/asm-generic/unistd.h:__SYSCALL(__NR_renameat, sys_renameat)
include/asm-generic/audit_dir_write.h:__NR_renameat,
include/net/9p/client.h:int p9_client_renameat(struct p9_fid *olddirfid, const char *old_name,

If renameat2() is accessible in 3.15, I must consider submitting TOMOYO changes
immediately. But so far I got no response from LSM users regarding the patch
( https://lkml.org/lkml/2014/1/18/26 ) which the TOMOYO changes depend on.

2014-04-23 14:21:45

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Sat, Apr 19, 2014 at 11:08 AM, Michael Kerrisk (man-pages)
<[email protected]> wrote:
> Hi Miklos,
>
>> The code changes are pushed to the git tree and the updated man page is below.
>
> Now that renameat2() is in 3.15, I've taken these changes. This had to be
> manually does, because I'd done some major reworking on the pages since you
> originally wrote your text. So, I may have injected some errors.
>
> I did a little light reworking of the text and added VERSIONS and
> CONFORMING TO pieces. I also added a couple of FIXMEs, to note points
> that will need to be updated once glibc support is (presumably) added.
> (Carlos, what's the usual process for triggering that sort of thing?)
>
> Could you please review the diff below (now against rename(2)).

Looks okay.

One comment on the current rename.2 page:

#include <fcntl.h> /* Definition of AT_* constants */

AT_ constants are not used in renameat() hence this seems unneeded.

Not sure where the RENAME_ constants need to go once renameat2() is
added to glibc, possibly to <stdio.h> since they are closely related
to rename.

Thanks,
Miklos

2014-04-23 14:24:37

by Miklos Szeredi

[permalink] [raw]
Subject: Re: [PATCH 11/11] ext4: add cross rename support

On Sat, Apr 19, 2014 at 2:08 PM, Tetsuo Handa
<[email protected]> wrote:
> Michael Kerrisk (man-pages) wrote:
>> Now that renameat2() is in 3.15, I've taken these changes.
>
> What!? I didn't know renameat2() goes to 3.15.
>
> But I assume that renameat2() is not accessible in 3.15, for I can see
> "asmlinkage long sys_renameat2(" but don't see "#define __NR_renameat2".

x86 automatically generates __NR_foo entries and syscall tables from
arch/x86/syscalls/syscall_*.tbl, which is why you don't find an
explicit definition in the git tree.

Thanks,
Miklos

Subject: Re: [PATCH 11/11] ext4: add cross rename support

On 04/23/2014 04:21 PM, Miklos Szeredi wrote:
> On Sat, Apr 19, 2014 at 11:08 AM, Michael Kerrisk (man-pages)
> <[email protected]> wrote:
>> Hi Miklos,
>>
>>> The code changes are pushed to the git tree and the updated man page is below.
>>
>> Now that renameat2() is in 3.15, I've taken these changes. This had to be
>> manually does, because I'd done some major reworking on the pages since you
>> originally wrote your text. So, I may have injected some errors.
>>
>> I did a little light reworking of the text and added VERSIONS and
>> CONFORMING TO pieces. I also added a couple of FIXMEs, to note points
>> that will need to be updated once glibc support is (presumably) added.
>> (Carlos, what's the usual process for triggering that sort of thing?)
>>
>> Could you please review the diff below (now against rename(2)).
>
> Looks okay.

Thanks for checking it.

> One comment on the current rename.2 page:
>
> #include <fcntl.h> /* Definition of AT_* constants */
>
> AT_ constants are not used in renameat() hence this seems unneeded.

I think you overlooked AT_FDCWD? (It is true though that that comment
is boilerplate across the *at() pages, some of which have several
AT_* constants.)

> Not sure where the RENAME_ constants need to go once renameat2() is
> added to glibc, possibly to <stdio.h> since they are closely related
> to rename.

Will you be prodding the glibc folks about this?

Thanks,

Michael


--
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/

2014-04-24 11:21:14

by Tetsuo Handa

[permalink] [raw]
Subject: [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM.

Miklos Szeredi wrote:
> On Sat, Apr 19, 2014 at 2:08 PM, Tetsuo Handa
> <[email protected]> wrote:
> > Michael Kerrisk (man-pages) wrote:
> >> Now that renameat2() is in 3.15, I've taken these changes.
> >
> > What!? I didn't know renameat2() goes to 3.15.
> >
> > But I assume that renameat2() is not accessible in 3.15, for I can see
> > "asmlinkage long sys_renameat2(" but don't see "#define __NR_renameat2".
>
> x86 automatically generates __NR_foo entries and syscall tables from
> arch/x86/syscalls/syscall_*.tbl, which is why you don't find an
> explicit definition in the git tree.
>
> Thanks,
> Miklos
>

Oh, I see. Then, I must submit patches for fixing a race window
caused by commit da1ce067 "vfs: add cross-rename".

Regards.

2014-04-24 11:22:25

by Tetsuo Handa

[permalink] [raw]
Subject: [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module.

>From 207346b4153e2b441e0c45d933043e6ba7abb419 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Thu, 24 Apr 2014 20:04:57 +0900
Subject: [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module.

Commit da1ce067 "vfs: add cross-rename" changed security_inode_rename() and
security_path_rename() to call LSM module with reversed arguments if the rename
flags contain RENAME_EXCHANGE. But that commit introduced a race window for
TOMOYO and AppArmor (users of security_path_rename) because the pathnames may
have been changed between the first call and the second call.

To fix this race condition, the rename flags should be passed to LSM module
in order to allow LSM module to avoid re-calculation of pathnames.
Passing the rename flags allows SMACK (one of users of security_inode_rename)
to avoid needlessly checking the same permission twice.

Signed-off-by: Tetsuo Handa <[email protected]>
---
include/linux/security.h | 8 ++++++--
security/apparmor/lsm.c | 3 ++-
security/capability.c | 6 ++++--
security/security.c | 10 ++++++----
security/selinux/hooks.c | 3 ++-
security/smack/smack_lsm.c | 4 +++-
security/tomoyo/tomoyo.c | 4 +++-
7 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/include/linux/security.h b/include/linux/security.h
index 9c6b972..12770bb 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -446,6 +446,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
* @old_dentry contains the dentry structure of the old link.
* @new_dir contains the inode structure for parent of the new link.
* @new_dentry contains the dentry structure of the new link.
+ * @flags contains rename flags.
* Return 0 if permission is granted.
* @path_rename:
* Check for permission to rename a file or directory.
@@ -453,6 +454,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
* @old_dentry contains the dentry structure of the old link.
* @new_dir contains the path structure for parent of the new link.
* @new_dentry contains the dentry structure of the new link.
+ * @flags contains rename flags.
* Return 0 if permission is granted.
* @path_chmod:
* Check for permission to change DAC's permission of a file or directory.
@@ -1492,7 +1494,8 @@ struct security_operations {
int (*path_link) (struct dentry *old_dentry, struct path *new_dir,
struct dentry *new_dentry);
int (*path_rename) (struct path *old_dir, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry);
+ struct path *new_dir, struct dentry *new_dentry,
+ unsigned int flags);
int (*path_chmod) (struct path *path, umode_t mode);
int (*path_chown) (struct path *path, kuid_t uid, kgid_t gid);
int (*path_chroot) (struct path *path);
@@ -1515,7 +1518,8 @@ struct security_operations {
int (*inode_mknod) (struct inode *dir, struct dentry *dentry,
umode_t mode, dev_t dev);
int (*inode_rename) (struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry);
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags);
int (*inode_readlink) (struct dentry *dentry);
int (*inode_follow_link) (struct dentry *dentry, struct nameidata *nd);
int (*inode_permission) (struct inode *inode, int mask);
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 9981000..c0b4366 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -315,7 +315,8 @@ static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
}

static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry)
+ struct path *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
{
struct aa_profile *profile;
int error = 0;
diff --git a/security/capability.c b/security/capability.c
index e76373d..b3690cc 100644
--- a/security/capability.c
+++ b/security/capability.c
@@ -176,7 +176,8 @@ static int cap_inode_mknod(struct inode *inode, struct dentry *dentry,
}

static int cap_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
- struct inode *new_inode, struct dentry *new_dentry)
+ struct inode *new_inode, struct dentry *new_dentry,
+ unsigned int flags)
{
return 0;
}
@@ -280,7 +281,8 @@ static int cap_path_link(struct dentry *old_dentry, struct path *new_dir,
}

static int cap_path_rename(struct path *old_path, struct dentry *old_dentry,
- struct path *new_path, struct dentry *new_dentry)
+ struct path *new_path, struct dentry *new_dentry,
+ unsigned int flags)
{
return 0;
}
diff --git a/security/security.c b/security/security.c
index 31614e9..65ceef3 100644
--- a/security/security.c
+++ b/security/security.c
@@ -442,13 +442,14 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,

if (flags & RENAME_EXCHANGE) {
int err = security_ops->path_rename(new_dir, new_dentry,
- old_dir, old_dentry);
+ old_dir, old_dentry,
+ flags);
if (err)
return err;
}

return security_ops->path_rename(old_dir, old_dentry, new_dir,
- new_dentry);
+ new_dentry, flags);
}
EXPORT_SYMBOL(security_path_rename);

@@ -542,13 +543,14 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,

if (flags & RENAME_EXCHANGE) {
int err = security_ops->inode_rename(new_dir, new_dentry,
- old_dir, old_dentry);
+ old_dir, old_dentry,
+ flags);
if (err)
return err;
}

return security_ops->inode_rename(old_dir, old_dentry,
- new_dir, new_dentry);
+ new_dir, new_dentry, flags);
}

int security_inode_readlink(struct dentry *dentry)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 6befc92..d4913d1 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2749,7 +2749,8 @@ static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t
}

static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
- struct inode *new_inode, struct dentry *new_dentry)
+ struct inode *new_inode, struct dentry *new_dentry,
+ unsigned int flags)
{
return may_rename(old_inode, old_dentry, new_inode, new_dentry);
}
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 8177e7d..41bd059 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -697,6 +697,7 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry)
* @old_dentry: unused
* @new_inode: the new directory
* @new_dentry: unused
+ * @flags: unused
*
* Read and write access is required on both the old and
* new directories.
@@ -706,7 +707,8 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry)
static int smack_inode_rename(struct inode *old_inode,
struct dentry *old_dentry,
struct inode *new_inode,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ unsigned int flags)
{
int rc;
char *isp;
diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
index f0b756e..ac7dd97 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -287,13 +287,15 @@ static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
* @old_dentry: Pointer to "struct dentry".
* @new_parent: Pointer to "struct path".
* @new_dentry: Pointer to "struct dentry".
+ * @flags: Rename flags.
*
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_path_rename(struct path *old_parent,
struct dentry *old_dentry,
struct path *new_parent,
- struct dentry *new_dentry)
+ struct dentry *new_dentry,
+ unsigned int flags)
{
struct path path1 = { old_parent->mnt, old_dentry };
struct path path2 = { new_parent->mnt, new_dentry };
--
1.7.9.5

2014-04-24 11:23:28

by Tetsuo Handa

[permalink] [raw]
Subject: [PATCH (for 3.15) 2/5] SELinux: Handle the rename flags.

>From d095d350fda528b993226752102caeda2d95af46 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Thu, 24 Apr 2014 20:06:45 +0900
Subject: [PATCH (for 3.15) 2/5] SELinux: Handle the rename flags.

For SELinux, the RENAME_EXCHANGE flag means "check permissions with
reversed arguments".

Signed-off-by: Tetsuo Handa <[email protected]>
---
security/selinux/hooks.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index d4913d1..b2847cb 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2752,7 +2752,10 @@ static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dent
struct inode *new_inode, struct dentry *new_dentry,
unsigned int flags)
{
- return may_rename(old_inode, old_dentry, new_inode, new_dentry);
+ int err = may_rename(old_inode, old_dentry, new_inode, new_dentry);
+ if (!err && (flags & RENAME_EXCHANGE))
+ err = may_rename(new_inode, new_dentry, old_inode, old_dentry);
+ return err;
}

static int selinux_inode_readlink(struct dentry *dentry)
--
1.7.9.5

2014-04-24 11:24:24

by Tetsuo Handa

[permalink] [raw]
Subject: [PATCH (for 3.15) 3/5] AppArmor: Handle the rename flags.

>From 3bce55d24ef7a55613d748182aac1f3986c144da Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Thu, 24 Apr 2014 20:07:58 +0900
Subject: [PATCH (for 3.15) 3/5] AppArmor: Handle the rename flags.

For AppArmor, the RENAME_EXCHANGE flag means "check permissions with
reversed arguments". Future patches will stop re-calculating pathnames.

Signed-off-by: Tetsuo Handa <[email protected]>
---
security/apparmor/lsm.c | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index c0b4366..b04218a 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -332,6 +332,7 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
old_dentry->d_inode->i_mode
};

+retry:
error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0,
MAY_READ | AA_MAY_META_READ | MAY_WRITE |
AA_MAY_META_WRITE | AA_MAY_DELETE,
@@ -340,6 +341,17 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
0, MAY_WRITE | AA_MAY_META_WRITE |
AA_MAY_CREATE, &cond);
+ if (!error && (flags & RENAME_EXCHANGE)) {
+ /* Cross rename requires both inodes to exist. */
+ old_path.mnt = new_dir->mnt;
+ old_path.dentry = new_dentry;
+ new_path.mnt = old_dir->mnt;
+ new_path.dentry = old_dentry;
+ cond.uid = new_dentry->d_inode->i_uid;
+ cond.mode = new_dentry->d_inode->i_mode;
+ flags = 0;
+ goto retry;
+ }

}
return error;
--
1.7.9.5

2014-04-24 11:25:26

by Tetsuo Handa

[permalink] [raw]
Subject: [PATCH (for 3.15) 4/5] TOMOYO: Handle the rename flags.

>From 85b260e2e72962efb3991a606496cf237e739bb0 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Thu, 24 Apr 2014 20:09:01 +0900
Subject: [PATCH (for 3.15) 4/5] TOMOYO: Handle the rename flags.

For TOMOYO, the RENAME_EXCHANGE flag means "check swapname permission once
rather than checking rename permission twice". This patch adds swapname
permission to TOMOYO.

Signed-off-by: Tetsuo Handa <[email protected]>
---
security/tomoyo/common.c | 1 +
security/tomoyo/common.h | 5 ++++-
security/tomoyo/file.c | 10 +++++++++-
security/tomoyo/tomoyo.c | 4 +++-
security/tomoyo/util.c | 1 +
5 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c
index 283862a..86747a7 100644
--- a/security/tomoyo/common.c
+++ b/security/tomoyo/common.c
@@ -36,6 +36,7 @@ const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX
[TOMOYO_MAC_FILE_MKCHAR] = "mkchar",
[TOMOYO_MAC_FILE_LINK] = "link",
[TOMOYO_MAC_FILE_RENAME] = "rename",
+ [TOMOYO_MAC_FILE_SWAPNAME] = "swapname",
[TOMOYO_MAC_FILE_CHMOD] = "chmod",
[TOMOYO_MAC_FILE_CHOWN] = "chown",
[TOMOYO_MAC_FILE_CHGRP] = "chgrp",
diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h
index b897d48..0349ae9 100644
--- a/security/tomoyo/common.h
+++ b/security/tomoyo/common.h
@@ -276,6 +276,7 @@ enum tomoyo_network_acl_index {
enum tomoyo_path2_acl_index {
TOMOYO_TYPE_LINK,
TOMOYO_TYPE_RENAME,
+ TOMOYO_TYPE_SWAPNAME,
TOMOYO_TYPE_PIVOT_ROOT,
TOMOYO_MAX_PATH2_OPERATION
};
@@ -335,6 +336,7 @@ enum tomoyo_mac_index {
TOMOYO_MAC_FILE_MKCHAR,
TOMOYO_MAC_FILE_LINK,
TOMOYO_MAC_FILE_RENAME,
+ TOMOYO_MAC_FILE_SWAPNAME,
TOMOYO_MAC_FILE_CHMOD,
TOMOYO_MAC_FILE_CHOWN,
TOMOYO_MAC_FILE_CHGRP,
@@ -730,7 +732,8 @@ struct tomoyo_mkdev_acl {
};

/*
- * Structure for "file rename", "file link" and "file pivot_root" directive.
+ * Structure for "file rename", "file swapname", "file link" and
+ * "file pivot_root" directive.
*/
struct tomoyo_path2_acl {
struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH2_ACL */
diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c
index 4003907..c7d9546 100644
--- a/security/tomoyo/file.c
+++ b/security/tomoyo/file.c
@@ -38,6 +38,7 @@ const u8 tomoyo_pnnn2mac[TOMOYO_MAX_MKDEV_OPERATION] = {
const u8 tomoyo_pp2mac[TOMOYO_MAX_PATH2_OPERATION] = {
[TOMOYO_TYPE_LINK] = TOMOYO_MAC_FILE_LINK,
[TOMOYO_TYPE_RENAME] = TOMOYO_MAC_FILE_RENAME,
+ [TOMOYO_TYPE_SWAPNAME] = TOMOYO_MAC_FILE_SWAPNAME,
[TOMOYO_TYPE_PIVOT_ROOT] = TOMOYO_MAC_FILE_PIVOT_ROOT,
};

@@ -874,7 +875,7 @@ int tomoyo_mkdev_perm(const u8 operation, struct path *path,
}

/**
- * tomoyo_path2_perm - Check permission for "rename", "link" and "pivot_root".
+ * tomoyo_path2_perm - Check permission for "rename", "swapname", "link" and "pivot_root".
*
* @operation: Type of operation.
* @path1: Pointer to "struct path".
@@ -916,6 +917,13 @@ int tomoyo_path2_perm(const u8 operation, struct path *path1,
tomoyo_add_slash(&buf1);
tomoyo_add_slash(&buf2);
break;
+ case TOMOYO_TYPE_SWAPNAME:
+ /* Cross rename requires both inodes to exist. */
+ if (S_ISDIR(path1->dentry->d_inode->i_mode))
+ tomoyo_add_slash(&buf1);
+ if (S_ISDIR(path2->dentry->d_inode->i_mode))
+ tomoyo_add_slash(&buf2);
+ break;
}
r.obj = &obj;
r.param_type = TOMOYO_TYPE_PATH2_ACL;
diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
index ac7dd97..8e9fb4a 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -299,7 +299,9 @@ static int tomoyo_path_rename(struct path *old_parent,
{
struct path path1 = { old_parent->mnt, old_dentry };
struct path path2 = { new_parent->mnt, new_dentry };
- return tomoyo_path2_perm(TOMOYO_TYPE_RENAME, &path1, &path2);
+ return tomoyo_path2_perm((flags & RENAME_EXCHANGE) ?
+ TOMOYO_TYPE_SWAPNAME : TOMOYO_TYPE_RENAME,
+ &path1, &path2);
}

/**
diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c
index 2952ba5..f0ac0be 100644
--- a/security/tomoyo/util.c
+++ b/security/tomoyo/util.c
@@ -34,6 +34,7 @@ const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX] = {
[TOMOYO_MAC_FILE_MKCHAR] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_LINK] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_RENAME] = TOMOYO_MAC_CATEGORY_FILE,
+ [TOMOYO_MAC_FILE_SWAPNAME] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_CHMOD] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_CHOWN] = TOMOYO_MAC_CATEGORY_FILE,
[TOMOYO_MAC_FILE_CHGRP] = TOMOYO_MAC_CATEGORY_FILE,
--
1.7.9.5

2014-04-24 11:26:31

by Tetsuo Handa

[permalink] [raw]
Subject: [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling.

>From fe5ea6a9442c95725d3c27eddfde754acd8c9785 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <[email protected]>
Date: Thu, 24 Apr 2014 20:09:57 +0900
Subject: [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling.

Since all LSM modules are now ready to handle the rename flags,
security_inode_rename() and security_path_rename() no longer need to
call each LSM module with reversed arguments.

Signed-off-by: Tetsuo Handa <[email protected]>
---
security/security.c | 18 ------------------
1 file changed, 18 deletions(-)

diff --git a/security/security.c b/security/security.c
index 65ceef3..96f9437 100644
--- a/security/security.c
+++ b/security/security.c
@@ -439,15 +439,6 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;
-
- if (flags & RENAME_EXCHANGE) {
- int err = security_ops->path_rename(new_dir, new_dentry,
- old_dir, old_dentry,
- flags);
- if (err)
- return err;
- }
-
return security_ops->path_rename(old_dir, old_dentry, new_dir,
new_dentry, flags);
}
@@ -540,15 +531,6 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
if (unlikely(IS_PRIVATE(old_dentry->d_inode) ||
(new_dentry->d_inode && IS_PRIVATE(new_dentry->d_inode))))
return 0;
-
- if (flags & RENAME_EXCHANGE) {
- int err = security_ops->inode_rename(new_dir, new_dentry,
- old_dir, old_dentry,
- flags);
- if (err)
- return err;
- }
-
return security_ops->inode_rename(old_dir, old_dentry,
new_dir, new_dentry, flags);
}
--
1.7.9.5

2014-04-25 20:56:44

by Casey Schaufler

[permalink] [raw]
Subject: Re: [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module.

On 4/24/2014 4:22 AM, Tetsuo Handa wrote:
> >From 207346b4153e2b441e0c45d933043e6ba7abb419 Mon Sep 17 00:00:00 2001
> From: Tetsuo Handa <[email protected]>
> Date: Thu, 24 Apr 2014 20:04:57 +0900
> Subject: [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module.
>
> Commit da1ce067 "vfs: add cross-rename" changed security_inode_rename() and
> security_path_rename() to call LSM module with reversed arguments if the rename
> flags contain RENAME_EXCHANGE. But that commit introduced a race window for
> TOMOYO and AppArmor (users of security_path_rename) because the pathnames may
> have been changed between the first call and the second call.
>
> To fix this race condition, the rename flags should be passed to LSM module
> in order to allow LSM module to avoid re-calculation of pathnames.
> Passing the rename flags allows SMACK (one of users of security_inode_rename)
> to avoid needlessly checking the same permission twice.
>
> Signed-off-by: Tetsuo Handa <[email protected]>

Acked-by: Casey Schaufler <[email protected]>

At least for the Smack changes.

> ---
> include/linux/security.h | 8 ++++++--
> security/apparmor/lsm.c | 3 ++-
> security/capability.c | 6 ++++--
> security/security.c | 10 ++++++----
> security/selinux/hooks.c | 3 ++-
> security/smack/smack_lsm.c | 4 +++-
> security/tomoyo/tomoyo.c | 4 +++-
> 7 files changed, 26 insertions(+), 12 deletions(-)
>
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 9c6b972..12770bb 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -446,6 +446,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
> * @old_dentry contains the dentry structure of the old link.
> * @new_dir contains the inode structure for parent of the new link.
> * @new_dentry contains the dentry structure of the new link.
> + * @flags contains rename flags.
> * Return 0 if permission is granted.
> * @path_rename:
> * Check for permission to rename a file or directory.
> @@ -453,6 +454,7 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
> * @old_dentry contains the dentry structure of the old link.
> * @new_dir contains the path structure for parent of the new link.
> * @new_dentry contains the dentry structure of the new link.
> + * @flags contains rename flags.
> * Return 0 if permission is granted.
> * @path_chmod:
> * Check for permission to change DAC's permission of a file or directory.
> @@ -1492,7 +1494,8 @@ struct security_operations {
> int (*path_link) (struct dentry *old_dentry, struct path *new_dir,
> struct dentry *new_dentry);
> int (*path_rename) (struct path *old_dir, struct dentry *old_dentry,
> - struct path *new_dir, struct dentry *new_dentry);
> + struct path *new_dir, struct dentry *new_dentry,
> + unsigned int flags);
> int (*path_chmod) (struct path *path, umode_t mode);
> int (*path_chown) (struct path *path, kuid_t uid, kgid_t gid);
> int (*path_chroot) (struct path *path);
> @@ -1515,7 +1518,8 @@ struct security_operations {
> int (*inode_mknod) (struct inode *dir, struct dentry *dentry,
> umode_t mode, dev_t dev);
> int (*inode_rename) (struct inode *old_dir, struct dentry *old_dentry,
> - struct inode *new_dir, struct dentry *new_dentry);
> + struct inode *new_dir, struct dentry *new_dentry,
> + unsigned int flags);
> int (*inode_readlink) (struct dentry *dentry);
> int (*inode_follow_link) (struct dentry *dentry, struct nameidata *nd);
> int (*inode_permission) (struct inode *inode, int mask);
> diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
> index 9981000..c0b4366 100644
> --- a/security/apparmor/lsm.c
> +++ b/security/apparmor/lsm.c
> @@ -315,7 +315,8 @@ static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
> }
>
> static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
> - struct path *new_dir, struct dentry *new_dentry)
> + struct path *new_dir, struct dentry *new_dentry,
> + unsigned int flags)
> {
> struct aa_profile *profile;
> int error = 0;
> diff --git a/security/capability.c b/security/capability.c
> index e76373d..b3690cc 100644
> --- a/security/capability.c
> +++ b/security/capability.c
> @@ -176,7 +176,8 @@ static int cap_inode_mknod(struct inode *inode, struct dentry *dentry,
> }
>
> static int cap_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
> - struct inode *new_inode, struct dentry *new_dentry)
> + struct inode *new_inode, struct dentry *new_dentry,
> + unsigned int flags)
> {
> return 0;
> }
> @@ -280,7 +281,8 @@ static int cap_path_link(struct dentry *old_dentry, struct path *new_dir,
> }
>
> static int cap_path_rename(struct path *old_path, struct dentry *old_dentry,
> - struct path *new_path, struct dentry *new_dentry)
> + struct path *new_path, struct dentry *new_dentry,
> + unsigned int flags)
> {
> return 0;
> }
> diff --git a/security/security.c b/security/security.c
> index 31614e9..65ceef3 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -442,13 +442,14 @@ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
>
> if (flags & RENAME_EXCHANGE) {
> int err = security_ops->path_rename(new_dir, new_dentry,
> - old_dir, old_dentry);
> + old_dir, old_dentry,
> + flags);
> if (err)
> return err;
> }
>
> return security_ops->path_rename(old_dir, old_dentry, new_dir,
> - new_dentry);
> + new_dentry, flags);
> }
> EXPORT_SYMBOL(security_path_rename);
>
> @@ -542,13 +543,14 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
>
> if (flags & RENAME_EXCHANGE) {
> int err = security_ops->inode_rename(new_dir, new_dentry,
> - old_dir, old_dentry);
> + old_dir, old_dentry,
> + flags);
> if (err)
> return err;
> }
>
> return security_ops->inode_rename(old_dir, old_dentry,
> - new_dir, new_dentry);
> + new_dir, new_dentry, flags);
> }
>
> int security_inode_readlink(struct dentry *dentry)
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 6befc92..d4913d1 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -2749,7 +2749,8 @@ static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t
> }
>
> static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dentry,
> - struct inode *new_inode, struct dentry *new_dentry)
> + struct inode *new_inode, struct dentry *new_dentry,
> + unsigned int flags)
> {
> return may_rename(old_inode, old_dentry, new_inode, new_dentry);
> }
> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
> index 8177e7d..41bd059 100644
> --- a/security/smack/smack_lsm.c
> +++ b/security/smack/smack_lsm.c
> @@ -697,6 +697,7 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry)
> * @old_dentry: unused
> * @new_inode: the new directory
> * @new_dentry: unused
> + * @flags: unused
> *
> * Read and write access is required on both the old and
> * new directories.
> @@ -706,7 +707,8 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry)
> static int smack_inode_rename(struct inode *old_inode,
> struct dentry *old_dentry,
> struct inode *new_inode,
> - struct dentry *new_dentry)
> + struct dentry *new_dentry,
> + unsigned int flags)
> {
> int rc;
> char *isp;
> diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
> index f0b756e..ac7dd97 100644
> --- a/security/tomoyo/tomoyo.c
> +++ b/security/tomoyo/tomoyo.c
> @@ -287,13 +287,15 @@ static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
> * @old_dentry: Pointer to "struct dentry".
> * @new_parent: Pointer to "struct path".
> * @new_dentry: Pointer to "struct dentry".
> + * @flags: Rename flags.
> *
> * Returns 0 on success, negative value otherwise.
> */
> static int tomoyo_path_rename(struct path *old_parent,
> struct dentry *old_dentry,
> struct path *new_parent,
> - struct dentry *new_dentry)
> + struct dentry *new_dentry,
> + unsigned int flags)
> {
> struct path path1 = { old_parent->mnt, old_dentry };
> struct path path2 = { new_parent->mnt, new_dentry };