Line data Source code
1 : // SPDX-License-Identifier: GPL-2.0-or-later
2 : /*
3 : * Copyright (C) 2017-2023 Oracle. All Rights Reserved.
4 : * Author: Darrick J. Wong <djwong@kernel.org>
5 : */
6 : #include "xfs.h"
7 : #include "xfs_fs.h"
8 : #include "xfs_shared.h"
9 : #include "xfs_format.h"
10 : #include "xfs_trans_resv.h"
11 : #include "xfs_mount.h"
12 : #include "xfs_log_format.h"
13 : #include "xfs_trans.h"
14 : #include "xfs_inode.h"
15 : #include "xfs_dir2.h"
16 : #include "xfs_dir2_priv.h"
17 : #include "xfs_attr_leaf.h"
18 : #include "scrub/scrub.h"
19 : #include "scrub/common.h"
20 : #include "scrub/trace.h"
21 : #include "scrub/dabtree.h"
22 :
23 : /* Directory/Attribute Btree */
24 :
25 : /*
26 : * Check for da btree operation errors. See the section about handling
27 : * operational errors in common.c.
28 : */
29 : bool
30 3102334 : xchk_da_process_error(
31 : struct xchk_da_btree *ds,
32 : int level,
33 : int *error)
34 : {
35 3102334 : struct xfs_scrub *sc = ds->sc;
36 :
37 3102334 : if (*error == 0)
38 : return true;
39 :
40 0 : switch (*error) {
41 0 : case -EDEADLOCK:
42 : case -ECHRNG:
43 : /* Used to restart an op with deadlock avoidance. */
44 0 : trace_xchk_deadlock_retry(sc->ip, sc->sm, *error);
45 0 : break;
46 0 : case -EFSBADCRC:
47 : case -EFSCORRUPTED:
48 : /* Note the badness but don't abort. */
49 0 : sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT;
50 0 : *error = 0;
51 0 : fallthrough;
52 0 : default:
53 0 : trace_xchk_file_op_error(sc, ds->dargs.whichfork,
54 0 : xfs_dir2_da_to_db(ds->dargs.geo,
55 0 : ds->state->path.blk[level].blkno),
56 : *error, __return_address);
57 0 : break;
58 : }
59 : return false;
60 : }
61 :
62 : /*
63 : * Check for da btree corruption. See the section about handling
64 : * operational errors in common.c.
65 : */
66 : void
67 0 : xchk_da_set_corrupt(
68 : struct xchk_da_btree *ds,
69 : int level)
70 : {
71 0 : struct xfs_scrub *sc = ds->sc;
72 :
73 0 : sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT;
74 :
75 0 : trace_xchk_fblock_error(sc, ds->dargs.whichfork,
76 0 : xfs_dir2_da_to_db(ds->dargs.geo,
77 0 : ds->state->path.blk[level].blkno),
78 : __return_address);
79 0 : }
80 :
81 : static struct xfs_da_node_entry *
82 131485763 : xchk_da_btree_node_entry(
83 : struct xchk_da_btree *ds,
84 : int level)
85 : {
86 131485763 : struct xfs_da_state_blk *blk = &ds->state->path.blk[level];
87 131302334 : struct xfs_da3_icnode_hdr hdr;
88 :
89 131302334 : ASSERT(blk->magic == XFS_DA_NODE_MAGIC);
90 :
91 131302334 : xfs_da3_node_hdr_from_disk(ds->sc->mp, &hdr, blk->bp->b_addr);
92 131295804 : return hdr.btree + blk->index;
93 : }
94 :
95 : /* Scrub a da btree hash (key). */
96 : int
97 152671218 : xchk_da_btree_hash(
98 : struct xchk_da_btree *ds,
99 : int level,
100 : __be32 *hashp)
101 : {
102 152671218 : struct xfs_da_node_entry *entry;
103 152671218 : xfs_dahash_t hash;
104 152671218 : xfs_dahash_t parent_hash;
105 :
106 : /* Is this hash in order? */
107 152671218 : hash = be32_to_cpu(*hashp);
108 152671218 : if (hash < ds->hashes[level])
109 0 : xchk_da_set_corrupt(ds, level);
110 152550609 : ds->hashes[level] = hash;
111 :
112 152543229 : if (level == 0)
113 : return 0;
114 :
115 : /* Is this hash no larger than the parent hash? */
116 130189402 : entry = xchk_da_btree_node_entry(ds, level - 1);
117 130268112 : parent_hash = be32_to_cpu(entry->hashval);
118 130268112 : if (parent_hash < hash)
119 0 : xchk_da_set_corrupt(ds, level);
120 :
121 : return 0;
122 : }
123 :
124 : /*
125 : * Check a da btree pointer. Returns true if it's ok to use this
126 : * pointer.
127 : */
128 : STATIC bool
129 2190505 : xchk_da_btree_ptr_ok(
130 : struct xchk_da_btree *ds,
131 : int level,
132 : xfs_dablk_t blkno)
133 : {
134 2190505 : if (blkno < ds->lowest || (ds->highest != 0 && blkno >= ds->highest)) {
135 0 : xchk_da_set_corrupt(ds, level);
136 0 : return false;
137 : }
138 :
139 : return true;
140 : }
141 :
142 : /*
143 : * The da btree scrubber can handle leaf1 blocks as a degenerate
144 : * form of leafn blocks. Since the regular da code doesn't handle
145 : * leaf1, we must multiplex the verifiers.
146 : */
147 : static void
148 38805 : xchk_da_btree_read_verify(
149 : struct xfs_buf *bp)
150 : {
151 38805 : struct xfs_da_blkinfo *info = bp->b_addr;
152 :
153 38805 : switch (be16_to_cpu(info->magic)) {
154 526 : case XFS_DIR2_LEAF1_MAGIC:
155 : case XFS_DIR3_LEAF1_MAGIC:
156 526 : bp->b_ops = &xfs_dir3_leaf1_buf_ops;
157 526 : bp->b_ops->verify_read(bp);
158 526 : return;
159 38279 : default:
160 : /*
161 : * xfs_da3_node_buf_ops already know how to handle
162 : * DA*_NODE, ATTR*_LEAF, and DIR*_LEAFN blocks.
163 : */
164 38279 : bp->b_ops = &xfs_da3_node_buf_ops;
165 38279 : bp->b_ops->verify_read(bp);
166 38279 : return;
167 : }
168 : }
169 : static void
170 0 : xchk_da_btree_write_verify(
171 : struct xfs_buf *bp)
172 : {
173 0 : struct xfs_da_blkinfo *info = bp->b_addr;
174 :
175 0 : switch (be16_to_cpu(info->magic)) {
176 0 : case XFS_DIR2_LEAF1_MAGIC:
177 : case XFS_DIR3_LEAF1_MAGIC:
178 0 : bp->b_ops = &xfs_dir3_leaf1_buf_ops;
179 0 : bp->b_ops->verify_write(bp);
180 0 : return;
181 0 : default:
182 : /*
183 : * xfs_da3_node_buf_ops already know how to handle
184 : * DA*_NODE, ATTR*_LEAF, and DIR*_LEAFN blocks.
185 : */
186 0 : bp->b_ops = &xfs_da3_node_buf_ops;
187 0 : bp->b_ops->verify_write(bp);
188 0 : return;
189 : }
190 : }
191 : static void *
192 0 : xchk_da_btree_verify(
193 : struct xfs_buf *bp)
194 : {
195 0 : struct xfs_da_blkinfo *info = bp->b_addr;
196 :
197 0 : switch (be16_to_cpu(info->magic)) {
198 0 : case XFS_DIR2_LEAF1_MAGIC:
199 : case XFS_DIR3_LEAF1_MAGIC:
200 0 : bp->b_ops = &xfs_dir3_leaf1_buf_ops;
201 0 : return bp->b_ops->verify_struct(bp);
202 0 : default:
203 0 : bp->b_ops = &xfs_da3_node_buf_ops;
204 0 : return bp->b_ops->verify_struct(bp);
205 : }
206 : }
207 :
208 : static const struct xfs_buf_ops xchk_da_btree_buf_ops = {
209 : .name = "xchk_da_btree",
210 : .verify_read = xchk_da_btree_read_verify,
211 : .verify_write = xchk_da_btree_write_verify,
212 : .verify_struct = xchk_da_btree_verify,
213 : };
214 :
215 : /* Check a block's sibling. */
216 : STATIC int
217 1041200 : xchk_da_btree_block_check_sibling(
218 : struct xchk_da_btree *ds,
219 : int level,
220 : int direction,
221 : xfs_dablk_t sibling)
222 : {
223 1041200 : struct xfs_da_state_path *path = &ds->state->path;
224 1041200 : struct xfs_da_state_path *altpath = &ds->state->altpath;
225 1041200 : int retval;
226 1041200 : int plevel;
227 1041200 : int error;
228 :
229 2082400 : memcpy(altpath, path, sizeof(ds->state->altpath));
230 :
231 : /*
232 : * If the pointer is null, we shouldn't be able to move the upper
233 : * level pointer anywhere.
234 : */
235 1041200 : if (sibling == 0) {
236 131695 : error = xfs_da3_path_shift(ds->state, altpath, direction,
237 : false, &retval);
238 131691 : if (error == 0 && retval == 0)
239 0 : xchk_da_set_corrupt(ds, level);
240 131691 : error = 0;
241 131691 : goto out;
242 : }
243 :
244 : /* Move the alternate cursor one block in the direction given. */
245 909505 : error = xfs_da3_path_shift(ds->state, altpath, direction, false,
246 : &retval);
247 909486 : if (!xchk_da_process_error(ds, level, &error))
248 0 : goto out;
249 909483 : if (retval) {
250 0 : xchk_da_set_corrupt(ds, level);
251 0 : goto out;
252 : }
253 909483 : if (altpath->blk[level].bp)
254 909484 : xchk_buffer_recheck(ds->sc, altpath->blk[level].bp);
255 :
256 : /* Compare upper level pointer to sibling pointer. */
257 909505 : if (altpath->blk[level].blkno != sibling)
258 0 : xchk_da_set_corrupt(ds, level);
259 :
260 909505 : out:
261 : /* Free all buffers in the altpath that aren't referenced from path. */
262 3343724 : for (plevel = 0; plevel < altpath->active; plevel++) {
263 2302521 : if (altpath->blk[plevel].bp == NULL ||
264 2302511 : (plevel < path->active &&
265 2302511 : altpath->blk[plevel].bp == path->blk[plevel].bp))
266 1392289 : continue;
267 :
268 910221 : xfs_trans_brelse(ds->dargs.trans, altpath->blk[plevel].bp);
269 910239 : altpath->blk[plevel].bp = NULL;
270 : }
271 :
272 1041203 : return error;
273 : }
274 :
275 : /* Check a block's sibling pointers. */
276 : STATIC int
277 2002412 : xchk_da_btree_block_check_siblings(
278 : struct xchk_da_btree *ds,
279 : int level,
280 : struct xfs_da_blkinfo *hdr)
281 : {
282 2002412 : xfs_dablk_t forw;
283 2002412 : xfs_dablk_t back;
284 2002412 : int error = 0;
285 :
286 2002412 : forw = be32_to_cpu(hdr->forw);
287 2002412 : back = be32_to_cpu(hdr->back);
288 :
289 : /* Top level blocks should not have sibling pointers. */
290 2002412 : if (level == 0) {
291 1481810 : if (forw != 0 || back != 0)
292 0 : xchk_da_set_corrupt(ds, level);
293 1481810 : return 0;
294 : }
295 :
296 : /*
297 : * Check back (left) and forw (right) pointers. These functions
298 : * absorb error codes for us.
299 : */
300 520602 : error = xchk_da_btree_block_check_sibling(ds, level, 0, back);
301 520601 : if (error)
302 0 : goto out;
303 520601 : error = xchk_da_btree_block_check_sibling(ds, level, 1, forw);
304 :
305 520605 : out:
306 520605 : memset(&ds->state->altpath, 0, sizeof(ds->state->altpath));
307 520605 : return error;
308 : }
309 :
310 : /* Load a dir/attribute block from a btree. */
311 : STATIC int
312 2194102 : xchk_da_btree_block(
313 : struct xchk_da_btree *ds,
314 : int level,
315 : xfs_dablk_t blkno)
316 : {
317 2194102 : struct xfs_da_state_blk *blk;
318 2194102 : struct xfs_da_intnode *node;
319 2194102 : struct xfs_da_node_entry *btree;
320 2194102 : struct xfs_da3_blkinfo *hdr3;
321 2194102 : struct xfs_da_args *dargs = &ds->dargs;
322 2194102 : struct xfs_inode *ip = ds->dargs.dp;
323 2194102 : xfs_ino_t owner;
324 2194102 : int *pmaxrecs;
325 2194102 : struct xfs_da3_icnode_hdr nodehdr;
326 2194102 : int error = 0;
327 :
328 2194102 : blk = &ds->state->path.blk[level];
329 2192781 : ds->state->path.active = level + 1;
330 :
331 : /* Release old block. */
332 2192781 : if (blk->bp) {
333 454747 : xfs_trans_brelse(dargs->trans, blk->bp);
334 454754 : blk->bp = NULL;
335 : }
336 :
337 : /* Check the pointer. */
338 2192788 : blk->blkno = blkno;
339 2192788 : if (!xchk_da_btree_ptr_ok(ds, level, blkno))
340 0 : goto out_nobuf;
341 :
342 : /* Read the buffer. */
343 2190516 : error = xfs_da_read_buf(dargs->trans, dargs->dp, blk->blkno,
344 : XFS_DABUF_MAP_HOLE_OK, &blk->bp, dargs->whichfork,
345 : &xchk_da_btree_buf_ops);
346 2194271 : if (!xchk_da_process_error(ds, level, &error))
347 0 : goto out_nobuf;
348 2191450 : if (blk->bp)
349 1999006 : xchk_buffer_recheck(ds->sc, blk->bp);
350 :
351 : /*
352 : * We didn't find a dir btree root block, which means that
353 : * there's no LEAF1/LEAFN tree (at least not where it's supposed
354 : * to be), so jump out now.
355 : */
356 2195125 : if (ds->dargs.whichfork == XFS_DATA_FORK && level == 0 &&
357 273836 : blk->bp == NULL)
358 192444 : goto out_nobuf;
359 :
360 : /* It's /not/ ok for attr trees not to have a da btree. */
361 2002681 : if (blk->bp == NULL) {
362 0 : xchk_da_set_corrupt(ds, level);
363 0 : goto out_nobuf;
364 : }
365 :
366 2002681 : hdr3 = blk->bp->b_addr;
367 2002681 : blk->magic = be16_to_cpu(hdr3->hdr.magic);
368 2002681 : pmaxrecs = &ds->maxrecs[level];
369 :
370 : /* We only started zeroing the header on v5 filesystems. */
371 2002682 : if (xfs_has_crc(ds->sc->mp) && hdr3->hdr.pad)
372 0 : xchk_da_set_corrupt(ds, level);
373 :
374 : /* Check the owner. */
375 2002682 : if (xfs_has_crc(ip->i_mount)) {
376 2002972 : owner = be64_to_cpu(hdr3->owner);
377 2002972 : if (owner != ip->i_ino)
378 0 : xchk_da_set_corrupt(ds, level);
379 : }
380 :
381 : /* Check the siblings. */
382 2002682 : error = xchk_da_btree_block_check_siblings(ds, level, &hdr3->hdr);
383 2001760 : if (error)
384 0 : goto out;
385 :
386 : /* Interpret the buffer. */
387 2001760 : switch (blk->magic) {
388 1507905 : case XFS_ATTR_LEAF_MAGIC:
389 : case XFS_ATTR3_LEAF_MAGIC:
390 1507905 : xfs_trans_buf_set_type(dargs->trans, blk->bp,
391 : XFS_BLFT_ATTR_LEAF_BUF);
392 1506929 : blk->magic = XFS_ATTR_LEAF_MAGIC;
393 1506929 : blk->hashval = xfs_attr_leaf_lasthash(blk->bp, pmaxrecs);
394 1506407 : if (ds->tree_level != 0)
395 0 : xchk_da_set_corrupt(ds, level);
396 : break;
397 397895 : case XFS_DIR2_LEAFN_MAGIC:
398 : case XFS_DIR3_LEAFN_MAGIC:
399 397895 : xfs_trans_buf_set_type(dargs->trans, blk->bp,
400 : XFS_BLFT_DIR_LEAFN_BUF);
401 397884 : blk->magic = XFS_DIR2_LEAFN_MAGIC;
402 397884 : blk->hashval = xfs_dir2_leaf_lasthash(ip, blk->bp, pmaxrecs);
403 397892 : if (ds->tree_level != 0)
404 0 : xchk_da_set_corrupt(ds, level);
405 : break;
406 29750 : case XFS_DIR2_LEAF1_MAGIC:
407 : case XFS_DIR3_LEAF1_MAGIC:
408 29750 : xfs_trans_buf_set_type(dargs->trans, blk->bp,
409 : XFS_BLFT_DIR_LEAF1_BUF);
410 29749 : blk->magic = XFS_DIR2_LEAF1_MAGIC;
411 29749 : blk->hashval = xfs_dir2_leaf_lasthash(ip, blk->bp, pmaxrecs);
412 29748 : if (ds->tree_level != 0)
413 0 : xchk_da_set_corrupt(ds, level);
414 : break;
415 66210 : case XFS_DA_NODE_MAGIC:
416 : case XFS_DA3_NODE_MAGIC:
417 66210 : xfs_trans_buf_set_type(dargs->trans, blk->bp,
418 : XFS_BLFT_DA_NODE_BUF);
419 66207 : blk->magic = XFS_DA_NODE_MAGIC;
420 66207 : node = blk->bp->b_addr;
421 66207 : xfs_da3_node_hdr_from_disk(ip->i_mount, &nodehdr, node);
422 66211 : btree = nodehdr.btree;
423 66211 : *pmaxrecs = nodehdr.count;
424 66211 : blk->hashval = be32_to_cpu(btree[*pmaxrecs - 1].hashval);
425 66211 : if (level == 0) {
426 65802 : if (nodehdr.level >= XFS_DA_NODE_MAXDEPTH) {
427 0 : xchk_da_set_corrupt(ds, level);
428 0 : goto out_freebp;
429 : }
430 65802 : ds->tree_level = nodehdr.level;
431 : } else {
432 409 : if (ds->tree_level != nodehdr.level) {
433 0 : xchk_da_set_corrupt(ds, level);
434 0 : goto out_freebp;
435 : }
436 : }
437 :
438 : /* XXX: Check hdr3.pad32 once we know how to fix it. */
439 : break;
440 0 : default:
441 0 : xchk_da_set_corrupt(ds, level);
442 0 : goto out_freebp;
443 : }
444 :
445 : /*
446 : * If we've been handed a block that is below the dabtree root, does
447 : * its hashval match what the parent block expected to see?
448 : */
449 2000258 : if (level > 0) {
450 520597 : struct xfs_da_node_entry *key;
451 :
452 520597 : key = xchk_da_btree_node_entry(ds, level - 1);
453 520599 : if (be32_to_cpu(key->hashval) != blk->hashval) {
454 0 : xchk_da_set_corrupt(ds, level);
455 0 : goto out_freebp;
456 : }
457 : }
458 :
459 2000260 : out:
460 : return error;
461 0 : out_freebp:
462 0 : xfs_trans_brelse(dargs->trans, blk->bp);
463 0 : blk->bp = NULL;
464 192444 : out_nobuf:
465 192444 : blk->blkno = 0;
466 192444 : return error;
467 : }
468 :
469 : /* Visit all nodes and leaves of a da btree. */
470 : int
471 90877077 : xchk_da_btree(
472 : struct xfs_scrub *sc,
473 : int whichfork,
474 : xchk_da_btree_rec_fn scrub_fn,
475 : void *private)
476 : {
477 90877077 : struct xchk_da_btree *ds;
478 90877077 : struct xfs_mount *mp = sc->mp;
479 90877077 : struct xfs_da_state_blk *blks;
480 90877077 : struct xfs_da_node_entry *key;
481 90877077 : xfs_dablk_t blkno;
482 90877077 : int level;
483 90877077 : int error;
484 :
485 : /* Skip short format data structures; no btree to scan. */
486 90877077 : if (!xfs_ifork_has_extents(xfs_ifork_ptr(sc->ip, whichfork)))
487 : return 0;
488 :
489 : /* Set up initial da state. */
490 1670171 : ds = kzalloc(sizeof(struct xchk_da_btree), XCHK_GFP_FLAGS);
491 1673258 : if (!ds)
492 : return -ENOMEM;
493 1673258 : ds->dargs.dp = sc->ip;
494 1673258 : ds->dargs.whichfork = whichfork;
495 1673258 : ds->dargs.trans = sc->tp;
496 1673258 : ds->dargs.op_flags = XFS_DA_OP_OKNOENT;
497 1673258 : ds->state = xfs_da_state_alloc(&ds->dargs);
498 1674313 : ds->sc = sc;
499 1674313 : ds->private = private;
500 1674313 : if (whichfork == XFS_ATTR_FORK) {
501 1400466 : ds->dargs.geo = mp->m_attr_geo;
502 1400466 : ds->lowest = 0;
503 1400466 : ds->highest = 0;
504 : } else {
505 273847 : ds->dargs.geo = mp->m_dir_geo;
506 273847 : ds->lowest = ds->dargs.geo->leafblk;
507 273847 : ds->highest = ds->dargs.geo->freeblk;
508 : }
509 1674313 : blkno = ds->lowest;
510 1674313 : level = 0;
511 :
512 : /* Find the root of the da tree, if present. */
513 1674313 : blks = ds->state->path.blk;
514 1674313 : error = xchk_da_btree_block(ds, level, blkno);
515 1673343 : if (error)
516 0 : goto out_state;
517 : /*
518 : * We didn't find a block at ds->lowest, which means that there's
519 : * no LEAF1/LEAFN tree (at least not where it's supposed to be),
520 : * so jump out now.
521 : */
522 1673343 : if (blks[level].bp == NULL)
523 192443 : goto out_state;
524 :
525 1480900 : blks[level].index = 0;
526 156285964 : while (level >= 0 && level < XFS_DA_NODE_MAXDEPTH) {
527 : /* Handle leaf block. */
528 154802149 : if (blks[level].magic != XFS_DA_NODE_MAGIC) {
529 : /* End of leaf, pop back towards the root. */
530 154215347 : if (blks[level].index >= ds->maxrecs[level]) {
531 1938288 : if (level > 0)
532 520183 : blks[level - 1].index++;
533 1938288 : ds->tree_level++;
534 1938288 : level--;
535 1938288 : continue;
536 : }
537 :
538 : /* Dispatch record scrubbing. */
539 152292231 : error = scrub_fn(ds, level);
540 152258747 : if (error)
541 : break;
542 152258747 : if (xchk_should_terminate(sc, &error) ||
543 152279981 : (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT))
544 : break;
545 :
546 152279981 : blks[level].index++;
547 152279981 : continue;
548 : }
549 :
550 :
551 : /* End of node, pop back towards the root. */
552 586802 : if (blks[level].index >= ds->maxrecs[level]) {
553 66202 : if (level > 0)
554 409 : blks[level - 1].index++;
555 66202 : ds->tree_level++;
556 66202 : level--;
557 66202 : continue;
558 : }
559 :
560 : /* Hashes in order for scrub? */
561 520599 : key = xchk_da_btree_node_entry(ds, level);
562 520597 : error = xchk_da_btree_hash(ds, level, &key->hashval);
563 520596 : if (error)
564 0 : goto out;
565 :
566 : /* Drill another level deeper. */
567 520596 : blkno = be32_to_cpu(key->before);
568 520596 : level++;
569 520596 : if (level >= XFS_DA_NODE_MAXDEPTH) {
570 : /* Too deep! */
571 0 : xchk_da_set_corrupt(ds, level - 1);
572 0 : break;
573 : }
574 520596 : ds->tree_level--;
575 520596 : error = xchk_da_btree_block(ds, level, blkno);
576 520593 : if (error)
577 0 : goto out;
578 520593 : if (blks[level].bp == NULL)
579 0 : goto out;
580 :
581 520593 : blks[level].index = 0;
582 : }
583 :
584 1483830 : out:
585 : /* Release all the buffers we're tracking. */
586 8897451 : for (level = 0; level < XFS_DA_NODE_MAXDEPTH; level++) {
587 7414491 : if (blks[level].bp == NULL)
588 5864955 : continue;
589 1549536 : xfs_trans_brelse(sc->tp, blks[level].bp);
590 1548666 : blks[level].bp = NULL;
591 : }
592 :
593 1482960 : out_state:
594 1675403 : xfs_da_state_free(ds->state);
595 1672887 : kfree(ds);
596 1673807 : return error;
597 : }
|