xref: /freebsd/contrib/kyua/utils/fs/directory.cpp (revision 65bae451c23b8d61b2433259d8e707250660eeff)
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.
directory_entry(const std::string & name_)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
operator ==(const directory_entry & other) const66 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
operator !=(const directory_entry & other) const78 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
operator <(const directory_entry & other) const90 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&
operator <<(std::ostream & output,const directory_entry & entry)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     /// Custom representation of the directory entry.
131     ///
132     /// We must keep this as a pointer so that we can support the common
133     /// operators (* and ->) over iterators.
134     std::unique_ptr< directory_entry > _entry;
135 
136     /// Constructs an iterator pointing to the "end" of the directory.
implutils::fs::detail::directory_iterator::impl137     impl(void) : _path("invalid-directory-entry"), _dirp(NULL)
138     {
139     }
140 
141     /// Constructs a new iterator to start scanning a directory.
142     ///
143     /// \param path The directory that will be scanned.
144     ///
145     /// \throw system_error If there is a problem opening the directory.
implutils::fs::detail::directory_iterator::impl146     explicit impl(const path& path) : _path(path)
147     {
148         DIR* dirp = ::opendir(_path.c_str());
149         if (dirp == NULL) {
150             const int original_errno = errno;
151             throw fs::system_error(F("opendir(%s) failed") % _path,
152                                    original_errno);
153         }
154         _dirp = dirp;
155 
156         // Initialize our first directory entry.  Note that this may actually
157         // close the directory we just opened if the directory happens to be
158         // empty -- but directories are never empty because they at least have
159         // '.' and '..' entries.
160         next();
161     }
162 
163     /// Destructor.
164     ///
165     /// This closes the directory if still open.
~implutils::fs::detail::directory_iterator::impl166     ~impl(void)
167     {
168         if (_dirp != NULL)
169             close();
170     }
171 
172     /// Closes the directory and invalidates the iterator.
173     void
closeutils::fs::detail::directory_iterator::impl174     close(void)
175     {
176         PRE(_dirp != NULL);
177         if (::closedir(_dirp) == -1) {
178             UNREACHABLE_MSG("Invalid dirp provided to closedir(3)");
179         }
180         _dirp = NULL;
181     }
182 
183     /// Advances the directory entry to the next one.
184     ///
185     /// It is possible to use this function on a new directory_entry object to
186     /// initialize the first entry.
187     ///
188     /// \throw system_error If the call to readdir fails.
189     void
nextutils::fs::detail::directory_iterator::impl190     next(void)
191     {
192         ::dirent* result;
193 
194         errno = 0;
195         if ((result = ::readdir(_dirp)) == NULL && errno != 0) {
196             const int original_errno = errno;
197             throw fs::system_error(F("readdir(%s) failed") % _path,
198                                    original_errno);
199         }
200         if (result == NULL) {
201             _entry.reset();
202             close();
203         } else {
204             _entry.reset(new directory_entry(result->d_name));
205         }
206     }
207 };
208 
209 
210 /// Constructs a new directory iterator.
211 ///
212 /// \param pimpl The constructed internal implementation structure to use.
directory_iterator(std::shared_ptr<impl> pimpl)213 detail::directory_iterator::directory_iterator(std::shared_ptr< impl > pimpl) :
214     _pimpl(pimpl)
215 {
216 }
217 
218 
219 /// Destructor.
~directory_iterator(void)220 detail::directory_iterator::~directory_iterator(void)
221 {
222 }
223 
224 
225 /// Creates a new directory iterator for a directory.
226 ///
227 /// \return The directory iterator.  Note that the result may be invalid.
228 ///
229 /// \throw system_error If opening the directory or reading its first entry
230 ///     fails.
231 detail::directory_iterator
new_begin(const path & path)232 detail::directory_iterator::new_begin(const path& path)
233 {
234     return directory_iterator(std::shared_ptr< impl >(new impl(path)));
235 }
236 
237 
238 /// Creates a new invalid directory iterator.
239 ///
240 /// \return The invalid directory iterator.
241 detail::directory_iterator
new_end(void)242 detail::directory_iterator::new_end(void)
243 {
244     return directory_iterator(std::shared_ptr< impl >(new impl()));
245 }
246 
247 
248 /// Checks if two iterators are equal.
249 ///
250 /// We consider two iterators to be equal if both of them are invalid or,
251 /// otherwise, if they have the exact same internal representation (as given by
252 /// equality of the pimpl pointers).
253 ///
254 /// \param other The object to compare to.
255 ///
256 /// \return True if the two iterators are equal; false otherwise.
257 bool
operator ==(const directory_iterator & other) const258 detail::directory_iterator::operator==(const directory_iterator& other) const
259 {
260     return (_pimpl->_dirp == NULL && other._pimpl->_dirp == NULL) ||
261         _pimpl == other._pimpl;
262 }
263 
264 
265 /// Checks if two iterators are different.
266 ///
267 /// \param other The object to compare to.
268 ///
269 /// \return True if the two iterators are different; false otherwise.
270 bool
operator !=(const directory_iterator & other) const271 detail::directory_iterator::operator!=(const directory_iterator& other) const
272 {
273     return !(*this == other);
274 }
275 
276 
277 /// Moves the iterator one element forward.
278 ///
279 /// \return A reference to the iterator.
280 ///
281 /// \throw system_error If advancing the iterator fails.
282 detail::directory_iterator&
operator ++(void)283 detail::directory_iterator::operator++(void)
284 {
285     _pimpl->next();
286     return *this;
287 }
288 
289 
290 /// Dereferences the iterator to its contents.
291 ///
292 /// \return A reference to the directory entry pointed to by the iterator.
293 const fs::directory_entry&
operator *(void) const294 detail::directory_iterator::operator*(void) const
295 {
296     PRE(_pimpl->_entry.get() != NULL);
297     return *_pimpl->_entry;
298 }
299 
300 
301 /// Dereferences the iterator to its contents.
302 ///
303 /// \return A pointer to the directory entry pointed to by the iterator.
304 const fs::directory_entry*
operator ->(void) const305 detail::directory_iterator::operator->(void) const
306 {
307     PRE(_pimpl->_entry.get() != NULL);
308     return _pimpl->_entry.get();
309 }
310 
311 
312 /// Internal implementation details for the directory.
313 struct utils::fs::directory::impl : utils::noncopyable {
314     /// Path to the directory to scan.
315     fs::path _path;
316 
317     /// Constructs a new directory.
318     ///
319     /// \param path_ Path to the directory to scan.
implutils::fs::directory::impl320     impl(const fs::path& path_) : _path(path_)
321     {
322     }
323 };
324 
325 
326 /// Constructs a new directory.
327 ///
328 /// \param path_ Path to the directory to scan.
directory(const path & path_)329 fs::directory::directory(const path& path_) : _pimpl(new impl(path_))
330 {
331 }
332 
333 
334 /// Returns an iterator to start scanning the directory.
335 ///
336 /// \return An iterator on the directory.
337 ///
338 /// \throw system_error If the directory cannot be opened to obtain its first
339 ///     entry.
340 fs::directory::const_iterator
begin(void) const341 fs::directory::begin(void) const
342 {
343     return const_iterator::new_begin(_pimpl->_path);
344 }
345 
346 
347 /// Returns an invalid iterator to check for the end of an scan.
348 ///
349 /// \return An invalid iterator.
350 fs::directory::const_iterator
end(void) const351 fs::directory::end(void) const
352 {
353     return const_iterator::new_end();
354 }
355