diff options
Diffstat (limited to 'fs/nfs/callback_xdr.c')
-rw-r--r-- | fs/nfs/callback_xdr.c | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/fs/nfs/callback_xdr.c b/fs/nfs/callback_xdr.c new file mode 100644 index 000000000000..d271df9df2b2 --- /dev/null +++ b/fs/nfs/callback_xdr.c | |||
@@ -0,0 +1,481 @@ | |||
1 | /* | ||
2 | * linux/fs/nfs/callback_xdr.c | ||
3 | * | ||
4 | * Copyright (C) 2004 Trond Myklebust | ||
5 | * | ||
6 | * NFSv4 callback encode/decode procedures | ||
7 | */ | ||
8 | #include <linux/config.h> | ||
9 | #include <linux/kernel.h> | ||
10 | #include <linux/sunrpc/svc.h> | ||
11 | #include <linux/nfs4.h> | ||
12 | #include <linux/nfs_fs.h> | ||
13 | #include "callback.h" | ||
14 | |||
15 | #define CB_OP_TAGLEN_MAXSZ (512) | ||
16 | #define CB_OP_HDR_RES_MAXSZ (2 + CB_OP_TAGLEN_MAXSZ) | ||
17 | #define CB_OP_GETATTR_BITMAP_MAXSZ (4) | ||
18 | #define CB_OP_GETATTR_RES_MAXSZ (CB_OP_HDR_RES_MAXSZ + \ | ||
19 | CB_OP_GETATTR_BITMAP_MAXSZ + \ | ||
20 | 2 + 2 + 3 + 3) | ||
21 | #define CB_OP_RECALL_RES_MAXSZ (CB_OP_HDR_RES_MAXSZ) | ||
22 | |||
23 | #define NFSDBG_FACILITY NFSDBG_CALLBACK | ||
24 | |||
25 | typedef unsigned (*callback_process_op_t)(void *, void *); | ||
26 | typedef unsigned (*callback_decode_arg_t)(struct svc_rqst *, struct xdr_stream *, void *); | ||
27 | typedef unsigned (*callback_encode_res_t)(struct svc_rqst *, struct xdr_stream *, void *); | ||
28 | |||
29 | |||
30 | struct callback_op { | ||
31 | callback_process_op_t process_op; | ||
32 | callback_decode_arg_t decode_args; | ||
33 | callback_encode_res_t encode_res; | ||
34 | long res_maxsize; | ||
35 | }; | ||
36 | |||
37 | static struct callback_op callback_ops[]; | ||
38 | |||
39 | static int nfs4_callback_null(struct svc_rqst *rqstp, void *argp, void *resp) | ||
40 | { | ||
41 | return htonl(NFS4_OK); | ||
42 | } | ||
43 | |||
44 | static int nfs4_decode_void(struct svc_rqst *rqstp, uint32_t *p, void *dummy) | ||
45 | { | ||
46 | return xdr_argsize_check(rqstp, p); | ||
47 | } | ||
48 | |||
49 | static int nfs4_encode_void(struct svc_rqst *rqstp, uint32_t *p, void *dummy) | ||
50 | { | ||
51 | return xdr_ressize_check(rqstp, p); | ||
52 | } | ||
53 | |||
54 | static uint32_t *read_buf(struct xdr_stream *xdr, int nbytes) | ||
55 | { | ||
56 | uint32_t *p; | ||
57 | |||
58 | p = xdr_inline_decode(xdr, nbytes); | ||
59 | if (unlikely(p == NULL)) | ||
60 | printk(KERN_WARNING "NFSv4 callback reply buffer overflowed!\n"); | ||
61 | return p; | ||
62 | } | ||
63 | |||
64 | static unsigned decode_string(struct xdr_stream *xdr, unsigned int *len, const char **str) | ||
65 | { | ||
66 | uint32_t *p; | ||
67 | |||
68 | p = read_buf(xdr, 4); | ||
69 | if (unlikely(p == NULL)) | ||
70 | return htonl(NFS4ERR_RESOURCE); | ||
71 | *len = ntohl(*p); | ||
72 | |||
73 | if (*len != 0) { | ||
74 | p = read_buf(xdr, *len); | ||
75 | if (unlikely(p == NULL)) | ||
76 | return htonl(NFS4ERR_RESOURCE); | ||
77 | *str = (const char *)p; | ||
78 | } else | ||
79 | *str = NULL; | ||
80 | |||
81 | return 0; | ||
82 | } | ||
83 | |||
84 | static unsigned decode_fh(struct xdr_stream *xdr, struct nfs_fh *fh) | ||
85 | { | ||
86 | uint32_t *p; | ||
87 | |||
88 | p = read_buf(xdr, 4); | ||
89 | if (unlikely(p == NULL)) | ||
90 | return htonl(NFS4ERR_RESOURCE); | ||
91 | fh->size = ntohl(*p); | ||
92 | if (fh->size > NFS4_FHSIZE) | ||
93 | return htonl(NFS4ERR_BADHANDLE); | ||
94 | p = read_buf(xdr, fh->size); | ||
95 | if (unlikely(p == NULL)) | ||
96 | return htonl(NFS4ERR_RESOURCE); | ||
97 | memcpy(&fh->data[0], p, fh->size); | ||
98 | memset(&fh->data[fh->size], 0, sizeof(fh->data) - fh->size); | ||
99 | return 0; | ||
100 | } | ||
101 | |||
102 | static unsigned decode_bitmap(struct xdr_stream *xdr, uint32_t *bitmap) | ||
103 | { | ||
104 | uint32_t *p; | ||
105 | unsigned int attrlen; | ||
106 | |||
107 | p = read_buf(xdr, 4); | ||
108 | if (unlikely(p == NULL)) | ||
109 | return htonl(NFS4ERR_RESOURCE); | ||
110 | attrlen = ntohl(*p); | ||
111 | p = read_buf(xdr, attrlen << 2); | ||
112 | if (unlikely(p == NULL)) | ||
113 | return htonl(NFS4ERR_RESOURCE); | ||
114 | if (likely(attrlen > 0)) | ||
115 | bitmap[0] = ntohl(*p++); | ||
116 | if (attrlen > 1) | ||
117 | bitmap[1] = ntohl(*p); | ||
118 | return 0; | ||
119 | } | ||
120 | |||
121 | static unsigned decode_stateid(struct xdr_stream *xdr, nfs4_stateid *stateid) | ||
122 | { | ||
123 | uint32_t *p; | ||
124 | |||
125 | p = read_buf(xdr, 16); | ||
126 | if (unlikely(p == NULL)) | ||
127 | return htonl(NFS4ERR_RESOURCE); | ||
128 | memcpy(stateid->data, p, 16); | ||
129 | return 0; | ||
130 | } | ||
131 | |||
132 | static unsigned decode_compound_hdr_arg(struct xdr_stream *xdr, struct cb_compound_hdr_arg *hdr) | ||
133 | { | ||
134 | uint32_t *p; | ||
135 | unsigned int minor_version; | ||
136 | unsigned status; | ||
137 | |||
138 | status = decode_string(xdr, &hdr->taglen, &hdr->tag); | ||
139 | if (unlikely(status != 0)) | ||
140 | return status; | ||
141 | /* We do not like overly long tags! */ | ||
142 | if (hdr->taglen > CB_OP_TAGLEN_MAXSZ-12 || hdr->taglen < 0) { | ||
143 | printk("NFSv4 CALLBACK %s: client sent tag of length %u\n", | ||
144 | __FUNCTION__, hdr->taglen); | ||
145 | return htonl(NFS4ERR_RESOURCE); | ||
146 | } | ||
147 | p = read_buf(xdr, 12); | ||
148 | if (unlikely(p == NULL)) | ||
149 | return htonl(NFS4ERR_RESOURCE); | ||
150 | minor_version = ntohl(*p++); | ||
151 | /* Check minor version is zero. */ | ||
152 | if (minor_version != 0) { | ||
153 | printk(KERN_WARNING "%s: NFSv4 server callback with illegal minor version %u!\n", | ||
154 | __FUNCTION__, minor_version); | ||
155 | return htonl(NFS4ERR_MINOR_VERS_MISMATCH); | ||
156 | } | ||
157 | hdr->callback_ident = ntohl(*p++); | ||
158 | hdr->nops = ntohl(*p); | ||
159 | return 0; | ||
160 | } | ||
161 | |||
162 | static unsigned decode_op_hdr(struct xdr_stream *xdr, unsigned int *op) | ||
163 | { | ||
164 | uint32_t *p; | ||
165 | p = read_buf(xdr, 4); | ||
166 | if (unlikely(p == NULL)) | ||
167 | return htonl(NFS4ERR_RESOURCE); | ||
168 | *op = ntohl(*p); | ||
169 | return 0; | ||
170 | } | ||
171 | |||
172 | static unsigned decode_getattr_args(struct svc_rqst *rqstp, struct xdr_stream *xdr, struct cb_getattrargs *args) | ||
173 | { | ||
174 | unsigned status; | ||
175 | |||
176 | status = decode_fh(xdr, &args->fh); | ||
177 | if (unlikely(status != 0)) | ||
178 | goto out; | ||
179 | args->addr = &rqstp->rq_addr; | ||
180 | status = decode_bitmap(xdr, args->bitmap); | ||
181 | out: | ||
182 | dprintk("%s: exit with status = %d\n", __FUNCTION__, status); | ||
183 | return status; | ||
184 | } | ||
185 | |||
186 | static unsigned decode_recall_args(struct svc_rqst *rqstp, struct xdr_stream *xdr, struct cb_recallargs *args) | ||
187 | { | ||
188 | uint32_t *p; | ||
189 | unsigned status; | ||
190 | |||
191 | args->addr = &rqstp->rq_addr; | ||
192 | status = decode_stateid(xdr, &args->stateid); | ||
193 | if (unlikely(status != 0)) | ||
194 | goto out; | ||
195 | p = read_buf(xdr, 4); | ||
196 | if (unlikely(p == NULL)) { | ||
197 | status = htonl(NFS4ERR_RESOURCE); | ||
198 | goto out; | ||
199 | } | ||
200 | args->truncate = ntohl(*p); | ||
201 | status = decode_fh(xdr, &args->fh); | ||
202 | out: | ||
203 | dprintk("%s: exit with status = %d\n", __FUNCTION__, status); | ||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | static unsigned encode_string(struct xdr_stream *xdr, unsigned int len, const char *str) | ||
208 | { | ||
209 | uint32_t *p; | ||
210 | |||
211 | p = xdr_reserve_space(xdr, 4 + len); | ||
212 | if (unlikely(p == NULL)) | ||
213 | return htonl(NFS4ERR_RESOURCE); | ||
214 | xdr_encode_opaque(p, str, len); | ||
215 | return 0; | ||
216 | } | ||
217 | |||
218 | #define CB_SUPPORTED_ATTR0 (FATTR4_WORD0_CHANGE|FATTR4_WORD0_SIZE) | ||
219 | #define CB_SUPPORTED_ATTR1 (FATTR4_WORD1_TIME_METADATA|FATTR4_WORD1_TIME_MODIFY) | ||
220 | static unsigned encode_attr_bitmap(struct xdr_stream *xdr, const uint32_t *bitmap, uint32_t **savep) | ||
221 | { | ||
222 | uint32_t bm[2]; | ||
223 | uint32_t *p; | ||
224 | |||
225 | bm[0] = htonl(bitmap[0] & CB_SUPPORTED_ATTR0); | ||
226 | bm[1] = htonl(bitmap[1] & CB_SUPPORTED_ATTR1); | ||
227 | if (bm[1] != 0) { | ||
228 | p = xdr_reserve_space(xdr, 16); | ||
229 | if (unlikely(p == NULL)) | ||
230 | return htonl(NFS4ERR_RESOURCE); | ||
231 | *p++ = htonl(2); | ||
232 | *p++ = bm[0]; | ||
233 | *p++ = bm[1]; | ||
234 | } else if (bm[0] != 0) { | ||
235 | p = xdr_reserve_space(xdr, 12); | ||
236 | if (unlikely(p == NULL)) | ||
237 | return htonl(NFS4ERR_RESOURCE); | ||
238 | *p++ = htonl(1); | ||
239 | *p++ = bm[0]; | ||
240 | } else { | ||
241 | p = xdr_reserve_space(xdr, 8); | ||
242 | if (unlikely(p == NULL)) | ||
243 | return htonl(NFS4ERR_RESOURCE); | ||
244 | *p++ = htonl(0); | ||
245 | } | ||
246 | *savep = p; | ||
247 | return 0; | ||
248 | } | ||
249 | |||
250 | static unsigned encode_attr_change(struct xdr_stream *xdr, const uint32_t *bitmap, uint64_t change) | ||
251 | { | ||
252 | uint32_t *p; | ||
253 | |||
254 | if (!(bitmap[0] & FATTR4_WORD0_CHANGE)) | ||
255 | return 0; | ||
256 | p = xdr_reserve_space(xdr, 8); | ||
257 | if (unlikely(p == 0)) | ||
258 | return htonl(NFS4ERR_RESOURCE); | ||
259 | p = xdr_encode_hyper(p, change); | ||
260 | return 0; | ||
261 | } | ||
262 | |||
263 | static unsigned encode_attr_size(struct xdr_stream *xdr, const uint32_t *bitmap, uint64_t size) | ||
264 | { | ||
265 | uint32_t *p; | ||
266 | |||
267 | if (!(bitmap[0] & FATTR4_WORD0_SIZE)) | ||
268 | return 0; | ||
269 | p = xdr_reserve_space(xdr, 8); | ||
270 | if (unlikely(p == 0)) | ||
271 | return htonl(NFS4ERR_RESOURCE); | ||
272 | p = xdr_encode_hyper(p, size); | ||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | static unsigned encode_attr_time(struct xdr_stream *xdr, const struct timespec *time) | ||
277 | { | ||
278 | uint32_t *p; | ||
279 | |||
280 | p = xdr_reserve_space(xdr, 12); | ||
281 | if (unlikely(p == 0)) | ||
282 | return htonl(NFS4ERR_RESOURCE); | ||
283 | p = xdr_encode_hyper(p, time->tv_sec); | ||
284 | *p = htonl(time->tv_nsec); | ||
285 | return 0; | ||
286 | } | ||
287 | |||
288 | static unsigned encode_attr_ctime(struct xdr_stream *xdr, const uint32_t *bitmap, const struct timespec *time) | ||
289 | { | ||
290 | if (!(bitmap[1] & FATTR4_WORD1_TIME_METADATA)) | ||
291 | return 0; | ||
292 | return encode_attr_time(xdr,time); | ||
293 | } | ||
294 | |||
295 | static unsigned encode_attr_mtime(struct xdr_stream *xdr, const uint32_t *bitmap, const struct timespec *time) | ||
296 | { | ||
297 | if (!(bitmap[1] & FATTR4_WORD1_TIME_MODIFY)) | ||
298 | return 0; | ||
299 | return encode_attr_time(xdr,time); | ||
300 | } | ||
301 | |||
302 | static unsigned encode_compound_hdr_res(struct xdr_stream *xdr, struct cb_compound_hdr_res *hdr) | ||
303 | { | ||
304 | unsigned status; | ||
305 | |||
306 | hdr->status = xdr_reserve_space(xdr, 4); | ||
307 | if (unlikely(hdr->status == NULL)) | ||
308 | return htonl(NFS4ERR_RESOURCE); | ||
309 | status = encode_string(xdr, hdr->taglen, hdr->tag); | ||
310 | if (unlikely(status != 0)) | ||
311 | return status; | ||
312 | hdr->nops = xdr_reserve_space(xdr, 4); | ||
313 | if (unlikely(hdr->nops == NULL)) | ||
314 | return htonl(NFS4ERR_RESOURCE); | ||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | static unsigned encode_op_hdr(struct xdr_stream *xdr, uint32_t op, uint32_t res) | ||
319 | { | ||
320 | uint32_t *p; | ||
321 | |||
322 | p = xdr_reserve_space(xdr, 8); | ||
323 | if (unlikely(p == NULL)) | ||
324 | return htonl(NFS4ERR_RESOURCE); | ||
325 | *p++ = htonl(op); | ||
326 | *p = res; | ||
327 | return 0; | ||
328 | } | ||
329 | |||
330 | static unsigned encode_getattr_res(struct svc_rqst *rqstp, struct xdr_stream *xdr, const struct cb_getattrres *res) | ||
331 | { | ||
332 | uint32_t *savep; | ||
333 | unsigned status = res->status; | ||
334 | |||
335 | if (unlikely(status != 0)) | ||
336 | goto out; | ||
337 | status = encode_attr_bitmap(xdr, res->bitmap, &savep); | ||
338 | if (unlikely(status != 0)) | ||
339 | goto out; | ||
340 | status = encode_attr_change(xdr, res->bitmap, res->change_attr); | ||
341 | if (unlikely(status != 0)) | ||
342 | goto out; | ||
343 | status = encode_attr_size(xdr, res->bitmap, res->size); | ||
344 | if (unlikely(status != 0)) | ||
345 | goto out; | ||
346 | status = encode_attr_ctime(xdr, res->bitmap, &res->ctime); | ||
347 | if (unlikely(status != 0)) | ||
348 | goto out; | ||
349 | status = encode_attr_mtime(xdr, res->bitmap, &res->mtime); | ||
350 | *savep = htonl((unsigned int)((char *)xdr->p - (char *)(savep+1))); | ||
351 | out: | ||
352 | dprintk("%s: exit with status = %d\n", __FUNCTION__, status); | ||
353 | return status; | ||
354 | } | ||
355 | |||
356 | static unsigned process_op(struct svc_rqst *rqstp, | ||
357 | struct xdr_stream *xdr_in, void *argp, | ||
358 | struct xdr_stream *xdr_out, void *resp) | ||
359 | { | ||
360 | struct callback_op *op; | ||
361 | unsigned int op_nr; | ||
362 | unsigned int status = 0; | ||
363 | long maxlen; | ||
364 | unsigned res; | ||
365 | |||
366 | dprintk("%s: start\n", __FUNCTION__); | ||
367 | status = decode_op_hdr(xdr_in, &op_nr); | ||
368 | if (unlikely(status != 0)) { | ||
369 | op_nr = OP_CB_ILLEGAL; | ||
370 | op = &callback_ops[0]; | ||
371 | } else if (unlikely(op_nr != OP_CB_GETATTR && op_nr != OP_CB_RECALL)) { | ||
372 | op_nr = OP_CB_ILLEGAL; | ||
373 | op = &callback_ops[0]; | ||
374 | status = htonl(NFS4ERR_OP_ILLEGAL); | ||
375 | } else | ||
376 | op = &callback_ops[op_nr]; | ||
377 | |||
378 | maxlen = xdr_out->end - xdr_out->p; | ||
379 | if (maxlen > 0 && maxlen < PAGE_SIZE) { | ||
380 | if (likely(status == 0 && op->decode_args != NULL)) | ||
381 | status = op->decode_args(rqstp, xdr_in, argp); | ||
382 | if (likely(status == 0 && op->process_op != NULL)) | ||
383 | status = op->process_op(argp, resp); | ||
384 | } else | ||
385 | status = htonl(NFS4ERR_RESOURCE); | ||
386 | |||
387 | res = encode_op_hdr(xdr_out, op_nr, status); | ||
388 | if (status == 0) | ||
389 | status = res; | ||
390 | if (op->encode_res != NULL && status == 0) | ||
391 | status = op->encode_res(rqstp, xdr_out, resp); | ||
392 | dprintk("%s: done, status = %d\n", __FUNCTION__, status); | ||
393 | return status; | ||
394 | } | ||
395 | |||
396 | /* | ||
397 | * Decode, process and encode a COMPOUND | ||
398 | */ | ||
399 | static int nfs4_callback_compound(struct svc_rqst *rqstp, void *argp, void *resp) | ||
400 | { | ||
401 | struct cb_compound_hdr_arg hdr_arg; | ||
402 | struct cb_compound_hdr_res hdr_res; | ||
403 | struct xdr_stream xdr_in, xdr_out; | ||
404 | uint32_t *p; | ||
405 | unsigned int status; | ||
406 | unsigned int nops = 1; | ||
407 | |||
408 | dprintk("%s: start\n", __FUNCTION__); | ||
409 | |||
410 | xdr_init_decode(&xdr_in, &rqstp->rq_arg, rqstp->rq_arg.head[0].iov_base); | ||
411 | |||
412 | p = (uint32_t*)((char *)rqstp->rq_res.head[0].iov_base + rqstp->rq_res.head[0].iov_len); | ||
413 | rqstp->rq_res.head[0].iov_len = PAGE_SIZE; | ||
414 | xdr_init_encode(&xdr_out, &rqstp->rq_res, p); | ||
415 | |||
416 | decode_compound_hdr_arg(&xdr_in, &hdr_arg); | ||
417 | hdr_res.taglen = hdr_arg.taglen; | ||
418 | hdr_res.tag = hdr_arg.tag; | ||
419 | encode_compound_hdr_res(&xdr_out, &hdr_res); | ||
420 | |||
421 | for (;;) { | ||
422 | status = process_op(rqstp, &xdr_in, argp, &xdr_out, resp); | ||
423 | if (status != 0) | ||
424 | break; | ||
425 | if (nops == hdr_arg.nops) | ||
426 | break; | ||
427 | nops++; | ||
428 | } | ||
429 | *hdr_res.status = status; | ||
430 | *hdr_res.nops = htonl(nops); | ||
431 | dprintk("%s: done, status = %u\n", __FUNCTION__, status); | ||
432 | return rpc_success; | ||
433 | } | ||
434 | |||
435 | /* | ||
436 | * Define NFS4 callback COMPOUND ops. | ||
437 | */ | ||
438 | static struct callback_op callback_ops[] = { | ||
439 | [0] = { | ||
440 | .res_maxsize = CB_OP_HDR_RES_MAXSZ, | ||
441 | }, | ||
442 | [OP_CB_GETATTR] = { | ||
443 | .process_op = (callback_process_op_t)nfs4_callback_getattr, | ||
444 | .decode_args = (callback_decode_arg_t)decode_getattr_args, | ||
445 | .encode_res = (callback_encode_res_t)encode_getattr_res, | ||
446 | .res_maxsize = CB_OP_GETATTR_RES_MAXSZ, | ||
447 | }, | ||
448 | [OP_CB_RECALL] = { | ||
449 | .process_op = (callback_process_op_t)nfs4_callback_recall, | ||
450 | .decode_args = (callback_decode_arg_t)decode_recall_args, | ||
451 | .res_maxsize = CB_OP_RECALL_RES_MAXSZ, | ||
452 | } | ||
453 | }; | ||
454 | |||
455 | /* | ||
456 | * Define NFS4 callback procedures | ||
457 | */ | ||
458 | static struct svc_procedure nfs4_callback_procedures1[] = { | ||
459 | [CB_NULL] = { | ||
460 | .pc_func = nfs4_callback_null, | ||
461 | .pc_decode = (kxdrproc_t)nfs4_decode_void, | ||
462 | .pc_encode = (kxdrproc_t)nfs4_encode_void, | ||
463 | .pc_xdrressize = 1, | ||
464 | }, | ||
465 | [CB_COMPOUND] = { | ||
466 | .pc_func = nfs4_callback_compound, | ||
467 | .pc_encode = (kxdrproc_t)nfs4_encode_void, | ||
468 | .pc_argsize = 256, | ||
469 | .pc_ressize = 256, | ||
470 | .pc_xdrressize = NFS4_CALLBACK_BUFSIZE, | ||
471 | } | ||
472 | }; | ||
473 | |||
474 | struct svc_version nfs4_callback_version1 = { | ||
475 | .vs_vers = 1, | ||
476 | .vs_nproc = ARRAY_SIZE(nfs4_callback_procedures1), | ||
477 | .vs_proc = nfs4_callback_procedures1, | ||
478 | .vs_xdrsize = NFS4_CALLBACK_XDRSIZE, | ||
479 | .vs_dispatch = NULL, | ||
480 | }; | ||
481 | |||