Print this page
7378 exported_lock held during nfs4 compound processing

Split Close
Expand all
Collapse all
          --- old/usr/src/uts/common/fs/nfs/nfs4_srv.c
          +++ new/usr/src/uts/common/fs/nfs/nfs4_srv.c
↓ open down ↓ 864 lines elided ↑ open up ↑
 865  865   * Used by rfs4_op_secinfo to get the security information from the
 866  866   * export structure associated with the component.
 867  867   */
 868  868  /* ARGSUSED */
 869  869  static nfsstat4
 870  870  do_rfs4_op_secinfo(struct compound_state *cs, char *nm, SECINFO4res *resp)
 871  871  {
 872  872          int error, different_export = 0;
 873  873          vnode_t *dvp, *vp;
 874  874          struct exportinfo *exi = NULL;
      875 +        struct exportinfo *oexi = NULL;
 875  876          fid_t fid;
 876  877          uint_t count, i;
 877  878          secinfo4 *resok_val;
 878  879          struct secinfo *secp;
 879  880          seconfig_t *si;
 880  881          bool_t did_traverse = FALSE;
 881  882          int dotdot, walk;
 882  883  
 883  884          dvp = cs->vp;
 884  885          dotdot = (nm[0] == '.' && nm[1] == '.' && nm[2] == '\0');
↓ open down ↓ 71 lines elided ↑ open up ↑
 956  957                          return (puterrno4(error));
 957  958                  }
 958  959                  /* remember that we had to traverse mountpoint */
 959  960                  did_traverse = TRUE;
 960  961                  different_export = 1;
 961  962          } else if (vp->v_vfsp != dvp->v_vfsp) {
 962  963                  /*
 963  964                   * If vp isn't a mountpoint and the vfs ptrs aren't the same,
 964  965                   * then vp is probably an LOFS object.  We don't need the
 965  966                   * realvp, we just need to know that we might have crossed
 966      -                 * a server fs boundary and need to call checkexport4.
      967 +                 * a server fs boundary and need to call checkexport.
 967  968                   * (LOFS lookup hides server fs mountpoints, and actually calls
 968  969                   * traverse)
 969  970                   */
 970  971                  different_export = 1;
 971  972          }
 972  973  
 973  974          /*
 974  975           * Get the export information for it.
 975  976           */
 976  977          if (different_export) {
 977  978  
 978  979                  bzero(&fid, sizeof (fid));
 979  980                  fid.fid_len = MAXFIDSZ;
 980  981                  error = vop_fid_pseudo(vp, &fid);
 981  982                  if (error) {
 982  983                          VN_RELE(vp);
 983  984                          return (puterrno4(error));
 984  985                  }
 985  986  
 986  987                  if (dotdot)
 987      -                        exi = nfs_vptoexi(NULL, vp, cs->cr, &walk, NULL, TRUE);
      988 +                        oexi = nfs_vptoexi(NULL, vp, cs->cr, &walk, NULL, TRUE);
 988  989                  else
 989      -                        exi = checkexport4(&vp->v_vfsp->vfs_fsid, &fid, vp);
      990 +                        oexi = checkexport(&vp->v_vfsp->vfs_fsid, &fid, vp);
 990  991  
 991      -                if (exi == NULL) {
      992 +                if (oexi == NULL) {
 992  993                          if (did_traverse == TRUE) {
 993  994                                  /*
 994  995                                   * If this vnode is a mounted-on vnode,
 995  996                                   * but the mounted-on file system is not
 996  997                                   * exported, send back the secinfo for
 997  998                                   * the exported node that the mounted-on
 998  999                                   * vnode lives in.
 999 1000                                   */
1000 1001                                  exi = cs->exi;
1001 1002                          } else {
1002 1003                                  VN_RELE(vp);
1003 1004                                  return (puterrno4(EACCES));
1004 1005                          }
     1006 +                } else {
     1007 +                        exi = oexi;
1005 1008                  }
1006 1009          } else {
1007 1010                  exi = cs->exi;
1008 1011          }
1009 1012          ASSERT(exi != NULL);
1010 1013  
1011 1014  
1012 1015          /*
1013 1016           * Create the secinfo result based on the security information
1014 1017           * from the exportinfo structure (exi).
1015 1018           *
1016 1019           * Return all flavors for a pseudo node.
1017 1020           * For a real export node, return the flavor that the client
1018 1021           * has access with.
1019 1022           */
1020      -        ASSERT(RW_LOCK_HELD(&exported_lock));
     1023 +        rw_enter(&exported_lock, RW_READER);
1021 1024          if (PSEUDO(exi)) {
1022 1025                  count = exi->exi_export.ex_seccnt; /* total sec count */
1023 1026                  resok_val = kmem_alloc(count * sizeof (secinfo4), KM_SLEEP);
1024 1027                  secp = exi->exi_export.ex_secinfo;
1025 1028  
1026 1029                  for (i = 0; i < count; i++) {
1027 1030                          si = &secp[i].s_secinfo;
1028 1031                          resok_val[i].flavor = si->sc_rpcnum;
1029 1032                          if (resok_val[i].flavor == RPCSEC_GSS) {
1030 1033                                  rpcsec_gss_info *info;
↓ open down ↓ 72 lines elided ↑ open up ↑
1103 1106                                  }
1104 1107                                  k++;
1105 1108                          }
1106 1109                          if (k >= ret_cnt)
1107 1110                                  break;
1108 1111                  }
1109 1112                  resp->SECINFO4resok_len = ret_cnt;
1110 1113                  resp->SECINFO4resok_val = resok_val;
1111 1114                  kmem_free(flavor_list, count * sizeof (int));
1112 1115          }
1113      -
     1116 +        rw_exit(&exported_lock);
     1117 +        if (oexi)
     1118 +                exi_rele(oexi);
1114 1119          VN_RELE(vp);
1115 1120          return (NFS4_OK);
1116 1121  }
1117 1122  
1118 1123  /*
1119 1124   * SECINFO (Operation 33): Obtain required security information on
1120 1125   * the component name in the format of (security-mechanism-oid, qop, service)
1121 1126   * triplets.
1122 1127   */
1123 1128  /* ARGSUSED */
↓ open down ↓ 1480 lines elided ↑ open up ↑
2604 2609   * Used by rfs4_op_lookup and rfs4_op_lookupp to do the actual work.
2605 2610   */
2606 2611  
2607 2612  /* ARGSUSED */
2608 2613  static nfsstat4
2609 2614  do_rfs4_op_lookup(char *nm, struct svc_req *req, struct compound_state *cs)
2610 2615  {
2611 2616          int error;
2612 2617          int different_export = 0;
2613 2618          vnode_t *vp, *pre_tvp = NULL, *oldvp = NULL;
2614      -        struct exportinfo *exi = NULL, *pre_exi = NULL;
     2619 +        struct exportinfo *exi = NULL, *pre_exi = NULL, *oexi = NULL;
2615 2620          nfsstat4 stat;
2616 2621          fid_t fid;
2617 2622          int attrdir, dotdot, walk;
2618 2623          bool_t is_newvp = FALSE;
2619 2624  
2620 2625          if (cs->vp->v_flag & V_XATTRDIR) {
2621 2626                  attrdir = 1;
2622 2627                  ASSERT(get_fh4_flag(&cs->fh, FH4_ATTRDIR));
2623 2628          } else {
2624 2629                  attrdir = 0;
↓ open down ↓ 73 lines elided ↑ open up ↑
2698 2703  
2699 2704          /*
2700 2705           * If it's a mountpoint, then traverse it.
2701 2706           */
2702 2707          if (vn_ismntpt(vp)) {
2703 2708                  pre_exi = cs->exi;      /* save pre-traversed exportinfo */
2704 2709                  pre_tvp = vp;           /* save pre-traversed vnode     */
2705 2710  
2706 2711                  /*
2707 2712                   * hold pre_tvp to counteract rele by traverse.  We will
2708      -                 * need pre_tvp below if checkexport4 fails
     2713 +                 * need pre_tvp below if checkexport fails
2709 2714                   */
2710 2715                  VN_HOLD(pre_tvp);
2711 2716                  if ((error = traverse(&vp)) != 0) {
2712 2717                          VN_RELE(vp);
2713 2718                          VN_RELE(pre_tvp);
2714 2719                          return (puterrno4(error));
2715 2720                  }
2716 2721                  different_export = 1;
2717 2722          } else if (vp->v_vfsp != cs->vp->v_vfsp) {
2718 2723                  /*
↓ open down ↓ 15 lines elided ↑ open up ↑
2734 2739                  if (error) {
2735 2740                          VN_RELE(vp);
2736 2741                          if (pre_tvp)
2737 2742                                  VN_RELE(pre_tvp);
2738 2743                          return (puterrno4(error));
2739 2744                  }
2740 2745  
2741 2746                  if (dotdot)
2742 2747                          exi = nfs_vptoexi(NULL, vp, cs->cr, &walk, NULL, TRUE);
2743 2748                  else
2744      -                        exi = checkexport4(&vp->v_vfsp->vfs_fsid, &fid, vp);
     2749 +                        exi = checkexport(&vp->v_vfsp->vfs_fsid, &fid, vp);
2745 2750  
2746 2751                  if (exi == NULL) {
2747 2752                          if (pre_tvp) {
2748 2753                                  /*
2749 2754                                   * If this vnode is a mounted-on vnode,
2750 2755                                   * but the mounted-on file system is not
2751 2756                                   * exported, send back the filehandle for
2752 2757                                   * the mounted-on vnode, not the root of
2753 2758                                   * the mounted-on file system.
2754 2759                                   */
2755 2760                                  VN_RELE(vp);
2756 2761                                  vp = pre_tvp;
2757 2762                                  exi = pre_exi;
     2763 +                                if (exi)
     2764 +                                        exi_hold(exi);
2758 2765                          } else {
2759 2766                                  VN_RELE(vp);
2760 2767                                  return (puterrno4(EACCES));
2761 2768                          }
2762 2769                  } else if (pre_tvp) {
2763 2770                          /* we're done with pre_tvp now. release extra hold */
2764 2771                          VN_RELE(pre_tvp);
2765 2772                  }
2766 2773  
     2774 +                if (cs->exi)
     2775 +                        exi_rele(cs->exi);
2767 2776                  cs->exi = exi;
2768 2777  
2769 2778                  /*
2770 2779                   * Now we do a checkauth4. The reason is that
2771 2780                   * this client/user may not have access to the new
2772 2781                   * exported file system, and if he does,
2773 2782                   * the client/user may be mapped to a different uid.
2774 2783                   *
2775 2784                   * We start with a new cr, because the checkauth4 done
2776 2785                   * in the PUT*FH operation over wrote the cred's uid,
↓ open down ↓ 660 lines elided ↑ open up ↑
3437 3446          if (exi_public == exi_root) {
3438 3447                  /*
3439 3448                   * No filesystem is actually shared public, so we default
3440 3449                   * to exi_root. In this case, we must check whether root
3441 3450                   * is exported.
3442 3451                   */
3443 3452                  fh_fmtp = (nfs_fh4_fmt_t *)cs->fh.nfs_fh4_val;
3444 3453  
3445 3454                  /*
3446 3455                   * if root filesystem is exported, the exportinfo struct that we
3447      -                 * should use is what checkexport4 returns, because root_exi is
     3456 +                 * should use is what checkexport returns, because root_exi is
3448 3457                   * actually a mostly empty struct.
3449 3458                   */
3450      -                exi = checkexport4(&fh_fmtp->fh4_fsid,
     3459 +                exi = checkexport(&fh_fmtp->fh4_fsid,
3451 3460                      (fid_t *)&fh_fmtp->fh4_xlen, NULL);
3452      -                cs->exi = ((exi != NULL) ? exi : exi_public);
     3461 +                if (exi) {
     3462 +                        cs->exi = exi;
     3463 +                } else {
     3464 +                        exi_hold(exi_public);
     3465 +                        cs->exi = exi_public;
     3466 +                }
3453 3467          } else {
3454 3468                  /*
3455 3469                   * it's a properly shared filesystem
3456 3470                   */
     3471 +                exi_hold(exi_public);
3457 3472                  cs->exi = exi_public;
3458 3473          }
3459 3474  
3460 3475          if (is_system_labeled()) {
3461 3476                  bslabel_t *clabel;
3462 3477  
3463 3478                  ASSERT(req->rq_label != NULL);
3464 3479                  clabel = req->rq_label;
3465 3480                  DTRACE_PROBE2(tx__rfs4__log__info__opputpubfh__clabel, char *,
3466 3481                      "got client label from request(1)",
3467 3482                      struct svc_req *, req);
3468 3483                  if (!blequal(&l_admin_low->tsl_label, clabel)) {
3469 3484                          if (!do_rfs_label_check(clabel, vp, DOMINANCE_CHECK,
3470 3485                              cs->exi)) {
3471 3486                                  *cs->statusp = resp->status =
3472 3487                                      NFS4ERR_SERVERFAULT;
     3488 +                                if (sav_exi)
     3489 +                                        exi_rele(sav_exi);
3473 3490                                  goto out;
3474 3491                          }
3475 3492                  }
3476 3493          }
3477 3494  
3478 3495          VN_HOLD(vp);
3479 3496          cs->vp = vp;
3480 3497  
3481 3498          if ((resp->status = call_checkauth4(cs, req)) != NFS4_OK) {
3482 3499                  VN_RELE(cs->vp);
3483 3500                  cs->vp = NULL;
     3501 +                exi_rele(cs->exi);
3484 3502                  cs->exi = sav_exi;
3485 3503                  goto out;
3486 3504          }
     3505 +        if (sav_exi)
     3506 +                exi_rele(sav_exi);
3487 3507  
3488 3508          *cs->statusp = resp->status = NFS4_OK;
3489 3509  out:
3490 3510          DTRACE_NFSV4_2(op__putpubfh__done, struct compound_state *, cs,
3491 3511              PUTPUBFH4res *, resp);
3492 3512  }
3493 3513  
3494 3514  /*
3495 3515   * XXX - issue with put*fh operations. Suppose /export/home is exported.
3496 3516   * Suppose an NFS client goes to mount /export/home/joe. If /export, home,
↓ open down ↓ 32 lines elided ↑ open up ↑
3529 3549                  cs->cr = NULL;
3530 3550          }
3531 3551  
3532 3552  
3533 3553          if (args->object.nfs_fh4_len < NFS_FH4_LEN) {
3534 3554                  *cs->statusp = resp->status = NFS4ERR_BADHANDLE;
3535 3555                  goto out;
3536 3556          }
3537 3557  
3538 3558          fh_fmtp = (nfs_fh4_fmt_t *)args->object.nfs_fh4_val;
3539      -        cs->exi = checkexport4(&fh_fmtp->fh4_fsid, (fid_t *)&fh_fmtp->fh4_xlen,
     3559 +        if (cs->exi)
     3560 +                exi_rele(cs->exi);
     3561 +        cs->exi = checkexport(&fh_fmtp->fh4_fsid, (fid_t *)&fh_fmtp->fh4_xlen,
3540 3562              NULL);
3541 3563  
3542 3564          if (cs->exi == NULL) {
3543 3565                  *cs->statusp = resp->status = NFS4ERR_STALE;
3544 3566                  goto out;
3545 3567          }
3546 3568  
3547 3569          cs->cr = crdup(cs->basecr);
3548 3570  
3549 3571          ASSERT(cs->cr != NULL);
↓ open down ↓ 53 lines elided ↑ open up ↑
3603 3625          }
3604 3626  
3605 3627          /*
3606 3628           * Then use the root fsid & fid it to find out if it's exported
3607 3629           *
3608 3630           * If the server root isn't exported directly, then
3609 3631           * it should at least be a pseudo export based on
3610 3632           * one or more exports further down in the server's
3611 3633           * file tree.
3612 3634           */
3613      -        exi = checkexport4(&rootdir->v_vfsp->vfs_fsid, &fid, NULL);
     3635 +        exi = checkexport(&rootdir->v_vfsp->vfs_fsid, &fid, NULL);
3614 3636          if (exi == NULL || exi->exi_export.ex_flags & EX_PUBLIC) {
3615 3637                  NFS4_DEBUG(rfs4_debug,
3616 3638                      (CE_WARN, "rfs4_op_putrootfh: export check failure"));
3617 3639                  *cs->statusp = resp->status = NFS4ERR_SERVERFAULT;
     3640 +                if (exi)
     3641 +                        exi_rele(exi);
3618 3642                  goto out;
3619 3643          }
3620 3644  
3621 3645          /*
3622 3646           * Now make a filehandle based on the root
3623 3647           * export and root vnode.
3624 3648           */
3625 3649          error = makefh4(&cs->fh, rootdir, exi);
3626 3650          if (error != 0) {
3627 3651                  *cs->statusp = resp->status = puterrno4(error);
     3652 +                exi_rele(exi);
3628 3653                  goto out;
3629 3654          }
3630 3655  
3631 3656          sav_exi = cs->exi;
3632 3657          cs->exi = exi;
3633 3658  
3634 3659          VN_HOLD(rootdir);
3635 3660          cs->vp = rootdir;
3636 3661  
3637 3662          if ((resp->status = call_checkauth4(cs, req)) != NFS4_OK) {
3638 3663                  VN_RELE(rootdir);
3639 3664                  cs->vp = NULL;
     3665 +                exi_rele(exi);
3640 3666                  cs->exi = sav_exi;
3641 3667                  goto out;
3642 3668          }
     3669 +        if (sav_exi)
     3670 +                exi_rele(sav_exi);
3643 3671  
3644 3672          *cs->statusp = resp->status = NFS4_OK;
3645 3673          cs->deleg = FALSE;
3646 3674  out:
3647 3675          DTRACE_NFSV4_2(op__putrootfh__done, struct compound_state *, cs,
3648 3676              PUTROOTFH4res *, resp);
3649 3677  }
3650 3678  
3651 3679  /*
3652 3680   * set_rdattr_params sets up the variables used to manage what information
↓ open down ↓ 1157 lines elided ↑ open up ↑
4810 4838          /* No need to check cs->access - we are not accessing any object */
4811 4839          if ((cs->saved_vp == NULL) || (cs->saved_fh.nfs_fh4_val == NULL)) {
4812 4840                  *cs->statusp = resp->status = NFS4ERR_RESTOREFH;
4813 4841                  goto out;
4814 4842          }
4815 4843          if (cs->vp != NULL) {
4816 4844                  VN_RELE(cs->vp);
4817 4845          }
4818 4846          cs->vp = cs->saved_vp;
4819 4847          cs->saved_vp = NULL;
     4848 +        if (cs->exi)
     4849 +                exi_rele(cs->exi);
4820 4850          cs->exi = cs->saved_exi;
     4851 +        if (cs->exi)
     4852 +                exi_hold(cs->exi);
4821 4853          nfs_fh4_copy(&cs->saved_fh, &cs->fh);
4822 4854          *cs->statusp = resp->status = NFS4_OK;
4823 4855          cs->deleg = FALSE;
4824 4856  
4825 4857  out:
4826 4858          DTRACE_NFSV4_2(op__restorefh__done, struct compound_state *, cs,
4827 4859              RESTOREFH4res *, resp);
4828 4860  }
4829 4861  
4830 4862  /* ARGSUSED */
↓ open down ↓ 8 lines elided ↑ open up ↑
4839 4871          /* No need to check cs->access - we are not accessing any object */
4840 4872          if (cs->vp == NULL) {
4841 4873                  *cs->statusp = resp->status = NFS4ERR_NOFILEHANDLE;
4842 4874                  goto out;
4843 4875          }
4844 4876          if (cs->saved_vp != NULL) {
4845 4877                  VN_RELE(cs->saved_vp);
4846 4878          }
4847 4879          cs->saved_vp = cs->vp;
4848 4880          VN_HOLD(cs->saved_vp);
     4881 +        if (cs->saved_exi)
     4882 +                exi_rele(cs->saved_exi);
4849 4883          cs->saved_exi = cs->exi;
     4884 +        if (cs->saved_exi)
     4885 +                exi_hold(cs->saved_exi);
4850 4886          /*
4851 4887           * since SAVEFH is fairly rare, don't alloc space for its fh
4852 4888           * unless necessary.
4853 4889           */
4854 4890          if (cs->saved_fh.nfs_fh4_val == NULL) {
4855 4891                  cs->saved_fh.nfs_fh4_val = kmem_alloc(NFS4_FHSIZE, KM_SLEEP);
4856 4892          }
4857 4893          nfs_fh4_copy(&cs->fh, &cs->saved_fh);
4858 4894          *cs->statusp = resp->status = NFS4_OK;
4859 4895  
↓ open down ↓ 944 lines elided ↑ open up ↑
5804 5840          resp->array_len = args->array_len;
5805 5841          resp->array = kmem_zalloc(args->array_len * sizeof (nfs_resop4),
5806 5842              KM_SLEEP);
5807 5843  
5808 5844          cs.basecr = cr;
5809 5845  
5810 5846          DTRACE_NFSV4_2(compound__start, struct compound_state *, &cs,
5811 5847              COMPOUND4args *, args);
5812 5848  
5813 5849          /*
5814      -         * For now, NFS4 compound processing must be protected by
5815      -         * exported_lock because it can access more than one exportinfo
5816      -         * per compound and share/unshare can now change multiple
5817      -         * exinfo structs.  The NFS2/3 code only refs 1 exportinfo
5818      -         * per proc (excluding public exinfo), and exi_count design
5819      -         * is sufficient to protect concurrent execution of NFS2/3
5820      -         * ops along with unexport.  This lock will be removed as
5821      -         * part of the NFSv4 phase 2 namespace redesign work.
5822      -         */
5823      -        rw_enter(&exported_lock, RW_READER);
5824      -
5825      -        /*
5826 5850           * If this is the first compound we've seen, we need to start all
5827 5851           * new instances' grace periods.
5828 5852           */
5829 5853          if (rfs4_seen_first_compound == 0) {
5830 5854                  rfs4_grace_start_new();
5831 5855                  /*
5832 5856                   * This must be set after rfs4_grace_start_new(), otherwise
5833 5857                   * another thread could proceed past here before the former
5834 5858                   * is finished.
5835 5859                   */
↓ open down ↓ 48 lines elided ↑ open up ↑
5884 5908                          bcopy(resp->array,
5885 5909                              new_res, (i+1) * sizeof (nfs_resop4));
5886 5910                          kmem_free(resp->array,
5887 5911                              args->array_len * sizeof (nfs_resop4));
5888 5912  
5889 5913                          resp->array_len =  i + 1;
5890 5914                          resp->array = new_res;
5891 5915                  }
5892 5916          }
5893 5917  
5894      -        rw_exit(&exported_lock);
5895 5918  
5896 5919          DTRACE_NFSV4_2(compound__done, struct compound_state *, &cs,
5897 5920              COMPOUND4res *, resp);
5898 5921  
     5922 +        if (cs.exi)
     5923 +                exi_rele(cs.exi);
     5924 +        if (cs.saved_exi)
     5925 +                exi_rele(cs.saved_exi);
5899 5926          if (cs.vp)
5900 5927                  VN_RELE(cs.vp);
5901 5928          if (cs.saved_vp)
5902 5929                  VN_RELE(cs.saved_vp);
5903 5930          if (cs.saved_fh.nfs_fh4_val)
5904 5931                  kmem_free(cs.saved_fh.nfs_fh4_val, NFS4_FHSIZE);
5905 5932  
5906 5933          if (cs.basecr)
5907 5934                  crfree(cs.basecr);
5908 5935          if (cs.cr)
↓ open down ↓ 1580 lines elided ↑ open up ↑
7489 7516                      oo->ro_reply_fh.nfs_fh4_val) {
7490 7517                          /*
7491 7518                           * If this is a replay, we must restore the
7492 7519                           * current filehandle/vp to that of what was
7493 7520                           * returned originally.  Try our best to do
7494 7521                           * it.
7495 7522                           */
7496 7523                          nfs_fh4_fmt_t *fh_fmtp =
7497 7524                              (nfs_fh4_fmt_t *)oo->ro_reply_fh.nfs_fh4_val;
7498 7525  
7499      -                        cs->exi = checkexport4(&fh_fmtp->fh4_fsid,
     7526 +                        if (cs->exi)
     7527 +                                exi_rele(cs->exi);
     7528 +                        cs->exi = checkexport(&fh_fmtp->fh4_fsid,
7500 7529                              (fid_t *)&fh_fmtp->fh4_xlen, NULL);
7501 7530  
7502 7531                          if (cs->exi == NULL) {
7503 7532                                  resp->status = NFS4ERR_STALE;
7504 7533                                  goto finish;
7505 7534                          }
7506 7535  
7507 7536                          VN_RELE(cs->vp);
7508 7537  
7509 7538                          cs->vp = nfs4_fhtovp(&oo->ro_reply_fh, cs->exi,
↓ open down ↓ 2378 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX