pnfs: Prepare for flexfiles by pulling out common code
[firefly-linux-kernel-4.4.55.git] / fs / nfs / pnfs_nfs.c
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);