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