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