1 /* NFSv4.1 client for Windows 2 * Copyright � 2012 The Regents of the University of Michigan 3 * 4 * Olga Kornievskaia <aglo@umich.edu> 5 * Casey Bodley <cbodley@umich.edu> 6 * 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. 11 * 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. 16 * 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 20 */ 21 22 #include "delegation.h" 23 #include "nfs41_ops.h" 24 #include "name_cache.h" 25 #include "util.h" 26 #include "daemon_debug.h" 27 28 #include <devioctl.h> 29 #include "nfs41_driver.h" /* for making downcall to invalidate cache */ 30 #include "util.h" 31 32 #define DGLVL 2 /* dprintf level for delegation logging */ 33 34 35 /* allocation and reference counting */ 36 static int delegation_create( 37 IN const nfs41_path_fh *parent, 38 IN const nfs41_path_fh *file, 39 IN const open_delegation4 *delegation, 40 OUT nfs41_delegation_state **deleg_out) 41 { 42 nfs41_delegation_state *state; 43 int status = NO_ERROR; 44 45 state = calloc(1, sizeof(nfs41_delegation_state)); 46 if (state == NULL) { 47 status = GetLastError(); 48 goto out; 49 } 50 51 memcpy(&state->state, delegation, sizeof(open_delegation4)); 52 53 abs_path_copy(&state->path, file->path); 54 path_fh_init(&state->file, &state->path); 55 fh_copy(&state->file.fh, &file->fh); 56 path_fh_init(&state->parent, &state->path); 57 last_component(state->path.path, state->file.name.name, 58 &state->parent.name); 59 fh_copy(&state->parent.fh, &parent->fh); 60 61 list_init(&state->client_entry); 62 state->status = DELEGATION_GRANTED; 63 InitializeSRWLock(&state->lock); 64 InitializeConditionVariable(&state->cond); 65 state->ref_count = 1; 66 *deleg_out = state; 67 out: 68 return status; 69 } 70 71 void nfs41_delegation_ref( 72 IN nfs41_delegation_state *state) 73 { 74 const LONG count = InterlockedIncrement(&state->ref_count); 75 dprintf(DGLVL, "nfs41_delegation_ref(%s) count %d\n", 76 state->path.path, count); 77 } 78 79 void nfs41_delegation_deref( 80 IN nfs41_delegation_state *state) 81 { 82 const LONG count = InterlockedDecrement(&state->ref_count); 83 dprintf(DGLVL, "nfs41_delegation_deref(%s) count %d\n", 84 state->path.path, count); 85 if (count == 0) 86 free(state); 87 } 88 89 #define open_entry(pos) list_container(pos, nfs41_open_state, client_entry) 90 91 static void delegation_remove( 92 IN nfs41_client *client, 93 IN nfs41_delegation_state *deleg) 94 { 95 struct list_entry *entry; 96 97 /* remove from the client's list */ 98 EnterCriticalSection(&client->state.lock); 99 list_remove(&deleg->client_entry); 100 101 /* remove from each associated open */ 102 list_for_each(entry, &client->state.opens) { 103 nfs41_open_state *open = open_entry(entry); 104 AcquireSRWLockExclusive(&open->lock); 105 if (open->delegation.state == deleg) { 106 /* drop the delegation reference */ 107 nfs41_delegation_deref(open->delegation.state); 108 open->delegation.state = NULL; 109 } 110 ReleaseSRWLockExclusive(&open->lock); 111 } 112 LeaveCriticalSection(&client->state.lock); 113 114 /* signal threads waiting on delegreturn */ 115 AcquireSRWLockExclusive(&deleg->lock); 116 deleg->status = DELEGATION_RETURNED; 117 WakeAllConditionVariable(&deleg->cond); 118 ReleaseSRWLockExclusive(&deleg->lock); 119 120 /* release the client's reference */ 121 nfs41_delegation_deref(deleg); 122 } 123 124 125 /* delegation return */ 126 #define lock_entry(pos) list_container(pos, nfs41_lock_state, open_entry) 127 128 static bool_t has_delegated_locks( 129 IN nfs41_open_state *open) 130 { 131 struct list_entry *entry; 132 list_for_each(entry, &open->locks.list) { 133 if (lock_entry(entry)->delegated) 134 return TRUE; 135 } 136 return FALSE; 137 } 138 139 static int open_deleg_cmp(const struct list_entry *entry, const void *value) 140 { 141 nfs41_open_state *open = open_entry(entry); 142 int result = -1; 143 144 /* open must match the delegation and have state to reclaim */ 145 AcquireSRWLockShared(&open->lock); 146 if (open->delegation.state != value) goto out; 147 if (open->do_close && !has_delegated_locks(open)) goto out; 148 result = 0; 149 out: 150 ReleaseSRWLockShared(&open->lock); 151 return result; 152 } 153 154 /* find the first open that needs recovery */ 155 static nfs41_open_state* deleg_open_find( 156 IN struct client_state *state, 157 IN const nfs41_delegation_state *deleg) 158 { 159 struct list_entry *entry; 160 nfs41_open_state *open = NULL; 161 162 EnterCriticalSection(&state->lock); 163 entry = list_search(&state->opens, deleg, open_deleg_cmp); 164 if (entry) { 165 open = open_entry(entry); 166 nfs41_open_state_ref(open); /* return a reference */ 167 } 168 LeaveCriticalSection(&state->lock); 169 return open; 170 } 171 172 /* find the first lock that needs recovery */ 173 static bool_t deleg_lock_find( 174 IN nfs41_open_state *open, 175 OUT nfs41_lock_state *lock_out) 176 { 177 struct list_entry *entry; 178 bool_t found = FALSE; 179 180 AcquireSRWLockShared(&open->lock); 181 list_for_each(entry, &open->locks.list) { 182 nfs41_lock_state *lock = lock_entry(entry); 183 if (lock->delegated) { 184 /* copy offset, length, type */ 185 lock_out->offset = lock->offset; 186 lock_out->length = lock->length; 187 lock_out->exclusive = lock->exclusive; 188 lock_out->id = lock->id; 189 found = TRUE; 190 break; 191 } 192 } 193 ReleaseSRWLockShared(&open->lock); 194 return found; 195 } 196 197 /* find the matching lock by id, and reset lock.delegated */ 198 static void deleg_lock_update( 199 IN nfs41_open_state *open, 200 IN const nfs41_lock_state *source) 201 { 202 struct list_entry *entry; 203 204 AcquireSRWLockExclusive(&open->lock); 205 list_for_each(entry, &open->locks.list) { 206 nfs41_lock_state *lock = lock_entry(entry); 207 if (lock->id == source->id) { 208 lock->delegated = FALSE; 209 break; 210 } 211 } 212 ReleaseSRWLockExclusive(&open->lock); 213 } 214 215 static int delegation_flush_locks( 216 IN nfs41_open_state *open, 217 IN bool_t try_recovery) 218 { 219 stateid_arg stateid; 220 nfs41_lock_state lock; 221 int status = NFS4_OK; 222 223 stateid.open = open; 224 stateid.delegation = NULL; 225 226 /* get the starting open/lock stateid */ 227 AcquireSRWLockShared(&open->lock); 228 if (open->locks.stateid.seqid) { 229 memcpy(&stateid.stateid, &open->locks.stateid, sizeof(stateid4)); 230 stateid.type = STATEID_LOCK; 231 } else { 232 memcpy(&stateid.stateid, &open->stateid, sizeof(stateid4)); 233 stateid.type = STATEID_OPEN; 234 } 235 ReleaseSRWLockShared(&open->lock); 236 237 /* send LOCK requests for each delegated lock range */ 238 while (deleg_lock_find(open, &lock)) { 239 status = nfs41_lock(open->session, &open->file, 240 &open->owner, lock.exclusive ? WRITE_LT : READ_LT, 241 lock.offset, lock.length, FALSE, try_recovery, &stateid); 242 if (status) 243 break; 244 deleg_lock_update(open, &lock); 245 } 246 247 /* save the updated lock stateid */ 248 if (stateid.type == STATEID_LOCK) { 249 AcquireSRWLockExclusive(&open->lock); 250 if (open->locks.stateid.seqid == 0) { 251 /* if it's a new lock stateid, copy it in */ 252 memcpy(&open->locks.stateid, &stateid.stateid, sizeof(stateid4)); 253 } else if (stateid.stateid.seqid > open->locks.stateid.seqid) { 254 /* update the seqid if it's more recent */ 255 open->locks.stateid.seqid = stateid.stateid.seqid; 256 } 257 ReleaseSRWLockExclusive(&open->lock); 258 } 259 return status; 260 } 261 262 #pragma warning (disable : 4706) /* assignment within conditional expression */ 263 264 static int delegation_return( 265 IN nfs41_client *client, 266 IN nfs41_delegation_state *deleg, 267 IN bool_t truncate, 268 IN bool_t try_recovery) 269 { 270 stateid_arg stateid; 271 nfs41_open_state *open; 272 int status; 273 274 if (deleg->srv_open) { 275 /* make an upcall to the kernel: invalide data cache */ 276 HANDLE pipe; 277 unsigned char inbuf[sizeof(HANDLE)], *buffer = inbuf; 278 DWORD inbuf_len = sizeof(HANDLE), outbuf_len, dstatus; 279 uint32_t length; 280 dprintf(1, "delegation_return: making a downcall for srv_open=%x\n", 281 deleg->srv_open); 282 pipe = CreateFile(NFS41_USER_DEVICE_NAME_A, GENERIC_READ|GENERIC_WRITE, 283 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); 284 if (pipe == INVALID_HANDLE_VALUE) { 285 eprintf("delegation_return: Unable to open downcall pipe %d\n", 286 GetLastError()); 287 goto out_downcall; 288 } 289 length = inbuf_len; 290 safe_write(&buffer, &length, &deleg->srv_open, sizeof(HANDLE)); 291 292 dstatus = DeviceIoControl(pipe, IOCTL_NFS41_INVALCACHE, inbuf, inbuf_len, 293 NULL, 0, (LPDWORD)&outbuf_len, NULL); 294 if (!dstatus) 295 eprintf("IOCTL_NFS41_INVALCACHE failed %d\n", GetLastError()); 296 CloseHandle(pipe); 297 } 298 out_downcall: 299 300 /* recover opens and locks associated with the delegation */ 301 while (open = deleg_open_find(&client->state, deleg)) { 302 status = nfs41_delegation_to_open(open, try_recovery); 303 if (status == NFS4_OK) 304 status = delegation_flush_locks(open, try_recovery); 305 nfs41_open_state_deref(open); 306 307 if (status) 308 break; 309 } 310 311 /* return the delegation */ 312 stateid.type = STATEID_DELEG_FILE; 313 stateid.open = NULL; 314 stateid.delegation = deleg; 315 AcquireSRWLockShared(&deleg->lock); 316 memcpy(&stateid.stateid, &deleg->state.stateid, sizeof(stateid4)); 317 ReleaseSRWLockShared(&deleg->lock); 318 319 status = nfs41_delegreturn(client->session, 320 &deleg->file, &stateid, try_recovery); 321 if (status == NFS4ERR_BADSESSION) 322 goto out; 323 324 delegation_remove(client, deleg); 325 out: 326 return status; 327 } 328 329 /* open delegation */ 330 int nfs41_delegation_granted( 331 IN nfs41_session *session, 332 IN nfs41_path_fh *parent, 333 IN nfs41_path_fh *file, 334 IN open_delegation4 *delegation, 335 IN bool_t try_recovery, 336 OUT nfs41_delegation_state **deleg_out) 337 { 338 stateid_arg stateid; 339 nfs41_client *client = session->client; 340 nfs41_delegation_state *state; 341 int status = NO_ERROR; 342 343 if (delegation->type != OPEN_DELEGATE_READ && 344 delegation->type != OPEN_DELEGATE_WRITE) 345 goto out; 346 347 if (delegation->recalled) { 348 status = NFS4ERR_DELEG_REVOKED; 349 goto out_return; 350 } 351 352 /* allocate the delegation state */ 353 status = delegation_create(parent, file, delegation, &state); 354 if (status) 355 goto out_return; 356 357 /* register the delegation with the client */ 358 EnterCriticalSection(&client->state.lock); 359 /* XXX: check for duplicates by fh and stateid? */ 360 list_add_tail(&client->state.delegations, &state->client_entry); 361 LeaveCriticalSection(&client->state.lock); 362 363 nfs41_delegation_ref(state); /* return a reference */ 364 *deleg_out = state; 365 out: 366 return status; 367 368 out_return: /* return the delegation on failure */ 369 memcpy(&stateid.stateid, &delegation->stateid, sizeof(stateid4)); 370 stateid.type = STATEID_DELEG_FILE; 371 stateid.open = NULL; 372 stateid.delegation = NULL; 373 nfs41_delegreturn(session, file, &stateid, try_recovery); 374 goto out; 375 } 376 377 #define deleg_entry(pos) list_container(pos, nfs41_delegation_state, client_entry) 378 379 static int deleg_file_cmp(const struct list_entry *entry, const void *value) 380 { 381 const nfs41_fh *lhs = &deleg_entry(entry)->file.fh; 382 const nfs41_fh *rhs = (const nfs41_fh*)value; 383 if (lhs->superblock != rhs->superblock) return -1; 384 if (lhs->fileid != rhs->fileid) return -1; 385 return 0; 386 } 387 388 static bool_t delegation_compatible( 389 IN enum open_delegation_type4 type, 390 IN uint32_t create, 391 IN uint32_t access, 392 IN uint32_t deny) 393 { 394 switch (type) { 395 case OPEN_DELEGATE_WRITE: 396 /* An OPEN_DELEGATE_WRITE delegation allows the client to handle, 397 * on its own, all opens. */ 398 return TRUE; 399 400 case OPEN_DELEGATE_READ: 401 /* An OPEN_DELEGATE_READ delegation allows a client to handle, 402 * on its own, requests to open a file for reading that do not 403 * deny OPEN4_SHARE_ACCESS_READ access to others. */ 404 if (create == OPEN4_CREATE) 405 return FALSE; 406 if (access & OPEN4_SHARE_ACCESS_WRITE || deny & OPEN4_SHARE_DENY_READ) 407 return FALSE; 408 return TRUE; 409 410 default: 411 return FALSE; 412 } 413 } 414 415 static int delegation_find( 416 IN nfs41_client *client, 417 IN const void *value, 418 IN list_compare_fn cmp, 419 OUT nfs41_delegation_state **deleg_out) 420 { 421 struct list_entry *entry; 422 int status = NFS4ERR_BADHANDLE; 423 424 EnterCriticalSection(&client->state.lock); 425 entry = list_search(&client->state.delegations, value, cmp); 426 if (entry) { 427 /* return a reference to the delegation */ 428 *deleg_out = deleg_entry(entry); 429 nfs41_delegation_ref(*deleg_out); 430 431 /* move to the 'most recently used' end of the list */ 432 list_remove(entry); 433 list_add_tail(&client->state.delegations, entry); 434 status = NFS4_OK; 435 } 436 LeaveCriticalSection(&client->state.lock); 437 return status; 438 } 439 440 static int delegation_truncate( 441 IN nfs41_delegation_state *deleg, 442 IN nfs41_client *client, 443 IN stateid_arg *stateid, 444 IN nfs41_file_info *info) 445 { 446 nfs41_superblock *superblock = deleg->file.fh.superblock; 447 448 /* use SETATTR to truncate the file */ 449 info->attrmask.arr[1] |= FATTR4_WORD1_TIME_CREATE | 450 FATTR4_WORD1_TIME_MODIFY_SET; 451 452 get_nfs_time(&info->time_create); 453 get_nfs_time(&info->time_modify); 454 info->time_delta = &superblock->time_delta; 455 456 /* mask out unsupported attributes */ 457 nfs41_superblock_supported_attrs(superblock, &info->attrmask); 458 459 return nfs41_setattr(client->session, &deleg->file, stateid, info); 460 } 461 462 int nfs41_delegate_open( 463 IN nfs41_open_state *state, 464 IN uint32_t create, 465 IN OPTIONAL nfs41_file_info *createattrs, 466 OUT nfs41_file_info *info) 467 { 468 nfs41_client *client = state->session->client; 469 nfs41_path_fh *file = &state->file; 470 uint32_t access = state->share_access; 471 uint32_t deny = state->share_deny; 472 nfs41_delegation_state *deleg; 473 stateid_arg stateid; 474 int status; 475 476 /* search for a delegation with this filehandle */ 477 status = delegation_find(client, &file->fh, deleg_file_cmp, &deleg); 478 if (status) 479 goto out; 480 481 AcquireSRWLockExclusive(&deleg->lock); 482 if (deleg->status != DELEGATION_GRANTED) { 483 /* the delegation is being returned, wait for it to finish */ 484 while (deleg->status != DELEGATION_RETURNED) 485 SleepConditionVariableSRW(&deleg->cond, &deleg->lock, INFINITE, 0); 486 status = NFS4ERR_BADHANDLE; 487 } 488 else if (!delegation_compatible(deleg->state.type, create, access, deny)) { 489 #ifdef DELEGATION_RETURN_ON_CONFLICT 490 /* this open will conflict, start the delegation return */ 491 deleg->status = DELEGATION_RETURNING; 492 status = NFS4ERR_DELEG_REVOKED; 493 #else 494 status = NFS4ERR_BADHANDLE; 495 #endif 496 } else if (create == OPEN4_CREATE) { 497 /* copy the stateid for SETATTR */ 498 stateid.open = NULL; 499 stateid.delegation = deleg; 500 stateid.type = STATEID_DELEG_FILE; 501 memcpy(&stateid.stateid, &deleg->state.stateid, sizeof(stateid4)); 502 } 503 if (!status) { 504 dprintf(1, "nfs41_delegate_open: updating srv_open from %x to %x\n", 505 deleg->srv_open, state->srv_open); 506 deleg->srv_open = state->srv_open; 507 } 508 ReleaseSRWLockExclusive(&deleg->lock); 509 510 if (status == NFS4ERR_DELEG_REVOKED) 511 goto out_return; 512 if (status) 513 goto out_deleg; 514 515 if (create == OPEN4_CREATE) { 516 memcpy(info, createattrs, sizeof(nfs41_file_info)); 517 518 /* write delegations allow us to simulate OPEN4_CREATE with SETATTR */ 519 status = delegation_truncate(deleg, client, &stateid, info); 520 if (status) 521 goto out_deleg; 522 } 523 524 /* TODO: check access against deleg->state.permissions or send ACCESS */ 525 526 state->delegation.state = deleg; 527 status = NFS4_OK; 528 out: 529 return status; 530 531 out_return: 532 delegation_return(client, deleg, create == OPEN4_CREATE, TRUE); 533 534 out_deleg: 535 nfs41_delegation_deref(deleg); 536 goto out; 537 } 538 539 int nfs41_delegation_to_open( 540 IN nfs41_open_state *open, 541 IN bool_t try_recovery) 542 { 543 open_delegation4 ignore; 544 open_claim4 claim; 545 stateid4 open_stateid = { 0 }; 546 stateid_arg deleg_stateid; 547 int status = NFS4_OK; 548 549 AcquireSRWLockExclusive(&open->lock); 550 if (open->delegation.state == NULL) /* no delegation to reclaim */ 551 goto out_unlock; 552 553 if (open->do_close) /* already have an open stateid */ 554 goto out_unlock; 555 556 /* if another thread is reclaiming the open stateid, 557 * wait for it to finish before returning success */ 558 if (open->delegation.reclaim) { 559 do { 560 SleepConditionVariableSRW(&open->delegation.cond, &open->lock, 561 INFINITE, 0); 562 } while (open->delegation.reclaim); 563 if (open->do_close) 564 goto out_unlock; 565 } 566 open->delegation.reclaim = 1; 567 568 AcquireSRWLockShared(&open->delegation.state->lock); 569 deleg_stateid.open = open; 570 deleg_stateid.delegation = NULL; 571 deleg_stateid.type = STATEID_DELEG_FILE; 572 memcpy(&deleg_stateid.stateid, &open->delegation.state->state.stateid, 573 sizeof(stateid4)); 574 ReleaseSRWLockShared(&open->delegation.state->lock); 575 576 ReleaseSRWLockExclusive(&open->lock); 577 578 /* send OPEN with CLAIM_DELEGATE_CUR */ 579 claim.claim = CLAIM_DELEGATE_CUR; 580 claim.u.deleg_cur.delegate_stateid = &deleg_stateid; 581 claim.u.deleg_cur.name = &open->file.name; 582 583 status = nfs41_open(open->session, &open->parent, &open->file, 584 &open->owner, &claim, open->share_access, open->share_deny, 585 OPEN4_NOCREATE, 0, NULL, try_recovery, &open_stateid, &ignore, NULL); 586 587 AcquireSRWLockExclusive(&open->lock); 588 if (status == NFS4_OK) { 589 /* save the new open stateid */ 590 memcpy(&open->stateid, &open_stateid, sizeof(stateid4)); 591 open->do_close = 1; 592 } else if (open->do_close && (status == NFS4ERR_BAD_STATEID || 593 status == NFS4ERR_STALE_STATEID || status == NFS4ERR_EXPIRED)) { 594 /* something triggered client state recovery, and the open stateid 595 * has already been reclaimed; see recover_stateid_delegation() */ 596 status = NFS4_OK; 597 } 598 open->delegation.reclaim = 0; 599 600 /* signal anyone waiting on the open stateid */ 601 WakeAllConditionVariable(&open->delegation.cond); 602 out_unlock: 603 ReleaseSRWLockExclusive(&open->lock); 604 if (status) 605 eprintf("nfs41_delegation_to_open(%p) failed with %s\n", 606 open, nfs_error_string(status)); 607 return status; 608 } 609 610 void nfs41_delegation_remove_srvopen( 611 IN nfs41_session *session, 612 IN nfs41_path_fh *file) 613 { 614 nfs41_delegation_state *deleg = NULL; 615 616 /* find a delegation for this file */ 617 if (delegation_find(session->client, &file->fh, deleg_file_cmp, &deleg)) 618 return; 619 dprintf(1, "nfs41_delegation_remove_srvopen: removing reference to " 620 "srv_open=%x\n", deleg->srv_open); 621 AcquireSRWLockExclusive(&deleg->lock); 622 deleg->srv_open = NULL; 623 ReleaseSRWLockExclusive(&deleg->lock); 624 nfs41_delegation_deref(deleg); 625 } 626 627 /* synchronous delegation return */ 628 #ifdef DELEGATION_RETURN_ON_CONFLICT 629 int nfs41_delegation_return( 630 IN nfs41_session *session, 631 IN nfs41_path_fh *file, 632 #ifndef __REACTOS__ 633 IN enum open_delegation_type4 access, 634 #else 635 IN int access, 636 #endif 637 IN bool_t truncate) 638 { 639 nfs41_client *client = session->client; 640 nfs41_delegation_state *deleg; 641 int status; 642 643 /* find a delegation for this file */ 644 status = delegation_find(client, &file->fh, deleg_file_cmp, &deleg); 645 if (status) 646 goto out; 647 648 AcquireSRWLockExclusive(&deleg->lock); 649 if (deleg->status == DELEGATION_GRANTED) { 650 /* return unless delegation is write and access is read */ 651 if (deleg->state.type != OPEN_DELEGATE_WRITE 652 || access != OPEN_DELEGATE_READ) { 653 deleg->status = DELEGATION_RETURNING; 654 status = NFS4ERR_DELEG_REVOKED; 655 } 656 } else { 657 /* the delegation is being returned, wait for it to finish */ 658 while (deleg->status == DELEGATION_RETURNING) 659 SleepConditionVariableSRW(&deleg->cond, &deleg->lock, INFINITE, 0); 660 status = NFS4ERR_BADHANDLE; 661 } 662 ReleaseSRWLockExclusive(&deleg->lock); 663 664 if (status == NFS4ERR_DELEG_REVOKED) { 665 delegation_return(client, deleg, truncate, TRUE); 666 status = NFS4_OK; 667 } 668 669 nfs41_delegation_deref(deleg); 670 out: 671 return status; 672 } 673 #endif 674 675 676 /* asynchronous delegation recall */ 677 struct recall_thread_args { 678 nfs41_client *client; 679 nfs41_delegation_state *delegation; 680 bool_t truncate; 681 }; 682 683 static unsigned int WINAPI delegation_recall_thread(void *args) 684 { 685 struct recall_thread_args *recall = (struct recall_thread_args*)args; 686 687 delegation_return(recall->client, recall->delegation, recall->truncate, TRUE); 688 689 /* clean up thread arguments */ 690 nfs41_delegation_deref(recall->delegation); 691 nfs41_root_deref(recall->client->root); 692 free(recall); 693 return 0; 694 } 695 696 static int deleg_stateid_cmp(const struct list_entry *entry, const void *value) 697 { 698 const stateid4 *lhs = &deleg_entry(entry)->state.stateid; 699 const stateid4 *rhs = (const stateid4*)value; 700 return memcmp(lhs->other, rhs->other, NFS4_STATEID_OTHER); 701 } 702 703 int nfs41_delegation_recall( 704 IN nfs41_client *client, 705 IN nfs41_fh *fh, 706 IN const stateid4 *stateid, 707 IN bool_t truncate) 708 { 709 nfs41_delegation_state *deleg; 710 struct recall_thread_args *args; 711 int status; 712 713 dprintf(2, "--> nfs41_delegation_recall()\n"); 714 715 /* search for the delegation by stateid instead of filehandle; 716 * deleg_file_cmp() relies on a proper superblock and fileid, 717 * which we don't get with CB_RECALL */ 718 status = delegation_find(client, stateid, deleg_stateid_cmp, &deleg); 719 if (status) 720 goto out; 721 722 AcquireSRWLockExclusive(&deleg->lock); 723 if (deleg->state.recalled) { 724 /* return BADHANDLE if we've already responded to CB_RECALL */ 725 status = NFS4ERR_BADHANDLE; 726 } else { 727 deleg->state.recalled = 1; 728 729 if (deleg->status == DELEGATION_GRANTED) { 730 /* start the delegation return */ 731 deleg->status = DELEGATION_RETURNING; 732 status = NFS4ERR_DELEG_REVOKED; 733 } /* else return NFS4_OK */ 734 } 735 ReleaseSRWLockExclusive(&deleg->lock); 736 737 if (status != NFS4ERR_DELEG_REVOKED) 738 goto out_deleg; 739 740 /* allocate thread arguments */ 741 args = calloc(1, sizeof(struct recall_thread_args)); 742 if (args == NULL) { 743 status = NFS4ERR_SERVERFAULT; 744 eprintf("nfs41_delegation_recall() failed to allocate arguments\n"); 745 goto out_deleg; 746 } 747 748 /* hold a reference on the root */ 749 nfs41_root_ref(client->root); 750 args->client = client; 751 args->delegation = deleg; 752 args->truncate = truncate; 753 754 /* the callback thread can't make rpc calls, so spawn a separate thread */ 755 if (_beginthreadex(NULL, 0, delegation_recall_thread, args, 0, NULL) == 0) { 756 status = NFS4ERR_SERVERFAULT; 757 eprintf("nfs41_delegation_recall() failed to start thread\n"); 758 goto out_args; 759 } 760 status = NFS4_OK; 761 out: 762 dprintf(DGLVL, "<-- nfs41_delegation_recall() returning %s\n", 763 nfs_error_string(status)); 764 return status; 765 766 out_args: 767 free(args); 768 nfs41_root_deref(client->root); 769 out_deleg: 770 nfs41_delegation_deref(deleg); 771 goto out; 772 } 773 774 775 static int deleg_fh_cmp(const struct list_entry *entry, const void *value) 776 { 777 const nfs41_fh *lhs = &deleg_entry(entry)->file.fh; 778 const nfs41_fh *rhs = (const nfs41_fh*)value; 779 if (lhs->len != rhs->len) return -1; 780 return memcmp(lhs->fh, rhs->fh, lhs->len); 781 } 782 783 int nfs41_delegation_getattr( 784 IN nfs41_client *client, 785 IN const nfs41_fh *fh, 786 IN const bitmap4 *attr_request, 787 OUT nfs41_file_info *info) 788 { 789 nfs41_delegation_state *deleg; 790 uint64_t fileid; 791 int status; 792 793 dprintf(2, "--> nfs41_delegation_getattr()\n"); 794 795 /* search for a delegation on this file handle */ 796 status = delegation_find(client, fh, deleg_fh_cmp, &deleg); 797 if (status) 798 goto out; 799 800 AcquireSRWLockShared(&deleg->lock); 801 fileid = deleg->file.fh.fileid; 802 if (deleg->status != DELEGATION_GRANTED || 803 deleg->state.type != OPEN_DELEGATE_WRITE) { 804 status = NFS4ERR_BADHANDLE; 805 } 806 ReleaseSRWLockShared(&deleg->lock); 807 if (status) 808 goto out_deleg; 809 810 ZeroMemory(info, sizeof(nfs41_file_info)); 811 812 /* find attributes for the given fileid */ 813 status = nfs41_attr_cache_lookup( 814 client_name_cache(client), fileid, info); 815 if (status) { 816 status = NFS4ERR_BADHANDLE; 817 goto out_deleg; 818 } 819 out_deleg: 820 nfs41_delegation_deref(deleg); 821 out: 822 dprintf(DGLVL, "<-- nfs41_delegation_getattr() returning %s\n", 823 nfs_error_string(status)); 824 return status; 825 } 826 827 828 void nfs41_client_delegation_free( 829 IN nfs41_client *client) 830 { 831 struct list_entry *entry, *tmp; 832 833 EnterCriticalSection(&client->state.lock); 834 list_for_each_tmp (entry, tmp, &client->state.delegations) { 835 list_remove(entry); 836 nfs41_delegation_deref(deleg_entry(entry)); 837 } 838 LeaveCriticalSection(&client->state.lock); 839 } 840 841 842 static int delegation_recovery_status( 843 IN nfs41_delegation_state *deleg) 844 { 845 int status = NFS4_OK; 846 847 AcquireSRWLockExclusive(&deleg->lock); 848 if (deleg->status == DELEGATION_GRANTED) { 849 if (deleg->revoked) { 850 deleg->status = DELEGATION_RETURNED; 851 status = NFS4ERR_BADHANDLE; 852 } else if (deleg->state.recalled) { 853 deleg->status = DELEGATION_RETURNING; 854 status = NFS4ERR_DELEG_REVOKED; 855 } 856 } 857 ReleaseSRWLockExclusive(&deleg->lock); 858 return status; 859 } 860 861 int nfs41_client_delegation_recovery( 862 IN nfs41_client *client) 863 { 864 struct list_entry *entry, *tmp; 865 nfs41_delegation_state *deleg; 866 int status = NFS4_OK; 867 868 list_for_each_tmp(entry, tmp, &client->state.delegations) { 869 deleg = list_container(entry, nfs41_delegation_state, client_entry); 870 871 status = delegation_recovery_status(deleg); 872 switch (status) { 873 case NFS4ERR_DELEG_REVOKED: 874 /* the delegation was reclaimed, but flagged as recalled; 875 * return it with try_recovery=FALSE */ 876 status = delegation_return(client, deleg, FALSE, FALSE); 877 break; 878 879 case NFS4ERR_BADHANDLE: 880 /* reclaim failed, so we have no delegation state on the server; 881 * 'forget' the delegation without trying to return it */ 882 delegation_remove(client, deleg); 883 status = NFS4_OK; 884 break; 885 } 886 887 if (status == NFS4ERR_BADSESSION) 888 goto out; 889 } 890 891 /* use DELEGPURGE to indicate that we're done reclaiming delegations */ 892 status = nfs41_delegpurge(client->session); 893 894 /* support for DELEGPURGE is optional; ignore any errors but BADSESSION */ 895 if (status != NFS4ERR_BADSESSION) 896 status = NFS4_OK; 897 out: 898 return status; 899 } 900 901 902 int nfs41_client_delegation_return_lru( 903 IN nfs41_client *client) 904 { 905 struct list_entry *entry; 906 nfs41_delegation_state *state = NULL; 907 int status = NFS4ERR_BADHANDLE; 908 909 /* starting from the least recently opened, find and return 910 * the first delegation that's not 'in use' (currently open) */ 911 912 /* TODO: use a more robust algorithm, taking into account: 913 * -number of total opens 914 * -time since last operation on an associated open, or 915 * -number of operations/second over last n seconds */ 916 EnterCriticalSection(&client->state.lock); 917 list_for_each(entry, &client->state.delegations) { 918 state = deleg_entry(entry); 919 920 /* skip if it's currently in use for an open; note that ref_count 921 * can't go from 1 to 2 without holding client->state.lock */ 922 if (state->ref_count > 1) 923 continue; 924 925 AcquireSRWLockExclusive(&state->lock); 926 if (state->status == DELEGATION_GRANTED) { 927 /* start returning the delegation */ 928 state->status = DELEGATION_RETURNING; 929 status = NFS4ERR_DELEG_REVOKED; 930 } 931 ReleaseSRWLockExclusive(&state->lock); 932 933 if (status == NFS4ERR_DELEG_REVOKED) 934 break; 935 } 936 LeaveCriticalSection(&client->state.lock); 937 938 if (status == NFS4ERR_DELEG_REVOKED) 939 status = delegation_return(client, state, FALSE, TRUE); 940 return status; 941 } 942