Skip to content

Commit 666241e

Browse files
authored
Add an API for directory iteration (ros2#323)
Signed-off-by: Scott K Logan <[email protected]>
1 parent 70599c8 commit 666241e

File tree

3 files changed

+230
-54
lines changed

3 files changed

+230
-54
lines changed

include/rcutils/filesystem.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,54 @@ RCUTILS_PUBLIC
243243
size_t
244244
rcutils_get_file_size(const char * file_path);
245245

246+
/// An iterator used for enumerating directory contents
247+
typedef struct rcutils_dir_iter_t
248+
{
249+
/// The name of the enumerated file or directory
250+
const char * entry_name;
251+
/// The allocator used internally by iteration functions
252+
rcutils_allocator_t allocator;
253+
/// The platform-specific iteration state
254+
void * state;
255+
} rcutils_dir_iter_t;
256+
257+
/// Begin iterating over the contents of the specified directory.
258+
/*
259+
* This function is used to list the files and directories that are contained in
260+
* a specified directory. The structure returned by it must be deallocated using
261+
* ::rcutils_dir_iter_end when the iteration is completed. The name of the
262+
* enumerated entry is stored in the `entry_name` member of the returned object,
263+
* and the first entry is already populated upon completion of this function. To
264+
* populate the entry with the name of the next entry, use the
265+
* ::rcutils_dir_iter_next function. Note that the "." and ".." entries are
266+
* typically among the entries enumerated.
267+
* \param[in] directory_path The directory path to iterate over the contents of.
268+
* \param[in] allocator Allocator used to create the returned structure.
269+
* \return An iterator object used to continue iterating directory contents
270+
* \return NULL if an error occurred
271+
*/
272+
RCUTILS_PUBLIC
273+
rcutils_dir_iter_t *
274+
rcutils_dir_iter_start(const char * directory_path, const rcutils_allocator_t allocator);
275+
276+
/// Continue iterating over the contents of a directory.
277+
/*
278+
* \param[in] iter An iterator created by ::rcutils_dir_iter_start.
279+
* \return `True` if another entry was found
280+
* \return `False` if there are no more entries in the directory
281+
*/
282+
RCUTILS_PUBLIC
283+
bool
284+
rcutils_dir_iter_next(rcutils_dir_iter_t * iter);
285+
286+
/// Finish iterating over the contents of a directory.
287+
/*
288+
* \param[in] iter An iterator created by ::rcutils_dir_iter_start.
289+
*/
290+
RCUTILS_PUBLIC
291+
void
292+
rcutils_dir_iter_end(rcutils_dir_iter_t * iter);
293+
246294
#ifdef __cplusplus
247295
}
248296
#endif

src/filesystem.c

Lines changed: 122 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ extern "C"
5454
# define RCUTILS_PATH_DELIMITER "/"
5555
#endif // _WIN32
5656

57+
typedef struct rcutils_dir_iter_state_t
58+
{
59+
#ifdef _WIN32
60+
HANDLE handle;
61+
WIN32_FIND_DATA data;
62+
#else
63+
DIR * dir;
64+
#endif
65+
} rcutils_dir_iter_state_t;
66+
5767
bool
5868
rcutils_get_cwd(char * buffer, size_t max_length)
5969
{
@@ -337,6 +347,7 @@ rcutils_calculate_directory_size_with_recursion(
337347
{
338348
dir_list_t * dir_list = NULL;
339349
rcutils_ret_t ret = RCUTILS_RET_OK;
350+
rcutils_dir_iter_t * iter = NULL;
340351

341352
if (NULL == directory_path) {
342353
RCUTILS_SAFE_FWRITE_TO_STDERR("directory_path is NULL !");
@@ -371,89 +382,146 @@ rcutils_calculate_directory_size_with_recursion(
371382

372383
*size = 0;
373384

374-
#ifdef _WIN32
375-
HANDLE handle = INVALID_HANDLE_VALUE;
376-
char * dir_path = NULL;
377-
378385
do {
379-
dir_path = rcutils_join_path(dir_list->path, "*", allocator);
380-
if (NULL == dir_path) {
381-
RCUTILS_SAFE_FWRITE_TO_STDERR("Failed to duplicate directory path !\n");
382-
ret = RCUTILS_RET_BAD_ALLOC;
383-
goto fail;
384-
}
385-
386-
WIN32_FIND_DATA data;
387-
handle = FindFirstFile(dir_path, &data);
388-
if (INVALID_HANDLE_VALUE == handle) {
389-
RCUTILS_SAFE_FWRITE_TO_STDERR_WITH_FORMAT_STRING(
390-
"Can't open directory %s. Error code: %lu\n", dir_list->path, GetLastError());
386+
iter = rcutils_dir_iter_start(dir_list->path, allocator);
387+
if (NULL == iter) {
391388
ret = RCUTILS_RET_ERROR;
392389
goto fail;
393390
}
394391

395392
do {
396-
ret = check_and_calculate_size(data.cFileName, size, max_depth, dir_list, allocator);
393+
ret = check_and_calculate_size(iter->entry_name, size, max_depth, dir_list, allocator);
397394
if (RCUTILS_RET_OK != ret) {
398395
goto fail;
399396
}
400-
} while (FindNextFile(handle, &data));
397+
} while (rcutils_dir_iter_next(iter));
401398

402-
FindClose(handle);
403-
allocator.deallocate(dir_path, allocator.state);
399+
rcutils_dir_iter_end(iter);
404400

405401
remove_first_dir_from_list(&dir_list, allocator);
406402
} while (dir_list);
407403

408404
return ret;
409405

410406
fail:
411-
if (NULL != dir_path) {
412-
allocator.deallocate(dir_path, allocator.state);
407+
rcutils_dir_iter_end(iter);
408+
free_dir_list(dir_list, allocator);
409+
return ret;
410+
}
411+
412+
rcutils_dir_iter_t *
413+
rcutils_dir_iter_start(const char * directory_path, const rcutils_allocator_t allocator)
414+
{
415+
RCUTILS_CHECK_ARGUMENT_FOR_NULL(directory_path, NULL);
416+
RCUTILS_CHECK_ALLOCATOR_WITH_MSG(
417+
&allocator, "allocator is invalid", return NULL);
418+
419+
rcutils_dir_iter_t * iter = (rcutils_dir_iter_t *)allocator.zero_allocate(
420+
1, sizeof(rcutils_dir_iter_t), allocator.state);
421+
if (NULL == iter) {
422+
return NULL;
413423
}
424+
iter->allocator = allocator;
414425

415-
if (INVALID_HANDLE_VALUE != handle) {
416-
FindClose(handle);
426+
rcutils_dir_iter_state_t * state = (rcutils_dir_iter_state_t *)allocator.zero_allocate(
427+
1, sizeof(rcutils_dir_iter_state_t), allocator.state);
428+
if (NULL == state) {
429+
RCUTILS_SET_ERROR_MSG(
430+
"Failed to allocate memory.\n");
431+
goto rcutils_dir_iter_start_fail;
417432
}
418-
free_dir_list(dir_list, allocator);
419-
return ret;
420-
#else
421-
DIR * dir = NULL;
433+
iter->state = (void *)state;
422434

423-
struct dirent * entry;
424-
do {
425-
dir = opendir(dir_list->path);
426-
if (NULL == dir) {
427-
RCUTILS_SAFE_FWRITE_TO_STDERR_WITH_FORMAT_STRING(
428-
"Can't open directory %s. Error code: %d\n", dir_list->path, errno);
429-
ret = RCUTILS_RET_ERROR;
430-
goto fail;
435+
#ifdef _WIN32
436+
char * search_path = rcutils_join_path(directory_path, "*", allocator);
437+
if (NULL == search_path) {
438+
goto rcutils_dir_iter_start_fail;
439+
}
440+
state->handle = FindFirstFile(search_path, &state->data);
441+
allocator.deallocate(search_path, allocator.state);
442+
if (INVALID_HANDLE_VALUE == state->handle) {
443+
DWORD error = GetLastError();
444+
if (ERROR_FILE_NOT_FOUND != error || !rcutils_is_directory(directory_path)) {
445+
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
446+
"Can't open directory %s. Error code: %d\n", directory_path, error);
447+
goto rcutils_dir_iter_start_fail;
431448
}
449+
} else {
450+
iter->entry_name = state->data.cFileName;
451+
}
452+
#else
453+
state->dir = opendir(directory_path);
454+
if (NULL == state->dir) {
455+
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
456+
"Can't open directory %s. Error code: %d\n", directory_path, errno);
457+
goto rcutils_dir_iter_start_fail;
458+
}
459+
460+
errno = 0;
461+
struct dirent * entry = readdir(state->dir);
462+
if (NULL != entry) {
463+
iter->entry_name = entry->d_name;
464+
} else if (0 != errno) {
465+
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
466+
"Can't iterate directory %s. Error code: %d\n", directory_path, errno);
467+
goto rcutils_dir_iter_start_fail;
468+
}
469+
#endif
432470

433-
// Scan in specified path
434-
// If found directory, add to dir_list
435-
// If found file, calculate file size
436-
while (NULL != (entry = readdir(dir))) {
437-
ret = check_and_calculate_size(entry->d_name, size, max_depth, dir_list, allocator);
438-
if (RCUTILS_RET_OK != ret) {
439-
goto fail;
440-
}
441-
}
471+
return iter;
442472

443-
closedir(dir);
473+
rcutils_dir_iter_start_fail:
474+
rcutils_dir_iter_end(iter);
475+
return NULL;
476+
}
444477

445-
remove_first_dir_from_list(&dir_list, allocator);
446-
} while (dir_list);
478+
bool
479+
rcutils_dir_iter_next(rcutils_dir_iter_t * iter)
480+
{
481+
RCUTILS_CHECK_ARGUMENT_FOR_NULL(iter, false);
482+
rcutils_dir_iter_state_t * state = (rcutils_dir_iter_state_t *)iter->state;
483+
RCUTILS_CHECK_FOR_NULL_WITH_MSG(state, "iter is invalid", false);
447484

448-
return ret;
485+
#ifdef _WIN32
486+
if (FindNextFile(state->handle, &state->data)) {
487+
iter->entry_name = state->data.cFileName;
488+
return true;
489+
}
490+
FindClose(state->handle);
491+
#else
492+
struct dirent * entry = readdir(state->dir);
493+
if (NULL != entry) {
494+
iter->entry_name = entry->d_name;
495+
return true;
496+
}
497+
#endif
449498

450-
fail:
451-
if (NULL != dir) {
452-
closedir(dir);
499+
iter->entry_name = NULL;
500+
return false;
501+
}
502+
503+
void
504+
rcutils_dir_iter_end(rcutils_dir_iter_t * iter)
505+
{
506+
if (NULL == iter) {
507+
return;
453508
}
454-
free_dir_list(dir_list, allocator);
455-
return ret;
509+
510+
rcutils_allocator_t allocator = iter->allocator;
511+
rcutils_dir_iter_state_t * state = (rcutils_dir_iter_state_t *)iter->state;
512+
if (NULL != state) {
513+
#ifdef _WIN32
514+
FindClose(state->handle);
515+
#else
516+
if (NULL != state->dir) {
517+
closedir(state->dir);
518+
}
456519
#endif
520+
521+
allocator.deallocate(state, allocator.state);
522+
}
523+
524+
allocator.deallocate(iter, allocator.state);
457525
}
458526

459527
size_t

test/test_filesystem.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
// limitations under the License.
1414

1515
#include <gtest/gtest.h>
16+
#include <set>
1617
#include <string>
1718

19+
#include "rcutils/error_handling.h"
1820
#include "rcutils/filesystem.h"
1921
#include "rcutils/get_env.h"
2022

@@ -456,6 +458,7 @@ TEST_F(TestFilesystemFixture, calculate_directory_size) {
456458
fs.exhaust_file_descriptors();
457459
ret = rcutils_calculate_directory_size(path, &size, g_allocator);
458460
EXPECT_EQ(RCUTILS_RET_ERROR, ret);
461+
rcutils_reset_error();
459462
}
460463
}
461464

@@ -513,6 +516,7 @@ TEST_F(TestFilesystemFixture, calculate_directory_size_with_recursion) {
513516
fs.exhaust_file_descriptors();
514517
ret = rcutils_calculate_directory_size_with_recursion(path, 0, &size, g_allocator);
515518
EXPECT_EQ(RCUTILS_RET_ERROR, ret);
519+
rcutils_reset_error();
516520
}
517521
}
518522

@@ -541,3 +545,59 @@ TEST_F(TestFilesystemFixture, calculate_file_size) {
541545
g_allocator.deallocate(non_existing_path, g_allocator.state);
542546
});
543547
}
548+
549+
TEST_F(TestFilesystemFixture, directory_iterator) {
550+
char * path =
551+
rcutils_join_path(this->test_path, "dummy_folder", g_allocator);
552+
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
553+
{
554+
g_allocator.deallocate(path, g_allocator.state);
555+
});
556+
557+
std::set<std::string> expected {
558+
".",
559+
"..",
560+
"dummy.dummy",
561+
};
562+
563+
rcutils_dir_iter_t * iter = rcutils_dir_iter_start(path, g_allocator);
564+
ASSERT_NE(nullptr, iter);
565+
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
566+
{
567+
rcutils_dir_iter_end(iter);
568+
});
569+
570+
do {
571+
if (1u != expected.erase(iter->entry_name)) {
572+
ADD_FAILURE() << "Unexpected entry '" << iter->entry_name << "' was enumerated";
573+
}
574+
} while (rcutils_dir_iter_next(iter));
575+
576+
for (std::string missing : expected) {
577+
ADD_FAILURE() << "Expected entry '" << missing << "' was not enumerated";
578+
}
579+
}
580+
581+
TEST_F(TestFilesystemFixture, directory_iterator_non_existing) {
582+
char * path =
583+
rcutils_join_path(this->test_path, "non_existing_folder", g_allocator);
584+
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
585+
{
586+
g_allocator.deallocate(path, g_allocator.state);
587+
});
588+
589+
EXPECT_EQ(nullptr, rcutils_dir_iter_start(path, g_allocator));
590+
rcutils_reset_error();
591+
}
592+
593+
TEST_F(TestFilesystemFixture, directory_iterator_on_file) {
594+
char * path =
595+
rcutils_join_path(this->test_path, "dummy_readable_file.txt", g_allocator);
596+
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
597+
{
598+
g_allocator.deallocate(path, g_allocator.state);
599+
});
600+
601+
EXPECT_EQ(nullptr, rcutils_dir_iter_start(path, g_allocator));
602+
rcutils_reset_error();
603+
}

0 commit comments

Comments
 (0)