Add crash manager API 74/212874/13
authorMateusz Moscicki <m.moscicki2@partner.samsung.com>
Thu, 25 Jul 2019 09:02:43 +0000 (11:02 +0200)
committerMateusz Moscicki <m.moscicki2@partner.samsung.com>
Thu, 26 Sep 2019 11:25:02 +0000 (13:25 +0200)
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

19 files changed:
CMakeLists.txt
packaging/crash-worker.spec
packaging/crash-worker_system-tests.spec
src/crash-manager/crash-manager.c
src/crash-manager/crash-manager.h
src/crash-service/CMakeLists.txt [new file with mode: 0644]
src/crash-service/crash-service.c [new file with mode: 0644]
src/crash-service/crash-service.conf [new file with mode: 0644]
src/crash-service/crash-service.pc.in [new file with mode: 0644]
src/crash-service/crash-service.service.m4 [new file with mode: 0644]
src/crash-service/libcrash-service.c [new file with mode: 0644]
src/crash-service/libcrash-service.h [new file with mode: 0644]
src/crash-service/org.tizen.system.crash.livedump.service [new file with mode: 0644]
src/shared/util.c
src/shared/util.h
tests/system/CMakeLists.txt
tests/system/libcrash-service/libcrash-service.sh.template [new file with mode: 0755]
tests/system/utils/CMakeLists.txt
tests/system/utils/libcrash-servicetest.c [new file with mode: 0644]

index 0053f4c..df5c180 100644 (file)
@@ -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)
 
index a597a4b..015bb07 100644 (file)
@@ -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
-
index 3fea883..320c900 100644 (file)
@@ -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
index be79b5b..dd2bab1 100644 (file)
@@ -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;
+}
+
index 244b37a..7c102bc 100644 (file)
@@ -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 (file)
index 0000000..37ad6b1
--- /dev/null
@@ -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 (file)
index 0000000..fa223f2
--- /dev/null
@@ -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 <gio/gio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#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[] =
+"<node>"
+" <interface name='org.tizen.system.crash.livedump'>"
+"  <method name='livedump_pid'>"
+"   <arg type='i' name='pid' direction='in'/>"
+"   <arg type='s' name='dump_reason' direction='in'/>"
+"   <arg type='s' name='report_path' direction='out'/>"
+"  </method>"
+" </interface>"
+"</node>";
+
+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 (file)
index 0000000..904d93a
--- /dev/null
@@ -0,0 +1,14 @@
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+       <policy user="root">
+               <allow own="org.tizen.system.crash.livedump"/>
+               <allow send_destination="org.tizen.system.crash.livedump"
+                      send_interface="org.tizen.system.crash.livedump"
+                      send_member="livedump_pid"/>
+       </policy>
+       <policy context="default">
+               <deny own="org.tizen.system.crash.livedump"/>
+               <deny send_destination="org.tizen.system.crash.livedump"/>
+       </policy>
+</busconfig>
diff --git a/src/crash-service/crash-service.pc.in b/src/crash-service/crash-service.pc.in
new file mode 100644 (file)
index 0000000..9d2d7c8
--- /dev/null
@@ -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 (file)
index 0000000..f080f40
--- /dev/null
@@ -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 (file)
index 0000000..25a36bc
--- /dev/null
@@ -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 <gio/gio.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..a2fa39c
--- /dev/null
@@ -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 <stdbool.h>
+#include <sys/types.h>
+
+/**
+ * @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 (file)
index 0000000..f1fc1fd
--- /dev/null
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name=org.tizen.system.crash.livedump
+Exec=/bin/false
+SystemdService=crash-service.service
index 1860bab..d5917e7 100644 (file)
@@ -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;
+}
+
 /**
  * @}
  */
index f3177e8..8e2a82f 100644 (file)
@@ -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
index 309e572..92796b6 100644 (file)
@@ -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 (executable)
index 0000000..611978a
--- /dev/null
@@ -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
index a486aab..5bd6710 100644 (file)
@@ -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 (file)
index 0000000..c83a2fc
--- /dev/null
@@ -0,0 +1,47 @@
+#include <libcrash-service.h>
+
+#include <linux/limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+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;
+}