1 /* 2 ** A utility for printing content from a write-ahead log file. 3 */ 4 #include <stdio.h> 5 #include <ctype.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 10 #define ISDIGIT(X) isdigit((unsigned char)(X)) 11 #define ISPRINT(X) isprint((unsigned char)(X)) 12 13 #if !defined(_MSC_VER) 14 #include <unistd.h> 15 #else 16 #include <io.h> 17 #endif 18 19 #include <stdlib.h> 20 #include <string.h> 21 22 23 static int pagesize = 1024; /* Size of a database page */ 24 static int fd = -1; /* File descriptor for reading the WAL file */ 25 static int mxFrame = 0; /* Last frame */ 26 static int perLine = 16; /* HEX elements to print per line */ 27 28 typedef long long int i64; /* Datatype for 64-bit integers */ 29 30 /* Information for computing the checksum */ 31 typedef struct Cksum Cksum; 32 struct Cksum { 33 int bSwap; /* True to do byte swapping on 32-bit words */ 34 unsigned s0, s1; /* Current checksum value */ 35 }; 36 37 /* 38 ** extract a 32-bit big-endian integer 39 */ 40 static unsigned int getInt32(const unsigned char *a){ 41 unsigned int x = (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; 42 return x; 43 } 44 45 /* 46 ** Swap bytes on a 32-bit unsigned integer 47 */ 48 static unsigned int swab32(unsigned int x){ 49 return (((x)&0x000000FF)<<24) + (((x)&0x0000FF00)<<8) 50 + (((x)&0x00FF0000)>>8) + (((x)&0xFF000000)>>24); 51 } 52 53 /* Extend the checksum. Reinitialize the checksum if bInit is true. 54 */ 55 static void extendCksum( 56 Cksum *pCksum, 57 unsigned char *aData, 58 unsigned int nByte, 59 int bInit 60 ){ 61 unsigned int *a32; 62 if( bInit ){ 63 int a = 0; 64 *((char*)&a) = 1; 65 if( a==1 ){ 66 /* Host is little-endian */ 67 pCksum->bSwap = getInt32(aData)!=0x377f0682; 68 }else{ 69 /* Host is big-endian */ 70 pCksum->bSwap = getInt32(aData)!=0x377f0683; 71 } 72 pCksum->s0 = 0; 73 pCksum->s1 = 0; 74 } 75 a32 = (unsigned int*)aData; 76 while( nByte>0 ){ 77 unsigned int x0 = a32[0]; 78 unsigned int x1 = a32[1]; 79 if( pCksum->bSwap ){ 80 x0 = swab32(x0); 81 x1 = swab32(x1); 82 } 83 pCksum->s0 += x0 + pCksum->s1; 84 pCksum->s1 += x1 + pCksum->s0; 85 nByte -= 8; 86 a32 += 2; 87 } 88 } 89 90 /* 91 ** Convert the var-int format into i64. Return the number of bytes 92 ** in the var-int. Write the var-int value into *pVal. 93 */ 94 static int decodeVarint(const unsigned char *z, i64 *pVal){ 95 i64 v = 0; 96 int i; 97 for(i=0; i<8; i++){ 98 v = (v<<7) + (z[i]&0x7f); 99 if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; } 100 } 101 v = (v<<8) + (z[i]&0xff); 102 *pVal = v; 103 return 9; 104 } 105 106 /* Report an out-of-memory error and die. 107 */ 108 static void out_of_memory(void){ 109 fprintf(stderr,"Out of memory...\n"); 110 exit(1); 111 } 112 113 /* 114 ** Read content from the file. 115 ** 116 ** Space to hold the content is obtained from malloc() and needs to be 117 ** freed by the caller. 118 */ 119 static unsigned char *getContent(int ofst, int nByte){ 120 unsigned char *aData; 121 aData = malloc(nByte); 122 if( aData==0 ) out_of_memory(); 123 lseek(fd, ofst, SEEK_SET); 124 read(fd, aData, nByte); 125 return aData; 126 } 127 128 /* 129 ** Print a range of bytes as hex and as ascii. 130 */ 131 static void print_byte_range( 132 int ofst, /* First byte in the range of bytes to print */ 133 int nByte, /* Number of bytes to print */ 134 unsigned char *aData, /* Content to print */ 135 int printOfst /* Add this amount to the index on the left column */ 136 ){ 137 int i, j; 138 const char *zOfstFmt; 139 140 if( ((printOfst+nByte)&~0xfff)==0 ){ 141 zOfstFmt = " %03x: "; 142 }else if( ((printOfst+nByte)&~0xffff)==0 ){ 143 zOfstFmt = " %04x: "; 144 }else if( ((printOfst+nByte)&~0xfffff)==0 ){ 145 zOfstFmt = " %05x: "; 146 }else if( ((printOfst+nByte)&~0xffffff)==0 ){ 147 zOfstFmt = " %06x: "; 148 }else{ 149 zOfstFmt = " %08x: "; 150 } 151 152 for(i=0; i<nByte; i += perLine){ 153 fprintf(stdout, zOfstFmt, i+printOfst); 154 for(j=0; j<perLine; j++){ 155 if( i+j>nByte ){ 156 fprintf(stdout, " "); 157 }else{ 158 fprintf(stdout,"%02x ", aData[i+j]); 159 } 160 } 161 for(j=0; j<perLine; j++){ 162 if( i+j>nByte ){ 163 fprintf(stdout, " "); 164 }else{ 165 fprintf(stdout,"%c", ISPRINT(aData[i+j]) ? aData[i+j] : '.'); 166 } 167 } 168 fprintf(stdout,"\n"); 169 } 170 } 171 172 /* Print a line of decode output showing a 4-byte integer. 173 */ 174 static void print_decode_line( 175 unsigned char *aData, /* Content being decoded */ 176 int ofst, int nByte, /* Start and size of decode */ 177 int asHex, /* If true, output value as hex */ 178 const char *zMsg /* Message to append */ 179 ){ 180 int i, j; 181 int val = aData[ofst]; 182 char zBuf[100]; 183 sprintf(zBuf, " %03x: %02x", ofst, aData[ofst]); 184 i = (int)strlen(zBuf); 185 for(j=1; j<4; j++){ 186 if( j>=nByte ){ 187 sprintf(&zBuf[i], " "); 188 }else{ 189 sprintf(&zBuf[i], " %02x", aData[ofst+j]); 190 val = val*256 + aData[ofst+j]; 191 } 192 i += (int)strlen(&zBuf[i]); 193 } 194 if( asHex ){ 195 sprintf(&zBuf[i], " 0x%08x", val); 196 }else{ 197 sprintf(&zBuf[i], " %9d", val); 198 } 199 printf("%s %s\n", zBuf, zMsg); 200 } 201 202 /* 203 ** Print an entire page of content as hex 204 */ 205 static void print_frame(int iFrame){ 206 int iStart; 207 unsigned char *aData; 208 iStart = 32 + (iFrame-1)*(pagesize+24); 209 fprintf(stdout, "Frame %d: (offsets 0x%x..0x%x)\n", 210 iFrame, iStart, iStart+pagesize+24); 211 aData = getContent(iStart, pagesize+24); 212 print_decode_line(aData, 0, 4, 0, "Page number"); 213 print_decode_line(aData, 4, 4, 0, "DB size, or 0 for non-commit"); 214 print_decode_line(aData, 8, 4, 1, "Salt-1"); 215 print_decode_line(aData,12, 4, 1, "Salt-2"); 216 print_decode_line(aData,16, 4, 1, "Checksum-1"); 217 print_decode_line(aData,20, 4, 1, "Checksum-2"); 218 print_byte_range(iStart+24, pagesize, aData+24, 0); 219 free(aData); 220 } 221 222 /* 223 ** Summarize a single frame on a single line. 224 */ 225 static void print_oneline_frame(int iFrame, Cksum *pCksum){ 226 int iStart; 227 unsigned char *aData; 228 unsigned int s0, s1; 229 iStart = 32 + (iFrame-1)*(pagesize+24); 230 aData = getContent(iStart, 24); 231 extendCksum(pCksum, aData, 8, 0); 232 extendCksum(pCksum, getContent(iStart+24, pagesize), pagesize, 0); 233 s0 = getInt32(aData+16); 234 s1 = getInt32(aData+20); 235 fprintf(stdout, "Frame %4d: %6d %6d 0x%08x,%08x 0x%08x,%08x %s\n", 236 iFrame, 237 getInt32(aData), 238 getInt32(aData+4), 239 getInt32(aData+8), 240 getInt32(aData+12), 241 s0, 242 s1, 243 (s0==pCksum->s0 && s1==pCksum->s1) ? "" : "cksum-fail" 244 ); 245 246 /* Reset the checksum so that a single frame checksum failure will not 247 ** cause all subsequent frames to also show a failure. */ 248 pCksum->s0 = s0; 249 pCksum->s1 = s1; 250 free(aData); 251 } 252 253 /* 254 ** Decode the WAL header. 255 */ 256 static void print_wal_header(Cksum *pCksum){ 257 unsigned char *aData; 258 aData = getContent(0, 32); 259 if( pCksum ){ 260 extendCksum(pCksum, aData, 24, 1); 261 printf("Checksum byte order: %s\n", pCksum->bSwap ? "swapped" : "native"); 262 } 263 printf("WAL Header:\n"); 264 print_decode_line(aData, 0, 4,1,"Magic. 0x377f0682 (le) or 0x377f0683 (be)"); 265 print_decode_line(aData, 4, 4, 0, "File format"); 266 print_decode_line(aData, 8, 4, 0, "Database page size"); 267 print_decode_line(aData, 12,4, 0, "Checkpoint sequence number"); 268 print_decode_line(aData, 16,4, 1, "Salt-1"); 269 print_decode_line(aData, 20,4, 1, "Salt-2"); 270 print_decode_line(aData, 24,4, 1, "Checksum-1"); 271 print_decode_line(aData, 28,4, 1, "Checksum-2"); 272 if( pCksum ){ 273 if( pCksum->s0!=getInt32(aData+24) ){ 274 printf("**** cksum-1 mismatch: 0x%08x\n", pCksum->s0); 275 } 276 if( pCksum->s1!=getInt32(aData+28) ){ 277 printf("**** cksum-2 mismatch: 0x%08x\n", pCksum->s1); 278 } 279 } 280 free(aData); 281 } 282 /* 283 ** Describe cell content. 284 */ 285 static i64 describeContent( 286 unsigned char *a, /* Cell content */ 287 i64 nLocal, /* Bytes in a[] */ 288 char *zDesc /* Write description here */ 289 ){ 290 int nDesc = 0; 291 int n, j; 292 i64 i, x, v; 293 const unsigned char *pData; 294 const unsigned char *pLimit; 295 char sep = ' '; 296 297 pLimit = &a[nLocal]; 298 n = decodeVarint(a, &x); 299 pData = &a[x]; 300 a += n; 301 i = x - n; 302 while( i>0 && pData<=pLimit ){ 303 n = decodeVarint(a, &x); 304 a += n; 305 i -= n; 306 nLocal -= n; 307 zDesc[0] = sep; 308 sep = ','; 309 nDesc++; 310 zDesc++; 311 if( x==0 ){ 312 sprintf(zDesc, "*"); /* NULL is a "*" */ 313 }else if( x>=1 && x<=6 ){ 314 v = (signed char)pData[0]; 315 pData++; 316 switch( x ){ 317 case 6: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; 318 case 5: v = (v<<16) + (pData[0]<<8) + pData[1]; pData += 2; 319 case 4: v = (v<<8) + pData[0]; pData++; 320 case 3: v = (v<<8) + pData[0]; pData++; 321 case 2: v = (v<<8) + pData[0]; pData++; 322 } 323 sprintf(zDesc, "%lld", v); 324 }else if( x==7 ){ 325 sprintf(zDesc, "real"); 326 pData += 8; 327 }else if( x==8 ){ 328 sprintf(zDesc, "0"); 329 }else if( x==9 ){ 330 sprintf(zDesc, "1"); 331 }else if( x>=12 ){ 332 i64 size = (x-12)/2; 333 if( (x&1)==0 ){ 334 sprintf(zDesc, "blob(%lld)", size); 335 }else{ 336 sprintf(zDesc, "txt(%lld)", size); 337 } 338 pData += size; 339 } 340 j = (int)strlen(zDesc); 341 zDesc += j; 342 nDesc += j; 343 } 344 return nDesc; 345 } 346 347 /* 348 ** Compute the local payload size given the total payload size and 349 ** the page size. 350 */ 351 static i64 localPayload(i64 nPayload, char cType){ 352 i64 maxLocal; 353 i64 minLocal; 354 i64 surplus; 355 i64 nLocal; 356 if( cType==13 ){ 357 /* Table leaf */ 358 maxLocal = pagesize-35; 359 minLocal = (pagesize-12)*32/255-23; 360 }else{ 361 maxLocal = (pagesize-12)*64/255-23; 362 minLocal = (pagesize-12)*32/255-23; 363 } 364 if( nPayload>maxLocal ){ 365 surplus = minLocal + (nPayload-minLocal)%(pagesize-4); 366 if( surplus<=maxLocal ){ 367 nLocal = surplus; 368 }else{ 369 nLocal = minLocal; 370 } 371 }else{ 372 nLocal = nPayload; 373 } 374 return nLocal; 375 } 376 377 /* 378 ** Create a description for a single cell. 379 ** 380 ** The return value is the local cell size. 381 */ 382 static i64 describeCell( 383 unsigned char cType, /* Page type */ 384 unsigned char *a, /* Cell content */ 385 int showCellContent, /* Show cell content if true */ 386 char **pzDesc /* Store description here */ 387 ){ 388 int i; 389 i64 nDesc = 0; 390 int n = 0; 391 int leftChild; 392 i64 nPayload; 393 i64 rowid; 394 i64 nLocal; 395 static char zDesc[1000]; 396 i = 0; 397 if( cType<=5 ){ 398 leftChild = ((a[0]*256 + a[1])*256 + a[2])*256 + a[3]; 399 a += 4; 400 n += 4; 401 sprintf(zDesc, "lx: %d ", leftChild); 402 nDesc = strlen(zDesc); 403 } 404 if( cType!=5 ){ 405 i = decodeVarint(a, &nPayload); 406 a += i; 407 n += i; 408 sprintf(&zDesc[nDesc], "n: %lld ", nPayload); 409 nDesc += strlen(&zDesc[nDesc]); 410 nLocal = localPayload(nPayload, cType); 411 }else{ 412 nPayload = nLocal = 0; 413 } 414 if( cType==5 || cType==13 ){ 415 i = decodeVarint(a, &rowid); 416 a += i; 417 n += i; 418 sprintf(&zDesc[nDesc], "r: %lld ", rowid); 419 nDesc += strlen(&zDesc[nDesc]); 420 } 421 if( nLocal<nPayload ){ 422 int ovfl; 423 unsigned char *b = &a[nLocal]; 424 ovfl = ((b[0]*256 + b[1])*256 + b[2])*256 + b[3]; 425 sprintf(&zDesc[nDesc], "ov: %d ", ovfl); 426 nDesc += strlen(&zDesc[nDesc]); 427 n += 4; 428 } 429 if( showCellContent && cType!=5 ){ 430 nDesc += describeContent(a, nLocal, &zDesc[nDesc-1]); 431 } 432 *pzDesc = zDesc; 433 return nLocal+n; 434 } 435 436 /* 437 ** Decode a btree page 438 */ 439 static void decode_btree_page( 440 unsigned char *a, /* Content of the btree page to be decoded */ 441 int pgno, /* Page number */ 442 int hdrSize, /* Size of the page1-header in bytes */ 443 const char *zArgs /* Flags to control formatting */ 444 ){ 445 const char *zType = "unknown"; 446 int nCell; 447 int i, j; 448 int iCellPtr; 449 int showCellContent = 0; 450 int showMap = 0; 451 char *zMap = 0; 452 switch( a[0] ){ 453 case 2: zType = "index interior node"; break; 454 case 5: zType = "table interior node"; break; 455 case 10: zType = "index leaf"; break; 456 case 13: zType = "table leaf"; break; 457 } 458 while( zArgs[0] ){ 459 switch( zArgs[0] ){ 460 case 'c': showCellContent = 1; break; 461 case 'm': showMap = 1; break; 462 } 463 zArgs++; 464 } 465 printf("Decode of btree page %d:\n", pgno); 466 print_decode_line(a, 0, 1, 0, zType); 467 print_decode_line(a, 1, 2, 0, "Offset to first freeblock"); 468 print_decode_line(a, 3, 2, 0, "Number of cells on this page"); 469 nCell = a[3]*256 + a[4]; 470 print_decode_line(a, 5, 2, 0, "Offset to cell content area"); 471 print_decode_line(a, 7, 1, 0, "Fragmented byte count"); 472 if( a[0]==2 || a[0]==5 ){ 473 print_decode_line(a, 8, 4, 0, "Right child"); 474 iCellPtr = 12; 475 }else{ 476 iCellPtr = 8; 477 } 478 if( nCell>0 ){ 479 printf(" key: lx=left-child n=payload-size r=rowid\n"); 480 } 481 if( showMap ){ 482 zMap = malloc(pagesize); 483 memset(zMap, '.', pagesize); 484 memset(zMap, '1', hdrSize); 485 memset(&zMap[hdrSize], 'H', iCellPtr); 486 memset(&zMap[hdrSize+iCellPtr], 'P', 2*nCell); 487 } 488 for(i=0; i<nCell; i++){ 489 int cofst = iCellPtr + i*2; 490 char *zDesc; 491 i64 n; 492 493 cofst = a[cofst]*256 + a[cofst+1]; 494 n = describeCell(a[0], &a[cofst-hdrSize], showCellContent, &zDesc); 495 if( showMap ){ 496 char zBuf[30]; 497 memset(&zMap[cofst], '*', (size_t)n); 498 zMap[cofst] = '['; 499 zMap[cofst+n-1] = ']'; 500 sprintf(zBuf, "%d", i); 501 j = (int)strlen(zBuf); 502 if( j<=n-2 ) memcpy(&zMap[cofst+1], zBuf, j); 503 } 504 printf(" %03x: cell[%d] %s\n", cofst, i, zDesc); 505 } 506 if( showMap ){ 507 for(i=0; i<pagesize; i+=64){ 508 printf(" %03x: %.64s\n", i, &zMap[i]); 509 } 510 free(zMap); 511 } 512 } 513 514 int main(int argc, char **argv){ 515 struct stat sbuf; 516 unsigned char zPgSz[4]; 517 if( argc<2 ){ 518 fprintf(stderr,"Usage: %s FILENAME ?PAGE? ...\n", argv[0]); 519 exit(1); 520 } 521 fd = open(argv[1], O_RDONLY); 522 if( fd<0 ){ 523 fprintf(stderr,"%s: can't open %s\n", argv[0], argv[1]); 524 exit(1); 525 } 526 zPgSz[0] = 0; 527 zPgSz[1] = 0; 528 lseek(fd, 8, SEEK_SET); 529 read(fd, zPgSz, 4); 530 pagesize = zPgSz[1]*65536 + zPgSz[2]*256 + zPgSz[3]; 531 if( pagesize==0 ) pagesize = 1024; 532 printf("Pagesize: %d\n", pagesize); 533 fstat(fd, &sbuf); 534 if( sbuf.st_size<32 ){ 535 printf("file too small to be a WAL\n"); 536 return 0; 537 } 538 mxFrame = (sbuf.st_size - 32)/(pagesize + 24); 539 printf("Available pages: 1..%d\n", mxFrame); 540 if( argc==2 ){ 541 int i; 542 Cksum x; 543 print_wal_header(&x); 544 for(i=1; i<=mxFrame; i++){ 545 print_oneline_frame(i, &x); 546 } 547 }else{ 548 int i; 549 for(i=2; i<argc; i++){ 550 int iStart, iEnd; 551 char *zLeft; 552 if( strcmp(argv[i], "header")==0 ){ 553 print_wal_header(0); 554 continue; 555 } 556 if( !ISDIGIT(argv[i][0]) ){ 557 fprintf(stderr, "%s: unknown option: [%s]\n", argv[0], argv[i]); 558 continue; 559 } 560 iStart = strtol(argv[i], &zLeft, 0); 561 if( zLeft && strcmp(zLeft,"..end")==0 ){ 562 iEnd = mxFrame; 563 }else if( zLeft && zLeft[0]=='.' && zLeft[1]=='.' ){ 564 iEnd = strtol(&zLeft[2], 0, 0); 565 }else if( zLeft && zLeft[0]=='b' ){ 566 int ofst, nByte, hdrSize; 567 unsigned char *a; 568 if( iStart==1 ){ 569 hdrSize = 100; 570 ofst = hdrSize = 100; 571 nByte = pagesize-100; 572 }else{ 573 hdrSize = 0; 574 ofst = (iStart-1)*pagesize; 575 nByte = pagesize; 576 } 577 ofst = 32 + hdrSize + (iStart-1)*(pagesize+24) + 24; 578 a = getContent(ofst, nByte); 579 decode_btree_page(a, iStart, hdrSize, zLeft+1); 580 free(a); 581 continue; 582 }else{ 583 iEnd = iStart; 584 } 585 if( iStart<1 || iEnd<iStart || iEnd>mxFrame ){ 586 fprintf(stderr, 587 "Page argument should be LOWER?..UPPER?. Range 1 to %d\n", 588 mxFrame); 589 exit(1); 590 } 591 while( iStart<=iEnd ){ 592 print_frame(iStart); 593 iStart++; 594 } 595 } 596 } 597 close(fd); 598 return 0; 599 } 600