/* Copyright (C) 2004 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; 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 */ // Text .frm files management routines #include "mysql_priv.h" #include #include #include #include /* write string with escaping SYNOPSIS write_escaped_string() file - IO_CACHE for record val_s - string for writing RETURN FALSE - OK TRUE - error */ static my_bool write_escaped_string(IO_CACHE *file, LEX_STRING *val_s) { char *eos= val_s->str + val_s->length; char *ptr= val_s->str; for (; ptr < eos; ptr++) { /* Should be in sync with read_escaped_string() and parse_quoted_escaped_string() */ switch(*ptr) { case '\\': // escape character if (my_b_append(file, (const byte *)STRING_WITH_LEN("\\\\"))) return TRUE; break; case '\n': // parameter value delimiter if (my_b_append(file, (const byte *)STRING_WITH_LEN("\\n"))) return TRUE; break; case '\0': // problem for some string processing utilities if (my_b_append(file, (const byte *)STRING_WITH_LEN("\\0"))) return TRUE; break; case 26: // problem for windows utilities (Ctrl-Z) if (my_b_append(file, (const byte *)STRING_WITH_LEN("\\z"))) return TRUE; break; case '\'': // list of string delimiter if (my_b_append(file, (const byte *)STRING_WITH_LEN("\\\'"))) return TRUE; break; default: if (my_b_append(file, (const byte *)ptr, 1)) return TRUE; } } return FALSE; } /* write parameter value to IO_CACHE SYNOPSIS write_parameter() file pointer to IO_CACHE structure for writing base pointer to data structure parameter pointer to parameter descriptor old_version for returning back old version number value RETURN FALSE - OK TRUE - error */ static my_bool write_parameter(IO_CACHE *file, gptr base, File_option *parameter, ulonglong *old_version) { char num_buf[20]; // buffer for numeric operations // string for numeric operations String num(num_buf, sizeof(num_buf), &my_charset_bin); DBUG_ENTER("write_parameter"); switch (parameter->type) { case FILE_OPTIONS_STRING: { LEX_STRING *val_s= (LEX_STRING *)(base + parameter->offset); if (my_b_append(file, (const byte *)val_s->str, val_s->length)) DBUG_RETURN(TRUE); break; } case FILE_OPTIONS_ESTRING: { if (write_escaped_string(file, (LEX_STRING *)(base + parameter->offset))) DBUG_RETURN(TRUE); break; } case FILE_OPTIONS_ULONGLONG: { num.set(*((ulonglong *)(base + parameter->offset)), &my_charset_bin); if (my_b_append(file, (const byte *)num.ptr(), num.length())) DBUG_RETURN(TRUE); break; } case FILE_OPTIONS_REV: { ulonglong *val_i= (ulonglong *)(base + parameter->offset); *old_version= (*val_i)++; num.set(*val_i, &my_charset_bin); if (my_b_append(file, (const byte *)num.ptr(), num.length())) DBUG_RETURN(TRUE); break; } case FILE_OPTIONS_TIMESTAMP: { /* string have to be allocated already */ LEX_STRING *val_s= (LEX_STRING *)(base + parameter->offset); time_t tm= time(NULL); get_date(val_s->str, GETDATE_DATE_TIME|GETDATE_GMT|GETDATE_FIXEDLENGTH, tm); val_s->length= PARSE_FILE_TIMESTAMPLENGTH; if (my_b_append(file, (const byte *)val_s->str, PARSE_FILE_TIMESTAMPLENGTH)) DBUG_RETURN(TRUE); break; } case FILE_OPTIONS_STRLIST: { List_iterator_fast it(*((List*) (base + parameter->offset))); bool first= 1; LEX_STRING *str; while ((str= it++)) { // We need ' ' after string to detect list continuation if ((!first && my_b_append(file, (const byte *)STRING_WITH_LEN(" "))) || my_b_append(file, (const byte *)STRING_WITH_LEN("\'")) || write_escaped_string(file, str) || my_b_append(file, (const byte *)STRING_WITH_LEN("\'"))) { DBUG_RETURN(TRUE); } first= 0; } break; } case FILE_OPTIONS_ULLLIST: { List_iterator_fast it(*((List*) (base + parameter->offset))); bool first= 1; ulonglong *val; while ((val= it++)) { num.set(*val, &my_charset_bin); // We need ' ' after string to detect list continuation if ((!first && my_b_append(file, (const byte *)STRING_WITH_LEN(" "))) || my_b_append(file, (const byte *)num.ptr(), num.length())) { DBUG_RETURN(TRUE); } first= 0; } break; } default: DBUG_ASSERT(0); // never should happened } DBUG_RETURN(FALSE); } /* write new .frm SYNOPSIS sql_create_definition_file() dir directory where put .frm file .frm file name type .frm type string (VIEW, TABLE) base base address for parameter reading (structure like TABLE) parameters parameters description max_versions number of versions to save RETURN FALSE - OK TRUE - error */ my_bool sql_create_definition_file(const LEX_STRING *dir, const LEX_STRING *file_name, const LEX_STRING *type, gptr base, File_option *parameters, uint max_versions) { File handler; IO_CACHE file; char path[FN_REFLEN+1]; // +1 to put temporary file name for sure ulonglong old_version= ULONGLONG_MAX; int path_end; File_option *param; DBUG_ENTER("sql_create_definition_file"); DBUG_PRINT("enter", ("Dir: %s, file: %s, base 0x%lx", dir->str, file_name->str, (ulong) base)); fn_format(path, file_name->str, dir->str, 0, MY_UNPACK_FILENAME); path_end= strlen(path); // temporary file name path[path_end]='~'; path[path_end+1]= '\0'; if ((handler= my_create(path, CREATE_MODE, O_RDWR | O_TRUNC, MYF(MY_WME))) <= 0) { DBUG_RETURN(TRUE); } if (init_io_cache(&file, handler, 0, SEQ_READ_APPEND, 0L, 0, MYF(MY_WME))) goto err_w_file; // write header (file signature) if (my_b_append(&file, (const byte *)STRING_WITH_LEN("TYPE=")) || my_b_append(&file, (const byte *)type->str, type->length) || my_b_append(&file, (const byte *)STRING_WITH_LEN("\n"))) goto err_w_file; // write parameters to temporary file for (param= parameters; param->name.str; param++) { if (my_b_append(&file, (const byte *)param->name.str, param->name.length) || my_b_append(&file, (const byte *)STRING_WITH_LEN("=")) || write_parameter(&file, base, param, &old_version) || my_b_append(&file, (const byte *)STRING_WITH_LEN("\n"))) goto err_w_cache; } if (end_io_cache(&file)) goto err_w_file; if (my_close(handler, MYF(MY_WME))) { DBUG_RETURN(TRUE); } // archive copies management path[path_end]='\0'; if (!access(path, F_OK)) { if (old_version != ULONGLONG_MAX && max_versions != 0) { // save backup char path_arc[FN_REFLEN]; // backup old version char path_to[FN_REFLEN]; // check archive directory existence fn_format(path_arc, "arc", dir->str, "", MY_UNPACK_FILENAME); if (access(path_arc, F_OK)) { if (my_mkdir(path_arc, 0777, MYF(MY_WME))) { DBUG_RETURN(TRUE); } } my_snprintf(path_to, FN_REFLEN, "%s/%s-%04lu", path_arc, file_name->str, (ulong) old_version); if (my_rename(path, path_to, MYF(MY_WME))) { DBUG_RETURN(TRUE); } // remove very old version if (old_version > max_versions) { my_snprintf(path_to, FN_REFLEN, "%s/%s-%04lu", path_arc, file_name->str, (ulong)(old_version - max_versions)); if (!access(path_arc, F_OK) && my_delete(path_to, MYF(MY_WME))) { DBUG_RETURN(TRUE); } } } else { if (my_delete(path, MYF(MY_WME))) // no backups { DBUG_RETURN(TRUE); } } } { // rename temporary file char path_to[FN_REFLEN]; memcpy(path_to, path, path_end+1); path[path_end]='~'; if (my_rename(path, path_to, MYF(MY_WME))) { DBUG_RETURN(TRUE); } } DBUG_RETURN(FALSE); err_w_cache: end_io_cache(&file); err_w_file: my_close(handler, MYF(MY_WME)); DBUG_RETURN(TRUE); } /* Renames a frm file (including backups) in same schema SYNOPSIS rename_in_schema_file schema name of given schema old_name original file name new_name new file name revision revision number num_view_backups number of backups RETURN 0 - OK 1 - Error (only if renaming of frm failed) */ my_bool rename_in_schema_file(const char *schema, const char *old_name, const char *new_name, ulonglong revision, uint num_view_backups) { char old_path[FN_REFLEN], new_path[FN_REFLEN], arc_path[FN_REFLEN]; strxnmov(old_path, FN_REFLEN, mysql_data_home, "/", schema, "/", old_name, reg_ext, NullS); (void) unpack_filename(old_path, old_path); strxnmov(new_path, FN_REFLEN, mysql_data_home, "/", schema, "/", new_name, reg_ext, NullS); (void) unpack_filename(new_path, new_path); if (my_rename(old_path, new_path, MYF(MY_WME))) return 1; /* check if arc_dir exists */ strxnmov(arc_path, FN_REFLEN, mysql_data_home, "/", schema, "/arc", NullS); (void) unpack_filename(arc_path, arc_path); if (revision > 0 && !access(arc_path, F_OK)) { ulonglong limit= ((revision > num_view_backups) ? revision - num_view_backups : 0); for (; revision > limit ; revision--) { my_snprintf(old_path, FN_REFLEN, "%s/%s%s-%04lu", arc_path, old_name, reg_ext, (ulong)revision); (void) unpack_filename(old_path, old_path); my_snprintf(new_path, FN_REFLEN, "%s/%s%s-%04lu", arc_path, new_name, reg_ext, (ulong)revision); (void) unpack_filename(new_path, new_path); my_rename(old_path, new_path, MYF(0)); } } return 0; } /* Prepare frm to parse (read to memory) SYNOPSIS sql_parse_prepare() file_name - path & filename to .frm file mem_root - MEM_ROOT for buffer allocation bad_format_errors - send errors on bad content RETURN 0 - error parser object NOTE returned pointer + 1 will be type of .frm */ File_parser * sql_parse_prepare(const LEX_STRING *file_name, MEM_ROOT *mem_root, bool bad_format_errors) { MY_STAT stat_info; uint len; char *end, *sign; File_parser *parser; File file; DBUG_ENTER("sql__parse_prepare"); if (!my_stat(file_name->str, &stat_info, MYF(MY_WME))) { DBUG_RETURN(0); } if (stat_info.st_size > INT_MAX-1) { my_error(ER_FPARSER_TOO_BIG_FILE, MYF(0), file_name->str); DBUG_RETURN(0); } if (!(parser= new(mem_root) File_parser)) { DBUG_RETURN(0); } if (!(parser->buff= alloc_root(mem_root, stat_info.st_size+1))) { DBUG_RETURN(0); } if ((file= my_open(file_name->str, O_RDONLY | O_SHARE, MYF(MY_WME))) < 0) { DBUG_RETURN(0); } if ((len= my_read(file, (byte *)parser->buff, stat_info.st_size, MYF(MY_WME))) == MY_FILE_ERROR) { my_close(file, MYF(MY_WME)); DBUG_RETURN(0); } if (my_close(file, MYF(MY_WME))) { DBUG_RETURN(0); } end= parser->end= parser->buff + len; *end= '\0'; // barrier for more simple parsing // 7 = 5 (TYPE=) + 1 (letter at least of type name) + 1 ('\n') if (len < 7 || parser->buff[0] != 'T' || parser->buff[1] != 'Y' || parser->buff[2] != 'P' || parser->buff[3] != 'E' || parser->buff[4] != '=') goto frm_error; // skip signature; parser->file_type.str= sign= parser->buff + 5; while (*sign >= 'A' && *sign <= 'Z' && sign < end) sign++; if (*sign != '\n') goto frm_error; parser->file_type.length= sign - parser->file_type.str; // EOS for file signature just for safety *sign= '\0'; parser->start= sign + 1; parser->content_ok= 1; DBUG_RETURN(parser); frm_error: if (bad_format_errors) { my_error(ER_FPARSER_BAD_HEADER, MYF(0), file_name->str); DBUG_RETURN(0); } else DBUG_RETURN(parser); // upper level have to check parser->ok() } /* parse LEX_STRING SYNOPSIS parse_string() ptr - pointer on string beginning end - pointer on symbol after parsed string end (still owned by buffer and can be accessed mem_root - MEM_ROOT for parameter allocation str - pointer on string, where results should be stored RETURN 0 - error # - pointer on symbol after string */ static char * parse_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str) { // get string length char *eol= strchr(ptr, '\n'); if (eol >= end) return 0; str->length= eol - ptr; if (!(str->str= alloc_root(mem_root, str->length+1))) return 0; memcpy(str->str, ptr, str->length); str->str[str->length]= '\0'; // just for safety return eol+1; } /* read escaped string from ptr to eol in already allocated str SYNOPSIS read_escaped_string() ptr - pointer on string beginning eol - pointer on character after end of string str - target string RETURN FALSE - OK TRUE - error */ my_bool read_escaped_string(char *ptr, char *eol, LEX_STRING *str) { char *write_pos= str->str; for (; ptr < eol; ptr++, write_pos++) { char c= *ptr; if (c == '\\') { ptr++; if (ptr >= eol) return TRUE; /* Should be in sync with write_escaped_string() and parse_quoted_escaped_string() */ switch(*ptr) { case '\\': *write_pos= '\\'; break; case 'n': *write_pos= '\n'; break; case '0': *write_pos= '\0'; break; case 'z': *write_pos= 26; break; case '\'': *write_pos= '\''; break; default: return TRUE; } } else *write_pos= c; } str->str[str->length= write_pos-str->str]= '\0'; // just for safety return FALSE; } /* parse \n delimited escaped string SYNOPSIS parse_escaped_string() ptr - pointer on string beginning end - pointer on symbol after parsed string end (still owned by buffer and can be accessed mem_root - MEM_ROOT for parameter allocation str - pointer on string, where results should be stored RETURN 0 - error # - pointer on symbol after string */ char * parse_escaped_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str) { char *eol= strchr(ptr, '\n'); if (eol == 0 || eol >= end || !(str->str= alloc_root(mem_root, (eol - ptr) + 1)) || read_escaped_string(ptr, eol, str)) return 0; return eol+1; } /* parse '' delimited escaped string SYNOPSIS parse_quoted_escaped_string() ptr - pointer on string beginning end - pointer on symbol after parsed string end (still owned by buffer and can be accessed mem_root - MEM_ROOT for parameter allocation str - pointer on string, where results should be stored RETURN 0 - error # - pointer on symbol after string */ static char * parse_quoted_escaped_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str) { char *eol; uint result_len= 0; bool escaped= 0; // starting ' if (*(ptr++) != '\'') return 0; // find ending ' for (eol= ptr; (*eol != '\'' || escaped) && eol < end; eol++) { if (!(escaped= (*eol == '\\' && !escaped))) result_len++; } // process string if (eol >= end || !(str->str= alloc_root(mem_root, result_len + 1)) || read_escaped_string(ptr, eol, str)) return 0; return eol+1; } /* Parser for FILE_OPTIONS_ULLLIST type value. SYNOPSIS get_file_options_ulllist() ptr [in/out] pointer to parameter end [in] end of the configuration line [in] pointer to the line begining base [in] base address for parameter writing (structure like TABLE) parameter [in] description mem_root [in] MEM_ROOT for parameters allocation */ bool get_file_options_ulllist(char *&ptr, char *end, char *line, gptr base, File_option *parameter, MEM_ROOT *mem_root) { List *nlist= (List*)(base + parameter->offset); ulonglong *num; nlist->empty(); // list parsing while (ptr < end) { int not_used; char *num_end= end; if (!(num= (ulonglong*)alloc_root(mem_root, sizeof(ulonglong))) || nlist->push_back(num, mem_root)) goto nlist_err; *num= my_strtoll10(ptr, &num_end, ¬_used); ptr= num_end; switch (*ptr) { case '\n': goto end_of_nlist; case ' ': // we cant go over buffer bounds, because we have \0 at the end ptr++; break; default: goto nlist_err_w_message; } } end_of_nlist: if (*(ptr++) != '\n') goto nlist_err; return FALSE; nlist_err_w_message: my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), parameter->name.str, line); nlist_err: return TRUE; } /* parse parameters SYNOPSIS File_parser::parse() base base address for parameter writing (structure like TABLE) mem_root MEM_ROOT for parameters allocation parameters parameters description required number of parameters in the above list. If the file contains more parameters than "required", they will be ignored. If the file contains less parameters then "required", non-existing parameters will remain their values. hook hook called for unknown keys hook_data some data specific for the hook RETURN FALSE - OK TRUE - error */ my_bool File_parser::parse(gptr base, MEM_ROOT *mem_root, struct File_option *parameters, uint required, Unknown_key_hook *hook) { uint first_param= 0, found= 0; char *ptr= start; char *eol; LEX_STRING *str; List *list; DBUG_ENTER("File_parser::parse"); while (ptr < end && found < required) { char *line= ptr; if (*ptr == '#') { // it is comment if (!(ptr= strchr(ptr, '\n'))) { my_error(ER_FPARSER_EOF_IN_COMMENT, MYF(0), line); DBUG_RETURN(TRUE); } ptr++; } else { File_option *parameter= parameters+first_param, *parameters_end= parameters+required; int len= 0; for (; parameter < parameters_end; parameter++) { len= parameter->name.length; // check length if (len < (end-ptr) && ptr[len] != '=') continue; // check keyword if (memcmp(parameter->name.str, ptr, len) == 0) break; } if (parameter < parameters_end) { found++; /* if we found first parameter, start search from next parameter next time. (this small optimisation should work, because they should be written in same order) */ if (parameter == parameters+first_param) first_param++; // get value ptr+= (len+1); switch (parameter->type) { case FILE_OPTIONS_STRING: { if (!(ptr= parse_string(ptr, end, mem_root, (LEX_STRING *)(base + parameter->offset)))) { my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), parameter->name.str, line); DBUG_RETURN(TRUE); } break; } case FILE_OPTIONS_ESTRING: { if (!(ptr= parse_escaped_string(ptr, end, mem_root, (LEX_STRING *) (base + parameter->offset)))) { my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), parameter->name.str, line); DBUG_RETURN(TRUE); } break; } case FILE_OPTIONS_ULONGLONG: case FILE_OPTIONS_REV: if (!(eol= strchr(ptr, '\n'))) { my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), parameter->name.str, line); DBUG_RETURN(TRUE); } { int not_used; *((ulonglong*)(base + parameter->offset))= my_strtoll10(ptr, 0, ¬_used); } ptr= eol+1; break; case FILE_OPTIONS_TIMESTAMP: { /* string have to be allocated already */ LEX_STRING *val= (LEX_STRING *)(base + parameter->offset); /* yyyy-mm-dd HH:MM:SS = 19(PARSE_FILE_TIMESTAMPLENGTH) characters */ if (ptr[PARSE_FILE_TIMESTAMPLENGTH] != '\n') { my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), parameter->name.str, line); DBUG_RETURN(TRUE); } memcpy(val->str, ptr, PARSE_FILE_TIMESTAMPLENGTH); val->str[val->length= PARSE_FILE_TIMESTAMPLENGTH]= '\0'; ptr+= (PARSE_FILE_TIMESTAMPLENGTH+1); break; } case FILE_OPTIONS_STRLIST: { list= (List*)(base + parameter->offset); list->empty(); // list parsing while (ptr < end) { if (!(str= (LEX_STRING*)alloc_root(mem_root, sizeof(LEX_STRING))) || list->push_back(str, mem_root)) goto list_err; if (!(ptr= parse_quoted_escaped_string(ptr, end, mem_root, str))) goto list_err_w_message; switch (*ptr) { case '\n': goto end_of_list; case ' ': // we cant go over buffer bounds, because we have \0 at the end ptr++; break; default: goto list_err_w_message; } } end_of_list: if (*(ptr++) != '\n') goto list_err; break; list_err_w_message: my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0), parameter->name.str, line); list_err: DBUG_RETURN(TRUE); } case FILE_OPTIONS_ULLLIST: if (get_file_options_ulllist(ptr, end, line, base, parameter, mem_root)) DBUG_RETURN(TRUE); break; default: DBUG_ASSERT(0); // never should happened } } else { ptr= line; if (hook->process_unknown_string(ptr, base, mem_root, end)) { DBUG_RETURN(TRUE); } // skip unknown parameter if (!(ptr= strchr(ptr, '\n'))) { my_error(ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER, MYF(0), line); DBUG_RETURN(TRUE); } ptr++; } } } /* NOTE: if we read less than "required" parameters, it is still Ok. Probably, we've just read the file of the previous version, which contains less parameters. */ DBUG_RETURN(FALSE); } /* Dummy unknown key hook SYNOPSIS File_parser_dummy_hook::process_unknown_string() unknown_key [in/out] reference on the line with unknown parameter and the parsing point base [in] base address for parameter writing (structure like TABLE) mem_root [in] MEM_ROOT for parameters allocation end [in] the end of the configuration NOTE This hook used to catch no longer supported keys and process them for backward compatibility, but it will not slow down processing of modern format files. This hook does nothing except debug output. RETURN FALSE OK TRUE Error */ bool File_parser_dummy_hook::process_unknown_string(char *&unknown_key, gptr base, MEM_ROOT *mem_root, char *end) { DBUG_ENTER("file_parser_dummy_hook::process_unknown_string"); DBUG_PRINT("info", ("Unknown key: '%60s'", unknown_key)); DBUG_RETURN(FALSE); }