/* * Copyright © 2017 Pekka Paalanen * Copyright © 2018 Zodiac Inflight Innovations * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include "shared/helpers.h" #include #include "weston-log-internal.h" #include "weston-debug-server-protocol.h" #include #include #include #include #include #include /** * @defgroup log Public Logging/Debugging API * @defgroup internal-log Private/Internal Logging/Debugging API * @defgroup debug-protocol weston-debug protocol specific */ /** Main weston-log context * * One per weston_compositor. Stores list of scopes created and a list pending * subscriptions. * * A pending subscription is a subscription to a scope which hasn't been * created. When the scope is finally created the pending subscription will be * removed from the pending subscription list, but not before was added in the * scope's subscription list and that of the subscriber list. * * Pending subscriptions only make sense for other types of streams, other than * those created by weston-debug protocol. In the case of the weston-debug * protocol, the subscription processes is done automatically whenever a client * connects and subscribes to a scope which was previously advertised by the * compositor. * * @ingroup internal-log */ struct weston_log_context { struct wl_global *global; struct wl_list scope_list; /**< weston_log_scope::compositor_link */ struct wl_list pending_subscription_list; /**< weston_log_subscription::source_link */ }; /** weston-log message scope * * This is used for scoping logging/debugging messages. Clients can subscribe * to only the scopes they are interested in. A scope is identified by its name * (also referred to as debug stream name). * * @ingroup log */ struct weston_log_scope { char *name; char *desc; weston_log_scope_cb new_subscription; weston_log_scope_cb destroy_subscription; void *user_data; struct wl_list compositor_link; struct wl_list subscription_list; /**< weston_log_subscription::source_link */ }; /** Ties a subscriber to a scope * * A subscription is created each time we'd want to subscribe to a scope. From * the stream type we can retrieve the subscriber and from the subscriber we * reach each of the streams callbacks. See also weston_log_subscriber object. * * When a subscription has been created we store it in the scope's subscription * list and in the subscriber's subscription list. The subscription might be a * pending subscription until the scope for which there's was a subscribe has * been created. The scope creation will take of looking through the pending * subscription list. * * A subscription can reached from a subscriber subscription list by using the * streams base class. * * @ingroup internal-log */ struct weston_log_subscription { struct weston_log_subscriber *owner; struct wl_list owner_link; /**< weston_log_subscriber::subscription_list */ char *scope_name; struct weston_log_scope *source; struct wl_list source_link; /**< weston_log_scope::subscription_list or weston_log_context::pending_subscription_list */ void *data; }; static struct weston_log_subscription * find_pending_subscription(struct weston_log_context *log_ctx, const char *scope_name) { struct weston_log_subscription *sub; wl_list_for_each(sub, &log_ctx->pending_subscription_list, source_link) if (!strcmp(sub->scope_name, scope_name)) return sub; return NULL; } /** Create a pending subscription and add it the list of pending subscriptions * * @param owner a subscriber represented by weston_log_subscriber object * @param scope_name the name of the scope (which we don't have in the list of scopes) * @param log_ctx the logging context used to add the pending subscription * * @memberof weston_log_subscription */ static void weston_log_subscription_create_pending(struct weston_log_subscriber *owner, const char *scope_name, struct weston_log_context *log_ctx) { assert(owner); assert(scope_name); struct weston_log_subscription *sub = zalloc(sizeof(*sub)); if (!sub) return; sub->scope_name = strdup(scope_name); sub->owner = owner; wl_list_insert(&log_ctx->pending_subscription_list, &sub->source_link); } /** Destroys the pending subscription created previously with * weston_log_subscription_create_pending() * * @param sub the weston_log_subscription object to remove from the list * of subscriptions and to destroy the subscription * * @memberof weston_log_subscription */ static void weston_log_subscription_destroy_pending(struct weston_log_subscription *sub) { assert(sub); /* pending subsriptions do not have a source */ wl_list_remove(&sub->source_link); free(sub->scope_name); free(sub); } /** Write to the stream's subscription * * @memberof weston_log_subscription */ static void weston_log_subscription_write(struct weston_log_subscription *sub, const char *data, size_t len) { if (sub->owner && sub->owner->write) sub->owner->write(sub->owner, data, len); } /** Write a formatted string to the stream's subscription * * @memberof weston_log_subscription */ static void weston_log_subscription_vprintf(struct weston_log_subscription *sub, const char *fmt, va_list ap) { static const char oom[] = "Out of memory"; char *str; int len; if (!weston_log_scope_is_enabled(sub->source)) return; len = vasprintf(&str, fmt, ap); if (len >= 0) { weston_log_subscription_write(sub, str, len); free(str); } else { weston_log_subscription_write(sub, oom, sizeof oom - 1); } } void weston_log_subscription_set_data(struct weston_log_subscription *sub, void *data) { /* don't allow data to already be set */ assert(!sub->data); sub->data = data; } void * weston_log_subscription_get_data(struct weston_log_subscription *sub) { return sub->data; } /** Creates a new subscription using the subscriber by \c owner. * * The subscription created is added to the \c owner subscription list. * Destroying the subscription using weston_log_subscription_destroy() will * remove the link from the subscription list and free storage alloc'ed. * * Note that this adds the subscription to the scope's subscription list * hence the \c scope required argument. * * @param owner the subscriber owner, must be created before creating a * subscription * @param scope the scope in order to add the subscription to the scope's * subscription list * @returns a weston_log_subscription object in case of success, or NULL * otherwise * * @sa weston_log_subscription_destroy, weston_log_subscription_remove, * weston_log_subscription_add * @memberof weston_log_subscription */ void weston_log_subscription_create(struct weston_log_subscriber *owner, struct weston_log_scope *scope) { struct weston_log_subscription *sub; assert(owner); assert(scope); assert(scope->name); sub = zalloc(sizeof(*sub)); if (!sub) return; sub->owner = owner; sub->scope_name = strdup(scope->name); wl_list_insert(&sub->owner->subscription_list, &sub->owner_link); weston_log_subscription_add(scope, sub); weston_log_run_cb_new_subscription(sub); } /** Destroys the subscription * * Removes the subscription from the scopes subscription list and from * subscriber's subscription list. It destroys the subscription afterwads. * * @memberof weston_log_subscription */ void weston_log_subscription_destroy(struct weston_log_subscription *sub) { assert(sub); if (sub->source->destroy_subscription) sub->source->destroy_subscription(sub, sub->source->user_data); if (sub->owner) wl_list_remove(&sub->owner_link); weston_log_subscription_remove(sub); free(sub->scope_name); free(sub); } /** Retrieve a subscription by using the subscriber * * This is useful when trying to find a subscription from the subscriber by * having only access to the stream. * * @param subscriber the subscriber in question * @returns a weston_log_subscription object * * @memberof weston_log_subscription */ struct weston_log_subscription * weston_log_subscriber_get_only_subscription(struct weston_log_subscriber *subscriber) { struct weston_log_subscription *sub; /* unlikely, but can happen */ if (wl_list_length(&subscriber->subscription_list) == 0) return NULL; assert(wl_list_length(&subscriber->subscription_list) == 1); return wl_container_of(subscriber->subscription_list.prev, sub, owner_link); } /** Adds the subscription \c sub to the subscription list of the * scope. * * This should used when the scope has been created, and the subscription \c * sub has be created before calling this function. * * @param scope the scope * @param sub the subscription, it must be created before, see * weston_log_subscription_create() * * @memberof weston_log_subscription */ void weston_log_subscription_add(struct weston_log_scope *scope, struct weston_log_subscription *sub) { assert(scope); assert(sub); /* don't allow subscriptions to have a source already! */ assert(!sub->source); sub->source = scope; wl_list_insert(&scope->subscription_list, &sub->source_link); } /** Removes the subscription from the scope's subscription list * * @memberof weston_log_subscription */ void weston_log_subscription_remove(struct weston_log_subscription *sub) { assert(sub); if (sub->source) wl_list_remove(&sub->source_link); sub->source = NULL; } /** Look-up the scope from the scope list stored in the log context, by * matching against the \c name. * * @param log_ctx * @param name the scope name, see weston_log_ctx_add_log_scope() and * weston_compositor_add_log_scope() * @returns NULL if none found, or a pointer to a weston_log_scope * * @ingroup internal-log */ struct weston_log_scope * weston_log_get_scope(struct weston_log_context *log_ctx, const char *name) { struct weston_log_scope *scope; wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) if (strcmp(name, scope->name) == 0) return scope; return NULL; } /** Wrapper to invoke the weston_log_scope_cb. Allows to call the cb * new_subscription of a log scope. * * @ingroup internal-log */ void weston_log_run_cb_new_subscription(struct weston_log_subscription *sub) { if (sub->source->new_subscription) sub->source->new_subscription(sub, sub->source->user_data); } /** Advertise the log scope name and the log scope description * * This is only used by the weston-debug protocol! * * @ingroup internal-log */ void weston_debug_protocol_advertise_scopes(struct weston_log_context *log_ctx, struct wl_resource *res) { struct weston_log_scope *scope; wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) weston_debug_v1_send_available(res, scope->name, scope->desc); } /** * Connect weston_compositor structure to weston_log_context structure. * * \param compositor * \param log_ctx * \return 0 on success, -1 on failure * * Sets weston_compositor::weston_log_ctx. * * @ingroup log */ int weston_log_ctx_compositor_setup(struct weston_compositor *compositor, struct weston_log_context *log_ctx) { assert(!compositor->weston_log_ctx); assert(log_ctx); compositor->weston_log_ctx = log_ctx; return 0; } /** Creates weston_log_context structure * * \return NULL in case of failure, or a weston_log_context object in case of * success * * weston_log_context is a singleton for each weston_compositor. * @ingroup log * */ WL_EXPORT struct weston_log_context * weston_log_ctx_compositor_create(void) { struct weston_log_context *log_ctx; log_ctx = zalloc(sizeof *log_ctx); if (!log_ctx) return NULL; wl_list_init(&log_ctx->scope_list); wl_list_init(&log_ctx->pending_subscription_list); return log_ctx; } /** Destroy weston_log_context structure * * \param compositor The libweston compositor whose weston-debug to tear down. * * Clears weston_compositor::weston_log_ctx. * @ingroup log * */ WL_EXPORT void weston_log_ctx_compositor_destroy(struct weston_compositor *compositor) { struct weston_log_context *log_ctx = compositor->weston_log_ctx; struct weston_log_scope *scope; struct weston_log_subscription *pending_sub, *pending_sub_tmp; if (log_ctx->global) wl_global_destroy(log_ctx->global); wl_list_for_each(scope, &log_ctx->scope_list, compositor_link) fprintf(stderr, "Internal warning: debug scope '%s' has not been destroyed.\n", scope->name); /* Remove head to not crash if scope removed later. */ wl_list_remove(&log_ctx->scope_list); /* Remove any pending subscription(s) which nobody subscribed to */ wl_list_for_each_safe(pending_sub, pending_sub_tmp, &log_ctx->pending_subscription_list, source_link) { weston_log_subscription_destroy_pending(pending_sub); } /* pending_subscription_list should be empty at this point */ free(log_ctx); compositor->weston_log_ctx = NULL; } /** Enable weston-debug protocol extension * * \param compositor The libweston compositor where to enable. * * This enables the weston_debug_v1 Wayland protocol extension which any client * can use to get debug messages from the compositor. * * WARNING: This feature should not be used in production. If a client * provides a file descriptor that blocks writes, it will block the whole * compositor indefinitely. * * There is no control on which client is allowed to subscribe to debug * messages. Any and all clients are allowed. * * The debug extension is disabled by default, and once enabled, cannot be * disabled again. * * @ingroup debug-protocol */ WL_EXPORT void weston_compositor_enable_debug_protocol(struct weston_compositor *compositor) { struct weston_log_context *log_ctx = compositor->weston_log_ctx; assert(log_ctx); if (log_ctx->global) return; log_ctx->global = wl_global_create(compositor->wl_display, &weston_debug_v1_interface, 1, log_ctx, weston_log_bind_weston_debug); if (!log_ctx->global) return; fprintf(stderr, "WARNING: debug protocol has been enabled. " "This is a potential denial-of-service attack vector and " "information leak.\n"); } /** Determine if the debug protocol has been enabled * * \param wc The libweston compositor to verify if debug protocol has been * enabled * * @ingroup debug-protocol */ WL_EXPORT bool weston_compositor_is_debug_protocol_enabled(struct weston_compositor *wc) { return wc->weston_log_ctx->global != NULL; } /** Register a new stream name, creating a log scope. * * @param log_ctx The weston_log_context where to add. * @param name The debug stream/scope name; must not be NULL. * @param description The log scope description for humans; must not be NULL. * @param new_subscription Optional callback when a client subscribes to this * scope. * @param destroy_subscription Optional callback when a client destroys the * subscription. * @param user_data Optional user data pointer for the callback. * @returns A valid pointer on success, NULL on failure. * * This function is used to create a log scope. All debug message printing * happens for a scope, which allows clients to subscribe to the kind of debug * messages they want by \c name. For the weston-debug protocol, * subscription for the scope will happen automatically but for other types of * streams, weston_log_subscribe() should be called as to create a subscription * and tie it to the scope created by this function. * * \p name must be unique in the weston_compositor instance. \p name * and \p description must both be provided. In case of the weston-debug * protocol, the description is printed when a client asks for a list of * supported log scopes. * * \p new_subscription, if not NULL, is called when a client subscribes to the log * scope creating a debug stream. This is for log scopes that need to print * messages as a response to a client appearing, e.g. printing a list of * windows on demand or a static preamble. The argument \p user_data is * passed in to the callback and is otherwise unused. * * For one-shot debug streams, \c new_subscription should finally call * weston_log_subscription_complete() to close the stream and tell the client * the printing is complete. Otherwise the client expects more data to be * written. The complete callback in weston_log_subscriber should be installed * to trigger it and it is set-up automatically for the weston-debug protocol. * * As subscription can take place before creating the scope, any pending * subscriptions to scope added by weston_log_subscribe(), will be checked * against the scope being created and if found will be added to the scope's * subscription list. * * The log scope must be destroyed using weston_log_scope_destroy() * before destroying the weston_compositor. * * @memberof weston_log_scope * @sa weston_log_scope_cb, weston_log_subscribe */ WL_EXPORT struct weston_log_scope * weston_log_ctx_add_log_scope(struct weston_log_context *log_ctx, const char *name, const char *description, weston_log_scope_cb new_subscription, weston_log_scope_cb destroy_subscription, void *user_data) { struct weston_log_scope *scope; struct weston_log_subscription *pending_sub = NULL; if (!name || !description) { fprintf(stderr, "Error: cannot add a debug scope without name or description.\n"); return NULL; } if (!log_ctx) { fprintf(stderr, "Error: cannot add debug scope '%s', infra not initialized.\n", name); return NULL; } if (weston_log_get_scope(log_ctx, name)){ fprintf(stderr, "Error: debug scope named '%s' is already registered.\n", name); return NULL; } scope = zalloc(sizeof *scope); if (!scope) { fprintf(stderr, "Error adding debug scope '%s': out of memory.\n", name); return NULL; } scope->name = strdup(name); scope->desc = strdup(description); scope->new_subscription = new_subscription; scope->destroy_subscription = destroy_subscription; scope->user_data = user_data; wl_list_init(&scope->subscription_list); if (!scope->name || !scope->desc) { fprintf(stderr, "Error adding debug scope '%s': out of memory.\n", name); free(scope->name); free(scope->desc); free(scope); return NULL; } wl_list_insert(log_ctx->scope_list.prev, &scope->compositor_link); /* check if there are any pending subscriptions to this scope */ while ((pending_sub = find_pending_subscription(log_ctx, scope->name)) != NULL) { weston_log_subscription_create(pending_sub->owner, scope); /* remove it from pending */ weston_log_subscription_destroy_pending(pending_sub); } return scope; } /** Register a new stream name, creating a log scope. * * @param compositor The compositor that contains the log context where the log * scope will be linked. * @param name The debug stream/scope name; must not be NULL. * @param description The log scope description for humans; must not be NULL. * @param new_subscription Optional callback when a client subscribes to this * scope. * @param destroy_subscription Optional callback when a client destroys the * subscription. * @param user_data Optional user data pointer for the callback. * @returns A valid pointer on success, NULL on failure. * * This function works like weston_log_ctx_add_log_scope(), but the log scope * created is linked to the log context of \c compositor. * * @memberof weston_compositor * @sa weston_log_ctx_add_log_scope */ WL_EXPORT struct weston_log_scope * weston_compositor_add_log_scope(struct weston_compositor *compositor, const char *name, const char *description, weston_log_scope_cb new_subscription, weston_log_scope_cb destroy_subscription, void *user_data) { struct weston_log_scope *scope; scope = weston_log_ctx_add_log_scope(compositor->weston_log_ctx, name, description, new_subscription, destroy_subscription, user_data); return scope; } /** Destroy a log scope * * @param scope The log scope to destroy; may be NULL. * * Destroys the log scope, calling each stream's destroy callback if one was * installed/created. * * @memberof weston_log_scope */ WL_EXPORT void weston_log_scope_destroy(struct weston_log_scope *scope) { struct weston_log_subscription *sub, *sub_tmp; if (!scope) return; wl_list_for_each_safe(sub, sub_tmp, &scope->subscription_list, source_link) { /* destroy each subscription */ if (sub->owner->destroy) sub->owner->destroy(sub->owner); weston_log_subscription_destroy(sub); } wl_list_remove(&scope->compositor_link); free(scope->name); free(scope->desc); free(scope); } /** Are there any active subscriptions to the scope? * * \param scope The log scope to check; may be NULL. * \return True if any streams are open for this scope, false otherwise. * * As printing some debugging messages may be relatively expensive, one * can use this function to determine if there is a need to gather the * debugging information at all. If this function returns false, all * printing for this scope is dropped, so gathering the information is * pointless. * * The return value of this function should not be stored, as new clients * may subscribe to the debug scope later. * * If the given scope is NULL, this function will always return false, * making it safe to use in teardown or destroy code, provided the * scope is initialized to NULL before creation and set to NULL after * destruction. * * \memberof weston_log_scope */ WL_EXPORT bool weston_log_scope_is_enabled(struct weston_log_scope *scope) { if (!scope) return false; return !wl_list_empty(&scope->subscription_list); } /** Close the stream's complete callback if one was installed/created. * * @ingroup log */ WL_EXPORT void weston_log_subscription_complete(struct weston_log_subscription *sub) { if (sub->owner && sub->owner->complete) sub->owner->complete(sub->owner); } /** Close the log scope. * * @param scope The log scope to complete; may be NULL. * * Complete the log scope, calling each stream's complete callback if one was * installed/created. This can be useful to signal the reading end that the * data has been transmited and should no longer expect that written over the * stream. Particularly useful for the weston-debug protocol. * * @memberof weston_log_scope * @sa weston_log_ctx_add_log_scope, weston_compositor_add_log_scope, * weston_log_scope_destroy */ WL_EXPORT void weston_log_scope_complete(struct weston_log_scope *scope) { struct weston_log_subscription *sub; if (!scope) return; wl_list_for_each(sub, &scope->subscription_list, source_link) weston_log_subscription_complete(sub); } /** Write log data for a scope * * \param scope The debug scope to write for; may be NULL, in which case * nothing will be written. * \param[in] data Pointer to the data to write. * \param len Number of bytes to write. * * Writes the given data to all subscribed clients' streams. * * \memberof weston_log_scope */ WL_EXPORT void weston_log_scope_write(struct weston_log_scope *scope, const char *data, size_t len) { struct weston_log_subscription *sub; if (!scope) return; wl_list_for_each(sub, &scope->subscription_list, source_link) weston_log_subscription_write(sub, data, len); } /** Write a formatted string for a scope (varargs) * * \param scope The log scope to write for; may be NULL, in which case * nothing will be written. * \param fmt Printf-style format string. * \param ap Formatting arguments. * * Writes to formatted string to all subscribed clients' streams. * * The behavioral details for each stream are the same as for * weston_debug_stream_write(). * * \memberof weston_log_scope */ WL_EXPORT int weston_log_scope_vprintf(struct weston_log_scope *scope, const char *fmt, va_list ap) { static const char oom[] = "Out of memory"; char *str; int len = 0; if (!weston_log_scope_is_enabled(scope)) return len; len = vasprintf(&str, fmt, ap); if (len >= 0) { weston_log_scope_write(scope, str, len); free(str); } else { weston_log_scope_write(scope, oom, sizeof oom - 1); } return len; } /** Write a formatted string for a scope * * \param scope The log scope to write for; may be NULL, in which case * nothing will be written. * \param fmt Printf-style format string and arguments. * * Writes to formatted string to all subscribed clients' streams. * * The behavioral details for each stream are the same as for * weston_debug_stream_write(). * * \memberof weston_log_scope */ WL_EXPORT int weston_log_scope_printf(struct weston_log_scope *scope, const char *fmt, ...) { va_list ap; int len; va_start(ap, fmt); len = weston_log_scope_vprintf(scope, fmt, ap); va_end(ap); return len; } /** Write a formatted string for a subscription * * \param sub The subscription to write for; may be NULL, in which case * nothing will be written. * \param fmt Printf-style format string and arguments. * * Writes to formatted string to the stream that created the subscription. * * @ingroup log */ WL_EXPORT void weston_log_subscription_printf(struct weston_log_subscription *sub, const char *fmt, ...) { va_list ap; va_start(ap, fmt); weston_log_subscription_vprintf(sub, fmt, ap); va_end(ap); } /** Write debug scope name and current time into string * * \param[in] scope debug scope; may be NULL * \param[out] buf Buffer to store the string. * \param len Available size in the buffer in bytes. * \return \c buf * * Reads the current local wall-clock time and formats it into a string. * and append the debug scope name to it, if a scope is available. * The string is NUL-terminated, even if truncated. * * @memberof weston_log_scope */ WL_EXPORT char * weston_log_scope_timestamp(struct weston_log_scope *scope, char *buf, size_t len) { struct timeval tv; struct tm *bdt; char string[128]; size_t ret = 0; gettimeofday(&tv, NULL); bdt = localtime(&tv.tv_sec); if (bdt) ret = strftime(string, sizeof string, "%Y-%m-%d %H:%M:%S", bdt); if (ret > 0) { snprintf(buf, len, "[%s.%03ld][%s]", string, tv.tv_usec / 1000, (scope) ? scope->name : "no scope"); } else { snprintf(buf, len, "[?][%s]", (scope) ? scope->name : "no scope"); } return buf; } /** Subscribe to a scope * * Creates a subscription which is used to subscribe the \p subscriber * to the scope \c scope_name. * * If \c scope_name has already been created (using * weston_log_ctx_add_log_scope or weston_compositor_add_log_scope) the * subscription will take place immediately, otherwise we store the * subscription into a pending list. See also weston_log_ctx_add_log_scope() * and weston_compositor_add_log_scope() * * @param log_ctx the log context, used for accessing pending list * @param subscriber the subscriber, which has to be created before * @param scope_name the scope name. In case the scope is not created * we temporarily store the subscription in the pending list. * * @ingroup log */ WL_EXPORT void weston_log_subscribe(struct weston_log_context *log_ctx, struct weston_log_subscriber *subscriber, const char *scope_name) { assert(log_ctx); assert(subscriber); assert(scope_name); struct weston_log_scope *scope; scope = weston_log_get_scope(log_ctx, scope_name); if (scope) weston_log_subscription_create(subscriber, scope); else /* * if we don't have already as scope for it, add it to pending * subscription list */ weston_log_subscription_create_pending(subscriber, scope_name, log_ctx); } /** Iterate over all subscriptions in a scope * * @param scope the scope for which you want to iterate * @param sub_iter the iterator, use NULL to start from the 'head' * @returns the next subscription from the log scope * * This is (quite) useful when 'log_scope' and 'log_subscription' are opaque. Do note * that \c sub_iter needs to be NULL-initialized before calling this function. * */ WL_EXPORT struct weston_log_subscription * weston_log_subscription_iterate(struct weston_log_scope *scope, struct weston_log_subscription *sub_iter) { struct wl_list *list = &scope->subscription_list; struct wl_list *node; /* go to the next item in the list or if not set starts with the head */ if (sub_iter) node = sub_iter->source_link.next; else node = list->next; assert(node); assert(!sub_iter || node != &sub_iter->source_link); /* if we're at the end */ if (node == list) return NULL; return container_of(node, struct weston_log_subscription, source_link); }