1168404Spjd/* 2168404Spjd * CDDL HEADER START 3168404Spjd * 4168404Spjd * The contents of this file are subject to the terms of the 5168404Spjd * Common Development and Distribution License (the "License"). 6168404Spjd * You may not use this file except in compliance with the License. 7168404Spjd * 8168404Spjd * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9168404Spjd * or http://www.opensolaris.org/os/licensing. 10168404Spjd * See the License for the specific language governing permissions 11168404Spjd * and limitations under the License. 12168404Spjd * 13168404Spjd * When distributing Covered Code, include this CDDL HEADER in each 14168404Spjd * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15168404Spjd * If applicable, add the following below this CDDL HEADER, with the 16168404Spjd * fields enclosed by brackets "[]" replaced with your own identifying 17168404Spjd * information: Portions Copyright [yyyy] [name of copyright owner] 18168404Spjd * 19168404Spjd * CDDL HEADER END 20168404Spjd */ 21259850Sdelphij 22168404Spjd/* 23219089Spjd * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. 24307046Smav * Copyright (c) 2012 Pawel Jakub Dawidek. All rights reserved. 25259850Sdelphij * Copyright 2013 Nexenta Systems, Inc. All rights reserved. 26260183Sdelphij * Copyright (c) 2013 by Delphix. All rights reserved. 27168404Spjd */ 28168404Spjd 29168404Spjd#include <libintl.h> 30168404Spjd#include <libuutil.h> 31168404Spjd#include <stddef.h> 32168404Spjd#include <stdio.h> 33168404Spjd#include <stdlib.h> 34168404Spjd#include <strings.h> 35168404Spjd 36168404Spjd#include <libzfs.h> 37168404Spjd 38168404Spjd#include "zfs_util.h" 39168404Spjd#include "zfs_iter.h" 40168404Spjd 41168404Spjd/* 42168404Spjd * This is a private interface used to gather up all the datasets specified on 43168404Spjd * the command line so that we can iterate over them in order. 44168404Spjd * 45168404Spjd * First, we iterate over all filesystems, gathering them together into an 46168404Spjd * AVL tree. We report errors for any explicitly specified datasets 47168404Spjd * that we couldn't open. 48168404Spjd * 49168404Spjd * When finished, we have an AVL tree of ZFS handles. We go through and execute 50168404Spjd * the provided callback for each one, passing whatever data the user supplied. 51168404Spjd */ 52168404Spjd 53168404Spjdtypedef struct zfs_node { 54168404Spjd zfs_handle_t *zn_handle; 55168404Spjd uu_avl_node_t zn_avlnode; 56168404Spjd} zfs_node_t; 57168404Spjd 58168404Spjdtypedef struct callback_data { 59205199Sdelphij uu_avl_t *cb_avl; 60205199Sdelphij int cb_flags; 61205199Sdelphij zfs_type_t cb_types; 62205199Sdelphij zfs_sort_column_t *cb_sortcol; 63205199Sdelphij zprop_list_t **cb_proplist; 64205199Sdelphij int cb_depth_limit; 65205199Sdelphij int cb_depth; 66205199Sdelphij uint8_t cb_props_table[ZFS_NUM_PROPS]; 67168404Spjd} callback_data_t; 68168404Spjd 69168404Spjduu_avl_pool_t *avl_pool; 70168404Spjd 71168404Spjd/* 72185029Spjd * Include snaps if they were requested or if this a zfs list where types 73185029Spjd * were not specified and the "listsnapshots" property is set on this pool. 74185029Spjd */ 75260183Sdelphijstatic boolean_t 76185029Spjdzfs_include_snapshots(zfs_handle_t *zhp, callback_data_t *cb) 77185029Spjd{ 78185029Spjd zpool_handle_t *zph; 79185029Spjd 80185029Spjd if ((cb->cb_flags & ZFS_ITER_PROP_LISTSNAPS) == 0) 81185029Spjd return (cb->cb_types & ZFS_TYPE_SNAPSHOT); 82185029Spjd 83185029Spjd zph = zfs_get_pool_handle(zhp); 84185029Spjd return (zpool_get_prop_int(zph, ZPOOL_PROP_LISTSNAPS, NULL)); 85185029Spjd} 86185029Spjd 87185029Spjd/* 88185029Spjd * Called for each dataset. If the object is of an appropriate type, 89168404Spjd * add it to the avl tree and recurse over any children as necessary. 90168404Spjd */ 91185029Spjdstatic int 92168404Spjdzfs_callback(zfs_handle_t *zhp, void *data) 93168404Spjd{ 94168404Spjd callback_data_t *cb = data; 95296533Smav boolean_t should_close = B_TRUE; 96260183Sdelphij boolean_t include_snaps = zfs_include_snapshots(zhp, cb); 97260183Sdelphij boolean_t include_bmarks = (cb->cb_types & ZFS_TYPE_BOOKMARK); 98168404Spjd 99185029Spjd if ((zfs_get_type(zhp) & cb->cb_types) || 100185029Spjd ((zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT) && include_snaps)) { 101168404Spjd uu_avl_index_t idx; 102168404Spjd zfs_node_t *node = safe_malloc(sizeof (zfs_node_t)); 103168404Spjd 104168404Spjd node->zn_handle = zhp; 105168404Spjd uu_avl_node_init(node, &node->zn_avlnode, avl_pool); 106168404Spjd if (uu_avl_find(cb->cb_avl, node, cb->cb_sortcol, 107168404Spjd &idx) == NULL) { 108205198Sdelphij if (cb->cb_proplist) { 109205198Sdelphij if ((*cb->cb_proplist) && 110205198Sdelphij !(*cb->cb_proplist)->pl_all) 111205198Sdelphij zfs_prune_proplist(zhp, 112205198Sdelphij cb->cb_props_table); 113205198Sdelphij 114219089Spjd if (zfs_expand_proplist(zhp, cb->cb_proplist, 115259850Sdelphij (cb->cb_flags & ZFS_ITER_RECVD_PROPS), 116259850Sdelphij (cb->cb_flags & ZFS_ITER_LITERAL_PROPS)) 117205198Sdelphij != 0) { 118205198Sdelphij free(node); 119205198Sdelphij return (-1); 120205198Sdelphij } 121168404Spjd } 122168404Spjd uu_avl_insert(cb->cb_avl, node, idx); 123296533Smav should_close = B_FALSE; 124168404Spjd } else { 125168404Spjd free(node); 126168404Spjd } 127168404Spjd } 128168404Spjd 129168404Spjd /* 130168404Spjd * Recurse if necessary. 131168404Spjd */ 132205199Sdelphij if (cb->cb_flags & ZFS_ITER_RECURSE && 133205199Sdelphij ((cb->cb_flags & ZFS_ITER_DEPTH_LIMIT) == 0 || 134205199Sdelphij cb->cb_depth < cb->cb_depth_limit)) { 135205199Sdelphij cb->cb_depth++; 136185029Spjd if (zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) 137185029Spjd (void) zfs_iter_filesystems(zhp, zfs_callback, data); 138260183Sdelphij if (((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT | 139260183Sdelphij ZFS_TYPE_BOOKMARK)) == 0) && include_snaps) 140230438Spjd (void) zfs_iter_snapshots(zhp, 141230438Spjd (cb->cb_flags & ZFS_ITER_SIMPLE) != 0, zfs_callback, 142230438Spjd data); 143260183Sdelphij if (((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT | 144260183Sdelphij ZFS_TYPE_BOOKMARK)) == 0) && include_bmarks) 145260183Sdelphij (void) zfs_iter_bookmarks(zhp, zfs_callback, data); 146205199Sdelphij cb->cb_depth--; 147185029Spjd } 148168404Spjd 149296533Smav if (should_close) 150168404Spjd zfs_close(zhp); 151168404Spjd 152168404Spjd return (0); 153168404Spjd} 154168404Spjd 155168404Spjdint 156168404Spjdzfs_add_sort_column(zfs_sort_column_t **sc, const char *name, 157168404Spjd boolean_t reverse) 158168404Spjd{ 159168404Spjd zfs_sort_column_t *col; 160168404Spjd zfs_prop_t prop; 161168404Spjd 162185029Spjd if ((prop = zfs_name_to_prop(name)) == ZPROP_INVAL && 163168404Spjd !zfs_prop_user(name)) 164168404Spjd return (-1); 165168404Spjd 166168404Spjd col = safe_malloc(sizeof (zfs_sort_column_t)); 167168404Spjd 168168404Spjd col->sc_prop = prop; 169168404Spjd col->sc_reverse = reverse; 170185029Spjd if (prop == ZPROP_INVAL) { 171168404Spjd col->sc_user_prop = safe_malloc(strlen(name) + 1); 172168404Spjd (void) strcpy(col->sc_user_prop, name); 173168404Spjd } 174168404Spjd 175168404Spjd if (*sc == NULL) { 176168404Spjd col->sc_last = col; 177168404Spjd *sc = col; 178168404Spjd } else { 179168404Spjd (*sc)->sc_last->sc_next = col; 180168404Spjd (*sc)->sc_last = col; 181168404Spjd } 182168404Spjd 183168404Spjd return (0); 184168404Spjd} 185168404Spjd 186168404Spjdvoid 187168404Spjdzfs_free_sort_columns(zfs_sort_column_t *sc) 188168404Spjd{ 189168404Spjd zfs_sort_column_t *col; 190168404Spjd 191168404Spjd while (sc != NULL) { 192168404Spjd col = sc->sc_next; 193168404Spjd free(sc->sc_user_prop); 194168404Spjd free(sc); 195168404Spjd sc = col; 196168404Spjd } 197168404Spjd} 198168404Spjd 199230438Spjdboolean_t 200230438Spjdzfs_sort_only_by_name(const zfs_sort_column_t *sc) 201230438Spjd{ 202230438Spjd 203230438Spjd return (sc != NULL && sc->sc_next == NULL && 204230438Spjd sc->sc_prop == ZFS_PROP_NAME); 205230438Spjd} 206230438Spjd 207168404Spjd/* ARGSUSED */ 208168404Spjdstatic int 209168404Spjdzfs_compare(const void *larg, const void *rarg, void *unused) 210168404Spjd{ 211168404Spjd zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle; 212168404Spjd zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle; 213168404Spjd const char *lname = zfs_get_name(l); 214168404Spjd const char *rname = zfs_get_name(r); 215168404Spjd char *lat, *rat; 216168404Spjd uint64_t lcreate, rcreate; 217168404Spjd int ret; 218168404Spjd 219168404Spjd lat = (char *)strchr(lname, '@'); 220168404Spjd rat = (char *)strchr(rname, '@'); 221168404Spjd 222168404Spjd if (lat != NULL) 223168404Spjd *lat = '\0'; 224168404Spjd if (rat != NULL) 225168404Spjd *rat = '\0'; 226168404Spjd 227168404Spjd ret = strcmp(lname, rname); 228347605Smav if (ret == 0 && (lat != NULL || rat != NULL)) { 229168404Spjd /* 230168404Spjd * If we're comparing a dataset to one of its snapshots, we 231168404Spjd * always make the full dataset first. 232168404Spjd */ 233168404Spjd if (lat == NULL) { 234168404Spjd ret = -1; 235168404Spjd } else if (rat == NULL) { 236168404Spjd ret = 1; 237168404Spjd } else { 238168404Spjd /* 239168404Spjd * If we have two snapshots from the same dataset, then 240168404Spjd * we want to sort them according to creation time. We 241168404Spjd * use the hidden CREATETXG property to get an absolute 242168404Spjd * ordering of snapshots. 243168404Spjd */ 244168404Spjd lcreate = zfs_prop_get_int(l, ZFS_PROP_CREATETXG); 245168404Spjd rcreate = zfs_prop_get_int(r, ZFS_PROP_CREATETXG); 246168404Spjd 247230438Spjd /* 248230438Spjd * Both lcreate and rcreate being 0 means we don't have 249230438Spjd * properties and we should compare full name. 250230438Spjd */ 251230438Spjd if (lcreate == 0 && rcreate == 0) 252230438Spjd ret = strcmp(lat + 1, rat + 1); 253230438Spjd else if (lcreate < rcreate) 254168404Spjd ret = -1; 255168404Spjd else if (lcreate > rcreate) 256168404Spjd ret = 1; 257168404Spjd } 258168404Spjd } 259168404Spjd 260168404Spjd if (lat != NULL) 261168404Spjd *lat = '@'; 262168404Spjd if (rat != NULL) 263168404Spjd *rat = '@'; 264168404Spjd 265168404Spjd return (ret); 266168404Spjd} 267168404Spjd 268168404Spjd/* 269168404Spjd * Sort datasets by specified columns. 270168404Spjd * 271168404Spjd * o Numeric types sort in ascending order. 272168404Spjd * o String types sort in alphabetical order. 273168404Spjd * o Types inappropriate for a row sort that row to the literal 274168404Spjd * bottom, regardless of the specified ordering. 275168404Spjd * 276168404Spjd * If no sort columns are specified, or two datasets compare equally 277168404Spjd * across all specified columns, they are sorted alphabetically by name 278168404Spjd * with snapshots grouped under their parents. 279168404Spjd */ 280168404Spjdstatic int 281168404Spjdzfs_sort(const void *larg, const void *rarg, void *data) 282168404Spjd{ 283168404Spjd zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle; 284168404Spjd zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle; 285168404Spjd zfs_sort_column_t *sc = (zfs_sort_column_t *)data; 286168404Spjd zfs_sort_column_t *psc; 287168404Spjd 288168404Spjd for (psc = sc; psc != NULL; psc = psc->sc_next) { 289168404Spjd char lbuf[ZFS_MAXPROPLEN], rbuf[ZFS_MAXPROPLEN]; 290168404Spjd char *lstr, *rstr; 291168404Spjd uint64_t lnum, rnum; 292168404Spjd boolean_t lvalid, rvalid; 293168404Spjd int ret = 0; 294168404Spjd 295168404Spjd /* 296168404Spjd * We group the checks below the generic code. If 'lstr' and 297168404Spjd * 'rstr' are non-NULL, then we do a string based comparison. 298168404Spjd * Otherwise, we compare 'lnum' and 'rnum'. 299168404Spjd */ 300168404Spjd lstr = rstr = NULL; 301185029Spjd if (psc->sc_prop == ZPROP_INVAL) { 302168404Spjd nvlist_t *luser, *ruser; 303168404Spjd nvlist_t *lval, *rval; 304168404Spjd 305168404Spjd luser = zfs_get_user_props(l); 306168404Spjd ruser = zfs_get_user_props(r); 307168404Spjd 308168404Spjd lvalid = (nvlist_lookup_nvlist(luser, 309168404Spjd psc->sc_user_prop, &lval) == 0); 310168404Spjd rvalid = (nvlist_lookup_nvlist(ruser, 311168404Spjd psc->sc_user_prop, &rval) == 0); 312168404Spjd 313168404Spjd if (lvalid) 314168404Spjd verify(nvlist_lookup_string(lval, 315185029Spjd ZPROP_VALUE, &lstr) == 0); 316168404Spjd if (rvalid) 317168404Spjd verify(nvlist_lookup_string(rval, 318185029Spjd ZPROP_VALUE, &rstr) == 0); 319230438Spjd } else if (psc->sc_prop == ZFS_PROP_NAME) { 320230438Spjd lvalid = rvalid = B_TRUE; 321168404Spjd 322307046Smav (void) strlcpy(lbuf, zfs_get_name(l), sizeof (lbuf)); 323307046Smav (void) strlcpy(rbuf, zfs_get_name(r), sizeof (rbuf)); 324230438Spjd 325230438Spjd lstr = lbuf; 326230438Spjd rstr = rbuf; 327168404Spjd } else if (zfs_prop_is_string(psc->sc_prop)) { 328168404Spjd lvalid = (zfs_prop_get(l, psc->sc_prop, lbuf, 329168404Spjd sizeof (lbuf), NULL, NULL, 0, B_TRUE) == 0); 330168404Spjd rvalid = (zfs_prop_get(r, psc->sc_prop, rbuf, 331168404Spjd sizeof (rbuf), NULL, NULL, 0, B_TRUE) == 0); 332168404Spjd 333168404Spjd lstr = lbuf; 334168404Spjd rstr = rbuf; 335168404Spjd } else { 336168404Spjd lvalid = zfs_prop_valid_for_type(psc->sc_prop, 337168404Spjd zfs_get_type(l)); 338168404Spjd rvalid = zfs_prop_valid_for_type(psc->sc_prop, 339168404Spjd zfs_get_type(r)); 340168404Spjd 341168404Spjd if (lvalid) 342168404Spjd (void) zfs_prop_get_numeric(l, psc->sc_prop, 343168404Spjd &lnum, NULL, NULL, 0); 344168404Spjd if (rvalid) 345168404Spjd (void) zfs_prop_get_numeric(r, psc->sc_prop, 346168404Spjd &rnum, NULL, NULL, 0); 347168404Spjd } 348168404Spjd 349168404Spjd if (!lvalid && !rvalid) 350168404Spjd continue; 351168404Spjd else if (!lvalid) 352168404Spjd return (1); 353168404Spjd else if (!rvalid) 354168404Spjd return (-1); 355168404Spjd 356168404Spjd if (lstr) 357168404Spjd ret = strcmp(lstr, rstr); 358185029Spjd else if (lnum < rnum) 359168404Spjd ret = -1; 360168404Spjd else if (lnum > rnum) 361168404Spjd ret = 1; 362168404Spjd 363168404Spjd if (ret != 0) { 364168404Spjd if (psc->sc_reverse == B_TRUE) 365168404Spjd ret = (ret < 0) ? 1 : -1; 366168404Spjd return (ret); 367168404Spjd } 368168404Spjd } 369168404Spjd 370168404Spjd return (zfs_compare(larg, rarg, NULL)); 371168404Spjd} 372168404Spjd 373168404Spjdint 374185029Spjdzfs_for_each(int argc, char **argv, int flags, zfs_type_t types, 375205199Sdelphij zfs_sort_column_t *sortcol, zprop_list_t **proplist, int limit, 376185029Spjd zfs_iter_f callback, void *data) 377168404Spjd{ 378205198Sdelphij callback_data_t cb = {0}; 379168404Spjd int ret = 0; 380168404Spjd zfs_node_t *node; 381168404Spjd uu_avl_walk_t *walk; 382168404Spjd 383168404Spjd avl_pool = uu_avl_pool_create("zfs_pool", sizeof (zfs_node_t), 384168404Spjd offsetof(zfs_node_t, zn_avlnode), zfs_sort, UU_DEFAULT); 385168404Spjd 386219089Spjd if (avl_pool == NULL) 387219089Spjd nomem(); 388168404Spjd 389168404Spjd cb.cb_sortcol = sortcol; 390185029Spjd cb.cb_flags = flags; 391168404Spjd cb.cb_proplist = proplist; 392168404Spjd cb.cb_types = types; 393205199Sdelphij cb.cb_depth_limit = limit; 394205198Sdelphij /* 395219089Spjd * If cb_proplist is provided then in the zfs_handles created we 396205198Sdelphij * retain only those properties listed in cb_proplist and sortcol. 397205198Sdelphij * The rest are pruned. So, the caller should make sure that no other 398205198Sdelphij * properties other than those listed in cb_proplist/sortcol are 399205198Sdelphij * accessed. 400205198Sdelphij * 401205200Sdelphij * If cb_proplist is NULL then we retain all the properties. We 402205200Sdelphij * always retain the zoned property, which some other properties 403205200Sdelphij * need (userquota & friends), and the createtxg property, which 404205200Sdelphij * we need to sort snapshots. 405205198Sdelphij */ 406205198Sdelphij if (cb.cb_proplist && *cb.cb_proplist) { 407205198Sdelphij zprop_list_t *p = *cb.cb_proplist; 408205198Sdelphij 409205198Sdelphij while (p) { 410205198Sdelphij if (p->pl_prop >= ZFS_PROP_TYPE && 411205198Sdelphij p->pl_prop < ZFS_NUM_PROPS) { 412205198Sdelphij cb.cb_props_table[p->pl_prop] = B_TRUE; 413205198Sdelphij } 414205198Sdelphij p = p->pl_next; 415205198Sdelphij } 416205198Sdelphij 417205198Sdelphij while (sortcol) { 418205198Sdelphij if (sortcol->sc_prop >= ZFS_PROP_TYPE && 419205198Sdelphij sortcol->sc_prop < ZFS_NUM_PROPS) { 420205198Sdelphij cb.cb_props_table[sortcol->sc_prop] = B_TRUE; 421205198Sdelphij } 422205198Sdelphij sortcol = sortcol->sc_next; 423205198Sdelphij } 424205200Sdelphij 425205200Sdelphij cb.cb_props_table[ZFS_PROP_ZONED] = B_TRUE; 426205200Sdelphij cb.cb_props_table[ZFS_PROP_CREATETXG] = B_TRUE; 427205198Sdelphij } else { 428205198Sdelphij (void) memset(cb.cb_props_table, B_TRUE, 429205198Sdelphij sizeof (cb.cb_props_table)); 430205198Sdelphij } 431205198Sdelphij 432219089Spjd if ((cb.cb_avl = uu_avl_create(avl_pool, NULL, UU_DEFAULT)) == NULL) 433219089Spjd nomem(); 434168404Spjd 435168404Spjd if (argc == 0) { 436168404Spjd /* 437168404Spjd * If given no arguments, iterate over all datasets. 438168404Spjd */ 439185029Spjd cb.cb_flags |= ZFS_ITER_RECURSE; 440168404Spjd ret = zfs_iter_root(g_zfs, zfs_callback, &cb); 441168404Spjd } else { 442168404Spjd int i; 443168404Spjd zfs_handle_t *zhp; 444168404Spjd zfs_type_t argtype; 445168404Spjd 446168404Spjd /* 447168404Spjd * If we're recursive, then we always allow filesystems as 448351209Savg * arguments. If we also are interested in snapshots or 449351209Savg * bookmarks, then we can take volumes as well. 450168404Spjd */ 451168404Spjd argtype = types; 452185029Spjd if (flags & ZFS_ITER_RECURSE) { 453168404Spjd argtype |= ZFS_TYPE_FILESYSTEM; 454351209Savg if (types & (ZFS_TYPE_SNAPSHOT | ZFS_TYPE_BOOKMARK)) 455168404Spjd argtype |= ZFS_TYPE_VOLUME; 456168404Spjd } 457168404Spjd 458168404Spjd for (i = 0; i < argc; i++) { 459185029Spjd if (flags & ZFS_ITER_ARGS_CAN_BE_PATHS) { 460168404Spjd zhp = zfs_path_to_zhandle(g_zfs, argv[i], 461168404Spjd argtype); 462168404Spjd } else { 463168404Spjd zhp = zfs_open(g_zfs, argv[i], argtype); 464168404Spjd } 465168404Spjd if (zhp != NULL) 466168404Spjd ret |= zfs_callback(zhp, &cb); 467168404Spjd else 468168404Spjd ret = 1; 469168404Spjd } 470168404Spjd } 471168404Spjd 472168404Spjd /* 473168404Spjd * At this point we've got our AVL tree full of zfs handles, so iterate 474168404Spjd * over each one and execute the real user callback. 475168404Spjd */ 476168404Spjd for (node = uu_avl_first(cb.cb_avl); node != NULL; 477168404Spjd node = uu_avl_next(cb.cb_avl, node)) 478168404Spjd ret |= callback(node->zn_handle, data); 479168404Spjd 480168404Spjd /* 481168404Spjd * Finally, clean up the AVL tree. 482168404Spjd */ 483219089Spjd if ((walk = uu_avl_walk_start(cb.cb_avl, UU_WALK_ROBUST)) == NULL) 484219089Spjd nomem(); 485168404Spjd 486168404Spjd while ((node = uu_avl_walk_next(walk)) != NULL) { 487168404Spjd uu_avl_remove(cb.cb_avl, node); 488168404Spjd zfs_close(node->zn_handle); 489168404Spjd free(node); 490168404Spjd } 491168404Spjd 492168404Spjd uu_avl_walk_end(walk); 493168404Spjd uu_avl_destroy(cb.cb_avl); 494168404Spjd uu_avl_pool_destroy(avl_pool); 495168404Spjd 496168404Spjd return (ret); 497168404Spjd} 498