Apply clang-format to source tree
[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,2016,2017,2019, 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 #if HAVE_DIRENT_H
57 #    include <dirent.h>
58 #endif
59 #if 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 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'", dirname);
107                 GMX_THROW_WITH_ERRNO(FileIOError(message), "_findfirst", code);
108             }
109             return NULL;
110         }
111         return new Impl(handle, finddata);
112     }
113     Impl(intptr_t handle, _finddata_t finddata) :
114         windows_handle(handle),
115         finddata(finddata),
116         bFirst_(true)
117     {
118     }
119     ~Impl() { _findclose(windows_handle); }
120
121     bool nextFile(std::string* filename)
122     {
123         if (bFirst_)
124         {
125             *filename = finddata.name;
126             bFirst_   = false;
127             return true;
128         }
129         else
130         {
131             errno = 0;
132             if (_findnext(windows_handle, &finddata) != 0)
133             {
134                 if (errno == 0 || errno == ENOENT)
135                 {
136                     filename->clear();
137                     return false;
138                 }
139                 else
140                 {
141                     GMX_THROW_WITH_ERRNO(FileIOError("Failed to list files in a directory"),
142                                          "_findnext", errno);
143                 }
144             }
145             *filename = finddata.name;
146             return true;
147         }
148     }
149
150 private:
151     intptr_t    windows_handle;
152     _finddata_t finddata;
153     bool        bFirst_;
154 };
155 #elif HAVE_DIRENT_H
156 class DirectoryEnumerator::Impl
157 {
158 public:
159     static Impl* init(const char* dirname, bool bThrow)
160     {
161         errno       = 0;
162         DIR* handle = opendir(dirname);
163         if (handle == nullptr)
164         {
165             if (bThrow)
166             {
167                 const int         code = errno;
168                 const std::string message =
169                         formatString("Failed to list files in directory '%s'", dirname);
170                 GMX_THROW_WITH_ERRNO(FileIOError(message), "opendir", code);
171             }
172             return nullptr;
173         }
174         return new Impl(handle);
175     }
176     explicit Impl(DIR* handle) : dirent_handle(handle) {}
177     ~Impl() { closedir(dirent_handle); }
178
179     bool nextFile(std::string* filename)
180     {
181         errno     = 0;
182         dirent* p = readdir(dirent_handle);
183         if (p == nullptr)
184         {
185             if (errno == 0)
186             {
187                 // All the files have been found.
188                 filename->clear();
189                 return false;
190             }
191             else
192             {
193                 GMX_THROW_WITH_ERRNO(FileIOError("Failed to list files in a directory"), "readdir", errno);
194             }
195         }
196         *filename = p->d_name;
197         return true;
198     }
199
200 private:
201     DIR* dirent_handle;
202 };
203 #else
204 class DirectoryEnumerator::Impl
205 {
206 public:
207     static Impl* init(const char* /*dirname*/, bool /*bThrow*/)
208     {
209         std::string message(
210                 "Source compiled without POSIX dirent or Windows support "
211                 "- cannot scan directories. In the very unlikely event "
212                 "this is not a compile-time mistake you could consider "
213                 "implementing support for your platform in "
214                 "directoryenumerator.cpp, but contact the developers "
215                 "to make sure it's really necessary!");
216         GMX_THROW(NotImplementedError(message));
217     }
218
219     bool nextFile(std::string* /*filename*/) { return false; }
220 };
221 #endif
222
223 /********************************************************************
224  * DirectoryEnumerator
225  */
226
227 // static
228 std::vector<std::string> DirectoryEnumerator::enumerateFilesWithExtension(const char* dirname,
229                                                                           const char* extension,
230                                                                           bool        bThrow)
231 {
232     std::vector<std::string> result;
233     DirectoryEnumerator      dir(dirname, bThrow);
234     std::string              nextName;
235     while (dir.nextFile(&nextName))
236     {
237         if (debug)
238         {
239             std::fprintf(debug, "dir '%s' file '%s'\n", dirname, nextName.c_str());
240         }
241         // TODO: What about case sensitivity?
242         if (endsWith(nextName, extension))
243         {
244             result.push_back(nextName);
245         }
246     }
247
248     std::sort(result.begin(), result.end());
249     return result;
250 }
251
252
253 DirectoryEnumerator::DirectoryEnumerator(const char* dirname, bool bThrow) : impl_(nullptr)
254 {
255     GMX_RELEASE_ASSERT(dirname != nullptr && dirname[0] != '\0',
256                        "Attempted to open empty/null directory path");
257     impl_.reset(Impl::init(dirname, bThrow));
258 }
259
260 DirectoryEnumerator::DirectoryEnumerator(const std::string& dirname, bool bThrow) : impl_(nullptr)
261 {
262     GMX_RELEASE_ASSERT(!dirname.empty(), "Attempted to open empty/null directory path");
263     impl_.reset(Impl::init(dirname.c_str(), bThrow));
264 }
265
266 DirectoryEnumerator::~DirectoryEnumerator() {}
267
268 bool DirectoryEnumerator::nextFile(std::string* filename)
269 {
270     if (impl_ == nullptr)
271     {
272         filename->clear();
273         return false;
274     }
275     return impl_->nextFile(filename);
276 }
277
278 } // namespace gmx