+/**
+ * gfs2_exchange - exchange two files
+ * @odir: Parent directory of old file name
+ * @odentry: The old dentry of the file
+ * @ndir: Parent directory of new file name
+ * @ndentry: The new dentry of the file
+ * @flags: The rename flags
+ *
+ * Returns: errno
+ */
+
+static int gfs2_exchange(struct inode *odir, struct dentry *odentry,
+ struct inode *ndir, struct dentry *ndentry,
+ unsigned int flags)
+{
+ struct gfs2_inode *odip = GFS2_I(odir);
+ struct gfs2_inode *ndip = GFS2_I(ndir);
+ struct gfs2_inode *oip = GFS2_I(odentry->d_inode);
+ struct gfs2_inode *nip = GFS2_I(ndentry->d_inode);
+ struct gfs2_sbd *sdp = GFS2_SB(odir);
+ struct gfs2_holder ghs[5], r_gh = { .gh_gl = NULL, };
+ unsigned int num_gh;
+ unsigned int x;
+ umode_t old_mode = oip->i_inode.i_mode;
+ umode_t new_mode = nip->i_inode.i_mode;
+ int error;
+
+ error = gfs2_rindex_update(sdp);
+ if (error)
+ return error;
+
+ if (odip != ndip) {
+ error = gfs2_glock_nq_init(sdp->sd_rename_gl, LM_ST_EXCLUSIVE,
+ 0, &r_gh);
+ if (error)
+ goto out;
+
+ if (S_ISDIR(old_mode)) {
+ /* don't move a directory into its subdir */
+ error = gfs2_ok_to_move(oip, ndip);
+ if (error)
+ goto out_gunlock_r;
+ }
+
+ if (S_ISDIR(new_mode)) {
+ /* don't move a directory into its subdir */
+ error = gfs2_ok_to_move(nip, odip);
+ if (error)
+ goto out_gunlock_r;
+ }
+ }
+
+ num_gh = 1;
+ gfs2_holder_init(odip->i_gl, LM_ST_EXCLUSIVE, 0, ghs);
+ if (odip != ndip) {
+ gfs2_holder_init(ndip->i_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh);
+ num_gh++;
+ }
+ gfs2_holder_init(oip->i_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh);
+ num_gh++;
+
+ gfs2_holder_init(nip->i_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh);
+ num_gh++;
+
+ for (x = 0; x < num_gh; x++) {
+ error = gfs2_glock_nq(ghs + x);
+ if (error)
+ goto out_gunlock;
+ }
+
+ error = -ENOENT;
+ if (oip->i_inode.i_nlink == 0 || nip->i_inode.i_nlink == 0)
+ goto out_gunlock;
+
+ error = gfs2_unlink_ok(odip, &odentry->d_name, oip);
+ if (error)
+ goto out_gunlock;
+ error = gfs2_unlink_ok(ndip, &ndentry->d_name, nip);
+ if (error)
+ goto out_gunlock;
+
+ if (S_ISDIR(old_mode)) {
+ error = gfs2_permission(odentry->d_inode, MAY_WRITE);
+ if (error)
+ goto out_gunlock;
+ }
+ if (S_ISDIR(new_mode)) {
+ error = gfs2_permission(ndentry->d_inode, MAY_WRITE);
+ if (error)
+ goto out_gunlock;
+ }
+ error = gfs2_trans_begin(sdp, 4 * RES_DINODE + 4 * RES_LEAF, 0);
+ if (error)
+ goto out_gunlock;
+
+ error = update_moved_ino(oip, ndip, S_ISDIR(old_mode));
+ if (error)
+ goto out_end_trans;
+
+ error = update_moved_ino(nip, odip, S_ISDIR(new_mode));
+ if (error)
+ goto out_end_trans;
+
+ error = gfs2_dir_mvino(ndip, &ndentry->d_name, oip,
+ IF2DT(old_mode));
+ if (error)
+ goto out_end_trans;
+
+ error = gfs2_dir_mvino(odip, &odentry->d_name, nip,
+ IF2DT(new_mode));
+ if (error)
+ goto out_end_trans;
+
+ if (odip != ndip) {
+ if (S_ISDIR(new_mode) && !S_ISDIR(old_mode)) {
+ inc_nlink(&odip->i_inode);
+ drop_nlink(&ndip->i_inode);
+ } else if (S_ISDIR(old_mode) && !S_ISDIR(new_mode)) {
+ inc_nlink(&ndip->i_inode);
+ drop_nlink(&odip->i_inode);
+ }
+ }
+ mark_inode_dirty(&ndip->i_inode);
+ if (odip != ndip)
+ mark_inode_dirty(&odip->i_inode);
+
+out_end_trans:
+ gfs2_trans_end(sdp);
+out_gunlock:
+ while (x--) {
+ gfs2_glock_dq(ghs + x);
+ gfs2_holder_uninit(ghs + x);
+ }
+out_gunlock_r:
+ if (r_gh.gh_gl)
+ gfs2_glock_dq_uninit(&r_gh);
+out:
+ return error;
+}
+
+static int gfs2_rename2(struct inode *odir, struct dentry *odentry,
+ struct inode *ndir, struct dentry *ndentry,
+ unsigned int flags)
+{
+ flags &= ~RENAME_NOREPLACE;
+
+ if (flags & ~RENAME_EXCHANGE)
+ return -EINVAL;
+
+ if (flags & RENAME_EXCHANGE)
+ return gfs2_exchange(odir, odentry, ndir, ndentry, flags);
+
+ return gfs2_rename(odir, odentry, ndir, ndentry);
+}
+