1 /* Copyright (c) 2008, 2010, 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 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 __WIN__
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     if (ignore_existing && errno == EEXIST)
226       return true;
227 
228     fprintf(stderr,
229             "Failed to create directory '%s', error: %d\n",
230             dir, errno);
231     return false;
232   }
233 #endif
234   return true;
235 }
236 
237 
Temp()238 NdbDir::Temp::Temp()
239 {
240 #ifdef _WIN32
241   DWORD len = GetTempPath(0, NULL);
242   m_path = new char[len];
243   if (GetTempPath(len, (char*)m_path) == 0)
244     abort();
245 #else
246   char* tmp = getenv("TMPDIR");
247   if (tmp)
248     m_path = tmp;
249   else
250     m_path = "/tmp/";
251 #endif
252 }
253 
~Temp()254 NdbDir::Temp::~Temp()
255 {
256 #ifdef _WIN32
257   delete [] m_path;
258 #endif
259 }
260 
261 
262 const char*
path(void) const263 NdbDir::Temp::path(void) const {
264   return m_path;
265 }
266 
267 
268 bool
remove(const char * path)269 NdbDir::remove(const char* path)
270 {
271 #ifdef _WIN32
272   if (RemoveDirectory(path) != 0)
273     return true; // Gone
274 #else
275   if (rmdir(path) == 0)
276     return true; // Gone
277 #endif
278   return false;
279 }
280 
281 bool
remove_recursive(const char * dir,bool only_contents)282 NdbDir::remove_recursive(const char* dir, bool only_contents)
283 {
284   char path[PATH_MAX];
285   if (basestring_snprintf(path, sizeof(path),
286                           "%s%s", dir, DIR_SEPARATOR) < 0) {
287     fprintf(stderr, "Too long path to remove: '%s'\n", dir);
288     return false;
289   }
290   int start_len = strlen(path);
291 
292   const char* name;
293   NdbDir::Iterator iter;
294 loop:
295   {
296     if (iter.open(path) != 0)
297     {
298       fprintf(stderr, "Failed to open iterator for '%s'\n",
299               path);
300       return false;
301     }
302 
303     while ((name = iter.next_entry()) != NULL)
304     {
305       if ((strcmp(".", name) == 0) || (strcmp("..", name) == 0))
306         continue;
307 
308       int end_len, len = strlen(path);
309       if ((end_len = basestring_snprintf(path + len, sizeof(path) - len,
310                                          "%s", name)) < 0)
311       {
312         fprintf(stderr, "Too long path detected: '%s'+'%s'\n",
313                 path, name);
314         return false;
315       }
316 
317       if (unlink(path) == 0 || NdbDir::remove(path) == true)
318       {
319         path[len] = 0;
320         continue;
321       }
322 
323       iter.close();
324 
325       // Append ending slash to the string
326       int pos = len + end_len;
327       if (basestring_snprintf(path + pos, sizeof(path) - pos,
328                               "%s", DIR_SEPARATOR) < 0)
329       {
330         fprintf(stderr, "Too long path detected: '%s'+'%s'\n",
331                 path, DIR_SEPARATOR);
332         return false;
333       }
334 
335       goto loop;
336     }
337     iter.close();
338 
339     int len = strlen(path);
340     path[len - 1] = 0; // remove ending slash
341 
342     char * prev_slash = strrchr(path, IF_WIN('\\', '/'));
343     if (len > start_len && prev_slash)
344     {
345       // Not done yet, step up one dir level
346       assert(prev_slash > path && prev_slash < path + sizeof(path));
347       prev_slash[1] = 0;
348       goto loop;
349     }
350   }
351 
352   if (only_contents == false && NdbDir::remove(dir) == false)
353   {
354     fprintf(stderr,
355             "Failed to remove directory '%s', error: %d\n",
356             dir, errno);
357     return false;
358   }
359 
360   return true;
361 }
362 
363 #ifdef _WIN32
364 #include <direct.h> // chdir
365 #endif
366 
367 int
chdir(const char * path)368 NdbDir::chdir(const char* path)
369 {
370   return ::chdir(path);
371 }
372 
373 
374 #ifdef TEST_NDBDIR
375 #include <NdbTap.hpp>
376 
377 #define CHECK(x) \
378   if (!(x)) {					       \
379     fprintf(stderr, "failed at line %d\n",  __LINE__ );	       \
380     abort(); }
381 
382 static void
build_tree(const char * path)383 build_tree(const char* path)
384 {
385   char tmp[PATH_MAX];
386   CHECK(NdbDir::create(path));
387 
388   // Create files in path/
389   for (int i = 8; i < 14; i++){
390     basestring_snprintf(tmp, sizeof(tmp), "%s%sfile%d", path, DIR_SEPARATOR, i);
391     fclose(fopen(tmp, "w"));
392   }
393 
394   // Create directories
395   for (int i = 8; i < 14; i++){
396     basestring_snprintf(tmp, sizeof(tmp), "%s%sdir%d", path, DIR_SEPARATOR, i);
397     CHECK(NdbDir::create(tmp));
398 
399     // Create files in dir
400     for (int j = 0; j < 6; j++){
401       basestring_snprintf(tmp, sizeof(tmp), "%s%sdir%d%sfile%d",
402 	       path, DIR_SEPARATOR, i, DIR_SEPARATOR, j);
403       fclose(fopen(tmp, "w"));
404     }
405   }
406 
407 #ifndef _WIN32
408   // Symlink the last file created to path/symlink
409   char tmp2[PATH_MAX];
410   basestring_snprintf(tmp2, sizeof(tmp2), "%s%ssymlink", path, DIR_SEPARATOR);
411   CHECK(symlink(tmp, tmp2) == 0);
412 #endif
413 }
414 
415 static bool
gone(const char * dir)416 gone(const char *dir) {
417   return (access(dir, F_OK) == -1 && errno == ENOENT);
418 }
419 
TAPTEST(DirIterator)420 TAPTEST(DirIterator)
421 {
422   NdbDir::Temp tempdir;
423   char path[PATH_MAX];
424   basestring_snprintf(path, sizeof(path),"%s%s%s",
425                       tempdir.path(), DIR_SEPARATOR, "ndbdir_test");
426 
427   printf("Using directory '%s'\n", path);
428 
429   // Remove dir if it exists
430   if (access(path, F_OK) == 0)
431     CHECK(NdbDir::remove_recursive(path));
432 
433   // Build dir tree
434   build_tree(path);
435   // Test to iterate over files
436   {
437     NdbDir::Iterator iter;
438     CHECK(iter.open(path) == 0);
439     const char* name;
440     int num_files = 0;
441     while((name = iter.next_file()) != NULL)
442     {
443       //printf("%s\n", name);
444       num_files++;
445     }
446     printf("Found %d files\n", num_files);
447     CHECK(num_files == 6);
448   }
449 
450   // Remove all of tree
451   CHECK(NdbDir::remove_recursive(path));
452   CHECK(gone(path));
453 
454   // Remove non existing directory
455   fprintf(stderr, "Checking that proper error is returned when "
456                   "opening non existing directory\n");
457   CHECK(!NdbDir::remove_recursive(path));
458   CHECK(gone(path));
459 
460   // Build dir tree and remove everything inside it
461   build_tree(path);
462   CHECK(NdbDir::remove_recursive(path, true));
463   CHECK(!gone(path));
464 
465   // Remove also the empty dir
466   CHECK(NdbDir::remove_recursive(path));
467   CHECK(gone(path));
468 
469   // Remove non exisiting directory(again)
470   CHECK(!NdbDir::remove_recursive(path));
471   CHECK(gone(path));
472 
473   // Create directory with non default mode
474   CHECK(NdbDir::create(path,
475                        NdbDir::u_rwx() | NdbDir::g_r() | NdbDir::o_r()));
476   CHECK(!gone(path));
477   CHECK(NdbDir::remove_recursive(path));
478   CHECK(gone(path));
479 
480   // Create already existing directory
481   CHECK(NdbDir::create(path, NdbDir::u_rwx()));
482   CHECK(!gone(path));
483   CHECK(NdbDir::create(path, NdbDir::u_rwx(), true /* ignore existing!! */));
484   CHECK(!gone(path));
485   CHECK(NdbDir::remove_recursive(path));
486   CHECK(gone(path));
487 
488   printf("Testing NdbDir::chdir...\n");
489   // Try chdir to the non existing dir, should fail
490   CHECK(NdbDir::chdir(path) != 0);
491 
492   // Build dir tree
493   build_tree(path);
494 
495   // Try chdir to the now existing dir, should work
496   CHECK(NdbDir::chdir(path) == 0);
497 
498   // Try chdir to the root of tmpdir, should work
499   CHECK(NdbDir::chdir(tempdir.path()) == 0);
500 
501   // Remove the dir tree again to leave clean
502   CHECK(NdbDir::remove_recursive(path));
503   CHECK(gone(path));
504 
505   return 1; // OK
506 }
507 #endif
508