mirror of
https://github.com/MariaDB/server.git
synced 2025-01-17 20:42:30 +01:00
2aecdd1a91
(All commit emails since 4.0.1 checked) This had to be done now, before the 4.1 tree changes to much, to make it easy to propagate bug fixes to the 4.1 tree.
554 lines
14 KiB
C++
554 lines
14 KiB
C++
/* Copyright (C) 2000 MySQL AB
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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 */
|
|
|
|
#define MYSQL_CLIENT
|
|
#undef MYSQL_SERVER
|
|
#include "client_priv.h"
|
|
#include <time.h>
|
|
#include "log_event.h"
|
|
|
|
#define BIN_LOG_HEADER_SIZE 4
|
|
#define PROBE_HEADER_LEN (BIN_LOG_HEADER_SIZE+EVENT_LEN_OFFSET+4)
|
|
|
|
|
|
#define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_LOCAL_FILES)
|
|
|
|
char server_version[SERVER_VERSION_LENGTH];
|
|
uint32 server_id = 0;
|
|
|
|
// needed by net_serv.c
|
|
ulong bytes_sent = 0L, bytes_received = 0L;
|
|
ulong mysqld_net_retry_count = 10L;
|
|
uint test_flags = 0;
|
|
|
|
static FILE *result_file;
|
|
|
|
#ifndef DBUG_OFF
|
|
static const char* default_dbug_option = "d:t:o,/tmp/mysqlbinlog.trace";
|
|
#endif
|
|
|
|
void sql_print_error(const char *format,...);
|
|
|
|
static bool one_database = 0;
|
|
static const char* database;
|
|
static bool short_form = 0;
|
|
static ulonglong offset = 0;
|
|
static const char* host = "localhost";
|
|
static int port = MYSQL_PORT;
|
|
static const char* user = "test";
|
|
static const char* pass = "";
|
|
static ulonglong position = 0;
|
|
static bool use_remote = 0;
|
|
static short binlog_flags = 0;
|
|
static MYSQL* mysql = NULL;
|
|
static const char* table = 0;
|
|
|
|
static void dump_local_log_entries(const char* logname);
|
|
static void dump_remote_log_entries(const char* logname);
|
|
static void dump_log_entries(const char* logname);
|
|
static void dump_remote_file(NET* net, const char* fname);
|
|
static void dump_remote_table(NET* net, const char* db, const char* table);
|
|
static void die(const char* fmt, ...);
|
|
static MYSQL* safe_connect();
|
|
|
|
static struct my_option my_long_options[] =
|
|
{
|
|
#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
|
|
{"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},
|
|
{"help", '?', "Display this help and exit",
|
|
0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
|
|
{"host", 'h', "Get the binlog from server", (gptr*) &host, (gptr*) &host,
|
|
0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
|
|
{"offset", 'o', "Skip the first N entries", (gptr*) &offset, (gptr*) &offset,
|
|
0, GET_ULL, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
|
|
{"password", 'p', "Password to connect to remote server",
|
|
0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
|
|
{"port", 'P', "Use port to connect to the remote server",
|
|
(gptr*) &port, (gptr*) &port, 0, GET_INT, REQUIRED_ARG, MYSQL_PORT, 0, 0,
|
|
0, 0, 0},
|
|
{"position", 'j', "Start reading the binlog at position N",
|
|
(gptr*) &position, (gptr*) &position, 0, GET_ULL, REQUIRED_ARG, 0, 0, 0, 0,
|
|
0, 0},
|
|
{"result-file", 'r', "Direct output to a given file", 0, 0, 0, GET_STR,
|
|
REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
|
|
{"short-form", 's', "Just show the queries, no extra info",
|
|
(gptr*) &short_form, (gptr*) &short_form, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0,
|
|
0, 0},
|
|
{"table", 't', "Get raw table dump using COM_TABLE_DUMB", (gptr*) &table,
|
|
(gptr*) &table, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
|
|
{"user", 'u', "Connect to the remote server as username",
|
|
(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},
|
|
{0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
|
|
};
|
|
|
|
|
|
void sql_print_error(const char *format,...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
fprintf(stderr, "ERROR: ");
|
|
vfprintf(stderr, format, args);
|
|
fprintf(stderr, "\n");
|
|
va_end(args);
|
|
}
|
|
|
|
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);
|
|
exit(1);
|
|
}
|
|
|
|
static void print_version()
|
|
{
|
|
printf("%s Ver 2.3 for %s at %s\n", my_progname, SYSTEM_TYPE, MACHINE_TYPE);
|
|
}
|
|
|
|
|
|
static void usage()
|
|
{
|
|
print_version();
|
|
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");
|
|
|
|
printf("\
|
|
Dumps a MySQL binary log in a format usable for viewing or for pipeing to\n\
|
|
the mysql command line client\n\n");
|
|
printf("Usage: %s [options] log-files\n", my_progname);
|
|
my_print_help(my_long_options);
|
|
my_print_variables(my_long_options);
|
|
}
|
|
|
|
static void dump_remote_file(NET* net, const char* fname)
|
|
{
|
|
char buf[FN_REFLEN+1];
|
|
uint len = (uint) strlen(fname);
|
|
buf[0] = 0;
|
|
memcpy(buf + 1, fname, len + 1);
|
|
if(my_net_write(net, buf, len +2) || net_flush(net))
|
|
die("Failed requesting the remote dump of %s", fname);
|
|
for(;;)
|
|
{
|
|
uint packet_len = my_net_read(net);
|
|
if(packet_len == 0)
|
|
{
|
|
if(my_net_write(net, "", 0) || net_flush(net))
|
|
die("Failed sending the ack packet");
|
|
|
|
// 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;
|
|
}
|
|
else if(packet_len == packet_error)
|
|
die("Failed reading a packet during the dump of %s ", fname);
|
|
|
|
if(!short_form)
|
|
(void)my_fwrite(result_file, (byte*) net->read_pos, packet_len,MYF(0));
|
|
}
|
|
|
|
fflush(result_file);
|
|
}
|
|
|
|
|
|
static my_bool
|
|
get_one_option(int optid, const struct my_option *opt __attribute__((unused)),
|
|
char *argument)
|
|
{
|
|
switch(optid) {
|
|
#ifndef DBUG_OFF
|
|
case '#':
|
|
DBUG_PUSH(argument ? argument : default_dbug_option);
|
|
break;
|
|
#endif
|
|
case 'd':
|
|
one_database = 1;
|
|
break;
|
|
case 'h':
|
|
use_remote = 1;
|
|
break;
|
|
case 'P':
|
|
use_remote = 1;
|
|
break;
|
|
case 'p':
|
|
use_remote = 1;
|
|
pass = my_strdup(argument, MYF(0));
|
|
break;
|
|
case 'r':
|
|
if (!(result_file = my_fopen(argument, O_WRONLY | O_BINARY, MYF(MY_WME))))
|
|
exit(1);
|
|
break;
|
|
case 'u':
|
|
use_remote = 1;
|
|
break;
|
|
case 'V':
|
|
print_version();
|
|
exit(0);
|
|
case '?':
|
|
default:
|
|
usage();
|
|
exit(0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int parse_args(int *argc, char*** argv)
|
|
{
|
|
int ho_error;
|
|
|
|
result_file = stdout;
|
|
if ((ho_error=handle_options(argc, argv, my_long_options, get_one_option)))
|
|
exit(ho_error);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static MYSQL* safe_connect()
|
|
{
|
|
MYSQL *local_mysql = mysql_init(NULL);
|
|
if(!local_mysql)
|
|
die("Failed on mysql_init");
|
|
|
|
if (!mysql_real_connect(local_mysql, host, user, pass, 0, port, 0, 0))
|
|
die("failed on connect: %s", mysql_error(local_mysql));
|
|
|
|
return local_mysql;
|
|
}
|
|
|
|
static void dump_log_entries(const char* logname)
|
|
{
|
|
if (use_remote)
|
|
dump_remote_log_entries(logname);
|
|
else
|
|
dump_local_log_entries(logname);
|
|
}
|
|
|
|
static void dump_remote_table(NET* net, const char* db, const char* table)
|
|
{
|
|
char buf[1024];
|
|
char * p = buf;
|
|
uint table_len = (uint) strlen(table);
|
|
uint db_len = (uint) strlen(db);
|
|
if (table_len + db_len > sizeof(buf) - 2)
|
|
die("Buffer overrun");
|
|
|
|
*p++ = db_len;
|
|
memcpy(p, db, db_len);
|
|
p += db_len;
|
|
*p++ = table_len;
|
|
memcpy(p, table, table_len);
|
|
|
|
if (simple_command(mysql, COM_TABLE_DUMP, buf, p - buf + table_len, 1))
|
|
die("Error sending the table dump command");
|
|
|
|
for (;;)
|
|
{
|
|
uint packet_len = my_net_read(net);
|
|
if (packet_len == 0) break; // end of file
|
|
if (packet_len == packet_error)
|
|
die("Error reading packet in table dump");
|
|
my_fwrite(result_file, (byte*)net->read_pos, packet_len, MYF(MY_WME));
|
|
fflush(result_file);
|
|
}
|
|
}
|
|
|
|
static int check_master_version(MYSQL* mysql)
|
|
{
|
|
MYSQL_RES* res = 0;
|
|
MYSQL_ROW row;
|
|
const char* version;
|
|
int old_format = 0;
|
|
|
|
if (mysql_query(mysql, "SELECT VERSION()") ||
|
|
!(res = mysql_store_result(mysql)))
|
|
{
|
|
mysql_close(mysql);
|
|
die("Error checking master version: %s",
|
|
mysql_error(mysql));
|
|
}
|
|
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");
|
|
}
|
|
|
|
switch (*version) {
|
|
case '3':
|
|
old_format = 1;
|
|
break;
|
|
case '4':
|
|
case '5':
|
|
old_format = 0;
|
|
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);
|
|
return old_format;
|
|
}
|
|
|
|
|
|
static void dump_remote_log_entries(const char* logname)
|
|
{
|
|
char buf[128];
|
|
char last_db[FN_REFLEN+1] = "";
|
|
uint len;
|
|
NET* net = &mysql->net;
|
|
int old_format;
|
|
old_format = check_master_version(mysql);
|
|
|
|
if (!position)
|
|
position = BIN_LOG_HEADER_SIZE; // protect the innocent from spam
|
|
if (position < BIN_LOG_HEADER_SIZE)
|
|
{
|
|
position = BIN_LOG_HEADER_SIZE;
|
|
// warn the guity
|
|
sql_print_error("Warning: The position in the binary log can't be less than %d.\nStarting from position %d\n", BIN_LOG_HEADER_SIZE, BIN_LOG_HEADER_SIZE);
|
|
}
|
|
int4store(buf, position);
|
|
int2store(buf + BIN_LOG_HEADER_SIZE, binlog_flags);
|
|
len = (uint) strlen(logname);
|
|
int4store(buf + 6, 0);
|
|
memcpy(buf + 10, logname,len);
|
|
if (simple_command(mysql, COM_BINLOG_DUMP, buf, len + 10, 1))
|
|
die("Error sending the log dump command");
|
|
|
|
for (;;)
|
|
{
|
|
const char *error;
|
|
len = net_safe_read(mysql);
|
|
if (len == packet_error)
|
|
die("Error reading packet from server: %s", mysql_error(mysql));
|
|
if (len == 1 && net->read_pos[0] == 254)
|
|
break; // end of data
|
|
DBUG_PRINT("info",( "len= %u, net->read_pos[5] = %d\n",
|
|
len, net->read_pos[5]));
|
|
Log_event *ev = Log_event::read_log_event((const char*) net->read_pos + 1 ,
|
|
len - 1, &error, old_format);
|
|
if (ev)
|
|
{
|
|
ev->print(result_file, short_form, last_db);
|
|
if (ev->get_type_code() == LOAD_EVENT)
|
|
dump_remote_file(net, ((Load_log_event*)ev)->fname);
|
|
delete ev;
|
|
}
|
|
else
|
|
die("Could not construct log event object");
|
|
}
|
|
}
|
|
|
|
|
|
static int check_header(IO_CACHE* file)
|
|
{
|
|
byte buf[PROBE_HEADER_LEN];
|
|
int old_format=0;
|
|
|
|
my_off_t pos = my_b_tell(file);
|
|
my_b_seek(file, (my_off_t)0);
|
|
if (my_b_read(file, buf, sizeof(buf)))
|
|
die("Failed reading header");
|
|
if (buf[EVENT_TYPE_OFFSET+4] == START_EVENT)
|
|
{
|
|
uint event_len;
|
|
event_len = uint4korr(buf + EVENT_LEN_OFFSET + 4);
|
|
old_format = (event_len < LOG_EVENT_HEADER_LEN + START_HEADER_LEN);
|
|
}
|
|
my_b_seek(file, pos);
|
|
return old_format;
|
|
}
|
|
|
|
static void dump_local_log_entries(const char* logname)
|
|
{
|
|
File fd = -1;
|
|
IO_CACHE cache,*file= &cache;
|
|
ulonglong rec_count = 0;
|
|
char last_db[FN_REFLEN+1] = "";
|
|
bool old_format = 0;
|
|
|
|
if (logname && logname[0] != '-')
|
|
{
|
|
if ((fd = my_open(logname, O_RDONLY | O_BINARY, MYF(MY_WME))) < 0)
|
|
exit(1);
|
|
if (init_io_cache(file, fd, 0, READ_CACHE, (my_off_t) position, 0,
|
|
MYF(MY_WME | MY_NABP)))
|
|
exit(1);
|
|
old_format = check_header(file);
|
|
}
|
|
else
|
|
{
|
|
if (init_io_cache(file, fileno(result_file), 0, READ_CACHE, (my_off_t) 0,
|
|
0, MYF(MY_WME | MY_NABP | MY_DONT_CHECK_FILESIZE)))
|
|
exit(1);
|
|
old_format = check_header(file);
|
|
if (position)
|
|
{
|
|
/* skip 'position' characters from stdout */
|
|
byte buff[IO_SIZE];
|
|
my_off_t length,tmp;
|
|
for (length= (my_off_t) position ; length > 0 ; length-=tmp)
|
|
{
|
|
tmp=min(length,sizeof(buff));
|
|
if (my_b_read(file, buff, (uint) tmp))
|
|
exit(1);
|
|
}
|
|
}
|
|
file->pos_in_file=position;
|
|
file->seek_not_done=0;
|
|
}
|
|
|
|
if (!position)
|
|
{
|
|
char magic[BIN_LOG_HEADER_SIZE];
|
|
if (my_b_read(file, (byte*) magic, sizeof(magic)))
|
|
die("I/O error reading binlog magic number");
|
|
if (memcmp(magic, BINLOG_MAGIC, 4))
|
|
die("Bad magic number; The file is probably not a MySQL binary log");
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
char llbuff[21];
|
|
my_off_t old_off = my_b_tell(file);
|
|
|
|
Log_event* ev = Log_event::read_log_event(file, old_format);
|
|
if (!ev)
|
|
{
|
|
if (file->error)
|
|
die("\
|
|
Could not read entry at offset %s : Error in log format or read error",
|
|
llstr(old_off,llbuff));
|
|
// file->error == 0 means EOF, that's OK, we break in this case
|
|
break;
|
|
}
|
|
if (rec_count >= offset)
|
|
{
|
|
// see if we should skip this event (only care about queries for now)
|
|
if (one_database)
|
|
{
|
|
if (ev->get_type_code() == QUERY_EVENT)
|
|
{
|
|
//const char * log_dbname = ev->get_db();
|
|
const char * log_dbname = ((Query_log_event*)ev)->db;
|
|
//printf("entry: %llu, database: %s\n", rec_count, log_dbname);
|
|
|
|
if ((log_dbname != NULL) && (strcmp(log_dbname, database)))
|
|
{
|
|
//printf("skipping, %s is not %s\n", log_dbname, database);
|
|
rec_count++;
|
|
delete ev;
|
|
continue; // next
|
|
}
|
|
#ifndef DBUG_OFF
|
|
else
|
|
{
|
|
printf("no skip\n");
|
|
}
|
|
#endif
|
|
}
|
|
#ifndef DBUG_OFF
|
|
else
|
|
{
|
|
const char * query_type = ev->get_type_str();
|
|
printf("not query -- %s\n", query_type);
|
|
}
|
|
#endif
|
|
}
|
|
if (!short_form)
|
|
fprintf(result_file, "# at %s\n",llstr(old_off,llbuff));
|
|
|
|
ev->print(result_file, short_form, last_db);
|
|
}
|
|
rec_count++;
|
|
delete ev;
|
|
}
|
|
if (fd >= 0)
|
|
my_close(fd, MYF(MY_WME));
|
|
end_io_cache(file);
|
|
}
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
MY_INIT(argv[0]);
|
|
parse_args(&argc, (char***)&argv);
|
|
|
|
if (!argc && !table)
|
|
{
|
|
usage();
|
|
return -1;
|
|
}
|
|
|
|
if (use_remote)
|
|
mysql = safe_connect();
|
|
|
|
if (table)
|
|
{
|
|
if (!use_remote)
|
|
die("You must specify connection parameter to get table dump");
|
|
char* db = (char*) table;
|
|
char* tbl = (char*) strchr(table, '.');
|
|
if (!tbl)
|
|
die("You must use database.table syntax to specify the table");
|
|
*tbl++ = 0;
|
|
dump_remote_table(&mysql->net, db, tbl);
|
|
}
|
|
else
|
|
{
|
|
while (--argc >= 0)
|
|
dump_log_entries(*(argv++));
|
|
}
|
|
if (result_file != stdout)
|
|
my_fclose(result_file, MYF(0));
|
|
if (use_remote)
|
|
mysql_close(mysql);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
We must include this here as it's compiled with different options for
|
|
the server
|
|
*/
|
|
|
|
#ifdef __WIN__
|
|
#include "log_event.cpp"
|
|
#else
|
|
#include "log_event.cc"
|
|
#endif
|