1 /* Handle fileserver selection and rotation. 2 * 3 * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved. 4 * Written by David Howells (dhowells@redhat.com) 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public Licence 8 * as published by the Free Software Foundation; either version 9 * 2 of the Licence, or (at your option) any later version. 10 */ 11 12 #include <linux/kernel.h> 13 #include <linux/slab.h> 14 #include <linux/fs.h> 15 #include <linux/sched.h> 16 #include <linux/delay.h> 17 #include <linux/sched/signal.h> 18 #include "internal.h" 19 #include "afs_fs.h" 20 21 /* 22 * Initialise a filesystem server cursor for iterating over FS servers. 23 */ 24 void afs_init_fs_cursor(struct afs_fs_cursor *fc, struct afs_vnode *vnode) 25 { 26 memset(fc, 0, sizeof(*fc)); 27 } 28 29 /* 30 * Begin an operation on the fileserver. 31 * 32 * Fileserver operations are serialised on the server by vnode, so we serialise 33 * them here also using the io_lock. 34 */ 35 bool afs_begin_vnode_operation(struct afs_fs_cursor *fc, struct afs_vnode *vnode, 36 struct key *key) 37 { 38 afs_init_fs_cursor(fc, vnode); 39 fc->vnode = vnode; 40 fc->key = key; 41 fc->ac.error = SHRT_MAX; 42 43 if (mutex_lock_interruptible(&vnode->io_lock) < 0) { 44 fc->ac.error = -EINTR; 45 fc->flags |= AFS_FS_CURSOR_STOP; 46 return false; 47 } 48 49 if (vnode->lock_state != AFS_VNODE_LOCK_NONE) 50 fc->flags |= AFS_FS_CURSOR_CUR_ONLY; 51 return true; 52 } 53 54 /* 55 * Begin iteration through a server list, starting with the vnode's last used 56 * server if possible, or the last recorded good server if not. 57 */ 58 static bool afs_start_fs_iteration(struct afs_fs_cursor *fc, 59 struct afs_vnode *vnode) 60 { 61 struct afs_cb_interest *cbi; 62 int i; 63 64 read_lock(&vnode->volume->servers_lock); 65 fc->server_list = afs_get_serverlist(vnode->volume->servers); 66 read_unlock(&vnode->volume->servers_lock); 67 68 cbi = vnode->cb_interest; 69 if (cbi) { 70 /* See if the vnode's preferred record is still available */ 71 for (i = 0; i < fc->server_list->nr_servers; i++) { 72 if (fc->server_list->servers[i].cb_interest == cbi) { 73 fc->start = i; 74 goto found_interest; 75 } 76 } 77 78 /* If we have a lock outstanding on a server that's no longer 79 * serving this vnode, then we can't switch to another server 80 * and have to return an error. 81 */ 82 if (fc->flags & AFS_FS_CURSOR_CUR_ONLY) { 83 fc->ac.error = -ESTALE; 84 return false; 85 } 86 87 /* Note that the callback promise is effectively broken */ 88 write_seqlock(&vnode->cb_lock); 89 ASSERTCMP(cbi, ==, vnode->cb_interest); 90 vnode->cb_interest = NULL; 91 if (test_and_clear_bit(AFS_VNODE_CB_PROMISED, &vnode->flags)) 92 vnode->cb_break++; 93 write_sequnlock(&vnode->cb_lock); 94 95 afs_put_cb_interest(afs_v2net(vnode), cbi); 96 cbi = NULL; 97 } else { 98 fc->start = READ_ONCE(fc->server_list->index); 99 } 100 101 found_interest: 102 fc->index = fc->start; 103 return true; 104 } 105 106 /* 107 * Post volume busy note. 108 */ 109 static void afs_busy(struct afs_volume *volume, u32 abort_code) 110 { 111 const char *m; 112 113 switch (abort_code) { 114 case VOFFLINE: m = "offline"; break; 115 case VRESTARTING: m = "restarting"; break; 116 case VSALVAGING: m = "being salvaged"; break; 117 default: m = "busy"; break; 118 } 119 120 pr_notice("kAFS: Volume %u '%s' is %s\n", volume->vid, volume->name, m); 121 } 122 123 /* 124 * Sleep and retry the operation to the same fileserver. 125 */ 126 static bool afs_sleep_and_retry(struct afs_fs_cursor *fc) 127 { 128 msleep_interruptible(1000); 129 if (signal_pending(current)) { 130 fc->ac.error = -ERESTARTSYS; 131 return false; 132 } 133 134 return true; 135 } 136 137 /* 138 * Select the fileserver to use. May be called multiple times to rotate 139 * through the fileservers. 140 */ 141 bool afs_select_fileserver(struct afs_fs_cursor *fc) 142 { 143 struct afs_addr_list *alist; 144 struct afs_server *server; 145 struct afs_vnode *vnode = fc->vnode; 146 147 _enter("%u/%u,%u/%u,%d,%d", 148 fc->index, fc->start, 149 fc->ac.index, fc->ac.start, 150 fc->ac.error, fc->ac.abort_code); 151 152 if (fc->flags & AFS_FS_CURSOR_STOP) { 153 _leave(" = f [stopped]"); 154 return false; 155 } 156 157 /* Evaluate the result of the previous operation, if there was one. */ 158 switch (fc->ac.error) { 159 case SHRT_MAX: 160 goto start; 161 162 case 0: 163 default: 164 /* Success or local failure. Stop. */ 165 fc->flags |= AFS_FS_CURSOR_STOP; 166 _leave(" = f [okay/local %d]", fc->ac.error); 167 return false; 168 169 case -ECONNABORTED: 170 /* The far side rejected the operation on some grounds. This 171 * might involve the server being busy or the volume having been moved. 172 */ 173 switch (fc->ac.abort_code) { 174 case VNOVOL: 175 /* This fileserver doesn't know about the volume. 176 * - May indicate that the VL is wrong - retry once and compare 177 * the results. 178 * - May indicate that the fileserver couldn't attach to the vol. 179 */ 180 if (fc->flags & AFS_FS_CURSOR_VNOVOL) { 181 fc->ac.error = -EREMOTEIO; 182 goto failed; 183 } 184 185 write_lock(&vnode->volume->servers_lock); 186 fc->server_list->vnovol_mask |= 1 << fc->index; 187 write_unlock(&vnode->volume->servers_lock); 188 189 set_bit(AFS_VOLUME_NEEDS_UPDATE, &vnode->volume->flags); 190 fc->ac.error = afs_check_volume_status(vnode->volume, fc->key); 191 if (fc->ac.error < 0) 192 goto failed; 193 194 if (test_bit(AFS_VOLUME_DELETED, &vnode->volume->flags)) { 195 fc->ac.error = -ENOMEDIUM; 196 goto failed; 197 } 198 199 /* If the server list didn't change, then assume that 200 * it's the fileserver having trouble. 201 */ 202 if (vnode->volume->servers == fc->server_list) { 203 fc->ac.error = -EREMOTEIO; 204 goto failed; 205 } 206 207 /* Try again */ 208 fc->flags |= AFS_FS_CURSOR_VNOVOL; 209 _leave(" = t [vnovol]"); 210 return true; 211 212 case VSALVAGE: /* TODO: Should this return an error or iterate? */ 213 case VVOLEXISTS: 214 case VNOSERVICE: 215 case VONLINE: 216 case VDISKFULL: 217 case VOVERQUOTA: 218 fc->ac.error = afs_abort_to_error(fc->ac.abort_code); 219 goto next_server; 220 221 case VOFFLINE: 222 if (!test_and_set_bit(AFS_VOLUME_OFFLINE, &vnode->volume->flags)) { 223 afs_busy(vnode->volume, fc->ac.abort_code); 224 clear_bit(AFS_VOLUME_BUSY, &vnode->volume->flags); 225 } 226 if (fc->flags & AFS_FS_CURSOR_NO_VSLEEP) { 227 fc->ac.error = -EADV; 228 goto failed; 229 } 230 if (fc->flags & AFS_FS_CURSOR_CUR_ONLY) { 231 fc->ac.error = -ESTALE; 232 goto failed; 233 } 234 goto busy; 235 236 case VSALVAGING: 237 case VRESTARTING: 238 case VBUSY: 239 /* Retry after going round all the servers unless we 240 * have a file lock we need to maintain. 241 */ 242 if (fc->flags & AFS_FS_CURSOR_NO_VSLEEP) { 243 fc->ac.error = -EBUSY; 244 goto failed; 245 } 246 if (!test_and_set_bit(AFS_VOLUME_BUSY, &vnode->volume->flags)) { 247 afs_busy(vnode->volume, fc->ac.abort_code); 248 clear_bit(AFS_VOLUME_OFFLINE, &vnode->volume->flags); 249 } 250 busy: 251 if (fc->flags & AFS_FS_CURSOR_CUR_ONLY) { 252 if (!afs_sleep_and_retry(fc)) 253 goto failed; 254 255 /* Retry with same server & address */ 256 _leave(" = t [vbusy]"); 257 return true; 258 } 259 260 fc->flags |= AFS_FS_CURSOR_VBUSY; 261 goto next_server; 262 263 case VMOVED: 264 /* The volume migrated to another server. We consider 265 * consider all locks and callbacks broken and request 266 * an update from the VLDB. 267 * 268 * We also limit the number of VMOVED hops we will 269 * honour, just in case someone sets up a loop. 270 */ 271 if (fc->flags & AFS_FS_CURSOR_VMOVED) { 272 fc->ac.error = -EREMOTEIO; 273 goto failed; 274 } 275 fc->flags |= AFS_FS_CURSOR_VMOVED; 276 277 set_bit(AFS_VOLUME_WAIT, &vnode->volume->flags); 278 set_bit(AFS_VOLUME_NEEDS_UPDATE, &vnode->volume->flags); 279 fc->ac.error = afs_check_volume_status(vnode->volume, fc->key); 280 if (fc->ac.error < 0) 281 goto failed; 282 283 /* If the server list didn't change, then the VLDB is 284 * out of sync with the fileservers. This is hopefully 285 * a temporary condition, however, so we don't want to 286 * permanently block access to the file. 287 * 288 * TODO: Try other fileservers if we can. 289 * 290 * TODO: Retry a few times with sleeps. 291 */ 292 if (vnode->volume->servers == fc->server_list) { 293 fc->ac.error = -ENOMEDIUM; 294 goto failed; 295 } 296 297 goto restart_from_beginning; 298 299 default: 300 clear_bit(AFS_VOLUME_OFFLINE, &vnode->volume->flags); 301 clear_bit(AFS_VOLUME_BUSY, &vnode->volume->flags); 302 fc->ac.error = afs_abort_to_error(fc->ac.abort_code); 303 goto failed; 304 } 305 306 case -ENETUNREACH: 307 case -EHOSTUNREACH: 308 case -ECONNREFUSED: 309 case -ETIMEDOUT: 310 case -ETIME: 311 _debug("no conn"); 312 goto iterate_address; 313 } 314 315 restart_from_beginning: 316 _debug("restart"); 317 afs_end_cursor(&fc->ac); 318 afs_put_cb_interest(afs_v2net(vnode), fc->cbi); 319 fc->cbi = NULL; 320 afs_put_serverlist(afs_v2net(vnode), fc->server_list); 321 fc->server_list = NULL; 322 start: 323 _debug("start"); 324 /* See if we need to do an update of the volume record. Note that the 325 * volume may have moved or even have been deleted. 326 */ 327 fc->ac.error = afs_check_volume_status(vnode->volume, fc->key); 328 if (fc->ac.error < 0) 329 goto failed; 330 331 if (!afs_start_fs_iteration(fc, vnode)) 332 goto failed; 333 goto use_server; 334 335 next_server: 336 _debug("next"); 337 afs_put_cb_interest(afs_v2net(vnode), fc->cbi); 338 fc->cbi = NULL; 339 fc->index++; 340 if (fc->index >= fc->server_list->nr_servers) 341 fc->index = 0; 342 if (fc->index != fc->start) 343 goto use_server; 344 345 /* That's all the servers poked to no good effect. Try again if some 346 * of them were busy. 347 */ 348 if (fc->flags & AFS_FS_CURSOR_VBUSY) 349 goto restart_from_beginning; 350 351 fc->ac.error = -EDESTADDRREQ; 352 goto failed; 353 354 use_server: 355 _debug("use"); 356 /* We're starting on a different fileserver from the list. We need to 357 * check it, create a callback intercept, find its address list and 358 * probe its capabilities before we use it. 359 */ 360 ASSERTCMP(fc->ac.alist, ==, NULL); 361 server = fc->server_list->servers[fc->index].server; 362 363 if (!afs_check_server_record(fc, server)) 364 goto failed; 365 366 _debug("USING SERVER: %pU", &server->uuid); 367 368 /* Make sure we've got a callback interest record for this server. We 369 * have to link it in before we send the request as we can be sent a 370 * break request before we've finished decoding the reply and 371 * installing the vnode. 372 */ 373 fc->ac.error = afs_register_server_cb_interest( 374 vnode, &fc->server_list->servers[fc->index]); 375 if (fc->ac.error < 0) 376 goto failed; 377 378 fc->cbi = afs_get_cb_interest(vnode->cb_interest); 379 380 read_lock(&server->fs_lock); 381 alist = rcu_dereference_protected(server->addresses, 382 lockdep_is_held(&server->fs_lock)); 383 afs_get_addrlist(alist); 384 read_unlock(&server->fs_lock); 385 386 387 /* Probe the current fileserver if we haven't done so yet. */ 388 if (!test_bit(AFS_SERVER_FL_PROBED, &server->flags)) { 389 fc->ac.alist = afs_get_addrlist(alist); 390 391 if (!afs_probe_fileserver(fc)) 392 goto failed; 393 } 394 395 if (!fc->ac.alist) 396 fc->ac.alist = alist; 397 else 398 afs_put_addrlist(alist); 399 400 fc->ac.addr = NULL; 401 fc->ac.start = READ_ONCE(alist->index); 402 fc->ac.index = fc->ac.start; 403 fc->ac.error = 0; 404 fc->ac.begun = false; 405 goto iterate_address; 406 407 iterate_address: 408 ASSERT(fc->ac.alist); 409 _debug("iterate %d/%d", fc->ac.index, fc->ac.alist->nr_addrs); 410 /* Iterate over the current server's address list to try and find an 411 * address on which it will respond to us. 412 */ 413 if (afs_iterate_addresses(&fc->ac)) { 414 _leave(" = t"); 415 return true; 416 } 417 418 afs_end_cursor(&fc->ac); 419 goto next_server; 420 421 failed: 422 fc->flags |= AFS_FS_CURSOR_STOP; 423 _leave(" = f [failed %d]", fc->ac.error); 424 return false; 425 } 426 427 /* 428 * Select the same fileserver we used for a vnode before and only that 429 * fileserver. We use this when we have a lock on that file, which is backed 430 * only by the fileserver we obtained it from. 431 */ 432 bool afs_select_current_fileserver(struct afs_fs_cursor *fc) 433 { 434 struct afs_vnode *vnode = fc->vnode; 435 struct afs_cb_interest *cbi = vnode->cb_interest; 436 struct afs_addr_list *alist; 437 438 _enter(""); 439 440 switch (fc->ac.error) { 441 case SHRT_MAX: 442 if (!cbi) { 443 fc->ac.error = -ESTALE; 444 fc->flags |= AFS_FS_CURSOR_STOP; 445 return false; 446 } 447 448 fc->cbi = afs_get_cb_interest(vnode->cb_interest); 449 450 read_lock(&cbi->server->fs_lock); 451 alist = rcu_dereference_protected(cbi->server->addresses, 452 lockdep_is_held(&cbi->server->fs_lock)); 453 afs_get_addrlist(alist); 454 read_unlock(&cbi->server->fs_lock); 455 if (!alist) { 456 fc->ac.error = -ESTALE; 457 fc->flags |= AFS_FS_CURSOR_STOP; 458 return false; 459 } 460 461 fc->ac.alist = alist; 462 fc->ac.addr = NULL; 463 fc->ac.start = READ_ONCE(alist->index); 464 fc->ac.index = fc->ac.start; 465 fc->ac.error = 0; 466 fc->ac.begun = false; 467 goto iterate_address; 468 469 case 0: 470 default: 471 /* Success or local failure. Stop. */ 472 fc->flags |= AFS_FS_CURSOR_STOP; 473 _leave(" = f [okay/local %d]", fc->ac.error); 474 return false; 475 476 case -ECONNABORTED: 477 fc->flags |= AFS_FS_CURSOR_STOP; 478 _leave(" = f [abort]"); 479 return false; 480 481 case -ENETUNREACH: 482 case -EHOSTUNREACH: 483 case -ECONNREFUSED: 484 case -ETIMEDOUT: 485 case -ETIME: 486 _debug("no conn"); 487 goto iterate_address; 488 } 489 490 iterate_address: 491 /* Iterate over the current server's address list to try and find an 492 * address on which it will respond to us. 493 */ 494 if (afs_iterate_addresses(&fc->ac)) { 495 _leave(" = t"); 496 return true; 497 } 498 499 afs_end_cursor(&fc->ac); 500 return false; 501 } 502 503 /* 504 * Tidy up a filesystem cursor and unlock the vnode. 505 */ 506 int afs_end_vnode_operation(struct afs_fs_cursor *fc) 507 { 508 struct afs_net *net = afs_v2net(fc->vnode); 509 int ret; 510 511 mutex_unlock(&fc->vnode->io_lock); 512 513 afs_end_cursor(&fc->ac); 514 afs_put_cb_interest(net, fc->cbi); 515 afs_put_serverlist(net, fc->server_list); 516 517 ret = fc->ac.error; 518 if (ret == -ECONNABORTED) 519 afs_abort_to_error(fc->ac.abort_code); 520 521 return fc->ac.error; 522 } 523 524 #if 0 525 /* 526 * Set a filesystem server cursor for using a specific FS server. 527 */ 528 int afs_set_fs_cursor(struct afs_fs_cursor *fc, struct afs_vnode *vnode) 529 { 530 afs_init_fs_cursor(fc, vnode); 531 532 read_seqlock_excl(&vnode->cb_lock); 533 if (vnode->cb_interest) { 534 if (vnode->cb_interest->server->fs_state == 0) 535 fc->server = afs_get_server(vnode->cb_interest->server); 536 else 537 fc->ac.error = vnode->cb_interest->server->fs_state; 538 } else { 539 fc->ac.error = -ESTALE; 540 } 541 read_sequnlock_excl(&vnode->cb_lock); 542 543 return fc->ac.error; 544 } 545 546 /* 547 * pick a server to use to try accessing this volume 548 * - returns with an elevated usage count on the server chosen 549 */ 550 bool afs_volume_pick_fileserver(struct afs_fs_cursor *fc, struct afs_vnode *vnode) 551 { 552 struct afs_volume *volume = vnode->volume; 553 struct afs_server *server; 554 int ret, state, loop; 555 556 _enter("%s", volume->vlocation->vldb.name); 557 558 /* stick with the server we're already using if we can */ 559 if (vnode->cb_interest && vnode->cb_interest->server->fs_state == 0) { 560 fc->server = afs_get_server(vnode->cb_interest->server); 561 goto set_server; 562 } 563 564 down_read(&volume->server_sem); 565 566 /* handle the no-server case */ 567 if (volume->nservers == 0) { 568 fc->ac.error = volume->rjservers ? -ENOMEDIUM : -ESTALE; 569 up_read(&volume->server_sem); 570 _leave(" = f [no servers %d]", fc->ac.error); 571 return false; 572 } 573 574 /* basically, just search the list for the first live server and use 575 * that */ 576 ret = 0; 577 for (loop = 0; loop < volume->nservers; loop++) { 578 server = volume->servers[loop]; 579 state = server->fs_state; 580 581 _debug("consider %d [%d]", loop, state); 582 583 switch (state) { 584 case 0: 585 goto picked_server; 586 587 case -ENETUNREACH: 588 if (ret == 0) 589 ret = state; 590 break; 591 592 case -EHOSTUNREACH: 593 if (ret == 0 || 594 ret == -ENETUNREACH) 595 ret = state; 596 break; 597 598 case -ECONNREFUSED: 599 if (ret == 0 || 600 ret == -ENETUNREACH || 601 ret == -EHOSTUNREACH) 602 ret = state; 603 break; 604 605 default: 606 case -EREMOTEIO: 607 if (ret == 0 || 608 ret == -ENETUNREACH || 609 ret == -EHOSTUNREACH || 610 ret == -ECONNREFUSED) 611 ret = state; 612 break; 613 } 614 } 615 616 error: 617 fc->ac.error = ret; 618 619 /* no available servers 620 * - TODO: handle the no active servers case better 621 */ 622 up_read(&volume->server_sem); 623 _leave(" = f [%d]", fc->ac.error); 624 return false; 625 626 picked_server: 627 /* Found an apparently healthy server. We need to register an interest 628 * in receiving callbacks before we talk to it. 629 */ 630 ret = afs_register_server_cb_interest(vnode, 631 &volume->cb_interests[loop], server); 632 if (ret < 0) 633 goto error; 634 635 fc->server = afs_get_server(server); 636 up_read(&volume->server_sem); 637 set_server: 638 fc->ac.alist = afs_get_addrlist(fc->server->addrs); 639 fc->ac.addr = &fc->ac.alist->addrs[0]; 640 _debug("USING SERVER: %pIS\n", &fc->ac.addr->transport); 641 _leave(" = t (picked %pIS)", &fc->ac.addr->transport); 642 return true; 643 } 644 645 /* 646 * release a server after use 647 * - releases the ref on the server struct that was acquired by picking 648 * - records result of using a particular server to access a volume 649 * - return true to try again, false if okay or to issue error 650 * - the caller must release the server struct if result was false 651 */ 652 bool afs_iterate_fs_cursor(struct afs_fs_cursor *fc, 653 struct afs_vnode *vnode) 654 { 655 struct afs_volume *volume = vnode->volume; 656 struct afs_server *server = fc->server; 657 unsigned loop; 658 659 _enter("%s,%pIS,%d", 660 volume->vlocation->vldb.name, &fc->ac.addr->transport, 661 fc->ac.error); 662 663 switch (fc->ac.error) { 664 /* success */ 665 case 0: 666 server->fs_state = 0; 667 _leave(" = f"); 668 return false; 669 670 /* the fileserver denied all knowledge of the volume */ 671 case -ENOMEDIUM: 672 down_write(&volume->server_sem); 673 674 /* firstly, find where the server is in the active list (if it 675 * is) */ 676 for (loop = 0; loop < volume->nservers; loop++) 677 if (volume->servers[loop] == server) 678 goto present; 679 680 /* no longer there - may have been discarded by another op */ 681 goto try_next_server_upw; 682 683 present: 684 volume->nservers--; 685 memmove(&volume->servers[loop], 686 &volume->servers[loop + 1], 687 sizeof(volume->servers[loop]) * 688 (volume->nservers - loop)); 689 volume->servers[volume->nservers] = NULL; 690 afs_put_server(afs_v2net(vnode), server); 691 volume->rjservers++; 692 693 if (volume->nservers > 0) 694 /* another server might acknowledge its existence */ 695 goto try_next_server_upw; 696 697 /* handle the case where all the fileservers have rejected the 698 * volume 699 * - TODO: try asking the fileservers for volume information 700 * - TODO: contact the VL server again to see if the volume is 701 * no longer registered 702 */ 703 up_write(&volume->server_sem); 704 afs_put_server(afs_v2net(vnode), server); 705 fc->server = NULL; 706 _leave(" = f [completely rejected]"); 707 return false; 708 709 /* problem reaching the server */ 710 case -ENETUNREACH: 711 case -EHOSTUNREACH: 712 case -ECONNREFUSED: 713 case -ETIME: 714 case -ETIMEDOUT: 715 case -EREMOTEIO: 716 /* mark the server as dead 717 * TODO: vary dead timeout depending on error 718 */ 719 spin_lock(&server->fs_lock); 720 if (!server->fs_state) { 721 server->fs_state = fc->ac.error; 722 printk("kAFS: SERVER DEAD state=%d\n", fc->ac.error); 723 } 724 spin_unlock(&server->fs_lock); 725 goto try_next_server; 726 727 /* miscellaneous error */ 728 default: 729 case -ENOMEM: 730 case -ENONET: 731 /* tell the caller to accept the result */ 732 afs_put_server(afs_v2net(vnode), server); 733 fc->server = NULL; 734 _leave(" = f [local failure]"); 735 return false; 736 } 737 738 /* tell the caller to loop around and try the next server */ 739 try_next_server_upw: 740 up_write(&volume->server_sem); 741 try_next_server: 742 afs_put_server(afs_v2net(vnode), server); 743 _leave(" = t [try next server]"); 744 return true; 745 } 746 747 /* 748 * Clean up a fileserver cursor. 749 */ 750 int afs_end_fs_cursor(struct afs_fs_cursor *fc, struct afs_net *net) 751 { 752 afs_end_cursor(&fc->ac); 753 afs_put_server(net, fc->server); 754 return fc->ac.error; 755 } 756 757 #endif 758