|
27 | 27 | #include <fcntl.h>
|
28 | 28 | #include <stdint.h>
|
29 | 29 | #include <sys/types.h>
|
| 30 | +#include <chrono> |
| 31 | +#include <iomanip> |
| 32 | +#include <sstream> |
| 33 | +#include <string> |
30 | 34 |
|
31 | 35 | #include "sql/dd/upgrade/server.h"
|
32 | 36 | #ifdef HAVE_UNISTD_H
|
|
35 | 39 | #include <vector>
|
36 | 40 |
|
37 | 41 | #include "my_dbug.h"
|
| 42 | +#include "my_rapidjson_size_t.h" |
38 | 43 | #include "mysql/components/services/log_builtins.h"
|
| 44 | +#include "mysql/psi/mysql_file.h" |
39 | 45 | #include "mysql/strings/m_ctype.h"
|
40 | 46 | #include "nulls.h"
|
| 47 | +#include "rapidjson/document.h" |
| 48 | +#include "rapidjson/prettywriter.h" |
| 49 | +#include "rapidjson/stringbuffer.h" |
41 | 50 | #include "scripts/mysql_fix_privilege_tables_sql.h"
|
42 | 51 | #include "scripts/sql_commands_system_tables_data_fix.h"
|
43 | 52 | #include "scripts/sql_firewall_sp_firewall_group_delist.h"
|
@@ -811,8 +820,145 @@ static bool check_views(THD *thd, std::unique_ptr<Schema> &schema,
|
811 | 820 | return thd->dd_client()->foreach<dd::View>(view_key.get(), process_view);
|
812 | 821 | }
|
813 | 822 |
|
| 823 | +/* Make sure the old unsupported "mysql_upgrade_info" file is removed. */ |
| 824 | +static void remove_legacy_upgrade_info_file() { |
| 825 | + char upgrade_file[FN_REFLEN] = {0}; |
| 826 | + fn_format(upgrade_file, "mysql_upgrade_info", mysql_real_data_home_ptr, "", |
| 827 | + MYF(0)); |
| 828 | + if (!my_access(upgrade_file, F_OK)) |
| 829 | + std::ignore = mysql_file_delete(key_file_misc, upgrade_file, MYF(0)); |
| 830 | +} |
814 | 831 | } // namespace
|
815 | 832 |
|
| 833 | +/* |
| 834 | + Maintain a file named "mysql_upgrade_history" in the data directory. |
| 835 | +
|
| 836 | + The file will contain one entry for each upgrade. The format is structured |
| 837 | + text on JSON format. |
| 838 | +
|
| 839 | + Errors will be written as warnings to the error log; if we e.g. fail to |
| 840 | + open the upgrade history file, we will not abort the server since this file |
| 841 | + is not considered a critical feature of the server. |
| 842 | +
|
| 843 | + @param initialize If this is the initialization of the data directory. |
| 844 | +*/ |
| 845 | +void update_upgrade_history_file(bool initialize) { |
| 846 | + /* Name of the "mysql_upgrade_history" file. */ |
| 847 | + char upgrade_file[FN_REFLEN] = {0}; |
| 848 | + fn_format(upgrade_file, "mysql_upgrade_history", mysql_real_data_home_ptr, "", |
| 849 | + MYF(0)); |
| 850 | + |
| 851 | + /* JSON keys. */ |
| 852 | + constexpr char k_file_format[] = "file_format"; |
| 853 | + constexpr char k_upgrade_history[] = "upgrade_history"; |
| 854 | + constexpr char k_date[] = "date"; |
| 855 | + constexpr char k_version[] = "version"; |
| 856 | + constexpr char k_maturity[] = "maturity"; |
| 857 | + constexpr char k_initialize[] = "initialize"; |
| 858 | + constexpr char v_file_format[] = "1"; |
| 859 | + |
| 860 | + /* If > max entries, we keep the first and the (max - 1) last ones. */ |
| 861 | + constexpr int MAX_HISTORY_SIZE = 1000; |
| 862 | + static_assert(MAX_HISTORY_SIZE >= 2, |
| 863 | + "The upgrade history should contain at least the first " |
| 864 | + "and last entry."); |
| 865 | + using namespace rapidjson; |
| 866 | + Document doc; |
| 867 | + |
| 868 | + /* Open file if it exists, auto close on return. */ |
| 869 | + auto deleter = [&](FILE *ptr) { |
| 870 | + if (ptr != nullptr) my_fclose(ptr, MYF(0)); |
| 871 | + }; |
| 872 | + std::unique_ptr<FILE, decltype(deleter)> fp(nullptr, deleter); |
| 873 | + |
| 874 | + MY_STAT sa; |
| 875 | + char errbuf[MYSYS_STRERROR_SIZE]; |
| 876 | + bool file_exists = (my_stat(upgrade_file, &sa, MYF(0)) != nullptr); |
| 877 | + bool append = file_exists; // Append to current doc if possible. |
| 878 | + |
| 879 | + /* If the file exists, read the doc and see if it is valid. */ |
| 880 | + if (file_exists) { |
| 881 | + fp.reset(my_fopen(upgrade_file, O_RDONLY, MYF(0))); |
| 882 | + if (fp == nullptr) { |
| 883 | + LogErr(WARNING_LEVEL, ER_SERVER_CANT_OPEN_FILE, upgrade_file, my_errno(), |
| 884 | + my_strerror(errbuf, sizeof(errbuf), my_errno())); |
| 885 | + return; |
| 886 | + } |
| 887 | + |
| 888 | + /* Read contents into buffer. */ |
| 889 | + char buff[512] = {0}; |
| 890 | + std::string parsed_value; |
| 891 | + do { |
| 892 | + parsed_value.append(buff); |
| 893 | + buff[0] = '\0'; |
| 894 | + } while (fgets(buff, sizeof(buff) - 1, fp.get())); |
| 895 | + |
| 896 | + /* Parse JSON, check expected format. */ |
| 897 | + ParseResult ok = doc.Parse(parsed_value.c_str()); |
| 898 | + if (!(ok && doc.IsObject() && doc[k_file_format].IsString() && |
| 899 | + doc[k_upgrade_history].IsArray())) { |
| 900 | + LogErr(WARNING_LEVEL, ER_INVALID_FILE_FORMAT, upgrade_file); |
| 901 | + append = false; // Cannot append, must overwrite with an empty doc. |
| 902 | + } |
| 903 | + } |
| 904 | + |
| 905 | + /* If the file existed with valid contents, append, otherwise, overwrite. */ |
| 906 | + if (append) { |
| 907 | + /* If current version is same as last entry, return. */ |
| 908 | + Value &hist = doc[k_upgrade_history].GetArray(); |
| 909 | + int count = hist.Size(); |
| 910 | + if (count > 0 && |
| 911 | + !strcmp(hist[count - 1][k_version].GetString(), server_version)) |
| 912 | + return; |
| 913 | + |
| 914 | + /* If the doc contains too many entries, remove from the second and on. */ |
| 915 | + int remove_count = (count - MAX_HISTORY_SIZE) + 1; |
| 916 | + if (remove_count > 0) { |
| 917 | + hist.Erase(hist.Begin() + 1, hist.Begin() + remove_count + 1); |
| 918 | + } |
| 919 | + } else { |
| 920 | + /* Otherwise, if no file existed, initialize an empty JSON document. */ |
| 921 | + doc.SetObject(); |
| 922 | + doc.AddMember(k_file_format, v_file_format, doc.GetAllocator()); |
| 923 | + Value history(kArrayType); |
| 924 | + doc.AddMember(k_upgrade_history, history, doc.GetAllocator()); |
| 925 | + } |
| 926 | + |
| 927 | + /* Append timestamp, MYSQL_SERVER_VERSION and LTS info to version array. */ |
| 928 | + std::stringstream str; |
| 929 | + std::time_t sec = my_micro_time() / 1000000; |
| 930 | + str << std::put_time(std::gmtime(&sec), "%F %T"); |
| 931 | + Value date(str.str().c_str(), str.str().size(), doc.GetAllocator()); |
| 932 | + Value version(server_version, strlen(server_version), doc.GetAllocator()); |
| 933 | + Value maturity(MYSQL_VERSION_MATURITY); |
| 934 | + |
| 935 | + Value new_version; |
| 936 | + new_version.SetObject(); |
| 937 | + new_version.AddMember(k_date, date, doc.GetAllocator()); |
| 938 | + new_version.AddMember(k_version, version, doc.GetAllocator()); |
| 939 | + new_version.AddMember(k_maturity, maturity, doc.GetAllocator()); |
| 940 | + if (initialize) { |
| 941 | + Value init(true); |
| 942 | + new_version.AddMember(k_initialize, init, doc.GetAllocator()); |
| 943 | + } |
| 944 | + doc[k_upgrade_history].GetArray().PushBack(new_version, doc.GetAllocator()); |
| 945 | + |
| 946 | + /* Reopen the file, which is auto closed on function return. */ |
| 947 | + fp.reset(my_fopen(upgrade_file, O_CREAT | O_TRUNC | O_WRONLY, MYF(0))); |
| 948 | + if (fp == nullptr) { |
| 949 | + LogErr(WARNING_LEVEL, ER_SERVER_CANT_OPEN_FILE, upgrade_file, my_errno(), |
| 950 | + my_strerror(errbuf, sizeof(errbuf), my_errno())); |
| 951 | + return; |
| 952 | + } |
| 953 | + |
| 954 | + /* Write JSON document to a buffer and further to file with a newline. */ |
| 955 | + rapidjson::StringBuffer buffer; |
| 956 | + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
| 957 | + doc.Accept(writer); |
| 958 | + fputs(buffer.GetString(), fp.get()); |
| 959 | + fputs("\n", fp.get()); |
| 960 | +} |
| 961 | + |
816 | 962 | /*
|
817 | 963 | This function runs checks on the database before running the upgrade to make
|
818 | 964 | sure that the database is ready to be upgraded to a newer version. New checks
|
@@ -1040,6 +1186,8 @@ bool upgrade_system_schemas(THD *thd) {
|
1040 | 1186 | dd::tables::DD_properties::instance().set(
|
1041 | 1187 | thd, "MYSQLD_VERSION_UPGRADED", MYSQL_VERSION_ID);
|
1042 | 1188 | }
|
| 1189 | + |
| 1190 | + remove_legacy_upgrade_info_file(); |
1043 | 1191 | bootstrap_error_handler.set_log_error(true);
|
1044 | 1192 |
|
1045 | 1193 | if (dd::bootstrap::DD_bootstrap_ctx::instance().is_server_patch_downgrade()) {
|
|
0 commit comments