xref: /freebsd/contrib/kyua/utils/fs/directory.cpp (revision 8a7df6c58d549bd2eaa5cad30b2ef93c3eca3c18)
1 // Copyright 2015 The Kyua Authors.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "utils/fs/directory.hpp"
30 
31 extern "C" {
32 #include <sys/types.h>
33 
34 #include <dirent.h>
35 #include <limits.h>
36 }
37 
38 #include <cerrno>
39 #include <memory>
40 
41 #include "utils/format/macros.hpp"
42 #include "utils/fs/exceptions.hpp"
43 #include "utils/fs/path.hpp"
44 #include "utils/noncopyable.hpp"
45 #include "utils/sanity.hpp"
46 #include "utils/text/operations.ipp"
47 
48 namespace detail = utils::fs::detail;
49 namespace fs = utils::fs;
50 namespace text = utils::text;
51 
52 
53 /// Constructs a new directory entry.
54 ///
55 /// \param name_ Name of the directory entry.
56 fs::directory_entry::directory_entry(const std::string& name_) : name(name_)
57 {
58 }
59 
60 
61 /// Checks if two directory entries are equal.
62 ///
63 /// \param other The entry to compare to.
64 ///
65 /// \return True if the two entries are equal; false otherwise.
66 bool
67 fs::directory_entry::operator==(const directory_entry& other) const
68 {
69     return name == other.name;
70 }
71 
72 
73 /// Checks if two directory entries are different.
74 ///
75 /// \param other The entry to compare to.
76 ///
77 /// \return True if the two entries are different; false otherwise.
78 bool
79 fs::directory_entry::operator!=(const directory_entry& other) const
80 {
81     return !(*this == other);
82 }
83 
84 
85 /// Checks if this entry sorts before another entry.
86 ///
87 /// \param other The entry to compare to.
88 ///
89 /// \return True if this entry sorts before the other entry; false otherwise.
90 bool
91 fs::directory_entry::operator<(const directory_entry& other) const
92 {
93     return name < other.name;
94 }
95 
96 
97 /// Formats a directory entry.
98 ///
99 /// \param output Stream into which to inject the formatted entry.
100 /// \param entry The entry to format.
101 ///
102 /// \return A reference to output.
103 std::ostream&
104 fs::operator<<(std::ostream& output, const directory_entry& entry)
105 {
106     output << F("directory_entry{name=%s}") % text::quote(entry.name, '\'');
107     return output;
108 }
109 
110 
111 /// Internal implementation details for the directory_iterator.
112 ///
113 /// In order to support multiple concurrent iterators over the same directory
114 /// object, this class is the one that performs all directory-level accesses.
115 /// In particular, even if it may seem surprising, this is the class that
116 /// handles the DIR object for the directory.
117 ///
118 /// Note that iterators implemented by this class do not rely on the container
119 /// directory class at all.  This should not be relied on for object lifecycle
120 /// purposes.
121 struct utils::fs::detail::directory_iterator::impl : utils::noncopyable {
122     /// Path of the directory accessed by this iterator.
123     const fs::path _path;
124 
125     /// Raw pointer to the system representation of the directory.
126     ///
127     /// We also use this to determine if the iterator is valid (at the end) or
128     /// not.  A null pointer means an invalid iterator.
129     ::DIR* _dirp;
130 
131     /// Custom representation of the directory entry.
132     std::unique_ptr< directory_entry > _entry;
133 
134     /// Constructs an iterator pointing to the "end" of the directory.
135     impl(void) : _path("invalid-directory-entry"), _dirp(NULL)
136     {
137     }
138 
139     /// Constructs a new iterator to start scanning a directory.
140     ///
141     /// \param path The directory that will be scanned.
142     ///
143     /// \throw system_error If there is a problem opening the directory.
144     explicit impl(const path& path) : _path(path)
145     {
146         DIR* dirp = ::opendir(_path.c_str());
147         if (dirp == NULL) {
148             const int original_errno = errno;
149             throw fs::system_error(F("opendir(%s) failed") % _path,
150                                    original_errno);
151         }
152         _dirp = dirp;
153 
154         // Initialize our first directory entry.  Note that this may actually
155         // close the directory we just opened if the directory happens to be
156         // empty -- but directories are never empty because they at least have
157         // '.' and '..' entries.
158         next();
159     }
160 
161     /// Destructor.
162     ///
163     /// This closes the directory if still open.
164     ~impl(void)
165     {
166         if (_dirp != NULL)
167             close();
168     }
169 
170     /// Closes the directory and invalidates the iterator.
171     void
172     close(void)
173     {
174         PRE(_dirp != NULL);
175         if (::closedir(_dirp) == -1) {
176             UNREACHABLE_MSG("Invalid dirp provided to closedir(3)");
177         }
178         _dirp = NULL;
179     }
180 
181     /// Advances the directory entry to the next one.
182     ///
183     /// It is possible to use this function on a new directory_entry object to
184     /// initialize the first entry.
185     ///
186     /// \throw system_error If the call to readdir fails.
187     void
188     next(void)
189     {
190         ::dirent* result;
191 
192         // TODO(ngie): use `std::filesystem::directory_iterator` once the
193         // minimum C++ standard is C++20.
194         result = ::readdir(_dirp);
195         if (result == NULL) {
196             _entry.reset();
197             close();
198         } else {
199             _entry.reset(new directory_entry(result->d_name));
200         }
201     }
202 };
203 
204 
205 /// Constructs a new directory iterator.
206 ///
207 /// \param pimpl The constructed internal implementation structure to use.
208 detail::directory_iterator::directory_iterator(std::shared_ptr< impl > pimpl) :
209     _pimpl(pimpl)
210 {
211 }
212 
213 
214 /// Destructor.
215 detail::directory_iterator::~directory_iterator(void)
216 {
217 }
218 
219 
220 /// Creates a new directory iterator for a directory.
221 ///
222 /// \return The directory iterator.  Note that the result may be invalid.
223 ///
224 /// \throw system_error If opening the directory or reading its first entry
225 ///     fails.
226 detail::directory_iterator
227 detail::directory_iterator::new_begin(const path& path)
228 {
229     return directory_iterator(std::shared_ptr< impl >(new impl(path)));
230 }
231 
232 
233 /// Creates a new invalid directory iterator.
234 ///
235 /// \return The invalid directory iterator.
236 detail::directory_iterator
237 detail::directory_iterator::new_end(void)
238 {
239     return directory_iterator(std::shared_ptr< impl >(new impl()));
240 }
241 
242 
243 /// Checks if two iterators are equal.
244 ///
245 /// We consider two iterators to be equal if both of them are invalid or,
246 /// otherwise, if they have the exact same internal representation (as given by
247 /// equality of the pimpl pointers).
248 ///
249 /// \param other The object to compare to.
250 ///
251 /// \return True if the two iterators are equal; false otherwise.
252 bool
253 detail::directory_iterator::operator==(const directory_iterator& other) const
254 {
255     return (_pimpl->_dirp == NULL && other._pimpl->_dirp == NULL) ||
256         _pimpl == other._pimpl;
257 }
258 
259 
260 /// Checks if two iterators are different.
261 ///
262 /// \param other The object to compare to.
263 ///
264 /// \return True if the two iterators are different; false otherwise.
265 bool
266 detail::directory_iterator::operator!=(const directory_iterator& other) const
267 {
268     return !(*this == other);
269 }
270 
271 
272 /// Moves the iterator one element forward.
273 ///
274 /// \return A reference to the iterator.
275 ///
276 /// \throw system_error If advancing the iterator fails.
277 detail::directory_iterator&
278 detail::directory_iterator::operator++(void)
279 {
280     _pimpl->next();
281     return *this;
282 }
283 
284 
285 /// Dereferences the iterator to its contents.
286 ///
287 /// \return A reference to the directory entry pointed to by the iterator.
288 const fs::directory_entry&
289 detail::directory_iterator::operator*(void) const
290 {
291     PRE(_pimpl->_entry.get() != NULL);
292     return *_pimpl->_entry;
293 }
294 
295 
296 /// Dereferences the iterator to its contents.
297 ///
298 /// \return A pointer to the directory entry pointed to by the iterator.
299 const fs::directory_entry*
300 detail::directory_iterator::operator->(void) const
301 {
302     PRE(_pimpl->_entry.get() != NULL);
303     return _pimpl->_entry.get();
304 }
305 
306 
307 /// Internal implementation details for the directory.
308 struct utils::fs::directory::impl : utils::noncopyable {
309     /// Path to the directory to scan.
310     fs::path _path;
311 
312     /// Constructs a new directory.
313     ///
314     /// \param path_ Path to the directory to scan.
315     impl(const fs::path& path_) : _path(path_)
316     {
317     }
318 };
319 
320 
321 /// Constructs a new directory.
322 ///
323 /// \param path_ Path to the directory to scan.
324 fs::directory::directory(const path& path_) : _pimpl(new impl(path_))
325 {
326 }
327 
328 
329 /// Returns an iterator to start scanning the directory.
330 ///
331 /// \return An iterator on the directory.
332 ///
333 /// \throw system_error If the directory cannot be opened to obtain its first
334 ///     entry.
335 fs::directory::const_iterator
336 fs::directory::begin(void) const
337 {
338     return const_iterator::new_begin(_pimpl->_path);
339 }
340 
341 
342 /// Returns an invalid iterator to check for the end of an scan.
343 ///
344 /// \return An invalid iterator.
345 fs::directory::const_iterator
346 fs::directory::end(void) const
347 {
348     return const_iterator::new_end();
349 }
350