| /* -*- 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 <config.h> |
| #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 */ |
| DBusPollable 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. */ |
| unsigned int oom_last_time : 1; /**< Whether it was OOM last time. */ |
| }; |
| |
| dbus_bool_t |
| _dbus_watch_get_enabled (DBusWatch *watch) |
| { |
| return watch->enabled; |
| } |
| |
| dbus_bool_t |
| _dbus_watch_get_oom_last_time (DBusWatch *watch) |
| { |
| return watch->oom_last_time; |
| } |
| |
| void |
| _dbus_watch_set_oom_last_time (DBusWatch *watch, |
| dbus_bool_t oom) |
| { |
| watch->oom_last_time = oom; |
| } |
| |
| /** |
| * 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 (DBusPollable 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) |
| { |
| if (_dbus_pollable_is_valid (watch->fd)) |
| _dbus_warn ("this watch should have been invalidated"); |
| |
| 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) |
| { |
| _dbus_pollable_invalidate (&watch->fd); |
| 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); |
| } |
| |
| #ifdef DBUS_ENABLE_VERBOSE_MODE |
| static const char* |
| watch_flags_to_string (int flags) |
| { |
| const char *watch_type; |
| |
| 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"; |
| return watch_type; |
| } |
| #endif /* DBUS_ENABLE_VERBOSE_MODE */ |
| |
| /** |
| * 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 |
| DBusWatch *watch = link->data; |
| |
| _dbus_verbose ("Adding a %s watch on fd %" DBUS_POLLABLE_FORMAT " using newly-set add watch function\n", |
| watch_flags_to_string (dbus_watch_get_flags (link->data)), |
| _dbus_pollable_printable (watch->fd)); |
| #endif |
| |
| 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); |
| #ifdef DBUS_ENABLE_VERBOSE_MODE |
| DBusWatch *watch2 = link2->data; |
| |
| _dbus_verbose ("Removing watch on fd %" DBUS_POLLABLE_FORMAT " using newly-set remove function because initial add failed\n", |
| _dbus_pollable_printable (watch2->fd)); |
| #endif |
| |
| (* 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 %" DBUS_POLLABLE_FORMAT "\n", |
| _dbus_pollable_printable (watch->fd)); |
| |
| 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 %" DBUS_POLLABLE_FORMAT "\n", |
| _dbus_pollable_printable (watch->fd)); |
| |
| (* 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 %" DBUS_POLLABLE_FORMAT " to %d\n", |
| watch, |
| _dbus_pollable_printable (watch->fd), |
| watch->enabled); |
| |
| (* watch_list->watch_toggled_function) (watch, |
| watch_list->watch_data); |
| } |
| } |
| |
| /** |
| * Sets all watches to the given enabled state, invoking the |
| * application's DBusWatchToggledFunction if appropriate. |
| * |
| * @param watch_list the watch list. |
| * @param enabled #TRUE to enable |
| */ |
| void |
| _dbus_watch_list_toggle_all_watches (DBusWatchList *watch_list, |
| dbus_bool_t enabled) |
| { |
| DBusList *link; |
| |
| for (link = _dbus_list_get_first_link (&watch_list->watches); |
| link != NULL; |
| link = _dbus_list_get_next_link (&watch_list->watches, link)) |
| { |
| _dbus_watch_list_toggle_watch (watch_list, link->data, enabled); |
| } |
| } |
| |
| /** |
| * 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) |
| { |
| _dbus_return_val_if_fail (watch != NULL, -1); |
| |
| 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) |
| { |
| _dbus_return_val_if_fail (watch != NULL, -1); |
| |
| /* 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 dbus_watch_get_socket( watch ); |
| #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) |
| { |
| _dbus_return_val_if_fail (watch != NULL, -1); |
| |
| #ifdef DBUS_UNIX |
| return watch->fd; |
| #else |
| return _dbus_socket_get_int (watch->fd); |
| #endif |
| } |
| |
| DBusSocket |
| _dbus_watch_get_socket (DBusWatch *watch) |
| { |
| DBusSocket s; |
| |
| _dbus_assert (watch != NULL); |
| |
| #ifdef DBUS_UNIX |
| s.fd = watch->fd; |
| #else |
| s = watch->fd; |
| #endif |
| |
| return s; |
| } |
| |
| DBusPollable |
| _dbus_watch_get_pollable (DBusWatch *watch) |
| { |
| _dbus_assert (watch != NULL); |
| |
| 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_return_val_if_fail (watch != NULL, 0); |
| _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) |
| { |
| _dbus_return_val_if_fail (watch != NULL, NULL); |
| |
| 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_return_if_fail (watch != NULL); |
| |
| _dbus_verbose ("Setting watch fd %" DBUS_POLLABLE_FORMAT " data to data = %p function = %p from data = %p function = %p\n", |
| _dbus_pollable_printable (watch->fd), |
| 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_return_val_if_fail (watch != NULL, FALSE); |
| |
| 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) |
| { |
| _dbus_return_val_if_fail (watch != NULL, FALSE); |
| |
| #ifndef DBUS_DISABLE_CHECKS |
| if (!_dbus_pollable_is_valid (watch->fd) || watch->flags == 0) |
| { |
| _dbus_warn_check_failed ("Watch is invalid, it should have been removed\n"); |
| return TRUE; |
| } |
| #endif |
| |
| _dbus_return_val_if_fail (_dbus_pollable_is_valid (watch->fd) /* fails if watch was removed */, TRUE); |
| |
| _dbus_watch_sanitize_condition (watch, &flags); |
| |
| if (flags == 0) |
| { |
| _dbus_verbose ("After sanitization, watch flags on fd %" DBUS_POLLABLE_FORMAT " were 0\n", |
| _dbus_pollable_printable (watch->fd)); |
| return TRUE; |
| } |
| else |
| return (* watch->handler) (watch, flags, |
| watch->handler_data); |
| } |
| |
| |
| /** @} */ |