Print this page
4500 mptsas_hash_traverse() is unsafe, leads to missing devices
Reviewed by: Hans Rosenfeld <hans.rosenfeld@nexenta.com>
Approved by: Albert Lee <trisk@nexenta.com>
4499 ::mptsas should show slot/enclosure for targets
Reviewed by: Hans Rosenfeld <hans.rosenfeld@nexenta.com>
Approved by: Albert Lee <trisk@nexenta.com>

Split Close
Expand all
Collapse all
          --- old/usr/src/cmd/mdb/common/modules/mpt_sas/mpt_sas.c
          +++ new/usr/src/cmd/mdb/common/modules/mpt_sas/mpt_sas.c
↓ open down ↓ 15 lines elided ↑ open up ↑
  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   * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  23   23   * Use is subject to license terms.
  24   24   */
  25   25  
       26 +/*
       27 + * Copyright 2014 Joyent, Inc.  All rights reserved.
       28 + */
       29 +
  26   30  #include <limits.h>
  27   31  #include <sys/mdb_modapi.h>
  28   32  #include <sys/sysinfo.h>
  29   33  #include <sys/sunmdi.h>
       34 +#include <sys/list.h>
  30   35  #include <sys/scsi/scsi.h>
  31   36  
  32   37  #pragma pack(1)
  33   38  #include <sys/scsi/adapters/mpt_sas/mpi/mpi2_type.h>
  34   39  #include <sys/scsi/adapters/mpt_sas/mpi/mpi2.h>
  35   40  #include <sys/scsi/adapters/mpt_sas/mpi/mpi2_cnfg.h>
  36   41  #include <sys/scsi/adapters/mpt_sas/mpi/mpi2_init.h>
  37   42  #include <sys/scsi/adapters/mpt_sas/mpi/mpi2_ioc.h>
  38   43  #include <sys/scsi/adapters/mpt_sas/mpi/mpi2_sas.h>
  39   44  #include <sys/scsi/adapters/mpt_sas/mpi/mpi2_raid.h>
  40   45  #include <sys/scsi/adapters/mpt_sas/mpi/mpi2_tool.h>
  41   46  #pragma pack()
  42   47  
  43   48  #include <sys/scsi/adapters/mpt_sas/mptsas_var.h>
       49 +#include <sys/scsi/adapters/mpt_sas/mptsas_hash.h>
  44   50  
  45   51  struct {
  46      -
  47   52          int     value;
  48   53          char    *text;
  49   54  } devinfo_array[] = {
  50   55          { MPI2_SAS_DEVICE_INFO_SEP,             "SEP" },
  51   56          { MPI2_SAS_DEVICE_INFO_ATAPI_DEVICE,    "ATAPI device" },
  52   57          { MPI2_SAS_DEVICE_INFO_LSI_DEVICE,      "LSI device" },
  53   58          { MPI2_SAS_DEVICE_INFO_DIRECT_ATTACH,   "direct attach" },
  54   59          { MPI2_SAS_DEVICE_INFO_SSP_TARGET,      "SSP tgt" },
  55   60          { MPI2_SAS_DEVICE_INFO_STP_TARGET,      "STP tgt" },
  56   61          { MPI2_SAS_DEVICE_INFO_SMP_TARGET,      "SMP tgt" },
  57   62          { MPI2_SAS_DEVICE_INFO_SATA_DEVICE,     "SATA dev" },
  58   63          { MPI2_SAS_DEVICE_INFO_SSP_INITIATOR,   "SSP init" },
  59   64          { MPI2_SAS_DEVICE_INFO_STP_INITIATOR,   "STP init" },
  60   65          { MPI2_SAS_DEVICE_INFO_SMP_INITIATOR,   "SMP init" },
  61   66          { MPI2_SAS_DEVICE_INFO_SATA_HOST,       "SATA host" }
  62   67  };
  63   68  
  64      -static int
  65      -atoi(const char *p)
  66      -{
  67      -        int n;
  68      -        int c = *p++;
  69      -
  70      -        for (n = 0; c >= '0' && c <= '9'; c = *p++) {
  71      -                n *= 10; /* two steps to avoid unnecessary overflow */
  72      -                n += '0' - c; /* accum neg to avoid surprises at MAX */
  73      -        }
  74      -        return (-n);
  75      -}
  76      -
  77   69  int
  78   70  construct_path(uintptr_t addr, char *result)
  79   71  {
  80   72          struct  dev_info        d;
  81   73          char    devi_node[PATH_MAX];
  82   74          char    devi_addr[PATH_MAX];
  83   75  
  84   76          if (mdb_vread(&d, sizeof (d), addr) == -1) {
  85   77                  mdb_warn("couldn't read dev_info");
  86   78                  return (DCMD_ERR);
↓ open down ↓ 22 lines elided ↑ open up ↑
 109  101          char    dev_path[PATH_MAX];
 110  102          char    string[PATH_MAX];
 111  103          int     mdi_target = 0, mdi_lun = 0;
 112  104          int     target = *(int *)cbdata;
 113  105  
 114  106          if (mdb_vread(&pi, sizeof (pi), addr) == -1) {
 115  107                  mdb_warn("couldn't read mdi_pathinfo");
 116  108                  return (DCMD_ERR);
 117  109          }
 118  110          mdb_readstr(string, sizeof (string), (uintptr_t)pi.pi_addr);
 119      -        mdi_target = atoi(string);
 120      -        mdi_lun = atoi(strchr(string, ',')+1);
      111 +        mdi_target = (int)mdb_strtoull(string);
      112 +        mdi_lun = (int)mdb_strtoull(strchr(string, ',') + 1);
 121  113          if (target != mdi_target)
 122  114                  return (0);
 123  115  
 124  116          if (mdb_vread(&c, sizeof (c), (uintptr_t)pi.pi_client) == -1) {
 125  117                  mdb_warn("couldn't read mdi_client");
 126  118                  return (-1);
 127  119          }
 128  120  
 129  121          *dev_path = NULL;
 130  122          if (construct_path((uintptr_t)c.ct_dip, dev_path) != DCMD_OK)
↓ open down ↓ 75 lines elided ↑ open up ↑
 206  198              pkt.pkt_address.a_target, pkt.pkt_address.a_lun);
 207  199  
 208  200          for (j = 0; j < m->cmd_cdblen; j++)
 209  201                  mdb_printf("%02x ", cdb[j]);
 210  202  
 211  203          mdb_printf("]\n");
 212  204  }
 213  205  
 214  206  
 215  207  void
 216      -display_ports(struct mptsas m)
      208 +display_ports(struct mptsas *mp)
 217  209  {
 218  210          int i;
 219  211          mdb_printf("\n");
 220  212          mdb_printf("phy number and port mapping table\n");
 221  213          for (i = 0; i < MPTSAS_MAX_PHYS; i++) {
 222      -                if (m.m_phy_info[i].attached_devhdl) {
      214 +                if (mp->m_phy_info[i].attached_devhdl) {
 223  215                          mdb_printf("phy %x --> port %x, phymask %x,"
 224      -                        "attached_devhdl %x\n", i, m.m_phy_info[i].port_num,
 225      -                            m.m_phy_info[i].phy_mask,
 226      -                            m.m_phy_info[i].attached_devhdl);
      216 +                        "attached_devhdl %x\n", i, mp->m_phy_info[i].port_num,
      217 +                            mp->m_phy_info[i].phy_mask,
      218 +                            mp->m_phy_info[i].attached_devhdl);
 227  219                  }
 228  220          }
 229  221          mdb_printf("\n");
 230  222  }
      223 +
      224 +static uintptr_t
      225 +klist_head(list_t *lp, uintptr_t klp)
      226 +{
      227 +        if ((uintptr_t)lp->list_head.list_next ==
      228 +            klp + offsetof(struct list, list_head))
      229 +                return (NULL);
      230 +
      231 +        return ((uintptr_t)(((char *)lp->list_head.list_next) -
      232 +            lp->list_offset));
      233 +}
      234 +
      235 +static uintptr_t
      236 +klist_next(list_t *lp, uintptr_t klp, void *op)
      237 +{
      238 +        /* LINTED E_BAD_PTR_CAST_ALIG */
      239 +        struct list_node *np = (struct list_node *)(((char *)op) +
      240 +            lp->list_offset);
      241 +
      242 +        if ((uintptr_t)np->list_next == klp + offsetof(struct list, list_head))
      243 +                return (NULL);
      244 +
      245 +        return (((uintptr_t)(np->list_next)) - lp->list_offset);
      246 +}
      247 +
 231  248  static void *
 232      -hash_traverse(mptsas_hash_table_t *hashtab, int pos, int alloc_size)
      249 +krefhash_first(uintptr_t khp)
 233  250  {
 234      -        mptsas_hash_node_t *this = NULL;
 235      -        mptsas_hash_node_t h;
 236      -        void *ret = NULL;
      251 +        refhash_t mh;
      252 +        uintptr_t klp;
      253 +        uintptr_t kop;
      254 +        void *rp;
 237  255  
 238      -        if (pos == MPTSAS_HASH_FIRST) {
 239      -                hashtab->line = 0;
 240      -                hashtab->cur = NULL;
 241      -                this = hashtab->head[0];
 242      -        } else {
 243      -                if (hashtab->cur == NULL) {
 244      -                        return (NULL);
 245      -                } else {
 246      -                        mdb_vread(&h, sizeof (h), (uintptr_t)hashtab->cur);
 247      -                        this = h.next;
 248      -                }
 249      -        }
      256 +        mdb_vread(&mh, sizeof (mh), khp);
      257 +        klp = klist_head(&mh.rh_objs, khp + offsetof(refhash_t, rh_objs));
      258 +        if (klp == 0)
      259 +                return (NULL);
 250  260  
 251      -        while (this == NULL) {
 252      -                hashtab->line++;
 253      -                if (hashtab->line >= MPTSAS_HASH_ARRAY_SIZE) {
 254      -                        /* the traverse reaches the end */
 255      -                        hashtab->cur = NULL;
 256      -                        return (NULL);
 257      -                } else {
 258      -                        this = hashtab->head[hashtab->line];
 259      -                }
      261 +        kop = klp - mh.rh_link_off;
      262 +        rp = mdb_alloc(mh.rh_obj_size, UM_SLEEP);
      263 +        mdb_vread(rp, mh.rh_obj_size, kop);
      264 +
      265 +        return (rp);
      266 +}
      267 +
      268 +static void *
      269 +krefhash_next(uintptr_t khp, void *op)
      270 +{
      271 +        refhash_t mh;
      272 +        void *prev = op;
      273 +        refhash_link_t *lp;
      274 +        uintptr_t klp;
      275 +        uintptr_t kop;
      276 +        refhash_link_t ml;
      277 +        void *rp;
      278 +
      279 +        mdb_vread(&mh, sizeof (mh), khp);
      280 +        /* LINTED E_BAD_PTR_CAST_ALIG */
      281 +        lp = (refhash_link_t *)(((char *)(op)) + mh.rh_link_off);
      282 +        ml = *lp;
      283 +        while ((klp = klist_next(&mh.rh_objs,
      284 +            khp + offsetof(refhash_t, rh_objs), &ml)) != NULL) {
      285 +                mdb_vread(&ml, sizeof (ml), klp);
      286 +                if (!(ml.rhl_flags & RHL_F_DEAD))
      287 +                        break;
 260  288          }
 261      -        hashtab->cur = this;
 262  289  
 263      -        if (mdb_vread(&h, sizeof (h), (uintptr_t)this) == -1) {
 264      -                mdb_warn("couldn't read hashtab");
      290 +        if (klp == 0) {
      291 +                mdb_free(prev, mh.rh_obj_size);
 265  292                  return (NULL);
 266  293          }
 267      -        ret = mdb_alloc(alloc_size, UM_SLEEP);
 268      -        if (mdb_vread(ret, alloc_size, (uintptr_t)h.data) == -1) {
 269      -                mdb_warn("couldn't read hashdata");
 270      -                return (NULL);
 271      -        }
 272      -        return (ret);
      294 +
      295 +        kop = klp - mh.rh_link_off;
      296 +        rp = mdb_alloc(mh.rh_obj_size, UM_SLEEP);
      297 +        mdb_vread(rp, mh.rh_obj_size, kop);
      298 +
      299 +        mdb_free(prev, mh.rh_obj_size);
      300 +        return (rp);
 273  301  }
      302 +
 274  303  void
 275      -display_targets(struct mptsas_slots *s)
      304 +display_targets(struct mptsas *mp)
 276  305  {
 277  306          mptsas_target_t *ptgt;
 278  307          mptsas_smp_t *psmp;
 279  308  
 280  309          mdb_printf("\n");
 281  310          mdb_printf("The SCSI target information\n");
 282      -        ptgt = (mptsas_target_t *)hash_traverse(&s->m_tgttbl,
 283      -            MPTSAS_HASH_FIRST, sizeof (mptsas_target_t));
 284      -        while (ptgt != NULL) {
      311 +        for (ptgt = (mptsas_target_t *)krefhash_first((uintptr_t)mp->m_targets);
      312 +            ptgt != NULL;
      313 +            ptgt = krefhash_next((uintptr_t)mp->m_targets, ptgt)) {
 285  314                  mdb_printf("\n");
 286  315                  mdb_printf("devhdl %x, sasaddress %"PRIx64", phymask %x,"
 287      -                    "devinfo %x\n", ptgt->m_devhdl, ptgt->m_sas_wwn,
 288      -                    ptgt->m_phymask, ptgt->m_deviceinfo);
 289      -                mdb_printf("throttle %x, dr_flag %x, m_t_ncmds %x\n",
 290      -                    ptgt->m_t_throttle, ptgt->m_dr_flag, ptgt->m_t_ncmds);
 291      -
 292      -                mdb_free(ptgt, sizeof (mptsas_target_t));
 293      -                ptgt = (mptsas_target_t *)hash_traverse(
 294      -                    &s->m_tgttbl, MPTSAS_HASH_NEXT, sizeof (mptsas_target_t));
      316 +                    "devinfo %x\n", ptgt->m_devhdl, ptgt->m_addr.mta_wwn,
      317 +                    ptgt->m_addr.mta_phymask, ptgt->m_deviceinfo);
      318 +                mdb_printf("throttle %x, dr_flag %x, m_t_ncmds %x, "
      319 +                    "enclosure %x, slot_num %x\n", ptgt->m_t_throttle,
      320 +                    ptgt->m_dr_flag, ptgt->m_t_ncmds, ptgt->m_enclosure,
      321 +                    ptgt->m_slot_num);
 295  322          }
      323 +
 296  324          mdb_printf("\n");
 297  325          mdb_printf("The smp child information\n");
 298      -        psmp = (mptsas_smp_t *)hash_traverse(&s->m_smptbl,
 299      -            MPTSAS_HASH_FIRST, sizeof (mptsas_smp_t));
 300      -        while (psmp != NULL) {
      326 +        for (psmp = (mptsas_smp_t *)krefhash_first(
      327 +            (uintptr_t)mp->m_smp_targets);
      328 +            psmp != NULL;
      329 +            psmp = krefhash_next((uintptr_t)mp->m_smp_targets, psmp)) {
 301  330                  mdb_printf("\n");
 302  331                  mdb_printf("devhdl %x, sasaddress %"PRIx64", phymask %x \n",
 303      -                    psmp->m_devhdl, psmp->m_sasaddr, psmp->m_phymask);
 304      -
 305      -                mdb_free(psmp, sizeof (mptsas_smp_t));
 306      -                psmp = (mptsas_smp_t *)hash_traverse(
 307      -                    &s->m_smptbl, MPTSAS_HASH_NEXT, sizeof (mptsas_smp_t));
      332 +                    psmp->m_devhdl, psmp->m_addr.mta_wwn,
      333 +                    psmp->m_addr.mta_phymask);
 308  334          }
 309  335          mdb_printf("\n");
 310  336  #if 0
 311  337          mdb_printf("targ         wwn      ncmds throttle "
 312  338              "dr_flag  timeout  dups\n");
 313  339          mdb_printf("-------------------------------"
 314  340              "--------------------------------\n");
 315  341          for (i = 0; i < MPTSAS_MAX_TARGETS; i++) {
 316      -                if (s->m_target[i].m_sas_wwn || s->m_target[i].m_deviceinfo) {
      342 +                if (s->m_target[i].m_addr.mta_wwn ||
      343 +                    s->m_target[i].m_deviceinfo) {
 317  344                          mdb_printf("%4d ", i);
 318      -                        if (s->m_target[i].m_sas_wwn)
      345 +                        if (s->m_target[i].m_addr.mta_wwn)
 319  346                                  mdb_printf("%"PRIx64" ",
 320      -                                    s->m_target[i].m_sas_wwn);
      347 +                                    s->m_target[i].m_addr.mta_wwn);
 321  348                          mdb_printf("%3d", s->m_target[i].m_t_ncmds);
 322  349                          switch (s->m_target[i].m_t_throttle) {
 323  350                                  case QFULL_THROTTLE:
 324  351                                          mdb_printf("   QFULL ");
 325  352                                          break;
 326  353                                  case DRAIN_THROTTLE:
 327  354                                          mdb_printf("   DRAIN ");
 328  355                                          break;
 329  356                                  case HOLD_THROTTLE:
 330  357                                          mdb_printf("    HOLD ");
↓ open down ↓ 89 lines elided ↑ open up ↑
 420  447          struct  mptsas_cmd              c, *q, *slots;
 421  448          int     header_output = 0;
 422  449          int     rv = DCMD_OK;
 423  450          int     slots_in_use = 0;
 424  451          int     tcmds = 0;
 425  452          int     mismatch = 0;
 426  453          int     wq, dq;
 427  454          int     ncmds = 0;
 428  455          ulong_t saved_indent;
 429  456  
 430      -        nslots = s->m_n_slots;
      457 +        nslots = s->m_n_normal;
 431  458  
 432  459          slots = mdb_alloc(sizeof (mptsas_cmd_t) * nslots, UM_SLEEP);
 433  460  
 434  461          for (i = 0; i < nslots; i++)
 435  462                  if (s->m_slot[i]) {
 436  463                          slots_in_use++;
 437  464                          if (mdb_vread(&slots[i], sizeof (mptsas_cmd_t),
 438  465                              (uintptr_t)s->m_slot[i]) == -1) {
 439  466                                  mdb_warn("couldn't read slot");
 440  467                                  s->m_slot[i] = NULL;
↓ open down ↓ 32 lines elided ↑ open up ↑
 473  500  
 474  501          mdb_printf("%7d ", m.m_ncmds);
 475  502          mdb_printf("%s", (m.m_ncmds == slots_in_use ? "  " : "!="));
 476  503          mdb_printf("%3d               total %3d ", slots_in_use, ncmds);
 477  504          mdb_printf("%s", (tcmds == ncmds ? "     " : "   !="));
 478  505          mdb_printf("%3d %2d %2d\n", tcmds, wq, dq);
 479  506  
 480  507          saved_indent = mdb_dec_indent(0);
 481  508          mdb_dec_indent(saved_indent);
 482  509  
 483      -        for (i = 0; i < s->m_n_slots; i++)
      510 +        for (i = 0; i < s->m_n_normal; i++)
 484  511                  if (s->m_slot[i]) {
 485  512                          if (!header_output) {
 486  513                                  mdb_printf("\n");
 487  514                                  mdb_printf("mptsas_cmd          slot cmd_slot "
 488  515                                      "cmd_flags cmd_pkt_flags scsi_pkt      "
 489  516                                      "  targ,lun [ pkt_cdbp ...\n");
 490  517                                  mdb_printf("-------------------------------"
 491  518                                      "--------------------------------------"
 492  519                                      "--------------------------------------"
 493  520                                      "------\n");
↓ open down ↓ 101 lines elided ↑ open up ↑
 595  622  exit:
 596  623          mdb_free(slots, sizeof (mptsas_cmd_t) * nslots);
 597  624          return (rv);
 598  625  #endif
 599  626          mdb_printf("\n");
 600  627          mdb_printf("The slot information is not implemented yet\n");
 601  628          return (0);
 602  629  }
 603  630  
 604  631  void
 605      -display_deviceinfo(struct mptsas m)
      632 +display_deviceinfo(struct mptsas *mp)
 606  633  {
 607  634          char    device_path[PATH_MAX];
 608  635  
 609  636          *device_path = 0;
 610      -        if (construct_path((uintptr_t)m.m_dip, device_path) != DCMD_OK) {
      637 +        if (construct_path((uintptr_t)mp->m_dip, device_path) != DCMD_OK) {
 611  638                  strcpy(device_path, "couldn't determine device path");
 612  639          }
 613  640  
 614  641          mdb_printf("\n");
 615  642          mdb_printf("Path in device tree %s\n", device_path);
 616  643  #if 0
 617  644          mdb_printf("base_wwid          phys "
 618  645              "mptid prodid  devid        revid   ssid\n");
 619  646          mdb_printf("-----------------------------"
 620  647              "----------------------------------\n");
↓ open down ↓ 64 lines elided ↑ open up ↑
 685  712                  }
 686  713          }
 687  714  #endif
 688  715          mdb_printf("\n");
 689  716  }
 690  717  
 691  718  static int
 692  719  mptsas_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
 693  720  {
 694  721          struct mptsas           m;
 695      -        struct  mptsas_slots    *s;
      722 +        struct mptsas_slots     *s;
 696  723  
 697  724          int                     nslots;
 698  725          int                     slot_size = 0;
 699  726          uint_t                  verbose = FALSE;
 700  727          uint_t                  target_info = FALSE;
 701  728          uint_t                  slot_info = FALSE;
 702  729          uint_t                  device_info = FALSE;
 703  730          uint_t                  port_info = FALSE;
 704  731          int                     rv = DCMD_OK;
 705  732          void                    *mptsas_state;
↓ open down ↓ 30 lines elided ↑ open up ↑
 736  763          s = mdb_alloc(sizeof (mptsas_slots_t), UM_SLEEP);
 737  764  
 738  765          if (mdb_vread(s, sizeof (mptsas_slots_t),
 739  766              (uintptr_t)m.m_active) == -1) {
 740  767                  mdb_warn("couldn't read small mptsas_slots_t at 0x%p",
 741  768                      m.m_active);
 742  769                  mdb_free(s, sizeof (mptsas_slots_t));
 743  770                  return (DCMD_ERR);
 744  771          }
 745  772  
 746      -        nslots = s->m_n_slots;
      773 +        nslots = s->m_n_normal;
 747  774  
 748  775          mdb_free(s, sizeof (mptsas_slots_t));
 749  776  
 750  777          slot_size = sizeof (mptsas_slots_t) +
 751  778              (sizeof (mptsas_cmd_t *) * (nslots-1));
 752  779  
 753  780          s = mdb_alloc(slot_size, UM_SLEEP);
 754  781  
 755  782          if (mdb_vread(s, slot_size, (uintptr_t)m.m_active) == -1) {
 756  783                  mdb_warn("couldn't read large mptsas_slots_t at 0x%p",
↓ open down ↓ 32 lines elided ↑ open up ↑
 789  816                          mdb_printf("OFF=D3 ");
 790  817                          break;
 791  818                  default:
 792  819                          mdb_printf("INVALD ");
 793  820          }
 794  821          mdb_printf("\n");
 795  822  
 796  823          mdb_inc_indent(17);
 797  824  
 798  825          if (target_info)
 799      -                display_targets(s);
      826 +                display_targets(&m);
 800  827  
 801  828          if (port_info)
 802      -                display_ports(m);
      829 +                display_ports(&m);
 803  830  
 804  831          if (device_info)
 805      -                display_deviceinfo(m);
      832 +                display_deviceinfo(&m);
 806  833  
 807  834          if (slot_info)
 808  835                  display_slotinfo();
 809  836  
 810  837          mdb_dec_indent(17);
 811  838  
 812  839          mdb_free(s, slot_size);
 813  840  
 814  841          return (rv);
 815  842  }
 816      -/*
 817      - * Only -t is implemented now, will add more later when the driver is stable
 818      - */
      843 +
 819  844  void
 820  845  mptsas_help()
 821  846  {
 822  847          mdb_printf("Prints summary information about each mpt_sas instance, "
 823  848              "including warning\nmessages when slot usage doesn't match "
 824  849              "summary information.\n"
 825  850              "Without the address of a \"struct mptsas\", prints every "
 826  851              "instance.\n\n"
 827  852              "Switches:\n"
 828  853              "  -t   includes information about targets\n"
↓ open down ↓ 18 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX