1/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2// vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4:
3#ident "$Id$"
4/*======
5This file is part of PerconaFT.
6
7
8Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved.
9
10 PerconaFT is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License, version 2,
12 as published by the Free Software Foundation.
13
14 PerconaFT is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with PerconaFT. If not, see <http://www.gnu.org/licenses/>.
21
22----------------------------------------
23
24 PerconaFT is free software: you can redistribute it and/or modify
25 it under the terms of the GNU Affero General Public License, version 3,
26 as published by the Free Software Foundation.
27
28 PerconaFT is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU Affero General Public License for more details.
32
33 You should have received a copy of the GNU Affero General Public License
34 along with PerconaFT. If not, see <http://www.gnu.org/licenses/>.
35======= */
36
37#ident "Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved."
38
39#include <my_global.h>
40#include "log-internal.h"
41#include "logger/logcursor.h"
42#include <limits.h>
43#include <unistd.h>
44
45enum lc_direction { LC_FORWARD, LC_BACKWARD, LC_FIRST, LC_LAST };
46
47struct toku_logcursor {
48 char *logdir; // absolute directory name
49 char **logfiles;
50 int n_logfiles;
51 int cur_logfiles_index;
52 FILE *cur_fp;
53 size_t buffer_size;
54 void *buffer;
55 bool is_open;
56 struct log_entry entry;
57 bool entry_valid;
58 LSN cur_lsn;
59 enum lc_direction last_direction;
60};
61
62#define LC_LSN_ERROR (DB_RUNRECOVERY)
63
64void toku_logcursor_print(TOKULOGCURSOR lc) {
65 printf("lc = %p\n", lc);
66 printf(" logdir = %s\n", lc->logdir);
67 printf(" logfiles = %p\n", lc->logfiles);
68 for (int lf=0;lf<lc->n_logfiles;lf++) {
69 printf(" logfile[%d] = %p (%s)\n", lf, lc->logfiles[lf], lc->logfiles[lf]);
70 }
71 printf(" n_logfiles = %d\n", lc->n_logfiles);
72 printf(" cur_logfiles_index = %d\n", lc->cur_logfiles_index);
73 printf(" cur_fp = %p\n", lc->cur_fp);
74 printf(" cur_lsn = %" PRIu64 "\n", lc->cur_lsn.lsn);
75 printf(" last_direction = %d\n", (int) lc->last_direction);
76}
77
78static int lc_close_cur_logfile(TOKULOGCURSOR lc) {
79 int r=0;
80 if ( lc->is_open ) {
81 r = fclose(lc->cur_fp);
82 assert(0==r);
83 lc->is_open = false;
84 }
85 return 0;
86}
87
88static toku_off_t lc_file_len(const char *name) {
89 toku_struct_stat buf;
90 int r = toku_stat(name, &buf, *tokudb_file_data_key);
91 assert(r == 0);
92 return buf.st_size;
93}
94
95// Cat the file and throw away the contents. This brings the file into the file system cache
96// and makes subsequent accesses to it fast. The intention is to speed up backward scans of the
97// file.
98static void lc_catfile(const char *fname, void *buffer, size_t buffer_size) {
99 int fd = open(fname, O_RDONLY);
100 if (fd >= 0) {
101 while (1) {
102 ssize_t r = read(fd, buffer, buffer_size);
103 if ((int)r <= 0)
104 break;
105 }
106 close(fd);
107 }
108}
109
110static int lc_open_logfile(TOKULOGCURSOR lc, int index) {
111 int r=0;
112 assert( !lc->is_open );
113 if( index == -1 || index >= lc->n_logfiles) return DB_NOTFOUND;
114 lc_catfile(lc->logfiles[index], lc->buffer, lc->buffer_size);
115 lc->cur_fp = fopen(lc->logfiles[index], "rb");
116 if ( lc->cur_fp == NULL )
117 return DB_NOTFOUND;
118 r = setvbuf(lc->cur_fp, (char *) lc->buffer, _IOFBF, lc->buffer_size);
119 assert(r == 0);
120 // position fp past header, ignore 0 length file (t:2384)
121 unsigned int version=0;
122 if ( lc_file_len(lc->logfiles[index]) >= 12 ) {
123 r = toku_read_logmagic(lc->cur_fp, &version);
124 if (r!=0)
125 return DB_BADFORMAT;
126 if (version < TOKU_LOG_MIN_SUPPORTED_VERSION || version > TOKU_LOG_VERSION)
127 return DB_BADFORMAT;
128 }
129 // mark as open
130 lc->is_open = true;
131 return r;
132}
133
134static int lc_check_lsn(TOKULOGCURSOR lc, int dir) {
135 int r=0;
136 LSN lsn = toku_log_entry_get_lsn(&(lc->entry));
137 if (((dir == LC_FORWARD) && ( lsn.lsn != lc->cur_lsn.lsn + 1 )) ||
138 ((dir == LC_BACKWARD) && ( lsn.lsn != lc->cur_lsn.lsn - 1 ))) {
139// int index = lc->cur_logfiles_index;
140// fprintf(stderr, "Bad LSN: %d %s direction = %d, lsn.lsn = %" PRIu64 ", cur_lsn.lsn=%" PRIu64 "\n",
141// index, lc->logfiles[index], dir, lsn.lsn, lc->cur_lsn.lsn);
142 if (tokuft_recovery_trace)
143 printf("DB_RUNRECOVERY: %s:%d r=%d\n", __FUNCTION__, __LINE__, 0);
144 return LC_LSN_ERROR;
145 }
146 lc->cur_lsn.lsn = lsn.lsn;
147 return r;
148}
149
150// toku_logcursor_create()
151// - returns a pointer to a logcursor
152
153static int lc_create(TOKULOGCURSOR *lc, const char *log_dir) {
154
155 // malloc a cursor
156 TOKULOGCURSOR cursor = (TOKULOGCURSOR) toku_xmalloc(sizeof(struct toku_logcursor));
157 // find logfiles in logdir
158 cursor->is_open = false;
159 cursor->cur_logfiles_index = 0;
160 cursor->entry_valid = false;
161 cursor->buffer_size = 1<<20; // use a 1MB stream buffer (setvbuf)
162 cursor->buffer = toku_malloc(cursor->buffer_size); // it does not matter if it failes
163 // cursor->logdir must be an absolute path
164 if (toku_os_is_absolute_name(log_dir)) {
165 cursor->logdir = (char *) toku_xmalloc(strlen(log_dir)+1);
166 sprintf(cursor->logdir, "%s", log_dir);
167 } else {
168 char cwdbuf[PATH_MAX];
169 char *cwd = getcwd(cwdbuf, PATH_MAX);
170 assert(cwd);
171 cursor->logdir = (char *) toku_xmalloc(strlen(cwd)+strlen(log_dir)+2);
172 sprintf(cursor->logdir, "%s/%s", cwd, log_dir);
173 }
174 cursor->logfiles = NULL;
175 cursor->n_logfiles = 0;
176 cursor->cur_fp = NULL;
177 cursor->cur_lsn.lsn=0;
178 cursor->last_direction=LC_FIRST;
179
180 *lc = cursor;
181 return 0;
182}
183
184static int lc_fix_bad_logfile(TOKULOGCURSOR lc);
185
186int toku_logcursor_create(TOKULOGCURSOR *lc, const char *log_dir) {
187 TOKULOGCURSOR cursor;
188 int r = lc_create(&cursor, log_dir);
189 if ( r!=0 )
190 return r;
191
192 r = toku_logger_find_logfiles(cursor->logdir, &(cursor->logfiles), &(cursor->n_logfiles));
193 if (r!=0) {
194 toku_logcursor_destroy(&cursor);
195 } else {
196 *lc = cursor;
197 }
198 return r;
199}
200
201int toku_logcursor_create_for_file(TOKULOGCURSOR *lc, const char *log_dir, const char *log_file) {
202 int r = lc_create(lc, log_dir);
203 if ( r!=0 )
204 return r;
205
206 TOKULOGCURSOR cursor = *lc;
207 int fullnamelen = strlen(cursor->logdir) + strlen(log_file) + 3;
208 char *XMALLOC_N(fullnamelen, log_file_fullname);
209 sprintf(log_file_fullname, "%s/%s", cursor->logdir, log_file);
210
211 cursor->n_logfiles=1;
212
213 char **XMALLOC(logfiles);
214 cursor->logfiles = logfiles;
215 cursor->logfiles[0] = log_file_fullname;
216 *lc = cursor;
217 return 0;
218}
219
220int toku_logcursor_destroy(TOKULOGCURSOR *lc) {
221 int r=0;
222 if ( *lc ) {
223 if ( (*lc)->entry_valid ) {
224 toku_log_free_log_entry_resources(&((*lc)->entry));
225 (*lc)->entry_valid = false;
226 }
227 r = lc_close_cur_logfile(*lc);
228 toku_logger_free_logfiles((*lc)->logfiles, (*lc)->n_logfiles);
229 if ( (*lc)->logdir ) toku_free((*lc)->logdir);
230 if ( (*lc)->buffer ) toku_free((*lc)->buffer);
231 toku_free(*lc);
232 *lc = NULL;
233 }
234 return r;
235}
236
237static int lc_log_read(TOKULOGCURSOR lc)
238{
239 int r = toku_log_fread(lc->cur_fp, &(lc->entry));
240 while ( r == EOF ) {
241 // move to next file
242 r = lc_close_cur_logfile(lc);
243 if (r!=0) return r;
244 if ( lc->cur_logfiles_index == lc->n_logfiles-1) return DB_NOTFOUND;
245 lc->cur_logfiles_index++;
246 r = lc_open_logfile(lc, lc->cur_logfiles_index);
247 if (r!=0) return r;
248 r = toku_log_fread(lc->cur_fp, &(lc->entry));
249 }
250 if (r!=0) {
251 toku_log_free_log_entry_resources(&(lc->entry));
252 time_t tnow = time(NULL);
253 if (r==DB_BADFORMAT) {
254 fprintf(stderr, "%.24s PerconaFT bad log format in %s\n", ctime(&tnow), lc->logfiles[lc->cur_logfiles_index]);
255 }
256 else {
257 fprintf(stderr, "%.24s PerconaFT unexpected log format error '%s' in %s\n", ctime(&tnow), strerror(r), lc->logfiles[lc->cur_logfiles_index]);
258 }
259 }
260 return r;
261}
262
263static int lc_log_read_backward(TOKULOGCURSOR lc)
264{
265 int r = toku_log_fread_backward(lc->cur_fp, &(lc->entry));
266 while ( -1 == r) { // if within header length of top of file
267 // move to previous file
268 r = lc_close_cur_logfile(lc);
269 if (r!=0)
270 return r;
271 if ( lc->cur_logfiles_index == 0 )
272 return DB_NOTFOUND;
273 lc->cur_logfiles_index--;
274 r = lc_open_logfile(lc, lc->cur_logfiles_index);
275 if (r!=0)
276 return r;
277 // seek to end
278 r = fseek(lc->cur_fp, 0, SEEK_END);
279 assert(0==r);
280 r = toku_log_fread_backward(lc->cur_fp, &(lc->entry));
281 }
282 if (r!=0) {
283 toku_log_free_log_entry_resources(&(lc->entry));
284 time_t tnow = time(NULL);
285 if (r==DB_BADFORMAT) {
286 fprintf(stderr, "%.24s PerconaFT bad log format in %s\n", ctime(&tnow), lc->logfiles[lc->cur_logfiles_index]);
287 }
288 else {
289 fprintf(stderr, "%.24s PerconaFT uUnexpected log format error '%s' in %s\n", ctime(&tnow), strerror(r), lc->logfiles[lc->cur_logfiles_index]);
290 }
291 }
292 return r;
293}
294
295int toku_logcursor_next(TOKULOGCURSOR lc, struct log_entry **le) {
296 int r=0;
297 if ( lc->entry_valid ) {
298 toku_log_free_log_entry_resources(&(lc->entry));
299 lc->entry_valid = false;
300 if (lc->last_direction == LC_BACKWARD) {
301 struct log_entry junk;
302 r = toku_log_fread(lc->cur_fp, &junk);
303 assert(r == 0);
304 toku_log_free_log_entry_resources(&junk);
305 }
306 } else {
307 r = toku_logcursor_first(lc, le);
308 return r;
309 }
310 // read the entry
311 r = lc_log_read(lc);
312 if (r!=0) return r;
313 r = lc_check_lsn(lc, LC_FORWARD);
314 if (r!=0) return r;
315 lc->last_direction = LC_FORWARD;
316 lc->entry_valid = true;
317 *le = &(lc->entry);
318 return r;
319}
320
321int toku_logcursor_prev(TOKULOGCURSOR lc, struct log_entry **le) {
322 int r=0;
323 if ( lc->entry_valid ) {
324 toku_log_free_log_entry_resources(&(lc->entry));
325 lc->entry_valid = false;
326 if (lc->last_direction == LC_FORWARD) {
327 struct log_entry junk;
328 r = toku_log_fread_backward(lc->cur_fp, &junk);
329 assert(r == 0);
330 toku_log_free_log_entry_resources(&junk);
331 }
332 } else {
333 r = toku_logcursor_last(lc, le);
334 return r;
335 }
336 // read the entry
337 r = lc_log_read_backward(lc);
338 if (r!=0) return r;
339 r = lc_check_lsn(lc, LC_BACKWARD);
340 if (r!=0) return r;
341 lc->last_direction = LC_BACKWARD;
342 lc->entry_valid = true;
343 *le = &(lc->entry);
344 return r;
345}
346
347int toku_logcursor_first(TOKULOGCURSOR lc, struct log_entry **le) {
348 int r=0;
349 if ( lc->entry_valid ) {
350 toku_log_free_log_entry_resources(&(lc->entry));
351 lc->entry_valid = false;
352 }
353 // close any but the first log file
354 if ( lc->cur_logfiles_index != 0 ) {
355 lc_close_cur_logfile(lc);
356 }
357 // open first log file if needed
358 if ( !lc->is_open ) {
359 r = lc_open_logfile(lc, 0);
360 if (r!=0)
361 return r;
362 lc->cur_logfiles_index = 0;
363 }
364 // read the entry
365 r = lc_log_read(lc);
366 if (r!=0) return r;
367
368 r = lc_check_lsn(lc, LC_FIRST);
369 if (r!=0) return r;
370 lc->last_direction = LC_FIRST;
371 lc->entry_valid = true;
372 *le = &(lc->entry);
373 return r;
374}
375
376//get last entry in the logfile specified by logcursor
377int toku_logcursor_last(TOKULOGCURSOR lc, struct log_entry **le) {
378 int r=0;
379 if ( lc->entry_valid ) {
380 toku_log_free_log_entry_resources(&(lc->entry));
381 lc->entry_valid = false;
382 }
383 // close any but last log file
384 if ( lc->cur_logfiles_index != lc->n_logfiles-1 ) {
385 lc_close_cur_logfile(lc);
386 }
387 // open last log file if needed
388 if ( !lc->is_open ) {
389 r = lc_open_logfile(lc, lc->n_logfiles-1);
390 if (r!=0)
391 return r;
392 lc->cur_logfiles_index = lc->n_logfiles-1;
393 }
394 while (1) {
395 // seek to end
396 r = fseek(lc->cur_fp, 0, SEEK_END); assert(r==0);
397 // read backward
398 r = toku_log_fread_backward(lc->cur_fp, &(lc->entry));
399 if (r==0) // got a good entry
400 break;
401 if (r>0) {
402 toku_log_free_log_entry_resources(&(lc->entry));
403 // got an error,
404 // probably a corrupted last log entry due to a crash
405 // try scanning forward from the beginning to find the last good entry
406 time_t tnow = time(NULL);
407 fprintf(stderr, "%.24s PerconaFT recovery repairing log\n", ctime(&tnow));
408 r = lc_fix_bad_logfile(lc);
409 if ( r != 0 ) {
410 fprintf(stderr, "%.24s PerconaFT recovery repair unsuccessful\n", ctime(&tnow));
411 return DB_BADFORMAT;
412 }
413 // try reading again
414 r = toku_log_fread_backward(lc->cur_fp, &(lc->entry));
415 if (r==0) // got a good entry
416 break;
417 }
418 // move to previous file
419 r = lc_close_cur_logfile(lc);
420 if (r!=0)
421 return r;
422 if ( lc->cur_logfiles_index == 0 )
423 return DB_NOTFOUND;
424 lc->cur_logfiles_index--;
425 r = lc_open_logfile(lc, lc->cur_logfiles_index);
426 if (r!=0)
427 return r;
428 }
429 r = lc_check_lsn(lc, LC_LAST);
430 if (r!=0)
431 return r;
432 lc->last_direction = LC_LAST;
433 lc->entry_valid = true;
434 *le = &(lc->entry);
435 return r;
436}
437
438// return 0 if log exists, ENOENT if no log
439int
440toku_logcursor_log_exists(const TOKULOGCURSOR lc) {
441 int r;
442
443 if (lc->n_logfiles)
444 r = 0;
445 else
446 r = ENOENT;
447
448 return r;
449}
450
451// fix a logfile with a bad last entry
452// - return with fp pointing to end-of-file so that toku_logcursor_last can be retried
453static int lc_fix_bad_logfile(TOKULOGCURSOR lc) {
454 struct log_entry le;
455 unsigned int version=0;
456 int r = 0;
457
458 r = fseek(lc->cur_fp, 0, SEEK_SET);
459 if ( r!=0 )
460 return r;
461 r = toku_read_logmagic(lc->cur_fp, &version);
462 if ( r!=0 )
463 return r;
464 if (version != TOKU_LOG_VERSION)
465 return -1;
466
467 toku_off_t last_good_pos;
468 last_good_pos = ftello(lc->cur_fp);
469 while (1) {
470 // initialize le
471 // - reading incomplete entries can result in fields that cannot be freed
472 memset(&le, 0, sizeof(le));
473 r = toku_log_fread(lc->cur_fp, &le);
474 toku_log_free_log_entry_resources(&le);
475 if ( r!=0 )
476 break;
477 last_good_pos = ftello(lc->cur_fp);
478 }
479 // now have position of last good entry
480 // 1) close the file
481 // 2) truncate the file to remove the error
482 // 3) reopen the file
483 // 4) set the pos to last
484 r = lc_close_cur_logfile(lc);
485 if ( r!=0 )
486 return r;
487 r = truncate(lc->logfiles[lc->n_logfiles - 1], last_good_pos);
488 if ( r!=0 )
489 return r;
490 r = lc_open_logfile(lc, lc->n_logfiles-1);
491 if ( r!=0 )
492 return r;
493 r = fseek(lc->cur_fp, 0, SEEK_END);
494 if ( r!=0 )
495 return r;
496 return 0;
497}
498