1 /* 2 ** Copyright 2008 D. Richard Hipp and Hipp, Wyrick & Company, Inc. 3 ** All Rights Reserved 4 ** 5 ****************************************************************************** 6 ** 7 ** This file implements a stand-alone utility program that converts 8 ** a binary file (usually an SQLite database) into a text format that 9 ** is compact and friendly to human-readers. 10 ** 11 ** Usage: 12 ** 13 ** dbtotxt [OPTIONS] FILENAME 14 ** 15 ** where OPTIONS are zero or more of: 16 ** 17 ** --for-cli prepending '.open --hexdb' to the output 18 ** 19 ** --script The input file is expected to start with a 20 ** zero-terminated SQL string. Output the 21 ** ".open --hexdb" header, then the database 22 ** then the SQL. 23 ** 24 ** --pagesize N set the database page size for later reading 25 ** 26 ** The translation of the database appears on standard output. If the 27 ** --pagesize command-line option is omitted, then the page size is taken 28 ** from the database header. 29 ** 30 ** Compactness is achieved by suppressing lines of all zero bytes. This 31 ** works well at compressing test databases that are mostly empty. But 32 ** the output will probably be lengthy for a real database containing lots 33 ** of real content. For maximum compactness, it is suggested that test 34 ** databases be constructed with "zeroblob()" rather than "randomblob()" 35 ** used for filler content and with "PRAGMA secure_delete=ON" selected to 36 ** zero-out deleted content. 37 */ 38 #include <stdio.h> 39 #include <string.h> 40 #include <stdlib.h> 41 #include <ctype.h> 42 43 /* Return true if the line is all zeros */ 44 static int allZero(unsigned char *aLine){ 45 int i; 46 for(i=0; i<16 && aLine[i]==0; i++){} 47 return i==16; 48 } 49 50 int main(int argc, char **argv){ 51 int pgsz = 0; /* page size */ 52 int forCli = 0; /* whether to prepend with .open */ 53 int bSQL = 0; /* Expect and SQL prefix */ 54 long szFile; /* Size of the input file in bytes */ 55 FILE *in; /* Input file */ 56 int nSQL; /* Number of bytes of script */ 57 int i, j; /* Loop counters */ 58 int nErr = 0; /* Number of errors */ 59 const char *zInputFile = 0; /* Name of the input file */ 60 const char *zBaseName = 0; /* Base name of the file */ 61 int lastPage = 0; /* Last page number shown */ 62 int iPage; /* Current page number */ 63 unsigned char *aData = 0; /* All data */ 64 unsigned char *aLine; /* A single line of the file */ 65 unsigned char *aHdr; /* File header */ 66 unsigned char bShow[256]; /* Characters ok to display */ 67 memset(bShow, '.', sizeof(bShow)); 68 for(i=' '; i<='~'; i++){ 69 if( i!='{' && i!='}' && i!='"' && i!='\\' ) bShow[i] = (unsigned char)i; 70 } 71 for(i=1; i<argc; i++){ 72 if( argv[i][0]=='-' ){ 73 const char *z = argv[i]; 74 z++; 75 if( z[0]=='-' ) z++; 76 if( strcmp(z,"pagesize")==0 ){ 77 i++; 78 pgsz = atoi(argv[i]); 79 if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ){ 80 fprintf(stderr, "Page size must be a power of two between" 81 " 512 and 65536.\n"); 82 nErr++; 83 } 84 continue; 85 }else if( strcmp(z,"for-cli")==0 ){ 86 forCli = 1; 87 continue; 88 }else if( strcmp(z,"script")==0 ){ 89 forCli = 1; 90 bSQL = 1; 91 continue; 92 } 93 fprintf(stderr, "Unknown option: %s\n", argv[i]); 94 nErr++; 95 }else if( zInputFile ){ 96 fprintf(stderr, "Already using a different input file: [%s]\n", argv[i]); 97 nErr++; 98 }else{ 99 zInputFile = argv[i]; 100 } 101 } 102 if( zInputFile==0 ){ 103 fprintf(stderr, "No input file specified.\n"); 104 nErr++; 105 } 106 if( nErr ){ 107 fprintf(stderr, 108 "Usage: %s [--pagesize N] [--script] [--for-cli] FILENAME\n", argv[0]); 109 exit(1); 110 } 111 in = fopen(zInputFile, "rb"); 112 if( in==0 ){ 113 fprintf(stderr, "Cannot open input file [%s]\n", zInputFile); 114 exit(1); 115 } 116 fseek(in, 0, SEEK_END); 117 szFile = ftell(in); 118 rewind(in); 119 if( szFile<100 ){ 120 fprintf(stderr, "File too short. Minimum size is 100 bytes.\n"); 121 exit(1); 122 } 123 aData = malloc( szFile+16 ); 124 if( aData==0 ){ 125 fprintf(stderr, "Failed to allocate %ld bytes\n", szFile); 126 exit(1); 127 } 128 if( fread(aData, szFile, 1, in)!=1 ){ 129 fprintf(stderr, "Cannot read file info memory\n"); 130 exit(1); 131 } 132 memset(aData+szFile, 0, 16); 133 fclose(in); 134 if( bSQL ){ 135 for(i=0; i<szFile && aData[i]!=0; i++){} 136 if( i==szFile ){ 137 fprintf(stderr, "No zero terminator on SQL script\n"); 138 exit(1); 139 } 140 nSQL = i+1; 141 if( szFile - nSQL<100 ){ 142 fprintf(stderr, "Less than 100 bytes in the database\n"); 143 exit(1); 144 } 145 }else{ 146 nSQL = 0; 147 } 148 aHdr = aData + nSQL; 149 if( pgsz==0 ){ 150 pgsz = (aHdr[16]<<8) | aHdr[17]; 151 if( pgsz==1 ) pgsz = 65536; 152 if( pgsz<512 || (pgsz&(pgsz-1))!=0 ){ 153 fprintf(stderr, "Invalid page size in header: %d\n", pgsz); 154 exit(1); 155 } 156 } 157 zBaseName = zInputFile; 158 for(i=0; zInputFile[i]; i++){ 159 if( zInputFile[i]=='/' && zInputFile[i+1]!=0 ) zBaseName = zInputFile+i+1; 160 } 161 if( forCli ){ 162 printf(".open --hexdb\n"); 163 } 164 printf("| size %d pagesize %d filename %s\n",(int)szFile,pgsz,zBaseName); 165 for(i=nSQL; i<szFile; i+=16){ 166 aLine = aData+i; 167 if( allZero(aLine) ) continue; 168 iPage = i/pgsz + 1; 169 if( lastPage!=iPage ){ 170 printf("| page %d offset %d\n", iPage, (iPage-1)*pgsz); 171 lastPage = iPage; 172 } 173 printf("| %5d:", i-(iPage-1)*pgsz); 174 for(j=0; j<16; j++) printf(" %02x", aLine[j]); 175 printf(" "); 176 for(j=0; j<16; j++){ 177 unsigned char c = (unsigned char)aLine[j]; 178 fputc( bShow[c], stdout); 179 } 180 fputc('\n', stdout); 181 } 182 printf("| end %s\n", zBaseName); 183 if( nSQL>0 ){ 184 printf("%s\n", aData); 185 } 186 free( aData ); 187 return 0; 188 } 189