]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * linux/fs/nfs/callback_xdr.c | |
3 | * | |
4 | * Copyright (C) 2004 Trond Myklebust | |
5 | * | |
6 | * NFSv4 callback encode/decode procedures | |
7 | */ | |
1da177e4 LT |
8 | #include <linux/kernel.h> |
9 | #include <linux/sunrpc/svc.h> | |
10 | #include <linux/nfs4.h> | |
11 | #include <linux/nfs_fs.h> | |
4ce79717 | 12 | #include "nfs4_fs.h" |
1da177e4 LT |
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 | ||
e6f684f6 AV |
25 | typedef __be32 (*callback_process_op_t)(void *, void *); |
26 | typedef __be32 (*callback_decode_arg_t)(struct svc_rqst *, struct xdr_stream *, void *); | |
27 | typedef __be32 (*callback_encode_res_t)(struct svc_rqst *, struct xdr_stream *, void *); | |
1da177e4 LT |
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 | ||
7111c66e | 39 | static __be32 nfs4_callback_null(struct svc_rqst *rqstp, void *argp, void *resp) |
1da177e4 LT |
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 | ||
e6f684f6 | 64 | static __be32 decode_string(struct xdr_stream *xdr, unsigned int *len, const char **str) |
1da177e4 LT |
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 | ||
e6f684f6 | 84 | static __be32 decode_fh(struct xdr_stream *xdr, struct nfs_fh *fh) |
1da177e4 LT |
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 | ||
e6f684f6 | 102 | static __be32 decode_bitmap(struct xdr_stream *xdr, uint32_t *bitmap) |
1da177e4 LT |
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 | ||
e6f684f6 | 121 | static __be32 decode_stateid(struct xdr_stream *xdr, nfs4_stateid *stateid) |
1da177e4 LT |
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 | ||
e6f684f6 | 132 | static __be32 decode_compound_hdr_arg(struct xdr_stream *xdr, struct cb_compound_hdr_arg *hdr) |
1da177e4 LT |
133 | { |
134 | uint32_t *p; | |
135 | unsigned int minor_version; | |
e6f684f6 | 136 | __be32 status; |
1da177e4 LT |
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 | ||
e6f684f6 | 162 | static __be32 decode_op_hdr(struct xdr_stream *xdr, unsigned int *op) |
1da177e4 LT |
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 | ||
e6f684f6 | 172 | static __be32 decode_getattr_args(struct svc_rqst *rqstp, struct xdr_stream *xdr, struct cb_getattrargs *args) |
1da177e4 | 173 | { |
e6f684f6 | 174 | __be32 status; |
1da177e4 LT |
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 | ||
e6f684f6 | 186 | static __be32 decode_recall_args(struct svc_rqst *rqstp, struct xdr_stream *xdr, struct cb_recallargs *args) |
1da177e4 LT |
187 | { |
188 | uint32_t *p; | |
e6f684f6 | 189 | __be32 status; |
1da177e4 LT |
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); | |
3873bc50 | 204 | return status; |
1da177e4 LT |
205 | } |
206 | ||
e6f684f6 | 207 | static __be32 encode_string(struct xdr_stream *xdr, unsigned int len, const char *str) |
1da177e4 LT |
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) | |
e6f684f6 | 220 | static __be32 encode_attr_bitmap(struct xdr_stream *xdr, const uint32_t *bitmap, uint32_t **savep) |
1da177e4 LT |
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 | ||
e6f684f6 | 250 | static __be32 encode_attr_change(struct xdr_stream *xdr, const uint32_t *bitmap, uint64_t change) |
1da177e4 LT |
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 | ||
e6f684f6 | 263 | static __be32 encode_attr_size(struct xdr_stream *xdr, const uint32_t *bitmap, uint64_t size) |
1da177e4 LT |
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 | ||
e6f684f6 | 276 | static __be32 encode_attr_time(struct xdr_stream *xdr, const struct timespec *time) |
1da177e4 LT |
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 | ||
e6f684f6 | 288 | static __be32 encode_attr_ctime(struct xdr_stream *xdr, const uint32_t *bitmap, const struct timespec *time) |
1da177e4 LT |
289 | { |
290 | if (!(bitmap[1] & FATTR4_WORD1_TIME_METADATA)) | |
291 | return 0; | |
292 | return encode_attr_time(xdr,time); | |
293 | } | |
294 | ||
e6f684f6 | 295 | static __be32 encode_attr_mtime(struct xdr_stream *xdr, const uint32_t *bitmap, const struct timespec *time) |
1da177e4 LT |
296 | { |
297 | if (!(bitmap[1] & FATTR4_WORD1_TIME_MODIFY)) | |
298 | return 0; | |
299 | return encode_attr_time(xdr,time); | |
300 | } | |
301 | ||
e6f684f6 | 302 | static __be32 encode_compound_hdr_res(struct xdr_stream *xdr, struct cb_compound_hdr_res *hdr) |
1da177e4 | 303 | { |
e6f684f6 | 304 | __be32 status; |
1da177e4 LT |
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 | ||
e6f684f6 | 318 | static __be32 encode_op_hdr(struct xdr_stream *xdr, uint32_t op, __be32 res) |
1da177e4 LT |
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 | ||
e6f684f6 | 330 | static __be32 encode_getattr_res(struct svc_rqst *rqstp, struct xdr_stream *xdr, const struct cb_getattrres *res) |
1da177e4 | 331 | { |
a162a6b8 | 332 | uint32_t *savep = NULL; |
e6f684f6 | 333 | __be32 status = res->status; |
1da177e4 LT |
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 | ||
e6f684f6 | 356 | static __be32 process_op(struct svc_rqst *rqstp, |
1da177e4 LT |
357 | struct xdr_stream *xdr_in, void *argp, |
358 | struct xdr_stream *xdr_out, void *resp) | |
359 | { | |
a162a6b8 TM |
360 | struct callback_op *op = &callback_ops[0]; |
361 | unsigned int op_nr = OP_CB_ILLEGAL; | |
e6f684f6 | 362 | __be32 status = 0; |
1da177e4 | 363 | long maxlen; |
e6f684f6 | 364 | __be32 res; |
1da177e4 LT |
365 | |
366 | dprintk("%s: start\n", __FUNCTION__); | |
367 | status = decode_op_hdr(xdr_in, &op_nr); | |
a162a6b8 TM |
368 | if (likely(status == 0)) { |
369 | switch (op_nr) { | |
370 | case OP_CB_GETATTR: | |
371 | case OP_CB_RECALL: | |
372 | op = &callback_ops[op_nr]; | |
373 | break; | |
374 | default: | |
375 | op_nr = OP_CB_ILLEGAL; | |
376 | op = &callback_ops[0]; | |
377 | status = htonl(NFS4ERR_OP_ILLEGAL); | |
378 | } | |
379 | } | |
1da177e4 LT |
380 | |
381 | maxlen = xdr_out->end - xdr_out->p; | |
382 | if (maxlen > 0 && maxlen < PAGE_SIZE) { | |
383 | if (likely(status == 0 && op->decode_args != NULL)) | |
384 | status = op->decode_args(rqstp, xdr_in, argp); | |
385 | if (likely(status == 0 && op->process_op != NULL)) | |
386 | status = op->process_op(argp, resp); | |
387 | } else | |
388 | status = htonl(NFS4ERR_RESOURCE); | |
389 | ||
390 | res = encode_op_hdr(xdr_out, op_nr, status); | |
391 | if (status == 0) | |
392 | status = res; | |
393 | if (op->encode_res != NULL && status == 0) | |
394 | status = op->encode_res(rqstp, xdr_out, resp); | |
395 | dprintk("%s: done, status = %d\n", __FUNCTION__, status); | |
396 | return status; | |
397 | } | |
398 | ||
399 | /* | |
400 | * Decode, process and encode a COMPOUND | |
401 | */ | |
7111c66e | 402 | static __be32 nfs4_callback_compound(struct svc_rqst *rqstp, void *argp, void *resp) |
1da177e4 LT |
403 | { |
404 | struct cb_compound_hdr_arg hdr_arg; | |
405 | struct cb_compound_hdr_res hdr_res; | |
406 | struct xdr_stream xdr_in, xdr_out; | |
407 | uint32_t *p; | |
e6f684f6 | 408 | __be32 status; |
1da177e4 LT |
409 | unsigned int nops = 1; |
410 | ||
411 | dprintk("%s: start\n", __FUNCTION__); | |
412 | ||
413 | xdr_init_decode(&xdr_in, &rqstp->rq_arg, rqstp->rq_arg.head[0].iov_base); | |
414 | ||
415 | p = (uint32_t*)((char *)rqstp->rq_res.head[0].iov_base + rqstp->rq_res.head[0].iov_len); | |
1da177e4 LT |
416 | xdr_init_encode(&xdr_out, &rqstp->rq_res, p); |
417 | ||
418 | decode_compound_hdr_arg(&xdr_in, &hdr_arg); | |
419 | hdr_res.taglen = hdr_arg.taglen; | |
420 | hdr_res.tag = hdr_arg.tag; | |
a162a6b8 | 421 | hdr_res.nops = NULL; |
1da177e4 LT |
422 | encode_compound_hdr_res(&xdr_out, &hdr_res); |
423 | ||
424 | for (;;) { | |
425 | status = process_op(rqstp, &xdr_in, argp, &xdr_out, resp); | |
426 | if (status != 0) | |
427 | break; | |
428 | if (nops == hdr_arg.nops) | |
429 | break; | |
430 | nops++; | |
431 | } | |
432 | *hdr_res.status = status; | |
433 | *hdr_res.nops = htonl(nops); | |
434 | dprintk("%s: done, status = %u\n", __FUNCTION__, status); | |
435 | return rpc_success; | |
436 | } | |
437 | ||
438 | /* | |
439 | * Define NFS4 callback COMPOUND ops. | |
440 | */ | |
441 | static struct callback_op callback_ops[] = { | |
442 | [0] = { | |
443 | .res_maxsize = CB_OP_HDR_RES_MAXSZ, | |
444 | }, | |
445 | [OP_CB_GETATTR] = { | |
446 | .process_op = (callback_process_op_t)nfs4_callback_getattr, | |
447 | .decode_args = (callback_decode_arg_t)decode_getattr_args, | |
448 | .encode_res = (callback_encode_res_t)encode_getattr_res, | |
449 | .res_maxsize = CB_OP_GETATTR_RES_MAXSZ, | |
450 | }, | |
451 | [OP_CB_RECALL] = { | |
452 | .process_op = (callback_process_op_t)nfs4_callback_recall, | |
453 | .decode_args = (callback_decode_arg_t)decode_recall_args, | |
454 | .res_maxsize = CB_OP_RECALL_RES_MAXSZ, | |
455 | } | |
456 | }; | |
457 | ||
458 | /* | |
459 | * Define NFS4 callback procedures | |
460 | */ | |
461 | static struct svc_procedure nfs4_callback_procedures1[] = { | |
462 | [CB_NULL] = { | |
463 | .pc_func = nfs4_callback_null, | |
464 | .pc_decode = (kxdrproc_t)nfs4_decode_void, | |
465 | .pc_encode = (kxdrproc_t)nfs4_encode_void, | |
466 | .pc_xdrressize = 1, | |
467 | }, | |
468 | [CB_COMPOUND] = { | |
469 | .pc_func = nfs4_callback_compound, | |
470 | .pc_encode = (kxdrproc_t)nfs4_encode_void, | |
471 | .pc_argsize = 256, | |
472 | .pc_ressize = 256, | |
473 | .pc_xdrressize = NFS4_CALLBACK_BUFSIZE, | |
474 | } | |
475 | }; | |
476 | ||
477 | struct svc_version nfs4_callback_version1 = { | |
478 | .vs_vers = 1, | |
479 | .vs_nproc = ARRAY_SIZE(nfs4_callback_procedures1), | |
480 | .vs_proc = nfs4_callback_procedures1, | |
481 | .vs_xdrsize = NFS4_CALLBACK_XDRSIZE, | |
482 | .vs_dispatch = NULL, | |
483 | }; | |
484 |