int aws_directory_traverse()

in source/posix/file.c [124:220]


int aws_directory_traverse(
    struct aws_allocator *allocator,
    const struct aws_string *path,
    bool recursive,
    aws_on_directory_entry *on_entry,
    void *user_data) {
    DIR *dir = opendir(aws_string_c_str(path));

    if (!dir) {
        return s_parse_and_raise_error(errno);
    }

    struct aws_byte_cursor current_path = aws_byte_cursor_from_string(path);
    if (current_path.ptr[current_path.len - 1] == AWS_PATH_DELIM) {
        current_path.len -= 1;
    }

    struct dirent *dirent = NULL;
    int ret_val = AWS_ERROR_SUCCESS;

    errno = 0;
    while (!ret_val && (dirent = readdir(dir)) != NULL) {
        /* note: dirent->name_len is only defined on the BSDs, but not linux. It's not in the
         * required posix spec. So we use dirent->d_name as a c string here. */
        struct aws_byte_cursor name_component = aws_byte_cursor_from_c_str(dirent->d_name);

        if (aws_byte_cursor_eq_c_str(&name_component, "..") || aws_byte_cursor_eq_c_str(&name_component, ".")) {
            continue;
        }

        struct aws_byte_buf relative_path;
        aws_byte_buf_init_copy_from_cursor(&relative_path, allocator, current_path);
        aws_byte_buf_append_byte_dynamic(&relative_path, AWS_PATH_DELIM);
        aws_byte_buf_append_dynamic(&relative_path, &name_component);
        aws_byte_buf_append_byte_dynamic(&relative_path, 0);
        relative_path.len -= 1;

        struct aws_directory_entry entry;
        AWS_ZERO_STRUCT(entry);

        struct stat dir_info;
        if (!lstat((const char *)relative_path.buffer, &dir_info)) {
            if (S_ISDIR(dir_info.st_mode)) {
                entry.file_type |= AWS_FILE_TYPE_DIRECTORY;
            }
            if (S_ISLNK(dir_info.st_mode)) {
                entry.file_type |= AWS_FILE_TYPE_SYM_LINK;
            }
            if (S_ISREG(dir_info.st_mode)) {
                entry.file_type |= AWS_FILE_TYPE_FILE;
                entry.file_size = dir_info.st_size;
            }

            if (!entry.file_type) {
                AWS_ASSERT("Unknown file type encountered");
            }

            entry.relative_path = aws_byte_cursor_from_buf(&relative_path);
            const char *full_path = realpath((const char *)relative_path.buffer, NULL);

            if (full_path) {
                entry.path = aws_byte_cursor_from_c_str(full_path);
            }

            if (recursive && entry.file_type & AWS_FILE_TYPE_DIRECTORY) {
                struct aws_string *rel_path_str = aws_string_new_from_cursor(allocator, &entry.relative_path);
                ret_val = aws_directory_traverse(allocator, rel_path_str, recursive, on_entry, user_data);
                aws_string_destroy(rel_path_str);
            }

            /* post order traversal, if a node below us ended the traversal, don't call the visitor again. */
            if (ret_val && aws_last_error() == AWS_ERROR_OPERATION_INTERUPTED) {
                goto cleanup;
            }

            if (!on_entry(&entry, user_data)) {
                ret_val = aws_raise_error(AWS_ERROR_OPERATION_INTERUPTED);
                goto cleanup;
            }

            if (ret_val) {
                goto cleanup;
            }

        cleanup:
            /* per https://man7.org/linux/man-pages/man3/realpath.3.html, realpath must be freed, if NULL was passed
             * to the second argument. */
            if (full_path) {
                free((void *)full_path);
            }
            aws_byte_buf_clean_up(&relative_path);
        }
    }

    closedir(dir);
    return ret_val;
}