/* 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 3 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, see . Authors: Domas Mituzas, Facebook ( domas at fb dot com ) Mark Leith, Oracle Corporation (mark dot leith at oracle dot com) Andrew Hutchings, SkySQL (andrew at skysql dot com) Max Bubenick, Percona RDBA (max dot bubenick at percona dot com) */ #define _LARGEFILE64_SOURCE #define _FILE_OFFSET_BITS 64 #include #if defined MARIADB_CLIENT_VERSION_STR && !defined MYSQL_SERVER_VERSION #define MYSQL_SERVER_VERSION MARIADB_CLIENT_VERSION_STR #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef WITH_BINLOG #include "binlog.h" #else #include "mydumper.h" #endif #include "server_detect.h" #include "connection.h" #include "common.h" #include "g_unix_signal.h" #include #include "getPassword.h" char *regexstring=NULL; const char DIRECTORY[]= "export"; #ifdef WITH_BINLOG const char BINLOG_DIRECTORY[]= "binlog_snapshot"; const char DAEMON_BINLOGS[]= "binlogs"; #endif /* Some earlier versions of MySQL do not yet define MYSQL_TYPE_JSON */ #ifndef MYSQL_TYPE_JSON #define MYSQL_TYPE_JSON 245 #endif static GMutex * init_mutex = NULL; /* Program options */ gchar *output_directory= NULL; guint statement_size= 1000000; guint rows_per_file= 0; guint chunk_filesize = 0; int longquery= 60; int build_empty_files= 0; int skip_tz= 0; int need_dummy_read= 0; int need_dummy_toku_read = 0; int compress_output= 0; int killqueries= 0; int detected_server= 0; int lock_all_tables=0; guint snapshot_interval= 60; gboolean daemon_mode= FALSE; gboolean have_snapshot_cloning= FALSE; gchar *ignore_engines= NULL; char **ignore= NULL; gchar *tables_list= NULL; GSequence *tables_skiplist= NULL; gchar *tables_skiplist_file= NULL; char **tables= NULL; GList *no_updated_tables=NULL; #ifdef WITH_BINLOG gboolean need_binlogs= FALSE; gchar *binlog_directory= NULL; gchar *daemon_binlog_directory= NULL; #endif gchar *logfile= NULL; FILE *logoutfile= NULL; gboolean no_schemas= FALSE; gboolean no_data= FALSE; gboolean no_locks= FALSE; gboolean dump_triggers= FALSE; gboolean dump_events= FALSE; gboolean dump_routines= FALSE; gboolean no_dump_views= FALSE; gboolean less_locking = FALSE; gboolean use_savepoints = FALSE; gboolean success_on_1146 = FALSE; gboolean no_backup_locks = FALSE; gboolean insert_ignore = FALSE; GList *innodb_tables= NULL; GList *non_innodb_table= NULL; GList *table_schemas= NULL; GList *view_schemas= NULL; GList *schema_post= NULL; gint non_innodb_table_counter= 0; gint non_innodb_done= 0; guint less_locking_threads = 0; guint updated_since = 0; guint trx_consistency_only = 0; guint complete_insert = 0; // For daemon mode, 0 or 1 guint dump_number= 0; guint binlog_connect_id= 0; gboolean shutdown_triggered= FALSE; GAsyncQueue *start_scheduled_dump; GMainLoop *m1; static GCond * ll_cond = NULL; static GMutex * ll_mutex = NULL; int errors; static GOptionEntry entries[] = { { "database", 'B', 0, G_OPTION_ARG_STRING, &db, "Database to dump", NULL }, { "tables-list", 'T', 0, G_OPTION_ARG_STRING, &tables_list, "Comma delimited table list to dump (does not exclude regex option)", NULL }, { "omit-from-file", 'O', 0, G_OPTION_ARG_STRING, &tables_skiplist_file, "File containing a list of database.table entries to skip, one per line (skips before applying regex option)", NULL }, { "outputdir", 'o', 0, G_OPTION_ARG_FILENAME, &output_directory, "Directory to output files to", NULL }, { "statement-size", 's', 0, G_OPTION_ARG_INT, &statement_size, "Attempted size of INSERT statement in bytes, default 1000000", NULL}, { "rows", 'r', 0, G_OPTION_ARG_INT, &rows_per_file, "Try to split tables into chunks of this many rows. This option turns off --chunk-filesize", NULL}, { "chunk-filesize", 'F', 0, G_OPTION_ARG_INT, &chunk_filesize, "Split tables into chunks of this output file size. This value is in MB", NULL }, { "compress", 'c', 0, G_OPTION_ARG_NONE, &compress_output, "Compress output files", NULL}, { "build-empty-files", 'e', 0, G_OPTION_ARG_NONE, &build_empty_files, "Build dump files even if no data available from table", NULL}, { "regex", 'x', 0, G_OPTION_ARG_STRING, ®exstring, "Regular expression for 'db.table' matching", NULL}, { "ignore-engines", 'i', 0, G_OPTION_ARG_STRING, &ignore_engines, "Comma delimited list of storage engines to ignore", NULL }, { "insert-ignore", 'N', 0, G_OPTION_ARG_NONE, &insert_ignore, "Dump rows with INSERT IGNORE", NULL }, { "no-schemas", 'm', 0, G_OPTION_ARG_NONE, &no_schemas, "Do not dump table schemas with the data", NULL }, { "no-data", 'd', 0, G_OPTION_ARG_NONE, &no_data, "Do not dump table data", NULL }, { "triggers", 'G', 0, G_OPTION_ARG_NONE, &dump_triggers, "Dump triggers", NULL }, { "events", 'E', 0, G_OPTION_ARG_NONE, &dump_events, "Dump events", NULL }, { "routines", 'R', 0, G_OPTION_ARG_NONE, &dump_routines, "Dump stored procedures and functions", NULL }, { "no-views", 'W', 0, G_OPTION_ARG_NONE, &no_dump_views, "Do not dump VIEWs", NULL }, { "no-locks", 'k', 0, G_OPTION_ARG_NONE, &no_locks, "Do not execute the temporary shared read lock. WARNING: This will cause inconsistent backups", NULL }, { "no-backup-locks", 0, 0, G_OPTION_ARG_NONE, &no_backup_locks, "Do not use Percona backup locks", NULL}, { "less-locking", 0, 0, G_OPTION_ARG_NONE, &less_locking, "Minimize locking time on InnoDB tables.", NULL}, { "long-query-guard", 'l', 0, G_OPTION_ARG_INT, &longquery, "Set long query timer in seconds, default 60", NULL }, { "kill-long-queries", 'K', 0, G_OPTION_ARG_NONE, &killqueries, "Kill long running queries (instead of aborting)", NULL }, #ifdef WITH_BINLOG { "binlogs", 'b', 0, G_OPTION_ARG_NONE, &need_binlogs, "Get a snapshot of the binary logs as well as dump data", NULL }, #endif { "daemon", 'D', 0, G_OPTION_ARG_NONE, &daemon_mode, "Enable daemon mode", NULL }, { "snapshot-interval", 'I', 0, G_OPTION_ARG_INT, &snapshot_interval, "Interval between each dump snapshot (in minutes), requires --daemon, default 60", NULL }, { "logfile", 'L', 0, G_OPTION_ARG_FILENAME, &logfile, "Log file name to use, by default stdout is used", NULL }, { "tz-utc", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &skip_tz, "SET TIME_ZONE='+00:00' at top of dump to allow dumping of TIMESTAMP data when a server has data in different time zones or data is being moved between servers with different time zones, defaults to on use --skip-tz-utc to disable.", NULL }, { "skip-tz-utc", 0, 0, G_OPTION_ARG_NONE, &skip_tz, "", NULL }, { "use-savepoints", 0, 0, G_OPTION_ARG_NONE, &use_savepoints, "Use savepoints to reduce metadata locking issues, needs SUPER privilege", NULL }, { "success-on-1146", 0, 0, G_OPTION_ARG_NONE, &success_on_1146, "Not increment error count and Warning instead of Critical in case of table doesn't exist", NULL}, { "lock-all-tables", 0, 0, G_OPTION_ARG_NONE, &lock_all_tables, "Use LOCK TABLE for all, instead of FTWRL", NULL}, { "updated-since", 'U', 0, G_OPTION_ARG_INT, &updated_since, "Use Update_time to dump only tables updated in the last U days", NULL}, { "trx-consistency-only", 0, 0, G_OPTION_ARG_NONE, &trx_consistency_only, "Transactional consistency only", NULL}, { "complete-insert", 0, 0, G_OPTION_ARG_NONE, &complete_insert, "Use complete INSERT statements that include column names", NULL}, { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } }; struct tm tval; void dump_schema_data(MYSQL *conn, char *database, char *table, char *filename); void dump_triggers_data(MYSQL *conn, char *database, char *table, char *filename); void dump_view_data(MYSQL *conn, char *database, char *table, char *filename, char *filename2); void dump_schema(MYSQL *conn, char *database, char *table, struct configuration *conf); void dump_view(char *database, char *table, struct configuration *conf); void dump_table(MYSQL *conn, char *database, char *table, struct configuration *conf, gboolean is_innodb); void dump_tables(MYSQL *, GList *, struct configuration *); void dump_schema_post(char *database, struct configuration *conf); void restore_charset(GString* statement); void set_charset(GString* statement, char *character_set, char *collation_connection); void dump_schema_post_data(MYSQL *conn, char *database, char *filename); guint64 dump_table_data(MYSQL *, FILE *, char *, char *, char *, char *); void dump_database(MYSQL *, char *, FILE *, struct configuration *); void dump_create_database(MYSQL *conn, char *database); void get_tables(MYSQL * conn, struct configuration *); void get_not_updated(MYSQL *conn); GList * get_chunks_for_table(MYSQL *, char *, char*, struct configuration *conf); guint64 estimate_count(MYSQL *conn, char *database, char *table, char *field, char *from, char *to); void dump_table_data_file(MYSQL *conn, char *database, char *table, char *where, char *filename); void create_backup_dir(char *directory); gboolean write_data(FILE *,GString*); gboolean check_regex(char *database, char *table); gboolean check_skiplist(char *database, char *table); int tables_skiplist_cmp(gconstpointer a, gconstpointer b, gpointer user_data); void read_tables_skiplist(const gchar * filename); void no_log(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data); void set_verbose(guint verbosity); #ifdef WITH_BINLOG MYSQL *reconnect_for_binlog(MYSQL *thrconn); void *binlog_thread(void *data); #endif void start_dump(MYSQL *conn); MYSQL *create_main_connection(); void *exec_thread(void *data); void write_log_file(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data); void no_log(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { (void) log_domain; (void) log_level; (void) message; (void) user_data; } void set_verbose(guint verbosity) { if (logfile) { logoutfile = g_fopen(logfile, "w"); if (!logoutfile) { g_critical("Could not open log file '%s' for writing: %d", logfile, errno); exit(EXIT_FAILURE); } } switch (verbosity) { case 0: g_log_set_handler(NULL, (GLogLevelFlags)(G_LOG_LEVEL_MASK), no_log, NULL); break; case 1: g_log_set_handler(NULL, (GLogLevelFlags)(G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE), no_log, NULL); if (logfile) g_log_set_handler(NULL, (GLogLevelFlags)(G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL), write_log_file, NULL); break; case 2: g_log_set_handler(NULL, (GLogLevelFlags)(G_LOG_LEVEL_MESSAGE), no_log, NULL); if (logfile) g_log_set_handler(NULL, (GLogLevelFlags)(G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL), write_log_file, NULL); break; default: if (logfile) g_log_set_handler(NULL, (GLogLevelFlags)(G_LOG_LEVEL_MASK), write_log_file, NULL); break; } } gboolean sig_triggered(gpointer user_data) { (void) user_data; g_message("Shutting down gracefully"); shutdown_triggered= TRUE; g_main_loop_quit(m1); return FALSE; } void clear_dump_directory() { GError *error= NULL; char* dump_directory= g_strdup_printf("%s/%d", output_directory, dump_number); GDir* dir= g_dir_open(dump_directory, 0, &error); if (error) { g_critical("cannot open directory %s, %s\n", dump_directory, error->message); errors++; return; } const gchar* filename= NULL; while((filename= g_dir_read_name(dir))) { gchar* path= g_build_filename(dump_directory, filename, NULL); if (g_unlink(path) == -1) { g_critical("error removing file %s (%d)\n", path, errno); errors++; return; } g_free(path); } g_dir_close(dir); g_free(dump_directory); } gboolean run_snapshot(gpointer *data) { (void) data; g_async_queue_push(start_scheduled_dump,GINT_TO_POINTER(1)); return (shutdown_triggered) ? FALSE : TRUE; } /* Check database.table string against regular expression */ gboolean check_regex(char *database, char *table) { /* This is not going to be used in threads */ static pcre *re = NULL; int rc; int ovector[9]= {0}; const char *error; int erroroffset; char *p; /* Let's compile the RE before we do anything */ if (!re) { re = pcre_compile(regexstring,PCRE_CASELESS|PCRE_MULTILINE,&error,&erroroffset,NULL); if(!re) { g_critical("Regular expression fail: %s", error); exit(EXIT_FAILURE); } } p=g_strdup_printf("%s.%s",database,table); rc = pcre_exec(re,NULL,p,strlen(p),0,0,ovector,9); g_free(p); return (rc>0)?TRUE:FALSE; } /* Check database.table string against skip list; returns TRUE if found */ gboolean check_skiplist(char *database, char *table) { if (g_sequence_lookup( tables_skiplist, g_strdup_printf("%s.%s", database, table), tables_skiplist_cmp, NULL )) { return TRUE; } else { return FALSE; }; } /* Comparison function for skiplist sort and lookup */ int tables_skiplist_cmp(gconstpointer a, gconstpointer b, gpointer user_data) { /* Not using user_data, but needed for function prototype, shutting up * compiler warnings about unused variable */ (void)user_data; /* Any sorting function would work, as long as its usage is consistent * between sort and lookup. strcmp should be one of the fastest. */ return strcmp(a, b); }; /* Read the list of tables to skip from the given filename, and prepares them * for future lookups. */ void read_tables_skiplist(const gchar * filename) { GIOChannel * tables_skiplist_channel = NULL; gchar * buf = NULL; GError * error = NULL; /* Create skiplist if it does not exist */ if (!tables_skiplist) { tables_skiplist = g_sequence_new(NULL); }; tables_skiplist_channel = g_io_channel_new_file(filename, "r", &error); /* Error opening/reading the file? bail out. */ if (!tables_skiplist_channel) { g_critical("cannot read/open file %s, %s\n", filename, error->message); errors++; return; }; /* Read lines, push them to the list */ do { g_io_channel_read_line(tables_skiplist_channel, &buf, NULL, NULL, NULL); if (buf) { g_strchomp(buf); g_sequence_append(tables_skiplist, buf); }; } while (buf); g_io_channel_shutdown(tables_skiplist_channel, FALSE, NULL); /* Sort the list, so that lookups work */ g_sequence_sort(tables_skiplist, tables_skiplist_cmp, NULL); g_message("Omit list file contains %d tables to skip\n", g_sequence_get_length(tables_skiplist)); return; }; /* Write some stuff we know about snapshot, before it changes */ void write_snapshot_info(MYSQL *conn, FILE *file) { MYSQL_RES *master=NULL, *slave=NULL, *mdb=NULL; MYSQL_FIELD *fields; MYSQL_ROW row; char *masterlog=NULL; char *masterpos=NULL; char *mastergtid=NULL; char *connname=NULL; char *slavehost=NULL; char *slavelog=NULL; char *slavepos=NULL; char *slavegtid=NULL; guint isms; guint i; mysql_query(conn,"SHOW MASTER STATUS"); master=mysql_store_result(conn); if (master && (row=mysql_fetch_row(master))) { masterlog=row[0]; masterpos=row[1]; /* Oracle/Percona GTID */ if(mysql_num_fields(master) == 5) { mastergtid=row[4]; } else { /* Let's try with MariaDB 10.x */ /* Use gtid_binlog_pos due to issue with gtid_current_pos with galera cluster, gtid_binlog_pos works as well with normal mariadb server https://jira.mariadb.org/browse/MDEV-10279 */ mysql_query(conn, "SELECT @@gtid_binlog_pos"); mdb=mysql_store_result(conn); if (mdb && (row=mysql_fetch_row(mdb))) { mastergtid=row[0]; } } } if (masterlog) { fprintf(file, "SHOW MASTER STATUS:\n\tLog: %s\n\tPos: %s\n\tGTID:%s\n\n", masterlog, masterpos,mastergtid); g_message("Written master status"); } isms = 0; mysql_query(conn,"SELECT @@default_master_connection"); MYSQL_RES *rest = mysql_store_result(conn); if(rest != NULL && mysql_num_rows(rest)){ mysql_free_result(rest); g_message("Multisource slave detected."); isms = 1; } if (isms) mysql_query(conn, "SHOW ALL SLAVES STATUS"); else mysql_query(conn, "SHOW SLAVE STATUS"); slave=mysql_store_result(conn); while (slave && (row=mysql_fetch_row(slave))) { fields=mysql_fetch_fields(slave); for (i=0; iconf; // mysql_init is not thread safe, especially in Connector/C g_mutex_lock(init_mutex); MYSQL *thrconn = mysql_init(NULL); g_mutex_unlock(init_mutex); configure_connection(thrconn,"mydumper"); if (!mysql_real_connect(thrconn, hostname, username, password, NULL, port, socket_path, 0)) { g_critical("Failed to connect to database: %s", mysql_error(thrconn)); exit(EXIT_FAILURE); } else { g_message("Thread %d connected using MySQL connection ID %lu", td->thread_id, mysql_thread_id(thrconn)); } if(use_savepoints && mysql_query(thrconn, "SET SQL_LOG_BIN = 0")){ g_critical("Failed to disable binlog for the thread: %s",mysql_error(thrconn)); exit(EXIT_FAILURE); } if ((detected_server == SERVER_TYPE_MYSQL) && mysql_query(thrconn, "SET SESSION wait_timeout = 2147483")){ g_warning("Failed to increase wait_timeout: %s", mysql_error(thrconn)); } if (mysql_query(thrconn, "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ")) { g_critical("Failed to set isolation level: %s", mysql_error(thrconn)); exit(EXIT_FAILURE); } if (mysql_query(thrconn, "START TRANSACTION /*!40108 WITH CONSISTENT SNAPSHOT */")) { g_critical("Failed to start consistent snapshot: %s",mysql_error(thrconn)); exit(EXIT_FAILURE); } if(!skip_tz && mysql_query(thrconn, "/*!40103 SET TIME_ZONE='+00:00' */")){ g_critical("Failed to set time zone: %s",mysql_error(thrconn)); } /* Unfortunately version before 4.1.8 did not support consistent snapshot transaction starts, so we cheat */ if (need_dummy_read) { mysql_query(thrconn,"SELECT /*!40001 SQL_NO_CACHE */ * FROM mysql.mydumperdummy"); MYSQL_RES *res=mysql_store_result(thrconn); if (res) mysql_free_result(res); } if(need_dummy_toku_read){ mysql_query(thrconn,"SELECT /*!40001 SQL_NO_CACHE */ * FROM mysql.tokudbdummy"); MYSQL_RES *res=mysql_store_result(thrconn); if (res) mysql_free_result(res); } mysql_query(thrconn, "/*!40101 SET NAMES binary*/"); g_async_queue_push(conf->ready,GINT_TO_POINTER(1)); struct job* job= NULL; struct table_job* tj= NULL; struct schema_job* sj= NULL; struct view_job* vj= NULL; struct schema_post_job* sp= NULL; #ifdef WITH_BINLOG struct binlog_job* bj= NULL; #endif /* if less locking we need to wait until that threads finish progressively waking up these threads */ if(less_locking){ g_mutex_lock(ll_mutex); while (less_locking_threads >= td->thread_id) { g_cond_wait (ll_cond, ll_mutex); } g_mutex_unlock(ll_mutex); } for(;;) { GTimeVal tv; g_get_current_time(&tv); g_time_val_add(&tv,1000*1000*1); job=(struct job *)g_async_queue_pop(conf->queue); if (shutdown_triggered && (job->type != JOB_SHUTDOWN)) { continue; } switch (job->type) { case JOB_DUMP: tj=(struct table_job *)job->job_data; if (tj->where) g_message("Thread %d dumping data for `%s`.`%s` where %s", td->thread_id, tj->database, tj->table, tj->where); else g_message("Thread %d dumping data for `%s`.`%s`", td->thread_id, tj->database, tj->table); if(use_savepoints && mysql_query(thrconn, "SAVEPOINT mydumper")){ g_critical("Savepoint failed: %s",mysql_error(thrconn)); } dump_table_data_file(thrconn, tj->database, tj->table, tj->where, tj->filename); if(use_savepoints && mysql_query(thrconn, "ROLLBACK TO SAVEPOINT mydumper")){ g_critical("Rollback to savepoint failed: %s",mysql_error(thrconn)); } if(tj->database) g_free(tj->database); if(tj->table) g_free(tj->table); if(tj->where) g_free(tj->where); if(tj->filename) g_free(tj->filename); g_free(tj); g_free(job); break; case JOB_DUMP_NON_INNODB: tj=(struct table_job *)job->job_data; if (tj->where) g_message("Thread %d dumping data for `%s`.`%s` where %s", td->thread_id, tj->database, tj->table, tj->where); else g_message("Thread %d dumping data for `%s`.`%s`", td->thread_id, tj->database, tj->table); if(use_savepoints && mysql_query(thrconn, "SAVEPOINT mydumper")){ g_critical("Savepoint failed: %s",mysql_error(thrconn)); } dump_table_data_file(thrconn, tj->database, tj->table, tj->where, tj->filename); if(use_savepoints && mysql_query(thrconn, "ROLLBACK TO SAVEPOINT mydumper")){ g_critical("Rollback to savepoint failed: %s",mysql_error(thrconn)); } if(tj->database) g_free(tj->database); if(tj->table) g_free(tj->table); if(tj->where) g_free(tj->where); if(tj->filename) g_free(tj->filename); g_free(tj); g_free(job); if (g_atomic_int_dec_and_test(&non_innodb_table_counter) && g_atomic_int_get(&non_innodb_done)) { g_async_queue_push(conf->unlock_tables, GINT_TO_POINTER(1)); } break; case JOB_SCHEMA: sj=(struct schema_job *)job->job_data; g_message("Thread %d dumping schema for `%s`.`%s`", td->thread_id, sj->database, sj->table); dump_schema_data(thrconn, sj->database, sj->table, sj->filename); if(sj->database) g_free(sj->database); if(sj->table) g_free(sj->table); if(sj->filename) g_free(sj->filename); g_free(sj); g_free(job); break; case JOB_VIEW: vj=(struct view_job *)job->job_data; g_message("Thread %d dumping view for `%s`.`%s`", td->thread_id, vj->database, vj->table); dump_view_data(thrconn, vj->database, vj->table, vj->filename, vj->filename2); if(vj->database) g_free(vj->database); if(vj->table) g_free(vj->table); if(vj->filename) g_free(vj->filename); if(vj->filename2) g_free(vj->filename2); g_free(vj); g_free(job); break; case JOB_TRIGGERS: sj=(struct schema_job *)job->job_data; g_message("Thread %d dumping triggers for `%s`.`%s`", td->thread_id, sj->database, sj->table); dump_triggers_data(thrconn, sj->database, sj->table, sj->filename); if(sj->database) g_free(sj->database); if(sj->table) g_free(sj->table); if(sj->filename) g_free(sj->filename); g_free(sj); g_free(job); break; case JOB_SCHEMA_POST: sp=(struct schema_post_job *)job->job_data; g_message("Thread %d dumping SP and VIEWs for `%s`", td->thread_id, sp->database); dump_schema_post_data(thrconn, sp->database, sp->filename); if(sp->database) g_free(sp->database); if(sp->filename) g_free(sp->filename); g_free(sp); g_free(job); break; #ifdef WITH_BINLOG case JOB_BINLOG: thrconn= reconnect_for_binlog(thrconn); g_message("Thread %d connected using MySQL connection ID %lu (in binlog mode)", td->thread_id, mysql_thread_id(thrconn)); bj=(struct binlog_job *)job->job_data; g_message("Thread %d dumping binary log file %s", td->thread_id, bj->filename); get_binlog_file(thrconn, bj->filename, binlog_directory, bj->start_position, bj->stop_position, FALSE); if(bj->filename) g_free(bj->filename); g_free(bj); g_free(job); break; #endif case JOB_SHUTDOWN: g_message("Thread %d shutting down", td->thread_id); if (thrconn) mysql_close(thrconn); g_free(job); mysql_thread_end(); return NULL; break; default: g_critical("Something very bad happened!"); exit(EXIT_FAILURE); } } if (thrconn) mysql_close(thrconn); mysql_thread_end(); return NULL; } void *process_queue_less_locking(struct thread_data *td) { struct configuration *conf= td->conf; // mysql_init is not thread safe, especially in Connector/C g_mutex_lock(init_mutex); MYSQL *thrconn = mysql_init(NULL); g_mutex_unlock(init_mutex); configure_connection(thrconn,"mydumper"); if (!mysql_real_connect(thrconn, hostname, username, password, NULL, port, socket_path, 0)) { g_critical("Failed to connect to database: %s", mysql_error(thrconn)); exit(EXIT_FAILURE); } else { g_message("Thread %d connected using MySQL connection ID %lu", td->thread_id, mysql_thread_id(thrconn)); } if ((detected_server == SERVER_TYPE_MYSQL) && mysql_query(thrconn, "SET SESSION wait_timeout = 2147483")){ g_warning("Failed to increase wait_timeout: %s", mysql_error(thrconn)); } if(!skip_tz && mysql_query(thrconn, "/*!40103 SET TIME_ZONE='+00:00' */")){ g_critical("Failed to set time zone: %s",mysql_error(thrconn)); } mysql_query(thrconn, "/*!40101 SET NAMES binary*/"); g_async_queue_push(conf->ready_less_locking,GINT_TO_POINTER(1)); struct job* job= NULL; struct table_job* tj= NULL; struct tables_job* mj=NULL; struct schema_job* sj= NULL; struct view_job* vj= NULL; struct schema_post_job* sp= NULL; #ifdef WITH_BINLOG struct binlog_job* bj= NULL; #endif GList* glj; int first = 1; GString *query= g_string_sized_new(1024); GString *prev_table = g_string_sized_new(100); GString *prev_database = g_string_sized_new(100); for(;;) { GTimeVal tv; g_get_current_time(&tv); g_time_val_add(&tv,1000*1000*1); job=(struct job *)g_async_queue_pop(conf->queue_less_locking); if (shutdown_triggered && (job->type != JOB_SHUTDOWN)) { continue; } switch (job->type) { case JOB_LOCK_DUMP_NON_INNODB: mj=(struct tables_job *)job->job_data; glj = g_list_copy(mj->table_job_list); for (glj= g_list_first(glj); glj; glj= g_list_next(glj)) { tj = (struct table_job *)glj->data; if(first){ g_string_printf(query, "LOCK TABLES `%s`.`%s` READ LOCAL",tj->database,tj->table); first = 0; }else{ if(g_ascii_strcasecmp(prev_database->str, tj->database) || g_ascii_strcasecmp(prev_table->str, tj->table)){ g_string_append_printf(query, ", `%s`.`%s` READ LOCAL",tj->database,tj->table); } } g_string_printf(prev_table, "%s", tj->table); g_string_printf(prev_database, "%s", tj->database); } first = 1; if(mysql_query(thrconn,query->str)){ g_critical("Non Innodb lock tables fail: %s", mysql_error(thrconn)); exit(EXIT_FAILURE); } if (g_atomic_int_dec_and_test(&non_innodb_table_counter) && g_atomic_int_get(&non_innodb_done)) { g_async_queue_push(conf->unlock_tables, GINT_TO_POINTER(1)); } for (mj->table_job_list= g_list_first(mj->table_job_list); mj->table_job_list; mj->table_job_list= g_list_next(mj->table_job_list)) { tj = (struct table_job *)mj->table_job_list->data; if (tj->where) g_message("Thread %d dumping data for `%s`.`%s` where %s", td->thread_id, tj->database, tj->table, tj->where); else g_message("Thread %d dumping data for `%s`.`%s`", td->thread_id, tj->database, tj->table); dump_table_data_file(thrconn, tj->database, tj->table, tj->where, tj->filename); if(tj->database) g_free(tj->database); if(tj->table) g_free(tj->table); if(tj->where) g_free(tj->where); if(tj->filename) g_free(tj->filename); g_free(tj); } mysql_query(thrconn, "UNLOCK TABLES /* Non Innodb */"); g_free(g_list_first(mj->table_job_list)); g_free(mj); g_free(job); break; case JOB_SCHEMA: sj=(struct schema_job *)job->job_data; g_message("Thread %d dumping schema for `%s`.`%s`", td->thread_id, sj->database, sj->table); dump_schema_data(thrconn, sj->database, sj->table, sj->filename); if(sj->database) g_free(sj->database); if(sj->table) g_free(sj->table); if(sj->filename) g_free(sj->filename); g_free(sj); g_free(job); break; case JOB_VIEW: vj=(struct view_job *)job->job_data; g_message("Thread %d dumping view for `%s`.`%s`", td->thread_id, sj->database, sj->table); dump_view_data(thrconn, vj->database, vj->table, vj->filename, vj->filename2); if(vj->database) g_free(vj->database); if(vj->table) g_free(vj->table); if(vj->filename) g_free(vj->filename); if(vj->filename2) g_free(vj->filename2); g_free(vj); g_free(job); break; case JOB_TRIGGERS: sj=(struct schema_job *)job->job_data; g_message("Thread %d dumping triggers for `%s`.`%s`", td->thread_id, sj->database, sj->table); dump_triggers_data(thrconn, sj->database, sj->table, sj->filename); if(sj->database) g_free(sj->database); if(sj->table) g_free(sj->table); if(sj->filename) g_free(sj->filename); g_free(sj); g_free(job); break; case JOB_SCHEMA_POST: sp=(struct schema_post_job *)job->job_data; g_message("Thread %d dumping SP and VIEWs for `%s`", td->thread_id, sp->database); dump_schema_post_data(thrconn, sp->database, sp->filename); if(sp->database) g_free(sp->database); if(sp->filename) g_free(sp->filename); g_free(sp); g_free(job); break; #ifdef WITH_BINLOG case JOB_BINLOG: thrconn= reconnect_for_binlog(thrconn); g_message("Thread %d connected using MySQL connection ID %lu (in binlog mode)", td->thread_id, mysql_thread_id(thrconn)); bj=(struct binlog_job *)job->job_data; g_message("Thread %d dumping binary log file %s", td->thread_id, bj->filename); get_binlog_file(thrconn, bj->filename, binlog_directory, bj->start_position, bj->stop_position, FALSE); if(bj->filename) g_free(bj->filename); g_free(bj); g_free(job); break; #endif case JOB_SHUTDOWN: g_message("Thread %d shutting down", td->thread_id); g_mutex_lock(ll_mutex); less_locking_threads--; g_cond_broadcast(ll_cond); g_mutex_unlock(ll_mutex); if (thrconn) mysql_close(thrconn); g_free(job); mysql_thread_end(); return NULL; break; default: g_critical("Something very bad happened!"); exit(EXIT_FAILURE); } } if (thrconn) mysql_close(thrconn); mysql_thread_end(); return NULL; } #ifdef WITH_BINLOG MYSQL *reconnect_for_binlog(MYSQL *thrconn) { if (thrconn) { mysql_close(thrconn); } g_mutex_lock(init_mutex); thrconn= mysql_init(NULL); g_mutex_unlock(init_mutex); configure_connection(thrconn,"mydumper"); int timeout= 1; mysql_options(thrconn, MYSQL_OPT_READ_TIMEOUT, (const char*)&timeout); if (!mysql_real_connect(thrconn, hostname, username, password, NULL, port, socket_path, 0)) { g_critical("Failed to re-connect to database: %s", mysql_error(thrconn)); exit(EXIT_FAILURE); } return thrconn; } #endif int main(int argc, char *argv[]) { GError *error = NULL; GOptionContext *context; g_thread_init(NULL); init_mutex = g_mutex_new(); ll_mutex = g_mutex_new(); ll_cond = g_cond_new(); context = g_option_context_new("multi-threaded MySQL dumping"); GOptionGroup *main_group= g_option_group_new("main", "Main Options", "Main Options", NULL, NULL); g_option_group_add_entries(main_group, entries); g_option_group_add_entries(main_group, common_entries); g_option_context_set_main_group(context, main_group); if (!g_option_context_parse(context, &argc, &argv, &error)) { g_print ("option parsing failed: %s, try --help\n", error->message); exit (EXIT_FAILURE); } g_option_context_free(context); //prompt for password if it's NULL if ( sizeof(password) == 0 || ( password == NULL && askPassword ) ){ password = passwordPrompt(); } //printf("your password is %s and the size is %d \n",password,sizeof(password)); if (program_version) { g_print("mydumper %s, built against MySQL %s\n", VERSION, MYSQL_VERSION_STR); exit (EXIT_SUCCESS); } set_verbose(verbose); time_t t; time(&t);localtime_r(&t,&tval); //rows chunks have precedence over chunk_filesize if (rows_per_file > 0 && chunk_filesize > 0){ chunk_filesize = 0; g_warning("--chunk-filesize disabled by --rows option"); } //until we have an unique option on lock types we need to ensure this if(no_locks || trx_consistency_only) less_locking = 0; /* savepoints workaround to avoid metadata locking issues doesnt work for chuncks */ if(rows_per_file && use_savepoints){ use_savepoints = FALSE; g_warning("--use-savepoints disabled by --rows"); } //clarify binlog coordinates with trx_consistency_only if(trx_consistency_only) g_warning("Using trx_consistency_only, binlog coordinates will not be accurate if you are writing to non transactional tables."); if (!output_directory) output_directory = g_strdup_printf("%s-%04d%02d%02d-%02d%02d%02d",DIRECTORY, tval.tm_year+1900, tval.tm_mon+1, tval.tm_mday, tval.tm_hour, tval.tm_min, tval.tm_sec); create_backup_dir(output_directory); if (daemon_mode) { pid_t pid, sid; pid= fork(); if (pid < 0) exit(EXIT_FAILURE); else if (pid > 0) exit(EXIT_SUCCESS); umask(0); sid= setsid(); if (sid < 0) exit(EXIT_FAILURE); char *dump_directory= g_strdup_printf("%s/0", output_directory); create_backup_dir(dump_directory); g_free(dump_directory); dump_directory= g_strdup_printf("%s/1", output_directory); create_backup_dir(dump_directory); g_free(dump_directory); #ifdef WITH_BINLOG daemon_binlog_directory= g_strdup_printf("%s/%s", output_directory, DAEMON_BINLOGS); create_backup_dir(daemon_binlog_directory); #endif } #ifdef WITH_BINLOG if (need_binlogs) { binlog_directory = g_strdup_printf("%s/%s", output_directory, BINLOG_DIRECTORY); create_backup_dir(binlog_directory); } #endif /* Give ourselves an array of engines to ignore */ if (ignore_engines) ignore = g_strsplit(ignore_engines, ",", 0); /* Give ourselves an array of tables to dump */ if (tables_list) tables = g_strsplit(tables_list, ",", 0); /* Process list of tables to omit if specified */ if (tables_skiplist_file) read_tables_skiplist(tables_skiplist_file); if (daemon_mode) { GError* terror; #ifdef WITH_BINLOG GThread *bthread= g_thread_create(binlog_thread, GINT_TO_POINTER(1), FALSE, &terror); if (bthread == NULL) { g_critical("Could not create binlog thread: %s", terror->message); g_error_free(terror); exit(EXIT_FAILURE); } #endif start_scheduled_dump= g_async_queue_new(); GThread *ethread= g_thread_create(exec_thread, GINT_TO_POINTER(1), FALSE, &terror); if (ethread == NULL) { g_critical("Could not create exec thread: %s", terror->message); g_error_free(terror); exit(EXIT_FAILURE); } // Run initial snapshot run_snapshot(NULL); #if GLIB_MINOR_VERSION < 14 g_timeout_add(snapshot_interval*60*1000, (GSourceFunc) run_snapshot, NULL); #else g_timeout_add_seconds(snapshot_interval*60, (GSourceFunc) run_snapshot, NULL); #endif guint sigsource= g_unix_signal_add(SIGINT, sig_triggered, NULL); sigsource= g_unix_signal_add(SIGTERM, sig_triggered, NULL); m1= g_main_loop_new(NULL, TRUE); g_main_loop_run(m1); g_source_remove(sigsource); } else { MYSQL *conn= create_main_connection(); start_dump(conn); } //sleep(5); mysql_thread_end(); mysql_library_end(); g_free(output_directory); g_strfreev(ignore); g_strfreev(tables); if (logoutfile) { fclose(logoutfile); } exit(errors ? EXIT_FAILURE : EXIT_SUCCESS); } MYSQL *create_main_connection() { MYSQL *conn; conn = mysql_init(NULL); configure_connection(conn,"mydumper"); if (!mysql_real_connect(conn, hostname, username, password, db, port, socket_path, 0)) { g_critical("Error connecting to database: %s", mysql_error(conn)); exit(EXIT_FAILURE); } detected_server= detect_server(conn); if ((detected_server == SERVER_TYPE_MYSQL) && mysql_query(conn, "SET SESSION wait_timeout = 2147483")){ g_warning("Failed to increase wait_timeout: %s", mysql_error(conn)); } if ((detected_server == SERVER_TYPE_MYSQL) && mysql_query(conn, "SET SESSION net_write_timeout = 2147483")){ g_warning("Failed to increase net_write_timeout: %s", mysql_error(conn)); } switch (detected_server) { case SERVER_TYPE_MYSQL: g_message("Connected to a MySQL server"); break; case SERVER_TYPE_DRIZZLE: g_message("Connected to a Drizzle server"); break; default: g_critical("Cannot detect server type"); exit(EXIT_FAILURE); break; } return conn; } void *exec_thread(void *data) { (void) data; while(1) { g_async_queue_pop(start_scheduled_dump); clear_dump_directory(); MYSQL *conn= create_main_connection(); start_dump(conn); // start_dump already closes mysql // mysql_close(conn); mysql_thread_end(); // Don't switch the symlink on shutdown because the dump is probably incomplete. if (!shutdown_triggered) { const char *dump_symlink_source= (dump_number == 0) ? "0" : "1"; char *dump_symlink_dest= g_strdup_printf("%s/last_dump", output_directory); // We don't care if this fails g_unlink(dump_symlink_dest); if (symlink(dump_symlink_source, dump_symlink_dest) == -1) { g_critical("error setting last good dump symlink %s, %d", dump_symlink_dest, errno); } g_free(dump_symlink_dest); dump_number= (dump_number == 1) ? 0 : 1; } } return NULL; } #ifdef WITH_BINLOG void *binlog_thread(void *data) { (void) data; MYSQL_RES *master= NULL; MYSQL_ROW row; MYSQL *conn; conn = mysql_init(NULL); if (defaults_file != NULL) { mysql_options(thrconn,MYSQL_READ_DEFAULT_FILE,defaults_file); } mysql_options(conn,MYSQL_READ_DEFAULT_GROUP,"mydumper"); if (!mysql_real_connect(conn, hostname, username, password, db, port, socket_path, 0)) { g_critical("Error connecting to database: %s", mysql_error(conn)); exit(EXIT_FAILURE); } mysql_query(conn,"SHOW MASTER STATUS"); master= mysql_store_result(conn); if (master && (row= mysql_fetch_row(master))) { MYSQL *binlog_connection= NULL; binlog_connection= reconnect_for_binlog(binlog_connection); binlog_connect_id= mysql_thread_id(binlog_connection); guint64 start_position= g_ascii_strtoull(row[1], NULL, 10); gchar* filename= g_strdup(row[0]); mysql_free_result(master); mysql_close(conn); g_message("Continuous binlog thread connected using MySQL connection ID %lu", mysql_thread_id(binlog_connection)); get_binlog_file(binlog_connection, filename, daemon_binlog_directory, start_position, 0, TRUE); g_free(filename); mysql_close(binlog_connection); } else { mysql_free_result(master); mysql_close(conn); } g_message("Continuous binlog thread shutdown"); mysql_thread_end(); return NULL; } #endif void start_dump(MYSQL *conn) { struct configuration conf = { 1, NULL, NULL, NULL, NULL, NULL, NULL, 0 }; char *p; char *p2; char *p3; char *u; guint64 nits[num_threads]; GList* nitl[num_threads]; int tn = 0; guint64 min = 0; time_t t; struct db_table *dbt; struct schema_post *sp; guint n; FILE* nufile = NULL; guint have_backup_locks = 0; for(n=0;n 0){ if (daemon_mode) u= g_strdup_printf("%s/%d/not_updated_tables", output_directory, dump_number); else u= g_strdup_printf("%s/not_updated_tables", output_directory); nufile=g_fopen(u,"w"); if(!nufile) { g_critical("Couldn't write not_updated_tables file (%d)",errno); exit(EXIT_FAILURE); } get_not_updated(conn); } /* We check SHOW PROCESSLIST, and if there're queries larger than preset value, we terminate the process. This avoids stalling whole server with flush */ if(!no_locks) { if (mysql_query(conn, "SHOW PROCESSLIST")) { g_warning("Could not check PROCESSLIST, no long query guard enabled: %s", mysql_error(conn)); } else { MYSQL_RES *res = mysql_store_result(conn); MYSQL_ROW row; /* Just in case PROCESSLIST output column order changes */ MYSQL_FIELD *fields = mysql_fetch_fields(res); guint i; int tcol=-1, ccol=-1, icol=-1; for(i=0; ilongquery) { if (killqueries) { if (mysql_query(conn,p3=g_strdup_printf("KILL %lu",atol(row[icol])))) g_warning("Could not KILL slow query: %s",mysql_error(conn)); else g_warning("Killed a query that was running for %ss",row[tcol]); g_free(p3); } else { g_critical("There are queries in PROCESSLIST running longer than %us, aborting dump,\n\t" "use --long-query-guard to change the guard value, kill queries (--kill-long-queries) or use \n\tdifferent server for dump", longquery); exit(EXIT_FAILURE); } } } mysql_free_result(res); } } if (!no_locks) { // Percona Backup Locks if(!no_backup_locks){ mysql_query(conn,"SELECT @@have_backup_locks"); MYSQL_RES *rest = mysql_store_result(conn); if(rest != NULL && mysql_num_rows(rest)){ mysql_free_result(rest); g_message("Using Percona Backup Locks"); have_backup_locks=1; } } if(have_backup_locks){ if(mysql_query(conn, "LOCK TABLES FOR BACKUP")) { g_critical("Couldn't acquire LOCK TABLES FOR BACKUP, snapshots will not be consistent: %s",mysql_error(conn)); errors++; } if(mysql_query(conn, "LOCK BINLOG FOR BACKUP")) { g_critical("Couldn't acquire LOCK BINLOG FOR BACKUP, snapshots will not be consistent: %s",mysql_error(conn)); errors++; } }else if(lock_all_tables){ // LOCK ALL TABLES GString *query= g_string_sized_new(16777216); gchar *dbtb = NULL; gchar **dt= NULL; GList *tables_lock = NULL; GList *iter = NULL; guint success = 0; guint retry = 0; guint lock = 1; int i = 0; if(db){ g_string_printf(query, "SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '%s' AND TABLE_TYPE ='BASE TABLE' AND NOT (TABLE_SCHEMA = 'mysql' AND (TABLE_NAME = 'slow_log' OR TABLE_NAME = 'general_log'))", db); } else if (tables) { for (i = 0; tables[i] != NULL; i++){ dt = g_strsplit(tables[i], ".", 0); dbtb = g_strdup_printf("`%s`.`%s`",dt[0],dt[1]); tables_lock = g_list_append(tables_lock,dbtb); } }else{ g_string_printf(query, "SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'data_dictionary') AND NOT (TABLE_SCHEMA = 'mysql' AND (TABLE_NAME = 'slow_log' OR TABLE_NAME = 'general_log'))"); } if (tables_lock == NULL) { if(mysql_query(conn, query->str)){ g_critical("Couldn't get table list for lock all tables: %s",mysql_error(conn)); errors++; }else{ MYSQL_RES *res = mysql_store_result(conn); MYSQL_ROW row; while ((row=mysql_fetch_row(res))) { lock = 1; if (tables) { int table_found=0; for (i = 0; tables[i] != NULL; i++) if (g_ascii_strcasecmp(tables[i], row[1]) == 0) table_found = 1; if (!table_found) lock = 0; } if (lock && tables_skiplist_file && check_skiplist(row[0],row[1])) continue; if (lock && regexstring && !check_regex(row[0],row[1])) continue; if(lock) { dbtb = g_strdup_printf("`%s`.`%s`",row[0],row[1]); tables_lock = g_list_append(tables_lock,dbtb); } } } } // Try three times to get the lock, this is in case of tmp tables disappearing while(!success && retry < 4){ n = 0; iter = tables_lock; for (iter= g_list_first(iter); iter; iter= g_list_next(iter)) { if(n == 0){ g_string_printf(query, "LOCK TABLE %s READ", (char *) iter->data); n = 1; }else{ g_string_append_printf(query, ", %s READ",(char *) iter->data); } } if(mysql_query(conn,query->str)){ gchar *failed_table = NULL; gchar **tmp_fail; tmp_fail = g_strsplit(mysql_error(conn), "'",0); tmp_fail = g_strsplit(tmp_fail[1], ".", 0); failed_table = g_strdup_printf("`%s`.`%s`", tmp_fail[0], tmp_fail[1]); iter = tables_lock; for (iter= g_list_first(iter); iter; iter= g_list_next(iter)) { if(strcmp (iter->data, failed_table) == 0){ tables_lock = g_list_remove(tables_lock, iter->data); } } g_free(tmp_fail); g_free(failed_table); }else{ success = 1; } retry += 1; } if(!success){ g_critical("Lock all tables fail: %s", mysql_error(conn)); exit(EXIT_FAILURE); } g_free(query->str); g_list_free(tables_lock); }else{ if(mysql_query(conn, "FLUSH TABLES WITH READ LOCK")) { g_critical("Couldn't acquire global lock, snapshots will not be consistent: %s",mysql_error(conn)); errors++; } } } else { g_warning("Executing in no-locks mode, snapshot will notbe consistent"); } if (mysql_get_server_version(conn) < 40108) { mysql_query(conn, "CREATE TABLE IF NOT EXISTS mysql.mydumperdummy (a INT) ENGINE=INNODB"); need_dummy_read=1; } //tokudb do not support consistent snapshot mysql_query(conn,"SELECT @@tokudb_version"); MYSQL_RES *rest = mysql_store_result(conn); if(rest != NULL && mysql_num_rows(rest)){ mysql_free_result(rest); g_message("TokuDB detected, creating dummy table for CS"); mysql_query(conn, "CREATE TABLE IF NOT EXISTS mysql.tokudbdummy (a INT) ENGINE=TokuDB"); need_dummy_toku_read=1; } mysql_query(conn, "START TRANSACTION /*!40108 WITH CONSISTENT SNAPSHOT */"); if (need_dummy_read) { mysql_query(conn,"SELECT /*!40001 SQL_NO_CACHE */ * FROM mysql.mydumperdummy"); MYSQL_RES *res=mysql_store_result(conn); if (res) mysql_free_result(res); } if(need_dummy_toku_read){ mysql_query(conn,"SELECT /*!40001 SQL_NO_CACHE */ * FROM mysql.tokudbdummy"); MYSQL_RES *res=mysql_store_result(conn); if (res) mysql_free_result(res); } time(&t); localtime_r(&t,&tval); fprintf(mdfile,"Started dump at: %04d-%02d-%02d %02d:%02d:%02d\n", tval.tm_year+1900, tval.tm_mon+1, tval.tm_mday, tval.tm_hour, tval.tm_min, tval.tm_sec); g_message("Started dump at: %04d-%02d-%02d %02d:%02d:%02d\n", tval.tm_year+1900, tval.tm_mon+1, tval.tm_mday, tval.tm_hour, tval.tm_min, tval.tm_sec); if (detected_server == SERVER_TYPE_MYSQL) { mysql_query(conn, "/*!40101 SET NAMES binary*/"); write_snapshot_info(conn, mdfile); } GThread **threads = g_new(GThread*,num_threads*(less_locking+1)); struct thread_data *td= g_new(struct thread_data, num_threads*(less_locking+1)); if(less_locking){ conf.queue_less_locking = g_async_queue_new(); conf.ready_less_locking = g_async_queue_new(); less_locking_threads = num_threads; for (n=num_threads; ndata; tn = 0; min = nits[0]; for (n=1; ndatalength; } for (n=0; n 0){ g_atomic_int_inc(&non_innodb_table_counter); dump_tables(conn, nitl[n], &conf); } } g_list_free(g_list_first(non_innodb_table)); if(g_atomic_int_get(&non_innodb_table_counter)) g_atomic_int_inc(&non_innodb_done); else g_async_queue_push(conf.unlock_tables, GINT_TO_POINTER(1)); for (n=0; ntype = JOB_SHUTDOWN; g_async_queue_push(conf.queue_less_locking,j); } }else{ for (non_innodb_table= g_list_first(non_innodb_table); non_innodb_table; non_innodb_table= g_list_next(non_innodb_table)) { dbt= (struct db_table*) non_innodb_table->data; dump_table(conn, dbt->database, dbt->table, &conf, FALSE); g_atomic_int_inc(&non_innodb_table_counter); } g_list_free(g_list_first(non_innodb_table)); g_atomic_int_inc(&non_innodb_done); } for (innodb_tables= g_list_first(innodb_tables); innodb_tables; innodb_tables= g_list_next(innodb_tables)) { dbt= (struct db_table*) innodb_tables->data; dump_table(conn, dbt->database, dbt->table, &conf, TRUE); } g_list_free(g_list_first(innodb_tables)); for (table_schemas= g_list_first(table_schemas); table_schemas; table_schemas= g_list_next(table_schemas)) { dbt= (struct db_table*) table_schemas->data; dump_schema(conn, dbt->database, dbt->table, &conf); g_free(dbt->table); g_free(dbt->database); g_free(dbt); } g_list_free(g_list_first(table_schemas)); for (view_schemas= g_list_first(view_schemas); view_schemas; view_schemas= g_list_next(view_schemas)) { dbt= (struct db_table*) view_schemas->data; dump_view(dbt->database, dbt->table, &conf); g_free(dbt->table); g_free(dbt->database); g_free(dbt); } g_list_free(g_list_first(view_schemas)); for (schema_post= g_list_first(schema_post); schema_post; schema_post= g_list_next(schema_post)) { sp= (struct schema_post*) schema_post->data; dump_schema_post(sp->database, &conf); g_free(sp->database); g_free(sp); } g_list_free(g_list_first(schema_post)); if (!no_locks && !trx_consistency_only) { g_async_queue_pop(conf.unlock_tables); g_message("Non-InnoDB dump complete, unlocking tables"); mysql_query(conn, "UNLOCK TABLES /* FTWRL */"); if(have_backup_locks) mysql_query(conn, "UNLOCK BINLOG"); } #ifdef WITH_BINLOG if (need_binlogs) { get_binlogs(conn, &conf); } #endif // close main connection mysql_close(conn); if(less_locking){ for (n=num_threads; ntype = JOB_SHUTDOWN; g_async_queue_push(conf.queue,j); } for (n=0; n 0) fclose(nufile); g_rename(p, p2); g_free(p); g_free(p2); g_message("Finished dump at: %04d-%02d-%02d %02d:%02d:%02d\n", tval.tm_year+1900, tval.tm_mon+1, tval.tm_mday, tval.tm_hour, tval.tm_min, tval.tm_sec); g_free(td); g_free(threads); } void dump_create_database(MYSQL *conn, char *database){ void* outfile = NULL; char* filename; char *query = NULL; MYSQL_RES *result = NULL; MYSQL_ROW row; if (daemon_mode) filename = g_strdup_printf("%s/%d/%s-schema-create.sql%s", output_directory, dump_number, database, (compress_output?".gz":"")); else filename = g_strdup_printf("%s/%s-schema-create.sql%s", output_directory, database, (compress_output?".gz":"")); if (!compress_output) outfile= g_fopen(filename, "w"); else outfile= (void*) gzopen(filename, "w"); if (!outfile) { g_critical("Error: DB: %s Could not create output file %s (%d)", database, filename, errno); errors++; return; } GString* statement = g_string_sized_new(statement_size); query= g_strdup_printf("SHOW CREATE DATABASE `%s`", database); if (mysql_query(conn, query) || !(result= mysql_use_result(conn))) { if(success_on_1146 && mysql_errno(conn) == 1146){ g_warning("Error dumping create database (%s): %s", database, mysql_error(conn)); }else{ g_critical("Error dumping create database (%s): %s", database, mysql_error(conn)); errors++; } g_free(query); return; } /* There should never be more than one row */ row = mysql_fetch_row(result); g_string_append(statement, row[1]); g_string_append(statement, ";\n"); if (!write_data((FILE *)outfile, statement)) { g_critical("Could not write create database for %s", database); errors++; } g_free(query); if (!compress_output) fclose((FILE *)outfile); else gzclose((gzFile)outfile); g_string_free(statement, TRUE); if (result) mysql_free_result(result); g_free(filename); return; } void get_not_updated(MYSQL *conn){ MYSQL_RES *res=NULL; MYSQL_ROW row; gchar *query = g_strdup_printf("SELECT CONCAT(TABLE_SCHEMA,'.',TABLE_NAME) FROM information_schema.TABLES WHERE UPDATE_TIME < NOW() - INTERVAL %d DAY",updated_since); mysql_query(conn,query); g_free(query); res = mysql_store_result(conn); while((row = mysql_fetch_row(res))) no_updated_tables = g_list_append(no_updated_tables, row[0]); } gboolean detect_generated_fields(MYSQL *conn, char *database, char *table){ MYSQL_RES *res=NULL; MYSQL_ROW row; gboolean result = FALSE; gchar *query = g_strdup_printf("select COLUMN_NAME from information_schema.COLUMNS where TABLE_SCHEMA='%s' and TABLE_NAME='%s' and extra like '%%GENERATED%%'", database, table); mysql_query(conn,query); g_free(query); res = mysql_store_result(conn); if((row = mysql_fetch_row(res))) { result = TRUE; } mysql_free_result(res); return result; } GString * get_insertable_fields(MYSQL *conn, char *database, char *table){ MYSQL_RES *res=NULL; MYSQL_ROW row; GString *field_list = g_string_new(""); gchar *query = g_strdup_printf("select COLUMN_NAME from information_schema.COLUMNS where TABLE_SCHEMA='%s' and TABLE_NAME='%s' and extra not like '%%GENERATED%%'", database, table); mysql_query(conn,query); g_free(query); res = mysql_store_result(conn); gboolean first = TRUE; while ((row = mysql_fetch_row(res))) { if(first) { first = FALSE; } else { g_string_append(field_list, ","); } g_string_append(field_list, row[0]); } mysql_free_result(res); return field_list; } /* Heuristic chunks building - based on estimates, produces list of ranges for datadumping WORK IN PROGRESS */ GList * get_chunks_for_table(MYSQL *conn, char *database, char *table, struct configuration *conf) { GList *chunks = NULL; MYSQL_RES *indexes=NULL, *minmax=NULL, *total=NULL; MYSQL_ROW row; char *field = NULL; int showed_nulls=0; /* first have to pick index, in future should be able to preset in configuration too */ gchar *query = g_strdup_printf("SHOW INDEX FROM `%s`.`%s`",database,table); mysql_query(conn,query); g_free(query); indexes=mysql_store_result(conn); while ((row=mysql_fetch_row(indexes))) { if (!strcmp(row[2],"PRIMARY") && (!strcmp(row[3],"1"))) { /* Pick first column in PK, cardinality doesn't matter */ field=row[4]; break; } } /* If no PK found, try using first UNIQUE index */ if (!field) { mysql_data_seek(indexes,0); while ((row=mysql_fetch_row(indexes))) { if(!strcmp(row[1],"0") && (!strcmp(row[3],"1"))) { /* Again, first column of any unique index */ field=row[4]; break; } } } /* Still unlucky? Pick any high-cardinality index */ if (!field && conf->use_any_index) { guint64 max_cardinality=0; guint64 cardinality=0; mysql_data_seek(indexes,0); while ((row=mysql_fetch_row(indexes))) { if(!strcmp(row[3],"1")) { if (row[6]) cardinality = strtoll(row[6],NULL,10); if (cardinality>max_cardinality) { field=row[4]; max_cardinality=cardinality; } } } } /* Oh well, no chunks today - no suitable index */ if (!field) goto cleanup; /* Get minimum/maximum */ mysql_query(conn, query=g_strdup_printf("SELECT %s MIN(`%s`),MAX(`%s`) FROM `%s`.`%s`", (detected_server == SERVER_TYPE_MYSQL) ? "/*!40001 SQL_NO_CACHE */" : "", field, field, database, table)); g_free(query); minmax=mysql_store_result(conn); if (!minmax) goto cleanup; row=mysql_fetch_row(minmax); MYSQL_FIELD * fields=mysql_fetch_fields(minmax); /* Check if all values are NULL */ if (row[0] == NULL) goto cleanup; char *min=row[0]; char *max=row[1]; /* Got total number of rows, skip chunk logic if estimates are low */ guint64 rows = estimate_count(conn, database, table, field, NULL, NULL); if (rows <= rows_per_file) goto cleanup; /* This is estimate, not to use as guarantee! Every chunk would have eventual adjustments */ guint64 estimated_chunks = rows / rows_per_file; guint64 estimated_step, nmin, nmax, cutoff; /* Support just bigger INTs for now, very dumb, no verify approach */ switch (fields[0].type) { case MYSQL_TYPE_LONG: case MYSQL_TYPE_LONGLONG: case MYSQL_TYPE_INT24: /* static stepping */ nmin = strtoll(min,NULL,10); nmax = strtoll(max,NULL,10); estimated_step = (nmax-nmin)/estimated_chunks+1; cutoff = nmin; while(cutoff<=nmax) { chunks=g_list_append(chunks,g_strdup_printf("%s%s%s%s(`%s` >= %llu AND `%s` < %llu)", !showed_nulls?"`":"", !showed_nulls?field:"", !showed_nulls?"`":"", !showed_nulls?" IS NULL OR ":"", field, (unsigned long long)cutoff, field, (unsigned long long)(cutoff+estimated_step))); cutoff+=estimated_step; showed_nulls=1; } default: goto cleanup; } cleanup: if (indexes) mysql_free_result(indexes); if (minmax) mysql_free_result(minmax); if (total) mysql_free_result(total); return chunks; } /* Try to get EXPLAIN'ed estimates of row in resultset */ guint64 estimate_count(MYSQL *conn, char *database, char *table, char *field, char *from, char *to) { char *querybase, *query; int ret; g_assert(conn && database && table); querybase = g_strdup_printf("EXPLAIN SELECT `%s` FROM `%s`.`%s`", (field?field:"*"), database, table); if (from || to) { g_assert(field != NULL); char *fromclause=NULL, *toclause=NULL; char *escaped; if (from) { escaped=g_new(char,strlen(from)*2+1); mysql_real_escape_string(conn,escaped,from,strlen(from)); fromclause = g_strdup_printf(" `%s` >= \"%s\" ", field, escaped); g_free(escaped); } if (to) { escaped=g_new(char,strlen(to)*2+1); mysql_real_escape_string(conn,escaped,from,strlen(from)); toclause = g_strdup_printf( " `%s` <= \"%s\"", field, escaped); g_free(escaped); } query = g_strdup_printf("%s WHERE `%s` %s %s", querybase, (from?fromclause:""), ((from&&to)?"AND":""), (to?toclause:"")); if (toclause) g_free(toclause); if (fromclause) g_free(fromclause); ret=mysql_query(conn,query); g_free(querybase); g_free(query); } else { ret=mysql_query(conn,querybase); g_free(querybase); } if (ret) { g_warning("Unable to get estimates for %s.%s: %s",database,table,mysql_error(conn)); } MYSQL_RES * result = mysql_store_result(conn); MYSQL_FIELD * fields = mysql_fetch_fields(result); guint i; for (i=0; i1 kicks in only in case of 5.0 SHOW FULL TABLES or SHOW TABLE STATUS row[1] == NULL if it is a view in 5.0 'SHOW TABLE STATUS' row[1] == "VIEW" if it is a view in 5.0 'SHOW FULL TABLES' */ if ((detected_server == SERVER_TYPE_MYSQL) && ( row[ccol] == NULL || !strcmp(row[ccol],"VIEW") )) is_view = 1; /* Check for broken tables, i.e. mrg with missing source tbl */ if ( !is_view && row[ecol] == NULL ) { g_warning("Broken table detected, please review: %s.%s", database, row[0]); dump = 0; } /* Skip ignored engines, handy for avoiding Merge, Federated or Blackhole :-) dumps */ if (dump && ignore && !is_view) { for (i = 0; ignore[i] != NULL; i++) { if (g_ascii_strcasecmp(ignore[i], row[ecol]) == 0) { dump = 0; break; } } } /* Skip views */ if (is_view && no_dump_views) dump = 0; if (!dump) continue; /* In case of table-list option is enabled, check if table is part of the list */ if (tables) { int table_found=0; for (i = 0; tables[i] != NULL; i++) if (g_ascii_strcasecmp(tables[i], row[0]) == 0) table_found = 1; if (!table_found) dump = 0; } if (!dump) continue; /* Special tables */ if(g_ascii_strcasecmp(database, "mysql") == 0 && (g_ascii_strcasecmp(row[0], "general_log") == 0 || g_ascii_strcasecmp(row[0], "slow_log") == 0 || g_ascii_strcasecmp(row[0], "innodb_index_stats") == 0 || g_ascii_strcasecmp(row[0], "innodb_table_stats") == 0)){ dump=0; continue; } /* Checks skip list on 'database.table' string */ if (tables_skiplist && check_skiplist(database,row[0])) continue; /* Checks PCRE expressions on 'database.table' string */ if (regexstring && !check_regex(database,row[0])) continue; /* Check if the table was recently updated */ if(no_updated_tables && !is_view){ iter = no_updated_tables; for (iter= g_list_first(iter); iter; iter= g_list_next(iter)) { if(g_ascii_strcasecmp (iter->data, g_strdup_printf("%s.%s", database, row[0])) == 0){ g_message("NO UPDATED TABLE: %s.%s", database, row[0]); fprintf(file, "%s.%s\n", database, row[0]); dump=0; } } } if (!dump) continue; /* Green light! */ struct db_table *dbt = g_new(struct db_table, 1); dbt->database= g_strdup(database); dbt->table= g_strdup(row[0]); if(!row[6]) dbt->datalength = 0; else dbt->datalength = g_ascii_strtoull(row[6], NULL, 10); //if is a view we care only about schema if(!is_view){ // with trx_consistency_only we dump all as innodb_tables // and we can start right now if(!no_data){ if(row[ecol] != NULL && g_ascii_strcasecmp("MRG_MYISAM", row[ecol])){ if (trx_consistency_only) { dump_table(conn, dbt->database, dbt->table, conf, TRUE); }else if (row[ecol] != NULL && !g_ascii_strcasecmp("InnoDB", row[ecol])) { innodb_tables= g_list_append(innodb_tables, dbt); }else if(row[ecol] != NULL && !g_ascii_strcasecmp("TokuDB", row[ecol])){ innodb_tables= g_list_append(innodb_tables, dbt); } else { non_innodb_table= g_list_append(non_innodb_table, dbt); } } } if (!no_schemas){ table_schemas= g_list_append(table_schemas, dbt); } }else{ if (!no_schemas){ view_schemas= g_list_append(view_schemas, dbt); } } } mysql_free_result(result); //Store Procedures and Events //As these are not attached to tables we need to define when we need to dump or not //Having regex filter make this hard because we dont now if a full schema is filtered or not //Also I cant decide this based on tables from a schema being dumped //So I will use only regex to dump or not SP and EVENTS //I only need one match to dump all int post_dump = 0; if(dump_routines){ //SP query = g_strdup_printf("SHOW PROCEDURE STATUS WHERE Db = '%s'", database); if (mysql_query(conn, (query))) { g_critical("Error: DB: %s - Could not execute query: %s", database, mysql_error(conn)); errors++; g_free(query); return; } result = mysql_store_result(conn); while ((row = mysql_fetch_row(result)) && !post_dump){ /* Checks skip list on 'database.sp' string */ if (tables_skiplist && check_skiplist(database,row[1])) continue; /* Checks PCRE expressions on 'database.sp' string */ if (regexstring && !check_regex(database,row[1])) continue; post_dump = 1; } if(!post_dump){ //FUNCTIONS query = g_strdup_printf("SHOW FUNCTION STATUS WHERE Db = '%s'", database); if (mysql_query(conn, (query))) { g_critical("Error: DB: %s - Could not execute query: %s", database, mysql_error(conn)); errors++; g_free(query); return; } result = mysql_store_result(conn); while ((row = mysql_fetch_row(result)) && !post_dump){ /* Checks skip list on 'database.sp' string */ if (tables_skiplist_file && check_skiplist(database,row[1])) continue; /* Checks PCRE expressions on 'database.sp' string */ if (regexstring && !check_regex(database,row[1])) continue; post_dump = 1; } } mysql_free_result(result); } if(dump_events && !post_dump){ //EVENTS query = g_strdup_printf("SHOW EVENTS FROM `%s`", database); if (mysql_query(conn, (query))) { g_critical("Error: DB: %s - Could not execute query: %s", database, mysql_error(conn)); errors++; g_free(query); return; } result = mysql_store_result(conn); while ((row = mysql_fetch_row(result)) && !post_dump){ /* Checks skip list on 'database.sp' string */ if (tables_skiplist_file && check_skiplist(database,row[1])) continue; /* Checks PCRE expressions on 'database.sp' string */ if (regexstring && !check_regex(database,row[1])) continue; post_dump = 1; } mysql_free_result(result); } if(post_dump){ struct schema_post *sp = g_new(struct schema_post, 1); sp->database= g_strdup(database); schema_post= g_list_append(schema_post, sp); } g_free(query); if(file) fflush(file); return; } void get_tables(MYSQL * conn, struct configuration *conf) { gchar **dt= NULL; char *query=NULL; guint i,x; for (x = 0; tables[x] != NULL; x++){ dt = g_strsplit(tables[x], ".", 0); query= g_strdup_printf("SHOW TABLE STATUS FROM %s LIKE '%s'", dt[0], dt[1]); if (mysql_query(conn, (query))) { g_critical("Error: DB: %s - Could not execute query: %s", dt[0], mysql_error(conn)); errors++; return; } MYSQL_RES *result = mysql_store_result(conn); MYSQL_FIELD *fields= mysql_fetch_fields(result); guint ecol= -1; guint ccol= -1; for (i=0; idatabase= g_strdup(dt[0]); dbt->table= g_strdup(dt[1]); if(!row[6]) dbt->datalength = 0; else dbt->datalength = g_ascii_strtoull(row[6], NULL, 10); if(!is_view){ if (trx_consistency_only) { dump_table(conn, dbt->database, dbt->table, conf, TRUE); }else if (!g_ascii_strcasecmp("InnoDB", row[ecol])) { innodb_tables= g_list_append(innodb_tables, dbt); }else if(!g_ascii_strcasecmp("TokuDB", row[ecol])){ innodb_tables= g_list_append(innodb_tables, dbt); } else { non_innodb_table= g_list_append(non_innodb_table, dbt); } if (!no_schemas) { table_schemas= g_list_append(table_schemas, dbt); } }else{ if (!no_schemas){ view_schemas= g_list_append(view_schemas, dbt); } } } } g_free(query); } void set_charset(GString* statement, char *character_set, char *collation_connection){ g_string_printf(statement,"SET @PREV_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT;\n"); g_string_append(statement,"SET @PREV_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS;\n"); g_string_append(statement,"SET @PREV_COLLATION_CONNECTION=@@COLLATION_CONNECTION;\n"); g_string_append_printf(statement, "SET character_set_client = %s;\n", character_set); g_string_append_printf(statement, "SET character_set_results = %s;\n", character_set); g_string_append_printf(statement, "SET collation_connection = %s;\n", collation_connection); } void restore_charset(GString* statement){ g_string_append(statement,"SET character_set_client = @PREV_CHARACTER_SET_CLIENT;\n"); g_string_append(statement,"SET character_set_results = @PREV_CHARACTER_SET_RESULTS;\n"); g_string_append(statement,"SET collation_connection = @PREV_COLLATION_CONNECTION;\n"); } void dump_schema_post_data(MYSQL *conn, char *database, char *filename){ void *outfile; char *query = NULL; MYSQL_RES *result = NULL; MYSQL_RES *result2 = NULL; MYSQL_ROW row; MYSQL_ROW row2; gchar **splited_st= NULL; if (!compress_output) outfile= g_fopen(filename, "w"); else outfile= (void*) gzopen(filename, "w"); if (!outfile) { g_critical("Error: DB: %s Could not create output file %s (%d)", database, filename, errno); errors++; return; } GString* statement = g_string_sized_new(statement_size); if(dump_routines){ // get functions query= g_strdup_printf("SHOW FUNCTION STATUS WHERE Db = '%s'", database); if (mysql_query(conn, query) || !(result= mysql_store_result(conn))) { if(success_on_1146 && mysql_errno(conn) == 1146){ g_warning("Error dumping functions from %s: %s", database, mysql_error(conn)); }else{ g_critical("Error dumping functions from %s: %s", database, mysql_error(conn)); errors++; } g_free(query); return; } while((row = mysql_fetch_row(result))){ set_charset(statement, row[8], row[9]); g_string_append_printf(statement,"DROP FUNCTION IF EXISTS `%s`;\n",row[1]); if (!write_data((FILE *)outfile,statement)) { g_critical("Could not write stored procedure data for %s.%s", database,row[1] ); errors++; return; } g_string_set_size(statement, 0); query= g_strdup_printf("SHOW CREATE FUNCTION `%s`.`%s`", database, row[1]); mysql_query(conn, query); result2= mysql_store_result(conn); row2 = mysql_fetch_row(result2); g_string_printf(statement,"%s",row2[2]); splited_st = g_strsplit(statement->str,";\n",0); g_string_printf(statement, "%s", g_strjoinv("; \n", splited_st)); g_string_append(statement, ";\n"); restore_charset(statement); if (!write_data((FILE *)outfile,statement)) { g_critical("Could not write function data for %s.%s", database,row[1] ); errors++; return; } g_string_set_size(statement, 0); } // get sp query= g_strdup_printf("SHOW PROCEDURE STATUS WHERE Db = '%s'", database); if (mysql_query(conn, query) || !(result= mysql_store_result(conn))) { if(success_on_1146 && mysql_errno(conn) == 1146){ g_warning("Error dumping stored procedures from %s: %s", database, mysql_error(conn)); }else{ g_critical("Error dumping stored procedures from %s: %s", database, mysql_error(conn)); errors++; } g_free(query); return; } while((row = mysql_fetch_row(result))){ set_charset(statement, row[8], row[9]); g_string_append_printf(statement,"DROP PROCEDURE IF EXISTS `%s`;\n",row[1]); if (!write_data((FILE *)outfile,statement)) { g_critical("Could not write stored procedure data for %s.%s", database,row[1] ); errors++; return; } g_string_set_size(statement, 0); query= g_strdup_printf("SHOW CREATE PROCEDURE `%s`.`%s`", database, row[1]); mysql_query(conn, query); result2= mysql_store_result(conn); row2 = mysql_fetch_row(result2); g_string_printf(statement,"%s",row2[2]); splited_st = g_strsplit(statement->str,";\n",0); g_string_printf(statement, "%s", g_strjoinv("; \n", splited_st)); g_string_append(statement, ";\n"); restore_charset(statement); if (!write_data((FILE *)outfile,statement)) { g_critical("Could not write stored procedure data for %s.%s", database,row[1] ); errors++; return; } g_string_set_size(statement, 0); } } // get events if(dump_events){ query= g_strdup_printf("SHOW EVENTS FROM `%s`", database); if (mysql_query(conn, query) || !(result= mysql_store_result(conn))) { if(success_on_1146 && mysql_errno(conn) == 1146){ g_warning("Error dumping events from %s: %s", database, mysql_error(conn)); }else{ g_critical("Error dumping events from %s: %s", database, mysql_error(conn)); errors++; } g_free(query); return; } while((row = mysql_fetch_row(result))){ set_charset(statement, row[12], row[13]); g_string_append_printf(statement,"DROP EVENT IF EXISTS `%s`;\n",row[1]); if (!write_data((FILE *)outfile,statement)) { g_critical("Could not write stored procedure data for %s.%s", database,row[1] ); errors++; return; } query= g_strdup_printf("SHOW CREATE EVENT `%s`.`%s`", database, row[1]); mysql_query(conn, query); result2= mysql_store_result(conn); //DROP EVENT IF EXISTS event_name row2 = mysql_fetch_row(result2); g_string_printf(statement,"%s",row2[3]); splited_st = g_strsplit(statement->str,";\n",0); g_string_printf(statement, "%s", g_strjoinv("; \n", splited_st)); g_string_append(statement, ";\n"); restore_charset(statement); if (!write_data((FILE *)outfile,statement)) { g_critical("Could not write event data for %s.%s", database,row[1] ); errors++; return; } g_string_set_size(statement, 0); } } g_free(query); if (!compress_output) fclose((FILE *)outfile); else gzclose((gzFile)outfile); g_string_free(statement, TRUE); g_strfreev(splited_st); if (result) mysql_free_result(result); if (result2) mysql_free_result(result2); return; } void dump_triggers_data(MYSQL *conn, char *database, char *table, char *filename){ void *outfile; char *query = NULL; MYSQL_RES *result = NULL; MYSQL_RES *result2 = NULL; MYSQL_ROW row; MYSQL_ROW row2; gchar **splited_st= NULL; if (!compress_output) outfile= g_fopen(filename, "w"); else outfile= (void*) gzopen(filename, "w"); if (!outfile) { g_critical("Error: DB: %s Could not create output file %s (%d)", database, filename, errno); errors++; return; } GString* statement = g_string_sized_new(statement_size); // get triggers query= g_strdup_printf("SHOW TRIGGERS FROM `%s` LIKE '%s'", database, table); if (mysql_query(conn, query) || !(result= mysql_store_result(conn))) { if(success_on_1146 && mysql_errno(conn) == 1146){ g_warning("Error dumping triggers (%s.%s): %s", database, table, mysql_error(conn)); }else{ g_critical("Error dumping triggers (%s.%s): %s", database, table, mysql_error(conn)); errors++; } g_free(query); return; } while ((row = mysql_fetch_row(result))) { set_charset(statement, row[8], row[9]); if (!write_data((FILE *)outfile,statement)) { g_critical("Could not write triggers data for %s.%s", database, table); errors++; return; } g_string_set_size(statement, 0); query= g_strdup_printf("SHOW CREATE TRIGGER `%s`.`%s`", database, row[0]); mysql_query(conn, query); result2= mysql_store_result(conn); row2 = mysql_fetch_row(result2); g_string_append_printf(statement,"%s",row2[2]); splited_st = g_strsplit(statement->str,";\n",0); g_string_printf(statement, "%s", g_strjoinv("; \n", splited_st)); g_string_append(statement, ";\n"); restore_charset(statement); if (!write_data((FILE *)outfile,statement)) { g_critical("Could not write triggers data for %s.%s", database, table); errors++; return; } g_string_set_size(statement, 0); } g_free(query); if (!compress_output) fclose((FILE *)outfile); else gzclose((gzFile)outfile); g_string_free(statement, TRUE); g_strfreev(splited_st); if (result) mysql_free_result(result); if (result2) mysql_free_result(result2); return; } void dump_schema_data(MYSQL *conn, char *database, char *table, char *filename) { void *outfile; char *query = NULL; MYSQL_RES *result = NULL; MYSQL_ROW row; if (!compress_output) outfile= g_fopen(filename, "w"); else outfile= (void*) gzopen(filename, "w"); if (!outfile) { g_critical("Error: DB: %s Could not create output file %s (%d)", database, filename, errno); errors++; return; } GString* statement = g_string_sized_new(statement_size); if (detected_server == SERVER_TYPE_MYSQL) { g_string_printf(statement,"/*!40101 SET NAMES binary*/;\n"); g_string_append(statement,"/*!40014 SET FOREIGN_KEY_CHECKS=0*/;\n\n"); if (!skip_tz) { g_string_append(statement,"/*!40103 SET TIME_ZONE='+00:00' */;\n"); } } else { g_string_printf(statement, "SET FOREIGN_KEY_CHECKS=0;\n"); } if (!write_data((FILE *)outfile,statement)) { g_critical("Could not write schema data for %s.%s", database, table); errors++; return; } query= g_strdup_printf("SHOW CREATE TABLE `%s`.`%s`", database, table); if (mysql_query(conn, query) || !(result= mysql_use_result(conn))) { if(success_on_1146 && mysql_errno(conn) == 1146){ g_warning("Error dumping schemas (%s.%s): %s", database, table, mysql_error(conn)); }else{ g_critical("Error dumping schemas (%s.%s): %s", database, table, mysql_error(conn)); errors++; } g_free(query); return; } g_string_set_size(statement, 0); /* There should never be more than one row */ row = mysql_fetch_row(result); g_string_append(statement, row[1]); g_string_append(statement, ";\n"); if (!write_data((FILE *)outfile, statement)) { g_critical("Could not write schema for %s.%s", database, table); errors++; } g_free(query); if (!compress_output) fclose((FILE *)outfile); else gzclose((gzFile)outfile); g_string_free(statement, TRUE); if (result) mysql_free_result(result); return; } void dump_view_data(MYSQL *conn, char *database, char *table, char *filename, char *filename2) { void *outfile, *outfile2; char *query = NULL; MYSQL_RES *result = NULL; MYSQL_ROW row; GString* statement = g_string_sized_new(statement_size); mysql_select_db(conn,database); if (!compress_output){ outfile= g_fopen(filename, "w"); outfile2= g_fopen(filename2, "w"); }else{ outfile= (void*) gzopen(filename, "w"); outfile2= (void*) gzopen(filename2, "w"); } if (!outfile || !outfile2) { g_critical("Error: DB: %s Could not create output file (%d)", database, errno); errors++; return; } if (detected_server == SERVER_TYPE_MYSQL) { g_string_printf(statement,"/*!40101 SET NAMES binary*/;\n"); } if (!write_data((FILE *)outfile,statement)) { g_critical("Could not write schema data for %s.%s", database, table); errors++; return; } g_string_append_printf(statement, "DROP TABLE IF EXISTS `%s`;\n", table); g_string_append_printf(statement, "DROP VIEW IF EXISTS `%s`;\n", table); if (!write_data((FILE *)outfile2,statement)) { g_critical("Could not write schema data for %s.%s", database, table); errors++; return; } //we create myisam tables as workaround //for view dependencies query= g_strdup_printf("SHOW FIELDS FROM `%s`.`%s`", database, table); if (mysql_query(conn, query) || !(result= mysql_use_result(conn))) { if(success_on_1146 && mysql_errno(conn) == 1146){ g_warning("Error dumping schemas (%s.%s): %s", database, table, mysql_error(conn)); }else{ g_critical("Error dumping schemas (%s.%s): %s", database, table, mysql_error(conn)); errors++; } g_free(query); return; } g_string_set_size(statement, 0); g_string_append_printf(statement, "CREATE TABLE `%s`(\n", table); row = mysql_fetch_row(result); g_string_append_printf(statement, "`%s` int", row[0]); while ((row = mysql_fetch_row(result))) { g_string_append(statement, ",\n"); g_string_append_printf(statement, "`%s` int", row[0]); } g_string_append(statement, "\n)ENGINE=MyISAM;\n"); if (!write_data((FILE *)outfile, statement)) { g_critical("Could not write view schema for %s.%s", database, table); errors++; } //real view query= g_strdup_printf("SHOW CREATE VIEW `%s`.`%s`", database, table); if (mysql_query(conn, query) || !(result= mysql_use_result(conn))) { if(success_on_1146 && mysql_errno(conn) == 1146){ g_warning("Error dumping schemas (%s.%s): %s", database, table, mysql_error(conn)); }else{ g_critical("Error dumping schemas (%s.%s): %s", database, table, mysql_error(conn)); errors++; } g_free(query); return; } g_string_set_size(statement, 0); /* There should never be more than one row */ row = mysql_fetch_row(result); set_charset(statement, row[2], row[3]); g_string_append(statement, row[1]); g_string_append(statement, ";\n"); restore_charset(statement); if (!write_data((FILE *)outfile2, statement)) { g_critical("Could not write schema for %s.%s", database, table); errors++; } g_free(query); if (!compress_output){ fclose((FILE *)outfile); fclose((FILE *)outfile2); }else{ gzclose((gzFile)outfile); gzclose((gzFile)outfile2); } g_string_free(statement, TRUE); if (result) mysql_free_result(result); return; } void dump_table_data_file(MYSQL *conn, char *database, char *table, char *where, char *filename) { void *outfile; if (!compress_output) outfile = g_fopen(filename, "w"); else outfile = (void*) gzopen(filename, "w"); if (!outfile) { g_critical("Error: DB: %s TABLE: %s Could not create output file %s (%d)", database, table, filename, errno); errors++; return; } guint64 rows_count = dump_table_data(conn, (FILE *)outfile, database, table, where, filename); if (!rows_count) g_message("Empty table %s.%s", database,table); } void dump_schema(MYSQL *conn, char *database, char *table, struct configuration *conf) { struct job *j = g_new0(struct job,1); struct schema_job *sj = g_new0(struct schema_job,1); j->job_data=(void*) sj; sj->database=g_strdup(database); sj->table=g_strdup(table); j->conf=conf; j->type=JOB_SCHEMA; if (daemon_mode) sj->filename = g_strdup_printf("%s/%d/%s.%s-schema.sql%s", output_directory, dump_number, database, table, (compress_output?".gz":"")); else sj->filename = g_strdup_printf("%s/%s.%s-schema.sql%s", output_directory, database, table, (compress_output?".gz":"")); g_async_queue_push(conf->queue,j); if(dump_triggers){ char *query = NULL; MYSQL_RES *result = NULL; query= g_strdup_printf("SHOW TRIGGERS FROM `%s` LIKE '%s'", database, table); if (mysql_query(conn, query) || !(result= mysql_store_result(conn))) { g_critical("Error Checking triggers for %s.%s. Err: %s", database, table, mysql_error(conn)); errors++; }else{ if(mysql_num_rows(result)){ struct job *t = g_new0(struct job,1); struct schema_job *st = g_new0(struct schema_job,1); t->job_data=(void*) st; st->database=g_strdup(database); st->table=g_strdup(table); t->conf=conf; t->type=JOB_TRIGGERS; if (daemon_mode) st->filename = g_strdup_printf("%s/%d/%s.%s-schema-triggers.sql%s", output_directory, dump_number, database, table, (compress_output?".gz":"")); else st->filename = g_strdup_printf("%s/%s.%s-schema-triggers.sql%s", output_directory, database, table, (compress_output?".gz":"")); g_async_queue_push(conf->queue,t); } } g_free(query); if (result) { mysql_free_result(result); } } return; } void dump_view(char *database, char *table, struct configuration *conf) { struct job *j = g_new0(struct job,1); struct view_job *vj = g_new0(struct view_job,1); j->job_data=(void*) vj; vj->database=g_strdup(database); vj->table=g_strdup(table); j->conf=conf; j->type=JOB_VIEW; if (daemon_mode){ vj->filename = g_strdup_printf("%s/%d/%s.%s-schema.sql%s", output_directory, dump_number, database, table, (compress_output?".gz":"")); vj->filename2 = g_strdup_printf("%s/%d/%s.%s-schema-view.sql%s", output_directory, dump_number, database, table, (compress_output?".gz":"")); }else{ vj->filename = g_strdup_printf("%s/%s.%s-schema.sql%s", output_directory, database, table, (compress_output?".gz":"")); vj->filename2 = g_strdup_printf("%s/%s.%s-schema-view.sql%s", output_directory, database, table, (compress_output?".gz":"")); } g_async_queue_push(conf->queue,j); return; } void dump_schema_post(char *database, struct configuration *conf) { struct job *j = g_new0(struct job,1); struct schema_post_job *sp = g_new0(struct schema_post_job,1); j->job_data=(void*) sp; sp->database=g_strdup(database); j->conf=conf; j->type=JOB_SCHEMA_POST; if (daemon_mode){ sp->filename = g_strdup_printf("%s/%d/%s-schema-post.sql%s", output_directory, dump_number, database, (compress_output?".gz":"")); }else{ sp->filename = g_strdup_printf("%s/%s-schema-post.sql%s", output_directory, database, (compress_output?".gz":"")); } g_async_queue_push(conf->queue,j); return; } void dump_table(MYSQL *conn, char *database, char *table, struct configuration *conf, gboolean is_innodb) { GList * chunks = NULL; if (rows_per_file) chunks = get_chunks_for_table(conn, database, table, conf); if (chunks) { int nchunk=0; for (chunks = g_list_first(chunks); chunks; chunks=g_list_next(chunks)) { struct job *j = g_new0(struct job,1); struct table_job *tj = g_new0(struct table_job,1); j->job_data=(void*) tj; tj->database=g_strdup(database); tj->table=g_strdup(table); j->conf=conf; j->type= is_innodb ? JOB_DUMP : JOB_DUMP_NON_INNODB; if (daemon_mode) tj->filename=g_strdup_printf("%s/%d/%s.%s.%05d.sql%s", output_directory, dump_number, database, table, nchunk,(compress_output?".gz":"")); else tj->filename=g_strdup_printf("%s/%s.%s.%05d.sql%s", output_directory, database, table, nchunk,(compress_output?".gz":"")); tj->where=(char *)chunks->data; if (!is_innodb && nchunk) g_atomic_int_inc(&non_innodb_table_counter); g_async_queue_push(conf->queue,j); nchunk++; } g_list_free(g_list_first(chunks)); } else { struct job *j = g_new0(struct job,1); struct table_job *tj = g_new0(struct table_job,1); j->job_data=(void*) tj; tj->database=g_strdup(database); tj->table=g_strdup(table); j->conf=conf; j->type= is_innodb ? JOB_DUMP : JOB_DUMP_NON_INNODB; if (daemon_mode) tj->filename = g_strdup_printf("%s/%d/%s.%s%s.sql%s", output_directory, dump_number, database, table,(chunk_filesize?".00001":""),(compress_output?".gz":"")); else tj->filename = g_strdup_printf("%s/%s.%s%s.sql%s", output_directory, database, table,(chunk_filesize?".00001":""),(compress_output?".gz":"")); g_async_queue_push(conf->queue,j); return; } } void dump_tables(MYSQL *conn, GList *noninnodb_tables_list, struct configuration *conf){ struct db_table* dbt; GList * chunks = NULL; struct job *j = g_new0(struct job,1); struct tables_job *tjs = g_new0(struct tables_job,1); j->conf=conf; j->type=JOB_LOCK_DUMP_NON_INNODB; j->job_data=(void*) tjs; for (noninnodb_tables_list= g_list_first(noninnodb_tables_list); noninnodb_tables_list; noninnodb_tables_list= g_list_next(noninnodb_tables_list)) { dbt = (struct db_table*) noninnodb_tables_list->data; if (rows_per_file) chunks = get_chunks_for_table(conn, dbt->database, dbt->table, conf); if(chunks){ int nchunk=0; for (chunks = g_list_first(chunks); chunks; chunks=g_list_next(chunks)) { struct table_job *tj = g_new0(struct table_job,1); tj->database = g_strdup_printf("%s",dbt->database); tj->table = g_strdup_printf("%s",dbt->table); if (daemon_mode) tj->filename=g_strdup_printf("%s/%d/%s.%s.%05d.sql%s", output_directory, dump_number, dbt->database, dbt->table, nchunk,(compress_output?".gz":"")); else tj->filename=g_strdup_printf("%s/%s.%s.%05d.sql%s", output_directory, dbt->database, dbt->table, nchunk,(compress_output?".gz":"")); tj->where=(char *)chunks->data; tjs->table_job_list= g_list_append(tjs->table_job_list, tj); nchunk++; } }else{ struct table_job *tj = g_new0(struct table_job,1); tj->database = g_strdup_printf("%s",dbt->database); tj->table = g_strdup_printf("%s",dbt->table); if (daemon_mode) tj->filename = g_strdup_printf("%s/%d/%s.%s%s.sql%s", output_directory, dump_number, dbt->database, dbt->table,(chunk_filesize?".00001":""),(compress_output?".gz":"")); else tj->filename = g_strdup_printf("%s/%s.%s%s.sql%s", output_directory, dbt->database, dbt->table,(chunk_filesize?".00001":""),(compress_output?".gz":"")); tj->where = NULL; tjs->table_job_list= g_list_append(tjs->table_job_list, tj); } } g_async_queue_push(conf->queue_less_locking,j); } /* Do actual data chunk reading/writing magic */ guint64 dump_table_data(MYSQL * conn, FILE *file, char *database, char *table, char *where, char *filename) { guint i; guint fn = 1; guint st_in_file = 0; guint num_fields = 0; guint64 num_rows = 0; guint64 num_rows_st = 0; MYSQL_RES *result = NULL; char *query = NULL; gchar *fcfile = NULL; gchar* filename_prefix = NULL; fcfile = g_strdup (filename); if(chunk_filesize){ gchar** split_filename= g_strsplit(filename, ".00001.sql", 0); filename_prefix= split_filename[0]; g_free(split_filename); } gboolean has_generated_fields = detect_generated_fields(conn, database, table); /* Ghm, not sure if this should be statement_size - but default isn't too big for now */ GString* statement = g_string_sized_new(statement_size); GString* statement_row = g_string_sized_new(0); GString* select_fields; if (has_generated_fields) { select_fields = get_insertable_fields(conn, database, table); } else { select_fields = g_string_new("*"); } /* Poor man's database code */ query = g_strdup_printf("SELECT %s %s FROM `%s`.`%s` %s %s", (detected_server == SERVER_TYPE_MYSQL) ? "/*!40001 SQL_NO_CACHE */" : "", select_fields->str, database, table, where?"WHERE":"", where?where:""); g_string_free(select_fields, TRUE); if (mysql_query(conn, query) || !(result=mysql_use_result(conn))) { //ERROR 1146 if(success_on_1146 && mysql_errno(conn) == 1146){ g_warning("Error dumping table (%s.%s) data: %s ",database, table, mysql_error(conn)); }else{ g_critical("Error dumping table (%s.%s) data: %s ",database, table, mysql_error(conn)); errors++; } g_free(query); return num_rows; } num_fields = mysql_num_fields(result); MYSQL_FIELD *fields = mysql_fetch_fields(result); /* Buffer for escaping field values */ GString *escaped = g_string_sized_new(3000); MYSQL_ROW row; g_string_set_size(statement,0); /* Poor man's data dump code */ while ((row = mysql_fetch_row(result))) { gulong *lengths = mysql_fetch_lengths(result); num_rows++; if (!statement->len){ if(!st_in_file){ if (detected_server == SERVER_TYPE_MYSQL) { g_string_printf(statement,"/*!40101 SET NAMES binary*/;\n"); g_string_append(statement,"/*!40014 SET FOREIGN_KEY_CHECKS=0*/;\n"); if (!skip_tz) { g_string_append(statement,"/*!40103 SET TIME_ZONE='+00:00' */;\n"); } } else { g_string_printf(statement,"SET FOREIGN_KEY_CHECKS=0;\n"); } if (!write_data(file,statement)) { g_critical("Could not write out data for %s.%s", database, table); return num_rows; } } if (complete_insert || has_generated_fields) { if (insert_ignore) { g_string_printf(statement, "INSERT IGNORE INTO `%s` (", table); } else { g_string_printf(statement, "INSERT INTO `%s` (", table); } for (i = 0; i < num_fields; ++i) { if (i > 0) { g_string_append_c(statement, ','); } g_string_append_printf(statement, "`%s`", fields[i].name); } g_string_append(statement, ") VALUES"); } else { if (insert_ignore) { g_string_printf(statement, "INSERT IGNORE INTO `%s` VALUES", table); } else { g_string_printf(statement, "INSERT INTO `%s` VALUES", table); } } num_rows_st = 0; } if (statement_row->len) { g_string_append(statement, statement_row->str); g_string_set_size(statement_row,0); num_rows_st++; } g_string_append(statement_row, "\n("); for (i = 0; i < num_fields; i++) { /* Don't escape safe formats, saves some time */ if (!row[i]) { g_string_append(statement_row, "NULL"); } else if (fields[i].flags & NUM_FLAG) { g_string_append(statement_row, row[i]); } else { /* We reuse buffers for string escaping, growing is expensive just at the beginning */ g_string_set_size(escaped, lengths[i]*2+1); mysql_real_escape_string(conn, escaped->str, row[i], lengths[i]); if (fields[i].type == MYSQL_TYPE_JSON) g_string_append(statement_row, "CONVERT("); g_string_append_c(statement_row,'\"'); g_string_append(statement_row,escaped->str); g_string_append_c(statement_row,'\"'); if (fields[i].type == MYSQL_TYPE_JSON) g_string_append(statement_row, " USING UTF8MB4)"); } if (i < num_fields - 1) { g_string_append_c(statement_row,','); } else { g_string_append_c(statement_row,')'); /* INSERT statement is closed before over limit */ if(statement->len+statement_row->len+1 > statement_size) { if(num_rows_st == 0){ g_string_append(statement, statement_row->str); g_string_set_size(statement_row,0); g_warning("Row bigger than statement_size for %s.%s", database, table); } g_string_append(statement,";\n"); if (!write_data(file,statement)) { g_critical("Could not write out data for %s.%s", database, table); goto cleanup; }else{ st_in_file++; if(chunk_filesize && st_in_file*(guint)ceil((float)statement_size/1024/1024) > chunk_filesize){ fn++; fcfile = g_strdup_printf("%s.%05d.sql%s", filename_prefix,fn,(compress_output?".gz":"")); if (!compress_output){ fclose((FILE *)file); file = g_fopen(fcfile, "w"); } else { gzclose((gzFile)file); file = (void*) gzopen(fcfile, "w"); } st_in_file = 0; } } g_string_set_size(statement,0); } else { if(num_rows_st) g_string_append_c(statement,','); g_string_append(statement, statement_row->str); num_rows_st++; g_string_set_size(statement_row,0); } } } } if (mysql_errno(conn)) { g_critical("Could not read data from %s.%s: %s", database, table, mysql_error(conn)); errors++; } if (statement_row->len > 0) { /* this last row has not been written out */ if (statement->len > 0) { /* strange, should not happen */ g_string_append(statement, statement_row->str); } else { if (complete_insert) { if (insert_ignore) { g_string_printf(statement, "INSERT IGNORE INTO `%s` (", table); } else { g_string_printf(statement, "INSERT INTO `%s` (", table); } for (i = 0; i < num_fields; ++i) { if (i > 0) { g_string_append_c(statement, ','); } g_string_append_printf(statement, "`%s`", fields[i].name); } g_string_append(statement, ") VALUES"); } else { if (insert_ignore) { g_string_printf(statement, "INSERT IGNORE INTO `%s` VALUES", table); } else { g_string_printf(statement, "INSERT INTO `%s` VALUES", table); } } g_string_append(statement, statement_row->str); } } if (statement->len > 0) { g_string_append(statement,";\n"); if (!write_data(file,statement)) { g_critical("Could not write out closing newline for %s.%s, now this is sad!", database, table); goto cleanup; } st_in_file++; } cleanup: g_free(query); g_string_free(escaped,TRUE); g_string_free(statement,TRUE); g_string_free(statement_row,TRUE); if (result) { mysql_free_result(result); } if (!compress_output){ fclose((FILE *)file); } else { gzclose((gzFile)file); } if (!st_in_file && !build_empty_files) { // dropping the useless file if (remove(fcfile)) { g_warning("Failed to remove empty file : %s\n", fcfile); } }else if(chunk_filesize && fn == 1){ fcfile = g_strdup_printf("%s.sql%s", filename_prefix,(compress_output?".gz":"")); g_rename(filename, fcfile); } g_free(filename_prefix); g_free(fcfile); return num_rows; } gboolean write_data(FILE* file,GString * data) { size_t written= 0; ssize_t r= 0; while (written < data->len) { if (!compress_output) r = write(fileno(file), data->str + written, data->len); else r = gzwrite((gzFile)file, data->str + written, data->len); if (r < 0) { g_critical("Couldn't write data to a file: %s", strerror(errno)); errors++; return FALSE; } written += r; } return TRUE; } void write_log_file(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { (void) log_domain; (void) user_data; gchar date[20]; time_t rawtime; struct tm timeinfo; time(&rawtime); localtime_r(&rawtime, &timeinfo); strftime(date, 20, "%Y-%m-%d %H:%M:%S", &timeinfo); GString* message_out = g_string_new(date); if (log_level & G_LOG_LEVEL_DEBUG) { g_string_append(message_out, " [DEBUG] - "); } else if ((log_level & G_LOG_LEVEL_INFO) || (log_level & G_LOG_LEVEL_MESSAGE)) { g_string_append(message_out, " [INFO] - "); } else if (log_level & G_LOG_LEVEL_WARNING) { g_string_append(message_out, " [WARNING] - "); } else if ((log_level & G_LOG_LEVEL_ERROR) || (log_level & G_LOG_LEVEL_CRITICAL)) { g_string_append(message_out, " [ERROR] - "); } g_string_append_printf(message_out, "%s\n", message); if (write(fileno(logoutfile), message_out->str, message_out->len) <= 0) { fprintf(stderr, "Cannot write to log file with error %d. Exiting...", errno); } g_string_free(message_out, TRUE); }