Print this page
    
    
      
        | Split | 
	Close | 
      
      | Expand all | 
      | Collapse all | 
    
    
          --- old/usr/src/lib/libzonecfg/common/getzoneent.c
          +++ new/usr/src/lib/libzonecfg/common/getzoneent.c
   1    1  /*
   2    2   * CDDL HEADER START
   3    3   *
   4    4   * The contents of this file are subject to the terms of the
   5    5   * Common Development and Distribution License (the "License").
   6    6   * You may not use this file except in compliance with the License.
   7    7   *
   8    8   * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9    9   * or http://www.opensolaris.org/os/licensing.
  10   10   * See the License for the specific language governing permissions
  11   11   * and limitations under the License.
  12   12   *
  13   13   * When distributing Covered Code, include this CDDL HEADER in each
  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   * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
  23   23   * Copyright 2015 Joyent, Inc.
  24   24   */
  25   25  
  26   26  
  27   27  /*
  28   28   * This module contains functions used for reading and writing the index file.
  29   29   * setzoneent() opens the file.  getzoneent() parses the file, doing the usual
  30   30   * skipping of comment lines, etc., and using gettok() to deal with the ":"
  31   31   * delimiters.  endzoneent() closes the file.  putzoneent() updates the file,
  32   32   * adding, deleting or modifying lines, locking and unlocking appropriately.
  33   33   */
  34   34  
  35   35  #include <stdlib.h>
  36   36  #include <string.h>
  37   37  #include <errno.h>
  38   38  #include <libzonecfg.h>
  39   39  #include <unistd.h>
  40   40  #include <fcntl.h>
  41   41  #include <sys/stat.h>
  42   42  #include <assert.h>
  43   43  #include <uuid/uuid.h>
  44   44  #include "zonecfg_impl.h"
  45   45  
  46   46  
  47   47  #define _PATH_TMPFILE   ZONE_CONFIG_ROOT "/zonecfg.XXXXXX"
  48   48  
  49   49  /*
  50   50   * gettok() is a helper function for parsing the index file, used to split
  51   51   * the lines by their ":" delimiters.  Note that an entry may contain a ":"
  52   52   * inside double quotes; this should only affect the zonepath, as zone names
  53   53   * do not allow such characters, and zone states do not have them either.
  54   54   * Same with double-quotes themselves: they are not allowed in zone names,
  55   55   * and do not occur in zone states, and in theory should never occur in a
  56   56   * zonepath since zonecfg does not support a method for escaping them.
  57   57   *
  58   58   * It never returns NULL.
  59   59   */
  60   60  
  61   61  static char *
  62   62  gettok(char **cpp)
  63   63  {
  64   64          char *cp = *cpp, *retv;
  65   65          boolean_t quoted = B_FALSE;
  66   66  
  67   67          if (cp == NULL)
  68   68                  return ("");
  69   69          if (*cp == '"') {
  70   70                  quoted = B_TRUE;
  71   71                  cp++;
  72   72          }
  73   73          retv = cp;
  74   74          if (quoted) {
  75   75                  while (*cp != '\0' && *cp != '"')
  76   76                          cp++;
  77   77                  if (*cp == '"')
  78   78                          *cp++ = '\0';
  79   79          }
  80   80          while (*cp != '\0' && *cp != ':')
  81   81                  cp++;
  82   82          if (*cp == '\0') {
  83   83                  *cpp = NULL;
  84   84          } else {
  85   85                  *cp++ = '\0';
  86   86                  *cpp = cp;
  87   87          }
  88   88          return (retv);
  89   89  }
  90   90  
  91   91  char *
  92   92  getzoneent(FILE *cookie)
  93   93  {
  94   94          struct zoneent *ze;
  95   95          char *name;
  96   96  
  97   97          if ((ze = getzoneent_private(cookie)) == NULL)
  98   98                  return (NULL);
  99   99          name = strdup(ze->zone_name);
 100  100          free(ze);
 101  101          return (name);
 102  102  }
 103  103  
 104  104  struct zoneent *
 105  105  getzoneent_private(FILE *cookie)
 106  106  {
 107  107          char *cp, buf[MAX_INDEX_LEN], *p;
 108  108          struct zoneent *ze;
 109  109  
 110  110          if (cookie == NULL)
 111  111                  return (NULL);
 112  112  
 113  113          if ((ze = malloc(sizeof (struct zoneent))) == NULL)
 114  114                  return (NULL);
 115  115  
 116  116          for (;;) {
 117  117                  if (fgets(buf, sizeof (buf), cookie) == NULL) {
 118  118                          free(ze);
 119  119                          return (NULL);
 120  120                  }
  
    | 
      ↓ open down ↓ | 
    120 lines elided | 
    
      ↑ open up ↑ | 
  
 121  121                  if ((cp = strpbrk(buf, "\r\n")) == NULL) {
 122  122                          /* this represents a line that's too long */
 123  123                          continue;
 124  124                  }
 125  125                  *cp = '\0';
 126  126                  cp = buf;
 127  127                  if (*cp == '#') {
 128  128                          /* skip comment lines */
 129  129                          continue;
 130  130                  }
 131      -
 132      -                /* zonename */
 133  131                  p = gettok(&cp);
 134  132                  if (*p == '\0' || strlen(p) >= ZONENAME_MAX) {
 135  133                          /*
 136  134                           * empty or very long zone names are not allowed
 137  135                           */
 138  136                          continue;
 139  137                  }
 140  138                  (void) strlcpy(ze->zone_name, p, ZONENAME_MAX);
 141  139  
 142      -                /* state */
 143  140                  p = gettok(&cp);
 144  141                  if (*p == '\0') {
 145  142                          /* state field should not be empty */
 146  143                          continue;
 147  144                  }
 148  145                  errno = 0;
 149  146                  if (strcmp(p, ZONE_STATE_STR_CONFIGURED) == 0) {
 150  147                          ze->zone_state = ZONE_STATE_CONFIGURED;
 151  148                  } else if (strcmp(p, ZONE_STATE_STR_INCOMPLETE) == 0) {
 152  149                          ze->zone_state = ZONE_STATE_INCOMPLETE;
 153  150                  } else if (strcmp(p, ZONE_STATE_STR_INSTALLED) == 0) {
 154  151                          ze->zone_state = ZONE_STATE_INSTALLED;
 155  152                  } else {
 156  153                          continue;
 157  154                  }
 158  155  
 159      -                /* zonepath */
 160  156                  p = gettok(&cp);
 161  157                  if (strlen(p) >= MAXPATHLEN) {
 162  158                          /* very long paths are not allowed */
 163  159                          continue;
 164  160                  }
 165  161                  (void) strlcpy(ze->zone_path, p, MAXPATHLEN);
 166  162  
 167      -                /* uuid */
 168  163                  p = gettok(&cp);
 169  164                  if (uuid_parse(p, ze->zone_uuid) == -1)
 170  165                          uuid_clear(ze->zone_uuid);
 171  166  
 172      -                /* brand [optional] */
 173      -                p = gettok(&cp);
 174      -                if (strlen(p) >= MAXNAMELEN) {
 175      -                        /* very long names are not allowed */
 176      -                        continue;
 177      -                }
 178      -                (void) strlcpy(ze->zone_brand, p, MAXNAMELEN);
 179      -
 180      -                /* IP type [optional] */
 181      -                p = gettok(&cp);
 182      -                if (strlen(p) >= MAXNAMELEN) {
 183      -                        /* very long names are not allowed */
 184      -                        continue;
 185      -                }
 186      -                ze->zone_iptype = ZS_SHARED;
 187      -                if (*p == 'e') {
 188      -                        ze->zone_iptype = ZS_EXCLUSIVE;
 189      -                }
 190      -
 191      -                /* debug ID [optional] */
 192      -                p = gettok(&cp);
 193      -                if (*p != '\0')
 194      -                        ze->zone_did = atoi(p);
 195      -
 196  167                  break;
 197  168          }
 198  169  
 199  170          return (ze);
 200  171  }
 201  172  
 202  173  static boolean_t
 203  174  path_common(char *path, size_t path_size, const char *stem)
 204  175  {
 205  176          const char *native_root = zone_get_nroot();
 206  177  
 207  178          if (native_root == NULL || zonecfg_in_alt_root()) {
 208  179                  /*
 209  180                   * Do not prepend the native system root (e.g. "/native") if an
 210  181                   * alternative configuration root has been selected.
 211  182                   */
 212  183                  native_root = "";
 213  184          }
 214  185  
 215  186          return (snprintf(path, path_size, "%s%s%s", native_root, zonecfg_root,
 216  187              stem) < path_size);
 217  188  }
 218  189  
 219  190  static boolean_t
 220  191  get_index_path(char *path, size_t path_size)
 221  192  {
 222  193          return (path_common(path, path_size, ZONE_INDEX_FILE));
 223  194  }
 224  195  
 225  196  static boolean_t
 226  197  get_temp_path(char *path, size_t path_size)
 227  198  {
 228  199          return (path_common(path, path_size, _PATH_TMPFILE));
 229  200  }
 230  201  
 231  202  FILE *
 232  203  setzoneent(void)
 233  204  {
 234  205          char path[MAXPATHLEN];
 235  206  
 236  207          if (!get_index_path(path, sizeof (path))) {
 237  208                  errno = EINVAL;
 238  209                  return (NULL);
 239  210          }
 240  211          return (fopen(path, "r"));
 241  212  }
 242  213  
 243  214  void
 244  215  endzoneent(FILE *cookie)
 245  216  {
 246  217          if (cookie != NULL)
 247  218                  (void) fclose(cookie);
 248  219  }
 249  220  
 250  221  static int
 251  222  lock_index_file(void)
 252  223  {
 253  224          int lock_fd;
 254  225          struct flock lock;
 255  226          char path[MAXPATHLEN];
 256  227  
 257  228          if (!path_common(path, sizeof (path), ZONE_INDEX_LOCK_DIR))
 258  229                  return (-1);
 259  230          if ((mkdir(path, S_IRWXU) == -1) && errno != EEXIST)
 260  231                  return (-1);
 261  232          if (strlcat(path, ZONE_INDEX_LOCK_FILE, sizeof (path)) >=
 262  233              sizeof (path))
 263  234                  return (-1);
 264  235          lock_fd = open(path, O_CREAT|O_RDWR, 0644);
 265  236          if (lock_fd == -1)
 266  237                  return (-1);
 267  238  
 268  239          lock.l_type = F_WRLCK;
 269  240          lock.l_whence = SEEK_SET;
 270  241          lock.l_start = 0;
 271  242          lock.l_len = 0;
 272  243  
 273  244          if (fcntl(lock_fd, F_SETLKW, &lock) == -1) {
 274  245                  (void) close(lock_fd);
 275  246                  return (-1);
 276  247          }
 277  248  
 278  249          return (lock_fd);
 279  250  }
 280  251  
 281  252  static int
 282  253  unlock_index_file(int lock_fd)
 283  254  {
 284  255          struct flock lock;
 285  256  
 286  257          lock.l_type = F_UNLCK;
 287  258          lock.l_whence = SEEK_SET;
 288  259          lock.l_start = 0;
 289  260          lock.l_len = 0;
 290  261  
 291  262          if (fcntl(lock_fd, F_SETLK, &lock) == -1)
 292  263                  return (Z_UNLOCKING_FILE);
 293  264  
 294  265          if (close(lock_fd) == -1)
 295  266                  return (Z_UNLOCKING_FILE);
 296  267  
 297  268          return (Z_OK);
 298  269  }
 299  270  
 300  271  /*
 301  272   * This function adds or removes a zone name et al. to the index file.
 302  273   *
 303  274   * If ze->zone_state is < 0, it means leave the
 304  275   * existing value unchanged; this is only meaningful when operation ==
 305  276   * PZE_MODIFY (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
 306  277   *
 307  278   * A zero-length ze->zone_path means leave the existing value
 308  279   * unchanged; this is only meaningful when operation == PZE_MODIFY
 309  280   * (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
 310  281   *
 311  282   * A zero-length ze->zone_newname means leave the existing name
 312  283   * unchanged; otherwise the zone is renamed to zone_newname.  This is
 313  284   * only meaningful when operation == PZE_MODIFY.
 314  285   *
 315  286   * Locking and unlocking is done via the functions above.
  
    | 
      ↓ open down ↓ | 
    110 lines elided | 
    
      ↑ open up ↑ | 
  
 316  287   * The file itself is not modified in place; rather, a copy is made which
 317  288   * is modified, then the copy is atomically renamed back to the main file.
 318  289   */
 319  290  int
 320  291  putzoneent(struct zoneent *ze, zoneent_op_t operation)
 321  292  {
 322  293          FILE *index_file, *tmp_file;
 323  294          char buf[MAX_INDEX_LEN];
 324  295          int tmp_file_desc, lock_fd, err;
 325  296          boolean_t exist, need_quotes;
 326      -        char *cp, *tmpp;
      297 +        char *cp;
 327  298          char tmp_path[MAXPATHLEN];
 328  299          char path[MAXPATHLEN];
 329  300          char uuidstr[UUID_PRINTABLE_STRING_LENGTH];
 330  301          size_t namelen;
 331      -        const char *zone_name, *zone_state, *zone_path, *zone_uuid,
 332      -            *zone_brand = "", *zone_iptype;
 333      -        zoneid_t zone_did;
      302 +        const char *zone_name, *zone_state, *zone_path, *zone_uuid;
 334  303  
 335  304          assert(ze != NULL);
 336  305  
 337  306          /*
 338  307           * Don't allow modification of Global Zone entry
 339  308           * in index file
 340  309           */
 341  310          if ((operation == PZE_MODIFY) &&
 342  311              (strcmp(ze->zone_name, GLOBAL_ZONENAME) == 0)) {
 343  312                  return (Z_OK);
 344  313          }
 345  314  
 346  315          if (operation == PZE_ADD &&
 347  316              (ze->zone_state < 0 || strlen(ze->zone_path) == 0))
 348  317                  return (Z_INVAL);
 349  318  
 350  319          if (operation != PZE_MODIFY && strlen(ze->zone_newname) != 0)
 351  320                  return (Z_INVAL);
 352  321  
 353  322          if ((lock_fd = lock_index_file()) == -1)
 354  323                  return (Z_LOCKING_FILE);
 355  324  
 356  325          if (!get_temp_path(tmp_path, sizeof (tmp_path))) {
 357  326                  (void) unlock_index_file(lock_fd);
 358  327                  return (Z_NOMEM);
 359  328          }
 360  329  
 361  330          tmp_file_desc = mkstemp(tmp_path);
 362  331          if (tmp_file_desc == -1) {
 363  332                  (void) unlink(tmp_path);
 364  333                  (void) unlock_index_file(lock_fd);
 365  334                  return (Z_TEMP_FILE);
 366  335          }
 367  336          (void) fchmod(tmp_file_desc, ZONE_INDEX_MODE);
 368  337          (void) fchown(tmp_file_desc, ZONE_INDEX_UID, ZONE_INDEX_GID);
 369  338          if ((tmp_file = fdopen(tmp_file_desc, "w")) == NULL) {
 370  339                  (void) close(tmp_file_desc);
 371  340                  err = Z_MISC_FS;
 372  341                  goto error;
 373  342          }
 374  343          if (!get_index_path(path, sizeof (path))) {
 375  344                  err = Z_MISC_FS;
  
    | 
      ↓ open down ↓ | 
    32 lines elided | 
    
      ↑ open up ↑ | 
  
 376  345                  goto error;
 377  346          }
 378  347          if ((index_file = fopen(path, "r")) == NULL) {
 379  348                  err = Z_MISC_FS;
 380  349                  goto error;
 381  350          }
 382  351  
 383  352          exist = B_FALSE;
 384  353          zone_name = ze->zone_name;
 385  354          namelen = strlen(zone_name);
 386      -        zone_brand = ze->zone_brand;
 387      -        zone_iptype = (ze->zone_iptype == ZS_SHARED ? "sh" : "ex");
 388      -        zone_did = ze->zone_did;
 389  355          for (;;) {
 390  356                  if (fgets(buf, sizeof (buf), index_file) == NULL) {
 391  357                          if (operation == PZE_ADD && !exist) {
 392  358                                  zone_state = zone_state_str(ze->zone_state);
 393  359                                  zone_path = ze->zone_path;
 394  360                                  zone_uuid = "";
 395  361                                  goto add_entry;
 396  362                          }
 397  363                          /*
 398  364                           * It's not considered an error to delete something
 399  365                           * that doesn't exist, but we can't modify a missing
 400  366                           * record.
 401  367                           */
 402  368                          if (operation == PZE_MODIFY && !exist) {
 403  369                                  err = Z_UPDATING_INDEX;
 404  370                                  goto error;
 405  371                          }
 406  372                          break;
 407  373                  }
 408  374  
 409  375                  if (buf[0] == '#') {
 410  376                          /* skip and preserve comment lines */
 411  377                          (void) fputs(buf, tmp_file);
 412  378                          continue;
 413  379                  }
 414  380  
 415  381                  if (strncmp(buf, zone_name, namelen) != 0 ||
 416  382                      buf[namelen] != ':') {
 417  383                          /* skip and preserve non-target lines */
 418  384                          (void) fputs(buf, tmp_file);
 419  385                          continue;
 420  386                  }
 421  387  
 422  388                  if ((cp = strpbrk(buf, "\r\n")) == NULL) {
 423  389                          /* this represents a line that's too long; delete */
 424  390                          continue;
 425  391                  }
 426  392                  *cp = '\0';
 427  393  
 428  394                  /*
 429  395                   * Skip over the zone name.  Because we've already matched the
 430  396                   * target zone (above), we know for certain here that the zone
 431  397                   * name is present and correctly formed.  No need to check.
 432  398                   */
  
    | 
      ↓ open down ↓ | 
    34 lines elided | 
    
      ↑ open up ↑ | 
  
 433  399                  cp = strchr(buf, ':') + 1;
 434  400  
 435  401                  zone_state = gettok(&cp);
 436  402                  if (*zone_state == '\0') {
 437  403                          /* state field should not be empty */
 438  404                          err = Z_UPDATING_INDEX;
 439  405                          goto error;
 440  406                  }
 441  407                  zone_path = gettok(&cp);
 442  408                  zone_uuid = gettok(&cp);
 443      -                zone_brand = gettok(&cp);
 444      -                zone_iptype = gettok(&cp);
 445      -                tmpp = gettok(&cp);
 446      -                if (*tmpp != '\0')
 447      -                        zone_did = atoi(tmpp);
 448  409  
 449  410                  switch (operation) {
 450  411                  case PZE_ADD:
 451  412                          /* can't add same zone */
 452  413                          err = Z_UPDATING_INDEX;
 453  414                          goto error;
 454  415  
 455  416                  case PZE_MODIFY:
 456  417                          /*
 457  418                           * If the caller specified a new state for the zone,
 458  419                           * then use that.  Otherwise, use the current state.
 459  420                           */
 460  421                          if (ze->zone_state >= 0) {
 461  422                                  zone_state = zone_state_str(ze->zone_state);
      423 +
      424 +                                /*
      425 +                                 * If the caller is uninstalling this zone,
      426 +                                 * then wipe out the uuid.  The zone's contents
      427 +                                 * are no longer known.
      428 +                                 */
      429 +                                if (ze->zone_state < ZONE_STATE_INSTALLED)
      430 +                                        zone_uuid = "";
 462  431                          }
 463  432  
 464  433                          /* If a new name is supplied, use it. */
 465  434                          if (ze->zone_newname[0] != '\0')
 466  435                                  zone_name = ze->zone_newname;
 467  436  
 468  437                          if (ze->zone_path[0] != '\0')
 469  438                                  zone_path = ze->zone_path;
 470      -
 471      -                        /* If new UUID provided, replace it */
 472      -                        if (!uuid_is_null(ze->zone_uuid)) {
 473      -                                uuid_unparse(ze->zone_uuid, uuidstr);
 474      -                                zone_uuid = uuidstr;
 475      -                        }
 476      -
 477      -                        /* If a brand is supplied, use it. */
 478      -                        if (ze->zone_brand[0] != '\0') {
 479      -                                zone_brand = ze->zone_brand;
 480      -
 481      -                                /*
 482      -                                 * Since the brand, iptype and did are optional,
 483      -                                 * we we only reset the iptype and did if the
 484      -                                 * brand is provided.
 485      -                                 */
 486      -                                zone_iptype = (ze->zone_iptype == ZS_SHARED ?
 487      -                                    "sh" : "ex");
 488      -                                zone_did = ze->zone_did;
 489      -                        }
 490      -
 491  439                          break;
 492  440  
 493  441                  case PZE_REMOVE:
 494  442                  default:
 495  443                          continue;
 496  444                  }
 497  445  
 498  446          add_entry:
 499  447                  /*
 500  448                   * If the entry in the file is in greater than configured
 501  449                   * state, then we must have a UUID.  Make sure that we do.
 502  450                   * (Note that the file entry is only tokenized, not fully
 503  451                   * parsed, so we need to do a string comparison here.)
 504  452                   */
 505  453                  if (strcmp(zone_state, ZONE_STATE_STR_CONFIGURED) != 0 &&
 506  454                      *zone_uuid == '\0') {
 507  455                          if (uuid_is_null(ze->zone_uuid))
 508  456                                  uuid_generate(ze->zone_uuid);
 509  457                          uuid_unparse(ze->zone_uuid, uuidstr);
 510  458                          zone_uuid = uuidstr;
 511  459                  }
  
    | 
      ↓ open down ↓ | 
    11 lines elided | 
    
      ↑ open up ↑ | 
  
 512  460                  /*
 513  461                   * We need to quote a path that contains a ":"; this should
 514  462                   * only affect the zonepath, as zone names do not allow such
 515  463                   * characters, and zone states do not have them either.  Same
 516  464                   * with double-quotes themselves: they are not allowed in zone
 517  465                   * names, and do not occur in zone states, and in theory should
 518  466                   * never occur in a zonepath since zonecfg does not support a
 519  467                   * method for escaping them.
 520  468                   */
 521  469                  need_quotes = (strchr(zone_path, ':') != NULL);
 522      -
 523      -                if (*zone_brand != '\0') {
 524      -                        (void) fprintf(tmp_file, "%s:%s:%s%s%s:%s:%s:%s:%d\n",
 525      -                            zone_name, zone_state, need_quotes ? "\"" : "",
 526      -                            zone_path, need_quotes ? "\"" : "", zone_uuid,
 527      -                            zone_brand, zone_iptype, zone_did);
 528      -                } else {
 529      -                        (void) fprintf(tmp_file, "%s:%s:%s%s%s:%s\n", zone_name,
 530      -                            zone_state, need_quotes ? "\"" : "", zone_path,
 531      -                            need_quotes ? "\"" : "", zone_uuid);
 532      -                }
      470 +                (void) fprintf(tmp_file, "%s:%s:%s%s%s:%s\n", zone_name,
      471 +                    zone_state, need_quotes ? "\"" : "", zone_path,
      472 +                    need_quotes ? "\"" : "", zone_uuid);
 533  473                  exist = B_TRUE;
 534  474          }
 535  475  
 536  476          (void) fclose(index_file);
 537  477          index_file = NULL;
 538  478          if (fclose(tmp_file) != 0) {
 539  479                  tmp_file = NULL;
 540  480                  err = Z_MISC_FS;
 541  481                  goto error;
 542  482          }
 543  483          tmp_file = NULL;
 544  484          if (rename(tmp_path, path) == -1) {
 545  485                  err = errno == EACCES ? Z_ACCES : Z_MISC_FS;
 546  486                  goto error;
 547  487          }
 548  488          if (unlock_index_file(lock_fd) != Z_OK)
 549  489                  return (Z_UNLOCKING_FILE);
 550  490          return (Z_OK);
 551  491  
 552  492  error:
 553  493          if (index_file != NULL)
 554  494                  (void) fclose(index_file);
 555  495          if (tmp_file != NULL)
 556  496                  (void) fclose(tmp_file);
 557  497          (void) unlink(tmp_path);
 558  498          (void) unlock_index_file(lock_fd);
 559  499          return (err);
 560  500  }
  
    | 
      ↓ open down ↓ | 
    18 lines elided | 
    
      ↑ open up ↑ | 
  
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX