Skip to content

Commit

Permalink
Add an API for directory iteration (#323)
Browse files Browse the repository at this point in the history
Signed-off-by: Scott K Logan <logans@cottsay.net>
  • Loading branch information
cottsay authored Jan 15, 2021
1 parent 70599c8 commit 666241e
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 54 deletions.
48 changes: 48 additions & 0 deletions include/rcutils/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,54 @@ RCUTILS_PUBLIC
size_t
rcutils_get_file_size(const char * file_path);

/// An iterator used for enumerating directory contents
typedef struct rcutils_dir_iter_t
{
/// The name of the enumerated file or directory
const char * entry_name;
/// The allocator used internally by iteration functions
rcutils_allocator_t allocator;
/// The platform-specific iteration state
void * state;
} rcutils_dir_iter_t;

/// Begin iterating over the contents of the specified directory.
/*
* This function is used to list the files and directories that are contained in
* a specified directory. The structure returned by it must be deallocated using
* ::rcutils_dir_iter_end when the iteration is completed. The name of the
* enumerated entry is stored in the `entry_name` member of the returned object,
* and the first entry is already populated upon completion of this function. To
* populate the entry with the name of the next entry, use the
* ::rcutils_dir_iter_next function. Note that the "." and ".." entries are
* typically among the entries enumerated.
* \param[in] directory_path The directory path to iterate over the contents of.
* \param[in] allocator Allocator used to create the returned structure.
* \return An iterator object used to continue iterating directory contents
* \return NULL if an error occurred
*/
RCUTILS_PUBLIC
rcutils_dir_iter_t *
rcutils_dir_iter_start(const char * directory_path, const rcutils_allocator_t allocator);

/// Continue iterating over the contents of a directory.
/*
* \param[in] iter An iterator created by ::rcutils_dir_iter_start.
* \return `True` if another entry was found
* \return `False` if there are no more entries in the directory
*/
RCUTILS_PUBLIC
bool
rcutils_dir_iter_next(rcutils_dir_iter_t * iter);

/// Finish iterating over the contents of a directory.
/*
* \param[in] iter An iterator created by ::rcutils_dir_iter_start.
*/
RCUTILS_PUBLIC
void
rcutils_dir_iter_end(rcutils_dir_iter_t * iter);

#ifdef __cplusplus
}
#endif
Expand Down
176 changes: 122 additions & 54 deletions src/filesystem.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ extern "C"
# define RCUTILS_PATH_DELIMITER "/"
#endif // _WIN32

typedef struct rcutils_dir_iter_state_t
{
#ifdef _WIN32
HANDLE handle;
WIN32_FIND_DATA data;
#else
DIR * dir;
#endif
} rcutils_dir_iter_state_t;

bool
rcutils_get_cwd(char * buffer, size_t max_length)
{
Expand Down Expand Up @@ -337,6 +347,7 @@ rcutils_calculate_directory_size_with_recursion(
{
dir_list_t * dir_list = NULL;
rcutils_ret_t ret = RCUTILS_RET_OK;
rcutils_dir_iter_t * iter = NULL;

if (NULL == directory_path) {
RCUTILS_SAFE_FWRITE_TO_STDERR("directory_path is NULL !");
Expand Down Expand Up @@ -371,89 +382,146 @@ rcutils_calculate_directory_size_with_recursion(

*size = 0;

#ifdef _WIN32
HANDLE handle = INVALID_HANDLE_VALUE;
char * dir_path = NULL;

do {
dir_path = rcutils_join_path(dir_list->path, "*", allocator);
if (NULL == dir_path) {
RCUTILS_SAFE_FWRITE_TO_STDERR("Failed to duplicate directory path !\n");
ret = RCUTILS_RET_BAD_ALLOC;
goto fail;
}

WIN32_FIND_DATA data;
handle = FindFirstFile(dir_path, &data);
if (INVALID_HANDLE_VALUE == handle) {
RCUTILS_SAFE_FWRITE_TO_STDERR_WITH_FORMAT_STRING(
"Can't open directory %s. Error code: %lu\n", dir_list->path, GetLastError());
iter = rcutils_dir_iter_start(dir_list->path, allocator);
if (NULL == iter) {
ret = RCUTILS_RET_ERROR;
goto fail;
}

do {
ret = check_and_calculate_size(data.cFileName, size, max_depth, dir_list, allocator);
ret = check_and_calculate_size(iter->entry_name, size, max_depth, dir_list, allocator);
if (RCUTILS_RET_OK != ret) {
goto fail;
}
} while (FindNextFile(handle, &data));
} while (rcutils_dir_iter_next(iter));

FindClose(handle);
allocator.deallocate(dir_path, allocator.state);
rcutils_dir_iter_end(iter);

remove_first_dir_from_list(&dir_list, allocator);
} while (dir_list);

return ret;

fail:
if (NULL != dir_path) {
allocator.deallocate(dir_path, allocator.state);
rcutils_dir_iter_end(iter);
free_dir_list(dir_list, allocator);
return ret;
}

rcutils_dir_iter_t *
rcutils_dir_iter_start(const char * directory_path, const rcutils_allocator_t allocator)
{
RCUTILS_CHECK_ARGUMENT_FOR_NULL(directory_path, NULL);
RCUTILS_CHECK_ALLOCATOR_WITH_MSG(
&allocator, "allocator is invalid", return NULL);

rcutils_dir_iter_t * iter = (rcutils_dir_iter_t *)allocator.zero_allocate(
1, sizeof(rcutils_dir_iter_t), allocator.state);
if (NULL == iter) {
return NULL;
}
iter->allocator = allocator;

if (INVALID_HANDLE_VALUE != handle) {
FindClose(handle);
rcutils_dir_iter_state_t * state = (rcutils_dir_iter_state_t *)allocator.zero_allocate(
1, sizeof(rcutils_dir_iter_state_t), allocator.state);
if (NULL == state) {
RCUTILS_SET_ERROR_MSG(
"Failed to allocate memory.\n");
goto rcutils_dir_iter_start_fail;
}
free_dir_list(dir_list, allocator);
return ret;
#else
DIR * dir = NULL;
iter->state = (void *)state;

struct dirent * entry;
do {
dir = opendir(dir_list->path);
if (NULL == dir) {
RCUTILS_SAFE_FWRITE_TO_STDERR_WITH_FORMAT_STRING(
"Can't open directory %s. Error code: %d\n", dir_list->path, errno);
ret = RCUTILS_RET_ERROR;
goto fail;
#ifdef _WIN32
char * search_path = rcutils_join_path(directory_path, "*", allocator);
if (NULL == search_path) {
goto rcutils_dir_iter_start_fail;
}
state->handle = FindFirstFile(search_path, &state->data);
allocator.deallocate(search_path, allocator.state);
if (INVALID_HANDLE_VALUE == state->handle) {
DWORD error = GetLastError();
if (ERROR_FILE_NOT_FOUND != error || !rcutils_is_directory(directory_path)) {
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
"Can't open directory %s. Error code: %d\n", directory_path, error);
goto rcutils_dir_iter_start_fail;
}
} else {
iter->entry_name = state->data.cFileName;
}
#else
state->dir = opendir(directory_path);
if (NULL == state->dir) {
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
"Can't open directory %s. Error code: %d\n", directory_path, errno);
goto rcutils_dir_iter_start_fail;
}

errno = 0;
struct dirent * entry = readdir(state->dir);
if (NULL != entry) {
iter->entry_name = entry->d_name;
} else if (0 != errno) {
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
"Can't iterate directory %s. Error code: %d\n", directory_path, errno);
goto rcutils_dir_iter_start_fail;
}
#endif

// Scan in specified path
// If found directory, add to dir_list
// If found file, calculate file size
while (NULL != (entry = readdir(dir))) {
ret = check_and_calculate_size(entry->d_name, size, max_depth, dir_list, allocator);
if (RCUTILS_RET_OK != ret) {
goto fail;
}
}
return iter;

closedir(dir);
rcutils_dir_iter_start_fail:
rcutils_dir_iter_end(iter);
return NULL;
}

remove_first_dir_from_list(&dir_list, allocator);
} while (dir_list);
bool
rcutils_dir_iter_next(rcutils_dir_iter_t * iter)
{
RCUTILS_CHECK_ARGUMENT_FOR_NULL(iter, false);
rcutils_dir_iter_state_t * state = (rcutils_dir_iter_state_t *)iter->state;
RCUTILS_CHECK_FOR_NULL_WITH_MSG(state, "iter is invalid", false);

return ret;
#ifdef _WIN32
if (FindNextFile(state->handle, &state->data)) {
iter->entry_name = state->data.cFileName;
return true;
}
FindClose(state->handle);
#else
struct dirent * entry = readdir(state->dir);
if (NULL != entry) {
iter->entry_name = entry->d_name;
return true;
}
#endif

fail:
if (NULL != dir) {
closedir(dir);
iter->entry_name = NULL;
return false;
}

void
rcutils_dir_iter_end(rcutils_dir_iter_t * iter)
{
if (NULL == iter) {
return;
}
free_dir_list(dir_list, allocator);
return ret;

rcutils_allocator_t allocator = iter->allocator;
rcutils_dir_iter_state_t * state = (rcutils_dir_iter_state_t *)iter->state;
if (NULL != state) {
#ifdef _WIN32
FindClose(state->handle);
#else
if (NULL != state->dir) {
closedir(state->dir);
}
#endif

allocator.deallocate(state, allocator.state);
}

allocator.deallocate(iter, allocator.state);
}

size_t
Expand Down
60 changes: 60 additions & 0 deletions test/test_filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
// limitations under the License.

#include <gtest/gtest.h>
#include <set>
#include <string>

#include "rcutils/error_handling.h"
#include "rcutils/filesystem.h"
#include "rcutils/get_env.h"

Expand Down Expand Up @@ -456,6 +458,7 @@ TEST_F(TestFilesystemFixture, calculate_directory_size) {
fs.exhaust_file_descriptors();
ret = rcutils_calculate_directory_size(path, &size, g_allocator);
EXPECT_EQ(RCUTILS_RET_ERROR, ret);
rcutils_reset_error();
}
}

Expand Down Expand Up @@ -513,6 +516,7 @@ TEST_F(TestFilesystemFixture, calculate_directory_size_with_recursion) {
fs.exhaust_file_descriptors();
ret = rcutils_calculate_directory_size_with_recursion(path, 0, &size, g_allocator);
EXPECT_EQ(RCUTILS_RET_ERROR, ret);
rcutils_reset_error();
}
}

Expand Down Expand Up @@ -541,3 +545,59 @@ TEST_F(TestFilesystemFixture, calculate_file_size) {
g_allocator.deallocate(non_existing_path, g_allocator.state);
});
}

TEST_F(TestFilesystemFixture, directory_iterator) {
char * path =
rcutils_join_path(this->test_path, "dummy_folder", g_allocator);
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
{
g_allocator.deallocate(path, g_allocator.state);
});

std::set<std::string> expected {
".",
"..",
"dummy.dummy",
};

rcutils_dir_iter_t * iter = rcutils_dir_iter_start(path, g_allocator);
ASSERT_NE(nullptr, iter);
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
{
rcutils_dir_iter_end(iter);
});

do {
if (1u != expected.erase(iter->entry_name)) {
ADD_FAILURE() << "Unexpected entry '" << iter->entry_name << "' was enumerated";
}
} while (rcutils_dir_iter_next(iter));

for (std::string missing : expected) {
ADD_FAILURE() << "Expected entry '" << missing << "' was not enumerated";
}
}

TEST_F(TestFilesystemFixture, directory_iterator_non_existing) {
char * path =
rcutils_join_path(this->test_path, "non_existing_folder", g_allocator);
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
{
g_allocator.deallocate(path, g_allocator.state);
});

EXPECT_EQ(nullptr, rcutils_dir_iter_start(path, g_allocator));
rcutils_reset_error();
}

TEST_F(TestFilesystemFixture, directory_iterator_on_file) {
char * path =
rcutils_join_path(this->test_path, "dummy_readable_file.txt", g_allocator);
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
{
g_allocator.deallocate(path, g_allocator.state);
});

EXPECT_EQ(nullptr, rcutils_dir_iter_start(path, g_allocator));
rcutils_reset_error();
}

0 comments on commit 666241e

Please sign in to comment.