zfs_iter.c revision 296533
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. 24230438Spjd * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>. 25230438Spjd * All rights reserved. 26259850Sdelphij * Copyright 2013 Nexenta Systems, Inc. All rights reserved. 27260183Sdelphij * Copyright (c) 2013 by Delphix. All rights reserved. 28168404Spjd */ 29168404Spjd 30168404Spjd#include <libintl.h> 31168404Spjd#include <libuutil.h> 32168404Spjd#include <stddef.h> 33168404Spjd#include <stdio.h> 34168404Spjd#include <stdlib.h> 35168404Spjd#include <strings.h> 36168404Spjd 37168404Spjd#include <libzfs.h> 38168404Spjd 39168404Spjd#include "zfs_util.h" 40168404Spjd#include "zfs_iter.h" 41168404Spjd 42168404Spjd/* 43168404Spjd * This is a private interface used to gather up all the datasets specified on 44168404Spjd * the command line so that we can iterate over them in order. 45168404Spjd * 46168404Spjd * First, we iterate over all filesystems, gathering them together into an 47168404Spjd * AVL tree. We report errors for any explicitly specified datasets 48168404Spjd * that we couldn't open. 49168404Spjd * 50168404Spjd * When finished, we have an AVL tree of ZFS handles. We go through and execute 51168404Spjd * the provided callback for each one, passing whatever data the user supplied. 52168404Spjd */ 53168404Spjd 54168404Spjdtypedef struct zfs_node { 55168404Spjd zfs_handle_t *zn_handle; 56168404Spjd uu_avl_node_t zn_avlnode; 57168404Spjd} zfs_node_t; 58168404Spjd 59168404Spjdtypedef struct callback_data { 60205199Sdelphij uu_avl_t *cb_avl; 61205199Sdelphij int cb_flags; 62205199Sdelphij zfs_type_t cb_types; 63205199Sdelphij zfs_sort_column_t *cb_sortcol; 64205199Sdelphij zprop_list_t **cb_proplist; 65205199Sdelphij int cb_depth_limit; 66205199Sdelphij int cb_depth; 67205199Sdelphij uint8_t cb_props_table[ZFS_NUM_PROPS]; 68168404Spjd} callback_data_t; 69168404Spjd 70168404Spjduu_avl_pool_t *avl_pool; 71168404Spjd 72168404Spjd/* 73185029Spjd * Include snaps if they were requested or if this a zfs list where types 74185029Spjd * were not specified and the "listsnapshots" property is set on this pool. 75185029Spjd */ 76260183Sdelphijstatic boolean_t 77185029Spjdzfs_include_snapshots(zfs_handle_t *zhp, callback_data_t *cb) 78185029Spjd{ 79185029Spjd zpool_handle_t *zph; 80185029Spjd 81185029Spjd if ((cb->cb_flags & ZFS_ITER_PROP_LISTSNAPS) == 0) 82185029Spjd return (cb->cb_types & ZFS_TYPE_SNAPSHOT); 83185029Spjd 84185029Spjd zph = zfs_get_pool_handle(zhp); 85185029Spjd return (zpool_get_prop_int(zph, ZPOOL_PROP_LISTSNAPS, NULL)); 86185029Spjd} 87185029Spjd 88185029Spjd/* 89185029Spjd * Called for each dataset. If the object is of an appropriate type, 90168404Spjd * add it to the avl tree and recurse over any children as necessary. 91168404Spjd */ 92185029Spjdstatic int 93168404Spjdzfs_callback(zfs_handle_t *zhp, void *data) 94168404Spjd{ 95168404Spjd callback_data_t *cb = data; 96296533Smav boolean_t should_close = B_TRUE; 97260183Sdelphij boolean_t include_snaps = zfs_include_snapshots(zhp, cb); 98260183Sdelphij boolean_t include_bmarks = (cb->cb_types & ZFS_TYPE_BOOKMARK); 99168404Spjd 100185029Spjd if ((zfs_get_type(zhp) & cb->cb_types) || 101185029Spjd ((zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT) && include_snaps)) { 102168404Spjd uu_avl_index_t idx; 103168404Spjd zfs_node_t *node = safe_malloc(sizeof (zfs_node_t)); 104168404Spjd 105168404Spjd node->zn_handle = zhp; 106168404Spjd uu_avl_node_init(node, &node->zn_avlnode, avl_pool); 107168404Spjd if (uu_avl_find(cb->cb_avl, node, cb->cb_sortcol, 108168404Spjd &idx) == NULL) { 109205198Sdelphij if (cb->cb_proplist) { 110205198Sdelphij if ((*cb->cb_proplist) && 111205198Sdelphij !(*cb->cb_proplist)->pl_all) 112205198Sdelphij zfs_prune_proplist(zhp, 113205198Sdelphij cb->cb_props_table); 114205198Sdelphij 115219089Spjd if (zfs_expand_proplist(zhp, cb->cb_proplist, 116259850Sdelphij (cb->cb_flags & ZFS_ITER_RECVD_PROPS), 117259850Sdelphij (cb->cb_flags & ZFS_ITER_LITERAL_PROPS)) 118205198Sdelphij != 0) { 119205198Sdelphij free(node); 120205198Sdelphij return (-1); 121205198Sdelphij } 122168404Spjd } 123168404Spjd uu_avl_insert(cb->cb_avl, node, idx); 124296533Smav should_close = B_FALSE; 125168404Spjd } else { 126168404Spjd free(node); 127168404Spjd } 128168404Spjd } 129168404Spjd 130168404Spjd /* 131168404Spjd * Recurse if necessary. 132168404Spjd */ 133205199Sdelphij if (cb->cb_flags & ZFS_ITER_RECURSE && 134205199Sdelphij ((cb->cb_flags & ZFS_ITER_DEPTH_LIMIT) == 0 || 135205199Sdelphij cb->cb_depth < cb->cb_depth_limit)) { 136205199Sdelphij cb->cb_depth++; 137185029Spjd if (zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) 138185029Spjd (void) zfs_iter_filesystems(zhp, zfs_callback, data); 139260183Sdelphij if (((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT | 140260183Sdelphij ZFS_TYPE_BOOKMARK)) == 0) && include_snaps) 141230438Spjd (void) zfs_iter_snapshots(zhp, 142230438Spjd (cb->cb_flags & ZFS_ITER_SIMPLE) != 0, zfs_callback, 143230438Spjd data); 144260183Sdelphij if (((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT | 145260183Sdelphij ZFS_TYPE_BOOKMARK)) == 0) && include_bmarks) 146260183Sdelphij (void) zfs_iter_bookmarks(zhp, zfs_callback, data); 147205199Sdelphij cb->cb_depth--; 148185029Spjd } 149168404Spjd 150296533Smav if (should_close) 151168404Spjd zfs_close(zhp); 152168404Spjd 153168404Spjd return (0); 154168404Spjd} 155168404Spjd 156168404Spjdint 157168404Spjdzfs_add_sort_column(zfs_sort_column_t **sc, const char *name, 158168404Spjd boolean_t reverse) 159168404Spjd{ 160168404Spjd zfs_sort_column_t *col; 161168404Spjd zfs_prop_t prop; 162168404Spjd 163185029Spjd if ((prop = zfs_name_to_prop(name)) == ZPROP_INVAL && 164168404Spjd !zfs_prop_user(name)) 165168404Spjd return (-1); 166168404Spjd 167168404Spjd col = safe_malloc(sizeof (zfs_sort_column_t)); 168168404Spjd 169168404Spjd col->sc_prop = prop; 170168404Spjd col->sc_reverse = reverse; 171185029Spjd if (prop == ZPROP_INVAL) { 172168404Spjd col->sc_user_prop = safe_malloc(strlen(name) + 1); 173168404Spjd (void) strcpy(col->sc_user_prop, name); 174168404Spjd } 175168404Spjd 176168404Spjd if (*sc == NULL) { 177168404Spjd col->sc_last = col; 178168404Spjd *sc = col; 179168404Spjd } else { 180168404Spjd (*sc)->sc_last->sc_next = col; 181168404Spjd (*sc)->sc_last = col; 182168404Spjd } 183168404Spjd 184168404Spjd return (0); 185168404Spjd} 186168404Spjd 187168404Spjdvoid 188168404Spjdzfs_free_sort_columns(zfs_sort_column_t *sc) 189168404Spjd{ 190168404Spjd zfs_sort_column_t *col; 191168404Spjd 192168404Spjd while (sc != NULL) { 193168404Spjd col = sc->sc_next; 194168404Spjd free(sc->sc_user_prop); 195168404Spjd free(sc); 196168404Spjd sc = col; 197168404Spjd } 198168404Spjd} 199168404Spjd 200230438Spjdboolean_t 201230438Spjdzfs_sort_only_by_name(const zfs_sort_column_t *sc) 202230438Spjd{ 203230438Spjd 204230438Spjd return (sc != NULL && sc->sc_next == NULL && 205230438Spjd sc->sc_prop == ZFS_PROP_NAME); 206230438Spjd} 207230438Spjd 208168404Spjd/* ARGSUSED */ 209168404Spjdstatic int 210168404Spjdzfs_compare(const void *larg, const void *rarg, void *unused) 211168404Spjd{ 212168404Spjd zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle; 213168404Spjd zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle; 214168404Spjd const char *lname = zfs_get_name(l); 215168404Spjd const char *rname = zfs_get_name(r); 216168404Spjd char *lat, *rat; 217168404Spjd uint64_t lcreate, rcreate; 218168404Spjd int ret; 219168404Spjd 220168404Spjd lat = (char *)strchr(lname, '@'); 221168404Spjd rat = (char *)strchr(rname, '@'); 222168404Spjd 223168404Spjd if (lat != NULL) 224168404Spjd *lat = '\0'; 225168404Spjd if (rat != NULL) 226168404Spjd *rat = '\0'; 227168404Spjd 228168404Spjd ret = strcmp(lname, rname); 229168404Spjd if (ret == 0) { 230168404Spjd /* 231168404Spjd * If we're comparing a dataset to one of its snapshots, we 232168404Spjd * always make the full dataset first. 233168404Spjd */ 234168404Spjd if (lat == NULL) { 235168404Spjd ret = -1; 236168404Spjd } else if (rat == NULL) { 237168404Spjd ret = 1; 238168404Spjd } else { 239168404Spjd /* 240168404Spjd * If we have two snapshots from the same dataset, then 241168404Spjd * we want to sort them according to creation time. We 242168404Spjd * use the hidden CREATETXG property to get an absolute 243168404Spjd * ordering of snapshots. 244168404Spjd */ 245168404Spjd lcreate = zfs_prop_get_int(l, ZFS_PROP_CREATETXG); 246168404Spjd rcreate = zfs_prop_get_int(r, ZFS_PROP_CREATETXG); 247168404Spjd 248230438Spjd /* 249230438Spjd * Both lcreate and rcreate being 0 means we don't have 250230438Spjd * properties and we should compare full name. 251230438Spjd */ 252230438Spjd if (lcreate == 0 && rcreate == 0) 253230438Spjd ret = strcmp(lat + 1, rat + 1); 254230438Spjd else if (lcreate < rcreate) 255168404Spjd ret = -1; 256168404Spjd else if (lcreate > rcreate) 257168404Spjd ret = 1; 258168404Spjd } 259168404Spjd } 260168404Spjd 261168404Spjd if (lat != NULL) 262168404Spjd *lat = '@'; 263168404Spjd if (rat != NULL) 264168404Spjd *rat = '@'; 265168404Spjd 266168404Spjd return (ret); 267168404Spjd} 268168404Spjd 269168404Spjd/* 270168404Spjd * Sort datasets by specified columns. 271168404Spjd * 272168404Spjd * o Numeric types sort in ascending order. 273168404Spjd * o String types sort in alphabetical order. 274168404Spjd * o Types inappropriate for a row sort that row to the literal 275168404Spjd * bottom, regardless of the specified ordering. 276168404Spjd * 277168404Spjd * If no sort columns are specified, or two datasets compare equally 278168404Spjd * across all specified columns, they are sorted alphabetically by name 279168404Spjd * with snapshots grouped under their parents. 280168404Spjd */ 281168404Spjdstatic int 282168404Spjdzfs_sort(const void *larg, const void *rarg, void *data) 283168404Spjd{ 284168404Spjd zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle; 285168404Spjd zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle; 286168404Spjd zfs_sort_column_t *sc = (zfs_sort_column_t *)data; 287168404Spjd zfs_sort_column_t *psc; 288168404Spjd 289168404Spjd for (psc = sc; psc != NULL; psc = psc->sc_next) { 290168404Spjd char lbuf[ZFS_MAXPROPLEN], rbuf[ZFS_MAXPROPLEN]; 291168404Spjd char *lstr, *rstr; 292168404Spjd uint64_t lnum, rnum; 293168404Spjd boolean_t lvalid, rvalid; 294168404Spjd int ret = 0; 295168404Spjd 296168404Spjd /* 297168404Spjd * We group the checks below the generic code. If 'lstr' and 298168404Spjd * 'rstr' are non-NULL, then we do a string based comparison. 299168404Spjd * Otherwise, we compare 'lnum' and 'rnum'. 300168404Spjd */ 301168404Spjd lstr = rstr = NULL; 302185029Spjd if (psc->sc_prop == ZPROP_INVAL) { 303168404Spjd nvlist_t *luser, *ruser; 304168404Spjd nvlist_t *lval, *rval; 305168404Spjd 306168404Spjd luser = zfs_get_user_props(l); 307168404Spjd ruser = zfs_get_user_props(r); 308168404Spjd 309168404Spjd lvalid = (nvlist_lookup_nvlist(luser, 310168404Spjd psc->sc_user_prop, &lval) == 0); 311168404Spjd rvalid = (nvlist_lookup_nvlist(ruser, 312168404Spjd psc->sc_user_prop, &rval) == 0); 313168404Spjd 314168404Spjd if (lvalid) 315168404Spjd verify(nvlist_lookup_string(lval, 316185029Spjd ZPROP_VALUE, &lstr) == 0); 317168404Spjd if (rvalid) 318168404Spjd verify(nvlist_lookup_string(rval, 319185029Spjd ZPROP_VALUE, &rstr) == 0); 320230438Spjd } else if (psc->sc_prop == ZFS_PROP_NAME) { 321230438Spjd lvalid = rvalid = B_TRUE; 322168404Spjd 323230438Spjd (void) strlcpy(lbuf, zfs_get_name(l), sizeof(lbuf)); 324230438Spjd (void) strlcpy(rbuf, zfs_get_name(r), sizeof(rbuf)); 325230438Spjd 326230438Spjd lstr = lbuf; 327230438Spjd rstr = rbuf; 328168404Spjd } else if (zfs_prop_is_string(psc->sc_prop)) { 329168404Spjd lvalid = (zfs_prop_get(l, psc->sc_prop, lbuf, 330168404Spjd sizeof (lbuf), NULL, NULL, 0, B_TRUE) == 0); 331168404Spjd rvalid = (zfs_prop_get(r, psc->sc_prop, rbuf, 332168404Spjd sizeof (rbuf), NULL, NULL, 0, B_TRUE) == 0); 333168404Spjd 334168404Spjd lstr = lbuf; 335168404Spjd rstr = rbuf; 336168404Spjd } else { 337168404Spjd lvalid = zfs_prop_valid_for_type(psc->sc_prop, 338168404Spjd zfs_get_type(l)); 339168404Spjd rvalid = zfs_prop_valid_for_type(psc->sc_prop, 340168404Spjd zfs_get_type(r)); 341168404Spjd 342168404Spjd if (lvalid) 343168404Spjd (void) zfs_prop_get_numeric(l, psc->sc_prop, 344168404Spjd &lnum, NULL, NULL, 0); 345168404Spjd if (rvalid) 346168404Spjd (void) zfs_prop_get_numeric(r, psc->sc_prop, 347168404Spjd &rnum, NULL, NULL, 0); 348168404Spjd } 349168404Spjd 350168404Spjd if (!lvalid && !rvalid) 351168404Spjd continue; 352168404Spjd else if (!lvalid) 353168404Spjd return (1); 354168404Spjd else if (!rvalid) 355168404Spjd return (-1); 356168404Spjd 357168404Spjd if (lstr) 358168404Spjd ret = strcmp(lstr, rstr); 359185029Spjd else if (lnum < rnum) 360168404Spjd ret = -1; 361168404Spjd else if (lnum > rnum) 362168404Spjd ret = 1; 363168404Spjd 364168404Spjd if (ret != 0) { 365168404Spjd if (psc->sc_reverse == B_TRUE) 366168404Spjd ret = (ret < 0) ? 1 : -1; 367168404Spjd return (ret); 368168404Spjd } 369168404Spjd } 370168404Spjd 371168404Spjd return (zfs_compare(larg, rarg, NULL)); 372168404Spjd} 373168404Spjd 374168404Spjdint 375185029Spjdzfs_for_each(int argc, char **argv, int flags, zfs_type_t types, 376205199Sdelphij zfs_sort_column_t *sortcol, zprop_list_t **proplist, int limit, 377185029Spjd zfs_iter_f callback, void *data) 378168404Spjd{ 379205198Sdelphij callback_data_t cb = {0}; 380168404Spjd int ret = 0; 381168404Spjd zfs_node_t *node; 382168404Spjd uu_avl_walk_t *walk; 383168404Spjd 384168404Spjd avl_pool = uu_avl_pool_create("zfs_pool", sizeof (zfs_node_t), 385168404Spjd offsetof(zfs_node_t, zn_avlnode), zfs_sort, UU_DEFAULT); 386168404Spjd 387219089Spjd if (avl_pool == NULL) 388219089Spjd nomem(); 389168404Spjd 390168404Spjd cb.cb_sortcol = sortcol; 391185029Spjd cb.cb_flags = flags; 392168404Spjd cb.cb_proplist = proplist; 393168404Spjd cb.cb_types = types; 394205199Sdelphij cb.cb_depth_limit = limit; 395205198Sdelphij /* 396219089Spjd * If cb_proplist is provided then in the zfs_handles created we 397205198Sdelphij * retain only those properties listed in cb_proplist and sortcol. 398205198Sdelphij * The rest are pruned. So, the caller should make sure that no other 399205198Sdelphij * properties other than those listed in cb_proplist/sortcol are 400205198Sdelphij * accessed. 401205198Sdelphij * 402205200Sdelphij * If cb_proplist is NULL then we retain all the properties. We 403205200Sdelphij * always retain the zoned property, which some other properties 404205200Sdelphij * need (userquota & friends), and the createtxg property, which 405205200Sdelphij * we need to sort snapshots. 406205198Sdelphij */ 407205198Sdelphij if (cb.cb_proplist && *cb.cb_proplist) { 408205198Sdelphij zprop_list_t *p = *cb.cb_proplist; 409205198Sdelphij 410205198Sdelphij while (p) { 411205198Sdelphij if (p->pl_prop >= ZFS_PROP_TYPE && 412205198Sdelphij p->pl_prop < ZFS_NUM_PROPS) { 413205198Sdelphij cb.cb_props_table[p->pl_prop] = B_TRUE; 414205198Sdelphij } 415205198Sdelphij p = p->pl_next; 416205198Sdelphij } 417205198Sdelphij 418205198Sdelphij while (sortcol) { 419205198Sdelphij if (sortcol->sc_prop >= ZFS_PROP_TYPE && 420205198Sdelphij sortcol->sc_prop < ZFS_NUM_PROPS) { 421205198Sdelphij cb.cb_props_table[sortcol->sc_prop] = B_TRUE; 422205198Sdelphij } 423205198Sdelphij sortcol = sortcol->sc_next; 424205198Sdelphij } 425205200Sdelphij 426205200Sdelphij cb.cb_props_table[ZFS_PROP_ZONED] = B_TRUE; 427205200Sdelphij cb.cb_props_table[ZFS_PROP_CREATETXG] = B_TRUE; 428205198Sdelphij } else { 429205198Sdelphij (void) memset(cb.cb_props_table, B_TRUE, 430205198Sdelphij sizeof (cb.cb_props_table)); 431205198Sdelphij } 432205198Sdelphij 433219089Spjd if ((cb.cb_avl = uu_avl_create(avl_pool, NULL, UU_DEFAULT)) == NULL) 434219089Spjd nomem(); 435168404Spjd 436168404Spjd if (argc == 0) { 437168404Spjd /* 438168404Spjd * If given no arguments, iterate over all datasets. 439168404Spjd */ 440185029Spjd cb.cb_flags |= ZFS_ITER_RECURSE; 441168404Spjd ret = zfs_iter_root(g_zfs, zfs_callback, &cb); 442168404Spjd } else { 443168404Spjd int i; 444168404Spjd zfs_handle_t *zhp; 445168404Spjd zfs_type_t argtype; 446168404Spjd 447168404Spjd /* 448168404Spjd * If we're recursive, then we always allow filesystems as 449168404Spjd * arguments. If we also are interested in snapshots, then we 450168404Spjd * can take volumes as well. 451168404Spjd */ 452168404Spjd argtype = types; 453185029Spjd if (flags & ZFS_ITER_RECURSE) { 454168404Spjd argtype |= ZFS_TYPE_FILESYSTEM; 455168404Spjd if (types & ZFS_TYPE_SNAPSHOT) 456168404Spjd argtype |= ZFS_TYPE_VOLUME; 457168404Spjd } 458168404Spjd 459168404Spjd for (i = 0; i < argc; i++) { 460185029Spjd if (flags & ZFS_ITER_ARGS_CAN_BE_PATHS) { 461168404Spjd zhp = zfs_path_to_zhandle(g_zfs, argv[i], 462168404Spjd argtype); 463168404Spjd } else { 464168404Spjd zhp = zfs_open(g_zfs, argv[i], argtype); 465168404Spjd } 466168404Spjd if (zhp != NULL) 467168404Spjd ret |= zfs_callback(zhp, &cb); 468168404Spjd else 469168404Spjd ret = 1; 470168404Spjd } 471168404Spjd } 472168404Spjd 473168404Spjd /* 474168404Spjd * At this point we've got our AVL tree full of zfs handles, so iterate 475168404Spjd * over each one and execute the real user callback. 476168404Spjd */ 477168404Spjd for (node = uu_avl_first(cb.cb_avl); node != NULL; 478168404Spjd node = uu_avl_next(cb.cb_avl, node)) 479168404Spjd ret |= callback(node->zn_handle, data); 480168404Spjd 481168404Spjd /* 482168404Spjd * Finally, clean up the AVL tree. 483168404Spjd */ 484219089Spjd if ((walk = uu_avl_walk_start(cb.cb_avl, UU_WALK_ROBUST)) == NULL) 485219089Spjd nomem(); 486168404Spjd 487168404Spjd while ((node = uu_avl_walk_next(walk)) != NULL) { 488168404Spjd uu_avl_remove(cb.cb_avl, node); 489168404Spjd zfs_close(node->zn_handle); 490168404Spjd free(node); 491168404Spjd } 492168404Spjd 493168404Spjd uu_avl_walk_end(walk); 494168404Spjd uu_avl_destroy(cb.cb_avl); 495168404Spjd uu_avl_pool_destroy(avl_pool); 496168404Spjd 497168404Spjd return (ret); 498168404Spjd} 499