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