forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
/
xfs_pnfs.c
335 lines (289 loc) · 8.48 KB
/
xfs_pnfs.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2014 Christoph Hellwig.
*/
#include "xfs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_log_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_inode.h"
#include "xfs_trans.h"
#include "xfs_bmap.h"
#include "xfs_iomap.h"
#include "xfs_pnfs.h"
/*
* Ensure that we do not have any outstanding pNFS layouts that can be used by
* clients to directly read from or write to this inode. This must be called
* before every operation that can remove blocks from the extent map.
* Additionally we call it during the write operation, where aren't concerned
* about exposing unallocated blocks but just want to provide basic
* synchronization between a local writer and pNFS clients. mmap writes would
* also benefit from this sort of synchronization, but due to the tricky locking
* rules in the page fault path we don't bother.
*/
int
xfs_break_leased_layouts(
struct inode *inode,
uint *iolock,
bool *did_unlock)
{
struct xfs_inode *ip = XFS_I(inode);
int error;
while ((error = break_layout(inode, false)) == -EWOULDBLOCK) {
xfs_iunlock(ip, *iolock);
*did_unlock = true;
error = break_layout(inode, true);
*iolock &= ~XFS_IOLOCK_SHARED;
*iolock |= XFS_IOLOCK_EXCL;
xfs_ilock(ip, *iolock);
}
return error;
}
/*
* Get a unique ID including its location so that the client can identify
* the exported device.
*/
int
xfs_fs_get_uuid(
struct super_block *sb,
u8 *buf,
u32 *len,
u64 *offset)
{
struct xfs_mount *mp = XFS_M(sb);
xfs_notice_once(mp,
"Using experimental pNFS feature, use at your own risk!");
if (*len < sizeof(uuid_t))
return -EINVAL;
memcpy(buf, &mp->m_sb.sb_uuid, sizeof(uuid_t));
*len = sizeof(uuid_t);
*offset = offsetof(struct xfs_dsb, sb_uuid);
return 0;
}
/*
* We cannot use file based VFS helpers such as file_modified() to update
* inode state as we modify the data/metadata in the inode here. Hence we have
* to open code the timestamp updates and SUID/SGID stripping. We also need
* to set the inode prealloc flag to ensure that the extents we allocate are not
* removed if the inode is reclaimed from memory before xfs_fs_block_commit()
* is from the client to indicate that data has been written and the file size
* can be extended.
*/
static int
xfs_fs_map_update_inode(
struct xfs_inode *ip)
{
struct xfs_trans *tp;
int error;
error = xfs_trans_alloc(ip->i_mount, &M_RES(ip->i_mount)->tr_writeid,
0, 0, 0, &tp);
if (error)
return error;
xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
VFS_I(ip)->i_mode &= ~S_ISUID;
if (VFS_I(ip)->i_mode & S_IXGRP)
VFS_I(ip)->i_mode &= ~S_ISGID;
xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
ip->i_diflags |= XFS_DIFLAG_PREALLOC;
xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
return xfs_trans_commit(tp);
}
/*
* Get a layout for the pNFS client.
*/
int
xfs_fs_map_blocks(
struct inode *inode,
loff_t offset,
u64 length,
struct iomap *iomap,
bool write,
u32 *device_generation)
{
struct xfs_inode *ip = XFS_I(inode);
struct xfs_mount *mp = ip->i_mount;
struct xfs_bmbt_irec imap;
xfs_fileoff_t offset_fsb, end_fsb;
loff_t limit;
int bmapi_flags = XFS_BMAPI_ENTIRE;
int nimaps = 1;
uint lock_flags;
int error = 0;
if (xfs_is_shutdown(mp))
return -EIO;
/*
* We can't export inodes residing on the realtime device. The realtime
* device doesn't have a UUID to identify it, so the client has no way
* to find it.
*/
if (XFS_IS_REALTIME_INODE(ip))
return -ENXIO;
/*
* The pNFS block layout spec actually supports reflink like
* functionality, but the Linux pNFS server doesn't implement it yet.
*/
if (xfs_is_reflink_inode(ip))
return -ENXIO;
/*
* Lock out any other I/O before we flush and invalidate the pagecache,
* and then hand out a layout to the remote system. This is very
* similar to direct I/O, except that the synchronization is much more
* complicated. See the comment near xfs_break_leased_layouts
* for a detailed explanation.
*/
xfs_ilock(ip, XFS_IOLOCK_EXCL);
error = -EINVAL;
limit = mp->m_super->s_maxbytes;
if (!write)
limit = max(limit, round_up(i_size_read(inode),
inode->i_sb->s_blocksize));
if (offset > limit)
goto out_unlock;
if (offset > limit - length)
length = limit - offset;
error = filemap_write_and_wait(inode->i_mapping);
if (error)
goto out_unlock;
error = invalidate_inode_pages2(inode->i_mapping);
if (WARN_ON_ONCE(error))
goto out_unlock;
end_fsb = XFS_B_TO_FSB(mp, (xfs_ufsize_t)offset + length);
offset_fsb = XFS_B_TO_FSBT(mp, offset);
lock_flags = xfs_ilock_data_map_shared(ip);
error = xfs_bmapi_read(ip, offset_fsb, end_fsb - offset_fsb,
&imap, &nimaps, bmapi_flags);
ASSERT(!nimaps || imap.br_startblock != DELAYSTARTBLOCK);
if (!error && write &&
(!nimaps || imap.br_startblock == HOLESTARTBLOCK)) {
if (offset + length > XFS_ISIZE(ip))
end_fsb = xfs_iomap_eof_align_last_fsb(ip, end_fsb);
else if (nimaps && imap.br_startblock == HOLESTARTBLOCK)
end_fsb = min(end_fsb, imap.br_startoff +
imap.br_blockcount);
xfs_iunlock(ip, lock_flags);
error = xfs_iomap_write_direct(ip, offset_fsb,
end_fsb - offset_fsb, 0, &imap);
if (error)
goto out_unlock;
/*
* Ensure the next transaction is committed synchronously so
* that the blocks allocated and handed out to the client are
* guaranteed to be present even after a server crash.
*/
error = xfs_fs_map_update_inode(ip);
if (!error)
error = xfs_log_force_inode(ip);
if (error)
goto out_unlock;
} else {
xfs_iunlock(ip, lock_flags);
}
xfs_iunlock(ip, XFS_IOLOCK_EXCL);
error = xfs_bmbt_to_iomap(ip, iomap, &imap, 0, 0);
*device_generation = mp->m_generation;
return error;
out_unlock:
xfs_iunlock(ip, XFS_IOLOCK_EXCL);
return error;
}
/*
* Ensure the size update falls into a valid allocated block.
*/
static int
xfs_pnfs_validate_isize(
struct xfs_inode *ip,
xfs_off_t isize)
{
struct xfs_bmbt_irec imap;
int nimaps = 1;
int error = 0;
xfs_ilock(ip, XFS_ILOCK_SHARED);
error = xfs_bmapi_read(ip, XFS_B_TO_FSBT(ip->i_mount, isize - 1), 1,
&imap, &nimaps, 0);
xfs_iunlock(ip, XFS_ILOCK_SHARED);
if (error)
return error;
if (imap.br_startblock == HOLESTARTBLOCK ||
imap.br_startblock == DELAYSTARTBLOCK ||
imap.br_state == XFS_EXT_UNWRITTEN)
return -EIO;
return 0;
}
/*
* Make sure the blocks described by maps are stable on disk. This includes
* converting any unwritten extents, flushing the disk cache and updating the
* time stamps.
*
* Note that we rely on the caller to always send us a timestamp update so that
* we always commit a transaction here. If that stops being true we will have
* to manually flush the cache here similar to what the fsync code path does
* for datasyncs on files that have no dirty metadata.
*/
int
xfs_fs_commit_blocks(
struct inode *inode,
struct iomap *maps,
int nr_maps,
struct iattr *iattr)
{
struct xfs_inode *ip = XFS_I(inode);
struct xfs_mount *mp = ip->i_mount;
struct xfs_trans *tp;
bool update_isize = false;
int error, i;
loff_t size;
ASSERT(iattr->ia_valid & (ATTR_ATIME|ATTR_CTIME|ATTR_MTIME));
xfs_ilock(ip, XFS_IOLOCK_EXCL);
size = i_size_read(inode);
if ((iattr->ia_valid & ATTR_SIZE) && iattr->ia_size > size) {
update_isize = true;
size = iattr->ia_size;
}
for (i = 0; i < nr_maps; i++) {
u64 start, length, end;
start = maps[i].offset;
if (start > size)
continue;
end = start + maps[i].length;
if (end > size)
end = size;
length = end - start;
if (!length)
continue;
/*
* Make sure reads through the pagecache see the new data.
*/
error = invalidate_inode_pages2_range(inode->i_mapping,
start >> PAGE_SHIFT,
(end - 1) >> PAGE_SHIFT);
WARN_ON_ONCE(error);
error = xfs_iomap_write_unwritten(ip, start, length, false);
if (error)
goto out_drop_iolock;
}
if (update_isize) {
error = xfs_pnfs_validate_isize(ip, size);
if (error)
goto out_drop_iolock;
}
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp);
if (error)
goto out_drop_iolock;
xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
ASSERT(!(iattr->ia_valid & (ATTR_UID | ATTR_GID)));
setattr_copy(&init_user_ns, inode, iattr);
if (update_isize) {
i_size_write(inode, iattr->ia_size);
ip->i_disk_size = iattr->ia_size;
}
xfs_trans_set_sync(tp);
error = xfs_trans_commit(tp);
out_drop_iolock:
xfs_iunlock(ip, XFS_IOLOCK_EXCL);
return error;
}