From 0d395b1e977387028e9515944b9c4e5731f52f19 Mon Sep 17 00:00:00 2001 From: Mateusz Moscicki Date: Thu, 25 Jul 2019 11:02:43 +0200 Subject: [PATCH] Add crash manager API Privileged processes can send a D-Bus method call to create report of any living process. Signature is (iss): - INT (in) PID - STRING (in) dump reason - STRING (out) report path There is a library libcrashservice that sends the D-Bus method call. A function signature is: int livedump_pid(pid_t pid, const char *dump_reason, char *report_path); Change-Id: Id8528bdbaac517d4b5fc649821368e0ff020862f --- CMakeLists.txt | 7 + packaging/crash-worker.spec | 20 +- packaging/crash-worker_system-tests.spec | 5 + src/crash-manager/crash-manager.c | 38 +++ src/crash-manager/crash-manager.h | 1 + src/crash-service/CMakeLists.txt | 60 ++++ src/crash-service/crash-service.c | 335 +++++++++++++++++++++ src/crash-service/crash-service.conf | 14 + src/crash-service/crash-service.pc.in | 10 + src/crash-service/crash-service.service.m4 | 10 + src/crash-service/libcrash-service.c | 94 ++++++ src/crash-service/libcrash-service.h | 30 ++ .../org.tizen.system.crash.livedump.service | 4 + src/shared/util.c | 30 ++ src/shared/util.h | 2 + tests/system/CMakeLists.txt | 1 + .../libcrash-service/libcrash-service.sh.template | 47 +++ tests/system/utils/CMakeLists.txt | 11 + tests/system/utils/libcrash-servicetest.c | 47 +++ 19 files changed, 764 insertions(+), 2 deletions(-) create mode 100644 src/crash-service/CMakeLists.txt create mode 100644 src/crash-service/crash-service.c create mode 100644 src/crash-service/crash-service.conf create mode 100644 src/crash-service/crash-service.pc.in create mode 100644 src/crash-service/crash-service.service.m4 create mode 100644 src/crash-service/libcrash-service.c create mode 100644 src/crash-service/libcrash-service.h create mode 100644 src/crash-service/org.tizen.system.crash.livedump.service create mode 100755 tests/system/libcrash-service/libcrash-service.sh.template create mode 100644 tests/system/utils/libcrash-servicetest.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0053f4c..df5c180 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,5 +26,12 @@ IF("${LIVEDUMPER}" STREQUAL "ON") ADD_SUBDIRECTORY(src/livedumper) ENDIF() +IF("${CRASH_SERVICE}" STREQUAL "ON") + if (NOT "${LIVEDUMPER}" STREQUAL "ON") + message(FATAL_ERROR "Livedumper is required to build crash-service") + ENDIF() + ADD_SUBDIRECTORY(src/crash-service) +ENDIF() + ADD_SUBDIRECTORY(tests) diff --git a/packaging/crash-worker.spec b/packaging/crash-worker.spec index a597a4b..015bb07 100644 --- a/packaging/crash-worker.spec +++ b/packaging/crash-worker.spec @@ -4,11 +4,13 @@ %define _with_tests on %define _with_logdump on %define _with_livedumper on +%define _with_crashservice on %bcond_with doc %bcond_with sys_assert %bcond_with tests %bcond_with logdump %bcond_with livedumper +%bcond_with crashservice # NOTE: To disable coredump set DumpCore=0 in configuration file @@ -55,6 +57,7 @@ Requires: %{_bindir}/buxton2ctl crash-manager %package devel +Requires: crash-worker Summary: Crash-manager development package %description devel This package provides library and header files. @@ -140,6 +143,7 @@ export CFLAGS+=" -Werror" -DSYS_ASSERT=%{on_off sys_assert} \ -DLOG_DUMP=%{on_off logdump} \ -DLIVEDUMPER=%{on_off livedumper} \ + -DCRASH_SERVICE=%{on_off crashservice} \ -DUPGRADE_SCRIPT_PATH=%{upgrade_script_path} \ -DLOGGER=dlog \ -DVERSION=%{version} @@ -215,6 +219,14 @@ sed -i "/${pattern}/D" %{_sysconfdir}/ld.so.preload %{_libexecdir}/crash-notify-send %{_libdir}/libcrash-manager.so.* +%if %{with crashservice} +%attr(0750,system_fw,system_fw) %{_bindir}/crash-service +%attr(-,root,root) %{_unitdir}/crash-service.service +%attr(-,root,root) %{_sysconfdir}/dbus-1/system.d/crash-service.conf +%attr(-,root,root) %{_datadir}/dbus-1/system-services/org.tizen.system.crash.livedump.service +%{_libdir}/libcrash-service.so.* +%endif + %if %{with logdump} %dir %{crash_all_log} %{crash_dump_gen}/* @@ -235,7 +247,12 @@ sed -i "/${pattern}/D" %{_sysconfdir}/ld.so.preload %files devel %{_includedir}/crash-manager.h %{_libdir}/libcrash-manager.so -%{_datadir}/pkgconfig/*.pc +%{_datadir}/pkgconfig/crash-manager.pc +%if %{with crashservice} +%{_includedir}/libcrash-service.h +%{_libdir}/libcrash-service.so +%{_datadir}/pkgconfig/crash-service.pc +%endif %if %{with doc} %files doc @@ -263,4 +280,3 @@ sed -i "/${pattern}/D" %{_sysconfdir}/ld.so.preload %manifest %{name}.manifest %{_bindir}/livedumper %endif - diff --git a/packaging/crash-worker_system-tests.spec b/packaging/crash-worker_system-tests.spec index 3fea883..320c900 100644 --- a/packaging/crash-worker_system-tests.spec +++ b/packaging/crash-worker_system-tests.spec @@ -16,6 +16,9 @@ Source0: %{name}-%{version}.tar.gz Source1001: crash-worker_system-tests.manifest BuildRequires: pkgconfig(glib-2.0) BuildRequires: pkgconfig(rpm) +BuildRequires: pkgconfig(dlog) +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: pkgconfig(crash-service) BuildRequires: cmake Requires: diff @@ -75,10 +78,12 @@ cd tests/system %{_libdir}/crash-worker_system-tests/time_test/time_test.sh %{_libdir}/crash-worker_system-tests/utils/btee %{_libdir}/crash-worker_system-tests/utils/kenny +%{_libdir}/crash-worker_system-tests/utils/libcrash-servicetest %{_libdir}/crash-worker_system-tests/utils/minicore-utils.sh %{_libdir}/crash-worker_system-tests/wait_for_opt_usr/wait_for_opt_usr.sh %{_libdir}/crash-worker_system-tests/without_core/without_core.sh %{_libdir}/crash-worker_system-tests/output_param/output_param.sh +%{_libdir}/crash-worker_system-tests/libcrash-service/libcrash-service.sh %if %{with livedumper} %{_libdir}/crash-worker_system-tests/livedumper/livedumper.sh %endif diff --git a/src/crash-manager/crash-manager.c b/src/crash-manager/crash-manager.c index be79b5b..dd2bab1 100644 --- a/src/crash-manager/crash-manager.c +++ b/src/crash-manager/crash-manager.c @@ -1216,6 +1216,18 @@ static bool crash_manager_prepare(struct crash_info *cinfo) return true; } +static void write_dump_reason(const char *reason, const char *base_dir, const char *name) +{ + char *reason_name; + + if (asprintf(&reason_name, "%s.dump_reason", name) == -1) { + _E("Failed to asprintf for reason_name: %m"); + } else { + write_to_file(reason, base_dir, reason_name); + free(reason_name); + } +} + void crash_manager_free(struct crash_info *cinfo) { if (cinfo->prstatus_fd >= 0) @@ -1235,3 +1247,29 @@ bool crash_manager_direct(struct crash_info *cinfo) return run(cinfo); } +bool crash_manager_livedump_pid(pid_t pid, const char *dump_reason, char *report_path, size_t report_path_len) +{ + bool result = false; + struct crash_info cinfo; + crash_info_init(&cinfo); + + cinfo.livedump = true; + cinfo.pid_info = pid; + + if (!crash_manager_prepare(&cinfo)) + goto exit; + + write_dump_reason(dump_reason, cinfo.pfx, cinfo.name); + + if (!run(&cinfo)) + goto exit; + + strncpy(report_path, cinfo.result_path, report_path_len); + + result = true; +exit: + crash_manager_free(&cinfo); + _I("Exiting with %s", result ? "success" : "fail"); + return result; +} + diff --git a/src/crash-manager/crash-manager.h b/src/crash-manager/crash-manager.h index 244b37a..7c102bc 100644 --- a/src/crash-manager/crash-manager.h +++ b/src/crash-manager/crash-manager.h @@ -57,6 +57,7 @@ struct crash_info { }; bool crash_manager_direct(struct crash_info *cinfo); +bool crash_manager_livedump_pid(pid_t pid, const char *dump_reason, char *report_path, size_t report_path_len); void crash_info_init(struct crash_info *cinfo); void crash_manager_free(struct crash_info *cinfo); #endif diff --git a/src/crash-service/CMakeLists.txt b/src/crash-service/CMakeLists.txt new file mode 100644 index 0000000..37ad6b1 --- /dev/null +++ b/src/crash-service/CMakeLists.txt @@ -0,0 +1,60 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(crash-service C) + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/) + +SET(CRASH_SERVICE_SRCS + crash-service.c + ) + +INCLUDE(FindPkgConfig) + +pkg_check_modules(crash-service_pkgs REQUIRED + dlog + gio-2.0 + ) + +FOREACH(flag ${crash-service_pkgs_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -fPIE -Wno-unused-function -Wno-unused-const-variable") + +INCLUDE(${CMAKE_SOURCE_DIR}/cmake/ProcessM4.cmake) + +LINK_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/crash-manager) +ADD_EXECUTABLE(${PROJECT_NAME} ${CRASH_SERVICE_SRCS}) +ADD_DEPENDENCIES(${PROJECT_NAME} crash-manager) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${crash-service_pkgs_LDFLAGS} -pie -lrt -lcrash-manager) + +ADD_LIBRARY(libcrash-service SHARED libcrash-service.c) +SET_TARGET_PROPERTIES(libcrash-service PROPERTIES + SOVERSION 1 + PUBLIC_HEADER libcrash-service.h + OUTPUT_NAME crash-service) + +PROCESS_M4("${M4_DEFINES}" + "${CMAKE_CURRENT_SOURCE_DIR}/crash-service.service.m4" + "${CMAKE_CURRENT_SOURCE_DIR}/crash-service.service") + +CONFIGURE_FILE(crash-service.pc.in crash-service.pc @ONLY) + +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/org.tizen.system.crash.livedump.service + DESTINATION /usr/share/dbus-1/system-services) + +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/crash-service.conf + DESTINATION /etc/dbus-1/system.d) + +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/crash-service.service + DESTINATION /usr/lib/systemd/system + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) + +INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + +INSTALL(TARGETS libcrash-service LIBRARY DESTINATION /usr/lib/ + PUBLIC_HEADER DESTINATION /usr/include) + +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/crash-service.pc + DESTINATION share/pkgconfig) diff --git a/src/crash-service/crash-service.c b/src/crash-service/crash-service.c new file mode 100644 index 0000000..fa223f2 --- /dev/null +++ b/src/crash-service/crash-service.c @@ -0,0 +1,335 @@ +/* + * crash-service + * + * Copyright (c) 2019 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 +#include +#include +#include +#include +#include +#include + +#include "crash-manager/crash-manager.h" +#include "libcrash-service.h" +#include "shared/log.h" + +/* Dbus activation */ +#define CRASH_BUS_NAME "org.tizen.system.crash.livedump" +#define CRASH_OBJECT_PATH "/Org/Tizen/System/Crash/Livedump" + +#define TIMEOUT_INTERVAL_SEC 60 +#define TIMEOUT_LIVEDUMP_SEC 50 + +#define CS_ERROR 1 +#define CS_ERR_PARAM 1 +#define CS_ERR_TIMEOUT 2 +#define CS_ERR_READ 3 +#define CS_ERR_INTERNAL 4 + +static GMainLoop *loop; +static GMutex timeout_mutex; +static guint timeout_id; +static GDBusNodeInfo *introspection_data; +static const gchar introspection_xml[] = +"" +" " +" " +" " +" " +" " +" " +" " +""; + +void child_exit(int sig) +{ + wait(NULL); +} + +static int timeout_cb(gpointer data) +{ + _I("Time out!"); + g_main_loop_quit((GMainLoop *)data); + + return 0; +} + +static void add_timeout(void) +{ + g_mutex_lock(&timeout_mutex); + + if (timeout_id) + g_source_remove(timeout_id); + timeout_id = g_timeout_add_seconds(TIMEOUT_INTERVAL_SEC, timeout_cb, loop); + + g_mutex_unlock(&timeout_mutex); + _D("Add loop timeout (%d)", TIMEOUT_INTERVAL_SEC); +} + +static void remove_timeout(void) +{ + g_mutex_lock(&timeout_mutex); + + if (timeout_id) { + g_source_remove(timeout_id); + timeout_id = 0; + } + + g_mutex_unlock(&timeout_mutex); + _D("Remove loop timeout"); +} + +struct livedump_cb_data { + int read_fd; + GDBusMethodInvocation *invocation; + GSource* source; + pid_t child_pid; +}; + +static bool data_ready(int fd) +{ + fd_set rfd; + FD_ZERO(&rfd); + FD_SET(fd, &rfd); + struct timeval tv = {.tv_sec = 0, .tv_usec = 0}; + int retval = select(fd+1, &rfd, NULL, NULL, &tv); + if (retval == -1) + _E("select() error: %m"); + return retval == 1; +} + +static gboolean read_result_cb(gpointer data) +{ + struct livedump_cb_data *cb_data = (struct livedump_cb_data*)data; + char report_path[PATH_MAX]; + + if (!data_ready(cb_data->read_fd)) { + _I("Report is not ready after %d seconds.", TIMEOUT_LIVEDUMP_SEC); + g_dbus_method_invocation_return_error(cb_data->invocation, + CS_ERROR, + CS_ERR_TIMEOUT, + "Report is not ready"); + kill(cb_data->child_pid, SIGKILL); + goto end; + } else { + if (read(cb_data->read_fd, report_path, sizeof(report_path)) == -1) { + _E("Read from child error: %m"); + g_dbus_method_invocation_return_error(cb_data->invocation, + CS_ERROR, + CS_ERR_READ, + "Error while obtaining report path"); + goto end; + } + } + + g_dbus_method_invocation_return_value(cb_data->invocation, + g_variant_new("(s)", report_path)); +end: + close(cb_data->read_fd); + free(cb_data); + return G_SOURCE_REMOVE; +} + +static bool livedump_run(int write_fd, pid_t pid, const gchar *dump_reason) +{ + char report_path[PATH_MAX]; + + if (!crash_manager_livedump_pid(pid, dump_reason, report_path, sizeof(report_path))) { + _E("crash_manager_livedump_pid error"); + return false; + } + + if (write(write_fd, report_path, strlen(report_path) + 1) == -1) { + _E("Write report_path error: %m"); + close(write_fd); + return false; + } + + close(write_fd); + return true; +} + +static void livedump_pid_handler(GDBusMethodInvocation *invocation, pid_t pid, gchar *dump_reason) +{ + /* + * We want to run the livedump in different process so as not to + * block the main loop, to be able to handle many method calls + * in the same time, so we need a communication cannel to send + * back a result report path. + */ + int fds[2]; + if (pipe(fds) == -1) { + _E("Pipe error: %m"); + g_dbus_method_invocation_return_error(invocation, + CS_ERROR, + CS_ERR_INTERNAL, + "Internal error"); + return; + } + + struct livedump_cb_data *cb_data = (struct livedump_cb_data*)malloc(sizeof(struct livedump_cb_data)); + if (cb_data == NULL) { + _E("cb_data malloc() error: %m"); + exit(EXIT_FAILURE); + } + cb_data->read_fd = fds[0]; + cb_data->invocation = invocation; + + GSource *source = g_timeout_source_new_seconds(TIMEOUT_LIVEDUMP_SEC); + cb_data->source = source; + g_source_add_unix_fd(source, fds[0], G_IO_IN); + g_source_set_callback(source, read_result_cb, cb_data, NULL); + g_source_attach(source, NULL); + + pid_t child_pid = fork(); + if (child_pid == 0) { + close(fds[0]); + if (livedump_run(fds[1], pid, dump_reason)) + exit(EXIT_SUCCESS); + else + exit(EXIT_FAILURE); + } else if (child_pid > 0) { + cb_data->child_pid = child_pid; + close(fds[1]); + } else { + _E("fork() error: %m"); + g_dbus_method_invocation_return_error(invocation, + CS_ERROR, + CS_ERR_INTERNAL, + "Internal error"); + close(fds[0]); + close(fds[1]); + } +} + +static void method_call_handler(GDBusConnection *conn, + const gchar *sender, + const gchar *object_path, + const gchar *iface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + remove_timeout(); + + if (g_strcmp0(method_name, "livedump_pid") == 0) { + gchar *dump_reason; + const pid_t pid; + + if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(is)"))) { + g_variant_get(parameters, "(is)", &pid, &dump_reason); + livedump_pid_handler(invocation, pid, dump_reason); + } else { + _E("Parameters are not of the correct type (is)"); + g_dbus_method_invocation_return_error(invocation, + CS_ERROR, + CS_ERR_PARAM, + "Parameters are not of the correct type (is)"); + } + } + + add_timeout(); +} + +static const GDBusInterfaceVTable interface_vtable = { + method_call_handler, + NULL, + NULL +}; + +static void on_bus_acquired(GDBusConnection *conn, + const gchar *name, + gpointer user_data) +{ + guint registration_id; + + GError *error = NULL; + registration_id = g_dbus_connection_register_object(conn, + CRASH_OBJECT_PATH, + introspection_data->interfaces[0], + &interface_vtable, NULL, NULL, &error); + if (registration_id == 0 || error) { + _E("Failed to g_dbus_connection_register_object: %s", error ? error->message : ""); + if (error) + g_error_free(error); + } +} + +static void on_name_acquired(GDBusConnection *conn, + const gchar *name, + gpointer user_data) +{ + _D("Acquired the name %s on the system bus", name); +} + +static void on_name_lost(GDBusConnection *conn, + const gchar *name, + gpointer user_data) +{ + _D("Lost the name %s on the system bus", name); +} + +static bool dbus_init(void) +{ + GError *error = NULL; + + introspection_data = + g_dbus_node_info_new_for_xml(introspection_xml, NULL); + if (introspection_data == NULL) { + _E("Failed to init g_dbus_info_new_for_xml"); + return false; + } + + GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!conn || error) { + _E("Failed to get dbus: %s", error ? error->message : ""); + if (error) + g_error_free(error); + return false; + } + + g_bus_own_name(G_BUS_TYPE_SYSTEM, CRASH_BUS_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired, + on_name_acquired, on_name_lost, NULL, NULL); + + return true; +} + +int main(void) +{ + signal(SIGCHLD, child_exit); + loop = g_main_loop_new(NULL, false); + + if (!dbus_init()) { + g_main_loop_unref(loop); + return EXIT_FAILURE; + } + + g_mutex_init(&timeout_mutex); + add_timeout(); + + g_main_loop_run(loop); + + if (introspection_data) + g_dbus_node_info_unref(introspection_data); + g_mutex_clear(&timeout_mutex); + g_main_loop_unref(loop); + + return EXIT_SUCCESS; +} diff --git a/src/crash-service/crash-service.conf b/src/crash-service/crash-service.conf new file mode 100644 index 0000000..904d93a --- /dev/null +++ b/src/crash-service/crash-service.conf @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/src/crash-service/crash-service.pc.in b/src/crash-service/crash-service.pc.in new file mode 100644 index 0000000..9d2d7c8 --- /dev/null +++ b/src/crash-service/crash-service.pc.in @@ -0,0 +1,10 @@ +prefix=/usr +exec_prefix=${prefix} +includedir=${prefix}/include +libdir=${exec_prefix}/lib + +Name: crash-service +Description: The crash-service library +Version: @VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lcrash-service diff --git a/src/crash-service/crash-service.service.m4 b/src/crash-service/crash-service.service.m4 new file mode 100644 index 0000000..f080f40 --- /dev/null +++ b/src/crash-service/crash-service.service.m4 @@ -0,0 +1,10 @@ +[Unit] +Description=crash service + +[Service] +Type=dbus +BusName=org.tizen.system.crash.livedump +ExecStart=/usr/bin/crash-service +SmackProcessLabel=System +Nice=-5 +KillMode=mixed diff --git a/src/crash-service/libcrash-service.c b/src/crash-service/libcrash-service.c new file mode 100644 index 0000000..25a36bc --- /dev/null +++ b/src/crash-service/libcrash-service.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016-2019 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 +#include +#include +#include +#include + +#define LOG_TAG "LIBCRASH-SERVICE" + +#include "shared/log.h" + +#define BUS_NAME "org.tizen.system.crash.livedump" +#define OBJECT_PATH "/Org/Tizen/System/Crash/Livedump" +#define INTERFACE_NAME "org.tizen.system.crash.livedump" +#define METHOD_NAME "livedump_pid" + +static GDBusConnection* dbus_init(void) +{ + GError *error = NULL; + GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + + if (!conn || error) { + _E("Failed to get dbus: %s", error ? error->message : ""); + if (error) + g_error_free(error); + return NULL; + } + + return conn; +} + +bool livedump_pid(pid_t pid, const char *dump_reason, char *report_path, size_t report_path_len) +{ + bool res = true; + GDBusConnection *conn = dbus_init(); + + if (!conn) + return false; + + GVariant *parameters = g_variant_new("(is)", pid, dump_reason); + + GError *error = NULL; + GVariant *reply = g_dbus_connection_call_sync(conn, + BUS_NAME, + OBJECT_PATH, + INTERFACE_NAME, + METHOD_NAME, + parameters, + G_VARIANT_TYPE("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (!reply || error) { + _E("Error while calling livedump_pid via dbus (pid=%d, reason=%s): %s", pid, dump_reason, error ? error->message : ""); + if (error) + g_error_free(error); + res = false; + goto exit; + } + + if (!g_variant_is_of_type(reply, G_VARIANT_TYPE("(s)"))) { + _E("reply is not of the correct type (s)"); + res = false; + goto exit; + } + + gchar *reply_str; + g_variant_get(reply, "(&s)", &reply_str); + if (strlen(reply_str) <= (report_path_len + 1)) { + strncpy(report_path, reply_str, report_path_len); + } else { + _E("Report path (%s) is longer than report_path_len", reply_str); + res = false; + } +exit: + g_object_unref(conn); + return res; +} diff --git a/src/crash-service/libcrash-service.h b/src/crash-service/libcrash-service.h new file mode 100644 index 0000000..a2fa39c --- /dev/null +++ b/src/crash-service/libcrash-service.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 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 LIBCRASH_SERVICE_H +#define LIBCRASH_SERVICE_H +#include +#include + +/** + * @brief Sends a request to the crash-service to create livedump report of specific process. + * @param pid PID of process for which the report should be created. + * @param dump_reason the reason that should be included in the raport. + * @param report_path pointer to the buffer in which will be saved the report path. + * @param report_path_len lenght of buffer for the report path. + * @return true on success and false on error. + */ +extern bool livedump_pid(pid_t pid, const char *dump_reason, char *report_path, size_t report_path_len); +#endif /* LIBCRASH_SERVICE_H */ diff --git a/src/crash-service/org.tizen.system.crash.livedump.service b/src/crash-service/org.tizen.system.crash.livedump.service new file mode 100644 index 0000000..f1fc1fd --- /dev/null +++ b/src/crash-service/org.tizen.system.crash.livedump.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.tizen.system.crash.livedump +Exec=/bin/false +SystemdService=crash-service.service diff --git a/src/shared/util.c b/src/shared/util.c index 1860bab..d5917e7 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -584,6 +584,36 @@ bool file_exists(const char *path) return stat(path, &buf) == 0; } +bool write_to_file(const char *content, const char *base_dir, const char *file_name) +{ + char *path; + bool result = false; + + if (asprintf(&path, "%s/%s", base_dir, file_name) == -1) { + _E("Failed to asprintf for path: %m"); + return false; + } + + int fd = open(path, O_WRONLY | O_CREAT, 0600); + + if (fd < 0) { + _E("Failed to open %s: %m", path); + goto exit; + } + + if (dprintf(fd, "%s", content) < 0) { + _E("Failed to write to file %s: %m", path); + close(fd); + goto exit; + } + + close(fd); + result = true; +exit: + free(path); + return result; +} + /** * @} */ diff --git a/src/shared/util.h b/src/shared/util.h index f3177e8..8e2a82f 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -70,6 +70,8 @@ bool string_ends_with(const char *string, const char *suffix); bool file_exists(const char *path); +bool write_to_file(const char *content, const char *base_dir, const char *file_name); + #ifdef __cplusplus } #endif diff --git a/tests/system/CMakeLists.txt b/tests/system/CMakeLists.txt index 309e572..92796b6 100644 --- a/tests/system/CMakeLists.txt +++ b/tests/system/CMakeLists.txt @@ -40,6 +40,7 @@ configure_test("livedumper") configure_test("extra_script") configure_test("dbus_notify") configure_test("output_param") +configure_test("libcrash-service") get_property(TESTS_LIST GLOBAL PROPERTY TMP_TESTS_LIST) diff --git a/tests/system/libcrash-service/libcrash-service.sh.template b/tests/system/libcrash-service/libcrash-service.sh.template new file mode 100755 index 0000000..611978a --- /dev/null +++ b/tests/system/libcrash-service/libcrash-service.sh.template @@ -0,0 +1,47 @@ +#!/bin/bash + +# Test --output parameter + +if [ -z "${CRASH_WORKER_SYSTEM_TESTS}" ]; then + CRASH_WORKER_SYSTEM_TESTS="@CRASH_SYSTEM_TESTS_PATH@" +fi + +. ${CRASH_WORKER_SYSTEM_TESTS}/utils/minicore-utils.sh + +save_core_pattern +trap restore_core_pattern 0 + +echo "|/usr/bin/crash-manager -p %p -u %u -g %g -s %s -t %t" > /proc/sys/kernel/core_pattern + +{ + ${CRASH_WORKER_SYSTEM_TESTS}/utils/kenny 10 & + KENNY_PID=$! + sleep 1 +} 1> /dev/null 2>&1 + +sleep 2 + +rm -rf ${LIVE_DUMP_PATH}/* +REASON="some reason" +${CRASH_WORKER_SYSTEM_TESTS}/utils/libcrash-servicetest -r "${REASON}" ${KENNY_PID} + +wait_for_file ${LIVE_DUMP_PATH}/kenny*zip + +kill -9 ${KENNY_PID} + +trap popd 0 + +pushd ${LIVE_DUMP_PATH} + +unzip kenny*zip +cd kenny* + +if [ ! -f *dump_reason ]; then + fail "dump_reason file doesn't exist" +fi + +if [ "$(cat *dump_reason)" != "${REASON}" ]; then + fail "Dump reason didn't match" +fi + +exit_ok diff --git a/tests/system/utils/CMakeLists.txt b/tests/system/utils/CMakeLists.txt index a486aab..5bd6710 100644 --- a/tests/system/utils/CMakeLists.txt +++ b/tests/system/utils/CMakeLists.txt @@ -8,7 +8,18 @@ find_package(Threads) target_link_libraries(kenny ${CMAKE_THREAD_LIBS_INIT}) set_target_properties(kenny PROPERTIES COMPILE_FLAGS "-std=c++11 -ggdb -O0") +add_executable(libcrash-servicetest libcrash-servicetest.c) + +INCLUDE(FindPkgConfig) +pkg_check_modules(helper_pkgs REQUIRED + crash-service + gio-2.0 + dlog) + +TARGET_LINK_LIBRARIES(libcrash-servicetest crash-service ${helper_pkgs_LDFLAGS}) + install(TARGETS kenny DESTINATION ${CRASH_SYSTEM_TESTS_PATH}/utils) install(TARGETS btee DESTINATION ${CRASH_SYSTEM_TESTS_PATH}/utils) install(FILES minicore-utils.sh DESTINATION ${CRASH_SYSTEM_TESTS_PATH}/utils) +install(TARGETS libcrash-servicetest DESTINATION ${CRASH_SYSTEM_TESTS_PATH}/utils) diff --git a/tests/system/utils/libcrash-servicetest.c b/tests/system/utils/libcrash-servicetest.c new file mode 100644 index 0000000..c83a2fc --- /dev/null +++ b/tests/system/utils/libcrash-servicetest.c @@ -0,0 +1,47 @@ +#include + +#include +#include +#include +#include + +void help(char *argv_0) +{ + printf("Usage: %s [-r dump_reason] pid\n", argv_0); +} + +int main(int argc, char *argv[]) +{ + int opt; + const char *dump_reason = NULL; + + while ((opt = getopt(argc, argv, "r:")) != -1) { + switch (opt) { + case 'r': + dump_reason = optarg; + break; + default: + help(argv[0]); + exit(EXIT_FAILURE); + } + } + + if (dump_reason == NULL) + dump_reason = "no reason"; + + if (optind >= argc) { + help(argv[0]); + exit(EXIT_FAILURE); + } + pid_t pid = strtol(argv[optind], NULL, 10); + if (pid == 0) { + printf("ERROR: pid must be a number\n"); + help(argv[0]); + return EXIT_FAILURE; + } + + char BUFF[PATH_MAX]; + bool res = livedump_pid(pid, dump_reason, BUFF, PATH_MAX); + printf("res: %s\nreport_path: %s\n", res ? "true" : "false", BUFF); + return res ? EXIT_SUCCESS : EXIT_FAILURE; +} -- 2.7.4