Print this page
OS-3463 expose process argv through procfs
OS-3207 in lx zone, 'ps auxww' does not show full cmdline for processes
Reviewed by: Robert Mustacchi <rm@joyent.com>
Reviewed by: Patrick Mooney <patrick.mooney@joyent.com>
OS-3383 lx brand: node.js test test-setproctitle.js fails
OS-15 add procfs equivalent to prctl(PR_SET_NAME)

Split Close
Expand all
Collapse all
          --- old/usr/src/uts/common/fs/proc/prvnops.c
          +++ new/usr/src/uts/common/fs/proc/prvnops.c
↓ open down ↓ 13 lines elided ↑ open up ↑
  14   14   * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15   15   * If applicable, add the following below this CDDL HEADER, with the
  16   16   * fields enclosed by brackets "[]" replaced with your own identifying
  17   17   * information: Portions Copyright [yyyy] [name of copyright owner]
  18   18   *
  19   19   * CDDL HEADER END
  20   20   */
  21   21  
  22   22  /*
  23   23   * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
  24      - * Copyright (c) 2014, Joyent, Inc. All rights reserved.
       24 + * Copyright 2015, Joyent, Inc.
  25   25   */
  26   26  
  27   27  /*      Copyright (c) 1984,      1986, 1987, 1988, 1989 AT&T    */
  28   28  /*        All Rights Reserved   */
  29   29  
  30   30  #include <sys/types.h>
  31   31  #include <sys/param.h>
  32   32  #include <sys/time.h>
  33   33  #include <sys/cred.h>
  34   34  #include <sys/policy.h>
↓ open down ↓ 54 lines elided ↑ open up ↑
  89   89  
  90   90  #define PRDIRSIZE       14
  91   91  struct prdirect {
  92   92          ushort_t        d_ino;
  93   93          char            d_name[PRDIRSIZE];
  94   94  };
  95   95  
  96   96  #define PRSDSIZE        (sizeof (struct prdirect))
  97   97  
  98   98  /*
       99 + * Maximum length of the /proc/$$/argv file:
      100 + */
      101 +int prmaxargvlen = 4096;
      102 +
      103 +/*
  99  104   * Directory characteristics.
 100  105   */
 101  106  typedef struct prdirent {
 102  107          ino64_t         d_ino;          /* "inode number" of entry */
 103  108          off64_t         d_off;          /* offset of disk directory entry */
 104  109          unsigned short  d_reclen;       /* length of this record */
 105  110          char            d_name[14];     /* name of file */
 106  111  } prdirent_t;
 107  112  
 108  113  /*
↓ open down ↓ 50 lines elided ↑ open up ↑
 159  164          { PR_PRIV,      24 * sizeof (prdirent_t), sizeof (prdirent_t),
 160  165                  "priv" },
 161  166          { PR_PATHDIR,   25 * sizeof (prdirent_t), sizeof (prdirent_t),
 162  167                  "path" },
 163  168          { PR_CTDIR,     26 * sizeof (prdirent_t), sizeof (prdirent_t),
 164  169                  "contracts" },
 165  170  #if defined(__x86)
 166  171          { PR_LDT,       27 * sizeof (prdirent_t), sizeof (prdirent_t),
 167  172                  "ldt" },
 168  173  #endif
      174 +        { PR_ARGV,      28 * sizeof (prdirent_t), sizeof (prdirent_t),
      175 +                "argv" },
 169  176  };
 170  177  
 171  178  #define NPIDDIRFILES    (sizeof (piddir) / sizeof (piddir[0]) - 2)
 172  179  
 173  180  /*
 174  181   * Contents of a /proc/<pid>/lwp/<lwpid> directory.
 175  182   */
 176  183  static prdirent_t lwpiddir[] = {
 177  184          { PR_LWPIDDIR,   1 * sizeof (prdirent_t), sizeof (prdirent_t),
 178  185                  "." },
↓ open down ↓ 396 lines elided ↑ open up ↑
 575  582  /*
 576  583   * Array of read functions, indexed by /proc file type.
 577  584   */
 578  585  static int pr_read_inval(), pr_read_as(), pr_read_status(),
 579  586          pr_read_lstatus(), pr_read_psinfo(), pr_read_lpsinfo(),
 580  587          pr_read_map(), pr_read_rmap(), pr_read_xmap(),
 581  588          pr_read_cred(), pr_read_sigact(), pr_read_auxv(),
 582  589  #if defined(__x86)
 583  590          pr_read_ldt(),
 584  591  #endif
      592 +        pr_read_argv(),
 585  593          pr_read_usage(), pr_read_lusage(), pr_read_pagedata(),
 586  594          pr_read_watch(), pr_read_lwpstatus(), pr_read_lwpsinfo(),
 587  595          pr_read_lwpusage(), pr_read_xregs(), pr_read_priv(),
 588  596          pr_read_spymaster(),
 589  597  #if defined(__sparc)
 590  598          pr_read_gwindows(), pr_read_asrs(),
 591  599  #endif
 592  600          pr_read_piddir(), pr_read_pidfile(), pr_read_opagedata();
 593  601  
 594  602  static int (*pr_read_function[PR_NFILES])() = {
↓ open down ↓ 8 lines elided ↑ open up ↑
 603  611          pr_read_lpsinfo,        /* /proc/<pid>/lpsinfo                  */
 604  612          pr_read_map,            /* /proc/<pid>/map                      */
 605  613          pr_read_rmap,           /* /proc/<pid>/rmap                     */
 606  614          pr_read_xmap,           /* /proc/<pid>/xmap                     */
 607  615          pr_read_cred,           /* /proc/<pid>/cred                     */
 608  616          pr_read_sigact,         /* /proc/<pid>/sigact                   */
 609  617          pr_read_auxv,           /* /proc/<pid>/auxv                     */
 610  618  #if defined(__x86)
 611  619          pr_read_ldt,            /* /proc/<pid>/ldt                      */
 612  620  #endif
      621 +        pr_read_argv,           /* /proc/<pid>/argv                     */
 613  622          pr_read_usage,          /* /proc/<pid>/usage                    */
 614  623          pr_read_lusage,         /* /proc/<pid>/lusage                   */
 615  624          pr_read_pagedata,       /* /proc/<pid>/pagedata                 */
 616  625          pr_read_watch,          /* /proc/<pid>/watch                    */
 617  626          pr_read_inval,          /* /proc/<pid>/cwd                      */
 618  627          pr_read_inval,          /* /proc/<pid>/root                     */
 619  628          pr_read_inval,          /* /proc/<pid>/fd                       */
 620  629          pr_read_inval,          /* /proc/<pid>/fd/nn                    */
 621  630          pr_read_inval,          /* /proc/<pid>/object                   */
 622  631          pr_read_inval,          /* /proc/<pid>/object/xxx               */
↓ open down ↓ 42 lines elided ↑ open up ↑
 665  674          count -= uiop->uio_offset;
 666  675          if (count > 0 && uiop->uio_offset >= 0) {
 667  676                  error = uiomove((char *)base + uiop->uio_offset,
 668  677                      count, UIO_READ, uiop);
 669  678          }
 670  679  
 671  680          return (error);
 672  681  }
 673  682  
 674  683  static int
      684 +pr_read_argv(prnode_t *pnp, uio_t *uiop)
      685 +{
      686 +        char *args;
      687 +        int error;
      688 +        size_t asz = prmaxargvlen, sz;
      689 +
      690 +        /*
      691 +         * Allocate a scratch buffer for collection of the process arguments.
      692 +         */
      693 +        args = kmem_alloc(asz, KM_SLEEP);
      694 +
      695 +        ASSERT(pnp->pr_type == PR_ARGV);
      696 +
      697 +        if ((error = prlock(pnp, ZNO)) != 0) {
      698 +                kmem_free(args, asz);
      699 +                return (error);
      700 +        }
      701 +
      702 +        if ((error = prreadargv(pnp->pr_common->prc_proc, args, asz,
      703 +            &sz)) != 0) {
      704 +                prunlock(pnp);
      705 +                kmem_free(args, asz);
      706 +                return (error);
      707 +        }
      708 +
      709 +        prunlock(pnp);
      710 +
      711 +        error = pr_uioread(args, sz, uiop);
      712 +
      713 +        kmem_free(args, asz);
      714 +
      715 +        return (error);
      716 +}
      717 +
      718 +static int
 675  719  pr_read_as(prnode_t *pnp, uio_t *uiop)
 676  720  {
 677  721          int error;
 678  722  
 679  723          ASSERT(pnp->pr_type == PR_AS);
 680  724  
 681  725          if ((error = prlock(pnp, ZNO)) == 0) {
 682  726                  proc_t *p = pnp->pr_common->prc_proc;
 683  727                  struct as *as = p->p_as;
 684  728  
↓ open down ↓ 1075 lines elided ↑ open up ↑
1760 1804          pr_read_lpsinfo_32,     /* /proc/<pid>/lpsinfo                  */
1761 1805          pr_read_map_32,         /* /proc/<pid>/map                      */
1762 1806          pr_read_rmap_32,        /* /proc/<pid>/rmap                     */
1763 1807          pr_read_xmap_32,        /* /proc/<pid>/xmap                     */
1764 1808          pr_read_cred,           /* /proc/<pid>/cred                     */
1765 1809          pr_read_sigact_32,      /* /proc/<pid>/sigact                   */
1766 1810          pr_read_auxv_32,        /* /proc/<pid>/auxv                     */
1767 1811  #if defined(__x86)
1768 1812          pr_read_ldt,            /* /proc/<pid>/ldt                      */
1769 1813  #endif
     1814 +        pr_read_argv,           /* /proc/<pid>/argv                     */
1770 1815          pr_read_usage_32,       /* /proc/<pid>/usage                    */
1771 1816          pr_read_lusage_32,      /* /proc/<pid>/lusage                   */
1772 1817          pr_read_pagedata_32,    /* /proc/<pid>/pagedata                 */
1773 1818          pr_read_watch_32,       /* /proc/<pid>/watch                    */
1774 1819          pr_read_inval,          /* /proc/<pid>/cwd                      */
1775 1820          pr_read_inval,          /* /proc/<pid>/root                     */
1776 1821          pr_read_inval,          /* /proc/<pid>/fd                       */
1777 1822          pr_read_inval,          /* /proc/<pid>/fd/nn                    */
1778 1823          pr_read_inval,          /* /proc/<pid>/object                   */
1779 1824          pr_read_inval,          /* /proc/<pid>/object/xxx               */
↓ open down ↓ 899 lines elided ↑ open up ↑
2679 2724           */
2680 2725          if (curproc->p_model == DATAMODEL_LP64)
2681 2726                  return (pr_read_function[pnp->pr_type](pnp, uiop));
2682 2727          else
2683 2728                  return (pr_read_function_32[pnp->pr_type](pnp, uiop));
2684 2729  #else
2685 2730          return (pr_read_function[pnp->pr_type](pnp, uiop));
2686 2731  #endif
2687 2732  }
2688 2733  
     2734 +/*
     2735 + * We make pr_write_psinfo_fname() somewhat simpler by asserting at compile
     2736 + * time that PRFNSZ has the same definition as MAXCOMLEN.
     2737 + */
     2738 +#if PRFNSZ != MAXCOMLEN
     2739 +#error PRFNSZ/MAXCOMLEN mismatch
     2740 +#endif
     2741 +
     2742 +static int
     2743 +pr_write_psinfo_fname(prnode_t *pnp, uio_t *uiop)
     2744 +{
     2745 +        char fname[PRFNSZ];
     2746 +        int offset = offsetof(psinfo_t, pr_fname), error;
     2747 +
     2748 +#ifdef _SYSCALL32_IMPL
     2749 +        if (curproc->p_model != DATAMODEL_LP64)
     2750 +                offset = offsetof(psinfo32_t, pr_fname);
     2751 +#endif
     2752 +
     2753 +        /*
     2754 +         * If this isn't a write to pr_fname (or if the size doesn't match
     2755 +         * PRFNSZ) return.
     2756 +         */
     2757 +        if (uiop->uio_offset != offset || uiop->uio_resid != PRFNSZ)
     2758 +                return (0);
     2759 +
     2760 +        if ((error = uiomove(fname, PRFNSZ, UIO_WRITE, uiop)) != 0)
     2761 +                return (error);
     2762 +
     2763 +        fname[PRFNSZ - 1] = '\0';
     2764 +
     2765 +        if ((error = prlock(pnp, ZNO)) != 0)
     2766 +                return (error);
     2767 +
     2768 +        bcopy(fname, pnp->pr_common->prc_proc->p_user.u_comm, PRFNSZ);
     2769 +
     2770 +        prunlock(pnp);
     2771 +
     2772 +        return (0);
     2773 +}
     2774 +
     2775 +/*
     2776 + * We make pr_write_psinfo_psargs() somewhat simpler by asserting at compile
     2777 + * time that PRARGSZ has the same definition as PSARGSZ.
     2778 + */
     2779 +#if PRARGSZ != PSARGSZ
     2780 +#error PRARGSZ/PSARGSZ mismatch
     2781 +#endif
     2782 +
     2783 +static int
     2784 +pr_write_psinfo_psargs(prnode_t *pnp, uio_t *uiop)
     2785 +{
     2786 +        char psargs[PRARGSZ];
     2787 +        int offset = offsetof(psinfo_t, pr_psargs), error;
     2788 +
     2789 +#ifdef _SYSCALL32_IMPL
     2790 +        if (curproc->p_model != DATAMODEL_LP64)
     2791 +                offset = offsetof(psinfo32_t, pr_psargs);
     2792 +#endif
     2793 +
     2794 +        /*
     2795 +         * If this isn't a write to pr_psargs (or if the size doesn't match
     2796 +         * PRARGSZ) return.
     2797 +         */
     2798 +        if (uiop->uio_offset != offset || uiop->uio_resid != PRARGSZ)
     2799 +                return (0);
     2800 +
     2801 +        if ((error = uiomove(psargs, PRARGSZ, UIO_WRITE, uiop)) != 0)
     2802 +                return (error);
     2803 +
     2804 +        psargs[PRARGSZ - 1] = '\0';
     2805 +
     2806 +        if ((error = prlock(pnp, ZNO)) != 0)
     2807 +                return (error);
     2808 +
     2809 +        bcopy(psargs, pnp->pr_common->prc_proc->p_user.u_psargs, PRARGSZ);
     2810 +
     2811 +        prunlock(pnp);
     2812 +
     2813 +        return (0);
     2814 +}
     2815 +
     2816 +int
     2817 +pr_write_psinfo(prnode_t *pnp, uio_t *uiop)
     2818 +{
     2819 +        int error;
     2820 +
     2821 +        if ((error = pr_write_psinfo_fname(pnp, uiop)) != 0)
     2822 +                return (error);
     2823 +
     2824 +        if ((error = pr_write_psinfo_psargs(pnp, uiop)) != 0)
     2825 +                return (error);
     2826 +
     2827 +        return (0);
     2828 +}
     2829 +
     2830 +
2689 2831  /* ARGSUSED */
2690 2832  static int
2691 2833  prwrite(vnode_t *vp, uio_t *uiop, int ioflag, cred_t *cr, caller_context_t *ct)
2692 2834  {
2693 2835          prnode_t *pnp = VTOP(vp);
2694 2836          int old = 0;
2695 2837          int error;
2696 2838          ssize_t resid;
2697 2839  
2698 2840          ASSERT(pnp->pr_type < PR_NFILES);
↓ open down ↓ 58 lines elided ↑ open up ↑
2757 2899                  error = prwritectl(vp, uiop, CRED());
2758 2900  #endif
2759 2901                  /*
2760 2902                   * This hack makes sure that the EINTR is passed
2761 2903                   * all the way back to the caller's write() call.
2762 2904                   */
2763 2905                  if (error == EINTR)
2764 2906                          uiop->uio_resid = resid;
2765 2907                  return (error);
2766 2908  
     2909 +        case PR_PSINFO:
     2910 +                return (pr_write_psinfo(pnp, uiop));
     2911 +
2767 2912          default:
2768 2913                  return ((vp->v_type == VDIR)? EISDIR : EBADF);
2769 2914          }
2770 2915          /* NOTREACHED */
2771 2916  }
2772 2917  
2773 2918  static int
2774 2919  prgetattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr,
2775 2920          caller_context_t *ct)
2776 2921  {
↓ open down ↓ 263 lines elided ↑ open up ↑
3040 3185                  vap->va_size = prgetprivsize();
3041 3186                  break;
3042 3187          case PR_SIGACT:
3043 3188                  nsig = PROC_IS_BRANDED(curproc)? BROP(curproc)->b_nsig : NSIG;
3044 3189                  vap->va_size = (nsig-1) *
3045 3190                      PR_OBJSIZE(struct sigaction32, struct sigaction);
3046 3191                  break;
3047 3192          case PR_AUXV:
3048 3193                  vap->va_size = __KERN_NAUXV_IMPL * PR_OBJSIZE(auxv32_t, auxv_t);
3049 3194                  break;
     3195 +        case PR_ARGV:
     3196 +                if ((p->p_flag & SSYS) || p->p_as == &kas) {
     3197 +                        vap->va_size = PSARGSZ;
     3198 +                } else {
     3199 +                        vap->va_size = prmaxargvlen;
     3200 +                }
     3201 +                break;
3050 3202  #if defined(__x86)
3051 3203          case PR_LDT:
3052 3204                  mutex_exit(&p->p_lock);
3053 3205                  mutex_enter(&p->p_ldtlock);
3054 3206                  vap->va_size = prnldt(p) * sizeof (struct ssd);
3055 3207                  mutex_exit(&p->p_ldtlock);
3056 3208                  mutex_enter(&p->p_lock);
3057 3209                  break;
3058 3210  #endif
3059 3211          case PR_USAGE:
↓ open down ↓ 155 lines elided ↑ open up ↑
3215 3367                  return (VOP_ACCESS(rvp, mode, flags, cr, ct));
3216 3368  
3217 3369          case PR_PSINFO:         /* these files can be read by anyone */
3218 3370          case PR_LPSINFO:
3219 3371          case PR_LWPSINFO:
3220 3372          case PR_LWPDIR:
3221 3373          case PR_LWPIDDIR:
3222 3374          case PR_USAGE:
3223 3375          case PR_LUSAGE:
3224 3376          case PR_LWPUSAGE:
     3377 +        case PR_ARGV:
3225 3378                  p = pr_p_lock(pnp);
3226 3379                  mutex_exit(&pr_pidlock);
3227 3380                  if (p == NULL)
3228 3381                          return (ENOENT);
3229 3382                  prunlock(pnp);
3230 3383                  break;
3231 3384  
3232 3385          default:
3233 3386                  /*
3234 3387                   * Except for the world-readable files above,
↓ open down ↓ 65 lines elided ↑ open up ↑
3300 3453          pr_lookup_notdir,       /* /proc/<pid>/lpsinfo                  */
3301 3454          pr_lookup_notdir,       /* /proc/<pid>/map                      */
3302 3455          pr_lookup_notdir,       /* /proc/<pid>/rmap                     */
3303 3456          pr_lookup_notdir,       /* /proc/<pid>/xmap                     */
3304 3457          pr_lookup_notdir,       /* /proc/<pid>/cred                     */
3305 3458          pr_lookup_notdir,       /* /proc/<pid>/sigact                   */
3306 3459          pr_lookup_notdir,       /* /proc/<pid>/auxv                     */
3307 3460  #if defined(__x86)
3308 3461          pr_lookup_notdir,       /* /proc/<pid>/ldt                      */
3309 3462  #endif
     3463 +        pr_lookup_notdir,       /* /proc/<pid>/argv                     */
3310 3464          pr_lookup_notdir,       /* /proc/<pid>/usage                    */
3311 3465          pr_lookup_notdir,       /* /proc/<pid>/lusage                   */
3312 3466          pr_lookup_notdir,       /* /proc/<pid>/pagedata                 */
3313 3467          pr_lookup_notdir,       /* /proc/<pid>/watch                    */
3314 3468          pr_lookup_notdir,       /* /proc/<pid>/cwd                      */
3315 3469          pr_lookup_notdir,       /* /proc/<pid>/root                     */
3316 3470          pr_lookup_fddir,        /* /proc/<pid>/fd                       */
3317 3471          pr_lookup_notdir,       /* /proc/<pid>/fd/nn                    */
3318 3472          pr_lookup_objectdir,    /* /proc/<pid>/object                   */
3319 3473          pr_lookup_notdir,       /* /proc/<pid>/object/xxx               */
↓ open down ↓ 1219 lines elided ↑ open up ↑
4539 4693          case PR_LWPCTL:
4540 4694                  pnp->pr_mode = 0200;    /* write-only by owner only */
4541 4695                  break;
4542 4696  
4543 4697          case PR_PIDFILE:
4544 4698          case PR_LWPIDFILE:
4545 4699                  pnp->pr_mode = 0600;    /* read-write by owner only */
4546 4700                  break;
4547 4701  
4548 4702          case PR_PSINFO:
     4703 +                pnp->pr_mode = 0644;    /* readable by all + owner can write */
     4704 +                break;
     4705 +
4549 4706          case PR_LPSINFO:
4550 4707          case PR_LWPSINFO:
4551 4708          case PR_USAGE:
4552 4709          case PR_LUSAGE:
4553 4710          case PR_LWPUSAGE:
     4711 +        case PR_ARGV:
4554 4712                  pnp->pr_mode = 0444;    /* read-only by all */
4555 4713                  break;
4556 4714  
4557 4715          default:
4558 4716                  pnp->pr_mode = 0400;    /* read-only by owner only */
4559 4717                  break;
4560 4718          }
4561 4719          vn_exists(vp);
4562 4720          return (pnp);
4563 4721  }
↓ open down ↓ 85 lines elided ↑ open up ↑
4649 4807          pr_readdir_notdir,      /* /proc/<pid>/lpsinfo                  */
4650 4808          pr_readdir_notdir,      /* /proc/<pid>/map                      */
4651 4809          pr_readdir_notdir,      /* /proc/<pid>/rmap                     */
4652 4810          pr_readdir_notdir,      /* /proc/<pid>/xmap                     */
4653 4811          pr_readdir_notdir,      /* /proc/<pid>/cred                     */
4654 4812          pr_readdir_notdir,      /* /proc/<pid>/sigact                   */
4655 4813          pr_readdir_notdir,      /* /proc/<pid>/auxv                     */
4656 4814  #if defined(__x86)
4657 4815          pr_readdir_notdir,      /* /proc/<pid>/ldt                      */
4658 4816  #endif
     4817 +        pr_readdir_notdir,      /* /proc/<pid>/argv                     */
4659 4818          pr_readdir_notdir,      /* /proc/<pid>/usage                    */
4660 4819          pr_readdir_notdir,      /* /proc/<pid>/lusage                   */
4661 4820          pr_readdir_notdir,      /* /proc/<pid>/pagedata                 */
4662 4821          pr_readdir_notdir,      /* /proc/<pid>/watch                    */
4663 4822          pr_readdir_notdir,      /* /proc/<pid>/cwd                      */
4664 4823          pr_readdir_notdir,      /* /proc/<pid>/root                     */
4665 4824          pr_readdir_fddir,       /* /proc/<pid>/fd                       */
4666 4825          pr_readdir_notdir,      /* /proc/<pid>/fd/nn                    */
4667 4826          pr_readdir_objectdir,   /* /proc/<pid>/object                   */
4668 4827          pr_readdir_notdir,      /* /proc/<pid>/object/xxx               */
↓ open down ↓ 129 lines elided ↑ open up ↑
4798 4957              uiop->uio_resid >= sizeof (prdirent_t) &&
4799 4958              dirp < &piddir[NPIDDIRFILES+2];
4800 4959              uiop->uio_offset = off + sizeof (prdirent_t), dirp++) {
4801 4960                  off = uiop->uio_offset;
4802 4961                  if (zombie) {
4803 4962                          switch (dirp->d_ino) {
4804 4963                          case PR_PIDDIR:
4805 4964                          case PR_PROCDIR:
4806 4965                          case PR_PSINFO:
4807 4966                          case PR_USAGE:
     4967 +                        case PR_ARGV:
4808 4968                                  break;
4809 4969                          default:
4810 4970                                  continue;
4811 4971                          }
4812 4972                  }
4813 4973                  bcopy(dirp, &dirent, sizeof (prdirent_t));
4814 4974                  if (dirent.d_ino == PR_PROCDIR)
4815 4975                          dirent.d_ino = PRROOTINO;
4816 4976                  else
4817 4977                          dirent.d_ino = pmkino(0, pnp->pr_pcommon->prc_slot,
↓ open down ↓ 1242 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX