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