aboutsummaryrefslogtreecommitdiffstats
path: root/fs/xfs/xfs_dfrag.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/xfs/xfs_dfrag.c')
-rw-r--r--fs/xfs/xfs_dfrag.c149
1 files changed, 117 insertions, 32 deletions
diff --git a/fs/xfs/xfs_dfrag.c b/fs/xfs/xfs_dfrag.c
index d1483a4f71b8..cd27c9d6c71f 100644
--- a/fs/xfs/xfs_dfrag.c
+++ b/fs/xfs/xfs_dfrag.c
@@ -45,15 +45,21 @@
45#include "xfs_vnodeops.h" 45#include "xfs_vnodeops.h"
46#include "xfs_trace.h" 46#include "xfs_trace.h"
47 47
48
49static int xfs_swap_extents(
50 xfs_inode_t *ip, /* target inode */
51 xfs_inode_t *tip, /* tmp inode */
52 xfs_swapext_t *sxp);
53
48/* 54/*
49 * Syssgi interface for swapext 55 * ioctl interface for swapext
50 */ 56 */
51int 57int
52xfs_swapext( 58xfs_swapext(
53 xfs_swapext_t *sxp) 59 xfs_swapext_t *sxp)
54{ 60{
55 xfs_inode_t *ip, *tip; 61 xfs_inode_t *ip, *tip;
56 struct file *file, *target_file; 62 struct file *file, *tmp_file;
57 int error = 0; 63 int error = 0;
58 64
59 /* Pull information for the target fd */ 65 /* Pull information for the target fd */
@@ -68,56 +74,128 @@ xfs_swapext(
68 goto out_put_file; 74 goto out_put_file;
69 } 75 }
70 76
71 target_file = fget((int)sxp->sx_fdtmp); 77 tmp_file = fget((int)sxp->sx_fdtmp);
72 if (!target_file) { 78 if (!tmp_file) {
73 error = XFS_ERROR(EINVAL); 79 error = XFS_ERROR(EINVAL);
74 goto out_put_file; 80 goto out_put_file;
75 } 81 }
76 82
77 if (!(target_file->f_mode & FMODE_WRITE) || 83 if (!(tmp_file->f_mode & FMODE_WRITE) ||
78 (target_file->f_flags & O_APPEND)) { 84 (tmp_file->f_flags & O_APPEND)) {
79 error = XFS_ERROR(EBADF); 85 error = XFS_ERROR(EBADF);
80 goto out_put_target_file; 86 goto out_put_tmp_file;
81 } 87 }
82 88
83 if (IS_SWAPFILE(file->f_path.dentry->d_inode) || 89 if (IS_SWAPFILE(file->f_path.dentry->d_inode) ||
84 IS_SWAPFILE(target_file->f_path.dentry->d_inode)) { 90 IS_SWAPFILE(tmp_file->f_path.dentry->d_inode)) {
85 error = XFS_ERROR(EINVAL); 91 error = XFS_ERROR(EINVAL);
86 goto out_put_target_file; 92 goto out_put_tmp_file;
87 } 93 }
88 94
89 ip = XFS_I(file->f_path.dentry->d_inode); 95 ip = XFS_I(file->f_path.dentry->d_inode);
90 tip = XFS_I(target_file->f_path.dentry->d_inode); 96 tip = XFS_I(tmp_file->f_path.dentry->d_inode);
91 97
92 if (ip->i_mount != tip->i_mount) { 98 if (ip->i_mount != tip->i_mount) {
93 error = XFS_ERROR(EINVAL); 99 error = XFS_ERROR(EINVAL);
94 goto out_put_target_file; 100 goto out_put_tmp_file;
95 } 101 }
96 102
97 if (ip->i_ino == tip->i_ino) { 103 if (ip->i_ino == tip->i_ino) {
98 error = XFS_ERROR(EINVAL); 104 error = XFS_ERROR(EINVAL);
99 goto out_put_target_file; 105 goto out_put_tmp_file;
100 } 106 }
101 107
102 if (XFS_FORCED_SHUTDOWN(ip->i_mount)) { 108 if (XFS_FORCED_SHUTDOWN(ip->i_mount)) {
103 error = XFS_ERROR(EIO); 109 error = XFS_ERROR(EIO);
104 goto out_put_target_file; 110 goto out_put_tmp_file;
105 } 111 }
106 112
107 error = xfs_swap_extents(ip, tip, sxp); 113 error = xfs_swap_extents(ip, tip, sxp);
108 114
109 out_put_target_file: 115 out_put_tmp_file:
110 fput(target_file); 116 fput(tmp_file);
111 out_put_file: 117 out_put_file:
112 fput(file); 118 fput(file);
113 out: 119 out:
114 return error; 120 return error;
115} 121}
116 122
117int 123/*
124 * We need to check that the format of the data fork in the temporary inode is
125 * valid for the target inode before doing the swap. This is not a problem with
126 * attr1 because of the fixed fork offset, but attr2 has a dynamically sized
127 * data fork depending on the space the attribute fork is taking so we can get
128 * invalid formats on the target inode.
129 *
130 * E.g. target has space for 7 extents in extent format, temp inode only has
131 * space for 6. If we defragment down to 7 extents, then the tmp format is a
132 * btree, but when swapped it needs to be in extent format. Hence we can't just
133 * blindly swap data forks on attr2 filesystems.
134 *
135 * Note that we check the swap in both directions so that we don't end up with
136 * a corrupt temporary inode, either.
137 *
138 * Note that fixing the way xfs_fsr sets up the attribute fork in the source
139 * inode will prevent this situation from occurring, so all we do here is
140 * reject and log the attempt. basically we are putting the responsibility on
141 * userspace to get this right.
142 */
143static int
144xfs_swap_extents_check_format(
145 xfs_inode_t *ip, /* target inode */
146 xfs_inode_t *tip) /* tmp inode */
147{
148
149 /* Should never get a local format */
150 if (ip->i_d.di_format == XFS_DINODE_FMT_LOCAL ||
151 tip->i_d.di_format == XFS_DINODE_FMT_LOCAL)
152 return EINVAL;
153
154 /*
155 * if the target inode has less extents that then temporary inode then
156 * why did userspace call us?
157 */
158 if (ip->i_d.di_nextents < tip->i_d.di_nextents)
159 return EINVAL;
160
161 /*
162 * if the target inode is in extent form and the temp inode is in btree
163 * form then we will end up with the target inode in the wrong format
164 * as we already know there are less extents in the temp inode.
165 */
166 if (ip->i_d.di_format == XFS_DINODE_FMT_EXTENTS &&
167 tip->i_d.di_format == XFS_DINODE_FMT_BTREE)
168 return EINVAL;
169
170 /* Check temp in extent form to max in target */
171 if (tip->i_d.di_format == XFS_DINODE_FMT_EXTENTS &&
172 XFS_IFORK_NEXTENTS(tip, XFS_DATA_FORK) > ip->i_df.if_ext_max)
173 return EINVAL;
174
175 /* Check target in extent form to max in temp */
176 if (ip->i_d.di_format == XFS_DINODE_FMT_EXTENTS &&
177 XFS_IFORK_NEXTENTS(ip, XFS_DATA_FORK) > tip->i_df.if_ext_max)
178 return EINVAL;
179
180 /* Check root block of temp in btree form to max in target */
181 if (tip->i_d.di_format == XFS_DINODE_FMT_BTREE &&
182 XFS_IFORK_BOFF(ip) &&
183 tip->i_df.if_broot_bytes > XFS_IFORK_BOFF(ip))
184 return EINVAL;
185
186 /* Check root block of target in btree form to max in temp */
187 if (ip->i_d.di_format == XFS_DINODE_FMT_BTREE &&
188 XFS_IFORK_BOFF(tip) &&
189 ip->i_df.if_broot_bytes > XFS_IFORK_BOFF(tip))
190 return EINVAL;
191
192 return 0;
193}
194
195static int
118xfs_swap_extents( 196xfs_swap_extents(
119 xfs_inode_t *ip, 197 xfs_inode_t *ip, /* target inode */
120 xfs_inode_t *tip, 198 xfs_inode_t *tip, /* tmp inode */
121 xfs_swapext_t *sxp) 199 xfs_swapext_t *sxp)
122{ 200{
123 xfs_mount_t *mp; 201 xfs_mount_t *mp;
@@ -161,13 +239,6 @@ xfs_swap_extents(
161 goto out_unlock; 239 goto out_unlock;
162 } 240 }
163 241
164 /* Should never get a local format */
165 if (ip->i_d.di_format == XFS_DINODE_FMT_LOCAL ||
166 tip->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
167 error = XFS_ERROR(EINVAL);
168 goto out_unlock;
169 }
170
171 if (VN_CACHED(VFS_I(tip)) != 0) { 242 if (VN_CACHED(VFS_I(tip)) != 0) {
172 error = xfs_flushinval_pages(tip, 0, -1, 243 error = xfs_flushinval_pages(tip, 0, -1,
173 FI_REMAPF_LOCKED); 244 FI_REMAPF_LOCKED);
@@ -189,13 +260,15 @@ xfs_swap_extents(
189 goto out_unlock; 260 goto out_unlock;
190 } 261 }
191 262
192 /* 263 trace_xfs_swap_extent_before(ip, 0);
193 * If the target has extended attributes, the tmp file 264 trace_xfs_swap_extent_before(tip, 1);
194 * must also in order to ensure the correct data fork 265
195 * format. 266 /* check inode formats now that data is flushed */
196 */ 267 error = xfs_swap_extents_check_format(ip, tip);
197 if ( XFS_IFORK_Q(ip) != XFS_IFORK_Q(tip) ) { 268 if (error) {
198 error = XFS_ERROR(EINVAL); 269 xfs_fs_cmn_err(CE_NOTE, mp,
270 "%s: inode 0x%llx format is incompatible for exchanging.",
271 __FILE__, ip->i_ino);
199 goto out_unlock; 272 goto out_unlock;
200 } 273 }
201 274
@@ -276,6 +349,16 @@ xfs_swap_extents(
276 *tifp = *tempifp; /* struct copy */ 349 *tifp = *tempifp; /* struct copy */
277 350
278 /* 351 /*
352 * Fix the in-memory data fork values that are dependent on the fork
353 * offset in the inode. We can't assume they remain the same as attr2
354 * has dynamic fork offsets.
355 */
356 ifp->if_ext_max = XFS_IFORK_SIZE(ip, XFS_DATA_FORK) /
357 (uint)sizeof(xfs_bmbt_rec_t);
358 tifp->if_ext_max = XFS_IFORK_SIZE(tip, XFS_DATA_FORK) /
359 (uint)sizeof(xfs_bmbt_rec_t);
360
361 /*
279 * Fix the on-disk inode values 362 * Fix the on-disk inode values
280 */ 363 */
281 tmp = (__uint64_t)ip->i_d.di_nblocks; 364 tmp = (__uint64_t)ip->i_d.di_nblocks;
@@ -347,6 +430,8 @@ xfs_swap_extents(
347 430
348 error = xfs_trans_commit(tp, XFS_TRANS_SWAPEXT); 431 error = xfs_trans_commit(tp, XFS_TRANS_SWAPEXT);
349 432
433 trace_xfs_swap_extent_after(ip, 0);
434 trace_xfs_swap_extent_after(tip, 1);
350out: 435out:
351 kmem_free(tempifp); 436 kmem_free(tempifp);
352 return error; 437 return error;