From aa02f917ee3c3a6d57c006831b892f48b3603111 Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Sat, 24 Jun 2023 15:58:06 -0500 Subject: [PATCH] Feature: Add f_file_select() for select(). This is more of a wrapper around select() (unlike f_file_poll() which is a bit more than a wrapper around poll()). The arguments would be more difficult than worth it to simplify and normalize in the FLL typedef style. --- level_0/f_file/c/file.c | 19 ++ level_0/f_file/c/file.h | 45 +++- level_0/f_file/data/build/settings-mocks | 1 + level_0/f_file/data/build/settings-tests | 2 +- level_0/f_file/tests/unit/c/mock-file.c | 13 ++ level_0/f_file/tests/unit/c/mock-file.h | 1 + level_0/f_file/tests/unit/c/test-file-select.c | 293 +++++++++++++++++++++++++ level_0/f_file/tests/unit/c/test-file-select.h | 34 +++ level_0/f_file/tests/unit/c/test-file.c | 5 + level_0/f_file/tests/unit/c/test-file.h | 1 + 10 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 level_0/f_file/tests/unit/c/test-file-select.c create mode 100644 level_0/f_file/tests/unit/c/test-file-select.h diff --git a/level_0/f_file/c/file.c b/level_0/f_file/c/file.c index dd5d4d2..c6cbd8c 100644 --- a/level_0/f_file/c/file.c +++ b/level_0/f_file/c/file.c @@ -1896,6 +1896,25 @@ extern "C" { } #endif // _di_f_file_seek_ +#ifndef _di_f_file_select_ + f_status_t f_file_select(const int highest_plus_one, fd_set * const read, fd_set * const write, fd_set * const except, struct timeval * const timeout) { + + if (!highest_plus_one) return F_data_not; + if (!read && !write && !except) return F_data_not; + + if (select(highest_plus_one, read, write, except, timeout) == -1) { + if (errno == EBADF) return F_status_set_error(F_file_descriptor); + if (errno == EINTR) return F_status_set_error(F_interrupt); + if (errno == EINVAL) return F_status_set_error(F_parameter); + if (errno == ENOMEM) return F_status_set_error(F_memory_not); + + return F_status_set_error(F_failure); + } + + return F_none; + } +#endif // _di_f_file_select_ + #ifndef _di_f_file_size_ f_status_t f_file_size(const f_string_static_t path, const bool dereference, off_t * const size) { #ifndef _di_level_0_parameter_checking_ diff --git a/level_0/f_file/c/file.h b/level_0/f_file/c/file.h index 261fc5e..d913aef 100644 --- a/level_0/f_file/c/file.h +++ b/level_0/f_file/c/file.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -1950,13 +1951,55 @@ extern "C" { * F_parameter (with error bit) if a parameter is invalid. * F_failure (with error bit) on any other error. * - * @see lseek + * @see lseek() */ #ifndef _di_f_file_seek_ extern f_status_t f_file_seek(const f_file_t file, const int whence, const off_t offset, off_t * const seeked); #endif // _di_f_file_seek_ /** + * Monitor one or more file descriptors. + * + * @todo Probably should implement a pselect(). + * + * Warning: Some libc implementations, such as GLIBC, use an upper limit of 1023 file descriptors. + * The linux kernel general does not have such a limit. + * To more safely handle more than 1023 file desciptors, instead consider f_file_poll(); + * + * @param highest + * The value of the highest file descriptor between all provided sets (read, write, and except) plus one. + * The caller must remember that one must be added to the highest file descriptor as per requirements by select(). + * This cannot be 0. + * @param read + * (optional) The set of file descriptors for descriptors that become available for reading. + * Set to NULL to not use. + * @param write + * (optional) The set of file descriptors for descriptors that become available for writing. + * Set to NULL to not use. + * @param except + * (optional) The set of file descriptors for descriptors that become available for any error conditions. + * Set to NULL to not use. + * @param timeout + * (optional) + * Set to NULL to not use. + * + * @return + * F_none on success. + * F_data_not if all three read, write, and except are NULL (having at least one is required) or when highest_plus_one is 0. + * + * F_file_descriptor (with error bit) if the file descriptor is invalid. + * F_interrupt (with error bit) when program received an interrupt signal, halting operation. + * F_memory_not (with error bit) if out of memory. + * F_parameter (with error bit) if a parameter is invalid. + * F_failure (with error bit) on any other error. + * + * @see select() + */ +#ifndef _di_f_file_select_ + extern f_status_t f_file_select(const int highest_plus_one, fd_set * const read, fd_set * const write, fd_set * const except, struct timeval * const timeout); +#endif // _di_f_file_select_ + +/** * Read the size of file. * * @param path diff --git a/level_0/f_file/data/build/settings-mocks b/level_0/f_file/data/build/settings-mocks index aeae42c..e42f3a4 100644 --- a/level_0/f_file/data/build/settings-mocks +++ b/level_0/f_file/data/build/settings-mocks @@ -106,6 +106,7 @@ flags -Wl,--wrap=readlinkat flags -Wl,--wrap=rename flags -Wl,--wrap=renameat flags -Wl,--wrap=renameat2 +flags -Wl,--wrap=select flags -Wl,--wrap=stat flags -Wl,--wrap=symlink flags -Wl,--wrap=symlinkat diff --git a/level_0/f_file/data/build/settings-tests b/level_0/f_file/data/build/settings-tests index b604c83..58503af 100644 --- a/level_0/f_file/data/build/settings-tests +++ b/level_0/f_file/data/build/settings-tests @@ -25,7 +25,7 @@ build_language c build_libraries -lc -lcmocka build_libraries-individual -lf_memory -lf_string -lf_file -build_sources_program test-file-access.c test-file-access_at.c test-file-clone.c test-file-close.c test-file-copy.c test-file-create.c test-file-create_at.c test-file-create_device.c test-file-create_device_at.c test-file-create_fifo.c test-file-create_fifo_at.c test-file-create_node.c test-file-create_node_at.c test-file-descriptor.c test-file-exists.c test-file-exists_at.c test-file-flush.c test-file-group_read.c test-file-is.c test-file-is_at.c test-file-is_stat.c test-file-link.c test-file-link_at.c test-file-link_hard.c test-file-link_hard_at.c test-file-link_read.c test-file-link_read_at.c test-file-manipulate.c test-file-mode_determine.c test-file-mode_from_string.c test-file-mode_read.c test-file-mode_read_at.c test-file-mode_set.c test-file-mode_set_at.c test-file-mode_to_mode.c test-file-name_base.c test-file-name_directory.c test-file-open.c test-file-open_at.c test-file-owner_read.c test-file-read.c test-file-read_block.c test-file-read_until.c test-file-remove.c test-file-remove_at.c test-file-rename.c test-file-rename_at.c test-file-role_change.c test-file-role_change_at.c test-file-poll.c test-file-seek.c test-file-size.c test-file-size_at.c test-file-size_by_id.c test-file-stat.c test-file-stat_at.c test-file-stat_by_id.c test-file-stream_close.c test-file-stream_open_descriptor.c test-file-stream_open.c test-file-stream_read.c test-file-stream_read_block.c test-file-stream_read_until.c test-file-stream_reopen.c test-file-stream_write.c test-file-stream_write_block.c test-file-stream_write_until.c test-file-stream_write_range.c test-file-touch.c test-file-touch_at.c test-file-type.c test-file-type_at.c test-file-umask_get.c test-file-umask_set.c test-file-write.c test-file-write_block.c test-file-write_until.c test-file-write_range.c +build_sources_program test-file-access.c test-file-access_at.c test-file-clone.c test-file-close.c test-file-copy.c test-file-create.c test-file-create_at.c test-file-create_device.c test-file-create_device_at.c test-file-create_fifo.c test-file-create_fifo_at.c test-file-create_node.c test-file-create_node_at.c test-file-descriptor.c test-file-exists.c test-file-exists_at.c test-file-flush.c test-file-group_read.c test-file-is.c test-file-is_at.c test-file-is_stat.c test-file-link.c test-file-link_at.c test-file-link_hard.c test-file-link_hard_at.c test-file-link_read.c test-file-link_read_at.c test-file-manipulate.c test-file-mode_determine.c test-file-mode_from_string.c test-file-mode_read.c test-file-mode_read_at.c test-file-mode_set.c test-file-mode_set_at.c test-file-mode_to_mode.c test-file-name_base.c test-file-name_directory.c test-file-open.c test-file-open_at.c test-file-owner_read.c test-file-read.c test-file-read_block.c test-file-read_until.c test-file-remove.c test-file-remove_at.c test-file-rename.c test-file-rename_at.c test-file-role_change.c test-file-role_change_at.c test-file-poll.c test-file-seek.c test-file-select.c test-file-size.c test-file-size_at.c test-file-size_by_id.c test-file-stat.c test-file-stat_at.c test-file-stat_by_id.c test-file-stream_close.c test-file-stream_open_descriptor.c test-file-stream_open.c test-file-stream_read.c test-file-stream_read_block.c test-file-stream_read_until.c test-file-stream_reopen.c test-file-stream_write.c test-file-stream_write_block.c test-file-stream_write_until.c test-file-stream_write_range.c test-file-touch.c test-file-touch_at.c test-file-type.c test-file-type_at.c test-file-umask_get.c test-file-umask_set.c test-file-write.c test-file-write_block.c test-file-write_until.c test-file-write_range.c build_sources_program test-file.c build_script no diff --git a/level_0/f_file/tests/unit/c/mock-file.c b/level_0/f_file/tests/unit/c/mock-file.c index 8069930..68d18ac 100644 --- a/level_0/f_file/tests/unit/c/mock-file.c +++ b/level_0/f_file/tests/unit/c/mock-file.c @@ -602,6 +602,19 @@ int __wrap_renameat2(int olddirfd, const char *oldpath, int newdirfd, const char return mock_type(int); } +int __wrap_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { + + const bool failure = mock_type(bool); + + if (failure) { + errno = mock_type(int); + + return -1; + } + + return mock_type(int); +} + int __wrap_stat(const char *pathname, struct stat *statbuf) { const bool failure = mock_type(bool); diff --git a/level_0/f_file/tests/unit/c/mock-file.h b/level_0/f_file/tests/unit/c/mock-file.h index 76c8fb9..dd584f4 100644 --- a/level_0/f_file/tests/unit/c/mock-file.h +++ b/level_0/f_file/tests/unit/c/mock-file.h @@ -73,6 +73,7 @@ extern ssize_t __wrap_readlinkat(int dirfd, const char *pathname, char *buf, siz extern int __wrap_rename(const char *oldpath, const char *newpath); extern int __wrap_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); extern int __wrap_renameat2(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags); +extern int __wrap_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); extern int __wrap_stat(const char *pathname, struct stat *statbuf); extern int __wrap_symlink(const char *target, const char *linkpath); extern int __wrap_symlinkat(const char *target, int newdirfd, const char *linkpath); diff --git a/level_0/f_file/tests/unit/c/test-file-select.c b/level_0/f_file/tests/unit/c/test-file-select.c new file mode 100644 index 0000000..14b37ca --- /dev/null +++ b/level_0/f_file/tests/unit/c/test-file-select.c @@ -0,0 +1,293 @@ +#include "test-file.h" +#include "test-file-select.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void test__f_file_select__fails(void **state) { + + { + fd_set read; + + memset(&read, 0, sizeof(fd_set)); + + int errnos[] = { + EBADF, + EINTR, + EINVAL, + ENOMEM, + mock_errno_generic, + }; + + f_status_t statuss[] = { + F_file_descriptor, + F_interrupt, + F_parameter, + F_memory_not, + F_failure, + }; + + for (int i = 0; i < 5; ++i) { + + will_return(__wrap_select, true); + will_return(__wrap_select, errnos[i]); + + const f_status_t status = f_file_select(1, &read, 0, 0, 0); + + assert_int_equal(status, F_status_set_error(statuss[i])); + } // for + } +} + +void test__f_file_select__returns_data_not(void **state) { + + fd_set read; + fd_set write; + fd_set except; + struct timeval timeout; + + memset(&read, 0, sizeof(fd_set)); + memset(&write, 0, sizeof(fd_set)); + memset(&except, 0, sizeof(fd_set)); + memset(&timeout, 0, sizeof(struct timeval)); + + { + const f_status_t status = f_file_select(0, &read, &write, &except, &timeout); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, &read, &write, 0, &timeout); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, &read, 0, &except, &timeout); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, 0, &write, &except, &timeout); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, 0, 0, &except, &timeout); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, 0, &write, 0, &timeout); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, 0, 0, &except, &timeout); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, &read, &write, &except, 0); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, &read, &write, 0, 0); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, &read, 0, &except, 0); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, 0, &write, &except, 0); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, 0, 0, &except, 0); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, 0, &write, 0, 0); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(0, 0, 0, &except, 0); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(1, 0, 0, 0, &timeout); + + assert_int_equal(status, F_data_not); + } + + { + const f_status_t status = f_file_select(1, 0, 0, 0, 0); + + assert_int_equal(status, F_data_not); + } +} + +void test__f_file_select__works(void **state) { + + fd_set read; + fd_set write; + fd_set except; + struct timeval timeout; + + memset(&read, 0, sizeof(fd_set)); + memset(&write, 0, sizeof(fd_set)); + memset(&except, 0, sizeof(fd_set)); + memset(&timeout, 0, sizeof(struct timeval)); + + { + will_return(__wrap_select, false); + will_return(__wrap_select, &timeout); + + const f_status_t status = f_file_select(1, &read, &write, &except, &timeout); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, &timeout); + + const f_status_t status = f_file_select(1, &read, &write, 0, &timeout); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, &timeout); + + const f_status_t status = f_file_select(1, &read, 0, &except, &timeout); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, &timeout); + + const f_status_t status = f_file_select(1, 0, &write, &except, &timeout); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, &timeout); + + const f_status_t status = f_file_select(1, &read, 0, 0, &timeout); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, &timeout); + + const f_status_t status = f_file_select(1, 0, &write, 0, &timeout); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, &timeout); + + const f_status_t status = f_file_select(1, 0, 0, &except, &timeout); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, 0); + + const f_status_t status = f_file_select(1, &read, &write, &except, 0); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, 0); + + const f_status_t status = f_file_select(1, &read, &write, 0, 0); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, 0); + + const f_status_t status = f_file_select(1, &read, 0, &except, 0); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, 0); + + const f_status_t status = f_file_select(1, 0, &write, &except, 0); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, 0); + + const f_status_t status = f_file_select(1, &read, 0, 0, 0); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, 0); + + const f_status_t status = f_file_select(1, 0, &write, 0, 0); + + assert_int_equal(status, F_none); + } + + { + will_return(__wrap_select, false); + will_return(__wrap_select, 0); + + const f_status_t status = f_file_select(1, 0, 0, &except, 0); + + assert_int_equal(status, F_none); + } +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_0/f_file/tests/unit/c/test-file-select.h b/level_0/f_file/tests/unit/c/test-file-select.h new file mode 100644 index 0000000..f84ea92 --- /dev/null +++ b/level_0/f_file/tests/unit/c/test-file-select.h @@ -0,0 +1,34 @@ +/** + * FLL - Level 0 + * + * Project: File + * API Version: 0.7 + * Licenses: lgpl-2.1-or-later + * + * Test the file project. + */ +#ifndef _TEST__F_file_select_h +#define _TEST__F_file_select_h + +/** + * Test that function fails. + * + * @see f_file_select() + */ +extern void test__f_file_select__fails(void **state); + +/** + * Test that function works but the path is empty. + * + * @see f_file_select() + */ +extern void test__f_file_select__returns_data_not(void **state); + +/** + * Test that function works. + * + * @see f_file_select() + */ +extern void test__f_file_select__works(void **state); + +#endif // _TEST__F_file_select_h diff --git a/level_0/f_file/tests/unit/c/test-file.c b/level_0/f_file/tests/unit/c/test-file.c index 55058f2..947ab32 100644 --- a/level_0/f_file/tests/unit/c/test-file.c +++ b/level_0/f_file/tests/unit/c/test-file.c @@ -261,6 +261,10 @@ int main(void) { cmocka_unit_test(test__f_file_seek__returns_file_descriptor_not), cmocka_unit_test(test__f_file_seek__works), + cmocka_unit_test(test__f_file_select__fails), + cmocka_unit_test(test__f_file_select__returns_data_not), + cmocka_unit_test(test__f_file_select__works), + cmocka_unit_test(test__f_file_size__fails), cmocka_unit_test(test__f_file_size__returns_data_not), cmocka_unit_test(test__f_file_size__works), @@ -429,6 +433,7 @@ int main(void) { // f_file_role_change() doesn't use parameter checking. // f_file_role_change_at() doesn't use parameter checking. cmocka_unit_test(test__f_file_seek__parameter_checking), + // f_file_select() doesn't use parameter checking. cmocka_unit_test(test__f_file_size__parameter_checking), cmocka_unit_test(test__f_file_size_at__parameter_checking), cmocka_unit_test(test__f_file_size_by_id__parameter_checking), diff --git a/level_0/f_file/tests/unit/c/test-file.h b/level_0/f_file/tests/unit/c/test-file.h index 0636fc5..df14c5a 100644 --- a/level_0/f_file/tests/unit/c/test-file.h +++ b/level_0/f_file/tests/unit/c/test-file.h @@ -77,6 +77,7 @@ #include "test-file-role_change.h" #include "test-file-role_change_at.h" #include "test-file-seek.h" +#include "test-file-select.h" #include "test-file-size.h" #include "test-file-size_at.h" #include "test-file-size_by_id.h" -- 1.8.3.1