diff options
Diffstat (limited to 'fs/exportfs/expfs.c')
-rw-r--r-- | fs/exportfs/expfs.c | 136 |
1 files changed, 130 insertions, 6 deletions
diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index 813011aad700..99294a23cd54 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c | |||
@@ -514,17 +514,141 @@ struct dentry *exportfs_decode_fh(struct vfsmount *mnt, struct fid *fid, | |||
514 | int (*acceptable)(void *, struct dentry *), void *context) | 514 | int (*acceptable)(void *, struct dentry *), void *context) |
515 | { | 515 | { |
516 | struct export_operations *nop = mnt->mnt_sb->s_export_op; | 516 | struct export_operations *nop = mnt->mnt_sb->s_export_op; |
517 | struct dentry *result; | 517 | struct dentry *result, *alias; |
518 | int err; | ||
518 | 519 | ||
519 | if (nop->decode_fh) { | 520 | /* |
520 | result = nop->decode_fh(mnt->mnt_sb, fid->raw, fh_len, | 521 | * Old way of doing things. Will go away soon. |
522 | */ | ||
523 | if (!nop->fh_to_dentry) { | ||
524 | if (nop->decode_fh) { | ||
525 | return nop->decode_fh(mnt->mnt_sb, fid->raw, fh_len, | ||
521 | fileid_type, acceptable, context); | 526 | fileid_type, acceptable, context); |
527 | } else { | ||
528 | return export_decode_fh(mnt->mnt_sb, fid->raw, fh_len, | ||
529 | fileid_type, acceptable, context); | ||
530 | } | ||
531 | } | ||
532 | |||
533 | /* | ||
534 | * Try to get any dentry for the given file handle from the filesystem. | ||
535 | */ | ||
536 | result = nop->fh_to_dentry(mnt->mnt_sb, fid, fh_len, fileid_type); | ||
537 | if (!result) | ||
538 | result = ERR_PTR(-ESTALE); | ||
539 | if (IS_ERR(result)) | ||
540 | return result; | ||
541 | |||
542 | if (S_ISDIR(result->d_inode->i_mode)) { | ||
543 | /* | ||
544 | * This request is for a directory. | ||
545 | * | ||
546 | * On the positive side there is only one dentry for each | ||
547 | * directory inode. On the negative side this implies that we | ||
548 | * to ensure our dentry is connected all the way up to the | ||
549 | * filesystem root. | ||
550 | */ | ||
551 | if (result->d_flags & DCACHE_DISCONNECTED) { | ||
552 | err = reconnect_path(mnt->mnt_sb, result); | ||
553 | if (err) | ||
554 | goto err_result; | ||
555 | } | ||
556 | |||
557 | if (!acceptable(context, result)) { | ||
558 | err = -EACCES; | ||
559 | goto err_result; | ||
560 | } | ||
561 | |||
562 | return result; | ||
522 | } else { | 563 | } else { |
523 | result = export_decode_fh(mnt->mnt_sb, fid->raw, fh_len, | 564 | /* |
524 | fileid_type, acceptable, context); | 565 | * It's not a directory. Life is a little more complicated. |
566 | */ | ||
567 | struct dentry *target_dir, *nresult; | ||
568 | char nbuf[NAME_MAX+1]; | ||
569 | |||
570 | /* | ||
571 | * See if either the dentry we just got from the filesystem | ||
572 | * or any alias for it is acceptable. This is always true | ||
573 | * if this filesystem is exported without the subtreecheck | ||
574 | * option. If the filesystem is exported with the subtree | ||
575 | * check option there's a fair chance we need to look at | ||
576 | * the parent directory in the file handle and make sure | ||
577 | * it's connected to the filesystem root. | ||
578 | */ | ||
579 | alias = find_acceptable_alias(result, acceptable, context); | ||
580 | if (alias) | ||
581 | return alias; | ||
582 | |||
583 | /* | ||
584 | * Try to extract a dentry for the parent directory from the | ||
585 | * file handle. If this fails we'll have to give up. | ||
586 | */ | ||
587 | err = -ESTALE; | ||
588 | if (!nop->fh_to_parent) | ||
589 | goto err_result; | ||
590 | |||
591 | target_dir = nop->fh_to_parent(mnt->mnt_sb, fid, | ||
592 | fh_len, fileid_type); | ||
593 | if (!target_dir) | ||
594 | goto err_result; | ||
595 | err = PTR_ERR(target_dir); | ||
596 | if (IS_ERR(target_dir)) | ||
597 | goto err_result; | ||
598 | |||
599 | /* | ||
600 | * And as usual we need to make sure the parent directory is | ||
601 | * connected to the filesystem root. The VFS really doesn't | ||
602 | * like disconnected directories.. | ||
603 | */ | ||
604 | err = reconnect_path(mnt->mnt_sb, target_dir); | ||
605 | if (err) { | ||
606 | dput(target_dir); | ||
607 | goto err_result; | ||
608 | } | ||
609 | |||
610 | /* | ||
611 | * Now that we've got both a well-connected parent and a | ||
612 | * dentry for the inode we're after, make sure that our | ||
613 | * inode is actually connected to the parent. | ||
614 | */ | ||
615 | err = exportfs_get_name(target_dir, nbuf, result); | ||
616 | if (!err) { | ||
617 | mutex_lock(&target_dir->d_inode->i_mutex); | ||
618 | nresult = lookup_one_len(nbuf, target_dir, | ||
619 | strlen(nbuf)); | ||
620 | mutex_unlock(&target_dir->d_inode->i_mutex); | ||
621 | if (!IS_ERR(nresult)) { | ||
622 | if (nresult->d_inode) { | ||
623 | dput(result); | ||
624 | result = nresult; | ||
625 | } else | ||
626 | dput(nresult); | ||
627 | } | ||
628 | } | ||
629 | |||
630 | /* | ||
631 | * At this point we are done with the parent, but it's pinned | ||
632 | * by the child dentry anyway. | ||
633 | */ | ||
634 | dput(target_dir); | ||
635 | |||
636 | /* | ||
637 | * And finally make sure the dentry is actually acceptable | ||
638 | * to NFSD. | ||
639 | */ | ||
640 | alias = find_acceptable_alias(result, acceptable, context); | ||
641 | if (!alias) { | ||
642 | err = -EACCES; | ||
643 | goto err_result; | ||
644 | } | ||
645 | |||
646 | return alias; | ||
525 | } | 647 | } |
526 | 648 | ||
527 | return result; | 649 | err_result: |
650 | dput(result); | ||
651 | return ERR_PTR(err); | ||
528 | } | 652 | } |
529 | EXPORT_SYMBOL_GPL(exportfs_decode_fh); | 653 | EXPORT_SYMBOL_GPL(exportfs_decode_fh); |
530 | 654 | ||