gstdio: Add g_clear_fd() and g_autofd

Inspired by libglnx's glnx_close_fd() and glnx_autofd, these let us
have the same patterns as g_clear_object() and g_autoptr(GObject), but
for file descriptors. g_clear_fd() is cross-platform, while g_autofd
is syntactic sugar requiring a supported compiler (gcc or clang).

Now that g_close() checks for EBADF as a programming error, we can
implement the equivalent of glnx_autofd as an inline function without
needing to have errno and EBADF in the header file.

g_clear_fd() is like glnx_close_fd(), but with error checking.
The private _g_clear_fd_ignore_error() function used to implement
g_autofd is a closer equivalent of glnx_close_fd().

Signed-off-by: Simon McVittie <smcv@collabora.com>
This commit is contained in:
Simon McVittie 2022-10-21 17:39:35 +01:00
parent b9e68b0bd4
commit b393413321
5 changed files with 211 additions and 1 deletions

View File

@ -1454,6 +1454,8 @@ g_creat
g_chdir
g_utime
g_close
g_clear_fd
g_autofd
<SUBSECTION Private>
g_file_error_quark

View File

@ -1276,7 +1276,7 @@
#if g_macro__has_attribute(cleanup)
/* these macros are private */
/* these macros are private; note that gstdio.h also uses _GLIB_CLEANUP */
#define _GLIB_AUTOPTR_FUNC_NAME(TypeName) glib_autoptr_cleanup_##TypeName
#define _GLIB_AUTOPTR_CLEAR_FUNC_NAME(TypeName) glib_autoptr_clear_##TypeName
#define _GLIB_AUTOPTR_TYPENAME(TypeName) TypeName##_autoptr

View File

@ -1837,3 +1837,103 @@ g_close (gint fd,
return TRUE;
}
/**
* g_clear_fd: (skip)
* @fd_ptr: (not nullable): a pointer to a file descriptor
* @error: Used to return an error on failure
*
* If @fd_ptr points to a file descriptor, close it and return
* whether closing it was successful, like g_close().
* If @fd_ptr points to a negative number, return %TRUE without closing
* anything.
* In both cases, set @fd_ptr to `-1` before returning.
*
* It is a programming error for @fd_ptr to point to a non-negative
* number that is not a valid file descriptor.
*
* A typical use of this function is to clean up a file descriptor at
* the end of its scope, whether it has been set successfully or not:
*
* |[
* gboolean
* operate_on_fd (GError **error)
* {
* gboolean ret = FALSE;
* int fd = -1;
*
* fd = open_a_fd (error);
*
* if (fd < 0)
* goto out;
*
* if (!do_something (fd, error))
* goto out;
*
* if (!g_clear_fd (&fd, error))
* goto out;
*
* ret = TRUE;
*
* out:
* // OK to call even if fd was never opened or was already closed
* g_clear_fd (&fd, NULL);
* return ret;
* }
* ]|
*
* This function is also useful in conjunction with #g_autofd.
*
* Returns: %TRUE on success
* Since: 2.76
*/
/**
* g_autofd: (skip)
*
* Macro to add an attribute to a file descriptor variable to ensure
* automatic cleanup using g_clear_fd().
*
* This macro behaves like #g_autofree rather than g_autoptr(): it is
* an attribute supplied before the type name, rather than wrapping the
* type definition.
*
* Otherwise, this macro has similar constraints as g_autoptr(): it is
* only supported on GCC and clang, and the variable must be initialized
* (to either a valid file descriptor or a negative number).
*
* Any error from closing the file descriptor when it goes out of scope
* is ignored. Use g_clear_fd() if error-checking is required.
*
* |[
* gboolean
* operate_on_fds (GError **error)
* {
* g_autofd int fd1 = open_a_fd (..., error);
* g_autofd int fd2 = -1;
*
* // it is safe to return early here, nothing will be closed
* if (fd1 < 0)
* return FALSE;
*
* fd2 = open_a_fd (..., error);
*
* // fd1 will be closed automatically if we return here
* if (fd2 < 0)
* return FALSE;
*
* // fd1 and fd2 will be closed automatically if we return here
* if (!do_something_useful (fd1, fd2, error))
* return FALSE;
*
* // fd2 will be closed automatically if we return here
* if (!g_clear_fd (&fd1, error))
* return FALSE;
*
* // fd2 will be automatically closed here if still open
* return TRUE;
* }
* ]|
*
* Since: 2.76
*/

View File

@ -178,6 +178,38 @@ GLIB_AVAILABLE_IN_2_36
gboolean g_close (gint fd,
GError **error);
GLIB_AVAILABLE_STATIC_INLINE_IN_2_76
static inline gboolean
g_clear_fd (int *fd_ptr,
GError **error)
{
int fd = *fd_ptr;
*fd_ptr = -1;
if (fd < 0)
return TRUE;
return g_close (fd, error);
}
/* g_autofd should be defined on the same compilers where g_autofree is
* This avoids duplicating the feature-detection here. */
#ifdef g_autofree
/* Not public API */
static inline void
_g_clear_fd_ignore_error (int *fd_ptr)
{
if (!g_clear_fd (fd_ptr, NULL))
{
/* Do nothing: we ignore all errors, except for EBADF which
* is a programming error, checked for by g_close(). */
}
}
#define g_autofd _GLIB_CLEANUP(_g_clear_fd_ignore_error)
#endif
G_END_DECLS
#endif /* __G_STDIO_H__ */

View File

@ -2453,6 +2453,81 @@ test_win32_zero_terminate_symlink (void)
#endif
static void
assert_fd_was_closed (int fd)
{
/* We can't tell a fd was really closed without behaving as though it
* was still valid */
if (g_test_undefined ())
{
int result = g_fsync (fd);
int errsv = errno;
g_assert_cmpint (result, !=, 0);
g_assert_cmpint (errsv, ==, EBADF);
}
}
static void
test_clear_fd (void)
{
char *name = NULL;
GError *error = NULL;
int fd;
int copy_of_fd;
#ifdef g_autofree
g_test_summary ("Test g_clear_fd() and g_autofd");
#else
g_test_summary ("Test g_clear_fd() (g_autofd unsupported by this compiler)");
#endif
/* g_clear_fd() normalizes any negative number to -1 */
fd = -23;
g_clear_fd (&fd, &error);
g_assert_cmpint (fd, ==, -1);
g_assert_no_error (error);
/* Nothing special about g_file_open_tmp, it's just a convenient way
* to get an open fd */
fd = g_file_open_tmp (NULL, &name, &error);
g_assert_cmpint (fd, !=, -1);
g_assert_no_error (error);
g_assert_nonnull (name);
copy_of_fd = fd;
g_clear_fd (&fd, &error);
g_assert_cmpint (fd, ==, -1);
g_assert_no_error (error);
assert_fd_was_closed (copy_of_fd);
g_unlink (name);
g_free (name);
/* g_clear_fd() is idempotent */
g_clear_fd (&fd, &error);
g_assert_cmpint (fd, ==, -1);
g_assert_no_error (error);
#ifdef g_autofree
fd = g_file_open_tmp (NULL, &name, &error);
g_assert_cmpint (fd, !=, -1);
g_assert_no_error (error);
g_assert_nonnull (name);
{
g_autofd int close_me = fd;
g_autofd int was_never_set = -42;
/* This avoids clang warnings about the variables being unused */
g_test_message ("Will be closed by autocleanup: %d, %d",
close_me, was_never_set);
}
assert_fd_was_closed (fd);
g_unlink (name);
g_free (name);
#endif
}
int
main (int argc,
char *argv[])
@ -2489,6 +2564,7 @@ main (int argc,
g_test_add_func ("/fileutils/read-link", test_read_link);
g_test_add_func ("/fileutils/stdio-wrappers", test_stdio_wrappers);
g_test_add_func ("/fileutils/fopen-modes", test_fopen_modes);
g_test_add_func ("/fileutils/clear-fd", test_clear_fd);
return g_test_run ();
}