1 /* NFSv4.1 client for Windows
2 * Copyright © 2012 The Regents of the University of Michigan
4 * Olga Kornievskaia <aglo@umich.edu>
5 * Casey Bodley <cbodley@umich.edu>
7 * This library is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or (at
10 * your option) any later version.
12 * This library is distributed in the hope that it will be useful, but
13 * without any warranty; without even the implied warranty of merchantability
14 * or fitness for a particular purpose. See the GNU Lesser General Public
15 * License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25 #include "nfs41_ops.h"
26 #include "delegation.h"
27 #include "nfs41_callback.h"
28 #include "daemon_debug.h"
31 #define CBSLVL 2 /* dprintf level for callback server logging */
34 static const char g_server_tag
[] = "ms-nfs41-callback";
37 /* callback session */
38 static void replay_cache_write(
39 IN nfs41_cb_session
*session
,
40 IN
struct cb_compound_args
*args
,
41 IN
struct cb_compound_res
*res
,
44 void nfs41_callback_session_init(
45 IN nfs41_session
*session
)
47 /* initialize the replay cache with status NFS4ERR_SEQ_MISORDERED */
48 struct cb_compound_res res
= { 0 };
49 StringCchCopyA(res
.tag
.str
, CB_COMPOUND_MAX_TAG
, g_server_tag
);
50 res
.tag
.len
= sizeof(g_server_tag
);
51 res
.status
= NFS4ERR_SEQ_MISORDERED
;
53 session
->cb_session
.cb_sessionid
= session
->session_id
;
55 replay_cache_write(&session
->cb_session
, NULL
, &res
, FALSE
);
59 /* OP_CB_LAYOUTRECALL */
60 static enum_t
handle_cb_layoutrecall(
61 IN nfs41_rpc_clnt
*rpc_clnt
,
62 IN
struct cb_layoutrecall_args
*args
,
63 OUT
struct cb_layoutrecall_res
*res
)
65 enum pnfs_status status
;
67 status
= pnfs_file_layout_recall(rpc_clnt
->client
, args
);
70 /* not enough information to process the recall yet */
71 res
->status
= NFS4ERR_DELAY
;
74 /* forgetful model for layout recalls */
75 res
->status
= NFS4ERR_NOMATCHING_LAYOUT
;
79 dprintf(CBSLVL
, " OP_CB_LAYOUTRECALL { %s, %s, recall %u } %s\n",
80 pnfs_layout_type_string(args
->type
),
81 pnfs_iomode_string(args
->iomode
), args
->recall
.type
,
82 nfs_error_string(res
->status
));
86 /* OP_CB_RECALL_SLOT */
87 static enum_t
handle_cb_recall_slot(
88 IN nfs41_rpc_clnt
*rpc_clnt
,
89 IN
struct cb_recall_slot_args
*args
,
90 OUT
struct cb_recall_slot_res
*res
)
92 res
->status
= nfs41_session_recall_slot(rpc_clnt
->client
->session
,
93 args
->target_highest_slotid
);
95 dprintf(CBSLVL
, " OP_CB_RECALL_SLOT { %u } %s\n",
96 args
->target_highest_slotid
, nfs_error_string(res
->status
));
101 static enum_t
handle_cb_sequence(
102 IN nfs41_rpc_clnt
*rpc_clnt
,
103 IN
struct cb_sequence_args
*args
,
104 OUT
struct cb_sequence_res
*res
,
105 OUT nfs41_cb_session
**session_out
,
106 OUT bool_t
*cachethis
)
108 nfs41_cb_session
*cb_session
= &rpc_clnt
->client
->session
->cb_session
;
109 uint32_t status
= NFS4_OK
;
110 res
->status
= NFS4_OK
;
112 *session_out
= cb_session
;
114 /* validate the sessionid */
115 if (memcmp(cb_session
->cb_sessionid
, args
->sessionid
,
116 NFS4_SESSIONID_SIZE
)) {
117 eprintf("[cb] received sessionid doesn't match session\n");
118 res
->status
= NFS4ERR_BADSESSION
;
122 /* we only support 1 slot for the back channel so slotid MUST be 0 */
123 if (args
->slotid
!= 0) {
124 eprintf("[cb] received unexpected slotid=%d\n", args
->slotid
);
125 res
->status
= NFS4ERR_BADSLOT
;
128 if (args
->highest_slotid
!= 0) {
129 eprintf("[cb] received unexpected highest_slotid=%d\n",
130 args
->highest_slotid
);
131 res
->status
= NFS4ERR_BAD_HIGH_SLOT
;
135 /* check for a retry with the same seqid */
136 if (args
->sequenceid
== cb_session
->cb_seqnum
) {
137 if (!cb_session
->replay
.res
.length
) {
138 /* return success for sequence, but fail the next operation */
139 res
->status
= NFS4_OK
;
140 status
= NFS4ERR_RETRY_UNCACHED_REP
;
142 /* return NFS4ERR_SEQ_FALSE_RETRY for all replays; if the retry
143 * turns out to be valid, this response will be replaced anyway */
144 status
= res
->status
= NFS4ERR_SEQ_FALSE_RETRY
;
149 /* error on any unexpected seqids */
150 if (args
->sequenceid
!= cb_session
->cb_seqnum
+1) {
151 eprintf("[cb] bad received seq#=%d, expected=%d\n",
152 args
->sequenceid
, cb_session
->cb_seqnum
+1);
153 res
->status
= NFS4ERR_SEQ_MISORDERED
;
157 cb_session
->cb_seqnum
= args
->sequenceid
;
158 *cachethis
= args
->cachethis
;
160 memcpy(res
->ok
.sessionid
, args
->sessionid
, NFS4_SESSIONID_SIZE
);
161 res
->ok
.sequenceid
= args
->sequenceid
;
162 res
->ok
.slotid
= args
->slotid
;
163 res
->ok
.highest_slotid
= args
->highest_slotid
;
164 res
->ok
.target_highest_slotid
= args
->highest_slotid
;
167 dprintf(CBSLVL
, " OP_CB_SEQUENCE { seqid %u, slot %u, cachethis %d } "
168 "%s\n", args
->sequenceid
, args
->slotid
, args
->cachethis
,
169 nfs_error_string(res
->status
));
174 static enum_t
handle_cb_getattr(
175 IN nfs41_rpc_clnt
*rpc_clnt
,
176 IN
struct cb_getattr_args
*args
,
177 OUT
struct cb_getattr_res
*res
)
179 /* look up cached attributes for the given filehandle */
180 res
->status
= nfs41_delegation_getattr(rpc_clnt
->client
,
181 &args
->fh
, &args
->attr_request
, &res
->info
);
186 static enum_t
handle_cb_recall(
187 IN nfs41_rpc_clnt
*rpc_clnt
,
188 IN
struct cb_recall_args
*args
,
189 OUT
struct cb_recall_res
*res
)
191 /* return the delegation asynchronously */
192 res
->status
= nfs41_delegation_recall(rpc_clnt
->client
,
193 &args
->fh
, &args
->stateid
, args
->truncate
);
197 /* OP_CB_NOTIFY_DEVICEID */
198 static enum_t
handle_cb_notify_deviceid(
199 IN nfs41_rpc_clnt
*rpc_clnt
,
200 IN
struct cb_notify_deviceid_args
*args
,
201 OUT
struct cb_notify_deviceid_res
*res
)
204 for (i
= 0; i
< args
->change_count
; i
++) {
205 pnfs_file_device_notify(rpc_clnt
->client
->devices
,
206 &args
->change_list
[i
]);
208 res
->status
= NFS4_OK
;
212 static void replay_cache_write(
213 IN nfs41_cb_session
*session
,
214 IN OPTIONAL
struct cb_compound_args
*args
,
215 IN
struct cb_compound_res
*res
,
221 session
->replay
.arg
.length
= 0;
222 session
->replay
.res
.length
= 0;
224 /* encode the reply directly into the replay cache */
225 xdrmem_create(&xdr
, (char*)session
->replay
.res
.buffer
,
226 NFS41_MAX_SERVER_CACHE
, XDR_ENCODE
);
228 /* always try to cache the result */
229 if (proc_cb_compound_res(&xdr
, res
)) {
230 session
->replay
.res
.length
= XDR_GETPOS(&xdr
);
233 /* encode the arguments into the request cache */
234 xdrmem_create(&xdr
, (char*)session
->replay
.arg
.buffer
,
235 NFS41_MAX_SERVER_CACHE
, XDR_ENCODE
);
237 if (proc_cb_compound_args(&xdr
, args
))
238 session
->replay
.arg
.length
= XDR_GETPOS(&xdr
);
240 } else if (cachethis
) {
241 /* on failure, only return errors if caching was requested */
242 res
->status
= NFS4ERR_REP_TOO_BIG_TO_CACHE
;
244 /* find the first operation that failed to encode */
245 for (i
= 0; i
< res
->resarray_count
; i
++) {
246 if (!res
->resarray
[i
].xdr_ok
) {
247 res
->resarray
[i
].res
.status
= NFS4ERR_REP_TOO_BIG_TO_CACHE
;
248 res
->resarray_count
= i
+ 1;
255 static bool_t
replay_validate_args(
256 IN
struct cb_compound_args
*args
,
257 IN
const struct replay_cache
*cache
)
259 char buffer
[NFS41_MAX_SERVER_CACHE
];
262 /* encode the current arguments into a temporary buffer */
263 xdrmem_create(&xdr
, buffer
, NFS41_MAX_SERVER_CACHE
, XDR_ENCODE
);
265 if (!proc_cb_compound_args(&xdr
, args
))
268 /* must match the cached length */
269 if (XDR_GETPOS(&xdr
) != cache
->length
)
272 /* must match the cached buffer contents */
273 return memcmp(cache
->buffer
, buffer
, cache
->length
) == 0;
276 static bool_t
replay_validate_ops(
277 IN
const struct cb_compound_args
*args
,
278 IN
const struct cb_compound_res
*res
)
281 for (i
= 0; i
< res
->resarray_count
; i
++) {
282 /* can't have more operations than the request */
283 if (i
>= args
->argarray_count
)
286 /* each opnum must match the request */
287 if (args
->argarray
[i
].opnum
!= res
->resarray
[i
].opnum
)
290 if (res
->resarray
[i
].res
.status
)
296 static int replay_cache_read(
297 IN nfs41_cb_session
*session
,
298 IN
struct cb_compound_args
*args
,
299 OUT
struct cb_compound_res
**res_out
)
302 struct cb_compound_res
*replay
;
303 struct cb_compound_res
*res
= *res_out
;
304 uint32_t status
= NFS4_OK
;
306 replay
= calloc(1, sizeof(struct cb_compound_res
));
307 if (replay
== NULL
) {
308 eprintf("[cb] failed to allocate replay buffer\n");
309 status
= NFS4ERR_SERVERFAULT
;
313 /* decode the response from the replay cache */
314 xdrmem_create(&xdr
, (char*)session
->replay
.res
.buffer
,
315 NFS41_MAX_SERVER_CACHE
, XDR_DECODE
);
316 if (!proc_cb_compound_res(&xdr
, replay
)) {
317 eprintf("[cb] failed to decode replay buffer\n");
318 status
= NFS4ERR_SEQ_FALSE_RETRY
;
319 goto out_free_replay
;
322 /* if we cached the arguments, use them to validate the retry */
323 if (session
->replay
.arg
.length
) {
324 if (!replay_validate_args(args
, &session
->replay
.arg
)) {
325 eprintf("[cb] retry attempt with different arguments\n");
326 status
= NFS4ERR_SEQ_FALSE_RETRY
;
327 goto out_free_replay
;
329 } else { /* otherwise, comparing opnums is the best we can do */
330 if (!replay_validate_ops(args
, replay
)) {
331 eprintf("[cb] retry attempt with different operations\n");
332 status
= NFS4ERR_SEQ_FALSE_RETRY
;
333 goto out_free_replay
;
337 /* free previous response and replace it with the replay */
339 proc_cb_compound_res(&xdr
, res
);
341 dprintf(2, "[cb] retry: returning cached response\n");
349 proc_cb_compound_res(&xdr
, replay
);
354 static void handle_cb_compound(nfs41_rpc_clnt
*rpc_clnt
, cb_req
*req
, struct cb_compound_res
**reply
)
356 struct cb_compound_args args
= { 0 };
357 struct cb_compound_res
*res
= NULL
;
358 struct cb_argop
*argop
;
359 struct cb_resop
*resop
;
360 XDR
*xdr
= (XDR
*)req
->xdr
;
361 nfs41_cb_session
*session
= NULL
;
362 bool_t cachethis
= FALSE
;
363 uint32_t i
, status
= NFS4_OK
;
365 dprintf(CBSLVL
, "--> handle_cb_compound()\n");
367 /* decode the arguments */
368 if (!proc_cb_compound_args(xdr
, &args
)) {
369 status
= NFS4ERR_BADXDR
;
370 eprintf("failed to decode compound arguments\n");
373 /* allocate the compound results */
374 res
= calloc(1, sizeof(struct cb_compound_res
));
376 status
= NFS4ERR_SERVERFAULT
;
379 res
->status
= status
;
380 StringCchCopyA(res
->tag
.str
, CB_COMPOUND_MAX_TAG
, g_server_tag
);
381 res
->tag
.str
[CB_COMPOUND_MAX_TAG
-1] = 0;
382 res
->tag
.len
= (uint32_t)strlen(res
->tag
.str
);
383 res
->resarray
= calloc(args
.argarray_count
, sizeof(struct cb_resop
));
384 if (res
->resarray
== NULL
) {
385 res
->status
= NFS4ERR_SERVERFAULT
;
389 dprintf(CBSLVL
, "CB_COMPOUND('%s', %u)\n", args
.tag
.str
, args
.argarray_count
);
390 if (args
.minorversion
!= 1) {
391 res
->status
= NFS4ERR_MINOR_VERS_MISMATCH
; //XXXXX
392 eprintf("args.minorversion %u != 1\n", args
.minorversion
);
396 /* handle each operation in the compound */
397 for (i
= 0; i
< args
.argarray_count
&& res
->status
== NFS4_OK
; i
++) {
398 argop
= &args
.argarray
[i
];
399 resop
= &res
->resarray
[i
];
400 resop
->opnum
= argop
->opnum
;
401 res
->resarray_count
++;
403 /* 20.9.3: The error NFS4ERR_SEQUENCE_POS MUST be returned
404 * when CB_SEQUENCE is found in any position in a CB_COMPOUND
405 * beyond the first. If any other operation is in the first
406 * position of CB_COMPOUND, NFS4ERR_OP_NOT_IN_SESSION MUST
409 if (i
== 0 && argop
->opnum
!= OP_CB_SEQUENCE
) {
410 res
->status
= resop
->res
.status
= NFS4ERR_OP_NOT_IN_SESSION
;
413 if (i
!= 0 && argop
->opnum
== OP_CB_SEQUENCE
) {
414 res
->status
= resop
->res
.status
= NFS4ERR_SEQUENCE_POS
;
417 if (status
== NFS4ERR_RETRY_UNCACHED_REP
) {
418 res
->status
= resop
->res
.status
= status
;
422 switch (argop
->opnum
) {
423 case OP_CB_LAYOUTRECALL
:
424 dprintf(1, "OP_CB_LAYOUTRECALL\n");
425 res
->status
= handle_cb_layoutrecall(rpc_clnt
,
426 &argop
->args
.layoutrecall
, &resop
->res
.layoutrecall
);
428 case OP_CB_RECALL_SLOT
:
429 dprintf(1, "OP_CB_RECALL_SLOT\n");
430 res
->status
= handle_cb_recall_slot(rpc_clnt
,
431 &argop
->args
.recall_slot
, &resop
->res
.recall_slot
);
434 dprintf(1, "OP_CB_SEQUENCE\n");
435 status
= handle_cb_sequence(rpc_clnt
, &argop
->args
.sequence
,
436 &resop
->res
.sequence
, &session
, &cachethis
);
438 if (status
== NFS4ERR_SEQ_FALSE_RETRY
) {
439 /* replace the current results with the cached response */
440 status
= replay_cache_read(session
, &args
, &res
);
441 if (status
) res
->status
= status
;
445 if (status
== NFS4_OK
)
446 res
->status
= resop
->res
.sequence
.status
;
449 dprintf(1, "OP_CB_GETATTR\n");
450 res
->status
= handle_cb_getattr(rpc_clnt
,
451 &argop
->args
.getattr
, &resop
->res
.getattr
);
454 dprintf(1, "OP_CB_RECALL\n");
455 res
->status
= handle_cb_recall(rpc_clnt
,
456 &argop
->args
.recall
, &resop
->res
.recall
);
459 dprintf(1, "OP_CB_NOTIFY\n");
460 res
->status
= NFS4ERR_NOTSUPP
;
462 case OP_CB_PUSH_DELEG
:
463 dprintf(1, "OP_CB_PUSH_DELEG\n");
464 res
->status
= NFS4ERR_NOTSUPP
;
466 case OP_CB_RECALL_ANY
:
467 dprintf(1, "OP_CB_RECALL_ANY\n");
468 res
->status
= NFS4ERR_NOTSUPP
;
470 case OP_CB_RECALLABLE_OBJ_AVAIL
:
471 dprintf(1, "OP_CB_RECALLABLE_OBJ_AVAIL\n");
472 res
->status
= NFS4ERR_NOTSUPP
;
474 case OP_CB_WANTS_CANCELLED
:
475 dprintf(1, "OP_CB_WANTS_CANCELLED\n");
476 res
->status
= NFS4ERR_NOTSUPP
;
478 case OP_CB_NOTIFY_LOCK
:
479 dprintf(1, "OP_CB_NOTIFY_LOCK\n");
480 res
->status
= NFS4ERR_NOTSUPP
;
482 case OP_CB_NOTIFY_DEVICEID
:
483 dprintf(1, "OP_CB_NOTIFY_DEVICEID\n");
484 res
->status
= NFS4_OK
;
487 dprintf(1, "OP_CB_ILLEGAL\n");
488 res
->status
= NFS4ERR_NOTSUPP
;
491 eprintf("operation %u not supported\n", argop
->opnum
);
492 res
->status
= NFS4ERR_NOTSUPP
;
497 /* always attempt to cache the reply */
499 replay_cache_write(session
, &args
, res
, cachethis
);
501 /* free the arguments */
502 xdr
->x_op
= XDR_FREE
;
503 proc_cb_compound_args(xdr
, &args
);
506 dprintf(CBSLVL
, "<-- handle_cb_compound() returning %s (%u results)\n",
507 nfs_error_string(res
? res
->status
: status
),
508 res
? res
->resarray_count
: 0);
512 int nfs41_handle_callback(void *rpc_clnt
, void *cb
, void * dummy
)
514 struct cb_compound_res
**reply
= dummy
;
516 int nfs41_handle_callback(void *rpc_clnt
, void *cb
, struct cb_compound_res
**reply
)
519 nfs41_rpc_clnt
*rpc
= (nfs41_rpc_clnt
*)rpc_clnt
;
520 cb_req
*request
= (cb_req
*)cb
;
523 dprintf(1, "nfs41_handle_callback: received call\n");
524 if (request
->rq_prog
!= NFS41_RPC_CBPROGRAM
) {
525 eprintf("invalid rpc program %u\n", request
->rq_prog
);
530 switch (request
->rq_proc
) {
532 dprintf(1, "CB_NULL\n");
536 dprintf(1, "CB_COMPOUND\n");
537 handle_cb_compound(rpc
, request
, reply
);
541 dprintf(1, "invalid rpc procedure %u\n", request
->rq_proc
);