Convert forcefield search to C++
[alexxy/gromacs.git] / src / gromacs / utility / directoryenumerator.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2010,2011,2014,2015, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS 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 GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements gmx::DirectoryEnumerator.
38  *
39  * \author Erik Lindahl (original C implementation)
40  * \author Teemu Murtola <teemu.murtola@gmail.com> (C++ wrapper + errno handling)
41  * \ingroup module_utility
42  */
43 #include "gmxpre.h"
44
45 #include "directoryenumerator.h"
46
47 #include "config.h"
48
49 #include <cerrno>
50 #include <cstdio>
51
52 #include <algorithm>
53 #include <string>
54 #include <vector>
55
56 #ifdef HAVE_DIRENT_H
57 #include <dirent.h>
58 #endif
59 #ifdef GMX_NATIVE_WINDOWS
60 #include <io.h>
61 #endif
62
63 #include "gromacs/utility/exceptions.h"
64 #include "gromacs/utility/fatalerror.h"
65 #include "gromacs/utility/futil.h"
66 #include "gromacs/utility/gmxassert.h"
67 #include "gromacs/utility/smalloc.h"
68 #include "gromacs/utility/stringutil.h"
69
70 namespace gmx
71 {
72
73 /********************************************************************
74  * DirectoryEnumerator::Impl
75  */
76
77 // TODO: Consider whether checking the return value of closing would be useful,
78 // and what could we do if it fails?
79 #if defined GMX_NATIVE_WINDOWS
80 // TODO: Consider if Windows provides more error details through other APIs.
81 class DirectoryEnumerator::Impl
82 {
83     public:
84         static Impl *init(const char *dirname, bool bThrow)
85         {
86             std::string tmpname(dirname);
87             // Remove possible trailing directory separator.
88             // TODO: Use a method in gmx::Path instead.
89             if (tmpname.back() == '/' || tmpname.back() == '\\')
90             {
91                 tmpname.pop_back();
92             }
93
94             // Add wildcard.
95             tmpname.append("/*");
96
97             errno = 0;
98             _finddata_t finddata;
99             intptr_t    handle = _findfirst(tmpname.c_str(), &finddata);
100             if (handle < 0L)
101             {
102                 if (errno != ENOENT && bThrow)
103                 {
104                     const int         code    = errno;
105                     const std::string message =
106                         formatString("Failed to list files in directory '%s'",
107                                      dirname);
108                     GMX_THROW_WITH_ERRNO(FileIOError(message), "_findfirst", code);
109                 }
110                 return NULL;
111             }
112             return new Impl(handle, finddata);
113         }
114         Impl(intptr_t handle, _finddata_t finddata)
115             : windows_handle(handle), finddata(finddata), bFirst_(true)
116         {
117         }
118         ~Impl()
119         {
120             _findclose(windows_handle);
121         }
122
123         bool nextFile(std::string *filename)
124         {
125             if (bFirst_)
126             {
127                 *filename = finddata.name;
128                 bFirst_   = false;
129                 return true;
130             }
131             else
132             {
133                 errno = 0;
134                 if (_findnext(windows_handle, &finddata) != 0)
135                 {
136                     if (errno == 0 || errno == ENOENT)
137                     {
138                         filename->clear();
139                         return false;
140                     }
141                     else
142                     {
143                         GMX_THROW_WITH_ERRNO(
144                                 FileIOError("Failed to list files in a directory"),
145                                 "_findnext", errno);
146                     }
147                 }
148                 *filename = finddata.name;
149                 return true;
150             }
151         }
152
153     private:
154         intptr_t     windows_handle;
155         _finddata_t  finddata;
156         bool         bFirst_;
157 };
158 #elif defined HAVE_DIRENT_H
159 class DirectoryEnumerator::Impl
160 {
161     public:
162         static Impl *init(const char *dirname, bool bThrow)
163         {
164             errno       = 0;
165             DIR *handle = opendir(dirname);
166             if (handle == NULL)
167             {
168                 if (bThrow)
169                 {
170                     const int         code    = errno;
171                     const std::string message =
172                         formatString("Failed to list files in directory '%s'",
173                                      dirname);
174                     GMX_THROW_WITH_ERRNO(FileIOError(message), "opendir", code);
175                 }
176                 return NULL;
177             }
178             return new Impl(handle);
179         }
180         explicit Impl(DIR *handle) : dirent_handle(handle)
181         {
182             // TODO: Use memory allocation that throws, and handle
183             // exception safety (close handle) in such a case.
184             /* On some platforms no space is present for d_name in dirent.
185              * Since d_name is guaranteed to be the last entry, allocating
186              * extra space for dirent will allow more size for d_name.
187              * GMX_MAX_PATH should always be >= the max possible d_name.
188              */
189             smalloc(direntp_large, sizeof(*direntp_large) + GMX_PATH_MAX);
190         }
191         ~Impl()
192         {
193             sfree(direntp_large);
194             closedir(dirent_handle);
195         }
196
197         bool nextFile(std::string *filename)
198         {
199             errno = 0;
200             dirent *p;
201             int     rc = readdir_r(dirent_handle, direntp_large, &p);
202             if (p == NULL && rc == 0)
203             {
204                 filename->clear();
205                 return false;
206             }
207             else if (rc != 0)
208             {
209                 GMX_THROW_WITH_ERRNO(
210                         FileIOError("Failed to list files in a directory"),
211                         "readdir_r", errno);
212             }
213             *filename = direntp_large->d_name;
214             return true;
215         }
216
217     private:
218         DIR    *dirent_handle;
219         dirent *direntp_large;
220 };
221 #else
222 class DirectoryEnumerator::Impl
223 {
224     public:
225         static Impl *init(const char * /*dirname*/, bool /*bThrow*/)
226         {
227             std::string message(
228                     "Source compiled without POSIX dirent or Windows support "
229                     "- cannot scan directories. In the very unlikely event "
230                     "this is not a compile-time mistake you could consider "
231                     "implementing support for your platform in "
232                     "directoryenumerator.cpp, but contact the developers "
233                     "to make sure it's really necessary!");
234             GMX_THROW(NotImplementedError(message));
235         }
236
237         bool nextFile(std::string * /*filename*/)
238         {
239             return false;
240         }
241 };
242 #endif
243
244 /********************************************************************
245  * DirectoryEnumerator
246  */
247
248 // static
249 std::vector<std::string>
250 DirectoryEnumerator::enumerateFilesWithExtension(
251         const char *dirname, const char *extension, bool bThrow)
252 {
253     std::vector<std::string> result;
254     DirectoryEnumerator      dir(dirname, bThrow);
255     std::string              nextName;
256     while (dir.nextFile(&nextName))
257     {
258         if (debug)
259         {
260             std::fprintf(debug, "dir '%s' file '%s'\n",
261                          dirname, nextName.c_str());
262         }
263         // TODO: What about case sensitivity?
264         if (endsWith(nextName, extension))
265         {
266             result.push_back(nextName);
267         }
268     }
269
270     std::sort(result.begin(), result.end());
271     return result;
272 }
273
274
275 DirectoryEnumerator::DirectoryEnumerator(const char *dirname, bool bThrow)
276     : impl_(NULL)
277 {
278     GMX_RELEASE_ASSERT(dirname != NULL && dirname[0] != '\0',
279                        "Attempted to open empty/null directory path");
280     impl_.reset(Impl::init(dirname, bThrow));
281 }
282
283 DirectoryEnumerator::DirectoryEnumerator(const std::string &dirname, bool bThrow)
284     : impl_(NULL)
285 {
286     GMX_RELEASE_ASSERT(!dirname.empty(),
287                        "Attempted to open empty/null directory path");
288     impl_.reset(Impl::init(dirname.c_str(), bThrow));
289 }
290
291 DirectoryEnumerator::~DirectoryEnumerator()
292 {
293 }
294
295 bool DirectoryEnumerator::nextFile(std::string *filename)
296 {
297     if (!impl_.get())
298     {
299         filename->clear();
300         return false;
301     }
302     return impl_->nextFile(filename);
303 }
304
305 } // namespace gmx