Commit | Line | Data |
---|---|---|
f54bcf2e TH |
1 | /* |
2 | * Common NFS I/O operations for the pnfs file based | |
3 | * layout drivers. | |
4 | * | |
5 | * Copyright (c) 2014, Primary Data, Inc. All rights reserved. | |
6 | * | |
7 | * Tom Haynes <loghyr@primarydata.com> | |
8 | */ | |
9 | ||
10 | #include <linux/nfs_fs.h> | |
11 | #include <linux/nfs_page.h> | |
12 | ||
13 | #include "internal.h" | |
14 | #include "pnfs.h" | |
15 | ||
16 | static void pnfs_generic_fenceme(struct inode *inode, | |
17 | struct pnfs_layout_hdr *lo) | |
18 | { | |
19 | if (!test_and_clear_bit(NFS_LAYOUT_RETURN, &lo->plh_flags)) | |
20 | return; | |
21 | pnfs_return_layout(inode); | |
22 | } | |
23 | ||
24 | void pnfs_generic_rw_release(void *data) | |
25 | { | |
26 | struct nfs_pgio_header *hdr = data; | |
27 | struct pnfs_layout_hdr *lo = hdr->lseg->pls_layout; | |
28 | ||
29 | pnfs_generic_fenceme(lo->plh_inode, lo); | |
30 | nfs_put_client(hdr->ds_clp); | |
31 | hdr->mds_ops->rpc_release(data); | |
32 | } | |
33 | EXPORT_SYMBOL_GPL(pnfs_generic_rw_release); | |
34 | ||
35 | /* Fake up some data that will cause nfs_commit_release to retry the writes. */ | |
36 | void pnfs_generic_prepare_to_resend_writes(struct nfs_commit_data *data) | |
37 | { | |
38 | struct nfs_page *first = nfs_list_entry(data->pages.next); | |
39 | ||
40 | data->task.tk_status = 0; | |
41 | memcpy(&data->verf.verifier, &first->wb_verf, | |
42 | sizeof(data->verf.verifier)); | |
43 | data->verf.verifier.data[0]++; /* ensure verifier mismatch */ | |
44 | } | |
45 | EXPORT_SYMBOL_GPL(pnfs_generic_prepare_to_resend_writes); | |
46 | ||
47 | void pnfs_generic_write_commit_done(struct rpc_task *task, void *data) | |
48 | { | |
49 | struct nfs_commit_data *wdata = data; | |
50 | ||
51 | /* Note this may cause RPC to be resent */ | |
52 | wdata->mds_ops->rpc_call_done(task, data); | |
53 | } | |
54 | EXPORT_SYMBOL_GPL(pnfs_generic_write_commit_done); | |
55 | ||
56 | void pnfs_generic_commit_release(void *calldata) | |
57 | { | |
58 | struct nfs_commit_data *data = calldata; | |
59 | ||
60 | data->completion_ops->completion(data); | |
61 | pnfs_put_lseg(data->lseg); | |
62 | nfs_put_client(data->ds_clp); | |
63 | nfs_commitdata_release(data); | |
64 | } | |
65 | EXPORT_SYMBOL_GPL(pnfs_generic_commit_release); | |
66 | ||
67 | /* The generic layer is about to remove the req from the commit list. | |
68 | * If this will make the bucket empty, it will need to put the lseg reference. | |
69 | * Note this is must be called holding the inode (/cinfo) lock | |
70 | */ | |
71 | void | |
72 | pnfs_generic_clear_request_commit(struct nfs_page *req, | |
73 | struct nfs_commit_info *cinfo) | |
74 | { | |
75 | struct pnfs_layout_segment *freeme = NULL; | |
76 | ||
77 | if (!test_and_clear_bit(PG_COMMIT_TO_DS, &req->wb_flags)) | |
78 | goto out; | |
79 | cinfo->ds->nwritten--; | |
80 | if (list_is_singular(&req->wb_list)) { | |
81 | struct pnfs_commit_bucket *bucket; | |
82 | ||
83 | bucket = list_first_entry(&req->wb_list, | |
84 | struct pnfs_commit_bucket, | |
85 | written); | |
86 | freeme = bucket->wlseg; | |
87 | bucket->wlseg = NULL; | |
88 | } | |
89 | out: | |
90 | nfs_request_remove_commit_list(req, cinfo); | |
91 | pnfs_put_lseg_locked(freeme); | |
92 | } | |
93 | EXPORT_SYMBOL_GPL(pnfs_generic_clear_request_commit); | |
94 | ||
95 | static int | |
96 | pnfs_generic_transfer_commit_list(struct list_head *src, struct list_head *dst, | |
97 | struct nfs_commit_info *cinfo, int max) | |
98 | { | |
99 | struct nfs_page *req, *tmp; | |
100 | int ret = 0; | |
101 | ||
102 | list_for_each_entry_safe(req, tmp, src, wb_list) { | |
103 | if (!nfs_lock_request(req)) | |
104 | continue; | |
105 | kref_get(&req->wb_kref); | |
106 | if (cond_resched_lock(cinfo->lock)) | |
107 | list_safe_reset_next(req, tmp, wb_list); | |
108 | nfs_request_remove_commit_list(req, cinfo); | |
109 | clear_bit(PG_COMMIT_TO_DS, &req->wb_flags); | |
110 | nfs_list_add_request(req, dst); | |
111 | ret++; | |
112 | if ((ret == max) && !cinfo->dreq) | |
113 | break; | |
114 | } | |
115 | return ret; | |
116 | } | |
117 | ||
118 | /* Note called with cinfo->lock held. */ | |
119 | static int | |
120 | pnfs_generic_scan_ds_commit_list(struct pnfs_commit_bucket *bucket, | |
121 | struct nfs_commit_info *cinfo, | |
122 | int max) | |
123 | { | |
124 | struct list_head *src = &bucket->written; | |
125 | struct list_head *dst = &bucket->committing; | |
126 | int ret; | |
127 | ||
128 | ret = pnfs_generic_transfer_commit_list(src, dst, cinfo, max); | |
129 | if (ret) { | |
130 | cinfo->ds->nwritten -= ret; | |
131 | cinfo->ds->ncommitting += ret; | |
132 | bucket->clseg = bucket->wlseg; | |
133 | if (list_empty(src)) | |
134 | bucket->wlseg = NULL; | |
135 | else | |
136 | pnfs_get_lseg(bucket->clseg); | |
137 | } | |
138 | return ret; | |
139 | } | |
140 | ||
141 | /* Move reqs from written to committing lists, returning count of number moved. | |
142 | * Note called with cinfo->lock held. | |
143 | */ | |
144 | int pnfs_generic_scan_commit_lists(struct nfs_commit_info *cinfo, | |
145 | int max) | |
146 | { | |
147 | int i, rv = 0, cnt; | |
148 | ||
149 | for (i = 0; i < cinfo->ds->nbuckets && max != 0; i++) { | |
150 | cnt = pnfs_generic_scan_ds_commit_list(&cinfo->ds->buckets[i], | |
151 | cinfo, max); | |
152 | max -= cnt; | |
153 | rv += cnt; | |
154 | } | |
155 | return rv; | |
156 | } | |
157 | EXPORT_SYMBOL_GPL(pnfs_generic_scan_commit_lists); | |
158 | ||
159 | /* Pull everything off the committing lists and dump into @dst */ | |
160 | void pnfs_generic_recover_commit_reqs(struct list_head *dst, | |
161 | struct nfs_commit_info *cinfo) | |
162 | { | |
163 | struct pnfs_commit_bucket *b; | |
164 | struct pnfs_layout_segment *freeme; | |
165 | int i; | |
166 | ||
167 | restart: | |
168 | spin_lock(cinfo->lock); | |
169 | for (i = 0, b = cinfo->ds->buckets; i < cinfo->ds->nbuckets; i++, b++) { | |
170 | if (pnfs_generic_transfer_commit_list(&b->written, dst, | |
171 | cinfo, 0)) { | |
172 | freeme = b->wlseg; | |
173 | b->wlseg = NULL; | |
174 | spin_unlock(cinfo->lock); | |
175 | pnfs_put_lseg(freeme); | |
176 | goto restart; | |
177 | } | |
178 | } | |
179 | cinfo->ds->nwritten = 0; | |
180 | spin_unlock(cinfo->lock); | |
181 | } | |
182 | EXPORT_SYMBOL_GPL(pnfs_generic_recover_commit_reqs); | |
183 | ||
184 | static void pnfs_generic_retry_commit(struct nfs_commit_info *cinfo, int idx) | |
185 | { | |
186 | struct pnfs_ds_commit_info *fl_cinfo = cinfo->ds; | |
187 | struct pnfs_commit_bucket *bucket; | |
188 | struct pnfs_layout_segment *freeme; | |
189 | int i; | |
190 | ||
191 | for (i = idx; i < fl_cinfo->nbuckets; i++) { | |
192 | bucket = &fl_cinfo->buckets[i]; | |
193 | if (list_empty(&bucket->committing)) | |
194 | continue; | |
195 | nfs_retry_commit(&bucket->committing, bucket->clseg, cinfo); | |
196 | spin_lock(cinfo->lock); | |
197 | freeme = bucket->clseg; | |
198 | bucket->clseg = NULL; | |
199 | spin_unlock(cinfo->lock); | |
200 | pnfs_put_lseg(freeme); | |
201 | } | |
202 | } | |
203 | ||
204 | static unsigned int | |
205 | pnfs_generic_alloc_ds_commits(struct nfs_commit_info *cinfo, | |
206 | struct list_head *list) | |
207 | { | |
208 | struct pnfs_ds_commit_info *fl_cinfo; | |
209 | struct pnfs_commit_bucket *bucket; | |
210 | struct nfs_commit_data *data; | |
211 | int i; | |
212 | unsigned int nreq = 0; | |
213 | ||
214 | fl_cinfo = cinfo->ds; | |
215 | bucket = fl_cinfo->buckets; | |
216 | for (i = 0; i < fl_cinfo->nbuckets; i++, bucket++) { | |
217 | if (list_empty(&bucket->committing)) | |
218 | continue; | |
219 | data = nfs_commitdata_alloc(); | |
220 | if (!data) | |
221 | break; | |
222 | data->ds_commit_index = i; | |
223 | spin_lock(cinfo->lock); | |
224 | data->lseg = bucket->clseg; | |
225 | bucket->clseg = NULL; | |
226 | spin_unlock(cinfo->lock); | |
227 | list_add(&data->pages, list); | |
228 | nreq++; | |
229 | } | |
230 | ||
231 | /* Clean up on error */ | |
232 | pnfs_generic_retry_commit(cinfo, i); | |
233 | return nreq; | |
234 | } | |
235 | ||
236 | /* This follows nfs_commit_list pretty closely */ | |
237 | int | |
238 | pnfs_generic_commit_pagelist(struct inode *inode, struct list_head *mds_pages, | |
239 | int how, struct nfs_commit_info *cinfo, | |
240 | int (*initiate_commit)(struct nfs_commit_data *data, | |
241 | int how)) | |
242 | { | |
243 | struct nfs_commit_data *data, *tmp; | |
244 | LIST_HEAD(list); | |
245 | unsigned int nreq = 0; | |
246 | ||
247 | if (!list_empty(mds_pages)) { | |
248 | data = nfs_commitdata_alloc(); | |
249 | if (data != NULL) { | |
250 | data->lseg = NULL; | |
251 | list_add(&data->pages, &list); | |
252 | nreq++; | |
253 | } else { | |
254 | nfs_retry_commit(mds_pages, NULL, cinfo); | |
255 | pnfs_generic_retry_commit(cinfo, 0); | |
256 | cinfo->completion_ops->error_cleanup(NFS_I(inode)); | |
257 | return -ENOMEM; | |
258 | } | |
259 | } | |
260 | ||
261 | nreq += pnfs_generic_alloc_ds_commits(cinfo, &list); | |
262 | ||
263 | if (nreq == 0) { | |
264 | cinfo->completion_ops->error_cleanup(NFS_I(inode)); | |
265 | goto out; | |
266 | } | |
267 | ||
268 | atomic_add(nreq, &cinfo->mds->rpcs_out); | |
269 | ||
270 | list_for_each_entry_safe(data, tmp, &list, pages) { | |
271 | list_del_init(&data->pages); | |
272 | if (!data->lseg) { | |
273 | nfs_init_commit(data, mds_pages, NULL, cinfo); | |
274 | nfs_initiate_commit(NFS_CLIENT(inode), data, | |
275 | data->mds_ops, how, 0); | |
276 | } else { | |
277 | struct pnfs_commit_bucket *buckets; | |
278 | ||
279 | buckets = cinfo->ds->buckets; | |
280 | nfs_init_commit(data, | |
281 | &buckets[data->ds_commit_index].committing, | |
282 | data->lseg, | |
283 | cinfo); | |
284 | initiate_commit(data, how); | |
285 | } | |
286 | } | |
287 | out: | |
288 | cinfo->ds->ncommitting = 0; | |
289 | return PNFS_ATTEMPTED; | |
290 | } | |
291 | EXPORT_SYMBOL_GPL(pnfs_generic_commit_pagelist); |