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 2553841 : xchk_da_process_error(
31 : struct xchk_da_btree *ds,
32 : int level,
33 : int *error)
34 : {
35 2553841 : struct xfs_scrub *sc = ds->sc;
36 :
37 2553841 : 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 135849667 : xchk_da_btree_node_entry(
83 : struct xchk_da_btree *ds,
84 : int level)
85 : {
86 135849667 : struct xfs_da_state_blk *blk = &ds->state->path.blk[level];
87 135851142 : struct xfs_da3_icnode_hdr hdr;
88 :
89 135851142 : ASSERT(blk->magic == XFS_DA_NODE_MAGIC);
90 :
91 135851142 : xfs_da3_node_hdr_from_disk(ds->sc->mp, &hdr, blk->bp->b_addr);
92 135850898 : return hdr.btree + blk->index;
93 : }
94 :
95 : /* Scrub a da btree hash (key). */
96 : int
97 150910694 : xchk_da_btree_hash(
98 : struct xchk_da_btree *ds,
99 : int level,
100 : __be32 *hashp)
101 : {
102 150910694 : struct xfs_da_node_entry *entry;
103 150910694 : xfs_dahash_t hash;
104 150910694 : xfs_dahash_t parent_hash;
105 :
106 : /* Is this hash in order? */
107 150910694 : hash = be32_to_cpu(*hashp);
108 150910694 : if (hash < ds->hashes[level])
109 0 : xchk_da_set_corrupt(ds, level);
110 150911582 : ds->hashes[level] = hash;
111 :
112 150911699 : if (level == 0)
113 : return 0;
114 :
115 : /* Is this hash no larger than the parent hash? */
116 134909503 : entry = xchk_da_btree_node_entry(ds, level - 1);
117 134910206 : parent_hash = be32_to_cpu(entry->hashval);
118 134910206 : 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 1719370 : xchk_da_btree_ptr_ok(
130 : struct xchk_da_btree *ds,
131 : int level,
132 : xfs_dablk_t blkno)
133 : {
134 1719370 : 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 35420 : xchk_da_btree_read_verify(
149 : struct xfs_buf *bp)
150 : {
151 35420 : struct xfs_da_blkinfo *info = bp->b_addr;
152 :
153 35420 : switch (be16_to_cpu(info->magic)) {
154 52 : case XFS_DIR2_LEAF1_MAGIC:
155 : case XFS_DIR3_LEAF1_MAGIC:
156 52 : bp->b_ops = &xfs_dir3_leaf1_buf_ops;
157 52 : bp->b_ops->verify_read(bp);
158 52 : return;
159 35368 : default:
160 : /*
161 : * xfs_da3_node_buf_ops already know how to handle
162 : * DA*_NODE, ATTR*_LEAF, and DIR*_LEAFN blocks.
163 : */
164 35368 : bp->b_ops = &xfs_da3_node_buf_ops;
165 35368 : bp->b_ops->verify_read(bp);
166 35368 : 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 941145 : 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 941145 : struct xfs_da_state_path *path = &ds->state->path;
224 941145 : struct xfs_da_state_path *altpath = &ds->state->altpath;
225 941145 : int retval;
226 941145 : int plevel;
227 941145 : int error;
228 :
229 1882290 : 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 941145 : if (sibling == 0) {
236 106684 : error = xfs_da3_path_shift(ds->state, altpath, direction,
237 : false, &retval);
238 106684 : if (error == 0 && retval == 0)
239 0 : xchk_da_set_corrupt(ds, level);
240 106684 : error = 0;
241 106684 : goto out;
242 : }
243 :
244 : /* Move the alternate cursor one block in the direction given. */
245 834461 : error = xfs_da3_path_shift(ds->state, altpath, direction, false,
246 : &retval);
247 834462 : if (!xchk_da_process_error(ds, level, &error))
248 0 : goto out;
249 834462 : if (retval) {
250 0 : xchk_da_set_corrupt(ds, level);
251 0 : goto out;
252 : }
253 834462 : if (altpath->blk[level].bp)
254 834462 : xchk_buffer_recheck(ds->sc, altpath->blk[level].bp);
255 :
256 : /* Compare upper level pointer to sibling pointer. */
257 834462 : if (altpath->blk[level].blkno != sibling)
258 0 : xchk_da_set_corrupt(ds, level);
259 :
260 834461 : out:
261 : /* Free all buffers in the altpath that aren't referenced from path. */
262 2848647 : for (plevel = 0; plevel < altpath->active; plevel++) {
263 1907501 : if (altpath->blk[plevel].bp == NULL ||
264 1907500 : (plevel < path->active &&
265 1907500 : altpath->blk[plevel].bp == path->blk[plevel].bp))
266 1072976 : continue;
267 :
268 834525 : xfs_trans_brelse(ds->dargs.trans, altpath->blk[plevel].bp);
269 834526 : altpath->blk[plevel].bp = NULL;
270 : }
271 :
272 941146 : return error;
273 : }
274 :
275 : /* Check a block's sibling pointers. */
276 : STATIC int
277 1585001 : xchk_da_btree_block_check_siblings(
278 : struct xchk_da_btree *ds,
279 : int level,
280 : struct xfs_da_blkinfo *hdr)
281 : {
282 1585001 : xfs_dablk_t forw;
283 1585001 : xfs_dablk_t back;
284 1585001 : int error = 0;
285 :
286 1585001 : forw = be32_to_cpu(hdr->forw);
287 1585001 : back = be32_to_cpu(hdr->back);
288 :
289 : /* Top level blocks should not have sibling pointers. */
290 1585001 : if (level == 0) {
291 1114428 : if (forw != 0 || back != 0)
292 0 : xchk_da_set_corrupt(ds, level);
293 1114428 : return 0;
294 : }
295 :
296 : /*
297 : * Check back (left) and forw (right) pointers. These functions
298 : * absorb error codes for us.
299 : */
300 470573 : error = xchk_da_btree_block_check_sibling(ds, level, 0, back);
301 470573 : if (error)
302 0 : goto out;
303 470573 : error = xchk_da_btree_block_check_sibling(ds, level, 1, forw);
304 :
305 470573 : out:
306 470573 : memset(&ds->state->altpath, 0, sizeof(ds->state->altpath));
307 470573 : return error;
308 : }
309 :
310 : /* Load a dir/attribute block from a btree. */
311 : STATIC int
312 1719383 : xchk_da_btree_block(
313 : struct xchk_da_btree *ds,
314 : int level,
315 : xfs_dablk_t blkno)
316 : {
317 1719383 : struct xfs_da_state_blk *blk;
318 1719383 : struct xfs_da_intnode *node;
319 1719383 : struct xfs_da_node_entry *btree;
320 1719383 : struct xfs_da3_blkinfo *hdr3;
321 1719383 : struct xfs_da_args *dargs = &ds->dargs;
322 1719383 : struct xfs_inode *ip = ds->dargs.dp;
323 1719383 : xfs_ino_t owner;
324 1719383 : int *pmaxrecs;
325 1719383 : struct xfs_da3_icnode_hdr nodehdr;
326 1719383 : int error = 0;
327 :
328 1719383 : blk = &ds->state->path.blk[level];
329 1719396 : ds->state->path.active = level + 1;
330 :
331 : /* Release old block. */
332 1719396 : if (blk->bp) {
333 417231 : xfs_trans_brelse(dargs->trans, blk->bp);
334 417231 : blk->bp = NULL;
335 : }
336 :
337 : /* Check the pointer. */
338 1719396 : blk->blkno = blkno;
339 1719396 : if (!xchk_da_btree_ptr_ok(ds, level, blkno))
340 0 : goto out_nobuf;
341 :
342 : /* Read the buffer. */
343 1719374 : 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 1719388 : if (!xchk_da_process_error(ds, level, &error))
347 0 : goto out_nobuf;
348 1719391 : if (blk->bp)
349 1585012 : 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 1719375 : if (ds->dargs.whichfork == XFS_DATA_FORK && level == 0 &&
357 186144 : blk->bp == NULL)
358 134379 : goto out_nobuf;
359 :
360 : /* It's /not/ ok for attr trees not to have a da btree. */
361 1584996 : if (blk->bp == NULL) {
362 0 : xchk_da_set_corrupt(ds, level);
363 0 : goto out_nobuf;
364 : }
365 :
366 1584996 : hdr3 = blk->bp->b_addr;
367 1584996 : blk->magic = be16_to_cpu(hdr3->hdr.magic);
368 1584996 : pmaxrecs = &ds->maxrecs[level];
369 :
370 : /* We only started zeroing the header on v5 filesystems. */
371 1584997 : if (xfs_has_crc(ds->sc->mp) && hdr3->hdr.pad)
372 0 : xchk_da_set_corrupt(ds, level);
373 :
374 : /* Check the owner. */
375 1584997 : if (xfs_has_crc(ip->i_mount)) {
376 1584998 : owner = be64_to_cpu(hdr3->owner);
377 1584998 : if (owner != ip->i_ino)
378 0 : xchk_da_set_corrupt(ds, level);
379 : }
380 :
381 : /* Check the siblings. */
382 1584997 : error = xchk_da_btree_block_check_siblings(ds, level, &hdr3->hdr);
383 1585004 : if (error)
384 0 : goto out;
385 :
386 : /* Interpret the buffer. */
387 1585004 : switch (blk->magic) {
388 1089045 : case XFS_ATTR_LEAF_MAGIC:
389 : case XFS_ATTR3_LEAF_MAGIC:
390 1089045 : xfs_trans_buf_set_type(dargs->trans, blk->bp,
391 : XFS_BLFT_ATTR_LEAF_BUF);
392 1089044 : blk->magic = XFS_ATTR_LEAF_MAGIC;
393 1089044 : blk->hashval = xfs_attr_leaf_lasthash(blk->bp, pmaxrecs);
394 1089048 : if (ds->tree_level != 0)
395 0 : xchk_da_set_corrupt(ds, level);
396 : break;
397 431269 : case XFS_DIR2_LEAFN_MAGIC:
398 : case XFS_DIR3_LEAFN_MAGIC:
399 431269 : xfs_trans_buf_set_type(dargs->trans, blk->bp,
400 : XFS_BLFT_DIR_LEAFN_BUF);
401 431269 : blk->magic = XFS_DIR2_LEAFN_MAGIC;
402 431269 : blk->hashval = xfs_dir2_leaf_lasthash(ip, blk->bp, pmaxrecs);
403 431269 : if (ds->tree_level != 0)
404 0 : xchk_da_set_corrupt(ds, level);
405 : break;
406 11316 : case XFS_DIR2_LEAF1_MAGIC:
407 : case XFS_DIR3_LEAF1_MAGIC:
408 11316 : xfs_trans_buf_set_type(dargs->trans, blk->bp,
409 : XFS_BLFT_DIR_LEAF1_BUF);
410 11316 : blk->magic = XFS_DIR2_LEAF1_MAGIC;
411 11316 : blk->hashval = xfs_dir2_leaf_lasthash(ip, blk->bp, pmaxrecs);
412 11316 : if (ds->tree_level != 0)
413 0 : xchk_da_set_corrupt(ds, level);
414 : break;
415 53374 : case XFS_DA_NODE_MAGIC:
416 : case XFS_DA3_NODE_MAGIC:
417 53374 : xfs_trans_buf_set_type(dargs->trans, blk->bp,
418 : XFS_BLFT_DA_NODE_BUF);
419 53374 : blk->magic = XFS_DA_NODE_MAGIC;
420 53374 : node = blk->bp->b_addr;
421 53374 : xfs_da3_node_hdr_from_disk(ip->i_mount, &nodehdr, node);
422 53374 : btree = nodehdr.btree;
423 53374 : *pmaxrecs = nodehdr.count;
424 53374 : blk->hashval = be32_to_cpu(btree[*pmaxrecs - 1].hashval);
425 53374 : if (level == 0) {
426 53337 : if (nodehdr.level >= XFS_DA_NODE_MAXDEPTH) {
427 0 : xchk_da_set_corrupt(ds, level);
428 0 : goto out_freebp;
429 : }
430 53337 : ds->tree_level = nodehdr.level;
431 : } else {
432 37 : 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 1585007 : if (level > 0) {
450 470573 : struct xfs_da_node_entry *key;
451 :
452 470573 : key = xchk_da_btree_node_entry(ds, level - 1);
453 941146 : 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 1585007 : out:
460 : return error;
461 0 : out_freebp:
462 0 : xfs_trans_brelse(dargs->trans, blk->bp);
463 0 : blk->bp = NULL;
464 134379 : out_nobuf:
465 134379 : blk->blkno = 0;
466 134379 : return error;
467 : }
468 :
469 : /* Visit all nodes and leaves of a da btree. */
470 : int
471 80767740 : xchk_da_btree(
472 : struct xfs_scrub *sc,
473 : int whichfork,
474 : xchk_da_btree_rec_fn scrub_fn,
475 : void *private)
476 : {
477 80767740 : struct xchk_da_btree *ds;
478 80767740 : struct xfs_mount *mp = sc->mp;
479 80767740 : struct xfs_da_state_blk *blks;
480 80767740 : struct xfs_da_node_entry *key;
481 80767740 : xfs_dablk_t blkno;
482 80767740 : int level;
483 80767740 : int error;
484 :
485 : /* Skip short format data structures; no btree to scan. */
486 80767740 : if (!xfs_ifork_has_extents(xfs_ifork_ptr(sc->ip, whichfork)))
487 : return 0;
488 :
489 : /* Set up initial da state. */
490 1248822 : ds = kzalloc(sizeof(struct xchk_da_btree), XCHK_GFP_FLAGS);
491 1248824 : if (!ds)
492 : return -ENOMEM;
493 1248824 : ds->dargs.dp = sc->ip;
494 1248824 : ds->dargs.whichfork = whichfork;
495 1248824 : ds->dargs.trans = sc->tp;
496 1248824 : ds->dargs.op_flags = XFS_DA_OP_OKNOENT;
497 1248824 : ds->state = xfs_da_state_alloc(&ds->dargs);
498 1248826 : ds->sc = sc;
499 1248826 : ds->private = private;
500 1248826 : if (whichfork == XFS_ATTR_FORK) {
501 1062682 : ds->dargs.geo = mp->m_attr_geo;
502 1062682 : ds->lowest = 0;
503 1062682 : ds->highest = 0;
504 : } else {
505 186144 : ds->dargs.geo = mp->m_dir_geo;
506 186144 : ds->lowest = ds->dargs.geo->leafblk;
507 186144 : ds->highest = ds->dargs.geo->freeblk;
508 : }
509 1248826 : blkno = ds->lowest;
510 1248826 : level = 0;
511 :
512 : /* Find the root of the da tree, if present. */
513 1248826 : blks = ds->state->path.blk;
514 1248826 : error = xchk_da_btree_block(ds, level, blkno);
515 1248822 : 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 1248822 : if (blks[level].bp == NULL)
523 134379 : goto out_state;
524 :
525 1114443 : blks[level].index = 0;
526 153609675 : while (level >= 0 && level < XFS_DA_NODE_MAXDEPTH) {
527 : /* Handle leaf block. */
528 152495254 : if (blks[level].magic != XFS_DA_NODE_MAGIC) {
529 : /* End of leaf, pop back towards the root. */
530 151971309 : if (blks[level].index >= ds->maxrecs[level]) {
531 1531610 : if (level > 0)
532 470534 : blks[level - 1].index++;
533 1531610 : ds->tree_level++;
534 1531610 : level--;
535 1531610 : continue;
536 : }
537 :
538 : /* Dispatch record scrubbing. */
539 150437581 : error = scrub_fn(ds, level);
540 150433951 : if (error)
541 : break;
542 150433951 : if (xchk_should_terminate(sc, &error) ||
543 150439675 : (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT))
544 : break;
545 :
546 150439675 : blks[level].index++;
547 150439675 : continue;
548 : }
549 :
550 :
551 : /* End of node, pop back towards the root. */
552 523945 : if (blks[level].index >= ds->maxrecs[level]) {
553 53374 : if (level > 0)
554 37 : blks[level - 1].index++;
555 53374 : ds->tree_level++;
556 53374 : level--;
557 53374 : continue;
558 : }
559 :
560 : /* Hashes in order for scrub? */
561 470573 : key = xchk_da_btree_node_entry(ds, level);
562 470573 : error = xchk_da_btree_hash(ds, level, &key->hashval);
563 470573 : if (error)
564 0 : goto out;
565 :
566 : /* Drill another level deeper. */
567 470573 : blkno = be32_to_cpu(key->before);
568 470573 : level++;
569 470573 : if (level >= XFS_DA_NODE_MAXDEPTH) {
570 : /* Too deep! */
571 0 : xchk_da_set_corrupt(ds, level - 1);
572 0 : break;
573 : }
574 470573 : ds->tree_level--;
575 470573 : error = xchk_da_btree_block(ds, level, blkno);
576 470573 : if (error)
577 0 : goto out;
578 470573 : if (blks[level].bp == NULL)
579 0 : goto out;
580 :
581 470573 : blks[level].index = 0;
582 : }
583 :
584 1114421 : out:
585 : /* Release all the buffers we're tracking. */
586 6686648 : for (level = 0; level < XFS_DA_NODE_MAXDEPTH; level++) {
587 5572201 : if (blks[level].bp == NULL)
588 4404442 : continue;
589 1167759 : xfs_trans_brelse(sc->tp, blks[level].bp);
590 1167785 : blks[level].bp = NULL;
591 : }
592 :
593 1114447 : out_state:
594 1248826 : xfs_da_state_free(ds->state);
595 1248824 : kfree(ds);
596 1248826 : return error;
597 : }
|