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