1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. 24 */ 25 26 /* 27 * Copyright 2013 Nexenta Systems, Inc. All rights reserved. 28 * Copyright 2015 Toomas Soome <tsoome@me.com> 29 * Copyright 2015 Gary Mills 30 * Copyright (c) 2016 Martin Matuska. All rights reserved. 31 */ 32 33 #include <assert.h> 34 #include <libintl.h> 35 #include <libnvpair.h> 36 #include <libzfs.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <strings.h> 41 #include <sys/types.h> 42 #include <sys/stat.h> 43 #include <unistd.h> 44 #include <errno.h> 45 46 #include <libbe.h> 47 #include <libbe_priv.h> 48 49 /* 50 * Callback data used for zfs_iter calls. 51 */ 52 typedef struct list_callback_data { 53 char *zpool_name; 54 char *be_name; 55 be_node_list_t *be_nodes_head; 56 be_node_list_t *be_nodes; 57 char current_be[MAXPATHLEN]; 58 } list_callback_data_t; 59 60 /* 61 * Private function prototypes 62 */ 63 static int be_add_children_callback(zfs_handle_t *zhp, void *data); 64 static int be_get_list_callback(zpool_handle_t *, void *); 65 static int be_get_node_data(zfs_handle_t *, be_node_list_t *, char *, 66 const char *, char *, char *); 67 static int be_get_zone_node_data(be_node_list_t *, char *); 68 static int be_get_ds_data(zfs_handle_t *, char *, be_dataset_list_t *, 69 be_node_list_t *); 70 static int be_get_ss_data(zfs_handle_t *, char *, be_snapshot_list_t *, 71 be_node_list_t *); 72 static int be_sort_list(be_node_list_t **, 73 int (*)(const void *, const void *)); 74 static int be_qsort_compare_BEs_name(const void *, const void *); 75 static int be_qsort_compare_BEs_name_rev(const void *, const void *); 76 static int be_qsort_compare_BEs_date(const void *, const void *); 77 static int be_qsort_compare_BEs_date_rev(const void *, const void *); 78 static int be_qsort_compare_BEs_space(const void *, const void *); 79 static int be_qsort_compare_BEs_space_rev(const void *, const void *); 80 static int be_qsort_compare_snapshots(const void *x, const void *y); 81 static int be_qsort_compare_datasets(const void *x, const void *y); 82 static void *be_list_alloc(int *, size_t); 83 84 /* 85 * Private data. 86 */ 87 static char be_container_ds[MAXPATHLEN]; 88 static boolean_t zone_be = B_FALSE; 89 90 /* ******************************************************************** */ 91 /* Public Functions */ 92 /* ******************************************************************** */ 93 94 /* 95 * Function: be_list 96 * Description: Calls _be_list which finds all the BEs on the system and 97 * returns the datasets and snapshots belonging to each BE. 98 * Also data, such as dataset and snapshot properties, 99 * for each BE and their snapshots and datasets is 100 * returned. The data returned is as described in the 101 * be_dataset_list_t, be_snapshot_list_t and be_node_list_t 102 * structures. 103 * Parameters: 104 * be_name - The name of the BE to look up. 105 * If NULL a list of all BEs will be returned. 106 * be_nodes - A reference pointer to the list of BEs. The list 107 * structure will be allocated by _be_list and must 108 * be freed by a call to be_free_list. If there are no 109 * BEs found on the system this reference will be 110 * set to NULL. 111 * Return: 112 * BE_SUCCESS - Success 113 * be_errno_t - Failure 114 * Scope: 115 * Public 116 */ 117 int 118 be_list(char *be_name, be_node_list_t **be_nodes) 119 { 120 int ret = BE_SUCCESS; 121 122 /* Initialize libzfs handle */ 123 if (!be_zfs_init()) 124 return (BE_ERR_INIT); 125 126 /* Validate be_name if its not NULL */ 127 if (be_name != NULL) { 128 if (!be_valid_be_name(be_name)) { 129 be_print_err(gettext("be_list: " 130 "invalid BE name %s\n"), be_name); 131 return (BE_ERR_INVAL); 132 } 133 } 134 135 ret = _be_list(be_name, be_nodes); 136 137 be_zfs_fini(); 138 139 return (ret); 140 } 141 142 /* 143 * Function: be_sort 144 * Description: Sort BE node list 145 * Parameters: 146 * pointer to address of list head 147 * sort order type 148 * Return: 149 * BE_SUCCESS - Success 150 * be_errno_t - Failure 151 * Side effect: 152 * node list sorted by name 153 * Scope: 154 * Public 155 */ 156 int 157 be_sort(be_node_list_t **be_nodes, int order) 158 { 159 int (*compar)(const void *, const void *) = be_qsort_compare_BEs_date; 160 161 if (be_nodes == NULL) 162 return (BE_ERR_INVAL); 163 164 switch (order) { 165 case BE_SORT_UNSPECIFIED: 166 case BE_SORT_DATE: 167 compar = be_qsort_compare_BEs_date; 168 break; 169 case BE_SORT_DATE_REV: 170 compar = be_qsort_compare_BEs_date_rev; 171 break; 172 case BE_SORT_NAME: 173 compar = be_qsort_compare_BEs_name; 174 break; 175 case BE_SORT_NAME_REV: 176 compar = be_qsort_compare_BEs_name_rev; 177 break; 178 case BE_SORT_SPACE: 179 compar = be_qsort_compare_BEs_space; 180 break; 181 case BE_SORT_SPACE_REV: 182 compar = be_qsort_compare_BEs_space_rev; 183 break; 184 default: 185 be_print_err(gettext("be_sort: invalid sort order %d\n"), 186 order); 187 return (BE_ERR_INVAL); 188 } 189 190 return (be_sort_list(be_nodes, compar)); 191 } 192 193 /* ******************************************************************** */ 194 /* Semi-Private Functions */ 195 /* ******************************************************************** */ 196 197 /* 198 * Function: _be_list 199 * Description: This does the actual work described in be_list. 200 * Parameters: 201 * be_name - The name of the BE to look up. 202 * If NULL a list of all BEs will be returned. 203 * be_nodes - A reference pointer to the list of BEs. The list 204 * structure will be allocated here and must 205 * be freed by a call to be_free_list. If there are no 206 * BEs found on the system this reference will be 207 * set to NULL. 208 * Return: 209 * BE_SUCCESS - Success 210 * be_errno_t - Failure 211 * Scope: 212 * Semi-private (library wide use only) 213 */ 214 int 215 _be_list(char *be_name, be_node_list_t **be_nodes) 216 { 217 list_callback_data_t cb = { 0 }; 218 be_transaction_data_t bt = { 0 }; 219 int ret = BE_SUCCESS; 220 int sret; 221 zpool_handle_t *zphp; 222 char *rpool = NULL; 223 struct be_defaults be_defaults; 224 225 if (be_nodes == NULL) 226 return (BE_ERR_INVAL); 227 228 be_get_defaults(&be_defaults); 229 230 if (be_find_current_be(&bt) != BE_SUCCESS) { 231 /* 232 * We were unable to find a currently booted BE which 233 * probably means that we're not booted in a BE envoronment. 234 * None of the BE's will be marked as the active BE. 235 */ 236 (void) strcpy(cb.current_be, "-"); 237 } else { 238 (void) strncpy(cb.current_be, bt.obe_name, 239 sizeof (cb.current_be)); 240 rpool = bt.obe_zpool; 241 } 242 243 /* 244 * If be_name is NULL we'll look for all BE's on the system. 245 * If not then we will only return data for the specified BE. 246 */ 247 if (be_name != NULL) 248 cb.be_name = strdup(be_name); 249 250 if (be_defaults.be_deflt_rpool_container && rpool != NULL) { 251 if ((zphp = zpool_open(g_zfs, rpool)) == NULL) { 252 be_print_err(gettext("be_list: failed to " 253 "open rpool (%s): %s\n"), rpool, 254 libzfs_error_description(g_zfs)); 255 free(cb.be_name); 256 return (zfs_err_to_be_err(g_zfs)); 257 } 258 259 ret = be_get_list_callback(zphp, &cb); 260 } else { 261 if ((zpool_iter(g_zfs, be_get_list_callback, &cb)) != 0) { 262 if (cb.be_nodes_head != NULL) { 263 be_free_list(cb.be_nodes_head); 264 cb.be_nodes_head = NULL; 265 cb.be_nodes = NULL; 266 } 267 ret = BE_ERR_BE_NOENT; 268 } 269 } 270 271 if (cb.be_nodes_head == NULL) { 272 if (be_name != NULL) 273 be_print_err(gettext("be_list: BE (%s) does not " 274 "exist\n"), be_name); 275 else 276 be_print_err(gettext("be_list: No BE's found\n")); 277 ret = BE_ERR_BE_NOENT; 278 } 279 280 *be_nodes = cb.be_nodes_head; 281 282 free(cb.be_name); 283 284 sret = be_sort(be_nodes, BE_SORT_DATE); 285 286 return ((ret == BE_SUCCESS) ? sret : ret); 287 } 288 289 /* 290 * Function: be_free_list 291 * Description: Frees up all the data allocated for the list of BEs, 292 * datasets and snapshots returned by be_list. 293 * Parameters: 294 * be_node - be_nodes_t structure returned from call to be_list. 295 * Returns: 296 * none 297 * Scope: 298 * Semi-private (library wide use only) 299 */ 300 void 301 be_free_list(be_node_list_t *be_nodes) 302 { 303 be_node_list_t *temp_node = NULL; 304 be_node_list_t *list = be_nodes; 305 306 while (list != NULL) { 307 be_dataset_list_t *datasets = list->be_node_datasets; 308 be_snapshot_list_t *snapshots = list->be_node_snapshots; 309 310 while (datasets != NULL) { 311 be_dataset_list_t *temp_ds = datasets; 312 datasets = datasets->be_next_dataset; 313 free(temp_ds->be_dataset_name); 314 free(temp_ds->be_ds_mntpt); 315 free(temp_ds->be_ds_plcy_type); 316 free(temp_ds); 317 } 318 319 while (snapshots != NULL) { 320 be_snapshot_list_t *temp_ss = snapshots; 321 snapshots = snapshots->be_next_snapshot; 322 free(temp_ss->be_snapshot_name); 323 free(temp_ss->be_snapshot_type); 324 free(temp_ss); 325 } 326 327 temp_node = list; 328 list = list->be_next_node; 329 free(temp_node->be_node_name); 330 free(temp_node->be_root_ds); 331 free(temp_node->be_rpool); 332 free(temp_node->be_mntpt); 333 free(temp_node->be_policy_type); 334 free(temp_node->be_uuid_str); 335 free(temp_node); 336 } 337 } 338 339 /* 340 * Function: be_get_zone_be_list 341 * Description: Finds all the BEs for this zone on the system. 342 * Parameters: 343 * zone_be_name - The name of the BE to look up. 344 * zone_be_container_ds - The dataset for the zone. 345 * zbe_nodes - A reference pointer to the list of BEs. The list 346 * structure will be allocated here and must 347 * be freed by a call to be_free_list. If there are no 348 * BEs found on the system this reference will be 349 * set to NULL. 350 * Return: 351 * BE_SUCCESS - Success 352 * be_errno_t - Failure 353 * Scope: 354 * Semi-private (library wide use only) 355 */ 356 int 357 be_get_zone_be_list( 358 /* LINTED */ 359 char *zone_be_name, 360 char *zone_be_container_ds, 361 be_node_list_t **zbe_nodes) 362 { 363 zfs_handle_t *zhp = NULL; 364 list_callback_data_t cb = { 0 }; 365 int ret = BE_SUCCESS; 366 367 if (zbe_nodes == NULL) 368 return (BE_ERR_INVAL); 369 370 if (!zfs_dataset_exists(g_zfs, zone_be_container_ds, 371 ZFS_TYPE_FILESYSTEM)) { 372 return (BE_ERR_BE_NOENT); 373 } 374 375 zone_be = B_TRUE; 376 377 if ((zhp = zfs_open(g_zfs, zone_be_container_ds, 378 ZFS_TYPE_FILESYSTEM)) == NULL) { 379 be_print_err(gettext("be_get_zone_be_list: failed to open " 380 "the zone BE dataset %s: %s\n"), zone_be_container_ds, 381 libzfs_error_description(g_zfs)); 382 ret = zfs_err_to_be_err(g_zfs); 383 goto cleanup; 384 } 385 386 (void) strcpy(be_container_ds, zone_be_container_ds); 387 388 if (cb.be_nodes_head == NULL) { 389 if ((cb.be_nodes_head = be_list_alloc(&ret, 390 sizeof (be_node_list_t))) == NULL) { 391 ZFS_CLOSE(zhp); 392 goto cleanup; 393 } 394 cb.be_nodes = cb.be_nodes_head; 395 } 396 if (ret == 0) 397 ret = zfs_iter_filesystems(zhp, be_add_children_callback, &cb); 398 ZFS_CLOSE(zhp); 399 400 *zbe_nodes = cb.be_nodes_head; 401 402 cleanup: 403 zone_be = B_FALSE; 404 405 return (ret); 406 } 407 408 /* ******************************************************************** */ 409 /* Private Functions */ 410 /* ******************************************************************** */ 411 412 /* 413 * Function: be_get_list_callback 414 * Description: Callback function used by zfs_iter to look through all 415 * the pools on the system looking for BEs. If a BE name was 416 * specified only that BE's information will be collected and 417 * returned. 418 * Parameters: 419 * zlp - handle to the first zfs dataset. (provided by the 420 * zfs_iter_* call) 421 * data - pointer to the callback data and where we'll pass 422 * the BE information back. 423 * Returns: 424 * 0 - Success 425 * be_errno_t - Failure 426 * Scope: 427 * Private 428 */ 429 static int 430 be_get_list_callback(zpool_handle_t *zlp, void *data) 431 { 432 list_callback_data_t *cb = (list_callback_data_t *)data; 433 char be_ds[MAXPATHLEN]; 434 char *open_ds = NULL; 435 char *rpool = NULL; 436 zfs_handle_t *zhp = NULL; 437 int ret = 0; 438 439 cb->zpool_name = rpool = (char *)zpool_get_name(zlp); 440 441 /* 442 * Generate string for the BE container dataset 443 */ 444 be_make_container_ds(rpool, be_container_ds, 445 sizeof (be_container_ds)); 446 447 /* 448 * If a BE name was specified we use it's root dataset in place of 449 * the container dataset. This is because we only want to collect 450 * the information for the specified BE. 451 */ 452 if (cb->be_name != NULL) { 453 if (!be_valid_be_name(cb->be_name)) 454 return (BE_ERR_INVAL); 455 /* 456 * Generate string for the BE root dataset 457 */ 458 be_make_root_ds(rpool, cb->be_name, be_ds, sizeof (be_ds)); 459 open_ds = be_ds; 460 } else { 461 open_ds = be_container_ds; 462 } 463 464 /* 465 * Check if the dataset exists 466 */ 467 if (!zfs_dataset_exists(g_zfs, open_ds, 468 ZFS_TYPE_FILESYSTEM)) { 469 /* 470 * The specified dataset does not exist in this pool or 471 * there are no valid BE's in this pool. Try the next zpool. 472 */ 473 zpool_close(zlp); 474 return (0); 475 } 476 477 if ((zhp = zfs_open(g_zfs, open_ds, ZFS_TYPE_FILESYSTEM)) == NULL) { 478 be_print_err(gettext("be_get_list_callback: failed to open " 479 "the BE dataset %s: %s\n"), open_ds, 480 libzfs_error_description(g_zfs)); 481 ret = zfs_err_to_be_err(g_zfs); 482 zpool_close(zlp); 483 return (ret); 484 } 485 486 /* 487 * If a BE name was specified we iterate through the datasets 488 * and snapshots for this BE only. Otherwise we will iterate 489 * through the next level of datasets to find all the BE's 490 * within the pool 491 */ 492 if (cb->be_name != NULL) { 493 if (cb->be_nodes_head == NULL) { 494 if ((cb->be_nodes_head = be_list_alloc(&ret, 495 sizeof (be_node_list_t))) == NULL) { 496 ZFS_CLOSE(zhp); 497 zpool_close(zlp); 498 return (ret); 499 } 500 cb->be_nodes = cb->be_nodes_head; 501 } 502 503 if ((ret = be_get_node_data(zhp, cb->be_nodes, cb->be_name, 504 rpool, cb->current_be, be_ds)) != BE_SUCCESS) { 505 ZFS_CLOSE(zhp); 506 zpool_close(zlp); 507 return (ret); 508 } 509 ret = zfs_iter_snapshots(zhp, B_FALSE, be_add_children_callback, 510 cb); 511 } 512 513 if (ret == 0) 514 ret = zfs_iter_filesystems(zhp, be_add_children_callback, cb); 515 ZFS_CLOSE(zhp); 516 517 zpool_close(zlp); 518 return (ret); 519 } 520 521 /* 522 * Function: be_add_children_callback 523 * Description: Callback function used by zfs_iter to look through all 524 * the datasets and snapshots for each BE and add them to 525 * the lists of information to be passed back. 526 * Parameters: 527 * zhp - handle to the first zfs dataset. (provided by the 528 * zfs_iter_* call) 529 * data - pointer to the callback data and where we'll pass 530 * the BE information back. 531 * Returns: 532 * 0 - Success 533 * be_errno_t - Failure 534 * Scope: 535 * Private 536 */ 537 static int 538 be_add_children_callback(zfs_handle_t *zhp, void *data) 539 { 540 list_callback_data_t *cb = (list_callback_data_t *)data; 541 char *str = NULL, *ds_path = NULL; 542 int ret = 0; 543 struct be_defaults be_defaults; 544 545 be_get_defaults(&be_defaults); 546 547 ds_path = str = strdup(zfs_get_name(zhp)); 548 549 /* 550 * get past the end of the container dataset plus the trailing "/" 551 */ 552 str = str + (strlen(be_container_ds) + 1); 553 if (be_defaults.be_deflt_rpool_container) { 554 /* just skip if invalid */ 555 if (!be_valid_be_name(str)) 556 return (BE_SUCCESS); 557 } 558 559 if (cb->be_nodes_head == NULL) { 560 if ((cb->be_nodes_head = be_list_alloc(&ret, 561 sizeof (be_node_list_t))) == NULL) { 562 ZFS_CLOSE(zhp); 563 return (ret); 564 } 565 cb->be_nodes = cb->be_nodes_head; 566 } 567 568 if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT && !zone_be) { 569 be_snapshot_list_t *snapshots = NULL; 570 if (cb->be_nodes->be_node_snapshots == NULL) { 571 if ((cb->be_nodes->be_node_snapshots = 572 be_list_alloc(&ret, sizeof (be_snapshot_list_t))) 573 == NULL || ret != BE_SUCCESS) { 574 ZFS_CLOSE(zhp); 575 return (ret); 576 } 577 cb->be_nodes->be_node_snapshots->be_next_snapshot = 578 NULL; 579 snapshots = cb->be_nodes->be_node_snapshots; 580 } else { 581 for (snapshots = cb->be_nodes->be_node_snapshots; 582 snapshots != NULL; 583 snapshots = snapshots->be_next_snapshot) { 584 if (snapshots->be_next_snapshot != NULL) 585 continue; 586 /* 587 * We're at the end of the list add the 588 * new snapshot. 589 */ 590 if ((snapshots->be_next_snapshot = 591 be_list_alloc(&ret, 592 sizeof (be_snapshot_list_t))) == NULL || 593 ret != BE_SUCCESS) { 594 ZFS_CLOSE(zhp); 595 return (ret); 596 } 597 snapshots = snapshots->be_next_snapshot; 598 snapshots->be_next_snapshot = NULL; 599 break; 600 } 601 } 602 if ((ret = be_get_ss_data(zhp, str, snapshots, 603 cb->be_nodes)) != BE_SUCCESS) { 604 ZFS_CLOSE(zhp); 605 return (ret); 606 } 607 } else if (strchr(str, '/') == NULL) { 608 if (cb->be_nodes->be_node_name != NULL) { 609 if ((cb->be_nodes->be_next_node = 610 be_list_alloc(&ret, sizeof (be_node_list_t))) == 611 NULL || ret != BE_SUCCESS) { 612 ZFS_CLOSE(zhp); 613 return (ret); 614 } 615 cb->be_nodes = cb->be_nodes->be_next_node; 616 cb->be_nodes->be_next_node = NULL; 617 } 618 619 /* 620 * If this is a zone root dataset then we only need 621 * the name of the zone BE at this point. We grab that 622 * and return. 623 */ 624 if (zone_be) { 625 ret = be_get_zone_node_data(cb->be_nodes, str); 626 ZFS_CLOSE(zhp); 627 return (ret); 628 } 629 630 if ((ret = be_get_node_data(zhp, cb->be_nodes, str, 631 cb->zpool_name, cb->current_be, ds_path)) != BE_SUCCESS) { 632 ZFS_CLOSE(zhp); 633 return (ret); 634 } 635 } else if (strchr(str, '/') != NULL && !zone_be) { 636 be_dataset_list_t *datasets = NULL; 637 if (cb->be_nodes->be_node_datasets == NULL) { 638 if ((cb->be_nodes->be_node_datasets = 639 be_list_alloc(&ret, sizeof (be_dataset_list_t))) 640 == NULL || ret != BE_SUCCESS) { 641 ZFS_CLOSE(zhp); 642 return (ret); 643 } 644 cb->be_nodes->be_node_datasets->be_next_dataset = NULL; 645 datasets = cb->be_nodes->be_node_datasets; 646 } else { 647 for (datasets = cb->be_nodes->be_node_datasets; 648 datasets != NULL; 649 datasets = datasets->be_next_dataset) { 650 if (datasets->be_next_dataset != NULL) 651 continue; 652 /* 653 * We're at the end of the list add 654 * the new dataset. 655 */ 656 if ((datasets->be_next_dataset = 657 be_list_alloc(&ret, 658 sizeof (be_dataset_list_t))) 659 == NULL || ret != BE_SUCCESS) { 660 ZFS_CLOSE(zhp); 661 return (ret); 662 } 663 datasets = datasets->be_next_dataset; 664 datasets->be_next_dataset = NULL; 665 break; 666 } 667 } 668 669 if ((ret = be_get_ds_data(zhp, str, 670 datasets, cb->be_nodes)) != BE_SUCCESS) { 671 ZFS_CLOSE(zhp); 672 return (ret); 673 } 674 } 675 ret = zfs_iter_children(zhp, be_add_children_callback, cb); 676 if (ret != 0) { 677 be_print_err(gettext("be_add_children_callback: " 678 "encountered error: %s\n"), 679 libzfs_error_description(g_zfs)); 680 ret = zfs_err_to_be_err(g_zfs); 681 } 682 ZFS_CLOSE(zhp); 683 return (ret); 684 } 685 686 /* 687 * Function: be_sort_list 688 * Description: Sort BE node list 689 * Parameters: 690 * pointer to address of list head 691 * compare function 692 * Return: 693 * BE_SUCCESS - Success 694 * be_errno_t - Failure 695 * Side effect: 696 * node list sorted by name 697 * Scope: 698 * Private 699 */ 700 static int 701 be_sort_list(be_node_list_t **pstart, int (*compar)(const void *, const void *)) 702 { 703 int ret = BE_SUCCESS; 704 size_t ibe, nbe; 705 be_node_list_t *p = NULL; 706 be_node_list_t **ptrlist = NULL; 707 be_node_list_t **ptrtmp; 708 709 if (pstart == NULL) /* Nothing to sort */ 710 return (BE_SUCCESS); 711 /* build array of linked list BE struct pointers */ 712 for (p = *pstart, nbe = 0; p != NULL; nbe++, p = p->be_next_node) { 713 ptrtmp = realloc(ptrlist, 714 sizeof (be_node_list_t *) * (nbe + 2)); 715 if (ptrtmp == NULL) { /* out of memory */ 716 be_print_err(gettext("be_sort_list: memory " 717 "allocation failed\n")); 718 ret = BE_ERR_NOMEM; 719 goto free; 720 } 721 ptrlist = ptrtmp; 722 ptrlist[nbe] = p; 723 } 724 if (nbe == 0) /* Nothing to sort */ 725 return (BE_SUCCESS); 726 /* in-place list quicksort using qsort(3C) */ 727 if (nbe > 1) /* no sort if less than 2 BEs */ 728 qsort(ptrlist, nbe, sizeof (be_node_list_t *), compar); 729 730 ptrlist[nbe] = NULL; /* add linked list terminator */ 731 *pstart = ptrlist[0]; /* set new linked list header */ 732 /* for each BE in list */ 733 for (ibe = 0; ibe < nbe; ibe++) { 734 size_t k, ns; /* subordinate index, count */ 735 736 /* rewrite list pointer chain, including terminator */ 737 ptrlist[ibe]->be_next_node = ptrlist[ibe + 1]; 738 /* sort subordinate snapshots */ 739 if (ptrlist[ibe]->be_node_num_snapshots > 1) { 740 const size_t nmax = ptrlist[ibe]->be_node_num_snapshots; 741 be_snapshot_list_t ** const slist = 742 malloc(sizeof (be_snapshot_list_t *) * (nmax + 1)); 743 be_snapshot_list_t *p; 744 745 if (slist == NULL) { 746 ret = BE_ERR_NOMEM; 747 continue; 748 } 749 /* build array of linked list snapshot struct ptrs */ 750 for (ns = 0, p = ptrlist[ibe]->be_node_snapshots; 751 ns < nmax && p != NULL; 752 ns++, p = p->be_next_snapshot) { 753 slist[ns] = p; 754 } 755 if (ns < 2) 756 goto end_snapshot; 757 slist[ns] = NULL; /* add terminator */ 758 /* in-place list quicksort using qsort(3C) */ 759 qsort(slist, ns, sizeof (be_snapshot_list_t *), 760 be_qsort_compare_snapshots); 761 /* rewrite list pointer chain, including terminator */ 762 ptrlist[ibe]->be_node_snapshots = slist[0]; 763 for (k = 0; k < ns; k++) 764 slist[k]->be_next_snapshot = slist[k + 1]; 765 end_snapshot: 766 free(slist); 767 } 768 /* sort subordinate datasets */ 769 if (ptrlist[ibe]->be_node_num_datasets > 1) { 770 const size_t nmax = ptrlist[ibe]->be_node_num_datasets; 771 be_dataset_list_t ** const slist = 772 malloc(sizeof (be_dataset_list_t *) * (nmax + 1)); 773 be_dataset_list_t *p; 774 775 if (slist == NULL) { 776 ret = BE_ERR_NOMEM; 777 continue; 778 } 779 /* build array of linked list dataset struct ptrs */ 780 for (ns = 0, p = ptrlist[ibe]->be_node_datasets; 781 ns < nmax && p != NULL; 782 ns++, p = p->be_next_dataset) { 783 slist[ns] = p; 784 } 785 if (ns < 2) /* subordinate datasets < 2 - no sort */ 786 goto end_dataset; 787 slist[ns] = NULL; /* add terminator */ 788 /* in-place list quicksort using qsort(3C) */ 789 qsort(slist, ns, sizeof (be_dataset_list_t *), 790 be_qsort_compare_datasets); 791 /* rewrite list pointer chain, including terminator */ 792 ptrlist[ibe]->be_node_datasets = slist[0]; 793 for (k = 0; k < ns; k++) 794 slist[k]->be_next_dataset = slist[k + 1]; 795 end_dataset: 796 free(slist); 797 } 798 } 799 free: 800 free(ptrlist); 801 return (ret); 802 } 803 804 /* 805 * Function: be_qsort_compare_BEs_date 806 * Description: compare BE creation times for qsort(3C) 807 * will sort BE list from oldest to most recent 808 * Parameters: 809 * x,y - BEs with names to compare 810 * Returns: 811 * positive if x>y, negative if y>x, 0 if equal 812 * Scope: 813 * Private 814 */ 815 static int 816 be_qsort_compare_BEs_date(const void *x, const void *y) 817 { 818 be_node_list_t *p = *(be_node_list_t **)x; 819 be_node_list_t *q = *(be_node_list_t **)y; 820 821 assert(p != NULL); 822 assert(q != NULL); 823 824 if (p->be_node_creation > q->be_node_creation) 825 return (1); 826 if (p->be_node_creation < q->be_node_creation) 827 return (-1); 828 return (0); 829 } 830 831 /* 832 * Function: be_qsort_compare_BEs_date_rev 833 * Description: compare BE creation times for qsort(3C) 834 * will sort BE list from recent to oldest 835 * Parameters: 836 * x,y - BEs with names to compare 837 * Returns: 838 * positive if y>x, negative if x>y, 0 if equal 839 * Scope: 840 * Private 841 */ 842 static int 843 be_qsort_compare_BEs_date_rev(const void *x, const void *y) 844 { 845 return (be_qsort_compare_BEs_date(y, x)); 846 } 847 848 /* 849 * Function: be_qsort_compare_BEs_name 850 * Description: lexical compare of BE names for qsort(3C) 851 * Parameters: 852 * x,y - BEs with names to compare 853 * Returns: 854 * positive if x>y, negative if y>x, 0 if equal 855 * Scope: 856 * Private 857 */ 858 static int 859 be_qsort_compare_BEs_name(const void *x, const void *y) 860 { 861 be_node_list_t *p = *(be_node_list_t **)x; 862 be_node_list_t *q = *(be_node_list_t **)y; 863 864 assert(p != NULL); 865 assert(p->be_node_name != NULL); 866 assert(q != NULL); 867 assert(q->be_node_name != NULL); 868 869 return (strcmp(p->be_node_name, q->be_node_name)); 870 } 871 872 /* 873 * Function: be_qsort_compare_BEs_name_rev 874 * Description: reverse lexical compare of BE names for qsort(3C) 875 * Parameters: 876 * x,y - BEs with names to compare 877 * Returns: 878 * positive if y>x, negative if x>y, 0 if equal 879 * Scope: 880 * Private 881 */ 882 static int 883 be_qsort_compare_BEs_name_rev(const void *x, const void *y) 884 { 885 return (be_qsort_compare_BEs_name(y, x)); 886 } 887 888 /* 889 * Function: be_qsort_compare_BEs_space 890 * Description: compare BE sizes for qsort(3C) 891 * will sort BE list in growing order 892 * Parameters: 893 * x,y - BEs with names to compare 894 * Returns: 895 * positive if x>y, negative if y>x, 0 if equal 896 * Scope: 897 * Private 898 */ 899 static int 900 be_qsort_compare_BEs_space(const void *x, const void *y) 901 { 902 be_node_list_t *p = *(be_node_list_t **)x; 903 be_node_list_t *q = *(be_node_list_t **)y; 904 905 assert(p != NULL); 906 assert(q != NULL); 907 908 if (p->be_space_used > q->be_space_used) 909 return (1); 910 if (p->be_space_used < q->be_space_used) 911 return (-1); 912 return (0); 913 } 914 915 /* 916 * Function: be_qsort_compare_BEs_space_rev 917 * Description: compare BE sizes for qsort(3C) 918 * will sort BE list in shrinking 919 * Parameters: 920 * x,y - BEs with names to compare 921 * Returns: 922 * positive if y>x, negative if x>y, 0 if equal 923 * Scope: 924 * Private 925 */ 926 static int 927 be_qsort_compare_BEs_space_rev(const void *x, const void *y) 928 { 929 return (be_qsort_compare_BEs_space(y, x)); 930 } 931 932 /* 933 * Function: be_qsort_compare_snapshots 934 * Description: lexical compare of BE names for qsort(3C) 935 * Parameters: 936 * x,y - BE snapshots with names to compare 937 * Returns: 938 * positive if y>x, negative if x>y, 0 if equal 939 * Scope: 940 * Private 941 */ 942 static int 943 be_qsort_compare_snapshots(const void *x, const void *y) 944 { 945 be_snapshot_list_t *p = *(be_snapshot_list_t **)x; 946 be_snapshot_list_t *q = *(be_snapshot_list_t **)y; 947 948 if (p == NULL || p->be_snapshot_name == NULL) 949 return (1); 950 if (q == NULL || q->be_snapshot_name == NULL) 951 return (-1); 952 return (strcmp(p->be_snapshot_name, q->be_snapshot_name)); 953 } 954 955 /* 956 * Function: be_qsort_compare_datasets 957 * Description: lexical compare of dataset names for qsort(3C) 958 * Parameters: 959 * x,y - BE snapshots with names to compare 960 * Returns: 961 * positive if y>x, negative if x>y, 0 if equal 962 * Scope: 963 * Private 964 */ 965 static int 966 be_qsort_compare_datasets(const void *x, const void *y) 967 { 968 be_dataset_list_t *p = *(be_dataset_list_t **)x; 969 be_dataset_list_t *q = *(be_dataset_list_t **)y; 970 971 if (p == NULL || p->be_dataset_name == NULL) 972 return (1); 973 if (q == NULL || q->be_dataset_name == NULL) 974 return (-1); 975 return (strcmp(p->be_dataset_name, q->be_dataset_name)); 976 } 977 978 /* 979 * Function: be_get_node_data 980 * Description: Helper function used to collect all the information to fill 981 * in the be_node_list structure to be returned by be_list. 982 * Parameters: 983 * zhp - Handle to the root dataset for the BE whose information 984 * we're collecting. 985 * be_node - a pointer to the node structure we're filling in. 986 * be_name - The BE name of the node whose information we're 987 * collecting. 988 * current_be - the name of the currently active BE. 989 * be_ds - The dataset name for the BE. 990 * 991 * Returns: 992 * BE_SUCCESS - Success 993 * be_errno_t - Failure 994 * Scope: 995 * Private 996 */ 997 static int 998 be_get_node_data( 999 zfs_handle_t *zhp, 1000 be_node_list_t *be_node, 1001 char *be_name, 1002 const char *rpool, 1003 char *current_be, 1004 char *be_ds) 1005 { 1006 char prop_buf[MAXPATHLEN]; 1007 nvlist_t *userprops = NULL; 1008 nvlist_t *propval = NULL; 1009 nvlist_t *zone_propval = NULL; 1010 char *prop_str = NULL; 1011 char *zone_prop_str = NULL; 1012 char *grub_default_bootfs = NULL; 1013 zpool_handle_t *zphp = NULL; 1014 int err = 0; 1015 1016 if (be_node == NULL || be_name == NULL || current_be == NULL || 1017 be_ds == NULL) { 1018 be_print_err(gettext("be_get_node_data: invalid arguments, " 1019 "can not be NULL\n")); 1020 return (BE_ERR_INVAL); 1021 } 1022 1023 errno = 0; 1024 1025 be_node->be_root_ds = strdup(be_ds); 1026 if ((err = errno) != 0 || be_node->be_root_ds == NULL) { 1027 be_print_err(gettext("be_get_node_data: failed to " 1028 "copy root dataset name\n")); 1029 return (errno_to_be_err(err)); 1030 } 1031 1032 be_node->be_node_name = strdup(be_name); 1033 if ((err = errno) != 0 || be_node->be_node_name == NULL) { 1034 be_print_err(gettext("be_get_node_data: failed to " 1035 "copy BE name\n")); 1036 return (errno_to_be_err(err)); 1037 } 1038 if (strncmp(be_name, current_be, MAXPATHLEN) == 0) 1039 be_node->be_active = B_TRUE; 1040 else 1041 be_node->be_active = B_FALSE; 1042 1043 be_node->be_rpool = strdup(rpool); 1044 if (be_node->be_rpool == NULL || (err = errno) != 0) { 1045 be_print_err(gettext("be_get_node_data: failed to " 1046 "copy root pool name\n")); 1047 return (errno_to_be_err(err)); 1048 } 1049 1050 be_node->be_space_used = zfs_prop_get_int(zhp, ZFS_PROP_USED); 1051 1052 if (getzoneid() == GLOBAL_ZONEID) { 1053 if ((zphp = zpool_open(g_zfs, rpool)) == NULL) { 1054 be_print_err(gettext("be_get_node_data: failed to open " 1055 "pool (%s): %s\n"), rpool, 1056 libzfs_error_description(g_zfs)); 1057 return (zfs_err_to_be_err(g_zfs)); 1058 } 1059 1060 (void) zpool_get_prop(zphp, ZPOOL_PROP_BOOTFS, prop_buf, 1061 ZFS_MAXPROPLEN, NULL, B_FALSE); 1062 if (be_has_grub() && (be_default_grub_bootfs(rpool, 1063 &grub_default_bootfs) == BE_SUCCESS) && 1064 grub_default_bootfs != NULL) 1065 if (strcmp(grub_default_bootfs, be_ds) == 0) 1066 be_node->be_active_on_boot = B_TRUE; 1067 else 1068 be_node->be_active_on_boot = B_FALSE; 1069 else if (prop_buf != NULL && strcmp(prop_buf, be_ds) == 0) 1070 be_node->be_active_on_boot = B_TRUE; 1071 else 1072 be_node->be_active_on_boot = B_FALSE; 1073 1074 be_node->be_global_active = B_TRUE; 1075 1076 free(grub_default_bootfs); 1077 zpool_close(zphp); 1078 } else { 1079 if (be_zone_compare_uuids(be_node->be_root_ds)) 1080 be_node->be_global_active = B_TRUE; 1081 else 1082 be_node->be_global_active = B_FALSE; 1083 } 1084 1085 /* 1086 * If the dataset is mounted use the mount point 1087 * returned from the zfs_is_mounted call. If the 1088 * dataset is not mounted then pull the mount 1089 * point information out of the zfs properties. 1090 */ 1091 be_node->be_mounted = zfs_is_mounted(zhp, 1092 &(be_node->be_mntpt)); 1093 if (!be_node->be_mounted) { 1094 if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, prop_buf, 1095 ZFS_MAXPROPLEN, NULL, NULL, 0, B_FALSE) == 0) 1096 be_node->be_mntpt = strdup(prop_buf); 1097 else 1098 return (zfs_err_to_be_err(g_zfs)); 1099 } 1100 1101 be_node->be_node_creation = (time_t)zfs_prop_get_int(zhp, 1102 ZFS_PROP_CREATION); 1103 1104 /* Get all user properties used for libbe */ 1105 if ((userprops = zfs_get_user_props(zhp)) == NULL) { 1106 be_node->be_policy_type = strdup(be_default_policy()); 1107 } else { 1108 if (getzoneid() != GLOBAL_ZONEID) { 1109 if (nvlist_lookup_nvlist(userprops, 1110 BE_ZONE_ACTIVE_PROPERTY, &zone_propval) != 0 || 1111 zone_propval == NULL) { 1112 be_node->be_active_on_boot = B_FALSE; 1113 } else { 1114 verify(nvlist_lookup_string(zone_propval, 1115 ZPROP_VALUE, &zone_prop_str) == 0); 1116 if (strcmp(zone_prop_str, "on") == 0) { 1117 be_node->be_active_on_boot = B_TRUE; 1118 } else { 1119 be_node->be_active_on_boot = B_FALSE; 1120 } 1121 } 1122 } 1123 1124 if (nvlist_lookup_nvlist(userprops, BE_POLICY_PROPERTY, 1125 &propval) != 0 || propval == NULL) { 1126 be_node->be_policy_type = 1127 strdup(be_default_policy()); 1128 } else { 1129 verify(nvlist_lookup_string(propval, ZPROP_VALUE, 1130 &prop_str) == 0); 1131 if (prop_str == NULL || strcmp(prop_str, "-") == 0 || 1132 strcmp(prop_str, "") == 0) 1133 be_node->be_policy_type = 1134 strdup(be_default_policy()); 1135 else 1136 be_node->be_policy_type = strdup(prop_str); 1137 } 1138 if (getzoneid() != GLOBAL_ZONEID) { 1139 if (nvlist_lookup_nvlist(userprops, 1140 BE_ZONE_PARENTBE_PROPERTY, &propval) != 0 && 1141 nvlist_lookup_string(propval, ZPROP_VALUE, 1142 &prop_str) == 0) { 1143 be_node->be_uuid_str = strdup(prop_str); 1144 } 1145 } else { 1146 if (nvlist_lookup_nvlist(userprops, BE_UUID_PROPERTY, 1147 &propval) == 0 && nvlist_lookup_string(propval, 1148 ZPROP_VALUE, &prop_str) == 0) { 1149 be_node->be_uuid_str = strdup(prop_str); 1150 } 1151 } 1152 } 1153 1154 /* 1155 * Increment the dataset counter to include the root dataset 1156 * of the BE. 1157 */ 1158 be_node->be_node_num_datasets++; 1159 1160 return (BE_SUCCESS); 1161 } 1162 1163 /* 1164 * Function: be_get_ds_data 1165 * Description: Helper function used by be_add_children_callback to collect 1166 * the dataset related information that will be returned by 1167 * be_list. 1168 * Parameters: 1169 * zhp - Handle to the zfs dataset whose information we're 1170 * collecting. 1171 * name - The name of the dataset we're processing. 1172 * dataset - A pointer to the be_dataset_list structure 1173 * we're filling in. 1174 * node - The node structure that this dataset belongs to. 1175 * Return: 1176 * BE_SUCCESS - Success 1177 * be_errno_t - Failure 1178 * Scope: 1179 * Private 1180 */ 1181 static int 1182 be_get_ds_data( 1183 zfs_handle_t *zfshp, 1184 char *name, 1185 be_dataset_list_t *dataset, 1186 be_node_list_t *node) 1187 { 1188 char prop_buf[ZFS_MAXPROPLEN]; 1189 nvlist_t *propval = NULL; 1190 nvlist_t *userprops = NULL; 1191 char *prop_str = NULL; 1192 int err = 0; 1193 1194 if (zfshp == NULL || name == NULL || dataset == NULL || node == NULL) { 1195 be_print_err(gettext("be_get_ds_data: invalid arguments, " 1196 "can not be NULL\n")); 1197 return (BE_ERR_INVAL); 1198 } 1199 1200 errno = 0; 1201 1202 dataset->be_dataset_name = strdup(name); 1203 if ((err = errno) != 0) { 1204 be_print_err(gettext("be_get_ds_data: failed to copy " 1205 "dataset name\n")); 1206 return (errno_to_be_err(err)); 1207 } 1208 1209 dataset->be_ds_space_used = zfs_prop_get_int(zfshp, ZFS_PROP_USED); 1210 1211 /* 1212 * If the dataset is mounted use the mount point 1213 * returned from the zfs_is_mounted call. If the 1214 * dataset is not mounted then pull the mount 1215 * point information out of the zfs properties. 1216 */ 1217 if (!(dataset->be_ds_mounted = zfs_is_mounted(zfshp, 1218 &(dataset->be_ds_mntpt)))) { 1219 if (zfs_prop_get(zfshp, ZFS_PROP_MOUNTPOINT, 1220 prop_buf, ZFS_MAXPROPLEN, NULL, NULL, 0, 1221 B_FALSE) == 0) 1222 dataset->be_ds_mntpt = strdup(prop_buf); 1223 else 1224 return (zfs_err_to_be_err(g_zfs)); 1225 } 1226 dataset->be_ds_creation = 1227 (time_t)zfs_prop_get_int(zfshp, ZFS_PROP_CREATION); 1228 1229 /* 1230 * Get the user property used for the libbe 1231 * cleaup policy 1232 */ 1233 if ((userprops = zfs_get_user_props(zfshp)) == NULL) { 1234 dataset->be_ds_plcy_type = 1235 strdup(node->be_policy_type); 1236 } else { 1237 if (nvlist_lookup_nvlist(userprops, 1238 BE_POLICY_PROPERTY, &propval) != 0 || 1239 propval == NULL) { 1240 dataset->be_ds_plcy_type = 1241 strdup(node->be_policy_type); 1242 } else { 1243 verify(nvlist_lookup_string(propval, 1244 ZPROP_VALUE, &prop_str) == 0); 1245 if (prop_str == NULL || 1246 strcmp(prop_str, "-") == 0 || 1247 strcmp(prop_str, "") == 0) 1248 dataset->be_ds_plcy_type 1249 = strdup(node->be_policy_type); 1250 else 1251 dataset->be_ds_plcy_type = strdup(prop_str); 1252 } 1253 } 1254 1255 node->be_node_num_datasets++; 1256 return (BE_SUCCESS); 1257 } 1258 1259 /* 1260 * Function: be_get_ss_data 1261 * Description: Helper function used by be_add_children_callback to collect 1262 * the dataset related information that will be returned by 1263 * be_list. 1264 * Parameters: 1265 * zhp - Handle to the zfs snapshot whose information we're 1266 * collecting. 1267 * name - The name of the snapshot we're processing. 1268 * shapshot - A pointer to the be_snapshot_list structure 1269 * we're filling in. 1270 * node - The node structure that this snapshot belongs to. 1271 * Returns: 1272 * BE_SUCCESS - Success 1273 * be_errno_t - Failure 1274 * Scope: 1275 * Private 1276 */ 1277 static int 1278 be_get_ss_data( 1279 zfs_handle_t *zfshp, 1280 char *name, 1281 be_snapshot_list_t *snapshot, 1282 be_node_list_t *node) 1283 { 1284 nvlist_t *propval = NULL; 1285 nvlist_t *userprops = NULL; 1286 char *prop_str = NULL; 1287 int err = 0; 1288 1289 if (zfshp == NULL || name == NULL || snapshot == NULL || node == NULL) { 1290 be_print_err(gettext("be_get_ss_data: invalid arguments, " 1291 "can not be NULL\n")); 1292 return (BE_ERR_INVAL); 1293 } 1294 1295 errno = 0; 1296 1297 snapshot->be_snapshot_name = strdup(name); 1298 if ((err = errno) != 0) { 1299 be_print_err(gettext("be_get_ss_data: failed to copy name\n")); 1300 return (errno_to_be_err(err)); 1301 } 1302 1303 snapshot->be_snapshot_creation = (time_t)zfs_prop_get_int(zfshp, 1304 ZFS_PROP_CREATION); 1305 1306 /* 1307 * Try to get this snapshot's cleanup policy from its 1308 * user properties first. If not there, use default 1309 * cleanup policy. 1310 */ 1311 if ((userprops = zfs_get_user_props(zfshp)) != NULL && 1312 nvlist_lookup_nvlist(userprops, BE_POLICY_PROPERTY, 1313 &propval) == 0 && nvlist_lookup_string(propval, 1314 ZPROP_VALUE, &prop_str) == 0) { 1315 snapshot->be_snapshot_type = 1316 strdup(prop_str); 1317 } else { 1318 snapshot->be_snapshot_type = 1319 strdup(be_default_policy()); 1320 } 1321 1322 snapshot->be_snapshot_space_used = zfs_prop_get_int(zfshp, 1323 ZFS_PROP_USED); 1324 1325 node->be_node_num_snapshots++; 1326 return (BE_SUCCESS); 1327 } 1328 1329 /* 1330 * Function: be_list_alloc 1331 * Description: Helper function used to allocate memory for the various 1332 * sructures that make up a BE node. 1333 * Parameters: 1334 * err - Used to return any errors encountered. 1335 * BE_SUCCESS - Success 1336 * BE_ERR_NOMEM - Allocation failure 1337 * size - The size of memory to allocate. 1338 * Returns: 1339 * Success - A pointer to the allocated memory 1340 * Failure - NULL 1341 * Scope: 1342 * Private 1343 */ 1344 static void* 1345 be_list_alloc(int *err, size_t size) 1346 { 1347 void *bep = NULL; 1348 1349 bep = calloc(1, size); 1350 if (bep == NULL) { 1351 be_print_err(gettext("be_list_alloc: memory " 1352 "allocation failed\n")); 1353 *err = BE_ERR_NOMEM; 1354 } 1355 *err = BE_SUCCESS; 1356 return (bep); 1357 } 1358 1359 /* 1360 * Function: be_get_zone_node_data 1361 * Description: Helper function used to collect all the information to 1362 * fill in the be_node_list structure to be returned by 1363 * be_get_zone_list. 1364 * Parameters: 1365 * be_node - a pointer to the node structure we're filling in. 1366 * be_name - The BE name of the node whose information we're 1367 * Returns: 1368 * BE_SUCCESS - Success 1369 * be_errno_t - Failure 1370 * Scope: 1371 * Private 1372 * 1373 * NOTE: This function currently only collects the zone BE name but when 1374 * support for beadm/libbe in a zone is provided it will need to fill 1375 * in the rest of the information needed for a zone BE. 1376 */ 1377 static int 1378 be_get_zone_node_data(be_node_list_t *be_node, char *be_name) 1379 { 1380 if ((be_node->be_node_name = strdup(be_name)) != NULL) 1381 return (BE_SUCCESS); 1382 return (BE_ERR_NOMEM); 1383 } 1384