mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-27 07:56:14 +01:00
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:
parent
b9e68b0bd4
commit
b393413321
@ -1454,6 +1454,8 @@ g_creat
|
|||||||
g_chdir
|
g_chdir
|
||||||
g_utime
|
g_utime
|
||||||
g_close
|
g_close
|
||||||
|
g_clear_fd
|
||||||
|
g_autofd
|
||||||
|
|
||||||
<SUBSECTION Private>
|
<SUBSECTION Private>
|
||||||
g_file_error_quark
|
g_file_error_quark
|
||||||
|
@ -1276,7 +1276,7 @@
|
|||||||
|
|
||||||
#if g_macro__has_attribute(cleanup)
|
#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_FUNC_NAME(TypeName) glib_autoptr_cleanup_##TypeName
|
||||||
#define _GLIB_AUTOPTR_CLEAR_FUNC_NAME(TypeName) glib_autoptr_clear_##TypeName
|
#define _GLIB_AUTOPTR_CLEAR_FUNC_NAME(TypeName) glib_autoptr_clear_##TypeName
|
||||||
#define _GLIB_AUTOPTR_TYPENAME(TypeName) TypeName##_autoptr
|
#define _GLIB_AUTOPTR_TYPENAME(TypeName) TypeName##_autoptr
|
||||||
|
100
glib/gstdio.c
100
glib/gstdio.c
@ -1837,3 +1837,103 @@ g_close (gint fd,
|
|||||||
|
|
||||||
return TRUE;
|
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
|
||||||
|
*/
|
||||||
|
@ -178,6 +178,38 @@ GLIB_AVAILABLE_IN_2_36
|
|||||||
gboolean g_close (gint fd,
|
gboolean g_close (gint fd,
|
||||||
GError **error);
|
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
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /* __G_STDIO_H__ */
|
#endif /* __G_STDIO_H__ */
|
||||||
|
@ -2453,6 +2453,81 @@ test_win32_zero_terminate_symlink (void)
|
|||||||
|
|
||||||
#endif
|
#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
|
int
|
||||||
main (int argc,
|
main (int argc,
|
||||||
char *argv[])
|
char *argv[])
|
||||||
@ -2489,6 +2564,7 @@ main (int argc,
|
|||||||
g_test_add_func ("/fileutils/read-link", test_read_link);
|
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/stdio-wrappers", test_stdio_wrappers);
|
||||||
g_test_add_func ("/fileutils/fopen-modes", test_fopen_modes);
|
g_test_add_func ("/fileutils/fopen-modes", test_fopen_modes);
|
||||||
|
g_test_add_func ("/fileutils/clear-fd", test_clear_fd);
|
||||||
|
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user