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