xref: /freebsd/contrib/kyua/utils/fs/directory.cpp (revision da5432eda807c4b7232d030d5157d5b417ea4f52)
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 }
36 
37 #include <cerrno>
38 #include <memory>
39 
40 #include "utils/format/macros.hpp"
41 #include "utils/fs/exceptions.hpp"
42 #include "utils/fs/path.hpp"
43 #include "utils/noncopyable.hpp"
44 #include "utils/sanity.hpp"
45 #include "utils/text/operations.ipp"
46 
47 namespace detail = utils::fs::detail;
48 namespace fs = utils::fs;
49 namespace text = utils::text;
50 
51 
52 /// Constructs a new directory entry.
53 ///
54 /// \param name_ Name of the directory entry.
55 fs::directory_entry::directory_entry(const std::string& name_) : name(name_)
56 {
57 }
58 
59 
60 /// Checks if two directory entries are equal.
61 ///
62 /// \param other The entry to compare to.
63 ///
64 /// \return True if the two entries are equal; false otherwise.
65 bool
66 fs::directory_entry::operator==(const directory_entry& other) const
67 {
68     return name == other.name;
69 }
70 
71 
72 /// Checks if two directory entries are different.
73 ///
74 /// \param other The entry to compare to.
75 ///
76 /// \return True if the two entries are different; false otherwise.
77 bool
78 fs::directory_entry::operator!=(const directory_entry& other) const
79 {
80     return !(*this == other);
81 }
82 
83 
84 /// Checks if this entry sorts before another entry.
85 ///
86 /// \param other The entry to compare to.
87 ///
88 /// \return True if this entry sorts before the other entry; false otherwise.
89 bool
90 fs::directory_entry::operator<(const directory_entry& other) const
91 {
92     return name < other.name;
93 }
94 
95 
96 /// Formats a directory entry.
97 ///
98 /// \param output Stream into which to inject the formatted entry.
99 /// \param entry The entry to format.
100 ///
101 /// \return A reference to output.
102 std::ostream&
103 fs::operator<<(std::ostream& output, const directory_entry& entry)
104 {
105     output << F("directory_entry{name=%s}") % text::quote(entry.name, '\'');
106     return output;
107 }
108 
109 
110 /// Internal implementation details for the directory_iterator.
111 ///
112 /// In order to support multiple concurrent iterators over the same directory
113 /// object, this class is the one that performs all directory-level accesses.
114 /// In particular, even if it may seem surprising, this is the class that
115 /// handles the DIR object for the directory.
116 ///
117 /// Note that iterators implemented by this class do not rely on the container
118 /// directory class at all.  This should not be relied on for object lifecycle
119 /// purposes.
120 struct utils::fs::detail::directory_iterator::impl : utils::noncopyable {
121     /// Path of the directory accessed by this iterator.
122     const fs::path _path;
123 
124     /// Raw pointer to the system representation of the directory.
125     ///
126     /// We also use this to determine if the iterator is valid (at the end) or
127     /// not.  A null pointer means an invalid iterator.
128     ::DIR* _dirp;
129 
130     /// Raw representation of the system directory entry.
131     ///
132     /// We need to keep this at the class level so that we can use the
133     /// readdir_r(3) function.
134     ::dirent _dirent;
135 
136     /// Custom representation of the directory entry.
137     ///
138     /// This is separate from _dirent because this is the type we return to the
139     /// user.  We must keep this as a pointer so that we can support the common
140     /// operators (* and ->) over iterators.
141     std::auto_ptr< directory_entry > _entry;
142 
143     /// Constructs an iterator pointing to the "end" of the directory.
144     impl(void) : _path("invalid-directory-entry"), _dirp(NULL)
145     {
146     }
147 
148     /// Constructs a new iterator to start scanning a directory.
149     ///
150     /// \param path The directory that will be scanned.
151     ///
152     /// \throw system_error If there is a problem opening the directory.
153     explicit impl(const path& path) : _path(path)
154     {
155         DIR* dirp = ::opendir(_path.c_str());
156         if (dirp == NULL) {
157             const int original_errno = errno;
158             throw fs::system_error(F("opendir(%s) failed") % _path,
159                                    original_errno);
160         }
161         _dirp = dirp;
162 
163         // Initialize our first directory entry.  Note that this may actually
164         // close the directory we just opened if the directory happens to be
165         // empty -- but directories are never empty because they at least have
166         // '.' and '..' entries.
167         next();
168     }
169 
170     /// Destructor.
171     ///
172     /// This closes the directory if still open.
173     ~impl(void)
174     {
175         if (_dirp != NULL)
176             close();
177     }
178 
179     /// Closes the directory and invalidates the iterator.
180     void
181     close(void)
182     {
183         PRE(_dirp != NULL);
184         if (::closedir(_dirp) == -1) {
185             UNREACHABLE_MSG("Invalid dirp provided to closedir(3)");
186         }
187         _dirp = NULL;
188     }
189 
190     /// Advances the directory entry to the next one.
191     ///
192     /// It is possible to use this function on a new directory_entry object to
193     /// initialize the first entry.
194     ///
195     /// \throw system_error If the call to readdir_r fails.
196     void
197     next(void)
198     {
199         ::dirent* result;
200 
201         if (::readdir_r(_dirp, &_dirent, &result) == -1) {
202             const int original_errno = errno;
203             throw fs::system_error(F("readdir_r(%s) failed") % _path,
204                                    original_errno);
205         }
206         if (result == NULL) {
207             _entry.reset(NULL);
208             close();
209         } else {
210             _entry.reset(new directory_entry(_dirent.d_name));
211         }
212     }
213 };
214 
215 
216 /// Constructs a new directory iterator.
217 ///
218 /// \param pimpl The constructed internal implementation structure to use.
219 detail::directory_iterator::directory_iterator(std::shared_ptr< impl > pimpl) :
220     _pimpl(pimpl)
221 {
222 }
223 
224 
225 /// Destructor.
226 detail::directory_iterator::~directory_iterator(void)
227 {
228 }
229 
230 
231 /// Creates a new directory iterator for a directory.
232 ///
233 /// \return The directory iterator.  Note that the result may be invalid.
234 ///
235 /// \throw system_error If opening the directory or reading its first entry
236 ///     fails.
237 detail::directory_iterator
238 detail::directory_iterator::new_begin(const path& path)
239 {
240     return directory_iterator(std::shared_ptr< impl >(new impl(path)));
241 }
242 
243 
244 /// Creates a new invalid directory iterator.
245 ///
246 /// \return The invalid directory iterator.
247 detail::directory_iterator
248 detail::directory_iterator::new_end(void)
249 {
250     return directory_iterator(std::shared_ptr< impl >(new impl()));
251 }
252 
253 
254 /// Checks if two iterators are equal.
255 ///
256 /// We consider two iterators to be equal if both of them are invalid or,
257 /// otherwise, if they have the exact same internal representation (as given by
258 /// equality of the pimpl pointers).
259 ///
260 /// \param other The object to compare to.
261 ///
262 /// \return True if the two iterators are equal; false otherwise.
263 bool
264 detail::directory_iterator::operator==(const directory_iterator& other) const
265 {
266     return (_pimpl->_dirp == NULL && other._pimpl->_dirp == NULL) ||
267         _pimpl == other._pimpl;
268 }
269 
270 
271 /// Checks if two iterators are different.
272 ///
273 /// \param other The object to compare to.
274 ///
275 /// \return True if the two iterators are different; false otherwise.
276 bool
277 detail::directory_iterator::operator!=(const directory_iterator& other) const
278 {
279     return !(*this == other);
280 }
281 
282 
283 /// Moves the iterator one element forward.
284 ///
285 /// \return A reference to the iterator.
286 ///
287 /// \throw system_error If advancing the iterator fails.
288 detail::directory_iterator&
289 detail::directory_iterator::operator++(void)
290 {
291     _pimpl->next();
292     return *this;
293 }
294 
295 
296 /// Dereferences the iterator to its contents.
297 ///
298 /// \return A reference 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;
304 }
305 
306 
307 /// Dereferences the iterator to its contents.
308 ///
309 /// \return A pointer to the directory entry pointed to by the iterator.
310 const fs::directory_entry*
311 detail::directory_iterator::operator->(void) const
312 {
313     PRE(_pimpl->_entry.get() != NULL);
314     return _pimpl->_entry.get();
315 }
316 
317 
318 /// Internal implementation details for the directory.
319 struct utils::fs::directory::impl : utils::noncopyable {
320     /// Path to the directory to scan.
321     fs::path _path;
322 
323     /// Constructs a new directory.
324     ///
325     /// \param path_ Path to the directory to scan.
326     impl(const fs::path& path_) : _path(path_)
327     {
328     }
329 };
330 
331 
332 /// Constructs a new directory.
333 ///
334 /// \param path_ Path to the directory to scan.
335 fs::directory::directory(const path& path_) : _pimpl(new impl(path_))
336 {
337 }
338 
339 
340 /// Returns an iterator to start scanning the directory.
341 ///
342 /// \return An iterator on the directory.
343 ///
344 /// \throw system_error If the directory cannot be opened to obtain its first
345 ///     entry.
346 fs::directory::const_iterator
347 fs::directory::begin(void) const
348 {
349     return const_iterator::new_begin(_pimpl->_path);
350 }
351 
352 
353 /// Returns an invalid iterator to check for the end of an scan.
354 ///
355 /// \return An invalid iterator.
356 fs::directory::const_iterator
357 fs::directory::end(void) const
358 {
359     return const_iterator::new_end();
360 }
361