From: Konrad Kuchciak Date: Mon, 30 Nov 2020 10:37:21 +0000 (+0100) Subject: Add new APIs for sending/receiving diagnostics events X-Git-Tag: submit/tizen/20210322.183501~3 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=76254d366ee6e7f4563979e9f2573a838f333e01;p=platform%2Fcore%2Fapi%2Fdiagnostics.git Add new APIs for sending/receiving diagnostics events Change-Id: Ib318c6a3f6fbdee1538caf2be22719af25a89509 Signed-off-by: Konrad Kuchciak --- diff --git a/CMakeLists.txt b/CMakeLists.txt index a7eb9c8..7d41126 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8) PROJECT(diagnostics LANGUAGES C) SET(target ${PROJECT_NAME}) -SET(dependency "glib-2.0 gio-unix-2.0 dlog dumpsys capi-system-info") +SET(dependency "glib-2.0 gio-unix-2.0 dlog dumpsys dumpsys-system bugreport capi-system-info bundle aul") # ADD_DEFINITIONS(-Wall -Werror -Wextra) # Options @@ -20,11 +20,11 @@ ADD_SUBDIRECTORY(src/library) # cmocka tasts and coverage option(ENABLE_COVERAGE "Measure the coverage and generate report" OFF) -option(ENABLE_TESTS "Run unit tests after build" ON) -if(ENABLE_TESTS OR ENABLE_COVERAGE) +option(ENABLE_TESTS "Run unit tests after build" OFF) +if(ENABLE_TESTS) FIND_PACKAGE(CMocka CONFIG REQUIRED) enable_testing() include(CTest) INCLUDE_DIRECTORIES(${LIBCMOCKA_INCLUDE_DIR}) ADD_SUBDIRECTORY(src/test) -endif(ENABLE_TESTS OR ENABLE_COVERAGE) +endif(ENABLE_TESTS) diff --git a/doc/diagnostics_doc.h b/doc/diagnostics_doc.h index 561feb1..f0a16a0 100644 --- a/doc/diagnostics_doc.h +++ b/doc/diagnostics_doc.h @@ -17,25 +17,40 @@ /** * @ingroup CAPI_SYSTEM_FRAMEWORK * @defgroup CAPI_SYSTEM_DIAGNOSTICS_MODULE Diagnostics - * @brief The @ref CAPI_SYSTEM_DIAGNOSTICS_MODULE API provides functions to receive crash reports and request logs from other apps. + * @brief The @ref CAPI_SYSTEM_DIAGNOSTICS_MODULE API provides functions to send and receive diagnostic events and data from other applications/services. * * @section CAPI_SYSTEM_DIAGNOSTICS_MODULE_HEADER Required Header * \#include * * @section CAPI_SYSTEM_DIAGNOSTICS_MODULE_OVERVIEW Overview - * This @ref CAPI_SYSTEM_DIAGNOSTICS_MODULE API allows applications to receive notification about newly created crash report, - * as well as request other apps to dump their logs in real time. Moreover, functions for reading crash and log contents are provided. + * This @ref CAPI_SYSTEM_DIAGNOSTICS_MODULE API allows applications to exchange diagnostic information. + * + * Client - a program using this API. In case of applications, client ID is set to the application ID, but services must set their ID explicitly using diagnostics_set_client_id(). + * + * Provider - a client that sends diagnostic events or serves diagnostic data. + * + * Subscriber - a client that subscribes to diagnostic events or requests diagnostic data. + * + * Event - simple signal sent to all subscribed clients. \n + * Event may be sent with additional data (bundle). \n + * There is a diagnostic context associated with the event, which holds information about sender (client ID), event name and event data. + * + * Data - any data, logs or files sent by providers via this API (dumpsys underneath). \n + * Subscribers may specify additional parameters when requesting diagnostic data. Based on this parameters, providers send appropriate data. + * + * Bugreport - diagnostic report created on demand with diagnostics_request_bugreport(). \n + * Bugreport may be process-specific (when pid argument is given) or system-specific (when pid <= 0). * * @section CAPI_SYSTEM_DIAGNOSTICS_MODULE_FEATURE Realted Features * * This API is related with the following feature:\n * - %http://tizen.org/feature/diagnostics\n * - * It is recommended to design feature related codes in your application for reliability.\n + * It is recommended to use features in your application for reliability. \n * - * You can check if a device supports the related features for this API by using @ref CAPI_SYSTEM_SYSTEM_INFO_MODULE,thereby controlling the procedure of your application.\n + * You can check if the device supports the related features for this API by using System Information, and control your application's actions accordingly. \n * - * To ensure your application is only running on the device with specific features, please define the features in your manifest file using the manifest editor in the SDK.\n + * To ensure your application is running only on devices with specific features, please define the features in your manifest file using the manifest editor in the SDK. * - * More details on featuring your application can be found from Feature Element. + * More details on using features in your application can be found in the feature element description. */ diff --git a/include/diagnostics.h b/include/diagnostics.h index 3db30e7..c97045a 100644 --- a/include/diagnostics.h +++ b/include/diagnostics.h @@ -19,6 +19,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -58,9 +59,9 @@ typedef enum { } diagnostics_error_e; /** - * @brief Notification callback fired when new bug report arrives. + * @brief Notification callback fired when new diagnostics event arrives. * @since_tizen 6.0 - * @remarks @a ctx should be released with diagnostics_destroy() + * @remarks @a ctx should be released with diagnostics_destroy(). * * @param[in] ctx Diagnostics context handle * @param[in] user_data The user data passed from the callback registration function @@ -68,8 +69,27 @@ typedef enum { typedef void(*diagnostics_notification_cb)(diagnostics_ctx_h ctx, void *user_data); /** - * @brief Sets the callback for bug report notification. + * @brief Request callback fired when someone requests diagnostics data. + * @since_tizen 6.5 + * @remarks @a data should be released with diagnostics_data_destroy(). + * + * @param[in] data Diagnostics data handle + * @param[in] params Array of request's parameters \n + * This array is owned by @a data, so it is available until @a data is released \n + * @a params are the same as passed to diagnostics_request_client_data() or diagnostics_get_data() + * @param[in] params_size Number of parameters + * @param[in] ctx Diagnostics context handling an event that the request is related to \n + * @a ctx is available when data has been requested with diagnostics_get_data() \n + * @a ctx is NULL when data has been requested with diagnostics_request_client_data() \n + * This parameter allows diagnostics client to find diagnostics data related to the specific event + * @param[in] user_data The user data passed from the callback registration function + */ +typedef void (*diagnostics_request_cb)(diagnostics_data_h data, char **params, int params_size, diagnostics_ctx_h ctx, void *user_data); + +/** + * @brief Sets the callback for diagnostics event notification. * @since_tizen 6.0 + * @remarks This function is dedicated to diagnostic information subscribers. * * @param[in] callback A callback function to set * @param[in] user_data The user data to be passed to the callback function @@ -79,32 +99,152 @@ typedef void(*diagnostics_notification_cb)(diagnostics_ctx_h ctx, void *user_dat * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid * @retval #DIAGNOSTICS_ERROR_RESOURCE_BUSY Callback already registered - * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occured + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred + * + * @code + * #include + * + * static void notification_handler(diagnostics_ctx_h ctx, void *user_data) + * { + * // Process diagnostics event + * + * diagnostics_destroy(ctx); + * } + * + * static GMainLoop *mainloop = NULL; + * + * int main(int argc, char **argv) + * { + * diagnostics_set_notification_cb(notification_handler, NULL); + * diagnostics_subscribe_event("ConnectionBroken", "org.tizen.someapp"); + * + * mainloop = g_main_loop_new(NULL, FALSE); + * g_main_loop_run(mainloop); + * } + * + * @endcode */ int diagnostics_set_notification_cb(diagnostics_notification_cb callback, void *user_data); /** - * @brief Unsets the callback for bug report notification. + * @brief Unsets the callback for diagnostics event notification. * @since_tizen 6.0 - * + * @remarks This function is dedicated to diagnostic information subscribers. \n + * It clears all the events added with diagnostics_subscribe_event(). + * * @return 0 on success, otherwise a negative error value * @retval #DIAGNOSTICS_ERROR_NONE Success * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported - * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occured + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred */ int diagnostics_unset_notification_cb(void); /** * @platform - * @brief Requests client to dump data. + * @brief Subscribes to an event sent by diagnostics client. + * @since_tizen 6.5 + * @privlevel platform + * @privilege + * @remarks This function is dedicated to diagnostic information subscribers. \n + * It is permitted only to an app signed by platform level certificates. + * + * @param[in] event_name Event name \n + * Its length is limited to 255 characters + null terminator + * @param[in] client_id The ID of the diagnostics client (event sender) \n + * Its length is limited to 255 characters + null terminator + * + * @return 0 on success, otherwise a negative error value + * @retval #DIAGNOSTICS_ERROR_NONE Success + * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported + * @retval #DIAGNOSTICS_ERROR_PERMISSION_DENIED Permission denied + * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred + * @retval #DIAGNOSTICS_ERROR_OUT_OF_MEMORY Out of memory + */ +int diagnostics_subscribe_event(const char *event_name, const char *client_id); + +/** + * @platform + * @brief Sets the callback for diagnostics data request. + * @since_tizen 6.5 + * @privlevel platform + * @privilege + * @remarks This function is dedicated to diagnostic information providers. \n + * It is permitted only to an app signed by platform level certificates. + * + * @param[in] callback A callback function to set + * @param[in] user_data The user data to be passed to the callback function + * + * @return 0 on success, otherwise a negative error value + * @retval #DIAGNOSTICS_ERROR_NONE Success + * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported + * @retval #DIAGNOSTICS_ERROR_PERMISSION_DENIED Permission denied + * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid or the caller ID not set (required for system services only) + * @retval #DIAGNOSTICS_ERROR_RESOURCE_BUSY Callback already registered + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred + * + * @code + * #include + * + * static void data_request_cb(diagnostics_data_h data, char **params, int params_size, diagnostics_ctx_h ctx, void *user_data) + * { + * const char *buf = "Some data"; + * size_t bytes_written; + * + * diagnostics_data_write(data, buf, sizeof(buf)/sizeof(char), &bytes_written); + * + * diagnostics_data_destroy(data); + * } + * + * static GMainLoop *mainloop = NULL; + * + * int main(int argc, char **argv) + * { + * diagnostics_data_h data; + * + * // This is required for services only, not applications + * diagnostics_set_client_id("diagnostics-client"); + * + * diagnostics_set_data_request_cb(data_request_cb, NULL); + * + * mainloop = g_main_loop_new(NULL, FALSE); + * g_main_loop_run(mainloop); + * } + * + * @endcode + */ +int diagnostics_set_data_request_cb(diagnostics_request_cb callback, void *user_data); + +/** + * @platform + * @brief Unsets the callback for diagnostics data request. + * @since_tizen 6.5 + * @privlevel platform + * @privilege + * @remarks This function is dedicated to diagnostic information providers. \n + * It is permitted only to an app signed by platform level certificates. + * + * @return 0 on success, otherwise a negative error value + * @retval #DIAGNOSTICS_ERROR_NONE Success + * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported + * @retval #DIAGNOSTICS_ERROR_PERMISSION_DENIED Permission denied + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred + */ +int diagnostics_unset_data_request_cb(void); + +/** + * @platform + * @brief Requests diagnostics client to dump data. * @since_tizen 6.0 * @privlevel platform * @privilege - * @remarks @a data should be released with diagnostics_data_destroy(). - * This function is permitted only to an app signed by platform level certificates. + * @remarks This function is dedicated to diagnostic information subscribers. \n + * It is permitted only to an app signed by platform level certificates. \n + * @a data should be released with diagnostics_data_destroy(). * - * @param[in] client_id An id of app or service to request - * @param[in] params Array of parameters + * @param[in] client_id An ID of application or service to request + * @param[in] params Array of parameters \n + * Refer to specific diagnostics client's documentation for available parameters * @param[in] params_size Number of parameters * @param[out] data Dumpsys data handle * @@ -113,86 +253,329 @@ int diagnostics_unset_notification_cb(void); * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported * @retval #DIAGNOSTICS_ERROR_PERMISSION_DENIED Permission denied * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid - * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occured + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred * @retval #DIAGNOSTICS_ERROR_OUT_OF_MEMORY Not enough memory to create data handle + * + * @code + * #include + * + * int main(int argc, char **argv) + * { + * diagnostics_data_h data; + * const char *params[] = {"foo", "bar", "baz"}; + * char buf[1000]; + * size_t bytes_read; + * + * diagnostics_request_client_data("diagnostics-client", params, sizeof(params)/sizeof(char*), &data); + * + * while (true) { + * diagnostics_data_read(data, buf, sizeof(buf)/sizeof(char), -1, &bytes_read); + * if (bytes_read == 0) // Reached EOF + * break; + * + * // Process the chunk of data + * } + * + * diagnostics_data_destroy(data); + * } + * + * @endcode */ int diagnostics_request_client_data(const char *client_id, const char **params, int params_size, diagnostics_data_h *data); +/** + * @platform + * @brief Requests diagnostics context's provider to dump data. + * @since_tizen 6.0 + * @privlevel platform + * @privilege + * @remarks This function is dedicated to diagnostic information subscribers. \n + * It is permitted only to an app signed by platform level certificates. \n + * @a data should be released with diagnostics_data_destroy(). \n + * The difference between this function and diagnostics_request_client_data() is that + * this function sends back the whole context to the context's provider to help it find the data specific to the generated event. + * + * @param[in] ctx Diagnostics context handle + * @param[in] params Array of parameters \n + * Refer to specific diagnostics client's documentation for available parameters + * @param[in] params_size Number of parameters + * @param[out] data Diagnostics data handle + * + * @return 0 on success, otherwise a negative error value + * @retval #DIAGNOSTICS_ERROR_NONE Success + * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported + * @retval #DIAGNOSTICS_ERROR_PERMISSION_DENIED Permission denied + * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred + * @retval #DIAGNOSTICS_ERROR_OUT_OF_MEMORY Not enough memory to create data handle + * + * @code + * + * void notification_handler(diagnostics_ctx_h ctx, void *user_data) + * { + * diagnostics_data_h data; + * const char *params[] = {"foo", "bar", "baz"}; + * + * // Request more data from the client that the event came from + * diagnostics_get_data(ctx, params, sizeof(params)/sizeof(char*), &data); + * + * // Read data + * + * diagnostics_data_destroy(data); + * diagnostics_destroy(ctx); + * } + * + * @endcode + */ +int diagnostics_get_data(diagnostics_ctx_h ctx, const char **params, int params_size, diagnostics_data_h *data); + +/** + * @brief Gets a file descriptor for diagnostics data. + * @since_tizen 6.5 + * @remarks This function provides a file descriptor to read/write diagnostic data in a custom way + * instead of using our diagnostics_data_read() and diagnostics_data_write() functions. \n + * Moreover, keep in mind that reading is allowed for diagnostic data subscribers and writing is allowed for providers. + * @param[in] data Diagnostics data handle + * @param[in,out] fd A file descriptor related to diagnostics data + * + * @return 0 on success, otherwise a negative error value + * @retval #DIAGNOSTICS_ERROR_NONE Success + * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported + * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid + */ +int diagnostics_data_get_fd(diagnostics_data_h data, int *fd); + /** * @brief Reads diagnostics data. * @since_tizen 6.0 - * @remarks @a data should be released with diagnostics_data_destroy(). - * This function is intended for use in loop until EOF is reached. - * EOF is when @a bytes_read == 0 and function returns #DIAGNOSTICS_ERROR_NONE. + * @remarks This function is dedicated to diagnostic information subscribers. \n + * It is intended for use in loop until EOF is reached. EOF is when @a bytes_read == 0 and function returns #DIAGNOSTICS_ERROR_NONE. + * * @param[in] data Diagnostics data handle * @param[in,out] buf Buffer to store read data \n - * Provided buffer must be large enough to contain @a count number of bytes + * Provided buffer must be large enough to contain a @a count number of bytes * @param[in] count Number of bytes to read * @param[in] timeout_ms Timeout [ms] for reading requested number of bytes (timeout_ms <= 0 means to wait forever) - * @param[out] bytes_read Real number of read bytes + * @param[out] bytes_read Real number of bytes read * * @return 0 on success, otherwise a negative error value * @retval #DIAGNOSTICS_ERROR_NONE Success * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid - * @retval #DIAGNOSTICS_ERROR_TIMED_OUT Timeout occured + * @retval #DIAGNOSTICS_ERROR_TIMED_OUT Timeout occurred * @retval #DIAGNOSTICS_ERROR_TRY_AGAIN Try again - * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occured while trying to read data, result is unspecified and *bytes_read is not updated + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred while trying to read data, result is unspecified and *bytes_read is not updated */ int diagnostics_data_read(diagnostics_data_h data, void *buf, size_t count, int timeout_ms, size_t *bytes_read); /** - * @brief Frees diagnostics data. - * @since_tizen 6.0 + * @brief Writes diagnostics data. + * @since_tizen 6.5 + * @remarks This function is dedicated to diagnostic information providers. \n + * Data is being written to the special file descriptor and then copied to the diagnostic information subscriber's endpoint. * * @param[in] data Diagnostics data handle + * @param[in] buf Buffer with data to write \n + * Provided buffer must be of size not less than a @a count number of bytes + * @param[in] count Number of bytes to write + * @param[out] bytes_written Real number of bytes written * * @return 0 on success, otherwise a negative error value * @retval #DIAGNOSTICS_ERROR_NONE Success * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid + * @retval #DIAGNOSTICS_ERROR_TRY_AGAIN Try again + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred while trying to write data, result is unspecified and *bytes_written is not updated */ -int diagnostics_data_destroy(diagnostics_data_h data); +int diagnostics_data_write(diagnostics_data_h data, const void *buf, size_t count, size_t *bytes_written); + +/** + * @platform + * @brief Sends diagnostics event. + * @since_tizen 6.5 + * @privlevel platform + * @privilege + * @remarks This function is dedicated to diagnostic information providers. \n + * It is permitted only to an app signed by platform level certificates. + * + * @param[in] event_name Diagnostics event name + * @param[in] event_data Event data or NULL. + * + * @return 0 on success, otherwise a negative error value + * @retval #DIAGNOSTICS_ERROR_NONE Success + * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported + * @retval #DIAGNOSTICS_ERROR_PERMISSION_DENIED Permission denied + * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid or the caller ID not set (required for system services only) + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred + * + * @code + * + * int main(int argc, char *argv[]) + * { + * bundle *event_data = bundle_create(); + * + * // This is required for services only, not applications + * diagnostics_set_client_id("diagnostics-client"); + * + * bundle_add_str(event_data, "somekey", "somevalue"); + * diagnostics_send_event("ConnectionBroken", event_data); + * + * bundle_free(event_data); + * } + * + * @endcode + */ +int diagnostics_send_event(const char *event_name, bundle *event_data); /** - * @brief Gets diagnostics context provider's id. + * @brief Gets diagnostics client ID (event sender). * @since_tizen 6.0 - * @remarks @a client_id should be released with free(). + * @remarks This function is dedicated to diagnostic information subscribers. \n + * @a client_id should be released with free(). * * @param[in] ctx Diagnostics context handle - * @param[out] client_id An id of the context provider + * @param[out] client_id ID of the diagnostics client * * @return 0 on success, otherwise a negative error value * @retval #DIAGNOSTICS_ERROR_NONE Success * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid + * + * @code + * + * void notification_handler(diagnostics_ctx_h ctx, void *user_data) + * { + * char *client_id; + * + * diagnostics_get_client_id(ctx, &client_id); + * + * // Process diagnostics event + * + * free(client_id); + * diagnostics_destroy(ctx); + * } + * + * @endcode */ int diagnostics_get_client_id(diagnostics_ctx_h ctx, char **client_id); +/** + * @brief Gets diagnostics event name. + * @since_tizen 6.5 + * @remarks This function is dedicated to diagnostic information subscribers. \n + * @a event_name should be released with free(). + * + * @param[in] ctx Diagnostics context handle + * @param[out] event_name Name of the diagnostics event + * + * @return 0 on success, otherwise a negative error value + * @retval #DIAGNOSTICS_ERROR_NONE Success + * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported + * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid + * + * @code + * + * void notification_handler(diagnostics_ctx_h ctx, void *user_data) + * { + * char *event_name; + * + * diagnostics_get_event_name(ctx, &event_name); + * + * // Process diagnostics event + * + * free(event_name); + * diagnostics_destroy(ctx); + * } + * + * @endcode + */ +int diagnostics_get_event_name(diagnostics_ctx_h ctx, char **event_name); + +/** + * @brief Gets diagnostics event data. + * @since_tizen 6.5 + * @remarks This function is dedicated to diagnostic information subscribers. \n + * @a event_data should be released with bundle_free(). + * + * @param[in] ctx Diagnostics context handle + * @param[out] event_data Data that came with the diagnostics event (may be NULL when no data has been provided) + * + * @return 0 on success, otherwise a negative error value + * @retval #DIAGNOSTICS_ERROR_NONE Success + * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Diagnostics feature is missing or the function has been called by application, not system service + * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid + * + * @code + * + * void notification_handler(diagnostics_ctx_h ctx, void *user_data) + * { + * bundle *event_data; + * + * diagnostics_get_event_data(ctx, &event_data); + * + * // Process diagnostics event + * + * bundle_free(event_data); + * diagnostics_destroy(ctx); + * } + * + * @endcode + */ +int diagnostics_get_event_data(diagnostics_ctx_h ctx, bundle **event_data); + +/** + * @brief Sets the diagnostics client ID. + * @since_tizen 6.5 + * @remarks This function is dedicated to diagnostic information providers. \n + * As services do not have any name associated with them, ID must be set explicitly with this function before calling any of the following functions: \n + * diagnostics_set_data_request_cb() \n + * diagnostics_send_event() \n + * diagnostics_request_bugreport() \n + * For applications, client ID is set to the application ID and cannot be changed. + * + * @param[in] client_id Diagnostics client ID to set. + * + * @return 0 on success, otherwise a negative error value + * @retval #DIAGNOSTICS_ERROR_NONE Success + * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported + * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid + * @retval #DIAGNOSTICS_ERROR_RESOURCE_BUSY Client ID has already been set, not possible to change + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred + */ +int diagnostics_set_client_id(const char *client_id); + /** * @platform - * @brief Gets report data. - * @since_tizen 6.0 + * @brief Requests bugreport creation. + * @since_tizen 6.5 * @privlevel platform - * @privilege - * @remarks @a data should be released with diagnostics_data_destroy(). - * This function is permitted only to an app signed by platform level certificates. + * @privilege http://tizen.org/privilege/bugreport.admin + * @remarks This function is dedicated to diagnostic information subscribers. \n + * To get bugreport's content, user must subscribe to 'BugreportCreated' diagnostics event, which is sent by crash-service after creating the report. * - * @param[in] ctx Diagnostics context handle - * @param[in] params Array of parameters \n - * Refer to context provider's documentation for available parameters - * @param[in] params_size Number of parameters - * @param[out] data Diagnostics data handle + * @param[in] pid ID of a process that should be livedumped. When @a pid <= 0, system-wide bugreport is created. * * @return 0 on success, otherwise a negative error value * @retval #DIAGNOSTICS_ERROR_NONE Success * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported * @retval #DIAGNOSTICS_ERROR_PERMISSION_DENIED Permission denied + * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid or the caller ID not set (required for system services only) + * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occurred + */ +int diagnostics_request_bugreport(int pid); + +/** + * @brief Frees diagnostics data. + * @since_tizen 6.0 + * + * @param[in] data Diagnostics data handle + * + * @return 0 on success, otherwise a negative error value + * @retval #DIAGNOSTICS_ERROR_NONE Success + * @retval #DIAGNOSTICS_ERROR_NOT_SUPPORTED Not supported * @retval #DIAGNOSTICS_ERROR_INVALID_PARAMETER Provided parameter is invalid - * @retval #DIAGNOSTICS_ERROR_IO_ERROR Internal error occured - * @retval #DIAGNOSTICS_ERROR_OUT_OF_MEMORY Not enough memory to create data handle */ -int diagnostics_get_data(diagnostics_ctx_h ctx, const char **params, int params_size, diagnostics_data_h *data); +int diagnostics_data_destroy(diagnostics_data_h data); /** * @brief Frees diagnostics context. diff --git a/packaging/diagnostics.spec b/packaging/diagnostics.spec index dfd4865..323205d 100644 --- a/packaging/diagnostics.spec +++ b/packaging/diagnostics.spec @@ -12,7 +12,11 @@ BuildRequires: pkgconfig(glib-2.0) BuildRequires: pkgconfig(gio-unix-2.0) BuildRequires: pkgconfig(dlog) BuildRequires: pkgconfig(dumpsys) +BuildRequires: pkgconfig(dumpsys-system) +BuildRequires: pkgconfig(bugreport) BuildRequires: pkgconfig(capi-system-info) +BuildRequires: pkgconfig(bundle) +BuildRequires: pkgconfig(aul) BuildRequires: glib2-devel %if 0%{?gcov:1} BuildRequires: lcov diff --git a/src/library/CMakeLists.txt b/src/library/CMakeLists.txt index 1fef613..5124018 100644 --- a/src/library/CMakeLists.txt +++ b/src/library/CMakeLists.txt @@ -4,7 +4,7 @@ AUX_SOURCE_DIRECTORY(. SOURCES) # Build ADD_LIBRARY(${target} SHARED ${SOURCES}) -SET_TARGET_PROPERTIES(${target} PROPERTIES COMPILE_FLAGS "${EXTRA_CFLAGS} -fPIE") +SET_TARGET_PROPERTIES(${target} PROPERTIES COMPILE_FLAGS "${EXTRA_CFLAGS} -fPIC") SET_TARGET_PROPERTIES(${target} PROPERTIES LINK_FLAGS "-pie") SET_TARGET_PROPERTIES(${target} PROPERTIES SOVERSION ${MAJORVER}) SET_TARGET_PROPERTIES(${target} PROPERTIES VERSION ${FULLVER}) @@ -12,7 +12,7 @@ TARGET_LINK_LIBRARIES(${target} ${pkgs_LDFLAGS}) ADD_LIBRARY(${target}_static STATIC ${SOURCES}) TARGET_INCLUDE_DIRECTORIES(${target}_static PRIVATE ${CMAKE_SOURCE_DIR}/src/library) -TARGET_COMPILE_OPTIONS(${target}_static BEFORE PRIVATE -include ../test/test_diagnostics_add_function_defs.h) +# TARGET_COMPILE_OPTIONS(${target}_static BEFORE PRIVATE -include ../test/test_diagnostics_add_function_defs.h) TARGET_COMPILE_DEFINITIONS(${target}_static PUBLIC -DUNIT_TEST) if(ENABLE_COVERAGE) TARGET_COMPILE_OPTIONS(${target}_static PUBLIC -g -fprofile-arcs -ftest-coverage) diff --git a/src/library/dbus.c b/src/library/dbus.c index e92f870..e6b5b68 100644 --- a/src/library/dbus.c +++ b/src/library/dbus.c @@ -19,8 +19,6 @@ #include "dbus.h" #include "log.h" -static int subscription_id = 0; - static GDBusConnection *dbus_connect() { GDBusConnection *conn; @@ -36,7 +34,10 @@ static GDBusConnection *dbus_connect() return conn; } -int dbus_subscribe(void (*signal_handler)(GDBusConnection *, +int dbus_subscribe(const char *interface_name, + const char *object_path, + const char *member_name, + void (*signal_handler)(GDBusConnection *, const gchar *, const gchar *, const gchar *, @@ -52,140 +53,70 @@ int dbus_subscribe(void (*signal_handler)(GDBusConnection *, ret = g_dbus_connection_signal_subscribe(conn, NULL, - NULL, - DBUS_MEMBER_CRASH, - NULL, + interface_name, + member_name, + object_path, NULL, G_DBUS_SIGNAL_FLAGS_NONE, signal_handler, NULL, NULL); if (ret == FALSE) { - _E("Unable to subscribe to crash signal"); + _E("g_dbus_connection_signal_subscribe() failed"); return -EIO; } - subscription_id = ret; - - return 0; + return ret; } -void dbus_unsubscribe(void) +void dbus_unsubscribe(int subscription_id) { GDBusConnection *conn = dbus_connect(); if (!conn) return; - if (subscription_id) + if (subscription_id > 0) g_dbus_connection_signal_unsubscribe(conn, subscription_id); - - subscription_id = 0; } -int dbus_get_file_from_report(const char *report_path, const int entry, int *fd) +int dbus_send_signal(const char *interface_name, + const char *object_path, + const char *signal_name, + GVariant *parameters) { - GDBusConnection *conn = dbus_connect(); - GDBusMessage *message = NULL; - GDBusMessage *reply = NULL; - GVariant *parameters = NULL; - GUnixFDList *fd_list = NULL; - GError *error = NULL; - int fd_out = -1; + GError *err = NULL; + gchar *parameters_str = parameters ? g_variant_print(parameters, TRUE) : NULL; int ret = -1; + GDBusConnection *conn = dbus_connect(); if (!conn) - goto finish; - - message = g_dbus_message_new_method_call(DBUS_BUS_NAME, - DBUS_OBJECT_PATH, - DBUS_INTERFACE_NAME, - DBUS_METHOD_GET_FILE); - parameters = g_variant_new("(si)", report_path, entry); - g_dbus_message_set_body(message, parameters); - - reply = g_dbus_connection_send_message_with_reply_sync(conn, - message, - G_DBUS_SEND_MESSAGE_FLAGS_NONE, - 10000, - NULL, - NULL, - &error); - - // _D("MESSAGE: \n%s", g_dbus_message_print(message, 4)); - // _D("REPLY: \n%s", g_dbus_message_print(reply, 4)); - - if (reply != NULL && g_dbus_message_get_message_type(reply) == G_DBUS_MESSAGE_TYPE_ERROR) - g_dbus_message_to_gerror(reply, &error); - - if (error != NULL) { - _E("Send message error: %s\n", error ? error->message : "unknown error"); - ret = error->code == G_DBUS_ERROR_ACCESS_DENIED ? -EACCES : -1; - goto finish; - } - - fd_list = g_dbus_message_get_unix_fd_list(reply); - if (!fd_list) { - _E("Method call get_file() haven't returned file descriptor"); + goto cleanup; + + ret = g_dbus_connection_emit_signal(conn, /* connection */ + NULL, /* destination_bus_name */ + object_path, /* object_path */ + interface_name, /* interface_name */ + signal_name, /* signal_name */ + parameters, /* parameters */ + &err); /* error */ + if (ret == FALSE) { + _E("g_dbus_connection_emit_signal() failed: %s", err->message); + g_error_free(err); ret = -1; - goto finish; + goto cleanup; } - fd_out = g_unix_fd_list_get(fd_list, 0, &error); - if (fd_out == -1 || error) { - _E("g_unix_fd_list_get() failed: %s", error ? error->message : ""); - ret = -1; - goto finish; - } + _D("Sent signal\n" + " interface_name: %s\n" + " object_path: %s\n" + " signal_name: %s\n" + " parameters: %s\n", + interface_name, object_path, signal_name, parameters_str); ret = 0; -finish: - *fd = fd_out; - if (parameters) - g_variant_unref(parameters); - if (message) - g_object_unref(message); - if (reply) - g_object_unref(reply); - if (error) - g_error_free(error); +cleanup: + g_free(parameters_str); return ret; } - -struct dbus_signal_s *dbus_signal_create(const gchar *sender_name, - const gchar *object_path, - const gchar *interface_name, - const gchar *signal_name, - GVariant *parameters) -{ - struct dbus_signal_s *signal; - - signal = calloc(1, sizeof(struct dbus_signal_s)); - if (!signal) { - _E("Unable to allocate memory"); - return NULL; - } - - signal->sender_name = g_strdup(sender_name); - signal->object_path = g_strdup(object_path); - signal->interface_name = g_strdup(interface_name); - signal->signal_name = g_strdup(signal_name); - signal->parameters = parameters; - g_variant_ref(signal->parameters); - - return signal; -} - -void dbus_signal_cleanup(struct dbus_signal_s *signal) -{ - if (!signal) - return; - - g_free(signal->sender_name); - g_free(signal->object_path); - g_free(signal->interface_name); - g_free(signal->signal_name); - g_variant_unref(signal->parameters); - free(signal); -} diff --git a/src/library/dbus.h b/src/library/dbus.h index 5b01f38..2940984 100644 --- a/src/library/dbus.h +++ b/src/library/dbus.h @@ -17,40 +17,27 @@ #ifndef __DBUS_H__ #define __DBUS_H__ -#define DBUS_MEMBER_CRASH "ProcessCrashedEx" -#define DBUS_SENDER_CRASH "org.tizen.system.crash" - -#define DBUS_BUS_NAME "org.tizen.system.diagnostics" -#define DBUS_OBJECT_PATH "/Org/Tizen/System/Diagnostics" -#define DBUS_INTERFACE_NAME DBUS_BUS_NAME -#define DBUS_METHOD_GET_FILE "get_file" +#define DBUS_EVENT_INTERFACE_NAME "org.tizen.system.diagnostics.event" +#define DBUS_EVENT_OBJECT_PATH "/Org/Tizen/System/Diagnostics/Event" #include -#include #include -struct dbus_signal_s { - gchar *sender_name; - gchar *object_path; - gchar *interface_name; - gchar *signal_name; - GVariant *parameters; -}; - -int dbus_subscribe(void (*signal_handler)(GDBusConnection *, +int dbus_subscribe(const char *interface_name, + const char *object_path, + const char *member_name, + void (*signal_handler)(GDBusConnection *, const gchar *, const gchar *, const gchar *, const gchar *, GVariant *, gpointer)); -void dbus_unsubscribe(void); -int dbus_get_file_from_report(const char *report_path, const int entry, int *fd); -struct dbus_signal_s *dbus_signal_create(const gchar *sender_name, - const gchar *object_path, - const gchar *interface_name, - const gchar *signal_name, - GVariant *parameters); -void dbus_signal_cleanup(struct dbus_signal_s *signal); +void dbus_unsubscribe(int subscription_id); + +int dbus_send_signal(const char *interface_name, + const char *object_path, + const char *signal_name, + GVariant *parameters); #endif /* __DBUS_H__ */ diff --git a/src/library/diagnostics.c b/src/library/diagnostics.c index e9dc515..203a42b 100644 --- a/src/library/diagnostics.c +++ b/src/library/diagnostics.c @@ -20,15 +20,19 @@ #include #include #include +#include +#include #include +#include +#include +#include #include "log.h" #include "signal.h" #include "dbus.h" #ifndef STATIC -#define STATIC - static +#define STATIC static #endif #define DIAGNOSTICS_FEATURE "http://tizen.org/feature/diagnostics" @@ -37,24 +41,69 @@ #define FEATURE_FALSE 0 #define FEATURE_TRUE 1 -STATIC struct _diagnostics_cb_info_s { - diagnostics_notification_cb cb; - void *user_data; -} cb_info = { - NULL, - NULL -}; +#define MAX_EVENT_NAME_LEN 255 +#define MAX_CLIENT_ID_LEN 255 +#define MAX_INT_DIGITS 11 struct _diagnostics_ctx_s { char *client_id; - struct dbus_signal_s *signal; - signal_type_e signal_type; + char *event_name; + bundle *event_data; }; struct _diagnostics_data_s { int fd; + char **params; + int params_size; +}; + +struct _diagnostics_event_s { + char *event_name; + char *client_id; + int subscription_id; +}; + +STATIC struct _diagnostics_notification_cb_info_s { + diagnostics_notification_cb cb; + void *user_data; + struct _diagnostics_event_s **events; + int events_count; +} notification_cb_info = { + NULL, + NULL, + NULL, + 0 }; +STATIC struct _dumpsys_cb_info_s { + diagnostics_request_cb cb; + void *user_data; + void *handler; +} dumpsys_cb_info = { + NULL, + NULL, + NULL +}; + +enum _ctx_params { + CTX_PARAM_MAGIC_TOKEN = -5, + CTX_PARAM_CLIENT_ID = -4, + CTX_PARAM_EVENT_NAME = -3, + CTX_PARAM_EVENT_DATA_RAW = -2, + CTX_PARAM_EVENT_DATA_LEN = -1, + CTX_PARAM_LEN = 5 +}; + +/* + * This magic_token is just a random string + * and is used to split user parameters and auto-added + * context parameters in dumpsys dump request + */ +STATIC char *magic_token = "4IJJSPh1CqITvhgm9KAg"; + +STATIC char app_id[MAX_CLIENT_ID_LEN + 1] = {0, }; +STATIC char service_name[MAX_CLIENT_ID_LEN + 1] = {0, }; + STATIC int diagnostics_feature = FEATURE_UNKNOWN; STATIC bool __is_feature_supported(void) @@ -72,11 +121,45 @@ STATIC bool __is_feature_supported(void) return (diagnostics_feature == FEATURE_TRUE ? true : false); } -STATIC struct _diagnostics_ctx_s *diagnostics_create(struct dbus_signal_s *signal) +STATIC bool is_app() { - RETV_IF(signal == NULL, NULL); + int ret; - struct _diagnostics_ctx_s *ctx; + if (app_id[0]) + return true; + + ret = aul_app_get_appid_bypid(getpid(), app_id, sizeof(app_id)); + if (ret) + return false; + + return true; +} + +STATIC char *get_client_id() +{ + if (is_app()) + return app_id; + + if (service_name[0]) + return service_name; + + _E("Client ID not set, first call diagnostics_set_client_id()"); + return NULL; +} + +STATIC void diagnostics_event_destroy(struct _diagnostics_event_s *event) +{ + free(event->event_name); + free(event->client_id); + free(event); +} + +STATIC struct _diagnostics_ctx_s *diagnostics_create(const char *client_id, const char *event_name, bundle *event_data) +{ + RETV_IF(client_id == NULL, NULL); + RETV_IF(event_name == NULL, NULL); + + struct _diagnostics_ctx_s *ctx = NULL; ctx = calloc(1, sizeof(struct _diagnostics_ctx_s)); if (!ctx) { @@ -84,22 +167,67 @@ STATIC struct _diagnostics_ctx_s *diagnostics_create(struct dbus_signal_s *signa return NULL; } - if (signal->signal_name && strcmp(signal->signal_name, DBUS_MEMBER_CRASH) == 0) { - ctx->client_id = DBUS_SENDER_CRASH; - ctx->signal_type = signal_is_valid_crash(signal) ? SIG_TYPE_CRASH : SIG_TYPE_INVALID; - } else { - _E("Unknown signal name"); - free(ctx); + ctx->client_id = strndup(client_id, MAX_CLIENT_ID_LEN); + if (!ctx->client_id) { + _E("Unable to allocate memory"); + diagnostics_destroy(ctx); + return NULL; + } + + ctx->event_name = strndup(event_name, MAX_EVENT_NAME_LEN); + if (!ctx->event_name) { + _E("Unable to allocate memory"); + diagnostics_destroy(ctx); + return NULL; + } + + ctx->event_data = event_data; + + return ctx; +} + +STATIC struct _diagnostics_ctx_s *diagnostics_create_from_params(char **params, int params_size) +{ + if (params == NULL || params_size < CTX_PARAM_LEN) + return NULL; + + struct _diagnostics_ctx_s *ctx = NULL; + bundle *event_data = NULL; + int event_data_len = 0; + + if (strncmp(params[params_size + CTX_PARAM_MAGIC_TOKEN], magic_token, strlen(magic_token)) != 0) + return NULL; + + errno = 0; + event_data_len = (int) strtol(params[params_size + CTX_PARAM_EVENT_DATA_LEN], NULL, 10); + if (event_data_len < 0 || errno != 0) { + _E("Unable to convert raw bundle size to int"); + return NULL; + } + + if (event_data_len > 0) { + event_data = bundle_decode((const bundle_raw *) params[params_size + CTX_PARAM_EVENT_DATA_RAW], event_data_len); + if (!event_data) { + _E("Unable to decode bundle"); + return NULL; + } + } + + ctx = diagnostics_create(params[params_size + CTX_PARAM_CLIENT_ID], params[params_size + CTX_PARAM_EVENT_NAME], event_data); + if (!ctx) { + _E("Unable to create ctx"); + bundle_free(event_data); return NULL; } - ctx->signal = signal; return ctx; } -STATIC struct _diagnostics_data_s *diagnostics_data_create(int fd) +STATIC struct _diagnostics_data_s *diagnostics_data_create(int fd, char **params, int params_size) { RETV_IF(fd < 0, NULL); + RETV_IF(params == NULL && params_size != 0, NULL); + RETV_IF(params_size < 0, NULL); struct _diagnostics_data_s *data; @@ -110,49 +238,93 @@ STATIC struct _diagnostics_data_s *diagnostics_data_create(int fd) } data->fd = fd; + + data->params = calloc(params_size, sizeof(char*)); + if (!data->params) { + _E("Unable to allocate memory"); + free(data); + return NULL; + } + + for (int i = 0; i < params_size; i++) { + data->params[i] = strdup(params[i]); + if (!data->params[i]) { + _E("Unable to allocate memory"); + diagnostics_data_destroy(data); + return NULL; + } + data->params_size = i + 1; + } + return data; } STATIC void signal_handler(GDBusConnection *connection, - const gchar *sender_name, - const gchar *object_path, - const gchar *interface_name, - const gchar *signal_name, - GVariant *parameters, - gpointer user_data) + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) { struct _diagnostics_ctx_s *ctx; - struct dbus_signal_s *signal; - - _D("signal_handler"); - _D("parameters: %s", g_variant_print(parameters, TRUE)); - - if (!cb_info.cb) { + struct _diagnostics_event_s *event = NULL; + bundle *event_data = NULL; + char *event_data_raw = NULL; + char *client_id = NULL; + int event_data_len; + + _D("Received signal\n" + " sender: %s\n" + " objpath: %s\n" + " interface: %s\n" + " member: %s\n" + " parameters: %s\n", + sender_name, object_path, interface_name, signal_name, g_variant_print(parameters, TRUE)); + + if (!notification_cb_info.cb) { _E("No user cb set"); return; } - _D("dbus_signal_create"); - signal = dbus_signal_create(sender_name, - object_path, - interface_name, - signal_name, - parameters); - if (!signal) { - _E("Unable to create signal structure"); - return; + g_variant_get(parameters, "(ssi)", &client_id, &event_data_raw, &event_data_len); + + /* Check if the event comes from the right client */ + for (int i = 0; i < notification_cb_info.events_count; i++) { + if (strncmp(notification_cb_info.events[i]->event_name, signal_name, MAX_EVENT_NAME_LEN) == 0 && + strncmp(notification_cb_info.events[i]->client_id, client_id, MAX_CLIENT_ID_LEN) == 0) { + + event = notification_cb_info.events[i]; + break; + } + } + + if (!event) { + _W("Cannot find (event_name, client_id) pair in subscribed events, ignoring event"); + goto cleanup; + } + + if (strlen(event_data_raw) > 0) { + event_data = bundle_decode((const bundle_raw *) event_data_raw, event_data_len); + if (!event_data) { + _E("Unable to decode bundle"); + goto cleanup; + } } - _D("diagnostics_create"); - ctx = diagnostics_create(signal); + ctx = diagnostics_create(event->client_id, event->event_name, event_data); if (!ctx) { _E("Unable to create diagnostics context"); - dbus_signal_cleanup(signal); - return; + bundle_free(event_data); + goto cleanup; } - _D("Fireing user cb!"); - cb_info.cb(ctx, cb_info.user_data); + _D("Fireing notification cb"); + notification_cb_info.cb(ctx, notification_cb_info.user_data); + +cleanup: + free(client_id); + free(event_data_raw); } int diagnostics_set_notification_cb(diagnostics_notification_cb callback, void *user_data) @@ -160,51 +332,276 @@ int diagnostics_set_notification_cb(diagnostics_notification_cb callback, void * RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); RETV_IF(callback == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + if (notification_cb_info.cb) { + _E("Notification callback has already been set"); + return DIAGNOSTICS_ERROR_RESOURCE_BUSY; + } + + notification_cb_info.cb = callback; + notification_cb_info.user_data = user_data; + + return DIAGNOSTICS_ERROR_NONE; +} + +int diagnostics_unset_notification_cb(void) +{ + RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); + + for (int i = 0; i < notification_cb_info.events_count; i++) { + dbus_unsubscribe(notification_cb_info.events[i]->subscription_id); + diagnostics_event_destroy(notification_cb_info.events[i]); + } + free(notification_cb_info.events); + + notification_cb_info.cb = NULL; + notification_cb_info.user_data = NULL; + notification_cb_info.events = NULL; + notification_cb_info.events_count = 0; + + return DIAGNOSTICS_ERROR_NONE; +} + +int diagnostics_send_event(const char *event_name, bundle *event_data) +{ + RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); + RETV_IF(event_name == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + + char *client_id; + GVariant *parameters = NULL; + bundle_raw *event_data_raw = NULL; + int event_data_len = 0; + int ret; + + client_id = get_client_id(); + if (!client_id) + return DIAGNOSTICS_ERROR_INVALID_PARAMETER; + + if (event_data) { + ret = bundle_encode(event_data, &event_data_raw, &event_data_len); + if (ret) { + _E("Unable to encode bundle: %d", ret); + return DIAGNOSTICS_ERROR_IO_ERROR; + } + } + + parameters = g_variant_new("(ssi)", client_id, event_data_raw ? (char *) event_data_raw : "", event_data_len); + free(event_data_raw); + + ret = dbus_send_signal(DBUS_EVENT_INTERFACE_NAME, DBUS_EVENT_OBJECT_PATH, event_name, parameters); + if (ret) { + _E("Unable to send dbus signal: %d", ret); + return DIAGNOSTICS_ERROR_IO_ERROR; + } + + return DIAGNOSTICS_ERROR_NONE; +} + +int diagnostics_subscribe_event(const char *event_name, const char *client_id) +{ + RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); + RETV_IF(event_name == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + RETV_IF(client_id == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + + struct _diagnostics_event_s *event; int ret; - _D("diagnostics_set_notification_cb()"); + /* Create new event structure */ + event = calloc(1, sizeof(struct _diagnostics_event_s)); + if (!event) { + _E("Unable to allocate memory"); + return DIAGNOSTICS_ERROR_OUT_OF_MEMORY; + } + + event->client_id = strndup(client_id, MAX_CLIENT_ID_LEN); + if (!event->client_id) { + _E("Unable to allocate memory"); + diagnostics_event_destroy(event); + return DIAGNOSTICS_ERROR_OUT_OF_MEMORY; + } + + event->event_name = strndup(event_name, MAX_EVENT_NAME_LEN); + if (!event->event_name) { + _E("Unable to allocate memory"); + diagnostics_event_destroy(event); + return DIAGNOSTICS_ERROR_OUT_OF_MEMORY; + } + + /* Add event to registered events */ + notification_cb_info.events = realloc(notification_cb_info.events, + (notification_cb_info.events_count + 1) * sizeof(struct _diagnostics_event_s *)); + if (!notification_cb_info.events) { + _E("Unable to allocate memory"); + diagnostics_event_destroy(event); + return DIAGNOSTICS_ERROR_OUT_OF_MEMORY; + } + + notification_cb_info.events[notification_cb_info.events_count++] = event; + + ret = dbus_subscribe(DBUS_EVENT_INTERFACE_NAME, DBUS_EVENT_OBJECT_PATH, event_name, signal_handler); + if (ret <= 0) { + _E("Unable to subscribe to dbus signal: %d", ret); + diagnostics_event_destroy(event); + notification_cb_info.events_count--; + return DIAGNOSTICS_ERROR_IO_ERROR; + } + + event->subscription_id = ret; + + return DIAGNOSTICS_ERROR_NONE; +} + +STATIC int dumpsys_handler(const int fd, const int argc, char **argv) +{ + struct _diagnostics_data_s *data; + struct _diagnostics_ctx_s *ctx = NULL; + + _D("dumpsys_handler"); + _D("argc: %d", argc); + for (int i = 0; i < argc; i++) + _D("argv[%d]: %s", i, argv[i]); - if (cb_info.cb) { - _E("Callback has already been set"); + if (!dumpsys_cb_info.cb) { + _E("No data request cb set"); + return -1; + } + + /* Create ctx if is shipped with dumpsys parameters (last 5) */ + ctx = diagnostics_create_from_params(argv, argc); + + data = diagnostics_data_create(fd, argv, ctx ? argc - CTX_PARAM_LEN : argc); + if (data == NULL) { + _E("Unable to create diagnostics_data"); + return -1; + } + + _D("Fireing dumpsys cb"); + dumpsys_cb_info.cb(data, data->params, data->params_size, ctx, dumpsys_cb_info.user_data); + + return 0; +} + +int diagnostics_set_data_request_cb(diagnostics_request_cb callback, void *user_data) +{ + RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); + RETV_IF(callback == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + + char *client_id; + int ret; + + if (dumpsys_cb_info.cb) { + _E("Data request callback has already been set"); return DIAGNOSTICS_ERROR_RESOURCE_BUSY; } - ret = dbus_subscribe(signal_handler); + client_id = get_client_id(); + if (!client_id) + return DIAGNOSTICS_ERROR_INVALID_PARAMETER; + + ret = dumpsys_system_register_dump_cb(dumpsys_handler, client_id, &dumpsys_cb_info.handler); if (ret) { - _E("Unable to subscribe to dbus signals"); + _E("dumpsys_system_register_dump_cb failed: %d", ret); return DIAGNOSTICS_ERROR_IO_ERROR; } - cb_info.cb = callback; - cb_info.user_data = user_data; + dumpsys_cb_info.cb = callback; + dumpsys_cb_info.user_data = user_data; return DIAGNOSTICS_ERROR_NONE; } -int diagnostics_unset_notification_cb(void) +int diagnostics_unset_data_request_cb(void) { RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); - _D("diagnostics_unset_notification_cb()"); + int ret = dumpsys_system_unregister_dump_cb(dumpsys_cb_info.handler); + if (ret) { + _E("dumpsys_system_unregister_dump_cb failed: %d", ret); + return DIAGNOSTICS_ERROR_IO_ERROR; + } - cb_info.cb = NULL; - cb_info.user_data = NULL; + dumpsys_cb_info.cb = NULL; + dumpsys_cb_info.user_data = NULL; + dumpsys_cb_info.handler = NULL; - dbus_unsubscribe(); return DIAGNOSTICS_ERROR_NONE; } -int diagnostics_request_client_data(const char *client_id, const char **params, int params_size, diagnostics_data_h *data) +STATIC int _diagnostics_request_client_data(const char *client_id, const char **params, int params_size, diagnostics_data_h *data, struct _diagnostics_ctx_s *ctx) { RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); RETV_IF(client_id == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + RETV_IF(params == NULL && params_size != 0, DIAGNOSTICS_ERROR_INVALID_PARAMETER); RETV_IF(params_size < 0, DIAGNOSTICS_ERROR_INVALID_PARAMETER); RETV_IF(data == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + const char *params_empty[] = {0}; + char **_params = NULL; + int _params_size; + bundle_raw *event_data_raw = NULL; + int event_data_len = 0; + char len_str[MAX_INT_DIGITS]; int fd = -1; int ret; - ret = dumpsys_dump(client_id, params_size, params, &fd); + if (ctx) { + /* We are going to append 5 more parameters to store ctx: + * + * magic_token + * ctx->client_id + * ctx->event_name + * event_data_raw + * event_data_len + * + */ + + _params_size = params_size + CTX_PARAM_LEN; + + _params = calloc(_params_size, sizeof(char *)); + if (!_params) { + _E("Unable to allocate memory"); + return DIAGNOSTICS_ERROR_OUT_OF_MEMORY; + } + + if (ctx->event_data) { + ret = bundle_encode(ctx->event_data, &event_data_raw, &event_data_len); + if (ret) { + _E("Unable to encode bundle: %d", ret); + free(_params); + return DIAGNOSTICS_ERROR_IO_ERROR; + } + } + + ret = snprintf(len_str, MAX_INT_DIGITS, "%d", event_data_len); + if (ret < 0) { + _E("snprintf() failed: %d", ret); + return DIAGNOSTICS_ERROR_IO_ERROR; + } + + for (int i = 0; i < params_size; i++) + _params[i] = (char *) params[i]; + + _params[_params_size + CTX_PARAM_MAGIC_TOKEN] = magic_token; + _params[_params_size + CTX_PARAM_CLIENT_ID] = ctx->client_id; + _params[_params_size + CTX_PARAM_EVENT_NAME] = ctx->event_name; + _params[_params_size + CTX_PARAM_EVENT_DATA_RAW] = event_data_raw ? (char *) event_data_raw : ""; + _params[_params_size + CTX_PARAM_EVENT_DATA_LEN] = len_str; + + _D("_params_size: %d", _params_size); + for (int i = 0; i < _params_size; i++) + _D("_params[%d]: %s", i, _params[i]); + + } else { + _params = (char **) (params ? params : params_empty); + _params_size = params_size; + } + + ret = dumpsys_dump(client_id, _params_size, (const char **) _params, &fd); + + if (ctx) { + free(event_data_raw); + free(_params); + } + if (ret != DIAGNOSTICS_ERROR_NONE) { _E("dumpsys_dump() failed: %d", ret); if (ret == TIZEN_ERROR_PERMISSION_DENIED) @@ -212,7 +609,7 @@ int diagnostics_request_client_data(const char *client_id, const char **params, return DIAGNOSTICS_ERROR_IO_ERROR; } - *data = diagnostics_data_create(fd); + *data = diagnostics_data_create(fd, (char **) params, params_size); if (*data == NULL) { _E("Unable to create diagnostics_data"); return DIAGNOSTICS_ERROR_OUT_OF_MEMORY; @@ -221,6 +618,29 @@ int diagnostics_request_client_data(const char *client_id, const char **params, return DIAGNOSTICS_ERROR_NONE; } +int diagnostics_request_client_data(const char *client_id, const char **params, int params_size, diagnostics_data_h *data) +{ + return _diagnostics_request_client_data(client_id, params, params_size, data, NULL); +} + +int diagnostics_get_data(diagnostics_ctx_h ctx, const char **params, int params_size, diagnostics_data_h *data) +{ + RETV_IF(ctx == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + + return _diagnostics_request_client_data(((struct _diagnostics_ctx_s *)ctx)->client_id, params, params_size, data, ctx); +} + +int diagnostics_data_get_fd(diagnostics_data_h data, int *fd) +{ + RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); + RETV_IF(data == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + RETV_IF(fd == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + + *fd = ((struct _diagnostics_data_s *)data)->fd; + + return DIAGNOSTICS_ERROR_NONE; +} + int diagnostics_data_read(diagnostics_data_h data, void *buf, size_t count, int timeout_ms, size_t *bytes_read) { RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); @@ -230,7 +650,7 @@ int diagnostics_data_read(diagnostics_data_h data, void *buf, size_t count, int struct pollfd poll_fd; int ready; - int ret; + int ret, e; poll_fd.fd = ((struct _diagnostics_data_s *)data)->fd; poll_fd.events = POLLIN; @@ -250,8 +670,9 @@ int diagnostics_data_read(diagnostics_data_h data, void *buf, size_t count, int if (poll_fd.revents & POLLIN) { ret = read(poll_fd.fd, buf, count); if (ret < 0) { + e = errno; _E("read() failed: %m, fd: %d", poll_fd.fd); - if (errno == EAGAIN) + if (e == EAGAIN) return DIAGNOSTICS_ERROR_TRY_AGAIN; return DIAGNOSTICS_ERROR_IO_ERROR; } @@ -266,91 +687,120 @@ int diagnostics_data_read(diagnostics_data_h data, void *buf, size_t count, int return DIAGNOSTICS_ERROR_IO_ERROR; } +int diagnostics_data_write(diagnostics_data_h data, const void *buf, size_t count, size_t *bytes_written) +{ + RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); + RETV_IF(data == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + RETV_IF(buf == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + RETV_IF(bytes_written == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + + struct _diagnostics_data_s *_data = (struct _diagnostics_data_s *) data; + int ret, e; + + ret = write(_data->fd, buf, count); + if (ret < 0) { + e = errno; + _E("write() failed: %m, fd: %d", _data->fd); + if (e == EAGAIN) + return DIAGNOSTICS_ERROR_TRY_AGAIN; + return DIAGNOSTICS_ERROR_IO_ERROR; + } + *bytes_written = ret; + + return DIAGNOSTICS_ERROR_NONE; +} + int diagnostics_get_client_id(diagnostics_ctx_h ctx, char **client_id) { RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); RETV_IF(ctx == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); RETV_IF(client_id == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); - *client_id = strdup(((struct _diagnostics_ctx_s *)ctx)->client_id); + *client_id = strndup(((struct _diagnostics_ctx_s *)ctx)->client_id, MAX_CLIENT_ID_LEN); return DIAGNOSTICS_ERROR_NONE; } -STATIC int get_report_path(diagnostics_ctx_h ctx, const char **path, size_t *len) +int diagnostics_get_event_name(diagnostics_ctx_h ctx, char **event_name) { + RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); RETV_IF(ctx == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); - RETV_IF(path == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); - RETV_IF(((struct _diagnostics_ctx_s *)ctx)->signal_type != SIG_TYPE_CRASH, DIAGNOSTICS_ERROR_NOT_SUPPORTED); - - GVariant *val; + RETV_IF(event_name == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); - val = g_variant_get_child_value(((struct _diagnostics_ctx_s *)ctx)->signal->parameters, SIG_CRASH_REPORTPATH); - *path = g_variant_get_string(val, (gsize *)len); + *event_name = strndup(((struct _diagnostics_ctx_s *)ctx)->event_name, MAX_EVENT_NAME_LEN); return DIAGNOSTICS_ERROR_NONE; } -int diagnostics_get_data(diagnostics_ctx_h ctx, const char **params, int params_size, diagnostics_data_h *data) +int diagnostics_get_event_data(diagnostics_ctx_h ctx, bundle **event_data) { RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); RETV_IF(ctx == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); - RETV_IF(params_size < 0, DIAGNOSTICS_ERROR_INVALID_PARAMETER); - RETV_IF(data == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); - RETV_IF(((struct _diagnostics_ctx_s *)ctx)->signal_type != SIG_TYPE_CRASH, DIAGNOSTICS_ERROR_NOT_SUPPORTED); + RETV_IF(event_data == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); - const char *report_path; - size_t len; - int report_id; - int fd; - int ret; + *event_data = bundle_dup(((struct _diagnostics_ctx_s *)ctx)->event_data); - /* - * TODO: Make this suitable for supporting other clients, not just crash-worker - */ + return DIAGNOSTICS_ERROR_NONE; +} - ret = get_report_path(((struct _diagnostics_ctx_s *)ctx), &report_path, &len); - if (ret) { - _E("diagnostics_get_report_path() failed: %d", ret); - return DIAGNOSTICS_ERROR_IO_ERROR; - } +int diagnostics_set_client_id(const char *client_id) +{ + RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); + RETV_IF(client_id == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); - if (params_size < 1) - return DIAGNOSTICS_ERROR_NOT_SUPPORTED; + int ret; - if (strcmp(params[0], "cs_full") == 0) - report_id = 0; - else if (strcmp(params[0], "cs_info_json") == 0) - report_id = 1; - else { - _E("Unsupported parameter: %s", params[0]); - return DIAGNOSTICS_ERROR_NOT_SUPPORTED; + if (is_app()) { + _E("Setting client ID is only possible for system services, not apps"); + return DIAGNOSTICS_ERROR_RESOURCE_BUSY; } - ret = dbus_get_file_from_report(report_path, report_id, &fd); - if (ret) { - _E("dbus_get_file_from_report() failed: %d", ret); - if (ret == -EACCES) - return DIAGNOSTICS_ERROR_PERMISSION_DENIED; - return DIAGNOSTICS_ERROR_IO_ERROR; + if (service_name[0]) { + _E("Client ID is already set to: '%s', it is not possible to change it during runtime", service_name); + return DIAGNOSTICS_ERROR_RESOURCE_BUSY; } - *data = diagnostics_data_create(fd); - if (*data == NULL) { - _E("Unable to create diagnostics_data"); - close(fd); - return DIAGNOSTICS_ERROR_OUT_OF_MEMORY; + ret = snprintf(service_name, MAX_CLIENT_ID_LEN, "%s", client_id); + if (ret < 0) { + _E("snprintf() failed: %d", ret); + return DIAGNOSTICS_ERROR_IO_ERROR; } return DIAGNOSTICS_ERROR_NONE; } -int diagnostics_destroy(diagnostics_ctx_h ctx) +int diagnostics_request_bugreport(int pid) { - RETV_IF(ctx == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + RETVM_IF(__is_feature_supported() == false, DIAGNOSTICS_ERROR_NOT_SUPPORTED, "Diagnostics feature is not supported"); + + char reason[256 + MAX_CLIENT_ID_LEN]; + char report_path[PATH_MAX]; + char *client_id; + int ret; + + client_id = get_client_id(); + if (!client_id) + return DIAGNOSTICS_ERROR_INVALID_PARAMETER; - dbus_signal_cleanup(((struct _diagnostics_ctx_s *)ctx)->signal); - free(ctx); + ret = snprintf(reason, sizeof(reason), + "Diagnostics API requested bugreport.\n" + "PID (requested): %d\n" + "Client ID (requester): %s\n", + pid, client_id); + + if (ret < 0) { + _E("Unable to set reason: %m"); + return DIAGNOSTICS_ERROR_IO_ERROR; + } + + /* + * TODO: livedump_pid() should be changed to return + * TIZEN_ERROR_PERMISSION_DENIED in case of missing permission + * TIZEN_ERROR_INVALID_PARAMETER in case of no such process + */ + ret = livedump_pid(pid > 0 ? pid : 0, reason, report_path, PATH_MAX); + if (ret == false) + return DIAGNOSTICS_ERROR_IO_ERROR; return DIAGNOSTICS_ERROR_NONE; } @@ -359,8 +809,28 @@ int diagnostics_data_destroy(diagnostics_data_h data) { RETV_IF(data == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); - close(((struct _diagnostics_data_s *)data)->fd); - free(data); + struct _diagnostics_data_s *_data = (struct _diagnostics_data_s *) data; + close(_data->fd); + + for (int i = 0; i < _data->params_size; i++) + free(_data->params[i]); + + free(_data->params); + free(_data); + + return DIAGNOSTICS_ERROR_NONE; +} + +int diagnostics_destroy(diagnostics_ctx_h ctx) +{ + RETV_IF(ctx == NULL, DIAGNOSTICS_ERROR_INVALID_PARAMETER); + + struct _diagnostics_ctx_s *_ctx = (struct _diagnostics_ctx_s *) ctx; + + free(_ctx->client_id); + free(_ctx->event_name); + bundle_free(_ctx->event_data); + free(_ctx); return DIAGNOSTICS_ERROR_NONE; } diff --git a/src/library/signal.c b/src/library/signal.c deleted file mode 100644 index 88e5d24..0000000 --- a/src/library/signal.c +++ /dev/null @@ -1,35 +0,0 @@ -/* -* Copyright (c) 2020 Samsung Electronics Co., Ltd. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -#include "signal.h" -#include "log.h" -#include "dbus.h" - -#include -#include - -int signal_is_valid_crash(struct dbus_signal_s *signal) -{ - GVariant *extra; - - RETV_IF(signal == NULL, FALSE); - RETV_IF(g_variant_n_children(signal->parameters) != SIG_CRASH_SIZE, FALSE); - - extra = g_variant_get_child_value(signal->parameters, SIG_CRASH_EX); - RETV_IF(g_variant_is_container(extra) == FALSE, FALSE); - - return TRUE; -} diff --git a/src/library/signal.h b/src/library/signal.h deleted file mode 100644 index d7a3a39..0000000 --- a/src/library/signal.h +++ /dev/null @@ -1,44 +0,0 @@ -/* -* Copyright (c) 2020 Samsung Electronics Co., Ltd. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -#ifndef __SIGNAL_H__ -#define __SIGNAL_H__ - -#define SIG_CRASH_SIZE 8 -#define SIG_CRASH_CMDNAME 0 -#define SIG_CRASH_CMDPATH 1 -#define SIG_CRASH_APPID 2 -#define SIG_CRASH_PKGID 3 -#define SIG_CRASH_REPORTPATH 4 -#define SIG_CRASH_PID 5 -#define SIG_CRASH_TID 6 -#define SIG_CRASH_EX 7 -#define SIG_CRASH_EX_SYSSIGNAL "sys.signal" -#define SIG_CRASH_EX_SYSTIDCOMM "sys.tid.comm" -#define SIG_CRASH_EX_ARMPC "arm.pc" -#define SIG_CRASH_EX_ARMLR "arm.lr" - -struct dbus_signal_s; - -typedef enum { - SIG_TYPE_CRASH, - /* SIG_TYPE_CRASH_LEGACY ? */ - SIG_TYPE_INVALID -} signal_type_e; - -int signal_is_valid_crash(struct dbus_signal_s *signal); - -#endif /* __SIGNAL_H__ */