/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* dbus-watch.c DBusWatch implementation * * Copyright (C) 2002, 2003 Red Hat Inc. * * Licensed under the Academic Free License version 2.1 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "dbus-internals.h" #include "dbus-watch.h" #include "dbus-list.h" /** * @defgroup DBusWatchInternals DBusWatch implementation details * @ingroup DBusInternals * @brief implementation details for DBusWatch * * @{ */ /** * Implementation of DBusWatch */ struct DBusWatch { int refcount; /**< Reference count */ int fd; /**< File descriptor. */ unsigned int flags; /**< Conditions to watch. */ DBusWatchHandler handler; /**< Watch handler. */ void *handler_data; /**< Watch handler data. */ DBusFreeFunction free_handler_data_function; /**< Free the watch handler data. */ void *data; /**< Application data. */ DBusFreeFunction free_data_function; /**< Free the application data. */ unsigned int enabled : 1; /**< Whether it's enabled. */ }; dbus_bool_t _dbus_watch_get_enabled (DBusWatch *watch) { return watch->enabled; } /** * Creates a new DBusWatch. Used to add a file descriptor to be polled * by a main loop. * * @param fd the file descriptor to be watched. * @param flags the conditions to watch for on the descriptor. * @param enabled the initial enabled state * @param handler the handler function * @param data data for handler function * @param free_data_function function to free the data * @returns the new DBusWatch object. */ DBusWatch* _dbus_watch_new (int fd, unsigned int flags, dbus_bool_t enabled, DBusWatchHandler handler, void *data, DBusFreeFunction free_data_function) { DBusWatch *watch; #define VALID_WATCH_FLAGS (DBUS_WATCH_WRITABLE | DBUS_WATCH_READABLE) _dbus_assert ((flags & VALID_WATCH_FLAGS) == flags); watch = dbus_new0 (DBusWatch, 1); if (watch == NULL) return NULL; watch->refcount = 1; watch->fd = fd; watch->flags = flags; watch->enabled = enabled; watch->handler = handler; watch->handler_data = data; watch->free_handler_data_function = free_data_function; return watch; } /** * Increments the reference count of a DBusWatch object. * * @param watch the watch object. * @returns the watch object. */ DBusWatch * _dbus_watch_ref (DBusWatch *watch) { watch->refcount += 1; return watch; } /** * Decrements the reference count of a DBusWatch object * and finalizes the object if the count reaches zero. * * @param watch the watch object. */ void _dbus_watch_unref (DBusWatch *watch) { _dbus_assert (watch != NULL); _dbus_assert (watch->refcount > 0); watch->refcount -= 1; if (watch->refcount == 0) { dbus_watch_set_data (watch, NULL, NULL); /* call free_data_function */ if (watch->free_handler_data_function) (* watch->free_handler_data_function) (watch->handler_data); dbus_free (watch); } } /** * Clears the file descriptor from a now-invalid watch object so that * no one tries to use it. This is because a watch may stay alive due * to reference counts after the file descriptor is closed. * Invalidation makes it easier to catch bugs. It also * keeps people from doing dorky things like assuming file descriptors * are unique (never recycled). * * @param watch the watch object. */ void _dbus_watch_invalidate (DBusWatch *watch) { watch->fd = -1; watch->flags = 0; } /** * Sanitizes the given condition so that it only contains * flags that the DBusWatch requested. e.g. if the * watch is a DBUS_WATCH_READABLE watch then * DBUS_WATCH_WRITABLE will be stripped from the condition. * * @param watch the watch object. * @param condition address of the condition to sanitize. */ void _dbus_watch_sanitize_condition (DBusWatch *watch, unsigned int *condition) { if (!(watch->flags & DBUS_WATCH_READABLE)) *condition &= ~DBUS_WATCH_READABLE; if (!(watch->flags & DBUS_WATCH_WRITABLE)) *condition &= ~DBUS_WATCH_WRITABLE; } /** * @typedef DBusWatchList * * Opaque data type representing a list of watches * and a set of DBusAddWatchFunction/DBusRemoveWatchFunction. * Automatically handles removing/re-adding watches * when the DBusAddWatchFunction is updated or changed. * Holds a reference count to each watch. * * Used in the implementation of both DBusServer and * DBusClient. * */ /** * DBusWatchList implementation details. All fields * are private. * */ struct DBusWatchList { DBusList *watches; /**< Watch objects. */ DBusAddWatchFunction add_watch_function; /**< Callback for adding a watch. */ DBusRemoveWatchFunction remove_watch_function; /**< Callback for removing a watch. */ DBusWatchToggledFunction watch_toggled_function; /**< Callback on toggling enablement */ void *watch_data; /**< Data for watch callbacks */ DBusFreeFunction watch_free_data_function; /**< Free function for watch callback data */ }; /** * Creates a new watch list. Returns #NULL if insufficient * memory exists. * * @returns the new watch list, or #NULL on failure. */ DBusWatchList* _dbus_watch_list_new (void) { DBusWatchList *watch_list; watch_list = dbus_new0 (DBusWatchList, 1); if (watch_list == NULL) return NULL; return watch_list; } /** * Frees a DBusWatchList. * * @param watch_list the watch list. */ void _dbus_watch_list_free (DBusWatchList *watch_list) { /* free watch_data and removes watches as a side effect */ _dbus_watch_list_set_functions (watch_list, NULL, NULL, NULL, NULL, NULL); _dbus_list_foreach (&watch_list->watches, (DBusForeachFunction) _dbus_watch_unref, NULL); _dbus_list_clear (&watch_list->watches); dbus_free (watch_list); } /** * Sets the watch functions. This function is the "backend" * for dbus_connection_set_watch_functions() and * dbus_server_set_watch_functions(). * * @param watch_list the watch list. * @param add_function the add watch function. * @param remove_function the remove watch function. * @param toggled_function function on toggling enabled flag, or #NULL * @param data the data for those functions. * @param free_data_function the function to free the data. * @returns #FALSE if not enough memory * */ dbus_bool_t _dbus_watch_list_set_functions (DBusWatchList *watch_list, DBusAddWatchFunction add_function, DBusRemoveWatchFunction remove_function, DBusWatchToggledFunction toggled_function, void *data, DBusFreeFunction free_data_function) { /* Add watches with the new watch function, failing on OOM */ if (add_function != NULL) { DBusList *link; link = _dbus_list_get_first_link (&watch_list->watches); while (link != NULL) { DBusList *next = _dbus_list_get_next_link (&watch_list->watches, link); #ifdef DBUS_ENABLE_VERBOSE_MODE { const char *watch_type; int flags; flags = dbus_watch_get_flags (link->data); if ((flags & DBUS_WATCH_READABLE) && (flags & DBUS_WATCH_WRITABLE)) watch_type = "readwrite"; else if (flags & DBUS_WATCH_READABLE) watch_type = "read"; else if (flags & DBUS_WATCH_WRITABLE) watch_type = "write"; else watch_type = "not read or write"; _dbus_verbose ("Adding a %s watch on fd %d using newly-set add watch function\n", watch_type, dbus_watch_get_socket (link->data)); } #endif /* DBUS_ENABLE_VERBOSE_MODE */ if (!(* add_function) (link->data, data)) { /* remove it all again and return FALSE */ DBusList *link2; link2 = _dbus_list_get_first_link (&watch_list->watches); while (link2 != link) { DBusList *next = _dbus_list_get_next_link (&watch_list->watches, link2); _dbus_verbose ("Removing watch on fd %d using newly-set remove function because initial add failed\n", dbus_watch_get_socket (link2->data)); (* remove_function) (link2->data, data); link2 = next; } return FALSE; } link = next; } } /* Remove all current watches from previous watch handlers */ if (watch_list->remove_watch_function != NULL) { _dbus_verbose ("Removing all pre-existing watches\n"); _dbus_list_foreach (&watch_list->watches, (DBusForeachFunction) watch_list->remove_watch_function, watch_list->watch_data); } if (watch_list->watch_free_data_function != NULL) (* watch_list->watch_free_data_function) (watch_list->watch_data); watch_list->add_watch_function = add_function; watch_list->remove_watch_function = remove_function; watch_list->watch_toggled_function = toggled_function; watch_list->watch_data = data; watch_list->watch_free_data_function = free_data_function; return TRUE; } /** * Adds a new watch to the watch list, invoking the * application DBusAddWatchFunction if appropriate. * * @param watch_list the watch list. * @param watch the watch to add. * @returns #TRUE on success, #FALSE if no memory. */ dbus_bool_t _dbus_watch_list_add_watch (DBusWatchList *watch_list, DBusWatch *watch) { if (!_dbus_list_append (&watch_list->watches, watch)) return FALSE; _dbus_watch_ref (watch); if (watch_list->add_watch_function != NULL) { _dbus_verbose ("Adding watch on fd %d\n", dbus_watch_get_socket (watch)); if (!(* watch_list->add_watch_function) (watch, watch_list->watch_data)) { _dbus_list_remove_last (&watch_list->watches, watch); _dbus_watch_unref (watch); return FALSE; } } return TRUE; } /** * Removes a watch from the watch list, invoking the * application's DBusRemoveWatchFunction if appropriate. * * @param watch_list the watch list. * @param watch the watch to remove. */ void _dbus_watch_list_remove_watch (DBusWatchList *watch_list, DBusWatch *watch) { if (!_dbus_list_remove (&watch_list->watches, watch)) _dbus_assert_not_reached ("Nonexistent watch was removed"); if (watch_list->remove_watch_function != NULL) { _dbus_verbose ("Removing watch on fd %d\n", dbus_watch_get_socket (watch)); (* watch_list->remove_watch_function) (watch, watch_list->watch_data); } _dbus_watch_unref (watch); } /** * Sets a watch to the given enabled state, invoking the * application's DBusWatchToggledFunction if appropriate. * * @param watch_list the watch list. * @param watch the watch to toggle. * @param enabled #TRUE to enable */ void _dbus_watch_list_toggle_watch (DBusWatchList *watch_list, DBusWatch *watch, dbus_bool_t enabled) { enabled = !!enabled; if (enabled == watch->enabled) return; watch->enabled = enabled; if (watch_list->watch_toggled_function != NULL) { _dbus_verbose ("Toggling watch %p on fd %d to %d\n", watch, dbus_watch_get_socket (watch), watch->enabled); (* watch_list->watch_toggled_function) (watch, watch_list->watch_data); } } /** * Sets the handler for the watch. * * @todo this function only exists because of the weird * way connection watches are done, see the note * in docs for _dbus_connection_handle_watch(). * * @param watch the watch * @param handler the new handler * @param data the data * @param free_data_function free data with this */ void _dbus_watch_set_handler (DBusWatch *watch, DBusWatchHandler handler, void *data, DBusFreeFunction free_data_function) { if (watch->free_handler_data_function) (* watch->free_handler_data_function) (watch->handler_data); watch->handler = handler; watch->handler_data = data; watch->free_handler_data_function = free_data_function; } /** @} */ /** * @defgroup DBusWatch DBusWatch * @ingroup DBus * @brief Object representing a file descriptor to be watched. * * Types and functions related to DBusWatch. A watch represents * a file descriptor that the main loop needs to monitor, * as in Qt's QSocketNotifier or GLib's g_io_add_watch(). * * Use dbus_connection_set_watch_functions() or dbus_server_set_watch_functions() * to be notified when libdbus needs to add or remove watches. * * @{ */ /** * @typedef DBusWatch * * Opaque object representing a file descriptor * to be watched for changes in readability, * writability, or hangup. */ /** * Deprecated former name of dbus_watch_get_unix_fd(). * * @param watch the DBusWatch object. * @returns the file descriptor to watch. */ int dbus_watch_get_fd (DBusWatch *watch) { return dbus_watch_get_unix_fd(watch); } /** * Returns a UNIX file descriptor to be watched, * which may be a pipe, socket, or other type of * descriptor. On UNIX this is preferred to * dbus_watch_get_socket() since it works with * more kinds of #DBusWatch. * * Always returns -1 on Windows. On Windows you use * dbus_watch_get_socket() to get a Winsock socket to watch. * * @param watch the DBusWatch object. * @returns the file descriptor to watch. */ int dbus_watch_get_unix_fd (DBusWatch *watch) { /* FIXME remove #ifdef and do this on a lower level * (watch should have set_socket and set_unix_fd and track * which it has, and the transport should provide the * appropriate watch type) */ #ifdef DBUS_UNIX return watch->fd; #else return -1; #endif } /** * Returns a socket to be watched, on UNIX this will return -1 if our * transport is not socket-based so dbus_watch_get_unix_fd() is * preferred. * * On Windows, dbus_watch_get_unix_fd() returns -1 but this function * returns a Winsock socket (assuming the transport is socket-based, * as it always is for now). * * @param watch the DBusWatch object. * @returns the socket to watch. */ int dbus_watch_get_socket (DBusWatch *watch) { return watch->fd; } /** * Gets flags from DBusWatchFlags indicating * what conditions should be monitored on the * file descriptor. * * The flags returned will only contain DBUS_WATCH_READABLE * and DBUS_WATCH_WRITABLE, never DBUS_WATCH_HANGUP or * DBUS_WATCH_ERROR; all watches implicitly include a watch * for hangups, errors, and other exceptional conditions. * * @param watch the DBusWatch object. * @returns the conditions to watch. */ unsigned int dbus_watch_get_flags (DBusWatch *watch) { _dbus_assert ((watch->flags & VALID_WATCH_FLAGS) == watch->flags); return watch->flags; } /** * Gets data previously set with dbus_watch_set_data() * or #NULL if none. * * @param watch the DBusWatch object. * @returns previously-set data. */ void* dbus_watch_get_data (DBusWatch *watch) { return watch->data; } /** * Sets data which can be retrieved with dbus_watch_get_data(). * Intended for use by the DBusAddWatchFunction and * DBusRemoveWatchFunction to store their own data. For example with * Qt you might store the QSocketNotifier for this watch and with GLib * you might store a GSource. * * @param watch the DBusWatch object. * @param data the data. * @param free_data_function function to be called to free the data. */ void dbus_watch_set_data (DBusWatch *watch, void *data, DBusFreeFunction free_data_function) { _dbus_verbose ("Setting watch fd %d data to data = %p function = %p from data = %p function = %p\n", dbus_watch_get_socket (watch), data, free_data_function, watch->data, watch->free_data_function); if (watch->free_data_function != NULL) (* watch->free_data_function) (watch->data); watch->data = data; watch->free_data_function = free_data_function; } /** * Returns whether a watch is enabled or not. If not * enabled, it should not be polled by the main loop. * * @param watch the DBusWatch object * @returns #TRUE if the watch is enabled */ dbus_bool_t dbus_watch_get_enabled (DBusWatch *watch) { _dbus_assert (watch != NULL); return watch->enabled; } /** * Called to notify the D-Bus library when a previously-added watch is * ready for reading or writing, or has an exception such as a hangup. * * If this function returns #FALSE, then the file descriptor may still * be ready for reading or writing, but more memory is needed in order * to do the reading or writing. If you ignore the #FALSE return, your * application may spin in a busy loop on the file descriptor until * memory becomes available, but nothing more catastrophic should * happen. * * dbus_watch_handle() cannot be called during the * DBusAddWatchFunction, as the connection will not be ready to handle * that watch yet. * * It is not allowed to reference a DBusWatch after it has been passed * to remove_function. * * @param watch the DBusWatch object. * @param flags the poll condition using #DBusWatchFlags values * @returns #FALSE if there wasn't enough memory */ dbus_bool_t dbus_watch_handle (DBusWatch *watch, unsigned int flags) { #ifndef DBUS_DISABLE_CHECKS if (watch->fd < 0 || watch->flags == 0) { _dbus_warn_check_failed ("%s: Watch is invalid, it should have been removed\n", _DBUS_FUNCTION_NAME); return TRUE; } #endif _dbus_return_val_if_fail (watch->fd >= 0 /* fails if watch was removed */, TRUE); _dbus_watch_sanitize_condition (watch, &flags); if (flags == 0) { _dbus_verbose ("After sanitization, watch flags on fd %d were 0\n", watch->fd); return TRUE; } else return (* watch->handler) (watch, flags, watch->handler_data); } /** @} */