1 /*
2    Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License, version 2.0,
6    as published by the Free Software Foundation.
7 
8    This program is also distributed with certain software (including
9    but not limited to OpenSSL) that is licensed under separate terms,
10    as designated in a particular file or component or in included license
11    documentation.  The authors of MySQL hereby grant you an additional
12    permission to link the program and your derivative works with the
13    separately licensed software that they have included with MySQL.
14 
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License, version 2.0, for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23 */
24 
25 /*
26   InnoDB offline file checksum utility.  85% of the code in this utility
27   is included from the InnoDB codebase.
28 
29   The final 15% was originally written by Mark Smith of Danga
30   Interactive, Inc. <junior@danga.com>
31 
32   Published with a permission.
33 */
34 
35 #include <my_config.h>
36 #include <my_global.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <time.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #ifndef __WIN__
43 # include <unistd.h>
44 #endif
45 #include <my_getopt.h>
46 #include <m_string.h>
47 #include <welcome_copyright_notice.h> /* ORACLE_WELCOME_COPYRIGHT_NOTICE */
48 
49 /* Only parts of these files are included from the InnoDB codebase.
50 The parts not included are excluded by #ifndef UNIV_INNOCHECKSUM. */
51 
52 #include "univ.i"                /*  include all of this */
53 
54 #include "buf0checksum.h"        /* buf_calc_page_*() */
55 #include "fil0fil.h"             /* FIL_* */
56 #include "fsp0fsp.h"             /* fsp_flags_get_page_size() &
57                                     fsp_flags_get_zip_size() */
58 #include "mach0data.h"           /* mach_read_from_4() */
59 #include "ut0crc32.h"            /* ut_crc32_init() */
60 
61 #ifdef UNIV_NONINL
62 # include "fsp0fsp.ic"
63 # include "mach0data.ic"
64 # include "ut0rnd.ic"
65 #endif
66 
67 /* Global variables */
68 static my_bool verbose;
69 static my_bool debug;
70 static my_bool just_count;
71 static ullint start_page;
72 static ullint end_page;
73 static ullint do_page;
74 static my_bool use_end_page;
75 static my_bool do_one_page;
76 ulong srv_page_size;              /* replaces declaration in srv0srv.c */
77 static ulong physical_page_size;  /* Page size in bytes on disk. */
78 static ulong logical_page_size;   /* Page size when uncompressed. */
79 
80 /* Get the page size of the filespace from the filespace header. */
81 static
82 my_bool
get_page_size(FILE * f,byte * buf,ulong * logical_page_size,ulong * physical_page_size)83 get_page_size(
84 /*==========*/
85   FILE*  f,                     /*!< in: file pointer, must be open
86                                          and set to start of file */
87   byte* buf,                    /*!< in: buffer used to read the page */
88   ulong* logical_page_size,     /*!< out: Logical/Uncompressed page size */
89   ulong* physical_page_size)    /*!< out: Physical/Commpressed page size */
90 {
91   ulong flags;
92 
93   ulong bytes= ulong(fread(buf, 1, UNIV_PAGE_SIZE_MIN, f));
94 
95   if (ferror(f))
96   {
97     perror("Error reading file header");
98     return FALSE;
99   }
100 
101   if (bytes != UNIV_PAGE_SIZE_MIN)
102   {
103     fprintf(stderr, "Error; Was not able to read the minimum page size ");
104     fprintf(stderr, "of %d bytes.  Bytes read was %lu\n", UNIV_PAGE_SIZE_MIN, bytes);
105     return FALSE;
106   }
107 
108   rewind(f);
109 
110   flags = mach_read_from_4(buf + FIL_PAGE_DATA + FSP_SPACE_FLAGS);
111 
112   /* srv_page_size is used by InnoDB code as UNIV_PAGE_SIZE */
113   srv_page_size = *logical_page_size = fsp_flags_get_page_size(flags);
114 
115   /* fsp_flags_get_zip_size() will return zero if not compressed. */
116   *physical_page_size = fsp_flags_get_zip_size(flags);
117   if (*physical_page_size == 0)
118     *physical_page_size= *logical_page_size;
119 
120   return TRUE;
121 }
122 
123 #ifdef __WIN__
124 /***********************************************//*
125  @param		[in] error	error no. from the getLastError().
126 
127  @retval error message corresponding to error no.
128 */
129 static
130 char*
win32_error_message(int error)131 win32_error_message(
132 	int	error)
133 {
134 	static char err_msg[1024] = {'\0'};
135 	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
136 		NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
137 		(LPTSTR)err_msg, sizeof(err_msg), NULL );
138 
139 	return (err_msg);
140 }
141 #endif /* __WIN__ */
142 
143 /***********************************************//*
144  @param [in] name	name of file.
145  @retval file pointer; file pointer is NULL when error occured.
146 */
147 
148 FILE*
open_file(const char * name)149 open_file(
150 	const char*	name)
151 {
152 	int	fd;		/* file descriptor. */
153 	FILE*	fil_in;
154 #ifdef __WIN__
155 	HANDLE		hFile;		/* handle to open file. */
156 	DWORD		access;		/* define access control */
157 	int		flags;		/* define the mode for file
158 					descriptor */
159 
160 	access = GENERIC_READ;
161 	flags = _O_RDONLY | _O_BINARY;
162 	hFile = CreateFile(
163 			(LPCTSTR) name, access, 0L, NULL,
164 			OPEN_EXISTING, NULL, NULL);
165 
166 	if (hFile == INVALID_HANDLE_VALUE) {
167 		/* print the error message. */
168 		fprintf(stderr, "Filename::%s %s\n",
169 			win32_error_message(GetLastError()));
170 
171 			return (NULL);
172 		}
173 
174 	/* get the file descriptor. */
175 	fd= _open_osfhandle((intptr_t)hFile, flags);
176 #else /* __WIN__ */
177 
178 	int	create_flag;
179 	create_flag = O_RDONLY;
180 
181 	fd = open(name, create_flag);
182 	if ( -1 == fd) {
183 		perror("open");
184 		return (NULL);
185 	}
186 
187 #endif /* __WIN__ */
188 
189 	fil_in = fdopen(fd, "rb");
190 
191 	return (fil_in);
192 }
193 
194 /* command line argument to do page checks (that's it) */
195 /* another argument to specify page ranges... seek to right spot and go from there */
196 
197 static struct my_option innochecksum_options[] =
198 {
199   {"help", '?', "Displays this help and exits.",
200     0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
201   {"info", 'I', "Synonym for --help.",
202     0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
203   {"version", 'V', "Displays version information and exits.",
204     0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
205   {"verbose", 'v', "Verbose (prints progress every 5 seconds).",
206     &verbose, &verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
207   {"debug", 'd', "Debug mode (prints checksums for each page, implies verbose).",
208     &debug, &debug, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
209   {"count", 'c', "Print the count of pages in the file.",
210     &just_count, &just_count, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
211   {"start_page", 's', "Start on this page number (0 based).",
212     &start_page, &start_page, 0, GET_ULL, REQUIRED_ARG,
213     0, 0, ULONGLONG_MAX, 0, 1, 0},
214   {"end_page", 'e', "End at this page number (0 based).",
215     &end_page, &end_page, 0, GET_ULL, REQUIRED_ARG,
216     0, 0, ULONGLONG_MAX, 0, 1, 0},
217   {"page", 'p', "Check only this page (0 based).",
218     &do_page, &do_page, 0, GET_ULL, REQUIRED_ARG,
219     0, 0, ULONGLONG_MAX, 0, 1, 0},
220 
221   {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
222 };
223 
print_version(void)224 static void print_version(void)
225 {
226   printf("%s Ver %s, for %s (%s)\n",
227          my_progname, INNODB_VERSION_STR,
228          SYSTEM_TYPE, MACHINE_TYPE);
229 }
230 
usage(void)231 static void usage(void)
232 {
233   print_version();
234   puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"));
235   printf("InnoDB offline file checksum utility.\n");
236   printf("Usage: %s [-c] [-s <start page>] [-e <end page>] [-p <page>] [-v] [-d] <filename>\n", my_progname);
237   my_print_help(innochecksum_options);
238   my_print_variables(innochecksum_options);
239 }
240 
241 extern "C" my_bool
innochecksum_get_one_option(int optid,const struct my_option * opt MY_ATTRIBUTE ((unused)),char * argument MY_ATTRIBUTE ((unused)))242 innochecksum_get_one_option(
243 /*========================*/
244   int optid,
245   const struct my_option *opt MY_ATTRIBUTE((unused)),
246   char *argument MY_ATTRIBUTE((unused)))
247 {
248   switch (optid) {
249   case 'd':
250     verbose=1;	/* debug implies verbose... */
251     break;
252   case 'e':
253     use_end_page= 1;
254     break;
255   case 'p':
256     end_page= start_page= do_page;
257     use_end_page= 1;
258     do_one_page= 1;
259     break;
260   case 'V':
261     print_version();
262     exit(0);
263     break;
264   case 'I':
265   case '?':
266     usage();
267     exit(0);
268     break;
269   }
270   return 0;
271 }
272 
get_options(int * argc,char *** argv)273 static int get_options(
274 /*===================*/
275   int *argc,
276   char ***argv)
277 {
278   int ho_error;
279 
280   if ((ho_error=handle_options(argc, argv, innochecksum_options, innochecksum_get_one_option)))
281     exit(ho_error);
282 
283   /* The next arg must be the filename */
284   if (!*argc)
285   {
286     usage();
287     return 1;
288   }
289   return 0;
290 } /* get_options */
291 
292 
main(int argc,char ** argv)293 int main(int argc, char **argv)
294 {
295   FILE* f;                       /* our input file */
296   char* filename;                /* our input filename. */
297   unsigned char buf[UNIV_PAGE_SIZE_MAX]; /* Buffer to store pages read */
298   ulong bytes;                   /* bytes read count */
299   ulint ct;                      /* current page number (0 based) */
300   time_t now;                    /* current time */
301   time_t lastt;                  /* last time */
302   ulint oldcsum, oldcsumfield, csum, csumfield, crc32, logseq, logseqfield;
303 
304                                  /* ulints for checksum storage */
305   /* stat, to get file size. */
306 #ifdef __WIN__
307   struct _stat64 st;
308 #else
309   struct stat st;
310 #endif
311   unsigned long long int size;   /* size of file (has to be 64 bits) */
312   ulint pages;                   /* number of pages in file */
313   off_t offset= 0;
314 
315   printf("InnoDB offline file checksum utility.\n");
316 
317   ut_crc32_init();
318 
319   MY_INIT(argv[0]);
320 
321   if (get_options(&argc,&argv))
322     exit(1);
323 
324   if (verbose)
325     my_print_variables(innochecksum_options);
326 
327   /* The file name is not optional */
328   filename = *argv;
329   if (*filename == '\0')
330   {
331     fprintf(stderr, "Error; File name missing\n");
332     return 1;
333   }
334 
335   /* stat the file to get size and page count */
336 #ifdef __WIN__
337   if (_stat64(filename, &st))
338 #else
339   if (stat(filename, &st))
340 #endif
341   {
342     fprintf(stderr, "Error; %s cannot be found\n", filename);
343     return 1;
344   }
345   size= st.st_size;
346 
347   /* Open the file for reading */
348   f= open_file(filename);
349   if (f == NULL) {
350     return 1;
351   }
352 
353   if (!get_page_size(f, buf, &logical_page_size, &physical_page_size))
354   {
355     return 1;
356   }
357 
358   /* This tool currently does not support Compressed tables */
359   if (logical_page_size != physical_page_size)
360   {
361     fprintf(stderr, "Error; This file contains compressed pages\n");
362     return 1;
363   }
364 
365   pages= (ulint) (size / physical_page_size);
366 
367   if (just_count)
368   {
369     if (verbose)
370       printf("Number of pages: ");
371     printf("%lu\n", pages);
372     return 0;
373   }
374   else if (verbose)
375   {
376     printf("file %s = %llu bytes (%lu pages)...\n", filename, size, pages);
377     if (do_one_page)
378       printf("InnoChecksum; checking page %llu\n", do_page);
379     else
380       printf("InnoChecksum; checking pages in range %llu to %llu\n", start_page, use_end_page ? end_page : (pages - 1));
381   }
382 
383   /* seek to the necessary position */
384   if (start_page)
385   {
386 
387     offset= (off_t)start_page * (off_t)physical_page_size;
388 
389 #ifdef __WIN__
390 	if (_fseeki64(f, offset, SEEK_SET)) {
391 #else
392 	if (fseeko(f, offset, SEEK_SET)) {
393 #endif /* __WIN__ */
394 	perror("Error; Unable to seek to necessary offset");
395 	return 1;
396     }
397   }
398 
399   /* main checksumming loop */
400   ct= start_page;
401   lastt= 0;
402   while (!feof(f))
403   {
404     bytes= ulong(fread(buf, 1, physical_page_size, f));
405     if (!bytes && feof(f))
406       return 0;
407 
408     if (ferror(f))
409     {
410       fprintf(stderr, "Error reading %lu bytes", physical_page_size);
411       perror(" ");
412       return 1;
413     }
414     if (bytes != physical_page_size)
415     {
416       fprintf(stderr, "Error; bytes read (%lu) doesn't match page size (%lu)\n", bytes, physical_page_size);
417       return 1;
418     }
419 
420     /* check the "stored log sequence numbers" */
421     logseq= mach_read_from_4(buf + FIL_PAGE_LSN + 4);
422     logseqfield= mach_read_from_4(buf + logical_page_size - FIL_PAGE_END_LSN_OLD_CHKSUM + 4);
423     if (debug)
424       printf("page %lu: log sequence number: first = %lu; second = %lu\n", ct, logseq, logseqfield);
425     if (logseq != logseqfield)
426     {
427       fprintf(stderr, "Fail; page %lu invalid (fails log sequence number check)\n", ct);
428       return 1;
429     }
430 
431     /* check old method of checksumming */
432     oldcsum= buf_calc_page_old_checksum(buf);
433     oldcsumfield= mach_read_from_4(buf + logical_page_size - FIL_PAGE_END_LSN_OLD_CHKSUM);
434     if (debug)
435       printf("page %lu: old style: calculated = %lu; recorded = %lu\n", ct, oldcsum, oldcsumfield);
436     if (oldcsumfield != mach_read_from_4(buf + FIL_PAGE_LSN) && oldcsumfield != oldcsum)
437     {
438       fprintf(stderr, "Fail;  page %lu invalid (fails old style checksum)\n", ct);
439       return 1;
440     }
441 
442     /* now check the new method */
443     csum= buf_calc_page_new_checksum(buf);
444     crc32= buf_calc_page_crc32(buf);
445     csumfield= mach_read_from_4(buf + FIL_PAGE_SPACE_OR_CHKSUM);
446     if (debug)
447       printf("page %lu: new style: calculated = %lu; crc32 = %lu; recorded = %lu\n",
448           ct, csum, crc32, csumfield);
449     if (csumfield != 0 && crc32 != csumfield && csum != csumfield)
450     {
451       fprintf(stderr, "Fail; page %lu invalid (fails innodb and crc32 checksum)\n", ct);
452       return 1;
453     }
454 
455     /* end if this was the last page we were supposed to check */
456     if (use_end_page && (ct >= end_page))
457       return 0;
458 
459     /* do counter increase and progress printing */
460     ct++;
461     if (verbose)
462     {
463       if (ct % 64 == 0)
464       {
465         now= time(0);
466         if (!lastt) lastt= now;
467         if (now - lastt >= 1)
468         {
469           printf("page %lu okay: %.3f%% done\n", (ct - 1), (float) ct / pages * 100);
470           lastt= now;
471         }
472       }
473     }
474   }
475   return 0;
476 }
477 
478