/* GIO - GLib Input, Output and Streaming Library * * Copyright © 2009 Codethink Limited * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2 of the licence or (at * your option) any later version. * * See the included COPYING file for more information. * * Authors: Ryan Lortie */ /** * SECTION:gunixfdlist * @title: GUnixFDList * @short_description: An object containing a set of UNIX file descriptors * @include: gio/gunixfdlist.h * @see_also: #GUnixFDMessage * * A #GUnixFDList contains a list of file descriptors. It owns the file * descriptors that it contains, closing them when finalized. * * It may be wrapped in a #GUnixFDMessage and sent over a #GSocket in * the %G_SOCKET_ADDRESS_UNIX family by using g_socket_send_message() * and received using g_socket_receive_message(). * * Note that `<gio/gunixfdlist.h>` belongs to the UNIX-specific GIO * interfaces, thus you have to use the `gio-unix-2.0.pc` pkg-config * file when using it. */ #include "config.h" #include #include #include #include #include "gunixfdlist.h" #include "gnetworking.h" #include "gioerror.h" struct _GUnixFDListPrivate { gint *fds; gint nfd; }; G_DEFINE_TYPE_WITH_PRIVATE (GUnixFDList, g_unix_fd_list, G_TYPE_OBJECT) static void g_unix_fd_list_init (GUnixFDList *list) { list->priv = g_unix_fd_list_get_instance_private (list); } static void g_unix_fd_list_finalize (GObject *object) { GUnixFDList *list = G_UNIX_FD_LIST (object); gint i; for (i = 0; i < list->priv->nfd; i++) close (list->priv->fds[i]); g_free (list->priv->fds); G_OBJECT_CLASS (g_unix_fd_list_parent_class) ->finalize (object); } static void g_unix_fd_list_class_init (GUnixFDListClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->finalize = g_unix_fd_list_finalize; } static int dup_close_on_exec_fd (gint fd, GError **error) { gint new_fd; gint s; #ifdef F_DUPFD_CLOEXEC do new_fd = fcntl (fd, F_DUPFD_CLOEXEC, 0l); while (new_fd < 0 && (errno == EINTR)); if (new_fd >= 0) return new_fd; /* if that didn't work (new libc/old kernel?), try it the other way. */ #endif do new_fd = dup (fd); while (new_fd < 0 && (errno == EINTR)); if (new_fd < 0) { int saved_errno = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), "dup: %s", g_strerror (saved_errno)); return -1; } do { s = fcntl (new_fd, F_GETFD); if (s >= 0) s = fcntl (new_fd, F_SETFD, (long) (s | FD_CLOEXEC)); } while (s < 0 && (errno == EINTR)); if (s < 0) { int saved_errno = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), "fcntl: %s", g_strerror (saved_errno)); close (new_fd); return -1; } return new_fd; } /** * g_unix_fd_list_new: * * Creates a new #GUnixFDList containing no file descriptors. * * Returns: a new #GUnixFDList * * Since: 2.24 **/ GUnixFDList * g_unix_fd_list_new (void) { return g_object_new (G_TYPE_UNIX_FD_LIST, NULL); } /** * g_unix_fd_list_new_from_array: * @fds: (array length=n_fds): the initial list of file descriptors * @n_fds: the length of #fds, or -1 * * Creates a new #GUnixFDList containing the file descriptors given in * @fds. The file descriptors become the property of the new list and * may no longer be used by the caller. The array itself is owned by * the caller. * * Each file descriptor in the array should be set to close-on-exec. * * If @n_fds is -1 then @fds must be terminated with -1. * * Returns: a new #GUnixFDList * * Since: 2.24 **/ GUnixFDList * g_unix_fd_list_new_from_array (const gint *fds, gint n_fds) { GUnixFDList *list; g_return_val_if_fail (fds != NULL || n_fds == 0, NULL); if (n_fds == -1) for (n_fds = 0; fds[n_fds] != -1; n_fds++); list = g_object_new (G_TYPE_UNIX_FD_LIST, NULL); list->priv->fds = g_new (gint, n_fds + 1); list->priv->nfd = n_fds; if (n_fds > 0) memcpy (list->priv->fds, fds, sizeof (gint) * n_fds); list->priv->fds[n_fds] = -1; return list; } /** * g_unix_fd_list_steal_fds: * @list: a #GUnixFDList * @length: (out) (allow-none): pointer to the length of the returned * array, or %NULL * * Returns the array of file descriptors that is contained in this * object. * * After this call, the descriptors are no longer contained in * @list. Further calls will return an empty list (unless more * descriptors have been added). * * The return result of this function must be freed with g_free(). * The caller is also responsible for closing all of the file * descriptors. The file descriptors in the array are set to * close-on-exec. * * If @length is non-%NULL then it is set to the number of file * descriptors in the returned array. The returned array is also * terminated with -1. * * This function never returns %NULL. In case there are no file * descriptors contained in @list, an empty array is returned. * * Returns: (array length=length) (transfer full): an array of file * descriptors * * Since: 2.24 */ gint * g_unix_fd_list_steal_fds (GUnixFDList *list, gint *length) { gint *result; g_return_val_if_fail (G_IS_UNIX_FD_LIST (list), NULL); /* will be true for fresh object or if we were just called */ if (list->priv->fds == NULL) { list->priv->fds = g_new (gint, 1); list->priv->fds[0] = -1; list->priv->nfd = 0; } if (length) *length = list->priv->nfd; result = list->priv->fds; list->priv->fds = NULL; list->priv->nfd = 0; return result; } /** * g_unix_fd_list_peek_fds: * @list: a #GUnixFDList * @length: (out) (allow-none): pointer to the length of the returned * array, or %NULL * * Returns the array of file descriptors that is contained in this * object. * * After this call, the descriptors remain the property of @list. The * caller must not close them and must not free the array. The array is * valid only until @list is changed in any way. * * If @length is non-%NULL then it is set to the number of file * descriptors in the returned array. The returned array is also * terminated with -1. * * This function never returns %NULL. In case there are no file * descriptors contained in @list, an empty array is returned. * * Returns: (array length=length) (transfer none): an array of file * descriptors * * Since: 2.24 */ const gint * g_unix_fd_list_peek_fds (GUnixFDList *list, gint *length) { g_return_val_if_fail (G_IS_UNIX_FD_LIST (list), NULL); /* will be true for fresh object or if steal() was just called */ if (list->priv->fds == NULL) { list->priv->fds = g_new (gint, 1); list->priv->fds[0] = -1; list->priv->nfd = 0; } if (length) *length = list->priv->nfd; return list->priv->fds; } /** * g_unix_fd_list_append: * @list: a #GUnixFDList * @fd: a valid open file descriptor * @error: a #GError pointer * * Adds a file descriptor to @list. * * The file descriptor is duplicated using dup(). You keep your copy * of the descriptor and the copy contained in @list will be closed * when @list is finalized. * * A possible cause of failure is exceeding the per-process or * system-wide file descriptor limit. * * The index of the file descriptor in the list is returned. If you use * this index with g_unix_fd_list_get() then you will receive back a * duplicated copy of the same file descriptor. * * Returns: the index of the appended fd in case of success, else -1 * (and @error is set) * * Since: 2.24 */ gint g_unix_fd_list_append (GUnixFDList *list, gint fd, GError **error) { gint new_fd; g_return_val_if_fail (G_IS_UNIX_FD_LIST (list), -1); g_return_val_if_fail (fd >= 0, -1); g_return_val_if_fail (error == NULL || *error == NULL, -1); if ((new_fd = dup_close_on_exec_fd (fd, error)) < 0) return -1; list->priv->fds = g_realloc (list->priv->fds, sizeof (gint) * (list->priv->nfd + 2)); list->priv->fds[list->priv->nfd++] = new_fd; list->priv->fds[list->priv->nfd] = -1; return list->priv->nfd - 1; } /** * g_unix_fd_list_get: * @list: a #GUnixFDList * @index_: the index into the list * @error: a #GError pointer * * Gets a file descriptor out of @list. * * @index_ specifies the index of the file descriptor to get. It is a * programmer error for @index_ to be out of range; see * g_unix_fd_list_get_length(). * * The file descriptor is duplicated using dup() and set as * close-on-exec before being returned. You must call close() on it * when you are done. * * A possible cause of failure is exceeding the per-process or * system-wide file descriptor limit. * * Returns: the file descriptor, or -1 in case of error * * Since: 2.24 **/ gint g_unix_fd_list_get (GUnixFDList *list, gint index_, GError **error) { g_return_val_if_fail (G_IS_UNIX_FD_LIST (list), -1); g_return_val_if_fail (index_ < list->priv->nfd, -1); g_return_val_if_fail (error == NULL || *error == NULL, -1); return dup_close_on_exec_fd (list->priv->fds[index_], error); } /** * g_unix_fd_list_get_length: * @list: a #GUnixFDList * * Gets the length of @list (ie: the number of file descriptors * contained within). * * Returns: the length of @list * * Since: 2.24 **/ gint g_unix_fd_list_get_length (GUnixFDList *list) { g_return_val_if_fail (G_IS_UNIX_FD_LIST (list), 0); return list->priv->nfd; }