1 /* 2 * NFS server file handle treatment. 3 * 4 * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> 5 * Portions Copyright (C) 1999 G. Allen Morris III <gam3@acm.org> 6 * Extensive rewrite by Neil Brown <neilb@cse.unsw.edu.au> Southern-Spring 1999 7 * ... and again Southern-Winter 2001 to support export_operations 8 */ 9 10 #include <linux/exportfs.h> 11 12 #include <linux/sunrpc/svcauth_gss.h> 13 #include "nfsd.h" 14 #include "vfs.h" 15 #include "auth.h" 16 17 #define NFSDDBG_FACILITY NFSDDBG_FH 18 19 20 /* 21 * our acceptability function. 22 * if NOSUBTREECHECK, accept anything 23 * if not, require that we can walk up to exp->ex_dentry 24 * doing some checks on the 'x' bits 25 */ 26 static int nfsd_acceptable(void *expv, struct dentry *dentry) 27 { 28 struct svc_export *exp = expv; 29 int rv; 30 struct dentry *tdentry; 31 struct dentry *parent; 32 33 if (exp->ex_flags & NFSEXP_NOSUBTREECHECK) 34 return 1; 35 36 tdentry = dget(dentry); 37 while (tdentry != exp->ex_path.dentry && !IS_ROOT(tdentry)) { 38 /* make sure parents give x permission to user */ 39 int err; 40 parent = dget_parent(tdentry); 41 err = inode_permission(d_inode(parent), MAY_EXEC); 42 if (err < 0) { 43 dput(parent); 44 break; 45 } 46 dput(tdentry); 47 tdentry = parent; 48 } 49 if (tdentry != exp->ex_path.dentry) 50 dprintk("nfsd_acceptable failed at %p %pd\n", tdentry, tdentry); 51 rv = (tdentry == exp->ex_path.dentry); 52 dput(tdentry); 53 return rv; 54 } 55 56 /* Type check. The correct error return for type mismatches does not seem to be 57 * generally agreed upon. SunOS seems to use EISDIR if file isn't S_IFREG; a 58 * comment in the NFSv3 spec says this is incorrect (implementation notes for 59 * the write call). 60 */ 61 static inline __be32 62 nfsd_mode_check(struct svc_rqst *rqstp, umode_t mode, umode_t requested) 63 { 64 mode &= S_IFMT; 65 66 if (requested == 0) /* the caller doesn't care */ 67 return nfs_ok; 68 if (mode == requested) 69 return nfs_ok; 70 /* 71 * v4 has an error more specific than err_notdir which we should 72 * return in preference to err_notdir: 73 */ 74 if (rqstp->rq_vers == 4 && mode == S_IFLNK) 75 return nfserr_symlink; 76 if (requested == S_IFDIR) 77 return nfserr_notdir; 78 if (mode == S_IFDIR) 79 return nfserr_isdir; 80 return nfserr_inval; 81 } 82 83 static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp, 84 struct svc_export *exp) 85 { 86 int flags = nfsexp_flags(rqstp, exp); 87 88 /* Check if the request originated from a secure port. */ 89 if (!test_bit(RQ_SECURE, &rqstp->rq_flags) && !(flags & NFSEXP_INSECURE_PORT)) { 90 RPC_IFDEBUG(char buf[RPC_MAX_ADDRBUFLEN]); 91 dprintk("nfsd: request from insecure port %s!\n", 92 svc_print_addr(rqstp, buf, sizeof(buf))); 93 return nfserr_perm; 94 } 95 96 /* Set user creds for this exportpoint */ 97 return nfserrno(nfsd_setuser(rqstp, exp)); 98 } 99 100 static inline __be32 check_pseudo_root(struct svc_rqst *rqstp, 101 struct dentry *dentry, struct svc_export *exp) 102 { 103 if (!(exp->ex_flags & NFSEXP_V4ROOT)) 104 return nfs_ok; 105 /* 106 * v2/v3 clients have no need for the V4ROOT export--they use 107 * the mount protocl instead; also, further V4ROOT checks may be 108 * in v4-specific code, in which case v2/v3 clients could bypass 109 * them. 110 */ 111 if (!nfsd_v4client(rqstp)) 112 return nfserr_stale; 113 /* 114 * We're exposing only the directories and symlinks that have to be 115 * traversed on the way to real exports: 116 */ 117 if (unlikely(!d_is_dir(dentry) && 118 !d_is_symlink(dentry))) 119 return nfserr_stale; 120 /* 121 * A pseudoroot export gives permission to access only one 122 * single directory; the kernel has to make another upcall 123 * before granting access to anything else under it: 124 */ 125 if (unlikely(dentry != exp->ex_path.dentry)) 126 return nfserr_stale; 127 return nfs_ok; 128 } 129 130 /* 131 * Use the given filehandle to look up the corresponding export and 132 * dentry. On success, the results are used to set fh_export and 133 * fh_dentry. 134 */ 135 static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp) 136 { 137 struct knfsd_fh *fh = &fhp->fh_handle; 138 struct fid *fid = NULL, sfid; 139 struct svc_export *exp; 140 struct dentry *dentry; 141 int fileid_type; 142 int data_left = fh->fh_size/4; 143 __be32 error; 144 145 error = nfserr_stale; 146 if (rqstp->rq_vers > 2) 147 error = nfserr_badhandle; 148 if (rqstp->rq_vers == 4 && fh->fh_size == 0) 149 return nfserr_nofilehandle; 150 151 if (fh->fh_version == 1) { 152 int len; 153 154 if (--data_left < 0) 155 return error; 156 if (fh->fh_auth_type != 0) 157 return error; 158 len = key_len(fh->fh_fsid_type) / 4; 159 if (len == 0) 160 return error; 161 if (fh->fh_fsid_type == FSID_MAJOR_MINOR) { 162 /* deprecated, convert to type 3 */ 163 len = key_len(FSID_ENCODE_DEV)/4; 164 fh->fh_fsid_type = FSID_ENCODE_DEV; 165 /* 166 * struct knfsd_fh uses host-endian fields, which are 167 * sometimes used to hold net-endian values. This 168 * confuses sparse, so we must use __force here to 169 * keep it from complaining. 170 */ 171 fh->fh_fsid[0] = new_encode_dev(MKDEV(ntohl((__force __be32)fh->fh_fsid[0]), 172 ntohl((__force __be32)fh->fh_fsid[1]))); 173 fh->fh_fsid[1] = fh->fh_fsid[2]; 174 } 175 data_left -= len; 176 if (data_left < 0) 177 return error; 178 exp = rqst_exp_find(rqstp, fh->fh_fsid_type, fh->fh_fsid); 179 fid = (struct fid *)(fh->fh_fsid + len); 180 } else { 181 __u32 tfh[2]; 182 dev_t xdev; 183 ino_t xino; 184 185 if (fh->fh_size != NFS_FHSIZE) 186 return error; 187 /* assume old filehandle format */ 188 xdev = old_decode_dev(fh->ofh_xdev); 189 xino = u32_to_ino_t(fh->ofh_xino); 190 mk_fsid(FSID_DEV, tfh, xdev, xino, 0, NULL); 191 exp = rqst_exp_find(rqstp, FSID_DEV, tfh); 192 } 193 194 error = nfserr_stale; 195 if (PTR_ERR(exp) == -ENOENT) 196 return error; 197 198 if (IS_ERR(exp)) 199 return nfserrno(PTR_ERR(exp)); 200 201 if (exp->ex_flags & NFSEXP_NOSUBTREECHECK) { 202 /* Elevate privileges so that the lack of 'r' or 'x' 203 * permission on some parent directory will 204 * not stop exportfs_decode_fh from being able 205 * to reconnect a directory into the dentry cache. 206 * The same problem can affect "SUBTREECHECK" exports, 207 * but as nfsd_acceptable depends on correct 208 * access control settings being in effect, we cannot 209 * fix that case easily. 210 */ 211 struct cred *new = prepare_creds(); 212 if (!new) { 213 error = nfserrno(-ENOMEM); 214 goto out; 215 } 216 new->cap_effective = 217 cap_raise_nfsd_set(new->cap_effective, 218 new->cap_permitted); 219 put_cred(override_creds(new)); 220 put_cred(new); 221 } else { 222 error = nfsd_setuser_and_check_port(rqstp, exp); 223 if (error) 224 goto out; 225 } 226 227 /* 228 * Look up the dentry using the NFS file handle. 229 */ 230 error = nfserr_stale; 231 if (rqstp->rq_vers > 2) 232 error = nfserr_badhandle; 233 234 if (fh->fh_version != 1) { 235 sfid.i32.ino = fh->ofh_ino; 236 sfid.i32.gen = fh->ofh_generation; 237 sfid.i32.parent_ino = fh->ofh_dirino; 238 fid = &sfid; 239 data_left = 3; 240 if (fh->ofh_dirino == 0) 241 fileid_type = FILEID_INO32_GEN; 242 else 243 fileid_type = FILEID_INO32_GEN_PARENT; 244 } else 245 fileid_type = fh->fh_fileid_type; 246 247 if (fileid_type == FILEID_ROOT) 248 dentry = dget(exp->ex_path.dentry); 249 else { 250 dentry = exportfs_decode_fh(exp->ex_path.mnt, fid, 251 data_left, fileid_type, 252 nfsd_acceptable, exp); 253 } 254 if (dentry == NULL) 255 goto out; 256 if (IS_ERR(dentry)) { 257 if (PTR_ERR(dentry) != -EINVAL) 258 error = nfserrno(PTR_ERR(dentry)); 259 goto out; 260 } 261 262 if (d_is_dir(dentry) && 263 (dentry->d_flags & DCACHE_DISCONNECTED)) { 264 printk("nfsd: find_fh_dentry returned a DISCONNECTED directory: %pd2\n", 265 dentry); 266 } 267 268 fhp->fh_dentry = dentry; 269 fhp->fh_export = exp; 270 return 0; 271 out: 272 exp_put(exp); 273 return error; 274 } 275 276 /** 277 * fh_verify - filehandle lookup and access checking 278 * @rqstp: pointer to current rpc request 279 * @fhp: filehandle to be verified 280 * @type: expected type of object pointed to by filehandle 281 * @access: type of access needed to object 282 * 283 * Look up a dentry from the on-the-wire filehandle, check the client's 284 * access to the export, and set the current task's credentials. 285 * 286 * Regardless of success or failure of fh_verify(), fh_put() should be 287 * called on @fhp when the caller is finished with the filehandle. 288 * 289 * fh_verify() may be called multiple times on a given filehandle, for 290 * example, when processing an NFSv4 compound. The first call will look 291 * up a dentry using the on-the-wire filehandle. Subsequent calls will 292 * skip the lookup and just perform the other checks and possibly change 293 * the current task's credentials. 294 * 295 * @type specifies the type of object expected using one of the S_IF* 296 * constants defined in include/linux/stat.h. The caller may use zero 297 * to indicate that it doesn't care, or a negative integer to indicate 298 * that it expects something not of the given type. 299 * 300 * @access is formed from the NFSD_MAY_* constants defined in 301 * include/linux/nfsd/nfsd.h. 302 */ 303 __be32 304 fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, umode_t type, int access) 305 { 306 struct svc_export *exp; 307 struct dentry *dentry; 308 __be32 error; 309 310 dprintk("nfsd: fh_verify(%s)\n", SVCFH_fmt(fhp)); 311 312 if (!fhp->fh_dentry) { 313 error = nfsd_set_fh_dentry(rqstp, fhp); 314 if (error) 315 goto out; 316 } 317 dentry = fhp->fh_dentry; 318 exp = fhp->fh_export; 319 /* 320 * We still have to do all these permission checks, even when 321 * fh_dentry is already set: 322 * - fh_verify may be called multiple times with different 323 * "access" arguments (e.g. nfsd_proc_create calls 324 * fh_verify(...,NFSD_MAY_EXEC) first, then later (in 325 * nfsd_create) calls fh_verify(...,NFSD_MAY_CREATE). 326 * - in the NFSv4 case, the filehandle may have been filled 327 * in by fh_compose, and given a dentry, but further 328 * compound operations performed with that filehandle 329 * still need permissions checks. In the worst case, a 330 * mountpoint crossing may have changed the export 331 * options, and we may now need to use a different uid 332 * (for example, if different id-squashing options are in 333 * effect on the new filesystem). 334 */ 335 error = check_pseudo_root(rqstp, dentry, exp); 336 if (error) 337 goto out; 338 339 error = nfsd_setuser_and_check_port(rqstp, exp); 340 if (error) 341 goto out; 342 343 error = nfsd_mode_check(rqstp, d_inode(dentry)->i_mode, type); 344 if (error) 345 goto out; 346 347 /* 348 * pseudoflavor restrictions are not enforced on NLM, 349 * which clients virtually always use auth_sys for, 350 * even while using RPCSEC_GSS for NFS. 351 */ 352 if (access & NFSD_MAY_LOCK || access & NFSD_MAY_BYPASS_GSS) 353 goto skip_pseudoflavor_check; 354 /* 355 * Clients may expect to be able to use auth_sys during mount, 356 * even if they use gss for everything else; see section 2.3.2 357 * of rfc 2623. 358 */ 359 if (access & NFSD_MAY_BYPASS_GSS_ON_ROOT 360 && exp->ex_path.dentry == dentry) 361 goto skip_pseudoflavor_check; 362 363 error = check_nfsd_access(exp, rqstp); 364 if (error) 365 goto out; 366 367 skip_pseudoflavor_check: 368 /* Finally, check access permissions. */ 369 error = nfsd_permission(rqstp, exp, dentry, access); 370 371 if (error) { 372 dprintk("fh_verify: %pd2 permission failure, " 373 "acc=%x, error=%d\n", 374 dentry, 375 access, ntohl(error)); 376 } 377 out: 378 if (error == nfserr_stale) 379 nfsdstats.fh_stale++; 380 return error; 381 } 382 383 384 /* 385 * Compose a file handle for an NFS reply. 386 * 387 * Note that when first composed, the dentry may not yet have 388 * an inode. In this case a call to fh_update should be made 389 * before the fh goes out on the wire ... 390 */ 391 static void _fh_update(struct svc_fh *fhp, struct svc_export *exp, 392 struct dentry *dentry) 393 { 394 if (dentry != exp->ex_path.dentry) { 395 struct fid *fid = (struct fid *) 396 (fhp->fh_handle.fh_fsid + fhp->fh_handle.fh_size/4 - 1); 397 int maxsize = (fhp->fh_maxsize - fhp->fh_handle.fh_size)/4; 398 int subtreecheck = !(exp->ex_flags & NFSEXP_NOSUBTREECHECK); 399 400 fhp->fh_handle.fh_fileid_type = 401 exportfs_encode_fh(dentry, fid, &maxsize, subtreecheck); 402 fhp->fh_handle.fh_size += maxsize * 4; 403 } else { 404 fhp->fh_handle.fh_fileid_type = FILEID_ROOT; 405 } 406 } 407 408 /* 409 * for composing old style file handles 410 */ 411 static inline void _fh_update_old(struct dentry *dentry, 412 struct svc_export *exp, 413 struct knfsd_fh *fh) 414 { 415 fh->ofh_ino = ino_t_to_u32(d_inode(dentry)->i_ino); 416 fh->ofh_generation = d_inode(dentry)->i_generation; 417 if (d_is_dir(dentry) || 418 (exp->ex_flags & NFSEXP_NOSUBTREECHECK)) 419 fh->ofh_dirino = 0; 420 } 421 422 static bool is_root_export(struct svc_export *exp) 423 { 424 return exp->ex_path.dentry == exp->ex_path.dentry->d_sb->s_root; 425 } 426 427 static struct super_block *exp_sb(struct svc_export *exp) 428 { 429 return exp->ex_path.dentry->d_sb; 430 } 431 432 static bool fsid_type_ok_for_exp(u8 fsid_type, struct svc_export *exp) 433 { 434 switch (fsid_type) { 435 case FSID_DEV: 436 if (!old_valid_dev(exp_sb(exp)->s_dev)) 437 return 0; 438 /* FALL THROUGH */ 439 case FSID_MAJOR_MINOR: 440 case FSID_ENCODE_DEV: 441 return exp_sb(exp)->s_type->fs_flags & FS_REQUIRES_DEV; 442 case FSID_NUM: 443 return exp->ex_flags & NFSEXP_FSID; 444 case FSID_UUID8: 445 case FSID_UUID16: 446 if (!is_root_export(exp)) 447 return 0; 448 /* fall through */ 449 case FSID_UUID4_INUM: 450 case FSID_UUID16_INUM: 451 return exp->ex_uuid != NULL; 452 } 453 return 1; 454 } 455 456 457 static void set_version_and_fsid_type(struct svc_fh *fhp, struct svc_export *exp, struct svc_fh *ref_fh) 458 { 459 u8 version; 460 u8 fsid_type; 461 retry: 462 version = 1; 463 if (ref_fh && ref_fh->fh_export == exp) { 464 version = ref_fh->fh_handle.fh_version; 465 fsid_type = ref_fh->fh_handle.fh_fsid_type; 466 467 ref_fh = NULL; 468 469 switch (version) { 470 case 0xca: 471 fsid_type = FSID_DEV; 472 break; 473 case 1: 474 break; 475 default: 476 goto retry; 477 } 478 479 /* 480 * As the fsid -> filesystem mapping was guided by 481 * user-space, there is no guarantee that the filesystem 482 * actually supports that fsid type. If it doesn't we 483 * loop around again without ref_fh set. 484 */ 485 if (!fsid_type_ok_for_exp(fsid_type, exp)) 486 goto retry; 487 } else if (exp->ex_flags & NFSEXP_FSID) { 488 fsid_type = FSID_NUM; 489 } else if (exp->ex_uuid) { 490 if (fhp->fh_maxsize >= 64) { 491 if (is_root_export(exp)) 492 fsid_type = FSID_UUID16; 493 else 494 fsid_type = FSID_UUID16_INUM; 495 } else { 496 if (is_root_export(exp)) 497 fsid_type = FSID_UUID8; 498 else 499 fsid_type = FSID_UUID4_INUM; 500 } 501 } else if (!old_valid_dev(exp_sb(exp)->s_dev)) 502 /* for newer device numbers, we must use a newer fsid format */ 503 fsid_type = FSID_ENCODE_DEV; 504 else 505 fsid_type = FSID_DEV; 506 fhp->fh_handle.fh_version = version; 507 if (version) 508 fhp->fh_handle.fh_fsid_type = fsid_type; 509 } 510 511 __be32 512 fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry, 513 struct svc_fh *ref_fh) 514 { 515 /* ref_fh is a reference file handle. 516 * if it is non-null and for the same filesystem, then we should compose 517 * a filehandle which is of the same version, where possible. 518 * Currently, that means that if ref_fh->fh_handle.fh_version == 0xca 519 * Then create a 32byte filehandle using nfs_fhbase_old 520 * 521 */ 522 523 struct inode * inode = d_inode(dentry); 524 dev_t ex_dev = exp_sb(exp)->s_dev; 525 526 dprintk("nfsd: fh_compose(exp %02x:%02x/%ld %pd2, ino=%ld)\n", 527 MAJOR(ex_dev), MINOR(ex_dev), 528 (long) d_inode(exp->ex_path.dentry)->i_ino, 529 dentry, 530 (inode ? inode->i_ino : 0)); 531 532 /* Choose filehandle version and fsid type based on 533 * the reference filehandle (if it is in the same export) 534 * or the export options. 535 */ 536 set_version_and_fsid_type(fhp, exp, ref_fh); 537 538 if (ref_fh == fhp) 539 fh_put(ref_fh); 540 541 if (fhp->fh_locked || fhp->fh_dentry) { 542 printk(KERN_ERR "fh_compose: fh %pd2 not initialized!\n", 543 dentry); 544 } 545 if (fhp->fh_maxsize < NFS_FHSIZE) 546 printk(KERN_ERR "fh_compose: called with maxsize %d! %pd2\n", 547 fhp->fh_maxsize, 548 dentry); 549 550 fhp->fh_dentry = dget(dentry); /* our internal copy */ 551 fhp->fh_export = exp_get(exp); 552 553 if (fhp->fh_handle.fh_version == 0xca) { 554 /* old style filehandle please */ 555 memset(&fhp->fh_handle.fh_base, 0, NFS_FHSIZE); 556 fhp->fh_handle.fh_size = NFS_FHSIZE; 557 fhp->fh_handle.ofh_dcookie = 0xfeebbaca; 558 fhp->fh_handle.ofh_dev = old_encode_dev(ex_dev); 559 fhp->fh_handle.ofh_xdev = fhp->fh_handle.ofh_dev; 560 fhp->fh_handle.ofh_xino = 561 ino_t_to_u32(d_inode(exp->ex_path.dentry)->i_ino); 562 fhp->fh_handle.ofh_dirino = ino_t_to_u32(parent_ino(dentry)); 563 if (inode) 564 _fh_update_old(dentry, exp, &fhp->fh_handle); 565 } else { 566 fhp->fh_handle.fh_size = 567 key_len(fhp->fh_handle.fh_fsid_type) + 4; 568 fhp->fh_handle.fh_auth_type = 0; 569 570 mk_fsid(fhp->fh_handle.fh_fsid_type, 571 fhp->fh_handle.fh_fsid, 572 ex_dev, 573 d_inode(exp->ex_path.dentry)->i_ino, 574 exp->ex_fsid, exp->ex_uuid); 575 576 if (inode) 577 _fh_update(fhp, exp, dentry); 578 if (fhp->fh_handle.fh_fileid_type == FILEID_INVALID) { 579 fh_put(fhp); 580 return nfserr_opnotsupp; 581 } 582 } 583 584 return 0; 585 } 586 587 /* 588 * Update file handle information after changing a dentry. 589 * This is only called by nfsd_create, nfsd_create_v3 and nfsd_proc_create 590 */ 591 __be32 592 fh_update(struct svc_fh *fhp) 593 { 594 struct dentry *dentry; 595 596 if (!fhp->fh_dentry) 597 goto out_bad; 598 599 dentry = fhp->fh_dentry; 600 if (d_really_is_negative(dentry)) 601 goto out_negative; 602 if (fhp->fh_handle.fh_version != 1) { 603 _fh_update_old(dentry, fhp->fh_export, &fhp->fh_handle); 604 } else { 605 if (fhp->fh_handle.fh_fileid_type != FILEID_ROOT) 606 return 0; 607 608 _fh_update(fhp, fhp->fh_export, dentry); 609 if (fhp->fh_handle.fh_fileid_type == FILEID_INVALID) 610 return nfserr_opnotsupp; 611 } 612 return 0; 613 out_bad: 614 printk(KERN_ERR "fh_update: fh not verified!\n"); 615 return nfserr_serverfault; 616 out_negative: 617 printk(KERN_ERR "fh_update: %pd2 still negative!\n", 618 dentry); 619 return nfserr_serverfault; 620 } 621 622 /* 623 * Release a file handle. 624 */ 625 void 626 fh_put(struct svc_fh *fhp) 627 { 628 struct dentry * dentry = fhp->fh_dentry; 629 struct svc_export * exp = fhp->fh_export; 630 if (dentry) { 631 fh_unlock(fhp); 632 fhp->fh_dentry = NULL; 633 dput(dentry); 634 fh_clear_wcc(fhp); 635 } 636 fh_drop_write(fhp); 637 if (exp) { 638 exp_put(exp); 639 fhp->fh_export = NULL; 640 } 641 return; 642 } 643 644 /* 645 * Shorthand for dprintk()'s 646 */ 647 char * SVCFH_fmt(struct svc_fh *fhp) 648 { 649 struct knfsd_fh *fh = &fhp->fh_handle; 650 651 static char buf[80]; 652 sprintf(buf, "%d: %08x %08x %08x %08x %08x %08x", 653 fh->fh_size, 654 fh->fh_base.fh_pad[0], 655 fh->fh_base.fh_pad[1], 656 fh->fh_base.fh_pad[2], 657 fh->fh_base.fh_pad[3], 658 fh->fh_base.fh_pad[4], 659 fh->fh_base.fh_pad[5]); 660 return buf; 661 } 662 663 enum fsid_source fsid_source(struct svc_fh *fhp) 664 { 665 if (fhp->fh_handle.fh_version != 1) 666 return FSIDSOURCE_DEV; 667 switch(fhp->fh_handle.fh_fsid_type) { 668 case FSID_DEV: 669 case FSID_ENCODE_DEV: 670 case FSID_MAJOR_MINOR: 671 if (exp_sb(fhp->fh_export)->s_type->fs_flags & FS_REQUIRES_DEV) 672 return FSIDSOURCE_DEV; 673 break; 674 case FSID_NUM: 675 if (fhp->fh_export->ex_flags & NFSEXP_FSID) 676 return FSIDSOURCE_FSID; 677 break; 678 default: 679 break; 680 } 681 /* either a UUID type filehandle, or the filehandle doesn't 682 * match the export. 683 */ 684 if (fhp->fh_export->ex_flags & NFSEXP_FSID) 685 return FSIDSOURCE_FSID; 686 if (fhp->fh_export->ex_uuid) 687 return FSIDSOURCE_UUID; 688 return FSIDSOURCE_DEV; 689 } 690