Print this page
5882 Temporary pool names
Reviewed by: Matt Ahrens <matt@delphix.com>
Reviewed by: Igor Kozhukhov <igor@dilos.org>
Reviewed by: John Kennedy <john.kennedy@delphix.com>
Approved by: Dan McDonald <danmcd@joyent.com>

Split Close
Expand all
Collapse all
          --- old/usr/src/cmd/zpool/zpool_main.c
          +++ new/usr/src/cmd/zpool/zpool_main.c
↓ open down ↓ 212 lines elided ↑ open up ↑
 213  213          case HELP_ADD:
 214  214                  return (gettext("\tadd [-fn] <pool> <vdev> ...\n"));
 215  215          case HELP_ATTACH:
 216  216                  return (gettext("\tattach [-f] <pool> <device> "
 217  217                      "<new-device>\n"));
 218  218          case HELP_CLEAR:
 219  219                  return (gettext("\tclear [-nF] <pool> [device]\n"));
 220  220          case HELP_CREATE:
 221  221                  return (gettext("\tcreate [-fnd] [-B] "
 222  222                      "[-o property=value] ... \n"
 223      -                    "\t    [-O file-system-property=value] ... \n"
 224      -                    "\t    [-m mountpoint] [-R root] <pool> <vdev> ...\n"));
      223 +                    "\t    [-O file-system-property=value] ...\n"
      224 +                    "\t    [-m mountpoint] [-R root] [-t tempname] "
      225 +                    "<pool> <vdev> ...\n"));
 225  226          case HELP_CHECKPOINT:
 226  227                  return (gettext("\tcheckpoint [--discard] <pool> ...\n"));
 227  228          case HELP_DESTROY:
 228  229                  return (gettext("\tdestroy [-f] <pool>\n"));
 229  230          case HELP_DETACH:
 230  231                  return (gettext("\tdetach <pool> <device>\n"));
 231  232          case HELP_EXPORT:
 232  233                  return (gettext("\texport [-f] <pool> ...\n"));
 233  234          case HELP_HISTORY:
 234  235                  return (gettext("\thistory [-il] [<pool>] ...\n"));
 235  236          case HELP_IMPORT:
 236  237                  return (gettext("\timport [-d dir] [-D]\n"
 237  238                      "\timport [-o mntopts] [-o property=value] ... \n"
 238  239                      "\t    [-d dir | -c cachefile] [-D] [-f] [-m] [-N] "
 239  240                      "[-R root] [-F [-n]] -a\n"
 240  241                      "\timport [-o mntopts] [-o property=value] ... \n"
 241  242                      "\t    [-d dir | -c cachefile] [-D] [-f] [-m] [-N] "
 242      -                    "[-R root] [-F [-n]]\n"
      243 +                    "[-R root] [-F [-n]] [-t]\n"
 243  244                      "\t    [--rewind-to-checkpoint] <pool | id> [newpool]\n"));
 244  245          case HELP_IOSTAT:
 245  246                  return (gettext("\tiostat [-v] [-T d|u] [pool] ... [interval "
 246  247                      "[count]]\n"));
 247  248          case HELP_LABELCLEAR:
 248  249                  return (gettext("\tlabelclear [-f] <vdev>\n"));
 249  250          case HELP_LIST:
 250  251                  return (gettext("\tlist [-Hp] [-o property[,...]] "
 251  252                      "[-T d|u] [pool] ... [interval [count]]\n"));
 252  253          case HELP_OFFLINE:
↓ open down ↓ 232 lines elided ↑ open up ↑
 485  486          if (nvlist_add_string(proplist, normnm, propval) != 0) {
 486  487                  (void) fprintf(stderr, gettext("internal "
 487  488                      "error: out of memory\n"));
 488  489                  return (1);
 489  490          }
 490  491  
 491  492          return (0);
 492  493  }
 493  494  
 494  495  /*
      496 + * Set a default property pair (name, string-value) in a property nvlist
      497 + */
      498 +static int
      499 +add_prop_list_default(const char *propname, char *propval, nvlist_t **props,
      500 +    boolean_t poolprop)
      501 +{
      502 +        char *pval;
      503 +
      504 +        if (nvlist_lookup_string(*props, propname, &pval) == 0)
      505 +                return (0);
      506 +
      507 +        return (add_prop_list(propname, propval, props, poolprop));
      508 +}
      509 +
      510 +/*
 495  511   * zpool add [-fn] <pool> <vdev> ...
 496  512   *
 497  513   *      -f      Force addition of devices, even if they appear in use
 498  514   *      -n      Do not add the devices, but display the resulting layout if
 499  515   *              they were to be added.
 500  516   *
 501  517   * Adds the given vdevs to 'pool'.  As with create, the bulk of this work is
 502  518   * handled by get_vdev_spec(), which constructs the nvlist needed to pass to
 503  519   * libzfs.
 504  520   */
↓ open down ↓ 337 lines elided ↑ open up ↑
 842  858  errout:
 843  859          free(name);
 844  860          (void) close(fd);
 845  861  
 846  862          return (ret);
 847  863  }
 848  864  
 849  865  /*
 850  866   * zpool create [-fnd] [-B] [-o property=value] ...
 851  867   *              [-O file-system-property=value] ...
 852      - *              [-R root] [-m mountpoint] <pool> <dev> ...
      868 + *              [-R root] [-m mountpoint] [-t tempname] <pool> <dev> ...
 853  869   *
 854  870   *      -B      Create boot partition.
 855  871   *      -f      Force creation, even if devices appear in use
 856  872   *      -n      Do not create the pool, but display the resulting layout if it
 857  873   *              were to be created.
 858      - *      -R      Create a pool under an alternate root
 859      - *      -m      Set default mountpoint for the root dataset.  By default it's
      874 + *      -R      Create a pool under an alternate root
      875 + *      -m      Set default mountpoint for the root dataset.  By default it's
 860  876   *              '/<pool>'
      877 + *      -t      Use the temporary name until the pool is exported.
 861  878   *      -o      Set property=value.
 862  879   *      -d      Don't automatically enable all supported pool features
 863  880   *              (individual features can be enabled with -o).
 864  881   *      -O      Set fsproperty=value in the pool's root file system
 865  882   *
 866  883   * Creates the named pool according to the given vdev specification.  The
 867  884   * bulk of the vdev processing is done in get_vdev_spec() in zpool_vdev.c.  Once
 868  885   * we get the nvlist back from get_vdev_spec(), we either print out the contents
 869  886   * (if '-n' was specified), or pass it to libzfs to do the creation.
 870  887   */
↓ open down ↓ 3 lines elided ↑ open up ↑
 874  891  zpool_do_create(int argc, char **argv)
 875  892  {
 876  893          boolean_t force = B_FALSE;
 877  894          boolean_t dryrun = B_FALSE;
 878  895          boolean_t enable_all_pool_feat = B_TRUE;
 879  896          zpool_boot_label_t boot_type = ZPOOL_NO_BOOT_LABEL;
 880  897          uint64_t boot_size = 0;
 881  898          int c;
 882  899          nvlist_t *nvroot = NULL;
 883  900          char *poolname;
      901 +        char *tname = NULL;
 884  902          int ret = 1;
 885  903          char *altroot = NULL;
 886  904          char *mountpoint = NULL;
 887  905          nvlist_t *fsprops = NULL;
 888  906          nvlist_t *props = NULL;
 889  907          char *propval;
 890  908  
 891  909          /* check options */
 892      -        while ((c = getopt(argc, argv, ":fndBR:m:o:O:")) != -1) {
      910 +        while ((c = getopt(argc, argv, ":fndBR:m:o:O:t:")) != -1) {
 893  911                  switch (c) {
 894  912                  case 'f':
 895  913                          force = B_TRUE;
 896  914                          break;
 897  915                  case 'n':
 898  916                          dryrun = B_TRUE;
 899  917                          break;
 900  918                  case 'd':
 901  919                          enable_all_pool_feat = B_FALSE;
 902  920                          break;
↓ open down ↓ 4 lines elided ↑ open up ↑
 907  925                           */
 908  926                          boot_type = ZPOOL_CREATE_BOOT_LABEL;
 909  927                          if (boot_size == 0)
 910  928                                  boot_size = SYSTEM256;
 911  929                          break;
 912  930                  case 'R':
 913  931                          altroot = optarg;
 914  932                          if (add_prop_list(zpool_prop_to_name(
 915  933                              ZPOOL_PROP_ALTROOT), optarg, &props, B_TRUE))
 916  934                                  goto errout;
 917      -                        if (nvlist_lookup_string(props,
 918      -                            zpool_prop_to_name(ZPOOL_PROP_CACHEFILE),
 919      -                            &propval) == 0)
 920      -                                break;
 921      -                        if (add_prop_list(zpool_prop_to_name(
      935 +                        if (add_prop_list_default(zpool_prop_to_name(
 922  936                              ZPOOL_PROP_CACHEFILE), "none", &props, B_TRUE))
 923  937                                  goto errout;
 924  938                          break;
 925  939                  case 'm':
 926  940                          /* Equivalent to -O mountpoint=optarg */
 927  941                          mountpoint = optarg;
 928  942                          break;
 929  943                  case 'o':
 930  944                          if ((propval = strchr(optarg, '=')) == NULL) {
 931  945                                  (void) fprintf(stderr, gettext("missing "
↓ open down ↓ 52 lines elided ↑ open up ↑
 984  998                           * more than once, to avoid conflict with -m.
 985  999                           */
 986 1000                          if (0 == strcmp(optarg,
 987 1001                              zfs_prop_to_name(ZFS_PROP_MOUNTPOINT))) {
 988 1002                                  mountpoint = propval;
 989 1003                          } else if (add_prop_list(optarg, propval, &fsprops,
 990 1004                              B_FALSE)) {
 991 1005                                  goto errout;
 992 1006                          }
 993 1007                          break;
     1008 +                case 't':
     1009 +                        /*
     1010 +                         * Sanity check temporary pool name.
     1011 +                         */
     1012 +                        if (strchr(optarg, '/') != NULL) {
     1013 +                                (void) fprintf(stderr, gettext("cannot create "
     1014 +                                    "'%s': invalid character '/' in temporary "
     1015 +                                    "name\n"), optarg);
     1016 +                                (void) fprintf(stderr, gettext("use 'zfs "
     1017 +                                    "create' to create a dataset\n"));
     1018 +                                goto errout;
     1019 +                        }
     1020 +
     1021 +                        if (add_prop_list(zpool_prop_to_name(
     1022 +                            ZPOOL_PROP_TNAME), optarg, &props, B_TRUE))
     1023 +                                goto errout;
     1024 +                        if (add_prop_list_default(zpool_prop_to_name(
     1025 +                            ZPOOL_PROP_CACHEFILE), "none", &props, B_TRUE))
     1026 +                                goto errout;
     1027 +                        tname = optarg;
     1028 +                        break;
 994 1029                  case ':':
 995 1030                          (void) fprintf(stderr, gettext("missing argument for "
 996 1031                              "'%c' option\n"), optopt);
 997 1032                          goto badusage;
 998 1033                  case '?':
 999 1034                          (void) fprintf(stderr, gettext("invalid option '%c'\n"),
1000 1035                              optopt);
1001 1036                          goto badusage;
1002 1037                  }
1003 1038          }
↓ open down ↓ 185 lines elided ↑ open up ↑
1189 1224                                  ret = add_prop_list(propname,
1190 1225                                      ZFS_FEATURE_ENABLED, &props, B_TRUE);
1191 1226                                  if (ret != 0)
1192 1227                                          goto errout;
1193 1228                          }
1194 1229                  }
1195 1230  
1196 1231                  ret = 1;
1197 1232                  if (zpool_create(g_zfs, poolname,
1198 1233                      nvroot, props, fsprops) == 0) {
1199      -                        zfs_handle_t *pool = zfs_open(g_zfs, poolname,
1200      -                            ZFS_TYPE_FILESYSTEM);
     1234 +                        zfs_handle_t *pool = zfs_open(g_zfs,
     1235 +                            tname ? tname : poolname, ZFS_TYPE_FILESYSTEM);
1201 1236                          if (pool != NULL) {
1202 1237                                  if (zfs_mount(pool, NULL, 0) == 0)
1203 1238                                          ret = zfs_shareall(pool);
1204 1239                                  zfs_close(pool);
1205 1240                          }
1206 1241                  } else if (libzfs_errno(g_zfs) == EZFS_INVALIDNAME) {
1207 1242                          (void) fprintf(stderr, gettext("pool name may have "
1208 1243                              "been omitted\n"));
1209 1244                  }
1210 1245          }
↓ open down ↓ 6 lines elided ↑ open up ↑
1217 1252  badusage:
1218 1253          nvlist_free(fsprops);
1219 1254          nvlist_free(props);
1220 1255          usage(B_FALSE);
1221 1256          return (2);
1222 1257  }
1223 1258  
1224 1259  /*
1225 1260   * zpool destroy <pool>
1226 1261   *
1227      - *      -f      Forcefully unmount any datasets
     1262 + *      -f      Forcefully unmount any datasets
1228 1263   *
1229 1264   * Destroy the given pool.  Automatically unmounts any datasets in the pool.
1230 1265   */
1231 1266  int
1232 1267  zpool_do_destroy(int argc, char **argv)
1233 1268  {
1234 1269          boolean_t force = B_FALSE;
1235 1270          int c;
1236 1271          char *pool;
1237 1272          zpool_handle_t *zhp;
↓ open down ↓ 853 lines elided ↑ open up ↑
2091 2126          }
2092 2127  
2093 2128          zpool_close(zhp);
2094 2129          return (0);
2095 2130  }
2096 2131  
2097 2132  /*
2098 2133   * zpool checkpoint <pool>
2099 2134   *       checkpoint --discard <pool>
2100 2135   *
2101      - *       -d         Discard the checkpoint from a checkpointed
2102      - *       --discard  pool.
     2136 + *      -d      Discard the checkpoint from a checkpointed
     2137 + *      --discard  pool.
2103 2138   *
2104 2139   * Checkpoints the specified pool, by taking a "snapshot" of its
2105 2140   * current state. A pool can only have one checkpoint at a time.
2106 2141   */
2107 2142  int
2108 2143  zpool_do_checkpoint(int argc, char **argv)
2109 2144  {
2110 2145          boolean_t discard;
2111 2146          char *pool;
2112 2147          zpool_handle_t *zhp;
↓ open down ↓ 52 lines elided ↑ open up ↑
2165 2200          return (err);
2166 2201  }
2167 2202  
2168 2203  #define CHECKPOINT_OPT  1024
2169 2204  
2170 2205  /*
2171 2206   * zpool import [-d dir] [-D]
2172 2207   *       import [-o mntopts] [-o prop=value] ... [-R root] [-D]
2173 2208   *              [-d dir | -c cachefile] [-f] -a
2174 2209   *       import [-o mntopts] [-o prop=value] ... [-R root] [-D]
2175      - *              [-d dir | -c cachefile] [-f] [-n] [-F] <pool | id> [newpool]
     2210 + *              [-d dir | -c cachefile] [-f] [-n] [-F] [-t]
     2211 + *              <pool | id> [newpool]
2176 2212   *
2177      - *       -c     Read pool information from a cachefile instead of searching
     2213 + *      -c      Read pool information from a cachefile instead of searching
2178 2214   *              devices.
2179 2215   *
2180      - *       -d     Scan in a specific directory, other than /dev/dsk.  More than
     2216 + *      -d      Scan in a specific directory, other than /dev/dsk.  More than
2181 2217   *              one directory can be specified using multiple '-d' options.
2182 2218   *
2183      - *       -D     Scan for previously destroyed pools or import all or only
2184      - *              specified destroyed pools.
     2219 + *      -D      Scan for previously destroyed pools or import all or only
     2220 + *              specified destroyed pools.
2185 2221   *
2186      - *       -R     Temporarily import the pool, with all mountpoints relative to
     2222 + *      -R      Temporarily import the pool, with all mountpoints relative to
2187 2223   *              the given root.  The pool will remain exported when the machine
2188 2224   *              is rebooted.
2189 2225   *
2190      - *       -V     Import even in the presence of faulted vdevs.  This is an
2191      - *              intentionally undocumented option for testing purposes, and
2192      - *              treats the pool configuration as complete, leaving any bad
     2226 + *      -V      Import even in the presence of faulted vdevs.  This is an
     2227 + *              intentionally undocumented option for testing purposes, and
     2228 + *              treats the pool configuration as complete, leaving any bad
2193 2229   *              vdevs in the FAULTED state. In other words, it does verbatim
2194 2230   *              import.
2195 2231   *
2196      - *       -f     Force import, even if it appears that the pool is active.
     2232 + *      -f      Force import, even if it appears that the pool is active.
2197 2233   *
2198      - *       -F     Attempt rewind if necessary.
     2234 + *      -F      Attempt rewind if necessary.
2199 2235   *
2200      - *       -n     See if rewind would work, but don't actually rewind.
     2236 + *      -n      See if rewind would work, but don't actually rewind.
2201 2237   *
2202      - *       -N     Import the pool but don't mount datasets.
     2238 + *      -N      Import the pool but don't mount datasets.
2203 2239   *
2204      - *       -T     Specify a starting txg to use for import. This option is
2205      - *              intentionally undocumented option for testing purposes.
     2240 + *      -t      Use newpool as a temporary pool name instead of renaming
     2241 + *              the pool.
2206 2242   *
2207      - *       -a     Import all pools found.
     2243 + *      -T      Specify a starting txg to use for import. This option is
     2244 + *              intentionally undocumented option for testing purposes.
2208 2245   *
2209      - *       -o     Set property=value and/or temporary mount options (without '=').
     2246 + *      -a      Import all pools found.
2210 2247   *
2211      - *       --rewind-to-checkpoint
2212      - *              Import the pool and revert back to the checkpoint.
     2248 + *      -o      Set property=value and/or temporary mount options (without '=').
2213 2249   *
     2250 + *      --rewind-to-checkpoint
     2251 + *              Import the pool and revert back to the checkpoint.
     2252 + *
2214 2253   * The import command scans for pools to import, and import pools based on pool
2215 2254   * name and GUID.  The pool can also be renamed as part of the import process.
2216 2255   */
2217 2256  int
2218 2257  zpool_do_import(int argc, char **argv)
2219 2258  {
2220 2259          char **searchdirs = NULL;
2221 2260          int nsearch = 0;
2222 2261          int c;
2223 2262          int err = 0;
↓ open down ↓ 20 lines elided ↑ open up ↑
2244 2283          importargs_t idata = { 0 };
2245 2284          char *endptr;
2246 2285  
2247 2286  
2248 2287          struct option long_options[] = {
2249 2288                  {"rewind-to-checkpoint", no_argument, NULL, CHECKPOINT_OPT},
2250 2289                  {0, 0, 0, 0}
2251 2290          };
2252 2291  
2253 2292          /* check options */
2254      -        while ((c = getopt_long(argc, argv, ":aCc:d:DEfFmnNo:rR:T:VX",
     2293 +        while ((c = getopt_long(argc, argv, ":aCc:d:DEfFmnNo:rR:tT:VX",
2255 2294              long_options, NULL)) != -1) {
2256 2295                  switch (c) {
2257 2296                  case 'a':
2258 2297                          do_all = B_TRUE;
2259 2298                          break;
2260 2299                  case 'c':
2261 2300                          cachefile = optarg;
2262 2301                          break;
2263 2302                  case 'd':
2264 2303                          if (searchdirs == NULL) {
↓ open down ↓ 34 lines elided ↑ open up ↑
2299 2338                                      &props, B_TRUE))
2300 2339                                          goto error;
2301 2340                          } else {
2302 2341                                  mntopts = optarg;
2303 2342                          }
2304 2343                          break;
2305 2344                  case 'R':
2306 2345                          if (add_prop_list(zpool_prop_to_name(
2307 2346                              ZPOOL_PROP_ALTROOT), optarg, &props, B_TRUE))
2308 2347                                  goto error;
2309      -                        if (nvlist_lookup_string(props,
2310      -                            zpool_prop_to_name(ZPOOL_PROP_CACHEFILE),
2311      -                            &propval) == 0)
2312      -                                break;
2313      -                        if (add_prop_list(zpool_prop_to_name(
     2348 +                        if (add_prop_list_default(zpool_prop_to_name(
2314 2349                              ZPOOL_PROP_CACHEFILE), "none", &props, B_TRUE))
2315 2350                                  goto error;
2316 2351                          break;
     2352 +                case 't':
     2353 +                        flags |= ZFS_IMPORT_TEMP_NAME;
     2354 +                        if (add_prop_list_default(zpool_prop_to_name(
     2355 +                            ZPOOL_PROP_CACHEFILE), "none", &props, B_TRUE))
     2356 +                                goto error;
     2357 +                        break;
2317 2358                  case 'T':
2318 2359                          errno = 0;
2319 2360                          txg = strtoull(optarg, &endptr, 0);
2320 2361                          if (errno != 0 || *endptr != '\0') {
2321 2362                                  (void) fprintf(stderr,
2322 2363                                      gettext("invalid txg value\n"));
2323 2364                                  usage(B_FALSE);
2324 2365                          }
2325 2366                          rewind_policy = ZPOOL_DO_REWIND | ZPOOL_EXTREME_REWIND;
2326 2367                          break;
↓ open down ↓ 115 lines elided ↑ open up ↑
2442 2483          idata.cachefile = cachefile;
2443 2484          idata.policy = policy;
2444 2485  
2445 2486          pools = zpool_search_import(g_zfs, &idata);
2446 2487  
2447 2488          if (pools != NULL && idata.exists &&
2448 2489              (argc == 1 || strcmp(argv[0], argv[1]) == 0)) {
2449 2490                  (void) fprintf(stderr, gettext("cannot import '%s': "
2450 2491                      "a pool with that name already exists\n"),
2451 2492                      argv[0]);
2452      -                (void) fprintf(stderr, gettext("use the form '%s "
2453      -                    "<pool | id> <newpool>' to give it a new name\n"),
2454      -                    "zpool import");
     2493 +                (void) fprintf(stderr, gettext("use the form 'zpool import "
     2494 +                    "[-t] <pool | id> <newpool>' to give it a new temporary "
     2495 +                    "or permanent name\n"));
2455 2496                  err = 1;
2456 2497          } else if (pools == NULL && idata.exists) {
2457 2498                  (void) fprintf(stderr, gettext("cannot import '%s': "
2458 2499                      "a pool with that name is already created/imported,\n"),
2459 2500                      argv[0]);
2460 2501                  (void) fprintf(stderr, gettext("and no additional pools "
2461 2502                      "with that name were found\n"));
2462 2503                  err = 1;
2463 2504          } else if (pools == NULL) {
2464 2505                  if (argc != 0) {
↓ open down ↓ 895 lines elided ↑ open up ↑
3360 3401  }
3361 3402  
3362 3403  /*
3363 3404   * zpool list [-Hp] [-o prop[,prop]*] [-T d|u] [pool] ... [interval [count]]
3364 3405   *
3365 3406   *      -H      Scripted mode.  Don't display headers, and separate properties
3366 3407   *              by a single tab.
3367 3408   *      -o      List of properties to display.  Defaults to
3368 3409   *              "name,size,allocated,free,expandsize,fragmentation,capacity,"
3369 3410   *              "dedupratio,health,altroot"
3370      - *      -p      Diplay values in parsable (exact) format.
     3411 + *      -p      Diplay values in parsable (exact) format.
3371 3412   *      -T      Display a timestamp in date(1) or Unix format
3372 3413   *
3373 3414   * List all pools in the system, whether or not they're healthy.  Output space
3374 3415   * statistics for each one, as well as health status summary.
3375 3416   */
3376 3417  int
3377 3418  zpool_do_list(int argc, char **argv)
3378 3419  {
3379 3420          int c;
3380 3421          int ret;
↓ open down ↓ 2451 lines elided ↑ open up ↑
5832 5873          return (0);
5833 5874  }
5834 5875  
5835 5876  /*
5836 5877   * zpool get [-Hp] [-o "all" | field[,...]] <"all" | property[,...]> <pool> ...
5837 5878   *
5838 5879   *      -H      Scripted mode.  Don't display headers, and separate properties
5839 5880   *              by a single tab.
5840 5881   *      -o      List of columns to display.  Defaults to
5841 5882   *              "name,property,value,source".
5842      - *      -p      Diplay values in parsable (exact) format.
     5883 + *      -p      Diplay values in parsable (exact) format.
5843 5884   *
5844 5885   * Get properties of pools in the system. Output space statistics
5845 5886   * for each one as well as other attributes.
5846 5887   */
5847 5888  int
5848 5889  zpool_do_get(int argc, char **argv)
5849 5890  {
5850 5891          zprop_get_cbdata_t cb = { 0 };
5851 5892          zprop_list_t fake_name = { 0 };
5852 5893          int ret;
↓ open down ↓ 277 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX