linux/fs/xfs/xfs_attr_list.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2000-2005 Silicon Graphics, Inc.
   3 * Copyright (c) 2013 Red Hat, Inc.
   4 * All Rights Reserved.
   5 *
   6 * This program is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU General Public License as
   8 * published by the Free Software Foundation.
   9 *
  10 * This program is distributed in the hope that it would be useful,
  11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13 * GNU General Public License for more details.
  14 *
  15 * You should have received a copy of the GNU General Public License
  16 * along with this program; if not, write the Free Software Foundation,
  17 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  18 */
  19#include "xfs.h"
  20#include "xfs_fs.h"
  21#include "xfs_format.h"
  22#include "xfs_log_format.h"
  23#include "xfs_trans_resv.h"
  24#include "xfs_bit.h"
  25#include "xfs_mount.h"
  26#include "xfs_da_format.h"
  27#include "xfs_da_btree.h"
  28#include "xfs_inode.h"
  29#include "xfs_trans.h"
  30#include "xfs_inode_item.h"
  31#include "xfs_bmap.h"
  32#include "xfs_attr.h"
  33#include "xfs_attr_sf.h"
  34#include "xfs_attr_remote.h"
  35#include "xfs_attr_leaf.h"
  36#include "xfs_error.h"
  37#include "xfs_trace.h"
  38#include "xfs_buf_item.h"
  39#include "xfs_cksum.h"
  40#include "xfs_dir2.h"
  41
  42STATIC int
  43xfs_attr_shortform_compare(const void *a, const void *b)
  44{
  45        xfs_attr_sf_sort_t *sa, *sb;
  46
  47        sa = (xfs_attr_sf_sort_t *)a;
  48        sb = (xfs_attr_sf_sort_t *)b;
  49        if (sa->hash < sb->hash) {
  50                return -1;
  51        } else if (sa->hash > sb->hash) {
  52                return 1;
  53        } else {
  54                return sa->entno - sb->entno;
  55        }
  56}
  57
  58#define XFS_ISRESET_CURSOR(cursor) \
  59        (!((cursor)->initted) && !((cursor)->hashval) && \
  60         !((cursor)->blkno) && !((cursor)->offset))
  61/*
  62 * Copy out entries of shortform attribute lists for attr_list().
  63 * Shortform attribute lists are not stored in hashval sorted order.
  64 * If the output buffer is not large enough to hold them all, then we
  65 * we have to calculate each entries' hashvalue and sort them before
  66 * we can begin returning them to the user.
  67 */
  68int
  69xfs_attr_shortform_list(xfs_attr_list_context_t *context)
  70{
  71        attrlist_cursor_kern_t *cursor;
  72        xfs_attr_sf_sort_t *sbuf, *sbp;
  73        xfs_attr_shortform_t *sf;
  74        xfs_attr_sf_entry_t *sfe;
  75        xfs_inode_t *dp;
  76        int sbsize, nsbuf, count, i;
  77        int error;
  78
  79        ASSERT(context != NULL);
  80        dp = context->dp;
  81        ASSERT(dp != NULL);
  82        ASSERT(dp->i_afp != NULL);
  83        sf = (xfs_attr_shortform_t *)dp->i_afp->if_u1.if_data;
  84        ASSERT(sf != NULL);
  85        if (!sf->hdr.count)
  86                return 0;
  87        cursor = context->cursor;
  88        ASSERT(cursor != NULL);
  89
  90        trace_xfs_attr_list_sf(context);
  91
  92        /*
  93         * If the buffer is large enough and the cursor is at the start,
  94         * do not bother with sorting since we will return everything in
  95         * one buffer and another call using the cursor won't need to be
  96         * made.
  97         * Note the generous fudge factor of 16 overhead bytes per entry.
  98         * If bufsize is zero then put_listent must be a search function
  99         * and can just scan through what we have.
 100         */
 101        if (context->bufsize == 0 ||
 102            (XFS_ISRESET_CURSOR(cursor) &&
 103             (dp->i_afp->if_bytes + sf->hdr.count * 16) < context->bufsize)) {
 104                for (i = 0, sfe = &sf->list[0]; i < sf->hdr.count; i++) {
 105                        error = context->put_listent(context,
 106                                           sfe->flags,
 107                                           sfe->nameval,
 108                                           (int)sfe->namelen,
 109                                           (int)sfe->valuelen,
 110                                           &sfe->nameval[sfe->namelen]);
 111
 112                        /*
 113                         * Either search callback finished early or
 114                         * didn't fit it all in the buffer after all.
 115                         */
 116                        if (context->seen_enough)
 117                                break;
 118
 119                        if (error)
 120                                return error;
 121                        sfe = XFS_ATTR_SF_NEXTENTRY(sfe);
 122                }
 123                trace_xfs_attr_list_sf_all(context);
 124                return 0;
 125        }
 126
 127        /* do no more for a search callback */
 128        if (context->bufsize == 0)
 129                return 0;
 130
 131        /*
 132         * It didn't all fit, so we have to sort everything on hashval.
 133         */
 134        sbsize = sf->hdr.count * sizeof(*sbuf);
 135        sbp = sbuf = kmem_alloc(sbsize, KM_SLEEP | KM_NOFS);
 136
 137        /*
 138         * Scan the attribute list for the rest of the entries, storing
 139         * the relevant info from only those that match into a buffer.
 140         */
 141        nsbuf = 0;
 142        for (i = 0, sfe = &sf->list[0]; i < sf->hdr.count; i++) {
 143                if (unlikely(
 144                    ((char *)sfe < (char *)sf) ||
 145                    ((char *)sfe >= ((char *)sf + dp->i_afp->if_bytes)))) {
 146                        XFS_CORRUPTION_ERROR("xfs_attr_shortform_list",
 147                                             XFS_ERRLEVEL_LOW,
 148                                             context->dp->i_mount, sfe);
 149                        kmem_free(sbuf);
 150                        return -EFSCORRUPTED;
 151                }
 152
 153                sbp->entno = i;
 154                sbp->hash = xfs_da_hashname(sfe->nameval, sfe->namelen);
 155                sbp->name = sfe->nameval;
 156                sbp->namelen = sfe->namelen;
 157                /* These are bytes, and both on-disk, don't endian-flip */
 158                sbp->valuelen = sfe->valuelen;
 159                sbp->flags = sfe->flags;
 160                sfe = XFS_ATTR_SF_NEXTENTRY(sfe);
 161                sbp++;
 162                nsbuf++;
 163        }
 164
 165        /*
 166         * Sort the entries on hash then entno.
 167         */
 168        xfs_sort(sbuf, nsbuf, sizeof(*sbuf), xfs_attr_shortform_compare);
 169
 170        /*
 171         * Re-find our place IN THE SORTED LIST.
 172         */
 173        count = 0;
 174        cursor->initted = 1;
 175        cursor->blkno = 0;
 176        for (sbp = sbuf, i = 0; i < nsbuf; i++, sbp++) {
 177                if (sbp->hash == cursor->hashval) {
 178                        if (cursor->offset == count) {
 179                                break;
 180                        }
 181                        count++;
 182                } else if (sbp->hash > cursor->hashval) {
 183                        break;
 184                }
 185        }
 186        if (i == nsbuf) {
 187                kmem_free(sbuf);
 188                return 0;
 189        }
 190
 191        /*
 192         * Loop putting entries into the user buffer.
 193         */
 194        for ( ; i < nsbuf; i++, sbp++) {
 195                if (cursor->hashval != sbp->hash) {
 196                        cursor->hashval = sbp->hash;
 197                        cursor->offset = 0;
 198                }
 199                error = context->put_listent(context,
 200                                        sbp->flags,
 201                                        sbp->name,
 202                                        sbp->namelen,
 203                                        sbp->valuelen,
 204                                        &sbp->name[sbp->namelen]);
 205                if (error)
 206                        return error;
 207                if (context->seen_enough)
 208                        break;
 209                cursor->offset++;
 210        }
 211
 212        kmem_free(sbuf);
 213        return 0;
 214}
 215
 216STATIC int
 217xfs_attr_node_list(xfs_attr_list_context_t *context)
 218{
 219        attrlist_cursor_kern_t *cursor;
 220        xfs_attr_leafblock_t *leaf;
 221        xfs_da_intnode_t *node;
 222        struct xfs_attr3_icleaf_hdr leafhdr;
 223        struct xfs_da3_icnode_hdr nodehdr;
 224        struct xfs_da_node_entry *btree;
 225        int error, i;
 226        struct xfs_buf *bp;
 227        struct xfs_inode        *dp = context->dp;
 228        struct xfs_mount        *mp = dp->i_mount;
 229
 230        trace_xfs_attr_node_list(context);
 231
 232        cursor = context->cursor;
 233        cursor->initted = 1;
 234
 235        /*
 236         * Do all sorts of validation on the passed-in cursor structure.
 237         * If anything is amiss, ignore the cursor and look up the hashval
 238         * starting from the btree root.
 239         */
 240        bp = NULL;
 241        if (cursor->blkno > 0) {
 242                error = xfs_da3_node_read(NULL, dp, cursor->blkno, -1,
 243                                              &bp, XFS_ATTR_FORK);
 244                if ((error != 0) && (error != -EFSCORRUPTED))
 245                        return error;
 246                if (bp) {
 247                        struct xfs_attr_leaf_entry *entries;
 248
 249                        node = bp->b_addr;
 250                        switch (be16_to_cpu(node->hdr.info.magic)) {
 251                        case XFS_DA_NODE_MAGIC:
 252                        case XFS_DA3_NODE_MAGIC:
 253                                trace_xfs_attr_list_wrong_blk(context);
 254                                xfs_trans_brelse(NULL, bp);
 255                                bp = NULL;
 256                                break;
 257                        case XFS_ATTR_LEAF_MAGIC:
 258                        case XFS_ATTR3_LEAF_MAGIC:
 259                                leaf = bp->b_addr;
 260                                xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo,
 261                                                             &leafhdr, leaf);
 262                                entries = xfs_attr3_leaf_entryp(leaf);
 263                                if (cursor->hashval > be32_to_cpu(
 264                                                entries[leafhdr.count - 1].hashval)) {
 265                                        trace_xfs_attr_list_wrong_blk(context);
 266                                        xfs_trans_brelse(NULL, bp);
 267                                        bp = NULL;
 268                                } else if (cursor->hashval <= be32_to_cpu(
 269                                                entries[0].hashval)) {
 270                                        trace_xfs_attr_list_wrong_blk(context);
 271                                        xfs_trans_brelse(NULL, bp);
 272                                        bp = NULL;
 273                                }
 274                                break;
 275                        default:
 276                                trace_xfs_attr_list_wrong_blk(context);
 277                                xfs_trans_brelse(NULL, bp);
 278                                bp = NULL;
 279                        }
 280                }
 281        }
 282
 283        /*
 284         * We did not find what we expected given the cursor's contents,
 285         * so we start from the top and work down based on the hash value.
 286         * Note that start of node block is same as start of leaf block.
 287         */
 288        if (bp == NULL) {
 289                cursor->blkno = 0;
 290                for (;;) {
 291                        __uint16_t magic;
 292
 293                        error = xfs_da3_node_read(NULL, dp,
 294                                                      cursor->blkno, -1, &bp,
 295                                                      XFS_ATTR_FORK);
 296                        if (error)
 297                                return error;
 298                        node = bp->b_addr;
 299                        magic = be16_to_cpu(node->hdr.info.magic);
 300                        if (magic == XFS_ATTR_LEAF_MAGIC ||
 301                            magic == XFS_ATTR3_LEAF_MAGIC)
 302                                break;
 303                        if (magic != XFS_DA_NODE_MAGIC &&
 304                            magic != XFS_DA3_NODE_MAGIC) {
 305                                XFS_CORRUPTION_ERROR("xfs_attr_node_list(3)",
 306                                                     XFS_ERRLEVEL_LOW,
 307                                                     context->dp->i_mount,
 308                                                     node);
 309                                xfs_trans_brelse(NULL, bp);
 310                                return -EFSCORRUPTED;
 311                        }
 312
 313                        dp->d_ops->node_hdr_from_disk(&nodehdr, node);
 314                        btree = dp->d_ops->node_tree_p(node);
 315                        for (i = 0; i < nodehdr.count; btree++, i++) {
 316                                if (cursor->hashval
 317                                                <= be32_to_cpu(btree->hashval)) {
 318                                        cursor->blkno = be32_to_cpu(btree->before);
 319                                        trace_xfs_attr_list_node_descend(context,
 320                                                                         btree);
 321                                        break;
 322                                }
 323                        }
 324                        if (i == nodehdr.count) {
 325                                xfs_trans_brelse(NULL, bp);
 326                                return 0;
 327                        }
 328                        xfs_trans_brelse(NULL, bp);
 329                }
 330        }
 331        ASSERT(bp != NULL);
 332
 333        /*
 334         * Roll upward through the blocks, processing each leaf block in
 335         * order.  As long as there is space in the result buffer, keep
 336         * adding the information.
 337         */
 338        for (;;) {
 339                leaf = bp->b_addr;
 340                error = xfs_attr3_leaf_list_int(bp, context);
 341                if (error) {
 342                        xfs_trans_brelse(NULL, bp);
 343                        return error;
 344                }
 345                xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &leafhdr, leaf);
 346                if (context->seen_enough || leafhdr.forw == 0)
 347                        break;
 348                cursor->blkno = leafhdr.forw;
 349                xfs_trans_brelse(NULL, bp);
 350                error = xfs_attr3_leaf_read(NULL, dp, cursor->blkno, -1, &bp);
 351                if (error)
 352                        return error;
 353        }
 354        xfs_trans_brelse(NULL, bp);
 355        return 0;
 356}
 357
 358/*
 359 * Copy out attribute list entries for attr_list(), for leaf attribute lists.
 360 */
 361int
 362xfs_attr3_leaf_list_int(
 363        struct xfs_buf                  *bp,
 364        struct xfs_attr_list_context    *context)
 365{
 366        struct attrlist_cursor_kern     *cursor;
 367        struct xfs_attr_leafblock       *leaf;
 368        struct xfs_attr3_icleaf_hdr     ichdr;
 369        struct xfs_attr_leaf_entry      *entries;
 370        struct xfs_attr_leaf_entry      *entry;
 371        int                             retval;
 372        int                             i;
 373        struct xfs_mount                *mp = context->dp->i_mount;
 374
 375        trace_xfs_attr_list_leaf(context);
 376
 377        leaf = bp->b_addr;
 378        xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &ichdr, leaf);
 379        entries = xfs_attr3_leaf_entryp(leaf);
 380
 381        cursor = context->cursor;
 382        cursor->initted = 1;
 383
 384        /*
 385         * Re-find our place in the leaf block if this is a new syscall.
 386         */
 387        if (context->resynch) {
 388                entry = &entries[0];
 389                for (i = 0; i < ichdr.count; entry++, i++) {
 390                        if (be32_to_cpu(entry->hashval) == cursor->hashval) {
 391                                if (cursor->offset == context->dupcnt) {
 392                                        context->dupcnt = 0;
 393                                        break;
 394                                }
 395                                context->dupcnt++;
 396                        } else if (be32_to_cpu(entry->hashval) >
 397                                        cursor->hashval) {
 398                                context->dupcnt = 0;
 399                                break;
 400                        }
 401                }
 402                if (i == ichdr.count) {
 403                        trace_xfs_attr_list_notfound(context);
 404                        return 0;
 405                }
 406        } else {
 407                entry = &entries[0];
 408                i = 0;
 409        }
 410        context->resynch = 0;
 411
 412        /*
 413         * We have found our place, start copying out the new attributes.
 414         */
 415        retval = 0;
 416        for (; i < ichdr.count; entry++, i++) {
 417                if (be32_to_cpu(entry->hashval) != cursor->hashval) {
 418                        cursor->hashval = be32_to_cpu(entry->hashval);
 419                        cursor->offset = 0;
 420                }
 421
 422                if (entry->flags & XFS_ATTR_INCOMPLETE)
 423                        continue;               /* skip incomplete entries */
 424
 425                if (entry->flags & XFS_ATTR_LOCAL) {
 426                        xfs_attr_leaf_name_local_t *name_loc =
 427                                xfs_attr3_leaf_name_local(leaf, i);
 428
 429                        retval = context->put_listent(context,
 430                                                entry->flags,
 431                                                name_loc->nameval,
 432                                                (int)name_loc->namelen,
 433                                                be16_to_cpu(name_loc->valuelen),
 434                                                &name_loc->nameval[name_loc->namelen]);
 435                        if (retval)
 436                                return retval;
 437                } else {
 438                        xfs_attr_leaf_name_remote_t *name_rmt =
 439                                xfs_attr3_leaf_name_remote(leaf, i);
 440
 441                        int valuelen = be32_to_cpu(name_rmt->valuelen);
 442
 443                        if (context->put_value) {
 444                                xfs_da_args_t args;
 445
 446                                memset((char *)&args, 0, sizeof(args));
 447                                args.geo = context->dp->i_mount->m_attr_geo;
 448                                args.dp = context->dp;
 449                                args.whichfork = XFS_ATTR_FORK;
 450                                args.valuelen = valuelen;
 451                                args.rmtvaluelen = valuelen;
 452                                args.value = kmem_alloc(valuelen, KM_SLEEP | KM_NOFS);
 453                                args.rmtblkno = be32_to_cpu(name_rmt->valueblk);
 454                                args.rmtblkcnt = xfs_attr3_rmt_blocks(
 455                                                        args.dp->i_mount, valuelen);
 456                                retval = xfs_attr_rmtval_get(&args);
 457                                if (retval)
 458                                        return retval;
 459                                retval = context->put_listent(context,
 460                                                entry->flags,
 461                                                name_rmt->name,
 462                                                (int)name_rmt->namelen,
 463                                                valuelen,
 464                                                args.value);
 465                                kmem_free(args.value);
 466                        } else {
 467                                retval = context->put_listent(context,
 468                                                entry->flags,
 469                                                name_rmt->name,
 470                                                (int)name_rmt->namelen,
 471                                                valuelen,
 472                                                NULL);
 473                        }
 474                        if (retval)
 475                                return retval;
 476                }
 477                if (context->seen_enough)
 478                        break;
 479                cursor->offset++;
 480        }
 481        trace_xfs_attr_list_leaf_end(context);
 482        return retval;
 483}
 484
 485/*
 486 * Copy out attribute entries for attr_list(), for leaf attribute lists.
 487 */
 488STATIC int
 489xfs_attr_leaf_list(xfs_attr_list_context_t *context)
 490{
 491        int error;
 492        struct xfs_buf *bp;
 493
 494        trace_xfs_attr_leaf_list(context);
 495
 496        context->cursor->blkno = 0;
 497        error = xfs_attr3_leaf_read(NULL, context->dp, 0, -1, &bp);
 498        if (error)
 499                return error;
 500
 501        error = xfs_attr3_leaf_list_int(bp, context);
 502        xfs_trans_brelse(NULL, bp);
 503        return error;
 504}
 505
 506int
 507xfs_attr_list_int(
 508        xfs_attr_list_context_t *context)
 509{
 510        int error;
 511        xfs_inode_t *dp = context->dp;
 512        uint            lock_mode;
 513
 514        XFS_STATS_INC(dp->i_mount, xs_attr_list);
 515
 516        if (XFS_FORCED_SHUTDOWN(dp->i_mount))
 517                return -EIO;
 518
 519        /*
 520         * Decide on what work routines to call based on the inode size.
 521         */
 522        lock_mode = xfs_ilock_attr_map_shared(dp);
 523        if (!xfs_inode_hasattr(dp)) {
 524                error = 0;
 525        } else if (dp->i_d.di_aformat == XFS_DINODE_FMT_LOCAL) {
 526                error = xfs_attr_shortform_list(context);
 527        } else if (xfs_bmap_one_block(dp, XFS_ATTR_FORK)) {
 528                error = xfs_attr_leaf_list(context);
 529        } else {
 530                error = xfs_attr_node_list(context);
 531        }
 532        xfs_iunlock(dp, lock_mode);
 533        return error;
 534}
 535
 536#define ATTR_ENTBASESIZE                /* minimum bytes used by an attr */ \
 537        (((struct attrlist_ent *) 0)->a_name - (char *) 0)
 538#define ATTR_ENTSIZE(namelen)           /* actual bytes used by an attr */ \
 539        ((ATTR_ENTBASESIZE + (namelen) + 1 + sizeof(u_int32_t)-1) \
 540         & ~(sizeof(u_int32_t)-1))
 541
 542/*
 543 * Format an attribute and copy it out to the user's buffer.
 544 * Take care to check values and protect against them changing later,
 545 * we may be reading them directly out of a user buffer.
 546 */
 547STATIC int
 548xfs_attr_put_listent(
 549        xfs_attr_list_context_t *context,
 550        int             flags,
 551        unsigned char   *name,
 552        int             namelen,
 553        int             valuelen,
 554        unsigned char   *value)
 555{
 556        struct attrlist *alist = (struct attrlist *)context->alist;
 557        attrlist_ent_t *aep;
 558        int arraytop;
 559
 560        ASSERT(!(context->flags & ATTR_KERNOVAL));
 561        ASSERT(context->count >= 0);
 562        ASSERT(context->count < (ATTR_MAX_VALUELEN/8));
 563        ASSERT(context->firstu >= sizeof(*alist));
 564        ASSERT(context->firstu <= context->bufsize);
 565
 566        /*
 567         * Only list entries in the right namespace.
 568         */
 569        if (((context->flags & ATTR_SECURE) == 0) !=
 570            ((flags & XFS_ATTR_SECURE) == 0))
 571                return 0;
 572        if (((context->flags & ATTR_ROOT) == 0) !=
 573            ((flags & XFS_ATTR_ROOT) == 0))
 574                return 0;
 575
 576        arraytop = sizeof(*alist) +
 577                        context->count * sizeof(alist->al_offset[0]);
 578        context->firstu -= ATTR_ENTSIZE(namelen);
 579        if (context->firstu < arraytop) {
 580                trace_xfs_attr_list_full(context);
 581                alist->al_more = 1;
 582                context->seen_enough = 1;
 583                return 1;
 584        }
 585
 586        aep = (attrlist_ent_t *)&context->alist[context->firstu];
 587        aep->a_valuelen = valuelen;
 588        memcpy(aep->a_name, name, namelen);
 589        aep->a_name[namelen] = 0;
 590        alist->al_offset[context->count++] = context->firstu;
 591        alist->al_count = context->count;
 592        trace_xfs_attr_list_add(context);
 593        return 0;
 594}
 595
 596/*
 597 * Generate a list of extended attribute names and optionally
 598 * also value lengths.  Positive return value follows the XFS
 599 * convention of being an error, zero or negative return code
 600 * is the length of the buffer returned (negated), indicating
 601 * success.
 602 */
 603int
 604xfs_attr_list(
 605        xfs_inode_t     *dp,
 606        char            *buffer,
 607        int             bufsize,
 608        int             flags,
 609        attrlist_cursor_kern_t *cursor)
 610{
 611        xfs_attr_list_context_t context;
 612        struct attrlist *alist;
 613        int error;
 614
 615        /*
 616         * Validate the cursor.
 617         */
 618        if (cursor->pad1 || cursor->pad2)
 619                return -EINVAL;
 620        if ((cursor->initted == 0) &&
 621            (cursor->hashval || cursor->blkno || cursor->offset))
 622                return -EINVAL;
 623
 624        /*
 625         * Check for a properly aligned buffer.
 626         */
 627        if (((long)buffer) & (sizeof(int)-1))
 628                return -EFAULT;
 629        if (flags & ATTR_KERNOVAL)
 630                bufsize = 0;
 631
 632        /*
 633         * Initialize the output buffer.
 634         */
 635        memset(&context, 0, sizeof(context));
 636        context.dp = dp;
 637        context.cursor = cursor;
 638        context.resynch = 1;
 639        context.flags = flags;
 640        context.alist = buffer;
 641        context.bufsize = (bufsize & ~(sizeof(int)-1));  /* align */
 642        context.firstu = context.bufsize;
 643        context.put_listent = xfs_attr_put_listent;
 644
 645        alist = (struct attrlist *)context.alist;
 646        alist->al_count = 0;
 647        alist->al_more = 0;
 648        alist->al_offset[0] = context.bufsize;
 649
 650        error = xfs_attr_list_int(&context);
 651        ASSERT(error <= 0);
 652        return error;
 653}
 654