Update instructions in containers.rst
[gromacs.git] / src / gromacs / utility / directoryenumerator.cpp
blob6631b74c97012f0f0341efcae20893e58ffe9fd3
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2010,2011,2014,2015,2016 by the GROMACS development team.
5 * Copyright (c) 2017,2019,2020, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
36 /*! \internal \file
37 * \brief
38 * Implements gmx::DirectoryEnumerator.
40 * \author Erik Lindahl (original C implementation)
41 * \author Teemu Murtola <teemu.murtola@gmail.com> (C++ wrapper + errno handling)
42 * \ingroup module_utility
44 #include "gmxpre.h"
46 #include "directoryenumerator.h"
48 #include "config.h"
50 #include <cerrno>
51 #include <cstdio>
53 #include <algorithm>
54 #include <string>
55 #include <vector>
57 #if HAVE_DIRENT_H
58 # include <dirent.h>
59 #endif
60 #if GMX_NATIVE_WINDOWS
61 # include <io.h>
62 #endif
64 #include "gromacs/utility/exceptions.h"
65 #include "gromacs/utility/fatalerror.h"
66 #include "gromacs/utility/futil.h"
67 #include "gromacs/utility/gmxassert.h"
68 #include "gromacs/utility/smalloc.h"
69 #include "gromacs/utility/stringutil.h"
71 namespace gmx
74 /********************************************************************
75 * DirectoryEnumerator::Impl
78 // TODO: Consider whether checking the return value of closing would be useful,
79 // and what could we do if it fails?
80 #if GMX_NATIVE_WINDOWS
81 // TODO: Consider if Windows provides more error details through other APIs.
82 class DirectoryEnumerator::Impl
84 public:
85 static Impl* init(const char* dirname, bool bThrow)
87 std::string tmpname(dirname);
88 // Remove possible trailing directory separator.
89 // TODO: Use a method in gmx::Path instead.
90 if (tmpname.back() == '/' || tmpname.back() == '\\')
92 tmpname.pop_back();
95 // Add wildcard.
96 tmpname.append("/*");
98 errno = 0;
99 _finddata_t finddata;
100 intptr_t handle = _findfirst(tmpname.c_str(), &finddata);
101 if (handle < 0L)
103 if (errno != ENOENT && bThrow)
105 const int code = errno;
106 const std::string message =
107 formatString("Failed to list files in directory '%s'", dirname);
108 GMX_THROW_WITH_ERRNO(FileIOError(message), "_findfirst", code);
110 return NULL;
112 return new Impl(handle, finddata);
114 Impl(intptr_t handle, _finddata_t finddata) :
115 windows_handle(handle),
116 finddata(finddata),
117 bFirst_(true)
120 ~Impl() { _findclose(windows_handle); }
122 bool nextFile(std::string* filename)
124 if (bFirst_)
126 *filename = finddata.name;
127 bFirst_ = false;
128 return true;
130 else
132 errno = 0;
133 if (_findnext(windows_handle, &finddata) != 0)
135 if (errno == 0 || errno == ENOENT)
137 filename->clear();
138 return false;
140 else
142 GMX_THROW_WITH_ERRNO(FileIOError("Failed to list files in a directory"),
143 "_findnext", errno);
146 *filename = finddata.name;
147 return true;
151 private:
152 intptr_t windows_handle;
153 _finddata_t finddata;
154 bool bFirst_;
156 #elif HAVE_DIRENT_H
157 class DirectoryEnumerator::Impl
159 public:
160 static Impl* init(const char* dirname, bool bThrow)
162 errno = 0;
163 DIR* handle = opendir(dirname);
164 if (handle == nullptr)
166 if (bThrow)
168 const int code = errno;
169 const std::string message =
170 formatString("Failed to list files in directory '%s'", dirname);
171 GMX_THROW_WITH_ERRNO(FileIOError(message), "opendir", code);
173 return nullptr;
175 return new Impl(handle);
177 explicit Impl(DIR* handle) : dirent_handle(handle) {}
178 ~Impl() { closedir(dirent_handle); }
180 bool nextFile(std::string* filename)
182 errno = 0;
183 dirent* p = readdir(dirent_handle);
184 if (p == nullptr)
186 if (errno == 0)
188 // All the files have been found.
189 filename->clear();
190 return false;
192 else
194 GMX_THROW_WITH_ERRNO(FileIOError("Failed to list files in a directory"), "readdir", errno);
197 *filename = p->d_name;
198 return true;
201 private:
202 DIR* dirent_handle;
204 #else
205 class DirectoryEnumerator::Impl
207 public:
208 static Impl* init(const char* /*dirname*/, bool /*bThrow*/)
210 std::string message(
211 "Source compiled without POSIX dirent or Windows support "
212 "- cannot scan directories. In the very unlikely event "
213 "this is not a compile-time mistake you could consider "
214 "implementing support for your platform in "
215 "directoryenumerator.cpp, but contact the developers "
216 "to make sure it's really necessary!");
217 GMX_THROW(NotImplementedError(message));
220 bool nextFile(std::string* /*filename*/) { return false; }
222 #endif
224 /********************************************************************
225 * DirectoryEnumerator
228 // static
229 std::vector<std::string> DirectoryEnumerator::enumerateFilesWithExtension(const char* dirname,
230 const char* extension,
231 bool bThrow)
233 std::vector<std::string> result;
234 DirectoryEnumerator dir(dirname, bThrow);
235 std::string nextName;
236 while (dir.nextFile(&nextName))
238 if (debug)
240 std::fprintf(debug, "dir '%s' file '%s'\n", dirname, nextName.c_str());
242 // TODO: What about case sensitivity?
243 if (endsWith(nextName, extension))
245 result.push_back(nextName);
249 std::sort(result.begin(), result.end());
250 return result;
254 DirectoryEnumerator::DirectoryEnumerator(const char* dirname, bool bThrow) : impl_(nullptr)
256 GMX_RELEASE_ASSERT(dirname != nullptr && dirname[0] != '\0',
257 "Attempted to open empty/null directory path");
258 impl_.reset(Impl::init(dirname, bThrow));
261 DirectoryEnumerator::DirectoryEnumerator(const std::string& dirname, bool bThrow) : impl_(nullptr)
263 GMX_RELEASE_ASSERT(!dirname.empty(), "Attempted to open empty/null directory path");
264 impl_.reset(Impl::init(dirname.c_str(), bThrow));
267 DirectoryEnumerator::~DirectoryEnumerator() {}
269 bool DirectoryEnumerator::nextFile(std::string* filename)
271 if (impl_ == nullptr)
273 filename->clear();
274 return false;
276 return impl_->nextFile(filename);
279 } // namespace gmx