From: Date: August 8 2008 7:27pm Subject: bzr push into mysql-6.0-backup branch (cbell:2680) Bug#35230 List-Archive: http://lists.mysql.com/commits/51213 X-Bug: 35230 Message-Id: <200808081727.m78HR738009755@mail.mysql.com> MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit 2680 Chuck Bell 2008-08-08 BUG#35230 Backup: no backupdir This patch adds the dynamic variable --backupdir which specifies the path where the image files are placed (backup) or accessed (restore) if a path is not specified in the command. added: mysql-test/r/backup_backupdir.result mysql-test/t/backup_backupdir.test modified: mysql-test/lib/mtr_report.pl mysql-test/r/backup_progress.result mysql-test/t/backup_progress.test sql/backup/backup_kernel.h sql/backup/kernel.cc sql/backup/stream.cc sql/backup/stream.h sql/mysqld.cc sql/set_var.cc sql/set_var.h sql/share/errmsg.txt sql/sql_parse.cc === modified file 'mysql-test/lib/mtr_report.pl' --- a/mysql-test/lib/mtr_report.pl 2008-08-08 11:17:37 +0000 +++ b/mysql-test/lib/mtr_report.pl 2008-08-08 17:21:31 +0000 @@ -333,6 +333,12 @@ sub mtr_report_stats ($) { ( /Backup:/ or /Restore:/ or /Can't open the online backup progress tables/ ) or + + # backup_backupdir test is supposed to trigger backup related errors + ($testname eq 'main.backup_backupdir') and + ( + /Backup:/ or /Can't write to backup location/ + ) or # backup_concurrent performs a backup that should fail ($testname eq 'main.backup_concurrent') and === added file 'mysql-test/r/backup_backupdir.result' --- a/mysql-test/r/backup_backupdir.result 1970-01-01 00:00:00 +0000 +++ b/mysql-test/r/backup_backupdir.result 2008-08-08 17:21:31 +0000 @@ -0,0 +1,56 @@ +Reset backupdir +SET @@global.backupdir = @@global.datadir; +DROP DATABASE IF EXISTS bup_backupdir; +Create a database +CREATE DATABASE bup_backupdir; +CREATE TABLE bup_backupdir.t1(a INT); +INSERT INTO bup_backupdir.t1 VALUES (1), (2), (3); +Create a directory for backup images +Reset backupdir +SET @@global.backupdir = '../tmp/backup'; +Perform backup +BACKUP DATABASE bup_backupdir TO 'bup_backupdir1.bak'; +backup_id +# +Ensure backup image file went to the correct location +/backup/bup_backupdir.bak +Perform restore +RESTORE FROM 'bup_backupdir1.bak'; +backup_id +# +Now do the backup and restore by specifying a path. +Perform backup +BACKUP DATABASE bup_backupdir TO '../bup_backupdir2.bak'; +backup_id +# +Ensure backup image file went to the correct location +Perform restore +RESTORE FROM '../bup_backupdir2.bak'; +backup_id +# +Perform backup +BACKUP DATABASE bup_backupdir TO '../../bup_backupdir3.bak'; +backup_id +# +Ensure backup image file went to the correct location +Reset backupdir with ending / +SET @@global.backupdir = '../tmp/backup/'; +Perform backup +BACKUP DATABASE bup_backupdir TO '../../bup_backupdir4.bak'; +backup_id +# +Ensure backup image file went to the correct location +Try a backup to an invalid relative path. +BACKUP DATABASE bup_backupdir TO '../../../../../../../../../../../../../../../../../../bup_backupdir5.bak'; +ERROR HY000: Can't write to backup location '../../../../../../../../../../../../../../../../../../bup_backup' (file already exists?) +Try a backup to an invalid hard path. +BACKUP DATABASE bup_backupdir TO '/dev/not/there/either/bup_backupdir6.bak'; +ERROR HY000: Can't write to backup location '/dev/not/there/either/bup_backupdir6.bak' (file already exists?) +SET @@global.backupdir = 'This_is_really_stupid/not/there/at/all'; +Warnings: +Warning 1725 The path specified for the system variable backupdir cannot be accessed or is invalid. ref: This_is_really_stupid/not/there/at/all +Warning 1725 The path specified for the system variable backupdir cannot be accessed or is invalid. ref: This_is_really_stupid/not/there/at/all +Cleanup +Reset backupdir +SET @@global.backupdir = @@global.datadir; +DROP DATABASE bup_backupdir; === modified file 'mysql-test/r/backup_progress.result' --- a/mysql-test/r/backup_progress.result 2008-08-08 12:59:55 +0000 +++ b/mysql-test/r/backup_progress.result 2008-08-08 17:21:31 +0000 @@ -108,7 +108,7 @@ start_time # stop_time # host_or_server_name localhost username root -backup_file backup_progress_orig.bak +backup_file # user_comment command BACKUP DATABASE backup_progress to 'backup_progress_orig.bak' drivers MyISAM, Default, Snapshot @@ -165,7 +165,7 @@ start_time # stop_time # host_or_server_name localhost username root -backup_file backup_progress_orig.bak +backup_file # user_comment command RESTORE FROM 'backup_progress_orig.bak' drivers MyISAM, Default, Snapshot === added file 'mysql-test/t/backup_backupdir.test' --- a/mysql-test/t/backup_backupdir.test 1970-01-01 00:00:00 +0000 +++ b/mysql-test/t/backup_backupdir.test 2008-08-08 17:21:31 +0000 @@ -0,0 +1,109 @@ +# +# This test is designed to test the new backupdir variable. +# + +--source include/not_embedded.inc + +--echo Reset backupdir +SET @@global.backupdir = @@global.datadir; + +--disable_warnings +DROP DATABASE IF EXISTS bup_backupdir; +--enable_warnings + +--echo Create a database +CREATE DATABASE bup_backupdir; +CREATE TABLE bup_backupdir.t1(a INT); +INSERT INTO bup_backupdir.t1 VALUES (1), (2), (3); + +# +# The following tests the backupdir variable by changing it to +# redirect the output of the backup command/input of the restore +# command. +# + +--error 0,1,2 +rmdir $MYSQLTEST_VARDIR/tmp/backup; + +--echo Create a directory for backup images +mkdir $MYSQLTEST_VARDIR/tmp/backup; + +--echo Reset backupdir +SET @@global.backupdir = '../tmp/backup'; + +--echo Perform backup +--replace_column 1 # +BACKUP DATABASE bup_backupdir TO 'bup_backupdir1.bak'; + +--echo Ensure backup image file went to the correct location +--echo $MYSQLTEST_DIR/backup/bup_backupdir.bak +--file_exists $MYSQLTEST_VARDIR/tmp/backup/bup_backupdir1.bak + +--echo Perform restore +--replace_column 1 # +RESTORE FROM 'bup_backupdir1.bak'; + +--echo Now do the backup and restore by specifying a path. + +--echo Perform backup +--replace_column 1 # +BACKUP DATABASE bup_backupdir TO '../bup_backupdir2.bak'; + +--echo Ensure backup image file went to the correct location +--file_exists $MYSQLTEST_VARDIR/tmp/bup_backupdir2.bak + +--echo Perform restore +--replace_column 1 # +RESTORE FROM '../bup_backupdir2.bak'; + +--echo Perform backup +--replace_column 1 # +BACKUP DATABASE bup_backupdir TO '../../bup_backupdir3.bak'; + +--echo Ensure backup image file went to the correct location +--file_exists $MYSQLTEST_VARDIR/bup_backupdir3.bak + +--echo Reset backupdir with ending / +SET @@global.backupdir = '../tmp/backup/'; + +--echo Perform backup +--replace_column 1 # +BACKUP DATABASE bup_backupdir TO '../../bup_backupdir4.bak'; + +--echo Ensure backup image file went to the correct location +--file_exists $MYSQLTEST_VARDIR/bup_backupdir4.bak + +--echo Try a backup to an invalid relative path. +--error ER_BACKUP_WRITE_LOC +BACKUP DATABASE bup_backupdir TO '../../../../../../../../../../../../../../../../../../bup_backupdir5.bak'; + +--echo Try a backup to an invalid hard path. +--error ER_BACKUP_WRITE_LOC +BACKUP DATABASE bup_backupdir TO '/dev/not/there/either/bup_backupdir6.bak'; + +# +# Attempt to set the backupdir to something invalid. +# +SET @@global.backupdir = 'This_is_really_stupid/not/there/at/all'; + +--echo Cleanup + +--echo Reset backupdir +SET @@global.backupdir = @@global.datadir; + +DROP DATABASE bup_backupdir; + +--error 0,1 +--remove_file $MYSQLTEST_VARDIR/tmp/bup_backupdir1.bak + +--error 0,1 +--remove_file $MYSQLTEST_VARDIR/tmp/backup/bup_backupdir2.bak + +--error 0,1 +--remove_file $MYSQLTEST_VARDIR/master-data/bup_backupdir3.bak + +--error 0,1 +--remove_file $MYSQLTEST_VARDIR/master-data/bup_backupdir4.bak + +--error 0,1 +rmdir $MYSQLTEST_VARDIR/tmp/backup; === modified file 'mysql-test/t/backup_progress.test' --- a/mysql-test/t/backup_progress.test 2008-08-08 12:59:55 +0000 +++ b/mysql-test/t/backup_progress.test 2008-08-08 17:21:31 +0000 @@ -131,7 +131,7 @@ connection con2; reap; #Show results ---replace_column 1 # 2 # 3 # 4 # 10 # 11 # 12 # +--replace_column 1 # 2 # 3 # 4 # 10 # 11 # 12 # 15 # --query_vertical SELECT ob.* FROM mysql.online_backup AS ob JOIN backup_progress.t1_res AS t1 ON ob.backup_id = t1.id; --replace_column 1 # 3 # 4 # SELECT obp.* FROM mysql.online_backup_progress AS obp JOIN backup_progress.t1_res AS t1 ON obp.backup_id = t1.id; @@ -180,7 +180,7 @@ reap; DELETE FROM backup_progress.t1_res; SELECT MAX(backup_id) INTO @bup_id FROM mysql.online_backup WHERE command LIKE "RESTORE FROM%"; INSERT INTO backup_progress.t1_res (id) VALUES (@bup_id); ---replace_column 1 # 2 # 3 # 4 # 10 # 11 # 12 # +--replace_column 1 # 2 # 3 # 4 # 10 # 11 # 12 # 15 # --query_vertical SELECT ob.* FROM mysql.online_backup AS ob JOIN backup_progress.t1_res AS t1 ON ob.backup_id = t1.id; --replace_column 1 # 3 # 4 # SELECT obp.* FROM mysql.online_backup_progress AS obp JOIN backup_progress.t1_res AS t1 ON obp.backup_id = t1.id; === modified file 'sql/backup/backup_kernel.h' --- a/sql/backup/backup_kernel.h 2008-08-08 11:17:37 +0000 +++ b/sql/backup/backup_kernel.h 2008-08-08 17:21:31 +0000 @@ -30,7 +30,7 @@ void backup_shutdown(); Called from the big switch in mysql_execute_command() to execute backup related statement */ -int execute_backup_command(THD*, LEX*); +int execute_backup_command(THD*, LEX*, String*); // forward declarations @@ -66,8 +66,12 @@ class Backup_restore_ctx: public backup: bool is_valid() const; ulonglong op_id() const; - Backup_info* prepare_for_backup(LEX_STRING location, const char*, bool); - Restore_info* prepare_for_restore(LEX_STRING location, const char*); + Backup_info* prepare_for_backup(String *location, + LEX_STRING orig_loc, + const char*, bool); + Restore_info* prepare_for_restore(String *location, + LEX_STRING orig_loc, + const char*); int do_backup(); int do_restore(); === modified file 'sql/backup/kernel.cc' --- a/sql/backup/kernel.cc 2008-08-08 11:17:37 +0000 +++ b/sql/backup/kernel.cc 2008-08-08 17:21:31 +0000 @@ -19,7 +19,8 @@ { Backup_restore_ctx context(thd); // create context instance - Backup_info *info= context.prepare_for_backup(location); // prepare for backup + Backup_info *info= context.prepare_for_backup(location, + orig_loc); // prepare for backup // select objects to backup info->add_all_dbs(); @@ -41,7 +42,8 @@ { Backup_restore_ctx context(thd); // create context instance - Restore_info *info= context.prepare_for_restore(location); // prepare for restore + Restore_info *info= context.prepare_for_restore(location, + orig_loc); // prepare for restore context.do_restore(); // perform restore @@ -118,7 +120,9 @@ static int send_reply(Backup_restore_ctx /** Call backup kernel API to execute backup related SQL statement. - @param lex results of parsing the statement. + @param[IN] thd current thread object reference. + @param[IN] lex results of parsing the statement. + @param[IN] backupdir value of the backupdir variable from server. @note This function sends response to the client (ok, result set or error). @@ -126,7 +130,7 @@ static int send_reply(Backup_restore_ctx */ int -execute_backup_command(THD *thd, LEX *lex) +execute_backup_command(THD *thd, LEX *lex, String *backupdir) { int res= 0; @@ -134,6 +138,7 @@ execute_backup_command(THD *thd, LEX *le DBUG_ASSERT(thd && lex); DEBUG_SYNC(thd, "before_backup_command"); + using namespace backup; Backup_restore_ctx context(thd); // reports errors @@ -141,13 +146,25 @@ execute_backup_command(THD *thd, LEX *le if (!context.is_valid()) DBUG_RETURN(send_error(context, ER_BACKUP_CONTEXT_CREATE)); + /* + Check backupdir for validity. This is needed since we cannot trust + that the path is still valid. Access could have changed or the + folders in the path could have been moved, deleted, etc. + */ + if (backupdir->length() && my_access(backupdir->c_ptr(), (F_OK|W_OK))) + { + context.fatal_error(ER_BACKUP_BACKUPDIR, backupdir->c_ptr()); + DBUG_RETURN(send_error(context, ER_BACKUP_BACKUPDIR, backupdir->c_ptr())); + } + switch (lex->sql_command) { case SQLCOM_BACKUP: { // prepare for backup operation - Backup_info *info= context.prepare_for_backup(lex->backup_dir, thd->query, + Backup_info *info= context.prepare_for_backup(backupdir, lex->backup_dir, + thd->query, lex->backup_compression); // reports errors @@ -192,7 +209,8 @@ execute_backup_command(THD *thd, LEX *le case SQLCOM_RESTORE: { - Restore_info *info= context.prepare_for_restore(lex->backup_dir, thd->query); + Restore_info *info= context.prepare_for_restore(backupdir, lex->backup_dir, + thd->query); if (!info || !info->is_valid()) DBUG_RETURN(send_error(context, ER_BACKUP_RESTORE_PREPARE)); @@ -424,6 +442,11 @@ int Backup_restore_ctx::prepare(LEX_STRI // check if location is valid (we assume it is a file path) + /* + For this error to work correctly, we need to check original + file specified by the user rather than the path formed + using the backupdir. + */ bool bad_filename= (location.length == 0); /* @@ -433,7 +456,7 @@ int Backup_restore_ctx::prepare(LEX_STRI #if defined(__WIN__) || defined(__EMX__) bad_filename = bad_filename || check_if_legal_filename(location.str); - + #endif if (bad_filename) @@ -471,7 +494,8 @@ int Backup_restore_ctx::prepare(LEX_STRI /** Prepare for backup operation. - @param[in] location path to the file where backup image should be stored + @param[in] backupdir path to the file where backup image should be stored + @param[in] orig_loc path as specified on command line for backup image @param[in] query BACKUP query starting the operation @param[in] with_compression backup image compression switch @@ -486,7 +510,9 @@ int Backup_restore_ctx::prepare(LEX_STRI is performed using @c do_backup() method. */ Backup_info* -Backup_restore_ctx::prepare_for_backup(LEX_STRING location, const char *query, +Backup_restore_ctx::prepare_for_backup(String *backupdir, + LEX_STRING orig_loc, + const char *query, bool with_compression) { using namespace backup; @@ -494,7 +520,7 @@ Backup_restore_ctx::prepare_for_backup(L if (m_error) return NULL; - if (Logger::init(BACKUP, location, query)) + if (Logger::init(BACKUP, orig_loc, query)) { fatal_error(ER_BACKUP_LOGGER_INIT); return NULL; @@ -507,16 +533,16 @@ Backup_restore_ctx::prepare_for_backup(L Do preparations common to backup and restore operations. After call to prepare() all meta-data changes are blocked. */ - if (prepare(location)) + if (prepare(orig_loc)) return NULL; - backup::String path(location); - /* Open output stream. */ - - Output_stream *s= new Output_stream(*this, path, with_compression); + Output_stream *s= new Output_stream(*this, + backupdir, + orig_loc, + with_compression); m_stream= s; if (!s) @@ -527,7 +553,11 @@ Backup_restore_ctx::prepare_for_backup(L if (!s->open()) { - fatal_error(ER_BACKUP_WRITE_LOC, path.ptr()); + /* + For this error, use the actual value returned instead of the + path complimented with backupdir. + */ + fatal_error(ER_BACKUP_WRITE_LOC, orig_loc.str); return NULL; } @@ -556,7 +586,8 @@ Backup_restore_ctx::prepare_for_backup(L /** Prepare for restore operation. - @param[in] location path to the file where backup image is stored + @param[in] backupdir path to the file where backup image is stored + @param[in] orig_loc path as specified on command line for backup image @param[in] query RESTORE query starting the operation @returns Pointer to a @c Restore_info instance containing catalogue of the @@ -565,14 +596,16 @@ Backup_restore_ctx::prepare_for_backup(L @note This function reports errors. */ Restore_info* -Backup_restore_ctx::prepare_for_restore(LEX_STRING location, const char *query) +Backup_restore_ctx::prepare_for_restore(String *backupdir, + LEX_STRING orig_loc, + const char *query) { using namespace backup; if (m_error) return NULL; - if (Logger::init(RESTORE, location, query)) + if (Logger::init(RESTORE, orig_loc, query)) { fatal_error(ER_BACKUP_LOGGER_INIT); return NULL; @@ -585,15 +618,14 @@ Backup_restore_ctx::prepare_for_restore( Do preparations common to backup and restore operations. After this call changes of meta-data are blocked. */ - if (prepare(location)) + if (prepare(orig_loc)) return NULL; /* Open input stream. */ - backup::String path(location); - Input_stream *s= new Input_stream(*this, path); + Input_stream *s= new Input_stream(*this, backupdir, orig_loc); m_stream= s; if (!s) @@ -604,7 +636,11 @@ Backup_restore_ctx::prepare_for_restore( if (!s->open()) { - fatal_error(ER_BACKUP_READ_LOC, path.ptr()); + /* + For this error, use the actual value returned instead of the + path complimented with backupdir. + */ + fatal_error(ER_BACKUP_READ_LOC, orig_loc.str); return NULL; } === modified file 'sql/backup/stream.cc' --- a/sql/backup/stream.cc 2008-07-02 11:27:17 +0000 +++ b/sql/backup/stream.cc 2008-08-08 17:21:31 +0000 @@ -190,9 +190,11 @@ extern "C" int stream_read(void *instanc } -Stream::Stream(Logger &log, const ::String &name, int flags) - :m_path(name), m_flags(flags), m_block_size(0), m_log(log) +Stream::Stream(Logger &log, ::String *backupdir, + LEX_STRING orig_loc, int flags) + :m_flags(flags), m_block_size(0), m_log(log) { + prepare_path(backupdir, orig_loc); bzero(&stream, sizeof(stream)); bzero(&buf, sizeof(buf)); bzero(&mem, sizeof(mem)); @@ -201,6 +203,150 @@ Stream::Stream(Logger &log, const ::Stri state= CLOSED; } +/** + Make a relative path. + + This method takes the backupdir and the path specified on the backup command + (orig_loc) and forms a combined path. It walks the backupdir from the right + and the orig_loc from the left to position the paths for concatenation. Output + is written to new_path. + + @param[OUT] new_path The newly combined path + file name. + @param[IN] orig_loc The path + file name specified in the backup command. + @param[IN] backupdir The backupdir system variable value. + + For example, if backupdir = '/dev/tmp' and orig_log = '../backup.bak', the + combined path is = '/dev/backup.bak'. + + @returns + 0 if success + 1 if cannot be combined. Note: m_path is set to '' when this occurs to + trigger error in call stack. +*/ +int Stream::make_relative_path(char *new_path, + char *orig_loc, + ::String *backupdir) +{ + char fixed_base[FN_LEN]; + char fixed_rel[FN_LEN]; + cleanup_dirname(fixed_base, backupdir->c_ptr()); + cleanup_dirname(fixed_rel, orig_loc); + char *rel; + char *base= fixed_base; + bool done= FALSE; + char *found= fixed_rel; + int j= backupdir->length() - 1; + new_path[0]= 0; // initialize the new path to an empty string + + /* + For each '../' in orig_loc, move the pointer to the right for rel. + For each '../' in orig_loc, move pointer to the left for base. + */ + while (!done) + { + rel= found; // save last known position + // find location of next level in relative path + found= strstr(found, FN_PARENTDIR); + if (found) + { + found+= 2; // move past '..\' + if (base[j] == FN_LIBCHAR) + j--; // move past last '\' + if (j == 0) // We are trying to move too far down the path + return 1; + /* + Look for the next level down. + */ + while ((base[j] != FN_LIBCHAR) && j) + j--; + } + else + done= TRUE; + } + strcpy (new_path, base); + strcpy (new_path + j, rel); + return 0; +} + +/** + Prepare path for access. + + This method takes the backupdir and the file name specified on the backup + command (orig_loc) and forms a combined path + file name as follows: + + 1. If orig_loc has a relative path, make it relative to backupdir + 2. If orig_loc has a hard path, use it. + 3. If orig_loc has no path, append to backupdir + + @param[IN] backupdir The backupdir system variable value. + @param[IN] orig_loc The path + file name specified in the backup command. + + @returns 0 +*/ +int Stream::prepare_path(::String *backupdir, + LEX_STRING orig_loc) +{ + int path_len= 0; + + /* + Prepare the path using the backupdir iff no relative path + or no hard path included. + + Relative paths are formed from the backupdir system variable. + */ + if (is_prefix(orig_loc.str, FN_PARENTDIR)) + { + /* + Case 1: Backup image file name has relative path. + Make relative to backupdir. + + Example BACKUP DATATBASE ... TO '../monthly/dec.bak' + If backupdir = '/dev/daily/backup' then the + calculated path becomes + '/dev/monthly/backup/dec.bak' + */ + char new_path[FN_LEN]; + if (make_relative_path(new_path, orig_loc.str, backupdir)) + m_path.length(0); + path_len= strlen(new_path) + 1; + m_path.alloc(path_len); + m_path.length(0); + m_path.append(new_path); + } + else if (!test_if_hard_path(orig_loc.str)) + { + /* + Case 2: Backup image file name has hard path. + + Example BACKUP DATATBASE ... TO '/dev/dec.bak' + If backupdir = '/dev/daily/backup' then the + calculated path becomes + '/dev/dec.bak' + */ + path_len=backupdir->length() + orig_loc.length + 1; + m_path.alloc(path_len); + fn_format(m_path.c_ptr(), orig_loc.str, backupdir->c_ptr(), "", + MY_UNPACK_FILENAME); + } + else + { + /* + Case 3: Backup image file name has no path. + + Example BACKUP DATATBASE ... TO 'week2.bak' + If backupdir = '/dev/weekly/backup' then the + calculated path becomes + '/dev/weekly/week2.bak' + */ + path_len= orig_loc.length + 1; + m_path.alloc(path_len); + m_path.length(0); + m_path.append(orig_loc.str); + } + m_path.length(path_len); + return 0; +} + bool Stream::open() { close(); @@ -228,9 +374,10 @@ bool Stream::rewind() } -Output_stream::Output_stream(Logger &log, const ::String &name, +Output_stream::Output_stream(Logger &log, ::String *backupdir, + LEX_STRING orig_loc, bool with_compression) - :Stream(log, name, 0) + :Stream(log, backupdir, orig_loc, 0) { m_with_compression= with_compression; stream.write= stream_write; @@ -414,8 +561,9 @@ bool Output_stream::rewind() } -Input_stream::Input_stream(Logger &log, const ::String &name) - :Stream(log, name, O_RDONLY) +Input_stream::Input_stream(Logger &log, ::String *backupdir, + LEX_STRING orig_loc) + :Stream(log, backupdir, orig_loc, O_RDONLY) { m_with_compression= false; stream.read= stream_read; === modified file 'sql/backup/stream.h' --- a/sql/backup/stream.h 2008-06-26 16:20:30 +0000 +++ b/sql/backup/stream.h 2008-08-08 17:21:31 +0000 @@ -92,7 +92,7 @@ class Stream: public fd_stream protected: - Stream(Logger&, const ::String&, int); + Stream(Logger&, ::String *, LEX_STRING, int); String m_path; int m_flags; ///< flags used when opening the file @@ -101,6 +101,15 @@ class Stream: public fd_stream friend int stream_write(void*, bstream_blob*, bstream_blob); friend int stream_read(void*, bstream_blob*, bstream_blob); + +private: + + int make_relative_path(char *new_path, + char *orig_loc, + ::String *backupdir); + int prepare_path(::String *backupdir, + LEX_STRING orig_loc); + }; /// Used to write to backup stream. @@ -109,7 +118,7 @@ class Output_stream: { public: - Output_stream(Logger&, const ::String&, bool); + Output_stream(Logger&, ::String *, LEX_STRING, bool); bool open(); void close(); @@ -127,7 +136,7 @@ class Input_stream: { public: - Input_stream(Logger&, const ::String &name); + Input_stream(Logger&, ::String *, LEX_STRING); bool open(); void close(); === modified file 'sql/mysqld.cc' --- a/sql/mysqld.cc 2008-07-09 07:12:43 +0000 +++ b/sql/mysqld.cc 2008-08-08 17:21:31 +0000 @@ -1364,6 +1364,7 @@ void clean_up(bool print_message) my_free(sys_init_slave.value, MYF(MY_ALLOW_ZERO_PTR)); my_free(sys_var_general_log_path.value, MYF(MY_ALLOW_ZERO_PTR)); my_free(sys_var_slow_log_path.value, MYF(MY_ALLOW_ZERO_PTR)); + my_free(sys_var_backupdir.value, MYF(MY_ALLOW_ZERO_PTR)); free_tmpdir(&mysql_tmpdir_list); #ifdef HAVE_REPLICATION my_free(slave_load_tmpdir,MYF(MY_ALLOW_ZERO_PTR)); @@ -5793,6 +5794,8 @@ struct my_option my_long_options[] = "Creating and dropping stored procedures alters ACLs. Disable with --skip-automatic-sp-privileges.", (uchar**) &sp_automatic_privileges, (uchar**) &sp_automatic_privileges, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"backupdir", 'B', "Path used to store backup data.", (uchar**) &sys_var_backupdir.value, + (uchar**) &sys_var_backupdir.value, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"basedir", 'b', "Path to installation directory. All paths are usually resolved relative to this.", (uchar**) &mysql_home_ptr, (uchar**) &mysql_home_ptr, 0, GET_STR, REQUIRED_ARG, @@ -7916,6 +7919,21 @@ mysqld_get_one_option(int optid, case 'l': opt_log=1; break; + case 'B': + { + /* + Check if directory exists and we have permission to create file and + write to file. + */ + if (my_access(argument, (F_OK|W_OK))) + { + sql_print_warning("The path specified for the variable backupdir cannot" + " be accessed or is invalid. ref:'%s'\n", argument); + } + sys_var_backupdir.value= my_strdup(argument, MYF(0)); + sys_var_backupdir.value_length= strlen(sys_var_backupdir.value); + break; + } case 'h': strmake(mysql_real_data_home,argument, sizeof(mysql_real_data_home)-1); /* Correct pointer set by my_getopt (for embedded library) */ @@ -8625,6 +8643,16 @@ static void fix_paths(void) convert_dirname(language,language,NullS); (void) my_load_path(mysql_home,mysql_home,""); // Resolve current dir (void) my_load_path(mysql_real_data_home,mysql_real_data_home,mysql_home); + + /* + Set backupdir if datadir set on command line but not otherwise set. + */ + if (!sys_var_backupdir.value) + { + sys_var_backupdir.value= my_strdup(mysql_real_data_home, MYF(0)); + sys_var_backupdir.value_length= strlen(mysql_real_data_home); + } + (void) my_load_path(pidfile_name,pidfile_name,mysql_real_data_home); (void) my_load_path(opt_plugin_dir, opt_plugin_dir_ptr ? opt_plugin_dir_ptr : get_relative_path(PLUGINDIR), mysql_home); === modified file 'sql/set_var.cc' --- a/sql/set_var.cc 2008-07-09 07:12:43 +0000 +++ b/sql/set_var.cc 2008-08-08 17:21:31 +0000 @@ -149,6 +149,9 @@ static bool sys_update_general_log_path( static void sys_default_general_log_path(THD *thd, enum_var_type type); static bool sys_update_slow_log_path(THD *thd, set_var * var); static void sys_default_slow_log_path(THD *thd, enum_var_type type); +static int sys_check_backupdir(THD *thd, set_var *var); +static bool sys_update_backupdir(THD *thd, set_var * var); +static void sys_default_backupdir(THD *thd, enum_var_type type); /* Variable definition list @@ -779,6 +782,15 @@ sys_var_str sys_var_slow_log_path(&vars, static sys_var_log_output sys_var_log_output_state(&vars, "log_output", &log_output_options, &log_output_typelib, 0); +/* + Create the backupdir dynamic variable. +*/ +sys_var_str sys_var_backupdir(&vars, "backupdir", + sys_check_backupdir, + sys_update_backupdir, + sys_default_backupdir, + 0); + /* Additional variables (not derived from sys_var class, not accessible as @@ -2611,6 +2623,140 @@ uchar *sys_var_log_output::value_ptr(THD if ((length= tmp.length())) length--; return (uchar*) thd->strmake(tmp.ptr(), length); +} + +/* + Functions for backupdir variable. +*/ + +/** + Set the default for the backupdir + + @param[IN] buff Buffer to use for making string. + + @returns pointer to string (buff) +*/ +char *make_default_backupdir(char *buff) +{ + strmake(buff, mysql_data_home, FN_REFLEN-5); + return buff; +} + +/** + Check the backupdir value for validity. + + This method ensures the users is specifying a valid path + for the backupdir. Validity in this case means the path + is accessible to the user. + + @param[IN] thd Current thread context. + @param[IN] var The new value set in set_var structure. + + @returns 0 +*/ +static int sys_check_backupdir(THD *thd, set_var *var) +{ + char path[FN_REFLEN], buff[FN_REFLEN]; + MY_STAT f_stat; + String str(buff, sizeof(buff), system_charset_info), *res; + const char *log_file_str; + size_t path_length; + + if (!(res= var->value->val_str(&str))) + goto err; + + log_file_str= res->c_ptr(); + bzero(&f_stat, sizeof(MY_STAT)); + + /* Get dirname of the file path. */ + (void) dirname_part(path, log_file_str, &path_length); + + /* Dirname is empty if file path is relative. */ + if (!path_length) + return 0; + + /* + Check if directory exists and we have permission to create file and + write to file. + */ + if (my_access(path, (F_OK|W_OK))) + goto err; + + return 0; + +err: + /* + We print a warning if backupdir is invalid but set it anyway. + */ + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_BACKUP_BACKUPDIR, ER(ER_BACKUP_BACKUPDIR), + var->value->str_value.c_ptr()); + return 0; +} + +/** + Update the backupdir variable + + This method is used to change the backupdir variable. + + @param[IN] thd Current thread context. + @param[IN] var The new value set in set_var structure. + + @returns 0 valid, 1 invalid. +*/ +static bool sys_update_backupdir(THD *thd, set_var * var) +{ + char buff[FN_REFLEN]; + char *res= 0, *old_value=(char *)(var ? var->value->str_value.ptr() : 0); + bool result= 0; + uint str_length= (var ? var->value->str_value.length() : 0); + + if (!old_value) + { + old_value= make_default_backupdir(buff); + str_length= strlen(old_value); + } + if (!(res= my_strndup(old_value, str_length, MYF(MY_FAE+MY_WME)))) + { + result= 1; + goto err; + } + + pthread_mutex_lock(&LOCK_global_system_variables); + logger.lock_exclusive(); + old_value= sys_var_backupdir.value; + sys_var_backupdir.value= res; + sys_var_backupdir.value_length= str_length; + logger.unlock(); + pthread_mutex_unlock(&LOCK_global_system_variables); + + if (my_access(sys_var_backupdir.value, (F_OK|W_OK))) + goto err; + + return result; + +err: + /* + We print a warning if backupdir is invalid but set it anyway. + */ + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_BACKUP_BACKUPDIR, ER(ER_BACKUP_BACKUPDIR), + var->value->str_value.c_ptr()); + return result; +} + +/** + Set the default value for the backupdir variable + + This method is used to reset the backupdir variable to the + default by calling the update method without a path. + + @param[IN] thd Current thread context. + @param[IN] type Ignored (needed for api). +*/ +static void sys_default_backupdir(THD *thd, enum_var_type type) +{ + sys_update_backupdir(thd, 0); } === modified file 'sql/set_var.h' --- a/sql/set_var.h 2008-07-09 07:12:43 +0000 +++ b/sql/set_var.h 2008-08-08 17:21:31 +0000 @@ -1345,7 +1345,8 @@ CHARSET_INFO *get_old_charset_by_name(co uchar* find_named(I_List *list, const char *name, uint length, NAMED_LIST **found); -extern sys_var_str sys_var_general_log_path, sys_var_slow_log_path; +extern sys_var_str sys_var_general_log_path, sys_var_slow_log_path, + sys_var_backupdir; /* key_cache functions */ KEY_CACHE *get_key_cache(LEX_STRING *cache_name); === modified file 'sql/share/errmsg.txt' --- a/sql/share/errmsg.txt 2008-07-09 07:12:43 +0000 +++ b/sql/share/errmsg.txt 2008-08-08 17:21:31 +0000 @@ -6372,3 +6372,5 @@ ER_BACKUP_OBTAIN_NAME_LOCK_FAILED eng "Restore failed to obtain the name locks on the tables." ER_BACKUP_RELEASE_NAME_LOCK_FAILED eng "Restore failed to release the name locks on the tables." +ER_BACKUP_BACKUPDIR + eng "The path specified for the system variable backupdir cannot be accessed or is invalid. ref: %-.64s" === modified file 'sql/sql_parse.cc' --- a/sql/sql_parse.cc 2008-07-14 14:56:49 +0000 +++ b/sql/sql_parse.cc 2008-08-08 17:21:31 +0000 @@ -41,7 +41,7 @@ @defgroup Runtime_Environment Runtime Environment @{ */ -int execute_backup_command(THD*,LEX*); +int execute_backup_command(THD*, LEX*, String*); /* Used in error handling only */ #define SP_TYPE_STRING(LP) \ @@ -2166,10 +2166,21 @@ mysql_execute_command(THD *thd) #else { /* + Create a string from the backupdir system variable and pass + to backup system. + */ + String backupdir; + + backupdir.alloc(sys_var_backupdir.value_length); + backupdir.set_ascii(sys_var_backupdir.value, + sys_var_backupdir.value_length); + backupdir.length(sys_var_backupdir.value_length); + + /* Note: execute_backup_command() sends a correct response to the client (either ok, result set or error message). - */ - if (execute_backup_command(thd,lex)) + */ + if (execute_backup_command(thd, lex, &backupdir)) goto error; break; }