mysqlbinlog.cc 47.6 KB
Newer Older
monty@mysql.com's avatar
monty@mysql.com committed
1
/* Copyright (C) 2001-2004 MySQL AB
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
2

bk@work.mysql.com's avatar
bk@work.mysql.com committed
3 4 5 6
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
7

bk@work.mysql.com's avatar
bk@work.mysql.com committed
8 9 10 11
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
12

bk@work.mysql.com's avatar
bk@work.mysql.com committed
13 14 15 16
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
/* 

   TODO: print the catalog (some USE catalog.db ????).

   Standalone program to read a MySQL binary log (or relay log);
   can read files produced by 3.23, 4.x, 5.0 servers. 

   Can read binlogs from 3.23/4.x/5.0 and relay logs from 4.x/5.0.
   Should be able to read any file of these categories, even with
   --start-position.
   An important fact: the Format_desc event of the log is at most the 3rd event
   of the log; if it is the 3rd then there is this combination:
   Format_desc_of_slave, Rotate_of_master, Format_desc_of_master.
*/

bk@work.mysql.com's avatar
bk@work.mysql.com committed
32 33
#define MYSQL_CLIENT
#undef MYSQL_SERVER
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
34
#include "client_priv.h"
35
#include <my_time.h>
36 37
/* That one is necessary for defines of OPTION_NO_FOREIGN_KEY_CHECKS etc */
#include "mysql_priv.h" 
38
#include "log_event.h"
39
#include "sql_common.h"
bk@work.mysql.com's avatar
bk@work.mysql.com committed
40

41
#define BIN_LOG_HEADER_SIZE	4
monty@mashka.mysql.fi's avatar
monty@mashka.mysql.fi committed
42
#define PROBE_HEADER_LEN	(EVENT_LEN_OFFSET+4)
43

44

bk@work.mysql.com's avatar
bk@work.mysql.com committed
45 46
#define CLIENT_CAPABILITIES	(CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_LOCAL_FILES)

47
char server_version[SERVER_VERSION_LENGTH];
48
ulong server_id = 0;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
49 50 51 52

// needed by net_serv.c
ulong bytes_sent = 0L, bytes_received = 0L;
ulong mysqld_net_retry_count = 10L;
53
ulong open_files_limit;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
54
uint test_flags = 0; 
55
static uint opt_protocol= 0;
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
56
static FILE *result_file;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
57 58 59 60

#ifndef DBUG_OFF
static const char* default_dbug_option = "d:t:o,/tmp/mysqlbinlog.trace";
#endif
monty@narttu.mysql.fi's avatar
monty@narttu.mysql.fi committed
61
static const char *load_default_groups[]= { "mysqlbinlog","client",0 };
bk@work.mysql.com's avatar
bk@work.mysql.com committed
62

jcole@mugatu.spaceapes.com's avatar
jcole@mugatu.spaceapes.com committed
63
void sql_print_error(const char *format, ...);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
64

65
static bool one_database=0, to_last_remote_log= 0, disable_log_bin= 0;
66
static bool opt_hexdump= 0;
67
static bool opt_base64_output= 0;
68 69
static const char* database= 0;
static my_bool force_opt= 0, short_form= 0, remote_opt= 0;
70
static ulonglong offset = 0;
71
static const char* host = 0;
72
static int port= 0;
73
static const char* sock= 0;
74
static const char* user = 0;
75
static char* pass = 0;
76
static char *charset= 0;
77 78 79 80 81 82 83 84

static ulonglong start_position, stop_position;
#define start_position_mot ((my_off_t)start_position)
#define stop_position_mot  ((my_off_t)stop_position)

static char *start_datetime_str, *stop_datetime_str;
static my_time_t start_datetime= 0, stop_datetime= MY_TIME_T_MAX;
static ulonglong rec_count= 0;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
85 86
static short binlog_flags = 0; 
static MYSQL* mysql = NULL;
87
static const char* dirname_for_local_load= 0;
88
static bool stop_passed= 0;
89

90 91 92 93 94 95 96 97
/*
  check_header() will set the pointer below.
  Why do we need here a pointer on an event instead of an event ?
  This is because the event will be created (alloced) in read_log_event()
  (which returns a pointer) in check_header().
*/
Format_description_log_event* description_event; 

monty@mysql.com's avatar
monty@mysql.com committed
98 99 100 101
static int dump_local_log_entries(const char* logname);
static int dump_remote_log_entries(const char* logname);
static int dump_log_entries(const char* logname);
static int dump_remote_file(NET* net, const char* fname);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
102 103 104
static void die(const char* fmt, ...);
static MYSQL* safe_connect();

monty@mysql.com's avatar
monty@mysql.com committed
105

106 107
class Load_log_processor
{
108
  char target_dir_name[FN_REFLEN];
109
  int target_dir_name_len;
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125

  /*
    When we see first event corresponding to some LOAD DATA statement in
    binlog, we create temporary file to store data to be loaded.
    We add name of this file to file_names array using its file_id as index.
    If we have Create_file event (i.e. we have binary log in pre-5.0.3
    format) we also store save event object to be able which is needed to
    emit LOAD DATA statement when we will meet Exec_load_data event.
    If we have Begin_load_query event we simply store 0 in
    File_name_record::event field.
  */
  struct File_name_record
  {
    char *fname;
    Create_file_log_event *event;
  };
126 127
  DYNAMIC_ARRAY file_names;

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
  /*
    Looking for new uniquie filename that doesn't exist yet by 
    adding postfix -%x

    SYNOPSIS 
       create_unique_file()
       
       filename       buffer for filename
       file_name_end  tail of buffer that should be changed
                      should point to a memory enough to printf("-%x",..)

    RETURN VALUES
      values less than 0      - can't find new filename
      values great or equal 0 - created file with found filename
  */
  File create_unique_file(char *filename, char *file_name_end)
144
    {
145 146 147 148 149 150 151 152 153 154
      File res;
      /* If we have to try more than 1000 times, something is seriously wrong */
      for (uint version= 0; version<1000; version++)
      {
	sprintf(file_name_end,"-%x",version);
	if ((res= my_create(filename,0,
			    O_CREAT|O_EXCL|O_BINARY|O_WRONLY,MYF(0)))!=-1)
	  return res;
      }
      return -1;
155 156 157
    }

public:
158
  Load_log_processor() {}
159
  ~Load_log_processor()
160 161 162 163 164 165 166
  {
    destroy();
    delete_dynamic(&file_names);
  }

  int init()
  {
167
    return init_dynamic_array(&file_names, sizeof(File_name_record),
168 169
			      100,100 CALLER_INFO);
  }
170

171
  void init_by_dir_name(const char *dir)
172
    {
173 174
      target_dir_name_len= (convert_dirname(target_dir_name, dir, NullS) -
			    target_dir_name);
175 176 177
    }
  void init_by_cur_dir()
    {
178 179 180
      if (my_getwd(target_dir_name,sizeof(target_dir_name),MYF(MY_WME)))
	exit(1);
      target_dir_name_len= strlen(target_dir_name);
181 182 183
    }
  void destroy()
    {
184 185
      File_name_record *ptr= (File_name_record *)file_names.buffer;
      File_name_record *end= ptr + file_names.elements;
186 187
      for (; ptr<end; ptr++)
      {
188
	if (ptr->fname)
189
	{
190 191 192
          my_free(ptr->fname, MYF(MY_WME));
          delete ptr->event;
          bzero((char *)ptr, sizeof(File_name_record));
193 194 195
	}
      }
    }
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213

  /*
    Obtain Create_file event for LOAD DATA statement by its file_id.

    SYNOPSIS
      grab_event()
        file_id - file_id identifiying LOAD DATA statement

    DESCRIPTION
      Checks whenever we have already seen Create_file event for this file_id.
      If yes then returns pointer to it and removes it from array describing
      active temporary files. Since this moment caller is responsible for
      freeing memory occupied by this event and associated file name.

    RETURN VALUES
      Pointer to Create_file event or 0 if there was no such event
      with this file_id.
  */
214 215
  Create_file_log_event *grab_event(uint file_id)
    {
216 217 218
      File_name_record *ptr;
      Create_file_log_event *res;

219 220
      if (file_id >= file_names.elements)
        return 0;
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
      ptr= dynamic_element(&file_names, file_id, File_name_record*);
      if ((res= ptr->event))
        bzero((char *)ptr, sizeof(File_name_record));
      return res;
    }

  /*
    Obtain file name of temporary file for LOAD DATA statement by its file_id.

    SYNOPSIS
      grab_fname()
        file_id - file_id identifiying LOAD DATA statement

    DESCRIPTION
      Checks whenever we have already seen Begin_load_query event for this
      file_id. If yes then returns file name of corresponding temporary file.
      Removes record about this file from the array of active temporary files.
      Since this moment caller is responsible for freeing memory occupied by
      this name.

    RETURN VALUES
      String with name of temporary file or 0 if we have not seen Begin_load_query
      event with this file_id.
  */
  char *grab_fname(uint file_id)
    {
      File_name_record *ptr;
      char *res= 0;

      if (file_id >= file_names.elements)
        return 0;
      ptr= dynamic_element(&file_names, file_id, File_name_record*);
      if (!ptr->event)
      {
        res= ptr->fname;
        bzero((char *)ptr, sizeof(File_name_record));
      }
258 259
      return res;
    }
260
  int process(Create_file_log_event *ce);
261
  int process(Begin_load_query_log_event *ce);
monty@mysql.com's avatar
monty@mysql.com committed
262
  int process(Append_block_log_event *ae);
263 264 265
  File prepare_new_file_for_old_format(Load_log_event *le, char *filename);
  int load_old_format_file(NET* net, const char *server_fname,
			   uint server_fname_len, File file);
266 267 268
  int process_first_event(const char *bname, uint blen, const char *block,
                          uint block_len, uint file_id,
                          Create_file_log_event *ce);
vva@eagle.mysql.r18.ru's avatar
vva@eagle.mysql.r18.ru committed
269 270
};

monty@mysql.com's avatar
monty@mysql.com committed
271 272


273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
File Load_log_processor::prepare_new_file_for_old_format(Load_log_event *le,
							 char *filename)
{
  uint len;
  char *tail;
  File file;
  
  fn_format(filename, le->fname, target_dir_name, "", 1);
  len= strlen(filename);
  tail= filename + len;
  
  if ((file= create_unique_file(filename,tail)) < 0)
  {
    sql_print_error("Could not construct local filename %s",filename);
    return -1;
  }
  
  le->set_fname_outside_temp_buf(filename,len+strlen(tail));
  
  return file;
}
294

monty@mysql.com's avatar
monty@mysql.com committed
295

296 297 298 299 300 301 302 303 304 305 306 307 308 309
int Load_log_processor::load_old_format_file(NET* net, const char*server_fname,
					     uint server_fname_len, File file)
{
  char buf[FN_REFLEN+1];
  buf[0] = 0;
  memcpy(buf + 1, server_fname, server_fname_len + 1);
  if (my_net_write(net, buf, server_fname_len +2) || net_flush(net))
  {
    sql_print_error("Failed  requesting the remote dump of %s", server_fname);
    return -1;
  }
  
  for (;;)
  {
310
    ulong packet_len = my_net_read(net);
311
    if (packet_len == 0)
312
    {
313
      if (my_net_write(net, "", 0) || net_flush(net))
314
      {
315 316
	sql_print_error("Failed sending the ack packet");
	return -1;
317
      }
318 319 320 321 322 323
      /*
	we just need to send something, as the server will read but
	not examine the packet - this is because mysql_load() sends 
	an OK when it is done
      */
      break;
324
    }
325 326 327 328 329 330 331
    else if (packet_len == packet_error)
    {
      sql_print_error("Failed reading a packet during the dump of %s ", 
		      server_fname);
      return -1;
    }
    
332 333 334 335 336 337 338
    if (packet_len > UINT_MAX)
    {
      sql_print_error("Illegal length of packet read from net");
      return -1;
    }
    if (my_write(file, (byte*) net->read_pos, 
		 (uint) packet_len, MYF(MY_WME|MY_NABP)))
339 340 341 342 343
      return -1;
  }
  
  return 0;
}
344

345

346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
/*
  Process first event in the sequence of events representing LOAD DATA
  statement.

  SYNOPSIS
    process_first_event()
      bname     - base name for temporary file to be created
      blen      - base name length
      block     - first block of data to be loaded
      block_len - first block length
      file_id   - identifies LOAD DATA statement
      ce        - pointer to Create_file event object if we are processing
                  this type of event.

  DESCRIPTION
    Creates temporary file to be used in LOAD DATA and writes first block of
    data to it. Registers its file name (and optional Create_file event)
    in the array of active temporary files.

  RETURN VALUES
    0     - success
    non-0 - error
*/

int Load_log_processor::process_first_event(const char *bname, uint blen,
                                            const char *block, uint block_len,
                                            uint file_id,
                                            Create_file_log_event *ce)
374 375
{
  uint full_len= target_dir_name_len + blen + 9 + 9 + 1;
monty@mysql.com's avatar
monty@mysql.com committed
376
  int error= 0;
377 378
  char *fname, *ptr;
  File file;
379 380
  File_name_record rec;
  DBUG_ENTER("Load_log_processor::process_first_event");
381

monty@mysql.com's avatar
monty@mysql.com committed
382
  if (!(fname= my_malloc(full_len,MYF(MY_WME))))
monty@mysql.com's avatar
monty@mysql.com committed
383
    DBUG_RETURN(-1);
384

385 386
  memcpy(fname, target_dir_name, target_dir_name_len);
  ptr= fname + target_dir_name_len;
387 388
  memcpy(ptr,bname,blen);
  ptr+= blen;
389
  ptr+= my_sprintf(ptr, (ptr, "-%x", file_id));
390

391 392 393 394
  if ((file= create_unique_file(fname,ptr)) < 0)
  {
    sql_print_error("Could not construct local filename %s%s",
		    target_dir_name,bname);
monty@mysql.com's avatar
monty@mysql.com committed
395
    DBUG_RETURN(-1);
396
  }
397

398 399 400 401 402 403 404 405 406 407 408 409 410 411
  rec.fname= fname;
  rec.event= ce;

  if (set_dynamic(&file_names, (gptr)&rec, file_id))
  {
    sql_print_error("Could not construct local filename %s%s",
		    target_dir_name, bname);
    DBUG_RETURN(-1);
  }

  if (ce)
    ce->set_fname_outside_temp_buf(fname, strlen(fname));

  if (my_write(file, (byte*)block, block_len, MYF(MY_WME|MY_NABP)))
monty@mysql.com's avatar
monty@mysql.com committed
412
    error= -1;
monty@mysql.com's avatar
monty@mysql.com committed
413
  if (my_close(file, MYF(MY_WME)))
monty@mysql.com's avatar
monty@mysql.com committed
414
    error= -1;
monty@mysql.com's avatar
monty@mysql.com committed
415
  DBUG_RETURN(error);
416 417
}

monty@mysql.com's avatar
monty@mysql.com committed
418

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
int Load_log_processor::process(Create_file_log_event *ce)
{
  const char *bname= ce->fname + dirname_length(ce->fname);
  uint blen= ce->fname_len - (bname-ce->fname);

  return process_first_event(bname, blen, ce->block, ce->block_len,
                             ce->file_id, ce);
}


int Load_log_processor::process(Begin_load_query_log_event *blqe)
{
  return process_first_event("SQL_LOAD_MB", 11, blqe->block, blqe->block_len,
                             blqe->file_id, 0);
}


monty@mysql.com's avatar
monty@mysql.com committed
436 437
int Load_log_processor::process(Append_block_log_event *ae)
{
monty@mysql.com's avatar
monty@mysql.com committed
438
  DBUG_ENTER("Load_log_processor::process");
439 440 441
  const char* fname= ((ae->file_id < file_names.elements) ?
                       dynamic_element(&file_names, ae->file_id,
                                       File_name_record*)->fname : 0);
442

443
  if (fname)
monty@mysql.com's avatar
monty@mysql.com committed
444 445 446
  {
    File file;
    int error= 0;
447
    if (((file= my_open(fname,
monty@mysql.com's avatar
monty@mysql.com committed
448
			O_APPEND|O_BINARY|O_WRONLY,MYF(MY_WME))) < 0))
monty@mysql.com's avatar
monty@mysql.com committed
449
      DBUG_RETURN(-1);
monty@mysql.com's avatar
monty@mysql.com committed
450 451 452 453
    if (my_write(file,(byte*)ae->block,ae->block_len,MYF(MY_WME|MY_NABP)))
      error= -1;
    if (my_close(file,MYF(MY_WME)))
      error= -1;
monty@mysql.com's avatar
monty@mysql.com committed
454
    DBUG_RETURN(error);
monty@mysql.com's avatar
monty@mysql.com committed
455 456 457 458
  }

  /*
    There is no Create_file event (a bad binlog or a big
459 460
    --start-position). Assuming it's a big --start-position, we just do
    nothing and print a warning.
461
  */
monty@mysql.com's avatar
monty@mysql.com committed
462 463
  fprintf(stderr,"Warning: ignoring Append_block as there is no \
Create_file event for file_id: %u\n",ae->file_id);
monty@mysql.com's avatar
monty@mysql.com committed
464
  DBUG_RETURN(-1);
monty@mysql.com's avatar
monty@mysql.com committed
465 466 467
}


468 469
Load_log_processor load_processor;

470

471 472 473 474 475 476 477 478
static bool check_database(const char *log_dbname)
{
  return one_database &&
         (log_dbname != NULL) &&
         strcmp(log_dbname, database);
}


479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502

static int
write_event_header_and_base64(Log_event *ev, FILE *result_file,
                              PRINT_EVENT_INFO *print_event_info)
{
  DBUG_ENTER("write_event_header_and_base64");
  /* Write header and base64 output to cache */
  IO_CACHE result_cache;
  if (init_io_cache(&result_cache, -1, 0, WRITE_CACHE, 0L, FALSE,
                    MYF(MY_WME | MY_NABP)))
  {
    return 1;
  }

  ev->print_header(&result_cache, print_event_info, FALSE);
  ev->print_base64(&result_cache, print_event_info, FALSE);

  /* Read data from cache and write to result file */
  my_b_copy_to_file(&result_cache, result_file);
  end_io_cache(&result_cache);
  DBUG_RETURN(0);
}


503
/*
504 505 506 507 508
  Process an event

  SYNOPSIS
    process_event()

509 510 511 512
  RETURN
    0           ok and continue
    1           error and terminate
    -1          ok and terminate
513
 
514 515 516
  TODO
    This function returns 0 even in some error cases. This should be changed.
*/
517 518 519



520
int process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev,
521
                  my_off_t pos)
522 523
{
  char ll_buff[21];
524
  Log_event_type ev_type= ev->get_type_code();
monty@mysql.com's avatar
monty@mysql.com committed
525
  DBUG_ENTER("process_event");
526
  print_event_info->short_form= short_form;
monty@mysql.com's avatar
monty@mysql.com committed
527

528 529 530 531
  /*
    Format events are not concerned by --offset and such, we always need to
    read them to be able to process the wanted events.
  */
532
  if ((rec_count >= offset) &&
533 534
      ((my_time_t)(ev->when) >= start_datetime) ||
      (ev_type == FORMAT_DESCRIPTION_EVENT))
535
  {
536 537 538 539 540 541 542 543 544 545
    if (ev_type != FORMAT_DESCRIPTION_EVENT)
    {
      /*
        We have found an event after start_datetime, from now on print
        everything (in case the binlog has timestamps increasing and
        decreasing, we do this to avoid cutting the middle).
      */
      start_datetime= 0;
      offset= 0; // print everything and protect against cycling rec_count
    }
546 547 548
    if (server_id && (server_id != ev->server_id)) {
      DBUG_RETURN(0);
    }
549 550 551 552 553 554
    if (((my_time_t)(ev->when) >= stop_datetime)
        || (pos >= stop_position_mot))
    {
      stop_passed= 1; // skip all next binlogs
      DBUG_RETURN(-1);
    }
555 556
    if (!short_form)
      fprintf(result_file, "# at %s\n",llstr(pos,ll_buff));
557

lars@mysql.com's avatar
lars@mysql.com committed
558
    if (!opt_hexdump)
559
      print_event_info->hexdump_from= 0; /* Disabled */
lars@mysql.com's avatar
lars@mysql.com committed
560
    else
561
      print_event_info->hexdump_from= pos;
562

563 564
    print_event_info->base64_output= opt_base64_output;

565 566
    DBUG_PRINT("debug", ("event_type: %s", ev->get_type_str()));

567
    switch (ev_type) {
568
    case QUERY_EVENT:
569 570
      if (check_database(((Query_log_event*)ev)->db))
        goto end;
571
      if (opt_base64_output)
572
        write_event_header_and_base64(ev, result_file, print_event_info);
573 574
      else
        ev->print(result_file, print_event_info);
575
      break;
576

577
    case CREATE_FILE_EVENT:
578
    {
579
      Create_file_log_event* ce= (Create_file_log_event*)ev;
580 581 582 583 584 585 586 587
      /*
        We test if this event has to be ignored. If yes, we don't save
        this event; this will have the good side-effect of ignoring all
        related Append_block and Exec_load.
        Note that Load event from 3.23 is not tested.
      */
      if (check_database(ce->db))
        goto end;                // Next event
588 589 590 591 592 593 594
      /*
	We print the event, but with a leading '#': this is just to inform 
	the user of the original command; the command we want to execute 
	will be a derivation of this original command (we will change the 
	filename and use LOCAL), prepared in the 'case EXEC_LOAD_EVENT' 
	below.
      */
595 596
      if (opt_base64_output)
      {
597
        write_event_header_and_base64(ce, result_file, print_event_info);
598 599 600
      }
      else
        ce->print(result_file, print_event_info, TRUE);
601

602 603
      // If this binlog is not 3.23 ; why this test??
      if (description_event->binlog_version >= 3)
604
      {
monty@mysql.com's avatar
monty@mysql.com committed
605 606
	if (load_processor.process(ce))
	  break;				// Error
607 608 609 610 611
	ev= 0;
      }
      break;
    }
    case APPEND_BLOCK_EVENT:
612
      ev->print(result_file, print_event_info);
monty@mysql.com's avatar
monty@mysql.com committed
613 614
      if (load_processor.process((Append_block_log_event*) ev))
	break;					// Error
615 616 617
      break;
    case EXEC_LOAD_EVENT:
    {
618
      ev->print(result_file, print_event_info);
619 620 621 622
      Execute_load_log_event *exv= (Execute_load_log_event*)ev;
      Create_file_log_event *ce= load_processor.grab_event(exv->file_id);
      /*
	if ce is 0, it probably means that we have not seen the Create_file
623
	event (a bad binlog, or most probably --start-position is after the
624 625 626 627
	Create_file event). Print a warning comment.
      */
      if (ce)
      {
628
	ce->print(result_file, print_event_info, TRUE);
629 630 631 632 633 634 635 636
	my_free((char*)ce->fname,MYF(MY_WME));
	delete ce;
      }
      else
	fprintf(stderr,"Warning: ignoring Exec_load as there is no \
Create_file event for file_id: %u\n",exv->file_id);
      break;
    }
637 638 639
    case FORMAT_DESCRIPTION_EVENT:
      delete description_event;
      description_event= (Format_description_log_event*) ev;
640 641
      print_event_info->common_header_len= description_event->common_header_len;
      ev->print(result_file, print_event_info);
642 643 644 645 646 647 648 649
      /*
        We don't want this event to be deleted now, so let's hide it (I
        (Guilhem) should later see if this triggers a non-serious Valgrind
        error). Not serious error, because we will free description_event
        later.
      */
      ev= 0;
      break;
650
    case BEGIN_LOAD_QUERY_EVENT:
651
      ev->print(result_file, print_event_info);
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
      load_processor.process((Begin_load_query_log_event*) ev);
      break;
    case EXECUTE_LOAD_QUERY_EVENT:
    {
      Execute_load_query_log_event *exlq= (Execute_load_query_log_event*)ev;
      char *fname= load_processor.grab_fname(exlq->file_id);

      if (check_database(exlq->db))
      {
        if (fname)
          my_free(fname, MYF(MY_WME));
        goto end;
      }

      if (fname)
      {
668
	exlq->print(result_file, print_event_info, fname);
669 670 671 672 673 674 675
	my_free(fname, MYF(MY_WME));
      }
      else
	fprintf(stderr,"Warning: ignoring Execute_load_query as there is no \
Begin_load_query event for file_id: %u\n", exlq->file_id);
      break;
    }
676
    default:
677
      ev->print(result_file, print_event_info);
678 679
    }
  }
monty@mysql.com's avatar
monty@mysql.com committed
680 681

end:
682
  rec_count++;
683 684
  if (ev)
    delete ev;
monty@mysql.com's avatar
monty@mysql.com committed
685
  DBUG_RETURN(0);
686 687 688
}


689 690
static struct my_option my_long_options[] =
{
paul@snake-hub.snake.net's avatar
paul@snake-hub.snake.net committed
691 692
  {"help", '?', "Display this help and exit.",
   0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
693
#ifdef __NETWARE__
694
  {"autoclose", OPT_AUTO_CLOSE, "Auto close the screen on exit for Netware.",
695 696
   0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
#endif
697 698 699 700 701 702
  {"base64-output", OPT_BASE64_OUTPUT,
   "Print all binlog entries using base64 encoding. "
   "This is for debugging only. Logs produced using this option "
   "should not be applied on production systems.",
   (gptr*) &opt_base64_output, (gptr*) &opt_base64_output, 0, GET_BOOL,
   NO_ARG, 0, 0, 0, 0, 0, 0},
703 704 705 706 707 708 709 710 711
  /*
    mysqlbinlog needs charsets knowledge, to be able to convert a charset
    number found in binlog to a charset name (to be able to print things
    like this:
    SET @`a`:=_cp850 0x4DFC6C6C6572 COLLATE `cp850_general_ci`;
  */
  {"character-sets-dir", OPT_CHARSETS_DIR,
   "Directory where character sets are.", (gptr*) &charsets_dir,
   (gptr*) &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
paul@snake-hub.snake.net's avatar
paul@snake-hub.snake.net committed
712 713 714
  {"database", 'd', "List entries for just this database (local log only).",
   (gptr*) &database, (gptr*) &database, 0, GET_STR_ALLOC, REQUIRED_ARG,
   0, 0, 0, 0, 0, 0},
715 716 717 718
#ifndef DBUG_OFF
  {"debug", '#', "Output debug log.", (gptr*) &default_dbug_option,
   (gptr*) &default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
#endif
719 720 721 722 723 724 725
  {"disable-log-bin", 'D', "Disable binary log. This is useful, if you "
    "enabled --to-last-log and are sending the output to the same MySQL server. "
    "This way you could avoid an endless loop. You would also like to use it "
    "when restoring after a crash to avoid duplication of the statements you "
    "already have. NOTE: you will need a SUPER privilege to use this option.",
   (gptr*) &disable_log_bin, (gptr*) &disable_log_bin, 0, GET_BOOL,
   NO_ARG, 0, 0, 0, 0, 0, 0},
726
  {"force-read", 'f', "Force reading unknown binlog events.",
727 728
   (gptr*) &force_opt, (gptr*) &force_opt, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0,
   0, 0},
729 730 731
  {"hexdump", 'H', "Augment output with hexadecimal and ASCII event dump.",
   (gptr*) &opt_hexdump, (gptr*) &opt_hexdump, 0, GET_BOOL, NO_ARG,
   0, 0, 0, 0, 0, 0},
732
  {"host", 'h', "Get the binlog from server.", (gptr*) &host, (gptr*) &host,
733
   0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
paul@snake-hub.snake.net's avatar
paul@snake-hub.snake.net committed
734 735 736
  {"local-load", 'l', "Prepare local temporary files for LOAD DATA INFILE in the specified directory.",
   (gptr*) &dirname_for_local_load, (gptr*) &dirname_for_local_load, 0,
   GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
737
  {"offset", 'o', "Skip the first N entries.", (gptr*) &offset, (gptr*) &offset,
738
   0, GET_ULL, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
739
  {"password", 'p', "Password to connect to remote server.",
740
   0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
741
  {"port", 'P', "Use port to connect to the remote server.",
742
   (gptr*) &port, (gptr*) &port, 0, GET_INT, REQUIRED_ARG, 0, 0, 0,
743
   0, 0, 0},
744 745 746 747 748
  {"position", 'j', "Deprecated. Use --start-position instead.",
   (gptr*) &start_position, (gptr*) &start_position, 0, GET_ULL,
   REQUIRED_ARG, BIN_LOG_HEADER_SIZE, BIN_LOG_HEADER_SIZE,
   /* COM_BINLOG_DUMP accepts only 4 bytes for the position */
   (ulonglong)(~(uint32)0), 0, 0, 0},
749 750 751
  {"protocol", OPT_MYSQL_PROTOCOL,
   "The protocol of connection (tcp,socket,pipe,memory).",
   0, 0, 0, GET_STR,  REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
752
  {"read-from-remote-server", 'R', "Read binary logs from a MySQL server",
753 754
   (gptr*) &remote_opt, (gptr*) &remote_opt, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0,
   0, 0},
paul@snake-hub.snake.net's avatar
paul@snake-hub.snake.net committed
755 756 757 758 759 760
  {"result-file", 'r', "Direct output to a given file.", 0, 0, 0, GET_STR,
   REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"server-id", OPT_SERVER_ID,
   "Extract only binlog entries created by the server having the given id.",
   (gptr*) &server_id, (gptr*) &server_id, 0, GET_ULONG,
   REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
761 762 763
  {"set-charset", OPT_SET_CHARSET,
   "Add 'SET NAMES character_set' to the output.", (gptr*) &charset,
   (gptr*) &charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
764
  {"short-form", 's', "Just show the queries, no extra info.",
765 766
   (gptr*) &short_form, (gptr*) &short_form, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0,
   0, 0},
vva@eagle.mysql.r18.ru's avatar
vva@eagle.mysql.r18.ru committed
767
  {"socket", 'S', "Socket file to use for connection.",
768
   (gptr*) &sock, (gptr*) &sock, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 
vva@eagle.mysql.r18.ru's avatar
vva@eagle.mysql.r18.ru committed
769
   0, 0},
770 771 772 773 774 775 776 777
  {"start-datetime", OPT_START_DATETIME,
   "Start reading the binlog at first event having a datetime equal or "
   "posterior to the argument; the argument must be a date and time "
   "in the local time zone, in any format accepted by the MySQL server "
   "for DATETIME and TIMESTAMP types, for example: 2004-12-25 11:25:56 "
   "(you should probably use quotes for your shell to set it properly).",
   (gptr*) &start_datetime_str, (gptr*) &start_datetime_str,
   0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
paul@snake-hub.snake.net's avatar
paul@snake-hub.snake.net committed
778 779 780 781 782 783 784
  {"start-position", OPT_START_POSITION,
   "Start reading the binlog at position N. Applies to the first binlog "
   "passed on the command line.",
   (gptr*) &start_position, (gptr*) &start_position, 0, GET_ULL,
   REQUIRED_ARG, BIN_LOG_HEADER_SIZE, BIN_LOG_HEADER_SIZE,
   /* COM_BINLOG_DUMP accepts only 4 bytes for the position */
   (ulonglong)(~(uint32)0), 0, 0, 0},
785 786 787 788 789 790 791 792 793 794 795 796 797 798
  {"stop-datetime", OPT_STOP_DATETIME,
   "Stop reading the binlog at first event having a datetime equal or "
   "posterior to the argument; the argument must be a date and time "
   "in the local time zone, in any format accepted by the MySQL server "
   "for DATETIME and TIMESTAMP types, for example: 2004-12-25 11:25:56 "
   "(you should probably use quotes for your shell to set it properly).",
   (gptr*) &stop_datetime_str, (gptr*) &stop_datetime_str,
   0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
  {"stop-position", OPT_STOP_POSITION,
   "Stop reading the binlog at position N. Applies to the last binlog "
   "passed on the command line.",
   (gptr*) &stop_position, (gptr*) &stop_position, 0, GET_ULL,
   REQUIRED_ARG, (ulonglong)(~(my_off_t)0), BIN_LOG_HEADER_SIZE,
   (ulonglong)(~(my_off_t)0), 0, 0, 0},
799 800
  {"to-last-log", 't', "Requires -R. Will not stop at the end of the \
requested binlog but rather continue printing until the end of the last \
801
binlog of the MySQL server. If you send the output to the same MySQL server, \
802 803 804
that may lead to an endless loop.",
   (gptr*) &to_last_remote_log, (gptr*) &to_last_remote_log, 0, GET_BOOL,
   NO_ARG, 0, 0, 0, 0, 0, 0},
805
  {"user", 'u', "Connect to the remote server as username.",
806 807 808 809
   (gptr*) &user, (gptr*) &user, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0,
   0, 0},
  {"version", 'V', "Print version and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0,
   0, 0, 0, 0, 0},
paul@snake-hub.snake.net's avatar
paul@snake-hub.snake.net committed
810 811 812 813
  {"open_files_limit", OPT_OPEN_FILES_LIMIT,
   "Used to reserve file descriptors for usage by this program",
   (gptr*) &open_files_limit, (gptr*) &open_files_limit, 0, GET_ULONG,
   REQUIRED_ARG, MY_NFILE, 8, OS_FILE_LIMIT, 0, 1, 0},
814 815 816
  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};

817 818 819

void sql_print_error(const char *format,...)
{
bk@work.mysql.com's avatar
bk@work.mysql.com committed
820 821 822 823 824 825
  va_list args;
  va_start(args, format);
  fprintf(stderr, "ERROR: ");
  vfprintf(stderr, format, args);
  fprintf(stderr, "\n");
  va_end(args);
826
}
bk@work.mysql.com's avatar
bk@work.mysql.com committed
827

828 829 830
static void cleanup()
{
  my_free(pass,MYF(MY_ALLOW_ZERO_PTR));
831 832 833 834
  my_free((char*) database, MYF(MY_ALLOW_ZERO_PTR));
  my_free((char*) host, MYF(MY_ALLOW_ZERO_PTR));
  my_free((char*) user, MYF(MY_ALLOW_ZERO_PTR));
  my_free((char*) dirname_for_local_load, MYF(MY_ALLOW_ZERO_PTR));
835 836
}

bk@work.mysql.com's avatar
bk@work.mysql.com committed
837 838 839 840 841 842 843 844
static void die(const char* fmt, ...)
{
  va_list args;
  va_start(args, fmt);
  fprintf(stderr, "ERROR: ");
  vfprintf(stderr, fmt, args);
  fprintf(stderr, "\n");
  va_end(args);
845
  cleanup();
846 847
  /* We cannot free DBUG, it is used in global destructors after exit(). */
  my_end(MY_DONT_FREE_DBUG);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
848 849 850
  exit(1);
}

monty@mysql.com's avatar
monty@mysql.com committed
851 852
#include <help_start.h>

853 854
static void print_version()
{
855
  printf("%s Ver 3.1 for %s at %s\n", my_progname, SYSTEM_TYPE, MACHINE_TYPE);
monty@mysql.com's avatar
monty@mysql.com committed
856
  NETWARE_SET_SCREEN_MODE(1);
857 858 859
}


bk@work.mysql.com's avatar
bk@work.mysql.com committed
860 861
static void usage()
{
862
  print_version();
863 864 865
  puts("By Monty and Sasha, for your professional use\n\
This software comes with NO WARRANTY:  This is free software,\n\
and you are welcome to modify and redistribute it under the GPL license\n");
866 867

  printf("\
jcole@mugatu.spaceapes.com's avatar
jcole@mugatu.spaceapes.com committed
868
Dumps a MySQL binary log in a format usable for viewing or for piping to\n\
869
the mysql command line client\n\n");
870 871 872
  printf("Usage: %s [options] log-files\n", my_progname);
  my_print_help(my_long_options);
  my_print_variables(my_long_options);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
873 874
}

875 876 877 878 879 880

static my_time_t convert_str_to_timestamp(const char* str)
{
  int was_cut;
  MYSQL_TIME l_time;
  long dummy_my_timezone;
monty@mishka.local's avatar
monty@mishka.local committed
881
  my_bool dummy_in_dst_time_gap;
882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
  /* We require a total specification (date AND time) */
  if (str_to_datetime(str, strlen(str), &l_time, 0, &was_cut) !=
      MYSQL_TIMESTAMP_DATETIME || was_cut)
  {
    fprintf(stderr, "Incorrect date and time argument: %s\n", str);
    exit(1);
  }
  /*
    Note that Feb 30th, Apr 31st cause no error messages and are mapped to
    the next existing day, like in mysqld. Maybe this could be changed when
    mysqld is changed too (with its "strict" mode?).
  */
  return
    my_system_gmt_sec(&l_time, &dummy_my_timezone, &dummy_in_dst_time_gap);
}

monty@mysql.com's avatar
monty@mysql.com committed
898 899
#include <help_end.h>

900
extern "C" my_bool
901 902 903
get_one_option(int optid, const struct my_option *opt __attribute__((unused)),
	       char *argument)
{
904
  bool tty_password=0;
905
  switch (optid) {
906 907 908 909 910
#ifdef __NETWARE__
  case OPT_AUTO_CLOSE:
    setscreenmode(SCR_AUTOCLOSE_ON_EXIT);
    break;
#endif
bk@work.mysql.com's avatar
bk@work.mysql.com committed
911
#ifndef DBUG_OFF
912 913 914
  case '#':
    DBUG_PUSH(argument ? argument : default_dbug_option);
    break;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
915
#endif
916 917 918 919
  case 'd':
    one_database = 1;
    break;
  case 'p':
920 921 922 923 924 925 926 927 928 929 930
    if (argument)
    {
      my_free(pass,MYF(MY_ALLOW_ZERO_PTR));
      char *start=argument;
      pass= my_strdup(argument,MYF(MY_FAE));
      while (*argument) *argument++= 'x';		/* Destroy argument */
      if (*start)
        start[1]=0;				/* Cut length of argument */
    }
    else
      tty_password=1;
931 932 933 934 935
    break;
  case 'r':
    if (!(result_file = my_fopen(argument, O_WRONLY | O_BINARY, MYF(MY_WME))))
      exit(1);
    break;
936 937
  case 'R':
    remote_opt= 1;
938
    break;
939 940
  case OPT_MYSQL_PROTOCOL:
  {
941
    if ((opt_protocol= find_type(argument, &sql_protocol_typelib,0)) <= 0)
942 943 944 945 946 947
    {
      fprintf(stderr, "Unknown option to protocol: %s\n", argument);
      exit(1);
    }
    break;
  }
948 949 950 951 952 953
  case OPT_START_DATETIME:
    start_datetime= convert_str_to_timestamp(start_datetime_str);
    break;
  case OPT_STOP_DATETIME:
    stop_datetime= convert_str_to_timestamp(stop_datetime_str);
    break;
954 955 956 957 958 959
  case 'V':
    print_version();
    exit(0);
  case '?':
    usage();
    exit(0);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
960
  }
961 962 963
  if (tty_password)
    pass= get_tty_password(NullS);

964 965 966
  return 0;
}

bk@work.mysql.com's avatar
bk@work.mysql.com committed
967

968 969 970
static int parse_args(int *argc, char*** argv)
{
  int ho_error;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
971

972
  result_file = stdout;
monty@narttu.mysql.fi's avatar
monty@narttu.mysql.fi committed
973
  load_defaults("my",load_default_groups,argc,argv);
974
  if ((ho_error=handle_options(argc, argv, my_long_options, get_one_option)))
975 976
    exit(ho_error);

bk@work.mysql.com's avatar
bk@work.mysql.com committed
977 978 979 980 981
  return 0;
}

static MYSQL* safe_connect()
{
monty@mysql.com's avatar
monty@mysql.com committed
982
  MYSQL *local_mysql= mysql_init(NULL);
983

984
  if (!local_mysql)
985
    die("Failed on mysql_init");
bk@work.mysql.com's avatar
bk@work.mysql.com committed
986

987 988
  if (opt_protocol)
    mysql_options(local_mysql, MYSQL_OPT_PROTOCOL, (char*) &opt_protocol);
vva@eagle.mysql.r18.ru's avatar
vva@eagle.mysql.r18.ru committed
989
  if (!mysql_real_connect(local_mysql, host, user, pass, 0, port, sock, 0))
monty@mysql.com's avatar
monty@mysql.com committed
990 991 992 993 994 995
  {
    char errmsg[256];
    strmake(errmsg, mysql_error(local_mysql), sizeof(errmsg)-1);
    mysql_close(local_mysql);
    die("failed on connect: %s", errmsg);
  }
996
  local_mysql->reconnect= 1;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
997 998 999
  return local_mysql;
}

monty@mysql.com's avatar
monty@mysql.com committed
1000 1001

static int dump_log_entries(const char* logname)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1002
{
1003 1004
  return (remote_opt ? dump_remote_log_entries(logname) :
          dump_local_log_entries(logname));
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1005 1006
}

monty@mysql.com's avatar
monty@mysql.com committed
1007

1008 1009 1010 1011 1012 1013 1014
/*
  This is not as smart as check_header() (used for local log); it will not work
  for a binlog which mixes format. TODO: fix this.
*/
static int check_master_version(MYSQL* mysql,
                                Format_description_log_event
                                **description_event)
1015 1016 1017 1018
{
  MYSQL_RES* res = 0;
  MYSQL_ROW row;
  const char* version;
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
1019

1020 1021
  if (mysql_query(mysql, "SELECT VERSION()") ||
      !(res = mysql_store_result(mysql)))
monty@mysql.com's avatar
monty@mysql.com committed
1022 1023 1024 1025 1026 1027
  {
    char errmsg[256];
    strmake(errmsg, mysql_error(mysql), sizeof(errmsg)-1);
    mysql_close(mysql);
    die("Error checking master version: %s", errmsg);
  }
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
  if (!(row = mysql_fetch_row(res)))
  {
    mysql_free_result(res);
    mysql_close(mysql);
    die("Master returned no rows for SELECT VERSION()");
    return 1;
  }
  if (!(version = row[0]))
  {
    mysql_free_result(res);
    mysql_close(mysql);
    die("Master reported NULL for the version");
  }
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
1041

1042
  switch (*version) {
1043
  case '3':
1044
    *description_event= new Format_description_log_event(1);
1045 1046
    break;
  case '4':
1047
    *description_event= new Format_description_log_event(3);
1048
  case '5':
1049 1050 1051 1052 1053 1054 1055
    /*
      The server is soon going to send us its Format_description log
      event, unless it is a 5.0 server with 3.23 or 4.0 binlogs.
      So we first assume that this is 4.0 (which is enough to read the
      Format_desc event if one comes).
    */
    *description_event= new Format_description_log_event(3);
1056 1057 1058 1059 1060 1061 1062 1063 1064
    break;
  default:
    sql_print_error("Master reported unrecognized MySQL version '%s'",
		    version);
    mysql_free_result(res);
    mysql_close(mysql);
    return 1;
  }
  mysql_free_result(res);
1065
  return 0;
1066
}
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1067

1068

monty@mysql.com's avatar
monty@mysql.com committed
1069
static int dump_remote_log_entries(const char* logname)
1070

bk@work.mysql.com's avatar
bk@work.mysql.com committed
1071 1072
{
  char buf[128];
1073
  PRINT_EVENT_INFO print_event_info;
lars@mysql.com's avatar
lars@mysql.com committed
1074 1075
  ulong len;
  uint logname_len;
1076 1077 1078 1079
  NET* net;
  int error= 0;
  my_off_t old_off= start_position_mot;
  char fname[FN_REFLEN+1];
monty@mysql.com's avatar
monty@mysql.com committed
1080 1081
  DBUG_ENTER("dump_remote_log_entries");

1082 1083 1084 1085 1086 1087 1088
  /*
    Even if we already read one binlog (case of >=2 binlogs on command line),
    we cannot re-use the same connection as before, because it is now dead
    (COM_BINLOG_DUMP kills the thread when it finishes).
  */
  mysql= safe_connect();
  net= &mysql->net;
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100

  if (check_master_version(mysql, &description_event))
  {
    fprintf(stderr, "Could not find server version");
    DBUG_RETURN(1);
  }
  if (!description_event || !description_event->is_valid())
  {
    fprintf(stderr, "Invalid Format_description log event; \
could be out of memory");
    DBUG_RETURN(1);
  }
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
1101

1102 1103 1104 1105 1106
  /*
    COM_BINLOG_DUMP accepts only 4 bytes for the position, so we are forced to
    cast to uint32.
  */
  int4store(buf, (uint32)start_position);
1107
  int2store(buf + BIN_LOG_HEADER_SIZE, binlog_flags);
1108 1109 1110 1111 1112 1113 1114 1115 1116

  size_s tlen = strlen(logname);
  if (tlen > UINT_MAX) 
  {
    fprintf(stderr,"Log name too long\n");
    error= 1;
    goto err;
  }
  logname_len = (uint) tlen;
1117
  int4store(buf + 6, 0);
1118 1119
  memcpy(buf + 10, logname, logname_len);
  if (simple_command(mysql, COM_BINLOG_DUMP, buf, logname_len + 10, 1))
monty@mysql.com's avatar
monty@mysql.com committed
1120 1121
  {
    fprintf(stderr,"Got fatal error sending the log dump command\n");
1122 1123
    error= 1;
    goto err;
monty@mysql.com's avatar
monty@mysql.com committed
1124
  }
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
1125

1126
  for (;;)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1127
  {
1128
    const char *error_msg;
1129 1130
    Log_event *ev;

1131
    len= cli_safe_read(mysql);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1132
    if (len == packet_error)
monty@mysql.com's avatar
monty@mysql.com committed
1133 1134 1135
    {
      fprintf(stderr, "Got error reading packet from server: %s\n",
	      mysql_error(mysql));
1136 1137
      error= 1;
      goto err;
monty@mysql.com's avatar
monty@mysql.com committed
1138
    }
vva@eagle.mysql.r18.ru's avatar
vva@eagle.mysql.r18.ru committed
1139
    if (len < 8 && net->read_pos[0] == 254)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1140 1141 1142
      break; // end of data
    DBUG_PRINT("info",( "len= %u, net->read_pos[5] = %d\n",
			len, net->read_pos[5]));
1143 1144 1145
    if (!(ev= Log_event::read_log_event((const char*) net->read_pos + 1 ,
                                        len - 1, &error_msg,
                                        description_event)))
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1146
    {
monty@mysql.com's avatar
monty@mysql.com committed
1147
      fprintf(stderr, "Could not construct log event object\n");
1148 1149
      error= 1;
      goto err;
1150
    }   
monty@mysql.com's avatar
monty@mysql.com committed
1151 1152

    Log_event_type type= ev->get_type_code();
1153 1154
    if (description_event->binlog_version >= 3 ||
        (type != LOAD_EVENT && type != CREATE_FILE_EVENT))
monty@mysql.com's avatar
monty@mysql.com committed
1155
    {
1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
      /*
        If this is a Rotate event, maybe it's the end of the requested binlog;
        in this case we are done (stop transfer).
        This is suitable for binlogs, not relay logs (but for now we don't read
        relay logs remotely because the server is not able to do that). If one
        day we read relay logs remotely, then we will have a problem with the
        detection below: relay logs contain Rotate events which are about the
        binlogs, so which would trigger the end-detection below.
      */
      if (type == ROTATE_EVENT)
1166 1167 1168 1169 1170 1171 1172 1173 1174
      {
        Rotate_log_event *rev= (Rotate_log_event *)ev;
        /*
          If this is a fake Rotate event, and not about our log, we can stop
          transfer. If this a real Rotate event (so it's not about our log,
          it's in our log describing the next log), we print it (because it's
          part of our log) and then we will stop when we receive the fake one
          soon.
        */
1175
        if (rev->when == 0)
1176
        {
1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193
          if (!to_last_remote_log)
          {
            if ((rev->ident_len != logname_len) ||
                memcmp(rev->new_log_ident, logname, logname_len))
            {
              error= 0;
              goto err;
            }
            /*
              Otherwise, this is a fake Rotate for our log, at the very
              beginning for sure. Skip it, because it was not in the original
              log. If we are running with to_last_remote_log, we print it,
              because it serves as a useful marker between binlogs then.
            */
            continue;
          }
          len= 1; // fake Rotate, so don't increment old_off
1194 1195
        }
      }
1196
      if ((error= process_event(&print_event_info, ev, old_off)))
1197 1198 1199 1200
      {
	error= ((error < 0) ? 0 : 1);
        goto err;
      }
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1201 1202
    }
    else
1203
    {
monty@mysql.com's avatar
monty@mysql.com committed
1204 1205 1206 1207
      Load_log_event *le= (Load_log_event*)ev;
      const char *old_fname= le->fname;
      uint old_len= le->fname_len;
      File file;
1208
      
monty@mysql.com's avatar
monty@mysql.com committed
1209
      if ((file= load_processor.prepare_new_file_for_old_format(le,fname)) < 0)
1210 1211 1212 1213
      {
        error= 1;
        goto err;
      }
1214
      
1215
      if ((error= process_event(&print_event_info, ev, old_off)))
1216
      {
1217
 	my_close(file,MYF(MY_WME));
1218 1219
	error= ((error < 0) ? 0 : 1);
        goto err;
1220
      }
1221 1222 1223
      error= load_processor.load_old_format_file(net,old_fname,old_len,file);
      my_close(file,MYF(MY_WME));
      if (error)
1224
      {
1225 1226
        error= 1;
        goto err;
1227
      }
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1228
    }
1229
    /*
monty@mysql.com's avatar
monty@mysql.com committed
1230
      Let's adjust offset for remote log as for local log to produce 
1231
      similar text.
1232
    */
1233
    old_off+= len-1;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1234
  }
1235

1236 1237 1238
err:
  mysql_close(mysql);
  DBUG_RETURN(error);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1239 1240
}

1241

1242 1243
static void check_header(IO_CACHE* file, 
                        Format_description_log_event **description_event) 
1244
{
monty@mashka.mysql.fi's avatar
monty@mashka.mysql.fi committed
1245
  byte header[BIN_LOG_HEADER_SIZE];
1246
  byte buf[PROBE_HEADER_LEN];
1247
  my_off_t tmp_pos, pos;
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
1248

1249 1250
  *description_event= new Format_description_log_event(3);
  pos= my_b_tell(file);
1251
  my_b_seek(file, (my_off_t)0);
monty@mashka.mysql.fi's avatar
monty@mashka.mysql.fi committed
1252 1253 1254 1255
  if (my_b_read(file, header, sizeof(header)))
    die("Failed reading header;  Probably an empty file");
  if (memcmp(header, BINLOG_MAGIC, sizeof(header)))
    die("File is not a binary log file");
1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270

  /*
    Imagine we are running with --start-position=1000. We still need
    to know the binlog format's. So we still need to find, if there is
    one, the Format_desc event, or to know if this is a 3.23
    binlog. So we need to first read the first events of the log,
    those around offset 4.  Even if we are reading a 3.23 binlog from
    the start (no --start-position): we need to know the header length
    (which is 13 in 3.23, 19 in 4.x) to be able to successfully print
    the first event (Start_log_event_v3). So even in this case, we
    need to "probe" the first bytes of the log *before* we do a real
    read_log_event(). Because read_log_event() needs to know the
    header's length to work fine.
  */
  for(;;)
1271
  {
1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295
    tmp_pos= my_b_tell(file); /* should be 4 the first time */
    if (my_b_read(file, buf, sizeof(buf)))
    {
      if (file->error)
        die("\
Could not read entry at offset %lu : Error in log format or read error",
            tmp_pos); 
      /*
        Otherwise this is just EOF : this log currently contains 0-2
        events.  Maybe it's going to be filled in the next
        milliseconds; then we are going to have a problem if this a
        3.23 log (imagine we are locally reading a 3.23 binlog which
        is being written presently): we won't know it in
        read_log_event() and will fail().  Similar problems could
        happen with hot relay logs if --start-position is used (but a
        --start-position which is posterior to the current size of the log).
        These are rare problems anyway (reading a hot log + when we
        read the first events there are not all there yet + when we
        read a bit later there are more events + using a strange
        --start-position).
      */
      break;
    }
    else
monty@mashka.mysql.fi's avatar
monty@mashka.mysql.fi committed
1296
    {
1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324
      DBUG_PRINT("info",("buf[4]=%d", buf[4]));
      /* always test for a Start_v3, even if no --start-position */
      if (buf[4] == START_EVENT_V3)       /* This is 3.23 or 4.x */
      {
        if (uint4korr(buf + EVENT_LEN_OFFSET) < 
            (LOG_EVENT_MINIMAL_HEADER_LEN + START_V3_HEADER_LEN))
        {
          /* This is 3.23 (format 1) */
          delete *description_event;
          *description_event= new Format_description_log_event(1);
        }
        break;
      }
      else if (tmp_pos >= start_position)
        break;
      else if (buf[4] == FORMAT_DESCRIPTION_EVENT) /* This is 5.0 */
      {
        my_b_seek(file, tmp_pos); /* seek back to event's start */
        if (!(*description_event= (Format_description_log_event*) 
              Log_event::read_log_event(file, *description_event)))
          /* EOF can't be hit here normally, so it's a real error */
          die("Could not read a Format_description_log_event event \
at offset %lu ; this could be a log format error or read error",
              tmp_pos); 
        DBUG_PRINT("info",("Setting description_event"));
      }
      else if (buf[4] == ROTATE_EVENT)
      {
1325
        Log_event *ev;
1326
        my_b_seek(file, tmp_pos); /* seek back to event's start */
1327
        if (!(ev= Log_event::read_log_event(file, *description_event)))
1328
          /* EOF can't be hit here normally, so it's a real error */
1329 1330 1331
          die("Could not read a Rotate_log_event event at offset %lu ;"
              " this could be a log format error or read error", tmp_pos);
        delete ev;
1332 1333 1334
      }
      else
        break;
monty@mashka.mysql.fi's avatar
monty@mashka.mysql.fi committed
1335
    }
1336 1337 1338 1339
  }
  my_b_seek(file, pos);
}

monty@mashka.mysql.fi's avatar
monty@mashka.mysql.fi committed
1340

monty@mysql.com's avatar
monty@mysql.com committed
1341
static int dump_local_log_entries(const char* logname)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1342
{
sasha@mysql.sashanet.com's avatar
sasha@mysql.sashanet.com committed
1343
  File fd = -1;
1344
  IO_CACHE cache,*file= &cache;
1345
  PRINT_EVENT_INFO print_event_info;
1346
  byte tmp_buff[BIN_LOG_HEADER_SIZE];
monty@mysql.com's avatar
monty@mysql.com committed
1347
  int error= 0;
1348 1349 1350 1351

  if (logname && logname[0] != '-')
  {
    if ((fd = my_open(logname, O_RDONLY | O_BINARY, MYF(MY_WME))) < 0)
monty@mysql.com's avatar
monty@mysql.com committed
1352
      return 1;
1353
    if (init_io_cache(file, fd, 0, READ_CACHE, start_position_mot, 0,
1354
		      MYF(MY_WME | MY_NABP)))
monty@mysql.com's avatar
monty@mysql.com committed
1355 1356
    {
      my_close(fd, MYF(MY_WME));
1357
      return 1;
monty@mysql.com's avatar
monty@mysql.com committed
1358
    }
1359
    check_header(file, &description_event);
1360
  }
1361
  else // reading from stdin;
1362
  {
1363
    /*
1364 1365 1366 1367 1368 1369
      Windows opens stdin in text mode by default. Certain characters
      such as CTRL-Z are interpeted as events and the read() method
      will stop. CTRL-Z is the EOF marker in Windows. to get past this
      you have to open stdin in binary mode. Setmode() is used to set
      stdin in binary mode. Errors on setting this mode result in 
      halting the function and printing an error message to stderr.
1370 1371 1372 1373 1374 1375 1376 1377
    */
#if defined (__WIN__) || (_WIN64)
    if (_setmode(fileno(stdin), O_BINARY) == -1)
    {
       fprintf(stderr, "Could not set binary mode on stdin.\n");
       return 1;
    }
#endif 
1378
    if (init_io_cache(file, fileno(stdin), 0, READ_CACHE, (my_off_t) 0,
1379
		      0, MYF(MY_WME | MY_NABP | MY_DONT_CHECK_FILESIZE)))
monty@mysql.com's avatar
monty@mysql.com committed
1380
      return 1;
1381
    check_header(file, &description_event);
1382
    if (start_position)
1383
    {
1384
      /* skip 'start_position' characters from stdin */
monty@donna.mysql.com's avatar
monty@donna.mysql.com committed
1385
      byte buff[IO_SIZE];
1386
      my_off_t length,tmp;
1387
      for (length= start_position_mot ; length > 0 ; length-=tmp)
1388 1389
      {
	tmp=min(length,sizeof(buff));
1390
	if (my_b_read(file, buff, (uint) tmp))
1391 1392 1393 1394
        {
          error= 1;
          goto end;
        }
1395 1396 1397 1398
      }
    }
  }

1399 1400 1401 1402
  if (!description_event || !description_event->is_valid())
    die("Invalid Format_description log event; could be out of memory");

  if (!start_position && my_b_read(file, tmp_buff, BIN_LOG_HEADER_SIZE))
monty@mysql.com's avatar
monty@mysql.com committed
1403
  {
1404 1405
    error= 1;
    goto end;
monty@mysql.com's avatar
monty@mysql.com committed
1406
  }
1407
  for (;;)
1408
  {
1409
    char llbuff[21];
1410 1411
    my_off_t old_off = my_b_tell(file);

1412
    Log_event* ev = Log_event::read_log_event(file, description_event);
1413 1414
    if (!ev)
    {
1415 1416
      /*
        if binlog wasn't closed properly ("in use" flag is set) don't complain
1417
        about a corruption, but treat it as EOF and move to the next binlog.
1418 1419
      */
      if (description_event->flags & LOG_EVENT_BINLOG_IN_USE_F)
1420
        file->error= 0;
1421
      else if (file->error)
monty@mysql.com's avatar
monty@mysql.com committed
1422
      {
1423
        fprintf(stderr,
1424 1425 1426 1427
                "Could not read entry at offset %s:"
                "Error in log format or read error\n",
                llstr(old_off,llbuff));
        error= 1;
monty@mysql.com's avatar
monty@mysql.com committed
1428
      }
1429
      // file->error == 0 means EOF, that's OK, we break in this case
1430 1431
      break;
    }
1432
    if ((error= process_event(&print_event_info, ev, old_off)))
1433
    {
1434 1435
      if (error < 0)
        error= 0;
monty@mysql.com's avatar
monty@mysql.com committed
1436
      break;
1437
    }
1438
  }
monty@mysql.com's avatar
monty@mysql.com committed
1439 1440

end:
1441
  if (fd >= 0)
1442
    my_close(fd, MYF(MY_WME));
1443
  end_io_cache(file);
1444
  delete description_event;
monty@mysql.com's avatar
monty@mysql.com committed
1445
  return error;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1446 1447
}

1448

bk@work.mysql.com's avatar
bk@work.mysql.com committed
1449 1450
int main(int argc, char** argv)
{
monty@narttu.mysql.fi's avatar
monty@narttu.mysql.fi committed
1451
  static char **defaults_argv;
1452 1453
  int exit_value= 0;
  ulonglong save_stop_position;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1454
  MY_INIT(argv[0]);
monty@mysql.com's avatar
monty@mysql.com committed
1455 1456
  DBUG_ENTER("main");
  DBUG_PROCESS(argv[0]);
monty@narttu.mysql.fi's avatar
monty@narttu.mysql.fi committed
1457

1458 1459
  init_time(); // for time functions

bk@work.mysql.com's avatar
bk@work.mysql.com committed
1460
  parse_args(&argc, (char***)&argv);
monty@narttu.mysql.fi's avatar
monty@narttu.mysql.fi committed
1461
  defaults_argv=argv;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1462

1463
  if (!argc)
1464 1465
  {
    usage();
monty@narttu.mysql.fi's avatar
monty@narttu.mysql.fi committed
1466
    free_defaults(defaults_argv);
monty@mysql.com's avatar
monty@mysql.com committed
1467
    exit(1);
1468
  }
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1469

1470
  my_set_max_open_files(open_files_limit);
1471 1472 1473 1474 1475 1476 1477

  MY_TMPDIR tmpdir;
  tmpdir.list= 0;
  if (!dirname_for_local_load)
  {
    if (init_tmpdir(&tmpdir, 0))
      exit(1);
monty@mysql.com's avatar
monty@mysql.com committed
1478
    dirname_for_local_load= my_strdup(my_tmpdir(&tmpdir), MY_WME);
1479 1480
  }

1481 1482
  if (load_processor.init())
    exit(1);
1483 1484
  if (dirname_for_local_load)
    load_processor.init_by_dir_name(dirname_for_local_load);
1485 1486
  else
    load_processor.init_by_cur_dir();
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1487

1488
  fprintf(result_file,
1489
	  "/*!40019 SET @@session.max_insert_delayed_threads=0*/;\n");
1490 1491 1492 1493 1494

  if (disable_log_bin)
    fprintf(result_file,
            "/*!32316 SET @OLD_SQL_LOG_BIN=@@SQL_LOG_BIN, SQL_LOG_BIN=0*/;\n");

1495 1496 1497 1498 1499 1500 1501 1502
  /*
    In mysqlbinlog|mysql, don't want mysql to be disconnected after each
    transaction (which would be the case with GLOBAL.COMPLETION_TYPE==2).
  */
  fprintf(result_file,
          "/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,"
          "COMPLETION_TYPE=0*/;\n");

1503 1504 1505 1506 1507 1508 1509
  if (charset)
    fprintf(result_file,
            "\n/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;"
            "\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;"
            "\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;"  
            "\n/*!40101 SET NAMES %s */;\n", charset);

1510 1511
  for (save_stop_position= stop_position, stop_position= ~(my_off_t)0 ;
       (--argc >= 0) && !stop_passed ; )
monty@mysql.com's avatar
monty@mysql.com committed
1512
  {
1513 1514
    if (argc == 0) // last log, --stop-position applies
      stop_position= save_stop_position;
monty@mysql.com's avatar
monty@mysql.com committed
1515 1516 1517 1518 1519
    if (dump_log_entries(*(argv++)))
    {
      exit_value=1;
      break;
    }
1520 1521
    // For next log, --start-position does not apply
    start_position= BIN_LOG_HEADER_SIZE;
monty@mysql.com's avatar
monty@mysql.com committed
1522
  }
1523

1524 1525 1526 1527 1528
  /*
    Issue a ROLLBACK in case the last printed binlog was crashed and had half
    of transaction.
  */
  fprintf(result_file,
1529
          "# End of log file\nROLLBACK /* added by mysqlbinlog */;\n"
1530
          "/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;\n");
1531 1532 1533
  if (disable_log_bin)
    fprintf(result_file, "/*!32316 SET SQL_LOG_BIN=@OLD_SQL_LOG_BIN*/;\n");

1534 1535 1536 1537 1538 1539
  if (charset)
    fprintf(result_file,
            "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n"
            "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n"
            "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");

1540 1541
  if (tmpdir.list)
    free_tmpdir(&tmpdir);
1542 1543
  if (result_file != stdout)
    my_fclose(result_file, MYF(0));
1544
  cleanup();
monty@narttu.mysql.fi's avatar
monty@narttu.mysql.fi committed
1545
  free_defaults(defaults_argv);
1546
  my_free_open_file_info();
1547 1548
  /* We cannot free DBUG, it is used in global destructors after exit(). */
  my_end(MY_DONT_FREE_DBUG);
monty@mysql.com's avatar
monty@mysql.com committed
1549
  exit(exit_value);
monty@mysql.com's avatar
monty@mysql.com committed
1550
  DBUG_RETURN(exit_value);			// Keep compilers happy
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1551 1552 1553 1554 1555 1556 1557
}

/*
  We must include this here as it's compiled with different options for
  the server
*/

1558
#if defined(__WIN__) && !defined(USING_CMAKE)
1559 1560 1561
#include "my_decimal.h"
#include "decimal.c"
#include "my_decimal.cpp"
1562 1563
#include "log_event.cpp"
#else
1564 1565 1566
#include "my_decimal.h"
#include "decimal.c"
#include "my_decimal.cc"
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1567
#include "log_event.cc"
1568
#endif
1569