Document file metadata wrapper behavior
All checks were successful
All checks were successful
Co-authored-by: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
37
src/stat.c
37
src/stat.c
@@ -3,6 +3,12 @@
|
||||
#include <errno.h>
|
||||
#include "aksl_internal.h"
|
||||
|
||||
/*
|
||||
* stat(2) follows pathname through any symbolic links and writes the target's
|
||||
* metadata into the caller-owned struct stat. A file that is gone, cannot be
|
||||
* searched, or lives below a non-directory component is reported as the errno
|
||||
* from libc rather than as a library-specific status.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_stat(const char *pathname, struct stat *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
@@ -12,6 +18,12 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_stat(const char *pathname, struct stat *
|
||||
FAIL_NONZERO_RETURN(e, stat(pathname, dest), AKSL_ERRNO_OR(AKERR_IO), "pathname=%s", pathname);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* lstat(2) is stat(2) without the final symbolic-link traversal. That is the
|
||||
* difference a caller needs when it is deciding whether a path is a link or
|
||||
* when the link's ownership and mode are the metadata of interest.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_lstat(const char *pathname, struct stat *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
@@ -21,11 +33,24 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_lstat(const char *pathname, struct stat
|
||||
FAIL_NONZERO_RETURN(e, lstat(pathname, dest), AKSL_ERRNO_OR(AKERR_IO), "pathname=%s", pathname);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* fstat(2) gets metadata from an already-open descriptor, so it has no path
|
||||
* lookup race and remains useful after the file has been renamed or unlinked.
|
||||
* A closed or otherwise invalid descriptor reports EBADF from libc.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* fstatat(2) is the directory-descriptor form of stat(2). pathname is resolved
|
||||
* relative to dirfd unless it is absolute, and flags retain the libc choices
|
||||
* such as inspecting a link itself. Keeping those flags unchanged prevents this
|
||||
* wrapper from inventing a smaller policy than the POSIX call already exposes.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fstatat(int dirfd, const char *pathname, struct stat *dest, int flags)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
@@ -35,6 +60,12 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fstatat(int dirfd, const char *pathname,
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* statvfs(3) writes information about the mounted filesystem containing path,
|
||||
* not merely the named file. The result includes the filesystem block sizes and
|
||||
* available space the caller needs before it decides whether an operation fits.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_statvfs(const char *path, struct statvfs *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
@@ -44,6 +75,12 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_statvfs(const char *path, struct statvfs
|
||||
FAIL_NONZERO_RETURN(e, statvfs(path, dest), AKSL_ERRNO_OR(AKERR_IO), "path=%s", path);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* fstatvfs(3) is the descriptor form of statvfs(3). It asks the filesystem that
|
||||
* owns fd for the same capacity and flag information without resolving a path
|
||||
* again, and reports a bad descriptor through the errno libc supplies.
|
||||
*/
|
||||
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);
|
||||
|
||||
@@ -10,6 +10,7 @@ static int test_stat_success_and_fstat(void)
|
||||
struct stat path_dest;
|
||||
struct stat fd_dest;
|
||||
struct statvfs vfs_dest;
|
||||
/* A real file makes the mode and four-byte size observable to stat(2). */
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
fd = open(path, O_RDWR);
|
||||
AKSL_CHECK(fd >= 0);
|
||||
@@ -17,11 +18,17 @@ static int test_stat_success_and_fstat(void)
|
||||
AKSL_CHECK_OK(aksl_stat(path, &path_dest));
|
||||
AKSL_CHECK(S_ISREG(path_dest.st_mode));
|
||||
AKSL_CHECK(path_dest.st_size == 4);
|
||||
|
||||
/* fstat(2) addresses the same opened inode, not a second pathname lookup. */
|
||||
AKSL_CHECK_OK(aksl_fstat(fd, &fd_dest));
|
||||
AKSL_CHECK(fd_dest.st_ino == path_dest.st_ino);
|
||||
|
||||
/* fstatvfs(3) describes the backing filesystem and has a usable block size. */
|
||||
AKSL_CHECK_OK(aksl_fstatvfs(fd, &vfs_dest));
|
||||
AKSL_CHECK(vfs_dest.f_frsize != 0);
|
||||
AKSL_CHECK(close(fd) == 0);
|
||||
|
||||
/* A descriptor that was just closed must surface libc's EBADF. */
|
||||
AKSL_CHECK_STATUS(aksl_fstat(fd, &fd_dest), EBADF);
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
@@ -35,20 +42,30 @@ static int test_stat_paths_and_fstatat(void)
|
||||
struct stat dest;
|
||||
struct stat ldest;
|
||||
struct statvfs vdest;
|
||||
/* Missing paths and a regular file used as a directory preserve errno. */
|
||||
AKSL_CHECK_STATUS(aksl_stat("/nonexistent/aksl/stat", &dest), ENOENT);
|
||||
/* The temporary path is a regular file, so adding a child tests ENOTDIR. */
|
||||
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);
|
||||
|
||||
/* stat follows the link while lstat reports the link object itself. */
|
||||
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));
|
||||
|
||||
/* AT_FDCWD makes fstatat resolve this path from the current directory. */
|
||||
AKSL_CHECK_OK(aksl_fstatat(AT_FDCWD, path, &dest, 0));
|
||||
|
||||
/* Invalid flags must replace stale errno with the EINVAL libc reports. */
|
||||
errno = E2BIG;
|
||||
AKSL_CHECK_STATUS(aksl_fstatat(AT_FDCWD, path, &dest, 0x40000000), EINVAL);
|
||||
AKSL_CHECK(errno == EINVAL);
|
||||
|
||||
/* statvfs reports the filesystem containing the current directory. */
|
||||
AKSL_CHECK_OK(aksl_statvfs(".", &vdest));
|
||||
AKSL_CHECK(vdest.f_frsize != 0);
|
||||
AKSL_CHECK(unlink(linkpath) == 0);
|
||||
@@ -61,7 +78,9 @@ static int test_stat_null_arguments(void)
|
||||
char path[AKSL_TMP_MAX];
|
||||
struct stat dest;
|
||||
struct statvfs vdest;
|
||||
/* Create a valid path so each failure below isolates a NULL argument. */
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
/* Every wrapper refuses either missing caller-owned input or output storage. */
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user