diff --git a/src/dir.c b/src/dir.c index 963d94d..cb72edf 100644 --- a/src/dir.c +++ b/src/dir.c @@ -1,10 +1,20 @@ -/* POSIX directory-stream wrappers. */ +/* + * POSIX directory-stream wrappers. + * + * opendir(3), fdopendir(3), readdir(3), closedir(3), and rewinddir(3) expose + * three different failure conventions between them. These wrappers turn all + * three into error contexts and make end-of-directory an explicit AKERR_EOF. + */ #include #include #include "aksl_internal.h" +/* + * opendir(3) returns NULL for failure. Clear *dest first so a failed open + * cannot leave the caller holding a stale directory stream. + */ akerr_ErrorContext AKERR_NOIGNORE *aksl_opendir(const char *pathname, DIR **dest) { PREPARE_ERROR(e); @@ -19,6 +29,10 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_opendir(const char *pathname, DIR **dest SUCCEED_RETURN(e); } +/* + * fdopendir(3) takes ownership of fd only when it succeeds. On success the + * matching aksl_closedir call closes both the stream and its descriptor. + */ akerr_ErrorContext AKERR_NOIGNORE *aksl_fdopendir(int fd, DIR **dest) { PREPARE_ERROR(e); @@ -30,6 +44,10 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fdopendir(int fd, DIR **dest) SUCCEED_RETURN(e); } +/* + * readdir(3) owns and may reuse its returned storage. Copy the entry into the + * caller's destination, and use errno to distinguish failure from exhaustion. + */ akerr_ErrorContext AKERR_NOIGNORE *aksl_readdir(DIR *dirp, struct dirent *dest) { struct dirent *entry = NULL; @@ -50,6 +68,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_readdir(DIR *dirp, struct dirent *dest) SUCCEED_RETURN(e); } +/* closedir(3) reports its failure directly and invalidates dirp on success. */ akerr_ErrorContext AKERR_NOIGNORE *aksl_closedir(DIR *dirp) { PREPARE_ERROR(e); @@ -60,6 +79,10 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_closedir(DIR *dirp) SUCCEED_RETURN(e); } +/* + * rewinddir(3) has no failure return. Preserve that contract after rejecting + * a NULL stream, which would otherwise be undefined behaviour. + */ akerr_ErrorContext AKERR_NOIGNORE *aksl_rewinddir(DIR *dirp) { PREPARE_ERROR(e); diff --git a/tests/test_dir.c b/tests/test_dir.c index b212512..cd3e18a 100644 --- a/tests/test_dir.c +++ b/tests/test_dir.c @@ -23,14 +23,21 @@ static int test_open_errors_and_nulls(void) DIR *dirp = (DIR *)1; struct dirent entry; + /* opendir propagates missing-path and non-directory failures. */ AKSL_CHECK_STATUS(aksl_opendir("/nonexistent/aksl/dir", &dirp), ENOENT); AKSL_CHECK(dirp == NULL); AKSL_CHECK(aksl_temp_file(file, sizeof(file)) == 0); AKSL_CHECK_STATUS(aksl_opendir(file, &dirp), ENOTDIR); + + /* Every pointer required by the wrapped operation rejects NULL. */ AKSL_CHECK_STATUS(aksl_opendir(NULL, &dirp), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_opendir(".", NULL), AKERR_NULLPOINTER); + + /* fdopendir propagates an invalid descriptor and validates its out-param. */ AKSL_CHECK_STATUS(aksl_fdopendir(-1, &dirp), EBADF); AKSL_CHECK_STATUS(aksl_fdopendir(0, NULL), AKERR_NULLPOINTER); + + /* The remaining wrappers reject NULL streams and destinations. */ AKSL_CHECK_STATUS(aksl_readdir(NULL, &entry), AKERR_NULLPOINTER); AKSL_CHECK_OK(aksl_opendir(".", &dirp)); AKSL_CHECK_STATUS(aksl_readdir(dirp, NULL), AKERR_NULLPOINTER);