Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/kyua/utils/fs/directory.cpp
48081 views
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
/// 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.
137
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.
146
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.
166
~impl(void)
167
{
168
if (_dirp != NULL)
169
close();
170
}
171
172
/// Closes the directory and invalidates the iterator.
173
void
174
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
190
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.
213
detail::directory_iterator::directory_iterator(std::shared_ptr< impl > pimpl) :
214
_pimpl(pimpl)
215
{
216
}
217
218
219
/// Destructor.
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
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
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
258
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
271
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&
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&
294
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*
305
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.
320
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.
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
341
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
351
fs::directory::end(void) const
352
{
353
return const_iterator::new_end();
354
}
355
356