1 /* Copyright (c) 2008, 2021, Oracle and/or its affiliates.
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 Street, Fifth Floor, Boston, MA 02110-1301, USA */
22
23 #include <ndb_global.h>
24 #include <NdbDir.hpp>
25
26 #include <util/basestring_vsnprintf.h>
27
28 #ifndef _WIN32
29
30 #include <dirent.h>
31
32 class DirIteratorImpl {
33 DIR* m_dirp;
34 const char *m_path;
35 char* m_buf;
36
is_regular_file(struct dirent * dp) const37 bool is_regular_file(struct dirent* dp) const {
38 #ifdef _DIRENT_HAVE_D_TYPE
39 /*
40 Using dirent's d_type field to determine if
41 it's a regular file
42 */
43 if(dp->d_type != DT_UNKNOWN)
44 return (dp->d_type == DT_REG);
45 #endif
46 /* Using stat to read more info about the file */
47 basestring_snprintf(m_buf, PATH_MAX,
48 "%s/%s", m_path, dp->d_name);
49
50 struct stat buf;
51 if (lstat(m_buf, &buf)) // Use lstat to not follow symlinks
52 return false; // 'stat' failed
53
54 return S_ISREG(buf.st_mode);
55
56 }
57
58 public:
DirIteratorImpl()59 DirIteratorImpl():
60 m_dirp(NULL) {
61 m_buf = new char[PATH_MAX];
62 };
63
~DirIteratorImpl()64 ~DirIteratorImpl() {
65 close();
66 delete [] m_buf;
67 }
68
open(const char * path)69 int open(const char* path){
70 if ((m_dirp = opendir(path)) == NULL){
71 return -1;
72 }
73 m_path= path;
74 return 0;
75 }
76
close(void)77 void close(void)
78 {
79 if (m_dirp)
80 closedir(m_dirp);
81 m_dirp = NULL;
82 }
83
next_entry(bool & is_reg)84 const char* next_entry(bool& is_reg)
85 {
86 struct dirent* dp = readdir(m_dirp);
87
88 if (dp == NULL)
89 return NULL;
90
91 is_reg = is_regular_file(dp);
92 return dp->d_name;
93 }
94 };
95
96 #else
97
98 class DirIteratorImpl {
99 bool m_first;
100 WIN32_FIND_DATA m_find_data;
101 HANDLE m_find_handle;
102
is_dir(const WIN32_FIND_DATA find_data) const103 bool is_dir(const WIN32_FIND_DATA find_data) const {
104 return (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
105 }
is_regular_file(const WIN32_FIND_DATA find_data) const106 bool is_regular_file(const WIN32_FIND_DATA find_data) const {
107 return !is_dir(find_data);
108 }
109
110 public:
DirIteratorImpl()111 DirIteratorImpl():
112 m_first(true),
113 m_find_handle(INVALID_HANDLE_VALUE) {};
114
~DirIteratorImpl()115 ~DirIteratorImpl() {
116 close();
117 }
118
open(const char * path)119 int open(const char* path){
120 char path_buf[PATH_MAX+2];
121 m_first = true;
122 basestring_snprintf(path_buf, sizeof(path_buf), "%s\\*", path);
123 m_find_handle = FindFirstFile(path_buf, &m_find_data);
124 if(m_find_handle == INVALID_HANDLE_VALUE)
125 {
126 if (GetLastError() == ERROR_FILE_NOT_FOUND)
127 m_first= false; // Will do a seek in 'next_file' and return NULL
128 else
129 return -1;
130 }
131 return 0;
132 }
133
close(void)134 void close(void)
135 {
136 if (m_find_handle)
137 FindClose(m_find_handle);
138 m_find_handle = NULL;
139 }
140
next_entry(bool & is_reg)141 const char* next_entry(bool& is_reg)
142 {
143 if (m_first || FindNextFile(m_find_handle, &m_find_data))
144 {
145 m_first = false;
146 is_reg = is_regular_file(m_find_data);
147 return m_find_data.cFileName;
148 }
149 return NULL;
150 }
151 };
152
153 #endif
154
155
Iterator()156 NdbDir::Iterator::Iterator() :
157 m_impl(*new DirIteratorImpl())
158 {
159 }
160
~Iterator()161 NdbDir::Iterator::~Iterator()
162 {
163 delete &m_impl;
164 }
165
166
open(const char * path)167 int NdbDir::Iterator::open(const char* path)
168 {
169 return m_impl.open(path);
170 }
171
close(void)172 void NdbDir::Iterator::close(void)
173 {
174 m_impl.close();
175 }
176
next_file(void)177 const char* NdbDir::Iterator::next_file(void)
178 {
179 bool is_reg;
180 const char* name;
181 while((name = m_impl.next_entry(is_reg)) != NULL){
182 if (is_reg == true)
183 return name; // Found regular file
184 }
185 return NULL;
186 }
187
next_entry(void)188 const char* NdbDir::Iterator::next_entry(void)
189 {
190 bool is_reg;
191 return m_impl.next_entry(is_reg);
192 }
193
u_r(void)194 mode_t NdbDir::u_r(void) { return IF_WIN(0, S_IRUSR); };
u_w(void)195 mode_t NdbDir::u_w(void) { return IF_WIN(0, S_IWUSR); };
u_x(void)196 mode_t NdbDir::u_x(void) { return IF_WIN(0, S_IXUSR); };
197
g_r(void)198 mode_t NdbDir::g_r(void) { return IF_WIN(0, S_IRGRP); };
g_w(void)199 mode_t NdbDir::g_w(void) { return IF_WIN(0, S_IWGRP); };
g_x(void)200 mode_t NdbDir::g_x(void) { return IF_WIN(0, S_IXGRP); };
201
o_r(void)202 mode_t NdbDir::o_r(void) { return IF_WIN(0, S_IROTH); };
o_w(void)203 mode_t NdbDir::o_w(void) { return IF_WIN(0, S_IWOTH); };
o_x(void)204 mode_t NdbDir::o_x(void) { return IF_WIN(0, S_IXOTH); };
205
206
207 bool
create(const char * dir,mode_t mode,bool ignore_existing)208 NdbDir::create(const char *dir, mode_t mode, bool ignore_existing)
209 {
210 #ifdef _WIN32
211 if (CreateDirectory(dir, NULL) == 0)
212 {
213 if (ignore_existing &&
214 GetLastError() == ERROR_ALREADY_EXISTS)
215 return true;
216
217 fprintf(stderr,
218 "Failed to create directory '%s', error: %d\n",
219 dir, GetLastError());
220 return false;
221 }
222 #else
223 if (mkdir(dir, mode) != 0)
224 {
225 int error = errno;
226 if (ignore_existing &&
227 (error == EEXIST ||
228 error == EISDIR))
229 return true;
230
231 fprintf(stderr,
232 "Failed to create directory '%s', error: %d\n",
233 dir, errno);
234 return false;
235 }
236 #endif
237 return true;
238 }
239
240
Temp()241 NdbDir::Temp::Temp()
242 {
243 #ifdef _WIN32
244 DWORD len = GetTempPath(0, NULL);
245 m_path = new char[len];
246 if (GetTempPath(len, (char*)m_path) == 0)
247 abort();
248 #else
249 char* tmp = getenv("TMPDIR");
250 if (tmp)
251 m_path = tmp;
252 else
253 m_path = "/tmp/";
254 #endif
255 }
256
~Temp()257 NdbDir::Temp::~Temp()
258 {
259 #ifdef _WIN32
260 delete [] m_path;
261 #endif
262 }
263
264
265 const char*
path(void) const266 NdbDir::Temp::path(void) const {
267 return m_path;
268 }
269
270
271 bool
remove(const char * path)272 NdbDir::remove(const char* path)
273 {
274 #ifdef _WIN32
275 if (RemoveDirectory(path) != 0)
276 return true; // Gone
277 #else
278 if (rmdir(path) == 0)
279 return true; // Gone
280 #endif
281 return false;
282 }
283
284 bool
remove_recursive(const char * dir,bool only_contents)285 NdbDir::remove_recursive(const char* dir, bool only_contents)
286 {
287 char path[PATH_MAX];
288 if (basestring_snprintf(path, sizeof(path),
289 "%s%s", dir, DIR_SEPARATOR) < 0) {
290 fprintf(stderr, "Too long path to remove: '%s'\n", dir);
291 return false;
292 }
293 int start_len = (int)strlen(path);
294
295 const char* name;
296 NdbDir::Iterator iter;
297 loop:
298 {
299 if (iter.open(path) != 0)
300 {
301 fprintf(stderr, "Failed to open iterator for '%s'\n",
302 path);
303 return false;
304 }
305
306 while ((name = iter.next_entry()) != NULL)
307 {
308 if ((strcmp(".", name) == 0) || (strcmp("..", name) == 0))
309 continue;
310
311 int end_len, len = (int)strlen(path);
312 if ((end_len = basestring_snprintf(path + len, sizeof(path) - len,
313 "%s", name)) < 0)
314 {
315 fprintf(stderr, "Too long path detected: '%s'+'%s'\n",
316 path, name);
317 return false;
318 }
319
320 if (unlink(path) == 0 || NdbDir::remove(path) == true)
321 {
322 path[len] = 0;
323 continue;
324 }
325
326 iter.close();
327
328 // Append ending slash to the string
329 int pos = len + end_len;
330 if (basestring_snprintf(path + pos, sizeof(path) - pos,
331 "%s", DIR_SEPARATOR) < 0)
332 {
333 fprintf(stderr, "Too long path detected: '%s'+'%s'\n",
334 path, DIR_SEPARATOR);
335 return false;
336 }
337
338 goto loop;
339 }
340 iter.close();
341
342 int len = (int)strlen(path);
343 path[len - 1] = 0; // remove ending slash
344
345 char * prev_slash = strrchr(path, IF_WIN('\\', '/'));
346 if (len > start_len && prev_slash)
347 {
348 // Not done yet, step up one dir level
349 assert(prev_slash > path && prev_slash < path + sizeof(path));
350 prev_slash[1] = 0;
351 goto loop;
352 }
353 }
354
355 if (only_contents == false && NdbDir::remove(dir) == false)
356 {
357 fprintf(stderr,
358 "Failed to remove directory '%s', error: %d\n",
359 dir, errno);
360 return false;
361 }
362
363 return true;
364 }
365
366 #ifdef _WIN32
367 #include <direct.h> // chdir
368 #endif
369
370 int
chdir(const char * path)371 NdbDir::chdir(const char* path)
372 {
373 return ::chdir(path);
374 }
375
376
377 #ifdef TEST_NDBDIR
378 #include <NdbTap.hpp>
379
380 #define CHECK(x) \
381 if (!(x)) { \
382 fprintf(stderr, "failed at line %d\n", __LINE__ ); \
383 abort(); }
384
385 static void
build_tree(const char * path)386 build_tree(const char* path)
387 {
388 char tmp[PATH_MAX];
389 CHECK(NdbDir::create(path));
390
391 // Create files in path/
392 for (int i = 8; i < 14; i++){
393 basestring_snprintf(tmp, sizeof(tmp), "%s%sfile%d", path, DIR_SEPARATOR, i);
394 fclose(fopen(tmp, "w"));
395 }
396
397 // Create directories
398 for (int i = 8; i < 14; i++){
399 basestring_snprintf(tmp, sizeof(tmp), "%s%sdir%d", path, DIR_SEPARATOR, i);
400 CHECK(NdbDir::create(tmp));
401
402 // Create files in dir
403 for (int j = 0; j < 6; j++){
404 basestring_snprintf(tmp, sizeof(tmp), "%s%sdir%d%sfile%d",
405 path, DIR_SEPARATOR, i, DIR_SEPARATOR, j);
406 fclose(fopen(tmp, "w"));
407 }
408 }
409
410 #ifndef _WIN32
411 // Symlink the last file created to path/symlink
412 char tmp2[PATH_MAX];
413 basestring_snprintf(tmp2, sizeof(tmp2), "%s%ssymlink", path, DIR_SEPARATOR);
414 CHECK(symlink(tmp, tmp2) == 0);
415 #endif
416 }
417
418 static bool
gone(const char * dir)419 gone(const char *dir) {
420 return (access(dir, F_OK) == -1 && errno == ENOENT);
421 }
422
TAPTEST(DirIterator)423 TAPTEST(DirIterator)
424 {
425 NdbDir::Temp tempdir;
426 char path[PATH_MAX];
427 basestring_snprintf(path, sizeof(path),"%s%s%s",
428 tempdir.path(), DIR_SEPARATOR, "ndbdir_test");
429
430 printf("Using directory '%s'\n", path);
431
432 // Remove dir if it exists
433 if (access(path, F_OK) == 0)
434 CHECK(NdbDir::remove_recursive(path));
435
436 // Build dir tree
437 build_tree(path);
438 // Test to iterate over files
439 {
440 NdbDir::Iterator iter;
441 CHECK(iter.open(path) == 0);
442 const char* name;
443 int num_files = 0;
444 while((name = iter.next_file()) != NULL)
445 {
446 //printf("%s\n", name);
447 num_files++;
448 }
449 printf("Found %d files\n", num_files);
450 CHECK(num_files == 6);
451 }
452
453 // Remove all of tree
454 CHECK(NdbDir::remove_recursive(path));
455 CHECK(gone(path));
456
457 // Remove non existing directory
458 fprintf(stderr, "Checking that proper error is returned when "
459 "opening non existing directory\n");
460 CHECK(!NdbDir::remove_recursive(path));
461 CHECK(gone(path));
462
463 // Build dir tree and remove everything inside it
464 build_tree(path);
465 CHECK(NdbDir::remove_recursive(path, true));
466 CHECK(!gone(path));
467
468 // Remove also the empty dir
469 CHECK(NdbDir::remove_recursive(path));
470 CHECK(gone(path));
471
472 // Remove non exisiting directory(again)
473 CHECK(!NdbDir::remove_recursive(path));
474 CHECK(gone(path));
475
476 // Create directory with non default mode
477 CHECK(NdbDir::create(path,
478 NdbDir::u_rwx() | NdbDir::g_r() | NdbDir::o_r()));
479 CHECK(!gone(path));
480 CHECK(NdbDir::remove_recursive(path));
481 CHECK(gone(path));
482
483 // Create already existing directory
484 CHECK(NdbDir::create(path, NdbDir::u_rwx()));
485 CHECK(!gone(path));
486 CHECK(NdbDir::create(path, NdbDir::u_rwx(), true /* ignore existing!! */));
487 CHECK(!gone(path));
488 CHECK(NdbDir::remove_recursive(path));
489 CHECK(gone(path));
490
491 printf("Testing NdbDir::chdir...\n");
492 // Try chdir to the non existing dir, should fail
493 CHECK(NdbDir::chdir(path) != 0);
494
495 // Build dir tree
496 build_tree(path);
497
498 // Try chdir to the now existing dir, should work
499 CHECK(NdbDir::chdir(path) == 0);
500
501 // Try chdir to the root of tmpdir, should work
502 CHECK(NdbDir::chdir(tempdir.path()) == 0);
503
504 // Remove the dir tree again to leave clean
505 CHECK(NdbDir::remove_recursive(path));
506 CHECK(gone(path));
507
508 return 1; // OK
509 }
510 #endif
511