diff --git a/CMakeLists.txt b/CMakeLists.txt index b6d9965..3ea02a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -207,6 +207,7 @@ add_library(akstdlib SHARED src/stdlib.c src/string.c src/stream.c + src/stat.c src/collections.c ) @@ -317,6 +318,7 @@ set(AKSL_TESTS strbuf stream streamio + stat strhash string strto diff --git a/include/akstdlib.h b/include/akstdlib.h index 8fe8282..84fef89 100644 --- a/include/akstdlib.h +++ b/include/akstdlib.h @@ -76,6 +76,8 @@ #include #include #include +#include +#include /* off_t, for the aksl_fseeko/aksl_ftello pair. POSIX, like aksl_realpath. */ #include @@ -808,6 +810,57 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2_str(const char *str, uint32 /** @} */ +/** + * @brief stat(2). + * @param[in] pathname Path to inspect. Required. + * @param[out] dest File metadata. Required. + * @return NULL on success, an error context otherwise. + */ +akerr_ErrorContext AKERR_NOIGNORE *aksl_stat(const char *pathname, struct stat *dest); + +/** + * @brief lstat(2), inspecting a symbolic link itself. + * @param[in] pathname Path to inspect. Required. + * @param[out] dest File metadata. Required. + * @return NULL on success, an error context otherwise. + */ +akerr_ErrorContext AKERR_NOIGNORE *aksl_lstat(const char *pathname, struct stat *dest); + +/** + * @brief fstat(2). + * @param[in] fd Open file descriptor. + * @param[out] dest File metadata. Required. + * @return NULL on success, an error context otherwise. + */ +akerr_ErrorContext AKERR_NOIGNORE *aksl_fstat(int fd, struct stat *dest); + +/** + * @brief fstatat(2). + * @param[in] dirfd Directory descriptor, or AT_FDCWD. + * @param[in] pathname Path to inspect. Required. + * @param[out] dest File metadata. Required. + * @param[in] flags libc fstatat flags, passed unchanged. + * @return NULL on success, an error context otherwise. + */ +akerr_ErrorContext AKERR_NOIGNORE *aksl_fstatat(int dirfd, const char *pathname, struct stat *dest, int flags); + +/** + * @brief statvfs(3). + * @param[in] path Path on the filesystem. Required. + * @param[out] dest Filesystem metadata. Required. + * @return NULL on success, an error context otherwise. + */ +akerr_ErrorContext AKERR_NOIGNORE *aksl_statvfs(const char *path, struct statvfs *dest); + +/** + * @brief fstatvfs(3). + * @param[in] fd Open file descriptor. + * @param[out] dest Filesystem metadata. Required. + * @return NULL on success, an error context otherwise. + */ +akerr_ErrorContext AKERR_NOIGNORE *aksl_fstatvfs(int fd, struct statvfs *dest); + + /* ====================================================================== */ /** @name Streams: open, read, write, close * diff --git a/src/stat.c b/src/stat.c new file mode 100644 index 0000000..92e5ae9 --- /dev/null +++ b/src/stat.c @@ -0,0 +1,51 @@ +/* sys/stat.h and sys/statvfs.h metadata wrappers. */ +#include +#include +#include "aksl_internal.h" + +akerr_ErrorContext AKERR_NOIGNORE *aksl_stat(const char *pathname, struct stat *dest) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, pathname, AKERR_NULLPOINTER, "pathname=%p, dest=%p", (void *)pathname, (void *)dest); + FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "pathname=%p, dest=%p", (void *)pathname, (void *)dest); + errno = 0; + FAIL_NONZERO_RETURN(e, stat(pathname, dest), AKSL_ERRNO_OR(AKERR_IO), "pathname=%s", pathname); + SUCCEED_RETURN(e); +} +akerr_ErrorContext AKERR_NOIGNORE *aksl_lstat(const char *pathname, struct stat *dest) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, pathname, AKERR_NULLPOINTER, "pathname=%p, dest=%p", (void *)pathname, (void *)dest); + FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "pathname=%p, dest=%p", (void *)pathname, (void *)dest); + errno = 0; + FAIL_NONZERO_RETURN(e, lstat(pathname, dest), AKSL_ERRNO_OR(AKERR_IO), "pathname=%s", pathname); + SUCCEED_RETURN(e); +} +akerr_ErrorContext AKERR_NOIGNORE *aksl_fstat(int fd, struct stat *dest) +{ + PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "fd=%d, dest=%p", fd, (void *)dest); + errno = 0; FAIL_NONZERO_RETURN(e, fstat(fd, dest), AKSL_ERRNO_OR(AKERR_IO), "fd=%d", fd); SUCCEED_RETURN(e); +} +akerr_ErrorContext AKERR_NOIGNORE *aksl_fstatat(int dirfd, const char *pathname, struct stat *dest, int flags) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, pathname, AKERR_NULLPOINTER, "dirfd=%d, pathname=%p, dest=%p", dirfd, (void *)pathname, (void *)dest); + FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dirfd=%d, pathname=%p, dest=%p", dirfd, (void *)pathname, (void *)dest); + errno = 0; + FAIL_NONZERO_RETURN(e, fstatat(dirfd, pathname, dest, flags), AKSL_ERRNO_OR(AKERR_IO), "dirfd=%d pathname=%s flags=%d", dirfd, pathname, flags); + SUCCEED_RETURN(e); +} +akerr_ErrorContext AKERR_NOIGNORE *aksl_statvfs(const char *path, struct statvfs *dest) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, path, AKERR_NULLPOINTER, "path=%p, dest=%p", (void *)path, (void *)dest); + FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "path=%p, dest=%p", (void *)path, (void *)dest); + errno = 0; + FAIL_NONZERO_RETURN(e, statvfs(path, dest), AKSL_ERRNO_OR(AKERR_IO), "path=%s", path); + SUCCEED_RETURN(e); +} +akerr_ErrorContext AKERR_NOIGNORE *aksl_fstatvfs(int fd, struct statvfs *dest) +{ + PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "fd=%d, dest=%p", fd, (void *)dest); + errno = 0; FAIL_NONZERO_RETURN(e, fstatvfs(fd, dest), AKSL_ERRNO_OR(AKERR_IO), "fd=%d", fd); SUCCEED_RETURN(e); +} diff --git a/tests/test_stat.c b/tests/test_stat.c new file mode 100644 index 0000000..9c6d254 --- /dev/null +++ b/tests/test_stat.c @@ -0,0 +1,86 @@ +/* File metadata wrapper tests. */ +#include "aksl_capture.h" +#include +#include + +static int test_stat_success_and_fstat(void) +{ + char path[AKSL_TMP_MAX]; + int fd = -1; + struct stat path_dest; + struct stat fd_dest; + struct statvfs vfs_dest; + AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0); + fd = open(path, O_RDWR); + AKSL_CHECK(fd >= 0); + AKSL_CHECK(write(fd, "stat", 4) == 4); + AKSL_CHECK_OK(aksl_stat(path, &path_dest)); + AKSL_CHECK(S_ISREG(path_dest.st_mode)); + AKSL_CHECK(path_dest.st_size == 4); + AKSL_CHECK_OK(aksl_fstat(fd, &fd_dest)); + AKSL_CHECK(fd_dest.st_ino == path_dest.st_ino); + AKSL_CHECK_OK(aksl_fstatvfs(fd, &vfs_dest)); + AKSL_CHECK(vfs_dest.f_frsize != 0); + AKSL_CHECK(close(fd) == 0); + AKSL_CHECK_STATUS(aksl_fstat(fd, &fd_dest), EBADF); + AKSL_CHECK(unlink(path) == 0); + return 0; +} + +static int test_stat_paths_and_fstatat(void) +{ + char path[AKSL_TMP_MAX]; + char child[AKSL_TMP_MAX * 2]; + char linkpath[AKSL_TMP_MAX * 2]; + struct stat dest; + struct stat ldest; + struct statvfs vdest; + AKSL_CHECK_STATUS(aksl_stat("/nonexistent/aksl/stat", &dest), ENOENT); + AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0); + AKSL_CHECK(snprintf(child, sizeof(child), "%s/child", path) < (int)sizeof(child)); + AKSL_CHECK_STATUS(aksl_stat(child, &dest), ENOTDIR); + AKSL_CHECK(snprintf(linkpath, sizeof(linkpath), "%s.link", path) < (int)sizeof(linkpath)); + AKSL_CHECK(symlink(path, linkpath) == 0); + AKSL_CHECK_OK(aksl_stat(linkpath, &dest)); + AKSL_CHECK_OK(aksl_lstat(linkpath, &ldest)); + AKSL_CHECK(S_ISREG(dest.st_mode)); + AKSL_CHECK(S_ISLNK(ldest.st_mode)); + AKSL_CHECK_OK(aksl_fstatat(AT_FDCWD, path, &dest, 0)); + errno = E2BIG; + AKSL_CHECK_STATUS(aksl_fstatat(AT_FDCWD, path, &dest, 0x40000000), EINVAL); + AKSL_CHECK(errno == EINVAL); + AKSL_CHECK_OK(aksl_statvfs(".", &vdest)); + AKSL_CHECK(vdest.f_frsize != 0); + AKSL_CHECK(unlink(linkpath) == 0); + AKSL_CHECK(unlink(path) == 0); + return 0; +} + +static int test_stat_null_arguments(void) +{ + char path[AKSL_TMP_MAX]; + struct stat dest; + struct statvfs vdest; + AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0); + AKSL_CHECK_STATUS(aksl_stat(NULL, &dest), AKERR_NULLPOINTER); + AKSL_CHECK_STATUS(aksl_stat(path, NULL), AKERR_NULLPOINTER); + AKSL_CHECK_STATUS(aksl_lstat(NULL, &dest), AKERR_NULLPOINTER); + AKSL_CHECK_STATUS(aksl_lstat(path, NULL), AKERR_NULLPOINTER); + AKSL_CHECK_STATUS(aksl_fstat(0, NULL), AKERR_NULLPOINTER); + AKSL_CHECK_STATUS(aksl_fstatat(AT_FDCWD, NULL, &dest, 0), AKERR_NULLPOINTER); + AKSL_CHECK_STATUS(aksl_fstatat(AT_FDCWD, path, NULL, 0), AKERR_NULLPOINTER); + AKSL_CHECK_STATUS(aksl_statvfs(NULL, &vdest), AKERR_NULLPOINTER); + AKSL_CHECK_STATUS(aksl_statvfs(path, NULL), AKERR_NULLPOINTER); + AKSL_CHECK_STATUS(aksl_fstatvfs(0, NULL), AKERR_NULLPOINTER); + AKSL_CHECK(unlink(path) == 0); + return 0; +} + +int main(void) +{ + int failures = 0; + AKSL_RUN(failures, test_stat_success_and_fstat); + AKSL_RUN(failures, test_stat_paths_and_fstatat); + AKSL_RUN(failures, test_stat_null_arguments); + AKSL_REPORT(failures); +}