From 043f74a444ad450f74b890359ebc9f62f494f2ca Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Tue, 20 Jun 2023 20:54:31 -0500 Subject: [PATCH] Feature: Add f_file_manipulate() as a wrapper for fcntl(). This also adds the unit tests. The unit tests includes additional fixes where several of the wrapped functions are not being setup properly. The fact that has not triggered an error in the past is concerning. --- level_0/f_file/c/file.c | 26 +++++++ level_0/f_file/c/file.h | 36 ++++++++++ level_0/f_file/data/build/settings-mocks | 11 +-- 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-manipulate.c | 84 ++++++++++++++++++++++ level_0/f_file/tests/unit/c/test-file-manipulate.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, 207 insertions(+), 6 deletions(-) create mode 100644 level_0/f_file/tests/unit/c/test-file-manipulate.c create mode 100644 level_0/f_file/tests/unit/c/test-file-manipulate.h diff --git a/level_0/f_file/c/file.c b/level_0/f_file/c/file.c index 5f9a499..f845275 100644 --- a/level_0/f_file/c/file.c +++ b/level_0/f_file/c/file.c @@ -628,6 +628,32 @@ extern "C" { } #endif // _di_f_file_link_read_at_ +#ifndef _di_f_file_manipulate_ + f_status_t f_file_manipulate(const f_file_t file, const int command, const long argument) { + + if (file.id == -1) return F_file_descriptor_not; + + if (fcntl(file.id, command, argument) == -1) { + if (errno == EACCES) return F_status_set_error(F_access_denied); + if (errno == EAGAIN) return F_status_set_error(F_again); + if (errno == EBADF) return F_status_set_error(F_file_descriptor); + if (errno == EBUSY) return F_status_set_error(F_busy); + if (errno == EDEADLK) return F_status_set_error(F_deadlock); + if (errno == EFAULT) return F_status_set_error(F_buffer); + if (errno == EINTR) return F_status_set_error(F_interrupt); + if (errno == EINVAL) return F_status_set_error(F_parameter); + if (errno == EMFILE) return F_status_set_error(F_file_descriptor_max); + if (errno == ENOLCK) return F_status_set_error(F_lock); + if (errno == ENOTDIR) return F_status_set_error(F_directory_not); + if (errno == EPERM) return F_status_set_error(F_prohibited); + + return F_status_set_error(F_failure); + } + + return F_none; + } +#endif // _di_f_file_manipulate_ + #ifndef _di_f_file_mode_determine_ f_status_t f_file_mode_determine(const mode_t mode_file, const f_file_mode_t mode_change, const uint8_t mode_replace, const bool directory_is, mode_t *mode) { #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 9f5fafa..b770f84 100644 --- a/level_0/f_file/c/file.h +++ b/level_0/f_file/c/file.h @@ -1046,6 +1046,42 @@ extern "C" { #endif // _di_f_file_link_read_at_ /** + * Manipulate the file by the file descriptor. + * + * @param file + * The data related to the file containing the file descriptor. + * The file descriptor is required to be valid. + * @param command + * The command to perform. + * See fcntl() for details. + * @param argument + * An argument associated with the command. + * See fcntl() for details. + * + * @return + * F_none on success. + * + * F_access_denied (with error bit) on access denied (due to locks being held by other processes). + * F_again (with error bit) on operation on file is prohibited (often due to file being memory mapped by another process). + * F_buffer (with error bit) if the buffer is invalid. + * F_busy (with error bit) if system is too busy to perform operation. + * F_deadlock (with error bit) if operation would cause a deadlock. + * F_directory_not (with error bit) on invalid directory. + * F_file_descriptor (with error bit) if file descriptor is invalid. + * F_file_descriptor_max (with error bit) if max file descriptors is reached. + * F_interrupt (with error bit) when program received an interrupt signal, halting operation. + * F_lock (with error bit) if failed to lock, such as lock table is full or too many open segments. + * F_parameter (with error bit) if a parameter is invalid. + * F_prohibited (with error bit) if operation is prohibited (maps to EPERM). + * F_failure (with error bit) for any other error. + * + * @see fcntl() + */ +#ifndef _di_f_file_manipulate_ + extern f_status_t f_file_manipulate(const f_file_t file, const int command, const long argument); +#endif // _di_f_file_manipulate_ + +/** * Determine how the mode should be applied based on different file properties and the given mode properties. * * This does not set mode based on umask(), which is already applied if f_file_mode_from_string() was used to create mode_change. diff --git a/level_0/f_file/data/build/settings-mocks b/level_0/f_file/data/build/settings-mocks index 33eb429..3b46a39 100644 --- a/level_0/f_file/data/build/settings-mocks +++ b/level_0/f_file/data/build/settings-mocks @@ -63,18 +63,21 @@ flags -Wl,--wrap=chmod flags -Wl,--wrap=chown flags -Wl,--wrap=close flags -Wl,--wrap=faccessat +flags -Wl,--wrap=fchmod flags -Wl,--wrap=fchmodat +flags -Wl,--wrap=fchown flags -Wl,--wrap=fchownat flags -Wl,--wrap=fclose +flags -Wl,--wrap=fopen flags -Wl,--wrap=fdopen +flags -Wl,--wrap=freopen flags -Wl,--wrap=feof_unlocked flags -Wl,--wrap=ferror_unlocked flags -Wl,--wrap=fflush flags -Wl,--wrap=fileno flags -Wl,--wrap=flockfile -flags -Wl,--wrap=fopen +flags -Wl,--wrap=fcntl flags -Wl,--wrap=fread_unlocked -flags -Wl,--wrap=freopen flags -Wl,--wrap=fstat flags -Wl,--wrap=fstatat flags -Wl,--wrap=fsync @@ -85,7 +88,6 @@ flags -Wl,--wrap=link flags -Wl,--wrap=linkat flags -Wl,--wrap=lseek flags -Wl,--wrap=lstat -flags -Wl,--wrap=makedev flags -Wl,--wrap=mkdir flags -Wl,--wrap=mkdirat flags -Wl,--wrap=mkfifo @@ -100,12 +102,11 @@ flags -Wl,--wrap=readlinkat flags -Wl,--wrap=rename flags -Wl,--wrap=renameat flags -Wl,--wrap=renameat2 -flags -Wl,--wrap=remove flags -Wl,--wrap=stat flags -Wl,--wrap=symlink flags -Wl,--wrap=symlinkat flags -Wl,--wrap=unlink flags -Wl,--wrap=unlinkat -flags -Wl,--wrap=utimensat flags -Wl,--wrap=umask +flags -Wl,--wrap=utimensat flags -Wl,--wrap=write diff --git a/level_0/f_file/data/build/settings-tests b/level_0/f_file/data/build/settings-tests index a382816..7bb518a 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-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-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-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.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 d929dcb..1aa8aba 100644 --- a/level_0/f_file/tests/unit/c/mock-file.c +++ b/level_0/f_file/tests/unit/c/mock-file.c @@ -205,6 +205,19 @@ void __wrap_flockfile(FILE *filehandle) { } +int __wrap_fcntl(int fd, int cmd, ...) { + + const bool failure = mock_type(bool); + + if (failure) { + errno = mock_type(int); + + return -1; + } + + return 0; +} + size_t __wrap_fread_unlocked(void *ptr, size_t size, size_t nmemb, FILE *stream) { return mock_type(int); 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 0862c8c..7b876f9 100644 --- a/level_0/f_file/tests/unit/c/mock-file.h +++ b/level_0/f_file/tests/unit/c/mock-file.h @@ -46,6 +46,7 @@ extern int __wrap_ferror_unlocked(FILE *stream); extern int __wrap_fflush(FILE *stream); extern int __wrap_fileno(FILE *stream); extern void __wrap_flockfile(FILE *filehandle); +extern int __wrap_fcntl(int fd, int cmd, ...); extern size_t __wrap_fread_unlocked(void *ptr, size_t size, size_t nmemb, FILE *stream); extern int __wrap_fstat(int fd, struct stat *statbuf); extern int __wrap_fstatat(int dirfd, const char *pathname, struct stat *statbuf, int flags); diff --git a/level_0/f_file/tests/unit/c/test-file-manipulate.c b/level_0/f_file/tests/unit/c/test-file-manipulate.c new file mode 100644 index 0000000..0b09ce1 --- /dev/null +++ b/level_0/f_file/tests/unit/c/test-file-manipulate.c @@ -0,0 +1,84 @@ +#include "test-file.h" +#include "test-file-manipulate.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void test__f_file_manipulate__fails(void **state) { + + const f_file_t file = macro_f_file_t_initialize_2(F_type_output_d, F_type_descriptor_output_d, F_file_flag_write_only_d); + + { + int errnos[] = { + EACCES, + EAGAIN, + EBADF, + EBUSY, + EDEADLK, + EFAULT, + EINTR, + EINVAL, + EMFILE, + ENOLCK, + ENOTDIR, + EPERM, + mock_errno_generic, + }; + + f_status_t statuss[] = { + F_access_denied, + F_again, + F_file_descriptor, + F_busy, + F_deadlock, + F_buffer, + F_interrupt, + F_parameter, + F_file_descriptor_max, + F_lock, + F_directory_not, + F_prohibited, + F_failure, + }; + + for (int i = 0; i < 13; ++i) { + + will_return(__wrap_fcntl, true); + will_return(__wrap_fcntl, errnos[i]); + + const f_status_t status = f_file_manipulate(file, 0, 0); + + assert_int_equal(status, F_status_set_error(statuss[i])); + } // for + } +} + +void test__f_file_manipulate__returns_file_descriptor_not(void **state) { + + const f_file_t file = macro_f_file_t_initialize_2(F_type_output_d, -1, F_file_flag_write_only_d); + + { + const f_status_t status = f_file_manipulate(file, 0, 0); + + assert_int_equal(status, F_file_descriptor_not); + } +} + +void test__f_file_manipulate__works(void **state) { + + const f_file_t file = macro_f_file_t_initialize_2(F_type_output_d, F_type_descriptor_output_d, F_file_flag_write_only_d); + + { + + will_return(__wrap_fcntl, false); + + const f_status_t status = f_file_manipulate(file, 0, 0); + + assert_int_equal(status, F_none); + } +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_0/f_file/tests/unit/c/test-file-manipulate.h b/level_0/f_file/tests/unit/c/test-file-manipulate.h new file mode 100644 index 0000000..7718c17 --- /dev/null +++ b/level_0/f_file/tests/unit/c/test-file-manipulate.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_manipulate_h +#define _TEST__F_file_manipulate_h + +/** + * Test that function fails. + * + * @see f_file_manipulate() + */ +extern void test__f_file_manipulate__fails(void **state); + +/** + * Test that function works but the descriptor is not valid. + * + * @see f_file_manipulate() + */ +extern void test__f_file_manipulate__returns_file_descriptor_not(void **state); + +/** + * Test that function works. + * + * @see f_file_manipulate() + */ +extern void test__f_file_manipulate__works(void **state); + +#endif // _TEST__F_file_manipulate_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 479513b..e74a43d 100644 --- a/level_0/f_file/tests/unit/c/test-file.c +++ b/level_0/f_file/tests/unit/c/test-file.c @@ -159,6 +159,10 @@ int main(void) { cmocka_unit_test(test__f_file_link_read_at__returns_file_descriptor_not), cmocka_unit_test(test__f_file_link_read_at__works), + cmocka_unit_test(test__f_file_manipulate__fails), + cmocka_unit_test(test__f_file_manipulate__returns_file_descriptor_not), + cmocka_unit_test(test__f_file_manipulate__works), + cmocka_unit_test(test__f_file_mode_determine__works_basic), cmocka_unit_test(test__f_file_mode_determine__works_basic_replace), @@ -397,6 +401,7 @@ int main(void) { // f_file_link_hard_at() doesn't use parameter checking. cmocka_unit_test(test__f_file_link_read__parameter_checking), cmocka_unit_test(test__f_file_link_read_at__parameter_checking), + // f_file_manipulate() doesn't use parameter checking. cmocka_unit_test(test__f_file_mode_determine__parameter_checking), cmocka_unit_test(test__f_file_mode_from_string__parameter_checking), cmocka_unit_test(test__f_file_mode_read__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 0b841d0..142c0d5 100644 --- a/level_0/f_file/tests/unit/c/test-file.h +++ b/level_0/f_file/tests/unit/c/test-file.h @@ -53,6 +53,7 @@ #include "test-file-link_hard_at.h" #include "test-file-link_read.h" #include "test-file-link_read_at.h" +#include "test-file-manipulate.h" #include "test-file-mode_determine.h" #include "test-file-mode_from_string.h" #include "test-file-mode_read.h" -- 1.8.3.1