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