From: Date: November 19 2008 3:01pm Subject: bzr commit into mysql-5.1 branch (ingo.struewing:2712) Bug#39277 List-Archive: http://lists.mysql.com/commits/59231 X-Bug: 39277 Message-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit #At file:///home2/mydev/bzrroot/mysql-5.1-bug39277/ 2712 Ingo Struewing 2008-11-19 Bug#39277 - symlink.test fails on Debian When the data directory contained a symbolic link to another file system, and the DATA or INDEX DIRECTORY clause of a CREATE TABLE statement referred to a subdirectory of the data directory by using another path, this was accepted. The problem was the use of a table file path name, which included the table name without an extension, for the comparison against the data directory path name. This was almost always a non-existent file. The internal algorithm failed to resolve symbolic links for non-existent files. So we compared unrelated path names. Fixed by doing a second comparison with a path name that has stripped off the last path element. In the case above it makes the directory for the table. If this is also a non-existent path, the creation of the table will fail anyway. modified: sql/sql_parse.cc per-file messages: sql/sql_parse.cc Bug#39277 - symlink.test fails on Debian Renamed test_if_data_home_dir() to static test_for_data_home_dir() and added a new test_if_data_home_dir(), which calls the renamed one twice. The second time after stripping off the last path element. === modified file 'sql/sql_parse.cc' --- a/sql/sql_parse.cc 2008-10-24 12:50:59 +0000 +++ b/sql/sql_parse.cc 2008-11-19 14:01:36 +0000 @@ -7519,53 +7519,134 @@ bool check_string_char_length(LEX_STRING } -/* +/** Check if path does not contain mysql data home directory - SYNOPSIS - test_if_data_home_dir() - dir directory - conv_home_dir converted data home directory - home_dir_len converted data home directory length - - RETURN VALUES - 0 ok - 1 error -*/ -C_MODE_START -int test_if_data_home_dir(const char *dir) -{ - char path[FN_REFLEN]; - int dir_len; - DBUG_ENTER("test_if_data_home_dir"); + @param[in] path file or directory path name - if (!dir) - DBUG_RETURN(0); + @return status + @retval 0 ok, does no contain data home dir + @retval 1 error, contains data home dir + + @note This function may not return the correct answer if the path in + question refers to no existing file or directory. This can happen if + the path contains symbolic links. The low-level functions that try to + resolve symlinks return the original path if the file does not exist, + even though one or more symlinks may be involved. If the original path + is not within the data home dir, but contains symlinks that point into + the data home dir, this function will falsely claim the path be not in + the data home dir. + + @note More technical details to the above: On some systems, we use + realpath(3) for the symlink resolution. This returns ENOENT if the + resolved path does not refer to an existing file. my_realpath() does + then copy the original path verbatim, without symlink resolution. - (void) fn_format(path, dir, "", "", - (MY_RETURN_REAL_PATH|MY_RESOLVE_SYMLINKS)); - dir_len= strlen(path); - if (mysql_unpacked_real_data_home_len<= dir_len) + @note For a resolution of non-existent file problem, see the function + definition of test_if_data_home_dir() below. +*/ + +static int test_for_data_home_dir(const char *path) +{ + char real_path[FN_REFLEN]; + int real_len; + DBUG_ENTER("test_for_data_home_dir"); + DBUG_ASSERT(path); + + /* Convert path to absolute, clean path with symlinks resolved. */ + (void) fn_format(real_path, path, "", "", + (MY_RETURN_REAL_PATH | MY_RESOLVE_SYMLINKS)); + real_len= strlen(real_path); + + /* + If real_path is shorter than data home dir, it cannot be within it. + Otherwise we have to look deeper. + */ + if (real_len >= mysql_unpacked_real_data_home_len) { - if (dir_len > mysql_unpacked_real_data_home_len && - path[mysql_unpacked_real_data_home_len] != FN_LIBCHAR) + /* + If the path name is longer that data home dir, and the character + at the position where data home dir ends, is not a path delimiter + (slash '/' in UNIX), it cannot be within data home dir. + */ + if (real_len > mysql_unpacked_real_data_home_len && + real_path[mysql_unpacked_real_data_home_len] != FN_LIBCHAR) DBUG_RETURN(0); + /* + If the real path matches data home dir until the end of + data home dir, it is a path within data home dir. + Either real_len == mysql_unpacked_real_data_home_len, + or a path delimiter follows in real_path. See the check above. + */ if (lower_case_file_system) { - if (!my_strnncoll(default_charset_info, (const uchar*) path, + /* purecov: inspected */ + if (!my_strnncoll(default_charset_info, (const uchar*) real_path, mysql_unpacked_real_data_home_len, (const uchar*) mysql_unpacked_real_data_home, mysql_unpacked_real_data_home_len)) DBUG_RETURN(1); + /* purecov: end */ } - else if (!memcmp(path, mysql_unpacked_real_data_home, + else if (!memcmp(real_path, mysql_unpacked_real_data_home, mysql_unpacked_real_data_home_len)) DBUG_RETURN(1); } DBUG_RETURN(0); } + +/** + Check if path does not contain mysql data home directory + + @param[in] path file or directory path name + + @return status + @retval 0 ok, does no contain data home dir + @retval 1 error, contains data home dir + + We need to do a trick here. test_for_data_home_dir() can fail if + 'path' contains symlinks and refers to a non-existent file or + directory. To defeat this flaw, we try two paths. First the original + one. If it passes the check, we strip off the last path element and + try its directory. The latter can fail too. But then it means that the + directory doesn't exist either. In this case the calling function + won't succeed with its operation anyway. + + @note For more information on the symlinks + non-existent file + problem, see the function definition of test_for_data_home_dir() above. +*/ +C_MODE_START + +int test_if_data_home_dir(const char *path) +{ + size_t dirlen; + char dirpath[FN_REFLEN]; + DBUG_ENTER("test_if_data_home_dir"); + + /* Missing path cannot match data home dir. */ + if (!path) + DBUG_RETURN(0); + + /* + Try the path verbatim. If it turns out to be within data home dir, + we can accept this as a true result. mysql_unpacked_real_data_home + is a resolved path. Either 'path' is within it literally, or + symlinks could be resolved and then the compare matched. + */ + if(test_for_data_home_dir(path)) + DBUG_RETURN(1); + + /* + 'path' seems to point outside data home dir. This could be due to + symlinks + non-existent path. Now retry the check with the last + path element stripped off. + */ + dirname_part(dirpath, path, &dirlen); + DBUG_RETURN(test_for_data_home_dir(dirpath)); +} + C_MODE_END