1 /* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 #include "sql/dd/sdi_file.h"
24 
25 #include "my_config.h"
26 
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <stddef.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <memory>
33 #include <ostream>
34 #include <string>
35 
36 #include "mysql/components/services/psi_file_bits.h"
37 #include "mysql/udf_registration_types.h"
38 
39 #ifdef HAVE_UNISTD_H
40 #include <unistd.h>
41 #endif
42 
43 #include "lex_string.h"
44 #include "m_ctype.h"
45 #include "my_dbug.h"
46 #include "my_dir.h"
47 #include "my_inttypes.h"
48 #include "my_io.h"
49 #include "my_sys.h"
50 #include "my_thread_local.h"
51 #include "mysql/psi/mysql_file.h"  // mysql_file_create
52 #include "mysqld_error.h"
53 #include "sql/dd/impl/sdi.h"        // dd::Sdi_type
54 #include "sql/dd/impl/sdi_utils.h"  // dd::sdi_util::checked_return
55 #include "sql/dd/types/schema.h"    // dd::Schema
56 #include "sql/dd/types/table.h"     // dd::Table
57 #include "sql/key.h"
58 #include "sql/mysqld.h"  // is_secure_file_path
59 #include "sql/sql_class.h"
60 #include "sql/sql_const.h"  // CREATE_MODE
61 #include "sql/sql_table.h"  // build_table_filename
62 
63 /**
64   @file
65   @ingroup sdi
66 
67   Storage and retrieval of SDIs to/from files. Default for SEs which do
68   not have the ability to store SDIs in tablespaces. File storage is
69   not transactional.
70 */
71 
72 using namespace dd::sdi_utils;
73 
74 extern PSI_file_key key_file_sdi;
75 namespace {
76 
write_sdi_file(const dd::String_type & fname,const dd::Sdi_type & sdi)77 bool write_sdi_file(const dd::String_type &fname, const dd::Sdi_type &sdi) {
78   File sdif = mysql_file_create(key_file_sdi, fname.c_str(), CREATE_MODE,
79                                 O_WRONLY | O_TRUNC, MYF(MY_FAE));
80   if (sdif < 0) {
81     char errbuf[MYSYS_STRERROR_SIZE];
82     my_error(ER_CANT_CREATE_FILE, MYF(0), fname.c_str(), my_errno(),
83              my_strerror(errbuf, sizeof(errbuf), my_errno()));
84     return checked_return(true);
85   }
86 
87   size_t bw =
88       mysql_file_write(sdif, reinterpret_cast<const uchar *>(sdi.c_str()),
89                        sdi.length(), MYF(MY_FNABP));
90 
91   if (bw == MY_FILE_ERROR) {
92 #ifndef DBUG_OFF
93     bool close_error =
94 #endif /* !DBUG_OFF */
95         mysql_file_close(sdif, MYF(0));
96     DBUG_ASSERT(close_error == false);
97     return checked_return(true);
98   }
99   DBUG_ASSERT(bw == 0);
100   return checked_return(mysql_file_close(sdif, MYF(MY_FAE)));
101 }
102 
sdi_file_exists(const dd::String_type & fname,bool * res)103 bool sdi_file_exists(const dd::String_type &fname, bool *res) {
104 #ifndef _WIN32
105 
106   if (my_access(fname.c_str(), F_OK) == 0) {
107     *res = true;
108     return false;
109   }
110 
111 #else /* _WIN32 */
112   // my_access cannot be used to test for the absence of a file on Windows
113   WIN32_FILE_ATTRIBUTE_DATA fileinfo;
114   BOOL result =
115       GetFileAttributesEx(fname.c_str(), GetFileExInfoStandard, &fileinfo);
116   if (result) {
117     *res = true;
118     return false;
119   }
120 
121   my_osmaperr(GetLastError());
122 
123 #endif /* _WIN32 */
124 
125   if (errno == ENOENT) {
126     *res = false;
127     return false;
128   }
129 
130   char errbuf[MYSYS_STRERROR_SIZE];
131   my_error(ER_CANT_GET_STAT, MYF(0), fname.c_str(), errno,
132            my_strerror(errbuf, sizeof(errbuf), errno));
133   return checked_return(true);
134 }
135 
pathncmp(const LEX_CSTRING & a,const LEX_CSTRING & b,size_t n)136 int pathncmp(const LEX_CSTRING &a, const LEX_CSTRING &b, size_t n) {
137   if (!lower_case_file_system) {
138     return strncmp(a.str, b.str, n);
139   }
140   return files_charset_info->coll->strnncoll(
141       files_charset_info, reinterpret_cast<const uchar *>(a.str), a.length,
142       reinterpret_cast<const uchar *>(b.str), b.length, false);
143 }
144 
145 struct Dir_pat_tuple {
146   dd::String_type dir;
147   dd::String_type pat;
148   bool m_is_inside_datadir;
149 };
150 
make_dir_pattern_tuple(const LEX_STRING & path,const LEX_CSTRING & schema_name)151 Dir_pat_tuple make_dir_pattern_tuple(const LEX_STRING &path,
152                                      const LEX_CSTRING &schema_name) {
153   char dirname[FN_REFLEN];
154   size_t dirname_len = 0;
155   // Return value same as dirname_len?
156   dirname_part(dirname, path.str, &dirname_len);
157   const dd::String_type fpat{path.str + dirname_len, path.length - dirname_len};
158 
159   dd::String_type data_dir{mysql_real_data_home_ptr};
160   if (test_if_hard_path(path.str)) {
161     bool is_in_datadir = false;
162     if (dirname_len >= data_dir.length()) {
163       const char *dirname_begin = dirname;
164       // const char *dirname_end= dirname+dirname_len;
165       is_in_datadir = (pathncmp({dirname_begin, dirname_len},
166                                 {data_dir.c_str(), data_dir.length()},
167                                 data_dir.length()) == 0);
168     }
169     return {dd::String_type{dirname, dirname_len}, std::move(fpat),
170             is_in_datadir};
171   }
172 
173   if (dirname_len == 0) {
174     data_dir.append(schema_name.str, schema_name.length);
175     data_dir.push_back(FN_LIBCHAR);
176   } else {
177     data_dir.append(dirname, dirname_len);
178   }
179 
180   return {std::move(data_dir), std::move(fpat), true};
181 }
182 
expand_sdi_pattern(const Dir_pat_tuple & dpt,dd::sdi_file::Paths_type * paths)183 bool expand_sdi_pattern(const Dir_pat_tuple &dpt,
184                         dd::sdi_file::Paths_type *paths) {
185   const char *dir_beg = dpt.dir.c_str();
186 
187   if (!is_secure_file_path(dir_beg)) {
188     dd::String_type x = "--secure-file-priv='";
189     x.append(opt_secure_file_priv);
190     x.append("'");
191     /* Read only allowed from within dir specified by secure_file_priv */
192     my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), x.c_str());
193     return true;
194   }
195 
196   MY_DIR *dir = my_dir(dir_beg, MYF(MY_DONT_SORT));
197   if (dir == nullptr) {
198     char errbuf[MYSYS_STRERROR_SIZE];
199     my_error(ER_CANT_READ_DIR, MYF(0), dir_beg, my_errno,
200              my_strerror(errbuf, sizeof(errbuf), my_errno()));
201     return true;
202   }
203 
204   auto dirender = [](MY_DIR *d) { my_dirend(d); };
205   std::unique_ptr<MY_DIR, decltype(dirender)> guard(dir, dirender);
206 
207   const char *pat_beg = dpt.pat.c_str();
208   const char *pat_end = dpt.pat.c_str() + dpt.pat.length();
209 
210   size_t path_count = paths->size();
211   for (uint i = 0; i < dir->number_off_files; ++i) {
212     bool match = false;
213     if (!lower_case_file_system)
214       match = (my_wildcmp_mb_bin(
215                    files_charset_info, dir->dir_entry[i].name,
216                    dir->dir_entry[i].name + strlen(dir->dir_entry[i].name),
217                    pat_beg, pat_end, '\\', '?', '*') == 0);
218     else
219       match =
220           (my_wildcmp(files_charset_info, dir->dir_entry[i].name,
221                       dir->dir_entry[i].name + strlen(dir->dir_entry[i].name),
222                       pat_beg, pat_end, '\\', '?', '*') == 0);
223     if (match) {
224       size_t len = strlen(dir->dir_entry[i].name);
225       const char *tail = dir->dir_entry[i].name + len - 4;
226       if (len < 4 || my_strcasecmp(files_charset_info, tail, ".sdi") != 0) {
227         my_error(ER_WRONG_FILE_NAME, MYF(0), dir->dir_entry[i].name);
228         return true;
229       }
230 
231       char sdi_pathname[FN_REFLEN];
232       fn_format(sdi_pathname, dir->dir_entry[i].name, dir_beg, "",
233                 MYF(MY_UNPACK_FILENAME | MY_SAFE_PATH));
234       paths->emplace_back(dd::String_type(sdi_pathname),
235                           dpt.m_is_inside_datadir);
236     }
237   }
238   if (path_count == paths->size()) {
239     my_error(ER_IMP_NO_FILES_MATCHED, MYF(0), pat_beg);
240     return true;
241   }
242   return false;
243 }
244 
245 }  // namespace
246 
247 namespace dd {
248 namespace sdi_file {
249 
sdi_filename(Object_id id,const String_type & entity_name,const String_type & schema)250 String_type sdi_filename(Object_id id, const String_type &entity_name,
251                          const String_type &schema) {
252   typedef String_type::const_iterator CHARIT;
253   const CHARIT begin = entity_name.begin();
254   const CHARIT end = entity_name.end();
255   CHARIT i = begin;
256   size_t count = 0;
257 
258   while (i != end && count < dd::sdi_file::FILENAME_PREFIX_CHARS) {
259     size_t charlen = my_mbcharlen(system_charset_info, static_cast<uchar>(*i));
260     DBUG_ASSERT(charlen > 0);
261     i += charlen;
262     ++count;
263   }
264 
265   Stringstream_type fnamestr;
266   fnamestr << String_type(begin, i) << "_" << id;
267 
268   char path[FN_REFLEN + 1];
269   bool was_truncated = false;
270   build_table_filename(path, sizeof(path) - 1, schema.c_str(),
271                        fnamestr.str().c_str(), EXT.c_str(), 0, &was_truncated);
272   DBUG_ASSERT(!was_truncated);
273 
274   return String_type(path);
275 }
276 
store_tbl_sdi(const dd::Sdi_type & sdi,const dd::Table & table,const dd::Schema & schema)277 bool store_tbl_sdi(const dd::Sdi_type &sdi, const dd::Table &table,
278                    const dd::Schema &schema) {
279   return checked_return(write_sdi_file(
280       sdi_filename(table.id(), table.name(), schema.name()), sdi));
281 }
282 
remove(const String_type & fname)283 bool remove(const String_type &fname) {
284   return checked_return(
285       mysql_file_delete(key_file_sdi, fname.c_str(), MYF(MY_FAE)));
286 }
287 
remove_sdi_file_if_exists(const String_type & fname)288 static bool remove_sdi_file_if_exists(const String_type &fname) {
289   bool file_exists = false;
290   if (sdi_file_exists(fname, &file_exists)) {
291     return checked_return(true);
292   }
293 
294   if (!file_exists) {
295     return false;
296   }
297 
298   return checked_return(remove(fname));
299 }
300 
drop_tbl_sdi(const dd::Table & table,const dd::Schema & schema)301 bool drop_tbl_sdi(const dd::Table &table, const dd::Schema &schema) {
302   String_type sdi_fname = sdi_filename(table.id(), table.name(), schema.name());
303   return checked_return(remove_sdi_file_if_exists(sdi_fname));
304 }
305 
load(THD *,const dd::String_type & fname,dd::String_type * buf)306 bool load(THD *, const dd::String_type &fname, dd::String_type *buf) {
307   File sdi_fd = mysql_file_open(key_file_sdi, fname.c_str(), O_RDONLY,
308                                 MYF(MY_FAE | MY_WME));
309   if (sdi_fd < 0) {
310     return dd::sdi_utils::checked_return(true);
311   }
312 
313   auto closer = [](File *f) {
314 #ifndef DBUG_OFF
315     bool ret =
316 #endif /* !DBUG_OFF */
317         mysql_file_close(*f, MYF(MY_FAE | MY_WME));
318     DBUG_ASSERT(ret == false);
319   };
320   std::unique_ptr<File, decltype(closer)> guard(&sdi_fd, closer);
321 
322   MY_STAT mystat;
323   if (mysql_file_fstat(sdi_fd, &mystat)) {
324     return true;
325   }
326 
327   if (mystat.st_size == 0) return false;
328   buf->resize(static_cast<size_t>(mystat.st_size));
329   uchar *sdi_buf = reinterpret_cast<uchar *>(&buf->front());
330 
331   if (mysql_file_read(sdi_fd, sdi_buf, buf->size(),
332                       MYF(MY_FAE | MY_WME | MY_NABP))) {
333     return true;
334   }
335   return false;
336 }
337 
expand_pattern(THD * thd,const LEX_STRING & pattern,Paths_type * paths)338 bool expand_pattern(THD *thd, const LEX_STRING &pattern, Paths_type *paths) {
339   auto dpt = make_dir_pattern_tuple(pattern, thd->db());
340   if (expand_sdi_pattern(dpt, paths)) {
341     return true;
342   }
343   return false;
344 }
345 
346 template <typename CLOS>
with_str_error(CLOS && clos)347 bool with_str_error(CLOS &&clos) {
348   char errbuf[MYSYS_STRERROR_SIZE];
349   return clos(my_strerror(errbuf, sizeof(errbuf), my_errno()));
350 }
351 
check_data_files_exist(const dd::String_type & schema_name,const dd::String_type & table_name)352 bool check_data_files_exist(const dd::String_type &schema_name,
353                             const dd::String_type &table_name) {
354   char path[FN_REFLEN + 1];
355   bool was_truncated = false;
356   size_t plen =
357       build_table_filename(path, sizeof(path) - 1, schema_name.c_str(),
358                            table_name.c_str(), ".MYD", 0, &was_truncated);
359 
360   MY_STAT sa;
361   if (nullptr == my_stat(path, &sa, MYF(0))) {
362     with_str_error([&](const char *strerr) {
363       my_error(ER_FILE_NOT_FOUND, MYF(0), path, my_errno(), strerr);
364       return false;
365     });
366     return true;
367   }
368 
369   path[plen - 3] = 0;
370   strcat(path, "MYI");
371 
372   if (nullptr == my_stat(path, &sa, MYF(0))) {
373     with_str_error([&](const char *strerr) {
374       my_error(ER_FILE_NOT_FOUND, MYF(0), path, my_errno(), strerr);
375       return false;
376     });
377     return true;
378   }
379   return false;
380 }
381 }  // namespace sdi_file
382 }  // namespace dd
383