linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/11] cross rename v3
@ 2014-01-08 22:10 Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 01/11] vfs: add d_is_dir() Miklos Szeredi
                   ` (11 more replies)
  0 siblings, 12 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

[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(-)



^ permalink raw reply	[flat|nested] 73+ messages in thread

* [PATCH 01/11] vfs: add d_is_dir()
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 02/11] vfs: rename: move d_move() up Miklos Szeredi
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

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 <mszeredi@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH 02/11] vfs: rename: move d_move() up
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 01/11] vfs: add d_is_dir() Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 03/11] vfs: rename: use common code for dir and non-dir Miklos Szeredi
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

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 <mszeredi@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH 03/11] vfs: rename: use common code for dir and non-dir
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 01/11] vfs: add d_is_dir() Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 02/11] vfs: rename: move d_move() up Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 04/11] vfs: add renameat2 syscall Miklos Szeredi
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

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 <mszeredi@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH 04/11] vfs: add renameat2 syscall
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
                   ` (2 preceding siblings ...)
  2014-01-08 22:10 ` [PATCH 03/11] vfs: rename: use common code for dir and non-dir Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-14 22:11   ` Tetsuo Handa
  2014-01-08 22:10 ` [PATCH 05/11] vfs: add RENAME_NOREPLACE flag Miklos Szeredi
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

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 <mszeredi@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH 05/11] vfs: add RENAME_NOREPLACE flag
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
                   ` (3 preceding siblings ...)
  2014-01-08 22:10 ` [PATCH 04/11] vfs: add renameat2 syscall Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-15 18:19   ` J. Bruce Fields
  2014-01-08 22:10 ` [PATCH 06/11] security: add flags to rename hooks Miklos Szeredi
                   ` (6 subsequent siblings)
  11 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

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 <luto@amacapital.net>
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH 06/11] security: add flags to rename hooks
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
                   ` (4 preceding siblings ...)
  2014-01-08 22:10 ` [PATCH 05/11] vfs: add RENAME_NOREPLACE flag Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 07/11] vfs: add cross-rename Miklos Szeredi
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

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

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH 07/11] vfs: add cross-rename
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
                   ` (5 preceding siblings ...)
  2014-01-08 22:10 ` [PATCH 06/11] security: add flags to rename hooks Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-13  7:52   ` Jan Kara
  2014-01-08 22:10 ` [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars Miklos Szeredi
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

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 <mszeredi@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
                   ` (6 preceding siblings ...)
  2014-01-08 22:10 ` [PATCH 07/11] vfs: add cross-rename Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 09/11] ext4: rename: move EMLINK check up Miklos Szeredi
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

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 <mszeredi@suse.cz>
Reviewed-by: Jan Kara <jack@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH 09/11] ext4: rename: move EMLINK check up
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
                   ` (7 preceding siblings ...)
  2014-01-08 22:10 ` [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 10/11] ext4: rename: split out helper functions Miklos Szeredi
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

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 <mszeredi@suse.cz>
Reviewed-by: Jan Kara <jack@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH 10/11] ext4: rename: split out helper functions
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
                   ` (8 preceding siblings ...)
  2014-01-08 22:10 ` [PATCH 09/11] ext4: rename: move EMLINK check up Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-08 22:10 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi
  2014-01-13 12:46 ` [PATCH 00/11] cross rename v3 Tetsuo Handa
  11 siblings, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

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 <mszeredi@suse.cz>
Reviewed-by: Jan Kara <jack@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH 11/11] ext4: add cross rename support
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
                   ` (9 preceding siblings ...)
  2014-01-08 22:10 ` [PATCH 10/11] ext4: rename: split out helper functions Miklos Szeredi
@ 2014-01-08 22:10 ` Miklos Szeredi
  2014-01-13 12:25   ` Jan Kara
  2014-01-13 12:46 ` [PATCH 00/11] cross rename v3 Tetsuo Handa
  11 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-08 22:10 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

Implement RENAME_EXCHANGE flag in renameat2 syscall.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
---
 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


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* Re: [PATCH 07/11] vfs: add cross-rename
  2014-01-08 22:10 ` [PATCH 07/11] vfs: add cross-rename Miklos Szeredi
@ 2014-01-13  7:52   ` Jan Kara
  2014-01-14 10:31     ` Miklos Szeredi
  0 siblings, 1 reply; 73+ messages in thread
From: Jan Kara @ 2014-01-13  7:52 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells,
	zab, jack, luto, mszeredi

On Wed 08-01-14 23:10:11, Miklos Szeredi wrote:
> From: Miklos Szeredi <mszeredi@suse.cz>
> 
> 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 <mszeredi@suse.cz>
> ---
>  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 <jack@suse.cz>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-08 22:10 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi
@ 2014-01-13 12:25   ` Jan Kara
  2014-01-14 10:35     ` Miklos Szeredi
  2014-01-15 18:23     ` J. Bruce Fields
  0 siblings, 2 replies; 73+ messages in thread
From: Jan Kara @ 2014-01-13 12:25 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells,
	zab, jack, luto, mszeredi

On Wed 08-01-14 23:10:15, Miklos Szeredi wrote:
> From: Miklos Szeredi <mszeredi@suse.cz>
> 
> 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 <jack@suse.cz>

One nitpick below...
 
> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
> ---
>  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 <jack@suse.cz>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 00/11] cross rename v3
  2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
                   ` (10 preceding siblings ...)
  2014-01-08 22:10 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi
@ 2014-01-13 12:46 ` Tetsuo Handa
  2014-01-13 17:08   ` Miklos Szeredi
  11 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-01-13 12:46 UTC (permalink / raw)
  To: miklos, viro, linux-security-module
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

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.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 00/11] cross rename v3
  2014-01-13 12:46 ` [PATCH 00/11] cross rename v3 Tetsuo Handa
@ 2014-01-13 17:08   ` Miklos Szeredi
  2014-01-13 22:03     ` Tetsuo Handa
  0 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-13 17:08 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: miklos, viro, linux-security-module, torvalds, linux-fsdevel,
	linux-kernel, hch, akpm, dhowells, zab, jack, luto

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



^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 00/11] cross rename v3
  2014-01-13 17:08   ` Miklos Szeredi
@ 2014-01-13 22:03     ` Tetsuo Handa
  2014-01-14  9:58       ` Miklos Szeredi
  0 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-01-13 22:03 UTC (permalink / raw)
  To: mszeredi, selinux, linux-security-module
  Cc: miklos, viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm,
	dhowells, zab, jack, luto

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.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 00/11] cross rename v3
  2014-01-13 22:03     ` Tetsuo Handa
@ 2014-01-14  9:58       ` Miklos Szeredi
  2014-01-14 13:03         ` Tetsuo Handa
  0 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-14  9:58 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: mszeredi, selinux, linux-security-module, Al Viro,
	Linus Torvalds, Linux-Fsdevel, Kernel Mailing List,
	Christoph Hellwig, Andrew Morton, David Howells, Zach Brown,
	Jan Kara, Andy Lutomirski

On Mon, Jan 13, 2014 at 11:03 PM, Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> 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

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 07/11] vfs: add cross-rename
  2014-01-13  7:52   ` Jan Kara
@ 2014-01-14 10:31     ` Miklos Szeredi
  2014-01-14 12:47       ` Jan Kara
  0 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-14 10:31 UTC (permalink / raw)
  To: Jan Kara
  Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells,
	zab, luto, mszeredi

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 <mszeredi@suse.cz>

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 <mszeredi@suse.cz>
---
 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);
 }

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-13 12:25   ` Jan Kara
@ 2014-01-14 10:35     ` Miklos Szeredi
  2014-01-15 18:23     ` J. Bruce Fields
  1 sibling, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-14 10:35 UTC (permalink / raw)
  To: Jan Kara
  Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells,
	zab, luto, mszeredi

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 <mszeredi@suse.cz>
> > 
> > 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 <jack@suse.cz>
> 
> 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 <mszeredi@suse.cz>

Implement RENAME_EXCHANGE flag in renameat2 syscall.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Reviewed-by: Jan Kara <jack@suse.cz>
---
 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);

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 07/11] vfs: add cross-rename
  2014-01-14 10:31     ` Miklos Szeredi
@ 2014-01-14 12:47       ` Jan Kara
  0 siblings, 0 replies; 73+ messages in thread
From: Jan Kara @ 2014-01-14 12:47 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Jan Kara, viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm,
	dhowells, zab, luto, mszeredi

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 <mszeredi@suse.cz>
> 
> 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 <jack@suse.cz>

								Honza

> 
> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
> ---
>  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 <jack@suse.cz>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 00/11] cross rename v3
  2014-01-14  9:58       ` Miklos Szeredi
@ 2014-01-14 13:03         ` Tetsuo Handa
  2014-01-14 20:10           ` John Johansen
  0 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-01-14 13:03 UTC (permalink / raw)
  To: miklos, john.johansen
  Cc: mszeredi, linux-security-module, viro, torvalds, linux-fsdevel,
	linux-kernel, hch, akpm, dhowells, zab, jack, luto

Miklos Szeredi wrote:
> On Mon, Jan 13, 2014 at 11:03 PM, Tetsuo Handa
> <penguin-kernel@i-love.sakura.ne.jp> 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 <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* Re: [PATCH 00/11] cross rename v3
  2014-01-14 13:03         ` Tetsuo Handa
@ 2014-01-14 20:10           ` John Johansen
  2014-01-14 20:53             ` Tetsuo Handa
  0 siblings, 1 reply; 73+ messages in thread
From: John Johansen @ 2014-01-14 20:10 UTC (permalink / raw)
  To: Tetsuo Handa, miklos
  Cc: mszeredi, linux-security-module, viro, torvalds, linux-fsdevel,
	linux-kernel, hch, akpm, dhowells, zab, jack, luto

On 01/14/2014 05:03 AM, Tetsuo Handa wrote:
> Miklos Szeredi wrote:
>> On Mon, Jan 13, 2014 at 11:03 PM, Tetsuo Handa
>> <penguin-kernel@i-love.sakura.ne.jp> 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 <penguin-kernel@I-love.SAKURA.ne.jp>
> 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 <penguin-kernel@I-love.SAKURA.ne.jp>
> ---
>  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,
> 


^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 00/11] cross rename v3
  2014-01-14 20:10           ` John Johansen
@ 2014-01-14 20:53             ` Tetsuo Handa
  2014-01-15 10:10               ` Miklos Szeredi
  0 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-01-14 20:53 UTC (permalink / raw)
  To: miklos
  Cc: john.johansen, mszeredi, linux-security-module, viro, torvalds,
	linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack,
	luto

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
> >> <penguin-kernel@i-love.sakura.ne.jp> 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 };

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* Re: [PATCH 04/11] vfs: add renameat2 syscall
  2014-01-08 22:10 ` [PATCH 04/11] vfs: add renameat2 syscall Miklos Szeredi
@ 2014-01-14 22:11   ` Tetsuo Handa
  2014-01-15 10:30     ` Miklos Szeredi
  0 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-01-14 22:11 UTC (permalink / raw)
  To: miklos
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

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.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 00/11] cross rename v3
  2014-01-14 20:53             ` Tetsuo Handa
@ 2014-01-15 10:10               ` Miklos Szeredi
  0 siblings, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-15 10:10 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: john.johansen, mszeredi, linux-security-module, viro, torvalds,
	linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab, jack,
	luto

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 };

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 04/11] vfs: add renameat2 syscall
  2014-01-14 22:11   ` Tetsuo Handa
@ 2014-01-15 10:30     ` Miklos Szeredi
  2014-01-15 13:50       ` Miklos Szeredi
  0 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-15 10:30 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

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

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 04/11] vfs: add renameat2 syscall
  2014-01-15 10:30     ` Miklos Szeredi
@ 2014-01-15 13:50       ` Miklos Szeredi
  2014-01-18 10:40         ` Tetsuo Handa
  0 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-15 13:50 UTC (permalink / raw)
  To: Tetsuo Handa
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

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(-)


^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag
  2014-01-08 22:10 ` [PATCH 05/11] vfs: add RENAME_NOREPLACE flag Miklos Szeredi
@ 2014-01-15 18:19   ` J. Bruce Fields
  2014-01-15 18:26     ` Andy Lutomirski
  2014-01-15 18:35     ` Miklos Szeredi
  0 siblings, 2 replies; 73+ messages in thread
From: J. Bruce Fields @ 2014-01-15 18:19 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells,
	zab, jack, luto, mszeredi

On Wed, Jan 08, 2014 at 11:10:09PM +0100, Miklos Szeredi wrote:
> From: Miklos Szeredi <mszeredi@suse.cz>
> 
> 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 <luto@amacapital.net>
> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
> ---
>  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 majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-13 12:25   ` Jan Kara
  2014-01-14 10:35     ` Miklos Szeredi
@ 2014-01-15 18:23     ` J. Bruce Fields
  2014-01-15 18:31       ` Miklos Szeredi
  1 sibling, 1 reply; 73+ messages in thread
From: J. Bruce Fields @ 2014-01-15 18:23 UTC (permalink / raw)
  To: Jan Kara
  Cc: Miklos Szeredi, viro, torvalds, linux-fsdevel, linux-kernel, hch,
	akpm, dhowells, zab, luto, mszeredi

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.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag
  2014-01-15 18:19   ` J. Bruce Fields
@ 2014-01-15 18:26     ` Andy Lutomirski
  2014-01-15 23:33       ` J. Bruce Fields
  2014-01-15 18:35     ` Miklos Szeredi
  1 sibling, 1 reply; 73+ messages in thread
From: Andy Lutomirski @ 2014-01-15 18:26 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Miklos Szeredi, Al Viro, Linus Torvalds, Linux FS Devel,
	linux-kernel, Christoph Hellwig, Andrew Morton, David Howells,
	Zach Brown, Jan Kara, mszeredi

On Wed, Jan 15, 2014 at 10:19 AM, J. Bruce Fields <bfields@fieldses.org> wrote:
> On Wed, Jan 08, 2014 at 11:10:09PM +0100, Miklos Szeredi wrote:
>> From: Miklos Szeredi <mszeredi@suse.cz>
>>
>> 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

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-15 18:23     ` J. Bruce Fields
@ 2014-01-15 18:31       ` Miklos Szeredi
  2014-01-16 10:54         ` Miklos Szeredi
  0 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-15 18:31 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Jan Kara, Al Viro, Linus Torvalds, Linux-Fsdevel,
	Kernel Mailing List, Christoph Hellwig, Andrew Morton,
	David Howells, Zach Brown, Andy Lutomirski, mszeredi

On Wed, Jan 15, 2014 at 7:23 PM, J. Bruce Fields <bfields@fieldses.org> 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

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag
  2014-01-15 18:19   ` J. Bruce Fields
  2014-01-15 18:26     ` Andy Lutomirski
@ 2014-01-15 18:35     ` Miklos Szeredi
  2014-01-15 23:31       ` J. Bruce Fields
  1 sibling, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-15 18:35 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Al Viro, Linus Torvalds, Linux-Fsdevel, Kernel Mailing List,
	Christoph Hellwig, Andrew Morton, David Howells, Zach Brown,
	Jan Kara, Andy Lutomirski, mszeredi

On Wed, Jan 15, 2014 at 7:19 PM, J. Bruce Fields <bfields@fieldses.org> wrote:
> On Wed, Jan 08, 2014 at 11:10:09PM +0100, Miklos Szeredi wrote:
>> From: Miklos Szeredi <mszeredi@suse.cz>
>>
>> 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

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag
  2014-01-15 18:35     ` Miklos Szeredi
@ 2014-01-15 23:31       ` J. Bruce Fields
  0 siblings, 0 replies; 73+ messages in thread
From: J. Bruce Fields @ 2014-01-15 23:31 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Al Viro, Linus Torvalds, Linux-Fsdevel, Kernel Mailing List,
	Christoph Hellwig, Andrew Morton, David Howells, Zach Brown,
	Jan Kara, Andy Lutomirski, mszeredi

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 <bfields@fieldses.org> wrote:
> > On Wed, Jan 08, 2014 at 11:10:09PM +0100, Miklos Szeredi wrote:
> >> From: Miklos Szeredi <mszeredi@suse.cz>
> >>
> >> 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.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag
  2014-01-15 18:26     ` Andy Lutomirski
@ 2014-01-15 23:33       ` J. Bruce Fields
  2014-01-16 10:45         ` Miklos Szeredi
  0 siblings, 1 reply; 73+ messages in thread
From: J. Bruce Fields @ 2014-01-15 23:33 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: Miklos Szeredi, Al Viro, Linus Torvalds, Linux FS Devel,
	linux-kernel, Christoph Hellwig, Andrew Morton, David Howells,
	Zach Brown, Jan Kara, mszeredi

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 <bfields@fieldses.org> wrote:
> > On Wed, Jan 08, 2014 at 11:10:09PM +0100, Miklos Szeredi wrote:
> >> From: Miklos Szeredi <mszeredi@suse.cz>
> >>
> >> 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.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 05/11] vfs: add RENAME_NOREPLACE flag
  2014-01-15 23:33       ` J. Bruce Fields
@ 2014-01-16 10:45         ` Miklos Szeredi
  0 siblings, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-16 10:45 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Andy Lutomirski, Al Viro, Linus Torvalds, Linux FS Devel,
	linux-kernel, Christoph Hellwig, Andrew Morton, David Howells,
	Zach Brown, Jan Kara, mszeredi

On Thu, Jan 16, 2014 at 12:33 AM, J. Bruce Fields <bfields@fieldses.org> 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

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-15 18:31       ` Miklos Szeredi
@ 2014-01-16 10:54         ` Miklos Szeredi
  2014-01-16 14:48           ` J. Bruce Fields
  2014-01-17 10:53           ` Michael Kerrisk (man-pages)
  0 siblings, 2 replies; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-16 10:54 UTC (permalink / raw)
  To: Michael Kerrisk
  Cc: J. Bruce Fields, Jan Kara, Al Viro, Linus Torvalds,
	Linux-Fsdevel, Kernel Mailing List, Christoph Hellwig,
	Andrew Morton, David Howells, Zach Brown, Andy Lutomirski,
	mszeredi

[-- Attachment #1: Type: text/plain, Size: 4274 bytes --]

> On Wed, Jan 15, 2014 at 7:23 PM, J. Bruce Fields <bfields@fieldses.org> 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)

[-- Attachment #2: renameat.2.patch --]
[-- Type: text/x-patch, Size: 3019 bytes --]

diff --git a/man2/renameat.2 b/man2/renameat.2
index 432ddfd..e2f80d8 100644
--- a/man2/renameat.2
+++ b/man2/renameat.2
@@ -24,7 +24,7 @@
 .\"
 .TH RENAMEAT 2 2012-05-04 "Linux" "Linux Programmer's Manual"
 .SH NAME
-renameat \- rename a file relative to directory file descriptors
+renameat, renameat2 \- rename a file relative to directory file descriptors
 .SH SYNOPSIS
 .nf
 .B #include <fcntl.h>           /* Definition of AT_* constants */
@@ -32,6 +32,9 @@ renameat \- rename a file relative to directory file descriptors
 .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
@@ -55,7 +58,9 @@ _ATFILE_SOURCE
 .SH DESCRIPTION
 The
 .BR renameat ()
-system call operates in exactly the same way as
+and
+.BR renameat2 ()
+system calls operate in exactly the same way as
 .BR rename (2),
 except for the differences described in this manual page.
 
@@ -94,10 +99,37 @@ is as for
 except that a relative pathname is interpreted relative
 to the directory referred to by the file descriptor
 .IR newdirfd .
+
+.BR renameat2 ()
+has an additional
+.I flags
+argument.
+.BR renameat2 ()
+call with a zero
+.I flags
+argument is equivalent to
+.BR renameat ().
+
+The
+.I flags
+argument is a bitfield consisting of zero or more of the following
+constants defined in
+.IR <linux/fs.h> :
+.TP
+.B RENAME_NOREPLACE
+Don't overwrite the target of the rename.  Return an error
+if the target would be overwritten.
+.TP
+.B 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).
 .SH RETURN VALUE
 On success,
 .BR renameat ()
-returns 0.
+and
+.BR renameat2 ()
+return 0.
 On error, \-1 is returned and
 .I errno
 is set to indicate the error.
@@ -105,9 +137,13 @@ is set to indicate the error.
 The same errors that occur for
 .BR rename (2)
 can also occur for
-.BR renameat ().
+.BR renameat ()
+and
+.BR renameat2 ().
 The following additional errors can occur for
-.BR renameat ():
+.BR renameat ()
+and
+.BR renameat2 ():
 .TP
 .B EBADF
 .I olddirfd
@@ -124,12 +160,37 @@ or similar for
 .I newpath
 and
 .I newdirfd
+.RE
+.PP
+The following additional errors are defined for
+.BR renameat2 ():
+.TP
+.B EOPNOTSUPP
+The filesystem does not support a flag in
+.I flags
+.TP 
+.B EINVAL
+Invalid combination of
+.I flags
+.TP
+.B EEXIST
+.I flags
+contain
+.B RENAME_NOREPLACE
+and the target of the rename exists
+.TP
+.B ENOENT
+.I flags
+contain
+.B RENAME_EXCHANGE
+and the target of the rename 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.
 .SH CONFORMING TO
-POSIX.1-2008.
+.BR renameat ()
+is specified in POSIX.1-2008.
 .SH NOTES
 See
 .BR openat (2)

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-16 10:54         ` Miklos Szeredi
@ 2014-01-16 14:48           ` J. Bruce Fields
  2014-01-17 10:53           ` Michael Kerrisk (man-pages)
  1 sibling, 0 replies; 73+ messages in thread
From: J. Bruce Fields @ 2014-01-16 14:48 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Michael Kerrisk, Jan Kara, Al Viro, Linus Torvalds,
	Linux-Fsdevel, Kernel Mailing List, Christoph Hellwig,
	Andrew Morton, David Howells, Zach Brown, Andy Lutomirski,
	mszeredi

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 <bfields@fieldses.org> 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.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-16 10:54         ` Miklos Szeredi
  2014-01-16 14:48           ` J. Bruce Fields
@ 2014-01-17 10:53           ` Michael Kerrisk (man-pages)
  2014-01-17 14:41             ` Miklos Szeredi
  2014-01-17 22:08             ` J. Bruce Fields
  1 sibling, 2 replies; 73+ messages in thread
From: Michael Kerrisk (man-pages) @ 2014-01-17 10:53 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: mtk.manpages, J. Bruce Fields, Jan Kara, Al Viro, Linus Torvalds,
	Linux-Fsdevel, Kernel Mailing List, Christoph Hellwig,
	Andrew Morton, David Howells, Zach Brown, Andy Lutomirski,
	mszeredi

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 <bfields@fieldses.org> 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/

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-17 10:53           ` Michael Kerrisk (man-pages)
@ 2014-01-17 14:41             ` Miklos Szeredi
  2014-04-19  9:08               ` Michael Kerrisk (man-pages)
  2014-01-17 22:08             ` J. Bruce Fields
  1 sibling, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-17 14:41 UTC (permalink / raw)
  To: Michael Kerrisk (man-pages)
  Cc: J. Bruce Fields, Jan Kara, Al Viro, Linus Torvalds,
	Linux-Fsdevel, Kernel Mailing List, Christoph Hellwig,
	Andrew Morton, David Howells, Zach Brown, Andy Lutomirski,
	mszeredi

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)

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-17 10:53           ` Michael Kerrisk (man-pages)
  2014-01-17 14:41             ` Miklos Szeredi
@ 2014-01-17 22:08             ` J. Bruce Fields
  2014-01-18  6:49               ` Miklos Szeredi
  1 sibling, 1 reply; 73+ messages in thread
From: J. Bruce Fields @ 2014-01-17 22:08 UTC (permalink / raw)
  To: Michael Kerrisk (man-pages)
  Cc: Miklos Szeredi, Jan Kara, Al Viro, Linus Torvalds, Linux-Fsdevel,
	Kernel Mailing List, Christoph Hellwig, Andrew Morton,
	David Howells, Zach Brown, Andy Lutomirski, mszeredi

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 <bfields@fieldses.org> 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 majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-17 22:08             ` J. Bruce Fields
@ 2014-01-18  6:49               ` Miklos Szeredi
  2014-01-18 16:27                 ` J. Bruce Fields
  0 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-18  6:49 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Michael Kerrisk (man-pages),
	Jan Kara, Al Viro, Linus Torvalds, Linux-Fsdevel,
	Kernel Mailing List, Christoph Hellwig, Andrew Morton,
	David Howells, Zach Brown, Andy Lutomirski, mszeredi

On Fri, Jan 17, 2014 at 11:08 PM, J. Bruce Fields <bfields@fieldses.org> 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

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 04/11] vfs: add renameat2 syscall
  2014-01-15 13:50       ` Miklos Szeredi
@ 2014-01-18 10:40         ` Tetsuo Handa
  0 siblings, 0 replies; 73+ messages in thread
From: Tetsuo Handa @ 2014-01-18 10:40 UTC (permalink / raw)
  To: miklos, linux-security-module
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

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 <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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 <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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 <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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 <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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 <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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
----------------------------------------

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-18  6:49               ` Miklos Szeredi
@ 2014-01-18 16:27                 ` J. Bruce Fields
  2014-01-20 11:39                   ` Miklos Szeredi
  0 siblings, 1 reply; 73+ messages in thread
From: J. Bruce Fields @ 2014-01-18 16:27 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Michael Kerrisk (man-pages),
	Jan Kara, Al Viro, Linus Torvalds, Linux-Fsdevel,
	Kernel Mailing List, Christoph Hellwig, Andrew Morton,
	David Howells, Zach Brown, Andy Lutomirski, mszeredi

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 <bfields@fieldses.org> 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.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-18 16:27                 ` J. Bruce Fields
@ 2014-01-20 11:39                   ` Miklos Szeredi
  2014-01-20 11:50                     ` Michael Kerrisk (man-pages)
  0 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-01-20 11:39 UTC (permalink / raw)
  To: J. Bruce Fields
  Cc: Michael Kerrisk (man-pages),
	Jan Kara, Al Viro, Linus Torvalds, Linux-Fsdevel,
	Kernel Mailing List, Christoph Hellwig, Andrew Morton,
	David Howells, Zach Brown, Andy Lutomirski, mszeredi

On Sat, Jan 18, 2014 at 5:27 PM, J. Bruce Fields <bfields@fieldses.org> 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 <bfields@fieldses.org> 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

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-20 11:39                   ` Miklos Szeredi
@ 2014-01-20 11:50                     ` Michael Kerrisk (man-pages)
  0 siblings, 0 replies; 73+ messages in thread
From: Michael Kerrisk (man-pages) @ 2014-01-20 11:50 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: J. Bruce Fields, Jan Kara, Al Viro, Linus Torvalds,
	Linux-Fsdevel, Kernel Mailing List, Christoph Hellwig,
	Andrew Morton, David Howells, Zach Brown, Andy Lutomirski,
	mszeredi

On Mon, Jan 20, 2014 at 12:39 PM, Miklos Szeredi <miklos@szeredi.hu> wrote:
> On Sat, Jan 18, 2014 at 5:27 PM, J. Bruce Fields <bfields@fieldses.org> 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 <bfields@fieldses.org> 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/

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-01-17 14:41             ` Miklos Szeredi
@ 2014-04-19  9:08               ` Michael Kerrisk (man-pages)
  2014-04-19 12:08                 ` Tetsuo Handa
  2014-04-23 14:21                 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi
  0 siblings, 2 replies; 73+ messages in thread
From: Michael Kerrisk (man-pages) @ 2014-04-19  9:08 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: mtk.manpages, J. Bruce Fields, Jan Kara, Al Viro, Linus Torvalds,
	Linux-Fsdevel, Kernel Mailing List, Christoph Hellwig,
	Andrew Morton, David Howells, Zach Brown, Andy Lutomirski,
	mszeredi, linux-man, Linux API, Carlos O'Donell

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 <michael@moria.de>
 .\" 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/

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-04-19  9:08               ` Michael Kerrisk (man-pages)
@ 2014-04-19 12:08                 ` Tetsuo Handa
  2014-04-23 14:24                   ` Miklos Szeredi
  2014-04-23 14:21                 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi
  1 sibling, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-04-19 12:08 UTC (permalink / raw)
  To: miklos, linux-security-module; +Cc: linux-fsdevel, linux-kernel

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.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-04-19  9:08               ` Michael Kerrisk (man-pages)
  2014-04-19 12:08                 ` Tetsuo Handa
@ 2014-04-23 14:21                 ` Miklos Szeredi
  2014-04-23 19:01                   ` Michael Kerrisk (man-pages)
  1 sibling, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-04-23 14:21 UTC (permalink / raw)
  To: Michael Kerrisk (man-pages)
  Cc: J. Bruce Fields, Jan Kara, Al Viro, Linus Torvalds,
	Linux-Fsdevel, Kernel Mailing List, Christoph Hellwig,
	Andrew Morton, David Howells, Zach Brown, Andy Lutomirski,
	mszeredi, linux-man, Linux API, Carlos O'Donell

On Sat, Apr 19, 2014 at 11:08 AM, Michael Kerrisk (man-pages)
<mtk.manpages@gmail.com> 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

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-04-19 12:08                 ` Tetsuo Handa
@ 2014-04-23 14:24                   ` Miklos Szeredi
  2014-04-24 11:20                     ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
  0 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2014-04-23 14:24 UTC (permalink / raw)
  To: Tetsuo Handa; +Cc: linux-security-module, Linux-Fsdevel, Kernel Mailing List

On Sat, Apr 19, 2014 at 2:08 PM, Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> 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

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2014-04-23 14:21                 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi
@ 2014-04-23 19:01                   ` Michael Kerrisk (man-pages)
  0 siblings, 0 replies; 73+ messages in thread
From: Michael Kerrisk (man-pages) @ 2014-04-23 19:01 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: mtk.manpages, J. Bruce Fields, Jan Kara, Al Viro, Linus Torvalds,
	Linux-Fsdevel, Kernel Mailing List, Christoph Hellwig,
	Andrew Morton, David Howells, Zach Brown, Andy Lutomirski,
	mszeredi, linux-man, Linux API, Carlos O'Donell

On 04/23/2014 04:21 PM, Miklos Szeredi wrote:
> On Sat, Apr 19, 2014 at 11:08 AM, Michael Kerrisk (man-pages)
> <mtk.manpages@gmail.com> 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/

^ permalink raw reply	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM.
  2014-04-23 14:24                   ` Miklos Szeredi
@ 2014-04-24 11:20                     ` Tetsuo Handa
  2014-04-24 11:22                       ` [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module Tetsuo Handa
                                         ` (5 more replies)
  0 siblings, 6 replies; 73+ messages in thread
From: Tetsuo Handa @ 2014-04-24 11:20 UTC (permalink / raw)
  To: miklos; +Cc: linux-security-module, linux-fsdevel, linux-kernel

Miklos Szeredi wrote:
> On Sat, Apr 19, 2014 at 2:08 PM, Tetsuo Handa
> <penguin-kernel@i-love.sakura.ne.jp> 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.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module.
  2014-04-24 11:20                     ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
@ 2014-04-24 11:22                       ` Tetsuo Handa
  2014-04-25 20:49                         ` Casey Schaufler
  2014-04-24 11:23                       ` [PATCH (for 3.15) 2/5] SELinux: Handle the rename flags Tetsuo Handa
                                         ` (4 subsequent siblings)
  5 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-04-24 11:22 UTC (permalink / raw)
  To: linux-security-module; +Cc: linux-fsdevel, linux-kernel, miklos

>From 207346b4153e2b441e0c45d933043e6ba7abb419 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 2/5] SELinux: Handle the rename flags.
  2014-04-24 11:20                     ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
  2014-04-24 11:22                       ` [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module Tetsuo Handa
@ 2014-04-24 11:23                       ` Tetsuo Handa
  2014-04-24 11:24                       ` [PATCH (for 3.15) 3/5] AppArmor: " Tetsuo Handa
                                         ` (3 subsequent siblings)
  5 siblings, 0 replies; 73+ messages in thread
From: Tetsuo Handa @ 2014-04-24 11:23 UTC (permalink / raw)
  To: linux-security-module; +Cc: linux-fsdevel, linux-kernel, miklos

>From d095d350fda528b993226752102caeda2d95af46 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 3/5] AppArmor: Handle the rename flags.
  2014-04-24 11:20                     ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
  2014-04-24 11:22                       ` [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module Tetsuo Handa
  2014-04-24 11:23                       ` [PATCH (for 3.15) 2/5] SELinux: Handle the rename flags Tetsuo Handa
@ 2014-04-24 11:24                       ` Tetsuo Handa
  2014-04-24 11:25                       ` [PATCH (for 3.15) 4/5] TOMOYO: " Tetsuo Handa
                                         ` (2 subsequent siblings)
  5 siblings, 0 replies; 73+ messages in thread
From: Tetsuo Handa @ 2014-04-24 11:24 UTC (permalink / raw)
  To: linux-security-module; +Cc: linux-fsdevel, linux-kernel, miklos

>From 3bce55d24ef7a55613d748182aac1f3986c144da Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 4/5] TOMOYO: Handle the rename flags.
  2014-04-24 11:20                     ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
                                         ` (2 preceding siblings ...)
  2014-04-24 11:24                       ` [PATCH (for 3.15) 3/5] AppArmor: " Tetsuo Handa
@ 2014-04-24 11:25                       ` Tetsuo Handa
  2014-04-24 11:26                       ` [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling Tetsuo Handa
  2014-05-01 11:58                       ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
  5 siblings, 0 replies; 73+ messages in thread
From: Tetsuo Handa @ 2014-04-24 11:25 UTC (permalink / raw)
  To: linux-security-module; +Cc: linux-fsdevel, linux-kernel, miklos

>From 85b260e2e72962efb3991a606496cf237e739bb0 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling.
  2014-04-24 11:20                     ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
                                         ` (3 preceding siblings ...)
  2014-04-24 11:25                       ` [PATCH (for 3.15) 4/5] TOMOYO: " Tetsuo Handa
@ 2014-04-24 11:26                       ` Tetsuo Handa
  2014-05-01 11:58                       ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
  5 siblings, 0 replies; 73+ messages in thread
From: Tetsuo Handa @ 2014-04-24 11:26 UTC (permalink / raw)
  To: linux-security-module; +Cc: linux-fsdevel, linux-kernel, miklos

>From fe5ea6a9442c95725d3c27eddfde754acd8c9785 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* Re: [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module.
  2014-04-24 11:22                       ` [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module Tetsuo Handa
@ 2014-04-25 20:49                         ` Casey Schaufler
  0 siblings, 0 replies; 73+ messages in thread
From: Casey Schaufler @ 2014-04-25 20:49 UTC (permalink / raw)
  To: Tetsuo Handa, linux-security-module; +Cc: linux-fsdevel, linux-kernel, miklos

On 4/24/2014 4:22 AM, Tetsuo Handa wrote:
> >From 207346b4153e2b441e0c45d933043e6ba7abb419 Mon Sep 17 00:00:00 2001
> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> 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 <penguin-kernel@I-love.SAKURA.ne.jp>

Acked-by: Casey Schaufler <casey@schaufler-ca.com>

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 };


^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM.
  2014-04-24 11:20                     ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
                                         ` (4 preceding siblings ...)
  2014-04-24 11:26                       ` [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling Tetsuo Handa
@ 2014-05-01 11:58                       ` Tetsuo Handa
  2014-05-05  5:49                         ` Tetsuo Handa
  5 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-05-01 11:58 UTC (permalink / raw)
  To: jmorris; +Cc: linux-security-module, linux-fsdevel, linux-kernel

James, would you send this patchset to Linus?
This patchset is expected to go to 3.15 because this is a kind of regression fix.

Tetsuo Handa wrote:
> Miklos Szeredi wrote:
> > On Sat, Apr 19, 2014 at 2:08 PM, Tetsuo Handa
> > <penguin-kernel@i-love.sakura.ne.jp> 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.
> 

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM.
  2014-05-01 11:58                       ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
@ 2014-05-05  5:49                         ` Tetsuo Handa
  2014-05-11 15:53                           ` Tetsuo Handa
  0 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-05-05  5:49 UTC (permalink / raw)
  To: selinux; +Cc: linux-security-module, linux-fsdevel, linux-kernel, torvalds

Hello SELinux people, may I have your response?

Miklos Szeredi posted v3 of cross rename patchset at
http://marc.info/?l=linux-fsdevel&m=138921924707564&w=4 . In that thread,
I questioned him whether he already explained this proposal to LSM people.
He answered no and explained me what renameat2() does. I asked him to get
confirmation from LSM people before he merges this change to linux-next.git
tree, and he answered that patch 07/11 already does what LSM people need. But
I commented that TOMOYO wants to avoid re-calculation of pathnames and posted
a patch at http://marc.info/?l=linux-security-module&m=138970469226106&w=4 and
John Johansen commented that the changes in AppArmor directory looks OK and
John would re-factor AppArmor to avoid re-calculation in the future. Therefore,
I posted a patch for SELinux/SMACK/Capability at
http://marc.info/?l=linux-fsdevel&m=138973289404450&w=4 but Miklos responded
that he doesn't want to include my patch which temporarily breaks TOMOYO and
AppArmor. Instead, he asked me to post a complete patch right after his cross
rename patchset goes in. Thus, I was waiting for his cross rename patchset to
arrive at linux-next.git tree.

By the day Linux 3.14 was released, Miklos's cross rename patchset did not
arrive at linux-next.git tree. Therefore, I assumed that the cross rename
patchset will not go to Linux 3.15-rc1. However, the patchset committed after
Linux 3.14 release (commit da1ce067 "vfs: add cross-rename") directly went to
linux.git tree without letting it known to LSM people and the merge window for
Linux 3.15 was closed. Then, I noticed that renameat2() will be in 3.15 at
http://marc.info/?l=linux-fsdevel&m=139789855823422&w=2 .

At first, I assumed that renameat2() is not callable as of 3.15 because I
couldn't find "#define __NR_renameat2" line. But Miklos told me that
renameat2() will be callable as of 3.15 because x86 automatically generates
__NR_renameat2 entries. I realized that TOMOYO and AppArmor now have a race
window (a kind of regression) introduced by the cross rename functionality, and
I re-posted my patch as a patchset in this thread. I can approve the changes in
TOMOYO directory and John Johansen already gave me a sort of Reviewed-by:
response to the changes in AppArmor directory in January's thread. In this
thread, Casey Schaufler gave me an Acked-by: response to the changes in SMACK
directory, but so far I have gotten no response from SELinux people.

Would somebody in SELinux community please respond to the changes in SELinux
directory so that we can merge this race window fix patchset before 3.15-final?

Regards.

Tetsuo Handa wrote:
> James, would you send this patchset to Linus?
> This patchset is expected to go to 3.15 because this is a kind of regression fix.
> 
> Tetsuo Handa wrote:
> > Miklos Szeredi wrote:
> > > On Sat, Apr 19, 2014 at 2:08 PM, Tetsuo Handa
> > > <penguin-kernel@i-love.sakura.ne.jp> 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.
> > 
> 

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM.
  2014-05-05  5:49                         ` Tetsuo Handa
@ 2014-05-11 15:53                           ` Tetsuo Handa
  2014-05-12 13:21                             ` [PATCH (for 3.15) 0/5] Fix cross rename regressions " Tetsuo Handa
  0 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-05-11 15:53 UTC (permalink / raw)
  To: miklos, jmorris, john.johansen
  Cc: selinux, linux-security-module, linux-fsdevel, linux-kernel, torvalds

Miklos, I have a question regarding renameat2(RENAME_EXCHANGE).
renameat2(RENAME_EXCHANGE) allows exchanging a directory and a non-directory,
doesn't it? If yes, TOMOYO and AppArmor got a regression bug described below.

TOMOYO and AppArmor handle pathnames of a directory and a non-directory
differently. It is considered as a directory's pathname if the pathname ends
with / , and it is considered as a non-directory's pathname if the pathname
does not end with / .

Until renameat2(RENAME_EXCHANGE) was introduced, if the old_dentry refers a
directory, it is guaranteed that the new_dentry will refer a directory upon
successful rename() operation. Therefore, TOMOYO and AppArmor automatically
appended / to the pathname calculated from new_dentry based on
S_ISDIR(old_dentry->d_inode->i_mode).

  int tomoyo_path2_perm(const u8 operation, struct path *path1,
                        struct path *path2)
  {
  (...snipped...)
          if (!tomoyo_get_realpath(&buf1, path1) ||
              !tomoyo_get_realpath(&buf2, path2))
                  goto out;
          switch (operation) {
                  struct dentry *dentry;
          case TOMOYO_TYPE_RENAME:
          case TOMOYO_TYPE_LINK:
                  dentry = path1->dentry;
                  if (!dentry->d_inode || !S_ISDIR(dentry->d_inode->i_mode))
                          break;
                  /* fall through */
          case TOMOYO_TYPE_PIVOT_ROOT:
                  tomoyo_add_slash(&buf1);
                  tomoyo_add_slash(&buf2);
                  break;
          }
  (...snipped...)
  }

  static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
                                  struct path *new_dir, struct dentry *new_dentry)
  {
  (...snipped...)
                  struct path_cond cond = { old_dentry->d_inode->i_uid,
                                            old_dentry->d_inode->i_mode
                  };
  
                  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,
                                       &cond);
                  if (!error)
                          error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
                                               0, MAY_WRITE | AA_MAY_META_WRITE |
                                               AA_MAY_CREATE, &cond);
  (...snipped...)
  }

Now, as renameat2(RENAME_EXCHANGE) is introduced, it is no longer guaranteed
that the new_dentry will refer a directory upon successful
renameat2(RENAME_EXCHANGE) because renameat2(RENAME_EXCHANGE) allows exchanging
a directory's pathname and a non-directory's pathname. TOMOYO and AppArmor can
no longer automatically append / to the pathname calculated from new_dentry
based on S_ISDIR(old_dentry->d_inode->i_mode).

To fix this problem, TOMOYO and AppArmor need to receive the flags argument and
use S_ISDIR(new_dentry->d_inode->i_mode) if renameat2(RENAME_EXCHANGE) and use
S_ISDIR(old_dentry->d_inode->i_mode) if renameat2(!RENAME_EXCHANGE).

Regarding TOMOYO, "[PATCH (for 3.15) 4/5] TOMOYO: Handle the rename flags." can
fix this problem. Regarding AppArmor, we need to update
"[PATCH (for 3.15) 3/5] AppArmor: Handle the rename flags.".

Anyway, James, can we merge
"[PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module." now?

Tetsuo Handa wrote:
> Hello SELinux people, may I have your response?
> 
> Miklos Szeredi posted v3 of cross rename patchset at
> http://marc.info/?l=linux-fsdevel&m=138921924707564&w=4 . In that thread,
> I questioned him whether he already explained this proposal to LSM people.
> He answered no and explained me what renameat2() does. I asked him to get
> confirmation from LSM people before he merges this change to linux-next.git
> tree, and he answered that patch 07/11 already does what LSM people need. But
> I commented that TOMOYO wants to avoid re-calculation of pathnames and posted
> a patch at http://marc.info/?l=linux-security-module&m=138970469226106&w=4 and
> John Johansen commented that the changes in AppArmor directory looks OK and
> John would re-factor AppArmor to avoid re-calculation in the future. Therefore,
> I posted a patch for SELinux/SMACK/Capability at
> http://marc.info/?l=linux-fsdevel&m=138973289404450&w=4 but Miklos responded
> that he doesn't want to include my patch which temporarily breaks TOMOYO and
> AppArmor. Instead, he asked me to post a complete patch right after his cross
> rename patchset goes in. Thus, I was waiting for his cross rename patchset to
> arrive at linux-next.git tree.
> 
> By the day Linux 3.14 was released, Miklos's cross rename patchset did not
> arrive at linux-next.git tree. Therefore, I assumed that the cross rename
> patchset will not go to Linux 3.15-rc1. However, the patchset committed after
> Linux 3.14 release (commit da1ce067 "vfs: add cross-rename") directly went to
> linux.git tree without letting it known to LSM people and the merge window for
> Linux 3.15 was closed. Then, I noticed that renameat2() will be in 3.15 at
> http://marc.info/?l=linux-fsdevel&m=139789855823422&w=2 .
> 
> At first, I assumed that renameat2() is not callable as of 3.15 because I
> couldn't find "#define __NR_renameat2" line. But Miklos told me that
> renameat2() will be callable as of 3.15 because x86 automatically generates
> __NR_renameat2 entries. I realized that TOMOYO and AppArmor now have a race
> window (a kind of regression) introduced by the cross rename functionality, and
> I re-posted my patch as a patchset in this thread. I can approve the changes in
> TOMOYO directory and John Johansen already gave me a sort of Reviewed-by:
> response to the changes in AppArmor directory in January's thread. In this
> thread, Casey Schaufler gave me an Acked-by: response to the changes in SMACK
> directory, but so far I have gotten no response from SELinux people.
> 
> Would somebody in SELinux community please respond to the changes in SELinux
> directory so that we can merge this race window fix patchset before 3.15-final?
> 
> Regards.
> 
> Tetsuo Handa wrote:
> > James, would you send this patchset to Linus?
> > This patchset is expected to go to 3.15 because this is a kind of regression fix.
> > 
> > Tetsuo Handa wrote:
> > > Miklos Szeredi wrote:
> > > > On Sat, Apr 19, 2014 at 2:08 PM, Tetsuo Handa
> > > > <penguin-kernel@i-love.sakura.ne.jp> 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.
> > > 
> > 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

^ permalink raw reply	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 0/5] Fix cross rename regressions for LSM.
  2014-05-11 15:53                           ` Tetsuo Handa
@ 2014-05-12 13:21                             ` Tetsuo Handa
  2014-05-12 13:22                               ` [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module Tetsuo Handa
                                                 ` (4 more replies)
  0 siblings, 5 replies; 73+ messages in thread
From: Tetsuo Handa @ 2014-05-12 13:21 UTC (permalink / raw)
  To: miklos, jmorris, john.johansen, selinux
  Cc: linux-security-module, linux-fsdevel, linux-kernel, torvalds

Changes from previous patchset.

  Clarified that this is a regression fix.
  AppArmor: Pass appropriate inode's mode to aa_path_perm().
  TOMOYO: Changed "file swapname" permission to "file exchange" permission.

^ permalink raw reply	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module.
  2014-05-12 13:21                             ` [PATCH (for 3.15) 0/5] Fix cross rename regressions " Tetsuo Handa
@ 2014-05-12 13:22                               ` Tetsuo Handa
  2014-05-19 12:19                                 ` John Johansen
  2014-05-12 13:23                               ` [PATCH (for 3.15) 2/5] SELinux: Handle the rename flags Tetsuo Handa
                                                 ` (3 subsequent siblings)
  4 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-05-12 13:22 UTC (permalink / raw)
  To: miklos, jmorris, john.johansen, selinux
  Cc: linux-security-module, linux-fsdevel, linux-kernel, torvalds

>From 579cfccf7ba49a53ff0f5fefe73f0b8dc618d878 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: Mon, 12 May 2014 21:05:52 +0900
Subject: [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module.

TOMOYO and AppArmor distinguish a directory's pathname and a non-directory's
pathname by whether the pathname ends with / character or not. Upon rename(),
they appended / character to the target dentry's pathname if the source
dentry's inode refers a directory, for the target dentry's inode will refer
a directory after successful rename(). This assumption was correct until
commit da1ce067 "vfs: add cross-rename" is introduced.

Commit da1ce067 changed security_{inode,path}_rename() to call LSM module
with reversed arguments if the rename flags contains RENAME_EXCHANGE. But
this is not sufficient for TOMOYO and AppArmor because RENAME_EXCHANGE
allows exchange of a directory's inode and a non-directory's inode. Also,
this is redundant for SMACK because there is no need to call
security_inode_rename() with reversed arguments.

To fix this regression, the rename flags needs to be passed to LSM module.
This patch is for allowing TOMOYO and AppArmor to handle RENAME_EXCHANGE
case differently, and for allowing SMACK to avoid needlessly checking
the same permission twice.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Acked-by: Casey Schaufler <casey@schaufler-ca.com> [smack]
---
 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 6478ce3..5158611 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 ad0d4de..620ba79 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 8b774f3..81d7ffe 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 2c7341d..33f6f56 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 14f52be..4c1933f 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.1

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 2/5] SELinux: Handle the rename flags.
  2014-05-12 13:21                             ` [PATCH (for 3.15) 0/5] Fix cross rename regressions " Tetsuo Handa
  2014-05-12 13:22                               ` [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module Tetsuo Handa
@ 2014-05-12 13:23                               ` Tetsuo Handa
  2014-05-12 13:24                               ` [PATCH (for 3.15) 3/5] AppArmor: " Tetsuo Handa
                                                 ` (2 subsequent siblings)
  4 siblings, 0 replies; 73+ messages in thread
From: Tetsuo Handa @ 2014-05-12 13:23 UTC (permalink / raw)
  To: miklos, jmorris, john.johansen, selinux
  Cc: linux-security-module, linux-fsdevel, linux-kernel, torvalds

>From 39e4aea3b42b53d844c87f6c7e54f0a9837ebbfd Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: Mon, 12 May 2014 21:34:36 +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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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 33f6f56..64d8497 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.1

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 3/5] AppArmor: Handle the rename flags.
  2014-05-12 13:21                             ` [PATCH (for 3.15) 0/5] Fix cross rename regressions " Tetsuo Handa
  2014-05-12 13:22                               ` [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module Tetsuo Handa
  2014-05-12 13:23                               ` [PATCH (for 3.15) 2/5] SELinux: Handle the rename flags Tetsuo Handa
@ 2014-05-12 13:24                               ` Tetsuo Handa
  2014-05-19 12:28                                 ` John Johansen
  2014-05-12 13:25                               ` [PATCH (for 3.15) 4/5] TOMOYO: " Tetsuo Handa
  2014-05-12 13:25                               ` [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling Tetsuo Handa
  4 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-05-12 13:24 UTC (permalink / raw)
  To: miklos, jmorris, john.johansen, selinux
  Cc: linux-security-module, linux-fsdevel, linux-kernel, torvalds

>From 819e94ae3a6d9235196d137a39afa4e0bbd79770 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: Mon, 12 May 2014 21:54:05 +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" and "distinguish condition of source and target".
Future patches will stop re-calculating pathnames.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 security/apparmor/lsm.c |   22 ++++++++++++++++++++--
 1 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index c0b4366..9f21296 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -331,7 +331,14 @@ 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
 		};
+		struct path_cond new_cond = cond;
 
+		if (flags & RENAME_EXCHANGE) {
+			/* Cross rename requires both inodes to exist. */
+			new_cond.uid = new_dentry->d_inode->i_uid;
+			new_cond.mode = new_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 +346,18 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
 		if (!error)
 			error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
 					     0, MAY_WRITE | AA_MAY_META_WRITE |
-					     AA_MAY_CREATE, &cond);
+					     AA_MAY_CREATE, &new_cond);
+		if (!error && (flags & RENAME_EXCHANGE)) {
+			struct path tmp_path = new_path;
+			struct path_cond tmp_cond = new_cond;
+
+			new_path = old_path;
+			old_path = tmp_path;
+			new_cond = cond;
+			cond = tmp_cond;
+			flags = 0;
+			goto retry;
+		}
 
 	}
 	return error;
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 4/5] TOMOYO: Handle the rename flags.
  2014-05-12 13:21                             ` [PATCH (for 3.15) 0/5] Fix cross rename regressions " Tetsuo Handa
                                                 ` (2 preceding siblings ...)
  2014-05-12 13:24                               ` [PATCH (for 3.15) 3/5] AppArmor: " Tetsuo Handa
@ 2014-05-12 13:25                               ` Tetsuo Handa
  2014-05-12 13:25                               ` [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling Tetsuo Handa
  4 siblings, 0 replies; 73+ messages in thread
From: Tetsuo Handa @ 2014-05-12 13:25 UTC (permalink / raw)
  To: miklos, jmorris, john.johansen, selinux
  Cc: linux-security-module, linux-fsdevel, linux-kernel, torvalds

>From b53f2feeb60fb11a0c2d3ee3021a057bd6d71eeb Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: Mon, 12 May 2014 22:00:53 +0900
Subject: [PATCH (for 3.15) 4/5] TOMOYO: Handle the rename flags.

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

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 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..7892485 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_EXCHANGE]   = "exchange",
 	[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..a167c5f 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_EXCHANGE,
 	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_EXCHANGE,
 	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 exchange", "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..5f10b76 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_EXCHANGE]   = TOMOYO_MAC_FILE_EXCHANGE,
 	[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", "exchange", "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_EXCHANGE:
+		/* 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..aa36259 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_EXCHANGE : TOMOYO_TYPE_RENAME,
+				 &path1, &path2);
 }
 
 /**
diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c
index 2952ba5..649794b 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_EXCHANGE]   = 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

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling.
  2014-05-12 13:21                             ` [PATCH (for 3.15) 0/5] Fix cross rename regressions " Tetsuo Handa
                                                 ` (3 preceding siblings ...)
  2014-05-12 13:25                               ` [PATCH (for 3.15) 4/5] TOMOYO: " Tetsuo Handa
@ 2014-05-12 13:25                               ` Tetsuo Handa
  2014-05-19 12:34                                 ` John Johansen
  4 siblings, 1 reply; 73+ messages in thread
From: Tetsuo Handa @ 2014-05-12 13:25 UTC (permalink / raw)
  To: miklos, jmorris, john.johansen, selinux
  Cc: linux-security-module, linux-fsdevel, linux-kernel, torvalds

>From 479d305929c4ef600de99948f42ed633b24458b0 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: Mon, 12 May 2014 22:02:30 +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 <penguin-kernel@I-love.SAKURA.ne.jp>
---
 security/security.c |   18 ------------------
 1 files changed, 0 insertions(+), 18 deletions(-)

diff --git a/security/security.c b/security/security.c
index 81d7ffe..fbc4968 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

^ permalink raw reply related	[flat|nested] 73+ messages in thread

* Re: [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module.
  2014-05-12 13:22                               ` [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module Tetsuo Handa
@ 2014-05-19 12:19                                 ` John Johansen
  0 siblings, 0 replies; 73+ messages in thread
From: John Johansen @ 2014-05-19 12:19 UTC (permalink / raw)
  To: Tetsuo Handa, miklos, jmorris, selinux
  Cc: linux-security-module, linux-fsdevel, linux-kernel, torvalds

On 05/12/2014 06:22 AM, Tetsuo Handa wrote:
>>From 579cfccf7ba49a53ff0f5fefe73f0b8dc618d878 Mon Sep 17 00:00:00 2001
> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> Date: Mon, 12 May 2014 21:05:52 +0900
> Subject: [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module.
> 
> TOMOYO and AppArmor distinguish a directory's pathname and a non-directory's
> pathname by whether the pathname ends with / character or not. Upon rename(),
> they appended / character to the target dentry's pathname if the source
> dentry's inode refers a directory, for the target dentry's inode will refer
> a directory after successful rename(). This assumption was correct until
> commit da1ce067 "vfs: add cross-rename" is introduced.
> 
> Commit da1ce067 changed security_{inode,path}_rename() to call LSM module
> with reversed arguments if the rename flags contains RENAME_EXCHANGE. But
> this is not sufficient for TOMOYO and AppArmor because RENAME_EXCHANGE
> allows exchange of a directory's inode and a non-directory's inode. Also,
> this is redundant for SMACK because there is no need to call
> security_inode_rename() with reversed arguments.
> 
> To fix this regression, the rename flags needs to be passed to LSM module.
> This patch is for allowing TOMOYO and AppArmor to handle RENAME_EXCHANGE
> case differently, and for allowing SMACK to avoid needlessly checking
> the same permission twice.
> 
> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> Acked-by: Casey Schaufler <casey@schaufler-ca.com> [smack]

This looks good
Reviewed-by: John Johansen <john.johansen@canonical.com>

> ---
>  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 6478ce3..5158611 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 ad0d4de..620ba79 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 8b774f3..81d7ffe 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 2c7341d..33f6f56 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 14f52be..4c1933f 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 };
> 


^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH (for 3.15) 3/5] AppArmor: Handle the rename flags.
  2014-05-12 13:24                               ` [PATCH (for 3.15) 3/5] AppArmor: " Tetsuo Handa
@ 2014-05-19 12:28                                 ` John Johansen
  0 siblings, 0 replies; 73+ messages in thread
From: John Johansen @ 2014-05-19 12:28 UTC (permalink / raw)
  To: Tetsuo Handa, miklos, jmorris, selinux
  Cc: linux-security-module, linux-fsdevel, linux-kernel, torvalds

On 05/12/2014 06:24 AM, Tetsuo Handa wrote:
>>From 819e94ae3a6d9235196d137a39afa4e0bbd79770 Mon Sep 17 00:00:00 2001
> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> Date: Mon, 12 May 2014 21:54:05 +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" and "distinguish condition of source and target".
> Future patches will stop re-calculating pathnames.
> 
> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>

This isn't quite right. For apparmor at this point these paths need to
be still treated like they are separate. The second aa_path_perm, is
checking the permission to move old_inode to new_path.

see below

I've added an updated patch below

> ---
>  security/apparmor/lsm.c |   22 ++++++++++++++++++++--
>  1 files changed, 20 insertions(+), 2 deletions(-)
> 
> diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
> index c0b4366..9f21296 100644
> --- a/security/apparmor/lsm.c
> +++ b/security/apparmor/lsm.c
> @@ -331,7 +331,14 @@ 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
>  		};
> +		struct path_cond new_cond = cond;
>  
> +		if (flags & RENAME_EXCHANGE) {
> +			/* Cross rename requires both inodes to exist. */
> +			new_cond.uid = new_dentry->d_inode->i_uid;
> +			new_cond.mode = new_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 +346,18 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
>  		if (!error)
>  			error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
>  					     0, MAY_WRITE | AA_MAY_META_WRITE |
> -					     AA_MAY_CREATE, &cond);
> +					     AA_MAY_CREATE, &new_cond);
This isn't new_cond because its the permission to move old_inode to new_path

> +		if (!error && (flags & RENAME_EXCHANGE)) {
> +			struct path tmp_path = new_path;
> +			struct path_cond tmp_cond = new_cond;
> +
> +			new_path = old_path;
> +			old_path = tmp_path;
> +			new_cond = cond;
> +			cond = tmp_cond;
> +			flags = 0;
> +			goto retry;
> +		}
>  
>  	}
>  	return error;
> 

>From c07677ce007bbb5689b82bce0fab15a159f59874 Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: Mon, 12 May 2014 21:54:05 +0900
Subject: [PATCH] AppArmor: Handle the rename flags.

For AppArmor, the RENAME_EXCHANGE flag means "check permissions with
reversed arguments" and "distinguish condition of source and target".
Future patches will stop re-calculating pathnames.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
 security/apparmor/lsm.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index c0b4366..d7d92ad 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,16 @@ 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)) {
+			struct path tmp_path = new_path;
+			new_path = old_path;
+			old_path = tmp_path;
+			/* Cross rename requires both inodes to exist. */
+			cond.uid = new_dentry->d_inode->i_uid;
+			cond.mode = new_dentry->d_inode->i_mode;
+			flags = 0;
+			goto retry;
+		}
 
 	}
 	return error;
-- 
2.0.0.rc0


^ permalink raw reply related	[flat|nested] 73+ messages in thread

* Re: [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling.
  2014-05-12 13:25                               ` [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling Tetsuo Handa
@ 2014-05-19 12:34                                 ` John Johansen
  0 siblings, 0 replies; 73+ messages in thread
From: John Johansen @ 2014-05-19 12:34 UTC (permalink / raw)
  To: Tetsuo Handa, miklos, jmorris, selinux
  Cc: linux-security-module, linux-fsdevel, linux-kernel, torvalds

On 05/12/2014 06:25 AM, Tetsuo Handa wrote:
>>From 479d305929c4ef600de99948f42ed633b24458b0 Mon Sep 17 00:00:00 2001
> From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> Date: Mon, 12 May 2014 22:02:30 +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 <penguin-kernel@I-love.SAKURA.ne.jp>
Acked-by: John Johansen <john.johansen@canonical.com>

> ---
>  security/security.c |   18 ------------------
>  1 files changed, 0 insertions(+), 18 deletions(-)
> 
> diff --git a/security/security.c b/security/security.c
> index 81d7ffe..fbc4968 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);
>  }
> 


^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2013-11-26  9:51   ` Jan Kara
@ 2013-11-26 13:47     ` Miklos Szeredi
  0 siblings, 0 replies; 73+ messages in thread
From: Miklos Szeredi @ 2013-11-26 13:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells,
	zab, luto, mszeredi

On Tue, Nov 26, 2013 at 10:51:25AM +0100, Jan Kara wrote:
> On Wed 20-11-13 14:01:52, Miklos Szeredi wrote:
> > From: Miklos Szeredi <mszeredi@suse.cz>
> > 
> > Implement RENAME_EXCHANGE flag in renameat2 syscall.
> > 
> > Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
> > ---
> >  fs/ext4/namei.c | 97 ++++++++++++++++++++++++++++++++++++++++-----------------
> >  1 file changed, 69 insertions(+), 28 deletions(-)
> > 
> > diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> > index d258b354b937..5307e482f403 100644
> > --- a/fs/ext4/namei.c
> > +++ b/fs/ext4/namei.c
> ...
> > -	old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir);
> > -	ext4_update_dx_flag(old.dir);
> > +	/* S_ISDIR(old.inode->i_mode */
> >  	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) {
> > -			/* checked empty_dir above, can't have another parent,
> > -			 * ext4_dec_count() won't work for many-linked dirs */
> > -			clear_nlink(new.inode);
> > -		} else {
> > +		if (!(flags & RENAME_EXCHANGE) || !S_ISDIR(new.inode->i_mode))
> > +			ext4_dec_count(handle, old.dir);
> > +
> > +		if (!new.inode || !S_ISDIR(new.inode->i_mode)) {
> >  			ext4_inc_count(handle, new.dir);
> >  			ext4_update_dx_flag(new.dir);
> >  			ext4_mark_inode_dirty(handle, new.dir);
> >  		}
> >  	}
> > +	/* (flags & RENAME_EXCHANGE) && S_ISDIR(new.inode->i_mode */
> > +	if (new.dir_bh) {
> > +		retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino);
> > +		if (retval)
> > +			goto end_rename;
> > +
> > +		if (!S_ISDIR(old.inode->i_mode)) {
> > +			ext4_dec_count(handle, new.dir);
> > +			ext4_inc_count(handle, old.dir);
> > +			ext4_mark_inode_dirty(handle, new.dir);
> > +		}
> > +	}
> >  	ext4_mark_inode_dirty(handle, old.dir);
> > -	if (new.inode) {
> > +	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);
> > +		}
>   This hunk looks strange. Why do you check S_ISDIR(old.inode->i_mode)? I'd
> presume we need to clear nlink if new.inode is a directory...

It's confusing, that's for sure.  I think it's correct, since S_ISDIR(old) is
equivalent to S_ISDIR(new) if not cross-renaming, but that's not a lot of
consolation to someone trying to understand the code.

> 
> >  		ext4_mark_inode_dirty(handle, new.inode);
> >  		if (!new.inode->i_nlink)
> > 			ext4_orphan_add(handle, new.inode);
>   Generally, I'm a bit unhappy about the number of various RENAME_EXCHANGE
> checks and the asymmetry between new & old which now shouldn't needed (that
> much). Especially the link count handling looks more complex than it should
> be.
> 
> I'd hope that it should be possible to "delete new.inode iff
> !RENAME_EXCHANGE" and then the rest shouldn't need to care about
> RENAME_EXCHANGE at all and treat old & new completely symmetrically... Now I
> realize this isn't that easy because we want to do all error checks first
> before doing any changes on disk but still I hope some improvement can be
> made (maybe just zero out new.inode in our 'new' ext4_renament to allow for
> code to be symmetric and delete it on disk only when ext4_rename() is
> finishing).
> 
> If the above won't be workable, we might at least make the link count
> handling more obvious by computing "old_link_cnt_update,
> new_link_cnt_update" - how link counts of parent dirs should be updated
> (-1, 0, +1) and then do the checks and updates based on this in one place.

Okay, will try to clean this up.  I agree that it became a bit too complicated.

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 73+ messages in thread

* Re: [PATCH 11/11] ext4: add cross rename support
  2013-11-20 13:01 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi
@ 2013-11-26  9:51   ` Jan Kara
  2013-11-26 13:47     ` Miklos Szeredi
  0 siblings, 1 reply; 73+ messages in thread
From: Jan Kara @ 2013-11-26  9:51 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: viro, torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells,
	zab, jack, luto, mszeredi

On Wed 20-11-13 14:01:52, Miklos Szeredi wrote:
> From: Miklos Szeredi <mszeredi@suse.cz>
> 
> Implement RENAME_EXCHANGE flag in renameat2 syscall.
> 
> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
> ---
>  fs/ext4/namei.c | 97 ++++++++++++++++++++++++++++++++++++++++-----------------
>  1 file changed, 69 insertions(+), 28 deletions(-)
> 
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index d258b354b937..5307e482f403 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
...
> -	old.dir->i_ctime = old.dir->i_mtime = ext4_current_time(old.dir);
> -	ext4_update_dx_flag(old.dir);
> +	/* S_ISDIR(old.inode->i_mode */
>  	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) {
> -			/* checked empty_dir above, can't have another parent,
> -			 * ext4_dec_count() won't work for many-linked dirs */
> -			clear_nlink(new.inode);
> -		} else {
> +		if (!(flags & RENAME_EXCHANGE) || !S_ISDIR(new.inode->i_mode))
> +			ext4_dec_count(handle, old.dir);
> +
> +		if (!new.inode || !S_ISDIR(new.inode->i_mode)) {
>  			ext4_inc_count(handle, new.dir);
>  			ext4_update_dx_flag(new.dir);
>  			ext4_mark_inode_dirty(handle, new.dir);
>  		}
>  	}
> +	/* (flags & RENAME_EXCHANGE) && S_ISDIR(new.inode->i_mode */
> +	if (new.dir_bh) {
> +		retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino);
> +		if (retval)
> +			goto end_rename;
> +
> +		if (!S_ISDIR(old.inode->i_mode)) {
> +			ext4_dec_count(handle, new.dir);
> +			ext4_inc_count(handle, old.dir);
> +			ext4_mark_inode_dirty(handle, new.dir);
> +		}
> +	}
>  	ext4_mark_inode_dirty(handle, old.dir);
> -	if (new.inode) {
> +	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);
> +		}
  This hunk looks strange. Why do you check S_ISDIR(old.inode->i_mode)? I'd
presume we need to clear nlink if new.inode is a directory...

>  		ext4_mark_inode_dirty(handle, new.inode);
>  		if (!new.inode->i_nlink)
> 			ext4_orphan_add(handle, new.inode);
  Generally, I'm a bit unhappy about the number of various RENAME_EXCHANGE
checks and the asymmetry between new & old which now shouldn't needed (that
much). Especially the link count handling looks more complex than it should
be.

I'd hope that it should be possible to "delete new.inode iff
!RENAME_EXCHANGE" and then the rest shouldn't need to care about
RENAME_EXCHANGE at all and treat old & new completely symmetrically... Now I
realize this isn't that easy because we want to do all error checks first
before doing any changes on disk but still I hope some improvement can be
made (maybe just zero out new.inode in our 'new' ext4_renament to allow for
code to be symmetric and delete it on disk only when ext4_rename() is
finishing).

If the above won't be workable, we might at least make the link count
handling more obvious by computing "old_link_cnt_update,
new_link_cnt_update" - how link counts of parent dirs should be updated
(-1, 0, +1) and then do the checks and updates based on this in one place.

								Honza
-- 
Jan Kara <jack@suse.cz>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 73+ messages in thread

* [PATCH 11/11] ext4: add cross rename support
  2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi
@ 2013-11-20 13:01 ` Miklos Szeredi
  2013-11-26  9:51   ` Jan Kara
  0 siblings, 1 reply; 73+ messages in thread
From: Miklos Szeredi @ 2013-11-20 13:01 UTC (permalink / raw)
  To: viro
  Cc: torvalds, linux-fsdevel, linux-kernel, hch, akpm, dhowells, zab,
	jack, luto, mszeredi

From: Miklos Szeredi <mszeredi@suse.cz>

Implement RENAME_EXCHANGE flag in renameat2 syscall.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
---
 fs/ext4/namei.c | 97 ++++++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 69 insertions(+), 28 deletions(-)

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index d258b354b937..5307e482f403 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3159,7 +3159,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);
@@ -3167,10 +3167,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.dentry->d_inode)
+	if (!(flags & RENAME_EXCHANGE) && new.dentry->d_inode)
 		dquot_initialize(new.dentry->d_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 +3186,22 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
 	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;
+	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,11 +3209,12 @@ 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) {
+		if (!(flags & RENAME_EXCHANGE) && new.inode) {
 			retval = -ENOTEMPTY;
 			if (!empty_dir(new.inode))
 				goto end_rename;
-		} else {
+		}
+		if (!new.inode || !S_ISDIR(new.inode->i_mode)) {
 			retval = -EMLINK;
 			if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir))
 				goto end_rename;
@@ -3217,15 +3223,34 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
 		if (retval)
 			goto end_rename;
 	}
+	if ((flags & RENAME_EXCHANGE) && S_ISDIR(new.inode->i_mode)) {
+		if (!S_ISDIR(old.inode->i_mode)) {
+			retval = -EMLINK;
+			if (new.dir != old.dir && EXT4_DIR_LINK_MAX(old.dir))
+				goto end_rename;
+		}
+		retval = ext4_rename_dir_prepare(handle, &new);
+		if (retval)
+			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 +3260,51 @@ 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);
+	/* S_ISDIR(old.inode->i_mode */
 	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) {
-			/* checked empty_dir above, can't have another parent,
-			 * ext4_dec_count() won't work for many-linked dirs */
-			clear_nlink(new.inode);
-		} else {
+		if (!(flags & RENAME_EXCHANGE) || !S_ISDIR(new.inode->i_mode))
+			ext4_dec_count(handle, old.dir);
+
+		if (!new.inode || !S_ISDIR(new.inode->i_mode)) {
 			ext4_inc_count(handle, new.dir);
 			ext4_update_dx_flag(new.dir);
 			ext4_mark_inode_dirty(handle, new.dir);
 		}
 	}
+	/* (flags & RENAME_EXCHANGE) && S_ISDIR(new.inode->i_mode */
+	if (new.dir_bh) {
+		retval = ext4_rename_dir_finish(handle, &new, old.dir->i_ino);
+		if (retval)
+			goto end_rename;
+
+		if (!S_ISDIR(old.inode->i_mode)) {
+			ext4_dec_count(handle, new.dir);
+			ext4_inc_count(handle, old.dir);
+			ext4_mark_inode_dirty(handle, new.dir);
+		}
+	}
 	ext4_mark_inode_dirty(handle, old.dir);
-	if (new.inode) {
+	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);
+		}
 		ext4_mark_inode_dirty(handle, new.inode);
 		if (!new.inode->i_nlink)
 			ext4_orphan_add(handle, new.inode);
-- 
1.8.1.4


^ permalink raw reply related	[flat|nested] 73+ messages in thread

end of thread, other threads:[~2014-05-19 12:34 UTC | newest]

Thread overview: 73+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-01-08 22:10 [PATCH 00/11] cross rename v3 Miklos Szeredi
2014-01-08 22:10 ` [PATCH 01/11] vfs: add d_is_dir() Miklos Szeredi
2014-01-08 22:10 ` [PATCH 02/11] vfs: rename: move d_move() up Miklos Szeredi
2014-01-08 22:10 ` [PATCH 03/11] vfs: rename: use common code for dir and non-dir Miklos Szeredi
2014-01-08 22:10 ` [PATCH 04/11] vfs: add renameat2 syscall Miklos Szeredi
2014-01-14 22:11   ` Tetsuo Handa
2014-01-15 10:30     ` Miklos Szeredi
2014-01-15 13:50       ` Miklos Szeredi
2014-01-18 10:40         ` Tetsuo Handa
2014-01-08 22:10 ` [PATCH 05/11] vfs: add RENAME_NOREPLACE flag Miklos Szeredi
2014-01-15 18:19   ` J. Bruce Fields
2014-01-15 18:26     ` Andy Lutomirski
2014-01-15 23:33       ` J. Bruce Fields
2014-01-16 10:45         ` Miklos Szeredi
2014-01-15 18:35     ` Miklos Szeredi
2014-01-15 23:31       ` J. Bruce Fields
2014-01-08 22:10 ` [PATCH 06/11] security: add flags to rename hooks Miklos Szeredi
2014-01-08 22:10 ` [PATCH 07/11] vfs: add cross-rename Miklos Szeredi
2014-01-13  7:52   ` Jan Kara
2014-01-14 10:31     ` Miklos Szeredi
2014-01-14 12:47       ` Jan Kara
2014-01-08 22:10 ` [PATCH 08/11] ext4: rename: create ext4_renament structure for local vars Miklos Szeredi
2014-01-08 22:10 ` [PATCH 09/11] ext4: rename: move EMLINK check up Miklos Szeredi
2014-01-08 22:10 ` [PATCH 10/11] ext4: rename: split out helper functions Miklos Szeredi
2014-01-08 22:10 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi
2014-01-13 12:25   ` Jan Kara
2014-01-14 10:35     ` Miklos Szeredi
2014-01-15 18:23     ` J. Bruce Fields
2014-01-15 18:31       ` Miklos Szeredi
2014-01-16 10:54         ` Miklos Szeredi
2014-01-16 14:48           ` J. Bruce Fields
2014-01-17 10:53           ` Michael Kerrisk (man-pages)
2014-01-17 14:41             ` Miklos Szeredi
2014-04-19  9:08               ` Michael Kerrisk (man-pages)
2014-04-19 12:08                 ` Tetsuo Handa
2014-04-23 14:24                   ` Miklos Szeredi
2014-04-24 11:20                     ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
2014-04-24 11:22                       ` [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module Tetsuo Handa
2014-04-25 20:49                         ` Casey Schaufler
2014-04-24 11:23                       ` [PATCH (for 3.15) 2/5] SELinux: Handle the rename flags Tetsuo Handa
2014-04-24 11:24                       ` [PATCH (for 3.15) 3/5] AppArmor: " Tetsuo Handa
2014-04-24 11:25                       ` [PATCH (for 3.15) 4/5] TOMOYO: " Tetsuo Handa
2014-04-24 11:26                       ` [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling Tetsuo Handa
2014-05-01 11:58                       ` [PATCH (for 3.15) 0/5] Fix cross rename race window for LSM Tetsuo Handa
2014-05-05  5:49                         ` Tetsuo Handa
2014-05-11 15:53                           ` Tetsuo Handa
2014-05-12 13:21                             ` [PATCH (for 3.15) 0/5] Fix cross rename regressions " Tetsuo Handa
2014-05-12 13:22                               ` [PATCH (for 3.15) 1/5] LSM: Pass the rename flags to each LSM module Tetsuo Handa
2014-05-19 12:19                                 ` John Johansen
2014-05-12 13:23                               ` [PATCH (for 3.15) 2/5] SELinux: Handle the rename flags Tetsuo Handa
2014-05-12 13:24                               ` [PATCH (for 3.15) 3/5] AppArmor: " Tetsuo Handa
2014-05-19 12:28                                 ` John Johansen
2014-05-12 13:25                               ` [PATCH (for 3.15) 4/5] TOMOYO: " Tetsuo Handa
2014-05-12 13:25                               ` [PATCH (for 3.15) 5/5] LSM: Remove duplicated rename handling Tetsuo Handa
2014-05-19 12:34                                 ` John Johansen
2014-04-23 14:21                 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi
2014-04-23 19:01                   ` Michael Kerrisk (man-pages)
2014-01-17 22:08             ` J. Bruce Fields
2014-01-18  6:49               ` Miklos Szeredi
2014-01-18 16:27                 ` J. Bruce Fields
2014-01-20 11:39                   ` Miklos Szeredi
2014-01-20 11:50                     ` Michael Kerrisk (man-pages)
2014-01-13 12:46 ` [PATCH 00/11] cross rename v3 Tetsuo Handa
2014-01-13 17:08   ` Miklos Szeredi
2014-01-13 22:03     ` Tetsuo Handa
2014-01-14  9:58       ` Miklos Szeredi
2014-01-14 13:03         ` Tetsuo Handa
2014-01-14 20:10           ` John Johansen
2014-01-14 20:53             ` Tetsuo Handa
2014-01-15 10:10               ` Miklos Szeredi
  -- strict thread matches above, loose matches on Subject: below --
2013-11-20 13:01 [PATCH 00/11] cross rename v2 Miklos Szeredi
2013-11-20 13:01 ` [PATCH 11/11] ext4: add cross rename support Miklos Szeredi
2013-11-26  9:51   ` Jan Kara
2013-11-26 13:47     ` Miklos Szeredi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).