~ubuntu-branches/ubuntu/karmic/firebird2.1/karmic

« back to all changes in this revision

Viewing changes to src/jrd/validation.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Damyan Ivanov
  • Date: 2008-05-26 23:59:25 UTC
  • Revision ID: james.westby@ubuntu.com-20080526235925-2pnqj6nxpppoeaer
Tags: upstream-2.1.0.17798-0.ds2
ImportĀ upstreamĀ versionĀ 2.1.0.17798-0.ds2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *      PROGRAM:        JRD Access Method
 
3
 *      MODULE:         validation.cpp
 
4
 *      DESCRIPTION:    Validation and garbage collection
 
5
 *
 
6
 * The contents of this file are subject to the Interbase Public
 
7
 * License Version 1.0 (the "License"); you may not use this file
 
8
 * except in compliance with the License. You may obtain a copy
 
9
 * of the License at http://www.Inprise.com/IPL.html
 
10
 *
 
11
 * Software distributed under the License is distributed on an
 
12
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 
13
 * or implied. See the License for the specific language governing
 
14
 * rights and limitations under the License.
 
15
 *
 
16
 * The Original Code was created by Inprise Corporation
 
17
 * and its predecessors. Portions created by Inprise Corporation are
 
18
 * Copyright (C) Inprise Corporation.
 
19
 *
 
20
 * All Rights Reserved.
 
21
 * Contributor(s): ______________________________________.
 
22
 */
 
23
 
 
24
 
 
25
/*
 
26
 
 
27
                      Database Validation and Repair
 
28
                      ==============================
 
29
 
 
30
                              Deej Bredenberg
 
31
                              March 16, 1994
 
32
                        Updated: 1996-Dec-11 David Schnepper 
 
33
 
 
34
 
 
35
I. TERMINOLOGY 
 
36
 
 
37
   The following terminology will be helpful to understand in this discussion:
 
38
 
 
39
   record fragment: The smallest recognizable piece of a record; multiple 
 
40
                    fragments can be linked together to form a single version.
 
41
   record version:  A single version of a record representing an INSERT, UPDATE
 
42
                    or DELETE by a particular transaction (note that deletion 
 
43
                    of a record causes a new version to be stored as a
 
44
                    deleted stub).
 
45
   record chain:    A linked list of record versions chained together to
 
46
                    represent a single logical "record".
 
47
   slot:            The line number of the record on page.  A
 
48
                    variable-length array on each data page stores the 
 
49
                    offsets to the stored records on 
 
50
                    that page, and the slot is an index into that array.
 
51
 
 
52
   For more information on data page format, see my paper on the internals
 
53
   of the InterBase Engine.
 
54
 
 
55
II. COMMAND OPTIONS
 
56
 
 
57
   Here are all the options for gfix which have to do with validation, and 
 
58
   what they do:
 
59
 
 
60
   gfix switch   dpb parameter      
 
61
   -----------   -------------      
 
62
 
 
63
   -validate    isc_dpb_verify  (gds__dpb_verify prior to 4.0)
 
64
 
 
65
    Invoke validation and repair.  All other switches modify this switch.
 
66
 
 
67
   -full        isc_dpb_records       
 
68
 
 
69
    Visit all records.  Without this switch, only page structures will be
 
70
    validated, which does involve some limited checking of records.     
 
71
 
 
72
   -mend        isc_dpb_repair     
 
73
 
 
74
    Attempts to mend the database where it can to make it viable for reading;
 
75
    does not guarantee to retain data.
 
76
 
 
77
   -no_update   isc_dpb_no_update  
 
78
 
 
79
    Specifies that orphan pages not be released, and allocated pages not 
 
80
    be marked in use when found to be free.  Actually a misleading switch
 
81
    name since -mend will update the database, but if -mend is not specified 
 
82
    and -no_update is specified, then no updates will occur to the database.
 
83
 
 
84
   -ignore      isc_dpb_ignore
 
85
 
 
86
    Tells the engine to ignore checksums in fetching pages.  Validate will 
 
87
    report on the checksums, however.  Should probably not even be a switch,
 
88
    it should just always be in effect.  Otherwise checksums will disrupt  
 
89
    the validation.  Customers should be advised to always use it.
 
90
    NOTE: Unix 4.0 (ODS 8.0) does not have on-page checksums, and all
 
91
          platforms under ODS 9.0 (NevaStone & above) does not have
 
92
          checksums.
 
93
 
 
94
III.  OPERATION
 
95
 
 
96
   Validation runs only with exclusive access to the database, to ensure
 
97
   that database structures are not modified during validation.  On attach,
 
98
   validate attempts to obtain an exclusive lock on the database.
 
99
 
 
100
   If other attachments are already made locally or through the same multi-
 
101
   client server, validate gives up with the message:
 
102
 
 
103
   "Lock timeout during wait transaction
 
104
   -- Object "database_filename.fdb" is in use"
 
105
 
 
106
   If other processes or servers are attached to the database, validate 
 
107
   waits for the exclusive lock on the database (i.e. waits for every 
 
108
   other server to get out of the database).
 
109
 
 
110
   NOTE: Ordinarily when processes gain exclusive access to the database, 
 
111
   all active transactions are marked as dead on the Transaction Inventory 
 
112
   Pages.  This feature is turned off for validation.
 
113
 
 
114
IV. PHASES OF VALIDATION
 
115
 
 
116
   There are two phases to the validation, the first of which is a walk through
 
117
   the entire database (described below).  During this phase, all pages visited 
 
118
   are stored in a bitmap for later use during the garbage collection phase.  
 
119
 
 
120
   A. Visiting Pages
 
121
 
 
122
      During the walk-through phase, any page that is fetched goes through a
 
123
      basic validation:
 
124
 
 
125
      1. Page Type Check
 
126
 
 
127
         Each page is check against its expected type.  If the wrong type
 
128
         page is found in the page header, the message:
 
129
 
 
130
         "Page xxx wrong type (expected xxx encountered xxx)"
 
131
 
 
132
         is returned.  This could represent a) a problem with the database 
 
133
         being overwritten, b) a bug with InterBase page allocation mechanisms 
 
134
         in which one page was written over another, or c) a page which was 
 
135
         allocated but never written to disk (most likely if the encountered
 
136
         page type was 0).
 
137
 
 
138
         The error does not tell you what page types are what, so here
 
139
         they are for reference:
 
140
 
 
141
         define pag_undefined     0    // purposely undefined
 
142
         define pag_header        1    // Database header page
 
143
         define pag_pages         2    // Page inventory page
 
144
         define pag_transactions  3    // Transaction inventory page
 
145
         define pag_pointer       4    // Pointer page
 
146
         define pag_data          5    // Data page
 
147
         define pag_root          6    // Index root page
 
148
         define pag_index         7    // Index (B-tree) page
 
149
         define pag_blob          8    // Blob data page
 
150
         define pag_ids           9    // Gen-ids
 
151
         define pag_log           10   // Write ahead log page: 4.0 only
 
152
 
 
153
      2. Checksum
 
154
 
 
155
         If -ignore is specified, the checksum is specifically checked in
 
156
         validate instead of in the engine.  If the checksum is found to 
 
157
         be wrong, the error:
 
158
 
 
159
         "Checksum error on page xxx"
 
160
 
 
161
         is returned.  This is harmless when found by validate, and the page
 
162
         will still continue to be validated--if data structures can be 
 
163
         validated on page, they will be.  If -mend is specified, the page 
 
164
         will be marked for write, so that when the page is written to disk 
 
165
         at the end of validation the checksum will automatically be 
 
166
         recalculated.
 
167
 
 
168
         Note: For 4.0 only Windows & NLM platforms keep page checksums.
 
169
 
 
170
      3. Revisit
 
171
 
 
172
         We check each page fetched against the page bitmap to make sure we
 
173
         have not visited already.  If we have, the error:
 
174
 
 
175
          "Page xxx doubly allocated"
 
176
 
 
177
         is returned.  This should catch the case when a page of the same type 
 
178
         is allocated for two different purposes.
 
179
 
 
180
         Data pages are not checked with the Revisit mechanism - when walking
 
181
         record chains and fragments they are frequently revisited.
 
182
 
 
183
   B. Garbage Collection
 
184
 
 
185
      During this phase, the Page Inventory (PIP) pages are checked against the
 
186
      bitmap of pages visited.  Two types of errors can be detected during
 
187
      this phase.
 
188
 
 
189
      1. Orphan Pages
 
190
 
 
191
         If any pages in the page inventory were not visited 
 
192
         during validation, the following error will be returned:
 
193
 
 
194
         "Page xxx is an orphan"
 
195
 
 
196
         If -no_update was not specified, the page will be marked as free
 
197
         on the PIP.
 
198
 
 
199
      2. Improperly Freed Pages
 
200
 
 
201
         If any pages marked free in the page inventory were in fact 
 
202
         found to be in use during validation, the following error 
 
203
         will be returned:
 
204
 
 
205
         "Page xxx is use but marked free"  (sic)
 
206
     
 
207
         If -no_update was not specified, the page will be marked in use
 
208
         on the PIP.
 
209
 
 
210
      NOTE:  If errors were found during the validation phase, no changes will
 
211
      be made to the PIP pages.  This assumes that we did not have a chance to
 
212
      visit all the pages because invalid structures were detected.
 
213
 
 
214
V. WALK-THROUGH PHASE
 
215
 
 
216
   A. Page Fetching
 
217
 
 
218
      In order to ensure that all pages are fetched during validation, the
 
219
      following pages are fetched just for the most basic validation:
 
220
 
 
221
      1. The header page (and for 4.0 any overflow header pages).
 
222
      2. Log pages for after-image journalling (4.0 only).
 
223
      3. Page Inventory pages.
 
224
      4. Transaction Inventory pages
 
225
 
 
226
         If the system relation RDB$PAGES could not be read or did not
 
227
         contain any TIP pages, the message: 
 
228
 
 
229
         "Transaction inventory pages lost"
 
230
 
 
231
         will be returned.  If a particular page is missing from the 
 
232
         sequence as established by RDB$PAGE_SEQUENCE, then the following
 
233
         message will be returned:
 
234
                                        
 
235
         "Transaction inventory page lost, sequence xxx"
 
236
 
 
237
         If -mend is specified, then a new TIP will be allocated on disk and 
 
238
         stored in RDB$PAGES in the proper sequence.  All transactions which 
 
239
         would have been on that page are assumed committed.
 
240
 
 
241
         If a TIP page does not point to the next one in sequence, the
 
242
         following message will be returned:
 
243
 
 
244
         "Transaction inventory pages confused, sequence xxx"
 
245
 
 
246
      5. Generator pages as identified in RDB$PAGES.
 
247
   
 
248
   B. Relation Walking
 
249
 
 
250
      All the relations in the database are walked.  For each relation, all
 
251
      indices defined on the relation are fetched, and all pointer and 
 
252
      data pages associated with the relation are fetched (see below).
 
253
 
 
254
      But first, the metadata is scanned from RDB$RELATIONS to fetch the
 
255
      format of the relation.  If this information is missing or 
 
256
      corrupted the relation cannot be walked.  
 
257
      If any bugchecks are encountered from the scan, the following 
 
258
      message is returned:
 
259
 
 
260
      "bugcheck during scan of table xxx (<table_name>)"
 
261
 
 
262
      This will prevent any further validation of the relation.
 
263
 
 
264
      NOTE: For views, the metadata is scanned but nothing further is done.
 
265
 
 
266
   C. Index Walking
 
267
 
 
268
      Prior to 4.5 (NevaStone) Indices were walked before data pages.
 
269
      In NevaStone Index walking was moved to after data page walking.
 
270
      Please refer to the later section entitled "Index Walking".
 
271
 
 
272
   D. Pointer Pages
 
273
 
 
274
      All the pointer pages for the relation are walked.  As they are walked
 
275
      all child data pages are walked (see below).  If a pointer page cannot 
 
276
      be found, the following message is returned:
 
277
 
 
278
      "Pointer page (sequence xxx) lost"
 
279
 
 
280
      If the pointer page is not part of the relation we expected or
 
281
      if it is not marked as being in the proper sequence, the following
 
282
      message is returned:
 
283
                       
 
284
      "Pointer page xxx is inconsistent"
 
285
 
 
286
      If each pointer page does not point to the next pointer page as
 
287
      stored in the RDB$PAGE_SEQUENCE field in RDB$PAGES, the following 
 
288
      error is returned:
 
289
 
 
290
      "Pointer page (sequence xxx) inconsistent"
 
291
 
 
292
   E. Data Pages
 
293
 
 
294
      Each of the data pages referenced by the pointer page is fetched.
 
295
      If any are found to be corrupt at the page level, and -mend is 
 
296
      specified, the page is deleted from its pointer page.  This will 
 
297
      cause a whole page of data to be lost.
 
298
 
 
299
      The data page is corrupt at the page level if it is not marked as
 
300
      part of the current relation, or if it is not marked as being in 
 
301
      the proper sequence.  If either of these conditions occurs, the 
 
302
      following error is returned:
 
303
 
 
304
      "Data page xxx (sequence xxx) is confused"
 
305
 
 
306
   F. Slot Validation
 
307
 
 
308
      Each of the slots on the data page is looked at, up to the count
 
309
      of records stored on page.  If the slot is non-zero, the record 
 
310
      fragment at the specified offset is retrieved.  If the record
 
311
      begins before the end of the slots array, or continues off the
 
312
      end of the page, the following error is returned:
 
313
 
 
314
      "Data page xxx (sequence xxx), line xxx is bad"
 
315
 
 
316
      where "line" means the slot number.  
 
317
  
 
318
      NOTE: If this condition is encountered, the data page is considered 
 
319
      corrupt at the page level (and thus will be removed from its
 
320
      pointer page if -mend is specified).
 
321
                                          
 
322
   G. Record Validation
 
323
                       
 
324
      The record at each slot is looked at for basic validation, regardless
 
325
      of whether -full is specified or not.  The fragment could be any of the 
 
326
      following:
 
327
 
 
328
      1.  Back Version
 
329
 
 
330
          If the fragment is marked as a back version, then it is skipped.  
 
331
          It will be fetched as part of its record.
 
332
 
 
333
      2.  Corrupt
 
334
 
 
335
          If the fragment is determined to be corrupt for any reason, and -mend 
 
336
          is specified, then the record header is marked as damaged.
 
337
 
 
338
      3.  Damaged
 
339
 
 
340
          If the fragment is marked damaged already from a previous visit or
 
341
          a previous validation, the following error is returned:
 
342
      
 
343
          "Record xxx is marked as damaged"
 
344
 
 
345
          where xxx is the record number.  
 
346
 
 
347
      4.  Bad Transaction 
 
348
 
 
349
          If the record is marked with a transaction id greater than the last 
 
350
          transaction started in the database, the following error is returned:
 
351
                                         
 
352
          "Record xxx has bad transaction xxx"
 
353
 
 
354
   H. Record Walking
 
355
 
 
356
      If -full is specified, and the fragment is the first fragment in a logical
 
357
      record, then the record at this slot number is fully retrieved.  This
 
358
      involves retrieving all versions, and all fragments of each 
 
359
      particular version.  In other 
 
360
      words, the entire logical record will be retrieved.
 
361
 
 
362
      1. Back Versions
 
363
 
 
364
         If there are any back versions, they are visited at this point.  
 
365
         If the back version is on another page, the page is fetched but 
 
366
         not validated since it will be walked separately.  
 
367
 
 
368
         If the slot number of the back version is greater than the max
 
369
         records on page, or there is no record stored at that slot number, 
 
370
         or it is a blob record, or it is a record fragment, or the 
 
371
         fragment itself is invalid, the following error 
 
372
         message is returned:
 
373
 
 
374
         "Chain for record xxx is broken"
 
375
 
 
376
      2. Incomplete
 
377
 
 
378
         If the record header is marked as incomplete, it means that there
 
379
         are additional fragments to be fetched--the record was too large 
 
380
         to be stored in one slot.
 
381
         A pointer is stored in the record to the next fragment in the list.
 
382
 
 
383
         For fragmented records, all fragments are fetched to form a full
 
384
         record version.  If any of the fragments is not in a valid position,
 
385
         or is not the correct length, the following error is returned:
 
386
 
 
387
         "Fragmented record xxx is corrupt"      
 
388
   
 
389
      Once the full record has been retrieved, the length of the format is
 
390
      checked against the expected format stored in RDB$FORMATS (the 
 
391
      format number is stored with the record, representing the exact 
 
392
      format of the relation at the time the record was stored.)  
 
393
      If the length of the reconstructed record does not match
 
394
      the expected format length, the following error is returned:
 
395
 
 
396
      "Record xxx is wrong length"
 
397
 
 
398
      For delta records (record versions which represent updates to the record)
 
399
      this check is not made.
 
400
 
 
401
   I. Blob Walking 
 
402
 
 
403
      If the slot on the data page points to a blob record, then the blob
 
404
      is fetched (even without -full).  This has several cases, corresponding 
 
405
      to the various blob levels.  (See the "Engine Internals" document for a 
 
406
      discussion of blob levels.)
 
407
             
 
408
      Level                      Action
 
409
      -----   ----------------------------------------------------------------- 
 
410
        0     These are just records on page, and no further validation is done.
 
411
        1     All the pages pointed to by the blob record are fetched and
 
412
              validated in sequence.
 
413
        2     All pages pointed to by the blob pointer pages are fetched and 
 
414
              validated.
 
415
        3     The blob page is itself a blob pointer page; all its children
 
416
              are fetched and validated.
 
417
 
 
418
      For each blob page found, some further validation is done.  If the
 
419
      page does not point back to the lead page, the following error 
 
420
      is returned:
 
421
 
 
422
      "Warning: blob xxx appears inconsistent"
 
423
   
 
424
      where xxx corresponds to the blob record number.  If any of the blob pages
 
425
      are not marked in the sequence we expect them to be in, the following
 
426
      error is returned:
 
427
 
 
428
      "Blob xxx is corrupt"
 
429
 
 
430
      Tip: the message for the same error in level 2 or 3 blobs is slightly
 
431
           different:
 
432
 
 
433
      "Blob xxx corrupt"
 
434
 
 
435
      If we have lost any of the blob pages in the sequence, the following error
 
436
      is returned:
 
437
 
 
438
      "Blob xxx is truncated"
 
439
 
 
440
      If the fetched blob is determined to be corrupt for any of the above
 
441
      reasons, and -mend is specified, then the blob record is marked as
 
442
      damaged.
 
443
 
 
444
   J. Index Walking
 
445
 
 
446
      In 4.5 (NevaStone) Index walking was moved to after the completion
 
447
      of data page walking.
 
448
 
 
449
      The indices for the relation are walked.  If the index root page
 
450
      is missing, the following message is returned:
 
451
 
 
452
      "Missing index root page"
 
453
 
 
454
      and the indices are not walked.  Otherwise the index root page
 
455
      is fetched and all indices on the page fetched. 
 
456
 
 
457
      For each index, the btree pages are fetched from top-down, left to
 
458
      right.  
 
459
      Basic validation is made on non-leaf pages to ensure that each node
 
460
      on page points to another index page.  If -full validation is specified
 
461
      then the lower level page is fetched to ensure it is starting index
 
462
      entry is consistent with the parent entry. 
 
463
      On leaf pages, the records pointed to by the index pages are not 
 
464
      fetched, the keys are looked at to ensure they are in correct 
 
465
      ascending order.
 
466
 
 
467
      If a visited page is not part of the specified relation and index,
 
468
      the following error is returned:
 
469
      
 
470
      "Index xxx is corrupt at page xxx"
 
471
        
 
472
      If there are orphan child pages, i.e. a child page does not have its entry 
 
473
      as yet in the parent page, however the child's left sibling page has it's 
 
474
      btr_sibling updated, the following error is returned
 
475
            
 
476
      "Index xxx has orphan child page at page xxx"
 
477
 
 
478
      If the page does not contain the number of nodes we would have
 
479
      expected from its marked length, the following error is returned:
 
480
 
 
481
      "Index xxx is corrupt on page xxx"
 
482
 
 
483
      While we are walking leaf pages, we keep a bitmap of all record
 
484
      numbers seen in the index.  At the conclusion of the index walk
 
485
      we compare this bitmap to the bitmap of all records in the 
 
486
      relation (calculated during data page/Record Validation phase).
 
487
      If the bitmaps are not equal then we have a corrupt index
 
488
      and the following error is reported:
 
489
 
 
490
      "Index %d is corrupt (missing entries)"
 
491
 
 
492
      We do NOT check that each version of each record has a valid
 
493
      index entry - nor do we check that the stored key for each item
 
494
      in the index corresponds to a version of the specified record.
 
495
 
 
496
   K. Relation Checking
 
497
 
 
498
      We count the number of backversions seen while walking pointer pages,
 
499
      and separately count the number of backversions seen while walking
 
500
      record chains.  If these numbers do not match it indicates either
 
501
      "orphan" backversion chains or double-linked chains.  If this is
 
502
      see the following error is returned:
 
503
 
 
504
     "Relation has %ld orphan backversions (%ld in use)"
 
505
 
 
506
      Currently we do not try to correct this condition, mearly report
 
507
      it.  For "orphan" backversions the space can be reclaimed by
 
508
      a backup/restore.  For double-linked chains a SWEEP should
 
509
      remove all the backversions.
 
510
 
 
511
VI. ADDITIONAL NOTES
 
512
 
 
513
   A.  Damaged Records
 
514
 
 
515
      If any corruption of a record fragment is seen during validation, the 
 
516
      record header is marked as "damaged".  As far as I can see, this has no 
 
517
      effect on the engine per se.  Records marked as damaged will still be 
 
518
      retrieved by the engine itself.  There is some question in my mind as
 
519
      to whether this record should be retrieved at all during a gbak.
 
520
 
 
521
      If a damaged record is visited, the following error message will appear:
 
522
 
 
523
      "Record xxx is marked as damaged"
 
524
 
 
525
      Note that when a damaged record is first detected, this message is not
 
526
      actually printed.  The record is simply marked as damaged.  It is only 
 
527
      thereafter when the record is visited that this message will appear.
 
528
      So I would postulate that unless a full validation is done at some point,
 
529
      you would not see this error message; once the full validation is done, 
 
530
      the message will be returned even if you do not specify -full.
 
531
 
 
532
   B. Damaged Blobs
 
533
 
 
534
      Blob records marked as damaged cannot be opened and will not be deleted 
 
535
      from disk.  This means that even during backup the blob structures marked 
 
536
      as damaged will not be fetched and backed up.  (Why this is done
 
537
      differently for blobs than for records I cannot say.  
 
538
      Perhaps it was viewed as too difficult to try to retrieve a damaged blob.)
 
539
 
 
540
*/
 
541
 
 
542
#include "firebird.h"
 
543
#include "memory_routines.h"
 
544
#include <stdio.h>
 
545
#include "../jrd/common.h"
 
546
#include <stdarg.h>
 
547
#include "../jrd/jrd.h"
 
548
#include "../jrd/ods.h"
 
549
#include "../jrd/pag.h"
 
550
#include "../jrd/ibase.h"
 
551
#include "../jrd/val.h"
 
552
#include "../jrd/btr.h"
 
553
#include "../jrd/btn.h"
 
554
#include "../jrd/all.h"
 
555
#include "../jrd/lck.h"
 
556
#include "../jrd/cch.h"
 
557
#include "../jrd/rse.h"
 
558
#include "../jrd/sbm.h"
 
559
#include "../jrd/tra.h"
 
560
#include "../jrd/btr_proto.h"
 
561
#include "../jrd/cch_proto.h"
 
562
#include "../jrd/dbg_proto.h"
 
563
#include "../jrd/dpm_proto.h"
 
564
#include "../jrd/err_proto.h"
 
565
#include "../jrd/jrd_proto.h"
 
566
#include "../jrd/gds_proto.h"
 
567
#include "../jrd/met_proto.h"
 
568
#include "../jrd/sch_proto.h"
 
569
#include "../jrd/thd.h"
 
570
#include "../jrd/tra_proto.h"
 
571
#include "../jrd/val_proto.h"
 
572
#include "../jrd/thread_proto.h"
 
573
 
 
574
#ifdef DEBUG_VAL_VERBOSE
 
575
#include "../jrd/dmp_proto.h"
 
576
/* Control variable for verbose output during debug of
 
577
   validation.  
 
578
   0 == logged errors only
 
579
   1 == logical output also
 
580
   2 == physical page output also */
 
581
static USHORT VAL_debug_level = 0;
 
582
#endif
 
583
 
 
584
using namespace Jrd;
 
585
using namespace Ods;
 
586
 
 
587
/* Validation/garbage collection/repair control block */
 
588
 
 
589
struct vdr
 
590
{
 
591
        PageBitmap* vdr_page_bitmap;
 
592
        SLONG vdr_max_page;
 
593
        USHORT vdr_flags;
 
594
        USHORT vdr_errors;
 
595
        SLONG vdr_max_transaction;
 
596
        ULONG vdr_rel_backversion_counter;      /* Counts slots w/rhd_chain */
 
597
        ULONG vdr_rel_chain_counter;    /* Counts chains w/rdr_chain */
 
598
        RecordBitmap* vdr_rel_records;          /* 1 bit per valid record */
 
599
        RecordBitmap* vdr_idx_records;          /* 1 bit per index item */
 
600
};
 
601
 
 
602
// vdr_flags
 
603
 
 
604
const USHORT vdr_update         = 2;            /* fix simple things */
 
605
const USHORT vdr_repair         = 4;            /* fix non-simple things (-mend) */
 
606
const USHORT vdr_records        = 8;            /* Walk all records */
 
607
 
 
608
enum FETCH_CODE {
 
609
        fetch_ok,
 
610
        //fetch_checksum,
 
611
        fetch_type,
 
612
        fetch_duplicate
 
613
};
 
614
 
 
615
enum RTN {
 
616
        rtn_ok,
 
617
        rtn_corrupt,
 
618
        rtn_eof
 
619
};
 
620
 
 
621
 
 
622
#pragma FB_COMPILER_MESSAGE("This table goes to gds__log and it's not localized")
 
623
 
 
624
static const TEXT msg_table[VAL_MAX_ERROR][66] =
 
625
{
 
626
        "Page %ld wrong type (expected %d encountered %d)",     // 0 
 
627
        "Checksum error on page %ld",
 
628
        "Page %ld doubly allocated",
 
629
        "Page %ld is used but marked free",
 
630
        "Page %ld is an orphan",
 
631
        "Warning: blob %ld appears inconsistent",       // 5 
 
632
        "Blob %ld is corrupt",
 
633
        "Blob %ld is truncated",
 
634
        "Chain for record %ld is broken",
 
635
        "Data page %ld (sequence %ld) is confused",
 
636
        "Data page %ld (sequence %ld), line %ld is bad",        // 10 
 
637
        "Index %d is corrupt on page %ld level %ld. File: %s, line: %ld\n\t",
 
638
        "Pointer page (sequence %ld) lost",
 
639
        "Pointer page (sequence %ld) inconsistent",
 
640
        "Record %ld is marked as damaged",
 
641
        "Record %ld has bad transaction %ld",   // 15 
 
642
        "Fragmented record %ld is corrupt",
 
643
        "Record %ld is wrong length",
 
644
        "Missing index root page",
 
645
        "Transaction inventory pages lost",
 
646
        "Transaction inventory page lost, sequence %ld",        // 20 
 
647
        "Transaction inventory pages confused, sequence %ld",
 
648
        "Relation has %ld orphan backversions (%ld in use)",
 
649
        "Index %d is corrupt (missing entries)",
 
650
        "Index %d has orphan child page at page %ld",
 
651
        "Index %d has a circular reference at page %ld"
 
652
};
 
653
 
 
654
 
 
655
static RTN corrupt(thread_db*, vdr*, USHORT, const jrd_rel*, ...);
 
656
static FETCH_CODE fetch_page(thread_db*, vdr*, SLONG, USHORT, WIN *, void *);
 
657
static void garbage_collect(thread_db*, vdr*);
 
658
#ifdef DEBUG_VAL_VERBOSE
 
659
static void print_rhd(USHORT, const rhd*);
 
660
#endif
 
661
static RTN walk_blob(thread_db*, vdr*, jrd_rel*, blh*, USHORT, SLONG);
 
662
static RTN walk_chain(thread_db*, vdr*, jrd_rel*, rhd*, SLONG);
 
663
static void walk_database(thread_db*, vdr*);
 
664
static RTN walk_data_page(thread_db*, vdr*, jrd_rel*, SLONG, SLONG);
 
665
static void walk_generators(thread_db*, vdr*);
 
666
static void walk_header(thread_db*, vdr*, SLONG);
 
667
static RTN walk_index(thread_db*, vdr*, jrd_rel*, index_root_page&, USHORT);
 
668
static void walk_log(thread_db*, vdr*);
 
669
static void walk_pip(thread_db*, vdr*);
 
670
static RTN walk_pointer_page(thread_db*, vdr*, jrd_rel*, int);
 
671
static RTN walk_record(thread_db*, vdr*, jrd_rel*, rhd*, USHORT, SLONG, bool);
 
672
static RTN walk_relation(thread_db*, vdr*, jrd_rel*);
 
673
static RTN walk_root(thread_db*, vdr*, jrd_rel*);
 
674
static RTN walk_tip(thread_db*, vdr*, SLONG);
 
675
 
 
676
 
 
677
 
 
678
bool VAL_validate(thread_db* tdbb, USHORT switches)
 
679
{
 
680
/**************************************
 
681
 *
 
682
 *      V A L _ v a l i d a t e
 
683
 *
 
684
 **************************************
 
685
 *
 
686
 * Functional description
 
687
 *      Validate a database.
 
688
 *
 
689
 **************************************/
 
690
        JrdMemoryPool* val_pool = 0;
 
691
 
 
692
        SET_TDBB(tdbb);
 
693
        Database* dbb = tdbb->getDatabase();
 
694
        Attachment* att = tdbb->getAttachment();
 
695
 
 
696
        try {
 
697
 
 
698
        val_pool = JrdMemoryPool::createPool();
 
699
        Jrd::ContextPoolHolder context(tdbb, val_pool);
 
700
 
 
701
        vdr control;
 
702
        control.vdr_page_bitmap = NULL;
 
703
        control.vdr_flags = 0;
 
704
        control.vdr_errors = 0;
 
705
 
 
706
        if (switches & isc_dpb_records)
 
707
                control.vdr_flags |= vdr_records;
 
708
 
 
709
        if (switches & isc_dpb_repair)
 
710
                control.vdr_flags |= vdr_repair;
 
711
 
 
712
        if (!(switches & isc_dpb_no_update))
 
713
                control.vdr_flags |= vdr_update;
 
714
 
 
715
        control.vdr_max_page = 0;
 
716
        control.vdr_rel_records = NULL;
 
717
        control.vdr_idx_records = NULL;
 
718
 
 
719
/* initialize validate errors */
 
720
 
 
721
        if (!att->att_val_errors) {
 
722
                att->att_val_errors = vcl::newVector(*dbb->dbb_permanent, VAL_MAX_ERROR);
 
723
        }
 
724
        else {
 
725
                for (USHORT i = 0; i < VAL_MAX_ERROR; i++)
 
726
                        (*att->att_val_errors)[i] = 0;
 
727
        }
 
728
 
 
729
        tdbb->tdbb_flags |= TDBB_sweeper;
 
730
        walk_database(tdbb, &control);
 
731
        if (control.vdr_errors)
 
732
                control.vdr_flags &= ~vdr_update;
 
733
 
 
734
        garbage_collect(tdbb, &control);
 
735
        CCH_flush(tdbb, FLUSH_FINI, 0);
 
736
 
 
737
        tdbb->tdbb_flags &= ~TDBB_sweeper;
 
738
        }       // try
 
739
        catch (const Firebird::Exception& ex) {
 
740
                Firebird::stuff_exception(tdbb->tdbb_status_vector, ex);
 
741
                JrdMemoryPool::deletePool(val_pool);
 
742
                tdbb->tdbb_flags &= ~TDBB_sweeper;
 
743
                return false;
 
744
        }
 
745
 
 
746
        JrdMemoryPool::deletePool(val_pool);
 
747
        return true;
 
748
}
 
749
 
 
750
static RTN corrupt(thread_db* tdbb, vdr* control, USHORT err_code, const jrd_rel* relation, ...)
 
751
{
 
752
/**************************************
 
753
 *
 
754
 *      c o r r u p t
 
755
 *
 
756
 **************************************
 
757
 *
 
758
 * Functional description
 
759
 *      Corruption has been detected.
 
760
 *
 
761
 **************************************/
 
762
 
 
763
        SET_TDBB(tdbb);
 
764
        Attachment* att = tdbb->getAttachment();
 
765
        if (err_code < att->att_val_errors->count())
 
766
                (*att->att_val_errors)[err_code]++;
 
767
 
 
768
        const TEXT* err_string = err_code < VAL_MAX_ERROR ? msg_table[err_code]: "Unknown error code";
 
769
 
 
770
        TEXT s[256] = "";
 
771
        va_list ptr;
 
772
        const char* fn = tdbb->getAttachment()->att_filename.c_str();
 
773
 
 
774
        va_start(ptr, relation);
 
775
        VSNPRINTF(s, sizeof(s), err_string, ptr);
 
776
        va_end(ptr);
 
777
 
 
778
#ifdef DEBUG_VAL_VERBOSE
 
779
        if (VAL_debug_level >= 0)
 
780
        {
 
781
                if (relation)
 
782
                {
 
783
                        fprintf(stdout, "LOG:\tDatabase: %s\n\t%s in table %s (%d)\n",
 
784
                                fn, s, relation->rel_name.c_str(), relation->rel_id);
 
785
                }
 
786
                else
 
787
                        fprintf(stdout, "LOG:\tDatabase: %s\n\t%s\n", fn, s);
 
788
        }
 
789
#endif
 
790
 
 
791
        if (relation)
 
792
        {
 
793
                gds__log("Database: %s\n\t%s in table %s (%d)",
 
794
                        fn, s, relation->rel_name.c_str(), relation->rel_id);
 
795
        }
 
796
        else
 
797
                gds__log("Database: %s\n\t%s", fn, s);
 
798
 
 
799
        if (control)
 
800
                ++control->vdr_errors;
 
801
 
 
802
        return rtn_corrupt;
 
803
}
 
804
 
 
805
static FETCH_CODE fetch_page(thread_db* tdbb,
 
806
                                                         vdr* control,
 
807
                                                         SLONG page_number,
 
808
                                                         USHORT type, WIN * window, void *page_pointer)
 
809
{
 
810
/**************************************
 
811
 *
 
812
 *      f e t c h _ p a g e
 
813
 *
 
814
 **************************************
 
815
 *
 
816
 * Functional description
 
817
 *      Fetch page and return type of illness, if any.  If a control block
 
818
 *      is present, check for doubly allocated pages and account for page
 
819
 *      use.
 
820
 *
 
821
 **************************************/
 
822
        SET_TDBB(tdbb);
 
823
        Database* dbb = tdbb->getDatabase();
 
824
        CHECK_DBB(dbb);
 
825
 
 
826
        if (--tdbb->tdbb_quantum < 0)
 
827
                JRD_reschedule(tdbb, 0, true);
 
828
 
 
829
        window->win_page = page_number;
 
830
        window->win_flags = 0;
 
831
        *(PAG *) page_pointer = CCH_FETCH_NO_SHADOW(tdbb, window, LCK_write, 0);
 
832
 
 
833
        if ((*(PAG *) page_pointer)->pag_type != type) {
 
834
                corrupt(tdbb, control, VAL_PAG_WRONG_TYPE,
 
835
                                0, page_number, type, (*(PAG *) page_pointer)->pag_type);
 
836
                return fetch_type;
 
837
        }
 
838
 
 
839
        if (!control)
 
840
                return fetch_ok;
 
841
 
 
842
/* If "damaged" flag was set, checksum may be incorrect.  Check. */
 
843
 
 
844
        if ((dbb->dbb_flags & DBB_damaged) && !CCH_validate(window)) {
 
845
                corrupt(tdbb, control, VAL_PAG_CHECKSUM_ERR, 0, page_number);
 
846
                if (control->vdr_flags & vdr_repair)
 
847
                        CCH_MARK(tdbb, window);
 
848
        }
 
849
 
 
850
        control->vdr_max_page = MAX(control->vdr_max_page, page_number);
 
851
 
 
852
/* For walking back versions & record fragments on data pages we
 
853
   sometimes will fetch the same page more than once.  In that
 
854
   event we don't report double allocation.  If the page is truely
 
855
   double allocated (to more than one relation) we'll find it
 
856
   when the on-page relation id doesn't match */
 
857
 
 
858
        if ((type != pag_data) && PageBitmap::test(control->vdr_page_bitmap, page_number)) {
 
859
                corrupt(tdbb, control, VAL_PAG_DOUBLE_ALLOC, 0, page_number);
 
860
                return fetch_duplicate;
 
861
        }
 
862
 
 
863
 
 
864
        PBM_SET(tdbb->getDefaultPool(), &control->vdr_page_bitmap, page_number);
 
865
 
 
866
        return fetch_ok;
 
867
}
 
868
 
 
869
static void garbage_collect(thread_db* tdbb, vdr* control)
 
870
{
 
871
/**************************************
 
872
 *
 
873
 *      g a r b a g e _ c o l l e c t
 
874
 *
 
875
 **************************************
 
876
 *
 
877
 * Functional description
 
878
 *      The database has been walked; compare the page inventory against
 
879
 *      the bitmap of pages visited.
 
880
 *
 
881
 **************************************/
 
882
        SET_TDBB(tdbb);
 
883
 
 
884
        Database* dbb = tdbb->getDatabase();
 
885
 
 
886
        PageManager& pageSpaceMgr = dbb->dbb_page_manager;
 
887
        PageSpace* pageSpace = pageSpaceMgr.findPageSpace(DB_PAGE_SPACE);
 
888
        fb_assert(pageSpace);
 
889
        
 
890
        WIN window(DB_PAGE_SPACE, -1);
 
891
 
 
892
        for (SLONG sequence = 0, number = 0; number < control->vdr_max_page; sequence++) 
 
893
        {
 
894
                const SLONG page_number = (sequence) ? sequence * pageSpaceMgr.pagesPerPIP - 1 : pageSpace->ppFirst;
 
895
                page_inv_page* page = 0;
 
896
                fetch_page(tdbb, 0, page_number, pag_pages, &window, &page);
 
897
                UCHAR* p = page->pip_bits;
 
898
                const UCHAR* const end = p + pageSpaceMgr.bytesBitPIP;
 
899
                while (p < end && number < control->vdr_max_page) {
 
900
                        UCHAR byte = *p++;
 
901
                        for (int i = 8; i; --i, byte >>= 1, number++) {
 
902
                                if (PageBitmap::test(control->vdr_page_bitmap, number)) {
 
903
                                        if (byte & 1) {
 
904
                                                corrupt(tdbb, control, VAL_PAG_IN_USE, 0, number);
 
905
                                                if (control->vdr_flags & vdr_update) {
 
906
                                                        CCH_MARK(tdbb, &window);
 
907
                                                        p[-1] &= ~(1 << (number & 7));
 
908
                                                }
 
909
                                                DEBUG;
 
910
                                        }
 
911
                                }
 
912
 
 
913
                        /* Page is potentially an orphan - but don't declare it as such
 
914
                           unless we think we walked all pages */
 
915
 
 
916
                                else if (!(byte & 1) && (control->vdr_flags & vdr_records)) {
 
917
                                        corrupt(tdbb, control, VAL_PAG_ORPHAN, 0, number);
 
918
                                        if (control->vdr_flags & vdr_update) {
 
919
                                                CCH_MARK(tdbb, &window);
 
920
                                                p[-1] |= 1 << (number & 7);
 
921
                                        }
 
922
                                        DEBUG;
 
923
                                }
 
924
                        }
 
925
                }
 
926
                const UCHAR test_byte = p[-1];
 
927
                CCH_RELEASE(tdbb, &window);
 
928
                if (test_byte & 0x80)
 
929
                        break;
 
930
        }
 
931
 
 
932
#ifdef DEBUG_VAL_VERBOSE
 
933
/* Dump verbose output of all the pages fetched */
 
934
        if (VAL_debug_level >= 2)
 
935
        {
 
936
                //We are assuming RSE_get_forward
 
937
                if (control->vdr_page_bitmap->getFirst())
 
938
                        do {
 
939
                                SLONG dmp_page_number = control->vdr_page_bitmap->current();
 
940
                                DMP_page(dmp_page_number, dbb->dbb_page_size);
 
941
                        } while (control->vdr_page_bitmap->getNext());
 
942
        }
 
943
#endif
 
944
}
 
945
 
 
946
#ifdef DEBUG_VAL_VERBOSE
 
947
static void print_rhd(USHORT length, const rhd* header)
 
948
{
 
949
/**************************************
 
950
 *
 
951
 *      p r i n t _ r h d
 
952
 *
 
953
 **************************************
 
954
 *
 
955
 * Functional description
 
956
 *      Debugging routine to print a
 
957
 *      Record Header Data.
 
958
 *
 
959
 **************************************/
 
960
        if (VAL_debug_level) {
 
961
                fprintf(stdout, "rhd: len %d TX %d format %d ",
 
962
                                   length, header->rhd_transaction, (int) header->rhd_format);
 
963
                fprintf(stdout, "BP %d/%d flags 0x%x ",
 
964
                                   header->rhd_b_page, header->rhd_b_line, header->rhd_flags);
 
965
                if (header->rhd_flags & rhd_incomplete) {
 
966
                        rhdf* fragment = (rhdf*) header;
 
967
                        fprintf(stdout, "FP %d/%d ",
 
968
                                           fragment->rhdf_f_page, fragment->rhdf_f_line);
 
969
                }
 
970
                fprintf(stdout, "%s ",
 
971
                                   (header->rhd_flags & rhd_deleted) ? "DEL" : "   ");
 
972
                fprintf(stdout, "%s ",
 
973
                                   (header->rhd_flags & rhd_chain) ? "CHN" : "   ");
 
974
                fprintf(stdout, "%s ",
 
975
                                   (header->rhd_flags & rhd_fragment) ? "FRG" : "   ");
 
976
                fprintf(stdout, "%s ",
 
977
                                   (header->rhd_flags & rhd_incomplete) ? "INC" : "   ");
 
978
                fprintf(stdout, "%s ",
 
979
                                   (header->rhd_flags & rhd_blob) ? "BLB" : "   ");
 
980
                fprintf(stdout, "%s ",
 
981
                                   (header->rhd_flags & rhd_delta) ? "DLT" : "   ");
 
982
                fprintf(stdout, "%s ",
 
983
                                   (header->rhd_flags & rhd_large) ? "LRG" : "   ");
 
984
                fprintf(stdout, "%s ",
 
985
                                   (header->rhd_flags & rhd_damaged) ? "DAM" : "   ");
 
986
                fprintf(stdout, "\n");
 
987
        }
 
988
}
 
989
#endif
 
990
 
 
991
static RTN walk_blob(thread_db* tdbb,
 
992
                                         vdr* control,
 
993
                                         jrd_rel* relation, blh* header, USHORT length, SLONG number)
 
994
{
 
995
/**************************************
 
996
 *
 
997
 *      w a l k _ b l o b
 
998
 *
 
999
 **************************************
 
1000
 *
 
1001
 * Functional description
 
1002
 *      Walk a blob.
 
1003
 *
 
1004
 **************************************/
 
1005
        SET_TDBB(tdbb);
 
1006
 
 
1007
#ifdef DEBUG_VAL_VERBOSE
 
1008
        if (VAL_debug_level) {
 
1009
                fprintf(stdout,
 
1010
                                   "walk_blob: level %d lead page %d max pages %d max segment %d\n",
 
1011
                                   header->blh_level, header->blh_lead_page,
 
1012
                                   header->blh_max_sequence, header->blh_max_segment);
 
1013
                fprintf(stdout, "           count %d, length %d sub_type %d\n",
 
1014
                                   header->blh_count, header->blh_length,
 
1015
                                   header->blh_sub_type);
 
1016
        }
 
1017
#endif
 
1018
 
 
1019
/* Level 0 blobs have no work to do. */
 
1020
 
 
1021
        if (header->blh_level == 0)
 
1022
                return rtn_ok;
 
1023
 
 
1024
/* Level 1 blobs are a little more complicated */
 
1025
        WIN window1(DB_PAGE_SPACE, -1), window2(DB_PAGE_SPACE, -1);
 
1026
 
 
1027
        const SLONG* pages1 = header->blh_page;
 
1028
        const SLONG* const end1 = pages1 + ((USHORT) (length - BLH_SIZE) >> SHIFTLONG);
 
1029
        SLONG sequence;
 
1030
 
 
1031
        for (sequence = 0; pages1 < end1; pages1++) {
 
1032
                blob_page* page1 = 0;
 
1033
                fetch_page(tdbb, control, *pages1, pag_blob, &window1, &page1);
 
1034
                if (page1->blp_lead_page != header->blh_lead_page)
 
1035
                        corrupt(tdbb, control, VAL_BLOB_INCONSISTENT, relation, number);
 
1036
                if ((header->blh_level == 1 && page1->blp_sequence != sequence)) {
 
1037
                        corrupt(tdbb, control, VAL_BLOB_CORRUPT, relation, number);
 
1038
                        CCH_RELEASE(tdbb, &window1);
 
1039
                        return rtn_corrupt;
 
1040
                }
 
1041
                if (header->blh_level == 1)
 
1042
                        sequence++;
 
1043
                else {
 
1044
                        const SLONG* pages2 = page1->blp_page;
 
1045
                        const SLONG* const end2 = pages2 + (page1->blp_length >> SHIFTLONG);
 
1046
                        for (; pages2 < end2; pages2++, sequence++) {
 
1047
                                blob_page* page2 = 0;
 
1048
                                fetch_page(tdbb, control, *pages2, pag_blob, &window2,
 
1049
                                                   &page2);
 
1050
                                if (page2->blp_lead_page != header->blh_lead_page
 
1051
                                        || page2->blp_sequence != sequence)
 
1052
                                {
 
1053
                                        corrupt(tdbb, control, VAL_BLOB_CORRUPT, relation,
 
1054
                                                        number);
 
1055
                                        CCH_RELEASE(tdbb, &window1);
 
1056
                                        CCH_RELEASE(tdbb, &window2);
 
1057
                                        return rtn_corrupt;
 
1058
                                }
 
1059
                                CCH_RELEASE(tdbb, &window2);
 
1060
                        }
 
1061
                }
 
1062
                CCH_RELEASE(tdbb, &window1);
 
1063
        }
 
1064
 
 
1065
        if (sequence - 1 != header->blh_max_sequence)
 
1066
                return corrupt(tdbb, control, VAL_BLOB_TRUNCATED, relation, number);
 
1067
 
 
1068
        return rtn_ok;
 
1069
}
 
1070
 
 
1071
static RTN walk_chain(thread_db* tdbb,
 
1072
                                          vdr* control,
 
1073
                                          jrd_rel* relation, rhd* header, SLONG head_number)
 
1074
{
 
1075
/**************************************
 
1076
 *
 
1077
 *      w a l k _ c h a i n
 
1078
 *
 
1079
 **************************************
 
1080
 *
 
1081
 * Functional description
 
1082
 *      Make sure chain of record versions is completely intact.
 
1083
 *
 
1084
 **************************************/
 
1085
#ifdef DEBUG_VAL_VERBOSE
 
1086
        USHORT counter = 0;
 
1087
#endif
 
1088
 
 
1089
        SET_TDBB(tdbb);
 
1090
 
 
1091
        SLONG page_number = header->rhd_b_page;
 
1092
        USHORT line_number = header->rhd_b_line;
 
1093
        WIN window(DB_PAGE_SPACE, -1);
 
1094
 
 
1095
        while (page_number) {
 
1096
                const bool delta_flag = (header->rhd_flags & rhd_delta) ? true : false;
 
1097
#ifdef DEBUG_VAL_VERBOSE
 
1098
                if (VAL_debug_level)
 
1099
                        fprintf(stdout, "  BV %02d: ", ++counter);
 
1100
#endif
 
1101
                control->vdr_rel_chain_counter++;
 
1102
                data_page* page = 0;
 
1103
                fetch_page(tdbb, control, page_number, pag_data, &window, &page);
 
1104
                const data_page::dpg_repeat* line = &page->dpg_rpt[line_number];
 
1105
                header = (rhd*) ((UCHAR *) page + line->dpg_offset);
 
1106
                if (page->dpg_count <= line_number ||
 
1107
                        !line->dpg_length ||
 
1108
                        (header->rhd_flags & (rhd_blob | rhd_fragment)) ||
 
1109
                        walk_record(tdbb, control, relation, header, line->dpg_length,
 
1110
                                                head_number, delta_flag) != rtn_ok) 
 
1111
                {
 
1112
                        CCH_RELEASE(tdbb, &window);
 
1113
                        return corrupt(tdbb, control, VAL_REC_CHAIN_BROKEN,
 
1114
                                                   relation, head_number);
 
1115
                }
 
1116
                page_number = header->rhd_b_page;
 
1117
                line_number = header->rhd_b_line;
 
1118
                CCH_RELEASE(tdbb, &window);
 
1119
        }
 
1120
 
 
1121
        return rtn_ok;
 
1122
}
 
1123
 
 
1124
static void walk_database(thread_db* tdbb, vdr* control)
 
1125
{
 
1126
/**************************************
 
1127
 *
 
1128
 *      w a l k _ d a t a b a s e
 
1129
 *
 
1130
 **************************************
 
1131
 *
 
1132
 * Functional description
 
1133
 *
 
1134
 **************************************/
 
1135
        SET_TDBB(tdbb);
 
1136
        Database* dbb = tdbb->getDatabase();
 
1137
 
 
1138
#ifdef DEBUG_VAL_VERBOSE
 
1139
        if (VAL_debug_level) {
 
1140
                fprintf(stdout,
 
1141
                                   "walk_database: %s\nODS: %d.%d  (creation ods %d)\nPage size %d\n",
 
1142
                                   dbb->dbb_filename.c_str(), dbb->dbb_ods_version,
 
1143
                                   dbb->dbb_minor_version, dbb->dbb_minor_original,
 
1144
                                   dbb->dbb_page_size);
 
1145
        }
 
1146
#endif
 
1147
 
 
1148
        DPM_scan_pages(tdbb);
 
1149
        WIN window(DB_PAGE_SPACE, -1);
 
1150
        header_page* page = 0;
 
1151
        fetch_page(tdbb, control, (SLONG) HEADER_PAGE, pag_header, &window,
 
1152
                           &page);
 
1153
        control->vdr_max_transaction = page->hdr_next_transaction;
 
1154
 
 
1155
        walk_header(tdbb, control, page->hdr_next_page);
 
1156
        walk_log(tdbb, control);
 
1157
        walk_pip(tdbb, control);
 
1158
        walk_tip(tdbb, control, page->hdr_next_transaction);
 
1159
        walk_generators(tdbb, control);
 
1160
 
 
1161
        vec<jrd_rel*>* vector;
 
1162
        for (USHORT i = 0; (vector = dbb->dbb_relations) && i < vector->count(); i++)
 
1163
        {
 
1164
#ifdef DEBUG_VAL_VERBOSE
 
1165
                if (i >= 32 /* rel_MAX */ ) // Why not system flag instead?
 
1166
                        VAL_debug_level = 2;
 
1167
#endif
 
1168
                jrd_rel* relation = (*vector)[i];
 
1169
                if (relation)
 
1170
                        walk_relation(tdbb, control, relation);
 
1171
        }
 
1172
 
 
1173
        CCH_RELEASE(tdbb, &window);
 
1174
}
 
1175
 
 
1176
static RTN walk_data_page(thread_db* tdbb,
 
1177
                                                  vdr* control,
 
1178
                                                  jrd_rel* relation, SLONG page_number, SLONG sequence)
 
1179
{
 
1180
/**************************************
 
1181
 *
 
1182
 *      w a l k _ d a t a _ p a g e
 
1183
 *
 
1184
 **************************************
 
1185
 *
 
1186
 * Functional description
 
1187
 *      Walk a single data page.
 
1188
 *
 
1189
 **************************************/
 
1190
        SET_TDBB(tdbb);
 
1191
        Database* dbb = tdbb->getDatabase();
 
1192
 
 
1193
        WIN window(DB_PAGE_SPACE, -1);
 
1194
        data_page* page = 0;
 
1195
        fetch_page(tdbb, control, page_number, pag_data, &window, &page);
 
1196
 
 
1197
#ifdef DEBUG_VAL_VERBOSE
 
1198
        if (VAL_debug_level) {
 
1199
                fprintf(stdout,
 
1200
                                   "walk_data_page: page %d rel %d seq %d count %d\n",
 
1201
                                   page_number, page->dpg_relation, page->dpg_sequence,
 
1202
                                   page->dpg_count);
 
1203
        }
 
1204
#endif
 
1205
 
 
1206
        if (page->dpg_relation != relation->rel_id
 
1207
                || page->dpg_sequence != sequence)
 
1208
        {
 
1209
                ++control->vdr_errors;
 
1210
                CCH_RELEASE(tdbb, &window);
 
1211
                return corrupt(tdbb, control, VAL_DATA_PAGE_CONFUSED,
 
1212
                                           relation, page_number, sequence);
 
1213
        }
 
1214
 
 
1215
/* Walk records */
 
1216
 
 
1217
        const UCHAR* const end_page = (UCHAR *) page + dbb->dbb_page_size;
 
1218
        const data_page::dpg_repeat* const end = page->dpg_rpt + page->dpg_count;
 
1219
        SLONG number = sequence * dbb->dbb_max_records;
 
1220
 
 
1221
        for (const data_page::dpg_repeat* line = page->dpg_rpt; line < end;
 
1222
                line++, number++)
 
1223
        {
 
1224
#ifdef DEBUG_VAL_VERBOSE
 
1225
                if (VAL_debug_level) {
 
1226
                        fprintf(stdout, "Slot %02d (%d,%d): ",
 
1227
                                           line - page->dpg_rpt,
 
1228
                                           line->dpg_offset, line->dpg_length);
 
1229
                }
 
1230
#endif
 
1231
                if (line->dpg_length) {
 
1232
                        rhd* header = (rhd*) ((UCHAR *) page + line->dpg_offset);
 
1233
                        if ((UCHAR *) header < (UCHAR *) end ||
 
1234
                                (UCHAR *) header + line->dpg_length > end_page)
 
1235
                        {
 
1236
                                return corrupt(tdbb, control, VAL_DATA_PAGE_LINE_ERR,
 
1237
                                                           relation, page_number, sequence,
 
1238
                                                           (SLONG) (line - page->dpg_rpt));
 
1239
                        }
 
1240
                        if (header->rhd_flags & rhd_chain)
 
1241
                                control->vdr_rel_backversion_counter++;
 
1242
 
 
1243
                        /* Record the existance of a primary version of a record */
 
1244
 
 
1245
                        if ((control->vdr_flags & vdr_records) &&
 
1246
                                !(header->rhd_flags & (rhd_chain | rhd_fragment | rhd_blob)))
 
1247
                        {
 
1248
                                /* Only set committed (or limbo) records in the bitmap. If there
 
1249
                                   is a backversion then at least one of the record versions is
 
1250
                                   committed. If there's no backversion then check transaction
 
1251
                                   state of the lone primary record version. */
 
1252
 
 
1253
                                if (header->rhd_b_page)
 
1254
                                        RBM_SET(tdbb->getDefaultPool(), &control->vdr_rel_records, number);
 
1255
                                else {
 
1256
                                        int state;
 
1257
                                        if (header->rhd_transaction < dbb->dbb_oldest_transaction)
 
1258
                                                state = tra_committed;
 
1259
                                        else
 
1260
                                                state =
 
1261
                                                        TRA_fetch_state(tdbb, header->rhd_transaction);
 
1262
                                        if (state == tra_committed || state == tra_limbo)
 
1263
                                                RBM_SET(tdbb->getDefaultPool(), &control->vdr_rel_records, number);
 
1264
                                }
 
1265
                        }
 
1266
 
 
1267
#ifdef DEBUG_VAL_VERBOSE
 
1268
                        if (VAL_debug_level) {
 
1269
                                if (header->rhd_flags & rhd_chain)
 
1270
                                        fprintf(stdout, "(backvers)");
 
1271
                                if (header->rhd_flags & rhd_fragment)
 
1272
                                        fprintf(stdout, "(fragment)");
 
1273
                                if (header->rhd_flags & (rhd_fragment | rhd_chain))
 
1274
                                        print_rhd(line->dpg_length, header);
 
1275
                        }
 
1276
#endif
 
1277
                        if (!(header->rhd_flags & rhd_chain) &&
 
1278
                                ((header->rhd_flags & rhd_large) ||
 
1279
                                 (control->vdr_flags & vdr_records)))
 
1280
                        {
 
1281
                                const RTN result = (header->rhd_flags & rhd_blob) ?
 
1282
                                        walk_blob(tdbb, control, relation, (blh*) header,
 
1283
                                                          line->dpg_length, number) :
 
1284
                                        walk_record(tdbb, control, relation, header,
 
1285
                                                                line->dpg_length, number, false);
 
1286
                                if ((result == rtn_corrupt)
 
1287
                                        && (control->vdr_flags & vdr_repair))
 
1288
                                {
 
1289
                                        CCH_MARK(tdbb, &window);
 
1290
                                        header->rhd_flags |= rhd_damaged;
 
1291
                                }
 
1292
                        }
 
1293
                }
 
1294
#ifdef DEBUG_VAL_VERBOSE
 
1295
                else if (VAL_debug_level)
 
1296
                        fprintf(stdout, "(empty)\n");
 
1297
#endif
 
1298
        }
 
1299
 
 
1300
        CCH_RELEASE(tdbb, &window);
 
1301
 
 
1302
#ifdef DEBUG_VAL_VERBOSE
 
1303
        if (VAL_debug_level)
 
1304
                fprintf(stdout, "------------------------------------\n");
 
1305
#endif
 
1306
 
 
1307
        return rtn_ok;
 
1308
}
 
1309
 
 
1310
static void walk_generators(thread_db* tdbb, vdr* control)
 
1311
{
 
1312
/**************************************
 
1313
 *
 
1314
 *      w a l k _ g e n e r a t o r s
 
1315
 *
 
1316
 **************************************
 
1317
 *
 
1318
 * Functional description
 
1319
 *      Walk the page inventory pages.
 
1320
 *
 
1321
 **************************************/
 
1322
        SET_TDBB(tdbb);
 
1323
        Database* dbb = tdbb->getDatabase();
 
1324
        CHECK_DBB(dbb);
 
1325
        WIN window(DB_PAGE_SPACE, -1);
 
1326
 
 
1327
        vcl* vector = dbb->dbb_gen_id_pages;
 
1328
        if (vector) {
 
1329
        vcl::iterator ptr, end;
 
1330
                for (ptr = vector->begin(), end = vector->end(); ptr < end; ++ptr) {
 
1331
                        if (*ptr) {
 
1332
#ifdef DEBUG_VAL_VERBOSE
 
1333
                                if (VAL_debug_level)
 
1334
                                        fprintf(stdout, "walk_generator: page %d\n", *ptr);
 
1335
#endif
 
1336
                                pointer_page* page = 0;
 
1337
                                fetch_page(tdbb, control, *ptr, pag_ids, &window, &page);
 
1338
                                CCH_RELEASE(tdbb, &window);
 
1339
                        }
 
1340
                }
 
1341
        }
 
1342
}
 
1343
 
 
1344
static void walk_header(thread_db* tdbb, vdr* control, SLONG page_num)
 
1345
{
 
1346
/**************************************
 
1347
 *
 
1348
 *      w a l k _ h e a d e r
 
1349
 *
 
1350
 **************************************
 
1351
 *
 
1352
 * Functional description
 
1353
 *      Walk the overflow header pages
 
1354
 *
 
1355
 **************************************/
 
1356
        SET_TDBB(tdbb);
 
1357
 
 
1358
        while (page_num) {
 
1359
#ifdef DEBUG_VAL_VERBOSE
 
1360
                if (VAL_debug_level)
 
1361
                        fprintf(stdout, "walk_header: page %d\n", page_num);
 
1362
#endif
 
1363
                WIN window(DB_PAGE_SPACE, -1);
 
1364
                header_page* page = 0;
 
1365
                fetch_page(tdbb, control, page_num, pag_header, &window, &page);
 
1366
                page_num = page->hdr_next_page;
 
1367
                CCH_RELEASE(tdbb, &window);
 
1368
        }
 
1369
}
 
1370
 
 
1371
static RTN walk_index(thread_db* tdbb, vdr* control, jrd_rel* relation, 
 
1372
                                          index_root_page& root_page, USHORT id)
 
1373
{
 
1374
/**************************************
 
1375
 *
 
1376
 *      w a l k _ i n d e x
 
1377
 *
 
1378
 **************************************
 
1379
 *
 
1380
 * Functional description
 
1381
 *      Walk all btree pages left-to-right and top-down.
 
1382
 *      Check all the pointers and keys for consistency 
 
1383
 *      relative to each other, and check sibling pointers. 
 
1384
 *
 
1385
 *      NOTE: id is the internal index id, relative for each 
 
1386
 *      relation.  It is 1 less than the user level index id.
 
1387
 *      So errors are reported against index id+1
 
1388
 *
 
1389
 **************************************/
 
1390
        const UCHAR* p;
 
1391
        const UCHAR* q;
 
1392
        USHORT l; // temporary variable for length
 
1393
 
 
1394
        SET_TDBB(tdbb);
 
1395
        Database* dbb = tdbb->getDatabase();
 
1396
        CHECK_DBB(dbb);
 
1397
 
 
1398
        const SLONG page_number = root_page.irt_rpt[id].irt_root;
 
1399
        if (!page_number) {
 
1400
                return rtn_ok;
 
1401
        }
 
1402
 
 
1403
        const bool unique = (root_page.irt_rpt[id].irt_flags & (irt_unique | idx_primary));
 
1404
 
 
1405
        temporary_key nullKey, *null_key = 0;
 
1406
        if (unique && tdbb->getDatabase()->dbb_ods_version >= ODS_VERSION11)
 
1407
        {
 
1408
                const bool isExpression = root_page.irt_rpt[id].irt_flags & irt_expression;
 
1409
                if (isExpression)
 
1410
                        root_page.irt_rpt[id].irt_flags &= ~irt_expression;
 
1411
 
 
1412
                index_desc idx;
 
1413
                BTR_description(tdbb, relation, &root_page, &idx, id);
 
1414
                if (isExpression)
 
1415
                        root_page.irt_rpt[id].irt_flags |= irt_expression;
 
1416
 
 
1417
                null_key = &nullKey;
 
1418
                BTR_make_null_key(tdbb, &idx, null_key);
 
1419
        }
 
1420
 
 
1421
        SLONG next = page_number;
 
1422
        SLONG down = page_number;
 
1423
        temporary_key key;
 
1424
        key.key_length = 0;
 
1425
        SLONG previous_number = 0;
 
1426
 
 
1427
        if (control) {
 
1428
                RecordBitmap::reset(control->vdr_idx_records);
 
1429
        }
 
1430
 
 
1431
        bool firstNode = true;
 
1432
        bool nullKeyNode = false;                       // current node is a null key of unique index
 
1433
        bool nullKeyHandled = !(unique && null_key);    // null key of unique index was handled
 
1434
 
 
1435
        UCHAR flags = 0;
 
1436
        UCHAR* pointer;
 
1437
        IndexNode node, lastNode;
 
1438
        PageBitmap visited_pages; // used to check circular page references, Diane Downie 2007-02-09
 
1439
 
 
1440
        while (next)
 
1441
        {
 
1442
                WIN window(DB_PAGE_SPACE, -1);
 
1443
                btree_page* page = 0;
 
1444
                fetch_page(tdbb, control, next, pag_index, &window, &page);
 
1445
                
 
1446
                // remember each page for circular reference detection 
 
1447
                visited_pages.set(next); 
 
1448
                
 
1449
                if ((next != page_number) &&
 
1450
                        (page->btr_header.pag_flags & BTR_FLAG_COPY_MASK) !=
 
1451
                        (flags & BTR_FLAG_COPY_MASK))
 
1452
                {
 
1453
                        corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation,
 
1454
                                id + 1, next, page->btr_level, __FILE__, __LINE__);
 
1455
                }
 
1456
                flags = page->btr_header.pag_flags;
 
1457
                const bool leafPage = (page->btr_level == 0);
 
1458
                const bool useJumpInfo = (flags & btr_jump_info);
 
1459
                const bool useAllRecordNumbers = (flags & btr_all_record_number);
 
1460
 
 
1461
                if (!useAllRecordNumbers)
 
1462
                        nullKeyHandled = true;
 
1463
                
 
1464
                if (page->btr_relation != relation->rel_id || 
 
1465
                        page->btr_id != (UCHAR) (id % 256)) 
 
1466
                {
 
1467
                        corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, id + 1,
 
1468
                                        next, page->btr_level, __FILE__, __LINE__);
 
1469
                        CCH_RELEASE(tdbb, &window);
 
1470
                        return rtn_corrupt;
 
1471
                }
 
1472
                
 
1473
                if (useJumpInfo) 
 
1474
                {
 
1475
                        IndexJumpInfo jumpInfo;
 
1476
                        pointer = BTreeNode::getPointerFirstNode(page, &jumpInfo);
 
1477
                        const USHORT headerSize = (pointer - (UCHAR*)page);
 
1478
                        // Check if firstNodeOffset is not out of page area.
 
1479
                        if ((jumpInfo.firstNodeOffset < headerSize) ||
 
1480
                                (jumpInfo.firstNodeOffset > page->btr_length)) 
 
1481
                        {
 
1482
                                corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation,
 
1483
                                        id + 1, next, page->btr_level, __FILE__, __LINE__);
 
1484
                        }
 
1485
 
 
1486
                        USHORT n = jumpInfo.jumpers;
 
1487
                        USHORT jumpersSize = 0;
 
1488
                        IndexNode checknode;
 
1489
                        IndexJumpNode jumpNode;
 
1490
                        while (n) {
 
1491
                                pointer = BTreeNode::readJumpNode(&jumpNode, pointer, flags);
 
1492
                                jumpersSize += BTreeNode::getJumpNodeSize(&jumpNode, flags);
 
1493
                                // Check if jump node offset is inside page.
 
1494
                                if ((jumpNode.offset < jumpInfo.firstNodeOffset) ||
 
1495
                                        (jumpNode.offset > page->btr_length))
 
1496
                                {
 
1497
                                        corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation,
 
1498
                                                id + 1, next, page->btr_level, __FILE__, __LINE__);
 
1499
                                }
 
1500
                                else {
 
1501
                                        // Check if jump node has same length as data node prefix.
 
1502
                                        BTreeNode::readNode(&checknode, 
 
1503
                                                (UCHAR*)page + jumpNode.offset, flags, leafPage);
 
1504
                                        if ((jumpNode.prefix + jumpNode.length) != checknode.prefix) {
 
1505
                                                corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation,
 
1506
                                                        id + 1, next, page->btr_level, __FILE__, __LINE__);
 
1507
                                        }
 
1508
                                }
 
1509
                                n--;
 
1510
                        }
 
1511
                }
 
1512
 
 
1513
                // go through all the nodes on the page and check for validity
 
1514
                pointer = BTreeNode::getPointerFirstNode(page);
 
1515
                if (useAllRecordNumbers && firstNode) {
 
1516
                        BTreeNode::readNode(&lastNode, pointer, flags, leafPage);
 
1517
                }
 
1518
 
 
1519
                const UCHAR* const endPointer = ((UCHAR *) page + page->btr_length);
 
1520
                while (pointer < endPointer) {
 
1521
 
 
1522
                        pointer = BTreeNode::readNode(&node, pointer, flags, leafPage);
 
1523
                        if (pointer > endPointer) {
 
1524
                                break;
 
1525
                        }
 
1526
 
 
1527
                        // make sure the current key is not less than the previous key
 
1528
                        bool duplicateNode = !firstNode && !node.isEndLevel && 
 
1529
                                        (key.key_length == (node.length + node.prefix));
 
1530
                        q = node.data;
 
1531
                        p = key.key_data + node.prefix;
 
1532
                        l = MIN(node.length, (USHORT) (key.key_length - node.prefix));
 
1533
                        for (; l; l--, p++, q++) {
 
1534
                                if (*p > *q) {
 
1535
                                        duplicateNode = false;
 
1536
                                        corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation,
 
1537
                                                        id + 1, next, page->btr_level, __FILE__, __LINE__);
 
1538
                                }
 
1539
                                else if (*p < *q) {
 
1540
                                        duplicateNode = false;
 
1541
                                        break;
 
1542
                                }
 
1543
                        }
 
1544
 
 
1545
                        if (!duplicateNode && nullKeyNode) {
 
1546
                                nullKeyHandled = true;
 
1547
                                nullKeyNode = false;
 
1548
                        }
 
1549
 
 
1550
                        if (useAllRecordNumbers && (node.recordNumber.getValue() >= 0) &&
 
1551
                                !firstNode && !node.isEndLevel)
 
1552
                        {
 
1553
                                // If this node is equal to the previous one and it's
 
1554
                                // not a MARKER, record number should be same or higher.
 
1555
                                if (duplicateNode) {
 
1556
                                        if ((!unique || (unique && nullKeyNode)) &&
 
1557
                                                (node.recordNumber < lastNode.recordNumber)) 
 
1558
                                        {
 
1559
                                                corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation,
 
1560
                                                        id + 1, next, page->btr_level, __FILE__, __LINE__);
 
1561
                                        }
 
1562
                                }
 
1563
 
 
1564
                                lastNode = node;
 
1565
                        }
 
1566
 
 
1567
                        // save the current key
 
1568
                        memcpy(key.key_data + node.prefix, node.data, node.length);
 
1569
                        //key.key_length = key.key_data + node.prefix + node.length - key.key_data;
 
1570
                        key.key_length = node.prefix + node.length;
 
1571
 
 
1572
                        if (!nullKeyHandled && !nullKeyNode && !duplicateNode)
 
1573
                        {
 
1574
                                nullKeyNode = (leafPage || (!leafPage && !firstNode) ) &&
 
1575
                                        !node.isEndLevel && (null_key->key_length == key.key_length) && 
 
1576
                                        (memcmp(null_key->key_data, key.key_data, null_key->key_length) == 0);
 
1577
                        }
 
1578
 
 
1579
                        if (firstNode) {
 
1580
                                firstNode = false;
 
1581
                        }
 
1582
                        
 
1583
                        if (node.isEndBucket || node.isEndLevel) {
 
1584
                                break;
 
1585
                        }
 
1586
 
 
1587
                        // Record the existance of a primary version of a record
 
1588
                        if (leafPage && control && (control->vdr_flags & vdr_records)) {
 
1589
                          RBM_SET(tdbb->getDefaultPool(), &control->vdr_idx_records, node.recordNumber.getValue());
 
1590
                        }
 
1591
 
 
1592
                        // fetch the next page down (if full validation was specified)
 
1593
                        if (!leafPage && control && (control->vdr_flags & vdr_records)) 
 
1594
                        {
 
1595
                                const SLONG down_number = node.pageNumber;
 
1596
                                const RecordNumber down_record_number = node.recordNumber;
 
1597
 
 
1598
                                // Note: control == 0 for the fetch_page() call here 
 
1599
                                // as we don't want to mark the page as visited yet - we'll 
 
1600
                                // mark it when we visit it for real later on
 
1601
                                WIN down_window(DB_PAGE_SPACE, -1);
 
1602
                                btree_page* down_page = 0;
 
1603
                                fetch_page(tdbb, 0, down_number, pag_index, &down_window,
 
1604
                                        &down_page);
 
1605
                                const bool downLeafPage = (down_page->btr_level == 0);
 
1606
 
 
1607
                                // make sure the initial key is greater than the pointer key
 
1608
                                UCHAR* downPointer = BTreeNode::getPointerFirstNode(down_page);
 
1609
 
 
1610
                                IndexNode downNode;
 
1611
                                downPointer = BTreeNode::readNode(&downNode, downPointer, flags, downLeafPage);
 
1612
 
 
1613
                                p = downNode.data;
 
1614
                                q = key.key_data;
 
1615
                                l = MIN(key.key_length, downNode.length);
 
1616
                                for (; l; l--, p++, q++) {
 
1617
                                        if (*p < *q) {
 
1618
                                                corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, 
 
1619
                                                        id + 1, next, page->btr_level, __FILE__, __LINE__);
 
1620
                                        }
 
1621
                                        else if (*p > *q) {
 
1622
                                                break;
 
1623
                                        }
 
1624
                                }
 
1625
 
 
1626
                                // Only check record-number if this isn't the first page in 
 
1627
                                // the level and it isn't a MARKER.
 
1628
                                // Also don't check on primary/unique keys, because duplicates aren't
 
1629
                                // sorted on recordnumber, except for NULL keys.
 
1630
                                if (useAllRecordNumbers && down_page->btr_left_sibling &&
 
1631
                                        !(downNode.isEndBucket || downNode.isEndLevel) &&
 
1632
                                        (!unique || (unique && nullKeyNode)) ) 
 
1633
                                {
 
1634
                                        // Check record number if key is equal with node on
 
1635
                                        // pointer page. In that case record number on page 
 
1636
                                        // down should be same or larger.
 
1637
                                        if ((l == 0) && (key.key_length == downNode.length) &&
 
1638
                                                (downNode.recordNumber < down_record_number))
 
1639
                                        {
 
1640
                                                corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, 
 
1641
                                                        id + 1, next, page->btr_level, __FILE__, __LINE__);
 
1642
                                        }
 
1643
                                }
 
1644
 
 
1645
                                // check the left and right sibling pointers against the parent pointers
 
1646
                                if (previous_number != down_page->btr_left_sibling) {
 
1647
                                        corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation,
 
1648
                                                        id + 1, next, page->btr_level, __FILE__, __LINE__);
 
1649
                                }
 
1650
 
 
1651
                                BTreeNode::readNode(&downNode, pointer, flags, leafPage);
 
1652
                                const SLONG next_number = downNode.pageNumber;
 
1653
 
 
1654
                                if (!(downNode.isEndBucket || downNode.isEndLevel) && 
 
1655
                                        (next_number != down_page->btr_sibling)) 
 
1656
                                {
 
1657
                                        corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation,
 
1658
                                                        id + 1, next, page->btr_level, __FILE__, __LINE__);
 
1659
                                }
 
1660
 
 
1661
                                if (downNode.isEndLevel && down_page->btr_sibling) {
 
1662
                                        corrupt(tdbb, control, VAL_INDEX_ORPHAN_CHILD, relation,
 
1663
                                                        id + 1, next);
 
1664
                                }
 
1665
                                previous_number = down_number;
 
1666
 
 
1667
                                CCH_RELEASE(tdbb, &down_window);
 
1668
                        }
 
1669
                }
 
1670
 
 
1671
                if (pointer != endPointer || page->btr_length > dbb->dbb_page_size) {
 
1672
                        corrupt(tdbb, control, VAL_INDEX_PAGE_CORRUPT, relation, id + 1,
 
1673
                                        next, page->btr_level, __FILE__, __LINE__);
 
1674
                }
 
1675
 
 
1676
                if (next == down) {
 
1677
                        if (page->btr_level) {
 
1678
                                IndexNode newPageNode;
 
1679
                                BTreeNode::readNode(&newPageNode, 
 
1680
                                        BTreeNode::getPointerFirstNode(page), flags, false);
 
1681
                                down = newPageNode.pageNumber;
 
1682
                        } 
 
1683
                        else {
 
1684
                                down = 0;
 
1685
                        }
 
1686
                }
 
1687
 
 
1688
                if (!(next = page->btr_sibling)) {
 
1689
                        next = down;
 
1690
                        key.key_length = 0;
 
1691
                        previous_number = 0;
 
1692
                        firstNode = true;
 
1693
                        nullKeyNode = false;
 
1694
                        nullKeyHandled = !(unique && null_key);
 
1695
                }
 
1696
 
 
1697
                // check for circular referenes
 
1698
                if (next && visited_pages.test(next)) 
 
1699
                {
 
1700
                        corrupt(tdbb, control, VAL_INDEX_CYCLE, relation,
 
1701
                                        id + 1, next);
 
1702
                        next = 0;
 
1703
                }
 
1704
                CCH_RELEASE(tdbb, &window);
 
1705
        }
 
1706
 
 
1707
        // If the index & relation contain different sets of records we
 
1708
        // have a corrupt index
 
1709
        if (control && (control->vdr_flags & vdr_records)) {
 
1710
                THREAD_EXIT();
 
1711
                RecordBitmap::Accessor accessor(control->vdr_rel_records);
 
1712
                if (accessor.getFirst()) 
 
1713
                        do {
 
1714
                                SINT64 next_number = accessor.current();
 
1715
                                if (!RecordBitmap::test(control->vdr_idx_records, next_number)) {
 
1716
                                        THREAD_ENTER();
 
1717
                                        return corrupt(tdbb, control, VAL_INDEX_MISSING_ROWS,
 
1718
                                                                   relation, id + 1);
 
1719
                                }
 
1720
                        } while (accessor.getNext());
 
1721
                THREAD_ENTER();
 
1722
        }
 
1723
 
 
1724
        return rtn_ok;
 
1725
}
 
1726
 
 
1727
static void walk_log(thread_db* tdbb, vdr* control)
 
1728
{
 
1729
/**************************************
 
1730
 *
 
1731
 *      w a l k _ l o g
 
1732
 *
 
1733
 **************************************
 
1734
 *
 
1735
 * Functional description
 
1736
 *      Walk the log and overflow pages
 
1737
 *
 
1738
 **************************************/
 
1739
        log_info_page* page = 0;
 
1740
        SLONG page_num = LOG_PAGE;
 
1741
 
 
1742
        SET_TDBB(tdbb);
 
1743
 
 
1744
        while (page_num) {
 
1745
                WIN window(DB_PAGE_SPACE, -1);
 
1746
                fetch_page(tdbb, control, page_num, pag_log, &window, &page);
 
1747
                page_num = page->log_next_page;
 
1748
                CCH_RELEASE(tdbb, &window);
 
1749
        }
 
1750
}
 
1751
 
 
1752
static void walk_pip(thread_db* tdbb, vdr* control)
 
1753
{
 
1754
/**************************************
 
1755
 *
 
1756
 *      w a l k _ p i p
 
1757
 *
 
1758
 **************************************
 
1759
 *
 
1760
 * Functional description
 
1761
 *      Walk the page inventory pages.
 
1762
 *
 
1763
 **************************************/
 
1764
        SET_TDBB(tdbb);
 
1765
        Database* dbb = tdbb->getDatabase();
 
1766
        CHECK_DBB(dbb);
 
1767
 
 
1768
        PageManager& pageSpaceMgr = dbb->dbb_page_manager;
 
1769
        const PageSpace* pageSpace = pageSpaceMgr.findPageSpace(DB_PAGE_SPACE);
 
1770
        fb_assert(pageSpace);
 
1771
 
 
1772
        page_inv_page* page = 0;
 
1773
 
 
1774
        for (USHORT sequence = 0;; sequence++) {
 
1775
                const SLONG page_number =
 
1776
                        (sequence) ? sequence * pageSpaceMgr.pagesPerPIP - 1 : pageSpace->ppFirst;
 
1777
#ifdef DEBUG_VAL_VERBOSE
 
1778
                if (VAL_debug_level)
 
1779
                        fprintf(stdout, "walk_pip: page %d\n", page_number);
 
1780
#endif
 
1781
                WIN window(DB_PAGE_SPACE, -1);
 
1782
                fetch_page(tdbb, control, page_number, pag_pages, &window, &page);
 
1783
                const UCHAR byte = page->pip_bits[pageSpaceMgr.bytesBitPIP - 1];
 
1784
                CCH_RELEASE(tdbb, &window);
 
1785
                if (byte & 0x80)
 
1786
                        break;
 
1787
        }
 
1788
}
 
1789
 
 
1790
static RTN walk_pointer_page(   thread_db*      tdbb,
 
1791
                                                                vdr*            control,
 
1792
                                                                jrd_rel*                relation,
 
1793
                                                                int             sequence)
 
1794
{
 
1795
/**************************************
 
1796
 *
 
1797
 *      w a l k _ p o i n t e r _ p a g e
 
1798
 *
 
1799
 **************************************
 
1800
 *
 
1801
 * Functional description
 
1802
 *      Walk a pointer page for a relation.  Return TRUE if there are more
 
1803
 *      to go.
 
1804
 *
 
1805
 **************************************/
 
1806
        SET_TDBB(tdbb);
 
1807
 
 
1808
        Database* dbb = tdbb->getDatabase();
 
1809
 
 
1810
        const vcl* vector = relation->getBasePages()->rel_pages;
 
1811
 
 
1812
        if (!vector || sequence >= static_cast<int>(vector->count())) {
 
1813
                return corrupt(tdbb, control, VAL_P_PAGE_LOST, relation, sequence);
 
1814
        }
 
1815
 
 
1816
        pointer_page* page = 0;
 
1817
        WIN window(DB_PAGE_SPACE, -1);
 
1818
        fetch_page(     tdbb,
 
1819
                                control,
 
1820
                                (*vector)[sequence],
 
1821
                                pag_pointer,
 
1822
                                &window,
 
1823
                                &page);
 
1824
 
 
1825
#ifdef DEBUG_VAL_VERBOSE
 
1826
        if (VAL_debug_level)
 
1827
                fprintf(stdout,
 
1828
                                   "walk_pointer_page: page %d relation %d sequence %d\n",
 
1829
                                   (*vector)[sequence], relation->rel_id, sequence);
 
1830
#endif
 
1831
 
 
1832
/* Give the page a quick once over */
 
1833
 
 
1834
        if (page->ppg_relation != relation->rel_id ||
 
1835
                page->ppg_sequence != sequence)
 
1836
        {
 
1837
                        return corrupt(tdbb, control, VAL_P_PAGE_INCONSISTENT, relation,
 
1838
                                                   sequence);
 
1839
        }
 
1840
 
 
1841
/* Walk the data pages (someday we may optionally walk pages with "large objects" */
 
1842
 
 
1843
        SLONG seq = (SLONG) sequence *dbb->dbb_dp_per_pp;
 
1844
 
 
1845
        USHORT slot = 0;
 
1846
        for (SLONG* pages = page->ppg_page; slot < page->ppg_count;
 
1847
                 slot++, pages++, seq++)
 
1848
        {
 
1849
                if (*pages) {
 
1850
                        const RTN result = walk_data_page(tdbb, control, relation, *pages, seq);
 
1851
                        if (result != rtn_ok && (control->vdr_flags & vdr_repair)) {
 
1852
                                CCH_MARK(tdbb, &window);
 
1853
                                *pages = 0;
 
1854
                        }
 
1855
                }
 
1856
        }
 
1857
 
 
1858
/* If this is the last pointer page in the relation, we're done */
 
1859
 
 
1860
        if (page->ppg_header.pag_flags & ppg_eof) {
 
1861
                CCH_RELEASE(tdbb, &window);
 
1862
                return rtn_eof;
 
1863
        }
 
1864
 
 
1865
/* Make sure the "next" pointer agrees with the pages relation */
 
1866
 
 
1867
        if (++sequence >= static_cast<int>(vector->count()) ||
 
1868
                (page->ppg_next && page->ppg_next != (*vector)[sequence]))
 
1869
        {
 
1870
                CCH_RELEASE(tdbb, &window);
 
1871
                return corrupt( tdbb,
 
1872
                                                control,
 
1873
                                                VAL_P_PAGE_INCONSISTENT,
 
1874
                                                relation,
 
1875
                                                sequence);
 
1876
        }
 
1877
 
 
1878
        CCH_RELEASE(tdbb, &window);
 
1879
        return rtn_ok;
 
1880
}
 
1881
 
 
1882
 
 
1883
static RTN walk_record(thread_db* tdbb,
 
1884
                                           vdr* control,
 
1885
                                           jrd_rel* relation,
 
1886
                                           rhd* header,
 
1887
                                           USHORT length, SLONG number, bool delta_flag)
 
1888
{
 
1889
/**************************************
 
1890
 *
 
1891
 *      w a l k _ r e c o r d
 
1892
 *
 
1893
 **************************************
 
1894
 *
 
1895
 * Functional description
 
1896
 *      Walk a record.
 
1897
 *
 
1898
 **************************************/
 
1899
        SET_TDBB(tdbb);
 
1900
 
 
1901
#ifdef DEBUG_VAL_VERBOSE
 
1902
        if (VAL_debug_level) {
 
1903
                fprintf(stdout, "record: number %ld (%d/%d) ",
 
1904
                                   number,
 
1905
                                   (USHORT) number / tdbb->getDatabase()->dbb_max_records,
 
1906
                                   (USHORT) number % tdbb->getDatabase()->dbb_max_records);
 
1907
                print_rhd(length, header);
 
1908
        }
 
1909
#endif
 
1910
 
 
1911
        if (header->rhd_flags & rhd_damaged) {
 
1912
                corrupt(tdbb, control, VAL_REC_DAMAGED, relation, number);
 
1913
                return rtn_ok;
 
1914
        }
 
1915
 
 
1916
        if (control && header->rhd_transaction > control->vdr_max_transaction)
 
1917
        {
 
1918
                corrupt(tdbb, control, VAL_REC_BAD_TID, relation, number,
 
1919
                                header->rhd_transaction);
 
1920
        }
 
1921
 
 
1922
/* If there's a back pointer, verify that it's good */
 
1923
 
 
1924
        if (header->rhd_b_page && !(header->rhd_flags & rhd_chain)) {
 
1925
                const RTN result = walk_chain(tdbb, control, relation, header, number);
 
1926
                if (result != rtn_ok)
 
1927
                        return result;
 
1928
        }
 
1929
 
 
1930
/* If the record is a fragment, not large, or we're not interested in
 
1931
   chasing records, skip the record */
 
1932
 
 
1933
        if (header->rhd_flags & (rhd_fragment | rhd_deleted) ||
 
1934
                !((header->rhd_flags & rhd_large) ||
 
1935
                  (control && (control->vdr_flags & vdr_records))))
 
1936
        {
 
1937
                return rtn_ok;
 
1938
        }
 
1939
 
 
1940
/* Pick up what length there is on the fragment */
 
1941
 
 
1942
        const rhdf* fragment = (rhdf*) header;
 
1943
 
 
1944
        const char* p;
 
1945
        const char* end;
 
1946
        if (header->rhd_flags & rhd_incomplete) {
 
1947
                p = (SCHAR *) fragment->rhdf_data;
 
1948
                end = p + length - OFFSETA(rhdf*, rhdf_data);
 
1949
        }
 
1950
        else {
 
1951
                p = (SCHAR *) header->rhd_data;
 
1952
                end = p + length - OFFSETA(rhd*, rhd_data);
 
1953
        }
 
1954
 
 
1955
        USHORT record_length = 0;
 
1956
 
 
1957
        while (p < end) {
 
1958
                const char c = *p++;
 
1959
                if (c >= 0) {
 
1960
                        record_length += c;
 
1961
                        p += c;
 
1962
                }
 
1963
                else {
 
1964
                        record_length -= c;
 
1965
                        p++;
 
1966
                }
 
1967
        }
 
1968
 
 
1969
/* Next, chase down fragments, if any */
 
1970
 
 
1971
        SLONG page_number = fragment->rhdf_f_page;
 
1972
        USHORT line_number = fragment->rhdf_f_line;
 
1973
        USHORT flags = fragment->rhdf_flags;
 
1974
 
 
1975
        data_page* page = 0;
 
1976
        while (flags & rhd_incomplete) {
 
1977
                WIN window(DB_PAGE_SPACE, -1);
 
1978
                fetch_page(tdbb, control, page_number, pag_data, &window, &page);
 
1979
                const data_page::dpg_repeat* line = &page->dpg_rpt[line_number];
 
1980
                if (page->dpg_relation != relation->rel_id ||
 
1981
                        line_number >= page->dpg_count || !(length = line->dpg_length))
 
1982
                {
 
1983
                        corrupt(tdbb, control, VAL_REC_FRAGMENT_CORRUPT, relation,
 
1984
                                        number);
 
1985
                        CCH_RELEASE(tdbb, &window);
 
1986
                        return rtn_corrupt;
 
1987
                }
 
1988
                fragment = (rhdf*) ((UCHAR *) page + line->dpg_offset);
 
1989
#ifdef DEBUG_VAL_VERBOSE
 
1990
                if (VAL_debug_level) {
 
1991
                        fprintf(stdout, "fragment: pg %d/%d ",
 
1992
                                           page_number, line_number);
 
1993
                        print_rhd(line->dpg_length, (rhd*) fragment);
 
1994
                }
 
1995
#endif
 
1996
                if (fragment->rhdf_flags & rhd_incomplete) {
 
1997
                        p = (SCHAR *) fragment->rhdf_data;
 
1998
                        end = p + line->dpg_length - OFFSETA(rhdf*, rhdf_data);
 
1999
                }
 
2000
                else {
 
2001
                        p = (SCHAR *) ((rhd*) fragment)->rhd_data;
 
2002
                        end = p + line->dpg_length - OFFSETA(rhd*, rhd_data);
 
2003
                }
 
2004
                while (p < end) {
 
2005
                        const char c = *p++;
 
2006
                        if (c >= 0) {
 
2007
                                record_length += c;
 
2008
                                p += c;
 
2009
                        }
 
2010
                        else {
 
2011
                                record_length -= c;
 
2012
                                p++;
 
2013
                        }
 
2014
                }
 
2015
                page_number = fragment->rhdf_f_page;
 
2016
                line_number = fragment->rhdf_f_line;
 
2017
                flags = fragment->rhdf_flags;
 
2018
                CCH_RELEASE(tdbb, &window);
 
2019
        }
 
2020
 
 
2021
/* Check out record length and format */
 
2022
 
 
2023
        const Format* format = MET_format(tdbb, relation, header->rhd_format);
 
2024
 
 
2025
        if (!delta_flag && record_length != format->fmt_length)
 
2026
                return corrupt(tdbb, control, VAL_REC_WRONG_LENGTH, relation, number);
 
2027
 
 
2028
        return rtn_ok;
 
2029
}
 
2030
 
 
2031
 
 
2032
static RTN walk_relation(thread_db* tdbb, vdr* control, jrd_rel* relation)
 
2033
{
 
2034
/**************************************
 
2035
 *
 
2036
 *      w a l k _ r e l a t i o n
 
2037
 *
 
2038
 **************************************
 
2039
 *
 
2040
 * Functional description
 
2041
 *      Walk all pages associated with a given relation.
 
2042
 *
 
2043
 **************************************/
 
2044
 
 
2045
        SET_TDBB(tdbb);
 
2046
 
 
2047
        try {
 
2048
 
 
2049
        // If relation hasn't been scanned, do so now
 
2050
 
 
2051
        if (!(relation->rel_flags & REL_scanned) ||
 
2052
                (relation->rel_flags & REL_being_scanned))
 
2053
        {
 
2054
                MET_scan_relation(tdbb, relation);
 
2055
        }
 
2056
 
 
2057
        // skip deleted relations
 
2058
        if (relation->rel_flags & (REL_deleted | REL_deleting)) {
 
2059
                return rtn_ok;
 
2060
        }
 
2061
 
 
2062
#ifdef DEBUG_VAL_VERBOSE
 
2063
        if (VAL_debug_level)
 
2064
                fprintf(stdout, "walk_relation: id %d Format %d %s %s\n",
 
2065
                                   relation->rel_id, relation->rel_current_fmt,
 
2066
                                   relation->rel_name.c_str(), relation->rel_owner_name.c_str());
 
2067
#endif
 
2068
 
 
2069
/* If it's a view, external file or virtual table, skip this */
 
2070
 
 
2071
        if (relation->rel_view_rse || relation->rel_file || relation->isVirtual()) {
 
2072
                return rtn_ok;
 
2073
        }
 
2074
 
 
2075
 
 
2076
/* Walk pointer and selected data pages associated with relation */
 
2077
 
 
2078
        if (control) {
 
2079
                control->vdr_rel_backversion_counter = 0;
 
2080
                control->vdr_rel_chain_counter = 0;
 
2081
                RecordBitmap::reset(control->vdr_rel_records);
 
2082
        }
 
2083
        for (SLONG sequence = 0; true; sequence++) {
 
2084
                const RTN result = walk_pointer_page(tdbb, control, relation, sequence);
 
2085
                if (result == rtn_eof) {
 
2086
                        break;
 
2087
                }
 
2088
                if (result != rtn_ok) {
 
2089
                        return result;
 
2090
                }
 
2091
        }
 
2092
 
 
2093
        // Walk indices for the relation
 
2094
        walk_root(tdbb, control, relation);
 
2095
 
 
2096
        // See if the counts of backversions match
 
2097
        if (control && (control->vdr_flags & vdr_records) &&
 
2098
                (control->vdr_rel_backversion_counter !=
 
2099
                 control->vdr_rel_chain_counter))
 
2100
        {
 
2101
                 return corrupt(tdbb,
 
2102
                                                control,
 
2103
                                                VAL_REL_CHAIN_ORPHANS,
 
2104
                                                relation,
 
2105
                                                control->vdr_rel_backversion_counter - control-> vdr_rel_chain_counter,
 
2106
                                                control-> vdr_rel_chain_counter);
 
2107
        }
 
2108
 
 
2109
        }       // try
 
2110
        catch (const Firebird::Exception&) {
 
2111
                const char* msg = relation->rel_name.length() > 0 ?
 
2112
                        "bugcheck during scan of table %d (%s)" :
 
2113
                        "bugcheck during scan of table %d";
 
2114
                gds__log(msg, relation->rel_id, relation->rel_name.c_str());
 
2115
#ifdef DEBUG_VAL_VERBOSE
 
2116
                if (VAL_debug_level)
 
2117
                {
 
2118
                        char s[256];
 
2119
                        SNPRINTF(s, sizeof(s), msg, relation->rel_id, relation->rel_name.c_str());
 
2120
                        fprintf(stdout, "LOG:\t%s\n", s);
 
2121
                }
 
2122
#endif
 
2123
                throw;
 
2124
        }
 
2125
 
 
2126
        return rtn_ok;
 
2127
}
 
2128
 
 
2129
 
 
2130
static RTN walk_root(thread_db* tdbb, vdr* control, jrd_rel* relation)
 
2131
{
 
2132
/**************************************
 
2133
 *
 
2134
 *      w a l k _ r o o t
 
2135
 *
 
2136
 **************************************
 
2137
 *
 
2138
 * Functional description
 
2139
 *      Walk index root page for a relation as well as any indices.
 
2140
 *
 
2141
 **************************************/
 
2142
        SET_TDBB(tdbb);
 
2143
 
 
2144
/* If the relation has an index root, walk it */
 
2145
        RelationPages* relPages = relation->getBasePages();
 
2146
 
 
2147
        if (!relPages->rel_index_root) {
 
2148
                return corrupt(tdbb, control, VAL_INDEX_ROOT_MISSING, relation);
 
2149
        }
 
2150
 
 
2151
        index_root_page* page = 0;
 
2152
        WIN window(DB_PAGE_SPACE, -1);
 
2153
        fetch_page(tdbb, control, relPages->rel_index_root, pag_root, &window,
 
2154
                           &page);
 
2155
 
 
2156
        for (USHORT i = 0; i < page->irt_count; i++) {
 
2157
                walk_index(tdbb, control, relation, *page, i);
 
2158
        }
 
2159
 
 
2160
        CCH_RELEASE(tdbb, &window);
 
2161
 
 
2162
        return rtn_ok;
 
2163
}
 
2164
 
 
2165
static RTN walk_tip(thread_db* tdbb, vdr* control, SLONG transaction)
 
2166
{
 
2167
/**************************************
 
2168
 *
 
2169
 *      w a l k _ t i p
 
2170
 *
 
2171
 **************************************
 
2172
 *
 
2173
 * Functional description
 
2174
 *      Walk transaction inventory pages.
 
2175
 *
 
2176
 **************************************/
 
2177
 
 
2178
        SET_TDBB(tdbb);
 
2179
        Database* dbb = tdbb->getDatabase();
 
2180
        CHECK_DBB(dbb);
 
2181
 
 
2182
        const vcl* vector = dbb->dbb_t_pages;
 
2183
        if (!vector) {
 
2184
                return corrupt(tdbb, control, VAL_TIP_LOST, 0);
 
2185
        }
 
2186
 
 
2187
        tx_inv_page* page = 0;
 
2188
        const ULONG pages = transaction / dbb->dbb_page_manager.transPerTIP;
 
2189
 
 
2190
        for (ULONG sequence = 0; sequence <= pages; sequence++) {
 
2191
                if (!(*vector)[sequence] || sequence >= vector->count()) {
 
2192
                        corrupt(tdbb, control, VAL_TIP_LOST_SEQUENCE, 0, sequence);
 
2193
                        if (!(control->vdr_flags & vdr_repair))
 
2194
                                continue;
 
2195
                        TRA_extend_tip(tdbb, sequence, 0);
 
2196
                        vector = dbb->dbb_t_pages;
 
2197
                }
 
2198
 
 
2199
                WIN window(DB_PAGE_SPACE, -1);
 
2200
                fetch_page(     tdbb,
 
2201
                                        control,
 
2202
                                        (*vector)[sequence],
 
2203
                                        pag_transactions,
 
2204
                                        &window,
 
2205
                                        &page);
 
2206
 
 
2207
#ifdef DEBUG_VAL_VERBOSE
 
2208
                if (VAL_debug_level)
 
2209
                        fprintf(stdout, "walk_tip: page %d next %d\n",
 
2210
                                           (*vector)[sequence], page->tip_next);
 
2211
#endif
 
2212
                if (page->tip_next && page->tip_next != (*vector)[sequence + 1])
 
2213
                {
 
2214
                        corrupt(tdbb, control, VAL_TIP_CONFUSED, 0, sequence);
 
2215
                }
 
2216
                CCH_RELEASE(tdbb, &window);
 
2217
        }
 
2218
 
 
2219
        return rtn_ok;
 
2220
}
 
2221