1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * Simple nfs V4 ops 26 */ 27 28 #include <rpc/types.h> 29 #include <rpc/auth.h> 30 #include <sys/t_lock.h> 31 #include "clnt.h" 32 #include <sys/fcntl.h> 33 #include <sys/vfs.h> 34 #include <errno.h> 35 #include <sys/promif.h> 36 #include <rpc/xdr.h> 37 #include "nfs_inet.h" 38 #include <sys/stat.h> 39 #include <sys/bootvfs.h> 40 #include <sys/bootdebug.h> 41 #include <sys/salib.h> 42 #include <sys/sacache.h> 43 #include <rpc/rpc.h> 44 #include "brpc.h" 45 #include <rpcsvc/nfs4_prot.h> 46 47 #define dprintf if (boothowto & RB_DEBUG) printf 48 49 static struct timeval zero_timeout = {0, 0}; /* default */ 50 51 /* 52 * NFS Version 4 specific functions 53 */ 54 55 ssize_t 56 nfs4read(struct nfs_file *filep, char *buf, size_t size) 57 { 58 enum clnt_stat status; 59 read4arg_t readargs; 60 read4res_t readres; 61 char *buf_offset; 62 uint_t count = 0; 63 uint_t readcnt = 0; 64 bool_t done = FALSE; 65 struct timeval timeout; 66 int framing_errs = 0; 67 static uint_t pos; 68 static char ind[] = "|/-\\"; 69 static int blks_read; 70 utf8string str; 71 char tagname[] = "inetboot read"; 72 73 bzero(&readres, sizeof (readres)); 74 75 str.utf8string_len = sizeof (tagname) - 1; 76 str.utf8string_val = tagname; 77 78 /* 79 * read 80 */ 81 buf_offset = buf; 82 83 if (nfs_readsize == 0) 84 nfs_readsize = READ4_SIZE; 85 86 if (size < nfs_readsize) 87 readargs.r_count = size; 88 else 89 readargs.r_count = nfs_readsize; 90 91 if (filep->fh.fh4.len > 0) 92 compound_init(&readargs.r_arg, &str, 0, 2, &filep->fh.fh4); 93 else 94 compound_init(&readargs.r_arg, &str, 0, 2, NULL); 95 96 readargs.r_opread = OP_READ; 97 /* 98 * zero out the stateid field 99 */ 100 bzero(&readargs.r_stateid, sizeof (readargs.r_stateid)); 101 readargs.r_offset = filep->offset; 102 103 do { 104 readres.r_data_val = buf_offset; 105 106 if ((count + readargs.r_count) > size) 107 readargs.r_count = size - count; 108 109 timeout.tv_sec = NFS_REXMIT_MIN; 110 timeout.tv_usec = 0; 111 112 do { 113 status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, 114 xdr_read4_args, (caddr_t)&readargs, 115 xdr_read4_res, (caddr_t)&readres, 116 timeout); 117 118 if (status == RPC_TIMEDOUT) { 119 dprintf("NFS read(%d) timed out. Retrying...\n", 120 readargs.r_count); 121 if (errno == ETIMEDOUT) 122 framing_errs++; 123 124 if (framing_errs > NFS_MAX_FERRS && 125 readargs.r_count > NFS_READ_DECR) { 126 readargs.r_count /= 2; 127 nfs_readsize /= 2; 128 dprintf("NFS read size now %d.\n", 129 nfs_readsize); 130 timeout.tv_sec = NFS_REXMIT_MIN; 131 framing_errs = 0; 132 } else { 133 if (timeout.tv_sec < NFS_REXMIT_MAX) 134 timeout.tv_sec++; 135 else 136 timeout.tv_sec = 0; 137 } 138 } 139 } while (status == RPC_TIMEDOUT); 140 141 if (status != RPC_SUCCESS) 142 return (-1); 143 144 if (readres.r_status != NFS4_OK) { 145 nfs4_error(readres.r_status); 146 return (-1); 147 } 148 149 readcnt = readres.r_data_len; 150 151 if (readres.r_eof == TRUE) 152 done = TRUE; 153 154 if (readcnt < readargs.r_count) { 155 #ifdef NFS_OPS_DEBUG 156 if ((boothowto & DBFLAGS) == DBFLAGS) 157 printf("nfs4read: partial read %d instead " 158 "of %d\n", readcnt, readargs.count); 159 #endif 160 done = TRUE; 161 } 162 163 count += readcnt; 164 filep->offset += readcnt; 165 buf_offset += readcnt; 166 readargs.r_offset += readcnt; 167 if ((blks_read++ & 0x3) == 0) 168 printf("%c\b", ind[pos++ & 3]); 169 } while (count < size && !done); 170 171 return (count); 172 } 173 174 175 static vtype_t nf4_to_vt[] = { 176 VBAD, VREG, VDIR, VBLK, VCHR, VLNK, VSOCK, VFIFO 177 }; 178 179 int 180 nfs4getattr(struct nfs_file *nfp, struct vattr *vap) 181 { 182 enum clnt_stat status; 183 attr4_bitmap1_t bitmap1; 184 attr4_bitmap2_t bitmap2; 185 getattr4arg_t getattrargs; 186 getattr4res_t getattrres; 187 b_fattr4_t *bfattr4; 188 utf8string str; 189 char tagname[] = "inetboot getattr"; 190 191 bzero(&getattrres, sizeof (getattrres)); 192 /* 193 * Putfh 194 */ 195 str.utf8string_len = sizeof (tagname) - 1; 196 str.utf8string_val = tagname; 197 198 if (nfp->fh.fh4.len > 0) 199 compound_init(&getattrargs.ga_arg, &str, 0, 2, &nfp->fh.fh4); 200 else 201 compound_init(&getattrargs.ga_arg, &str, 0, 2, NULL); 202 203 /* 204 * getattr 205 */ 206 getattrargs.ga_opgetattr = OP_GETATTR; 207 /* 208 * Set up the attribute bitmap. We pretty much need everything 209 * except for the filehandle and supported attrs. 210 */ 211 bitmap1.word = 0; 212 bitmap1.bm_fattr4_type = 1; 213 bitmap1.bm_fattr4_size = 1; 214 bitmap1.bm_fattr4_fileid = 1; 215 bitmap2.word = 0; 216 bitmap2.bm_fattr4_mode = 1; 217 bitmap2.bm_fattr4_time_access = 1; 218 bitmap2.bm_fattr4_time_metadata = 1; 219 bitmap2.bm_fattr4_time_modify = 1; 220 221 getattrargs.ga_attr_req.b_bitmap_len = NFS4_MAX_BITWORDS; 222 getattrargs.ga_attr_req.b_bitmap_val[0] = bitmap1.word; 223 getattrargs.ga_attr_req.b_bitmap_val[1] = bitmap2.word; 224 225 status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_getattr4_args, 226 (caddr_t)&getattrargs, xdr_getattr4_res, 227 (caddr_t)&getattrres, zero_timeout); 228 229 if (status != RPC_SUCCESS) { 230 dprintf("nfs4getattr: RPC error %d\n", status); 231 return (-1); 232 } 233 234 if (getattrres.gr_attr_status != NFS4_OK) { 235 nfs4_error(getattrres.gr_attr_status); 236 return (getattrres.gr_attr_status); 237 } 238 239 bfattr4 = &getattrres.gr_attrs; 240 if (vap->va_mask & AT_TYPE) { 241 if (bfattr4->b_fattr4_type < NF4REG || 242 bfattr4->b_fattr4_type > NF4FIFO) 243 vap->va_type = VBAD; 244 else 245 vap->va_type = nf4_to_vt[bfattr4->b_fattr4_type]; 246 } 247 if (vap->va_mask & AT_MODE) 248 vap->va_mode = (mode_t)bfattr4->b_fattr4_mode; 249 if (vap->va_mask & AT_SIZE) 250 vap->va_size = (u_offset_t)bfattr4->b_fattr4_size; 251 if (vap->va_mask & AT_NODEID) 252 vap->va_nodeid = (uint64_t)bfattr4->b_fattr4_fileid; 253 /* 254 * XXX - may need to do something more here. 255 */ 256 if (vap->va_mask & AT_ATIME) { 257 vap->va_atime.tv_sec = bfattr4->b_fattr4_time_access.seconds; 258 vap->va_atime.tv_nsec = bfattr4->b_fattr4_time_access.nseconds; 259 } 260 if (vap->va_mask & AT_CTIME) { 261 vap->va_ctime.tv_sec = bfattr4->b_fattr4_time_metadata.seconds; 262 vap->va_ctime.tv_nsec = 263 bfattr4->b_fattr4_time_metadata.nseconds; 264 } 265 if (vap->va_mask & AT_MTIME) { 266 vap->va_mtime.tv_sec = bfattr4->b_fattr4_time_modify.seconds; 267 vap->va_mtime.tv_nsec = bfattr4->b_fattr4_time_modify.nseconds; 268 } 269 270 return (NFS4_OK); 271 } 272 273 /* 274 * Display nfs error messages. 275 */ 276 /*ARGSUSED*/ 277 void 278 nfs4_error(enum nfsstat4 status) 279 { 280 if (!(boothowto & RB_DEBUG)) 281 return; 282 283 switch (status) { 284 case NFS4_OK: 285 printf("NFS: No error.\n"); 286 break; 287 case NFS4ERR_PERM: 288 printf("NFS: Not owner.\n"); 289 break; 290 case NFS4ERR_NOENT: 291 #ifdef NFS_OPS_DEBUG 292 printf("NFS: No such file or directory.\n"); 293 #endif /* NFS_OPS_DEBUG */ 294 break; 295 case NFS4ERR_IO: 296 printf("NFS: IO ERROR occurred on NFS server.\n"); 297 break; 298 case NFS4ERR_NXIO: 299 printf("NFS: No such device or address.\n"); 300 break; 301 case NFS4ERR_ACCESS: 302 printf("NFS: Permission denied.\n"); 303 break; 304 case NFS4ERR_EXIST: 305 printf("NFS: File exists.\n"); 306 break; 307 case NFS4ERR_XDEV: 308 printf("NFS: Cross device hard link.\n"); 309 break; 310 case NFS4ERR_NOTDIR: 311 printf("NFS: Not a directory.\n"); 312 break; 313 case NFS4ERR_ISDIR: 314 printf("NFS: Is a directory.\n"); 315 break; 316 case NFS4ERR_INVAL: 317 printf("NFS: Invalid argument.\n"); 318 break; 319 case NFS4ERR_FBIG: 320 printf("NFS: File too large.\n"); 321 break; 322 case NFS4ERR_NOSPC: 323 printf("NFS: No space left on device.\n"); 324 break; 325 case NFS4ERR_ROFS: 326 printf("NFS: Read-only filesystem.\n"); 327 break; 328 case NFS4ERR_MLINK: 329 printf("NFS: Too many hard links.\n"); 330 break; 331 case NFS4ERR_NAMETOOLONG: 332 printf("NFS: File name too long.\n"); 333 break; 334 case NFS4ERR_NOTEMPTY: 335 printf("NFS: Directory not empty.\n"); 336 break; 337 case NFS4ERR_DQUOT: 338 printf("NFS: Disk quota exceeded.\n"); 339 break; 340 case NFS4ERR_STALE: 341 printf("NFS: Stale file handle.\n"); 342 break; 343 case NFS4ERR_BADHANDLE: 344 printf("NFS: Illegal NFS file handle.\n"); 345 break; 346 case NFS4ERR_BAD_COOKIE: 347 printf("NFS: Stale Cookie.\n"); 348 break; 349 case NFS4ERR_NOTSUPP: 350 printf("NFS: Operation is not supported.\n"); 351 break; 352 case NFS4ERR_TOOSMALL: 353 printf("NFS: Buffer too small.\n"); 354 break; 355 case NFS4ERR_SERVERFAULT: 356 printf("NFS: Server fault.\n"); 357 break; 358 case NFS4ERR_BADTYPE: 359 printf("NFS: Unsupported object type.\n"); 360 break; 361 case NFS4ERR_BAD_STATEID: 362 printf("NFS: Bad stateid\n"); 363 break; 364 case NFS4ERR_BAD_SEQID: 365 printf("NFS: Bad seqid\n"); 366 break; 367 default: 368 printf("NFS: unknown error.\n"); 369 break; 370 } 371 } 372 373 /* 374 * lookup one component. for multicomponent lookup use a driver like lookup(). 375 */ 376 struct nfs_file * 377 nfs4lookup(struct nfs_file *dir, char *name, int *nstat) 378 { 379 static struct nfs_file cd; 380 attr4_bitmap1_t bitmap1; 381 lookup4arg_t lookupargs; 382 lookup4res_t lookupres; 383 enum clnt_stat status; 384 utf8string str; 385 char tagname[] = "inetboot lookup"; 386 387 /* 388 * NFSv4 uses a special LOOKUPP op 389 * for looking up the parent directory. 390 */ 391 if (strcmp(name, "..") == 0) 392 return (nfs4lookupp(dir, nstat, NULL)); 393 394 *nstat = (int)NFS4_OK; 395 396 bzero(&lookupres, sizeof (lookupres)); 397 398 /* 399 * Check if we have a filehandle and initialize the compound 400 * with putfh or putrootfh appropriately. 401 */ 402 str.utf8string_len = sizeof (tagname) - 1; 403 str.utf8string_val = tagname; 404 405 if (dir->fh.fh4.len > 0) 406 compound_init(&lookupargs.la_arg, &str, 0, 3, &dir->fh.fh4); 407 else 408 compound_init(&lookupargs.la_arg, &str, 0, 3, NULL); 409 410 /* 411 * lookup 412 */ 413 lookupargs.la_oplookup = OP_LOOKUP; 414 /* 415 * convert the pathname from char * to utf8string 416 */ 417 lookupargs.la_pathname.utf8string_len = strlen(name); 418 lookupargs.la_pathname.utf8string_val = 419 bkmem_alloc(lookupargs.la_pathname.utf8string_len); 420 if (lookupargs.la_pathname.utf8string_val == NULL) { 421 dprintf("nfs4lookup: bkmem_alloc failed\n"); 422 return (NULL); 423 } 424 bcopy(name, lookupargs.la_pathname.utf8string_val, 425 lookupargs.la_pathname.utf8string_len); 426 427 /* 428 * Setup the attr bitmap. All we need is the type and filehandle info 429 */ 430 lookupargs.la_opgetattr = OP_GETATTR; 431 bitmap1.word = 0; 432 bitmap1.bm_fattr4_type = 1; 433 bitmap1.bm_fattr4_filehandle = 1; 434 lookupargs.la_attr_req.b_bitmap_len = 1; 435 lookupargs.la_attr_req.b_bitmap_val[0] = bitmap1.word; 436 lookupargs.la_attr_req.b_bitmap_val[1] = 0; 437 438 status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_lookup4_args, 439 (caddr_t)&lookupargs, xdr_lookup4_res, 440 (caddr_t)&lookupres, zero_timeout); 441 442 if (status != RPC_SUCCESS) { 443 dprintf("nfs4lookup: RPC error. status %d\n", status); 444 return (NULL); 445 } 446 447 if (lookupres.lr_lookup_status != NFS4_OK) { 448 #ifdef DEBUG 449 dprintf("nfs4lookup: lookup status = %d\n", 450 lookupres.lr_lookup_status); 451 #endif 452 nfs4_error(lookupres.lr_lookup_status); 453 *nstat = (int)lookupres.lr_lookup_status; 454 if (lookupargs.la_pathname.utf8string_val != NULL) 455 bkmem_free(lookupargs.la_pathname.utf8string_val, 456 lookupargs.la_pathname.utf8string_len); 457 return (NULL); 458 } 459 460 if (lookupres.lr_attr_status != NFS4_OK) { 461 #ifdef DEBUG 462 dprintf("nfs4lookup: getattr status = %d\n", 463 lookupres.lr_attr_status); 464 #endif 465 nfs4_error(lookupres.lr_attr_status); 466 *nstat = (int)lookupres.lr_attr_status; 467 if (lookupargs.la_pathname.utf8string_val != NULL) 468 bkmem_free(lookupargs.la_pathname.utf8string_val, 469 lookupargs.la_pathname.utf8string_len); 470 return (NULL); 471 } 472 473 /* 474 * We have all the information we need to update the file pointer 475 */ 476 bzero((caddr_t)&cd, sizeof (struct nfs_file)); 477 cd.version = NFS_V4; 478 cd.ftype.type4 = lookupres.lr_attrs.b_fattr4_type; 479 cd.fh.fh4.len = lookupres.lr_attrs.b_fattr4_filehandle.len; 480 bcopy(lookupres.lr_attrs.b_fattr4_filehandle.data, cd.fh.fh4.data, 481 cd.fh.fh4.len); 482 483 /* 484 * Free the arg string 485 */ 486 if (lookupargs.la_pathname.utf8string_val != NULL) 487 bkmem_free(lookupargs.la_pathname.utf8string_val, 488 lookupargs.la_pathname.utf8string_len); 489 490 return (&cd); 491 } 492 493 /* 494 * lookup parent directory. 495 */ 496 struct nfs_file * 497 nfs4lookupp(struct nfs_file *dir, int *nstat, uint64_t *fileid) 498 { 499 static struct nfs_file cd; 500 attr4_bitmap1_t bitmap1; 501 lookupp4arg_t lookuppargs; 502 lookup4res_t lookupres; 503 enum clnt_stat status; 504 utf8string str; 505 char tagname[] = "inetboot lookupp"; 506 507 *nstat = (int)NFS4_OK; 508 509 bzero(&lookupres, sizeof (lookupres)); 510 511 /* 512 * Check if we have a filehandle and initialize the compound 513 * with putfh or putrootfh appropriately. 514 */ 515 str.utf8string_len = sizeof (tagname) - 1; 516 str.utf8string_val = tagname; 517 518 if (dir->fh.fh4.len > 0) 519 compound_init(&lookuppargs.la_arg, &str, 0, 3, &dir->fh.fh4); 520 else 521 compound_init(&lookuppargs.la_arg, &str, 0, 3, NULL); 522 523 /* 524 * lookupp 525 */ 526 lookuppargs.la_oplookupp = OP_LOOKUPP; 527 /* 528 * Setup the attr bitmap. Normally, all we need is the type and 529 * filehandle info, but getdents might require the fileid of the 530 * parent. 531 */ 532 lookuppargs.la_opgetattr = OP_GETATTR; 533 bitmap1.word = 0; 534 bitmap1.bm_fattr4_type = 1; 535 bitmap1.bm_fattr4_filehandle = 1; 536 if (fileid != NULL) 537 bitmap1.bm_fattr4_fileid = 1; 538 lookuppargs.la_attr_req.b_bitmap_len = 1; 539 lookuppargs.la_attr_req.b_bitmap_val[0] = bitmap1.word; 540 lookuppargs.la_attr_req.b_bitmap_val[1] = 0; 541 542 status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_lookupp4_args, 543 (caddr_t)&lookuppargs, xdr_lookup4_res, 544 (caddr_t)&lookupres, zero_timeout); 545 546 if (status != RPC_SUCCESS) { 547 dprintf("nfs4lookupp: RPC error. status %d\n", status); 548 return (NULL); 549 } 550 551 if (lookupres.lr_lookup_status != NFS4_OK) { 552 #ifdef DEBUG 553 dprintf("nfs4lookupp: lookupp status = %d\n", 554 lookupres.lr_lookup_status); 555 #endif 556 nfs4_error(lookupres.lr_lookup_status); 557 *nstat = (int)lookupres.lr_lookup_status; 558 return (NULL); 559 } 560 561 if (lookupres.lr_attr_status != NFS4_OK) { 562 #ifdef DEBUG 563 dprintf("nfs4lookupp: getattr status = %d\n", 564 lookupres.lr_attr_status); 565 #endif 566 nfs4_error(lookupres.lr_attr_status); 567 *nstat = (int)lookupres.lr_attr_status; 568 return (NULL); 569 } 570 571 /* 572 * We have all the information we need to update the file pointer 573 */ 574 bzero((caddr_t)&cd, sizeof (struct nfs_file)); 575 cd.version = NFS_V4; 576 cd.ftype.type4 = lookupres.lr_attrs.b_fattr4_type; 577 cd.fh.fh4.len = lookupres.lr_attrs.b_fattr4_filehandle.len; 578 bcopy(lookupres.lr_attrs.b_fattr4_filehandle.data, cd.fh.fh4.data, 579 cd.fh.fh4.len); 580 581 /* 582 * Fill in the fileid if the user passed in one 583 */ 584 if (fileid != NULL) 585 *fileid = lookupres.lr_attrs.b_fattr4_fileid; 586 587 return (&cd); 588 } 589 590 /* 591 * Gets symbolic link into pathname. 592 */ 593 int 594 nfs4getsymlink(struct nfs_file *cfile, char **path) 595 { 596 enum clnt_stat status; 597 readlink4arg_t readlinkargs; 598 readlink4res_t readlinkres; 599 static char symlink_path[NFS_MAXPATHLEN]; 600 int spathlen; 601 utf8string str; 602 char tagname[] = "inetboot getsymlink"; 603 int error = NFS4_OK; 604 605 bzero(&readlinkres, sizeof (readlinkres)); 606 607 /* 608 * readlink 609 */ 610 str.utf8string_len = sizeof (tagname) - 1; 611 str.utf8string_val = tagname; 612 613 if (cfile->fh.fh4.len > 0) 614 compound_init(&readlinkargs.rl_arg, &str, 0, 2, 615 &cfile->fh.fh4); 616 else 617 compound_init(&readlinkargs.rl_arg, &str, 0, 2, NULL); 618 619 readlinkargs.rl_opreadlink = OP_READLINK; 620 status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_readlink4_args, 621 (caddr_t)&readlinkargs, xdr_readlink4_res, 622 (caddr_t)&readlinkres, zero_timeout); 623 624 if (status != RPC_SUCCESS) { 625 dprintf("nfs4getsymlink: RPC readlink error %d\n", status); 626 error = -1; 627 goto out; 628 } 629 630 if (readlinkres.rl_status != NFS4_OK) { 631 nfs4_error(readlinkres.rl_status); 632 error = readlinkres.rl_status; 633 goto out; 634 } 635 636 /* 637 * Convert the utf8string to a normal character string 638 */ 639 spathlen = readlinkres.rl_link.utf8string_len; 640 if (spathlen <= 0 || readlinkres.rl_link.utf8string_val == NULL) { 641 *path = NULL; 642 error = readlinkres.rl_status; 643 goto out; 644 } 645 646 bcopy(readlinkres.rl_link.utf8string_val, symlink_path, spathlen); 647 symlink_path[spathlen] = '\0'; 648 *path = symlink_path; 649 650 out: 651 /* 652 * Free the results 653 */ 654 if (readlinkres.rl_link.utf8string_val != NULL) 655 bkmem_free(readlinkres.rl_link.utf8string_val, spathlen); 656 657 return (error); 658 } 659 660 /* 661 * Should just forget about the tag, but will leave in support for the time 662 * being. 663 */ 664 void 665 compound_init(b_compound_t *cp, utf8string *str, uint_t mvers, uint_t arglen, 666 struct nfs_bfh4 *pfh) 667 { 668 if (str == NULL || str->utf8string_len == 0) { 669 cp->ca_tag.utf8string_len = 0; 670 cp->ca_tag.utf8string_val = NULL; 671 } else { 672 cp->ca_tag.utf8string_len = str->utf8string_len; 673 cp->ca_tag.utf8string_val = str->utf8string_val; 674 } 675 cp->ca_minorversion = mvers; 676 cp->ca_argarray_len = arglen; 677 if (pfh == NULL) { 678 cp->ca_isputrootfh = TRUE; 679 cp->ca_opputfh.pf_opnum = OP_PUTROOTFH; 680 } else { 681 cp->ca_isputrootfh = FALSE; 682 cp->ca_opputfh.pf_opnum = OP_PUTFH; 683 cp->ca_opputfh.pf_filehandle.len = pfh->len; 684 bcopy(pfh->data, cp->ca_opputfh.pf_filehandle.data, pfh->len); 685 } 686 } 687