diff options
author | Christoph Hellwig <hch@lst.de> | 2011-07-08 08:34:23 -0400 |
---|---|---|
committer | Christoph Hellwig <hch@lst.de> | 2011-07-08 08:34:23 -0400 |
commit | c4ed4243c40f97ed5b7b121777bbbc6aeaa722f0 (patch) | |
tree | fda6054305f357705687016906a1ca254018c2e7 /fs/xfs/xfs_vnodeops.c | |
parent | dec58f1dfd30a3c3e9dadc808692f4e5cd922745 (diff) |
xfs: split xfs_setattr
Split up xfs_setattr into two functions, one for the complex truncate
handling, and one for the trivial attribute updates. Also move both
new routines to xfs_iops.c as they are fairly Linux-specific.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Alex Elder <aelder@sgi.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Diffstat (limited to 'fs/xfs/xfs_vnodeops.c')
-rw-r--r-- | fs/xfs/xfs_vnodeops.c | 426 |
1 files changed, 1 insertions, 425 deletions
diff --git a/fs/xfs/xfs_vnodeops.c b/fs/xfs/xfs_vnodeops.c index 619720705bc6..a4f56a42ef90 100644 --- a/fs/xfs/xfs_vnodeops.c +++ b/fs/xfs/xfs_vnodeops.c | |||
@@ -50,430 +50,6 @@ | |||
50 | #include "xfs_vnodeops.h" | 50 | #include "xfs_vnodeops.h" |
51 | #include "xfs_trace.h" | 51 | #include "xfs_trace.h" |
52 | 52 | ||
53 | int | ||
54 | xfs_setattr( | ||
55 | struct xfs_inode *ip, | ||
56 | struct iattr *iattr, | ||
57 | int flags) | ||
58 | { | ||
59 | xfs_mount_t *mp = ip->i_mount; | ||
60 | struct inode *inode = VFS_I(ip); | ||
61 | int mask = iattr->ia_valid; | ||
62 | xfs_trans_t *tp; | ||
63 | int code; | ||
64 | uint lock_flags; | ||
65 | uint commit_flags=0; | ||
66 | uid_t uid=0, iuid=0; | ||
67 | gid_t gid=0, igid=0; | ||
68 | struct xfs_dquot *udqp, *gdqp, *olddquot1, *olddquot2; | ||
69 | int need_iolock = 1; | ||
70 | |||
71 | trace_xfs_setattr(ip); | ||
72 | |||
73 | if (mp->m_flags & XFS_MOUNT_RDONLY) | ||
74 | return XFS_ERROR(EROFS); | ||
75 | |||
76 | if (XFS_FORCED_SHUTDOWN(mp)) | ||
77 | return XFS_ERROR(EIO); | ||
78 | |||
79 | code = -inode_change_ok(inode, iattr); | ||
80 | if (code) | ||
81 | return code; | ||
82 | |||
83 | olddquot1 = olddquot2 = NULL; | ||
84 | udqp = gdqp = NULL; | ||
85 | |||
86 | /* | ||
87 | * If disk quotas is on, we make sure that the dquots do exist on disk, | ||
88 | * before we start any other transactions. Trying to do this later | ||
89 | * is messy. We don't care to take a readlock to look at the ids | ||
90 | * in inode here, because we can't hold it across the trans_reserve. | ||
91 | * If the IDs do change before we take the ilock, we're covered | ||
92 | * because the i_*dquot fields will get updated anyway. | ||
93 | */ | ||
94 | if (XFS_IS_QUOTA_ON(mp) && (mask & (ATTR_UID|ATTR_GID))) { | ||
95 | uint qflags = 0; | ||
96 | |||
97 | if ((mask & ATTR_UID) && XFS_IS_UQUOTA_ON(mp)) { | ||
98 | uid = iattr->ia_uid; | ||
99 | qflags |= XFS_QMOPT_UQUOTA; | ||
100 | } else { | ||
101 | uid = ip->i_d.di_uid; | ||
102 | } | ||
103 | if ((mask & ATTR_GID) && XFS_IS_GQUOTA_ON(mp)) { | ||
104 | gid = iattr->ia_gid; | ||
105 | qflags |= XFS_QMOPT_GQUOTA; | ||
106 | } else { | ||
107 | gid = ip->i_d.di_gid; | ||
108 | } | ||
109 | |||
110 | /* | ||
111 | * We take a reference when we initialize udqp and gdqp, | ||
112 | * so it is important that we never blindly double trip on | ||
113 | * the same variable. See xfs_create() for an example. | ||
114 | */ | ||
115 | ASSERT(udqp == NULL); | ||
116 | ASSERT(gdqp == NULL); | ||
117 | code = xfs_qm_vop_dqalloc(ip, uid, gid, xfs_get_projid(ip), | ||
118 | qflags, &udqp, &gdqp); | ||
119 | if (code) | ||
120 | return code; | ||
121 | } | ||
122 | |||
123 | /* | ||
124 | * For the other attributes, we acquire the inode lock and | ||
125 | * first do an error checking pass. | ||
126 | */ | ||
127 | tp = NULL; | ||
128 | lock_flags = XFS_ILOCK_EXCL; | ||
129 | if (flags & XFS_ATTR_NOLOCK) | ||
130 | need_iolock = 0; | ||
131 | if (!(mask & ATTR_SIZE)) { | ||
132 | tp = xfs_trans_alloc(mp, XFS_TRANS_SETATTR_NOT_SIZE); | ||
133 | commit_flags = 0; | ||
134 | code = xfs_trans_reserve(tp, 0, XFS_ICHANGE_LOG_RES(mp), | ||
135 | 0, 0, 0); | ||
136 | if (code) { | ||
137 | lock_flags = 0; | ||
138 | goto error_return; | ||
139 | } | ||
140 | } else { | ||
141 | if (need_iolock) | ||
142 | lock_flags |= XFS_IOLOCK_EXCL; | ||
143 | } | ||
144 | |||
145 | xfs_ilock(ip, lock_flags); | ||
146 | |||
147 | /* | ||
148 | * Change file ownership. Must be the owner or privileged. | ||
149 | */ | ||
150 | if (mask & (ATTR_UID|ATTR_GID)) { | ||
151 | /* | ||
152 | * These IDs could have changed since we last looked at them. | ||
153 | * But, we're assured that if the ownership did change | ||
154 | * while we didn't have the inode locked, inode's dquot(s) | ||
155 | * would have changed also. | ||
156 | */ | ||
157 | iuid = ip->i_d.di_uid; | ||
158 | igid = ip->i_d.di_gid; | ||
159 | gid = (mask & ATTR_GID) ? iattr->ia_gid : igid; | ||
160 | uid = (mask & ATTR_UID) ? iattr->ia_uid : iuid; | ||
161 | |||
162 | /* | ||
163 | * Do a quota reservation only if uid/gid is actually | ||
164 | * going to change. | ||
165 | */ | ||
166 | if (XFS_IS_QUOTA_RUNNING(mp) && | ||
167 | ((XFS_IS_UQUOTA_ON(mp) && iuid != uid) || | ||
168 | (XFS_IS_GQUOTA_ON(mp) && igid != gid))) { | ||
169 | ASSERT(tp); | ||
170 | code = xfs_qm_vop_chown_reserve(tp, ip, udqp, gdqp, | ||
171 | capable(CAP_FOWNER) ? | ||
172 | XFS_QMOPT_FORCE_RES : 0); | ||
173 | if (code) /* out of quota */ | ||
174 | goto error_return; | ||
175 | } | ||
176 | } | ||
177 | |||
178 | /* | ||
179 | * Truncate file. Must have write permission and not be a directory. | ||
180 | */ | ||
181 | if (mask & ATTR_SIZE) { | ||
182 | /* Short circuit the truncate case for zero length files */ | ||
183 | if (iattr->ia_size == 0 && | ||
184 | ip->i_size == 0 && ip->i_d.di_nextents == 0) { | ||
185 | xfs_iunlock(ip, XFS_ILOCK_EXCL); | ||
186 | lock_flags &= ~XFS_ILOCK_EXCL; | ||
187 | if (mask & ATTR_CTIME) { | ||
188 | inode->i_mtime = inode->i_ctime = | ||
189 | current_fs_time(inode->i_sb); | ||
190 | xfs_mark_inode_dirty_sync(ip); | ||
191 | } | ||
192 | code = 0; | ||
193 | goto error_return; | ||
194 | } | ||
195 | |||
196 | if (S_ISDIR(ip->i_d.di_mode)) { | ||
197 | code = XFS_ERROR(EISDIR); | ||
198 | goto error_return; | ||
199 | } else if (!S_ISREG(ip->i_d.di_mode)) { | ||
200 | code = XFS_ERROR(EINVAL); | ||
201 | goto error_return; | ||
202 | } | ||
203 | |||
204 | /* | ||
205 | * Make sure that the dquots are attached to the inode. | ||
206 | */ | ||
207 | code = xfs_qm_dqattach_locked(ip, 0); | ||
208 | if (code) | ||
209 | goto error_return; | ||
210 | |||
211 | /* | ||
212 | * Now we can make the changes. Before we join the inode | ||
213 | * to the transaction, if ATTR_SIZE is set then take care of | ||
214 | * the part of the truncation that must be done without the | ||
215 | * inode lock. This needs to be done before joining the inode | ||
216 | * to the transaction, because the inode cannot be unlocked | ||
217 | * once it is a part of the transaction. | ||
218 | */ | ||
219 | if (iattr->ia_size > ip->i_size) { | ||
220 | /* | ||
221 | * Do the first part of growing a file: zero any data | ||
222 | * in the last block that is beyond the old EOF. We | ||
223 | * need to do this before the inode is joined to the | ||
224 | * transaction to modify the i_size. | ||
225 | */ | ||
226 | code = xfs_zero_eof(ip, iattr->ia_size, ip->i_size); | ||
227 | if (code) | ||
228 | goto error_return; | ||
229 | } | ||
230 | xfs_iunlock(ip, XFS_ILOCK_EXCL); | ||
231 | lock_flags &= ~XFS_ILOCK_EXCL; | ||
232 | |||
233 | /* | ||
234 | * We are going to log the inode size change in this | ||
235 | * transaction so any previous writes that are beyond the on | ||
236 | * disk EOF and the new EOF that have not been written out need | ||
237 | * to be written here. If we do not write the data out, we | ||
238 | * expose ourselves to the null files problem. | ||
239 | * | ||
240 | * Only flush from the on disk size to the smaller of the in | ||
241 | * memory file size or the new size as that's the range we | ||
242 | * really care about here and prevents waiting for other data | ||
243 | * not within the range we care about here. | ||
244 | */ | ||
245 | if (ip->i_size != ip->i_d.di_size && | ||
246 | iattr->ia_size > ip->i_d.di_size) { | ||
247 | code = xfs_flush_pages(ip, | ||
248 | ip->i_d.di_size, iattr->ia_size, | ||
249 | XBF_ASYNC, FI_NONE); | ||
250 | if (code) | ||
251 | goto error_return; | ||
252 | } | ||
253 | |||
254 | /* wait for all I/O to complete */ | ||
255 | xfs_ioend_wait(ip); | ||
256 | |||
257 | code = -block_truncate_page(inode->i_mapping, iattr->ia_size, | ||
258 | xfs_get_blocks); | ||
259 | if (code) | ||
260 | goto error_return; | ||
261 | |||
262 | tp = xfs_trans_alloc(mp, XFS_TRANS_SETATTR_SIZE); | ||
263 | code = xfs_trans_reserve(tp, 0, XFS_ITRUNCATE_LOG_RES(mp), 0, | ||
264 | XFS_TRANS_PERM_LOG_RES, | ||
265 | XFS_ITRUNCATE_LOG_COUNT); | ||
266 | if (code) | ||
267 | goto error_return; | ||
268 | |||
269 | truncate_setsize(inode, iattr->ia_size); | ||
270 | |||
271 | commit_flags = XFS_TRANS_RELEASE_LOG_RES; | ||
272 | lock_flags |= XFS_ILOCK_EXCL; | ||
273 | |||
274 | xfs_ilock(ip, XFS_ILOCK_EXCL); | ||
275 | |||
276 | xfs_trans_ijoin(tp, ip); | ||
277 | |||
278 | /* | ||
279 | * Only change the c/mtime if we are changing the size | ||
280 | * or we are explicitly asked to change it. This handles | ||
281 | * the semantic difference between truncate() and ftruncate() | ||
282 | * as implemented in the VFS. | ||
283 | * | ||
284 | * The regular truncate() case without ATTR_CTIME and ATTR_MTIME | ||
285 | * is a special case where we need to update the times despite | ||
286 | * not having these flags set. For all other operations the | ||
287 | * VFS set these flags explicitly if it wants a timestamp | ||
288 | * update. | ||
289 | */ | ||
290 | if (iattr->ia_size != ip->i_size && | ||
291 | (!(mask & (ATTR_CTIME | ATTR_MTIME)))) { | ||
292 | iattr->ia_ctime = iattr->ia_mtime = | ||
293 | current_fs_time(inode->i_sb); | ||
294 | mask |= ATTR_CTIME | ATTR_MTIME; | ||
295 | } | ||
296 | |||
297 | if (iattr->ia_size > ip->i_size) { | ||
298 | ip->i_d.di_size = iattr->ia_size; | ||
299 | ip->i_size = iattr->ia_size; | ||
300 | xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); | ||
301 | } else if (iattr->ia_size <= ip->i_size || | ||
302 | (iattr->ia_size == 0 && ip->i_d.di_nextents)) { | ||
303 | /* | ||
304 | * signal a sync transaction unless | ||
305 | * we're truncating an already unlinked | ||
306 | * file on a wsync filesystem | ||
307 | */ | ||
308 | code = xfs_itruncate_finish(&tp, ip, iattr->ia_size, | ||
309 | XFS_DATA_FORK, | ||
310 | ((ip->i_d.di_nlink != 0 || | ||
311 | !(mp->m_flags & XFS_MOUNT_WSYNC)) | ||
312 | ? 1 : 0)); | ||
313 | if (code) | ||
314 | goto abort_return; | ||
315 | /* | ||
316 | * Truncated "down", so we're removing references | ||
317 | * to old data here - if we now delay flushing for | ||
318 | * a long time, we expose ourselves unduly to the | ||
319 | * notorious NULL files problem. So, we mark this | ||
320 | * vnode and flush it when the file is closed, and | ||
321 | * do not wait the usual (long) time for writeout. | ||
322 | */ | ||
323 | xfs_iflags_set(ip, XFS_ITRUNCATED); | ||
324 | } | ||
325 | } else if (tp) { | ||
326 | xfs_trans_ijoin(tp, ip); | ||
327 | } | ||
328 | |||
329 | /* | ||
330 | * Change file ownership. Must be the owner or privileged. | ||
331 | */ | ||
332 | if (mask & (ATTR_UID|ATTR_GID)) { | ||
333 | /* | ||
334 | * CAP_FSETID overrides the following restrictions: | ||
335 | * | ||
336 | * The set-user-ID and set-group-ID bits of a file will be | ||
337 | * cleared upon successful return from chown() | ||
338 | */ | ||
339 | if ((ip->i_d.di_mode & (S_ISUID|S_ISGID)) && | ||
340 | !capable(CAP_FSETID)) { | ||
341 | ip->i_d.di_mode &= ~(S_ISUID|S_ISGID); | ||
342 | } | ||
343 | |||
344 | /* | ||
345 | * Change the ownerships and register quota modifications | ||
346 | * in the transaction. | ||
347 | */ | ||
348 | if (iuid != uid) { | ||
349 | if (XFS_IS_QUOTA_RUNNING(mp) && XFS_IS_UQUOTA_ON(mp)) { | ||
350 | ASSERT(mask & ATTR_UID); | ||
351 | ASSERT(udqp); | ||
352 | olddquot1 = xfs_qm_vop_chown(tp, ip, | ||
353 | &ip->i_udquot, udqp); | ||
354 | } | ||
355 | ip->i_d.di_uid = uid; | ||
356 | inode->i_uid = uid; | ||
357 | } | ||
358 | if (igid != gid) { | ||
359 | if (XFS_IS_QUOTA_RUNNING(mp) && XFS_IS_GQUOTA_ON(mp)) { | ||
360 | ASSERT(!XFS_IS_PQUOTA_ON(mp)); | ||
361 | ASSERT(mask & ATTR_GID); | ||
362 | ASSERT(gdqp); | ||
363 | olddquot2 = xfs_qm_vop_chown(tp, ip, | ||
364 | &ip->i_gdquot, gdqp); | ||
365 | } | ||
366 | ip->i_d.di_gid = gid; | ||
367 | inode->i_gid = gid; | ||
368 | } | ||
369 | } | ||
370 | |||
371 | /* | ||
372 | * Change file access modes. | ||
373 | */ | ||
374 | if (mask & ATTR_MODE) { | ||
375 | umode_t mode = iattr->ia_mode; | ||
376 | |||
377 | if (!in_group_p(inode->i_gid) && !capable(CAP_FSETID)) | ||
378 | mode &= ~S_ISGID; | ||
379 | |||
380 | ip->i_d.di_mode &= S_IFMT; | ||
381 | ip->i_d.di_mode |= mode & ~S_IFMT; | ||
382 | |||
383 | inode->i_mode &= S_IFMT; | ||
384 | inode->i_mode |= mode & ~S_IFMT; | ||
385 | } | ||
386 | |||
387 | /* | ||
388 | * Change file access or modified times. | ||
389 | */ | ||
390 | if (mask & ATTR_ATIME) { | ||
391 | inode->i_atime = iattr->ia_atime; | ||
392 | ip->i_d.di_atime.t_sec = iattr->ia_atime.tv_sec; | ||
393 | ip->i_d.di_atime.t_nsec = iattr->ia_atime.tv_nsec; | ||
394 | ip->i_update_core = 1; | ||
395 | } | ||
396 | if (mask & ATTR_CTIME) { | ||
397 | inode->i_ctime = iattr->ia_ctime; | ||
398 | ip->i_d.di_ctime.t_sec = iattr->ia_ctime.tv_sec; | ||
399 | ip->i_d.di_ctime.t_nsec = iattr->ia_ctime.tv_nsec; | ||
400 | ip->i_update_core = 1; | ||
401 | } | ||
402 | if (mask & ATTR_MTIME) { | ||
403 | inode->i_mtime = iattr->ia_mtime; | ||
404 | ip->i_d.di_mtime.t_sec = iattr->ia_mtime.tv_sec; | ||
405 | ip->i_d.di_mtime.t_nsec = iattr->ia_mtime.tv_nsec; | ||
406 | ip->i_update_core = 1; | ||
407 | } | ||
408 | |||
409 | /* | ||
410 | * And finally, log the inode core if any attribute in it | ||
411 | * has been changed. | ||
412 | */ | ||
413 | if (mask & (ATTR_UID|ATTR_GID|ATTR_MODE| | ||
414 | ATTR_ATIME|ATTR_CTIME|ATTR_MTIME)) | ||
415 | xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); | ||
416 | |||
417 | XFS_STATS_INC(xs_ig_attrchg); | ||
418 | |||
419 | /* | ||
420 | * If this is a synchronous mount, make sure that the | ||
421 | * transaction goes to disk before returning to the user. | ||
422 | * This is slightly sub-optimal in that truncates require | ||
423 | * two sync transactions instead of one for wsync filesystems. | ||
424 | * One for the truncate and one for the timestamps since we | ||
425 | * don't want to change the timestamps unless we're sure the | ||
426 | * truncate worked. Truncates are less than 1% of the laddis | ||
427 | * mix so this probably isn't worth the trouble to optimize. | ||
428 | */ | ||
429 | code = 0; | ||
430 | if (mp->m_flags & XFS_MOUNT_WSYNC) | ||
431 | xfs_trans_set_sync(tp); | ||
432 | |||
433 | code = xfs_trans_commit(tp, commit_flags); | ||
434 | |||
435 | xfs_iunlock(ip, lock_flags); | ||
436 | |||
437 | /* | ||
438 | * Release any dquot(s) the inode had kept before chown. | ||
439 | */ | ||
440 | xfs_qm_dqrele(olddquot1); | ||
441 | xfs_qm_dqrele(olddquot2); | ||
442 | xfs_qm_dqrele(udqp); | ||
443 | xfs_qm_dqrele(gdqp); | ||
444 | |||
445 | if (code) | ||
446 | return code; | ||
447 | |||
448 | /* | ||
449 | * XXX(hch): Updating the ACL entries is not atomic vs the i_mode | ||
450 | * update. We could avoid this with linked transactions | ||
451 | * and passing down the transaction pointer all the way | ||
452 | * to attr_set. No previous user of the generic | ||
453 | * Posix ACL code seems to care about this issue either. | ||
454 | */ | ||
455 | if ((mask & ATTR_MODE) && !(flags & XFS_ATTR_NOACL)) { | ||
456 | code = -xfs_acl_chmod(inode); | ||
457 | if (code) | ||
458 | return XFS_ERROR(code); | ||
459 | } | ||
460 | |||
461 | return 0; | ||
462 | |||
463 | abort_return: | ||
464 | commit_flags |= XFS_TRANS_ABORT; | ||
465 | error_return: | ||
466 | xfs_qm_dqrele(udqp); | ||
467 | xfs_qm_dqrele(gdqp); | ||
468 | if (tp) { | ||
469 | xfs_trans_cancel(tp, commit_flags); | ||
470 | } | ||
471 | if (lock_flags != 0) { | ||
472 | xfs_iunlock(ip, lock_flags); | ||
473 | } | ||
474 | return code; | ||
475 | } | ||
476 | |||
477 | /* | 53 | /* |
478 | * The maximum pathlen is 1024 bytes. Since the minimum file system | 54 | * The maximum pathlen is 1024 bytes. Since the minimum file system |
479 | * blocksize is 512 bytes, we can get a max of 2 extents back from | 55 | * blocksize is 512 bytes, we can get a max of 2 extents back from |
@@ -2784,7 +2360,7 @@ xfs_change_file_space( | |||
2784 | iattr.ia_valid = ATTR_SIZE; | 2360 | iattr.ia_valid = ATTR_SIZE; |
2785 | iattr.ia_size = startoffset; | 2361 | iattr.ia_size = startoffset; |
2786 | 2362 | ||
2787 | error = xfs_setattr(ip, &iattr, attr_flags); | 2363 | error = xfs_setattr_size(ip, &iattr, attr_flags); |
2788 | 2364 | ||
2789 | if (error) | 2365 | if (error) |
2790 | return error; | 2366 | return error; |