#At file:///home2/mydev/bzrroot/mysql-6.0-bug43747-2/ based on revid:jorgen.loland@stripped
2797 Ingo Struewing 2009-04-24
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
A backupdir variable value like 'existent_file.txt' and a backup image
file name like '../backup_file.bak' allowed for a successful BACKUP and
RESTORE (Bug #43747). It had been decided that we do not want to allow
BACKUP or RESTORE to succeed, if backupdir is set to an invalid path
(compare Bug 43536 - BACKUP with absolute path fails on invalid
backupdir).
A backupdir variable value like 'symlink_to_dir1_dir2' and a backup image
file name like '../backup_file.bak' located the file in the wrong
directory. If "symlink_to_dir1_dir2 -> dir1/dir2", the used file was
just "backup_file.bak". Cleanup of "../" was done based on the symlink
itself, not based on its target (dir2). Normal file system operations
interpret a path of "symlink_to_dir1_dir2/../backup_file.bak" as
"dir1/backup_file.bak".
The main problem was that MySQL did not have a path handling function,
which cleans a path while respecting symbolic links, works even if the
path name references a non-existent file system object, and works on
Windows too. For more details see the explanation in the bug report
#43747.
Another problem was that the backupdir variable was checked for an
existent and writable file system object only. It was not required that
this object needs to be a directory.
The main part of the patch is the new function safe_cleanup_cat_path()
in mf_pack.c.
Another important new function is test_if_directory() in my_lib.c. It
allows to verify that backupdir is not just a writable path, but also a
directory.
Yet another new function is cut_print_path() in mf_format.c. It supports
the creation of good error messages. Many error message templates have
limited space for path names, e.g. %.64s. Often the last part of a path
name is more informative for error messages than its begin. This
function returns a cut path with a prepended "... ", if the path is
longer than the length limit.
Yet another new function is is_usable_directory() in set_var.cc. It
supports checking of variable values when they are set.
To avoid frequent use of strlen() I added length variables to the
seldom or never changed variables home_dir, mysql_real_data_home, and
opt_secure_backup_file_priv.
Finally I used these functions in the backup kernel to build an
absolute, clean path for the backupdir to check it for being a writable
directory and to prepend it to a relative backup image file path.
Additionally I used the functions on opt_secure_backup_file_priv before
comparing it against the resulting backup image file path.
To have a good test coverage for the new non-trivial function
safe_cleanup_cat_path(), I made a unittest for it.
@ include/my_dir.h
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added declaration for test_if_directory().
@ include/my_sys.h
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added declarations for home_dir_len, cut_print_path(), and
cleanup_cat_path().
@ mysql-test/suite/backup/r/backup_backupdir.result
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Updated test result.
@ mysql-test/suite/backup/r/backup_backupdir_not_windows.result
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
New test result.
@ mysql-test/suite/backup/r/backup_securebackup.result
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Updated test result.
@ mysql-test/suite/backup/t/backup_backupdir.test
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added proper cleanup for backupdir.
Added SELECTs for backupdir to show its changes.
Removed tests for limited length of the variable.
Made the test for a "really long string" better checkable.
Added new tests for the reported bug.
Added tests for special variable values (NULL and DEFAULT).
@ mysql-test/suite/backup/t/backup_backupdir_not_windows.test
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
New test case for Bug#43767.
@ mysql-test/suite/backup/t/backup_securebackup-master.opt
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added a --backupdir option for coverage testing.
@ mysql-test/suite/backup/t/backup_securebackup.test
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added proper cleanup for backupdir.
Fixed wrong tests (bup_sfp5.bak, bup_sfp1.bak).
Added a printout for the backupdir option.
@ mysys/mf_format.c
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added definition of new function cut_print_path().
@ mysys/mf_pack.c
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added definition of new function safe_cleanup_cat_path().
@ mysys/my_init.c
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added setup for home_dir_len.
@ mysys/my_lib.c
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added definition of new function test_if_directory().
@ mysys/my_static.c
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added declaration of home_dir_len.
@ sql/backup/kernel.cc
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Moved check of backupdir from execute_backup_command() to
Backup_restore_ctx::prepare_path().
Changed the latter to use safe_cleanup_cat_path() and
check_backupdir().
@ sql/backup/stream.cc
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Changed Stream::test_secure_file_priv_access() to use
safe_cleanup_cat_path() on opt_secure_backup_file_priv.
@ sql/mysql_priv.h
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added declarations of mysql_real_data_home_len and
opt_secure_backup_file_priv_len.
@ sql/mysqld.cc
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added definitions and setup of mysql_real_data_home_len and
opt_secure_backup_file_priv_len.
Moved check of backupdir value from mysqld_get_one_option() to
fix_paths().
Used is_usable_directory() on backupdir and
opt_secure_backup_file_priv.
@ sql/set_var.cc
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Removed double declarations.
Removed sys_check_backupdir() altogether. All checks are done in
sys_update_backupdir() now.
Changed make_default_backupdir() to default_backupdir(), which
allocates the value.
Added definition of new function check_backupdir().
Added definition of new function is_usable_directory().
Changed sys_update_backupdir() to use is_usable_directory().
@ sql/set_var.h
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added declarations for is_usable_directory(), check_backupdir(),
and default_backupdir().
@ sql/share/errmsg.txt
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Changed ER_BACKUP_BACKUPDIR to the more genaral usable ER_NOT_RW_DIR.
@ unittest/mysys/CMakeLists.txt
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added safe_cleanup_cat_path-t.
@ unittest/mysys/Makefile.am
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
Added safe_cleanup_cat_path-t.
@ unittest/mysys/safe_cleanup_cat_path-t.c
Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
Bug#43767 - Wrong path if backupdir is symlink and backref path used
New unit test for safe_cleanup_cat_path().
added:
mysql-test/suite/backup/r/backup_backupdir_not_windows.result
mysql-test/suite/backup/t/backup_backupdir_not_windows.test
unittest/mysys/safe_cleanup_cat_path-t.c
modified:
include/my_dir.h
include/my_sys.h
mysql-test/suite/backup/r/backup_backupdir.result
mysql-test/suite/backup/r/backup_securebackup.result
mysql-test/suite/backup/t/backup_backupdir.test
mysql-test/suite/backup/t/backup_securebackup-master.opt
mysql-test/suite/backup/t/backup_securebackup.test
mysys/mf_format.c
mysys/mf_pack.c
mysys/my_init.c
mysys/my_lib.c
mysys/my_static.c
sql/backup/kernel.cc
sql/backup/stream.cc
sql/mysql_priv.h
sql/mysqld.cc
sql/set_var.cc
sql/set_var.h
sql/share/errmsg.txt
unittest/mysys/CMakeLists.txt
unittest/mysys/Makefile.am
=== modified file 'include/my_dir.h'
--- a/include/my_dir.h 2008-06-20 00:27:48 +0000
+++ b/include/my_dir.h 2009-04-24 14:55:53 +0000
@@ -100,6 +100,7 @@ extern MY_DIR *my_dir(const char *path,m
extern void my_dirend(MY_DIR *buffer);
extern MY_STAT *my_stat(const char *path, MY_STAT *stat_area, myf my_flags);
extern int my_fstat(int filenr, MY_STAT *stat_area, myf MyFlags);
+extern my_bool test_if_directory(const char* path);
#endif /* MY_DIR_H */
=== modified file 'include/my_sys.h'
--- a/include/my_sys.h 2009-04-01 21:36:07 +0000
+++ b/include/my_sys.h 2009-04-24 14:55:53 +0000
@@ -234,6 +234,7 @@ extern int errno; /* declare errno */
#endif
#endif /* #ifndef errno */
extern char *home_dir; /* Home directory for user */
+extern size_t home_dir_len; /* Length of home_dir value. */
extern const char *my_progname; /* program-name (printed in errors) */
extern const char *my_progname_short; /* like above but without directory */
extern char NEAR curr_dir[]; /* Current directory for user */
@@ -765,10 +766,12 @@ extern char * fn_same(char * toname,cons
extern char * fn_format(char * to,const char *name,const char *dir,
const char *form, uint flag);
extern size_t strlength(const char *str);
+extern char *cut_print_path(const char* path, size_t length_limit);
extern void pack_dirname(char * to,const char *from);
extern size_t normalize_dirname(char * to, const char *from);
extern size_t unpack_dirname(char * to,const char *from);
extern size_t cleanup_dirname(char * to,const char *from);
+size_t safe_cleanup_cat_path(char *to, size_t to_size, ...);
extern size_t system_filename(char * to,const char *from);
extern size_t unpack_filename(char * to,const char *from);
extern char * intern_filename(char * to,const char *from);
=== modified file 'mysql-test/suite/backup/r/backup_backupdir.result'
--- a/mysql-test/suite/backup/r/backup_backupdir.result 2009-03-18 22:01:12 +0000
+++ b/mysql-test/suite/backup/r/backup_backupdir.result 2009-04-24 14:55:53 +0000
@@ -1,5 +1,4 @@
-Reset backupdir
-SET @@global.backupdir = @@global.datadir;
+SET @old_backupdir= @@backupdir;
DROP DATABASE IF EXISTS bup_backupdir;
Create a database
CREATE DATABASE bup_backupdir;
@@ -8,6 +7,9 @@ INSERT INTO bup_backupdir.t1 VALUES (1),
Create a directory for backup images
Reset backupdir
SET @@global.backupdir = '../../tmp/backup';
+SELECT @@backupdir;
+@@backupdir
+../../tmp/backup
Perform backup
BACKUP DATABASE bup_backupdir TO 'bup_backupdir1.bak';
backup_id
@@ -35,6 +37,9 @@ backup_id
Ensure backup image file went to the correct location
Reset backupdir with ending /
SET @@global.backupdir = '../../tmp/backup/';
+SELECT @@backupdir;
+@@backupdir
+../../tmp/backup/
Perform backup
BACKUP DATABASE bup_backupdir TO '../../bup_backupdir4.bak';
backup_id
@@ -49,34 +54,90 @@ Try a backup to an invalid hard path.
*Performing:
*BACKUP DATABASE bup_backupdir TO '$MYSQLTEST_VARDIR/not/there/either/bup_backupdir6.bak';
ERROR HY000: Can't create/write to file 'MYSQLTEST_VARDIR/not/there/either/bup_backupdir6.bak' (Errcode: #)
-
-Attempt to set the backupdir to a really long string.
+#
+# Set the backupdir to a really long string.
+#
set global max_allowed_packet=1024*100;
-
-Now attempt to set the backupdir to 512 characters.
-
-SET @@global.backupdir = repeat('a',####);
+SET @@global.backupdir =
+CONCAT('bup_start/', REPEAT('a', 99000 - 18), '/bup_end');
Warnings:
-Warning #### The path specified for the system variable backupdir cannot be accessed or is invalid. ref:
-
-Now attempt to set the backupdir to 513 characters.
-
-SET @@global.backupdir = repeat('a',513);
-ERROR HY000: The path specified for backupdir is too long.
-
-Now attempt to set the backupdir to a really long string.
-
-SET @@global.backupdir = repeat('a',99*1024);
-ERROR HY000: The path specified for backupdir is too long.
-
-Attempt to set the backupdir to something invalid.
-
+Warning #### The path specified for the system variable backupdir is not a directory or cannot be written: ... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#bup_end
+SELECT LENGTH(@@backupdir), LEFT(@@backupdir, 30), RIGHT(@@backupdir, 30);
+LENGTH(@@backupdir) 99000
+LEFT(@@backupdir, 30) bup_start/aaaaaaaaaaaaaaaaaaaa
+RIGHT(@@backupdir, 30) aaaaaaaaaaaaaaaaaaaaaa#bup_end
+set global max_allowed_packet=DEFAULT;
+#
+# Set the backupdir to something invalid.
+#
SET @@global.backupdir = 'This_is_really_stupid/not/there/at/all';
Warnings:
-Warning #### The path specified for the system variable backupdir cannot be accessed or is invalid. ref: This_is_really_stupid/not/there/at/all
-Warning #### The path specified for the system variable backupdir cannot be accessed or is invalid. ref: This_is_really_stupid/not/there/at/all
-set global max_allowed_packet=DEFAULT;
+Warning #### The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLD_DATADIR/This_is_really_stupid/not/there/at/all
+SELECT @@backupdir;
+@@backupdir
+This_is_really_stupid/not/there/at/all
+#
+# Set backupdir to an existent file.
+#
+# Create file.
+# Set backupdir.
+SET @@global.backupdir = '../../tmp/backup_file.txt';
+Warnings:
+Warning #### The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLTEST_VARDIR/tmp/backup_file.txt
+SELECT @@backupdir;
+@@backupdir
+../../tmp/backup_file.txt
+# Attempt backup to a pure file name in invalid backupdir.
+BACKUP DATABASE bup_backupdir TO 'bup_backupdir_x.bak';
+ERROR HY000: The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLTEST_VARDIR/tmp/backup_file.txt
+# Attempt restore from a pure file name in invalid backupdir.
+RESTORE FROM 'bup_backupdir_x.bak' OVERWRITE;
+ERROR HY000: The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLTEST_VARDIR/tmp/backup_file.txt
+# Attempt backup to a relative path name in invalid backupdir.
+# Try 'test' in case current directory is used instead of backupdir.
+BACKUP DATABASE bup_backupdir TO 'test/bup_backupdir_x.bak';
+ERROR HY000: The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLTEST_VARDIR/tmp/backup_file.txt
+# Attempt restore from a relative path name in invalid backupdir.
+# Try 'test' in case current directory is used instead of backupdir.
+RESTORE FROM 'test/bup_backupdir_x.bak' OVERWRITE;
+ERROR HY000: The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLTEST_VARDIR/tmp/backup_file.txt
+# Attempt backup to a backref path in invalid backupdir.
+BACKUP DATABASE bup_backupdir TO '../bup_backupdir_x.bak';
+ERROR HY000: The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLTEST_VARDIR/tmp/backup_file.txt
+# Attempt restore from a backref path in invalid backupdir.
+RESTORE FROM '../bup_backupdir_x.bak' OVERWRITE;
+ERROR HY000: The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLTEST_VARDIR/tmp/backup_file.txt
+# Attempt backup to an absolute path.
+# *Actual BACKUP DATABASE command not printed due to
+# non-deterministic path.
+# *Performing:
+# *BACKUP DATABASE bup_backupdir TO
+# '$MYSQLTEST_VARDIR/bup_backupdir_x.bak';
+ERROR HY000: The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLTEST_VARDIR/tmp/backup_file.txt
+# Ensure backup image file does not exist
+# Attempt restore from an absolute path.
+# *Actual RESTORE command not printed due to
+# non-deterministic path.
+# *Performing:
+# *RESTORE FROM
+# '$MYSQLTEST_VARDIR/bup_backupdir_x.bak';
+ERROR HY000: The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLTEST_VARDIR/tmp/backup_file.txt
+#
+# Set backupdir to NULL.
+#
+SET @@global.backupdir = NULL;
+ERROR 42000: Variable 'backupdir' can't be set to the value of 'NULL'
+SELECT @@backupdir;
+@@backupdir
+../../tmp/backup_file.txt
+#
+# Set backupdir to DEFAULT.
+#
+SET @@global.backupdir = DEFAULT;
+SELECT @@backupdir;
+@@backupdir
+MYSQLD_DATADIR
Cleanup
Reset backupdir
-SET @@global.backupdir = @@global.datadir;
+SET @@global.backupdir = @old_backupdir;
DROP DATABASE bup_backupdir;
=== added file 'mysql-test/suite/backup/r/backup_backupdir_not_windows.result'
--- a/mysql-test/suite/backup/r/backup_backupdir_not_windows.result 1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/backup/r/backup_backupdir_not_windows.result 2009-04-24 14:55:53 +0000
@@ -0,0 +1,46 @@
+# Save backupdir
+SET @old_backupdir= @@backupdir;
+DROP DATABASE IF EXISTS bup_backupdir_nw;
+#
+# Create a database to be used throughout the test case
+#
+CREATE DATABASE bup_backupdir_nw;
+CREATE TABLE bup_backupdir_nw.t1(a INT);
+INSERT INTO bup_backupdir_nw.t1 VALUES (1), (2), (3);
+# Ensure that the backup directory does not exist.
+# Create a symlink.
+# VARDIR/tmp/bup_backupdir_nw_symlink ->
+# VARDIR/tmp/bup_backupdir_nw/symdir
+#
+# Symlink to existent directory.
+#
+# Create directories for symlink tests
+# MYSQLTEST_VARDIR/tmp/bup_backupdir_nw
+# MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/symdir
+# MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/symdir/subdir
+# Set backupdir to symlink
+# VARDIR/tmp/bup_backupdir_nw_symlink ->
+# VARDIR/tmp/bup_backupdir_nw/symdir
+SET @@global.backupdir = '../../tmp/bup_backupdir_nw_symlink';
+# Perform backup to a backref path name.
+BACKUP DATABASE bup_backupdir_nw TO '../bup_backupdir_nw_1.bak';
+backup_id
+#
+# Ensure backup image file went to the correct location
+# Perform restore from a backref path name.
+RESTORE FROM '../bup_backupdir_nw_1.bak' OVERWRITE;
+backup_id
+#
+# Remove symlink
+#
+# Attempt to restore from a file name relative to $HOME.
+#
+SET @@global.backupdir = DEFAULT;
+RESTORE FROM '~/bup_backupdir_x.bak' OVERWRITE;
+ERROR HY000: File 'HOME/bup_backupdir_x.bak' not found (Errcode: 2)
+#
+# Cleanup
+#
+DROP DATABASE bup_backupdir_nw;
+# Reset backupdir
+SET @@global.backupdir= @old_backupdir;
=== modified file 'mysql-test/suite/backup/r/backup_securebackup.result'
--- a/mysql-test/suite/backup/r/backup_securebackup.result 2009-02-09 18:17:55 +0000
+++ b/mysql-test/suite/backup/r/backup_securebackup.result 2009-04-24 14:55:53 +0000
@@ -1,4 +1,6 @@
Initializing tests
+SET @old_backupdir = @@backupdir;
+SET @@global.backupdir = @@datadir;
Create directories for backup images
Creating database and populating tables
DROP DATABASE IF EXISTS mysqltest;
@@ -63,15 +65,27 @@ ERROR HY000: The MySQL server is running
Change backupdir to securebackup_path/subpath
(MYSQLD_DATADIR/securefilepriv_path/subpath)
SET @@global.backupdir = 'securefilepriv_path/subpath';
-(MYSQLD_DATADIR/securefilepriv_path)
-BACKUP DATABASE mysqltest TO 'securefilepriv_path/bup_sfp5.bak';
+(MYSQLD_DATADIR/securefilepriv_path/subpath)
+BACKUP DATABASE mysqltest TO 'bup_sfp5.bak';
ERROR HY000: The MySQL server is running with the --secure-backup-file-priv option so it cannot execute this statement
-(MYSQLD_DATADIR/securefilepriv_path)
-RESTORE FROM 'securefilepriv_path/bup_sfp1.bak';
+(MYSQLD_DATADIR/securefilepriv_path/subpath)
+RESTORE FROM 'bup_sfp1.bak';
ERROR HY000: The MySQL server is running with the --secure-backup-file-priv option so it cannot execute this statement
+#
+# Bug#43767 - Wrong path if backupdir is symlink and backref path used
+# Coverage test for command line option handling in mysqld.cc.
+# This is not a test here, but an additional --backupdir option in
+# backup_securebackup-master.opt. This seems to be the only time
+# backupdir is set by command line option. Show what the option was.
+#
+SELECT @old_backupdir;
+@old_backupdir
+see_comment_in_test_case_re_bug_43767_coverage_test
Cleanup
DROP TABLE mysqltest.t1;
DROP DATABASE mysqltest;
-SET @@global.backupdir = @@global.datadir;
+SET @@global.backupdir = @old_backupdir;
+Warnings:
+Warning #### The path specified for the system variable backupdir is not a directory or cannot be written: MYSQLD_DATADIR/see_comment_in_test_case_re_bug_43767_coverage_test
=== modified file 'mysql-test/suite/backup/t/backup_backupdir.test'
--- a/mysql-test/suite/backup/t/backup_backupdir.test 2009-02-26 22:19:09 +0000
+++ b/mysql-test/suite/backup/t/backup_backupdir.test 2009-04-24 14:55:53 +0000
@@ -10,10 +10,10 @@ call mtr.add_suppression("Backup:");
call mtr.add_suppression("Restore:");
enable_query_log;
-let $MYSQLD_DATADIR= `select @@datadir`;
-
---echo Reset backupdir
-SET @@global.backupdir = @@global.datadir;
+let $MYSQLD_DATADIR= `SELECT @@datadir`;
+# Datadir without trailing FN_LIBCHAR.
+let $MYSQLD_DATADIR_TRIM = `SELECT LEFT(@@datadir, CHAR_LENGTH(@@datadir) - 1)`;
+SET @old_backupdir= @@backupdir;
--disable_warnings
DROP DATABASE IF EXISTS bup_backupdir;
@@ -38,6 +38,8 @@ mkdir $MYSQLTEST_VARDIR/tmp/backup;
--echo Reset backupdir
SET @@global.backupdir = '../../tmp/backup';
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+SELECT @@backupdir;
--echo Perform backup
--replace_column 1 #
@@ -73,6 +75,8 @@ BACKUP DATABASE bup_backupdir TO '../../
--echo Reset backupdir with ending /
SET @@global.backupdir = '../../tmp/backup/';
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+SELECT @@backupdir;
--echo Perform backup
--replace_column 1 #
@@ -82,8 +86,8 @@ BACKUP DATABASE bup_backupdir TO '../../
--file_exists $MYSQLTEST_VARDIR/bup_backupdir4.bak
--echo Try a backup to an invalid relative path.
---replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
--replace_regex /Errcode: [0-9]+/Errcode: #/
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
--error 1
BACKUP DATABASE bup_backupdir TO '../not/there/bup_backupdir5.bak';
@@ -102,43 +106,130 @@ BACKUP DATABASE bup_backupdir TO '../not
--eval BACKUP DATABASE bup_backupdir TO '$MYSQLTEST_VARDIR/not/there/either/bup_backupdir6.bak';
--enable_query_log
---echo
---echo Attempt to set the backupdir to a really long string.
-
+--echo #
+--echo # Set the backupdir to a really long string.
+--echo #
set global max_allowed_packet=1024*100;
-
---echo
---echo Now attempt to set the backupdir to 512 characters.
---echo
# Mask out the error value on the warning.
---replace_regex /[0000-9999]+/####/
-SET @@global.backupdir = repeat('a',512);
+--replace_column 2 ####
+--replace_regex /a[\/\\]bup_end/a#bup_end/
+SET @@global.backupdir =
+ CONCAT('bup_start/', REPEAT('a', 99000 - 18), '/bup_end');
+--replace_result $MYSQLD_DATADIR MYSQLD_DATADIR/
+--replace_regex /a[\/\\]bup_end/a#bup_end/
+query_vertical
+SELECT LENGTH(@@backupdir), LEFT(@@backupdir, 30), RIGHT(@@backupdir, 30);
+set global max_allowed_packet=DEFAULT;
---echo
---echo Now attempt to set the backupdir to 513 characters.
---echo
---error ER_PATH_LENGTH
-SET @@global.backupdir = repeat('a',513);
-
---echo
---echo Now attempt to set the backupdir to a really long string.
---echo
---error ER_PATH_LENGTH
-SET @@global.backupdir = repeat('a',99*1024);
-
---echo
---echo Attempt to set the backupdir to something invalid.
---echo
+--echo #
+--echo # Set the backupdir to something invalid.
+--echo #
# Mask out the error value on the warning.
---replace_regex /[0000-9999]+/####/
+--replace_column 2 ####
+--replace_result $MYSQLD_DATADIR MYSQLD_DATADIR/
SET @@global.backupdir = 'This_is_really_stupid/not/there/at/all';
+--replace_result $MYSQLD_DATADIR MYSQLD_DATADIR/
+SELECT @@backupdir;
-set global max_allowed_packet=DEFAULT;
+--echo #
+--echo # Set backupdir to an existent file.
+--echo #
+
+--echo # Create file.
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/tmp/backup_file.txt
+--write_file $MYSQLTEST_VARDIR/tmp/backup_file.txt EOF
+blabla
+EOF
+--echo # Set backupdir.
+# Mask out the error value on the warning.
+--replace_column 2 ####
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+SET @@global.backupdir = '../../tmp/backup_file.txt';
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+SELECT @@backupdir;
+
+--echo # Attempt backup to a pure file name in invalid backupdir.
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--error ER_NOT_RW_DIR
+BACKUP DATABASE bup_backupdir TO 'bup_backupdir_x.bak';
+
+--echo # Attempt restore from a pure file name in invalid backupdir.
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--error ER_NOT_RW_DIR
+RESTORE FROM 'bup_backupdir_x.bak' OVERWRITE;
+
+--echo # Attempt backup to a relative path name in invalid backupdir.
+--echo # Try 'test' in case current directory is used instead of backupdir.
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--error ER_NOT_RW_DIR
+BACKUP DATABASE bup_backupdir TO 'test/bup_backupdir_x.bak';
+
+--echo # Attempt restore from a relative path name in invalid backupdir.
+--echo # Try 'test' in case current directory is used instead of backupdir.
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--error ER_NOT_RW_DIR
+RESTORE FROM 'test/bup_backupdir_x.bak' OVERWRITE;
+
+--echo # Attempt backup to a backref path in invalid backupdir.
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--error ER_NOT_RW_DIR
+BACKUP DATABASE bup_backupdir TO '../bup_backupdir_x.bak';
+
+--echo # Attempt restore from a backref path in invalid backupdir.
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--error ER_NOT_RW_DIR
+RESTORE FROM '../bup_backupdir_x.bak' OVERWRITE;
+
+--echo # Attempt backup to an absolute path.
+--echo # *Actual BACKUP DATABASE command not printed due to
+--echo # non-deterministic path.
+--echo # *Performing:
+--echo # *BACKUP DATABASE bup_backupdir TO
+--echo # '\$MYSQLTEST_VARDIR/bup_backupdir_x.bak';
+--disable_query_log
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--error ER_NOT_RW_DIR
+eval BACKUP DATABASE bup_backupdir TO '$MYSQLTEST_VARDIR/bup_backupdir_x.bak';
+--enable_query_log
+
+--echo # Ensure backup image file does not exist
+--error 1
+--file_exists $MYSQLTEST_VARDIR/bup_backupdir_x.bak
+
+--echo # Attempt restore from an absolute path.
+--echo # *Actual RESTORE command not printed due to
+--echo # non-deterministic path.
+--echo # *Performing:
+--echo # *RESTORE FROM
+--echo # '\$MYSQLTEST_VARDIR/bup_backupdir_x.bak';
+--disable_query_log
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--error ER_NOT_RW_DIR
+eval RESTORE FROM '$MYSQLTEST_VARDIR/bup_backupdir_x.bak' OVERWRITE;
+--enable_query_log
+
+--echo #
+--echo # Set backupdir to NULL.
+--echo #
+--error ER_WRONG_VALUE_FOR_VAR
+SET @@global.backupdir = NULL;
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+SELECT @@backupdir;
+
+--echo #
+--echo # Set backupdir to DEFAULT.
+--echo #
+SET @@global.backupdir = DEFAULT;
+--replace_result $MYSQLD_DATADIR_TRIM MYSQLD_DATADIR
+SELECT @@backupdir;
--echo Cleanup
--echo Reset backupdir
-SET @@global.backupdir = @@global.datadir;
+--disable_warnings
+SET @@global.backupdir = @old_backupdir;
+--enable_warnings
DROP DATABASE bup_backupdir;
=== added file 'mysql-test/suite/backup/t/backup_backupdir_not_windows.test'
--- a/mysql-test/suite/backup/t/backup_backupdir_not_windows.test 1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/backup/t/backup_backupdir_not_windows.test 2009-04-24 14:55:53 +0000
@@ -0,0 +1,87 @@
+#
+# This test is designed to test the new backupdir variable
+# with symbolic links.
+# Bug#43747 - BACKUP with backref path succeeds on invalid backupdir
+# Ingo Struewing, 2009-04-17
+#
+
+--source include/not_embedded.inc
+--source include/not_windows.inc
+
+--echo # Save backupdir
+SET @old_backupdir= @@backupdir;
+
+--disable_warnings
+DROP DATABASE IF EXISTS bup_backupdir_nw;
+--enable_warnings
+
+--echo #
+--echo # Create a database to be used throughout the test case
+--echo #
+CREATE DATABASE bup_backupdir_nw;
+CREATE TABLE bup_backupdir_nw.t1(a INT);
+INSERT INTO bup_backupdir_nw.t1 VALUES (1), (2), (3);
+
+--echo # Ensure that the backup directory does not exist.
+--error 0,1,2
+--rmdir $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/symdir/subdir
+--error 0,1,2
+--rmdir $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/symdir
+--error 0,1,2
+--rmdir $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw
+
+--echo # Create a symlink.
+--echo # VARDIR/tmp/bup_backupdir_nw_symlink ->
+--echo # VARDIR/tmp/bup_backupdir_nw/symdir
+--exec ln -s bup_backupdir_nw/symdir $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw_symlink
+
+--echo #
+--echo # Symlink to existent directory.
+--echo #
+
+--echo # Create directories for symlink tests
+--echo # MYSQLTEST_VARDIR/tmp/bup_backupdir_nw
+--echo # MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/symdir
+--echo # MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/symdir/subdir
+--mkdir $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw
+--mkdir $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/symdir
+--mkdir $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/symdir/subdir
+
+--echo # Set backupdir to symlink
+--echo # VARDIR/tmp/bup_backupdir_nw_symlink ->
+--echo # VARDIR/tmp/bup_backupdir_nw/symdir
+SET @@global.backupdir = '../../tmp/bup_backupdir_nw_symlink';
+
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/bup_backupdir_nw_1.bak
+
+--echo # Perform backup to a backref path name.
+--replace_column 1 #
+BACKUP DATABASE bup_backupdir_nw TO '../bup_backupdir_nw_1.bak';
+
+--echo # Ensure backup image file went to the correct location
+--file_exists $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/bup_backupdir_nw_1.bak
+
+--echo # Perform restore from a backref path name.
+--replace_column 1 #
+RESTORE FROM '../bup_backupdir_nw_1.bak' OVERWRITE;
+--remove_file $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw/bup_backupdir_nw_1.bak
+
+--echo # Remove symlink
+--remove_file $MYSQLTEST_VARDIR/tmp/bup_backupdir_nw_symlink
+
+--echo #
+--echo # Attempt to restore from a file name relative to \$HOME.
+--echo #
+SET @@global.backupdir = DEFAULT;
+--replace_result $HOME HOME
+--error 29
+RESTORE FROM '~/bup_backupdir_x.bak' OVERWRITE;
+
+--echo #
+--echo # Cleanup
+--echo #
+DROP DATABASE bup_backupdir_nw;
+--echo # Reset backupdir
+SET @@global.backupdir= @old_backupdir;
+
=== modified file 'mysql-test/suite/backup/t/backup_securebackup-master.opt'
--- a/mysql-test/suite/backup/t/backup_securebackup-master.opt 2009-02-09 18:17:55 +0000
+++ b/mysql-test/suite/backup/t/backup_securebackup-master.opt 2009-04-24 14:55:53 +0000
@@ -1,2 +1,3 @@
+--backupdir=see_comment_in_test_case_re_bug_43767_coverage_test
--secure-backup-file-priv=$MYSQLTEST_VARDIR/mysqld.1/data/securebackup_path
--secure-file-priv=$MYSQLTEST_VARDIR/mysqld.1/data/securefilepriv_path
=== modified file 'mysql-test/suite/backup/t/backup_securebackup.test'
--- a/mysql-test/suite/backup/t/backup_securebackup.test 2009-02-09 18:17:55 +0000
+++ b/mysql-test/suite/backup/t/backup_securebackup.test 2009-04-24 14:55:53 +0000
@@ -12,7 +12,9 @@
--echo Initializing tests
-let $MYSQLD_DATADIR = `select @@datadir`;
+let $MYSQLD_DATADIR = `SELECT @@datadir`;
+SET @old_backupdir = @@backupdir;
+SET @@global.backupdir = @@datadir;
--error 0,1
rmdir $MYSQLD_DATADIR/securefilepriv_path/subpath;
@@ -144,12 +146,14 @@ RESTORE FROM 'securefilepriv_path/bup_sf
SET @@global.backupdir = 'securefilepriv_path/subpath';
#
-# Now check to ensure backup cannot write to the --secure-file-priv location even
-# if the backupdir is set to the same as --secure-file-priv location.
+# Now check to ensure backup cannot write to the --secure-file-priv location
+# even if the backupdir is set to the same as --secure-file-priv location.
+# The file is destined for backupdir/file_name, which is
+# MYSQLD_DATADIR/securefilepriv_path/subpath/bup_sfp5.bak
#
---echo (MYSQLD_DATADIR/securefilepriv_path)
+--echo (MYSQLD_DATADIR/securefilepriv_path/subpath)
--error ER_OPTION_PREVENTS_STATEMENT
-BACKUP DATABASE mysqltest TO 'securefilepriv_path/bup_sfp5.bak';
+BACKUP DATABASE mysqltest TO 'bup_sfp5.bak';
#
# Now make sure restore cannot read from the --secure-file-priv location even
@@ -158,9 +162,19 @@ BACKUP DATABASE mysqltest TO 'securefile
# Note: The error will still be correct even though the file doesn't
# exist and the system should not report the file is missing.
#
---echo (MYSQLD_DATADIR/securefilepriv_path)
+--echo (MYSQLD_DATADIR/securefilepriv_path/subpath)
--error ER_OPTION_PREVENTS_STATEMENT
-RESTORE FROM 'securefilepriv_path/bup_sfp1.bak';
+RESTORE FROM 'bup_sfp1.bak';
+
+--echo #
+--echo # Bug#43767 - Wrong path if backupdir is symlink and backref path used
+--echo # Coverage test for command line option handling in mysqld.cc.
+--echo # This is not a test here, but an additional --backupdir option in
+--echo # backup_securebackup-master.opt. This seems to be the only time
+--echo # backupdir is set by command line option. Show what the option was.
+--echo #
+--replace_result $MYSQLD_DATADIR MYSQLD_DATADIR/
+SELECT @old_backupdir;
--echo
--echo Cleanup
@@ -169,7 +183,10 @@ RESTORE FROM 'securefilepriv_path/bup_sf
DROP TABLE mysqltest.t1;
DROP DATABASE mysqltest;
-SET @@global.backupdir = @@global.datadir;
+# Mask out the error value on the warning.
+--replace_column 2 ####
+--replace_result $MYSQLD_DATADIR MYSQLD_DATADIR/
+SET @@global.backupdir = @old_backupdir;
--error 0,1,2
rmdir $MYSQLD_DATADIR/securefilepriv_path/subpath;
=== modified file 'mysys/mf_format.c'
--- a/mysys/mf_format.c 2007-05-10 09:59:39 +0000
+++ b/mysys/mf_format.c 2009-04-24 14:55:53 +0000
@@ -21,6 +21,31 @@
Function can handle the case where 'to' == 'name'
For a description of the flag values, consult my_sys.h
The arguments should be in unix format.
+
+ Warning: The flag MY_UNPACK_FILENAME is evil. It makes a nice, clean
+ path with no ./ and ../. But if a path element is a symbolic link and
+ sufficient ../ are present to "step backwards" over it, the resulting
+ path will be based on the symlink itself, not on its target:
+ Assume symlink: /path/with/symlink -> /target/place
+ fn_format(..., /path/with/symlink/../myfile, ...MY_UNPACK_FILENAME)
+ results in /path/with/myfile
+ OS functions would affect /target/myfile with the original path.
+ MY_RETURN_REAL_PATH does a better job, but returns all symlinks
+ resolved, which is normally not wanted. Also, it does not resolve
+ the path, if the final target does not exist. Also, it cuts the
+ resulting path name to FN_REFLEN if it is longer than that. Also,
+ on Windows it does not clean the path.
+
+ A correct algorithm has been tried with safe_cleanup_cat_path() in
+ mf_pack.c. It calls my_is_symlink() on each path element from the
+ beginning. If the element is not a symlink, it can be cleaned out. If
+ a symlink is encountered, the part of the path behind it is treated as
+ a relative path in itself. It is cleaned in itself like the above, but
+ my_is_symlink() takes the concatenated previous path segments
+ prepended. If another symlink is found, the algorithm repeats. That
+ way we might end up with no ../ except possibly after symlinks:
+ Input: /path/with/symlink/up/../../yet/another/symlink/up/../end
+ Output: /path/with/symlink/../yet/another/symlink/end
*/
char * fn_format(char * to, const char *name, const char *dir,
@@ -140,3 +165,52 @@ size_t strlength(const char *str)
}
DBUG_RETURN((size_t) (found - str));
} /* strlength */
+
+
+/**
+ Cut path to a printable length.
+
+ This can be used to put a path name into error/warning/info messages,
+ which contain a conversion flag with length limit like %.64s.
+
+ If 'path' is longer than 'length_limit', the end of the path is
+ returned with prepended "... ". Often the end of a path is of
+ more interest in error messages than the begin.
+
+ @note A pointer to static storage is returned. It is assumed that
+ only one path per error message needs to be cut.
+
+ @param[in] path path name
+ @param[in] length_limit length limit from format string
+
+ @return pointer to static string
+*/
+
+char *cut_print_path(const char* path, size_t length_limit)
+{
+ static char path_buff[FN_REFLEN];
+ size_t plen= strlen(path);
+ size_t offset= 0;
+
+ if (length_limit >= FN_REFLEN)
+ length_limit= FN_REFLEN - 1; /* Room for '\0'. */ /* purecov: inspected */
+
+ if (plen > length_limit)
+ {
+ offset= plen - length_limit;
+ plen= length_limit;
+ }
+
+ memcpy(path_buff, path + offset, plen + 1); /* Include '\0'. */
+
+ if (offset)
+ {
+ path_buff[0]= '.';
+ path_buff[1]= '.';
+ path_buff[2]= '.';
+ path_buff[3]= ' ';
+ }
+
+ return path_buff;
+}
+
=== modified file 'mysys/mf_pack.c'
--- a/mysys/mf_pack.c 2008-08-18 17:16:28 +0000
+++ b/mysys/mf_pack.c 2009-04-24 14:55:53 +0000
@@ -23,6 +23,8 @@
#include <iodef.h>
#include <descrip.h>
#endif /* VMS */
+#include <stdarg.h>
+
static char * NEAR_F expand_tilde(char * *path);
@@ -65,7 +67,7 @@ void pack_dirname(char * to, const char
length=0;
if (home_dir)
{
- length= strlen(home_dir);
+ length= home_dir_len;
if (home_dir[length-1] == FN_LIBCHAR)
length--; /* Don't test last '/' */
}
@@ -233,6 +235,317 @@ size_t cleanup_dirname(register char *to
/*
+ Concatenate paths and remove unwanted chars, taking symlinks into account.
+
+ The function takes a variable length list of path names and
+ concatenates them. An FN_LIBCHAR is inserted, if not already present.
+ Empty arguments ("") are ignored. No double FN_LIBCHAR is inserted.
+
+ "./" at begin is removed, possibly multiple times.
+ "/./" is replaced by "/".
+ "/." at end is removed.
+ "../" at begin is retained.
+ "/../" removes previous path element, unless it is a symlink.
+ "/../" at begin is replaced by "/".
+ "/.." at end removes previous path element, unless it is a symlink.
+ "~/" at path begin replaces ~ by home directory.
+ "//" is replaced by "/", except on Win32 at begin.
+
+ If the cleanup process would end up with an empty path, "." is
+ returned.
+
+ The resulting path name has no trailing FN_LIBCHAR.
+
+ If the resulting path exceeds 'to_size', zero is returned, but 'to'
+ can contain a partial path.
+
+ The 'to' buffer is used during the cleanup. It must not overlap with
+ any of the 'from' buffers.
+
+ No attempt is made to create an absolute path, except for "~/" if
+ $HOME is an absolute path. If 'path' is absolute, 'to' will be
+ absolute too.
+
+ For each path element from the beginning, my_is_symlink() is called.
+ If the element is not a symlink, it can be cleaned out. If a symlink
+ is encountered, the part of the path behind it is treated as a
+ relative path in itself. It is cleaned in itself like the above, but
+ my_is_symlink() takes the concatenated previous path segments
+ prepended. If another symlink is found, the algorithm repeats. That
+ way we might end up with no ../ except possibly after symlinks:
+ Input: /path/with/symlink/up/../../yet/another/symlink/up/../end
+ Output: /path/with/symlink/../yet/another/symlink/end
+
+ @param[out] to address of destination buffer
+ @param[in] to_size size of destination buffer
+ @param[in] ... Multiple paths (!= to), terminated by NullS
+
+ @return length of new path name
+ @retval 0 destination buffer size too small
+*/
+
+size_t safe_cleanup_cat_path(char *to, size_t to_size, ...)
+{
+ va_list args;
+ size_t length;
+ size_t parent_len;
+ char *from_ptr;
+ char *start;
+ char *pos;
+ char *end;
+ char parent[5]; /* for "FN_PARENTDIR" */
+ DBUG_ENTER("safe_cleanup_cat_path");
+
+ /* Prepare for variable argument list. */
+ va_start(args, to_size);
+
+ /* Prepare "/.." for comparisons. */
+ parent[0]= FN_LIBCHAR;
+ parent_len= (size_t) (strmov(parent + 1, FN_PARENTDIR) - parent);
+
+ /* Get first non-empty path name. */
+ do
+ {
+ /* Get first (next) argument. */
+ from_ptr= va_arg(args, char*);
+ /* Skip empty strings. */
+ } while (from_ptr && !*from_ptr);
+ /* One path should be present. But if not, just return zero. */
+ if (!from_ptr)
+ {
+ /* purecov: begin tested */ /* unittest */
+ pos= to;
+ goto end;
+ /* purecov: end */
+ }
+ DBUG_PRINT("scc_path", ("from: '%s'", from_ptr));
+ /* Get other start values. */
+ start= to;
+ end= to + to_size;
+
+ IF_DBUG({uint idx; for(idx=0; idx<to_size; idx++) {to[idx]= '0'+idx%10;}});
+
+ /* Handle special conditions. */
+#ifdef FN_DEVCHAR
+ if ((pos= strrchr(from_ptr, FN_DEVCHAR)))
+ {
+ /* Skip device part */
+ length= (size_t) (pos - from_ptr) + 1;
+ if (length >= to_size)
+ {
+ pos= to; /* Return zero length (== error). */
+ goto end;
+ }
+ memcpy(to, from_ptr, length);
+ start= to + length;
+ from_ptr+= length;
+ }
+#endif
+ /* If path starts with ~/ replace by $HOME. */
+ if ((from_ptr[0] == FN_HOMELIB) &&
+ ((from_ptr[1] == '/') || (from_ptr[1] == FN_LIBCHAR)) &&
+ home_dir)
+ {
+ length= home_dir_len;
+ /* Do not copy trailing FN_LIBCHAR. */
+ if (length && (home_dir[length - 1] == FN_LIBCHAR))
+ length--; /* purecov: tested */ /* unittest */
+ if (length >= to_size)
+ {
+ /* purecov: begin inspected */
+ pos= to; /* Return zero length (== error). */
+ goto end;
+ /* purecov: end */
+ }
+ memcpy(to, home_dir, length);
+ /*
+ Do not allow $HOME path members to be eaten by ../
+ Otherwise we must check every $HOME path element for symlink.
+ */
+ start= to + length;
+ /* Skip ~ and point at / */
+ from_ptr++;
+ }
+
+ /* Copy every character from from to to. Check at every FN_LIBCHAR. */
+ for (pos= start; pos < end; pos++)
+ {
+ /* Copy a character from 'from' to 'to'. */
+ *pos= *from_ptr++;
+
+ /* If at end of path name, turn to next path name. */
+ if (!*pos)
+ {
+ do
+ {
+ /* Get next argument. */
+ from_ptr= va_arg(args, char*);
+ /* Skip empty strings. */
+ } while (from_ptr && !*from_ptr);
+ DBUG_PRINT("scc_path", ("from: '%s'", from_ptr ? from_ptr : "[NullS]"));
+
+ /*
+ Do not stop here, even if no argument follows.
+ At the end of the resulting path name we must pretend to have a
+ FN_LIBCHAR to handle trailing /. or /..
+ Insert FN_LIBCHAR after every from-path. If it is duplicate or
+ at end, it is removed below.
+ */
+ *pos= FN_LIBCHAR;
+ }
+
+ /* Convert internally used slashes to FN_LIBCHAR. */
+ if (*pos == '/')
+ *pos= FN_LIBCHAR;
+
+ /* At every FN_LIBCHAR check for additional changes. */
+ if (*pos == FN_LIBCHAR)
+ {
+ DBUG_PRINT("scc_path_loop", ("FN_LIBCHAR at end of '%.*s'",
+ (int) (pos + 1 - to), to));
+ /* Check if we are at a FN_LIBCHAR behind /.. */
+ if ((size_t) (pos - start) >= parent_len &&
+ bcmp(pos - parent_len, parent, parent_len) == 0)
+ {
+ /* If .../../ skip previous path element. Backstep over /.. first. */
+ pos-= parent_len;
+ DBUG_PRINT("scc_path_loop", ("skipped backref, now at end of '%.*s'",
+ (int) (pos + 1 - to), to));
+ /* Now at the / of /.. */
+ if (pos != start)
+ {
+ /* Not at root. Step back over FN_LIBCHAR. */
+ pos--;
+ /*
+ Step back over the previous path element.
+ This might bring pos below to (there is no valid character left).
+ */
+ while ((pos >= start) && (*pos != FN_LIBCHAR))
+ pos--;
+ DBUG_PRINT("scc_path_loop", ("skipped parent, now at end of '%.*s'",
+ (int) (pos + 1 - to), to));
+ }
+ /*
+ At root, we are back at the beginning.
+ After a symlink, start points to the first dot of /..
+ so we won't enter this branch, but the ../ branch below.
+ */
+ }
+ /*
+ If path starts as ../ or has ../ after a symlink,
+ set a new start to avoid its removal.
+ */
+ else if (((size_t) (pos - start) == (parent_len - 1)) &&
+ !bcmp(start, parent + 1, parent_len - 1))
+ {
+ start= pos + 1;
+ }
+ /* Remove duplicate '/' */
+ else if ((pos > start) && (pos[-1] == FN_LIBCHAR))
+ {
+#ifdef FN_NETWORK_DRIVES
+ /* Keep // at path begin. */
+ if ((pos - to) != 1)
+#endif
+ pos--;
+ }
+ /* Skip /./ */
+ else if (((pos - start) > 1) &&
+ (pos[-1] == FN_CURLIB) &&
+ (pos[-2] == FN_LIBCHAR))
+ {
+ pos-= 2; /* purecov: tested */ /* unittest */
+ }
+ /* Skip ./ at path begin. */
+ else if (((pos - to) == 1) &&
+ (pos[-1] == FN_CURLIB))
+ {
+ /* This brings pos below to (there is no valid character left). */
+ pos-= 2; /* purecov: tested */ /* unittest */
+ }
+ /* No cleanup done. Check if path element is a symbolic link. */
+ else
+ {
+ if (pos > start)
+ {
+ /* Temporarily terminate the path name. */
+ *pos= '\0';
+ if (my_is_symlink(to))
+ {
+ /* Prevent backstep over symlink. */
+ start= pos + 1;
+ }
+ DBUG_PRINT("scc_path", ("%s symlink: '%s'", (start == pos + 1) ?
+ "is" : "is not", to));
+ }
+ /* Restore FN_LIBCHAR if not at last argument. */
+ *pos= from_ptr ? FN_LIBCHAR : '\0';
+ }
+ DBUG_PRINT("scc_path_loop", ("handled FN_LIBCHAR, now at end of '%.*s'",
+ (int) (pos + 1 - to), to));
+ /*
+ Stop if NullS found (end of arguments). Note that the path name is
+ not terminated with '\0' if a cleanup action has been done in this
+ iteration. Pos points at a terminating FN_LIBCHAR instead or is <=
+ to.
+ */
+ if (!from_ptr)
+ break;
+ } /* end if (*pos == FN_LIBCHAR) */
+ } /* end for(pos < end) */
+
+ /*
+ The loop ends either if the 'to' buffer is full or the end of the
+ argument list is reached.
+ If the 'to' buffer is full, return zero length (== error).
+ */
+ if (pos >= end)
+ {
+ /* purecov: begin tested */ /* unittest */
+ pos= to;
+ goto end;
+ /* purecov: end */
+ }
+
+ /*
+ The end of the argument list is reached. This means that the last
+ character received from the last argument was a terminating '\0',
+ which has been replaces by a FN_LIBCHAR and not necessarily been
+ changed back. There was space in the buffer for the terminator.
+ Whatever cleanup action has been done in between can only make more
+ room in the buffer. So we can safely restore the terminator, if not
+ already present. However, we do not want to remove a single
+ FN_LIBCHAR. It means the file system root directory "/".
+ If pos < to, the path is cleaned out completely, leaving it empty.
+ */
+ if ((pos < to) || (*pos == FN_LIBCHAR))
+ {
+ if (pos <= to)
+ pos++; /* purecov: tested */ /* unittest */
+ *pos= '\0';
+ }
+
+ /* Trim trailing FN_LIBCHAR. But leave a single FN_LIBCHAR at begin. */
+ while ((pos > to + 1) && (pos[-1] == FN_LIBCHAR))
+ *(--pos)= '\0'; /* purecov: tested */ /* unittest */
+
+ /* We do not want to deliver an empty path. It would be invalid. */
+ if ((pos == to) && !*pos && (to_size > 1))
+ {
+ /* purecov: begin tested */ /* unittest */
+ *(pos++)= '.';
+ *pos= '\0';
+ /* purecov: end */
+ }
+
+end:
+ DBUG_PRINT("scc_path", ("to: '%.*s'", (int) (pos - to), to));
+ va_end(args);
+ DBUG_RETURN((size_t) (pos - to));
+} /* safe_cleanup_cat_path */
+
+
+/*
On system where you don't have symbolic links, the following
code will allow you to create a file:
directory-name.sym that should contain the real path
=== modified file 'mysys/my_init.c'
--- a/mysys/my_init.c 2009-03-11 17:17:00 +0000
+++ b/mysys/my_init.c 2009-04-24 14:55:53 +0000
@@ -108,8 +108,13 @@ my_bool my_init(void)
if (!home_dir)
{ /* Don't initialize twice */
my_win_init();
- if ((home_dir=getenv("HOME")) != 0)
- home_dir=intern_filename(home_dir_buff,home_dir);
+ if ((home_dir= getenv("HOME")) != NULL)
+ {
+ home_dir= intern_filename(home_dir_buff, home_dir);
+ home_dir_len= strlen(home_dir);
+ }
+ else
+ home_dir_len= 0; /* purecov: inspected */
#ifndef VMS
/* Default creation of new files */
if ((str=getenv("UMASK")) != 0)
=== modified file 'mysys/my_lib.c'
--- a/mysys/my_lib.c 2008-06-17 23:37:23 +0000
+++ b/mysys/my_lib.c 2009-04-24 14:55:53 +0000
@@ -508,6 +508,28 @@ error:
#endif /* _WIN32 */
+
+/**
+ Test if path is a directory.
+
+ @param[in] path path name
+
+ @return test result
+ @retval TRUE path is directory
+ @retval FALSE path is not directory
+*/
+
+my_bool test_if_directory(const char* path)
+{
+ MY_STAT statbuf;
+
+ if (my_stat(path, &statbuf, MYF(0)) &&
+ MY_S_ISDIR(statbuf.st_mode))
+ return TRUE;
+ return FALSE;
+}
+
+
/****************************************************************************
** File status
** Note that MY_STAT is assumed to be same as struct stat
=== modified file 'mysys/my_static.c'
--- a/mysys/my_static.c 2009-04-01 20:48:42 +0000
+++ b/mysys/my_static.c 2009-04-24 14:55:53 +0000
@@ -25,7 +25,8 @@
my_bool timed_mutexes= 0;
/* from my_init */
-char * home_dir=0;
+char *home_dir= NULL;
+size_t home_dir_len= 0;
const char *my_progname= NULL, *my_progname_short= NULL;
char NEAR curr_dir[FN_REFLEN]= {0},
NEAR home_dir_buff[FN_REFLEN]= {0};
=== modified file 'sql/backup/kernel.cc'
--- a/sql/backup/kernel.cc 2009-04-01 11:08:48 +0000
+++ b/sql/backup/kernel.cc 2009-04-24 14:55:53 +0000
@@ -156,14 +156,6 @@ execute_backup_command(THD *thd,
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)))
- DBUG_RETURN(send_error(context, ER_BACKUP_BACKUPDIR, backupdir->c_ptr()));
-
switch (lex->sql_command) {
case SQLCOM_BACKUP:
@@ -425,13 +417,55 @@ Backup_restore_ctx::~Backup_restore_ctx(
@param[in] backupdir The backupdir system variable value.
@param[in] orig_loc The path + file name specified in the backup command.
- @returns 0
+ @returns status
+ @retval 0 ok
+ @retval != 0 error number
*/
int Backup_restore_ctx::prepare_path(::String *backupdir,
LEX_STRING orig_loc)
{
- char fix_path[FN_REFLEN];
- char full_path[FN_REFLEN];
+ const char *datadir_ptr= "";
+ char *abs_backupdir_ptr;
+ size_t datadir_len= 0;
+ size_t abs_backupdir_len;
+ size_t plen= 0;
+ DBUG_ENTER("Backup_restore_ctx::prepare_path");
+
+ /*
+ Contruct the full path to the backupdir.
+ The resulting path can be a maximum of:
+ datadir + '/' + backudir + '\0' + home_dir
+ Home_dir comes into the game if backudir starts with "~/".
+ The clean path will not be longer than the concatenated one.
+ */
+ if (!test_if_hard_path(backupdir->c_ptr_safe()))
+ {
+ /* Backupdir is relative itself. Prepend datadir. */
+ datadir_ptr= mysql_real_data_home;
+ datadir_len= mysql_real_data_home_len;
+ }
+ abs_backupdir_len= datadir_len + 1 + backupdir->length() + 1 + home_dir_len;
+ abs_backupdir_ptr= (char*) my_malloc(abs_backupdir_len, MYF(MY_WME));
+ if (!abs_backupdir_ptr)
+ goto end; /* purecov: inspected */
+ abs_backupdir_len=
+ safe_cleanup_cat_path(abs_backupdir_ptr, abs_backupdir_len,
+ datadir_ptr, backupdir->c_ptr_safe(), NullS);
+
+ /*
+ 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.
+ This can of course happen after the check too...
+ It has been decided that this check shall be done even if the
+ BACKUP/RESTORE statement contains an absolute path name.
+ If this requirement is dropped one day, we can easily add
+ !test_if_hard_path(lex->backup_dir.str) &&. See Bug#43536.
+ */
+ if (check_backupdir(abs_backupdir_ptr))
+ DBUG_RETURN(send_error(*this, ER_NOT_RW_DIR, "backupdir",
+ cut_print_path(abs_backupdir_ptr,
+ ER_NOT_RW_DIR_PATHSIZE)));
/*
Prepare the path using the backupdir iff no relative path
@@ -467,24 +501,32 @@ int Backup_restore_ctx::prepare_path(::S
*/
/*
- First, we construct the complete path from backupdir.
- */
- fn_format(fix_path, backupdir->ptr(), mysql_real_data_home, "",
- MY_UNPACK_FILENAME | MY_RELATIVE_PATH);
-
- /*
- Next, we contruct the full path to the backup file.
- */
- fn_format(full_path, orig_loc.str, fix_path, "",
- MY_UNPACK_FILENAME | MY_RELATIVE_PATH);
-
- /*
- Copy result to member variable for Stream class.
- */
- m_path.copy(full_path, strlen(full_path), system_charset_info);
- report_backup_file(m_path.c_ptr());
-
- return 0;
+ Contruct the full path to the backup file.
+ The resulting path can be a maximum of:
+ abs_backudir + '/' + orig_loc + '\0' + home_dir
+ Home_dir comes into the game if orig_loc starts with "~/".
+ The clean path will not be longer than the concatenated one.
+ */
+ if (test_if_hard_path(orig_loc.str))
+ {
+ /* Absolute path. Prepend nothing. */
+ my_free(abs_backupdir_ptr, MYF(0));
+ abs_backupdir_ptr= (char*) "";
+ abs_backupdir_len= 0;
+ }
+ m_path.alloc(abs_backupdir_len + 1 + orig_loc.length + 1 + home_dir_len);
+ plen= safe_cleanup_cat_path(&m_path[0], m_path.alloced_length(),
+ abs_backupdir_ptr, orig_loc.str, NullS);
+ m_path.length(plen);
+ DBUG_PRINT("backupdir", ("file: '%s' len: %u",
+ m_path.c_ptr(), m_path.length()));
+
+ report_backup_file(m_path.c_ptr_safe());
+
+ if (abs_backupdir_len)
+ my_free(abs_backupdir_ptr, MYF(0));
+ end:
+ DBUG_RETURN(plen ? 0 : ER_PATH_LENGTH);
}
/**
@@ -560,9 +602,11 @@ int Backup_restore_ctx::prepare(::String
return fatal_error(report_error(ER_BAD_PATH, location.str));
/*
- Computer full path to backup file.
+ Compute full path to backup file.
*/
- prepare_path(backupdir, location);
+ ret= prepare_path(backupdir, location);
+ if (ret)
+ return ret;
// create new instance of memory allocator for backup stream library
=== modified file 'sql/backup/stream.cc'
--- a/sql/backup/stream.cc 2009-03-16 14:38:05 +0000
+++ b/sql/backup/stream.cc 2009-04-24 14:55:53 +0000
@@ -214,27 +214,107 @@ Stream::Stream(Logger &log, ::String *pa
or not backup tries to write to the path (or a sub-path) specified
by secure-backup-file-priv.
+ Access is granted if:
+ a) option not specified, or
+ b) path is (subpath of) secure-backup-file-priv option
+
Reports error ER_OPTION_PREVENTS_STATEMENT if backup tries to write
to a different path than specified by secure-backup-file-priv.
-
+
@retval TRUE backup is allowed to write to this path
@retval FALSE backup is not allowed to write to this path. Side
effect: error is reported
*/
-bool Stream::test_secure_file_priv_access(char *path) {
+
+bool Stream::test_secure_file_priv_access(char *path)
+{
+ const char *datadir_ptr;
+ char *clean_secpath= NULL;
+ size_t datadir_len;
+ size_t slen;
+ size_t plen;
+ bool has_access= TRUE;
+ DBUG_ENTER("Stream::test_secure_file_priv_access");
+
/*
- Access is granted if:
- a) option not specified, or
- b) path is (subpath of) secure-backup-file-priv option
+ If secure-backup-file-priv option is not set, access is granted.
*/
- bool has_access = !opt_secure_backup_file_priv ||
- !strncmp(opt_secure_backup_file_priv, path,
- strlen(opt_secure_backup_file_priv));
- if (!has_access)
- m_log.report_error(ER_OPTION_PREVENTS_STATEMENT,
- "--secure-backup-file-priv");
+ if (!opt_secure_backup_file_priv)
+ goto end;
+
+ /* Change default to simplify error reporting. */
+ has_access= FALSE;
+
+ /*
+ To be safe, we must check on clean paths to defeat tricks with
+ symbolic links and backwards referencing path elements.
+ The path name in opt_secure_backup_file_priv may meanwhile reference
+ a different place in the file system. One or more path elements may
+ have changed to be a symbolic link or vice versa. Since the path
+ cleaning algorithm must respect symbolic links (explanation in
+ function comment of safe_cleanup_cat_path()), we need to clean it
+ before every use.
+ The resulting path can be a maximum of:
+ datadir + '/' + securepath + '/' + '\0' + home_dir
+ Home_dir comes into the game if securepath starts with "~/".
+ The clean path will not be longer than the concatenated one.
+ */
+ if (test_if_hard_path(opt_secure_backup_file_priv))
+ {
+ /* Absolute path. Prepend nothing. */
+ datadir_ptr= "";
+ datadir_len= 0;
+ }
+ else
+ {
+ /* Relative path. Prepend datadir. */
+ /* purecov: begin inspected */
+ datadir_ptr= mysql_real_data_home;
+ datadir_len= mysql_real_data_home_len;
+ /* purecov: end */
+ }
+ slen= datadir_len + 1 + opt_secure_backup_file_priv_len + 2 + home_dir_len;
+ clean_secpath= (char*) my_malloc(slen, MYF(MY_WME));
+ if (unlikely(!clean_secpath))
+ goto cleanup; /* purecov: inspected */
+ /* Reserve one character for an appended FN_LIBCHAR. */
+ slen= safe_cleanup_cat_path(clean_secpath, slen - 1,
+ datadir_ptr , opt_secure_backup_file_priv, NullS);
+ if (unlikely(!slen))
+ goto err; /* purecov: inspected */
+ /* Append a FN_LIBCHAR. Space was reserved above. */
+ clean_secpath[slen++]= FN_LIBCHAR;
+ clean_secpath[slen]= '\0';
+ DBUG_PRINT("backupdir", ("clean_secp: '%s'", clean_secpath));
+
+ /*
+ The backup image file path has been made absolute and cleaned in the
+ backup kernel.
+ */
+ DBUG_PRINT("backupdir", ("clean path: '%s'", path));
+
+ /*
+ Path must be something that starts like securepath but continues
+ behind it.
+ */
+ has_access= (strlen(path) > slen) && !strncmp(clean_secpath, path, slen);
+
+ if (!has_access)
+ m_log.report_error(ER_OPTION_PREVENTS_STATEMENT,
+ "--secure-backup-file-priv");
+ goto cleanup;
+
+ /* purecov: begin inspected */
+ err:
+ DBUG_PRINT("backupdir", ("_error: too long"));
+ my_error(ER_PATH_LENGTH, MYF(0), "backup image file");
+ /* purecov: end */
+
+ cleanup:
+ my_free(clean_secpath, MYF(MY_ALLOW_ZERO_PTR));
- return has_access;
+ end:
+ DBUG_RETURN(has_access);
}
/**
=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h 2009-04-01 21:36:07 +0000
+++ b/sql/mysql_priv.h 2009-04-24 14:55:53 +0000
@@ -1902,6 +1902,7 @@ extern time_t server_start_time, flush_s
#endif /* MYSQL_SERVER */
#if defined MYSQL_SERVER || defined INNODB_COMPATIBILITY_HOOKS
extern uint mysql_data_home_len;
+extern uint mysql_real_data_home_len;
extern char *mysql_data_home,server_version[SERVER_VERSION_LENGTH],
mysql_real_data_home[], mysql_unpacked_real_data_home[];
extern CHARSET_INFO *character_set_filesystem;
@@ -2017,6 +2018,7 @@ extern my_bool opt_enable_named_pipe, op
extern my_bool opt_secure_auth;
extern char* opt_secure_file_priv;
extern char* opt_secure_backup_file_priv;
+extern size_t opt_secure_backup_file_priv_len;
extern my_bool opt_log_slow_admin_statements, opt_log_slow_slave_statements;
extern my_bool sp_automatic_privileges, opt_noacl;
extern my_bool opt_old_style_user_limits, trust_function_creators;
=== modified file 'sql/mysqld.cc'
--- a/sql/mysqld.cc 2009-04-01 21:36:07 +0000
+++ b/sql/mysqld.cc 2009-04-24 14:55:53 +0000
@@ -501,7 +501,8 @@ my_bool relay_log_recovery;
my_bool opt_sync_frm, opt_allow_suspicious_udfs;
my_bool opt_secure_auth= 0;
char* opt_secure_file_priv= 0;
-char* opt_secure_backup_file_priv= 0;
+char* opt_secure_backup_file_priv= NULL;
+size_t opt_secure_backup_file_priv_len= 0;
my_bool opt_log_slow_admin_statements= 0;
my_bool opt_log_slow_slave_statements= 0;
my_bool lower_case_file_system= 0;
@@ -634,6 +635,7 @@ char mysql_real_data_home[FN_REFLEN],
def_ft_boolean_syntax[sizeof(ft_boolean_syntax)];
char mysql_unpacked_real_data_home[FN_REFLEN];
int mysql_unpacked_real_data_home_len;
+uint mysql_real_data_home_len;
uint reg_ext_length;
const key_map key_map_empty(0);
key_map key_map_full(0); // Will be initialized later
@@ -1408,6 +1410,7 @@ void clean_up(bool print_message)
x_free(opt_relay_logname);
x_free(opt_secure_file_priv);
x_free(opt_secure_backup_file_priv);
+ opt_secure_backup_file_priv_len= 0;
bitmap_free(&temp_pool);
free_max_user_conn();
#ifdef HAVE_REPLICATION
@@ -6584,7 +6587,8 @@ Can't be set to 1 if --log-slave-updates
GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"secure-backup-file-priv", OPT_SECURE_BACKUP_FILE_PRIV,
"Limit BACKUP and RESTORE to files within specified directory",
- (uchar**) &opt_secure_backup_file_priv, (uchar**) &opt_secure_backup_file_priv, 0,
+ (uchar**) &opt_secure_backup_file_priv,
+ (uchar**) &opt_secure_backup_file_priv, 0,
GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"server-id", OPT_SERVER_ID,
"Uniquely identifies the server instance in the community of replication partners.",
@@ -7931,7 +7935,8 @@ static int mysql_init_variables(void)
opt_tc_log_file= (char *)"tc.log"; // no hostname in tc_log file name !
opt_secure_auth= 0;
opt_secure_file_priv= 0;
- opt_secure_backup_file_priv= 0;
+ opt_secure_backup_file_priv= NULL;
+ opt_secure_backup_file_priv_len= 0;
opt_bootstrap= opt_myisam_logical_log= 0;
mqh_used= 0;
segfaulted= kill_in_progress= 0;
@@ -8012,8 +8017,9 @@ static int mysql_init_variables(void)
/* Set directory paths */
strmake(language, LANGUAGE, sizeof(language)-1);
- strmake(mysql_real_data_home, get_relative_path(DATADIR),
- sizeof(mysql_real_data_home)-1);
+ mysql_real_data_home_len=
+ strmake(mysql_real_data_home, get_relative_path(DATADIR),
+ sizeof(mysql_real_data_home) - 1) - mysql_real_data_home;
mysql_data_home_buff[0]=FN_CURLIB; // all paths are relative from here
mysql_data_home_buff[1]=0;
mysql_data_home_len= 2;
@@ -8192,21 +8198,15 @@ mysqld_get_one_option(int optid,
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);
+ sys_var_backupdir.value= my_strdup(argument, MYF(MY_WME));
+ sys_var_backupdir.value_length= (sys_var_backupdir.value ?
+ strlen(sys_var_backupdir.value) : 0);
break;
}
case 'h':
- strmake(mysql_real_data_home,argument, sizeof(mysql_real_data_home)-1);
+ mysql_real_data_home_len=
+ strmake(mysql_real_data_home, argument,
+ sizeof(mysql_real_data_home) - 1) - mysql_real_data_home;
/* Correct pointer set by my_getopt (for embedded library) */
mysql_data_home= mysql_real_data_home;
mysql_data_home_len= strlen(mysql_data_home);
@@ -8961,15 +8961,20 @@ static int 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.
- */
+ mysql_real_data_home_len= strlen(mysql_real_data_home);
+
+ /* Set default backupdir variable if value is not set. */
if (!sys_var_backupdir.value)
+ sys_var_backupdir.value= default_backupdir();
+ 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);
+ sys_var_backupdir.value_length= strlen(sys_var_backupdir.value);
+ /* Warn if not a directory. */
+ (void) is_usable_directory(NULL, sys_var_backupdir.name,
+ sys_var_backupdir.value, mysql_real_data_home);
}
+ else
+ sys_var_backupdir.value_length= 0; /* purecov: inspected */
(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 :
@@ -9014,16 +9019,16 @@ static int fix_paths(void)
opt_secure_file_priv= my_strdup(buff, MYF(MY_FAE));
}
- /*
- Convert the secure-backup-file-priv option to system format, allowing
- a quick strcmp to check if read or write is in an allowed dir
- */
if (opt_secure_backup_file_priv)
{
- convert_dirname(buff, opt_secure_backup_file_priv, NullS);
- my_free(opt_secure_backup_file_priv, MYF(0));
- opt_secure_backup_file_priv= my_strdup(buff, MYF(MY_FAE));
+ opt_secure_backup_file_priv_len= strlen(opt_secure_backup_file_priv);
+ /* Warn if not a directory. */
+ (void) is_usable_directory(NULL, "secure_backup_file_priv",
+ opt_secure_backup_file_priv,
+ mysql_real_data_home);
}
+ else
+ opt_secure_backup_file_priv_len= 0;
return 0;
}
=== modified file 'sql/set_var.cc'
--- a/sql/set_var.cc 2009-04-01 21:36:07 +0000
+++ b/sql/set_var.cc 2009-04-24 14:55:53 +0000
@@ -155,10 +155,6 @@ static bool sys_update_backup_progress_l
static void sys_default_backup_progress_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);
-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);
@@ -958,11 +954,8 @@ sys_var_str sys_var_backup_progress_log_
/*
Create the backupdir dynamic variable.
*/
-sys_var_str sys_var_backupdir(&vars, "backupdir",
- sys_check_backupdir,
- sys_update_backupdir,
- sys_default_backupdir,
- 0);
+sys_var_str sys_var_backupdir(&vars, "backupdir", 0, sys_update_backupdir,
+ sys_default_backupdir, 0);
sys_var_str sys_var_slow_log_path(&vars, "slow_query_log_file", sys_check_log_path,
sys_update_slow_log_path,
@@ -2982,159 +2975,237 @@ uchar *sys_var_log_backup_output::value_
return (uchar*) thd->strmake(tmp.ptr(), length);
}
+
/*
Functions for backupdir variable.
*/
/**
- Set the default for the backupdir
+ Get the default for the backupdir.
- @param[IN] buff Buffer to use for making string.
+ The resulting path name will not have trailing FN_LIBCHAR. It is
+ allocated with my_malloc(), so needs to be freed after use.
- @returns pointer to string (buff)
+ @returns pointer
+ @retval NULL memory allocation error
+ @retval != NULL pointer to path name string
*/
-char *make_default_backupdir(char *buff)
+
+char *default_backupdir(void)
{
- strmake(buff, mysql_data_home, FN_REFLEN-5);
- return buff;
+ char *path;
+ size_t plen;
+ DBUG_ENTER("default_backupdir");
+
+ path= my_strdup(mysql_real_data_home, MYF(MY_WME));
+ plen= mysql_real_data_home_len;
+
+ /* Strip off trailing FN_LIBCHAR. */
+ while (plen && (path[plen - 1] == FN_LIBCHAR))
+ path[--plen]= '\0';
+
+ DBUG_PRINT("backupdir", ("default: '%s'", path));
+ DBUG_RETURN(path);
}
+
/**
- Check the backupdir value for validity.
+ Check if backupdir is bad: not a directory or not writable.
- 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] backupdir backupdir
- @param[IN] thd Current thread context.
- @param[IN] var The new value set in set_var structure.
+ @return status
+ @retval TRUE bad, not directory or not writable
+ @retval FALSE good, is directory and is writable
- @returns 0
+ @note The path name must not have a trailing FN_LIBCHAR.
*/
-static int sys_check_backupdir(THD *thd, set_var *var)
+
+bool check_backupdir(char *backupdir)
{
- 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;
+ int is_bad;
+ DBUG_ENTER("check_backupdir");
+ DBUG_PRINT("backupdir", ("path: '%s'", backupdir));
- if (!(res= var->value->val_str(&str)))
- goto err;
+ is_bad= (!test_if_directory(backupdir) ||
+ my_access(backupdir, W_OK));
- log_file_str= res->c_ptr_safe();
- bzero(&f_stat, sizeof(MY_STAT));
+ DBUG_PRINT("backupdir", ("is_bad: %d", is_bad));
+ DBUG_RETURN(is_bad);
+}
- /* 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 path is a writable directory.
- /*
- 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;
+ Internally, the path is prepended with a prefix if it is not an
+ absolute path.
- return 0;
+ Issue a warning if the resulting path is not a directory or not
+ writable.
+
+ Throw an error and return FALSE if memory allocation fails.
+
+ @param[in] thd thread context, may be NULL
+ @param[in] varname name of variable
+ @param[in] path path name
+ @param[in] prefix path name to prepend if path is relative
+
+ @return status
+ @retval TRUE path is a writable directory
+ @retval FALSE not a writable directory or memory error
+*/
+
+bool is_usable_directory(THD *thd, const char *varname,
+ const char *path, const char *prefix)
+{
+ char *alloc_path= NULL;
+ const char *datadir_ptr;
+ size_t datadir_len;
+ size_t alen;
+ size_t plen;
+ bool is_dir= FALSE;
+ DBUG_ENTER("is_usable_directory");
+
+ if (test_if_hard_path(path))
+ {
+ /* Absolute path. Prepend nothing. */
+ datadir_ptr= "";
+ datadir_len= 0;
+ }
+ else
+ {
+ /* Relative path. Prepend datadir. */
+ datadir_ptr= mysql_real_data_home;
+ datadir_len= mysql_real_data_home_len;
+ }
+ plen= strlen(path);
-err:
/*
- We print a warning if backupdir is invalid but set it anyway.
+ The resulting path can be a maximum of:
+ Datadir + '/' + path + '\0' + home_dir
+ Home_dir comes into the game if path starts with "~/".
+ The clean path will not be longer than the concatenated one.
*/
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_BACKUP_BACKUPDIR, ER(ER_BACKUP_BACKUPDIR),
- var->value->str_value.c_ptr());
- return 0;
+ alen= datadir_len + 1 + plen + 1 + home_dir_len;
+ alloc_path= (char*) my_malloc(alen, MYF(MY_WME));
+ if (!alloc_path)
+ goto end; /* purecov: inspected */
+
+ plen= safe_cleanup_cat_path(alloc_path, alen, datadir_ptr, path, NullS);
+ if (unlikely(!plen))
+ goto err; /* purecov: inspected */
+
+ /* Check path but don't stop if it's bad, just warn. */
+ if (check_backupdir(alloc_path))
+ {
+ if (!thd)
+ {
+ /*
+ Cannot use ER(ER_NOT_RW_DIR) at this place.
+ The errmesg array may not yet be initialized.
+ We can profit from this by inserting the variable name.
+ secure_backup_file_priv is initialized at server startup only,
+ so it will always take this branch.
+ */
+ sql_print_warning("The path specified for the variable %s is not"
+ " a directory or cannot be written: '%s'\n",
+ varname, alloc_path);
+ }
+ else
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_NOT_RW_DIR, ER(ER_NOT_RW_DIR), varname,
+ cut_print_path(alloc_path, ER_NOT_RW_DIR_PATHSIZE));
+ }
+ }
+ else
+ is_dir= TRUE;
+ goto end;
+
+ /* purecov: begin inspected */
+ err:
+ DBUG_PRINT("error", (": path too long"));
+ my_error(ER_PATH_LENGTH, MYF(0), varname);
+ /* purecov: end */
+
+ end:
+ my_free(alloc_path, MYF(MY_ALLOW_ZERO_PTR));
+ DBUG_RETURN(is_dir);
}
+
/**
- Update the backupdir variable
-
+ 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.
+ @param[in] thd thread context
+ @param[in] var new value for backupdir in a set_var structure
- @returns 0 valid, 1 invalid.
+ @return error_status
+ @retval TRUE error, value not changed
+ @retval FALSE ok, value changed
*/
-static bool sys_update_backupdir(THD *thd, set_var * var)
+static bool sys_update_backupdir(THD *thd, set_var *var)
{
char buff[FN_REFLEN];
- char *new_value= 0, *copied_value= NULL;
- bool result= 0;
- uint str_length;
String str(buff, sizeof(buff), system_charset_info);
+ char *backupdir;
+ uint str_length;
+ bool error_status= TRUE;
+ DBUG_ENTER("sys_update_backupdir");
if (var)
{
String *strres;
if (!(strres= var->value->val_str(&str)))
- goto err;
- copied_value= strres->c_ptr_safe();
- str_length= strres->length();
+ {
+ my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), var->var->name, "NULL");
+ goto end;
+ }
+ backupdir= my_strndup(strres->ptr(), strres->length(), MYF(MY_WME));
}
else
{
- copied_value= make_default_backupdir(buff);
- str_length= strlen(copied_value);
- }
-
- /*
- Check maximum string length and error if too long.
- Do not set the value.
- */
- if (str_length > FN_REFLEN)
- {
- my_error(ER_PATH_LENGTH, MYF(0), var->var->name);
- return 1;
+ backupdir= default_backupdir();
}
+ if (unlikely(!backupdir))
+ goto end; /* purecov: inspected */
+ str_length= strlen(backupdir);
- if (!(new_value= my_strndup(copied_value, str_length, MYF(MY_FAE+MY_WME))))
- {
- result= 1;
- goto err;
- }
+ /* Warn if not a directory. */
+ (void) is_usable_directory(thd, "backupdir", backupdir,
+ mysql_real_data_home);
pthread_mutex_lock(&LOCK_global_system_variables);
logger.lock_exclusive();
my_free(sys_var_backupdir.value, MYF(MY_ALLOW_ZERO_PTR));
- sys_var_backupdir.value= new_value;
+ sys_var_backupdir.value= backupdir;
sys_var_backupdir.value_length= str_length;
logger.unlock();
pthread_mutex_unlock(&LOCK_global_system_variables);
+ error_status= FALSE;
- 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;
+ end:
+ DBUG_RETURN(error_status);
}
+
/**
- Set the default value for the backupdir variable
-
+ 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).
+ @param[in] thd thread context
+ @param[in] type ignored (needed for api)
*/
-static void sys_default_backupdir(THD *thd, enum_var_type type)
+static void sys_default_backupdir(THD *thd,
+ enum_var_type type __attribute__((unused)))
{
- sys_update_backupdir(thd, 0);
+ sys_update_backupdir(thd, NULL);
}
=== modified file 'sql/set_var.h'
--- a/sql/set_var.h 2008-12-24 10:48:24 +0000
+++ b/sql/set_var.h 2009-04-24 14:55:53 +0000
@@ -1536,6 +1536,13 @@ CHARSET_INFO *get_old_charset_by_name(co
uchar* find_named(I_List<NAMED_LIST> *list, const char *name, uint length,
NAMED_LIST **found);
+/* This must match the path length limit in the ER_NOT_RW_DIR error msg. */
+#define ER_NOT_RW_DIR_PATHSIZE 200
+bool is_usable_directory(THD *thd, const char *varname,
+ const char *path, const char *prefix);
+bool check_backupdir(char *backupdir);
+char *default_backupdir(void);
+
extern sys_var_str sys_var_general_log_path, sys_var_slow_log_path,
sys_var_backup_history_log_path, sys_var_backup_progress_log_path,
sys_var_backupdir;
=== modified file 'sql/share/errmsg.txt'
--- a/sql/share/errmsg.txt 2009-04-03 17:58:04 +0000
+++ b/sql/share/errmsg.txt 2009-04-24 14:55:53 +0000
@@ -6408,8 +6408,12 @@ 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"
+
+# NOTE: If changing the length limit for the path name in this message,
+# please change ER_NOT_RW_DIR_PATHSIZE in set_var.h too.
+ER_NOT_RW_DIR
+ eng "The path specified for the system variable %s is not a directory or cannot be written: %.200s"
+
ER_DDL_TIMEOUT
eng "The backup wait timeout has expired for query '%-64s'."
ER_BACKUP_LIST_DB_PRIV
=== modified file 'unittest/mysys/CMakeLists.txt'
--- a/unittest/mysys/CMakeLists.txt 2009-03-11 17:20:26 +0000
+++ b/unittest/mysys/CMakeLists.txt 2009-04-24 14:55:53 +0000
@@ -32,3 +32,7 @@ TARGET_LINK_LIBRARIES(lf-t mytap mysys d
ADD_EXECUTABLE(waiting_threads-t waiting_threads-t.c)
TARGET_LINK_LIBRARIES(waiting_threads-t mytap mysys dbug strings)
+
+ADD_EXECUTABLE(safe_cleanup_cat_path-t safe_cleanup_cat_path-t.c)
+TARGET_LINK_LIBRARIES(safe_cleanup_cat_path-t mytap mysys dbug strings)
+
=== modified file 'unittest/mysys/Makefile.am'
--- a/unittest/mysys/Makefile.am 2009-01-07 10:58:33 +0000
+++ b/unittest/mysys/Makefile.am 2009-04-24 14:55:53 +0000
@@ -18,7 +18,8 @@ AM_CPPFLAGS = @ZLIB_INCLUDES@ \
noinst_HEADERS = thr_template.c
-noinst_PROGRAMS = bitmap-t base64-t my_atomic-t lf-t waiting_threads-t
+noinst_PROGRAMS = bitmap-t base64-t my_atomic-t lf-t waiting_threads-t \
+ safe_cleanup_cat_path-t
LDADD = $(top_builddir)/unittest/mytap/libmytap.a \
$(top_builddir)/mysys/libmysyslt.la \
=== added file 'unittest/mysys/safe_cleanup_cat_path-t.c'
--- a/unittest/mysys/safe_cleanup_cat_path-t.c 1970-01-01 00:00:00 +0000
+++ b/unittest/mysys/safe_cleanup_cat_path-t.c 2009-04-24 14:55:53 +0000
@@ -0,0 +1,206 @@
+/* Copyright (C) 2009 Sun Microsystems, Inc.
+
+ 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; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include <my_global.h>
+#include <my_sys.h>
+#include <tap.h>
+#include <string.h>
+
+#define DST_SIZE 80
+
+static char *concat_buf= NULL;
+static size_t concat_size= 0;
+
+
+/**
+ Concatenate two strings.
+
+ @param[in] str1 string 1
+ @param[in] str2 string 2
+
+ @return string in allocated storage
+*/
+
+static char*
+concat_str(const char *str1, const char *str2)
+{
+ size_t len1;
+ size_t len2;
+ size_t sum;
+
+ len1= strlen(str1);
+ len2= strlen(str2);
+ sum= len1 + len2 + 1; /* Include terminating '\0'. */
+
+ if (sum > concat_size)
+ {
+ concat_buf= my_realloc(concat_buf, sum, MYF(MY_FAE | MY_ALLOW_ZERO_PTR));
+ if (!concat_buf)
+ exit(3); /* purecov: inspected */
+ concat_size= sum;
+ }
+ memcpy(concat_buf, str1, len1);
+ memcpy(concat_buf + len1, str2, len2 + 1); /* Include terminating '\0'. */
+ return concat_buf;
+}
+
+
+/**
+ Do a test for a path name.
+
+ @param[in] path input path name
+ @param[out] expect expected output path name
+*/
+
+static void
+check_result(const char *path1, const char *path2,
+ const char *path3, const char *result, const char *expect)
+{
+ const char *exp_src;
+ char *exp_dst;
+ char *exp_end;
+ char exp[DST_SIZE];
+
+ /* Convert FN_LIBCHAR to slash for comparable results. */
+ for (exp_src= expect, exp_dst= exp, exp_end= exp + DST_SIZE - 1;
+ *exp_src && (exp_dst < exp_end);
+ exp_src++, exp_dst++)
+ {
+ if (*exp_src == '/')
+ *exp_dst= FN_LIBCHAR;
+ else
+ *exp_dst= *exp_src;
+ }
+ *exp_dst= '\0';
+
+ /* Check and print result. */
+ ok(!strcmp(result, exp),
+ "input: '%s' '%s' '%s' result: '%s' expected: '%s'",
+ path1 ? path1 : "[NullS]",
+ path2 ? path2 : "[NullS]",
+ path3 ? path3 : "[NullS]",
+ result, exp);
+}
+
+
+/**
+ Do a test for a path name.
+
+ @param[in] path1 input path name
+ @param[in] path2 input path name, optional NullS
+ @param[in] path3 input path name, optional NullS
+ @param[out] expect expected output path name
+*/
+
+static void
+do_multi_test(const char *path1, const char *path2,
+ const char *path3, const char *expect)
+{
+ size_t dst_len;
+ char dst[DST_SIZE];
+
+ /* Cleanup the path. That's the core test. */
+ dst_len= safe_cleanup_cat_path(dst, DST_SIZE, path1, path2, path3, NullS);
+
+ /* If path too long, dst_len == 0 but dst has partial result. Clear it. */
+ if (!dst_len)
+ dst[0]= '\0';
+
+ /* Check and print result. */
+ check_result(path1, path2, path3, dst, expect);
+}
+
+
+#define do_test(path, expect) do_multi_test(path, NullS, NullS, expect)
+
+
+int
+main(void)
+{
+ /* Set HOME for ~/ test. Trailing slash is for coverage test. */
+ putenv((char*) "HOME=/home/unittest/");
+
+ MY_INIT("safe_cleanup_cat_path-t");
+
+ plan(39);
+
+ do_test("a", "a");
+ do_test("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.bbb",
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.bbb");
+ do_test("ccc/ccc", "ccc/ccc");
+ do_test("ddd/./ddd", "ddd/ddd");
+ do_test("eee/../fff", "fff");
+ do_test("ggg/hhh/iii/../../j", "ggg/j");
+ do_test("/kkk", "/kkk");
+ do_test("~/lll", concat_str(home_dir, "lll"));
+ do_test("./mmm", "mmm");
+ do_test("nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
+ "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
+ "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
+ "");
+#ifdef HAVE_READLINK
+ {
+ int rc;
+ (void) my_delete("safe_cleanup_cat_path_symlink", MYF(0));
+ rc= my_symlink("symlink_target", "safe_cleanup_cat_path_symlink",
+ MYF(MY_WME));
+ do_test("safe_cleanup_cat_path_symlink/../../ooo/././oo/../../o",
+ "safe_cleanup_cat_path_symlink/../../o");
+ (void) my_delete("safe_cleanup_cat_path_symlink", MYF(0));
+ }
+#else
+ {
+ do_test("dummy_test_to_please_the_test_counter",
+ "dummy_test_to_please_the_test_counter");
+ }
+#endif
+ do_test("ppp///ppp", "ppp/ppp");
+ do_test("qqq/qqq///", "qqq/qqq");
+ do_test("../../rrr", "../../rrr");
+ do_test("", "");
+ do_test("///", "/");
+ do_test("sss/sss/..", "sss");
+ do_test("ttt/..", ".");
+#ifdef FN_NETWORK_DRIVES
+ do_test("//uuu///", "//uuu");
+ do_test("///uuu///", "//uuu");
+#else
+ do_test("//uuu///", "/uuu");
+ do_test("///uuu///", "/uuu");
+#endif
+ do_test("/../vvv", "/vvv");
+ do_test("/../../www", "/www");
+ do_test("/../../", "/");
+ do_test("/../..", "/");
+ do_test("././", ".");
+ do_test("./.", ".");
+ do_test("../../", "../..");
+ do_test("../..", "../..");
+ do_test("..", "..");
+ do_test("xxx/~/xxx", "xxx/~/xxx");
+
+ do_multi_test(NullS, NullS, NullS, "");
+ do_multi_test("", NullS, NullS, "");
+ do_multi_test("", "", NullS, "");
+ do_multi_test("", "/AAA", NullS, "/AAA");
+ do_multi_test("BBB/", "/BBB", NullS, "BBB/BBB");
+ do_multi_test("CCC", "CCC", NullS, "CCC/CCC");
+ do_multi_test("DDD", "", "DDD", "DDD/DDD");
+ do_multi_test("EEE/", "../EEE", NullS, "EEE");
+ do_multi_test("", "FFF", NullS, "FFF");
+
+ my_free(concat_buf, MYF(MY_ALLOW_ZERO_PTR));
+ return exit_status();
+}
Attachment: [text/bzr-bundle] bzr/ingo.struewing@sun.com-20090424145553-xmtiynisc12yf3wl.bundle