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